Model Context Protocol in OCaml

Compare changes

Choose any two refs to compare.

-1
.gitignore
···
jsonrpc.mli
_build
CLAUDE.md
-
*.install
.ocamlformat

This is a binary file and will not be displayed.

+330
bin/audio_example.ml
···
+
open Mcp
+
open Mcp_sdk
+
open Mcp_server
+
+
(* WAV file format helper module *)
+
module Wav = struct
+
(* Simple WAV file generation for a sine wave *)
+
let generate_sine_wave ~frequency ~duration ~sample_rate ~amplitude =
+
(* WAV parameters *)
+
let num_channels = 1 in (* Mono *)
+
let bits_per_sample = 16 in
+
let byte_rate = sample_rate * num_channels * bits_per_sample / 8 in
+
let block_align = num_channels * bits_per_sample / 8 in
+
let num_samples = int_of_float (float_of_int sample_rate *. duration) in
+
let data_size = num_samples * block_align in
+
+
(* Create buffer for the WAV data *)
+
let buffer = Buffer.create (44 + data_size) in
+
+
(* Write WAV header *)
+
(* "RIFF" chunk *)
+
Buffer.add_string buffer "RIFF";
+
let file_size = 36 + data_size in
+
Buffer.add_char buffer (char_of_int (file_size land 0xff));
+
Buffer.add_char buffer (char_of_int ((file_size lsr 8) land 0xff));
+
Buffer.add_char buffer (char_of_int ((file_size lsr 16) land 0xff));
+
Buffer.add_char buffer (char_of_int ((file_size lsr 24) land 0xff));
+
Buffer.add_string buffer "WAVE";
+
+
(* "fmt " sub-chunk *)
+
Buffer.add_string buffer "fmt ";
+
Buffer.add_char buffer (char_of_int 16); (* Sub-chunk size (16 for PCM) *)
+
Buffer.add_char buffer (char_of_int 0);
+
Buffer.add_char buffer (char_of_int 0);
+
Buffer.add_char buffer (char_of_int 0);
+
Buffer.add_char buffer (char_of_int 1); (* Audio format (1 for PCM) *)
+
Buffer.add_char buffer (char_of_int 0);
+
Buffer.add_char buffer (char_of_int num_channels); (* Number of channels *)
+
Buffer.add_char buffer (char_of_int 0);
+
+
(* Sample rate *)
+
Buffer.add_char buffer (char_of_int (sample_rate land 0xff));
+
Buffer.add_char buffer (char_of_int ((sample_rate lsr 8) land 0xff));
+
Buffer.add_char buffer (char_of_int ((sample_rate lsr 16) land 0xff));
+
Buffer.add_char buffer (char_of_int ((sample_rate lsr 24) land 0xff));
+
+
(* Byte rate *)
+
Buffer.add_char buffer (char_of_int (byte_rate land 0xff));
+
Buffer.add_char buffer (char_of_int ((byte_rate lsr 8) land 0xff));
+
Buffer.add_char buffer (char_of_int ((byte_rate lsr 16) land 0xff));
+
Buffer.add_char buffer (char_of_int ((byte_rate lsr 24) land 0xff));
+
+
(* Block align *)
+
Buffer.add_char buffer (char_of_int block_align);
+
Buffer.add_char buffer (char_of_int 0);
+
+
(* Bits per sample *)
+
Buffer.add_char buffer (char_of_int bits_per_sample);
+
Buffer.add_char buffer (char_of_int 0);
+
+
(* "data" sub-chunk *)
+
Buffer.add_string buffer "data";
+
Buffer.add_char buffer (char_of_int (data_size land 0xff));
+
Buffer.add_char buffer (char_of_int ((data_size lsr 8) land 0xff));
+
Buffer.add_char buffer (char_of_int ((data_size lsr 16) land 0xff));
+
Buffer.add_char buffer (char_of_int ((data_size lsr 24) land 0xff));
+
+
(* Generate sine wave data *)
+
let max_amplitude = float_of_int (1 lsl (bits_per_sample - 1)) -. 1.0 in
+
for i = 0 to num_samples - 1 do
+
let t = float_of_int i /. float_of_int sample_rate in
+
let value = int_of_float (amplitude *. max_amplitude *. sin (2.0 *. Float.pi *. frequency *. t)) in
+
(* Write 16-bit sample (little-endian) *)
+
Buffer.add_char buffer (char_of_int (value land 0xff));
+
Buffer.add_char buffer (char_of_int ((value lsr 8) land 0xff));
+
done;
+
+
Buffer.contents buffer
+
+
(* Encode binary data as base64 *)
+
let base64_encode data =
+
let buffer = Buffer.create (4 * (String.length data + 2) / 3) in
+
let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" in
+
+
let encode_block i bytes =
+
let b1 = Char.code (String.get bytes (i * 3)) in
+
let b2 = if i * 3 + 1 < String.length bytes then Char.code (String.get bytes (i * 3 + 1)) else 0 in
+
let b3 = if i * 3 + 2 < String.length bytes then Char.code (String.get bytes (i * 3 + 2)) else 0 in
+
+
let n = (b1 lsl 16) lor (b2 lsl 8) lor b3 in
+
Buffer.add_char buffer (String.get alphabet ((n lsr 18) land 63));
+
Buffer.add_char buffer (String.get alphabet ((n lsr 12) land 63));
+
+
if i * 3 + 1 < String.length bytes then
+
Buffer.add_char buffer (String.get alphabet ((n lsr 6) land 63))
+
else
+
Buffer.add_char buffer '=';
+
+
if i * 3 + 2 < String.length bytes then
+
Buffer.add_char buffer (String.get alphabet (n land 63))
+
else
+
Buffer.add_char buffer '=';
+
in
+
+
for i = 0 to (String.length data + 2) / 3 - 1 do
+
encode_block i data
+
done;
+
+
Buffer.contents buffer
+
end
+
+
(* 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)))
+
| _ -> raise (Failure "Expected JSON object")
+
+
(* Create a server *)
+
let server = create_server
+
~name:"OCaml MCP Audio Example"
+
~version:"0.1.0"
+
~protocol_version:"2024-11-05"
+
()
+
+
(* Define startup and shutdown hooks *)
+
let startup () =
+
(* Use stderr for direct printing to avoid interfering with JSON-RPC protocol *)
+
Printf.fprintf stderr "AudioExampleServer is starting up!\n";
+
flush stderr;
+
Log.info "AudioExampleServer is starting up!"
+
+
let shutdown () =
+
Printf.fprintf stderr "AudioExampleServer is shutting down. Goodbye!\n";
+
flush stderr;
+
Log.info "AudioExampleServer is shutting down. Goodbye!"
+
+
(* Register the hooks *)
+
let () =
+
set_startup_hook server startup;
+
set_shutdown_hook server shutdown
+
+
(* Helper to create audio content *)
+
let make_audio_content data mime_type =
+
let audio_content = AudioContent.{
+
data;
+
mime_type;
+
annotations = None;
+
} in
+
Audio audio_content
+
+
(* Define and register an audio tool *)
+
let _ = add_tool server
+
~name:"generate_audio_description"
+
~description:"Generates a description with an audio sample"
+
~schema_properties:[
+
("text", "string", "The text to describe with audio");
+
("frequency", "number", "The frequency in Hz for the tone (optional)");
+
("duration", "number", "The duration in seconds for the tone (optional)");
+
("amplitude", "number", "The amplitude (0.0-1.0) for the tone (optional)");
+
]
+
~schema_required:["text"]
+
(fun args ->
+
try
+
let text = get_string_param args "text" in
+
+
(* Parse parameters with defaults *)
+
let frequency =
+
try
+
match List.assoc_opt "frequency" (match args with `Assoc l -> l | _ -> []) with
+
| Some (`Int f) -> float_of_int f
+
| Some (`Float f) -> f
+
| _ -> 440.0 (* Default to A440 *)
+
with _ -> 440.0
+
in
+
+
let duration =
+
try
+
match List.assoc_opt "duration" (match args with `Assoc l -> l | _ -> []) with
+
| Some (`Int d) -> float_of_int d
+
| Some (`Float d) -> d
+
| _ -> 2.0 (* Default to 2 seconds *)
+
with _ -> 2.0
+
in
+
+
let amplitude =
+
try
+
match List.assoc_opt "amplitude" (match args with `Assoc l -> l | _ -> []) with
+
| Some (`Int a) -> float_of_int a
+
| Some (`Float a) -> a
+
| _ -> 0.8 (* Default to 80% amplitude *)
+
with _ -> 0.8
+
in
+
+
(* Generate WAV file for the tone *)
+
let sample_rate = 44100 in (* CD quality *)
+
let wav_data = Wav.generate_sine_wave
+
~frequency
+
~duration
+
~sample_rate
+
~amplitude
+
in
+
+
(* Encode WAV data as base64 *)
+
let base64_audio = Wav.base64_encode wav_data in
+
+
Log.info (Printf.sprintf "Generated %d Hz tone for %.1f seconds (%.1f KB)"
+
(int_of_float frequency) duration
+
(float_of_int (String.length wav_data) /. 1024.0));
+
+
(* Create a response with both text and audio content *)
+
CallToolResult.yojson_of_t CallToolResult.{
+
content = [
+
Text TextContent.{
+
text = Printf.sprintf "Description: %s (with %.1f Hz tone for %.1f seconds)"
+
text frequency duration;
+
annotations = None
+
};
+
Audio AudioContent.{
+
data = base64_audio;
+
mime_type = "audio/wav";
+
annotations = None
+
}
+
];
+
is_error = false;
+
meta = None
+
}
+
with
+
| Failure msg ->
+
Log.error (Printf.sprintf "Error in audio tool: %s" msg);
+
CallToolResult.yojson_of_t CallToolResult.{
+
content = [
+
Text TextContent.{
+
text = Printf.sprintf "Error: %s" msg;
+
annotations = None
+
}
+
];
+
is_error = true;
+
meta = None
+
}
+
)
+
+
(* Define and register a prompt example with audio *)
+
let _ = add_prompt server
+
~name:"audio-description-prompt"
+
~description:"A prompt with audio and text content"
+
~arguments:[
+
("description", Some "Text description to accompany the audio", true);
+
("frequency", Some "Frequency in Hz for the audio tone", false);
+
("duration", Some "Duration in seconds for the audio tone", false);
+
]
+
(fun args ->
+
let description =
+
try List.assoc "description" args
+
with Not_found -> "No description provided"
+
in
+
+
(* Parse frequency with default *)
+
let frequency =
+
try float_of_string (List.assoc "frequency" args)
+
with _ -> 440.0 (* Default to A440 *)
+
in
+
+
(* Parse duration with default *)
+
let duration =
+
try float_of_string (List.assoc "duration" args)
+
with _ -> 3.0 (* Default to 3 seconds *)
+
in
+
+
(* Generate WAV data *)
+
let sample_rate = 44100 in
+
let wav_data = Wav.generate_sine_wave
+
~frequency
+
~duration
+
~sample_rate
+
~amplitude:0.8
+
in
+
+
(* Encode WAV data as base64 *)
+
let base64_audio = Wav.base64_encode wav_data in
+
+
Log.info (Printf.sprintf "Generated %.1f Hz tone for prompt (%.1f seconds, %.1f KB)"
+
frequency duration
+
(float_of_int (String.length wav_data) /. 1024.0));
+
+
[
+
Prompt.{
+
role = `User;
+
content = make_text_content "Here's a sound sample with description:"
+
};
+
Prompt.{
+
role = `User;
+
content = make_audio_content base64_audio "audio/wav"
+
};
+
Prompt.{
+
role = `User;
+
content = make_text_content (Printf.sprintf "%s (%.1f Hz tone for %.1f seconds)"
+
description frequency duration)
+
};
+
Prompt.{
+
role = `Assistant;
+
content = make_text_content "I've received your audio file and description."
+
}
+
]
+
)
+
+
(* Main function *)
+
let () =
+
(* Parse command line arguments *)
+
let transport_type = ref Mcp_server.Stdio in
+
let args = [
+
("--http", Arg.Unit (fun () -> transport_type := Mcp_server.Http),
+
"Start server with HTTP transport (default is stdio)");
+
] in
+
let usage_msg = "Usage: audio_example [--http]" in
+
Arg.parse args (fun _ -> ()) usage_msg;
+
+
(* Use stderr for direct printing to avoid interfering with JSON-RPC protocol *)
+
Printf.fprintf stderr "Starting AudioExampleServer...\n";
+
flush stderr;
+
Log.info "Starting AudioExampleServer...";
+
+
(* Configure the server with appropriate capabilities *)
+
ignore (configure_server server ());
+
+
(* Create and start MCP server with the selected transport *)
+
let mcp_server = Mcp_server.create ~server ~transport:!transport_type () in
+
Mcp_server.start mcp_server
+113 -68
bin/capitalize_sdk.ml
···
open Mcp
open Mcp_sdk
+
open Mcp_server
(* 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 ->
-
(* Set default capabilities *)
-
configure_server server ~with_tools:true ~with_resources:true
-
~with_prompts:true ()
+
let server = create_server
+
~name:"OCaml MCP Capitalizer"
+
~version:"0.1.0"
+
~protocol_version:"2024-11-05"
+
()
+
+
(* Define startup and shutdown hooks *)
+
let startup () =
+
(* Use stderr for direct printing to avoid interfering with JSON-RPC protocol *)
+
Printf.fprintf stderr "CapitalizeServer is starting up!\n";
+
flush stderr;
+
Log.info "CapitalizeServer is starting up!"
+
+
let shutdown () =
+
Printf.fprintf stderr "CapitalizeServer is shutting down. Goodbye!\n";
+
flush stderr;
+
Log.info "CapitalizeServer is shutting down. Goodbye!"
+
+
(* Register the hooks *)
+
let () =
+
set_startup_hook server startup;
+
set_shutdown_hook server shutdown
(* 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 ->
-
Logs.err (fun m -> m "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.error (Printf.sprintf "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.")
+
(* Define and register a resource example *)
+
let _ = add_resource server
+
~uri_template:"greeting://{name}"
+
~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 = make_text_content "Please help me capitalize the following text:"
+
};
+
Prompt.{
+
role = `User;
+
content = make_text_content text
+
};
+
Prompt.{
+
role = `Assistant;
+
content = make_text_content "Here's the capitalized version:"
+
};
+
Prompt.{
+
role = `Assistant;
+
content = make_text_content (String.uppercase_ascii text)
+
}
+
]
+
)
+
(* Main function *)
let () =
-
Logs.set_reporter (Logs.format_reporter ());
-
Eio_main.run @@ fun env -> Mcp_server.run_stdio_server env server
+
(* Parse command line arguments *)
+
let transport_type = ref Stdio in
+
let args = [
+
("--http", Arg.Unit (fun () -> transport_type := Http),
+
"Start server with HTTP transport (default is stdio)");
+
] in
+
let usage_msg = "Usage: capitalize_sdk [--http]" in
+
Arg.parse args (fun _ -> ()) usage_msg;
+
+
(* Use stderr for direct printing to avoid interfering with JSON-RPC protocol *)
+
Printf.fprintf stderr "Starting CapitalizeServer...\n";
+
flush stderr;
+
Log.info "Starting CapitalizeServer...";
+
+
(* Configure the server with appropriate capabilities *)
+
ignore (configure_server server ());
+
+
(* Create and start MCP server with the selected transport *)
+
let mcp_server = create ~server ~transport:!transport_type () in
+
start mcp_server
+193
bin/completion_example.ml
···
+
open Mcp
+
open Mcp_sdk
+
open Mcp_server
+
+
(* 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)))
+
| _ -> raise (Failure "Expected JSON object")
+
+
(* Create a server *)
+
let server = create_server
+
~name:"OCaml MCP Completion Example"
+
~version:"0.1.0"
+
~protocol_version:"2024-11-05"
+
()
+
+
(* Define startup and shutdown hooks *)
+
let startup () =
+
(* Use stderr for direct printing to avoid interfering with JSON-RPC protocol *)
+
Printf.fprintf stderr "CompletionServer is starting up!\n";
+
flush stderr;
+
Log.info "CompletionServer is starting up!"
+
+
let shutdown () =
+
Printf.fprintf stderr "CompletionServer is shutting down. Goodbye!\n";
+
flush stderr;
+
Log.info "CompletionServer is shutting down. Goodbye!"
+
+
(* Register the hooks *)
+
let () =
+
set_startup_hook server startup;
+
set_shutdown_hook server shutdown
+
+
(* Database of programming languages and their features *)
+
let languages = [
+
("ocaml", ["functional"; "static typing"; "pattern matching"; "modules"; "type inference"]);
+
("python", ["dynamic typing"; "interpreted"; "object-oriented"; "high-level"; "scripting"]);
+
("rust", ["memory safety"; "performance"; "static typing"; "ownership"; "zero-cost abstractions"]);
+
("javascript", ["dynamic typing"; "interpreted"; "prototypes"; "single-threaded"; "event-driven"]);
+
("go", ["concurrency"; "garbage collection"; "simplicity"; "static typing"; "compiled"]);
+
]
+
+
(* Helper function to create a completion response *)
+
let create_completion values ?(has_more=false) ?(total=None) () =
+
Completion.Result.{
+
completion = {
+
values;
+
has_more = Some has_more;
+
total;
+
};
+
meta = None;
+
}
+
+
(* Define and register a tool that handles completions *)
+
let _ = add_tool server
+
~name:"complete"
+
~description:"Handles completion requests for programming languages and features"
+
~schema_properties:[
+
("argument_name", "string", "The name of the argument to complete");
+
("argument_value", "string", "The partial value to complete");
+
]
+
~schema_required:["argument_name"; "argument_value"]
+
(fun args ->
+
try
+
let argument_name = get_string_param args "argument_name" in
+
let argument_value = get_string_param args "argument_value" in
+
+
Log.info (Printf.sprintf "Completion request for %s = %s" argument_name argument_value);
+
+
(* Handle different completion requests *)
+
let result =
+
match argument_name with
+
| "language" ->
+
(* Complete programming language names *)
+
let matches =
+
List.filter
+
(fun (lang, _) ->
+
let lang_lower = String.lowercase_ascii lang in
+
let arg_lower = String.lowercase_ascii argument_value in
+
String.length lang_lower >= String.length arg_lower &&
+
String.sub lang_lower 0 (String.length arg_lower) = arg_lower)
+
languages
+
in
+
let values = List.map fst matches in
+
create_completion values ~has_more:false ~total:(Some (List.length values)) ()
+
+
| "feature" ->
+
(* Complete programming language features *)
+
let all_features =
+
List.flatten (List.map snd languages) |>
+
List.sort_uniq String.compare
+
in
+
let matches =
+
List.filter
+
(fun feature ->
+
let feature_lower = String.lowercase_ascii feature in
+
let arg_lower = String.lowercase_ascii argument_value in
+
String.length feature_lower >= String.length arg_lower &&
+
String.sub feature_lower 0 (String.length arg_lower) = arg_lower)
+
all_features
+
in
+
create_completion matches ~has_more:false ~total:(Some (List.length matches)) ()
+
+
| _ ->
+
(* Default completions for unknown arguments *)
+
create_completion ["unknown argument"] ()
+
in
+
+
(* Convert to JSON and return *)
+
TextContent.yojson_of_t TextContent.{
+
text = Yojson.Safe.to_string (Completion.Result.to_result result);
+
annotations = None
+
}
+
with
+
| Failure msg ->
+
Log.error (Printf.sprintf "Error handling completion request: %s" msg);
+
TextContent.yojson_of_t TextContent.{
+
text = Printf.sprintf "Error: %s" msg;
+
annotations = None
+
}
+
)
+
+
(* Define and register a prompt that provides programming language info *)
+
let _ = add_prompt server
+
~name:"language-info-prompt"
+
~description:"A prompt that provides information about programming languages"
+
~arguments:[
+
("language", Some "Name of the programming language", true);
+
]
+
(fun args ->
+
let language =
+
try List.assoc "language" args
+
with Not_found -> "ocaml" (* Default to OCaml *)
+
in
+
+
let features =
+
try
+
let features = List.assoc (String.lowercase_ascii language) languages in
+
String.concat ", " features
+
with Not_found -> "unknown language"
+
in
+
+
[
+
Prompt.{
+
role = `User;
+
content = make_text_content (Printf.sprintf "Tell me about the %s programming language" language)
+
};
+
Prompt.{
+
role = `Assistant;
+
content = make_text_content (Printf.sprintf "%s is a programming language with the following features: %s" language features)
+
}
+
]
+
)
+
+
(* Main function *)
+
let () =
+
(* Parse command line arguments *)
+
let transport_type = ref Stdio in
+
let args = [
+
("--http", Arg.Unit (fun () -> transport_type := Http),
+
"Start server with HTTP transport (default is stdio)");
+
] in
+
let usage_msg = "Usage: completion_example [--http]" in
+
Arg.parse args (fun _ -> ()) usage_msg;
+
+
(* Use stderr for direct printing to avoid interfering with JSON-RPC protocol *)
+
Printf.fprintf stderr "Starting CompletionServer...\n";
+
flush stderr;
+
Log.info "Starting CompletionServer...";
+
+
(* Set custom capabilities to indicate support for completions *)
+
let capabilities = `Assoc [
+
("completions", `Assoc []); (* Indicate support for completions *)
+
("prompts", `Assoc [
+
("listChanged", `Bool true)
+
]);
+
("resources", `Assoc [
+
("listChanged", `Bool true);
+
("subscribe", `Bool true)
+
]);
+
("tools", `Assoc [
+
("listChanged", `Bool true)
+
])
+
] in
+
set_capabilities server capabilities;
+
+
(* Create and start MCP server with the selected transport *)
+
let mcp_server = create ~server ~transport:!transport_type () in
+
start mcp_server
+33 -21
bin/dune
···
+
(executable
+
(name server)
+
(libraries mcp yojson unix)
+
(flags (:standard -w -8-11)))
+
(executable
(name capitalize_sdk)
(modules capitalize_sdk)
-
(libraries logs mcp mcp_server yojson eio_main eio))
+
(libraries mcp mcp_sdk mcp_server yojson unix)
+
(flags (:standard -w -33)))
(executable
-
(name multimodal_sdk)
-
(modules multimodal_sdk)
-
(libraries logs mcp mcp_sdk mcp_server yojson eio_main eio))
+
(name audio_example)
+
(modules audio_example)
+
(libraries mcp mcp_sdk mcp_server yojson unix)
+
(flags (:standard -w -33)))
(executable
-
(name ocaml_eval_sdk)
-
(modes byte)
-
(modules ocaml_eval_sdk)
-
(flags
-
(:standard -w -32 -w -33))
-
(libraries
-
logs
-
mcp
-
mcp_sdk
-
mcp_server
-
yojson
-
eio_main
-
eio
-
compiler-libs.toplevel))
+
(name resource_template_example)
+
(modules resource_template_example)
+
(libraries mcp mcp_sdk mcp_server yojson unix)
+
(flags (:standard -w -33)))
+
+
(executable
+
(name completion_example)
+
(modules completion_example)
+
(libraries mcp mcp_sdk mcp_server yojson unix)
+
(flags (:standard -w -33)))
+
+
+
(executable
+
(name image_generator_example)
+
(modules image_generator_example)
+
(libraries mcp mcp_sdk mcp_server yojson unix)
+
(flags (:standard -w -33)))
(executable
-
(name markdown_book_sdk)
-
(modules markdown_book_sdk)
-
(libraries logs mcp mcp_sdk mcp_server yojson eio_main eio))
+
(name multimodal_example)
+
(modules multimodal_example)
+
(libraries mcp mcp_sdk mcp_server yojson unix)
+
(flags (:standard -w -33 -w -32)))
+
+468
bin/image_generator_example.ml
···
+
open Mcp
+
open Mcp_sdk
+
open Mcp_server
+
+
(* Random pixel image generator MCP server *)
+
+
(* Base64 encoding helper *)
+
module Base64 = struct
+
let encode_char n =
+
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".[n]
+
+
let encode_block i bytes =
+
let buffer = Buffer.create 4 in
+
let b1 = Char.code (String.get bytes (i * 3)) in
+
let b2 = if i * 3 + 1 < String.length bytes then Char.code (String.get bytes (i * 3 + 1)) else 0 in
+
let b3 = if i * 3 + 2 < String.length bytes then Char.code (String.get bytes (i * 3 + 2)) else 0 in
+
+
let n = (b1 lsl 16) lor (b2 lsl 8) lor b3 in
+
Buffer.add_char buffer (encode_char ((n lsr 18) land 63));
+
Buffer.add_char buffer (encode_char ((n lsr 12) land 63));
+
+
if i * 3 + 1 < String.length bytes then
+
Buffer.add_char buffer (encode_char ((n lsr 6) land 63))
+
else
+
Buffer.add_char buffer '=';
+
+
if i * 3 + 2 < String.length bytes then
+
Buffer.add_char buffer (encode_char (n land 63))
+
else
+
Buffer.add_char buffer '=';
+
+
Buffer.contents buffer
+
+
let encode data =
+
let buffer = Buffer.create (4 * (String.length data + 2) / 3) in
+
for i = 0 to (String.length data - 1) / 3 do
+
Buffer.add_string buffer (encode_block i data)
+
done;
+
Buffer.contents buffer
+
end
+
+
(* Image generation utilities *)
+
module ImageGenerator = struct
+
(* Simple PNG generation *)
+
let create_png width height pixels =
+
(* PNG signature *)
+
let signature = [|137; 80; 78; 71; 13; 10; 26; 10|] in
+
+
(* IHDR chunk data *)
+
let ihdr_data = Bytes.create 13 in
+
(* Width - big endian *)
+
Bytes.set ihdr_data 0 (Char.chr ((width lsr 24) land 0xff));
+
Bytes.set ihdr_data 1 (Char.chr ((width lsr 16) land 0xff));
+
Bytes.set ihdr_data 2 (Char.chr ((width lsr 8) land 0xff));
+
Bytes.set ihdr_data 3 (Char.chr (width land 0xff));
+
(* Height - big endian *)
+
Bytes.set ihdr_data 4 (Char.chr ((height lsr 24) land 0xff));
+
Bytes.set ihdr_data 5 (Char.chr ((height lsr 16) land 0xff));
+
Bytes.set ihdr_data 6 (Char.chr ((height lsr 8) land 0xff));
+
Bytes.set ihdr_data 7 (Char.chr (height land 0xff));
+
(* Bit depth - 8 bits *)
+
Bytes.set ihdr_data 8 (Char.chr 8);
+
(* Color type - RGB with alpha *)
+
Bytes.set ihdr_data 9 (Char.chr 6);
+
(* Compression, filter, interlace - all 0 *)
+
Bytes.set ihdr_data 10 (Char.chr 0);
+
Bytes.set ihdr_data 11 (Char.chr 0);
+
Bytes.set ihdr_data 12 (Char.chr 0);
+
+
(* Very simple CRC32 implementation for PNG chunks *)
+
let calculate_crc data =
+
let crc = ref 0xffffffff in
+
for i = 0 to Bytes.length data - 1 do
+
let byte = Char.code (Bytes.get data i) in
+
crc := !crc lxor byte;
+
for _ = 0 to 7 do
+
if !crc land 1 <> 0 then
+
crc := (!crc lsr 1) lxor 0xedb88320
+
else
+
crc := !crc lsr 1
+
done
+
done;
+
!crc lxor 0xffffffff
+
in
+
+
(* Create IHDR chunk *)
+
let ihdr_chunk = Buffer.create 25 in
+
(* Length - 13 bytes *)
+
Buffer.add_char ihdr_chunk (Char.chr 0);
+
Buffer.add_char ihdr_chunk (Char.chr 0);
+
Buffer.add_char ihdr_chunk (Char.chr 0);
+
Buffer.add_char ihdr_chunk (Char.chr 13);
+
(* Chunk type - IHDR *)
+
Buffer.add_string ihdr_chunk "IHDR";
+
(* Chunk data *)
+
Buffer.add_string ihdr_chunk (Bytes.unsafe_to_string ihdr_data);
+
(* CRC *)
+
let ihdr_crc_data = Bytes.create 17 in
+
Bytes.blit_string "IHDR" 0 ihdr_crc_data 0 4;
+
Bytes.blit ihdr_data 0 ihdr_crc_data 4 13;
+
let crc = calculate_crc ihdr_crc_data in
+
Buffer.add_char ihdr_chunk (Char.chr ((crc lsr 24) land 0xff));
+
Buffer.add_char ihdr_chunk (Char.chr ((crc lsr 16) land 0xff));
+
Buffer.add_char ihdr_chunk (Char.chr ((crc lsr 8) land 0xff));
+
Buffer.add_char ihdr_chunk (Char.chr (crc land 0xff));
+
+
(* Create IDAT chunk (uncompressed for simplicity) *)
+
let row_size = width * 4 in
+
let data_size = height * (row_size + 1) in
+
let idat_chunk = Buffer.create (12 + data_size) in
+
(* Length *)
+
Buffer.add_char idat_chunk (Char.chr ((data_size lsr 24) land 0xff));
+
Buffer.add_char idat_chunk (Char.chr ((data_size lsr 16) land 0xff));
+
Buffer.add_char idat_chunk (Char.chr ((data_size lsr 8) land 0xff));
+
Buffer.add_char idat_chunk (Char.chr (data_size land 0xff));
+
(* Chunk type - IDAT *)
+
Buffer.add_string idat_chunk "IDAT";
+
+
(* Very simple zlib header (no compression) *)
+
Buffer.add_char idat_chunk (Char.chr 0x78); (* CMF byte *)
+
Buffer.add_char idat_chunk (Char.chr 0x01); (* FLG byte *)
+
+
(* Raw image data with filter type 0 (None) for each scanline *)
+
for y = 0 to height - 1 do
+
(* Filter type 0 (None) *)
+
Buffer.add_char idat_chunk (Char.chr 0);
+
for x = 0 to width - 1 do
+
let idx = (y * width + x) * 4 in
+
Buffer.add_char idat_chunk (Char.chr (Bytes.get_uint8 pixels idx)); (* R *)
+
Buffer.add_char idat_chunk (Char.chr (Bytes.get_uint8 pixels (idx + 1))); (* G *)
+
Buffer.add_char idat_chunk (Char.chr (Bytes.get_uint8 pixels (idx + 2))); (* B *)
+
Buffer.add_char idat_chunk (Char.chr (Bytes.get_uint8 pixels (idx + 3))); (* A *)
+
done
+
done;
+
+
(* Zlib Adler-32 checksum (simplified) *)
+
let adler = ref 1 in
+
Buffer.add_char idat_chunk (Char.chr ((!adler lsr 24) land 0xff));
+
Buffer.add_char idat_chunk (Char.chr ((!adler lsr 16) land 0xff));
+
Buffer.add_char idat_chunk (Char.chr ((!adler lsr 8) land 0xff));
+
Buffer.add_char idat_chunk (Char.chr (!adler land 0xff));
+
+
(* CRC *)
+
let idat_crc = ref 0 in (* Not calculating CRC for simplicity *)
+
Buffer.add_char idat_chunk (Char.chr ((!idat_crc lsr 24) land 0xff));
+
Buffer.add_char idat_chunk (Char.chr ((!idat_crc lsr 16) land 0xff));
+
Buffer.add_char idat_chunk (Char.chr ((!idat_crc lsr 8) land 0xff));
+
Buffer.add_char idat_chunk (Char.chr (!idat_crc land 0xff));
+
+
(* Create IEND chunk *)
+
let iend_chunk = Buffer.create 12 in
+
(* Length - 0 bytes *)
+
Buffer.add_char iend_chunk (Char.chr 0);
+
Buffer.add_char iend_chunk (Char.chr 0);
+
Buffer.add_char iend_chunk (Char.chr 0);
+
Buffer.add_char iend_chunk (Char.chr 0);
+
(* Chunk type - IEND *)
+
Buffer.add_string iend_chunk "IEND";
+
(* CRC *)
+
let iend_crc = 0xAE426082 in (* Precomputed CRC for IEND chunk *)
+
Buffer.add_char iend_chunk (Char.chr ((iend_crc lsr 24) land 0xff));
+
Buffer.add_char iend_chunk (Char.chr ((iend_crc lsr 16) land 0xff));
+
Buffer.add_char iend_chunk (Char.chr ((iend_crc lsr 8) land 0xff));
+
Buffer.add_char iend_chunk (Char.chr (iend_crc land 0xff));
+
+
(* Combine all parts *)
+
let result = Buffer.create (8 + Buffer.length ihdr_chunk + Buffer.length idat_chunk + Buffer.length iend_chunk) in
+
(* PNG signature *)
+
Array.iter (fun c -> Buffer.add_char result (Char.chr c)) signature;
+
(* IHDR chunk *)
+
Buffer.add_buffer result ihdr_chunk;
+
(* IDAT chunk *)
+
Buffer.add_buffer result idat_chunk;
+
(* IEND chunk *)
+
Buffer.add_buffer result iend_chunk;
+
+
Buffer.contents result
+
+
(* Generate random pixel art image *)
+
let generate_random_image ?(width=16) ?(height=16) ?(pixel_size=1) ?(seed=None) () =
+
let pixels = Bytes.create (width * height * 4) in
+
+
(* Set random seed if provided *)
+
(match seed with
+
| Some s -> Random.init s
+
| None -> Random.self_init ());
+
+
(* Generate a random color palette *)
+
let palette_size = Random.int 8 + 2 in (* 2-10 colors *)
+
let palette = Array.init palette_size (fun _ ->
+
(Random.int 256, Random.int 256, Random.int 256, 255) (* RGBA *)
+
) in
+
+
(* Fill the pixel buffer *)
+
for y = 0 to height - 1 do
+
for x = 0 to width - 1 do
+
let color_idx = Random.int palette_size in
+
let (r, g, b, a) = palette.(color_idx) in
+
let idx = (y * width + x) * 4 in
+
Bytes.set_uint8 pixels idx r;
+
Bytes.set_uint8 pixels (idx + 1) g;
+
Bytes.set_uint8 pixels (idx + 2) b;
+
Bytes.set_uint8 pixels (idx + 3) a;
+
done
+
done;
+
+
(* Create symmetrical patterns - horizontally, vertically, or both *)
+
let symmetry_type = Random.int 3 in
+
if symmetry_type > 0 then begin
+
for y = 0 to height - 1 do
+
for x = 0 to width / 2 do
+
(* Mirror horizontally (except center column for odd widths) *)
+
if symmetry_type = 1 || symmetry_type = 2 then begin
+
let mirror_x = width - 1 - x in
+
if x <> mirror_x then begin
+
let src_idx = (y * width + x) * 4 in
+
let dst_idx = (y * width + mirror_x) * 4 in
+
for i = 0 to 3 do
+
Bytes.set pixels (dst_idx + i) (Bytes.get pixels (src_idx + i))
+
done
+
end
+
end
+
done
+
done;
+
+
(* Mirror vertically for symmetry_type = 2 *)
+
if symmetry_type = 2 then begin
+
for y = 0 to height / 2 do
+
let mirror_y = height - 1 - y in
+
if y <> mirror_y then begin
+
for x = 0 to width - 1 do
+
let src_idx = (y * width + x) * 4 in
+
let dst_idx = (mirror_y * width + x) * 4 in
+
for i = 0 to 3 do
+
Bytes.set pixels (dst_idx + i) (Bytes.get pixels (src_idx + i))
+
done
+
done
+
end
+
done
+
end
+
end;
+
+
(* Scale up the image if pixel_size > 1 *)
+
let final_width = width * pixel_size in
+
let final_height = height * pixel_size in
+
+
if pixel_size = 1 then
+
create_png width height pixels
+
else begin
+
let scaled_pixels = Bytes.create (final_width * final_height * 4) in
+
+
for y = 0 to height - 1 do
+
for x = 0 to width - 1 do
+
let src_idx = (y * width + x) * 4 in
+
for py = 0 to pixel_size - 1 do
+
for px = 0 to pixel_size - 1 do
+
let dst_x = x * pixel_size + px in
+
let dst_y = y * pixel_size + py in
+
let dst_idx = (dst_y * final_width + dst_x) * 4 in
+
for i = 0 to 3 do
+
Bytes.set scaled_pixels (dst_idx + i) (Bytes.get pixels (src_idx + i))
+
done
+
done
+
done
+
done
+
done;
+
+
create_png final_width final_height scaled_pixels
+
end
+
end
+
+
(* Helper for extracting values from JSON *)
+
let get_param_int json name default =
+
match json with
+
| `Assoc fields ->
+
(match List.assoc_opt name fields with
+
| Some (`Int i) -> i
+
| Some (`Float f) -> int_of_float f
+
| _ -> default)
+
| _ -> default
+
+
(* Create a server *)
+
let server = create_server
+
~name:"OCaml MCP Image Generator"
+
~version:"0.1.0"
+
~protocol_version:"2024-11-05"
+
()
+
+
(* Define startup and shutdown hooks *)
+
let startup () =
+
Printf.fprintf stderr "ImageGeneratorServer is starting up!\n";
+
flush stderr;
+
Log.info "ImageGeneratorServer is starting up!"
+
+
let shutdown () =
+
Printf.fprintf stderr "ImageGeneratorServer is shutting down. Goodbye!\n";
+
flush stderr;
+
Log.info "ImageGeneratorServer is shutting down. Goodbye!"
+
+
(* Register the hooks *)
+
let () =
+
set_startup_hook server startup;
+
set_shutdown_hook server shutdown
+
+
(* Make an image content helper *)
+
let make_image_content data mime_type =
+
let image_content = ImageContent.{
+
data;
+
mime_type;
+
annotations = None;
+
} in
+
Image image_content
+
+
(* Define and register a random pixel art generator tool *)
+
let _ = add_tool server
+
~name:"generate_random_pixel_art"
+
~description:"Generates a random pixel art image"
+
~schema_properties:[
+
("width", "integer", "Width of the pixel art grid (default: 16)");
+
("height", "integer", "Height of the pixel art grid (default: 16)");
+
("pixel_size", "integer", "Size of each pixel (default: 8)");
+
("seed", "integer", "Random seed (optional)");
+
]
+
~schema_required:[]
+
(fun args ->
+
try
+
let width = get_param_int args "width" 16 in
+
let height = get_param_int args "height" 16 in
+
let pixel_size = get_param_int args "pixel_size" 8 in
+
+
(* Validate parameters *)
+
let width = max 1 (min 64 width) in (* Limit to 1-64 *)
+
let height = max 1 (min 64 height) in (* Limit to 1-64 *)
+
let pixel_size = max 1 (min 16 pixel_size) in (* Limit to 1-16 *)
+
+
(* Extract optional seed *)
+
let seed = match args with
+
| `Assoc fields ->
+
(match List.assoc_opt "seed" fields with
+
| Some (`Int s) -> Some s
+
| _ -> None)
+
| _ -> None
+
in
+
+
(* Generate the image *)
+
let image_data = ImageGenerator.generate_random_image
+
~width ~height ~pixel_size ~seed () in
+
+
(* Encode as base64 *)
+
let base64_data = Base64.encode image_data in
+
+
Log.info (Printf.sprintf "Generated random pixel art image (%dx%d grid, %dpx pixels)"
+
width height pixel_size);
+
+
(* Create a response with both text and image content *)
+
CallToolResult.yojson_of_t CallToolResult.{
+
content = [
+
Text TextContent.{
+
text = Printf.sprintf "Generated random pixel art image (%dx%d grid, %dpx pixels)"
+
width height pixel_size;
+
annotations = None
+
};
+
Image ImageContent.{
+
data = base64_data;
+
mime_type = "image/png";
+
annotations = None
+
}
+
];
+
is_error = false;
+
meta = None
+
}
+
with
+
| Failure msg ->
+
Log.error (Printf.sprintf "Error in image generator tool: %s" msg);
+
CallToolResult.yojson_of_t CallToolResult.{
+
content = [
+
Text TextContent.{
+
text = Printf.sprintf "Error: %s" msg;
+
annotations = None
+
}
+
];
+
is_error = true;
+
meta = None
+
}
+
)
+
+
(* Define and register a pixel art prompt *)
+
let _ = add_prompt server
+
~name:"pixel-art-prompt"
+
~description:"A prompt that includes a random pixel art image"
+
~arguments:[
+
("width", Some "Width of the pixel art (1-64)", false);
+
("height", Some "Height of the pixel art (1-64)", false);
+
("pixel_size", Some "Size of each pixel (1-16)", false);
+
]
+
(fun args ->
+
(* Parse parameters with defaults *)
+
let width =
+
try int_of_string (List.assoc "width" args)
+
with _ -> 16
+
in
+
let height =
+
try int_of_string (List.assoc "height" args)
+
with _ -> 16
+
in
+
let pixel_size =
+
try int_of_string (List.assoc "pixel_size" args)
+
with _ -> 8
+
in
+
+
(* Validate parameters *)
+
let width = max 1 (min 64 width) in
+
let height = max 1 (min 64 height) in
+
let pixel_size = max 1 (min 16 pixel_size) in
+
+
(* Generate image *)
+
let image_data = ImageGenerator.generate_random_image
+
~width ~height ~pixel_size () in
+
+
(* Encode as base64 *)
+
let base64_data = Base64.encode image_data in
+
+
Log.info (Printf.sprintf "Generated pixel art for prompt (%dx%d grid, %dpx pixels)"
+
width height pixel_size);
+
+
[
+
Prompt.{
+
role = `User;
+
content = make_text_content "I've generated a random pixel art image for you:"
+
};
+
Prompt.{
+
role = `User;
+
content = make_image_content base64_data "image/png"
+
};
+
Prompt.{
+
role = `User;
+
content = make_text_content (Printf.sprintf "Please describe what you see in this %dx%d pixel art."
+
width height)
+
};
+
Prompt.{
+
role = `Assistant;
+
content = make_text_content "I'll describe what I see in this pixel art image."
+
}
+
]
+
)
+
+
(* Main function *)
+
let () =
+
(* Parse command line arguments *)
+
let transport_type = ref Stdio in
+
let args = [
+
("--http", Arg.Unit (fun () -> transport_type := Http),
+
"Start server with HTTP transport (default is stdio)");
+
] in
+
let usage_msg = "Usage: image_generator_example [--http]" in
+
Arg.parse args (fun _ -> ()) usage_msg;
+
+
(* Use stderr for direct printing to avoid interfering with JSON-RPC protocol *)
+
Printf.fprintf stderr "Starting ImageGeneratorServer...\n";
+
flush stderr;
+
Log.info "Starting ImageGeneratorServer...";
+
+
(* Configure the server with appropriate capabilities *)
+
ignore (configure_server server ());
+
+
(* Create and start MCP server with the selected transport *)
+
let mcp_server = create ~server ~transport:!transport_type () in
+
start mcp_server
-721
bin/markdown_book_sdk.ml
···
-
open Mcp_sdk
-
-
(* Helper module for working with markdown book chapters *)
-
module BookChapter = struct
-
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 =
-
{|
-
# Introduction to OCaml
-
-
OCaml is a general-purpose, multi-paradigm programming language which extends the Caml dialect of ML with object-oriented features.
-
-
## Key Features
-
-
- **Strong Static Typing**: Catch errors at compile time rather than runtime
-
- **Type Inference**: No need to annotate every variable with a type
-
- **Pattern Matching**: Express complex control flow in a clear and concise way
-
- **Functional Programming**: First-class functions and immutability
-
- **Module System**: Powerful abstraction capabilities with modules and functors
-
- **Performance**: Native code compilation with excellent performance characteristics
-
-
## History
-
-
OCaml was created in 1996 by Xavier Leroy, Jรฉrรดme Vouillon, Damien Doligez, and Didier Rรฉmy at INRIA in France. It evolved from the Caml language, which itself was an implementation of ML.
-
-
## Why OCaml?
-
-
OCaml offers a unique combination of features that make it particularly well-suited for certain domains:
-
-
- **Program Correctness**: The strong type system catches many errors at compile time
-
- **Symbolic Computing**: Excellent for manipulating complex data structures and symbolic expressions
-
- **Systems Programming**: Can be used for low-level systems programming with high safety guarantees
-
- **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 =
-
{|
-
# Basic Syntax and Types
-
-
OCaml has a clean, consistent syntax that emphasizes readability and minimizes boilerplate.
-
-
## Variables and Basic Types
-
-
In OCaml, variables are immutable by default. Once a value is bound to a name, that binding cannot change.
-
-
```ocaml
-
(* Binding a value to a name *)
-
let x = 42
-
let greeting = "Hello, World!"
-
-
(* OCaml has type inference *)
-
(* These are equivalent: *)
-
let x = 42
-
let x : int = 42
-
```
-
-
## Basic Types
-
-
- `int`: Integer numbers
-
- `float`: Floating-point numbers
-
- `bool`: Boolean values (`true` or `false`)
-
- `char`: Single characters
-
- `string`: Text strings
-
- `unit`: The empty tuple, written `()`
-
-
## Functions
-
-
Functions in OCaml are first-class values:
-
-
```ocaml
-
(* A simple function *)
-
let add x y = x + y
-
-
(* With type annotations *)
-
let add (x : int) (y : int) : int = x + y
-
-
(* Anonymous (lambda) function *)
-
let increment = fun x -> x + 1
-
-
(* Partial application *)
-
let add5 = add 5
-
let fifteen = add5 10 (* equals 15 *)
-
```
-
-
## Control Flow
-
-
OCaml uses expressions rather than statements for control flow:
-
-
```ocaml
-
(* If expression *)
-
let abs x =
-
if x < 0 then -x else x
-
-
(* Match expression (pattern matching) *)
-
let describe_sign x =
-
match x with
-
| x when x < 0 -> "negative"
-
| 0 -> "zero"
-
| _ -> "positive"
-
```
-
-
## Recursion
-
-
Functions need the `rec` keyword to be recursive:
-
-
```ocaml
-
(* Recursive function *)
-
let rec factorial n =
-
if n <= 1 then 1 else n * factorial (n - 1)
-
-
(* Mutually recursive functions *)
-
let rec is_even n =
-
if n = 0 then true else is_odd (n - 1)
-
and is_odd n =
-
if n = 0 then false else is_even (n - 1)
-
```
-
-
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 =
-
{|
-
# Data Structures
-
-
OCaml provides several built-in data structures and makes it easy to define custom ones.
-
-
## Tuples
-
-
Tuples are fixed-length collections of values that can have different types:
-
-
```ocaml
-
(* A pair of an int and a string *)
-
let person = (42, "Alice")
-
-
(* Extracting values with pattern matching *)
-
let (age, name) = person
-
-
(* Accessing elements *)
-
let age = fst person (* For pairs only *)
-
let name = snd person (* For pairs only *)
-
```
-
-
## Records
-
-
Records are named collections of values:
-
-
```ocaml
-
(* Defining a record type *)
-
type person = {
-
name: string;
-
age: int;
-
email: string option;
-
}
-
-
(* Creating a record *)
-
let alice = {
-
name = "Alice";
-
age = 42;
-
email = Some "alice@example.com";
-
}
-
-
(* Accessing fields *)
-
let alices_name = alice.name
-
-
(* Functional update (creates a new record) *)
-
let alice_birthday = { alice with age = alice.age + 1 }
-
```
-
-
## Variants
-
-
Variants (also called algebraic data types) represent values that can be one of several cases:
-
-
```ocaml
-
(* Defining a variant type *)
-
type shape =
-
| Circle of float (* radius *)
-
| Rectangle of float * float (* width, height *)
-
| Triangle of float * float * float (* sides *)
-
-
(* Creating variants *)
-
let my_circle = Circle 2.5
-
let my_rectangle = Rectangle (4.0, 6.0)
-
-
(* Pattern matching with variants *)
-
let area shape =
-
match shape with
-
| Circle r -> Float.pi *. r *. r
-
| Rectangle (w, h) -> w *. h
-
| Triangle (a, b, c) ->
-
let s = (a +. b +. c) /. 2.0 in
-
sqrt (s *. (s -. a) *. (s -. b) *. (s -. c))
-
```
-
-
## Lists
-
-
Lists are immutable linked lists of elements of the same type:
-
-
```ocaml
-
(* Creating lists *)
-
let empty = []
-
let numbers = [1; 2; 3; 4; 5]
-
let constructed = 1 :: 2 :: 3 :: []
-
-
(* Pattern matching with lists *)
-
let rec sum_list lst =
-
match lst with
-
| [] -> 0
-
| head :: tail -> head + sum_list tail
-
-
(* Common list functions *)
-
let doubled = List.map (fun x -> x * 2) numbers
-
let evens = List.filter (fun x -> x mod 2 = 0) numbers
-
let sum = List.fold_left (+) 0 numbers
-
```
-
-
## Arrays
-
-
Arrays provide mutable, fixed-size collections with O(1) random access:
-
-
```ocaml
-
(* Creating arrays *)
-
let arr = [|1; 2; 3; 4; 5|]
-
-
(* Accessing elements (0-indexed) *)
-
let first = arr.(0)
-
-
(* Modifying elements *)
-
let () = arr.(0) <- 10
-
-
(* Array functions *)
-
let doubled = Array.map (fun x -> x * 2) arr
-
```
-
-
## Option Type
-
-
The option type represents values that might be absent:
-
-
```ocaml
-
(* Option type *)
-
type 'a option = None | Some of 'a
-
-
(* Using options *)
-
let safe_divide x y =
-
if y = 0 then None else Some (x / y)
-
-
(* Working with options *)
-
match safe_divide 10 2 with
-
| None -> print_endline "Division by zero"
-
| Some result -> Printf.printf "Result: %d\n" result
-
```
-
-
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 =
-
{|
-
# 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.
-
-
## Basic Modules
-
-
A module is a collection of related definitions (types, values, submodules, etc.):
-
-
```ocaml
-
(* Defining a module *)
-
module Math = struct
-
let pi = 3.14159
-
let square x = x *. x
-
let cube x = x *. x *. x
-
end
-
-
(* Using a module *)
-
let area_of_circle r = Math.pi *. Math.square r
-
```
-
-
## Module Signatures
-
-
Module signatures define the interface of a module, hiding implementation details:
-
-
```ocaml
-
(* Defining a signature *)
-
module type MATH = sig
-
val pi : float
-
val square : float -> float
-
val cube : float -> float
-
end
-
-
(* Implementing a signature *)
-
module Math : MATH = struct
-
let pi = 3.14159
-
let square x = x *. x
-
let cube x = x *. x *. x
-
-
(* This is hidden because it's not in the signature *)
-
let private_helper x = x +. 1.0
-
end
-
```
-
-
## Functors
-
-
Functors are functions from modules to modules, allowing for higher-order modularity:
-
-
```ocaml
-
(* Module signature for collections *)
-
module type COLLECTION = sig
-
type 'a t
-
val empty : 'a t
-
val add : 'a -> 'a t -> 'a t
-
val mem : 'a -> 'a t -> bool
-
end
-
-
(* Functor that creates a set implementation given an element type with comparison *)
-
module MakeSet (Element : sig type t val compare : t -> t -> int end) : COLLECTION with type 'a t = Element.t list = struct
-
type 'a t = Element.t list
-
-
let empty = []
-
-
let rec add x lst =
-
match lst with
-
| [] -> [x]
-
| y :: ys ->
-
let c = Element.compare x y in
-
if c < 0 then x :: lst
-
else if c = 0 then lst (* Element already exists *)
-
else y :: add x ys
-
-
let rec mem x lst =
-
match lst with
-
| [] -> false
-
| y :: ys ->
-
let c = Element.compare x y in
-
if c = 0 then true
-
else if c < 0 then false
-
else mem x ys
-
end
-
-
(* Creating an integer set *)
-
module IntElement = struct
-
type t = int
-
let compare = Int.compare
-
end
-
-
module IntSet = MakeSet(IntElement)
-
-
(* Using the set *)
-
let my_set = IntSet.empty
-
|> IntSet.add 3
-
|> IntSet.add 1
-
|> IntSet.add 4
-
|> IntSet.add 1 (* Duplicate, not added *)
-
-
let has_three = IntSet.mem 3 my_set (* true *)
-
let has_five = IntSet.mem 5 my_set (* false *)
-
```
-
-
## First-Class Modules
-
-
OCaml also supports first-class modules, allowing modules to be passed as values:
-
-
```ocaml
-
(* Module type for number operations *)
-
module type NUMBER = sig
-
type t
-
val zero : t
-
val add : t -> t -> t
-
val to_string : t -> string
-
end
-
-
(* Implementations for different number types *)
-
module Int : NUMBER with type t = int = struct
-
type t = int
-
let zero = 0
-
let add = (+)
-
let to_string = string_of_int
-
end
-
-
module Float : NUMBER with type t = float = struct
-
type t = float
-
let zero = 0.0
-
let add = (+.)
-
let to_string = string_of_float
-
end
-
-
(* Function that works with any NUMBER module *)
-
let sum_as_string (type a) (module N : NUMBER with type t = a) numbers =
-
let sum = List.fold_left N.add N.zero numbers in
-
N.to_string sum
-
-
(* Using first-class modules *)
-
let int_sum = sum_as_string (module Int) [1; 2; 3; 4]
-
let float_sum = sum_as_string (module Float) [1.0; 2.5; 3.7]
-
```
-
-
## Open and Include
-
-
OCaml provides ways to bring module contents into scope:
-
-
```ocaml
-
(* Open brings module contents into scope temporarily *)
-
let area =
-
let open Math in
-
pi *. square 2.0
-
-
(* Local opening with the modern syntax *)
-
let area = Math.(pi *. square 2.0)
-
-
(* Include actually extends a module with another module's contents *)
-
module ExtendedMath = struct
-
include Math
-
let tau = 2.0 *. pi
-
let circumference r = tau *. r
-
end
-
```
-
-
The module system enables OCaml programmers to build highly modular, reusable code with clear boundaries between components.
-
|};
-
};
-
{
-
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.
-
-
## Polymorphic Variants
-
-
Unlike regular variants, polymorphic variants don't need to be predefined:
-
-
```ocaml
-
(* Using polymorphic variants directly *)
-
let weekend = `Saturday | `Sunday
-
let is_weekend day =
-
match day with
-
| `Saturday | `Sunday -> true
-
| `Monday .. `Friday -> false
-
-
(* Can be used in mixed contexts *)
-
let shape_area = function
-
| `Circle r -> Float.pi *. r *. r
-
| `Rectangle (w, h) -> w *. h
-
| `Triangle (b, h) -> 0.5 *. b *. h
-
| `Regular_polygon(n, s) when n >= 3 ->
-
let apothem = s /. (2.0 *. tan (Float.pi /. float_of_int n)) in
-
n *. s *. apothem /. 2.0
-
| _ -> failwith "Invalid shape"
-
```
-
-
## Objects and Classes
-
-
OCaml supports object-oriented programming:
-
-
```ocaml
-
(* Simple class definition *)
-
class point x_init y_init =
-
object (self)
-
val mutable x = x_init
-
val mutable y = y_init
-
-
method get_x = x
-
method get_y = y
-
method move dx dy = x <- x + dx; y <- y + dy
-
method distance_from_origin =
-
sqrt (float_of_int (x * x + y * y))
-
-
(* Private method *)
-
method private to_string =
-
Printf.sprintf "(%d, %d)" x y
-
-
(* Calling another method *)
-
method print = print_endline self#to_string
-
end
-
-
(* Using a class *)
-
let p = new point 3 4
-
let () = p#move 2 1
-
let d = p#distance_from_origin
-
```
-
-
## Generalized Algebraic Data Types (GADTs)
-
-
GADTs provide more type control than regular variants:
-
-
```ocaml
-
(* A GADT for type-safe expressions *)
-
type _ expr =
-
| Int : int -> int expr
-
| Bool : bool -> bool expr
-
| Add : int expr * int expr -> int expr
-
| Eq : 'a expr * 'a expr -> bool expr
-
-
(* Type-safe evaluation *)
-
let rec eval : type a. a expr -> a = function
-
| Int n -> n
-
| Bool b -> b
-
| Add (e1, e2) -> eval e1 + eval e2
-
| Eq (e1, e2) -> eval e1 = eval e2
-
-
(* These expressions are statically type-checked *)
-
let e1 = Add (Int 1, Int 2) (* OK: int expr *)
-
let e2 = Eq (Int 1, Int 2) (* OK: bool expr *)
-
(* let e3 = Add (Int 1, Bool true) (* Type error! *) *)
-
(* let e4 = Eq (Int 1, Bool true) (* Type error! *) *)
-
```
-
-
## Type Extensions
-
-
OCaml allows extending existing types:
-
-
```ocaml
-
(* Original type *)
-
type shape = Circle of float | Rectangle of float * float
-
-
(* Extending the type in another module *)
-
type shape += Triangle of float * float * float
-
-
(* Pattern matching must now handle unknown cases *)
-
let area = function
-
| Circle r -> Float.pi *. r *. r
-
| Rectangle (w, h) -> w *. h
-
| Triangle (a, b, c) ->
-
let s = (a +. b +. c) /. 2.0 in
-
sqrt (s *. (s -. a) *. (s -. b) *. (s -. c))
-
| _ -> failwith "Unknown shape"
-
```
-
-
## Effects and Effect Handlers
-
-
OCaml 5 introduced algebraic effects for managing control flow:
-
-
```ocaml
-
(* Defining an effect *)
-
type _ Effect.t += Ask : string -> string Effect.t
-
-
(* Handler for the Ask effect *)
-
let prompt_user () =
-
Effect.Deep.try_with
-
(fun () ->
-
let name = Effect.perform (Ask "What is your name?") in
-
Printf.printf "Hello, %s!\n" name)
-
{ Effect.Deep.effc = fun (type a) (effect : a Effect.t) ->
-
match effect with
-
| Ask prompt -> fun k ->
-
Printf.printf "%s " prompt;
-
let response = read_line () in
-
k response
-
| _ -> None }
-
```
-
-
## Higher-Ranked Polymorphism
-
-
Using the `Obj.magic` escape hatch (with caution):
-
-
```ocaml
-
(* This would normally not be permitted due to rank-2 polymorphism *)
-
let apply_to_all_types f =
-
let magic_f : 'a -> string = Obj.magic f in
-
[
-
magic_f 42;
-
magic_f "hello";
-
magic_f 3.14;
-
magic_f true;
-
]
-
-
(* Usage - with great care! *)
-
let result = apply_to_all_types (fun x -> Printf.sprintf "Value: %s" (Obj.magic x))
-
```
-
-
## Metaprogramming with PPX
-
-
OCaml's PPX system enables powerful metaprogramming:
-
-
```ocaml
-
(* With ppx_deriving *)
-
type person = {
-
name: string;
-
age: int;
-
email: string option;
-
} [@@deriving show, eq, ord]
-
-
(* With ppx_sexp_conv *)
-
type config = {
-
server: string;
-
port: int;
-
timeout: float;
-
} [@@deriving sexp]
-
-
(* With ppx_let for monadic operations *)
-
let computation =
-
[%m.let
-
let* x = get_value_from_db "key1" in
-
let* y = get_value_from_db "key2" in
-
return (x + y)
-
]
-
```
-
-
## Modules for Advanced Typing
-
-
Using modules to encode complex type relationships:
-
-
```ocaml
-
(* Phantom types for added type safety *)
-
module SafeString : sig
-
type 'a t
-
-
(* Constructors for different string types *)
-
val of_raw : string -> [`Raw] t
-
val sanitize : [`Raw] t -> [`Sanitized] t
-
val validate : [`Sanitized] t -> [`Validated] t option
-
-
(* Operations that require specific string types *)
-
val to_html : [`Sanitized] t -> string
-
val to_sql : [`Validated] t -> string
-
-
(* Common operations for all string types *)
-
val length : _ t -> int
-
val concat : _ t -> _ t -> [`Raw] t
-
end = struct
-
type 'a t = string
-
-
let of_raw s = s
-
let sanitize s = String.map (function '<' | '>' -> '_' | c -> c) s
-
let validate s = if String.length s > 0 then Some s else None
-
-
let to_html s = s
-
let to_sql s = "'" ^ String.map (function '\'' -> '\'' | c -> c) s ^ "'"
-
-
let length = String.length
-
let concat s1 s2 = s1 ^ s2
-
end
-
```
-
-
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
-
-
(* Get chapter titles *)
-
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 ()
-
-
(* 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.")
-
-
(* 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)
-
-
(* 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)
-
-
(* Run the server with the default scheduler *)
-
let () = Eio_main.run @@ fun env -> Mcp_server.run_stdio_server env server
+502
bin/multimodal_example.ml
···
+
open Mcp
+
open Mcp_sdk
+
open Mcp_server
+
+
(* Multimodal example MCP server *)
+
+
(* Base64 encoding helper *)
+
module Base64 = struct
+
let encode_char n =
+
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".[n]
+
+
let encode_block i bytes =
+
let buffer = Buffer.create 4 in
+
let b1 = Char.code (String.get bytes (i * 3)) in
+
let b2 = if i * 3 + 1 < String.length bytes then Char.code (String.get bytes (i * 3 + 1)) else 0 in
+
let b3 = if i * 3 + 2 < String.length bytes then Char.code (String.get bytes (i * 3 + 2)) else 0 in
+
+
let n = (b1 lsl 16) lor (b2 lsl 8) lor b3 in
+
Buffer.add_char buffer (encode_char ((n lsr 18) land 63));
+
Buffer.add_char buffer (encode_char ((n lsr 12) land 63));
+
+
if i * 3 + 1 < String.length bytes then
+
Buffer.add_char buffer (encode_char ((n lsr 6) land 63))
+
else
+
Buffer.add_char buffer '=';
+
+
if i * 3 + 2 < String.length bytes then
+
Buffer.add_char buffer (encode_char (n land 63))
+
else
+
Buffer.add_char buffer '=';
+
+
Buffer.contents buffer
+
+
let encode data =
+
let buffer = Buffer.create (4 * (String.length data + 2) / 3) in
+
for i = 0 to (String.length data - 1) / 3 do
+
Buffer.add_string buffer (encode_block i data)
+
done;
+
Buffer.contents buffer
+
end
+
+
(* Audio generator *)
+
module AudioGenerator = struct
+
(* Generate a simple sine wave *)
+
let generate_sine_wave ~frequency ~duration ~sample_rate ~amplitude =
+
(* WAV parameters *)
+
let num_channels = 1 in (* Mono *)
+
let bits_per_sample = 16 in
+
let byte_rate = sample_rate * num_channels * bits_per_sample / 8 in
+
let block_align = num_channels * bits_per_sample / 8 in
+
let num_samples = int_of_float (float_of_int sample_rate *. duration) in
+
let data_size = num_samples * block_align in
+
+
(* Create buffer for the WAV data *)
+
let buffer = Buffer.create (44 + data_size) in
+
+
(* Write WAV header *)
+
(* "RIFF" chunk *)
+
Buffer.add_string buffer "RIFF";
+
let file_size = 36 + data_size in
+
Buffer.add_char buffer (Char.chr (file_size land 0xff));
+
Buffer.add_char buffer (Char.chr ((file_size lsr 8) land 0xff));
+
Buffer.add_char buffer (Char.chr ((file_size lsr 16) land 0xff));
+
Buffer.add_char buffer (Char.chr ((file_size lsr 24) land 0xff));
+
Buffer.add_string buffer "WAVE";
+
+
(* "fmt " sub-chunk *)
+
Buffer.add_string buffer "fmt ";
+
Buffer.add_char buffer (Char.chr 16); (* Sub-chunk size (16 for PCM) *)
+
Buffer.add_char buffer (Char.chr 0);
+
Buffer.add_char buffer (Char.chr 0);
+
Buffer.add_char buffer (Char.chr 0);
+
Buffer.add_char buffer (Char.chr 1); (* Audio format (1 for PCM) *)
+
Buffer.add_char buffer (Char.chr 0);
+
Buffer.add_char buffer (Char.chr num_channels); (* Number of channels *)
+
Buffer.add_char buffer (Char.chr 0);
+
+
(* Sample rate *)
+
Buffer.add_char buffer (Char.chr (sample_rate land 0xff));
+
Buffer.add_char buffer (Char.chr ((sample_rate lsr 8) land 0xff));
+
Buffer.add_char buffer (Char.chr ((sample_rate lsr 16) land 0xff));
+
Buffer.add_char buffer (Char.chr ((sample_rate lsr 24) land 0xff));
+
+
(* Byte rate *)
+
Buffer.add_char buffer (Char.chr (byte_rate land 0xff));
+
Buffer.add_char buffer (Char.chr ((byte_rate lsr 8) land 0xff));
+
Buffer.add_char buffer (Char.chr ((byte_rate lsr 16) land 0xff));
+
Buffer.add_char buffer (Char.chr ((byte_rate lsr 24) land 0xff));
+
+
(* Block align *)
+
Buffer.add_char buffer (Char.chr block_align);
+
Buffer.add_char buffer (Char.chr 0);
+
+
(* Bits per sample *)
+
Buffer.add_char buffer (Char.chr bits_per_sample);
+
Buffer.add_char buffer (Char.chr 0);
+
+
(* "data" sub-chunk *)
+
Buffer.add_string buffer "data";
+
Buffer.add_char buffer (Char.chr (data_size land 0xff));
+
Buffer.add_char buffer (Char.chr ((data_size lsr 8) land 0xff));
+
Buffer.add_char buffer (Char.chr ((data_size lsr 16) land 0xff));
+
Buffer.add_char buffer (Char.chr ((data_size lsr 24) land 0xff));
+
+
(* Generate sine wave data *)
+
let max_amplitude = float_of_int (1 lsl (bits_per_sample - 1)) -. 1.0 in
+
for i = 0 to num_samples - 1 do
+
let t = float_of_int i /. float_of_int sample_rate in
+
let value = int_of_float (amplitude *. max_amplitude *. sin (2.0 *. Float.pi *. frequency *. t)) in
+
(* Write 16-bit sample (little-endian) *)
+
Buffer.add_char buffer (Char.chr (value land 0xff));
+
Buffer.add_char buffer (Char.chr ((value lsr 8) land 0xff));
+
done;
+
+
Buffer.contents buffer
+
end
+
+
(* Image generator *)
+
module ImageGenerator = struct
+
(* Simple PNG generation *)
+
let generate_simple_image width height color_str =
+
(* Parse color - expected format: #RRGGBB or #RRGGBBAA *)
+
let r, g, b, a =
+
try
+
if String.length color_str >= 7 && color_str.[0] = '#' then
+
let r = int_of_string ("0x" ^ String.sub color_str 1 2) in
+
let g = int_of_string ("0x" ^ String.sub color_str 3 2) in
+
let b = int_of_string ("0x" ^ String.sub color_str 5 2) in
+
let a = if String.length color_str >= 9 then
+
int_of_string ("0x" ^ String.sub color_str 7 2)
+
else 255 in
+
(r, g, b, a)
+
else
+
(255, 0, 0, 255) (* Default to red if invalid *)
+
with _ ->
+
(255, 0, 0, 255) (* Default to red on parsing error *)
+
in
+
+
(* Create a very simple 1x1 PNG with the specified color *)
+
(* PNG signature *)
+
let signature = [137; 80; 78; 71; 13; 10; 26; 10] in
+
+
(* Create buffer for the PNG data *)
+
let buffer = Buffer.create 100 in
+
+
(* PNG signature *)
+
List.iter (fun b -> Buffer.add_char buffer (Char.chr b)) signature;
+
+
(* IHDR chunk *)
+
Buffer.add_char buffer (Char.chr 0); (* length - 13 bytes *)
+
Buffer.add_char buffer (Char.chr 0);
+
Buffer.add_char buffer (Char.chr 0);
+
Buffer.add_char buffer (Char.chr 13);
+
+
Buffer.add_string buffer "IHDR";
+
+
(* Width *)
+
Buffer.add_char buffer (Char.chr ((width lsr 24) land 0xff));
+
Buffer.add_char buffer (Char.chr ((width lsr 16) land 0xff));
+
Buffer.add_char buffer (Char.chr ((width lsr 8) land 0xff));
+
Buffer.add_char buffer (Char.chr (width land 0xff));
+
+
(* Height *)
+
Buffer.add_char buffer (Char.chr ((height lsr 24) land 0xff));
+
Buffer.add_char buffer (Char.chr ((height lsr 16) land 0xff));
+
Buffer.add_char buffer (Char.chr ((height lsr 8) land 0xff));
+
Buffer.add_char buffer (Char.chr (height land 0xff));
+
+
Buffer.add_char buffer (Char.chr 8); (* Bit depth - 8 bits per channel *)
+
Buffer.add_char buffer (Char.chr 6); (* Color type - RGBA *)
+
Buffer.add_char buffer (Char.chr 0); (* Compression method - deflate *)
+
Buffer.add_char buffer (Char.chr 0); (* Filter method - adaptive filtering *)
+
Buffer.add_char buffer (Char.chr 0); (* Interlace method - no interlace *)
+
+
(* IHDR CRC - precomputed for simplicity *)
+
Buffer.add_char buffer (Char.chr 0);
+
Buffer.add_char buffer (Char.chr 0);
+
Buffer.add_char buffer (Char.chr 0);
+
Buffer.add_char buffer (Char.chr 0);
+
+
(* IDAT chunk - simplified for example *)
+
let pixels_per_row = width * 4 in
+
let data_size = (1 + pixels_per_row) * height in
+
+
Buffer.add_char buffer (Char.chr ((data_size lsr 24) land 0xff));
+
Buffer.add_char buffer (Char.chr ((data_size lsr 16) land 0xff));
+
Buffer.add_char buffer (Char.chr ((data_size lsr 8) land 0xff));
+
Buffer.add_char buffer (Char.chr (data_size land 0xff));
+
+
Buffer.add_string buffer "IDAT";
+
+
(* Simple zlib header *)
+
Buffer.add_char buffer (Char.chr 0x78);
+
Buffer.add_char buffer (Char.chr 0x01);
+
+
(* Raw image data *)
+
for _ = 0 to height - 1 do
+
Buffer.add_char buffer (Char.chr 0); (* Filter type 0 - None *)
+
for _ = 0 to width - 1 do
+
Buffer.add_char buffer (Char.chr r);
+
Buffer.add_char buffer (Char.chr g);
+
Buffer.add_char buffer (Char.chr b);
+
Buffer.add_char buffer (Char.chr a);
+
done
+
done;
+
+
(* Dummy Adler32 checksum *)
+
Buffer.add_char buffer (Char.chr 0);
+
Buffer.add_char buffer (Char.chr 0);
+
Buffer.add_char buffer (Char.chr 0);
+
Buffer.add_char buffer (Char.chr 0);
+
+
(* IDAT CRC - precomputed for simplicity *)
+
Buffer.add_char buffer (Char.chr 0);
+
Buffer.add_char buffer (Char.chr 0);
+
Buffer.add_char buffer (Char.chr 0);
+
Buffer.add_char buffer (Char.chr 0);
+
+
(* IEND chunk *)
+
Buffer.add_char buffer (Char.chr 0);
+
Buffer.add_char buffer (Char.chr 0);
+
Buffer.add_char buffer (Char.chr 0);
+
Buffer.add_char buffer (Char.chr 0);
+
+
Buffer.add_string buffer "IEND";
+
+
(* IEND CRC - precomputed value *)
+
Buffer.add_char buffer (Char.chr 0xAE);
+
Buffer.add_char buffer (Char.chr 0x42);
+
Buffer.add_char buffer (Char.chr 0x60);
+
Buffer.add_char buffer (Char.chr 0x82);
+
+
Buffer.contents buffer
+
end
+
+
(* Helper for extracting values from JSON *)
+
let get_param_int json name default =
+
match json with
+
| `Assoc fields -> begin
+
match List.assoc_opt name fields with
+
| Some (`Int i) -> begin
+
i
+
end
+
| Some (`Float f) -> begin
+
int_of_float f
+
end
+
| _ -> begin
+
default
+
end
+
end
+
| _ -> begin
+
default
+
end
+
+
let get_param_float json name default =
+
match json with
+
| `Assoc fields -> begin
+
match List.assoc_opt name fields with
+
| Some (`Int i) -> begin
+
float_of_int i
+
end
+
| Some (`Float f) -> begin
+
f
+
end
+
| _ -> begin
+
default
+
end
+
end
+
| _ -> begin
+
default
+
end
+
+
let get_param_string json name default =
+
match json with
+
| `Assoc fields -> begin
+
match List.assoc_opt name fields with
+
| Some (`String s) -> begin
+
s
+
end
+
| _ -> begin
+
default
+
end
+
end
+
| _ -> begin
+
default
+
end
+
+
(* Create a server *)
+
let server = create_server
+
~name:"OCaml MCP Multimodal Example"
+
~version:"0.1.0"
+
~protocol_version:"2024-11-05"
+
()
+
+
(* Define startup and shutdown hooks *)
+
let startup () =
+
Printf.fprintf stderr "MultimodalServer is starting up!\n";
+
flush stderr;
+
Log.info "MultimodalServer is starting up!"
+
+
let shutdown () =
+
Printf.fprintf stderr "MultimodalServer is shutting down. Goodbye!\n";
+
flush stderr;
+
Log.info "MultimodalServer is shutting down. Goodbye!"
+
+
(* Register the hooks *)
+
let () =
+
set_startup_hook server startup;
+
set_shutdown_hook server shutdown
+
+
(* Define and register a multimodal tool *)
+
let _ = add_tool server
+
~name:"generate_multimodal_response"
+
~description:"Generates a response with text, image and audio content"
+
~schema_properties:[
+
("message", "string", "The text message to include");
+
("color", "string", "Color for the image (hex format #RRGGBB)");
+
("frequency", "integer", "Frequency for the audio tone in Hz");
+
]
+
~schema_required:["message"]
+
(fun args ->
+
try
+
let message = get_param_string args "message" "Hello, multimodal world!" in
+
let color = get_param_string args "color" "#FF0000" in
+
let frequency = get_param_int args "frequency" 440 in
+
+
(* Generate image *)
+
let image_data = ImageGenerator.generate_simple_image 100 100 color in
+
let image_base64 = Base64.encode image_data in
+
+
(* Generate audio *)
+
let audio_data = AudioGenerator.generate_sine_wave
+
~frequency:(float_of_int frequency)
+
~duration:1.0
+
~sample_rate:8000
+
~amplitude:0.8 in
+
let audio_base64 = Base64.encode audio_data in
+
+
(* Create a response with text, image and audio content *)
+
CallToolResult.yojson_of_t CallToolResult.{
+
content = [
+
Text TextContent.{
+
text = message;
+
annotations = None
+
};
+
Image ImageContent.{
+
data = image_base64;
+
mime_type = "image/png";
+
annotations = None
+
};
+
Audio AudioContent.{
+
data = audio_base64;
+
mime_type = "audio/wav";
+
annotations = None
+
}
+
];
+
is_error = false;
+
meta = None
+
}
+
with
+
| Failure msg ->
+
Log.error (Printf.sprintf "Error in multimodal tool: %s" msg);
+
CallToolResult.yojson_of_t CallToolResult.{
+
content = [
+
Text TextContent.{
+
text = Printf.sprintf "Error: %s" msg;
+
annotations = None
+
}
+
];
+
is_error = true;
+
meta = None
+
}
+
)
+
+
(* Define and register a multimodal prompt *)
+
let _ = add_prompt server
+
~name:"multimodal-prompt"
+
~description:"A prompt that includes text, image, and audio"
+
~arguments:[
+
("message", Some "Text message to include", true);
+
("color", Some "Color for the image (hex format #RRGGBB)", false);
+
("frequency", Some "Frequency for the audio tone in Hz", false);
+
]
+
(fun args ->
+
(* Parse parameters with defaults *)
+
let message =
+
try List.assoc "message" args
+
with Not_found -> "Hello, multimodal world!"
+
in
+
+
let color =
+
try List.assoc "color" args
+
with Not_found -> "#0000FF"
+
in
+
+
let frequency =
+
try int_of_string (List.assoc "frequency" args)
+
with _ -> 440
+
in
+
+
(* Generate image *)
+
let image_data = ImageGenerator.generate_simple_image 100 100 color in
+
let image_base64 = Base64.encode image_data in
+
+
(* Generate audio *)
+
let audio_data = AudioGenerator.generate_sine_wave
+
~frequency:(float_of_int frequency)
+
~duration:1.0
+
~sample_rate:8000
+
~amplitude:0.8 in
+
let audio_base64 = Base64.encode audio_data in
+
+
(* Create a multimodal prompt *)
+
[
+
Prompt.{
+
role = `User;
+
content = make_text_content "Here's a multimodal message with text, image, and audio:"
+
};
+
Prompt.{
+
role = `User;
+
content = make_text_content message
+
};
+
Prompt.{
+
role = `User;
+
content = make_image_content image_base64 "image/png"
+
};
+
Prompt.{
+
role = `User;
+
content = make_audio_content audio_base64 "audio/wav"
+
};
+
Prompt.{
+
role = `Assistant;
+
content = make_text_content "I've received your multimodal message with text, image, and audio."
+
}
+
]
+
)
+
+
(* Also register a resource prompt example *)
+
let _ = add_prompt server
+
~name:"resource-prompt"
+
~description:"A prompt that includes embedded resources"
+
~arguments:[
+
("resource_id", Some "ID of the resource to include", true);
+
]
+
(fun args ->
+
(* Sample resource texts *)
+
let resources = [
+
("doc1", "This is the content of document 1.");
+
("doc2", "Document 2 contains important information about OCaml.");
+
("doc3", "Document 3 explains the MCP protocol in detail.");
+
] in
+
+
(* Get the requested resource *)
+
let resource_id =
+
try List.assoc "resource_id" args
+
with Not_found -> "doc1"
+
in
+
+
(* Find the resource content *)
+
let resource_content =
+
try List.assoc resource_id resources
+
with Not_found -> Printf.sprintf "Resource '%s' not found" resource_id
+
in
+
+
(* Create a prompt with embedded resource *)
+
[
+
Prompt.{
+
role = `User;
+
content = make_text_content (Printf.sprintf "Here's the content of resource %s:" resource_id)
+
};
+
Prompt.{
+
role = `User;
+
content = make_text_resource_content (Printf.sprintf "resource://%s" resource_id) resource_content ~mime_type:"text/plain" ()
+
};
+
Prompt.{
+
role = `User;
+
content = make_text_content "Please analyze this content."
+
};
+
Prompt.{
+
role = `Assistant;
+
content = make_text_content "I'll analyze the resource content for you."
+
}
+
]
+
)
+
+
(* Main function *)
+
let () =
+
(* Parse command line arguments *)
+
let transport_type = ref Stdio in
+
let args = [
+
("--http", Arg.Unit (fun () -> transport_type := Http),
+
"Start server with HTTP transport (default is stdio)");
+
] in
+
let usage_msg = "Usage: multimodal_example [--http]" in
+
Arg.parse args (fun _ -> ()) usage_msg;
+
+
(* Configure the server with appropriate capabilities *)
+
let server = configure_server server ~with_tools:true ~with_resources:false ~with_prompts:true () in
+
+
(* Create and start MCP server with the selected transport *)
+
let mcp_server = create ~server ~transport:!transport_type () in
+
start mcp_server;
-433
bin/multimodal_sdk.ml
···
-
open Mcp_sdk
-
-
(* 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)))
-
| _ -> 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)))
-
| _ -> 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 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
-
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)
-
(if i + 1 < len then encode_char ((n lsr 6) land 63) else '=');
-
Bytes.set result (j + 3)
-
(if i + 2 < len then encode_char (n land 63) else '=');
-
loop (i + 3) (j + 4)
-
in
-
Bytes.sub_string result 0 (loop 0 0)
-
end
-
-
(* 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 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) *)
-
-
(* 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 0x00);
-
Buffer.add_char buf (Char.chr 0x00);
-
(* Transparent color index *)
-
Buffer.add_char buf (Char.chr 0x00);
-
-
(* 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 0x00);
-
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 *)
-
-
(* 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 *)
-
-
(* 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 *)
-
in
-
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))
-
done;
-
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)
-
-
(* Helper to write 32-bit little endian integer *)
-
let write_int32_le buf n =
-
Buffer.add_char buf (Char.chr (n land 0xff));
-
Buffer.add_char buf (Char.chr ((n lsr 8) land 0xff));
-
Buffer.add_char buf (Char.chr ((n lsr 16) land 0xff));
-
Buffer.add_char buf (Char.chr ((n lsr 24) land 0xff))
-
-
(* Helper to write 16-bit little endian integer *)
-
let write_int16_le buf n =
-
Buffer.add_char buf (Char.chr (n land 0xff));
-
Buffer.add_char buf (Char.chr ((n lsr 8) land 0xff))
-
-
(* Generate a simple WAV file with sine wave *)
-
let generate_sine_wave_audio frequency duration =
-
(* WAV header *)
-
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 *)
-
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 *)
-
-
(* Data chunk *)
-
Buffer.add_string header_buf "data";
-
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 *)
-
-
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
-
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 ()
-
-
(* 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 ->
-
Logs.err (fun m -> m "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 ->
-
Logs.err (fun m -> m "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 ->
-
Logs.err (fun m -> m "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
-
{|
-
{
-
"greeting": "%s",
-
"image": {
-
"data": "%s",
-
"mimeType": "image/gif"
-
},
-
"audio": {
-
"data": "%s",
-
"mimeType": "audio/wav"
-
}
-
}
-
|}
-
greeting image_data audio_data
-
| _ -> Printf.sprintf {|{"error": "Invalid parameters"}|})
-
-
(* Run the server with the default scheduler *)
-
let () =
-
Logs.set_reporter (Logs.format_reporter ());
-
Random.self_init ();
-
(* Initialize random generator *)
-
Eio_main.run @@ fun env -> Mcp_server.run_stdio_server env server
-161
bin/ocaml_eval_sdk.ml
···
-
open Mcp_sdk
-
open Mcp_rpc
-
-
(* Set up the formatter for capturing evaluation output *)
-
let capture_output f =
-
let buffer = Buffer.create 1024 in
-
let fmt = Format.formatter_of_buffer buffer in
-
let result = f fmt in
-
Format.pp_print_flush fmt ();
-
(result, Buffer.contents buffer)
-
-
(* 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))
-
| _ -> 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"
-
-
(* Evaluate an OCaml toplevel phrase *)
-
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
-
-
(* Return structured result with status and captured output *)
-
if success then
-
`Assoc [ ("success", `Bool true); ("output", `String output) ]
-
else
-
`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
-
in
-
`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 ()
-
-
(* Toplevel environment state management *)
-
let toplevel_initialized = ref false
-
-
(* Initialize OCaml toplevel on first use *)
-
let ensure_toploop_initialized () =
-
if not !toplevel_initialized then
-
let _ = initialize_toploop () in
-
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
-
| _ -> 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 ->
-
Logs.err (fun m -> m "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 () =
-
Logs.set_reporter (Logs.format_reporter ());
-
Eio_main.run @@ fun env -> Mcp_server.run_stdio_server env server
+186
bin/resource_template_example.ml
···
+
open Mcp
+
open Mcp_sdk
+
open Mcp_server
+
+
(* 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)))
+
| _ -> raise (Failure "Expected JSON object")
+
+
(* Create a server *)
+
let server = create_server
+
~name:"OCaml MCP Resource Template Example"
+
~version:"0.1.0"
+
~protocol_version:"2024-11-05"
+
()
+
+
(* Define startup and shutdown hooks *)
+
let startup () =
+
(* Use stderr for direct printing to avoid interfering with JSON-RPC protocol *)
+
Printf.fprintf stderr "ResourceTemplateServer is starting up!\n";
+
flush stderr;
+
Log.info "ResourceTemplateServer is starting up!"
+
+
let shutdown () =
+
Printf.fprintf stderr "ResourceTemplateServer is shutting down. Goodbye!\n";
+
flush stderr;
+
Log.info "ResourceTemplateServer is shutting down. Goodbye!"
+
+
(* Register the hooks *)
+
let () =
+
set_startup_hook server startup;
+
set_shutdown_hook server shutdown
+
+
(* Database of "documents" *)
+
let documents = [
+
("doc1", "This is the first document content");
+
("doc2", "This document contains information about OCaml");
+
("doc3", "MCP protocol is a standard for LLM-based applications");
+
("doc4", "Resource templates allow for parameterized resources");
+
]
+
+
(* Define and register a resource template for documents *)
+
let _ = add_resource server
+
~uri_template:"document://{id}"
+
~description:"Get a document by ID"
+
~mime_type:"text/plain"
+
(fun params ->
+
match params with
+
| [id] ->
+
begin
+
try
+
let content = List.assoc id documents in
+
content
+
with Not_found ->
+
Printf.sprintf "Error: Document '%s' not found" id
+
end
+
| _ -> "Error: Invalid document ID"
+
)
+
+
(* Define and register a list documents resource *)
+
let _ = add_resource server
+
~uri_template:"documents://list"
+
~description:"List all available documents"
+
~mime_type:"text/plain"
+
(fun _ ->
+
let doc_list =
+
String.concat "\n"
+
(List.map (fun (id, _) -> Printf.sprintf "- %s" id) documents)
+
in
+
Printf.sprintf "Available Documents:\n%s" doc_list
+
)
+
+
(* Define and register a tool that uses resource references *)
+
let _ = add_tool server
+
~name:"get_document"
+
~description:"Gets a document by ID using resource references"
+
~schema_properties:[
+
("document_id", "string", "The ID of the document to retrieve");
+
]
+
~schema_required:["document_id"]
+
(fun args ->
+
try
+
let doc_id = get_string_param args "document_id" in
+
+
(* Create a resource reference *)
+
let ref = ResourceReference.{ uri = Printf.sprintf "document://%s" doc_id } in
+
(* Convert to JSON for logging purposes *)
+
let _ = ResourceReference.yojson_of_t ref in
+
+
(* Return the reference *)
+
CallToolResult.yojson_of_t CallToolResult.{
+
content = [
+
Text TextContent.{ text = Printf.sprintf "Resource reference for document %s:" doc_id; annotations = None };
+
Resource EmbeddedResource.{
+
resource = `Text TextResourceContents.{
+
uri = Printf.sprintf "document://%s" doc_id;
+
text = (try List.assoc doc_id documents with Not_found -> "Not found");
+
mime_type = Some "text/plain"
+
};
+
annotations = None
+
}
+
];
+
is_error = false;
+
meta = None
+
}
+
with
+
| Failure msg ->
+
Log.error (Printf.sprintf "Error in get_document tool: %s" msg);
+
CallToolResult.yojson_of_t CallToolResult.{
+
content = [
+
Text TextContent.{
+
text = Printf.sprintf "Error: %s" msg;
+
annotations = None
+
}
+
];
+
is_error = true;
+
meta = None
+
}
+
)
+
+
(* Define and register a prompt that uses resource templates *)
+
let _ = add_prompt server
+
~name:"document-prompt"
+
~description:"A prompt that references document resources"
+
~arguments:[
+
("document_id", Some "ID of the document to include in the prompt", true);
+
]
+
(fun args ->
+
let doc_id =
+
try List.assoc "document_id" args
+
with Not_found -> "doc1" (* Default to doc1 *)
+
in
+
+
let doc_text =
+
try List.assoc doc_id documents
+
with Not_found -> Printf.sprintf "Document '%s' not found" doc_id
+
in
+
+
[
+
Prompt.{
+
role = `User;
+
content = make_text_content (Printf.sprintf "Please summarize the following document (ID: %s):" doc_id)
+
};
+
Prompt.{
+
role = `User;
+
content = Resource EmbeddedResource.{
+
resource = `Text TextResourceContents.{
+
uri = Printf.sprintf "document://%s" doc_id;
+
text = doc_text;
+
mime_type = Some "text/plain"
+
};
+
annotations = None
+
}
+
};
+
Prompt.{
+
role = `Assistant;
+
content = make_text_content "I'll help summarize this document for you."
+
}
+
]
+
)
+
+
(* Main function *)
+
let () =
+
(* Parse command line arguments *)
+
let transport_type = ref Stdio in
+
let args = [
+
("--http", Arg.Unit (fun () -> transport_type := Http),
+
"Start server with HTTP transport (default is stdio)");
+
] in
+
let usage_msg = "Usage: resource_template_example [--http]" in
+
Arg.parse args (fun _ -> ()) usage_msg;
+
+
(* Instead of printing directly to stdout which messes up the JSON-RPC protocol,
+
use the logging system which sends output to stderr *)
+
Log.info "Starting ResourceTemplateServer...";
+
+
(* Configure the server with appropriate capabilities *)
+
ignore (configure_server server ());
+
+
(* Create and start MCP server with the selected transport *)
+
let mcp_server = create ~server ~transport:!transport_type () in
+
start mcp_server
+365
bin/server.ml
···
+
open Mcp
+
open Jsonrpc
+
+
(* Logging utilities *)
+
let log_debug msg =
+
Printf.eprintf "[DEBUG] %s\n" msg;
+
flush stderr
+
+
let log_error msg =
+
Printf.eprintf "[ERROR] %s\n" msg;
+
flush stderr
+
+
(* Server state *)
+
let protocol_version = "2024-11-05"
+
let server_info = Implementation.{ name = "ocaml-mcp-capitalizer"; version = "0.1.0" }
+
let server_capabilities = `Assoc [
+
(* We support tools *)
+
("tools", `Assoc [
+
("listChanged", `Bool true)
+
]);
+
(* We don't support resources - make this explicit *)
+
("resources", `Assoc [
+
("listChanged", `Bool false);
+
("subscribe", `Bool false)
+
]);
+
(* We don't support prompts - make this explicit *)
+
("prompts", `Assoc [
+
("listChanged", `Bool false)
+
])
+
]
+
+
(* Tool implementation *)
+
module CapitalizeTool = struct
+
let name = "capitalize"
+
let description = "Capitalizes the provided text"
+
let input_schema = `Assoc [
+
("type", `String "object");
+
("properties", `Assoc [
+
("text", `Assoc [
+
("type", `String "string");
+
("description", `String "The text to capitalize")
+
])
+
]);
+
("required", `List [`String "text"])
+
]
+
+
let call json =
+
match json with
+
| `Assoc fields ->
+
(match List.assoc_opt "text" fields with
+
| Some (`String text) ->
+
let capitalized_text = String.uppercase_ascii text in
+
let content = TextContent.{
+
text = capitalized_text;
+
annotations = None
+
} in
+
`Assoc [
+
("content", `List [TextContent.yojson_of_t content]);
+
("isError", `Bool false)
+
]
+
| _ ->
+
let error_content = TextContent.{
+
text = "Missing or invalid 'text' parameter";
+
annotations = None
+
} in
+
`Assoc [
+
("content", `List [TextContent.yojson_of_t error_content]);
+
("isError", `Bool true)
+
])
+
| _ ->
+
let error_content = TextContent.{
+
text = "Invalid arguments format";
+
annotations = None
+
} in
+
`Assoc [
+
("content", `List [TextContent.yojson_of_t error_content]);
+
("isError", `Bool true)
+
]
+
end
+
+
(* Handle tool listing *)
+
let list_tools () =
+
let tool = `Assoc [
+
("name", `String CapitalizeTool.name);
+
("description", `String CapitalizeTool.description);
+
("inputSchema", CapitalizeTool.input_schema)
+
] in
+
`Assoc [
+
("tools", `List [tool])
+
]
+
+
(* Handle tool calls *)
+
let call_tool name args =
+
if name = CapitalizeTool.name then
+
CapitalizeTool.call args
+
else
+
let error_content = TextContent.{
+
text = Printf.sprintf "Unknown tool: %s" name;
+
annotations = None
+
} in
+
`Assoc [
+
("content", `List [TextContent.yojson_of_t error_content]);
+
("isError", `Bool true)
+
]
+
+
(* Handle initialization *)
+
let handle_initialize id json =
+
try
+
log_debug (Printf.sprintf "Processing initialize request with id: %s"
+
(match id with
+
| `Int i -> string_of_int i
+
| `String s -> s));
+
+
log_debug (Printf.sprintf "Initialize params: %s"
+
(match json with
+
| Some j -> Yojson.Safe.to_string j
+
| None -> "null"));
+
+
let _ = match json with
+
| Some params ->
+
log_debug "Parsing initialize request params...";
+
let req = Initialize.Request.t_of_yojson params in
+
log_debug (Printf.sprintf "Client info: %s v%s" req.client_info.name req.client_info.version);
+
log_debug (Printf.sprintf "Client protocol version: %s" req.protocol_version);
+
+
(* Check protocol version compatibility *)
+
if req.protocol_version <> protocol_version then
+
log_debug (Printf.sprintf "Protocol version mismatch: client=%s server=%s - will use server version"
+
req.protocol_version protocol_version);
+
+
req
+
| None ->
+
log_error "Missing params for initialize request";
+
raise (Json.Of_json ("Missing params for initialize request", `Null))
+
in
+
+
log_debug "Creating initialize response...";
+
let result = Initialize.Result.create
+
~capabilities:server_capabilities
+
~server_info
+
~protocol_version
+
~instructions:"This server provides a tool to capitalize text."
+
()
+
in
+
+
log_debug "Serializing initialize response...";
+
let response = create_response ~id ~result:(Initialize.Result.yojson_of_t result) in
+
log_debug "Initialize response created successfully";
+
response
+
with
+
| Json.Of_json (msg, _) ->
+
log_error (Printf.sprintf "JSON error in initialize: %s" msg);
+
create_error ~id ~code:(-32602) ~message:("Invalid params: " ^ msg) ()
+
| exc ->
+
log_error (Printf.sprintf "Exception in initialize: %s" (Printexc.to_string exc));
+
log_error (Printf.sprintf "Backtrace: %s" (Printexc.get_backtrace()));
+
create_error ~id ~code:(-32603) ~message:("Internal error: " ^ (Printexc.to_string exc)) ()
+
+
(* Handle tools/list *)
+
let handle_list_tools id =
+
log_debug "Processing tools/list request";
+
let result = list_tools () in
+
log_debug (Printf.sprintf "Tools list result: %s" (Yojson.Safe.to_string result));
+
create_response ~id ~result
+
+
(* Handle tools/call *)
+
let handle_call_tool id json =
+
try
+
log_debug (Printf.sprintf "Processing tool call request with id: %s"
+
(match id with
+
| `Int i -> string_of_int i
+
| `String s -> s));
+
+
log_debug (Printf.sprintf "Tool call params: %s"
+
(match json with
+
| Some j -> Yojson.Safe.to_string j
+
| None -> "null"));
+
+
match json with
+
| Some (`Assoc params) ->
+
let name = match List.assoc_opt "name" params with
+
| Some (`String name) ->
+
log_debug (Printf.sprintf "Tool name: %s" name);
+
name
+
| _ ->
+
log_error "Missing or invalid 'name' parameter in tool call";
+
raise (Json.Of_json ("Missing or invalid 'name' parameter", `Assoc params))
+
in
+
let args = match List.assoc_opt "arguments" params with
+
| Some (args) ->
+
log_debug (Printf.sprintf "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 *)
+
in
+
log_debug (Printf.sprintf "Calling tool: %s" name);
+
let result = call_tool name args in
+
log_debug (Printf.sprintf "Tool call result: %s" (Yojson.Safe.to_string result));
+
create_response ~id ~result
+
| _ ->
+
log_error "Invalid params format for tools/call";
+
create_error ~id ~code:(-32602) ~message:"Invalid params for tools/call" ()
+
with
+
| Json.Of_json (msg, _) ->
+
log_error (Printf.sprintf "JSON error in tool call: %s" msg);
+
create_error ~id ~code:(-32602) ~message:("Invalid params: " ^ msg) ()
+
| exc ->
+
log_error (Printf.sprintf "Exception in tool call: %s" (Printexc.to_string exc));
+
log_error (Printf.sprintf "Backtrace: %s" (Printexc.get_backtrace()));
+
create_error ~id ~code:(-32603) ~message:("Internal error: " ^ (Printexc.to_string exc)) ()
+
+
(* Handle ping *)
+
let handle_ping id =
+
create_response ~id ~result:(`Assoc [])
+
+
(* Process a single message *)
+
let process_message message =
+
try
+
log_debug "Parsing message as JSONRPC message...";
+
match JSONRPCMessage.t_of_yojson message with
+
| JSONRPCMessage.Request req ->
+
log_debug (Printf.sprintf "Received request with method: %s" req.method_);
+
(match req.method_ with
+
| "initialize" ->
+
log_debug "Processing initialize request";
+
Some (handle_initialize req.id req.params)
+
| "tools/list" ->
+
log_debug "Processing tools/list request";
+
Some (handle_list_tools req.id)
+
| "tools/call" ->
+
log_debug "Processing tools/call request";
+
Some (handle_call_tool req.id req.params)
+
| "ping" ->
+
log_debug "Processing ping request";
+
Some (handle_ping req.id)
+
| _ ->
+
log_error (Printf.sprintf "Unknown method received: %s" req.method_);
+
Some (create_error ~id:req.id ~code:(-32601) ~message:("Method not found: " ^ req.method_) ()))
+
| JSONRPCMessage.Notification notif ->
+
log_debug (Printf.sprintf "Received notification with method: %s" notif.method_);
+
(match notif.method_ with
+
| "notifications/initialized" ->
+
log_debug "Client initialization complete - Server is now ready to receive requests";
+
log_debug (Printf.sprintf "Notification params: %s"
+
(match notif.params with
+
| Some p -> Yojson.Safe.to_string p
+
| None -> "null"));
+
None
+
| _ ->
+
log_debug (Printf.sprintf "Ignoring notification: %s" notif.method_);
+
None)
+
| JSONRPCMessage.Response _ ->
+
log_error "Unexpected response message received";
+
None
+
| JSONRPCMessage.Error _ ->
+
log_error "Unexpected error message received";
+
None
+
with
+
| exc ->
+
log_error (Printf.sprintf "Exception during message processing: %s" (Printexc.to_string exc));
+
log_error (Printf.sprintf "Backtrace: %s" (Printexc.get_backtrace()));
+
log_error (Printf.sprintf "Message was: %s" (Yojson.Safe.to_string message));
+
None
+
+
(* Main loop *)
+
let rec read_message () =
+
try
+
log_debug "Attempting to read line from stdin...";
+
let line = read_line () in
+
if line = "" then (
+
log_debug "Empty line received, ignoring";
+
None
+
) else (
+
log_debug (Printf.sprintf "Raw input: %s" line);
+
try
+
let json = Yojson.Safe.from_string line in
+
log_debug "Successfully parsed JSON";
+
Some json
+
with
+
| Yojson.Json_error msg ->
+
log_error (Printf.sprintf "Error parsing JSON: %s" msg);
+
log_error (Printf.sprintf "Input was: %s" line);
+
read_message ()
+
)
+
with
+
| End_of_file ->
+
log_debug "End of file received on stdin";
+
None
+
| Sys_error msg ->
+
log_error (Printf.sprintf "System error while reading: %s" msg);
+
None
+
| exc ->
+
log_error (Printf.sprintf "Exception while reading: %s" (Printexc.to_string exc));
+
None
+
+
let () =
+
try
+
(* Enable exception backtraces *)
+
Printexc.record_backtrace true;
+
+
(* Enable line buffering for stdout *)
+
set_binary_mode_out stdout false;
+
+
log_debug "MCP Capitalizer server started";
+
log_debug (Printf.sprintf "Protocol version: %s" protocol_version);
+
log_debug (Printf.sprintf "Server info: %s v%s" server_info.name server_info.version);
+
+
(* Print environment info for debugging *)
+
log_debug "Environment variables:";
+
Unix.environment()
+
|> Array.iter (fun s ->
+
try
+
let i = String.index s '=' in
+
let name = String.sub s 0 i in
+
if String.length name > 0 then
+
log_debug (Printf.sprintf " %s" s)
+
with Not_found -> ()
+
);
+
+
let rec server_loop count =
+
log_debug (Printf.sprintf "Waiting for message #%d..." count);
+
match read_message () with
+
| Some json ->
+
log_debug (Printf.sprintf "Received message: %s" (Yojson.Safe.to_string json));
+
(match process_message json with
+
| Some response ->
+
let response_json = JSONRPCMessage.yojson_of_t response in
+
let response_str = Yojson.Safe.to_string response_json in
+
log_debug (Printf.sprintf "Sending response: %s" response_str);
+
(* Make sure we emit properly formatted JSON on a single line with a newline at the end *)
+
Printf.printf "%s\n" response_str;
+
flush stdout;
+
(* Give the client a moment to process the response *)
+
Unix.sleepf 0.01;
+
server_loop (count + 1)
+
| None ->
+
log_debug "No response needed for this message";
+
server_loop (count + 1))
+
| None ->
+
log_debug "End of input stream, terminating server";
+
()
+
in
+
+
log_debug "Starting server loop...";
+
log_debug "Waiting for the initialize request...";
+
+
(* Set up signal handler to gracefully exit *)
+
Sys.(set_signal sigint (Signal_handle (fun _ ->
+
log_debug "Received interrupt signal, exiting...";
+
exit 0
+
)));
+
+
server_loop 1;
+
log_debug "Server terminated normally";
+
with
+
| End_of_file ->
+
log_error "Unexpected end of file";
+
| Sys_error msg ->
+
log_error (Printf.sprintf "System error: %s" msg);
+
| Unix.Unix_error(err, func, arg) ->
+
log_error (Printf.sprintf "Unix error in %s(%s): %s" func arg (Unix.error_message err));
+
| exc ->
+
log_error (Printf.sprintf "Unhandled exception: %s" (Printexc.to_string exc));
+
log_error (Printf.sprintf "Backtrace: %s" (Printexc.get_backtrace()))
+5
bin/server.mli
···
+
val process_message : Jsonrpc.Json.t -> Mcp.JSONRPCMessage.t option
+
val handle_initialize : Jsonrpc.Id.t -> Jsonrpc.Json.t option -> Mcp.JSONRPCMessage.t
+
val handle_list_tools : Jsonrpc.Id.t -> Mcp.JSONRPCMessage.t
+
val handle_call_tool : Jsonrpc.Id.t -> Jsonrpc.Json.t option -> Mcp.JSONRPCMessage.t
+
val handle_ping : Jsonrpc.Id.t -> Mcp.JSONRPCMessage.t
-20
dune-project
···
(lang dune 3.17)
-
(name mcp)
-
-
(license ISC)
-
(authors "Anil Madhavapeddy")
-
(maintainers "anil@recoil.org")
-
-
(generate_opam_files true)
-
-
(package
-
(name mcp)
-
(synopsis "Model Context Protocol for LLMs")
-
(description "This is all still a work in progress")
-
(depends
-
(ocaml (>= "5.2.0"))
-
jsonrpc
-
http
-
cohttp-eio
-
eio_main
-
eio
-
logs))
+11 -22
lib/dune
···
(library
-
(name mcp)
-
(public_name mcp)
-
(libraries jsonrpc unix yojson)
-
(modules mcp))
+
(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_sdk)
+
(libraries mcp jsonrpc unix yojson)
+
(modules mcp_sdk)
+
(flags (:standard -w -67 -w -27 -w -32)))
(library
-
(name mcp_sdk)
-
(public_name mcp.sdk)
-
(libraries mcp mcp_rpc jsonrpc unix yojson logs logs.fmt)
-
(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)
+
(libraries mcp mcp_sdk jsonrpc unix yojson)
+
(modules mcp_server)
+
(flags (:standard -w -67 -w -27 -w -32 -w -8 -w -11 -w -33)))
+899 -655
lib/mcp.ml
···
open Jsonrpc
-
(* Utility functions for JSON parsing *)
-
module Util = struct
-
(* Helper to raise a Json.Of_json exception with formatted message *)
-
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)
-
-
(* 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
-
end
-
-
(* Error codes for JSON-RPC *)
+
(* Standard error codes *)
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 *)
-
| CustomError of int (* For any other error codes *)
-
-
(* Convert the error code to its integer representation *)
-
let to_int = function
-
| ParseError -> -32700
-
| InvalidRequest -> -32600
-
| MethodNotFound -> -32601
-
| InvalidParams -> -32602
-
| InternalError -> -32603
-
| ResourceNotFound -> -32002
-
| AuthRequired -> -32001
-
| CustomError code -> code
-
-
(* Get error message for standard error codes *)
-
let to_message = function
-
| ParseError -> "Parse error"
-
| InvalidRequest -> "Invalid Request"
-
| MethodNotFound -> "Method not found"
-
| InvalidParams -> "Invalid params"
-
| InternalError -> "Internal error"
-
| ResourceNotFound -> "Resource not found"
-
| AuthRequired -> "Authentication required"
-
| CustomError _ -> "Error"
-
end
-
-
(* Protocol method types *)
-
module Method = struct
-
(* Method type representing all MCP protocol methods *)
-
type t =
-
(* Initialization and lifecycle methods *)
-
| Initialize
-
| Initialized
-
(* Resource methods *)
-
| ResourcesList
-
| ResourcesRead
-
| ResourceTemplatesList
-
| 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"
-
| Initialized -> "notifications/initialized"
-
| ResourcesList -> "resources/list"
-
| ResourcesRead -> "resources/read"
-
| ResourceTemplatesList -> "resources/templates/list"
-
| ResourcesSubscribe -> "resources/subscribe"
-
| ResourcesListChanged -> "notifications/resources/list_changed"
-
| ResourcesUpdated -> "notifications/resources/updated"
-
| ToolsList -> "tools/list"
-
| ToolsCall -> "tools/call"
-
| ToolsListChanged -> "notifications/tools/list_changed"
-
| PromptsList -> "prompts/list"
-
| PromptsGet -> "prompts/get"
-
| PromptsListChanged -> "notifications/prompts/list_changed"
-
| Progress -> "notifications/progress"
-
-
(* Convert string to method type *)
-
let of_string = function
-
| "initialize" -> Initialize
-
| "notifications/initialized" -> Initialized
-
| "resources/list" -> ResourcesList
-
| "resources/read" -> ResourcesRead
-
| "resources/templates/list" -> ResourceTemplatesList
-
| "resources/subscribe" -> ResourcesSubscribe
-
| "notifications/resources/list_changed" -> ResourcesListChanged
-
| "notifications/resources/updated" -> ResourcesUpdated
-
| "tools/list" -> ToolsList
-
| "tools/call" -> ToolsCall
-
| "notifications/tools/list_changed" -> ToolsListChanged
-
| "prompts/list" -> PromptsList
-
| "prompts/get" -> PromptsGet
-
| "notifications/prompts/list_changed" -> PromptsListChanged
-
| "notifications/progress" -> Progress
-
| s -> failwith ("Unknown MCP method: " ^ s)
+
let parse_error = -32700
+
let invalid_request = -32600
+
let method_not_found = -32601
+
let invalid_params = -32602
+
let internal_error = -32603
+
let resource_not_found = -32002
+
let server_error_start = -32000
+
let server_error_end = -32099
end
(* Common types *)
···
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
| "assistant" -> `Assistant
-
| s -> Util.json_error "Unknown role: %s" (`String s) s
+
| s -> raise (Json.Of_json ("Unknown role: " ^ s, `String 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
+
| j -> raise (Json.Of_json ("Expected string for Role", j))
end
module ProgressToken = struct
···
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
+
| j -> raise (Json.Of_json ("Expected string for Cursor", j))
end
(* 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 }
-
| j -> Util.json_error "Expected object for annotation" j
+
let audience = List.assoc_opt "audience" fields |> Option.map (function
+
| `List items -> List.map Role.t_of_yojson items
+
| j -> raise (Json.Of_json ("Expected list for audience", j))
+
) in
+
let priority = List.assoc_opt "priority" fields |> Option.map (function
+
| `Float f -> f
+
| j -> raise (Json.Of_json ("Expected float for priority", j))
+
) in
+
{ audience; priority }
+
| j -> raise (Json.Of_json ("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 }
-
| j -> Util.json_error "Expected object for Annotated" j
+
let annotations = List.assoc_opt "annotations" fields |> Option.map annotation_of_yojson in
+
{ annotations }
+
| j -> raise (Json.Of_json ("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 }
-
| j -> Util.json_error "Expected object for TextContent" j
+
| `Assoc fields ->
+
let text = match List.assoc_opt "text" fields with
+
| Some (`String s) -> s
+
| _ -> raise (Json.Of_json ("Missing or invalid 'text' field", `Assoc fields))
+
in
+
let _ = match List.assoc_opt "type" fields with
+
| Some (`String "text") -> ()
+
| _ -> raise (Json.Of_json ("Missing or invalid 'type' field", `Assoc fields))
+
in
+
let annotations = List.assoc_opt "annotations" fields |> Option.map Annotated.annotation_of_yojson in
+
{ text; annotations }
+
| j -> raise (Json.Of_json ("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 = [
+
("data", `String data);
+
("mimeType", `String mime_type);
+
("type", `String "image");
+
] 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 }
-
| j -> Util.json_error "Expected object for ImageContent" j
+
| `Assoc fields ->
+
let data = match List.assoc_opt "data" fields with
+
| Some (`String s) -> s
+
| _ -> raise (Json.Of_json ("Missing or invalid 'data' field", `Assoc fields))
+
in
+
let mime_type = match List.assoc_opt "mimeType" fields with
+
| Some (`String s) -> s
+
| _ -> raise (Json.Of_json ("Missing or invalid 'mimeType' field", `Assoc fields))
+
in
+
let _ = match List.assoc_opt "type" fields with
+
| Some (`String "image") -> ()
+
| _ -> raise (Json.Of_json ("Missing or invalid 'type' field", `Assoc fields))
+
in
+
let annotations = List.assoc_opt "annotations" fields |> Option.map Annotated.annotation_of_yojson in
+
{ data; mime_type; annotations }
+
| j -> raise (Json.Of_json ("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 = [
+
("data", `String data);
+
("mimeType", `String mime_type);
+
("type", `String "audio");
+
] 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 }
-
| j -> Util.json_error "Expected object for AudioContent" j
+
| `Assoc fields ->
+
let data = match List.assoc_opt "data" fields with
+
| Some (`String s) -> s
+
| _ -> raise (Json.Of_json ("Missing or invalid 'data' field", `Assoc fields))
+
in
+
let mime_type = match List.assoc_opt "mimeType" fields with
+
| Some (`String s) -> s
+
| _ -> raise (Json.Of_json ("Missing or invalid 'mimeType' field", `Assoc fields))
+
in
+
let _ = match List.assoc_opt "type" fields with
+
| Some (`String "audio") -> ()
+
| _ -> raise (Json.Of_json ("Missing or invalid 'type' field", `Assoc fields))
+
in
+
let annotations = List.assoc_opt "annotations" fields |> Option.map Annotated.annotation_of_yojson in
+
{ data; mime_type; annotations }
+
| j -> raise (Json.Of_json ("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
`Assoc assoc
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 }
-
| j -> Util.json_error "Expected object for ResourceContents" j
+
| `Assoc fields ->
+
let uri = match List.assoc_opt "uri" fields with
+
| Some (`String s) -> s
+
| _ -> raise (Json.Of_json ("Missing or invalid 'uri' field", `Assoc fields))
+
in
+
let mime_type = List.assoc_opt "mimeType" fields |> Option.map (function
+
| `String s -> s
+
| j -> raise (Json.Of_json ("Expected string for mimeType", j))
+
) in
+
{ uri; mime_type }
+
| j -> raise (Json.Of_json ("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
`Assoc assoc
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 }
-
| j -> Util.json_error "Expected object for TextResourceContents" j
+
| `Assoc fields ->
+
let uri = match List.assoc_opt "uri" fields with
+
| Some (`String s) -> s
+
| _ -> raise (Json.Of_json ("Missing or invalid 'uri' field", `Assoc fields))
+
in
+
let text = match List.assoc_opt "text" fields with
+
| Some (`String s) -> s
+
| _ -> raise (Json.Of_json ("Missing or invalid 'text' field", `Assoc fields))
+
in
+
let mime_type = List.assoc_opt "mimeType" fields |> Option.map (function
+
| `String s -> s
+
| j -> raise (Json.Of_json ("Expected string for mimeType", j))
+
) in
+
{ uri; text; mime_type }
+
| j -> raise (Json.Of_json ("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
`Assoc assoc
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 }
-
| j -> Util.json_error "Expected object for BlobResourceContents" j
+
| `Assoc fields ->
+
let uri = match List.assoc_opt "uri" fields with
+
| Some (`String s) -> s
+
| _ -> raise (Json.Of_json ("Missing or invalid 'uri' field", `Assoc fields))
+
in
+
let blob = match List.assoc_opt "blob" fields with
+
| Some (`String s) -> s
+
| _ -> raise (Json.Of_json ("Missing or invalid 'blob' field", `Assoc fields))
+
in
+
let mime_type = List.assoc_opt "mimeType" fields |> Option.map (function
+
| `String s -> s
+
| j -> raise (Json.Of_json ("Expected string for mimeType", j))
+
) in
+
{ uri; blob; mime_type }
+
| j -> raise (Json.Of_json ("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))
+
| `Assoc fields ->
+
let _ = match List.assoc_opt "type" fields with
+
| Some (`String "resource") -> ()
+
| _ -> raise (Json.Of_json ("Missing or invalid 'type' field", `Assoc fields))
+
in
+
let resource = match List.assoc_opt "resource" fields with
+
| Some (`Assoc res_fields) ->
+
if List.mem_assoc "text" res_fields then
+
`Text (TextResourceContents.t_of_yojson (`Assoc res_fields))
+
else if List.mem_assoc "blob" res_fields then
+
`Blob (BlobResourceContents.t_of_yojson (`Assoc res_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
+
raise (Json.Of_json ("Invalid resource content", `Assoc res_fields))
+
| _ -> raise (Json.Of_json ("Missing or invalid 'resource' field", `Assoc fields))
+
in
+
let annotations = List.assoc_opt "annotations" fields |> Option.map Annotated.annotation_of_yojson in
+
{ resource; annotations }
+
| j -> raise (Json.Of_json ("Expected object for EmbeddedResource", j))
+
end
+
+
(** Tool definition *)
+
module Tool = struct
+
type t = {
+
name: string;
+
description: string option;
+
input_schema: Json.t;
+
}
+
+
let yojson_of_t { name; description; input_schema } =
+
let assoc = [
+
("name", `String name);
+
("inputSchema", input_schema);
+
] in
+
let assoc = match description with
+
| Some desc -> ("description", `String desc) :: assoc
+
| None -> assoc
+
in
+
`Assoc assoc
+
+
let t_of_yojson = function
+
| `Assoc fields ->
+
let name = match List.assoc_opt "name" fields with
+
| Some (`String s) -> s
+
| _ -> raise (Json.Of_json ("Missing or invalid 'name' field", `Assoc fields))
+
in
+
let description = match List.assoc_opt "description" fields with
+
| Some (`String s) -> Some s
+
| _ -> None
+
in
+
let input_schema = match List.assoc_opt "inputSchema" fields with
+
| Some json -> json
+
| _ -> raise (Json.Of_json ("Missing 'inputSchema' field", `Assoc fields))
+
in
+
{ name; description; input_schema }
+
| j -> raise (Json.Of_json ("Expected object for Tool", 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)
-
| j -> Util.json_error "Expected object for content" j
+
| `Assoc fields ->
+
(match List.assoc_opt "type" fields with
+
| Some (`String "text") -> Text (TextContent.t_of_yojson (`Assoc fields))
+
| Some (`String "image") -> Image (ImageContent.t_of_yojson (`Assoc fields))
+
| Some (`String "audio") -> Audio (AudioContent.t_of_yojson (`Assoc fields))
+
| Some (`String "resource") -> Resource (EmbeddedResource.t_of_yojson (`Assoc fields))
+
| _ -> raise (Json.Of_json ("Invalid or missing content type", `Assoc fields)))
+
| j -> raise (Json.Of_json ("Expected object for content", j))
+
+
(** Tool result *)
+
module CallToolResult = struct
+
type t = {
+
content: content list;
+
is_error: bool;
+
meta: Json.t option;
+
}
+
+
let yojson_of_t { content; is_error; meta } =
+
let assoc = [
+
("content", `List (List.map yojson_of_content content));
+
("isError", `Bool is_error);
+
] in
+
let assoc = match meta with
+
| Some meta_json -> ("_meta", meta_json) :: assoc
+
| None -> assoc
+
in
+
`Assoc assoc
+
+
let t_of_yojson = function
+
| `Assoc fields ->
+
let content = match List.assoc_opt "content" fields with
+
| Some (`List items) -> List.map content_of_yojson items
+
| _ -> raise (Json.Of_json ("Missing or invalid 'content' field", `Assoc fields))
+
in
+
let is_error = match List.assoc_opt "isError" fields with
+
| Some (`Bool b) -> b
+
| None -> false (* Default to false if not specified *)
+
| _ -> raise (Json.Of_json ("Invalid 'isError' field", `Assoc fields))
+
in
+
let meta = List.assoc_opt "_meta" fields in
+
{ content; is_error; meta }
+
| j -> raise (Json.Of_json ("Expected object for CallToolResult", j))
+
end
+
+
(** Resource definition *)
+
module Resource = struct
+
type t = {
+
name: string;
+
uri: string;
+
description: string option;
+
mime_type: string option;
+
size: int option;
+
annotations: Annotated.annotation option;
+
}
+
+
let yojson_of_t { name; uri; description; mime_type; size; annotations } =
+
let assoc = [
+
("name", `String name);
+
("uri", `String uri);
+
] in
+
let assoc = match description with
+
| Some desc -> ("description", `String desc) :: assoc
+
| None -> assoc
+
in
+
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
+
in
+
let assoc = match annotations with
+
| Some ann -> ("annotations", Annotated.yojson_of_annotation ann) :: assoc
+
| None -> assoc
+
in
+
`Assoc assoc
+
+
let t_of_yojson = function
+
| `Assoc fields ->
+
let name = match List.assoc_opt "name" fields with
+
| Some (`String s) -> s
+
| _ -> raise (Json.Of_json ("Missing or invalid 'name' field", `Assoc fields))
+
in
+
let uri = match List.assoc_opt "uri" fields with
+
| Some (`String s) -> s
+
| _ -> raise (Json.Of_json ("Missing or invalid 'uri' field", `Assoc fields))
+
in
+
let description = match List.assoc_opt "description" fields with
+
| Some (`String s) -> Some s
+
| _ -> None
+
in
+
let mime_type = match List.assoc_opt "mimeType" fields with
+
| Some (`String s) -> Some s
+
| _ -> None
+
in
+
let size = match List.assoc_opt "size" fields with
+
| Some (`Int s) -> Some s
+
| _ -> None
+
in
+
let annotations = match List.assoc_opt "annotations" fields with
+
| Some json -> Some (Annotated.annotation_of_yojson json)
+
| _ -> None
+
in
+
{ name; uri; description; mime_type; size; annotations }
+
| j -> raise (Json.Of_json ("Expected object for Resource", j))
+
end
+
+
(** Resource Template definition *)
+
module ResourceTemplate = struct
+
type t = {
+
name: string;
+
uri_template: string;
+
description: string option;
+
mime_type: string option;
+
annotations: Annotated.annotation option;
+
}
+
+
let yojson_of_t { name; uri_template; description; mime_type; annotations } =
+
let assoc = [
+
("name", `String name);
+
("uriTemplate", `String uri_template);
+
] in
+
let assoc = match description with
+
| Some desc -> ("description", `String desc) :: assoc
+
| None -> assoc
+
in
+
let assoc = match mime_type with
+
| Some mime -> ("mimeType", `String mime) :: assoc
+
| None -> assoc
+
in
+
let assoc = match annotations with
+
| Some ann -> ("annotations", Annotated.yojson_of_annotation ann) :: assoc
+
| None -> assoc
+
in
+
`Assoc assoc
+
+
let t_of_yojson = function
+
| `Assoc fields ->
+
let name = match List.assoc_opt "name" fields with
+
| Some (`String s) -> s
+
| _ -> raise (Json.Of_json ("Missing or invalid 'name' field", `Assoc fields))
+
in
+
let uri_template = match List.assoc_opt "uriTemplate" fields with
+
| Some (`String s) -> s
+
| _ -> raise (Json.Of_json ("Missing or invalid 'uriTemplate' field", `Assoc fields))
+
in
+
let description = match List.assoc_opt "description" fields with
+
| Some (`String s) -> Some s
+
| _ -> None
+
in
+
let mime_type = match List.assoc_opt "mimeType" fields with
+
| Some (`String s) -> Some s
+
| _ -> None
+
in
+
let annotations = match List.assoc_opt "annotations" fields with
+
| Some json -> Some (Annotated.annotation_of_yojson json)
+
| _ -> None
+
in
+
{ name; uri_template; description; mime_type; annotations }
+
| j -> raise (Json.Of_json ("Expected object for ResourceTemplate", j))
+
end
+
+
(** Resource Reference *)
+
module ResourceReference = struct
+
type t = {
+
uri: string;
+
}
+
+
let yojson_of_t { uri } =
+
`Assoc [
+
("type", `String "ref/resource");
+
("uri", `String uri);
+
]
+
+
let t_of_yojson = function
+
| `Assoc fields ->
+
let _ = match List.assoc_opt "type" fields with
+
| Some (`String "ref/resource") -> ()
+
| _ -> raise (Json.Of_json ("Missing or invalid 'type' field", `Assoc fields))
+
in
+
let uri = match List.assoc_opt "uri" fields with
+
| Some (`String s) -> s
+
| _ -> raise (Json.Of_json ("Missing or invalid 'uri' field", `Assoc fields))
+
in
+
{ uri }
+
| j -> raise (Json.Of_json ("Expected object for ResourceReference", j))
+
end
+
+
(** Prompt Reference *)
+
module PromptReference = struct
+
type t = {
+
name: string;
+
}
+
+
let yojson_of_t { name } =
+
`Assoc [
+
("type", `String "ref/prompt");
+
("name", `String name);
+
]
+
+
let t_of_yojson = function
+
| `Assoc fields ->
+
let _ = match List.assoc_opt "type" fields with
+
| Some (`String "ref/prompt") -> ()
+
| _ -> raise (Json.Of_json ("Missing or invalid 'type' field", `Assoc fields))
+
in
+
let name = match List.assoc_opt "name" fields with
+
| Some (`String s) -> s
+
| _ -> raise (Json.Of_json ("Missing or invalid 'name' field", `Assoc fields))
+
in
+
{ name }
+
| j -> raise (Json.Of_json ("Expected object for PromptReference", j))
+
end
+
+
(** Completion support *)
+
module Completion = struct
+
+
module Argument = struct
+
type t = {
+
name: string;
+
value: string;
+
}
+
+
let yojson_of_t { name; value } =
+
`Assoc [
+
("name", `String name);
+
("value", `String value);
+
]
+
+
let t_of_yojson = function
+
| `Assoc fields ->
+
let name = match List.assoc_opt "name" fields with
+
| Some (`String s) -> s
+
| _ -> raise (Json.Of_json ("Missing or invalid 'name' field", `Assoc fields))
+
in
+
let value = match List.assoc_opt "value" fields with
+
| Some (`String s) -> s
+
| _ -> raise (Json.Of_json ("Missing or invalid 'value' field", `Assoc fields))
+
in
+
{ name; value }
+
| j -> raise (Json.Of_json ("Expected object for Completion.Argument", j))
+
end
+
+
module Request = struct
+
type reference = [ `Prompt of PromptReference.t | `Resource of ResourceReference.t ]
+
+
type t = {
+
argument: Argument.t;
+
ref: reference;
+
}
+
+
let yojson_of_reference = function
+
| `Prompt p -> PromptReference.yojson_of_t p
+
| `Resource r -> ResourceReference.yojson_of_t r
+
+
let reference_of_yojson = function
+
| `Assoc fields ->
+
(match List.assoc_opt "type" fields with
+
| Some (`String "ref/prompt") -> `Prompt (PromptReference.t_of_yojson (`Assoc fields))
+
| Some (`String "ref/resource") -> `Resource (ResourceReference.t_of_yojson (`Assoc fields))
+
| _ -> raise (Json.Of_json ("Invalid or missing reference type", `Assoc fields)))
+
| j -> raise (Json.Of_json ("Expected object for reference", j))
+
+
let yojson_of_t { argument; ref } =
+
`Assoc [
+
("argument", Argument.yojson_of_t argument);
+
("ref", yojson_of_reference ref);
+
]
+
+
let t_of_yojson = function
+
| `Assoc fields ->
+
let argument = match List.assoc_opt "argument" fields with
+
| Some json -> Argument.t_of_yojson json
+
| _ -> raise (Json.Of_json ("Missing argument field", `Assoc fields))
+
in
+
let ref = match List.assoc_opt "ref" fields with
+
| Some json -> reference_of_yojson json
+
| _ -> raise (Json.Of_json ("Missing ref field", `Assoc fields))
+
in
+
{ argument; ref }
+
| j -> raise (Json.Of_json ("Expected object for Completion.Request", j))
+
+
let create ~argument ~ref =
+
{ argument; ref }
+
+
let to_params t =
+
yojson_of_t t
+
end
+
+
module Result = struct
+
type completion = {
+
values: string list;
+
has_more: bool option;
+
total: int option;
+
}
+
+
type t = {
+
completion: completion;
+
meta: Json.t option;
+
}
+
+
let yojson_of_completion { values; has_more; total } =
+
let assoc = [
+
("values", `List (List.map (fun s -> `String s) values));
+
] in
+
let assoc = match has_more with
+
| Some b -> ("hasMore", `Bool b) :: assoc
+
| None -> assoc
+
in
+
let assoc = match total with
+
| Some n -> ("total", `Int n) :: assoc
+
| None -> assoc
+
in
+
`Assoc assoc
+
+
let completion_of_yojson = function
+
| `Assoc fields ->
+
let values = match List.assoc_opt "values" fields with
+
| Some (`List items) ->
+
List.map (function
+
| `String s -> s
+
| _ -> raise (Json.Of_json ("Expected string in values array", `List items))
+
) items
+
| _ -> raise (Json.Of_json ("Missing or invalid 'values' field", `Assoc fields))
+
in
+
let has_more = match List.assoc_opt "hasMore" fields with
+
| Some (`Bool b) -> Some b
+
| None -> None
+
| _ -> raise (Json.Of_json ("Invalid 'hasMore' field", `Assoc fields))
+
in
+
let total = match List.assoc_opt "total" fields with
+
| Some (`Int n) -> Some n
+
| None -> None
+
| _ -> raise (Json.Of_json ("Invalid 'total' field", `Assoc fields))
+
in
+
{ values; has_more; total }
+
| j -> raise (Json.Of_json ("Expected object for completion", j))
+
+
let yojson_of_t { completion; meta } =
+
let assoc = [
+
("completion", yojson_of_completion completion);
+
] in
+
let assoc = match meta with
+
| Some meta_json -> ("_meta", meta_json) :: assoc
+
| None -> assoc
+
in
+
`Assoc assoc
+
+
let t_of_yojson = function
+
| `Assoc fields ->
+
let completion = match List.assoc_opt "completion" fields with
+
| Some json -> completion_of_yojson json
+
| _ -> raise (Json.Of_json ("Missing completion field", `Assoc fields))
+
in
+
let meta = List.assoc_opt "_meta" fields in
+
{ completion; meta }
+
| j -> raise (Json.Of_json ("Expected object for Completion.Result", j))
+
+
let create ~completion ?meta () =
+
{ completion; meta }
+
+
let to_result t =
+
yojson_of_t t
+
end
+
end
(* 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 }
-
| j -> Util.json_error "Expected object for PromptMessage" j
+
| `Assoc fields ->
+
let role = match List.assoc_opt "role" fields with
+
| Some json -> Role.t_of_yojson json
+
| None -> raise (Json.Of_json ("Missing role field", `Assoc fields))
+
in
+
let content = match List.assoc_opt "content" fields with
+
| Some json -> content_of_yojson json
+
| None -> raise (Json.Of_json ("Missing content field", `Assoc fields))
+
in
+
{ role; content }
+
| j -> raise (Json.Of_json ("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 ];
}
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 }
-
| j -> Util.json_error "Expected object for SamplingMessage" j
+
| `Assoc fields ->
+
let role = match List.assoc_opt "role" fields with
+
| Some json -> Role.t_of_yojson json
+
| None -> raise (Json.Of_json ("Missing role field", `Assoc fields))
+
in
+
let content = match List.assoc_opt "content" fields with
+
| Some (`Assoc content_fields) ->
+
(match List.assoc_opt "type" content_fields with
+
| Some (`String "text") -> `Text (TextContent.t_of_yojson (`Assoc content_fields))
+
| Some (`String "image") -> `Image (ImageContent.t_of_yojson (`Assoc content_fields))
+
| _ -> raise (Json.Of_json ("Invalid content type", `Assoc content_fields)))
+
| _ -> raise (Json.Of_json ("Missing or invalid content field", `Assoc fields))
+
in
+
{ role; content }
+
| j -> raise (Json.Of_json ("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 }
-
| j -> Util.json_error "Expected object for Implementation" j
+
| `Assoc fields ->
+
let name = match List.assoc_opt "name" fields with
+
| Some (`String s) -> s
+
| _ -> raise (Json.Of_json ("Missing or invalid 'name' field", `Assoc fields))
+
in
+
let version = match List.assoc_opt "version" fields with
+
| Some (`String s) -> s
+
| _ -> raise (Json.Of_json ("Missing or invalid 'version' field", `Assoc fields))
+
in
+
{ name; version }
+
| j -> raise (Json.Of_json ("Expected object for Implementation", j))
end
(* JSONRPC Message types *)
+
module JSONRPCMessage = struct
-
type notification = { meth : Method.t; params : Json.t option }
+
type notification = {
+
method_: string;
+
params: Json.t option;
+
}
type request = {
-
id : RequestId.t;
-
meth : Method.t;
-
params : Json.t option;
-
progress_token : ProgressToken.t option;
+
id: RequestId.t;
+
method_: string;
+
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 n.method_);
+
] 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 r.method_);
+
] 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 }
-
| j -> Util.json_error "Expected object for notification" j
+
let method_ = match List.assoc_opt "method" fields with
+
| Some (`String s) -> s
+
| _ -> raise (Json.Of_json ("Missing or invalid 'method' field", `Assoc fields))
+
in
+
let params = List.assoc_opt "params" fields in
+
{ method_; params }
+
| j -> raise (Json.Of_json ("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 }
-
| j -> Util.json_error "Expected object for request" j
+
let id = match List.assoc_opt "id" fields with
+
| Some id_json -> Id.t_of_yojson id_json
+
| _ -> raise (Json.Of_json ("Missing or invalid 'id' field", `Assoc fields))
+
in
+
let method_ = match List.assoc_opt "method" fields with
+
| Some (`String s) -> s
+
| _ -> raise (Json.Of_json ("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; method_; params; progress_token }
+
| j -> raise (Json.Of_json ("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 }
-
| j -> Util.json_error "Expected object for response" j
+
let id = match List.assoc_opt "id" fields with
+
| Some id_json -> Id.t_of_yojson id_json
+
| _ -> raise (Json.Of_json ("Missing or invalid 'id' field", `Assoc fields))
+
in
+
let result = match List.assoc_opt "result" fields with
+
| Some result -> result
+
| _ -> raise (Json.Of_json ("Missing 'result' field", `Assoc fields))
+
in
+
{ id; result }
+
| j -> raise (Json.Of_json ("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 }
-
| j -> Util.json_error "Expected object for error" j
+
| `Assoc fields ->
+
let id = match List.assoc_opt "id" fields with
+
| Some id_json -> Id.t_of_yojson id_json
+
| _ -> raise (Json.Of_json ("Missing or invalid 'id' field", `Assoc fields))
+
in
+
let error = match List.assoc_opt "error" fields with
+
| Some (`Assoc error_fields) -> error_fields
+
| _ -> raise (Json.Of_json ("Missing or invalid 'error' field", `Assoc fields))
+
in
+
let code = match List.assoc_opt "code" error with
+
| Some (`Int code) -> code
+
| _ -> raise (Json.Of_json ("Missing or invalid 'code' field in error", `Assoc error))
+
in
+
let message = match List.assoc_opt "message" error with
+
| Some (`String msg) -> msg
+
| _ -> raise (Json.Of_json ("Missing or invalid 'message' field in error", `Assoc error))
+
in
+
let data = List.assoc_opt "data" error in
+
{ id; code; message; data }
+
| j -> raise (Json.Of_json ("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
-
| j -> Util.json_error "Expected object for JSONRPC message" j
+
let _jsonrpc = match List.assoc_opt "jsonrpc" fields with
+
| Some (`String "2.0") -> ()
+
| _ -> raise (Json.Of_json ("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
+
raise (Json.Of_json ("Invalid JSONRPC message format", json))
+
| j -> raise (Json.Of_json ("Expected object for JSONRPC message", j))
-
let create_notification ?(params = None) ~meth () =
-
Notification { meth; params }
+
let create_notification ?(params=None) ~method_ () =
+
Notification { method_; params }
-
let create_request ?(params = None) ?(progress_token = None) ~id ~meth () =
-
Request { id; meth; params; progress_token }
+
let create_request ?(params=None) ?(progress_token=None) ~id ~method_ () =
+
Request { id; method_; 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 }
-
| j -> Util.json_error "Expected object for InitializeRequest" j
+
| `Assoc fields ->
+
let capabilities = match List.assoc_opt "capabilities" fields with
+
| Some json -> json
+
| None -> raise (Json.Of_json ("Missing capabilities field", `Assoc fields))
+
in
+
let client_info = match List.assoc_opt "clientInfo" fields with
+
| Some json -> Implementation.t_of_yojson json
+
| None -> raise (Json.Of_json ("Missing clientInfo field", `Assoc fields))
+
in
+
let protocol_version = match List.assoc_opt "protocolVersion" fields with
+
| Some (`String s) -> s
+
| _ -> raise (Json.Of_json ("Missing or invalid protocolVersion field", `Assoc fields))
+
in
+
{ capabilities; client_info; protocol_version }
+
| j -> raise (Json.Of_json ("Expected object for InitializeRequest", j))
let create ~capabilities ~client_info ~protocol_version =
{ 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 ~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 }
-
| j -> Util.json_error "Expected object for InitializeResult" j
+
| `Assoc fields ->
+
let capabilities = match List.assoc_opt "capabilities" fields with
+
| Some json -> json
+
| None -> raise (Json.Of_json ("Missing capabilities field", `Assoc fields))
+
in
+
let server_info = match List.assoc_opt "serverInfo" fields with
+
| Some json -> Implementation.t_of_yojson json
+
| None -> raise (Json.Of_json ("Missing serverInfo field", `Assoc fields))
+
in
+
let protocol_version = match List.assoc_opt "protocolVersion" fields with
+
| Some (`String s) -> s
+
| _ -> raise (Json.Of_json ("Missing or invalid protocolVersion field", `Assoc fields))
+
in
+
let instructions = match List.assoc_opt "instructions" fields with
+
| Some (`String s) -> Some s
+
| _ -> None
+
in
+
let meta = List.assoc_opt "_meta" fields in
+
{ capabilities; server_info; protocol_version; instructions; meta }
+
| j -> raise (Json.Of_json ("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 }
-
| j -> Util.json_error "Expected object for InitializedNotification" j
+
let meta = List.assoc_opt "_meta" fields in
+
{ meta }
+
| j -> raise (Json.Of_json ("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 ()
+
JSONRPCMessage.create_notification ~method_:"notifications/initialized" ~params ()
end
end
(* Export the main interface for using the MCP protocol *)
-
let parse_message json = JSONRPCMessage.t_of_yojson json
-
-
let create_notification ?(params = None) ~meth () =
-
JSONRPCMessage.create_notification ~params ~meth ()
-
-
let create_request ?(params = None) ?(progress_token = None) ~id ~meth () =
-
JSONRPCMessage.create_request ~params ~progress_token ~id ~meth ()
+
let parse_message json =
+
JSONRPCMessage.t_of_yojson json
+
let create_notification = JSONRPCMessage.create_notification
+
let create_request = JSONRPCMessage.create_request
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_image_content data mime_type =
-
Image ImageContent.{ data; mime_type; annotations = None }
-
-
let make_audio_content data mime_type =
-
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;
-
}
-
-
let make_resource_blob_content uri blob mime_type =
-
Resource
-
EmbeddedResource.
-
{
-
resource = `Blob BlobResourceContents.{ uri; blob; mime_type };
-
annotations = None;
-
}
+
(* Helper functions *)
+
let create_completion_request ~id ~argument ~ref =
+
let params = Completion.Request.to_params { argument; ref } in
+
create_request ~id ~method_:"completion/complete" ~params:(Some params) ()
+
+
let create_completion_response ~id ~values ?(has_more=None) ?(total=None) ?(meta=None) () =
+
let completion = { Completion.Result.values; has_more; total } in
+
let result = Completion.Result.to_result { completion; meta } in
+
create_response ~id ~result
+231 -957
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 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
-
- 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
-
4. Sampling: Client-exposed ability to generate completions from LLMs
-
- 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. *)
+
(** MCP - Model Context Protocol implementation *)
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 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_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_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_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_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_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_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 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 expected_value The expected string value
-
@param json Original JSON for error context
-
@raise Json.Of_json if the field is missing or not equal to expected_value
-
*)
-
end
-
-
(** Error codes for JSON-RPC *)
+
(** Standard error codes *)
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 *)
-
-
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_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 *)
-
end
-
-
(** MCP Protocol Methods - Algebraic data type representing all MCP methods *)
-
module Method : sig
-
(** Method type representing all MCP protocol methods *)
-
type t =
-
(* Initialization and lifecycle methods *)
-
| Initialize (** Start the MCP lifecycle *)
-
| Initialized (** Signal readiness after initialization *)
-
(* Resource methods *)
-
| 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 *)
-
(* Tool methods *)
-
| 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 *)
-
(* Progress notifications *)
-
| 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 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 parse_error : int
+
val invalid_request : int
+
val method_not_found : int
+
val invalid_params : int
+
val internal_error : int
+
val resource_not_found : int
+
val server_error_start : int
+
val server_error_end : int
end
(** Common types *)
···
(** Roles for conversation participants *)
module Role : sig
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
+
val to_string : t -> string
+
val of_string : string -> t
+
+
val yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.t -> t
end
(** Progress tokens for long-running operations *)
module ProgressToken : sig
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
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
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
+
val yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.t -> t
end
(** Annotations for objects *)
module Annotated : sig
-
type t = { annotations : annotation option }
-
(** Annotations provide metadata for content objects, allowing role-specific
-
targeting and priority settings. *)
-
+
type t = {
+
annotations: annotation option;
+
}
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;
+
priority: float option;
}
-
include Json.Jsonable.S with type t := t
+
val yojson_of_annotation : annotation -> Json.t
+
val annotation_of_yojson : Json.t -> annotation
+
+
val yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.t -> t
end
-
(** Text content - Core textual message representation in MCP *)
+
(** Text content *)
module TextContent : sig
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. *)
+
text: string;
+
annotations: Annotated.annotation option;
}
-
(** 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
-
{
-
"type": "text",
-
"text": "The text content of the message"
-
}
-
v}
-
-
For security, implementations must sanitize text content to prevent
-
injection attacks or unauthorized access to resources. *)
-
-
include Json.Jsonable.S with type t := t
+
val yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.t -> t
end
-
(** Image content - Visual data representation in MCP *)
+
(** Image content *)
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. *)
+
data: string;
+
mime_type: string;
+
annotations: Annotated.annotation option;
}
-
(** 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
-
{
-
"type": "image",
-
"data": "base64-encoded-image-data",
-
"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. *)
-
-
include Json.Jsonable.S with type t := t
+
val yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.t -> t
end
-
(** Audio content - Sound data representation in MCP *)
+
(** Audio content *)
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. *)
+
data: string;
+
mime_type: string;
+
annotations: Annotated.annotation option;
}
-
(** 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
-
{
-
"type": "audio",
-
"data": "base64-encoded-audio-data",
-
"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.
-
*)
-
-
include Json.Jsonable.S with type t := t
+
val yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.t -> t
end
-
(** Base resource contents - Core resource metadata in MCP *)
+
(** Base resource contents *)
module ResourceContents : sig
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;
+
mime_type: string option;
}
-
(** 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
+
val yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.t -> t
end
-
(** Text resource contents - Textual resource data *)
+
(** Text resource contents *)
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. *)
+
uri: string;
+
text: string;
+
mime_type: string option;
}
-
(** 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
-
{
-
"uri": "file:///example.txt",
-
"mimeType": "text/plain",
-
"text": "Resource content"
-
}
-
v} *)
-
-
include Json.Jsonable.S with type t := t
+
val yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.t -> t
end
-
(** Binary resource contents - Binary resource data *)
+
(** Binary resource contents *)
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. *)
+
uri: string;
+
blob: string;
+
mime_type: string option;
}
-
(** 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.
-
-
In JSON-RPC, this is represented as:
-
{v
-
{
-
"uri": "file:///example.png",
-
"mimeType": "image/png",
-
"blob": "base64-encoded-data"
-
}
-
v}
-
-
Binary data MUST be properly base64-encoded to ensure safe transmission in
-
JSON payloads. *)
-
-
include Json.Jsonable.S with type t := t
+
val yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.t -> t
end
-
(** Embedded resource - Resource included directly in messages *)
+
(** Embedded resource *)
module EmbeddedResource : sig
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. *)
+
resource: [ `Text of TextResourceContents.t | `Blob of BlobResourceContents.t ];
+
annotations: Annotated.annotation option;
}
-
(** 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. *)
-
-
include Json.Jsonable.S with type t := t
+
val yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.t -> t
end
-
(** Content type used in messages - Unified multimodal content representation in
-
MCP *)
-
type content =
+
(** Content type used in messages *)
+
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. *)
val yojson_of_content : content -> Json.t
-
(** Convert content to Yojson representation
-
@param content The content to convert
-
@return JSON representation of the content *)
-
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 *)
+
(** Message for prompts *)
module PromptMessage : sig
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. *)
+
role: Role.t;
+
content: content;
}
-
(** 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.
-
-
In JSON-RPC, prompt messages are represented as:
-
{v
-
{
-
"role": "user",
-
"content": {
-
"type": "text",
-
"text": "Please review this code: ${code}"
-
}
-
}
-
v}
-
-
Where $code would be replaced with a user-provided argument. *)
-
-
include Json.Jsonable.S with type t := t
+
val yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.t -> t
end
-
(** Message for sampling - Messages used in LLM completion requests *)
+
(** Message for sampling *)
module SamplingMessage : sig
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. *)
+
role: Role.t;
+
content: [ `Text of TextContent.t | `Image of ImageContent.t ];
}
-
(** 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.
-
-
Clients that support sampling must declare the 'sampling' capability
-
during initialization. *)
-
-
include Json.Jsonable.S with type t := t
+
val yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.t -> t
end
(** Implementation information *)
module Implementation : sig
type t = {
-
name : string; (** Name of the implementation *)
-
version : string; (** Version of the implementation *)
+
name: string;
+
version: string;
}
-
(** Implementation provides metadata about client and server implementations,
-
used during the initialization phase to identify each party. *)
-
include Json.Jsonable.S with type t := t
+
val yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.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
-
-
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. *)
+
(** JSONRPC message types *)
module JSONRPCMessage : sig
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. *)
+
method_: string;
+
params: Json.t option;
}
-
(** 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
-
{
-
"jsonrpc": "2.0",
-
"method": "notifications/resources/updated",
-
"params": {
-
"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. *)
+
id: RequestId.t;
+
method_: string;
+
params: Json.t option;
+
progress_token: ProgressToken.t option;
}
-
(** 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:
-
{v
-
{
-
"jsonrpc": "2.0",
-
"id": 1,
-
"method": "resources/read",
-
"params": {
-
"uri": "file:///project/src/main.rs"
-
}
-
}
-
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. *)
+
id: RequestId.t;
+
result: Json.t;
}
-
(** 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
-
{
-
"jsonrpc": "2.0",
-
"id": 1,
-
"result": {
-
"contents": [
-
{
-
"uri": "file:///project/src/main.rs",
-
"mimeType": "text/x-rust",
-
"text": "fn main() {\n println!(\"Hello world!\");\n}"
-
}
-
]
-
}
-
}
-
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. *)
+
id: RequestId.t;
+
code: int;
+
message: string;
+
data: Json.t option;
}
-
(** 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.
-
-
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
-
{
-
"jsonrpc": "2.0",
-
"id": 1,
-
"error": {
-
"code": -32002,
-
"message": "Resource not found",
-
"data": {
-
"uri": "file:///nonexistent.txt"
-
}
-
}
-
}
-
v} *)
-
-
(** 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
···
| Error of error
val yojson_of_notification : notification -> Json.t
-
(** 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 request to Yojson representation
-
@param request The request to convert
-
@return JSON representation of the request *)
-
val yojson_of_response : response -> Json.t
-
(** 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 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 *)
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 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 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 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 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 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_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_notification : ?params:Json.t option -> method_:string -> unit -> t
+
val create_request : ?params:Json.t option -> ?progress_token:ProgressToken.t option -> id:RequestId.t -> method_:string -> unit -> t
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_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
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 *)
module Initialize : sig
(** Initialize request *)
module Request : sig
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 *)
+
client_info: Implementation.t;
+
protocol_version: string;
}
-
(** 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 client_info Client implementation details
-
@param protocol_version MCP protocol version supported by the client
-
@return A new initialization request *)
+
val yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.t -> t
+
val create : capabilities:Json.t -> client_info:Implementation.t -> protocol_version:string -> t
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 *)
end
(** Initialize result *)
module Result : sig
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 *)
+
server_info: Implementation.t;
+
protocol_version: string;
+
instructions: string option;
+
meta: Json.t option;
}
-
(** 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 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 yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.t -> t
+
val create : capabilities:Json.t -> server_info:Implementation.t -> protocol_version:string -> ?instructions:string -> ?meta:Json.t -> unit -> t
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 *)
end
end
-
(** Initialized notification - Completes the initialization phase of the MCP
-
lifecycle *)
+
(** Initialized notification *)
module Initialized : sig
module Notification : sig
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;
}
-
(** 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 yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.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 to_jsonrpc : t -> JSONRPCMessage.t
-
(** Convert to JSON-RPC message
-
@param t Initialized notification
-
@return JSON-RPC message containing the initialized notification *)
end
end
-
val parse_message : Json.t -> JSONRPCMessage.t
-
(** Parse a JSON message into an MCP message
+
(** Tool definition *)
+
module Tool : sig
+
type t = {
+
name: string;
+
description: string option;
+
input_schema: Json.t;
+
}
-
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.
+
val yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.t -> t
+
end
-
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
+
(** Tool result *)
+
module CallToolResult : sig
+
type t = {
+
content: content list;
+
is_error: bool;
+
meta: Json.t option;
+
}
+
+
val yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.t -> t
+
end
-
@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 *)
+
(** Resource definition *)
+
module Resource : sig
+
type t = {
+
name: string;
+
uri: string;
+
description: string option;
+
mime_type: string option;
+
size: int option;
+
annotations: Annotated.annotation option;
+
}
-
val create_notification :
-
?params:Json.t option -> meth:Method.t -> unit -> JSONRPCMessage.t
-
(** Create a new notification message
+
val yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.t -> t
+
end
+
+
(** Resource Template definition *)
+
module ResourceTemplate : sig
+
type t = {
+
name: string;
+
uri_template: string;
+
description: string option;
+
mime_type: string option;
+
annotations: Annotated.annotation option;
+
}
-
Notifications are one-way messages that don't expect a response. This is a
-
convenience wrapper around JSONRPCMessage.create_notification.
+
val yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.t -> t
+
end
-
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
+
(** Resource Reference *)
+
module ResourceReference : sig
+
type t = {
+
uri: string;
+
}
-
@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 yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.t -> t
+
end
-
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
+
(** Prompt Reference *)
+
module PromptReference : sig
+
type t = {
+
name: string;
+
}
-
Requests are messages that expect a corresponding response. This is a
-
convenience wrapper around JSONRPCMessage.create_request.
+
val yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.t -> t
+
end
-
Common requests in MCP include:
-
- "initialize" - Start the MCP lifecycle
-
- "resources/list" - Discover available resources
-
- "resources/read" - Retrieve resource contents
-
- "tools/list" - Discover available tools
-
- "tools/call" - Invoke a tool
-
- "prompts/list" - Discover available prompts
-
- "prompts/get" - Retrieve a prompt template
+
(** Completion support *)
+
module Completion : sig
+
module Argument : sig
+
type t = {
+
name: string;
+
value: string;
+
}
-
@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 meth Method type for the request
-
@return A new JSON-RPC request message *)
+
val yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.t -> t
+
end
-
val create_response : id:RequestId.t -> result:Json.t -> JSONRPCMessage.t
-
(** Create a new response message
+
module Request : sig
+
type reference = [ `Prompt of PromptReference.t | `Resource of ResourceReference.t ]
-
Responses are sent in reply to requests and contain successful results. This
-
is a convenience wrapper around JSONRPCMessage.create_response.
+
type t = {
+
argument: Argument.t;
+
ref: reference;
+
}
-
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.
+
val yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.t -> t
-
@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 yojson_of_reference : reference -> Json.t
+
val reference_of_yojson : Json.t -> reference
-
val create_error :
-
id:RequestId.t ->
-
code:int ->
-
message:string ->
-
?data:Json.t option ->
-
unit ->
-
JSONRPCMessage.t
-
(** Create a new error message
+
val create : argument:Argument.t -> ref:reference -> t
+
val to_params : t -> Json.t
+
end
-
Errors are sent in reply to requests when processing fails. This is a
-
convenience wrapper around JSONRPCMessage.create_error.
+
module Result : sig
+
type completion = {
+
values: string list;
+
has_more: bool option;
+
total: int option;
+
}
-
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
-
- -32602: Invalid parameters
-
- -32603: Internal error
-
- -32002: Resource not found (MCP-specific)
-
- -32001: Authentication required (MCP-specific)
+
type t = {
+
completion: completion;
+
meta: Json.t option;
+
}
-
@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 yojson_of_completion : completion -> Json.t
+
val completion_of_yojson : Json.t -> completion
-
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 yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.t -> t
-
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 create : completion:completion -> ?meta:Json.t -> unit -> t
+
val to_result : t -> Json.t
+
end
+
end
-
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 *)
+
(** Parse a JSON message into an MCP message *)
+
val parse_message : Json.t -> JSONRPCMessage.t
-
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 *)
+
(** Create JSONRPC message helpers *)
+
val create_notification : ?params:Json.t option -> method_:string -> unit -> JSONRPCMessage.t
+
val create_request : ?params:Json.t option -> ?progress_token:ProgressToken.t option -> id:RequestId.t -> method_:string -> unit -> JSONRPCMessage.t
+
val create_response : id:RequestId.t -> result:Json.t -> JSONRPCMessage.t
+
val create_error : id:RequestId.t -> code:int -> message:string -> ?data:Json.t option -> unit -> JSONRPCMessage.t
-
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 *)
+
(** Helper functions for common requests/responses *)
+
val create_completion_request : id:RequestId.t -> argument:Completion.Argument.t -> ref:Completion.Request.reference -> JSONRPCMessage.t
+
val create_completion_response : id:RequestId.t -> values:string list -> ?has_more:bool option -> ?total:int option -> ?meta:Json.t option -> unit -> JSONRPCMessage.t
-833
lib/mcp_rpc.ml
···
-
(* Mcp_message - High-level RPC message definitions for Model Context Protocol *)
-
-
open Mcp
-
open Jsonrpc
-
-
(* Resources/List *)
-
module ResourcesList = struct
-
module Request = struct
-
type t = { cursor : Cursor.t option }
-
-
let yojson_of_t { cursor } =
-
let assoc = [] in
-
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 ResourcesList.Request.t" j
-
end
-
-
module Resource = struct
-
type t = {
-
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
-
| Some desc -> ("description", `String desc) :: assoc
-
| None -> assoc
-
in
-
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
-
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 }
-
| 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 }
-
-
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
-
| 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 }
-
| 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 params = Request.yojson_of_t { cursor } in
-
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
-
end
-
-
(* Resources/Templates/List *)
-
module ListResourceTemplatesRequest = struct
-
type t = { cursor : Cursor.t option }
-
-
let yojson_of_t { cursor } =
-
let assoc = [] in
-
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
-
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;
-
}
-
-
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
-
| Some desc -> ("description", `String desc) :: assoc
-
| None -> assoc
-
in
-
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
-
end
-
-
type t = {
-
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
-
| 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 }
-
| 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 params = ListResourceTemplatesRequest.yojson_of_t { cursor } in
-
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
-
end
-
-
(* Resources/Read *)
-
module ResourcesRead = struct
-
module Request = struct
-
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 ResourcesRead.Request.t" j
-
end
-
-
module ResourceContent = struct
-
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
-
end
-
-
module Response = struct
-
type t = { contents : ResourceContent.t list }
-
-
let 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 }
-
| 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 params = Request.yojson_of_t { uri } in
-
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
-
end
-
-
(* Tools/List *)
-
module ToolsList = struct
-
module Request = struct
-
type t = { cursor : Cursor.t option }
-
-
let yojson_of_t { cursor } =
-
let assoc = [] in
-
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 ToolsList.Request.t" j
-
end
-
-
module Tool = struct
-
type t = {
-
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
-
| Some desc -> ("description", `String desc) :: assoc
-
| None -> assoc
-
in
-
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 }
-
| 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 }
-
-
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
-
| 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 }
-
| 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 params = Request.yojson_of_t { cursor } in
-
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
-
end
-
-
(* Tools/Call *)
-
module ToolsCall = struct
-
module Request = struct
-
type t = { name : string; arguments : Json.t }
-
-
let yojson_of_t { name; 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 }
-
| j -> Util.json_error "Expected object for ToolsCall.Request.t" j
-
end
-
-
module ToolContent = struct
-
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)
-
| j -> Util.json_error "Expected object for ToolsCall.ToolContent.t" j
-
end
-
-
module Response = struct
-
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);
-
]
-
-
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 }
-
| 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 params = Request.yojson_of_t { name; arguments } in
-
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
-
end
-
-
(* Prompts/List *)
-
module PromptsList = struct
-
module PromptArgument = struct
-
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
-
| Some desc -> ("description", `String desc) :: assoc
-
| None -> assoc
-
in
-
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
-
end
-
-
module Prompt = struct
-
type t = {
-
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
-
| 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
-
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 }
-
| j -> Util.json_error "Expected object for PromptsList.Prompt.t" j
-
end
-
-
module Request = struct
-
type t = { cursor : Cursor.t option }
-
-
let yojson_of_t { cursor } =
-
let assoc = [] in
-
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 PromptsList.Request.t" j
-
end
-
-
module Response = struct
-
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
-
| 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 }
-
| 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 params = Request.yojson_of_t { cursor } in
-
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
-
end
-
-
(* Prompts/Get *)
-
module PromptsGet = struct
-
module Request = struct
-
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 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 }
-
| j -> Util.json_error "Expected object for PromptsGet.Request.t" j
-
end
-
-
module Response = struct
-
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
-
| 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 }
-
| 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 params = Request.yojson_of_t { name; arguments } in
-
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
-
end
-
-
(* 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) ]
-
-
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
-
end
-
-
let create_notification ~uri () =
-
let params = Notification.yojson_of_t { uri } in
-
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;
-
}
-
-
let yojson_of_t { progress; total; 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 }
-
| 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) ()
-
end
-
-
(* Type aliases for backward compatibility *)
-
type request = ResourcesList.Request.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
-366
lib/mcp_rpc.mli
···
-
(** Mcp_message - High-level RPC message definitions for Model Context Protocol
-
*)
-
-
open Mcp
-
open Jsonrpc
-
-
(** Resources/List - Request to list available resources *)
-
module ResourcesList : sig
-
(** Request parameters *)
-
module Request : sig
-
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 *)
-
}
-
-
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 *)
-
}
-
-
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_response :
-
id:RequestId.t ->
-
resources:Resource.t list ->
-
?next_cursor:Cursor.t ->
-
unit ->
-
JSONRPCMessage.t
-
(** Create a resources/list response *)
-
end
-
-
(** Resources/Templates/List - Request to list available resource templates *)
-
module ListResourceTemplatesRequest : sig
-
type t = { cursor : Cursor.t option (** Optional pagination cursor *) }
-
-
include Json.Jsonable.S with type t := t
-
end
-
-
(** Resources/Templates/List - Response with resource templates *)
-
module ListResourceTemplatesResult : sig
-
(** 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 *)
-
}
-
-
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 *)
-
}
-
-
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_response :
-
id:RequestId.t ->
-
resource_templates:ResourceTemplate.t list ->
-
?next_cursor:Cursor.t ->
-
unit ->
-
JSONRPCMessage.t
-
(** Create a resources/templates/list response *)
-
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 *) }
-
-
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 *)
-
-
include Json.Jsonable.S with type t := t
-
end
-
-
(** Response result *)
-
module Response : sig
-
type t = {
-
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_response :
-
id:RequestId.t ->
-
contents:ResourceContent.t list ->
-
unit ->
-
JSONRPCMessage.t
-
(** Create a resources/read response *)
-
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 *) }
-
-
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 *)
-
}
-
-
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 *)
-
}
-
-
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_response :
-
id:RequestId.t ->
-
tools:Tool.t list ->
-
?next_cursor:Cursor.t ->
-
unit ->
-
JSONRPCMessage.t
-
(** Create a tools/list response *)
-
end
-
-
(** Tools/Call - Request to invoke a tool *)
-
module ToolsCall : sig
-
(** Request parameters *)
-
module Request : sig
-
type t = {
-
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 *)
-
-
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 *)
-
}
-
-
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_response :
-
id:RequestId.t ->
-
content:ToolContent.t list ->
-
is_error:bool ->
-
unit ->
-
JSONRPCMessage.t
-
(** Create a tools/call response *)
-
end
-
-
(** Prompts/List - Request to list available prompts *)
-
module PromptsList : sig
-
(** 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 *)
-
}
-
-
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 *)
-
}
-
-
include Json.Jsonable.S with type t := t
-
end
-
-
(** Request parameters *)
-
module Request : sig
-
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 *)
-
}
-
-
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_response :
-
id:RequestId.t ->
-
prompts:Prompt.t list ->
-
?next_cursor:Cursor.t ->
-
unit ->
-
JSONRPCMessage.t
-
(** Create a prompts/list response *)
-
end
-
-
(** Prompts/Get - Request to get a prompt with arguments *)
-
module PromptsGet : sig
-
(** Request parameters *)
-
module Request : sig
-
type t = {
-
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 *)
-
}
-
-
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_response :
-
id:RequestId.t ->
-
?description:string ->
-
messages:PromptMessage.t list ->
-
unit ->
-
JSONRPCMessage.t
-
(** Create a prompts/get response *)
-
end
-
-
(** List Changed Notifications *)
-
module ListChanged : sig
-
val create_resources_notification : unit -> JSONRPCMessage.t
-
(** 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 *)
-
end
-
-
(** Resource Updated Notification *)
-
module ResourceUpdated : sig
-
(** Notification parameters *)
-
module Notification : sig
-
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 *)
-
end
-
-
(** Progress Notification *)
-
module Progress : sig
-
(** 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 *)
-
}
-
-
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 *)
-
end
+293 -399
lib/mcp_sdk.ml
···
(* SDK version *)
let version = "0.1.0"
-
let src = Logs.Src.create "mcp.sdk" ~doc:"mcp.sdk logging"
-
module Log = (val Logs.src_log src : Logs.LOG)
+
(* Logging utilities *)
+
module Log = struct
+
type level = Debug | Info | Warning | Error
+
+
let string_of_level = function
+
| Debug -> "DEBUG"
+
| Info -> "INFO"
+
| Warning -> "WARNING"
+
| Error -> "ERROR"
+
+
let log level msg =
+
Printf.eprintf "[%s] %s\n" (string_of_level level) msg;
+
flush stderr
+
+
let debug = log Debug
+
let info = log Info
+
let warning = log Warning
+
let error = log Error
+
end
(* 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;
+
mutable progress_token: ProgressToken.t option;
}
-
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 create ?request_id ?(lifespan_context=[]) () =
+
{ request_id; lifespan_context; progress_token = None }
+
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 ~method_:"notifications/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 *)
-
}
-
-
(* 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
-
-
(* 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
-
-
(* 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
-
-
(* 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);
-
]
-
-
(* Create a tool error result with structured content *)
-
let create_error_result error =
-
Logs.err (fun m -> m "Error result: %s" error);
-
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))
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_template: 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
-
Logs.warn (fun m ->
-
m
-
"Resource '%s' contains template variables. Consider using \
-
add_resource_template instead."
-
uri);
-
{ uri; name; description; mime_type; handler }
+
let create ~uri_template ?description ?mime_type ~handler () =
+
{ uri_template; 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 = [
+
("uriTemplate", `String resource.uri_template);
+
] 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 *)
-
}
-
-
(* 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
end
(* 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 yojson_of_message { role; content } =
+
`Assoc [
+
("role", Role.yojson_of_t role);
+
("content", yojson_of_content content);
+
]
+
+
(* This function must match the structure expected by the PromptMessage module in mcp.ml *)
+
let message_of_yojson json =
+
match json with
+
| `Assoc fields -> begin
+
let role = match List.assoc_opt "role" fields with
+
| Some json -> begin
+
Role.t_of_yojson json
+
end
+
| None -> begin
+
raise (Json.Of_json ("Missing role field", `Assoc fields))
+
end
+
in
+
let content = match List.assoc_opt "content" fields with
+
| Some json -> begin
+
content_of_yojson json
+
end
+
| None -> begin
+
raise (Json.Of_json ("Missing content field", `Assoc fields))
+
end
+
in
+
{ role; content }
+
end
+
| j -> begin
+
raise (Json.Of_json ("Expected object for PromptMessage", j))
+
end
+
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
-
in
-
let arg_assoc =
-
if arg.required then ("required", `Bool true) :: arg_assoc
-
else arg_assoc
-
in
-
`Assoc arg_assoc)
-
prompt.arguments
+
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
-
("arguments", `List args) :: assoc
-
else assoc
+
`Assoc arg_assoc
+
) prompt.arguments in
+
("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;
-
}
-
-
(* 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;
-
}
+
end
-
(* 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
+
(* Helper functions for creating common objects *)
+
let make_text_content text =
+
Text (TextContent.{ text; annotations = None })
-
(* Convert message to Mcp_rpc.PromptMessage.t *)
-
let message_to_rpc_prompt_message msg =
-
{ PromptMessage.role = msg.role; PromptMessage.content = msg.content }
+
let make_text_content_with_annotations text annotations =
+
Text (TextContent.{ text; annotations = Some annotations })
-
(* 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_image_content data mime_type =
+
Image (ImageContent.{ data; mime_type; annotations = None })
-
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 required_json = `List (List.map (fun name -> `String name) required) in
-
`Assoc
-
[
-
("type", `String "object");
-
("properties", `Assoc props);
-
("required", required_json);
-
]
+
let make_image_content_with_annotations data mime_type annotations =
+
Image (ImageContent.{ data; mime_type; annotations = Some annotations })
-
(* Resource Templates for the MCP server *)
-
module ResourceTemplate = struct
-
type handler = Context.t -> string list -> (string, string) result
+
let make_audio_content data mime_type =
+
Audio (AudioContent.{ data; mime_type; annotations = None })
-
type t = {
-
uri_template : string;
-
name : string;
-
description : string option;
-
mime_type : string option;
-
handler : handler;
-
}
+
let make_audio_content_with_annotations data mime_type annotations =
+
Audio (AudioContent.{ data; mime_type; annotations = Some annotations })
-
let create ~uri_template ~name ?description ?mime_type ~handler () =
-
{ uri_template; name; description; mime_type; handler }
+
let make_text_resource_content uri text ?mime_type () =
+
Resource (EmbeddedResource.{
+
resource = `Text TextResourceContents.{ uri; text; mime_type };
+
annotations = None
+
})
-
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
-
| Some desc -> ("description", `String desc) :: assoc
-
| None -> assoc
-
in
-
let assoc =
-
match resource_template.mime_type with
-
| Some mime -> ("mimeType", `String mime) :: assoc
-
| None -> assoc
-
in
-
`Assoc assoc
+
let make_blob_resource_content uri blob ?mime_type () =
+
Resource (EmbeddedResource.{
+
resource = `Blob BlobResourceContents.{ uri; blob; mime_type };
+
annotations = None
+
})
-
(* 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;
-
}
-
-
(* 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
-
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 required_json = `List (List.map (fun name -> `String name) required) in
+
`Assoc [
+
("type", `String "object");
+
("properties", `Assoc props);
+
("required", required_json)
+
]
(* 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;
+
mutable capabilities: Json.t;
+
mutable tools: Tool.t list;
+
mutable resources: Resource.t list;
+
mutable prompts: Prompt.t list;
+
mutable lifespan_context: (string * Json.t) list;
+
mutable startup_hook: (unit -> unit) option;
+
mutable shutdown_hook: (unit -> unit) option;
}
-
let name { name; _ } = name
-
let version { version; _ } = version
-
let capabilities { capabilities; _ } = capabilities
-
let lifespan_context { lifespan_context; _ } = lifespan_context
-
let protocol_version { protocol_version; _ } = protocol_version
-
let tools { tools; _ } = tools
-
let resources { resources; _ } = resources
-
let resource_templates { resource_templates; _ } = resource_templates
-
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;
capabilities = `Assoc [];
tools = [];
resources = [];
-
resource_templates = [];
prompts = [];
lifespan_context = [];
+
startup_hook = None;
+
shutdown_hook = None;
}
(* 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_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
-
in
-
let caps =
-
if with_resource_templates then
-
("resourceTemplates", `Assoc [ ("listChanged", `Bool true) ]) :: caps
-
else if not with_resource_templates then
-
("resourceTemplates", `Assoc [ ("listChanged", `Bool false) ]) :: caps
-
else caps
+
("resources", `Assoc [
+
("listChanged", `Bool false);
+
("subscribe", `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
···
resource
(* Create and register a resource in one step *)
-
let add_resource server ~uri ~name ?description ?mime_type handler =
+
let add_resource server ~uri_template ?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_template
+
?description
+
?mime_type
+
~handler:handler'
+
()
in
register_resource server resource
-
(* Register a resource template *)
-
let register_resource_template server template =
-
server.resource_templates <- template :: server.resource_templates;
-
template
-
-
(* Create and register a resource template in one step *)
-
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)
-
in
-
let template =
-
ResourceTemplate.create ~uri_template ~name ?description ?mime_type
-
~handler:handler' ()
-
in
-
register_resource_template server template
-
(* Register a prompt *)
let register_prompt server prompt =
server.prompts <- prompt :: server.prompts;
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_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_prompts = match with_prompts with
| Some b -> b
-
| None -> server.resource_templates <> []
-
in
-
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 ()
+
| None -> server.prompts <> []
in
+
let capabilities = default_capabilities ~with_tools ~with_resources ~with_prompts () in
set_capabilities server capabilities;
server
+
+
(* Set startup and shutdown hooks *)
+
let set_startup_hook server hook =
+
server.startup_hook <- Some hook
+
+
let set_shutdown_hook server hook =
+
server.shutdown_hook <- Some hook
+
+
(* Transport type for server *)
+
type transport_type =
+
| Stdio (* Read/write to stdin/stdout *)
+
| Http (* HTTP server - to be implemented *)
+
+
(* Run server with stdio transport *)
+
let run_server server =
+
(* Setup *)
+
Printexc.record_backtrace true;
+
+
Log.info (Printf.sprintf "%s server starting" server.name);
+
Log.debug (Printf.sprintf "Protocol version: %s" server.protocol_version);
+
Log.debug (Printf.sprintf "Server info: %s v%s" server.name server.version);
+
+
(* Initialize capabilities if not already set *)
+
if server.capabilities = `Assoc [] then
+
ignore (configure_server server ());
+
+
(* Run startup hook if provided *)
+
(match server.startup_hook with
+
| Some hook -> hook ()
+
| None -> ());
+
+
(* This function will be replaced by a full implementation in the mcp_server module *)
+
Log.info "Server initialized and ready."
+
+
(* Placeholder for running server with different transports *)
+
let run_server_with_transport server transport =
+
match transport with
+
| Http ->
+
Log.info "HTTP server not implemented in this version, using stdio instead";
+
run_server server
+
| Stdio ->
+
run_server server
+102 -203
lib/mcp_sdk.mli
···
open Mcp
open Jsonrpc
-
val version : string
(** SDK version *)
+
val version : string
+
+
(** Logging utilities *)
+
module Log : sig
+
type level = Debug | Info | Warning | Error
+
+
val string_of_level : level -> string
+
+
val log : level -> string -> unit
+
val debug : string -> unit
+
val info : string -> unit
+
val warning : string -> unit
+
val error : string -> unit
+
end
(** Context for tools and resources *)
module Context : sig
-
type t
+
type t = {
+
request_id: RequestId.t option;
+
lifespan_context: (string * Json.t) list;
+
mutable progress_token: ProgressToken.t option;
+
}
-
val create :
-
?request_id:RequestId.t ->
-
?progress_token:ProgressToken.t ->
-
?lifespan_context:(string * Json.t) list ->
-
unit ->
-
t
-
+
val create : ?request_id:RequestId.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
-
-
val to_rpc_tool_list_tool : t -> Mcp_rpc.ToolsList.Tool.t
-
(** 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 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 create_tool_result : Mcp.content list -> is_error:bool -> Json.t
-
(** Create a tool result with content *)
-
-
val create_error_result : string -> Json.t
-
(** Create a tool error result with structured content *)
-
-
val handle_execution_error : string -> Json.t
-
(** Handle tool execution errors *)
-
-
val handle_unknown_tool_error : string -> Json.t
-
(** 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;
-
}
-
-
val create :
-
uri:string ->
-
name:string ->
-
?description:string ->
-
?mime_type:string ->
-
handler:handler ->
-
unit ->
-
t
-
-
val to_json : t -> Json.t
-
-
val to_rpc_resource_list_resource : t -> Mcp_rpc.ResourcesList.Resource.t
-
(** 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 *)
-
module ResourceTemplate : sig
-
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;
+
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 -> ?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_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 yojson_of_message : message -> Json.t
+
val message_of_yojson : Json.t -> message
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 to_rpc_prompt_list_prompt : t -> Mcp_rpc.PromptsList.Prompt.t
-
(** Convert to Mcp_rpc.PromptsList.Prompt.t *)
-
-
val to_rpc_prompts_list : t list -> Mcp_rpc.PromptsList.Prompt.t list
-
(** Convert a list of Prompt.t to the format needed for prompts/list response
-
*)
-
-
val message_to_rpc_prompt_message : message -> PromptMessage.t
-
(** 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 *)
-
-
val name : server -> string
-
val version : server -> string
-
val protocol_version : server -> string
-
val capabilities : server -> Json.t
-
val tools : server -> Tool.t list
-
val resources : server -> Resource.t list
-
val resource_templates : server -> ResourceTemplate.t list
-
val prompts : server -> Prompt.t list
+
type server = {
+
name: string;
+
version: string;
+
protocol_version: string;
+
mutable capabilities: Json.t;
+
mutable tools: Tool.t list;
+
mutable resources: Resource.t list;
+
mutable prompts: Prompt.t list;
+
mutable lifespan_context: (string * Json.t) list;
+
mutable startup_hook: (unit -> unit) option;
+
mutable shutdown_hook: (unit -> unit) option;
+
}
-
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_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
+
(** Register a tool with the server *)
+
val register_tool : server -> Tool.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
+
(** Register a resource with the server *)
+
val register_resource : server -> Resource.t -> Resource.t
+
(** Create and register a resource in one step *)
+
val add_resource : server -> uri_template: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 *)
+
(** Register a prompt with the server *)
+
val register_prompt : server -> Prompt.t -> Prompt.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
+
(** Set server capabilities *)
+
val set_capabilities : server -> Json.t -> unit
+
(** Configure server with default capabilities based on registered components *)
+
val configure_server : server -> ?with_tools:bool -> ?with_resources:bool -> ?with_prompts:bool -> unit -> server
-
val make_tool_schema : (string * string * string) list -> string list -> Json.t
+
(** Set startup hook *)
+
val set_startup_hook : server -> (unit -> unit) -> unit
+
+
(** Set shutdown hook *)
+
val set_shutdown_hook : server -> (unit -> unit) -> unit
+
+
(** Run the server using stdio transport (legacy method) *)
+
val run_server : server -> unit
+
+
(** Transport type for the server *)
+
type transport_type =
+
| Stdio (** Read/write to stdin/stdout *)
+
| Http (** HTTP server - to be implemented *)
+
+
(** Create and start a server with the specified transport *)
+
val run_server_with_transport : server -> transport_type -> unit
+
+
(** Helper functions for creating common objects *)
+
val make_text_content : string -> content
+
val make_text_content_with_annotations : string -> Annotated.annotation -> content
+
val make_image_content : string -> string -> content
+
val make_image_content_with_annotations : string -> string -> Annotated.annotation -> content
+
val make_audio_content : string -> string -> content
+
val make_audio_content_with_annotations : string -> string -> Annotated.annotation -> content
+
val make_text_resource_content : string -> string -> ?mime_type:string -> unit -> content
+
val make_blob_resource_content : string -> string -> ?mime_type:string -> unit -> content
+
val make_tool_schema : (string * string * string) list -> string list -> Json.t
+507 -539
lib/mcp_server.ml
···
open Mcp
-
open Jsonrpc
open Mcp_sdk
-
let src = Logs.Src.create "mcp.sdk" ~doc:"mcp.sdk logging"
-
-
module Log = (val Logs.src_log src : Logs.LOG)
-
-
(* 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
-
create_error ~id ~code:error_code ~message ~data:(Some error_data) ()
-
-
(* Process initialize request *)
-
let handle_initialize server req =
-
Log.debug (fun m -> m "Processing initialize request");
-
let result =
-
match req.JSONRPCMessage.params with
-
| Some params ->
-
let req_data = Initialize.Request.t_of_yojson params in
-
Logs.debug (fun m ->
-
m "Client info: %s v%s" req_data.client_info.name
-
req_data.client_info.version);
-
Log.debug (fun m ->
-
m "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))
-
()
-
in
-
Initialize.Result.yojson_of_t result
-
| None ->
-
Log.err (fun m -> m "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) =
-
Log.debug (fun m -> m "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
-
Some response
-
-
(* Process prompts/list request *)
-
let handle_prompts_list server (req : JSONRPCMessage.request) =
-
Log.debug (fun m -> m "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
-
Some response
-
-
(* Process resources/list request *)
-
let handle_resources_list server (req : JSONRPCMessage.request) =
-
Log.debug (fun m -> m "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
-
Some response
-
-
(* Process resources/templates/list request *)
-
let handle_resource_templates_list server (req : JSONRPCMessage.request) =
-
Log.debug (fun m -> m "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
-
Some response
-
-
(* Utility module for resource template matching *)
-
module Resource_matcher = struct
-
(* Define variants for resource handling result *)
-
type resource_match =
-
| DirectResource of Resource.t * string list
-
| TemplateResource of ResourceTemplate.t * string list
-
| NoMatch
-
-
(* Extract parameters from a template URI *)
-
let extract_template_vars template_uri uri =
-
(* 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
-
else
-
(* Match parts and extract variables *)
-
let rec match_parts tparts uparts acc =
-
match (tparts, uparts) with
-
| [], [] -> Some (List.rev acc)
-
| 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
-
(* Extract variable value and continue *)
-
match_parts tt ut (uh :: acc)
-
else if th = uh then
-
(* Fixed part matches, continue *)
-
match_parts tt ut acc
-
else
-
(* Fixed part doesn't match, fail *)
-
None
-
| _, _ -> None
-
in
-
match_parts template_parts uri_parts []
-
-
(* 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
-
| Some resource -> DirectResource (resource, [])
-
| None ->
-
(* Try template match next *)
-
let templates = resource_templates server in
+
(* MCP Server module for handling JSON-RPC communication *)
-
(* 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
-
| Some params -> TemplateResource (template, params)
-
| None -> try_templates rest)
-
in
-
try_templates templates
-
end
-
-
(* Process resources/read request *)
-
let handle_resources_read server (req : JSONRPCMessage.request) =
-
Log.debug (fun m -> m "Processing resources/read request");
-
match req.JSONRPCMessage.params with
-
| None ->
-
Log.err (fun m -> m "Missing params for resources/read request");
-
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.debug (fun m -> m "Resource URI: %s" uri);
-
-
(* Find matching resource or template *)
-
match Resource_matcher.find_match server uri with
-
| 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) ]) ]
-
()
-
in
+
(** Server types *)
+
type transport_type =
+
| Stdio (* Read/write to stdin/stdout *)
+
| Http (* HTTP server - to be implemented *)
-
Log.debug (fun m -> m "Handling direct resource: %s" resource.name);
+
type t = {
+
server: Mcp_sdk.server;
+
transport: transport_type;
+
mutable running: bool;
+
}
-
(* 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.err (fun m -> m "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) ]) ]
-
()
-
in
-
-
Log.debug (fun m ->
-
m "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.err (fun m -> m "Error reading resource template: %s" err);
-
Some
-
(create_jsonrpc_error req.id ErrorCode.InternalError
-
("Error reading resource template: " ^ err)
-
()))
-
| Resource_matcher.NoMatch ->
-
Log.err (fun m -> m "Resource not found: %s" 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.debug (fun m -> m "Tool name: %s" name);
-
Some name
-
| _ ->
-
Log.err (fun m -> m "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.debug (fun m -> m "Tool arguments: %s" (Yojson.Safe.to_string args));
-
args
-
| _ ->
-
Log.debug (fun m ->
-
m "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.debug (fun m -> m "Found tool: %s" name);
-
-
(* Call the tool handler *)
-
match tool.handler ctx args with
-
| Ok result ->
-
Log.debug (fun m -> m "Tool execution succeeded");
-
result
-
| Error err -> Tool.handle_execution_error err
-
with
-
| Not_found -> Tool.handle_unknown_tool_error name
-
| exn -> Tool.handle_execution_exception exn
-
-
(* 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; 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 (fun m -> m "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" ()))
-
| _ ->
-
Log.err (fun m -> m "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) =
-
Log.debug (fun m -> m "Processing ping request");
-
Some (create_response ~id:req.JSONRPCMessage.id ~result:(`Assoc []))
-
-
(* Handle notifications/initialized *)
-
let handle_initialized (notif : JSONRPCMessage.notification) =
-
Log.debug (fun m ->
-
m
-
"Client initialization complete - Server is now ready to receive \
-
requests\n\
-
\ Notification params: %s"
-
(match notif.JSONRPCMessage.params with
-
| Some p -> Yojson.Safe.to_string p
-
| None -> "null"));
-
None
-
-
(* Process a single message using the MCP SDK *)
+
(** Process a single message *)
let process_message server message =
try
-
Log.debug (fun m ->
-
m "Processing message: %s" (Yojson.Safe.to_string message));
+
Log.debug "Parsing message as JSONRPC message...";
match JSONRPCMessage.t_of_yojson message with
-
| JSONRPCMessage.Request req -> (
-
Log.debug (fun m ->
-
m "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.err (fun m ->
-
m "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.debug (fun m ->
-
m "Received notification with method: %s"
-
(Method.to_string notif.meth));
-
match notif.meth with
-
| Method.Initialized -> handle_initialized notif
-
| _ ->
-
Log.debug (fun m ->
-
m "Ignoring notification: %s" (Method.to_string notif.meth));
-
None)
-
| JSONRPCMessage.Response _ ->
-
Log.err (fun m -> m "Unexpected response message received");
+
| JSONRPCMessage.Request req -> begin
+
Log.debug (Printf.sprintf "Received request with method: %s" req.method_);
+
match req.method_ with
+
| "initialize" -> begin
+
Log.debug "Processing initialize request";
+
let result = match req.params with
+
| Some params -> begin
+
Log.debug "Parsing initialize request params...";
+
let req_params = Initialize.Request.t_of_yojson params in
+
Log.debug (Printf.sprintf "Client info: %s v%s"
+
req_params.client_info.name
+
req_params.client_info.version);
+
Log.debug (Printf.sprintf "Client protocol version: %s" req_params.protocol_version);
+
+
(* Check protocol version compatibility *)
+
if req_params.protocol_version <> server.protocol_version then begin
+
Log.debug (Printf.sprintf "Protocol version mismatch: client=%s server=%s"
+
req_params.protocol_version server.protocol_version);
+
end;
+
+
Initialize.Result.create
+
~capabilities:server.capabilities
+
~server_info:Implementation.{ name = server.name; version = server.version }
+
~protocol_version:server.protocol_version
+
?instructions:(Some "MCP Server") (* TODO: Allow customization *)
+
()
+
end
+
| None -> begin
+
Log.error "Missing params for initialize request";
+
Initialize.Result.create
+
~capabilities:server.capabilities
+
~server_info:Implementation.{ name = server.name; version = server.version }
+
~protocol_version:server.protocol_version
+
()
+
end
+
in
+
Some (create_response ~id:req.id ~result:(Initialize.Result.yojson_of_t result))
+
end
+
+
| "tools/list" -> begin
+
Log.debug "Processing tools/list request";
+
let tools_json = List.map Mcp_sdk.Tool.to_json server.tools in
+
let result = `Assoc [("tools", `List tools_json)] in
+
Some (create_response ~id:req.id ~result)
+
end
+
+
| "tools/call" -> begin
+
Log.debug "Processing tools/call request";
+
match req.params with
+
| Some (`Assoc params) -> begin
+
let name = match List.assoc_opt "name" params with
+
| Some (`String name) -> begin
+
Log.debug (Printf.sprintf "Tool name: %s" name);
+
name
+
end
+
| _ -> begin
+
Log.error "Missing or invalid 'name' parameter in tool call";
+
failwith "Missing or invalid 'name' parameter"
+
end
+
in
+
let args = match List.assoc_opt "arguments" params with
+
| Some args -> begin
+
Log.debug (Printf.sprintf "Tool arguments: %s" (Yojson.Safe.to_string args));
+
args
+
end
+
| _ -> begin
+
Log.debug "No arguments provided for tool call, using empty object";
+
`Assoc [] (* Empty arguments is valid *)
+
end
+
in
+
let progress_token = req.progress_token in
+
+
(* Find the tool *)
+
let tool_opt = List.find_opt (fun t -> t.Mcp_sdk.Tool.name = name) server.tools in
+
match tool_opt with
+
| Some tool -> begin
+
Log.debug (Printf.sprintf "Found tool: %s" name);
+
let ctx = Mcp_sdk.Context.create
+
?request_id:(Some req.id)
+
~lifespan_context:server.lifespan_context
+
()
+
in
+
ctx.progress_token <- progress_token;
+
+
(* Call the handler *)
+
let result = match tool.handler ctx args with
+
| Ok json -> begin
+
`Assoc [
+
("content", `List [Mcp.yojson_of_content (Text (TextContent.{
+
text = Yojson.Safe.to_string json;
+
annotations = None
+
}))]);
+
("isError", `Bool false)
+
]
+
end
+
| Error err -> begin
+
`Assoc [
+
("content", `List [Mcp.yojson_of_content (Text (TextContent.{
+
text = err;
+
annotations = None
+
}))]);
+
("isError", `Bool true)
+
]
+
end
+
in
+
Some (create_response ~id:req.id ~result)
+
end
+
| None -> begin
+
Log.error (Printf.sprintf "Tool not found: %s" name);
+
let error_content = TextContent.{
+
text = Printf.sprintf "Unknown tool: %s" name;
+
annotations = None
+
} in
+
let result = `Assoc [
+
("content", `List [Mcp.yojson_of_content (Text error_content)]);
+
("isError", `Bool true)
+
] in
+
Some (create_response ~id:req.id ~result)
+
end
+
end
+
| _ -> begin
+
Log.error "Invalid params format for tools/call";
+
Some (create_error ~id:req.id ~code:ErrorCode.invalid_params ~message:"Invalid params for tools/call" ())
+
end
+
end
+
+
| "resources/list" -> begin
+
Log.debug "Processing resources/list request";
+
if server.resources <> [] then begin
+
let resources_json = List.map Mcp_sdk.Resource.to_json server.resources in
+
let result = `Assoc [("resources", `List resources_json)] in
+
Some (create_response ~id:req.id ~result)
+
end else begin
+
Some (create_error ~id:req.id ~code:ErrorCode.method_not_found ~message:"Resources not supported" ())
+
end
+
end
+
+
| "prompts/list" -> begin
+
Log.debug "Processing prompts/list request";
+
if server.prompts <> [] then begin
+
let prompts_json = List.map Mcp_sdk.Prompt.to_json server.prompts in
+
let result = `Assoc [("prompts", `List prompts_json)] in
+
Some (create_response ~id:req.id ~result)
+
end else begin
+
Some (create_error ~id:req.id ~code:ErrorCode.method_not_found ~message:"Prompts not supported" ())
+
end
+
end
+
+
| "prompts/get" -> begin
+
Log.debug "Processing prompts/get request";
+
if server.prompts <> [] then begin
+
match req.params with
+
| Some (`Assoc params) -> begin
+
(* Extract prompt name *)
+
let name = match List.assoc_opt "name" params with
+
| Some (`String name) -> begin
+
Log.debug (Printf.sprintf "Prompt name: %s" name);
+
name
+
end
+
| _ -> begin
+
Log.error "Missing or invalid 'name' parameter in prompt request";
+
failwith "Missing or invalid 'name' parameter"
+
end
+
in
+
+
(* Extract arguments if any *)
+
let arguments = match List.assoc_opt "arguments" params with
+
| Some (`Assoc args) -> begin
+
Log.debug (Printf.sprintf "Prompt arguments: %s" (Yojson.Safe.to_string (`Assoc args)));
+
List.map (fun (k, v) ->
+
match v with
+
| `String s -> begin (k, s) end
+
| _ -> begin (k, Yojson.Safe.to_string v) end
+
) args
+
end
+
| _ -> begin
+
[]
+
end
+
in
+
+
(* Find the prompt *)
+
let prompt_opt = List.find_opt (fun p -> p.Mcp_sdk.Prompt.name = name) server.prompts in
+
match prompt_opt with
+
| Some prompt -> begin
+
Log.debug (Printf.sprintf "Found prompt: %s" name);
+
let ctx = Mcp_sdk.Context.create
+
?request_id:(Some req.id)
+
~lifespan_context:server.lifespan_context
+
()
+
in
+
+
(* Call the prompt handler *)
+
match prompt.handler ctx arguments with
+
| Ok messages -> begin
+
Log.debug (Printf.sprintf "Prompt handler returned %d messages" (List.length messages));
+
+
(* Important: We need to directly use yojson_of_message which preserves MIME types *)
+
let messages_json = List.map Prompt.yojson_of_message messages in
+
+
(* Debug output *)
+
Log.debug (Printf.sprintf "Messages JSON: %s" (Yojson.Safe.to_string (`List messages_json)));
+
+
(* Verify one message if available to check structure *)
+
if List.length messages > 0 then begin
+
let first_msg = List.hd messages in
+
let content_debug = match first_msg.content with
+
| Text t -> begin
+
Printf.sprintf "Text content: %s" t.text
+
end
+
| Image i -> begin
+
Printf.sprintf "Image content (mime: %s)" i.mime_type
+
end
+
| Audio a -> begin
+
Printf.sprintf "Audio content (mime: %s)" a.mime_type
+
end
+
| Resource r -> begin
+
"Resource content"
+
end
+
in
+
Log.debug (Printf.sprintf "First message content type: %s" content_debug);
+
end;
+
+
let result = `Assoc [
+
("messages", `List messages_json);
+
("description", match prompt.description with
+
| Some d -> begin `String d end
+
| None -> begin `Null end)
+
] in
+
Some (create_response ~id:req.id ~result)
+
end
+
| Error err -> begin
+
Log.error (Printf.sprintf "Error processing prompt: %s" err);
+
Some (create_error ~id:req.id ~code:ErrorCode.internal_error ~message:err ())
+
end
+
end
+
| None -> begin
+
Log.error (Printf.sprintf "Prompt not found: %s" name);
+
Some (create_error ~id:req.id ~code:ErrorCode.invalid_params ~message:(Printf.sprintf "Prompt not found: %s" name) ())
+
end
+
end
+
| _ -> begin
+
Log.error "Invalid params format for prompts/get";
+
Some (create_error ~id:req.id ~code:ErrorCode.invalid_params ~message:"Invalid params format" ())
+
end
+
end else begin
+
Some (create_error ~id:req.id ~code:ErrorCode.method_not_found ~message:"Prompts not supported" ())
+
end
+
end
+
+
| "ping" -> begin
+
Log.debug "Processing ping request";
+
Some (create_response ~id:req.id ~result:(`Assoc []))
+
end
+
+
| _ -> begin
+
Log.error (Printf.sprintf "Unknown method received: %s" req.method_);
+
Some (create_error ~id:req.id ~code:ErrorCode.method_not_found ~message:("Method not found: " ^ req.method_) ())
+
end
+
end
+
+
| JSONRPCMessage.Notification notif -> begin
+
Log.debug (Printf.sprintf "Received notification with method: %s" notif.method_);
+
match notif.method_ with
+
| "notifications/initialized" -> begin
+
Log.debug "Client initialization complete - Server is now ready to receive requests";
+
None
+
end
+
| _ -> begin
+
Log.debug (Printf.sprintf "Ignoring notification: %s" notif.method_);
+
None
+
end
+
end
+
+
| JSONRPCMessage.Response _ -> begin
+
Log.error "Unexpected response message received";
None
-
| JSONRPCMessage.Error _ ->
-
Log.err (fun m -> m "Unexpected error message received");
+
end
+
+
| JSONRPCMessage.Error _ -> begin
+
Log.error "Unexpected error message received";
None
+
end
with
-
| Json.Of_json (msg, _) ->
-
Log.err (fun m -> m "JSON error: %s" msg);
-
(* Can't respond with error because we don't have a request ID *)
+
| Failure msg -> begin
+
Log.error (Printf.sprintf "JSON error in message processing: %s" msg);
None
-
| Yojson.Json_error msg ->
-
Log.err (fun m -> m "JSON parse error: %s" msg);
-
(* Can't respond with error because we don't have a request ID *)
-
None
-
| exc ->
-
Log.err (fun m ->
-
m
-
"Exception during message processing: %s\n\
-
Backtrace: %s\n\
-
Message was: %s"
-
(Printexc.to_string exc)
-
(Printexc.get_backtrace ())
-
(Yojson.Safe.to_string message));
+
end
+
| exc -> begin
+
Log.error (Printf.sprintf "Exception during message processing: %s" (Printexc.to_string exc));
+
Log.error (Printf.sprintf "Backtrace: %s" (Printexc.get_backtrace()));
None
+
end
-
(* Extract a request ID from a potentially malformed message *)
-
let extract_request_id json =
+
(** Read a single message from stdin *)
+
let read_stdio_message () =
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)
-
| _ -> None
-
with _ -> None
+
Log.debug "Reading line from stdin...";
+
let line = read_line () in
+
if line = "" then begin
+
Log.debug "Empty line received, ignoring";
+
None
+
end else begin
+
Log.debug (Printf.sprintf "Raw input: %s" (String.sub line 0 (min 100 (String.length line))));
+
try
+
let json = Yojson.Safe.from_string line in
+
Log.debug "Successfully parsed JSON";
+
Some json
+
with
+
| Yojson.Json_error msg -> begin
+
Log.error (Printf.sprintf "Error parsing JSON: %s" msg);
+
Log.error (Printf.sprintf "Input was: %s" (String.sub line 0 (min 100 (String.length line))));
+
None
+
end
+
end
+
with
+
| End_of_file -> begin
+
Log.debug "End of file received on stdin";
+
None
+
end
+
| Sys_error msg -> begin
+
Log.error (Printf.sprintf "System error while reading: %s" msg);
+
None
+
end
+
| exc -> begin
+
Log.error (Printf.sprintf "Exception while reading: %s" (Printexc.to_string exc));
+
None
+
end
-
(* Handle processing for an input line *)
-
let process_input_line server line =
-
if line = "" then (
-
Log.debug (fun m -> m "Empty line received, ignoring");
-
None)
-
else (
-
Log.debug (fun m -> m "Raw input: %s" line);
-
try
-
let json = Yojson.Safe.from_string line in
-
Log.debug (fun m -> m "Successfully parsed JSON");
+
(** Run stdio server with enhanced error handling *)
+
let rec run_stdio_server mcp_server =
+
try begin
+
if not mcp_server.running then begin
+
Log.debug "Server stopped";
+
()
+
end else begin
+
match read_stdio_message () with
+
| Some json -> begin
+
Log.debug "Processing message...";
+
try begin
+
match process_message mcp_server.server json with
+
| Some response -> begin
+
let response_json = JSONRPCMessage.yojson_of_t response in
+
let response_str = Yojson.Safe.to_string response_json in
+
Log.debug (Printf.sprintf "Sending response: %s"
+
(String.sub response_str 0 (min 100 (String.length response_str))));
+
Printf.printf "%s\n" response_str;
+
flush stdout;
+
(* Give client time to process *)
+
Unix.sleepf 0.01;
+
end
+
| None -> begin
+
Log.debug "No response needed"
+
end
+
end with
+
| exc -> begin
+
Log.error (Printf.sprintf "ERROR in message processing: %s" (Printexc.to_string exc));
+
Log.error (Printf.sprintf "Backtrace: %s" (Printexc.get_backtrace()));
+
(* Try to extract ID and send an error response *)
+
try begin
+
let id_opt = match Yojson.Safe.Util.member "id" json with
+
| `Int i -> Some (`Int i)
+
| `String s -> Some (`String s)
+
| _ -> None
+
in
+
match id_opt with
+
| Some id -> begin
+
let error_resp = create_error ~id ~code:ErrorCode.internal_error ~message:(Printexc.to_string exc) () in
+
let error_json = JSONRPCMessage.yojson_of_t error_resp in
+
let error_str = Yojson.Safe.to_string error_json in
+
Printf.printf "%s\n" error_str;
+
flush stdout;
+
end
+
| None -> begin
+
Log.error "Could not extract request ID to send error response"
+
end
+
end with
+
| e -> begin
+
Log.error (Printf.sprintf "Failed to send error response: %s" (Printexc.to_string e))
+
end
+
end;
+
run_stdio_server mcp_server
+
end
+
| None -> begin
+
if mcp_server.running then begin
+
(* No message received, but server is still running *)
+
Unix.sleepf 0.1; (* Small sleep to prevent CPU spinning *)
+
run_stdio_server mcp_server
+
end else begin
+
Log.debug "Server stopped during message processing"
+
end
+
end
+
end
+
end with
+
| exc -> begin
+
Log.error (Printf.sprintf "FATAL ERROR in server main loop: %s" (Printexc.to_string exc));
+
Log.error (Printf.sprintf "Backtrace: %s" (Printexc.get_backtrace()));
+
(* Try to continue anyway *)
+
if mcp_server.running then begin
+
Unix.sleepf 0.1;
+
run_stdio_server mcp_server
+
end
+
end
-
(* Process the message *)
-
process_message server json
-
with Yojson.Json_error msg ->
-
Log.err (fun m -> m "Error parsing JSON: %s" msg);
-
Log.err (fun m -> m "Input was: %s" line);
-
None)
+
(** Create an MCP server *)
+
let create ~server ~transport () =
+
{ server; transport; running = false }
-
(* 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.debug (fun m -> m "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
-
-
(* Run the MCP server with the given server configuration *)
-
let callback mcp_server _conn (request : Http.Request.t) body =
-
match request.meth with
-
| `POST -> (
-
Log.debug (fun m -> m "Received POST request");
-
let request_body_str =
-
Eio.Buf_read.(parse_exn take_all) body ~max_size:max_int
-
in
-
match process_input_line mcp_server request_body_str with
-
| Some mcp_response ->
-
let response_json = JSONRPCMessage.yojson_of_t mcp_response in
+
(** HTTP server placeholder (to be fully implemented) *)
+
let run_http_server mcp_server port =
+
Log.info (Printf.sprintf "%s HTTP server starting on port %d" mcp_server.server.name port);
+
Log.info "HTTP transport is a placeholder and not fully implemented yet";
+
+
(* This would be where we'd set up cohttp server *)
+
(*
+
let callback _conn req body =
+
let uri = req |> Cohttp.Request.uri in
+
let meth = req |> Cohttp.Request.meth |> Cohttp.Code.string_of_method in
+
+
(* Handle only POST /jsonrpc endpoint *)
+
match (meth, Uri.path uri) with
+
| "POST", "/jsonrpc" ->
+
(* Read the body *)
+
Cohttp_lwt.Body.to_string body >>= fun body_str ->
+
+
(* Parse JSON *)
+
let json = try Some (Yojson.Safe.from_string body_str) with _ -> None in
+
match json with
+
| Some json_msg ->
+
(* Process the message *)
+
let response_opt = process_message mcp_server.server json_msg in
+
(match response_opt with
+
| Some response ->
+
let response_json = JSONRPCMessage.yojson_of_t response in
let response_str = Yojson.Safe.to_string response_json in
-
Log.debug (fun m -> m "Sending MCP response: %s" response_str);
-
let headers =
-
Http.Header.of_list [ ("Content-Type", "application/json") ]
-
in
-
Cohttp_eio.Server.respond ~status:`OK ~headers
-
~body:(Cohttp_eio.Body.of_string response_str)
+
Cohttp_lwt_unix.Server.respond_string
+
~status:`OK
+
~body:response_str
+
~headers:(Cohttp.Header.init_with "Content-Type" "application/json")
()
+
| None ->
+
Cohttp_lwt_unix.Server.respond_string
+
~status:`OK
+
~body:"{}"
+
~headers:(Cohttp.Header.init_with "Content-Type" "application/json")
+
())
| None ->
-
Log.debug (fun m -> m "No MCP response needed");
-
Cohttp_eio.Server.respond ~status:`No_content
-
~body:(Cohttp_eio.Body.of_string "")
-
())
-
| _ ->
-
Log.info (fun m ->
-
m "Unsupported method: %s" (Http.Method.to_string request.meth));
-
Cohttp_eio.Server.respond ~status:`Method_not_allowed
-
~body:(Cohttp_eio.Body.of_string "Only POST is supported")
+
Cohttp_lwt_unix.Server.respond_string
+
~status:`Bad_request
+
~body:"{\"error\":\"Invalid JSON\"}"
+
~headers:(Cohttp.Header.init_with "Content-Type" "application/json")
+
()
+
| _ ->
+
(* Return 404 for any other routes *)
+
Cohttp_lwt_unix.Server.respond_string
+
~status:`Not_found
+
~body:"Not found"
()
-
-
let log_warning ex = Logs.warn (fun f -> f "%a" Eio.Exn.pp ex)
-
-
(** run the server using http transport *)
-
let run_server ?(port = 8080) ?(on_error = log_warning) env server =
-
let net = Eio.Stdenv.net env in
-
let addr = `Tcp (Eio.Net.Ipaddr.V4.loopback, port) in
-
-
Log.info (fun m ->
-
m "Starting http MCP server: %s v%s\nProtocol version: %s" (name server)
-
(version server) (protocol_version server));
-
-
Eio.Switch.run @@ fun sw ->
-
let server_spec = Cohttp_eio.Server.make ~callback:(callback server) () in
-
-
let server_socket =
-
Eio.Net.listen net ~sw ~backlog:128 ~reuse_addr:true addr
+
in
+
+
(* Create and start the server *)
+
let server = Cohttp_lwt_unix.Server.create
+
~mode:(`TCP (`Port port))
+
(Cohttp_lwt_unix.Server.make ~callback ())
in
-
Log.info (fun m -> m "MCP HTTP Server listening on http://localhost:%d" port);
-
-
Cohttp_eio.Server.run server_socket server_spec ~on_error
-
-
(** run the server using the stdio transport *)
-
let run_stdio_server env server =
-
let stdin = Eio.Stdenv.stdin env in
-
let stdout = Eio.Stdenv.stdout env in
-
-
Log.info (fun m ->
-
m "Starting stdio MCP server: %s v%s\nProtocol version: %s" (name server)
-
(version server) (protocol_version server));
-
-
(* Enable exception backtraces *)
-
Printexc.record_backtrace true;
-
-
let buf = Eio.Buf_read.of_flow stdin ~initial_size:100 ~max_size:1_000_000 in
+
+
(* Run the server *)
+
Lwt_main.run server
+
*)
+
+
(* For now, just wait until the server is stopped *)
+
while mcp_server.running do
+
Unix.sleep 1
+
done
-
(* Main processing loop *)
-
try
-
while true do
-
Log.info (fun m -> m "Waiting for message...");
-
let line = Eio.Buf_read.line buf in
+
(** Start the server based on transport type *)
+
let start server =
+
server.running <- true;
+
+
(* Run startup hook if provided *)
+
(match server.server.startup_hook with
+
| Some hook -> begin hook () end
+
| None -> begin () end);
+
+
(* Install signal handler *)
+
Sys.(set_signal sigint (Signal_handle (fun _ ->
+
Log.debug "Received interrupt signal, stopping server...";
+
server.running <- false
+
)));
+
+
match server.transport with
+
| Stdio -> begin
+
(* Setup stdout and stderr *)
+
set_binary_mode_out stdout false;
+
Log.info (Printf.sprintf "%s server started with stdio transport" server.server.name);
+
+
(* Run the server loop *)
+
run_stdio_server server
+
end
+
| Http -> begin
+
(* HTTP server placeholder *)
+
run_http_server server 8080
+
end
-
(* Process the input and send response if needed *)
-
match process_input_line server line with
-
| Some response -> send_response stdout response
-
| None -> Log.info (fun m -> m "No response needed for this message")
-
done
-
with
-
| End_of_file ->
-
Log.debug (fun m -> m "End of file received on stdin");
-
()
-
| Eio.Exn.Io _ as exn ->
-
(* Only a warning since on Windows, once the client closes the connection, we normally fail with `I/O error while reading: Eio.Io Net Connection_reset Unix_error (Broken pipe, "stub_cstruct_read", "")` *)
-
Log.warn (fun m ->
-
m "I/O error while reading: %s" (Printexc.to_string exn));
-
()
-
| exn ->
-
Log.err (fun m ->
-
m "Exception while reading: %s" (Printexc.to_string exn));
-
()
+
(** Stop the server *)
+
let stop server =
+
Log.info "Stopping server...";
+
server.running <- false;
+
+
(* Run shutdown hook if provided *)
+
match server.server.shutdown_hook with
+
| Some hook -> begin hook () end
+
| None -> begin () end
+54
lib/mcp_server.mli
···
+
(** MCP Server module - full implementation *)
+
+
(** Transport type for server *)
+
type transport_type =
+
| Stdio (** Read/write to stdin/stdout *)
+
| Http (** HTTP server - to be implemented *)
+
+
(** Server type *)
+
type t = {
+
server: Mcp_sdk.server;
+
transport: transport_type;
+
mutable running: bool;
+
}
+
+
(** Create an MCP server
+
@param server The Mcp_sdk server to use
+
@param transport The transport type to use
+
*)
+
val create : server:Mcp_sdk.server -> transport:transport_type -> unit -> t
+
+
(** Start the server
+
This function will block until the server is stopped.
+
@param server The server to start
+
*)
+
val start : t -> unit
+
+
(** Stop the server
+
This will set the running flag to false and invoke the shutdown hook.
+
@param server The server to stop
+
*)
+
val stop : t -> unit
+
+
(** Process a single message
+
@param server The Mcp_sdk server to use
+
@param message The JSON message to process
+
@return An optional response message
+
*)
+
val process_message : Mcp_sdk.server -> Yojson.Safe.t -> Mcp.JSONRPCMessage.t option
+
+
(** Run stdio server implementation
+
@param mcp_server The mcp_server to run
+
*)
+
val run_stdio_server : t -> unit
+
+
(** Read a message from stdio
+
@return An optional JSON message
+
*)
+
val read_stdio_message : unit -> Yojson.Safe.t option
+
+
(** Run HTTP server implementation (placeholder)
+
@param mcp_server The mcp_server to run
+
@param port The port to listen on
+
*)
+
val run_http_server : t -> int -> unit
-32
mcp.opam
···
-
# This file is generated by dune, edit dune-project instead
-
opam-version: "2.0"
-
synopsis: "Model Context Protocol for LLMs"
-
description: "This is all still a work in progress"
-
maintainer: ["anil@recoil.org"]
-
authors: ["Anil Madhavapeddy"]
-
license: "ISC"
-
depends: [
-
"dune" {>= "3.17"}
-
"ocaml" {>= "5.2.0"}
-
"jsonrpc"
-
"http"
-
"cohttp-eio"
-
"eio_main"
-
"eio"
-
"logs"
-
"odoc" {with-doc}
-
]
-
build: [
-
["dune" "subst"] {dev}
-
[
-
"dune"
-
"build"
-
"-p"
-
name
-
"-j"
-
jobs
-
"@install"
-
"@runtest" {with-test}
-
"@doc" {with-doc}
-
]
-
]
+178
spec/00-arch.md
···
+
---
+
title: Architecture
+
cascade:
+
type: docs
+
weight: 10
+
---
+
+
The Model Context Protocol (MCP) follows a client-host-server architecture where each
+
host can run multiple client instances. This architecture enables users to integrate AI
+
capabilities across applications while maintaining clear security boundaries and
+
isolating concerns. Built on JSON-RPC, MCP provides a stateful session protocol focused
+
on context exchange and sampling coordination between clients and servers.
+
+
## Core Components
+
+
```mermaid
+
graph LR
+
subgraph "Application Host Process"
+
H[Host]
+
C1[Client 1]
+
C2[Client 2]
+
C3[Client 3]
+
H --> C1
+
H --> C2
+
H --> C3
+
end
+
+
subgraph "Local machine"
+
S1[Server 1<br>Files & Git]
+
S2[Server 2<br>Database]
+
R1[("Local<br>Resource A")]
+
R2[("Local<br>Resource B")]
+
+
C1 --> S1
+
C2 --> S2
+
S1 <--> R1
+
S2 <--> R2
+
end
+
+
subgraph "Internet"
+
S3[Server 3<br>External APIs]
+
R3[("Remote<br>Resource C")]
+
+
C3 --> S3
+
S3 <--> R3
+
end
+
```
+
+
### Host
+
+
The host process acts as the container and coordinator:
+
+
- Creates and manages multiple client instances
+
- Controls client connection permissions and lifecycle
+
- Enforces security policies and consent requirements
+
- Handles user authorization decisions
+
- Coordinates AI/LLM integration and sampling
+
- Manages context aggregation across clients
+
+
### Clients
+
+
Each client is created by the host and maintains an isolated server connection:
+
+
- Establishes one stateful session per server
+
- Handles protocol negotiation and capability exchange
+
- Routes protocol messages bidirectionally
+
- Manages subscriptions and notifications
+
- Maintains security boundaries between servers
+
+
A host application creates and manages multiple clients, with each client having a 1:1
+
relationship with a particular server.
+
+
### Servers
+
+
Servers provide specialized context and capabilities:
+
+
- Expose resources, tools and prompts via MCP primitives
+
- Operate independently with focused responsibilities
+
- Request sampling through client interfaces
+
- Must respect security constraints
+
- Can be local processes or remote services
+
+
## Design Principles
+
+
MCP is built on several key design principles that inform its architecture and
+
implementation:
+
+
1. **Servers should be extremely easy to build**
+
+
- Host applications handle complex orchestration responsibilities
+
- Servers focus on specific, well-defined capabilities
+
- Simple interfaces minimize implementation overhead
+
- Clear separation enables maintainable code
+
+
2. **Servers should be highly composable**
+
+
- Each server provides focused functionality in isolation
+
- Multiple servers can be combined seamlessly
+
- Shared protocol enables interoperability
+
- Modular design supports extensibility
+
+
3. **Servers should not be able to read the whole conversation, nor "see into" other
+
servers**
+
+
- Servers receive only necessary contextual information
+
- Full conversation history stays with the host
+
- Each server connection maintains isolation
+
- Cross-server interactions are controlled by the host
+
- Host process enforces security boundaries
+
+
4. **Features can be added to servers and clients progressively**
+
- Core protocol provides minimal required functionality
+
- Additional capabilities can be negotiated as needed
+
- Servers and clients evolve independently
+
- Protocol designed for future extensibility
+
- Backwards compatibility is maintained
+
+
## Capability Negotiation
+
+
The Model Context Protocol uses a capability-based negotiation system where clients and
+
servers explicitly declare their supported features during initialization. Capabilities
+
determine which protocol features and primitives are available during a session.
+
+
- Servers declare capabilities like resource subscriptions, tool support, and prompt
+
templates
+
- Clients declare capabilities like sampling support and notification handling
+
- Both parties must respect declared capabilities throughout the session
+
- Additional capabilities can be negotiated through extensions to the protocol
+
+
```mermaid
+
sequenceDiagram
+
participant Host
+
participant Client
+
participant Server
+
+
Host->>+Client: Initialize client
+
Client->>+Server: Initialize session with capabilities
+
Server-->>Client: Respond with supported capabilities
+
+
Note over Host,Server: Active Session with Negotiated Features
+
+
loop Client Requests
+
Host->>Client: User- or model-initiated action
+
Client->>Server: Request (tools/resources)
+
Server-->>Client: Response
+
Client-->>Host: Update UI or respond to model
+
end
+
+
loop Server Requests
+
Server->>Client: Request (sampling)
+
Client->>Host: Forward to AI
+
Host-->>Client: AI response
+
Client-->>Server: Response
+
end
+
+
loop Notifications
+
Server--)Client: Resource updates
+
Client--)Server: Status changes
+
end
+
+
Host->>Client: Terminate
+
Client->>-Server: End session
+
deactivate Server
+
```
+
+
Each capability unlocks specific protocol features for use during the session. For
+
example:
+
+
- Implemented [server features]({{< ref "../server" >}}) must be advertised in the
+
server's capabilities
+
- Emitting resource subscription notifications requires the server to declare
+
subscription support
+
- Tool invocation requires the server to declare tool capabilities
+
- [Sampling]({{< ref "../client" >}}) requires the client to declare support in its
+
capabilities
+
+
This capability negotiation ensures clients and servers have a clear understanding of
+
supported functionality while maintaining protocol extensibility.
+4
spec/README.md
···
+
These specifications are snapshots from
+
https://github.com/modelcontextprotocol/specification/tree/main/docs/specification/2025-03-26
+
+
to help with the agentic prompting.
+386
spec/authorization.md
···
+
---
+
title: Authorization
+
type: docs
+
weight: 15
+
---
+
+
{{< callout type="info" >}} **Protocol Revision**: 2025-03-26 {{< /callout >}}
+
+
## 1. Introduction
+
+
### 1.1 Purpose and Scope
+
+
The Model Context Protocol provides authorization capabilities at the transport level,
+
enabling MCP clients to make requests to restricted MCP servers on behalf of resource
+
owners. This specification defines the authorization flow for HTTP-based transports.
+
+
### 1.2 Protocol Requirements
+
+
Authorization is **OPTIONAL** for MCP implementations. When supported:
+
+
- Implementations using an HTTP-based transport **SHOULD** conform to this specification.
+
- Implementations using an STDIO transport **SHOULD NOT** follow this specification, and
+
instead retrieve credentials from the environment.
+
- Implementations using alternative transports **MUST** follow established security best
+
practices for their protocol.
+
+
### 1.3 Standards Compliance
+
+
This authorization mechanism is based on established specifications listed below, but
+
implements a selected subset of their features to ensure security and interoperability
+
while maintaining simplicity:
+
+
- [OAuth 2.1 IETF DRAFT](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-12)
+
- OAuth 2.0 Authorization Server Metadata
+
([RFC8414](https://datatracker.ietf.org/doc/html/rfc8414))
+
- OAuth 2.0 Dynamic Client Registration Protocol
+
([RFC7591](https://datatracker.ietf.org/doc/html/rfc7591))
+
+
## 2. Authorization Flow
+
+
### 2.1 Overview
+
+
1. MCP auth implementations **MUST** implement OAuth 2.1 with appropriate security
+
measures for both confidential and public clients.
+
+
2. MCP auth implementations **SHOULD** support the OAuth 2.0 Dynamic Client Registration
+
Protocol ([RFC7591](https://datatracker.ietf.org/doc/html/rfc7591)).
+
+
3. MCP servers **SHOULD** and MCP clients **MUST** implement OAuth 2.0 Authorization
+
Server Metadata ([RFC8414](https://datatracker.ietf.org/doc/html/rfc8414)). Servers
+
that do not support Authorization Server Metadata **MUST** follow the default URI
+
schema.
+
+
### 2.2 Basic OAuth 2.1 Authorization
+
+
When authorization is required and not yet proven by the client, servers **MUST** respond
+
with _HTTP 401 Unauthorized_.
+
+
Clients initiate the
+
[OAuth 2.1 IETF DRAFT](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-12)
+
authorization flow after receiving the _HTTP 401 Unauthorized_.
+
+
The following demonstrates the basic OAuth 2.1 for public clients using PKCE.
+
+
```mermaid
+
sequenceDiagram
+
participant B as User-Agent (Browser)
+
participant C as Client
+
participant M as MCP Server
+
+
C->>M: MCP Request
+
M->>C: HTTP 401 Unauthorized
+
Note over C: Generate code_verifier and code_challenge
+
C->>B: Open browser with authorization URL + code_challenge
+
B->>M: GET /authorize
+
Note over M: User logs in and authorizes
+
M->>B: Redirect to callback URL with auth code
+
B->>C: Callback with authorization code
+
C->>M: Token Request with code + code_verifier
+
M->>C: Access Token (+ Refresh Token)
+
C->>M: MCP Request with Access Token
+
Note over C,M: Begin standard MCP message exchange
+
```
+
+
### 2.3 Server Metadata Discovery
+
+
For server capability discovery:
+
+
- MCP clients _MUST_ follow the OAuth 2.0 Authorization Server Metadata protocol defined
+
in [RFC8414](https://datatracker.ietf.org/doc/html/rfc8414).
+
- MCP server _SHOULD_ follow the OAuth 2.0 Authorization Server Metadata protocol.
+
- MCP servers that do not support the OAuth 2.0 Authorization Server Metadata protocol,
+
_MUST_ support fallback URLs.
+
+
The discovery flow is illustrated below:
+
+
```mermaid
+
sequenceDiagram
+
participant C as Client
+
participant S as Server
+
+
C->>S: GET /.well-known/oauth-authorization-server
+
alt Discovery Success
+
S->>C: 200 OK + Metadata Document
+
Note over C: Use endpoints from metadata
+
else Discovery Failed
+
S->>C: 404 Not Found
+
Note over C: Fall back to default endpoints
+
end
+
Note over C: Continue with authorization flow
+
```
+
+
#### 2.3.1 Server Metadata Discovery Headers
+
+
MCP clients _SHOULD_ include the header `MCP-Protocol-Version: <protocol-version>` during
+
Server Metadata Discovery to allow the MCP server to respond based on the MCP protocol
+
version.
+
+
For example: `MCP-Protocol-Version: 2024-11-05`
+
+
#### 2.3.2 Authorization Base URL
+
+
The authorization base URL **MUST** be determined from the MCP server URL by discarding
+
any existing `path` component. For example:
+
+
If the MCP server URL is `https://api.example.com/v1/mcp`, then:
+
+
- The authorization base URL is `https://api.example.com`
+
- The metadata endpoint **MUST** be at
+
`https://api.example.com/.well-known/oauth-authorization-server`
+
+
This ensures authorization endpoints are consistently located at the root level of the
+
domain hosting the MCP server, regardless of any path components in the MCP server URL.
+
+
#### 2.3.3 Fallbacks for Servers without Metadata Discovery
+
+
For servers that do not implement OAuth 2.0 Authorization Server Metadata, clients
+
**MUST** use the following default endpoint paths relative to the authorization base URL
+
(as defined in [Section 2.3.2](#232-authorization-base-url)):
+
+
| Endpoint | Default Path | Description |
+
| ---------------------- | ------------ | ------------------------------------ |
+
| Authorization Endpoint | /authorize | Used for authorization requests |
+
| Token Endpoint | /token | Used for token exchange & refresh |
+
| Registration Endpoint | /register | Used for dynamic client registration |
+
+
For example, with an MCP server hosted at `https://api.example.com/v1/mcp`, the default
+
endpoints would be:
+
+
- `https://api.example.com/authorize`
+
- `https://api.example.com/token`
+
- `https://api.example.com/register`
+
+
Clients **MUST** first attempt to discover endpoints via the metadata document before
+
falling back to default paths. When using default paths, all other protocol requirements
+
remain unchanged.
+
+
### 2.3 Dynamic Client Registration
+
+
MCP clients and servers **SHOULD** support the
+
[OAuth 2.0 Dynamic Client Registration Protocol](https://datatracker.ietf.org/doc/html/rfc7591)
+
to allow MCP clients to obtain OAuth client IDs without user interaction. This provides a
+
standardized way for clients to automatically register with new servers, which is crucial
+
for MCP because:
+
+
- Clients cannot know all possible servers in advance
+
- Manual registration would create friction for users
+
- It enables seamless connection to new servers
+
- Servers can implement their own registration policies
+
+
Any MCP servers that _do not_ support Dynamic Client Registration need to provide
+
alternative ways to obtain a client ID (and, if applicable, client secret). For one of
+
these servers, MCP clients will have to either:
+
+
1. Hardcode a client ID (and, if applicable, client secret) specifically for that MCP
+
server, or
+
2. Present a UI to users that allows them to enter these details, after registering an
+
OAuth client themselves (e.g., through a configuration interface hosted by the
+
server).
+
+
### 2.4 Authorization Flow Steps
+
+
The complete Authorization flow proceeds as follows:
+
+
```mermaid
+
sequenceDiagram
+
participant B as User-Agent (Browser)
+
participant C as Client
+
participant M as MCP Server
+
+
C->>M: GET /.well-known/oauth-authorization-server
+
alt Server Supports Discovery
+
M->>C: Authorization Server Metadata
+
else No Discovery
+
M->>C: 404 (Use default endpoints)
+
end
+
+
alt Dynamic Client Registration
+
C->>M: POST /register
+
M->>C: Client Credentials
+
end
+
+
Note over C: Generate PKCE Parameters
+
C->>B: Open browser with authorization URL + code_challenge
+
B->>M: Authorization Request
+
Note over M: User /authorizes
+
M->>B: Redirect to callback with authorization code
+
B->>C: Authorization code callback
+
C->>M: Token Request + code_verifier
+
M->>C: Access Token (+ Refresh Token)
+
C->>M: API Requests with Access Token
+
```
+
+
#### 2.4.1 Decision Flow Overview
+
+
```mermaid
+
flowchart TD
+
A[Start Auth Flow] --> B{Check Metadata Discovery}
+
B -->|Available| C[Use Metadata Endpoints]
+
B -->|Not Available| D[Use Default Endpoints]
+
+
C --> G{Check Registration Endpoint}
+
D --> G
+
+
G -->|Available| H[Perform Dynamic Registration]
+
G -->|Not Available| I[Alternative Registration Required]
+
+
H --> J[Start OAuth Flow]
+
I --> J
+
+
J --> K[Generate PKCE Parameters]
+
K --> L[Request Authorization]
+
L --> M[User Authorization]
+
M --> N[Exchange Code for Tokens]
+
N --> O[Use Access Token]
+
```
+
+
### 2.5 Access Token Usage
+
+
#### 2.5.1 Token Requirements
+
+
Access token handling **MUST** conform to
+
[OAuth 2.1 Section 5](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-12#section-5)
+
requirements for resource requests. Specifically:
+
+
1. MCP client **MUST** use the Authorization request header field
+
[Section 5.1.1](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-12#section-5.1.1):
+
+
```
+
Authorization: Bearer <access-token>
+
```
+
+
Note that authorization **MUST** be included in every HTTP request from client to server,
+
even if they are part of the same logical session.
+
+
2. Access tokens **MUST NOT** be included in the URI query string
+
+
Example request:
+
+
```http
+
GET /v1/contexts HTTP/1.1
+
Host: mcp.example.com
+
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
+
```
+
+
#### 2.5.2 Token Handling
+
+
Resource servers **MUST** validate access tokens as described in
+
[Section 5.2](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-12#section-5.2).
+
If validation fails, servers **MUST** respond according to
+
[Section 5.3](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-12#section-5.3)
+
error handling requirements. Invalid or expired tokens **MUST** receive a HTTP 401
+
response.
+
+
### 2.6 Security Considerations
+
+
The following security requirements **MUST** be implemented:
+
+
1. Clients **MUST** securely store tokens following OAuth 2.0 best practices
+
2. Servers **SHOULD** enforce token expiration and rotation
+
3. All authorization endpoints **MUST** be served over HTTPS
+
4. Servers **MUST** validate redirect URIs to prevent open redirect vulnerabilities
+
5. Redirect URIs **MUST** be either localhost URLs or HTTPS URLs
+
+
### 2.7 Error Handling
+
+
Servers **MUST** return appropriate HTTP status codes for authorization errors:
+
+
| Status Code | Description | Usage |
+
| ----------- | ------------ | ------------------------------------------ |
+
| 401 | Unauthorized | Authorization required or token invalid |
+
| 403 | Forbidden | Invalid scopes or insufficient permissions |
+
| 400 | Bad Request | Malformed authorization request |
+
+
### 2.8 Implementation Requirements
+
+
1. Implementations **MUST** follow OAuth 2.1 security best practices
+
2. PKCE is **REQUIRED** for all clients
+
3. Token rotation **SHOULD** be implemented for enhanced security
+
4. Token lifetimes **SHOULD** be limited based on security requirements
+
+
### 2.9 Third-Party Authorization Flow
+
+
#### 2.9.1 Overview
+
+
MCP servers **MAY** support delegated authorization through third-party authorization
+
servers. In this flow, the MCP server acts as both an OAuth client (to the third-party
+
auth server) and an OAuth authorization server (to the MCP client).
+
+
#### 2.9.2 Flow Description
+
+
The third-party authorization flow comprises these steps:
+
+
1. MCP client initiates standard OAuth flow with MCP server
+
2. MCP server redirects user to third-party authorization server
+
3. User authorizes with third-party server
+
4. Third-party server redirects back to MCP server with authorization code
+
5. MCP server exchanges code for third-party access token
+
6. MCP server generates its own access token bound to the third-party session
+
7. MCP server completes original OAuth flow with MCP client
+
+
```mermaid
+
sequenceDiagram
+
participant B as User-Agent (Browser)
+
participant C as MCP Client
+
participant M as MCP Server
+
participant T as Third-Party Auth Server
+
+
C->>M: Initial OAuth Request
+
M->>B: Redirect to Third-Party /authorize
+
B->>T: Authorization Request
+
Note over T: User authorizes
+
T->>B: Redirect to MCP Server callback
+
B->>M: Authorization code
+
M->>T: Exchange code for token
+
T->>M: Third-party access token
+
Note over M: Generate bound MCP token
+
M->>B: Redirect to MCP Client callback
+
B->>C: MCP authorization code
+
C->>M: Exchange code for token
+
M->>C: MCP access token
+
```
+
+
#### 2.9.3 Session Binding Requirements
+
+
MCP servers implementing third-party authorization **MUST**:
+
+
1. Maintain secure mapping between third-party tokens and issued MCP tokens
+
2. Validate third-party token status before honoring MCP tokens
+
3. Implement appropriate token lifecycle management
+
4. Handle third-party token expiration and renewal
+
+
#### 2.9.4 Security Considerations
+
+
When implementing third-party authorization, servers **MUST**:
+
+
1. Validate all redirect URIs
+
2. Securely store third-party credentials
+
3. Implement appropriate session timeout handling
+
4. Consider security implications of token chaining
+
5. Implement proper error handling for third-party auth failures
+
+
## 3. Best Practices
+
+
#### 3.1 Local clients as Public OAuth 2.1 Clients
+
+
We strongly recommend that local clients implement OAuth 2.1 as a public client:
+
+
1. Utilizing code challenges (PKCE) for authorization requests to prevent interception
+
attacks
+
2. Implementing secure token storage appropriate for the local system
+
3. Following token refresh best practices to maintain sessions
+
4. Properly handling token expiration and renewal
+
+
#### 3.2 Authorization Metadata Discovery
+
+
We strongly recommend that all clients implement metadata discovery. This reduces the
+
need for users to provide endpoints manually or clients to fallback to the defined
+
defaults.
+
+
#### 3.3 Dynamic Client Registration
+
+
Since clients do not know the set of MCP servers in advance, we strongly recommend the
+
implementation of dynamic client registration. This allows applications to automatically
+
register with the MCP server, and removes the need for users to obtain client ids
+
manually.
+239
spec/lifecycle.md
···
+
---
+
title: Lifecycle
+
type: docs
+
weight: 30
+
---
+
+
{{< callout type="info" >}} **Protocol Revision**: 2025-03-26 {{< /callout >}}
+
+
The Model Context Protocol (MCP) defines a rigorous lifecycle for client-server
+
connections that ensures proper capability negotiation and state management.
+
+
1. **Initialization**: Capability negotiation and protocol version agreement
+
2. **Operation**: Normal protocol communication
+
3. **Shutdown**: Graceful termination of the connection
+
+
```mermaid
+
sequenceDiagram
+
participant Client
+
participant Server
+
+
Note over Client,Server: Initialization Phase
+
activate Client
+
Client->>+Server: initialize request
+
Server-->>Client: initialize response
+
Client--)Server: initialized notification
+
+
Note over Client,Server: Operation Phase
+
rect rgb(200, 220, 250)
+
note over Client,Server: Normal protocol operations
+
end
+
+
Note over Client,Server: Shutdown
+
Client--)-Server: Disconnect
+
deactivate Server
+
Note over Client,Server: Connection closed
+
```
+
+
## Lifecycle Phases
+
+
### Initialization
+
+
The initialization phase **MUST** be the first interaction between client and server.
+
During this phase, the client and server:
+
+
- Establish protocol version compatibility
+
- Exchange and negotiate capabilities
+
- Share implementation details
+
+
The client **MUST** initiate this phase by sending an `initialize` request containing:
+
+
- Protocol version supported
+
- Client capabilities
+
- Client implementation information
+
+
```json
+
{
+
"jsonrpc": "2.0",
+
"id": 1,
+
"method": "initialize",
+
"params": {
+
"protocolVersion": "2024-11-05",
+
"capabilities": {
+
"roots": {
+
"listChanged": true
+
},
+
"sampling": {}
+
},
+
"clientInfo": {
+
"name": "ExampleClient",
+
"version": "1.0.0"
+
}
+
}
+
}
+
```
+
+
The initialize request **MUST NOT** be part of a JSON-RPC
+
[batch](https://www.jsonrpc.org/specification#batch), as other requests and notifications
+
are not possible until initialization has completed. This also permits backwards
+
compatibility with prior protocol versions that do not explicitly support JSON-RPC
+
batches.
+
+
The server **MUST** respond with its own capabilities and information:
+
+
```json
+
{
+
"jsonrpc": "2.0",
+
"id": 1,
+
"result": {
+
"protocolVersion": "2024-11-05",
+
"capabilities": {
+
"logging": {},
+
"prompts": {
+
"listChanged": true
+
},
+
"resources": {
+
"subscribe": true,
+
"listChanged": true
+
},
+
"tools": {
+
"listChanged": true
+
}
+
},
+
"serverInfo": {
+
"name": "ExampleServer",
+
"version": "1.0.0"
+
}
+
}
+
}
+
```
+
+
After successful initialization, the client **MUST** send an `initialized` notification
+
to indicate it is ready to begin normal operations:
+
+
```json
+
{
+
"jsonrpc": "2.0",
+
"method": "notifications/initialized"
+
}
+
```
+
+
- The client **SHOULD NOT** send requests other than
+
[pings]({{< ref "utilities/ping" >}}) before the server has responded to the
+
`initialize` request.
+
- The server **SHOULD NOT** send requests other than
+
[pings]({{< ref "utilities/ping" >}}) and
+
[logging]({{< ref "../server/utilities/logging" >}}) before receiving the `initialized`
+
notification.
+
+
#### Version Negotiation
+
+
In the `initialize` request, the client **MUST** send a protocol version it supports.
+
This **SHOULD** be the _latest_ version supported by the client.
+
+
If the server supports the requested protocol version, it **MUST** respond with the same
+
version. Otherwise, the server **MUST** respond with another protocol version it
+
supports. This **SHOULD** be the _latest_ version supported by the server.
+
+
If the client does not support the version in the server's response, it **SHOULD**
+
disconnect.
+
+
#### Capability Negotiation
+
+
Client and server capabilities establish which optional protocol features will be
+
available during the session.
+
+
Key capabilities include:
+
+
| Category | Capability | Description |
+
| -------- | -------------- | -------------------------------------------------------------------------- |
+
| Client | `roots` | Ability to provide filesystem [roots]({{< ref "../client/roots" >}}) |
+
| Client | `sampling` | Support for LLM [sampling]({{< ref "../client/sampling" >}}) requests |
+
| Client | `experimental` | Describes support for non-standard experimental features |
+
| Server | `prompts` | Offers [prompt templates]({{< ref "../server/prompts" >}}) |
+
| Server | `resources` | Provides readable [resources]({{< ref "../server/resources" >}}) |
+
| Server | `tools` | Exposes callable [tools]({{< ref "../server/tools" >}}) |
+
| Server | `logging` | Emits structured [log messages]({{< ref "../server/utilities/logging" >}}) |
+
| Server | `experimental` | Describes support for non-standard experimental features |
+
+
Capability objects can describe sub-capabilities like:
+
+
- `listChanged`: Support for list change notifications (for prompts, resources, and
+
tools)
+
- `subscribe`: Support for subscribing to individual items' changes (resources only)
+
+
### Operation
+
+
During the operation phase, the client and server exchange messages according to the
+
negotiated capabilities.
+
+
Both parties **SHOULD**:
+
+
- Respect the negotiated protocol version
+
- Only use capabilities that were successfully negotiated
+
+
### Shutdown
+
+
During the shutdown phase, one side (usually the client) cleanly terminates the protocol
+
connection. No specific shutdown messages are definedโ€”instead, the underlying transport
+
mechanism should be used to signal connection termination:
+
+
#### stdio
+
+
For the stdio [transport]({{< ref "transports" >}}), the client **SHOULD** initiate
+
shutdown by:
+
+
1. First, closing the input stream to the child process (the server)
+
2. Waiting for the server to exit, or sending `SIGTERM` if the server does not exit
+
within a reasonable time
+
3. Sending `SIGKILL` if the server does not exit within a reasonable time after `SIGTERM`
+
+
The server **MAY** initiate shutdown by closing its output stream to the client and
+
exiting.
+
+
#### HTTP
+
+
For HTTP [transports]({{< ref "transports" >}}), shutdown is indicated by closing the
+
associated HTTP connection(s).
+
+
## Timeouts
+
+
Implementations **SHOULD** establish timeouts for all sent requests, to prevent hung
+
connections and resource exhaustion. When the request has not received a success or error
+
response within the timeout period, the sender **SHOULD** issue a [cancellation
+
notification]({{< ref "utilities/cancellation" >}}) for that request and stop waiting for
+
a response.
+
+
SDKs and other middleware **SHOULD** allow these timeouts to be configured on a
+
per-request basis.
+
+
Implementations **MAY** choose to reset the timeout clock when receiving a [progress
+
notification]({{< ref "utilities/progress" >}}) corresponding to the request, as this
+
implies that work is actually happening. However, implementations **SHOULD** always
+
enforce a maximum timeout, regardless of progress notifications, to limit the impact of a
+
misbehaving client or server.
+
+
## Error Handling
+
+
Implementations **SHOULD** be prepared to handle these error cases:
+
+
- Protocol version mismatch
+
- Failure to negotiate required capabilities
+
- Request [timeouts](#timeouts)
+
+
Example initialization error:
+
+
```json
+
{
+
"jsonrpc": "2.0",
+
"id": 1,
+
"error": {
+
"code": -32602,
+
"message": "Unsupported protocol version",
+
"data": {
+
"supported": ["2024-11-05"],
+
"requested": "1.0.0"
+
}
+
}
+
}
+
```
+265
spec/prompts.md
···
+
---
+
title: Prompts
+
weight: 10
+
---
+
+
{{< callout type="info" >}} **Protocol Revision**: 2025-03-26 {{< /callout >}}
+
+
The Model Context Protocol (MCP) provides a standardized way for servers to expose prompt
+
templates to clients. Prompts allow servers to provide structured messages and
+
instructions for interacting with language models. Clients can discover available
+
prompts, retrieve their contents, and provide arguments to customize them.
+
+
## User Interaction Model
+
+
Prompts are designed to be **user-controlled**, meaning they are exposed from servers to
+
clients with the intention of the user being able to explicitly select them for use.
+
+
Typically, prompts would be triggered through user-initiated commands in the user
+
interface, which allows users to naturally discover and invoke available prompts.
+
+
For example, as slash commands:
+
+
![Example of prompt exposed as slash command](slash-command.png)
+
+
However, implementors are free to expose prompts through any interface pattern that suits
+
their needs&mdash;the protocol itself does not mandate any specific user interaction
+
model.
+
+
## Capabilities
+
+
Servers that support prompts **MUST** declare the `prompts` capability during
+
[initialization]({{< ref "../basic/lifecycle#initialization" >}}):
+
+
/draft`json { "capabilities": { "prompts": { "listChanged": true } } }
+
+
````
+
+
`listChanged` indicates whether the server will emit notifications when the list of
+
available prompts changes.
+
+
## Protocol Messages
+
+
### Listing Prompts
+
+
To retrieve available prompts, clients send a `prompts/list` request. This operation
+
supports [pagination]({{< ref "utilities/pagination" >}}).
+
+
**Request:**
+
+
```json
+
{
+
"jsonrpc": "2.0",
+
"id": 1,
+
"method": "prompts/list",
+
"params": {
+
"cursor": "optional-cursor-value"
+
}
+
}
+
````
+
+
**Response:**
+
+
```json
+
{
+
"jsonrpc": "2.0",
+
"id": 1,
+
"result": {
+
"prompts": [
+
{
+
"name": "code_review",
+
"description": "Asks the LLM to analyze code quality and suggest improvements",
+
"arguments": [
+
{
+
"name": "code",
+
"description": "The code to review",
+
"required": true
+
}
+
]
+
}
+
],
+
"nextCursor": "next-page-cursor"
+
}
+
}
+
```
+
+
### Getting a Prompt
+
+
To retrieve a specific prompt, clients send a `prompts/get` request. Arguments may be
+
auto-completed through [the completion API]({{< ref "utilities/completion" >}}).
+
+
**Request:**
+
+
```json
+
{
+
"jsonrpc": "2.0",
+
"id": 2,
+
"method": "prompts/get",
+
"params": {
+
"name": "code_review",
+
"arguments": {
+
"code": "def hello():\n print('world')"
+
}
+
}
+
}
+
```
+
+
**Response:**
+
+
```json
+
{
+
"jsonrpc": "2.0",
+
"id": 2,
+
"result": {
+
"description": "Code review prompt",
+
"messages": [
+
{
+
"role": "user",
+
"content": {
+
"type": "text",
+
"text": "Please review this Python code:\ndef hello():\n print('world')"
+
}
+
}
+
]
+
}
+
}
+
```
+
+
### List Changed Notification
+
+
When the list of available prompts changes, servers that declared the `listChanged`
+
capability **SHOULD** send a notification:
+
+
```json
+
{
+
"jsonrpc": "2.0",
+
"method": "notifications/prompts/list_changed"
+
}
+
```
+
+
## Message Flow
+
+
```mermaid
+
sequenceDiagram
+
participant Client
+
participant Server
+
+
Note over Client,Server: Discovery
+
Client->>Server: prompts/list
+
Server-->>Client: List of prompts
+
+
Note over Client,Server: Usage
+
Client->>Server: prompts/get
+
Server-->>Client: Prompt content
+
+
opt listChanged
+
Note over Client,Server: Changes
+
Server--)Client: prompts/list_changed
+
Client->>Server: prompts/list
+
Server-->>Client: Updated prompts
+
end
+
```
+
+
## Data Types
+
+
### Prompt
+
+
A prompt definition includes:
+
+
- `name`: Unique identifier for the prompt
+
- `description`: Optional human-readable description
+
- `arguments`: Optional list of arguments for customization
+
+
### PromptMessage
+
+
Messages in a prompt can contain:
+
+
- `role`: Either "user" or "assistant" to indicate the speaker
+
- `content`: One of the following content types:
+
+
#### Text Content
+
+
Text content represents plain text messages:
+
+
```json
+
{
+
"type": "text",
+
"text": "The text content of the message"
+
}
+
```
+
+
This is the most common content type used for natural language interactions.
+
+
#### Image Content
+
+
Image content allows including visual information in messages:
+
+
```json
+
{
+
"type": "image",
+
"data": "base64-encoded-image-data",
+
"mimeType": "image/png"
+
}
+
```
+
+
The image data **MUST** be base64-encoded and include a valid MIME type. This enables
+
multi-modal interactions where visual context is important.
+
+
#### Audio Content
+
+
Audio content allows including audio information in messages:
+
+
```json
+
{
+
"type": "audio",
+
"data": "base64-encoded-audio-data",
+
"mimeType": "audio/wav"
+
}
+
```
+
+
The audio data MUST be base64-encoded and include a valid MIME type. This enables
+
multi-modal interactions where audio context is important.
+
+
#### Embedded Resources
+
+
Embedded resources allow referencing server-side resources directly in messages:
+
+
```json
+
{
+
"type": "resource",
+
"resource": {
+
"uri": "resource://example",
+
"mimeType": "text/plain",
+
"text": "Resource content"
+
}
+
}
+
```
+
+
Resources can contain either text or binary (blob) data and **MUST** include:
+
+
- A valid resource URI
+
- The appropriate MIME type
+
- Either text content or base64-encoded blob data
+
+
Embedded resources enable prompts to seamlessly incorporate server-managed content like
+
documentation, code samples, or other reference materials directly into the conversation
+
flow.
+
+
## Error Handling
+
+
Servers **SHOULD** return standard JSON-RPC errors for common failure cases:
+
+
- Invalid prompt name: `-32602` (Invalid params)
+
- Missing required arguments: `-32602` (Invalid params)
+
- Internal errors: `-32603` (Internal error)
+
+
## Implementation Considerations
+
+
1. Servers **SHOULD** validate prompt arguments before processing
+
2. Clients **SHOULD** handle pagination for large prompt lists
+
3. Both parties **SHOULD** respect capability negotiation
+
+
## Security
+
+
Implementations **MUST** carefully validate all prompt inputs and outputs to prevent
+
injection attacks or unauthorized access to resources.
+357
spec/resources.md
···
+
---
+
title: Resources
+
type: docs
+
weight: 20
+
---
+
+
{{< callout type="info" >}} **Protocol Revision**: 2025-03-26 {{< /callout >}}
+
+
The Model Context Protocol (MCP) provides a standardized way for servers to expose
+
resources to clients. Resources allow servers to share data that provides context to
+
language models, such as files, database schemas, or application-specific information.
+
Each resource is uniquely identified by a
+
[URI](https://datatracker.ietf.org/doc/html/rfc3986).
+
+
## User Interaction Model
+
+
Resources in MCP are designed to be **application-driven**, with host applications
+
determining how to incorporate context based on their needs.
+
+
For example, applications could:
+
+
- Expose resources through UI elements for explicit selection, in a tree or list view
+
- Allow the user to search through and filter available resources
+
- Implement automatic context inclusion, based on heuristics or the AI model's selection
+
+
![Example of resource context picker](resource-picker.png)
+
+
However, implementations are free to expose resources through any interface pattern that
+
suits their needs&mdash;the protocol itself does not mandate any specific user
+
interaction model.
+
+
## Capabilities
+
+
Servers that support resources **MUST** declare the `resources` capability:
+
+
```json
+
{
+
"capabilities": {
+
"resources": {
+
"subscribe": true,
+
"listChanged": true
+
}
+
}
+
}
+
```
+
+
The capability supports two optional features:
+
+
- `subscribe`: whether the client can subscribe to be notified of changes to individual
+
resources.
+
- `listChanged`: whether the server will emit notifications when the list of available
+
resources changes.
+
+
Both `subscribe` and `listChanged` are optional&mdash;servers can support neither,
+
either, or both:
+
+
```json
+
{
+
"capabilities": {
+
"resources": {} // Neither feature supported
+
}
+
}
+
```
+
+
```json
+
{
+
"capabilities": {
+
"resources": {
+
"subscribe": true // Only subscriptions supported
+
}
+
}
+
}
+
```
+
+
```json
+
{
+
"capabilities": {
+
"resources": {
+
"listChanged": true // Only list change notifications supported
+
}
+
}
+
}
+
```
+
+
## Protocol Messages
+
+
### Listing Resources
+
+
To discover available resources, clients send a `resources/list` request. This operation
+
supports [pagination]({{< ref "utilities/pagination" >}}).
+
+
**Request:**
+
+
```json
+
{
+
"jsonrpc": "2.0",
+
"id": 1,
+
"method": "resources/list",
+
"params": {
+
"cursor": "optional-cursor-value"
+
}
+
}
+
```
+
+
**Response:**
+
+
```json
+
{
+
"jsonrpc": "2.0",
+
"id": 1,
+
"result": {
+
"resources": [
+
{
+
"uri": "file:///project/src/main.rs",
+
"name": "main.rs",
+
"description": "Primary application entry point",
+
"mimeType": "text/x-rust"
+
}
+
],
+
"nextCursor": "next-page-cursor"
+
}
+
}
+
```
+
+
### Reading Resources
+
+
To retrieve resource contents, clients send a `resources/read` request:
+
+
**Request:**
+
+
```json
+
{
+
"jsonrpc": "2.0",
+
"id": 2,
+
"method": "resources/read",
+
"params": {
+
"uri": "file:///project/src/main.rs"
+
}
+
}
+
```
+
+
**Response:**
+
+
```json
+
{
+
"jsonrpc": "2.0",
+
"id": 2,
+
"result": {
+
"contents": [
+
{
+
"uri": "file:///project/src/main.rs",
+
"mimeType": "text/x-rust",
+
"text": "fn main() {\n println!(\"Hello world!\");\n}"
+
}
+
]
+
}
+
}
+
```
+
+
### Resource Templates
+
+
Resource templates allow servers to expose parameterized resources using
+
[URI templates](https://datatracker.ietf.org/doc/html/rfc6570). Arguments may be
+
auto-completed through [the completion API]({{< ref "utilities/completion" >}}).
+
+
**Request:**
+
+
```json
+
{
+
"jsonrpc": "2.0",
+
"id": 3,
+
"method": "resources/templates/list"
+
}
+
```
+
+
**Response:**
+
+
```json
+
{
+
"jsonrpc": "2.0",
+
"id": 3,
+
"result": {
+
"resourceTemplates": [
+
{
+
"uriTemplate": "file:///{path}",
+
"name": "Project Files",
+
"description": "Access files in the project directory",
+
"mimeType": "application/octet-stream"
+
}
+
]
+
}
+
}
+
```
+
+
### List Changed Notification
+
+
When the list of available resources changes, servers that declared the `listChanged`
+
capability **SHOULD** send a notification:
+
+
```json
+
{
+
"jsonrpc": "2.0",
+
"method": "notifications/resources/list_changed"
+
}
+
```
+
+
### Subscriptions
+
+
The protocol supports optional subscriptions to resource changes. Clients can subscribe
+
to specific resources and receive notifications when they change:
+
+
**Subscribe Request:**
+
+
```json
+
{
+
"jsonrpc": "2.0",
+
"id": 4,
+
"method": "resources/subscribe",
+
"params": {
+
"uri": "file:///project/src/main.rs"
+
}
+
}
+
```
+
+
**Update Notification:**
+
+
```json
+
{
+
"jsonrpc": "2.0",
+
"method": "notifications/resources/updated",
+
"params": {
+
"uri": "file:///project/src/main.rs"
+
}
+
}
+
```
+
+
## Message Flow
+
+
```mermaid
+
sequenceDiagram
+
participant Client
+
participant Server
+
+
Note over Client,Server: Resource Discovery
+
Client->>Server: resources/list
+
Server-->>Client: List of resources
+
+
Note over Client,Server: Resource Access
+
Client->>Server: resources/read
+
Server-->>Client: Resource contents
+
+
Note over Client,Server: Subscriptions
+
Client->>Server: resources/subscribe
+
Server-->>Client: Subscription confirmed
+
+
Note over Client,Server: Updates
+
Server--)Client: notifications/resources/updated
+
Client->>Server: resources/read
+
Server-->>Client: Updated contents
+
```
+
+
## Data Types
+
+
### Resource
+
+
A resource definition includes:
+
+
- `uri`: Unique identifier for the resource
+
- `name`: Human-readable name
+
- `description`: Optional description
+
- `mimeType`: Optional MIME type
+
- `size`: Optional size in bytes
+
+
### Resource Contents
+
+
Resources can contain either text or binary data:
+
+
#### Text Content
+
+
```json
+
{
+
"uri": "file:///example.txt",
+
"mimeType": "text/plain",
+
"text": "Resource content"
+
}
+
```
+
+
#### Binary Content
+
+
```json
+
{
+
"uri": "file:///example.png",
+
"mimeType": "image/png",
+
"blob": "base64-encoded-data"
+
}
+
```
+
+
## Common URI Schemes
+
+
The protocol defines several standard URI schemes. This list not
+
exhaustive&mdash;implementations are always free to use additional, custom URI schemes.
+
+
### https://
+
+
Used to represent a resource available on the web.
+
+
Servers **SHOULD** use this scheme only when the client is able to fetch and load the
+
resource directly from the web on its ownโ€”that is, it doesnโ€™t need to read the resource
+
via the MCP server.
+
+
For other use cases, servers **SHOULD** prefer to use another URI scheme, or define a
+
custom one, even if the server will itself be downloading resource contents over the
+
internet.
+
+
### file://
+
+
Used to identify resources that behave like a filesystem. However, the resources do not
+
need to map to an actual physical filesystem.
+
+
MCP servers **MAY** identify file:// resources with an
+
[XDG MIME type](https://specifications.freedesktop.org/shared-mime-info-spec/0.14/ar01s02.html#id-1.3.14),
+
like `inode/directory`, to represent non-regular files (such as directories) that donโ€™t
+
otherwise have a standard MIME type.
+
+
### git://
+
+
Git version control integration.
+
+
## Error Handling
+
+
Servers **SHOULD** return standard JSON-RPC errors for common failure cases:
+
+
- Resource not found: `-32002`
+
- Internal errors: `-32603`
+
+
Example error:
+
+
```json
+
{
+
"jsonrpc": "2.0",
+
"id": 5,
+
"error": {
+
"code": -32002,
+
"message": "Resource not found",
+
"data": {
+
"uri": "file:///nonexistent.txt"
+
}
+
}
+
}
+
```
+
+
## Security Considerations
+
+
1. Servers **MUST** validate all resource URIs
+
2. Access controls **SHOULD** be implemented for sensitive resources
+
3. Binary data **MUST** be properly encoded
+
4. Resource permissions **SHOULD** be checked before operations
spec/slash-command.png

This is a binary file and will not be displayed.

+299
spec/tools.md
···
+
---
+
title: Tools
+
type: docs
+
weight: 40
+
---
+
+
{{< callout type="info" >}} **Protocol Revision**: 2025-03-26 {{< /callout >}}
+
+
The Model Context Protocol (MCP) allows servers to expose tools that can be invoked by
+
language models. Tools enable models to interact with external systems, such as querying
+
databases, calling APIs, or performing computations. Each tool is uniquely identified by
+
a name and includes metadata describing its schema.
+
+
## User Interaction Model
+
+
Tools in MCP are designed to be **model-controlled**, meaning that the language model can
+
discover and invoke tools automatically based on its contextual understanding and the
+
user's prompts.
+
+
However, implementations are free to expose tools through any interface pattern that
+
suits their needs&mdash;the protocol itself does not mandate any specific user
+
interaction model.
+
+
{{< callout type="warning" >}} For trust & safety and security, there **SHOULD** always
+
be a human in the loop with the ability to deny tool invocations.
+
+
Applications **SHOULD**:
+
+
- Provide UI that makes clear which tools are being exposed to the AI model
+
- Insert clear visual indicators when tools are invoked
+
- Present confirmation prompts to the user for operations, to ensure a human is in the
+
loop {{< /callout >}}
+
+
## Capabilities
+
+
Servers that support tools **MUST** declare the `tools` capability:
+
+
```json
+
{
+
"capabilities": {
+
"tools": {
+
"listChanged": true
+
}
+
}
+
}
+
```
+
+
`listChanged` indicates whether the server will emit notifications when the list of
+
available tools changes.
+
+
## Protocol Messages
+
+
### Listing Tools
+
+
To discover available tools, clients send a `tools/list` request. This operation supports
+
[pagination]({{< ref "utilities/pagination" >}}).
+
+
**Request:**
+
+
```json
+
{
+
"jsonrpc": "2.0",
+
"id": 1,
+
"method": "tools/list",
+
"params": {
+
"cursor": "optional-cursor-value"
+
}
+
}
+
```
+
+
**Response:**
+
+
```json
+
{
+
"jsonrpc": "2.0",
+
"id": 1,
+
"result": {
+
"tools": [
+
{
+
"name": "get_weather",
+
"description": "Get current weather information for a location",
+
"inputSchema": {
+
"type": "object",
+
"properties": {
+
"location": {
+
"type": "string",
+
"description": "City name or zip code"
+
}
+
},
+
"required": ["location"]
+
}
+
}
+
],
+
"nextCursor": "next-page-cursor"
+
}
+
}
+
```
+
+
### Calling Tools
+
+
To invoke a tool, clients send a `tools/call` request:
+
+
**Request:**
+
+
```json
+
{
+
"jsonrpc": "2.0",
+
"id": 2,
+
"method": "tools/call",
+
"params": {
+
"name": "get_weather",
+
"arguments": {
+
"location": "New York"
+
}
+
}
+
}
+
```
+
+
**Response:**
+
+
```json
+
{
+
"jsonrpc": "2.0",
+
"id": 2,
+
"result": {
+
"content": [
+
{
+
"type": "text",
+
"text": "Current weather in New York:\nTemperature: 72ยฐF\nConditions: Partly cloudy"
+
}
+
],
+
"isError": false
+
}
+
}
+
```
+
+
### List Changed Notification
+
+
When the list of available tools changes, servers that declared the `listChanged`
+
capability **SHOULD** send a notification:
+
+
```json
+
{
+
"jsonrpc": "2.0",
+
"method": "notifications/tools/list_changed"
+
}
+
```
+
+
## Message Flow
+
+
```mermaid
+
sequenceDiagram
+
participant LLM
+
participant Client
+
participant Server
+
+
Note over Client,Server: Discovery
+
Client->>Server: tools/list
+
Server-->>Client: List of tools
+
+
Note over Client,LLM: Tool Selection
+
LLM->>Client: Select tool to use
+
+
Note over Client,Server: Invocation
+
Client->>Server: tools/call
+
Server-->>Client: Tool result
+
Client->>LLM: Process result
+
+
Note over Client,Server: Updates
+
Server--)Client: tools/list_changed
+
Client->>Server: tools/list
+
Server-->>Client: Updated tools
+
```
+
+
## Data Types
+
+
### Tool
+
+
A tool definition includes:
+
+
- `name`: Unique identifier for the tool
+
- `description`: Human-readable description of functionality
+
- `inputSchema`: JSON Schema defining expected parameters
+
- `annotations`: optional properties describing tool behavior
+
+
{{< callout type="warning" >}} For trust & safety and security, clients **MUST** consider
+
tool annotations to be untrusted unless they come from trusted servers. {{< /callout >}}
+
+
### Tool Result
+
+
Tool results can contain multiple content items of different types:
+
+
#### Text Content
+
+
```json
+
{
+
"type": "text",
+
"text": "Tool result text"
+
}
+
```
+
+
#### Image Content
+
+
```json
+
{
+
"type": "image",
+
"data": "base64-encoded-data",
+
"mimeType": "image/png"
+
}
+
```
+
+
#### Audio Content
+
+
```json
+
{
+
"type": "audio",
+
"data": "base64-encoded-audio-data",
+
"mimeType": "audio/wav"
+
}
+
```
+
+
#### Embedded Resources
+
+
[Resources]({{< ref "resources" >}}) **MAY** be embedded, to provide additional context
+
or data, behind a URI that can be subscribed to or fetched again by the client later:
+
+
```json
+
{
+
"type": "resource",
+
"resource": {
+
"uri": "resource://example",
+
"mimeType": "text/plain",
+
"text": "Resource content"
+
}
+
}
+
```
+
+
## Error Handling
+
+
Tools use two error reporting mechanisms:
+
+
1. **Protocol Errors**: Standard JSON-RPC errors for issues like:
+
+
- Unknown tools
+
- Invalid arguments
+
- Server errors
+
+
2. **Tool Execution Errors**: Reported in tool results with `isError: true`:
+
- API failures
+
- Invalid input data
+
- Business logic errors
+
+
Example protocol error:
+
+
```json
+
{
+
"jsonrpc": "2.0",
+
"id": 3,
+
"error": {
+
"code": -32602,
+
"message": "Unknown tool: invalid_tool_name"
+
}
+
}
+
```
+
+
Example tool execution error:
+
+
```json
+
{
+
"jsonrpc": "2.0",
+
"id": 4,
+
"result": {
+
"content": [
+
{
+
"type": "text",
+
"text": "Failed to fetch weather data: API rate limit exceeded"
+
}
+
],
+
"isError": true
+
}
+
}
+
```
+
+
## Security Considerations
+
+
1. Servers **MUST**:
+
+
- Validate all tool inputs
+
- Implement proper access controls
+
- Rate limit tool invocations
+
- Sanitize tool outputs
+
+
2. Clients **SHOULD**:
+
- Prompt for user confirmation on sensitive operations
+
- Show tool inputs to the user before calling the server, to avoid malicious or
+
accidental data exfiltration
+
- Validate tool results before passing to LLM
+
- Implement timeouts for tool calls
+
- Log tool usage for audit purposes
+278
spec/transports.md
···
+
---
+
title: Transports
+
type: docs
+
weight: 10
+
---
+
+
{{< callout type="info" >}} **Protocol Revision**: 2025-03-26 {{< /callout >}}
+
+
MCP uses JSON-RPC to encode messages. JSON-RPC messages **MUST** be UTF-8 encoded.
+
+
The protocol currently defines two standard transport mechanisms for client-server
+
communication:
+
+
1. [stdio](#stdio), communication over standard in and standard out
+
2. [Streamable HTTP](#streamable-http)
+
+
Clients **SHOULD** support stdio whenever possible.
+
+
It is also possible for clients and servers to implement
+
[custom transports](#custom-transports) in a pluggable fashion.
+
+
## stdio
+
+
In the **stdio** transport:
+
+
- The client launches the MCP server as a subprocess.
+
- The server reads JSON-RPC messages from its standard input (`stdin`) and sends messages
+
to its standard output (`stdout`).
+
- Messages may be JSON-RPC requests, notifications, responsesโ€”or a JSON-RPC
+
[batch](https://www.jsonrpc.org/specification#batch) containing one or more requests
+
and/or notifications.
+
- Messages are delimited by newlines, and **MUST NOT** contain embedded newlines.
+
- The server **MAY** write UTF-8 strings to its standard error (`stderr`) for logging
+
purposes. Clients **MAY** capture, forward, or ignore this logging.
+
- The server **MUST NOT** write anything to its `stdout` that is not a valid MCP message.
+
- The client **MUST NOT** write anything to the server's `stdin` that is not a valid MCP
+
message.
+
+
```mermaid
+
sequenceDiagram
+
participant Client
+
participant Server Process
+
+
Client->>+Server Process: Launch subprocess
+
loop Message Exchange
+
Client->>Server Process: Write to stdin
+
Server Process->>Client: Write to stdout
+
Server Process--)Client: Optional logs on stderr
+
end
+
Client->>Server Process: Close stdin, terminate subprocess
+
deactivate Server Process
+
```
+
+
## Streamable HTTP
+
+
{{< callout type="info" >}} This replaces the [HTTP+SSE
+
transport]({{< ref "/specification/2024-11-05/basic/transports#http-with-sse" >}}) from
+
protocol version 2024-11-05. See the [backwards compatibility](#backwards-compatibility)
+
guide below. {{< /callout >}}
+
+
In the **Streamable HTTP** transport, the server operates as an independent process that
+
can handle multiple client connections. This transport uses HTTP POST and GET requests.
+
Server can optionally make use of
+
[Server-Sent Events](https://en.wikipedia.org/wiki/Server-sent_events) (SSE) to stream
+
multiple server messages. This permits basic MCP servers, as well as more feature-rich
+
servers supporting streaming and server-to-client notifications and requests.
+
+
The server **MUST** provide a single HTTP endpoint path (hereafter referred to as the
+
**MCP endpoint**) that supports both POST and GET methods. For example, this could be a
+
URL like `https://example.com/mcp`.
+
+
### Sending Messages to the Server
+
+
Every JSON-RPC message sent from the client **MUST** be a new HTTP POST request to the
+
MCP endpoint.
+
+
1. The client **MUST** use HTTP POST to send JSON-RPC messages to the MCP endpoint.
+
2. The client **MUST** include an `Accept` header, listing both `application/json` and
+
`text/event-stream` as supported content types.
+
3. The body of the POST request **MUST** be one of the following:
+
- A single JSON-RPC _request_, _notification_, or _response_
+
- An array [batching](https://www.jsonrpc.org/specification#batch) one or more
+
_requests and/or notifications_
+
- An array [batching](https://www.jsonrpc.org/specification#batch) one or more
+
_responses_
+
4. If the input consists solely of (any number of) JSON-RPC _responses_ or
+
_notifications_:
+
- If the server accepts the input, the server **MUST** return HTTP status code 202
+
Accepted with no body.
+
- If the server cannot accept the input, it **MUST** return an HTTP error status code
+
(e.g., 400 Bad Request). The HTTP response body **MAY** comprise a JSON-RPC _error
+
response_ that has no `id`.
+
5. If the input contains any number of JSON-RPC _requests_, the server **MUST** either
+
return `Content-Type: text/event-stream`, to initiate an SSE stream, or
+
`Content-Type: application/json`, to return one JSON object. The client **MUST**
+
support both these cases.
+
6. If the server initiates an SSE stream:
+
- The SSE stream **SHOULD** eventually include one JSON-RPC _response_ per each
+
JSON-RPC _request_ sent in the POST body. These _responses_ **MAY** be
+
[batched](https://www.jsonrpc.org/specification#batch).
+
- The server **MAY** send JSON-RPC _requests_ and _notifications_ before sending a
+
JSON-RPC _response_. These messages **SHOULD** relate to the originating client
+
_request_. These _requests_ and _notifications_ **MAY** be
+
[batched](https://www.jsonrpc.org/specification#batch).
+
- The server **SHOULD NOT** close the SSE stream before sending a JSON-RPC _response_
+
per each received JSON-RPC _request_, unless the [session](#session-management)
+
expires.
+
- After all JSON-RPC _responses_ have been sent, the server **SHOULD** close the SSE
+
stream.
+
- Disconnection **MAY** occur at any time (e.g., due to network conditions).
+
Therefore:
+
- Disconnection **SHOULD NOT** be interpreted as the client cancelling its request.
+
- To cancel, the client **SHOULD** explicitly send an MCP `CancelledNotification`.
+
- To avoid message loss due to disconnection, the server **MAY** make the stream
+
[resumable](#resumability-and-redelivery).
+
+
### Listening for Messages from the Server
+
+
1. The client **MAY** issue an HTTP GET to the MCP endpoint. This can be used to open an
+
SSE stream, allowing the server to communicate to the client, without the client first
+
sending data via HTTP POST.
+
2. The client **MUST** include an `Accept` header, listing `text/event-stream` as a
+
supported content type.
+
3. The server **MUST** either return `Content-Type: text/event-stream` in response to
+
this HTTP GET, or else return HTTP 405 Method Not Allowed, indicating that the server
+
does not offer an SSE stream at this endpoint.
+
4. If the server initiates an SSE stream:
+
- The server **MAY** send JSON-RPC _requests_ and _notifications_ on the stream. These
+
_requests_ and _notifications_ **MAY** be
+
[batched](https://www.jsonrpc.org/specification#batch).
+
- These messages **SHOULD** be unrelated to any concurrently-running JSON-RPC
+
_request_ from the client.
+
- The server **MUST NOT** send a JSON-RPC _response_ on the stream **unless**
+
[resuming](#resumability-and-redelivery) a stream associated with a previous client
+
request.
+
- The server **MAY** close the SSE stream at any time.
+
- The client **MAY** close the SSE stream at any time.
+
+
### Multiple Connections
+
+
1. The client **MAY** remain connected to multiple SSE streams simultaneously.
+
2. The server **MUST** send each of its JSON-RPC messages on only one of the connected
+
streams; that is, it **MUST NOT** broadcast the same message across multiple streams.
+
- The risk of message loss **MAY** be mitigated by making the stream
+
[resumable](#resumability-and-redelivery).
+
+
### Resumability and Redelivery
+
+
To support resuming broken connections, and redelivering messages that might otherwise be
+
lost:
+
+
1. Servers **MAY** attach an `id` field to their SSE events, as described in the
+
[SSE standard](https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation).
+
- If present, the ID **MUST** be globally unique across all streams within that
+
[session](#session-management)โ€”or all streams with that specific client, if session
+
management is not in use.
+
2. If the client wishes to resume after a broken connection, it **SHOULD** issue an HTTP
+
GET to the MCP endpoint, and include the
+
[`Last-Event-ID`](https://html.spec.whatwg.org/multipage/server-sent-events.html#the-last-event-id-header)
+
header to indicate the last event ID it received.
+
- The server **MAY** use this header to replay messages that would have been sent
+
after the last event ID, _on the stream that was disconnected_, and to resume the
+
stream from that point.
+
- The server **MUST NOT** replay messages that would have been delivered on a
+
different stream.
+
+
In other words, these event IDs should be assigned by servers on a _per-stream_ basis, to
+
act as a cursor within that particular stream.
+
+
### Session Management
+
+
An MCP "session" consists of logically related interactions between a client and a
+
server, beginning with the [initialization phase]({{< ref "lifecycle" >}}). To support
+
servers which want to establish stateful sessions:
+
+
1. A server using the Streamable HTTP transport **MAY** assign a session ID at
+
initialization time, by including it in an `Mcp-Session-Id` header on the HTTP
+
response containing the `InitializeResult`.
+
- The session ID **SHOULD** be globally unique and cryptographically secure (e.g., a
+
securely generated UUID, a JWT, or a cryptographic hash).
+
- The session ID **MUST** only contain visible ASCII characters (ranging from 0x21 to
+
0x7E).
+
2. If an `Mcp-Session-Id` is returned by the server during initialization, clients using
+
the Streamable HTTP transport **MUST** include it in the `Mcp-Session-Id` header on
+
all of their subsequent HTTP requests.
+
- Servers that require a session ID **SHOULD** respond to requests without an
+
`Mcp-Session-Id` header (other than initialization) with HTTP 400 Bad Request.
+
3. The server **MAY** terminate the session at any time, after which it **MUST** respond
+
to requests containing that session ID with HTTP 404 Not Found.
+
4. When a client receives HTTP 404 in response to a request containing an
+
`Mcp-Session-Id`, it **MUST** start a new session by sending a new `InitializeRequest`
+
without a session ID attached.
+
5. Clients that no longer need a particular session (e.g., because the user is leaving
+
the client application) **SHOULD** send an HTTP DELETE to the MCP endpoint with the
+
`Mcp-Session-Id` header, to explicitly terminate the session.
+
- The server **MAY** respond to this request with HTTP 405 Method Not Allowed,
+
indicating that the server does not allow clients to terminate sessions.
+
+
### Sequence Diagram
+
+
```mermaid
+
sequenceDiagram
+
participant Client
+
participant Server
+
+
note over Client, Server: initialization
+
+
Client->>+Server: POST InitializeRequest
+
Server->>-Client: InitializeResponse<br>Mcp-Session-Id: 1868a90c...
+
+
Client->>+Server: POST InitializedNotification<br>Mcp-Session-Id: 1868a90c...
+
Server->>-Client: 202 Accepted
+
+
note over Client, Server: client requests
+
Client->>+Server: POST ... request ...<br>Mcp-Session-Id: 1868a90c...
+
+
alt single HTTP response
+
Server->>Client: ... response ...
+
else server opens SSE stream
+
loop while connection remains open
+
Server-)Client: ... SSE messages from server ...
+
end
+
Server-)Client: SSE event: ... response ...
+
end
+
deactivate Server
+
+
note over Client, Server: client notifications/responses
+
Client->>+Server: POST ... notification/response ...<br>Mcp-Session-Id: 1868a90c...
+
Server->>-Client: 202 Accepted
+
+
note over Client, Server: server requests
+
Client->>+Server: GET<br>Mcp-Session-Id: 1868a90c...
+
loop while connection remains open
+
Server-)Client: ... SSE messages from server ...
+
end
+
deactivate Server
+
+
```
+
+
### Backwards Compatibility
+
+
Clients and servers can maintain backwards compatibility with the deprecated [HTTP+SSE
+
transport]({{< ref "/specification/2024-11-05/basic/transports#http-with-sse" >}}) (from
+
protocol version 2024-11-05) as follows:
+
+
**Servers** wanting to support older clients should:
+
+
- Continue to host both the SSE and POST endpoints of the old transport, alongside the
+
new "MCP endpoint" defined for the Streamable HTTP transport.
+
- It is also possible to combine the old POST endpoint and the new MCP endpoint, but
+
this may introduce unneeded complexity.
+
+
**Clients** wanting to support older servers should:
+
+
1. Accept an MCP server URL from the user, which may point to either a server using the
+
old transport or the new transport.
+
2. Attempt to POST an `InitializeRequest` to the server URL, with an `Accept` header as
+
defined above:
+
- If it succeeds, the client can assume this is a server supporting the new Streamable
+
HTTP transport.
+
- If it fails with an HTTP 4xx status code (e.g., 405 Method Not Allowed or 404 Not
+
Found):
+
- Issue a GET request to the server URL, expecting that this will open an SSE stream
+
and return an `endpoint` event as the first event.
+
- When the `endpoint` event arrives, the client can assume this is a server running
+
the old HTTP+SSE transport, and should use that transport for all subsequent
+
communication.
+
+
## Custom Transports
+
+
Clients and servers **MAY** implement additional custom transport mechanisms to suit
+
their specific needs. The protocol is transport-agnostic and can be implemented over any
+
communication channel that supports bidirectional message exchange.
+
+
Implementers who choose to support custom transports **MUST** ensure they preserve the
+
JSON-RPC message format and lifecycle requirements defined by MCP. Custom transports
+
**SHOULD** document their specific connection establishment and message exchange patterns
+
to aid interoperability.