Zum Inhalt springen
getnextpdf.com

Eine Agenten-Dokumentsitzung über MCP steuern

Dies ist eine vollständige Agentensitzung mit dem Model-Context-Protocol-Server (MCP) von NextPDF Connect, Nachricht für Nachricht: initialize, tools/list, sechs tools/call-Aufrufe, die ein einseitiges Projekt-Briefing erstellen, und der Human-in-the-Loop-Roundtrip (HITL), der den finalen Dateischreibvorgang absichert. Jede der folgenden JSON-RPC-Nachrichten wurde wortgetreu aus einem laufenden bin/nextpdf-mcp-Prozess (ausschließlich Core-Tier-Tools) aufgezeichnet und anschließend in genau zwei Punkten bereinigt: Das einmalig verwendbare Bestätigungs-Token wird als confirm_<single-use-hex> dargestellt, und das temporäre Systemverzeichnis des Rechners ist auf C:\Temp gekürzt. Bezeichner, Schemata, Positionen und Byte-Zahlen sind exakt das, was der Server gesendet hat.

Terminal-Fenster
composer require nextpdf/server

Binden Sie den Stdio-Transport in Ihren MCP-Host ein — für Claude Desktop (Hosts starten den Befehl aus ihrem eigenen Verzeichnis, verwenden Sie daher einen absoluten Pfad; der Stdio-Transport benötigt, anders als der REST-Transport, keinen API-Schlüssel):

{
"mcpServers": {
"nextpdf": {
"command": "php",
"args": ["/absolute/path/to/your/project/vendor/bin/nextpdf-mcp"]
}
}
}

Der Server spricht zeilengetrenntes JSON-RPC 2.0 über stdin/stdout und hält die Protokollausgabe strikt von der Diagnose getrennt: Start- und Audit-Zeilen gehen an stderr, niemals an stdout.

Eine MCP-Dokumentsitzung ist zustandsbehaftet. create_pdf öffnet ein Dokument im speicherinternen Store des Servers und gibt eine document_id zurück; jeder spätere Aufruf adressiert diesen Bezeichner. Inhalts-Tools (set_font, add_text, add_table) werden sofort auf der Risikostufe Vorsicht mit Audit-Protokollierung ausgeführt; preview_layout ist ein sicherer Lesevorgang; und output_pdf mit einem file_path ist „Genehmigung erforderlich“ — es läuft beim ersten Aufruf nicht. Stattdessen gibt der Server eine Challenge mit einem einmalig verwendbaren Token zurück, der Agent gibt die Challenge an den Menschen weiter, und erst ein erneuter Aufruf mit _confirmation_token führt den Schreibvorgang aus. Im Store verbliebene Dokumente laufen nach der konfigurierten Lebensdauer ab (standardmäßig 30 Minuten).

Dieselben Tool-Aufrufe steuern die Tool-Engine über REST und gRPC — die Transporte teilen sich einen Executor — sodass alles hier außer dem Stdio-Framing übertragbar ist. Siehe Eine Rechnung durchgängig über REST rendern für dieselbe Engine auf der HTTP-Oberfläche.

ToolRolle in dieser SitzungRisikostufe
create_pdfDas Dokument öffnen, document_id erhaltenVorsicht
set_fontÜberschrift, dann Fließtextschrift wählenVorsicht
add_textTitelzeile, dann EinleitungsabsatzVorsicht
add_tableChecklisten-Tabelle mit Verantwortlichen/FälligkeitsdatenVorsicht
preview_layoutLayout-Status vor der Ausgabe lesenSicher
output_pdf (file-Modus)Das PDF schreiben — über das GateGenehmigung erforderlich

Das hier aufgezeichnete Deployment registrierte 20 Tools (13 Core, 6 Pro, 1 Enterprise — die Zahlen erscheinen weiter unten in der initialize-Antwort); diese Sitzung verwendet ausschließlich Core-Tools und läuft daher unverändert auf einer reinen Open-Source-Installation. Maßgeblich ist die tools/list-Antwort Ihres eigenen Servers, und die Risikoleiter ist in der Referenz zu den HITL-Risikostufen definiert.

Der Client eröffnet die Sitzung und gibt seine Protokollversion an:

