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