Documentation
¶
Overview ¶
Package backend defines the transport-agnostic connection to one MCP server (stdio subprocess or remote streamable-http endpoint).
Index ¶
- Constants
- Variables
- type Backend
- type ErrUnauthorized
- type Factory
- type RemoteBackend
- func (b *RemoteBackend) Close() error
- func (b *RemoteBackend) Done() <-chan struct{}
- func (b *RemoteBackend) Err() error
- func (b *RemoteBackend) Name() string
- func (b *RemoteBackend) Recv() <-chan []byte
- func (b *RemoteBackend) Send(ctx context.Context, msg []byte) error
- func (b *RemoteBackend) SessionID() string
- func (b *RemoteBackend) Start(_ context.Context) error
- type SSEBackend
- func (b *SSEBackend) Close() error
- func (b *SSEBackend) Done() <-chan struct{}
- func (b *SSEBackend) Err() error
- func (b *SSEBackend) Name() string
- func (b *SSEBackend) Recv() <-chan []byte
- func (b *SSEBackend) Send(ctx context.Context, msg []byte) error
- func (b *SSEBackend) Start(ctx context.Context) error
- type StdioBackend
- func (b *StdioBackend) Close() error
- func (b *StdioBackend) Done() <-chan struct{}
- func (b *StdioBackend) Err() error
- func (b *StdioBackend) Name() string
- func (b *StdioBackend) Recv() <-chan []byte
- func (b *StdioBackend) Send(_ context.Context, msg []byte) error
- func (b *StdioBackend) Start(ctx context.Context) error
Constants ¶
const ( TransportStdio = "stdio" TransportStreamableHTTP = "streamable-http" // TransportSSE is the legacy HTTP+SSE transport (2024-11-05): GET // event stream + POST-to-endpoint. Prefer streamable-http; several // hosted servers (Atlassian, Monday, Square…) still require this. TransportSSE = "sse" )
Transport identifiers accepted in config.Backend.Transport.
Variables ¶
var ErrClosed = errors.New("backend closed")
ErrClosed is returned by Send after the backend terminated.
Functions ¶
This section is empty.
Types ¶
type Backend ¶
type Backend interface {
// Name is the configured backend name (used as tool prefix in
// aggregation and as the audit backend label).
Name() string
// Start establishes the connection: it spawns the child or probes the
// remote. Call it once before Send.
Start(ctx context.Context) error
// Send delivers one JSON-RPC message (a complete envelope) to the
// server.
Send(ctx context.Context, msg []byte) error
// Recv yields messages from the server. The channel closes when the
// backend terminates.
Recv() <-chan []byte
// Done closes when the backend has terminated for any reason.
Done() <-chan struct{}
// Err reports the termination cause after Done closes; nil on clean
// shutdown.
Err() error
// Close terminates the connection and reaps any subprocess. Idempotent.
Close() error
}
Backend is one live connection to an MCP server. One instance serves one MCP session, and the gateway serializes Send calls per session.
type ErrUnauthorized ¶
ErrUnauthorized reports a 401 from a remote backend. Callers unwrap it to read the WWW-Authenticate challenge, which carries the resource-metadata URL that drives OAuth discovery (RFC 9728).
func (*ErrUnauthorized) Error ¶
func (e *ErrUnauthorized) Error() string
type Factory ¶
Factory builds a Backend for one session from its configuration.
func NewFactory ¶
func NewFactory(name string, cfg config.Backend, ts func(ctx context.Context) (string, error)) Factory
NewFactory returns a Factory building one unstarted Backend per session from cfg.
ts, when non-nil, mints the outbound bearer token for each request to a remote backend. Static, passthrough and OAuth outbound auth all plug in here. Stdio backends ignore it and inherit credentials through their environment instead.
Configuration errors (unknown transport, missing command/url) surface from the returned Factory rather than from NewFactory, so a misconfigured backend fails its own session instead of the whole daemon's wiring. config.Validate already rejects the same conditions.
type RemoteBackend ¶
type RemoteBackend struct {
// TokenSource, when non-nil, mints the outbound bearer token for each
// request. Passthrough and OAuth outbound auth plug in here, and the
// minted token overrides any Authorization in the static headers.
TokenSource func(ctx context.Context) (string, error)
// Client overrides the per-instance HTTP client. Set before Start.
Client *http.Client
// contains filtered or unexported fields
}
RemoteBackend speaks MCP streamable-HTTP: every client message is POSTed to a single endpoint, and the remote answers with one JSON object or with an SSE stream carrying several messages. Send normalizes both shapes into the Recv channel, so callers cannot tell them apart.
func NewRemote ¶
NewRemote builds an unstarted streamable-HTTP backend. cfg.URL must be set; cfg.Headers are sent verbatim on every request.
func (*RemoteBackend) Close ¶
func (b *RemoteBackend) Close() error
Close stops accepting new traffic, aborts in-flight streams and closes Recv. Idempotent.
func (*RemoteBackend) Done ¶
func (b *RemoteBackend) Done() <-chan struct{}
func (*RemoteBackend) Err ¶
func (b *RemoteBackend) Err() error
Err always returns nil. A remote backend has no asynchronous failure mode of its own: transport errors surface from Send, and only Close closes Done.
func (*RemoteBackend) Name ¶
func (b *RemoteBackend) Name() string
func (*RemoteBackend) Recv ¶
func (b *RemoteBackend) Recv() <-chan []byte
func (*RemoteBackend) Send ¶
func (b *RemoteBackend) Send(ctx context.Context, msg []byte) error
Send POSTs one JSON-RPC message and routes whatever comes back into Recv. It returns once it has read the response headers and, for JSON responses, consumed the body. A background goroutine drains SSE bodies so a long tool call does not block the caller.
func (*RemoteBackend) SessionID ¶
func (b *RemoteBackend) SessionID() string
SessionID exposes the remote-assigned Mcp-Session-Id, for tests and logs.
type SSEBackend ¶
type SSEBackend struct {
// TokenSource, when non-nil, mints the outbound bearer token for each
// request (stream GET and message POSTs alike). Overrides any
// Authorization in the static headers.
TokenSource func(ctx context.Context) (string, error)
// Client overrides the per-instance HTTP client. Set before Start.
Client *http.Client
// contains filtered or unexported fields
}
SSEBackend speaks the legacy MCP HTTP+SSE transport (protocol revision 2024-11-05), which several hosted servers still require (Atlassian, Monday, Square, Replicate, …):
GET <url> → long-lived text/event-stream
event: endpoint → data carries the URL to POST messages to
event: message → data carries one JSON-RPC message
POST <endpoint URL> → one JSON-RPC message; responses arrive on the
GET stream, not in the POST body (usually 202)
The newer streamable-HTTP transport (RemoteBackend) replaced this design. Point new integrations there and reach for SSEBackend only when the remote offers nothing else.
func NewSSE ¶
NewSSE builds an unstarted legacy HTTP+SSE backend. cfg.URL is the SSE stream URL; cfg.Headers are sent on every request.
func (*SSEBackend) Close ¶
func (b *SSEBackend) Close() error
Close aborts the stream and releases resources. Idempotent. The stream reader observes the aborted body and finishes the teardown (closing recv and done), so Close never races it on the channels.
func (*SSEBackend) Err ¶
func (b *SSEBackend) Err() error
Err reports why the stream terminated; nil on clean Close.
type StdioBackend ¶
type StdioBackend struct {
// ShutdownGrace bounds each step of Close's escalation (stdin EOF,
// SIGTERM, then SIGKILL). Zero means defaultShutdownGrace.
ShutdownGrace time.Duration
// contains filtered or unexported fields
}
StdioBackend runs an MCP server as a child process and speaks newline-delimited JSON-RPC over its stdin/stdout. It logs the child's stderr at debug level, since MCP servers use that stream for human-readable diagnostics rather than protocol traffic.
Start puts the child in its own process group so Close reaps the whole tree. The common launcher (`npx some-mcp-server`) forks a node grandchild that would otherwise survive and keep holding the pipes.
func NewStdio ¶
NewStdio builds an unstarted stdio backend. cfg.Command must be non-empty; cfg.Env is layered over the parent environment.
func (*StdioBackend) Close ¶
func (b *StdioBackend) Close() error
Close escalates until the child is gone: EOF on stdin (the polite exit signal for stdio MCP servers), SIGTERM to the process group, then SIGKILL. Signals target the group rather than the pid, so an `npx` launcher's node grandchild dies with it. Close blocks until reap finishes and tolerates repeated calls.
func (*StdioBackend) Done ¶
func (b *StdioBackend) Done() <-chan struct{}
func (*StdioBackend) Err ¶
func (b *StdioBackend) Err() error
func (*StdioBackend) Name ¶
func (b *StdioBackend) Name() string
func (*StdioBackend) Recv ¶
func (b *StdioBackend) Recv() <-chan []byte