···
+
(* Logging utilities *)
+
type level = Debug | Info | Warning | Error
+
let string_of_level = function
+
Printf.eprintf "[%s] %s\n" (string_of_level level) msg;
+
Printf.printf "[%s] %s\n" (string_of_level level) msg;
+
let warning = log Warning
+
(* Context for tools and resources *)
+
module Context = struct
+
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
+
let report_progress ctx value total =
+
match ctx.progress_token, ctx.request_id with
+
| Some token, Some id ->
+
("progress", `Float value);
+
("total", `Float total);
+
("progressToken", ProgressToken.yojson_of_t token)
+
Some (create_notification ~method_:"notifications/progress" ~params:(Some params) ())
+
(* Tools for the MCP server *)
+
type handler = Context.t -> Json.t -> (Json.t, string) result
+
description: string option;
+
input_schema: Json.t; (* JSON Schema *)
+
let create ~name ?description ~input_schema ~handler () =
+
{ name; description; input_schema; handler }
+
("name", `String tool.name);
+
("inputSchema", tool.input_schema);
+
let assoc = match tool.description with
+
| Some desc -> ("description", `String desc) :: assoc
+
(* Resources for the MCP server *)
+
module Resource = struct
+
type handler = Context.t -> string list -> (string, string) result
+
description: string option;
+
mime_type: string option;
+
let create ~uri_template ?description ?mime_type ~handler () =
+
{ uri_template; description; mime_type; handler }
+
("uriTemplate", `String resource.uri_template);
+
let assoc = match resource.description with
+
| Some desc -> ("description", `String desc) :: assoc
+
let assoc = match resource.mime_type with
+
| Some mime -> ("mimeType", `String mime) :: assoc
+
(* Prompts for the MCP server *)
+
description: string option;
+
type handler = Context.t -> (string * string) list -> (message list, string) result
+
description: string option;
+
arguments: argument list;
+
let create ~name ?description ?(arguments=[]) ~handler () =
+
{ name; description; arguments; handler }
+
let create_argument ~name ?description ?(required=false) () =
+
{ name; description; required }
+
("name", `String prompt.name);
+
let assoc = match prompt.description with
+
| Some desc -> ("description", `String desc) :: assoc
+
let assoc = if prompt.arguments <> [] then
+
let args = List.map (fun (arg: argument) ->
+
("name", `String arg.name);
+
let arg_assoc = match arg.description with
+
| Some desc -> ("description", `String desc) :: arg_assoc
+
("required", `Bool true) :: arg_assoc
+
("arguments", `List args) :: assoc
+
(* Helper functions for creating common objects *)
+
let make_text_content text =
+
Text (TextContent.{ text; annotations = None })
+
let make_tool_schema properties required =
+
let props = List.map (fun (name, schema_type, description) ->
+
("type", `String schema_type);
+
("description", `String description)
+
let required_json = `List (List.map (fun name -> `String name) required) in
+
("type", `String "object");
+
("properties", `Assoc props);
+
("required", required_json)
+
(* Server implementation *)
+
type startup_hook = unit -> unit
+
type shutdown_hook = unit -> unit
+
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;
+
startup_hook: startup_hook option;
+
shutdown_hook: shutdown_hook option;
+
let create ~name ?(version="0.1.0") ?(protocol_version="2024-11-05") ?startup_hook ?shutdown_hook () =
+
capabilities = `Assoc [];
+
let register_tool server tool =
+
server.tools <- tool :: server.tools;
+
(* Register a resource *)
+
let register_resource server resource =
+
server.resources <- resource :: server.resources;
+
(* Register a prompt *)
+
let register_prompt server prompt =
+
server.prompts <- prompt :: server.prompts;
+
(* Default server capabilities *)
+
let default_capabilities ?(with_tools=true) ?(with_resources=false) ?(with_prompts=false) () =
+
("listChanged", `Bool true)
+
("listChanged", `Bool true);
+
("subscribe", `Bool false)
+
else if not with_resources then
+
("listChanged", `Bool false);
+
("subscribe", `Bool false)
+
("listChanged", `Bool true)
+
else if not with_prompts then
+
("listChanged", `Bool false)
+
(* Update server capabilities *)
+
let update_capabilities server capabilities =
+
server.capabilities <- capabilities
+
(* Process a message *)
+
let process_message _server _json =
+
(* Placeholder implementation *)
+
(* Helper function for default capabilities *)
+
let default_capabilities = Server.default_capabilities
+
(* Add syntactic sugar for creating a server *)
+
module MakeServer(S: sig val name: string val version: string option end) = struct
+
let _config = (S.name, S.version) (* Used to prevent unused parameter warning *)
+
let server = Server.create
+
~protocol_version:"2024-11-05"
+
let tool ?name ?description ?(schema_properties=[]) ?(schema_required=[]) handler =
+
let name = match name with
+
| Some None | None -> "tool" in
+
let input_schema = make_tool_schema schema_properties schema_required in
+
let handler' ctx args =
+
Error (Printexc.to_string exn)
+
server.tools <- tool :: server.tools;
+
(* Create a resource *)
+
let resource ?uri_template ?description ?mime_type handler =
+
let uri_template = match uri_template with
+
| Some (Some uri) -> uri
+
| Some None | None -> "resource://" in
+
let handler' ctx params =
+
Error (Printexc.to_string exn)
+
let resource = Resource.create
+
server.resources <- resource :: server.resources;
+
let prompt ?name ?description ?(arguments=[]) handler =
+
let name = match name with
+
| Some None | None -> "prompt" in
+
let prompt_args = List.map (fun (name, desc, required) ->
+
Prompt.create_argument ~name ?description:desc ~required ()
+
let handler' ctx args =
+
Error (Printexc.to_string exn)
+
let prompt = Prompt.create
+
server.prompts <- prompt :: server.prompts;
+
let run ?with_tools ?with_resources ?with_prompts () =
+
let with_tools = match with_tools with
+
| None -> server.tools <> []
+
let with_resources = match with_resources with
+
| None -> server.resources <> []
+
let with_prompts = match with_prompts with
+
| None -> server.prompts <> []
+
let capabilities = Server.default_capabilities ~with_tools ~with_resources ~with_prompts () in
+
server.capabilities <- capabilities;
+
Log.info "Starting server...";
+
Log.info (Printf.sprintf "Server info: %s v%s" server.name
+
(match S.version with Some v -> v | None -> "unknown"));
+
Printexc.record_backtrace true;
+
set_binary_mode_out stdout false;
+
Log.info "This is just a placeholder server implementation."