コンテンツにスキップ
getnextpdf.com

MCP 経由でエージェントのドキュメントセッションを実行する

これは NextPDF Connect の Model Context Protocol(MCP)サーバーに対する 1 つの完全なエージェントセッションを、メッセージ単位で示したものです。initializetools/list、1 ページのプロジェクトブリーフを構築する 6 回の tools/call 呼び出し、そして最終的なファイル書き込みをゲートする human-in-the-loop(HITL)のラウンドトリップが含まれます。以下のすべての JSON-RPC メッセージは、実際に稼働している bin/nextpdf-mcp プロセス(Core 層のツールのみ)から逐語的にキャプチャしたうえで、ちょうど 2 か所だけをサニタイズしています。1 回限りの確認トークンは confirm_<single-use-hex> として示し、マシンのシステム一時ディレクトリは C:\Temp に短縮しています。識別子、スキーマ、位置、バイト数は、サーバーが送信した内容そのままです。

Terminal window
composer require nextpdf/server

MCP ホストで stdio トランスポートをバインドします。Claude Desktop の場合は次のとおりです(ホストは自身のディレクトリからコマンドを起動するため、絶対パスを使用してください。REST トランスポートと異なり、stdio トランスポートに API キーは不要です)。

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

サーバーは stdin/stdout 上で改行区切りの JSON-RPC 2.0 を話し、プロトコル出力を診断情報から厳格に分離します。起動行と監査行は stderr に送られ、決して stdout には出ません。

MCP のドキュメントセッションはステートフルです。create_pdf はサーバーのインメモリストアにドキュメントを開き、document_id を返します。それ以降のすべての呼び出しはその識別子を対象とします。コンテンツツール(set_fontadd_textadd_table)は Caution リスクレベルで監査ログとともに即座に実行されます。preview_layout は Safe な読み取りです。そして file_path を伴う output_pdf は Approval Required であり、最初の呼び出しでは実行されません。代わりにサーバーは 1 回限りのトークンを含むチャレンジを返し、エージェントがそのチャレンジを人間へ中継し、_confirmation_token を添えた再呼び出しのみが書き込みを実行します。ストアに残されたドキュメントは、設定された time to live(既定では 30 分)の後に失効します。

同じツール呼び出しが REST と gRPC 経由でもツールエンジンを駆動します。これらのトランスポートは 1 つのエグゼキューターを共有するため、stdio のフレーミングを除き、ここでの内容はすべてそのまま当てはまります。HTTP 上での同じエンジンについては Render an invoice end to end over REST を参照してください。

ツールこのセッションでの役割リスクレベル
create_pdfドキュメントを開き、document_id を取得するCaution
set_font見出し、続いて本文の書体を選択するCaution
add_textタイトル行、続いて導入段落を配置するCaution
add_table担当者/期日のチェックリスト表Caution
preview_layout出力前にレイアウト状態を読み取るSafe
output_pdf(ファイルモード)PDF を書き込む — ゲート対象Approval Required

ここでキャプチャしたデプロイでは 20 個のツールが登録されていました(Core 13、Pro 6、Enterprise 1 — これらの数値は下記の initialize 応答に現れます)。このセッションは Core ツールのみを使用するため、オープンソースのみのインストールでもそのまま動作します。正式なカタログはご自身のサーバーの tools/list 応答であり、リスクの階梯は HITL risk tiers reference で定義されています。

クライアントはセッションを開き、自身のプロトコルバージョンを表明します。

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

サーバーはプロトコルバージョンを確認し、自身のケイパビリティ(層ごとのツール数や、HITL ゲートが有効であること)を宣言します。

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

クライアントは通知で確認します(通知は id を持たず、応答を受け取りません)。

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

完全な応答は、登録済みの 20 個すべてのツールを、その完全な入力スキーマとともに列挙します。ここではこのセッションの開始と終了を担う 2 つのツールに短縮して示しています。省略した 18 個のエントリも同じ形をしています。

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

output_pdf のスキーマに注目してください。file_path は省略可能で、アノテーションは openWorldHint: true を持ちます。このツールはセッションの外部の世界に触れられるということであり、まさにそれがファイルモードをゲートする理由です。

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

すべてのツール結果は 1 つのメッセージ内に二重に到着します。人間が読める content テキストブロックと、機械が読める structuredContent です。structuredContent.document_id を読み取り、それ以降のすべての呼び出しに通してください。

太字 16 ポイントの書体を設定してから、タイトルを配置します。

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

導入テキストのために通常の 11 ポイントの書体へ戻します。width: 0 は全幅のマルチセルレイアウトを選択します。

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

