Skip to content

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 for per-client config snippets.

Install

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

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

Run

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:

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. 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):

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

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

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

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:

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]'.