قيادة جلسة مستندات وكيلٍ عبر MCP
لمحة سريعة
قسم بعنوان «لمحة سريعة»هذه جلسة وكيلٍ كاملة واحدة مع خادم Model Context Protocol (MCP) الخاص بـNextPDF Connect، رسالةً برسالة: initialize، وtools/list، وستّة استدعاءات tools/call تبني موجز مشروع من صفحة واحدة، ورحلة الذهاب والإياب ذات التدخّل البشري (HITL) التي تُخضِع كتابة الملف النهائية للبوابة. التُقطت كل رسالة JSON-RPC أدناه حرفيًا من عملية bin/nextpdf-mcp حيّة (أدوات فئة النواة فقط)، ثم جرى تنقيحها بطريقتين فقط لا غير: يظهر رمز التأكيد الصالح لمرة واحدة على هيئة confirm_<single-use-hex>، ويُختصَر الدليل المؤقت لنظام الجهاز إلى C:\Temp. المعرّفات والمخططات والمواضع وأعداد البايتات هي بالضبط ما أرسله الخادم.
التثبيت
قسم بعنوان «التثبيت»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.
سطح API
قسم بعنوان «سطح API»| الأداة | الدور في هذه الجلسة | فئة المخاطر |
|---|---|---|
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.
الجلسة، رسالةً برسالة
قسم بعنوان «الجلسة، رسالةً برسالة»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"}يسرد الردّ الكامل جميع الأدوات المسجّلة العشرين مع مخططات إدخالها الكاملة. وهو معروض هنا مختصرًا إلى الأداتين اللتين تفتحان هذه الجلسة وتُغلقانها — للإدخالات الثمانية عشر المحذوفة الشكل نفسه:
{ "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" } }}تصل كل نتيجة أداة مرتين في رسالة واحدة: كتلة نصّ 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 استدعاء “آمن” للقراءة فقط — يتحقّق الوكيل حَسَن السلوك ممّا بناه قبل أن يطلب من إنسان الموافقة على الكتابة:
{ "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.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 ثانية). بعد الانتهاء أو الاستهلاك، يحصل الاستدعاء الخاضع للبوابة التالي على تحدٍّ جديد؛ انقل الجديد.
- يحطّ إخراج الملف داخل دليل مُدرَج في قائمة السماح. يرفض الخادم
أي
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 أعلاه السلامة البنيوية للملف المكتوب فقط؛ أما المطابقة لمعيار ما فيحدّدها مدقّق مستقل، ولا تؤكّدها البرمجية المُنتِجة.
اطلع أيضًا
قسم بعنوان «اطلع أيضًا»- اشتراط موافقة بشرية على إخراج الملفات — بوابة التأكيد بتعمّق، بما في ذلك مسار الرفض.
- تصيير فاتورة من طرفٍ إلى طرف عبر REST — محرّك الأدوات نفسه عبر HTTP، مع نصّ تخاطب الشبكة المُلتقَط.
- أنشئ أول ملف PDF لك — أصغر جلسة Connect.
- اصطلاحات وصفات Connect — العقد الذي تتبعه كل وصفة Connect.
- فئات مخاطر HITL — سلّم المخاطر المعتمد وحلّ السياسة.
- كتالوج الأدوات — الكتالوج المرجعي للأدوات.