Model Context Protocol in OCaml

Change Method module to use algebraic data type instead of strings

Replace string-based methods with a type-safe algebraic data type to
better enforce correctness in the protocol implementation.

- Added Method.t variant type with all MCP methods as constructors
- Added to_string and of_string conversion functions
- Updated JSONRPCMessage to use Method.t instead of strings
- Modified create_notification and create_request to use Method.t parameter
- Updated all method references throughout the codebase

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

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

+80 -33
lib/mcp.ml
···
| CustomError _ -> "Error"
end
-
(* Protocol method strings *)
module Method = struct
-
(* Initialization and lifecycle methods *)
-
let initialize = "initialize"
-
let initialized = "notifications/initialized"
-
(* Resource methods *)
-
let resources_list = "resources/list"
-
let resources_read = "resources/read"
-
let resources_templates_list = "resources/templates/list"
-
let resources_subscribe = "resources/subscribe"
-
let resources_list_changed = "notifications/resources/list_changed"
-
let resources_updated = "notifications/resources/updated"
-
-
(* Tool methods *)
-
let tools_list = "tools/list"
-
let tools_call = "tools/call"
-
let tools_list_changed = "notifications/tools/list_changed"
-
-
(* Prompt methods *)
-
let prompts_list = "prompts/list"
-
let prompts_get = "prompts/get"
-
let prompts_list_changed = "notifications/prompts/list_changed"
-
(* Progress notifications *)
-
let progress = "notifications/progress"
end
(* Common types *)
···
module JSONRPCMessage = struct
type notification = {
-
method_: string;
params: Json.t option;
}
type request = {
id: RequestId.t;
-
method_: string;
params: Json.t option;
progress_token: ProgressToken.t option;
}
···
let yojson_of_notification (n: notification) =
let assoc = [
("jsonrpc", `String "2.0");
-
("method", `String n.method_);
] in
let assoc = match n.params with
| Some params -> ("params", params) :: assoc
···
let assoc = [
("jsonrpc", `String "2.0");
("id", Id.yojson_of_t r.id);
-
("method", `String r.method_);
] in
let assoc = match r.params with
| Some params ->
···
let notification_of_yojson = function
| `Assoc fields ->
let method_ = match List.assoc_opt "method" fields with
-
| Some (`String s) -> s
| _ -> raise (Json.Of_json ("Missing or invalid 'method' field", `Assoc fields))
in
let params = List.assoc_opt "params" fields in
···
| _ -> raise (Json.Of_json ("Missing or invalid 'id' field", `Assoc fields))
in
let method_ = match List.assoc_opt "method" fields with
-
| Some (`String s) -> s
| _ -> raise (Json.Of_json ("Missing or invalid 'method' field", `Assoc fields))
in
let params = List.assoc_opt "params" fields in
···
let to_jsonrpc ~id t =
let params = yojson_of_t t in
-
JSONRPCMessage.create_request ~id ~method_:"initialize" ~params:(Some params) ()
end
module Result = struct
···
| `Assoc [] -> None
| json -> Some json
in
-
JSONRPCMessage.create_notification ~method_:"notifications/initialized" ~params ()
end
end
···
let parse_message json =
JSONRPCMessage.t_of_yojson json
-
let create_notification = JSONRPCMessage.create_notification
-
let create_request = JSONRPCMessage.create_request
let create_response = JSONRPCMessage.create_response
let create_error = JSONRPCMessage.create_error
···
| CustomError _ -> "Error"
end
+
(* Protocol method types *)
module Method = struct
+
(* Method type representing all MCP protocol methods *)
+
type t =
+
(* Initialization and lifecycle methods *)
+
| Initialize
+
| Initialized
+
+
(* Resource methods *)
+
| ResourcesList
+
| ResourcesRead
+
| ResourcesTemplatesList
+
| ResourcesSubscribe
+
| ResourcesListChanged
+
| ResourcesUpdated
+
+
(* Tool methods *)
+
| ToolsList
+
| ToolsCall
+
| ToolsListChanged
+
+
(* Prompt methods *)
+
| PromptsList
+
| PromptsGet
+
| PromptsListChanged
+
+
(* Progress notifications *)
+
| Progress
+
(* Convert method type to string representation *)
+
let to_string = function
+
| Initialize -> "initialize"
+
| Initialized -> "notifications/initialized"
+
| ResourcesList -> "resources/list"
+
| ResourcesRead -> "resources/read"
+
| ResourcesTemplatesList -> "resources/templates/list"
+
| ResourcesSubscribe -> "resources/subscribe"
+
| ResourcesListChanged -> "notifications/resources/list_changed"
+
| ResourcesUpdated -> "notifications/resources/updated"
+
| ToolsList -> "tools/list"
+
| ToolsCall -> "tools/call"
+
| ToolsListChanged -> "notifications/tools/list_changed"
+
| PromptsList -> "prompts/list"
+
| PromptsGet -> "prompts/get"
+
| PromptsListChanged -> "notifications/prompts/list_changed"
+
| Progress -> "notifications/progress"
+
(* Convert string to method type *)
+
let of_string = function
+
| "initialize" -> Initialize
+
| "notifications/initialized" -> Initialized
+
| "resources/list" -> ResourcesList
+
| "resources/read" -> ResourcesRead
+
| "resources/templates/list" -> ResourcesTemplatesList
+
| "resources/subscribe" -> ResourcesSubscribe
+
| "notifications/resources/list_changed" -> ResourcesListChanged
+
| "notifications/resources/updated" -> ResourcesUpdated
+
| "tools/list" -> ToolsList
+
| "tools/call" -> ToolsCall
+
| "notifications/tools/list_changed" -> ToolsListChanged
+
| "prompts/list" -> PromptsList
+
| "prompts/get" -> PromptsGet
+
| "notifications/prompts/list_changed" -> PromptsListChanged
+
| "notifications/progress" -> Progress
+
| s -> failwith ("Unknown MCP method: " ^ s)
end
(* Common types *)
···
module JSONRPCMessage = struct
type notification = {
+
method_: Method.t;
params: Json.t option;
}
type request = {
id: RequestId.t;
+
method_: Method.t;
params: Json.t option;
progress_token: ProgressToken.t option;
}
···
let yojson_of_notification (n: notification) =
let assoc = [
("jsonrpc", `String "2.0");
+
("method", `String (Method.to_string n.method_));
] in
let assoc = match n.params with
| Some params -> ("params", params) :: assoc
···
let assoc = [
("jsonrpc", `String "2.0");
("id", Id.yojson_of_t r.id);
+
("method", `String (Method.to_string r.method_));
] in
let assoc = match r.params with
| Some params ->
···
let notification_of_yojson = function
| `Assoc fields ->
let method_ = match List.assoc_opt "method" fields with
+
| Some (`String s) ->
+
(try Method.of_string s
+
with Failure msg -> raise (Json.Of_json (msg, `String s)))
| _ -> raise (Json.Of_json ("Missing or invalid 'method' field", `Assoc fields))
in
let params = List.assoc_opt "params" fields in
···
| _ -> raise (Json.Of_json ("Missing or invalid 'id' field", `Assoc fields))
in
let method_ = match List.assoc_opt "method" fields with
+
| Some (`String s) ->
+
(try Method.of_string s
+
with Failure msg -> raise (Json.Of_json (msg, `String s)))
| _ -> raise (Json.Of_json ("Missing or invalid 'method' field", `Assoc fields))
in
let params = List.assoc_opt "params" fields in
···
let to_jsonrpc ~id t =
let params = yojson_of_t t in
+
JSONRPCMessage.create_request ~id ~method_:Method.Initialize ~params:(Some params) ()
end
module Result = struct
···
| `Assoc [] -> None
| json -> Some json
in
+
JSONRPCMessage.create_notification ~method_:Method.Initialized ~params ()
end
end
···
let parse_message json =
JSONRPCMessage.t_of_yojson json
+
let create_notification ?(params=None) ~method_ () =
+
JSONRPCMessage.create_notification ~params ~method_ ()
+
+
let create_request ?(params=None) ?(progress_token=None) ~id ~method_ () =
+
JSONRPCMessage.create_request ~params ~progress_token ~id ~method_ ()
+
let create_response = JSONRPCMessage.create_response
let create_error = JSONRPCMessage.create_error
+50 -50
lib/mcp.mli
···
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 *)
-
-
val initialize : string (** "initialize" - Start the MCP lifecycle *)
-
-
val initialized : string (** "notifications/initialized" - Signal readiness after initialization *)
-
val resources_list : string (** "resources/list" - Discover available resources *)
-
-
val resources_read : string (** "resources/read" - Retrieve resource contents *)
-
-
val resources_templates_list : string (** "resources/templates/list" - List available resource templates *)
-
-
val resources_subscribe : string (** "resources/subscribe" - Subscribe to resource changes *)
-
-
val resources_list_changed : string (** "notifications/resources/list_changed" - Resource list has changed *)
-
-
val resources_updated : string (** "notifications/resources/updated" - Resource has been updated *)
-
-
val tools_list : string (** "tools/list" - Discover available tools *)
-
-
val tools_call : string (** "tools/call" - Invoke a tool *)
-
-
val tools_list_changed : string (** "notifications/tools/list_changed" - Tool list has changed *)
-
-
val prompts_list : string (** "prompts/list" - Discover available prompts *)
-
-
val prompts_get : string (** "prompts/get" - Retrieve a prompt template with arguments *)
-
-
val prompts_list_changed : string (** "notifications/prompts/list_changed" - Prompt list has changed *)
-
val progress : string (** "notifications/progress" - Progress update for long-running operations *)
end
···
v}
*)
type notification = {
-
method_: string;
-
(** Method name for the notification, following the MCP naming conventions.
-
Common method patterns include:
-
- "notifications/X" for standard notifications
-
- "notifications/X/Y" for more specific notifications
-
-
Examples: "notifications/initialized", "notifications/resources/updated" *)
params: Json.t option;
(** Optional parameters for the notification as arbitrary JSON.
The structure depends on the specific notification method. *)
···
id: RequestId.t;
(** Unique identifier for the request, which will be echoed in the response.
This can be a string or integer and should be unique within the session. *)
-
method_: string;
-
(** Method name for the request, following the MCP naming conventions.
-
Common method patterns include:
-
- "X/Y" for standard operations
-
- "X/Y/Z" for more specific operations
-
-
Examples: "initialize", "resources/read", "tools/call", "prompts/get" *)
params: Json.t option;
(** Optional parameters for the request as arbitrary JSON.
The structure depends on the specific request method. *)
···
@param method_ Method name for the notification
@return A new JSON-RPC notification message
*)
-
val create_notification : ?params:Json.t option -> method_:string -> unit -> t
(** Create a new request message
@param params Optional parameters for the request
···
@param method_ Method name for the request
@return A new JSON-RPC request message
*)
-
val create_request : ?params:Json.t option -> ?progress_token:ProgressToken.t option -> id:RequestId.t -> method_:string -> unit -> t
(** Create a new response message
@param id ID matching the original request
···
- "notifications/tools/list_changed" - Available tools changed
@param params Optional parameters for the notification as a JSON value
-
@param method_ Method name for the notification, typically following MCP naming conventions
@return A new JSON-RPC notification message
*)
-
val create_notification : ?params:Json.t option -> method_:string -> unit -> JSONRPCMessage.t
(** Create a new request message
···
@param progress_token Optional progress token for long-running operations
that can report progress updates
@param id Unique identifier for the request, used to correlate with the response
-
@param method_ Method name for the request, following MCP naming conventions
@return A new JSON-RPC request message
*)
-
val create_request : ?params:Json.t option -> ?progress_token:ProgressToken.t option -> id:RequestId.t -> method_:string -> unit -> JSONRPCMessage.t
(** Create a new response message
···
val to_message : t -> string
end
+
(** MCP Protocol Methods - Algebraic data type representing all MCP methods *)
module Method : sig
+
(** Method type representing all MCP protocol methods *)
+
type t =
+
(* Initialization and lifecycle methods *)
+
| Initialize (** Start the MCP lifecycle *)
+
| Initialized (** Signal readiness after initialization *)
+
+
(* Resource methods *)
+
| ResourcesList (** Discover available resources *)
+
| ResourcesRead (** Retrieve resource contents *)
+
| ResourcesTemplatesList (** List available resource templates *)
+
| ResourcesSubscribe (** Subscribe to resource changes *)
+
| ResourcesListChanged (** Resource list has changed *)
+
| ResourcesUpdated (** Resource has been updated *)
+
+
(* Tool methods *)
+
| ToolsList (** Discover available tools *)
+
| ToolsCall (** Invoke a tool *)
+
| ToolsListChanged (** Tool list has changed *)
+
+
(* Prompt methods *)
+
| PromptsList (** Discover available prompts *)
+
| PromptsGet (** Retrieve a prompt template with arguments *)
+
| PromptsListChanged (** Prompt list has changed *)
+
+
(* Progress notifications *)
+
| Progress (** Progress update for long-running operations *)
+
(** Convert method type to string representation
+
@param method_ The method to convert
+
@return The string representation of the method (e.g., "initialize", "resources/list")
+
*)
+
val to_string : t -> string
+
(** Convert string to method type
+
@param s The string representation of the method
+
@return The corresponding method type
+
@raise Failure if the string is not a valid MCP method
+
*)
+
val of_string : string -> t
end
···
v}
*)
type notification = {
+
method_: Method.t;
+
(** Method for the notification, using the Method.t type to ensure type safety.
+
Examples: Method.Initialized, Method.ResourcesUpdated *)
params: Json.t option;
(** Optional parameters for the notification as arbitrary JSON.
The structure depends on the specific notification method. *)
···
id: RequestId.t;
(** Unique identifier for the request, which will be echoed in the response.
This can be a string or integer and should be unique within the session. *)
+
method_: Method.t;
+
(** Method for the request, using the Method.t type to ensure type safety.
+
Examples: Method.Initialize, Method.ResourcesRead, Method.ToolsCall *)
params: Json.t option;
(** Optional parameters for the request as arbitrary JSON.
The structure depends on the specific request method. *)
···
@param method_ Method name for the notification
@return A new JSON-RPC notification message
*)
+
val create_notification : ?params:Json.t option -> method_:Method.t -> unit -> t
(** Create a new request message
@param params Optional parameters for the request
···
@param method_ Method name for the request
@return A new JSON-RPC request message
*)
+
val create_request : ?params:Json.t option -> ?progress_token:ProgressToken.t option -> id:RequestId.t -> method_:Method.t -> unit -> t
(** Create a new response message
@param id ID matching the original request
···
- "notifications/tools/list_changed" - Available tools changed
@param params Optional parameters for the notification as a JSON value
+
@param method_ Method type for the notification
@return A new JSON-RPC notification message
*)
+
val create_notification : ?params:Json.t option -> method_:Method.t -> unit -> JSONRPCMessage.t
(** Create a new request message
···
@param progress_token Optional progress token for long-running operations
that can report progress updates
@param id Unique identifier for the request, used to correlate with the response
+
@param method_ Method type for the request
@return A new JSON-RPC request message
*)
+
val create_request : ?params:Json.t option -> ?progress_token:ProgressToken.t option -> id:RequestId.t -> method_:Method.t -> unit -> JSONRPCMessage.t
(** Create a new response message
+11 -11
lib/mcp_message.ml
···
| None -> `Int (Random.int 10000)
in
let params = Request.yojson_of_t { cursor } in
-
JSONRPCMessage.create_request ~id ~method_:Method.resources_list ~params:(Some params) ()
let create_response ~id ~resources ?next_cursor () =
let result = Response.yojson_of_t { resources; next_cursor } in
···
| None -> `Int (Random.int 10000)
in
let params = Request.yojson_of_t { uri } in
-
JSONRPCMessage.create_request ~id ~method_:Method.resources_read ~params:(Some params) ()
let create_response ~id ~contents () =
let result = Response.yojson_of_t { contents } in
···
| None -> `Int (Random.int 10000)
in
let params = Request.yojson_of_t { cursor } in
-
JSONRPCMessage.create_request ~id ~method_:Method.tools_list ~params:(Some params) ()
let create_response ~id ~tools ?next_cursor () =
let result = Response.yojson_of_t { tools; next_cursor } in
···
| None -> `Int (Random.int 10000)
in
let params = Request.yojson_of_t { name; arguments } in
-
JSONRPCMessage.create_request ~id ~method_:Method.tools_call ~params:(Some params) ()
let create_response ~id ~content ~is_error () =
let result = Response.yojson_of_t { content; is_error } in
···
| None -> `Int (Random.int 10000)
in
let params = Request.yojson_of_t { cursor } in
-
JSONRPCMessage.create_request ~id ~method_:Method.prompts_list ~params:(Some params) ()
let create_response ~id ~prompts ?next_cursor () =
let result = Response.yojson_of_t { prompts; next_cursor } in
···
| None -> `Int (Random.int 10000)
in
let params = Request.yojson_of_t { name; arguments } in
-
JSONRPCMessage.create_request ~id ~method_:Method.prompts_get ~params:(Some params) ()
let create_response ~id ?description ~messages () =
let result = Response.yojson_of_t { description; messages } in
···
(* No parameters for these notifications *)
let create_resources_notification () =
-
JSONRPCMessage.create_notification ~method_:Method.resources_list_changed ()
let create_tools_notification () =
-
JSONRPCMessage.create_notification ~method_:Method.tools_list_changed ()
let create_prompts_notification () =
-
JSONRPCMessage.create_notification ~method_:Method.prompts_list_changed ()
end
(* Resource Updated Notification *)
···
let create_notification ~uri () =
let params = Notification.yojson_of_t { uri } in
-
JSONRPCMessage.create_notification ~method_:Method.resources_updated ~params:(Some params) ()
end
(* Progress Notification *)
···
let create_notification ~progress ~total ~progress_token () =
let params = Notification.yojson_of_t { progress; total; progress_token } in
-
JSONRPCMessage.create_notification ~method_:Method.progress ~params:(Some params) ()
end
(* Type aliases for backward compatibility *)
···
| None -> `Int (Random.int 10000)
in
let params = Request.yojson_of_t { cursor } in
+
JSONRPCMessage.create_request ~id ~method_:Method.ResourcesList ~params:(Some params) ()
let create_response ~id ~resources ?next_cursor () =
let result = Response.yojson_of_t { resources; next_cursor } in
···
| None -> `Int (Random.int 10000)
in
let params = Request.yojson_of_t { uri } in
+
JSONRPCMessage.create_request ~id ~method_:Method.ResourcesRead ~params:(Some params) ()
let create_response ~id ~contents () =
let result = Response.yojson_of_t { contents } in
···
| None -> `Int (Random.int 10000)
in
let params = Request.yojson_of_t { cursor } in
+
JSONRPCMessage.create_request ~id ~method_:Method.ToolsList ~params:(Some params) ()
let create_response ~id ~tools ?next_cursor () =
let result = Response.yojson_of_t { tools; next_cursor } in
···
| None -> `Int (Random.int 10000)
in
let params = Request.yojson_of_t { name; arguments } in
+
JSONRPCMessage.create_request ~id ~method_:Method.ToolsCall ~params:(Some params) ()
let create_response ~id ~content ~is_error () =
let result = Response.yojson_of_t { content; is_error } in
···
| None -> `Int (Random.int 10000)
in
let params = Request.yojson_of_t { cursor } in
+
JSONRPCMessage.create_request ~id ~method_:Method.PromptsList ~params:(Some params) ()
let create_response ~id ~prompts ?next_cursor () =
let result = Response.yojson_of_t { prompts; next_cursor } in
···
| None -> `Int (Random.int 10000)
in
let params = Request.yojson_of_t { name; arguments } in
+
JSONRPCMessage.create_request ~id ~method_:Method.PromptsGet ~params:(Some params) ()
let create_response ~id ?description ~messages () =
let result = Response.yojson_of_t { description; messages } in
···
(* No parameters for these notifications *)
let create_resources_notification () =
+
JSONRPCMessage.create_notification ~method_:Method.ResourcesListChanged ()
let create_tools_notification () =
+
JSONRPCMessage.create_notification ~method_:Method.ToolsListChanged ()
let create_prompts_notification () =
+
JSONRPCMessage.create_notification ~method_:Method.PromptsListChanged ()
end
(* Resource Updated Notification *)
···
let create_notification ~uri () =
let params = Notification.yojson_of_t { uri } in
+
JSONRPCMessage.create_notification ~method_:Method.ResourcesUpdated ~params:(Some params) ()
end
(* Progress Notification *)
···
let create_notification ~progress ~total ~progress_token () =
let params = Notification.yojson_of_t { progress; total; progress_token } in
+
JSONRPCMessage.create_notification ~method_:Method.Progress ~params:(Some params) ()
end
(* Type aliases for backward compatibility *)
+1 -1
lib/mcp_sdk.ml
···
("total", `Float total);
("progressToken", ProgressToken.yojson_of_t token)
] in
-
Some (create_notification ~method_:"notifications/progress" ~params:(Some params) ())
| _ -> None
end
···
("total", `Float total);
("progressToken", ProgressToken.yojson_of_t token)
] in
+
Some (create_notification ~method_:Method.Progress ~params:(Some params) ())
| _ -> None
end
+11 -12
lib/mcp_server.ml
···
Log.debug (Printf.sprintf "Processing message: %s" (Yojson.Safe.to_string message));
match JSONRPCMessage.t_of_yojson message with
| JSONRPCMessage.Request req ->
-
Log.debug (Printf.sprintf "Received request with method: %s" req.method_);
(match req.method_ with
-
| "initialize" -> handle_initialize server req
-
| "tools/list" -> handle_tools_list server req
-
| "tools/call" -> handle_tools_call server req
-
| "prompts/list" -> handle_prompts_list server req
-
| "resources/list" -> handle_resources_list server req
-
| "ping" -> handle_ping req
| _ ->
-
Log.error (Printf.sprintf "Unknown method received: %s" req.method_);
-
Some (create_jsonrpc_error req.id MethodNotFound ("Method not found: " ^ req.method_) ()))
| JSONRPCMessage.Notification notif ->
-
Log.debug (Printf.sprintf "Received notification with method: %s" notif.method_);
(match notif.method_ with
-
| "notifications/initialized" -> handle_initialized notif
| _ ->
-
Log.debug (Printf.sprintf "Ignoring notification: %s" notif.method_);
None)
| JSONRPCMessage.Response _ ->
Log.error "Unexpected response message received";
···
Log.debug (Printf.sprintf "Processing message: %s" (Yojson.Safe.to_string message));
match JSONRPCMessage.t_of_yojson message with
| JSONRPCMessage.Request req ->
+
Log.debug (Printf.sprintf "Received request with method: %s" (Method.to_string req.method_));
(match req.method_ with
+
| Method.Initialize -> handle_initialize server req
+
| Method.ToolsList -> handle_tools_list server req
+
| Method.ToolsCall -> handle_tools_call server req
+
| Method.PromptsList -> handle_prompts_list server req
+
| Method.ResourcesList -> handle_resources_list server req
| _ ->
+
Log.error (Printf.sprintf "Unknown method received: %s" (Method.to_string req.method_));
+
Some (create_jsonrpc_error req.id MethodNotFound ("Method not found: " ^ (Method.to_string req.method_)) ()))
| JSONRPCMessage.Notification notif ->
+
Log.debug (Printf.sprintf "Received notification with method: %s" (Method.to_string notif.method_));
(match notif.method_ with
+
| Method.Initialized -> handle_initialized notif
| _ ->
+
Log.debug (Printf.sprintf "Ignoring notification: %s" (Method.to_string notif.method_));
None)
| JSONRPCMessage.Response _ ->
Log.error "Unexpected response message received";