···
+
(** Example MCP server using the Server_session API.
+
This example demonstrates how to create a simple MCP server that provides:
+
- A few example tools (add, echo)
+
- A simple resource (example://greeting)
+
Note: This is a template/demonstration. A real MCP server would typically:
+
1. Read JSON-RPC messages from stdin
+
2. Write JSON-RPC responses to stdout
+
3. Use proper error handling and logging
+
To use this as a real server, you would need to:
+
- Create a custom Transport that reads/writes to stdio
+
- Handle process lifecycle properly
+
- Add comprehensive error handling
+
(* Helper to find field in Jsont.Object *)
+
let find_field name fields =
+
List.find_map (fun ((n, _), value) ->
+
if n = name then Some value else None
+
(* Example tool: Add two numbers *)
+
let handle_add_tool ~name:_ ~arguments =
+
let open Messages.Tools in
+
| Some (Jsont.Object (fields, _)) ->
+
let a = match find_field "a" fields with
+
| Some (Jsont.Number (n, _)) -> n
+
let b = match find_field "b" fields with
+
| Some (Jsont.Number (n, _)) -> n
+
Content.text (Printf.sprintf "Result: %.2f" result)
+
make_call_result ~content ()
+
~content:[Content.text "Invalid arguments"]
+
(* Example tool: Echo a message *)
+
let handle_echo_tool ~name:_ ~arguments =
+
let open Messages.Tools in
+
| Some (Jsont.Object (fields, _)) ->
+
(match find_field "message" fields with
+
| Some (Jsont.String (msg, _)) ->
+
let content = [Content.text msg] in
+
make_call_result ~content ()
+
~content:[Content.text "No message provided"]
+
~content:[Content.text "Invalid arguments"]
+
let call_tool ~name ~arguments =
+
| "add" -> handle_add_tool ~name ~arguments
+
| "echo" -> handle_echo_tool ~name ~arguments
+
Messages.Tools.make_call_result
+
~content:[Content.text (Printf.sprintf "Unknown tool: %s" name)]
+
(* List available tools *)
+
let list_tools ~cursor:_ =
+
let open Messages.Tools in
+
let meta = Jsont.Meta.none in
+
~description:"Add two numbers together"
+
~input_schema:(Jsont.Object ([
+
(("type", meta), Jsont.String ("object", meta));
+
(("properties", meta), Jsont.Object ([
+
(("a", meta), Jsont.Object ([(("type", meta), Jsont.String ("number", meta))], meta));
+
(("b", meta), Jsont.Object ([(("type", meta), Jsont.String ("number", meta))], meta));
+
(("required", meta), Jsont.Array ([Jsont.String ("a", meta); Jsont.String ("b", meta)], meta));
+
~description:"Echo a message back"
+
~input_schema:(Jsont.Object ([
+
(("type", meta), Jsont.String ("object", meta));
+
(("properties", meta), Jsont.Object ([
+
(("message", meta), Jsont.Object ([(("type", meta), Jsont.String ("string", meta))], meta));
+
(("required", meta), Jsont.Array ([Jsont.String ("message", meta)], meta));
+
make_list_result ~tools ()
+
let read_resource ~uri =
+
let open Messages.Resources in
+
| "example://greeting" ->
+
~text:"Hello from the MCP server!"
+
~mime_type:"text/plain"
+
make_read_result ~contents
+
failwith (Printf.sprintf "Unknown resource: %s" uri)
+
let list_resources ~cursor:_ =
+
let open Messages.Resources in
+
~uri:"example://greeting"
+
~description:"A simple greeting message"
+
~mime_type:"text/plain"
+
make_list_result ~resources ()
+
(* Main server function *)
+
Switch.run @@ fun sw ->
+
(* Create stdio transport for the server *)
+
(* Note: For a real server, you would typically use a transport that
+
reads from stdin and writes to stdout. Here we create a simple
+
in-process transport for demonstration. *)
+
Transport_stdio.command = "cat"; (* Echo back for demo *)
+
max_buffer_size = None;
+
let transport = Transport_stdio.create
+
~process_mgr:(Eio.Stdenv.process_mgr env)
+
Server_session.server_info = Capabilities.Implementation.make
+
~name:"example-mcp-server"
+
server_capabilities = Capabilities.Server.make
+
~tools:(Capabilities.Tools.make ())
+
~resources:(Capabilities.Resources.make ())
+
instructions = Some "Example MCP server with basic tools and resources";
+
Server_session.list_resources = Some list_resources;
+
list_resource_templates = None;
+
read_resource = Some read_resource;
+
subscribe_resource = None;
+
unsubscribe_resource = None;
+
list_tools = Some list_tools;
+
call_tool = Some call_tool;
+
(* Create and run server *)
+
let server = Server_session.create
+
(* Log server startup *)
+
let client_info = Server_session.client_info server in
+
Eio.traceln "MCP server started, connected to client: %s v%s"
+
client_info.Capabilities.Implementation.name
+
client_info.Capabilities.Implementation.version;
+
(* The server runs in the background via the Session's receive loop *)
+
(* The switch will keep the server alive until it's explicitly closed or an error occurs *)
+
(* For this example, we just let it run until the process is terminated *)
+
(* Note: In a real server, you would wait for some termination signal or condition *)
+
Eio_main.run @@ fun env ->
+
Printf.eprintf "Server error: %s\n%!" (Printexc.to_string exn);