透過 MCP 驅動代理人文件工作階段
這是針對 NextPDF Connect Model Context Protocol(MCP)伺服器的一次完整代理人工作階段,逐則訊息呈現:initialize、tools/list、六次建構一頁式專案簡報的 tools/call 呼叫,以及為最終檔案寫入把關的人工介入(HITL)
往返。下方每一則 JSON-RPC 訊息都是從一個實際運作的 bin/nextpdf-mcp
程序(僅 core 等級工具)逐字擷取,接著僅以兩種方式加以淨化:一次性確認權杖以 confirm_<single-use-hex> 表示,而機器的系統暫存目錄縮寫為
C:\Temp。識別碼、schema、位置與位元組數皆與伺服器實際送出的內容完全一致。
composer require nextpdf/server在您的 MCP host 中綁定 stdio 傳輸層——以 Claude Desktop 為例(host 會從自己的目錄啟動指令,因此請使用絕對路徑;與 REST 傳輸層不同,stdio 傳輸層不需要 API key):
{ "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)——它不會在第一次呼叫時執行。伺服器會改為回傳一個帶有一次性權杖的挑戰碼,代理人將挑戰碼轉達給人類,只有攜帶 _confirmation_token 重新呼叫才會執行寫入。留在儲存區的文件會在設定的存活時間後過期(預設為 30 分鐘)。
相同的工具呼叫也能透過 REST 與 gRPC 驅動工具引擎——這些傳輸層共用同一個 executor——因此此處除了 stdio 框架之外的一切都可沿用。若要在 HTTP 介面上使用相同引擎,請參閱 透過 REST 端對端產生發票。
API 介面
標題為「API 介面」的區段| 工具 | 在本工作階段中的角色 | 風險等級 |
|---|---|---|
create_pdf | 開啟文件,取得 document_id | 注意 |
set_font | 選擇標題字體,再選內文字體 | 注意 |
add_text | 標題行,再加入前言段落 | 注意 |
add_table | 負責人/到期日檢查清單表格 | 注意 |
preview_layout | 輸出前讀取版面狀態 | 安全 |
output_pdf(檔案模式) | 寫入 PDF——受閘門管控 | 需要核准 |
此處擷取的部署註冊了 20 個工具(13 個 core、6 個 Pro、1 個
Enterprise——這些數量出現在下方的 initialize 回應中);本工作階段僅使用 core 工具,因此在僅含開源版本的安裝上也能原封不動地執行。權威的目錄以您自己伺服器的 tools/list 回覆為準,而風險階梯定義於
HITL 風險等級參考。
逐則訊息呈現的工作階段
標題為「逐則訊息呈現的工作階段」的區段1. 初始化連線
標題為「1. 初始化連線」的區段client 開啟工作階段並宣告其協定版本:
{ "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" } }}client 以一則通知確認(通知不帶 id,也不會收到回應):
{ "jsonrpc": "2.0", "method": "notifications/initialized"}2. 探索工具
標題為「2. 探索工具」的區段{ "jsonrpc": "2.0", "id": 2, "method": "tools/list"}完整回覆會列出全部 20 個已註冊工具及其完整的 input schema。此處縮短為開啟與結束本工作階段的兩個工具——省略的 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 的 schema:file_path 是選用的,且 annotations 帶有
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" } }}每個工具結果都在同一則訊息中出現兩次:一個供人類閱讀的 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 會選擇全寬的 multi-cell
版面:
{ "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 是「安全」的唯讀呼叫——行為良好的代理人會在請求人類核准寫入之前,先檢查自己建構的成果:
{ "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 將完成的簡報寫入磁碟,並保留文件的存活狀態
(destroy: false),以防人類拒絕而需要退回改用 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 } }}檔案不會被寫入。由於檔案模式屬於「需要核准」,伺服器會改以確認挑戰碼回應:
{ "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 位元組、一頁,與
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。 - 權杖僅能使用一次且會過期。 挑戰碼會標明到期時間(300 秒)。過期或被消耗後,下一次受閘門管控的呼叫會取得新的挑戰碼;請轉達新的那一個。
- 檔案輸出會落在允許清單內的目錄。 伺服器會以
Output path rejected by security policy拒絕位於其設定暫存目錄之外的file_path。預設的允許清單根目錄為系統暫存目錄下的nextpdf-mcp; 操作者可透過nextpdf-mcp.yaml中的temp_dir設定加以變更。 - base64 模式不受閘門管控。 不含
file_path的output_pdf會在 「審查」(Review)等級以 base64 回傳 PDF,且不產生任何檔案系統副作用——如需深入了解該邊界,請參閱 為檔案輸出要求人類核准。 - 挑戰碼是結果,而非錯誤。 挑戰碼訊息會帶著
isError: false送達; 等待核准是一種工作流程暫停。請勿以迴圈重試,也絕不可偽造權杖。 - 通知不會有回覆。 在
notifications/initialized之後,請勿阻塞等待回應行。
本工作階段自始至終皆在記憶體中運作:在擷取的執行中,內容呼叫於數毫秒內回傳,而實際耗時主要取決於人類核准的往返——這正是閘門的用意。文件儲存區預設會將工作階段保留 30 分鐘的閒置時間(最多 50 份文件),因此緩慢的核准不會遺失已建構的文件——但被棄置的文件會被回收。
安全注意事項
標題為「安全注意事項」的區段- 請將確認權杖視為一次性密鑰。 將挑戰碼文字轉達給人類;請勿記錄或持久化該權杖。本頁正是基於此原因而遮蔽了擷取到的權杖。
- 稽核軌跡位於 stderr。 每一次「注意」或更高等級的執行都會透過 PSR-3 記錄稽核(工具、風險、引數、結果),並遮蔽敏感參數。診斷資訊絕不會混入協定串流。
- 路徑允許清單即檔案系統邊界。 請將
temp_dir指向專供 Connect 輸出使用的目錄;不要將其擴大到通用位置。 - 風險等級只會向上調整。
nextpdf-mcp.yaml中的操作者覆寫可以提高某個工具的風險等級,但絕不能將output_pdf降到「需要核准」以下。
合規性
標題為「合規性」的區段本食譜未提出任何規範性標準主張。它記錄的是 MCP stdio 傳輸層
(JSON-RPC 2.0,協定版本 2025-06-18,即擷取的 initialize 交握中所協商者),以及伺服器的風險與確認契約。上方的 qpdf --check 步驟僅確認已寫入檔案的結構完整性;對標準的合規性由獨立驗證器判定,而非由產生軟體所主張。
另請參閱
標題為「另請參閱」的區段- 為檔案輸出要求人類核准 ——深入探討確認閘門,包括拒絕路徑。
- 透過 REST 端對端產生發票 ——透過 HTTP 的相同工具引擎,附擷取的傳輸紀錄。
- 產生您的第一份 PDF——最精簡的 Connect 工作階段。
- Connect 食譜慣例 ——每個 Connect 食譜都遵循的契約。
- HITL 風險等級——權威的風險階梯與政策解析。
- 工具目錄——權威的工具目錄。