···
5
+
let version = "0.1.0"
7
+
(* Logging utilities *)
9
+
type level = Debug | Info | Warning | Error
11
+
let string_of_level = function
14
+
| Warning -> "WARNING"
18
+
Printf.eprintf "[%s] %s\n" (string_of_level level) msg;
20
+
Printf.printf "[%s] %s\n" (string_of_level level) msg;
23
+
let debug = log Debug
25
+
let warning = log Warning
26
+
let error = log Error
29
+
(* Context for tools and resources *)
30
+
module Context = struct
32
+
request_id: RequestId.t option;
33
+
lifespan_context: (string * Json.t) list;
34
+
mutable progress_token: ProgressToken.t option;
37
+
let create ?request_id ?(lifespan_context=[]) () =
38
+
{ request_id; lifespan_context; progress_token = None }
40
+
let get_context_value ctx key =
41
+
List.assoc_opt key ctx.lifespan_context
43
+
let report_progress ctx value total =
44
+
match ctx.progress_token, ctx.request_id with
45
+
| Some token, Some id ->
46
+
let params = `Assoc [
47
+
("progress", `Float value);
48
+
("total", `Float total);
49
+
("progressToken", ProgressToken.yojson_of_t token)
51
+
Some (create_notification ~method_:"notifications/progress" ~params:(Some params) ())
55
+
(* Tools for the MCP server *)
56
+
module Tool = struct
57
+
type handler = Context.t -> Json.t -> (Json.t, string) result
61
+
description: string option;
62
+
input_schema: Json.t; (* JSON Schema *)
66
+
let create ~name ?description ~input_schema ~handler () =
67
+
{ name; description; input_schema; handler }
71
+
("name", `String tool.name);
72
+
("inputSchema", tool.input_schema);
74
+
let assoc = match tool.description with
75
+
| Some desc -> ("description", `String desc) :: assoc
81
+
(* Resources for the MCP server *)
82
+
module Resource = struct
83
+
type handler = Context.t -> string list -> (string, string) result
86
+
uri_template: string;
87
+
description: string option;
88
+
mime_type: string option;
92
+
let create ~uri_template ?description ?mime_type ~handler () =
93
+
{ uri_template; description; mime_type; handler }
95
+
let to_json resource =
97
+
("uriTemplate", `String resource.uri_template);
99
+
let assoc = match resource.description with
100
+
| Some desc -> ("description", `String desc) :: assoc
103
+
let assoc = match resource.mime_type with
104
+
| Some mime -> ("mimeType", `String mime) :: assoc
110
+
(* Prompts for the MCP server *)
111
+
module Prompt = struct
114
+
description: string option;
123
+
type handler = Context.t -> (string * string) list -> (message list, string) result
127
+
description: string option;
128
+
arguments: argument list;
132
+
let create ~name ?description ?(arguments=[]) ~handler () =
133
+
{ name; description; arguments; handler }
135
+
let create_argument ~name ?description ?(required=false) () =
136
+
{ name; description; required }
138
+
let to_json prompt =
140
+
("name", `String prompt.name);
142
+
let assoc = match prompt.description with
143
+
| Some desc -> ("description", `String desc) :: assoc
146
+
let assoc = if prompt.arguments <> [] then
147
+
let args = List.map (fun (arg: argument) ->
149
+
("name", `String arg.name);
151
+
let arg_assoc = match arg.description with
152
+
| Some desc -> ("description", `String desc) :: arg_assoc
153
+
| None -> arg_assoc
156
+
if arg.required then
157
+
("required", `Bool true) :: arg_assoc
162
+
) prompt.arguments in
163
+
("arguments", `List args) :: assoc
170
+
(* Helper functions for creating common objects *)
171
+
let make_text_content text =
172
+
Text (TextContent.{ text; annotations = None })
174
+
let make_tool_schema properties required =
175
+
let props = List.map (fun (name, schema_type, description) ->
177
+
("type", `String schema_type);
178
+
("description", `String description)
181
+
let required_json = `List (List.map (fun name -> `String name) required) in
183
+
("type", `String "object");
184
+
("properties", `Assoc props);
185
+
("required", required_json)
188
+
(* Server implementation *)
189
+
module Server = struct
190
+
type startup_hook = unit -> unit
191
+
type shutdown_hook = unit -> unit
196
+
protocol_version: string;
197
+
mutable capabilities: Json.t;
198
+
mutable tools: Tool.t list;
199
+
mutable resources: Resource.t list;
200
+
mutable prompts: Prompt.t list;
201
+
mutable lifespan_context: (string * Json.t) list;
202
+
startup_hook: startup_hook option;
203
+
shutdown_hook: shutdown_hook option;
206
+
let create ~name ?(version="0.1.0") ?(protocol_version="2024-11-05") ?startup_hook ?shutdown_hook () =
211
+
capabilities = `Assoc [];
215
+
lifespan_context = [];
220
+
(* Register a tool *)
221
+
let register_tool server tool =
222
+
server.tools <- tool :: server.tools;
225
+
(* Register a resource *)
226
+
let register_resource server resource =
227
+
server.resources <- resource :: server.resources;
230
+
(* Register a prompt *)
231
+
let register_prompt server prompt =
232
+
server.prompts <- prompt :: server.prompts;
235
+
(* Default server capabilities *)
236
+
let default_capabilities ?(with_tools=true) ?(with_resources=false) ?(with_prompts=false) () =
241
+
("listChanged", `Bool true)
247
+
if with_resources then
248
+
("resources", `Assoc [
249
+
("listChanged", `Bool true);
250
+
("subscribe", `Bool false)
252
+
else if not with_resources then
253
+
("resources", `Assoc [
254
+
("listChanged", `Bool false);
255
+
("subscribe", `Bool false)
261
+
if with_prompts then
262
+
("prompts", `Assoc [
263
+
("listChanged", `Bool true)
265
+
else if not with_prompts then
266
+
("prompts", `Assoc [
267
+
("listChanged", `Bool false)
274
+
(* Update server capabilities *)
275
+
let update_capabilities server capabilities =
276
+
server.capabilities <- capabilities
278
+
(* Process a message *)
279
+
let process_message _server _json =
282
+
(* Main server loop *)
284
+
(* Placeholder implementation *)
288
+
(* Helper function for default capabilities *)
289
+
let default_capabilities = Server.default_capabilities
291
+
(* Add syntactic sugar for creating a server *)
292
+
module MakeServer(S: sig val name: string val version: string option end) = struct
293
+
let _config = (S.name, S.version) (* Used to prevent unused parameter warning *)
295
+
(* Create server *)
296
+
let server = Server.create
299
+
~protocol_version:"2024-11-05"
302
+
(* Create a tool *)
303
+
let tool ?name ?description ?(schema_properties=[]) ?(schema_required=[]) handler =
304
+
let name = match name with
305
+
| Some (Some n) -> n
306
+
| Some None | None -> "tool" in
307
+
let input_schema = make_tool_schema schema_properties schema_required in
308
+
let handler' ctx args =
312
+
Error (Printexc.to_string exn)
314
+
let tool = Tool.create
321
+
server.tools <- tool :: server.tools;
324
+
(* Create a resource *)
325
+
let resource ?uri_template ?description ?mime_type handler =
326
+
let uri_template = match uri_template with
327
+
| Some (Some uri) -> uri
328
+
| Some None | None -> "resource://" in
329
+
let handler' ctx params =
331
+
Ok (handler params)
333
+
Error (Printexc.to_string exn)
335
+
let resource = Resource.create
342
+
server.resources <- resource :: server.resources;
345
+
(* Create a prompt *)
346
+
let prompt ?name ?description ?(arguments=[]) handler =
347
+
let name = match name with
348
+
| Some (Some n) -> n
349
+
| Some None | None -> "prompt" in
350
+
let prompt_args = List.map (fun (name, desc, required) ->
351
+
Prompt.create_argument ~name ?description:desc ~required ()
353
+
let handler' ctx args =
357
+
Error (Printexc.to_string exn)
359
+
let prompt = Prompt.create
362
+
~arguments:prompt_args
366
+
server.prompts <- prompt :: server.prompts;
369
+
(* Run the server *)
370
+
let run ?with_tools ?with_resources ?with_prompts () =
371
+
let with_tools = match with_tools with
373
+
| None -> server.tools <> []
375
+
let with_resources = match with_resources with
377
+
| None -> server.resources <> []
379
+
let with_prompts = match with_prompts with
381
+
| None -> server.prompts <> []
383
+
let capabilities = Server.default_capabilities ~with_tools ~with_resources ~with_prompts () in
384
+
server.capabilities <- capabilities;
385
+
Log.info "Starting server...";
386
+
Log.info (Printf.sprintf "Server info: %s v%s" server.name
387
+
(match S.version with Some v -> v | None -> "unknown"));
388
+
Printexc.record_backtrace true;
389
+
set_binary_mode_out stdout false;
390
+
Log.info "This is just a placeholder server implementation."