الأدوات والموارد والتعليمات
أنماط الأدوات المتقدمة
الأنماط التالية تجيب كلها عن سؤال واحد بصيغ مختلفة: كم من العمل ينبغي أن يجري داخل نداء أداة واحد، وكم منه يُترك للنموذج ينظّمه بنفسه؟
تركيب الأدوات
الأداتان اللتان تُستخدمان معاً دائماً يكون دمجهما في أداة واحدة أفضل غالباً. فكل رحلة ذهاب وإياب تكلّف دوراً، وكل دور فرصة ليفقد النموذج خيط الموضوع:
أداتان دقيقتان مقابل أداة واحدة مركّبة
التركيب ليس مجانياً. فالأداة المركّبة تخفي خطواتها الوسيطة، وحين يخرج الملخص خاطئاً لن تعرف أالبحث أخفق أم التلخيص. القاعدة العملية: ركّب حين لا تكون النتيجة الوسيطة مفيدة بذاتها أبداً، وافصل حين يكون من المشروع أن يتوقف النموذج في منتصف الطريق.
@mcp.tool()
async def search_and_summarize(query: str, max_results: int = 5) -> str:
"""البحث في المستندات وإرجاع ملخص لأفضل النتائج المطابقة."""
results = await search_documents(query)
return await generate_summary(results[:max_results])
القيمة الافتراضية موجودة في توقيع الدالة، فتظهر في المخطط المُولَّد وتُطبَّق حين يُغفل النموذج
الوسيط. أما كتابة القيمة الافتراضية مرتين — مرة في المخطط ومرة في استدعاء .get(..., 5) —
فهي الطريقة التي تفترق بها النسختان.
الإبلاغ عن التقدم في العمليات الطويلة
استدعاء الأداة يُرجع نتيجة واحدة فقط. لا يمكنك استخدام yield لإخراج نتائج جزئية من أداة —
فالمولِّد غير المتزامن ليس قيمة إرجاع صالحة، والاستدعاء يفشل عند التحقق. لكن ما يمكنك فعله
هو إرسال إشعارات تقدم بينما الاستدعاء الواحد ما يزال مفتوحاً:
from mcp.server.mcpserver import Context
@mcp.tool()
async def analyze_large_dataset(data: str, ctx: Context) -> str:
"""تحليل مجموعة بيانات على دفعات."""
batches = await plan_batches(data)
results = []
for i, batch in enumerate(batches, start=1):
results.append(await process(batch))
await ctx.report_progress(
progress=i,
total=len(batches),
message=f"تمت معالجة الدفعة {i} من {len(batches)}",
)
return json.dumps(results)
تفصيلان يستحقان الحفظ:
ctxليس جزءاً من مخطط أداتك. يتعرف الـ SDK على تعليق النوعContextويوفّره بنفسه، فيظل النموذج يرى أداة ذات وسيط واحد. تحصل على الجلسة دون أن تدفع ثمنها في الواجهة التي يقرأها النموذج.- التقدم إرشادي. لا شيء ينتظره ولا شيء يعيد إرساله، وللمضيف أن يتجاهله تماماً. أبلغ عن التقدم لإبقاء إنسان على اطلاع، ولا تجعل صحة النتيجة تتوقف أبداً على وصول إشعار.
الإجراءات القابلة للتأكيد
للعمليات الخطرة، اطلب التأكيد:
@mcp.tool()
async def delete_all_files(directory: str, confirm: bool) -> str:
"""حذف كل ملف في مجلد. إجراء مدمر — لا يمكن التراجع عنه.
اضبط confirm=true فقط بعد أن يوافق المستخدم صراحةً على هذا المجلد بعينه.
"""
if not confirm:
return "الإجراء غير مؤكد. اطلب من المستخدم تأكيد المجلد ثم أعد المحاولة بـ confirm=true."
count = await delete_files(directory)
return f"تم حذف {count} ملفاً من {directory}"
لاحظ أين وُضِعت التعليمات: في نص التوثيق، لأنه ما يقرأه النموذج قبل أن يقرر. وعلامة
confirm التي يستطيع النموذج ضبطها بنفسه مطبٌّ للسرعة لا ضمانة؛ فالحماية الحقيقية هي أن
يسأل المضيف إنساناً، وهذا ما وُجدت من أجله تعليقات الأدوات التوضيحية وآلية الموافقة في
المضيف نفسه. اعتبر هذا النمط وسيلة لجعل الخطوة المدمرة مرئية، لا وسيلة للتفويض.
تبعيات الأدوات
بناء أدوات تعتمد على أدوات أخرى:
class ToolRegistry:
def __init__(self):
self.tools = {}
def register(self, name, handler):
self.tools[name] = handler
async def call(self, name, arguments):
return await self.tools[name](arguments)
registry = ToolRegistry()
async def get_user_handler(args):
return await db.get_user(args["id"])
async def get_user_orders_handler(args):
# يعتمد على get_user
user = await registry.call("get_user", {"id": args["user_id"]})
return await db.get_orders(user["id"])
registry.register("get_user", get_user_handler)
registry.register("get_user_orders", get_user_orders_handler)
في القسم التالي، سنستكشف أنماط الموارد المتقدمة. :::
سجّل الدخول للتقييم