Documentation
¶
Overview ¶
Package protocolcodec is Dockyard's single isolation seam for every MCP extension wire format.
Why this package exists (RFC §5.4, §16; binding property P3) ¶
The MCP protocol and its extensions move independently and fast. Confining every extension wire format — the exact _meta key shapes, the capability blocks, the Tasks method envelopes — to one internal package makes a spec bump a localized, regenerate-and-diff change rather than a cross-cutting refactor. Nothing outside internal/protocolcodec is permitted to import or construct raw MCP extension wire types; handler-facing and manifest-facing Dockyard APIs deal only in Dockyard's own domain types and convert at this seam. AGENTS.md §10 and §13 make that boundary binding; it is mechanically enforced by TestNoRawWireTypeImportsOutsideSeam in boundary_test.go.
What lives here ¶
Versioned codecs (see Codec, CodecFor) keyed on the negotiated MCP protocolVersion string. A codec encodes Dockyard domain types into the wire shape for its version and decodes wire shapes back. Deprecated shapes — notably the flat _meta["ui/resourceUri"] form from the MCP Apps spec — are tolerated on read but NEVER emitted (RFC §16 item 3).
Typed _meta accessors for the MCP Apps extension (io.modelcontextprotocol/ui, spec revision 2026-01-26, SEP-1865) and the MCP Tasks extension (io.modelcontextprotocol/tasks, experimental, SEP-1686/2663). Because _meta is untyped on the wire (map[string]any), these accessors make extension-metadata bugs surface in Dockyard's own validation rather than at runtime inside a host (brief 03 R7).
Vendored specs ¶
The wire shapes implemented here are pinned to vendored spec snapshots under docs/specifications/ (mcp-apps-2026-01-26.mdx and mcp-tasks-experimental.schema.ts). A spec revision is a deliberate, reviewed update of those files followed by a regenerate-and-diff of this package.
Index ¶
- Constants
- Variables
- type AppsCSP
- type AppsExtensionCapability
- type AppsPermissions
- type AppsResourceMeta
- type AppsToolMeta
- type Codec
- type CreateTaskResult
- type CreateTaskResultMeta
- type ListTasksParams
- type ListTasksResult
- type Meta
- type ProtocolVersion
- type SupplyInputParams
- type Task
- type TaskID
- type TaskMeta
- type TaskStatus
- type TasksServerCapability
- type ToolTaskSupport
Constants ¶
const ( // VisibilityModel makes a tool callable by the agent/LLM. VisibilityModel = "model" // VisibilityApp restricts a tool to same-server App-initiated calls. VisibilityApp = "app" )
Visibility values for an Apps tool (`_meta.ui.visibility`). Per spec the default when the array is absent is both: ["model","app"].
const ( // ExtensionApps is the MCP Apps extension identifier (SEP-1865). ExtensionApps = "io.modelcontextprotocol/ui" // ExtensionTasks is the MCP Tasks extension identifier (SEP-1686/2663). ExtensionTasks = "io.modelcontextprotocol/tasks" )
Extension identifiers, exactly as registered in the MCP capability registry.
const DefaultVersion = VersionMCP20251125
DefaultVersion is the protocol version assumed when a peer negotiated none or negotiated an unrecognised one. Dockyard targets the stable core spec by default and degrades gracefully (RFC §16 items 6/7).
const MIMETypeApp = "text/html;profile=mcp-app"
MIMETypeApp is the only MCP Apps resource MIME type defined by the MVP spec (`text/html;profile=mcp-app`). Routing it through one constant means a future profile type is a one-line change (brief 01 §5, sharp edge 5).
Variables ¶
var ErrMalformedMeta = errors.New("protocolcodec: malformed extension metadata")
ErrMalformedMeta is returned (wrapped) when a `_meta` value has the wrong shape for the extension key it sits under. Surfacing it as an error — rather than panicking or silently dropping data — lets Dockyard's own validation catch extension-metadata bugs before a host does (brief 03 R7).
var ErrUnknownVersion = errors.New("protocolcodec: no codec registered for protocol version")
ErrUnknownVersion is returned (wrapped) by CodecForStrict when no codec is registered for a requested protocol version.
Functions ¶
This section is empty.
Types ¶
type AppsCSP ¶
type AppsCSP struct {
// ConnectDomains are origins for network requests (fetch/XHR/WebSocket) —
// CSP `connect-src`.
ConnectDomains []string
// ResourceDomains are origins for static resources (scripts, images,
// styles, fonts, media) — CSP `img-src`/`script-src`/`style-src`/etc.
ResourceDomains []string
// FrameDomains are origins for nested iframes — CSP `frame-src`.
FrameDomains []string
// BaseURIDomains are allowed document base URIs — CSP `base-uri`.
BaseURIDomains []string
}
AppsCSP is the Dockyard domain view of `_meta.ui.csp` — the Content Security Policy domains a UI resource declares (brief 01 §2.5). All four lists map to CSP directives the host enforces; an empty/omitted list is the secure default (no external origins).
type AppsExtensionCapability ¶
type AppsExtensionCapability struct {
MIMETypes []string
}
AppsExtensionCapability is the value Dockyard advertises (and a host advertises) for `capabilities.extensions["io.modelcontextprotocol/ui"]` (brief 01 §2.7). `MIMETypes` is REQUIRED by spec.
type AppsPermissions ¶
AppsPermissions is the Dockyard domain view of `_meta.ui.permissions` — the sandbox iframe capabilities a UI resource requests. On the wire each granted permission is an empty object; here each is a bool (brief 01 §2.5).
type AppsResourceMeta ¶
type AppsResourceMeta struct {
// CSP is the Content Security Policy domain declaration.
CSP AppsCSP
// Permissions is the sandbox-capability request.
Permissions AppsPermissions
// Domain requests a stable dedicated sandbox origin. Host-dependent; the
// seam carries it verbatim and never derives it (derivation lives behind
// host profiles — RFC §7.5, D-012).
Domain string
// PrefersBorder is the visual-boundary preference. A nil pointer means the
// resource declared none and lets the host decide; the spec recommends an
// explicit value.
PrefersBorder *bool
}
AppsResourceMeta is the Dockyard domain view of the MCP Apps metadata carried by a UI resource and, critically, by a `resources/read` response — the `_meta.ui` object on a `Resource` / resource-read content (brief 01 §2.2).
type AppsToolMeta ¶
type AppsToolMeta struct {
// ResourceURI is the `ui://` URI of the UI resource this tool renders
// into. Empty means the tool declares no UI.
ResourceURI string
// Visibility is who may invoke the tool: any of [VisibilityModel] /
// [VisibilityApp]. Empty slice means "unspecified"; per spec a host treats
// that as ["model","app"]. Dockyard preserves it as-given and lets the
// caller decide whether to default it.
Visibility []string
// EmitLegacyResourceURI opts the encoder into additionally writing the
// DEPRECATED flat `_meta["ui/resourceUri"]` key alongside the canonical
// nested `_meta.ui.resourceUri` (D-177). It is a control flag, NOT a wire
// field — the encoder reads it but never serialises it, and the decoder
// never sets it. The default (false) is the RFC-compliant nested-only
// output; the 2026-01-26 spec marks the flat form deprecated, so it is
// emitted only on an explicit opt-in (the server-level
// Options.EmitLegacyToolUIMeta, threaded by runtime/apps).
EmitLegacyResourceURI bool
}
AppsToolMeta is the Dockyard domain view of the MCP Apps metadata carried by a tool definition — the `_meta.ui` object on a `Tool` (brief 01 §2.3).
type Codec ¶
type Codec interface {
// Version reports the protocol version this codec serves.
Version() ProtocolVersion
// EncodeAppsToolMeta merges the Apps tool metadata into base and returns
// the resulting `_meta` map. base may be nil; it is never mutated. If m
// carries no UI information the `ui` key is omitted. The deprecated flat
// `ui/resourceUri` key is removed if present in base — encoders never emit
// it UNLESS m.EmitLegacyResourceURI is set, the explicit opt-in (D-177), in
// which case it is written equal to the nested resourceUri.
EncodeAppsToolMeta(base Meta, m AppsToolMeta) (Meta, error)
// DecodeAppsToolMeta extracts Apps tool metadata from a `_meta` map. It
// reads the nested `_meta.ui` form and, if that is absent, falls back to
// the deprecated flat `_meta["ui/resourceUri"]` form (tolerated on read).
// A `_meta` with no Apps keys yields a zero AppsToolMeta and ok == false.
DecodeAppsToolMeta(meta Meta) (m AppsToolMeta, ok bool, err error)
// EncodeAppsResourceMeta merges the Apps resource metadata into base and
// returns the resulting `_meta` map. base may be nil and is never
// mutated. If m is zero the `ui` key is omitted.
EncodeAppsResourceMeta(base Meta, m AppsResourceMeta) (Meta, error)
// DecodeAppsResourceMeta extracts Apps resource metadata from a `_meta`
// map. A `_meta` with no `ui` key yields a zero value and ok == false.
DecodeAppsResourceMeta(meta Meta) (m AppsResourceMeta, ok bool, err error)
// EncodeAppsExtensionCapability returns the JSON value for the
// `capabilities.extensions["io.modelcontextprotocol/ui"]` block.
EncodeAppsExtensionCapability(c AppsExtensionCapability) (json.RawMessage, error)
// DecodeAppsExtensionCapability parses an extensions-capability value. A
// nil/empty input yields a zero value and ok == false.
DecodeAppsExtensionCapability(raw json.RawMessage) (c AppsExtensionCapability, ok bool, err error)
// EncodeTask returns the JSON of a Tasks `Task` object.
EncodeTask(t Task) (json.RawMessage, error)
// DecodeTask parses a Tasks `Task` object.
DecodeTask(raw json.RawMessage) (Task, error)
// EncodeTaskMeta returns the JSON value for the request-augmentation
// `task` field (`TaskMetadata`).
EncodeTaskMeta(m TaskMeta) (json.RawMessage, error)
// DecodeTaskMeta parses the request-augmentation `task` field. A
// nil/empty input yields a zero value and ok == false.
DecodeTaskMeta(raw json.RawMessage) (m TaskMeta, ok bool, err error)
// EncodeRelatedTaskMeta merges the `io.modelcontextprotocol/related-task`
// association key into base and returns the resulting `_meta`. base may
// be nil and is never mutated.
EncodeRelatedTaskMeta(base Meta, taskID string) (Meta, error)
// DecodeRelatedTaskMeta extracts the related-task id from a `_meta` map.
// A `_meta` without the key yields "" and ok == false.
DecodeRelatedTaskMeta(meta Meta) (taskID string, ok bool, err error)
// EncodeCreateTaskResultMeta merges the optional CreateTaskResult `_meta`
// keys into base and returns the resulting `_meta`. base may be nil and
// is never mutated. If m is zero, base is returned unchanged.
EncodeCreateTaskResultMeta(base Meta, m CreateTaskResultMeta) (Meta, error)
// DecodeCreateTaskResultMeta extracts the optional CreateTaskResult
// `_meta` keys. A `_meta` without them yields a zero value and
// ok == false.
DecodeCreateTaskResultMeta(meta Meta) (m CreateTaskResultMeta, ok bool, err error)
// EncodeTasksServerCapability returns the JSON value for the
// `capabilities.tasks` block.
EncodeTasksServerCapability(c TasksServerCapability) (json.RawMessage, error)
// DecodeTasksServerCapability parses a `capabilities.tasks` block. A
// nil/empty input yields a zero value and ok == false.
DecodeTasksServerCapability(raw json.RawMessage) (c TasksServerCapability, ok bool, err error)
// EncodeCreateTaskResult returns the JSON of a `CreateTaskResult` — the
// result a receiver returns for an accepted task-augmented request.
EncodeCreateTaskResult(r CreateTaskResult) (json.RawMessage, error)
// DecodeCreateTaskResult parses a `CreateTaskResult`.
DecodeCreateTaskResult(raw json.RawMessage) (CreateTaskResult, error)
// EncodeTaskIDParams returns the JSON of the params object shared by
// `tasks/get`, `tasks/result` and `tasks/cancel` (`{ "taskId": ... }`).
EncodeTaskIDParams(p TaskID) (json.RawMessage, error)
// DecodeTaskIDParams parses the `{ "taskId": ... }` params object. An empty
// `taskId` is a malformed-meta error — the field is required.
DecodeTaskIDParams(raw json.RawMessage) (TaskID, error)
// EncodeGetTaskResult returns the JSON of a `GetTaskResult` /
// `CancelTaskResult` — the flat `Result & Task` shape.
EncodeGetTaskResult(t Task) (json.RawMessage, error)
// DecodeGetTaskResult parses a `GetTaskResult` / `CancelTaskResult`.
DecodeGetTaskResult(raw json.RawMessage) (Task, error)
// EncodeListTasksParams returns the JSON of the `tasks/list` request params.
EncodeListTasksParams(p ListTasksParams) (json.RawMessage, error)
// DecodeListTasksParams parses the `tasks/list` request params. A nil/empty
// input yields a zero value (first page) and a nil error.
DecodeListTasksParams(raw json.RawMessage) (ListTasksParams, error)
// EncodeListTasksResult returns the JSON of a `ListTasksResult`.
EncodeListTasksResult(r ListTasksResult) (json.RawMessage, error)
// DecodeListTasksResult parses a `ListTasksResult`.
DecodeListTasksResult(raw json.RawMessage) (ListTasksResult, error)
// DecodeSupplyInputParams parses the Dockyard-internal
// `dockyard/tasks/supplyInput` params (Phase 25 / D-134) — the wire
// half of `tasks.Engine.SupplyInput`. An empty `taskId` is a
// malformed-meta error.
DecodeSupplyInputParams(raw json.RawMessage) (SupplyInputParams, error)
}
Codec encodes Dockyard domain types into MCP extension wire formats and decodes wire formats back, for one negotiated protocol version. It is the only surface through which the rest of Dockyard touches extension wire shapes (RFC §5.4). Obtain one with CodecFor or CodecForStrict.
A Codec is stateless and safe for concurrent use by multiple goroutines — every method takes its inputs as arguments and returns fresh values. The "reusable artifact ⇒ concurrent-reuse test" rule (AGENTS.md §14) is met by TestCodecConcurrentReuse.
Decoders are tolerant: an unknown extra key inside a recognised `_meta` object is ignored, and the deprecated flat MCP Apps form is accepted. Encoders are strict: they emit only the current spec shapes and NEVER the deprecated form (RFC §16 item 3).
func CodecFor ¶
func CodecFor(version ProtocolVersion) Codec
CodecFor returns the Codec for a negotiated protocol version, falling back to the DefaultVersion codec when version is empty or unrecognised. It never returns nil — graceful degradation is mandatory (RFC §16 item 7), so callers in the hot path use this. Use CodecForStrict when an unknown version must be surfaced as an error (e.g. in `dockyard validate`).
func CodecForStrict ¶
func CodecForStrict(version ProtocolVersion) (Codec, error)
CodecForStrict is like CodecFor but returns a wrapped ErrUnknownVersion instead of falling back, so tooling can flag an unrecognised protocol version rather than silently degrading.
type CreateTaskResult ¶
type CreateTaskResult struct {
// Task is the durable task the request was accepted as.
Task Task
// Meta is the optional `_meta` carried on the result — currently just the
// provisional model-immediate-response key. A nil map omits `_meta`.
Meta Meta
}
CreateTaskResult is the Dockyard domain view of the schema's `CreateTaskResult` — the result a receiver returns for an accepted task-augmented request, in place of the underlying request's own result (brief 02 §2.3). It wraps a Task; the schema attaches no `resultType` discriminator (brief 02 §5 "avoid").
type CreateTaskResultMeta ¶
type CreateTaskResultMeta struct {
// ModelImmediateResponse, when non-empty, is a placeholder string handed
// to the model so the host can return control while the task runs. It is
// emitted under the provisional
// `io.modelcontextprotocol/model-immediate-response` key.
ModelImmediateResponse string
}
CreateTaskResultMeta is the Dockyard domain view of the optional `_meta` keys carried on a `CreateTaskResult` — currently just the provisional model-immediate-response string (brief 02 §2.7; D-014).
type ListTasksParams ¶
type ListTasksParams struct {
// Cursor is the opaque pagination cursor; empty requests the first page.
Cursor string
}
ListTasksParams is the Dockyard domain view of the `tasks/list` request params — a `PaginatedRequest` carrying an optional opaque `cursor` (brief 02 §2.3).
type ListTasksResult ¶
type ListTasksResult struct {
// Tasks is the page of tasks.
Tasks []Task
// NextCursor is the opaque cursor for the next page; empty means the page
// is the last one.
NextCursor string
}
ListTasksResult is the Dockyard domain view of the `tasks/list` result — a `PaginatedResult` carrying the page of tasks and an optional `nextCursor` (brief 02 §2.3).
type Meta ¶
Meta is the raw, untyped MCP `_meta` object as it appears on the wire.
It is structurally identical to the go-sdk's `mcp.Meta` (`map[string]any`, JSON-tagged `_meta`); protocolcodec deliberately keeps its own alias so the seam has no compile-time dependency on a specific SDK release for a type this trivial. A `nil` Meta marshals to an absent `_meta` field.
Meta is only ever produced or consumed at this seam. Code outside internal/protocolcodec works with Dockyard domain types (AppsToolMeta, AppsResourceMeta, TaskMeta, …) and converts through a Codec.
type ProtocolVersion ¶
type ProtocolVersion string
ProtocolVersion is a negotiated MCP protocol-version string, e.g. "2025-11-25" or "2026-01-26". It is the key on which CodecFor selects a Codec (RFC §16 item 3).
const ( // VersionMCP20251125 is the current stable MCP spec version and the one // the pinned go-sdk targets (RFC §16 item 6). VersionMCP20251125 ProtocolVersion = "2025-11-25" // VersionApps20260126 is the MCP Apps spec revision (SEP-1865); the Apps // extension negotiates against it independently of the core spec version. VersionApps20260126 ProtocolVersion = "2026-01-26" )
Known MCP protocol versions Dockyard recognises. The list is deliberately small: V1 targets the spec version the pinned go-sdk supports (RFC §16 item 6), and the Apps spec revision adds one more.
func KnownVersions ¶
func KnownVersions() []ProtocolVersion
KnownVersions returns the protocol versions protocolcodec has a codec for, in no particular order. Intended for diagnostics and tests.
type SupplyInputParams ¶
type SupplyInputParams struct {
// TaskID is the suspended task to resume. Required.
TaskID string
// Data is the opaque payload the requestor supplied — the handler
// receives it as `tasks.InputResponse.Data` raw JSON. Absent when
// Declined is true.
Data json.RawMessage
// Declined signals the requestor explicitly declined to provide
// input. The handler receives it as `tasks.InputResponse.Declined`.
Declined bool
}
SupplyInputParams is the Dockyard-internal `dockyard/tasks/supplyInput` params object (Phase 25 / D-134). It is the wire half of [tasks.Engine.SupplyInput] — the inspector posts it to deliver an App's elicitation-response to a suspended `input_required` task. Distinct from the vendored experimental Tasks spec (which defines no standard shape for resuming an input_required task); the `dockyard/` method prefix and this struct keep it from being confused with a future standard wire shape.
type Task ¶
type Task struct {
// ID is the receiver-side task identifier (`taskId`).
ID string
// Status is the current lifecycle status.
Status TaskStatus
// StatusMessage is an optional human-readable status description.
StatusMessage string
// CreatedAt / LastUpdatedAt are ISO-8601 timestamps on the wire.
CreatedAt time.Time
LastUpdatedAt time.Time
// TTL is the actual retention duration in milliseconds; a nil pointer
// encodes the schema's `null` (unlimited retention). Per schema this field
// is always present on the wire — never omitted — so nil marshals to JSON
// `null`, not an absent key.
TTL *int64
// PollInterval is the receiver's suggested polling interval in
// milliseconds; a nil pointer omits it.
PollInterval *int64
}
Task is the Dockyard domain view of the Tasks `Task` object — the durable state machine returned inside a `CreateTaskResult` and by `tasks/get` / `tasks/cancel` (brief 02 §2.3).
type TaskID ¶
type TaskID struct {
// ID is the `taskId` request parameter.
ID string
}
TaskID is the Dockyard domain view of the params of `tasks/get`, `tasks/result` and `tasks/cancel` — each takes a single `taskId` string (brief 02 §2.3). It is one type because the three method params are structurally identical.
type TaskMeta ¶
type TaskMeta struct {
// TTL is the requested retention duration in milliseconds; a nil pointer
// omits it (the requestor expresses no preference).
TTL *int64
}
TaskMeta is the Dockyard domain view of the request-augmentation metadata — the `task` field a requestor adds to request params (`TaskMetadata`, brief 02 §2.3). It is NOT a `_meta` key; it is a top-level request param. It is carried here so the whole Tasks wire surface lives behind one seam.
type TaskStatus ¶
type TaskStatus string
TaskStatus is the MCP Tasks lifecycle status. The five values and their legal transitions are fixed by the schema (brief 02 §2.2).
const ( TaskWorking TaskStatus = "working" TaskInputRequired TaskStatus = "input_required" TaskCompleted TaskStatus = "completed" TaskFailed TaskStatus = "failed" TaskCancelled TaskStatus = "cancelled" )
The five task statuses. A task MUST begin in TaskWorking; TaskCompleted, TaskFailed and TaskCancelled are terminal and immutable.
func (TaskStatus) CanTransitionTo ¶
func (s TaskStatus) CanTransitionTo(to TaskStatus) bool
CanTransitionTo reports whether a task may move from status s to status to, per the schema's transition rules: working → {input_required, completed, failed, cancelled}; input_required → {working, completed, failed, cancelled}; terminal states have no outgoing transitions (brief 02 §2.2).
func (TaskStatus) IsTerminal ¶
func (s TaskStatus) IsTerminal() bool
IsTerminal reports whether s is a terminal task status.
func (TaskStatus) Valid ¶
func (s TaskStatus) Valid() bool
Valid reports whether s is one of the five spec-defined statuses.
type TasksServerCapability ¶
type TasksServerCapability struct {
// List gates `tasks/list`.
List bool
// Cancel gates `tasks/cancel`.
Cancel bool
// ToolsCall reports that the server accepts task-augmented `tools/call`.
ToolsCall bool
}
TasksServerCapability is the value Dockyard advertises for `capabilities.tasks` (brief 02 §2.6). Each bool gates an operation; the `requests` set is exhaustive — an absent request type means unsupported.
type ToolTaskSupport ¶
type ToolTaskSupport string
ToolTaskSupport is a tool's declared relationship to the Tasks extension, surfaced as `execution.taskSupport` in `tools/list` (brief 02 §2.6).
const ( TaskSupportForbidden ToolTaskSupport = "forbidden" TaskSupportOptional ToolTaskSupport = "optional" TaskSupportRequired ToolTaskSupport = "required" )
The three tool task-support values. Absent on the wire defaults to TaskSupportForbidden.
func (ToolTaskSupport) Valid ¶
func (t ToolTaskSupport) Valid() bool
Valid reports whether t is one of the three spec-defined values.