Model Context Protocol in OCaml

Update SDK examples to use consistent content creation API

Refactor all SDK examples to use the standardized Mcp.make_*_content
functions instead of direct constructors for better consistency.

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

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

+4 -4
bin/capitalize_sdk.ml
···
[
Prompt.{
role = `User;
-
content = make_text_content "Please help me capitalize the following text:"
+
content = Mcp.make_text_content "Please help me capitalize the following text:"
};
Prompt.{
role = `User;
-
content = make_text_content text
+
content = Mcp.make_text_content text
};
Prompt.{
role = `Assistant;
-
content = make_text_content "Here's the capitalized version:"
+
content = Mcp.make_text_content "Here's the capitalized version:"
};
Prompt.{
role = `Assistant;
-
content = make_text_content (String.uppercase_ascii text)
+
content = Mcp.make_text_content (String.uppercase_ascii text)
}
]
)
+13 -14
bin/multimodal_sdk.ml
···
let audio_data = generate_sine_wave_audio (float_of_int frequency) duration in
(* Create a multimodal tool result *)
-
create_rich_tool_result
-
~text:(Some message)
-
~image:(Some (image_data, "image/gif"))
-
~audio:(Some (audio_data, "audio/wav"))
-
~is_error:false
-
()
+
create_tool_result [
+
Mcp.make_text_content message;
+
Mcp.make_image_content image_data "image/gif";
+
Mcp.make_audio_content audio_data "audio/wav"
+
] ~is_error:false
with
| Failure msg ->
Log.error (Printf.sprintf "Error in multimodal tool: %s" msg);
-
create_tool_result [TextContent (Printf.sprintf "Error: %s" msg)] ~is_error:true
+
create_tool_result [Mcp.make_text_content (Printf.sprintf "Error: %s" msg)] ~is_error:true
)
(* Define and register a tool for generating only images *)
···
if width < 1 || width > 1024 || height < 1 || height > 1024 then
create_tool_result
-
[TextContent "Error: Dimensions must be between 1 and 1024 pixels"]
+
[Mcp.make_text_content "Error: Dimensions must be between 1 and 1024 pixels"]
~is_error:true
else
let image_data = generate_random_image width height in
create_tool_result
-
[ImageContent { data = image_data; mime_type = "image/gif" }]
+
[Mcp.make_image_content image_data "image/gif"]
~is_error:false
with
| Failure msg ->
Log.error (Printf.sprintf "Error in generate_image tool: %s" msg);
-
create_tool_result [TextContent (Printf.sprintf "Error: %s" msg)] ~is_error:true
+
create_tool_result [Mcp.make_text_content (Printf.sprintf "Error: %s" msg)] ~is_error:true
)
(* Define and register a tool for generating only audio *)
···
if frequency < 20 || frequency > 20000 then
create_tool_result
-
[TextContent "Error: Frequency must be between 20Hz and 20,000Hz"]
+
[Mcp.make_text_content "Error: Frequency must be between 20Hz and 20,000Hz"]
~is_error:true
else if duration < 1 || duration > 10 then
create_tool_result
-
[TextContent "Error: Duration must be between 1 and 10 seconds"]
+
[Mcp.make_text_content "Error: Duration must be between 1 and 10 seconds"]
~is_error:true
else
let audio_data = generate_sine_wave_audio (float_of_int frequency) duration in
create_tool_result
-
[AudioContent { data = audio_data; mime_type = "audio/wav" }]
+
[Mcp.make_audio_content audio_data "audio/wav"]
~is_error:false
with
| Failure msg ->
Log.error (Printf.sprintf "Error in generate_audio tool: %s" msg);
-
create_tool_result [TextContent (Printf.sprintf "Error: %s" msg)] ~is_error:true
+
create_tool_result [Mcp.make_text_content (Printf.sprintf "Error: %s" msg)] ~is_error:true
)
(* Define and register a resource example with multimodal content *)
+5 -6
bin/ocaml_eval_sdk.ml
···
(* Create a server *)
let server = create_server
~name:"OCaml MCP Structured API Demo"
-
~version:"0.1.0"
-
~protocol_version:"2024-11-05" () |>
+
~version:"0.1.0" () |>
fun server ->
(* Set default capabilities *)
configure_server server ~with_tools:true ~with_resources:true ~with_prompts:true ()
···
] in
(* Create a typed response *)
-
create_resources_list_response ~id:request_id ~resources ()
+
ResourcesList.create_response ~id:request_id ~resources ()
(* Process a tools/list request using the typed API *)
let handle_tools_list_request request_id =
···
] in
(* Create a typed response *)
-
create_tools_list_response ~id:request_id ~tools ()
+
ToolsList.create_response ~id:request_id ~tools ()
(* Process a prompts/list request using the typed API *)
let handle_prompts_list_request request_id =
···
] in
(* Create a typed response *)
-
create_prompts_list_response ~id:request_id ~prompts ()
+
PromptsList.create_response ~id:request_id ~prompts ()
(* Run the server *)
let () =
···
(* Run the server with the default scheduler *)
Eio_main.run @@ fun env->
-
Mcp_server.run_server env server
+
Mcp_server.run_server env server
+58 -1
lib/mcp.ml
···
open Jsonrpc
+
(* Error codes for JSON-RPC *)
+
module ErrorCode = struct
+
type t =
+
| ParseError (* -32700 - Invalid JSON *)
+
| InvalidRequest (* -32600 - Invalid JSON-RPC request *)
+
| MethodNotFound (* -32601 - Method not available *)
+
| InvalidParams (* -32602 - Invalid method parameters *)
+
| InternalError (* -32603 - Internal JSON-RPC error *)
+
| ResourceNotFound (* -32002 - Custom MCP error: requested resource not found *)
+
| AuthRequired (* -32001 - Custom MCP error: authentication required *)
+
| CustomError of int (* For any other error codes *)
+
+
(* Convert the error code to its integer representation *)
+
let to_int = function
+
| ParseError -> -32700
+
| InvalidRequest -> -32600
+
| MethodNotFound -> -32601
+
| InvalidParams -> -32602
+
| InternalError -> -32603
+
| ResourceNotFound -> -32002
+
| AuthRequired -> -32001
+
| CustomError code -> code
+
+
(* Get error message for standard error codes *)
+
let to_message = function
+
| ParseError -> "Parse error"
+
| InvalidRequest -> "Invalid Request"
+
| MethodNotFound -> "Method not found"
+
| InvalidParams -> "Invalid params"
+
| InternalError -> "Internal error"
+
| ResourceNotFound -> "Resource not found"
+
| AuthRequired -> "Authentication required"
+
| CustomError _ -> "Error"
+
end
+
(* Protocol method strings *)
module Method = struct
(* Initialization and lifecycle methods *)
···
let create_notification = JSONRPCMessage.create_notification
let create_request = JSONRPCMessage.create_request
let create_response = JSONRPCMessage.create_response
-
let create_error = JSONRPCMessage.create_error
+
let create_error = JSONRPCMessage.create_error
+
+
(* Content type constructors *)
+
let make_text_content text =
+
Text (TextContent.{ text; annotations = None })
+
+
let make_image_content data mime_type =
+
Image (ImageContent.{ data; mime_type; annotations = None })
+
+
let make_audio_content data mime_type =
+
Audio (AudioContent.{ data; mime_type; annotations = None })
+
+
let make_resource_text_content uri text mime_type =
+
Resource (EmbeddedResource.{
+
resource = `Text TextResourceContents.{ uri; text; mime_type };
+
annotations = None;
+
})
+
+
let make_resource_blob_content uri blob mime_type =
+
Resource (EmbeddedResource.{
+
resource = `Blob BlobResourceContents.{ uri; blob; mime_type };
+
annotations = None;
+
})
+62
lib/mcp.mli
···
open Jsonrpc
+
(** Error codes for JSON-RPC *)
+
module ErrorCode : sig
+
(** Standard JSON-RPC error codes with MCP-specific additions *)
+
type t =
+
| ParseError (** -32700 - Invalid JSON *)
+
| InvalidRequest (** -32600 - Invalid JSON-RPC request *)
+
| MethodNotFound (** -32601 - Method not available *)
+
| InvalidParams (** -32602 - Invalid method parameters *)
+
| InternalError (** -32603 - Internal JSON-RPC error *)
+
| ResourceNotFound (** -32002 - Custom MCP error: requested resource not found *)
+
| AuthRequired (** -32001 - Custom MCP error: authentication required *)
+
| CustomError of int (** For any other error codes *)
+
+
(** Convert the error code to its integer representation
+
@param code The error code to convert
+
@return The integer error code as defined in the JSON-RPC spec
+
*)
+
val to_int : t -> int
+
+
(** Get error message for standard error codes
+
@param code The error code to get message for
+
@return A standard message for the error code
+
*)
+
val to_message : t -> string
+
end
+
(** MCP Protocol Methods - Standard method names used in JSON-RPC messages *)
module Method : sig
(** Standard protocol methods used in MCP JSON-RPC messages *)
···
@return A new JSON-RPC error message
*)
val create_error : id:RequestId.t -> code:int -> message:string -> ?data:Json.t option -> unit -> JSONRPCMessage.t
+
+
(** Create a new text content object
+
@param text The text content
+
@return A content value with the text
+
*)
+
val make_text_content : string -> content
+
+
(** Create a new image content object
+
@param data Base64-encoded image data
+
@param mime_type MIME type of the image (e.g., "image/png", "image/jpeg")
+
@return A content value with the image
+
*)
+
val make_image_content : string -> string -> content
+
+
(** Create a new audio content object
+
@param data Base64-encoded audio data
+
@param mime_type MIME type of the audio (e.g., "audio/wav", "audio/mp3")
+
@return A content value with the audio
+
*)
+
val make_audio_content : string -> string -> content
+
+
(** Create a new text resource content object
+
@param uri URI that uniquely identifies the resource
+
@param text The text content of the resource
+
@param mime_type Optional MIME type of the text content
+
@return A content value with the text resource
+
*)
+
val make_resource_text_content : string -> string -> string option -> content
+
+
(** Create a new binary resource content object
+
@param uri URI that uniquely identifies the resource
+
@param blob Base64-encoded binary data
+
@param mime_type Optional MIME type of the binary content
+
@return A content value with the binary resource
+
*)
+
val make_resource_blob_content : string -> string -> string option -> content
+25 -153
lib/mcp_sdk.ml
···
`Assoc assoc
end
-
(* Helper functions for creating common objects *)
-
(* Content type constructors *)
-
let make_text_content text =
-
Text (TextContent.{ text; annotations = None })
-
-
let make_image_content data mime_type =
-
Image (ImageContent.{ data; mime_type; annotations = None })
+
(* Content type constructors have been moved to the Mcp module *)
-
(* Create audio content using the AudioContent module *)
-
let make_audio_content data mime_type =
-
Audio (AudioContent.{ data; mime_type; annotations = None })
-
-
let make_resource_text_content uri text mime_type =
-
Resource (EmbeddedResource.{
-
resource = `Text TextResourceContents.{ uri; text; mime_type };
-
annotations = None;
-
})
-
-
let make_resource_blob_content uri blob mime_type =
-
Resource (EmbeddedResource.{
-
resource = `Blob BlobResourceContents.{ uri; blob; mime_type };
-
annotations = None;
-
})
-
-
(* Tool result handling *)
-
type tool_content =
-
| TextContent of string
-
| ImageContent of { data: string; mime_type: string }
-
| AudioContent of { data: string; mime_type: string }
-
| ResourceContent of { uri: string; data: string; is_blob: bool; mime_type: string option }
+
(* Tool result handling using Mcp_message.ToolsCall.ToolContent *)
let create_tool_result contents ~is_error =
-
let content_json = List.map (function
-
| TextContent text ->
-
`Assoc [
-
("type", `String "text");
-
("text", `String text)
-
]
-
| ImageContent { data; mime_type } ->
-
`Assoc [
-
("type", `String "image");
-
("data", `String data);
-
("mimeType", `String mime_type)
-
]
-
| AudioContent { data; mime_type } ->
-
`Assoc [
-
("type", `String "audio");
-
("data", `String data);
-
("mimeType", `String mime_type)
-
]
-
| ResourceContent { uri; data; is_blob; mime_type } ->
-
let resource_data = if is_blob then
-
`Assoc (
-
[("uri", `String uri);
-
("blob", `String data)] @
-
(match mime_type with
-
| Some mime -> [("mimeType", `String mime)]
-
| None -> [])
-
)
-
else
-
`Assoc (
-
[("uri", `String uri);
-
("text", `String data)] @
-
(match mime_type with
-
| Some mime -> [("mimeType", `String mime)]
-
| None -> [])
-
)
-
in
-
`Assoc [
-
("type", `String "resource");
-
("resource", resource_data)
-
]
+
(* Use the original Mcp.content values as is *)
+
let content = List.map (fun content ->
+
match content with
+
| Text text_content ->
+
Mcp_message.ToolsCall.ToolContent.Text text_content
+
| Image image_content ->
+
Mcp_message.ToolsCall.ToolContent.Image image_content
+
| Audio audio_content ->
+
Mcp_message.ToolsCall.ToolContent.Audio audio_content
+
| Resource resource_content ->
+
Mcp_message.ToolsCall.ToolContent.Resource resource_content
) contents in
-
`Assoc [
-
("content", `List content_json);
-
("isError", `Bool is_error)
-
]
-
-
(* Error types with standard JSON-RPC error codes *)
-
type error_code =
-
| ParseError (* -32700 *)
-
| InvalidRequest (* -32600 *)
-
| MethodNotFound (* -32601 *)
-
| InvalidParams (* -32602 *)
-
| InternalError (* -32603 *)
-
| ResourceNotFound (* -32002 Custom code for MCP *)
-
| AuthenticationRequired (* -32001 Custom code for MCP *)
-
| CustomError of int
+
(* Use the ToolsCall.Response module's JSON conversion *)
+
Mcp_message.ToolsCall.Response.yojson_of_t { content; is_error }
-
let error_code_to_int = function
-
| ParseError -> -32700
-
| InvalidRequest -> -32600
-
| MethodNotFound -> -32601
-
| InvalidParams -> -32602
-
| InternalError -> -32603
-
| ResourceNotFound -> -32002
-
| AuthenticationRequired -> -32001
-
| CustomError code -> code
+
(* Using error codes from Mcp.ErrorCode module *)
let make_tool_schema properties required =
let props = List.map (fun (name, schema_type, description) ->
···
(* Add text content if provided *)
let contents = match text with
-
| Some text -> (TextContent text) :: contents
+
| Some text -> (Mcp.make_text_content text) :: contents
| None -> contents
in
(* Add image content if provided *)
let contents = match image with
-
| Some (data, mime_type) -> (ImageContent { data; mime_type }) :: contents
+
| Some (data, mime_type) -> (Mcp.make_image_content data mime_type) :: contents
| None -> contents
in
(* Add audio content if provided *)
let contents = match audio with
-
| Some (data, mime_type) -> (AudioContent { data; mime_type }) :: contents
+
| Some (data, mime_type) -> (Mcp.make_audio_content data mime_type) :: contents
| None -> contents
in
(* Add resource content if provided *)
let contents = match resource with
| Some (uri, data, is_blob, mime_type) ->
-
(ResourceContent { uri; data; is_blob; mime_type }) :: contents
+
(if is_blob then
+
Mcp.make_resource_blob_content uri data mime_type
+
else
+
Mcp.make_resource_text_content uri data mime_type) :: contents
| None -> contents
in
···
set_capabilities server capabilities;
server
-
(* MCP Protocol Message Helpers *)
-
-
(* Resources helpers *)
-
let create_resources_list_request ?cursor ?id () =
-
Mcp_message.ResourcesList.create_request ?cursor ?id ()
-
-
let create_resources_list_response ~id ~resources ?next_cursor () =
-
Mcp_message.ResourcesList.create_response ~id ~resources ?next_cursor ()
-
-
let create_resources_read_request ~uri ?id () =
-
Mcp_message.ResourcesRead.create_request ~uri ?id ()
-
-
let create_resources_read_response ~id ~contents () =
-
Mcp_message.ResourcesRead.create_response ~id ~contents ()
-
-
let create_resources_list_changed_notification () =
-
Mcp_message.ListChanged.create_resources_notification ()
-
-
let create_resources_updated_notification ~uri () =
-
Mcp_message.ResourceUpdated.create_notification ~uri ()
-
-
(* Tools helpers *)
-
let create_tools_list_request ?cursor ?id () =
-
Mcp_message.ToolsList.create_request ?cursor ?id ()
-
-
let create_tools_list_response ~id ~tools ?next_cursor () =
-
Mcp_message.ToolsList.create_response ~id ~tools ?next_cursor ()
-
-
let create_tools_call_request ~name ~arguments ?id () =
-
Mcp_message.ToolsCall.create_request ~name ~arguments ?id ()
-
-
let create_tools_call_response ~id ~content ~is_error () =
-
Mcp_message.ToolsCall.create_response ~id ~content ~is_error ()
-
-
let create_tools_list_changed_notification () =
-
Mcp_message.ListChanged.create_tools_notification ()
-
-
(* Prompts helpers *)
-
let create_prompts_list_request ?cursor ?id () =
-
Mcp_message.PromptsList.create_request ?cursor ?id ()
-
-
let create_prompts_list_response ~id ~prompts ?next_cursor () =
-
Mcp_message.PromptsList.create_response ~id ~prompts ?next_cursor ()
-
-
let create_prompts_get_request ~name ~arguments ?id () =
-
Mcp_message.PromptsGet.create_request ~name ~arguments ?id ()
-
-
let create_prompts_get_response ~id ?description ~messages () =
-
Mcp_message.PromptsGet.create_response ~id ?description ~messages ()
-
-
let create_prompts_list_changed_notification () =
-
Mcp_message.ListChanged.create_prompts_notification ()
-
-
(* Progress notification *)
-
let create_progress_notification ~progress ~total ~progress_token () =
-
Mcp_message.Progress.create_notification ~progress ~total ~progress_token ()
+
(* The MCP message helpers have been moved to Mcp_message module.
+
This module now reexports them through open statements. *)
+1 -57
lib/mcp_sdk.mli
···
open Mcp
open Jsonrpc
-
open Mcp_message
(** SDK version *)
val version : string
···
(** Configure server with default capabilities based on registered components *)
val configure_server : server -> ?with_tools:bool -> ?with_resources:bool -> ?with_prompts:bool -> unit -> server
-
(** Content type constructors *)
-
val make_text_content : string -> content
-
val make_image_content : string -> string -> content
-
val make_audio_content : string -> string -> content
-
val make_resource_text_content : string -> string -> string option -> content
-
val make_resource_blob_content : string -> string -> string option -> content
-
-
(** Error types with standard JSON-RPC error codes *)
-
type error_code =
-
| ParseError (** -32700 *)
-
| InvalidRequest (** -32600 *)
-
| MethodNotFound (** -32601 *)
-
| InvalidParams (** -32602 *)
-
| InternalError (** -32603 *)
-
| ResourceNotFound (** -32002 Custom code for MCP *)
-
| AuthenticationRequired (** -32001 Custom code for MCP *)
-
| CustomError of int
-
-
val error_code_to_int : error_code -> int
-
-
(** Tool result handling *)
-
type tool_content =
-
| TextContent of string
-
| ImageContent of { data: string; mime_type: string }
-
| AudioContent of { data: string; mime_type: string }
-
| ResourceContent of { uri: string; data: string; is_blob: bool; mime_type: string option }
-
-
val create_tool_result : tool_content list -> is_error:bool -> Json.t
+
val create_tool_result : content list -> is_error:bool -> Json.t
(** Create a rich tool result with multiple content types *)
val create_rich_tool_result :
···
is_error:bool ->
unit -> Json.t
-
(** Helper functions for creating common objects *)
val make_tool_schema : (string * string * string) list -> string list -> Json.t
-
-
(** MCP Protocol Message Helpers for handling JSON-RPC messages *)
-
-
(** Resource message functions *)
-
val create_resources_list_request : ?cursor:Cursor.t -> ?id:RequestId.t -> unit -> JSONRPCMessage.t
-
val create_resources_list_response : id:RequestId.t -> resources:ResourcesList.Resource.t list -> ?next_cursor:Cursor.t -> unit -> JSONRPCMessage.t
-
val create_resources_read_request : uri:string -> ?id:RequestId.t -> unit -> JSONRPCMessage.t
-
val create_resources_read_response : id:RequestId.t -> contents:ResourcesRead.ResourceContent.t list -> unit -> JSONRPCMessage.t
-
val create_resources_list_changed_notification : unit -> JSONRPCMessage.t
-
val create_resources_updated_notification : uri:string -> unit -> JSONRPCMessage.t
-
-
(** Tool message functions *)
-
val create_tools_list_request : ?cursor:Cursor.t -> ?id:RequestId.t -> unit -> JSONRPCMessage.t
-
val create_tools_list_response : id:RequestId.t -> tools:ToolsList.Tool.t list -> ?next_cursor:Cursor.t -> unit -> JSONRPCMessage.t
-
val create_tools_call_request : name:string -> arguments:Json.t -> ?id:RequestId.t -> unit -> JSONRPCMessage.t
-
val create_tools_call_response : id:RequestId.t -> content:ToolsCall.ToolContent.t list -> is_error:bool -> unit -> JSONRPCMessage.t
-
val create_tools_list_changed_notification : unit -> JSONRPCMessage.t
-
-
(** Prompt message functions *)
-
val create_prompts_list_request : ?cursor:Cursor.t -> ?id:RequestId.t -> unit -> JSONRPCMessage.t
-
val create_prompts_list_response : id:RequestId.t -> prompts:PromptsList.Prompt.t list -> ?next_cursor:Cursor.t -> unit -> JSONRPCMessage.t
-
val create_prompts_get_request : name:string -> arguments:(string * string) list -> ?id:RequestId.t -> unit -> JSONRPCMessage.t
-
val create_prompts_get_response : id:RequestId.t -> ?description:string -> messages:PromptMessage.t list -> unit -> JSONRPCMessage.t
-
val create_prompts_list_changed_notification : unit -> JSONRPCMessage.t
-
-
(** Progress notification *)
-
val create_progress_notification : progress:float -> total:float -> progress_token:ProgressToken.t -> unit -> JSONRPCMessage.t
+2 -2
lib/mcp_server.ml
···
(* Create a proper JSONRPC error with code and data *)
let create_jsonrpc_error id code message ?data () =
-
let error_code = error_code_to_int code in
+
let error_code = ErrorCode.to_int code in
let error_data = match data with
| Some d -> d
| None -> `Null
···
(* Create a tool error result with structured content *)
let create_tool_error_result error =
-
create_tool_result [TextContent error] ~is_error:true
+
create_tool_result [make_text_content error] ~is_error:true
(* Handle tool execution errors *)
let handle_tool_execution_error err =