···32323333 {2 Domain Types}
3434 - {!Content_block}: Content blocks (text, tool use, tool results, thinking)
3535- - {!Message}: Messages exchanged with Claude (user, assistant, system, result)
3535+ - {!Message}: Messages exchanged with Claude (user, assistant, system,
3636+ result)
3637 - {!Tool_input}: Opaque tool input with typed accessors
3738 - {!Server_info}: Server capabilities and metadata
3839···4445 {[
4546 open Eio.Std
46474747- let () = Eio_main.run @@ fun env ->
4848+ let () =
4949+ Eio_main.run @@ fun env ->
4850 Switch.run @@ fun sw ->
4949- let client = Claude.Client.create ~sw
5050- ~process_mgr:(Eio.Stdenv.process_mgr env) () in
5151+ let client =
5252+ Claude.Client.create ~sw ~process_mgr:(Eio.Stdenv.process_mgr env) ()
5353+ in
51545255 Claude.Client.query client "What is 2+2?";
53565454- let handler = object
5555- inherit Claude.Handler.default
5656- method! on_text t = print_endline (Claude.Response.Text.content t)
5757- end in
5757+ let handler =
5858+ object
5959+ inherit Claude.Handler.default
6060+ method! on_text t = print_endline (Claude.Response.Text.content t)
6161+ end
6262+ in
58635964 Claude.Client.run client ~handler
6065 ]}
···6873 Subclass {!Handler.default} and override only the methods you need:
69747075 {[
7171- let my_handler = object
7272- inherit Claude.Handler.default
7373-7474- method! on_text t =
7575- print_endline (Claude.Response.Text.content t)
7676+ let my_handler =
7777+ object
7878+ inherit Claude.Handler.default
7979+ method! on_text t = print_endline (Claude.Response.Text.content t)
76807777- method! on_tool_use t =
7878- Printf.printf "Tool: %s\n" (Claude.Response.Tool_use.name t)
8181+ method! on_tool_use t =
8282+ Printf.printf "Tool: %s\n" (Claude.Response.Tool_use.name t)
79838080- method! on_complete c =
8181- Printf.printf "Done! Cost: $%.4f\n"
8282- (Option.value ~default:0.0 (Claude.Response.Complete.total_cost_usd c))
8383- end in
8484+ method! on_complete c =
8585+ Printf.printf "Done! Cost: $%.4f\n"
8686+ (Option.value ~default:0.0
8787+ (Claude.Response.Complete.total_cost_usd c))
8888+ end
8989+ in
84908591 Claude.Client.run client ~handler:my_handler
8692 ]}
···9298 {[
9399 Claude.Client.receive client
94100 |> Seq.iter (function
9595- | Claude.Response.Text t -> print_endline (Claude.Response.Text.content t)
101101+ | Claude.Response.Text t ->
102102+ print_endline (Claude.Response.Text.content t)
96103 | Claude.Response.Complete c -> Printf.printf "Done!\n"
97104 | _ -> ())
98105 ]}
···105112 let options =
106113 Claude.Options.default
107114 |> Claude.Options.with_allowed_tools [ "Read"; "Write"; "Bash" ]
108108- |> Claude.Options.with_permission_mode Claude.Permissions.Mode.Accept_edits
115115+ |> Claude.Options.with_permission_mode
116116+ Claude.Permissions.Mode.Accept_edits
109117 ]}
110118111119 {2 Custom Permission Callbacks}
···115123 {[
116124 let my_callback ctx =
117125 if ctx.Claude.Permissions.tool_name = "Bash" then
118118- Claude.Permissions.Decision.deny ~message:"Bash not allowed" ~interrupt:false
119119- else
120120- Claude.Permissions.Decision.allow ()
126126+ Claude.Permissions.Decision.deny ~message:"Bash not allowed"
127127+ ~interrupt:false
128128+ else Claude.Permissions.Decision.allow ()
121129122130 let options =
123131 Claude.Options.default
···132140 let hooks =
133141 Claude.Hooks.empty
134142 |> Claude.Hooks.on_pre_tool_use ~pattern:"Bash" (fun input ->
135135- if String.is_prefix ~prefix:"rm" (input.tool_input |> Claude.Tool_input.get_string "command" |> Option.value ~default:"") then
136136- Claude.Hooks.PreToolUse.deny ~reason:"Dangerous command" ()
137137- else
138138- Claude.Hooks.PreToolUse.continue ())
143143+ if
144144+ String.is_prefix ~prefix:"rm"
145145+ (input.tool_input
146146+ |> Claude.Tool_input.get_string "command"
147147+ |> Option.value ~default:"")
148148+ then Claude.Hooks.PreToolUse.deny ~reason:"Dangerous command" ()
149149+ else Claude.Hooks.PreToolUse.continue ())
139150140140- let options =
141141- Claude.Options.default |> Claude.Options.with_hooks hooks
151151+ let options = Claude.Options.default |> Claude.Options.with_hooks hooks
142152 ]}
143153144154 {1 Error Handling}
···146156 The library uses a structured exception type {!Err.E} for all errors:
147157148158 {[
149149- try
150150- Claude.Client.query client "Hello"
159159+ try Claude.Client.query client "Hello"
151160 with Claude.Err.E err ->
152161 Printf.eprintf "Error: %s\n" (Claude.Err.to_string err)
153162 ]}
···222231 {2 Example}
223232224233 {[
225225- let greet = Claude.Tool.create
226226- ~name:"greet"
227227- ~description:"Greet a user"
228228- ~input_schema:(Claude.Tool.schema_object
229229- ["name", Claude.Tool.schema_string]
230230- ~required:["name"])
231231- ~handler:(fun args ->
232232- match Claude.Tool_input.get_string args "name" with
233233- | Some name -> Ok (Claude.Tool.text_result ("Hello, " ^ name ^ "!"))
234234- | None -> Error "Missing name")
234234+ let greet =
235235+ Claude.Tool.create ~name:"greet" ~description:"Greet a user"
236236+ ~input_schema:
237237+ (Claude.Tool.schema_object
238238+ [ ("name", Claude.Tool.schema_string) ]
239239+ ~required:[ "name" ])
240240+ ~handler:(fun args ->
241241+ match Claude.Tool_input.get_string args "name" with
242242+ | Some name -> Ok (Claude.Tool.text_result ("Hello, " ^ name ^ "!"))
243243+ | None -> Error "Missing name")
235244236236- let server = Claude.Mcp_server.create
237237- ~name:"my-tools"
238238- ~tools:[greet]
239239- ()
245245+ let server = Claude.Mcp_server.create ~name:"my-tools" ~tools:[ greet ] ()
240246241241- let options = Claude.Options.default
247247+ let options =
248248+ Claude.Options.default
242249 |> Claude.Options.with_mcp_server ~name:"tools" server
243243- |> Claude.Options.with_allowed_tools ["mcp__tools__greet"]
250250+ |> Claude.Options.with_allowed_tools [ "mcp__tools__greet" ]
244251 ]} *)
245252246253module Tool = Tool
+88-54
lib/client.ml
···1616 |> Err.get_ok ~msg:"Control_response.success: "
17171818 let error ~request_id ~code ~message ?data () =
1919- let error_detail = Sdk_control.Response.error_detail ~code ~message ?data () in
1919+ let error_detail =
2020+ Sdk_control.Response.error_detail ~code ~message ?data ()
2121+ in
2022 let resp = Sdk_control.Response.error ~request_id ~error:error_detail () in
2123 let ctrl = Sdk_control.create_response ~response:resp () in
2224 Jsont.Json.encode Sdk_control.jsont ctrl
···8183 (json_to_string input_json));
8284 (* Convert permission_suggestions to suggested rules *)
8385 let suggestions = Option.value req.permission_suggestions ~default:[] in
8484- let suggested_rules = Permissions.extract_rules_from_proto_updates suggestions in
8686+ let suggested_rules =
8787+ Permissions.extract_rules_from_proto_updates suggestions
8888+ in
85898690 (* Convert input to Tool_input.t *)
8791 let input = Tool_input.of_json input_json in
···9498 Log.info (fun m ->
9599 m "Invoking permission callback for tool: %s" tool_name);
96100 let callback =
9797- Option.value t.permission_callback
9898- ~default:Permissions.default_allow
101101+ Option.value t.permission_callback ~default:Permissions.default_allow
99102 in
100103 let decision = callback context in
101104 Log.info (fun m ->
···103106 (if Permissions.Decision.is_allow decision then "ALLOW" else "DENY"));
104107105108 (* Convert permission decision to proto result *)
106106- let proto_result = Permissions.Decision.to_proto_result ~original_input:input decision in
109109+ let proto_result =
110110+ Permissions.Decision.to_proto_result ~original_input:input decision
111111+ in
107112108113 (* Encode to JSON *)
109114 let response_data =
···148153 in
149154 Log.err (fun m -> m "%s" error_msg);
150155 Transport.send t.transport
151151- (Control_response.error ~request_id ~code:`Method_not_found ~message:error_msg ())
156156+ (Control_response.error ~request_id ~code:`Method_not_found
157157+ ~message:error_msg ())
152158 | exn ->
153159 let error_msg =
154160 Printf.sprintf "Hook callback error: %s" (Printexc.to_string exn)
155161 in
156162 Log.err (fun m -> m "%s" error_msg);
157163 Transport.send t.transport
158158- (Control_response.error ~request_id ~code:`Internal_error ~message:error_msg ()))
164164+ (Control_response.error ~request_id ~code:`Internal_error
165165+ ~message:error_msg ()))
159166 | Sdk_control.Request.Mcp_message req -> (
160160- (* Handle MCP request for in-process SDK servers *)
161161- let module J = Jsont.Json in
167167+ let module
168168+ (* Handle MCP request for in-process SDK servers *)
169169+ J =
170170+ Jsont.Json
171171+ in
162172 let server_name = req.server_name in
163173 let message = req.message in
164174 Log.info (fun m -> m "MCP request for server '%s'" server_name);
165175166176 match Hashtbl.find_opt t.mcp_servers server_name with
167177 | None ->
168168- let error_msg = Printf.sprintf "MCP server '%s' not found" server_name in
178178+ let error_msg =
179179+ Printf.sprintf "MCP server '%s' not found" server_name
180180+ in
169181 Log.err (fun m -> m "%s" error_msg);
170182 (* Return JSONRPC error in mcp_response format *)
171171- let mcp_error = J.object' [
172172- J.mem (J.name "jsonrpc") (J.string "2.0");
173173- J.mem (J.name "id") (J.null ());
174174- J.mem (J.name "error") (J.object' [
175175- J.mem (J.name "code") (J.number (-32601.0));
176176- J.mem (J.name "message") (J.string error_msg)
177177- ])
178178- ] in
179179- let response_data = J.object' [J.mem (J.name "mcp_response") mcp_error] in
180180- let response = Control_response.success ~request_id ~response:(Some response_data) in
183183+ let mcp_error =
184184+ J.object'
185185+ [
186186+ J.mem (J.name "jsonrpc") (J.string "2.0");
187187+ J.mem (J.name "id") (J.null ());
188188+ J.mem (J.name "error")
189189+ (J.object'
190190+ [
191191+ J.mem (J.name "code") (J.number (-32601.0));
192192+ J.mem (J.name "message") (J.string error_msg);
193193+ ]);
194194+ ]
195195+ in
196196+ let response_data =
197197+ J.object' [ J.mem (J.name "mcp_response") mcp_error ]
198198+ in
199199+ let response =
200200+ Control_response.success ~request_id ~response:(Some response_data)
201201+ in
181202 Transport.send t.transport response
182203 | Some server ->
183204 let mcp_response = Mcp_server.handle_json_message server message in
184184- Log.debug (fun m -> m "MCP response: %s" (json_to_string mcp_response));
185185- let response_data = J.object' [J.mem (J.name "mcp_response") mcp_response] in
186186- let response = Control_response.success ~request_id ~response:(Some response_data) in
205205+ Log.debug (fun m ->
206206+ m "MCP response: %s" (json_to_string mcp_response));
207207+ let response_data =
208208+ J.object' [ J.mem (J.name "mcp_response") mcp_response ]
209209+ in
210210+ let response =
211211+ Control_response.success ~request_id ~response:(Some response_data)
212212+ in
187213 Transport.send t.transport response)
188214 | _ ->
189215 (* Other request types not handled here *)
190216 let error_msg = "Unsupported control request type" in
191217 Transport.send t.transport
192192- (Control_response.error ~request_id ~code:`Invalid_request ~message:error_msg ())
218218+ (Control_response.error ~request_id ~code:`Invalid_request
219219+ ~message:error_msg ())
193220194221let handle_control_response t control_resp =
195222 let request_id =
···219246 | Some line -> (
220247 (* Use unified Incoming codec for all message types *)
221248 match Jsont_bytesrw.decode_string' Incoming.jsont line with
222222- | Ok incoming ->
223223- Seq.Cons (incoming, loop)
249249+ | Ok incoming -> Seq.Cons (incoming, loop)
224250 | Error err ->
225251 Log.err (fun m ->
226252 m "Failed to decode incoming message: %s\nLine: %s"
···262288 ctrl_req.request_id);
263289 handle_control_request t ctrl_req;
264290 loop rest)
265265-266291 and emit_responses responses rest =
267292 match responses with
268293 | [] -> loop rest
···288313289314 (* Setup MCP servers from options *)
290315 let mcp_servers_ht = Hashtbl.create 16 in
291291- List.iter (fun (name, server) ->
292292- Log.info (fun m -> m "Registering MCP server: %s" name);
293293- Hashtbl.add mcp_servers_ht name server
294294- ) (Options.mcp_servers options);
316316+ List.iter
317317+ (fun (name, server) ->
318318+ Log.info (fun m -> m "Registering MCP server: %s" name);
319319+ Hashtbl.add mcp_servers_ht name server)
320320+ (Options.mcp_servers options);
295321296322 let t =
297323 {
···331357 incr next_callback_id;
332358 Hashtbl.add hook_callbacks callback_id callback;
333359 Log.debug (fun m ->
334334- m "Registered callback: %s for event: %s"
335335- callback_id event_name);
360360+ m "Registered callback: %s for event: %s" callback_id
361361+ event_name);
336362 Hook_matcher_wire.
337337- {
338338- matcher = pattern;
339339- hook_callback_ids = [callback_id];
340340- })
363363+ { matcher = pattern; hook_callback_ids = [ callback_id ] })
341364 matchers
342365 in
343366 (event_name, Hook_matcher_wire.encode matcher_wires))
···373396let respond_to_tool t ~tool_use_id ~content ?(is_error = false) () =
374397 (* Check for duplicate response - prevents API errors from multiple responses *)
375398 if Hashtbl.mem t.responded_tool_ids tool_use_id then begin
376376- Log.warn (fun m -> m "Skipping duplicate tool response for tool_use_id: %s" tool_use_id)
377377- end else begin
399399+ Log.warn (fun m ->
400400+ m "Skipping duplicate tool response for tool_use_id: %s" tool_use_id)
401401+ end
402402+ else begin
378403 Hashtbl.add t.responded_tool_ids tool_use_id ();
379379- let user_msg = Message.User.with_tool_result ~tool_use_id ~content ~is_error () in
404404+ let user_msg =
405405+ Message.User.with_tool_result ~tool_use_id ~content ~is_error ()
406406+ in
380407 let msg = Message.User user_msg in
381408 send_message t msg
382409 end
383410384411let respond_to_tools t responses =
385412 (* Filter out duplicates *)
386386- let new_responses = List.filter (fun (tool_use_id, _, _) ->
387387- if Hashtbl.mem t.responded_tool_ids tool_use_id then begin
388388- Log.warn (fun m -> m "Skipping duplicate tool response for tool_use_id: %s" tool_use_id);
389389- false
390390- end else begin
391391- Hashtbl.add t.responded_tool_ids tool_use_id ();
392392- true
393393- end
394394- ) responses in
413413+ let new_responses =
414414+ List.filter
415415+ (fun (tool_use_id, _, _) ->
416416+ if Hashtbl.mem t.responded_tool_ids tool_use_id then begin
417417+ Log.warn (fun m ->
418418+ m "Skipping duplicate tool response for tool_use_id: %s"
419419+ tool_use_id);
420420+ false
421421+ end
422422+ else begin
423423+ Hashtbl.add t.responded_tool_ids tool_use_id ();
424424+ true
425425+ end)
426426+ responses
427427+ in
395428 if new_responses <> [] then begin
396429 let tool_results =
397430 List.map
···405438 send_message t msg
406439 end
407440408408-let clear_tool_response_tracking t =
409409- Hashtbl.clear t.responded_tool_ids
410410-441441+let clear_tool_response_tracking t = Hashtbl.clear t.responded_tool_ids
411442let receive t = fun () -> handle_messages t
412443413444let run t ~handler =
···416447 let rec loop seq =
417448 match seq () with
418449 | Seq.Nil -> ()
419419- | Seq.Cons (Response.Complete _ as resp, _) ->
450450+ | Seq.Cons ((Response.Complete _ as resp), _) ->
420451 Handler.dispatch handler resp
421452 | Seq.Cons (resp, rest) ->
422453 Handler.dispatch handler resp;
···506537 match response with
507538 | Sdk_control.Response.Success s -> s.response
508539 | Sdk_control.Response.Error e ->
509509- raise (Failure (Printf.sprintf "Control request failed: [%d] %s" e.error.code e.error.message))
540540+ raise
541541+ (Failure
542542+ (Printf.sprintf "Control request failed: [%d] %s" e.error.code
543543+ e.error.message))
510544511545let set_permission_mode t mode =
512546 let request_id = Printf.sprintf "set_perm_mode_%f" (Eio.Time.now t.clock) in
+34-24
lib/client.mli
···3636 {2 Message Flow}
37373838 1. Create a client with {!create} 2. Send messages with {!query} or
3939- {!Advanced.send_message} 3. Receive responses with {!receive} or {!receive_all} 4.
4040- Continue multi-turn conversations by sending more messages 5. Client
4141- automatically cleans up when the switch exits
3939+ {!Advanced.send_message} 3. Receive responses with {!receive} or
4040+ {!receive_all} 4. Continue multi-turn conversations by sending more messages
4141+ 5. Client automatically cleans up when the switch exits
42424343 {2 Advanced Features}
4444···8181 {!Advanced.send_message} instead. *)
82828383val respond_to_tool :
8484- t -> tool_use_id:string -> content:Jsont.json -> ?is_error:bool -> unit -> unit
8484+ t ->
8585+ tool_use_id:string ->
8686+ content:Jsont.json ->
8787+ ?is_error:bool ->
8888+ unit ->
8989+ unit
8590(** [respond_to_tool t ~tool_use_id ~content ?is_error ()] responds to a tool
8691 use request.
8792···9095 prevents API errors from duplicate tool responses.
91969297 @param tool_use_id The ID from the {!Response.Tool_use.t} event
9393- @param content The result content (can be a string or array of content blocks)
9898+ @param content
9999+ The result content (can be a string or array of content blocks)
94100 @param is_error Whether this is an error response (default: false) *)
9510196102val respond_to_tools : t -> (string * Jsont.json * bool option) list -> unit
97103(** [respond_to_tools t responses] responds to multiple tool use requests at
98104 once.
99105100100- {b Duplicate protection:} Any [tool_use_id] that has already been
101101- responded to is filtered out with a warning log.
106106+ {b Duplicate protection:} Any [tool_use_id] that has already been responded
107107+ to is filtered out with a warning log.
102108103103- Each tuple is [(tool_use_id, content, is_error option)] where content
104104- can be a string or array of content blocks.
109109+ Each tuple is [(tool_use_id, content, is_error option)] where content can be
110110+ a string or array of content blocks.
105111106112 Example:
107113 {[
···116122(** [clear_tool_response_tracking t] clears the internal tracking of which
117123 tool_use_ids have been responded to.
118124119119- This is useful when starting a new conversation or turn where you want
120120- to allow responses to previously-seen tool IDs. Normally this is not
121121- needed as tool IDs are unique per conversation turn. *)
125125+ This is useful when starting a new conversation or turn where you want to
126126+ allow responses to previously-seen tool IDs. Normally this is not needed as
127127+ tool IDs are unique per conversation turn. *)
122128123129(** {1 Response Handling} *)
124130···130136131137 Example:
132138 {[
133133- let my_handler = object
134134- inherit Claude.Handler.default
135135- method! on_text t = print_endline (Response.Text.content t)
136136- method! on_complete c =
137137- Printf.printf "Cost: $%.4f\n"
138138- (Option.value ~default:0.0 (Response.Complete.total_cost_usd c))
139139- end in
139139+ let my_handler =
140140+ object
141141+ inherit Claude.Handler.default
142142+ method! on_text t = print_endline (Response.Text.content t)
143143+144144+ method! on_complete c =
145145+ Printf.printf "Cost: $%.4f\n"
146146+ (Option.value ~default:0.0 (Response.Complete.total_cost_usd c))
147147+ end
148148+ in
140149 Client.query client "Hello";
141150 Client.run client ~handler:my_handler
142151 ]} *)
···293302 val send_raw : t -> Sdk_control.t -> unit
294303 (** [send_raw t control] sends a raw SDK control message.
295304296296- This is for advanced use cases that need direct control protocol access. *)
305305+ This is for advanced use cases that need direct control protocol access.
306306+ *)
297307298308 val send_json : t -> Jsont.json -> unit
299309 (** [send_json t json] sends raw JSON to Claude.
···305315306316 This includes all message types before Response conversion:
307317 - {!Proto.Incoming.t.constructor-Message} - Regular messages
308308- - {!Proto.Incoming.t.constructor-Control_response} - Control responses (normally handled
309309- internally)
310310- - {!Proto.Incoming.t.constructor-Control_request} - Control requests (normally handled
311311- internally)
318318+ - {!Proto.Incoming.t.constructor-Control_response} - Control responses
319319+ (normally handled internally)
320320+ - {!Proto.Incoming.t.constructor-Control_request} - Control requests
321321+ (normally handled internally)
312322313323 Most users should use {!receive} or {!run} instead. *)
314324end
+5-7
lib/content_block.ml
···20202121 let id = Proto.Content_block.Tool_use.id
2222 let name = Proto.Content_block.Tool_use.name
2323-2424- let input t =
2525- Proto.Content_block.Tool_use.input t |> Tool_input.of_json
2626-2323+ let input t = Proto.Content_block.Tool_use.input t |> Tool_input.of_json
2724 let of_proto proto = proto
2828-2925 let to_proto t = t
3026end
3127···6763 | Proto.Content_block.Tool_use proto_tool_use ->
6864 Tool_use (Tool_use.of_proto proto_tool_use)
6965 | _ ->
7070- failwith "Internal error: Proto.Content_block.tool_use returned non-Tool_use"
6666+ failwith
6767+ "Internal error: Proto.Content_block.tool_use returned non-Tool_use"
71687269let tool_result ~tool_use_id ?content ?is_error () =
7370 let proto =
···7875 Tool_result (Tool_result.of_proto proto_tool_result)
7976 | _ ->
8077 failwith
8181- "Internal error: Proto.Content_block.tool_result returned non-Tool_result"
7878+ "Internal error: Proto.Content_block.tool_result returned \
7979+ non-Tool_result"
82808381let thinking ~thinking ~signature =
8482 let proto = Proto.Content_block.thinking ~thinking ~signature in
···4040(** {1 Result Helpers} *)
41414242val get_ok : msg:string -> ('a, string) result -> 'a
4343-(** [get_ok ~msg result] returns the Ok value or raises Protocol_error with msg prefix. *)
4343+(** [get_ok ~msg result] returns the Ok value or raises Protocol_error with msg
4444+ prefix. *)
44454546val get_ok' : msg:string -> ('a, string) result -> 'a
4646-(** [get_ok' ~msg result] returns the Ok value or raises Protocol_error with string error. *)
4747+(** [get_ok' ~msg result] returns the Ok value or raises Protocol_error with
4848+ string error. *)
+9-10
lib/handler.ml
···7788(** {1 Handler Interface} *)
991010-class type handler =
1111- object
1212- method on_text : Response.Text.t -> unit
1313- method on_tool_use : Response.Tool_use.t -> unit
1414- method on_tool_result : Content_block.Tool_result.t -> unit
1515- method on_thinking : Response.Thinking.t -> unit
1616- method on_init : Response.Init.t -> unit
1717- method on_error : Response.Error.t -> unit
1818- method on_complete : Response.Complete.t -> unit
1919- end
1010+class type handler = object
1111+ method on_text : Response.Text.t -> unit
1212+ method on_tool_use : Response.Tool_use.t -> unit
1313+ method on_tool_result : Content_block.Tool_result.t -> unit
1414+ method on_thinking : Response.Thinking.t -> unit
1515+ method on_init : Response.Init.t -> unit
1616+ method on_error : Response.Error.t -> unit
1717+ method on_complete : Response.Complete.t -> unit
1818+end
20192120(** {1 Concrete Implementations} *)
2221
+49-42
lib/handler.mli
···1616 methods you care about:
17171818 {[
1919- let my_handler = object
2020- inherit Claude.Handler.default
2121- method! on_text t = print_endline (Response.Text.content t)
2222- method! on_complete c =
2323- Printf.printf "Done! Cost: $%.4f\n"
2424- (Option.value ~default:0.0 (Response.Complete.total_cost_usd c))
2525- end
1919+ let my_handler =
2020+ object
2121+ inherit Claude.Handler.default
2222+ method! on_text t = print_endline (Response.Text.content t)
2323+2424+ method! on_complete c =
2525+ Printf.printf "Done! Cost: $%.4f\n"
2626+ (Option.value ~default:0.0 (Response.Complete.total_cost_usd c))
2727+ end
2628 ]}
27292830 For compile-time guarantees that all events are handled, inherit from
···43454446(** {1 Handler Interface} *)
45474848+(** The handler interface for processing response events.
4949+5050+ Each method corresponds to a variant of {!Response.t}. Handlers can be
5151+ passed to {!Client.run} to process responses in an event-driven style. *)
4652class type handler = object
4753 method on_text : Response.Text.t -> unit
4854 (** [on_text t] is called when text content is received from the assistant. *)
49555056 method on_tool_use : Response.Tool_use.t -> unit
5157 (** [on_tool_use t] is called when the assistant requests a tool invocation.
5252- The caller is responsible for responding with
5353- {!Client.respond_to_tool}. *)
5858+ The caller is responsible for responding with {!Client.respond_to_tool}.
5959+ *)
54605561 method on_tool_result : Content_block.Tool_result.t -> unit
5656- (** [on_tool_result t] is called when a tool result is observed in the
5757- message stream. This is typically an echo of what was sent to Claude. *)
6262+ (** [on_tool_result t] is called when a tool result is observed in the message
6363+ stream. This is typically an echo of what was sent to Claude. *)
58645965 method on_thinking : Response.Thinking.t -> unit
6066 (** [on_thinking t] is called when internal reasoning content is received. *)
···7177 (** [on_complete t] is called when the conversation completes. This provides
7278 final metrics like duration, cost, and token usage. *)
7379end
7474-(** The handler interface for processing response events.
7575-7676- Each method corresponds to a variant of {!Response.t}. Handlers can be
7777- passed to {!Client.run} to process responses in an event-driven style. *)
78807981(** {1 Concrete Implementations} *)
8082···8587 methods you need:
86888789 {[
8888- let handler = object
8989- inherit Claude.Handler.default
9090- method! on_text t = Printf.printf "Text: %s\n" (Response.Text.content t)
9191- end
9090+ let handler =
9191+ object
9292+ inherit Claude.Handler.default
9393+9494+ method! on_text t =
9595+ Printf.printf "Text: %s\n" (Response.Text.content t)
9696+ end
9297 ]}
93989499 Methods you don't override will simply be ignored, making this ideal for
95100 prototyping and for cases where you only care about specific events. *)
96101102102+(** Abstract handler requiring all methods to be implemented.
103103+104104+ Use this when you want compile-time guarantees that all events are handled:
105105+106106+ {[
107107+ let handler = object
108108+ inherit Claude.Handler.abstract
109109+ method on_text t = (* required *)
110110+ method on_tool_use t = (* required *)
111111+ method on_tool_result t = (* required *)
112112+ method on_thinking t = (* required *)
113113+ method on_init t = (* required *)
114114+ method on_error t = (* required *)
115115+ method on_complete t = (* required *)
116116+ end
117117+ ]}
118118+119119+ The compiler will enforce that you implement all methods, ensuring no events
120120+ are silently ignored. *)
97121class virtual abstract : object
98122 method virtual on_text : Response.Text.t -> unit
99123 (** [on_text t] must be implemented by subclasses. *)
···116140 method virtual on_complete : Response.Complete.t -> unit
117141 (** [on_complete t] must be implemented by subclasses. *)
118142end
119119-(** Abstract handler requiring all methods to be implemented.
120120-121121- Use this when you want compile-time guarantees that all events are handled:
122122-123123- {[
124124- let handler = object
125125- inherit Claude.Handler.abstract
126126- method on_text t = (* required *)
127127- method on_tool_use t = (* required *)
128128- method on_tool_result t = (* required *)
129129- method on_thinking t = (* required *)
130130- method on_init t = (* required *)
131131- method on_error t = (* required *)
132132- method on_complete t = (* required *)
133133- end
134134- ]}
135135-136136- The compiler will enforce that you implement all methods, ensuring no events
137137- are silently ignored. *)
138143139144(** {1 Dispatch Functions} *)
140145···144149145150 Example:
146151 {[
147147- let handler = object
148148- inherit Claude.Handler.default
149149- method! on_text t = print_endline (Response.Text.content t)
150150- end in
152152+ let handler =
153153+ object
154154+ inherit Claude.Handler.default
155155+ method! on_text t = print_endline (Response.Text.content t)
156156+ end
157157+ in
151158 dispatch handler (Response.Text text_event)
152159 ]} *)
153160
+11-19
lib/hooks.ml
···30303131 let deny ?reason () = { decision = Some Deny; reason; updated_input = None }
3232 let ask ?reason () = { decision = Some Ask; reason; updated_input = None }
3333-3434- let continue () =
3535- { decision = None; reason = None; updated_input = None }
3333+ let continue () = { decision = None; reason = None; updated_input = None }
36343735 type callback = input -> output
3836···5250 let updated_input =
5351 Option.map Tool_input.to_json output.updated_input
5452 in
5555- Proto.Hooks.PreToolUse.Output.allow ?reason:output.reason
5656- ?updated_input ()
5353+ Proto.Hooks.PreToolUse.Output.allow ?reason:output.reason ?updated_input
5454+ ()
5755 | Some Deny -> Proto.Hooks.PreToolUse.Output.deny ?reason:output.reason ()
5856 | Some Ask -> Proto.Hooks.PreToolUse.Output.ask ?reason:output.reason ()
5957end
···127125 let input_of_proto proto =
128126 {
129127 session_id = Proto.Hooks.UserPromptSubmit.Input.session_id proto;
130130- transcript_path =
131131- Proto.Hooks.UserPromptSubmit.Input.transcript_path proto;
128128+ transcript_path = Proto.Hooks.UserPromptSubmit.Input.transcript_path proto;
132129 prompt = Proto.Hooks.UserPromptSubmit.Input.prompt proto;
133130 }
134131···164161 }
165162166163 let output_to_proto output =
167167- if output.block then
168168- Proto.Hooks.Stop.Output.block ?reason:output.reason ()
164164+ if output.block then Proto.Hooks.Stop.Output.block ?reason:output.reason ()
169165 else Proto.Hooks.Stop.Output.continue ()
170166end
171167···191187192188module PreCompact = struct
193189 type input = { session_id : string; transcript_path : string }
194194-195190 type callback = input -> unit
196191197192 let input_of_proto proto =
···247242 | PostToolUseHook (pattern, callback) ->
248243 post_tool_use_hooks := (pattern, callback) :: !post_tool_use_hooks
249244 | UserPromptSubmitHook callback ->
250250- user_prompt_submit_hooks := (None, callback) :: !user_prompt_submit_hooks
245245+ user_prompt_submit_hooks :=
246246+ (None, callback) :: !user_prompt_submit_hooks
251247 | StopHook callback -> stop_hooks := (None, callback) :: !stop_hooks
252248 | SubagentStopHook callback ->
253249 subagent_stop_hooks := (None, callback) :: !subagent_stop_hooks
···289285 proto_output
290286 with
291287 | Ok json -> json
292292- | Error msg ->
293293- failwith ("PreToolUse output encoding: " ^ msg)
288288+ | Error msg -> failwith ("PreToolUse output encoding: " ^ msg)
294289 in
295290 (* Return wire format result *)
296291 Proto.Hooks.continue ~hook_specific_output ()
···328323 proto_output
329324 with
330325 | Ok json -> json
331331- | Error msg ->
332332- failwith ("PostToolUse output encoding: " ^ msg)
326326+ | Error msg -> failwith ("PostToolUse output encoding: " ^ msg)
333327 in
334328 if typed_output.block then
335329 Proto.Hooks.block ~hook_specific_output ()
···396390 match Jsont.Json.decode Proto.Hooks.Stop.Input.jsont json with
397391 | Ok input -> input
398392 | Error msg ->
399399- Log.err (fun m ->
400400- m "Stop: failed to decode input: %s" msg);
393393+ Log.err (fun m -> m "Stop: failed to decode input: %s" msg);
401394 raise (Invalid_argument ("Stop input: " ^ msg))
402395 in
403396 let typed_input = Stop.input_of_proto proto_input in
···447440 proto_output
448441 with
449442 | Ok json -> json
450450- | Error msg ->
451451- failwith ("SubagentStop output encoding: " ^ msg)
443443+ | Error msg -> failwith ("SubagentStop output encoding: " ^ msg)
452444 in
453445 if typed_output.block then
454446 Proto.Hooks.block ~hook_specific_output ()
+15-23
lib/hooks.mli
···6161 type decision =
6262 | Allow
6363 | Deny
6464- | Ask
6565- (** Permission decision for tool usage. *)
6464+ | Ask (** Permission decision for tool usage. *)
66656766 type output = {
6867 decision : decision option;
···112111 transcript_path : string;
113112 tool_name : string;
114113 tool_input : Tool_input.t;
115115- tool_response : Jsont.json; (* Response varies by tool *)
114114+ tool_response : Jsont.json; (* Response varies by tool *)
116115 }
117117- (** Input provided to PostToolUse hooks.
118118- Note: [tool_response] remains as {!type:Jsont.json} since response schemas
119119- vary by tool. *)
116116+ (** Input provided to PostToolUse hooks. Note: [tool_response] remains as
117117+ {!type:Jsont.json} since response schemas vary by tool. *)
120118121119 (** {2 Output} *)
122120···133131 (** [continue ?additional_context ()] creates a continue response.
134132 @param additional_context Optional context to add to the transcript *)
135133136136- val block :
137137- ?reason:string -> ?additional_context:string -> unit -> output
134134+ val block : ?reason:string -> ?additional_context:string -> unit -> output
138135 (** [block ?reason ?additional_context ()] creates a block response.
139136 @param reason Optional explanation for blocking
140137 @param additional_context Optional context to add to the transcript *)
···210207211208 (** {2 Output} *)
212209213213- type output = {
214214- block : bool;
215215- reason : string option;
216216- }
210210+ type output = { block : bool; reason : string option }
217211 (** Output from Stop hooks. *)
218212219213 (** {2 Response Builders} *)
···278272module PreCompact : sig
279273 (** {2 Input} *)
280274281281- type input = {
282282- session_id : string;
283283- transcript_path : string;
284284- }
275275+ type input = { session_id : string; transcript_path : string }
285276 (** Input provided to PreCompact hooks. *)
286277287278 (** {2 Callback Type} *)
288279289280 type callback = input -> unit
290290- (** Callback function type for PreCompact hooks.
291291- PreCompact hooks have no output - they are notification-only. *)
281281+ (** Callback function type for PreCompact hooks. PreCompact hooks have no
282282+ output - they are notification-only. *)
292283293284 (** {2 Conversion Functions} *)
294285···313304314305val on_pre_tool_use : ?pattern:string -> PreToolUse.callback -> t -> t
315306(** [on_pre_tool_use ?pattern callback config] adds a PreToolUse hook.
316316- @param pattern Optional regex pattern to match tool names (e.g., "Bash|Edit")
307307+ @param pattern
308308+ Optional regex pattern to match tool names (e.g., "Bash|Edit")
317309 @param callback Function to invoke on matching events *)
318310319311val on_post_tool_use : ?pattern:string -> PostToolUse.callback -> t -> t
···341333342334val get_callbacks :
343335 t ->
344344- (Proto.Hooks.event * (string option * (Jsont.json -> Proto.Hooks.result))
345345- list)
336336+ (Proto.Hooks.event
337337+ * (string option * (Jsont.json -> Proto.Hooks.result)) list)
346338 list
347339(** [get_callbacks config] returns hook configuration in format suitable for
348340 registration with the CLI.
···353345 - Invoke the user's typed callback
354346 - Convert output back to wire format using output_to_proto
355347356356- This is an internal function used by {!Client} - you should not need to
357357- call it directly. *)
348348+ This is an internal function used by {!Client} - you should not need to call
349349+ it directly. *)
+67-54
lib/mcp_server.ml
···2424(* JSONRPC helpers using Jsont.Json builders *)
25252626let jsonrpc_success ~id result =
2727- J.object' [
2828- J.mem (J.name "jsonrpc") (J.string "2.0");
2929- J.mem (J.name "id") id;
3030- J.mem (J.name "result") result
3131- ]
2727+ J.object'
2828+ [
2929+ J.mem (J.name "jsonrpc") (J.string "2.0");
3030+ J.mem (J.name "id") id;
3131+ J.mem (J.name "result") result;
3232+ ]
32333334let jsonrpc_error ~id ~code ~message =
3434- J.object' [
3535- J.mem (J.name "jsonrpc") (J.string "2.0");
3636- J.mem (J.name "id") id;
3737- J.mem (J.name "error") (J.object' [
3838- J.mem (J.name "code") (J.number (Float.of_int code));
3939- J.mem (J.name "message") (J.string message)
4040- ])
4141- ]
3535+ J.object'
3636+ [
3737+ J.mem (J.name "jsonrpc") (J.string "2.0");
3838+ J.mem (J.name "id") id;
3939+ J.mem (J.name "error")
4040+ (J.object'
4141+ [
4242+ J.mem (J.name "code") (J.number (Float.of_int code));
4343+ J.mem (J.name "message") (J.string message);
4444+ ]);
4545+ ]
42464347(* Extract string from JSON *)
4448let get_string key (obj : Jsont.json) =
···6266let get_id (msg : Jsont.json) : Jsont.json =
6367 match msg with
6468 | Jsont.Object (mems, _) -> (
6565- match J.find_mem "id" mems with
6666- | Some (_, id) -> id
6767- | None -> J.null ())
6969+ match J.find_mem "id" mems with Some (_, id) -> id | None -> J.null ())
6870 | _ -> J.null ()
69717072(* Handle initialize request *)
7173let handle_initialize t ~id =
7272- jsonrpc_success ~id (J.object' [
7373- J.mem (J.name "protocolVersion") (J.string "2024-11-05");
7474- J.mem (J.name "capabilities") (J.object' [
7575- J.mem (J.name "tools") (J.object' [])
7676- ]);
7777- J.mem (J.name "serverInfo") (J.object' [
7878- J.mem (J.name "name") (J.string t.name);
7979- J.mem (J.name "version") (J.string t.version)
8080- ])
8181- ])
7474+ jsonrpc_success ~id
7575+ (J.object'
7676+ [
7777+ J.mem (J.name "protocolVersion") (J.string "2024-11-05");
7878+ J.mem (J.name "capabilities")
7979+ (J.object' [ J.mem (J.name "tools") (J.object' []) ]);
8080+ J.mem (J.name "serverInfo")
8181+ (J.object'
8282+ [
8383+ J.mem (J.name "name") (J.string t.name);
8484+ J.mem (J.name "version") (J.string t.version);
8585+ ]);
8686+ ])
82878388(* Handle tools/list request *)
8489let handle_tools_list t ~id =
8585- let tools_json = List.map (fun tool ->
8686- J.object' [
8787- J.mem (J.name "name") (J.string (Tool.name tool));
8888- J.mem (J.name "description") (J.string (Tool.description tool));
8989- J.mem (J.name "inputSchema") (Tool.input_schema tool)
9090- ]
9191- ) t.tools in
9292- jsonrpc_success ~id (J.object' [J.mem (J.name "tools") (J.list tools_json)])
9090+ let tools_json =
9191+ List.map
9292+ (fun tool ->
9393+ J.object'
9494+ [
9595+ J.mem (J.name "name") (J.string (Tool.name tool));
9696+ J.mem (J.name "description") (J.string (Tool.description tool));
9797+ J.mem (J.name "inputSchema") (Tool.input_schema tool);
9898+ ])
9999+ t.tools
100100+ in
101101+ jsonrpc_success ~id (J.object' [ J.mem (J.name "tools") (J.list tools_json) ])
9310294103(* Handle tools/call request *)
95104let handle_tools_call t ~id ~params =
96105 match get_string "name" params with
9797- | None ->
9898- jsonrpc_error ~id ~code:(-32602) ~message:"Missing 'name' parameter"
9999- | Some tool_name ->
106106+ | None -> jsonrpc_error ~id ~code:(-32602) ~message:"Missing 'name' parameter"
107107+ | Some tool_name -> (
100108 match Hashtbl.find_opt t.tool_map tool_name with
101109 | None ->
102110 jsonrpc_error ~id ~code:(-32601)
103111 ~message:(Printf.sprintf "Tool '%s' not found" tool_name)
104104- | Some tool ->
105105- let arguments = match get_object "arguments" params with
112112+ | Some tool -> (
113113+ let arguments =
114114+ match get_object "arguments" params with
106115 | Some args -> args
107116 | None -> J.object' []
108117 in
109118 let input = Tool_input.of_json arguments in
110119 match Tool.call tool input with
111120 | Ok content ->
112112- jsonrpc_success ~id (J.object' [J.mem (J.name "content") content])
121121+ jsonrpc_success ~id
122122+ (J.object' [ J.mem (J.name "content") content ])
113123 | Error msg ->
114124 (* Return error as content with is_error flag *)
115115- jsonrpc_success ~id (J.object' [
116116- J.mem (J.name "content") (J.list [J.object' [
117117- J.mem (J.name "type") (J.string "text");
118118- J.mem (J.name "text") (J.string msg)
119119- ]]);
120120- J.mem (J.name "isError") (J.bool true)
121121- ])
125125+ jsonrpc_success ~id
126126+ (J.object'
127127+ [
128128+ J.mem (J.name "content")
129129+ (J.list
130130+ [
131131+ J.object'
132132+ [
133133+ J.mem (J.name "type") (J.string "text");
134134+ J.mem (J.name "text") (J.string msg);
135135+ ];
136136+ ]);
137137+ J.mem (J.name "isError") (J.bool true);
138138+ ])))
122139123140let handle_request t ~method_ ~params ~id =
124141 match method_ with
···130147 ~message:(Printf.sprintf "Method '%s' not found" method_)
131148132149let handle_json_message t (msg : Jsont.json) =
133133- let method_ = match get_string "method" msg with
134134- | Some m -> m
135135- | None -> ""
136136- in
137137- let params = match get_object "params" msg with
138138- | Some p -> p
139139- | None -> J.object' []
150150+ let method_ = match get_string "method" msg with Some m -> m | None -> "" in
151151+ let params =
152152+ match get_object "params" msg with Some p -> p | None -> J.object' []
140153 in
141154 let id = get_id msg in
142155 handle_request t ~method_ ~params ~id
+22-30
lib/mcp_server.mli
···1212 {2 Basic Usage}
13131414 {[
1515- let greet = Tool.create
1616- ~name:"greet"
1717- ~description:"Greet a user"
1818- ~input_schema:(Tool.schema_object ["name", Tool.schema_string] ~required:["name"])
1919- ~handler:(fun args ->
2020- match Tool_input.get_string args "name" with
2121- | Some name -> Ok (Tool.text_result (Printf.sprintf "Hello, %s!" name))
2222- | None -> Error "Missing name")
1515+ let greet =
1616+ Tool.create ~name:"greet" ~description:"Greet a user"
1717+ ~input_schema:
1818+ (Tool.schema_object
1919+ [ ("name", Tool.schema_string) ]
2020+ ~required:[ "name" ])
2121+ ~handler:(fun args ->
2222+ match Tool_input.get_string args "name" with
2323+ | Some name ->
2424+ Ok (Tool.text_result (Printf.sprintf "Hello, %s!" name))
2525+ | None -> Error "Missing name")
23262424- let server = Mcp_server.create
2525- ~name:"my-tools"
2626- ~tools:[greet]
2727- ()
2727+ let server = Mcp_server.create ~name:"my-tools" ~tools:[ greet ] ()
28282929- let options = Options.default
2929+ let options =
3030+ Options.default
3031 |> Options.with_mcp_server ~name:"tools" server
3131- |> Options.with_allowed_tools ["mcp__tools__greet"]
3232+ |> Options.with_allowed_tools [ "mcp__tools__greet" ]
3233 ]}
33343435 {2 Tool Naming}
35363636- When you register an MCP server with name "foo" containing a tool "bar",
3737- the full tool name becomes [mcp__foo__bar]. This is how Claude CLI
3838- routes MCP tool calls.
3737+ When you register an MCP server with name "foo" containing a tool "bar", the
3838+ full tool name becomes [mcp__foo__bar]. This is how Claude CLI routes MCP
3939+ tool calls.
39404041 {2 Protocol}
4142···4748type t
4849(** Abstract type for MCP servers. *)
49505050-val create :
5151- name:string ->
5252- ?version:string ->
5353- tools:Tool.t list ->
5454- unit ->
5555- t
5151+val create : name:string -> ?version:string -> tools:Tool.t list -> unit -> t
5652(** [create ~name ?version ~tools ()] creates an in-process MCP server.
57535854 @param name Server identifier. Used in tool naming: [mcp__<name>__<tool>].
···7167(** {1 MCP Protocol Handling} *)
72687369val handle_request :
7474- t ->
7575- method_:string ->
7676- params:Jsont.json ->
7777- id:Jsont.json ->
7878- Jsont.json
7070+ t -> method_:string -> params:Jsont.json -> id:Jsont.json -> Jsont.json
7971(** [handle_request t ~method_ ~params ~id] handles an MCP JSONRPC request.
80728173 Returns a JSONRPC response object with the given [id].
···9082val handle_json_message : t -> Jsont.json -> Jsont.json
9183(** [handle_json_message t msg] handles a complete JSONRPC message.
92849393- Extracts method, params, and id from the message and delegates
9494- to {!handle_request}. *)
8585+ Extracts method, params, and id from the message and delegates to
8686+ {!handle_request}. *)
+20-13
lib/message.ml
···1111 type t = Proto.Message.User.t
12121313 let of_string s = Proto.Message.User.create_string s
1414- let of_blocks blocks = Proto.Message.User.create_blocks (List.map Content_block.to_proto blocks)
1414+1515+ let of_blocks blocks =
1616+ Proto.Message.User.create_blocks (List.map Content_block.to_proto blocks)
15171618 let with_tool_result ~tool_use_id ~content ?is_error () =
1717- Proto.Message.User.create_with_tool_result ~tool_use_id ~content ?is_error ()
1919+ Proto.Message.User.create_with_tool_result ~tool_use_id ~content ?is_error
2020+ ()
18211922 let as_text t =
2023 match Proto.Message.User.content t with
···40434144module Assistant = struct
4245 type error = Proto.Message.Assistant.error
4646+ type t = Proto.Message.Assistant.t
43474444- type t = Proto.Message.Assistant.t
4848+ let content t =
4949+ List.map Content_block.of_proto (Proto.Message.Assistant.content t)
45504646- let content t = List.map Content_block.of_proto (Proto.Message.Assistant.content t)
4751 let model t = Proto.Message.Assistant.model t
4852 let error t = Proto.Message.Assistant.error t
4953···6569 (content t)
66706771 let has_tool_use t =
6868- List.exists (function Content_block.Tool_use _ -> true | _ -> false) (content t)
7272+ List.exists
7373+ (function Content_block.Tool_use _ -> true | _ -> false)
7474+ (content t)
69757076 let combined_text t = String.concat "\n" (text_blocks t)
7171-7277 let of_proto proto = proto
7378 let to_proto t = t
7479···9095 let model = Proto.Message.System.model
9196 let cwd = Proto.Message.System.cwd
9297 let error_message = Proto.Message.System.error_msg
9393-9498 let of_proto proto = proto
9599 let to_proto t = t
96100···110114 let input_tokens = Proto.Message.Result.Usage.input_tokens
111115 let output_tokens = Proto.Message.Result.Usage.output_tokens
112116 let total_tokens = Proto.Message.Result.Usage.total_tokens
113113- let cache_creation_input_tokens = Proto.Message.Result.Usage.cache_creation_input_tokens
114114- let cache_read_input_tokens = Proto.Message.Result.Usage.cache_read_input_tokens
117117+118118+ let cache_creation_input_tokens =
119119+ Proto.Message.Result.Usage.cache_creation_input_tokens
120120+121121+ let cache_read_input_tokens =
122122+ Proto.Message.Result.Usage.cache_read_input_tokens
115123116124 let of_proto proto = proto
117125 end
···124132 let num_turns = Proto.Message.Result.num_turns
125133 let session_id = Proto.Message.Result.session_id
126134 let total_cost_usd = Proto.Message.Result.total_cost_usd
127127-128135 let usage t = Option.map Usage.of_proto (Proto.Message.Result.usage t)
129136 let result_text = Proto.Message.Result.result
130137 let structured_output = Proto.Message.Result.structured_output
131131-132138 let of_proto proto = proto
133139 let to_proto t = t
134140···176182 if text = "" then None else Some text
177183 | _ -> None
178184179179-let extract_tool_uses = function Assistant a -> Assistant.tool_uses a | _ -> []
185185+let extract_tool_uses = function
186186+ | Assistant a -> Assistant.tool_uses a
187187+ | _ -> []
180188181189let get_session_id = function
182190 | System s -> System.session_id s
···193201(* Convenience constructors *)
194202let user_string s = User (User.of_string s)
195203let user_blocks blocks = User (User.of_blocks blocks)
196196-197204let pp fmt t = Jsont.pp_value Proto.Message.jsont () fmt (to_proto t)
198205let log_received t = Log.info (fun m -> m "← %a" pp t)
199206let log_sending t = Log.info (fun m -> m "→ %a" pp t)
+7-4
lib/options.ml
···124124let with_settings path t = { t with settings = Some path }
125125let with_add_dirs dirs t = { t with add_dirs = dirs }
126126let with_extra_args args t = { t with extra_args = args }
127127-let with_debug_stderr sink t = { t with debug_stderr = Some (sink :> Eio.Flow.sink_ty Eio.Flow.sink) }
127127+128128+let with_debug_stderr sink t =
129129+ { t with debug_stderr = Some (sink :> Eio.Flow.sink_ty Eio.Flow.sink) }
130130+128131let with_hooks hooks t = { t with hooks = Some hooks }
129132let with_max_budget_usd budget t = { t with max_budget_usd = Some budget }
130133let with_fallback_model model t = { t with fallback_model = Some model }
131131-132134let with_no_settings t = { t with setting_sources = Some [] }
133133-134135let with_max_buffer_size size t = { t with max_buffer_size = Some size }
135136let with_user user t = { t with user = Some user }
136137let with_output_format format t = { t with output_format = Some format }
···157158 let base = Proto.Options.empty in
158159 let base = Proto.Options.with_allowed_tools t.allowed_tools base in
159160 let base = Proto.Options.with_disallowed_tools t.disallowed_tools base in
160160- let base = Proto.Options.with_max_thinking_tokens t.max_thinking_tokens base in
161161+ let base =
162162+ Proto.Options.with_max_thinking_tokens t.max_thinking_tokens base
163163+ in
161164 let base =
162165 match t.system_prompt with
163166 | None -> base
+2-1
lib/options.mli
···279279module Advanced : sig
280280 val to_wire : t -> Proto.Options.t
281281 (** [to_wire t] converts to wire format (excludes Eio types and callbacks).
282282- This is used internally by the client to send options to the Claude CLI. *)
282282+ This is used internally by the client to send options to the Claude CLI.
283283+ *)
283284end
+9-7
lib/permissions.ml
···5454 }
55555656 let to_proto (t : t) : Proto.Permissions.Rule.t =
5757- Proto.Permissions.Rule.create ~tool_name:t.tool_name ?rule_content:t.rule_content
5858- ()
5757+ Proto.Permissions.Rule.create ~tool_name:t.tool_name
5858+ ?rule_content:t.rule_content ()
5959end
60606161(** Permission decisions *)
···66666767 let allow ?updated_input () = Allow { updated_input }
6868 let deny ~message ~interrupt = Deny { message; interrupt }
6969-7069 let is_allow = function Allow _ -> true | Deny _ -> false
7170 let is_deny = function Allow _ -> false | Deny _ -> true
7271···7877 | Allow _ -> None
7978 | Deny { message; _ } -> Some message
80798181- let deny_interrupt = function Allow _ -> false | Deny { interrupt; _ } -> interrupt
8080+ let deny_interrupt = function
8181+ | Allow _ -> false
8282+ | Deny { interrupt; _ } -> interrupt
82838384 let to_proto_result ~original_input (t : t) : Proto.Permissions.Result.t =
8485 match t with
···8687 let updated_input_json =
8788 match updated_input with
8889 | Some input -> Some (Tool_input.to_json input)
8989- | None -> Some (Tool_input.to_json original_input) (* Return original when not modified *)
9090+ | None -> Some (Tool_input.to_json original_input)
9191+ (* Return original when not modified *)
9092 in
9193 Proto.Permissions.Result.allow ?updated_input:updated_input_json ()
9294 | Deny { message; interrupt } ->
9395 Proto.Permissions.Result.deny ~message ~interrupt ()
9496end
95979696-(** Permission context *)
9798type context = {
9899 tool_name : string;
99100 input : Tool_input.t;
100101 suggested_rules : Rule.t list;
101102}
103103+(** Permission context *)
102104103105let extract_rules_from_proto_updates updates =
104106 List.concat_map
···108110 | None -> [])
109111 updates
110112111111-(** Permission callback type *)
112113type callback = context -> Decision.t
114114+(** Permission callback type *)
113115114116(** Default callbacks *)
115117let default_allow _ctx = Decision.allow ()
+12-7
lib/permissions.mli
···1717module Mode : sig
1818 (** Permission modes control the overall behavior of the permission system. *)
19192020+ (** The type of permission modes. *)
2021 type t =
2122 | Default (** Standard permission mode with normal checks *)
2223 | Accept_edits (** Automatically accept file edits *)
2324 | Plan (** Planning mode with restricted execution *)
2425 | Bypass_permissions (** Bypass all permission checks *)
2525- (** The type of permission modes. *)
26262727 val to_string : t -> string
2828 (** [to_string t] converts a mode to its string representation. *)
···9595 (** [deny_message t] returns the denial message if the decision is deny. *)
96969797 val deny_interrupt : t -> bool
9898- (** [deny_interrupt t] returns whether to interrupt if the decision is deny. *)
9898+ (** [deny_interrupt t] returns whether to interrupt if the decision is deny.
9999+ *)
99100100100- val to_proto_result : original_input:Tool_input.t -> t -> Proto.Permissions.Result.t
101101- (** [to_proto_result ~original_input t] converts to the protocol result representation.
102102- When the decision allows without modification, the original_input is returned. *)
101101+ val to_proto_result :
102102+ original_input:Tool_input.t -> t -> Proto.Permissions.Result.t
103103+ (** [to_proto_result ~original_input t] converts to the protocol result
104104+ representation. When the decision allows without modification, the
105105+ original_input is returned. *)
103106end
104107105108(** {1 Permission Context} *)
···111114}
112115(** The context provided to permission callbacks. *)
113116114114-val extract_rules_from_proto_updates : Proto.Permissions.Update.t list -> Rule.t list
117117+val extract_rules_from_proto_updates :
118118+ Proto.Permissions.Update.t list -> Rule.t list
115119(** [extract_rules_from_proto_updates updates] extracts rules from protocol
116120 permission updates. Used internally to convert protocol suggestions into
117121 context rules. *)
···136140(** {1 Logging} *)
137141138142val log_permission_check : tool_name:string -> decision:Decision.t -> unit
139139-(** [log_permission_check ~tool_name ~decision] logs a permission check result. *)
143143+(** [log_permission_check ~tool_name ~decision] logs a permission check result.
144144+*)
+6-9
lib/response.ml
···3333 let session_id = Message.System.session_id
3434 let model = Message.System.model
3535 let cwd = Message.System.cwd
3636-3737- let of_system sys =
3838- if Message.System.is_init sys then Some sys else None
3636+ let of_system sys = if Message.System.is_init sys then Some sys else None
3937end
40384139module Error = struct
···5654 | `Unknown -> "Unknown error")
57555856 let is_system_error = function System_error _ -> true | _ -> false
5959-6057 let is_assistant_error = function Assistant_error _ -> true | _ -> false
61586259 let of_system sys =
···10299 (* Convert content blocks to response events *)
103100 Message.Assistant.content msg
104101 |> List.map (function
105105- | Content_block.Text text -> Text (Text.of_block text)
106106- | Content_block.Tool_use tool -> Tool_use (Tool_use.of_block tool)
107107- | Content_block.Tool_result result -> Tool_result result
108108- | Content_block.Thinking thinking ->
109109- Thinking (Thinking.of_block thinking)))
102102+ | Content_block.Text text -> Text (Text.of_block text)
103103+ | Content_block.Tool_use tool -> Tool_use (Tool_use.of_block tool)
104104+ | Content_block.Tool_result result -> Tool_result result
105105+ | Content_block.Thinking thinking ->
106106+ Thinking (Thinking.of_block thinking)))
110107 | Message.System sys -> (
111108 (* System messages can be Init or Error *)
112109 match Init.of_system sys with
+6-5
lib/response.mli
···134134135135(** {1 Response Event Union Type} *)
136136137137+(** The type of response events that can be received from Claude. *)
137138type t =
138139 | Text of Text.t (** Text content from assistant *)
139140 | Tool_use of Tool_use.t (** Tool invocation request *)
140140- | Tool_result of Content_block.Tool_result.t (** Tool result (pass-through) *)
141141+ | Tool_result of Content_block.Tool_result.t
142142+ (** Tool result (pass-through) *)
141143 | Thinking of Thinking.t (** Internal reasoning *)
142144 | Init of Init.t (** Session initialization *)
143145 | Error of Error.t (** Error event *)
144146 | Complete of Complete.t (** Session completion *)
145145- (** The type of response events that can be received from Claude. *)
146147147148(** {1 Conversion} *)
148149149150val of_message : Message.t -> t list
150150-(** [of_message msg] converts a message to response events. An assistant
151151- message may produce multiple events (one per content block). User messages
152152- produce empty lists since they are not responses. *)
151151+(** [of_message msg] converts a message to response events. An assistant message
152152+ may produce multiple events (one per content block). User messages produce
153153+ empty lists since they are not responses. *)
···183183module Response : sig
184184 (** SDK control response types. *)
185185186186- (** Re-export Error_code from Proto for convenience. *)
187186 module Error_code = Proto.Control.Response.Error_code
187187+ (** Re-export Error_code from Proto for convenience. *)
188188189189- (** Structured error detail similar to JSON-RPC.
190190-191191- This allows programmatic error handling with numeric error codes and
192192- optional structured data for additional context. *)
193189 type error_detail = {
194190 code : int; (** Error code for programmatic handling *)
195191 message : string; (** Human-readable error message *)
196192 data : Jsont.json option; (** Optional additional error data *)
197193 }
194194+ (** Structured error detail similar to JSON-RPC.
195195+196196+ This allows programmatic error handling with numeric error codes and
197197+ optional structured data for additional context. *)
198198199199 val error_detail :
200200- code:[< Error_code.t] -> message:string -> ?data:Jsont.json -> unit -> error_detail
200200+ code:[< Error_code.t ] ->
201201+ message:string ->
202202+ ?data:Jsont.json ->
203203+ unit ->
204204+ error_detail
201205 (** [error_detail ~code ~message ?data ()] creates a structured error detail
202206 using typed error codes.
203207204208 Example:
205209 {[
206206- error_detail
207207- ~code:`Method_not_found
208208- ~message:"Hook callback not found"
210210+ error_detail ~code:`Method_not_found ~message:"Hook callback not found"
209211 ()
210212 ]} *)
211213···238240239241 val error :
240242 request_id:string -> error:error_detail -> ?unknown:Unknown.t -> unit -> t
241241- (** [error ~request_id ~error ?unknown] creates an error response with structured error detail. *)
243243+ (** [error ~request_id ~error ?unknown] creates an error response with
244244+ structured error detail. *)
242245243246 val jsont : t Jsont.t
244247 (** [jsont] is the jsont codec for responses. Use [Jsont.pp_value jsont ()]
-3
lib/server_info.ml
···1616let capabilities t = t.capabilities
1717let commands t = t.commands
1818let output_styles t = t.output_styles
1919-2019let has_capability t cap = List.mem cap t.capabilities
2121-2220let supports_hooks t = has_capability t "hooks"
2323-2421let supports_structured_output t = has_capability t "structured-output"
25222623let of_proto (proto : Proto.Control.Server_info.t) : t =
+2-2
lib/server_info.mli
···3636(** [supports_hooks t] checks if the hooks capability is available. *)
37373838val supports_structured_output : t -> bool
3939-(** [supports_structured_output t] checks if the structured output capability
4040- is available. *)
3939+(** [supports_structured_output t] checks if the structured output capability is
4040+ available. *)
41414242(** {1 Internal} *)
4343
+4-2
lib/structured_output.mli
···23232424 {2 Creating Output Formats}
25252626- Use {!of_json_schema} to specify a JSON Schema as a {!type:Jsont.json} value:
2626+ Use {!of_json_schema} to specify a JSON Schema as a {!type:Jsont.json}
2727+ value:
2728 {[
2829 let meta = Jsont.Meta.none in
2930 let schema = Jsont.Object ([
···122123val of_json_schema : Jsont.json -> t
123124(** [of_json_schema schema] creates an output format from a JSON Schema.
124125125125- The schema should be a valid JSON Schema Draft 7 as a {!type:Jsont.json} value.
126126+ The schema should be a valid JSON Schema Draft 7 as a {!type:Jsont.json}
127127+ value.
126128127129 Example:
128130 {[
···5566(** Custom tool definitions for MCP servers.
7788- Tools are functions that Claude can invoke. They run in-process within
99- your OCaml application via the MCP (Model Context Protocol).
88+ Tools are functions that Claude can invoke. They run in-process within your
99+ OCaml application via the MCP (Model Context Protocol).
10101111 {2 Basic Usage}
12121313 {[
1414- let greet = Tool.create
1515- ~name:"greet"
1616- ~description:"Greet a user by name"
1717- ~input_schema:(`O [
1818- "type", `String "object";
1919- "properties", `O [
2020- "name", `O ["type", `String "string"]
2121- ];
2222- "required", `A [`String "name"]
2323- ])
2424- ~handler:(fun args ->
2525- match Tool_input.get_string args "name" with
2626- | Some name -> Ok (`A [`O ["type", `String "text";
2727- "text", `String (Printf.sprintf "Hello, %s!" name)]])
2828- | None -> Error "Missing 'name' parameter")
1414+ let greet =
1515+ Tool.create ~name:"greet" ~description:"Greet a user by name"
1616+ ~input_schema:
1717+ (`O
1818+ [
1919+ ("type", `String "object");
2020+ ( "properties",
2121+ `O [ ("name", `O [ ("type", `String "string") ]) ] );
2222+ ("required", `A [ `String "name" ]);
2323+ ])
2424+ ~handler:(fun args ->
2525+ match Tool_input.get_string args "name" with
2626+ | Some name ->
2727+ Ok
2828+ (`A
2929+ [
3030+ `O
3131+ [
3232+ ("type", `String "text");
3333+ ("text", `String (Printf.sprintf "Hello, %s!" name));
3434+ ];
3535+ ])
3636+ | None -> Error "Missing 'name' parameter")
2937 ]}
30383139 {2 Tool Response Format}
···36443745 Content blocks are typically:
3846 {[
3939- `A [`O ["type", `String "text"; "text", `String "result"]]
4747+ `A [ `O [ ("type", `String "text"); ("text", `String "result") ] ]
4048 ]} *)
41494250type t
···5058 t
5159(** [create ~name ~description ~input_schema ~handler] creates a custom tool.
52605353- @param name Unique tool identifier. Claude uses this in function calls.
5454- When registered with an MCP server named "foo", the full tool name
5555- becomes [mcp__foo__<name>].
5656- @param description Human-readable description. Helps Claude understand
5757- when to use the tool.
5858- @param input_schema JSON Schema defining input parameters. Should be
5959- a valid JSON Schema object with "type", "properties", etc.
6060- @param handler Function that executes the tool. Receives tool input,
6161- returns content array or error message. *)
6161+ @param name
6262+ Unique tool identifier. Claude uses this in function calls. When
6363+ registered with an MCP server named "foo", the full tool name becomes
6464+ [mcp__foo__<name>].
6565+ @param description
6666+ Human-readable description. Helps Claude understand when to use the tool.
6767+ @param input_schema
6868+ JSON Schema defining input parameters. Should be a valid JSON Schema
6969+ object with "type", "properties", etc.
7070+ @param handler
7171+ Function that executes the tool. Receives tool input, returns content
7272+ array or error message. *)
62736374val name : t -> string
6475(** [name t] returns the tool's name. *)
···87988899 Build JSON Schema objects more easily. *)
891009090-val schema_object : (string * Jsont.json) list -> required:string list -> Jsont.json
101101+val schema_object :
102102+ (string * Jsont.json) list -> required:string list -> Jsont.json
91103(** [schema_object props ~required] creates an object schema.
92104 {[
93105 schema_object
9494- ["name", schema_string; "age", schema_int]
9595- ~required:["name"]
106106+ [ ("name", schema_string); ("age", schema_int) ]
107107+ ~required:[ "name" ]
96108 ]} *)
9710998110val schema_string : Jsont.json
+5-4
lib/tool_input.ml
···1515(** {1 Helper Functions} *)
16161717(* Extract members from JSON object, or return empty list if not an object *)
1818-let get_members = function
1919- | Jsont.Object (members, _) -> members
2020- | _ -> []
1818+let get_members = function Jsont.Object (members, _) -> members | _ -> []
21192220(* Find a member by key in the object *)
2321let find_member key members =
···8785 List.map (fun ((name, _), _) -> name) members
88868987let is_empty t =
9090- match t with Jsont.Object ([], _) -> true | Jsont.Object _ -> false | _ -> true
8888+ match t with
8989+ | Jsont.Object ([], _) -> true
9090+ | Jsont.Object _ -> false
9191+ | _ -> true
91929293(** {1 Construction} *)
9394
+6-3
lib/tool_input.mli
···1919 string. *)
20202121val get_int : t -> string -> int option
2222-(** [get_int t key] returns the integer value for [key], if present and an int. *)
2222+(** [get_int t key] returns the integer value for [key], if present and an int.
2323+*)
23242425val get_bool : t -> string -> bool option
2525-(** [get_bool t key] returns the boolean value for [key], if present and a bool. *)
2626+(** [get_bool t key] returns the boolean value for [key], if present and a bool.
2727+*)
26282729val get_float : t -> string -> float option
2828-(** [get_float t key] returns the float value for [key], if present and a float. *)
3030+(** [get_float t key] returns the float value for [key], if present and a float.
3131+*)
29323033val get_string_list : t -> string -> string list option
3134(** [get_string_list t key] returns the string list for [key], if present and a
+4-5
lib/transport.ml
···140140 let preserved =
141141 List.filter_map
142142 (fun var ->
143143- Option.map (fun value -> Printf.sprintf "%s=%s" var value)
143143+ Option.map
144144+ (fun value -> Printf.sprintf "%s=%s" var value)
144145 (Sys.getenv_opt var))
145146 preserve_vars
146147 in
···196197 let max_size =
197198 match Options.max_buffer_size options with
198199 | Some size -> size
199199- | None -> 1_000_000 (* Default 1MB *)
200200+ | None -> 100_000_000 (* Default 100MB *)
200201 in
201202 let stdout =
202203 Eio.Buf_read.of_flow ~max_size (stdout_r :> Eio.Flow.source_ty r)
···239240 Log.info (fun m -> m "Sending interrupt signal");
240241 (* Create interrupt request using Proto types *)
241242 let request = Proto.Control.Request.interrupt () in
242242- let envelope =
243243- Proto.Control.create_request ~request_id:"" ~request ()
244244- in
243243+ let envelope = Proto.Control.create_request ~request_id:"" ~request () in
245244 let outgoing = Proto.Outgoing.Control_request envelope in
246245 let interrupt_msg = Proto.Outgoing.to_json outgoing in
247246 send t interrupt_msg
+6-1
proto/content_block.ml
···1919end
20202121module Tool_use = struct
2222- type t = { id : string; name : string; input : Jsont.json; unknown : Unknown.t }
2222+ type t = {
2323+ id : string;
2424+ name : string;
2525+ input : Jsont.json;
2626+ unknown : Unknown.t;
2727+ }
23282429 let create ~id ~name ~input = { id; name; input; unknown = Unknown.empty }
2530 let make id name input unknown = { id; name; input; unknown }
+2-1
proto/content_block.mli
···8282 (** [create ~tool_use_id ?content ?is_error ()] creates a new tool result
8383 block.
8484 @param tool_use_id The ID of the corresponding tool use block
8585- @param content Optional result content (can be string or array of content blocks)
8585+ @param content
8686+ Optional result content (can be string or array of content blocks)
8687 @param is_error Whether the tool execution resulted in an error *)
87888889 val tool_use_id : t -> string
···1717 - Helper functions for common responses
18181919 This is the wire format module - it does not include the callback system or
2020- Eio dependencies. For the full hooks system with callbacks, see the
2121- [Hooks] module in the [lib] directory. *)
2020+ Eio dependencies. For the full hooks system with callbacks, see the [Hooks]
2121+ module in the [lib] directory. *)
22222323(** {1 Hook Events} *)
24242525+(** Hook event types *)
2526type event =
2627 | Pre_tool_use (** Fires before a tool is executed *)
2728 | Post_tool_use (** Fires after a tool completes *)
···2930 | Stop (** Fires when conversation stops *)
3031 | Subagent_stop (** Fires when a subagent stops *)
3132 | Pre_compact (** Fires before message compaction *)
3232-(** Hook event types *)
33333434val event_to_string : event -> string
3535-(** [event_to_string event] converts an event to its wire format string.
3636- Wire format: "PreToolUse", "PostToolUse", "UserPromptSubmit", "Stop",
3535+(** [event_to_string event] converts an event to its wire format string. Wire
3636+ format: "PreToolUse", "PostToolUse", "UserPromptSubmit", "Stop",
3737 "SubagentStop", "PreCompact" *)
38383939val event_of_string : string -> event
···67676868(** {1 Decisions} *)
69697070+(** Hook decision control *)
7071type decision =
7172 | Continue (** Allow the action to proceed *)
7273 | Block (** Block the action *)
7373-(** Hook decision control *)
74747575val decision_jsont : decision Jsont.t
7676-(** [decision_jsont] is the Jsont codec for hook decisions.
7777- Wire format: "continue", "block" *)
7676+(** [decision_jsont] is the Jsont codec for hook decisions. Wire format:
7777+ "continue", "block" *)
78787979(** {1 Typed Hook Modules} *)
8080···108108 (** {2 Output} *)
109109110110 type permission_decision = [ `Allow | `Deny | `Ask ]
111111- (** Permission decision for tool usage.
112112- Wire format: "allow", "deny", "ask" *)
111111+ (** Permission decision for tool usage. Wire format: "allow", "deny", "ask" *)
113112114113 val permission_decision_jsont : permission_decision Jsont.t
115115- (** [permission_decision_jsont] is the Jsont codec for permission decisions. *)
114114+ (** [permission_decision_jsont] is the Jsont codec for permission decisions.
115115+ *)
116116117117 module Output : sig
118118 type t
···121121 val jsont : t Jsont.t
122122 (** [jsont] is the Jsont codec for PreToolUse output. *)
123123124124- val allow :
125125- ?reason:string -> ?updated_input:Jsont.json -> unit -> t
124124+ val allow : ?reason:string -> ?updated_input:Jsont.json -> unit -> t
126125 (** [allow ?reason ?updated_input ()] creates an allow response.
127126 @param reason Optional explanation for allowing
128127 @param updated_input Optional modified tool input *)
···32323333 val outgoing_jsont : t Jsont.t
3434 (** [outgoing_jsont] is the codec for encoding outgoing user messages to CLI.
3535- This produces the envelope format with "message" wrapper containing
3636- "role" and "content" fields. *)
3535+ This produces the envelope format with "message" wrapper containing "role"
3636+ and "content" fields. *)
37373838 val create_string : string -> t
3939 (** [create_string s] creates a user message with simple text content. *)
···4444 val create_with_tool_result :
4545 tool_use_id:string -> content:Jsont.json -> ?is_error:bool -> unit -> t
4646 (** [create_with_tool_result ~tool_use_id ~content ?is_error ()] creates a
4747- user message containing a tool result. Content can be a string or array. *)
4747+ user message containing a tool result. Content can be a string or array.
4848+ *)
48494950 val content : t -> content
5051 (** [content t] returns the content of the user message. *)
+7-3
proto/options.ml
···1111let setting_source_jsont : setting_source Jsont.t =
1212 Jsont.enum [ ("user", User); ("project", Project); ("local", Local) ]
13131414-(** Configuration type *)
1514type t = {
1615 allowed_tools : string list;
1716 disallowed_tools : string list;
···3433 output_format : Structured_output.t option;
3534 unknown : Unknown.t;
3635}
3636+(** Configuration type *)
37373838let empty =
3939 {
···61616262(** Accessor functions *)
6363let allowed_tools t = t.allowed_tools
6464+6465let disallowed_tools t = t.disallowed_tools
6566let max_thinking_tokens t = t.max_thinking_tokens
6667let system_prompt t = t.system_prompt
···83848485(** Builder functions *)
8586let with_allowed_tools allowed_tools t = { t with allowed_tools }
8787+8688let with_disallowed_tools disallowed_tools t = { t with disallowed_tools }
87898890let with_max_thinking_tokens max_thinking_tokens t =
···133135 let make allowed_tools disallowed_tools max_thinking_tokens system_prompt
134136 append_system_prompt permission_mode model continue_conversation resume
135137 max_turns permission_prompt_tool_name settings add_dirs max_budget_usd
136136- fallback_model setting_sources max_buffer_size user output_format unknown =
138138+ fallback_model setting_sources max_buffer_size user output_format unknown
139139+ =
137140 {
138141 allowed_tools;
139142 disallowed_tools;
···178181 |> mem "addDirs" (Jsont.list Jsont.string) ~enc:add_dirs ~dec_absent:[]
179182 |> opt_mem "maxBudgetUsd" Jsont.number ~enc:max_budget_usd
180183 |> opt_mem "fallbackModel" Model.jsont ~enc:fallback_model
181181- |> opt_mem "settingSources" (Jsont.list setting_source_jsont)
184184+ |> opt_mem "settingSources"
185185+ (Jsont.list setting_source_jsont)
182186 ~enc:setting_sources
183187 |> opt_mem "maxBufferSize" Jsont.int ~enc:max_buffer_size
184188 |> opt_mem "user" Jsont.string ~enc:user
+4-4
proto/options.mli
···14141515(** {1 Setting Sources} *)
16161717+(** The type of setting sources, indicating where configuration was loaded from.
1818+*)
1719type setting_source =
1820 | User (** User-level settings *)
1921 | Project (** Project-level settings *)
2022 | Local (** Local directory settings *)
2121-(** The type of setting sources, indicating where configuration was loaded
2222- from. *)
23232424(** {1 Configuration Type} *)
25252626type t
2727(** The type of configuration options.
28282929- This represents all configurable options for Claude interactions, encoded
3030- in JSON format. *)
2929+ This represents all configurable options for Claude interactions, encoded in
3030+ JSON format. *)
31313232val jsont : t Jsont.t
3333(** [jsont] is the Jsont codec for configuration options.
···1414module Mode : sig
1515 (** Permission modes control the overall behavior of the permission system. *)
16161717+ (** The type of permission modes. *)
1718 type t =
1819 | Default (** Standard permission mode with normal checks *)
1920 | Accept_edits (** Automatically accept file edits *)
2021 | Plan (** Planning mode with restricted execution *)
2122 | Bypass_permissions (** Bypass all permission checks *)
2222- (** The type of permission modes. *)
23232424 val jsont : t Jsont.t
2525 (** [jsont] is the Jsont codec for permission modes. Wire format uses
···3838module Behavior : sig
3939 (** Behaviors determine how permission requests are handled. *)
40404141+ (** The type of permission behaviors. *)
4142 type t =
4243 | Allow (** Allow the operation *)
4344 | Deny (** Deny the operation *)
4445 | Ask (** Ask the user for permission *)
4545- (** The type of permission behaviors. *)
46464747 val jsont : t Jsont.t
4848 (** [jsont] is the Jsont codec for permission behaviors. Wire format uses
···9292module Update : sig
9393 (** Updates modify permission settings. *)
94949595+ (** The destination for permission updates. *)
9596 type destination =
9697 | User_settings (** Apply to user settings *)
9798 | Project_settings (** Apply to project settings *)
9899 | Local_settings (** Apply to local settings *)
99100 | Session (** Apply to current session only *)
100100- (** The destination for permission updates. *)
101101102102+ (** The type of permission update. *)
102103 type update_type =
103104 | Add_rules (** Add new rules *)
104105 | Replace_rules (** Replace existing rules *)
···106107 | Set_mode (** Set permission mode *)
107108 | Add_directories (** Add allowed directories *)
108109 | Remove_directories (** Remove allowed directories *)
109109- (** The type of permission update. *)
110110111111 type t
112112 (** The type of permission updates. *)
···200200 interrupt : bool; (** Whether to interrupt execution *)
201201 unknown : Unknown.t; (** Unknown fields *)
202202 }
203203- (** The type of permission results. Wire format uses a discriminated union
204204- with "behavior" field set to "allow" or "deny". *)
203203+ (** The type of permission results. Wire format uses a discriminated
204204+ union with "behavior" field set to "allow" or "deny". *)
205205206206 val jsont : t Jsont.t
207207 (** [jsont] is the Jsont codec for permission results. Preserves unknown
+2-1
proto/structured_output.mli
···2323val of_json_schema : Jsont.json -> t
2424(** [of_json_schema schema] creates an output format from a JSON Schema.
25252626- The schema should be a valid JSON Schema Draft 7 as a {!type:Jsont.json} value.
2626+ The schema should be a valid JSON Schema Draft 7 as a {!type:Jsont.json}
2727+ value.
27282829 Example:
2930 {[
+9-7
proto/unknown.ml
···3939(** Mems codec for use with Jsont.Object.keep_unknown.
40404141 This provides a custom mems codec that converts between our (string *
4242- Jsont.json) list representation and the Jsont.mem list representation
4343- used by keep_unknown. *)
4242+ Jsont.json) list representation and the Jsont.mem list representation used
4343+ by keep_unknown. *)
4444let mems : (t, Jsont.json, Jsont.mem list) Jsont.Object.Mems.map =
4545 let open Jsont in
4646 (* The decoder builds up a mem list (the third type parameter) and
···5353 in
5454 let enc =
5555 {
5656- Object.Mems.enc = (fun k fields acc ->
5757- List.fold_left
5858- (fun acc (name, json) -> k Meta.none name json acc)
5959- acc fields);
5656+ Object.Mems.enc =
5757+ (fun k fields acc ->
5858+ List.fold_left
5959+ (fun acc (name, json) -> k Meta.none name json acc)
6060+ acc fields);
6061 }
6162 in
6262- Object.Mems.map ~kind:"Unknown" ~dec_empty ~dec_add ~dec_finish ~enc Jsont.json
6363+ Object.Mems.map ~kind:"Unknown" ~dec_empty ~dec_add ~dec_finish ~enc
6464+ Jsont.json
···2626 m "%s thinking: %s" name (Claude.Response.Thinking.content t))
2727 | Claude.Response.Complete c ->
2828 (if Claude.Response.Complete.total_cost_usd c <> None then
2929- let cost = Option.get (Claude.Response.Complete.total_cost_usd c) in
2929+ let cost =
3030+ Option.get (Claude.Response.Complete.total_cost_usd c)
3131+ in
3032 Log.info (fun m -> m "%s's joke cost: $%.6f" name cost));
3133 Log.debug (fun m ->
3234 m "%s session: %s, duration: %dms" name
3335 (Claude.Response.Complete.session_id c)
3436 (Claude.Response.Complete.duration_ms c))
3537 | Claude.Response.Error e ->
3636- Log.err (fun m -> m "Error from %s: %s" name (Claude.Response.Error.message e))
3838+ Log.err (fun m ->
3939+ m "Error from %s: %s" name (Claude.Response.Error.message e))
3740 | Claude.Response.Init _ ->
3841 (* Init messages are already logged by the library *)
3942 ()
···5154 in
52555356 let client =
5454- Claude.Client.create ~options ~sw ~process_mgr:env#process_mgr ~clock:env#clock ()
5757+ Claude.Client.create ~options ~sw ~process_mgr:env#process_mgr
5858+ ~clock:env#clock ()
5559 in
56605761 Claude.Client.query client prompt;
+5-5
test/discovery_demo.ml
···2323 String.sub content 0 100 ^ "..."
2424 else content))
2525 | Claude.Response.Tool_use t ->
2626- Log.info (fun m ->
2727- m "Tool use: %s" (Claude.Response.Tool_use.name t))
2626+ Log.info (fun m -> m "Tool use: %s" (Claude.Response.Tool_use.name t))
2827 | Claude.Response.Error err ->
2928 Log.err (fun m -> m "Error: %s" (Claude.Response.Error.message err))
3030- | Claude.Response.Complete result ->
3131- (match Claude.Response.Complete.total_cost_usd result with
2929+ | Claude.Response.Complete result -> (
3030+ match Claude.Response.Complete.total_cost_usd result with
3231 | Some cost -> Log.info (fun m -> m "Cost: $%.6f" cost)
3332 | None -> ())
3433 | _ -> ())
···4544 |> Claude.Options.with_model (Claude.Proto.Model.of_string "sonnet")
4645 in
4746 let client =
4848- Claude.Client.create ~options ~sw ~process_mgr:env#process_mgr ~clock:env#clock ()
4747+ Claude.Client.create ~options ~sw ~process_mgr:env#process_mgr
4848+ ~clock:env#clock ()
4949 in
5050 Claude.Client.enable_permission_discovery client;
5151
+1
test/dune
···44 (libraries jsont jsont.bytesrw))
5566; Consolidated unit test suite using alcotest
77+78(test
89 (name test_claude)
910 (modules test_claude)
+10-7
test/hooks_example.ml
···1212(* Example 1: Block dangerous bash commands *)
1313let block_dangerous_bash input =
1414 if input.Claude.Hooks.PreToolUse.tool_name = "Bash" then
1515- match Claude.Tool_input.get_string input.Claude.Hooks.PreToolUse.tool_input "command" with
1515+ match
1616+ Claude.Tool_input.get_string input.Claude.Hooks.PreToolUse.tool_input
1717+ "command"
1818+ with
1619 | Some command ->
1720 if String.length command >= 6 && String.sub command 0 6 = "rm -rf" then begin
1821 Log.app (fun m -> m "🚫 Blocked dangerous command: %s" command);
···25282629(* Example 2: Log all tool usage *)
2730let log_tool_usage input =
2828- Log.app (fun m -> m "📝 Tool %s called" input.Claude.Hooks.PreToolUse.tool_name);
3131+ Log.app (fun m ->
3232+ m "📝 Tool %s called" input.Claude.Hooks.PreToolUse.tool_name);
2933 Claude.Hooks.PreToolUse.continue ()
30343135let run_example ~sw ~env =
···4650 in
47514852 let client =
4949- Claude.Client.create ~options ~sw ~process_mgr:env#process_mgr ~clock:env#clock ()
5353+ Claude.Client.create ~options ~sw ~process_mgr:env#process_mgr
5454+ ~clock:env#clock ()
5055 in
51565257 (* Test 1: Safe command (should work) *)
···6166 let content = Claude.Response.Text.content text in
6267 if String.length content > 0 then
6368 Log.app (fun m -> m "Claude: %s" content)
6464- | Claude.Response.Complete _ ->
6565- Log.app (fun m -> m "✅ Test 1 complete\n")
6969+ | Claude.Response.Complete _ -> Log.app (fun m -> m "✅ Test 1 complete\n")
6670 | Claude.Response.Error err ->
6771 Log.err (fun m -> m "❌ Error: %s" (Claude.Response.Error.message err))
6872 | _ -> ())
···8084 let content = Claude.Response.Text.content text in
8185 if String.length content > 0 then
8286 Log.app (fun m -> m "Claude: %s" content)
8383- | Claude.Response.Complete _ ->
8484- Log.app (fun m -> m "✅ Test 2 complete")
8787+ | Claude.Response.Complete _ -> Log.app (fun m -> m "✅ Test 2 complete")
8588 | Claude.Response.Error err ->
8689 Log.err (fun m -> m "❌ Error: %s" (Claude.Response.Error.message err))
8790 | _ -> ())
+6-3
test/permission_demo.ml
···46464747 (* Log the full input for debugging *)
4848 let input_json = Claude.Tool_input.to_json input in
4949- Log.info (fun m -> m "Full input JSON: %s" (Test_json_utils.to_string input_json));
4949+ Log.info (fun m ->
5050+ m "Full input JSON: %s" (Test_json_utils.to_string input_json));
50515152 (* Show input details *)
5253 (* Try to extract key information from the input *)
···8081 | Some path -> Log.app (fun m -> m "Path: %s" path)
8182 | None -> Log.app (fun m -> m "Path: (current directory)"))
8283 | None -> ())
8383- | _ -> Log.app (fun m -> m "Input: %s" (Test_json_utils.to_string input_json))
8484+ | _ ->
8585+ Log.app (fun m -> m "Input: %s" (Test_json_utils.to_string input_json))
8486 with exn ->
8587 Log.info (fun m ->
8688 m "Failed to parse input details: %s" (Printexc.to_string exn)));
···159161 in
160162161163 let client =
162162- Claude.Client.create ~options ~sw ~process_mgr:env#process_mgr ~clock:env#clock ()
164164+ Claude.Client.create ~options ~sw ~process_mgr:env#process_mgr
165165+ ~clock:env#clock ()
163166 in
164167165168 (* First prompt - Claude will need to request Read permission for ../lib *)
+12-5
test/simple_permission_test.ml
···1313let auto_allow_callback ctx =
1414 Log.app (fun m -> m "\n🔐 Permission callback invoked!");
1515 Log.app (fun m -> m " Tool: %s" ctx.Claude.Permissions.tool_name);
1616- Log.app (fun m -> m " Input: %s" (Test_json_utils.to_string (Claude.Tool_input.to_json ctx.Claude.Permissions.input)));
1616+ Log.app (fun m ->
1717+ m " Input: %s"
1818+ (Test_json_utils.to_string
1919+ (Claude.Tool_input.to_json ctx.Claude.Permissions.input)));
1720 Log.app (fun m -> m " ✅ Auto-allowing");
1821 Claude.Permissions.Decision.allow ()
1922···30333134 Log.app (fun m -> m "Creating client with permission callback...");
3235 let client =
3333- Claude.Client.create ~options ~sw ~process_mgr:env#process_mgr ~clock:env#clock ()
3636+ Claude.Client.create ~options ~sw ~process_mgr:env#process_mgr
3737+ ~clock:env#clock ()
3438 in
35393640 (* Query that should trigger Write tool *)
···6771 Log.app (fun m -> m "\n⚠️ Tool result error for %s:" tool_use_id);
6872 match Claude.Content_block.Tool_result.content r with
6973 | Some json ->
7070- let s = match Jsont_bytesrw.encode_string' Jsont.json json with
7474+ let s =
7575+ match Jsont_bytesrw.encode_string' Jsont.json json with
7176 | Ok str -> str
7277 | Error _ -> "<encoding error>"
7378 in
···8085 | Some cost -> Log.app (fun m -> m "💰 Cost: $%.6f" cost)
8186 | None -> ());
8287 Log.app (fun m ->
8383- m "⏱️ Duration: %dms" (Claude.Response.Complete.duration_ms result))
8888+ m "⏱️ Duration: %dms"
8989+ (Claude.Response.Complete.duration_ms result))
8490 | Claude.Response.Error err ->
8585- Log.err (fun m -> m "\n❌ Error: %s" (Claude.Response.Error.message err))
9191+ Log.err (fun m ->
9292+ m "\n❌ Error: %s" (Claude.Response.Error.message err))
8693 | _ -> ())
8794 messages;
8895
+3-12
test/simulated_permissions.ml
···8888 ~interrupt:false
8989 | _ ->
9090 Log.app (fun m -> m " → Denied (invalid response)");
9191- Decision.deny ~message:"Invalid permission response"
9292- ~interrupt:false
9191+ Decision.deny ~message:"Invalid permission response" ~interrupt:false
9392 end
94939594(* Demonstrate the permission system *)
···117116 let tool_input = Claude.Tool_input.of_json input in
118117 let ctx =
119118 Claude.Permissions.
120120- {
121121- tool_name;
122122- input = tool_input;
123123- suggested_rules = [];
124124- }
119119+ { tool_name; input = tool_input; suggested_rules = [] }
125120 in
126121 let decision = example_permission_callback ctx in
127122···175170 let tool_input = Claude.Tool_input.of_json input in
176171 let ctx =
177172 Claude.Permissions.
178178- {
179179- tool_name;
180180- input = tool_input;
181181- suggested_rules = [];
182182- }
173173+ { tool_name; input = tool_input; suggested_rules = [] }
183174 in
184175 let _ = callback ctx in
185176 ())
+8-3
test/structured_output_demo.ml
···105105 in
106106107107 (* Create structured output format from the schema *)
108108- let output_format = Claude.Proto.Structured_output.of_json_schema analysis_schema in
108108+ let output_format =
109109+ Claude.Proto.Structured_output.of_json_schema analysis_schema
110110+ in
109111110112 (* Configure Claude with structured output *)
111113 let options =
···148150 Printf.printf " Using tool: %s\n" (C.Response.Tool_use.name tool)
149151 | C.Response.Complete result -> (
150152 Printf.printf "\n=== Result ===\n";
151151- Printf.printf "Duration: %dms\n" (C.Response.Complete.duration_ms result);
153153+ Printf.printf "Duration: %dms\n"
154154+ (C.Response.Complete.duration_ms result);
152155 Printf.printf "Cost: $%.4f\n"
153153- (Option.value (C.Response.Complete.total_cost_usd result) ~default:0.0);
156156+ (Option.value
157157+ (C.Response.Complete.total_cost_usd result)
158158+ ~default:0.0);
154159155160 (* Extract and display structured output *)
156161 match C.Response.Complete.structured_output result with
+3-1
test/structured_output_simple.ml
···4949 Meta.none )
5050 in
51515252- let output_format = Claude.Proto.Structured_output.of_json_schema person_schema in
5252+ let output_format =
5353+ Claude.Proto.Structured_output.of_json_schema person_schema
5454+ in
53555456 let options =
5557 C.Options.default
+321-221
test/test_claude.ml
···6868 | Claude.Proto.Control.Response.Error e ->
6969 Alcotest.(check string) "request_id" "test-req-2" e.request_id;
7070 Alcotest.(check int) "error code" (-32603) e.error.code;
7171- Alcotest.(check string) "error message" "Something went wrong" e.error.message
7171+ Alcotest.(check string)
7272+ "error message" "Something went wrong" e.error.message
7273 | Claude.Proto.Control.Response.Success _ ->
7374 Alcotest.fail "Got success response instead of error")
7475 | Ok _ -> Alcotest.fail "Wrong message type decoded"
7576 | Error err -> Alcotest.fail (Jsont.Error.to_string err)
76777777-let protocol_tests = [
7878- Alcotest.test_case "decode user message" `Quick test_decode_user_message;
7979- Alcotest.test_case "decode assistant message" `Quick test_decode_assistant_message;
8080- Alcotest.test_case "decode system message" `Quick test_decode_system_message;
8181- Alcotest.test_case "decode control response success" `Quick test_decode_control_response_success;
8282- Alcotest.test_case "decode control response error" `Quick test_decode_control_response_error;
8383-]
7878+let protocol_tests =
7979+ [
8080+ Alcotest.test_case "decode user message" `Quick test_decode_user_message;
8181+ Alcotest.test_case "decode assistant message" `Quick
8282+ test_decode_assistant_message;
8383+ Alcotest.test_case "decode system message" `Quick test_decode_system_message;
8484+ Alcotest.test_case "decode control response success" `Quick
8585+ test_decode_control_response_success;
8686+ Alcotest.test_case "decode control response error" `Quick
8787+ test_decode_control_response_error;
8888+ ]
84898590(* ============================================
8691 Tool Module Tests
8792 ============================================ *)
88938989-let json_testable = Alcotest.testable
9090- (fun fmt json ->
9191- match Jsont_bytesrw.encode_string' Jsont.json json with
9292- | Ok s -> Format.pp_print_string fmt s
9393- | Error e -> Format.pp_print_string fmt (Jsont.Error.to_string e))
9494- (fun a b ->
9595- match Jsont_bytesrw.encode_string' Jsont.json a, Jsont_bytesrw.encode_string' Jsont.json b with
9696- | Ok sa, Ok sb -> String.equal sa sb
9797- | _ -> false)
9494+let json_testable =
9595+ Alcotest.testable
9696+ (fun fmt json ->
9797+ match Jsont_bytesrw.encode_string' Jsont.json json with
9898+ | Ok s -> Format.pp_print_string fmt s
9999+ | Error e -> Format.pp_print_string fmt (Jsont.Error.to_string e))
100100+ (fun a b ->
101101+ match
102102+ ( Jsont_bytesrw.encode_string' Jsont.json a,
103103+ Jsont_bytesrw.encode_string' Jsont.json b )
104104+ with
105105+ | Ok sa, Ok sb -> String.equal sa sb
106106+ | _ -> false)
9810799108let test_tool_schema_string () =
100109 let schema = Claude.Tool.schema_string in
101101- let expected = J.object' [J.mem (J.name "type") (J.string "string")] in
110110+ let expected = J.object' [ J.mem (J.name "type") (J.string "string") ] in
102111 Alcotest.check json_testable "schema_string" expected schema
103112104113let test_tool_schema_int () =
105114 let schema = Claude.Tool.schema_int in
106106- let expected = J.object' [J.mem (J.name "type") (J.string "integer")] in
115115+ let expected = J.object' [ J.mem (J.name "type") (J.string "integer") ] in
107116 Alcotest.check json_testable "schema_int" expected schema
108117109118let test_tool_schema_number () =
110119 let schema = Claude.Tool.schema_number in
111111- let expected = J.object' [J.mem (J.name "type") (J.string "number")] in
120120+ let expected = J.object' [ J.mem (J.name "type") (J.string "number") ] in
112121 Alcotest.check json_testable "schema_number" expected schema
113122114123let test_tool_schema_bool () =
115124 let schema = Claude.Tool.schema_bool in
116116- let expected = J.object' [J.mem (J.name "type") (J.string "boolean")] in
125125+ let expected = J.object' [ J.mem (J.name "type") (J.string "boolean") ] in
117126 Alcotest.check json_testable "schema_bool" expected schema
118127119128let test_tool_schema_array () =
120129 let schema = Claude.Tool.schema_array Claude.Tool.schema_string in
121121- let expected = J.object' [
122122- J.mem (J.name "type") (J.string "array");
123123- J.mem (J.name "items") (J.object' [J.mem (J.name "type") (J.string "string")])
124124- ] in
130130+ let expected =
131131+ J.object'
132132+ [
133133+ J.mem (J.name "type") (J.string "array");
134134+ J.mem (J.name "items")
135135+ (J.object' [ J.mem (J.name "type") (J.string "string") ]);
136136+ ]
137137+ in
125138 Alcotest.check json_testable "schema_array" expected schema
126139127140let test_tool_schema_string_enum () =
128128- let schema = Claude.Tool.schema_string_enum ["foo"; "bar"; "baz"] in
129129- let expected = J.object' [
130130- J.mem (J.name "type") (J.string "string");
131131- J.mem (J.name "enum") (J.list [J.string "foo"; J.string "bar"; J.string "baz"])
132132- ] in
141141+ let schema = Claude.Tool.schema_string_enum [ "foo"; "bar"; "baz" ] in
142142+ let expected =
143143+ J.object'
144144+ [
145145+ J.mem (J.name "type") (J.string "string");
146146+ J.mem (J.name "enum")
147147+ (J.list [ J.string "foo"; J.string "bar"; J.string "baz" ]);
148148+ ]
149149+ in
133150 Alcotest.check json_testable "schema_string_enum" expected schema
134151135152let test_tool_schema_object () =
136136- let schema = Claude.Tool.schema_object
137137- [("name", Claude.Tool.schema_string); ("age", Claude.Tool.schema_int)]
138138- ~required:["name"]
153153+ let schema =
154154+ Claude.Tool.schema_object
155155+ [ ("name", Claude.Tool.schema_string); ("age", Claude.Tool.schema_int) ]
156156+ ~required:[ "name" ]
139157 in
140140- let expected = J.object' [
141141- J.mem (J.name "type") (J.string "object");
142142- J.mem (J.name "properties") (J.object' [
143143- J.mem (J.name "name") (J.object' [J.mem (J.name "type") (J.string "string")]);
144144- J.mem (J.name "age") (J.object' [J.mem (J.name "type") (J.string "integer")])
145145- ]);
146146- J.mem (J.name "required") (J.list [J.string "name"])
147147- ] in
158158+ let expected =
159159+ J.object'
160160+ [
161161+ J.mem (J.name "type") (J.string "object");
162162+ J.mem (J.name "properties")
163163+ (J.object'
164164+ [
165165+ J.mem (J.name "name")
166166+ (J.object' [ J.mem (J.name "type") (J.string "string") ]);
167167+ J.mem (J.name "age")
168168+ (J.object' [ J.mem (J.name "type") (J.string "integer") ]);
169169+ ]);
170170+ J.mem (J.name "required") (J.list [ J.string "name" ]);
171171+ ]
172172+ in
148173 Alcotest.check json_testable "schema_object" expected schema
149174150175let test_tool_text_result () =
151176 let result = Claude.Tool.text_result "Hello, world!" in
152152- let expected = J.list [J.object' [
153153- J.mem (J.name "type") (J.string "text");
154154- J.mem (J.name "text") (J.string "Hello, world!")
155155- ]] in
177177+ let expected =
178178+ J.list
179179+ [
180180+ J.object'
181181+ [
182182+ J.mem (J.name "type") (J.string "text");
183183+ J.mem (J.name "text") (J.string "Hello, world!");
184184+ ];
185185+ ]
186186+ in
156187 Alcotest.check json_testable "text_result" expected result
157188158189let test_tool_error_result () =
159190 let result = Claude.Tool.error_result "Something went wrong" in
160160- let expected = J.list [J.object' [
161161- J.mem (J.name "type") (J.string "text");
162162- J.mem (J.name "text") (J.string "Something went wrong");
163163- J.mem (J.name "is_error") (J.bool true)
164164- ]] in
191191+ let expected =
192192+ J.list
193193+ [
194194+ J.object'
195195+ [
196196+ J.mem (J.name "type") (J.string "text");
197197+ J.mem (J.name "text") (J.string "Something went wrong");
198198+ J.mem (J.name "is_error") (J.bool true);
199199+ ];
200200+ ]
201201+ in
165202 Alcotest.check json_testable "error_result" expected result
166203167204let test_tool_create_and_call () =
168168- let greet = Claude.Tool.create
169169- ~name:"greet"
170170- ~description:"Greet a user"
171171- ~input_schema:(Claude.Tool.schema_object
172172- [("name", Claude.Tool.schema_string)]
173173- ~required:["name"])
174174- ~handler:(fun args ->
175175- match Claude.Tool_input.get_string args "name" with
176176- | Some name -> Ok (Claude.Tool.text_result ("Hello, " ^ name ^ "!"))
177177- | None -> Error "Missing name parameter")
205205+ let greet =
206206+ Claude.Tool.create ~name:"greet" ~description:"Greet a user"
207207+ ~input_schema:
208208+ (Claude.Tool.schema_object
209209+ [ ("name", Claude.Tool.schema_string) ]
210210+ ~required:[ "name" ])
211211+ ~handler:(fun args ->
212212+ match Claude.Tool_input.get_string args "name" with
213213+ | Some name -> Ok (Claude.Tool.text_result ("Hello, " ^ name ^ "!"))
214214+ | None -> Error "Missing name parameter")
178215 in
179216 Alcotest.(check string) "tool name" "greet" (Claude.Tool.name greet);
180180- Alcotest.(check string) "tool description" "Greet a user" (Claude.Tool.description greet);
217217+ Alcotest.(check string)
218218+ "tool description" "Greet a user"
219219+ (Claude.Tool.description greet);
181220182221 (* Test successful call *)
183183- let input_json = J.object' [J.mem (J.name "name") (J.string "Alice")] in
222222+ let input_json = J.object' [ J.mem (J.name "name") (J.string "Alice") ] in
184223 let input = Claude.Tool_input.of_json input_json in
185224 match Claude.Tool.call greet input with
186225 | Ok result ->
187226 let expected = Claude.Tool.text_result "Hello, Alice!" in
188227 Alcotest.check json_testable "call result" expected result
189189- | Error msg ->
190190- Alcotest.fail msg
228228+ | Error msg -> Alcotest.fail msg
191229192230let test_tool_call_error () =
193193- let tool = Claude.Tool.create
194194- ~name:"fail"
195195- ~description:"Always fails"
196196- ~input_schema:(Claude.Tool.schema_object [] ~required:[])
197197- ~handler:(fun _ -> Error "Intentional failure")
231231+ let tool =
232232+ Claude.Tool.create ~name:"fail" ~description:"Always fails"
233233+ ~input_schema:(Claude.Tool.schema_object [] ~required:[])
234234+ ~handler:(fun _ -> Error "Intentional failure")
198235 in
199236 let input = Claude.Tool_input.of_json (J.object' []) in
200237 match Claude.Tool.call tool input with
201238 | Ok _ -> Alcotest.fail "Expected error"
202202- | Error msg -> Alcotest.(check string) "error message" "Intentional failure" msg
239239+ | Error msg ->
240240+ Alcotest.(check string) "error message" "Intentional failure" msg
203241204204-let tool_tests = [
205205- Alcotest.test_case "schema_string" `Quick test_tool_schema_string;
206206- Alcotest.test_case "schema_int" `Quick test_tool_schema_int;
207207- Alcotest.test_case "schema_number" `Quick test_tool_schema_number;
208208- Alcotest.test_case "schema_bool" `Quick test_tool_schema_bool;
209209- Alcotest.test_case "schema_array" `Quick test_tool_schema_array;
210210- Alcotest.test_case "schema_string_enum" `Quick test_tool_schema_string_enum;
211211- Alcotest.test_case "schema_object" `Quick test_tool_schema_object;
212212- Alcotest.test_case "text_result" `Quick test_tool_text_result;
213213- Alcotest.test_case "error_result" `Quick test_tool_error_result;
214214- Alcotest.test_case "create and call" `Quick test_tool_create_and_call;
215215- Alcotest.test_case "call error" `Quick test_tool_call_error;
216216-]
242242+let tool_tests =
243243+ [
244244+ Alcotest.test_case "schema_string" `Quick test_tool_schema_string;
245245+ Alcotest.test_case "schema_int" `Quick test_tool_schema_int;
246246+ Alcotest.test_case "schema_number" `Quick test_tool_schema_number;
247247+ Alcotest.test_case "schema_bool" `Quick test_tool_schema_bool;
248248+ Alcotest.test_case "schema_array" `Quick test_tool_schema_array;
249249+ Alcotest.test_case "schema_string_enum" `Quick test_tool_schema_string_enum;
250250+ Alcotest.test_case "schema_object" `Quick test_tool_schema_object;
251251+ Alcotest.test_case "text_result" `Quick test_tool_text_result;
252252+ Alcotest.test_case "error_result" `Quick test_tool_error_result;
253253+ Alcotest.test_case "create and call" `Quick test_tool_create_and_call;
254254+ Alcotest.test_case "call error" `Quick test_tool_call_error;
255255+ ]
217256218257(* ============================================
219258 Mcp_server Module Tests
220259 ============================================ *)
221260222261let test_mcp_server_create () =
223223- let tool = Claude.Tool.create
224224- ~name:"echo"
225225- ~description:"Echo input"
226226- ~input_schema:(Claude.Tool.schema_object [("text", Claude.Tool.schema_string)] ~required:["text"])
227227- ~handler:(fun args ->
228228- match Claude.Tool_input.get_string args "text" with
229229- | Some text -> Ok (Claude.Tool.text_result text)
230230- | None -> Error "Missing text")
262262+ let tool =
263263+ Claude.Tool.create ~name:"echo" ~description:"Echo input"
264264+ ~input_schema:
265265+ (Claude.Tool.schema_object
266266+ [ ("text", Claude.Tool.schema_string) ]
267267+ ~required:[ "text" ])
268268+ ~handler:(fun args ->
269269+ match Claude.Tool_input.get_string args "text" with
270270+ | Some text -> Ok (Claude.Tool.text_result text)
271271+ | None -> Error "Missing text")
231272 in
232232- let server = Claude.Mcp_server.create ~name:"test-server" ~version:"2.0.0" ~tools:[tool] () in
233233- Alcotest.(check string) "server name" "test-server" (Claude.Mcp_server.name server);
234234- Alcotest.(check string) "server version" "2.0.0" (Claude.Mcp_server.version server);
235235- Alcotest.(check int) "tools count" 1 (List.length (Claude.Mcp_server.tools server))
273273+ let server =
274274+ Claude.Mcp_server.create ~name:"test-server" ~version:"2.0.0"
275275+ ~tools:[ tool ] ()
276276+ in
277277+ Alcotest.(check string)
278278+ "server name" "test-server"
279279+ (Claude.Mcp_server.name server);
280280+ Alcotest.(check string)
281281+ "server version" "2.0.0"
282282+ (Claude.Mcp_server.version server);
283283+ Alcotest.(check int)
284284+ "tools count" 1
285285+ (List.length (Claude.Mcp_server.tools server))
236286237287let test_mcp_server_initialize () =
238288 let server = Claude.Mcp_server.create ~name:"init-test" ~tools:[] () in
239239- let request = J.object' [
240240- J.mem (J.name "jsonrpc") (J.string "2.0");
241241- J.mem (J.name "id") (J.number 1.0);
242242- J.mem (J.name "method") (J.string "initialize");
243243- J.mem (J.name "params") (J.object' [])
244244- ] in
289289+ let request =
290290+ J.object'
291291+ [
292292+ J.mem (J.name "jsonrpc") (J.string "2.0");
293293+ J.mem (J.name "id") (J.number 1.0);
294294+ J.mem (J.name "method") (J.string "initialize");
295295+ J.mem (J.name "params") (J.object' []);
296296+ ]
297297+ in
245298 let response = Claude.Mcp_server.handle_json_message server request in
246299 (* Check it's a success response with serverInfo *)
247300 match response with
···251304 | _ -> Alcotest.fail "Expected object response"
252305253306let test_mcp_server_tools_list () =
254254- let tool = Claude.Tool.create
255255- ~name:"my_tool"
256256- ~description:"My test tool"
257257- ~input_schema:(Claude.Tool.schema_object [] ~required:[])
258258- ~handler:(fun _ -> Ok (Claude.Tool.text_result "ok"))
307307+ let tool =
308308+ Claude.Tool.create ~name:"my_tool" ~description:"My test tool"
309309+ ~input_schema:(Claude.Tool.schema_object [] ~required:[])
310310+ ~handler:(fun _ -> Ok (Claude.Tool.text_result "ok"))
259311 in
260260- let server = Claude.Mcp_server.create ~name:"list-test" ~tools:[tool] () in
261261- let request = J.object' [
262262- J.mem (J.name "jsonrpc") (J.string "2.0");
263263- J.mem (J.name "id") (J.number 2.0);
264264- J.mem (J.name "method") (J.string "tools/list");
265265- J.mem (J.name "params") (J.object' [])
266266- ] in
312312+ let server = Claude.Mcp_server.create ~name:"list-test" ~tools:[ tool ] () in
313313+ let request =
314314+ J.object'
315315+ [
316316+ J.mem (J.name "jsonrpc") (J.string "2.0");
317317+ J.mem (J.name "id") (J.number 2.0);
318318+ J.mem (J.name "method") (J.string "tools/list");
319319+ J.mem (J.name "params") (J.object' []);
320320+ ]
321321+ in
267322 let response = Claude.Mcp_server.handle_json_message server request in
268323 match response with
269324 | Jsont.Object (mems, _) -> (
···277332 | _ -> Alcotest.fail "Expected object response"
278333279334let test_mcp_server_tools_call () =
280280- let tool = Claude.Tool.create
281281- ~name:"uppercase"
282282- ~description:"Convert to uppercase"
283283- ~input_schema:(Claude.Tool.schema_object [("text", Claude.Tool.schema_string)] ~required:["text"])
284284- ~handler:(fun args ->
285285- match Claude.Tool_input.get_string args "text" with
286286- | Some text -> Ok (Claude.Tool.text_result (String.uppercase_ascii text))
287287- | None -> Error "Missing text")
335335+ let tool =
336336+ Claude.Tool.create ~name:"uppercase" ~description:"Convert to uppercase"
337337+ ~input_schema:
338338+ (Claude.Tool.schema_object
339339+ [ ("text", Claude.Tool.schema_string) ]
340340+ ~required:[ "text" ])
341341+ ~handler:(fun args ->
342342+ match Claude.Tool_input.get_string args "text" with
343343+ | Some text ->
344344+ Ok (Claude.Tool.text_result (String.uppercase_ascii text))
345345+ | None -> Error "Missing text")
288346 in
289289- let server = Claude.Mcp_server.create ~name:"call-test" ~tools:[tool] () in
290290- let request = J.object' [
291291- J.mem (J.name "jsonrpc") (J.string "2.0");
292292- J.mem (J.name "id") (J.number 3.0);
293293- J.mem (J.name "method") (J.string "tools/call");
294294- J.mem (J.name "params") (J.object' [
295295- J.mem (J.name "name") (J.string "uppercase");
296296- J.mem (J.name "arguments") (J.object' [
297297- J.mem (J.name "text") (J.string "hello")
298298- ])
299299- ])
300300- ] in
347347+ let server = Claude.Mcp_server.create ~name:"call-test" ~tools:[ tool ] () in
348348+ let request =
349349+ J.object'
350350+ [
351351+ J.mem (J.name "jsonrpc") (J.string "2.0");
352352+ J.mem (J.name "id") (J.number 3.0);
353353+ J.mem (J.name "method") (J.string "tools/call");
354354+ J.mem (J.name "params")
355355+ (J.object'
356356+ [
357357+ J.mem (J.name "name") (J.string "uppercase");
358358+ J.mem (J.name "arguments")
359359+ (J.object' [ J.mem (J.name "text") (J.string "hello") ]);
360360+ ]);
361361+ ]
362362+ in
301363 let response = Claude.Mcp_server.handle_json_message server request in
302364 (* Verify it contains the expected uppercase result *)
303303- let response_str = match Jsont_bytesrw.encode_string' Jsont.json response with
304304- | Ok s -> s | Error _ -> ""
365365+ let response_str =
366366+ match Jsont_bytesrw.encode_string' Jsont.json response with
367367+ | Ok s -> s
368368+ | Error _ -> ""
305369 in
306370 (* Simple substring check for HELLO in response *)
307371 let contains_hello =
···309373 if i + 5 > String.length response_str then false
310374 else if String.sub response_str i 5 = "HELLO" then true
311375 else check (i + 1)
312312- in check 0
376376+ in
377377+ check 0
313378 in
314379 Alcotest.(check bool) "contains HELLO" true contains_hello
315380316381let test_mcp_server_tool_not_found () =
317382 let server = Claude.Mcp_server.create ~name:"notfound-test" ~tools:[] () in
318318- let request = J.object' [
319319- J.mem (J.name "jsonrpc") (J.string "2.0");
320320- J.mem (J.name "id") (J.number 4.0);
321321- J.mem (J.name "method") (J.string "tools/call");
322322- J.mem (J.name "params") (J.object' [
323323- J.mem (J.name "name") (J.string "nonexistent")
324324- ])
325325- ] in
383383+ let request =
384384+ J.object'
385385+ [
386386+ J.mem (J.name "jsonrpc") (J.string "2.0");
387387+ J.mem (J.name "id") (J.number 4.0);
388388+ J.mem (J.name "method") (J.string "tools/call");
389389+ J.mem (J.name "params")
390390+ (J.object' [ J.mem (J.name "name") (J.string "nonexistent") ]);
391391+ ]
392392+ in
326393 let response = Claude.Mcp_server.handle_json_message server request in
327394 (* Should return an error response *)
328395 match response with
···332399 | _ -> Alcotest.fail "Expected object response"
333400334401let test_mcp_server_method_not_found () =
335335- let server = Claude.Mcp_server.create ~name:"method-notfound-test" ~tools:[] () in
336336- let request = J.object' [
337337- J.mem (J.name "jsonrpc") (J.string "2.0");
338338- J.mem (J.name "id") (J.number 5.0);
339339- J.mem (J.name "method") (J.string "unknown/method");
340340- J.mem (J.name "params") (J.object' [])
341341- ] in
402402+ let server =
403403+ Claude.Mcp_server.create ~name:"method-notfound-test" ~tools:[] ()
404404+ in
405405+ let request =
406406+ J.object'
407407+ [
408408+ J.mem (J.name "jsonrpc") (J.string "2.0");
409409+ J.mem (J.name "id") (J.number 5.0);
410410+ J.mem (J.name "method") (J.string "unknown/method");
411411+ J.mem (J.name "params") (J.object' []);
412412+ ]
413413+ in
342414 let response = Claude.Mcp_server.handle_json_message server request in
343415 match response with
344416 | Jsont.Object (mems, _) ->
···346418 Alcotest.(check bool) "has error" true has_error
347419 | _ -> Alcotest.fail "Expected object response"
348420349349-let mcp_server_tests = [
350350- Alcotest.test_case "create server" `Quick test_mcp_server_create;
351351- Alcotest.test_case "initialize" `Quick test_mcp_server_initialize;
352352- Alcotest.test_case "tools/list" `Quick test_mcp_server_tools_list;
353353- Alcotest.test_case "tools/call" `Quick test_mcp_server_tools_call;
354354- Alcotest.test_case "tool not found" `Quick test_mcp_server_tool_not_found;
355355- Alcotest.test_case "method not found" `Quick test_mcp_server_method_not_found;
356356-]
421421+let mcp_server_tests =
422422+ [
423423+ Alcotest.test_case "create server" `Quick test_mcp_server_create;
424424+ Alcotest.test_case "initialize" `Quick test_mcp_server_initialize;
425425+ Alcotest.test_case "tools/list" `Quick test_mcp_server_tools_list;
426426+ Alcotest.test_case "tools/call" `Quick test_mcp_server_tools_call;
427427+ Alcotest.test_case "tool not found" `Quick test_mcp_server_tool_not_found;
428428+ Alcotest.test_case "method not found" `Quick
429429+ test_mcp_server_method_not_found;
430430+ ]
357431358432(* ============================================
359433 Structured Error Tests
360434 ============================================ *)
361435362436let test_error_detail_creation () =
363363- let error = Claude.Proto.Control.Response.error_detail
364364- ~code:`Method_not_found
365365- ~message:"Method not found"
366366- ()
437437+ let error =
438438+ Claude.Proto.Control.Response.error_detail ~code:`Method_not_found
439439+ ~message:"Method not found" ()
367440 in
368441 Alcotest.(check int) "error code" (-32601) error.code;
369442 Alcotest.(check string) "error message" "Method not found" error.message
370443371444let test_error_code_conventions () =
372372- let codes = [
373373- (`Parse_error, -32700);
374374- (`Invalid_request, -32600);
375375- (`Method_not_found, -32601);
376376- (`Invalid_params, -32602);
377377- (`Internal_error, -32603);
378378- (`Custom 1, 1);
379379- ] in
380380- List.iter (fun (code, expected_int) ->
381381- let err = Claude.Proto.Control.Response.error_detail ~code ~message:"test" () in
382382- Alcotest.(check int) "error code value" expected_int err.code
383383- ) codes
445445+ let codes =
446446+ [
447447+ (`Parse_error, -32700);
448448+ (`Invalid_request, -32600);
449449+ (`Method_not_found, -32601);
450450+ (`Invalid_params, -32602);
451451+ (`Internal_error, -32603);
452452+ (`Custom 1, 1);
453453+ ]
454454+ in
455455+ List.iter
456456+ (fun (code, expected_int) ->
457457+ let err =
458458+ Claude.Proto.Control.Response.error_detail ~code ~message:"test" ()
459459+ in
460460+ Alcotest.(check int) "error code value" expected_int err.code)
461461+ codes
384462385463let test_error_response_encoding () =
386386- let error_detail = Claude.Proto.Control.Response.error_detail
387387- ~code:`Invalid_params
388388- ~message:"Invalid parameters"
389389- ()
464464+ let error_detail =
465465+ Claude.Proto.Control.Response.error_detail ~code:`Invalid_params
466466+ ~message:"Invalid parameters" ()
390467 in
391391- let error_resp = Claude.Proto.Control.Response.error
392392- ~request_id:"test-123"
393393- ~error:error_detail
394394- ()
468468+ let error_resp =
469469+ Claude.Proto.Control.Response.error ~request_id:"test-123"
470470+ ~error:error_detail ()
395471 in
396472 match Jsont.Json.encode Claude.Proto.Control.Response.jsont error_resp with
397473 | Ok json -> (
···399475 | Ok (Claude.Proto.Control.Response.Error decoded) ->
400476 Alcotest.(check string) "request_id" "test-123" decoded.request_id;
401477 Alcotest.(check int) "error code" (-32602) decoded.error.code;
402402- Alcotest.(check string) "error message" "Invalid parameters" decoded.error.message
478478+ Alcotest.(check string)
479479+ "error message" "Invalid parameters" decoded.error.message
403480 | Ok _ -> Alcotest.fail "Wrong response type decoded"
404481 | Error e -> Alcotest.fail e)
405482 | Error e -> Alcotest.fail e
406483407407-let structured_error_tests = [
408408- Alcotest.test_case "error detail creation" `Quick test_error_detail_creation;
409409- Alcotest.test_case "error code conventions" `Quick test_error_code_conventions;
410410- Alcotest.test_case "error response encoding" `Quick test_error_response_encoding;
411411-]
484484+let structured_error_tests =
485485+ [
486486+ Alcotest.test_case "error detail creation" `Quick test_error_detail_creation;
487487+ Alcotest.test_case "error code conventions" `Quick
488488+ test_error_code_conventions;
489489+ Alcotest.test_case "error response encoding" `Quick
490490+ test_error_response_encoding;
491491+ ]
412492413493(* ============================================
414494 Tool_input Tests
415495 ============================================ *)
416496417497let test_tool_input_get_string () =
418418- let json = J.object' [J.mem (J.name "foo") (J.string "bar")] in
498498+ let json = J.object' [ J.mem (J.name "foo") (J.string "bar") ] in
419499 let input = Claude.Tool_input.of_json json in
420420- Alcotest.(check (option string)) "get_string foo" (Some "bar") (Claude.Tool_input.get_string input "foo");
421421- Alcotest.(check (option string)) "get_string missing" None (Claude.Tool_input.get_string input "missing")
500500+ Alcotest.(check (option string))
501501+ "get_string foo" (Some "bar")
502502+ (Claude.Tool_input.get_string input "foo");
503503+ Alcotest.(check (option string))
504504+ "get_string missing" None
505505+ (Claude.Tool_input.get_string input "missing")
422506423507let test_tool_input_get_int () =
424424- let json = J.object' [J.mem (J.name "count") (J.number 42.0)] in
508508+ let json = J.object' [ J.mem (J.name "count") (J.number 42.0) ] in
425509 let input = Claude.Tool_input.of_json json in
426426- Alcotest.(check (option int)) "get_int count" (Some 42) (Claude.Tool_input.get_int input "count")
510510+ Alcotest.(check (option int))
511511+ "get_int count" (Some 42)
512512+ (Claude.Tool_input.get_int input "count")
427513428514let test_tool_input_get_float () =
429429- let json = J.object' [J.mem (J.name "pi") (J.number 3.14159)] in
515515+ let json = J.object' [ J.mem (J.name "pi") (J.number 3.14159) ] in
430516 let input = Claude.Tool_input.of_json json in
431517 match Claude.Tool_input.get_float input "pi" with
432432- | Some f -> Alcotest.(check bool) "get_float pi approx" true (abs_float (f -. 3.14159) < 0.0001)
518518+ | Some f ->
519519+ Alcotest.(check bool)
520520+ "get_float pi approx" true
521521+ (abs_float (f -. 3.14159) < 0.0001)
433522 | None -> Alcotest.fail "Expected float"
434523435524let test_tool_input_get_bool () =
436436- let json = J.object' [
437437- J.mem (J.name "yes") (J.bool true);
438438- J.mem (J.name "no") (J.bool false)
439439- ] in
525525+ let json =
526526+ J.object'
527527+ [ J.mem (J.name "yes") (J.bool true); J.mem (J.name "no") (J.bool false) ]
528528+ in
440529 let input = Claude.Tool_input.of_json json in
441441- Alcotest.(check (option bool)) "get_bool yes" (Some true) (Claude.Tool_input.get_bool input "yes");
442442- Alcotest.(check (option bool)) "get_bool no" (Some false) (Claude.Tool_input.get_bool input "no")
530530+ Alcotest.(check (option bool))
531531+ "get_bool yes" (Some true)
532532+ (Claude.Tool_input.get_bool input "yes");
533533+ Alcotest.(check (option bool))
534534+ "get_bool no" (Some false)
535535+ (Claude.Tool_input.get_bool input "no")
443536444537let test_tool_input_get_string_list () =
445445- let json = J.object' [
446446- J.mem (J.name "items") (J.list [J.string "a"; J.string "b"; J.string "c"])
447447- ] in
538538+ let json =
539539+ J.object'
540540+ [
541541+ J.mem (J.name "items")
542542+ (J.list [ J.string "a"; J.string "b"; J.string "c" ]);
543543+ ]
544544+ in
448545 let input = Claude.Tool_input.of_json json in
449449- Alcotest.(check (option (list string))) "get_string_list"
450450- (Some ["a"; "b"; "c"])
546546+ Alcotest.(check (option (list string)))
547547+ "get_string_list"
548548+ (Some [ "a"; "b"; "c" ])
451549 (Claude.Tool_input.get_string_list input "items")
452550453453-let tool_input_tests = [
454454- Alcotest.test_case "get_string" `Quick test_tool_input_get_string;
455455- Alcotest.test_case "get_int" `Quick test_tool_input_get_int;
456456- Alcotest.test_case "get_float" `Quick test_tool_input_get_float;
457457- Alcotest.test_case "get_bool" `Quick test_tool_input_get_bool;
458458- Alcotest.test_case "get_string_list" `Quick test_tool_input_get_string_list;
459459-]
551551+let tool_input_tests =
552552+ [
553553+ Alcotest.test_case "get_string" `Quick test_tool_input_get_string;
554554+ Alcotest.test_case "get_int" `Quick test_tool_input_get_int;
555555+ Alcotest.test_case "get_float" `Quick test_tool_input_get_float;
556556+ Alcotest.test_case "get_bool" `Quick test_tool_input_get_bool;
557557+ Alcotest.test_case "get_string_list" `Quick test_tool_input_get_string_list;
558558+ ]
460559461560(* ============================================
462561 Main test runner
463562 ============================================ *)
464563465564let () =
466466- Alcotest.run "Claude SDK" [
467467- "Protocol", protocol_tests;
468468- "Tool", tool_tests;
469469- "Mcp_server", mcp_server_tests;
470470- "Structured errors", structured_error_tests;
471471- "Tool_input", tool_input_tests;
472472- ]
565565+ Alcotest.run "Claude SDK"
566566+ [
567567+ ("Protocol", protocol_tests);
568568+ ("Tool", tool_tests);
569569+ ("Mcp_server", mcp_server_tests);
570570+ ("Structured errors", structured_error_tests);
571571+ ("Tool_input", tool_input_tests);
572572+ ]
+4-3
test/test_incoming.ml
···6565 | Ok (Proto.Incoming.Control_response resp) -> (
6666 match resp.response with
6767 | Proto.Control.Response.Error e ->
6868- if e.request_id = "test-req-2"
6969- && e.error.code = -32603
7070- && e.error.message = "Something went wrong"
6868+ if
6969+ e.request_id = "test-req-2"
7070+ && e.error.code = -32603
7171+ && e.error.message = "Something went wrong"
7172 then print_endline "✓ Decoded control error response successfully"
7273 else Printf.printf "✗ Wrong error content\n"
7374 | Proto.Control.Response.Success _ ->
+4-2
test/test_permissions.ml
···11111212(* Simple auto-allow permission callback *)
1313let auto_allow_callback ctx =
1414- Log.app (fun m -> m "✅ Auto-allowing tool: %s" ctx.Claude.Permissions.tool_name);
1414+ Log.app (fun m ->
1515+ m "✅ Auto-allowing tool: %s" ctx.Claude.Permissions.tool_name);
1516 Claude.Permissions.Decision.allow ()
16171718let run_test ~sw ~env =
···27282829 Log.app (fun m -> m "Creating client with permission callback...");
2930 let client =
3030- Claude.Client.create ~options ~sw ~process_mgr:env#process_mgr ~clock:env#clock ()
3131+ Claude.Client.create ~options ~sw ~process_mgr:env#process_mgr
3232+ ~clock:env#clock ()
3133 in
32343335 (* Simple query that will trigger tool use *)
+82-74
test/test_structured_error.ml
···1111 print_endline "\nTesting structured error creation...";
12121313 (* Create a simple error *)
1414- let error1 = Proto.Control.Response.error_detail
1515- ~code:`Method_not_found
1616- ~message:"Method not found"
1717- ()
1414+ let error1 =
1515+ Proto.Control.Response.error_detail ~code:`Method_not_found
1616+ ~message:"Method not found" ()
1817 in
1918 Printf.printf "✓ Created error: [%d] %s\n" error1.code error1.message;
20192120 (* Create an error without additional data for simplicity *)
2222- let error2 = Proto.Control.Response.error_detail
2323- ~code:`Invalid_params
2424- ~message:"Invalid parameters"
2525- ()
2121+ let error2 =
2222+ Proto.Control.Response.error_detail ~code:`Invalid_params
2323+ ~message:"Invalid parameters" ()
2624 in
2725 Printf.printf "✓ Created error: [%d] %s\n" error2.code error2.message;
28262927 (* Encode and decode an error response *)
3030- let error_resp = Proto.Control.Response.error
3131- ~request_id:"test-123"
3232- ~error:error2
3333- ()
2828+ let error_resp =
2929+ Proto.Control.Response.error ~request_id:"test-123" ~error:error2 ()
3430 in
35313632 match Jsont.Json.encode Proto.Control.Response.jsont error_resp with
3737- | Ok json ->
3838- let json_str = match Jsont_bytesrw.encode_string' Jsont.json json with
3333+ | Ok json -> (
3434+ let json_str =
3535+ match Jsont_bytesrw.encode_string' Jsont.json json with
3936 | Ok s -> s
4037 | Error e -> Jsont.Error.to_string e
4138 in
4239 Printf.printf "✓ Encoded error response: %s\n" json_str;
43404441 (* Decode it back *)
4545- (match Jsont.Json.decode Proto.Control.Response.jsont json with
4242+ match Jsont.Json.decode Proto.Control.Response.jsont json with
4643 | Ok (Proto.Control.Response.Error decoded) ->
4747- Printf.printf "✓ Decoded error: [%d] %s\n"
4848- decoded.error.code decoded.error.message
4444+ Printf.printf "✓ Decoded error: [%d] %s\n" decoded.error.code
4545+ decoded.error.message
4946 | Ok _ -> print_endline "✗ Wrong response type"
5047 | Error e -> Printf.printf "✗ Decode failed: %s\n" e)
5151- | Error e ->
5252- Printf.printf "✗ Encode failed: %s\n" e
4848+ | Error e -> Printf.printf "✗ Encode failed: %s\n" e
53495450let test_error_code_conventions () =
5551 print_endline "\nTesting JSON-RPC error code conventions...";
56525753 (* Standard JSON-RPC errors using the typed API with polymorphic variants *)
5858- let errors = [
5959- (`Parse_error, "Parse error");
6060- (`Invalid_request, "Invalid request");
6161- (`Method_not_found, "Method not found");
6262- (`Invalid_params, "Invalid params");
6363- (`Internal_error, "Internal error");
6464- (`Custom 1, "Application error");
6565- ] in
5454+ let errors =
5555+ [
5656+ (`Parse_error, "Parse error");
5757+ (`Invalid_request, "Invalid request");
5858+ (`Method_not_found, "Method not found");
5959+ (`Invalid_params, "Invalid params");
6060+ (`Internal_error, "Internal error");
6161+ (`Custom 1, "Application error");
6262+ ]
6363+ in
66646767- List.iter (fun (code, msg) ->
6868- let err = Proto.Control.Response.error_detail ~code ~message:msg () in
6969- Printf.printf "✓ Error [%d]: %s (typed)\n" err.code err.message
7070- ) errors
6565+ List.iter
6666+ (fun (code, msg) ->
6767+ let err = Proto.Control.Response.error_detail ~code ~message:msg () in
6868+ Printf.printf "✓ Error [%d]: %s (typed)\n" err.code err.message)
6969+ errors
71707271let test_provoke_api_error ~sw ~env =
7372 print_endline "\nTesting API error from Claude...";
···7574 (* Configure client with an invalid model to provoke an API error *)
7675 let options =
7776 Claude.Options.default
7878- |> Claude.Options.with_model (Claude.Model.of_string "invalid-model-that-does-not-exist")
7777+ |> Claude.Options.with_model
7878+ (Claude.Model.of_string "invalid-model-that-does-not-exist")
7979 in
80808181 Printf.printf "Creating client with invalid model...\n";
82828383 try
8484 let client =
8585- Claude.Client.create ~options ~sw ~process_mgr:env#process_mgr ~clock:env#clock ()
8585+ Claude.Client.create ~options ~sw ~process_mgr:env#process_mgr
8686+ ~clock:env#clock ()
8687 in
87888889 Printf.printf "Sending query to provoke API error...\n";
8989- Claude.Client.query client "Hello, this should fail with an invalid model error";
9090+ Claude.Client.query client
9191+ "Hello, this should fail with an invalid model error";
90929193 (* Process responses to see if we get an error *)
9294 let messages = Claude.Client.receive_all client in
···106108 (Claude.Response.Error.is_assistant_error err)
107109 | Claude.Response.Text text ->
108110 let content = Claude.Response.Text.content text in
109109- if String.length content > 0 &&
110110- (String.contains content '4' || String.contains content 'e') then begin
111111+ if
112112+ String.length content > 0
113113+ && (String.contains content '4' || String.contains content 'e')
114114+ then begin
111115 text_error_found := true;
112116 Printf.printf "✓ Received error as text: %s\n" content
113117 end
···121125 Printf.printf "✓ Successfully caught structured error response\n"
122126 else if !text_error_found then
123127 Printf.printf "✓ Successfully caught error (returned as text)\n"
124124- else
125125- Printf.printf "✗ No error was returned (unexpected)\n"
126126-128128+ else Printf.printf "✗ No error was returned (unexpected)\n"
127129 with
128130 | Claude.Transport.Connection_error msg ->
129131 Printf.printf "✓ Connection error as expected: %s\n" msg
···135137 print_endline "\nTesting control protocol error encoding/decoding...";
136138137139 (* Test that we can create and encode a control protocol error using polymorphic variant codes *)
138138- let error_detail = Proto.Control.Response.error_detail
139139- ~code:`Invalid_params
140140- ~message:"Invalid params for permission request"
141141- ~data:(Jsont.Object ([
142142- (("tool_name", Jsont.Meta.none), Jsont.String ("Write", Jsont.Meta.none));
143143- (("reason", Jsont.Meta.none), Jsont.String ("Missing required file_path parameter", Jsont.Meta.none));
144144- ], Jsont.Meta.none))
145145- ()
140140+ let error_detail =
141141+ Proto.Control.Response.error_detail ~code:`Invalid_params
142142+ ~message:"Invalid params for permission request"
143143+ ~data:
144144+ (Jsont.Object
145145+ ( [
146146+ ( ("tool_name", Jsont.Meta.none),
147147+ Jsont.String ("Write", Jsont.Meta.none) );
148148+ ( ("reason", Jsont.Meta.none),
149149+ Jsont.String
150150+ ("Missing required file_path parameter", Jsont.Meta.none) );
151151+ ],
152152+ Jsont.Meta.none ))
153153+ ()
146154 in
147155148148- let error_response = Proto.Control.Response.error
149149- ~request_id:"test-req-456"
150150- ~error:error_detail
151151- ()
156156+ let error_response =
157157+ Proto.Control.Response.error ~request_id:"test-req-456" ~error:error_detail
158158+ ()
152159 in
153160154161 match Jsont.Json.encode Proto.Control.Response.jsont error_response with
155155- | Ok json ->
156156- let json_str = match Jsont_bytesrw.encode_string' Jsont.json json with
162162+ | Ok json -> (
163163+ let json_str =
164164+ match Jsont_bytesrw.encode_string' Jsont.json json with
157165 | Ok s -> s
158166 | Error e -> Jsont.Error.to_string e
159167 in
160168 Printf.printf "✓ Encoded control error with data:\n %s\n" json_str;
161169162170 (* Verify we can decode it back *)
163163- (match Jsont.Json.decode Proto.Control.Response.jsont json with
164164- | Ok (Proto.Control.Response.Error decoded) ->
171171+ match Jsont.Json.decode Proto.Control.Response.jsont json with
172172+ | Ok (Proto.Control.Response.Error decoded) -> (
165173 Printf.printf "✓ Decoded control error:\n";
166174 Printf.printf " Code: %d\n" decoded.error.code;
167175 Printf.printf " Message: %s\n" decoded.error.message;
168176 Printf.printf " Has data: %b\n" (Option.is_some decoded.error.data);
169169- (match decoded.error.data with
177177+ match decoded.error.data with
170178 | Some data ->
171171- let data_str = match Jsont_bytesrw.encode_string' Jsont.json data with
179179+ let data_str =
180180+ match Jsont_bytesrw.encode_string' Jsont.json data with
172181 | Ok s -> s
173182 | Error e -> Jsont.Error.to_string e
174183 in
···176185 | None -> ())
177186 | Ok _ -> print_endline "✗ Wrong response type"
178187 | Error e -> Printf.printf "✗ Decode failed: %s\n" e)
179179- | Error e ->
180180- Printf.printf "✗ Encode failed: %s\n" e
188188+ | Error e -> Printf.printf "✗ Encode failed: %s\n" e
181189182190let test_hook_error ~sw ~env =
183191 print_endline "\nTesting hook callback errors trigger JSON-RPC error codes...";
184192185193 (* Create a hook that will throw an exception *)
186194 let failing_hook input =
187187- Printf.printf "✓ Hook called for tool: %s\n" input.Claude.Hooks.PreToolUse.tool_name;
195195+ Printf.printf "✓ Hook called for tool: %s\n"
196196+ input.Claude.Hooks.PreToolUse.tool_name;
188197 failwith "Intentional hook failure to test error handling"
189198 in
190199···204213205214 try
206215 let client =
207207- Claude.Client.create ~options ~sw ~process_mgr:env#process_mgr ~clock:env#clock ()
216216+ Claude.Client.create ~options ~sw ~process_mgr:env#process_mgr
217217+ ~clock:env#clock ()
208218 in
209219210210- Printf.printf "Asking Claude to write a file (should trigger failing hook)...\n";
220220+ Printf.printf
221221+ "Asking Claude to write a file (should trigger failing hook)...\n";
211222 Claude.Client.query client "Write 'test' to /tmp/test_hook_error.txt";
212223213224 (* Process responses *)
···226237 end
227238 | Claude.Response.Error err ->
228239 error_found := true;
229229- Printf.printf " Error response: %s\n" (Claude.Response.Error.message err)
230230- | Claude.Response.Complete _ ->
231231- Printf.printf " Query completed\n"
240240+ Printf.printf " Error response: %s\n"
241241+ (Claude.Response.Error.message err)
242242+ | Claude.Response.Complete _ -> Printf.printf " Query completed\n"
232243 | _ -> ())
233244 messages;
234245235246 if !hook_called then
236247 Printf.printf "✓ Hook was triggered, exception caught by SDK\n"
237248 else
238238- Printf.printf " Note: Hook may not have been called if query didn't use Write tool\n";
249249+ Printf.printf
250250+ " Note: Hook may not have been called if query didn't use Write tool\n";
239251240252 Printf.printf "✓ Test completed (SDK sent -32603 Internal Error to CLI)\n"
241241-242242- with
243243- | exn ->
244244- Printf.printf "Exception during test: %s\n" (Printexc.to_string exn);
245245- Printexc.print_backtrace stdout
253253+ with exn ->
254254+ Printf.printf "Exception during test: %s\n" (Printexc.to_string exn);
255255+ Printexc.print_backtrace stdout
246256247257let run_all_tests env =
248258 print_endline "=== Structured Error Tests ===";
···262272263273let () =
264274 Eio_main.run @@ fun env ->
265265- try
266266- run_all_tests env
267267- with
275275+ try run_all_tests env with
268276 | Claude.Transport.CLI_not_found msg ->
269277 Printf.eprintf "Error: Claude CLI not found\n%s\n" msg;
270278 Printf.eprintf "Make sure 'claude' is installed and in your PATH\n";