Skip to main content
Glama
clankercode

mcp-fetch-patents

by clankercode

mcp-fetch-patents

The last patent data tool your agent will ever need.

Give any AI agent instant access to the entire global patent corpus. One MCP tool call. Throw any patent ID at it — US, EP, WO, JP, CN, KR, AU, CA, NZ, BR, IN — in any format, with or without kind codes, even raw Google Patents URLs. Get back PDF, Markdown, and structured metadata in seconds. Ask for it again? Instant, from cache.

Your agent shouldn't have to know that US patents live on USPTO PPUBS, European ones require EPO OPS OAuth2, and PCT applications need to be scraped from WIPO PatentScope. It shouldn't care that Espacenet has different HTML than CIPO, or that Google Patents needs a headless browser. This server ate all of that complexity so your agent never has to think about it.

"Fetch US7654321, EP1234567B1, and WO2024/123456"
  → 3 PDFs, 3 Markdown files, 3 metadata objects — cached forever

9 patent sources. 4 PDF-to-Markdown converters with OCR. Two-layer SQLite cache. Automatic retry with exponential backoff. Web search fallback when everything else fails. Zero configuration required — works out of the box with 6 of 9 sources.


Why this exists

Patent data is the most fragmented public dataset on the planet. Every national patent office has its own API, auth scheme, document format, and rate limits. An agent doing patent analysis shouldn't need to know any of that — it should just say "get me this patent" and get it.

9 patent sources, 1 tool call:

Source

Coverage

Auth required?

USPTO PPUBS

US granted + applications

No (session-based)

EPO OPS

EP, WO, 100+ offices via exchange data

Yes (OAuth2)

Espacenet

EP + EPO member states

No (scraped)

WIPO PatentScope

WO / PCT international

No (scraped)

IP Australia

AU patents

No (REST API)

CIPO

Canadian patents

No (scraped)

Google Patents

All jurisdictions

No

Google BigQuery

Bulk patent data

Yes (GCP credentials)

Web search fallback

Anything missed

No (DuckDuckGo) / Optional (SerpAPI)

Sources are tried in priority order. First success wins (unless you set PATENT_FETCH_ALL_SOURCES=true to aggregate from all). If every structured source fails, the web search fallback finds the PDF anyway. We really don't like returning empty-handed.

Related MCP server: USPTO Patent MCP Server

Quick start

Install

# Rust (recommended)
cargo install patent-mcp-server

# or build from source:
cargo build --release --manifest-path src/rust/Cargo.toml

# Python (reference implementation, also works standalone)
pip install patent-mcp-server

Configure your MCP client

Add to your Claude Desktop, Claude Code, Cursor, or Cline config:

{
  "mcpServers": {
    "patents": {
      "command": "patent-mcp-server"
    }
  }
}

That's the whole setup. No API keys needed — 6 of 9 sources work without auth (USPTO, Espacenet, WIPO, IP Australia, CIPO, DuckDuckGo). Add keys later to unlock EPO OPS, BigQuery, and SerpAPI. See docs/api-keys.md.

For an installed Rust server, secrets can live in ~/.patents.toml, normal environment variables, or ~/.patents-mcp.env autoloaded by the server. A repo-local .env is only useful when the server is launched from that checkout.

If your MCP client launches stdio servers inside a sandboxed subprocess, use the localhost HTTP transport instead:

patent-mcp-server serve-http

Then configure the client to connect to:

http://127.0.0.1:38473/mcp

The repo pins the Python HTTP endpoint to /mcp, and the Rust server uses the same default URL. The shared contract today is that URL and localhost-only binding, not byte-for-byte HTTP transport parity.

Use it

"Fetch patents US7654321 and EP1234567B1, then summarize the key claims."

The agent calls fetch_patents, gets back file paths and metadata, reads the Markdown, and does its thing. You don't configure sources. You don't pick formats. You don't manage cache. It just works.

Tools

fetch_patents

Fetch one or more patents by ID. Accepts any format — bare numbers, jurisdiction-prefixed, with kind codes, even Google Patents URLs.

Parameters:

Name

Type

Required

Description

patent_ids

string[]

Yes

Patent IDs in any format

force_refresh

bool

No

Bypass cache, re-fetch from sources

formats

string[]

No

Requested formats (default: ["pdf", "txt", "md"])

postprocess_query

string

No

Reserved for v2 post-processing

Response:

{
  "results": [
    {
      "canonical_id": "US7654321",
      "success": true,
      "from_cache": false,
      "files": {
        "pdf": "~/.local/share/patent-cache/patents/US7654321/US7654321.pdf",
        "md": "~/.local/share/patent-cache/patents/US7654321/US7654321.md"
      },
      "metadata": {
        "title": "Method and apparatus for ...",
        "inventors": ["Jane Doe", "John Smith"],
        "assignee": "Acme Corp",
        "filing_date": "2005-03-15",
        "publication_date": "2010-02-02",
        "jurisdiction": "US",
        "doc_type": "patent"
      },
      "sources": [
        {"source": "USPTO", "success": true, "elapsed_ms": 1842}
      ]
    }
  ],
  "summary": {
    "total": 1,
    "success": 1,
    "cached": 0,
    "errors": 0,
    "total_duration_ms": 2105
  }
}

