إنشاء خادم MCP لكل من Claude Code وGitHub Copilot

ابنِ خادم MCP واحدًا بلغة TypeScript وسجّله في كل من Claude Code وGitHub Copilot. ستحصل على كود أدوات يعمل، وإعدادات دقيقة لكل عميل، وروتين لتصحيح الأخطاء باستخدام MCP Inspector، وأداة لتوليد الصور تستدعي واجهة PicassoIA API.

إنشاء خادم MCP لكل من Claude Code وGitHub Copilot
Cristian Da Conceicao
مؤسس Picasso IA

كتبتَ سكربتًا يوفّر عليك عشر دقائق يوميًا، والآن تريد أن يشغّله مساعدك الذكي دون المرور بخطوة النسخ واللصق. ابنِه مرة واحدة كخادم Model Context Protocol، وسيتمكن كل من Claude Code وGitHub Copilot من استدعاء الأدوات نفسها، لأن MCP هي اللغة المشتركة التي يتخاطبان بها مع أي شيء خارج المحرر. يبني هذا الدليل خادمًا صغيرًا بلغة TypeScript، ويسجّله في Claude Code، ثم يسجّله في Copilot داخل VS Code، وينتهي بأداة صور حقيقية تستدعي واجهة PicassoIA API. خصّص نحو 40 دقيقة وحوالي 100 سطر من الكود.

لماذا يتفوّق خادم واحد على اثنين

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

منظر علوي لمكتب عليه مخطط مرسوم يدويًا بمربعات وأسهم بجانب حاسوب محمول وفنجان قهوة

البروتوكول نفسه، وعميلان

يمكن للخادم أن يعرض ثلاثة أنواع من الإمكانيات:

  • الأدوات: دوال يستطيع النموذج استدعاءها، مثل add_note أو generate_image.
  • الموارد: بيانات للقراءة فقط يمكن للعميل إرفاقها بالمحادثة، مثل ملف سجل أو مخطط.
  • الأوامر الجاهزة: قوالب قابلة لإعادة الاستخدام يُفعّلها المستخدم عن قصد.

الأدوات هي مصدر معظم القيمة اليوم، لذلك يركّز هذا المقال عليها. يتحدث كل من Claude Code و Copilot لغة رسائل JSON-RPC نفسها عبر وسائل النقل نفسها، وهذا يعني أن الخادم الذي يعمل مع أحدهما سيعمل مع الآخر تقريبًا دون تغييرات.

مطوّر يقف بجانب لوح أبيض مغطى بملاحظات لاصقة مرتبة في ثلاثة أعمدة

أين تختلف الإعدادات

الخادم واحد، لكن التسجيل مختلف. إليك الفرق كاملًا في جدول واحد:

الإعدادClaude CodeGitHub Copilot في VS Code
ملف الإعداد.mcp.json في المشروع، أو ~/.claude.json.vscode/mcp.json، أو ملف تعريف المستخدم
الخاصية الجذريةmcpServersservers
الإضافة من الطرفيةclaude mcp addلوحة الأوامر: MCP: Add Server
حقل النقلtype (stdio، http، sse)type إلزامي (stdio أو http)
الأسرارعلامة --env أو توسيع ${VAR}كتلة inputs مع ${input:id}
مكان تشغيل الأدواتأي جلسةوضع الوكيل فقط

💡 نصيحة: الخاصية الجذرية هي الفخ الكلاسيكي. إذا لصقتَ إعداد Claude Code في VS Code دون تعديل، فلن يُحمَّل أي شيء، لأن Copilot يبحث عن servers وليس mcpServers.

إعداد المشروع

اختر مجلدًا خارج مستودعك الرئيسي حتى يتمكن الخادم من خدمة عدة مشاريع لاحقًا. تحتاج إلى Node.js 20 أو أحدث وإلى طرفية.

لقطة مقرّبة ليدي مطوّر وهما تكتبان على لوحة المفاتيح، وخلفهما محرر أكواد غير واضح

تثبيت SDK

