Model Context Protocol in OCaml

Remove OCaml functors from Mcp_sdk module

This commit refactors the Mcp_sdk module to use a direct approach instead of
OCaml functors for creating servers. The changes include:

- Remove the MakeServer functor and Server module
- Create direct functions for working with servers
- Expose register_* and add_* functions for tools, resources, and prompts
- Update the capitalize_sdk.ml example to use the new API
- Simplify the server creation and configuration process

The non-functor API provides a more straightforward approach for creating
MCP servers in OCaml.

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

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

Changed files
+260 -269
bin
lib
+36 -38
bin/capitalize_sdk.ml
···
open Mcp
open Mcp_sdk
-
(* Create the server module *)
-
module CapitalizeServer = MakeServer(struct
-
let name = "OCaml MCP Capitalizer"
-
let version = Some "0.1.0"
-
end)
-
(* Helper for extracting string value from JSON *)
let get_string_param json name =
match json with
···
| _ -> raise (Failure (Printf.sprintf "Missing or invalid parameter: %s" name)))
| _ -> raise (Failure "Expected JSON object")
-
(* Define a capitalize tool *)
-
let _ = CapitalizeServer.tool
-
~name:(Some "capitalize")
+
(* Create a server *)
+
let server = create_server
+
~name:"OCaml MCP Capitalizer"
+
~version:"0.1.0"
+
~protocol_version:"2024-11-05"
+
()
+
+
(* Define startup and shutdown hooks *)
+
let startup () =
+
Printf.printf "CapitalizeServer is starting up!\n";
+
flush stdout;
+
Log.info "CapitalizeServer is starting up!"
+
+
let shutdown () =
+
Printf.printf "CapitalizeServer is shutting down. Goodbye!\n";
+
flush stdout;
+
Log.info "CapitalizeServer is shutting down. Goodbye!"
+
+
(* Register the hooks *)
+
let () =
+
set_startup_hook server startup;
+
set_shutdown_hook server shutdown
+
+
(* Define and register a capitalize tool *)
+
let _ = add_tool server
+
~name:"capitalize"
~description:"Capitalizes the provided text"
~schema_properties:[
("text", "string", "The text to capitalize")
···
}
)
-
(* Define a resource example *)
-
let _ = CapitalizeServer.resource
-
~uri_template:(Some "greeting://{name}")
+
(* Define and register a resource example *)
+
let _ = add_resource server
+
~uri_template:"greeting://{name}"
~description:"Get a greeting for a name"
~mime_type:"text/plain"
(fun params ->
···
| _ -> "Hello, world! Welcome to the OCaml MCP server."
)
-
(* Define a prompt example *)
-
let _ = CapitalizeServer.prompt
-
~name:(Some "capitalize-prompt")
+
(* Define and register a prompt example *)
+
let _ = add_prompt server
+
~name:"capitalize-prompt"
~description:"A prompt to help with text capitalization"
~arguments:[
("text", Some "The text to be capitalized", true)
···
]
)
-
(* Define startup and shutdown hooks *)
-
let startup () =
-
Printf.printf "CapitalizeServer is starting up!\n";
-
flush stdout;
-
Log.info "CapitalizeServer is starting up!"
-
-
let shutdown () =
-
Printf.printf "CapitalizeServer is shutting down. Goodbye!\n";
-
flush stdout;
-
Log.info "CapitalizeServer is shutting down. Goodbye!"
-
(* Main function *)
let () =
(* Print directly to ensure we see output *)
Printf.printf "Starting CapitalizeServer...\n";
flush stdout;
-
(* Run the server with all our registered capabilities *)
-
let server_with_hooks = { CapitalizeServer.server with
-
Server.startup_hook = Some startup;
-
Server.shutdown_hook = Some shutdown;
-
} in
-
-
(* Run the startup hook directly *)
-
(match server_with_hooks.Server.startup_hook with
-
| Some hook -> hook()
-
| None -> ());
+
(* Configure the server with appropriate capabilities *)
+
ignore (configure_server server ());
-
(* Now run the server *)
-
Server.run server_with_hooks
+
(* Run the server *)
+
run_server server
+174 -191
lib/mcp_sdk.ml
···
let report_progress ctx value total =
match ctx.progress_token, ctx.request_id with
-
| Some token, Some id ->
+
| Some token, Some _id ->
let params = `Assoc [
("progress", `Float value);
("total", `Float total);
···
("required", required_json)
]
-
(* Server implementation *)
-
module Server = struct
-
type startup_hook = unit -> unit
-
type shutdown_hook = unit -> unit
+
(* Main server type *)
+
type server = {
+
name: string;
+
version: string;
+
protocol_version: string;
+
mutable capabilities: Json.t;
+
mutable tools: Tool.t list;
+
mutable resources: Resource.t list;
+
mutable prompts: Prompt.t list;
+
mutable lifespan_context: (string * Json.t) list;
+
mutable startup_hook: (unit -> unit) option;
+
mutable shutdown_hook: (unit -> unit) option;
+
}
-
type t = {
-
name: string;
-
version: string;
-
protocol_version: string;
-
mutable capabilities: Json.t;
-
mutable tools: Tool.t list;
-
mutable resources: Resource.t list;
-
mutable prompts: Prompt.t list;
-
mutable lifespan_context: (string * Json.t) list;
-
startup_hook: startup_hook option;
-
shutdown_hook: shutdown_hook option;
+
(* Create a new server *)
+
let create_server ~name ?(version="0.1.0") ?(protocol_version="2024-11-05") () =
+
{
+
name;
+
version;
+
protocol_version;
+
capabilities = `Assoc [];
+
tools = [];
+
resources = [];
+
prompts = [];
+
lifespan_context = [];
+
startup_hook = None;
+
shutdown_hook = None;
}
-
let create ~name ?(version="0.1.0") ?(protocol_version="2024-11-05") ?startup_hook ?shutdown_hook () =
-
{
-
name;
-
version;
-
protocol_version;
-
capabilities = `Assoc [];
-
tools = [];
-
resources = [];
-
prompts = [];
-
lifespan_context = [];
-
startup_hook;
-
shutdown_hook;
-
}
+
(* Default capabilities for the server *)
+
let default_capabilities ?(with_tools=true) ?(with_resources=false) ?(with_prompts=false) () =
+
let caps = [] in
+
let caps =
+
if with_tools then
+
("tools", `Assoc [
+
("listChanged", `Bool true)
+
]) :: caps
+
else
+
caps
+
in
+
let caps =
+
if with_resources then
+
("resources", `Assoc [
+
("listChanged", `Bool true);
+
("subscribe", `Bool false)
+
]) :: caps
+
else if not with_resources then
+
("resources", `Assoc [
+
("listChanged", `Bool false);
+
("subscribe", `Bool false)
+
]) :: caps
+
else
+
caps
+
in
+
let caps =
+
if with_prompts then
+
("prompts", `Assoc [
+
("listChanged", `Bool true)
+
]) :: caps
+
else if not with_prompts then
+
("prompts", `Assoc [
+
("listChanged", `Bool false)
+
]) :: caps
+
else
+
caps
+
in
+
`Assoc caps
+
+
(* Register a tool *)
+
let register_tool server tool =
+
server.tools <- tool :: server.tools;
+
tool
-
(* Register a tool *)
-
let register_tool server tool =
-
server.tools <- tool :: server.tools;
+
(* 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 handler' ctx args =
+
try
+
Ok (handler args)
+
with exn ->
+
Error (Printexc.to_string exn)
+
in
+
let tool = Tool.create
+
~name
+
?description
+
~input_schema
+
~handler:handler'
()
+
in
+
register_tool server tool
-
(* Register a resource *)
-
let register_resource server resource =
-
server.resources <- resource :: server.resources;
+
(* Register a resource *)
+
let register_resource server resource =
+
server.resources <- resource :: server.resources;
+
resource
+
+
(* Create and register a resource in one step *)
+
let add_resource server ~uri_template ?description ?mime_type handler =
+
let handler' _ctx params =
+
try
+
Ok (handler params)
+
with exn ->
+
Error (Printexc.to_string exn)
+
in
+
let resource = Resource.create
+
~uri_template
+
?description
+
?mime_type
+
~handler:handler'
()
+
in
+
register_resource server resource
-
(* Register a prompt *)
-
let register_prompt server prompt =
-
server.prompts <- prompt :: server.prompts;
+
(* Register a prompt *)
+
let register_prompt server prompt =
+
server.prompts <- prompt :: server.prompts;
+
prompt
+
+
(* Create and register a prompt in one step *)
+
let add_prompt server ~name ?description ?(arguments=[]) handler =
+
let prompt_args = List.map (fun (name, desc, required) ->
+
Prompt.create_argument ~name ?description:desc ~required ()
+
) arguments in
+
let handler' _ctx args =
+
try
+
Ok (handler args)
+
with exn ->
+
Error (Printexc.to_string exn)
+
in
+
let prompt = Prompt.create
+
~name
+
?description
+
~arguments:prompt_args
+
~handler:handler'
()
-
-
(* Default server capabilities *)
-
let default_capabilities ?(with_tools=true) ?(with_resources=false) ?(with_prompts=false) () =
-
let caps = [] in
-
let caps =
-
if with_tools then
-
("tools", `Assoc [
-
("listChanged", `Bool true)
-
]) :: caps
-
else
-
caps
-
in
-
let caps =
-
if with_resources then
-
("resources", `Assoc [
-
("listChanged", `Bool true);
-
("subscribe", `Bool false)
-
]) :: caps
-
else if not with_resources then
-
("resources", `Assoc [
-
("listChanged", `Bool false);
-
("subscribe", `Bool false)
-
]) :: caps
-
else
-
caps
-
in
-
let caps =
-
if with_prompts then
-
("prompts", `Assoc [
-
("listChanged", `Bool true)
-
]) :: caps
-
else if not with_prompts then
-
("prompts", `Assoc [
-
("listChanged", `Bool false)
-
]) :: caps
-
else
-
caps
-
in
-
`Assoc caps
+
in
+
register_prompt server prompt
+
+
(* Set server capabilities *)
+
let set_capabilities server capabilities =
+
server.capabilities <- capabilities
-
(* Update server capabilities *)
-
let update_capabilities server capabilities =
-
server.capabilities <- capabilities
+
(* Configure server with default capabilities based on registered components *)
+
let configure_server server ?with_tools ?with_resources ?with_prompts () =
+
let with_tools = match with_tools with
+
| Some b -> b
+
| None -> server.tools <> []
+
in
+
let with_resources = match with_resources with
+
| Some b -> b
+
| None -> server.resources <> []
+
in
+
let with_prompts = match with_prompts with
+
| Some b -> b
+
| None -> server.prompts <> []
+
in
+
let capabilities = default_capabilities ~with_tools ~with_resources ~with_prompts () in
+
set_capabilities server capabilities;
+
server
-
(* Process a message *)
-
let process_message _server _json =
-
None
-
-
(* Main server loop *)
-
let run _server =
-
(* Placeholder implementation *)
-
()
-
end
+
(* Set startup and shutdown hooks *)
+
let set_startup_hook server hook =
+
server.startup_hook <- Some hook
-
(* Helper function for default capabilities *)
-
let default_capabilities = Server.default_capabilities
+
let set_shutdown_hook server hook =
+
server.shutdown_hook <- Some hook
-
(* Add syntactic sugar for creating a server *)
-
module MakeServer(S: sig val name: string val version: string option end) = struct
-
let _config = (S.name, S.version) (* Used to prevent unused parameter warning *)
+
(* Run the server *)
+
let run_server server =
+
(* Setup *)
+
Printexc.record_backtrace true;
+
set_binary_mode_out stdout false;
-
(* Create server *)
-
let server = Server.create
-
~name:S.name
-
?version:S.version
-
~protocol_version:"2024-11-05"
-
()
-
-
(* Create a tool *)
-
let tool ?name ?description ?(schema_properties=[]) ?(schema_required=[]) handler =
-
let name = match name with
-
| Some (Some n) -> n
-
| Some None | None -> "tool" in
-
let input_schema = make_tool_schema schema_properties schema_required in
-
let handler' ctx args =
-
try
-
Ok (handler args)
-
with exn ->
-
Error (Printexc.to_string exn)
-
in
-
let tool = Tool.create
-
~name
-
?description
-
~input_schema
-
~handler:handler'
-
()
-
in
-
server.tools <- tool :: server.tools;
-
tool
-
-
(* Create a resource *)
-
let resource ?uri_template ?description ?mime_type handler =
-
let uri_template = match uri_template with
-
| Some (Some uri) -> uri
-
| Some None | None -> "resource://" in
-
let handler' ctx params =
-
try
-
Ok (handler params)
-
with exn ->
-
Error (Printexc.to_string exn)
-
in
-
let resource = Resource.create
-
~uri_template
-
?description
-
?mime_type
-
~handler:handler'
-
()
-
in
-
server.resources <- resource :: server.resources;
-
resource
-
-
(* Create a prompt *)
-
let prompt ?name ?description ?(arguments=[]) handler =
-
let name = match name with
-
| Some (Some n) -> n
-
| Some None | None -> "prompt" in
-
let prompt_args = List.map (fun (name, desc, required) ->
-
Prompt.create_argument ~name ?description:desc ~required ()
-
) arguments in
-
let handler' ctx args =
-
try
-
Ok (handler args)
-
with exn ->
-
Error (Printexc.to_string exn)
-
in
-
let prompt = Prompt.create
-
~name
-
?description
-
~arguments:prompt_args
-
~handler:handler'
-
()
-
in
-
server.prompts <- prompt :: server.prompts;
-
prompt
-
-
(* Run the server *)
-
let run ?with_tools ?with_resources ?with_prompts () =
-
let with_tools = match with_tools with
-
| Some b -> b
-
| None -> server.tools <> []
-
in
-
let with_resources = match with_resources with
-
| Some b -> b
-
| None -> server.resources <> []
-
in
-
let with_prompts = match with_prompts with
-
| Some b -> b
-
| None -> server.prompts <> []
-
in
-
let capabilities = Server.default_capabilities ~with_tools ~with_resources ~with_prompts () in
-
server.capabilities <- capabilities;
-
Log.info "Starting server...";
-
Log.info (Printf.sprintf "Server info: %s v%s" server.name
-
(match S.version with Some v -> v | None -> "unknown"));
-
Printexc.record_backtrace true;
-
set_binary_mode_out stdout false;
-
Log.info "This is just a placeholder server implementation."
-
end
+
Log.info (Printf.sprintf "%s server started" server.name);
+
Log.debug (Printf.sprintf "Protocol version: %s" server.protocol_version);
+
Log.debug (Printf.sprintf "Server info: %s v%s" server.name server.version);
+
+
(* Initialize capabilities if not already set *)
+
if server.capabilities = `Assoc [] then
+
ignore (configure_server server ());
+
+
(* Run startup hook if provided *)
+
(match server.startup_hook with
+
| Some hook -> hook ()
+
| None -> ());
+
+
Log.info "Server initialized and ready."
+50 -40
lib/mcp_sdk.mli
···
val to_json : t -> Json.t
end
-
(** Server implementation *)
-
module Server : sig
-
type startup_hook = unit -> unit
-
type shutdown_hook = unit -> unit
+
(** Main server type *)
+
type server = {
+
name: string;
+
version: string;
+
protocol_version: string;
+
mutable capabilities: Json.t;
+
mutable tools: Tool.t list;
+
mutable resources: Resource.t list;
+
mutable prompts: Prompt.t list;
+
mutable lifespan_context: (string * Json.t) list;
+
mutable startup_hook: (unit -> unit) option;
+
mutable shutdown_hook: (unit -> unit) option;
+
}
-
type t = {
-
name: string;
-
version: string;
-
protocol_version: string;
-
mutable capabilities: Json.t;
-
mutable tools: Tool.t list;
-
mutable resources: Resource.t list;
-
mutable prompts: Prompt.t list;
-
mutable lifespan_context: (string * Json.t) list;
-
startup_hook: startup_hook option;
-
shutdown_hook: shutdown_hook option;
-
}
+
(** Create a new server *)
+
val create_server : name:string -> ?version:string -> ?protocol_version:string -> unit -> server
-
val create : name:string -> ?version:string -> ?protocol_version:string -> ?startup_hook:startup_hook -> ?shutdown_hook:shutdown_hook -> unit -> t
-
val register_tool : t -> Tool.t -> unit
-
val register_resource : t -> Resource.t -> unit
-
val register_prompt : t -> Prompt.t -> unit
-
val default_capabilities : ?with_tools:bool -> ?with_resources:bool -> ?with_prompts:bool -> unit -> Json.t
-
val update_capabilities : t -> Json.t -> unit
+
(** Default capabilities for the server *)
+
val default_capabilities : ?with_tools:bool -> ?with_resources:bool -> ?with_prompts:bool -> unit -> Json.t
-
val process_message : t -> Json.t -> JSONRPCMessage.t option
-
val run : t -> unit
-
end
+
(** Register a tool with the server *)
+
val register_tool : server -> Tool.t -> Tool.t
-
(** Helper functions for creating common objects *)
-
val make_text_content : string -> content
-
val make_tool_schema : (string * string * string) list -> string list -> Json.t
+
(** Create and register a tool in one step *)
+
val add_tool : server -> name:string -> ?description:string -> ?schema_properties:(string * string * string) list -> ?schema_required:string list -> (Json.t -> Json.t) -> Tool.t
+
+
(** Register a resource with the server *)
+
val register_resource : server -> Resource.t -> Resource.t
+
+
(** Create and register a resource in one step *)
+
val add_resource : server -> uri_template:string -> ?description:string -> ?mime_type:string -> (string list -> string) -> Resource.t
+
+
(** Register a prompt with the server *)
+
val register_prompt : server -> Prompt.t -> Prompt.t
+
+
(** Create and register a prompt in one step *)
+
val add_prompt : server -> name:string -> ?description:string -> ?arguments:(string * string option * bool) list -> ((string * string) list -> Prompt.message list) -> Prompt.t
+
+
(** Set server capabilities *)
+
val set_capabilities : server -> Json.t -> unit
+
+
(** Configure server with default capabilities based on registered components *)
+
val configure_server : server -> ?with_tools:bool -> ?with_resources:bool -> ?with_prompts:bool -> unit -> server
+
+
(** Set startup hook *)
+
val set_startup_hook : server -> (unit -> unit) -> unit
-
(** Syntax sugar for creating an MCP server *)
-
module MakeServer : functor (S : sig
-
val name : string
-
val version : string option
-
end) -> sig
-
val _config : string * string option (* Used to prevent unused parameter warning *)
-
val server : Server.t
+
(** Set shutdown hook *)
+
val set_shutdown_hook : server -> (unit -> unit) -> unit
-
val tool : ?name:string option -> ?description:string -> ?schema_properties:(string * string * string) list -> ?schema_required:string list -> (Json.t -> Json.t) -> Tool.t
-
val resource : ?uri_template:string option -> ?description:string -> ?mime_type:string -> (string list -> string) -> Resource.t
-
val prompt : ?name:string option -> ?description:string -> ?arguments:(string * string option * bool) list -> ((string * string) list -> Prompt.message list) -> Prompt.t
-
val run : ?with_tools:bool -> ?with_resources:bool -> ?with_prompts:bool -> unit -> unit
-
end
+
(** Run the server *)
+
val run_server : server -> unit
+
+
(** Helper functions for creating common objects *)
+
val make_text_content : string -> content
+
val make_tool_schema : (string * string * string) list -> string list -> Json.t