Documentation
¶
Overview ¶
Package tool is Dockyard's contract-first typed tool builder — the app-facing API an author uses to declare an MCP tool (RFC §6, brief 04 §3).
A tool is defined by two Go structs: its input contract and its output contract. They are the single source of truth (P1, RFC §6.1). The builder generates the tool's JSON Schema from those structs via internal/codegen and registers the tool on a runtime/server with the generated schema, so the schema a host sees is provably the one the contract describes — never a hand-written one that can silently drift (the mcp-use failure mode, brief §2.6).
Usage:
err := tool.New[ShowRevenueInput, ShowRevenueOutput]("show_revenue").
Describe("Render the revenue dashboard").
UI("revenue_card").
Handler(handleShowRevenue).
Register(srv)
The handler returns a Result[Out]: its Text is model-facing (content[]) and its Structured value is the typed, UI-facing payload (structuredContent), per RFC §6.3.
Note on shape: brief 04's sketch writes app.Tool("x").Input[T]().Output[T](). Go does not permit type parameters on methods, so the contract types are bound once by the package-level generic constructor New[In, Out]; the rest of the chain is plain methods. The fluent, contract-first ergonomics are preserved. See docs/decisions.md D-029.
Index ¶
- Constants
- Variables
- func MarshalSchema(s *jsonschema.Schema) ([]byte, error)
- type ArgumentError
- type Builder
- func (b *Builder[In, Out]) Describe(desc string) *Builder[In, Out]
- func (b *Builder[In, Out]) Flags() []Flag
- func (b *Builder[In, Out]) Handler(h Handler[In, Out]) *Builder[In, Out]
- func (b *Builder[In, Out]) Name() string
- func (b *Builder[In, Out]) Register(s *server.Server) error
- func (b *Builder[In, Out]) Schemas() (in, out *jsonschema.Schema, err error)
- func (b *Builder[In, Out]) UI(resourceName string, visibility ...string) *Builder[In, Out]
- func (b *Builder[In, Out]) UIResource() string
- type Flag
- type FlagKind
- type Handler
- type Result
Examples ¶
Constants ¶
const ( VisibilityModel = apps.VisibilityModel VisibilityApp = apps.VisibilityApp )
Visibility values for a tool's _meta.ui.visibility (RFC §7.1, brief 01 §2.3), re-exported from runtime/apps so a tool author sets visibility through the builder without importing runtime/apps. VisibilityModel makes a tool callable by the agent/model; VisibilityApp restricts it to same-server App-initiated calls (a UI-only action tool). Passing neither to UI leaves visibility unspecified — a host treats that as both.
const DefaultOutputSizeBudget = 256 * 1024
DefaultOutputSizeBudget is the conservative size budget, in bytes, the handler runtime uses to flag an oversized tool output. It is a heuristic, not an MCP protocol limit: a structuredContent payload larger than this is *flagged*, not rejected — a large-but-legitimate payload stays observable rather than blocked (RFC §6.3, the braindump's "oversized output payloads" caution; D-045).
Variables ¶
var ErrInvalidArguments = errors.New("dockyard/runtime/tool: invalid tool arguments")
ErrInvalidArguments is the sentinel for an edge argument-validation failure: incoming tool-call arguments that violate the tool's generated input schema. Callers branch with errors.Is(err, ErrInvalidArguments); the concrete error is an *ArgumentError carrying the offending tool's name and the detail.
Functions ¶
func MarshalSchema ¶
func MarshalSchema(s *jsonschema.Schema) ([]byte, error)
MarshalSchema serializes a tool-contract JSON Schema to deterministic, indented JSON — identical input always yields byte-identical output.
It is the public re-export of the codegen pipeline's deterministic schema marshaller (internal/codegen.Marshal). A scaffolded project lives in its own Go module and cannot import Dockyard's internal/ packages, but `dockyard generate` regenerates a project's per-contract schema files by `go run`-ing a small generator inside that project (Phase 18, D-081): that ephemeral generator obtains a tool's *jsonschema.Schema from Builder.Schemas and needs the same byte-stable marshalling the rest of the pipeline uses, so the regenerated file matches what `dockyard validate`'s stale-codegen check expects. MarshalSchema is that seam — exported here because runtime/tool is public and internal/codegen is not.
Determinism is what makes regeneration safe and `dockyard validate`'s stale-codegen drift detection meaningful (RFC §6.2): a real change in a contract surfaces as a visible diff, never as formatting churn.
Types ¶
type ArgumentError ¶
type ArgumentError struct {
// Tool is the wire name of the tool whose call was rejected.
Tool string
// Detail is the underlying schema-validation failure, human-readable.
Detail string
}
ArgumentError is the typed error the handler runtime produces when incoming tool-call arguments fail validation against the tool's generated input JSON Schema, at the catalog edge — before the handler runs. It wraps ErrInvalidArguments. Producing a typed error here means an invalid argument is a precise Dockyard diagnostic, never a panic and never a vague failure (RFC §5, AGENTS.md §5/§13; D-044).
func (*ArgumentError) Unwrap ¶
func (e *ArgumentError) Unwrap() error
Unwrap returns ErrInvalidArguments so errors.Is(err, ErrInvalidArguments) reports true for any ArgumentError.
type Builder ¶
type Builder[In, Out any] struct { // contains filtered or unexported fields }
Builder declares a single MCP tool in the contract-first style (RFC §6). The input and output contract types are bound by New; the fluent methods set the remaining metadata; Register generates the schema and installs the tool on a server. A Builder is not safe for concurrent use — build a tool, register it, then discard the Builder; independent Builders on independent servers may run concurrently.
func New ¶
New starts a contract-first tool declaration. In is the tool's input contract type and Out its output contract type — both must be object types (a struct or a string-keyed map), the MCP requirement for tool schemas. name is the tool's wire name and is required.
The type parameters are bound here, at construction, rather than by fluent .Input[T]()/.Output[T]() methods, because Go does not permit type parameters on methods (D-029).
Example ¶
ExampleNew shows the canonical contract-first tool declaration: bind the typed input + output contracts to New, set the description and the handler with the fluent builder, then Register the tool on a runtime/server.Server. The schema the host sees is generated from EchoInput / EchoOutput via internal/codegen.
package main
import (
"context"
"fmt"
"github.com/hurtener/dockyard/runtime/server"
"github.com/hurtener/dockyard/runtime/tool"
)
// EchoInput is the input contract for a contract-first echo tool — a
// typed Go struct. The JSON Schema the host sees is generated from
// THIS struct, not hand-written (Dockyard P1, RFC §6).
type EchoInput struct {
// Message is the text to echo back to the model.
Message string `json:"message"`
}
// EchoOutput is the matching output contract.
type EchoOutput struct {
// Echo is the echoed text.
Echo string `json:"echo"`
}
// ExampleNew shows the canonical contract-first tool declaration: bind
// the typed input + output contracts to New, set the description and
// the handler with the fluent builder, then Register the tool on a
// runtime/server.Server. The schema the host sees is generated from
// EchoInput / EchoOutput via internal/codegen.
func main() {
srv, _ := server.New(server.Info{Name: "echo-example", Version: "0.1.0"}, nil)
err := tool.New[EchoInput, EchoOutput]("echo").
Describe("Echo the input message back to the caller.").
Handler(func(_ context.Context, in EchoInput) (tool.Result[EchoOutput], error) {
return tool.Result[EchoOutput]{
Text: "echoed: " + in.Message,
Structured: EchoOutput{Echo: in.Message},
}, nil
}).
Register(srv)
if err != nil {
fmt.Println("register:", err)
return
}
fmt.Println("registered tools:", srv.Tools())
}
Output: registered tools: [echo]
func (*Builder[In, Out]) Describe ¶
Describe sets the tool description — the hint surfaced to the model.
func (*Builder[In, Out]) Flags ¶
Flags reports the routing flags — oversized outputs, misrouted UI payloads — raised by this tool's handler since Register, newest last (RFC §6.3; D-045). A flag is non-fatal: it never failed a tool call, it is recorded for inspection. The returned slice is a copy and safe for the caller to retain. Flags is safe to call concurrently with in-flight tool calls. It returns nil before Register installs the handler runtime.
func (*Builder[In, Out]) Register ¶
Register generates the tool's JSON Schema from its contract types and installs the tool on s. The registered tool's input and output schema is the generated schema — that is the contract-first guarantee (P1, RFC §6.1).
Register validates the builder is complete and the contract types are valid; it returns a typed error rather than panicking on any misuse.
func (*Builder[In, Out]) Schemas ¶
func (b *Builder[In, Out]) Schemas() (in, out *jsonschema.Schema, err error)
Schemas returns the generated input and output JSON Schemas for the tool's contract types, without registering anything. It is exported so the manifest (Phase 06) and the validate command (Phase 18) can obtain a tool's schema without a server. The returned schemas are the same ones Register installs.
func (*Builder[In, Out]) UI ¶
UI associates the tool with a ui:// App resource by the App's programmatic name (the name passed to apps.Register / the manifest app name). At Register time the builder resolves the name to the App's ui:// URI and emits _meta.ui.resourceUri on the tool definition (RFC §7.1) — so a host that renders MCP Apps links the tool result to its App. The App MUST be registered (apps.Register) before the tool, or Register fails loud rather than dropping the link silently (D-173).
Optional visibility (VisibilityModel / VisibilityApp) sets _meta.ui.visibility: who may invoke the tool. Passing none leaves it unspecified, which a host treats as both — the spec default. Pass VisibilityApp alone for a UI-only action tool (brief 01 §2.3).
tool.New[In, Out]("create_chart").UI("widgets") // model + app
tool.New[In, Out]("save_edits").UI("widgets", tool.VisibilityApp) // app-only
func (*Builder[In, Out]) UIResource ¶
UIResource reports the ui:// resource name set by UI, or "" if none. It is the read seam Phase 06's manifest and Phase 09's Apps layer use to discover the tool-to-UI wiring.
type Flag ¶
type Flag struct {
// Kind classifies the defect.
Kind FlagKind
// Tool is the wire name of the tool whose call raised the flag.
Tool string
// Detail is a human-readable explanation.
Detail string
// SizeBytes is the serialized payload size in bytes for FlagOversizeOutput;
// zero for kinds where size is not meaningful.
SizeBytes int
}
Flag is a typed, non-fatal handler-runtime signal: an oversized output or a misrouted payload. A Flag never fails the tool call — it is recorded so the defect is observable in Dockyard's own surfaces before a host ever sees the result (brief 03 R7, the same principle as typed _meta accessors). A future obs/v1 bridge consumes these; Phase 08 exposes them through Builder.Flags.
type FlagKind ¶
type FlagKind int
FlagKind classifies a handler-runtime payload-routing defect.
const ( // FlagOversizeOutput marks a tool output whose serialized structuredContent // exceeds the size budget (DefaultOutputSizeBudget). FlagOversizeOutput FlagKind = iota + 1 // FlagMisroutedContent marks model-facing Text that is itself UI-shaped // data — a JSON object or array — and so pollutes and inflates the model // context instead of being routed to structuredContent (RFC §6.3). FlagMisroutedContent )
type Handler ¶
Handler is a contract-first tool handler: it receives the typed, decoded and schema-validated input and returns a typed Result. 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 Result ¶
type Result[Out any] struct { // Text is the model-facing text rendered into content[]. Text string // Structured is the typed, UI-facing output rendered into // structuredContent. Its type is the tool's output contract. Structured Out // Meta is optional extension metadata rendered into _meta. Meta map[string]any }
Result is what a Dockyard tool handler returns: a small typed value the runtime maps onto a standard MCP CallToolResult (RFC §6.3).
- Text becomes content[] — model-facing text that enters the LLM context.
- Structured becomes structuredContent — the typed, UI-facing payload, kept out of the model context. Its shape is the tool's generated output schema.
- Meta becomes _meta — extension metadata (for example a viewUUID once the Apps layer lands).
Phase 04 ships this split so the contract-first builder is usable end to end. The full handler-runtime semantics — oversized-payload detection, the content/structuredContent validation warnings — are Phase 08's (RFC §6.3).