Bỏ qua để đến nội dung
getnextpdf.com

Điều khiển phiên tài liệu của agent qua MCP

Đây là một phiên agent hoàn chỉnh với máy chủ NextPDF Connect Model Context Protocol (MCP), từng thông điệp một: initialize, tools/list, sáu lời gọi tools/call dựng nên một bản tóm tắt dự án một trang, và vòng trao đổi có con người tham gia (HITL) đặt cổng kiểm soát cho thao tác ghi tệp cuối cùng. Mọi thông điệp JSON-RPC bên dưới đều được ghi lại nguyên văn từ một tiến trình bin/nextpdf-mcp đang chạy (chỉ các công cụ bậc core), rồi được làm sạch theo đúng hai cách: token xác nhận dùng một lần được hiển thị dưới dạng confirm_<single-use-hex>, và thư mục tạm hệ thống của máy được rút gọn thành C:\Temp. Các định danh, schema, vị trí và số byte đúng chính xác những gì máy chủ đã gửi.

Terminal window
composer require nextpdf/server

Liên kết transport stdio trong MCP host của bạn — với Claude Desktop (các host khởi chạy lệnh từ thư mục của riêng chúng, nên hãy dùng đường dẫn tuyệt đối; transport stdio không cần API key, khác với transport REST):

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

Máy chủ giao tiếp JSON-RPC 2.0 phân tách bằng ký tự xuống dòng trên stdin/stdout và giữ đầu ra giao thức tách biệt hoàn toàn khỏi thông tin chẩn đoán: các dòng khởi động và audit đi tới stderr, không bao giờ tới stdout.

Một phiên tài liệu MCP có trạng thái. create_pdf mở một tài liệu trong kho lưu trữ trong bộ nhớ của máy chủ và trả về một document_id; mọi lời gọi sau đó đều nhắm tới định danh đó. Các công cụ nội dung (set_font, add_text, add_table) thực thi ngay lập tức ở mức rủi ro Caution kèm ghi log audit; preview_layout là một thao tác đọc Safe; và output_pdf kèm file_path là Approval Required — nó không chạy ở lời gọi đầu tiên. Thay vào đó, máy chủ trả về một thử thách kèm token dùng một lần, agent chuyển tiếp thử thách đó tới con người, và chỉ một lời gọi lại mang theo _confirmation_token mới thực thi thao tác ghi. Các tài liệu để lại trong kho sẽ hết hạn sau thời gian tồn tại được cấu hình (mặc định 30 phút).

Cũng chính những lời gọi công cụ này điều khiển tool engine qua REST và gRPC — các transport dùng chung một executor — nên mọi thứ ở đây, ngoại trừ khung stdio, đều áp dụng được. Xem Kết xuất hóa đơn từ đầu đến cuối qua REST để xem chính engine đó trên bề mặt HTTP.

Công cụVai trò trong phiên nàyMức rủi ro
create_pdfMở tài liệu, lấy document_idCaution
set_fontChọn phông tiêu đề, rồi phông nội dungCaution
add_textDòng tiêu đề, rồi đoạn giới thiệuCaution
add_tableBảng danh sách kiểm người phụ trách/hạn chótCaution
preview_layoutĐọc trạng thái bố cục trước khi xuấtSafe
output_pdf (chế độ tệp)Ghi PDF — có cổng kiểm soátApproval Required

Bản triển khai được ghi lại ở đây đã đăng ký 20 công cụ (13 core, 6 Pro, 1 Enterprise — các con số xuất hiện trong phản hồi initialize bên dưới); phiên này chỉ dùng các công cụ core, nên nó chạy y nguyên trên một bản cài đặt chỉ mã nguồn mở. Danh mục chính thức là phản hồi tools/list từ chính máy chủ của bạn, và thang rủi ro được định nghĩa trong tài liệu tham chiếu HITL risk tiers.

Client mở phiên và khai báo phiên bản giao thức của nó:

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

Máy chủ xác nhận phiên bản giao thức và khai báo các khả năng của nó, bao gồm số lượng công cụ theo từng bậc và việc cổng kiểm soát HITL đã được bật:

{
"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 xác nhận bằng một thông báo (các thông báo không mang id và không nhận phản hồi):

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

Phản hồi đầy đủ liệt kê toàn bộ 20 công cụ đã đăng ký cùng các input schema hoàn chỉnh của chúng. Ở đây nó được rút gọn còn hai công cụ mở và đóng phiên này — 18 mục bị lược bỏ có cùng dạng cấu trúc:

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

Lưu ý schema của output_pdf: file_path là tùy chọn, và các annotation mang openWorldHint: true — công cụ này có thể tác động tới thế giới bên ngoài phiên, và đó chính là lý do chế độ tệp có cổng kiểm soát.

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

Mỗi kết quả công cụ xuất hiện hai lần trong cùng một thông điệp: một khối văn bản content mà con người đọc được, và structuredContent mà máy đọc được. Đọc structuredContent.document_id và luồn nó qua mọi lời gọi tiếp theo.

Chọn phông đậm cỡ 16 point, rồi đặt tiêu đề:

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

Trở lại phông thường cỡ 11 point cho phần văn bản giới thiệu; width: 0 chọn bố cục multi-cell toàn chiều rộng:

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

Mỗi lời gọi nội dung trả về vị trí con trỏ position đã cập nhật, nên agent luôn biết phần tử tiếp theo sẽ rơi vào đâu.

7. Xem trước rồi mới yêu cầu phê duyệt

Phần tiêu đề “7. Xem trước rồi mới yêu cầu phê duyệt”

preview_layout là một lời gọi Safe, chỉ đọc — một agent hành xử đúng mực sẽ kiểm tra thứ nó vừa dựng trước khi yêu cầu con người phê duyệt một thao tác ghi:

{
"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. Yêu cầu ghi tệp — cổng kiểm soát trả lời trước

Phần tiêu đề “8. Yêu cầu ghi tệp — cổng kiểm soát trả lời trước”

Agent yêu cầu output_pdf ghi bản tóm tắt đã hoàn thiện ra đĩa, giữ cho tài liệu còn tồn tại (destroy: false) phòng khi con người từ chối và nó cần chuyển sang đầu ra 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
}
}
}

