Tool catalogue
The version 1 tool catalogue is the public discovery contract shared by MCP,
WebMCP and external emitters. tungsten schema tool-catalog prints its JSON Schema.
Rust integrations use tungsten_emit::tool_catalog::build(&ir) and validate with
tool_catalog::validate. Its schema identifier is
https://tungsten.dev/schemas/tool-catalog-v1.json.
A catalogue contains catalogVersion, API and generator versions, tools and named
groups. Every visible callable operation and supported macro has a deterministic
name, its operation ID or macro name, a descriptor reference, arguments and optional
response JSON Schema, safety tier, idempotency policy, collision-safe reserved
fields, cluster, and sensitive response metadata. Planned and hidden operations
are excluded. Invalid macros produce diagnostics and are excluded. Names use
ASCII snake case, at most 48 characters; collisions receive stable ID digests.
Arguments use the SDK's compact argument layout. Reserved idempotency and confirmation fields receive leading underscores when they conflict with API arguments. Caller-owned persisted keys are required. Destructive and irreversible tools require preview confirmation. Output schemas describe the first JSON object success response; streams and non-object responses have no output schema. Response reference closures are bounded to keep discovery small; schema references are local and resolvable within each schema. A catalogue contains no credential values, environment values, output paths, search indexes or server instructions.
descriptor: { kind, id } identifies an operation or macro in the generated SDK's
descriptor document. It is an identity reference, not an import path. Adapters
resolve it using their runtime's descriptor registry. MCP adds annotations,
measured token budgets, BM25 disclosure and initialization instructions. WebMCP
adds manifestVersion and browser registration. Both reuse the same shared name,
argument schema, safety and reserved-field compiler.
Browser resource groups
Generated WebMCP packages export neutral discovery data at <package>/catalog.
They also export <package>/groups/<group-key>: each module contains only that
group's tool manifest, operation dependencies and reachable model schemas. Group
keys are normalized from cluster names or namespace/resource paths using the same
stable collision-aware naming rule. The catalogue's groups list gives each
group's moduleKey and tool names. Importing a group does not import the eager SDK client or full
catalogue and does not register tools.
await controller.replace(async () => {
const group = await import("my-api-webmcp/groups/public_pets");
return group.createScope({ baseUrl: "/api" });
});
createScope(client) returns a core, manifest and macro registry when needed.
Registration defaults to read-only tools. Explicitly opt into mutations or macros
by adding allowlist: ["exact_tool_name"] to the returned scope. The host chooses the backend and lifecycle signal. Preview,
verification and macro step dependencies remain available to the scoped core
without exposing extra tools. Shared model dependencies can occur in multiple
groups; actual bundling and chunk boundaries remain the application's choice.
External emitter conformance
External emitters keep JSON protocol version 1. They can compile a catalogue from
the request's existing IR without a new protocol field. Validate the catalogue
against the published schema and with tool_catalog::validate, resolve descriptor
IDs in the generated SDK document, and preserve safety and reserved fields.
tool_catalog::external_conformance(&catalog, &first, &second) accepts two
independently produced protocol responses. It rejects unsupported versions,
dangling or external schema references, invalid safety metadata, reassigned
catalogue tool identities, unsafe/duplicate output paths, and differing output
bytes or ordering. Conformance fixtures use UTF-8 text files. The helper performs
no process, network or filesystem reads and receives no credentials. It
supplements the external runner's normal timeout, output size and file guards.
Run the emitter twice on the same request and compare the responses; timestamps,
absolute machine paths and nondeterministic iteration fail the check.