#pragma once
#include <mw/url.hpp>
#include "message.hpp"
#include "tool.hpp"
namespace agent_smith::mcp
{
/// An unsolicited server notification.
struct Notification
{
/// JSON-RPC notification method.
std::string method;
/// Original notification parameters.
nlohmann::json params;
};
/// Observable connection transitions.
enum class LifecycleEventKind
{
/// Initialization and discovery succeeded.
CONNECTED,
/// Transport failed or explicit shutdown began.
DISCONNECTED,
/// A replacement connection is being initialized.
RECONNECTING,
/// Replacement connection could not be initialized.
RECONNECT_FAILED
};
/// A connection transition for one configured server.
struct LifecycleEvent
{
/// Transition being reported.
LifecycleEventKind kind;
/// Configured server alias.
std::string server_alias;
/// Human-readable transition context.
std::string message;
};
/// Server notification or connection transition.
using Event = std::variant<Notification, LifecycleEvent>;
/// Best-effort observer; must not block or reenter the client.
using EventHook = std::function<void(const Event&)>;
/// JSON-RPC request envelope.
struct Request
{
/// Client-generated request identifier.
std::int64_t id;
/// Method being invoked.
std::string method;
/// Method parameters.
nlohmann::json params;
};
/// Remote JSON-RPC failure.
struct RpcError
{
/// Server's JSON-RPC error code.
int code;
/// Server's error description.
std::string message;
/// Optional original error details.
nlohmann::json data;
};
/// JSON-RPC response envelope.
struct Response
{
/// Identifier of the request being answered.
std::int64_t id;
/// Result payload or remote error.
std::variant<nlohmann::json, RpcError> body;
};
/// Server identity and original metadata.
struct ServerInfo
{
/// Reported implementation name.
std::string name;
/// Reported implementation version.
std::string version;
/// Original serverInfo object.
nlohmann::json raw;
};
/// One advertised resource.
struct ResourceInfo
{
/// Identifier passed to resources/read.
std::string uri;
/// Human-readable resource name.
std::string name;
/// Optional server description.
std::optional<std::string> description;
/// Optional advertised media type.
std::optional<std::string> mime_type;
/// Original resource descriptor.
nlohmann::json raw;
};
/// One advertised prompt template.
struct PromptInfo
{
/// Name passed to prompts/get.
std::string name;
/// Optional server description.
std::optional<std::string> description;
/// Original argument descriptors.
nlohmann::json arguments = nlohmann::json::array();
/// Original prompt descriptor.
nlohmann::json raw;
};
/// Text-only rendered prompt and original response.
struct PromptRendering
{
/// Rendered user and assistant text messages.
std::vector<Message> messages;
/// Original prompts/get response.
nlohmann::json raw;
};
/// Supported transport selectors; HTTP arrives in milestone 15.
enum class McpTransportKind
{
/// A child process using newline-delimited JSON on stdin/stdout.
STDIO,
/// Reserved for the future HTTP transport.
STREAMABLE_HTTP
};
/// Executable and arguments, without shell interpretation.
struct StdioServerConfig
{
/// Executable followed by arguments; passed directly without a shell.
std::vector<std::string> argv;
};
/// Streamable HTTP endpoint configuration.
struct HttpServerConfig
{
/// Future HTTP endpoint; not used by stdio.
mw::URL endpoint;
};
/// Connection, discovery, filtering and timeout policy.
struct ServerConfig
{
/// Unique alias used in local tool names; letters, digits, '_' and '-'.
std::string alias;
/// Transport selector; must match transport's active variant.
McpTransportKind kind = McpTransportKind::STDIO;
/// Transport-specific configuration.
std::variant<StdioServerConfig, HttpServerConfig> transport;
/// Reserved HTTP authentication; not used by stdio.
std::optional<std::string> bearer_token;
/// Reserved HTTP headers; not used by stdio.
std::vector<std::pair<std::string, std::string>> extra_headers;
/// Default budget for initialization, discovery, resources and prompts.
std::chrono::milliseconds request_timeout{30000};
/// Allowed remote tool names; empty permits all non-denied names.
std::vector<std::string> allow_tools;
/// Excluded remote names; takes precedence over allow_tools.
std::vector<std::string> deny_tools;
/// Fail connection when tools/list is unavailable.
bool tools_required = true;
/// Reconnect once for read-only failure or a later operation after loss.
bool reconnect_on_transport_error = true;
};
/// Bidirectional framed transport. Runtime must outlive the transport.
class Transport
{
public:
/// Release transport resources.
virtual ~Transport() = default;
/// Send one frame within an absolute deadline. A failed write may have
/// delivered a prefix and must not be automatically replayed.
virtual mw::Task<mw::E<void>>
send(nlohmann::json frame,
std::chrono::steady_clock::time_point deadline =
std::chrono::steady_clock::now() + std::chrono::milliseconds(100),
CancellationToken cancel = {}) = 0;
/// Receive one frame or a typed failure.
virtual mw::Task<mw::E<nlohmann::json>> recv() = 0;
/// Wake an idle receiver without closing descriptors during active I/O.
virtual void interrupt() noexcept = 0;
/// Stop I/O and reap the child.
virtual mw::Task<mw::E<void>> close() = 0;
};
/// Concurrent MCP requests over one stdio connection.
/// Runtime must outlive this client and all in-flight calls.
/// Discovery getters must not be used while a reconnect is in progress.
class Client
{
public:
/// Initialize and discover a server before returning it.
static mw::Task<mw::E<std::unique_ptr<Client>>>
connect(Runtime& runtime, ServerConfig config, EventHook hook = {});
/// Close owned transport, including when shutdown was omitted.
~Client();
/// Negotiated server metadata.
const ServerInfo& info() const;
/// Filtered tool schemas, retaining original remote names.
const std::vector<ToolSchema>& tools() const;
/// Advertised resources.
const std::vector<ResourceInfo>& resources() const;
/// Advertised prompt templates.
const std::vector<PromptInfo>& prompts() const;
/// Stable configured server alias.
const std::string& alias() const;
/// Invoke a tool; never automatically replay a written request.
mw::Task<mw::E<ToolResult>> callTool(std::string name, nlohmann::json args,
std::chrono::milliseconds timeout,
CancellationToken cancel = {});
/// Read text resources; binary contents are unsupported.
mw::Task<mw::E<std::string>> readResource(
std::string uri, CancellationToken cancel = {},
std::optional<std::chrono::milliseconds> timeout = std::nullopt);
/// Render a text-only prompt without applying it to an agent.
mw::Task<mw::E<PromptRendering>> getPrompt(std::string name,
nlohmann::json args);
/// Replace the notification/lifecycle observer.
void setEventHook(EventHook hook);
/// Close connection and wake pending operations.
mw::Task<mw::E<void>> shutdown();
private:
struct State;
explicit Client(std::shared_ptr<State> state);
static mw::Task<mw::E<void>> receive(std::shared_ptr<State> state);
mw::Task<mw::E<nlohmann::json>> request(std::string method,
nlohmann::json params,
std::chrono::milliseconds timeout,
CancellationToken cancel = {},
bool allow_reconnect = true);
mw::Task<mw::E<void>> discover();
mw::Task<mw::E<void>> reconnect(std::shared_ptr<State> previous);
std::mutex state_mutex_;
bool reconnecting_ = false;
bool stopped_ = false;
std::string alias_;
std::chrono::milliseconds default_timeout_;
std::shared_ptr<State> state_;
ServerInfo info_;
std::vector<ToolSchema> tools_;
std::vector<ResourceInfo> resources_;
std::vector<PromptInfo> prompts_;
};
/// Adapt all remote tools and a resource reader; retain the client safely.
std::vector<std::unique_ptr<Tool>> adaptTools(std::shared_ptr<Client> client);
} // namespace agent_smith::mcp