Skip to main content
Glama

PMCP - Progressive MCP

PyPI version License: MIT

A gateway that lets your AI coding assistant use dozens of external tools without loading them all up front.

PMCP sits between your assistant (Claude Code, Codex, and others) and the services it can plug into — GitHub, Jira, databases, 90+ more. Instead of loading every tool's full schema before you've asked a question, it offers a short menu first and fetches the detailed schema only when a tool is actually used.

Why it exists

Assistants reach external services through MCP (Model Context Protocol) — a common interface for an AI to talk to other software. When an assistant connects to a dozen MCP servers directly, it loads all of their tool definitions into context at once.

Context is limited and metered: every tool definition costs tokens and crowds out room for the actual work. Loading 50+ tools you may never call is slow and expensive, and adding a new service usually means restarting the assistant. Anthropic has highlighted context bloat as a key challenge with MCP tooling.

PMCP is the single connection point that keeps this compact and on-demand.

Related MCP server: Caplets

Who it's for

Developers and teams running AI coding assistants with many connected tools — especially anyone hitting "too many tools" or context-full limits.

What you get

  • Lower token cost — the assistant sees a compact capability card, not 50+ full schemas, before doing real work.

  • Load tools only when needed — downstream servers stay dormant until first use ("lazy by default"), and eager-start only when listed in autoStart.

  • Add tools without restarting — provision a new server on demand from a manifest of 90+.

  • One stable connection — your assistant sees ~26 steady meta-tools instead of a shifting set of many.

  • Guardrails built in — output size caps and optional secret redaction.

  • Works out of the box — core capability matching needs no API key.

Quick Start

Installation

# With uv (recommended)
uv pip install pmcp

# Or run directly without installing
uvx pmcp

# With pip
pip install pmcp

Upgrading from 2.x

3.0.0 refuses several things 2.7.3 accepted. Most importantly, a repository's .mcp.json, .pmcp/manifest.yaml and .mcp-gateway-policy.yaml are ignored until you approve them with pmcp trust approve <absolute path>. MIGRATING.md has an upgrade checklist, a section for each breaking change (with a check, the fix and how to verify it), and how to roll back. To stay on 2.x for now, pin pmcp<3.

Capability matching is built-in — no API key needed. gateway.request_capability uses a pure-Python matcher that can return direct CLI guidance for installed native tools, MCP server candidates, or registry search guidance.

Configure with pmcp setup

PMCP includes a wizard-style helper that can render ready-to-use MCP client config for Claude and OpenCode. The generated config only connects your client to the PMCP gateway. Downstream MCP servers stay lazy until first use unless you add them to autoStart in your .mcp.json.

Use pmcp setup to print the generated config:

pmcp setup --client claude --mode stdio    # Claude local stdio
pmcp setup --client claude --mode http     # Claude shared-service HTTP
pmcp setup --client opencode --mode stdio  # OpenCode local stdio
pmcp setup --client opencode --mode http   # OpenCode shared-service HTTP

Named profiles cover the common modes:

pmcp setup --profile local-stdio
pmcp setup --profile shared-local-http
pmcp setup --profile authenticated-shared-http
pmcp setup --profile ci

Write directly into your client config with --write:

pmcp setup --client claude --mode http --write

Without --write, pmcp setup prints the config so you can paste it into:

  • Claude: ~/.mcp.json

  • OpenCode: ~/.config/opencode/opencode.json

Use shared-service HTTP mode when running one PMCP service for multiple sessions or clients. Use single-process stdio mode for local testing.

Shared Service Mode (Manual)

If you prefer manual config, point each client to the shared HTTP endpoint:

{
  "mcpServers": {
    "pmcp": {
      "type": "http",
      "url": "http://127.0.0.1:3344/mcp"
    }
  }
}

Why this mode: PMCP uses a singleton lock (~/.pmcp/gateway.lock), so multiple local launches can conflict. One shared service avoids lock collisions and keeps tool state consistent.

Shared gateway state:

  • All clients connected to one PMCP HTTP gateway share downstream server connections, pending requests, provisioned tools, and live lifecycle state.

  • gateway.refresh(force=true), gateway.disconnect_server(force=true), and gateway.restart_server(force=true) can cancel or interrupt downstream work started by another client using the same gateway.

  • gateway.health and live pmcp status --verbose show startup policy observations for downstream servers without exposing secret values.

  • --rate-limit / PMCP_RATE_LIMIT applies per observed source IP on /mcp; localhost clients and reverse-proxied clients can share one bucket unless the proxy preserves distinct client IPs.

Quick verification:

systemctl --user is-active pmcp
curl -sS http://127.0.0.1:3344/health

/mcp is POST-only as of 2.0.0 — a bare curl against it returns 405 Method Not Allowed with Allow: POST, DELETE, so use /health for a liveness check.

Security

HTTP transport is unauthenticated by default. For any non-localhost exposure, choose an HTTP auth mode and terminate TLS in front of PMCP.

shared-secret mode is the backward-compatible single-tenant guard. It accepts one static bearer value on /mcp:

# Start with bearer auth from the environment
PMCP_AUTH_TOKEN=mysecrettoken pmcp --transport http

Avoid passing production tokens with --auth-token; command-line arguments can be visible in process listings on shared hosts.

Clients must then include Authorization: Bearer mysecrettoken on /mcp requests. /health and /metrics remain unauthenticated by design; protect them with firewall rules, IP allowlists, or reverse-proxy policy before any non-localhost exposure.

resource-server mode makes PMCP validate Authorization Server issued access tokens as an OAuth 2.1 Resource Server. Configure the HTTP app with a public issuer, JWKS URL, resource audience, required scopes, and exact allowed origins:

create_http_app(
    mcp_server,
    auth_mode="resource-server",
    resource_server_issuer="https://issuer.example",
    resource_server_jwks_url="https://issuer.example/.well-known/jwks.json",
    resource_server_audience="https://pmcp.example/mcp",
    resource_server_allowed_algorithms=("RS256", "ES256"),
    required_scopes=["pmcp.invoke"],
    allowed_origins=["https://app.example"],
)

