Documentation
¶
Overview ¶
Package registry — typed manifest of every clawtool MCP tool. Codex's #1 ROI architectural recommendation (BIAM task a3ef5af9): collapse server.go's hand-maintained list of RegisterX calls + CoreToolDocs's parallel description list + the slash-command + skill routing-map cross-references into ONE typed source of truth.
Step 1 (this commit): ship the package + types + an empty Manifest. server.go is unchanged. Subsequent commits migrate tool registration through the registry, one cohesive group at a time, with the surface_drift_test guarding each step.
Why type-driven, not config-driven: a TOML manifest would need a runtime registry of register funcs anyway. Putting the register-fn pointer ON the typed ToolSpec keeps the type system honest — a misspelled tool name fails to compile, not at boot.
Why a separate package, not a method on core: core/ already owns ~30 RegisterX functions. Importing core to build the manifest, then having core import registry to look up specs, would be a cycle. registry stays a leaf — core (and any future tool source) imports it; server.go calls registry.Apply.
Index ¶
- func IsValidCategory(c Category) bool
- type Category
- type Manifest
- func (m *Manifest) Append(spec ToolSpec)
- func (m *Manifest) Apply(s *server.MCPServer, rt Runtime, pred func(toolName string) bool)
- func (m *Manifest) Names() []string
- func (m *Manifest) SearchDocs(pred func(toolName string) bool) []search.Doc
- func (m *Manifest) SortedNames() []string
- func (m *Manifest) Specs() []ToolSpec
- type RegisterFn
- type Runtime
- type ToolSpec
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func IsValidCategory ¶
IsValidCategory is the load-time guard. A typo in a ToolSpec's Category field crashes the manifest builder rather than slipping into the wild as a tool that no group lists.
Types ¶
type Category ¶
type Category string
Category enumerates the canonical groupings. New categories require code review — adding one without thinking through the existing seven leads to single-tool buckets that no UI can surface.
const ( CategoryShell Category = "shell" // Bash, BashOutput, BashKill, Verify CategoryFile Category = "file" // Read, Edit, Write, Glob, Grep CategoryWeb Category = "web" // WebFetch, WebSearch, BrowserFetch, BrowserScrape, Portal* CategoryDispatch Category = "dispatch" // SendMessage, AgentList, Task*, TaskNotify CategoryAuthoring Category = "authoring" // McpNew/Run/Build/Install/List, SkillNew, AgentNew CategorySetup Category = "setup" // Recipe*, Bridge*, Sandbox* CategoryDiscovery Category = "discovery" // ToolSearch, SemanticSearch CategoryCheckpoint Category = "checkpoint" // Commit, RulesCheck (future: Snapshot, Restore) )
type Manifest ¶
type Manifest struct {
// contains filtered or unexported fields
}
Manifest is the ordered collection of ToolSpec entries. Order matters for two reasons:
- server.go's RegisterX call order today is preserved during incremental migration so behaviour change is observable per-tool.
- tools/list output groups by Category but ties break on manifest order; deterministic output simplifies test fixtures.
func (*Manifest) Append ¶
Append registers one ToolSpec. Duplicate names panic — the manifest is built at boot, before any user request, so a duplicate is a programmer error worth crashing on.
func (*Manifest) Apply ¶
Apply walks the manifest and calls each spec's Register fn, gated by the caller-supplied predicate. Mirrors server.go's hand-maintained `if cfg.IsEnabled(name) { core.RegisterX(s) }` chain — once the migration completes, server.go calls `manifest.Apply(s, runtime, cfg.IsEnabled)` and that chain disappears entirely.
Specs with a nil Register fn are skipped silently. This is intentional during incremental migration: a spec added to the manifest for documentation purposes (so SearchDocs picks it up) without yet being wired to the new register flow stays harmless until its turn comes.
func (*Manifest) Names ¶
Names returns every spec name in insertion order. Useful for diff-against-something tests.
func (*Manifest) SearchDocs ¶
SearchDocs flattens the manifest into search.Doc entries for the bleve indexer. Always-on tools always appear; gateable tools are filtered by the caller-supplied gate predicate (typically `cfg.IsEnabled(name).Enabled`). When pred is nil every spec is included.
func (*Manifest) SortedNames ¶
SortedNames returns the manifest's tool names alphabetically. Tests that need deterministic output independent of insertion order use this; runtime code prefers Names() to preserve the gate / display ordering.
type RegisterFn ¶
RegisterFn is the shape every typed register callback adopts. Mirrors mcp-go's AddTool but receives Runtime so register-time dependencies stay explicit — no package-level singletons leak into tool implementations.
type Runtime ¶
type Runtime struct {
// Index is the bleve search index ToolSearch closes over.
// Step 4 wires ToolSearch through the manifest, so this
// field becomes load-bearing rather than aspirational.
Index *search.Index
// Secrets is the secrets store WebSearch reads its API key
// from at registration time. Typed as *secrets.Store at the
// importer's site (server.go / core); registry stays a leaf
// by holding it as `any` and letting the per-tool register
// fn type-assert. The trade-off (slightly worse type safety
// at registration) is preferable to having registry depend
// on internal/secrets — keeps the import graph linear.
Secrets any
}
Runtime carries the cross-cutting dependencies a register fn might need. Passed by value (struct of pointers / interfaces) so the manifest stays composable and tests can stub fields independently. Add fields as new tools demand them; never remove without a deprecation cycle.
type ToolSpec ¶
type ToolSpec struct {
// Name is the canonical MCP tool name. PascalCase per ADR-006.
// MUST be unique within a Manifest; duplicates are a load-time
// error.
Name string
// Description is the one-paragraph human form. Same string the
// tool surfaces via tools/list AND ToolSearch.
Description string
// Keywords feed the bleve BM25 index. Lowercase, single words,
// 3-12 entries is the sweet spot.
Keywords []string
// Category groups tools for introspection / grouping in
// tools/list and the README. See package-level Category*
// constants for the canonical set.
Category Category
// Gate names the config.IsEnabled key for this tool. Empty =
// always-on (BridgeAdd / Verify / SemanticSearch / etc.).
// "Bash" gate also covers BashOutput + BashKill (companions).
Gate string
// Register is the MCP wiring callback. Receives the server +
// per-tool runtime dependencies (search index, secrets store,
// sources manager) via the Runtime struct. Empty when the
// tool is documented in the manifest but registered through
// a legacy direct path — useful during incremental migration.
Register RegisterFn
}
ToolSpec is the typed manifest entry for one MCP tool. Every shipped tool is described by exactly one ToolSpec. The fields match the four planes of the shipping contract (docs/feature-shipping-contract.md):
- Name + Description + Keywords → search index + ToolSearch
- Category → introspection + grouping
- Gate → config.IsEnabled subset
- Register → the actual MCP wiring
Slash command + skill row don't live on the spec because they're *file*-shaped (commands/clawtool-X.md, skills/clawtool/SKILL.md routing rows). The surface drift test (internal/server/surface_drift_test.go) cross-references the manifest against those files at test time.