Model Context Protocol in OCaml

Use ocamlformat

Signed-off-by: Marcello Seri <marcello.seri@gmail.com>

.ocamlformat

This is a binary file and will not be displayed.

+65 -77
bin/capitalize_sdk.ml
···
(* Helper for extracting string value from JSON *)
let get_string_param json name =
match json with
-
| `Assoc fields ->
-
(match List.assoc_opt name fields with
-
| Some (`String value) -> value
-
| _ -> raise (Failure (Printf.sprintf "Missing or invalid parameter: %s" name)))
+
| `Assoc fields -> (
+
match List.assoc_opt name fields with
+
| Some (`String value) -> value
+
| _ ->
+
raise
+
(Failure (Printf.sprintf "Missing or invalid parameter: %s" name)))
| _ -> raise (Failure "Expected JSON object")
(* Create a server *)
-
let server = create_server
-
~name:"OCaml MCP Capitalizer"
-
~version:"0.1.0"
-
~protocol_version:"2024-11-05" () |>
-
fun server ->
+
let server =
+
create_server ~name:"OCaml MCP Capitalizer" ~version:"0.1.0"
+
~protocol_version:"2024-11-05" ()
+
|> fun server ->
(* Set default capabilities *)
-
configure_server server ~with_tools:true ~with_resources:true ~with_prompts:true ()
+
configure_server server ~with_tools:true ~with_resources:true
+
~with_prompts:true ()
(* Define and register a capitalize tool *)
-
let _ = add_tool server
-
~name:"capitalize"
-
~description:"Capitalizes the provided text"
-
~schema_properties:[
-
("text", "string", "The text to capitalize")
-
]
-
~schema_required:["text"]
-
(fun args ->
-
try
-
let text = get_string_param args "text" in
-
let capitalized_text = String.uppercase_ascii text in
-
TextContent.yojson_of_t TextContent.{
-
text = capitalized_text;
-
annotations = None
-
}
-
with
-
| Failure msg ->
-
Log.errorf "Error in capitalize tool: %s" msg;
-
TextContent.yojson_of_t TextContent.{
-
text = Printf.sprintf "Error: %s" msg;
-
annotations = None
-
}
-
)
+
let _ =
+
add_tool server ~name:"capitalize"
+
~description:"Capitalizes the provided text"
+
~schema_properties:[ ("text", "string", "The text to capitalize") ]
+
~schema_required:[ "text" ]
+
(fun args ->
+
try
+
let text = get_string_param args "text" in
+
let capitalized_text = String.uppercase_ascii text in
+
TextContent.yojson_of_t
+
TextContent.{ text = capitalized_text; annotations = None }
+
with Failure msg ->
+
Log.errorf "Error in capitalize tool: %s" msg;
+
TextContent.yojson_of_t
+
TextContent.
+
{ text = Printf.sprintf "Error: %s" msg; annotations = None })
(* Define and register a resource template example *)
-
let _ = add_resource_template server
-
~uri_template:"greeting://{name}"
-
~name:"Greeting"
-
~description:"Get a greeting for a name"
-
~mime_type:"text/plain"
-
(fun params ->
-
match params with
-
| [name] -> Printf.sprintf "Hello, %s! Welcome to the OCaml MCP server." name
-
| _ -> "Hello, world! Welcome to the OCaml MCP server."
-
)
+
let _ =
+
add_resource_template server ~uri_template:"greeting://{name}"
+
~name:"Greeting" ~description:"Get a greeting for a name"
+
~mime_type:"text/plain" (fun params ->
+
match params with
+
| [ name ] ->
+
Printf.sprintf "Hello, %s! Welcome to the OCaml MCP server." name
+
| _ -> "Hello, world! Welcome to the OCaml MCP server.")
(* Define and register a prompt example *)
-
let _ = add_prompt server
-
~name:"capitalize-prompt"
-
~description:"A prompt to help with text capitalization"
-
~arguments:[
-
("text", Some "The text to be capitalized", true)
-
]
-
(fun args ->
-
let text =
-
try
-
List.assoc "text" args
-
with
-
| Not_found -> "No text provided"
-
in
-
[
-
Prompt.{
-
role = `User;
-
content = Mcp.make_text_content "Please help me capitalize the following text:"
-
};
-
Prompt.{
-
role = `User;
-
content = Mcp.make_text_content text
-
};
-
Prompt.{
-
role = `Assistant;
-
content = Mcp.make_text_content "Here's the capitalized version:"
-
};
-
Prompt.{
-
role = `Assistant;
-
content = Mcp.make_text_content (String.uppercase_ascii text)
-
}
-
]
-
)
+
let _ =
+
add_prompt server ~name:"capitalize-prompt"
+
~description:"A prompt to help with text capitalization"
+
~arguments:[ ("text", Some "The text to be capitalized", true) ]
+
(fun args ->
+
let text =
+
try List.assoc "text" args with Not_found -> "No text provided"
+
in
+
[
+
Prompt.
+
{
+
role = `User;
+
content =
+
Mcp.make_text_content
+
"Please help me capitalize the following text:";
+
};
+
Prompt.{ role = `User; content = Mcp.make_text_content text };
+
Prompt.
+
{
+
role = `Assistant;
+
content = Mcp.make_text_content "Here's the capitalized version:";
+
};
+
Prompt.
+
{
+
role = `Assistant;
+
content = Mcp.make_text_content (String.uppercase_ascii text);
+
};
+
])
let () =
(* Run the server with the default scheduler *)
-
Eio_main.run @@ fun env->
-
Mcp_server.run_server env server
+
Eio_main.run @@ fun env -> Mcp_server.run_server env server
+10 -2
bin/dune
···
(name ocaml_eval_sdk)
(modes byte)
(modules ocaml_eval_sdk)
-
(flags (:standard -w -32 -w -33))
-
(libraries mcp mcp_sdk mcp_server yojson eio_main eio compiler-libs.toplevel))
+
(flags
+
(:standard -w -32 -w -33))
+
(libraries
+
mcp
+
mcp_sdk
+
mcp_server
+
yojson
+
eio_main
+
eio
+
compiler-libs.toplevel))
(executable
(name markdown_book_sdk)
+90 -97
bin/markdown_book_sdk.ml
···
(* Helper module for working with markdown book chapters *)
module BookChapter = struct
-
type t = {
-
id: string;
-
title: string;
-
contents: string;
-
}
+
type t = { id : string; title : string; contents : string }
(* Book chapters as a series of markdown files *)
-
let chapters = [
-
{
-
id = "chapter1";
-
title = "# Introduction to OCaml";
-
contents = {|
+
let chapters =
+
[
+
{
+
id = "chapter1";
+
title = "# Introduction to OCaml";
+
contents =
+
{|
# Introduction to OCaml
OCaml is a general-purpose, multi-paradigm programming language which extends the Caml dialect of ML with object-oriented features.
···
- **Web Development**: Modern frameworks like Dream make web development straightforward
In the following chapters, we'll explore the language features in depth and learn how to leverage OCaml's strengths for building robust, maintainable software.
-
|}
-
};
-
{
-
id = "chapter2";
-
title = "# Basic Syntax and Types";
-
contents = {|
+
|};
+
};
+
{
+
id = "chapter2";
+
title = "# Basic Syntax and Types";
+
contents =
+
{|
# Basic Syntax and Types
OCaml has a clean, consistent syntax that emphasizes readability and minimizes boilerplate.
···
```
This introduction to basic syntax sets the foundation for understanding OCaml's more advanced features, which we'll explore in the next chapters.
-
|}
-
};
-
{
-
id = "chapter3";
-
title = "# Data Structures";
-
contents = {|
+
|};
+
};
+
{
+
id = "chapter3";
+
title = "# Data Structures";
+
contents =
+
{|
# Data Structures
OCaml provides several built-in data structures and makes it easy to define custom ones.
···
```
These data structures form the backbone of OCaml programming and allow for expressing complex data relationships in a type-safe way.
-
|}
-
};
-
{
-
id = "chapter4";
-
title = "# Modules and Functors";
-
contents = {|
+
|};
+
};
+
{
+
id = "chapter4";
+
title = "# Modules and Functors";
+
contents =
+
{|
# Modules and Functors
OCaml's module system is one of its most powerful features. It allows for organizing code into reusable components with clear interfaces.
···
```
The module system enables OCaml programmers to build highly modular, reusable code with clear boundaries between components.
-
|}
-
};
-
{
-
id = "chapter5";
-
title = "# Advanced Features";
-
contents = {|
+
|};
+
};
+
{
+
id = "chapter5";
+
title = "# Advanced Features";
+
contents =
+
{|
# Advanced Features
OCaml offers several advanced features that set it apart from other languages. This chapter explores some of the more powerful language constructs.
···
```
These advanced features make OCaml a uniquely powerful language for expressing complex programs with strong guarantees about correctness.
-
|}
-
};
-
]
+
|};
+
};
+
]
(* Get a chapter by ID *)
let get_by_id id =
-
try Some (List.find (fun c -> c.id = id) chapters)
-
with Not_found -> None
-
+
try Some (List.find (fun c -> c.id = id) chapters) with Not_found -> None
+
(* Get chapter titles *)
-
let get_all_titles () =
-
List.map (fun c -> (c.id, c.title)) chapters
+
let get_all_titles () = List.map (fun c -> (c.id, c.title)) chapters
end
(* Create a server *)
-
let server = create_server
-
~name:"OCaml MCP Book Resource Example"
-
~version:"0.1.0" () |>
-
fun server ->
-
(* Set default capabilities *)
-
configure_server server
-
~with_tools:false
-
~with_resources:true
-
~with_resource_templates:true
-
~with_prompts:false ()
+
let server =
+
create_server ~name:"OCaml MCP Book Resource Example" ~version:"0.1.0" ()
+
|> fun server ->
+
(* Set default capabilities *)
+
configure_server server ~with_tools:false ~with_resources:true
+
~with_resource_templates:true ~with_prompts:false ()
(* Add a resource template to get book chapters *)
-
let _ = add_resource_template server
-
~uri_template:"book/chapter/{id}"
-
~name:"Chapter Resource"
-
~description:"Get a specific chapter from the OCaml book by its ID"
-
~mime_type:"text/markdown"
-
(fun params ->
-
match params with
-
| [id] ->
-
(match BookChapter.get_by_id id with
-
| Some chapter -> chapter.contents
-
| None -> Printf.sprintf "# Error\n\nChapter with ID '%s' not found." id)
-
| _ -> "# Error\n\nInvalid parameters. Expected chapter ID."
-
)
+
let _ =
+
add_resource_template server ~uri_template:"book/chapter/{id}"
+
~name:"Chapter Resource"
+
~description:"Get a specific chapter from the OCaml book by its ID"
+
~mime_type:"text/markdown" (fun params ->
+
match params with
+
| [ id ] -> (
+
match BookChapter.get_by_id id with
+
| Some chapter -> chapter.contents
+
| None ->
+
Printf.sprintf "# Error\n\nChapter with ID '%s' not found." id)
+
| _ -> "# Error\n\nInvalid parameters. Expected chapter ID.")
(* Add a regular resource to get table of contents (no variables) *)
-
let _ = add_resource server
-
~uri:"book/toc"
-
~name:"Table of Contents"
-
~description:"Get the table of contents for the OCaml book"
-
~mime_type:"text/markdown"
-
(fun _params ->
-
let titles = BookChapter.get_all_titles() in
-
let toc = "# OCaml Book - Table of Contents\n\n" ^
-
(List.mapi (fun i (id, title) ->
-
Printf.sprintf "%d. [%s](book/chapter/%s)\n"
-
(i + 1)
-
(String.sub title 2 (String.length title - 2)) (* Remove "# " prefix *)
-
id
-
) titles |> String.concat "")
-
in
-
toc
-
)
+
let _ =
+
add_resource server ~uri:"book/toc" ~name:"Table of Contents"
+
~description:"Get the table of contents for the OCaml book"
+
~mime_type:"text/markdown" (fun _params ->
+
let titles = BookChapter.get_all_titles () in
+
let toc =
+
"# OCaml Book - Table of Contents\n\n"
+
^ (List.mapi
+
(fun i (id, title) ->
+
Printf.sprintf "%d. [%s](book/chapter/%s)\n" (i + 1)
+
(String.sub title 2 (String.length title - 2))
+
(* Remove "# " prefix *)
+
id)
+
titles
+
|> String.concat "")
+
in
+
toc)
(* Add a regular resource for a complete book (no variables) *)
-
let _ = add_resource server
-
~uri:"book/complete"
-
~name:"Full contents"
-
~description:"Get the complete OCaml book as a single document"
-
~mime_type:"text/markdown"
-
(fun _params ->
-
let chapter_contents = List.map (fun c -> c.BookChapter.contents) BookChapter.chapters in
-
let content = "# The OCaml Book\n\n*A comprehensive guide to OCaml programming*\n\n" ^
-
(String.concat "\n\n---\n\n" chapter_contents)
-
in
-
content
-
)
+
let _ =
+
add_resource server ~uri:"book/complete" ~name:"Full contents"
+
~description:"Get the complete OCaml book as a single document"
+
~mime_type:"text/markdown" (fun _params ->
+
let chapter_contents =
+
List.map (fun c -> c.BookChapter.contents) BookChapter.chapters
+
in
+
let content =
+
"# The OCaml Book\n\n*A comprehensive guide to OCaml programming*\n\n"
+
^ String.concat "\n\n---\n\n" chapter_contents
+
in
+
content)
(* Run the server with the default scheduler *)
-
let () =
-
Eio_main.run @@ fun env ->
-
Mcp_server.run_server env server
+
let () = Eio_main.run @@ fun env -> Mcp_server.run_server env server
+265 -204
bin/multimodal_sdk.ml
···
(* Helper for extracting string value from JSON *)
let get_string_param json name =
match json with
-
| `Assoc fields ->
-
(match List.assoc_opt name fields with
-
| Some (`String value) -> value
-
| _ -> raise (Failure (Printf.sprintf "Missing or invalid parameter: %s" name)))
+
| `Assoc fields -> (
+
match List.assoc_opt name fields with
+
| Some (`String value) -> value
+
| _ ->
+
raise
+
(Failure (Printf.sprintf "Missing or invalid parameter: %s" name)))
| _ -> raise (Failure "Expected JSON object")
(* Helper for extracting integer value from JSON *)
let get_int_param json name =
match json with
-
| `Assoc fields ->
-
(match List.assoc_opt name fields with
-
| Some (`Int value) -> value
-
| Some (`String value) -> int_of_string value
-
| _ -> raise (Failure (Printf.sprintf "Missing or invalid parameter: %s" name)))
+
| `Assoc fields -> (
+
match List.assoc_opt name fields with
+
| Some (`Int value) -> value
+
| Some (`String value) -> int_of_string value
+
| _ ->
+
raise
+
(Failure (Printf.sprintf "Missing or invalid parameter: %s" name)))
| _ -> raise (Failure "Expected JSON object")
(* Base64 encoding - simplified version *)
module Base64 = struct
let encode_char idx =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".[idx]
-
+
let encode s =
let len = String.length s in
-
let result = Bytes.create (((len + 2) / 3) * 4) in
-
+
let result = Bytes.create ((len + 2) / 3 * 4) in
+
let rec loop i j =
if i >= len then j
else
let n =
let n = Char.code s.[i] lsl 16 in
-
let n = if i + 1 < len then n lor (Char.code s.[i+1] lsl 8) else n in
-
if i + 2 < len then n lor Char.code s.[i+2] else n
+
let n =
+
if i + 1 < len then n lor (Char.code s.[i + 1] lsl 8) else n
+
in
+
if i + 2 < len then n lor Char.code s.[i + 2] else n
in
Bytes.set result j (encode_char ((n lsr 18) land 63));
-
Bytes.set result (j+1) (encode_char ((n lsr 12) land 63));
-
Bytes.set result (j+2)
+
Bytes.set result (j + 1) (encode_char ((n lsr 12) land 63));
+
Bytes.set result (j + 2)
(if i + 1 < len then encode_char ((n lsr 6) land 63) else '=');
-
Bytes.set result (j+3)
+
Bytes.set result (j + 3)
(if i + 2 < len then encode_char (n land 63) else '=');
loop (i + 3) (j + 4)
in
···
(* Generate a simple GIF format image *)
let generate_random_image width height =
(* Ensure dimensions are reasonable *)
-
let width = min 256 (max 16 width) in
+
let width = min 256 (max 16 width) in
let height = min 256 (max 16 height) in
-
+
(* Create a buffer for GIF data *)
let buf = Buffer.create 1024 in
-
+
(* GIF Header - "GIF89a" *)
Buffer.add_string buf "GIF89a";
-
+
(* Logical Screen Descriptor *)
(* Width - 2 bytes little endian *)
Buffer.add_char buf (Char.chr (width land 0xff));
Buffer.add_char buf (Char.chr ((width lsr 8) land 0xff));
-
+
(* Height - 2 bytes little endian *)
Buffer.add_char buf (Char.chr (height land 0xff));
Buffer.add_char buf (Char.chr ((height lsr 8) land 0xff));
-
+
(* Packed fields - 1 byte:
Global Color Table Flag - 1 bit (1)
Color Resolution - 3 bits (7 = 8 bits per color)
Sort Flag - 1 bit (0)
Size of Global Color Table - 3 bits (2 = 8 colors) *)
Buffer.add_char buf (Char.chr 0xF2);
-
+
(* Background color index - 1 byte *)
Buffer.add_char buf (Char.chr 0);
-
+
(* Pixel aspect ratio - 1 byte *)
Buffer.add_char buf (Char.chr 0);
-
+
(* Global Color Table - 8 colors x 3 bytes (R,G,B) *)
(* Simple 8-color palette *)
-
Buffer.add_string buf "\xFF\xFF\xFF"; (* White (0) *)
-
Buffer.add_string buf "\xFF\x00\x00"; (* Red (1) *)
-
Buffer.add_string buf "\x00\xFF\x00"; (* Green (2) *)
-
Buffer.add_string buf "\x00\x00\xFF"; (* Blue (3) *)
-
Buffer.add_string buf "\xFF\xFF\x00"; (* Yellow (4) *)
-
Buffer.add_string buf "\xFF\x00\xFF"; (* Magenta (5) *)
-
Buffer.add_string buf "\x00\xFF\xFF"; (* Cyan (6) *)
-
Buffer.add_string buf "\x00\x00\x00"; (* Black (7) *)
-
+
Buffer.add_string buf "\xFF\xFF\xFF";
+
(* White (0) *)
+
Buffer.add_string buf "\xFF\x00\x00";
+
(* Red (1) *)
+
Buffer.add_string buf "\x00\xFF\x00";
+
(* Green (2) *)
+
Buffer.add_string buf "\x00\x00\xFF";
+
(* Blue (3) *)
+
Buffer.add_string buf "\xFF\xFF\x00";
+
(* Yellow (4) *)
+
Buffer.add_string buf "\xFF\x00\xFF";
+
(* Magenta (5) *)
+
Buffer.add_string buf "\x00\xFF\xFF";
+
(* Cyan (6) *)
+
Buffer.add_string buf "\x00\x00\x00";
+
+
(* Black (7) *)
+
(* Graphics Control Extension (optional) *)
-
Buffer.add_char buf (Char.chr 0x21); (* Extension Introducer *)
-
Buffer.add_char buf (Char.chr 0xF9); (* Graphic Control Label *)
-
Buffer.add_char buf (Char.chr 0x04); (* Block Size *)
-
Buffer.add_char buf (Char.chr 0x01); (* Packed field: 1 bit for transparency *)
-
Buffer.add_char buf (Char.chr 0x00); (* Delay time (1/100s) - 2 bytes *)
+
Buffer.add_char buf (Char.chr 0x21);
+
(* Extension Introducer *)
+
Buffer.add_char buf (Char.chr 0xF9);
+
(* Graphic Control Label *)
+
Buffer.add_char buf (Char.chr 0x04);
+
(* Block Size *)
+
Buffer.add_char buf (Char.chr 0x01);
+
(* Packed field: 1 bit for transparency *)
+
Buffer.add_char buf (Char.chr 0x00);
+
(* Delay time (1/100s) - 2 bytes *)
+
Buffer.add_char buf (Char.chr 0x00);
+
Buffer.add_char buf (Char.chr 0x00);
+
(* Transparent color index *)
Buffer.add_char buf (Char.chr 0x00);
-
Buffer.add_char buf (Char.chr 0x00); (* Transparent color index *)
-
Buffer.add_char buf (Char.chr 0x00); (* Block terminator *)
-
+
+
(* Block terminator *)
+
(* Image Descriptor *)
-
Buffer.add_char buf (Char.chr 0x2C); (* Image Separator *)
-
Buffer.add_char buf (Char.chr 0x00); (* Left position - 2 bytes *)
+
Buffer.add_char buf (Char.chr 0x2C);
+
(* Image Separator *)
+
Buffer.add_char buf (Char.chr 0x00);
+
(* Left position - 2 bytes *)
Buffer.add_char buf (Char.chr 0x00);
-
Buffer.add_char buf (Char.chr 0x00); (* Top position - 2 bytes *)
+
Buffer.add_char buf (Char.chr 0x00);
+
(* Top position - 2 bytes *)
Buffer.add_char buf (Char.chr 0x00);
-
+
(* Image width - 2 bytes little endian *)
Buffer.add_char buf (Char.chr (width land 0xff));
Buffer.add_char buf (Char.chr ((width lsr 8) land 0xff));
-
+
(* Image height - 2 bytes little endian *)
Buffer.add_char buf (Char.chr (height land 0xff));
Buffer.add_char buf (Char.chr ((height lsr 8) land 0xff));
-
+
(* Packed fields - 1 byte - no local color table *)
Buffer.add_char buf (Char.chr 0x00);
-
+
(* LZW Minimum Code Size - 1 byte *)
-
Buffer.add_char buf (Char.chr 0x03); (* Minimum code size 3 for 8 colors *)
-
+
Buffer.add_char buf (Char.chr 0x03);
+
+
(* Minimum code size 3 for 8 colors *)
+
(* Generate a simple image - a checkerboard pattern *)
let step = width / 8 in
let image_data = Buffer.create (width * height / 4) in
-
+
(* Very simple LZW compression - just store raw clear codes and color indexes *)
(* Start with Clear code *)
-
Buffer.add_char image_data (Char.chr 0x08); (* Clear code 8 *)
-
+
Buffer.add_char image_data (Char.chr 0x08);
+
+
(* Clear code 8 *)
+
(* For very simple encoding, we'll just use a sequence of color indexes *)
for y = 0 to height - 1 do
for x = 0 to width - 1 do
(* Checkerboard pattern with different colors *)
let color =
-
if ((x / step) + (y / step)) mod 2 = 0 then
-
3 (* Blue *)
-
else
-
1 (* Red *)
+
if ((x / step) + (y / step)) mod 2 = 0 then 3 (* Blue *)
+
else 1 (* Red *)
in
-
Buffer.add_char image_data (Char.chr color);
+
Buffer.add_char image_data (Char.chr color)
done
done;
-
+
(* End with End of Information code *)
Buffer.add_char image_data (Char.chr 0x09);
-
+
(* Add image data blocks - GIF uses 255-byte max chunks *)
let data = Buffer.contents image_data in
let data_len = String.length data in
let pos = ref 0 in
-
+
while !pos < data_len do
let chunk_size = min 255 (data_len - !pos) in
Buffer.add_char buf (Char.chr chunk_size);
for i = 0 to chunk_size - 1 do
-
Buffer.add_char buf (String.get data (!pos + i));
+
Buffer.add_char buf (String.get data (!pos + i))
done;
-
pos := !pos + chunk_size;
+
pos := !pos + chunk_size
done;
-
+
(* Zero-length block to end the image data *)
Buffer.add_char buf (Char.chr 0x00);
-
+
(* GIF Trailer *)
Buffer.add_char buf (Char.chr 0x3B);
-
+
(* Base64 encode the GIF data *)
Base64.encode (Buffer.contents buf)
···
let sample_rate = 8000 in
let num_samples = sample_rate * duration in
let header_buf = Buffer.create 44 in
-
+
(* Fill WAV header properly *)
Buffer.add_string header_buf "RIFF";
-
write_int32_le header_buf (36 + num_samples * 2); (* File size minus 8 *)
+
write_int32_le header_buf (36 + (num_samples * 2));
+
(* File size minus 8 *)
Buffer.add_string header_buf "WAVE";
-
+
(* Format chunk *)
Buffer.add_string header_buf "fmt ";
-
write_int32_le header_buf 16; (* Format chunk size *)
-
write_int16_le header_buf 1; (* PCM format *)
-
write_int16_le header_buf 1; (* Mono *)
-
write_int32_le header_buf sample_rate; (* Sample rate *)
-
write_int32_le header_buf (sample_rate * 2); (* Byte rate *)
-
write_int16_le header_buf 2; (* Block align *)
-
write_int16_le header_buf 16; (* Bits per sample *)
-
+
write_int32_le header_buf 16;
+
(* Format chunk size *)
+
write_int16_le header_buf 1;
+
(* PCM format *)
+
write_int16_le header_buf 1;
+
(* Mono *)
+
write_int32_le header_buf sample_rate;
+
(* Sample rate *)
+
write_int32_le header_buf (sample_rate * 2);
+
(* Byte rate *)
+
write_int16_le header_buf 2;
+
(* Block align *)
+
write_int16_le header_buf 16;
+
+
(* Bits per sample *)
+
(* Data chunk *)
Buffer.add_string header_buf "data";
-
write_int32_le header_buf (num_samples * 2); (* Data size *)
-
+
write_int32_le header_buf (num_samples * 2);
+
+
(* Data size *)
+
(* Generate sine wave samples *)
let samples_buf = Buffer.create (num_samples * 2) in
-
let amplitude = 16384.0 in (* 16-bit with headroom *)
-
+
let amplitude = 16384.0 in
+
(* 16-bit with headroom *)
+
for i = 0 to num_samples - 1 do
let t = float_of_int i /. float_of_int sample_rate in
let value = amplitude *. sin (2.0 *. Float.pi *. frequency *. t) in
let sample = int_of_float value in
-
+
(* Convert to 16-bit little-endian *)
let sample = if sample < 0 then sample + 65536 else sample in
-
write_int16_le samples_buf sample;
+
write_int16_le samples_buf sample
done;
-
+
(* Combine header and samples, then encode as Base64 *)
let wav_data = Buffer.contents header_buf ^ Buffer.contents samples_buf in
Base64.encode wav_data
(* Create a server *)
-
let server = create_server
-
~name:"OCaml MCP Multimodal Example"
-
~version:"0.1.0"
-
~protocol_version:"2024-11-05" () |>
-
fun server ->
-
(* Set default capabilities *)
-
configure_server server ~with_tools:true ~with_resources:true ~with_prompts:true ()
+
let server =
+
create_server ~name:"OCaml MCP Multimodal Example" ~version:"0.1.0"
+
~protocol_version:"2024-11-05" ()
+
|> fun server ->
+
(* Set default capabilities *)
+
configure_server server ~with_tools:true ~with_resources:true
+
~with_prompts:true ()
(* Define and register a multimodal tool that returns text, images, and audio *)
-
let _ = add_tool server
-
~name:"multimodal_demo"
-
~description:"Demonstrates multimodal content with text, image, and audio"
-
~schema_properties:[
-
("width", "integer", "Width of the generated image (pixels)");
-
("height", "integer", "Height of the generated image (pixels)");
-
("frequency", "integer", "Frequency of the generated audio tone (Hz)");
-
("duration", "integer", "Duration of the generated audio (seconds)");
-
("message", "string", "Text message to include")
-
]
-
~schema_required:["message"]
-
(fun args ->
-
try
-
(* Extract parameters with defaults if not provided *)
-
let message = get_string_param args "message" in
-
let width = try get_int_param args "width" with _ -> 128 in
-
let height = try get_int_param args "height" with _ -> 128 in
-
let frequency = try get_int_param args "frequency" with _ -> 440 in
-
let duration = try get_int_param args "duration" with _ -> 1 in
-
-
(* Generate image and audio data *)
-
let image_data = generate_random_image width height in
-
let audio_data = generate_sine_wave_audio (float_of_int frequency) duration in
-
-
(* Create a multimodal tool result *)
-
Tool.create_tool_result [
-
Mcp.make_text_content message;
-
Mcp.make_image_content image_data "image/gif";
-
Mcp.make_audio_content audio_data "audio/wav"
-
] ~is_error:false
-
with
-
| Failure msg ->
-
Log.errorf "Error in multimodal tool: %s" msg;
-
Tool.create_tool_result [Mcp.make_text_content (Printf.sprintf "Error: %s" msg)] ~is_error:true
-
)
+
let _ =
+
add_tool server ~name:"multimodal_demo"
+
~description:"Demonstrates multimodal content with text, image, and audio"
+
~schema_properties:
+
[
+
("width", "integer", "Width of the generated image (pixels)");
+
("height", "integer", "Height of the generated image (pixels)");
+
("frequency", "integer", "Frequency of the generated audio tone (Hz)");
+
("duration", "integer", "Duration of the generated audio (seconds)");
+
("message", "string", "Text message to include");
+
]
+
~schema_required:[ "message" ]
+
(fun args ->
+
try
+
(* Extract parameters with defaults if not provided *)
+
let message = get_string_param args "message" in
+
let width = try get_int_param args "width" with _ -> 128 in
+
let height = try get_int_param args "height" with _ -> 128 in
+
let frequency = try get_int_param args "frequency" with _ -> 440 in
+
let duration = try get_int_param args "duration" with _ -> 1 in
-
(* Define and register a tool for generating only images *)
-
let _ = add_tool server
-
~name:"generate_image"
-
~description:"Generates a random image with specified dimensions"
-
~schema_properties:[
-
("width", "integer", "Width of the generated image (pixels)");
-
("height", "integer", "Height of the generated image (pixels)")
-
]
-
~schema_required:["width"; "height"]
-
(fun args ->
-
try
-
let width = get_int_param args "width" in
-
let height = get_int_param args "height" in
-
-
if width < 1 || width > 1024 || height < 1 || height > 1024 then
-
Tool.create_tool_result
-
[Mcp.make_text_content "Error: Dimensions must be between 1 and 1024 pixels"]
-
~is_error:true
-
else
+
(* Generate image and audio data *)
let image_data = generate_random_image width height in
-
Tool.create_tool_result
-
[Mcp.make_image_content image_data "image/gif"]
+
let audio_data =
+
generate_sine_wave_audio (float_of_int frequency) duration
+
in
+
+
(* Create a multimodal tool result *)
+
Tool.create_tool_result
+
[
+
Mcp.make_text_content message;
+
Mcp.make_image_content image_data "image/gif";
+
Mcp.make_audio_content audio_data "audio/wav";
+
]
~is_error:false
-
with
-
| Failure msg ->
-
Log.errorf "Error in generate_image tool: %s" msg;
-
Tool.create_tool_result [Mcp.make_text_content (Printf.sprintf "Error: %s" msg)] ~is_error:true
-
)
+
with Failure msg ->
+
Log.errorf "Error in multimodal tool: %s" msg;
+
Tool.create_tool_result
+
[ Mcp.make_text_content (Printf.sprintf "Error: %s" msg) ]
+
~is_error:true)
+
+
(* Define and register a tool for generating only images *)
+
let _ =
+
add_tool server ~name:"generate_image"
+
~description:"Generates a random image with specified dimensions"
+
~schema_properties:
+
[
+
("width", "integer", "Width of the generated image (pixels)");
+
("height", "integer", "Height of the generated image (pixels)");
+
]
+
~schema_required:[ "width"; "height" ]
+
(fun args ->
+
try
+
let width = get_int_param args "width" in
+
let height = get_int_param args "height" in
+
+
if width < 1 || width > 1024 || height < 1 || height > 1024 then
+
Tool.create_tool_result
+
[
+
Mcp.make_text_content
+
"Error: Dimensions must be between 1 and 1024 pixels";
+
]
+
~is_error:true
+
else
+
let image_data = generate_random_image width height in
+
Tool.create_tool_result
+
[ Mcp.make_image_content image_data "image/gif" ]
+
~is_error:false
+
with Failure msg ->
+
Log.errorf "Error in generate_image tool: %s" msg;
+
Tool.create_tool_result
+
[ Mcp.make_text_content (Printf.sprintf "Error: %s" msg) ]
+
~is_error:true)
(* Define and register a tool for generating only audio *)
-
let _ = add_tool server
-
~name:"generate_audio"
-
~description:"Generates an audio tone with specified frequency and duration"
-
~schema_properties:[
-
("frequency", "integer", "Frequency of the tone in Hz (20-20000)");
-
("duration", "integer", "Duration of the tone in seconds (1-10)")
-
]
-
~schema_required:["frequency"; "duration"]
-
(fun args ->
-
try
-
let frequency = get_int_param args "frequency" in
-
let duration = get_int_param args "duration" in
-
-
if frequency < 20 || frequency > 20000 then
-
Tool.create_tool_result
-
[Mcp.make_text_content "Error: Frequency must be between 20Hz and 20,000Hz"]
-
~is_error:true
-
else if duration < 1 || duration > 10 then
-
Tool.create_tool_result
-
[Mcp.make_text_content "Error: Duration must be between 1 and 10 seconds"]
-
~is_error:true
-
else
-
let audio_data = generate_sine_wave_audio (float_of_int frequency) duration in
-
Tool.create_tool_result
-
[Mcp.make_audio_content audio_data "audio/wav"]
-
~is_error:false
-
with
-
| Failure msg ->
-
Log.errorf "Error in generate_audio tool: %s" msg;
-
Tool.create_tool_result [Mcp.make_text_content (Printf.sprintf "Error: %s" msg)] ~is_error:true
-
)
+
let _ =
+
add_tool server ~name:"generate_audio"
+
~description:"Generates an audio tone with specified frequency and duration"
+
~schema_properties:
+
[
+
("frequency", "integer", "Frequency of the tone in Hz (20-20000)");
+
("duration", "integer", "Duration of the tone in seconds (1-10)");
+
]
+
~schema_required:[ "frequency"; "duration" ]
+
(fun args ->
+
try
+
let frequency = get_int_param args "frequency" in
+
let duration = get_int_param args "duration" in
+
+
if frequency < 20 || frequency > 20000 then
+
Tool.create_tool_result
+
[
+
Mcp.make_text_content
+
"Error: Frequency must be between 20Hz and 20,000Hz";
+
]
+
~is_error:true
+
else if duration < 1 || duration > 10 then
+
Tool.create_tool_result
+
[
+
Mcp.make_text_content
+
"Error: Duration must be between 1 and 10 seconds";
+
]
+
~is_error:true
+
else
+
let audio_data =
+
generate_sine_wave_audio (float_of_int frequency) duration
+
in
+
Tool.create_tool_result
+
[ Mcp.make_audio_content audio_data "audio/wav" ]
+
~is_error:false
+
with Failure msg ->
+
Log.errorf "Error in generate_audio tool: %s" msg;
+
Tool.create_tool_result
+
[ Mcp.make_text_content (Printf.sprintf "Error: %s" msg) ]
+
~is_error:true)
(* Define and register a resource template example with multimodal content *)
-
let _ = add_resource_template server
-
~uri_template:"multimodal://{name}"
-
~name:"Multimodal Greeting"
-
~description:"Get a multimodal greeting with text, image and audio"
-
~mime_type:"application/json"
-
(fun params ->
-
match params with
-
| [name] ->
-
let greeting = Printf.sprintf "Hello, %s! Welcome to the multimodal MCP example." name in
-
let image_data = generate_random_image 128 128 in
-
let audio_data = generate_sine_wave_audio 440.0 1 in
-
-
Printf.sprintf {|
+
let _ =
+
add_resource_template server ~uri_template:"multimodal://{name}"
+
~name:"Multimodal Greeting"
+
~description:"Get a multimodal greeting with text, image and audio"
+
~mime_type:"application/json" (fun params ->
+
match params with
+
| [ name ] ->
+
let greeting =
+
Printf.sprintf "Hello, %s! Welcome to the multimodal MCP example."
+
name
+
in
+
let image_data = generate_random_image 128 128 in
+
let audio_data = generate_sine_wave_audio 440.0 1 in
+
+
Printf.sprintf
+
{|
{
"greeting": "%s",
"image": {
···
"mimeType": "audio/wav"
}
}
-
|} greeting image_data audio_data
-
| _ -> Printf.sprintf {|{"error": "Invalid parameters"}|}
-
)
+
|}
+
greeting image_data audio_data
+
| _ -> Printf.sprintf {|{"error": "Invalid parameters"}|})
(* Run the server with the default scheduler *)
let () =
-
Random.self_init(); (* Initialize random generator *)
-
Eio_main.run @@ fun env ->
-
Mcp_server.run_server env server
+
Random.self_init ();
+
(* Initialize random generator *)
+
Eio_main.run @@ fun env -> Mcp_server.run_server env server
+104 -111
bin/ocaml_eval_sdk.ml
···
(* Helper for extracting string value from JSON *)
let get_string_param json name =
match json with
-
| `Assoc fields ->
-
(match List.assoc_opt name fields with
-
| Some (`String value) -> value
-
| _ -> failwith (Printf.sprintf "Missing or invalid parameter: %s" name))
+
| `Assoc fields -> (
+
match List.assoc_opt name fields with
+
| Some (`String value) -> value
+
| _ -> failwith (Printf.sprintf "Missing or invalid parameter: %s" name))
| _ -> failwith "Expected JSON object"
-
+
(* Initialize the OCaml toploop with standard libraries *)
let initialize_toploop () =
(* Initialize the toplevel environment *)
Toploop.initialize_toplevel_env ();
-
+
(* Set up the toplevel as if using the standard OCaml REPL *)
Clflags.nopervasives := false;
Clflags.real_paths := true;
Clflags.recursive_types := false;
Clflags.strict_sequence := false;
Clflags.applicative_functors := true;
-
+
(* Return success message *)
"OCaml evaluation environment initialized"
···
let evaluate_phrase phrase =
(* Parse the input text as a toplevel phrase *)
let lexbuf = Lexing.from_string phrase in
-
+
(* Capture both success/failure status and output *)
try
let parsed_phrase = !Toploop.parse_toplevel_phrase lexbuf in
-
let (success, output) = capture_output (fun fmt ->
-
Toploop.execute_phrase true fmt parsed_phrase
-
) in
-
+
let success, output =
+
capture_output (fun fmt -> Toploop.execute_phrase true fmt parsed_phrase)
+
in
+
(* Return structured result with status and captured output *)
if success then
-
`Assoc [
-
("success", `Bool true);
-
("output", `String output);
-
]
+
`Assoc [ ("success", `Bool true); ("output", `String output) ]
else
-
`Assoc [
-
("success", `Bool false);
-
("error", `String "Execution failed");
-
("output", `String output);
-
]
+
`Assoc
+
[
+
("success", `Bool false);
+
("error", `String "Execution failed");
+
("output", `String output);
+
]
with e ->
(* Handle parsing or other errors with more detailed messages *)
-
let error_msg = match e with
-
| Syntaxerr.Error err ->
-
let msg = match err with
-
| Syntaxerr.Unclosed _ -> "Syntax error: Unclosed delimiter"
-
| Syntaxerr.Expecting _ -> "Syntax error: Expecting a different token"
-
| Syntaxerr.Not_expecting _ -> "Syntax error: Unexpected token"
-
| Syntaxerr.Applicative_path _ -> "Syntax error: Invalid applicative path"
-
| Syntaxerr.Variable_in_scope _ -> "Syntax error: Variable in scope"
-
| Syntaxerr.Other _ -> "Syntax error"
-
| _ -> "Syntax error (unknown kind)"
-
in
-
msg
-
-
| Lexer.Error (err, _) ->
-
let msg = match err with
-
| Lexer.Illegal_character _ -> "Lexer error: Illegal character"
-
| Lexer.Illegal_escape _ -> "Lexer error: Illegal escape sequence"
-
| Lexer.Unterminated_comment _ -> "Lexer error: Unterminated comment"
-
| Lexer.Unterminated_string -> "Lexer error: Unterminated string"
-
| Lexer.Unterminated_string_in_comment _ -> "Lexer error: Unterminated string in comment"
-
| Lexer.Invalid_literal _ -> "Lexer error: Invalid literal"
-
| _ -> "Lexer error (unknown kind)"
-
in
-
msg
-
| _ -> Printexc.to_string e
+
let error_msg =
+
match e with
+
| Syntaxerr.Error err ->
+
let msg =
+
match err with
+
| Syntaxerr.Unclosed _ -> "Syntax error: Unclosed delimiter"
+
| Syntaxerr.Expecting _ ->
+
"Syntax error: Expecting a different token"
+
| Syntaxerr.Not_expecting _ -> "Syntax error: Unexpected token"
+
| Syntaxerr.Applicative_path _ ->
+
"Syntax error: Invalid applicative path"
+
| Syntaxerr.Variable_in_scope _ -> "Syntax error: Variable in scope"
+
| Syntaxerr.Other _ -> "Syntax error"
+
| _ -> "Syntax error (unknown kind)"
+
in
+
msg
+
| Lexer.Error (err, _) ->
+
let msg =
+
match err with
+
| Lexer.Illegal_character _ -> "Lexer error: Illegal character"
+
| Lexer.Illegal_escape _ -> "Lexer error: Illegal escape sequence"
+
| Lexer.Unterminated_comment _ ->
+
"Lexer error: Unterminated comment"
+
| Lexer.Unterminated_string -> "Lexer error: Unterminated string"
+
| Lexer.Unterminated_string_in_comment _ ->
+
"Lexer error: Unterminated string in comment"
+
| Lexer.Invalid_literal _ -> "Lexer error: Invalid literal"
+
| _ -> "Lexer error (unknown kind)"
+
in
+
msg
+
| _ -> Printexc.to_string e
in
-
`Assoc [
-
("success", `Bool false);
-
("error", `String error_msg);
-
]
+
`Assoc [ ("success", `Bool false); ("error", `String error_msg) ]
(* Create evaluation server *)
-
let server = create_server
-
~name:"OCaml Evaluation Server"
-
~version:"0.1.0" () |>
-
fun server ->
-
(* Set default capabilities *)
-
configure_server server ~with_tools:true ()
+
let server =
+
create_server ~name:"OCaml Evaluation Server" ~version:"0.1.0" ()
+
|> fun server ->
+
(* Set default capabilities *)
+
configure_server server ~with_tools:true ()
(* Toplevel environment state management *)
let toplevel_initialized = ref false
(* Initialize OCaml toplevel on first use *)
let ensure_toploop_initialized () =
-
if not !toplevel_initialized then begin
+
if not !toplevel_initialized then
let _ = initialize_toploop () in
-
toplevel_initialized := true;
-
end
+
toplevel_initialized := true
(* Register eval tool *)
-
let _ = add_tool server
-
~name:"ocaml_eval"
-
~description:"Evaluates OCaml toplevel phrases and returns the result"
-
~schema_properties:[
-
("code", "string", "OCaml code to evaluate")
-
]
-
~schema_required:["code"]
-
(fun args ->
-
ensure_toploop_initialized ();
-
-
try
-
(* Extract code parameter *)
-
let code = get_string_param args "code" in
-
-
(* Execute the code *)
-
let result = evaluate_phrase code in
-
-
(* Return formatted result *)
-
let success = match result with
-
| `Assoc fields -> (
-
match List.assoc_opt "success" fields with
-
| Some (`Bool true) -> true
+
let _ =
+
add_tool server ~name:"ocaml_eval"
+
~description:"Evaluates OCaml toplevel phrases and returns the result"
+
~schema_properties:[ ("code", "string", "OCaml code to evaluate") ]
+
~schema_required:[ "code" ]
+
(fun args ->
+
ensure_toploop_initialized ();
+
+
try
+
(* Extract code parameter *)
+
let code = get_string_param args "code" in
+
+
(* Execute the code *)
+
let result = evaluate_phrase code in
+
+
(* Return formatted result *)
+
let success =
+
match result with
+
| `Assoc fields -> (
+
match List.assoc_opt "success" fields with
+
| Some (`Bool true) -> true
+
| _ -> false)
| _ -> false
-
)
-
| _ -> false
-
in
-
-
let output = match result with
-
| `Assoc fields -> (
-
match List.assoc_opt "output" fields with
-
| Some (`String s) -> s
-
| _ -> (
-
match List.assoc_opt "error" fields with
-
| Some (`String s) -> s
-
| _ -> "Unknown result"
-
)
-
)
-
| _ -> "Unknown result"
-
in
-
-
(* Create a tool result with colorized output *)
-
Tool.create_tool_result [
-
Mcp.make_text_content output
-
] ~is_error:(not success)
-
-
with
-
| Failure msg ->
-
Log.errorf "Error in OCaml eval tool: %s" msg;
-
Tool.create_tool_result [Mcp.make_text_content (Printf.sprintf "Error: %s" msg)] ~is_error:true
-
)
+
in
+
+
let output =
+
match result with
+
| `Assoc fields -> (
+
match List.assoc_opt "output" fields with
+
| Some (`String s) -> s
+
| _ -> (
+
match List.assoc_opt "error" fields with
+
| Some (`String s) -> s
+
| _ -> "Unknown result"))
+
| _ -> "Unknown result"
+
in
+
+
(* Create a tool result with colorized output *)
+
Tool.create_tool_result
+
[ Mcp.make_text_content output ]
+
~is_error:(not success)
+
with Failure msg ->
+
Log.errorf "Error in OCaml eval tool: %s" msg;
+
Tool.create_tool_result
+
[ Mcp.make_text_content (Printf.sprintf "Error: %s" msg) ]
+
~is_error:true)
(* Run the server with the default scheduler *)
-
let () =
-
Eio_main.run @@ fun env->
-
Mcp_server.run_server env server
+
let () = Eio_main.run @@ fun env -> Mcp_server.run_server env server
+20 -19
lib/dune
···
(library
-
(name mcp)
-
(public_name mcp)
-
(libraries jsonrpc unix yojson)
-
(modules mcp))
+
(name mcp)
+
(public_name mcp)
+
(libraries jsonrpc unix yojson)
+
(modules mcp))
(library
-
(name mcp_rpc)
-
(public_name mcp.rpc)
-
(libraries mcp jsonrpc unix yojson)
-
(modules mcp_rpc)
-
(flags (:standard -w -67 -w -27 -w -32 -w -33 -w -34)))
+
(name mcp_rpc)
+
(public_name mcp.rpc)
+
(libraries mcp jsonrpc unix yojson)
+
(modules mcp_rpc)
+
(flags
+
(:standard -w -67 -w -27 -w -32 -w -33 -w -34)))
(library
-
(name mcp_sdk)
-
(public_name mcp.sdk)
-
(libraries mcp mcp_rpc jsonrpc unix yojson)
-
(modules mcp_sdk)
-
(flags (:standard -w -67 -w -27 -w -32)))
+
(name mcp_sdk)
+
(public_name mcp.sdk)
+
(libraries mcp mcp_rpc jsonrpc unix yojson)
+
(modules mcp_sdk)
+
(flags
+
(:standard -w -67 -w -27 -w -32)))
(library
-
(name mcp_server)
-
(public_name mcp.server)
-
(libraries mcp_sdk jsonrpc eio_main eio http cohttp-eio)
-
(modules mcp_server)
-
)
+
(name mcp_server)
+
(public_name mcp.server)
+
(libraries mcp_sdk jsonrpc eio_main eio http cohttp-eio)
+
(modules mcp_server))
+477 -436
lib/mcp.ml
···
(* Utility functions for JSON parsing *)
module Util = struct
(* Helper to raise a Json.Of_json exception with formatted message *)
-
let json_error fmt json =
+
let json_error fmt json =
Printf.ksprintf (fun msg -> raise (Json.Of_json (msg, json))) fmt
-
+
(* Extract a string field from JSON object or raise an error *)
let get_string_field fields name json =
match List.assoc_opt name fields with
| Some (`String s) -> s
| _ -> json_error "Missing or invalid '%s' field" json name
-
+
(* Extract an optional string field from JSON object *)
let get_optional_string_field fields name =
-
List.assoc_opt name fields |> Option.map (function
-
| `String s -> s
-
| j -> json_error "Expected string for %s" j name
-
)
-
+
List.assoc_opt name fields
+
|> Option.map (function
+
| `String s -> s
+
| j -> json_error "Expected string for %s" j name)
+
(* Extract an int field from JSON object or raise an error *)
let get_int_field fields name json =
match List.assoc_opt name fields with
| Some (`Int i) -> i
| _ -> json_error "Missing or invalid '%s' field" json name
-
+
(* Extract a float field from JSON object or raise an error *)
let get_float_field fields name json =
match List.assoc_opt name fields with
| Some (`Float f) -> f
| _ -> json_error "Missing or invalid '%s' field" json name
-
+
(* Extract a boolean field from JSON object or raise an error *)
let get_bool_field fields name json =
match List.assoc_opt name fields with
| Some (`Bool b) -> b
| _ -> json_error "Missing or invalid '%s' field" json name
-
+
(* Extract an object field from JSON object or raise an error *)
let get_object_field fields name json =
match List.assoc_opt name fields with
| Some (`Assoc obj) -> obj
| _ -> json_error "Missing or invalid '%s' field" json name
-
+
(* Extract a list field from JSON object or raise an error *)
let get_list_field fields name json =
match List.assoc_opt name fields with
| Some (`List items) -> items
| _ -> json_error "Missing or invalid '%s' field" json name
-
+
(* Verify a specific string value in a field *)
let verify_string_field fields name expected_value json =
match List.assoc_opt name fields with
| Some (`String s) when s = expected_value -> ()
-
| _ -> json_error "Field '%s' missing or not equal to '%s'" json name expected_value
+
| _ ->
+
json_error "Field '%s' missing or not equal to '%s'" json name
+
expected_value
end
(* Error codes for JSON-RPC *)
module ErrorCode = struct
-
type t =
-
| ParseError (* -32700 - Invalid JSON *)
-
| InvalidRequest (* -32600 - Invalid JSON-RPC request *)
-
| MethodNotFound (* -32601 - Method not available *)
-
| InvalidParams (* -32602 - Invalid method parameters *)
-
| InternalError (* -32603 - Internal JSON-RPC error *)
-
| ResourceNotFound (* -32002 - Custom MCP error: requested resource not found *)
-
| AuthRequired (* -32001 - Custom MCP error: authentication required *)
+
type t =
+
| ParseError (* -32700 - Invalid JSON *)
+
| InvalidRequest (* -32600 - Invalid JSON-RPC request *)
+
| MethodNotFound (* -32601 - Method not available *)
+
| InvalidParams (* -32602 - Invalid method parameters *)
+
| InternalError (* -32603 - Internal JSON-RPC error *)
+
| ResourceNotFound
+
(* -32002 - Custom MCP error: requested resource not found *)
+
| AuthRequired (* -32001 - Custom MCP error: authentication required *)
| CustomError of int (* For any other error codes *)
-
+
(* Convert the error code to its integer representation *)
let to_int = function
| ParseError -> -32700
···
| ResourceNotFound -> -32002
| AuthRequired -> -32001
| CustomError code -> code
-
+
(* Get error message for standard error codes *)
let to_message = function
| ParseError -> "Parse error"
···
(* Initialization and lifecycle methods *)
| Initialize
| Initialized
-
(* Resource methods *)
| ResourcesList
| ResourcesRead
···
| ResourcesSubscribe
| ResourcesListChanged
| ResourcesUpdated
-
(* Tool methods *)
| ToolsList
| ToolsCall
| ToolsListChanged
-
(* Prompt methods *)
| PromptsList
| PromptsGet
| PromptsListChanged
-
(* Progress notifications *)
| Progress
-
+
(* Convert method type to string representation *)
let to_string = function
| Initialize -> "initialize"
···
| PromptsGet -> "prompts/get"
| PromptsListChanged -> "notifications/prompts/list_changed"
| Progress -> "notifications/progress"
-
+
(* Convert string to method type *)
let of_string = function
| "initialize" -> Initialize
···
module Role = struct
type t = [ `User | `Assistant ]
-
let to_string = function
-
| `User -> "user"
-
| `Assistant -> "assistant"
+
let to_string = function `User -> "user" | `Assistant -> "assistant"
let of_string = function
| "user" -> `User
···
| s -> Util.json_error "Unknown role: %s" (`String s) s
let yojson_of_t t = `String (to_string t)
+
let t_of_yojson = function
| `String s -> of_string s
| j -> Util.json_error "Expected string for Role" j
···
type t = string
let yojson_of_t t = `String t
+
let t_of_yojson = function
| `String s -> s
| j -> Util.json_error "Expected string for Cursor" j
···
(* Annotations *)
module Annotated = struct
-
type t = {
-
annotations: annotation option;
-
}
-
and annotation = {
-
audience: Role.t list option;
-
priority: float option;
-
}
+
type t = { annotations : annotation option }
+
and annotation = { audience : Role.t list option; priority : float option }
let yojson_of_annotation { audience; priority } =
let assoc = [] in
-
let assoc = match audience with
-
| Some audience -> ("audience", `List (List.map Role.yojson_of_t audience)) :: assoc
+
let assoc =
+
match audience with
+
| Some audience ->
+
("audience", `List (List.map Role.yojson_of_t audience)) :: assoc
| None -> assoc
in
-
let assoc = match priority with
+
let assoc =
+
match priority with
| Some priority -> ("priority", `Float priority) :: assoc
| None -> assoc
in
···
let annotation_of_yojson = function
| `Assoc fields ->
-
let audience = List.assoc_opt "audience" fields |> Option.map (function
-
| `List items -> List.map Role.t_of_yojson items
-
| j -> Util.json_error "Expected list for audience" j
-
) in
-
let priority = List.assoc_opt "priority" fields |> Option.map (function
-
| `Float f -> f
-
| j -> Util.json_error "Expected float for priority" j
-
) in
-
{ audience; priority }
+
let audience =
+
List.assoc_opt "audience" fields
+
|> Option.map (function
+
| `List items -> List.map Role.t_of_yojson items
+
| j -> Util.json_error "Expected list for audience" j)
+
in
+
let priority =
+
List.assoc_opt "priority" fields
+
|> Option.map (function
+
| `Float f -> f
+
| j -> Util.json_error "Expected float for priority" j)
+
in
+
{ audience; priority }
| j -> Util.json_error "Expected object for annotation" j
let yojson_of_t { annotations } =
match annotations with
-
| Some annotations -> `Assoc [ "annotations", yojson_of_annotation annotations ]
+
| Some annotations ->
+
`Assoc [ ("annotations", yojson_of_annotation annotations) ]
| None -> `Assoc []
let t_of_yojson = function
| `Assoc fields ->
-
let annotations = List.assoc_opt "annotations" fields |> Option.map annotation_of_yojson in
-
{ annotations }
+
let annotations =
+
List.assoc_opt "annotations" fields |> Option.map annotation_of_yojson
+
in
+
{ annotations }
| j -> Util.json_error "Expected object for Annotated" j
end
(* Content types *)
module TextContent = struct
-
type t = {
-
text: string;
-
annotations: Annotated.annotation option;
-
}
+
type t = { text : string; annotations : Annotated.annotation option }
let yojson_of_t { text; annotations } =
-
let assoc = [
-
("text", `String text);
-
("type", `String "text");
-
] in
-
let assoc = match annotations with
-
| Some annotations -> ("annotations", Annotated.yojson_of_annotation annotations) :: assoc
+
let assoc = [ ("text", `String text); ("type", `String "text") ] in
+
let assoc =
+
match annotations with
+
| Some annotations ->
+
("annotations", Annotated.yojson_of_annotation annotations) :: assoc
| None -> assoc
in
`Assoc assoc
let t_of_yojson = function
| `Assoc fields as json ->
-
let text = Util.get_string_field fields "text" json in
-
Util.verify_string_field fields "type" "text" json;
-
let annotations = List.assoc_opt "annotations" fields |> Option.map Annotated.annotation_of_yojson in
-
{ text; annotations }
+
let text = Util.get_string_field fields "text" json in
+
Util.verify_string_field fields "type" "text" json;
+
let annotations =
+
List.assoc_opt "annotations" fields
+
|> Option.map Annotated.annotation_of_yojson
+
in
+
{ text; annotations }
| j -> Util.json_error "Expected object for TextContent" j
end
module ImageContent = struct
type t = {
-
data: string;
-
mime_type: string;
-
annotations: Annotated.annotation option;
+
data : string;
+
mime_type : string;
+
annotations : Annotated.annotation option;
}
let yojson_of_t { data; mime_type; annotations } =
-
let assoc = [
-
("type", `String "image");
-
("data", `String data);
-
("mimeType", `String mime_type);
-
] in
-
let assoc = match annotations with
-
| Some annotations -> ("annotations", Annotated.yojson_of_annotation annotations) :: assoc
+
let assoc =
+
[
+
("type", `String "image");
+
("data", `String data);
+
("mimeType", `String mime_type);
+
]
+
in
+
let assoc =
+
match annotations with
+
| Some annotations ->
+
("annotations", Annotated.yojson_of_annotation annotations) :: assoc
| None -> assoc
in
`Assoc assoc
let t_of_yojson = function
| `Assoc fields as json ->
-
let data = Util.get_string_field fields "data" json in
-
let mime_type = Util.get_string_field fields "mimeType" json in
-
Util.verify_string_field fields "type" "image" json;
-
let annotations = List.assoc_opt "annotations" fields |> Option.map Annotated.annotation_of_yojson in
-
{ data; mime_type; annotations }
+
let data = Util.get_string_field fields "data" json in
+
let mime_type = Util.get_string_field fields "mimeType" json in
+
Util.verify_string_field fields "type" "image" json;
+
let annotations =
+
List.assoc_opt "annotations" fields
+
|> Option.map Annotated.annotation_of_yojson
+
in
+
{ data; mime_type; annotations }
| j -> Util.json_error "Expected object for ImageContent" j
end
module AudioContent = struct
type t = {
-
data: string;
-
mime_type: string;
-
annotations: Annotated.annotation option;
+
data : string;
+
mime_type : string;
+
annotations : Annotated.annotation option;
}
let yojson_of_t { data; mime_type; annotations } =
-
let assoc = [
-
("type", `String "audio");
-
("data", `String data);
-
("mimeType", `String mime_type);
-
] in
-
let assoc = match annotations with
-
| Some annotations -> ("annotations", Annotated.yojson_of_annotation annotations) :: assoc
+
let assoc =
+
[
+
("type", `String "audio");
+
("data", `String data);
+
("mimeType", `String mime_type);
+
]
+
in
+
let assoc =
+
match annotations with
+
| Some annotations ->
+
("annotations", Annotated.yojson_of_annotation annotations) :: assoc
| None -> assoc
in
`Assoc assoc
let t_of_yojson = function
| `Assoc fields as json ->
-
let data = Util.get_string_field fields "data" json in
-
let mime_type = Util.get_string_field fields "mimeType" json in
-
Util.verify_string_field fields "type" "audio" json;
-
let annotations = List.assoc_opt "annotations" fields |> Option.map Annotated.annotation_of_yojson in
-
{ data; mime_type; annotations }
+
let data = Util.get_string_field fields "data" json in
+
let mime_type = Util.get_string_field fields "mimeType" json in
+
Util.verify_string_field fields "type" "audio" json;
+
let annotations =
+
List.assoc_opt "annotations" fields
+
|> Option.map Annotated.annotation_of_yojson
+
in
+
{ data; mime_type; annotations }
| j -> Util.json_error "Expected object for AudioContent" j
end
module ResourceContents = struct
-
type t = {
-
uri: string;
-
mime_type: string option;
-
}
+
type t = { uri : string; mime_type : string option }
let yojson_of_t { uri; mime_type } =
-
let assoc = [
-
("uri", `String uri);
-
] in
-
let assoc = match mime_type with
+
let assoc = [ ("uri", `String uri) ] in
+
let assoc =
+
match mime_type with
| Some mime_type -> ("mimeType", `String mime_type) :: assoc
| None -> assoc
in
···
let t_of_yojson = function
| `Assoc fields as json ->
-
let uri = Util.get_string_field fields "uri" json in
-
let mime_type = Util.get_optional_string_field fields "mimeType" in
-
{ uri; mime_type }
+
let uri = Util.get_string_field fields "uri" json in
+
let mime_type = Util.get_optional_string_field fields "mimeType" in
+
{ uri; mime_type }
| j -> Util.json_error "Expected object for ResourceContents" j
end
module TextResourceContents = struct
-
type t = {
-
uri: string;
-
text: string;
-
mime_type: string option;
-
}
+
type t = { uri : string; text : string; mime_type : string option }
let yojson_of_t { uri; text; mime_type } =
-
let assoc = [
-
("uri", `String uri);
-
("text", `String text);
-
] in
-
let assoc = match mime_type with
+
let assoc = [ ("uri", `String uri); ("text", `String text) ] in
+
let assoc =
+
match mime_type with
| Some mime_type -> ("mimeType", `String mime_type) :: assoc
| None -> assoc
in
···
let t_of_yojson = function
| `Assoc fields as json ->
-
let uri = Util.get_string_field fields "uri" json in
-
let text = Util.get_string_field fields "text" json in
-
let mime_type = Util.get_optional_string_field fields "mimeType" in
-
{ uri; text; mime_type }
+
let uri = Util.get_string_field fields "uri" json in
+
let text = Util.get_string_field fields "text" json in
+
let mime_type = Util.get_optional_string_field fields "mimeType" in
+
{ uri; text; mime_type }
| j -> Util.json_error "Expected object for TextResourceContents" j
end
module BlobResourceContents = struct
-
type t = {
-
uri: string;
-
blob: string;
-
mime_type: string option;
-
}
+
type t = { uri : string; blob : string; mime_type : string option }
let yojson_of_t { uri; blob; mime_type } =
-
let assoc = [
-
("uri", `String uri);
-
("blob", `String blob);
-
] in
-
let assoc = match mime_type with
+
let assoc = [ ("uri", `String uri); ("blob", `String blob) ] in
+
let assoc =
+
match mime_type with
| Some mime_type -> ("mimeType", `String mime_type) :: assoc
| None -> assoc
in
···
let t_of_yojson = function
| `Assoc fields as json ->
-
let uri = Util.get_string_field fields "uri" json in
-
let blob = Util.get_string_field fields "blob" json in
-
let mime_type = Util.get_optional_string_field fields "mimeType" in
-
{ uri; blob; mime_type }
+
let uri = Util.get_string_field fields "uri" json in
+
let blob = Util.get_string_field fields "blob" json in
+
let mime_type = Util.get_optional_string_field fields "mimeType" in
+
{ uri; blob; mime_type }
| j -> Util.json_error "Expected object for BlobResourceContents" j
end
module EmbeddedResource = struct
type t = {
-
resource: [ `Text of TextResourceContents.t | `Blob of BlobResourceContents.t ];
-
annotations: Annotated.annotation option;
+
resource :
+
[ `Text of TextResourceContents.t | `Blob of BlobResourceContents.t ];
+
annotations : Annotated.annotation option;
}
let yojson_of_t { resource; annotations } =
-
let resource_json = match resource with
+
let resource_json =
+
match resource with
| `Text txt -> TextResourceContents.yojson_of_t txt
| `Blob blob -> BlobResourceContents.yojson_of_t blob
in
-
let assoc = [
-
("resource", resource_json);
-
("type", `String "resource");
-
] in
-
let assoc = match annotations with
-
| Some annotations -> ("annotations", Annotated.yojson_of_annotation annotations) :: assoc
+
let assoc = [ ("resource", resource_json); ("type", `String "resource") ] in
+
let assoc =
+
match annotations with
+
| Some annotations ->
+
("annotations", Annotated.yojson_of_annotation annotations) :: assoc
| None -> assoc
in
`Assoc assoc
let t_of_yojson = function
| `Assoc fields as json ->
-
Util.verify_string_field fields "type" "resource" json;
-
let resource_fields = match List.assoc_opt "resource" fields with
-
| Some (`Assoc res_fields) -> res_fields
-
| _ -> Util.json_error "Missing or invalid 'resource' field" json
-
in
-
let resource =
-
if List.mem_assoc "text" resource_fields then
-
`Text (TextResourceContents.t_of_yojson (`Assoc resource_fields))
-
else if List.mem_assoc "blob" resource_fields then
-
`Blob (BlobResourceContents.t_of_yojson (`Assoc resource_fields))
-
else
-
Util.json_error "Invalid resource content" (`Assoc resource_fields)
-
in
-
let annotations = List.assoc_opt "annotations" fields |> Option.map Annotated.annotation_of_yojson in
-
{ resource; annotations }
+
Util.verify_string_field fields "type" "resource" json;
+
let resource_fields =
+
match List.assoc_opt "resource" fields with
+
| Some (`Assoc res_fields) -> res_fields
+
| _ -> Util.json_error "Missing or invalid 'resource' field" json
+
in
+
let resource =
+
if List.mem_assoc "text" resource_fields then
+
`Text (TextResourceContents.t_of_yojson (`Assoc resource_fields))
+
else if List.mem_assoc "blob" resource_fields then
+
`Blob (BlobResourceContents.t_of_yojson (`Assoc resource_fields))
+
else
+
Util.json_error "Invalid resource content" (`Assoc resource_fields)
+
in
+
let annotations =
+
List.assoc_opt "annotations" fields
+
|> Option.map Annotated.annotation_of_yojson
+
in
+
{ resource; annotations }
| j -> Util.json_error "Expected object for EmbeddedResource" j
end
-
type content =
+
type content =
| Text of TextContent.t
| Image of ImageContent.t
| Audio of AudioContent.t
···
| Resource r -> EmbeddedResource.yojson_of_t r
let content_of_yojson = function
-
| `Assoc fields as json ->
-
(match List.assoc_opt "type" fields with
-
| Some (`String "text") -> Text (TextContent.t_of_yojson json)
-
| Some (`String "image") -> Image (ImageContent.t_of_yojson json)
-
| Some (`String "audio") -> Audio (AudioContent.t_of_yojson json)
-
| Some (`String "resource") -> Resource (EmbeddedResource.t_of_yojson json)
-
| _ -> Util.json_error "Invalid or missing content type" json)
+
| `Assoc fields as json -> (
+
match List.assoc_opt "type" fields with
+
| Some (`String "text") -> Text (TextContent.t_of_yojson json)
+
| Some (`String "image") -> Image (ImageContent.t_of_yojson json)
+
| Some (`String "audio") -> Audio (AudioContent.t_of_yojson json)
+
| Some (`String "resource") ->
+
Resource (EmbeddedResource.t_of_yojson json)
+
| _ -> Util.json_error "Invalid or missing content type" json)
| j -> Util.json_error "Expected object for content" j
(* Message types *)
module PromptMessage = struct
-
type t = {
-
role: Role.t;
-
content: content;
-
}
+
type t = { role : Role.t; content : content }
let yojson_of_t { role; content } =
-
`Assoc [
-
("role", Role.yojson_of_t role);
-
("content", yojson_of_content content);
-
]
+
`Assoc
+
[
+
("role", Role.yojson_of_t role); ("content", yojson_of_content content);
+
]
let t_of_yojson = function
| `Assoc fields as json ->
-
let role = match List.assoc_opt "role" fields with
-
| Some json -> Role.t_of_yojson json
-
| None -> Util.json_error "Missing role field" json
-
in
-
let content = match List.assoc_opt "content" fields with
-
| Some json -> content_of_yojson json
-
| None -> Util.json_error "Missing content field" json
-
in
-
{ role; content }
+
let role =
+
match List.assoc_opt "role" fields with
+
| Some json -> Role.t_of_yojson json
+
| None -> Util.json_error "Missing role field" json
+
in
+
let content =
+
match List.assoc_opt "content" fields with
+
| Some json -> content_of_yojson json
+
| None -> Util.json_error "Missing content field" json
+
in
+
{ role; content }
| j -> Util.json_error "Expected object for PromptMessage" j
end
module SamplingMessage = struct
type t = {
-
role: Role.t;
-
content: [ `Text of TextContent.t | `Image of ImageContent.t | `Audio of AudioContent.t ];
+
role : Role.t;
+
content :
+
[ `Text of TextContent.t
+
| `Image of ImageContent.t
+
| `Audio of AudioContent.t ];
}
let yojson_of_t { role; content } =
-
let content_json = match content with
+
let content_json =
+
match content with
| `Text t -> TextContent.yojson_of_t t
| `Image i -> ImageContent.yojson_of_t i
| `Audio a -> AudioContent.yojson_of_t a
in
-
`Assoc [
-
("role", Role.yojson_of_t role);
-
("content", content_json);
-
]
+
`Assoc [ ("role", Role.yojson_of_t role); ("content", content_json) ]
let t_of_yojson = function
| `Assoc fields as json ->
-
let role = match List.assoc_opt "role" fields with
-
| Some json -> Role.t_of_yojson json
-
| None -> Util.json_error "Missing role field" json
-
in
-
let content_obj = match List.assoc_opt "content" fields with
-
| Some (`Assoc content_fields) -> content_fields
-
| _ -> Util.json_error "Missing or invalid content field" json
-
in
-
let content_type = match List.assoc_opt "type" content_obj with
-
| Some (`String ty) -> ty
-
| _ -> Util.json_error "Missing or invalid content type" (`Assoc content_obj)
-
in
-
let content =
-
match content_type with
-
| "text" -> `Text (TextContent.t_of_yojson (`Assoc content_obj))
-
| "image" -> `Image (ImageContent.t_of_yojson (`Assoc content_obj))
-
| "audio" -> `Audio (AudioContent.t_of_yojson (`Assoc content_obj))
-
| _ -> Util.json_error "Invalid content type: %s" (`Assoc content_obj) content_type
-
in
-
{ role; content }
+
let role =
+
match List.assoc_opt "role" fields with
+
| Some json -> Role.t_of_yojson json
+
| None -> Util.json_error "Missing role field" json
+
in
+
let content_obj =
+
match List.assoc_opt "content" fields with
+
| Some (`Assoc content_fields) -> content_fields
+
| _ -> Util.json_error "Missing or invalid content field" json
+
in
+
let content_type =
+
match List.assoc_opt "type" content_obj with
+
| Some (`String ty) -> ty
+
| _ ->
+
Util.json_error "Missing or invalid content type"
+
(`Assoc content_obj)
+
in
+
let content =
+
match content_type with
+
| "text" -> `Text (TextContent.t_of_yojson (`Assoc content_obj))
+
| "image" -> `Image (ImageContent.t_of_yojson (`Assoc content_obj))
+
| "audio" -> `Audio (AudioContent.t_of_yojson (`Assoc content_obj))
+
| _ ->
+
Util.json_error "Invalid content type: %s" (`Assoc content_obj)
+
content_type
+
in
+
{ role; content }
| j -> Util.json_error "Expected object for SamplingMessage" j
end
(* Implementation info *)
module Implementation = struct
-
type t = {
-
name: string;
-
version: string;
-
}
+
type t = { name : string; version : string }
let yojson_of_t { name; version } =
-
`Assoc [
-
("name", `String name);
-
("version", `String version);
-
]
+
`Assoc [ ("name", `String name); ("version", `String version) ]
let t_of_yojson = function
| `Assoc fields as json ->
-
let name = Util.get_string_field fields "name" json in
-
let version = Util.get_string_field fields "version" json in
-
{ name; version }
+
let name = Util.get_string_field fields "name" json in
+
let version = Util.get_string_field fields "version" json in
+
{ name; version }
| j -> Util.json_error "Expected object for Implementation" j
end
(* JSONRPC Message types *)
module JSONRPCMessage = struct
-
type notification = {
-
meth: Method.t;
-
params: Json.t option;
-
}
+
type notification = { meth : Method.t; params : Json.t option }
type request = {
-
id: RequestId.t;
-
meth: Method.t;
-
params: Json.t option;
-
progress_token: ProgressToken.t option;
+
id : RequestId.t;
+
meth : Method.t;
+
params : Json.t option;
+
progress_token : ProgressToken.t option;
}
-
type response = {
-
id: RequestId.t;
-
result: Json.t;
-
}
+
type response = { id : RequestId.t; result : Json.t }
type error = {
-
id: RequestId.t;
-
code: int;
-
message: string;
-
data: Json.t option;
+
id : RequestId.t;
+
code : int;
+
message : string;
+
data : Json.t option;
}
type t =
···
| Response of response
| Error of error
-
let yojson_of_notification (n: notification) =
-
let assoc = [
-
("jsonrpc", `String "2.0");
-
("method", `String (Method.to_string n.meth));
-
] in
-
let assoc = match n.params with
+
let yojson_of_notification (n : notification) =
+
let assoc =
+
[
+
("jsonrpc", `String "2.0"); ("method", `String (Method.to_string n.meth));
+
]
+
in
+
let assoc =
+
match n.params with
| Some params -> ("params", params) :: assoc
| None -> assoc
in
`Assoc assoc
-
let yojson_of_request (r: request) =
-
let assoc = [
-
("jsonrpc", `String "2.0");
-
("id", Id.yojson_of_t r.id);
-
("method", `String (Method.to_string r.meth));
-
] in
-
let assoc = match r.params with
+
let yojson_of_request (r : request) =
+
let assoc =
+
[
+
("jsonrpc", `String "2.0");
+
("id", Id.yojson_of_t r.id);
+
("method", `String (Method.to_string r.meth));
+
]
+
in
+
let assoc =
+
match r.params with
| Some params ->
-
let params_json = match params with
-
| `Assoc fields ->
-
let fields = match r.progress_token with
-
| Some token ->
-
let meta = `Assoc [ "progressToken", ProgressToken.yojson_of_t token ] in
-
("_meta", meta) :: fields
-
| None -> fields
-
in
-
`Assoc fields
-
| _ -> params
-
in
-
("params", params_json) :: assoc
+
let params_json =
+
match params with
+
| `Assoc fields ->
+
let fields =
+
match r.progress_token with
+
| Some token ->
+
let meta =
+
`Assoc
+
[ ("progressToken", ProgressToken.yojson_of_t token) ]
+
in
+
("_meta", meta) :: fields
+
| None -> fields
+
in
+
`Assoc fields
+
| _ -> params
+
in
+
("params", params_json) :: assoc
| None -> assoc
in
`Assoc assoc
-
let yojson_of_response (r: response) =
-
`Assoc [
-
("jsonrpc", `String "2.0");
-
("id", Id.yojson_of_t r.id);
-
("result", r.result);
-
]
+
let yojson_of_response (r : response) =
+
`Assoc
+
[
+
("jsonrpc", `String "2.0");
+
("id", Id.yojson_of_t r.id);
+
("result", r.result);
+
]
-
let yojson_of_error (e: error) =
-
let error_assoc = [
-
("code", `Int e.code);
-
("message", `String e.message);
-
] in
-
let error_assoc = match e.data with
+
let yojson_of_error (e : error) =
+
let error_assoc =
+
[ ("code", `Int e.code); ("message", `String e.message) ]
+
in
+
let error_assoc =
+
match e.data with
| Some data -> ("data", data) :: error_assoc
| None -> error_assoc
in
-
`Assoc [
-
("jsonrpc", `String "2.0");
-
("id", Id.yojson_of_t e.id);
-
("error", `Assoc error_assoc);
-
]
+
`Assoc
+
[
+
("jsonrpc", `String "2.0");
+
("id", Id.yojson_of_t e.id);
+
("error", `Assoc error_assoc);
+
]
let yojson_of_t = function
| Notification n -> yojson_of_notification n
···
let notification_of_yojson = function
| `Assoc fields ->
-
let meth = match List.assoc_opt "method" fields with
-
| Some (`String s) ->
-
(try Method.of_string s
-
with Failure msg -> Util.json_error "%s" (`String s) msg)
-
| _ -> Util.json_error "Missing or invalid 'method' field" (`Assoc fields)
-
in
-
let params = List.assoc_opt "params" fields in
-
{ meth; params }
+
let meth =
+
match List.assoc_opt "method" fields with
+
| Some (`String s) -> (
+
try Method.of_string s
+
with Failure msg -> Util.json_error "%s" (`String s) msg)
+
| _ ->
+
Util.json_error "Missing or invalid 'method' field"
+
(`Assoc fields)
+
in
+
let params = List.assoc_opt "params" fields in
+
{ meth; params }
| j -> Util.json_error "Expected object for notification" j
let request_of_yojson = function
| `Assoc fields ->
-
let id = match List.assoc_opt "id" fields with
-
| Some id_json -> Id.t_of_yojson id_json
-
| _ -> Util.json_error "Missing or invalid 'id' field" (`Assoc fields)
-
in
-
let meth = match List.assoc_opt "method" fields with
-
| Some (`String s) ->
-
(try Method.of_string s
-
with Failure msg -> Util.json_error "%s" (`String s) msg)
-
| _ -> Util.json_error "Missing or invalid 'method' field" (`Assoc fields)
-
in
-
let params = List.assoc_opt "params" fields in
-
let progress_token =
-
match params with
-
| Some (`Assoc param_fields) ->
-
(match List.assoc_opt "_meta" param_fields with
-
| Some (`Assoc meta_fields) ->
-
(match List.assoc_opt "progressToken" meta_fields with
-
| Some token_json -> Some (ProgressToken.t_of_yojson token_json)
-
| None -> None)
-
| _ -> None)
-
| _ -> None
-
in
-
{ id; meth; params; progress_token }
+
let id =
+
match List.assoc_opt "id" fields with
+
| Some id_json -> Id.t_of_yojson id_json
+
| _ -> Util.json_error "Missing or invalid 'id' field" (`Assoc fields)
+
in
+
let meth =
+
match List.assoc_opt "method" fields with
+
| Some (`String s) -> (
+
try Method.of_string s
+
with Failure msg -> Util.json_error "%s" (`String s) msg)
+
| _ ->
+
Util.json_error "Missing or invalid 'method' field"
+
(`Assoc fields)
+
in
+
let params = List.assoc_opt "params" fields in
+
let progress_token =
+
match params with
+
| Some (`Assoc param_fields) -> (
+
match List.assoc_opt "_meta" param_fields with
+
| Some (`Assoc meta_fields) -> (
+
match List.assoc_opt "progressToken" meta_fields with
+
| Some token_json ->
+
Some (ProgressToken.t_of_yojson token_json)
+
| None -> None)
+
| _ -> None)
+
| _ -> None
+
in
+
{ id; meth; params; progress_token }
| j -> Util.json_error "Expected object for request" j
let response_of_yojson = function
| `Assoc fields ->
-
let id = match List.assoc_opt "id" fields with
-
| Some id_json -> Id.t_of_yojson id_json
-
| _ -> Util.json_error "Missing or invalid 'id' field" (`Assoc fields)
-
in
-
let result = match List.assoc_opt "result" fields with
-
| Some result -> result
-
| _ -> Util.json_error "Missing 'result' field" (`Assoc fields)
-
in
-
{ id; result }
+
let id =
+
match List.assoc_opt "id" fields with
+
| Some id_json -> Id.t_of_yojson id_json
+
| _ -> Util.json_error "Missing or invalid 'id' field" (`Assoc fields)
+
in
+
let result =
+
match List.assoc_opt "result" fields with
+
| Some result -> result
+
| _ -> Util.json_error "Missing 'result' field" (`Assoc fields)
+
in
+
{ id; result }
| j -> Util.json_error "Expected object for response" j
let error_of_yojson = function
| `Assoc fields as json ->
-
let id = match List.assoc_opt "id" fields with
-
| Some id_json -> Id.t_of_yojson id_json
-
| _ -> Util.json_error "Missing or invalid 'id' field" json
-
in
-
let error = match List.assoc_opt "error" fields with
-
| Some (`Assoc error_fields) -> error_fields
-
| _ -> Util.json_error "Missing or invalid 'error' field" json
-
in
-
let code = match List.assoc_opt "code" error with
-
| Some (`Int code) -> code
-
| _ -> Util.json_error "Missing or invalid 'code' field in error" (`Assoc error)
-
in
-
let message = match List.assoc_opt "message" error with
-
| Some (`String msg) -> msg
-
| _ -> Util.json_error "Missing or invalid 'message' field in error" (`Assoc error)
-
in
-
let data = List.assoc_opt "data" error in
-
{ id; code; message; data }
+
let id =
+
match List.assoc_opt "id" fields with
+
| Some id_json -> Id.t_of_yojson id_json
+
| _ -> Util.json_error "Missing or invalid 'id' field" json
+
in
+
let error =
+
match List.assoc_opt "error" fields with
+
| Some (`Assoc error_fields) -> error_fields
+
| _ -> Util.json_error "Missing or invalid 'error' field" json
+
in
+
let code =
+
match List.assoc_opt "code" error with
+
| Some (`Int code) -> code
+
| _ ->
+
Util.json_error "Missing or invalid 'code' field in error"
+
(`Assoc error)
+
in
+
let message =
+
match List.assoc_opt "message" error with
+
| Some (`String msg) -> msg
+
| _ ->
+
Util.json_error "Missing or invalid 'message' field in error"
+
(`Assoc error)
+
in
+
let data = List.assoc_opt "data" error in
+
{ id; code; message; data }
| j -> Util.json_error "Expected object for error" j
let t_of_yojson json =
match json with
| `Assoc fields ->
-
let _jsonrpc = match List.assoc_opt "jsonrpc" fields with
-
| Some (`String "2.0") -> ()
-
| _ -> Util.json_error "Missing or invalid 'jsonrpc' field" json
-
in
-
if List.mem_assoc "method" fields then
-
if List.mem_assoc "id" fields then
-
Request (request_of_yojson json)
-
else
-
Notification (notification_of_yojson json)
-
else if List.mem_assoc "result" fields then
-
Response (response_of_yojson json)
-
else if List.mem_assoc "error" fields then
-
Error (error_of_yojson json)
-
else
-
Util.json_error "Invalid JSONRPC message format" json
+
let _jsonrpc =
+
match List.assoc_opt "jsonrpc" fields with
+
| Some (`String "2.0") -> ()
+
| _ -> Util.json_error "Missing or invalid 'jsonrpc' field" json
+
in
+
if List.mem_assoc "method" fields then
+
if List.mem_assoc "id" fields then Request (request_of_yojson json)
+
else Notification (notification_of_yojson json)
+
else if List.mem_assoc "result" fields then
+
Response (response_of_yojson json)
+
else if List.mem_assoc "error" fields then Error (error_of_yojson json)
+
else Util.json_error "Invalid JSONRPC message format" json
| j -> Util.json_error "Expected object for JSONRPC message" j
-
let create_notification ?(params=None) ~meth () =
+
let create_notification ?(params = None) ~meth () =
Notification { meth; params }
-
let create_request ?(params=None) ?(progress_token=None) ~id ~meth () =
+
let create_request ?(params = None) ?(progress_token = None) ~id ~meth () =
Request { id; meth; params; progress_token }
-
let create_response ~id ~result =
-
Response { id; result }
+
let create_response ~id ~result = Response { id; result }
-
let create_error ~id ~code ~message ?(data=None) () =
+
let create_error ~id ~code ~message ?(data = None) () =
Error { id; code; message; data }
end
···
module Initialize = struct
module Request = struct
type t = {
-
capabilities: Json.t; (* ClientCapabilities *)
-
client_info: Implementation.t;
-
protocol_version: string;
+
capabilities : Json.t; (* ClientCapabilities *)
+
client_info : Implementation.t;
+
protocol_version : string;
}
let yojson_of_t { capabilities; client_info; protocol_version } =
-
`Assoc [
-
("capabilities", capabilities);
-
("clientInfo", Implementation.yojson_of_t client_info);
-
("protocolVersion", `String protocol_version);
-
]
+
`Assoc
+
[
+
("capabilities", capabilities);
+
("clientInfo", Implementation.yojson_of_t client_info);
+
("protocolVersion", `String protocol_version);
+
]
let t_of_yojson = function
| `Assoc fields as json ->
-
let capabilities = match List.assoc_opt "capabilities" fields with
-
| Some json -> json
-
| None -> Util.json_error "Missing capabilities field" json
-
in
-
let client_info = match List.assoc_opt "clientInfo" fields with
-
| Some json -> Implementation.t_of_yojson json
-
| None -> Util.json_error "Missing clientInfo field" json
-
in
-
let protocol_version = Util.get_string_field fields "protocolVersion" json in
-
{ capabilities; client_info; protocol_version }
+
let capabilities =
+
match List.assoc_opt "capabilities" fields with
+
| Some json -> json
+
| None -> Util.json_error "Missing capabilities field" json
+
in
+
let client_info =
+
match List.assoc_opt "clientInfo" fields with
+
| Some json -> Implementation.t_of_yojson json
+
| None -> Util.json_error "Missing clientInfo field" json
+
in
+
let protocol_version =
+
Util.get_string_field fields "protocolVersion" json
+
in
+
{ capabilities; client_info; protocol_version }
| j -> Util.json_error "Expected object for InitializeRequest" j
let create ~capabilities ~client_info ~protocol_version =
···
let to_jsonrpc ~id t =
let params = yojson_of_t t in
-
JSONRPCMessage.create_request ~id ~meth:Method.Initialize ~params:(Some params) ()
+
JSONRPCMessage.create_request ~id ~meth:Method.Initialize
+
~params:(Some params) ()
end
module Result = struct
type t = {
-
capabilities: Json.t; (* ServerCapabilities *)
-
server_info: Implementation.t;
-
protocol_version: string;
-
instructions: string option;
-
meta: Json.t option;
+
capabilities : Json.t; (* ServerCapabilities *)
+
server_info : Implementation.t;
+
protocol_version : string;
+
instructions : string option;
+
meta : Json.t option;
}
-
let yojson_of_t { capabilities; server_info; protocol_version; instructions; meta } =
-
let assoc = [
-
("capabilities", capabilities);
-
("serverInfo", Implementation.yojson_of_t server_info);
-
("protocolVersion", `String protocol_version);
-
] in
-
let assoc = match instructions with
+
let yojson_of_t
+
{ capabilities; server_info; protocol_version; instructions; meta } =
+
let assoc =
+
[
+
("capabilities", capabilities);
+
("serverInfo", Implementation.yojson_of_t server_info);
+
("protocolVersion", `String protocol_version);
+
]
+
in
+
let assoc =
+
match instructions with
| Some instr -> ("instructions", `String instr) :: assoc
| None -> assoc
in
-
let assoc = match meta with
-
| Some meta -> ("_meta", meta) :: assoc
-
| None -> assoc
+
let assoc =
+
match meta with Some meta -> ("_meta", meta) :: assoc | None -> assoc
in
`Assoc assoc
let t_of_yojson = function
| `Assoc fields as json ->
-
let capabilities = match List.assoc_opt "capabilities" fields with
-
| Some json -> json
-
| None -> Util.json_error "Missing capabilities field" json
-
in
-
let server_info = match List.assoc_opt "serverInfo" fields with
-
| Some json -> Implementation.t_of_yojson json
-
| None -> Util.json_error "Missing serverInfo field" json
-
in
-
let protocol_version = Util.get_string_field fields "protocolVersion" json in
-
let instructions = Util.get_optional_string_field fields "instructions" in
-
let meta = List.assoc_opt "_meta" fields in
-
{ capabilities; server_info; protocol_version; instructions; meta }
+
let capabilities =
+
match List.assoc_opt "capabilities" fields with
+
| Some json -> json
+
| None -> Util.json_error "Missing capabilities field" json
+
in
+
let server_info =
+
match List.assoc_opt "serverInfo" fields with
+
| Some json -> Implementation.t_of_yojson json
+
| None -> Util.json_error "Missing serverInfo field" json
+
in
+
let protocol_version =
+
Util.get_string_field fields "protocolVersion" json
+
in
+
let instructions =
+
Util.get_optional_string_field fields "instructions"
+
in
+
let meta = List.assoc_opt "_meta" fields in
+
{ capabilities; server_info; protocol_version; instructions; meta }
| j -> Util.json_error "Expected object for InitializeResult" j
-
let create ~capabilities ~server_info ~protocol_version ?instructions ?meta () =
+
let create ~capabilities ~server_info ~protocol_version ?instructions ?meta
+
() =
{ capabilities; server_info; protocol_version; instructions; meta }
let to_jsonrpc ~id t =
···
module Initialized = struct
module Notification = struct
-
type t = {
-
meta: Json.t option;
-
}
+
type t = { meta : Json.t option }
let yojson_of_t { meta } =
let assoc = [] in
-
let assoc = match meta with
-
| Some meta -> ("_meta", meta) :: assoc
-
| None -> assoc
+
let assoc =
+
match meta with Some meta -> ("_meta", meta) :: assoc | None -> assoc
in
`Assoc assoc
let t_of_yojson = function
| `Assoc fields ->
-
let meta = List.assoc_opt "_meta" fields in
-
{ meta }
+
let meta = List.assoc_opt "_meta" fields in
+
{ meta }
| j -> Util.json_error "Expected object for InitializedNotification" j
let create ?meta () = { meta }
let to_jsonrpc t =
-
let params = match yojson_of_t t with
-
| `Assoc [] -> None
-
| json -> Some json
+
let params =
+
match yojson_of_t t with `Assoc [] -> None | json -> Some json
in
JSONRPCMessage.create_notification ~meth:Method.Initialized ~params ()
end
end
-
(* Export the main interface for using the MCP protocol *)
-
let parse_message json =
-
JSONRPCMessage.t_of_yojson json
+
let parse_message json = JSONRPCMessage.t_of_yojson json
-
let create_notification ?(params=None) ~meth () =
+
let create_notification ?(params = None) ~meth () =
JSONRPCMessage.create_notification ~params ~meth ()
-
let create_request ?(params=None) ?(progress_token=None) ~id ~meth () =
+
let create_request ?(params = None) ?(progress_token = None) ~id ~meth () =
JSONRPCMessage.create_request ~params ~progress_token ~id ~meth ()
let create_response = JSONRPCMessage.create_response
let create_error = JSONRPCMessage.create_error
(* Content type constructors *)
-
let make_text_content text =
-
Text (TextContent.{ text; annotations = None })
+
let make_text_content text = Text TextContent.{ text; annotations = None }
let make_image_content data mime_type =
-
Image (ImageContent.{ data; mime_type; annotations = None })
+
Image ImageContent.{ data; mime_type; annotations = None }
let make_audio_content data mime_type =
-
Audio (AudioContent.{ data; mime_type; annotations = None })
+
Audio AudioContent.{ data; mime_type; annotations = None }
let make_resource_text_content uri text mime_type =
-
Resource (EmbeddedResource.{
-
resource = `Text TextResourceContents.{ uri; text; mime_type };
-
annotations = None;
-
})
+
Resource
+
EmbeddedResource.
+
{
+
resource = `Text TextResourceContents.{ uri; text; mime_type };
+
annotations = None;
+
}
let make_resource_blob_content uri blob mime_type =
-
Resource (EmbeddedResource.{
-
resource = `Blob BlobResourceContents.{ uri; blob; mime_type };
-
annotations = None;
-
})
+
Resource
+
EmbeddedResource.
+
{
+
resource = `Blob BlobResourceContents.{ uri; blob; mime_type };
+
annotations = None;
+
}
+667 -609
lib/mcp.mli
···
-
(** MCP - Model Context Protocol implementation
-
-
The Model Context Protocol (MCP) is a standardized protocol for AI agents to exchange context
-
with servers. This module provides the core OCaml implementation of MCP including
-
all message types, content representations, and serialization functionality.
-
+
(** MCP - Model Context Protocol implementation
+
+
The Model Context Protocol (MCP) is a standardized protocol for AI agents to
+
exchange context with servers. This module provides the core OCaml
+
implementation of MCP including all message types, content representations,
+
and serialization functionality.
+
MCP Architecture:
- Uses JSON-RPC 2.0 as its underlying message format with UTF-8 encoding
-
- Follows a client-server model where clients (often LLM-integrated applications) communicate with MCP servers
+
- Follows a client-server model where clients (often LLM-integrated
+
applications) communicate with MCP servers
- Supports multiple transport methods including stdio and streamable HTTP
-
- Implements a three-phase connection lifecycle: initialization, operation, and shutdown
-
- Provides capability negotiation during initialization to determine available features
-
- Offers four primary context exchange mechanisms:
-
1. Resources: Server-exposed data that provides context to language models
-
2. Tools: Server-exposed functionality that can be invoked by language models
-
3. Prompts: Server-defined templates for structuring interactions with models
+
- Implements a three-phase connection lifecycle: initialization, operation,
+
and shutdown
+
- Provides capability negotiation during initialization to determine
+
available features
+
- Offers four primary context exchange mechanisms: 1. Resources:
+
Server-exposed data that provides context to language models 2. Tools:
+
Server-exposed functionality that can be invoked by language models 3.
+
Prompts: Server-defined templates for structuring interactions with models
4. Sampling: Client-exposed ability to generate completions from LLMs
-
- Supports multimodal content types: text, images, audio, and embedded resources
+
- Supports multimodal content types: text, images, audio, and embedded
+
resources
- Includes standardized error handling with defined error codes
-
-
This implementation follows Protocol Revision 2025-03-26.
-
*)
+
+
This implementation follows Protocol Revision 2025-03-26. *)
open Jsonrpc
(** Utility functions for JSON parsing *)
module Util : sig
+
val json_error : ('a, unit, string, 'b) format4 -> Json.t -> 'a
(** Helper to raise a Json.Of_json exception with formatted message
@param fmt Format string for the error message
@param json JSON value to include in the exception
@return Never returns, always raises an exception
-
@raise Json.Of_json with the formatted message and JSON value
-
*)
-
val json_error : ('a, unit, string, 'b) format4 -> Json.t -> 'a
-
-
(** Extract a string field from JSON object or raise an error
+
@raise Json.Of_json with the formatted message and JSON value *)
+
+
val get_string_field : (string * Json.t) list -> string -> Json.t -> string
+
(** Extract a string field from JSON object or raise an error
@param fields Assoc list of fields from JSON object
@param name Field name to extract
@param json Original JSON for error context
@return The string value of the field
-
@raise Json.Of_json if the field is missing or not a string
-
*)
-
val get_string_field : (string * Json.t) list -> string -> Json.t -> string
-
+
@raise Json.Of_json if the field is missing or not a string *)
+
+
val get_optional_string_field :
+
(string * Json.t) list -> string -> string option
(** Extract an optional string field from JSON object
@param fields Assoc list of fields from JSON object
@param name Field name to extract
@return Some string if present and a string, None if missing
-
@raise Json.Of_json if the field exists but is not a string
-
*)
-
val get_optional_string_field : (string * Json.t) list -> string -> string option
-
+
@raise Json.Of_json if the field exists but is not a string *)
+
+
val get_int_field : (string * Json.t) list -> string -> Json.t -> int
(** Extract an int field from JSON object or raise an error
@param fields Assoc list of fields from JSON object
@param name Field name to extract
@param json Original JSON for error context
@return The int value of the field
-
@raise Json.Of_json if the field is missing or not an int
-
*)
-
val get_int_field : (string * Json.t) list -> string -> Json.t -> int
-
+
@raise Json.Of_json if the field is missing or not an int *)
+
+
val get_float_field : (string * Json.t) list -> string -> Json.t -> float
(** Extract a float field from JSON object or raise an error
@param fields Assoc list of fields from JSON object
@param name Field name to extract
@param json Original JSON for error context
@return The float value of the field
-
@raise Json.Of_json if the field is missing or not a float
-
*)
-
val get_float_field : (string * Json.t) list -> string -> Json.t -> float
-
+
@raise Json.Of_json if the field is missing or not a float *)
+
+
val get_bool_field : (string * Json.t) list -> string -> Json.t -> bool
(** Extract a boolean field from JSON object or raise an error
@param fields Assoc list of fields from JSON object
@param name Field name to extract
@param json Original JSON for error context
@return The boolean value of the field
-
@raise Json.Of_json if the field is missing or not a boolean
-
*)
-
val get_bool_field : (string * Json.t) list -> string -> Json.t -> bool
-
+
@raise Json.Of_json if the field is missing or not a boolean *)
+
+
val get_object_field :
+
(string * Json.t) list -> string -> Json.t -> (string * Json.t) list
(** Extract an object field from JSON object or raise an error
@param fields Assoc list of fields from JSON object
@param name Field name to extract
@param json Original JSON for error context
@return The object as an assoc list
-
@raise Json.Of_json if the field is missing or not an object
-
*)
-
val get_object_field : (string * Json.t) list -> string -> Json.t -> (string * Json.t) list
-
+
@raise Json.Of_json if the field is missing or not an object *)
+
+
val get_list_field : (string * Json.t) list -> string -> Json.t -> Json.t list
(** Extract a list field from JSON object or raise an error
@param fields Assoc list of fields from JSON object
@param name Field name to extract
@param json Original JSON for error context
@return The list items
-
@raise Json.Of_json if the field is missing or not a list
-
*)
-
val get_list_field : (string * Json.t) list -> string -> Json.t -> Json.t list
-
+
@raise Json.Of_json if the field is missing or not a list *)
+
+
val verify_string_field :
+
(string * Json.t) list -> string -> string -> Json.t -> unit
(** Verify a specific string value in a field
@param fields Assoc list of fields from JSON object
@param name Field name to check
···
@param json Original JSON for error context
@raise Json.Of_json if the field is missing or not equal to expected_value
*)
-
val verify_string_field : (string * Json.t) list -> string -> string -> Json.t -> unit
end
(** Error codes for JSON-RPC *)
module ErrorCode : sig
(** Standard JSON-RPC error codes with MCP-specific additions *)
-
type t =
-
| ParseError (** -32700 - Invalid JSON *)
-
| InvalidRequest (** -32600 - Invalid JSON-RPC request *)
-
| MethodNotFound (** -32601 - Method not available *)
-
| InvalidParams (** -32602 - Invalid method parameters *)
-
| InternalError (** -32603 - Internal JSON-RPC error *)
-
| ResourceNotFound (** -32002 - Custom MCP error: requested resource not found *)
-
| AuthRequired (** -32001 - Custom MCP error: authentication required *)
-
| CustomError of int (** For any other error codes *)
-
+
type t =
+
| ParseError (** -32700 - Invalid JSON *)
+
| InvalidRequest (** -32600 - Invalid JSON-RPC request *)
+
| MethodNotFound (** -32601 - Method not available *)
+
| InvalidParams (** -32602 - Invalid method parameters *)
+
| InternalError (** -32603 - Internal JSON-RPC error *)
+
| ResourceNotFound
+
(** -32002 - Custom MCP error: requested resource not found *)
+
| AuthRequired (** -32001 - Custom MCP error: authentication required *)
+
| CustomError of int (** For any other error codes *)
+
+
val to_int : t -> int
(** Convert the error code to its integer representation
@param code The error code to convert
-
@return The integer error code as defined in the JSON-RPC spec
-
*)
-
val to_int : t -> int
-
+
@return The integer error code as defined in the JSON-RPC spec *)
+
+
val to_message : t -> string
(** Get error message for standard error codes
@param code The error code to get message for
-
@return A standard message for the error code
-
*)
-
val to_message : t -> string
+
@return A standard message for the error code *)
end
(** MCP Protocol Methods - Algebraic data type representing all MCP methods *)
···
(** Method type representing all MCP protocol methods *)
type t =
(* Initialization and lifecycle methods *)
-
| Initialize (** Start the MCP lifecycle *)
-
| Initialized (** Signal readiness after initialization *)
-
+
| Initialize (** Start the MCP lifecycle *)
+
| Initialized (** Signal readiness after initialization *)
(* Resource methods *)
-
| ResourcesList (** Discover available resources *)
-
| ResourcesRead (** Retrieve resource contents *)
+
| ResourcesList (** Discover available resources *)
+
| ResourcesRead (** Retrieve resource contents *)
| ResourceTemplatesList (** List available resource templates *)
-
| ResourcesSubscribe (** Subscribe to resource changes *)
-
| ResourcesListChanged (** Resource list has changed *)
-
| ResourcesUpdated (** Resource has been updated *)
-
+
| ResourcesSubscribe (** Subscribe to resource changes *)
+
| ResourcesListChanged (** Resource list has changed *)
+
| ResourcesUpdated (** Resource has been updated *)
(* Tool methods *)
-
| ToolsList (** Discover available tools *)
-
| ToolsCall (** Invoke a tool *)
-
| ToolsListChanged (** Tool list has changed *)
-
+
| ToolsList (** Discover available tools *)
+
| ToolsCall (** Invoke a tool *)
+
| ToolsListChanged (** Tool list has changed *)
(* Prompt methods *)
-
| PromptsList (** Discover available prompts *)
-
| PromptsGet (** Retrieve a prompt template with arguments *)
-
| PromptsListChanged (** Prompt list has changed *)
-
+
| PromptsList (** Discover available prompts *)
+
| PromptsGet (** Retrieve a prompt template with arguments *)
+
| PromptsListChanged (** Prompt list has changed *)
(* Progress notifications *)
-
| Progress (** Progress update for long-running operations *)
-
+
| Progress (** Progress update for long-running operations *)
+
+
val to_string : t -> string
(** Convert method type to string representation
@param meth The method to convert
-
@return The string representation of the method (e.g., "initialize", "resources/list")
-
*)
-
val to_string : t -> string
-
+
@return
+
The string representation of the method (e.g., "initialize",
+
"resources/list") *)
+
+
val of_string : string -> t
(** Convert string to method type
@param s The string representation of the method
@return The corresponding method type
-
@raise Failure if the string is not a valid MCP method
-
*)
-
val of_string : string -> t
+
@raise Failure if the string is not a valid MCP method *)
end
-
(** Common types *)
(** Roles for conversation participants *)
module Role : sig
-
(** Role represents conversation participants in MCP messages.
-
Roles can be either 'user' or 'assistant', determining the
-
source of each message in a conversation. *)
type t = [ `User | `Assistant ]
+
(** Role represents conversation participants in MCP messages. Roles can be
+
either 'user' or 'assistant', determining the source of each message in a
+
conversation. *)
+
include Json.Jsonable.S with type t := t
end
(** Progress tokens for long-running operations *)
module ProgressToken : sig
-
(** Progress tokens identify long-running operations and enable
-
servers to provide progress updates to clients. This is used
-
to track operations that may take significant time to complete. *)
type t = [ `String of string | `Int of int ]
+
(** Progress tokens identify long-running operations and enable servers to
+
provide progress updates to clients. This is used to track operations that
+
may take significant time to complete. *)
+
include Json.Jsonable.S with type t := t
end
(** Request IDs *)
module RequestId : sig
-
(** Request IDs uniquely identify JSON-RPC requests, allowing responses
-
to be correlated with their originating requests. They can be either
-
string or integer values. *)
type t = [ `String of string | `Int of int ]
+
(** Request IDs uniquely identify JSON-RPC requests, allowing responses to be
+
correlated with their originating requests. They can be either string or
+
integer values. *)
+
include Json.Jsonable.S with type t := t
end
(** Cursors for pagination *)
module Cursor : sig
-
(** Cursors enable pagination in list operations for resources, tools, and prompts.
-
When a server has more items than can be returned in a single response,
-
it provides a cursor for the client to retrieve subsequent pages. *)
type t = string
+
(** Cursors enable pagination in list operations for resources, tools, and
+
prompts. When a server has more items than can be returned in a single
+
response, it provides a cursor for the client to retrieve subsequent
+
pages. *)
+
include Json.Jsonable.S with type t := t
end
(** Annotations for objects *)
module Annotated : sig
-
(** Annotations provide metadata for content objects, allowing
-
role-specific targeting and priority settings. *)
-
type t = {
-
annotations: annotation option;
-
}
+
type t = { annotations : annotation option }
+
(** Annotations provide metadata for content objects, allowing role-specific
+
targeting and priority settings. *)
+
and annotation = {
-
audience: Role.t list option;
-
(** Optional list of roles that should receive this content *)
-
priority: float option;
-
(** Optional priority value for this content *)
+
audience : Role.t list option;
+
(** Optional list of roles that should receive this content *)
+
priority : float option; (** Optional priority value for this content *)
}
+
include Json.Jsonable.S with type t := t
end
(** Text content - Core textual message representation in MCP *)
module TextContent : sig
-
(** TextContent represents plain text messages in MCP conversations.
-
This is the most common content type used for natural language interactions
-
between users and assistants. Text content is used in prompts, tool results,
-
and model responses.
-
+
type t = {
+
text : string; (** The actual text content as a UTF-8 encoded string *)
+
annotations : Annotated.annotation option;
+
(** Optional annotations for audience targeting and priority.
+
Annotations can restrict content visibility to specific roles
+
(user/assistant) and indicate relative importance of different
+
content elements. *)
+
}
+
(** TextContent represents plain text messages in MCP conversations. This is
+
the most common content type used for natural language interactions
+
between users and assistants. Text content is used in prompts, tool
+
results, and model responses.
+
In JSON-RPC, this is represented as:
{v
{
···
"text": "The text content of the message"
}
v}
-
+
For security, implementations must sanitize text content to prevent
injection attacks or unauthorized access to resources. *)
-
type t = {
-
text: string;
-
(** The actual text content as a UTF-8 encoded string *)
-
annotations: Annotated.annotation option;
-
(** Optional annotations for audience targeting and priority.
-
Annotations can restrict content visibility to specific roles (user/assistant)
-
and indicate relative importance of different content elements. *)
-
}
+
include Json.Jsonable.S with type t := t
end
(** Image content - Visual data representation in MCP *)
module ImageContent : sig
+
type t = {
+
data : string;
+
(** Base64-encoded image data. All binary image data must be encoded
+
using standard base64 encoding (RFC 4648) to safely transmit within
+
JSON. *)
+
mime_type : string;
+
(** MIME type of the image (e.g., "image/png", "image/jpeg",
+
"image/gif", "image/svg+xml"). This field is required and must
+
accurately represent the image format to ensure proper handling by
+
clients. *)
+
annotations : Annotated.annotation option;
+
(** Optional annotations for audience targeting and priority.
+
Annotations can restrict content visibility to specific roles
+
(user/assistant) and indicate relative importance of different
+
content elements. *)
+
}
(** ImageContent enables including visual information in MCP messages,
supporting multimodal interactions where visual context is important.
-
+
Images can be used in several scenarios:
- As user inputs for visual understanding tasks
- As context for generating descriptions or analysis
- As outputs from tools that generate visualizations
- As part of prompt templates with visual components
-
+
In JSON-RPC, this is represented as:
{v
{
···
"mimeType": "image/png"
}
v}
-
+
The data MUST be base64-encoded to ensure safe transmission in JSON.
-
Common mime types include image/png, image/jpeg, image/gif, and image/svg+xml. *)
-
type t = {
-
data: string;
-
(** Base64-encoded image data. All binary image data must be encoded using
-
standard base64 encoding (RFC 4648) to safely transmit within JSON. *)
-
mime_type: string;
-
(** MIME type of the image (e.g., "image/png", "image/jpeg", "image/gif", "image/svg+xml").
-
This field is required and must accurately represent the image format to ensure
-
proper handling by clients. *)
-
annotations: Annotated.annotation option;
-
(** Optional annotations for audience targeting and priority.
-
Annotations can restrict content visibility to specific roles (user/assistant)
-
and indicate relative importance of different content elements. *)
-
}
+
Common mime types include image/png, image/jpeg, image/gif, and
+
image/svg+xml. *)
+
include Json.Jsonable.S with type t := t
end
(** Audio content - Sound data representation in MCP *)
module AudioContent : sig
+
type t = {
+
data : string;
+
(** Base64-encoded audio data. All binary audio data must be encoded
+
using standard base64 encoding (RFC 4648) to safely transmit within
+
JSON. *)
+
mime_type : string;
+
(** MIME type of the audio (e.g., "audio/wav", "audio/mp3", "audio/ogg",
+
"audio/mpeg"). This field is required and must accurately represent
+
the audio format to ensure proper handling by clients. *)
+
annotations : Annotated.annotation option;
+
(** Optional annotations for audience targeting and priority.
+
Annotations can restrict content visibility to specific roles
+
(user/assistant) and indicate relative importance of different
+
content elements. *)
+
}
(** AudioContent enables including audio information in MCP messages,
supporting multimodal interactions where audio context is important.
-
+
Audio can be used in several scenarios:
- As user inputs for speech recognition or audio analysis
- As context for transcription or sound classification tasks
- As outputs from tools that generate audio samples
- As part of prompt templates with audio components
-
+
In JSON-RPC, this is represented as:
{v
{
···
"mimeType": "audio/wav"
}
v}
-
+
The data MUST be base64-encoded to ensure safe transmission in JSON.
-
Common mime types include audio/wav, audio/mp3, audio/ogg, and audio/mpeg. *)
-
type t = {
-
data: string;
-
(** Base64-encoded audio data. All binary audio data must be encoded using
-
standard base64 encoding (RFC 4648) to safely transmit within JSON. *)
-
mime_type: string;
-
(** MIME type of the audio (e.g., "audio/wav", "audio/mp3", "audio/ogg", "audio/mpeg").
-
This field is required and must accurately represent the audio format to ensure
-
proper handling by clients. *)
-
annotations: Annotated.annotation option;
-
(** Optional annotations for audience targeting and priority.
-
Annotations can restrict content visibility to specific roles (user/assistant)
-
and indicate relative importance of different content elements. *)
-
}
+
Common mime types include audio/wav, audio/mp3, audio/ogg, and audio/mpeg.
+
*)
+
include Json.Jsonable.S with type t := t
end
(** Base resource contents - Core resource metadata in MCP *)
module ResourceContents : sig
-
(** ResourceContents provides basic metadata for resources in MCP.
-
-
Resources are server-exposed data that provides context to language models,
-
such as files, database schemas, or application-specific information.
-
Each resource is uniquely identified by a URI.
-
-
The MCP resources architecture is designed to be application-driven, with
-
host applications determining how to incorporate context based on their needs.
-
-
In the protocol, resources are discovered via the 'resources/list' endpoint
-
and retrieved via the 'resources/read' endpoint. Servers that support resources
-
must declare the 'resources' capability during initialization. *)
type t = {
-
uri: string;
-
(** URI that uniquely identifies the resource.
-
-
Resources use standard URI schemes including:
-
- file:// - For filesystem-like resources
-
- https:// - For web-accessible resources
-
- git:// - For version control integration
-
-
The URI serves as a stable identifier even if the underlying content changes. *)
-
mime_type: string option;
-
(** Optional MIME type of the resource content to aid in client rendering.
-
Common MIME types include text/plain, application/json, image/png, etc.
-
For directories, the XDG MIME type inode/directory may be used. *)
+
uri : string;
+
(** URI that uniquely identifies the resource.
+
+
Resources use standard URI schemes including:
+
- file:// - For filesystem-like resources
+
- https:// - For web-accessible resources
+
- git:// - For version control integration
+
+
The URI serves as a stable identifier even if the underlying content
+
changes. *)
+
mime_type : string option;
+
(** Optional MIME type of the resource content to aid in client
+
rendering. Common MIME types include text/plain, application/json,
+
image/png, etc. For directories, the XDG MIME type inode/directory
+
may be used. *)
}
+
(** ResourceContents provides basic metadata for resources in MCP.
+
+
Resources are server-exposed data that provides context to language
+
models, such as files, database schemas, or application-specific
+
information. Each resource is uniquely identified by a URI.
+
+
The MCP resources architecture is designed to be application-driven, with
+
host applications determining how to incorporate context based on their
+
needs.
+
+
In the protocol, resources are discovered via the 'resources/list'
+
endpoint and retrieved via the 'resources/read' endpoint. Servers that
+
support resources must declare the 'resources' capability during
+
initialization. *)
+
include Json.Jsonable.S with type t := t
end
(** Text resource contents - Textual resource data *)
module TextResourceContents : sig
+
type t = {
+
uri : string;
+
(** URI that uniquely identifies the resource. This URI can be
+
referenced in subsequent requests to fetch updates. *)
+
text : string;
+
(** The actual text content of the resource as a UTF-8 encoded string.
+
This may be sanitized by the server to remove sensitive information.
+
*)
+
mime_type : string option;
+
(** Optional MIME type of the text content to aid in client rendering.
+
Common text MIME types include: text/plain, text/markdown,
+
text/x-python, application/json, text/html, text/csv, etc. *)
+
}
(** TextResourceContents represents a text-based resource in MCP.
-
+
Text resources are used for sharing code snippets, documentation, logs,
configuration files, and other textual information with language models.
-
+
The server handles access control and security, ensuring that only
authorized resources are shared with clients.
-
+
In JSON-RPC, this is represented as:
{v
{
···
"mimeType": "text/plain",
"text": "Resource content"
}
-
v}
-
*)
-
type t = {
-
uri: string;
-
(** URI that uniquely identifies the resource.
-
This URI can be referenced in subsequent requests to fetch updates. *)
-
text: string;
-
(** The actual text content of the resource as a UTF-8 encoded string.
-
This may be sanitized by the server to remove sensitive information. *)
-
mime_type: string option;
-
(** Optional MIME type of the text content to aid in client rendering.
-
Common text MIME types include: text/plain, text/markdown, text/x-python,
-
application/json, text/html, text/csv, etc. *)
-
}
+
v} *)
+
include Json.Jsonable.S with type t := t
end
(** Binary resource contents - Binary resource data *)
module BlobResourceContents : sig
+
type t = {
+
uri : string;
+
(** URI that uniquely identifies the resource. This URI can be
+
referenced in subsequent requests to fetch updates. *)
+
blob : string;
+
(** Base64-encoded binary data using standard base64 encoding (RFC
+
4648). This encoding ensures that binary data can be safely
+
transmitted in JSON. *)
+
mime_type : string option;
+
(** Optional MIME type of the binary content to aid in client rendering.
+
Common binary MIME types include: image/png, image/jpeg,
+
application/pdf, audio/wav, video/mp4, application/octet-stream,
+
etc. *)
+
}
(** BlobResourceContents represents a binary resource in MCP.
-
+
Binary resources allow sharing non-textual data like images, audio files,
-
PDFs, and other binary formats with language models that support processing
-
such content.
-
+
PDFs, and other binary formats with language models that support
+
processing such content.
+
In JSON-RPC, this is represented as:
{v
{
···
"blob": "base64-encoded-data"
}
v}
-
-
Binary data MUST be properly base64-encoded to ensure safe transmission
-
in JSON payloads. *)
-
type t = {
-
uri: string;
-
(** URI that uniquely identifies the resource.
-
This URI can be referenced in subsequent requests to fetch updates. *)
-
blob: string;
-
(** Base64-encoded binary data using standard base64 encoding (RFC 4648).
-
This encoding ensures that binary data can be safely transmitted in JSON. *)
-
mime_type: string option;
-
(** Optional MIME type of the binary content to aid in client rendering.
-
Common binary MIME types include: image/png, image/jpeg, application/pdf,
-
audio/wav, video/mp4, application/octet-stream, etc. *)
-
}
+
+
Binary data MUST be properly base64-encoded to ensure safe transmission in
+
JSON payloads. *)
+
include Json.Jsonable.S with type t := t
end
(** Embedded resource - Resource included directly in messages *)
module EmbeddedResource : sig
-
(** EmbeddedResource allows referencing server-side resources directly
-
in MCP messages, enabling seamless incorporation of managed content.
-
+
type t = {
+
resource :
+
[ `Text of TextResourceContents.t | `Blob of BlobResourceContents.t ];
+
(** The resource content, either as text or binary blob. *)
+
annotations : Annotated.annotation option;
+
(** Optional annotations for audience targeting and priority.
+
Annotations can restrict resource visibility to specific roles
+
(user/assistant) and indicate relative importance of different
+
content elements. *)
+
}
+
(** EmbeddedResource allows referencing server-side resources directly in MCP
+
messages, enabling seamless incorporation of managed content.
+
Embedded resources can be included in:
- Tool results to provide rich context
- Prompt templates to include reference materials
- Messages to provide additional context to language models
-
+
In contrast to direct content (TextContent, ImageContent, AudioContent),
-
embedded resources have the advantage of being persistently stored on the server
-
with a stable URI, allowing later retrieval and updates through the resources API.
-
-
For example, a tool might return an embedded resource containing a chart or
-
a large dataset that the client can later reference or update. *)
-
type t = {
-
resource: [ `Text of TextResourceContents.t | `Blob of BlobResourceContents.t ];
-
(** The resource content, either as text or binary blob. *)
-
annotations: Annotated.annotation option;
-
(** Optional annotations for audience targeting and priority.
-
Annotations can restrict resource visibility to specific roles (user/assistant)
-
and indicate relative importance of different content elements. *)
-
}
+
embedded resources have the advantage of being persistently stored on the
+
server with a stable URI, allowing later retrieval and updates through the
+
resources API.
+
+
For example, a tool might return an embedded resource containing a chart
+
or a large dataset that the client can later reference or update. *)
+
include Json.Jsonable.S with type t := t
end
-
(** Content type used in messages - Unified multimodal content representation in MCP *)
-
type content =
-
| Text of TextContent.t (** Text content for natural language messages. This is the most common content type for user-assistant interactions. *)
-
| Image of ImageContent.t (** Image content for visual data. Used for sharing visual context in multimodal conversations. *)
-
| Audio of AudioContent.t (** Audio content for audio data. Used for sharing audio context in multimodal conversations. *)
-
| Resource of EmbeddedResource.t (** Resource content for referencing server-side resources. Used for incorporating managed server content with stable URIs. *)
+
(** Content type used in messages - Unified multimodal content representation in
+
MCP *)
+
type content =
+
| Text of TextContent.t
+
(** Text content for natural language messages. This is the most common
+
content type for user-assistant interactions. *)
+
| Image of ImageContent.t
+
(** Image content for visual data. Used for sharing visual context in
+
multimodal conversations. *)
+
| Audio of AudioContent.t
+
(** Audio content for audio data. Used for sharing audio context in
+
multimodal conversations. *)
+
| Resource of EmbeddedResource.t
+
(** Resource content for referencing server-side resources. Used for
+
incorporating managed server content with stable URIs. *)
-
(** Convert content to Yojson representation
-
@param content The content to convert
-
@return JSON representation of the content
-
*)
val yojson_of_content : content -> Json.t
+
(** Convert content to Yojson representation
+
@param content The content to convert
+
@return JSON representation of the content *)
-
(** Convert Yojson representation to content
-
@param json JSON representation of content
-
@return Parsed content object
-
*)
val content_of_yojson : Json.t -> content
+
(** Convert Yojson representation to content
+
@param json JSON representation of content
+
@return Parsed content object *)
(** Message for prompts - Template messages in the MCP prompts feature *)
module PromptMessage : sig
-
(** PromptMessage represents a message in an MCP prompt template,
-
containing a role and content which can be customized with arguments.
-
+
type t = {
+
role : Role.t;
+
(** The role of the message sender (user or assistant). Prompt templates
+
typically alternate between user and assistant messages to create a
+
conversation structure. *)
+
content : content;
+
(** The message content, which can be text, image, audio, or resource.
+
This unified content type supports rich multimodal prompts. *)
+
}
+
(** PromptMessage represents a message in an MCP prompt template, containing a
+
role and content which can be customized with arguments.
+
Prompt messages are part of prompt templates exposed by servers through
the prompts/get endpoint. They define structured conversation templates
that can be instantiated with user-provided arguments.
-
-
The prompt feature is designed to be user-controlled, with prompts typically
-
exposed through UI elements like slash commands that users can explicitly select.
-
+
+
The prompt feature is designed to be user-controlled, with prompts
+
typically exposed through UI elements like slash commands that users can
+
explicitly select.
+
In JSON-RPC, prompt messages are represented as:
{v
{
···
}
}
v}
-
+
Where $code would be replaced with a user-provided argument. *)
-
type t = {
-
role: Role.t;
-
(** The role of the message sender (user or assistant).
-
Prompt templates typically alternate between user and assistant messages
-
to create a conversation structure. *)
-
content: content;
-
(** The message content, which can be text, image, audio, or resource.
-
This unified content type supports rich multimodal prompts. *)
-
}
+
include Json.Jsonable.S with type t := t
end
(** Message for sampling - Messages used in LLM completion requests *)
module SamplingMessage : sig
-
(** SamplingMessage represents a message in an MCP sampling request,
-
used for AI model generation based on a prompt.
-
+
type t = {
+
role : Role.t;
+
(** The role of the message sender (user or assistant). Typically, a
+
sampling request will contain multiple messages representing a
+
conversation history, with alternating roles. *)
+
content :
+
[ `Text of TextContent.t
+
| `Image of ImageContent.t
+
| `Audio of AudioContent.t ];
+
(** The message content, restricted to text, image, or audio (no
+
resources). Resources are not included since sampling messages
+
represent the actual context window for the LLM, not template
+
definitions. *)
+
}
+
(** SamplingMessage represents a message in an MCP sampling request, used for
+
AI model generation based on a prompt.
+
The sampling feature allows clients to expose language model capabilities
to servers, enabling servers to request completions from the client's LLM.
This is effectively the reverse of the normal MCP flow, with the server
requesting generative capabilities from the client.
-
+
Sampling messages differ from prompt messages in that they don't support
-
embedded resources, as they represent the actual context window being
-
sent to the LLM rather than template definitions.
-
+
embedded resources, as they represent the actual context window being sent
+
to the LLM rather than template definitions.
+
Clients that support sampling must declare the 'sampling' capability
during initialization. *)
-
type t = {
-
role: Role.t;
-
(** The role of the message sender (user or assistant).
-
Typically, a sampling request will contain multiple messages
-
representing a conversation history, with alternating roles. *)
-
content: [ `Text of TextContent.t | `Image of ImageContent.t | `Audio of AudioContent.t ];
-
(** The message content, restricted to text, image, or audio (no resources).
-
Resources are not included since sampling messages represent the
-
actual context window for the LLM, not template definitions. *)
-
}
+
include Json.Jsonable.S with type t := t
end
(** Implementation information *)
module Implementation : sig
+
type t = {
+
name : string; (** Name of the implementation *)
+
version : string; (** Version of the implementation *)
+
}
(** Implementation provides metadata about client and server implementations,
used during the initialization phase to identify each party. *)
-
type t = {
-
name: string;
-
(** Name of the implementation *)
-
version: string;
-
(** Version of the implementation *)
-
}
+
include Json.Jsonable.S with type t := t
end
(** JSONRPC message types - Core message protocol for MCP
-
MCP uses JSON-RPC 2.0 as its underlying messaging protocol.
-
All MCP messages are encoded as JSON-RPC 2.0 messages with UTF-8 encoding,
-
following the standard JSON-RPC message formats with some MCP-specific extensions.
-
-
MCP defines four message types:
-
1. Notifications: One-way messages that don't expect a response
-
2. Requests: Messages that expect a corresponding response
-
3. Responses: Replies to requests with successful results
-
4. Errors: Replies to requests with error information
-
+
MCP uses JSON-RPC 2.0 as its underlying messaging protocol. All MCP messages
+
are encoded as JSON-RPC 2.0 messages with UTF-8 encoding, following the
+
standard JSON-RPC message formats with some MCP-specific extensions.
+
+
MCP defines four message types: 1. Notifications: One-way messages that
+
don't expect a response 2. Requests: Messages that expect a corresponding
+
response 3. Responses: Replies to requests with successful results 4.
+
Errors: Replies to requests with error information
+
These can be transported over multiple transport mechanisms:
- stdio: Communication over standard input/output
- Streamable HTTP: HTTP POST/GET with SSE for server streaming
- Custom transports: Implementation-specific transports
-
-
Messages may be sent individually or as part of a JSON-RPC batch.
-
*)
+
+
Messages may be sent individually or as part of a JSON-RPC batch. *)
module JSONRPCMessage : sig
-
(** Notification represents a JSON-RPC notification (one-way message without a response).
-
+
type notification = {
+
meth : Method.t;
+
(** Method for the notification, using the Method.t type to ensure type
+
safety. Examples: Method.Initialized, Method.ResourcesUpdated *)
+
params : Json.t option;
+
(** Optional parameters for the notification as arbitrary JSON. The
+
structure depends on the specific notification method. *)
+
}
+
(** Notification represents a JSON-RPC notification (one-way message without a
+
response).
+
Notifications are used for events that don't require a response, such as:
- The 'initialized' notification completing initialization
- Resource change notifications
- Progress updates for long-running operations
- List changed notifications for tools, resources, and prompts
-
+
In JSON-RPC, notifications are identified by the absence of an 'id' field:
{v
{
···
"uri": "file:///project/src/main.rs"
}
}
-
v}
-
*)
-
type notification = {
-
meth: Method.t;
-
(** Method for the notification, using the Method.t type to ensure type safety.
-
Examples: Method.Initialized, Method.ResourcesUpdated *)
-
params: Json.t option;
-
(** Optional parameters for the notification as arbitrary JSON.
-
The structure depends on the specific notification method. *)
-
}
+
v} *)
+
type request = {
+
id : RequestId.t;
+
(** Unique identifier for the request, which will be echoed in the
+
response. This can be a string or integer and should be unique
+
within the session. *)
+
meth : Method.t;
+
(** Method for the request, using the Method.t type to ensure type
+
safety. Examples: Method.Initialize, Method.ResourcesRead,
+
Method.ToolsCall *)
+
params : Json.t option;
+
(** Optional parameters for the request as arbitrary JSON. The structure
+
depends on the specific request method. *)
+
progress_token : ProgressToken.t option;
+
(** Optional progress token for long-running operations. If provided,
+
the server can send progress notifications using this token to
+
inform the client about the operation's status. *)
+
}
(** Request represents a JSON-RPC request that expects a response.
-
+
Requests are used for operations that require a response, such as:
- Initialization
- Listing resources, tools, or prompts
- Reading resources
- Calling tools
- Getting prompts
-
-
In JSON-RPC, requests include an 'id' field that correlates with the response:
+
+
In JSON-RPC, requests include an 'id' field that correlates with the
+
response:
{v
{
"jsonrpc": "2.0",
···
"uri": "file:///project/src/main.rs"
}
}
-
v}
-
*)
-
type request = {
-
id: RequestId.t;
-
(** Unique identifier for the request, which will be echoed in the response.
-
This can be a string or integer and should be unique within the session. *)
-
meth: Method.t;
-
(** Method for the request, using the Method.t type to ensure type safety.
-
Examples: Method.Initialize, Method.ResourcesRead, Method.ToolsCall *)
-
params: Json.t option;
-
(** Optional parameters for the request as arbitrary JSON.
-
The structure depends on the specific request method. *)
-
progress_token: ProgressToken.t option;
-
(** Optional progress token for long-running operations.
-
If provided, the server can send progress notifications using this token
-
to inform the client about the operation's status. *)
-
}
+
v} *)
+
type response = {
+
id : RequestId.t;
+
(** ID matching the original request, allowing clients to correlate
+
responses with their originating requests, especially important when
+
multiple requests are in flight. *)
+
result : Json.t;
+
(** Result of the successful request as arbitrary JSON. The structure
+
depends on the specific request method that was called. *)
+
}
(** Response represents a successful JSON-RPC response to a request.
-
+
Responses are sent in reply to requests and contain the successful result.
Each response must include the same ID as its corresponding request.
-
+
In JSON-RPC, responses include the 'id' field matching the request:
{v
{
···
]
}
}
-
v}
-
*)
-
type response = {
-
id: RequestId.t;
-
(** ID matching the original request, allowing clients to correlate
-
responses with their originating requests, especially important
-
when multiple requests are in flight. *)
-
result: Json.t;
-
(** Result of the successful request as arbitrary JSON.
-
The structure depends on the specific request method that was called. *)
-
}
+
v} *)
+
type error = {
+
id : RequestId.t;
+
(** ID matching the original request, allowing clients to correlate
+
errors with their originating requests. *)
+
code : int;
+
(** Error code indicating the type of error, following the JSON-RPC
+
standard. Common codes include:
+
- -32700: Parse error
+
- -32600: Invalid request
+
- -32601: Method not found
+
- -32602: Invalid params
+
- -32603: Internal error
+
- -32002: Resource not found (MCP-specific)
+
- -32001: Authentication required (MCP-specific) *)
+
message : string;
+
(** Human-readable error message describing the issue. This should be
+
concise but informative enough for debugging. *)
+
data : Json.t option;
+
(** Optional additional error data as arbitrary JSON. This can provide
+
more context about the error, such as which resource wasn't found or
+
which parameter was invalid. *)
+
}
(** Error represents an error response to a JSON-RPC request.
-
-
Errors are sent in reply to requests when processing fails.
-
Each error must include the same ID as its corresponding request.
-
+
+
Errors are sent in reply to requests when processing fails. Each error
+
must include the same ID as its corresponding request.
+
MCP defines several standard error codes:
- Standard JSON-RPC errors (-32700 to -32603)
- MCP-specific errors (-32002 for resource not found, etc.)
-
+
In JSON-RPC, errors follow this structure:
{v
{
···
}
}
}
-
v}
-
*)
-
type error = {
-
id: RequestId.t;
-
(** ID matching the original request, allowing clients to correlate
-
errors with their originating requests. *)
-
code: int;
-
(** Error code indicating the type of error, following the JSON-RPC standard.
-
Common codes include:
-
- -32700: Parse error
-
- -32600: Invalid request
-
- -32601: Method not found
-
- -32602: Invalid params
-
- -32603: Internal error
-
- -32002: Resource not found (MCP-specific)
-
- -32001: Authentication required (MCP-specific) *)
-
message: string;
-
(** Human-readable error message describing the issue.
-
This should be concise but informative enough for debugging. *)
-
data: Json.t option;
-
(** Optional additional error data as arbitrary JSON.
-
This can provide more context about the error, such as which
-
resource wasn't found or which parameter was invalid. *)
-
}
+
v} *)
-
(** Union type for all JSON-RPC message kinds, providing a single type
-
that can represent any MCP message. *)
+
(** Union type for all JSON-RPC message kinds, providing a single type that
+
can represent any MCP message. *)
type t =
| Notification of notification
| Request of request
| Response of response
| Error of error
-
(** Convert notification to Yojson representation
-
@param notification The notification to convert
-
@return JSON representation of the notification
-
*)
val yojson_of_notification : notification -> Json.t
-
-
(** Convert request to Yojson representation
-
@param request The request to convert
-
@return JSON representation of the request
-
*)
+
(** Convert notification to Yojson representation
+
@param notification The notification to convert
+
@return JSON representation of the notification *)
+
val yojson_of_request : request -> Json.t
-
-
(** Convert response to Yojson representation
-
@param response The response to convert
-
@return JSON representation of the response
-
*)
+
(** Convert request to Yojson representation
+
@param request The request to convert
+
@return JSON representation of the request *)
+
val yojson_of_response : response -> Json.t
-
-
(** Convert error to Yojson representation
-
@param error The error to convert
-
@return JSON representation of the error
-
*)
+
(** Convert response to Yojson representation
+
@param response The response to convert
+
@return JSON representation of the response *)
+
val yojson_of_error : error -> Json.t
-
-
(** Convert any message to Yojson representation
-
@param message The message to convert
-
@return JSON representation of the message
-
*)
+
(** Convert error to Yojson representation
+
@param error The error to convert
+
@return JSON representation of the error *)
+
val yojson_of_t : t -> Json.t
+
(** Convert any message to Yojson representation
+
@param message The message to convert
+
@return JSON representation of the message *)
-
(** Convert Yojson representation to notification
+
val notification_of_yojson : Json.t -> notification
+
(** Convert Yojson representation to notification
@param json JSON representation of a notification
@return Parsed notification object
-
@raise Parse error if the JSON is not a valid notification
-
*)
-
val notification_of_yojson : Json.t -> notification
-
-
(** Convert Yojson representation to request
+
@raise Parse error if the JSON is not a valid notification *)
+
+
val request_of_yojson : Json.t -> request
+
(** Convert Yojson representation to request
@param json JSON representation of a request
@return Parsed request object
-
@raise Parse error if the JSON is not a valid request
-
*)
-
val request_of_yojson : Json.t -> request
-
-
(** Convert Yojson representation to response
+
@raise Parse error if the JSON is not a valid request *)
+
+
val response_of_yojson : Json.t -> response
+
(** Convert Yojson representation to response
@param json JSON representation of a response
@return Parsed response object
-
@raise Parse error if the JSON is not a valid response
-
*)
-
val response_of_yojson : Json.t -> response
-
-
(** Convert Yojson representation to error
+
@raise Parse error if the JSON is not a valid response *)
+
+
val error_of_yojson : Json.t -> error
+
(** Convert Yojson representation to error
@param json JSON representation of an error
@return Parsed error object
-
@raise Parse error if the JSON is not a valid error
-
*)
-
val error_of_yojson : Json.t -> error
-
-
(** Convert Yojson representation to any message
+
@raise Parse error if the JSON is not a valid error *)
+
+
val t_of_yojson : Json.t -> t
+
(** Convert Yojson representation to any message
@param json JSON representation of any message type
@return Parsed message object
-
@raise Parse error if the JSON is not a valid message
-
*)
-
val t_of_yojson : Json.t -> t
+
@raise Parse error if the JSON is not a valid message *)
+
val create_notification : ?params:Json.t option -> meth:Method.t -> unit -> t
(** Create a new notification message
@param params Optional parameters for the notification
@param meth Method name for the notification
-
@return A new JSON-RPC notification message
-
*)
-
val create_notification : ?params:Json.t option -> meth:Method.t -> unit -> t
-
+
@return A new JSON-RPC notification message *)
+
+
val create_request :
+
?params:Json.t option ->
+
?progress_token:ProgressToken.t option ->
+
id:RequestId.t ->
+
meth:Method.t ->
+
unit ->
+
t
(** Create a new request message
@param params Optional parameters for the request
@param progress_token Optional progress token for long-running operations
@param id Unique identifier for the request
@param meth Method name for the request
-
@return A new JSON-RPC request message
-
*)
-
val create_request : ?params:Json.t option -> ?progress_token:ProgressToken.t option -> id:RequestId.t -> meth:Method.t -> unit -> t
-
+
@return A new JSON-RPC request message *)
+
+
val create_response : id:RequestId.t -> result:Json.t -> t
(** Create a new response message
@param id ID matching the original request
@param result Result of the successful request
-
@return A new JSON-RPC response message
-
*)
-
val create_response : id:RequestId.t -> result:Json.t -> t
-
+
@return A new JSON-RPC response message *)
+
+
val create_error :
+
id:RequestId.t ->
+
code:int ->
+
message:string ->
+
?data:Json.t option ->
+
unit ->
+
t
(** Create a new error message
@param id ID matching the original request
@param code Error code indicating the type of error
@param message Human-readable error message
@param data Optional additional error data
-
@return A new JSON-RPC error message
-
*)
-
val create_error : id:RequestId.t -> code:int -> message:string -> ?data:Json.t option -> unit -> t
+
@return A new JSON-RPC error message *)
end
-
(** Initialize request/response - The first phase of the MCP lifecycle
-
-
The initialization phase is the mandatory first interaction between client and server.
-
During this phase, the protocol version is negotiated and capabilities are exchanged
-
to determine which optional features will be available during the session.
-
-
This follows a strict sequence:
-
1. Client sends an InitializeRequest containing its capabilities and protocol version
-
2. Server responds with an InitializeResult containing its capabilities and protocol version
-
3. Client sends an InitializedNotification to signal it's ready for normal operations
-
-
The Initialize module handles steps 1 and 2 of this process.
-
*)
+
(** Initialize request/response - The first phase of the MCP lifecycle
+
+
The initialization phase is the mandatory first interaction between client
+
and server. During this phase, the protocol version is negotiated and
+
capabilities are exchanged to determine which optional features will be
+
available during the session.
+
+
This follows a strict sequence: 1. Client sends an InitializeRequest
+
containing its capabilities and protocol version 2. Server responds with an
+
InitializeResult containing its capabilities and protocol version 3. Client
+
sends an InitializedNotification to signal it's ready for normal operations
+
+
The Initialize module handles steps 1 and 2 of this process. *)
module Initialize : sig
(** Initialize request *)
module Request : sig
-
(** InitializeRequest starts the MCP lifecycle, negotiating capabilities
-
and protocol versions between client and server. This is always the first
-
message sent by the client and MUST NOT be part of a JSON-RPC batch.
-
-
The client SHOULD send the latest protocol version it supports. If the server
-
does not support this version, it will respond with a version it does support,
-
and the client must either use that version or disconnect. *)
type t = {
-
capabilities: Json.t; (** ClientCapabilities that define supported optional features.
-
This includes which optional protocol features the client supports,
-
such as 'roots' (filesystem access), 'sampling' (LLM generation),
-
and any experimental features. *)
-
client_info: Implementation.t;
-
(** Client implementation details (name and version) used for identification
-
and debugging. Helps servers understand which client they're working with. *)
-
protocol_version: string;
-
(** MCP protocol version supported by the client, formatted as YYYY-MM-DD
-
according to the MCP versioning scheme. Example: "2025-03-26" *)
+
capabilities : Json.t;
+
(** ClientCapabilities that define supported optional features. This
+
includes which optional protocol features the client supports,
+
such as 'roots' (filesystem access), 'sampling' (LLM generation),
+
and any experimental features. *)
+
client_info : Implementation.t;
+
(** Client implementation details (name and version) used for
+
identification and debugging. Helps servers understand which
+
client they're working with. *)
+
protocol_version : string;
+
(** MCP protocol version supported by the client, formatted as
+
YYYY-MM-DD according to the MCP versioning scheme. Example:
+
"2025-03-26" *)
}
+
(** InitializeRequest starts the MCP lifecycle, negotiating capabilities and
+
protocol versions between client and server. This is always the first
+
message sent by the client and MUST NOT be part of a JSON-RPC batch.
+
+
The client SHOULD send the latest protocol version it supports. If the
+
server does not support this version, it will respond with a version it
+
does support, and the client must either use that version or disconnect.
+
*)
+
include Json.Jsonable.S with type t := t
+
val create :
+
capabilities:Json.t ->
+
client_info:Implementation.t ->
+
protocol_version:string ->
+
t
(** Create a new initialization request
-
@param capabilities Client capabilities that define supported optional features
+
@param capabilities
+
Client capabilities that define supported optional features
@param client_info Client implementation details
@param protocol_version MCP protocol version supported by the client
-
@return A new initialization request
-
*)
-
val create : capabilities:Json.t -> client_info:Implementation.t -> protocol_version:string -> t
-
+
@return A new initialization request *)
+
+
val to_jsonrpc : id:RequestId.t -> t -> JSONRPCMessage.t
(** Convert to JSON-RPC message
@param id Unique request identifier
@param t Initialization request
-
@return JSON-RPC message containing the initialization request
-
*)
-
val to_jsonrpc : id:RequestId.t -> t -> JSONRPCMessage.t
+
@return JSON-RPC message containing the initialization request *)
end
(** Initialize result *)
module Result : sig
-
(** InitializeResult is the server's response to an initialization request,
-
completing capability negotiation and establishing the protocol version.
-
-
After receiving this message, the client must send an InitializedNotification.
-
The server should not send any requests other than pings and logging before
-
receiving the initialized notification. *)
type t = {
-
capabilities: Json.t; (** ServerCapabilities that define supported optional features.
-
This declares which server features are available, including:
-
- prompts: Server provides prompt templates
-
- resources: Server provides readable resources
-
- tools: Server exposes callable tools
-
- logging: Server emits structured log messages
-
-
Each capability may have sub-capabilities like:
-
- listChanged: Server will notify when available items change
-
- subscribe: Clients can subscribe to individual resources *)
-
server_info: Implementation.t;
-
(** Server implementation details (name and version) used for identification
-
and debugging. Helps clients understand which server they're working with. *)
-
protocol_version: string;
-
(** MCP protocol version supported by the server, formatted as YYYY-MM-DD.
-
If the server supports the client's requested version, it responds with
-
the same version. Otherwise, it responds with a version it does support. *)
-
instructions: string option;
-
(** Optional instructions for using the server. These can provide human-readable
-
guidance on how to interact with this specific server implementation. *)
-
meta: Json.t option;
-
(** Optional additional metadata as arbitrary JSON. Can contain server-specific
-
information not covered by the standard fields. *)
+
capabilities : Json.t;
+
(** ServerCapabilities that define supported optional features. This
+
declares which server features are available, including:
+
- prompts: Server provides prompt templates
+
- resources: Server provides readable resources
+
- tools: Server exposes callable tools
+
- logging: Server emits structured log messages
+
+
Each capability may have sub-capabilities like:
+
- listChanged: Server will notify when available items change
+
- subscribe: Clients can subscribe to individual resources *)
+
server_info : Implementation.t;
+
(** Server implementation details (name and version) used for
+
identification and debugging. Helps clients understand which
+
server they're working with. *)
+
protocol_version : string;
+
(** MCP protocol version supported by the server, formatted as
+
YYYY-MM-DD. If the server supports the client's requested version,
+
it responds with the same version. Otherwise, it responds with a
+
version it does support. *)
+
instructions : string option;
+
(** Optional instructions for using the server. These can provide
+
human-readable guidance on how to interact with this specific
+
server implementation. *)
+
meta : Json.t option;
+
(** Optional additional metadata as arbitrary JSON. Can contain
+
server-specific information not covered by the standard fields. *)
}
+
(** InitializeResult is the server's response to an initialization request,
+
completing capability negotiation and establishing the protocol version.
+
+
After receiving this message, the client must send an
+
InitializedNotification. The server should not send any requests other
+
than pings and logging before receiving the initialized notification. *)
+
include Json.Jsonable.S with type t := t
+
val create :
+
capabilities:Json.t ->
+
server_info:Implementation.t ->
+
protocol_version:string ->
+
?instructions:string ->
+
?meta:Json.t ->
+
unit ->
+
t
(** Create a new initialization result
-
@param capabilities Server capabilities that define supported optional features
+
@param capabilities
+
Server capabilities that define supported optional features
@param server_info Server implementation details
@param protocol_version MCP protocol version supported by the server
@param instructions Optional instructions for using the server
@param meta Optional additional metadata
-
@return A new initialization result
-
*)
-
val create : capabilities:Json.t -> server_info:Implementation.t -> protocol_version:string -> ?instructions:string -> ?meta:Json.t -> unit -> t
-
+
@return A new initialization result *)
+
+
val to_jsonrpc : id:RequestId.t -> t -> JSONRPCMessage.t
(** Convert to JSON-RPC message
@param id ID matching the original request
@param t Initialization result
-
@return JSON-RPC message containing the initialization result
-
*)
-
val to_jsonrpc : id:RequestId.t -> t -> JSONRPCMessage.t
+
@return JSON-RPC message containing the initialization result *)
end
end
-
(** Initialized notification - Completes the initialization phase of the MCP lifecycle *)
+
(** Initialized notification - Completes the initialization phase of the MCP
+
lifecycle *)
module Initialized : sig
module Notification : sig
-
(** InitializedNotification is sent by the client after receiving the initialization
-
response, indicating it's ready to begin normal operations. This completes the
-
three-step initialization process, after which both client and server can
-
freely exchange messages according to the negotiated capabilities.
-
-
Only after this notification has been sent should the client begin normal operations
-
like listing resources, calling tools, or requesting prompts. *)
type t = {
-
meta: Json.t option;
-
(** Optional additional metadata as arbitrary JSON. Can contain client-specific
-
information not covered by the standard fields. *)
+
meta : Json.t option;
+
(** Optional additional metadata as arbitrary JSON. Can contain
+
client-specific information not covered by the standard fields. *)
}
+
(** InitializedNotification is sent by the client after receiving the
+
initialization response, indicating it's ready to begin normal
+
operations. This completes the three-step initialization process, after
+
which both client and server can freely exchange messages according to
+
the negotiated capabilities.
+
+
Only after this notification has been sent should the client begin
+
normal operations like listing resources, calling tools, or requesting
+
prompts. *)
+
include Json.Jsonable.S with type t := t
+
val create : ?meta:Json.t -> unit -> t
(** Create a new initialized notification
@param meta Optional additional metadata
-
@return A new initialized notification
-
*)
-
val create : ?meta:Json.t -> unit -> t
-
+
@return A new initialized notification *)
+
+
val to_jsonrpc : t -> JSONRPCMessage.t
(** Convert to JSON-RPC message
@param t Initialized notification
-
@return JSON-RPC message containing the initialized notification
-
*)
-
val to_jsonrpc : t -> JSONRPCMessage.t
+
@return JSON-RPC message containing the initialized notification *)
end
end
-
(** Parse a JSON message into an MCP message
-
-
This function takes a raw JSON value and parses it into a structured MCP message.
-
It's the primary entry point for processing incoming JSON-RPC messages in the MCP protocol.
-
-
The function determines the message type (notification, request, response, or error)
-
based on the presence and values of specific fields:
+
val parse_message : Json.t -> JSONRPCMessage.t
+
(** Parse a JSON message into an MCP message
+
+
This function takes a raw JSON value and parses it into a structured MCP
+
message. It's the primary entry point for processing incoming JSON-RPC
+
messages in the MCP protocol.
+
+
The function determines the message type (notification, request, response,
+
or error) based on the presence and values of specific fields:
- A message with "method" but no "id" is a notification
- A message with "method" and "id" is a request
- A message with "id" and "result" is a response
- A message with "id" and "error" is an error
-
-
@param json The JSON message to parse, typically received from the transport layer
+
+
@param json
+
The JSON message to parse, typically received from the transport layer
@return The parsed MCP message as a structured JSONRPCMessage.t value
-
@raise Parse error if the JSON cannot be parsed as a valid MCP message
-
*)
-
val parse_message : Json.t -> JSONRPCMessage.t
+
@raise Parse error if the JSON cannot be parsed as a valid MCP message *)
+
val create_notification :
+
?params:Json.t option -> meth:Method.t -> unit -> JSONRPCMessage.t
(** Create a new notification message
-
-
Notifications are one-way messages that don't expect a response.
-
This is a convenience wrapper around JSONRPCMessage.create_notification.
-
+
+
Notifications are one-way messages that don't expect a response. This is a
+
convenience wrapper around JSONRPCMessage.create_notification.
+
Common notifications in MCP include:
- "notifications/initialized" - Sent after initialization
- "notifications/progress" - Updates on long-running operations
- "notifications/resources/updated" - Resource content changed
- "notifications/prompts/list_changed" - Available prompts changed
- "notifications/tools/list_changed" - Available tools changed
-
+
@param params Optional parameters for the notification as a JSON value
@param meth Method type for the notification
-
@return A new JSON-RPC notification message
-
*)
-
val create_notification : ?params:Json.t option -> meth:Method.t -> unit -> JSONRPCMessage.t
+
@return A new JSON-RPC notification message *)
+
val create_request :
+
?params:Json.t option ->
+
?progress_token:ProgressToken.t option ->
+
id:RequestId.t ->
+
meth:Method.t ->
+
unit ->
+
JSONRPCMessage.t
(** Create a new request message
-
-
Requests are messages that expect a corresponding response.
-
This is a convenience wrapper around JSONRPCMessage.create_request.
-
+
+
Requests are messages that expect a corresponding response. This is a
+
convenience wrapper around JSONRPCMessage.create_request.
+
Common requests in MCP include:
- "initialize" - Start the MCP lifecycle
- "resources/list" - Discover available resources
···
- "tools/call" - Invoke a tool
- "prompts/list" - Discover available prompts
- "prompts/get" - Retrieve a prompt template
-
+
@param params Optional parameters for the request as a JSON value
-
@param progress_token Optional progress token for long-running operations
-
that can report progress updates
-
@param id Unique identifier for the request, used to correlate with the response
+
@param progress_token
+
Optional progress token for long-running operations that can report
+
progress updates
+
@param id
+
Unique identifier for the request, used to correlate with the response
@param meth Method type for the request
-
@return A new JSON-RPC request message
-
*)
-
val create_request : ?params:Json.t option -> ?progress_token:ProgressToken.t option -> id:RequestId.t -> meth:Method.t -> unit -> JSONRPCMessage.t
+
@return A new JSON-RPC request message *)
+
val create_response : id:RequestId.t -> result:Json.t -> JSONRPCMessage.t
(** Create a new response message
-
-
Responses are sent in reply to requests and contain successful results.
-
This is a convenience wrapper around JSONRPCMessage.create_response.
-
-
Each response must include the same ID as its corresponding request
-
to allow the client to correlate them, especially when multiple
-
requests are in flight simultaneously.
-
+
+
Responses are sent in reply to requests and contain successful results. This
+
is a convenience wrapper around JSONRPCMessage.create_response.
+
+
Each response must include the same ID as its corresponding request to allow
+
the client to correlate them, especially when multiple requests are in
+
flight simultaneously.
+
@param id ID matching the original request
@param result Result of the successful request as a JSON value
-
@return A new JSON-RPC response message
-
*)
-
val create_response : id:RequestId.t -> result:Json.t -> JSONRPCMessage.t
+
@return A new JSON-RPC response message *)
+
val create_error :
+
id:RequestId.t ->
+
code:int ->
+
message:string ->
+
?data:Json.t option ->
+
unit ->
+
JSONRPCMessage.t
(** Create a new error message
-
-
Errors are sent in reply to requests when processing fails.
-
This is a convenience wrapper around JSONRPCMessage.create_error.
-
-
MCP uses standard JSON-RPC error codes as well as some protocol-specific codes:
+
+
Errors are sent in reply to requests when processing fails. This is a
+
convenience wrapper around JSONRPCMessage.create_error.
+
+
MCP uses standard JSON-RPC error codes as well as some protocol-specific
+
codes:
- -32700: Parse error (invalid JSON)
- -32600: Invalid request (malformed JSON-RPC)
- -32601: Method not found
···
- -32603: Internal error
- -32002: Resource not found (MCP-specific)
- -32001: Authentication required (MCP-specific)
-
+
@param id ID matching the original request
@param code Error code indicating the type of error
@param message Human-readable error message describing the issue
@param data Optional additional error data providing more context
-
@return A new JSON-RPC error message
-
*)
-
val create_error : id:RequestId.t -> code:int -> message:string -> ?data:Json.t option -> unit -> JSONRPCMessage.t
+
@return A new JSON-RPC error message *)
+
val make_text_content : string -> content
(** Create a new text content object
@param text The text content
-
@return A content value with the text
-
*)
-
val make_text_content : string -> content
+
@return A content value with the text *)
+
val make_image_content : string -> string -> content
(** Create a new image content object
@param data Base64-encoded image data
-
@param mime_type MIME type of the image (e.g., "image/png", "image/jpeg")
-
@return A content value with the image
-
*)
-
val make_image_content : string -> string -> content
+
@param mime_type MIME type of the image (e.g., "image/png", "image/jpeg")
+
@return A content value with the image *)
+
val make_audio_content : string -> string -> content
(** Create a new audio content object
@param data Base64-encoded audio data
@param mime_type MIME type of the audio (e.g., "audio/wav", "audio/mp3")
-
@return A content value with the audio
-
*)
-
val make_audio_content : string -> string -> content
+
@return A content value with the audio *)
+
val make_resource_text_content : string -> string -> string option -> content
(** Create a new text resource content object
@param uri URI that uniquely identifies the resource
@param text The text content of the resource
@param mime_type Optional MIME type of the text content
-
@return A content value with the text resource
-
*)
-
val make_resource_text_content : string -> string -> string option -> content
+
@return A content value with the text resource *)
+
val make_resource_blob_content : string -> string -> string option -> content
(** Create a new binary resource content object
@param uri URI that uniquely identifies the resource
@param blob Base64-encoded binary data
@param mime_type Optional MIME type of the binary content
-
@return A content value with the binary resource
-
*)
-
val make_resource_blob_content : string -> string -> string option -> content
+
@return A content value with the binary resource *)
+500 -501
lib/mcp_rpc.ml
···
(* Resources/List *)
module ResourcesList = struct
module Request = struct
-
type t = {
-
cursor: Cursor.t option;
-
}
-
+
type t = { cursor : Cursor.t option }
+
let yojson_of_t { cursor } =
let assoc = [] in
-
let assoc = match cursor with
+
let assoc =
+
match cursor with
| Some c -> ("cursor", Cursor.yojson_of_t c) :: assoc
| None -> assoc
in
`Assoc assoc
-
+
let t_of_yojson = function
| `Assoc fields ->
-
let cursor = List.assoc_opt "cursor" fields |> Option.map Cursor.t_of_yojson in
-
{ cursor }
+
let cursor =
+
List.assoc_opt "cursor" fields |> Option.map Cursor.t_of_yojson
+
in
+
{ cursor }
| j -> Util.json_error "Expected object for ResourcesList.Request.t" j
-
end
-
+
module Resource = struct
type t = {
-
uri: string;
-
name: string;
-
description: string option;
-
mime_type: string option;
-
size: int option;
+
uri : string;
+
name : string;
+
description : string option;
+
mime_type : string option;
+
size : int option;
}
-
+
let yojson_of_t { uri; name; description; mime_type; size } =
-
let assoc = [
-
("uri", `String uri);
-
("name", `String name);
-
] in
-
let assoc = match description with
+
let assoc = [ ("uri", `String uri); ("name", `String name) ] in
+
let assoc =
+
match description with
| Some desc -> ("description", `String desc) :: assoc
| None -> assoc
in
-
let assoc = match mime_type with
+
let assoc =
+
match mime_type with
| Some mime -> ("mimeType", `String mime) :: assoc
| None -> assoc
in
-
let assoc = match size with
-
| Some s -> ("size", `Int s) :: assoc
-
| None -> assoc
+
let assoc =
+
match size with Some s -> ("size", `Int s) :: assoc | None -> assoc
in
`Assoc assoc
-
+
let t_of_yojson = function
| `Assoc fields as json ->
-
let uri = match List.assoc_opt "uri" fields with
-
| Some (`String s) -> s
-
| _ -> Util.json_error "Missing or invalid 'uri' field" json
-
in
-
let name = match List.assoc_opt "name" fields with
-
| Some (`String s) -> s
-
| _ -> Util.json_error "Missing or invalid 'name' field" json
-
in
-
let description = List.assoc_opt "description" fields |> Option.map (function
-
| `String s -> s
-
| j -> Util.json_error "Expected string for description" j
-
) in
-
let mime_type = List.assoc_opt "mimeType" fields |> Option.map (function
-
| `String s -> s
-
| j -> Util.json_error "Expected string for mimeType" j
-
) in
-
let size = List.assoc_opt "size" fields |> Option.map (function
-
| `Int i -> i
-
| j -> Util.json_error "Expected int for size" j
-
) in
-
{ uri; name; description; mime_type; size }
+
let uri =
+
match List.assoc_opt "uri" fields with
+
| Some (`String s) -> s
+
| _ -> Util.json_error "Missing or invalid 'uri' field" json
+
in
+
let name =
+
match List.assoc_opt "name" fields with
+
| Some (`String s) -> s
+
| _ -> Util.json_error "Missing or invalid 'name' field" json
+
in
+
let description =
+
List.assoc_opt "description" fields
+
|> Option.map (function
+
| `String s -> s
+
| j -> Util.json_error "Expected string for description" j)
+
in
+
let mime_type =
+
List.assoc_opt "mimeType" fields
+
|> Option.map (function
+
| `String s -> s
+
| j -> Util.json_error "Expected string for mimeType" j)
+
in
+
let size =
+
List.assoc_opt "size" fields
+
|> Option.map (function
+
| `Int i -> i
+
| j -> Util.json_error "Expected int for size" j)
+
in
+
{ uri; name; description; mime_type; size }
| j -> Util.json_error "Expected object for ResourcesList.Resource.t" j
end
-
+
module Response = struct
-
type t = {
-
resources: Resource.t list;
-
next_cursor: Cursor.t option;
-
}
-
+
type t = { resources : Resource.t list; next_cursor : Cursor.t option }
+
let yojson_of_t { resources; next_cursor } =
-
let assoc = [
-
("resources", `List (List.map Resource.yojson_of_t resources));
-
] in
-
let assoc = match next_cursor with
+
let assoc =
+
[ ("resources", `List (List.map Resource.yojson_of_t resources)) ]
+
in
+
let assoc =
+
match next_cursor with
| Some c -> ("nextCursor", Cursor.yojson_of_t c) :: assoc
| None -> assoc
in
`Assoc assoc
-
+
let t_of_yojson = function
| `Assoc fields as json ->
-
let resources = match List.assoc_opt "resources" fields with
-
| Some (`List items) -> List.map Resource.t_of_yojson items
-
| _ -> Util.json_error "Missing or invalid 'resources' field" json
-
in
-
let next_cursor = List.assoc_opt "nextCursor" fields |> Option.map Cursor.t_of_yojson in
-
{ resources; next_cursor }
+
let resources =
+
match List.assoc_opt "resources" fields with
+
| Some (`List items) -> List.map Resource.t_of_yojson items
+
| _ -> Util.json_error "Missing or invalid 'resources' field" json
+
in
+
let next_cursor =
+
List.assoc_opt "nextCursor" fields |> Option.map Cursor.t_of_yojson
+
in
+
{ resources; next_cursor }
| j -> Util.json_error "Expected object for ResourcesList.Response.t" j
-
end
-
+
(* Request/response creation helpers *)
let create_request ?cursor ?id () =
-
let id = match id with
-
| Some i -> i
-
| None -> `Int (Random.int 10000)
-
in
+
let id = match id with Some i -> i | None -> `Int (Random.int 10000) in
let params = Request.yojson_of_t { cursor } in
-
JSONRPCMessage.create_request ~id ~meth:Method.ResourcesList ~params:(Some params) ()
-
+
JSONRPCMessage.create_request ~id ~meth:Method.ResourcesList
+
~params:(Some params) ()
+
let create_response ~id ~resources ?next_cursor () =
let result = Response.yojson_of_t { resources; next_cursor } in
JSONRPCMessage.create_response ~id ~result
···
(* Resources/Templates/List *)
module ListResourceTemplatesRequest = struct
-
type t = {
-
cursor: Cursor.t option;
-
}
-
+
type t = { cursor : Cursor.t option }
+
let yojson_of_t { cursor } =
let assoc = [] in
-
let assoc = match cursor with
+
let assoc =
+
match cursor with
| Some c -> ("cursor", Cursor.yojson_of_t c) :: assoc
| None -> assoc
in
`Assoc assoc
-
+
let t_of_yojson = function
| `Assoc fields ->
-
let cursor = List.assoc_opt "cursor" fields |> Option.map Cursor.t_of_yojson in
-
{ cursor }
-
| j -> Util.json_error "Expected object for ListResourceTemplatesRequest.t" j
-
+
let cursor =
+
List.assoc_opt "cursor" fields |> Option.map Cursor.t_of_yojson
+
in
+
{ cursor }
+
| j ->
+
Util.json_error "Expected object for ListResourceTemplatesRequest.t" j
end
(* Resources/Templates/List Response *)
module ListResourceTemplatesResult = struct
module ResourceTemplate = struct
type t = {
-
uri_template: string;
-
name: string;
-
description: string option;
-
mime_type: string option;
+
uri_template : string;
+
name : string;
+
description : string option;
+
mime_type : string option;
}
-
+
let yojson_of_t { uri_template; name; description; mime_type } =
-
let assoc = [
-
("uriTemplate", `String uri_template);
-
("name", `String name);
-
] in
-
let assoc = match description with
+
let assoc =
+
[ ("uriTemplate", `String uri_template); ("name", `String name) ]
+
in
+
let assoc =
+
match description with
| Some desc -> ("description", `String desc) :: assoc
| None -> assoc
in
-
let assoc = match mime_type with
+
let assoc =
+
match mime_type with
| Some mime -> ("mimeType", `String mime) :: assoc
| None -> assoc
in
`Assoc assoc
-
+
let t_of_yojson = function
| `Assoc fields as json ->
-
let uri_template = match List.assoc_opt "uriTemplate" fields with
-
| Some (`String s) -> s
-
| _ -> Util.json_error "Missing or invalid 'uriTemplate' field" json
-
in
-
let name = match List.assoc_opt "name" fields with
-
| Some (`String s) -> s
-
| _ -> Util.json_error "Missing or invalid 'name' field" json
-
in
-
let description = List.assoc_opt "description" fields |> Option.map (function
-
| `String s -> s
-
| j -> Util.json_error "Expected string for description" j
-
) in
-
let mime_type = List.assoc_opt "mimeType" fields |> Option.map (function
-
| `String s -> s
-
| j -> Util.json_error "Expected string for mimeType" j
-
) in
-
{ uri_template; name; description; mime_type }
-
| j -> Util.json_error "Expected object for ListResourceTemplatesResult.ResourceTemplate.t" j
+
let uri_template =
+
match List.assoc_opt "uriTemplate" fields with
+
| Some (`String s) -> s
+
| _ -> Util.json_error "Missing or invalid 'uriTemplate' field" json
+
in
+
let name =
+
match List.assoc_opt "name" fields with
+
| Some (`String s) -> s
+
| _ -> Util.json_error "Missing or invalid 'name' field" json
+
in
+
let description =
+
List.assoc_opt "description" fields
+
|> Option.map (function
+
| `String s -> s
+
| j -> Util.json_error "Expected string for description" j)
+
in
+
let mime_type =
+
List.assoc_opt "mimeType" fields
+
|> Option.map (function
+
| `String s -> s
+
| j -> Util.json_error "Expected string for mimeType" j)
+
in
+
{ uri_template; name; description; mime_type }
+
| j ->
+
Util.json_error
+
"Expected object for ListResourceTemplatesResult.ResourceTemplate.t"
+
j
end
-
+
type t = {
-
resource_templates: ResourceTemplate.t list;
-
next_cursor: Cursor.t option;
+
resource_templates : ResourceTemplate.t list;
+
next_cursor : Cursor.t option;
}
-
+
let yojson_of_t { resource_templates; next_cursor } =
-
let assoc = [
-
("resourceTemplates", `List (List.map ResourceTemplate.yojson_of_t resource_templates));
-
] in
-
let assoc = match next_cursor with
+
let assoc =
+
[
+
( "resourceTemplates",
+
`List (List.map ResourceTemplate.yojson_of_t resource_templates) );
+
]
+
in
+
let assoc =
+
match next_cursor with
| Some c -> ("nextCursor", Cursor.yojson_of_t c) :: assoc
| None -> assoc
in
`Assoc assoc
-
+
let t_of_yojson = function
| `Assoc fields as json ->
-
let resource_templates = match List.assoc_opt "resourceTemplates" fields with
-
| Some (`List items) -> List.map ResourceTemplate.t_of_yojson items
-
| _ -> Util.json_error "Missing or invalid 'resourceTemplates' field" json
-
in
-
let next_cursor = List.assoc_opt "nextCursor" fields |> Option.map Cursor.t_of_yojson in
-
{ resource_templates; next_cursor }
+
let resource_templates =
+
match List.assoc_opt "resourceTemplates" fields with
+
| Some (`List items) -> List.map ResourceTemplate.t_of_yojson items
+
| _ ->
+
Util.json_error "Missing or invalid 'resourceTemplates' field"
+
json
+
in
+
let next_cursor =
+
List.assoc_opt "nextCursor" fields |> Option.map Cursor.t_of_yojson
+
in
+
{ resource_templates; next_cursor }
| j -> Util.json_error "Expected object for ListResourceTemplatesResult.t" j
-
+
(* Request/response creation helpers *)
let create_request ?cursor ?id () =
-
let id = match id with
-
| Some i -> i
-
| None -> `Int (Random.int 10000)
-
in
+
let id = match id with Some i -> i | None -> `Int (Random.int 10000) in
let params = ListResourceTemplatesRequest.yojson_of_t { cursor } in
-
JSONRPCMessage.create_request ~id ~meth:Method.ResourceTemplatesList ~params:(Some params) ()
-
+
JSONRPCMessage.create_request ~id ~meth:Method.ResourceTemplatesList
+
~params:(Some params) ()
+
let create_response ~id ~resource_templates ?next_cursor () =
let result = yojson_of_t { resource_templates; next_cursor } in
JSONRPCMessage.create_response ~id ~result
···
(* Resources/Read *)
module ResourcesRead = struct
module Request = struct
-
type t = {
-
uri: string;
-
}
-
-
let yojson_of_t { uri } =
-
`Assoc [
-
("uri", `String uri);
-
]
-
+
type t = { uri : string }
+
+
let yojson_of_t { uri } = `Assoc [ ("uri", `String uri) ]
+
let t_of_yojson = function
| `Assoc fields as json ->
-
let uri = match List.assoc_opt "uri" fields with
-
| Some (`String s) -> s
-
| _ -> Util.json_error "Missing or invalid 'uri' field" json
-
in
-
{ uri }
+
let uri =
+
match List.assoc_opt "uri" fields with
+
| Some (`String s) -> s
+
| _ -> Util.json_error "Missing or invalid 'uri' field" json
+
in
+
{ uri }
| j -> Util.json_error "Expected object for ResourcesRead.Request.t" j
-
end
-
+
module ResourceContent = struct
-
type t =
+
type t =
| TextResource of TextResourceContents.t
| BlobResource of BlobResourceContents.t
-
+
let yojson_of_t = function
| TextResource tr -> TextResourceContents.yojson_of_t tr
| BlobResource br -> BlobResourceContents.yojson_of_t br
-
+
let t_of_yojson json =
match json with
| `Assoc fields ->
-
if List.mem_assoc "text" fields then
-
TextResource (TextResourceContents.t_of_yojson json)
-
else if List.mem_assoc "blob" fields then
-
BlobResource (BlobResourceContents.t_of_yojson json)
-
else
-
Util.json_error "Invalid resource content" json
-
| j -> Util.json_error "Expected object for ResourcesRead.ResourceContent.t" j
-
+
if List.mem_assoc "text" fields then
+
TextResource (TextResourceContents.t_of_yojson json)
+
else if List.mem_assoc "blob" fields then
+
BlobResource (BlobResourceContents.t_of_yojson json)
+
else Util.json_error "Invalid resource content" json
+
| j ->
+
Util.json_error "Expected object for ResourcesRead.ResourceContent.t"
+
j
end
-
+
module Response = struct
-
type t = {
-
contents: ResourceContent.t list;
-
}
-
+
type t = { contents : ResourceContent.t list }
+
let yojson_of_t { contents } =
-
`Assoc [
-
("contents", `List (List.map ResourceContent.yojson_of_t contents));
-
]
-
+
`Assoc
+
[ ("contents", `List (List.map ResourceContent.yojson_of_t contents)) ]
+
let t_of_yojson = function
| `Assoc fields as json ->
-
let contents = match List.assoc_opt "contents" fields with
-
| Some (`List items) -> List.map ResourceContent.t_of_yojson items
-
| _ -> Util.json_error "Missing or invalid 'contents' field" json
-
in
-
{ contents }
+
let contents =
+
match List.assoc_opt "contents" fields with
+
| Some (`List items) -> List.map ResourceContent.t_of_yojson items
+
| _ -> Util.json_error "Missing or invalid 'contents' field" json
+
in
+
{ contents }
| j -> Util.json_error "Expected object for ResourcesRead.Response.t" j
-
end
-
+
(* Request/response creation helpers *)
let create_request ~uri ?id () =
-
let id = match id with
-
| Some i -> i
-
| None -> `Int (Random.int 10000)
-
in
+
let id = match id with Some i -> i | None -> `Int (Random.int 10000) in
let params = Request.yojson_of_t { uri } in
-
JSONRPCMessage.create_request ~id ~meth:Method.ResourcesRead ~params:(Some params) ()
-
+
JSONRPCMessage.create_request ~id ~meth:Method.ResourcesRead
+
~params:(Some params) ()
+
let create_response ~id ~contents () =
let result = Response.yojson_of_t { contents } in
JSONRPCMessage.create_response ~id ~result
···
(* Tools/List *)
module ToolsList = struct
module Request = struct
-
type t = {
-
cursor: Cursor.t option;
-
}
-
+
type t = { cursor : Cursor.t option }
+
let yojson_of_t { cursor } =
let assoc = [] in
-
let assoc = match cursor with
+
let assoc =
+
match cursor with
| Some c -> ("cursor", Cursor.yojson_of_t c) :: assoc
| None -> assoc
in
`Assoc assoc
-
+
let t_of_yojson = function
| `Assoc fields ->
-
let cursor = List.assoc_opt "cursor" fields |> Option.map Cursor.t_of_yojson in
-
{ cursor }
+
let cursor =
+
List.assoc_opt "cursor" fields |> Option.map Cursor.t_of_yojson
+
in
+
{ cursor }
| j -> Util.json_error "Expected object for ToolsList.Request.t" j
-
end
-
+
module Tool = struct
type t = {
-
name: string;
-
description: string option;
-
input_schema: Json.t;
-
annotations: Json.t option;
+
name : string;
+
description : string option;
+
input_schema : Json.t;
+
annotations : Json.t option;
}
-
+
let yojson_of_t { name; description; input_schema; annotations } =
-
let assoc = [
-
("name", `String name);
-
("inputSchema", input_schema);
-
] in
-
let assoc = match description with
+
let assoc = [ ("name", `String name); ("inputSchema", input_schema) ] in
+
let assoc =
+
match description with
| Some desc -> ("description", `String desc) :: assoc
| None -> assoc
in
-
let assoc = match annotations with
+
let assoc =
+
match annotations with
| Some anno -> ("annotations", anno) :: assoc
| None -> assoc
in
`Assoc assoc
-
+
let t_of_yojson = function
| `Assoc fields as json ->
-
let name = match List.assoc_opt "name" fields with
-
| Some (`String s) -> s
-
| _ -> Util.json_error "Missing or invalid 'name' field" json
-
in
-
let description = List.assoc_opt "description" fields |> Option.map (function
-
| `String s -> s
-
| j -> Util.json_error "Expected string for description" j
-
) in
-
let input_schema = match List.assoc_opt "inputSchema" fields with
-
| Some schema -> schema
-
| None -> Util.json_error "Missing 'inputSchema' field" json
-
in
-
let annotations = List.assoc_opt "annotations" fields in
-
{ name; description; input_schema; annotations }
+
let name =
+
match List.assoc_opt "name" fields with
+
| Some (`String s) -> s
+
| _ -> Util.json_error "Missing or invalid 'name' field" json
+
in
+
let description =
+
List.assoc_opt "description" fields
+
|> Option.map (function
+
| `String s -> s
+
| j -> Util.json_error "Expected string for description" j)
+
in
+
let input_schema =
+
match List.assoc_opt "inputSchema" fields with
+
| Some schema -> schema
+
| None -> Util.json_error "Missing 'inputSchema' field" json
+
in
+
let annotations = List.assoc_opt "annotations" fields in
+
{ name; description; input_schema; annotations }
| j -> Util.json_error "Expected object for ToolsList.Tool.t" j
-
end
-
+
module Response = struct
-
type t = {
-
tools: Tool.t list;
-
next_cursor: Cursor.t option;
-
}
-
+
type t = { tools : Tool.t list; next_cursor : Cursor.t option }
+
let yojson_of_t { tools; next_cursor } =
-
let assoc = [
-
("tools", `List (List.map Tool.yojson_of_t tools));
-
] in
-
let assoc = match next_cursor with
+
let assoc = [ ("tools", `List (List.map Tool.yojson_of_t tools)) ] in
+
let assoc =
+
match next_cursor with
| Some c -> ("nextCursor", Cursor.yojson_of_t c) :: assoc
| None -> assoc
in
`Assoc assoc
-
+
let t_of_yojson = function
| `Assoc fields as json ->
-
let tools = match List.assoc_opt "tools" fields with
-
| Some (`List items) -> List.map Tool.t_of_yojson items
-
| _ -> Util.json_error "Missing or invalid 'tools' field" json
-
in
-
let next_cursor = List.assoc_opt "nextCursor" fields |> Option.map Cursor.t_of_yojson in
-
{ tools; next_cursor }
+
let tools =
+
match List.assoc_opt "tools" fields with
+
| Some (`List items) -> List.map Tool.t_of_yojson items
+
| _ -> Util.json_error "Missing or invalid 'tools' field" json
+
in
+
let next_cursor =
+
List.assoc_opt "nextCursor" fields |> Option.map Cursor.t_of_yojson
+
in
+
{ tools; next_cursor }
| j -> Util.json_error "Expected object for ToolsList.Response.t" j
-
end
-
+
(* Request/response creation helpers *)
let create_request ?cursor ?id () =
-
let id = match id with
-
| Some i -> i
-
| None -> `Int (Random.int 10000)
-
in
+
let id = match id with Some i -> i | None -> `Int (Random.int 10000) in
let params = Request.yojson_of_t { cursor } in
-
JSONRPCMessage.create_request ~id ~meth:Method.ToolsList ~params:(Some params) ()
-
+
JSONRPCMessage.create_request ~id ~meth:Method.ToolsList
+
~params:(Some params) ()
+
let create_response ~id ~tools ?next_cursor () =
let result = Response.yojson_of_t { tools; next_cursor } in
JSONRPCMessage.create_response ~id ~result
···
(* Tools/Call *)
module ToolsCall = struct
module Request = struct
-
type t = {
-
name: string;
-
arguments: Json.t;
-
}
-
+
type t = { name : string; arguments : Json.t }
+
let yojson_of_t { name; arguments } =
-
`Assoc [
-
("name", `String name);
-
("arguments", arguments);
-
]
-
+
`Assoc [ ("name", `String name); ("arguments", arguments) ]
+
let t_of_yojson = function
| `Assoc fields as json ->
-
let name = match List.assoc_opt "name" fields with
-
| Some (`String s) -> s
-
| _ -> Util.json_error "Missing or invalid 'name' field" json
-
in
-
let arguments = match List.assoc_opt "arguments" fields with
-
| Some json -> json
-
| None -> Util.json_error "Missing 'arguments' field" json
-
in
-
{ name; arguments }
+
let name =
+
match List.assoc_opt "name" fields with
+
| Some (`String s) -> s
+
| _ -> Util.json_error "Missing or invalid 'name' field" json
+
in
+
let arguments =
+
match List.assoc_opt "arguments" fields with
+
| Some json -> json
+
| None -> Util.json_error "Missing 'arguments' field" json
+
in
+
{ name; arguments }
| j -> Util.json_error "Expected object for ToolsCall.Request.t" j
-
end
-
+
module ToolContent = struct
-
type t =
+
type t =
| Text of TextContent.t
| Image of ImageContent.t
| Audio of AudioContent.t
| Resource of EmbeddedResource.t
-
+
let yojson_of_t = function
| Text t -> TextContent.yojson_of_t t
| Image i -> ImageContent.yojson_of_t i
| Audio a -> AudioContent.yojson_of_t a
| Resource r -> EmbeddedResource.yojson_of_t r
-
+
let t_of_yojson json =
match json with
-
| `Assoc fields ->
-
(match List.assoc_opt "type" fields with
-
| Some (`String "text") -> Text (TextContent.t_of_yojson json)
-
| Some (`String "image") -> Image (ImageContent.t_of_yojson json)
-
| Some (`String "audio") -> Audio (AudioContent.t_of_yojson json)
-
| Some (`String "resource") -> Resource (EmbeddedResource.t_of_yojson json)
-
| _ -> Util.json_error "Invalid or missing content type" json)
+
| `Assoc fields -> (
+
match List.assoc_opt "type" fields with
+
| Some (`String "text") -> Text (TextContent.t_of_yojson json)
+
| Some (`String "image") -> Image (ImageContent.t_of_yojson json)
+
| Some (`String "audio") -> Audio (AudioContent.t_of_yojson json)
+
| Some (`String "resource") ->
+
Resource (EmbeddedResource.t_of_yojson json)
+
| _ -> Util.json_error "Invalid or missing content type" json)
| j -> Util.json_error "Expected object for ToolsCall.ToolContent.t" j
-
end
-
+
module Response = struct
-
type t = {
-
content: ToolContent.t list;
-
is_error: bool;
-
}
-
+
type t = { content : ToolContent.t list; is_error : bool }
+
let yojson_of_t { content; is_error } =
-
`Assoc [
-
("content", `List (List.map ToolContent.yojson_of_t content));
-
("isError", `Bool is_error);
-
]
-
+
`Assoc
+
[
+
("content", `List (List.map ToolContent.yojson_of_t content));
+
("isError", `Bool is_error);
+
]
+
let t_of_yojson = function
| `Assoc fields as json ->
-
let content = match List.assoc_opt "content" fields with
-
| Some (`List items) -> List.map ToolContent.t_of_yojson items
-
| _ -> Util.json_error "Missing or invalid 'content' field" json
-
in
-
let is_error = match List.assoc_opt "isError" fields with
-
| Some (`Bool b) -> b
-
| _ -> false
-
in
-
{ content; is_error }
+
let content =
+
match List.assoc_opt "content" fields with
+
| Some (`List items) -> List.map ToolContent.t_of_yojson items
+
| _ -> Util.json_error "Missing or invalid 'content' field" json
+
in
+
let is_error =
+
match List.assoc_opt "isError" fields with
+
| Some (`Bool b) -> b
+
| _ -> false
+
in
+
{ content; is_error }
| j -> Util.json_error "Expected object for ToolsCall.Response.t" j
-
end
-
+
(* Request/response creation helpers *)
let create_request ~name ~arguments ?id () =
-
let id = match id with
-
| Some i -> i
-
| None -> `Int (Random.int 10000)
-
in
+
let id = match id with Some i -> i | None -> `Int (Random.int 10000) in
let params = Request.yojson_of_t { name; arguments } in
-
JSONRPCMessage.create_request ~id ~meth:Method.ToolsCall ~params:(Some params) ()
-
+
JSONRPCMessage.create_request ~id ~meth:Method.ToolsCall
+
~params:(Some params) ()
+
let create_response ~id ~content ~is_error () =
let result = Response.yojson_of_t { content; is_error } in
JSONRPCMessage.create_response ~id ~result
···
(* Prompts/List *)
module PromptsList = struct
module PromptArgument = struct
-
type t = {
-
name: string;
-
description: string option;
-
required: bool;
-
}
-
+
type t = { name : string; description : string option; required : bool }
+
let yojson_of_t { name; description; required } =
-
let assoc = [
-
("name", `String name);
-
] in
-
let assoc = match description with
+
let assoc = [ ("name", `String name) ] in
+
let assoc =
+
match description with
| Some desc -> ("description", `String desc) :: assoc
| None -> assoc
in
-
let assoc = if required then
-
("required", `Bool true) :: assoc
-
else
-
assoc
+
let assoc =
+
if required then ("required", `Bool true) :: assoc else assoc
in
`Assoc assoc
-
+
let t_of_yojson = function
| `Assoc fields as json ->
-
let name = match List.assoc_opt "name" fields with
-
| Some (`String s) -> s
-
| _ -> Util.json_error "Missing or invalid 'name' field" json
-
in
-
let description = List.assoc_opt "description" fields |> Option.map (function
-
| `String s -> s
-
| j -> Util.json_error "Expected string for description" j
-
) in
-
let required = match List.assoc_opt "required" fields with
-
| Some (`Bool b) -> b
-
| _ -> false
-
in
-
{ name; description; required }
-
| j -> Util.json_error "Expected object for PromptsList.PromptArgument.t" j
-
+
let name =
+
match List.assoc_opt "name" fields with
+
| Some (`String s) -> s
+
| _ -> Util.json_error "Missing or invalid 'name' field" json
+
in
+
let description =
+
List.assoc_opt "description" fields
+
|> Option.map (function
+
| `String s -> s
+
| j -> Util.json_error "Expected string for description" j)
+
in
+
let required =
+
match List.assoc_opt "required" fields with
+
| Some (`Bool b) -> b
+
| _ -> false
+
in
+
{ name; description; required }
+
| j ->
+
Util.json_error "Expected object for PromptsList.PromptArgument.t" j
end
-
+
module Prompt = struct
type t = {
-
name: string;
-
description: string option;
-
arguments: PromptArgument.t list;
+
name : string;
+
description : string option;
+
arguments : PromptArgument.t list;
}
-
+
let yojson_of_t { name; description; arguments } =
-
let assoc = [
-
("name", `String name);
-
] in
-
let assoc = match description with
+
let assoc = [ ("name", `String name) ] in
+
let assoc =
+
match description with
| Some desc -> ("description", `String desc) :: assoc
| None -> assoc
in
-
let assoc = if arguments <> [] then
-
("arguments", `List (List.map PromptArgument.yojson_of_t arguments)) :: assoc
-
else
-
assoc
+
let assoc =
+
if arguments <> [] then
+
("arguments", `List (List.map PromptArgument.yojson_of_t arguments))
+
:: assoc
+
else assoc
in
`Assoc assoc
-
+
let t_of_yojson = function
| `Assoc fields as json ->
-
let name = match List.assoc_opt "name" fields with
-
| Some (`String s) -> s
-
| _ -> Util.json_error "Missing or invalid 'name' field" json
-
in
-
let description = List.assoc_opt "description" fields |> Option.map (function
-
| `String s -> s
-
| j -> Util.json_error "Expected string for description" j
-
) in
-
let arguments = match List.assoc_opt "arguments" fields with
-
| Some (`List items) -> List.map PromptArgument.t_of_yojson items
-
| _ -> []
-
in
-
{ name; description; arguments }
+
let name =
+
match List.assoc_opt "name" fields with
+
| Some (`String s) -> s
+
| _ -> Util.json_error "Missing or invalid 'name' field" json
+
in
+
let description =
+
List.assoc_opt "description" fields
+
|> Option.map (function
+
| `String s -> s
+
| j -> Util.json_error "Expected string for description" j)
+
in
+
let arguments =
+
match List.assoc_opt "arguments" fields with
+
| Some (`List items) -> List.map PromptArgument.t_of_yojson items
+
| _ -> []
+
in
+
{ name; description; arguments }
| j -> Util.json_error "Expected object for PromptsList.Prompt.t" j
-
end
-
+
module Request = struct
-
type t = {
-
cursor: Cursor.t option;
-
}
-
+
type t = { cursor : Cursor.t option }
+
let yojson_of_t { cursor } =
let assoc = [] in
-
let assoc = match cursor with
+
let assoc =
+
match cursor with
| Some c -> ("cursor", Cursor.yojson_of_t c) :: assoc
| None -> assoc
in
`Assoc assoc
-
+
let t_of_yojson = function
| `Assoc fields ->
-
let cursor = List.assoc_opt "cursor" fields |> Option.map Cursor.t_of_yojson in
-
{ cursor }
+
let cursor =
+
List.assoc_opt "cursor" fields |> Option.map Cursor.t_of_yojson
+
in
+
{ cursor }
| j -> Util.json_error "Expected object for PromptsList.Request.t" j
-
end
-
+
module Response = struct
-
type t = {
-
prompts: Prompt.t list;
-
next_cursor: Cursor.t option;
-
}
-
+
type t = { prompts : Prompt.t list; next_cursor : Cursor.t option }
+
let yojson_of_t { prompts; next_cursor } =
-
let assoc = [
-
("prompts", `List (List.map Prompt.yojson_of_t prompts));
-
] in
-
let assoc = match next_cursor with
+
let assoc =
+
[ ("prompts", `List (List.map Prompt.yojson_of_t prompts)) ]
+
in
+
let assoc =
+
match next_cursor with
| Some c -> ("nextCursor", Cursor.yojson_of_t c) :: assoc
| None -> assoc
in
`Assoc assoc
-
+
let t_of_yojson = function
| `Assoc fields as json ->
-
let prompts = match List.assoc_opt "prompts" fields with
-
| Some (`List items) -> List.map Prompt.t_of_yojson items
-
| _ -> Util.json_error "Missing or invalid 'prompts' field" json
-
in
-
let next_cursor = List.assoc_opt "nextCursor" fields |> Option.map Cursor.t_of_yojson in
-
{ prompts; next_cursor }
+
let prompts =
+
match List.assoc_opt "prompts" fields with
+
| Some (`List items) -> List.map Prompt.t_of_yojson items
+
| _ -> Util.json_error "Missing or invalid 'prompts' field" json
+
in
+
let next_cursor =
+
List.assoc_opt "nextCursor" fields |> Option.map Cursor.t_of_yojson
+
in
+
{ prompts; next_cursor }
| j -> Util.json_error "Expected object for PromptsList.Response.t" j
-
end
-
+
(* Request/response creation helpers *)
let create_request ?cursor ?id () =
-
let id = match id with
-
| Some i -> i
-
| None -> `Int (Random.int 10000)
-
in
+
let id = match id with Some i -> i | None -> `Int (Random.int 10000) in
let params = Request.yojson_of_t { cursor } in
-
JSONRPCMessage.create_request ~id ~meth:Method.PromptsList ~params:(Some params) ()
-
+
JSONRPCMessage.create_request ~id ~meth:Method.PromptsList
+
~params:(Some params) ()
+
let create_response ~id ~prompts ?next_cursor () =
let result = Response.yojson_of_t { prompts; next_cursor } in
JSONRPCMessage.create_response ~id ~result
···
(* Prompts/Get *)
module PromptsGet = struct
module Request = struct
-
type t = {
-
name: string;
-
arguments: (string * string) list;
-
}
-
+
type t = { name : string; arguments : (string * string) list }
+
let yojson_of_t { name; arguments } =
-
let args_json = `Assoc (List.map (fun (k, v) -> (k, `String v)) arguments) in
-
`Assoc [
-
("name", `String name);
-
("arguments", args_json);
-
]
-
+
let args_json =
+
`Assoc (List.map (fun (k, v) -> (k, `String v)) arguments)
+
in
+
`Assoc [ ("name", `String name); ("arguments", args_json) ]
+
let t_of_yojson = function
| `Assoc fields as json ->
-
let name = match List.assoc_opt "name" fields with
-
| Some (`String s) -> s
-
| _ -> Util.json_error "Missing or invalid 'name' field" json
-
in
-
let arguments = match List.assoc_opt "arguments" fields with
-
| Some (`Assoc args) ->
-
List.map (fun (k, v) ->
-
match v with
-
| `String s -> (k, s)
-
| _ -> Util.json_error "Expected string value for argument" v
-
) args
-
| _ -> []
-
in
-
{ name; arguments }
+
let name =
+
match List.assoc_opt "name" fields with
+
| Some (`String s) -> s
+
| _ -> Util.json_error "Missing or invalid 'name' field" json
+
in
+
let arguments =
+
match List.assoc_opt "arguments" fields with
+
| Some (`Assoc args) ->
+
List.map
+
(fun (k, v) ->
+
match v with
+
| `String s -> (k, s)
+
| _ ->
+
Util.json_error "Expected string value for argument" v)
+
args
+
| _ -> []
+
in
+
{ name; arguments }
| j -> Util.json_error "Expected object for PromptsGet.Request.t" j
-
end
-
+
module Response = struct
-
type t = {
-
description: string option;
-
messages: PromptMessage.t list;
-
}
-
+
type t = { description : string option; messages : PromptMessage.t list }
+
let yojson_of_t { description; messages } =
-
let assoc = [
-
("messages", `List (List.map PromptMessage.yojson_of_t messages));
-
] in
-
let assoc = match description with
+
let assoc =
+
[ ("messages", `List (List.map PromptMessage.yojson_of_t messages)) ]
+
in
+
let assoc =
+
match description with
| Some desc -> ("description", `String desc) :: assoc
| None -> assoc
in
`Assoc assoc
-
+
let t_of_yojson = function
| `Assoc fields as json ->
-
let messages = match List.assoc_opt "messages" fields with
-
| Some (`List items) -> List.map PromptMessage.t_of_yojson items
-
| _ -> Util.json_error "Missing or invalid 'messages' field" json
-
in
-
let description = List.assoc_opt "description" fields |> Option.map (function
-
| `String s -> s
-
| j -> Util.json_error "Expected string for description" j
-
) in
-
{ description; messages }
+
let messages =
+
match List.assoc_opt "messages" fields with
+
| Some (`List items) -> List.map PromptMessage.t_of_yojson items
+
| _ -> Util.json_error "Missing or invalid 'messages' field" json
+
in
+
let description =
+
List.assoc_opt "description" fields
+
|> Option.map (function
+
| `String s -> s
+
| j -> Util.json_error "Expected string for description" j)
+
in
+
{ description; messages }
| j -> Util.json_error "Expected object for PromptsGet.Response.t" j
-
end
-
+
(* Request/response creation helpers *)
let create_request ~name ~arguments ?id () =
-
let id = match id with
-
| Some i -> i
-
| None -> `Int (Random.int 10000)
-
in
+
let id = match id with Some i -> i | None -> `Int (Random.int 10000) in
let params = Request.yojson_of_t { name; arguments } in
-
JSONRPCMessage.create_request ~id ~meth:Method.PromptsGet ~params:(Some params) ()
-
+
JSONRPCMessage.create_request ~id ~meth:Method.PromptsGet
+
~params:(Some params) ()
+
let create_response ~id ?description ~messages () =
let result = Response.yojson_of_t { description; messages } in
JSONRPCMessage.create_response ~id ~result
···
(* List Changed Notifications *)
module ListChanged = struct
(* No parameters for these notifications *)
-
+
let create_resources_notification () =
JSONRPCMessage.create_notification ~meth:Method.ResourcesListChanged ()
-
+
let create_tools_notification () =
JSONRPCMessage.create_notification ~meth:Method.ToolsListChanged ()
-
+
let create_prompts_notification () =
JSONRPCMessage.create_notification ~meth:Method.PromptsListChanged ()
end
···
(* Resource Updated Notification *)
module ResourceUpdated = struct
module Notification = struct
-
type t = {
-
uri: string;
-
}
-
-
let yojson_of_t { uri } =
-
`Assoc [
-
("uri", `String uri);
-
]
-
+
type t = { uri : string }
+
+
let yojson_of_t { uri } = `Assoc [ ("uri", `String uri) ]
+
let t_of_yojson = function
| `Assoc fields as json ->
-
let uri = match List.assoc_opt "uri" fields with
-
| Some (`String s) -> s
-
| _ -> Util.json_error "Missing or invalid 'uri' field" json
-
in
-
{ uri }
-
| j -> Util.json_error "Expected object for ResourceUpdated.Notification.t" j
-
+
let uri =
+
match List.assoc_opt "uri" fields with
+
| Some (`String s) -> s
+
| _ -> Util.json_error "Missing or invalid 'uri' field" json
+
in
+
{ uri }
+
| j ->
+
Util.json_error "Expected object for ResourceUpdated.Notification.t" j
end
-
+
let create_notification ~uri () =
let params = Notification.yojson_of_t { uri } in
-
JSONRPCMessage.create_notification ~meth:Method.ResourcesUpdated ~params:(Some params) ()
+
JSONRPCMessage.create_notification ~meth:Method.ResourcesUpdated
+
~params:(Some params) ()
end
(* Progress Notification *)
module Progress = struct
module Notification = struct
type t = {
-
progress: float;
-
total: float;
-
progress_token: ProgressToken.t;
+
progress : float;
+
total : float;
+
progress_token : ProgressToken.t;
}
-
+
let yojson_of_t { progress; total; progress_token } =
-
`Assoc [
-
("progress", `Float progress);
-
("total", `Float total);
-
("progressToken", ProgressToken.yojson_of_t progress_token);
-
]
-
+
`Assoc
+
[
+
("progress", `Float progress);
+
("total", `Float total);
+
("progressToken", ProgressToken.yojson_of_t progress_token);
+
]
+
let t_of_yojson = function
| `Assoc fields as json ->
-
let progress = match List.assoc_opt "progress" fields with
-
| Some (`Float f) -> f
-
| _ -> Util.json_error "Missing or invalid 'progress' field" json
-
in
-
let total = match List.assoc_opt "total" fields with
-
| Some (`Float f) -> f
-
| _ -> Util.json_error "Missing or invalid 'total' field" json
-
in
-
let progress_token = match List.assoc_opt "progressToken" fields with
-
| Some token -> ProgressToken.t_of_yojson token
-
| _ -> Util.json_error "Missing or invalid 'progressToken' field" json
-
in
-
{ progress; total; progress_token }
+
let progress =
+
match List.assoc_opt "progress" fields with
+
| Some (`Float f) -> f
+
| _ -> Util.json_error "Missing or invalid 'progress' field" json
+
in
+
let total =
+
match List.assoc_opt "total" fields with
+
| Some (`Float f) -> f
+
| _ -> Util.json_error "Missing or invalid 'total' field" json
+
in
+
let progress_token =
+
match List.assoc_opt "progressToken" fields with
+
| Some token -> ProgressToken.t_of_yojson token
+
| _ ->
+
Util.json_error "Missing or invalid 'progressToken' field" json
+
in
+
{ progress; total; progress_token }
| j -> Util.json_error "Expected object for Progress.Notification.t" j
-
end
-
+
let create_notification ~progress ~total ~progress_token () =
let params = Notification.yojson_of_t { progress; total; progress_token } in
-
JSONRPCMessage.create_notification ~meth:Method.Progress ~params:(Some params) ()
+
JSONRPCMessage.create_notification ~meth:Method.Progress
+
~params:(Some params) ()
end
(* Type aliases for backward compatibility *)
type request = ResourcesList.Request.t
-
type response = ResourcesList.Response.t
+
type response = ResourcesList.Response.t
type resource = ResourcesList.Resource.t
type resource_content = ResourcesRead.ResourceContent.t
type tool = ToolsList.Tool.t
type tool_content = ToolsCall.ToolContent.t
type prompt = PromptsList.Prompt.t
-
type prompt_argument = PromptsList.PromptArgument.t
+
type prompt_argument = PromptsList.PromptArgument.t
+184 -116
lib/mcp_rpc.mli
···
-
(** Mcp_message - High-level RPC message definitions for Model Context Protocol *)
+
(** Mcp_message - High-level RPC message definitions for Model Context Protocol
+
*)
open Mcp
open Jsonrpc
···
module ResourcesList : sig
(** Request parameters *)
module Request : sig
-
type t = {
-
cursor: Cursor.t option; (** Optional pagination cursor *)
-
}
+
type t = { cursor : Cursor.t option (** Optional pagination cursor *) }
+
include Json.Jsonable.S with type t := t
end
-
+
(** Resource definition *)
module Resource : sig
type t = {
-
uri: string; (** Unique identifier for the resource *)
-
name: string; (** Human-readable name *)
-
description: string option; (** Optional description *)
-
mime_type: string option; (** Optional MIME type *)
-
size: int option; (** Optional size in bytes *)
+
uri : string; (** Unique identifier for the resource *)
+
name : string; (** Human-readable name *)
+
description : string option; (** Optional description *)
+
mime_type : string option; (** Optional MIME type *)
+
size : int option; (** Optional size in bytes *)
}
+
include Json.Jsonable.S with type t := t
end
-
+
(** Response result *)
module Response : sig
type t = {
-
resources: Resource.t list; (** List of available resources *)
-
next_cursor: Cursor.t option; (** Optional cursor for the next page *)
+
resources : Resource.t list; (** List of available resources *)
+
next_cursor : Cursor.t option; (** Optional cursor for the next page *)
}
+
include Json.Jsonable.S with type t := t
end
-
+
+
val create_request :
+
?cursor:Cursor.t -> ?id:RequestId.t -> unit -> JSONRPCMessage.t
(** Create a resources/list request *)
-
val create_request : ?cursor:Cursor.t -> ?id:RequestId.t -> unit -> JSONRPCMessage.t
-
+
+
val create_response :
+
id:RequestId.t ->
+
resources:Resource.t list ->
+
?next_cursor:Cursor.t ->
+
unit ->
+
JSONRPCMessage.t
(** Create a resources/list response *)
-
val create_response : id:RequestId.t -> resources:Resource.t list -> ?next_cursor:Cursor.t -> unit -> JSONRPCMessage.t
end
(** Resources/Templates/List - Request to list available resource templates *)
module ListResourceTemplatesRequest : sig
-
type t = {
-
cursor: Cursor.t option; (** Optional pagination cursor *)
-
}
+
type t = { cursor : Cursor.t option (** Optional pagination cursor *) }
+
include Json.Jsonable.S with type t := t
end
···
(** Resource Template definition *)
module ResourceTemplate : sig
type t = {
-
uri_template: string; (** URI template for the resource *)
-
name: string; (** Human-readable name *)
-
description: string option; (** Optional description *)
-
mime_type: string option; (** Optional MIME type *)
+
uri_template : string; (** URI template for the resource *)
+
name : string; (** Human-readable name *)
+
description : string option; (** Optional description *)
+
mime_type : string option; (** Optional MIME type *)
}
+
include Json.Jsonable.S with type t := t
end
-
+
type t = {
-
resource_templates: ResourceTemplate.t list; (** List of available resource templates *)
-
next_cursor: Cursor.t option; (** Optional cursor for the next page *)
+
resource_templates : ResourceTemplate.t list;
+
(** List of available resource templates *)
+
next_cursor : Cursor.t option; (** Optional cursor for the next page *)
}
+
include Json.Jsonable.S with type t := t
-
+
+
val create_request :
+
?cursor:Cursor.t -> ?id:RequestId.t -> unit -> JSONRPCMessage.t
(** Create a resources/templates/list request *)
-
val create_request : ?cursor:Cursor.t -> ?id:RequestId.t -> unit -> JSONRPCMessage.t
-
+
+
val create_response :
+
id:RequestId.t ->
+
resource_templates:ResourceTemplate.t list ->
+
?next_cursor:Cursor.t ->
+
unit ->
+
JSONRPCMessage.t
(** Create a resources/templates/list response *)
-
val create_response : id:RequestId.t -> resource_templates:ResourceTemplate.t list -> ?next_cursor:Cursor.t -> unit -> JSONRPCMessage.t
end
(** Resources/Read - Request to read resource contents *)
module ResourcesRead : sig
(** Request parameters *)
module Request : sig
-
type t = {
-
uri: string; (** URI of the resource to read *)
-
}
+
type t = { uri : string (** URI of the resource to read *) }
+
include Json.Jsonable.S with type t := t
end
-
+
(** Resource content *)
module ResourceContent : sig
-
type t =
-
| TextResource of TextResourceContents.t (** Text content *)
-
| BlobResource of BlobResourceContents.t (** Binary content *)
+
type t =
+
| TextResource of TextResourceContents.t (** Text content *)
+
| BlobResource of BlobResourceContents.t (** Binary content *)
+
include Json.Jsonable.S with type t := t
end
-
+
(** Response result *)
module Response : sig
type t = {
-
contents: ResourceContent.t list; (** List of resource contents *)
+
contents : ResourceContent.t list; (** List of resource contents *)
}
+
include Json.Jsonable.S with type t := t
end
-
+
+
val create_request : uri:string -> ?id:RequestId.t -> unit -> JSONRPCMessage.t
(** Create a resources/read request *)
-
val create_request : uri:string -> ?id:RequestId.t -> unit -> JSONRPCMessage.t
-
+
+
val create_response :
+
id:RequestId.t ->
+
contents:ResourceContent.t list ->
+
unit ->
+
JSONRPCMessage.t
(** Create a resources/read response *)
-
val create_response : id:RequestId.t -> contents:ResourceContent.t list -> unit -> JSONRPCMessage.t
end
(** Tools/List - Request to list available tools *)
module ToolsList : sig
(** Request parameters *)
module Request : sig
-
type t = {
-
cursor: Cursor.t option; (** Optional pagination cursor *)
-
}
+
type t = { cursor : Cursor.t option (** Optional pagination cursor *) }
+
include Json.Jsonable.S with type t := t
end
-
+
(** Tool definition *)
module Tool : sig
type t = {
-
name: string; (** Unique identifier for the tool *)
-
description: string option; (** Human-readable description *)
-
input_schema: Json.t; (** JSON Schema defining expected parameters *)
-
annotations: Json.t option; (** Optional properties describing tool behavior *)
+
name : string; (** Unique identifier for the tool *)
+
description : string option; (** Human-readable description *)
+
input_schema : Json.t; (** JSON Schema defining expected parameters *)
+
annotations : Json.t option;
+
(** Optional properties describing tool behavior *)
}
+
include Json.Jsonable.S with type t := t
end
-
+
(** Response result *)
module Response : sig
type t = {
-
tools: Tool.t list; (** List of available tools *)
-
next_cursor: Cursor.t option; (** Optional cursor for the next page *)
+
tools : Tool.t list; (** List of available tools *)
+
next_cursor : Cursor.t option; (** Optional cursor for the next page *)
}
+
include Json.Jsonable.S with type t := t
end
-
+
+
val create_request :
+
?cursor:Cursor.t -> ?id:RequestId.t -> unit -> JSONRPCMessage.t
(** Create a tools/list request *)
-
val create_request : ?cursor:Cursor.t -> ?id:RequestId.t -> unit -> JSONRPCMessage.t
-
+
+
val create_response :
+
id:RequestId.t ->
+
tools:Tool.t list ->
+
?next_cursor:Cursor.t ->
+
unit ->
+
JSONRPCMessage.t
(** Create a tools/list response *)
-
val create_response : id:RequestId.t -> tools:Tool.t list -> ?next_cursor:Cursor.t -> unit -> JSONRPCMessage.t
end
(** Tools/Call - Request to invoke a tool *)
···
(** Request parameters *)
module Request : sig
type t = {
-
name: string; (** Name of the tool to call *)
-
arguments: Json.t; (** Arguments for the tool invocation *)
+
name : string; (** Name of the tool to call *)
+
arguments : Json.t; (** Arguments for the tool invocation *)
}
+
include Json.Jsonable.S with type t := t
end
-
+
(** Tool content *)
module ToolContent : sig
-
type t =
-
| Text of TextContent.t (** Text content *)
-
| Image of ImageContent.t (** Image content *)
-
| Audio of AudioContent.t (** Audio content *)
-
| Resource of EmbeddedResource.t (** Resource content *)
+
type t =
+
| Text of TextContent.t (** Text content *)
+
| Image of ImageContent.t (** Image content *)
+
| Audio of AudioContent.t (** Audio content *)
+
| Resource of EmbeddedResource.t (** Resource content *)
+
include Json.Jsonable.S with type t := t
end
-
+
(** Response result *)
module Response : sig
type t = {
-
content: ToolContent.t list; (** List of content items returned by the tool *)
-
is_error: bool; (** Whether the result represents an error *)
+
content : ToolContent.t list;
+
(** List of content items returned by the tool *)
+
is_error : bool; (** Whether the result represents an error *)
}
+
include Json.Jsonable.S with type t := t
end
-
+
+
val create_request :
+
name:string ->
+
arguments:Json.t ->
+
?id:RequestId.t ->
+
unit ->
+
JSONRPCMessage.t
(** Create a tools/call request *)
-
val create_request : name:string -> arguments:Json.t -> ?id:RequestId.t -> unit -> JSONRPCMessage.t
-
+
+
val create_response :
+
id:RequestId.t ->
+
content:ToolContent.t list ->
+
is_error:bool ->
+
unit ->
+
JSONRPCMessage.t
(** Create a tools/call response *)
-
val create_response : id:RequestId.t -> content:ToolContent.t list -> is_error:bool -> unit -> JSONRPCMessage.t
end
(** Prompts/List - Request to list available prompts *)
···
(** Prompt argument *)
module PromptArgument : sig
type t = {
-
name: string; (** Name of the argument *)
-
description: string option; (** Description of the argument *)
-
required: bool; (** Whether the argument is required *)
+
name : string; (** Name of the argument *)
+
description : string option; (** Description of the argument *)
+
required : bool; (** Whether the argument is required *)
}
+
include Json.Jsonable.S with type t := t
end
-
+
(** Prompt definition *)
module Prompt : sig
type t = {
-
name: string; (** Unique identifier for the prompt *)
-
description: string option; (** Human-readable description *)
-
arguments: PromptArgument.t list; (** Arguments for customization *)
+
name : string; (** Unique identifier for the prompt *)
+
description : string option; (** Human-readable description *)
+
arguments : PromptArgument.t list; (** Arguments for customization *)
}
+
include Json.Jsonable.S with type t := t
end
-
+
(** Request parameters *)
module Request : sig
-
type t = {
-
cursor: Cursor.t option; (** Optional pagination cursor *)
-
}
+
type t = { cursor : Cursor.t option (** Optional pagination cursor *) }
+
include Json.Jsonable.S with type t := t
end
-
+
(** Response result *)
module Response : sig
type t = {
-
prompts: Prompt.t list; (** List of available prompts *)
-
next_cursor: Cursor.t option; (** Optional cursor for the next page *)
+
prompts : Prompt.t list; (** List of available prompts *)
+
next_cursor : Cursor.t option; (** Optional cursor for the next page *)
}
+
include Json.Jsonable.S with type t := t
end
-
+
+
val create_request :
+
?cursor:Cursor.t -> ?id:RequestId.t -> unit -> JSONRPCMessage.t
(** Create a prompts/list request *)
-
val create_request : ?cursor:Cursor.t -> ?id:RequestId.t -> unit -> JSONRPCMessage.t
-
+
+
val create_response :
+
id:RequestId.t ->
+
prompts:Prompt.t list ->
+
?next_cursor:Cursor.t ->
+
unit ->
+
JSONRPCMessage.t
(** Create a prompts/list response *)
-
val create_response : id:RequestId.t -> prompts:Prompt.t list -> ?next_cursor:Cursor.t -> unit -> JSONRPCMessage.t
end
(** Prompts/Get - Request to get a prompt with arguments *)
···
(** Request parameters *)
module Request : sig
type t = {
-
name: string; (** Name of the prompt to get *)
-
arguments: (string * string) list; (** Arguments for the prompt *)
+
name : string; (** Name of the prompt to get *)
+
arguments : (string * string) list; (** Arguments for the prompt *)
}
+
include Json.Jsonable.S with type t := t
end
-
+
(** Response result *)
module Response : sig
type t = {
-
description: string option; (** Description of the prompt *)
-
messages: PromptMessage.t list; (** List of messages in the prompt *)
+
description : string option; (** Description of the prompt *)
+
messages : PromptMessage.t list; (** List of messages in the prompt *)
}
+
include Json.Jsonable.S with type t := t
end
-
+
+
val create_request :
+
name:string ->
+
arguments:(string * string) list ->
+
?id:RequestId.t ->
+
unit ->
+
JSONRPCMessage.t
(** Create a prompts/get request *)
-
val create_request : name:string -> arguments:(string * string) list -> ?id:RequestId.t -> unit -> JSONRPCMessage.t
-
+
+
val create_response :
+
id:RequestId.t ->
+
?description:string ->
+
messages:PromptMessage.t list ->
+
unit ->
+
JSONRPCMessage.t
(** Create a prompts/get response *)
-
val create_response : id:RequestId.t -> ?description:string -> messages:PromptMessage.t list -> unit -> JSONRPCMessage.t
end
(** List Changed Notifications *)
module ListChanged : sig
-
(** Create a resources/list_changed notification *)
val create_resources_notification : unit -> JSONRPCMessage.t
-
-
(** Create a tools/list_changed notification *)
+
(** Create a resources/list_changed notification *)
+
val create_tools_notification : unit -> JSONRPCMessage.t
-
+
(** Create a tools/list_changed notification *)
+
+
val create_prompts_notification : unit -> JSONRPCMessage.t
(** Create a prompts/list_changed notification *)
-
val create_prompts_notification : unit -> JSONRPCMessage.t
end
(** Resource Updated Notification *)
module ResourceUpdated : sig
(** Notification parameters *)
module Notification : sig
-
type t = {
-
uri: string; (** URI of the updated resource *)
-
}
+
type t = { uri : string (** URI of the updated resource *) }
+
include Json.Jsonable.S with type t := t
end
-
+
+
val create_notification : uri:string -> unit -> JSONRPCMessage.t
(** Create a resources/updated notification *)
-
val create_notification : uri:string -> unit -> JSONRPCMessage.t
end
(** Progress Notification *)
···
(** Notification parameters *)
module Notification : sig
type t = {
-
progress: float; (** Current progress value *)
-
total: float; (** Total progress value *)
-
progress_token: ProgressToken.t; (** Token identifying the operation *)
+
progress : float; (** Current progress value *)
+
total : float; (** Total progress value *)
+
progress_token : ProgressToken.t; (** Token identifying the operation *)
}
+
include Json.Jsonable.S with type t := t
end
-
+
+
val create_notification :
+
progress:float ->
+
total:float ->
+
progress_token:ProgressToken.t ->
+
unit ->
+
JSONRPCMessage.t
(** Create a progress notification *)
-
val create_notification : progress:float -> total:float -> progress_token:ProgressToken.t -> unit -> JSONRPCMessage.t
end
+316 -327
lib/mcp_sdk.ml
···
let logf level fmt =
Printf.fprintf stderr "[%s] " (string_of_level level);
-
Printf.kfprintf (fun oc -> Printf.fprintf oc "\n"; flush oc) stderr fmt
+
Printf.kfprintf
+
(fun oc ->
+
Printf.fprintf oc "\n";
+
flush oc)
+
stderr fmt
let debugf fmt = logf Debug fmt
let infof fmt = logf Info fmt
let warningf fmt = logf Warning fmt
let errorf fmt = logf Error fmt
-
+
(* Backward compatibility functions that take a simple string *)
let log level msg = logf level "%s" msg
let debug msg = debugf "%s" msg
···
(* Context for tools and resources *)
module Context = struct
type t = {
-
request_id: RequestId.t option;
-
lifespan_context: (string * Json.t) list;
-
progress_token: ProgressToken.t option;
+
request_id : RequestId.t option;
+
lifespan_context : (string * Json.t) list;
+
progress_token : ProgressToken.t option;
}
-
let create ?request_id ?progress_token ?(lifespan_context=[]) () =
+
let create ?request_id ?progress_token ?(lifespan_context = []) () =
{ request_id; lifespan_context; progress_token }
-
let get_context_value ctx key =
-
List.assoc_opt key ctx.lifespan_context
-
+
let get_context_value ctx key = List.assoc_opt key ctx.lifespan_context
+
let report_progress ctx value total =
-
match ctx.progress_token, ctx.request_id with
+
match (ctx.progress_token, ctx.request_id) with
| Some token, Some _id ->
-
let params = `Assoc [
-
("progress", `Float value);
-
("total", `Float total);
-
("progressToken", ProgressToken.yojson_of_t token)
-
] in
-
Some (create_notification ~meth:Method.Progress ~params:(Some params) ())
+
let params =
+
`Assoc
+
[
+
("progress", `Float value);
+
("total", `Float total);
+
("progressToken", ProgressToken.yojson_of_t token);
+
]
+
in
+
Some
+
(create_notification ~meth:Method.Progress ~params:(Some params) ())
| _ -> None
end
···
type handler = Context.t -> Json.t -> (Json.t, string) result
type t = {
-
name: string;
-
description: string option;
-
input_schema: Json.t; (* JSON Schema *)
-
handler: handler;
+
name : string;
+
description : string option;
+
input_schema : Json.t; (* JSON Schema *)
+
handler : handler;
}
-
let create ~name ?description ~input_schema ~handler () =
+
let create ~name ?description ~input_schema ~handler () =
{ name; description; input_schema; handler }
let to_json tool =
-
let assoc = [
-
("name", `String tool.name);
-
("inputSchema", tool.input_schema);
-
] in
-
let assoc = match tool.description with
+
let assoc =
+
[ ("name", `String tool.name); ("inputSchema", tool.input_schema) ]
+
in
+
let assoc =
+
match tool.description with
| Some desc -> ("description", `String desc) :: assoc
| None -> assoc
in
`Assoc assoc
-
+
(* Convert to Mcp_rpc.ToolsList.Tool.t *)
-
let to_rpc_tool_list_tool (tool:t) =
-
Mcp_rpc.ToolsList.Tool.{
-
name = tool.name;
-
description = tool.description;
-
input_schema = tool.input_schema;
-
annotations = None; (* Could be extended to support annotations *)
-
}
+
let to_rpc_tool_list_tool (tool : t) =
+
Mcp_rpc.ToolsList.Tool.
+
{
+
name = tool.name;
+
description = tool.description;
+
input_schema = tool.input_schema;
+
annotations = None;
+
(* Could be extended to support annotations *)
+
}
(* Convert a list of Tool.t to the format needed for tools/list response *)
-
let to_rpc_tools_list tools =
-
List.map to_rpc_tool_list_tool tools
+
let to_rpc_tools_list tools = List.map to_rpc_tool_list_tool tools
(* Convert Mcp_rpc.ToolsCall response content to Mcp.content list *)
let rpc_content_to_mcp_content content =
-
List.map (function
-
| Mcp_rpc.ToolsCall.ToolContent.Text t ->
-
Mcp.Text { TextContent.text = t.text; annotations = None }
-
| Mcp_rpc.ToolsCall.ToolContent.Image i ->
-
Mcp.Image {
-
ImageContent.mime_type = i.mime_type;
-
data = i.data;
-
annotations = None
-
}
-
| Mcp_rpc.ToolsCall.ToolContent.Audio a ->
-
Mcp.Audio {
-
AudioContent.mime_type = a.mime_type;
-
data = a.data;
-
annotations = None
-
}
-
| Mcp_rpc.ToolsCall.ToolContent.Resource r ->
-
(* Create a simple text resource from the embedded resource *)
-
let uri = match r with
-
| { EmbeddedResource.resource = `Text tr; _ } -> tr.uri
-
| { EmbeddedResource.resource = `Blob br; _ } -> br.uri
-
in
-
let text_content = match r with
-
| { EmbeddedResource.resource = `Text tr; _ } -> tr.text
-
| { EmbeddedResource.resource = `Blob br; _ } -> "Binary content"
-
in
-
let mime_type = match r with
-
| { EmbeddedResource.resource = `Text tr; _ } -> tr.mime_type
-
| { EmbeddedResource.resource = `Blob br; _ } -> br.mime_type
-
in
-
let text_resource = {
-
TextResourceContents.uri;
-
text = text_content;
-
mime_type
-
} in
-
Mcp.Resource {
-
EmbeddedResource.resource = `Text text_resource;
-
annotations = None
-
}
-
) content
+
List.map
+
(function
+
| Mcp_rpc.ToolsCall.ToolContent.Text t ->
+
Mcp.Text { TextContent.text = t.text; annotations = None }
+
| Mcp_rpc.ToolsCall.ToolContent.Image i ->
+
Mcp.Image
+
{
+
ImageContent.mime_type = i.mime_type;
+
data = i.data;
+
annotations = None;
+
}
+
| Mcp_rpc.ToolsCall.ToolContent.Audio a ->
+
Mcp.Audio
+
{
+
AudioContent.mime_type = a.mime_type;
+
data = a.data;
+
annotations = None;
+
}
+
| Mcp_rpc.ToolsCall.ToolContent.Resource r ->
+
(* Create a simple text resource from the embedded resource *)
+
let uri =
+
match r with
+
| { EmbeddedResource.resource = `Text tr; _ } -> tr.uri
+
| { EmbeddedResource.resource = `Blob br; _ } -> br.uri
+
in
+
let text_content =
+
match r with
+
| { EmbeddedResource.resource = `Text tr; _ } -> tr.text
+
| { EmbeddedResource.resource = `Blob br; _ } -> "Binary content"
+
in
+
let mime_type =
+
match r with
+
| { EmbeddedResource.resource = `Text tr; _ } -> tr.mime_type
+
| { EmbeddedResource.resource = `Blob br; _ } -> br.mime_type
+
in
+
let text_resource =
+
{ TextResourceContents.uri; text = text_content; mime_type }
+
in
+
Mcp.Resource
+
{
+
EmbeddedResource.resource = `Text text_resource;
+
annotations = None;
+
})
+
content
(* Convert Mcp.content list to Mcp_rpc.ToolsCall.ToolContent.t list *)
let mcp_content_to_rpc_content content =
-
List.map (function
-
| Mcp.Text t ->
-
Mcp_rpc.ToolsCall.ToolContent.Text t
-
| Mcp.Image img ->
-
Mcp_rpc.ToolsCall.ToolContent.Image img
-
| Mcp.Audio aud ->
-
Mcp_rpc.ToolsCall.ToolContent.Audio aud
-
| Mcp.Resource res ->
-
let resource_data = match res.resource with
-
| `Text txt -> `Text txt
-
| `Blob blob -> `Blob blob
-
in
-
let resource = {
-
EmbeddedResource.resource = resource_data;
-
annotations = res.annotations
-
} in
-
Mcp_rpc.ToolsCall.ToolContent.Resource resource
-
) content
-
+
List.map
+
(function
+
| Mcp.Text t -> Mcp_rpc.ToolsCall.ToolContent.Text t
+
| Mcp.Image img -> Mcp_rpc.ToolsCall.ToolContent.Image img
+
| Mcp.Audio aud -> Mcp_rpc.ToolsCall.ToolContent.Audio aud
+
| Mcp.Resource res ->
+
let resource_data =
+
match res.resource with
+
| `Text txt -> `Text txt
+
| `Blob blob -> `Blob blob
+
in
+
let resource =
+
{
+
EmbeddedResource.resource = resource_data;
+
annotations = res.annotations;
+
}
+
in
+
Mcp_rpc.ToolsCall.ToolContent.Resource resource)
+
content
+
(* Create a tool result with content *)
let create_tool_result content ~is_error =
-
`Assoc [
-
("content", `List (List.map Mcp.yojson_of_content content));
-
("isError", `Bool is_error);
-
]
+
`Assoc
+
[
+
("content", `List (List.map Mcp.yojson_of_content content));
+
("isError", `Bool is_error);
+
]
(* Create a tool error result with structured content *)
let create_error_result error =
Log.errorf "Error result: %s" error;
-
create_tool_result [Mcp.make_text_content error] ~is_error:true
-
+
create_tool_result [ Mcp.make_text_content error ] ~is_error:true
+
(* Handle tool execution errors *)
let handle_execution_error err =
create_error_result (Printf.sprintf "Error executing tool: %s" err)
-
+
(* Handle unknown tool error *)
let handle_unknown_tool_error name =
create_error_result (Printf.sprintf "Unknown tool: %s" name)
-
+
(* Handle general tool execution exception *)
let handle_execution_exception exn =
-
create_error_result (Printf.sprintf "Internal error: %s" (Printexc.to_string exn))
+
create_error_result
+
(Printf.sprintf "Internal error: %s" (Printexc.to_string exn))
end
(* Resources for the MCP server *)
···
type handler = Context.t -> string list -> (string, string) result
type t = {
-
uri: string; (* For resources, this is the exact URI (no variables) *)
-
name: string;
-
description: string option;
-
mime_type: string option;
-
handler: handler;
+
uri : string; (* For resources, this is the exact URI (no variables) *)
+
name : string;
+
description : string option;
+
mime_type : string option;
+
handler : handler;
}
let create ~uri ~name ?description ?mime_type ~handler () =
(* Validate that the URI doesn't contain template variables *)
if String.contains uri '{' || String.contains uri '}' then
-
Log.warningf "Resource '%s' contains template variables. Consider using add_resource_template instead." uri;
+
Log.warningf
+
"Resource '%s' contains template variables. Consider using \
+
add_resource_template instead."
+
uri;
{ uri; name; description; mime_type; handler }
let to_json resource =
-
let assoc = [
-
("uri", `String resource.uri);
-
("name", `String resource.name);
-
] in
-
let assoc = match resource.description with
+
let assoc =
+
[ ("uri", `String resource.uri); ("name", `String resource.name) ]
+
in
+
let assoc =
+
match resource.description with
| Some desc -> ("description", `String desc) :: assoc
| None -> assoc
in
-
let assoc = match resource.mime_type with
+
let assoc =
+
match resource.mime_type with
| Some mime -> ("mimeType", `String mime) :: assoc
| None -> assoc
in
`Assoc assoc
-
+
(* Convert to Mcp_rpc.ResourcesList.Resource.t *)
-
let to_rpc_resource_list_resource (resource:t) =
-
Mcp_rpc.ResourcesList.Resource.{
-
uri = resource.uri;
-
name = resource.name;
-
description = resource.description;
-
mime_type = resource.mime_type;
-
size = None; (* Size can be added when we have actual resource content *)
-
}
-
+
let to_rpc_resource_list_resource (resource : t) =
+
Mcp_rpc.ResourcesList.Resource.
+
{
+
uri = resource.uri;
+
name = resource.name;
+
description = resource.description;
+
mime_type = resource.mime_type;
+
size = None;
+
(* Size can be added when we have actual resource content *)
+
}
+
(* Convert a list of Resource.t to the format needed for resources/list response *)
let to_rpc_resources_list resources =
List.map to_rpc_resource_list_resource resources
···
(* Prompts for the MCP server *)
module Prompt = struct
type argument = {
-
name: string;
-
description: string option;
-
required: bool;
+
name : string;
+
description : string option;
+
required : bool;
}
-
type message = {
-
role: Role.t;
-
content: content;
-
}
+
type message = { role : Role.t; content : content }
-
type handler = Context.t -> (string * string) list -> (message list, string) result
+
type handler =
+
Context.t -> (string * string) list -> (message list, string) result
type t = {
-
name: string;
-
description: string option;
-
arguments: argument list;
-
handler: handler;
+
name : string;
+
description : string option;
+
arguments : argument list;
+
handler : handler;
}
-
let create ~name ?description ?(arguments=[]) ~handler () =
+
let create ~name ?description ?(arguments = []) ~handler () =
{ name; description; arguments; handler }
-
let create_argument ~name ?description ?(required=false) () =
+
let create_argument ~name ?description ?(required = false) () =
{ name; description; required }
let to_json prompt =
-
let assoc = [
-
("name", `String prompt.name);
-
] in
-
let assoc = match prompt.description with
+
let assoc = [ ("name", `String prompt.name) ] in
+
let assoc =
+
match prompt.description with
| Some desc -> ("description", `String desc) :: assoc
| None -> assoc
in
-
let assoc = if prompt.arguments <> [] then
-
let args = List.map (fun (arg: argument) ->
-
let arg_assoc = [
-
("name", `String arg.name);
-
] in
-
let arg_assoc = match arg.description with
-
| Some desc -> ("description", `String desc) :: arg_assoc
-
| None -> arg_assoc
+
let assoc =
+
if prompt.arguments <> [] then
+
let args =
+
List.map
+
(fun (arg : argument) ->
+
let arg_assoc = [ ("name", `String arg.name) ] in
+
let arg_assoc =
+
match arg.description with
+
| Some desc -> ("description", `String desc) :: arg_assoc
+
| None -> arg_assoc
+
in
+
let arg_assoc =
+
if arg.required then ("required", `Bool true) :: arg_assoc
+
else arg_assoc
+
in
+
`Assoc arg_assoc)
+
prompt.arguments
in
-
let arg_assoc =
-
if arg.required then
-
("required", `Bool true) :: arg_assoc
-
else
-
arg_assoc
-
in
-
`Assoc arg_assoc
-
) prompt.arguments in
-
("arguments", `List args) :: assoc
-
else
-
assoc
+
("arguments", `List args) :: assoc
+
else assoc
in
`Assoc assoc
-
+
(* Convert argument to Mcp_rpc.PromptsList.PromptArgument.t *)
-
let argument_to_rpc_prompt_argument (arg:argument) =
-
Mcp_rpc.PromptsList.PromptArgument.{
-
name = arg.name;
-
description = arg.description;
-
required = arg.required;
-
}
-
+
let argument_to_rpc_prompt_argument (arg : argument) =
+
Mcp_rpc.PromptsList.PromptArgument.
+
{
+
name = arg.name;
+
description = arg.description;
+
required = arg.required;
+
}
+
(* Convert to Mcp_rpc.PromptsList.Prompt.t *)
-
let to_rpc_prompt_list_prompt (prompt:t) =
-
Mcp_rpc.PromptsList.Prompt.{
-
name = prompt.name;
-
description = prompt.description;
-
arguments = List.map argument_to_rpc_prompt_argument prompt.arguments;
-
}
-
+
let to_rpc_prompt_list_prompt (prompt : t) =
+
Mcp_rpc.PromptsList.Prompt.
+
{
+
name = prompt.name;
+
description = prompt.description;
+
arguments = List.map argument_to_rpc_prompt_argument prompt.arguments;
+
}
+
(* Convert a list of Prompt.t to the format needed for prompts/list response *)
-
let to_rpc_prompts_list prompts =
-
List.map to_rpc_prompt_list_prompt prompts
-
+
let to_rpc_prompts_list prompts = List.map to_rpc_prompt_list_prompt prompts
+
(* Convert message to Mcp_rpc.PromptMessage.t *)
let message_to_rpc_prompt_message msg =
-
{
-
PromptMessage.role = msg.role;
-
PromptMessage.content = msg.content;
-
}
-
+
{ PromptMessage.role = msg.role; PromptMessage.content = msg.content }
+
(* Convert a list of messages to the format needed for prompts/get response *)
let messages_to_rpc_prompt_messages messages =
List.map message_to_rpc_prompt_message messages
end
let make_tool_schema properties required =
-
let props = List.map (fun (name, schema_type, description) ->
-
(name, `Assoc [
-
("type", `String schema_type);
-
("description", `String description)
-
])
-
) properties in
+
let props =
+
List.map
+
(fun (name, schema_type, description) ->
+
( name,
+
`Assoc
+
[
+
("type", `String schema_type); ("description", `String description);
+
] ))
+
properties
+
in
let required_json = `List (List.map (fun name -> `String name) required) in
-
`Assoc [
-
("type", `String "object");
-
("properties", `Assoc props);
-
("required", required_json)
-
]
+
`Assoc
+
[
+
("type", `String "object");
+
("properties", `Assoc props);
+
("required", required_json);
+
]
(* Resource Templates for the MCP server *)
module ResourceTemplate = struct
type handler = Context.t -> string list -> (string, string) result
type t = {
-
uri_template: string;
-
name: string;
-
description: string option;
-
mime_type: string option;
-
handler: handler;
+
uri_template : string;
+
name : string;
+
description : string option;
+
mime_type : string option;
+
handler : handler;
}
-
let create ~uri_template ~name ?description ?mime_type ~handler () =
+
let create ~uri_template ~name ?description ?mime_type ~handler () =
{ uri_template; name; description; mime_type; handler }
let to_json resource_template =
-
let assoc = [
-
("uriTemplate", `String resource_template.uri_template);
-
("name", `String resource_template.name);
-
] in
-
let assoc = match resource_template.description with
+
let assoc =
+
[
+
("uriTemplate", `String resource_template.uri_template);
+
("name", `String resource_template.name);
+
]
+
in
+
let assoc =
+
match resource_template.description with
| Some desc -> ("description", `String desc) :: assoc
| None -> assoc
in
-
let assoc = match resource_template.mime_type with
+
let assoc =
+
match resource_template.mime_type with
| Some mime -> ("mimeType", `String mime) :: assoc
| None -> assoc
in
`Assoc assoc
-
+
(* Convert to Mcp_rpc.ResourceTemplatesList.ResourceTemplate.t *)
-
let to_rpc_resource_template (template:t) =
-
Mcp_rpc.ListResourceTemplatesResult.ResourceTemplate.{
-
uri_template = template.uri_template;
-
name = template.name;
-
description = template.description;
-
mime_type = template.mime_type;
-
}
-
+
let to_rpc_resource_template (template : t) =
+
Mcp_rpc.ListResourceTemplatesResult.ResourceTemplate.
+
{
+
uri_template = template.uri_template;
+
name = template.name;
+
description = template.description;
+
mime_type = template.mime_type;
+
}
+
(* Convert a list of ResourceTemplate.t to the format needed for resources/templates/list response *)
let to_rpc_resource_templates_list templates =
List.map to_rpc_resource_template templates
···
(* Main server type *)
type server = {
-
name: string;
-
version: string;
-
protocol_version: string;
-
lifespan_context: (string * Json.t) list;
-
mutable capabilities: Json.t;
-
mutable tools: Tool.t list;
-
mutable resources: Resource.t list;
-
mutable resource_templates: ResourceTemplate.t list;
-
mutable prompts: Prompt.t list;
-
}
+
name : string;
+
version : string;
+
protocol_version : string;
+
lifespan_context : (string * Json.t) list;
+
mutable capabilities : Json.t;
+
mutable tools : Tool.t list;
+
mutable resources : Resource.t list;
+
mutable resource_templates : ResourceTemplate.t list;
+
mutable prompts : Prompt.t list;
+
}
let name { name; _ } = name
let version { version; _ } = version
···
let prompts { prompts; _ } = prompts
(* Create a new server *)
-
let create_server ~name ?(version="0.1.0") ?(protocol_version="2024-11-05") () =
-
{
+
let create_server ~name ?(version = "0.1.0") ?(protocol_version = "2024-11-05")
+
() =
+
{
name;
version;
protocol_version;
···
}
(* Default capabilities for the server *)
-
let default_capabilities ?(with_tools=true) ?(with_resources=false) ?(with_resource_templates=false) ?(with_prompts=false) () =
+
let default_capabilities ?(with_tools = true) ?(with_resources = false)
+
?(with_resource_templates = false) ?(with_prompts = false) () =
let caps = [] in
-
let caps =
-
if with_tools then
-
("tools", `Assoc [
-
("listChanged", `Bool true)
-
]) :: caps
-
else
-
caps
+
let caps =
+
if with_tools then ("tools", `Assoc [ ("listChanged", `Bool true) ]) :: caps
+
else caps
in
-
let caps =
+
let caps =
if with_resources then
-
("resources", `Assoc [
-
("listChanged", `Bool true);
-
("subscribe", `Bool false)
-
]) :: caps
+
( "resources",
+
`Assoc [ ("listChanged", `Bool true); ("subscribe", `Bool false) ] )
+
:: caps
else if not with_resources then
-
("resources", `Assoc [
-
("listChanged", `Bool false);
-
("subscribe", `Bool false)
-
]) :: caps
-
else
-
caps
+
( "resources",
+
`Assoc [ ("listChanged", `Bool false); ("subscribe", `Bool false) ] )
+
:: caps
+
else caps
in
let caps =
if with_resource_templates then
-
("resourceTemplates", `Assoc [
-
("listChanged", `Bool true)
-
]) :: caps
+
("resourceTemplates", `Assoc [ ("listChanged", `Bool true) ]) :: caps
else if not with_resource_templates then
-
("resourceTemplates", `Assoc [
-
("listChanged", `Bool false)
-
]) :: caps
-
else
-
caps
+
("resourceTemplates", `Assoc [ ("listChanged", `Bool false) ]) :: caps
+
else caps
in
-
let caps =
+
let caps =
if with_prompts then
-
("prompts", `Assoc [
-
("listChanged", `Bool true)
-
]) :: caps
+
("prompts", `Assoc [ ("listChanged", `Bool true) ]) :: caps
else if not with_prompts then
-
("prompts", `Assoc [
-
("listChanged", `Bool false)
-
]) :: caps
-
else
-
caps
+
("prompts", `Assoc [ ("listChanged", `Bool false) ]) :: caps
+
else caps
in
`Assoc caps
···
tool
(* Create and register a tool in one step *)
-
let add_tool server ~name ?description ?(schema_properties=[]) ?(schema_required=[]) handler =
+
let add_tool server ~name ?description ?(schema_properties = [])
+
?(schema_required = []) handler =
let input_schema = make_tool_schema schema_properties schema_required in
let handler' ctx args =
-
try
-
Ok (handler args)
-
with exn ->
-
Error (Printexc.to_string exn)
+
try Ok (handler args) with exn -> Error (Printexc.to_string exn)
in
-
let tool = Tool.create
-
~name
-
?description
-
~input_schema
-
~handler:handler'
-
()
+
let tool =
+
Tool.create ~name ?description ~input_schema ~handler:handler' ()
in
register_tool server tool
···
(* Create and register a resource in one step *)
let add_resource server ~uri ~name ?description ?mime_type handler =
let handler' _ctx params =
-
try
-
Ok (handler params)
-
with exn ->
-
Error (Printexc.to_string exn)
+
try Ok (handler params) with exn -> Error (Printexc.to_string exn)
in
-
let resource = Resource.create
-
~uri
-
~name
-
?description
-
?mime_type
-
~handler:handler'
-
()
+
let resource =
+
Resource.create ~uri ~name ?description ?mime_type ~handler:handler' ()
in
register_resource server resource
···
template
(* Create and register a resource template in one step *)
-
let add_resource_template server ~uri_template ~name ?description ?mime_type handler =
+
let add_resource_template server ~uri_template ~name ?description ?mime_type
+
handler =
let handler' _ctx params =
-
try
-
Ok (handler params)
-
with exn ->
-
Error (Printexc.to_string exn)
+
try Ok (handler params) with exn -> Error (Printexc.to_string exn)
in
-
let template = ResourceTemplate.create
-
~uri_template
-
~name
-
?description
-
?mime_type
-
~handler:handler'
-
()
+
let template =
+
ResourceTemplate.create ~uri_template ~name ?description ?mime_type
+
~handler:handler' ()
in
register_resource_template server template
···
prompt
(* Create and register a prompt in one step *)
-
let add_prompt server ~name ?description ?(arguments=[]) handler =
-
let prompt_args = List.map (fun (name, desc, required) ->
-
Prompt.create_argument ~name ?description:desc ~required ()
-
) arguments in
+
let add_prompt server ~name ?description ?(arguments = []) handler =
+
let prompt_args =
+
List.map
+
(fun (name, desc, required) ->
+
Prompt.create_argument ~name ?description:desc ~required ())
+
arguments
+
in
let handler' _ctx args =
-
try
-
Ok (handler args)
-
with exn ->
-
Error (Printexc.to_string exn)
+
try Ok (handler args) with exn -> Error (Printexc.to_string exn)
in
-
let prompt = Prompt.create
-
~name
-
?description
-
~arguments:prompt_args
-
~handler:handler'
-
()
+
let prompt =
+
Prompt.create ~name ?description ~arguments:prompt_args ~handler:handler' ()
in
register_prompt server prompt
(* Set server capabilities *)
-
let set_capabilities server capabilities =
-
server.capabilities <- capabilities
+
let set_capabilities server capabilities = server.capabilities <- capabilities
(* Configure server with default capabilities based on registered components *)
-
let configure_server server ?with_tools ?with_resources ?with_resource_templates ?with_prompts () =
-
let with_tools = match with_tools with
-
| Some b -> b
-
| None -> server.tools <> []
+
let configure_server server ?with_tools ?with_resources ?with_resource_templates
+
?with_prompts () =
+
let with_tools =
+
match with_tools with Some b -> b | None -> server.tools <> []
in
-
let with_resources = match with_resources with
-
| Some b -> b
-
| None -> server.resources <> []
+
let with_resources =
+
match with_resources with Some b -> b | None -> server.resources <> []
in
-
let with_resource_templates = match with_resource_templates with
+
let with_resource_templates =
+
match with_resource_templates with
| Some b -> b
| None -> server.resource_templates <> []
in
-
let with_prompts = match with_prompts with
-
| Some b -> b
-
| None -> server.prompts <> []
+
let with_prompts =
+
match with_prompts with Some b -> b | None -> server.prompts <> []
in
-
let capabilities = default_capabilities ~with_tools ~with_resources ~with_resource_templates ~with_prompts () in
+
let capabilities =
+
default_capabilities ~with_tools ~with_resources ~with_resource_templates
+
~with_prompts ()
+
in
set_capabilities server capabilities;
server
+168 -80
lib/mcp_sdk.mli
···
open Mcp
open Jsonrpc
-
(** SDK version *)
val version : string
+
(** SDK version *)
(** Logging utilities *)
module Log : sig
···
val string_of_level : level -> string
-
(** Format-string based logging functions *)
val logf : level -> ('a, out_channel, unit) format -> 'a
+
(** Format-string based logging functions *)
+
val debugf : ('a, out_channel, unit) format -> 'a
val infof : ('a, out_channel, unit) format -> 'a
val warningf : ('a, out_channel, unit) format -> 'a
val errorf : ('a, out_channel, unit) format -> 'a
+
val log : level -> string -> unit
(** Simple string logging functions (for backward compatibility) *)
-
val log : level -> string -> unit
+
val debug : string -> unit
val info : string -> unit
val warning : string -> unit
···
module Context : sig
type t
-
val create : ?request_id:RequestId.t -> ?progress_token:ProgressToken.t -> ?lifespan_context:(string * Json.t) list -> unit -> t
+
val create :
+
?request_id:RequestId.t ->
+
?progress_token:ProgressToken.t ->
+
?lifespan_context:(string * Json.t) list ->
+
unit ->
+
t
+
val get_context_value : t -> string -> Json.t option
val report_progress : t -> float -> float -> JSONRPCMessage.t option
end
···
type handler = Context.t -> Json.t -> (Json.t, string) result
type t = {
-
name: string;
-
description: string option;
-
input_schema: Json.t;
-
handler: handler;
+
name : string;
+
description : string option;
+
input_schema : Json.t;
+
handler : handler;
}
-
val create : name:string -> ?description:string -> input_schema:Json.t -> handler:handler -> unit -> t
+
val create :
+
name:string ->
+
?description:string ->
+
input_schema:Json.t ->
+
handler:handler ->
+
unit ->
+
t
+
val to_json : t -> Json.t
-
-
(** Convert to Mcp_rpc.ToolsList.Tool.t *)
+
val to_rpc_tool_list_tool : t -> Mcp_rpc.ToolsList.Tool.t
-
-
(** Convert a list of Tool.t to the format needed for tools/list response *)
+
(** Convert to Mcp_rpc.ToolsList.Tool.t *)
+
val to_rpc_tools_list : t list -> Mcp_rpc.ToolsList.Tool.t list
-
+
(** Convert a list of Tool.t to the format needed for tools/list response *)
+
+
val rpc_content_to_mcp_content :
+
Mcp_rpc.ToolsCall.ToolContent.t list -> Mcp.content list
(** Convert Mcp_rpc.ToolsCall response content to Mcp.content list *)
-
val rpc_content_to_mcp_content : Mcp_rpc.ToolsCall.ToolContent.t list -> Mcp.content list
-
+
+
val mcp_content_to_rpc_content :
+
Mcp.content list -> Mcp_rpc.ToolsCall.ToolContent.t list
(** Convert Mcp.content list to Mcp_rpc.ToolsCall.ToolContent.t list *)
-
val mcp_content_to_rpc_content : Mcp.content list -> Mcp_rpc.ToolsCall.ToolContent.t list
-
+
+
val create_tool_result : Mcp.content list -> is_error:bool -> Json.t
(** Create a tool result with content *)
-
val create_tool_result : Mcp.content list -> is_error:bool -> Json.t
-
+
+
val create_error_result : string -> Json.t
(** Create a tool error result with structured content *)
-
val create_error_result : string -> Json.t
-
-
(** Handle tool execution errors *)
+
val handle_execution_error : string -> Json.t
-
-
(** Handle unknown tool error *)
+
(** Handle tool execution errors *)
+
val handle_unknown_tool_error : string -> Json.t
-
-
(** Handle general tool execution exception *)
+
(** Handle unknown tool error *)
+
val handle_execution_exception : exn -> Json.t
+
(** Handle general tool execution exception *)
end
(** Resources for the MCP server *)
···
type handler = Context.t -> string list -> (string, string) result
type t = {
-
uri: string;
-
name: string;
-
description: string option;
-
mime_type: string option;
-
handler: handler;
+
uri : string;
+
name : string;
+
description : string option;
+
mime_type : string option;
+
handler : handler;
}
-
val create : uri:string -> name:string -> ?description:string -> ?mime_type:string -> handler:handler -> unit -> t
+
val create :
+
uri:string ->
+
name:string ->
+
?description:string ->
+
?mime_type:string ->
+
handler:handler ->
+
unit ->
+
t
+
val to_json : t -> Json.t
-
-
(** Convert to Mcp_rpc.ResourcesList.Resource.t *)
+
val to_rpc_resource_list_resource : t -> Mcp_rpc.ResourcesList.Resource.t
-
-
(** Convert a list of Resource.t to the format needed for resources/list response *)
+
(** Convert to Mcp_rpc.ResourcesList.Resource.t *)
+
val to_rpc_resources_list : t list -> Mcp_rpc.ResourcesList.Resource.t list
+
(** Convert a list of Resource.t to the format needed for resources/list
+
response *)
end
(** Resource Templates for the MCP server *)
···
type handler = Context.t -> string list -> (string, string) result
type t = {
-
uri_template: string;
-
name: string;
-
description: string option;
-
mime_type: string option;
-
handler: handler;
+
uri_template : string;
+
name : string;
+
description : string option;
+
mime_type : string option;
+
handler : handler;
}
-
val create : uri_template:string -> name:string -> ?description:string -> ?mime_type:string -> handler:handler -> unit -> t
+
val create :
+
uri_template:string ->
+
name:string ->
+
?description:string ->
+
?mime_type:string ->
+
handler:handler ->
+
unit ->
+
t
+
val to_json : t -> Json.t
-
+
+
val to_rpc_resource_template :
+
t -> Mcp_rpc.ListResourceTemplatesResult.ResourceTemplate.t
(** Convert to Mcp_rpc.ResourceTemplatesList.ResourceTemplate.t *)
-
val to_rpc_resource_template : t -> Mcp_rpc.ListResourceTemplatesResult.ResourceTemplate.t
-
-
(** Convert a list of ResourceTemplate.t to the format needed for resources/templates/list response *)
-
val to_rpc_resource_templates_list : t list -> Mcp_rpc.ListResourceTemplatesResult.ResourceTemplate.t list
+
+
val to_rpc_resource_templates_list :
+
t list -> Mcp_rpc.ListResourceTemplatesResult.ResourceTemplate.t list
+
(** Convert a list of ResourceTemplate.t to the format needed for
+
resources/templates/list response *)
end
(** Prompts for the MCP server *)
module Prompt : sig
type argument = {
-
name: string;
-
description: string option;
-
required: bool;
+
name : string;
+
description : string option;
+
required : bool;
}
-
type message = {
-
role: Role.t;
-
content: content;
-
}
+
type message = { role : Role.t; content : content }
-
type handler = Context.t -> (string * string) list -> (message list, string) result
+
type handler =
+
Context.t -> (string * string) list -> (message list, string) result
type t = {
-
name: string;
-
description: string option;
-
arguments: argument list;
-
handler: handler;
+
name : string;
+
description : string option;
+
arguments : argument list;
+
handler : handler;
}
-
val create : name:string -> ?description:string -> ?arguments:argument list -> handler:handler -> unit -> t
-
val create_argument : name:string -> ?description:string -> ?required:bool -> unit -> argument
+
val create :
+
name:string ->
+
?description:string ->
+
?arguments:argument list ->
+
handler:handler ->
+
unit ->
+
t
+
+
val create_argument :
+
name:string -> ?description:string -> ?required:bool -> unit -> argument
+
val to_json : t -> Json.t
-
+
+
val argument_to_rpc_prompt_argument :
+
argument -> Mcp_rpc.PromptsList.PromptArgument.t
(** Convert argument to Mcp_rpc.PromptsList.PromptArgument.t *)
-
val argument_to_rpc_prompt_argument : argument -> Mcp_rpc.PromptsList.PromptArgument.t
-
-
(** Convert to Mcp_rpc.PromptsList.Prompt.t *)
+
val to_rpc_prompt_list_prompt : t -> Mcp_rpc.PromptsList.Prompt.t
-
-
(** Convert a list of Prompt.t to the format needed for prompts/list response *)
+
(** Convert to Mcp_rpc.PromptsList.Prompt.t *)
+
val to_rpc_prompts_list : t list -> Mcp_rpc.PromptsList.Prompt.t list
-
-
(** Convert message to Mcp_rpc.PromptMessage.t *)
+
(** Convert a list of Prompt.t to the format needed for prompts/list response
+
*)
+
val message_to_rpc_prompt_message : message -> PromptMessage.t
-
-
(** Convert a list of messages to the format needed for prompts/get response *)
+
(** Convert message to Mcp_rpc.PromptMessage.t *)
+
val messages_to_rpc_prompt_messages : message list -> PromptMessage.t list
+
(** Convert a list of messages to the format needed for prompts/get response
+
*)
end
+
type server
(** Main server type *)
-
type server
val name : server -> string
val version : server -> string
···
val resource_templates : server -> ResourceTemplate.t list
val prompts : server -> Prompt.t list
+
val create_server :
+
name:string -> ?version:string -> ?protocol_version:string -> unit -> server
(** Create a new server *)
-
val create_server : name:string -> ?version:string -> ?protocol_version:string -> unit -> server
+
val default_capabilities :
+
?with_tools:bool ->
+
?with_resources:bool ->
+
?with_resource_templates:bool ->
+
?with_prompts:bool ->
+
unit ->
+
Json.t
(** Default capabilities for the server *)
-
val default_capabilities : ?with_tools:bool -> ?with_resources:bool -> ?with_resource_templates:bool -> ?with_prompts:bool -> unit -> Json.t
+
val add_tool :
+
server ->
+
name:string ->
+
?description:string ->
+
?schema_properties:(string * string * string) list ->
+
?schema_required:string list ->
+
(Json.t -> Json.t) ->
+
Tool.t
(** Create and register a tool in one step *)
-
val add_tool : server -> name:string -> ?description:string -> ?schema_properties:(string * string * string) list -> ?schema_required:string list -> (Json.t -> Json.t) -> Tool.t
+
val add_resource :
+
server ->
+
uri:string ->
+
name:string ->
+
?description:string ->
+
?mime_type:string ->
+
(string list -> string) ->
+
Resource.t
(** Create and register a resource in one step *)
-
val add_resource : server -> uri:string -> name:string -> ?description:string -> ?mime_type:string -> (string list -> string) -> Resource.t
+
val add_resource_template :
+
server ->
+
uri_template:string ->
+
name:string ->
+
?description:string ->
+
?mime_type:string ->
+
(string list -> string) ->
+
ResourceTemplate.t
(** Create and register a resource template in one step *)
-
val add_resource_template : server -> uri_template:string -> name:string -> ?description:string -> ?mime_type:string -> (string list -> string) -> ResourceTemplate.t
+
val add_prompt :
+
server ->
+
name:string ->
+
?description:string ->
+
?arguments:(string * string option * bool) list ->
+
((string * string) list -> Prompt.message list) ->
+
Prompt.t
(** Create and register a prompt in one step *)
-
val add_prompt : server -> name:string -> ?description:string -> ?arguments:(string * string option * bool) list -> ((string * string) list -> Prompt.message list) -> Prompt.t
+
val configure_server :
+
server ->
+
?with_tools:bool ->
+
?with_resources:bool ->
+
?with_resource_templates:bool ->
+
?with_prompts:bool ->
+
unit ->
+
server
(** Configure server with default capabilities based on registered components *)
-
val configure_server : server -> ?with_tools:bool -> ?with_resources:bool -> ?with_resource_templates:bool -> ?with_prompts:bool -> unit -> server
val make_tool_schema : (string * string * string) list -> string list -> Json.t
+280 -227
lib/mcp_server.ml
···
(* Create a proper JSONRPC error with code and data *)
let create_jsonrpc_error id code message ?data () =
let error_code = ErrorCode.to_int code in
-
let error_data = match data with
-
| Some d -> d
-
| None -> `Null
-
in
+
let error_data = match data with Some d -> d | None -> `Null in
create_error ~id ~code:error_code ~message ~data:(Some error_data) ()
(* Process initialize request *)
let handle_initialize server req =
Log.debug "Processing initialize request";
-
let result = match req.JSONRPCMessage.params with
-
| Some params ->
+
let result =
+
match req.JSONRPCMessage.params with
+
| Some params ->
let req_data = Initialize.Request.t_of_yojson params in
-
Log.debugf "Client info: %s v%s"
-
req_data.client_info.name req_data.client_info.version;
+
Log.debugf "Client info: %s v%s" req_data.client_info.name
+
req_data.client_info.version;
Log.debugf "Client protocol version: %s" req_data.protocol_version;
-
+
(* Create initialize response *)
-
let result = Initialize.Result.create
-
~capabilities:(capabilities server)
-
~server_info:Implementation.{
-
name = name server;
-
version = version server
-
}
-
~protocol_version:(protocol_version server)
-
~instructions:(Printf.sprintf "This server provides tools for %s." (name server))
-
()
+
let result =
+
Initialize.Result.create ~capabilities:(capabilities server)
+
~server_info:
+
Implementation.{ name = name server; version = version server }
+
~protocol_version:(protocol_version server)
+
~instructions:
+
(Printf.sprintf "This server provides tools for %s." (name server))
+
()
in
Initialize.Result.yojson_of_t result
-
| None ->
+
| None ->
Log.error "Missing params for initialize request";
-
`Assoc [("error", `String "Missing params for initialize request")]
+
`Assoc [ ("error", `String "Missing params for initialize request") ]
in
Some (create_response ~id:req.id ~result)
(* Process tools/list request *)
-
let handle_tools_list server (req:JSONRPCMessage.request) =
+
let handle_tools_list server (req : JSONRPCMessage.request) =
Log.debug "Processing tools/list request";
let tools_list = Tool.to_rpc_tools_list (tools server) in
-
let response = Mcp_rpc.ToolsList.create_response ~id:req.id ~tools:tools_list () in
+
let response =
+
Mcp_rpc.ToolsList.create_response ~id:req.id ~tools:tools_list ()
+
in
Some response
(* Process prompts/list request *)
-
let handle_prompts_list server (req:JSONRPCMessage.request) =
+
let handle_prompts_list server (req : JSONRPCMessage.request) =
Log.debug "Processing prompts/list request";
let prompts_list = Prompt.to_rpc_prompts_list (prompts server) in
-
let response = Mcp_rpc.PromptsList.create_response ~id:req.id ~prompts:prompts_list () in
+
let response =
+
Mcp_rpc.PromptsList.create_response ~id:req.id ~prompts:prompts_list ()
+
in
Some response
(* Process resources/list request *)
-
let handle_resources_list server (req:JSONRPCMessage.request) =
+
let handle_resources_list server (req : JSONRPCMessage.request) =
Log.debug "Processing resources/list request";
let resources_list = Resource.to_rpc_resources_list (resources server) in
-
let response = Mcp_rpc.ResourcesList.create_response ~id:req.id ~resources:resources_list () in
+
let response =
+
Mcp_rpc.ResourcesList.create_response ~id:req.id ~resources:resources_list
+
()
+
in
Some response
(* Process resources/templates/list request *)
-
let handle_resource_templates_list server (req:JSONRPCMessage.request) =
+
let handle_resource_templates_list server (req : JSONRPCMessage.request) =
Log.debug "Processing resources/templates/list request";
-
let templates_list = ResourceTemplate.to_rpc_resource_templates_list (resource_templates server) in
-
let response = Mcp_rpc.ListResourceTemplatesResult.create_response ~id:req.id ~resource_templates:templates_list () in
+
let templates_list =
+
ResourceTemplate.to_rpc_resource_templates_list (resource_templates server)
+
in
+
let response =
+
Mcp_rpc.ListResourceTemplatesResult.create_response ~id:req.id
+
~resource_templates:templates_list ()
+
in
Some response
(* Utility module for resource template matching *)
···
(* Simple template variable extraction - could be enhanced with regex *)
let template_parts = String.split_on_char '/' template_uri in
let uri_parts = String.split_on_char '/' uri in
-
-
if List.length template_parts <> List.length uri_parts then
-
None
+
+
if List.length template_parts <> List.length uri_parts then None
else
(* Match parts and extract variables *)
let rec match_parts tparts uparts acc =
-
match tparts, uparts with
+
match (tparts, uparts) with
| [], [] -> Some (List.rev acc)
-
| th::tt, uh::ut ->
+
| th :: tt, uh :: ut ->
(* Check if this part is a template variable *)
-
if String.length th > 2 &&
-
String.get th 0 = '{' &&
-
String.get th (String.length th - 1) = '}' then
+
if
+
String.length th > 2
+
&& String.get th 0 = '{'
+
&& String.get th (String.length th - 1) = '}'
+
then
(* Extract variable value and continue *)
-
match_parts tt ut (uh::acc)
+
match_parts tt ut (uh :: acc)
else if th = uh then
(* Fixed part matches, continue *)
match_parts tt ut acc
···
(* Find a matching resource or template for a URI *)
let find_match server uri =
(* Try direct resource match first *)
-
match List.find_opt (fun resource -> resource.Resource.uri = uri) (resources server) with
+
match
+
List.find_opt
+
(fun resource -> resource.Resource.uri = uri)
+
(resources server)
+
with
| Some resource -> DirectResource (resource, [])
| None ->
(* Try template match next *)
let templates = resource_templates server in
-
+
(* Try each template to see if it matches *)
let rec try_templates templates =
match templates with
| [] -> NoMatch
-
| template::rest ->
-
match extract_template_vars template.ResourceTemplate.uri_template uri with
+
| template :: rest -> (
+
match
+
extract_template_vars template.ResourceTemplate.uri_template uri
+
with
| Some params -> TemplateResource (template, params)
-
| None -> try_templates rest
+
| None -> try_templates rest)
in
try_templates templates
end
(* Process resources/read request *)
-
let handle_resources_read server (req:JSONRPCMessage.request) =
+
let handle_resources_read server (req : JSONRPCMessage.request) =
Log.debug "Processing resources/read request";
match req.JSONRPCMessage.params with
| None ->
Log.error "Missing params for resources/read request";
-
Some (create_jsonrpc_error req.id ErrorCode.InvalidParams "Missing params for resources/read request" ())
-
| Some params ->
+
Some
+
(create_jsonrpc_error req.id ErrorCode.InvalidParams
+
"Missing params for resources/read request" ())
+
| Some params -> (
let req_data = Mcp_rpc.ResourcesRead.Request.t_of_yojson params in
let uri = req_data.uri in
Log.debugf "Resource URI: %s" uri;
-
+
(* Find matching resource or template *)
match Resource_matcher.find_match server uri with
-
| Resource_matcher.DirectResource (resource, params) ->
+
| Resource_matcher.DirectResource (resource, params) -> (
(* Create context for this request *)
-
let ctx = Context.create
-
?request_id:(Some req.id)
-
?progress_token:req.progress_token
-
~lifespan_context:[("resources/read", `Assoc [("uri", `String uri)])]
-
()
+
let ctx =
+
Context.create ?request_id:(Some req.id)
+
?progress_token:req.progress_token
+
~lifespan_context:
+
[ ("resources/read", `Assoc [ ("uri", `String uri) ]) ]
+
()
in
-
+
Log.debugf "Handling direct resource: %s" resource.name;
-
+
(* Call the resource handler *)
-
(match resource.handler ctx params with
-
| Ok content ->
-
(* Create text resource content *)
-
let mime_type = match resource.mime_type with
-
| Some mime -> mime
-
| None -> "text/plain"
-
in
-
let text_resource = {
-
TextResourceContents.uri;
-
text = content;
-
mime_type = Some mime_type
-
} in
-
let resource_content = Mcp_rpc.ResourcesRead.ResourceContent.TextResource text_resource in
-
let response = Mcp_rpc.ResourcesRead.create_response ~id:req.id ~contents:[resource_content] () in
-
Some response
-
| Error err ->
-
Log.errorf "Error reading resource: %s" err;
-
Some (create_jsonrpc_error req.id ErrorCode.InternalError ("Error reading resource: " ^ err) ()))
-
-
| Resource_matcher.TemplateResource (template, params) ->
+
match resource.handler ctx params with
+
| Ok content ->
+
(* Create text resource content *)
+
let mime_type =
+
match resource.mime_type with
+
| Some mime -> mime
+
| None -> "text/plain"
+
in
+
let text_resource =
+
{
+
TextResourceContents.uri;
+
text = content;
+
mime_type = Some mime_type;
+
}
+
in
+
let resource_content =
+
Mcp_rpc.ResourcesRead.ResourceContent.TextResource text_resource
+
in
+
let response =
+
Mcp_rpc.ResourcesRead.create_response ~id:req.id
+
~contents:[ resource_content ] ()
+
in
+
Some response
+
| Error err ->
+
Log.errorf "Error reading resource: %s" err;
+
Some
+
(create_jsonrpc_error req.id ErrorCode.InternalError
+
("Error reading resource: " ^ err)
+
()))
+
| Resource_matcher.TemplateResource (template, params) -> (
(* Create context for this request *)
-
let ctx = Context.create
-
?request_id:(Some req.id)
-
?progress_token:req.progress_token
-
~lifespan_context:[("resources/read", `Assoc [("uri", `String uri)])]
-
()
+
let ctx =
+
Context.create ?request_id:(Some req.id)
+
?progress_token:req.progress_token
+
~lifespan_context:
+
[ ("resources/read", `Assoc [ ("uri", `String uri) ]) ]
+
()
in
-
-
Log.debugf "Handling resource template: %s with params: [%s]"
-
template.name
+
+
Log.debugf "Handling resource template: %s with params: [%s]"
+
template.name
(String.concat ", " params);
-
+
(* Call the template handler *)
-
(match template.handler ctx params with
-
| Ok content ->
-
(* Create text resource content *)
-
let mime_type = match template.mime_type with
-
| Some mime -> mime
-
| None -> "text/plain"
-
in
-
let text_resource = {
-
TextResourceContents.uri;
-
text = content;
-
mime_type = Some mime_type
-
} in
-
let resource_content = Mcp_rpc.ResourcesRead.ResourceContent.TextResource text_resource in
-
let response = Mcp_rpc.ResourcesRead.create_response ~id:req.id ~contents:[resource_content] () in
-
Some response
-
| Error err ->
-
Log.errorf "Error reading resource template: %s" err;
-
Some (create_jsonrpc_error req.id ErrorCode.InternalError ("Error reading resource template: " ^ err) ()))
-
+
match template.handler ctx params with
+
| Ok content ->
+
(* Create text resource content *)
+
let mime_type =
+
match template.mime_type with
+
| Some mime -> mime
+
| None -> "text/plain"
+
in
+
let text_resource =
+
{
+
TextResourceContents.uri;
+
text = content;
+
mime_type = Some mime_type;
+
}
+
in
+
let resource_content =
+
Mcp_rpc.ResourcesRead.ResourceContent.TextResource text_resource
+
in
+
let response =
+
Mcp_rpc.ResourcesRead.create_response ~id:req.id
+
~contents:[ resource_content ] ()
+
in
+
Some response
+
| Error err ->
+
Log.errorf "Error reading resource template: %s" err;
+
Some
+
(create_jsonrpc_error req.id ErrorCode.InternalError
+
("Error reading resource template: " ^ err)
+
()))
| Resource_matcher.NoMatch ->
Log.errorf "Resource not found: %s" uri;
-
Some (create_jsonrpc_error req.id ErrorCode.InvalidParams ("Resource not found: " ^ uri) ())
+
Some
+
(create_jsonrpc_error req.id ErrorCode.InvalidParams
+
("Resource not found: " ^ uri)
+
()))
(* Extract the tool name from params *)
let extract_tool_name params =
match List.assoc_opt "name" params with
-
| Some (`String name) ->
-
Log.debugf "Tool name: %s" name;
-
Some name
-
| _ ->
-
Log.error "Missing or invalid 'name' parameter in tool call";
-
None
+
| Some (`String name) ->
+
Log.debugf "Tool name: %s" name;
+
Some name
+
| _ ->
+
Log.error "Missing or invalid 'name' parameter in tool call";
+
None
(* Extract the tool arguments from params *)
let extract_tool_arguments params =
match List.assoc_opt "arguments" params with
-
| Some (args) ->
-
Log.debugf "Tool arguments: %s" (Yojson.Safe.to_string args);
-
args
-
| _ ->
-
Log.debug "No arguments provided for tool call, using empty object";
-
`Assoc [] (* Empty arguments is valid *)
+
| Some args ->
+
Log.debugf "Tool arguments: %s" (Yojson.Safe.to_string args);
+
args
+
| _ ->
+
Log.debug "No arguments provided for tool call, using empty object";
+
`Assoc [] (* Empty arguments is valid *)
(* Execute a tool *)
let execute_tool server ctx name args =
try
let tool = List.find (fun t -> t.Tool.name = name) (tools server) in
Log.debugf "Found tool: %s" name;
-
+
(* Call the tool handler *)
match tool.handler ctx args with
-
| Ok result ->
+
| Ok result ->
Log.debug "Tool execution succeeded";
result
| Error err -> Tool.handle_execution_error err
···
(* Convert JSON tool result to RPC content format *)
let json_to_rpc_content json =
match json with
-
| `Assoc fields ->
-
(match List.assoc_opt "content" fields, List.assoc_opt "isError" fields with
-
| Some (`List content_items), Some (`Bool is_error) ->
-
let mcp_content = List.map Mcp.content_of_yojson content_items in
-
let rpc_content = Tool.mcp_content_to_rpc_content mcp_content in
-
(rpc_content, is_error)
-
| _ ->
-
(* Fallback for compatibility with older formats *)
-
let text = Yojson.Safe.to_string json in
-
let text_content = { TextContent.text = text; annotations = None } in
-
([Mcp_rpc.ToolsCall.ToolContent.Text text_content], false))
+
| `Assoc fields -> (
+
match
+
(List.assoc_opt "content" fields, List.assoc_opt "isError" fields)
+
with
+
| Some (`List content_items), Some (`Bool is_error) ->
+
let mcp_content = List.map Mcp.content_of_yojson content_items in
+
let rpc_content = Tool.mcp_content_to_rpc_content mcp_content in
+
(rpc_content, is_error)
+
| _ ->
+
(* Fallback for compatibility with older formats *)
+
let text = Yojson.Safe.to_string json in
+
let text_content = { TextContent.text; annotations = None } in
+
([ Mcp_rpc.ToolsCall.ToolContent.Text text_content ], false))
| _ ->
-
(* Simple fallback for non-object results *)
-
let text = Yojson.Safe.to_string json in
-
let text_content = { TextContent.text = text; annotations = None } in
-
([Mcp_rpc.ToolsCall.ToolContent.Text text_content], false)
+
(* Simple fallback for non-object results *)
+
let text = Yojson.Safe.to_string json in
+
let text_content = { TextContent.text; annotations = None } in
+
([ Mcp_rpc.ToolsCall.ToolContent.Text text_content ], false)
(* Process tools/call request *)
let handle_tools_call server req =
Log.debug "Processing tools/call request";
match req.JSONRPCMessage.params with
-
| Some (`Assoc params) ->
-
(match extract_tool_name params with
-
| Some name ->
-
let args = extract_tool_arguments params in
-
-
(* Create context for this request *)
-
let ctx = Context.create
-
?request_id:(Some req.id)
-
?progress_token:req.progress_token
-
~lifespan_context:[("tools/call", `Assoc params)]
-
()
-
in
-
-
(* Execute the tool *)
-
let result_json = execute_tool server ctx name args in
-
-
(* Convert JSON result to RPC format *)
-
let content, is_error = json_to_rpc_content result_json in
-
-
(* Create the RPC response *)
-
let response = Mcp_rpc.ToolsCall.create_response
-
~id:req.id
-
~content
-
~is_error
-
()
-
in
-
-
Some response
-
| None ->
-
Some (create_jsonrpc_error req.id InvalidParams "Missing tool name parameter" ()))
+
| Some (`Assoc params) -> (
+
match extract_tool_name params with
+
| Some name ->
+
let args = extract_tool_arguments params in
+
+
(* Create context for this request *)
+
let ctx =
+
Context.create ?request_id:(Some req.id)
+
?progress_token:req.progress_token
+
~lifespan_context:[ ("tools/call", `Assoc params) ]
+
()
+
in
+
+
(* Execute the tool *)
+
let result_json = execute_tool server ctx name args in
+
+
(* Convert JSON result to RPC format *)
+
let content, is_error = json_to_rpc_content result_json in
+
+
(* Create the RPC response *)
+
let response =
+
Mcp_rpc.ToolsCall.create_response ~id:req.id ~content ~is_error ()
+
in
+
+
Some response
+
| None ->
+
Some
+
(create_jsonrpc_error req.id InvalidParams
+
"Missing tool name parameter" ()))
| _ ->
-
Log.error "Invalid params format for tools/call";
-
Some (create_jsonrpc_error req.id InvalidParams "Invalid params format for tools/call" ())
+
Log.error "Invalid params format for tools/call";
+
Some
+
(create_jsonrpc_error req.id InvalidParams
+
"Invalid params format for tools/call" ())
(* Process ping request *)
-
let handle_ping (req:JSONRPCMessage.request) =
+
let handle_ping (req : JSONRPCMessage.request) =
Log.debug "Processing ping request";
Some (create_response ~id:req.JSONRPCMessage.id ~result:(`Assoc []))
(* Handle notifications/initialized *)
-
let handle_initialized (notif:JSONRPCMessage.notification) =
-
Log.debug "Client initialization complete - Server is now ready to receive requests";
-
Log.debugf "Notification params: %s"
+
let handle_initialized (notif : JSONRPCMessage.notification) =
+
Log.debug
+
"Client initialization complete - Server is now ready to receive requests";
+
Log.debugf "Notification params: %s"
(match notif.JSONRPCMessage.params with
-
| Some p -> Yojson.Safe.to_string p
-
| None -> "null");
+
| Some p -> Yojson.Safe.to_string p
+
| None -> "null");
None
(* Process a single message using the MCP SDK *)
···
try
Log.debugf "Processing message: %s" (Yojson.Safe.to_string message);
match JSONRPCMessage.t_of_yojson message with
-
| JSONRPCMessage.Request req ->
-
Log.debugf "Received request with method: %s" (Method.to_string req.meth);
-
(match req.meth with
-
| Method.Initialize -> handle_initialize server req
-
| Method.ToolsList -> handle_tools_list server req
-
| Method.ToolsCall -> handle_tools_call server req
-
| Method.PromptsList -> handle_prompts_list server req
-
| Method.ResourcesList -> handle_resources_list server req
-
| Method.ResourcesRead -> handle_resources_read server req
-
| Method.ResourceTemplatesList -> handle_resource_templates_list server req
-
| _ ->
-
Log.errorf "Unknown method received: %s" (Method.to_string req.meth);
-
Some (create_jsonrpc_error req.id ErrorCode.MethodNotFound ("Method not found: " ^ (Method.to_string req.meth)) ()))
-
| JSONRPCMessage.Notification notif ->
-
Log.debugf "Received notification with method: %s" (Method.to_string notif.meth);
-
(match notif.meth with
-
| Method.Initialized -> handle_initialized notif
-
| _ ->
-
Log.debugf "Ignoring notification: %s" (Method.to_string notif.meth);
-
None)
+
| JSONRPCMessage.Request req -> (
+
Log.debugf "Received request with method: %s"
+
(Method.to_string req.meth);
+
match req.meth with
+
| Method.Initialize -> handle_initialize server req
+
| Method.ToolsList -> handle_tools_list server req
+
| Method.ToolsCall -> handle_tools_call server req
+
| Method.PromptsList -> handle_prompts_list server req
+
| Method.ResourcesList -> handle_resources_list server req
+
| Method.ResourcesRead -> handle_resources_read server req
+
| Method.ResourceTemplatesList ->
+
handle_resource_templates_list server req
+
| _ ->
+
Log.errorf "Unknown method received: %s" (Method.to_string req.meth);
+
Some
+
(create_jsonrpc_error req.id ErrorCode.MethodNotFound
+
("Method not found: " ^ Method.to_string req.meth)
+
()))
+
| JSONRPCMessage.Notification notif -> (
+
Log.debugf "Received notification with method: %s"
+
(Method.to_string notif.meth);
+
match notif.meth with
+
| Method.Initialized -> handle_initialized notif
+
| _ ->
+
Log.debugf "Ignoring notification: %s" (Method.to_string notif.meth);
+
None)
| JSONRPCMessage.Response _ ->
-
Log.error "Unexpected response message received";
-
None
+
Log.error "Unexpected response message received";
+
None
| JSONRPCMessage.Error _ ->
-
Log.error "Unexpected error message received";
-
None
+
Log.error "Unexpected error message received";
+
None
with
| Json.Of_json (msg, _) ->
-
Log.errorf "JSON error: %s" msg;
-
(* Can't respond with error because we don't have a request ID *)
-
None
-
| Yojson.Json_error msg ->
-
Log.errorf "JSON parse error: %s" msg;
-
(* Can't respond with error because we don't have a request ID *)
-
None
+
Log.errorf "JSON error: %s" msg;
+
(* Can't respond with error because we don't have a request ID *)
+
None
+
| Yojson.Json_error msg ->
+
Log.errorf "JSON parse error: %s" msg;
+
(* Can't respond with error because we don't have a request ID *)
+
None
| exc ->
-
Log.errorf "Exception during message processing: %s" (Printexc.to_string exc);
-
Log.errorf "Backtrace: %s" (Printexc.get_backtrace());
-
Log.errorf "Message was: %s" (Yojson.Safe.to_string message);
-
None
+
Log.errorf "Exception during message processing: %s"
+
(Printexc.to_string exc);
+
Log.errorf "Backtrace: %s" (Printexc.get_backtrace ());
+
Log.errorf "Message was: %s" (Yojson.Safe.to_string message);
+
None
(* Extract a request ID from a potentially malformed message *)
let extract_request_id json =
try
match json with
-
| `Assoc fields ->
-
(match List.assoc_opt "id" fields with
-
| Some (`Int id) -> Some (`Int id)
-
| Some (`String id) -> Some (`String id)
-
| _ -> None)
+
| `Assoc fields -> (
+
match List.assoc_opt "id" fields with
+
| Some (`Int id) -> Some (`Int id)
+
| Some (`String id) -> Some (`String id)
+
| _ -> None)
| _ -> None
with _ -> None
···
let process_input_line server line =
if line = "" then (
Log.debug "Empty line received, ignoring";
-
None
-
) else (
+
None)
+
else (
Log.debugf "Raw input: %s" line;
try
let json = Yojson.Safe.from_string line in
Log.debug "Successfully parsed JSON";
-
+
(* Process the message *)
process_message server json
-
with
-
| Yojson.Json_error msg -> begin
-
Log.errorf "Error parsing JSON: %s" msg;
-
Log.errorf "Input was: %s" line;
-
None
-
end
-
)
+
with Yojson.Json_error msg ->
+
Log.errorf "Error parsing JSON: %s" msg;
+
Log.errorf "Input was: %s" line;
+
None)
(* Send a response to the client *)
let send_response stdout response =
let response_json = JSONRPCMessage.yojson_of_t response in
let response_str = Yojson.Safe.to_string response_json in
Log.debugf "Sending response: %s" response_str;
-
+
(* Write the response followed by a newline *)
Eio.Flow.copy_string response_str stdout;
Eio.Flow.copy_string "\n" stdout
···
()
| None ->
Log.debug "No MCP response needed";
-
Cohttp_eio.Server.respond ~status:`No_content ~body:(Cohttp_eio.Body.of_string "") ())
+
Cohttp_eio.Server.respond ~status:`No_content
+
~body:(Cohttp_eio.Body.of_string "")
+
())
| _ ->
Log.infof "Unsupported method: %s" (Http.Method.to_string request.meth);
Cohttp_eio.Server.respond ~status:`Method_not_allowed
···
(* Enable exception backtraces *)
Printexc.record_backtrace true;
-
+
let buf = Eio.Buf_read.of_flow stdin ~initial_size:100 ~max_size:1_000_000 in
-
+
(* Main processing loop *)
try
while true do
Log.debug "Waiting for message...";
let line = Eio.Buf_read.line buf in
-
+
(* Process the input and send response if needed *)
match process_input_line server line with
| Some response -> send_response stdout response
| None -> Log.debug "No response needed for this message"
done
with
-
| End_of_file ->
+
| End_of_file ->
Log.debug "End of file received on stdin";
()
| Eio.Exn.Io _ as exn ->