Model Context Protocol in OCaml

Add Util module to Mcp for common JSON parsing patterns

- Create a new Util submodule with common parsing utilities
- Refactor JSON parsing across various modules to use these utilities
- Improve error handling with more precise error messages

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

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

+4 -4
lib/dune
···
(modules mcp))
(library
-
(name mcp_message)
-
(public_name mcp.message)
(libraries mcp jsonrpc unix yojson)
-
(modules mcp_message)
(flags (:standard -w -67 -w -27 -w -32 -w -33 -w -34)))
(library
(name mcp_sdk)
(public_name mcp.sdk)
-
(libraries mcp mcp_message jsonrpc unix yojson)
(modules mcp_sdk)
(flags (:standard -w -67 -w -27 -w -32)))
···
(modules mcp))
(library
+
(name mcp_rpc)
+
(public_name mcp.rpc)
(libraries mcp jsonrpc unix yojson)
+
(modules mcp_rpc)
(flags (:standard -w -67 -w -27 -w -32 -w -33 -w -34)))
(library
(name mcp_sdk)
(public_name mcp.sdk)
+
(libraries mcp mcp_rpc jsonrpc unix yojson)
(modules mcp_sdk)
(flags (:standard -w -67 -w -27 -w -32)))
+117 -123
lib/mcp.ml
···
open Jsonrpc
(* Error codes for JSON-RPC *)
module ErrorCode = struct
type t =
···
`Assoc assoc
let t_of_yojson = function
-
| `Assoc fields ->
-
let text = match List.assoc_opt "text" fields with
-
| Some (`String s) -> s
-
| _ -> raise (Json.Of_json ("Missing or invalid 'text' field", `Assoc fields))
-
in
-
let _ = match List.assoc_opt "type" fields with
-
| Some (`String "text") -> ()
-
| _ -> raise (Json.Of_json ("Missing or invalid 'type' field", `Assoc fields))
-
in
let annotations = List.assoc_opt "annotations" fields |> Option.map Annotated.annotation_of_yojson in
{ text; annotations }
| j -> raise (Json.Of_json ("Expected object for TextContent", j))
···
`Assoc assoc
let t_of_yojson = function
-
| `Assoc fields ->
-
let data = match List.assoc_opt "data" fields with
-
| Some (`String s) -> s
-
| _ -> raise (Json.Of_json ("Missing or invalid 'data' field", `Assoc fields))
-
in
-
let mime_type = match List.assoc_opt "mimeType" fields with
-
| Some (`String s) -> s
-
| _ -> raise (Json.Of_json ("Missing or invalid 'mimeType' field", `Assoc fields))
-
in
-
let _ = match List.assoc_opt "type" fields with
-
| Some (`String "image") -> ()
-
| _ -> raise (Json.Of_json ("Missing or invalid 'type' field", `Assoc fields))
-
in
let annotations = List.assoc_opt "annotations" fields |> Option.map Annotated.annotation_of_yojson in
{ data; mime_type; annotations }
| j -> raise (Json.Of_json ("Expected object for ImageContent", j))
···
`Assoc assoc
let t_of_yojson = function
-
| `Assoc fields ->
-
let data = match List.assoc_opt "data" fields with
-
| Some (`String s) -> s
-
| _ -> raise (Json.Of_json ("Missing or invalid 'data' field", `Assoc fields))
-
in
-
let mime_type = match List.assoc_opt "mimeType" fields with
-
| Some (`String s) -> s
-
| _ -> raise (Json.Of_json ("Missing or invalid 'mimeType' field", `Assoc fields))
-
in
-
let _ = match List.assoc_opt "type" fields with
-
| Some (`String "audio") -> ()
-
| _ -> raise (Json.Of_json ("Missing or invalid 'type' field", `Assoc fields))
-
in
let annotations = List.assoc_opt "annotations" fields |> Option.map Annotated.annotation_of_yojson in
{ data; mime_type; annotations }
| j -> raise (Json.Of_json ("Expected object for AudioContent", j))
···
`Assoc assoc
let t_of_yojson = function
-
| `Assoc fields ->
-
let uri = match List.assoc_opt "uri" fields with
-
| Some (`String s) -> s
-
| _ -> raise (Json.Of_json ("Missing or invalid 'uri' field", `Assoc fields))
-
in
-
let mime_type = List.assoc_opt "mimeType" fields |> Option.map (function
-
| `String s -> s
-
| j -> raise (Json.Of_json ("Expected string for mimeType", j))
-
) in
{ uri; mime_type }
| j -> raise (Json.Of_json ("Expected object for ResourceContents", j))
end
···
`Assoc assoc
let t_of_yojson = function
-
| `Assoc fields ->
-
let uri = match List.assoc_opt "uri" fields with
-
| Some (`String s) -> s
-
| _ -> raise (Json.Of_json ("Missing or invalid 'uri' field", `Assoc fields))
-
in
-
let text = match List.assoc_opt "text" fields with
-
| Some (`String s) -> s
-
| _ -> raise (Json.Of_json ("Missing or invalid 'text' field", `Assoc fields))
-
in
-
let mime_type = List.assoc_opt "mimeType" fields |> Option.map (function
-
| `String s -> s
-
| j -> raise (Json.Of_json ("Expected string for mimeType", j))
-
) in
{ uri; text; mime_type }
| j -> raise (Json.Of_json ("Expected object for TextResourceContents", j))
end
···
`Assoc assoc
let t_of_yojson = function
-
| `Assoc fields ->
-
let uri = match List.assoc_opt "uri" fields with
-
| Some (`String s) -> s
-
| _ -> raise (Json.Of_json ("Missing or invalid 'uri' field", `Assoc fields))
-
in
-
let blob = match List.assoc_opt "blob" fields with
-
| Some (`String s) -> s
-
| _ -> raise (Json.Of_json ("Missing or invalid 'blob' field", `Assoc fields))
-
in
-
let mime_type = List.assoc_opt "mimeType" fields |> Option.map (function
-
| `String s -> s
-
| j -> raise (Json.Of_json ("Expected string for mimeType", j))
-
) in
{ uri; blob; mime_type }
| j -> raise (Json.Of_json ("Expected object for BlobResourceContents", j))
end
···
`Assoc assoc
let t_of_yojson = function
-
| `Assoc fields ->
-
let _ = match List.assoc_opt "type" fields with
-
| Some (`String "resource") -> ()
-
| _ -> raise (Json.Of_json ("Missing or invalid 'type' field", `Assoc fields))
in
-
let resource = match List.assoc_opt "resource" fields with
-
| Some (`Assoc res_fields) ->
-
if List.mem_assoc "text" res_fields then
-
`Text (TextResourceContents.t_of_yojson (`Assoc res_fields))
-
else if List.mem_assoc "blob" res_fields then
-
`Blob (BlobResourceContents.t_of_yojson (`Assoc res_fields))
-
else
-
raise (Json.Of_json ("Invalid resource content", `Assoc res_fields))
-
| _ -> raise (Json.Of_json ("Missing or invalid 'resource' field", `Assoc fields))
in
let annotations = List.assoc_opt "annotations" fields |> Option.map Annotated.annotation_of_yojson in
{ resource; annotations }
···
]
let t_of_yojson = function
-
| `Assoc fields ->
let role = match List.assoc_opt "role" fields with
| Some json -> Role.t_of_yojson json
-
| None -> raise (Json.Of_json ("Missing role field", `Assoc fields))
in
let content = match List.assoc_opt "content" fields with
| Some json -> content_of_yojson json
-
| None -> raise (Json.Of_json ("Missing content field", `Assoc fields))
in
{ role; content }
| j -> raise (Json.Of_json ("Expected object for PromptMessage", j))
···
]
let t_of_yojson = function
-
| `Assoc fields ->
let role = match List.assoc_opt "role" fields with
| Some json -> Role.t_of_yojson json
-
| None -> raise (Json.Of_json ("Missing role field", `Assoc fields))
in
-
let content = match List.assoc_opt "content" fields with
-
| Some (`Assoc content_fields) ->
-
(match List.assoc_opt "type" content_fields with
-
| Some (`String "text") -> `Text (TextContent.t_of_yojson (`Assoc content_fields))
-
| Some (`String "image") -> `Image (ImageContent.t_of_yojson (`Assoc content_fields))
-
| Some (`String "audio") -> `Audio (AudioContent.t_of_yojson (`Assoc content_fields))
-
| _ -> raise (Json.Of_json ("Invalid content type", `Assoc content_fields)))
-
| _ -> raise (Json.Of_json ("Missing or invalid content field", `Assoc fields))
in
{ role; content }
| j -> raise (Json.Of_json ("Expected object for SamplingMessage", j))
···
]
let t_of_yojson = function
-
| `Assoc fields ->
-
let name = match List.assoc_opt "name" fields with
-
| Some (`String s) -> s
-
| _ -> raise (Json.Of_json ("Missing or invalid 'name' field", `Assoc fields))
-
in
-
let version = match List.assoc_opt "version" fields with
-
| Some (`String s) -> s
-
| _ -> raise (Json.Of_json ("Missing or invalid 'version' field", `Assoc fields))
-
in
{ name; version }
| j -> raise (Json.Of_json ("Expected object for Implementation", j))
end
···
]
let t_of_yojson = function
-
| `Assoc fields ->
let capabilities = match List.assoc_opt "capabilities" fields with
| Some json -> json
-
| None -> raise (Json.Of_json ("Missing capabilities field", `Assoc fields))
in
let client_info = match List.assoc_opt "clientInfo" fields with
| Some json -> Implementation.t_of_yojson json
-
| None -> raise (Json.Of_json ("Missing clientInfo field", `Assoc fields))
in
-
let protocol_version = match List.assoc_opt "protocolVersion" fields with
-
| Some (`String s) -> s
-
| _ -> raise (Json.Of_json ("Missing or invalid protocolVersion field", `Assoc fields))
-
in
{ capabilities; client_info; protocol_version }
| j -> raise (Json.Of_json ("Expected object for InitializeRequest", j))
···
`Assoc assoc
let t_of_yojson = function
-
| `Assoc fields ->
let capabilities = match List.assoc_opt "capabilities" fields with
| Some json -> json
-
| None -> raise (Json.Of_json ("Missing capabilities field", `Assoc fields))
in
let server_info = match List.assoc_opt "serverInfo" fields with
| Some json -> Implementation.t_of_yojson json
-
| None -> raise (Json.Of_json ("Missing serverInfo field", `Assoc fields))
-
in
-
let protocol_version = match List.assoc_opt "protocolVersion" fields with
-
| Some (`String s) -> s
-
| _ -> raise (Json.Of_json ("Missing or invalid protocolVersion field", `Assoc fields))
-
in
-
let instructions = match List.assoc_opt "instructions" fields with
-
| Some (`String s) -> Some s
-
| _ -> None
in
let meta = List.assoc_opt "_meta" fields in
{ capabilities; server_info; protocol_version; instructions; meta }
| j -> raise (Json.Of_json ("Expected object for InitializeResult", j))
···
open Jsonrpc
+
(* Utility functions for JSON parsing *)
+
module Util = struct
+
(* Extract a string field from JSON object or raise an error *)
+
let get_string_field fields name json =
+
match List.assoc_opt name fields with
+
| Some (`String s) -> s
+
| _ -> raise (Json.Of_json (Printf.sprintf "Missing or invalid '%s' field" name, json))
+
+
(* Extract an optional string field from JSON object *)
+
let get_optional_string_field fields name =
+
List.assoc_opt name fields |> Option.map (function
+
| `String s -> s
+
| j -> raise (Json.Of_json (Printf.sprintf "Expected string for %s" name, j))
+
)
+
+
(* Extract an int field from JSON object or raise an error *)
+
let get_int_field fields name json =
+
match List.assoc_opt name fields with
+
| Some (`Int i) -> i
+
| _ -> raise (Json.Of_json (Printf.sprintf "Missing or invalid '%s' field" name, json))
+
+
(* Extract a float field from JSON object or raise an error *)
+
let get_float_field fields name json =
+
match List.assoc_opt name fields with
+
| Some (`Float f) -> f
+
| _ -> raise (Json.Of_json (Printf.sprintf "Missing or invalid '%s' field" name, json))
+
+
(* Extract a boolean field from JSON object or raise an error *)
+
let get_bool_field fields name json =
+
match List.assoc_opt name fields with
+
| Some (`Bool b) -> b
+
| _ -> raise (Json.Of_json (Printf.sprintf "Missing or invalid '%s' field" name, json))
+
+
(* Extract an object field from JSON object or raise an error *)
+
let get_object_field fields name json =
+
match List.assoc_opt name fields with
+
| Some (`Assoc obj) -> obj
+
| _ -> raise (Json.Of_json (Printf.sprintf "Missing or invalid '%s' field" name, json))
+
+
(* Extract a list field from JSON object or raise an error *)
+
let get_list_field fields name json =
+
match List.assoc_opt name fields with
+
| Some (`List items) -> items
+
| _ -> raise (Json.Of_json (Printf.sprintf "Missing or invalid '%s' field" name, json))
+
+
(* Verify a specific string value in a field *)
+
let verify_string_field fields name expected_value json =
+
match List.assoc_opt name fields with
+
| Some (`String s) when s = expected_value -> ()
+
| _ -> raise (Json.Of_json (Printf.sprintf "Field '%s' missing or not equal to '%s'" name expected_value, json))
+
end
+
(* Error codes for JSON-RPC *)
module ErrorCode = struct
type t =
···
`Assoc assoc
let t_of_yojson = function
+
| `Assoc fields as json ->
+
let text = Util.get_string_field fields "text" json in
+
Util.verify_string_field fields "type" "text" json;
let annotations = List.assoc_opt "annotations" fields |> Option.map Annotated.annotation_of_yojson in
{ text; annotations }
| j -> raise (Json.Of_json ("Expected object for TextContent", j))
···
`Assoc assoc
let t_of_yojson = function
+
| `Assoc fields as json ->
+
let data = Util.get_string_field fields "data" json in
+
let mime_type = Util.get_string_field fields "mimeType" json in
+
Util.verify_string_field fields "type" "image" json;
let annotations = List.assoc_opt "annotations" fields |> Option.map Annotated.annotation_of_yojson in
{ data; mime_type; annotations }
| j -> raise (Json.Of_json ("Expected object for ImageContent", j))
···
`Assoc assoc
let t_of_yojson = function
+
| `Assoc fields as json ->
+
let data = Util.get_string_field fields "data" json in
+
let mime_type = Util.get_string_field fields "mimeType" json in
+
Util.verify_string_field fields "type" "audio" json;
let annotations = List.assoc_opt "annotations" fields |> Option.map Annotated.annotation_of_yojson in
{ data; mime_type; annotations }
| j -> raise (Json.Of_json ("Expected object for AudioContent", j))
···
`Assoc assoc
let t_of_yojson = function
+
| `Assoc fields as json ->
+
let uri = Util.get_string_field fields "uri" json in
+
let mime_type = Util.get_optional_string_field fields "mimeType" in
{ uri; mime_type }
| j -> raise (Json.Of_json ("Expected object for ResourceContents", j))
end
···
`Assoc assoc
let t_of_yojson = function
+
| `Assoc fields as json ->
+
let uri = Util.get_string_field fields "uri" json in
+
let text = Util.get_string_field fields "text" json in
+
let mime_type = Util.get_optional_string_field fields "mimeType" in
{ uri; text; mime_type }
| j -> raise (Json.Of_json ("Expected object for TextResourceContents", j))
end
···
`Assoc assoc
let t_of_yojson = function
+
| `Assoc fields as json ->
+
let uri = Util.get_string_field fields "uri" json in
+
let blob = Util.get_string_field fields "blob" json in
+
let mime_type = Util.get_optional_string_field fields "mimeType" in
{ uri; blob; mime_type }
| j -> raise (Json.Of_json ("Expected object for BlobResourceContents", j))
end
···
`Assoc assoc
let t_of_yojson = function
+
| `Assoc fields as json ->
+
Util.verify_string_field fields "type" "resource" json;
+
let resource_fields = match List.assoc_opt "resource" fields with
+
| Some (`Assoc res_fields) -> res_fields
+
| _ -> raise (Json.Of_json ("Missing or invalid 'resource' field", json))
in
+
let resource =
+
if List.mem_assoc "text" resource_fields then
+
`Text (TextResourceContents.t_of_yojson (`Assoc resource_fields))
+
else if List.mem_assoc "blob" resource_fields then
+
`Blob (BlobResourceContents.t_of_yojson (`Assoc resource_fields))
+
else
+
raise (Json.Of_json ("Invalid resource content", `Assoc resource_fields))
in
let annotations = List.assoc_opt "annotations" fields |> Option.map Annotated.annotation_of_yojson in
{ resource; annotations }
···
]
let t_of_yojson = function
+
| `Assoc fields as json ->
let role = match List.assoc_opt "role" fields with
| Some json -> Role.t_of_yojson json
+
| None -> raise (Json.Of_json ("Missing role field", json))
in
let content = match List.assoc_opt "content" fields with
| Some json -> content_of_yojson json
+
| None -> raise (Json.Of_json ("Missing content field", json))
in
{ role; content }
| j -> raise (Json.Of_json ("Expected object for PromptMessage", j))
···
]
let t_of_yojson = function
+
| `Assoc fields as json ->
let role = match List.assoc_opt "role" fields with
| Some json -> Role.t_of_yojson json
+
| None -> raise (Json.Of_json ("Missing role field", json))
in
+
let content_obj = match List.assoc_opt "content" fields with
+
| Some (`Assoc content_fields) -> content_fields
+
| _ -> raise (Json.Of_json ("Missing or invalid content field", json))
+
in
+
let content_type = match List.assoc_opt "type" content_obj with
+
| Some (`String ty) -> ty
+
| _ -> raise (Json.Of_json ("Missing or invalid content type", `Assoc content_obj))
+
in
+
let content =
+
match content_type with
+
| "text" -> `Text (TextContent.t_of_yojson (`Assoc content_obj))
+
| "image" -> `Image (ImageContent.t_of_yojson (`Assoc content_obj))
+
| "audio" -> `Audio (AudioContent.t_of_yojson (`Assoc content_obj))
+
| _ -> raise (Json.Of_json (Printf.sprintf "Invalid content type: %s" content_type, `Assoc content_obj))
in
{ role; content }
| j -> raise (Json.Of_json ("Expected object for SamplingMessage", j))
···
]
let t_of_yojson = function
+
| `Assoc fields as json ->
+
let name = Util.get_string_field fields "name" json in
+
let version = Util.get_string_field fields "version" json in
{ name; version }
| j -> raise (Json.Of_json ("Expected object for Implementation", j))
end
···
]
let t_of_yojson = function
+
| `Assoc fields as json ->
let capabilities = match List.assoc_opt "capabilities" fields with
| Some json -> json
+
| None -> raise (Json.Of_json ("Missing capabilities field", json))
in
let client_info = match List.assoc_opt "clientInfo" fields with
| Some json -> Implementation.t_of_yojson json
+
| None -> raise (Json.Of_json ("Missing clientInfo field", json))
in
+
let protocol_version = Util.get_string_field fields "protocolVersion" json in
{ capabilities; client_info; protocol_version }
| j -> raise (Json.Of_json ("Expected object for InitializeRequest", j))
···
`Assoc assoc
let t_of_yojson = function
+
| `Assoc fields as json ->
let capabilities = match List.assoc_opt "capabilities" fields with
| Some json -> json
+
| None -> raise (Json.Of_json ("Missing capabilities field", json))
in
let server_info = match List.assoc_opt "serverInfo" fields with
| Some json -> Implementation.t_of_yojson json
+
| None -> raise (Json.Of_json ("Missing serverInfo field", json))
in
+
let protocol_version = Util.get_string_field fields "protocolVersion" json in
+
let instructions = Util.get_optional_string_field fields "instructions" in
let meta = List.assoc_opt "_meta" fields in
{ capabilities; server_info; protocol_version; instructions; meta }
| j -> raise (Json.Of_json ("Expected object for InitializeResult", j))
+74
lib/mcp.mli
···
open Jsonrpc
(** Error codes for JSON-RPC *)
module ErrorCode : sig
(** Standard JSON-RPC error codes with MCP-specific additions *)
···
open Jsonrpc
+
(** Utility functions for JSON parsing *)
+
module Util : sig
+
(** Extract a string field from JSON object or raise an error
+
@param fields Assoc list of fields from JSON object
+
@param name Field name to extract
+
@param json Original JSON for error context
+
@return The string value of the field
+
@raise Json.Of_json if the field is missing or not a string
+
*)
+
val get_string_field : (string * Json.t) list -> string -> Json.t -> string
+
+
(** Extract an optional string field from JSON object
+
@param fields Assoc list of fields from JSON object
+
@param name Field name to extract
+
@return Some string if present and a string, None if missing
+
@raise Json.Of_json if the field exists but is not a string
+
*)
+
val get_optional_string_field : (string * Json.t) list -> string -> string option
+
+
(** Extract an int field from JSON object or raise an error
+
@param fields Assoc list of fields from JSON object
+
@param name Field name to extract
+
@param json Original JSON for error context
+
@return The int value of the field
+
@raise Json.Of_json if the field is missing or not an int
+
*)
+
val get_int_field : (string * Json.t) list -> string -> Json.t -> int
+
+
(** Extract a float field from JSON object or raise an error
+
@param fields Assoc list of fields from JSON object
+
@param name Field name to extract
+
@param json Original JSON for error context
+
@return The float value of the field
+
@raise Json.Of_json if the field is missing or not a float
+
*)
+
val get_float_field : (string * Json.t) list -> string -> Json.t -> float
+
+
(** Extract a boolean field from JSON object or raise an error
+
@param fields Assoc list of fields from JSON object
+
@param name Field name to extract
+
@param json Original JSON for error context
+
@return The boolean value of the field
+
@raise Json.Of_json if the field is missing or not a boolean
+
*)
+
val get_bool_field : (string * Json.t) list -> string -> Json.t -> bool
+
+
(** Extract an object field from JSON object or raise an error
+
@param fields Assoc list of fields from JSON object
+
@param name Field name to extract
+
@param json Original JSON for error context
+
@return The object as an assoc list
+
@raise Json.Of_json if the field is missing or not an object
+
*)
+
val get_object_field : (string * Json.t) list -> string -> Json.t -> (string * Json.t) list
+
+
(** Extract a list field from JSON object or raise an error
+
@param fields Assoc list of fields from JSON object
+
@param name Field name to extract
+
@param json Original JSON for error context
+
@return The list items
+
@raise Json.Of_json if the field is missing or not a list
+
*)
+
val get_list_field : (string * Json.t) list -> string -> Json.t -> Json.t list
+
+
(** Verify a specific string value in a field
+
@param fields Assoc list of fields from JSON object
+
@param name Field name to check
+
@param expected_value The expected string value
+
@param json Original JSON for error context
+
@raise Json.Of_json if the field is missing or not equal to expected_value
+
*)
+
val verify_string_field : (string * Json.t) list -> string -> string -> Json.t -> unit
+
end
+
(** Error codes for JSON-RPC *)
module ErrorCode : sig
(** Standard JSON-RPC error codes with MCP-specific additions *)
+26 -61
lib/mcp_sdk.ml
···
| None -> assoc
in
`Assoc assoc
end
(* Resources for the MCP server *)
···
`Assoc assoc
end
-
(* Content type constructors have been moved to the Mcp module *)
-
-
(* Tool result handling using Mcp_message.ToolsCall.ToolContent *)
-
-
let create_tool_result contents ~is_error =
-
(* 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
-
-
(* Use the ToolsCall.Response module's JSON conversion *)
-
Mcp_message.ToolsCall.Response.yojson_of_t { content; is_error }
-
-
(* Using error codes from Mcp.ErrorCode module *)
-
let make_tool_schema properties required =
let props = List.map (fun (name, schema_type, description) ->
(name, `Assoc [
···
server.tools <- tool :: server.tools;
tool
-
(* Create a rich tool result with multiple content types *)
-
let create_rich_tool_result ?(text=None) ?(image=None) ?(audio=None) ?(resource=None) ~is_error () =
-
let contents = [] in
-
-
(* Add text content if provided *)
-
let contents = match text with
-
| 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) -> (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) -> (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) ->
-
(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
-
-
(* Create the final tool result *)
-
create_tool_result (List.rev contents) ~is_error
-
(* Create and register a tool in one step *)
let add_tool server ~name ?description ?(schema_properties=[]) ?(schema_required=[]) handler =
let input_schema = make_tool_schema schema_properties schema_required in
···
let capabilities = default_capabilities ~with_tools ~with_resources ~with_prompts () in
set_capabilities server capabilities;
server
-
-
(* The MCP message helpers have been moved to Mcp_message module.
-
This module now reexports them through open statements. *)
···
| None -> assoc
in
`Assoc assoc
+
+
(* Create a tool result with content *)
+
let create_tool_result content ~is_error =
+
`Assoc [
+
("content", `List (List.map Mcp.yojson_of_content content));
+
("isError", `Bool is_error);
+
]
+
+
(* Create a tool error result with structured content *)
+
let create_error_result error =
+
create_tool_result [Mcp.make_text_content error] ~is_error:true
+
+
(* Handle tool execution errors *)
+
let handle_execution_error err =
+
Log.error (Printf.sprintf "Tool execution failed: %s" err);
+
create_error_result (Printf.sprintf "Error executing tool: %s" err)
+
+
(* Handle unknown tool error *)
+
let handle_unknown_tool_error name =
+
Log.error (Printf.sprintf "Unknown tool: %s" name);
+
create_error_result (Printf.sprintf "Unknown tool: %s" name)
+
+
(* Handle general tool execution exception *)
+
let handle_execution_exception exn =
+
Log.error (Printf.sprintf "Exception executing tool: %s" (Printexc.to_string exn));
+
create_error_result (Printf.sprintf "Internal error: %s" (Printexc.to_string exn))
end
(* Resources for the MCP server *)
···
`Assoc assoc
end
let make_tool_schema properties required =
let props = List.map (fun (name, schema_type, description) ->
(name, `Assoc [
···
server.tools <- tool :: server.tools;
tool
(* Create and register a tool in one step *)
let add_tool server ~name ?description ?(schema_properties=[]) ?(schema_required=[]) handler =
let input_schema = make_tool_schema schema_properties schema_required in
···
let capabilities = default_capabilities ~with_tools ~with_resources ~with_prompts () in
set_capabilities server capabilities;
server
+15 -11
lib/mcp_sdk.mli
···
val create : name:string -> ?description:string -> input_schema:Json.t -> handler:handler -> unit -> t
val to_json : t -> Json.t
end
(** Resources for the MCP server *)
···
(** Configure server with default capabilities based on registered components *)
val configure_server : server -> ?with_tools:bool -> ?with_resources:bool -> ?with_prompts:bool -> unit -> server
-
-
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 :
-
?text:string option ->
-
?image:(string * string) option ->
-
?audio:(string * string) option ->
-
?resource:(string * string * bool * string option) option ->
-
is_error:bool ->
-
unit -> Json.t
val make_tool_schema : (string * string * string) list -> string list -> Json.t
···
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
+
+
(** Create a tool error result with structured content *)
+
val create_error_result : string -> Json.t
+
+
(** Handle tool execution errors *)
+
val handle_execution_error : string -> Json.t
+
+
(** Handle unknown tool error *)
+
val handle_unknown_tool_error : string -> Json.t
+
+
(** Handle general tool execution exception *)
+
val handle_execution_exception : exn -> Json.t
end
(** Resources for the MCP server *)
···
(** Configure server with default capabilities based on registered components *)
val configure_server : server -> ?with_tools:bool -> ?with_resources:bool -> ?with_prompts:bool -> unit -> server
val make_tool_schema : (string * string * string) list -> string list -> Json.t
+3 -21
lib/mcp_server.ml
···
in
create_error ~id ~code:error_code ~message ~data:(Some error_data) ()
-
(* Create a tool error result with structured content *)
-
let create_tool_error_result error =
-
create_tool_result [make_text_content error] ~is_error:true
-
-
(* Handle tool execution errors *)
-
let handle_tool_execution_error err =
-
Log.error (Printf.sprintf "Tool execution failed: %s" err);
-
create_tool_error_result (Printf.sprintf "Error executing tool: %s" err)
-
-
(* Handle unknown tool error *)
-
let handle_unknown_tool_error name =
-
Log.error (Printf.sprintf "Unknown tool: %s" name);
-
create_tool_error_result (Printf.sprintf "Unknown tool: %s" name)
-
-
(* Handle general tool execution exception *)
-
let handle_tool_execution_exception exn =
-
Log.error (Printf.sprintf "Exception executing tool: %s" (Printexc.to_string exn));
-
create_tool_error_result (Printf.sprintf "Internal error: %s" (Printexc.to_string exn))
(* Execute a tool *)
let execute_tool server ctx name args =
···
| Ok result ->
Log.debug "Tool execution succeeded";
result
-
| Error err -> handle_tool_execution_error err
with
-
| Not_found -> handle_unknown_tool_error name
-
| exn -> handle_tool_execution_exception exn
(* Process tools/call request *)
let handle_tools_call server req =
···
in
create_error ~id ~code:error_code ~message ~data:(Some error_data) ()
(* Execute a tool *)
let execute_tool server ctx name args =
···
| Ok result ->
Log.debug "Tool execution succeeded";
result
+
| Error err -> Tool.handle_execution_error err
with
+
| Not_found -> Tool.handle_unknown_tool_error name
+
| exn -> Tool.handle_execution_exception exn
(* Process tools/call request *)
let handle_tools_call server req =