ข้ามไปยังเนื้อหา
getnextpdf.com

ขับเคลื่อนเซสชันเอกสารของเอเจนต์ผ่าน MCP

นี่คือเซสชันเอเจนต์ที่สมบูรณ์หนึ่งเซสชันกับเซิร์ฟเวอร์ NextPDF Connect Model Context Protocol (MCP) แสดงทีละข้อความ ได้แก่ initialize, tools/list, การเรียก tools/call หกครั้งที่สร้างเอกสารสรุปโครงการหนึ่งหน้า และการรับส่งแบบ human-in-the-loop (HITL) ที่ควบคุมการเขียนไฟล์สุดท้าย ทุกข้อความ JSON-RPC ด้านล่างถูกบันทึกแบบคำต่อคำจากโปรเซส bin/nextpdf-mcp ที่ทำงานจริง (เฉพาะเครื่องมือระดับ core) แล้วปรับให้ปลอดภัยเพียงสองวิธีเท่านั้น คือ token การยืนยันแบบใช้ได้ครั้งเดียวจะแสดงเป็น confirm_<single-use-hex> และไดเรกทอรีชั่วคราวของระบบในเครื่องถูกย่อเป็น C:\Temp ตัวระบุ สคีมา ตำแหน่ง และจำนวนไบต์เป็นค่าที่เซิร์ฟเวอร์ส่งมาตามจริงทุกประการ

Terminal window
composer require nextpdf/server

ผูก stdio transport ใน MCP host ของคุณ สำหรับ Claude Desktop (host จะเรียกใช้คำสั่งจากไดเรกทอรีของตัวเอง จึงต้องใช้ path แบบสัมบูรณ์ และ stdio transport ไม่ต้องใช้ API key ต่างจาก REST transport)

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

เซิร์ฟเวอร์สื่อสารด้วย JSON-RPC 2.0 แบบคั่นด้วยการขึ้นบรรทัดใหม่บน stdin/stdout และแยกเอาต์พุตของโปรโตคอลออกจากข้อมูลวินิจฉัยอย่างเคร่งครัด บรรทัดการเริ่มทำงานและการตรวจสอบจะไปที่ stderr ไม่ใช่ stdout

เซสชันเอกสารของ MCP มีสถานะ create_pdf จะเปิดเอกสารในที่เก็บข้อมูลแบบ in-memory ของเซิร์ฟเวอร์และส่งคืน document_id การเรียกทุกครั้งถัดมาจะอ้างถึงตัวระบุนั้น เครื่องมือด้านเนื้อหา (set_font, add_text, add_table) จะทำงานทันทีที่ระดับความเสี่ยง Caution พร้อมบันทึกการตรวจสอบ preview_layout เป็นการอ่านระดับ Safe และ output_pdf ที่มี file_path เป็น Approval Required ซึ่งจะไม่ทำงานในการเรียกครั้งแรก แต่เซิร์ฟเวอร์จะส่งคืน challenge พร้อม token แบบใช้ได้ครั้งเดียวแทน เอเจนต์จะส่งต่อ challenge ให้มนุษย์ และมีเพียงการเรียกซ้ำที่มี _confirmation_token เท่านั้นที่จะดำเนินการเขียนไฟล์ เอกสารที่ค้างอยู่ในที่เก็บข้อมูลจะหมดอายุหลังจากระยะเวลา time to live ที่กำหนด (ค่าเริ่มต้น 30 นาที)

การเรียกเครื่องมือชุดเดียวกันนี้ขับเคลื่อน tool engine ผ่าน REST และ gRPC เนื่องจาก transport ทั้งหลายใช้ executor ร่วมกัน ดังนั้นทุกอย่างในที่นี้ยกเว้นการจัดเฟรมของ stdio จึงนำไปใช้ได้เหมือนกัน ดู เรนเดอร์ใบแจ้งหนี้แบบครบวงจรผ่าน REST สำหรับ engine เดียวกันบนพื้นผิว HTTP

เครื่องมือบทบาทในเซสชันนี้ระดับความเสี่ยง
create_pdfเปิดเอกสาร รับ document_idCaution
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

ไคลเอนต์เปิดเซสชันและระบุเวอร์ชันโปรโตคอลของตน

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

ไคลเอนต์ตอบรับด้วย notification (notification ไม่มี id และไม่ได้รับการตอบกลับ)

{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}
{
"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
}
}
]
}
}

สังเกต schema ของ output_pdf file_path เป็นค่าที่ไม่บังคับ และ annotation มี 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"
}
}
}

ผลลัพธ์ของเครื่องมือทุกรายการมาถึงสองครั้งในข้อความเดียว คือบล็อกข้อความ 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 เลือกเลย์เอาต์แบบ 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
}
}
}
}
{
"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 เขียนเอกสารสรุปที่เสร็จแล้วลงดิสก์ โดยคงเอกสารไว้ (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
}
}
}

ไฟล์ไม่ได้ถูกเขียน เนื่องจากโหมดไฟล์เป็น Approval Required เซิร์ฟเวอร์จึงตอบกลับด้วย challenge การยืนยันแทน

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

เอเจนต์ส่งต่อข้อความ challenge ให้มนุษย์ เมื่อได้รับอนุมัติ จะเรียก output_pdf อีกครั้งด้วยอาร์กิวเมนต์ชุดเดิมพร้อม _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 ถูกใช้ไป การเขียนไฟล์ดำเนินการ และผลลัพธ์รายงานไฟล์ที่เขียนแล้ว

