تخطَّ إلى المحتوى
getnextpdf.com

قيادة جلسة مستندات وكيلٍ عبر MCP

هذه جلسة وكيلٍ كاملة واحدة مع خادم ⁨Model Context Protocol⁩ (⁨MCP⁩) الخاص بـ⁨NextPDF Connect⁩، رسالةً برسالة: initialize، وtools/list، وستّة استدعاءات tools/call تبني موجز مشروع من صفحة واحدة، ورحلة الذهاب والإياب ذات التدخّل البشري (⁨HITL⁩) التي تُخضِع كتابة الملف النهائية للبوابة. التُقطت كل رسالة ⁨JSON-RPC⁩ أدناه حرفيًا من عملية bin/nextpdf-mcp حيّة (أدوات فئة النواة فقط)، ثم جرى تنقيحها بطريقتين فقط لا غير: يظهر رمز التأكيد الصالح لمرة واحدة على هيئة confirm_<single-use-hex>، ويُختصَر الدليل المؤقت لنظام الجهاز إلى C:\Temp. المعرّفات والمخططات والمواضع وأعداد البايتات هي بالضبط ما أرسله الخادم.

Terminal window
composer require nextpdf/server

اربط ناقل ⁨stdio⁩ في مضيف ⁨MCP⁩ لديك — مثلًا في ⁨Claude Desktop⁩ (تُشغّل المضيفات الأمر من دليلها الخاص، لذا استخدم مسارًا مطلقًا؛ ولا يحتاج ناقل ⁨stdio⁩ إلى مفتاح ⁨API⁩، خلافًا لناقل ⁨REST⁩):

{
"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 مستندًا في مخزن الخادم في الذاكرة ويُرجع document_id؛ ويستهدف كل استدعاء لاحق ذلك المعرّف. تُنفَّذ أدوات المحتوى (set_font، وadd_text، وadd_table) فورًا عند مستوى المخاطر “حذر” مع تسجيل التدقيق؛ وpreview_layout قراءة “آمنة”؛ أما output_pdf مع file_path فمستواه “موافقة مطلوبة” — ولا يُنفَّذ في الاستدعاء الأول. بل يُرجع الخادم تحدّيًا يحمل رمزًا مميزًا صالحًا لمرة واحدة، فينقل الوكيل التحدّي إلى الإنسان، ولا تُنفَّذ الكتابة إلا بإعادة استدعاءٍ تحمل _confirmation_token. تنتهي صلاحية المستندات المتروكة في المخزن بعد مدّة البقاء المُهيّأة (30 دقيقة افتراضيًا).

تُشغّل استدعاءات الأدوات نفسها محرّك الأدوات عبر ⁨REST⁩ و⁨gRPC⁩ — تتشارك النواقل مُنفِّذًا واحدًا — لذا فإن كل ما هنا عدا تأطير ⁨stdio⁩ ينطبق كذلك. راجع تصيير فاتورة من طرفٍ إلى طرف عبر ⁨REST⁩ للاطلاع على المحرّك نفسه على سطح ⁨HTTP⁩.

الأداةالدور في هذه الجلسةفئة المخاطر
create_pdfفتح المستند، والحصول على document_idحذر
set_fontاختيار وجه العنوان، ثم وجه المتنحذر
add_textسطر العنوان، ثم فقرة المقدمةحذر
add_tableجدول قائمة المهام بالمسؤول وتاريخ الاستحقاقحذر
preview_layoutقراءة حالة التخطيط قبل الإخراجآمن
output_pdf (وضع الملف)كتابة ملف ⁨PDF⁩ — خاضع للبوابةموافقة مطلوبة

سجّلت عملية النشر المُلتقطة هنا 20 أداة (13 من النواة، و6 من ⁨Pro⁩، و1 من ⁨Enterprise⁩ — تظهر الأعداد في استجابة initialize أدناه)؛ تستخدم هذه الجلسة أدوات النواة فقط، فتعمل من دون تغيير على أي تثبيت مفتوح المصدر بحت. الكتالوج المرجعي هو ردّ 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"
}
}
}

يُقرّ العميل بإشعار (لا تحمل الإشعارات id ولا تتلقّى ردًّا):

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

يسرد الردّ الكامل جميع الأدوات المسجّلة العشرين مع مخططات إدخالها الكاملة. وهو معروض هنا مختصرًا إلى الأداتين اللتين تفتحان هذه الجلسة وتُغلقانها — للإدخالات الثمانية عشر المحذوفة الشكل نفسه:

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

تصل كل نتيجة أداة مرتين في رسالة واحدة: كتلة نصّ 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 (موضع المؤشر) المُحدَّث، فيعرف الوكيل دائمًا أين يحطّ العنصر التالي.

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. موافقة الإنسان — إعادة الاستدعاء بالرمز المميز»

ينقل الوكيل نصّ التحدّي إلى الإنسان. وعند الموافقة، يستدعي 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>"
}
}
}

يُستهلَك الرمز المميز، وتُنفَّذ الكتابة، وتُبلّغ النتيجة عن الملف المكتوب:

{
"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⁩ نفسها — لا تحديد للمطابقة.

  • يجب أن تُكرّر إعادة الاستدعاء الوسائط نفسها. يرتبط رمز التأكيد باسم الأداة إضافةً إلى مُلخّص تجزئة موحّد للوسائط التي صدر لأجلها. وإعادة الاستدعاء مع تغيير أي شيء — حتى قلب destroy — لا تستهلك الرمز المميز؛ بل يردّ الخادم بتحدٍّ جديد بدلًا من ذلك. كرّر الوسائط تمامًا وأضِف _confirmation_token فقط.
  • الرمز المميز صالح لمرة واحدة وتنتهي صلاحيته. يذكر التحدّي مدّة الانتهاء (300 ثانية). بعد الانتهاء أو الاستهلاك، يحصل الاستدعاء الخاضع للبوابة التالي على تحدٍّ جديد؛ انقل الجديد.
  • يحطّ إخراج الملف داخل دليل مُدرَج في قائمة السماح. يرفض الخادم أي file_path خارج دليله المؤقت المُهيّأ بالرسالة Output path rejected by security policy. جذر قائمة السماح الافتراضي هو nextpdf-mcp ضمن الدليل المؤقت للنظام؛ ويغيّره المشغّلون عبر الإعداد temp_dir في nextpdf-mcp.yaml.
  • وضع ⁨base64⁩ غير خاضع للبوابة. يُرجع output_pdf دون file_path ملف ⁨PDF⁩ بترميز ⁨base64⁩ عند مستوى “مراجعة”، دون أي أثر جانبي في نظام الملفات — راجع اشتراط موافقة بشرية على إخراج الملفات للاطلاع على ذلك الحدّ بتعمّق.
  • التحدّي نتيجة، لا خطأ. تصل رسالة التحدّي مع 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 أعلاه السلامة البنيوية للملف المكتوب فقط؛ أما المطابقة لمعيار ما فيحدّدها مدقّق مستقل، ولا تؤكّدها البرمجية المُنتِجة.