My agentic slop goes here. Not intended for anyone else!
1(** Example MCP server using the Server_session API.
2
3 This example demonstrates how to create a simple MCP server that provides:
4 - A few example tools (add, echo)
5 - A simple resource (example://greeting)
6 - Basic ping support
7
8 Note: This is a template/demonstration. A real MCP server would typically:
9 1. Read JSON-RPC messages from stdin
10 2. Write JSON-RPC responses to stdout
11 3. Use proper error handling and logging
12
13 To use this as a real server, you would need to:
14 - Create a custom Transport that reads/writes to stdio
15 - Handle process lifecycle properly
16 - Add comprehensive error handling
17*)
18
19open Mcp
20
21(* Helper to find field in Jsont.Object *)
22let find_field name fields =
23 List.find_map (fun ((n, _), value) ->
24 if n = name then Some value else None
25 ) fields
26
27(* Example tool: Add two numbers *)
28let handle_add_tool ~name:_ ~arguments =
29 let open Messages.Tools in
30 match arguments with
31 | Some (Jsont.Object (fields, _)) ->
32 let a = match find_field "a" fields with
33 | Some (Jsont.Number (n, _)) -> n
34 | _ -> 0.0
35 in
36 let b = match find_field "b" fields with
37 | Some (Jsont.Number (n, _)) -> n
38 | _ -> 0.0
39 in
40 let result = a +. b in
41 let content = [
42 Content.text (Printf.sprintf "Result: %.2f" result)
43 ] in
44 make_call_result ~content ()
45 | _ ->
46 make_call_result
47 ~content:[Content.text "Invalid arguments"]
48 ~is_error:true
49 ()
50
51(* Example tool: Echo a message *)
52let handle_echo_tool ~name:_ ~arguments =
53 let open Messages.Tools in
54 match arguments with
55 | Some (Jsont.Object (fields, _)) ->
56 (match find_field "message" fields with
57 | Some (Jsont.String (msg, _)) ->
58 let content = [Content.text msg] in
59 make_call_result ~content ()
60 | _ ->
61 make_call_result
62 ~content:[Content.text "No message provided"]
63 ~is_error:true
64 ())
65 | _ ->
66 make_call_result
67 ~content:[Content.text "Invalid arguments"]
68 ~is_error:true
69 ()
70
71(* Tool dispatcher *)
72let call_tool ~name ~arguments =
73 match name with
74 | "add" -> handle_add_tool ~name ~arguments
75 | "echo" -> handle_echo_tool ~name ~arguments
76 | _ ->
77 Messages.Tools.make_call_result
78 ~content:[Content.text (Printf.sprintf "Unknown tool: %s" name)]
79 ~is_error:true
80 ()
81
82(* List available tools *)
83let list_tools ~cursor:_ =
84 let open Messages.Tools in
85 let meta = Jsont.Meta.none in
86 let tools = [
87 make_tool
88 ~name:"add"
89 ~description:"Add two numbers together"
90 ~input_schema:(Jsont.Object ([
91 (("type", meta), Jsont.String ("object", meta));
92 (("properties", meta), Jsont.Object ([
93 (("a", meta), Jsont.Object ([(("type", meta), Jsont.String ("number", meta))], meta));
94 (("b", meta), Jsont.Object ([(("type", meta), Jsont.String ("number", meta))], meta));
95 ], meta));
96 (("required", meta), Jsont.Array ([Jsont.String ("a", meta); Jsont.String ("b", meta)], meta));
97 ], meta))
98 ();
99 make_tool
100 ~name:"echo"
101 ~description:"Echo a message back"
102 ~input_schema:(Jsont.Object ([
103 (("type", meta), Jsont.String ("object", meta));
104 (("properties", meta), Jsont.Object ([
105 (("message", meta), Jsont.Object ([(("type", meta), Jsont.String ("string", meta))], meta));
106 ], meta));
107 (("required", meta), Jsont.Array ([Jsont.String ("message", meta)], meta));
108 ], meta))
109 ();
110 ] in
111 make_list_result ~tools ()
112
113(* Example resource *)
114let read_resource ~uri =
115 let open Messages.Resources in
116 match uri with
117 | "example://greeting" ->
118 let contents = [
119 make_text_contents
120 ~uri
121 ~text:"Hello from the MCP server!"
122 ~mime_type:"text/plain"
123 ()
124 ] in
125 make_read_result ~contents
126 | _ ->
127 failwith (Printf.sprintf "Unknown resource: %s" uri)
128
129let list_resources ~cursor:_ =
130 let open Messages.Resources in
131 let resources = [
132 make_resource
133 ~uri:"example://greeting"
134 ~name:"Greeting"
135 ~description:"A simple greeting message"
136 ~mime_type:"text/plain"
137 ()
138 ] in
139 make_list_result ~resources ()
140
141(* Ping handler *)
142let ping () = ()
143
144(* Main server function *)
145let run_server env =
146 let open Eio in
147
148 Switch.run @@ fun sw ->
149 (* Create stdio transport for the server *)
150 (* Note: For a real server, you would typically use a transport that
151 reads from stdin and writes to stdout. Here we create a simple
152 in-process transport for demonstration. *)
153 let params = {
154 Transport_stdio.command = "cat"; (* Echo back for demo *)
155 args = [];
156 env = None;
157 max_buffer_size = None;
158 } in
159 let transport = Transport_stdio.create
160 ~sw
161 ~process_mgr:(Eio.Stdenv.process_mgr env)
162 params
163 in
164
165 (* Configure server *)
166 let config = {
167 Server_session.server_info = Capabilities.Implementation.make
168 ~name:"example-mcp-server"
169 ~version:"1.0.0";
170 server_capabilities = Capabilities.Server.make
171 ~tools:(Capabilities.Tools.make ())
172 ~resources:(Capabilities.Resources.make ())
173 ()
174 ;
175 instructions = Some "Example MCP server with basic tools and resources";
176 } in
177
178 (* Set up handlers *)
179 let handlers = {
180 Server_session.list_resources = Some list_resources;
181 list_resource_templates = None;
182 read_resource = Some read_resource;
183 subscribe_resource = None;
184 unsubscribe_resource = None;
185 list_tools = Some list_tools;
186 call_tool = Some call_tool;
187 list_prompts = None;
188 get_prompt = None;
189 complete = None;
190 ping = Some ping;
191 } in
192
193 (* Create and run server *)
194 let server = Server_session.create
195 ~sw
196 ~transport
197 config
198 handlers
199 in
200
201 (* Log server startup *)
202 let client_info = Server_session.client_info server in
203 Eio.traceln "MCP server started, connected to client: %s v%s"
204 client_info.Capabilities.Implementation.name
205 client_info.Capabilities.Implementation.version;
206
207 (* The server runs in the background via the Session's receive loop *)
208 (* The switch will keep the server alive until it's explicitly closed or an error occurs *)
209 (* For this example, we just let it run until the process is terminated *)
210 ()
211 (* Note: In a real server, you would wait for some termination signal or condition *)
212
213let () =
214 Eio_main.run @@ fun env ->
215 try
216 run_server env
217 with
218 | exn ->
219 Printf.eprintf "Server error: %s\n%!" (Printexc.to_string exn);
220 exit 1