mkdir team-notes-mcp && cd team-notes-mcp
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript tsx @types/node

افتح package.json وأضف "type": "module" مع سكربتين، "build": "tsc" و"dev": "tsx src/index.ts". ثم أنشئ tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "dist",
    "strict": true,
    "esModuleInterop": true
  },
  "include": ["src"]
}

تستخدم الأمثلة API @modelcontextprotocol/sdk 1.x مع McpServer وregisterTool. إذا غيّر إصدار رئيسي أحدث مسار استيراد، فستبقى المفاهيم الواردة أدناه كما هي.

البدء مع stdio

يحدد MCP وسيلتي نقل رئيسيتين. stdio تعني أن العميل يشغّل خادمك كعملية فرعية ويتبادل الرسائل عبر الإدخال والإخراج القياسيين. أما Streamable HTTP فتعني أن الخادم يعمل بشكل مستقل ويتصل به العملاء عبر عنوان URL. ابدأ مع stdio. فهي لا تحتاج إلى منفذ ولا إلى طبقة مصادقة ولا إلى استضافة، ويدعمها العميلان مباشرة. انتقل إلى HTTP فقط عندما يحتاج عدة أشخاص إلى مشاركة نسخة واحدة قيد التشغيل.

كتابة الخادم

مثالنا خادم صغير لملاحظات الفريق، فيه أداتان: واحدة تحفظ ملاحظة، وأخرى تبحث فيها. وهو صغير بما يكفي لقراءته في دقيقة، وواقعي بما يكفي ليكون مفيدًا.

مطوّران يجلسان جنبًا إلى جنب بينما يشير أحدهما إلى شاشة حاسوب محمول

تسجيل أداة

أنشئ src/index.ts:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { promises as fs } from "node:fs";
import path from "node:path";

const NOTES_FILE = path.join(process.env.NOTES_DIR ?? process.cwd(), "notes.json");

type Note = { id: number; text: string; tags: string[]; createdAt: string };

async function readNotes(): Promise<Note[]> {
  try {
    return JSON.parse(await fs.readFile(NOTES_FILE, "utf8"));
  } catch {
    return [];
  }
}

const server = new McpServer({ name: "team-notes", version: "1.0.0" });

server.registerTool(
  "add_note",
  {
    title: "Add note",
    description:
      "Save a short engineering note with optional tags. Use it when the user asks to remember a decision, a command or a bug.",
    inputSchema: {
      text: z.string().min(3).max(2000),
      tags: z.array(z.string()).default([]),
    },
  },
  async ({ text, tags }) => {
    const notes = await readNotes();
    const note: Note = {
      id: notes.length + 1,
      text,
      tags,
      createdAt: new Date().toISOString(),
    };
    await fs.writeFile(NOTES_FILE, JSON.stringify([...notes, note], null, 2));
    return { content: [{ type: "text", text: `Saved note #${note.id}` }] };
  }
);

server.registerTool(
  "search_notes",
  {
    title: "Search notes",
    description: "Find saved notes whose text or tags contain the query.",
    inputSchema: { query: z.string().min(1) },
  },
  async ({ query }) => {
    const q = query.toLowerCase();
    const hits = (await readNotes()).filter(
      (n) =>
        n.text.toLowerCase().includes(q) ||
        n.tags.some((t) => t.toLowerCase().includes(q))
    );
    const text = hits.length
      ? hits.map((n) => `#${n.id} [${n.tags.join(", ")}] ${n.text}`).join("\n")
      : "No notes matched.";
    return { content: [{ type: "text", text }] };
  }
);

await server.connect(new StdioServerTransport());
console.error("team-notes MCP server running on stdio");

شغّل npm run build. ستحصل الآن على dist/index.js، وهذا الملف هو الشيء الوحيد الذي يحتاج العميلان إلى معرفته.

إرجاع نتائج نظيفة

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

return {
  isError: true,
  content: [{ type: "text", text: "notes.json is not valid JSON. Fix or delete it." }],
};

