mcpbroker

package
v0.8.21 Latest Latest
Warning

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

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

Documentation

Overview

Package mcpbroker brokers Model Context Protocol (MCP) calls on behalf of a contained island — the execution half of the audited MCP broker (docs/mcp-broker-spec.md). It mirrors the capability broker (internal/capability) exactly: a deny-all default, a host-side curated set of named MCP servers the operator authored, per-island grants of those names, a fixed wire contract, and every call a typed Ledger entry.

A Broker maps a (server, method, params) request to one JSON-RPC call against a granted MCP server. It never constructs a shell command from request data: it execs the operator-curated server program with a fixed argv and speaks JSON-RPC over its stdio. Who may invoke which server (grants) lives in internal/project; this package is only ever reached after a grant check.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrServerNotFound: the named server isn't in the host registry (or the
	// registry file is absent). Fail closed — same posture as a missing grant.
	ErrServerNotFound = errors.New("mcp server not found in host registry")
	// ErrServerUntrusted: the registry file failed its trust checks (not owned
	// by the daemon user, or group/world-writable), so its contents can't be
	// trusted to name safe server programs.
	ErrServerUntrusted = errors.New("mcp server registry failed its trust checks")
	// ErrMethodNotAllowed: the requested JSON-RPC method is outside the brokered
	// surface (see AllowedMethods). The lifecycle/handshake methods are driven by
	// the broker itself and are never callable by a client.
	ErrMethodNotAllowed = errors.New("mcp method not permitted by the broker")
	// ErrTimeout: the server didn't complete the call within the wall-clock bound.
	ErrTimeout = errors.New("mcp call timed out")
	// ErrProtocol: the server spoke malformed JSON-RPC, closed early, or returned
	// a JSON-RPC error to a lifecycle/brokered request.
	ErrProtocol = errors.New("mcp protocol error")
)

Sentinel errors let the API layer map broker failures to HTTP status codes without leaking host detail. A tools/call that *ran* and returned an MCP application error (isError:true) is NOT one of these — it returns a Result with IsError set and a nil error; only transport/protocol failures error.

View Source
var AllowedMethods = map[string]bool{
	"tools/list":     true,
	"tools/call":     true,
	"resources/list": true,
	"resources/read": true,
	"prompts/list":   true,
	"prompts/get":    true,
}

AllowedMethods is the brokered JSON-RPC surface: discovery + invocation, never the lifecycle/handshake methods (initialize, notifications/*), which the broker owns. Keeping this a fixed allow-list is what makes the Ledger tractable — every entry names a published, bounded operation, exactly the argument that rejected a general command broker for capabilities.

Functions

func MethodAllowed

func MethodAllowed(method string) bool

MethodAllowed reports whether method is in the brokered surface.

func ValidateServerName

func ValidateServerName(name string) error

ValidateServerName checks a registry server name — the handle a grant and a call address. It must be a single safe token: 1–64 chars, no path separators or traversal, no whitespace or control characters. (Server commands can be anything the operator curates; the *name* stays a clean identifier.)

Types

type Broker

type Broker interface {
	Name() string
	Call(ctx context.Context, req Request) (Result, error)
}

Broker maps a Request to one JSON-RPC call against a granted, host-curated MCP server. Implementations MUST never build a shell command from request data: exec the operator-authored server program with a fixed argv and speak JSON-RPC over stdio. Name identifies the transport for the Ledger ("stdio").

func Default

func Default() (Broker, error)

Default returns the broker backed by ~/.dejima/mcp/servers.toml.

type Registry

type Registry struct {
	Path string
}

Registry is the host-side catalogue of curated MCP servers. It is read fresh on every lookup (like the capability script adapter Lstats its target each call), so an operator editing servers.toml takes effect with no daemon restart. Path is the servers.toml location.

func DefaultRegistry

func DefaultRegistry() (*Registry, error)

DefaultRegistry returns the registry at ~/.dejima/mcp/servers.toml.

func (*Registry) List

func (r *Registry) List() ([]ServerSpec, error)

List returns every curated server, or an empty slice when no registry exists yet (deny-all: nothing to invoke). A present-but-untrusted registry is an error — fail closed rather than trust a file some other account could edit.

func (*Registry) Lookup

func (r *Registry) Lookup(name string) (ServerSpec, error)

Lookup returns the spec for name, or ErrServerNotFound. The name is matched exactly against the curated set; a server present in the registry but not yet granted to the island is still rejected upstream (deny-all grant check).

type Request

type Request struct {
	Island string
	Agent  string
	Server string
	Method string
	Params json.RawMessage
}

Request is one brokered MCP call. The island/agent are carried for the Ledger and the server's minimal environment; Server names a granted registry entry; Method is a member of AllowedMethods; Params is the JSON-RPC params object (may be nil). Nothing here reaches a shell.

type Result

type Result struct {
	Output  json.RawMessage
	IsError bool
}

Result is the outcome of a brokered call that completed the JSON-RPC round-trip. Output is the raw JSON-RPC `result` value (bounded). IsError reports whether a tools/call result carried `isError: true` — an application-level failure that still completed the protocol, mirroring how the capability adapter treats a non-zero exit code: the caller's concern, not a broker error.

type ServerSpec

type ServerSpec struct {
	// Name is the handle a grant and a call address. Validated as a single safe
	// token (see ValidateServerName) — no separators, bounded length.
	Name string `toml:"name"`
	// Transport is the MCP transport. Only "stdio" in V1 (the dominant transport,
	// and the one with no network surface — the broker spawns the program and
	// speaks JSON-RPC over its pipes). Empty defaults to "stdio".
	Transport string `toml:"transport,omitempty"`
	// Command is the server program. The operator vouches for it by listing it in
	// the trust-checked registry; the broker execs it with a fixed argv and never
	// through a shell.
	Command string `toml:"command"`
	// Args is the fixed argv passed to Command. Never interpolated with request
	// data — request params travel as JSON-RPC on stdin.
	Args []string `toml:"args,omitempty"`
	// Env is an explicit "K=V" passthrough (e.g. an API key the server needs).
	// Only these — plus a minimal PATH and the island identity — reach the
	// process; the daemon's own environment is never inherited.
	Env []string `toml:"env,omitempty"`
}

ServerSpec is one operator-curated MCP server the broker may invoke. The set of specs — authored host-side in ~/.dejima/mcp/servers.toml, which the island cannot write — is the security boundary, exactly like the capability broker's ~/.dejima/capabilities/ scripts dir: the island can only invoke a server the user already chose to expose, and only one explicitly granted to it.

type StdioBroker

type StdioBroker struct {
	Registry  *Registry
	Timeout   time.Duration // 0 → defaultCallTimeout
	MaxOutput int64         // 0 → defaultMaxOutput
}

StdioBroker invokes stdio MCP servers named in a host-side Registry. It is the V1 broker; a future transport (e.g. HTTP/SSE) is a sibling implementation selected the same way, never a generalization of this one.

func (*StdioBroker) Call

func (b *StdioBroker) Call(ctx context.Context, req Request) (Result, error)

Call resolves the named server in the registry (deny-closed if absent or the registry is untrusted), enforces the brokered method surface, and performs one JSON-RPC round-trip bounded by the timeout and output cap. The grant check (deny-all) happens upstream in the API layer; this is only ever reached after it passes.

func (*StdioBroker) Name

func (b *StdioBroker) Name() string

Jump to

Keyboard shortcuts

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