cMCP 0.4.1
Model Context Protocol library in pure C11
Loading...
Searching...
No Matches
cmcp_client.h
Go to the documentation of this file.
1/**
2 * @file cmcp_client.h
3 * @brief Asynchronous MCP client: handshake, list/call/read/subscribe,
4 * cancellation, progress, sampling, roots, elicitation.
5 *
6 * The client owns a transport and a reader thread that demultiplexes
7 * responses by id against an in-flight table. Every call is async at
8 * the wire level — `cmcp_client_call_async` returns immediately with
9 * a handle, and `cmcp_client_wait` blocks for completion; many calls
10 * can be in flight concurrently and they complete in any order.
11 * `cmcp_client_request` is sync sugar over the async pair.
12 *
13 * Client-side handlers (notification routing, sampling, elicitation)
14 * run on the reader thread; from inside one, non-blocking calls are
15 * fine but the blocking pair (request/wait) deadlocks — see the
16 * "Thread-safety contract" section below for the full rules.
17 * `cmcp_client_set_roots` advertises filesystem scope to the server;
18 * `cmcp_client_cancel` issues a `notifications/cancelled` for an
19 * in-flight call.
20 *
21 * For multi-server agents see `cmcp_session.h`, which aggregates
22 * several already-handshaken clients under a single primitive
23 * surface.
24 */
25#ifndef CMCP_CLIENT_H
26#define CMCP_CLIENT_H
27
28#include "cmcp_types.h"
29#include "cmcp_transport.h"
30
31typedef struct cmcp_client cmcp_client_t;
32
33/* Outcome of a tools/call. The MCP `tools/call` method can fail in
34 * distinct ways the host has to reason about separately:
35 *
36 * - a JSON-RPC error on the channel (peer rejected the call,
37 * transport failed mid-flight, unknown tool surfaced as -32602
38 * with structured data),
39 * - a tool-level error (handler succeeded at the channel level but
40 * reported a failure in result.isError + result.content[]; this is
41 * also how a server-side argument-schema rejection arrives — see
42 * docs/schema-conformance.md),
43 * - host-initiated cancellation (cmcp_client_cancel won the race, or
44 * the client is shutting down) — distinct from a server error so a
45 * host does not log a cancelled call as a failure.
46 *
47 * Hosts that try to flatten this into a single resp.error check miss
48 * the tool-level case; hosts that only check result.isError miss the
49 * protocol case. The outcome enum puts every case into one switch. */
50typedef enum {
51 CMCP_TOOL_OK = 0, /* success — result holds the result object */
52 CMCP_TOOL_ERR_TOOL_LEVEL = 1, /* tool said no — text holds content[0].text */
53 CMCP_TOOL_ERR_PROTOCOL = 2, /* channel said no — error holds the rpc error */
54 CMCP_TOOL_ERR_CANCELLED = 3, /* host cancelled / shutdown — no payload (F5) */
56
57/* The result of a tools/call, returned BY VALUE. Exactly one payload
58 * field is meaningful, selected by `.outcome`:
59 *
60 * CMCP_TOOL_OK .result = result object (owned). Includes
61 * content[], structuredContent (if
62 * any), isError == false.
63 * CMCP_TOOL_ERR_TOOL_LEVEL .text = first content[].text, owned
64 * NUL-terminated string. Empty
65 * string if the server set isError
66 * with no text content.
67 * CMCP_TOOL_ERR_PROTOCOL .error = owned cmcp_rpc_error_t. Carries
68 * the wire error (unknown tool
69 * -32602, method-not-found -32601,
70 * internal -32603, …) OR a
71 * synthesized error when the
72 * failure was transport-side (the
73 * call never reached the peer or
74 * no response came back).
75 * CMCP_TOOL_ERR_CANCELLED (no payload — all three pointers NULL.)
76 *
77 * Returning the payload by value rather than through out-params removes
78 * the P6 eval-order footgun: there is no pointer the caller can read
79 * before the call has populated it. Free EVERY result with
80 * cmcp_tool_result_clear regardless of outcome — it frees whichever
81 * payload `.outcome` selected and zeros the struct. */
88
89/* Free the owned payload a cmcp_tool_result_t carries and zero it.
90 * Safe on a zeroed struct and idempotent (clears the pointers it
91 * frees). NULL `r` is a no-op. */
93
94/* An in-flight tools/call, binding its request id to the client that
95 * issued it. Returned by cmcp_client_tool_call_async, consumed by
96 * cmcp_client_tool_wait. Binding id→client makes it structurally
97 * impossible to wait on the wrong client (the P6 cross-session
98 * mis-routing footgun: per-client id spaces collide, so a bare id is
99 * ambiguous across clients). Treat the fields as read-only. */
100typedef struct {
102 long long id;
104
105/* A handle is valid iff it names a client and carries a positive id.
106 * cmcp_client_tool_call_async returns an invalid handle (client == NULL)
107 * on dispatch failure; cmcp_client_tool_wait maps an invalid handle to a
108 * CMCP_TOOL_ERR_PROTOCOL result so the caller's switch needs no extra
109 * guard. */
110static inline int cmcp_tool_handle_valid(cmcp_tool_handle_t h) {
111 return h.client != NULL && h.id > 0;
112}
113
114/* Single-client typed helpers (below) re-use the session-layer record
115 * shapes from cmcp_session.h, so a host pulls in one struct definition
116 * and one set of *_free destructors regardless of whether it talks to
117 * one server or N. The include sits after the cmcp_client_t typedef and
118 * the tool-call types above so cmcp_session.h's references to all of
119 * them resolve cleanly when a consumer #includes cmcp_client.h before
120 * (or instead of) cmcp_session.h. */
121#include "cmcp_session.h"
122
123/* ====================================================================== */
124/* Thread-safety contract */
125/* ====================================================================== */
126/* A cmcp_client_t owns one background reader thread (demuxing responses
127 * and dispatching server-initiated traffic). The rules below are the
128 * AS-BUILT guarantees — what the current implementation actually makes
129 * safe — not aspirations. Audited against src/client.c + src/rpc.c.
130 *
131 * SAFE FROM MULTIPLE HOST THREADS, concurrently, on the same client:
132 * - cmcp_client_request, cmcp_client_call_async, cmcp_client_notify,
133 * cmcp_client_cancel, and the typed wrappers (tool_call, *_list,
134 * resource_read, prompt_get, ...). Id allocation (pending-table
135 * mutex), the in-flight list (list_mu), and the transport writer
136 * (per-transport write mutex) are each internally locked, and no
137 * frame is ever written half-interleaved.
138 * - cmcp_client_wait — but only on DISTINCT ids per thread (see below).
139 * - The post-handshake read-backs (server_name/_version/_protocol) and
140 * cmcp_client_set_roots / notify_roots_changed (roots_mu-guarded,
141 * because the reader consults roots when answering roots/list).
142 *
143 * SINGLE-OWNER RULES — not enforced; violating them is a use-after-free
144 * or a leak, not a graceful error:
145 * - At most ONE thread may wait on a given id. The id returned by
146 * call_async is owned by one waiter; two waiters on the same id race
147 * to consume-and-free the completion record. Hand the id to one
148 * thread.
149 * - Every id from call_async must eventually be waited on, EVEN after
150 * cmcp_client_cancel. Cancel removes the entry from the pending
151 * table and wakes the waiter (which then returns CMCP_ECANCELLED and
152 * reclaims the record); a cancelled-but-never-waited call's record
153 * is reclaimed only at cmcp_client_free.
154 *
155 * CALLBACKS (notification / sampling / elicitation / progress handlers)
156 * all run ON THE READER THREAD:
157 * - They are serialized — never two at once — so they need no locking
158 * against each other. But they share the reader thread with response
159 * delivery: a callback that blocks or runs long stalls ALL responses
160 * and notifications for this client until it returns. A sampling or
161 * elicitation handler doing real work (LLM round-trip, interactive
162 * prompt) holds up the whole connection meanwhile — keep it quick or
163 * hand the work to another thread and return.
164 * - REENTRANCY: a callback MUST NOT call a *blocking* API on the same
165 * client — cmcp_client_request or cmcp_client_wait — because their
166 * completion depends on the reader thread, which is the very thread
167 * running the callback. That self-wait deadlocks. The *non-blocking*
168 * APIs (cmcp_client_call_async, cmcp_client_notify,
169 * cmcp_client_cancel) ARE safe to call from a callback: they don't
170 * wait on the reader and don't run under a lock the dispatch path
171 * still holds.
172 *
173 * SETUP / TEARDOWN:
174 * - Handler/capability setters other than set_roots
175 * (set_notification_handler, set_sampling_handler,
176 * set_elicitation_handler, set_capabilities, set_log_level) are NOT
177 * synchronized against a live reader. Configure them during setup,
178 * before connect / before traffic flows — not concurrently with
179 * active dispatch.
180 * - cmcp_client_free must be called by exactly one thread with no
181 * other thread inside any client call; it wakes and joins the reader,
182 * then cancels and frees any still-outstanding completions.
183 *
184 * cmcp_session_t (cmcp_session.h) inherits this contract through the
185 * clients it aggregates, but the session object's own bookkeeping is not
186 * separately locked: drive a given session from a single host thread.
187 */
188
189/* ====================================================================== */
190/* Lifecycle */
191/* ====================================================================== */
192
193cmcp_client_t *cmcp_client_new(const char *name, const char *version);
194
195/* Tears down the client. If the client owns a spawned child (via
196 * cmcp_client_connect_stdio) the child is asked to exit (transport
197 * close → SIGTERM fallback) and reaped before this returns. Cancels
198 * any in-flight calls with CMCP_ECANCELLED. */
200
202 const cmcp_client_capabilities_t *caps);
203
204/* Optional human-readable description, echoed in the handshake's
205 * `clientInfo.description` (MCP 2025-11-25 Minor 2). Pass NULL to
206 * clear. Returns CMCP_OK or CMCP_ENOMEM. */
207int cmcp_client_set_description(cmcp_client_t *c, const char *description);
208
209/* ====================================================================== */
210/* Notification routing */
211/* ====================================================================== */
212
213/* Invoked on the client's reader thread for each server-to-client
214 * notification. The handler MUST NOT call back into the same client
215 * (would deadlock). params may be NULL. */
216typedef void (*cmcp_notification_fn)(const char *method,
217 const cmcp_json_t *params,
218 void *userdata);
219
222 void *userdata);
223
224/* ====================================================================== */
225/* Handshake & transport */
226/* ====================================================================== */
227
228/* Drive the initialize → notifications/initialized handshake on a
229 * caller-provided transport. The client borrows the transport for its
230 * lifetime; caller still closes it. Starts the reader thread before
231 * sending the initialize request. Returns CMCP_OK; CMCP_EPROTOCOL on
232 * spec violation; CMCP_EIO on transport failure. */
233int cmcp_client_handshake(cmcp_client_t *c, cmcp_transport_t *t);
234
235/* Spawn a child process (path + argv, optional envp) connected via a
236 * stdio pipe pair, run the handshake, and start the reader thread.
237 * The client takes ownership of both the child process and the
238 * transport — cmcp_client_free closes them.
239 *
240 * argv[0] should typically equal `path`; argv must end with NULL.
241 * envp may be NULL to inherit the parent environment.
242 *
243 * Returns CMCP_OK; CMCP_EIO if fork/exec/handshake failed. */
245 const char *path,
246 char *const argv[],
247 char *const envp[]);
248
249/* ====================================================================== */
250/* Requests */
251/* ====================================================================== */
252
253/* Async request. Sends method+params and returns immediately. *out_id
254 * receives the in-flight request ID; pass it to cmcp_client_wait.
255 * Every async call must be matched by exactly one wait (or the
256 * client must be freed, which cancels all in-flight calls).
257 *
258 * params is consumed (ownership transferred). */
260 const char *method,
261 cmcp_json_t *params,
262 long long *out_id);
263
264/* Block the calling thread until the response for id arrives, or
265 * until the client is shut down. On success *out_response is
266 * initialised and owns its fields — caller must
267 * cmcp_rpc_message_clear() it.
268 *
269 * Returns CMCP_OK, CMCP_ECANCELLED on shutdown, or CMCP_ENOTFOUND if
270 * the id is not in the pending table (already consumed by callback or
271 * never registered). */
272int cmcp_client_wait(cmcp_client_t *c, long long id,
273 cmcp_rpc_message_t *out_response);
274
275/* Synchronous convenience: call_async + wait. params is consumed. */
276int cmcp_client_request(cmcp_client_t *c, const char *method,
277 cmcp_json_t *params,
278 cmcp_rpc_message_t *out_response);
279
280/* Fire-and-forget notification. params is consumed. */
281int cmcp_client_notify(cmcp_client_t *c, const char *method,
282 cmcp_json_t *params);
283
284/* Cancel an in-flight call. Emits `notifications/cancelled`
285 * `{requestId, reason?}` on the wire AND unblocks any thread parked in
286 * cmcp_client_wait(id) — that wait then returns CMCP_ECANCELLED. The
287 * pending entry is dropped, so a server response arriving after the
288 * cancel is silently discarded.
289 *
290 * `reason` may be NULL (the field is then omitted from the wire). The
291 * server MAY honour the cancel cooperatively; a slow handler that
292 * doesn't poll cmcp_handler_cancelled will still run to completion,
293 * but its response is dropped per spec.
294 *
295 * Returns:
296 * CMCP_OK cancel signalled + wire frame sent (or attempted)
297 * CMCP_EINVAL id is unknown or already completed
298 * CMCP_ENOMEM allocation failure */
299int cmcp_client_cancel(cmcp_client_t *c, long long id, const char *reason);
300
301/* Per-call progress callback. Invoked on the client's reader thread for
302 * each `notifications/progress` frame whose `_meta.progressToken`
303 * matches the token attached by cmcp_client_call_async_progress.
304 *
305 * progress The amount of work done so far.
306 * total Expected total, or negative if the server didn't send
307 * one (the spec marks `total` optional).
308 * message Optional human-readable status, NULL if absent.
309 * userdata The userdata pointer registered with the call.
310 *
311 * The handler MUST NOT call back into the same client (would deadlock)
312 * and should not block — slow handlers stall inbound frames. */
313typedef void (*cmcp_progress_fn)(double progress, double total,
314 const char *message, void *userdata);
315
316/* Async request with a per-call progress callback. The library generates
317 * a monotonically-unique integer progress token, attaches it to
318 * `_meta.progressToken` in `params` (replacing any caller-supplied
319 * value at that path), and routes matching `notifications/progress`
320 * frames to `fn` for the lifetime of the call.
321 *
322 * Otherwise identical to cmcp_client_call_async — pair with
323 * cmcp_client_wait. When the call completes (wait returns), the
324 * subscription is torn down with the completion record — no late
325 * progress notification will fire `fn` after wait returns.
326 *
327 * `params` is consumed (NULL is upgraded to an empty object so the
328 * library has somewhere to attach `_meta`). `fn` must not be NULL.
329 * Progress notifications carrying tokens that don't match any
330 * call-attached subscription still reach the generic notification
331 * handler (set via cmcp_client_set_notification_handler). */
333 const char *method,
334 cmcp_json_t *params,
336 void *userdata,
337 long long *out_id);
338
339/* ====================================================================== */
340/* Single-client typed helpers */
341/* ====================================================================== */
342
343/* These helpers exist so a host talking to ONE server doesn't have to
344 * drop to raw cmcp_json_object_get walking, and doesn't have to wrap
345 * the single client in a cmcp_session_t just to get list-iteration and
346 * pagination handling for free. They mirror the cmcp_session_t shapes:
347 * the record types and *_free destructors live in cmcp_session.h.
348 *
349 * For records produced by these helpers, the `server` and `qualified`
350 * fields (where present on the session-layer struct) are populated
351 * with NULL — there's only one client, so there's no namespacing to
352 * carry. Pair every list helper with the matching cmcp_session_*_free
353 * (which is NULL-safe on those fields). */
354
355/* Send `tools/list` and accumulate every page into a flat array. On
356 * CMCP_OK *out_tools is a malloc'd array of length *out_n (free with
357 * cmcp_session_tools_free) and the .server / .qualified fields on
358 * each entry are NULL.
359 *
360 * Returns CMCP_OK on success (including the empty-list case); the
361 * standard transport errors from cmcp_client_request on a wire failure;
362 * CMCP_EPROTOCOL if the server returned a JSON-RPC error for the first
363 * page. */
365 cmcp_session_tool_t **out_tools,
366 size_t *out_n);
367
368/* Send `resources/list` and accumulate every page. Same ownership and
369 * error model as cmcp_client_tools_list. Free with
370 * cmcp_session_resources_free. */
372 cmcp_session_resource_t **out_resources,
373 size_t *out_n);
374
375/* Send `prompts/list` and accumulate every page. Same ownership and
376 * error model as cmcp_client_tools_list. Free with
377 * cmcp_session_prompts_free. */
379 cmcp_session_prompt_t **out_prompts,
380 size_t *out_n);
381
382/* Send `resources/read` for `uri` and surface the first content item's
383 * text body. On CMCP_OK *out_text is a malloc'd NUL-terminated string
384 * (caller frees with free()) and *out_n is its byte length (excluding
385 * the NUL).
386 *
387 * Decision on binary `blob` content (per v0.6.0 acceptance doc rule 1):
388 * we keep the helper text-only. If the server returns a `blob` content
389 * item the helper returns CMCP_EUNSUPPORTED — a host that needs binary
390 * resources drops to cmcp_client_request("resources/read", ...) and
391 * walks the result tree itself.
392 *
393 * Returns CMCP_OK; CMCP_EINVAL on bad arguments; CMCP_EPROTOCOL if the
394 * server returned a JSON-RPC error or a structurally invalid result;
395 * CMCP_ENOTFOUND if `result.contents` is an empty array;
396 * CMCP_EUNSUPPORTED if the first content item is a `blob`; the
397 * standard transport errors on a wire failure. */
399 char **out_text, size_t *out_n);
400
401/* Send `prompts/get` for `name` with optional `args` (consumed; may be
402 * NULL) and surface the messages array. On CMCP_OK *out_messages is an
403 * owned cmcp_json_t whose `type` is CMCP_JSON_ARRAY — caller frees
404 * with cmcp_json_free.
405 *
406 * Returns CMCP_OK; CMCP_EINVAL on bad arguments; CMCP_EPROTOCOL if the
407 * server returned a JSON-RPC error or a structurally invalid result;
408 * the standard transport errors on a wire failure. */
409int cmcp_client_prompt_get(cmcp_client_t *c, const char *name,
410 cmcp_json_t *args,
411 cmcp_json_t **out_messages);
412
413/* Call a tool synchronously, flattening the multi-channel error model
414 * into one by-value result. `args` is consumed (may be NULL — the
415 * helper sends `{}` so the wire stays well-formed and server-side
416 * schema validation still runs).
417 *
418 * A NULL `c` or NULL `name` yields a CMCP_TOOL_ERR_PROTOCOL result with
419 * a synthesized -32602 error, so the caller's `switch` handles it with
420 * no default arm. Always free the returned result with
421 * cmcp_tool_result_clear. */
423 const char *name,
424 cmcp_json_t *args);
425
426/* Typed async pair. cmcp_client_tool_call is the sync sugar; this pair
427 * lets the host fan out N concurrent tool calls and reap them in any
428 * order without dropping back to the raw cmcp_client_call_async +
429 * cmcp_client_wait surface (which would re-expose the multi-channel
430 * error model the outcome enum eliminated).
431 *
432 * Wire shape, ownership, and error semantics match cmcp_client_tool_call
433 * — the split is purely about scheduling.
434 *
435 * cmcp_client_tool_call_async: builds {name, arguments} (NULL args
436 * becomes {}), dispatches via the async core, returns a handle binding
437 * the in-flight id to `c`. `args` is consumed in every code path
438 * (including caller misuse: NULL c / NULL name). On dispatch failure the
439 * returned handle is invalid (cmcp_tool_handle_valid == 0).
440 *
441 * cmcp_client_tool_wait: blocks until the response for `h` arrives, then
442 * maps it onto the outcome enum. An invalid handle, an unknown id, a
443 * malformed response, or a transport failure surface as
444 * CMCP_TOOL_ERR_PROTOCOL with a synthesized error; a cancelled call
445 * surfaces as CMCP_TOOL_ERR_CANCELLED. Always free the result with
446 * cmcp_tool_result_clear. */
448 const char *name,
449 cmcp_json_t *args);
450
452
453/* Content-shortcut helper, A5 (v0.7 candidate surfaced by the v0.6.0
454 * dogfood rewrite). Many host call sites want one thing from a
455 * `tools/call`: the LLM-facing text. Both the success branch
456 * (result.content[0].text) and the tool-error branch
457 * (isError:true + result.content[0].text) produce that text — but
458 * cmcp_client_tool_call hands the success branch back as a raw
459 * cmcp_json_t the host then has to walk. A5 flattens both:
460 *
461 * On CMCP_OK:
462 * *out_text is the first content[].text from the result, as
463 * an owned malloc-d C string. Empty string if the result had
464 * no content items or the first item had no text. *out_rpc_err
465 * stays NULL. The success-vs-tool-error distinction is
466 * DELIBERATELY collapsed; if you need it, use
467 * cmcp_client_tool_call.
468 *
469 * On negative return (CMCP_EPROTOCOL):
470 * *out_rpc_err is an owned cmcp_rpc_error_t (free with
471 * cmcp_rpc_error_free). *out_text stays NULL.
472 *
473 * `args` is consumed in every code path. NULL out_text or NULL
474 * out_rpc_err is silently freed. NULL c / NULL name / NULL out_text
475 * is reported as CMCP_EPROTOCOL with a synthesized -32602. */
477 const char *name,
478 cmcp_json_t *args,
479 char **out_text,
480 cmcp_rpc_error_t **out_rpc_err);
481
482/* ====================================================================== */
483/* Sampling (server → host LLM call) */
484/* ====================================================================== */
485
486/* Handler for `sampling/createMessage`. Servers invoke this when they
487 * want the host to run a completion through its configured LLM (e.g.
488 * a tool that wants the model to summarise its raw output before
489 * surfacing it).
490 *
491 * `params` The full params object: messages array + optional
492 * modelPreferences, systemPrompt, includeContext,
493 * temperature, maxTokens, stopSequences, metadata.
494 * Borrowed.
495 * `userdata` What was passed to set_sampling_handler.
496 * `out_result` OUT. Owned cmcp_json_t object, e.g.
497 * {"role":"assistant","content":{...},"model":"...",
498 * "stopReason":"endTurn"}.
499 * The library takes ownership on success.
500 *
501 * Return CMCP_OK on success, non-zero for INTERNAL_ERROR (-32603).
502 *
503 * Authorisation note: cMCP does NOT impose its own allow-list. The
504 * host (openclawd) decides per-server whether to register a handler;
505 * a server with no handler attached gets the default -32601 response.
506 * That's the trust gate. Don't register a handler for a server you
507 * don't trust to spend tokens. */
508typedef int (*cmcp_sampling_handler_fn)(const cmcp_json_t *params,
509 void *userdata,
510 cmcp_json_t **out_result);
511
512/* Register a sampling handler. Pass fn=NULL to clear. Setting the
513 * handler does NOT automatically advertise the `sampling` capability
514 * — call cmcp_client_set_capabilities to opt in to the wire signal
515 * (and do so BEFORE cmcp_client_handshake; the cap travels in the
516 * initialize request and isn't re-negotiated). */
519 void *userdata);
520
521/* Convenience: build a sampling result with a single-text-content
522 * assistant message. stop_reason is one of "endTurn", "stopSequence",
523 * "maxTokens" per spec. Returns NULL on allocation failure. */
524cmcp_json_t *cmcp_sampling_text_result(const char *text,
525 const char *model,
526 const char *stop_reason);
527
528/* ====================================================================== */
529/* Elicitation (server → host structured-input prompt) */
530/* ====================================================================== */
531
532/* Handler for `elicitation/create`. Servers invoke this mid-tool-call
533 * when they need additional structured input from the user — e.g. a
534 * confirmation, a missing argument, a credential.
535 *
536 * `params` `{message: string, requestedSchema: object}`.
537 * `requestedSchema` is a restricted flat object of
538 * primitive properties per the MCP spec (string / number
539 * / boolean / enum — no nesting). Borrowed.
540 * `userdata` What was passed to set_elicitation_handler.
541 * `out_result` OUT. Owned cmcp_json_t object built via
542 * cmcp_elicitation_result(action, content). The library
543 * takes ownership on success.
544 *
545 * Return CMCP_OK on success, non-zero for INTERNAL_ERROR (-32603).
546 *
547 * Trust note: same per-client model as sampling — register a handler
548 * only on clients whose servers are allowed to interrupt the user. */
549typedef int (*cmcp_elicitation_handler_fn)(const cmcp_json_t *params,
550 void *userdata,
551 cmcp_json_t **out_result);
552
553/* Register an elicitation handler. Pass fn=NULL to clear. Setting the
554 * handler does NOT automatically advertise the `elicitation` capability
555 * — call cmcp_client_set_capabilities to opt in to the wire signal
556 * (and do so BEFORE cmcp_client_handshake). */
559 void *userdata);
560
561/* Convenience: build an elicitation response envelope.
562 *
563 * action "accept", "decline", or "cancel" — required.
564 * content Required for "accept" — owned cmcp_json_t OBJECT shaped per
565 * the request's `requestedSchema`. Ignored (freed) for
566 * "decline"/"cancel" since the spec omits content there.
567 *
568 * Returns NULL on bad input; on failure, any passed-in content is
569 * freed so the caller never leaks. */
570cmcp_json_t *cmcp_elicitation_result(const char *action,
571 cmcp_json_t *content);
572
573/* ====================================================================== */
574/* Roots (host → server filesystem scoping) */
575/* ====================================================================== */
576
577/* Roots tell servers which paths the host considers in-scope. A
578 * filesystem-shaped server (or any server that operates on URIs) is
579 * expected to read this list before doing anything that touches the
580 * outside world. Roots are advisory — the server is the one that
581 * enforces the boundary. cMCP just carries the list. */
582
583typedef struct {
584 const char *uri; /* required, typically `file:///abs/path` */
585 const char *name; /* optional human-readable display name */
587
588/* Replace the current roots set with a deep-copy of the caller's
589 * array. Pass roots=NULL,n=0 to clear. After this call, the client's
590 * `roots` capability is advertised (presence of the key signals
591 * support for `roots/list`); if you also set
592 * `caps.roots_list_changed = 1` via cmcp_client_set_capabilities,
593 * `listChanged` is added.
594 *
595 * Safe to call before OR after handshake. Calling after handshake
596 * does NOT re-negotiate caps, but the new list takes effect
597 * immediately for subsequent server `roots/list` requests; pair with
598 * cmcp_client_notify_roots_changed if your server cares.
599 *
600 * Returns CMCP_OK / CMCP_EINVAL / CMCP_ENOMEM. */
602 const cmcp_root_t *roots, size_t n);
603
604/* Emit `notifications/roots/list_changed`. Requires
605 * caps.roots_list_changed = 1 (CMCP_EPROTOCOL otherwise). Caller is
606 * responsible for the order: typically you set the new roots first,
607 * then call this so the server's re-list sees the new state. */
609
610/* ====================================================================== */
611/* Logging */
612/* ====================================================================== */
613
614/* Ask the server to raise/lower its `notifications/message` floor.
615 * Sends a `logging/setLevel` request with `{level: "<name>"}` and
616 * blocks until the response arrives. Levels below the floor will be
617 * dropped server-side after this returns.
618 *
619 * Requires the server to have advertised the `logging` capability;
620 * otherwise the peer answers -32601 and this returns CMCP_EPROTOCOL.
621 *
622 * Returns CMCP_OK on success, CMCP_EPROTOCOL on a peer-side error or
623 * if the server doesn't speak logging, or the standard transport
624 * errors from cmcp_client_request. */
626
627/* ====================================================================== */
628/* Server identity (post-handshake) */
629/* ====================================================================== */
630
634/* Server-advertised description (Minor 2 / MCP 2025-11-25). NULL if
635 * the server didn't send one or the handshake hasn't completed. */
637
638/* The protocol version the server advertised at handshake. Per the MCP
639 * spec this may differ from CMCP_PROTOCOL_VERSION — negotiation is
640 * non-fatal, the handshake still succeeds. A host that requires an
641 * exact match should inspect this and disconnect on mismatch. Returns
642 * NULL before the handshake completes. */
644
645#endif
int cmcp_client_cancel(cmcp_client_t *c, long long id, const char *reason)
void cmcp_tool_result_clear(cmcp_tool_result_t *r)
void cmcp_client_free(cmcp_client_t *c)
cmcp_tool_result_t cmcp_client_tool_call(cmcp_client_t *c, const char *name, cmcp_json_t *args)
void cmcp_client_set_elicitation_handler(cmcp_client_t *c, cmcp_elicitation_handler_fn fn, void *userdata)
int cmcp_client_resources_list(cmcp_client_t *c, cmcp_session_resource_t **out_resources, size_t *out_n)
void(* cmcp_progress_fn)(double progress, double total, const char *message, void *userdata)
int(* cmcp_elicitation_handler_fn)(const cmcp_json_t *params, void *userdata, cmcp_json_t **out_result)
cmcp_tool_result_t cmcp_client_tool_wait(cmcp_tool_handle_t h)
int cmcp_client_handshake(cmcp_client_t *c, cmcp_transport_t *t)
cmcp_json_t * cmcp_sampling_text_result(const char *text, const char *model, const char *stop_reason)
void cmcp_client_set_capabilities(cmcp_client_t *c, const cmcp_client_capabilities_t *caps)
int cmcp_client_request(cmcp_client_t *c, const char *method, cmcp_json_t *params, cmcp_rpc_message_t *out_response)
cmcp_tool_handle_t cmcp_client_tool_call_async(cmcp_client_t *c, const char *name, cmcp_json_t *args)
const char * cmcp_client_server_protocol(const cmcp_client_t *c)
cmcp_json_t * cmcp_elicitation_result(const char *action, cmcp_json_t *content)
int cmcp_client_tools_list(cmcp_client_t *c, cmcp_session_tool_t **out_tools, size_t *out_n)
cmcp_tool_outcome_t
Definition cmcp_client.h:50
@ CMCP_TOOL_ERR_CANCELLED
Definition cmcp_client.h:54
@ CMCP_TOOL_ERR_TOOL_LEVEL
Definition cmcp_client.h:52
@ CMCP_TOOL_ERR_PROTOCOL
Definition cmcp_client.h:53
@ CMCP_TOOL_OK
Definition cmcp_client.h:51
void cmcp_client_set_sampling_handler(cmcp_client_t *c, cmcp_sampling_handler_fn fn, void *userdata)
int cmcp_client_prompts_list(cmcp_client_t *c, cmcp_session_prompt_t **out_prompts, size_t *out_n)
cmcp_client_t * cmcp_client_new(const char *name, const char *version)
int cmcp_client_resource_read(cmcp_client_t *c, const char *uri, char **out_text, size_t *out_n)
void cmcp_client_set_notification_handler(cmcp_client_t *c, cmcp_notification_fn fn, void *userdata)
int cmcp_client_call_async_progress(cmcp_client_t *c, const char *method, cmcp_json_t *params, cmcp_progress_fn fn, void *userdata, long long *out_id)
const cmcp_server_capabilities_t * cmcp_client_server_caps(const cmcp_client_t *c)
int(* cmcp_sampling_handler_fn)(const cmcp_json_t *params, void *userdata, cmcp_json_t **out_result)
const char * cmcp_client_server_name(const cmcp_client_t *c)
int cmcp_client_notify_roots_changed(cmcp_client_t *c)
int cmcp_client_prompt_get(cmcp_client_t *c, const char *name, cmcp_json_t *args, cmcp_json_t **out_messages)
struct cmcp_client cmcp_client_t
Definition cmcp_client.h:31
int cmcp_client_call_async(cmcp_client_t *c, const char *method, cmcp_json_t *params, long long *out_id)
const char * cmcp_client_server_version(const cmcp_client_t *c)
int cmcp_client_set_description(cmcp_client_t *c, const char *description)
int cmcp_client_tool_call_text(cmcp_client_t *c, const char *name, cmcp_json_t *args, char **out_text, cmcp_rpc_error_t **out_rpc_err)
int cmcp_client_wait(cmcp_client_t *c, long long id, cmcp_rpc_message_t *out_response)
int cmcp_client_set_log_level(cmcp_client_t *c, cmcp_log_level_t level)
int cmcp_client_notify(cmcp_client_t *c, const char *method, cmcp_json_t *params)
const char * cmcp_client_server_description(const cmcp_client_t *c)
int cmcp_client_connect_stdio(cmcp_client_t *c, const char *path, char *const argv[], char *const envp[])
void(* cmcp_notification_fn)(const char *method, const cmcp_json_t *params, void *userdata)
int cmcp_client_set_roots(cmcp_client_t *c, const cmcp_root_t *roots, size_t n)
Multi-server primitive aggregator on top of N cMCP clients.
Transport vtable + stdio/HTTP constructors.
JSON-RPC 2.0 message shapes, capability structs, dispatch types.
cmcp_log_level_t
Definition cmcp_types.h:56
const char * name
const char * uri
cmcp_client_t * client
cmcp_json_t * result
Definition cmcp_client.h:84
cmcp_tool_outcome_t outcome
Definition cmcp_client.h:83
cmcp_rpc_error_t * error
Definition cmcp_client.h:86