نشر خادم MCP على npm و PyPI خطوة بخطوة

انشر خادم MCP واحدًا على السجلين معًا، ليتمكن أي عميل من تشغيله باستخدام npx أو uvx. جهّز package.json و pyproject.toml، وتحقق من الأرشيف المضغوط، واختبره في MCP Inspector، وانشره عبر النشر الموثوق في GitHub Actions، وأضف أدوات توليد الصور والفيديو عبر API.

نشر خادم MCP على npm و PyPI خطوة بخطوة
Cristian Da Conceicao
مؤسس Picasso IA

بنيت خادم MCP وهو يعمل على حاسوبك المحمول. الآن يريد زميل، أو غريب على الإنترنت، تشغيله بسطر إعداد واحد فقط: بلا git clone، وبلا خطوة بناء، وبلا سلسلة رسائل تسأل "أي إصدار من Node تستخدم؟". هذا بالضبط ما يمنحك إياه السجل. انشر على npm فيبدأ العملاء خادمك باستخدام npx. وانشر على PyPI فيبدأونه باستخدام uvx. إذا فعلت الأمرين، يستطيع كل عميل MCP، من Claude Desktop إلى Cursor و VS Code، تشغيل خادمك بالاعتماد على اسم الحزمة وحده.

يتبع هذا الدليل المسار بالترتيب: بنية المستودع، package.json، pyproject.toml، الاختبارات المحلية، أول نشر يدوي، وسير عمل في GitHub Actions ينشر الحزمتين معًا من وسم واحد. تفترض كل خطوة خادمًا من نوع stdio يعمل محليًا بالفعل.

💡 قبل أن تبدأ: تحتاج إلى Node 18 أو أحدث من أجل npm، أو Python 3.10 أو أحدث من أجل PyPI، إضافة إلى حسابين مجانيين على npmjs.com و pypi.org. فعّل المصادقة الثنائية على الحسابين، لأن كلا السجلين يشترطانها على كل من ينشر.

لماذا النشر في السجلين معًا

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

صندوقا بريد خشبيان متآكلان، أحدهما أخضر والآخر كحلي، واقفان جنبًا إلى جنب على طريق ريفي ضبابي عند شروق الشمس

جمهوران، خادم واحد

إليك مقارنة بين المسارين جنبًا إلى جنب:

npmPyPI
أمر التشغيلnpx -y your-packageuvx your-package
SDK الرسمي@modelcontextprotocol/sdkmcp (يتضمن FastMCP)
ملف البيانpackage.jsonpyproject.toml
ما الذي يُرفعأرشيف tarball مبني من dist/أرشيف مصدر بالإضافة إلى wheel
أمر النشرnpm publishuv publish أو twine upload
المصادقة في CIالنشر الموثوق أو توكن دقيق الصلاحياتالنشر الموثوق أو توكن API

أنظف إعداد هو تنفيذ واحد لكل لغة مع عقد أدوات مشترك واحد. تبقى أسماء الأدوات ومخططات الإدخال وأوصافها متطابقة في الحزمتين، فيعمل الأمر النصي الذي ينجح مع إصدار npm بالطريقة نفسها مع إصدار PyPI. احتفظ بهذا العقد في ملف JSON صغير داخل المستودع، واجعل CI يقارن الإصدارين به.

تجنّب الاختصار المتمثل في غلاف Python رفيع يستدعي npx عبر سطر الأوامر. سيعمل ذلك إلى أن يكون لدى المستخدم Node غير مثبت، وعندها يفشل بخطأ لا يستطيع أحد فهمه بنظرة سريعة.

اختر بنية الحزمة

مستودع واحد يحوي مجلدين يُبقي قصة الإصدار بسيطة:

my-mcp-server/
  README.md
  LICENSE
  node/
    package.json
    tsconfig.json
    src/index.ts
  python/
    pyproject.toml
    src/my_mcp_server/__init__.py
    src/my_mcp_server/server.py
  .github/workflows/release.yml

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

كل مجلد حزمة مستقلة لها ملف بيانها الخاص. يستخدم سير عمل الإصدار لاحقًا working-directory لبنائها بشكل منفصل، فلا يتسرب شيء من جانب إلى آخر.

