platform-mcp-stub
# platform-mcp-stub
A minimal MCP server (TypeScript, stdio transport, official
`@modelcontextprotocol/sdk`) that demonstrates **agent-native platform
onboarding with human-in-the-loop provisioning**: an agent can discover its
usage and limits on its own, but getting a credential always routes through a
visible human approval gate.
## Tools
| Tool | What it does | Real vs fixture |
| --- | --- | --- |
| `request_api_key(scope, justification)` | Files a **pending approval request** in a local `approvals.json` ledger and returns `pending human approval: <id>`. Nothing is minted by the agent. A human runs the companion `approve <id>` CLI to flip it, which attaches a **deliberately fake** key (`sk-ant-demo-...`). The agent can poll with `scope="status:<id>"` and re-fetch an already-approved scope. | The **gate is real** (ledger + CLI); the key is intentionally fake — this stub never touches real credential minting. |
| `get_usage_summary()` | Last 7 days of org token usage, daily buckets grouped by model, via the Anthropic **Usage Admin API** (`GET /v1/organizations/usage_report/messages`). | **Live** when `ANTHROPIC_ADMIN_KEY` is set (real HTTP call, real schema). Otherwise returns a fixture explicitly labeled `_fixture: true` whose shape mirrors the documented response. |
| `get_limit_status()` | The org's configured rate-limit groups (per-model-group RPM / input-TPM / output-TPM, batch queue limits) via the **Rate Limits Admin API** (`GET /v1/organizations/rate_limits`). | Same pattern: live with an admin key, labeled fixture without. |
The human-approval gate is the whole point: the agent's request, the human's
decision, and the resulting (fake) credential are all visible in one
plain-JSON ledger.
## What is real vs fixture
- **Real:** the MCP protocol surface (official SDK, stdio JSON-RPC), the
approvals ledger and CLI flow, and — when `ANTHROPIC_ADMIN_KEY` is present —
the HTTP calls to the two Admin API endpoints above (correct headers:
`anthropic-version: 2023-06-01`, `x-api-key`).
- **Fixture:** without an admin key, `get_usage_summary` / `get_limit_status`
return synthetic data labeled `_fixture: true` with a `_note`, matching the
real response schemas so downstream handling is identical.
- **Always fake:** issued keys. Format `sk-ant-demo-<hex>`; they mint nothing
and authenticate nowhere.
Note: the Admin API requires an **Admin API key** (`sk-ant-admin01-...`),
which is a different credential from a regular Claude API key, and is only
available on organization (not individual) accounts.
## Run it
```bash
npm install
npm run build
npm start # MCP server on stdio (Ctrl-C to stop)
```
Demo the approval flow end to end:
```bash
# 1. In an MCP client (or via raw JSON-RPC — see below), call:
# request_api_key(scope="usage:read", justification="dashboard prototype")
# -> "pending human approval: apprq_xxxxxxxx"
# 2. As the human operator:
npm run approve -- list
npm run approve -- apprq_xxxxxxxx
# -> approved; prints the FAKE sk-ant-demo-... key
# (to reject instead: npm run approve -- deny apprq_xxxxxxxx)
# 3. Agent re-calls request_api_key (same scope, or scope="status:apprq_xxxxxxxx")
# -> gets the demo key
```
No MCP client handy? Drive the server with raw JSON-RPC over stdio. Pipe
into `node dist/server.js` directly — not `npm start`, because npm prints
its script banner to stdout, which pollutes the JSON-RPC stream:
```bash
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"probe","version":"0.0.1"}}}' \
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"request_api_key","arguments":{"scope":"usage:read","justification":"dashboard prototype"}}}' \
| node dist/server.js
```
The same pattern works for `get_usage_summary` and `get_limit_status`
(empty `arguments`).
To exercise the live Admin API paths:
```bash
export ANTHROPIC_ADMIN_KEY=sk-ant-admin01-...
npm start
```
## Wire into Claude Code
```bash
claude mcp add platform-stub -- node /absolute/path/to/platform-mcp-stub/dist/server.js
```
Optionally pass the admin key through:
```bash
claude mcp add platform-stub -e ANTHROPIC_ADMIN_KEY=sk-ant-admin01-... -- \
node /absolute/path/to/platform-mcp-stub/dist/server.js
```
Then in a Claude Code session: "Request an API key for scope messages:write
and check our current usage and limits." Approve from a second terminal with
`npm run approve -- <id>`.
## Honest scope notes (v1)
- Evening-sized prototype, not a provisioning product: single-file JSON
ledger, no auth on the approve CLI, no expiry/rotation, no real key minting
(by design — the Admin API does support key management, which a v2 could
gate behind the same approval flow).
- Usage summary is a fixed query (7 days, `1d` buckets, `group_by[]=model`);
the real endpoint supports much richer filtering/grouping.
- `approvals.json` lives at the repo root by default; override with
`APPROVALS_FILE`.
## Requirements
- Node 20+
- `ANTHROPIC_ADMIN_KEY` (admin key, `sk-ant-admin01-...`) only for live
usage/limit data — everything else works offline.
TDQS
Scored across 3 tools
Each tool addresses a distinct domain area: rate limits, API key requests, and usage summaries. There is no meaningful overlap between them, and the status-checking behavior inside request_api_key is clearly documented rather than being a separate ambiguous tool.
All tool names follow a consistent verb_noun pattern in snake_case: get_limit_status, request_api_key, get_usage_summary. The verbs are descriptive and the naming style is uniform across the set.
Three tools is a reasonable scope for a platform admin stub focused on limits, usage, and key requests. Each tool covers a distinct administrative area without unnecessary bloat or redundant operations.
The set covers the main admin concerns suggested by the server name: rate limits, usage reporting, and API key access requests. Minor gaps exist, such as no direct approval or managed key listing, but these appear intentionally delegated to an out-of-band CLI process.