Model Context Protocol in OCaml

Extend Mcp_sdk with Tool, Resource and Prompt conversion functions

- Add translator functions to convert between SDK and RPC types
- Use these functions in the Mcp_server for all RPC handlers
- Implement proper content type conversion for tool responses

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

Changed files
+196 -11
lib
+121
lib/mcp_sdk.ml
···
in
`Assoc assoc
(* Create a tool result with content *)
let create_tool_result content ~is_error =
`Assoc [
···
| None -> assoc
in
`Assoc assoc
end
(* Prompts for the MCP server *)
···
assoc
in
`Assoc assoc
end
let make_tool_schema properties required =
···
in
`Assoc assoc
+
(* Convert to Mcp_rpc.ToolsList.Tool.t *)
+
let to_rpc_tool_list_tool (tool:t) =
+
Mcp_rpc.ToolsList.Tool.{
+
name = tool.name;
+
description = tool.description;
+
input_schema = tool.input_schema;
+
annotations = None; (* Could be extended to support annotations *)
+
}
+
+
(* Convert a list of Tool.t to the format needed for tools/list response *)
+
let to_rpc_tools_list tools =
+
List.map to_rpc_tool_list_tool tools
+
+
(* Convert Mcp_rpc.ToolsCall response content to Mcp.content list *)
+
let rpc_content_to_mcp_content content =
+
List.map (function
+
| Mcp_rpc.ToolsCall.ToolContent.Text t ->
+
Mcp.Text { TextContent.text = t.text; annotations = None }
+
| Mcp_rpc.ToolsCall.ToolContent.Image i ->
+
Mcp.Image {
+
ImageContent.mime_type = i.mime_type;
+
data = i.data;
+
annotations = None
+
}
+
| Mcp_rpc.ToolsCall.ToolContent.Audio a ->
+
Mcp.Audio {
+
AudioContent.mime_type = a.mime_type;
+
data = a.data;
+
annotations = None
+
}
+
| Mcp_rpc.ToolsCall.ToolContent.Resource r ->
+
(* Create a simple text resource from the embedded resource *)
+
let uri = match r with
+
| { EmbeddedResource.resource = `Text tr; _ } -> tr.uri
+
| { EmbeddedResource.resource = `Blob br; _ } -> br.uri
+
in
+
let text_content = match r with
+
| { EmbeddedResource.resource = `Text tr; _ } -> tr.text
+
| { EmbeddedResource.resource = `Blob br; _ } -> "Binary content"
+
in
+
let mime_type = match r with
+
| { EmbeddedResource.resource = `Text tr; _ } -> tr.mime_type
+
| { EmbeddedResource.resource = `Blob br; _ } -> br.mime_type
+
in
+
let text_resource = {
+
TextResourceContents.uri;
+
text = text_content;
+
mime_type
+
} in
+
Mcp.Resource {
+
EmbeddedResource.resource = `Text text_resource;
+
annotations = None
+
}
+
) content
+
+
(* Convert Mcp.content list to Mcp_rpc.ToolsCall.ToolContent.t list *)
+
let mcp_content_to_rpc_content content =
+
List.map (function
+
| Mcp.Text t ->
+
Mcp_rpc.ToolsCall.ToolContent.Text t
+
| Mcp.Image img ->
+
Mcp_rpc.ToolsCall.ToolContent.Image img
+
| Mcp.Audio aud ->
+
Mcp_rpc.ToolsCall.ToolContent.Audio aud
+
| Mcp.Resource res ->
+
let resource_data = match res.resource with
+
| `Text txt -> `Text txt
+
| `Blob blob -> `Blob blob
+
in
+
let resource = {
+
EmbeddedResource.resource = resource_data;
+
annotations = res.annotations
+
} in
+
Mcp_rpc.ToolsCall.ToolContent.Resource resource
+
) content
+
(* Create a tool result with content *)
let create_tool_result content ~is_error =
`Assoc [
···
| None -> assoc
in
`Assoc assoc
+
+
(* Convert to Mcp_rpc.ResourcesList.Resource.t *)
+
let to_rpc_resource_list_resource (resource:t) =
+
Mcp_rpc.ResourcesList.Resource.{
+
uri = resource.uri_template;
+
name = resource.uri_template; (* Use uri as name by default *)
+
description = resource.description;
+
mime_type = resource.mime_type;
+
size = None; (* Size can be added when we have actual resource content *)
+
}
+
+
(* Convert a list of Resource.t to the format needed for resources/list response *)
+
let to_rpc_resources_list resources =
+
List.map to_rpc_resource_list_resource resources
end
(* Prompts for the MCP server *)
···
assoc
in
`Assoc assoc
+
+
(* Convert argument to Mcp_rpc.PromptsList.PromptArgument.t *)
+
let argument_to_rpc_prompt_argument (arg:argument) =
+
Mcp_rpc.PromptsList.PromptArgument.{
+
name = arg.name;
+
description = arg.description;
+
required = arg.required;
+
}
+
+
(* Convert to Mcp_rpc.PromptsList.Prompt.t *)
+
let to_rpc_prompt_list_prompt (prompt:t) =
+
Mcp_rpc.PromptsList.Prompt.{
+
name = prompt.name;
+
description = prompt.description;
+
arguments = List.map argument_to_rpc_prompt_argument prompt.arguments;
+
}
+
+
(* Convert a list of Prompt.t to the format needed for prompts/list response *)
+
let to_rpc_prompts_list prompts =
+
List.map to_rpc_prompt_list_prompt prompts
+
+
(* Convert message to Mcp_rpc.PromptMessage.t *)
+
let message_to_rpc_prompt_message msg =
+
{
+
PromptMessage.role = msg.role;
+
PromptMessage.content = msg.content;
+
}
+
+
(* Convert a list of messages to the format needed for prompts/get response *)
+
let messages_to_rpc_prompt_messages messages =
+
List.map message_to_rpc_prompt_message messages
end
let make_tool_schema properties required =
+33
lib/mcp_sdk.mli
···
val create : name:string -> ?description:string -> input_schema:Json.t -> handler:handler -> unit -> t
val to_json : t -> Json.t
(** Create a tool result with content *)
val create_tool_result : Mcp.content list -> is_error:bool -> Json.t
···
val create : uri_template:string -> ?description:string -> ?mime_type:string -> handler:handler -> unit -> t
val to_json : t -> Json.t
end
(** Prompts for the MCP server *)
···
val create : name:string -> ?description:string -> ?arguments:argument list -> handler:handler -> unit -> t
val create_argument : name:string -> ?description:string -> ?required:bool -> unit -> argument
val to_json : t -> Json.t
end
(** Main server type *)
···
val create : name:string -> ?description:string -> input_schema:Json.t -> handler:handler -> unit -> t
val to_json : t -> Json.t
+
(** Convert to Mcp_rpc.ToolsList.Tool.t *)
+
val to_rpc_tool_list_tool : t -> Mcp_rpc.ToolsList.Tool.t
+
+
(** Convert a list of Tool.t to the format needed for tools/list response *)
+
val to_rpc_tools_list : t list -> Mcp_rpc.ToolsList.Tool.t list
+
+
(** Convert Mcp_rpc.ToolsCall response content to Mcp.content list *)
+
val rpc_content_to_mcp_content : Mcp_rpc.ToolsCall.ToolContent.t list -> Mcp.content list
+
+
(** Convert Mcp.content list to Mcp_rpc.ToolsCall.ToolContent.t list *)
+
val mcp_content_to_rpc_content : Mcp.content list -> Mcp_rpc.ToolsCall.ToolContent.t list
+
(** Create a tool result with content *)
val create_tool_result : Mcp.content list -> is_error:bool -> Json.t
···
val create : uri_template:string -> ?description:string -> ?mime_type:string -> handler:handler -> unit -> t
val to_json : t -> Json.t
+
+
(** Convert to Mcp_rpc.ResourcesList.Resource.t *)
+
val to_rpc_resource_list_resource : t -> Mcp_rpc.ResourcesList.Resource.t
+
+
(** Convert a list of Resource.t to the format needed for resources/list response *)
+
val to_rpc_resources_list : t list -> Mcp_rpc.ResourcesList.Resource.t list
end
(** Prompts for the MCP server *)
···
val create : name:string -> ?description:string -> ?arguments:argument list -> handler:handler -> unit -> t
val create_argument : name:string -> ?description:string -> ?required:bool -> unit -> argument
val to_json : t -> Json.t
+
+
(** Convert argument to Mcp_rpc.PromptsList.PromptArgument.t *)
+
val argument_to_rpc_prompt_argument : argument -> Mcp_rpc.PromptsList.PromptArgument.t
+
+
(** Convert to Mcp_rpc.PromptsList.Prompt.t *)
+
val to_rpc_prompt_list_prompt : t -> Mcp_rpc.PromptsList.Prompt.t
+
+
(** Convert a list of Prompt.t to the format needed for prompts/list response *)
+
val to_rpc_prompts_list : t list -> Mcp_rpc.PromptsList.Prompt.t list
+
+
(** Convert message to Mcp_rpc.PromptMessage.t *)
+
val message_to_rpc_prompt_message : message -> PromptMessage.t
+
+
(** Convert a list of messages to the format needed for prompts/get response *)
+
val messages_to_rpc_prompt_messages : message list -> PromptMessage.t list
end
(** Main server type *)
+42 -11
lib/mcp_server.ml
···
(* Process tools/list request *)
let handle_tools_list server (req:JSONRPCMessage.request) =
Log.debug "Processing tools/list request";
-
let tool_list = List.map Tool.to_json (tools server) in
-
let result = `Assoc [("tools", `List tool_list)] in
-
Some (create_response ~id:req.id ~result)
(* Process prompts/list request *)
let handle_prompts_list server (req:JSONRPCMessage.request) =
Log.debug "Processing prompts/list request";
-
let prompt_list = List.map Prompt.to_json (prompts server) in
-
let result = `Assoc [("prompts", `List prompt_list)] in
-
Some (create_response ~id:req.id ~result)
(* Process resources/list request *)
let handle_resources_list server (req:JSONRPCMessage.request) =
Log.debug "Processing resources/list request";
-
let resource_list = List.map Resource.to_json (resources server) in
-
let result = `Assoc [("resources", `List resource_list)] in
-
Some (create_response ~id:req.id ~result)
(* Extract the tool name from params *)
let extract_tool_name params =
···
| Not_found -> Tool.handle_unknown_tool_error name
| exn -> Tool.handle_execution_exception exn
(* Process tools/call request *)
let handle_tools_call server req =
Log.debug "Processing tools/call request";
···
in
(* Execute the tool *)
-
let result = execute_tool server ctx name args in
-
Some (create_response ~id:req.id ~result)
| None ->
Some (create_jsonrpc_error req.id InvalidParams "Missing tool name parameter" ()))
| _ ->
···
(* Process tools/list request *)
let handle_tools_list server (req:JSONRPCMessage.request) =
Log.debug "Processing tools/list request";
+
let tools_list = Tool.to_rpc_tools_list (tools server) in
+
let response = Mcp_rpc.ToolsList.create_response ~id:req.id ~tools:tools_list () in
+
Some response
(* Process prompts/list request *)
let handle_prompts_list server (req:JSONRPCMessage.request) =
Log.debug "Processing prompts/list request";
+
let prompts_list = Prompt.to_rpc_prompts_list (prompts server) in
+
let response = Mcp_rpc.PromptsList.create_response ~id:req.id ~prompts:prompts_list () in
+
Some response
(* Process resources/list request *)
let handle_resources_list server (req:JSONRPCMessage.request) =
Log.debug "Processing resources/list request";
+
let resources_list = Resource.to_rpc_resources_list (resources server) in
+
let response = Mcp_rpc.ResourcesList.create_response ~id:req.id ~resources:resources_list () in
+
Some response
(* Extract the tool name from params *)
let extract_tool_name params =
···
| Not_found -> Tool.handle_unknown_tool_error name
| exn -> Tool.handle_execution_exception exn
+
(* Convert JSON tool result to RPC content format *)
+
let json_to_rpc_content json =
+
match json with
+
| `Assoc fields ->
+
(match List.assoc_opt "content" fields, List.assoc_opt "isError" fields with
+
| Some (`List content_items), Some (`Bool is_error) ->
+
let mcp_content = List.map Mcp.content_of_yojson content_items in
+
let rpc_content = Tool.mcp_content_to_rpc_content mcp_content in
+
(rpc_content, is_error)
+
| _ ->
+
(* Fallback for compatibility with older formats *)
+
let text = Yojson.Safe.to_string json in
+
let text_content = { TextContent.text = text; annotations = None } in
+
([Mcp_rpc.ToolsCall.ToolContent.Text text_content], false))
+
| _ ->
+
(* Simple fallback for non-object results *)
+
let text = Yojson.Safe.to_string json in
+
let text_content = { TextContent.text = text; annotations = None } in
+
([Mcp_rpc.ToolsCall.ToolContent.Text text_content], false)
+
(* Process tools/call request *)
let handle_tools_call server req =
Log.debug "Processing tools/call request";
···
in
(* Execute the tool *)
+
let result_json = execute_tool server ctx name args in
+
(* Convert JSON result to RPC format *)
+
let content, is_error = json_to_rpc_content result_json in
+
+
(* Create the RPC response *)
+
let response = Mcp_rpc.ToolsCall.create_response
+
~id:req.id
+
~content
+
~is_error
+
()
+
in
+
+
Some response
| None ->
Some (create_jsonrpc_error req.id InvalidParams "Missing tool name parameter" ()))
| _ ->