تتيح نتيجة isError للمساعد أن يشرح لك المشكلة أو يعيد المحاولة بمدخلات مختلفة. أما الانهيار فلا يُظهر سوى رسالة غامضة بعنوان "server disconnected".

إبقاء stdout صامتًا

هذا هو السبب الأكثر شيوعًا لفشل أول خادم. مع stdio، الإخراج القياسي ملك للبروتوكول. يكفي استدعاء console.log واحد عابر ليحقن نصًا عاديًا في تدفق JSON-RPC، فيُسقط العميل الاتصال. سجّل عبر console.error، الذي يكتب إلى stderr، وسيلتقطه العميلان كمخرجات تشخيصية بدلًا من ذلك.

💡 نصيحة: اكتب أوصاف الأدوات كتعليمات للنموذج، لا كتوثيق للبشر. عبارة "استخدمها عندما يطلب المستخدم تذكّر قرار" تجعل الأداة تُختار في اللحظة المناسبة، أما "أداة ملاحظات" فلا تفعل.

الاتصال مع Claude Code

لقطة مقرّبة لنافذة طرفية على شاشة حاسوب محمول منعكسة في زوج من النظارات

الإضافة عبر CLI

أمر واحد يسجّل الخادم. تأتي الخيارات قبل الاسم، وتفصل شرطتان (--) بين الاسم والأمر الذي سيشغّله Claude Code:

claude mcp add --transport stdio --scope user \
  --env NOTES_DIR=/home/dev/notes \
  team-notes -- node /absolute/path/to/team-notes-mcp/dist/index.js

استخدم مسارًا مطلقًا. يبدأ Claude Code العملية من أي مجلد تستخدمه جلستك، لذلك تنكسر المسارات النسبية بمجرد أن تفتح مشروعًا آخر. ثم تحقّق من ذلك:

claude mcp list
claude mcp get team-notes

داخل الجلسة، اكتب /mcp لرؤية حالة الاتصال وقائمة الأدوات. اطرح سؤالًا طبيعيًا، مثل "تذكّر أننا ننشر يوم الخميس، ضع عليه وسم release"، وراقب Claude Code وهو يطلب الإذن لاستدعاء add_note.

المشاركة عبر .mcp.json

تحدد علامة النطاق من يحصل على الخادم. local تُبقيه خاصًا بك داخل مشروع واحد، وuser تجعله متاحًا في كل مشروع، وproject تكتب ملف .mcp.json يمكنك إيداعه في المستودع ليحصل عليه الفريق كله. إليك إعدادًا مشتركًا يتجنب المسارات المكتوبة يدويًا:

{
  "mcpServers": {
    "team-notes": {
      "type": "stdio",
      "command": "node",
      "args": ["${TEAM_NOTES_PATH}/dist/index.js"],
      "env": { "NOTES_DIR": "${NOTES_DIR:-.notes}" }
    }
  }
}

يضبط كل زميل في فريقه TEAM_NOTES_PATH مرة واحدة في الصدفة الخاصة به. تُوفّر صيغة ${NOTES_DIR:-.notes} قيمة افتراضية عندما يكون المتغير غير موجود. يطلب Claude Code الموافقة في المرة الأولى التي يرى فيها خادمًا بنطاق المشروع، وهذا إجراء وقائي سليم لأي شيء يُسحب من مستودع.

الاتصال مع GitHub Copilot

مطوّر يتكئ على كرسي مكتب ويبتسم أمام شاشتين في ضوء ما بعد العصر

كتابة .vscode/mcp.json

أنشئ .vscode/mcp.json في مساحة العمل. تذكّر الخاصية الجذرية المختلفة وtype الإلزامي:

{
  "servers": {
    "team-notes": {
      "type": "stdio",
      "command": "node",
      "args": ["${workspaceFolder}/tools/team-notes-mcp/dist/index.js"],
      "env": { "NOTES_DIR": "${workspaceFolder}/.notes" }
    }
  }
}

