MCP 経由でエージェントのドキュメントセッションを実行する
これは NextPDF Connect の Model Context Protocol(MCP)サーバーに対する 1 つの完全なエージェントセッションを、メッセージ単位で示したものです。initialize、tools/list、1 ページのプロジェクトブリーフを構築する 6 回の tools/call 呼び出し、そして最終的なファイル書き込みをゲートする human-in-the-loop(HITL)のラウンドトリップが含まれます。以下のすべての JSON-RPC メッセージは、実際に稼働している bin/nextpdf-mcp プロセス(Core 層のツールのみ)から逐語的にキャプチャしたうえで、ちょうど 2 か所だけをサニタイズしています。1 回限りの確認トークンは confirm_<single-use-hex> として示し、マシンのシステム一時ディレクトリは C:\Temp に短縮しています。識別子、スキーマ、位置、バイト数は、サーバーが送信した内容そのままです。
インストール
「インストール」という見出しのセクションcomposer require nextpdf/serverMCP ホストで 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_font、add_text、add_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 を参照してください。
API サーフェス
「API サーフェス」という見出しのセクション| ツール | このセッションでの役割 | リスクレベル |
|---|---|---|
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 で定義されています。
セッション、メッセージ単位で
「セッション、メッセージ単位で」という見出しのセクション1. 接続を初期化する
「1. 接続を初期化する」という見出しのセクションクライアントはセッションを開き、自身のプロトコルバージョンを表明します。
{ "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"}2. ツールを発見する
「2. ツールを発見する」という見出しのセクション{ "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 を持ちます。このツールはセッションの外部の世界に触れられるということであり、まさにそれがファイルモードをゲートする理由です。
3. ドキュメントを開く
「3. ドキュメントを開く」という見出しのセクション{ "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 を読み取り、それ以降のすべての呼び出しに通してください。
4. 見出しを追加する
「4. 見出しを追加する」という見出しのセクション太字 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 } } }}5. 本文段落を追加する
「5. 本文段落を追加する」という見出しのセクション導入テキストのために通常の 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 } } }}6. チェックリスト表を追加する
「6. チェックリスト表を追加する」という見出しのセクション{ "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 を返すため、エージェントは次の要素がどこに配置されるかを常に把握できます。
7. 承認を求める前にプレビューする
「7. 承認を求める前にプレビューする」という見出しのセクション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 } } }}8. ファイル書き込みを要求する — ゲートが先に応答する
「8. ファイル書き込みを要求する — ゲートが先に応答する」という見出しのセクションエージェントは、完成したブリーフをディスクに書き込むよう 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 }}9. 人間が承認する — トークンを添えて再呼び出しする
「9. 人間が承認する — トークンを添えて再呼び出しする」という見出しのセクションエージェントはチャレンジのテキストを人間へ中継します。承認されると、同じ引数 に _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_size と page_count に一致)。まさにそのファイルに対してキャプチャした qpdf --check の出力です。
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 detectこれは qpdf 自身の言葉による構造チェックであって、適合性の判定ではありません。
エッジケースと落とし穴
「エッジケースと落とし穴」という見出しのセクション- 再呼び出しは同じ引数を繰り返さなければなりません。 確認トークンは、ツール名と、発行時の引数の正規ダイジェストに紐づけられています。何かを変更した状態で再呼び出しすると — たとえ
destroyを反転させただけでも — トークンは消費されず、サーバーは代わりに新しいチャレンジで応答します。引数を正確に繰り返し、_confirmation_tokenだけを追加してください。 - トークンは 1 回限りで、失効します。 チャレンジには失効までの時間(300 秒)が示されます。失効または消費の後、次のゲート対象呼び出しは新しいチャレンジを受け取ります。その新しいチャレンジを中継してください。
- ファイル出力は許可リスト化されたディレクトリの内側に配置されます。 サーバーは、設定された一時ディレクトリの外にある
file_pathをOutput path rejected by security policyで拒否します。既定の許可リストのルートは、システム一時ディレクトリ配下のnextpdf-mcpです。オペレーターはnextpdf-mcp.yamlのtemp_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 ステップは、書き込まれたファイルの構造的整合性のみを確認します。標準への適合性は独立したバリデーターによって判定されるものであり、生成側のソフトウェアが主張するものではありません。
- Require human approval for file output — 拒否パスを含む、確認ゲートの詳細。
- Render an invoice end to end over REST — HTTP 上での同じツールエンジンと、キャプチャしたワイヤトランスクリプト。
- Generate your first PDF — 最小の Connect セッション。
- Connect recipe conventions — すべての Connect レシピが従うコントラクト。
- HITL risk tiers — 正式なリスクの階梯とポリシー解決。
- Tool catalog — 正式なツールカタログ。