يكتب وكيل الدعم "escalate SUP-1042" في Claude Code فتبدأ الأداة عملها. ثم تتوقف، لأن أحدًا لم يخبرها بالأولوية، ولا بمجال المنتج، ولا بما إذا كان العملاء محرومين من الوصول. الإعداد الضعيف يخمّن ويستدعي الفريق الخطأ. أما الإعداد الأفضل فيسأل. هذا السؤال، الذي يُرسَل من الخادم إلى الشخص أمام الطرفية، هو ما يفعله MCP Elicitation. يعرض هذا المثال على MCP Elicitation لمكتب دعم العملاء الحلقة كاملة: كود الخادم، ومخطط النموذج، وإعداد Claude Code، وما يجب فعله عندما يقول الوكيل "لا".
كل ما يلي موجّه إلى النسخة 2025-11-25 من Model Context Protocol وإلى TypeScript SDK. الخادم صغير بما يكفي لقراءته في جلسة واحدة، وكل جزء فيه يقابل قاعدة في المواصفات.
ما الذي يفعله MCP Elicitation فعليًا
عادةً يستدعي عميل MCP أداة، ويعمل الخادم، ثم تعود النتيجة. يضيف Elicitation خطوة واحدة في المنتصف. أثناء تشغيل الأداة، يرسل الخادم طلب elicitation/create إلى العميل. يعرض العميل على الشخص مربع حوار، ويجمع الإجابة ويعيدها. ثم تكمل الأداة عملها ببيانات حقيقية بدلًا من التخمين.
هذا يجعل Elicitation أداة من نوع human in the loop. يبقى الخادم مسؤولًا عمّا يحتاجه. ويبقى العميل مسؤولًا عن كيف يظهر السؤال، وأي الخوادم يُسمح لها بالسؤال، وما إذا كان الشخص يملك حق الرفض.
وضع النموذج ووضع URL
تحدد المواصفات وضعين:
وضع النموذج يجمع بيانات منظمة داخل قناة الاتصال نفسها. يرسل الخادم رسالة قصيرة مع requestedSchema، ويعرض العميل نموذجًا مبنيًا عليه.
وضع URL يوجّه الشخص إلى عنوان خارجي لأي شيء حساس، مثل تسجيل الدخول أو الدفع. لا تمرّ البيانات عبر العميل أبدًا. وقد قدّمت نسخة 2025-11-25 هذا الوضع.
صُممت مخططات النماذج لتكون صغيرة عمدًا. فهي كائنات مسطّحة تحتوي على خصائص بدائية فقط:
تُستبعد الكائنات المتداخلة ومصفوفات الكائنات عمدًا، حتى يتمكن أي عميل من رسم النموذج دون تخمين.
ثلاث إجابات ممكنة
يحمل كل رد action:
accept: أرسل الشخص النموذج، وcontent يحتوي على القيم.
decline: قال الشخص "لا" عن قصد.
cancel: أغلق الشخص مربع الحوار دون أن يختار شيئًا.
يجب أن يتعامل خادمك مع الحالات الثلاث كنتائج طبيعية. أغلب أخطاء Elicitation تنشأ من معالجة الحالة الأولى وحدها.
سيناريو دعم العملاء
تخيّل فريق دعم لديه مكتب مساعدة مليء بالتذاكر، ومناوبة صغيرة للطوارئ. يعمل الوكلاء داخل Claude Code، ويمنح خادم MCP اسمه support-desk النموذجَ إجراء كتابة واحدًا: escalate_ticket. يأخذ معرّف التذكرة ويسلّم الحالة إلى المهندسين المختصين.
لماذا يفشل التخمين هنا
يستطيع النموذج قراءة التذكرة واستنتاج الأولوية. وسيصيب في كثير من الأحيان. لكن عندما يخطئ، يصل سؤال عن الفوترة إلى قناة الحوادث، أو يبقى انقطاع في تسجيل الدخول في طابور بطيء طوال الليل. يمكنك توسيع مخطط مدخلات الأداة وتأمل أن يملأ النموذج كل حقل بشكل صحيح، لكن استدعاء الأداة بقيم مختلقة يبدو تمامًا مثل استدعائها بقيم حقيقية.
يُسلّم Elicitation القرار إلى الشخص الذي يملكه. يوفّر النموذج معرّف التذكرة، ويوفّر الإنسان الحكم.
الحقول التي يطلبها الخادم
خمسة حقول تكفي. أكثر من ذلك، ويبدأ الوكلاء في تجاهل مربع الحوار.
الحقل
النوع
سبب وجوده
priority
تعداد أحادي الاختيار (enum)
يوجّه الطلب إلى قائمة المناوبة الصحيحة
area
تعداد أحادي الاختيار (enum)
يحدد الفريق المسؤول
affectedCustomers
integer، من 1 إلى 10000
يفصل المستخدم الواحد عن حادثة واسعة النطاق
customerEmail
string، بصيغة email
يتيح للمهندس المتابعة
outage
boolean
يفتح قناة الحادثة
فقط priority وarea مطلوبان. الحقول الأخرى لها قيم افتراضية، فيستطيع وكيل في عجلة من أمره أن يقبل ويكمل.
💡 اجعل النموذج قصيرًا. كل حقل إضافي سبب يدفع الوكيل إلى الضغط على إلغاء.
بناء خادم الدعم
الخادم ملف TypeScript واحد، ونقل stdio، واعتماديتان صغيرتان.
ضبط type على module يسمح للملف باستخدام await على المستوى الأعلى واستيرادات ES.
أداة التصعيد
احفظ هذا الملف باسم src/server.ts:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({ name: "support-desk", version: "1.0.0" });
const EscalationForm = z.object({
priority: z.enum(["p1", "p2", "p3"]),
area: z.enum(["billing", "login", "api", "mobile-app"]),
affectedCustomers: z.number().int().min(1).max(10000).default(1),
customerEmail: z.string().email().optional(),
outage: z.boolean().default(false),
});
// Stub: swap in your helpdesk API call.
async function createEscalation(ticketId: string, data: z.infer<typeof EscalationForm>) {
return `ESC-${Date.now().toString(36).toUpperCase()}`;
}
const reply = (text: string, isError = false) => ({
content: [{ type: "text" as const, text }],
isError,
});
server.registerTool(
"escalate_ticket",
{
description: "Escalate a support ticket to the on-call team. Asks the agent for missing details.",
inputSchema: { ticketId: z.string().describe("Ticket ID, for example SUP-1042") },
},
async ({ ticketId }) => {
if (!server.server.getClientCapabilities()?.elicitation) {
return reply("This client cannot show forms. Ask the agent for priority and area, then retry.", true);
}
const answer = await server.server.elicitInput({
message: `Ticket ${ticketId} needs a few details before it reaches the on-call team.`,
requestedSchema: {
type: "object",
properties: {
priority: {
type: "string",
title: "Priority",
oneOf: [
{ const: "p1", title: "P1 Service down" },
{ const: "p2", title: "P2 Major feature broken" },
{ const: "p3", title: "P3 Minor issue" },
],
},
area: {
type: "string",
title: "Product area",
enum: ["billing", "login", "api", "mobile-app"],
},
affectedCustomers: {
type: "integer",
title: "Customers affected",
minimum: 1,
maximum: 10000,
default: 1,
},
customerEmail: { type: "string", format: "email", title: "Customer email" },
outage: { type: "boolean", title: "Is this an outage?", default: false },
},
required: ["priority", "area"],
},
});
if (answer.action === "decline") {
return reply(`The agent declined to escalate ${ticketId}. The ticket is unchanged.`);
}
if (answer.action === "cancel") {
return reply(`Escalation of ${ticketId} was cancelled. Nothing was changed.`);
}
const parsed = EscalationForm.safeParse(answer.content);
if (!parsed.success) {
return reply("The form answers were invalid. Ask again.", true);
}
const escalationId = await createEscalation(ticketId, parsed.data);
return reply(`Escalated ${ticketId} as ${parsed.data.priority} in ${parsed.data.area}. Reference ${escalationId}.`);
}
);
await server.connect(new StdioServerTransport());
لاحظ أن mode غير موجود في استدعاء elicitInput. وضع النموذج هو الافتراضي، وهذا يُبقي الطلب مقروءًا للعملاء الأقدم.
تحقق من قدرات العميل أولًا
الأسطر الأولى من المعالج أهم مما تبدو عليه. يعلن العميل الذي يدعم Elicitation عن قدرة elicitation أثناء التهيئة. الكائن elicitation الفارغ يعني وضع النموذج فقط، ولا يجوز للخادم أبدًا أن يرسل وضعًا لم يعلن العميل عنه.
إذا كانت القدرة غير موجودة، يعيد الخادم رسالة خطأ نصية بسيطة بدلًا من أن يتعلق. يقرأ النموذج تلك الرسالة ويسأل الوكيل في الدردشة. الأداة التي تفشل بلباقة أفضل من أداة تتجمد.
💡 في خادم stdio، لا تطبع أبدًا على الإخراج القياسي. أسطر console.log الضالة تُفسد تدفق البروتوكول. أرسل مخرجات التصحيح عبر console.error.
إعداد Claude Code خطوة بخطوة
بعد كتابة الخادم، يحتاج Claude Code إلى معرفة أنه موجود.
تسجيل الخادم
من أي مجلد، أضفه باستخدام CLI. كل ما يأتي بعد الشرطتين المزدوجتين هو الأمر الذي يشغّل الخادم:
claude mcp add support-desk -- npx -y tsx /absolute/path/to/support-desk-mcp/src/server.ts
لمشاركة الإعداد مع زملائك، أضف --scope project. عندها يكتب Claude Code ملف .mcp.json في جذر المستودع:
💡 على Windows الأصلي، غلّف المشغّل: claude mcp add support-desk -- cmd /c npx -y tsx C:\path\to\src\server.ts.
تأكيد الاتصال
فحصان يخبرانك أن الخادم يعمل:
claude --version
claude mcp list
داخل الجلسة، يفتح /mcp لوحة الخادم مع حالة الاتصال. يجب أن ترى support-desk مدرجًا كمتصل.
إذا توقف خادم عن الاتصال بعد تحديث، فراجع سجل التغييرات في Claude Code. تنص ملاحظات الإصدار 2.1.287، التي أضافت روابط URL من الخوادم وفق بروتوكول 2025-11-25، على إضافة "bareElicitationCapability": true إلى إدخال الإعداد الخاص بذلك الخادم عندما لم يعد يتصل.
تشغيل تدفق التصعيد
ابدأ جلسة في مشروعك واكتب طلبًا بسيطًا:
Escalate ticket SUP-1042 using the support-desk tool.
يستدعي Claude الأداة mcp__support-desk__escalate_ticket. تتوقف الأداة مؤقتًا، ويعرض Claude Code النموذج، ويملأ الوكيل الأولوية والمجال وعدد العملاء المتأثرين والبريد الإلكتروني وعلامة الانقطاع.
بعد أن يرسل الوكيل النموذج، تستأنف الأداة وتعيد شيئًا مثل Escalated SUP-1042 as p1 in login. Reference ESC-LQ3F9A2. يمكن للنموذج أن ينقل هذا المرجع مباشرة إلى ردّه.
يوفّر Claude Code أيضًا خطافات Elicitation وElicitationResult، تُطابَق مع اسم الخادم. يمكن لسكربت أن يجيب على نموذج معروف تلقائيًا، أو أن يسجّل كل رد قبل وصوله إلى الخادم. استخدم ذلك في النماذج منخفضة المخاطر ضمن الأتمتة، وأبقِ البشر أمام أي شيء يمس العملاء.
معالجة الرفض والإلغاء والمدخلات الخاطئة
عروض المسار السعيد تخفي الجزء الذي يحدد ما إذا كان الوكلاء سيثقون بالأداة. الناس يغلقون مربعات الحوار، ويغيّرون رأيهم، ويدخلون قيمًا خاطئة.
ما الذي ينبغي أن يُطلقه كل إجراء
الإجراء
ما فعله الشخص
ما ينبغي أن تفعله الأداة
accept
أرسل النموذج
تحقق مرة أخرى، ثم أنشئ التصعيد
decline
قال "لا" عن قصد
لا تمسّ التذكرة، وأبلغ عن ذلك، واعرض مسارًا يدويًا
cancel
أغلق مربع الحوار
لا تغيّر شيئًا، واسمح بإعادة المحاولة لاحقًا
لاحظ استدعاء safeParse في الخادم. ينبغي للعملاء أن يتحققوا من الإجابات مقابل المخطط، وينبغي للخوادم أن تفعل ذلك مرة أخرى. القيم الافتراضية والتنسيقات مجرد تلميحات للواجهة، وليست ضمانات لما يصل من البيانات.
أعد نتيجة isError عند وجود مدخلات خاطئة بدلًا من رمي خطأ. يرى النموذج الرسالة ويستطيع أن يطلب من الوكيل تشغيل الأداة مرة أخرى.
أخطاء تكسر Elicitation
تأتي معظم الإخفاقات من قائمة قصيرة من العادات.
البيانات الحساسة في النماذج
تقول المواصفات بوضوح: يجب ألا يطلب الخوادم كلمات المرور أو رموز API أو بيانات اعتماد الدفع عبر وضع النموذج. تمرّ إجابات النموذج عبر العميل، لذلك قد تنتهي في السجلات وفي النصوص المحفوظة. وأي شيء سري ينتمي إلى وضع URL، حيث يكتبه الشخص في صفحة لا يستطيع العميل قراءتها.
الاسم أو عنوان البريد الإلكتروني مختلف. يجوز للخادم أن يطلبهما، ويستطيع الشخص مراجعتهما ورفضهما.
المخططات المتداخلة
requestedSchema يحتوي على كائن متداخل أو قائمة من الكائنات سيُرفض أو سيُعرض بشكل سيئ. سطّحه. إذا احتجت إلى قائمة بالبنود، فشغّل عدة عمليات Elicitation صغيرة، أو استخدم enum متعدد الاختيار.
عادات أخرى تسبب المشاكل:
معاملة cancel كأنها decline، فيبدو مربع الحوار المغلق كأنه رفض.
الثقة بهوية كُتبت في نموذج. حدّد هوية المستخدمين عبر التفويض، لا عبر حقل نصي.
طلب التفاصيل نفسها مرتين في الجلسة الواحدة.
نسيان أن الخادم البعيد يجب أن يربط حالته بالمستخدم، لا بمعرّف الجلسة فقط.
قائمة فحص قصيرة للاختبار
شغّل هذه الحالات الخمس قبل أن تلمس التذاكر الحقيقية الأداة:
قبول مع القيم الافتراضية فقط، وتأكد أن affectedCustomers يصبح 1.
قبول ببريد إلكتروني فيه خطأ مطبعي، وتأكد أن الخادم يرفضه.
رفض، وتأكد أن التذكرة لم تتغير.
إلغاء بزر Escape، وتأكد أنه لم يُكتب شيء.
الاتصال من عميل لا يدعم Elicitation، وتأكد من ظهور الرسالة النصية الاحتياطية.
يمكنك أيضًا تشغيل الخادم تحت MCP Inspector من مجلد المشروع باستخدام npx @modelcontextprotocol/inspector npx tsx src/server.ts لمراقبة رسائل elicitation/create الخام وهي تمر.
صياغة الردود باستخدام Claude Sonnet 5
بعد أن يعيد التصعيد مرجعًا، ما زال على الوكيل أن يقدّم جوابًا للعميل. يتولى Claude Sonnet 5 على PicassoIA خطوة الصياغة، ويقرأ لقطات الشاشة أيضًا، وهذا يفيد عندما يرفق العميل صورة لخطأ ما.
افتح صفحة Claude Sonnet 5 والصق ملخص التذكرة مع مرجع التصعيد في Prompt.
اضبط System Prompt مرة واحدة: "You write short, calm support replies. Never promise a fix time."
أبقِ Effort على low للمسودات السريعة. ارفعه إلى medium أو high عندما تحتاج التذكرة إلى استدلال حقيقي. وفقًا لصفحة النموذج، يعطّل low التفكير للحصول على أسرع الردود وأرخصها.
خفّض Max Tokens من القيمة الافتراضية 8192 إلى نحو 600 حتى تبقى الردود قصيرة.
أرفق لقطة شاشة في Image إذا أرسلها العميل. القيمة الافتراضية للحقل Max Image Resolution هي 0.5 ميغابكسل، وهي كافية لمربع حوار خطأ.
المعامل
القيمة الافتراضية
نصيحة لردود الدعم
Effort
low
ارفعه للأخطاء المعقدة فقط
Max Tokens
8192
اضبطه قرب 600
System Prompt
فارغ
حدّد النبرة والحدود مرة واحدة
Max Image Resolution
0.5 MP
أبقه كما هو للقطات الشاشة
للاستدلال الأصعب، يُدرج Claude Opus 4.7 ضمن المجموعة نفسها. ابدأ باستخدام Sonnet 5 ولا تنتقل إلى الأعلى إلا عندما لا تصيب المسودة الهدف.
أنشئ صورك الخاصة مع Picasso IA
توثيق الدعم ومقالات مركز المساعدة تُقرأ بشكل أفضل مع صور حقيقية. مشاهد المكتب نفسها التي رأيتها أعلاه، كسماعة رأس على طاولة أو لوحة مشبك ورق تحت ضوء النافذة، تحتاج إلى أمر نصي واحد لإنشائها.
جرّب PicassoIA Image للمحاولة الأولى السريعة، أو Flux 2 Pro عندما تريد نسيجًا أدق. أمر نصي يعمل جيدًا:
A support engineer at a wooden desk, 35mm lens, soft window light from the left, shallow depth of field, Kodak Portra 400 film grain, no text.
اختر نموذجًا، والصق الأمر النصي، وغيّر تفصيلًا واحدًا في كل تشغيل، ثم قارن النتائج. عشر دقائق من التجريب ستعلمك عن صياغة الأوامر النصية أكثر من أي قائمة قواعد. افتح Picasso IA، وأنشئ صورة الرأس الأولى لك، وضعها في مقال الدعم التالي.