تخطي إلى المحتوى
Integration

OpenAPI إلى خادم MCP: 150 نقطة نهاية و49 أداة للذكاء الاصطناعي

| 9 min read

كيف قمنا بتحويل مواصفات OpenAPI إلى خادم MCP منسق يحتوي على 49 أداة. تحويل المخطط وأوصاف الأدوات والتعليقات التوضيحية ونقل HTTP عديم الحالة.

Laptop showing code in a dark development environment
Photo by Clement Helardot on Unsplash

تحتوي واجهة برمجة تطبيقات REST الخاصة بـ Botoi على أكثر من 150 نقطة نهاية. عندما قمنا ببناء خادم MCP، قمنا بتسجيل 49 منها كأدوات. ليس لأن الباقي لا يعمل. لأن إعطاء نموذج الذكاء الاصطناعي 150 أداة يشبه إعطاء شخص ما قائمة مكونة من 200 صفحة؛ سيختارون شيئًا ما، لكنه لن يكون الشيء الصحيح.

يستعرض هذا المنشور العملية الكاملة: تنظيم قائمة الأدوات، وتحويل مخططات OpenAPI إلى كائنات Zod، وكتابة الأوصاف وتحليل نماذج الذكاء الاصطناعي جيدًا، وإضافة التعليقات التوضيحية لـ MCP، وتشغيل الأمر برمته كعامل Cloudflare عديم الحالة. إذا كنت تحتفظ بواجهة برمجة التطبيقات (API) وترغب في إنشاء خادم MCP منها، فهذا هو دليل التشغيل.

لماذا 49 أداة، وليس 150

يتم إجراء تسلسل لكل أداة تقوم بتسجيلها في خادم MCP في نافذة سياق النموذج. يستهلك اسم الأداة ووصفها ومخطط الإدخال الكامل الرموز المميزة. يمكن لبيان مكون من 150 أداة أن يحرق أكثر من 30000 رمز مميز قبل أن يكتب المستخدم كلمة واحدة.

وهذا يخلق مشكلتين:

  • تم ترك عدد أقل من الرموز للمحادثة نفسها
  • يختار النموذج الأداة الخاطئة في كثير من الأحيان عندما تكون القائمة طويلة

اختبرنا هذا. مع تسجيل جميع نقاط النهاية التي يزيد عددها عن 150 نقطة، اختار كلود الأداة الصحيحة في المحاولة الأولى في حوالي 72% من الحالات. ومع وجود 49 أداة منسقة، قفز هذا العدد إلى 94%. القائمة الأصغر حجمًا والمركزة جعلت النموذج أفضل في وظيفته.

كانت معايير التنظيم بسيطة:

  • هل يحتاج وكيل الذكاء الاصطناعي إلى هذه المحادثة في منتصفها؟ (بحث DNS: نعم. إنشاء PDF: نادرًا.)
  • هل تقوم الأداة بإرجاع البيانات المنظمة التي يمكن للنموذج أن يفكر فيها؟ (JSON: نعم. الصورة الثنائية: لا.)
  • هل يستطيع النموذج ملء المعلمات المطلوبة من اللغة الطبيعية؟ (اسم المجال: نعم. كائنات التكوين المتداخلة المعقدة: لا.)

هيكل بيان الأداة

تقوم كل أداة منسقة بتعيين اسم أداة MCP لمسار API وطريقة HTTP والوصف والتعليقات التوضيحية. إليك واجهة TypeScript:

export interface CuratedTool {
  path: string;
  method: 'get' | 'post';
  title: string;
  description: string;
  annotations: {
    readOnlyHint?: boolean;
    destructiveHint?: boolean;
    idempotentHint?: boolean;
    openWorldHint?: boolean;
  };
}

وإليك الشكل الذي يبدو عليه الإدخالان عمليًا:

// curated-tools.ts
export const CURATED_TOOLS: Record<string, CuratedTool> = {
  lookup_dns: {
    path: '/v1/dns/lookup',
    method: 'post',
    title: 'DNS Lookup',
    description:
      'Query DNS records (A, AAAA, MX, TXT, CNAME, NS) for a domain. ' +
      'Use when you need to check DNS configuration or troubleshoot domain resolution.',
    annotations: { readOnlyHint: true, openWorldHint: true },
  },
  dev_hash: {
    path: '/v1/hash',
    method: 'post',
    title: 'Hash Text',
    description:
      'Generate a hash (MD5, SHA-1, SHA-256, SHA-512) of input text. ' +
      'Use for checksums, data integrity, or fingerprinting.',
    annotations: { readOnlyHint: true },
  },
  // ... 47 more tools
};

