كيف تبني وتطلق خادم MCP مخصص في 10 دقائق

💡هل ترغب في إنشاء سير عمل خاص بك باستخدام الذكاء الاصطناعي بدون كود؟ يمكنك بسهولة إنشاء سير عمل باستخدام Anakin AI دون الحاجة لأي معرفة بالبرمجة. اتصل بواجهات برمجة التطبيقات للنماذج اللغوية الكبيرة مثل: GPT-4، Claude 3.5 Sonnet، Uncensored Dolphin-Mixtral، Stable Diffusion، DALLE، Web Scraping.... في سير عمل واحد!

Build APIs Faster & Together in Apidog

كيف تبني وتطلق خادم MCP مخصص في 10 دقائق

Start for free
Inhalte
💡
هل ترغب في إنشاء سير عمل خاص بك باستخدام الذكاء الاصطناعي بدون كود؟

يمكنك بسهولة إنشاء سير عمل باستخدام Anakin AI دون الحاجة لأي معرفة بالبرمجة. اتصل بواجهات برمجة التطبيقات للنماذج اللغوية الكبيرة مثل: GPT-4، Claude 3.5 Sonnet، Uncensored Dolphin-Mixtral، Stable Diffusion، DALLE، Web Scraping.... في سير عمل واحد!

انسَ الترميز المعقد، قم بأتمتة عملك اليومي باستخدام Anakin AI!

لفترة محدودة، يمكنك أيضًا استخدام Google Gemini 1.5 وStable Diffusion مجانًا!
قم ببناء سير عمل ذكاء اصطناعي بسهولة باستخدام Anakin AI!
قم ببناء سير عمل ذكاء اصطناعي بسهولة باستخدام Anakin AI

المقدمة

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

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

هل 10 دقائق كافية؟ بالنسبة لنظام جاهز للإنتاج، بالتأكيد لا. ولكن لنموذج عمل يعمل يقوم بعرض المفاهيم الرئيسية؟ بالتأكيد. دعونا نبدأ!

المتطلبات

قبل أن نبدأ، ستحتاج إلى:

  • تثبيت Node.js (الإصدار 16+) على نظامك
  • معرفة أساسية بـ JavaScript/TypeScript
  • الإلمام بـ Express.js أو إطار عمل ويب مشابه
  • محرر كود (يوصى بـ VS Code)
  • الوصول إلى الطرفية/سطر الأوامر
  • مدير حزم npm أو yarn

الخطوة 1: إعداد مشروعك (2 دقائق)

أولاً، دعنا ننشئ دليلًا جديدًا ونقوم بتهيئة مشروعنا:

mkdir mcp-server
cd mcp-server
npm init -y

الآن، قم بتثبيت المكتبات الضرورية:

npm install express cors typescript ts-node @types/node @types/express @types/cors
npm install --save-dev nodemon

أنشئ ملف تكوين TypeScript:

npx tsc --init

قم بتحرير ملف tsconfig.json الذي تم إنشاؤه لتضمين هذه الإعدادات الأساسية:

{
  "compilerOptions": {
    "target": "es2018",
    "module": "commonjs",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true
  },
  "include": ["src/**/*"]
}

قم بتحديث قسم السكربتات في package.json:

"scripts": {
  "start": "node dist/index.js",
  "dev": "nodemon --exec ts-node src/index.ts",
  "build": "tsc"
}

الخطوة 2: إنشاء الخادم الأساسي (3 دقائق)

قم بإنشاء دليل المصدر وملف الخادم الرئيسي:

mkdir -p src/handlers
touch src/index.ts
touch src/handlers/messageHandler.ts
touch src/types.ts

دعنا نحدد الأنواع الخاصة بنا أولاً في src/types.ts:

// هيكل الرسالة الأساسية
export interface Content {
  type: string;
  text?: string;
}

export interface Message {
  role: "user" | "assistant" | "system";
  content: Content[] | string;
}

// هياكل الطلب والاستجابة
export interface ModelRequest {
  messages: Message[];
  max_tokens?: number;
  temperature?: number;
  stream?: boolean;
}

export interface ModelResponse {
  message: Message;
}

// واجهات استدعاء الأدوات
export interface Tool {
  name: string;
  description: string;
  input_schema: {
    type: string;
    properties: Record;
    required?: string[];
  };
}

export interface ToolCall {
  type: "tool_call";
  id: string;
  name: string;
  input: Record;
}

export interface ToolResult {
  type: "tool_result";
  tool_call_id: string;
  content: string;
}

الآن، نفذ الخادم الأساسي في src/index.ts:

import express from 'express';
import cors from 'cors';
import { handleMessageRequest } from './handlers/messageHandler';

const app = express();
const PORT = process.env.PORT || 3000;

// البرمجيات الوسيطة
app.use(cors());
app.use(express.json({ limit: '10mb' }));

// نقطة النهاية الرئيسية لمعالجة الرسائل
app.post('/v1/messages', handleMessageRequest);

// نقطة النهاية لفحص الحالة
app.get('/health', (req, res) => {
  res.status(200).json({ status: 'ok' });
});

// بدء الخادم
app.listen(PORT, () => {
  console.log(`الخادم يعمل على المنفذ ${PORT}`);
  console.log(`فحص الحالة: http://localhost:${PORT}/health`);
  console.log(`نقطة النهاية للرسائل: http://localhost:${PORT}/v1/messages`);
});

بعد ذلك، نفذ معالج الرسالة في src/handlers/messageHandler.ts:

import { Request, Response } from 'express';
import { ModelRequest, ModelResponse, Message, Content } from '../types';

export async function handleMessageRequest(req: Request, res: Response) {
  try {
    const request = req.body as ModelRequest;
    
    // التحقق الأساسي
    if (!request.messages || !Array.isArray(request.messages) || request.messages.length === 0) {
      return res.status(400).json({ error: 'تنسيق الطلب غير صالح. مصفوفة الرسائل مطلوبة.' });
    }
    
    // سجّل الطلب الوارد (لإجراء تصحيح الأخطاء)
    console.log('تم استلام الطلب مع', request.messages.length, 'رسالة(s)');
    
    // معالجة الرسائل
    const response = processMessages(request.messages);
    
    // إرجاع الاستجابة
    return res.status(200).json(response);
  } catch (error) {
    console.error('خطأ أثناء معالجة الطلب:', error);
    return res.status(500).json({ error: 'خطأ داخلي في الخادم' });
  }
}

function processMessages(messages: Message[]): ModelResponse {
  // استخراج آخر رسالة من المستخدم
  const lastUserMessage = findLastUserMessage(messages);
  
  if (!lastUserMessage) {
    return createErrorResponse("لم يتم العثور على رسالة من المستخدم في المحادثة");
  }
  
  const userQuery = extractTextContent(lastUserMessage);
  
  // منطق بسيط لتوليد الاستجابة
  let responseText = "";
  
  if (userQuery.toLowerCase().includes('hello') || userQuery.toLowerCase().includes('hi')) {
    responseText = "مرحباً! كيف يمكنني مساعدتك اليوم؟";
  } else if (userQuery.toLowerCase().includes('weather')) {
    responseText = "ليس لدي وصول إلى بيانات الطقس في الوقت الحقيقي، ولكن يمكنني مساعدتك في فهم أنماط الطقس بشكل عام.";
  } else if (userQuery.toLowerCase().includes('time')) {
    responseText = `الوقت الحالي على الخادم هو ${new Date().toLocaleTimeString()}.`;
  } else {
    responseText = "لقد تلقيت رسالتك. هذه هي استجابة خادم نموذج بسيط.";
  }
  
  // بناء وإرجاع الاستجابة
  return {
    message: {
      role: "assistant",
      content: [{ 
        type: "text", 
        text: responseText 
      }]
    }
  };
}

function findLastUserMessage(messages: Message[]): Message | undefined {
  // البحث عن آخر رسالة تحمل دور 'المستخدم'
  for (let i = messages.length - 1; i >= 0; i--) {
    if (messages[i].role === 'user') {
      return messages[i];
    }
  }
  return undefined;
}

function extractTextContent(message: Message): string {
  if (typeof message.content === 'string') {
    return message.content;
  } else if (Array.isArray(message.content)) {
    return message.content
      .filter(item => item.type === 'text' && item.text)
      .map(item => item.text)
      .join(' ');
  }
  return '';
}

function createErrorResponse(errorMessage: string): ModelResponse {
  return {
    message: {
      role: "assistant",
      content: [{ 
        type: "text", 
        text: `خطأ: ${errorMessage}` 
      }]
    }
  };
}

الخطوة 3: إضافة القدرة على استدعاء الأدوات (3 دقائق)

قم بإنشاء ملف جديد لتعريفات الأدوات والتنفيذات:

touch src/tools.ts

نفذ بعض الأدوات الأساسية في src/tools.ts:

import { Tool } from './types';