{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {},
"clientInfo": {
"name": "planning-agent",
"version": "1.0.0"
}
}
}

Der Server bestätigt die Protokollversion und deklariert seine Capabilities, einschließlich der Tool-Zahlen je Tier und der Aktivierung des HITL-Gatings:

{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-06-18",
"capabilities": {
"tools": {
"listChanged": false
},
"nextpdf": {
"tiers": {
"core": 13,
"pro": 6,
"enterprise": 1
},
"tool_count": 20,
"risk_model_version": 1,
"hitl_enabled": true
}
},
"serverInfo": {
"name": "NextPDF Connect",
"version": "1.0.0"
}
}
}

Der Client quittiert mit einer Notification (Notifications tragen keine id und erhalten keine Antwort):

{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/list"
}

Die vollständige Antwort listet alle 20 registrierten Tools mit ihren kompletten Eingabeschemata auf. Sie ist hier auf die zwei Tools gekürzt dargestellt, die diese Sitzung eröffnen und beenden — die ausgelassenen 18 Einträge haben dieselbe Struktur:

{
"jsonrpc": "2.0",
"id": 2,
"result": {
"tools": [
{
"name": "create_pdf",
"description": "Create a new PDF document and return a document_id for subsequent operations",
"inputSchema": {
"type": "object",
"properties": {
"page_size": {
"type": "string",
"description": "Page size name (e.g. \"A4\", \"Letter\", \"Legal\", \"A3\")",
"default": "A4"
},
"orientation": {
"type": "string",
"enum": [
"portrait",
"landscape"
],
"description": "Page orientation",
"default": "portrait"
},
"title": {
"type": "string",
"description": "Document title metadata"
},
"author": {
"type": "string",
"description": "Document author metadata"
}
},
"required": []
},
"annotations": {
"destructiveHint": false,
"idempotentHint": false
}
},
{
"name": "output_pdf",
"description": "Finalize the PDF and output to file or return as base64",
"inputSchema": {
"type": "object",
"properties": {
"document_id": {
"type": "string",
"description": "The document_id returned by create_pdf"
},
"file_path": {
"type": "string",
"description": "Absolute file path to save the PDF. If omitted, returns base64-encoded PDF data."
},
"destroy": {
"type": "boolean",
"description": "Whether to remove the document from the store after output",
"default": true
}
},
"required": [
"document_id"
]
},
"annotations": {
"destructiveHint": false,
"openWorldHint": true
}
}
]
}
}

Beachten Sie das Schema von output_pdf: file_path ist optional, und die Annotationen tragen openWorldHint: true — das Tool kann auf die Welt außerhalb der Sitzung einwirken, und genau deshalb läuft der file-Modus über das Gate.

{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "create_pdf",
"arguments": {
"page_size": "A4",
"orientation": "portrait",
"title": "Project kickoff brief",
"author": "Planning agent"
}
}
}
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [
{
"type": "text",
"text": "{\"document_id\":\"doc_3b9f435efa0f32d1da7a131d\",\"page_count\":1,\"page_size\":\"A4\",\"orientation\":\"portrait\"}"
}
],
"structuredContent": {
"document_id": "doc_3b9f435efa0f32d1da7a131d",
"page_count": 1,
"page_size": "A4",
"orientation": "portrait"
}
}
}

Jedes Tool-Ergebnis kommt zweifach in einer Nachricht an: als menschenlesbarer content-Textblock und als maschinenlesbarer structuredContent. Lesen Sie structuredContent.document_id aus und führen Sie sie durch jeden folgenden Aufruf.

Setzen Sie eine fette 16-Punkt-Schrift und platzieren Sie dann den Titel:

