···
-
(* Logging utilities *)
-
Printf.eprintf "[DEBUG] %s\n" msg;
-
Printf.eprintf "[ERROR] %s\n" msg;
-
let protocol_version = "2024-11-05"
-
let server_info = Implementation.{ name = "ocaml-mcp-capitalizer"; version = "0.1.0" }
-
let server_capabilities = `Assoc [
-
("listChanged", `Bool true)
-
(* We don't support resources - make this explicit *)
-
("listChanged", `Bool false);
-
("subscribe", `Bool false)
-
(* We don't support prompts - make this explicit *)
-
("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 [
-
("type", `String "string");
-
("description", `String "The text to capitalize")
-
("required", `List [`String "text"])
-
(match List.assoc_opt "text" fields with
-
| Some (`String text) ->
-
let capitalized_text = String.uppercase_ascii text in
-
let content = TextContent.{
-
text = capitalized_text;
-
("content", `List [TextContent.yojson_of_t content]);
-
("isError", `Bool false)
-
let error_content = TextContent.{
-
text = "Missing or invalid 'text' parameter";
-
("content", `List [TextContent.yojson_of_t error_content]);
-
("isError", `Bool true)
-
let error_content = TextContent.{
-
text = "Invalid arguments format";
-
("content", `List [TextContent.yojson_of_t error_content]);
-
("isError", `Bool true)
-
(* Handle tool listing *)
-
("name", `String CapitalizeTool.name);
-
("description", `String CapitalizeTool.description);
-
("inputSchema", CapitalizeTool.input_schema)
-
("tools", `List [tool])
-
(* Handle tool calls *)
-
let call_tool name args =
-
if name = CapitalizeTool.name then
-
CapitalizeTool.call args
-
let error_content = TextContent.{
-
text = Printf.sprintf "Unknown tool: %s" name;
-
("content", `List [TextContent.yojson_of_t error_content]);
-
("isError", `Bool true)
-
(* Handle initialization *)
-
let handle_initialize id json =
-
log_debug (Printf.sprintf "Processing initialize request with id: %s"
-
| `Int i -> string_of_int i
-
log_debug (Printf.sprintf "Initialize params: %s"
-
| Some j -> Yojson.Safe.to_string j
-
let _ = match json with
-
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);
-
log_error "Missing params for initialize request";
-
raise (Json.Of_json ("Missing params for initialize request", `Null))
-
log_debug "Creating initialize response...";
-
let result = Initialize.Result.create
-
~capabilities:server_capabilities
-
~instructions:"This server provides a tool to capitalize text."
-
log_debug "Serializing initialize response...";
-
let response = create_response ~id ~result:(Initialize.Result.yojson_of_t result) in
-
log_debug "Initialize response created successfully";
-
| Json.Of_json (msg, _) ->
-
log_error (Printf.sprintf "JSON error in initialize: %s" msg);
-
create_error ~id ~code:(-32602) ~message:("Invalid params: " ^ msg) ()
-
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 =
-
log_debug (Printf.sprintf "Processing tool call request with id: %s"
-
| `Int i -> string_of_int i
-
log_debug (Printf.sprintf "Tool call params: %s"
-
| Some j -> Yojson.Safe.to_string j
-
| Some (`Assoc params) ->
-
let name = match List.assoc_opt "name" params with
-
| Some (`String name) ->
-
log_debug (Printf.sprintf "Tool name: %s" name);
-
log_error "Missing or invalid 'name' parameter in tool call";
-
raise (Json.Of_json ("Missing or invalid 'name' parameter", `Assoc params))
-
let args = match List.assoc_opt "arguments" params with
-
log_debug (Printf.sprintf "Tool arguments: %s" (Yojson.Safe.to_string args));
-
log_debug "No arguments provided for tool call, using empty object";
-
`Assoc [] (* Empty arguments is valid *)
-
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" ()
-
| Json.Of_json (msg, _) ->
-
log_error (Printf.sprintf "JSON error in tool call: %s" msg);
-
create_error ~id ~code:(-32602) ~message:("Invalid params: " ^ msg) ()
-
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)) ()
-
create_response ~id ~result:(`Assoc [])
-
(* Process a single message *)
-
let process_message message =
-
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
-
log_debug "Processing initialize request";
-
Some (handle_initialize req.id req.params)
-
log_debug "Processing tools/list request";
-
Some (handle_list_tools req.id)
-
log_debug "Processing tools/call request";
-
Some (handle_call_tool req.id req.params)
-
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
-
log_debug (Printf.sprintf "Ignoring notification: %s" notif.method_);
-
| JSONRPCMessage.Response _ ->
-
log_error "Unexpected response message received";
-
| JSONRPCMessage.Error _ ->
-
log_error "Unexpected error message received";
-
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));
-
let rec read_message () =
-
log_debug "Attempting to read line from stdin...";
-
let line = read_line () in
-
log_debug "Empty line received, ignoring";
-
log_debug (Printf.sprintf "Raw input: %s" line);
-
let json = Yojson.Safe.from_string line in
-
log_debug "Successfully parsed JSON";
-
| Yojson.Json_error msg ->
-
log_error (Printf.sprintf "Error parsing JSON: %s" msg);
-
log_error (Printf.sprintf "Input was: %s" line);
-
log_debug "End of file received on stdin";
-
log_error (Printf.sprintf "System error while reading: %s" msg);
-
log_error (Printf.sprintf "Exception while reading: %s" (Printexc.to_string exc));
-
(* 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:";
-
|> Array.iter (fun s ->
-
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)
-
let rec server_loop count =
-
log_debug (Printf.sprintf "Waiting for message #%d..." count);
-
match read_message () with
-
log_debug (Printf.sprintf "Received message: %s" (Yojson.Safe.to_string json));
-
(match process_message json with
-
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;
-
(* Give the client a moment to process the response *)
-
server_loop (count + 1)
-
log_debug "No response needed for this message";
-
server_loop (count + 1))
-
log_debug "End of input stream, terminating server";
-
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...";
-
log_debug "Server terminated normally";
-
log_error "Unexpected end of file";
-
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));
-
log_error (Printf.sprintf "Unhandled exception: %s" (Printexc.to_string exc));
-
log_error (Printf.sprintf "Backtrace: %s" (Printexc.get_backtrace()))