Model Context Protocol in OCaml
1(* Mcp_message - High-level RPC message definitions for Model Context Protocol *)
2
3open Mcp
4open Jsonrpc
5
6(* Resources/List *)
7module ResourcesList = struct
8 module Request = struct
9 type t = {
10 cursor: Cursor.t option;
11 }
12
13 let yojson_of_t { cursor } =
14 let assoc = [] in
15 let assoc = match cursor with
16 | Some c -> ("cursor", Cursor.yojson_of_t c) :: assoc
17 | None -> assoc
18 in
19 `Assoc assoc
20
21 let t_of_yojson = function
22 | `Assoc fields ->
23 let cursor = List.assoc_opt "cursor" fields |> Option.map Cursor.t_of_yojson in
24 { cursor }
25 | j -> raise (Json.Of_json ("Expected object for ResourcesList.Request.t", j))
26
27 end
28
29 module Resource = struct
30 type t = {
31 uri: string;
32 name: string;
33 description: string option;
34 mime_type: string option;
35 size: int option;
36 }
37
38 let yojson_of_t { uri; name; description; mime_type; size } =
39 let assoc = [
40 ("uri", `String uri);
41 ("name", `String name);
42 ] in
43 let assoc = match description with
44 | Some desc -> ("description", `String desc) :: assoc
45 | None -> assoc
46 in
47 let assoc = match mime_type with
48 | Some mime -> ("mimeType", `String mime) :: assoc
49 | None -> assoc
50 in
51 let assoc = match size with
52 | Some s -> ("size", `Int s) :: assoc
53 | None -> assoc
54 in
55 `Assoc assoc
56
57 let t_of_yojson = function
58 | `Assoc fields ->
59 let uri = match List.assoc_opt "uri" fields with
60 | Some (`String s) -> s
61 | _ -> raise (Json.Of_json ("Missing or invalid 'uri' field", `Assoc fields))
62 in
63 let name = match List.assoc_opt "name" fields with
64 | Some (`String s) -> s
65 | _ -> raise (Json.Of_json ("Missing or invalid 'name' field", `Assoc fields))
66 in
67 let description = List.assoc_opt "description" fields |> Option.map (function
68 | `String s -> s
69 | j -> raise (Json.Of_json ("Expected string for description", j))
70 ) in
71 let mime_type = List.assoc_opt "mimeType" fields |> Option.map (function
72 | `String s -> s
73 | j -> raise (Json.Of_json ("Expected string for mimeType", j))
74 ) in
75 let size = List.assoc_opt "size" fields |> Option.map (function
76 | `Int i -> i
77 | j -> raise (Json.Of_json ("Expected int for size", j))
78 ) in
79 { uri; name; description; mime_type; size }
80 | j -> raise (Json.Of_json ("Expected object for ResourcesList.Resource.t", j))
81 end
82
83 module Response = struct
84 type t = {
85 resources: Resource.t list;
86 next_cursor: Cursor.t option;
87 }
88
89 let yojson_of_t { resources; next_cursor } =
90 let assoc = [
91 ("resources", `List (List.map Resource.yojson_of_t resources));
92 ] in
93 let assoc = match next_cursor with
94 | Some c -> ("nextCursor", Cursor.yojson_of_t c) :: assoc
95 | None -> assoc
96 in
97 `Assoc assoc
98
99 let t_of_yojson = function
100 | `Assoc fields ->
101 let resources = match List.assoc_opt "resources" fields with
102 | Some (`List items) -> List.map Resource.t_of_yojson items
103 | _ -> raise (Json.Of_json ("Missing or invalid 'resources' field", `Assoc fields))
104 in
105 let next_cursor = List.assoc_opt "nextCursor" fields |> Option.map Cursor.t_of_yojson in
106 { resources; next_cursor }
107 | j -> raise (Json.Of_json ("Expected object for ResourcesList.Response.t", j))
108
109 end
110
111 (* Request/response creation helpers *)
112 let create_request ?cursor ?id () =
113 let id = match id with
114 | Some i -> i
115 | None -> `Int (Random.int 10000)
116 in
117 let params = Request.yojson_of_t { cursor } in
118 JSONRPCMessage.create_request ~id ~meth:Method.ResourcesList ~params:(Some params) ()
119
120 let create_response ~id ~resources ?next_cursor () =
121 let result = Response.yojson_of_t { resources; next_cursor } in
122 JSONRPCMessage.create_response ~id ~result
123end
124
125(* Resources/Read *)
126module ResourcesRead = struct
127 module Request = struct
128 type t = {
129 uri: string;
130 }
131
132 let yojson_of_t { uri } =
133 `Assoc [
134 ("uri", `String uri);
135 ]
136
137 let t_of_yojson = function
138 | `Assoc fields ->
139 let uri = match List.assoc_opt "uri" fields with
140 | Some (`String s) -> s
141 | _ -> raise (Json.Of_json ("Missing or invalid 'uri' field", `Assoc fields))
142 in
143 { uri }
144 | j -> raise (Json.Of_json ("Expected object for ResourcesRead.Request.t", j))
145
146 end
147
148 module ResourceContent = struct
149 type t =
150 | TextResource of TextResourceContents.t
151 | BlobResource of BlobResourceContents.t
152
153 let yojson_of_t = function
154 | TextResource tr -> TextResourceContents.yojson_of_t tr
155 | BlobResource br -> BlobResourceContents.yojson_of_t br
156
157 let t_of_yojson json =
158 match json with
159 | `Assoc fields ->
160 if List.mem_assoc "text" fields then
161 TextResource (TextResourceContents.t_of_yojson json)
162 else if List.mem_assoc "blob" fields then
163 BlobResource (BlobResourceContents.t_of_yojson json)
164 else
165 raise (Json.Of_json ("Invalid resource content", json))
166 | j -> raise (Json.Of_json ("Expected object for ResourcesRead.ResourceContent.t", j))
167
168 end
169
170 module Response = struct
171 type t = {
172 contents: ResourceContent.t list;
173 }
174
175 let yojson_of_t { contents } =
176 `Assoc [
177 ("contents", `List (List.map ResourceContent.yojson_of_t contents));
178 ]
179
180 let t_of_yojson = function
181 | `Assoc fields ->
182 let contents = match List.assoc_opt "contents" fields with
183 | Some (`List items) -> List.map ResourceContent.t_of_yojson items
184 | _ -> raise (Json.Of_json ("Missing or invalid 'contents' field", `Assoc fields))
185 in
186 { contents }
187 | j -> raise (Json.Of_json ("Expected object for ResourcesRead.Response.t", j))
188
189 end
190
191 (* Request/response creation helpers *)
192 let create_request ~uri ?id () =
193 let id = match id with
194 | Some i -> i
195 | None -> `Int (Random.int 10000)
196 in
197 let params = Request.yojson_of_t { uri } in
198 JSONRPCMessage.create_request ~id ~meth:Method.ResourcesRead ~params:(Some params) ()
199
200 let create_response ~id ~contents () =
201 let result = Response.yojson_of_t { contents } in
202 JSONRPCMessage.create_response ~id ~result
203end
204
205(* Tools/List *)
206module ToolsList = struct
207 module Request = struct
208 type t = {
209 cursor: Cursor.t option;
210 }
211
212 let yojson_of_t { cursor } =
213 let assoc = [] in
214 let assoc = match cursor with
215 | Some c -> ("cursor", Cursor.yojson_of_t c) :: assoc
216 | None -> assoc
217 in
218 `Assoc assoc
219
220 let t_of_yojson = function
221 | `Assoc fields ->
222 let cursor = List.assoc_opt "cursor" fields |> Option.map Cursor.t_of_yojson in
223 { cursor }
224 | j -> raise (Json.Of_json ("Expected object for ToolsList.Request.t", j))
225
226 end
227
228 module Tool = struct
229 type t = {
230 name: string;
231 description: string option;
232 input_schema: Json.t;
233 annotations: Json.t option;
234 }
235
236 let yojson_of_t { name; description; input_schema; annotations } =
237 let assoc = [
238 ("name", `String name);
239 ("inputSchema", input_schema);
240 ] in
241 let assoc = match description with
242 | Some desc -> ("description", `String desc) :: assoc
243 | None -> assoc
244 in
245 let assoc = match annotations with
246 | Some anno -> ("annotations", anno) :: assoc
247 | None -> assoc
248 in
249 `Assoc assoc
250
251 let t_of_yojson = function
252 | `Assoc fields ->
253 let name = match List.assoc_opt "name" fields with
254 | Some (`String s) -> s
255 | _ -> raise (Json.Of_json ("Missing or invalid 'name' field", `Assoc fields))
256 in
257 let description = List.assoc_opt "description" fields |> Option.map (function
258 | `String s -> s
259 | j -> raise (Json.Of_json ("Expected string for description", j))
260 ) in
261 let input_schema = match List.assoc_opt "inputSchema" fields with
262 | Some schema -> schema
263 | None -> raise (Json.Of_json ("Missing 'inputSchema' field", `Assoc fields))
264 in
265 let annotations = List.assoc_opt "annotations" fields in
266 { name; description; input_schema; annotations }
267 | j -> raise (Json.Of_json ("Expected object for ToolsList.Tool.t", j))
268
269 end
270
271 module Response = struct
272 type t = {
273 tools: Tool.t list;
274 next_cursor: Cursor.t option;
275 }
276
277 let yojson_of_t { tools; next_cursor } =
278 let assoc = [
279 ("tools", `List (List.map Tool.yojson_of_t tools));
280 ] in
281 let assoc = match next_cursor with
282 | Some c -> ("nextCursor", Cursor.yojson_of_t c) :: assoc
283 | None -> assoc
284 in
285 `Assoc assoc
286
287 let t_of_yojson = function
288 | `Assoc fields ->
289 let tools = match List.assoc_opt "tools" fields with
290 | Some (`List items) -> List.map Tool.t_of_yojson items
291 | _ -> raise (Json.Of_json ("Missing or invalid 'tools' field", `Assoc fields))
292 in
293 let next_cursor = List.assoc_opt "nextCursor" fields |> Option.map Cursor.t_of_yojson in
294 { tools; next_cursor }
295 | j -> raise (Json.Of_json ("Expected object for ToolsList.Response.t", j))
296
297 end
298
299 (* Request/response creation helpers *)
300 let create_request ?cursor ?id () =
301 let id = match id with
302 | Some i -> i
303 | None -> `Int (Random.int 10000)
304 in
305 let params = Request.yojson_of_t { cursor } in
306 JSONRPCMessage.create_request ~id ~meth:Method.ToolsList ~params:(Some params) ()
307
308 let create_response ~id ~tools ?next_cursor () =
309 let result = Response.yojson_of_t { tools; next_cursor } in
310 JSONRPCMessage.create_response ~id ~result
311end
312
313(* Tools/Call *)
314module ToolsCall = struct
315 module Request = struct
316 type t = {
317 name: string;
318 arguments: Json.t;
319 }
320
321 let yojson_of_t { name; arguments } =
322 `Assoc [
323 ("name", `String name);
324 ("arguments", arguments);
325 ]
326
327 let t_of_yojson = function
328 | `Assoc fields ->
329 let name = match List.assoc_opt "name" fields with
330 | Some (`String s) -> s
331 | _ -> raise (Json.Of_json ("Missing or invalid 'name' field", `Assoc fields))
332 in
333 let arguments = match List.assoc_opt "arguments" fields with
334 | Some json -> json
335 | None -> raise (Json.Of_json ("Missing 'arguments' field", `Assoc fields))
336 in
337 { name; arguments }
338 | j -> raise (Json.Of_json ("Expected object for ToolsCall.Request.t", j))
339
340 end
341
342 module ToolContent = struct
343 type t =
344 | Text of TextContent.t
345 | Image of ImageContent.t
346 | Audio of AudioContent.t
347 | Resource of EmbeddedResource.t
348
349 let yojson_of_t = function
350 | Text t -> TextContent.yojson_of_t t
351 | Image i -> ImageContent.yojson_of_t i
352 | Audio a -> AudioContent.yojson_of_t a
353 | Resource r -> EmbeddedResource.yojson_of_t r
354
355 let t_of_yojson json =
356 match json with
357 | `Assoc fields ->
358 (match List.assoc_opt "type" fields with
359 | Some (`String "text") -> Text (TextContent.t_of_yojson json)
360 | Some (`String "image") -> Image (ImageContent.t_of_yojson json)
361 | Some (`String "audio") -> Audio (AudioContent.t_of_yojson json)
362 | Some (`String "resource") -> Resource (EmbeddedResource.t_of_yojson json)
363 | _ -> raise (Json.Of_json ("Invalid or missing content type", json)))
364 | j -> raise (Json.Of_json ("Expected object for ToolsCall.ToolContent.t", j))
365
366 end
367
368 module Response = struct
369 type t = {
370 content: ToolContent.t list;
371 is_error: bool;
372 }
373
374 let yojson_of_t { content; is_error } =
375 `Assoc [
376 ("content", `List (List.map ToolContent.yojson_of_t content));
377 ("isError", `Bool is_error);
378 ]
379
380 let t_of_yojson = function
381 | `Assoc fields ->
382 let content = match List.assoc_opt "content" fields with
383 | Some (`List items) -> List.map ToolContent.t_of_yojson items
384 | _ -> raise (Json.Of_json ("Missing or invalid 'content' field", `Assoc fields))
385 in
386 let is_error = match List.assoc_opt "isError" fields with
387 | Some (`Bool b) -> b
388 | _ -> false
389 in
390 { content; is_error }
391 | j -> raise (Json.Of_json ("Expected object for ToolsCall.Response.t", j))
392
393 end
394
395 (* Request/response creation helpers *)
396 let create_request ~name ~arguments ?id () =
397 let id = match id with
398 | Some i -> i
399 | None -> `Int (Random.int 10000)
400 in
401 let params = Request.yojson_of_t { name; arguments } in
402 JSONRPCMessage.create_request ~id ~meth:Method.ToolsCall ~params:(Some params) ()
403
404 let create_response ~id ~content ~is_error () =
405 let result = Response.yojson_of_t { content; is_error } in
406 JSONRPCMessage.create_response ~id ~result
407end
408
409(* Prompts/List *)
410module PromptsList = struct
411 module PromptArgument = struct
412 type t = {
413 name: string;
414 description: string option;
415 required: bool;
416 }
417
418 let yojson_of_t { name; description; required } =
419 let assoc = [
420 ("name", `String name);
421 ] in
422 let assoc = match description with
423 | Some desc -> ("description", `String desc) :: assoc
424 | None -> assoc
425 in
426 let assoc = if required then
427 ("required", `Bool true) :: assoc
428 else
429 assoc
430 in
431 `Assoc assoc
432
433 let t_of_yojson = function
434 | `Assoc fields ->
435 let name = match List.assoc_opt "name" fields with
436 | Some (`String s) -> s
437 | _ -> raise (Json.Of_json ("Missing or invalid 'name' field", `Assoc fields))
438 in
439 let description = List.assoc_opt "description" fields |> Option.map (function
440 | `String s -> s
441 | j -> raise (Json.Of_json ("Expected string for description", j))
442 ) in
443 let required = match List.assoc_opt "required" fields with
444 | Some (`Bool b) -> b
445 | _ -> false
446 in
447 { name; description; required }
448 | j -> raise (Json.Of_json ("Expected object for PromptsList.PromptArgument.t", j))
449
450 end
451
452 module Prompt = struct
453 type t = {
454 name: string;
455 description: string option;
456 arguments: PromptArgument.t list;
457 }
458
459 let yojson_of_t { name; description; arguments } =
460 let assoc = [
461 ("name", `String name);
462 ] in
463 let assoc = match description with
464 | Some desc -> ("description", `String desc) :: assoc
465 | None -> assoc
466 in
467 let assoc = if arguments <> [] then
468 ("arguments", `List (List.map PromptArgument.yojson_of_t arguments)) :: assoc
469 else
470 assoc
471 in
472 `Assoc assoc
473
474 let t_of_yojson = function
475 | `Assoc fields ->
476 let name = match List.assoc_opt "name" fields with
477 | Some (`String s) -> s
478 | _ -> raise (Json.Of_json ("Missing or invalid 'name' field", `Assoc fields))
479 in
480 let description = List.assoc_opt "description" fields |> Option.map (function
481 | `String s -> s
482 | j -> raise (Json.Of_json ("Expected string for description", j))
483 ) in
484 let arguments = match List.assoc_opt "arguments" fields with
485 | Some (`List items) -> List.map PromptArgument.t_of_yojson items
486 | _ -> []
487 in
488 { name; description; arguments }
489 | j -> raise (Json.Of_json ("Expected object for PromptsList.Prompt.t", j))
490
491 end
492
493 module Request = struct
494 type t = {
495 cursor: Cursor.t option;
496 }
497
498 let yojson_of_t { cursor } =
499 let assoc = [] in
500 let assoc = match cursor with
501 | Some c -> ("cursor", Cursor.yojson_of_t c) :: assoc
502 | None -> assoc
503 in
504 `Assoc assoc
505
506 let t_of_yojson = function
507 | `Assoc fields ->
508 let cursor = List.assoc_opt "cursor" fields |> Option.map Cursor.t_of_yojson in
509 { cursor }
510 | j -> raise (Json.Of_json ("Expected object for PromptsList.Request.t", j))
511
512 end
513
514 module Response = struct
515 type t = {
516 prompts: Prompt.t list;
517 next_cursor: Cursor.t option;
518 }
519
520 let yojson_of_t { prompts; next_cursor } =
521 let assoc = [
522 ("prompts", `List (List.map Prompt.yojson_of_t prompts));
523 ] in
524 let assoc = match next_cursor with
525 | Some c -> ("nextCursor", Cursor.yojson_of_t c) :: assoc
526 | None -> assoc
527 in
528 `Assoc assoc
529
530 let t_of_yojson = function
531 | `Assoc fields ->
532 let prompts = match List.assoc_opt "prompts" fields with
533 | Some (`List items) -> List.map Prompt.t_of_yojson items
534 | _ -> raise (Json.Of_json ("Missing or invalid 'prompts' field", `Assoc fields))
535 in
536 let next_cursor = List.assoc_opt "nextCursor" fields |> Option.map Cursor.t_of_yojson in
537 { prompts; next_cursor }
538 | j -> raise (Json.Of_json ("Expected object for PromptsList.Response.t", j))
539
540 end
541
542 (* Request/response creation helpers *)
543 let create_request ?cursor ?id () =
544 let id = match id with
545 | Some i -> i
546 | None -> `Int (Random.int 10000)
547 in
548 let params = Request.yojson_of_t { cursor } in
549 JSONRPCMessage.create_request ~id ~meth:Method.PromptsList ~params:(Some params) ()
550
551 let create_response ~id ~prompts ?next_cursor () =
552 let result = Response.yojson_of_t { prompts; next_cursor } in
553 JSONRPCMessage.create_response ~id ~result
554end
555
556(* Prompts/Get *)
557module PromptsGet = struct
558 module Request = struct
559 type t = {
560 name: string;
561 arguments: (string * string) list;
562 }
563
564 let yojson_of_t { name; arguments } =
565 let args_json = `Assoc (List.map (fun (k, v) -> (k, `String v)) arguments) in
566 `Assoc [
567 ("name", `String name);
568 ("arguments", args_json);
569 ]
570
571 let t_of_yojson = function
572 | `Assoc fields ->
573 let name = match List.assoc_opt "name" fields with
574 | Some (`String s) -> s
575 | _ -> raise (Json.Of_json ("Missing or invalid 'name' field", `Assoc fields))
576 in
577 let arguments = match List.assoc_opt "arguments" fields with
578 | Some (`Assoc args) ->
579 List.map (fun (k, v) ->
580 match v with
581 | `String s -> (k, s)
582 | _ -> raise (Json.Of_json ("Expected string value for argument", v))
583 ) args
584 | _ -> []
585 in
586 { name; arguments }
587 | j -> raise (Json.Of_json ("Expected object for PromptsGet.Request.t", j))
588
589 end
590
591 module Response = struct
592 type t = {
593 description: string option;
594 messages: PromptMessage.t list;
595 }
596
597 let yojson_of_t { description; messages } =
598 let assoc = [
599 ("messages", `List (List.map PromptMessage.yojson_of_t messages));
600 ] in
601 let assoc = match description with
602 | Some desc -> ("description", `String desc) :: assoc
603 | None -> assoc
604 in
605 `Assoc assoc
606
607 let t_of_yojson = function
608 | `Assoc fields ->
609 let messages = match List.assoc_opt "messages" fields with
610 | Some (`List items) -> List.map PromptMessage.t_of_yojson items
611 | _ -> raise (Json.Of_json ("Missing or invalid 'messages' field", `Assoc fields))
612 in
613 let description = List.assoc_opt "description" fields |> Option.map (function
614 | `String s -> s
615 | j -> raise (Json.Of_json ("Expected string for description", j))
616 ) in
617 { description; messages }
618 | j -> raise (Json.Of_json ("Expected object for PromptsGet.Response.t", j))
619
620 end
621
622 (* Request/response creation helpers *)
623 let create_request ~name ~arguments ?id () =
624 let id = match id with
625 | Some i -> i
626 | None -> `Int (Random.int 10000)
627 in
628 let params = Request.yojson_of_t { name; arguments } in
629 JSONRPCMessage.create_request ~id ~meth:Method.PromptsGet ~params:(Some params) ()
630
631 let create_response ~id ?description ~messages () =
632 let result = Response.yojson_of_t { description; messages } in
633 JSONRPCMessage.create_response ~id ~result
634end
635
636(* List Changed Notifications *)
637module ListChanged = struct
638 (* No parameters for these notifications *)
639
640 let create_resources_notification () =
641 JSONRPCMessage.create_notification ~meth:Method.ResourcesListChanged ()
642
643 let create_tools_notification () =
644 JSONRPCMessage.create_notification ~meth:Method.ToolsListChanged ()
645
646 let create_prompts_notification () =
647 JSONRPCMessage.create_notification ~meth:Method.PromptsListChanged ()
648end
649
650(* Resource Updated Notification *)
651module ResourceUpdated = struct
652 module Notification = struct
653 type t = {
654 uri: string;
655 }
656
657 let yojson_of_t { uri } =
658 `Assoc [
659 ("uri", `String uri);
660 ]
661
662 let t_of_yojson = function
663 | `Assoc fields ->
664 let uri = match List.assoc_opt "uri" fields with
665 | Some (`String s) -> s
666 | _ -> raise (Json.Of_json ("Missing or invalid 'uri' field", `Assoc fields))
667 in
668 { uri }
669 | j -> raise (Json.Of_json ("Expected object for ResourceUpdated.Notification.t", j))
670
671 end
672
673 let create_notification ~uri () =
674 let params = Notification.yojson_of_t { uri } in
675 JSONRPCMessage.create_notification ~meth:Method.ResourcesUpdated ~params:(Some params) ()
676end
677
678(* Progress Notification *)
679module Progress = struct
680 module Notification = struct
681 type t = {
682 progress: float;
683 total: float;
684 progress_token: ProgressToken.t;
685 }
686
687 let yojson_of_t { progress; total; progress_token } =
688 `Assoc [
689 ("progress", `Float progress);
690 ("total", `Float total);
691 ("progressToken", ProgressToken.yojson_of_t progress_token);
692 ]
693
694 let t_of_yojson = function
695 | `Assoc fields ->
696 let progress = match List.assoc_opt "progress" fields with
697 | Some (`Float f) -> f
698 | _ -> raise (Json.Of_json ("Missing or invalid 'progress' field", `Assoc fields))
699 in
700 let total = match List.assoc_opt "total" fields with
701 | Some (`Float f) -> f
702 | _ -> raise (Json.Of_json ("Missing or invalid 'total' field", `Assoc fields))
703 in
704 let progress_token = match List.assoc_opt "progressToken" fields with
705 | Some token -> ProgressToken.t_of_yojson token
706 | _ -> raise (Json.Of_json ("Missing or invalid 'progressToken' field", `Assoc fields))
707 in
708 { progress; total; progress_token }
709 | j -> raise (Json.Of_json ("Expected object for Progress.Notification.t", j))
710
711 end
712
713 let create_notification ~progress ~total ~progress_token () =
714 let params = Notification.yojson_of_t { progress; total; progress_token } in
715 JSONRPCMessage.create_notification ~meth:Method.Progress ~params:(Some params) ()
716end
717
718(* Type aliases for backward compatibility *)
719type request = ResourcesList.Request.t
720type response = ResourcesList.Response.t
721type resource = ResourcesList.Resource.t
722type resource_content = ResourcesRead.ResourceContent.t
723type tool = ToolsList.Tool.t
724type tool_content = ToolsCall.ToolContent.t
725type prompt = PromptsList.Prompt.t
726type prompt_argument = PromptsList.PromptArgument.t