Model Context Protocol in OCaml
1open Mcp
2open Mcp_sdk
3open Mcp_message
4
5(* Create a server *)
6let server = create_server
7 ~name:"OCaml MCP Structured API Demo"
8 ~version:"0.1.0"
9 ~protocol_version:"2024-11-05" () |>
10 fun server ->
11 (* Set default capabilities *)
12 configure_server server ~with_tools:true ~with_resources:true ~with_prompts:true ()
13
14(* Helper for extracting string value from JSON *)
15let get_string_param json name =
16 match json with
17 | `Assoc fields ->
18 (match List.assoc_opt name fields with
19 | Some (`String value) -> value
20 | _ -> raise (Failure (Printf.sprintf "Missing or invalid parameter: %s" name)))
21 | _ -> raise (Failure "Expected JSON object")
22
23(* Register a ping tool that demonstrates the typed API *)
24let _ = add_tool server
25 ~name:"ping"
26 ~description:"A simple ping tool that demonstrates the structured API"
27 ~schema_properties:[
28 ("message", "string", "The message to echo back")
29 ]
30 ~schema_required:["message"]
31 (fun args ->
32 try
33 let message = get_string_param args "message" in
34 (* Create a typed tool response using the new API *)
35 let content = [
36 ToolsCall.ToolContent.Text (TextContent.{
37 text = Printf.sprintf "Pong: %s" message;
38 annotations = None
39 })
40 ] in
41 (* Convert to JSON for the response *)
42 ToolsCall.Response.yojson_of_t {
43 content;
44 is_error = false
45 }
46 with
47 | Failure msg ->
48 Log.error (Printf.sprintf "Error in ping tool: %s" msg);
49 ToolsCall.Response.yojson_of_t {
50 content = [
51 ToolsCall.ToolContent.Text (TextContent.{
52 text = Printf.sprintf "Error: %s" msg;
53 annotations = None
54 })
55 ];
56 is_error = true
57 }
58 )
59
60(* Register a timestamp resource that uses the typed API *)
61let _ = add_resource server
62 ~uri_template:"timestamp://{format}"
63 ~description:"Get the current timestamp in the specified format"
64 ~mime_type:"text/plain"
65 (fun params ->
66 let format =
67 match params with
68 | [format] -> format
69 | _ -> "iso" (* default format *)
70 in
71 let timestamp =
72 match format with
73 | "unix" -> string_of_float (Unix.time ())
74 | "iso" | _ ->
75 let tm = Unix.gmtime (Unix.time ()) in
76 Printf.sprintf "%04d-%02d-%02dT%02d:%02d:%02dZ"
77 (tm.tm_year + 1900) (tm.tm_mon + 1) tm.tm_mday
78 tm.tm_hour tm.tm_min tm.tm_sec
79 in
80 timestamp
81 )
82
83(* Register a structured prompt that uses the typed API *)
84let _ = add_prompt server
85 ~name:"greet"
86 ~description:"A prompt to greet someone using the structured API"
87 ~arguments:[
88 ("name", Some "The name of the person to greet", true);
89 ("formal", Some "Whether to use formal greetings", false)
90 ]
91 (fun args ->
92 let name = try List.assoc "name" args with Not_found -> "friend" in
93 let formal = try List.assoc "formal" args = "true" with Not_found -> false in
94
95 let greeting =
96 if formal then
97 Printf.sprintf "Greetings, %s. It's a pleasure to make your acquaintance." name
98 else
99 Printf.sprintf "Hey %s! Nice to meet you!" name
100 in
101
102 [
103 PromptMessage.{
104 role = `User;
105 content = make_text_content "I'd like to meet someone new."
106 };
107 PromptMessage.{
108 role = `Assistant;
109 content = make_text_content greeting
110 };
111 PromptMessage.{
112 role = `User;
113 content = make_text_content "What should I say next?"
114 }
115 ]
116 )
117
118(* Process a resources/list request using the typed API *)
119let handle_resources_list_request request_id =
120 let resources = [
121 ResourcesList.Resource.{
122 uri = "timestamp://iso";
123 name = "ISO Timestamp";
124 description = Some "Current time in ISO format";
125 mime_type = Some "text/plain";
126 size = None;
127 };
128 ResourcesList.Resource.{
129 uri = "timestamp://unix";
130 name = "Unix Timestamp";
131 description = Some "Current time as Unix epoch";
132 mime_type = Some "text/plain";
133 size = None;
134 }
135 ] in
136
137 (* Create a typed response *)
138 create_resources_list_response ~id:request_id ~resources ()
139
140(* Process a tools/list request using the typed API *)
141let handle_tools_list_request request_id =
142 let tools = [
143 ToolsList.Tool.{
144 name = "ping";
145 description = Some "A simple ping tool that demonstrates the structured API";
146 input_schema = `Assoc [
147 ("type", `String "object");
148 ("properties", `Assoc [
149 ("message", `Assoc [
150 ("type", `String "string");
151 ("description", `String "The message to echo back")
152 ])
153 ]);
154 ("required", `List [`String "message"])
155 ];
156 annotations = None;
157 }
158 ] in
159
160 (* Create a typed response *)
161 create_tools_list_response ~id:request_id ~tools ()
162
163(* Process a prompts/list request using the typed API *)
164let handle_prompts_list_request request_id =
165 let prompts = [
166 PromptsList.Prompt.{
167 name = "greet";
168 description = Some "A prompt to greet someone using the structured API";
169 arguments = [
170 PromptsList.PromptArgument.{
171 name = "name";
172 description = Some "The name of the person to greet";
173 required = true;
174 };
175 PromptsList.PromptArgument.{
176 name = "formal";
177 description = Some "Whether to use formal greetings";
178 required = false;
179 }
180 ];
181 }
182 ] in
183
184 (* Create a typed response *)
185 create_prompts_list_response ~id:request_id ~prompts ()
186
187(* Run the server *)
188let () =
189 (* Example of creating a structured message directly - not actually used in the server *)
190 let example_structured_request =
191 ToolsCall.create_request
192 ~name:"ping"
193 ~arguments:(`Assoc [("message", `String "hello")])
194 ~id:(`Int 12345)
195 ()
196 in
197
198 (* Log the example request for demonstration *)
199 let json_str = Yojson.Safe.to_string (JSONRPCMessage.yojson_of_t example_structured_request) in
200 Log.info (Printf.sprintf "Example structured request: %s" json_str);
201
202 (* Run the server with the default scheduler *)
203 Eio_main.run @@ fun env->
204 Mcp_server.run_server env server