{
"jsonrpc": "2.0",
"id": 4,
"method": "tools/call",
"params": {
"name": "set_font",
"arguments": {
"document_id": "doc_3b9f435efa0f32d1da7a131d",
"family": "helvetica",
"style": "B",
"size": 16
}
}
}
{
"jsonrpc": "2.0",
"id": 4,
"result": {
"content": [
{
"type": "text",
"text": "Font set to helvetica B 16pt on document doc_3b9f435efa0f32d1da7a131d."
}
]
}
}
{
"jsonrpc": "2.0",
"id": 5,
"method": "tools/call",
"params": {
"name": "add_text",
"arguments": {
"document_id": "doc_3b9f435efa0f32d1da7a131d",
"text": "Project kickoff brief"
}
}
}
{
"jsonrpc": "2.0",
"id": 5,
"result": {
"content": [
{
"type": "text",
"text": "{\"document_id\":\"doc_3b9f435efa0f32d1da7a131d\",\"position\":{\"x\":10,\"y\":16,\"page\":0}}"
}
],
"structuredContent": {
"document_id": "doc_3b9f435efa0f32d1da7a131d",
"position": {
"x": 10,
"y": 16,
"page": 0
}
}
}
}

Zurück zu einer regulären 11-Punkt-Schrift für den Einleitungstext; width: 0 wählt ein Multi-Cell-Layout über die volle Breite:

{
"jsonrpc": "2.0",
"id": 6,
"method": "tools/call",
"params": {
"name": "set_font",
"arguments": {
"document_id": "doc_3b9f435efa0f32d1da7a131d",
"family": "helvetica",
"style": "",
"size": 11
}
}
}
{
"jsonrpc": "2.0",
"id": 6,
"result": {
"content": [
{
"type": "text",
"text": "Font set to helvetica 11pt on document doc_3b9f435efa0f32d1da7a131d."
}
]
}
}
{
"jsonrpc": "2.0",
"id": 7,
"method": "tools/call",
"params": {
"name": "add_text",
"arguments": {
"document_id": "doc_3b9f435efa0f32d1da7a131d",
"text": "Prepared by the planning agent for the 14 July kickoff. Scope, owners, and the first-week checklist are tabled below.",
"width": 0,
"line_height": 6
}
}
}
{
"jsonrpc": "2.0",
"id": 7,
"result": {
"content": [
{
"type": "text",
"text": "{\"document_id\":\"doc_3b9f435efa0f32d1da7a131d\",\"position\":{\"x\":10,\"y\":29.75,\"page\":0}}"
}
],
"structuredContent": {
"document_id": "doc_3b9f435efa0f32d1da7a131d",
"position": {
"x": 10,
"y": 29.75,
"page": 0
}
}
}
}
{
"jsonrpc": "2.0",
"id": 8,
"method": "tools/call",
"params": {
"name": "add_table",
"arguments": {
"document_id": "doc_3b9f435efa0f32d1da7a131d",
"html": "<table><tr><th>Work item</th><th>Owner</th><th>Due</th></tr><tr><td>Repository bootstrap</td><td>Devon</td><td>2026-07-15</td></tr><tr><td>CI pipeline</td><td>Ana</td><td>2026-07-17</td></tr><tr><td>Staging deploy</td><td>Priya</td><td>2026-07-21</td></tr></table>"
}
}
}
{
"jsonrpc": "2.0",
"id": 8,
"result": {
"content": [
{
"type": "text",
"text": "{\"document_id\":\"doc_3b9f435efa0f32d1da7a131d\",\"position\":{\"x\":10,\"y\":84.75,\"page\":0}}"
}
],
"structuredContent": {
"document_id": "doc_3b9f435efa0f32d1da7a131d",
"position": {
"x": 10,
"y": 84.75,
"page": 0
}
}
}
}

Jeder Inhaltsaufruf gibt die aktualisierte Cursor-position zurück, sodass der Agent stets weiß, wo das nächste Element landet.

preview_layout ist ein nur lesender Aufruf der Risikostufe Sicher — ein wohlerzogener Agent prüft, was er erstellt hat, bevor er einen Menschen um die Freigabe eines Schreibvorgangs bittet:

