§ GUIDE · MCP SERVER · paphwey-mcp

MCP server integration guide.

Drop a Paphwey tool set into any Model Context Protocol client. Claude Desktop, ChatGPT Agent Mode, OpenAI Agent SDK, Cursor, Cline — same stdio server, same five tools, scrubbed error envelopes, and a hard wall between the agent and your API key.

Package · paphwey-mcp Transport · stdio (v1) · HTTP (v2) Tools · 5 · create / present / verify / list / revoke

§ 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.

Delegation stays user-approved. The MCP server is a tool-mediator, not a trust bridge. When the agent calls 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.

VariableRequiredNotes
PAPHWEY_API_KEYYesServer-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_URLNoDefaults 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_AUDIENCENoPre-fills audience when the agent omits it.
PAPHWEY_LOG_LEVELNodebug, info (default), warn.
PAPHWEY_TIMEOUT_SECONDSNoHTTP timeout in seconds; default 15.
Scope the key tightly. The agent can call any tool you expose. Give the MCP server a dedicated key with 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."

Absolute paths on Windows. If Claude Desktop cannot find 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 binding. Set the 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.json under mcpServers.

§ 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)
HTTP transport (v2). For hosted agent runtimes where stdio is awkward, v2 adds an HTTP transport. Same tool shapes, same env variables, 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
  }
}
Common pitfall. Older snippets showed 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"
}
Tool outputs. Each tool returns a JSON-string envelope: {"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.
Treat agent input as untrusted. The MCP server validates every tool argument against the tenant's policy before it hits the gateway. But your prompt-level guardrails matter too — an agent that is told to "just approve whatever" will try to.

§ 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:delegate scope only.
  • Key rotation schedule is documented for the machine that runs the server.
  • PAPHWEY_BASE_URL points 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.

Every MCP client

One server, five tools, zero secret leakage.