تجعل ${workspaceFolder} الملف محمولًا، فيمكنك إيداعه في المستودع. أما الأسرار فأضف لها مصفوفة inputs. يطلب VS Code القيمة مرة واحدة، ويخزّنها بشكل آمن، ثم يحقنها:

{
  "inputs": [
    { "type": "promptString", "id": "picassoia-token", "description": "PicassoIA API token", "password": true }
  ],
  "servers": {
    "team-notes": {
      "type": "stdio",
      "command": "node",
      "args": ["${workspaceFolder}/tools/team-notes-mcp/dist/index.js"],
      "env": { "PICASSOIA_API_TOKEN": "${input:picassoia-token}" }
    }
  }
}

التبديل إلى وضع الوكيل

يفتح Copilot Chat في وضع Ask افتراضيًا، وأدوات MCP لا تعمل إلا في وضع Agent. بدّل الوضع في لوحة الدردشة، وافتح منتقي الأدوات، وتأكد من ظهور team-notes مع تحديد الأداتين. إذا لم يظهر، فشغّل MCP: List Servers من لوحة الأوامر، واختر الخادم، واقرأ مخرجاته. أعد تشغيله من القائمة نفسها بعد كل إعادة بناء.

يتيح لك Copilot أيضًا الاختيار بين النماذج التي تقدمها خطتك، فيُختبر الخادم نفسه بنماذج مختلفة. وهذه طريقة رخيصة للتحقق من أن أوصاف أدواتك واضحة بما يكفي لكل واحد منها.

الاختبار والتصحيح قبل النشر

منظر جانبي لمطوّر ملتحٍ يرتدي قبعة صوفية عند مكتب واقف بجانب نافذة ممطرة

تشغيل MCP Inspector

قبل أن تلوم أيًّا من العميلين، اختبر الخادم وحده. يفتح Inspector الرسمي صفحة ويب محلية تستطيع من خلالها عرض الأدوات، وتعبئة المعاملات، ورؤية الاستجابات الخام:

npx @modelcontextprotocol/inspector node dist/index.js

استدعِ add_note بقيمة text فارغة. يجب أن يرفضها مخطط Zod لديك برسالة تحقق واضحة. ثم استدعِ search_notes بوسم حفظته للتو. إذا عملت الاثنتان هنا، فأي مشكلة متبقية تكمن في إعداد العميل، لا في كودك.

إصلاح الأعطال الشائعة

العَرَضالسبب المرجّحالحل
الخادم لا يتصل أبدًاconsole.log كتب إلى stdoutالتحويل إلى console.error
"Command not found"مسار نسبي أو بناء مفقوداستخدم مسارًا مطلقًا وشغّل npm run build
الأدوات غير موجودة في Copilotالدردشة في وضع Askالتبديل إلى وضع Agent
الأداة موجودة لكنها لا تُختار أبدًاوصف غامضأعد كتابته بعبارات محفّزة
متغير بيئة فارغغير مُعلن في الإعدادأضفه إلى كتلة env
استدعاءات الصور تفشل تحت الضغطأكثر من 5 مهام في وقت واحدضع الاستدعاءات في طابور داخل الأداة

أضِف أداة توليد صور إلى خادمك

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

منظر علوي لأربعة أشخاص يشيرون إلى مخططات مطبوعة حول طاولة خشبية طويلة من البلوط

استدعاء واجهة PicassoIA API

تتوفر PicassoIA API للمطورين على https://api.picassoia.com/v1، وتستخدم توكن Bearer يبدأ بالنص pia_sk_. التنبؤات غير متزامنة وتعمل بأسلوب Replicate: تنشئ تنبؤًا، ثم تستعلم عنه بشكل متكرر حتى تصبح حالته succeeded. يقبل نموذج PicassoIA Image prompt بطول يصل إلى 4,000 حرف، وكذلك aspect_ratio، ويُعيد قائمة بعناوين URL للصور. أضف هذه الأداة قبل سطر server.connect:

const API = "https://api.picassoia.com/v1";
const headers = {
  Authorization: `Bearer ${process.env.PICASSOIA_API_TOKEN}`,
  "Content-Type": "application/json",
};

