![]() |
boost::taar::mcp::endpoint — An MCP (Model Context Protocol) server endpoint over Streamable HTTP.
// In header: <boost/taar/mcp/endpoint.hpp> template<typename Session = stateless, typename... Context> class endpoint { public: // types typedef handler::contexts< Context... > incoming_contexts; typedef std::conditional_t< stateful, handler::contexts< Context..., Session, session_info >, handler::contexts< Context... > > contexts_type; typedef std::conditional_t< stateful, jsonrpc::endpoint< Context..., Session, session_info >, jsonrpc::endpoint< Context... > > rpc_type; typedef unspecified session_handle; // Handle to a live session: the store entry in stateful mode,nullptrotherwise. typedef std::conditional_t< stateful, handler::contexts< Context..., Session, session_info, jsonrpc::request_scope >, handler::contexts< Context..., jsonrpc::request_scope > > call_contexts; // Contexts a tool or resource callable may request: the session ones plus the per-request jsonrpc::request_scope. typedef boost::beast::http::request< boost::beast::http::string_body > request_type; typedef boost::beast::http::response< boost::beast::http::string_body > response_type; // member classes/structs/unions // The HTTP handler for the MCP route (POST, andDELETEin stateful mode). class handler_type { public: // types typedef endpoint::request_type request_type; // public member functions explicit handler_type(endpoint *); template<typename Contexts> awaitable< response_type > operator()(Contexts const &, request_type const &, matcher::context const &); // public data members static constexpr bool context_aware; }; // public member functions explicit endpoint(server_info); endpoint(endpoint const &) = delete; endpoint & operator=(endpoint const &) = delete; template<typename Callable, typename... Providers> endpoint & tool(std::string, std::string, Callable &&, Providers...); template<typename Callable, typename... Providers> endpoint & tool(tool_spec, Callable &&, Providers...); template<typename Completer> endpoint & complete_prompt(std::string, std::string, Completer); template<typename Completer> endpoint & complete_resource(std::string, std::string, Completer); template<typename Callable, typename... Providers> endpoint & resource(std::string, std::string, std::string, Callable &&, Providers...); template<typename Callable, typename... Providers> endpoint & prompt(std::string, std::string, Callable &&, Providers...); std::vector< prompt_info > prompts() const; std::size_t resource_updated(std::string const &); std::size_t tools_changed(); std::size_t resources_changed(); std::size_t prompts_changed(); template<typename F> endpoint & on_initialize(F); awaitable< boost::json::value > request(std::string const &, std::string, boost::json::value = {}, std::chrono::steady_clock::duration = {}); awaitable< boost::json::value > create_message(std::string const &, boost::json::object, std::chrono::steady_clock::duration = {}); awaitable< boost::json::value > elicit(std::string const &, std::string, boost::json::object, std::chrono::steady_clock::duration = {}); awaitable< boost::json::value > list_roots(std::string const &, std::chrono::steady_clock::duration = {}); void on_client_notification(std::string, std::function< void(contexts_type const &, boost::json::value const &)>); void set_tool_error_handler(tool_error_handler); void set_origin_validator(std::function< bool(std::string_view)>); rpc_type & rpc() noexcept; rpc_type const & rpc() const noexcept; server_info const & info() const noexcept; std::vector< tool_info > tools() const; std::vector< resource_info > resources() const; std::size_t session_count() const; std::size_t sweep_sessions(); bool notify(std::string const &, std::string_view, boost::json::value = {}); bool log(std::string const &, log_level, boost::json::value, std::string = {}); std::size_t log_all(log_level, boost::json::value, std::string = {}); std::size_t broadcast(std::string_view, boost::json::value = {}); auto stream_handler(); handler_type handler(); awaitable< response_type > handle(incoming_contexts const &, request_type const &, matcher::context const &); // public data members static constexpr bool stateful; };
Built on jsonrpc::endpoint. Register tools and resources, then register handler() on a session route for POST (and DELETE in stateful mode). The endpoint implements the protocol's lifecycle (initialize, notifications/initialized, ping), discovery (tools/list, resources/list, resources/templates/list) and the tools/call / resources/read dispatch, plus the HTTP-level rules of the transport (Mcp-Session-Id, MCP-Protocol-Version, Origin). Custom JSON-RPC methods can be added through rpc().
taar::mcp::endpoint<calc_session, conn_stats> mcp { {.name = "calc-server", .version = "1.0.0"}}; mcp.tool("add", "Add two integers", &add, mcp_arg("a"), mcp_arg("b")); mcp.resource("calc://history/last", "text/plain", "Most recent result", &last); session.register_request_handler(target == "/mcp", mcp.handler());
Behaviour notes:
Protocol version: the client's requested version is echoed when supported, otherwise the newest supported version is offered. A request carrying an unsupported MCP-Protocol-Version header is rejected with 400.
JSON-RPC batches are not accepted on this transport (-32600).
A JSON-RPC response or notification posted by the client is acknowledged with 202 and no body.
Tool callables that throw produce an isError: true result; see set_tool_error_handler. Argument errors and unknown tools are JSON-RPC errors (-32602). Failures carrying an MCP code with a wire code of its own (error::resource_not_found, error::request_cancelled) are sent with that code.
GET opens the server-to-client event stream (stateful mode only) when stream_handler is registered for it; otherwise 405. Messages queued with notify or broadcast are delivered as SSE message events on that stream.
The same endpoint can serve the stdio transport with serve_stdio() (newline-delimited JSON-RPC on stdin/stdout, one implicit session, notifications written to stdout).
typename Session = stateless
Per-session state type. stateless (the default) disables sessions: no Mcp-Session-Id header is issued or required. Any other type enables stateful mode: a session is created on initialize, identified by Mcp-Session-Id, serialised (one call at a time), and removed by DELETE or after the idle timeout.
typename... Context
Context types forwarded from the HTTP session (the connection context, keyed contexts from wrappers). Tool and resource callables may request any of these and, in stateful mode, the Session type and session_info (the session's id).
endpoint public member functionsexplicit endpoint(server_info info);
endpoint(endpoint const &) = delete;
endpoint & operator=(endpoint const &) = delete;
template<typename Callable, typename... Providers> endpoint & tool(std::string name, std::string description, Callable && callable, Providers... providers);Register a tool.
Leading context parameters are injected; the rest come from mcp_arg providers. The callable may return a plain value (rendered as text plus structuredContent) or one of the typed results in <boost/taar/mcp/content.hpp>: tool_result, image, audio, embedded_resource or text.
template<typename Callable, typename... Providers> endpoint & tool(tool_spec spec, Callable && callable, Providers... providers);Register a tool with a full tool_spec (title, annotations, output schema).
template<typename Completer> endpoint & complete_prompt(std::string prompt, std::string argument, Completer completer);Register a completer for a prompt argument (
completion/complete with ref/prompt). completer is called with a completion_request, or just the typed value as a std::string_view, and returns a completion_result or a std::vector<std::string>.
template<typename Completer> endpoint & complete_resource(std::string uri_template, std::string argument, Completer completer);Register a completer for a resource template argument (
completion/complete with ref/resource). template<typename Callable, typename... Providers> endpoint & resource(std::string uri, std::string mime_type, std::string description, Callable && callable, Providers... providers);Register a resource by fixed URI or URI template (
{name} placeholders bound with uri_arg). template<typename Callable, typename... Providers> endpoint & prompt(std::string name, std::string description, Callable && callable, Providers... providers);Register a prompt.
Arguments are bound with mcp_arg from params.arguments (prompt arguments are strings). The callable may return a std::string (one user text message), a boost::json::array of messages, or an object with messages (and optional description).
std::vector< prompt_info > prompts() const;
std::size_t resource_updated(std::string const & uri);Tell every session subscribed to uri that the resource changed (
notifications/resources/updated). Returns the number notified.
std::size_t tools_changed();Broadcast
notifications/tools/list_changed to every session. std::size_t resources_changed();Broadcast
notifications/resources/list_changed to every session. std::size_t prompts_changed();Broadcast
notifications/prompts/list_changed to every session. template<typename F> endpoint & on_initialize(F f);Hook run on every
initialize. In stateful mode the callable is (Session&, initialize_params const&) and runs on the freshly created session; if it throws, the session is discarded and the client receives an error. In stateless mode it is (initialize_params const&).
awaitable< boost::json::value > request(std::string const & session_id, std::string method, boost::json::value params = {}, std::chrono::steady_clock::duration timeout = {});Send a JSON-RPC request to one session's client and await its reply (stateful mode).
The request travels over the session's GET stream (or stdout on stdio); the client answers by posting a JSON-RPC response to the endpoint. Returns the result. Failures throw boost::taar::exception whose failure carries
error::client_error when the client answers with an error: the client's message, and data {"code": <client code>, "data": <client data or null>};
error::unknown_session if the session is unknown;
errc::timed_out after timeout;
errc::operation_canceled if the session ends first.
Cancelling the awaiting coroutine (for instance through request_scope) abandons the request.
Usable from a tool: it does not need the session lock, which the calling tool already holds. A zero timeout means server_info::request_timeout.
awaitable< boost::json::value > create_message(std::string const & session_id, boost::json::object params, std::chrono::steady_clock::duration timeout = {});
sampling/createMessage: ask the client's model for a completion. params is the protocol's request object. awaitable< boost::json::value > elicit(std::string const & session_id, std::string message, boost::json::object requested_schema, std::chrono::steady_clock::duration timeout = {});
elicitation/create: ask the user for structured input. requested_schema is a flat JSON Schema object describing the fields. The result is the protocol's {action, content} object, where action is accept, decline or cancel.
awaitable< boost::json::value > list_roots(std::string const & session_id, std::chrono::steady_clock::duration timeout = {});
roots/list: the client's filesystem roots, as the protocol's {roots: [...]} object. void on_client_notification(std::string method, std::function< void(contexts_type const &, boost::json::value const &)> handler);Handle a notification sent by the client, such as
notifications/roots/list_changed. Runs under the session lock with the session contexts; the second argument is params.
void set_tool_error_handler(tool_error_handler h);Replace how failed tool calls are reported.
See default_tool_error_handler.
h is invoked as h(taar::failure const&) and returns the isError text, or nullopt to send a JSON-RPC error instead. A handler that throws falls back to the default.
endpoint.set_tool_error_handler([](taar::failure const& f) -> std::optional<std::string> { if (f.as<division_by_zero>()) return std::nullopt; // a JSON-RPC error return mcp::default_tool_error_handler(f); });
void set_origin_validator(std::function< bool(std::string_view)> v);Install an
Origin validator. Requests whose Origin fails it get 403. rpc_type & rpc() noexcept;The underlying JSON-RPC endpoint, for custom methods and the error mapper.
rpc_type const & rpc() const noexcept;
server_info const & info() const noexcept;
std::vector< tool_info > tools() const;
std::vector< resource_info > resources() const;
std::size_t session_count() const;Number of live sessions (stateful mode).
std::size_t sweep_sessions();Remove idle sessions now (stateful mode). Also runs opportunistically on access.
bool notify(std::string const & session_id, std::string_view method, boost::json::value params = {});Queue a JSON-RPC notification for one session's event stream (stateful mode).
Delivered when the client has a GET stream open; otherwise held until it opens one or the session ends. Does not refresh the session's idle timer.
Returns: |
false if the session is unknown or expired. |
bool log(std::string const & session_id, log_level level, boost::json::value data, std::string logger = {});Queue a notification for every live session (stateful mode). Returns the number queued.
Send a notifications/message log entry to one session (stateful mode).
Dropped, returning false, when level is below the level the client set with logging/setLevel (initially server_info::default_log_level) or the session is unknown.
std::size_t log_all(log_level level, boost::json::value data, std::string logger = {});
log() to every session; returns how many accepted the entry. std::size_t broadcast(std::string_view method, boost::json::value params = {});Send the notification method with params to every session; returns how many sessions it was queued for.
auto stream_handler();The HTTP handler for the
GET event stream (stateful mode). Register it on the same route as handler() for GET:
session.register_request_handler(method == http::verb::get && target == "/mcp", mcp.stream_handler()); session.register_request_handler(target == "/mcp", mcp.handler());
The response is text/event-stream; one stream per session (a second GET while one is open is answered 409). The stream ends when the session is deleted or expires.
handler_type handler();Returns the handler to register. The endpoint must outlive the session.
awaitable< response_type > handle(incoming_contexts const & incoming, request_type const & request, matcher::context const & mctx);Handle one HTTP request addressed to the MCP route.