NextPDF Connect developer guide
At a glance
Section titled “At a glance”NextPDF Connect (nextpdf/server) wraps the framework-agnostic NextPDF PDF 2.0 engine as a service. It does not reimplement PDF generation. It exposes each engine capability as a named tool with a schema, then serves that catalog over three transports: Model Context Protocol (MCP) over standard input and output, Representational State Transfer (REST) Application Programming Interface (API), and gRPC. Use this guide when you build against the server, extend its tool set, or run it in production.
Three concepts define the design: the tool registry, the three independent transports, and the human-in-the-loop (HITL) confirmation gate. This page explains how they fit together and how to work with them without weakening the safety model. For exact tool, remote procedure call (RPC), and message symbols, see the API reference.
Prerequisites: PHP 8.4, Composer 2, and — for the networked transports — the RoadRunner binary and at least one API key. Install with composer require nextpdf/server.
Architecture boundary
Section titled “Architecture boundary”Keep each responsibility on the correct side of its boundary. A tool is a thin wrapper around an engine call; it must not contain layout interpretation, document semantics, or transformation logic.
| Layer | Owned by | Responsibility | Do not put here |
|---|---|---|---|
| Client or agent | Your integration | Choose which tool to call; relay confirmation challenges to a human. | Engine logic or tier detection. |
| Transport | nextpdf/server | Frame requests as JSON-RPC, HTTP, or Protocol Buffers; authenticate; and route to the tool executor. | Document semantics. |
| Tool registry | nextpdf/server | Discover tiers, register tools subject to the security allowlist, look up a tool by name. | PDF generation. |
| Tool | nextpdf/server | Validate arguments against the input schema and call the engine. | Layout interpretation or multi-step orchestration. |
| Confirmation gate | nextpdf/server | Hold an ApprovalRequired operation until a human approves it. | Caller authentication. |
| Engine | nextpdf/core (and nextpdf/premium) | Generate, inspect, and transform PDF content. | Transport or authentication concerns. |
Runtime lifecycle
Section titled “Runtime lifecycle”Each transport has its own entry point and boot factory. Each one constructs its object graph explicitly. There is no dependency-injection container to register.
- Load configuration. The MCP server resolves configuration from environment variables (
NEXTPDF_MCP_*), then the YAML file’snextpdf_mcpsection, then built-in defaults, and produces areadonlyMcpConfig. The REST and gRPC servers readHttpConfigfromNEXTPDF_*environment variables. See Configuration. - Build the security policy. The
enabled_toolsallowlist is built before the registry, so it constrains discovery from the first registration. - Construct the registry and discover tools.
ToolRegistry::registerDefaults()registers the core tier, then the Pro and Enterprise providers when their classes resolve, then the bundled AST and mutation providers subject to their environment gates. - Build the shared stores and gate. The in-memory document store is built from the configured TTL and capacity; the
ConfirmationGateis assembled with its single-use token store. - Bind the transport. MCP enters a read-handle-write loop over stdio until end of file. REST and gRPC build their route or service table from the detected tiers, then hand the request loop to RoadRunner.
A request then follows this path: authenticate (REST and gRPC), resolve the tool or operation, run the confirmation gate for ApprovalRequired work, execute against the engine, and return the result. See Boot and discovery.
Transport model
Section titled “Transport model”The three transports share the registry, configuration, and gate concepts, but they run as independent processes. Starting one does not start the others.
| Transport | Entry point | When to choose it |
|---|---|---|
| MCP | bin/nextpdf-mcp | A local artificial intelligence (AI) client that launches the server as a trusted subprocess. |
| REST | bin/nextpdf-server | Networked HTTP clients; described by an OpenAPI 3.1 document. |
| gRPC | bin/nextpdf-grpc | Typed, streaming clients; the nextpdf.connect.v1.NextPDFConnect service. |
Choose transports by the RoadRunner profile you run: .rr.yaml (REST only), .rr.grpc.yaml (gRPC only), or .rr.full.yaml (both). The MCP transport is a plain subprocess and needs no supervisor. For wire details, see MCP transport, REST transport, and gRPC transport.
Recommended deployment structure
Section titled “Recommended deployment structure”Run the networked transports under RoadRunner with shared stores and secret-mounted keys. The combined profile lets REST and gRPC share a supervisor.
| Path or setting | Purpose |
|---|---|
.rr.full.yaml | Combined REST and gRPC profile under a single supervisor. |
NEXTPDF_API_KEYS_FILE | Path to a secret-mounted, hot-reloading API key file. |
NEXTPDF_REDIS_HOST | Enables Redis-backed rate-limit, idempotency, and document stores for multi-worker pools. |
NEXTPDF_WORKER_COUNT / NEXTPDF_GRPC_WORKER_COUNT | Worker pool sizing for the HTTP and gRPC pools. |
| Output base directory | Dedicated volume with least-privilege file system permissions for file-output tools. |
The following shell sample boots the combined profile with secret-mounted keys and a shared Redis store. It contains no secrets; keys are mounted at /run/secrets/api-keys.
export NEXTPDF_API_KEYS_FILE=/run/secrets/api-keysexport NEXTPDF_WORKER_COUNT=8export NEXTPDF_GRPC_WORKER_COUNT=4export NEXTPDF_REDIS_HOST=redis./vendor/bin/rr serve -c .rr.full.yamlFor a multi-worker pool, configure Redis and confirm ext-redis is present in the running image. Without it, the rate-limit, idempotency, and document stores are per-worker. See Deployment.
Tool registry and tier resolution
Section titled “Tool registry and tier resolution”NextPDF\Server\ToolRegistry (src/ToolRegistry.php) builds the catalog at boot. Tier is a declared invariant: each tool returns its own tier() and riskLevel(). The registry never infers tier from namespace or packaging.
- Core tier registers unconditionally: the document and diagnostic tools, plus
generate_barcodewhen the core barcode encoder registry is present, plusparse_pdfonly whenNEXTPDF_MCP_TOOL_PARSE_PDF_ENABLEDistrueor1. - Pro and Enterprise providers register when their provider classes resolve, probed with
class_exists(). An absent tier is skipped silently. - Bundled AST and mutation providers register under the Pro tier, gated by
NEXTPDF_AST_TOOLS_ENABLEDandNEXTPDF_MUTATION_TOOLS_ENABLED(both enabled by default). - The security-policy filter intersects every registration with the
enabled_toolsallowlist. The allowlist subtracts; it never adds. The per-tier counter counts only tools the policy admits.
The MCP initialize response and the REST GET /api/v1/capabilities endpoint report the resulting per-tier counts and total. Treat any fixed total in prose as stale; query the running server. See Tool catalog.
Risk tiers and the confirmation gate
Section titled “Risk tiers and the confirmation gate”Every tool declares one of four risk levels from the RiskLevel enum (src/Config/RiskLevel.php): Safe (0), Caution (1), Review (2), and ApprovalRequired (3). Audit logging applies at Caution and above. A configuration override may raise a tool’s risk; it may never lower a tool that is ApprovalRequired by design. The configuration loader throws at load time, and the server refuses to boot rather than run with a weakened gate.
When an ApprovalRequired tool is invoked without a valid token, the ConfirmationGate (src/Mcp/ConfirmationGate.php) returns a single-use challenge token. The token binds the tool name, a random nonce, and a 300-second time-to-live (TTL), but not the arguments, because clients may re-serialize arguments with different key ordering on retry. The agent relays the challenge to a human and re-invokes the same tool with the token in the _confirmation_token argument. The token is consumed on use, releasing exactly one gated call.
The following PHP sample is a transport-agnostic helper. It drives an MCP tool call and, on a confirmation challenge, shows the challenge to a human approver before retrying with the issued token. It declares strict types, is fully type-hinted, and catches the most specific exception rather than swallowing every error.
<?php
declare(strict_types=1);
namespace App\Connect;
use JsonException;
/** * Drives one tool call and resolves an ApprovalRequired confirmation * challenge through a human approver before retrying. */final readonly class ConfirmingToolCaller{ public function __construct( private McpClientInterface $client, private HumanApproverInterface $approver, ) {}
/** * @param non-empty-string $toolName * @param array<string, mixed> $arguments * * @return array<string, mixed> The tool result content * * @throws JsonException When a response cannot be decoded * @throws ApprovalDeniedException When the human declines the challenge */ public function call(string $toolName, array $arguments): array { $response = $this->client->callTool($toolName, $arguments);
if (!isset($response['challenge'], $response['token'])) { return $response; }
$challenge = (string) $response['challenge']; $token = (string) $response['token'];
if (!$this->approver->approve($toolName, $challenge)) { throw new ApprovalDeniedException($toolName); }
$arguments['_confirmation_token'] = $token;
return $this->client->callTool($toolName, $arguments); }}Wire McpClientInterface, HumanApproverInterface, and ApprovalDeniedException to your own transport and approval channel. The retry reuses the original arguments plus the issued token; never auto-approve a challenge without a human decision. See HITL risk tiers.
Extension points
Section titled “Extension points”Extend the server by adding tools and supplying providers, not by editing the registry.
| Extension point | Use it for | Constraint |
|---|---|---|
A class implementing ToolInterface | A new engine capability exposed as a tool. | Declare tier(), riskLevel(), category(), and a JSON Schema inputSchema(); keep the class a thin engine wrapper. |
A ToolProviderInterface provider | Registering a set of tools for a tier. | Pro and Enterprise providers are discovered by class_exists(); do not require the proprietary package from the server. |
enabled_tools allowlist | Least-privilege scoping of the exposed catalog. | The allowlist subtracts only; it cannot register an absent tool. |
risk_level_overrides | Hardening a deployment by raising a tool’s risk. | Upgrade-only; a downgrade of an ApprovalRequired tool fails boot. |
| Injectable transport and worker seams | Testing the server in isolation. | These boundaries exist for tests, not for application wiring. |
Operations workflow
Section titled “Operations workflow”- Choose a profile. Run
.rr.yaml,.rr.grpc.yaml, or.rr.full.yamlfor the transports you expose. - Mount keys from a secret. Point
NEXTPDF_API_KEYS_FILEat a secret file; prefer the hot-reloading file key store so rotation does not require a restart. - Configure shared stores. Set
NEXTPDF_REDIS_HOSTand confirmext-redisfor any pool larger than one worker; put the SQLite job store on a volume all workers can write. - Terminate TLS. Run REST behind a Transport Layer Security (TLS) terminator; run gRPC with mutual TLS on any untrusted network, with the server key, server certificate, and client certificate authority supplied as deployment secrets.
- Probe health. Use the anonymous
/healthzand/readyzendpoints (REST) or theHealthCheckandReadinessCheckRPCs (gRPC) for orchestrator probes. - Scope the catalog. Restrict
enabled_toolsto the minimum set an integration needs.
Verify Redis health rather than assuming it. The REST server falls back to in-memory stores when a configured Redis connection fails. See Deployment and Security and operations.
Failure handling
Section titled “Failure handling”| Failure | Where it surfaces | Recommended response |
|---|---|---|
Unknown document_id | Tool execution | Return a defined error to the caller; instruct it to call create_pdf first. |
| Stale ETag on a mutation | AST mutation tool | Re-read the document with get_document_ast and retry with the fresh ETag. |
| Missing or invalid API key (REST) | Authentication middleware | Return 401 with a WWW-Authenticate: Bearer challenge; do not leak which part was wrong. |
| Tier not entitled (REST) | Authorization | Return 403; the key’s tier is below the operation’s tier. |
| Tier route absent (REST) | Router | Return 404; the package is not installed. This is an expected operation, not a fault. |
| Bad token (gRPC) | gRPC authenticator | Fail the call with UNAUTHENTICATED. |
| Redis unreachable | Boot or runtime | Degrade to in-memory stores; alert operators and verify Redis health. |
| Output path outside the base directory | File-output tool | Fail closed; the path is canonicalized and traversal is rejected. |
Surface engine failures as defined error objects, never as silent successes. The API reference details the error model for each transport.
Safe defaults
Section titled “Safe defaults”| Concern | Default | When to override |
|---|---|---|
parse_pdf | Disabled (opt-in via NEXTPDF_MCP_TOOL_PARSE_PDF_ENABLED). | Enable only when an integration needs structural inspection. |
enabled_tools | Empty (all discovered tools allowed). | Set an explicit allowlist for least-privilege deployments. |
| Risk overrides | None. | Raise risk for a hardened deployment; never attempt a downgrade. |
document_ttl / max_documents | 1800 seconds / 50 documents. | Lower for residency-sensitive or memory-limited deployments. |
allow_file_output | Enabled. | Set to false for stateless, residency-sensitive deployments. |
| Worker count | Four (HTTP), two (gRPC). | Size against observed latency and available cores. |
| REST listener | Plaintext HTTP behind a TLS terminator. | Always terminate TLS upstream; never expose plaintext on an untrusted network. |
| gRPC on untrusted networks | Mutual TLS. | Required; never run a plaintext gRPC listener on an untrusted network. |
Testing checklist
Section titled “Testing checklist”- Registry tests assert that an absent Pro or Enterprise tier is skipped silently and the core catalog still registers.
- Allowlist tests assert that
enabled_toolssubtracts and never adds a tool the registry did not discover. - Confirmation-gate tests assert that an
ApprovalRequiredtool returns a challenge on first call, runs once on a valid single-use token, and expires the token after its TTL. - Downgrade tests assert that a
risk_level_overridesentry weakening anApprovalRequiredtool fails boot. - Authentication tests cover missing, malformed, disabled, and expired keys on REST (
401withWWW-Authenticate) and gRPC (UNAUTHENTICATED), and tier-entitlement rejection (403). - Concurrency tests assert that a stale ETag fails a mutation and that a repeated
idempotency_keyreplays the cached result. - Path-containment tests assert that a file-output path resolving outside the base directory is rejected.
- Keep fixtures small and non-sensitive; never commit a real API key or document content.
See also
Section titled “See also”- API reference — exact tool, RPC, and message symbols
- Tool catalog — the verified core set and the runtime count
- HITL risk tiers — the risk model and the confirmation envelope
- Configuration — the resolution order and the upgrade-only override
- Deployment — RoadRunner profiles, Redis, and mutual TLS
- Security and operations — authentication, transport security, and the threat model