Model Context Protocol in OCaml
1open Jsonrpc
2
3(* Utility functions for JSON parsing *)
4module Util = struct
5 (* Helper to raise a Json.Of_json exception with formatted message *)
6 let json_error fmt json =
7 Printf.ksprintf (fun msg -> raise (Json.Of_json (msg, json))) fmt
8
9 (* Extract a string field from JSON object or raise an error *)
10 let get_string_field fields name json =
11 match List.assoc_opt name fields with
12 | Some (`String s) -> s
13 | _ -> json_error "Missing or invalid '%s' field" json name
14
15 (* Extract an optional string field from JSON object *)
16 let get_optional_string_field fields name =
17 List.assoc_opt name fields
18 |> Option.map (function
19 | `String s -> s
20 | j -> json_error "Expected string for %s" j name)
21
22 (* Extract an int field from JSON object or raise an error *)
23 let get_int_field fields name json =
24 match List.assoc_opt name fields with
25 | Some (`Int i) -> i
26 | _ -> json_error "Missing or invalid '%s' field" json name
27
28 (* Extract a float field from JSON object or raise an error *)
29 let get_float_field fields name json =
30 match List.assoc_opt name fields with
31 | Some (`Float f) -> f
32 | _ -> json_error "Missing or invalid '%s' field" json name
33
34 (* Extract a boolean field from JSON object or raise an error *)
35 let get_bool_field fields name json =
36 match List.assoc_opt name fields with
37 | Some (`Bool b) -> b
38 | _ -> json_error "Missing or invalid '%s' field" json name
39
40 (* Extract an object field from JSON object or raise an error *)
41 let get_object_field fields name json =
42 match List.assoc_opt name fields with
43 | Some (`Assoc obj) -> obj
44 | _ -> json_error "Missing or invalid '%s' field" json name
45
46 (* Extract a list field from JSON object or raise an error *)
47 let get_list_field fields name json =
48 match List.assoc_opt name fields with
49 | Some (`List items) -> items
50 | _ -> json_error "Missing or invalid '%s' field" json name
51
52 (* Verify a specific string value in a field *)
53 let verify_string_field fields name expected_value json =
54 match List.assoc_opt name fields with
55 | Some (`String s) when s = expected_value -> ()
56 | _ ->
57 json_error "Field '%s' missing or not equal to '%s'" json name
58 expected_value
59end
60
61(* Error codes for JSON-RPC *)
62module ErrorCode = struct
63 type t =
64 | ParseError (* -32700 - Invalid JSON *)
65 | InvalidRequest (* -32600 - Invalid JSON-RPC request *)
66 | MethodNotFound (* -32601 - Method not available *)
67 | InvalidParams (* -32602 - Invalid method parameters *)
68 | InternalError (* -32603 - Internal JSON-RPC error *)
69 | ResourceNotFound
70 (* -32002 - Custom MCP error: requested resource not found *)
71 | AuthRequired (* -32001 - Custom MCP error: authentication required *)
72 | CustomError of int (* For any other error codes *)
73
74 (* Convert the error code to its integer representation *)
75 let to_int = function
76 | ParseError -> -32700
77 | InvalidRequest -> -32600
78 | MethodNotFound -> -32601
79 | InvalidParams -> -32602
80 | InternalError -> -32603
81 | ResourceNotFound -> -32002
82 | AuthRequired -> -32001
83 | CustomError code -> code
84
85 (* Get error message for standard error codes *)
86 let to_message = function
87 | ParseError -> "Parse error"
88 | InvalidRequest -> "Invalid Request"
89 | MethodNotFound -> "Method not found"
90 | InvalidParams -> "Invalid params"
91 | InternalError -> "Internal error"
92 | ResourceNotFound -> "Resource not found"
93 | AuthRequired -> "Authentication required"
94 | CustomError _ -> "Error"
95end
96
97(* Protocol method types *)
98module Method = struct
99 (* Method type representing all MCP protocol methods *)
100 type t =
101 (* Initialization and lifecycle methods *)
102 | Initialize
103 | Initialized
104 (* Resource methods *)
105 | ResourcesList
106 | ResourcesRead
107 | ResourceTemplatesList
108 | ResourcesSubscribe
109 | ResourcesListChanged
110 | ResourcesUpdated
111 (* Tool methods *)
112 | ToolsList
113 | ToolsCall
114 | ToolsListChanged
115 (* Prompt methods *)
116 | PromptsList
117 | PromptsGet
118 | PromptsListChanged
119 (* Progress notifications *)
120 | Progress
121
122 (* Convert method type to string representation *)
123 let to_string = function
124 | Initialize -> "initialize"
125 | Initialized -> "notifications/initialized"
126 | ResourcesList -> "resources/list"
127 | ResourcesRead -> "resources/read"
128 | ResourceTemplatesList -> "resources/templates/list"
129 | ResourcesSubscribe -> "resources/subscribe"
130 | ResourcesListChanged -> "notifications/resources/list_changed"
131 | ResourcesUpdated -> "notifications/resources/updated"
132 | ToolsList -> "tools/list"
133 | ToolsCall -> "tools/call"
134 | ToolsListChanged -> "notifications/tools/list_changed"
135 | PromptsList -> "prompts/list"
136 | PromptsGet -> "prompts/get"
137 | PromptsListChanged -> "notifications/prompts/list_changed"
138 | Progress -> "notifications/progress"
139
140 (* Convert string to method type *)
141 let of_string = function
142 | "initialize" -> Initialize
143 | "notifications/initialized" -> Initialized
144 | "resources/list" -> ResourcesList
145 | "resources/read" -> ResourcesRead
146 | "resources/templates/list" -> ResourceTemplatesList
147 | "resources/subscribe" -> ResourcesSubscribe
148 | "notifications/resources/list_changed" -> ResourcesListChanged
149 | "notifications/resources/updated" -> ResourcesUpdated
150 | "tools/list" -> ToolsList
151 | "tools/call" -> ToolsCall
152 | "notifications/tools/list_changed" -> ToolsListChanged
153 | "prompts/list" -> PromptsList
154 | "prompts/get" -> PromptsGet
155 | "notifications/prompts/list_changed" -> PromptsListChanged
156 | "notifications/progress" -> Progress
157 | s -> failwith ("Unknown MCP method: " ^ s)
158end
159
160(* Common types *)
161
162module Role = struct
163 type t = [ `User | `Assistant ]
164
165 let to_string = function `User -> "user" | `Assistant -> "assistant"
166
167 let of_string = function
168 | "user" -> `User
169 | "assistant" -> `Assistant
170 | s -> Util.json_error "Unknown role: %s" (`String s) s
171
172 let yojson_of_t t = `String (to_string t)
173
174 let t_of_yojson = function
175 | `String s -> of_string s
176 | j -> Util.json_error "Expected string for Role" j
177end
178
179module ProgressToken = struct
180 type t = [ `String of string | `Int of int ]
181
182 include (Id : Json.Jsonable.S with type t := t)
183end
184
185module RequestId = Id
186
187module Cursor = struct
188 type t = string
189
190 let yojson_of_t t = `String t
191
192 let t_of_yojson = function
193 | `String s -> s
194 | j -> Util.json_error "Expected string for Cursor" j
195end
196
197(* Annotations *)
198
199module Annotated = struct
200 type t = { annotations : annotation option }
201 and annotation = { audience : Role.t list option; priority : float option }
202
203 let yojson_of_annotation { audience; priority } =
204 let assoc = [] in
205 let assoc =
206 match audience with
207 | Some audience ->
208 ("audience", `List (List.map Role.yojson_of_t audience)) :: assoc
209 | None -> assoc
210 in
211 let assoc =
212 match priority with
213 | Some priority -> ("priority", `Float priority) :: assoc
214 | None -> assoc
215 in
216 `Assoc assoc
217
218 let annotation_of_yojson = function
219 | `Assoc fields ->
220 let audience =
221 List.assoc_opt "audience" fields
222 |> Option.map (function
223 | `List items -> List.map Role.t_of_yojson items
224 | j -> Util.json_error "Expected list for audience" j)
225 in
226 let priority =
227 List.assoc_opt "priority" fields
228 |> Option.map (function
229 | `Float f -> f
230 | j -> Util.json_error "Expected float for priority" j)
231 in
232 { audience; priority }
233 | j -> Util.json_error "Expected object for annotation" j
234
235 let yojson_of_t { annotations } =
236 match annotations with
237 | Some annotations ->
238 `Assoc [ ("annotations", yojson_of_annotation annotations) ]
239 | None -> `Assoc []
240
241 let t_of_yojson = function
242 | `Assoc fields ->
243 let annotations =
244 List.assoc_opt "annotations" fields |> Option.map annotation_of_yojson
245 in
246 { annotations }
247 | j -> Util.json_error "Expected object for Annotated" j
248end
249
250(* Content types *)
251
252module TextContent = struct
253 type t = { text : string; annotations : Annotated.annotation option }
254
255 let yojson_of_t { text; annotations } =
256 let assoc = [ ("text", `String text); ("type", `String "text") ] in
257 let assoc =
258 match annotations with
259 | Some annotations ->
260 ("annotations", Annotated.yojson_of_annotation annotations) :: assoc
261 | None -> assoc
262 in
263 `Assoc assoc
264
265 let t_of_yojson = function
266 | `Assoc fields as json ->
267 let text = Util.get_string_field fields "text" json in
268 Util.verify_string_field fields "type" "text" json;
269 let annotations =
270 List.assoc_opt "annotations" fields
271 |> Option.map Annotated.annotation_of_yojson
272 in
273 { text; annotations }
274 | j -> Util.json_error "Expected object for TextContent" j
275end
276
277module ImageContent = struct
278 type t = {
279 data : string;
280 mime_type : string;
281 annotations : Annotated.annotation option;
282 }
283
284 let yojson_of_t { data; mime_type; annotations } =
285 let assoc =
286 [
287 ("type", `String "image");
288 ("data", `String data);
289 ("mimeType", `String mime_type);
290 ]
291 in
292 let assoc =
293 match annotations with
294 | Some annotations ->
295 ("annotations", Annotated.yojson_of_annotation annotations) :: assoc
296 | None -> assoc
297 in
298 `Assoc assoc
299
300 let t_of_yojson = function
301 | `Assoc fields as json ->
302 let data = Util.get_string_field fields "data" json in
303 let mime_type = Util.get_string_field fields "mimeType" json in
304 Util.verify_string_field fields "type" "image" json;
305 let annotations =
306 List.assoc_opt "annotations" fields
307 |> Option.map Annotated.annotation_of_yojson
308 in
309 { data; mime_type; annotations }
310 | j -> Util.json_error "Expected object for ImageContent" j
311end
312
313module AudioContent = struct
314 type t = {
315 data : string;
316 mime_type : string;
317 annotations : Annotated.annotation option;
318 }
319
320 let yojson_of_t { data; mime_type; annotations } =
321 let assoc =
322 [
323 ("type", `String "audio");
324 ("data", `String data);
325 ("mimeType", `String mime_type);
326 ]
327 in
328 let assoc =
329 match annotations with
330 | Some annotations ->
331 ("annotations", Annotated.yojson_of_annotation annotations) :: assoc
332 | None -> assoc
333 in
334 `Assoc assoc
335
336 let t_of_yojson = function
337 | `Assoc fields as json ->
338 let data = Util.get_string_field fields "data" json in
339 let mime_type = Util.get_string_field fields "mimeType" json in
340 Util.verify_string_field fields "type" "audio" json;
341 let annotations =
342 List.assoc_opt "annotations" fields
343 |> Option.map Annotated.annotation_of_yojson
344 in
345 { data; mime_type; annotations }
346 | j -> Util.json_error "Expected object for AudioContent" j
347end
348
349module ResourceContents = struct
350 type t = { uri : string; mime_type : string option }
351
352 let yojson_of_t { uri; mime_type } =
353 let assoc = [ ("uri", `String uri) ] in
354 let assoc =
355 match mime_type with
356 | Some mime_type -> ("mimeType", `String mime_type) :: assoc
357 | None -> assoc
358 in
359 `Assoc assoc
360
361 let t_of_yojson = function
362 | `Assoc fields as json ->
363 let uri = Util.get_string_field fields "uri" json in
364 let mime_type = Util.get_optional_string_field fields "mimeType" in
365 { uri; mime_type }
366 | j -> Util.json_error "Expected object for ResourceContents" j
367end
368
369module TextResourceContents = struct
370 type t = { uri : string; text : string; mime_type : string option }
371
372 let yojson_of_t { uri; text; mime_type } =
373 let assoc = [ ("uri", `String uri); ("text", `String text) ] in
374 let assoc =
375 match mime_type with
376 | Some mime_type -> ("mimeType", `String mime_type) :: assoc
377 | None -> assoc
378 in
379 `Assoc assoc
380
381 let t_of_yojson = function
382 | `Assoc fields as json ->
383 let uri = Util.get_string_field fields "uri" json in
384 let text = Util.get_string_field fields "text" json in
385 let mime_type = Util.get_optional_string_field fields "mimeType" in
386 { uri; text; mime_type }
387 | j -> Util.json_error "Expected object for TextResourceContents" j
388end
389
390module BlobResourceContents = struct
391 type t = { uri : string; blob : string; mime_type : string option }
392
393 let yojson_of_t { uri; blob; mime_type } =
394 let assoc = [ ("uri", `String uri); ("blob", `String blob) ] in
395 let assoc =
396 match mime_type with
397 | Some mime_type -> ("mimeType", `String mime_type) :: assoc
398 | None -> assoc
399 in
400 `Assoc assoc
401
402 let t_of_yojson = function
403 | `Assoc fields as json ->
404 let uri = Util.get_string_field fields "uri" json in
405 let blob = Util.get_string_field fields "blob" json in
406 let mime_type = Util.get_optional_string_field fields "mimeType" in
407 { uri; blob; mime_type }
408 | j -> Util.json_error "Expected object for BlobResourceContents" j
409end
410
411module EmbeddedResource = struct
412 type t = {
413 resource :
414 [ `Text of TextResourceContents.t | `Blob of BlobResourceContents.t ];
415 annotations : Annotated.annotation option;
416 }
417
418 let yojson_of_t { resource; annotations } =
419 let resource_json =
420 match resource with
421 | `Text txt -> TextResourceContents.yojson_of_t txt
422 | `Blob blob -> BlobResourceContents.yojson_of_t blob
423 in
424 let assoc = [ ("resource", resource_json); ("type", `String "resource") ] in
425 let assoc =
426 match annotations with
427 | Some annotations ->
428 ("annotations", Annotated.yojson_of_annotation annotations) :: assoc
429 | None -> assoc
430 in
431 `Assoc assoc
432
433 let t_of_yojson = function
434 | `Assoc fields as json ->
435 Util.verify_string_field fields "type" "resource" json;
436 let resource_fields =
437 match List.assoc_opt "resource" fields with
438 | Some (`Assoc res_fields) -> res_fields
439 | _ -> Util.json_error "Missing or invalid 'resource' field" json
440 in
441 let resource =
442 if List.mem_assoc "text" resource_fields then
443 `Text (TextResourceContents.t_of_yojson (`Assoc resource_fields))
444 else if List.mem_assoc "blob" resource_fields then
445 `Blob (BlobResourceContents.t_of_yojson (`Assoc resource_fields))
446 else
447 Util.json_error "Invalid resource content" (`Assoc resource_fields)
448 in
449 let annotations =
450 List.assoc_opt "annotations" fields
451 |> Option.map Annotated.annotation_of_yojson
452 in
453 { resource; annotations }
454 | j -> Util.json_error "Expected object for EmbeddedResource" j
455end
456
457type content =
458 | Text of TextContent.t
459 | Image of ImageContent.t
460 | Audio of AudioContent.t
461 | Resource of EmbeddedResource.t
462
463let yojson_of_content = function
464 | Text t -> TextContent.yojson_of_t t
465 | Image i -> ImageContent.yojson_of_t i
466 | Audio a -> AudioContent.yojson_of_t a
467 | Resource r -> EmbeddedResource.yojson_of_t r
468
469let content_of_yojson = function
470 | `Assoc fields as json -> (
471 match List.assoc_opt "type" fields with
472 | Some (`String "text") -> Text (TextContent.t_of_yojson json)
473 | Some (`String "image") -> Image (ImageContent.t_of_yojson json)
474 | Some (`String "audio") -> Audio (AudioContent.t_of_yojson json)
475 | Some (`String "resource") ->
476 Resource (EmbeddedResource.t_of_yojson json)
477 | _ -> Util.json_error "Invalid or missing content type" json)
478 | j -> Util.json_error "Expected object for content" j
479
480(* Message types *)
481
482module PromptMessage = struct
483 type t = { role : Role.t; content : content }
484
485 let yojson_of_t { role; content } =
486 `Assoc
487 [
488 ("role", Role.yojson_of_t role); ("content", yojson_of_content content);
489 ]
490
491 let t_of_yojson = function
492 | `Assoc fields as json ->
493 let role =
494 match List.assoc_opt "role" fields with
495 | Some json -> Role.t_of_yojson json
496 | None -> Util.json_error "Missing role field" json
497 in
498 let content =
499 match List.assoc_opt "content" fields with
500 | Some json -> content_of_yojson json
501 | None -> Util.json_error "Missing content field" json
502 in
503 { role; content }
504 | j -> Util.json_error "Expected object for PromptMessage" j
505end
506
507module SamplingMessage = struct
508 type t = {
509 role : Role.t;
510 content :
511 [ `Text of TextContent.t
512 | `Image of ImageContent.t
513 | `Audio of AudioContent.t ];
514 }
515
516 let yojson_of_t { role; content } =
517 let content_json =
518 match content with
519 | `Text t -> TextContent.yojson_of_t t
520 | `Image i -> ImageContent.yojson_of_t i
521 | `Audio a -> AudioContent.yojson_of_t a
522 in
523 `Assoc [ ("role", Role.yojson_of_t role); ("content", content_json) ]
524
525 let t_of_yojson = function
526 | `Assoc fields as json ->
527 let role =
528 match List.assoc_opt "role" fields with
529 | Some json -> Role.t_of_yojson json
530 | None -> Util.json_error "Missing role field" json
531 in
532 let content_obj =
533 match List.assoc_opt "content" fields with
534 | Some (`Assoc content_fields) -> content_fields
535 | _ -> Util.json_error "Missing or invalid content field" json
536 in
537 let content_type =
538 match List.assoc_opt "type" content_obj with
539 | Some (`String ty) -> ty
540 | _ ->
541 Util.json_error "Missing or invalid content type"
542 (`Assoc content_obj)
543 in
544 let content =
545 match content_type with
546 | "text" -> `Text (TextContent.t_of_yojson (`Assoc content_obj))
547 | "image" -> `Image (ImageContent.t_of_yojson (`Assoc content_obj))
548 | "audio" -> `Audio (AudioContent.t_of_yojson (`Assoc content_obj))
549 | _ ->
550 Util.json_error "Invalid content type: %s" (`Assoc content_obj)
551 content_type
552 in
553 { role; content }
554 | j -> Util.json_error "Expected object for SamplingMessage" j
555end
556
557(* Implementation info *)
558
559module Implementation = struct
560 type t = { name : string; version : string }
561
562 let yojson_of_t { name; version } =
563 `Assoc [ ("name", `String name); ("version", `String version) ]
564
565 let t_of_yojson = function
566 | `Assoc fields as json ->
567 let name = Util.get_string_field fields "name" json in
568 let version = Util.get_string_field fields "version" json in
569 { name; version }
570 | j -> Util.json_error "Expected object for Implementation" j
571end
572
573(* JSONRPC Message types *)
574
575module JSONRPCMessage = struct
576 type notification = { meth : Method.t; params : Json.t option }
577
578 type request = {
579 id : RequestId.t;
580 meth : Method.t;
581 params : Json.t option;
582 progress_token : ProgressToken.t option;
583 }
584
585 type response = { id : RequestId.t; result : Json.t }
586
587 type error = {
588 id : RequestId.t;
589 code : int;
590 message : string;
591 data : Json.t option;
592 }
593
594 type t =
595 | Notification of notification
596 | Request of request
597 | Response of response
598 | Error of error
599
600 let yojson_of_notification (n : notification) =
601 let assoc =
602 [
603 ("jsonrpc", `String "2.0"); ("method", `String (Method.to_string n.meth));
604 ]
605 in
606 let assoc =
607 match n.params with
608 | Some params -> ("params", params) :: assoc
609 | None -> assoc
610 in
611 `Assoc assoc
612
613 let yojson_of_request (r : request) =
614 let assoc =
615 [
616 ("jsonrpc", `String "2.0");
617 ("id", Id.yojson_of_t r.id);
618 ("method", `String (Method.to_string r.meth));
619 ]
620 in
621 let assoc =
622 match r.params with
623 | Some params ->
624 let params_json =
625 match params with
626 | `Assoc fields ->
627 let fields =
628 match r.progress_token with
629 | Some token ->
630 let meta =
631 `Assoc
632 [ ("progressToken", ProgressToken.yojson_of_t token) ]
633 in
634 ("_meta", meta) :: fields
635 | None -> fields
636 in
637 `Assoc fields
638 | _ -> params
639 in
640 ("params", params_json) :: assoc
641 | None -> assoc
642 in
643 `Assoc assoc
644
645 let yojson_of_response (r : response) =
646 `Assoc
647 [
648 ("jsonrpc", `String "2.0");
649 ("id", Id.yojson_of_t r.id);
650 ("result", r.result);
651 ]
652
653 let yojson_of_error (e : error) =
654 let error_assoc =
655 [ ("code", `Int e.code); ("message", `String e.message) ]
656 in
657 let error_assoc =
658 match e.data with
659 | Some data -> ("data", data) :: error_assoc
660 | None -> error_assoc
661 in
662 `Assoc
663 [
664 ("jsonrpc", `String "2.0");
665 ("id", Id.yojson_of_t e.id);
666 ("error", `Assoc error_assoc);
667 ]
668
669 let yojson_of_t = function
670 | Notification n -> yojson_of_notification n
671 | Request r -> yojson_of_request r
672 | Response r -> yojson_of_response r
673 | Error e -> yojson_of_error e
674
675 let notification_of_yojson = function
676 | `Assoc fields ->
677 let meth =
678 match List.assoc_opt "method" fields with
679 | Some (`String s) -> (
680 try Method.of_string s
681 with Failure msg -> Util.json_error "%s" (`String s) msg)
682 | _ ->
683 Util.json_error "Missing or invalid 'method' field"
684 (`Assoc fields)
685 in
686 let params = List.assoc_opt "params" fields in
687 { meth; params }
688 | j -> Util.json_error "Expected object for notification" j
689
690 let request_of_yojson = function
691 | `Assoc fields ->
692 let id =
693 match List.assoc_opt "id" fields with
694 | Some id_json -> Id.t_of_yojson id_json
695 | _ -> Util.json_error "Missing or invalid 'id' field" (`Assoc fields)
696 in
697 let meth =
698 match List.assoc_opt "method" fields with
699 | Some (`String s) -> (
700 try Method.of_string s
701 with Failure msg -> Util.json_error "%s" (`String s) msg)
702 | _ ->
703 Util.json_error "Missing or invalid 'method' field"
704 (`Assoc fields)
705 in
706 let params = List.assoc_opt "params" fields in
707 let progress_token =
708 match params with
709 | Some (`Assoc param_fields) -> (
710 match List.assoc_opt "_meta" param_fields with
711 | Some (`Assoc meta_fields) -> (
712 match List.assoc_opt "progressToken" meta_fields with
713 | Some token_json ->
714 Some (ProgressToken.t_of_yojson token_json)
715 | None -> None)
716 | _ -> None)
717 | _ -> None
718 in
719 { id; meth; params; progress_token }
720 | j -> Util.json_error "Expected object for request" j
721
722 let response_of_yojson = function
723 | `Assoc fields ->
724 let id =
725 match List.assoc_opt "id" fields with
726 | Some id_json -> Id.t_of_yojson id_json
727 | _ -> Util.json_error "Missing or invalid 'id' field" (`Assoc fields)
728 in
729 let result =
730 match List.assoc_opt "result" fields with
731 | Some result -> result
732 | _ -> Util.json_error "Missing 'result' field" (`Assoc fields)
733 in
734 { id; result }
735 | j -> Util.json_error "Expected object for response" j
736
737 let error_of_yojson = function
738 | `Assoc fields as json ->
739 let id =
740 match List.assoc_opt "id" fields with
741 | Some id_json -> Id.t_of_yojson id_json
742 | _ -> Util.json_error "Missing or invalid 'id' field" json
743 in
744 let error =
745 match List.assoc_opt "error" fields with
746 | Some (`Assoc error_fields) -> error_fields
747 | _ -> Util.json_error "Missing or invalid 'error' field" json
748 in
749 let code =
750 match List.assoc_opt "code" error with
751 | Some (`Int code) -> code
752 | _ ->
753 Util.json_error "Missing or invalid 'code' field in error"
754 (`Assoc error)
755 in
756 let message =
757 match List.assoc_opt "message" error with
758 | Some (`String msg) -> msg
759 | _ ->
760 Util.json_error "Missing or invalid 'message' field in error"
761 (`Assoc error)
762 in
763 let data = List.assoc_opt "data" error in
764 { id; code; message; data }
765 | j -> Util.json_error "Expected object for error" j
766
767 let t_of_yojson json =
768 match json with
769 | `Assoc fields ->
770 let _jsonrpc =
771 match List.assoc_opt "jsonrpc" fields with
772 | Some (`String "2.0") -> ()
773 | _ -> Util.json_error "Missing or invalid 'jsonrpc' field" json
774 in
775 if List.mem_assoc "method" fields then
776 if List.mem_assoc "id" fields then Request (request_of_yojson json)
777 else Notification (notification_of_yojson json)
778 else if List.mem_assoc "result" fields then
779 Response (response_of_yojson json)
780 else if List.mem_assoc "error" fields then Error (error_of_yojson json)
781 else Util.json_error "Invalid JSONRPC message format" json
782 | j -> Util.json_error "Expected object for JSONRPC message" j
783
784 let create_notification ?(params = None) ~meth () =
785 Notification { meth; params }
786
787 let create_request ?(params = None) ?(progress_token = None) ~id ~meth () =
788 Request { id; meth; params; progress_token }
789
790 let create_response ~id ~result = Response { id; result }
791
792 let create_error ~id ~code ~message ?(data = None) () =
793 Error { id; code; message; data }
794end
795
796(* MCP-specific request/response types *)
797
798module Initialize = struct
799 module Request = struct
800 type t = {
801 capabilities : Json.t; (* ClientCapabilities *)
802 client_info : Implementation.t;
803 protocol_version : string;
804 }
805
806 let yojson_of_t { capabilities; client_info; protocol_version } =
807 `Assoc
808 [
809 ("capabilities", capabilities);
810 ("clientInfo", Implementation.yojson_of_t client_info);
811 ("protocolVersion", `String protocol_version);
812 ]
813
814 let t_of_yojson = function
815 | `Assoc fields as json ->
816 let capabilities =
817 match List.assoc_opt "capabilities" fields with
818 | Some json -> json
819 | None -> Util.json_error "Missing capabilities field" json
820 in
821 let client_info =
822 match List.assoc_opt "clientInfo" fields with
823 | Some json -> Implementation.t_of_yojson json
824 | None -> Util.json_error "Missing clientInfo field" json
825 in
826 let protocol_version =
827 Util.get_string_field fields "protocolVersion" json
828 in
829 { capabilities; client_info; protocol_version }
830 | j -> Util.json_error "Expected object for InitializeRequest" j
831
832 let create ~capabilities ~client_info ~protocol_version =
833 { capabilities; client_info; protocol_version }
834
835 let to_jsonrpc ~id t =
836 let params = yojson_of_t t in
837 JSONRPCMessage.create_request ~id ~meth:Method.Initialize
838 ~params:(Some params) ()
839 end
840
841 module Result = struct
842 type t = {
843 capabilities : Json.t; (* ServerCapabilities *)
844 server_info : Implementation.t;
845 protocol_version : string;
846 instructions : string option;
847 meta : Json.t option;
848 }
849
850 let yojson_of_t
851 { capabilities; server_info; protocol_version; instructions; meta } =
852 let assoc =
853 [
854 ("capabilities", capabilities);
855 ("serverInfo", Implementation.yojson_of_t server_info);
856 ("protocolVersion", `String protocol_version);
857 ]
858 in
859 let assoc =
860 match instructions with
861 | Some instr -> ("instructions", `String instr) :: assoc
862 | None -> assoc
863 in
864 let assoc =
865 match meta with Some meta -> ("_meta", meta) :: assoc | None -> assoc
866 in
867 `Assoc assoc
868
869 let t_of_yojson = function
870 | `Assoc fields as json ->
871 let capabilities =
872 match List.assoc_opt "capabilities" fields with
873 | Some json -> json
874 | None -> Util.json_error "Missing capabilities field" json
875 in
876 let server_info =
877 match List.assoc_opt "serverInfo" fields with
878 | Some json -> Implementation.t_of_yojson json
879 | None -> Util.json_error "Missing serverInfo field" json
880 in
881 let protocol_version =
882 Util.get_string_field fields "protocolVersion" json
883 in
884 let instructions =
885 Util.get_optional_string_field fields "instructions"
886 in
887 let meta = List.assoc_opt "_meta" fields in
888 { capabilities; server_info; protocol_version; instructions; meta }
889 | j -> Util.json_error "Expected object for InitializeResult" j
890
891 let create ~capabilities ~server_info ~protocol_version ?instructions ?meta
892 () =
893 { capabilities; server_info; protocol_version; instructions; meta }
894
895 let to_jsonrpc ~id t =
896 JSONRPCMessage.create_response ~id ~result:(yojson_of_t t)
897 end
898end
899
900module Initialized = struct
901 module Notification = struct
902 type t = { meta : Json.t option }
903
904 let yojson_of_t { meta } =
905 let assoc = [] in
906 let assoc =
907 match meta with Some meta -> ("_meta", meta) :: assoc | None -> assoc
908 in
909 `Assoc assoc
910
911 let t_of_yojson = function
912 | `Assoc fields ->
913 let meta = List.assoc_opt "_meta" fields in
914 { meta }
915 | j -> Util.json_error "Expected object for InitializedNotification" j
916
917 let create ?meta () = { meta }
918
919 let to_jsonrpc t =
920 let params =
921 match yojson_of_t t with `Assoc [] -> None | json -> Some json
922 in
923 JSONRPCMessage.create_notification ~meth:Method.Initialized ~params ()
924 end
925end
926
927(* Export the main interface for using the MCP protocol *)
928
929let parse_message json = JSONRPCMessage.t_of_yojson json
930
931let create_notification ?(params = None) ~meth () =
932 JSONRPCMessage.create_notification ~params ~meth ()
933
934let create_request ?(params = None) ?(progress_token = None) ~id ~meth () =
935 JSONRPCMessage.create_request ~params ~progress_token ~id ~meth ()
936
937let create_response = JSONRPCMessage.create_response
938let create_error = JSONRPCMessage.create_error
939
940(* Content type constructors *)
941let make_text_content text = Text TextContent.{ text; annotations = None }
942
943let make_image_content data mime_type =
944 Image ImageContent.{ data; mime_type; annotations = None }
945
946let make_audio_content data mime_type =
947 Audio AudioContent.{ data; mime_type; annotations = None }
948
949let make_resource_text_content uri text mime_type =
950 Resource
951 EmbeddedResource.
952 {
953 resource = `Text TextResourceContents.{ uri; text; mime_type };
954 annotations = None;
955 }
956
957let make_resource_blob_content uri blob mime_type =
958 Resource
959 EmbeddedResource.
960 {
961 resource = `Blob BlobResourceContents.{ uri; blob; mime_type };
962 annotations = None;
963 }