···
4
+
(* Logging utilities *)
6
+
Printf.eprintf "[DEBUG] %s\n" msg;
10
+
Printf.eprintf "[ERROR] %s\n" msg;
14
+
let protocol_version = "2024-11-05"
15
+
let server_info = Implementation.{ name = "ocaml-mcp-capitalizer"; version = "0.1.0" }
16
+
let server_capabilities = `Assoc [
17
+
(* We support tools *)
19
+
("listChanged", `Bool true)
21
+
(* We don't support resources - make this explicit *)
22
+
("resources", `Assoc [
23
+
("listChanged", `Bool false);
24
+
("subscribe", `Bool false)
26
+
(* We don't support prompts - make this explicit *)
27
+
("prompts", `Assoc [
28
+
("listChanged", `Bool false)
32
+
(* Tool implementation *)
33
+
module CapitalizeTool = struct
34
+
let name = "capitalize"
35
+
let description = "Capitalizes the provided text"
36
+
let input_schema = `Assoc [
37
+
("type", `String "object");
38
+
("properties", `Assoc [
40
+
("type", `String "string");
41
+
("description", `String "The text to capitalize")
44
+
("required", `List [`String "text"])
50
+
(match List.assoc_opt "text" fields with
51
+
| Some (`String text) ->
52
+
let capitalized_text = String.uppercase_ascii text in
53
+
let content = TextContent.{
54
+
text = capitalized_text;
58
+
("content", `List [TextContent.yojson_of_t content]);
59
+
("isError", `Bool false)
62
+
let error_content = TextContent.{
63
+
text = "Missing or invalid 'text' parameter";
67
+
("content", `List [TextContent.yojson_of_t error_content]);
68
+
("isError", `Bool true)
71
+
let error_content = TextContent.{
72
+
text = "Invalid arguments format";
76
+
("content", `List [TextContent.yojson_of_t error_content]);
77
+
("isError", `Bool true)
81
+
(* Handle tool listing *)
84
+
("name", `String CapitalizeTool.name);
85
+
("description", `String CapitalizeTool.description);
86
+
("inputSchema", CapitalizeTool.input_schema)
89
+
("tools", `List [tool])
92
+
(* Handle tool calls *)
93
+
let call_tool name args =
94
+
if name = CapitalizeTool.name then
95
+
CapitalizeTool.call args
97
+
let error_content = TextContent.{
98
+
text = Printf.sprintf "Unknown tool: %s" name;
102
+
("content", `List [TextContent.yojson_of_t error_content]);
103
+
("isError", `Bool true)
106
+
(* Handle initialization *)
107
+
let handle_initialize id json =
109
+
log_debug (Printf.sprintf "Processing initialize request with id: %s"
111
+
| `Int i -> string_of_int i
112
+
| `String s -> s));
114
+
log_debug (Printf.sprintf "Initialize params: %s"
116
+
| Some j -> Yojson.Safe.to_string j
117
+
| None -> "null"));
119
+
let _ = match json with
121
+
log_debug "Parsing initialize request params...";
122
+
let req = Initialize.Request.t_of_yojson params in
123
+
log_debug (Printf.sprintf "Client info: %s v%s" req.client_info.name req.client_info.version);
124
+
log_debug (Printf.sprintf "Client protocol version: %s" req.protocol_version);
126
+
(* Check protocol version compatibility *)
127
+
if req.protocol_version <> protocol_version then
128
+
log_debug (Printf.sprintf "Protocol version mismatch: client=%s server=%s - will use server version"
129
+
req.protocol_version protocol_version);
133
+
log_error "Missing params for initialize request";
134
+
raise (Json.Of_json ("Missing params for initialize request", `Null))
137
+
log_debug "Creating initialize response...";
138
+
let result = Initialize.Result.create
139
+
~capabilities:server_capabilities
142
+
~instructions:"This server provides a tool to capitalize text."
146
+
log_debug "Serializing initialize response...";
147
+
let response = create_response ~id ~result:(Initialize.Result.yojson_of_t result) in
148
+
log_debug "Initialize response created successfully";
151
+
| Json.Of_json (msg, _) ->
152
+
log_error (Printf.sprintf "JSON error in initialize: %s" msg);
153
+
create_error ~id ~code:(-32602) ~message:("Invalid params: " ^ msg) ()
155
+
log_error (Printf.sprintf "Exception in initialize: %s" (Printexc.to_string exc));
156
+
log_error (Printf.sprintf "Backtrace: %s" (Printexc.get_backtrace()));
157
+
create_error ~id ~code:(-32603) ~message:("Internal error: " ^ (Printexc.to_string exc)) ()
159
+
(* Handle tools/list *)
160
+
let handle_list_tools id =
161
+
log_debug "Processing tools/list request";
162
+
let result = list_tools () in
163
+
log_debug (Printf.sprintf "Tools list result: %s" (Yojson.Safe.to_string result));
164
+
create_response ~id ~result
166
+
(* Handle tools/call *)
167
+
let handle_call_tool id json =
169
+
log_debug (Printf.sprintf "Processing tool call request with id: %s"
171
+
| `Int i -> string_of_int i
172
+
| `String s -> s));
174
+
log_debug (Printf.sprintf "Tool call params: %s"
176
+
| Some j -> Yojson.Safe.to_string j
177
+
| None -> "null"));
180
+
| Some (`Assoc params) ->
181
+
let name = match List.assoc_opt "name" params with
182
+
| Some (`String name) ->
183
+
log_debug (Printf.sprintf "Tool name: %s" name);
186
+
log_error "Missing or invalid 'name' parameter in tool call";
187
+
raise (Json.Of_json ("Missing or invalid 'name' parameter", `Assoc params))
189
+
let args = match List.assoc_opt "arguments" params with
191
+
log_debug (Printf.sprintf "Tool arguments: %s" (Yojson.Safe.to_string args));
194
+
log_debug "No arguments provided for tool call, using empty object";
195
+
`Assoc [] (* Empty arguments is valid *)
197
+
log_debug (Printf.sprintf "Calling tool: %s" name);
198
+
let result = call_tool name args in
199
+
log_debug (Printf.sprintf "Tool call result: %s" (Yojson.Safe.to_string result));
200
+
create_response ~id ~result
202
+
log_error "Invalid params format for tools/call";
203
+
create_error ~id ~code:(-32602) ~message:"Invalid params for tools/call" ()
205
+
| Json.Of_json (msg, _) ->
206
+
log_error (Printf.sprintf "JSON error in tool call: %s" msg);
207
+
create_error ~id ~code:(-32602) ~message:("Invalid params: " ^ msg) ()
209
+
log_error (Printf.sprintf "Exception in tool call: %s" (Printexc.to_string exc));
210
+
log_error (Printf.sprintf "Backtrace: %s" (Printexc.get_backtrace()));
211
+
create_error ~id ~code:(-32603) ~message:("Internal error: " ^ (Printexc.to_string exc)) ()
214
+
let handle_ping id =
215
+
create_response ~id ~result:(`Assoc [])
217
+
(* Process a single message *)
218
+
let process_message message =
220
+
log_debug "Parsing message as JSONRPC message...";
221
+
match JSONRPCMessage.t_of_yojson message with
222
+
| JSONRPCMessage.Request req ->
223
+
log_debug (Printf.sprintf "Received request with method: %s" req.method_);
224
+
(match req.method_ with
226
+
log_debug "Processing initialize request";
227
+
Some (handle_initialize req.id req.params)
229
+
log_debug "Processing tools/list request";
230
+
Some (handle_list_tools req.id)
232
+
log_debug "Processing tools/call request";
233
+
Some (handle_call_tool req.id req.params)
235
+
log_debug "Processing ping request";
236
+
Some (handle_ping req.id)
238
+
log_error (Printf.sprintf "Unknown method received: %s" req.method_);
239
+
Some (create_error ~id:req.id ~code:(-32601) ~message:("Method not found: " ^ req.method_) ()))
240
+
| JSONRPCMessage.Notification notif ->
241
+
log_debug (Printf.sprintf "Received notification with method: %s" notif.method_);
242
+
(match notif.method_ with
243
+
| "notifications/initialized" ->
244
+
log_debug "Client initialization complete - Server is now ready to receive requests";
245
+
log_debug (Printf.sprintf "Notification params: %s"
246
+
(match notif.params with
247
+
| Some p -> Yojson.Safe.to_string p
248
+
| None -> "null"));
251
+
log_debug (Printf.sprintf "Ignoring notification: %s" notif.method_);
253
+
| JSONRPCMessage.Response _ ->
254
+
log_error "Unexpected response message received";
256
+
| JSONRPCMessage.Error _ ->
257
+
log_error "Unexpected error message received";
261
+
log_error (Printf.sprintf "Exception during message processing: %s" (Printexc.to_string exc));
262
+
log_error (Printf.sprintf "Backtrace: %s" (Printexc.get_backtrace()));
263
+
log_error (Printf.sprintf "Message was: %s" (Yojson.Safe.to_string message));
267
+
let rec read_message () =
269
+
log_debug "Attempting to read line from stdin...";
270
+
let line = read_line () in
271
+
if line = "" then (
272
+
log_debug "Empty line received, ignoring";
275
+
log_debug (Printf.sprintf "Raw input: %s" line);
277
+
let json = Yojson.Safe.from_string line in
278
+
log_debug "Successfully parsed JSON";
281
+
| Yojson.Json_error msg ->
282
+
log_error (Printf.sprintf "Error parsing JSON: %s" msg);
283
+
log_error (Printf.sprintf "Input was: %s" line);
288
+
log_debug "End of file received on stdin";
291
+
log_error (Printf.sprintf "System error while reading: %s" msg);
294
+
log_error (Printf.sprintf "Exception while reading: %s" (Printexc.to_string exc));
299
+
(* Enable exception backtraces *)
300
+
Printexc.record_backtrace true;
302
+
(* Enable line buffering for stdout *)
303
+
set_binary_mode_out stdout false;
305
+
log_debug "MCP Capitalizer server started";
306
+
log_debug (Printf.sprintf "Protocol version: %s" protocol_version);
307
+
log_debug (Printf.sprintf "Server info: %s v%s" server_info.name server_info.version);
309
+
(* Print environment info for debugging *)
310
+
log_debug "Environment variables:";
312
+
|> Array.iter (fun s ->
314
+
let i = String.index s '=' in
315
+
let name = String.sub s 0 i in
316
+
if String.length name > 0 then
317
+
log_debug (Printf.sprintf " %s" s)
318
+
with Not_found -> ()
321
+
let rec server_loop count =
322
+
log_debug (Printf.sprintf "Waiting for message #%d..." count);
323
+
match read_message () with
325
+
log_debug (Printf.sprintf "Received message: %s" (Yojson.Safe.to_string json));
326
+
(match process_message json with
328
+
let response_json = JSONRPCMessage.yojson_of_t response in
329
+
let response_str = Yojson.Safe.to_string response_json in
330
+
log_debug (Printf.sprintf "Sending response: %s" response_str);
331
+
(* Make sure we emit properly formatted JSON on a single line with a newline at the end *)
332
+
Printf.printf "%s\n" response_str;
334
+
(* Give the client a moment to process the response *)
336
+
server_loop (count + 1)
338
+
log_debug "No response needed for this message";
339
+
server_loop (count + 1))
341
+
log_debug "End of input stream, terminating server";
345
+
log_debug "Starting server loop...";
346
+
log_debug "Waiting for the initialize request...";
348
+
(* Set up signal handler to gracefully exit *)
349
+
Sys.(set_signal sigint (Signal_handle (fun _ ->
350
+
log_debug "Received interrupt signal, exiting...";
355
+
log_debug "Server terminated normally";
358
+
log_error "Unexpected end of file";
360
+
log_error (Printf.sprintf "System error: %s" msg);
361
+
| Unix.Unix_error(err, func, arg) ->
362
+
log_error (Printf.sprintf "Unix error in %s(%s): %s" func arg (Unix.error_message err));
364
+
log_error (Printf.sprintf "Unhandled exception: %s" (Printexc.to_string exc));
365
+
log_error (Printf.sprintf "Backtrace: %s" (Printexc.get_backtrace()))