Skip to content

NextPDF Connect developer guide

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.

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.

LayerOwned byResponsibilityDo not put here
Client or agentYour integrationChoose which tool to call; relay confirmation challenges to a human.Engine logic or tier detection.
Transportnextpdf/serverFrame requests as JSON-RPC, HTTP, or Protocol Buffers; authenticate; and route to the tool executor.Document semantics.
Tool registrynextpdf/serverDiscover tiers, register tools subject to the security allowlist, look up a tool by name.PDF generation.
Toolnextpdf/serverValidate arguments against the input schema and call the engine.Layout interpretation or multi-step orchestration.
Confirmation gatenextpdf/serverHold an ApprovalRequired operation until a human approves it.Caller authentication.
Enginenextpdf/core (and nextpdf/premium)Generate, inspect, and transform PDF content.Transport or authentication concerns.

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.

  1. Load configuration. The MCP server resolves configuration from environment variables (NEXTPDF_MCP_*), then the YAML file’s nextpdf_mcp section, then built-in defaults, and produces a readonlyMcpConfig. The REST and gRPC servers read HttpConfig from NEXTPDF_* environment variables. See Configuration.
  2. Build the security policy. The enabled_tools allowlist is built before the registry, so it constrains discovery from the first registration.
  3. 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.
  4. Build the shared stores and gate. The in-memory document store is built from the configured TTL and capacity; the ConfirmationGate is assembled with its single-use token store.
  5. 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.

The three transports share the registry, configuration, and gate concepts, but they run as independent processes. Starting one does not start the others.

TransportEntry pointWhen to choose it
MCPbin/nextpdf-mcpA local artificial intelligence (AI) client that launches the server as a trusted subprocess.
RESTbin/nextpdf-serverNetworked HTTP clients; described by an OpenAPI 3.1 document.
gRPCbin/nextpdf-grpcTyped, 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.

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 settingPurpose
.rr.full.yamlCombined REST and gRPC profile under a single supervisor.
NEXTPDF_API_KEYS_FILEPath to a secret-mounted, hot-reloading API key file.
NEXTPDF_REDIS_HOSTEnables Redis-backed rate-limit, idempotency, and document stores for multi-worker pools.
NEXTPDF_WORKER_COUNT / NEXTPDF_GRPC_WORKER_COUNTWorker pool sizing for the HTTP and gRPC pools.
Output base directoryDedicated 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.

Terminal window
export NEXTPDF_API_KEYS_FILE=/run/secrets/api-keys
export NEXTPDF_WORKER_COUNT=8
export NEXTPDF_GRPC_WORKER_COUNT=4
export NEXTPDF_REDIS_HOST=redis
./vendor/bin/rr serve -c .rr.full.yaml

For 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.

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.

  1. Core tier registers unconditionally: the document and diagnostic tools, plus generate_barcode when the core barcode encoder registry is present, plus parse_pdf only when NEXTPDF_MCP_TOOL_PARSE_PDF_ENABLED is true or 1.
  2. Pro and Enterprise providers register when their provider classes resolve, probed with class_exists(). An absent tier is skipped silently.
  3. Bundled AST and mutation providers register under the Pro tier, gated by NEXTPDF_AST_TOOLS_ENABLED and NEXTPDF_MUTATION_TOOLS_ENABLED (both enabled by default).
  4. The security-policy filter intersects every registration with the enabled_tools allowlist. 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.

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.

examples/connect/confirm-and-call.php
<?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.

Extend the server by adding tools and supplying providers, not by editing the registry.

Extension pointUse it forConstraint
A class implementing ToolInterfaceA 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 providerRegistering 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 allowlistLeast-privilege scoping of the exposed catalog.The allowlist subtracts only; it cannot register an absent tool.
risk_level_overridesHardening a deployment by raising a tool’s risk.Upgrade-only; a downgrade of an ApprovalRequired tool fails boot.
Injectable transport and worker seamsTesting the server in isolation.These boundaries exist for tests, not for application wiring.
  1. Choose a profile. Run .rr.yaml, .rr.grpc.yaml, or .rr.full.yaml for the transports you expose.
  2. Mount keys from a secret. Point NEXTPDF_API_KEYS_FILE at a secret file; prefer the hot-reloading file key store so rotation does not require a restart.
  3. Configure shared stores. Set NEXTPDF_REDIS_HOST and confirm ext-redis for any pool larger than one worker; put the SQLite job store on a volume all workers can write.
  4. 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.
  5. Probe health. Use the anonymous /healthz and /readyz endpoints (REST) or the HealthCheck and ReadinessCheck RPCs (gRPC) for orchestrator probes.
  6. Scope the catalog. Restrict enabled_tools to 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.

FailureWhere it surfacesRecommended response
Unknown document_idTool executionReturn a defined error to the caller; instruct it to call create_pdf first.
Stale ETag on a mutationAST mutation toolRe-read the document with get_document_ast and retry with the fresh ETag.
Missing or invalid API key (REST)Authentication middlewareReturn 401 with a WWW-Authenticate: Bearer challenge; do not leak which part was wrong.
Tier not entitled (REST)AuthorizationReturn 403; the key’s tier is below the operation’s tier.
Tier route absent (REST)RouterReturn 404; the package is not installed. This is an expected operation, not a fault.
Bad token (gRPC)gRPC authenticatorFail the call with UNAUTHENTICATED.
Redis unreachableBoot or runtimeDegrade to in-memory stores; alert operators and verify Redis health.
Output path outside the base directoryFile-output toolFail 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.

ConcernDefaultWhen to override
parse_pdfDisabled (opt-in via NEXTPDF_MCP_TOOL_PARSE_PDF_ENABLED).Enable only when an integration needs structural inspection.
enabled_toolsEmpty (all discovered tools allowed).Set an explicit allowlist for least-privilege deployments.
Risk overridesNone.Raise risk for a hardened deployment; never attempt a downgrade.
document_ttl / max_documents1800 seconds / 50 documents.Lower for residency-sensitive or memory-limited deployments.
allow_file_outputEnabled.Set to false for stateless, residency-sensitive deployments.
Worker countFour (HTTP), two (gRPC).Size against observed latency and available cores.
REST listenerPlaintext HTTP behind a TLS terminator.Always terminate TLS upstream; never expose plaintext on an untrusted network.
gRPC on untrusted networksMutual TLS.Required; never run a plaintext gRPC listener on an untrusted network.
  • Registry tests assert that an absent Pro or Enterprise tier is skipped silently and the core catalog still registers.
  • Allowlist tests assert that enabled_tools subtracts and never adds a tool the registry did not discover.
  • Confirmation-gate tests assert that an ApprovalRequired tool 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_overrides entry weakening an ApprovalRequired tool fails boot.
  • Authentication tests cover missing, malformed, disabled, and expired keys on REST (401 with WWW-Authenticate) and gRPC (UNAUTHENTICATED), and tier-entitlement rejection (403).
  • Concurrency tests assert that a stale ETag fails a mutation and that a repeated idempotency_key replays 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.