ال path و method تشير الحقول إلى نقطة نهاية REST الحالية. ال description يخبر النموذج متى يستخدم الأداة. ال annotations أخبر النموذج كيف تتصرف الأداة.

تحويل مخططات OpenAPI إلى Zod

يتوقع MCP SDK مخططات إدخال الأداة ككائنات Zod. تحتوي واجهة برمجة التطبيقات (API) الخاصة بنا بالفعل على مواصفات OpenAPI 3.1 مع تعريفات نص الطلب الكاملة لكل نقطة نهاية. يقرأ منشئ المخطط هذه التعريفات وينشئ أنواع Zod عند بدء تشغيل الخادم.

تقوم وظيفة التحويل الأساسية بتعيين كل نوع خاصية OpenAPI إلى ما يعادله من Zod:

// schema-builder.ts
import { z } from 'zod';
import { paths } from '../../openapi-paths';

function mapPropertyToZod(
  prop: OpenApiProperty,
  isRequired: boolean
): z.ZodTypeAny {
  let schema: z.ZodTypeAny;

  if (prop.enum && prop.enum.length > 0) {
    schema = z.enum(prop.enum as [string, ...string[]]);
  } else {
    switch (prop.type) {
      case 'number':
      case 'integer':
        schema = z.number();
        break;
      case 'boolean':
        schema = z.boolean();
        break;
      case 'array':
        schema = z.array(z.string());
        break;
      case 'object':
        schema = z.record(z.unknown());
        break;
      default:
        schema = z.string();
        break;
    }
  }

  if (prop.description) {
    schema = schema.describe(prop.description);
  }
  if (prop.default !== undefined) {
    schema = schema.default(prop.default);
  }
  if (!isRequired) {
    schema = schema.optional();
  }

  return schema;
}

القرارات الرئيسية في هذه الوظيفة:

  • enum تصبح القيم z.enum()، مما يمنح النموذج مجموعة ثابتة من الخيارات الصالحة
  • الحقول المطلوبة تظل إلزامية؛ الحصول على الحقول الاختيارية .optional()
  • واجهة برمجة التطبيقات المفتوحة description ينقل عبر .describe()، والذي يتضمنه MCP SDK في بيان الأداة
  • تنتشر القيم الافتراضية من خلال .default()

ال buildZodSchema تتعامل الدالة مع نقطتي النهاية POST (نص الطلب) وGET (معلمات الاستعلام):

export function buildZodSchema(
  apiPath: string,
  method: 'get' | 'post'
): Record<string, z.ZodTypeAny> {
  const operation = getOperation(apiPath, method);
  if (!operation) return {};

  // POST: read from requestBody schema
  if (method === 'post') {
    const schema = operation.requestBody
      ?.content?.['application/json']?.schema;
    if (!schema?.properties) return {};

    const required = new Set(schema.required ?? []);
    const result: Record<string, z.ZodTypeAny> = {};

    for (const [key, prop] of Object.entries(schema.properties)) {
      result[key] = mapPropertyToZod(prop, required.has(key));
    }
    return result;
  }

  // GET: read from query parameters
  const params = operation.parameters;
  if (!params || params.length === 0) return {};

  const result: Record<string, z.ZodTypeAny> = {};
  for (const param of params) {
    if (param.in !== 'query') continue;
    const prop: OpenApiProperty = {
      type: param.schema?.type ?? 'string',
      description: param.description ?? param.schema?.description,
      default: param.schema?.default,
      enum: param.schema?.enum,
    };
    result[param.name] = mapPropertyToZod(prop, param.required === true);
  }
  return result;
}

تعمل هذه الوظيفة مرة واحدة لكل أداة أثناء إنشاء الخادم. يعود شقة Record<string, z.ZodTypeAny> أن MCP SDK يتم إجراء تسلسل له في مخطط JSON لبيان الأداة.

أوصاف أداة الكتابة يتم تحليل نماذج الذكاء الاصطناعي بشكل جيد

يعد وصف الأداة هو الحقل الأكثر أهمية لاختيار الأداة بشكل صحيح. تقرأها النماذج لتقرر ما إذا كانت الأداة تتوافق مع نية المستخدم. تؤدي الأوصاف الغامضة إلى اختيارات خاطئة للأدوات.

استقرينا على نمط من جملتين:

  1. الجملة الأولى: ما تفعله الأداة، ابتداءً من الفعل. قم بتضمين أنواع البيانات أو التنسيقات المحددة التي يتعامل معها.
  2. الجملة الثانية: متى تستخدمه، بدءًا من "استخدم متى". وهذا يعطي النموذج شرط الزناد.

