My agentic slop goes here. Not intended for anyone else!

more

+288
claudeio/lib_mcp/SERVER_SESSION_README.md
···
···
+
# MCP Server Session API
+
+
The `Server_session` module provides a high-level, easy-to-use API for creating MCP (Model Context Protocol) servers in OCaml using Eio.
+
+
## Overview
+
+
This module handles:
+
- **Initialization handshake**: Automatically handles the MCP initialization protocol
+
- **Request routing**: Routes incoming requests to your handler functions
+
- **Response encoding**: Automatically encodes responses using the correct MCP message types
+
- **Notification sending**: Provides convenient functions for sending notifications to clients
+
- **Error handling**: Returns proper JSON-RPC error responses for missing handlers
+
+
## Quick Start
+
+
```ocaml
+
open Mcp
+
+
(* Define your handlers *)
+
let list_tools ~cursor:_ =
+
let tool = Messages.Tools.make_tool
+
~name:"my_tool"
+
~description:"An example tool"
+
~input_schema:(Jsont.Object ([], Jsont.Meta.none))
+
()
+
in
+
Messages.Tools.make_list_result ~tools:[tool] ()
+
+
let call_tool ~name ~arguments =
+
match name with
+
| "my_tool" ->
+
Messages.Tools.make_call_result
+
~content:[Content.text "Tool result"]
+
()
+
| _ ->
+
Messages.Tools.make_call_result
+
~content:[Content.text "Unknown tool"]
+
~is_error:true
+
()
+
+
(* Configure your server *)
+
let config = {
+
Server_session.server_info = Capabilities.Implementation.make
+
~name:"my-server"
+
~version:"1.0.0";
+
server_capabilities = Capabilities.Server.make
+
~tools:(Capabilities.Tools.make ())
+
();
+
instructions = Some "My MCP server";
+
}
+
+
let handlers = {
+
Server_session.list_tools = Some list_tools;
+
call_tool = Some call_tool;
+
list_resources = None;
+
list_resource_templates = None;
+
read_resource = None;
+
subscribe_resource = None;
+
unsubscribe_resource = None;
+
list_prompts = None;
+
get_prompt = None;
+
complete = None;
+
ping = None;
+
}
+
+
(* Start the server *)
+
let () =
+
Eio_main.run @@ fun env ->
+
Eio.Switch.run @@ fun sw ->
+
let transport = (* create your transport *) in
+
let server = Server_session.create
+
~sw
+
~transport
+
config
+
handlers
+
in
+
(* Server is now running *)
+
```
+
+
## Architecture
+
+
### Initialization Flow
+
+
1. **Client sends Initialize request** → Server stores client capabilities and info
+
2. **Server responds with capabilities** → Returns server capabilities and info
+
3. **Client sends Initialized notification** → Server marks initialization complete
+
4. **Server is ready** → Now accepts requests and can send notifications
+
+
### Request Handling
+
+
When a request arrives:
+
+
1. The method name is matched against handler types
+
2. If a handler exists, it's called with the decoded parameters
+
3. The result is encoded and sent back
+
4. If no handler exists, a METHOD_NOT_FOUND error is returned
+
+
### Error Handling
+
+
The module automatically handles:
+
- Missing handlers → METHOD_NOT_FOUND error
+
- Invalid params → INVALID_PARAMS error
+
- Handler exceptions → INTERNAL_ERROR with exception message
+
- Pre-initialization requests → Rejected with error
+
+
## Supported Capabilities
+
+
The server can implement any combination of these capabilities:
+
+
### Resources
+
- `list_resources`: List available resources with optional pagination
+
- `list_resource_templates`: List resource templates
+
- `read_resource`: Read resource contents by URI
+
- `subscribe_resource`: Subscribe to resource updates
+
- `unsubscribe_resource`: Unsubscribe from updates
+
+
Notifications you can send:
+
- `send_resource_updated`: Notify about a specific resource update
+
- `send_resource_list_changed`: Notify that the resource list changed
+
+
### Tools
+
- `list_tools`: List available tools with optional pagination
+
- `call_tool`: Execute a tool by name with arguments
+
+
Notifications you can send:
+
- `send_tool_list_changed`: Notify that the tool list changed
+
+
### Prompts
+
- `list_prompts`: List available prompts with optional pagination
+
- `get_prompt`: Get a prompt by name with arguments
+
+
Notifications you can send:
+
- `send_prompt_list_changed`: Notify that the prompt list changed
+
+
### Other
+
- `complete`: Auto-completion suggestions
+
- `ping`: Keepalive handler
+
+
### Logging
+
- `send_log_message`: Send log messages to the client
+
+
### Progress
+
- `send_progress`: Send progress updates for long-running operations
+
+
## Handler Signatures
+
+
All handlers return strongly-typed message results:
+
+
```ocaml
+
type handlers = {
+
list_resources : (cursor:string option -> Messages.Resources.list_result) option;
+
read_resource : (uri:string -> Messages.Resources.read_result) option;
+
list_tools : (cursor:string option -> Messages.Tools.list_result) option;
+
call_tool : (name:string -> arguments:Jsont.json option -> Messages.Tools.call_result) option;
+
list_prompts : (cursor:string option -> Messages.Prompts.list_result) option;
+
get_prompt : (name:string -> arguments:(string * string) list option -> Messages.Prompts.get_result) option;
+
complete : (ref_:Messages.Completions.completion_ref -> argument:string -> Messages.Completions.result) option;
+
ping : (unit -> unit) option;
+
(* ... *)
+
}
+
```
+
+
Set a handler to `None` if you don't support that operation.
+
+
## Accessing Client Information
+
+
After initialization, you can query client capabilities:
+
+
```ocaml
+
let server = Server_session.create ~sw ~transport config handlers in
+
+
(* Get client capabilities *)
+
let client_caps = Server_session.client_capabilities server in
+
match client_caps.roots with
+
| Some _ -> (* Client supports roots *)
+
| None -> (* Client doesn't support roots *)
+
+
(* Get client info *)
+
let client_info = Server_session.client_info server in
+
Printf.printf "Connected to: %s v%s\n"
+
client_info.name
+
client_info.version;
+
+
(* Get protocol version *)
+
let version = Server_session.protocol_version server in
+
```
+
+
## Sending Notifications
+
+
Send notifications to inform clients of changes:
+
+
```ocaml
+
(* Resource was updated *)
+
Server_session.send_resource_updated server ~uri:"file:///example.txt";
+
+
(* Resource list changed *)
+
Server_session.send_resource_list_changed server;
+
+
(* Tool list changed *)
+
Server_session.send_tool_list_changed server;
+
+
(* Log a message *)
+
let log_data = Jsont.String ("Something happened", Jsont.Meta.none) in
+
Server_session.send_log_message server
+
~level:Messages.Logging.Info
+
~data:log_data
+
();
+
+
(* Report progress *)
+
Server_session.send_progress server
+
~progress_token:"operation-123"
+
~progress:0.5
+
~total:100.0
+
();
+
```
+
+
## Requesting from Client
+
+
Some servers may need to request information from clients:
+
+
```ocaml
+
(* Request the list of roots (if client supports it) *)
+
match Server_session.request_roots_list server with
+
| Some result ->
+
List.iter (fun root ->
+
Printf.printf "Root: %s\n" root.Messages.Roots.uri
+
) result.roots
+
| None ->
+
(* Client doesn't support roots capability *)
+
()
+
```
+
+
## Example: Simple Tool Server
+
+
See `examples/mcp_server_example.ml` for a complete example that demonstrates:
+
- Tool implementation (add, echo)
+
- Resource serving (example://greeting)
+
- Proper capability declaration
+
- Handler implementation
+
+
## Implementation Notes
+
+
### Thread Safety
+
The module uses Eio structured concurrency. All operations are safe within the same Eio domain. The Session module handles concurrent requests using Eio fibers.
+
+
### Timeout Support
+
You can configure request timeouts:
+
+
```ocaml
+
let server = Server_session.create
+
~sw
+
~transport
+
~timeout:30.0 (* 30 second timeout *)
+
~clock:(Session.C (Eio.Stdenv.clock env))
+
config
+
handlers
+
```
+
+
### Error Propagation
+
- Handler exceptions are caught and converted to INTERNAL_ERROR responses
+
- The server continues running after handler errors
+
- Use proper error handling in your handlers for better error messages
+
+
### Shutdown
+
The server runs as long as the Eio switch is active:
+
+
```ocaml
+
(* Explicit close *)
+
Server_session.close server;
+
+
(* Or let the switch handle cleanup *)
+
Eio.Switch.run @@ fun sw ->
+
let server = Server_session.create ~sw ~transport config handlers in
+
(* server auto-closes when switch exits *)
+
```
+
+
## Related Modules
+
+
- `Session`: Low-level bidirectional JSON-RPC session
+
- `Messages`: MCP protocol message types
+
- `Capabilities`: Capability negotiation types
+
- `Transport`: Transport layer abstraction
+
- `Content`: Content block types (text, image, etc.)
+
+
## References
+
+
- [MCP Specification](https://spec.modelcontextprotocol.io/)
+
- [JSON-RPC 2.0 Specification](https://www.jsonrpc.org/)
+284
claudeio/lib_mcp/capabilities.ml
···
···
+
(** MCP Capability negotiation types *)
+
+
(* Implementation Info *)
+
+
module Implementation = struct
+
type t = {
+
name : string;
+
version : string;
+
unknown : Jsont.json;
+
}
+
+
let make ~name ~version =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ name; version; unknown }
+
+
let jsont : t Jsont.t =
+
let make name version unknown = { name; version; unknown } in
+
Jsont.Object.map ~kind:"Implementation" make
+
|> Jsont.Object.mem "name" Jsont.string ~enc:(fun i -> i.name)
+
|> Jsont.Object.mem "version" Jsont.string ~enc:(fun i -> i.version)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun i -> i.unknown)
+
|> Jsont.Object.finish
+
+
let pp fmt i =
+
Format.fprintf fmt "%s/%s" i.name i.version
+
end
+
+
(* Client Capabilities *)
+
+
module Sampling = struct
+
type t = {
+
context : bool option;
+
tools : bool option;
+
unknown : Jsont.json;
+
}
+
+
let empty =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ context = None; tools = None; unknown }
+
+
let make ?context ?tools () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ context; tools; unknown }
+
+
let jsont : t Jsont.t =
+
let make context tools unknown = { context; tools; unknown } in
+
Jsont.Object.map ~kind:"Sampling" make
+
|> Jsont.Object.opt_mem "context" Jsont.bool ~enc:(fun s -> s.context)
+
|> Jsont.Object.opt_mem "tools" Jsont.bool ~enc:(fun s -> s.tools)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun s -> s.unknown)
+
|> Jsont.Object.finish
+
end
+
+
module Elicitation = struct
+
type t = {
+
unknown : Jsont.json;
+
}
+
+
let empty =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ unknown }
+
+
let jsont : t Jsont.t =
+
let make unknown = { unknown } in
+
Jsont.Object.map ~kind:"Elicitation" make
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun e -> e.unknown)
+
|> Jsont.Object.finish
+
end
+
+
module Roots = struct
+
type t = {
+
list_changed : bool option;
+
unknown : Jsont.json;
+
}
+
+
let empty =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ list_changed = None; unknown }
+
+
let make ?list_changed () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ list_changed; unknown }
+
+
let jsont : t Jsont.t =
+
let make list_changed unknown = { list_changed; unknown } in
+
Jsont.Object.map ~kind:"Roots" make
+
|> Jsont.Object.opt_mem "listChanged" Jsont.bool ~enc:(fun r -> r.list_changed)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun r -> r.unknown)
+
|> Jsont.Object.finish
+
end
+
+
module Client = struct
+
type t = {
+
sampling : Sampling.t option;
+
elicitation : Elicitation.t option;
+
roots : Roots.t option;
+
experimental : Jsont.json option;
+
unknown : Jsont.json;
+
}
+
+
let empty =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ sampling = None; elicitation = None; roots = None; experimental = None; unknown }
+
+
let make ?sampling ?elicitation ?roots ?experimental () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ sampling; elicitation; roots; experimental; unknown }
+
+
let jsont : t Jsont.t =
+
let make sampling elicitation roots experimental unknown =
+
{ sampling; elicitation; roots; experimental; unknown }
+
in
+
Jsont.Object.map ~kind:"ClientCapabilities" make
+
|> Jsont.Object.opt_mem "sampling" Sampling.jsont ~enc:(fun c -> c.sampling)
+
|> Jsont.Object.opt_mem "elicitation" Elicitation.jsont ~enc:(fun c -> c.elicitation)
+
|> Jsont.Object.opt_mem "roots" Roots.jsont ~enc:(fun c -> c.roots)
+
|> Jsont.Object.opt_mem "experimental" Jsont.json ~enc:(fun c -> c.experimental)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun c -> c.unknown)
+
|> Jsont.Object.finish
+
+
let pp fmt c =
+
let caps = [
+
(match c.sampling with Some _ -> Some "sampling" | None -> None);
+
(match c.elicitation with Some _ -> Some "elicitation" | None -> None);
+
(match c.roots with Some _ -> Some "roots" | None -> None);
+
] |> List.filter_map Fun.id in
+
Format.fprintf fmt "[%s]" (String.concat ", " caps)
+
end
+
+
(* Server Capabilities *)
+
+
module Logging = struct
+
type t = {
+
unknown : Jsont.json;
+
}
+
+
let empty =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ unknown }
+
+
let jsont : t Jsont.t =
+
let make unknown = { unknown } in
+
Jsont.Object.map ~kind:"Logging" make
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun l -> l.unknown)
+
|> Jsont.Object.finish
+
end
+
+
module Prompts = struct
+
type t = {
+
list_changed : bool option;
+
unknown : Jsont.json;
+
}
+
+
let empty =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ list_changed = None; unknown }
+
+
let make ?list_changed () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ list_changed; unknown }
+
+
let jsont : t Jsont.t =
+
let make list_changed unknown = { list_changed; unknown } in
+
Jsont.Object.map ~kind:"Prompts" make
+
|> Jsont.Object.opt_mem "listChanged" Jsont.bool ~enc:(fun p -> p.list_changed)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun p -> p.unknown)
+
|> Jsont.Object.finish
+
end
+
+
module Resources = struct
+
type t = {
+
subscribe : bool option;
+
list_changed : bool option;
+
unknown : Jsont.json;
+
}
+
+
let empty =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ subscribe = None; list_changed = None; unknown }
+
+
let make ?subscribe ?list_changed () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ subscribe; list_changed; unknown }
+
+
let jsont : t Jsont.t =
+
let make subscribe list_changed unknown =
+
{ subscribe; list_changed; unknown }
+
in
+
Jsont.Object.map ~kind:"Resources" make
+
|> Jsont.Object.opt_mem "subscribe" Jsont.bool ~enc:(fun r -> r.subscribe)
+
|> Jsont.Object.opt_mem "listChanged" Jsont.bool ~enc:(fun r -> r.list_changed)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun r -> r.unknown)
+
|> Jsont.Object.finish
+
end
+
+
module Tools = struct
+
type t = {
+
list_changed : bool option;
+
unknown : Jsont.json;
+
}
+
+
let empty =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ list_changed = None; unknown }
+
+
let make ?list_changed () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ list_changed; unknown }
+
+
let jsont : t Jsont.t =
+
let make list_changed unknown = { list_changed; unknown } in
+
Jsont.Object.map ~kind:"Tools" make
+
|> Jsont.Object.opt_mem "listChanged" Jsont.bool ~enc:(fun t -> t.list_changed)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun t -> t.unknown)
+
|> Jsont.Object.finish
+
end
+
+
module Completions = struct
+
type t = {
+
unknown : Jsont.json;
+
}
+
+
let empty =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ unknown }
+
+
let jsont : t Jsont.t =
+
let make unknown = { unknown } in
+
Jsont.Object.map ~kind:"Completions" make
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun c -> c.unknown)
+
|> Jsont.Object.finish
+
end
+
+
module Server = struct
+
type t = {
+
logging : Logging.t option;
+
prompts : Prompts.t option;
+
resources : Resources.t option;
+
tools : Tools.t option;
+
completions : Completions.t option;
+
experimental : Jsont.json option;
+
unknown : Jsont.json;
+
}
+
+
let empty =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{
+
logging = None;
+
prompts = None;
+
resources = None;
+
tools = None;
+
completions = None;
+
experimental = None;
+
unknown;
+
}
+
+
let make ?logging ?prompts ?resources ?tools ?completions ?experimental () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ logging; prompts; resources; tools; completions; experimental; unknown }
+
+
let jsont : t Jsont.t =
+
let make logging prompts resources tools completions experimental unknown =
+
{ logging; prompts; resources; tools; completions; experimental; unknown }
+
in
+
Jsont.Object.map ~kind:"ServerCapabilities" make
+
|> Jsont.Object.opt_mem "logging" Logging.jsont ~enc:(fun s -> s.logging)
+
|> Jsont.Object.opt_mem "prompts" Prompts.jsont ~enc:(fun s -> s.prompts)
+
|> Jsont.Object.opt_mem "resources" Resources.jsont ~enc:(fun s -> s.resources)
+
|> Jsont.Object.opt_mem "tools" Tools.jsont ~enc:(fun s -> s.tools)
+
|> Jsont.Object.opt_mem "completions" Completions.jsont ~enc:(fun s -> s.completions)
+
|> Jsont.Object.opt_mem "experimental" Jsont.json ~enc:(fun s -> s.experimental)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun s -> s.unknown)
+
|> Jsont.Object.finish
+
+
let pp fmt s =
+
let caps = [
+
(match s.logging with Some _ -> Some "logging" | None -> None);
+
(match s.prompts with Some _ -> Some "prompts" | None -> None);
+
(match s.resources with Some _ -> Some "resources" | None -> None);
+
(match s.tools with Some _ -> Some "tools" | None -> None);
+
(match s.completions with Some _ -> Some "completions" | None -> None);
+
] |> List.filter_map Fun.id in
+
Format.fprintf fmt "[%s]" (String.concat ", " caps)
+
end
+161
claudeio/lib_mcp/capabilities.mli
···
···
+
(** MCP Capability negotiation types.
+
+
Capabilities are exchanged during initialization to determine what features
+
the client and server support. *)
+
+
(** {1 Implementation Info} *)
+
+
module Implementation : sig
+
type t = {
+
name : string;
+
version : string;
+
unknown : Jsont.json;
+
}
+
(** Information about client or server implementation *)
+
+
val make : name:string -> version:string -> t
+
val jsont : t Jsont.t
+
val pp : Format.formatter -> t -> unit
+
end
+
+
(** {1 Client Capabilities} *)
+
+
module Sampling : sig
+
type t = {
+
context : bool option;
+
tools : bool option;
+
unknown : Jsont.json;
+
}
+
(** Sampling capability (for servers to request LLM sampling from clients) *)
+
+
val empty : t
+
val make : ?context:bool -> ?tools:bool -> unit -> t
+
val jsont : t Jsont.t
+
end
+
+
module Elicitation : sig
+
type t = {
+
unknown : Jsont.json;
+
}
+
(** Elicitation capability (for servers to request user input) *)
+
+
val empty : t
+
val jsont : t Jsont.t
+
end
+
+
module Roots : sig
+
type t = {
+
list_changed : bool option;
+
unknown : Jsont.json;
+
}
+
(** Roots capability (for servers to query filesystem roots) *)
+
+
val empty : t
+
val make : ?list_changed:bool -> unit -> t
+
val jsont : t Jsont.t
+
end
+
+
module Client : sig
+
type t = {
+
sampling : Sampling.t option;
+
elicitation : Elicitation.t option;
+
roots : Roots.t option;
+
experimental : Jsont.json option;
+
unknown : Jsont.json;
+
}
+
(** Client capabilities advertised during initialization *)
+
+
val empty : t
+
val make :
+
?sampling:Sampling.t ->
+
?elicitation:Elicitation.t ->
+
?roots:Roots.t ->
+
?experimental:Jsont.json ->
+
unit -> t
+
val jsont : t Jsont.t
+
val pp : Format.formatter -> t -> unit
+
end
+
+
(** {1 Server Capabilities} *)
+
+
module Logging : sig
+
type t = {
+
unknown : Jsont.json;
+
}
+
(** Logging capability *)
+
+
val empty : t
+
val jsont : t Jsont.t
+
end
+
+
module Prompts : sig
+
type t = {
+
list_changed : bool option;
+
unknown : Jsont.json;
+
}
+
(** Prompts capability *)
+
+
val empty : t
+
val make : ?list_changed:bool -> unit -> t
+
val jsont : t Jsont.t
+
end
+
+
module Resources : sig
+
type t = {
+
subscribe : bool option;
+
list_changed : bool option;
+
unknown : Jsont.json;
+
}
+
(** Resources capability *)
+
+
val empty : t
+
val make : ?subscribe:bool -> ?list_changed:bool -> unit -> t
+
val jsont : t Jsont.t
+
end
+
+
module Tools : sig
+
type t = {
+
list_changed : bool option;
+
unknown : Jsont.json;
+
}
+
(** Tools capability *)
+
+
val empty : t
+
val make : ?list_changed:bool -> unit -> t
+
val jsont : t Jsont.t
+
end
+
+
module Completions : sig
+
type t = {
+
unknown : Jsont.json;
+
}
+
(** Completions capability (for auto-complete) *)
+
+
val empty : t
+
val jsont : t Jsont.t
+
end
+
+
module Server : sig
+
type t = {
+
logging : Logging.t option;
+
prompts : Prompts.t option;
+
resources : Resources.t option;
+
tools : Tools.t option;
+
completions : Completions.t option;
+
experimental : Jsont.json option;
+
unknown : Jsont.json;
+
}
+
(** Server capabilities advertised during initialization *)
+
+
val empty : t
+
val make :
+
?logging:Logging.t ->
+
?prompts:Prompts.t ->
+
?resources:Resources.t ->
+
?tools:Tools.t ->
+
?completions:Completions.t ->
+
?experimental:Jsont.json ->
+
unit -> t
+
val jsont : t Jsont.t
+
val pp : Format.formatter -> t -> unit
+
end
+356
claudeio/lib_mcp/client_session.ml
···
···
+
(** High-level MCP client session implementation *)
+
+
(** {1 Configuration} *)
+
+
type config = {
+
client_info : Capabilities.Implementation.t;
+
client_capabilities : Capabilities.Client.t;
+
}
+
+
(** {1 Internal State} *)
+
+
exception Initialization_error of string
+
+
type notification_handlers = {
+
mutable on_resource_updated : (uri:string -> unit) option;
+
mutable on_resource_list_changed : (unit -> unit) option;
+
mutable on_tool_list_changed : (unit -> unit) option;
+
mutable on_prompt_list_changed : (unit -> unit) option;
+
mutable on_log_message : (level:Messages.Logging.level -> logger:string option -> data:Jsont.json -> unit) option;
+
}
+
+
type t = {
+
session : Session.t;
+
server_capabilities : Capabilities.Server.t;
+
server_info : Capabilities.Implementation.t;
+
server_instructions : string option;
+
handlers : notification_handlers;
+
}
+
+
(** {1 Helper Functions} *)
+
+
(* Encode a value to JSON using jsont codec *)
+
let encode codec value =
+
match Jsont.Json.encode codec value with
+
| Ok json -> json
+
| Error msg -> failwith ("Failed to encode: " ^ msg)
+
+
(* Decode a JSON value using jsont codec *)
+
let decode codec json =
+
match Jsont.Json.decode codec json with
+
| Ok value -> value
+
| Error msg -> failwith ("Failed to decode: " ^ msg)
+
+
(* Parse notification parameters - returns None if params is None or null *)
+
let parse_notification_params codec params_opt =
+
match params_opt with
+
| None -> None
+
| Some (Jsont.Null _) -> None
+
| Some json -> Some (decode codec json)
+
+
(** {1 Notification Routing} *)
+
+
let create_notification_handler handlers : Session.notification_handler =
+
fun ~method_ ~params ->
+
match method_ with
+
| "notifications/resources/updated" ->
+
(match handlers.on_resource_updated with
+
| None -> ()
+
| Some handler ->
+
let notif = parse_notification_params
+
Messages.Resources.updated_notification_jsont params in
+
(match notif with
+
| None -> ()
+
| Some n -> handler ~uri:n.Messages.Resources.uri))
+
+
| "notifications/resources/list_changed" ->
+
(match handlers.on_resource_list_changed with
+
| None -> ()
+
| Some handler -> handler ())
+
+
| "notifications/tools/list_changed" ->
+
(match handlers.on_tool_list_changed with
+
| None -> ()
+
| Some handler -> handler ())
+
+
| "notifications/prompts/list_changed" ->
+
(match handlers.on_prompt_list_changed with
+
| None -> ()
+
| Some handler -> handler ())
+
+
| "notifications/message" ->
+
(match handlers.on_log_message with
+
| None -> ()
+
| Some handler ->
+
let notif = parse_notification_params
+
Messages.Logging.notification_jsont params in
+
(match notif with
+
| None -> ()
+
| Some n ->
+
let data = match n.Messages.Logging.data with
+
| None -> Jsont.Null ((), Jsont.Meta.none)
+
| Some d -> d
+
in
+
handler
+
~level:n.Messages.Logging.level
+
~logger:n.Messages.Logging.logger
+
~data))
+
+
| _ ->
+
(* Unknown notification - ignore *)
+
()
+
+
(** {1 Request Handler} *)
+
+
(* Client doesn't expect to receive requests from server in most cases *)
+
let create_request_handler () : Session.request_handler =
+
fun ~method_ ~params:_ ->
+
(* Default: return method not found error *)
+
let error = Jsonrpc.Error_data.make
+
~code:Method_not_found
+
~message:(Printf.sprintf "Client does not handle method: %s" method_)
+
()
+
in
+
raise (Session.Remote_error error)
+
+
(** {1 Initialization} *)
+
+
let perform_initialization session config =
+
(* Send Initialize request *)
+
let init_params = Messages.Initialize.make_request_params
+
~protocol_version:"2024-11-05"
+
~capabilities:config.client_capabilities
+
~client_info:config.client_info
+
()
+
in
+
let params_json = encode Messages.Initialize.request_params_jsont init_params in
+
+
let response_json = Session.send_request session
+
~method_:Messages.Initialize.method_
+
~params:params_json
+
()
+
in
+
+
(* Decode Initialize result *)
+
let init_result = decode Messages.Initialize.result_jsont response_json in
+
+
(* Send Initialized notification *)
+
let initialized_notif = Messages.Initialized.make_notification () in
+
let notif_json = encode Messages.Initialized.notification_jsont initialized_notif in
+
Session.send_notification session
+
~method_:Messages.Initialized.method_
+
~params:notif_json
+
();
+
+
(* Return server info *)
+
(init_result.Messages.Initialize.capabilities,
+
init_result.Messages.Initialize.server_info,
+
init_result.Messages.Initialize.instructions)
+
+
(** {1 Public API} *)
+
+
let create ~sw ~transport ?timeout ?clock config =
+
(* Create notification handlers *)
+
let handlers = {
+
on_resource_updated = None;
+
on_resource_list_changed = None;
+
on_tool_list_changed = None;
+
on_prompt_list_changed = None;
+
on_log_message = None;
+
} in
+
+
(* Create session config *)
+
let session_config : Session.config = {
+
transport;
+
request_handler = create_request_handler ();
+
notification_handler = create_notification_handler handlers;
+
timeout;
+
clock;
+
} in
+
+
(* Create underlying session *)
+
let session = Session.create ~sw session_config in
+
+
try
+
(* Perform initialization handshake *)
+
let (server_capabilities, server_info, server_instructions) =
+
perform_initialization session config
+
in
+
+
(* Return client session *)
+
{
+
session;
+
server_capabilities;
+
server_info;
+
server_instructions;
+
handlers;
+
}
+
with
+
| Session.Remote_error err ->
+
Session.close session;
+
raise (Initialization_error
+
(Printf.sprintf "Server returned error: %s" err.Jsonrpc.Error_data.message))
+
| Session.Timeout msg ->
+
Session.close session;
+
raise (Initialization_error ("Initialization timeout: " ^ msg))
+
| exn ->
+
Session.close session;
+
raise (Initialization_error
+
(Printf.sprintf "Initialization failed: %s" (Printexc.to_string exn)))
+
+
(** {1 Server Information} *)
+
+
let server_capabilities t = t.server_capabilities
+
let server_info t = t.server_info
+
let server_instructions t = t.server_instructions
+
+
(** {1 Basic Operations} *)
+
+
let ping t =
+
let params = Messages.Ping.make_params () in
+
let params_json = encode Messages.Ping.params_jsont params in
+
let response_json = Session.send_request t.session
+
~method_:Messages.Ping.method_
+
~params:params_json
+
()
+
in
+
let _result = decode Messages.Ping.result_jsont response_json in
+
()
+
+
(** {1 Resources} *)
+
+
let list_resources t ?cursor () =
+
let request = Messages.Resources.make_list_request ?cursor () in
+
let params_json = encode Messages.Resources.list_request_jsont request in
+
let response_json = Session.send_request t.session
+
~method_:Messages.Resources.list_method
+
~params:params_json
+
()
+
in
+
decode Messages.Resources.list_result_jsont response_json
+
+
let read_resource t ~uri =
+
let request = Messages.Resources.make_read_request ~uri in
+
let params_json = encode Messages.Resources.read_request_jsont request in
+
let response_json = Session.send_request t.session
+
~method_:Messages.Resources.read_method
+
~params:params_json
+
()
+
in
+
decode Messages.Resources.read_result_jsont response_json
+
+
let subscribe_resource t ~uri =
+
let request = Messages.Resources.make_subscribe_request ~uri in
+
let params_json = encode Messages.Resources.subscribe_request_jsont request in
+
let _response_json = Session.send_request t.session
+
~method_:Messages.Resources.subscribe_method
+
~params:params_json
+
()
+
in
+
()
+
+
let unsubscribe_resource t ~uri =
+
let request = Messages.Resources.make_unsubscribe_request ~uri in
+
let params_json = encode Messages.Resources.unsubscribe_request_jsont request in
+
let _response_json = Session.send_request t.session
+
~method_:Messages.Resources.unsubscribe_method
+
~params:params_json
+
()
+
in
+
()
+
+
(** {1 Tools} *)
+
+
let list_tools t ?cursor () =
+
let request = Messages.Tools.make_list_request ?cursor () in
+
let params_json = encode Messages.Tools.list_request_jsont request in
+
let response_json = Session.send_request t.session
+
~method_:Messages.Tools.list_method
+
~params:params_json
+
()
+
in
+
decode Messages.Tools.list_result_jsont response_json
+
+
let call_tool t ~name ?arguments () =
+
let request = Messages.Tools.make_call_request ~name ?arguments () in
+
let params_json = encode Messages.Tools.call_request_jsont request in
+
let response_json = Session.send_request t.session
+
~method_:Messages.Tools.call_method
+
~params:params_json
+
()
+
in
+
decode Messages.Tools.call_result_jsont response_json
+
+
(** {1 Prompts} *)
+
+
let list_prompts t ?cursor () =
+
let request = Messages.Prompts.make_list_request ?cursor () in
+
let params_json = encode Messages.Prompts.list_request_jsont request in
+
let response_json = Session.send_request t.session
+
~method_:Messages.Prompts.list_method
+
~params:params_json
+
()
+
in
+
decode Messages.Prompts.list_result_jsont response_json
+
+
let get_prompt t ~name ?arguments () =
+
let request = Messages.Prompts.make_get_request ~name ?arguments () in
+
let params_json = encode Messages.Prompts.get_request_jsont request in
+
let response_json = Session.send_request t.session
+
~method_:Messages.Prompts.get_method
+
~params:params_json
+
()
+
in
+
decode Messages.Prompts.get_result_jsont response_json
+
+
(** {1 Completions} *)
+
+
let complete t ~ref ~argument =
+
let request = Messages.Completions.make_request ~ref_:ref ~argument () in
+
let params_json = encode Messages.Completions.request_jsont request in
+
let response_json = Session.send_request t.session
+
~method_:Messages.Completions.method_
+
~params:params_json
+
()
+
in
+
decode Messages.Completions.result_jsont response_json
+
+
(** {1 Logging} *)
+
+
let set_log_level t level =
+
(* Create a simple request with level parameter *)
+
let level_json = encode Messages.Logging.level_jsont level in
+
let params = Jsont.Object ([
+
(("level", Jsont.Meta.none), level_json)
+
], Jsont.Meta.none) in
+
let _response_json = Session.send_request t.session
+
~method_:"logging/setLevel"
+
~params
+
()
+
in
+
()
+
+
(** {1 Notification Handlers} *)
+
+
let on_resource_updated t handler =
+
t.handlers.on_resource_updated <- Some handler
+
+
let on_resource_list_changed t handler =
+
t.handlers.on_resource_list_changed <- Some handler
+
+
let on_tool_list_changed t handler =
+
t.handlers.on_tool_list_changed <- Some handler
+
+
let on_prompt_list_changed t handler =
+
t.handlers.on_prompt_list_changed <- Some handler
+
+
let on_log_message t handler =
+
t.handlers.on_log_message <- Some handler
+
+
(** {1 Session Control} *)
+
+
let close t =
+
Session.close t.session
+
+
let is_closed t =
+
Session.is_closed t.session
+217
claudeio/lib_mcp/client_session.mli
···
···
+
(** High-level MCP client session API.
+
+
This module provides a high-level client API for connecting to MCP servers.
+
It handles the initialization handshake, capability negotiation, and provides
+
typed methods for all MCP protocol operations.
+
+
{1 Example Usage}
+
+
{[
+
Eio_main.run @@ fun env ->
+
Eio.Switch.run @@ fun sw ->
+
let transport = Transport_stdio.create ~sw (module Eio.Stdenv : Eio.Stdenv.S with type t = _) env in
+
+
let config = {
+
client_info = Capabilities.Implementation.make
+
~name:"my-client"
+
~version:"1.0.0";
+
client_capabilities = Capabilities.Client.make
+
~roots:(Capabilities.Roots.make ~list_changed:true ())
+
();
+
} in
+
+
let client = Client_session.create ~sw ~transport config in
+
+
(* List available tools *)
+
let tools_result = Client_session.list_tools client () in
+
List.iter (fun tool ->
+
Printf.printf "Tool: %s\n" tool.Messages.Tools.name
+
) tools_result.Messages.Tools.tools;
+
+
(* Call a tool *)
+
let args = `Object [("query", `String "hello")] in
+
let result = Client_session.call_tool client
+
~name:"search"
+
~arguments:args
+
()
+
in
+
+
Client_session.close client
+
]} *)
+
+
(** {1 Configuration} *)
+
+
type config = {
+
client_info : Capabilities.Implementation.t;
+
(** Client implementation information (name and version) *)
+
client_capabilities : Capabilities.Client.t;
+
(** Client capabilities to advertise to server *)
+
}
+
(** Client session configuration *)
+
+
(** {1 Session Management} *)
+
+
type t
+
(** Client session handle *)
+
+
exception Initialization_error of string
+
(** Raised when initialization handshake fails *)
+
+
val create :
+
sw:Eio.Switch.t ->
+
transport:Transport.t ->
+
?timeout:float ->
+
?clock:Session.clock ->
+
config ->
+
t
+
(** Create a client session and perform the initialization handshake.
+
+
This sends an Initialize request to the server, stores the server's
+
capabilities and info, then sends an Initialized notification.
+
+
@param sw Switch for background fibers
+
@param transport Transport layer for communication
+
@param timeout Optional request timeout in seconds
+
@param clock Clock for timeout handling (required if timeout is set)
+
@param config Client configuration
+
@raise Initialization_error if the handshake fails
+
@raise Session.Remote_error if the server returns an error
+
@raise Session.Timeout if the initialize request times out *)
+
+
(** {1 Server Information} *)
+
+
val server_capabilities : t -> Capabilities.Server.t
+
(** Get the server's advertised capabilities from initialization *)
+
+
val server_info : t -> Capabilities.Implementation.t
+
(** Get the server's implementation info (name and version) *)
+
+
val server_instructions : t -> string option
+
(** Get optional server instructions from initialization *)
+
+
(** {1 Basic Operations} *)
+
+
val ping : t -> unit
+
(** Send a ping request to the server (keepalive).
+
@raise Session.Remote_error if the server returns an error
+
@raise Session.Timeout if the request times out
+
@raise Session.Session_closed if the session is closed *)
+
+
(** {1 Resources} *)
+
+
val list_resources : t -> ?cursor:string -> unit -> Messages.Resources.list_result
+
(** List available resources.
+
@param cursor Optional pagination cursor
+
@raise Session.Remote_error if the server returns an error
+
@raise Session.Timeout if the request times out
+
@raise Session.Session_closed if the session is closed *)
+
+
val read_resource : t -> uri:string -> Messages.Resources.read_result
+
(** Read resource contents by URI.
+
@param uri Resource URI to read
+
@raise Session.Remote_error if the server returns an error
+
@raise Session.Timeout if the request times out
+
@raise Session.Session_closed if the session is closed *)
+
+
val subscribe_resource : t -> uri:string -> unit
+
(** Subscribe to resource update notifications.
+
@param uri Resource URI to subscribe to
+
@raise Session.Remote_error if the server returns an error
+
@raise Session.Timeout if the request times out
+
@raise Session.Session_closed if the session is closed *)
+
+
val unsubscribe_resource : t -> uri:string -> unit
+
(** Unsubscribe from resource update notifications.
+
@param uri Resource URI to unsubscribe from
+
@raise Session.Remote_error if the server returns an error
+
@raise Session.Timeout if the request times out
+
@raise Session.Session_closed if the session is closed *)
+
+
(** {1 Tools} *)
+
+
val list_tools : t -> ?cursor:string -> unit -> Messages.Tools.list_result
+
(** List available tools.
+
@param cursor Optional pagination cursor
+
@raise Session.Remote_error if the server returns an error
+
@raise Session.Timeout if the request times out
+
@raise Session.Session_closed if the session is closed *)
+
+
val call_tool : t -> name:string -> ?arguments:Jsont.json -> unit -> Messages.Tools.call_result
+
(** Call a tool by name.
+
@param name Tool name
+
@param arguments Optional tool arguments (JSON object)
+
@raise Session.Remote_error if the server returns an error
+
@raise Session.Timeout if the request times out
+
@raise Session.Session_closed if the session is closed *)
+
+
(** {1 Prompts} *)
+
+
val list_prompts : t -> ?cursor:string -> unit -> Messages.Prompts.list_result
+
(** List available prompts.
+
@param cursor Optional pagination cursor
+
@raise Session.Remote_error if the server returns an error
+
@raise Session.Timeout if the request times out
+
@raise Session.Session_closed if the session is closed *)
+
+
val get_prompt : t -> name:string -> ?arguments:(string * string) list -> unit -> Messages.Prompts.get_result
+
(** Get a prompt by name with optional arguments.
+
@param name Prompt name
+
@param arguments Optional key-value pairs for prompt arguments
+
@raise Session.Remote_error if the server returns an error
+
@raise Session.Timeout if the request times out
+
@raise Session.Session_closed if the session is closed *)
+
+
(** {1 Completions} *)
+
+
val complete : t -> ref:Messages.Completions.completion_ref -> argument:string -> Messages.Completions.result
+
(** Request auto-completion suggestions.
+
@param ref Completion reference (prompt or resource)
+
@param argument Argument value to complete
+
@raise Session.Remote_error if the server returns an error
+
@raise Session.Timeout if the request times out
+
@raise Session.Session_closed if the session is closed *)
+
+
(** {1 Logging} *)
+
+
val set_log_level : t -> Messages.Logging.level -> unit
+
(** Set the server's logging level.
+
Note: This sends a "logging/setLevel" request.
+
@param level Desired log level
+
@raise Session.Remote_error if the server returns an error
+
@raise Session.Timeout if the request times out
+
@raise Session.Session_closed if the session is closed *)
+
+
(** {1 Notification Handlers} *)
+
+
val on_resource_updated : t -> (uri:string -> unit) -> unit
+
(** Register a handler for resource update notifications.
+
The handler is called when a subscribed resource is updated.
+
Only one handler can be registered at a time (replaces previous handler). *)
+
+
val on_resource_list_changed : t -> (unit -> unit) -> unit
+
(** Register a handler for resource list change notifications.
+
The handler is called when the list of available resources changes.
+
Only one handler can be registered at a time (replaces previous handler). *)
+
+
val on_tool_list_changed : t -> (unit -> unit) -> unit
+
(** Register a handler for tool list change notifications.
+
The handler is called when the list of available tools changes.
+
Only one handler can be registered at a time (replaces previous handler). *)
+
+
val on_prompt_list_changed : t -> (unit -> unit) -> unit
+
(** Register a handler for prompt list change notifications.
+
The handler is called when the list of available prompts changes.
+
Only one handler can be registered at a time (replaces previous handler). *)
+
+
val on_log_message : t -> (level:Messages.Logging.level -> logger:string option -> data:Jsont.json -> unit) -> unit
+
(** Register a handler for log message notifications from the server.
+
Only one handler can be registered at a time (replaces previous handler). *)
+
+
(** {1 Session Control} *)
+
+
val close : t -> unit
+
(** Close the client session and underlying transport.
+
This is idempotent - safe to call multiple times. *)
+
+
val is_closed : t -> bool
+
(** Check if the session is closed. *)
+246
claudeio/lib_mcp/content.ml
···
···
+
(** MCP Content Block types *)
+
+
(* Annotations *)
+
+
module Audience = struct
+
type t = User | Assistant
+
+
let jsont : t Jsont.t =
+
Jsont.enum [
+
"user", User;
+
"assistant", Assistant;
+
]
+
+
let pp fmt = function
+
| User -> Format.fprintf fmt "user"
+
| Assistant -> Format.fprintf fmt "assistant"
+
end
+
+
module Annotations = struct
+
type t = {
+
audience : Audience.t list option;
+
priority : float option;
+
unknown : Jsont.json;
+
}
+
+
let empty =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ audience = None; priority = None; unknown }
+
+
let jsont : t Jsont.t =
+
let make audience priority unknown = { audience; priority; unknown } in
+
Jsont.Object.map ~kind:"Annotations" make
+
|> Jsont.Object.opt_mem "audience" (Jsont.list Audience.jsont)
+
~enc:(fun a -> a.audience)
+
|> Jsont.Object.opt_mem "priority" Jsont.number
+
~enc:(fun a -> a.priority)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun a -> a.unknown)
+
|> Jsont.Object.finish
+
+
let pp fmt _ann =
+
Format.fprintf fmt "{annotations}"
+
end
+
+
(* Text Content *)
+
+
module Text = struct
+
type t = {
+
text : string;
+
annotations : Annotations.t option;
+
unknown : Jsont.json;
+
}
+
+
let make text =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ text; annotations = None; unknown }
+
+
let jsont : t Jsont.t =
+
let make text annotations unknown = { text; annotations; unknown } in
+
Jsont.Object.map ~kind:"TextContent" make
+
|> Jsont.Object.mem "text" Jsont.string ~enc:(fun t -> t.text)
+
|> Jsont.Object.opt_mem "annotations" Annotations.jsont
+
~enc:(fun t -> t.annotations)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun t -> t.unknown)
+
|> Jsont.Object.finish
+
+
let pp fmt t =
+
Format.fprintf fmt "%S" t.text
+
end
+
+
(* Image Content *)
+
+
module Image = struct
+
type t = {
+
data : string;
+
mime_type : string;
+
annotations : Annotations.t option;
+
unknown : Jsont.json;
+
}
+
+
let make ~data ~mime_type =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ data; mime_type; annotations = None; unknown }
+
+
let jsont : t Jsont.t =
+
let make data mime_type annotations unknown =
+
{ data; mime_type; annotations; unknown }
+
in
+
Jsont.Object.map ~kind:"ImageContent" make
+
|> Jsont.Object.mem "data" Jsont.string ~enc:(fun i -> i.data)
+
|> Jsont.Object.mem "mimeType" Jsont.string ~enc:(fun i -> i.mime_type)
+
|> Jsont.Object.opt_mem "annotations" Annotations.jsont
+
~enc:(fun i -> i.annotations)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun i -> i.unknown)
+
|> Jsont.Object.finish
+
+
let pp fmt i =
+
Format.fprintf fmt "Image(%s, %d bytes)" i.mime_type (String.length i.data)
+
end
+
+
(* Audio Content *)
+
+
module Audio = struct
+
type t = {
+
data : string;
+
mime_type : string;
+
annotations : Annotations.t option;
+
unknown : Jsont.json;
+
}
+
+
let make ~data ~mime_type =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ data; mime_type; annotations = None; unknown }
+
+
let jsont : t Jsont.t =
+
let make data mime_type annotations unknown =
+
{ data; mime_type; annotations; unknown }
+
in
+
Jsont.Object.map ~kind:"AudioContent" make
+
|> Jsont.Object.mem "data" Jsont.string ~enc:(fun a -> a.data)
+
|> Jsont.Object.mem "mimeType" Jsont.string ~enc:(fun a -> a.mime_type)
+
|> Jsont.Object.opt_mem "annotations" Annotations.jsont
+
~enc:(fun a -> a.annotations)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun a -> a.unknown)
+
|> Jsont.Object.finish
+
+
let pp fmt a =
+
Format.fprintf fmt "Audio(%s, %d bytes)" a.mime_type (String.length a.data)
+
end
+
+
(* Embedded Resource *)
+
+
module Embedded_resource = struct
+
type resource = {
+
uri : string;
+
mime_type : string option;
+
text : string option;
+
blob : string option;
+
unknown : Jsont.json;
+
}
+
+
let resource_jsont : resource Jsont.t =
+
let make uri mime_type text blob unknown =
+
{ uri; mime_type; text; blob; unknown }
+
in
+
Jsont.Object.map ~kind:"Resource" make
+
|> Jsont.Object.mem "uri" Jsont.string ~enc:(fun r -> r.uri)
+
|> Jsont.Object.opt_mem "mimeType" Jsont.string ~enc:(fun r -> r.mime_type)
+
|> Jsont.Object.opt_mem "text" Jsont.string ~enc:(fun r -> r.text)
+
|> Jsont.Object.opt_mem "blob" Jsont.string ~enc:(fun r -> r.blob)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun r -> r.unknown)
+
|> Jsont.Object.finish
+
+
type t = {
+
resource : resource;
+
annotations : Annotations.t option;
+
unknown : Jsont.json;
+
}
+
+
let make_text ~uri ~text ?mime_type () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
let resource = {
+
uri;
+
mime_type;
+
text = Some text;
+
blob = None;
+
unknown;
+
} in
+
{ resource; annotations = None; unknown }
+
+
let make_blob ~uri ~blob ~mime_type =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
let resource = {
+
uri;
+
mime_type = Some mime_type;
+
text = None;
+
blob = Some blob;
+
unknown;
+
} in
+
{ resource; annotations = None; unknown }
+
+
let jsont : t Jsont.t =
+
let make resource annotations unknown =
+
{ resource; annotations; unknown }
+
in
+
Jsont.Object.map ~kind:"EmbeddedResource" make
+
|> Jsont.Object.mem "resource" resource_jsont ~enc:(fun e -> e.resource)
+
|> Jsont.Object.opt_mem "annotations" Annotations.jsont
+
~enc:(fun e -> e.annotations)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun e -> e.unknown)
+
|> Jsont.Object.finish
+
+
let pp fmt e =
+
Format.fprintf fmt "Resource(%s)" e.resource.uri
+
end
+
+
(* Content Block *)
+
+
type block =
+
| Text of Text.t
+
| Image of Image.t
+
| Audio of Audio.t
+
| Embedded_resource of Embedded_resource.t
+
+
let block_jsont : block Jsont.t =
+
(* Content blocks use "type" discriminator *)
+
let case_map kind obj dec = Jsont.Object.Case.map kind obj ~dec in
+
+
let case_text = case_map "text" Text.jsont (fun v -> Text v) in
+
let case_image = case_map "image" Image.jsont (fun v -> Image v) in
+
let case_audio = case_map "audio" Audio.jsont (fun v -> Audio v) in
+
let case_resource = case_map "resource" Embedded_resource.jsont
+
(fun v -> Embedded_resource v)
+
in
+
+
let enc_case = function
+
| Text v -> Jsont.Object.Case.value case_text v
+
| Image v -> Jsont.Object.Case.value case_image v
+
| Audio v -> Jsont.Object.Case.value case_audio v
+
| Embedded_resource v -> Jsont.Object.Case.value case_resource v
+
in
+
+
let cases = Jsont.Object.Case.[
+
make case_text;
+
make case_image;
+
make case_audio;
+
make case_resource;
+
] in
+
+
Jsont.Object.map ~kind:"ContentBlock" Fun.id
+
|> Jsont.Object.case_mem "type" Jsont.string ~enc:Fun.id ~enc_case cases
+
~tag_to_string:Fun.id ~tag_compare:String.compare
+
|> Jsont.Object.finish
+
+
let pp_block fmt = function
+
| Text t -> Text.pp fmt t
+
| Image i -> Image.pp fmt i
+
| Audio a -> Audio.pp fmt a
+
| Embedded_resource e -> Embedded_resource.pp fmt e
+
+
(* Convenience constructors *)
+
+
let text s = Text (Text.make s)
+
+
let image ~data ~mime_type = Image (Image.make ~data ~mime_type)
+
+
let audio ~data ~mime_type = Audio (Audio.make ~data ~mime_type)
+125
claudeio/lib_mcp/content.mli
···
···
+
(** MCP Content Block types.
+
+
Content blocks are the building blocks for tool results, prompts, and resource contents.
+
They support text, images, audio, embedded resources, and tool use/results. *)
+
+
(** {1 Annotations} *)
+
+
module Audience : sig
+
type t = User | Assistant
+
(** Who should see this content *)
+
+
val jsont : t Jsont.t
+
val pp : Format.formatter -> t -> unit
+
end
+
+
module Annotations : sig
+
type t = {
+
audience : Audience.t list option;
+
priority : float option;
+
unknown : Jsont.json;
+
}
+
(** Hints about content visibility and importance *)
+
+
val empty : t
+
val jsont : t Jsont.t
+
val pp : Format.formatter -> t -> unit
+
end
+
+
(** {1 Text Content} *)
+
+
module Text : sig
+
type t = {
+
text : string;
+
annotations : Annotations.t option;
+
unknown : Jsont.json;
+
}
+
(** Plain text content *)
+
+
val make : string -> t
+
val jsont : t Jsont.t
+
val pp : Format.formatter -> t -> unit
+
end
+
+
(** {1 Image Content} *)
+
+
module Image : sig
+
type t = {
+
data : string; (** Base64-encoded image data *)
+
mime_type : string; (** e.g. "image/png" *)
+
annotations : Annotations.t option;
+
unknown : Jsont.json;
+
}
+
(** Image content (base64-encoded) *)
+
+
val make : data:string -> mime_type:string -> t
+
val jsont : t Jsont.t
+
val pp : Format.formatter -> t -> unit
+
end
+
+
(** {1 Audio Content} *)
+
+
module Audio : sig
+
type t = {
+
data : string; (** Base64-encoded audio data *)
+
mime_type : string; (** e.g. "audio/mp3" *)
+
annotations : Annotations.t option;
+
unknown : Jsont.json;
+
}
+
(** Audio content (base64-encoded) *)
+
+
val make : data:string -> mime_type:string -> t
+
val jsont : t Jsont.t
+
val pp : Format.formatter -> t -> unit
+
end
+
+
(** {1 Embedded Resource} *)
+
+
module Embedded_resource : sig
+
type resource = {
+
uri : string;
+
mime_type : string option;
+
text : string option;
+
blob : string option; (** Base64-encoded binary data *)
+
unknown : Jsont.json;
+
}
+
(** Resource contents *)
+
+
type t = {
+
resource : resource;
+
annotations : Annotations.t option;
+
unknown : Jsont.json;
+
}
+
(** Embedded resource content *)
+
+
val make_text : uri:string -> text:string -> ?mime_type:string -> unit -> t
+
val make_blob : uri:string -> blob:string -> mime_type:string -> t
+
val jsont : t Jsont.t
+
val pp : Format.formatter -> t -> unit
+
end
+
+
(** {1 Content Block} *)
+
+
type block =
+
| Text of Text.t
+
| Image of Image.t
+
| Audio of Audio.t
+
| Embedded_resource of Embedded_resource.t
+
(** Content block variants *)
+
+
val block_jsont : block Jsont.t
+
(** Codec for content blocks (discriminated by "type" field) *)
+
+
val pp_block : Format.formatter -> block -> unit
+
(** Pretty-print a content block *)
+
+
(** {1 Convenience Constructors} *)
+
+
val text : string -> block
+
(** Create a text content block *)
+
+
val image : data:string -> mime_type:string -> block
+
(** Create an image content block *)
+
+
val audio : data:string -> mime_type:string -> block
+
(** Create an audio content block *)
+4
claudeio/lib_mcp/dune
···
···
+
(library
+
(name mcp)
+
(public_name mcp)
+
(libraries eio eio.unix fmt logs jsont jsont.bytesrw unix))
+279
claudeio/lib_mcp/jsonrpc.ml
···
···
+
(** JSON-RPC 2.0 protocol implementation *)
+
+
(* Protocol Version *)
+
+
type jsonrpc = [ `V2 ]
+
+
let jsonrpc_jsont = Jsont.enum ["2.0", `V2]
+
+
(* Request/Response Identifiers *)
+
+
module Id = struct
+
type t = [ `String of string | `Number of float | `Null ]
+
+
let jsont : t Jsont.t =
+
let null = Jsont.null `Null in
+
let string =
+
let dec s = `String s in
+
let enc = function `String s -> s | _ -> assert false in
+
Jsont.map ~dec ~enc Jsont.string
+
in
+
let number =
+
let dec n = `Number n in
+
let enc = function `Number n -> n | _ -> assert false in
+
Jsont.map ~dec ~enc Jsont.number
+
in
+
let enc = function
+
| `Null -> null | `String _ -> string | `Number _ -> number
+
in
+
Jsont.any ~dec_null:null ~dec_string:string ~dec_number:number ~enc ()
+
+
let to_string = function
+
| `String s -> s
+
| `Number n -> string_of_float n
+
| `Null -> "null"
+
+
let compare a b = match a, b with
+
| `Null, `Null -> 0
+
| `Null, _ -> -1
+
| _, `Null -> 1
+
| `String s1, `String s2 -> String.compare s1 s2
+
| `String _, _ -> -1
+
| _, `String _ -> 1
+
| `Number n1, `Number n2 -> Float.compare n1 n2
+
+
let pp fmt = function
+
| `String s -> Format.fprintf fmt "%S" s
+
| `Number n -> Format.fprintf fmt "%g" n
+
| `Null -> Format.fprintf fmt "null"
+
end
+
+
(* Error Codes *)
+
+
module Error_code = struct
+
type t =
+
| Parse_error
+
| Invalid_request
+
| Method_not_found
+
| Invalid_params
+
| Internal_error
+
| Connection_closed
+
| Server_error of int
+
| Other of int
+
+
let to_int = function
+
| Parse_error -> -32700
+
| Invalid_request -> -32600
+
| Method_not_found -> -32601
+
| Invalid_params -> -32602
+
| Internal_error -> -32603
+
| Connection_closed -> -32000
+
| Server_error n -> n
+
| Other n -> n
+
+
let of_int = function
+
| -32700 -> Parse_error
+
| -32600 -> Invalid_request
+
| -32601 -> Method_not_found
+
| -32602 -> Invalid_params
+
| -32603 -> Internal_error
+
| -32000 -> Connection_closed
+
| n when n >= -32099 && n <= -32001 -> Server_error n
+
| n -> Other n
+
+
let jsont : t Jsont.t =
+
let dec n = of_int n in
+
let enc code = to_int code in
+
Jsont.map ~dec ~enc Jsont.int
+
+
let pp fmt code =
+
Format.fprintf fmt "%d" (to_int code)
+
end
+
+
(* Error Data *)
+
+
module Error_data = struct
+
type t = {
+
code : Error_code.t;
+
message : string;
+
data : Jsont.json option;
+
unknown : Jsont.json;
+
}
+
+
let make ~code ~message ?data () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ code; message; data; unknown }
+
+
let jsont : t Jsont.t =
+
let make code message data unknown = { code; message; data; unknown } in
+
Jsont.Object.map ~kind:"ErrorData" make
+
|> Jsont.Object.mem "code" Error_code.jsont ~enc:(fun e -> e.code)
+
|> Jsont.Object.mem "message" Jsont.string ~enc:(fun e -> e.message)
+
|> Jsont.Object.opt_mem "data" Jsont.json ~enc:(fun e -> e.data)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun e -> e.unknown)
+
|> Jsont.Object.finish
+
+
let pp fmt err =
+
Format.fprintf fmt "{code=%a, message=%S}" Error_code.pp err.code err.message
+
end
+
+
(* Params *)
+
+
type params = Jsont.json
+
+
let params_jsont =
+
let enc = function
+
| Jsont.Object _ | Jsont.Array _ -> Jsont.json
+
| j ->
+
let meta = Jsont.Meta.none in
+
let fnd = Jsont.Sort.to_string (Jsont.Json.sort j) in
+
Jsont.Error.expected meta "object or array" ~fnd
+
in
+
let kind = "JSON-RPC params" in
+
Jsont.any ~kind ~dec_array:Jsont.json ~dec_object:Jsont.json ~enc ()
+
+
(* Request Message *)
+
+
module Request = struct
+
type t = {
+
jsonrpc : jsonrpc;
+
method_ : string;
+
params : params option;
+
id : Id.t option;
+
unknown : Jsont.json;
+
}
+
+
let make ~method_ ?params ?id () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ jsonrpc = `V2; method_; params; id; unknown }
+
+
let jsont : t Jsont.t =
+
let make jsonrpc method_ params id unknown =
+
{ jsonrpc; method_; params; id; unknown }
+
in
+
Jsont.Object.map ~kind:"JSONRPCRequest" make
+
|> Jsont.Object.mem "jsonrpc" jsonrpc_jsont ~enc:(fun r -> r.jsonrpc)
+
|> Jsont.Object.mem "method" Jsont.string ~enc:(fun r -> r.method_)
+
|> Jsont.Object.opt_mem "params" params_jsont ~enc:(fun r -> r.params)
+
|> Jsont.Object.opt_mem "id" Id.jsont ~enc:(fun r -> r.id)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun r -> r.unknown)
+
|> Jsont.Object.finish
+
+
let pp fmt req =
+
let id_str = match req.id with
+
| Some id -> Id.to_string id
+
| None -> "none"
+
in
+
Format.fprintf fmt "{method=%S, id=%s}" req.method_ id_str
+
end
+
+
(* Response Message *)
+
+
module Response = struct
+
type t = {
+
jsonrpc : jsonrpc;
+
value : (Jsont.json, Error_data.t) result;
+
id : Id.t;
+
unknown : Jsont.json;
+
}
+
+
let make_result ~id ~result =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ jsonrpc = `V2; value = Ok result; id; unknown }
+
+
let make_error ~id ~error =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ jsonrpc = `V2; value = Error error; id; unknown }
+
+
let response_result r = match r.value with Ok v -> Some v | Error _ -> None
+
let response_error r = match r.value with Ok _ -> None | Error e -> Some e
+
+
let response jsonrpc result error id : t =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
let err_both () =
+
Jsont.Error.msgf Jsont.Meta.none "Both %a and %a members are defined"
+
Jsont.Repr.pp_code "result" Jsont.Repr.pp_code "error"
+
in
+
let err_none () =
+
Jsont.Error.msgf Jsont.Meta.none "Missing either %a or %a member"
+
Jsont.Repr.pp_code "result" Jsont.Repr.pp_code "error"
+
in
+
match result, error with
+
| Some result, None -> { jsonrpc; value = Ok result; id; unknown }
+
| None, Some error -> { jsonrpc; value = Error error; id; unknown }
+
| Some _ , Some _ -> err_both ()
+
| None, None -> err_none ()
+
+
let jsont : t Jsont.t =
+
let make jsonrpc result error id unknown =
+
let resp = response jsonrpc result error id in
+
{ resp with unknown }
+
in
+
Jsont.Object.map ~kind:"JSONRPCResponse" make
+
|> Jsont.Object.mem "jsonrpc" jsonrpc_jsont ~enc:(fun r -> r.jsonrpc)
+
|> Jsont.Object.opt_mem "result" Jsont.json ~enc:response_result
+
|> Jsont.Object.opt_mem "error" Error_data.jsont ~enc:response_error
+
|> Jsont.Object.mem "id" Id.jsont ~enc:(fun r -> r.id)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun r -> r.unknown)
+
|> Jsont.Object.finish
+
+
let pp fmt resp =
+
let result_str = match resp.value with
+
| Ok _ -> "Ok(...)"
+
| Error err -> Format.asprintf "Error(%a)" Error_data.pp err
+
in
+
Format.fprintf fmt "{id=%a, %s}" Id.pp resp.id result_str
+
end
+
+
(* Message Union *)
+
+
module Message = struct
+
type t =
+
| Request of Request.t
+
| Response of Response.t
+
+
let classify json =
+
(* Detect message type by presence of fields:
+
- "method" -> Request
+
- "result" or "error" -> Response *)
+
match json with
+
| Jsont.Object (members, _) ->
+
let has_method = List.exists (fun ((name, _), _) -> name = "method") members in
+
let has_result_or_error =
+
List.exists (fun ((name, _), _) ->
+
name = "result" || name = "error"
+
) members
+
in
+
if has_method then
+
match Jsont.Json.decode Request.jsont json with
+
| Ok req -> Request req
+
| Error msg -> failwith ("Failed to decode request: " ^ msg)
+
else if has_result_or_error then
+
match Jsont.Json.decode Response.jsont json with
+
| Ok resp -> Response resp
+
| Error msg -> failwith ("Failed to decode response: " ^ msg)
+
else
+
failwith "Invalid JSON-RPC message: missing method or result/error"
+
| _ ->
+
failwith "Invalid JSON-RPC message: not an object"
+
+
let jsont : t Jsont.t =
+
let enc = function
+
| Request req ->
+
(match Jsont.Json.encode Request.jsont req with
+
| Ok json -> json
+
| Error msg -> failwith ("Failed to encode request: " ^ msg))
+
| Response resp ->
+
(match Jsont.Json.encode Response.jsont resp with
+
| Ok json -> json
+
| Error msg -> failwith ("Failed to encode response: " ^ msg))
+
in
+
let dec json =
+
classify json
+
in
+
Jsont.map ~kind:"JSONRPCMessage" ~dec ~enc Jsont.json
+
+
let pp fmt = function
+
| Request req -> Format.fprintf fmt "Request(%a)" Request.pp req
+
| Response resp -> Format.fprintf fmt "Response(%a)" Response.pp resp
+
end
+161
claudeio/lib_mcp/jsonrpc.mli
···
···
+
(** JSON-RPC 2.0 protocol implementation using jsont.
+
+
Based on the JSON-RPC 2.0 specification: https://www.jsonrpc.org/
+
+
This module provides type-safe encoding/decoding of JSON-RPC messages
+
with forward-compatible unknown field preservation. *)
+
+
(** {1 Protocol Version} *)
+
+
type jsonrpc = [ `V2 ]
+
(** JSON-RPC protocol version *)
+
+
val jsonrpc_jsont : jsonrpc Jsont.t
+
(** Codec for protocol version *)
+
+
(** {1 Request/Response Identifiers} *)
+
+
module Id : sig
+
type t = [ `String of string | `Number of float | `Null ]
+
(** Request/response correlation ID.
+
Can be a string, number, or null. *)
+
+
val jsont : t Jsont.t
+
(** Codec for IDs *)
+
+
val to_string : t -> string
+
(** Convert ID to string representation *)
+
+
val compare : t -> t -> int
+
(** Compare IDs for ordering *)
+
+
val pp : Format.formatter -> t -> unit
+
(** Pretty-print an ID *)
+
end
+
+
(** {1 Error Codes} *)
+
+
module Error_code : sig
+
type t =
+
| Parse_error (** -32700: Invalid JSON *)
+
| Invalid_request (** -32600: Invalid Request object *)
+
| Method_not_found (** -32601: Method does not exist *)
+
| Invalid_params (** -32602: Invalid method parameters *)
+
| Internal_error (** -32603: Internal JSON-RPC error *)
+
| Connection_closed (** -32000: MCP-specific: connection closed *)
+
| Server_error of int (** -32099 to -32000: Server error *)
+
| Other of int (** Implementation-defined error *)
+
+
val jsont : t Jsont.t
+
(** Codec for error codes *)
+
+
val to_int : t -> int
+
(** Convert error code to integer *)
+
+
val of_int : int -> t
+
(** Convert integer to error code *)
+
+
val pp : Format.formatter -> t -> unit
+
(** Pretty-print an error code *)
+
end
+
+
(** {1 Error Data} *)
+
+
module Error_data : sig
+
type t = {
+
code : Error_code.t;
+
message : string;
+
data : Jsont.json option;
+
unknown : Jsont.json;
+
}
+
(** Error information *)
+
+
val make : code:Error_code.t -> message:string -> ?data:Jsont.json -> unit -> t
+
(** Create error data *)
+
+
val jsont : t Jsont.t
+
(** Codec for error data *)
+
+
val pp : Format.formatter -> t -> unit
+
(** Pretty-print error data *)
+
end
+
+
(** {1 Params} *)
+
+
type params = Jsont.json
+
(** Parameters for requests (must be Array or Object) *)
+
+
val params_jsont : params Jsont.t
+
(** Codec for params (validates array or object) *)
+
+
(** {1 Request Message} *)
+
+
module Request : sig
+
type t = {
+
jsonrpc : jsonrpc;
+
method_ : string;
+
params : params option;
+
id : Id.t option;
+
unknown : Jsont.json;
+
}
+
(** JSON-RPC request.
+
- If [id] is [Some _], expects a response
+
- If [id] is [None], it's a notification (no response) *)
+
+
val make :
+
method_:string ->
+
?params:params ->
+
?id:Id.t ->
+
unit ->
+
t
+
(** Create a request *)
+
+
val jsont : t Jsont.t
+
(** Codec for requests *)
+
+
val pp : Format.formatter -> t -> unit
+
(** Pretty-print a request *)
+
end
+
+
(** {1 Response Message} *)
+
+
module Response : sig
+
type t = {
+
jsonrpc : jsonrpc;
+
value : (Jsont.json, Error_data.t) result;
+
id : Id.t;
+
unknown : Jsont.json;
+
}
+
(** JSON-RPC response.
+
Either contains [Ok result] or [Error error]. *)
+
+
val make_result : id:Id.t -> result:Jsont.json -> t
+
(** Create a successful response *)
+
+
val make_error : id:Id.t -> error:Error_data.t -> t
+
(** Create an error response *)
+
+
val jsont : t Jsont.t
+
(** Codec for responses *)
+
+
val pp : Format.formatter -> t -> unit
+
(** Pretty-print a response *)
+
end
+
+
(** {1 Message Union} *)
+
+
module Message : sig
+
type t =
+
| Request of Request.t
+
| Response of Response.t
+
(** Union of all JSON-RPC message types *)
+
+
val jsont : t Jsont.t
+
(** Codec for messages *)
+
+
val classify : Jsont.json -> t
+
(** Classify a JSON value as a specific message type *)
+
+
val pp : Format.formatter -> t -> unit
+
(** Pretty-print a message *)
+
end
+11
claudeio/lib_mcp/mcp.ml
···
···
+
(** Model Context Protocol (MCP) OCaml Implementation *)
+
+
module Jsonrpc = Jsonrpc
+
module Content = Content
+
module Capabilities = Capabilities
+
module Messages = Messages
+
module Session = Session
+
module Transport = Transport
+
module Transport_stdio = Transport_stdio
+
module Server_session = Server_session
+
module Client_session = Client_session
+63
claudeio/lib_mcp/mcp.mli
···
···
+
(** Model Context Protocol (MCP) OCaml Implementation.
+
+
This library provides a type-safe, Eio-based implementation of the Model Context Protocol,
+
using jsont for JSON serialization with forward-compatible unknown field preservation.
+
+
{1 Quick Start}
+
+
The MCP library is organized into several modules:
+
+
- {!Jsonrpc}: JSON-RPC 2.0 protocol layer
+
- {!Content}: Content block types (text, image, audio, resources)
+
- {!Capabilities}: Client and server capability negotiation
+
+
{1 Example}
+
+
{[
+
open Mcp
+
+
(* Create client capabilities *)
+
let client_caps = Capabilities.Client.make
+
~sampling:(Capabilities.Sampling.make ~tools:true ())
+
()
+
+
(* Create content blocks *)
+
let text_block = Content.text "Hello, MCP!"
+
let image_block = Content.image ~data:"..." ~mime_type:"image/png"
+
]}
+
+
{1 Design Principles}
+
+
- {b Type Safety}: All protocol types use jsont codecs for bidirectional JSON serialization
+
- {b Forward Compatibility}: Unknown fields are preserved in all types
+
- {b Eio Integration}: Uses Eio for structured concurrency
+
- {b Protocol Compliance}: Follows MCP specification exactly
+
+
{1 Modules} *)
+
+
(** JSON-RPC 2.0 protocol implementation *)
+
module Jsonrpc : module type of Jsonrpc
+
+
(** MCP content block types *)
+
module Content : module type of Content
+
+
(** Client and server capability negotiation *)
+
module Capabilities : module type of Capabilities
+
+
(** MCP protocol messages (initialize, resources, tools, prompts, logging, etc.) *)
+
module Messages : module type of Messages
+
+
(** Bidirectional JSON-RPC session management *)
+
module Session : module type of Session
+
+
(** Transport layer for JSON-RPC communication *)
+
module Transport : module type of Transport
+
+
(** Stdio transport implementation *)
+
module Transport_stdio : module type of Transport_stdio
+
+
(** High-level MCP server session API *)
+
module Server_session : module type of Server_session
+
+
(** High-level MCP client session API *)
+
module Client_session : module type of Client_session
+900
claudeio/lib_mcp/messages.ml
···
···
+
(** MCP Protocol Messages *)
+
+
(* Protocol Version *)
+
+
type protocol_version = string
+
+
let protocol_version_jsont = Jsont.string
+
+
(* Initialize Protocol *)
+
+
module Initialize = struct
+
type request_params = {
+
protocol_version : protocol_version;
+
capabilities : Capabilities.Client.t;
+
client_info : Capabilities.Implementation.t;
+
unknown : Jsont.json;
+
}
+
+
let make_request_params ~protocol_version ~capabilities ~client_info () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ protocol_version; capabilities; client_info; unknown }
+
+
let request_params_jsont : request_params Jsont.t =
+
let make protocol_version capabilities client_info unknown =
+
{ protocol_version; capabilities; client_info; unknown }
+
in
+
Jsont.Object.map ~kind:"InitializeRequestParams" make
+
|> Jsont.Object.mem "protocolVersion" protocol_version_jsont
+
~enc:(fun p -> p.protocol_version)
+
|> Jsont.Object.mem "capabilities" Capabilities.Client.jsont
+
~enc:(fun p -> p.capabilities)
+
|> Jsont.Object.mem "clientInfo" Capabilities.Implementation.jsont
+
~enc:(fun p -> p.client_info)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun p -> p.unknown)
+
|> Jsont.Object.finish
+
+
type result = {
+
protocol_version : protocol_version;
+
capabilities : Capabilities.Server.t;
+
server_info : Capabilities.Implementation.t;
+
instructions : string option;
+
unknown : Jsont.json;
+
}
+
+
let make_result ~protocol_version ~capabilities ~server_info ?instructions () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ protocol_version; capabilities; server_info; instructions; unknown }
+
+
let result_jsont : result Jsont.t =
+
let make protocol_version capabilities server_info instructions unknown =
+
{ protocol_version; capabilities; server_info; instructions; unknown }
+
in
+
Jsont.Object.map ~kind:"InitializeResult" make
+
|> Jsont.Object.mem "protocolVersion" protocol_version_jsont
+
~enc:(fun r -> r.protocol_version)
+
|> Jsont.Object.mem "capabilities" Capabilities.Server.jsont
+
~enc:(fun r -> r.capabilities)
+
|> Jsont.Object.mem "serverInfo" Capabilities.Implementation.jsont
+
~enc:(fun r -> r.server_info)
+
|> Jsont.Object.opt_mem "instructions" Jsont.string
+
~enc:(fun r -> r.instructions)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun r -> r.unknown)
+
|> Jsont.Object.finish
+
+
let method_ = "initialize"
+
end
+
+
module Initialized = struct
+
type notification = {
+
unknown : Jsont.json;
+
}
+
+
let make_notification () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ unknown }
+
+
let notification_jsont : notification Jsont.t =
+
let make unknown = { unknown } in
+
Jsont.Object.map ~kind:"InitializedNotification" make
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun n -> n.unknown)
+
|> Jsont.Object.finish
+
+
let method_ = "notifications/initialized"
+
end
+
+
module Ping = struct
+
type params = {
+
unknown : Jsont.json;
+
}
+
+
let make_params () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ unknown }
+
+
let params_jsont : params Jsont.t =
+
let make unknown = { unknown } in
+
Jsont.Object.map ~kind:"PingParams" make
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun p -> p.unknown)
+
|> Jsont.Object.finish
+
+
type result = {
+
unknown : Jsont.json;
+
}
+
+
let make_result () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ unknown }
+
+
let result_jsont : result Jsont.t =
+
let make unknown = { unknown } in
+
Jsont.Object.map ~kind:"PingResult" make
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun r -> r.unknown)
+
|> Jsont.Object.finish
+
+
let method_ = "ping"
+
end
+
+
(* Resources *)
+
+
module Resources = struct
+
type resource = {
+
uri : string;
+
name : string;
+
description : string option;
+
mime_type : string option;
+
unknown : Jsont.json;
+
}
+
+
let make_resource ~uri ~name ?description ?mime_type () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ uri; name; description; mime_type; unknown }
+
+
let resource_jsont : resource Jsont.t =
+
let make uri name description mime_type unknown =
+
{ uri; name; description; mime_type; unknown }
+
in
+
Jsont.Object.map ~kind:"Resource" make
+
|> Jsont.Object.mem "uri" Jsont.string ~enc:(fun r -> r.uri)
+
|> Jsont.Object.mem "name" Jsont.string ~enc:(fun r -> r.name)
+
|> Jsont.Object.opt_mem "description" Jsont.string ~enc:(fun r -> r.description)
+
|> Jsont.Object.opt_mem "mimeType" Jsont.string ~enc:(fun r -> r.mime_type)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun r -> r.unknown)
+
|> Jsont.Object.finish
+
+
type resource_template = {
+
uri_template : string;
+
name : string;
+
description : string option;
+
mime_type : string option;
+
unknown : Jsont.json;
+
}
+
+
let make_resource_template ~uri_template ~name ?description ?mime_type () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ uri_template; name; description; mime_type; unknown }
+
+
let resource_template_jsont : resource_template Jsont.t =
+
let make uri_template name description mime_type unknown =
+
{ uri_template; name; description; mime_type; unknown }
+
in
+
Jsont.Object.map ~kind:"ResourceTemplate" make
+
|> Jsont.Object.mem "uriTemplate" Jsont.string ~enc:(fun t -> t.uri_template)
+
|> Jsont.Object.mem "name" Jsont.string ~enc:(fun t -> t.name)
+
|> Jsont.Object.opt_mem "description" Jsont.string ~enc:(fun t -> t.description)
+
|> Jsont.Object.opt_mem "mimeType" Jsont.string ~enc:(fun t -> t.mime_type)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun t -> t.unknown)
+
|> Jsont.Object.finish
+
+
type resource_contents = {
+
uri : string;
+
mime_type : string option;
+
text : string option;
+
blob : string option;
+
unknown : Jsont.json;
+
}
+
+
let make_text_contents ~uri ~text ?mime_type () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ uri; mime_type; text = Some text; blob = None; unknown }
+
+
let make_blob_contents ~uri ~blob ~mime_type =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ uri; mime_type = Some mime_type; text = None; blob = Some blob; unknown }
+
+
let resource_contents_jsont : resource_contents Jsont.t =
+
let make uri mime_type text blob unknown =
+
{ uri; mime_type; text; blob; unknown }
+
in
+
Jsont.Object.map ~kind:"ResourceContents" make
+
|> Jsont.Object.mem "uri" Jsont.string ~enc:(fun c -> c.uri)
+
|> Jsont.Object.opt_mem "mimeType" Jsont.string ~enc:(fun c -> c.mime_type)
+
|> Jsont.Object.opt_mem "text" Jsont.string ~enc:(fun c -> c.text)
+
|> Jsont.Object.opt_mem "blob" Jsont.string ~enc:(fun c -> c.blob)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun c -> c.unknown)
+
|> Jsont.Object.finish
+
+
type list_request = {
+
cursor : string option;
+
unknown : Jsont.json;
+
}
+
+
let make_list_request ?cursor () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ cursor; unknown }
+
+
let list_request_jsont : list_request Jsont.t =
+
let make cursor unknown = { cursor; unknown } in
+
Jsont.Object.map ~kind:"ResourcesListRequest" make
+
|> Jsont.Object.opt_mem "cursor" Jsont.string ~enc:(fun r -> r.cursor)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun r -> r.unknown)
+
|> Jsont.Object.finish
+
+
type list_result = {
+
resources : resource list;
+
next_cursor : string option;
+
unknown : Jsont.json;
+
}
+
+
let make_list_result ~resources ?next_cursor () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ resources; next_cursor; unknown }
+
+
let list_result_jsont : list_result Jsont.t =
+
let make resources next_cursor unknown =
+
{ resources; next_cursor; unknown }
+
in
+
Jsont.Object.map ~kind:"ResourcesListResult" make
+
|> Jsont.Object.mem "resources" (Jsont.list resource_jsont)
+
~enc:(fun r -> r.resources)
+
|> Jsont.Object.opt_mem "nextCursor" Jsont.string
+
~enc:(fun r -> r.next_cursor)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun r -> r.unknown)
+
|> Jsont.Object.finish
+
+
type read_request = {
+
uri : string;
+
unknown : Jsont.json;
+
}
+
+
let make_read_request ~uri =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ uri; unknown }
+
+
let read_request_jsont : read_request Jsont.t =
+
let make uri unknown = { uri; unknown } in
+
Jsont.Object.map ~kind:"ResourcesReadRequest" make
+
|> Jsont.Object.mem "uri" Jsont.string ~enc:(fun r -> r.uri)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun r -> r.unknown)
+
|> Jsont.Object.finish
+
+
type read_result = {
+
contents : resource_contents list;
+
unknown : Jsont.json;
+
}
+
+
let make_read_result ~contents =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ contents; unknown }
+
+
let read_result_jsont : read_result Jsont.t =
+
let make contents unknown = { contents; unknown } in
+
Jsont.Object.map ~kind:"ResourcesReadResult" make
+
|> Jsont.Object.mem "contents" (Jsont.list resource_contents_jsont)
+
~enc:(fun r -> r.contents)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun r -> r.unknown)
+
|> Jsont.Object.finish
+
+
type subscribe_request = {
+
uri : string;
+
unknown : Jsont.json;
+
}
+
+
let make_subscribe_request ~uri =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ uri; unknown }
+
+
let subscribe_request_jsont : subscribe_request Jsont.t =
+
let make uri unknown = { uri; unknown } in
+
Jsont.Object.map ~kind:"ResourcesSubscribeRequest" make
+
|> Jsont.Object.mem "uri" Jsont.string ~enc:(fun r -> r.uri)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun r -> r.unknown)
+
|> Jsont.Object.finish
+
+
type unsubscribe_request = {
+
uri : string;
+
unknown : Jsont.json;
+
}
+
+
let make_unsubscribe_request ~uri =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ uri; unknown }
+
+
let unsubscribe_request_jsont : unsubscribe_request Jsont.t =
+
let make uri unknown = { uri; unknown } in
+
Jsont.Object.map ~kind:"ResourcesUnsubscribeRequest" make
+
|> Jsont.Object.mem "uri" Jsont.string ~enc:(fun r -> r.uri)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun r -> r.unknown)
+
|> Jsont.Object.finish
+
+
type updated_notification = {
+
uri : string;
+
unknown : Jsont.json;
+
}
+
+
let make_updated_notification ~uri =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ uri; unknown }
+
+
let updated_notification_jsont : updated_notification Jsont.t =
+
let make uri unknown = { uri; unknown } in
+
Jsont.Object.map ~kind:"ResourceUpdatedNotification" make
+
|> Jsont.Object.mem "uri" Jsont.string ~enc:(fun n -> n.uri)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun n -> n.unknown)
+
|> Jsont.Object.finish
+
+
type list_changed_notification = {
+
unknown : Jsont.json;
+
}
+
+
let make_list_changed_notification () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ unknown }
+
+
let list_changed_notification_jsont : list_changed_notification Jsont.t =
+
let make unknown = { unknown } in
+
Jsont.Object.map ~kind:"ResourceListChangedNotification" make
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun n -> n.unknown)
+
|> Jsont.Object.finish
+
+
let list_method = "resources/list"
+
let read_method = "resources/read"
+
let subscribe_method = "resources/subscribe"
+
let unsubscribe_method = "resources/unsubscribe"
+
let updated_notification_method = "notifications/resources/updated"
+
let list_changed_notification_method = "notifications/resources/list_changed"
+
end
+
+
(* Tools *)
+
+
module Tools = struct
+
type tool = {
+
name : string;
+
description : string option;
+
input_schema : Jsont.json;
+
unknown : Jsont.json;
+
}
+
+
let make_tool ~name ?description ~input_schema () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ name; description; input_schema; unknown }
+
+
let tool_jsont : tool Jsont.t =
+
let make name description input_schema unknown =
+
{ name; description; input_schema; unknown }
+
in
+
Jsont.Object.map ~kind:"Tool" make
+
|> Jsont.Object.mem "name" Jsont.string ~enc:(fun t -> t.name)
+
|> Jsont.Object.opt_mem "description" Jsont.string ~enc:(fun t -> t.description)
+
|> Jsont.Object.mem "inputSchema" Jsont.json ~enc:(fun t -> t.input_schema)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun t -> t.unknown)
+
|> Jsont.Object.finish
+
+
type list_request = {
+
cursor : string option;
+
unknown : Jsont.json;
+
}
+
+
let make_list_request ?cursor () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ cursor; unknown }
+
+
let list_request_jsont : list_request Jsont.t =
+
let make cursor unknown = { cursor; unknown } in
+
Jsont.Object.map ~kind:"ToolsListRequest" make
+
|> Jsont.Object.opt_mem "cursor" Jsont.string ~enc:(fun r -> r.cursor)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun r -> r.unknown)
+
|> Jsont.Object.finish
+
+
type list_result = {
+
tools : tool list;
+
next_cursor : string option;
+
unknown : Jsont.json;
+
}
+
+
let make_list_result ~tools ?next_cursor () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ tools; next_cursor; unknown }
+
+
let list_result_jsont : list_result Jsont.t =
+
let make tools next_cursor unknown =
+
{ tools; next_cursor; unknown }
+
in
+
Jsont.Object.map ~kind:"ToolsListResult" make
+
|> Jsont.Object.mem "tools" (Jsont.list tool_jsont)
+
~enc:(fun r -> r.tools)
+
|> Jsont.Object.opt_mem "nextCursor" Jsont.string
+
~enc:(fun r -> r.next_cursor)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun r -> r.unknown)
+
|> Jsont.Object.finish
+
+
type call_request = {
+
name : string;
+
arguments : Jsont.json option;
+
unknown : Jsont.json;
+
}
+
+
let make_call_request ~name ?arguments () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ name; arguments; unknown }
+
+
let call_request_jsont : call_request Jsont.t =
+
let make name arguments unknown = { name; arguments; unknown } in
+
Jsont.Object.map ~kind:"ToolsCallRequest" make
+
|> Jsont.Object.mem "name" Jsont.string ~enc:(fun r -> r.name)
+
|> Jsont.Object.opt_mem "arguments" Jsont.json ~enc:(fun r -> r.arguments)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun r -> r.unknown)
+
|> Jsont.Object.finish
+
+
type call_result = {
+
content : Content.block list;
+
is_error : bool option;
+
unknown : Jsont.json;
+
}
+
+
let make_call_result ~content ?is_error () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ content; is_error; unknown }
+
+
let call_result_jsont : call_result Jsont.t =
+
let make content is_error unknown =
+
{ content; is_error; unknown }
+
in
+
Jsont.Object.map ~kind:"ToolsCallResult" make
+
|> Jsont.Object.mem "content" (Jsont.list Content.block_jsont)
+
~enc:(fun r -> r.content)
+
|> Jsont.Object.opt_mem "isError" Jsont.bool ~enc:(fun r -> r.is_error)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun r -> r.unknown)
+
|> Jsont.Object.finish
+
+
type list_changed_notification = {
+
unknown : Jsont.json;
+
}
+
+
let make_list_changed_notification () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ unknown }
+
+
let list_changed_notification_jsont : list_changed_notification Jsont.t =
+
let make unknown = { unknown } in
+
Jsont.Object.map ~kind:"ToolsListChangedNotification" make
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun n -> n.unknown)
+
|> Jsont.Object.finish
+
+
let list_method = "tools/list"
+
let call_method = "tools/call"
+
let list_changed_notification_method = "notifications/tools/list_changed"
+
end
+
+
(* Prompts *)
+
+
module Prompts = struct
+
type prompt_argument = {
+
name : string;
+
description : string option;
+
required : bool option;
+
unknown : Jsont.json;
+
}
+
+
let make_prompt_argument ~name ?description ?required () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ name; description; required; unknown }
+
+
let prompt_argument_jsont : prompt_argument Jsont.t =
+
let make name description required unknown =
+
{ name; description; required; unknown }
+
in
+
Jsont.Object.map ~kind:"PromptArgument" make
+
|> Jsont.Object.mem "name" Jsont.string ~enc:(fun a -> a.name)
+
|> Jsont.Object.opt_mem "description" Jsont.string ~enc:(fun a -> a.description)
+
|> Jsont.Object.opt_mem "required" Jsont.bool ~enc:(fun a -> a.required)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun a -> a.unknown)
+
|> Jsont.Object.finish
+
+
type prompt = {
+
name : string;
+
description : string option;
+
arguments : prompt_argument list option;
+
unknown : Jsont.json;
+
}
+
+
let make_prompt ~name ?description ?arguments () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ name; description; arguments; unknown }
+
+
let prompt_jsont : prompt Jsont.t =
+
let make name description arguments unknown =
+
{ name; description; arguments; unknown }
+
in
+
Jsont.Object.map ~kind:"Prompt" make
+
|> Jsont.Object.mem "name" Jsont.string ~enc:(fun p -> p.name)
+
|> Jsont.Object.opt_mem "description" Jsont.string ~enc:(fun p -> p.description)
+
|> Jsont.Object.opt_mem "arguments" (Jsont.list prompt_argument_jsont)
+
~enc:(fun p -> p.arguments)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun p -> p.unknown)
+
|> Jsont.Object.finish
+
+
type role = User | Assistant
+
+
let role_jsont : role Jsont.t =
+
Jsont.enum [
+
"user", User;
+
"assistant", Assistant;
+
]
+
+
type prompt_message = {
+
role : role;
+
content : Content.block list;
+
unknown : Jsont.json;
+
}
+
+
let make_prompt_message ~role ~content () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ role; content; unknown }
+
+
let prompt_message_jsont : prompt_message Jsont.t =
+
let make role content unknown = { role; content; unknown } in
+
Jsont.Object.map ~kind:"PromptMessage" make
+
|> Jsont.Object.mem "role" role_jsont ~enc:(fun m -> m.role)
+
|> Jsont.Object.mem "content" (Jsont.list Content.block_jsont)
+
~enc:(fun m -> m.content)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun m -> m.unknown)
+
|> Jsont.Object.finish
+
+
type list_request = {
+
cursor : string option;
+
unknown : Jsont.json;
+
}
+
+
let make_list_request ?cursor () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ cursor; unknown }
+
+
let list_request_jsont : list_request Jsont.t =
+
let make cursor unknown = { cursor; unknown } in
+
Jsont.Object.map ~kind:"PromptsListRequest" make
+
|> Jsont.Object.opt_mem "cursor" Jsont.string ~enc:(fun r -> r.cursor)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun r -> r.unknown)
+
|> Jsont.Object.finish
+
+
type list_result = {
+
prompts : prompt list;
+
next_cursor : string option;
+
unknown : Jsont.json;
+
}
+
+
let make_list_result ~prompts ?next_cursor () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ prompts; next_cursor; unknown }
+
+
let list_result_jsont : list_result Jsont.t =
+
let make prompts next_cursor unknown =
+
{ prompts; next_cursor; unknown }
+
in
+
Jsont.Object.map ~kind:"PromptsListResult" make
+
|> Jsont.Object.mem "prompts" (Jsont.list prompt_jsont)
+
~enc:(fun r -> r.prompts)
+
|> Jsont.Object.opt_mem "nextCursor" Jsont.string
+
~enc:(fun r -> r.next_cursor)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun r -> r.unknown)
+
|> Jsont.Object.finish
+
+
(* Arguments as object with string keys *)
+
let arguments_jsont : (string * string) list Jsont.t =
+
let enc_obj args =
+
let pairs = List.map (fun (k, v) ->
+
((k, Jsont.Meta.none), Jsont.String (v, Jsont.Meta.none))
+
) args in
+
Jsont.Object (pairs, Jsont.Meta.none)
+
in
+
let dec_obj = function
+
| Jsont.Object (members, _) ->
+
List.map (fun ((k, _), v) ->
+
match v with
+
| Jsont.String (s, _) -> (k, s)
+
| _ -> Jsont.Error.msgf Jsont.Meta.none
+
"Argument values must be strings"
+
) members
+
| _ ->
+
Jsont.Error.msgf Jsont.Meta.none "Arguments must be an object"
+
in
+
Jsont.map ~kind:"PromptArguments" ~dec:dec_obj ~enc:enc_obj Jsont.json
+
+
type get_request = {
+
name : string;
+
arguments : (string * string) list option;
+
unknown : Jsont.json;
+
}
+
+
let make_get_request ~name ?arguments () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ name; arguments; unknown }
+
+
let get_request_jsont : get_request Jsont.t =
+
let make name arguments unknown = { name; arguments; unknown } in
+
Jsont.Object.map ~kind:"PromptsGetRequest" make
+
|> Jsont.Object.mem "name" Jsont.string ~enc:(fun r -> r.name)
+
|> Jsont.Object.opt_mem "arguments" arguments_jsont ~enc:(fun r -> r.arguments)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun r -> r.unknown)
+
|> Jsont.Object.finish
+
+
type get_result = {
+
description : string option;
+
messages : prompt_message list;
+
unknown : Jsont.json;
+
}
+
+
let make_get_result ?description ~messages () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ description; messages; unknown }
+
+
let get_result_jsont : get_result Jsont.t =
+
let make description messages unknown =
+
{ description; messages; unknown }
+
in
+
Jsont.Object.map ~kind:"PromptsGetResult" make
+
|> Jsont.Object.opt_mem "description" Jsont.string ~enc:(fun r -> r.description)
+
|> Jsont.Object.mem "messages" (Jsont.list prompt_message_jsont)
+
~enc:(fun r -> r.messages)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun r -> r.unknown)
+
|> Jsont.Object.finish
+
+
type list_changed_notification = {
+
unknown : Jsont.json;
+
}
+
+
let make_list_changed_notification () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ unknown }
+
+
let list_changed_notification_jsont : list_changed_notification Jsont.t =
+
let make unknown = { unknown } in
+
Jsont.Object.map ~kind:"PromptsListChangedNotification" make
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun n -> n.unknown)
+
|> Jsont.Object.finish
+
+
let list_method = "prompts/list"
+
let get_method = "prompts/get"
+
let list_changed_notification_method = "notifications/prompts/list_changed"
+
end
+
+
(* Logging *)
+
+
module Logging = struct
+
type level =
+
| Debug
+
| Info
+
| Notice
+
| Warning
+
| Error
+
| Critical
+
| Alert
+
| Emergency
+
+
let level_jsont : level Jsont.t =
+
Jsont.enum [
+
"debug", Debug;
+
"info", Info;
+
"notice", Notice;
+
"warning", Warning;
+
"error", Error;
+
"critical", Critical;
+
"alert", Alert;
+
"emergency", Emergency;
+
]
+
+
let level_to_string = function
+
| Debug -> "debug"
+
| Info -> "info"
+
| Notice -> "notice"
+
| Warning -> "warning"
+
| Error -> "error"
+
| Critical -> "critical"
+
| Alert -> "alert"
+
| Emergency -> "emergency"
+
+
type notification = {
+
level : level;
+
logger : string option;
+
data : Jsont.json option;
+
unknown : Jsont.json;
+
}
+
+
let make_notification ~level ?logger ?data () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ level; logger; data; unknown }
+
+
let notification_jsont : notification Jsont.t =
+
let make level logger data unknown =
+
{ level; logger; data; unknown }
+
in
+
Jsont.Object.map ~kind:"LoggingNotification" make
+
|> Jsont.Object.mem "level" level_jsont ~enc:(fun n -> n.level)
+
|> Jsont.Object.opt_mem "logger" Jsont.string ~enc:(fun n -> n.logger)
+
|> Jsont.Object.opt_mem "data" Jsont.json ~enc:(fun n -> n.data)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun n -> n.unknown)
+
|> Jsont.Object.finish
+
+
let method_ = "notifications/message"
+
end
+
+
(* Completions *)
+
+
module Completions = struct
+
type completion_ref = {
+
ref_type : string;
+
uri : string;
+
unknown : Jsont.json;
+
}
+
+
let make_completion_ref ~ref_type ~uri () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ ref_type; uri; unknown }
+
+
let completion_ref_jsont : completion_ref Jsont.t =
+
let make ref_type uri unknown = { ref_type; uri; unknown } in
+
Jsont.Object.map ~kind:"CompletionRef" make
+
|> Jsont.Object.mem "type" Jsont.string ~enc:(fun r -> r.ref_type)
+
|> Jsont.Object.mem "uri" Jsont.string ~enc:(fun r -> r.uri)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun r -> r.unknown)
+
|> Jsont.Object.finish
+
+
type request = {
+
ref_ : completion_ref;
+
argument : string option;
+
unknown : Jsont.json;
+
}
+
+
let make_request ~ref_ ?argument () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ ref_; argument; unknown }
+
+
let request_jsont : request Jsont.t =
+
let make ref_ argument unknown = { ref_; argument; unknown } in
+
Jsont.Object.map ~kind:"CompletionRequest" make
+
|> Jsont.Object.mem "ref" completion_ref_jsont ~enc:(fun r -> r.ref_)
+
|> Jsont.Object.opt_mem "argument" Jsont.string ~enc:(fun r -> r.argument)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun r -> r.unknown)
+
|> Jsont.Object.finish
+
+
type result = {
+
completion : string list;
+
total : int option;
+
has_more : bool option;
+
unknown : Jsont.json;
+
}
+
+
let make_result ~completion ?total ?has_more () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ completion; total; has_more; unknown }
+
+
let result_jsont : result Jsont.t =
+
let make completion total has_more unknown =
+
{ completion; total; has_more; unknown }
+
in
+
Jsont.Object.map ~kind:"CompletionResult" make
+
|> Jsont.Object.mem "completion" (Jsont.list Jsont.string)
+
~enc:(fun r -> r.completion)
+
|> Jsont.Object.opt_mem "total" Jsont.int ~enc:(fun r -> r.total)
+
|> Jsont.Object.opt_mem "hasMore" Jsont.bool ~enc:(fun r -> r.has_more)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun r -> r.unknown)
+
|> Jsont.Object.finish
+
+
let method_ = "completion/complete"
+
end
+
+
(* Roots *)
+
+
module Roots = struct
+
type root = {
+
uri : string;
+
name : string option;
+
unknown : Jsont.json;
+
}
+
+
let make_root ~uri ?name () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ uri; name; unknown }
+
+
let root_jsont : root Jsont.t =
+
let make uri name unknown = { uri; name; unknown } in
+
Jsont.Object.map ~kind:"Root" make
+
|> Jsont.Object.mem "uri" Jsont.string ~enc:(fun r -> r.uri)
+
|> Jsont.Object.opt_mem "name" Jsont.string ~enc:(fun r -> r.name)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun r -> r.unknown)
+
|> Jsont.Object.finish
+
+
type list_request = {
+
unknown : Jsont.json;
+
}
+
+
let make_list_request () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ unknown }
+
+
let list_request_jsont : list_request Jsont.t =
+
let make unknown = { unknown } in
+
Jsont.Object.map ~kind:"RootsListRequest" make
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun r -> r.unknown)
+
|> Jsont.Object.finish
+
+
type list_result = {
+
roots : root list;
+
unknown : Jsont.json;
+
}
+
+
let make_list_result ~roots =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ roots; unknown }
+
+
let list_result_jsont : list_result Jsont.t =
+
let make roots unknown = { roots; unknown } in
+
Jsont.Object.map ~kind:"RootsListResult" make
+
|> Jsont.Object.mem "roots" (Jsont.list root_jsont)
+
~enc:(fun r -> r.roots)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun r -> r.unknown)
+
|> Jsont.Object.finish
+
+
type list_changed_notification = {
+
unknown : Jsont.json;
+
}
+
+
let make_list_changed_notification () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ unknown }
+
+
let list_changed_notification_jsont : list_changed_notification Jsont.t =
+
let make unknown = { unknown } in
+
Jsont.Object.map ~kind:"RootsListChangedNotification" make
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun n -> n.unknown)
+
|> Jsont.Object.finish
+
+
let list_method = "roots/list"
+
let list_changed_notification_method = "notifications/roots/list_changed"
+
end
+
+
(* Progress *)
+
+
module Progress = struct
+
type notification = {
+
progress_token : string;
+
progress : float;
+
total : float option;
+
unknown : Jsont.json;
+
}
+
+
let make_notification ~progress_token ~progress ?total () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ progress_token; progress; total; unknown }
+
+
let notification_jsont : notification Jsont.t =
+
let make progress_token progress total unknown =
+
{ progress_token; progress; total; unknown }
+
in
+
Jsont.Object.map ~kind:"ProgressNotification" make
+
|> Jsont.Object.mem "progressToken" Jsont.string
+
~enc:(fun n -> n.progress_token)
+
|> Jsont.Object.mem "progress" Jsont.number ~enc:(fun n -> n.progress)
+
|> Jsont.Object.opt_mem "total" Jsont.number ~enc:(fun n -> n.total)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun n -> n.unknown)
+
|> Jsont.Object.finish
+
+
let method_ = "notifications/progress"
+
end
+
+
(* Cancellation *)
+
+
module Cancellation = struct
+
type notification = {
+
request_id : Jsonrpc.Id.t;
+
reason : string option;
+
unknown : Jsont.json;
+
}
+
+
let make_notification ~request_id ?reason () =
+
let unknown = Jsont.Object ([], Jsont.Meta.none) in
+
{ request_id; reason; unknown }
+
+
let notification_jsont : notification Jsont.t =
+
let make request_id reason unknown =
+
{ request_id; reason; unknown }
+
in
+
Jsont.Object.map ~kind:"CancellationNotification" make
+
|> Jsont.Object.mem "requestId" Jsonrpc.Id.jsont
+
~enc:(fun n -> n.request_id)
+
|> Jsont.Object.opt_mem "reason" Jsont.string ~enc:(fun n -> n.reason)
+
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun n -> n.unknown)
+
|> Jsont.Object.finish
+
+
let method_ = "notifications/cancelled"
+
end
+660
claudeio/lib_mcp/messages.mli
···
···
+
(** MCP Protocol Messages.
+
+
This module provides all protocol message types for the Model Context Protocol (MCP).
+
It includes initialization, resources, tools, prompts, logging, and other protocol messages.
+
+
All types include unknown field preservation for forward compatibility. *)
+
+
(** {1 Protocol Version} *)
+
+
type protocol_version = string
+
(** MCP protocol version string (e.g., "2024-11-05") *)
+
+
val protocol_version_jsont : protocol_version Jsont.t
+
+
(** {1 Initialize Protocol} *)
+
+
module Initialize : sig
+
(** Initialize request parameters *)
+
type request_params = {
+
protocol_version : protocol_version;
+
capabilities : Capabilities.Client.t;
+
client_info : Capabilities.Implementation.t;
+
unknown : Jsont.json;
+
}
+
+
val make_request_params :
+
protocol_version:protocol_version ->
+
capabilities:Capabilities.Client.t ->
+
client_info:Capabilities.Implementation.t ->
+
unit ->
+
request_params
+
+
val request_params_jsont : request_params Jsont.t
+
+
(** Initialize result *)
+
type result = {
+
protocol_version : protocol_version;
+
capabilities : Capabilities.Server.t;
+
server_info : Capabilities.Implementation.t;
+
instructions : string option;
+
unknown : Jsont.json;
+
}
+
+
val make_result :
+
protocol_version:protocol_version ->
+
capabilities:Capabilities.Server.t ->
+
server_info:Capabilities.Implementation.t ->
+
?instructions:string ->
+
unit ->
+
result
+
+
val result_jsont : result Jsont.t
+
+
val method_ : string
+
(** Method name: "initialize" *)
+
end
+
+
module Initialized : sig
+
(** Initialized notification (sent after initialize completes) *)
+
type notification = {
+
unknown : Jsont.json;
+
}
+
+
val make_notification : unit -> notification
+
val notification_jsont : notification Jsont.t
+
+
val method_ : string
+
(** Method name: "notifications/initialized" *)
+
end
+
+
module Ping : sig
+
(** Ping request (keepalive) *)
+
type params = {
+
unknown : Jsont.json;
+
}
+
+
val make_params : unit -> params
+
val params_jsont : params Jsont.t
+
+
(** Ping result (empty object) *)
+
type result = {
+
unknown : Jsont.json;
+
}
+
+
val make_result : unit -> result
+
val result_jsont : result Jsont.t
+
+
val method_ : string
+
(** Method name: "ping" *)
+
end
+
+
(** {1 Resources} *)
+
+
module Resources : sig
+
(** Resource descriptor *)
+
type resource = {
+
uri : string;
+
name : string;
+
description : string option;
+
mime_type : string option;
+
unknown : Jsont.json;
+
}
+
+
val make_resource :
+
uri:string ->
+
name:string ->
+
?description:string ->
+
?mime_type:string ->
+
unit ->
+
resource
+
+
val resource_jsont : resource Jsont.t
+
+
(** Resource template (URI template with placeholders) *)
+
type resource_template = {
+
uri_template : string;
+
name : string;
+
description : string option;
+
mime_type : string option;
+
unknown : Jsont.json;
+
}
+
+
val make_resource_template :
+
uri_template:string ->
+
name:string ->
+
?description:string ->
+
?mime_type:string ->
+
unit ->
+
resource_template
+
+
val resource_template_jsont : resource_template Jsont.t
+
+
(** Resource contents (from read request) *)
+
type resource_contents = {
+
uri : string;
+
mime_type : string option;
+
text : string option;
+
blob : string option; (** Base64-encoded binary data *)
+
unknown : Jsont.json;
+
}
+
+
val make_text_contents :
+
uri:string ->
+
text:string ->
+
?mime_type:string ->
+
unit ->
+
resource_contents
+
+
val make_blob_contents :
+
uri:string ->
+
blob:string ->
+
mime_type:string ->
+
resource_contents
+
+
val resource_contents_jsont : resource_contents Jsont.t
+
+
(** List resources request *)
+
type list_request = {
+
cursor : string option;
+
unknown : Jsont.json;
+
}
+
+
val make_list_request : ?cursor:string -> unit -> list_request
+
val list_request_jsont : list_request Jsont.t
+
+
(** List resources result *)
+
type list_result = {
+
resources : resource list;
+
next_cursor : string option;
+
unknown : Jsont.json;
+
}
+
+
val make_list_result :
+
resources:resource list ->
+
?next_cursor:string ->
+
unit ->
+
list_result
+
+
val list_result_jsont : list_result Jsont.t
+
+
(** Read resource request *)
+
type read_request = {
+
uri : string;
+
unknown : Jsont.json;
+
}
+
+
val make_read_request : uri:string -> read_request
+
val read_request_jsont : read_request Jsont.t
+
+
(** Read resource result *)
+
type read_result = {
+
contents : resource_contents list;
+
unknown : Jsont.json;
+
}
+
+
val make_read_result : contents:resource_contents list -> read_result
+
val read_result_jsont : read_result Jsont.t
+
+
(** Subscribe to resource updates *)
+
type subscribe_request = {
+
uri : string;
+
unknown : Jsont.json;
+
}
+
+
val make_subscribe_request : uri:string -> subscribe_request
+
val subscribe_request_jsont : subscribe_request Jsont.t
+
+
(** Unsubscribe from resource updates *)
+
type unsubscribe_request = {
+
uri : string;
+
unknown : Jsont.json;
+
}
+
+
val make_unsubscribe_request : uri:string -> unsubscribe_request
+
val unsubscribe_request_jsont : unsubscribe_request Jsont.t
+
+
(** Resource updated notification *)
+
type updated_notification = {
+
uri : string;
+
unknown : Jsont.json;
+
}
+
+
val make_updated_notification : uri:string -> updated_notification
+
val updated_notification_jsont : updated_notification Jsont.t
+
+
(** Resource list changed notification *)
+
type list_changed_notification = {
+
unknown : Jsont.json;
+
}
+
+
val make_list_changed_notification : unit -> list_changed_notification
+
val list_changed_notification_jsont : list_changed_notification Jsont.t
+
+
val list_method : string
+
(** Method name: "resources/list" *)
+
+
val read_method : string
+
(** Method name: "resources/read" *)
+
+
val subscribe_method : string
+
(** Method name: "resources/subscribe" *)
+
+
val unsubscribe_method : string
+
(** Method name: "resources/unsubscribe" *)
+
+
val updated_notification_method : string
+
(** Method name: "notifications/resources/updated" *)
+
+
val list_changed_notification_method : string
+
(** Method name: "notifications/resources/list_changed" *)
+
end
+
+
(** {1 Tools} *)
+
+
module Tools : sig
+
(** Tool descriptor *)
+
type tool = {
+
name : string;
+
description : string option;
+
input_schema : Jsont.json; (** JSON Schema for tool inputs *)
+
unknown : Jsont.json;
+
}
+
+
val make_tool :
+
name:string ->
+
?description:string ->
+
input_schema:Jsont.json ->
+
unit ->
+
tool
+
+
val tool_jsont : tool Jsont.t
+
+
(** List tools request *)
+
type list_request = {
+
cursor : string option;
+
unknown : Jsont.json;
+
}
+
+
val make_list_request : ?cursor:string -> unit -> list_request
+
val list_request_jsont : list_request Jsont.t
+
+
(** List tools result *)
+
type list_result = {
+
tools : tool list;
+
next_cursor : string option;
+
unknown : Jsont.json;
+
}
+
+
val make_list_result :
+
tools:tool list ->
+
?next_cursor:string ->
+
unit ->
+
list_result
+
+
val list_result_jsont : list_result Jsont.t
+
+
(** Call tool request *)
+
type call_request = {
+
name : string;
+
arguments : Jsont.json option;
+
unknown : Jsont.json;
+
}
+
+
val make_call_request :
+
name:string ->
+
?arguments:Jsont.json ->
+
unit ->
+
call_request
+
+
val call_request_jsont : call_request Jsont.t
+
+
(** Call tool result *)
+
type call_result = {
+
content : Content.block list;
+
is_error : bool option;
+
unknown : Jsont.json;
+
}
+
+
val make_call_result :
+
content:Content.block list ->
+
?is_error:bool ->
+
unit ->
+
call_result
+
+
val call_result_jsont : call_result Jsont.t
+
+
(** Tool list changed notification *)
+
type list_changed_notification = {
+
unknown : Jsont.json;
+
}
+
+
val make_list_changed_notification : unit -> list_changed_notification
+
val list_changed_notification_jsont : list_changed_notification Jsont.t
+
+
val list_method : string
+
(** Method name: "tools/list" *)
+
+
val call_method : string
+
(** Method name: "tools/call" *)
+
+
val list_changed_notification_method : string
+
(** Method name: "notifications/tools/list_changed" *)
+
end
+
+
(** {1 Prompts} *)
+
+
module Prompts : sig
+
(** Prompt argument descriptor *)
+
type prompt_argument = {
+
name : string;
+
description : string option;
+
required : bool option;
+
unknown : Jsont.json;
+
}
+
+
val make_prompt_argument :
+
name:string ->
+
?description:string ->
+
?required:bool ->
+
unit ->
+
prompt_argument
+
+
val prompt_argument_jsont : prompt_argument Jsont.t
+
+
(** Prompt descriptor *)
+
type prompt = {
+
name : string;
+
description : string option;
+
arguments : prompt_argument list option;
+
unknown : Jsont.json;
+
}
+
+
val make_prompt :
+
name:string ->
+
?description:string ->
+
?arguments:prompt_argument list ->
+
unit ->
+
prompt
+
+
val prompt_jsont : prompt Jsont.t
+
+
(** Prompt message role *)
+
type role = User | Assistant
+
+
val role_jsont : role Jsont.t
+
+
(** Prompt message *)
+
type prompt_message = {
+
role : role;
+
content : Content.block list;
+
unknown : Jsont.json;
+
}
+
+
val make_prompt_message :
+
role:role ->
+
content:Content.block list ->
+
unit ->
+
prompt_message
+
+
val prompt_message_jsont : prompt_message Jsont.t
+
+
(** List prompts request *)
+
type list_request = {
+
cursor : string option;
+
unknown : Jsont.json;
+
}
+
+
val make_list_request : ?cursor:string -> unit -> list_request
+
val list_request_jsont : list_request Jsont.t
+
+
(** List prompts result *)
+
type list_result = {
+
prompts : prompt list;
+
next_cursor : string option;
+
unknown : Jsont.json;
+
}
+
+
val make_list_result :
+
prompts:prompt list ->
+
?next_cursor:string ->
+
unit ->
+
list_result
+
+
val list_result_jsont : list_result Jsont.t
+
+
(** Get prompt request *)
+
type get_request = {
+
name : string;
+
arguments : (string * string) list option; (** Key-value pairs *)
+
unknown : Jsont.json;
+
}
+
+
val make_get_request :
+
name:string ->
+
?arguments:(string * string) list ->
+
unit ->
+
get_request
+
+
val get_request_jsont : get_request Jsont.t
+
+
(** Get prompt result *)
+
type get_result = {
+
description : string option;
+
messages : prompt_message list;
+
unknown : Jsont.json;
+
}
+
+
val make_get_result :
+
?description:string ->
+
messages:prompt_message list ->
+
unit ->
+
get_result
+
+
val get_result_jsont : get_result Jsont.t
+
+
(** Prompt list changed notification *)
+
type list_changed_notification = {
+
unknown : Jsont.json;
+
}
+
+
val make_list_changed_notification : unit -> list_changed_notification
+
val list_changed_notification_jsont : list_changed_notification Jsont.t
+
+
val list_method : string
+
(** Method name: "prompts/list" *)
+
+
val get_method : string
+
(** Method name: "prompts/get" *)
+
+
val list_changed_notification_method : string
+
(** Method name: "notifications/prompts/list_changed" *)
+
end
+
+
(** {1 Logging} *)
+
+
module Logging : sig
+
(** Log level *)
+
type level =
+
| Debug
+
| Info
+
| Notice
+
| Warning
+
| Error
+
| Critical
+
| Alert
+
| Emergency
+
+
val level_jsont : level Jsont.t
+
val level_to_string : level -> string
+
+
(** Logging message notification *)
+
type notification = {
+
level : level;
+
logger : string option;
+
data : Jsont.json option;
+
unknown : Jsont.json;
+
}
+
+
val make_notification :
+
level:level ->
+
?logger:string ->
+
?data:Jsont.json ->
+
unit ->
+
notification
+
+
val notification_jsont : notification Jsont.t
+
+
val method_ : string
+
(** Method name: "notifications/message" *)
+
end
+
+
(** {1 Completions} *)
+
+
module Completions : sig
+
(** Completion reference (argument or resource URI) *)
+
type completion_ref = {
+
ref_type : string; (** "ref/prompt" or "ref/resource" *)
+
uri : string;
+
unknown : Jsont.json;
+
}
+
+
val make_completion_ref :
+
ref_type:string ->
+
uri:string ->
+
unit ->
+
completion_ref
+
+
val completion_ref_jsont : completion_ref Jsont.t
+
+
(** Completion request *)
+
type request = {
+
ref_ : completion_ref;
+
argument : string option;
+
unknown : Jsont.json;
+
}
+
+
val make_request :
+
ref_:completion_ref ->
+
?argument:string ->
+
unit ->
+
request
+
+
val request_jsont : request Jsont.t
+
+
(** Completion result *)
+
type result = {
+
completion : string list;
+
total : int option;
+
has_more : bool option;
+
unknown : Jsont.json;
+
}
+
+
val make_result :
+
completion:string list ->
+
?total:int ->
+
?has_more:bool ->
+
unit ->
+
result
+
+
val result_jsont : result Jsont.t
+
+
val method_ : string
+
(** Method name: "completion/complete" *)
+
end
+
+
(** {1 Roots} *)
+
+
module Roots : sig
+
(** Root descriptor *)
+
type root = {
+
uri : string;
+
name : string option;
+
unknown : Jsont.json;
+
}
+
+
val make_root :
+
uri:string ->
+
?name:string ->
+
unit ->
+
root
+
+
val root_jsont : root Jsont.t
+
+
(** List roots request *)
+
type list_request = {
+
unknown : Jsont.json;
+
}
+
+
val make_list_request : unit -> list_request
+
val list_request_jsont : list_request Jsont.t
+
+
(** List roots result *)
+
type list_result = {
+
roots : root list;
+
unknown : Jsont.json;
+
}
+
+
val make_list_result : roots:root list -> list_result
+
val list_result_jsont : list_result Jsont.t
+
+
(** Roots list changed notification *)
+
type list_changed_notification = {
+
unknown : Jsont.json;
+
}
+
+
val make_list_changed_notification : unit -> list_changed_notification
+
val list_changed_notification_jsont : list_changed_notification Jsont.t
+
+
val list_method : string
+
(** Method name: "roots/list" *)
+
+
val list_changed_notification_method : string
+
(** Method name: "notifications/roots/list_changed" *)
+
end
+
+
(** {1 Progress} *)
+
+
module Progress : sig
+
(** Progress notification *)
+
type notification = {
+
progress_token : string; (** Unique token identifying the operation *)
+
progress : float; (** Progress value (0.0 to 1.0) *)
+
total : float option; (** Optional total value *)
+
unknown : Jsont.json;
+
}
+
+
val make_notification :
+
progress_token:string ->
+
progress:float ->
+
?total:float ->
+
unit ->
+
notification
+
+
val notification_jsont : notification Jsont.t
+
+
val method_ : string
+
(** Method name: "notifications/progress" *)
+
end
+
+
(** {1 Cancellation} *)
+
+
module Cancellation : sig
+
(** Cancel request notification *)
+
type notification = {
+
request_id : Jsonrpc.Id.t;
+
reason : string option;
+
unknown : Jsont.json;
+
}
+
+
val make_notification :
+
request_id:Jsonrpc.Id.t ->
+
?reason:string ->
+
unit ->
+
notification
+
+
val notification_jsont : notification Jsont.t
+
+
val method_ : string
+
(** Method name: "notifications/cancelled" *)
+
end
+371
claudeio/lib_mcp/server_session.ml
···
···
+
(** High-level MCP server session API *)
+
+
(** {1 Types} *)
+
+
type config = {
+
server_info : Capabilities.Implementation.t;
+
server_capabilities : Capabilities.Server.t;
+
instructions : string option;
+
}
+
+
type handlers = {
+
list_resources : (cursor:string option -> Messages.Resources.list_result) option;
+
list_resource_templates : (cursor:string option -> Messages.Resources.list_result) option;
+
read_resource : (uri:string -> Messages.Resources.read_result) option;
+
subscribe_resource : (uri:string -> unit) option;
+
unsubscribe_resource : (uri:string -> unit) option;
+
list_tools : (cursor:string option -> Messages.Tools.list_result) option;
+
call_tool : (name:string -> arguments:Jsont.json option -> Messages.Tools.call_result) option;
+
list_prompts : (cursor:string option -> Messages.Prompts.list_result) option;
+
get_prompt : (name:string -> arguments:(string * string) list option -> Messages.Prompts.get_result) option;
+
complete : (ref_:Messages.Completions.completion_ref -> argument:string -> Messages.Completions.result) option;
+
ping : (unit -> unit) option;
+
}
+
+
type t = {
+
session : Session.t;
+
config : config;
+
handlers : handlers;
+
mutable client_capabilities : Capabilities.Client.t option;
+
mutable client_info : Capabilities.Implementation.t option;
+
mutable protocol_version : string option;
+
mutable initialized : bool;
+
}
+
+
(** {1 Helper Functions} *)
+
+
let encode_json jsont value =
+
match Jsont.Json.encode jsont value with
+
| Ok json -> json
+
| Error e -> failwith ("Failed to encode JSON: " ^ e)
+
+
let decode_json jsont json =
+
match Jsont.Json.decode jsont json with
+
| Ok value -> value
+
| Error e -> failwith ("Failed to decode JSON: " ^ e)
+
+
let method_not_found method_ =
+
let error = Jsonrpc.Error_data.make
+
~code:Method_not_found
+
~message:(Printf.sprintf "Method not found: %s" method_)
+
()
+
in
+
raise (Session.Remote_error error)
+
+
let invalid_params method_ msg =
+
let error = Jsonrpc.Error_data.make
+
~code:Invalid_params
+
~message:(Printf.sprintf "Invalid params for %s: %s" method_ msg)
+
()
+
in
+
raise (Session.Remote_error error)
+
+
(** {1 Request Handler} *)
+
+
let handle_request t ~method_ ~params =
+
(* Ensure initialization has completed for non-init requests *)
+
if method_ <> Messages.Initialize.method_ && not t.initialized then begin
+
let error = Jsonrpc.Error_data.make
+
~code:Internal_error
+
~message:"Server not initialized"
+
()
+
in
+
raise (Session.Remote_error error)
+
end;
+
+
(* Route to appropriate handler *)
+
match method_ with
+
| m when m = Messages.Initialize.method_ ->
+
(* Handle initialization *)
+
let req_params = match params with
+
| Some p -> decode_json Messages.Initialize.request_params_jsont p
+
| None -> invalid_params method_ "missing params"
+
in
+
+
(* Store client info *)
+
t.client_capabilities <- Some req_params.capabilities;
+
t.client_info <- Some req_params.client_info;
+
t.protocol_version <- Some req_params.protocol_version;
+
+
(* Build response *)
+
let result = Messages.Initialize.make_result
+
~protocol_version:req_params.protocol_version
+
~capabilities:t.config.server_capabilities
+
~server_info:t.config.server_info
+
?instructions:t.config.instructions
+
()
+
in
+
encode_json Messages.Initialize.result_jsont result
+
+
| m when m = Messages.Ping.method_ ->
+
let handler = t.handlers.ping in
+
(match handler with
+
| None -> method_not_found method_
+
| Some h ->
+
h ();
+
let result = Messages.Ping.make_result () in
+
encode_json Messages.Ping.result_jsont result)
+
+
| m when m = Messages.Resources.list_method ->
+
let handler = t.handlers.list_resources in
+
(match handler with
+
| None -> method_not_found method_
+
| Some h ->
+
let req = match params with
+
| Some p -> decode_json Messages.Resources.list_request_jsont p
+
| None -> Messages.Resources.make_list_request ()
+
in
+
let result = h ~cursor:req.cursor in
+
encode_json Messages.Resources.list_result_jsont result)
+
+
| m when m = Messages.Resources.read_method ->
+
let handler = t.handlers.read_resource in
+
(match handler with
+
| None -> method_not_found method_
+
| Some h ->
+
let req = match params with
+
| Some p -> decode_json Messages.Resources.read_request_jsont p
+
| None -> invalid_params method_ "missing params"
+
in
+
let result = h ~uri:req.uri in
+
encode_json Messages.Resources.read_result_jsont result)
+
+
| m when m = Messages.Resources.subscribe_method ->
+
let handler = t.handlers.subscribe_resource in
+
(match handler with
+
| None -> method_not_found method_
+
| Some h ->
+
let req = match params with
+
| Some p -> decode_json Messages.Resources.subscribe_request_jsont p
+
| None -> invalid_params method_ "missing params"
+
in
+
h ~uri:req.uri;
+
Jsont.Object ([], Jsont.Meta.none)) (* Empty response *)
+
+
| m when m = Messages.Resources.unsubscribe_method ->
+
let handler = t.handlers.unsubscribe_resource in
+
(match handler with
+
| None -> method_not_found method_
+
| Some h ->
+
let req = match params with
+
| Some p -> decode_json Messages.Resources.unsubscribe_request_jsont p
+
| None -> invalid_params method_ "missing params"
+
in
+
h ~uri:req.uri;
+
Jsont.Object ([], Jsont.Meta.none)) (* Empty response *)
+
+
| m when m = Messages.Tools.list_method ->
+
let handler = t.handlers.list_tools in
+
(match handler with
+
| None -> method_not_found method_
+
| Some h ->
+
let req = match params with
+
| Some p -> decode_json Messages.Tools.list_request_jsont p
+
| None -> Messages.Tools.make_list_request ()
+
in
+
let result = h ~cursor:req.cursor in
+
encode_json Messages.Tools.list_result_jsont result)
+
+
| m when m = Messages.Tools.call_method ->
+
let handler = t.handlers.call_tool in
+
(match handler with
+
| None -> method_not_found method_
+
| Some h ->
+
let req = match params with
+
| Some p -> decode_json Messages.Tools.call_request_jsont p
+
| None -> invalid_params method_ "missing params"
+
in
+
let result = h ~name:req.name ~arguments:req.arguments in
+
encode_json Messages.Tools.call_result_jsont result)
+
+
| m when m = Messages.Prompts.list_method ->
+
let handler = t.handlers.list_prompts in
+
(match handler with
+
| None -> method_not_found method_
+
| Some h ->
+
let req = match params with
+
| Some p -> decode_json Messages.Prompts.list_request_jsont p
+
| None -> Messages.Prompts.make_list_request ()
+
in
+
let result = h ~cursor:req.cursor in
+
encode_json Messages.Prompts.list_result_jsont result)
+
+
| m when m = Messages.Prompts.get_method ->
+
let handler = t.handlers.get_prompt in
+
(match handler with
+
| None -> method_not_found method_
+
| Some h ->
+
let req = match params with
+
| Some p -> decode_json Messages.Prompts.get_request_jsont p
+
| None -> invalid_params method_ "missing params"
+
in
+
let result = h ~name:req.name ~arguments:req.arguments in
+
encode_json Messages.Prompts.get_result_jsont result)
+
+
| m when m = Messages.Completions.method_ ->
+
let handler = t.handlers.complete in
+
(match handler with
+
| None -> method_not_found method_
+
| Some h ->
+
let req = match params with
+
| Some p -> decode_json Messages.Completions.request_jsont p
+
| None -> invalid_params method_ "missing params"
+
in
+
let argument = match req.argument with
+
| Some a -> a
+
| None -> ""
+
in
+
let result = h ~ref_:req.ref_ ~argument in
+
encode_json Messages.Completions.result_jsont result)
+
+
| _ ->
+
method_not_found method_
+
+
(** {1 Notification Handler} *)
+
+
let handle_notification t ~method_ ~params =
+
match method_ with
+
| m when m = Messages.Initialized.method_ ->
+
(* Client has confirmed initialization *)
+
let _notif = match params with
+
| Some p -> decode_json Messages.Initialized.notification_jsont p
+
| None -> Messages.Initialized.make_notification ()
+
in
+
t.initialized <- true
+
+
| _ ->
+
(* Ignore unknown notifications *)
+
()
+
+
(** {1 Public API} *)
+
+
let create ~sw ~transport ?timeout ?clock config handlers =
+
(* Create session with handlers *)
+
let t_ref = ref None in
+
+
let request_handler ~method_ ~params =
+
match !t_ref with
+
| None -> failwith "Server session not initialized"
+
| Some t -> handle_request t ~method_ ~params
+
in
+
+
let notification_handler ~method_ ~params =
+
match !t_ref with
+
| None -> ()
+
| Some t -> handle_notification t ~method_ ~params
+
in
+
+
let session_config = {
+
Session.transport;
+
request_handler;
+
notification_handler;
+
timeout;
+
clock;
+
} in
+
+
let session = Session.create ~sw session_config in
+
+
let t = {
+
session;
+
config;
+
handlers;
+
client_capabilities = None;
+
client_info = None;
+
protocol_version = None;
+
initialized = false;
+
} in
+
+
t_ref := Some t;
+
+
t
+
+
let client_capabilities t =
+
match t.client_capabilities with
+
| Some c -> c
+
| None -> invalid_arg "Server_session.client_capabilities: not initialized"
+
+
let client_info t =
+
match t.client_info with
+
| Some i -> i
+
| None -> invalid_arg "Server_session.client_info: not initialized"
+
+
let protocol_version t =
+
match t.protocol_version with
+
| Some v -> v
+
| None -> invalid_arg "Server_session.protocol_version: not initialized"
+
+
(** {1 Sending Notifications} *)
+
+
let send_notification t method_ params_jsont params =
+
let params_json = encode_json params_jsont params in
+
Session.send_notification t.session ~method_ ~params:params_json ()
+
+
let send_resource_updated t ~uri =
+
let notif = Messages.Resources.make_updated_notification ~uri in
+
send_notification t
+
Messages.Resources.updated_notification_method
+
Messages.Resources.updated_notification_jsont
+
notif
+
+
let send_resource_list_changed t =
+
let notif = Messages.Resources.make_list_changed_notification () in
+
send_notification t
+
Messages.Resources.list_changed_notification_method
+
Messages.Resources.list_changed_notification_jsont
+
notif
+
+
let send_tool_list_changed t =
+
let notif = Messages.Tools.make_list_changed_notification () in
+
send_notification t
+
Messages.Tools.list_changed_notification_method
+
Messages.Tools.list_changed_notification_jsont
+
notif
+
+
let send_prompt_list_changed t =
+
let notif = Messages.Prompts.make_list_changed_notification () in
+
send_notification t
+
Messages.Prompts.list_changed_notification_method
+
Messages.Prompts.list_changed_notification_jsont
+
notif
+
+
let send_roots_list_changed t =
+
let notif = Messages.Roots.make_list_changed_notification () in
+
send_notification t
+
Messages.Roots.list_changed_notification_method
+
Messages.Roots.list_changed_notification_jsont
+
notif
+
+
let send_log_message t ~level ?logger ~data () =
+
let notif = Messages.Logging.make_notification ~level ?logger ~data () in
+
send_notification t
+
Messages.Logging.method_
+
Messages.Logging.notification_jsont
+
notif
+
+
let send_progress t ~progress_token ~progress ?total () =
+
let notif = Messages.Progress.make_notification ~progress_token ~progress ?total () in
+
send_notification t
+
Messages.Progress.method_
+
Messages.Progress.notification_jsont
+
notif
+
+
(** {1 Requesting from Client} *)
+
+
let request_roots_list t =
+
match client_capabilities t with
+
| { roots = None; _ } -> None
+
| { roots = Some _; _ } ->
+
let req = Messages.Roots.make_list_request () in
+
let params = encode_json Messages.Roots.list_request_jsont req in
+
let result_json = Session.send_request t.session
+
~method_:Messages.Roots.list_method
+
~params
+
()
+
in
+
let result = decode_json Messages.Roots.list_result_jsont result_json in
+
Some result
+
+
(** {1 Session Management} *)
+
+
let close t =
+
Session.close t.session
+208
claudeio/lib_mcp/server_session.mli
···
···
+
(** High-level MCP server session API.
+
+
This module provides a convenient server-side API for hosting MCP servers.
+
It handles the initialization handshake, routes incoming requests to handlers,
+
and provides helpers for sending notifications to clients.
+
+
{1 Example Usage}
+
+
{[
+
let config = {
+
server_info = Capabilities.Implementation.make
+
~name:"my-server"
+
~version:"1.0.0";
+
server_capabilities = Capabilities.Server.make
+
~tools:(Some (Capabilities.Tools.make ()))
+
();
+
instructions = Some "This is my MCP server";
+
} in
+
+
let handlers = {
+
list_tools = Some (fun ~cursor ->
+
Messages.Tools.make_list_result
+
~tools:[
+
Messages.Tools.make_tool
+
~name:"example"
+
~description:"An example tool"
+
~input_schema:(`Object [])
+
();
+
]
+
()
+
);
+
call_tool = Some (fun ~name ~arguments ->
+
Messages.Tools.make_call_result
+
~content:[Content.text "Tool result"]
+
()
+
);
+
(* ... other handlers ... *)
+
list_resources = None;
+
list_resource_templates = None;
+
read_resource = None;
+
subscribe_resource = None;
+
unsubscribe_resource = None;
+
list_prompts = None;
+
get_prompt = None;
+
complete = None;
+
ping = None;
+
} in
+
+
Eio.Switch.run @@ fun sw ->
+
let server = Server_session.create
+
~sw
+
~transport
+
config
+
handlers
+
in
+
(* Server is now running and handling requests *)
+
(* Send notifications as needed *)
+
Server_session.send_tool_list_changed server
+
]} *)
+
+
(** {1 Types} *)
+
+
type t
+
(** Server session handle *)
+
+
(** {1 Configuration} *)
+
+
type config = {
+
server_info : Capabilities.Implementation.t;
+
(** Server implementation information (name, version) *)
+
server_capabilities : Capabilities.Server.t;
+
(** Server capabilities to advertise to client *)
+
instructions : string option;
+
(** Optional instructions for using the server *)
+
}
+
(** Server configuration *)
+
+
(** {1 Request Handlers} *)
+
+
type handlers = {
+
(* Resources *)
+
list_resources : (cursor:string option -> Messages.Resources.list_result) option;
+
(** Handler for resources/list requests *)
+
+
list_resource_templates : (cursor:string option -> Messages.Resources.list_result) option;
+
(** Handler for resources/templates/list requests *)
+
+
read_resource : (uri:string -> Messages.Resources.read_result) option;
+
(** Handler for resources/read requests *)
+
+
subscribe_resource : (uri:string -> unit) option;
+
(** Handler for resources/subscribe requests *)
+
+
unsubscribe_resource : (uri:string -> unit) option;
+
(** Handler for resources/unsubscribe requests *)
+
+
(* Tools *)
+
list_tools : (cursor:string option -> Messages.Tools.list_result) option;
+
(** Handler for tools/list requests *)
+
+
call_tool : (name:string -> arguments:Jsont.json option -> Messages.Tools.call_result) option;
+
(** Handler for tools/call requests *)
+
+
(* Prompts *)
+
list_prompts : (cursor:string option -> Messages.Prompts.list_result) option;
+
(** Handler for prompts/list requests *)
+
+
get_prompt : (name:string -> arguments:(string * string) list option -> Messages.Prompts.get_result) option;
+
(** Handler for prompts/get requests *)
+
+
(* Completions *)
+
complete : (ref_:Messages.Completions.completion_ref -> argument:string -> Messages.Completions.result) option;
+
(** Handler for completion/complete requests *)
+
+
(* Ping *)
+
ping : (unit -> unit) option;
+
(** Handler for ping requests *)
+
}
+
(** Request handler callbacks.
+
Set to [None] to indicate the method is not supported.
+
If a request is received for an unsupported method, a METHOD_NOT_FOUND error is returned. *)
+
+
(** {1 Server Creation} *)
+
+
val create :
+
sw:Eio.Switch.t ->
+
transport:Transport.t ->
+
?timeout:float ->
+
?clock:Session.clock ->
+
config ->
+
handlers ->
+
t
+
(** Create and initialize a server session.
+
+
This function:
+
1. Creates an underlying Session
+
2. Waits for the Initialize request from the client
+
3. Returns the Initialize response with server capabilities
+
4. Waits for the Initialized notification
+
5. Returns a ready-to-use server session
+
+
The server will then handle incoming requests by routing them to the provided handlers.
+
+
@param sw Switch for the session background fibers
+
@param transport Transport layer for communication
+
@param timeout Optional request timeout in seconds
+
@param clock Optional clock for timeout handling (required if timeout is set)
+
@raise Invalid_argument if initialization fails or times out *)
+
+
(** {1 Client Information} *)
+
+
val client_capabilities : t -> Capabilities.Client.t
+
(** Get the client's advertised capabilities *)
+
+
val client_info : t -> Capabilities.Implementation.t
+
(** Get the client's implementation information *)
+
+
val protocol_version : t -> string
+
(** Get the negotiated protocol version *)
+
+
(** {1 Sending Notifications} *)
+
+
val send_resource_updated : t -> uri:string -> unit
+
(** Send a notification that a resource has been updated.
+
Only works if client supports resource subscriptions.
+
@param uri The URI of the updated resource *)
+
+
val send_resource_list_changed : t -> unit
+
(** Send a notification that the resource list has changed.
+
Only works if client supports resource list_changed capability. *)
+
+
val send_tool_list_changed : t -> unit
+
(** Send a notification that the tool list has changed.
+
Only works if server advertised tools capability. *)
+
+
val send_prompt_list_changed : t -> unit
+
(** Send a notification that the prompt list has changed.
+
Only works if server advertised prompts capability. *)
+
+
val send_roots_list_changed : t -> unit
+
(** Send a notification that the roots list has changed.
+
Only works if client supports roots capability. *)
+
+
val send_log_message : t -> level:Messages.Logging.level -> ?logger:string -> data:Jsont.json -> unit -> unit
+
(** Send a log message notification.
+
Only works if server advertised logging capability.
+
@param level Log level
+
@param logger Optional logger name
+
@param data Log message data (any JSON value) *)
+
+
val send_progress : t -> progress_token:string -> progress:float -> ?total:float -> unit -> unit
+
(** Send a progress notification.
+
@param progress_token Unique token identifying the operation
+
@param progress Progress value (0.0 to 1.0)
+
@param total Optional total value *)
+
+
(** {1 Requesting from Client} *)
+
+
val request_roots_list : t -> Messages.Roots.list_result option
+
(** Request the list of roots from the client.
+
Returns [None] if the client doesn't support the roots capability.
+
@raise Session.Timeout if the request times out
+
@raise Session.Remote_error if the client returns an error *)
+
+
(** {1 Session Management} *)
+
+
val close : t -> unit
+
(** Close the server session and underlying transport *)
+254
claudeio/lib_mcp/session.ml
···
···
+
(** Bidirectional JSON-RPC session management with request/response correlation *)
+
+
(** {1 Handlers} *)
+
+
type request_handler =
+
method_:string ->
+
params:Jsont.json option ->
+
Jsont.json
+
+
type notification_handler =
+
method_:string ->
+
params:Jsont.json option ->
+
unit
+
+
(** {1 Configuration} *)
+
+
type clock = C : _ Eio.Time.clock -> clock
+
+
type config = {
+
transport : Transport.t;
+
request_handler : request_handler;
+
notification_handler : notification_handler;
+
timeout : float option;
+
clock : clock option;
+
(** Clock for timeout handling. Required if timeout is set. *)
+
}
+
+
(** {1 Exceptions} *)
+
+
exception Timeout of string
+
exception Session_closed
+
exception Unknown_response of Jsonrpc.Id.t
+
exception Remote_error of Jsonrpc.Error_data.t
+
+
(** {1 Internal Types} *)
+
+
type response_result =
+
| Success of Jsont.json
+
| Error of exn
+
+
type pending_request = {
+
id : Jsonrpc.Id.t;
+
resolver : response_result Eio.Promise.u;
+
mutable cancelled : bool; (* Flag to indicate request was completed *)
+
}
+
+
type t = {
+
transport : Transport.t;
+
mutable next_id : int;
+
pending : (Jsonrpc.Id.t, pending_request) Hashtbl.t;
+
request_handler : request_handler;
+
notification_handler : notification_handler;
+
timeout : float option;
+
clock : clock option;
+
sw : Eio.Switch.t;
+
mutable closed : bool;
+
}
+
+
(** {1 Helper Functions} *)
+
+
let encode_message msg =
+
match Jsont.Json.encode Jsonrpc.Message.jsont msg with
+
| Ok json -> json
+
| Error e -> failwith ("Failed to encode message: " ^ e)
+
+
let send_json t json =
+
if t.closed then raise Session_closed;
+
Transport.send t.transport json
+
+
(** Handle an incoming request by calling the user's handler and sending response *)
+
let handle_request t req =
+
let open Jsonrpc in
+
let id = match req.Request.id with
+
| Some id -> id
+
| None ->
+
(* This is a notification, not a request - no response needed *)
+
t.notification_handler
+
~method_:req.Request.method_
+
~params:req.Request.params;
+
raise Exit (* Exit this handler without sending response *)
+
in
+
+
try
+
(* Call user's request handler *)
+
let result = t.request_handler
+
~method_:req.Request.method_
+
~params:req.Request.params
+
in
+
(* Send success response *)
+
let response = Response.make_result ~id ~result in
+
let msg = Message.Response response in
+
let json = encode_message msg in
+
send_json t json
+
with
+
| Exit -> () (* Notification, no response *)
+
| exn ->
+
(* Convert exception to error response *)
+
let error = Error_data.make
+
~code:Internal_error
+
~message:(Printexc.to_string exn)
+
()
+
in
+
let response = Response.make_error ~id ~error in
+
let msg = Message.Response response in
+
let json = encode_message msg in
+
send_json t json
+
+
(** Resolve a pending request with a response *)
+
let resolve_response t resp =
+
let open Jsonrpc in
+
let id = resp.Response.id in
+
match Hashtbl.find_opt t.pending id with
+
| None ->
+
(* Received response for unknown request ID *)
+
raise (Unknown_response id)
+
| Some pending ->
+
Hashtbl.remove t.pending id;
+
(* Mark as cancelled so timeout doesn't fire *)
+
pending.cancelled <- true;
+
(* Resolve the promise with result or error *)
+
(match resp.Response.value with
+
| Ok result ->
+
Eio.Promise.resolve pending.resolver (Success result)
+
| Stdlib.Result.Error error ->
+
Eio.Promise.resolve pending.resolver (Error (Remote_error error)))
+
+
(** Background receive loop - reads messages and routes them *)
+
let rec receive_loop t =
+
if t.closed then () else
+
match Transport.receive t.transport with
+
| None ->
+
(* Transport closed *)
+
t.closed <- true;
+
(* Cancel all pending requests *)
+
Hashtbl.iter (fun _ pending ->
+
Eio.Promise.resolve pending.resolver (Error Session_closed)
+
) t.pending;
+
Hashtbl.clear t.pending
+
| Some json ->
+
(try
+
let msg = Jsonrpc.Message.classify json in
+
match msg with
+
| Request req ->
+
(* Handle request in new fibre so it doesn't block receive loop *)
+
Eio.Fiber.fork_promise ~sw:t.sw (fun () ->
+
handle_request t req
+
) |> ignore
+
| Response resp ->
+
(* Resolve pending promise *)
+
resolve_response t resp
+
with exn ->
+
(* Log error but continue receive loop *)
+
Printf.eprintf "Error in receive loop: %s\n%!" (Printexc.to_string exn)
+
);
+
receive_loop t
+
+
(** {1 Public API} *)
+
+
let create ~sw (config : config) : t =
+
(* Validate that clock is provided if timeout is set *)
+
(match config.timeout with
+
| Some _ when config.clock = None ->
+
invalid_arg "Session.create: clock must be provided when timeout is set"
+
| _ -> ());
+
+
let t = {
+
transport = config.transport;
+
next_id = 1;
+
pending = Hashtbl.create 16;
+
request_handler = config.request_handler;
+
notification_handler = config.notification_handler;
+
timeout = config.timeout;
+
clock = config.clock;
+
sw;
+
closed = false;
+
} in
+
+
(* Start background receive loop *)
+
Eio.Fiber.fork ~sw (fun () -> receive_loop t);
+
+
t
+
+
let send_request t ~method_ ?params () =
+
if t.closed then raise Session_closed;
+
+
(* Generate unique request ID *)
+
let id = `Number (float_of_int t.next_id) in
+
t.next_id <- t.next_id + 1;
+
+
(* Create promise for response *)
+
let promise, resolver = Eio.Promise.create () in
+
+
(* Register pending request *)
+
let pending = {
+
id;
+
resolver;
+
cancelled = false;
+
} in
+
Hashtbl.add t.pending id pending;
+
+
(* Setup timeout if configured *)
+
(match t.timeout, t.clock with
+
| None, _ | _, None -> ()
+
| Some timeout_sec, Some (C clock) ->
+
(* Start timeout fiber *)
+
Eio.Fiber.fork ~sw:t.sw (fun () ->
+
Eio.Time.sleep clock timeout_sec;
+
(* Timeout expired - check if request is still pending and not cancelled *)
+
if not pending.cancelled then begin
+
match Hashtbl.find_opt t.pending id with
+
| Some _ ->
+
Hashtbl.remove t.pending id;
+
let msg = Printf.sprintf "Request timeout after %.1fs: %s" timeout_sec method_ in
+
Eio.Promise.resolve pending.resolver (Error (Timeout msg))
+
| None ->
+
(* Request already completed, nothing to do *)
+
()
+
end
+
)
+
);
+
+
(* Send request *)
+
let req = Jsonrpc.Request.make ~method_ ?params ~id () in
+
let msg = Jsonrpc.Message.Request req in
+
let json = encode_message msg in
+
send_json t json;
+
+
(* Wait for response *)
+
match Eio.Promise.await promise with
+
| Success result -> result
+
| Error exn -> raise exn
+
+
let send_notification t ~method_ ?params () =
+
if t.closed then raise Session_closed;
+
+
(* Create notification (request with no ID) *)
+
let req = Jsonrpc.Request.make ~method_ ?params () in
+
let msg = Jsonrpc.Message.Request req in
+
let json = encode_message msg in
+
send_json t json
+
+
let close t =
+
if not t.closed then begin
+
t.closed <- true;
+
(* Cancel all pending requests *)
+
Hashtbl.iter (fun _ pending ->
+
Eio.Promise.resolve pending.resolver (Error Session_closed)
+
) t.pending;
+
Hashtbl.clear t.pending;
+
(* Close transport *)
+
Transport.close t.transport
+
end
+
+
let is_closed t = t.closed
+140
claudeio/lib_mcp/session.mli
···
···
+
(** Bidirectional JSON-RPC session management with request/response correlation.
+
+
This module provides a high-level session abstraction over a transport layer,
+
handling request ID generation, response correlation via promises, and
+
bidirectional message routing using Eio structured concurrency.
+
+
{1 Architecture}
+
+
Sessions run a background receive loop in an Eio fiber that continuously
+
reads from the transport and routes messages:
+
- Incoming requests → dispatched to request_handler in new fiber
+
- Incoming responses → resolve pending promises
+
- Incoming notifications → dispatched to notification_handler
+
+
Outgoing messages (requests and notifications) are sent directly on the
+
transport. Requests return promises that are resolved when the corresponding
+
response arrives.
+
+
{1 Example Usage}
+
+
{[
+
Eio_main.run @@ fun env ->
+
let config = {
+
transport;
+
request_handler = (fun ~method_ ~params ->
+
(* Handle incoming requests *)
+
match method_ with
+
| "ping" -> `String "pong"
+
| _ -> failwith "Unknown method"
+
);
+
notification_handler = (fun ~method_ ~params ->
+
(* Handle incoming notifications *)
+
Printf.printf "Notification: %s\n" method_
+
);
+
timeout = Some 30.0; (* 30 second timeout *)
+
clock = Some (C (Eio.Stdenv.clock env));
+
}
+
in
+
+
Eio.Switch.run @@ fun sw ->
+
let session = Session.create ~sw config in
+
+
(* Send a request and wait for response *)
+
let response = Session.send_request session
+
~method_:"initialize"
+
~params:(`Object [("version", `String "1.0")])
+
()
+
in
+
+
(* Send a notification (no response expected) *)
+
Session.send_notification session
+
~method_:"progress"
+
~params:(`Object [("percent", `Number 50.0)])
+
()
+
]} *)
+
+
(** {1 Handlers} *)
+
+
type request_handler =
+
method_:string ->
+
params:Jsont.json option ->
+
Jsont.json
+
(** Handler for incoming requests. Should return the result value.
+
May raise exceptions which will be converted to JSON-RPC errors. *)
+
+
type notification_handler =
+
method_:string ->
+
params:Jsont.json option ->
+
unit
+
(** Handler for incoming notifications. No response is expected. *)
+
+
(** {1 Configuration} *)
+
+
type clock = C : _ Eio.Time.clock -> clock
+
(** Wrapper for existential clock type *)
+
+
type config = {
+
transport : Transport.t;
+
(** Transport layer for sending/receiving JSON messages *)
+
request_handler : request_handler;
+
(** Handler for incoming requests *)
+
notification_handler : notification_handler;
+
(** Handler for incoming notifications *)
+
timeout : float option;
+
(** Request timeout in seconds. [None] means no timeout. *)
+
clock : clock option;
+
(** Clock for timeout handling. Required if [timeout] is set. *)
+
}
+
(** Session configuration *)
+
+
(** {1 Session Management} *)
+
+
type t
+
(** Session handle *)
+
+
exception Timeout of string
+
(** Raised when a request times out *)
+
+
exception Session_closed
+
(** Raised when attempting to use a closed session *)
+
+
exception Unknown_response of Jsonrpc.Id.t
+
(** Raised when receiving a response for an unknown request ID *)
+
+
exception Remote_error of Jsonrpc.Error_data.t
+
(** Raised when the remote side returns an error response *)
+
+
val create :
+
sw:Eio.Switch.t ->
+
config ->
+
t
+
(** Create and start a session with a background receive loop.
+
The receive loop runs in a background fiber attached to [sw]. *)
+
+
val send_request :
+
t ->
+
method_:string ->
+
?params:Jsont.json ->
+
unit ->
+
Jsont.json
+
(** Send a request and await the response.
+
Raises [Timeout] if the request times out.
+
Raises [Remote_error] if the server returns an error.
+
Raises [Session_closed] if the session is closed. *)
+
+
val send_notification :
+
t ->
+
method_:string ->
+
?params:Jsont.json ->
+
unit ->
+
unit
+
(** Send a notification (no response expected).
+
Raises [Session_closed] if the session is closed. *)
+
+
val close : t -> unit
+
(** Close the session and underlying transport.
+
This will cancel all pending requests. *)
+
+
val is_closed : t -> bool
+
(** Check if the session is closed *)
+29
claudeio/lib_mcp/transport.ml
···
···
+
(** Abstract transport layer for MCP communication *)
+
+
exception Connection_error of string
+
+
(** Internal module type that transport implementations must satisfy *)
+
module type TRANSPORT = sig
+
type state
+
+
val send : state -> Jsont.json -> unit
+
val receive : state -> Jsont.json option
+
val close : state -> unit
+
val is_closed : state -> bool
+
end
+
+
(** The abstract transport type - hides the concrete implementation *)
+
type t = T : (module TRANSPORT with type state = 'a) * 'a -> t
+
[@@warning "-37"] (* Constructor T is used in transport implementations *)
+
+
let send (T ((module M), state)) json =
+
M.send state json
+
+
let receive (T ((module M), state)) =
+
M.receive state
+
+
let close (T ((module M), state)) =
+
M.close state
+
+
let is_closed (T ((module M), state)) =
+
M.is_closed state
+34
claudeio/lib_mcp/transport.mli
···
···
+
(** Abstract transport layer for MCP communication *)
+
+
(** Module type that transport implementations must satisfy *)
+
module type TRANSPORT = sig
+
type state
+
+
val send : state -> Jsont.json -> unit
+
val receive : state -> Jsont.json option
+
val close : state -> unit
+
val is_closed : state -> bool
+
end
+
+
(** The abstract transport type for sending/receiving JSON messages *)
+
type t = T : (module TRANSPORT with type state = 'a) * 'a -> t
+
+
(** [send t json] sends a JSON message through the transport.
+
@raise Connection_error if the transport is closed or sending fails *)
+
val send : t -> Jsont.json -> unit
+
+
(** [receive t] receives a JSON message from the transport (blocking).
+
Returns [None] on EOF or when the transport is closed.
+
@raise Connection_error if receiving fails for reasons other than EOF *)
+
val receive : t -> Jsont.json option
+
+
(** [close t] closes the transport and releases all associated resources.
+
This is idempotent - calling close multiple times is safe. *)
+
val close : t -> unit
+
+
(** [is_closed t] checks if the transport is closed.
+
Returns [true] if the transport has been closed, [false] otherwise. *)
+
val is_closed : t -> bool
+
+
(** Exception raised when transport operations fail *)
+
exception Connection_error of string
+179
claudeio/lib_mcp/transport_stdio.ml
···
···
+
(** Stdio transport implementation for MCP *)
+
+
let src = Logs.Src.create "mcp.transport.stdio" ~doc:"MCP stdio transport"
+
module Log = (val Logs.src_log src : Logs.LOG)
+
+
exception Process_spawn_error of string
+
+
(** Parameters for creating a stdio transport *)
+
type params = {
+
command : string;
+
args : string list;
+
env : (string * string) list option;
+
max_buffer_size : int option;
+
}
+
+
(** Wrapper for existential process type *)
+
type process = P : _ Eio.Process.t -> process
+
+
(** Internal state for stdio transport *)
+
type state = {
+
process : process;
+
stdin : Eio.Flow.sink_ty Eio.Resource.t;
+
stdin_close : [`Close | `Flow] Eio.Resource.t;
+
stdout : Eio.Buf_read.t;
+
mutable closed : bool;
+
sw : Eio.Switch.t;
+
}
+
+
(** Send a JSON message by encoding to a line-delimited string *)
+
let send state json =
+
if state.closed then
+
raise (Transport.Connection_error "Transport is closed");
+
+
let data = match Jsont_bytesrw.encode_string' Jsont.json json with
+
| Ok s -> s
+
| Error err ->
+
let msg = Jsont.Error.to_string err in
+
raise (Transport.Connection_error ("JSON encoding failed: " ^ msg))
+
in
+
+
Log.debug (fun m -> m "Sending: %s" data);
+
+
try
+
Eio.Flow.write state.stdin [Cstruct.of_string (data ^ "\n")]
+
with
+
| exn ->
+
Log.err (fun m -> m "Failed to send message: %s" (Printexc.to_string exn));
+
raise (Transport.Connection_error
+
(Printf.sprintf "Failed to send message: %s" (Printexc.to_string exn)))
+
+
(** Receive a JSON message by reading a line and decoding *)
+
let receive state =
+
if state.closed then
+
None
+
else
+
try
+
match Eio.Buf_read.line state.stdout with
+
| line ->
+
Log.debug (fun m -> m "Received: %s" line);
+
(match Jsont_bytesrw.decode_string' Jsont.json line with
+
| Ok json -> Some json
+
| Error err ->
+
let msg = Jsont.Error.to_string err in
+
Log.err (fun m -> m "JSON decoding failed: %s" msg);
+
raise (Transport.Connection_error ("JSON decoding failed: " ^ msg)))
+
| exception End_of_file ->
+
Log.debug (fun m -> m "Received EOF");
+
state.closed <- true;
+
None
+
with
+
| Transport.Connection_error _ as e -> raise e
+
| exn ->
+
Log.err (fun m -> m "Failed to receive message: %s" (Printexc.to_string exn));
+
raise (Transport.Connection_error
+
(Printf.sprintf "Failed to receive message: %s" (Printexc.to_string exn)))
+
+
(** Close the transport and cleanup resources *)
+
let close state =
+
if not state.closed then begin
+
state.closed <- true;
+
try
+
Eio.Flow.close state.stdin_close;
+
let (P process) = state.process in
+
Eio.Process.await_exn process
+
with _ -> ()
+
end
+
+
(** Check if transport is closed *)
+
let is_closed state =
+
state.closed
+
+
(** The transport module implementation *)
+
module Stdio_transport : Transport.TRANSPORT with type state = state = struct
+
type nonrec state = state
+
+
let send = send
+
let receive = receive
+
let close = close
+
let is_closed = is_closed
+
end
+
+
(** Create a new stdio transport *)
+
let create ~sw ~process_mgr params =
+
(* Build command arguments *)
+
let cmd = params.command :: params.args in
+
+
(* Build environment - preserve essential vars and add custom ones *)
+
let home = try Unix.getenv "HOME" with Not_found -> "/tmp" in
+
let path = try Unix.getenv "PATH" with Not_found -> "/usr/bin:/bin" in
+
+
(* Preserve other potentially important environment variables *)
+
let preserve_vars = [
+
"USER"; "LOGNAME"; "SHELL"; "TERM";
+
"XDG_CONFIG_HOME"; "XDG_DATA_HOME"; "XDG_CACHE_HOME";
+
] in
+
+
let preserved = List.filter_map (fun var ->
+
try Some (Printf.sprintf "%s=%s" var (Unix.getenv var))
+
with Not_found -> None
+
) preserve_vars in
+
+
let base_env = [
+
Printf.sprintf "HOME=%s" home;
+
Printf.sprintf "PATH=%s" path;
+
] @ preserved in
+
+
let custom_env = match params.env with
+
| None -> []
+
| Some vars -> List.map (fun (k, v) -> Printf.sprintf "%s=%s" k v) vars
+
in
+
+
let env = Array.of_list (base_env @ custom_env) in
+
+
Log.debug (fun m -> m "Environment: HOME=%s, PATH=%s" home path);
+
Log.info (fun m -> m "Spawning command: %s" (String.concat " " cmd));
+
+
(* Create pipes for stdin/stdout *)
+
let stdin_r, stdin_w = Eio.Process.pipe ~sw process_mgr in
+
let stdout_r, stdout_w = Eio.Process.pipe ~sw process_mgr in
+
+
(* Spawn the process *)
+
let process =
+
try
+
Eio.Process.spawn ~sw process_mgr
+
~env
+
~stdin:(stdin_r :> Eio.Flow.source_ty Eio.Resource.t)
+
~stdout:(stdout_w :> Eio.Flow.sink_ty Eio.Resource.t)
+
cmd
+
with
+
| exn ->
+
Log.err (fun m -> m "Failed to spawn process: %s" (Printexc.to_string exn));
+
raise (Process_spawn_error
+
(Printf.sprintf "Failed to spawn process: %s" (Printexc.to_string exn)))
+
in
+
+
(* Setup stdin for writing *)
+
let stdin = (stdin_w :> Eio.Flow.sink_ty Eio.Resource.t) in
+
let stdin_close = (stdin_w :> [`Close | `Flow] Eio.Resource.t) in
+
+
(* Setup stdout for reading with buffering *)
+
let max_size = match params.max_buffer_size with
+
| Some size -> size
+
| None -> 1_000_000 (* Default 1MB *)
+
in
+
let stdout = Eio.Buf_read.of_flow ~max_size
+
(stdout_r :> Eio.Flow.source_ty Eio.Resource.t) in
+
+
(* Create the state *)
+
let state = {
+
process = P process;
+
stdin;
+
stdin_close;
+
stdout;
+
closed = false;
+
sw;
+
} in
+
+
(* Wrap in abstract transport type *)
+
Transport.T ((module Stdio_transport), state)
+38
claudeio/lib_mcp/transport_stdio.mli
···
···
+
(** Stdio transport implementation for MCP *)
+
+
(** Parameters for creating a stdio transport *)
+
type params = {
+
command : string;
+
(** The command to execute (executable path or name in PATH) *)
+
+
args : string list;
+
(** Command-line arguments to pass to the command *)
+
+
env : (string * string) list option;
+
(** Optional environment variables to set. If [None], inherits parent environment.
+
If [Some vars], these are ADDED to essential preserved variables (HOME, PATH, etc.) *)
+
+
max_buffer_size : int option;
+
(** Maximum buffer size for reading from stdout. Defaults to 1MB if [None] *)
+
}
+
+
(** [create ~sw ~process_mgr params] creates a new stdio transport by spawning
+
a subprocess with the given parameters.
+
+
The subprocess communicates via line-delimited JSON on stdin/stdout:
+
- Each message is a single JSON object on one line
+
- Lines are terminated with newline ('\n')
+
- The transport handles encoding/decoding automatically
+
+
@param sw The Eio switch that manages the subprocess lifetime
+
@param process_mgr The Eio process manager for spawning subprocesses
+
@param params Configuration parameters for the subprocess
+
@raise Transport.Connection_error if subprocess spawning fails *)
+
val create :
+
sw:Eio.Switch.t ->
+
process_mgr:_ Eio.Process.mgr ->
+
params ->
+
Transport.t
+
+
(** Exception raised when subprocess spawning fails *)
+
exception Process_spawn_error of string
+30
claudeio/mcp.opam
···
···
+
# This file is generated by dune, edit dune-project instead
+
opam-version: "2.0"
+
synopsis: "Model Context Protocol (MCP) implementation in OCaml"
+
description:
+
"An Eio-based OCaml library implementing the Model Context Protocol for connecting AI assistants with tools and data sources"
+
depends: [
+
"ocaml"
+
"dune" {>= "3.0"}
+
"eio"
+
"fmt"
+
"logs"
+
"jsont" {>= "0.2.0"}
+
"jsont_bytesrw" {>= "0.2.0"}
+
"alcotest" {with-test}
+
"odoc" {with-doc}
+
]
+
build: [
+
["dune" "subst"] {dev}
+
[
+
"dune"
+
"build"
+
"-p"
+
name
+
"-j"
+
jobs
+
"@install"
+
"@runtest" {with-test}
+
"@doc" {with-doc}
+
]
+
]