Tệp không được ghi. Vì chế độ tệp là Approval Required, máy chủ trả lời bằng một thử thách xác nhận thay vì ghi:

{
"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. Con người phê duyệt — gọi lại với token

Phần tiêu đề “9. Con người phê duyệt — gọi lại với token”

Agent chuyển tiếp nội dung thử thách tới con người. Khi được phê duyệt, nó gọi output_pdf lần nữa với cùng các tham số cộng thêm _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>"
}
}
}

Token bị tiêu thụ, thao tác ghi được thực thi, và kết quả báo cáo tệp đã ghi:

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

Phiên này đã ghi kickoff-brief.pdf (3.612 byte, một trang, khớp với structuredContent.file_sizepage_count). Đầu ra qpdf --check được ghi lại cho đúng tệp đó:

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

Đó là một kiểm tra cấu trúc, theo đúng lời của qpdf — không phải một phán định tuân thủ.

  • Lời gọi lại phải lặp lại đúng các tham số. Token xác nhận được ràng buộc với tên công cụ cộng với một digest chuẩn tắc của bộ tham số mà token được cấp cho. Gọi lại với bất kỳ thay đổi nào — kể cả đảo destroy — đều không tiêu thụ token; thay vào đó máy chủ trả lời bằng một thử thách mới. Hãy lặp lại các tham số y hệt và chỉ thêm _confirmation_token.
  • Token dùng một lần và sẽ hết hạn. Thử thách nêu rõ thời hạn (300 giây). Sau khi hết hạn hoặc bị tiêu thụ, lời gọi có cổng kiểm soát tiếp theo sẽ nhận một thử thách mới; hãy chuyển tiếp thử thách mới đó.
  • Đầu ra tệp rơi vào một thư mục nằm trong danh sách cho phép. Máy chủ từ chối một file_path nằm ngoài thư mục tạm đã cấu hình với thông báo Output path rejected by security policy. Gốc danh sách cho phép mặc định là nextpdf-mcp bên dưới thư mục tạm hệ thống; người vận hành thay đổi nó bằng thiết lập temp_dir trong nextpdf-mcp.yaml.
  • Chế độ base64 không có cổng kiểm soát. output_pdf không có file_path trả về PDF dưới dạng base64 ở mức Review, không có tác dụng phụ tới hệ thống tệp — xem Yêu cầu phê duyệt của con người để xuất tệp để tìm hiểu sâu về ranh giới đó.
  • Một thử thách là một kết quả, không phải một lỗi. Thông điệp thử thách mang isError: false; một phê duyệt đang chờ là một điểm tạm dừng của quy trình. Đừng thử lại trong vòng lặp, và tuyệt đối đừng bịa ra một token.
  • Các thông báo không có phản hồi. Sau notifications/initialized, đừng chặn để chờ một dòng phản hồi.

Phiên này hoàn toàn nằm trong bộ nhớ từ đầu đến cuối: các lời gọi nội dung trả về trong vài mili-giây ở lần chạy được ghi lại, và thời gian thực tế bị chi phối bởi vòng trao đổi phê duyệt của con người, vốn chính là mục đích của cổng kiểm soát. Kho tài liệu giữ một phiên trong 30 phút nhàn rỗi theo mặc định (tối đa 50 tài liệu), nên một lần phê duyệt chậm không làm mất tài liệu đã dựng — nhưng một tài liệu bị bỏ rơi sẽ được thu hồi.

  • Hãy coi token xác nhận như một bí mật dùng một lần. Chuyển tiếp nội dung thử thách tới con người; đừng ghi log token hay lưu lại nó. Trang này che token đã ghi lại vì đúng lý do đó.
  • Vết audit nằm trên stderr. Mọi thao tác ở mức Caution trở lên đều được ghi log audit (công cụ, rủi ro, tham số, kết quả) qua PSR-3, với các tham số nhạy cảm được che. Thông tin chẩn đoán không bao giờ trộn vào luồng giao thức.
  • Danh sách cho phép đường dẫn là ranh giới của hệ thống tệp. Hãy trỏ temp_dir tới một thư mục dành riêng cho đầu ra Connect; đừng mở rộng nó tới một vị trí dùng chung.
  • Các mức rủi ro chỉ tăng một chiều. Một ghi đè của người vận hành trong nextpdf-mcp.yaml có thể nâng mức rủi ro của một công cụ nhưng không bao giờ có thể hạ output_pdf xuống dưới Approval Required.

Công thức này không đưa ra tuyên bố tiêu chuẩn mang tính quy phạm nào. Nó ghi lại transport stdio của MCP (JSON-RPC 2.0, phiên bản giao thức 2025-06-18 như được thương lượng trong trao đổi initialize đã ghi lại) và hợp đồng rủi ro và xác nhận của máy chủ. Bước qpdf --check ở trên chỉ xác nhận tính toàn vẹn cấu trúc của tệp đã ghi mà thôi; sự tuân thủ một tiêu chuẩn được xác định bởi một trình kiểm định độc lập, chứ không phải do phần mềm tạo ra tệp khẳng định.