{
"jsonrpc": "2.0",
"id": 9,
"method": "tools/call",
"params": {
"name": "preview_layout",
"arguments": {
"document_id": "doc_3b9f435efa0f32d1da7a131d"
}
}
}
{
"jsonrpc": "2.0",
"id": 9,
"result": {
"content": [
{
"type": "text",
"text": "{\"document_id\":\"doc_3b9f435efa0f32d1da7a131d\",\"total_pages\":1,\"current_page\":0,\"page_dimensions\":{\"width\":595.276,\"height\":841.89},\"margins\":{\"top\":10,\"right\":10,\"bottom\":10,\"left\":10},\"cursor_position\":{\"x\":10,\"y\":84.75}}"
}
],
"structuredContent": {
"document_id": "doc_3b9f435efa0f32d1da7a131d",
"total_pages": 1,
"current_page": 0,
"page_dimensions": {
"width": 595.276,
"height": 841.89
},
"margins": {
"top": 10,
"right": 10,
"bottom": 10,
"left": 10
},
"cursor_position": {
"x": 10,
"y": 84.75
}
}
}
}

8. Den Dateischreibvorgang anfordern — das Gate antwortet zuerst

Abschnitt betitelt „8. Den Dateischreibvorgang anfordern — das Gate antwortet zuerst“

Der Agent bittet output_pdf, das fertige Briefing auf die Festplatte zu schreiben, und hält das Dokument am Leben (destroy: false), falls der Mensch ablehnt und auf die base64-Ausgabe zurückgegriffen werden muss:

{
"jsonrpc": "2.0",
"id": 10,
"method": "tools/call",
"params": {
"name": "output_pdf",
"arguments": {
"document_id": "doc_3b9f435efa0f32d1da7a131d",
"file_path": "C:\\Temp\\nextpdf-mcp\\kickoff-brief.pdf",
"destroy": false
}
}
}

Die Datei wird nicht geschrieben. Da der file-Modus „Genehmigung erforderlich“ ist, antwortet der Server stattdessen mit einer Bestätigungs-Challenge:

{
"jsonrpc": "2.0",
"id": 10,
"result": {
"content": [
{
"type": "text",
"text": "⚠️ CONFIRMATION REQUIRED\n\nOperation: output_pdf\nDescription: Finalize the PDF and output to file or return as base64\n\nTo proceed, call output_pdf again with parameter _confirmation_token: \"confirm_<single-use-hex>\"\nExpires in 300 seconds."
}
],
"isError": false
}
}

9. Der Mensch genehmigt — erneuter Aufruf mit dem Token

Abschnitt betitelt „9. Der Mensch genehmigt — erneuter Aufruf mit dem Token“

Der Agent gibt den Challenge-Text an den Menschen weiter. Nach der Freigabe ruft er output_pdf erneut mit denselben Argumenten plus _confirmation_token auf:

{
"jsonrpc": "2.0",
"id": 11,
"method": "tools/call",
"params": {
"name": "output_pdf",
"arguments": {
"document_id": "doc_3b9f435efa0f32d1da7a131d",
"file_path": "C:\\Temp\\nextpdf-mcp\\kickoff-brief.pdf",
"destroy": false,
"_confirmation_token": "confirm_<single-use-hex>"
}
}
}

Das Token wird verbraucht, der Schreibvorgang wird ausgeführt, und das Ergebnis meldet die geschriebene Datei:

{
"jsonrpc": "2.0",
"id": 11,
"result": {
"content": [
{
"type": "text",
"text": "{\"document_id\":\"doc_3b9f435efa0f32d1da7a131d\",\"file_path\":\"C:\\\\Temp\\\\nextpdf-mcp\\\\kickoff-brief.pdf\",\"file_size\":3612,\"page_count\":1,\"destroyed\":false}"
}
],
"structuredContent": {
"document_id": "doc_3b9f435efa0f32d1da7a131d",
"file_path": "C:\\Temp\\nextpdf-mcp\\kickoff-brief.pdf",
"file_size": 3612,
"page_count": 1,
"destroyed": false
}
}
}

Die Sitzung schrieb kickoff-brief.pdf (3.612 Bytes, eine Seite, passend zu structuredContent.file_size und page_count). Aufgezeichnete qpdf --check- Ausgabe für genau diese Datei:

checking kickoff-brief.pdf
PDF Version: 2.0
File is not encrypted
File is not linearized
No syntax or stream encoding errors found; the file may still contain
errors that qpdf cannot detect

