§ 01 · The mental model
MCP clients talk to tool servers over stdio or HTTP.
paphwey-mcp is a local process — it
runs next to the agent runtime on the developer's
or operator's machine, holds the API key in its own
environment, and the agent can only see the five
tools it exposes.
The agent never sees your
sk_live_* / sk_test_*
key. It only sees
the tool surface. The MCP server brokers the calls
to www.paphwey.com on the agent's
behalf, scrubs error envelopes for PII, and returns
a compact, machine-friendly shape.
present_delegation, the user still
approves on their phone. The MCP server never
auto-approves.
§ 02 · Install
The server ships on PyPI. It has no hard
dependencies beyond the mcp runtime
and httpx.
pip install paphwey-mcp # Or, for systems where pipx is the right shape: pipx install paphwey-mcp # Verify: paphwey-mcp --version
The entry point is paphwey-mcp. It
speaks the MCP stdio framing out of the box — no
flags required for the default configuration.
§ 03 · Environment & secrets
The server reads two environment variables. It
refuses to start without PAPHWEY_API_KEY.
| Variable | Required | Notes |
|---|---|---|
PAPHWEY_API_KEY | Yes | Server-side RP key. Production tenants are issued sk_live_…; sandbox tenants (RP is_sandbox=true) are issued sk_test_…. Both authenticate against the same agent triad endpoints. |
PAPHWEY_BASE_URL | No | Defaults to https://www.paphwey.com (so the package "just works" against the live tenant). Override to http://localhost:8000 when running against a local dev stack. |
PAPHWEY_DEFAULT_AUDIENCE | No | Pre-fills audience when the agent omits it. |
PAPHWEY_LOG_LEVEL | No | debug, info (default), warn. |
PAPHWEY_TIMEOUT_SECONDS | No | HTTP timeout in seconds; default 15. |
agent:delegate scope only — not a
broader admin key.
§ 04 · Claude Desktop
Open
~/Library/Application Support/Claude/claude_desktop_config.json
on macOS, or
%APPDATA%\Claude\claude_desktop_config.json
on Windows.
{
"mcpServers": {
"paphwey": {
"command": "paphwey-mcp",
"env": {
"PAPHWEY_API_KEY": "sk_live_...",
"PAPHWEY_BASE_URL": "https://www.paphwey.com",
"PAPHWEY_DEFAULT_AUDIENCE": "merchant.example"
}
}
}
}
Restart Claude Desktop. Open any conversation, and the five Paphwey tools appear in the tool menu. Test with a prompt like "List the Paphwey policies available to me."
paphwey-mcp, replace
"command": "paphwey-mcp" with the
absolute path returned by where
paphwey-mcp.
§ 05 · ChatGPT Agent Mode
ChatGPT's Agent Mode reads the same
mcpServers shape. Add the server in
the agent's connectors panel (or edit the config
file your install exposes):
{
"mcpServers": {
"paphwey": {
"command": "paphwey-mcp",
"env": {
"PAPHWEY_API_KEY": "sk_live_...",
"PAPHWEY_BASE_URL": "https://www.paphwey.com"
}
}
}
}
The tool surface is identical to Claude Desktop. One difference: ChatGPT Agent Mode will surface the full tool schema to the user on first use and ask for approval. Say yes once per tool.
§ 06 · OpenAI Agent SDK
The OpenAI Agent SDK ships an MCPServer
adapter. Launch
paphwey-mcp as a stdio child process
and register its tools with your agent.
import asyncio
from openai.agents import Agent, Runner
from openai.agents.mcp import MCPServerStdio
async def main() -> None:
async with MCPServerStdio(
name="paphwey",
params={
"command": "paphwey-mcp",
"env": {
"PAPHWEY_API_KEY": os.environ["PAPHWEY_API_KEY"],
"PAPHWEY_BASE_URL": "https://www.paphwey.com",
},
},
) as paphwey_tools:
agent = Agent(
name="shopping-agent",
instructions="Open a Paphwey challenge before any purchase.",
mcp_servers=[paphwey_tools],
)
result = await Runner.run(agent, "Buy the £15 book on my reading list.")
print(result.final_output)
asyncio.run(main())
agent_id you use in
create_delegation to a stable value
per deployment — shopping-agent
across runs, not a random UUID. It makes audit
reconciliation possible.
§ 07 · Cursor, Cline, Continue
All three IDE / coding-agent shells speak the
mcpServers convention. The block
shape is the same:
{
"mcpServers": {
"paphwey": {
"command": "paphwey-mcp",
"env": {
"PAPHWEY_API_KEY": "sk_live_...",
"PAPHWEY_BASE_URL": "https://www.paphwey.com"
}
}
}
}
Where it lives differs per client:
- Cursor —
~/.cursor/mcp.json. - Cline (VS Code) — the MCP Servers tab in the Cline panel.
- Continue —
~/.continue/config.jsonundermcpServers.
§ 08 · Custom MCP clients
Any runtime with the MCP client protocol can speak
to paphwey-mcp. Two things matter:
the stdio framing is JSON-RPC over newline-delimited
messages, and the tool schemas are served via
tools/list.
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
params = StdioServerParameters(
command="paphwey-mcp",
env={
"PAPHWEY_API_KEY": os.environ["PAPHWEY_API_KEY"],
"PAPHWEY_BASE_URL": "https://www.paphwey.com",
},
)
async with stdio_client(params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
tools = await session.list_tools()
for tool in tools.tools:
print(tool.name, "-", tool.description)
result = await session.call_tool("list_policies", {})
print(result.content)
paphwey-mcp --http 127.0.0.1:7701.
§ 09 · Tool reference
Each tool's input schema is registered with the MCP runtime, so a compliant client surfaces field names, types, and required-flags directly to the agent. Examples below mirror the canonical agent-action triad — same field names as the REST guide and the Python SDK.
create_delegation
Required: provider,
agent_id, allowed_scopes,
allowed_challenge_types,
currency, valid_until, plus
one of principal_email /
principal_id. max_amount_minor
is required for monetary scopes (pass 0
for non-monetary flows like age verification).
{
"principal_email": "alice@example.com",
"provider": "openai",
"agent_id": "shopping-agent",
"allowed_scopes": ["purchase"],
"allowed_challenge_types": ["HIGH_VALUE_PURCHASE_REQUIRED"],
"max_amount_minor": 2500,
"currency": "GBP",
"valid_until": "2026-05-01T00:00:00Z"
}
present_delegation
Required: delegation_id,
challenge_type, payload
(object), audience. Optional:
nonce, agent_pop.
action_context, required_claims,
minimum_assurance all live inside
payload — not at the top level.
agent_context is auto-injected from the
delegation if you omit it.
{
"delegation_id": "c5a3b2e1-...",
"challenge_type": "HIGH_VALUE_PURCHASE_REQUIRED",
"audience": "merchant.example",
"nonce": "session-nonce-123",
"payload": {
"action_context": {
"scope": "purchase",
"amount_minor": 1500,
"currency": "GBP"
},
"required_claims": {"principal_present": true},
"minimum_assurance": 10
}
}
action_context and
minimum_assurance at the top level. The
server rejects that shape with
validation_error. Always nest under
payload.
verify_outcome
Required: attestation_token,
audience. Optional but strongly
recommended: expected_delegation_id,
expected_nonce.
{
"attestation_token": "eyJhbGciOi...",
"audience": "merchant.example",
"expected_delegation_id": "c5a3b2e1-...",
"expected_nonce": "session-nonce-123"
}
list_policies
No arguments. Returns the active policy codes (the
same strings you pass as challenge_type)
and their scope envelopes for the calling tenant.
{}
revoke_delegation
Required: delegation_id.
Optional: reason (free-form audit
string).
{
"delegation_id": "c5a3b2e1-...",
"reason": "user_request"
}
{"ok": true, "result": ...} on success,
{"ok": false, "error": {"code", "message", "details"}}
on failure. Error codes mirror the HTTP layer
(authentication_failed,
permission_denied,
validation_error,
not_found, rate_limited,
server_error).
§ 10 · Safety & scrubbing
The MCP server is the layer between the agent and a live production system. Three invariants protect you:
- PII scrubbing — error envelopes are rewritten to remove principal emails, IPs, and stack traces before they are returned as tool output.
- No implicit approval — the server never accepts a challenge on the user's behalf; the approval URL is always surfaced out-of-band.
- Scope ceiling enforced twice — once at the gateway (authoritative) and once locally by the MCP server (defence in depth). A mis-configured agent that tries to exceed the tenant ceiling fails fast without a round-trip.
§ 11 · Logs & diagnostics
Logs are emitted to stderr in JSON Lines format.
MCP clients usually capture stderr to a log file
(Claude Desktop:
~/Library/Logs/Claude/mcp-server-paphwey.log).
{"t":"2026-04-18T09:34:02Z","lvl":"info","evt":"tool.call",
"tool":"verify_outcome","dlg":"dlg_01HZ...","trace":"01HZ..."}
{"t":"2026-04-18T09:34:02Z","lvl":"info","evt":"tool.result",
"tool":"verify_outcome","ok":true,"ms":118,"trace":"01HZ..."}
Set PAPHWEY_LOG_LEVEL=debug when
diagnosing a handshake or framing issue. Debug
logs include the JSON-RPC frames — but never the
API key and never the attestation-token
contents.
§ 12 · Pre-production checklist
- API key is unique to the MCP server install and has
agent:delegatescope only. - Key rotation schedule is documented for the machine that runs the server.
PAPHWEY_BASE_URLpoints at production only in production environments; sandbox everywhere else.- MCP client is configured to launch the server with environment — not by passing the key as a command-line argument.
- Log capture is enabled on the MCP client so you can reconcile audit events by
trace_id. - Agent prompts explicitly require opening a Paphwey challenge before any mutating action.
- E2E smoke test (run a real challenge → approve on a test phone → verify) is part of the release procedure.