server.registerTool(
  "generate_image",
  {
    title: "Generate image",
    description: "Create an image from a text prompt with PicassoIA and return its URL.",
    inputSchema: {
      prompt: z.string().min(1).max(4000),
      aspect_ratio: z.enum(["1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3"]).default("16:9"),
    },
  },
  async ({ prompt, aspect_ratio }) => {
    const created = await fetch(`${API}/models/picassoia/picassoia-image/predictions`, {
      method: "POST",
      headers,
      body: JSON.stringify({ input: { prompt, aspect_ratio } }),
    }).then((r) => r.json());

    let prediction = created;
    while (["starting", "processing"].includes(prediction.status)) {
      const wait = prediction.eta?.next_poll_in_seconds ?? 2;
      await new Promise((resolve) => setTimeout(resolve, wait * 1000));
      prediction = await fetch(`${API}/predictions/${created.id}`, { headers }).then((r) => r.json());
    }

    if (prediction.status !== "succeeded") {
      return {
        isError: true,
        content: [{ type: "text", text: `Generation ${prediction.status ?? "request failed"}` }],
      };
    }
    return { content: [{ type: "text", text: prediction.output[0] }] };
  }
);

يسمح الحساب بعدد 5 تنبؤات متزامنة، مشتركة بين كل التوكنات والاتصالات، لذلك ستصطدم حلقة تُطلق عشر صور دفعة واحدة بهذا السقف. ولّد الصور واحدة تلو الأخرى داخل الأداة، أو احتفظ بطابور صغير.

💡 نصيحة: تحقّق من التسعير الحالي ومتطلبات الخطة في صفحة واجهة PicassoIA API قبل أن تنشر خادمًا ليستخدمه الآخرون. الوثائق وصفحة التسعير تصفان الوصول بطريقتين مختلفتين، لذا تأكد مما تتضمنه خطتك أنت.

كيفية استخدام Sonnet 5 على PicassoIA

أوصاف أدواتك أوامر نصية، ونموذج لغوي هو أفضل محرر لها. يُعد Claude Sonnet 5 خيارًا قويًا لهذه المهمة، ويمكنك تشغيله على PicassoIA دون مغادرة المتصفح.

  1. افتح صفحة Claude Sonnet 5 في مجموعة النماذج اللغوية الكبيرة.
  2. الصق تعريفات أدواتك، بما فيها الأسماء والأوصاف والمخططات، في الأمر النصي.
  3. اطلب منه إعادة كتابة كل وصف كتعليمة قصيرة تحدد متى تُستدعى الأداة وماذا تُرجع.
  4. اطلب عشر مدخلات لحالات حدّية لكل أداة، مثل السلاسل الفارغة والنصوص الطويلة جدًا والوسوم غير المعتادة.
  5. مرّر تلك المدخلات عبر MCP Inspector، وأصلح كل فشل، والصق الأوصاف المحسّنة في كودك.

للحصول على رأي ثانٍ في المنطق المعقد، يُدرج كلٌّ من Claude Fable 5 وGPT 5.6 Sol ضمن مهام البرمجة. ومقارنة إعادة كتابتهما لوصف واحد تكشف غالبًا أي صياغة ملتبسة.

جرّبه بنفسك على PicassoIA

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

حاسوب محمول وقهوة فلات وايت على طاولة مقهى مع هاتف ذكي بجانبهما

ابدأ بأداة الصور، لأنها تعطيك ردود فعل فورية. اكتب أمرًا نصيًا، واستدعِ generate_image من Claude Code أو Copilot، وشاهد النتيجة خلال ثوانٍ. ثم افتح صفحة PicassoIA Image وجرّب نسب العرض إلى الارتفاع وأساليب الأوامر النصية مباشرة، أو تصفّح كل النماذج المتاحة على picassoia.com/en/all-models. صورتك الأولى لا تبعد عنك سوى أمر نصي واحد.

شارك هذا المقال

اختر لغتك

مقالات ذات صلة