قارن بين هذين الوصفين لنفس أداة بحث DNS:

إصدار وصف مشكلة
سيء "أداة DNS للبحث عن الأشياء" لا توجد أنواع سجلات مدرجة، ولا يوجد شرط تشغيل، غامض
جيد "الاستعلام عن سجلات DNS (A، AAAA، MX، TXT، CNAME، NS) لمجال ما. استخدمها عندما تحتاج إلى التحقق من تكوين DNS أو استكشاف أخطاء دقة المجال وإصلاحها." لا أحد

يخبر الإصدار الجيد النموذج بأنواع السجلات الدقيقة التي يمكنه الاستعلام عنها (حتى يعرف أن هذه الأداة تتعامل مع عمليات بحث MX) والمواقف التي يجب أن تؤدي إلى تشغيلها (فحوصات تكوين DNS، واستكشاف الأخطاء وإصلاحها). يطابق النموذج نية المستخدم مقابل هذه الكلمات الرئيسية.

التعليقات التوضيحية لـ MCP: إخبار النماذج بكيفية تصرف الأدوات

التعليقات التوضيحية هي علامات بيانات تعريفية على كل أداة. أنها لا تؤثر على التنفيذ. يخبرون النموذج بنوع الآثار الجانبية المتوقعة.

// Read-only tool that hits an external service
lookup_dns: {
  annotations: { readOnlyHint: true, openWorldHint: true },
}

// Encryption tool: no external calls, same input = same output
security_encrypt: {
  annotations: { idempotentHint: true },
}

الشروح الأربعة وما تدل عليه:

تعليق توضيحي إشارة مثال
readOnlyHint تقرأ هذه الأداة البيانات ولكنها لا تعدل أي شيء أبدًا بحث DNS، WHOIS، فحص SSL
destructiveHint تقوم هذه الأداة بحذف البيانات أو الكتابة فوقها حذف البريد الوارد عبر Webhook (ليس في مجموعتنا المنسقة)
idempotentHint يؤدي استدعاء هذه الأداة مرتين بنفس الإدخال إلى نفس النتيجة تشفير AES، فك تشفير AES
openWorldHint تقوم هذه الأداة بتقديم طلبات الشبكة الخارجية بحث IP، وبيانات تعريف URL، والكشف الفني

من بين 49 أداة لدينا، 44 منها محمولة readOnlyHint: true. تحمل أدوات البحث الـ 12 أيضًا openWorldHint: true لأنهم يتصلون بخوادم DNS الخارجية، أو سجلات WHOIS، أو يجلبون صفحات الويب المباشرة. تحمل أدوات التشفير/فك التشفير idempotentHint: true لأنها تحولات حتمية.

لا تحمل أي من أدواتنا المنسقة destructiveHint. وكان ذلك اختيارا متعمدا. لقد استبعدنا أدوات مثل حذف البريد الوارد على الويب وحذف اللصق من المجموعة المنسقة لأن نماذج الذكاء الاصطناعي لا ينبغي أن تحذف بيانات المستخدم بدون حواجز حماية قوية.

أدوات التسجيل على خادم MCP

تربط حلقة التسجيل كل شيء معًا. فهو يتكرر عبر قائمة الأدوات المنسقة، ويبني مخطط Zod من مواصفات OpenAPI، ويسجل كل أداة مع وصفها وتعليقاتها التوضيحية:

// server.ts
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { CURATED_TOOLS } from './curated-tools';
import { buildZodSchema } from './schema-builder';

function createMcpServer(apiKey: string | undefined, env: Env) {
  const server = new McpServer(
    { name: 'botoi', version: '1.0.0' },
    { jsonSchemaValidator: new CfWorkerJsonSchemaValidator() }
  );

  for (const [toolName, tool] of Object.entries(CURATED_TOOLS)) {
    const zodSchema = buildZodSchema(tool.path, tool.method);

    server.registerTool(toolName, {
      title: tool.title,
      description: tool.description,
      inputSchema: zodSchema,
      annotations: tool.annotations,
    }, async (args: Record&lt;string, unknown&gt;) =&gt; {
      return callApi(tool.path, tool.method, args, apiKey, env);
    });
  }

  return server;
}

عندما يستدعي النموذج أداة، تتلقى وظيفة المعالج الوسائط التي تم تحليلها وتعيد توجيهها إلى مسار واجهة برمجة التطبيقات الداخلية. ال callApi تنشئ الدالة طلب HTTP داخليًا وترجع الاستجابة كمحتوى بتنسيق MCP:

