#ifndef LLSH_MCP_H #define LLSH_MCP_H #include "util.h" /* Mirrors mcp.rs: a minimal Model Context Protocol client. * mcpServers entries are either stdio (command/args/env) or HTTP (url/headers). * Transport framing is JSON-RPC 2.0: one message per line over stdio, a single * request/response (plain JSON or SSE) over HTTP. A wedged server must not hang * the agent: every call is bounded by MCP_TIMEOUT_SECS. */ #define MCP_TIMEOUT_SECS 120L #define MCP_PROTOCOL_VERSION "2024-11-05" /* Effective per-call timeout. MCP_TIMEOUT_SECS unless the LLSH_MCP_TIMEOUT * environment variable sets a positive whole number of seconds. */ long mcp_timeout_secs(void); typedef struct { char *key; char *val; } mcp_kv; /* One mcpServers entry. All fields owned (NULL/0 when absent). */ typedef struct { char *command; char **args; size_t arg_count; mcp_kv *env; size_t env_count; char *url; mcp_kv *headers; size_t header_count; } mcp_server_config; typedef struct mcp_client mcp_client; /* A tool advertised by a server's tools/list. client is a back-reference. */ typedef struct { char *name; /* remote name */ char *qualified_name; /* exposed as mcp_____ */ char *description; char *input_schema; /* JSON text */ mcp_client *client; } mcp_tool; /* Everything connected: the flattened tool list plus the owning clients. */ typedef struct { mcp_tool *tools; size_t tool_count; mcp_client **clients; size_t client_count; } mcp_registry; /* Test-only loopback transport: queued responses + recorded outgoing lines. * Call mcp_memory_init before use; the caller keeps ownership, the client * borrows it for the lifetime of the connection. */ typedef struct { strlist responses; strlist sent; } mcp_memory; void mcp_memory_init(mcp_memory *m); /* Parse a raw mcpServers value (JSON text). Returns 1 and fills *out, or 0 * (no error) when it declares neither a command nor a url. */ int mcp_server_config_from_json(const char *json, mcp_server_config *out); void mcp_server_config_free(mcp_server_config *c); /* Spawn/attach a server. Returns NULL with *err on failure. */ mcp_client *mcp_connect_stdio(const mcp_server_config *cfg, char **err); mcp_client *mcp_connect_http(const mcp_server_config *cfg, char **err); /* Test-only: wrap already-open stdio fds (closed on free, no child reaped). */ mcp_client *mcp_connect_fds(int in_fd, int out_fd, char **err); /* Test-only: one buffered line read with carry semantics. */ char *mcp_read_line(mcp_client *c, const char *method, char **err); mcp_client *mcp_connect_memory(mcp_memory *mem, char **err); void mcp_client_free(mcp_client *c); /* Handshake: initialize request + notifications/initialized notification. */ int mcp_initialize(mcp_client *c, char **err); /* Discover the server's tools. Caller frees with mcp_tools_free (does not * free the clients). */ int mcp_list_tools(mcp_client *c, mcp_tool **tools, size_t *n, char **err); void mcp_tools_free(mcp_tool *tools, size_t n); /* Invoke a tool; returns its text content or NULL with *err set. */ char *mcp_call_tool(mcp_client *c, const char *name, const char *args_json, char **err); /* Low-level JSON-RPC: returns the `result` value as JSON text (owned), or * NULL with *err ("server error {code}: {message}" on JSON-RPC errors). */ char *mcp_request(mcp_client *c, const char *method, const char *params_json, char **err); int mcp_notify(mcp_client *c, const char *method, const char *params_json, char **err); /* Extract the JSON-RPC response from an HTTP body (plain or SSE). */ char *mcp_extract_json(const char *content_type, const char *body, char **err); /* Connect, handshake, and discover every server, in order. Unreachable * servers become warnings (`mcp \`name\`: reason`) and are skipped. */ void mcp_connect_all(const char *const *names, const char *const *configs, size_t n, mcp_registry *out, strlist *warnings); void mcp_registry_free(mcp_registry *r); /* Dispatch through the owning client, wrapping failures as * `MCP tool \`name\` failed: ...`. */ char *mcp_tool_invoke(const mcp_tool *t, const char *args_json, char **err); #endif