list_cached_patents

List all cached patents. No parameters, returns {patents: [...], count: N}.

get_patent_metadata

Cache-only metadata lookup — no network calls, instant response.

Parameters: patent_ids: string[] Returns: {results: [{patent_id, canonical_id, metadata}]}

Search tools (patent-search MCP server)

The search server (python -m patent_mcp.search) provides tools for natural language patent search, structured queries, citation chains, and research sessions.

It also supports localhost Streamable HTTP:

python -m patent_mcp.search serve-http

Search startup includes a best-effort browser prewarm only when the optional browser dependencies are installed. It is safe to ignore if unavailable, and it does not guarantee that later search calls reuse the same browser/profile.

patent_search_natural

Search using plain English. The planner expands your description into multiple query variants using keyword/synonym expansion, runs them against Google Patents (browser and/or SerpAPI), and reranks results.

Parameters:

Name

Type

Required

Description

description

string

Yes

Natural language description of the technology

date_cutoff

string

No

ISO date — only return patents before this date

jurisdictions

string[]

No

Filter by jurisdiction (e.g. ["US", "EP"])

session_id

string

No

Auto-save results to a research session

max_results

int

No

Maximum results after ranking (default 25)

backend

string

No

"browser", "serpapi", or "auto" (default)

enrich_top_n

int

No

Enrich top N hits with full metadata (default 5)

Other search tools

  • patent_search_structured — Expert Boolean query syntax against USPTO, EPO OPS, Google Patents

  • patent_citation_chain — Follow citations forward/backward (depth 1-3)

  • patent_classification_search — Search by IPC/CPC code

  • patent_family_search — Find patent family members across jurisdictions

  • patent_suggest_queries — Generate search strategy without executing

  • patent_search_profile_login_start — Launch headed browser for Google login

Research sessions

Sessions persist search results across multiple tool calls:

  • patent_session_create — Start a new research session

  • patent_session_load/list/note/annotate — Manage session state

  • patent_session_export — Generate Markdown report

Patent ID formats

Don't worry about formatting. The canonicalizer has seen it all:

Input

Canonical form

Jurisdiction

US7654321

US7654321

US

US7654321B2

US7654321

US (with kind code)

US20230001234A1

US20230001234

US application

7654321

US7654321

US (inferred)

EP1234567B1

EP1234567

EP

WO2024/123456

WO2024/123456

International (PCT)

JP2023-123456

JP2023123456

Japan

CN202310001234A

CN202310001234

China

KR10-1234567

KR10-1234567

South Korea

AU2023123456

AU2023123456

Australia

CA3123456

CA3123456

Canada

https://patents.google.com/patent/US7654321/en

US7654321

(extracted)

Also handles NZ, BR, and IN formats. 22+ patterns total. If it looks like a patent number, we'll figure it out.

How it works

Agent                    MCP Server (Rust)
  │                           │
  ├─ fetch_patents ──────────►│
  │                           ├─ cache lookup (SQLite)
  │                           │   HIT? return immediately
  │                           │   MISS?
  │                           │     ├─ try sources in priority order
  │                           │     │   USPTO → EPO → Espacenet → WIPO → ...
  │                           │     ├─ download PDF (async, concurrent)
  │                           │     ├─ convert PDF → Markdown
  │                           │     │   (pymupdf4llm → pdfplumber → pdftotext → marker)
  │                           │     ├─ extract metadata
  │                           │     └─ store in cache
  │◄──────────────────────────┤
  │   files + metadata        │

Cache: Single global SQLite DB at ~/.local/share/patent-cache/index.db shared across all projects. Patent files live in ~/.local/share/patent-cache/patents/{CANONICAL_ID}/. First fetch takes seconds. Every subsequent fetch is instant — even across different repos.

HTTP transport: Rust, Python fetch, and Python search can each run on localhost HTTP at the default URL http://127.0.0.1:38473/mcp when launched with serve-http. Stdio remains the default when you launch a server without serve-http. The implementations share the same default URL and localhost-only bind, but this repo does not currently claim wire-level Rust/Python HTTP parity.

Activity journal: Each tool call is logged to .patent-activity.jsonl in the current working directory (JSONL format). This file is meant to be git-tracked so each project retains a record of what was searched, fetched, and accessed. Disable with PATENT_ACTIVITY_JOURNAL="".

PDF → Markdown: Four converter backends tried in order (pymupdf4llm → pdfplumber → pdftotext → marker). If one fails, the next picks up. Tables extracted and merged. OCR via tesseract for scanned patent figures. The output is clean enough for an LLM to read directly.

