Help us shape the future of Go! Take the 2026 Go Developer Survey

proxy

package
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: Oct 9, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package proxy is the MCP stdio pump: it sits between an MCP client and the governed server, forwarding newline-delimited JSON-RPC frames byte-for-byte while observing them, and giving the harness two abilities the transport alone would not: injecting its own requests toward the server (with their responses routed out of band, never to the client) and — in later phases — replacing a frame with a synthesized refusal.

Byte-fidelity is the default and the exception is narrow: the only frames ever rewritten are client requests whose id trespasses on the harness's reserved namespace (see reservedPrefix), which would otherwise let a client collide with an injected request's id and confuse response routing.

Index

Constants

View Source
const MaxFrameBytes = 64 * 1024 * 1024

MaxFrameBytes bounds one MCP stdio line. Tool results legitimately carry base64 screenshots, so this is far larger than the control channel's limit.

Variables

View Source
var ErrFrameTooLong = errors.New("proxy: frame exceeds MaxFrameBytes")

ErrFrameTooLong reports a line over MaxFrameBytes on either stream.

Functions

This section is empty.

Types

type Interceptor added in v0.3.0

type Interceptor interface {
	Intercept(raw []byte) []byte
}

Interceptor can refuse a client→server request. For each client frame it returns a non-nil response frame to refuse — that frame is written to the client verbatim and the request is NOT forwarded to the server — or nil to forward normally. This is the seam the policy engine sits on: the governed server never sees a request the harness refuses.

Intercept runs on the client→server pump goroutine, so it must not block indefinitely; a policy decision that needs to wait (an approval, a signal round-trip) is the caller's responsibility to bound.

The same seam answers calls to harness-injected tools: for a tools/call of a tool the harness added to the manifest (see ToolInjector), Intercept returns the tool's result frame — a success, not a refusal — so the call is answered by the harness and never reaches the server, which does not have that tool.

type NopObserver

type NopObserver struct{}

NopObserver observes nothing.

func (NopObserver) OnClientFrame

func (NopObserver) OnClientFrame([]byte)

func (NopObserver) OnServerFrame

func (NopObserver) OnServerFrame([]byte)

type Observer

type Observer interface {
	// OnClientFrame sees each client→server frame before forwarding.
	OnClientFrame(raw []byte)
	// OnServerFrame sees each server→client frame before forwarding. Frames
	// consumed as injected-request responses are not reported.
	OnServerFrame(raw []byte)
}

Observer sees every frame. Callbacks run on the pumping goroutine for their direction: cheap work only, and no re-entrant Inject calls.

type Proxy

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

Proxy pumps frames between a client (stdin/stdout of the harness) and a server (stdin/stdout of the child process).

func New

func New(clientIn io.Reader, clientOut io.Writer, serverIn io.Writer, serverOut io.Reader, obs Observer) *Proxy

New builds a proxy over the four streams. obs must not be nil; use NopObserver.

func (*Proxy) Inject

func (p *Proxy) Inject(ctx context.Context, body map[string]any) ([]byte, error)

Inject sends a harness-originated request to the server and returns the raw response frame. The response is consumed by the pump and never reaches the client. body is the request WITHOUT an id; Inject assigns one from the reserved namespace.

func (*Proxy) Run

func (p *Proxy) Run(ctx context.Context) error

Run pumps both directions until either stream ends or ctx is canceled. A clean client EOF (the MCP host closed stdin) returns nil; anything else returns the first error. Both directions stop before Run returns.

func (*Proxy) SetInterceptor added in v0.3.0

func (p *Proxy) SetInterceptor(i Interceptor)

SetInterceptor installs (or replaces) the refusal seam. It is safe to call while Run is pumping — the enforcement decider is built once the servant connects, after the pump has started — so a session can begin fail-closed and swap to the policy decider without a restart.

func (*Proxy) SetToolInjector added in v0.7.0

func (p *Proxy) SetToolInjector(i ToolInjector)

SetToolInjector installs (or replaces) the tool-injection seam. Safe to call while pumping, for the same reason SetInterceptor is: the injected manifest is decided once the servant connects.

type ToolInjector added in v0.7.0

type ToolInjector interface {
	Tools() []json.RawMessage
}

ToolInjector adds tools to the manifest the client sees. Tools returns the tool definitions (each the raw JSON of one MCP Tool object) to append to every tools/list result the server sends. The client's manifest is then the server's tools plus these, and the observer fingerprints that combined surface — so the injected tools sit inside the rug-pull baseline, not outside it. Calls to the injected tools are answered by the Interceptor.

Jump to

Keyboard shortcuts

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