Pular para o conteúdo
getnextpdf.com

Conduza uma sessão de documento de agente por MCP

Esta é uma sessão de agente completa com o servidor Model Context Protocol (MCP) do NextPDF Connect, mensagem por mensagem: initialize, tools/list, seis invocações de tools/call que montam um resumo de projeto de uma página e o ciclo com humano no circuito (HITL) que protege a gravação final do arquivo. Cada mensagem JSON-RPC abaixo foi capturada literalmente de um processo bin/nextpdf-mcp em execução (apenas ferramentas do tier core) e, então, higienizada de exatamente duas formas: o token de confirmação de uso único é mostrado como confirm_<single-use-hex> e o diretório temporário do sistema da máquina é abreviado para C:\Temp. Identificadores, schemas, posições e contagens de bytes são exatamente o que o servidor enviou.

Terminal window
composer require nextpdf/server

Configure o transporte stdio no seu host MCP — para o Claude Desktop (os hosts iniciam o comando a partir do próprio diretório, então use um caminho absoluto; o transporte stdio não precisa de API key, ao contrário do transporte REST):

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

O servidor fala JSON-RPC 2.0 delimitado por quebra de linha em stdin/stdout e mantém a saída do protocolo estritamente separada dos diagnósticos: as linhas de inicialização e de auditoria vão para stderr, nunca para stdout.

Uma sessão de documento do MCP tem estado. create_pdf abre um documento no armazenamento em memória do servidor e retorna um document_id; toda chamada posterior aponta para esse identificador. As ferramentas de conteúdo (set_font, add_text, add_table) são executadas imediatamente no nível de risco Cautela, com registro de auditoria; preview_layout é uma leitura Segura; e output_pdf com um file_path é Aprovação Obrigatória — não é executada na primeira chamada. Em vez disso, o servidor retorna um desafio com um token de uso único, o agente repassa o desafio ao humano e apenas uma nova chamada que carregue _confirmation_token executa a gravação. Os documentos deixados no armazenamento expiram após o tempo de vida configurado (30 minutos por padrão).

As mesmas chamadas de ferramenta conduzem o motor de ferramentas sobre REST e gRPC — os transportes compartilham um único executor — então tudo aqui, exceto o enquadramento stdio, se aplica igualmente. Veja Renderize uma fatura de ponta a ponta sobre REST para o mesmo motor na superfície HTTP.

FerramentaPapel nesta sessãoNível de risco
create_pdfAbre o documento, obtém document_idCautela
set_fontSeleciona a fonte do título, depois a do corpoCautela
add_textLinha do título, depois o parágrafo de introduçãoCautela
add_tableTabela de checklist com responsável/prazoCautela
preview_layoutLê o estado do layout antes da saídaSeguro
output_pdf (modo de arquivo)Grava o PDF — com barreiraAprovação Obrigatória

A implantação capturada aqui registrou 20 ferramentas (13 core, 6 Pro, 1 Enterprise — as contagens aparecem na resposta de initialize abaixo); esta sessão usa apenas ferramentas core, então roda sem alterações em uma instalação somente open source. O catálogo de referência é a resposta de tools/list do seu próprio servidor, e a escala de risco é definida na referência dos níveis de risco HITL.

O cliente abre a sessão e informa a versão do seu protocolo:

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

O servidor confirma a versão do protocolo e declara suas capacidades, incluindo as contagens de ferramentas por tier e que a barreira HITL está habilitada:

{
"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"
}
}
}

O cliente confirma com uma notificação (as notificações não carregam id e não recebem resposta):

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

A resposta completa lista todas as 20 ferramentas registradas com seus schemas de entrada completos. Aqui, ela aparece reduzida às duas ferramentas que abrem e fecham esta sessão — as 18 entradas omitidas têm o mesmo formato:

{
"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
}
}
]
}
}

Observe o schema de output_pdf: file_path é opcional, e as anotações carregam openWorldHint: true — a ferramenta pode afetar o mundo fora da sessão, e é exatamente por isso que o modo de arquivo tem barreira.

{
"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"
}
}
}

Todo resultado de ferramenta chega duas vezes em uma mensagem: um bloco de texto content legível por humanos e um structuredContent legível por máquina. Leia structuredContent.document_id e encadeie-o em cada chamada seguinte.

Defina uma fonte em negrito de 16 pontos e, então, posicione o título:

{
"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
}
}
}
}

De volta a uma fonte normal de 11 pontos para o texto de introdução; width: 0 seleciona o layout multi-célula de largura total:

{
"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
}
}
}
}

Cada chamada de conteúdo retorna a position atualizada do cursor, então o agente sempre sabe onde o próximo elemento vai cair.

