Python MCP server
Python MCP server
Section titled “Python MCP server”The NextPDF Python SDK includes a Model Context Protocol (MCP) server that exposes PDF extraction operations as native tools for agents. An MCP-capable agent, such as Claude Code, registers the server once and then calls NextPDF tools the same way it calls any other tool.
The server is a thin adapter. Each tool reads a PDF from local disk, calls the async client for your NextPDF Connect endpoint, and returns the result as a JSON string. The server keeps no business logic and stores no data between calls.
Install the SDK with the MCP extra:
pip install nextpdf[mcp]The mcp extra installs the upstream mcp package (constraint mcp>=1.0,<2.0).
The server requires Python 3.10 or newer.
Run the server module from your MCP client configuration. The example below reads both connection values from the host environment instead of embedding a secret in the configuration file (see Security):
{ "mcpServers": { "nextpdf": { "command": "python", "args": ["-m", "nextpdf.mcp"], "env": { "NEXTPDF_BASE_URL": "https://connect.example.com", "NEXTPDF_API_KEY": "${NEXTPDF_API_KEY}" } } }}The python -m nextpdf.mcp entry point runs main(), which starts the server
over standard input/output (stdio) with asyncio.run(serve()). Do not confuse
it with python -m nextpdf, which runs the command-line interface (CLI), not
the MCP server.
NEXTPDF_BASE_URL and NEXTPDF_API_KEY are required. The server creates its
client lazily on the first tool call. If either variable is empty, it raises a
RuntimeError and returns it to the agent as a tool error instead of crashing
the process.
Tool catalog and SDK mapping
Section titled “Tool catalog and SDK mapping”The server registers eight tools. Every tool name uses the nextpdf_ prefix.
Each tool maps to a method on the async client’s ast namespace
(AsyncNextPDF.ast), except for the two composite tools noted below. The server
assembles those from lower-level calls.
| MCP tool | SDK call | Notes |
|---|---|---|
nextpdf_extract_text | ast.extract_cited_text(pdf_data, page_index=..., headings_only=...) | Returns a list of CitedTextBlock. |
nextpdf_extract_tables | ast.extract_cited_tables(pdf_data, page_range=...) | Returns ExtractCitedTablesResponse. |
nextpdf_get_ast | ast.get_document_ast(pdf_data, page_range_start=0, page_range_end=..., token_budget=...) | Returns AstDocument. |
nextpdf_info | ast.get_document_ast(pdf_data) | Server projects a metadata summary; no dedicated endpoint. |
nextpdf_health | none | Inspects environment variables only; performs no network call. |
nextpdf_search | ast.search_ast_nodes(pdf_data, node_type=..., page_index=..., text_query=..., max_results=...) | Returns SearchAstNodesResponse. |
nextpdf_get_outline | ast.search_ast_nodes(pdf_data, node_type="heading", max_results=500) | Server reshapes heading nodes into an outline. |
nextpdf_diff | ast.get_ast_diff(original_pdf_data, modified_pdf_data) | Returns GetAstDiffResponse. |
Keep these tool-input details in mind before you wire up an agent:
- All path inputs (
pdf_path,original_pdf_path,modified_pdf_path) are absolute paths to files on the machine running the server. The agent passes a path, and the server reads the bytes locally. There is no upload tool. nextpdf_extract_textdeclares amax_pagesfield in its input schema, but the text handler does not pass it to the SDK. Page scoping for text happens throughpage_index(a single 0-based page). Usenextpdf_get_astwithmax_pageswhen you need to bound a whole-document walk.nextpdf_get_asttranslatesmax_pagesinto an inclusive page range of[0, max_pages - 1](defaultmax_pagesis 50). Passtoken_budgetto cap the size of the returned tree.nextpdf_inforeturnsschema_version,source_hash,page_count,estimated_tokens,root_node_type, androot_children_count. These values come from theAstDocumentmodel, whereestimated_tokensis a computed property (roughly four characters per token).nextpdf_get_outlinereturns one entry per heading withid,page_index,text, anddepth(read from the node’sattributes["level"], defaulting to 1), plusheading_count,total_matches, andtruncated.
The cited-extraction tools attach a CitationAnchor to every result. Each
anchor includes node_id, page_index, a normalized bbox (coordinates in the
range 0.0 to 1.0), and a confidence score (0.0 to 1.0). Agents that need
provenance should show these fields, not raw text alone.
Error handling, timeouts, and quota
Section titled “Error handling, timeouts, and quota”The server never lets an exception escape to the agent transport. The
call_tool dispatcher catches every error and returns it as JSON TextContent,
so a failed tool call produces a structured payload the agent can read instead of
a dropped connection. The payload shapes are:
| Condition | Returned JSON |
|---|---|
| Unknown tool name | {"error": "Unknown tool: <name>"} |
| Missing input file | {"error": "PDF file not found: <path>"} |
Any NextPDFError subclass | {"error": "<message>", "error_type": "<class>", "status_code": <int?>} |
| Any other exception | {"error": "Unexpected error: <message>"} |
status_code appears only when the underlying error carries one. The SDK
maps HTTP responses to a typed exception hierarchy, all rooted at NextPDFError:
| Exception | HTTP status | error_code | When |
|---|---|---|---|
NextPDFLicenseError | 402 | license/tier-required | The endpoint requires a higher server-side license tier for the operation. |
AstNoStructTreeError | 422 | ast/no-struct-tree | The PDF is untagged and heuristic fallback is not enabled on the server. |
QuotaExceededError | 429 | quota/exceeded | A rate limit or quota was hit. Carries retry_after (seconds) when the server sends a Retry-After header. |
AstBuildTimeoutError | 504 | ast/build-timeout | The AST build exceeded the server’s time budget. Reduce the page range. |
NextPDFAPIError | other 4xx/5xx | server-provided | Any other API-level failure. |
Use this guidance for agent integrations:
- Timeouts. The HTTP client uses a fixed default timeout: 60 seconds total
with a 10-second connect timeout. A slow or large document surfaces as either
an
AstBuildTimeoutError(the server gave up building the AST) or, if the client itself times out, anUnexpected errorpayload from the transport layer. When you seeast/build-timeout, tell the agent to narrow the scope: lowermax_pagesonnextpdf_get_ast, or setpage_index/page_startandpage_endon the extraction tools. - Quota and backoff. On a 429, the tool returns
error_typeofQuotaExceededErrorwithstatus_code429. Theretry_aftervalue lives on the exception object. Because the server serializes onlyerror,error_type, andstatus_code, the agent should treat 429 as a signal to pause and retry later instead of parsing a retry header from the tool output. Enforce quotas at the Connect endpoint, not in the agent. - Untagged PDFs. A 422
ast/no-struct-treemeans the source PDF has no structure tree. Enable heuristic mode on the server for those documents, or route them to a tagging step before extraction.
Security: API-key scoping and least privilege
Section titled “Security: API-key scoping and least privilege”Treat the API key as a secret, with the same care you give a database password.
- Never embed the key in the MCP configuration file. The JSON example above
references
${NEXTPDF_API_KEY}so the value resolves from the host environment or a secret manager at launch time. A configuration file may be committed to source control; a secret must not. - Scope the key to read-only extraction. The MCP server calls only the
AST extraction surface (
extract_cited_text,extract_cited_tables,get_document_ast,search_ast_nodes,get_ast_diff). It does not render, sign, redact, or mutate documents. Issue the agent a key whose server-side scope is limited to those read paths, so a compromised agent cannot reach write or higher-tier operations. - Use a dedicated key per agent. A per-agent key lets you revoke or rotate one integration without affecting others, and it makes endpoint logs attributable to a specific agent.
- Constrain the filesystem. Because every tool reads an absolute path from local disk, the server can read any file the host process can read. Run it as an unprivileged user, restrict its working directory to a documents folder, and never run it as a privileged account.
- Prefer Transport Layer Security (TLS). Point
NEXTPDF_BASE_URLat anhttps://endpoint for any non-local deployment. The SDK sends the key as aBearertoken in theAuthorizationheader, so plaintext transport would expose it on the wire.
See Connect security and operations for the endpoint-side controls that support these client-side practices.
Testing the server locally before wiring an agent
Section titled “Testing the server locally before wiring an agent”Validate the server in isolation before you connect an agent. The fastest check needs no PDF and no network:
python -c "from nextpdf.mcp import _tool_definitions; print(len(_tool_definitions()))"A correct install prints 8. If you see an ImportError mentioning the mcp
extra, the optional dependency is missing. Reinstall with pip install nextpdf[mcp].
Next, exercise the same SDK paths the tools use through the CLI. The CLI talks to your endpoint with the same two environment variables. Set them once:
export NEXTPDF_BASE_URL="https://connect.example.com"export NEXTPDF_API_KEY="$(cat /run/secrets/nextpdf_api_key)"Then confirm version, connectivity, and a real extraction:
nextpdf versionnextpdf info /path/to/sample.pdfnextpdf extract text /path/to/sample.pdf --headings-onlynextpdf version runs without credentials and confirms the package imports.
nextpdf info exercises get_document_ast, the same call behind
nextpdf_get_ast and nextpdf_info. If both succeed, the credentials and
endpoint are correct and the matching MCP tools will work.
To drive the MCP protocol directly, use the upstream MCP Inspector (shipped with
the mcp package). Point it at the same command and environment your agent will
use, then list and invoke tools by hand. Verify that nextpdf_health reports
status: "ok". It returns misconfigured whenever NEXTPDF_BASE_URL or
NEXTPDF_API_KEY is unset, which is the quickest way to catch a missing
environment value before an agent calls a real tool.
Monitoring and debugging tool calls
Section titled “Monitoring and debugging tool calls”The MCP server communicates over standard input/output (stdio), so its standard output carries the protocol stream and must stay clean. The server does not configure its own application logging. Your primary observability channels are the structured tool-error payloads, the CLI, and your endpoint’s own logs.
- Tool-error payloads are the signal. Every failed call returns a JSON
object with
errorand, for SDK errors,error_typeandstatus_code(see Error handling). Have the agent host record these payloads. They identify the failing tool and the precise cause without extra server instrumentation. - Reproduce through the CLI with debug logging. The MCP server itself emits
no logs, but the CLI exercises the same SDK calls and does log. Reproduce a
failing tool through the matching CLI command with
--log-level debug. The CLI logs to stderr with timestamps and records full tracebacks for unexpected errors, which is the most direct way to see what a handler is doing without attaching a debugger. - Health as a probe. Call
nextpdf_healthto confirm the server sees a base URL and an API key. The result reportssdk_version,server_url,api_key_configured(a boolean, never the key itself), andstatus. - Endpoint-side observability. Because each tool maps to one Connect request, correlate tool activity with endpoint access logs by API key and timestamp. Run the endpoint behind the same authentication, quota, and observability controls you use for other service clients.
Troubleshooting common agent-integration issues
Section titled “Troubleshooting common agent-integration issues”| Symptom | Likely cause | Resolution |
|---|---|---|
Server fails to start with an ImportError about the mcp extra | The mcp optional dependency is not installed | Install with pip install nextpdf[mcp]. |
First tool call returns {"error": "NEXTPDF_BASE_URL environment variable is required..."} | The MCP env block did not pass the base URL, or the shell did not expand ${NEXTPDF_BASE_URL} | Set the variable in the agent host environment and confirm the launcher expands it. |
nextpdf_health reports "status": "misconfigured" | One of the two required variables is empty | Supply both NEXTPDF_BASE_URL and NEXTPDF_API_KEY. |
Every path-based tool returns {"error": "PDF file not found: <path>"} | The agent passed a relative or host-side path the server process cannot see | Pass an absolute path readable by the server’s user; confirm with nextpdf info <path>. |
Tool returns error_typeNextPDFLicenseError (status 402) | The operation needs a higher server-side license tier | Use an endpoint and key entitled to the operation. |
Tool returns error_typeAstNoStructTreeError (status 422) | The PDF is untagged and heuristic fallback is off | Enable heuristic mode on the endpoint, or tag the PDF first. |
Tool returns error_typeQuotaExceededError (status 429) | A rate limit or quota was reached | Pause and retry; raise the endpoint quota if the limit is too low. |
Tool returns error_typeAstBuildTimeoutError (status 504), or a transport timeout | The document is too large for the time budget | Narrow scope with max_pages, page_index, or page_start/page_end. |
| The agent registers no NextPDF tools | The agent invoked python -m nextpdf (the CLI) instead of python -m nextpdf.mcp | Use python -m nextpdf.mcp as the command/args. |
For endpoint-level failures and deployment checks, see Connect troubleshooting. For the SDK operations these tools wrap, see the CLI reference and the SDK overview.