mcpx

package
v0.23.1 Latest Latest
Warning

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

Go to latest
Published: Sep 27, 2026 License: Apache-2.0 Imports: 26 Imported by: 0

Documentation

Overview

Package mcpx implements a JSON-RPC 2.0 engine for the Model Context Protocol (MCP) over three transports: HTTP, WebSocket, and STDIO.

An MCP surface is an Engine

Every knob — tool set, descriptors, server identity, per-tool roles and ownership, rate buckets, timeouts, transport policy, audit hooks and the auth verifier — is owned by an explicitly constructed *Engine:

eng, err := mcpx.NewEngine(
	mcpx.WithTools(tools),
	mcpx.WithToolDescriptors(descriptors),
	mcpx.WithServerInfo("my-service", "1.0.0"),
	mcpx.WithAuthVerifier(verify),
	mcpx.WithWSOriginAllowlist([]string{"https://app.example"}),
)

ENG-4634 / GitLab #364: this package used to expose a package-level HandleHTTP/ServeWS/ServeSTDIO plus a family of Set* mutators writing one implicit, process-wide configuration. Two MCP surfaces in one process (a generated server plus `apic mcp`, two tenants, two configs) therefore shared one verifier, one descriptor table and one origin allowlist, and the last Set* call won. That surface is gone; construct an Engine per surface. See docs/archive/migrations/MIGRATION_mcpx-engine.md.

Per-transport authentication posture (SEC-0031)

The three transports do NOT share a uniform authentication model. A consumer wiring up mcpx MUST understand the posture of each before exposing it:

  • HTTP ((*Engine).HandleHTTP): authenticated. Fails CLOSED when the engine was built without an auth verifier (WithAuthVerifier) — every request is rejected with HTTP 401, and a verifier error rejects the request before any dispatch. The verifier may return a request-scoped context (e.g. carrying JWT claims / roles via ContextWithRoles) that is threaded into tool bodies.

  • WebSocket ((*Engine).ServeWS): authenticated. Fails CLOSED identically to HTTP on a nil verifier, and additionally enforces a fail-closed Origin allowlist (SEC-0008): upgrades are rejected with HTTP 403 unless the Origin is allowlisted (WithWSOriginAllowlist) or WithWSAllowAnyOrigin was explicitly enabled. A session's tools run on a context that ends with the connection and, with WithShutdownContext, when the server shuts down: the session is then closed with status 1001 (going away).

  • STDIO ((*Engine).ServeSTDIO): UNAUTHENTICATED BY DESIGN. The STDIO loop never consults the auth verifier — the STDIO protocol has no *http.Request to verify and no per-message credential channel. Every dispatched tools/call runs with the authority of the local process that owns the server's stdin/stdout. Cancelling the ctx passed to ServeSTDIO ends the loop even while it waits for the next line, and leaves no reader goroutine behind when stdin's read can be interrupted.

STDIO trust boundary

Because ServeSTDIO performs no authentication, it MUST only be exposed across a TRUSTED local process boundary — for example a desktop MCP host that spawns this binary as a child process and speaks JSON-RPC over the child's stdio. Never bridge ServeSTDIO to a network listener, a shared pseudo-terminal, or any stream an untrusted party can write to: doing so grants that party unauthenticated access to every registered tool.

Per-tool role and ownership policy is still enforced on the STDIO path, but only against the principal carried on the dispatch context. (*Engine).ServeSTDIO REQUIRES a non-nil ctx (APIC-UP-006) and treats it as the parent of every tool invocation on that transport: a host that has authenticated the local peer out-of-band installs that principal with ContextWithCaller, and a host that has not passes a bare root context and gets an unknown caller with no roles. Attaching a context is an enrichment hook — it is NOT an authentication gate, and it does not change the fact that the STDIO transport itself verifies nothing.

Request-scoped caller (SONNY-792)