{
"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.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 เอง ไม่ใช่การตัดสินความสอดคล้อง

  • การเรียกซ้ำต้องใช้อาร์กิวเมนต์ชุดเดิม token การยืนยันผูกกับชื่อเครื่องมือบวกกับ canonical digest ของอาร์กิวเมนต์ที่ออก token นั้นให้ การเรียกซ้ำโดยเปลี่ยนแปลงสิ่งใด แม้แต่การสลับค่า destroy จะไม่ทำให้ token ถูกใช้ไป เซิร์ฟเวอร์จะตอบกลับด้วย challenge ใหม่แทน ใช้อาร์กิวเมนต์ซ้ำให้เหมือนเดิมทุกประการและเพิ่มเพียง _confirmation_token
  • token ใช้ได้ครั้งเดียวและมีกำหนดหมดอายุ challenge ระบุเวลาหมดอายุ (300 วินาที) หลังจากหมดอายุหรือถูกใช้ไปแล้ว การเรียกที่ควบคุมด้วยเกตครั้งถัดไปจะได้ challenge ใหม่ ให้ส่งต่ออันใหม่
  • เอาต์พุตไฟล์จะอยู่ภายในไดเรกทอรีที่อยู่ใน allow-list เซิร์ฟเวอร์จะปฏิเสธ file_path ที่อยู่นอกไดเรกทอรีชั่วคราวที่กำหนดไว้ด้วยข้อความ Output path rejected by security policy รากของ allow-list เริ่มต้นคือ nextpdf-mcp ภายใต้ไดเรกทอรีชั่วคราวของระบบ ผู้ดูแลระบบเปลี่ยนได้ด้วยการตั้งค่า temp_dir ใน nextpdf-mcp.yaml
  • โหมด base64 ไม่ถูกควบคุมด้วยเกต output_pdf ที่ไม่มี file_path จะส่งคืน PDF เป็น base64 ที่ระดับ Review โดยไม่มีผลข้างเคียงต่อระบบไฟล์ ดู กำหนดให้มนุษย์อนุมัติก่อนเขียนไฟล์เอาต์พุต สำหรับขอบเขตดังกล่าวโดยละเอียด
  • challenge คือผลลัพธ์ ไม่ใช่ข้อผิดพลาด ข้อความ challenge มาพร้อม isError: false การอนุมัติที่รออยู่คือการหยุดพักของเวิร์กโฟลว์ อย่าลองใหม่แบบวนซ้ำ และอย่าสร้าง token ปลอมขึ้นมาเด็ดขาด
  • notification ไม่มีการตอบกลับ หลังจาก notifications/initialized อย่าบล็อกเพื่อรอบรรทัดการตอบกลับ

เซสชันนี้ทำงานแบบ in-memory ตลอดทั้งกระบวนการ การเรียกเนื้อหาส่งคืนผลในหลักมิลลิวินาทีในการรันที่บันทึกไว้ และเวลารวมส่วนใหญ่ใช้ไปกับการรับส่งเพื่อการอนุมัติของมนุษย์ ซึ่งเป็นจุดประสงค์ของเกต ที่เก็บข้อมูลเอกสารจะคงเซสชันไว้เป็นเวลา 30 นาทีของการไม่มีกิจกรรมตามค่าเริ่มต้น (เอกสารสูงสุด 50 รายการ) ดังนั้นการอนุมัติที่ล่าช้าจึงไม่ทำให้เอกสารที่สร้างไว้สูญหาย แต่เอกสารที่ถูกทิ้งร้างจะถูกเรียกคืน

  • จัดการ token การยืนยันเสมือนความลับแบบใช้ครั้งเดียว ส่งต่อข้อความ challenge ให้มนุษย์ อย่าบันทึก token ลง log หรือเก็บไว้ถาวร หน้านี้ปกปิด token ที่บันทึกไว้ด้วยเหตุผลดังกล่าวโดยเฉพาะ
  • ร่องรอยการตรวจสอบอยู่บน stderr การทำงานทุกครั้งที่ระดับ Caution ขึ้นไปจะถูกบันทึกการตรวจสอบ (เครื่องมือ ความเสี่ยง อาร์กิวเมนต์ ผลลัพธ์) ผ่าน PSR-3 โดยปกปิดพารามิเตอร์ที่ละเอียดอ่อน ข้อมูลวินิจฉัยจะไม่ปะปนเข้าไปในสตรีมของโปรโตคอลเด็ดขาด
  • allow-list ของ path คือขอบเขตของระบบไฟล์ ชี้ temp_dir ไปยังไดเรกทอรีที่ใช้เฉพาะสำหรับเอาต์พุตของ Connect อย่าขยายให้ครอบคลุมตำแหน่งที่ใช้งานทั่วไป
  • ระดับความเสี่ยงปรับขึ้นได้เท่านั้น การ override ของผู้ดูแลระบบใน nextpdf-mcp.yaml สามารถยกระดับความเสี่ยงของเครื่องมือได้ แต่ไม่สามารถลด output_pdf ให้ต่ำกว่า Approval Required ได้เลย

สูตรนี้ไม่ได้อ้างมาตรฐานเชิงบรรทัดฐานใด ๆ แต่บันทึก MCP stdio transport (JSON-RPC 2.0 เวอร์ชันโปรโตคอล 2025-06-18 ตามที่เจรจากันในการแลกเปลี่ยน initialize ที่บันทึกไว้) และสัญญาด้านความเสี่ยงและการยืนยันของเซิร์ฟเวอร์ ขั้นตอน qpdf --check ข้างต้นยืนยันความสมบูรณ์เชิงโครงสร้างของไฟล์ที่เขียนเท่านั้น ความสอดคล้องกับมาตรฐานตัดสินโดยตัวตรวจสอบอิสระ ไม่ใช่การยืนยันโดยซอฟต์แวร์ที่ผลิตไฟล์