Das ist eine Strukturprüfung, in qpdfs eigenen Worten — keine Konformitätsfeststellung.

  • Der erneute Aufruf muss dieselben Argumente wiederholen. Das Bestätigungs-Token ist an den Toolnamen sowie an einen kanonischen Digest der Argumente gebunden, für die es ausgestellt wurde. Ein erneuter Aufruf mit irgendeiner Änderung — selbst das Umschalten von destroy — verbraucht das Token nicht; der Server antwortet stattdessen mit einer neuen Challenge. Wiederholen Sie die Argumente exakt und fügen Sie ausschließlich _confirmation_token hinzu.
  • Das Token ist einmalig verwendbar und läuft ab. Die Challenge nennt die Ablaufzeit (300 Sekunden). Nach Ablauf oder Verbrauch erhält der nächste über das Gate laufende Aufruf eine neue Challenge; geben Sie die neue weiter.
  • Die Dateiausgabe landet in einem auf der Allow-Liste stehenden Verzeichnis. Der Server weist einen file_path außerhalb seines konfigurierten temporären Verzeichnisses mit Output path rejected by security policy zurück. Der Standard-Wurzelpfad der Allow-Liste ist nextpdf-mcp unter dem temporären Systemverzeichnis; Betreiber ändern ihn über die Einstellung temp_dir in nextpdf-mcp.yaml.
  • Der base64-Modus läuft nicht über das Gate. output_pdf ohne file_path gibt das PDF als base64 auf der Review-Stufe zurück, ohne Seiteneffekt im Dateisystem — siehe Menschliche Freigabe für die Dateiausgabe verlangen für diese Grenze im Detail.
  • Eine Challenge ist ein Ergebnis, kein Fehler. Die Challenge-Nachricht kommt mit isError: false an; eine ausstehende Freigabe ist eine Workflow-Pause. Wiederholen Sie den Aufruf nicht in einer Schleife und erfinden Sie niemals ein Token.
  • Notifications erhalten keine Antwort. Blockieren Sie nach notifications/initialized nicht in der Erwartung einer Antwortzeile.

Die Sitzung läuft durchgängig im Arbeitsspeicher: Inhaltsaufrufe kehrten im aufgezeichneten Lauf in Millisekunden zurück, und die Gesamtlaufzeit wird vom menschlichen Freigabe-Roundtrip dominiert — was gerade der Sinn des Gates ist. Der Dokumentspeicher hält eine Sitzung standardmäßig 30 Minuten im Leerlauf (maximal 50 Dokumente), sodass eine langsame Freigabe das erstellte Dokument nicht verliert — ein aufgegebenes wird jedoch zurückgewonnen.

  • Behandeln Sie das Bestätigungs-Token als einmaliges Geheimnis. Geben Sie den Challenge-Text an den Menschen weiter; protokollieren oder speichern Sie das Token nicht. Diese Seite schwärzt das aufgezeichnete Token aus genau diesem Grund.
  • Der Audit-Trail liegt auf stderr. Jede Ausführung ab der Stufe Vorsicht wird per PSR-3 protokolliert (Tool, Risiko, Argumente, Ergebnis), wobei sensible Parameter geschwärzt werden. Diagnose vermischt sich niemals mit dem Protokollstrom.
  • Die Pfad-Allow-Liste ist die Dateisystemgrenze. Richten Sie temp_dir auf ein Verzeichnis, das ausschließlich der Connect-Ausgabe dient; weiten Sie es nicht auf einen Allzweck-Speicherort aus.
  • Risikostufen können nur nach oben verschoben werden. Ein Betreiber-Override in nextpdf-mcp.yaml kann die Risikostufe eines Tools anheben, output_pdf jedoch niemals unter „Genehmigung erforderlich“ senken.

Dieses Rezept erhebt keinen normativen Standardanspruch. Es dokumentiert den MCP-Stdio-Transport (JSON-RPC 2.0, Protokollversion 2025-06-18, wie im aufgezeichneten initialize-Austausch ausgehandelt) sowie den Risiko- und Bestätigungsvertrag des Servers. Der obige qpdf --check-Schritt bestätigt ausschließlich die strukturelle Integrität der geschriebenen Datei; die Konformität mit einem Standard wird von einem unabhängigen Validator festgestellt, nicht von der erzeugenden Software behauptet.