# Running `patent-client-agents` as a stdio MCP server

The `[mcp]` extra ships a ready-to-run stdio MCP server that exposes
all patent and IP tools to any MCP-speaking client — Claude Code,
Claude Desktop, OpenAI Codex CLI, Google Gemini CLI, Cursor, Windsurf,
Cline, Zed, Continue.dev, VS Code Copilot Chat, JetBrains AI Assistant,
CoWork, or a homegrown fastmcp Client. See
[installation.md §5](installation.md#5-stdio-mcp-from-any-mcp-client)
for per-client config snippets.

## Install

```bash
pip install 'patent-client-agents[mcp]'
```

This installs the runtime (`fastmcp`, `starlette`) and adds the
`patent-client-agents-mcp` console script.

## Run

```bash
patent-client-agents-mcp                  # default: stdio transport, no auth
```

`patent-client-agents-mcp` is a thin wrapper around `patent_client_agents.mcp.server:mcp` that
runs via fastmcp. You can also invoke it directly:

```bash
python -m patent_client_agents.mcp.server
fastmcp run patent_client_agents.mcp.server:mcp
```

## MCP client configuration (example: Claude Code)

Per-client config locations and exact syntax for Codex CLI, Gemini CLI,
Cursor, Windsurf, Cline, Zed, Continue.dev, VS Code Copilot Chat,
JetBrains AI, and Claude Desktop are in
[installation.md §5](installation.md#5-stdio-mcp-from-any-mcp-client).
For Claude Code specifically — add one of the following blocks to your
MCP config (`.mcp.json` at the project root or `~/.claude.json` for
user-scope):

```json
{
  "mcpServers": {
    "patent-client-agents": {
      "command": "patent-client-agents-mcp"
    }
  }
}
```

If you prefer to invoke a specific venv or Python interpreter:

```json
{
  "mcpServers": {
    "patent-client-agents": {
      "command": "/path/to/.venv/bin/patent-client-agents-mcp",
      "env": {
        "USPTO_ODP_API_KEY": "…",
        "EPO_OPS_API_KEY": "…",
        "EPO_OPS_API_SECRET": "…"
      }
    }
  }
}
```

The server starts with no authentication in stdio mode. Any env vars
set in the `env` block are available to the connectors — USPTO ODP,
EPO OPS, and JPO all consume credentials from env (see each connector's
`CATALOG.md` entry).

## Tools exposed

The server mounts `ip_mcp`, which composes the per-source and per-intent
sub-servers under `src/patent_client_agents/mcp/tools/`.

111 patent + IP MCP tools are exposed by default. Private/local
deployments expose up to 168 tools when every env-gated family is
configured. Tool counts are generated by importing the composed FastMCP
server, not by static decorator scans:

```bash
uv run python scripts/mcp_tool_counts.py --check-docs
```

The major always-on families include Google Patents, USPTO ODP / PPUBS /
assignments / office actions / petitions / bulk data, TSDR/TMEP/TM
assignments, EPO OPS, CPC, MPEP, EPO/EPC/UPC substantive-law corpora,
USITC, CAFC, US Copyright Office, WIPO Lex, IP Australia bulk, PRV/PRH,
and the fee-schedule tools. Credential-gated families include JPO,
CanLII, EUIPO, IP Australia live APIs, KIPO, TIPO, and INPI France.

## Downloads — two transports

Tools that return bytes (PDFs, file-history docs, JPO bundles, etc.)
ride **two transports out of one fetch**:

1. An HMAC-signed `download_url` (HTTPS) when
   `LAW_TOOLS_CORE_PUBLIC_URL` is set on a remote HTTP deployment.
2. A `pca://...` MCP resource URI served via `resources/read` over
   the existing MCP session.

Tool responses include both: structured content carries
`download_url`, `resource_uri`, `filename`, `content_type`,
`size_bytes` (and `expires_at` on rotating URLs); content blocks
carry a `ResourceLink` pointing at the same `pca://` URI.

**Why two paths.** Hosted sandboxes like Claude CoWork allowlist
the MCP server but block outbound HTTP to arbitrary hosts — a
curl against `mcp.patentclient.com/downloads/...` fails even
though the tool call that minted the URL succeeded. The
`resources/read` path rides the session the client is already
holding, so the bytes flow without ever touching the network gate.
URL-comfortable clients (Claude Desktop, Cursor, homegrown
clients) keep fetching `download_url` directly and ignore the
resource link.

The resource templates are advertised via
`resources/templates/list`:

```
pca://patents/{publication_number}
pca://publications/{publication_number}
pca://epo/patents/{publication_number}
pca://uspto/applications/{application_number}/documents/{document_identifier}
pca://ptab/documents/{document_identifier}
pca://cafc/opinions/{appeal_number}
pca://usitc/documents/{document_id}/attachments/{attachment_id}
pca://jpo/documents/{ip_type}/{application_number}/{doc_kind}     # env-gated
```

The path after `pca://` matches the HTTPS download path
one-for-one — `pca://patents/US12345678B2` and
`/downloads/patents/US12345678B2` resolve the same cached bytes.

**Bulk downloads.** Bulk tools (e.g. `download_file_history`,
`download_ptab_trial_documents`) return one `ResourceLink` per
successfully fetched item alongside a zip URL. The structured
manifest carries per-item `resource_uri` + `download_url`, so
resource-aware clients can pull per-doc through MCP and avoid
JSON-RPC message caps that bite on large archives. Bulk zip
resources themselves are HTTP-only — use the per-doc URIs over
MCP, or the zip `download_url` over HTTPS.

In stdio mode without `LAW_TOOLS_CORE_PUBLIC_URL`, tools write
bytes to a tempfile and return `file_path` instead. The
`resource_uri` field is still set so MCP clients that resolve it
locally see the same path.

## Verifying

A one-off smoke test against the running server:

```python
import asyncio
from fastmcp import Client
from fastmcp.client.transports import StdioTransport

async def main():
    async with Client(StdioTransport(command="patent-client-agents-mcp", args=[])) as c:
        tools = await c.list_tools()
        print(len(tools), "tools")
        result = await c.call_tool("get_mpep_section", {"section": "2106"})
        print(result.data.get("title") if result.data else None)

asyncio.run(main())
```

Expect 111 tools by default and up to 168 tools when every env-gated
family is configured. Title should be `2106 … Patent Subject Matter
Eligibility`.

## Not installed?

If `patent-client-agents-mcp` is on PATH but startup fails with
`ModuleNotFoundError: No module named 'fastmcp'`, you installed
`patent-client-agents` without the `[mcp]` extra. Re-install with
`pip install 'patent-client-agents[mcp]'`.