preview_layout é uma chamada Segura, somente leitura — um agente bem-comportado verifica o que construiu antes de pedir a um humano que aprove uma gravação:

{
"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. Solicite a gravação do arquivo — a barreira responde primeiro

Seção intitulada “8. Solicite a gravação do arquivo — a barreira responde primeiro”

O agente pede a output_pdf que grave o resumo finalizado em disco, mantendo o documento vivo (destroy: false) caso o humano rejeite e seja necessário recorrer à saída em base64:

{
"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
}
}
}

O arquivo não é gravado. Como o modo de arquivo é Aprovação Obrigatória, o servidor responde, em vez disso, com um desafio de confirmação:

{
"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. O humano aprova — chame novamente com o token

Seção intitulada “9. O humano aprova — chame novamente com o token”

O agente repassa o texto do desafio ao humano. Após a aprovação, ele chama output_pdf novamente com os mesmos argumentos mais _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>"
}
}
}

O token é consumido, a gravação é executada e o resultado informa o arquivo gravado:

{
"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
}
}
}

A sessão gravou kickoff-brief.pdf (3.612 bytes, uma página, correspondendo a structuredContent.file_size e page_count). Saída capturada de qpdf --check para esse arquivo exato:

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

Isso é uma verificação estrutural, nas palavras do próprio qpdf — não uma determinação de conformidade.

  • A nova chamada deve repetir os mesmos argumentos. O token de confirmação está vinculado ao nome da ferramenta mais um digest canônico dos argumentos para os quais foi emitido. Chamar novamente com qualquer coisa alterada — mesmo inverter destroy — não consome o token; o servidor responde, em vez disso, com um novo desafio. Repita os argumentos exatamente e adicione apenas _confirmation_token.
  • O token é de uso único e expira. O desafio informa o prazo de expiração (300 segundos). Após a expiração ou o consumo, a próxima chamada com barreira recebe um novo desafio; repasse o novo.
  • A saída de arquivo cai dentro de um diretório da lista de permissões. O servidor rejeita um file_path fora do seu diretório temporário configurado com Output path rejected by security policy. A raiz padrão da lista de permissões é nextpdf-mcp sob o diretório temporário do sistema; os operadores a alteram com a configuração temp_dir em nextpdf-mcp.yaml.
  • O modo base64 não tem barreira. output_pdf sem file_path retorna o PDF como base64 no nível Revisão, sem efeito colateral no sistema de arquivos — veja Exija aprovação humana para a saída de arquivo para conhecer esse limite em profundidade.
  • Um desafio é um resultado, não um erro. A mensagem de desafio chega com isError: false; uma aprovação pendente é uma pausa no fluxo de trabalho. Não repita em loop e nunca fabrique um token.
  • Notificações não recebem resposta. Após notifications/initialized, não fique bloqueado esperando por uma linha de resposta.

A sessão é totalmente em memória de ponta a ponta: as chamadas de conteúdo retornaram em milissegundos na execução capturada, e o tempo total é dominado pelo ciclo de aprovação humana, que é o propósito da barreira. O armazenamento de documentos mantém uma sessão por 30 minutos de tempo ocioso por padrão (no máximo 50 documentos), então uma aprovação lenta não perde o documento construído — mas um documento abandonado é descartado.

  • Trate o token de confirmação como um segredo de uso único. Repasse o texto do desafio ao humano; não registre o token em log nem o persista. Esta página oculta o token capturado exatamente por essa razão.
  • A trilha de auditoria fica no stderr. Toda execução no nível Cautela ou acima é registrada em auditoria (ferramenta, risco, argumentos, resultado) via PSR-3, com os parâmetros sensíveis ocultados. Os diagnósticos nunca se misturam ao fluxo do protocolo.
  • A lista de permissões de caminhos é o limite do sistema de arquivos. Aponte temp_dir para um diretório dedicado à saída do Connect; não a amplie para um local de uso geral.
  • Os níveis de risco só sobem. Uma sobrescrita de operador em nextpdf-mcp.yaml pode elevar o nível de risco de uma ferramenta, mas nunca pode rebaixar output_pdf abaixo de Aprovação Obrigatória.

Esta receita não faz nenhuma afirmação normativa de conformidade com padrões. Ela documenta o transporte stdio do MCP (JSON-RPC 2.0, versão de protocolo 2025-06-18 conforme negociada na troca de initialize capturada) e o contrato de risco e confirmação do servidor. A etapa qpdf --check acima confirma apenas a integridade estrutural do arquivo gravado; a conformidade com um padrão é determinada por um validador independente, não afirmada pelo software produtor.