# Pmcp

> MCP Gateway - A meta-server for minimal Claude Code tool bloat with progressive disclosure

- **Type:** MCP server
- **Install:** `agentstack add mcp-consiliency-pmcp`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [Consiliency](https://agentstack.voostack.com/s/consiliency)
- **Installs:** 0
- **Category:** [Integrations](https://agentstack.voostack.com/c/integrations)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [Consiliency](https://github.com/Consiliency)
- **Source:** https://github.com/Consiliency/pmcp

## Install

```sh
agentstack add mcp-consiliency-pmcp
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# PMCP - Progressive MCP

[](https://pypi.org/project/pmcp/)
[](LICENSE)

**Progressive disclosure for MCP** - Minimal context bloat with on-demand tool discovery and dynamic server provisioning.

## The Problem

When Claude Code connects directly to multiple MCP servers (GitHub, Jira, DB, etc.), it loads **all** tool schemas into context. This causes:
- **Context bloat**: Dozens of tool definitions consume tokens before you even ask a question
- **Static configuration**: Requires Claude Code restart to see new servers
- **No progressive disclosure**: Full schemas shown even when not needed

Anthropic has [highlighted context bloat](https://www.anthropic.com/news) as a key challenge with MCP tooling.

## The Solution

**PMCP** acts as a single MCP server that Claude Code connects to. Instead of exposing all downstream tools, it provides:

- **26 stable meta-tools** (not the 50+ underlying tools)
- **Lazy by default**: downstream servers are available on demand and only eager-start when listed in `autoStart`
- **Dynamically provisions** new servers on-demand from a manifest of 90+
- **Progressive disclosure**: Compact capability cards first, detailed schemas only on request
- **Policy enforcement**: Output size caps and optional secret redaction

## Quick Start

### Installation

```bash
# With uv (recommended)
uv pip install pmcp

# Or run directly without installing
uvx pmcp

# With pip
pip install pmcp

```

> **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:

```bash
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:

```bash
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`:

```bash
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:

```json
{
  "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:

```bash
systemctl --user is-active pmcp
curl -sS http://127.0.0.1:3344/mcp
```

### 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`:

```bash
# 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:

```python
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 on a public host. 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 returns `503` while an invalid token returns `401`.
It rejects private, link-local, loopback, multicast, and unspecified hosts in
public auth metadata URLs. 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
  `PMCP_AUTH_TOKEN`.
- Config files (`.mcp.json`) are trusted inputs — treat them like code; do not load untrusted configs.
- Secrets in `.env` files are passed to child MCP server processes; protect the `.env` file with filesystem permissions.

**Production background service (Linux systemd):**

```ini
# ~/.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
```

```bash
systemctl --user enable --now pmcp
```

Or with nohup:

```bash
PMCP_AUTH_TOKEN=replace-with-secret-token nohup pmcp --transport http >> ~/.pmcp/logs/gateway.log 2>&1 &
```

### 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`):

```nginx
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

Create `~/.codex/mcp.json` (verify path in Codex documentation):

```json
{
  "mcpServers": {
    "gateway": {
      "command": "pmcp",
      "args": []
    }
  }
}
```

#### Gemini CLI

Create the appropriate config file (verify path in Gemini CLI documentation):

```json
{
  "mcpServers": {
    "gateway": {
      "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_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](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 |

### Monitoring Tools

| Tool | Purpose |
|------|---------|
| `gateway.list_pending` | List pending tool invoca

…

## Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [Consiliency](https://github.com/Consiliency)
- **Source:** [Consiliency/pmcp](https://github.com/Consiliency/pmcp)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-consiliency-pmcp
- Seller: https://agentstack.voostack.com/s/consiliency
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