各コンテンツ呼び出しは更新後のカーソル position を返すため、エージェントは次の要素がどこに配置されるかを常に把握できます。

preview_layout は Safe な読み取り専用の呼び出しです。行儀のよいエージェントは、人間に書き込みの承認を求める前に、自分が構築したものを確認します。

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

エージェントは、完成したブリーフをディスクに書き込むよう output_pdf に要求します。人間が拒否して base64 出力へフォールバックする必要が生じた場合に備えて、ドキュメントを存続させます(destroy: false)。

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

ファイルは書き込まれ ません。ファイルモードは Approval Required であるため、サーバーは代わりに確認チャレンジで応答します。

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

エージェントはチャレンジのテキストを人間へ中継します。承認されると、同じ引数_confirmation_token を加えて output_pdf を再び呼び出します。

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

トークンが消費され、書き込みが実行され、結果は書き込まれたファイルを報告します。

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

このセッションは kickoff-brief.pdf を書き込みました(3,612 バイト、1 ページで、structuredContent.file_sizepage_count に一致)。まさにそのファイルに対してキャプチャした qpdf --check の出力です。

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

これは qpdf 自身の言葉による構造チェックであって、適合性の判定ではありません。

  • 再呼び出しは同じ引数を繰り返さなければなりません。 確認トークンは、ツール名と、発行時の引数の正規ダイジェストに紐づけられています。何かを変更した状態で再呼び出しすると — たとえ destroy を反転させただけでも — トークンは消費されず、サーバーは代わりに新しいチャレンジで応答します。引数を正確に繰り返し、_confirmation_token だけを追加してください。
  • トークンは 1 回限りで、失効します。 チャレンジには失効までの時間(300 秒)が示されます。失効または消費の後、次のゲート対象呼び出しは新しいチャレンジを受け取ります。その新しいチャレンジを中継してください。
  • ファイル出力は許可リスト化されたディレクトリの内側に配置されます。 サーバーは、設定された一時ディレクトリの外にある file_pathOutput path rejected by security policy で拒否します。既定の許可リストのルートは、システム一時ディレクトリ配下の nextpdf-mcp です。オペレーターは nextpdf-mcp.yamltemp_dir 設定でこれを変更します。
  • base64 モードはゲート対象ではありません。 file_path を伴わない output_pdf は、ファイルシステムへの副作用なしに、Review レベルで PDF を base64 として返します。その境界の詳細は Require human approval for file output を参照してください。
  • チャレンジはエラーではなく結果です。 チャレンジメッセージは isError: false を伴って到着します。承認の保留はワークフローの一時停止です。ループ内で再試行せず、決してトークンを捏造しないでください。
  • 通知には応答がありません。 notifications/initialized の後、応答行を待ってブロックしないでください。

このセッションは端から端までインメモリです。キャプチャした実行ではコンテンツ呼び出しはミリ秒単位で返り、実時間の大半は、人間による承認のラウンドトリップが占めます。それこそがゲートの狙いです。ドキュメントストアはセッションを既定でアイドル 30 分間(最大 50 ドキュメント)保持するため、承認が遅くても構築済みのドキュメントは失われません。ただし放棄されたものは回収されます。

  • 確認トークンはワンタイムの秘密として扱ってください。 チャレンジのテキストを人間へ中継し、トークンをログ出力したり永続化したりしないでください。このページがキャプチャしたトークンを伏せているのは、まさにその理由です。
  • 監査証跡は stderr にあります。 Caution 以上のすべての実行は、機微なパラメーターを伏せたうえで PSR-3 を介して監査ログに記録されます(ツール、リスク、引数、結果)。診断情報がプロトコルストリームに混ざることは決してありません。
  • パス許可リストがファイルシステムの境界です。 temp_dir は Connect の出力専用のディレクトリに向けてください。汎用の場所へ広げないでください。
  • リスクレベルは上げる方向にしか動きません。 nextpdf-mcp.yaml でのオペレーターのオーバーライドはツールのリスクレベルを引き上げられますが、output_pdf を Approval Required より下げることは決してできません。

このレシピは規範的な標準に関する主張は行いません。MCP の stdio トランスポート(JSON-RPC 2.0、キャプチャした initialize 交換でネゴシエートされたプロトコルバージョン 2025-06-18)と、サーバーのリスクおよび確認のコントラクトを記述するものです。上記の qpdf --check ステップは、書き込まれたファイルの構造的整合性のみを確認します。標準への適合性は独立したバリデーターによって判定されるものであり、生成側のソフトウェアが主張するものではありません。