A tool reads the caller with callerctx.MustCaller(ctx) — the same callerctx.Caller a generated REST or WebSocket handler receives:

  • HTTP and WebSocket: WithCallerBuilder derives it from the request carrying the context the auth verifier returned (never the original request's context), once per HTTP request and once per WebSocket connection, after the verifier (and, for WebSocket, the upgrade) accepted it. The generated server wires the builder to securex.CallerFromRequest. A nil result installs an unknown caller, so a caller inherited from the request's parent context never reaches a tool.

  • STDIO: the builder is never consulted. A tool sees the caller the host put on the ctx it passed to ServeSTDIO (callerctx.ContextWithCaller), or an unknown caller.

The engine's role and ownership gates (WithToolRoles, WithToolOwnership) evaluate the same installed caller -- its Roles, and its Subject only when it is a human caller -- so a gate can never approve one principal while the tool reads another. ContextWithRoles / ContextWithSubject are a fallback, consulted only when no caller is installed. ContextWithCaller installs a caller and sets that fallback from it in one call.

Index

Constants

View Source
const DefaultMaxHTTPBodyBytes int64 = 1 << 20

DefaultMaxHTTPBodyBytes is the default per-request body cap that HandleHTTP enforces via io.LimitReader. Mirrors the route-level MaxBytesReader cap in apic's emitted /mcp wrapper. APPSEC-1.

Variables

View Source
var (
	ErrInvalidConfig = errors.New("mcpx: invalid configuration")
	ErrAuthFailed    = errors.New("mcpx: authentication failed")
)

Sentinel errors for MCP runtime. Kept at package level per repo conventions. Messages use a lowercase, package-prefixed, diagnostic form so log output reads naturally; errors.Is callers should rely on pointer equality rather than substring matching of Error().

View Source
var ErrInvalidRequest = errors.New("mcpx: invalid request")

ErrInvalidRequest indicates a malformed JSON-RPC 2.0 request.

View Source
var ErrNoTools = errors.New("mcpx: engine has no tools; pass WithTools")

ErrNoTools is returned by NewEngine when no tool set was supplied. An engine with no tools cannot serve tools/list or tools/call and is almost always a wiring mistake, so it fails closed at construction rather than at first request.

View Source
var ErrResourceNotFound = errors.New("mcpx: resource not found")

ErrResourceNotFound is returned by a ResourceReader when the addressed resource does not exist. resources/read reports it as the MCP "Resource not found" JSON-RPC error (-32002).

View Source
var ErrResourceTimeout = errors.New("mcpx: resource read timeout")

ErrResourceTimeout indicates a resource read exceeded its allotted time.

View Source
var ErrSTDIOLineTooLong = errors.New("mcpx: stdio line exceeds maximum length")

ErrSTDIOLineTooLong indicates a single STDIO JSON-RPC line exceeded the configured maximum length (stdioMaxLineBytes) before a newline. The loop returns it rather than buffering a no-newline multi-megabyte line without limit. T-03.

View Source
var ErrSTDIOWrite = errors.New("mcpx: stdio write failed")

ErrSTDIOWrite indicates the STDIO transport could not write a complete response line: the write to stdout failed, or the response could not be encoded. The loop returns it, wrapping the cause, rather than carrying on: part of the line may already be on stdout, and a JSON-RPC peer cannot resynchronize a stream after a truncated message (SONNY-792).

View Source
var ErrToolTimeout = errors.New("mcpx: tool timeout")

ErrToolTimeout indicates a tool exceeded its allotted time.

Functions

func BuildToolBuckets

func BuildToolBuckets(tools map[string]Tool, rate, burst float64) map[string]*bucket

BuildToolBuckets constructs default buckets for tools.

func ContextWithCaller added in v0.23.0

func ContextWithCaller(ctx context.Context, c *callerctx.Caller) context.Context

ContextWithCaller returns ctx carrying c as the ONE identity every MCP gate and tool on it evaluates (SONNY-792). It is the helper a STDIO host -- or any other direct pkg/mcpx user -- uses to install a principal on the ctx it hands the engine (for a generated server, the ctx passed to Serve).

It stores a defensive copy (callerctx.ContextWithCaller) and sets the fallback accessors from that SAME copy: RolesFromContext reports its Roles (control-character roles dropped, as ContextWithRoles does) and SubjectFromContext its Subject for a callerctx.KindHuman caller, nothing otherwise. Whatever caller or fallback values ctx already carried are replaced, so no reader of the result -- the role and ownership gates, a tool's callerctx.MustCaller, or code still using the fallback accessors -- can see a different principal. A nil c installs an unknown caller (callerctx.KindUnknown, no roles, no subject), never one inherited from ctx. ctx must be non-nil, as for context.WithValue.

func ContextWithRoles

func ContextWithRoles(ctx context.Context, roles []string) context.Context

ContextWithRoles returns a copy of ctx carrying the supplied role set. The MCP auth verifier (or an adapter around it) calls this so the dispatcher can enforce per-tool required-role policy without re-parsing the bearer token. A nil/empty slice is stored as-is (RolesFromContext then reports no roles).

PRECEDENCE (SONNY-792): this role set is only the FALLBACK. When a caller is installed on the context (callerctx; see ContextWithCaller and WithCallerBuilder), the role gate and the tools/list filter read the caller's Roles and ignore this value, so the gates decide on the same identity the tool reads. It drives them only when no caller is installed.

Role strings carrying a C0 control character (U+0000..U+001F) or DEL are DROPPED before stashing (SEC-0081, #381): securex refuses them at the claims seam for its own verifiers, but a consumer-supplied verifier can hand this function anything, and a role that can never match a configured tool policy (hasAnyRole is exact-match) has no legitimate reason to carry a control byte -- it is only useful as a cache-key or log-injection payload. Dropping (fail-closed: fewer privileges) rather than rejecting keeps the call signature stable. A clean slice is stored without copying.

func ContextWithSubject

func ContextWithSubject(ctx context.Context, sub string) context.Context

ContextWithSubject returns a copy of ctx carrying the authenticated subject (JWT "sub"). The MCP auth verifier (or an adapter around it) calls this once per request so the dispatcher can enforce per-tool ownership policy (WithToolOwnership) without re-parsing the bearer token. An empty subject is stored as-is; SubjectFromContext then reports no subject so an ownership-gated tool fails closed.

PRECEDENCE (SONNY-792): this subject is only the FALLBACK. When a caller is installed on the context, the ownership gate reads the caller instead -- its Subject, and only for a callerctx.KindHuman caller (the securex.HumanSubject rule: a workload, service, device or unknown principal never owns a resource) -- and ignores this value. It drives the gate only when no caller is installed.

ctx must be non-nil: like ContextWithRoles and context.WithValue it panics on a nil parent rather than inventing a detached one (SONNY-792).

func RolesFromContext

func RolesFromContext(ctx context.Context) []string

RolesFromContext returns the role set stashed by ContextWithRoles, or nil if none was set. It reads that fallback value only: the engine's gates prefer an installed caller's Roles (see ContextWithRoles's precedence note).

func SubjectFromContext

func SubjectFromContext(ctx context.Context) (string, bool)

SubjectFromContext returns the authenticated subject stashed by ContextWithSubject, or ("", false) if none was set (or it was empty). An empty subject is reported as absent so a fail-closed ownership check never treats "" as a valid owner. It reads that fallback value only: the ownership gate prefers an installed caller (see ContextWithSubject's precedence note).

Types

type AuditHooks added in v0.19.2

type AuditHooks struct {
	AuthOK   func(transport, subject string)
	AuthFail func(transport, reason string)
	ToolCall func(name string)
	// ResourceRead fires before a resources/read reader runs (SONNY-507).
	ResourceRead func(name string)
}

AuditHooks are the per-engine audit callbacks. They replace the exported package-level AuditMCP struct (and the SetAuditHooks mutator that briefly stood in for it), which was process-wide, unguarded and racy when written after serving started. Nil callbacks are replaced with no-ops at construction so the dispatch path never nil-checks.

type CallToolResult

type CallToolResult struct {
	Content           []Content      `json:"content"`
	StructuredContent jsontext.Value `json:"structuredContent,omitzero"`
	IsError           bool           `json:"isError"`
}

CallToolResult is the MCP tools/call result shape (2025-06-18). A successful tool returns IsError=false with its output as a text Content block (and the typed object in StructuredContent); a tool that runs but fails returns IsError=true with the error message as a text block.

type Content

type Content struct {
	Type string `json:"type"`
	Text string `json:"text"`
}

Content is one block of an MCP CallToolResult. Only text content is emitted by the generated tools (the marshaled handler response as JSON text).

type Engine added in v0.19.2

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

Engine is one MCP server instance. Every descriptor table, server identity, role map, ownership map, rate-limit bucket, tools/list memo, transport knob and auth verifier lives on the instance, so a process may host any number of independently configured MCP surfaces (a generated server plus `apic mcp`, two tenants under test, two configs in one binary) with no cross-talk.

ENG-4634 / GitLab #364. Before this change all of that was package-level state and the LAST Set* call in a process won. Task 13 removed that surface outright -- there is no package-level HandleHTTP/ServeWS/ServeSTDIO, no Set* mutator and no implicit process-wide configuration left to inherit from, so an MCP surface exists only as an explicitly constructed Engine.

func NewEngine added in v0.19.2

func NewEngine(opts ...Option) (*Engine, error)

NewEngine builds an Engine. It never reads or mutates process state.

func (*Engine) HandleHTTP added in v0.19.2

func (e *Engine) HandleHTTP(w http.ResponseWriter, r *http.Request)

HandleHTTP serves one MCP JSON-RPC request over plain HTTP POST for this engine.

func (*Engine) ServeSTDIO added in v0.19.2

func (e *Engine) ServeSTDIO(ctx context.Context) error

ServeSTDIO runs this engine's JSON-RPC 2.0 loop over os.Stdin/os.Stdout until ctx is cancelled or stdin reaches EOF.

ctx is REQUIRED and is the parent of every tool invocation on this transport. Cancelling it ends the loop: at once when the loop is waiting for the next line, otherwise once the in-flight message completes (its tool sees ctx.Done()). Passing a bare root context here is a caller decision, made once at the process root, not something this package makes on the caller's behalf (APIC-UP-006 / ENG-4640 — a nil ctx is an error, never a silently substituted background context).

A response the loop cannot write completely -- a failed write to stdout, or one that cannot be encoded -- ends the loop with ErrSTDIOWrite wrapping the cause: a JSON-RPC peer cannot resynchronize after a truncated message.

Interrupting the idle read (SONNY-792). The pending read of stdin is interrupted when the loop ends, leaving no goroutine behind, whenever stdin allows it: when the runtime already polls it (the process inherited it in non-blocking mode), and when it is a pipe or socket in blocking mode, which ServeSTDIO then reads through a duplicate in non-blocking mode. Otherwise -- an interactive terminal, a FIFO on the kqueue platforms, a platform without the duplicate, or a stdin that is the same file as stdout or stderr (inetd, systemd StandardInput=socket, socat EXEC) -- the loop still returns at once, and its one reader goroutine ends when that read returns. The duplicate shares stdin's open file description, and O_NONBLOCK belongs to the description, so while ServeSTDIO reads through one:

  • ServeSTDIO owns stdin: another reader of os.Stdin in this process may see EAGAIN;
  • a child process spawned meanwhile inherits stdin in non-blocking mode;
  • ServeSTDIO restores blocking mode when it returns, but a process killed with SIGKILL cannot: the description is left non-blocking, which matters only if another live process shares it.

A stdin that is the same file as stdout or stderr is never read that way: the shared description would make writes to them fail with EAGAIN.

SEC-0031 — TRUST BOUNDARY: the STDIO transport is UNAUTHENTICATED by design. Unlike HandleHTTP and ServeWS — which fail closed when no auth verifier is configured and reject the request with HTTP 401 — this loop NEVER consults the verifier (there is no *http.Request to verify and no per-message credential channel in the STDIO protocol). Every tools/call it dispatches runs with the authority of the local process that owns this server's stdin/stdout.

Consequently it MUST only be exposed across a TRUSTED local process boundary — e.g. a desktop MCP host that spawns this binary as a child process and pipes JSON-RPC over the child's stdio. Do NOT bridge it to a network socket, a shared pseudo-terminal, or any input an untrusted party can write to: doing so grants that party unauthenticated access to every registered tool. Per-tool role and ownership policy (WithToolRoles, WithToolOwnership) is still enforced, but only against the principal carried on ctx: a host that needs one on this transport installs it with ContextWithCaller before calling (SONNY-792). That one call sets the caller tools read AND the identity the gates evaluate; WithCallerBuilder is never consulted on STDIO. Without it a tool sees an unknown caller and every gated tool is refused. (This contract previously lived on the package-level ServeSTDIO free function, removed with the rest of the process-wide surface in ENG-4634.)

func (*Engine) ServeWS added in v0.19.2

func (e *Engine) ServeWS(w http.ResponseWriter, r *http.Request)

ServeWS upgrades to WebSocket and serves JSON-RPC 2.0 messages for this engine. Each message reloads the engine's snapshot (never mid-message), so a reconfiguration takes effect on the next message.

func (*Engine) ServerInfo added in v0.19.2

func (e *Engine) ServerInfo() (string, string)

ServerInfo returns this engine's advertised name and version.

func (*Engine) ToolDescriptorByName added in v0.19.2

func (e *Engine) ToolDescriptorByName(name string) *ToolDescriptor

ToolDescriptorByName returns a copy of this engine's descriptor for one tool, or nil. InputSchema is deep-copied: jsontext.Value is a []byte, so a struct copy would share the backing array with the engine's own table and a caller mutating the returned schema bytes would corrupt it.

func (*Engine) WSOriginAllowlist added in v0.19.2

func (e *Engine) WSOriginAllowlist() []string

WSOriginAllowlist returns a copy of this engine's WebSocket origin allowlist. Exposed so a host can log or assert the effective policy.

type Option added in v0.19.2

type Option func(*engineBuild) error

Option configures an Engine at construction. Options are applied in order; the first error aborts construction.

func WithAuditHooks added in v0.19.2

func WithAuditHooks(h AuditHooks) Option

WithAuditHooks installs per-engine audit callbacks. Nil callbacks inside h become no-ops.

func WithAuthVerifier added in v0.19.2

func WithAuthVerifier(v func(*http.Request) (context.Context, error)) Option

WithAuthVerifier installs the HTTP/WS authentication verifier. HTTP and WS fail closed when it is absent (SEC-0031); STDIO is a local trust boundary and is not gated.

Roles and a subject the verifier stashes on the context it returns, with ContextWithRoles / ContextWithSubject, are only the fallback the role and ownership gates (WithToolRoles, WithToolOwnership) use when no caller is on the ctx. Any caller there takes precedence: the one WithCallerBuilder installs or, on an engine built without a caller builder, one already on the context the verifier returned -- put on the request's context by upstream middleware, or by the verifier itself with ContextWithCaller.

func WithBatchMaxConcurrency added in v0.19.2

func WithBatchMaxConcurrency(n int64) Option

WithBatchMaxConcurrency bounds parallel tool execution inside one batch. A value <= 0 keeps the default (8); 1 disables parallelism.

func WithBatchToolTimeout added in v0.19.2

func WithBatchToolTimeout(d time.Duration) Option

WithBatchToolTimeout bounds a single tool inside a parallel batch. A value <= 0 falls back to the per-call tool timeout.

func WithCallerBuilder added in v0.23.0

func WithCallerBuilder(fn func(*http.Request) *callerctx.Caller) Option

WithCallerBuilder installs the function that derives the request-scoped caller (callerctx.Caller) for the HTTP and WebSocket transports; a tool reads it with callerctx.MustCaller(ctx). The generated server wires it to securex.CallerFromRequest, so an MCP tool sees exactly the caller a REST handler on the same server would (SONNY-792).

The engine calls fn once per HTTP request and once per WebSocket connection -- after the upgrade, so every message on the connection shares one caller -- and only for a request the auth verifier accepted. fn gets the request carrying the context the VERIFIER RETURNED, with the engine's request id folded in, and must read that context, never r's original one: a verifier may attest a principal only through the context it returns (WithAuthVerifier), so a caller built from the original request would miss it.

The engine installs a defensive copy of what fn returns (ContextWithCaller): this is a public seam, so fn may keep its value and change it later without reaching a request or connection already in flight. (The generated REST and WebSocket wrappers skip that copy with callerctx.ContextWithOwnedCaller only because they build the caller themselves and never keep it.) A nil return installs an unknown caller (callerctx.KindUnknown, no roles, no scopes) rather than leaving in place any caller the request's parent context already carried, which nothing verified for this request. A nil fn installs no caller.

STDIO never consults fn: that transport has no request and no network identity. A tool served over STDIO sees whatever caller the host put on the ctx it passed to ServeSTDIO.

The caller is also what the engine's gates evaluate: WithToolRoles checks its Roles and WithToolOwnership its Subject (human callers only), so the gates and the tool decide on one identity. The engine also sets the ContextWithRoles / ContextWithSubject fallback values from the same caller, for code that still reads RolesFromContext / SubjectFromContext.

func WithMaxBatchElements added in v0.19.2

func WithMaxBatchElements(n int) Option

WithMaxBatchElements caps the number of JSON-RPC elements in one batch (R2-3). A value <= 0 keeps the default (256).

func WithMaxHTTPBodyBytes added in v0.19.2

func WithMaxHTTPBodyBytes(n int64) Option

WithMaxHTTPBodyBytes caps the HTTP request body (APPSEC-1). 0 disables the cap, which is discouraged.

func WithResourceRoles added in v0.22.0

func WithResourceRoles(roles map[string][]string) Option

WithResourceRoles sets the per-resource RBAC requirements, keyed by resource name. A resource absent from the map (or mapped to an empty list) has no role requirement. Deep-copied like WithToolRoles.

func WithResources added in v0.22.0

func WithResources(resources []Resource) Option

WithResources installs the engine's MCP resources. Names must be unique and non-empty, each resource sets exactly one of URI / URITemplate, fixed URIs and templates must be unique, and Read must be non-nil. An empty list installs nothing (no resources capability is advertised).

func WithSTDIOMaxLineBytes added in v0.19.2

func WithSTDIOMaxLineBytes(n int) Option

WithSTDIOMaxLineBytes caps one stdio JSON-RPC line (T-03). A value <= 0 keeps the default.

func WithSerialTools added in v0.19.2

func WithSerialTools(s map[string]bool) Option

WithSerialTools names tools that must not run in parallel within a batch.

func WithServerInfo added in v0.19.2

func WithServerInfo(name, version string) Option

WithServerInfo sets the name and version returned by initialize.

func WithShutdownContext added in v0.23.0

func WithShutdownContext(ctx context.Context) Option

WithShutdownContext bounds every WebSocket session the engine serves (ServeWS): once ctx is cancelled -- the server is shutting down -- each open session's connection is closed with status 1001 (going away, RFC 6455 §7.4.1) and the ctx its tools run on is cancelled, so a tool in flight sees ctx.Done() instead of running into its per-call timeout. http.Server.Shutdown neither closes nor waits for these hijacked connections, so a server should cancel ctx when it shuts down; the generated Serve passes the shutdown context it gives its WebSocket routes (SONNY-792). Nil, the default, leaves each session open until its connection ends. An HTTP request (HandleHTTP) already ends with its request, and ServeSTDIO with the ctx passed to it.

func WithToolBuckets added in v0.19.2

func WithToolBuckets(rate, burst float64) Option

WithToolBuckets installs a per-tool token bucket at rate/burst. Buckets are built from the engine's own tool set at construction, so the option is order-independent with respect to WithTools.

func WithToolDescriptors added in v0.19.2

func WithToolDescriptors(d []ToolDescriptor) Option

WithToolDescriptors installs the tools/list descriptor table. InputSchema is deep-copied (mirroring copyDescriptor in descriptor.go): jsontext.Value is a []byte, so storing td as-is would share the backing array with the caller's slice, and the caller mutating its own schema bytes after NewEngine returns would silently corrupt the engine's table.

func WithToolOwnership added in v0.19.2

func WithToolOwnership(ownership map[string]string) Option

WithToolOwnership sets the per-tool BOLA argument name (SEC-0027). The named argument must equal the installed caller's Subject, and only a callerctx.KindHuman caller has one for this purpose; with no caller installed, the ContextWithSubject fallback is compared instead (SONNY-792).

func WithToolRoles added in v0.19.2

func WithToolRoles(roles map[string][]string) Option

WithToolRoles sets the per-tool RBAC requirements. A tool absent from the map (or mapped to an empty list) has no role requirement. The map and every slice in it are deep-copied, so the caller may keep and mutate its own copy.

The gate (and the tools/list filter) evaluates the installed caller's Roles -- the identity the tool reads -- and falls back to ContextWithRoles only when no caller is installed (SONNY-792).

func WithToolTimeout added in v0.19.2

func WithToolTimeout(d time.Duration) Option

WithToolTimeout bounds a single tools/call. A value <= 0 keeps the default.

func WithTools added in v0.19.2

func WithTools(tools map[string]Tool) Option

WithTools sets the engine's tool set. Required.

func WithWSAllowAnyOrigin added in v0.19.2

func WithWSAllowAnyOrigin(allow bool) Option

WithWSAllowAnyOrigin disables Origin checking for origin-agnostic surfaces (SEC-0008). Opt in deliberately: it accepts any Origin header value.

APPSEC-14: an engine built with it records a structured audit event once NewEngine SUCCEEDS, so a misconfiguration leaves a forensic trail exactly as the removed SetWSAllowAnyOrigin(true) transition did. Passing false is silent -- it is the secure default and asserting it is not a change.

func WithWSHandlerGroup added in v0.23.0

func WithWSHandlerGroup(g *wsx.HandlerGroup) Option

WithWSHandlerGroup counts, in g, every WebSocket session the engine serves (ServeWS), from the upgrade request until the session's loop returns, so a server's shutdown can wait for them (g.Wait) -- http.Server.Shutdown does not wait for hijacked connections. Once g.Wait has begun, a new upgrade is refused with 503 before the auth verifier runs. The generated Serve passes the group it waits on under its shutdown deadline, after WithShutdownContext has ended the sessions (SONNY-792). Nil, the default, counts nothing.

func WithWSMaxNonDataFrames added in v0.19.2

func WithWSMaxNonDataFrames(n int) Option

WithWSMaxNonDataFrames caps consecutive control/binary frames (T-01). A value <= 0 keeps the default.

func WithWSOriginAllowlist added in v0.19.2

func WithWSOriginAllowlist(origins []string) Option

WithWSOriginAllowlist sets the WebSocket Origin allowlist (SEC-0008). An empty list is fail-closed unless WithWSAllowAnyOrigin(true) is also passed.

func WithWSTimeouts added in v0.19.2

func WithWSTimeouts(read, write, pingInterval, pongTimeout time.Duration) Option

WithWSTimeouts sets the WebSocket liveness deadlines (T-01). Any value <= 0 keeps that deadline's default.

type Resource added in v0.22.0

type Resource struct {
	URI         string
	URITemplate string
	Name        string
	Title       string
	Description string
	MimeType    string
	Read        ResourceReader
}

Resource declares one MCP resource. Exactly one of URI and URITemplate is set. Name is the unique programmatic identifier (the role-policy key).

type ResourceReader added in v0.22.0

type ResourceReader func(ctx context.Context, uri string, vars map[string]string) (jsontext.Value, error)

ResourceReader reads one resource. ctx is the request-scoped context (the auth-verifier-enriched context on HTTP/WS, with the per-call deadline applied), uri is the exact URI the client asked for, and vars carries the decoded URI-template variables (empty for a fixed-URI resource). The returned bytes become the text of the single resources/read content block.

vars values are untrusted client input, exactly like a REST path parameter: validate them before use.

type Tool

type Tool func(ctx context.Context, params jsontext.Value) (jsontext.Value, error)

Tool is a function that takes the request-scoped context plus params as raw JSON and returns result JSON. The ctx is bounded by the per-call timeout. On HTTP and WebSocket it carries the values the auth verifier attached (e.g. JWT claims) and, with WithCallerBuilder, the verified callerctx.Caller, so tool bodies read them natively rather than reaching for a per-goroutine stash. On STDIO it derives from the ctx the host passed to ServeSTDIO and carries only what the host put there: the transport never manufactures a context or a caller (SONNY-792).

type ToolDescriptor

type ToolDescriptor struct {
	Name        string         `json:"name"`
	Title       string         `json:"title,omitzero"`
	Description string         `json:"description,omitzero"`
	InputSchema jsontext.Value `json:"inputSchema"`
}

ToolDescriptor is the MCP tools/list metadata for one tool. InputSchema is a JSON Schema object describing the tool's arguments (MCP requires it). The generator emits these and hands them to the engine via WithToolDescriptors.

Jump to

Keyboard shortcuts

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