Model Context Protocol in OCaml

Compare changes

Choose any two refs to compare.

+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
+46 -12
bin/capitalize_sdk.ml
···
open Mcp
open Mcp_sdk
(* Helper for extracting string value from JSON *)
let get_string_param json name =
···
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 ()
(* Define and register a capitalize tool *)
let _ = add_tool server
···
}
with
| Failure msg ->
-
Log.errorf "Error in capitalize tool: %s" msg;
TextContent.yojson_of_t TextContent.{
text = Printf.sprintf "Error: %s" msg;
annotations = None
···
[
Prompt.{
role = `User;
-
content = Mcp.make_text_content "Please help me capitalize the following text:"
};
Prompt.{
role = `User;
-
content = Mcp.make_text_content text
};
Prompt.{
role = `Assistant;
-
content = Mcp.make_text_content "Here's the capitalized version:"
};
Prompt.{
role = `Assistant;
-
content = Mcp.make_text_content (String.uppercase_ascii text)
}
]
)
let () =
-
(* Run the server with the default scheduler *)
-
Eio_main.run @@ fun env->
-
Mcp_server.run_server env server
···
open Mcp
open Mcp_sdk
+
open Mcp_server
(* Helper for extracting string value from JSON *)
let get_string_param json name =
···
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
···
}
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
···
[
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 () =
+
(* 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
+35 -9
bin/dune
···
(executable
(name capitalize_sdk)
(modules capitalize_sdk)
-
(libraries mcp mcp_server yojson eio_main eio))
(executable
-
(name multimodal_sdk)
-
(modules multimodal_sdk)
-
(libraries mcp mcp_sdk mcp_server yojson eio_main eio))
(executable
-
(name ocaml_eval_sdk)
-
(modes byte)
-
(modules ocaml_eval_sdk)
-
(flags (:standard -w -32 -w -33))
-
(libraries mcp mcp_sdk mcp_server yojson eio_main eio compiler-libs.toplevel))
···
+
(executable
+
(name server)
+
(libraries mcp yojson unix)
+
(flags (:standard -w -8-11)))
+
(executable
(name capitalize_sdk)
(modules capitalize_sdk)
+
(libraries mcp mcp_sdk mcp_server yojson unix)
+
(flags (:standard -w -33)))
(executable
+
(name audio_example)
+
(modules audio_example)
+
(libraries mcp mcp_sdk mcp_server yojson unix)
+
(flags (:standard -w -33)))
(executable
+
(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 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
+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;
-370
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 ->
-
Log.errorf "Error in multimodal tool: %s" msg;
-
Tool.create_tool_result [Mcp.make_text_content (Printf.sprintf "Error: %s" msg)] ~is_error:true
-
)
-
-
(* Define and register a tool for generating only images *)
-
let _ = add_tool server
-
~name:"generate_image"
-
~description:"Generates a random image with specified dimensions"
-
~schema_properties:[
-
("width", "integer", "Width of the generated image (pixels)");
-
("height", "integer", "Height of the generated image (pixels)")
-
]
-
~schema_required:["width"; "height"]
-
(fun args ->
-
try
-
let width = get_int_param args "width" in
-
let height = get_int_param args "height" in
-
-
if width < 1 || width > 1024 || height < 1 || height > 1024 then
-
Tool.create_tool_result
-
[Mcp.make_text_content "Error: Dimensions must be between 1 and 1024 pixels"]
-
~is_error:true
-
else
-
let image_data = generate_random_image width height in
-
Tool.create_tool_result
-
[Mcp.make_image_content image_data "image/gif"]
-
~is_error:false
-
with
-
| Failure msg ->
-
Log.errorf "Error in generate_image tool: %s" msg;
-
Tool.create_tool_result [Mcp.make_text_content (Printf.sprintf "Error: %s" msg)] ~is_error:true
-
)
-
-
(* Define and register a tool for generating only audio *)
-
let _ = add_tool server
-
~name:"generate_audio"
-
~description:"Generates an audio tone with specified frequency and duration"
-
~schema_properties:[
-
("frequency", "integer", "Frequency of the tone in Hz (20-20000)");
-
("duration", "integer", "Duration of the tone in seconds (1-10)")
-
]
-
~schema_required:["frequency"; "duration"]
-
(fun args ->
-
try
-
let frequency = get_int_param args "frequency" in
-
let duration = get_int_param args "duration" in
-
-
if frequency < 20 || frequency > 20000 then
-
Tool.create_tool_result
-
[Mcp.make_text_content "Error: Frequency must be between 20Hz and 20,000Hz"]
-
~is_error:true
-
else if duration < 1 || duration > 10 then
-
Tool.create_tool_result
-
[Mcp.make_text_content "Error: Duration must be between 1 and 10 seconds"]
-
~is_error:true
-
else
-
let audio_data = generate_sine_wave_audio (float_of_int frequency) duration in
-
Tool.create_tool_result
-
[Mcp.make_audio_content audio_data "audio/wav"]
-
~is_error:false
-
with
-
| Failure msg ->
-
Log.errorf "Error in generate_audio tool: %s" msg;
-
Tool.create_tool_result [Mcp.make_text_content (Printf.sprintf "Error: %s" msg)] ~is_error:true
-
)
-
-
(* Define and register a resource example with multimodal content *)
-
let _ = add_resource server
-
~uri_template:"multimodal://{name}"
-
~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 () =
-
Random.self_init(); (* Initialize random generator *)
-
Eio_main.run @@ fun env ->
-
Mcp_server.run_server env server
···
-166
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 begin
-
let _ = initialize_toploop () in
-
toplevel_initialized := true;
-
end
-
-
(* 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 ->
-
Log.errorf "Error in OCaml eval tool: %s" msg;
-
Tool.create_tool_result [Mcp.make_text_content (Printf.sprintf "Error: %s" msg)] ~is_error:true
-
)
-
-
(* Run the server with the default scheduler *)
-
let () =
-
Eio_main.run @@ fun env->
-
Mcp_server.run_server env server
···
+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
-18
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
-
eio_main
-
eio))
-
···
(lang dune 3.17)
+3 -13
lib/dune
···
(library
(name mcp)
-
(public_name mcp)
(libraries jsonrpc unix yojson)
(modules mcp))
-
-
(library
-
(name mcp_rpc)
-
(public_name mcp.rpc)
-
(libraries mcp jsonrpc unix yojson)
-
(modules mcp_rpc)
-
(flags (:standard -w -67 -w -27 -w -32 -w -33 -w -34)))
(library
(name mcp_sdk)
-
(public_name mcp.sdk)
-
(libraries mcp mcp_rpc jsonrpc unix yojson)
(modules mcp_sdk)
(flags (:standard -w -67 -w -27 -w -32)))
(library
(name mcp_server)
-
(public_name mcp.server)
-
(libraries mcp_sdk jsonrpc eio_main eio)
(modules mcp_server)
-
)
···
(library
(name mcp)
(libraries jsonrpc unix yojson)
(modules mcp))
(library
(name mcp_sdk)
+
(libraries mcp jsonrpc unix yojson)
(modules mcp_sdk)
(flags (:standard -w -67 -w -27 -w -32)))
(library
(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)))
+601 -316
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 *)
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
-
| ResourcesTemplatesList
-
| 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"
-
| ResourcesTemplatesList -> "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" -> ResourcesTemplatesList
-
| "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)
end
(* Common types *)
···
let of_string = function
| "user" -> `User
| "assistant" -> `Assistant
-
| s -> Util.json_error "Unknown role: %s" (`String s) s
let yojson_of_t t = `String (to_string t)
let t_of_yojson = function
| `String s -> of_string s
-
| j -> Util.json_error "Expected string for Role" j
end
module ProgressToken = struct
···
let yojson_of_t t = `String t
let t_of_yojson = function
| `String s -> s
-
| j -> Util.json_error "Expected string for Cursor" j
end
(* Annotations *)
···
| `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 yojson_of_t { annotations } =
match annotations with
···
| `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
end
(* Content types *)
···
`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
end
module ImageContent = struct
···
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
···
`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
end
module AudioContent = struct
···
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
···
`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
end
module ResourceContents = struct
···
`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
end
module TextResourceContents = struct
···
`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
end
module BlobResourceContents = struct
···
`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
end
module EmbeddedResource = struct
···
`Assoc assoc
let t_of_yojson = function
-
| `Assoc fields as json ->
-
Util.verify_string_field fields "type" "resource" json;
-
let resource_fields = match List.assoc_opt "resource" fields with
-
| Some (`Assoc res_fields) -> res_fields
-
| _ -> Util.json_error "Missing or invalid 'resource' field" json
in
-
let resource =
-
if List.mem_assoc "text" resource_fields then
-
`Text (TextResourceContents.t_of_yojson (`Assoc resource_fields))
-
else if List.mem_assoc "blob" resource_fields then
-
`Blob (BlobResourceContents.t_of_yojson (`Assoc resource_fields))
-
else
-
Util.json_error "Invalid resource content" (`Assoc resource_fields)
in
let annotations = List.assoc_opt "annotations" fields |> Option.map Annotated.annotation_of_yojson in
{ resource; annotations }
-
| j -> Util.json_error "Expected object for EmbeddedResource" j
end
type content =
···
| 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
(* Message types *)
···
]
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
end
module SamplingMessage = struct
type t = {
role: Role.t;
-
content: [ `Text of TextContent.t | `Image of ImageContent.t | `Audio of AudioContent.t ];
}
let yojson_of_t { role; content } =
let content_json = match content with
| `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);
···
]
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
end
(* Implementation info *)
···
]
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
end
(* JSONRPC Message types *)
module JSONRPCMessage = struct
type notification = {
-
meth: Method.t;
params: Json.t option;
}
type request = {
id: RequestId.t;
-
meth: Method.t;
params: Json.t option;
progress_token: ProgressToken.t option;
}
···
let yojson_of_notification (n: notification) =
let assoc = [
("jsonrpc", `String "2.0");
-
("method", `String (Method.to_string n.meth));
] in
let assoc = match n.params with
| Some params -> ("params", params) :: assoc
···
let assoc = [
("jsonrpc", `String "2.0");
("id", Id.yojson_of_t r.id);
-
("method", `String (Method.to_string r.meth));
] in
let assoc = match r.params with
| Some params ->
···
let 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 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 =
···
| _ -> None)
| _ -> None
in
-
{ id; meth; params; progress_token }
-
| j -> Util.json_error "Expected object for request" j
let response_of_yojson = function
| `Assoc fields ->
let id = match List.assoc_opt "id" fields with
| Some id_json -> Id.t_of_yojson id_json
-
| _ -> Util.json_error "Missing or invalid 'id' field" (`Assoc fields)
in
let result = match List.assoc_opt "result" fields with
| Some result -> result
-
| _ -> Util.json_error "Missing 'result' field" (`Assoc fields)
in
{ id; result }
-
| j -> Util.json_error "Expected object for response" j
let error_of_yojson = function
-
| `Assoc fields as json ->
let id = match List.assoc_opt "id" fields with
| Some id_json -> Id.t_of_yojson id_json
-
| _ -> Util.json_error "Missing or invalid 'id' field" json
in
let error = match List.assoc_opt "error" fields with
| Some (`Assoc error_fields) -> error_fields
-
| _ -> Util.json_error "Missing or invalid 'error' field" json
in
let code = match List.assoc_opt "code" error with
| Some (`Int code) -> code
-
| _ -> Util.json_error "Missing or invalid 'code' field in error" (`Assoc error)
in
let message = match List.assoc_opt "message" error with
| Some (`String msg) -> msg
-
| _ -> Util.json_error "Missing or invalid 'message' field in error" (`Assoc error)
in
let data = List.assoc_opt "data" error in
{ id; code; message; data }
-
| j -> Util.json_error "Expected object for error" j
let t_of_yojson json =
match json with
| `Assoc fields ->
let _jsonrpc = match List.assoc_opt "jsonrpc" fields with
| Some (`String "2.0") -> ()
-
| _ -> Util.json_error "Missing or invalid 'jsonrpc' field" json
in
if List.mem_assoc "method" fields then
if List.mem_assoc "id" fields then
···
else if List.mem_assoc "error" fields then
Error (error_of_yojson json)
else
-
Util.json_error "Invalid JSONRPC message format" json
-
| j -> Util.json_error "Expected object for JSONRPC message" j
-
let create_notification ?(params=None) ~meth () =
-
Notification { meth; params }
-
let create_request ?(params=None) ?(progress_token=None) ~id ~meth () =
-
Request { id; meth; params; progress_token }
let create_response ~id ~result =
Response { id; result }
···
]
let 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
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) ()
end
module Result = struct
···
`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
let create ~capabilities ~server_info ~protocol_version ?instructions ?meta () =
{ capabilities; server_info; protocol_version; instructions; meta }
···
| `Assoc fields ->
let meta = List.assoc_opt "_meta" fields in
{ meta }
-
| j -> Util.json_error "Expected object for InitializedNotification" j
let create ?meta () = { meta }
···
| `Assoc [] -> None
| json -> Some json
in
-
JSONRPCMessage.create_notification ~meth:Method.Initialized ~params ()
end
end
-
(* Export the main interface for using the MCP protocol *)
let parse_message json =
JSONRPCMessage.t_of_yojson json
-
let 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 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;
-
})
···
open Jsonrpc
+
(* Standard error codes *)
module ErrorCode = struct
+
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 *)
···
let of_string = function
| "user" -> `User
| "assistant" -> `Assistant
+
| 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 -> raise (Json.Of_json ("Expected string for Role", j))
end
module ProgressToken = struct
···
let yojson_of_t t = `String t
let t_of_yojson = function
| `String s -> s
+
| j -> raise (Json.Of_json ("Expected string for Cursor", j))
end
(* Annotations *)
···
| `Assoc fields ->
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
···
| `Assoc fields ->
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 *)
···
`Assoc assoc
let t_of_yojson = function
+
| `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
···
let yojson_of_t { data; mime_type; annotations } =
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
···
`Assoc assoc
let t_of_yojson = function
+
| `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
···
let yojson_of_t { data; mime_type; annotations } =
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
···
`Assoc assoc
let t_of_yojson = function
+
| `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
···
`Assoc assoc
let t_of_yojson = function
+
| `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
···
`Assoc assoc
let t_of_yojson = function
+
| `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
···
`Assoc assoc
let t_of_yojson = function
+
| `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
···
`Assoc assoc
let t_of_yojson = function
+
| `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
+
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 =
···
| Resource r -> EmbeddedResource.yojson_of_t r
let content_of_yojson = function
+
| `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 *)
···
]
let t_of_yojson = function
+
| `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 ];
}
let yojson_of_t { role; content } =
let content_json = match content with
| `Text t -> TextContent.yojson_of_t t
| `Image i -> ImageContent.yojson_of_t i
in
`Assoc [
("role", Role.yojson_of_t role);
···
]
let t_of_yojson = function
+
| `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 *)
···
]
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 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 = {
+
method_: string;
params: Json.t option;
}
type request = {
id: RequestId.t;
+
method_: string;
params: Json.t option;
progress_token: ProgressToken.t option;
}
···
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
···
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 notification_of_yojson = function
| `Assoc fields ->
+
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
+
| _ -> 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 =
···
| _ -> 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
+
| _ -> 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 ->
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") -> ()
+
| _ -> 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
···
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) ~method_ () =
+
Notification { method_; params }
+
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 t_of_yojson = function
+
| `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 ~method_:"initialize" ~params:(Some params) ()
end
module Result = struct
···
`Assoc assoc
let t_of_yojson = function
+
| `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 () =
{ capabilities; server_info; protocol_version; instructions; meta }
···
| `Assoc fields ->
let meta = List.assoc_opt "_meta" fields in
{ meta }
+
| j -> raise (Json.Of_json ("Expected object for InitializedNotification", j))
let create ?meta () = { meta }
···
| `Assoc [] -> None
| json -> Some json
in
+
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 = JSONRPCMessage.create_notification
+
let create_request = JSONRPCMessage.create_request
let create_response = JSONRPCMessage.create_response
let create_error = JSONRPCMessage.create_error
+
(* 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
+219 -887
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.
-
*)
open Jsonrpc
-
(** Utility functions for JSON parsing *)
-
module Util : sig
-
(** Helper to raise a Json.Of_json exception with formatted message
-
@param fmt Format string for the error message
-
@param json JSON value to include in the exception
-
@return Never returns, always raises an exception
-
@raise Json.Of_json with the formatted message and JSON value
-
*)
-
val json_error : ('a, unit, string, 'b) format4 -> Json.t -> 'a
-
-
(** Extract a string field from JSON object or raise an error
-
@param fields Assoc list of fields from JSON object
-
@param name Field name to extract
-
@param json Original JSON for error context
-
@return The string value of the field
-
@raise Json.Of_json if the field is missing or not a string
-
*)
-
val get_string_field : (string * Json.t) list -> string -> Json.t -> string
-
-
(** Extract an optional string field from JSON object
-
@param fields Assoc list of fields from JSON object
-
@param name Field name to extract
-
@return Some string if present and a string, None if missing
-
@raise Json.Of_json if the field exists but is not a string
-
*)
-
val get_optional_string_field : (string * Json.t) list -> string -> string option
-
-
(** Extract an int field from JSON object or raise an error
-
@param fields Assoc list of fields from JSON object
-
@param name Field name to extract
-
@param json Original JSON for error context
-
@return The int value of the field
-
@raise Json.Of_json if the field is missing or not an int
-
*)
-
val get_int_field : (string * Json.t) list -> string -> Json.t -> int
-
-
(** Extract a float field from JSON object or raise an error
-
@param fields Assoc list of fields from JSON object
-
@param name Field name to extract
-
@param json Original JSON for error context
-
@return The float value of the field
-
@raise Json.Of_json if the field is missing or not a float
-
*)
-
val get_float_field : (string * Json.t) list -> string -> Json.t -> float
-
-
(** Extract a boolean field from JSON object or raise an error
-
@param fields Assoc list of fields from JSON object
-
@param name Field name to extract
-
@param json Original JSON for error context
-
@return The boolean value of the field
-
@raise Json.Of_json if the field is missing or not a boolean
-
*)
-
val get_bool_field : (string * Json.t) list -> string -> Json.t -> bool
-
-
(** Extract an object field from JSON object or raise an error
-
@param fields Assoc list of fields from JSON object
-
@param name Field name to extract
-
@param json Original JSON for error context
-
@return The object as an assoc list
-
@raise Json.Of_json if the field is missing or not an object
-
*)
-
val get_object_field : (string * Json.t) list -> string -> Json.t -> (string * Json.t) list
-
-
(** Extract a list field from JSON object or raise an error
-
@param fields Assoc list of fields from JSON object
-
@param name Field name to extract
-
@param json Original JSON for error context
-
@return The list items
-
@raise Json.Of_json if the field is missing or not a list
-
*)
-
val get_list_field : (string * Json.t) list -> string -> Json.t -> Json.t list
-
-
(** 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
-
*)
-
val verify_string_field : (string * Json.t) list -> string -> string -> Json.t -> unit
-
end
-
-
(** Error codes for JSON-RPC *)
module ErrorCode : sig
-
(** Standard JSON-RPC error codes with MCP-specific additions *)
-
type t =
-
| ParseError (** -32700 - Invalid JSON *)
-
| InvalidRequest (** -32600 - Invalid JSON-RPC request *)
-
| MethodNotFound (** -32601 - Method not available *)
-
| InvalidParams (** -32602 - Invalid method parameters *)
-
| InternalError (** -32603 - Internal JSON-RPC error *)
-
| ResourceNotFound (** -32002 - Custom MCP error: requested resource not found *)
-
| AuthRequired (** -32001 - Custom MCP error: authentication required *)
-
| CustomError of int (** For any other error codes *)
-
-
(** Convert the error code to its integer representation
-
@param code The error code to convert
-
@return The integer error code as defined in the JSON-RPC spec
-
*)
-
val to_int : t -> int
-
-
(** Get error message for standard error codes
-
@param code The error code to get message for
-
@return A standard message for the error code
-
*)
-
val to_message : t -> string
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 *)
-
| ResourcesTemplatesList (** 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 *)
-
-
(** Convert method type to string representation
-
@param meth The method to convert
-
@return The string representation of the method (e.g., "initialize", "resources/list")
-
*)
-
val to_string : t -> string
-
-
(** Convert string to method type
-
@param s The string representation of the method
-
@return The corresponding method type
-
@raise Failure if the string is not a valid MCP method
-
*)
-
val of_string : string -> t
-
end
-
-
(** Common types *)
(** Roles for conversation participants *)
module Role : sig
-
(** Role represents conversation participants in MCP messages.
-
Roles can be either 'user' or 'assistant', determining the
-
source of each message in a conversation. *)
type t = [ `User | `Assistant ]
-
include Json.Jsonable.S with type t := t
end
(** Progress tokens for long-running operations *)
module ProgressToken : sig
-
(** Progress tokens identify long-running operations and enable
-
servers to provide progress updates to clients. This is used
-
to track operations that may take significant time to complete. *)
type t = [ `String of string | `Int of int ]
include Json.Jsonable.S with type t := t
end
(** Request IDs *)
module RequestId : sig
-
(** Request IDs uniquely identify JSON-RPC requests, allowing responses
-
to be correlated with their originating requests. They can be either
-
string or integer values. *)
type t = [ `String of string | `Int of int ]
include Json.Jsonable.S with type t := t
end
(** Cursors for pagination *)
module Cursor : sig
-
(** Cursors enable pagination in list operations for resources, tools, and prompts.
-
When a server has more items than can be returned in a single response,
-
it provides a cursor for the client to retrieve subsequent pages. *)
type t = string
-
include Json.Jsonable.S with type t := t
end
(** Annotations for objects *)
module Annotated : sig
-
(** Annotations provide metadata for content objects, allowing
-
role-specific targeting and priority settings. *)
type t = {
annotations: annotation option;
}
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 *)
}
-
include Json.Jsonable.S with type t := t
end
-
(** Text content - Core textual message representation in MCP *)
module TextContent : sig
-
(** TextContent represents plain text messages in MCP conversations.
-
This is the most common content type used for natural language interactions
-
between users and assistants. Text content is used in prompts, tool results,
-
and model responses.
-
-
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. *)
type t = {
text: string;
-
(** The actual text content as a UTF-8 encoded string *)
annotations: Annotated.annotation option;
-
(** Optional annotations for audience targeting and priority.
-
Annotations can restrict content visibility to specific roles (user/assistant)
-
and indicate relative importance of different content elements. *)
}
-
include Json.Jsonable.S with type t := t
end
-
(** Image content - Visual data representation in MCP *)
module ImageContent : sig
-
(** 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. *)
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. *)
}
-
include Json.Jsonable.S with type t := t
end
-
(** Audio content - Sound data representation in MCP *)
module AudioContent : sig
-
(** 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. *)
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. *)
}
-
include Json.Jsonable.S with type t := t
end
-
(** Base resource contents - Core resource metadata in MCP *)
module ResourceContents : sig
-
(** ResourceContents provides basic metadata for resources in MCP.
-
-
Resources are server-exposed data that provides context to language models,
-
such as files, database schemas, or application-specific information.
-
Each resource is uniquely identified by a URI.
-
-
The MCP resources architecture is designed to be application-driven, with
-
host applications determining how to incorporate context based on their needs.
-
-
In the protocol, resources are discovered via the 'resources/list' endpoint
-
and retrieved via the 'resources/read' endpoint. Servers that support resources
-
must declare the 'resources' capability during initialization. *)
type t = {
uri: string;
-
(** URI that uniquely identifies the resource.
-
-
Resources use standard URI schemes including:
-
- file:// - For filesystem-like resources
-
- https:// - For web-accessible resources
-
- git:// - For version control integration
-
-
The URI serves as a stable identifier even if the underlying content changes. *)
mime_type: string option;
-
(** Optional MIME type of the resource content to aid in client rendering.
-
Common MIME types include text/plain, application/json, image/png, etc.
-
For directories, the XDG MIME type inode/directory may be used. *)
}
-
include Json.Jsonable.S with type t := t
end
-
(** Text resource contents - Textual resource data *)
module TextResourceContents : sig
-
(** 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}
-
*)
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. *)
}
-
include Json.Jsonable.S with type t := t
end
-
(** Binary resource contents - Binary resource data *)
module BlobResourceContents : sig
-
(** 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. *)
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. *)
}
-
include Json.Jsonable.S with type t := t
end
-
(** Embedded resource - Resource included directly in messages *)
module EmbeddedResource : sig
-
(** EmbeddedResource allows referencing server-side resources directly
-
in MCP messages, enabling seamless incorporation of managed content.
-
-
Embedded resources can be included in:
-
- Tool results to provide rich context
-
- Prompt templates to include reference materials
-
- Messages to provide additional context to language models
-
-
In contrast to direct content (TextContent, ImageContent, AudioContent),
-
embedded resources have the advantage of being persistently stored on the server
-
with a stable URI, allowing later retrieval and updates through the resources API.
-
-
For example, a tool might return an embedded resource containing a chart or
-
a large dataset that the client can later reference or update. *)
type t = {
resource: [ `Text of TextResourceContents.t | `Blob of BlobResourceContents.t ];
-
(** The resource content, either as text or binary blob. *)
annotations: Annotated.annotation option;
-
(** Optional annotations for audience targeting and priority.
-
Annotations can restrict resource visibility to specific roles (user/assistant)
-
and indicate relative importance of different content elements. *)
}
-
include Json.Jsonable.S with type t := t
end
-
(** Content type used in messages - Unified multimodal content representation in MCP *)
type content =
-
| Text of TextContent.t (** Text content for natural language messages. This is the most common content type for user-assistant interactions. *)
-
| Image of ImageContent.t (** Image content for visual data. Used for sharing visual context in multimodal conversations. *)
-
| Audio of AudioContent.t (** Audio content for audio data. Used for sharing audio context in multimodal conversations. *)
-
| Resource of EmbeddedResource.t (** Resource content for referencing server-side resources. Used for incorporating managed server content with stable URIs. *)
-
(** Convert content to Yojson representation
-
@param content The content to convert
-
@return JSON representation of the content
-
*)
val yojson_of_content : content -> Json.t
-
-
(** Convert Yojson representation to content
-
@param json JSON representation of content
-
@return Parsed content object
-
*)
val content_of_yojson : Json.t -> content
-
(** Message for prompts - Template messages in the MCP prompts feature *)
module PromptMessage : sig
-
(** PromptMessage represents a message in an MCP prompt template,
-
containing a role and content which can be customized with arguments.
-
-
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. *)
type t = {
role: Role.t;
-
(** The role of the message sender (user or assistant).
-
Prompt templates typically alternate between user and assistant messages
-
to create a conversation structure. *)
content: content;
-
(** The message content, which can be text, image, audio, or resource.
-
This unified content type supports rich multimodal prompts. *)
}
-
include Json.Jsonable.S with type t := t
end
-
(** Message for sampling - Messages used in LLM completion requests *)
module SamplingMessage : sig
-
(** SamplingMessage represents a message in an MCP sampling request,
-
used for AI model generation based on a prompt.
-
-
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. *)
type t = {
role: Role.t;
-
(** The role of the message sender (user or assistant).
-
Typically, a sampling request will contain multiple messages
-
representing a conversation history, with alternating roles. *)
-
content: [ `Text of TextContent.t | `Image of ImageContent.t | `Audio of AudioContent.t ];
-
(** The message content, restricted to text, image, or audio (no resources).
-
Resources are not included since sampling messages represent the
-
actual context window for the LLM, not template definitions. *)
}
-
include Json.Jsonable.S with type t := t
end
(** Implementation information *)
module Implementation : sig
-
(** Implementation provides metadata about client and server implementations,
-
used during the initialization phase to identify each party. *)
type t = {
name: string;
-
(** Name of the implementation *)
version: string;
-
(** Version of the implementation *)
}
-
include Json.Jsonable.S with type t := t
end
-
(** JSONRPC message types - Core message protocol for MCP
-
-
MCP uses JSON-RPC 2.0 as its underlying messaging protocol.
-
All MCP messages are encoded as JSON-RPC 2.0 messages with UTF-8 encoding,
-
following the standard JSON-RPC message formats with some MCP-specific extensions.
-
-
MCP defines four message types:
-
1. Notifications: One-way messages that don't expect a response
-
2. Requests: Messages that expect a corresponding response
-
3. Responses: Replies to requests with successful results
-
4. Errors: Replies to requests with error information
-
-
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.
-
*)
module JSONRPCMessage : sig
-
(** 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 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. *)
}
-
(** 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 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. *)
}
-
(** 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 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. *)
}
-
(** 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}
-
*)
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. *)
}
-
(** Union type for all JSON-RPC message kinds, providing a single type
-
that can represent any MCP message. *)
type t =
| Notification of notification
| Request of request
| Response of response
| Error of error
-
(** Convert notification to Yojson representation
-
@param notification The notification to convert
-
@return JSON representation of the notification
-
*)
val yojson_of_notification : notification -> Json.t
-
-
(** Convert request to Yojson representation
-
@param request The request to convert
-
@return JSON representation of the request
-
*)
val yojson_of_request : request -> Json.t
-
-
(** Convert response to Yojson representation
-
@param response The response to convert
-
@return JSON representation of the response
-
*)
val yojson_of_response : response -> Json.t
-
-
(** Convert error to Yojson representation
-
@param error The error to convert
-
@return JSON representation of the error
-
*)
val yojson_of_error : error -> Json.t
-
-
(** Convert any message to Yojson representation
-
@param message The message to convert
-
@return JSON representation of the message
-
*)
val yojson_of_t : t -> Json.t
-
(** Convert Yojson representation to notification
-
@param json JSON representation of a notification
-
@return Parsed notification object
-
@raise Parse error if the JSON is not a valid notification
-
*)
val notification_of_yojson : Json.t -> notification
-
-
(** Convert Yojson representation to request
-
@param json JSON representation of a request
-
@return Parsed request object
-
@raise Parse error if the JSON is not a valid request
-
*)
val request_of_yojson : Json.t -> request
-
-
(** Convert Yojson representation to response
-
@param json JSON representation of a response
-
@return Parsed response object
-
@raise Parse error if the JSON is not a valid response
-
*)
val response_of_yojson : Json.t -> response
-
-
(** Convert Yojson representation to error
-
@param json JSON representation of an error
-
@return Parsed error object
-
@raise Parse error if the JSON is not a valid error
-
*)
val error_of_yojson : Json.t -> error
-
-
(** Convert Yojson representation to any message
-
@param json JSON representation of any message type
-
@return Parsed message object
-
@raise Parse error if the JSON is not a valid message
-
*)
val t_of_yojson : Json.t -> t
-
(** Create a new notification message
-
@param params Optional parameters for the notification
-
@param meth Method name for the notification
-
@return A new JSON-RPC notification message
-
*)
-
val create_notification : ?params:Json.t option -> meth:Method.t -> unit -> t
-
-
(** Create a new request message
-
@param params Optional parameters for the request
-
@param progress_token Optional progress token for long-running operations
-
@param id Unique identifier for the request
-
@param meth Method name for the request
-
@return A new JSON-RPC request message
-
*)
-
val create_request : ?params:Json.t option -> ?progress_token:ProgressToken.t option -> id:RequestId.t -> meth:Method.t -> unit -> t
-
-
(** Create a new response message
-
@param id ID matching the original request
-
@param result Result of the successful request
-
@return A new JSON-RPC response message
-
*)
val create_response : id:RequestId.t -> result:Json.t -> t
-
-
(** 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.
-
*)
module Initialize : sig
(** Initialize request *)
module Request : sig
-
(** InitializeRequest starts the MCP lifecycle, negotiating capabilities
-
and protocol versions between client and server. This is always the first
-
message sent by the client and MUST NOT be part of a JSON-RPC batch.
-
-
The client SHOULD send the latest protocol version it supports. If the server
-
does not support this version, it will respond with a version it does support,
-
and the client must either use that version or disconnect. *)
type t = {
-
capabilities: Json.t; (** ClientCapabilities that define supported optional features.
-
This includes which optional protocol features the client supports,
-
such as 'roots' (filesystem access), 'sampling' (LLM generation),
-
and any experimental features. *)
client_info: Implementation.t;
-
(** Client implementation details (name and version) used for identification
-
and debugging. Helps servers understand which client they're working with. *)
protocol_version: string;
-
(** MCP protocol version supported by the client, formatted as YYYY-MM-DD
-
according to the MCP versioning scheme. Example: "2025-03-26" *)
}
-
include Json.Jsonable.S with type t := 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 create : capabilities:Json.t -> client_info:Implementation.t -> protocol_version:string -> t
-
-
(** Convert to JSON-RPC message
-
@param id Unique request identifier
-
@param t Initialization request
-
@return JSON-RPC message containing the initialization request
-
*)
val to_jsonrpc : id:RequestId.t -> t -> JSONRPCMessage.t
end
(** Initialize result *)
module Result : sig
-
(** InitializeResult is the server's response to an initialization request,
-
completing capability negotiation and establishing the protocol version.
-
-
After receiving this message, the client must send an InitializedNotification.
-
The server should not send any requests other than pings and logging before
-
receiving the initialized notification. *)
type t = {
-
capabilities: Json.t; (** ServerCapabilities that define supported optional features.
-
This declares which server features are available, including:
-
- prompts: Server provides prompt templates
-
- resources: Server provides readable resources
-
- tools: Server exposes callable tools
-
- logging: Server emits structured log messages
-
-
Each capability may have sub-capabilities like:
-
- listChanged: Server will notify when available items change
-
- subscribe: Clients can subscribe to individual resources *)
server_info: Implementation.t;
-
(** Server implementation details (name and version) used for identification
-
and debugging. Helps clients understand which server they're working with. *)
protocol_version: string;
-
(** MCP protocol version supported by the server, formatted as YYYY-MM-DD.
-
If the server supports the client's requested version, it responds with
-
the same version. Otherwise, it responds with a version it does support. *)
instructions: string option;
-
(** Optional instructions for using the server. These can provide human-readable
-
guidance on how to interact with this specific server implementation. *)
meta: Json.t option;
-
(** Optional additional metadata as arbitrary JSON. Can contain server-specific
-
information not covered by the standard fields. *)
}
-
include Json.Jsonable.S with type t := 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 create : capabilities:Json.t -> server_info:Implementation.t -> protocol_version:string -> ?instructions:string -> ?meta:Json.t -> unit -> t
-
-
(** Convert to JSON-RPC message
-
@param id ID matching the original request
-
@param t Initialization result
-
@return JSON-RPC message containing the initialization result
-
*)
val to_jsonrpc : id:RequestId.t -> t -> JSONRPCMessage.t
end
end
-
(** Initialized notification - Completes the initialization phase of the MCP lifecycle *)
module Initialized : sig
module Notification : sig
-
(** InitializedNotification is sent by the client after receiving the initialization
-
response, indicating it's ready to begin normal operations. This completes the
-
three-step initialization process, after which both client and server can
-
freely exchange messages according to the negotiated capabilities.
-
-
Only after this notification has been sent should the client begin normal operations
-
like listing resources, calling tools, or requesting prompts. *)
type t = {
meta: Json.t option;
-
(** Optional additional metadata as arbitrary JSON. Can contain client-specific
-
information not covered by the standard fields. *)
}
-
include Json.Jsonable.S with type t := t
-
(** Create a new initialized notification
-
@param meta Optional additional metadata
-
@return A new initialized notification
-
*)
val create : ?meta:Json.t -> unit -> t
-
-
(** Convert to JSON-RPC message
-
@param t Initialized notification
-
@return JSON-RPC message containing the initialized notification
-
*)
val to_jsonrpc : t -> JSONRPCMessage.t
end
end
-
(** Parse a JSON message into an MCP message
-
-
This function takes a raw JSON value and parses it into a structured MCP message.
-
It's the primary entry point for processing incoming JSON-RPC messages in the MCP protocol.
-
-
The function determines the message type (notification, request, response, or error)
-
based on the presence and values of specific fields:
-
- A message with "method" but no "id" is a notification
-
- A message with "method" and "id" is a request
-
- A message with "id" and "result" is a response
-
- A message with "id" and "error" is an error
-
-
@param json The JSON message to parse, typically received from the transport layer
-
@return The parsed MCP message as a structured JSONRPCMessage.t value
-
@raise Parse error if the JSON cannot be parsed as a valid MCP message
-
*)
-
val parse_message : Json.t -> JSONRPCMessage.t
-
(** Create a new notification message
-
-
Notifications are one-way messages that don't expect a response.
-
This is a convenience wrapper around JSONRPCMessage.create_notification.
-
-
Common notifications in MCP include:
-
- "notifications/initialized" - Sent after initialization
-
- "notifications/progress" - Updates on long-running operations
-
- "notifications/resources/updated" - Resource content changed
-
- "notifications/prompts/list_changed" - Available prompts changed
-
- "notifications/tools/list_changed" - Available tools changed
-
-
@param params Optional parameters for the notification as a JSON value
-
@param meth Method type for the notification
-
@return A new JSON-RPC notification message
-
*)
-
val create_notification : ?params:Json.t option -> meth:Method.t -> unit -> JSONRPCMessage.t
-
(** Create a new request message
-
-
Requests are messages that expect a corresponding response.
-
This is a convenience wrapper around JSONRPCMessage.create_request.
-
-
Common requests in MCP include:
-
- "initialize" - Start the MCP lifecycle
-
- "resources/list" - Discover available resources
-
- "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
-
-
@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 create_request : ?params:Json.t option -> ?progress_token:ProgressToken.t option -> id:RequestId.t -> meth:Method.t -> unit -> JSONRPCMessage.t
-
(** Create a new response message
-
-
Responses are sent in reply to requests and contain successful results.
-
This is a convenience wrapper around JSONRPCMessage.create_response.
-
-
Each response must include the same ID as its corresponding request
-
to allow the client to correlate them, especially when multiple
-
requests are in flight simultaneously.
-
-
@param id ID matching the original request
-
@param result Result of the successful request as a JSON value
-
@return A new JSON-RPC response message
-
*)
-
val create_response : id:RequestId.t -> result:Json.t -> JSONRPCMessage.t
-
(** Create a new error message
-
-
Errors are sent in reply to requests when processing fails.
-
This is a convenience wrapper around JSONRPCMessage.create_error.
-
-
MCP uses standard JSON-RPC error codes as well as some protocol-specific codes:
-
- -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)
-
-
@param id ID matching the original request
-
@param code Error code indicating the type of error
-
@param message Human-readable error message describing the issue
-
@param data Optional additional error data providing more context
-
@return A new JSON-RPC error message
-
*)
-
val create_error : id:RequestId.t -> code:int -> message:string -> ?data:Json.t option -> unit -> JSONRPCMessage.t
-
(** Create a new text content object
-
@param text The text content
-
@return A content value with the text
-
*)
-
val make_text_content : string -> content
-
(** Create a new image content object
-
@param data Base64-encoded image data
-
@param mime_type MIME type of the image (e.g., "image/png", "image/jpeg")
-
@return A content value with the image
-
*)
-
val make_image_content : string -> string -> content
-
(** Create a new audio content object
-
@param data Base64-encoded audio data
-
@param mime_type MIME type of the audio (e.g., "audio/wav", "audio/mp3")
-
@return A content value with the audio
-
*)
-
val make_audio_content : string -> string -> content
-
(** Create a new text resource content object
-
@param uri URI that uniquely identifies the resource
-
@param text The text content of the resource
-
@param mime_type Optional MIME type of the text content
-
@return A content value with the text resource
-
*)
-
val make_resource_text_content : string -> string -> string option -> content
-
(** Create a new binary resource content object
-
@param uri URI that uniquely identifies the resource
-
@param blob Base64-encoded binary data
-
@param mime_type Optional MIME type of the binary content
-
@return A content value with the binary resource
-
*)
-
val make_resource_blob_content : string -> string -> string option -> content
···
+
(** MCP - Model Context Protocol implementation *)
open Jsonrpc
+
(** Standard error codes *)
module ErrorCode : sig
+
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 ]
+
+
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 ]
+
include Json.Jsonable.S with type t := t
end
(** Request IDs *)
module RequestId : sig
type t = [ `String of string | `Int of int ]
+
include Json.Jsonable.S with type t := t
end
(** Cursors for pagination *)
module Cursor : sig
type t = string
+
+
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;
}
and annotation = {
audience: Role.t list option;
priority: float option;
}
+
+
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 *)
module TextContent : sig
type t = {
text: string;
annotations: Annotated.annotation option;
}
+
+
val yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.t -> t
end
+
(** Image content *)
module ImageContent : sig
type t = {
data: string;
mime_type: string;
annotations: Annotated.annotation option;
}
+
+
val yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.t -> t
end
+
(** Audio content *)
module AudioContent : sig
type t = {
data: string;
mime_type: string;
annotations: Annotated.annotation option;
}
+
+
val yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.t -> t
end
+
(** Base resource contents *)
module ResourceContents : sig
type t = {
uri: string;
mime_type: string option;
}
+
+
val yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.t -> t
end
+
(** Text resource contents *)
module TextResourceContents : sig
type t = {
uri: string;
text: string;
mime_type: string option;
}
+
+
val yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.t -> t
end
+
(** Binary resource contents *)
module BlobResourceContents : sig
type t = {
uri: string;
blob: string;
mime_type: string option;
}
+
+
val yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.t -> t
end
+
(** Embedded resource *)
module EmbeddedResource : sig
type t = {
resource: [ `Text of TextResourceContents.t | `Blob of BlobResourceContents.t ];
annotations: Annotated.annotation option;
}
+
+
val yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.t -> t
end
+
(** Content type used in messages *)
type content =
+
| Text of TextContent.t
+
| Image of ImageContent.t
+
| Audio of AudioContent.t
+
| Resource of EmbeddedResource.t
val yojson_of_content : content -> Json.t
val content_of_yojson : Json.t -> content
+
(** Message for prompts *)
module PromptMessage : sig
type t = {
role: Role.t;
content: content;
}
+
+
val yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.t -> t
end
+
(** Message for sampling *)
module SamplingMessage : sig
type t = {
role: Role.t;
+
content: [ `Text of TextContent.t | `Image of ImageContent.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;
version: string;
}
+
+
val yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.t -> t
end
+
(** JSONRPC message types *)
module JSONRPCMessage : sig
type notification = {
+
method_: string;
params: Json.t option;
}
type request = {
id: RequestId.t;
+
method_: string;
params: Json.t option;
progress_token: ProgressToken.t option;
}
type response = {
id: RequestId.t;
result: Json.t;
}
type error = {
id: RequestId.t;
code: int;
message: string;
data: Json.t option;
}
type t =
| Notification of notification
| Request of request
| Response of response
| Error of error
val yojson_of_notification : notification -> Json.t
val yojson_of_request : request -> Json.t
val yojson_of_response : response -> Json.t
val yojson_of_error : error -> Json.t
val yojson_of_t : t -> Json.t
val notification_of_yojson : Json.t -> notification
val request_of_yojson : Json.t -> request
val response_of_yojson : Json.t -> response
val error_of_yojson : Json.t -> error
val t_of_yojson : Json.t -> t
+
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
val create_error : id:RequestId.t -> code:int -> message:string -> ?data:Json.t option -> unit -> t
end
+
(** Initialize request/response *)
module Initialize : sig
(** Initialize request *)
module Request : sig
type t = {
+
capabilities: Json.t; (** ClientCapabilities *)
client_info: Implementation.t;
protocol_version: string;
}
+
+
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
end
(** Initialize result *)
module Result : sig
type t = {
+
capabilities: Json.t; (** ServerCapabilities *)
server_info: Implementation.t;
protocol_version: string;
instructions: string option;
meta: Json.t option;
}
+
+
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
end
end
+
(** Initialized notification *)
module Initialized : sig
module Notification : sig
type t = {
meta: Json.t option;
}
+
+
val yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.t -> t
val create : ?meta:Json.t -> unit -> t
val to_jsonrpc : t -> JSONRPCMessage.t
end
end
+
(** Tool definition *)
+
module Tool : sig
+
type t = {
+
name: string;
+
description: string option;
+
input_schema: Json.t;
+
}
+
val yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.t -> t
+
end
+
(** 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
+
(** 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 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;
+
}
+
val yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.t -> t
+
end
+
(** Resource Reference *)
+
module ResourceReference : sig
+
type t = {
+
uri: string;
+
}
+
val yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.t -> t
+
end
+
(** Prompt Reference *)
+
module PromptReference : sig
+
type t = {
+
name: string;
+
}
+
val yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.t -> t
+
end
+
+
(** Completion support *)
+
module Completion : sig
+
module Argument : sig
+
type t = {
+
name: string;
+
value: string;
+
}
+
+
val yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.t -> t
+
end
+
+
module Request : sig
+
type reference = [ `Prompt of PromptReference.t | `Resource of ResourceReference.t ]
+
+
type t = {
+
argument: Argument.t;
+
ref: reference;
+
}
+
+
val yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.t -> t
+
+
val yojson_of_reference : reference -> Json.t
+
val reference_of_yojson : Json.t -> reference
+
+
val create : argument:Argument.t -> ref:reference -> t
+
val to_params : t -> Json.t
+
end
+
+
module Result : sig
+
type completion = {
+
values: string list;
+
has_more: bool option;
+
total: int option;
+
}
+
+
type t = {
+
completion: completion;
+
meta: Json.t option;
+
}
+
+
val yojson_of_completion : completion -> Json.t
+
val completion_of_yojson : Json.t -> completion
+
+
val yojson_of_t : t -> Json.t
+
val t_of_yojson : Json.t -> t
+
+
val create : completion:completion -> ?meta:Json.t -> unit -> t
+
val to_result : t -> Json.t
+
end
+
end
+
+
(** Parse a JSON message into an MCP message *)
+
val parse_message : Json.t -> JSONRPCMessage.t
+
+
(** 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
+
+
(** 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
-726
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/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
···
-264
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
-
-
(** Create a resources/list request *)
-
val create_request : ?cursor:Cursor.t -> ?id:RequestId.t -> unit -> JSONRPCMessage.t
-
-
(** Create a resources/list response *)
-
val create_response : id:RequestId.t -> resources:Resource.t list -> ?next_cursor:Cursor.t -> unit -> JSONRPCMessage.t
-
end
-
-
(** Resources/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
-
-
(** Create a resources/read request *)
-
val create_request : uri:string -> ?id:RequestId.t -> unit -> JSONRPCMessage.t
-
-
(** Create a resources/read response *)
-
val create_response : id:RequestId.t -> contents:ResourceContent.t list -> unit -> JSONRPCMessage.t
-
end
-
-
(** Tools/List - Request to list available tools *)
-
module ToolsList : sig
-
(** Request parameters *)
-
module Request : sig
-
type t = {
-
cursor: Cursor.t option; (** Optional pagination cursor *)
-
}
-
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
-
-
(** Create a tools/list request *)
-
val create_request : ?cursor:Cursor.t -> ?id:RequestId.t -> unit -> JSONRPCMessage.t
-
-
(** Create a tools/list response *)
-
val create_response : id:RequestId.t -> tools:Tool.t list -> ?next_cursor:Cursor.t -> unit -> JSONRPCMessage.t
-
end
-
-
(** Tools/Call - Request to invoke a tool *)
-
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
-
-
(** Create a tools/call request *)
-
val create_request : name:string -> arguments:Json.t -> ?id:RequestId.t -> unit -> JSONRPCMessage.t
-
-
(** Create a tools/call response *)
-
val create_response : id:RequestId.t -> content:ToolContent.t list -> is_error:bool -> unit -> JSONRPCMessage.t
-
end
-
-
(** Prompts/List - Request to list available prompts *)
-
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
-
-
(** Create a prompts/list request *)
-
val create_request : ?cursor:Cursor.t -> ?id:RequestId.t -> unit -> JSONRPCMessage.t
-
-
(** Create a prompts/list response *)
-
val create_response : id:RequestId.t -> prompts:Prompt.t list -> ?next_cursor:Cursor.t -> unit -> JSONRPCMessage.t
-
end
-
-
(** Prompts/Get - Request to get a prompt with arguments *)
-
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
-
-
(** Create a prompts/get request *)
-
val create_request : name:string -> arguments:(string * string) list -> ?id:RequestId.t -> unit -> JSONRPCMessage.t
-
-
(** Create a prompts/get response *)
-
val create_response : id:RequestId.t -> ?description:string -> messages:PromptMessage.t list -> unit -> JSONRPCMessage.t
-
end
-
-
(** List Changed Notifications *)
-
module ListChanged : sig
-
(** Create a resources/list_changed notification *)
-
val create_resources_notification : unit -> JSONRPCMessage.t
-
-
(** Create a tools/list_changed notification *)
-
val create_tools_notification : unit -> JSONRPCMessage.t
-
-
(** Create a prompts/list_changed notification *)
-
val create_prompts_notification : unit -> JSONRPCMessage.t
-
end
-
-
(** Resource Updated Notification *)
-
module ResourceUpdated : sig
-
(** Notification parameters *)
-
module Notification : sig
-
type t = {
-
uri: string; (** URI of the updated resource *)
-
}
-
include Json.Jsonable.S with type t := t
-
end
-
-
(** Create a resources/updated notification *)
-
val create_notification : uri:string -> unit -> JSONRPCMessage.t
-
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
-
-
(** Create a progress notification *)
-
val create_notification : progress:float -> total:float -> progress_token:ProgressToken.t -> unit -> JSONRPCMessage.t
-
end
···
+122 -174
lib/mcp_sdk.ml
···
| Warning -> "WARNING"
| Error -> "ERROR"
-
let logf level fmt =
-
Printf.fprintf stderr "[%s] " (string_of_level level);
-
Printf.kfprintf (fun oc -> Printf.fprintf oc "\n"; flush oc) stderr fmt
-
let debugf fmt = logf Debug fmt
-
let infof fmt = logf Info fmt
-
let warningf fmt = logf Warning fmt
-
let errorf fmt = logf Error fmt
-
-
(* Backward compatibility functions that take a simple string *)
-
let log level msg = logf level "%s" msg
-
let debug msg = debugf "%s" msg
-
let info msg = infof "%s" msg
-
let warning msg = warningf "%s" msg
-
let error msg = errorf "%s" msg
end
(* Context for tools and resources *)
···
type t = {
request_id: RequestId.t option;
lifespan_context: (string * Json.t) list;
-
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
···
("total", `Float total);
("progressToken", ProgressToken.yojson_of_t token)
] in
-
Some (create_notification ~meth:Method.Progress ~params:(Some params) ())
| _ -> None
end
···
| 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 =
-
Log.errorf "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 *)
···
| 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_template;
-
name = resource.uri_template; (* Use uri as name by default *)
-
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 *)
···
let create_argument ~name ?description ?(required=false) () =
{ name; description; required }
let to_json prompt =
let assoc = [
("name", `String prompt.name);
···
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;
-
}
-
-
(* 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
-
-
(* Convert message to Mcp_rpc.PromptMessage.t *)
-
let message_to_rpc_prompt_message msg =
-
{
-
PromptMessage.role = msg.role;
-
PromptMessage.content = msg.content;
-
}
-
-
(* Convert a list of messages to the format needed for prompts/get response *)
-
let messages_to_rpc_prompt_messages messages =
-
List.map message_to_rpc_prompt_message messages
end
let make_tool_schema properties required =
let props = List.map (fun (name, schema_type, description) ->
(name, `Assoc [
···
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 prompts: Prompt.t list;
-
}
-
-
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 prompts { prompts; _ } = prompts
(* Create a new server *)
let create_server ~name ?(version="0.1.0") ?(protocol_version="2024-11-05") () =
···
resources = [];
prompts = [];
lifespan_context = [];
}
(* Default capabilities for the server *)
···
let capabilities = default_capabilities ~with_tools ~with_resources ~with_prompts () in
set_capabilities server capabilities;
server
···
| 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 *)
···
type t = {
request_id: RequestId.t option;
lifespan_context: (string * Json.t) list;
+
mutable progress_token: ProgressToken.t option;
}
+
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
···
("total", `Float total);
("progressToken", ProgressToken.yojson_of_t token)
] in
+
Some (create_notification ~method_:"notifications/progress" ~params:(Some params) ())
| _ -> None
end
···
| None -> assoc
in
`Assoc assoc
end
(* Resources for the MCP server *)
···
| None -> assoc
in
`Assoc assoc
end
(* Prompts for the MCP server *)
···
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);
···
assoc
in
`Assoc assoc
end
+
(* Helper functions for creating common objects *)
+
let make_text_content text =
+
Text (TextContent.{ text; annotations = None })
+
+
let make_text_content_with_annotations text annotations =
+
Text (TextContent.{ text; annotations = Some annotations })
+
+
let make_image_content data mime_type =
+
Image (ImageContent.{ data; mime_type; annotations = None })
+
+
let make_image_content_with_annotations data mime_type annotations =
+
Image (ImageContent.{ data; mime_type; annotations = Some annotations })
+
+
let make_audio_content data mime_type =
+
Audio (AudioContent.{ data; mime_type; annotations = None })
+
+
let make_audio_content_with_annotations data mime_type annotations =
+
Audio (AudioContent.{ data; mime_type; annotations = Some annotations })
+
+
let make_text_resource_content uri text ?mime_type () =
+
Resource (EmbeddedResource.{
+
resource = `Text TextResourceContents.{ uri; text; mime_type };
+
annotations = None
+
})
+
+
let make_blob_resource_content uri blob ?mime_type () =
+
Resource (EmbeddedResource.{
+
resource = `Blob BlobResourceContents.{ uri; blob; mime_type };
+
annotations = None
+
})
+
let make_tool_schema properties required =
let props = List.map (fun (name, schema_type, description) ->
(name, `Assoc [
···
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;
+
}
(* Create a new server *)
let create_server ~name ?(version="0.1.0") ?(protocol_version="2024-11-05") () =
···
resources = [];
prompts = [];
lifespan_context = [];
+
startup_hook = None;
+
shutdown_hook = None;
}
(* Default capabilities for the server *)
···
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
+59 -68
lib/mcp_sdk.mli
···
val string_of_level : level -> string
-
(** Format-string based logging functions *)
-
val logf : level -> ('a, out_channel, unit) format -> 'a
-
val debugf : ('a, out_channel, unit) format -> 'a
-
val infof : ('a, out_channel, unit) format -> 'a
-
val warningf : ('a, out_channel, unit) format -> 'a
-
val errorf : ('a, out_channel, unit) format -> 'a
-
-
(** Simple string logging functions (for backward compatibility) *)
val log : level -> string -> unit
val debug : string -> unit
val info : string -> unit
···
(** Context for tools and resources *)
module Context : sig
-
type t
-
val create : ?request_id:RequestId.t -> ?progress_token:ProgressToken.t -> ?lifespan_context:(string * Json.t) list -> unit -> t
val get_context_value : t -> string -> Json.t option
val report_progress : t -> float -> float -> JSONRPCMessage.t option
end
···
val create : name:string -> ?description:string -> input_schema:Json.t -> handler:handler -> unit -> t
val to_json : t -> Json.t
-
-
(** Convert to Mcp_rpc.ToolsList.Tool.t *)
-
val to_rpc_tool_list_tool : t -> Mcp_rpc.ToolsList.Tool.t
-
-
(** Convert a list of Tool.t to the format needed for tools/list response *)
-
val to_rpc_tools_list : t list -> Mcp_rpc.ToolsList.Tool.t list
-
-
(** Convert Mcp_rpc.ToolsCall response content to Mcp.content list *)
-
val rpc_content_to_mcp_content : Mcp_rpc.ToolsCall.ToolContent.t list -> Mcp.content list
-
-
(** Convert Mcp.content list to Mcp_rpc.ToolsCall.ToolContent.t list *)
-
val mcp_content_to_rpc_content : Mcp.content list -> Mcp_rpc.ToolsCall.ToolContent.t list
-
-
(** Create a tool result with content *)
-
val create_tool_result : Mcp.content list -> is_error:bool -> Json.t
-
-
(** Create a tool error result with structured content *)
-
val create_error_result : string -> Json.t
-
-
(** Handle tool execution errors *)
-
val handle_execution_error : string -> Json.t
-
-
(** Handle unknown tool error *)
-
val handle_unknown_tool_error : string -> Json.t
-
-
(** Handle general tool execution exception *)
-
val handle_execution_exception : exn -> Json.t
end
(** Resources for the MCP server *)
···
val create : uri_template:string -> ?description:string -> ?mime_type:string -> handler:handler -> unit -> t
val to_json : t -> Json.t
-
-
(** Convert to Mcp_rpc.ResourcesList.Resource.t *)
-
val to_rpc_resource_list_resource : t -> Mcp_rpc.ResourcesList.Resource.t
-
-
(** Convert a list of Resource.t to the format needed for resources/list response *)
-
val to_rpc_resources_list : t list -> Mcp_rpc.ResourcesList.Resource.t list
end
(** Prompts for the MCP server *)
···
val create : name:string -> ?description:string -> ?arguments:argument list -> handler:handler -> unit -> t
val create_argument : name:string -> ?description:string -> ?required:bool -> unit -> argument
val to_json : t -> Json.t
-
-
(** Convert argument to Mcp_rpc.PromptsList.PromptArgument.t *)
-
val argument_to_rpc_prompt_argument : argument -> Mcp_rpc.PromptsList.PromptArgument.t
-
-
(** Convert to Mcp_rpc.PromptsList.Prompt.t *)
-
val to_rpc_prompt_list_prompt : t -> Mcp_rpc.PromptsList.Prompt.t
-
-
(** Convert a list of Prompt.t to the format needed for prompts/list response *)
-
val to_rpc_prompts_list : t list -> Mcp_rpc.PromptsList.Prompt.t list
-
-
(** Convert message to Mcp_rpc.PromptMessage.t *)
-
val message_to_rpc_prompt_message : message -> PromptMessage.t
-
-
(** Convert a list of messages to the format needed for prompts/get response *)
-
val messages_to_rpc_prompt_messages : message list -> PromptMessage.t list
end
(** Main server type *)
-
type server
-
-
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 prompts : server -> Prompt.t list
(** Create a new server *)
val create_server : name:string -> ?version:string -> ?protocol_version:string -> unit -> server
···
(** Default capabilities for the server *)
val default_capabilities : ?with_tools:bool -> ?with_resources:bool -> ?with_prompts:bool -> unit -> Json.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
(** 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
(** 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
(** 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
···
val string_of_level : level -> string
val log : level -> string -> unit
val debug : string -> unit
val info : string -> unit
···
(** Context for tools and resources *)
module Context : sig
+
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 -> ?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
···
val create : name:string -> ?description:string -> input_schema:Json.t -> handler:handler -> unit -> t
val to_json : t -> Json.t
end
(** Resources for the MCP server *)
···
val create : uri_template:string -> ?description:string -> ?mime_type:string -> handler:handler -> unit -> t
val to_json : t -> Json.t
end
(** Prompts for the MCP server *)
···
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
end
(** Main server type *)
+
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;
+
}
(** Create a new server *)
val create_server : name:string -> ?version:string -> ?protocol_version:string -> unit -> server
···
(** Default capabilities for the server *)
val default_capabilities : ?with_tools:bool -> ?with_resources:bool -> ?with_prompts:bool -> unit -> Json.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
+
(** 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
+
+
(** Register a prompt with the server *)
+
val register_prompt : server -> Prompt.t -> 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
+
(** 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
+
(** 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
+512 -271
lib/mcp_server.ml
···
open Mcp
-
open Jsonrpc
open Mcp_sdk
-
(* Process initialize request *)
-
let handle_initialize server req =
-
Log.debug "Processing initialize request";
-
let result = match req.JSONRPCMessage.params with
-
| Some params ->
-
let req_data = Initialize.Request.t_of_yojson params in
-
Log.debugf "Client info: %s v%s"
-
req_data.client_info.name req_data.client_info.version;
-
Log.debugf "Client 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.error "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 "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 "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 "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
-
-
(* Extract the tool name from params *)
-
let extract_tool_name params =
-
match List.assoc_opt "name" params with
-
| Some (`String name) ->
-
Log.debugf "Tool name: %s" name;
-
Some name
-
| _ ->
-
Log.error "Missing or invalid 'name' parameter in tool call";
-
None
-
-
(* Extract the tool arguments from params *)
-
let extract_tool_arguments params =
-
match List.assoc_opt "arguments" params with
-
| Some (args) ->
-
Log.debugf "Tool arguments: %s" (Yojson.Safe.to_string args);
-
args
-
| _ ->
-
Log.debug "No arguments provided for tool call, using empty object";
-
`Assoc [] (* Empty arguments is valid *)
-
-
(* 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) ()
-
-
-
(* Execute a tool *)
-
let execute_tool server ctx name args =
-
try
-
let tool = List.find (fun t -> t.Tool.name = name) (tools server) in
-
Log.debugf "Found tool: %s" name;
-
-
(* Call the tool handler *)
-
match tool.handler ctx args with
-
| Ok result ->
-
Log.debug "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 = text; annotations = None } in
-
([Mcp_rpc.ToolsCall.ToolContent.Text text_content], false))
-
| _ ->
-
(* Simple fallback for non-object results *)
-
let text = Yojson.Safe.to_string json in
-
let text_content = { TextContent.text = text; annotations = None } in
-
([Mcp_rpc.ToolsCall.ToolContent.Text text_content], false)
-
-
(* Process tools/call request *)
-
let handle_tools_call server req =
-
Log.debug "Processing tools/call request";
-
match req.JSONRPCMessage.params with
-
| Some (`Assoc params) ->
-
(match extract_tool_name params with
-
| Some name ->
-
let args = extract_tool_arguments params in
-
-
(* Create context for this request *)
-
let ctx = Context.create
-
?request_id:(Some req.id)
-
?progress_token:req.progress_token
-
~lifespan_context:[("tools/call", `Assoc params)]
-
()
-
in
-
-
(* Execute the tool *)
-
let result_json = execute_tool server ctx name args in
-
-
(* Convert JSON result to RPC format *)
-
let content, is_error = json_to_rpc_content result_json in
-
-
(* Create the RPC response *)
-
let response = Mcp_rpc.ToolsCall.create_response
-
~id:req.id
-
~content
-
~is_error
-
()
-
in
-
-
Some response
-
| None ->
-
Some (create_jsonrpc_error req.id InvalidParams "Missing tool name parameter" ()))
-
| _ ->
-
Log.error "Invalid params format for tools/call";
-
Some (create_jsonrpc_error req.id InvalidParams "Invalid params format for tools/call" ())
-
(* Process ping request *)
-
let handle_ping (req:JSONRPCMessage.request) =
-
Log.debug "Processing ping request";
-
Some (create_response ~id:req.JSONRPCMessage.id ~result:(`Assoc []))
-
(* Handle notifications/initialized *)
-
let handle_initialized (notif:JSONRPCMessage.notification) =
-
Log.debug "Client initialization complete - Server is now ready to receive requests";
-
Log.debugf "Notification params: %s"
-
(match notif.JSONRPCMessage.params with
-
| Some p -> Yojson.Safe.to_string p
-
| None -> "null");
-
None
-
(* Process a single message using the MCP SDK *)
let process_message server message =
try
-
Log.debugf "Processing message: %s" (Yojson.Safe.to_string message);
match JSONRPCMessage.t_of_yojson message with
-
| JSONRPCMessage.Request req ->
-
Log.debugf "Received request with method: %s" (Method.to_string req.meth);
-
(match req.meth with
-
| Method.Initialize -> handle_initialize server req
-
| Method.ToolsList -> handle_tools_list server req
-
| Method.ToolsCall -> handle_tools_call server req
-
| Method.PromptsList -> handle_prompts_list server req
-
| Method.ResourcesList -> handle_resources_list server req
-
| _ ->
-
Log.errorf "Unknown method received: %s" (Method.to_string req.meth);
-
Some (create_jsonrpc_error req.id MethodNotFound ("Method not found: " ^ (Method.to_string req.meth)) ()))
-
| JSONRPCMessage.Notification notif ->
-
Log.debugf "Received notification with method: %s" (Method.to_string notif.meth);
-
(match notif.meth with
-
| Method.Initialized -> handle_initialized notif
-
| _ ->
-
Log.debugf "Ignoring notification: %s" (Method.to_string notif.meth);
-
None)
-
| JSONRPCMessage.Response _ ->
-
Log.error "Unexpected response message received";
None
-
| JSONRPCMessage.Error _ ->
-
Log.error "Unexpected error message received";
None
-
with
-
| Json.Of_json (msg, _) ->
-
Log.errorf "JSON error: %s" msg;
-
(* Can't respond with error because we don't have a request ID *)
-
None
-
| Yojson.Json_error msg ->
-
Log.errorf "JSON parse error: %s" msg;
-
(* Can't respond with error because we don't have a request ID *)
-
None
-
| exc ->
-
Log.errorf "Exception during message processing: %s" (Printexc.to_string exc);
-
Log.errorf "Backtrace: %s" (Printexc.get_backtrace());
-
Log.errorf "Message was: %s" (Yojson.Safe.to_string message);
-
None
-
(* Extract a request ID from a potentially malformed message *)
-
let extract_request_id json =
try
-
match json with
-
| `Assoc fields ->
-
(match List.assoc_opt "id" fields with
-
| Some (`Int id) -> Some (`Int id)
-
| Some (`String id) -> Some (`String id)
-
| _ -> None)
-
| _ -> None
-
with _ -> None
-
(* Handle processing for an input line *)
-
let process_input_line server line =
-
if line = "" then (
-
Log.debug "Empty line received, ignoring";
-
None
-
) else (
-
Log.debugf "Raw input: %s" line;
-
try
-
let json = Yojson.Safe.from_string line in
-
Log.debug "Successfully parsed JSON";
-
-
(* Process the message *)
-
process_message server json
-
with
-
| Yojson.Json_error msg -> begin
-
Log.errorf "Error parsing JSON: %s" msg;
-
Log.errorf "Input was: %s" line;
-
None
end
-
)
-
(* Send a response to the client *)
-
let send_response stdout response =
-
let response_json = JSONRPCMessage.yojson_of_t response in
-
let response_str = Yojson.Safe.to_string response_json in
-
Log.debugf "Sending response: %s" response_str;
-
(* Write the response followed by a newline *)
-
Eio.Flow.copy_string response_str stdout;
-
Eio.Flow.copy_string "\n" stdout
-
-
(* Run the MCP server with the given server configuration *)
-
let run_server env server =
-
let stdin = Eio.Stdenv.stdin env in
-
let stdout = Eio.Stdenv.stdout env in
-
-
Log.debugf "Starting MCP server: %s v%s" (name server) (version server);
-
Log.debugf "Protocol version: %s" (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
-
(* Main processing loop *)
-
try
-
while true do
-
Log.debug "Waiting for message...";
-
let line = Eio.Buf_read.line buf in
-
(* Process the input and send response if needed *)
-
match process_input_line server line with
-
| Some response -> send_response stdout response
-
| None -> Log.debug "No response needed for this message"
-
done
-
with
-
| End_of_file ->
-
Log.debug "End of file received on stdin";
-
()
-
| Eio.Exn.Io _ as exn ->
-
Log.errorf "I/O error while reading: %s" (Printexc.to_string exn);
-
()
-
| exn ->
-
Log.errorf "Exception while reading: %s" (Printexc.to_string exn);
-
()
···
open Mcp
open Mcp_sdk
+
(* MCP Server module for handling JSON-RPC communication *)
+
(** Server types *)
+
type transport_type =
+
| Stdio (* Read/write to stdin/stdout *)
+
| Http (* HTTP server - to be implemented *)
+
type t = {
+
server: Mcp_sdk.server;
+
transport: transport_type;
+
mutable running: bool;
+
}
+
(** Process a single message *)
let process_message server message =
try
+
Log.debug "Parsing message as JSONRPC message...";
match JSONRPCMessage.t_of_yojson message with
+
| 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
+
end
+
+
| JSONRPCMessage.Error _ -> begin
+
Log.error "Unexpected error message received";
+
None
+
end
+
with
+
| Failure msg -> begin
+
Log.error (Printf.sprintf "JSON error in message processing: %s" msg);
None
+
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
+
(** Read a single message from stdin *)
+
let read_stdio_message () =
try
+
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
+
(** 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
+
(** Create an MCP server *)
+
let create ~server ~transport () =
+
{ server; transport; running = false }
+
+
(** 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
+
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 ->
+
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"
+
()
+
in
+
+
(* Create and start the server *)
+
let server = Cohttp_lwt_unix.Server.create
+
~mode:(`TCP (`Port port))
+
(Cohttp_lwt_unix.Server.make ~callback ())
+
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
+
(** 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
+
+
(** 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
-29
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"
-
"eio_main"
-
"eio"
-
"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.