Documentation
¶
Overview ¶
Package server is the Dockyard app-runtime MCP server core.
It is the importable library half of Dockyard (RFC §3, §5): a generated Dockyard app's main.go is thin and delegates the protocol weight to this package. The package wraps the official MCP SDK (github.com/modelcontextprotocol/go-sdk, pinned per brief 03) rather than re-implementing the protocol — the SDK is the settled foundation (RFC §5.1, D-002) and Dockyard never forks it.
Phase 01 establishes the skeleton: constructing a server, registering a typed tool via AddTool, and serving over stdio.
Phase 04 adds the contract-first registration seam the runtime/tool builder composes (RFC §6, P1): AddToolWithSchemas registers a typed tool with caller-supplied input and output JSON Schemas — the schemas internal/codegen generates from the Go contract structs — so the registered tool's schema is provably the generated contract, not whatever the SDK would infer separately. Its handler returns a ToolOutput[Out] (a ToolOutputFunc), which splits the two channels of an MCP CallToolResult (RFC §6.3): Text is model-facing and lands in content[], Structured is the typed UI payload and lands in structuredContent, and Meta lands in _meta.
Phase 07 completes the MCP server core (RFC §5): typed resource registration (AddResource), the streamable-HTTP transport (HTTPHandler) alongside stdio, and the in-memory transport (ServeInMemory) for tests and the inspector. HTTP security — DNS-rebinding protection, Origin/Content-Type verification, and cross-origin protection — is set explicitly via HTTPSecurity and never inherited from an SDK default, because the SDK has flipped those defaults between releases (RFC §5.2, AGENTS.md §7, brief 03 §2.3). Phase 07 also retires the temporary exported MCP() SDK seam (D-021, D-042): the Dockyard-owned registration and transport surface is now complete.
Panic safety is a toolchain-enforced guarantee, not a docstring instruction (AGENTS.md §5, §13; D-053): every tool and resource handler invocation is routed through guardHandler, which recovers a panicking app-author handler into a typed error (ErrHandlerPanic) so a bad handler on a live tools/call or resources/read degrades into a clean error result instead of crashing the server process. AddResourceTemplate (D-054) registers an RFC 6570 URI-template family — the typed surface the Apps layer's ui:// auto-discovery composes — with the same panic-recovered handler invocation as AddResource.
Phase 28 adds the prompts surface (AddPrompt). Prompts are templates the host PULLS via prompts/get; the Phase 28 API is a thin pass-through to the SDK's prompt registration with the same panic recovery and the same obs/v1 lifecycle every other Dockyard handler gets (obs.KindPromptGet). Dockyard's contract-first pattern does not extend naturally to prompts — MCP prompt arguments are typed as a flat string map, not a structured object — so AddPrompt is a focused, registration-only surface (D-152) rather than a contract-first builder.
The Apps and Tasks extension layers and the obs/v1 stream land in later phases (RFC §5.3, §7, §8, §11); the seams here are kept deliberately small so those phases extend without reshaping this package.
Index ¶
- Variables
- func AddPrompt(s *Server, def PromptDef, fn PromptHandler) error
- func AddTool[In, Out any](s *Server, def ToolDef, fn ToolFunc[In, Out]) error
- func AddToolWithSchemas[In, Out any](s *Server, def ToolDef, in, out *jsonschema.Schema, fn ToolOutputFunc[In, Out]) error
- func RawArguments(ctx context.Context) json.RawMessage
- func WithRawArguments(ctx context.Context, raw json.RawMessage) context.Context
- type AppLink
- type ExtensionCapability
- type HTTPOptions
- type HTTPSecurity
- type Info
- type LogBridge
- type LogLevel
- type LogRecord
- type Options
- type PromptArgument
- type PromptDef
- type PromptHandler
- type PromptMessage
- type PromptRequest
- type PromptResult
- type ResourceContent
- type ResourceDef
- type ResourceFunc
- type ResourceTemplateDef
- type Server
- func (s *Server) AddResource(def ResourceDef, fn ResourceFunc) error
- func (s *Server) AddResourceTemplate(def ResourceTemplateDef, fn ResourceFunc) error
- func (s *Server) AppLinkByName(name string) (AppLink, bool)
- func (s *Server) EmitLegacyToolUIMeta() bool
- func (s *Server) HTTPHandler(opts *HTTPOptions) (http.Handler, error)
- func (s *Server) Info() Info
- func (s *Server) LogBridge() *LogBridge
- func (s *Server) Prompts() []string
- func (s *Server) Recorder() *obs.Recorder
- func (s *Server) RegisterAppLink(name string, link AppLink) error
- func (s *Server) ResourceTemplates() []string
- func (s *Server) Resources() []string
- func (s *Server) Run(ctx context.Context, t mcpsdk.Transport) error
- func (s *Server) ServeInMemory(ctx context.Context) mcpsdk.Transport
- func (s *Server) ServeStdio(ctx context.Context) error
- func (s *Server) TasksEnabled() bool
- func (s *Server) Tools() []string
- func (s *Server) WithTasks(engine *tasks.Engine, authContext tasks.AuthContextFunc) *Server
- type ToolDef
- type ToolFunc
- type ToolOutput
- type ToolOutputFunc
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ErrHandlerPanic = fmt.Errorf("dockyard/runtime/server: handler panicked")
ErrHandlerPanic is the sentinel every recovered handler panic wraps. A caller can branch with errors.Is(err, ErrHandlerPanic); the rendered message names the offending tool or resource and the recovered value.
A panicking tool or resource handler is a bug in the app author's code, not a protocol condition — but the "no panic across the MCP boundary" rule (AGENTS.md §5, §13) demands the server process survive it. The handler wrappers recover the panic and convert it into this error so a single bad handler degrades one tools/call or resources/read into a clean error result instead of crashing the server for every connected host.
var ErrNoTransport = errors.New("dockyard/runtime/server: nil transport")
ErrNoTransport is returned by Run when it is called with a nil transport.
Functions ¶
func AddPrompt ¶
func AddPrompt(s *Server, def PromptDef, fn PromptHandler) error
AddPrompt registers an MCP Prompt on the server. It must be called before Run / ServeStdio / HTTPHandler. The runtime emits an obs/v1 `prompt.get` lifecycle pair on every prompts/get invocation (RFC §11.2, P2) and recovers any handler panic so the server process survives a bad handler (AGENTS.md §5, §13).
AddPrompt is a thin, focused pass-through to the SDK's prompt registration rather than a contract-first builder (D-152): MCP prompt arguments are typed as flat string maps, not structured objects, so the Go struct → JSON Schema pattern that backs AddTool / runtime/tool.New does not extend naturally. A Dockyard developer who needs a richer argument shape composes the prompt of multiple smaller prompts, or wraps the typed call in a contract-first tool that internally drives the prompt — the same idiom the MCP spec encourages.
Registration is not safe for concurrent calls; a server is assembled once before Run, then served.
Example ¶
ExampleAddPrompt shows the Phase 28 prompts API: a thin pass-through to the SDK's prompt registration with the same panic recovery + the obs/v1 prompt.get lifecycle every Dockyard handler gets.
Prompts are templates the host PULLS via prompts/get; a host might surface them as `/slash` commands. The argument map is flat strings (MCP's prompt-argument shape) — Dockyard's contract-first pattern does not extend to prompts (D-152), so AddPrompt is a focused pass-through rather than a typed-contract builder.
package main
import (
"context"
"fmt"
"github.com/hurtener/dockyard/runtime/server"
)
func main() {
srv, _ := server.New(server.Info{Name: "prompts-example", Version: "0.1.0"}, nil)
err := server.AddPrompt(srv, server.PromptDef{
Name: "summarize",
Title: "Summarise a passage",
Description: "Two-sentence summary fit for an engineering peer.",
Arguments: []server.PromptArgument{
{Name: "passage", Required: true},
},
}, func(_ context.Context, req server.PromptRequest) (server.PromptResult, error) {
return server.PromptResult{
Messages: []server.PromptMessage{
{Role: "system", Text: "You are a careful summariser."},
{Role: "user", Text: "Please summarise:\n" + req.Arguments["passage"]},
},
}, nil
})
if err != nil {
fmt.Println("add prompt:", err)
return
}
fmt.Println("prompts:", srv.Prompts())
}
Output: prompts: [summarize]
func AddTool ¶
AddTool registers a typed tool on the server. It must be called before Run. In and Out must each be a struct (or map) so the inferred input schema has JSON type "object", as the MCP spec requires.
AddTool is a package function rather than a method because Go does not allow type parameters on methods; this mirrors the SDK's own mcp.AddTool.
func AddToolWithSchemas ¶
func AddToolWithSchemas[In, Out any]( s *Server, def ToolDef, in, out *jsonschema.Schema, fn ToolOutputFunc[In, Out], ) error
AddToolWithSchemas registers a typed tool whose input and output JSON Schemas are supplied by the caller rather than inferred by the SDK at registration time. It is the seam the contract-first tool builder (runtime/tool, Phase 04) composes: the builder generates the schema from the Go contract struct via internal/codegen and hands it here, so the registered tool's schema is guaranteed to be the generated schema — the contract-first guarantee (P1, RFC §6.1), not whatever the SDK would infer separately.
The handler returns a ToolOutput, so the builder controls the content/structuredContent split (RFC §6.3): ToolOutput.Text lands in content[], ToolOutput.Structured in structuredContent, ToolOutput.Meta in _meta.
Either schema may be nil, in which case the SDK falls back to inferring it from In/Out (the same behaviour as AddTool). When non-nil, a schema must have JSON type "object" — the MCP spec's requirement for tool input/output schemas.
In and Out must still be structs (or maps) so the SDK can decode arguments into In and encode Out into structuredContent.
func RawArguments ¶
func RawArguments(ctx context.Context) json.RawMessage
RawArguments returns the raw, undecoded JSON arguments of the in-flight tool call, or nil if none are available (the call carried no arguments, or ctx is not a tool-handler context).
It is the seam the contract-first handler runtime (runtime/tool, Phase 08) uses to validate incoming arguments against the tool's generated input JSON Schema *at the catalog edge* — before the typed handler runs — so a schema-violating argument becomes a typed Dockyard error rather than a vague failure (RFC §5, §6.3). The returned slice is the handler's to read, not to retain past the call.
func WithRawArguments ¶
WithRawArguments returns a copy of ctx carrying raw, undecoded tool-call arguments retrievable via RawArguments. AddToolWithSchemas calls it on every tool-handler invocation; it is also exported so an in-process invoker of the handler runtime — the inspector, a contract test — can drive edge validation without an over-the-wire call. Passing nil or empty args leaves ctx unchanged.
Types ¶
type AppLink ¶ added in v1.5.0
type AppLink struct {
// URI is the App's ui:// resource URI.
URI string
// Domain is the App's host-supplied dedicated origin (_meta.ui.domain), or
// "" when the App declares none. The server records it only to power the
// stdio-only startup warning (warnDomainOnStdio): a dedicated origin is
// honoured on a remote connector and ignored on a local (stdio) one
// (D-176). It is a plain string, not an Apps wire type (P3).
Domain string
}
AppLink is the server-recorded link from an App's programmatic name to its ui:// resource. runtime/apps.Register records one per App; the runtime/tool builder reads it to resolve a tool's .UI(name) to the App's URI and emit _meta.ui (RFC §7.1; D-173). It is a small, protocol-agnostic value — the server stores no Apps wire types (P3).
type ExtensionCapability ¶
type ExtensionCapability struct {
// Name is the registry-scoped extension identifier, e.g.
// "io.modelcontextprotocol/ui".
Name string
// Settings is the per-extension settings object, as opaque wire JSON. A
// nil/empty value advertises the extension with no settings object.
Settings json.RawMessage
}
ExtensionCapability is one MCP extension-capability advertisement: a registry-scoped extension name and its opaque settings JSON. It is the runtime-facing carrier for the SEP-2133 `extensions` capability block (RFC §5.3) — Settings is produced by internal/protocolcodec, so the runtime surface never exposes a raw protocol struct (P3).
type HTTPOptions ¶
type HTTPOptions struct {
// Security is the explicit HTTP security posture. The zero value of
// HTTPSecurity has all protections OFF; HTTPHandler substitutes
// DefaultHTTPSecurity when Security is the zero value, so an app that does
// not opt out is secure by default.
Security HTTPSecurity
// ServerForRequest is the per-request server seam (the SDK's getServer
// callback, RFC §5.2): it is invoked once per incoming HTTP request to
// select the Server that handles it, enabling per-session or multi-tenant
// wiring. When nil, every request is served by the receiver Server.
ServerForRequest func(*http.Request) *Server
// Stateless serves each request with a fresh, default-initialized session
// and no Mcp-Session-Id validation. Off by default.
Stateless bool
}
HTTPOptions configures the streamable-HTTP transport. A nil *HTTPOptions is treated as the zero value with DefaultHTTPSecurity.
type HTTPSecurity ¶
type HTTPSecurity struct {
// DNSRebindingProtection rejects requests arriving via a localhost address
// whose Host header is non-localhost — the SDK's localhost-protection
// guard against DNS-rebinding attacks. Dockyard expresses it as a
// positive-sense flag and maps it explicitly onto the SDK's negative
// DisableLocalhostProtection knob (D-040).
DNSRebindingProtection bool
// CrossOriginProtection rejects non-safe cross-origin browser requests
// (CSRF protection). Applied as net/http.CrossOriginProtection middleware
// wrapping the SDK handler — the SDK's own field is deprecated in v1.6.0
// in favour of this approach (D-041).
CrossOriginProtection bool
// ContentTypeVerification rejects a POST whose request-body Content-Type is
// not the JSON media type (application/json) the MCP streamable-HTTP
// transport mandates. Dockyard verifies this EXPLICITLY rather than relying
// on whatever the linked go-sdk happens to enforce — SDK security defaults
// have flipped between releases (AGENTS.md §7, D-112). Applied as Dockyard
// middleware wrapping the SDK handler; a violating POST gets a 415.
ContentTypeVerification bool
// TrustedOrigins are origins exempted from cross-origin protection — for
// example a known App host. Each must be a scheme://host[:port] origin.
// Ignored when CrossOriginProtection is false.
TrustedOrigins []string
}
HTTPSecurity is the explicit security posture for the streamable-HTTP transport (RFC §5.2, AGENTS.md §7). Every field is set deliberately by Dockyard rather than inherited from an SDK default: the SDK has flipped security-relevant defaults between releases — cross-origin protection was on in v1.4.1 and off again in v1.6.0 (brief 03 §2.3) — so a Dockyard HTTP deployment must never depend on whatever the linked SDK happens to default to. The zero value has all protections OFF and is deliberately *not* the recommended posture; use DefaultHTTPSecurity for the secure default.
func DefaultHTTPSecurity ¶
func DefaultHTTPSecurity() HTTPSecurity
DefaultHTTPSecurity returns the recommended secure posture for an HTTP deployment: DNS-rebinding protection, cross-origin protection, and Content-Type verification all ON, with no trusted-origin exemptions. This is the value a Dockyard app uses unless it has a specific reason to relax a protection.
type Info ¶
type Info struct {
// Name is the programmatic server identifier. Required.
Name string
// Title is the human-readable display name. Optional.
Title string
// Version is the app's semantic version. Required.
Version string
}
Info identifies a Dockyard app to connecting MCP hosts. It maps onto the SDK's mcp.Implementation; Dockyard keeps its own type so the runtime-facing API never exposes raw SDK structs to app authors (RFC §5.4, P3).
type LogBridge ¶
type LogBridge struct {
// contains filtered or unexported fields
}
LogBridge bridges the MCP logging capability into obs/v1. A Dockyard server owns exactly one; obtain it via Server.LogBridge. It is a reusable concurrent artifact — Log is safe from many goroutines (the obs.Recorder and the SDK ServerSession both are).
func (*LogBridge) Log ¶
Log delivers rec to a client as a standard MCP notifications/message AND emits it as an obs/v1 log event. It is the bridge's primary entry point: a tool handler calls it with its handler context, and the bridge resolves the in-flight MCP ServerSession from that context (threaded by runtime/server) — the typed handler never touches a raw SDK session (P3).
When ctx carries no session (a record logged outside a request) the MCP notifications/message delivery is skipped but the obs/v1 log event is still emitted, so an out-of-request record is still observable.
The standard MCP path is unchanged: ServerSession.Log applies the client's negotiated minimum level exactly as before — a client that called SetLevel receives notifications/message per spec, a client that did not receives nothing on the MCP channel. The obs/v1 log event is independent of the client's MCP log level: the inspector sees every server log record.
Any MCP delivery error is returned so a caller can react, but it does NOT suppress the obs/v1 log event — observability never fails a request (P2).
func (*LogBridge) LogTo ¶
LogTo is LogBridge.Log with an explicitly supplied MCP ServerSession, rather than one resolved from the context. It is the lower-level entry point for a caller that already holds a session (e.g. a non-tool subsystem). A nil sess skips the MCP notifications/message delivery; the obs/v1 log event is still emitted.
type LogLevel ¶
type LogLevel string
LogLevel is the RFC 5424 severity of a server log record — the level set shared by MCP logging and the obs/v1 log event. It is a Dockyard-owned type so the server-facing API never exposes a raw SDK type (RFC §5.4, P3).
const ( LogDebug LogLevel = "debug" LogInfo LogLevel = "info" LogNotice LogLevel = "notice" LogWarning LogLevel = "warning" LogError LogLevel = "error" LogCritical LogLevel = "critical" LogAlert LogLevel = "alert" LogEmergency LogLevel = "emergency" )
The RFC 5424 severities MCP logging uses (see the MCP logging capability and the go-sdk LoggingLevel set).
type LogRecord ¶
type LogRecord struct {
// Level is the RFC 5424 severity. An empty or unknown level is treated as
// LogInfo.
Level LogLevel
// Logger is the optional logger name carried on both the MCP record and the
// obs/v1 log event.
Logger string
// Message is the log message.
Message string
// Data is optional structured detail attached to the MCP
// notifications/message `data` field. The obs/v1 log event carries the
// message and level only (LogPayload) — structured data capture stays
// shape-bounded and is out of obs/v1 log-event scope.
Data any
}
LogRecord is one server log record. It is the bridge's input: the same record is delivered to a client as an MCP notifications/message AND emitted as an obs/v1 log event.
type Options ¶
type Options struct {
// Logger receives the server's structured logs. When nil, slog.Default()
// is used. AGENTS.md §5 mandates log/slog.
Logger *slog.Logger
// Extensions are the MCP extension capabilities the server advertises
// during the initialize handshake (the SEP-2133 `extensions` capability
// block; RFC §5.3). The Apps layer (runtime/apps, Phase 09) supplies the
// io.modelcontextprotocol/ui entry here. A nil/empty slice advertises no
// extensions — the server behaves as a plain MCP server.
//
// Each entry's Settings is opaque wire JSON produced by the owning
// extension layer through internal/protocolcodec; the runtime never
// inspects it, preserving the protocolcodec isolation seam (P3, RFC §5.4).
Extensions []ExtensionCapability
// Obs is the obs/v1 observability emitter the server emits tool, resource,
// and lifecycle events to (RFC §11, P2). A nil emitter disables emission —
// the server is headless either way; observability is a protocol the
// runtime PRODUCES, never a back channel anything reads (CLAUDE.md §6).
//
// The runtime depends only on the obs.Emitter interface: the ring-buffer
// driver (Phase 15), the SSE sink and the OTel adapter (Phase 16) all plug
// in here behind the same seam (CLAUDE.md §4.4).
Obs obs.Emitter
// CapturePolicy controls how much of a tool's input/output an emitted
// event carries. The default — the zero value, obs.CapturePolicyShape —
// captures shape + size only, never full content (CLAUDE.md §7).
CapturePolicy obs.CapturePolicy
// Redactor is the redaction-aware hook obs.CapturePolicyFull requires.
// Without it, full-content capture degrades to shape+size.
Redactor obs.Redactor
// Tasks is the MCP Tasks engine to mount onto the server's transports
// (RFC §8.2). When non-nil the server intercepts tasks/* JSON-RPC frames
// at the raw-frame layer ahead of the SDK server — the go-sdk rejects an
// unknown JSON-RPC method before any middleware runs, so tasks/get,
// tasks/result, tasks/cancel and tasks/list cannot reach the engine
// through the SDK's dispatch table; the Tasks transport mount
// (runtime/tasks.Mount) is the "shim, by necessity" that routes them.
// The capabilities.tasks block is injected into the initialize handshake
// response (the SDK has no native field for it).
//
// When Tasks is nil the server behaves exactly as a plain MCP server with
// no tasks/* interception and no added overhead. The engine is attached
// via WithTasks too — Tasks is the option-struct idiom matching Obs.
Tasks *tasks.Engine
// TasksAuthContext derives a requestor's opaque authorization-context
// token from an HTTP request — for example a verified bearer-token
// subject. It is consulted only when Tasks is non-nil and the server
// serves over the streamable-HTTP transport, enabling auth-context
// binding of tasks/* (RFC §8.5). A nil value treats every requestor as
// unauthenticated, which is the correct posture for single-user stdio.
TasksAuthContext tasks.AuthContextFunc
// EmitLegacyToolUIMeta opts the server into additionally emitting the
// DEPRECATED flat tool-UI _meta key alongside the canonical nested
// `_meta.ui.resourceUri` on every UI-bearing tool registered through the
// runtime/tool builder (D-177). The default (false) is RFC-compliant
// nested-only output (RFC §7.1, brief 01 §2.3); the 2026-01-26 MCP Apps spec
// marks the flat form deprecated, so Dockyard never emits it by default.
// Turn it on only when targeting a host that still reads the flat key — it
// is a wire-compat escape hatch, not a recommended posture. The flat key's
// literal lives only in internal/protocolcodec (P3).
EmitLegacyToolUIMeta bool
}
Options tunes a Server. The zero value is valid; a nil *Options is treated as the zero value.
type PromptArgument ¶
type PromptArgument struct {
// Name is the argument's wire name.
Name string
// Title is the optional human-facing label.
Title string
// Description is the optional human-readable description.
Description string
// Required reports whether the host must supply this argument.
Required bool
}
PromptArgument describes one argument an MCP Prompt accepts.
MCP prompts (RFC §6 — adjacent to tools and resources) carry a string-valued argument list rather than a typed input contract: the host renders the prompt template, fills the arguments from the user / model, and pulls the rendered messages back via prompts/get. Because the argument shape is a flat list of named strings (not a structured JSON object), Dockyard's P1 contract-first pattern — typed Go struct → JSON Schema — does not extend naturally to prompts (D-152). The PromptArgument shape mirrors mcp.PromptArgument verbatim so a developer reads the same fields the spec documents.
type PromptDef ¶
type PromptDef struct {
// Name is the prompt's wire identifier; required.
Name string
// Title is the optional human-facing display label.
Title string
// Description is an optional model-facing summary.
Description string
// Arguments is the prompt's argument schema. Optional — a prompt with
// no arguments renders a fixed template.
Arguments []PromptArgument
}
PromptDef describes an MCP Prompt to register. Name is required.
type PromptHandler ¶
type PromptHandler func(ctx context.Context, req PromptRequest) (PromptResult, error)
PromptHandler renders a prompt: the host's prompts/get call carries the request arguments, and the handler returns the rendered messages.
A handler returns its typed result and an error. Returning a non-nil error surfaces as an MCP prompts/get error to the host; a handler must never panic across the MCP boundary (AGENTS.md §5, §13) — Dockyard recovers the panic and converts it into a typed Dockyard error.
type PromptMessage ¶
type PromptMessage struct {
// Role is the message role: "user" | "assistant" | "system".
Role string
// Text is the message body. Empty text emits an empty TextContent
// block, which the spec permits and the inspector renders as a blank
// message.
Text string
}
PromptMessage is one message in a rendered prompt — a role plus a text body. The wire shape carries richer Content variants (resource embeds, images, …); Dockyard exposes the text-only shape for V1 because the in-tree consumer (the inspector + a host that renders the prompt as a chat-message seed) needs nothing more, and the typed shape stays small and explainable. A future phase that needs richer content adds a dedicated variant rather than leaking the SDK's wireContent here (P3).
type PromptRequest ¶
type PromptRequest struct {
// Name is the prompt name the host asked for.
Name string
// Arguments is the host-supplied argument map; nil when none were sent.
// Values are always strings — the MCP spec scopes prompt arguments to
// string values.
Arguments map[string]string
}
PromptRequest is the arguments a host supplied with a prompts/get call. It is the Dockyard-facing view of mcp.GetPromptParams, so a handler signature never imports the raw SDK type (P3 — RFC §5.4).
type PromptResult ¶
type PromptResult struct {
// Description is an optional rendered-description override. When empty
// the registered PromptDef.Description is used.
Description string
// Messages are the rendered messages in the order they should be
// presented. At least one message is the conventional shape, but the
// spec does not require it; an empty Messages slice surfaces as an
// empty `messages` array to the host.
Messages []PromptMessage
}
PromptResult is what a prompts/get handler returns: an optional description plus the rendered messages.
type ResourceContent ¶
type ResourceContent struct {
// MIMEType is the media type of the content. When empty, the registered
// ResourceDef.MIMEType is used.
MIMEType string
// Text is the textual content of the resource.
Text string
// Blob is the binary content of the resource. When non-empty it takes
// precedence over Text.
Blob []byte
// Meta is the `_meta` object attached to this resource-read content entry.
// It is the choke point the MCP Apps spec mandates: a host reads
// `_meta.ui.csp` and `_meta.ui.domain` from the resources/read *response*,
// not only the static resource declaration (brief 01 §2.2). The Apps layer
// (runtime/apps, Phase 09) sets it through internal/protocolcodec; the
// runtime copies it verbatim onto the read reply and never inspects it
// (P3, RFC §5.4). A nil map yields a read reply with no `_meta`.
Meta map[string]any
}
ResourceContent is the body of a resource read. It is the runtime-facing return type for a ResourceFunc: it carries either Text or Blob, never raw SDK structs, so the runtime surface stays free of protocol types (P3, RFC §5.4).
Exactly one of Text or Blob is meaningful for a given resource; when both are set, Blob takes precedence.
type ResourceDef ¶
type ResourceDef struct {
// URI is the resource's address. Required, and must be absolute (carry a
// scheme), as the MCP spec requires.
URI string
// Name is the programmatic resource identifier. Required.
Name string
// Title is the human-readable display name. Optional.
Title string
// Description is a hint surfaced to the model. Optional.
Description string
// MIMEType is the resource's media type, if known. Optional.
MIMEType string
// Meta is the resource declaration's `_meta` object — the metadata a host
// sees in resources/list. The Apps layer (runtime/apps, Phase 09) supplies
// `_meta.ui` here; the runtime copies it verbatim and never inspects it
// (P3, RFC §5.4). Note that the MCP Apps spec reads CSP/domain from the
// resources/read *response*, so the choke point is ResourceContent.Meta —
// this field carries the static declaration only (brief 01 §2.2).
Meta map[string]any
}
ResourceDef describes a resource to register on the server. URI and Name are required; the rest are hints surfaced to MCP hosts (RFC §5.3).
A resource is a server-provided piece of content addressable by URI. Wave 4's MCP Apps work serves an App's HTML bundle as a resource under the ui:// scheme (RFC §7.1, brief 01); Phase 07 lands the typed registration surface so that layer composes the Dockyard runtime rather than reaching past it to the raw SDK (P3, RFC §5.4).
type ResourceFunc ¶
type ResourceFunc func(ctx context.Context, uri string) (ResourceContent, error)
ResourceFunc reads a resource. It receives the requested URI — useful when a handler is registered for a family of URIs — and returns the resource content or an error. A handler must never panic across the MCP boundary (AGENTS.md §5, §13).
type ResourceTemplateDef ¶
type ResourceTemplateDef struct {
// URITemplate is the RFC 6570 URI template the family is addressed by, e.g.
// "ui://customer-health/{view}". Required, and must be absolute (carry a
// scheme), as the MCP spec requires.
URITemplate string
// Name is the programmatic template identifier. Required.
Name string
// Title is the human-readable display name. Optional.
Title string
// Description is a hint surfaced to the model. Optional.
Description string
// MIMEType is the media type shared by every resource the template matches,
// when they all share one. Optional.
MIMEType string
// Meta is the template declaration's `_meta` object — the metadata a host
// sees in resources/templates/list. The Apps layer supplies `_meta.ui` here;
// the runtime copies it verbatim and never inspects it (P3, RFC §5.4).
Meta map[string]any
}
ResourceTemplateDef describes a resource template to register on the server. A resource template serves a *family* of resources addressed by an RFC 6570 URI template rather than a single fixed URI — the shape the Apps layer (Phase 10) uses to register a `ui://` family without enumerating every member (RFC §5.1, brief 03 §2.2). URITemplate and Name are required; the rest are hints surfaced to MCP hosts (RFC §5.3).
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is the Dockyard app-runtime MCP server. It wraps an SDK *mcp.Server and is the seam later phases extend with the Apps, Tasks, and obs/v1 layers (RFC §5). A Server is safe to construct once and serve repeatedly; tool and resource registration happens before Run.
func New ¶
New constructs a Dockyard MCP server. It returns an error rather than panicking so a thin app main.go can fail cleanly (AGENTS.md §5: never panic across the MCP boundary).
When opts.Extensions is non-empty the server advertises those MCP extension capabilities during the initialize handshake (RFC §5.3); the SDK's inferred tools/resources capabilities are preserved — only the explicit extension block is added.
Example ¶
ExampleNew is the minimal "construct + serve over stdio" recipe. Construct the server with an Info identity, optionally pass Options (logger, obs emitter, Tasks engine), then call srv.ServeStdio under a context the caller cancels on shutdown.
package main
import (
"fmt"
"github.com/hurtener/dockyard/runtime/server"
)
func main() {
srv, err := server.New(server.Info{
Name: "example-server",
Title: "Example Server",
Version: "0.1.0",
}, nil)
if err != nil {
fmt.Println("new:", err)
return
}
fmt.Println(srv.Info().Name)
}
Output: example-server
func (*Server) AddResource ¶
func (s *Server) AddResource(def ResourceDef, fn ResourceFunc) error
AddResource registers a resource on the server. It must be called before Run. The URI must be absolute (carry a scheme); a duplicate URI is rejected.
AddResource is a method (unlike AddTool) because a resource is not generic over typed contracts — its body is opaque content addressed by URI.
func (*Server) AddResourceTemplate ¶
func (s *Server) AddResourceTemplate(def ResourceTemplateDef, fn ResourceFunc) error
AddResourceTemplate registers a resource template on the server — a family of resources addressed by an RFC 6570 URI template. It must be called before Run. The URI template must be absolute (carry a scheme); a duplicate template is rejected.
It is consistent with AddResource: a typed ResourceTemplateDef, the same ResourceFunc handler shape (the handler receives the concrete URI a host requested, not the template), the same panic-recovered handler invocation, and no raw SDK struct on the surface (P3, RFC §5.4).
func (*Server) AppLinkByName ¶ added in v1.5.0
AppLinkByName returns the link recorded for an App name, or ok=false when no App was registered under that name. The runtime/tool builder uses it to wire a tool's _meta.ui and to fail loud when .UI(name) references no App.
func (*Server) EmitLegacyToolUIMeta ¶ added in v1.6.0
EmitLegacyToolUIMeta reports whether the server opted into emitting the deprecated flat tool-UI _meta key alongside the nested form (Options.EmitLegacyToolUIMeta; D-177). The runtime/tool builder reads it when wiring a tool's _meta.ui so the server-level opt-in threads to the encoder without a per-builder parameter. Default false — RFC-compliant nested-only.
func (*Server) HTTPHandler ¶
func (s *Server) HTTPHandler(opts *HTTPOptions) (http.Handler, error)
HTTPHandler returns an http.Handler that serves the MCP protocol over the streamable-HTTP transport (RFC §5.2). Mount it on an *http.Server.
Security is set explicitly from opts.Security (see HTTPSecurity); the returned handler never inherits an SDK security default. Cross-origin protection, when enabled, is applied as standard net/http middleware wrapping the SDK handler (D-041); Content-Type verification, when enabled, is applied as Dockyard middleware (D-112) — both are Dockyard's own posture, never delegated to the linked SDK.
func (*Server) LogBridge ¶
LogBridge returns the server's MCP-logging → obs/v1 bridge. It is never nil; a server constructed without an obs emitter still returns a usable bridge whose obs side discards events (the Recorder is a NopEmitter) while the MCP notifications/message side is unaffected.
func (*Server) Prompts ¶
Prompts returns the names of registered prompts, in registration order (Phase 28). The returned slice is a copy and safe for the caller to retain. Empty when no prompt has been registered.
func (*Server) Recorder ¶
Recorder returns the server's obs/v1 emit helper. It is never nil. Extension layers (runtime/apps, runtime/tasks) emit their own obs/v1 events through it so every subsystem shares one emitter and one server identity — they EMIT, they never read each other's internals (P2, CLAUDE.md §6).
func (*Server) RegisterAppLink ¶ added in v1.5.0
RegisterAppLink records the link from an App's name to its ui:// resource. runtime/apps.Register calls it after the App's resource is installed, so a tool registered later can resolve .UI(name) → URI. It returns a typed error on a duplicate name — two Apps registered under one name is a programming error caught at registration, not a silent overwrite. Like tool/resource registration it is called during single-threaded setup.
func (*Server) ResourceTemplates ¶
ResourceTemplates returns the URI templates of registered resource templates, in registration order. The returned slice is a copy and safe for the caller to retain.
func (*Server) Resources ¶
Resources returns the URIs of registered resources, in registration order. The returned slice is a copy and safe for the caller to retain.
func (*Server) Run ¶
Run serves the MCP protocol over the given transport until the context is cancelled or the peer disconnects. ServeStdio wires stdio; HTTPHandler wires streamable-HTTP; ServeInMemory wires the in-memory transport (RFC §5.2).
func (*Server) ServeInMemory ¶
ServeInMemory serves the server over an in-memory transport and returns the matching client-side transport (RFC §5.2). It is the backbone of the inspector and the contract tests (brief 03 §2.3): no OS pipe, no socket — the two transports are connected in process.
ServeInMemory starts the server in a background goroutine bound to ctx and returns once the server is connected, so the caller can immediately connect a client to the returned transport. The server stops when ctx is cancelled. Any serve error is logged; callers that need it should use Run directly.
func (*Server) ServeStdio ¶
ServeStdio serves the server over the stdio transport — the local deployment mode (RFC §5.2). It blocks until ctx is cancelled or the host closes the pipe.
When a Tasks engine is attached (Options.Tasks / WithTasks) the stdio path runs the Tasks transport mount: tasks/* JSON-RPC frames on stdin are intercepted and answered by the engine, every other frame is forwarded to the SDK server (RFC §8.2). With no Tasks engine attached the SDK serves stdin/stdout directly, exactly as before.
func (*Server) TasksEnabled ¶
TasksEnabled reports whether a Tasks engine is attached — true once Options.Tasks or WithTasks has supplied one. It lets a transport entrypoint and a smoke check observe the wiring without reaching into server internals.
func (*Server) Tools ¶
Tools returns the names of registered tools, in registration order. The returned slice is a copy and safe for the caller to retain.
func (*Server) WithTasks ¶
WithTasks attaches a Tasks engine to the server, mounting tasks/* onto its transports (RFC §8.2). It is the imperative counterpart of Options.Tasks for a caller that constructs the server before the engine exists; calling it replaces any previously attached engine. authContext derives an HTTP requestor's authorization context (RFC §8.5) — pass nil for the unauthenticated single-user case. WithTasks must be called before Run / ServeStdio / HTTPHandler; it is not safe to call concurrently with serving.
type ToolDef ¶
type ToolDef struct {
Name string
Description string
// Meta is the tool definition's `_meta` object — the metadata a host sees
// in tools/list, distinct from a CallToolResult's `_meta`. The Apps layer
// (runtime/apps, Phase 09) supplies `_meta.ui` here to link a tool to its
// ui:// resource (RFC §7.1). The map is opaque wire metadata built through
// internal/protocolcodec; the runtime copies it verbatim onto the
// registered tool and never inspects it (P3, RFC §5.4). A nil map leaves
// the tool with no `_meta`.
Meta map[string]any
}
ToolDef describes a tool to register. Name is required; Description is a hint surfaced to the model.
type ToolFunc ¶
ToolFunc is a Dockyard tool handler. It is generic over a typed input and a typed output struct — the contract-first shape (RFC §6, P1): the SDK infers the JSON Schema from In and Out and validates incoming arguments before the handler runs. The full contract-first builder (app.Tool(...).Input[T]()...) lands in Phase 04; Phase 01 ships the minimal typed registration it sits on.
A handler returns its typed output and an error. Returning a non-nil error surfaces as an MCP tool error to the host; a handler must never panic across the MCP boundary (AGENTS.md §5, §13).
type ToolOutput ¶
ToolOutput is the result of a contract-first tool handler. It splits the two channels of an MCP CallToolResult (RFC §6.3): Text is model-facing and lands in content[]; Structured is the typed, UI-facing payload and lands in structuredContent; Meta lands in _meta.
It is the seam the contract-first tool builder (runtime/tool, Phase 04) uses so the builder controls the content/structuredContent routing without reaching past the runtime into the raw SDK result type (P3 — the runtime surface does not expose raw protocol structs).
type ToolOutputFunc ¶
type ToolOutputFunc[In, Out any] func(ctx context.Context, in In) (ToolOutput[Out], error)
ToolOutputFunc is a tool handler that returns the full ToolOutput split.