Dual implementation, cross-verified: Rust is the production server — standalone, async, with native HTTP fetchers and retry logic. Python is the reference implementation. 39 cross-implementation parity tests verify both produce identical results for ID canonicalization, source ordering, converter output, and web search queries. When we say they match, we mean it — it's tested.

Configuration

All config via autoloaded env files, ~/.patents.toml, or environment variables (env vars take precedence). The Rust server autoloads ~/.patents-mcp.env and then .env from the current working directory before reading ~/.patents.toml and explicit environment variables.

Env var

Default

Description

PATENT_CACHE_DIR

~/.local/share/patent-cache/patents

Cache directory for patent files

PATENT_CONCURRENCY

10

Max concurrent fetches

PATENT_TIMEOUT

30.0

HTTP timeout (seconds)

PATENT_EPO_KEY

EPO OPS client_id:client_secret

PATENT_LENS_KEY

Lens.org API key

PATENT_SERPAPI_KEY

SerpAPI key (web search fallback)

PATENT_BIGQUERY_PROJECT

GCP project for BigQuery source

PATENT_FETCH_ALL_SOURCES

true

Try all sources even after first success

PATENT_ACTIVITY_JOURNAL

.patent-activity.jsonl

Per-repo activity journal (empty = disabled)

PATENT_SEARCH_BACKEND_DEFAULT

browser

Default search backend (browser, serpapi, auto)

PATENT_SEARCH_BROWSER_HEADLESS

true

Run Playwright in headless mode

PATENT_SEARCH_BROWSER_IDLE_TIMEOUT

1800

Browser idle timeout in seconds (default 30 min)

PATENT_SEARCH_BROWSER_MAX_PAGES

3

Max Google Patents result pages per query

PATENT_SEARCH_ENRICH_TOP_N

5

Enrich top N search results with full metadata

Development

Running tests

# Rust tests (237 tests, <0.1s)
cargo test --manifest-path src/rust/Cargo.toml

# Start the Rust server over localhost HTTP
just serve-rust-http

# Start the Python fetch server over localhost HTTP
just serve-http

# Start the Python search server over localhost HTTP
just serve-search-http

# Direct stdio MCP smoke test against the Rust dev server
just mcp-smoke-rust

# Direct stdio MCP smoke test against the installed Rust binary
just mcp-smoke-rust-installed

# Fast Python unit tests (<1s, all I/O mocked)
pytest tests/python/ -m "not browser and not integration and not slow"

# Full Python suite (includes fuzz tests via Hypothesis, slow tests)
pytest tests/python/

# Cross-implementation parity — verifies Python == Rust (39 tests)
pytest tests/cross_impl/

# Manual E2E tests — automated script (31 tests)
python3 run_manual_e2e.py

Project structure

src/
  python/patent_mcp/       # Python reference implementation
    id_canon.py            #   Patent ID canonicalization (22+ formats)
    cache.py               #   Dual-layer SQLite cache
    config.py              #   TOML + env var config loading
    fetchers/
      http.py              #   All HTTP/API source implementations
      web_search.py        #   DuckDuckGo / SerpAPI fallback
      orchestrator.py      #   Priority-ordered source orchestration
    converters/            #   PDF → Markdown (4 backends + OCR)
    server.py              #   MCP server (FastMCP, stdin/stdout)
  rust/                    # Rust production server (standalone)
    src/
      id_canon/            #   Canonicalization (must match Python)
      cache/               #   Two-layer SQLite cache (rusqlite)
      config/              #   TOML + env var config loading
      converters/          #   PDF → Markdown (4 backends + OCR)
      fetchers/
        http/              #   9 native async HTTP sources with retry
        web_search/        #   DuckDuckGo + SerpAPI fallback
        browser.rs         #   Native Google Patents HTML/microdata fetcher
        mod.rs             #   Priority-ordered source orchestrator
      server/              #   JSON-RPC 2.0 over stdin/stdout
tests/
  python/                  # 16 test files: unit, integration, fuzz
  cross_impl/              # Python == Rust parity tests
  fixtures/                # Shared test data
docs/
  api-keys.md              # API key setup for each source

What makes this different

Most patent tools give you an API wrapper for one database. This gives your agent the entire global patent system behind a single function call. It handles the authentication, the format differences, the fallbacks, the caching, and the PDF-to-text conversion. Your agent asks for a patent and gets back something it can read. That's it. That's the product.

MCP Debugging

If your editor or MCP client seems to be using a stale server process, test the Rust server directly over stdio JSON-RPC instead of guessing whether the client reloaded:

# Current checkout / dev binary
just mcp-smoke-rust

# Installed binary from ~/.cargo/bin
just mcp-smoke-rust-installed

# Different patent
just mcp-smoke-rust US7654321B2

These recipes send initialize and tools/call requests straight to the server process, so they verify the real binary behavior without depending on Claude/OpenCode reload state.

License

CC0 1.0 Universal

A
license - permissive license
-
quality - not tested
D
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/clankercode/mcp-fetch-patents'

If you have feedback or need assistance with the MCP directory API, please join our Discord server