mcp

package
v0.1.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 15, 2026 License: Apache-2.0 Imports: 19 Imported by: 0

Documentation

Overview

Package mcp serves a set of the agent's tools to a Model Context Protocol client over a JSON-RPC 2.0 stdio transport, so an external program (another agent's harness, an editor, any MCP client) can call the agent's tools without being handed the host directly.

The point of the server is where a called tool runs: every tools/call is routed through the same dispatch waist as a native loop, so the caller's effects are admitted against the run's capability grant, gated by the containment level its trust requires, subject to the safety brakes, and recorded on the event spine. Presence in tools/list makes a tool reachable, never automatically permitted: authority is decided at the waist at call time, exactly as it is for the agent's own loop. A client that is denied sees an ordinary error tool result and can adapt, while the denial lands on the spine as a rejected action.

The server is a pure protocol and routing layer. It holds no grant and no brake of its own: the governance bindings ride on the context passed to Serve (the caller binds the run's grant with capability.Into and the run id with brakes.Into, the same way the mission executor does before it dispatches), and the server propagates that context into every Govern call. So a host that wants a read-only session binds a narrow grant; a host that wants a kill-switch binds the brakes; the server needs no knowledge of either.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type CallResult added in v0.1.3

type CallResult struct {
	Text    string
	IsError bool
}

CallResult is the outcome of a tools/call: the tool's textual output and whether the server marked it a tool-level error. A tool-level error (IsError) is a normal result the calling model sees and adapts to, distinct from a transport or protocol failure, which surfaces as the error return of CallTool. Text is UNTRUSTED extension output; a consumer size-bounds and masks it before it reaches a model or the spine.

type Client added in v0.1.3

type Client struct {
	// contains filtered or unexported fields
}

Client is the MCP consumer half: it speaks JSON-RPC 2.0 over a byte stream to an MCP server (an extension subprocess reached over its stdio pipes) and turns its advertised tools into calls. It is transport-agnostic on purpose: it reads from any io.Reader and writes to any io.Writer, so the sandbox launch that supplies the subprocess pipes and the client that talks over them stay decoupled, and the protocol logic is testable against an in-memory pair with no process at all.

The client is hardened against a hostile or broken peer, since an extension is treated as potentially compromised: reads are size-bounded (a reply cannot exhaust memory), every request carries a deadline through its context (a peer that never answers cannot wedge a call), replies are matched to their request by id (a spurious or duplicate id is ignored, never mismatched to a waiting call), and a dead transport fails every in-flight and future call closed rather than hanging.

The channel is strictly one-directional: flynn only ever initiates requests and only ever consumes their replies. A message the server ORIGINATES (a sampling or elicitation request, a notification) is dropped, never dispatched and never answered, so an extension can send tools and results but can never call back into flynn. This is the structural answer to "consuming MCP adds a surface": the surface is a client that speaks three methods and refuses everything the server tries to drive.

func NewClient added in v0.1.3

func NewClient(r io.Reader, w io.Writer, opts ...ClientOption) *Client

NewClient starts a client reading replies from r and writing requests to w, and launches the background read loop. The caller must call Close when done (and should defer it), which stops the read loop and fails any in-flight call; Close does not close r or w, whose lifetimes belong to whoever supplied them (the sandbox process handle). A client is safe for concurrent use.

func (*Client) CallTool added in v0.1.3

func (c *Client) CallTool(ctx context.Context, name string, args json.RawMessage) (CallResult, error)

CallTool invokes one tool by its own (un-namespaced) name with the given arguments and returns the tool's textual output. A tool-level failure the server reports is returned as a CallResult with IsError set, not as an error; the error return is reserved for a transport or protocol failure (a timeout, a dead peer, a malformed reply). The returned text is untrusted and must be bounded and masked by the caller before use.

func (*Client) Close added in v0.1.3

func (c *Client) Close() error

Close stops the client and fails any in-flight call. It is idempotent and does not close the underlying reader or writer.

func (*Client) Initialize added in v0.1.3

func (c *Client) Initialize(ctx context.Context) (Info, error)

Initialize performs the MCP handshake and returns the server's reported identity. It must be called once before ListTools or CallTool. The negotiated protocol version is checked for presence only; a server that answers the handshake at all is accepted, and the tools it later advertises are validated per call.

func (*Client) ListTools added in v0.1.3

func (c *Client) ListTools(ctx context.Context) ([]ToolDesc, error)

ListTools asks the server for every tool it exposes. The names, descriptions, and schemas returned are untrusted extension data: the caller namespaces the names and bounds and masks the descriptions before any of it reaches a model or the spine.

type ClientOption added in v0.1.3

type ClientOption func(*Client)

ClientOption configures a Client at construction.

func WithClientInfo added in v0.1.3

func WithClientInfo(i Info) ClientOption

WithClientInfo sets the identity the client reports in the initialize handshake. A zero Info reports a default name and empty version.

type Info

type Info struct {
	Name    string `json:"name"`
	Version string `json:"version"`
}

Info identifies a party in the initialize handshake: the server's name and version reported to the client, and the shape the client reports back.

type Option

type Option func(*Server)

Option configures a Server at construction.

func WithGoal

func WithGoal(id string) Option

WithGoal sets the goal id every governed tool call runs under, so the actions a client drives are threaded onto the same goal as the run that hosts the server. Empty means the calls belong to no specific goal.

func WithInfo

func WithInfo(i Info) Option

WithInfo sets the server identity reported in the initialize handshake. A zero Info reports a default name and an empty version.

func WithScope

func WithScope(s state.Scope) Option

WithScope sets the scope every governed tool call is attributed to on the spine, so a session's actions are located on the instance/project/workspace axis like any native action. The zero scope is the global scope.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server exposes a fixed set of mission.Tools to one MCP client connection and governs every call through a dispatch waist. Construct it with NewServer and run it with Serve. It is safe to reuse across sequential connections, but a single Serve call drives one connection.

func NewServer

func NewServer(d *dispatch.Dispatcher, tools []mission.Tool, opts ...Option) *Server

NewServer builds a server that serves tools through d. Tools are keyed by name; a later tool with a duplicate name replaces an earlier one, and tools/list reports each name once in first-registration order. A nil dispatcher is replaced with a zero-config one (allow-all, discard sink), so the server is usable standalone, though a real host passes a governed dispatcher.

func (*Server) HTTPHandler

func (s *Server) HTTPHandler(base context.Context, token string) http.Handler

HTTPHandler adapts the server to the Model Context Protocol streamable-HTTP transport, so a client that connects to a URL rather than spawning a subprocess (an in-process loopback bridge is the motivating case) drives the same governed tools over one HTTP endpoint. Each POST carries one JSON-RPC message and gets its reply as a single application/json body; the tools here answer synchronously, so the handler never needs to open a server-sent-event stream.

base is the governed context the handler dispatches under: the caller binds the run's capability grant with capability.Into and the run id with brakes.Into before constructing the handler, exactly as for the stdio Serve path, and every request is served on a context derived from base so a run-level halt or shutdown cancels an in-flight call. token, when non-empty, is a bearer token the client must present, so a loopback port a co-tenant could also reach is not open to it.

func (*Server) Serve

func (s *Server) Serve(ctx context.Context, r io.Reader, w io.Writer) error

Serve reads newline-delimited JSON-RPC messages from r, dispatches each, and writes replies to w, until r reaches EOF or ctx is cancelled. It returns nil on a clean client disconnect (EOF) or context cancellation, and a wrapped error only on an unrecoverable transport failure. Governance bindings on ctx (the run's grant and brakes) are propagated into every tool call; a caller that wants a governed session binds them before calling Serve.

type ToolDesc added in v0.1.3

type ToolDesc struct {
	// Name is the tool's own name as the server reports it, before any namespacing.
	Name string
	// Description is the model-facing text the server supplies. Untrusted; treat as data.
	Description string
	// InputSchema is the JSON Schema for the tool's arguments, or nil when the server
	// declares none (the consumer substitutes an empty-object schema).
	InputSchema json.RawMessage
}

ToolDesc is one tool as an extension server advertises it in tools/list: its name, the description the model reads to decide when to call it, and the JSON Schema for its arguments. It is the client-side view of the wire toolDef, kept separate so the consumer package need not reach into the server's unexported types. Everything in a ToolDesc is UNTRUSTED data authored by the extension: a consumer namespaces the name, and size-bounds and masks the description before it enters any model context or the signed spine (anti tool-poisoning).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL