Model Context Protocol in OCaml

eio

+7 -28
bin/capitalize_sdk.ml
···
let server = create_server
~name:"OCaml MCP Capitalizer"
~version:"0.1.0"
-
~protocol_version:"2024-11-05"
-
()
-
-
(* Define startup and shutdown hooks *)
-
let startup () =
-
Printf.printf "CapitalizeServer is starting up!\n";
-
flush stdout;
-
Log.info "CapitalizeServer is starting up!"
-
-
let shutdown () =
-
Printf.printf "CapitalizeServer is shutting down. Goodbye!\n";
-
flush stdout;
-
Log.info "CapitalizeServer is shutting down. Goodbye!"
-
-
(* Register the hooks *)
-
let () =
-
set_startup_hook server startup;
-
set_shutdown_hook server shutdown
+
~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
···
]
)
-
(* Main function *)
let () =
-
(* Print directly to ensure we see output *)
-
Printf.printf "Starting CapitalizeServer...\n";
-
flush stdout;
-
-
(* Configure the server with appropriate capabilities *)
-
ignore (configure_server server ());
-
-
(* Run the server *)
-
run_server server
+
(* Run the server with the default scheduler *)
+
Eio_main.run @@ fun env->
+
Mcp_server.run_server env server
+1 -5
bin/dune
···
-
(executable
-
(name server)
-
(libraries mcp yojson unix))
-
(executable
(name capitalize_sdk)
(modules capitalize_sdk)
-
(libraries mcp mcp_sdk yojson unix))
+
(libraries mcp mcp_server yojson eio_main eio))
-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
+6
lib/dune
···
(libraries mcp jsonrpc unix yojson)
(modules mcp_sdk)
(flags (:standard -w -67 -w -27 -w -32)))
+
+
(library
+
(name mcp_server)
+
(libraries mcp_sdk jsonrpc eio_main eio)
+
(modules mcp_server)
+
)
+10 -37
lib/mcp_sdk.ml
···
let log level msg =
Printf.eprintf "[%s] %s\n" (string_of_level level) msg;
-
flush stderr;
-
Printf.printf "[%s] %s\n" (string_of_level level) msg;
-
flush stdout
+
flush stderr
let debug = log Debug
let info = log Info
···
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;
-
mutable lifespan_context: (string * Json.t) list;
-
mutable startup_hook: (unit -> unit) option;
-
mutable shutdown_hook: (unit -> unit) option;
-
}
+
}
+
+
let name { name; _ } = name
+
let version { version; _ } = version
+
let capabilities { capabilities; _ } = capabilities
+
let lifespan_context { lifespan_context; _ } = lifespan_context
+
let protocol_version { protocol_version; _ } = protocol_version
+
let tools { tools; _ } = tools
(* 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
-
-
(* Run the server *)
-
let run_server server =
-
(* Setup *)
-
Printexc.record_backtrace true;
-
set_binary_mode_out stdout false;
-
-
Log.info (Printf.sprintf "%s server started" 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 -> ());
-
-
Log.info "Server initialized and ready."
+7 -21
lib/mcp_sdk.mli
···
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;
-
}
+
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
(** Create a new server *)
val create_server : name:string -> ?version:string -> ?protocol_version:string -> unit -> server
···
(** 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 *)
-
val run_server : server -> unit
(** Helper functions for creating common objects *)
val make_text_content : string -> content
+207
lib/mcp_server.ml
···
+
open Mcp
+
open Jsonrpc
+
open Mcp_sdk
+
+
(* Process a single message using the MCP SDK *)
+
let process_message server message =
+
try
+
Log.debug (Printf.sprintf "Processing message: %s" (Yojson.Safe.to_string 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";
+
let result = match req.params with
+
| Some params ->
+
let req_data = Initialize.Request.t_of_yojson params in
+
Log.debug (Printf.sprintf "Client info: %s v%s"
+
req_data.client_info.name req_data.client_info.version);
+
Log.debug (Printf.sprintf "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)
+
| "tools/list" ->
+
Log.debug "Processing tools/list request";
+
let tool_list = List.map Tool.to_json (tools server) in
+
let result = `Assoc [("tools", `List tool_list)] in
+
Some (create_response ~id:req.id ~result)
+
| "tools/call" ->
+
Log.debug "Processing tools/call request";
+
(match req.params 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";
+
""
+
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
+
+
(* Find the matching tool *)
+
let result =
+
try
+
let tool = List.find (fun t -> t.Tool.name = name) (tools server) in
+
Log.debug (Printf.sprintf "Found tool: %s" name);
+
+
(* Create context for this request *)
+
let ctx = Context.create ?request_id:req.progress_token () in
+
+
(* Call the tool handler *)
+
match tool.handler ctx args with
+
| Ok result ->
+
Log.debug (Printf.sprintf "Tool execution succeeded");
+
result
+
| Error err ->
+
Log.error (Printf.sprintf "Tool execution failed: %s" err);
+
let error_content = TextContent.{
+
text = Printf.sprintf "Error executing tool: %s" err;
+
annotations = None
+
} in
+
`Assoc [
+
("content", `List [TextContent.yojson_of_t error_content]);
+
("isError", `Bool true)
+
]
+
with
+
| Not_found ->
+
Log.error (Printf.sprintf "Unknown tool: %s" name);
+
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)
+
]
+
| exn ->
+
Log.error (Printf.sprintf "Exception executing tool: %s" (Printexc.to_string exn));
+
let error_content = TextContent.{
+
text = Printf.sprintf "Internal error: %s" (Printexc.to_string exn);
+
annotations = None
+
} in
+
`Assoc [
+
("content", `List [TextContent.yojson_of_t error_content]);
+
("isError", `Bool true)
+
]
+
in
+
Some (create_response ~id:req.id ~result)
+
| _ ->
+
Log.error "Invalid params format for tools/call";
+
Some (create_error ~id:req.id ~code:(-32602) ~message:"Invalid params for tools/call" ()))
+
| "ping" ->
+
Log.debug "Processing ping request";
+
Some (create_response ~id:req.id ~result:(`Assoc []))
+
| _ ->
+
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
+
| Json.Of_json (msg, _) ->
+
Log.error (Printf.sprintf "JSON error: %s" msg);
+
None
+
| 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
+
+
(* 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.debug (Printf.sprintf "Starting MCP server: %s v%s" (name server) (version server));
+
Log.debug (Printf.sprintf "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 (Printf.sprintf "Waiting for message..." );
+
let line = Eio.Buf_read.line buf in
+
if line = "" then (
+
Log.debug "Empty line received, ignoring";
+
) else (
+
Log.debug (Printf.sprintf "Raw input: %s" line);
+
try
+
let json = Yojson.Safe.from_string line in
+
Log.debug "Successfully parsed JSON";
+
+
(* Process the message *)
+
match process_message server json with
+
| Some response -> begin
+
(* Send 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);
+
+
(* Write the response followed by a newline *)
+
Eio.Flow.copy_string response_str stdout;
+
Eio.Flow.copy_string "\n" stdout;
+
end
+
| None ->
+
Log.debug "No response needed for this message";
+
with
+
| Yojson.Json_error msg -> begin
+
Log.error (Printf.sprintf "Error parsing JSON: %s" msg);
+
Log.error (Printf.sprintf "Input was: %s" line);
+
end
+
)
+
done
+
with
+
| End_of_file ->
+
Log.debug "End of file received on stdin";
+
()
+
| Eio.Exn.Io _ as exn ->
+
Log.error (Printf.sprintf "I/O error while reading: %s" (Printexc.to_string exn));
+
()
+
| exc ->
+
Log.error (Printf.sprintf "Exception while reading: %s" (Printexc.to_string exc));
+
()
+