Skip to main content

Toolspecs & the Toolpack Engine

Most of the registry's 221 cataloged services don't have a Go MCP server implementation — established MCP servers are mostly TypeScript/Python, and the gateway currently runs only static binaries from scratch-based images. The toolpack strategy makes the catalog installable anyway, with a hybrid approach:

  • Tier 1 — adopt Go upstreams (expected ~5–15 services): where a high-quality Go upstream MCP server exists (official or well-maintained, permissive license, stdio transport, CGO-free static build), the manifest's source points at it and the manifest's tool list is rewritten to match upstream's real tools. These build with the default go-static builder.
  • Tier 2 — the generic toolpack engine (the long tail): one static Go MCP engine (github.com/gigmcp/toolpack) driven by a per-service declarative toolspec. This is the Composio model: each tool is a templated HTTP request against the service's REST API. 189 manifests currently select builder: toolpack.

A toolspec is data, not code — consistent with the registry's aggregator policy (manifests and build recipes only). It lives at toolspecs/<name>/<version>.yaml, paired 1:1 with manifests/<name>/<version>.yaml, and is lint-enforced against its manifest by registryctl lint-toolspecs.

Toolspec format

The authoritative schema is schema/toolspec.go (the same module the gateway and CI share). Parsing is strict — unknown fields are errors.

schemaVersion: 1
name: ably
version: 0.1.0
baseUrl: https://rest.ably.io # https only; host must be allowed by manifest egress
auth: # ONLY for entrusted tier (see below)
header: Authorization
format: "Bearer {token}"
tools:
- name: list_channels # tool set must equal the manifest's, 1:1
description: List active channels
method: GET # GET|POST|PUT|PATCH|DELETE
path: /channels # absolute; {placeholders} bind to in:path params
baseUrl: https://other.host.com # optional per-tool override, also egress-checked
encoding: json # json|form (body encoding; default json)
params:
- name: limit
in: query # path|query|body|header
type: integer # string|integer|number|boolean|object|array
required: false
description: Max channels to return

Top-level fields

FieldTypeRequiredConstraints
schemaVersionintyesMust be 1
namestringyesLowercase [a-z0-9-], no underscores; must equal the paired manifest's name
versionstringyesMAJOR.MINOR.PATCH; must equal the paired manifest's version
baseUrlstringyeshttps://<host> only — no port, path, query, fragment, or userinfo. The host must pass the paired manifest's egress matcher
authobjectconditionalRequired iff the paired manifest is entrusted-tier and declares credentials; forbidden for sealed tier
toolsarrayyesMust be non-empty

auth

FieldTypeConstraints
auth.headerstringRequired; the HTTP header the engine sets
auth.formatstringRequired; must contain {token}

Why the tier rule: an entrusted manifest's credential inject is env-only — it says which environment variable holds the secret, but not where the secret goes in requests, so the toolspec must say. A sealed manifest's credential inject (header + format) is authoritative, and the egress proxy performs the injection — a toolspec auth block there would be misleading, so it's rejected.

tools[]

FieldTypeRequiredConstraints
namestringyesUnique within the spec; the tool set must exactly equal the manifest's tool set
descriptionstringyesBecomes the MCP tool description
methodstringyesGET, POST, PUT, PATCH, or DELETE
pathstringyesAbsolute (/...); {placeholder} segments bind to in: path params
baseUrlstringnoPer-tool override for services with multiple API hosts; same syntax and egress rules as the spec-level base URL
encodingstringnoBody encoding for in: body params: json (default) or form (application/x-www-form-urlencoded)
paramsarraynoTool parameters — see below

params[]

FieldTypeRequiredConstraints
namestringyesUnique within the tool. Params share one flat namespace: they become the properties of the MCP tool's input schema, regardless of where each one is sent
instringyespath, query, body, or header
typestringyesstring, integer, number, boolean, object, or array
requiredboolnoin: path params must be required
descriptionstringnoSurfaced in the tool's input schema

Per-tool validation rules:

  • Every {placeholder} in path needs a matching in: path param, and vice versa.
  • in: body params are only allowed on POST, PUT, and PATCH.
  • in: header params must not collide (case-insensitively) with the auth header.

Pairing rules against the manifest

registryctl lint-toolspecs additionally enforces, for each spec/manifest pair:

  • name and version match.
  • The tool sets are exactly equal — no manifest tool missing from the spec, no spec tool undeclared in the manifest.
  • Every base URL host (spec-level and per-tool) is allowed by the manifest's egress allowlist, using the proxy's exact matcher semantics.
  • Tier/auth coherence: entrusted + credentials ⇒ auth required; sealed ⇒ auth forbidden.

A real example

From toolspecs/linear/0.1.0.yaml — Linear's API is GraphQL, so every tool POSTs to /graphql with the query and variables as body params:

schemaVersion: 1
name: linear
version: 0.1.0
baseUrl: https://api.linear.app
tools:
- name: get_issue
description: Retrieve a single issue by its ID or identifier (e.g. LIN-123)
method: POST
path: /graphql
params:
- {name: query, in: body, type: string, required: true, description: "GraphQL query string for the issue query (e.g. 'query($id:String!){ issue(id:$id){ id title description state { name } assignee { name } priority } }')"}
- {name: variables, in: body, type: object, description: "GraphQL variables: id (String, required) — UUID or issue identifier such as LIN-123"}

No auth block: linear is sealed-tier, so the manifest's inject (Authorization: Bearer {token}) is authoritative and the proxy injects the real token.

How the engine maps tools to HTTP

The toolpack engine is a static Go binary speaking MCP over stdio (built on modelcontextprotocol/go-sdk, the same SDK as the gateway). At startup it reads /app/manifest.yaml and /app/toolspec.yaml — both baked into the image by the toolpack builder — and registers one MCP tool per spec entry, with an input JSON schema derived from params.

On a tool call it executes the templated HTTP request:

  • Networking: the default Go transport honors the sandbox's HTTPS_PROXY and SSL_CERT_FILE (the MITM proxy CA) — no special wiring needed.
  • Auth, sealed tier: the engine sets the manifest credential's inject.header to inject.format with {token} replaced by a placeholder; the proxy recognizes the placeholder and injects the real secret, for allowlisted hosts only.
  • Auth, entrusted tier: the engine reads the real secret from the manifest's inject.env variable and applies the toolspec's auth.header/auth.format directly.
  • Responses: the body is returned as text content, truncated at 100 KB; non-2xx responses become MCP tool errors carrying the status and a body snippet.

Status

The toolpack design is approved (2026-06-07) and toolspecs exist alongside the manifests that select builder: toolpack, but the engine repo is not yet published or tagged, no images are built, and all digests remain placeholders. None of the toolpack entries are installable yet — see the registry overview for the bootstrap path.