// تعريفات الأدوات
export const availableTools: Tool[] = [
  {
    name: "get_current_time",
    description: "الحصول على الوقت الحالي للخادم",
    input_schema: {
      type: "object",
      properties: {
        timezone: {
          type: "string",
          description: "المنطقة الزمنية الاختيارية (الافتراضي إلى منطقة الوقت الخاصة بالخادم)"
        }
      }
    }
  },
  {
    name: "calculate",
    description: "إجراء عملية حسابية رياضية",
    input_schema: {
      type: "object",
      properties: {
        expression: {
          type: "string",
          description: "التعبير الرياضي الذي سيتم تقييمه"
        }
      },
      required: ["expression"]
    }
  }
];

// تنفيذ الأدوات
export function executeToolCall(name: string, params: Record): string {
  switch (name) {
    case "get_current_time":
      return getTime(params.timezone);
    case "calculate":
      return calculate(params.expression);
    default:
      throw new Error(`أداة غير معروفة: ${name}`);
  }
}

function getTime(timezone?: string): string {
  const options: Intl.DateTimeFormatOptions = { 
    hour: '2-digit', 
    minute: '2-digit', 
    second: '2-digit',
    timeZoneName: 'short' 
  };
  
  try {
    if (timezone) {
      options.timeZone = timezone;
    }
    return new Date().toLocaleTimeString('en-US', options);
  } catch (error) {
    return `${new Date().toLocaleTimeString()} (وقت الخادم)`;
  }
}

function calculate(expression: string): string {
  try {
    const sanitizedExpression = expression.replace(/[^0-9+\-*/().\s]/g, '');
    const result = eval(sanitizedExpression);
    return `${expression} = ${result}`;
  } catch (error) {
    return `خطأ في حساب ${expression}: ${error instanceof Error ? error.message : String(error)}`;
  }
}

الآن، قم بتحديث معالج الرسالة لدعم استدعاء الأدوات عن طريق تعديل src/handlers/messageHandler.ts:

// أضف هذه الواردات في الأعلى
import { availableTools, executeToolCall } from '../tools';
import { ToolCall, ToolResult } from '../types';

// تحديث وظيفة handleMessageRequest
export async function handleMessageRequest(req: Request, res: Response) {
  try {
    const request = req.body as ModelRequest;
    
    // التحقق الأساسي
    if (!request.messages || !Array.isArray(request.messages) || request.messages.length === 0) {
      return res.status(400).json({ error: 'تنسيق الطلب غير صالح. مصفوفة الرسائل مطلوبة.' });
    }
    
    // تحقق مما إذا كانت هذه طلبًا مع نتائج الأدوات
    const lastMessage = request.messages[request.messages.length - 1];
    if (lastMessage.role === 'assistant' && Array.isArray(lastMessage.content)) {
      const toolCalls = lastMessage.content.filter(item => 
        item.type === 'tool_call') as ToolCall[];
      
      if (toolCalls.length > 0) {
        // هذه متابعة بنتائج الأدوات
        return handleToolResultsResponse(request, res);
      }
    }
    
    // معالجة كنص رسالة عادي
    const response = processMessages(request.messages);
    
    // إرجاع الاستجابة
    return res.status(200).json(response);
  } catch (error) {
    console.error('خطأ أثناء معالجة الطلب:', error);
    return res.status(500).json({ error: 'خطأ داخلي في الخادم' });
  }
}

// أضف هذه الوظيفة للتعامل مع استدعاءات الأدوات
function handleToolResultsResponse(request: ModelRequest, res: Response) {
  const messages = request.messages;
  const lastAssistantMessage = messages[messages.length - 1];
  
  if (lastAssistantMessage.role !== 'assistant' || !Array.isArray(lastAssistantMessage.content)) {
    return res.status(400).json({ error: 'تنسيق نتائج الأدوات غير صالح' });
  }
  
  // البحث عن استدعاءات الأدوات والنتائج
  const toolCalls = lastAssistantMessage.content.filter(
    item => item.type === 'tool_call'
  ) as ToolCall[];
  
  const toolResults = lastAssistantMessage.content.filter(
    item => item.type === 'tool_result'
  ) as ToolResult[];
  
  // معالجة النتائج
  let finalResponse = "لقد قمت بمعالجة المعلومات التالية:\n\n";
  
  toolResults.forEach(result => {
    const relatedCall = toolCalls.find(call => call.id === result.tool_call_id);
    if (relatedCall) {
      finalResponse += `- ${relatedCall.name}: ${result.content}\n`;
    }
  });
  
  return res.status(200).json({
    message: {
      role: "assistant",
      content: [{ type: "text", text: finalResponse }]
    }
  });
}

