···11+(** Example MCP server using the Server_session API.
22+33+ This example demonstrates how to create a simple MCP server that provides:
44+ - A few example tools (add, echo)
55+ - A simple resource (example://greeting)
66+ - Basic ping support
77+88+ Note: This is a template/demonstration. A real MCP server would typically:
99+ 1. Read JSON-RPC messages from stdin
1010+ 2. Write JSON-RPC responses to stdout
1111+ 3. Use proper error handling and logging
1212+1313+ To use this as a real server, you would need to:
1414+ - Create a custom Transport that reads/writes to stdio
1515+ - Handle process lifecycle properly
1616+ - Add comprehensive error handling
1717+*)
1818+1919+open Mcp
2020+2121+(* Helper to find field in Jsont.Object *)
2222+let find_field name fields =
2323+ List.find_map (fun ((n, _), value) ->
2424+ if n = name then Some value else None
2525+ ) fields
2626+2727+(* Example tool: Add two numbers *)
2828+let handle_add_tool ~name:_ ~arguments =
2929+ let open Messages.Tools in
3030+ match arguments with
3131+ | Some (Jsont.Object (fields, _)) ->
3232+ let a = match find_field "a" fields with
3333+ | Some (Jsont.Number (n, _)) -> n
3434+ | _ -> 0.0
3535+ in
3636+ let b = match find_field "b" fields with
3737+ | Some (Jsont.Number (n, _)) -> n
3838+ | _ -> 0.0
3939+ in
4040+ let result = a +. b in
4141+ let content = [
4242+ Content.text (Printf.sprintf "Result: %.2f" result)
4343+ ] in
4444+ make_call_result ~content ()
4545+ | _ ->
4646+ make_call_result
4747+ ~content:[Content.text "Invalid arguments"]
4848+ ~is_error:true
4949+ ()
5050+5151+(* Example tool: Echo a message *)
5252+let handle_echo_tool ~name:_ ~arguments =
5353+ let open Messages.Tools in
5454+ match arguments with
5555+ | Some (Jsont.Object (fields, _)) ->
5656+ (match find_field "message" fields with
5757+ | Some (Jsont.String (msg, _)) ->
5858+ let content = [Content.text msg] in
5959+ make_call_result ~content ()
6060+ | _ ->
6161+ make_call_result
6262+ ~content:[Content.text "No message provided"]
6363+ ~is_error:true
6464+ ())
6565+ | _ ->
6666+ make_call_result
6767+ ~content:[Content.text "Invalid arguments"]
6868+ ~is_error:true
6969+ ()
7070+7171+(* Tool dispatcher *)
7272+let call_tool ~name ~arguments =
7373+ match name with
7474+ | "add" -> handle_add_tool ~name ~arguments
7575+ | "echo" -> handle_echo_tool ~name ~arguments
7676+ | _ ->
7777+ Messages.Tools.make_call_result
7878+ ~content:[Content.text (Printf.sprintf "Unknown tool: %s" name)]
7979+ ~is_error:true
8080+ ()
8181+8282+(* List available tools *)
8383+let list_tools ~cursor:_ =
8484+ let open Messages.Tools in
8585+ let meta = Jsont.Meta.none in
8686+ let tools = [
8787+ make_tool
8888+ ~name:"add"
8989+ ~description:"Add two numbers together"
9090+ ~input_schema:(Jsont.Object ([
9191+ (("type", meta), Jsont.String ("object", meta));
9292+ (("properties", meta), Jsont.Object ([
9393+ (("a", meta), Jsont.Object ([(("type", meta), Jsont.String ("number", meta))], meta));
9494+ (("b", meta), Jsont.Object ([(("type", meta), Jsont.String ("number", meta))], meta));
9595+ ], meta));
9696+ (("required", meta), Jsont.Array ([Jsont.String ("a", meta); Jsont.String ("b", meta)], meta));
9797+ ], meta))
9898+ ();
9999+ make_tool
100100+ ~name:"echo"
101101+ ~description:"Echo a message back"
102102+ ~input_schema:(Jsont.Object ([
103103+ (("type", meta), Jsont.String ("object", meta));
104104+ (("properties", meta), Jsont.Object ([
105105+ (("message", meta), Jsont.Object ([(("type", meta), Jsont.String ("string", meta))], meta));
106106+ ], meta));
107107+ (("required", meta), Jsont.Array ([Jsont.String ("message", meta)], meta));
108108+ ], meta))
109109+ ();
110110+ ] in
111111+ make_list_result ~tools ()
112112+113113+(* Example resource *)
114114+let read_resource ~uri =
115115+ let open Messages.Resources in
116116+ match uri with
117117+ | "example://greeting" ->
118118+ let contents = [
119119+ make_text_contents
120120+ ~uri
121121+ ~text:"Hello from the MCP server!"
122122+ ~mime_type:"text/plain"
123123+ ()
124124+ ] in
125125+ make_read_result ~contents
126126+ | _ ->
127127+ failwith (Printf.sprintf "Unknown resource: %s" uri)
128128+129129+let list_resources ~cursor:_ =
130130+ let open Messages.Resources in
131131+ let resources = [
132132+ make_resource
133133+ ~uri:"example://greeting"
134134+ ~name:"Greeting"
135135+ ~description:"A simple greeting message"
136136+ ~mime_type:"text/plain"
137137+ ()
138138+ ] in
139139+ make_list_result ~resources ()
140140+141141+(* Ping handler *)
142142+let ping () = ()
143143+144144+(* Main server function *)
145145+let run_server env =
146146+ let open Eio in
147147+148148+ Switch.run @@ fun sw ->
149149+ (* Create stdio transport for the server *)
150150+ (* Note: For a real server, you would typically use a transport that
151151+ reads from stdin and writes to stdout. Here we create a simple
152152+ in-process transport for demonstration. *)
153153+ let params = {
154154+ Transport_stdio.command = "cat"; (* Echo back for demo *)
155155+ args = [];
156156+ env = None;
157157+ max_buffer_size = None;
158158+ } in
159159+ let transport = Transport_stdio.create
160160+ ~sw
161161+ ~process_mgr:(Eio.Stdenv.process_mgr env)
162162+ params
163163+ in
164164+165165+ (* Configure server *)
166166+ let config = {
167167+ Server_session.server_info = Capabilities.Implementation.make
168168+ ~name:"example-mcp-server"
169169+ ~version:"1.0.0";
170170+ server_capabilities = Capabilities.Server.make
171171+ ~tools:(Capabilities.Tools.make ())
172172+ ~resources:(Capabilities.Resources.make ())
173173+ ()
174174+ ;
175175+ instructions = Some "Example MCP server with basic tools and resources";
176176+ } in
177177+178178+ (* Set up handlers *)
179179+ let handlers = {
180180+ Server_session.list_resources = Some list_resources;
181181+ list_resource_templates = None;
182182+ read_resource = Some read_resource;
183183+ subscribe_resource = None;
184184+ unsubscribe_resource = None;
185185+ list_tools = Some list_tools;
186186+ call_tool = Some call_tool;
187187+ list_prompts = None;
188188+ get_prompt = None;
189189+ complete = None;
190190+ ping = Some ping;
191191+ } in
192192+193193+ (* Create and run server *)
194194+ let server = Server_session.create
195195+ ~sw
196196+ ~transport
197197+ config
198198+ handlers
199199+ in
200200+201201+ (* Log server startup *)
202202+ let client_info = Server_session.client_info server in
203203+ Eio.traceln "MCP server started, connected to client: %s v%s"
204204+ client_info.Capabilities.Implementation.name
205205+ client_info.Capabilities.Implementation.version;
206206+207207+ (* The server runs in the background via the Session's receive loop *)
208208+ (* The switch will keep the server alive until it's explicitly closed or an error occurs *)
209209+ (* For this example, we just let it run until the process is terminated *)
210210+ ()
211211+ (* Note: In a real server, you would wait for some termination signal or condition *)
212212+213213+let () =
214214+ Eio_main.run @@ fun env ->
215215+ try
216216+ run_server env
217217+ with
218218+ | exn ->
219219+ Printf.eprintf "Server error: %s\n%!" (Printexc.to_string exn);
220220+ exit 1