PMCP validates token signature, issuer, expiry, not-before, and audience. The audience is bound to the configured resource_server_audience (the server's canonical resource URI, per RFC 8707); it is never derived from the request Host header. resource-server mode fails closed at startup if the issuer, JWKS URL, or audience is missing, and resource_server_jwks_url must be an https URL and is rejected when its host is a non-public IP literal or localhost (the accepted form is below). Token signatures are only accepted for the operator-configured resource_server_allowed_algorithms allowlist (default RS256/ES256); the token's own alg header is never trusted. JWKS is fetched asynchronously and cached, so validation never blocks the event loop; an unreachable JWKS endpoint, or a key set with no usable keys, returns 503 while an invalid token returns 401. Each JWKS fetch has a 5 s total timeout, and a failed fetch opens a 5 s backoff shared by the requests waiting on it. A token with an unknown kid forces at most one refresh per 10 s; others in that window are checked against the cached keys. The resource published at /.well-known/oauth-protected-resource is the configured resource_server_audience, or, when that is unset, the origin of the configured protected-resource metadata URL plus /mcp, with a scheme-default port such as :443 dropped. A path prefix in the metadata URL is not carried over: PMCP serves MCP at /mcp on the address it listens on, and that is the public URL only when nothing in front of PMCP rewrites the path. Behind a reverse proxy that strips a path prefix (for example nginx location /pmcp/ { proxy_pass http://127.0.0.1:3344/; }), MCP's public URL is https://<host>/pmcp/mcp; set --oauth-audience to that URL so the token audience names it. That is all a pmcp CLI deployment needs: the CLI does not publish protected-resource metadata, so it serves no metadata route.

The metadata URL is a create_http_app parameter (protected_resource_metadata_url) for applications that embed PMCP. There, set resource_server_audience to the public MCP URL as well, so the published resource names it, and pick a metadata URL whose path reaches PMCP unchanged: PMCP serves the metadata at the URL's literal path, so https://<host>/pmcp/.well-known/oauth-protected-resource behind that proxy arrives as /.well-known/oauth-protected-resource and returns 404 (Consiliency/pmcp#334 tracks that case). Use the RFC 9728 form for a resource with a path, https://<host>/.well-known/oauth-protected-resource/pmcp/mcp, and forward that path to PMCP unchanged. An application that mounts PMCP under a root_path must route that path to the mounted app as well: mounted at /pmcp, it answers at /pmcp/.well-known/oauth-protected-resource/pmcp/mcp, not at the URL's own path. The resource is never taken from the request Host. A forged token, including one whose algorithm does not match the published key's type, gets 401, never 500. In resource-server mode over HTTP, PMCP refuses to start, with a one-line error, if the JWKS URL (--oauth-jwks-url, PMCP_OAUTH_JWKS_URL or resource_server_jwks_url) is not an accepted auth URL, or if a required scope is not a single RFC 6749 scope (printable ASCII with no space, quote or backslash; --required-scope and PMCP_REQUIRED_SCOPES alike), so every 401/403/503 it later sends can be built. The CLI takes no metadata URL. An application that embeds PMCP and passes protected_resource_metadata_url to create_http_app gets the same refusal for that URL, in any auth mode; such a URL used to be dropped, with the metadata route silently omitted.

An auth URL is accepted only if it is an absolute https:// URL whose port, if it has one, is a number no greater than 65535, and whose host is in canonical form -- a DNS name of dot-separated labels of 1 to 63 ASCII letters, digits and hyphens, each starting and ending with a letter or digit, the last starting with a letter, so never a number (an IDN in its xn-- form); a dotted-quad IPv4 address with no leading zeros; or a bracketed IPv6 address with no zone id -- and is a public address or a name other than localhost. An IPv6 address that embeds an IPv4 address (IPv4-mapped, IPv4-translated, IPv4-compatible, NAT64, ISATAP, 6to4, Teredo) must be public on the embedded address too; the local-use NAT64 prefix 64:ff9b:1::/48 is refused. Because ISATAP is recognised by its interface id, an address whose interface id is ISATAP-shaped and embeds a non-public IPv4 address is refused even under a global prefix: [2606:4700::5efe:a00:5] (10.0.0.5) is refused, as it was before, while [2606:4700::5efe:808:808] is accepted. A host in any other spelling is refused rather than rewritten, because the HTTP client and a browser would rewrite it first: https://127.0.0.1/ (full-width digits), 127。0。0。1, full-width localhost, 127%2E0%2E0%2E1, 127.0.0.1. (trailing dot), legacy numeric and octal forms such as 2130706433 or 0177.0.0.1, 127.000.0.1, example.123, -a.example.com, [v1.fe] and [fe80::1%25eth0] are all refused, as are bücher.example (write xn--bcher-kva.example), underscores and empty labels. PMCP strips leading spaces and C0 control characters, and trailing tabs, CRs and LFs, and nothing else; any control character (U+0000 to U+001F, or DEL) left after that strip is refused, a backslash is refused anywhere in the URL, and, for a JWKS or metadata URL, so is any whitespace character (anything Python's str.isspace matches, so a trailing space, a no-break space or U+2028 too). A DNS name is not resolved (see below), and only the exact name localhost is treated as loopback: *.localhost is a name like any other and passes unresolved. Userinfo such as user:pass@ is accepted and dropped. Everything else is refused, including plain http:// to any host, loopback hosts too, and https:// to localhost or a loopback address. (Plain http:// to localhost, 127.0.0.0/8, [::1] or [::ffff:127.x.y.z] is accepted in one place only: a URL the operator types into gateway.auth_connect.) For example, as a JWKS URL or a metadata URL:

URL

Result

https://auth.example.com/jwks.json

accepted

https://8.8.8.8/jwks.json

accepted

https://auth.example.com:8443/jwks.json

accepted

https://user:pass@auth.example.com/jwks.json

accepted (userinfo dropped)

http://auth.example.com/jwks.json

refused: plain http

http://127.0.0.1:8080/jwks.json

refused: plain http

http://localhost/jwks.json

refused: plain http

https://localhost/jwks.json

refused: non-public host

https://127.0.0.1/jwks.json

refused: non-public host

https://10.0.0.5/jwks.json

refused: non-public host

/.well-known/oauth-protected-resource

refused: not an absolute http(s) URL

ftp://auth.example.com/jwks.json

refused: not an absolute http(s) URL

https://auth.example.com:abc/jwks.json

refused: invalid URL

https://auth.example.com/key set.json

refused: whitespace

https://xn--bcher-kva.example/jwks.json

accepted

https://127.0.0.1/jwks.json

refused: host not in canonical form

https://127。0。0。1/jwks.json

refused: host not in canonical form

https://127.0.0.1./jwks.json

refused: host not in canonical form

https://2130706433/jwks.json

refused: host not in canonical form

https://[v1.fe]/jwks.json

refused: host not in canonical form

https://127.000.0.1/jwks.json

refused: host not in canonical form

https://example.123/jwks.json

refused: host not in canonical form

https://-a.example.com/jwks.json

refused: host not in canonical form

https://[::ffff:0:a9fe:a9fe]/jwks.json

refused: non-public host

https://127.0.0.1\@auth.example.com/jwks.json

refused: backslash

In public auth metadata URLs it rejects hosts written as non-public IP literals — private, CGNAT, link-local, loopback, multicast, site-local, and unspecified — including IPv4 addresses embedded in IPv6 literals and legacy numeric forms such as 2852039166. This is a filter on literals only: a DNS name is accepted without being resolved, so a name that points at an internal address still passes. PMCP therefore no longer presents such a URL as one it checked: a server-supplied URL is relayed unverified and presented as such — UrlElicitationInfo.url_verified, AuthMetadataInfo.verified_urls, and AuthChallengeInfo.resource_metadata_url_verified all default to unverified, and the caveat is carried in the next_step an agent follows and in CLI output. Where PMCP fetches a URL itself it fails closed instead, requiring a verified public literal (Consiliency/pmcp#211). PMCP is still not an Authorization Server and does not provide dynamic client registration, SSO, RBAC, billing, or a complete multi-tenant identity service.

Auth mode and OAuth resource-server parameters are configurable from the CLI or environment (CLI flags take precedence; env values are read only when the flag is unset):

Flag

Env var

Purpose

--auth-mode {none,shared-secret,resource-server}

PMCP_AUTH_MODE

Select the HTTP auth mode. When unset, PMCP infers shared-secret if a token is present, otherwise none.

--oauth-issuer

PMCP_OAUTH_ISSUER

Authorization Server issuer (resource-server mode).

--oauth-jwks-url

PMCP_OAUTH_JWKS_URL

Public https JWKS URL (resource-server mode).

--oauth-audience

PMCP_OAUTH_AUDIENCE

Canonical resource audience, RFC 8707 (resource-server mode).

--required-scope (repeatable)

PMCP_REQUIRED_SCOPES (comma-separated)

Scopes every token must present.

--allowed-origin (repeatable)

PMCP_ALLOWED_ORIGINS (comma-separated)

Browser Origins permitted on /mcp; also enables Host-header validation.

Origin and Host posture (DNS-rebinding defense). The Origin check runs by default in every auth mode, even when no --allowed-origin is configured: a request carrying a browser Origin header is rejected with 403 unless the origin is loopback, same-origin with the request Host, or explicitly allow-listed. Requests with no Origin header — the normal case for non-browser MCP clients — always pass. Configuring --allowed-origin (or PMCP_ALLOWED_ORIGINS) additionally turns on Host-header validation: the request Host must be loopback or one of the hosts derived from the configured origins and the gateway's own canonical resource host (--oauth-audience / protected-resource metadata URL); other Hosts get 403. Host validation stays off by default so that reverse-proxy deployments that forward an arbitrary public Host keep working; if you enable it behind a proxy, make sure your gateway's public hostname is reachable through the configured origins or audience so the proxied Host is accepted.

Assumptions and trust model:

  • PMCP binds to 127.0.0.1 by default — not safe to expose publicly without an HTTP auth mode (PMCP_AUTH_TOKEN for shared-secret, or resource-server).

  • User-scoped and explicitly configured sources (~/.mcp.json, an explicit --config, an explicit --policy) are trusted inputs — treat them like code. A project-scoped .mcp.json, .pmcp/manifest.yaml or .mcp-gateway-policy.yaml checked into a repository is not trusted until you approve it with pmcp trust approve <absolute path>; until then it is ignored, and approval is keyed to the file's exact bytes, so editing an approved file revokes the approval. See the v13 trust boundary section of SECURITY.md for the full model.

  • Secrets you export in the shell that starts PMCP are inherited by child MCP server processes; protect them accordingly. Keys that PMCP itself loads from a project .env or .env.pmcp are not propagated to spawned servers — only a server's own declared credential is — so a repository's .env cannot bleed into every downstream server.

Production background service (Linux systemd):

# ~/.config/systemd/user/pmcp.service
[Unit]
Description=PMCP MCP Gateway

[Service]
Environment=PMCP_AUTH_TOKEN=replace-with-secret-token
ExecStart=/usr/local/bin/pmcp --transport http
Restart=on-failure

[Install]
WantedBy=default.target
systemctl --user enable --now pmcp

Or with nohup:

cd ~ && PMCP_AUTH_TOKEN=replace-with-secret-token nohup pmcp --transport http > /dev/null 2>&1 &

The gateway writes its own log to .pmcp/logs/gateway.log relative to its working directory (rotated at 1 MB, 5 backups), so started from ~ as above — or as a systemd user service, whose working directory is your home — it lands in ~/.pmcp/logs/gateway.log. Don't redirect stderr onto that same file: every line would be written twice.

TLS / Reverse Proxy

PMCP's HTTP transport is plaintext. For any exposure beyond localhost, terminate TLS at a reverse proxy and forward to 127.0.0.1:3344. Keep --host 127.0.0.1 (the default) so PMCP only listens on the loopback interface.

Nginx (/etc/nginx/sites-available/pmcp):

server {
    listen 443 ssl;
    server_name pmcp.example.com;

    ssl_certificate     /etc/letsencrypt/live/pmcp.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/pmcp.example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:3344;
        proxy_set_header Authorization $http_authorization;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
}

Caddy (Caddyfile):

pmcp.example.com {
    reverse_proxy 127.0.0.1:3344
}

Caddy handles TLS automatically via Let's Encrypt.

Other MCP Clients

PMCP works with any MCP-compatible client. Below are configuration examples for popular clients.

Codex CLI

Add to ~/.codex/config.toml (verify in the Codex documentation):

[mcp_servers.pmcp]
command = "pmcp"
args = []

Gemini CLI

Add to ~/.gemini/settings.json (verify in the Gemini CLI documentation):

{
  "mcpServers": {
    "pmcp": {
      "command": "pmcp",
      "args": []
    }
  }
}

Note: Configuration paths and formats vary by client. Verify the exact location and format in each client's official documentation.

Your First Interaction

You: "Take a screenshot of google.com"

Claude uses: gateway.invoke {
  tool_id: "playwright::browser_navigate",
  arguments: { url: "https://google.com" }
}
// Then: gateway.invoke { tool_id: "playwright::browser_take_screenshot" }

Returns: Screenshot of google.com

Architecture

┌─────────────────────────────────────────────────────────────┐
│                        Claude Code                          │
│  Only connects to PMCP (single server in config)            │
└────────────────────────────┬────────────────────────────────┘
                             │
                             ▼
┌─────────────────────────────────────────────────────────────┐
│                          PMCP                               │
│  • 26 meta-tools (catalog, invoke, tasks, config, etc.)     │
│  • Progressive disclosure (compact cards → full schemas)    │
│  • Policy enforcement (allow/deny lists)                    │
└────────────────────────────┬────────────────────────────────┘
                             │
        ┌────────────────────┼────────────────────┐
        ▼                    ▼                    ▼
┌───────────────┐  ┌─────────────────┐  ┌─────────────────┐
│  Explicit     │  │    Manifest     │  │  Custom Servers │
│  autoStart    │  │   (90+ servers  │  │  (your own MCP  │
│  servers      │  │   on-demand)    │  │  servers)       │
└───────────────┘  └─────────────────┘  └─────────────────┘

Key principle: Users configure ONLY pmcp in Claude Code. The gateway discovers and manages all other servers.

Why Single-Gateway?

  1. No context bloat - Claude sees 26 tools, not 50+

  2. No restarts - Provision new servers without restarting Claude Code

  3. Consistent interface - All tools accessed via gateway.invoke

  4. Policy control - Centralized allow/deny rules

Gateway Tools

The gateway exposes 26 meta-tools organized into four categories:

Tool annotations are preserved as untrusted hints only; policy and safety notes continue to use PMCP's own risk model. When a tool schema omits $schema, PMCP reports the JSON Schema dialect as https://json-schema.org/draft/2020-12/schema. See SPEC_COMPLIANCE.md for the current MCP specification compliance matrix and next-revision tracking checklist.

Core Tools

Tool

Purpose

gateway.catalog_search

Search available tools, returns compact capability cards with small metadata such as title, icons, execution hints, and schema dialect, plus additive compact CLI hints, registry candidates, and (with include_offline=true) manifest provision candidates (manifest_candidates) carrying provisionable/provision_tool/requires_api_key/api_key_available/env_var so an agent can provision the exact server

gateway.describe

Get detailed schema and richer metadata for a specific tool, including output schema, annotations, execution/task support, icons, and schema dialect

gateway.invoke

Call a downstream tool with argument validation, including task-augmented execution for task-capable tools

gateway.refresh

Reload backend configs and reconnect; refuses while requests or active MCP tasks are pending unless force=true

gateway.health

Get gateway and server health status

gateway.config_status

Read effective config and startup/auth status with source attribution

gateway.get_startup_policy

Read persisted autoStart and legacy disableAutoStart entries by source

gateway.set_startup_policy

Preview or explicitly apply autoStart add/remove/set operations against one selected source

Lifecycle Tools

Tool

Purpose

gateway.connect_server

Connect or start a known configured, manifest/provisioned, or registered discovered server

gateway.disconnect_server

Runtime-stop a server without editing .mcp.json or changing autoStart

gateway.restart_server

Runtime-stop then reconnect a server without changing persistent config

Capability Discovery Tools

Tool

Purpose

gateway.request_capability

Natural language capability matching that can return direct CLI guidance or MCP server candidates

gateway.sync_environment

Detect platform and available CLIs

gateway.provision

Install and start MCP servers on-demand

gateway.update_server

Update an MCP server package and reconnect it

gateway.auth_connect

Store API-key credentials or acknowledge URL-mode elicitation and retry provisioning

gateway.submit_feedback

Preview/submit technical PMCP feedback issues to GitHub

gateway.provision_status

Check installation progress

gateway.search_registry

Search the cached public MCP Registry metadata for external servers

gateway.register_discovered_server

Register a registry result for provisioning, pinned to its resolved npm version; provisioning needs operator approval (see Discovered packages)

Monitoring Tools

Tool

Purpose

gateway.list_pending

List pending tool invocations with health status

gateway.cancel

Cancel a pending tool invocation

gateway.tasks_list

List brokered downstream MCP tasks by opaque task ID

gateway.tasks_get

Get current status for one downstream MCP task

gateway.tasks_result

Fetch and process a downstream MCP task result

gateway.tasks_cancel

Cancel a downstream MCP task

gateway.refresh is intentionally conservative in shared-service mode. If a downstream request or active MCP task is in flight, refresh returns ok=false without disconnecting or reconnecting servers. Use gateway.list_pending to inspect active PMCP request IDs and gateway.tasks_list to inspect downstream MCP task IDs, then retry with force=true only when cancelling that work is acceptable.

gateway.disconnect_server and gateway.restart_server follow the same shared-service disruption policy for the target server: they refuse while that server has pending requests or active MCP tasks unless force=true. With force=true, only pending requests and active tasks for the named server are cancelled. These controls are runtime-only; they free local resources and update live gateway state, but they do not edit .mcp.json, remove server definitions, or change autoStart. In HTTP shared service mode, stopping or restarting a downstream server can affect other clients using the same PMCP gateway.

MCP task IDs are downstream server identifiers and remain distinct from PMCP pending request IDs such as server::local_id. Use gateway.cancel only for PMCP request IDs from gateway.list_pending; use gateway.tasks_cancel for MCP task IDs. Task records are transient in-memory gateway state. PMCP can bind visibility to the server and requestor context it observes, but unauthenticated local transports cannot provide cross-user authorization isolation.

Auth And Elicitation

PMCP reports downstream authorization as structured, non-secret state. Gateway outputs and health rows may include auth_state values of none, missing_auth, insufficient_scope, elicitation_required, policy_denied, or unknown, plus optional next_step, auth_methods, scope names, sanitized metadata URLs, and URL-mode elicitation summaries.

Supported flows:

  • Local API-key servers continue to use env-store credentials. When gateway.provision reports auth_state="missing_auth" and auth_mode="api_key", call gateway.auth_connect with a credential and PMCP stores it in the selected user or project env file. User scope writes ~/.config/pmcp/pmcp.env; project scope writes <project>/.env.pmcp. Project scope is useful for local development and CI workspaces, while user scope is better for credentials that should follow one operator across projects.

  • Remote bearer headers use env placeholders such as Authorization: Bearer ${REMOTE_API_TOKEN}. PMCP resolves placeholders from process env, project env-store, and user env-store values, but status, doctor, health, and feedback output only show required or missing env var names, not the resolved header value.

  • A tenant-scoped header resolver exists for applications that embed PMCP: it reads only <project>/.pmcp/tenants/<tenant_id>/pmcp.env and reports missing env var names without printing header values. The gateway's own remote connections never select a tenant, and no config key, flag or env var turns tenant mode on (see Consiliency/pmcp#353).

  • Remote authorization discovery is diagnostic-only. PMCP can preserve and report OAuth Protected Resource Metadata, Authorization Server Metadata, OpenID Connect discovery, Client ID Metadata Document URLs, and declared scopes when a server or WWW-Authenticate challenge provides them.

  • URL-mode elicitation is out of band. PMCP returns a sanitized URL and elicitation_id; complete that URL flow outside PMCP, then acknowledge it with gateway.auth_connect(auth_mode="url_elicitation", elicitation_id=..., consent_acknowledged=true).

PMCP is not an authorization server and does not implement enterprise SSO, Cross-App Access, DPoP, workload identity federation, or third-party refresh token storage. Do not paste OAuth codes or third-party credentials into URL-mode gateway calls.

Subordinate MCP Updates

  • gateway.update_server is the phase-1 update path for subordinate MCPs.

  • pmcp update <server> and pmcp update --all call the same gateway update workflow.

  • Update information is reported on request by gateway.update_server; the gateway does not volunteer unprompted "update available" notices. It cannot observe which package version a running server is actually executing, so a volunteered notice could be wrong in either direction (Consiliency/pmcp#150).

  • npm package identity comes from npm's own parser, or not at all. For an npx/npm server the gateway asks the host npm's own nopt, config definitions and npm-package-arg which package that command line would run, and refuses rather than guess when anything could redirect resolution — a cwd inside a node project, an npm_config_* variable in the server's env or the gateway's own, or any flag beyond --yes/--package. A refused server keeps working; it just loses auto-update and version reporting, and refreshes its cached descriptions every cycle. Where node is not installed the gateway falls back to its own flag tables, which is the pre-2.5.2 behaviour (Consiliency/pmcp#195).

The environment across the update's probe window. gateway.update_server probes for a new package version and then re-resolves the server config before restarting anything. Two different kinds of environment change behave differently across that window, and they are not one rule:

  • A config-driven change is refused. That means anything resolved into the server's env — which includes every manifest credential, since a manifest credential is resolved into the server config at load time. If it changes while the probe is running, update_server returns ok=False and does not restart: the package is fetched but not activated, and no version is recorded. The guarantee is that the config restarted onto is the config that was probed (Consiliency/pmcp#151). Your change is not lost — rotate a credential mid-update and it applies on the next update, rather than to a process that was probed with the old value.

  • A genuinely ambient variable — one set in the gateway's own environment rather than in a server's config — is live at spawn. It can never cause a refusal (both sides of the check derive from the same base environment, so an ambient change affects them equally and cancels out), and the restarted server receives the value in force when it is spawned, not the one in force when the update began. This is the same behaviour as gateway.connect_server, gateway.refresh and auto-reconnect: every spawn path reads the ambient environment at spawn time.

Freezing the ambient environment across an update is deliberately not done (Consiliency/pmcp#162).

Feedback Telemetry

  • PMCP can emit failure feedback hints and generate GitHub issue payload previews for agents.

  • Telemetry is technical-only and warns before submission; payloads include PMCP/tool context.

  • Disable permanently with pmcp guidance --telemetry off.

Preview is the default; posting is opt-in. gateway.submit_feedback builds the exact issue payload and a browser URL an operator can open. PMCP posts nothing itself unless both of the following are true, and it never spawns gh:

  1. The operator has allowed it. pmcp guidance --feedback-submission on sets guidance.enable_feedback_submission: true in ~/.claude/gateway-guidance.yaml. It is off by default, pmcp guidance --feedback-submission off turns it back off, and pmcp guidance shows the current value. enable_telemetry: false overrides it: with telemetry off nothing is built or sent at all. An agent's confirm_submission=true is the user's consent to the payload, not the operator's authority to send it, and is not by itself enough to post.

  2. The operator has exported a token. PMCP_FEEDBACK_TOKEN — a dedicated GitHub token with permission to open an issue on the destination repository. GITHUB_TOKEN and GH_TOKEN are not read on this path.

The token must be exported in the shell that starts pmcp. PMCP refuses a PMCP_FEEDBACK_TOKEN it introduced into its own environment: one stored through gateway.auth_connect, one loaded at startup from PMCP's own credential stores (~/.config/pmcp/pmcp.env, the project .env.pmcp), or one a .env in the current checkout supplied. The credential that authorises an outbound post has to come from you, not from something the agent or the repository could have written.

The destination is validated. The default is Consiliency/pmcp, this project's repository. PMCP_FEEDBACK_REPO overrides it, under the same rule: an override a checkout's .env introduced is refused, so is one that is not owner/repo shaped, and a refused destination is never rendered into a URL you are handed to open.

Refusals are loud rather than silent. Each one says what to do — the exact pmcp guidance command, or the variable to export — and the ones that can still be acted on by hand return the payload and a browser URL. If PMCP sent a request and never saw the response, it says so (submission_outcome: "dispatched_unconfirmed") and hands back a search URL, because the issue may already exist.

Progressive Disclosure Workflow

PMCP follows a progressive disclosure pattern - start with natural language, get recommendations, drill down as needed.

Step 1: Request a Capability

You: "I need to look up library documentation"

gateway.request_capability({ query: "library documentation" })

For local work where an installed native CLI is the right surface, PMCP returns compact CLI guidance and does not execute the command:

gateway.request_capability({ query: "git commits", available_clis: ["git"] })

Returns:

{
  "status": "use_cli",
  "message": "Use Bash/direct CLI with 'git'. PMCP is recommending the native command here; it is not executing the command or provisioning an MCP server for this path.",
  "cli": {
    "name": "git",
    "description": "Git version control CLI",
    "available": true,
    "help_command": ["git", "--help"],
    "examples": ["git status --short", "git log --oneline -5"],
    "reason": "Matched query against CLI keywords and examples."
  },
  "recommendation": "Run 'git' directly via Bash/direct CLI. Use gateway.request_capability again only if you need an MCP server."
}

After status: "use_cli", use Bash/direct CLI. PMCP stops at guidance here: it does not execute the command and does not fetch full native help output for the normal compact path. If PMCP returns server candidates instead, continue with MCP provisioning, gateway.describe, and gateway.invoke.

Returns:

{
  "status": "candidates",
  "candidates": [{
    "name": "context7",
    "candidate_type": "server",
    "relevance_score": 0.95,
    "is_running": true,
    "reasoning": "Context7 provides up-to-date documentation for any package"
  }],
  "recommendation": "Use context7 - already running"
}

Step 2: Search Available Tools

gateway.catalog_search({ query: "documentation" })

CLI recommendations are returned separately from MCP tool cards:

gateway.catalog_search({ "query": "git" })

Returns:

{
  "results": [{
    "tool_id": "github::list_issues",
    "server": "github",
    "tool_name": "list_issues",
    "short_description": "List issues in a repository",
    "tags": ["github", "git", "search"],
    "availability": "online",
    "risk_hint": "low"
  }],
  "total_available": 3,
  "truncated": false,
  "cli_hints": [{
    "name": "git",
    "description": "Git version control CLI",
    "available": true,
    "path": "/usr/bin/git",
    "help_command": ["git", "--help"],
    "examples": ["git status --short", "git log --oneline -5"],
    "reason": "Matched query against CLI name."
  }]
}

Use cli_hints as recommendations for Bash/direct CLI commands. They are not MCP tools, do not appear in results, and cannot be passed to gateway.describe or gateway.invoke. Start with either gateway.request_capability or gateway.catalog_search; when PMCP returns use_cli or matching cli_hints, that is enough context to switch to Bash/direct CLI. Otherwise stay on the MCP path.

Registry-backed matches can appear as registry_candidates in gateway.catalog_search or as status="candidates" from gateway.request_capability. They are read-only discovery metadata from the MCP Registry cache and may include package identifiers, transport, remote (streamable-http/sse) endpoints for hosted servers, server-card URLs, protected-resource metadata URLs, authorization-server metadata URLs, declared scopes, and placeholder header names. Candidates are deduplicated to the latest published version. PMCP does not install, connect, or pass credentials for a registry result until you explicitly register and provision the selected server.

Step 3: Get Tool Details

gateway.describe({ tool_id: "context7::query-docs" })

Step 4: Invoke the Tool

gateway.invoke({
  tool_id: "context7::query-docs",
  arguments: { libraryId: "/facebook/react", query: "useEffect cleanup" }
})

Downstream tool names and arguments come from the downstream server, not from PMCP; the shipped context7 and playwright entries run the latest published package, so use gateway.describe for the current schema.

Offline Tool Discovery

When using gateway.catalog_search, you can discover tools from servers that haven't started yet:

// Search all tools including offline/lazy servers
gateway.catalog_search({
  "query": "browser",
  "include_offline": true
})

This uses pre-cached tool descriptions from .mcp-gateway/descriptions.yaml, relative to the gateway's working directory. pmcp refresh writes to .pmcp/ by default, so point it at the directory the gateway reads (see Consiliency/pmcp#352):

pmcp refresh --cache-dir .mcp-gateway

Note: Cached tools show metadata only. Full schemas are available after the server starts (use gateway.describe to trigger lazy start).

The MCP Registry cache is stored separately, at $XDG_CACHE_HOME/pmcp/registry-cache.json (~/.cache/pmcp/registry-cache.json when XDG_CACHE_HOME is unset); PMCP uses the cache when the public registry is unavailable. Registry candidates can coexist with cached offline tool cards without changing total_available.

Private registry (debugging, opt-in)

By default PMCP discovers only from the public MCP Registry and surfaces GA-shaped, latest-version entries. Developers debugging their own private MCP servers can opt in with an environment flag (default off):

export PMCP_REGISTRY_ALLOW_PRIVATE=1
export PMCP_REGISTRY_PRIVATE_ENDPOINT=https://registry.internal.example/v0/servers

The opt-in can also be set with a top-level "allowPrivateRegistry": true in .mcp.json (project, then user, then --config; first one that states it wins). PMCP_REGISTRY_ALLOW_PRIVATE overrides the config value only when it is set. The endpoint must be https:// (plain http:// only for localhost or a loopback address) and must not be a link-local, multicast or unspecified address; otherwise PMCP logs a warning and uses the public registry.

When enabled, PMCP fetches from the configured private endpoint and tolerates draft/non-GA server.json schema fields, surfacing all versions (including non-latest entries) for inspection. This is a debugging aid, not for production discovery; with the flag off, behavior is unchanged.

Dynamic Server Provisioning

PMCP can install and start MCP servers on-demand from a curated manifest of 90+ servers.

Example: Adding GitHub Support

You: "I need to manage GitHub issues"

gateway.request_capability({ query: "github issues" })

Returns (if not already configured):

{
  "status": "candidates",
  "candidates": [{
    "name": "github",
    "candidate_type": "server",
    "is_running": false,
    "requires_api_key": true,
    "env_var": "GITHUB_PERSONAL_ACCESS_TOKEN",
    "env_instructions": "Create at https://github.com/settings/tokens with repo scope"
  }]
}

requires_api_key here reflects the effective requirement, not merely whether the entry declares one — a server whose manifest entry carries api_key_optional_when and whose named variable is set reports requires_api_key: false and no auth_connect recommendation, even though the underlying entry still has requires_api_key: true. See Private manifest overlay below.

Provisioning

# 1. Set API key (if required)
export GITHUB_PERSONAL_ACCESS_TOKEN=ghp_...

# 2. Provision via gateway
gateway.provision({ server_name: "github" })

Optional Eager Startup

Packaged manifest servers do not start automatically. They are lazy by default: PMCP can discover or provision them from the manifest, then connect on first use.

To eagerly start a server every time PMCP starts, list it in top-level autoStart:

{
  "autoStart": ["playwright", "context7"],
  "mcpServers": {}
}

Common opt-in choices:

Server

Description

API Key

playwright

Browser automation - navigation, screenshots, DOM inspection

Not required

context7

Library documentation lookup - up-to-date docs for any package

Not required

Startup policy decisions are visible through gateway.health and live pmcp status --verbose. Health rows keep the existing name, status, tool_count, and error fields, and may also include:

Field

Meaning

startup_policy

eager, lazy, skipped, or unknown

startup_source

Resolver source such as project, user, manifest, configured, or auto_start

startup_skip_reason

Machine-readable skip reason such as policy_denied, missing_auth, or unknown_auto_start

startup_env_var

Required environment variable name for missing-auth skips

auth_state

Machine-readable downstream auth state such as missing_auth, insufficient_scope, elicitation_required, or policy_denied

next_step

Non-secret suggested next action when an auth state needs operator action

For persistent administration, use the config tools:

gateway.config_status({})
gateway.get_startup_policy({})
gateway.set_startup_policy({
  "operation": "add",
  "names": ["playwright"],
  "source": "project"
})

gateway.set_startup_policy is preview-only by default. To write, select exactly one source or path and pass both "apply": true and "dry_run": false. The writer updates only top-level autoStart, preserves unrelated .mcp.json keys and server definitions, writes atomically, and returns a refresh next step instead of silently reconnecting servers. Diagnostics report stale autoStart, legacy disableAutoStart conflicts, policy-denied rows, and missing-auth rows without printing secret values.

PMCP negotiates the current MCP protocol version with downstream servers and continues to connect to older supported servers. The local conformance matrix covers negotiated status handling for 2024-11-05, 2025-03-26, 2025-06-18, and 2025-11-25, with 2025-11-25 preferred for new initialization attempts. gateway.health and pmcp status --json can include the negotiated protocol_version and declared server capabilities when a connected server reports them.

Modern MCP task support is conservative. PMCP forwards task-augmented tool calls only when a tool advertises execution.taskSupport and the downstream server advertises task capability. Required-task tools fail before dispatch if the server does not advertise task support. Task records are transient gateway state, not durable PMCP storage.

The tenant code-mode host contract in specs/tenant-code-mode-host-contract.md freezes the PMCP/companion-server boundary for future hosted sandbox execution. PMCP remains the broker; the companion tenant server remains the execution authority.

Gateway observability is local and structured. gateway.invoke accepts trace context through _meta.traceparent, _meta.tracestate, and _meta.baggage and preserves those string values on PMCP-owned downstream request metadata. The same keys are tolerated on HTTP requests. PMCP does not require or configure an OpenTelemetry exporter.

gateway.health may include gateway_diagnostics and recent audit_events. Diagnostics report transport/header compatibility, trace support, audit buffer readiness, auth metadata presence, and rate-limit configuration without secret values. Audit events are bounded in memory and include method/action, server or tool identity, protocol version when known, task ID when present, outcome, latency, auth state, and redacted error text.

PMCP's Streamable HTTP endpoint serves two protocol eras on the same /mcp route, upstream of clients. A client that negotiates through initialize is served the handshake era — 2024-11-05 through 2025-11-25. A client that instead sends an MCP-Protocol-Version: 2026-07-28 header together with a params._meta envelope carrying io.modelcontextprotocol/protocolVersion and io.modelcontextprotocol/clientCapabilities is served the modern era — 2026-07-28 — for tools/list, tools/call, resources/list, resources/read, prompts/list, prompts/get, and server/discover. The modern era is not reachable through initialize; it is selected per request by those headers. The modern era has no server-initiated request back-channel, so sampling/createMessage and elicitation/create do not exist at 2026-07-28 — PMCP does not proxy either today, so this is a protocol-era limitation, not a PMCP gap. (Server-initiated notifications are a separate mechanism — subscriptions/listen, below.) Downstream, PMCP's connections to the servers it proxies are negotiated only at the handshake era described above (2025-11-25 preferred) — no downstream server is ever reached at 2026-07-28. A modern-era client's tools/call is still proxied live over that handshake-era downstream connection; only the upstream envelope differs.

GET /mcp is retired. It now answers 405 Method Not Allowed with Allow: POST, DELETE instead of accepting a standing connection; there is no persistent GET/SSE channel of any kind, pre-session or otherwise. GET /health and GET /metrics are unaffected and remain separate, unauthenticated routes. The replacement for server-initiated notifications is subscriptions/listen — a long-lived POST stream reachable only at protocol version 2026-07-28 — over which a client that opens a subscription receives notifications/tools/list_changed, notifications/resources/list_changed, and notifications/prompts/list_changed as PMCP's own catalog changes — a gateway.connect_server, gateway.disconnect_server, or gateway.refresh call that adds, removes, or updates downstream tools, resources, or prompts, or a downstream server's own notifications/*/list_changed. A downstream notification is not relayed as-is: PMCP re-indexes that server's catalog first and publishes only once reconciliation confirms something actually moved, so a client that refetches on receipt of the notification sees the new catalog rather than the stale one. Progress and logging notifications from a downstream server are not proxied — catalog-change notifications only. No existing client loses delivered data from the GET retirement — PMCP never published anything on the old GET stream, so this removes a channel PMCP never wrote to, not one clients were receiving events over. The concurrency cap the old pre-session keep-alive shim enforced returns one-for-one as PMCP_MAX_LISTEN_STREAMS (default 64, same as before), now bounding subscriptions/listen instead. The old shim's absolute-lifetime cap (PMCP_KEEPALIVE_MAX_SECONDS) is deliberately not replaced — a subscription is long-lived by design, and severing it every N seconds was the defect this release fixes, not a property worth preserving. PMCP_MAX_KEEPALIVE_STREAMS and PMCP_KEEPALIVE_MAX_SECONDS are both gone; if you relied on either, there is no drop-in replacement for the lifetime cap — what bounds exposure instead is the concurrency cap, the SDK's own per-stream event-backlog cap, and /mcp auth whenever auth_mode is configured.

Servers stopped with gateway.disconnect_server remain visible in health as offline or lazy when PMCP still knows their configuration, and startup policy observation fields are preserved.

Example missing-auth health row:

{
  "name": "github",
  "status": "offline",
  "tool_count": 0,
  "startup_policy": "skipped",
  "startup_source": "manifest",
  "startup_skip_reason": "missing_auth",
  "startup_env_var": "GITHUB_PERSONAL_ACCESS_TOKEN"
}

Available Servers

The manifest includes 90+ servers that can be provisioned on-demand:

No API Key Required

Server

Description

filesystem

File operations - read, write, search

memory

Persistent knowledge graph

fetch

HTTP requests with robots.txt compliance

sequential-thinking

Problem solving through thought sequences

git

Git operations via MCP

sqlite

SQLite database operations

time

Timezone operations

puppeteer

Headless Chrome automation

Requires API Key

Server

Description

Environment Variable

github

GitHub API - issues, PRs, repos

GITHUB_PERSONAL_ACCESS_TOKEN

gitlab

GitLab API - projects, MRs

GITLAB_PERSONAL_ACCESS_TOKEN

slack

Slack messaging

SLACK_BOT_TOKEN

notion

Notion workspace

NOTION_TOKEN

linear

Linear issue tracking

LINEAR_API_KEY

postgres

PostgreSQL database

POSTGRES_URL

brave-search

Web search

BRAVE_API_KEY

google-drive

Google Drive files

GDRIVE_CREDENTIALS

sentry

Error tracking

SENTRY_AUTH_TOKEN

stripe

Payments and billing

STRIPE_SECRET_KEY

datadog

Monitoring and observability

DATADOG_API_KEY

cloudflare

Edge network and Workers

CLOUDFLARE_API_TOKEN

figma

Design files and components

FIGMA_API_KEY

jira

Issue tracking

ATLASSIAN_API_TOKEN (also set ATLASSIAN_SITE_NAME, ATLASSIAN_USER_EMAIL)

airtable

Spreadsheet database

AIRTABLE_API_KEY

hubspot

CRM and marketing

HUBSPOT_ACCESS_TOKEN

twilio

SMS and voice

TWILIO_ACCOUNT_SID

...and 70+ more

Use gateway.catalog_search to explore

—

See .env.example for all supported environment variables.

Code Execution Guidance

PMCP includes built-in guidance to encourage models to use code execution patterns, reducing context bloat and improving workflow efficiency.

Guidance Layers

L0 (MCP Instructions): Brief philosophy in server instructions (~60-90 tokens)

  • "Write code to orchestrate tools - use loops, filters, conditionals"

L1 (Code Hints): Ultra-terse hints in search results (~8-12 tokens/card)

  • Single-word hints: "loop", "filter", "try", "poll" (cut to max_hint_length, default 8 characters)

L2 (Code Snippets): Minimal examples in describe output (~40-80 tokens, opt-in)

  • 3-4 line code examples showing practical usage

L3 (Methodology Resource): Full guide (lazy-loaded, 0 tokens)

  • Accessible via pmcp://guidance/code-execution resource

Guidance Configuration

Create ~/.claude/gateway-guidance.yaml:

guidance:
  level: "minimal"  # Options: "off", "minimal", "standard"

  layers:
    mcp_instructions: true   # L0 philosophy
    code_hints: true         # L1 hints
    code_snippets: false     # L2 examples (default: off)
    methodology_resource: true  # L3 guide

  enable_telemetry: true            # failure feedback hints and issue previews
  enable_feedback_submission: false # let PMCP POST feedback to GitHub (default: off)
  # custom_instructions: "..."     # optional: replaces the default L0 workflow text

Levels:

  • minimal (default): L0 + L1 (~230 tokens overhead)

  • standard: L0 + L1 + L2 (~290 tokens overhead)

  • off: No guidance

level is a preset that sets all four layers flags, and it is applied after the file is read, so it overrides whatever layers: says: level: minimal with code_snippets: true still has no L2 snippets. To get L2, set level: standard.

View Guidance Status

pmcp guidance                              # Show configuration
pmcp guidance --show-budget                # Show token estimates
pmcp guidance --telemetry on|off           # Feedback telemetry
pmcp guidance --feedback-submission on|off # Let PMCP post feedback (default: off)

pmcp guidance prints both switches, and each -- form above persists the decision to ~/.claude/gateway-guidance.yaml before printing the new state. See Feedback Telemetry for what submission requires.

Token Budget

  • Minimal mode: ~230 tokens typical workflow (L0 + a 15-card search)

  • Standard mode: ~290 tokens (L0 + search + 1 describe)

  • 80% reduction vs loading all tool schemas upfront!

Configuration

Config Discovery

PMCP discovers MCP servers from:

  1. Project config: .mcp.json in project root (highest priority). The project root is --project when given, otherwise the nearest ancestor of the working directory holding a .mcp.json, .git, package.json, pyproject.toml or .pmcp/manifest.yaml, stopping before $HOME. Every project input -- this file, the manifest overlay, the project policy and the project's credentials -- comes from that one project root. A project .mcp.json is ignored until you run pmcp trust approve <path> (see Security).

  2. User config: ~/.mcp.json or ~/.claude/.mcp.json

  3. Custom config: Via --config flag or PMCP_CONFIG env var

On a server-name collision the earlier source wins. PMCP_CONFIG, PMCP_POLICY and PMCP_MANIFEST_PATH are honoured only when exported in the shell that starts PMCP; a value set by a .env or .env.pmcp file is ignored with a warning.

Private manifest overlay

If you built your own MCP servers (or want private provisionable definitions), you can add manifest entries without editing the shipped manifest. PMCP merges these overlay files over the built-in manifest, so your servers get first-class treatment in gateway.request_capability keyword matching, gateway.catalog_search offline discovery, gateway.provision, and startup resolution — answering "can I add my own private manifest items?" with yes.

Overlay locations, lowest → highest precedence (later overrides earlier, by server name; a same-named entry is replaced whole, not deep-merged):

  1. Shipped manifest (base)

  2. User: ~/.pmcp/manifest.yaml

  3. Project: <project>/.pmcp/manifest.yaml at the served project root -- --project when given, otherwise the project root found from the working directory (see the project config above); never $HOME's, which is the user overlay; ignored until you run pmcp trust approve <path>

  4. Explicit: PMCP_MANIFEST_PATH env var (wins over all; honoured only when exported, not when set by a .env file)

Overlays use the same entry schema as the shipped manifest. Example ~/.pmcp/manifest.yaml:

servers:
  # Local (stdio) server provisioned via a command
  my-private:
    description: "My private internal server"
    keywords: [myprivate, internal widget]
    command: "npx"
    args: ["-y", "@me/my-mcp"]
    requires_api_key: true
    env_var: MY_TOKEN
  # Remote server reached over streamable HTTP
  my-remote:
    description: "My private remote server"
    keywords: [myremote, internal api]
    url: "https://mcp.internal.example.com/mcp"
    headers:
      Authorization: "Bearer ${MY_REMOTE_TOKEN}"

A server that supports a self-hosted, keyless deployment can declare which extra_env variable makes its credential optional via api_key_optional_when. Declaring the field alone changes nothing — an operator must separately supply that variable. The shipped firecrawl entry already declares api_key_optional_when: ["FIRECRAWL_API_URL"], so supplying the URL is all an overlay needs to do — via a server_env patch, not a servers: block (servers: is whole-entry replace: a partial firecrawl: entry here would erase its command/install metadata and reset requires_api_key to its unset default, turning the credential gate off):

server_env:
  firecrawl:
    FIRECRAWL_API_URL: "http://localhost:3002"

Both parties must act — the manifest entry names the variable, and the operator supplies it — so no overlay can unilaterally relax a credential the entry never declared relaxable. A server naming its own credential as its own relaxer is ignored, and an unset, empty, or unexpanded ${VAR} value fails closed: the credential stays required.

Overlay loading is fail-soft: a missing file is skipped silently, and a malformed file logs a warning and is skipped; so does a single entry that pmcp could not use: a field some part of pmcp would fail on, such as keywords: [1], an args list holding a number, a command or transport that is not a string, or a CLI alternative with an empty check_command. Fields pmcp stores but never reads (status), or reads only as true or false (auto_start, requires_api_key), are accepted whatever their value, as before. A transport string without a url is accepted as before, whatever the string; with a url it must be one of pmcp's transport names (local, remote, sse, http, streamable-http). The warning names the field, not its value, and does not show the entry's name unless pmcp ships it. A blank field (description: with nothing after it) means "not set". None of this crashes the gateway — the shipped manifest always still loads. pmcp re-reads overlay files on every manifest load, so edits apply without a restart, and logs each warning once per change rather than on every load — except that a file that cannot be read or parsed is reported on every load until it is fixed.

An overlay's servers are found by their own keywords. Sharing a keyword with another server, or replacing a shipped server, never hides another server from gateway.catalog_search or gateway.request_capability (within the result limit; ties rank by name; a query that names a server resolves to it). A CLI an overlay adds or replaces is recommended only when no server matches, and is listed after pmcp's own CLIs.

Security: a manifest entry can specify an arbitrary command/args to run when provisioned — treat an overlay file with the same trust as your own .mcp.json. Policy still applies (denied servers stay denied).

Pinning a client version

To hold an npx-launched server at one version, add to ~/.pmcp/manifest.yaml (or an approved project overlay):

server_version:
  firecrawl: "3.25.5"

or version: "3.25.5" on a whole servers: entry. pmcp writes the pin into the server's args and every install argv, keeping the package name. It must be one exact version: a range, a dist-tag such as latest, build metadata or a package spec is ignored with a warning. What happens to an earlier pin depends on where the bad pin sits. Sources apply in order (shipped, user, project, PMCP_MANIFEST_PATH), and within one source servers: entries replace whole entries before server_version patches apply.

  • A bad server_version: any pin from an earlier source stands, unless a servers: entry for this server in this or a later source replaced it, or a later source set another pin.

  • A bad version:: it rides on a whole servers: entry, which replaced the earlier entry and any pin with it. So the server is unpinned unless this source's server_version or a later source pins it. server_version: {name: null} sets nothing; to remove a pin, delete its line.

Only npx servers whose package slot is a plain registry name (pkg, pkg@1.2.3, pkg@tag) can be pinned this way, and only if the entry's own env cannot redirect npm: locale and terminal keys, npm's logging/timing settings and the keys pmcp ships for that server are fine; anything else (such as npm_config_registry or NODE_OPTIONS) makes pmcp ignore the pin. Only a bare npx launcher is pinned (not a path such as ./npx or node_modules/.bin/npx; on Windows, npx.cmd/npx.exe too). The launcher of every install argv is judged by the rules of the host pmcp runs on, whatever platform key it sits under, so a windows: argv using npx.cmd refuses the pin on Linux or macOS. For uvx, pip, cargo or docker, or any other case, pin the version in the server's own command/args in .mcp.json (or the file you pass with --config).

The pin fixes the npm spec; npm resolves it in your own environment (your .npmrc, npm's cache, and a node_modules in the directory pmcp runs in; a .npmrc there can also set the npm keys allowed next to a pin). gateway.update_server does not move a pinned server. pmcp update shows it as [PINNED] with the version you wrote, except when npm configuration is set where pmcp cannot see past it. That happens in three cases:

  • the entry itself sets an npm setting such as npm_config_loglevel;

  • the gateway process's own environment sets any npm_config_* variable (for example an exported npm_config_cache) or NODE_OPTIONS, which turns off package detection for the whole process;

  • the directory the gateway runs in, or any directory above it, contains a package.json or a node_modules directory (npm would then read that project's .npmrc or run a local bin). This covers running pmcp from inside a Node project, or anywhere under a directory tree with a node_modules higher up.

In each case it prints [FAILED] … Could not determine a registry package, and still moves nothing: the pin is still in every argv.

Adding Custom Servers

For MCP servers not in the manifest, add them to ~/.mcp.json:

{
  "autoStart": ["my-custom-server"],
  "mcpServers": {
    "my-custom-server": {
      "command": "node",
      "args": ["./my-server.js"],
      "env": {
        "API_KEY": "..."
      }
    }
  }
}

PMCP supports both local command-based and remote URL-based downstream entries from discovered config files. Entries in mcpServers make downstream servers available lazily/on demand; they do not by themselves mean the server should be eagerly started.

index-it-mcp code-index pilot

For a fleet pilot of the local-first code indexer, add index-it-mcp to your .mcp.json with a pinned version and its operational env. PMCP spawns it over stdio and applies the env block verbatim on top of the gateway's environment, minus PMCP-stored credentials and keys PMCP loaded from .env / .env.pmcp (_connect_stdio builds the child env with sanitized_subprocess_env(config.env, project_root)), so this is the supported channel for the server's configuration:

{
  "mcpServers": {
    "index-it-mcp": {
      "command": "uvx",
      "args": ["--from", "index-it-mcp==<approved-version>", "index-it-mcp", "stdio"],
      "env": {
        "MCP_ALLOWED_ROOTS": "/path/to/repo",
        "SEMANTIC_SEARCH_ENABLED": "true",
        "SEMANTIC_DEFAULT_PROFILE": "code",
        "SEMANTIC_EMBEDDING_BASE_URL": "http://localhost:8000/v1",
        "QDRANT_URL": "http://localhost:6333",
        "OPENAI_API_KEY": "local-vllm-placeholder"
      }
    }
  }
}

Notes:

  • Pin the version (index-it-mcp==<approved-version>) so a PyPI release-line change can't silently swap the indexer under a running fleet. Replace <approved-version> with the operator-approved pin.

  • OPENAI_API_KEY is only the token the server presents to a local OpenAI-compatible endpoint (e.g. a vLLM embedding server at SEMANTIC_EMBEDDING_BASE_URL); it is not an api.openai.com secret.

  • Env must go via .mcp.json. An env: key in a shipped-manifest or private-overlay entry is ignored. Manifest entries take non-secret variables only through extra_env: (or an overlay server_env: patch), which cannot carry a credential; for a server like this one, put its env in the .mcp.json mcpServers entry.

  • Pin the Python interpreter with "args": ["--python", "3.12", "--from", …]. index-it-mcp==1.2.0 depends on tree-sitter-languages, which has no wheel for CPython 3.13; without the pin, uvx may pick 3.13 and fail to launch.

Repository registration must use the same storage env as the config. Before agents get indexed results, each repo must be registered and the gateway-spawned server must read the registry the registration wrote. The registry location is resolved from MCP_INDEX_STORAGE_PATH / MCP_REPO_REGISTRY. If you set those in the .mcp.json env above but run index-it-mcp repository register without them, the CLI writes to the default ~/.mcp/repository_registry.json while the PMCP-spawned server reads $MCP_INDEX_STORAGE_PATH/repository_registry.json — so the server reports repositories: [] and every query falls back to native search (unregistered_repository). Register with the same env the config uses:

export MCP_INDEX_STORAGE_PATH=/path/to/shared/.indexes
export MCP_REPO_REGISTRY="$MCP_INDEX_STORAGE_PATH/repository_registry.json"
for repo in /path/to/repo-a /path/to/repo-b; do
  uvx --python 3.12 --from index-it-mcp==1.2.0 index-it-mcp repository register "$repo"
done
# Verify the server-side view before trusting results:
uvx --python 3.12 --from index-it-mcp==1.2.0 index-it-mcp repository list   # expect your repos
uvx --python 3.12 --from index-it-mcp==1.2.0 index-it-mcp preflight         # readiness check

Check readiness before trusting indexed answers. The server's status/query tools report a readiness state (ready, unregistered_repository, missing_index, stale_commit, …) and, when not ready, safe_fallback: "native_search". Agents should treat any non-ready state as non-authoritative and fall back to native search rather than reporting stale/empty index results.

The top-level autoStart list controls explicit eager startup. Names can refer to servers defined in mcpServers or packaged manifest entries such as playwright and context7. Omit a server from autoStart to keep it lazy.

The legacy top-level disableAutoStart list is still honoured and overrides autoStart. PMCP_LEGACY_MANIFEST_AUTOSTART=1 additionally eager-starts manifest entries that declare auto_start: true; no shipped entry does, so it matters only for overlay entries.

The same policy is available locally from the CLI:

pmcp config status --json
pmcp config startup-policy
pmcp config set-startup-policy add playwright --source project
pmcp config set-startup-policy add playwright --source project --apply

CLI mutation previews by default. --apply is required before writing. Writing an approved project .mcp.json this way carries its trust approval forward to the rewritten bytes; an unapproved one stays ignored until pmcp trust approve.

Lazy Excalidraw example:

{
  "mcpServers": {
    "excalidraw": {
      "type": "http",
      "url": "https://mcp.excalidraw.com/mcp"
    }
  }
}

Eager Excalidraw example:

{
  "autoStart": ["excalidraw"],
  "mcpServers": {
    "excalidraw": {
      "type": "http",
      "url": "https://mcp.excalidraw.com/mcp"
    }
  }
}

Remote Downstream Servers

You can also configure downstream MCP servers over HTTP/SSE directly in .mcp.json using type: "sse" or type: "http" (or type: "remote" for generic remote transport):

{
  "mcpServers": {
    "acme-sse": {
      "type": "sse",
      "url": "https://mcp.acme.dev/sse",
      "headers": {
        "Authorization": "Bearer ${ACME_MCP_TOKEN}",
        "X-Tenant": "${ACME_TENANT_ID}"
      }
    },
    "acme-http": {
      "type": "http",
      "url": "https://mcp.acme.dev/mcp",
      "headers": {
        "Authorization": "Bearer ${ACME_MCP_TOKEN}"
      }
    }
  }
}
  • url should be the full remote endpoint for that server.

  • headers values support ${ENV_VAR} interpolation (Consiliency/pmcp#40).

  • Resolve those environment variables from your shell environment or ~/.config/pmcp/pmcp.env.

Important: Don't add pmcp itself to this file. PMCP is configured in your MCP client config, not in the downstream server list.

Tenant Code-Mode Server Registration

PMCP can broker a separate tenant code-mode MCP server as a normal downstream server. The contract in specs/tenant-code-mode-host-contract.md defines the boundary: PMCP discovers, invokes, monitors, truncates, and redacts through gateway surfaces; the companion tenant server owns sandbox execution, tenant authorization, logs, and artifacts. PMCP does not run scripts itself.

Register the hosted server in .mcp.json with the configured name tenant-code-mode:

{
  "mcpServers": {
    "tenant-code-mode": {
      "type": "streamable-http",
      "url": "https://tenant.example.com/mcp",
      "headers": {
        "Authorization": "Bearer ${TENANT_CODE_MODE_MCP_TOKEN}",
        "X-Tenant-ID": "${TENANT_CODE_MODE_TENANT_ID}"
      }
    }
  }
}

For local companion-server development, use a replaceable stdio command from that server's checkout:

{
  "mcpServers": {
    "tenant-code-mode": {
      "command": "/path/to/tenant-code-mode-server",
      "args": ["serve", "--transport", "stdio"]
    }
  }
}

The registration is lazy by default. Add tenant-code-mode to top-level autoStart only when the operator wants PMCP to connect during startup. Discovery and startup use the existing gateway.request_capability, gateway.catalog_search with include_offline: true, gateway.provision, and gateway.invoke flow.

Tenant runs use the existing task broker. Submit long-running work with gateway.invoke and non-secret task.metadata, task.ttl, task.poll_interval, task.requestor_context, and trace keys such as _meta.traceparent; PMCP forwards those fields to the downstream server only when the server and tool advertise task support. task.ttl and task.poll_interval are in seconds, at most 9,007,199,254,740. PMCP sends them downstream in milliseconds, which is the unit MCP 2025-11-25 uses (ttl: 300 reaches the server as ttl: 300000). The ttl and poll_interval of every task PMCP returns are converted back from the server's milliseconds to seconds, and may be fractional (1500 ms is reported as 1.5). A task's raw object keeps the values exactly as the server sent them. The returned downstream MCP task ID is then used with gateway.tasks_list, gateway.tasks_get, gateway.tasks_result, and gateway.tasks_cancel. Do not use PMCP request IDs from gateway.list_pending or gateway.cancel for tenant task operations. gateway.tasks_result continues to apply host-side truncation and optional secret redaction to sandbox-shaped logs and diagnostics.

Credential Scope Management (pmcp secrets)

PMCP stores secrets in environment files by scope:

  • user scope: ~/.config/pmcp/pmcp.env

  • project scope: <project_root>/.env.pmcp

You can manage both scopes with pmcp secrets:

# Store a secret in user scope (shared by all projects); prompts for the value
pmcp secrets set API_TOKEN --scope user

# Store a secret in project scope (the default scope), reading the value from stdin
printf '%s' "$TOKEN" | pmcp secrets set API_TOKEN --stdin --project /path/to/project

# Copy all user-scoped secrets into project scope
pmcp secrets sync --from-scope user --to-scope project --overwrite

# Copy project-scoped secrets into user scope
pmcp secrets sync --from-scope project --to-scope user --overwrite

Copying out of a project copies credentials only. A project .env.pmcp is the repository's, and your user store loads into pmcp's environment at every start (and pmcp upgrade runs uv or pip in it), so sync copies a name only if it is credential-shaped (*_TOKEN, *_KEY, *_SECRET(S), *_PASSWORD, *_CREDENTIAL(S), *_PAT, *_DSN, *_AUTH) or a server in the manifest declares it as its credential (POSTGRES_URL, say). Everything else -- UV_INDEX_URL, PIP_CONFIG_FILE, DATABASE_URL, anything named PMCP_*, proxies, LD_*/NODE_*/NPM_CONFIG_* and the like, even when credential-shaped -- is skipped, as is any value containing ${ or spanning more than one line. Each skipped name prints one pmcp: Not copying <NAME> from .env.pmcp: ... line and is listed under "refused"; the other keys still sync. Set one yourself with pmcp secrets set <NAME> --scope user if you mean it.

Passing the value on the command line (pmcp secrets set API_TOKEN your-token) still works but exposes it in ps output and shell history. pmcp secrets check prints, as JSON, the keys your configured servers require and which are present.

Use scope-appropriate values such as API_TOKEN and keep the values in the generated .env files; PMCP and downstream MCP servers read from these files according to your active mode.

For service users, ~/.config/pmcp/pmcp.env is ideal for shared tokens used by all sessions.

Policy File

Create a policy file to control access and limits:

~/.claude/gateway-policy.yaml:

servers:
  allowlist: []  # Empty = allow all
  denylist:
    - dangerous-server

tools:
  denylist:
    - "*::delete_*"
    - "*::drop_*"

packages:  # npm packages; globs match the package NAME only
  allowlist:
    - "@acme/*"  # Discovered servers provision without a per-version approval
  denylist:
    - "*-evil-*"  # Refused even when approved, manifest-backed servers included

limits:
  max_tools_per_server: 100
  max_output_bytes: 50000
  max_output_tokens: 4000

redaction:
  patterns:
    - "(api[_-]?key)[\\s]*[:=][\\s]*[\"']?([^\\s\"']+)"
    - "(password|secret)[\\s]*[:=][\\s]*[\"']?([^\\s\"']+)"

Every section has an allowlist and a denylist of glob patterns; a denylist match always refuses, and a non-empty allowlist admits only what it matches. Besides servers, tools (server::tool), packages, limits and redaction shown above, three sections are available:

  • gateway_tools — which gateway.* tools are advertised and callable (for example, allowlist only gateway.health, gateway.catalog_search, gateway.describe and gateway.invoke).

  • resources — downstream resources, matched as server::uri.

  • prompts — downstream prompts, matched as server::name.

PMCP looks for the policy at ~/.claude/gateway-policy.yaml or ~/.claude/gateway-policy.json, and for a project policy at .mcp-gateway-policy.yaml or .mcp-gateway-policy.json at the project root (--project when given, otherwise the project root found from the working directory -- the same root as every other project input); a project policy is ignored until approved with pmcp trust approve, and can only narrow the operator's policy. --policy / PMCP_POLICY names one explicitly.

An explicitly requested policy (--policy or PMCP_POLICY) is a fail-closed boundary: a missing, unreadable, malformed, or schema-invalid file terminates startup.

An automatically discovered policy at a default location is fail-closed too, with one deliberate exception. A discovered file that parses but is not a valid policy terminates startup exactly like an explicit one, because falling back would replace it with the default allow-all policy and silently unrestrict the gateway. Best-effort fallback now covers only a file that cannot be read, or that the parser rejects outright — which could be anything rather than a policy; that case warns, says that no policy is in effect, and continues.

The line between the two is drawn by the parser, not by the file's shape, so it falls in different places for the two formats. In a .yaml file a list root, a scalar root and an empty file all load cleanly — yaml.safe_load returns a list, a str and None — and so all three are fatal. In a .json file, a document whose root is valid JSON but not an object ([], 42, null) is likewise fatal, but an empty .json file is not valid JSON at all, so it takes the warn-and-continue path. If you are testing this behaviour, use an empty .yaml file to see the refusal.

Discovered packages need an operator's approval

A server registered with gateway.register_discovered_server runs an npm package an agent chose, so it does not start until an operator opts that package in. Registration resolves the package in the npm registry and pins the resolved version into the server's install command and its args (npx -y name@version); a package that does not resolve to one exact version is refused at registration. After that, gateway.provision, gateway.connect_server and gateway.restart_server refuse the server until one of these holds:

  • The exact version is approved. The refusal message prints the command to run, for example:

    pmcp trust approve-package @acme/example-server@1.4.2

    Then provision again. The approval covers that one version: a registration that resolves to a newer version needs a new approval.

  • The operator's policy allowlists the package name under packages.allowlist, as in the example above.

A packages.denylist match refuses the package in every case, including when an approval is recorded; such a refusal reports auth_state="policy_denied", and every other package refusal reports auth_state="unknown". Package globs match the name alone. A version-bearing entry such as pkg@1.2.3 is rejected when the policy loads, because it could never match. A project .mcp-gateway-policy.yaml can deny a package but cannot allow one the operator's policy does not.

gateway.update_server never updates a discovered server, approved or not. To move one to a newer version, call gateway.register_discovered_server again with the same server_name and package, approve the version it resolves, then connect it.

A discovered server may declare, in env_vars or through gateway.auth_connect, only credential-shaped variable names: names ending in _TOKEN, _KEY, _SECRET, _SECRETS, _PASSWORD, _CREDENTIAL, _CREDENTIALS, _PAT, _DSN or _AUTH. Names that start with NPM_CONFIG_, NODE_, COREPACK_, YARN_, PNPM_ or BUN_ (in any case) and names that affect code loading are refused even when credential-shaped, because they change what npx fetches or how it runs. Configure a server that needs any other variable in .mcp.json instead.

Manifest-backed and .mcp.json servers need no approval and keep their env var rules. The one exception is the denylist: a packages.denylist entry also refuses a manifest-backed server whose npm package it names (its package field, or a package its npx command or install command runs, including one chosen with -p or --package), at gateway.provision, gateway.connect_server, gateway.restart_server and gateway.update_server. While any packages.denylist is in force, a manifest entry whose npx packages pmcp cannot determine (an unrecognised npx option such as --registry or -c, or a package selected as github:owner/repo or ./dir) is refused too, and the refusal names the argument; without a denylist it starts as before. .mcp.json servers are not checked against the package lists.

Approvals live in ~/.config/pmcp/package_approvals.json, beside the trust store. Review and remove them with:

pmcp trust list-packages
pmcp trust revoke-package @acme/example-server@1.4.2  # one version
pmcp trust revoke-package @acme/example-server        # every version

Every install spawn logs its command at WARNING before it runs. That includes starting a stdio server whose command is npx, uvx, pnpx or bunx, in any Windows or POSIX spelling (such as C:\tools\npx.cmd or UVX.EXE), and every gateway.update_server probe. Arguments are redacted except the executable, the flags -y, --yes and --quiet, --registry (its name, not its value) and a pinned name@version, so an operator can see which package ran without a credential reaching the log.

Scoped advisor research

PMCP v1.20.0 adds the scoped_advisor_audit.v1 profile for isolated advisor research. Start each seat with the shipped policy, a unique lock directory, and an explicit audit sink:

pmcp \
  --policy examples/scoped-advisor-policy.yaml \
  --audit-jsonl /run/board/seat-1/audit.jsonl \
  --lock-dir /run/board/seat-1/locks

The profile exposes only gateway.health, gateway.catalog_search, gateway.describe, and gateway.invoke; downstream invocation is limited to the policy's Firecrawl and Bright Data research patterns. MCP resource and prompt surfaces are denied, and scoped catalog results omit native-CLI, registry, and provision candidates. Every invoke must supply run_correlation_id, seat_correlation_id, and a SHA-256 evidence_label_digest together. The append-only audit stores correlations, tool/status/policy/result digests, and a hashed public-source reference—not raw URLs, queries, arguments, credentials, or result bodies—and ends with one fsynced completeness marker. A call whose arguments fail the tool's input schema is recorded as a separate audit.rejection event (terminal_status: "invalid_arguments") carrying only the tool, the failing JSON path (caller-chosen keys shown as null) and the failing schema keyword. Only arguments that passed the tool's input schema, and only the top-level keys it declares, are read into any record; a call denied by policy records none of its arguments.

Consumers can fail closed on older installations with:

pmcp capabilities --json

The capability is active in gateway.health only when the exact explicit advisor policy and audit sink are both present. Concurrent seats must use unique --lock-dir and --audit-jsonl paths.

Tenant code-mode hosting uses the same policy fields. This example allows only the tenant server, blocks a high-risk submission tool, bounds output, and adds a tenant artifact redaction pattern without granting access to unrelated MCP servers:

servers:
  allowlist:
    - tenant-code-mode

tools:
  denylist:
    - "tenant-code-mode::run_script"
  allowlist:
    - "tenant-code-mode::get_*"
    - "tenant-code-mode::cancel_*"

limits:
  max_output_bytes: 50000
  max_output_tokens: 4000

redaction:
  patterns:
    - "TENANT_CODE_MODE_[A-Z_]+=[^\\s]+"
    - "artifact_token=[^\\s]+"

For hosted tenant auth, keep credentials in PMCP env storage or tenant-scoped project storage and reference only placeholders from config: ${TENANT_CODE_MODE_MCP_TOKEN} and ${TENANT_CODE_MODE_TENANT_ID}. Use pmcp secrets set ... --scope project or gateway.auth_connect to populate env-store values (a key also in your user store wins over the project's; see MIGRATING.md); the gateway does not select a per-tenant env file itself (see Auth And Elicitation). PMCP diagnostics report missing field or env-var names such as TENANT_CODE_MODE_MCP_TOKEN; they must not print token values.

Hosted operators should require Bearer auth on /mcp, tune --rate-limit or PMCP_RATE_LIMIT for the deployment, and keep /health and /metrics behind network controls. gateway.refresh, gateway.disconnect_server, and gateway.restart_server can disrupt in-flight downstream work unless forced by policy; use downstream task IDs with gateway.tasks_cancel for tenant run cancellation. PMCP task records are transient, and bounded: at most 100 per server and 1000 in total. To cancel a task pmcp no longer tracks, call gateway.tasks_get first, which tracks it again. A forced disconnect, restart or refresh cancels only the tasks pmcp still tracks when it starts. On a remote tenant server, a task that was evicted before then keeps running after the disconnect. Cancel it explicitly first, with gateway.tasks_get and then gateway.tasks_cancel. Durable sandbox logs, artifacts, tenant authorization, and artifact retention remain responsibilities of the companion tenant server and its deployment controls.

CLI Commands

# Start the gateway server (default)
pmcp

# Check server status
pmcp status
pmcp status --json              # JSON output
pmcp status --verbose           # Include startup policy details when available
pmcp status --server playwright # Filter by server
pmcp status --pending           # Include pending requests
pmcp status --probe             # Actively connect to each configured server

# View logs (reads .pmcp/logs/gateway.log in the current directory)
pmcp logs
pmcp logs --follow              # Live tail
pmcp logs --tail 100            # Last 100 lines (default 50)
pmcp logs --level error         # Filter by level
pmcp logs --server github       # Filter by server name

# Regenerate cached tool descriptions (default cache dir: .pmcp; the gateway
# reads .mcp-gateway, so pass --cache-dir .mcp-gateway for offline discovery)
pmcp refresh
pmcp refresh --server github    # Refresh specific server
pmcp refresh --force            # Refresh even if not stale
pmcp refresh --check-versions   # Report stale cached descriptions without refreshing
                                # (honours --cache-dir; a local server whose package
                                #  cannot be classified reports stale on every run)

# Update subordinate MCP server packages (needs a running HTTP gateway at PMCP_GATEWAY_URL)
pmcp update github
pmcp update --all --json

# Upgrade pmcp itself (detects uv tool vs pip --user)
pmcp upgrade --dry-run
pmcp upgrade --restart-service

# Machine-readable capability/version metadata
pmcp capabilities --json

# Initialize config (interactive)
pmcp init

# Render client setup snippets
pmcp setup
pmcp setup --client claude --mode stdio
pmcp setup --client opencode --mode http --write

# Run diagnostics for lock/mode/http checks
pmcp doctor
pmcp doctor --project /path/to/project
pmcp doctor --timeout 10        # HTTP health probe timeout (default 3s)

# Manage project/user secrets
pmcp secrets set API_TOKEN --scope user   # prompts for the value
pmcp secrets sync --from-scope user --to-scope project --overwrite
pmcp secrets check              # JSON: keys required by configured servers, present/missing

# Store a credential for a server and retry provisioning through the running
# HTTP gateway (CLI form of gateway.auth_connect)
pmcp auth connect github --scope user     # prompts for the credential
pmcp auth acknowledge my-remote --elicitation-id <id>

# Approve, list and revoke trust decisions
pmcp trust approve /abs/path/to/project/.mcp.json   # a project config file's current bytes
pmcp trust list
pmcp trust revoke /abs/path/to/project/.mcp.json
pmcp trust approve-package @acme/example-server@1.4.2  # a discovered package, one exact version
pmcp trust list-packages
pmcp trust revoke-package @acme/example-server         # every version, or name@version for one

Gateway options and environment variables

Options for the gateway itself (pmcp with no subcommand):

Flag

Env var

Default

Purpose

--transport {stdio,http}

PMCP_TRANSPORT

stdio

Transport to serve.

--host

PMCP_HOST

127.0.0.1

HTTP bind address.

--port

PMCP_PORT

3344

HTTP port.

-l, --log-level

PMCP_LOG_LEVEL

info

debug, info, warn or error; --debug and -q are shorthands.

--log-format {text,json}

—

text

json emits one JSON object per line.

-p, --project

—

discovered

Project root for .mcp.json discovery.

-c, --config

PMCP_CONFIG

—

Extra MCP config file.

--policy

PMCP_POLICY

discovered

Policy file (YAML or JSON).

--lock-dir

PMCP_LOCK_DIR

~/.pmcp

Singleton lock directory.

--audit-jsonl

PMCP_AUDIT_JSONL

—

Scoped-advisor audit sink.

--auth-token

PMCP_AUTH_TOKEN

—

Shared-secret bearer token (HTTP only).

--auth-token-file

—

—

Read the token from a file (whitespace stripped); cannot be combined with --auth-token.

--max-concurrent-spawns

PMCP_MAX_SPAWNS

8

Child MCP server processes spawned at once.

--rate-limit

PMCP_RATE_LIMIT

0 (unlimited)

Requests per minute per client IP on /mcp.

--request-timeout

PMCP_REQUEST_TIMEOUT

60

HTTP request timeout, in seconds.

The auth and OAuth flags are listed under Security. Precedence is not uniform: PMCP_TRANSPORT, PMCP_HOST, PMCP_PORT and PMCP_LOG_LEVEL override the flag whenever they are set (for the log level, --debug and -q still win over PMCP_LOG_LEVEL), while every other variable above is read only when its flag is absent. --auth-token-file wins over --auth-token, which wins over PMCP_AUTH_TOKEN.

Other environment variables:

Env var

Purpose

PMCP_GATEWAY_URL

Running HTTP gateway that pmcp status, pmcp update, pmcp auth and pmcp doctor talk to (default http://127.0.0.1:3344/mcp).

PMCP_MANIFEST_PATH

Explicit manifest overlay (Private manifest overlay).

PMCP_MAX_LISTEN_STREAMS

Concurrent subscriptions/listen streams (default 64).

PMCP_STDIO_READ_LIMIT, PMCP_REQUEST_CEILING_MS

Downstream call tunables.

PMCP_REGISTRY_ALLOW_PRIVATE, PMCP_REGISTRY_PRIVATE_ENDPOINT

Private registry.

PMCP_FEEDBACK_TOKEN, PMCP_FEEDBACK_REPO

Feedback Telemetry.

PMCP_LEGACY_MANIFEST_AUTOSTART

Legacy manifest auto_start entries (see Adding Custom Servers).

pmcp doctor (Recommended before/after upgrades)

Use pmcp doctor to diagnose common PMCP startup and connectivity issues. It checks:

  • lock: reports whether a gateway holds the singleton lock at ~/.pmcp/gateway.lock (the file itself stays between runs and is harmless when nothing holds it)

  • mode: detects local command-mode MCP config conflicts when a shared PMCP system service is running

  • http: probes the unauthenticated /health endpoint derived from PMCP_GATEWAY_URL or http://127.0.0.1:3344/mcp

  • remote: detects unresolved remote downstream header environment references

  • install: detects conflicting uv tool and pip --user installs

Example:

pmcp doctor

If any checks fail, follow the command in the output and rerun pmcp doctor.

Singleton Lock

By default, PMCP uses a global lock at ~/.pmcp/gateway.lock to ensure only one gateway runs per user. The lock is held by the running gateway and released when it exits (or dies); the file itself is never removed, so a leftover file never blocks a start and never needs deleting. The lock file must be a plain regular file, and a link at its name is refused. The default ~/.pmcp may be your own symlink (for example into a dotfiles repository): it follows exactly the rule ~/.config/pmcp follows for the trust store -- accepted wherever it leads, except into a checkout being judged (the project pmcp serves, or the one it was started in). An explicit --lock-dir / PMCP_LOCK_DIR that is itself a symlink is refused, since a repository can supply that path. This prevents multiple gateway instances from spawning duplicate downstream servers.

Override the lock directory:

# CLI flag
pmcp --lock-dir /custom/path

# Environment variable
export PMCP_LOCK_DIR=/custom/path
pmcp

Per-project lock (not recommended):

pmcp --lock-dir ./.mcp-gateway

Downstream call tunables

# Per-line stdout read limit for downstream stdio servers (default 10 MiB).
# A single response line larger than this is dropped (failing only that call,
# with the server kept connected) rather than disconnecting the server.
export PMCP_STDIO_READ_LIMIT=$((20 * 1024 * 1024))

# Absolute backstop for a single downstream tool call (default 600000ms / 10 min).
# `timeout_ms` is an INACTIVITY timeout — a call survives as long as the server
# keeps producing output; this ceiling caps total wall-clock time for tool calls
# so a chatty-but-never-completing call cannot hang forever.
export PMCP_REQUEST_CEILING_MS=600000

The per-call inactivity timeout is gateway.invoke's options.timeout_ms (default 30000, range 1000–300000).

Deprecations

  • The mcp-gateway command no longer exists; the package installs only pmcp.

  • Use pmcp for all CLI commands.

  • Migration examples:

    • mcp-gateway refresh --force -> pmcp refresh --force

    • mcp-gateway status --json -> pmcp status --json

Docker

# Using Docker
docker run -it --rm \
  -v ~/.mcp.json:/home/appuser/.mcp.json:ro \
  -v ~/.env:/app/.env:ro \
  ghcr.io/consiliency/pmcp:latest

# Using Docker Compose
docker-compose up -d

Development

# Clone the repo
git clone https://github.com/Consiliency/pmcp
cd pmcp

# Install with uv (recommended)
uv sync --all-extras

# Run tests
uv run pytest

# Run with debug logging
uv run pmcp --debug

Running Tests

# Run all tests
uv run pytest

# Run with coverage
uv run pytest --cov=pmcp

# Run specific test file
uv run pytest tests/test_policy.py -v

Project Structure

pmcp/
├── src/pmcp/
│   ├── __init__.py
│   ├── __main__.py           # python -m pmcp entry
│   ├── cli.py                # CLI commands (status, logs, setup, doctor, trust, ...)
│   ├── server.py             # MCP server implementation
│   ├── config/
│   │   └── loader.py         # Config discovery (.mcp.json)
│   ├── client/
│   │   └── manager.py        # Downstream server connections
│   ├── policy/
│   │   └── policy.py         # Allow/deny lists
│   ├── tools/
│   │   └── handlers.py       # Gateway tool implementations
│   ├── transport/
│   │   └── http.py           # Streamable HTTP transport
│   ├── auth.py               # HTTP auth modes (shared-secret, resource-server)
│   └── manifest/
│       ├── manifest.yaml     # Server manifest (90+ servers)
│       ├── loader.py         # Manifest loading and overlays
│       ├── installer.py      # Server provisioning
│       └── environment.py    # Platform/CLI detection
├── tests/                    # 8000+ tests
├── Dockerfile
├── docker-compose.yml
├── .env.example
├── pyproject.toml
└── README.md

Troubleshooting

Server Won't Connect

pmcp status --probe             # actively connect to each configured server
pmcp logs --level error

Then reconnect it with gateway.restart_server or gateway.connect_server. (pmcp refresh regenerates cached tool descriptions; it does not reconnect a running gateway's servers.)

Missing API Key

gateway.request_capability and gateway.provision report the variable a manifest server needs (env_var, env_instructions); pmcp secrets check lists the keys your configured servers require and which are missing.

# Set the key in the shell that starts pmcp...
export GITHUB_PERSONAL_ACCESS_TOKEN=ghp_...

# ...or store it through a running HTTP gateway (prompts for the value)
pmcp auth connect github --scope user

Tool Invocation Fails

gateway.catalog_search({ query: "tool-name" })
gateway.describe({ tool_id: "server::tool-name" })
gateway.list_pending()

If gateway.refresh reports pending requests or active MCP tasks, inspect them with gateway.list_pending() and gateway.tasks_list(), or retry refresh with force=true to cancel them before reloading server configuration.

If gateway.disconnect_server or gateway.restart_server reports pending requests or active MCP tasks, inspect gateway.list_pending(server="<name>") and gateway.tasks_list(server_name="<name>"), or retry with force=true to cancel only that server's pending work.

License

MIT

Available Tools

26 tools
gateway.auth_connectA

Store credentials for a server and make them available to provisioning. Use this when gateway.provision reports missing authentication.

ParametersJSON Schema
NameRequiredDescriptionDefault
scopeNoWhere to store the credentialuser
env_varNoOptional explicit environment variable key
auth_modeNoAPI-key storage or URL-mode elicitation acknowledgementapi_key
credentialNoAPI key, token, or subscription credential to store
server_nameYesServer name that needs authentication
elicitation_idNoURL-mode elicitation identifier
elicitation_urlNoSanitized URL-mode elicitation URL
consent_acknowledgedNoAcknowledge that the out-of-band URL flow was completed

TDQS

A3.7/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden of behavioral disclosure. It only says 'store credentials' without addressing persistence, security, overwrite behavior, or the implications of the two auth modes (api_key vs url_elicitation). This is a significant gap for a credential-management tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, the first for purpose and the second for usage context. No wasted words; it is highly efficient and front-loaded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The schema is rich with descriptions and enums, but the description does not explain the relationship between auth modes and parameters like elicitation_id and consent_acknowledged. It also doesn't tell the agent what to expect after invoking (no output schema). It is adequate but not comprehensive for a tool with this complexity.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema has 100% parameter coverage with individual descriptions, so the baseline of 3 applies. The description itself adds no parameter-specific meaning, but the schema already handles that sufficiently.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's function: storing credentials for a server and making them available to provisioning. It distinguishes itself from siblings by explicitly referencing the gateway.provision missing-authentication scenario, which differentiates it from other connection/server management tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It provides a specific, actionable when-to-use condition: 'Use this when gateway.provision reports missing authentication.' This gives clear context but does not mention alternatives or when not to use it, so it stops short of the most complete guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

gateway.cancelA

Cancel a pending tool invocation. By default, refuses to cancel healthy requests (recent heartbeat). Use force=true to cancel anyway. Use gateway.list_pending first to see request IDs and health status.

ParametersJSON Schema
NameRequiredDescriptionDefault
forceNoForce cancel even if request is healthy (has recent heartbeat)
request_idYesRequest ID in format "server_name::local_id" from gateway.list_pending

TDQS

A4.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries full burden. It discloses the key behavioral trait: default refusal of healthy requests and the force flag to override. It also notes the request ID format. It could mention idempotency or error states, but the core behavior is well-transparent for a cancel operation.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three concise sentences: purpose, default behavior with force alternative, and prerequisite advice. No wasted words, front-loaded with the core action. Ideal structure for quick agent comprehension.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no output schema and simple tool, the description covers all needed context: prerequisite list_pending, default safety behavior, force option, ID format. For the complexity level, it is fully complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The description adds context to schema-defined parameters: it explains the request ID format (already in schema) and the force parameter's effect. With 100% schema coverage, baseline is 3, but the description adds extra usage context, raising it to 4.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool cancels a pending tool invocation, specifies the verb and resource, and distinguishes from siblings by referencing gateway.list_pending and explaining default refusal of healthy requests. It leaves no ambiguity about the tool's purpose.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly advises to use gateway.list_pending first to get request IDs and health status, and explains the default behavior and force flag. This provides clear when-to-use and when-not-to-use guidance, effectively integrating with the sibling tool.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

gateway.config_statusA

Show read-only effective configuration and startup policy status with source attribution and non-secret diagnostics.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A3.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden. It clearly discloses the read-only nature and adds useful context like 'non-secret diagnostics' and 'source attribution.' It doesn't mention authentication or rate limits, but for a status tool this is adequate.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, concise sentence that front-loads the read-only nature and scope. Every word earns its place with no filler or repetition.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a no-parameter status tool with no output schema, the description sufficiently covers the tool's purpose and output nature. It could elaborate on what 'effective configuration' includes, but overall it is complete for a tool of this complexity.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters, so the input schema is empty (100% coverage). The description adds meaning by explaining what the tool reports (effective configuration, startup policy status, source attribution, non-secret diagnostics), going beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses the specific verb 'Show' and identifies the resource as 'effective configuration and startup policy status,' clearly stating the tool's purpose. However, it doesn't explicitly differentiate itself from sibling gateway.get_startup_policy, which also deals with startup policy.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance is provided on when to use this tool versus alternatives like gateway.get_startup_policy or gateway.health. The description only states what it does, not when to prefer it or what scenarios it is designed for.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

gateway.connect_serverA

Connect or start a known downstream MCP server by name. Resolves configured, provisioned manifest, and registered discovered servers.

ParametersJSON Schema
NameRequiredDescriptionDefault
server_nameYesName of the server to connect

TDQS

A3.6/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden. It only states the action ('connect or start') without disclosing side effects, required permissions, rate limits, or safety considerations. For a tool that may alter server state, more transparency is needed.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences, concise and to the point. Every word adds value, with no filler or repetition. The structure is well-suited for quick understanding.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's simplicity (one parameter, no output schema, no annotations), the description adequately covers its purpose and scope. However, for a gateway tool in a diverse set, slightly more context on when to use this over sibling tools would improve completeness.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 100% schema description coverage, the schema already defines the parameter. The description adds no extra meaning beyond 'by name', which is redundant. No additional context on parameter format, source, or constraints is given, so it meets the baseline but adds minimal value.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action ('connect or start'), the resource ('downstream MCP server'), and the method ('by name'). It also distinguishes from siblings by specifying it resolves configured, provisioned manifest, and registered discovered servers, making the purpose specific and distinct.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies when to use (to connect servers that are configured, provisioned, or discovered), but does not explicitly state when not to use it or provide alternatives. Sibling tools like 'provision' or 'register_discovered_server' are not contrasted, leaving some ambiguity.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

gateway.describeA

Get detailed information about a specific tool, including its arguments and constraints. Use this before invoking a tool to understand its requirements.

ParametersJSON Schema
NameRequiredDescriptionDefault
tool_idYesThe tool ID in format "server_name::tool_name"

TDQS

A4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries full burden. It indicates a read-only operation by describing it as 'get detailed information', but does not explicitly state it is non-destructive or disclose any other behavioral traits like auth needs or rate limits.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, both essential and front-loaded. No redundant or vague language. Every word contributes value.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple describe tool with one parameter, the description adequately explains what the output contains (arguments and constraints). However, without an output schema, slightly more detail on the output structure would improve completeness.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema already covers the single parameter 'tool_id' with format guidance. The description does not add additional meaning beyond the schema, so with 100% coverage, a baseline of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description explicitly states the verb 'Get detailed information' and the resource 'a specific tool', making the purpose clear. It distinguishes itself from siblings like invoke or search_registry by focusing on tool metadata.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides a clear usage context: 'Use this before invoking a tool to understand its requirements.' This guides the agent on when to use it, though it does not explicitly mention when not to use or alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

gateway.disconnect_serverA

Disconnect a running downstream MCP server without changing persistent config. Refuses by default when that server has pending requests; set force=true to cancel them.

ParametersJSON Schema
NameRequiredDescriptionDefault
forceNoCancel this server's pending requests before disconnecting
server_nameYesName of the server to disconnect

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden. It discloses a critical behavioral trait: the tool refuses to disconnect if there are pending requests unless force=true is set, which cancels them. This goes beyond a simple statement of action and informs the agent of side effects. It omits other details like reversibility, but the disclosed behavior is significant.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences, front-loaded with the action, and every phrase earns its place. No filler or repetition of schema details.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with no output schema and no annotations, the description covers the core behavior, the default refusal condition, and the force override. It does not explain what happens after disconnect (e.g., how to reconnect) but that may be implied by sibling tools. Overall, it provides sufficient context for correct invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% and both parameters have descriptions. The tool description adds meaningful context for the force parameter by explaining the default refusal behavior and that setting force=true cancels pending requests. This enriches the schema's default value without redundancy.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with 'Disconnect a running downstream MCP server' – a specific verb and resource. It further clarifies 'without changing persistent config', which distinguishes it from configuration-changing tools and gives a precise scope of action.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides clear operational context: it refuses by default when pending requests exist and explains when to use force=true. It does not explicitly name alternatives or exclusion criteria, but the persistent-config qualification helps differentiate from sibling tools like update_server or connect_server.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

gateway.get_startup_policyA

Return persisted autoStart and legacy disableAutoStart entries grouped by config source.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden. It indicates a read operation ('Return') but does not explicitly confirm no side effects, permissions required, or other behaviors like rate limits. Adequate but not explicit.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single sentence that efficiently conveys the tool's exact purpose with no extraneous words.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no output schema or annotations, the description provides enough context for the tool's core function. It identifies what is returned and grouping, though details on 'config source' or output format are missing. Adequate for a simple retrieval.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has no parameters (0 params, 100% coverage). The description adds no param info, but none is needed. Baseline 4 is appropriate for a parameterless tool.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool returns persisted autoStart and disableAutoStart entries grouped by config source, using a specific verb ('Return') and resource. It implicitly distinguishes from the sibling 'set_startup_policy'.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No explicit usage guidance is provided, but the purpose is clear as a retrieval operation. The description implies when to use (when you need startup policy data), but does not mention exclusions or when-not-to-use.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

gateway.healthA

Get the health status of the gateway and all connected MCP servers. Shows server status, tool counts, and last refresh time.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden of behavioral disclosure. It clearly states the tool returns health status, server status, tool counts, and last refresh time, implying a read-only, non-destructive operation. However, it does not explicitly state that it is non-destructive or mention any auth requirements.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, front-loaded sentence that efficiently communicates the tool's purpose and output without any unnecessary words.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no parameters and no output schema, the description is complete. It specifies the key outputs (server status, tool counts, last refresh time) that an agent would need to understand what the tool does.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters, and the schema description coverage is 100%. The description adds value by explaining what the tool returns, which goes beyond the empty schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses the specific verb 'Get' and clearly identifies the resource as 'health status of the gateway and all connected MCP servers'. It distinguishes from sibling tools by focusing on health, tool counts, and refresh time, which no other sibling explicitly covers.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description does not explicitly state when to use this tool versus alternatives or when not to use it. While the purpose is clear, there is no guidance on proper usage context or exclusions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

gateway.invokeB

Invoke a tool on a downstream MCP server. Arguments are validated against the tool schema before execution. Output is automatically truncated if too large.

ParametersJSON Schema
NameRequiredDescriptionDefault
optionsNo
tool_idYesThe tool ID in format "server_name::tool_name"
argumentsNoArguments to pass to the tool (must match tool schema)
run_correlation_idNoScoped-advisor run correlation ID
seat_correlation_idNoScoped-advisor seat correlation ID
evidence_label_digestNoSHA-256 digest of the caller evidence label

TDQS

B3.2/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden. It discloses validation before execution and automatic output truncation, but lacks details on error handling, idempotency, or authentication requirements.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, no redundant information, purpose is front-loaded. Every sentence provides essential behavioral insight.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Despite describing core behaviors, the description omits return value format, error handling, and how to structure the nested 'options' object. For a complex gateway proxy tool, more detail is needed.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 83%, so the schema already describes most parameters. The description adds context about validation and truncation but does not elaborate on parameter usage beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool invokes a tool on a downstream MCP server, with specific actions like validation and truncation. It distinguishes from sibling tools as no other gateway tool serves a similar proxy invocation purpose.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance is provided on when to use this tool versus alternatives, nor any conditions or exclusions. The agent has no help in deciding context.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

gateway.list_pendingA

List all pending tool invocations with health status. Shows elapsed time, heartbeat age, and current state for each request. Use this to monitor long-running operations before deciding to cancel.

ParametersJSON Schema
NameRequiredDescriptionDefault
serverNoFilter to pending requests on a specific server (optional)

TDQS

A4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description must carry the burden of behavioral disclosure. It describes the output fields but does not explicitly state that the operation is read-only or that it does not modify state. However, the action 'list' generally implies safety, and the use case suggests monitoring rather than mutation.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences: the first explains what the tool does, the second provides usage guidance. It is concise with no wasted words.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description includes what information the output contains, which is helpful given the lack of an output schema. The parameter is well-documented in the schema, and the overall context is sufficient for correct invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has 100% coverage for the single parameter 'server', which already includes a description. The tool description adds no additional meaning beyond the schema, so the baseline score of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description explicitly states the verb (List), resource (pending tool invocations), and the specific data shown (health status, elapsed time, heartbeat age, current state). This clearly distinguishes it from sibling tools like gateway.tasks_list.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides a clear use case: 'Use this to monitor long-running operations before deciding to cancel.' It implies when to use but does not explicitly contrast with alternatives, though the context of canceling hints at the sibling cancel tool.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

gateway.provisionA

Provision (install and start) a specific MCP server from the manifest. Use this after reviewing candidates from gateway.request_capability. Returns immediately with a job_id for tracking. Poll gateway.provision_status to check progress. Use gateway.request_capability instead if you don't know the exact server name.

ParametersJSON Schema
NameRequiredDescriptionDefault
server_nameYesName of the server to provision (from manifest)

TDQS

A4.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden for behavioral transparency. It discloses that the tool returns immediately with a job_id and directs the agent to poll gateway.provision_status for progress, covering the asynchronous execution model. It does not mention potential side effects beyond install/start (which is the intended function), but overall it provides meaningful behavioral context.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a compact set of three sentences, each earning its place: purpose, usage context, and asynchronous behavior. No fluff, key information is front-loaded, and the structure is easy to scan.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple one-parameter tool with no output schema, this description is complete. It explains what it does, when to use it, what to expect (job_id), how to track progress (poll provision_status), and when to use an alternative (request_capability). No critical missing context for an agent to select and invoke the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema already covers server_name with a description ('Name of the server to provision (from manifest)'), and schema coverage is 100%. The description adds workflow context by emphasizing the need for the 'exact server name' and connecting it to the request_capability discovery process, which enriches the parameter's meaning beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action ('Provision (install and start)') and the specific resource ('a specific MCP server from the manifest'). It distinguishes itself from siblings like gateway.request_capability (discovery) and gateway.provision_status (tracking) by focusing on the installation/start action.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit usage guidance is provided: 'Use this after reviewing candidates from gateway.request_capability' and 'Use gateway.request_capability instead if you don't know the exact server name.' This clearly tells when to use this tool versus the alternative, with a concrete workflow.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

gateway.provision_statusA

Check the status of a running server installation. Use after gateway.provision returns a job_id. Returns progress percentage, output log, and final tools when complete.

ParametersJSON Schema
NameRequiredDescriptionDefault
job_idYesJob ID from gateway.provision response

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description discloses what the tool returns: 'progress percentage, output log, and final tools when complete.' Since no annotations are provided, the description carries the full burden and does so excellently, indicating a read-only operation without side effects.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, no redundant words. Key information is front-loaded: purpose, usage context, and return values. Every sentence earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a single-parameter tool with no output schema, the description fully covers what the tool does, when to use it, and what to expect in return. The agent can use it without additional guesswork.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema defines job_id with a description, but the tool description adds context ('Use after gateway.provision returns a job_id'), clarifying the parameter's origin and purpose, going beyond the schema's minimal description.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool checks the status of a server installation, specifying the verb 'check' and the resource 'status of a running server installation'. It distinguishes itself from siblings like 'config_status' or 'health' by focusing on the provisioning lifecycle.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly states when to use the tool: 'Use after gateway.provision returns a job_id.' This provides clear context, though it does not mention when not to use or list alternatives, which would further differentiate it from siblings.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

gateway.refreshA

Reload backend MCP server configurations and reconnect. Use this when new MCP servers have been configured or to recover from connection errors. Refuses by default while downstream requests are pending; set force=true to cancel them.

ParametersJSON Schema
NameRequiredDescriptionDefault
forceNoCancel pending downstream requests before refreshing
reasonNoReason for refresh (for logging)
sourceNoConfig source to reload from

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It reveals an important behavioral trait: 'Refuses by default while downstream requests are pending; set force=true to cancel them.' This adds meaningful context beyond a simple reload/reconnect statement, though it could mention additional side effects (e.g., impact on active connections).

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences, front-loaded with the primary action, and every word earns its place. There is no redundant or filler content.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no output schema and no annotations, the description adequately covers purpose, usage context, and a key behavioral nuance (refusal/force). It lacks return-value or error details, but those are not strictly required for a tool of this simplicity and are sufficiently implied.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3. The description reinforces the 'force' parameter ('set force=true to cancel them') but adds no new semantic meaning beyond what the schema already provides for reason and source.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with 'Reload backend MCP server configurations and reconnect,' a specific verb+resource pair that clearly states what the tool does. It also distinguishes from siblings by explicitly mentioning the use cases: 'when new MCP servers have been configured or to recover from connection errors.'

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides clear context for when to use the tool ('Use this when new MCP servers have been configured or to recover from connection errors'). However, it does not mention when not to use it or suggest alternative tools, so it falls short of a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

gateway.register_discovered_serverA

Register an externally-discovered MCP server package so it can be provisioned. Call this after gateway.search_registry to register the chosen package, then call gateway.provision to install and start it.

ParametersJSON Schema
NameRequiredDescriptionDefault
packageYesnpm package identifier (e.g. '@modelcontextprotocol/server-github')
env_varsNoRequired environment variable names (e.g. ['GITHUB_TOKEN'])
descriptionNoShort description of the server's purpose
server_nameYesLogical name for this server (e.g. 'github') used with gateway.provision

TDQS

A4.1/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations exist, so the description carries full burden. It hints at the registration action but does not disclose idempotency, overwrite behavior, auth requirements, or failure modes. Adequate but incomplete.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences efficiently convey purpose and usage sequence. No wasted words.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no output schema and no annotations, the description explains the tool's role in a pipeline but omits return value, error conditions, or confirmation details. Adequate but not fully complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% with descriptions for all four parameters. The description does not add extra meaning beyond what the schema already provides, thus baseline score applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action 'Register an externally-discovered MCP server package' and distinguishes the tool from siblings by placing it in a pipeline: after gateway.search_registry and before gateway.provision.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly tells when to use this tool: 'Call this after gateway.search_registry to register the chosen package, then call gateway.provision to install and start it.' This provides clear context and ordering.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

gateway.request_capabilityA

Recommend the right tool for a task — describe what you need in natural language. Examples: 'scrape a website', 'search Slack messages', 'query Postgres', 'browse the web'. Matches against installed CLIs and 90+ provisionable MCP servers and returns ranked candidates; it does NOT start anything — call gateway.provision to actually install/start the recommended server. Prefer this over gateway.provision when you don't already know the exact server name.

ParametersJSON Schema
NameRequiredDescriptionDefault
queryYesNatural language description of the capability needed (e.g., 'I need to scrape a website', 'browser automation')
available_clisNoOptional: CLIs known to be available in the environment

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description fully explains that the tool returns ranked candidates by matching against CLIs and MCP servers. It explicitly states it does NOT start anything, making the non-destructive, advisory nature clear. Absent are details about return structure or limitations like rate limits, but these are minor for a recommendation tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is extremely concise with three sentences: one explaining the tool's purpose with examples, one clarifying it doesn't execute, and one giving usage guidance. Every sentence adds value with no redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple recommendation tool with two parameters and no output schema, the description covers essential aspects: purpose, examples, non-execution behavior, and sibling differentiation. It mentions matching against 90+ provisional MCP servers, providing helpful context. Minor omissions like exact return format are acceptable given the tool's simplicity.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema covers 100% of parameters, establishing a baseline of 3. The description adds value by providing contextual examples for the query parameter (e.g., 'scrape a website') and explaining the available_clis parameter's purpose. It also mentions that results are ranked candidates, going beyond schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states this tool recommends the right tool for a task based on natural language description. It uses specific verbs ('Recommend', 'describe what you need') and distinguishes itself from sibling tool gateway.provision by clarifying it does not start anything.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly advises preferring this tool over gateway.provision when the exact server name is unknown, providing clear when-to-use guidance. It also offers examples of queries, though it lacks explicit when-not-to-use scenarios beyond the contrast with provision.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

gateway.restart_serverA

Restart a known downstream MCP server without changing persistent config. Refuses by default when that server has pending requests; set force=true to cancel them.

ParametersJSON Schema
NameRequiredDescriptionDefault
forceNoCancel this server's pending requests before restarting
server_nameYesName of the server to restart

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden. It discloses that the tool refuses by default if pending requests exist and that force=true cancels them. This adds meaningful behavioral context beyond just saying 'restarts a server'. It could mention that the restart is not persistent and that the server must be known, but overall it's good.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences, front-loaded with the main action. Every sentence adds value without redundancy. It is efficient and well-structured.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the simplicity of the tool (2 parameters, no output schema, no nested objects), the description is sufficiently complete. It covers the default behavior and the force option. It could mention what happens after restart (e.g., server comes back online), but the core functionality is clear.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so baseline is 3. The description adds value by explaining the default refusal behavior for the force parameter and how it relates to pending requests. This is extra context beyond the schema's 'Cancel this server's pending requests before restarting', justifying a 4.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool restarts a known downstream MCP server without changing persistent config. It uses a specific verb (restart) and resource (downstream MCP server), avoiding tautology. It also implicitly distinguishes from sibling tools like connect_server or update_server.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides explicit guidance on when to use the force parameter: when there are pending requests, the tool refuses by default, so force=true is needed to cancel them. It does not mention alternatives or explicitly state when not to use this tool, but the context is clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

gateway.search_registryA

Search the public MCP Registry for external servers not in the local manifest. Use this when gateway.request_capability returns not_available. Returns package names and metadata; call gateway.register_discovered_server then gateway.provision to install.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMaximum number of results to return
queryYesNatural language description of the capability needed

TDQS

A4.2/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden. It indicates the tool is a search operation returning package names and metadata, but does not disclose potential side effects, authentication requirements, rate limits, or error handling. The description is adequate but lacks detailed behavioral context beyond the basic read-only nature implied by 'search'.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences, front-loading the purpose and usage condition, then providing the expected output and follow-up steps. Every sentence adds value, and there is no redundancy or wasted words.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a search tool with two well-documented parameters and no output schema, the description provides essential context: trigger condition, output type, and next steps. It does not cover edge cases or output formatting, but the schema compensates. Given the tool's simplicity, this is sufficiently complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% with clear descriptions for both 'query' and 'limit'. The description adds context by stating the query is a 'natural language description of the capability needed', which aligns with the schema. However, it does not elaborate on the 'limit' parameter, so it adds no meaning beyond the schema's own description.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb ('Search'), resource ('public MCP Registry'), and scope ('external servers not in the local manifest'). It explicitly distinguishes this tool from siblings like gateway.catalog_search and gateway.request_capability by specifying the registry target and the condition for use.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides an explicit when-to-use condition ('Use this when gateway.request_capability returns not_available') and outlines the subsequent workflow ('call gateway.register_discovered_server then gateway.provision to install'). This gives clear guidance on when and how to use the tool.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

gateway.set_startup_policyC

Preview or explicitly apply an autoStart add/remove/set operation against one selected config source or path.

ParametersJSON Schema
NameRequiredDescriptionDefault
pathNo
applyNo
namesNo
sourceNo
dry_runNo
operationYes

TDQS

C2.7/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description mentions 'preview or explicitly apply' but does not disclose side effects, reversibility, or required permissions. With no annotations, the agent lacks key behavioral context.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Single sentence with no wasted words, though it sacrifices clarity for brevity. Could be better structured to separate preview vs apply modes.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Missing output schema and no return value description. Does not explain what happens after applying the policy or how to interpret preview results. Insufficient for a tool with 6 parameters.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must explain parameters. It covers 'operation' (add/remove/set) and implies 'dry_run' via 'preview', but leaves 'path', 'apply', 'names', and 'source' undefined or ambiguous.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool performs preview or explicit application of an autoStart add/remove/set operation on a config source or path. It distinguishes from the sibling 'get_startup_policy' which reads the policy.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No explicit guidance on when to use this tool versus alternatives like 'get_startup_policy'. No mention of prerequisites or scenarios where preview/apply is appropriate.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

gateway.submit_feedbackA

Prepare and optionally submit a PMCP feedback issue to GitHub. By default returns an exact preview payload; set confirm_submission=true to submit.

ParametersJSON Schema
NameRequiredDescriptionDefault
titleYesIssue title
issue_typeNobug
descriptionYesIssue details (technical data only)
failed_tool_callNoSpecific failed tool call (if known)
confirm_submissionNoSet true only after user confirms submission
subordinate_serverNoSubordinate MCP server involved (if known)

TDQS

A3.9/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden. It discloses the two-phase behavior: by default returns a preview, and submission only occurs when confirm_submission is true. This is clear and transparent, though no further details like rate limits or auth needs are given.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is extremely concise: two sentences, no wasted words. It front-loads the main purpose and then clarifies the key behavior (default vs. submission). This is an efficient and well-structured description.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given six parameters and no output schema, the description covers the main behavioral distinction (preview vs. submit) but omits details such as the preview payload format, error handling, or expected output. This leaves the agent with some gaps about how to interpret the result.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 83% (5 of 6 parameters described), so the baseline is 3. The description adds value only for confirm_submission (explaining its role in submission) but does not elaborate on other parameters like title, description, or issue_type. Thus, it provides marginal additional meaning.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool prepares and optionally submits a PMCP feedback issue to GitHub. It specifies the default behavior (preview) and the action to submit (confirm_submission=true). The tool is distinct from siblings, which are all about gateway operations, not feedback submission.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage for feedback preparation or submission but does not provide explicit guidance on when to use this tool versus alternatives or when not to use it. No exclusions or alternative tools are mentioned, so the guidance is minimal.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

gateway.sync_environmentA

Sync environment information from the host. Detects the platform (mac/wsl/linux/windows) and probes for installed CLIs. This information is used to prefer CLIs over MCP servers when matching capabilities.

ParametersJSON Schema
NameRequiredDescriptionDefault
platformNoOverride detected platform (optional)
detected_clisNoOverride detected CLIs (optional)

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description fully explains the tool's behavior: it syncs environment info by detecting platform and CLIs, and the info is used for capability matching. It does not mention side effects like updates to internal state, but for a sync operation this is reasonable transparency.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences with no redundancy. Every clause carries useful information: what it does, how it works, and why it's used. Ideal conciseness.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool has no required parameters, no output schema, and simple behavior. The description covers the core purpose and parameters well. Minor omission: it doesn't state what the sync returns (if anything), but for a sync operation this is a minor gap.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3. The description adds context by explaining that parameters override detected values, which is meaningful beyond the schema's 'override' phrase. This elevates the score slightly.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description specifies the verb 'sync' and the resource 'environment information', and elaborates that it detects platform and CLIs. This clearly distinguishes it from all sibling tools, none of which mention environment syncing.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explains that synced info is used to prefer CLIs over MCP servers for capability matching, giving clear context for use. It does not explicitly state when not to use, but no sibling tools serve a similar purpose, so alternatives are not needed.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

gateway.tasks_cancelB

Cancel a downstream MCP task by opaque task ID. Use gateway.cancel only for PMCP request IDs from gateway.list_pending.

ParametersJSON Schema
NameRequiredDescriptionDefault
forceNo
task_idYes
server_nameYes

TDQS

B3.4/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations provided, so description must disclose behavior. It only says 'Cancel' but does not mention side effects (e.g., whether task termination is immediate or graceful), auth requirements, or success/failure conditions. The 'force' parameter is not explained.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, no redundancy. However, efficiency is slightly compromised by not integrating parameter details, though the structure remains clean.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Without output schema or annotations, the description fails to explain return values, error states, or parameter semantics. The tool is a cancellation operation, but the user is left unsure of behavior.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters1/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, but description adds no extra meaning for any of the three parameters. 'force' is a boolean with default false, but its effect is not specified. 'server_name' and 'task_id' lack format or value constraints.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Description clearly states the verb 'Cancel' and resource 'downstream MCP task', and specifies the identifier type 'opaque task ID'. It distinguishes from sibling 'gateway.cancel' by noting it is for PMCP request IDs.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly states when to use this tool vs 'gateway.cancel', providing a clear boundary: 'Use gateway.cancel only for PMCP request IDs from gateway.list_pending.'

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

gateway.tasks_getC

Get current status for one downstream MCP task.

ParametersJSON Schema
NameRequiredDescriptionDefault
task_idYes
server_nameYes

TDQS

C2.4/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full responsibility for behavioral traits. It does not disclose idempotency, error handling, or any side effects. The minimal statement 'get current status' lacks depth on what constitutes 'status' or expected outcomes.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single sentence with 9 words, achieving brevity. However, it is too sparse to be fully useful; it sacrifices necessary detail for conciseness. Front-loaded with the verb, but lacks structure for additional context.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's simplicity (2 params, no output schema) and lack of annotations, the description should provide more detail. It does not explain what 'current status' means, output format, or how to use the parameters effectively, leaving gaps for an agent.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters1/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, and the description adds no meaning to the parameters (task_id, server_name). It does not explain their purpose, format, or constraints, forcing the agent to rely solely on the parameter names and types.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb 'get' and the resource 'current status for one downstream MCP task', differentiating it from siblings like tasks_list (list) and tasks_cancel (cancel). However, it does not elaborate on what 'downstream' means, slightly reducing clarity.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance on when to use this tool versus alternatives. It does not mention prerequisites, exclusions, or when not to use. The description simply states the function without context for selection.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

gateway.tasks_listA

List brokered downstream MCP tasks. MCP task IDs are opaque downstream task identifiers, not PMCP request IDs.

ParametersJSON Schema
NameRequiredDescriptionDefault
cursorNoOptional downstream pagination cursor
server_nameNoOptional server filter

TDQS

A3.8/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden. It discloses an important semantic distinction about task IDs being opaque downstream identifiers, which is useful context. However, it does not mention read-only behavior, return structure, or pagination; the schema covers the cursor parameter, but the description adds only this one behavioral nuance.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single sentence of 12 words, front-loaded with the verb and resource. The additional clarification about opaque identifiers is concise and earns its place without wasted words.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple list tool with two optional parameters and no output schema, the description covers the core function and adds a key clarification about ID semantics. The cursor parameter's schema description already provides pagination context, making the description largely complete. A brief mention of the return format would be helpful but is not essential for correct invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema already provides descriptions for both parameters (cursor and server_name) with 100% coverage. The description does not add any parameter-specific information beyond the schema, so the baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb ('List') and identifies the resource ('brokered downstream MCP tasks'). The added clarification that task IDs are opaque downstream identifiers, not PMCP request IDs, further distinguishes this tool from related request-id tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage for downstream tasks rather than PMCP request IDs, but it does not explicitly state when to use this tool versus alternatives like gateway.list_pending or gateway.tasks_get. No exclusions or alternative names are provided.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

gateway.tasks_resultA

Fetch a downstream MCP task result and apply the same output redaction and truncation options as gateway.invoke.

ParametersJSON Schema
NameRequiredDescriptionDefault
optionsNo
task_idYes
server_nameYes

TDQS

A3.5/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden. It adds value by disclosing that output redaction and truncation options are applied, but it omits important behavioral details such as whether the call waits for task completion, error handling for missing/pending tasks, or permission requirements.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single sentence with a front-loaded verb, no redundant words, and all information is directly relevant to the tool's purpose.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no output schema or annotations, and the description does not explain return values, error conditions, or the relationship to the asynchronous invoke flow. Given the existence of siblings like tasks_get and tasks_list, the description does not provide enough context for an agent to reliably choose and use this tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0% and the description does not explain the parameters server_name, task_id, or the options object beyond referencing gateway.invoke for redaction/truncation. While the parameter names are self-explanatory, the description adds minimal semantic value and does not compensate for the lack of schema descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses the specific verb 'Fetch' and clearly identifies the resource as a 'downstream MCP task result', which distinguishes it from siblings like tasks_list or tasks_get. It also references gateway.invoke to clarify the output processing scope.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage for retrieving results from a downstream MCP task and mentions the same redaction/truncation options as gateway.invoke, but it does not explicitly state when to use this tool versus alternatives like tasks_get, nor does it mention prerequisites or exclusions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

gateway.update_serverA

Update a subordinate MCP server package to latest version and restart it so the new version is actually running. Call this to check for and apply an update -- the gateway does not volunteer update notices, so nothing will prompt you. Refuses to restart by default when the server has pending requests; set force=true to cancel them.

ParametersJSON Schema
NameRequiredDescriptionDefault
forceNoCancel this server's pending requests before restarting
server_nameYesName of server to update

TDQS

A4.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Despite no annotations, the description transparently discloses that the tool mutates the server by updating and restarting it. It also details the conditional refusal to restart when pending requests exist and the effect of force=true. While it does not mention potential side effects like version compatibility or downtime, the core behaviors are clearly described. The lack of annotations is compensated by the explicit statements in the description.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is concise yet informative, comprising three sentences that cover purpose, when to call, and behavioral nuance. It is well-structured, starting with the primary action, then usage context, and finally a caveat about pending requests. No unnecessary words or repetition; every sentence adds value.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given that there is no output schema, the description adequately covers all necessary context: the action (update and restart), the trigger (check for updates), and the behavioral condition (force). It provides enough information for an agent to invoke the tool correctly without needing additional details. The description is self-sufficient.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema provides descriptions for both parameters: 'Name of server to update' and 'Cancel this server's pending requests before restarting'. These descriptions are clear and adequate, and the tool description adds further context about force, explaining its purpose. The semantics are unambiguous, though the parameter descriptions are minimal. Overall, the meaning is well conveyed.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description explicitly states the tool's purpose: 'Update a subordinate MCP server package to latest version and restart it.' It uses a specific verb ('update') and a clear resource ('subordinate MCP server package'). It distinguishes itself from sibling tools like 'restart_server' by emphasizing the update aspect, making it easy for an agent to select this tool for update operations.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides clear guidance on when to call: 'Call this to check for and apply an update' and explains that the gateway does not volunteer update notices, so proactive checking is needed. It also mentions the conditional behavior with pending requests and force, giving explicit instructions on how to override the default behavior. This is sufficient for an agent to know when and how to use the tool.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 1 tool updatev2.1.1
    • Changedgateway.update_server1 field changed
      • addedInput schema / properties / force
        Added value: +{
        +  "default": false,
        +  "description": "Cancel this server's pending requests before restarting",
        +  "type": "boolean"
        +}
  2. 8 tool updatesv1.21.1
    • Addedgateway.auth_connect
    • Addedgateway.catalog_search
    • Addedgateway.config_status
    • Addedgateway.disconnect_server
    • Addedgateway.provision
    • Addedgateway.refresh
    • Addedgateway.tasks_list
    • Addedgateway.tasks_result
  3. 9 tool updatesv1.20.0
    • Removedgateway.auth_connect
    • Removedgateway.catalog_search
    • Removedgateway.config_status
    • Removedgateway.disconnect_server
    • Changedgateway.invoke3 fields changed
      • addedInput schema / properties / evidence_label_digest
        Added value: +{
        +  "description": "SHA-256 digest of the caller evidence label",
        +  "pattern": "^[0-9a-f]{64}$",
        +  "type": "string"
        +}
      • addedInput schema / properties / run_correlation_id
        Added value: +{
        +  "description": "Scoped-advisor run correlation ID",
        +  "type": "string"
        +}
      • addedInput schema / properties / seat_correlation_id
        Added value: +{
        +  "description": "Scoped-advisor seat correlation ID",
        +  "type": "string"
        +}
    • Removedgateway.provision
    • Removedgateway.refresh
    • Removedgateway.tasks_list
    • Removedgateway.tasks_result
  4. 26 tool updatesv1.18.0
    • First observedgateway.auth_connect
    • First observedgateway.cancel
    • First observedgateway.catalog_search
    • First observedgateway.config_status
    • First observedgateway.connect_server
    • First observedgateway.describe
    • First observedgateway.disconnect_server
    • First observedgateway.get_startup_policy
    • First observedgateway.health
    • First observedgateway.invoke
    • First observedgateway.list_pending
    • First observedgateway.provision
    • First observedgateway.provision_status
    • First observedgateway.refresh
    • First observedgateway.register_discovered_server
    • First observedgateway.request_capability
    • First observedgateway.restart_server
    • First observedgateway.search_registry
    • First observedgateway.set_startup_policy
    • First observedgateway.submit_feedback
    • First observedgateway.sync_environment
    • First observedgateway.tasks_cancel
    • First observedgateway.tasks_get
    • First observedgateway.tasks_list
    • First observedgateway.tasks_result
    • First observedgateway.update_server

TDQS

A3.6/5.0

Scored across 26 tools

Disambiguation5/5

Every tool has a clearly distinct purpose with descriptive names and detailed descriptions. Ambiguities like gateway.cancel vs gateway.tasks_cancel are explicitly resolved in the descriptions, so agents can reliably differentiate them.

Naming Consistency5/5

All tools follow a consistent `gateway.` prefix with lowercase snake_case verbs and nouns (e.g., list_pending, config_status, search_registry). The naming pattern is uniform and predictable across the entire set.

Tool Count2/5

With 26 tools, the count exceeds the recommended upper bound of 25 for a cohesive toolset. While the gateway domain is broad, several tools could be consolidated (e.g., merging status/check tools) to reduce cognitive overhead.

Completeness4/5

The tool surface covers core lifecycle operations: discovery, provisioning, invocation, task management, config, health, and feedback. Minor gaps exist (no explicit uninstall/remove server tool), but the common workflows are well-supported.

Maintenance

ActivityActive
ResponsivenessResponsive

Related MCP Connectors

Related MCP Servers

  • F
    license
    A
    quality
    A
    maintenance
    A multiplexing gateway that aggregates multiple MCP servers into a single port, significantly reducing context token usage through a Meta-MCP discovery system. It enables dynamic tool discovery and invocation across various transport protocols including stdio, HTTP, and SSE.
    15
    78
    -
  • A
    license
    Not graded
    quality
    A
    maintenance
    A progressive-disclosure gateway for MCP servers that keeps tool lists small by exposing one top-level tool per server, allowing agents to search, list, inspect, and call underlying tools within a selected domain.
    14
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    A gateway that aggregates multiple MCP servers into a single endpoint, namespacing their tools and forwarding calls, so an agent connects to one MCP to access the entire stack.
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    A discovery and routing layer for MCP servers that loads tool definitions on demand, reducing token usage by keeping servers out of the context window until needed.
    4
    MIT