سمِّ مرة واحدة، وتحقق مرتين

اختر اسمًا واحدًا واستخدمه في السجلين. يتذكره المستخدمون، وتتطابق نتائج البحث.

  • npm: أحرف صغيرة، صالحة في الروابط، بدون مسافات. يتجنب اسم ضمن نطاق مثل @yourscope/my-mcp-server التعارضات، ويعمل جيدًا مع خوادم MCP.
  • PyPI: الأسماء غير حساسة لحالة الأحرف، وتعامل - و _ و . على أنها الحرف نفسه، لذلك يتعارض My_MCP.Server مع my-mcp-server.
  • التوفر: يعيد npm view my-mcp-server الرمز 404 عندما يكون الاسم متاحًا. وفي PyPI، افتح pypi.org/project/my-mcp-server/، ويعني الرمز 404 الشيء نفسه.

💡 تحقق من الاسمين قبل أن تكتب README حول أحدهما. اكتشاف أن الاسم مأخوذ يوم النشر يكلّفك فترة ما بعد الظهر كاملة في إعادة التسمية.

اكتب README يعمل كتوثيق

يعرض كلا السجلين ملف README الخاص بك كصفحة للحزمة، وبالنسبة لكثير من المستخدمين هو الوثيقة الوحيدة التي يقرؤونها. ضع فيه أربعة أشياء بهذا الترتيب:

  1. جملة واحدة تصف ما يفعله الخادم
  2. إعداد عميل جاهز للنسخ واللصق من أجل npx وآخر من أجل uvx
  3. جدول للأدوات بسطر واحد لكل أداة
  4. كل متغير بيئة يقرؤه الخادم، مع تحديد ما إذا كان مطلوبًا أو اختياريًا

انشر حزمة npm

جهّز package.json

ثلاثة حقول تحدد ما إذا كان npx يعمل من الأساس: bin، و files، و سطر shebang في ملف الدخول.

{
  "name": "@yourscope/my-mcp-server",
  "version": "0.1.0",
  "description": "MCP server that does one useful thing",
  "type": "module",
  "bin": { "my-mcp-server": "dist/index.js" },
  "files": ["dist", "README.md", "LICENSE"],
  "engines": { "node": ">=18" },
  "scripts": {
    "build": "tsc",
    "prepublishOnly": "npm run build"
  },
  "dependencies": { "@modelcontextprotocol/sdk": "^1.0.0" },
  "license": "MIT"
}
  • bin يربط اسم الأمر بملف الدخول المُجمّع. بدونه لا يملك npx ما يشغّله.
  • files قائمة بيضاء. فقط dist/ و README والترخيص يدخلون إلى الأرشيف.
  • prepublishOnly يعيد البناء قبل كل نشر، فلا ترفع مخرجات قديمة أبدًا.
  • سطر shebang #!/usr/bin/env node يجب أن يكون السطر الأول في src/index.ts. تحتفظ TypeScript به في الملف المُجمّع.

لقطة مقربة ليدين تعبئان صندوق كرتون صغيرًا بورق كرافت وظرف مختوم بالشمع على طاولة ورشة

تحقق من الأرشيف قبل الإرسال

شغّل npm pack --dry-run واقرأ قائمة الملفات التي يطبعها. تريد أن ترى dist/، و README، والترخيص، و package.json. لا تريد أن ترى .env، أو ملفات الاختبار، أو ملفات source maps لم تقصد مشاركتها، أو node_modules شاردًا.

💡 .env المسرّب هو الخطأ الأكثر شيوعًا في أول نشر، ولا يمكن التراجع عن نسخة منشورة. قائمة files البيضاء هي شبكة أمانك، فاحتفظ بها.

نفّذ أول نشر

npm login
npm publish --access public

الحزم ذات النطاق تكون خاصة افتراضيًا، ولهذا يهم --access public في أول نشر. أدخل رمز المصادقة الثنائية عندما يُطلب منك. ثم أثبت أنها تعمل من مجلد آخر:

cd $(mktemp -d)
npx -y @yourscope/my-mcp-server

ينبغي أن تبدأ العملية وتنتظر الإدخال على stdin. هذا الصمت صحيح، لأن خادم stdio يتكلم فقط عندما يتحدث إليه عميل.

