1package mcp
2
3import (
4 "encoding/json"
5 "fmt"
6)
7
8// ClientRequest types
9var _ ClientRequest = &PingRequest{}
10var _ ClientRequest = &InitializeRequest{}
11var _ ClientRequest = &CompleteRequest{}
12var _ ClientRequest = &SetLevelRequest{}
13var _ ClientRequest = &GetPromptRequest{}
14var _ ClientRequest = &ListPromptsRequest{}
15var _ ClientRequest = &ListResourcesRequest{}
16var _ ClientRequest = &ReadResourceRequest{}
17var _ ClientRequest = &SubscribeRequest{}
18var _ ClientRequest = &UnsubscribeRequest{}
19var _ ClientRequest = &CallToolRequest{}
20var _ ClientRequest = &ListToolsRequest{}
21
22// ClientNotification types
23var _ ClientNotification = &CancelledNotification{}
24var _ ClientNotification = &ProgressNotification{}
25var _ ClientNotification = &InitializedNotification{}
26var _ ClientNotification = &RootsListChangedNotification{}
27
28// ClientResult types
29var _ ClientResult = &EmptyResult{}
30var _ ClientResult = &CreateMessageResult{}
31var _ ClientResult = &ListRootsResult{}
32
33// ServerRequest types
34var _ ServerRequest = &PingRequest{}
35var _ ServerRequest = &CreateMessageRequest{}
36var _ ServerRequest = &ListRootsRequest{}
37
38// ServerNotification types
39var _ ServerNotification = &CancelledNotification{}
40var _ ServerNotification = &ProgressNotification{}
41var _ ServerNotification = &LoggingMessageNotification{}
42var _ ServerNotification = &ResourceUpdatedNotification{}
43var _ ServerNotification = &ResourceListChangedNotification{}
44var _ ServerNotification = &ToolListChangedNotification{}
45var _ ServerNotification = &PromptListChangedNotification{}
46
47// ServerResult types
48var _ ServerResult = &EmptyResult{}
49var _ ServerResult = &InitializeResult{}
50var _ ServerResult = &CompleteResult{}
51var _ ServerResult = &GetPromptResult{}
52var _ ServerResult = &ListPromptsResult{}
53var _ ServerResult = &ListResourcesResult{}
54var _ ServerResult = &ReadResourceResult{}
55var _ ServerResult = &CallToolResult{}
56var _ ServerResult = &ListToolsResult{}
57
58// Helper functions for type assertions
59
60// asType attempts to cast the given interface to the given type
61func asType[T any](content interface{}) (*T, bool) {
62 tc, ok := content.(T)
63 if !ok {
64 return nil, false
65 }
66 return &tc, true
67}
68
69// AsTextContent attempts to cast the given interface to TextContent
70func AsTextContent(content interface{}) (*TextContent, bool) {
71 return asType[TextContent](content)
72}
73
74// AsImageContent attempts to cast the given interface to ImageContent
75func AsImageContent(content interface{}) (*ImageContent, bool) {
76 return asType[ImageContent](content)
77}
78
79// AsEmbeddedResource attempts to cast the given interface to EmbeddedResource
80func AsEmbeddedResource(content interface{}) (*EmbeddedResource, bool) {
81 return asType[EmbeddedResource](content)
82}
83
84// AsTextResourceContents attempts to cast the given interface to TextResourceContents
85func AsTextResourceContents(content interface{}) (*TextResourceContents, bool) {
86 return asType[TextResourceContents](content)
87}
88
89// AsBlobResourceContents attempts to cast the given interface to BlobResourceContents
90func AsBlobResourceContents(content interface{}) (*BlobResourceContents, bool) {
91 return asType[BlobResourceContents](content)
92}
93
94// Helper function for JSON-RPC
95
96// NewJSONRPCResponse creates a new JSONRPCResponse with the given id and result
97func NewJSONRPCResponse(id RequestId, result Result) JSONRPCResponse {
98 return JSONRPCResponse{
99 JSONRPC: JSONRPC_VERSION,
100 ID: id,
101 Result: result,
102 }
103}
104
105// NewJSONRPCError creates a new JSONRPCResponse with the given id, code, and message
106func NewJSONRPCError(
107 id RequestId,
108 code int,
109 message string,
110 data interface{},
111) JSONRPCError {
112 return JSONRPCError{
113 JSONRPC: JSONRPC_VERSION,
114 ID: id,
115 Error: struct {
116 Code int `json:"code"`
117 Message string `json:"message"`
118 Data interface{} `json:"data,omitempty"`
119 }{
120 Code: code,
121 Message: message,
122 Data: data,
123 },
124 }
125}
126
127// Helper function for creating a progress notification
128func NewProgressNotification(
129 token ProgressToken,
130 progress float64,
131 total *float64,
132) ProgressNotification {
133 notification := ProgressNotification{
134 Notification: Notification{
135 Method: "notifications/progress",
136 },
137 Params: struct {
138 ProgressToken ProgressToken `json:"progressToken"`
139 Progress float64 `json:"progress"`
140 Total float64 `json:"total,omitempty"`
141 }{
142 ProgressToken: token,
143 Progress: progress,
144 },
145 }
146 if total != nil {
147 notification.Params.Total = *total
148 }
149 return notification
150}
151
152// Helper function for creating a logging message notification
153func NewLoggingMessageNotification(
154 level LoggingLevel,
155 logger string,
156 data interface{},
157) LoggingMessageNotification {
158 return LoggingMessageNotification{
159 Notification: Notification{
160 Method: "notifications/message",
161 },
162 Params: struct {
163 Level LoggingLevel `json:"level"`
164 Logger string `json:"logger,omitempty"`
165 Data interface{} `json:"data"`
166 }{
167 Level: level,
168 Logger: logger,
169 Data: data,
170 },
171 }
172}
173
174// Helper function to create a new PromptMessage
175func NewPromptMessage(role Role, content Content) PromptMessage {
176 return PromptMessage{
177 Role: role,
178 Content: content,
179 }
180}
181
182// Helper function to create a new TextContent
183func NewTextContent(text string) TextContent {
184 return TextContent{
185 Type: "text",
186 Text: text,
187 }
188}
189
190// Helper function to create a new ImageContent
191func NewImageContent(data, mimeType string) ImageContent {
192 return ImageContent{
193 Type: "image",
194 Data: data,
195 MIMEType: mimeType,
196 }
197}
198
199// Helper function to create a new EmbeddedResource
200func NewEmbeddedResource(resource ResourceContents) EmbeddedResource {
201 return EmbeddedResource{
202 Type: "resource",
203 Resource: resource,
204 }
205}
206
207// NewToolResultText creates a new CallToolResult with a text content
208func NewToolResultText(text string) *CallToolResult {
209 return &CallToolResult{
210 Content: []Content{
211 TextContent{
212 Type: "text",
213 Text: text,
214 },
215 },
216 }
217}
218
219// NewToolResultImage creates a new CallToolResult with both text and image content
220func NewToolResultImage(text, imageData, mimeType string) *CallToolResult {
221 return &CallToolResult{
222 Content: []Content{
223 TextContent{
224 Type: "text",
225 Text: text,
226 },
227 ImageContent{
228 Type: "image",
229 Data: imageData,
230 MIMEType: mimeType,
231 },
232 },
233 }
234}
235
236// NewToolResultResource creates a new CallToolResult with an embedded resource
237func NewToolResultResource(
238 text string,
239 resource ResourceContents,
240) *CallToolResult {
241 return &CallToolResult{
242 Content: []Content{
243 TextContent{
244 Type: "text",
245 Text: text,
246 },
247 EmbeddedResource{
248 Type: "resource",
249 Resource: resource,
250 },
251 },
252 }
253}
254
255// NewListResourcesResult creates a new ListResourcesResult
256func NewListResourcesResult(
257 resources []Resource,
258 nextCursor Cursor,
259) *ListResourcesResult {
260 return &ListResourcesResult{
261 PaginatedResult: PaginatedResult{
262 NextCursor: nextCursor,
263 },
264 Resources: resources,
265 }
266}
267
268// NewListResourceTemplatesResult creates a new ListResourceTemplatesResult
269func NewListResourceTemplatesResult(
270 templates []ResourceTemplate,
271 nextCursor Cursor,
272) *ListResourceTemplatesResult {
273 return &ListResourceTemplatesResult{
274 PaginatedResult: PaginatedResult{
275 NextCursor: nextCursor,
276 },
277 ResourceTemplates: templates,
278 }
279}
280
281// NewReadResourceResult creates a new ReadResourceResult with text content
282func NewReadResourceResult(text string) *ReadResourceResult {
283 return &ReadResourceResult{
284 Contents: []ResourceContents{
285 TextResourceContents{
286 Text: text,
287 },
288 },
289 }
290}
291
292// NewListPromptsResult creates a new ListPromptsResult
293func NewListPromptsResult(
294 prompts []Prompt,
295 nextCursor Cursor,
296) *ListPromptsResult {
297 return &ListPromptsResult{
298 PaginatedResult: PaginatedResult{
299 NextCursor: nextCursor,
300 },
301 Prompts: prompts,
302 }
303}
304
305// NewGetPromptResult creates a new GetPromptResult
306func NewGetPromptResult(
307 description string,
308 messages []PromptMessage,
309) *GetPromptResult {
310 return &GetPromptResult{
311 Description: description,
312 Messages: messages,
313 }
314}
315
316// NewListToolsResult creates a new ListToolsResult
317func NewListToolsResult(tools []Tool, nextCursor Cursor) *ListToolsResult {
318 return &ListToolsResult{
319 PaginatedResult: PaginatedResult{
320 NextCursor: nextCursor,
321 },
322 Tools: tools,
323 }
324}
325
326// NewInitializeResult creates a new InitializeResult
327func NewInitializeResult(
328 protocolVersion string,
329 capabilities ServerCapabilities,
330 serverInfo Implementation,
331 instructions string,
332) *InitializeResult {
333 return &InitializeResult{
334 ProtocolVersion: protocolVersion,
335 Capabilities: capabilities,
336 ServerInfo: serverInfo,
337 Instructions: instructions,
338 }
339}
340
341// Helper for formatting numbers in tool results
342func FormatNumberResult(value float64) *CallToolResult {
343 return NewToolResultText(fmt.Sprintf("%.2f", value))
344}
345
346func ExtractString(data map[string]any, key string) string {
347 if value, ok := data[key]; ok {
348 if str, ok := value.(string); ok {
349 return str
350 }
351 }
352 return ""
353}
354
355func ExtractMap(data map[string]any, key string) map[string]any {
356 if value, ok := data[key]; ok {
357 if m, ok := value.(map[string]any); ok {
358 return m
359 }
360 }
361 return nil
362}
363
364func ParseContent(contentMap map[string]any) (Content, error) {
365 contentType := ExtractString(contentMap, "type")
366
367 switch contentType {
368 case "text":
369 text := ExtractString(contentMap, "text")
370 if text == "" {
371 return nil, fmt.Errorf("text is missing")
372 }
373 return NewTextContent(text), nil
374
375 case "image":
376 data := ExtractString(contentMap, "data")
377 mimeType := ExtractString(contentMap, "mimeType")
378 if data == "" || mimeType == "" {
379 return nil, fmt.Errorf("image data or mimeType is missing")
380 }
381 return NewImageContent(data, mimeType), nil
382
383 case "resource":
384 resourceMap := ExtractMap(contentMap, "resource")
385 if resourceMap == nil {
386 return nil, fmt.Errorf("resource is missing")
387 }
388
389 resourceContents, err := ParseResourceContents(resourceMap)
390 if err != nil {
391 return nil, err
392 }
393
394 return NewEmbeddedResource(resourceContents), nil
395 }
396
397 return nil, fmt.Errorf("unsupported content type: %s", contentType)
398}
399
400func ParseGetPromptResult(rawMessage *json.RawMessage) (*GetPromptResult, error) {
401 var jsonContent map[string]any
402 if err := json.Unmarshal(*rawMessage, &jsonContent); err != nil {
403 return nil, fmt.Errorf("failed to unmarshal response: %w", err)
404 }
405
406 result := GetPromptResult{}
407
408 meta, ok := jsonContent["_meta"]
409 if ok {
410 if metaMap, ok := meta.(map[string]any); ok {
411 result.Meta = metaMap
412 }
413 }
414
415 description, ok := jsonContent["description"]
416 if ok {
417 if descriptionStr, ok := description.(string); ok {
418 result.Description = descriptionStr
419 }
420 }
421
422 messages, ok := jsonContent["messages"]
423 if ok {
424 messagesArr, ok := messages.([]any)
425 if !ok {
426 return nil, fmt.Errorf("messages is not an array")
427 }
428
429 for _, message := range messagesArr {
430 messageMap, ok := message.(map[string]any)
431 if !ok {
432 return nil, fmt.Errorf("message is not an object")
433 }
434
435 // Extract role
436 roleStr := ExtractString(messageMap, "role")
437 if roleStr == "" || (roleStr != string(RoleAssistant) && roleStr != string(RoleUser)) {
438 return nil, fmt.Errorf("unsupported role: %s", roleStr)
439 }
440
441 // Extract content
442 contentMap, ok := messageMap["content"].(map[string]any)
443 if !ok {
444 return nil, fmt.Errorf("content is not an object")
445 }
446
447 // Process content
448 content, err := ParseContent(contentMap)
449 if err != nil {
450 return nil, err
451 }
452
453 // Append processed message
454 result.Messages = append(result.Messages, NewPromptMessage(Role(roleStr), content))
455
456 }
457 }
458
459 return &result, nil
460}
461
462func ParseCallToolResult(rawMessage *json.RawMessage) (*CallToolResult, error) {
463 var jsonContent map[string]any
464 if err := json.Unmarshal(*rawMessage, &jsonContent); err != nil {
465 return nil, fmt.Errorf("failed to unmarshal response: %w", err)
466 }
467
468 var result CallToolResult
469
470 meta, ok := jsonContent["_meta"]
471 if ok {
472 if metaMap, ok := meta.(map[string]any); ok {
473 result.Meta = metaMap
474 }
475 }
476
477 isError, ok := jsonContent["isError"]
478 if ok {
479 if isErrorBool, ok := isError.(bool); ok {
480 result.IsError = isErrorBool
481 }
482 }
483
484 contents, ok := jsonContent["content"]
485 if !ok {
486 return nil, fmt.Errorf("content is missing")
487 }
488
489 contentArr, ok := contents.([]any)
490 if !ok {
491 return nil, fmt.Errorf("content is not an array")
492 }
493
494 for _, content := range contentArr {
495 // Extract content
496 contentMap, ok := content.(map[string]any)
497 if !ok {
498 return nil, fmt.Errorf("content is not an object")
499 }
500
501 // Process content
502 content, err := ParseContent(contentMap)
503 if err != nil {
504 return nil, err
505 }
506
507 result.Content = append(result.Content, content)
508 }
509
510 return &result, nil
511}
512
513func ParseResourceContents(contentMap map[string]any) (ResourceContents, error) {
514 uri := ExtractString(contentMap, "uri")
515 if uri == "" {
516 return nil, fmt.Errorf("resource uri is missing")
517 }
518
519 mimeType := ExtractString(contentMap, "mimeType")
520
521 if text := ExtractString(contentMap, "text"); text != "" {
522 return TextResourceContents{
523 URI: uri,
524 MIMEType: mimeType,
525 Text: text,
526 }, nil
527 }
528
529 if blob := ExtractString(contentMap, "blob"); blob != "" {
530 return BlobResourceContents{
531 URI: uri,
532 MIMEType: mimeType,
533 Blob: blob,
534 }, nil
535 }
536
537 return nil, fmt.Errorf("unsupported resource type")
538}
539
540func ParseReadResourceResult(rawMessage *json.RawMessage) (*ReadResourceResult, error) {
541 var jsonContent map[string]any
542 if err := json.Unmarshal(*rawMessage, &jsonContent); err != nil {
543 return nil, fmt.Errorf("failed to unmarshal response: %w", err)
544 }
545
546 var result ReadResourceResult
547
548 meta, ok := jsonContent["_meta"]
549 if ok {
550 if metaMap, ok := meta.(map[string]any); ok {
551 result.Meta = metaMap
552 }
553 }
554
555 contents, ok := jsonContent["contents"]
556 if !ok {
557 return nil, fmt.Errorf("contents is missing")
558 }
559
560 contentArr, ok := contents.([]any)
561 if !ok {
562 return nil, fmt.Errorf("contents is not an array")
563 }
564
565 for _, content := range contentArr {
566 // Extract content
567 contentMap, ok := content.(map[string]any)
568 if !ok {
569 return nil, fmt.Errorf("content is not an object")
570 }
571
572 // Process content
573 content, err := ParseResourceContents(contentMap)
574 if err != nil {
575 return nil, err
576 }
577
578 result.Contents = append(result.Contents, content)
579 }
580
581 return &result, nil
582}