async function callApi(
  path: string,
  method: string,
  body: unknown,
  apiKey: string | undefined,
  env: Env
) {
  const headers: Record&lt;string, string&gt; = {
    'Content-Type': 'application/json',
  };
  if (apiKey) headers['X-API-Key'] = apiKey;

  const req = new Request(\`http://internal\$\{path}\`, {
    method: method.toUpperCase(),
    headers,
    body: method === 'post' ? JSON.stringify(body) : undefined,
  });

  const res = appFetcher
    ? await appFetcher(req, env)
    : await fetch(req);

  const json = await res.json();
  if (!json.success) {
    return {
      content: [{ type: 'text', text: JSON.stringify(json.error, null, 2) }],
      isError: true,
    };
  }
  return {
    content: [{ type: 'text', text: JSON.stringify(json.data, null, 2) }],
  };
}

ال appFetcher يتيح النمط لخادم MCP استدعاء مسارات API من خلال مرجع وظيفة داخلي بدلاً من تقديم طلب HTTP خارجي. وهذا يتجنب رحلات الشبكة ذهابًا وإيابًا. يعمل معالج MCP ومسارات واجهة برمجة التطبيقات في نفس Cloudflare Worker، لذا فإن التوجيه الداخلي هو استدعاء دالة، وليس قفزة HTTP.

نقل HTTP عديم الحالة على Cloudflare Workers

يدعم MCP طريقتي نقل: stdio (للعمليات المحلية) وHTTP القابل للتدفق (للخوادم البعيدة). لقد اخترنا Streamable HTTP لأن الخادم يعمل على Cloudflare Workers، وهو ما لا يدعم العمليات طويلة الأمد.

// Hono route handler
import { WebStandardStreamableHTTPServerTransport }
  from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';

app.all('/mcp', async (c) =&gt; {
  const apiKey =
    c.req.header('X-API-Key') ||
    c.req.header('Authorization')?.replace('Bearer ', '');

  const server = createMcpServer(apiKey, c.env);
  const transport = new WebStandardStreamableHTTPServerTransport();
  await server.connect(transport);
  return transport.handleRequest(c.req.raw);
});

كل طلب يخلق جديدة McpServer مثال. لا توجد حالة جلسة تستمر بين الطلبات. يعد هذا أمرًا جيدًا لأن كل استدعاء للأداة مستقل بذاته؛ يرسل النموذج اسم الأداة والوسائط، ويعيد الخادم النتيجة. لا توجد معاملات متعددة الخطوات.

يتميز التصميم عديم الجنسية بثلاث مزايا:

  • لا حاجة إلى تخزين الجلسة (لا يوجد Redis، ولا KV، ولا قاعدة بيانات)
  • يتغير المقياس إلى الصفر عندما يكون خاملاً، ويتغير أفقيًا تحت الحمل
  • يتم النشر في أكثر من 300 موقع Cloudflare Edge بدون تكوين

تتم معالجة مفتاح API في طبقة MCP. يرسل العميل المفتاح عبر X-API-Key أو Authorization: Bearer header. يستخرجه مسار MCP ويمرره إلى استدعاء API الداخلي. لا توجد برامج وسيطة مصادقة منفصلة على مسار MCP نفسه.

قواعد اللعبة لواجهة برمجة التطبيقات (API) الخاصة بك

إذا كانت لديك مواصفات OpenAPI وترغب في إنشاء خادم MCP، فإليك النسخة المختصرة:

  1. تنظيم قائمة الأدوات الخاصة بك. اختر نقاط النهاية من 20 إلى 80 التي تعرض بيانات منظمة وتقبل المدخلات البسيطة. تخطي نقاط النهاية التي تُرجع بيانات ثنائية، أو تتطلب تحميلات للملفات، أو تحتوي على مخططات إدخال متداخلة بعمق.
  2. كتابة محول المخطط. قم بتعيين أنواع خصائص OpenAPI الخاصة بك إلى Zod. قم بترحيل الأوصاف والافتراضيات وقيم التعداد. التعامل مع أنماط نص الطلب (POST) ومعلمات الاستعلام (GET).
  3. اكتب وصفًا من جملتين. الجملة الأولى: ما تفعله الأداة، ابتداءً بفعل. الجملة الثانية: "استخدم متى" + شرط التشغيل. كن محددًا بشأن أنواع البيانات وتنسيقاتها.
  4. إضافة التعليقات التوضيحية. وضع علامة على الأدوات للقراءة فقط. أدوات العلم التي تجري مكالمات الشبكة الخارجية. تحديد العمليات العاجزة. استبعاد الأدوات المدمرة ما لم يكن لديك تدفقات تأكيد.
  5. اختر وسائل النقل الخاصة بك. استخدم HTTP القابل للتدفق للخوادم البعيدة، وstdio لأدوات CLI المحلية. يوفر MCP SDK كليهما.
  6. توجيه مكالمات الأداة إلى واجهة برمجة التطبيقات (API) الموجودة لديك. لا تعيد كتابة منطق العمل. اتصل بالطرق الخاصة بك داخليًا. خادم MCP عبارة عن طبقة محول رقيقة.

خادم Botoi's MCP هو 4 ملفات: curated-tools.ts (49 تعريفًا للأداة)، schema-builder.ts (محول OpenAPI إلى Zod)، server.ts (التسجيل والتوجيه)، و tools.ts (نقطة النهاية العامة للبيان). يضيف الأمر برمته حوالي 400 سطر من TypeScript إلى واجهة برمجة التطبيقات الموجودة.

جربه

خادم Botoi MCP موجود حاليًا https://api.botoi.com/mcp. قم بتوصيله بـ Claude Desktop، أو Claude Code، أو Cursor، أو VS Code في أقل من دقيقة. انظر مستندات إعداد MCP لمقتطفات التكوين لكل عميل مدعوم.

تصفح بيان الأداة الكاملة لرؤية جميع تعريفات الأدوات الـ 49 مع مخططاتها وتعليقاتها التوضيحية. ال مستندات API قم بتغطية المجموعة الكاملة المكونة من أكثر من 150 نقطة نهاية REST خلف خادم MCP.

FAQ

كيف يمكنك إنشاء خادم MCP من مواصفات OpenAPI؟
قم بتحليل تعريفات مسار OpenAPI، واستخرج مخطط نص الطلب (لـ POST) أو معلمات الاستعلام (لـ GET)، وقم بتحويل كل خاصية إلى نوع Zod، ثم قم بتسجيل كل أداة على مثيل McpServer باستخدام مخطط Zod كمخطط إدخال. يتعامل MCP SDK مع نقل JSON-RPC واكتشاف الأدوات.
لماذا لا تعرض جميع نقاط نهاية واجهة برمجة التطبيقات (API) كأدوات MCP؟
نماذج الذكاء الاصطناعي لها حد لنافذة السياق. يستهلك كل تعريف أداة الرموز المميزة. يمكن للبيان المكون من 150 أداة أن يأكل أكثر من 30000 رمزًا قبل بدء المحادثة. يؤدي تنظيم ما يصل إلى 49 أداة عالية القيمة إلى إبقاء البيان أقل من 8000 رمزًا مميزًا وتحسين دقة اختيار الأداة.
ما هي التعليقات التوضيحية لأداة MCP وما سبب أهميتها؟
التعليقات التوضيحية هي تلميحات بيانات التعريف مثل readOnlyHint وdestructiveHint وidempotentHint وopenWorldHint. فهي تخبر نماذج الذكاء الاصطناعي ما إذا كانت الأداة تقرأ البيانات أو تكتبها، وما إذا كانت تتصل بخدمات خارجية، وما إذا كانت إعادة المحاولة آمنة. تستخدم النماذج هذه التلميحات لتخطيط سير العمل متعدد الخطوات وتجنب الإجراءات المدمرة دون تأكيد.
هل يمكن تشغيل خادم MCP على Cloudflare Workers؟
نعم. استخدم WebStandardStreamableHTTPServerTransport من MCP SDK. إنه يعمل مع أي وقت تشغيل يدعم واجهة برمجة تطبيقات الطلب/الاستجابة لمعايير الويب. كل من Cloudflare Workers وDeno Deploy وVercel Edge Functions كلها مؤهلة. يقوم كل طلب بإنشاء مثيل McpServer جديد، لذلك ليست هناك حاجة إلى حالة جلسة العمل.
كيف يمكنني كتابة أوصاف أداة MCP لنماذج الذكاء الاصطناعي؟
ابدأ بفعل. اذكر ما تفعله الأداة في جملة واحدة. أضف جملة ثانية تبدأ بـ "استخدم متى" والتي تصف حالة التشغيل. تخطي تفاصيل التنفيذ. مثال: "الاستعلام عن سجلات DNS (A، AAAA، MX، TXT، CNAME، NS) لمجال ما. استخدمها عندما تحتاج إلى التحقق من تكوين DNS أو استكشاف أخطاء دقة المجال وإصلاحها."

ابدأ البناء مع botoi

أكثر من 150 نقطة نهاية API للبحث ومعالجة النصوص وتوليد الصور وأدوات المطورين. باقة مجانية، بدون بطاقة ائتمان.