انشر حزمة PyPI

اكتب pyproject.toml

يتكون تغليف Python من ملف واحد. تستخدم هذه النسخة hatchling كخلفية للبناء:

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "my-mcp-server"
version = "0.1.0"
description = "MCP server that does one useful thing"
readme = "README.md"
requires-python = ">=3.10"
license = { text = "MIT" }
dependencies = ["mcp>=1.0"]

[project.scripts]
my-mcp-server = "my_mcp_server.server:main"

جدول [project.scripts] يعادل bin. وهو ينشئ أمرًا عند التثبيت. إذا طابق اسم السكربت اسم الحزمة، فإن uvx my-mcp-server يعمل مباشرة. وإذا اختلفا، شغّل uvx --from my-mcp-server script-name.

خادم بسيط يستخدم FastMCP من SDK Python الرسمي:

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("my-mcp-server")


@mcp.tool()
def ping() -> str:
    """Return pong so clients can confirm the server is alive."""
    return "pong"


def main() -> None:
    mcp.run()  # stdio transport by default

يدان متآكلتان تعدلان دولاب دراجة فولاذيًا في ورشة دراجات، والأسلاك تتفرع عبر الإطار

ابنِ الملفات وتحقق منها

cd python
uv build
uvx twine check dist/*

uv build يكتب ملفين داخل dist/: أرشيف مصدر (.tar.gz) و wheel (.whl). تُثبَّت ملفات wheel بسرعة لأنه لا يلزم تجميع شيء، ولهذا يفضلها uvx و pip. twine check يتحقق من أن README يُعرض كصفحة للحزمة، فتكتشف بيانات وصفية معطوبة قبل أن يكتشفها PyPI.

جرّب TestPyPI، ثم انشر

TestPyPI موقع منفصل بحسابات ورموز منفصلة. وُجد كي لا يكلّفك أي رفع فاشل لأول مرة شيئًا.

uv publish --publish-url https://test.pypi.org/legacy/ --token <testpypi-token>
pip install --index-url https://test.pypi.org/simple/ \
  --extra-index-url https://pypi.org/simple/ my-mcp-server

خيار الفهرس الإضافي مهم. تعتمد حزمتك على اعتماديات، بما فيها mcp، وهي موجودة على PyPI الحقيقي، ولا يحملها TestPyPI. بعد نجاح تثبيت الاختبار، انشر فعليًا:

uv publish --token <pypi-token>
uvx my-mcp-server

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

اختبر قبل الإطلاق

منظر جانبي لصانع ساعات يفحص حركة ميكانيكية عبر عدسة مكبرة على طاولة عمل

شغّله في MCP Inspector

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

npx @modelcontextprotocol/inspector node dist/index.js
npx @modelcontextprotocol/inspector uvx my-mcp-server

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

أبقِ stdout نظيفًا

في stdio، يحمل stdout البروتوكول. يحقن console.log() أو print() شاردًا نصًا داخل تدفق JSON-RPC، فينقطع العميل بخطأ تحليل. أرسل كل سطر سجل إلى stderr بدلًا من ذلك: console.error() في Node، و print(..., file=sys.stderr) أو وحدة logging في Python.

اختبر إعداد العميل

هذا هو الإعداد الذي سيلصقه مستخدموك. جرّب المدخلين في عميل حقيقي:

{
  "mcpServers": {
    "my-server-npm": {
      "command": "npx",
      "args": ["-y", "@yourscope/my-mcp-server"]
    },
    "my-server-pypi": {
      "command": "uvx",
      "args": ["my-mcp-server"]
    }
  }
}

شغّل قائمة التحقق قبل النشر من قشرة نظيفة داخل مجلد مؤقت. قد يخفي التثبيت العام أو node_modules قريب ملفًا مفقودًا لأسابيع.

  • npm pack --dry-run يسرد فقط ما تنوي نشره
  • twine check dist/* ينجح بدون أي تحذيرات
  • Inspector يسرد كل أداة عبر npx وعبر uvx
  • لا يكتب شيء إلى stdout سوى رسائل البروتوكول
  • كتل إعداد README تطابق ما اختبرته للتو

أتمتة الإصدارات وترقيمها

منظور من زاوية منخفضة لطرود كرافت تنزلق على ناقل بكرات فولاذي نحو باب التحميل في غرفة تغليف مضيئة

النشر الموثوق، بلا رموز مخزّنة

يتيح كلا السجلين لسير عمل GitHub Actions النشر عبر OpenID Connect. تسجّل مستودعك وملف سير العمل على جانب السجل مرة واحدة، ويثق السجل بذلك السير بالضبط من ذلك الحين. لا يبقى أي رمز طويل الأمد في أسرار مستودعك، فلا يوجد ما يمكن تسريبه أو تدويره. يحتاج سير العمل فقط إلى صلاحية id-token: write.

في PyPI، أضف ناشرًا موثوقًا من إعدادات النشر للمشروع. يمكن لمشروع جديد تمامًا استخدام pending publisher، فيمكن أن يأتي الإصدار الأول من CI أيضًا. وفي npm، أضف الناشر الموثوق من إعدادات الحزمة. تتغير شاشات الإعداد من حين لآخر، فاتبع المطالبات الحالية.

وسم واحد، نشران

دفع وسم مثل v0.1.0 يشغّل المهمتين بالتوازي:

name: release
on:
  push:
    tags: ["v*"]

permissions:
  id-token: write
  contents: read

jobs:
  npm:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: node
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 24
          registry-url: https://registry.npmjs.org
      - run: npm install -g npm@latest
      - run: npm ci
      - run: npm publish --provenance --access public

  pypi:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v5
      - run: uv build
        working-directory: python
      - uses: pypa/gh-action-pypi-publish@release/v1
        with:
          packages-dir: python/dist

خطوة npm install -g npm@latest تضمن أن واجهة npm CLI حديثة بما يكفي للنشر الموثوق. أضف خطوة needs: مع مهمة الاختبار إذا أردت أن يمنع فشل البناء الإصدار.

Semver يحترمه العملاء

تعتمد عملاء MCP والوكلاء الذين يقفون خلفها على أسماء أدواتك ومخططات إدخالها، فعامل ذلك كواجهة برمجة عامة لديك:

التغييررفع الإصدار
إصلاح خطأ دون تغيير في المخططإصدار تصحيحي (0.1.1)
إضافة أداة جديدة أو وسيط اختياريإصدار فرعي (0.2.0)
إعادة تسمية أداة أو حذفها، أو إضافة وسيط إلزاميإصدار رئيسي (1.0.0)

حدّث ملفي البيان إلى الرقم نفسه في commit واحد، ثم ضع وسمًا عليه. سكربت صغير يعدّل package.json و pyproject.toml معًا يمنع التعارض الكلاسيكي حيث يكون npm على 1.2.0 و PyPI على 1.1.0. يمكن للمستخدمين الذين يريدون الاستقرار تثبيت رقم رئيسي في إعدادهم، على سبيل المثال @yourscope/my-mcp-server@1.

درج حجري مشمس يصعد حديقة على تلة مدرجة بثلاث منصات متمايزة

بعد أن تصبح الحزمتان منشورتين، يمكنك أيضًا إدراج الخادم في MCP Registry الرسمي. يتحقق السجل من الملكية عبر قراءة حقل mcpName في package.json وسطر mcp-name: مطابق في README لحزمة PyPI، ثم ينشر البيانات الوصفية باستخدام أمر mcp-publisher. لا يزال السجل قيد التطوير، فاقرأ وثائقه الحالية قبل أن تعتمد على الصيغة الدقيقة.

أضف أدوات توليد الصور والفيديو

يصبح الخادم المنشور أكثر فائدة لحظة يستطيع أن يصنع شيئًا. توليد الصور والفيديو من أكثر الأدوات طلبًا، وتجعلها واجهة PicassoIA API إضافة قصيرة. عنوان الأساس هو https://api.picassoia.com/v1، والمصادقة بتوكن Bearer يبدأ بالنص pia_sk_، والمهام غير متزامنة: تنشئ توقعًا، ثم تستعلم عنه، ثم تقرأ النتيجة.

تتوفر أربعة نماذج عبر API: Picasso IA Image، و Picasso IA Image Editor Pro، و Picasso IA Video، و Seedance 2.5 Lite، الذي يضيف صوتًا إلى مقاطعه. يمكن لحساب واحد تشغيل 5 توقعات في وقت واحد، ويمكن أن تصل الأوامر النصية إلى 4,000 حرف.

استوديو تصوير فيه كاميرا بدون مرآة على حامل ثلاثي تواجه مزهرية خزفية أمام خلفية ورقية رمادية

استدعِ API من أداة. يُنشئ مساعد TypeScript التالي توقعًا على Picasso IA Image ويستعلم حتى ينتهي:

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

export async function generateImage(prompt: string): Promise<string> {
  const created = await fetch(
    `${BASE}/models/picassoia/picassoia-image/predictions`,
    { method: "POST", headers, body: JSON.stringify({ input: { prompt } }) }
  ).then((r) => r.json());

  while (true) {
    const p = await fetch(`${BASE}/predictions/${created.id}`, { headers })
      .then((r) => r.json());
    if (p.status === "succeeded") {
      return Array.isArray(p.output) ? p.output[0] : p.output;
    }
    if (p.status === "failed" || p.status === "canceled") {
      throw new Error(p.error ?? p.status);
    }
    await new Promise((resolve) => setTimeout(resolve, 2000));
  }
}

راجع وثائق API لمعرفة حقول الإدخال الدقيقة لكل نموذج، لأن شكل output والمعاملات المقبولة تختلف من نموذج إلى آخر.

مرّر التوكن عبر إعداد العميل. لا تضمّن توكنًا داخل الحزمة أبدًا. اقرأه من متغيرات البيئة، ودع كل مستخدم يضبطه في إعداد MCP الخاص به:

{
  "mcpServers": {
    "my-server": {
      "command": "npx",
      "args": ["-y", "@yourscope/my-mcp-server"],
      "env": { "PICASSOIA_API_TOKEN": "pia_sk_your_token_here" }
    }
  }
}

💡 اقرأ متطلبات الخطة الحالية في صفحة PicassoIA API قبل أن تعد باستخدام مجاني في README. قد تتغير صياغة التسعير، وسيحاسبك مستخدموك على ما كتبته.

اكتب مسودة ملاحظات الإصدار باستخدام نموذج لغوي. يستطيع نموذج لغوي التعامل مع المهمة الرتيبة التي تجعل الناس يتجاهلون سجلات التغيير. إليك سير عمل سريع مع Claude Sonnet 5:

  1. شغّل git log v0.1.0..HEAD --oneline وانسخ المخرجات.
  2. افتح صفحة النموذج على PicassoIA والصق السجل مع تعليمة من سطر واحد: صنّف التغييرات ضمن فئات Added و Changed و Fixed بلغة بسيطة.
  3. أخبره بالتغييرات التي تمس أسماء الأدوات أو المخططات، حتى تُوسم بأنها تغييرات كاسرة.
  4. اقرأ النتيجة مقابل الفرق (diff) والصقها في إصدار GitHub.

للحصول على رأي ثانٍ في ملف pyproject.toml أو ملف سير العمل، يُعد GPT 5.6 Sol مراجعًا جيدًا لمهام البرمجة.

جرّب بنفسك على Picasso IA

تستحق صفحة حزمتك صورة رئيسية حقيقية ومقطعًا توضيحيًا قصيرًا، لا لقطة شاشة لطرفية. أنشئ الصورة الرئيسية باستخدام Picasso IA Image، ثم حسّن التفاصيل باستخدام Picasso IA Image Editor Pro، ثم حوّل الإطار الأخير إلى مقطع قصير باستخدام Picasso IA Video.

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

تجربة أولى بسيطة:

  • اكتب أمرًا نصيًا من 40 كلمة يصف مكتب مطور هادئ في ضوء الصباح
  • ولّد ثلاث صور متنوعة واحتفظ بأوضحها
  • احفظها كصورة رئيسية في README
  • حرّكها للإعلان عن الإصدار

تصفّح كل النماذج المتاحة على picassoia.com/en/all-models، واختر ما يناسب أسلوبك، وانشر شيئًا يستحق الفتح. إصدارك الأول لا يبعد عنك سوى وسم واحد.

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

اختر لغتك

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