// تعديل دالة processMessages للتعامل مع استدعاءات الأدوات المحتملة
function processMessages(messages: Message[]): ModelResponse {
  const lastUserMessage = findLastUserMessage(messages);
  
  if (!lastUserMessage) {
    return createErrorResponse("لم يتم العثور على رسالة من المستخدم في المحادثة");
  }
  
  const userQuery = extractTextContent(lastUserMessage);
  
  // البحث عن الكلمات المفتاحية التي قد تحفز استخدام الأدوات
  if (userQuery.toLowerCase().includes('time')) {
    return {
      message: {
        role: "assistant",
        content: [
          { type: "tool_call", id: "call_001", name: "get_current_time", input: {} },
          { 
            type: "text", 
            text: "سأتحقق من الوقت الحالي لك." 
          }
        ]
      }
    };
  } else if (userQuery.toLowerCase().match(/calculate|compute|what is \d+[\+\-\*\/]/)) {
    // استخراج عملية حسابية محتملة
    const expression = userQuery.match(/(\d+[\+\-\*\/\(\)\.]*\d+)/)?.[0] || "1+1";
    
    return {
      message: {
        role: "assistant",
        content: [
          { 
            type: "tool_call", 
            id: "call_002", 
            name: "calculate", 
            input: { expression } 
          },
          { 
            type: "text", 
            text: `سأحسب ${expression} لك.` 
          }
        ]
      }
    };
  }
  
  // الاستجابة الافتراضية للاستفسارات الأخرى
  return {
    message: {
      role: "assistant",
      content: [{ 
        type: "text", 
        text: `لقد تلقيت رسالتك: "${userQuery}". كيف يمكنني مساعدتك أكثر؟` 
      }]
    }
  };
}

الخطوة 4: الاختبار والنشر (2 دقائق)

دعونا ننشئ سكربت اختبار بسيط للتحقق من خادمنا. أنشئ ملف test.js في الدليل الجذري:

const fetch = require('node-fetch');

async function testServer() {
  const url = 'http://localhost:3000/v1/messages';
  
  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      messages: [
        {
          role: "user",
          content: [
            {
              type: "text",
              text: "ما هو الوقت الآن؟"
            }
          ]
        }
      ]
    })
  });
  
  const data = await response.json();
  console.log(JSON.stringify(data, null, 2));
}

testServer().catch(console.error);

لاختبار خادمك:

# بدء الخادم
npm run dev

# في طرفية أخرى، قم بتشغيل الاختبار
node test.js

للنشر السريع، دعونا ننشئ Dockerfile بسيط:

FROM node:16-alpine

WORKDIR /app

COPY package*.json ./
RUN npm install

COPY . .
RUN npm run build

EXPOSE 3000

CMD ["npm", "start"]

ابنِ وشغّل الحاوية:

docker build -t mcp-server .
docker run -p 3000:3000 mcp-server

الخاتمة

في غضون 10 دقائق فقط، قمنا ببناء خادم أساسي ينفذ المفاهيم الرئيسية لبروتوكولات الرسائل الحديثة في الذكاء الاصطناعي. يمكن لخادمنا:

  1. معالجة طلبات الرسائل المنظمة
  2. الاستجابة بتنسيق موحد
  3. التعامل مع الوظيفة الأساسية لاستدعاء الأدوات
  4. معالجة والاستجابة لنتائج الأدوات

بينما هذه التنفيذة مبسطة، إلا أنها توفر قاعدة صلبة للمزيد من التطوير. لتوسيع هذا الخادم للاستخدام الإنتاجي، ضع في اعتبارك:

  • إضافة المصادقة وتحديد المعدل
  • تنفيذ معالجة الخطأ والتحقق الصحيح
  • الاتصال بنماذج الذكاء الاصطناعي الفعلية للمعالجة
  • إضافة أدوات أكثر تطورًا
  • تنفيذ استجابات البث
  • إضافة تسجيل شامل ومراقبة

تذكر أنه بينما تتبع تنفيذتنا المبادئ العامة لبروتوكولات الرسائل الحديثة في الذكاء الاصطناعي، قد تتطلب تنفيذات محددة مثل واجهة برمجة تطبيقات OpenAI أو واجهة برمجة تطبيقات Claude من Anthropic متطلبات إضافية أو اختلافات طفيفة في تنسيقاتها المتوقعة. دائمًا استشر الوثائق الرسمية للخدمة المحددة التي تتكامل معها.

مجال بروتوكولات رسائل الذكاء الاصطناعي في تطور سريع، لذا ابقَ على اطلاع بأحدث التطورات واستعد لتكييف تنفيذك مع تطور المعايير. من خلال فهم المفاهيم الأساسية المعروضة في هذا الدليل، ستكون مجهزًا تمامًا للعمل مع أي تطورات جديدة تظهر في هذا المجال المثير.