Een agentdocumentsessie aansturen via MCP
In één oogopslag
Sectie met titel “In één oogopslag”Dit is één complete agentsessie tegen de NextPDF Connect Model
Context Protocol (MCP)-server, bericht voor bericht: initialize,
tools/list, zes tools/call-aanroepen die een projectbriefing van
één pagina opbouwen, en de human-in-the-loop (HITL)-uitwisseling die de
uiteindelijke schrijfactie naar het bestand bewaakt. Elk JSON-RPC-bericht
hieronder is letterlijk vastgelegd uit een live bin/nextpdf-mcp-proces
(alleen core-tier-tools) en vervolgens op precies twee manieren
geschoond: het eenmalige bevestigingstoken wordt getoond als
confirm_<single-use-hex>, en de tijdelijke systeemmap van de machine is
afgekort tot C:\Temp. Identifiers, schema’s, posities en byte-aantallen
zijn exact wat de server heeft verzonden.
Installeren
Sectie met titel “Installeren”composer require nextpdf/serverBind het stdio-transport in je MCP-host — voor Claude Desktop (hosts starten het commando vanuit hun eigen map, gebruik dus een absoluut pad; het stdio-transport heeft geen API-key nodig, in tegenstelling tot het REST-transport):
{ "mcpServers": { "nextpdf": { "command": "php", "args": ["/absolute/path/to/your/project/vendor/bin/nextpdf-mcp"] } }}De server spreekt newline-gescheiden JSON-RPC 2.0 op stdin/stdout en houdt protocoluitvoer strikt gescheiden van diagnostiek: opstart- en auditregels gaan naar stderr, nooit naar stdout.
Conceptueel overzicht
Sectie met titel “Conceptueel overzicht”Een MCP-documentsessie is stateful. create_pdf opent een document in de
in-memory store van de server en geeft een document_id terug; elke
latere aanroep richt zich op die identifier. Content-tools (set_font,
add_text, add_table) worden onmiddellijk uitgevoerd op het
risiconiveau Caution, met auditlogging; preview_layout is een Safe
leesactie; en output_pdf met een file_path is Approval Required —
deze draait niet bij de eerste aanroep. In plaats daarvan geeft de server
een challenge met een eenmalig token terug, geeft de agent de challenge
door aan de mens, en pas een nieuwe aanroep met _confirmation_token
voert de schrijfactie uit. Documenten die in de store achterblijven,
verlopen na de geconfigureerde time to live (standaard 30 minuten).
Dezelfde toolaanroepen sturen de tool-engine aan over REST en gRPC — de transports delen één executor — dus alles hier, behalve de stdio-framing, is overdraagbaar. Zie Een factuur end-to-end renderen via REST voor dezelfde engine op het HTTP-oppervlak.
API-oppervlak
Sectie met titel “API-oppervlak”| Tool | Rol in deze sessie | Risiconiveau |
|---|---|---|
create_pdf | Het document openen, document_id ophalen | Caution |
set_font | Kop-, daarna body-lettertype selecteren | Caution |
add_text | Titelregel, daarna introparagraaf | Caution |
add_table | Checklisttabel met eigenaar/deadline | Caution |
preview_layout | Lay-outstatus lezen vóór uitvoer | Safe |
output_pdf (bestandsmodus) | De PDF schrijven — gated | Approval Required |
De hier vastgelegde deployment registreerde 20 tools (13 core, 6 Pro, 1
Enterprise — de aantallen verschijnen in de initialize-respons
hieronder); deze sessie gebruikt alleen core-tools en draait dus
ongewijzigd op een installatie met uitsluitend open source. De maatgevende
catalogus is de tools/list-respons van je eigen server, en de
risicoladder is gedefinieerd in de
HITL risk tiers reference.
De sessie, bericht voor bericht
Sectie met titel “De sessie, bericht voor bericht”1. De verbinding initialiseren
Sectie met titel “1. De verbinding initialiseren”De client opent de sessie en geeft zijn protocolversie op:
{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2025-06-18", "capabilities": {}, "clientInfo": { "name": "planning-agent", "version": "1.0.0" } }}De server bevestigt de protocolversie en declareert zijn capabilities, waaronder de tool-aantallen per tier en dat HITL-gating is ingeschakeld:
{ "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" } }}De client bevestigt met een notification (notifications dragen geen id
en krijgen geen respons):
{ "jsonrpc": "2.0", "method": "notifications/initialized"}2. De tools ontdekken
Sectie met titel “2. De tools ontdekken”{ "jsonrpc": "2.0", "id": 2, "method": "tools/list"}De volledige respons somt alle 20 geregistreerde tools op met hun complete input-schema’s. Hier wordt deze ingekort tot de twee tools die deze sessie openen en afsluiten — de weggelaten 18 vermeldingen hebben dezelfde vorm:
{ "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 } } ] }}Let op het schema van output_pdf: file_path is optioneel, en de
annotations bevatten openWorldHint: true — de tool kan de wereld buiten
de sessie raken, precies daarom is de bestandsmodus gated.
3. Het document openen
Sectie met titel “3. Het document openen”{ "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" } }}Elk toolresultaat komt tweemaal binnen in één bericht: een voor mensen
leesbaar content-tekstblok, en machineleesbare structuredContent. Lees
structuredContent.document_id en geef het door aan elke volgende
aanroep.
4. De kop toevoegen
Sectie met titel “4. De kop toevoegen”Stel een vet lettertype van 16 punt in en plaats vervolgens de 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 } } }}5. De body-paragraaf toevoegen
Sectie met titel “5. De body-paragraaf toevoegen”Terug naar een normaal lettertype van 11 punt voor de introtekst;
width: 0 selecteert een multi-cell-lay-out over de volle breedte:
{ "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 } } }}6. De checklisttabel toevoegen
Sectie met titel “6. De checklisttabel toevoegen”{ "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 } } }}Elke content-aanroep geeft de bijgewerkte cursor-position terug, zodat
de agent altijd weet waar het volgende element belandt.
7. Bekijk een preview voordat je om goedkeuring vraagt
Sectie met titel “7. Bekijk een preview voordat je om goedkeuring vraagt”preview_layout is een Safe, alleen-lezen aanroep — een net
functionerende agent controleert wat hij heeft gebouwd voordat hij een
mens om goedkeuring voor een schrijfactie vraagt:
{ "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. Vraag de schrijfactie aan — de gate antwoordt eerst
Sectie met titel “8. Vraag de schrijfactie aan — de gate antwoordt eerst”De agent vraagt output_pdf om de voltooide briefing naar schijf te
schrijven en houdt het document in leven (destroy: false) voor het geval
de mens weigert en er teruggevallen moet worden op base64-uitvoer:
{ "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 } }}Het bestand wordt niet geschreven. Omdat de bestandsmodus Approval Required is, antwoordt de server in plaats daarvan met een bevestigingschallenge:
{ "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. De mens keurt goed — roep opnieuw aan met het token
Sectie met titel “9. De mens keurt goed — roep opnieuw aan met het token”De agent geeft de challenge-tekst door aan de mens. Bij goedkeuring roept
hij output_pdf opnieuw aan met dezelfde argumenten plus
_confirmation_token:
{ "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>" } }}Het token wordt verbruikt, de schrijfactie wordt uitgevoerd, en het resultaat rapporteert het geschreven bestand:
{ "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 } }}Het geschreven bestand verifiëren
Sectie met titel “Het geschreven bestand verifiëren”De sessie schreef kickoff-brief.pdf (3.612 bytes, één pagina,
overeenkomend met structuredContent.file_size en page_count).
Vastgelegde qpdf --check-uitvoer voor exact dat bestand:
checking kickoff-brief.pdfPDF Version: 2.0File is not encryptedFile is not linearizedNo syntax or stream encoding errors found; the file may still containerrors that qpdf cannot detectDat is een structurele controle, in qpdf’s eigen woorden — geen conformiteitsbepaling.
Randgevallen & valkuilen
Sectie met titel “Randgevallen & valkuilen”- De nieuwe aanroep moet dezelfde argumenten herhalen. Het
bevestigingstoken is gebonden aan de toolnaam plus een canonieke digest
van de argumenten waarvoor het is uitgegeven. Opnieuw aanroepen waarbij
ook maar iets is gewijzigd — zelfs het omzetten van
destroy— verbruikt het token niet; de server antwoordt in plaats daarvan met een nieuwe challenge. Herhaal de argumenten exact en voeg alleen_confirmation_tokentoe. - Het token is eenmalig en verloopt. De challenge vermeldt de vervaltijd (300 seconden). Na verval of verbruik krijgt de volgende gated aanroep een nieuwe challenge; geef de nieuwe door.
- Bestandsuitvoer belandt binnen een allow-listed map. De server
weigert een
file_pathbuiten zijn geconfigureerde tijdelijke map metOutput path rejected by security policy. De standaard allow-list-root isnextpdf-mcponder de tijdelijke systeemmap; operators wijzigen dit met de instellingtemp_dirinnextpdf-mcp.yaml. - base64-modus is niet gated.
output_pdfzonderfile_pathgeeft de PDF als base64 terug op het Review-niveau, zonder neveneffect op het bestandssysteem — zie Menselijke goedkeuring vereisen voor bestandsuitvoer voor die grens in detail. - Een challenge is een resultaat, geen fout. Het challenge-bericht
komt binnen met
isError: false; een openstaande goedkeuring is een pauze in de workflow. Probeer niet in een lus opnieuw, en verzin nooit een token. - Notifications krijgen geen antwoord. Blokkeer na
notifications/initializedniet in afwachting van een responseregel.
Prestaties
Sectie met titel “Prestaties”De sessie is end-to-end in-memory: content-aanroepen kwamen in milliseconden terug tijdens de vastgelegde run, en de wandkloktijd wordt gedomineerd door de uitwisseling voor menselijke goedkeuring, wat precies het doel van de gate is. De document-store bewaart een sessie standaard 30 minuten bij inactiviteit (maximaal 50 documenten), zodat een trage goedkeuring het opgebouwde document niet verliest — maar een verlaten document wordt teruggevorderd.
Beveiligingsnotities
Sectie met titel “Beveiligingsnotities”- Behandel het bevestigingstoken als een eenmalig geheim. Geef de challenge-tekst door aan de mens; log het token niet en bewaar het niet. Deze pagina redigeert het vastgelegde token precies om die reden.
- De audit trail staat op stderr. Elke uitvoering op Caution-niveau of hoger wordt geaudit (tool, risico, argumenten, uitkomst) via PSR-3, met gevoelige parameters geredigeerd. Diagnostiek vermengt zich nooit met de protocolstream.
- De pad-allow-list is de grens van het bestandssysteem. Richt
temp_dirop een map die specifiek voor Connect-uitvoer is bedoeld; verbreed deze niet naar een algemene locatie. - Risiconiveaus kunnen alleen omhoog. Een operator-override in
nextpdf-mcp.yamlkan het risiconiveau van een tool verhogen, maar kanoutput_pdfnooit onder Approval Required brengen.
Conformiteit
Sectie met titel “Conformiteit”Dit recept doet geen normatieve claim over standaarden. Het documenteert
het MCP-stdio-transport (JSON-RPC 2.0, protocolversie 2025-06-18 zoals
onderhandeld in de vastgelegde initialize-uitwisseling) en het risico-
en bevestigingscontract van de server. De qpdf --check-stap hierboven
bevestigt uitsluitend de structurele integriteit van het geschreven
bestand; conformiteit met een standaard wordt bepaald door een
onafhankelijke validator, niet beweerd door de producerende software.
Zie ook
Sectie met titel “Zie ook”- Menselijke goedkeuring vereisen voor bestandsuitvoer — de bevestigingsgate in detail, inclusief het weigeringspad.
- Een factuur end-to-end renderen via REST — dezelfde tool-engine over HTTP, met het vastgelegde wire-transcript.
- Genereer je eerste PDF — de kleinste Connect-sessie.
- Connect-receptconventies — het contract dat elk Connect-recept volgt.
- HITL risk tiers — de canonieke risicoladder en policy-resolutie.
- Tool catalog — de maatgevende toolcatalogus.