أنظمة MCP الإنتاجية
المشروع النهائي: ابنِ خادم MCP خاصاً بـ GitHub
النتيجة: بنهاية هذا الدرس سيكون لديك خادم MCP يعمل في Claude Desktop يكشف مستودعات GitHub الخاصة بك كأدوات — يستطيع Claude البحث في كودك، قراءة أي ملف، سرد المشكلات المفتوحة، وصياغة طلبات pull، مؤسّساً على كودك الفعلي.
هذا المشروع النهائي يجمع كل الوحدات: مصافحة JSON-RPC (م1)، أساسيات الخادم (م2)، الأدوات والموارد (م3)، المصادقة (م4)، وتقوية الإنتاج (م5).
ما ستشحنه — هذا الموجّه سيعمل في Claude Desktop عند الانتهاء:
"في مستودع
my-blogخاصتي، اعثر على كل منشور يذكرKubernetesنُشر بعد 2026-03-01، اقرأ الأقدم، وصِغ منشور متابعة يتضمن تغييرات CSI 1.30 الجديدة."
سيستدعي Claude search_code، read_file، و create_draft_post على خادمك — مؤسساً على مستودعاتك، ليس هلوسة.
الجزء 1 — إعداد المشروع (5 دقائق)
github-mcp/
├── server.py
├── github_client.py
├── pyproject.toml
├── .env.example
└── README.md
pyproject.toml:
[project]
name = "github-mcp"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = [
"mcp>=1.1.0",
"httpx>=0.27",
"python-dotenv>=1.0",
"pydantic>=2",
]
.env.example:
GITHUB_TOKEN=ghp_...
GITHUB_OWNER=your-username
أنشئ رمز وصول شخصي GitHub بصلاحية repo. الصقه في .env.
pip install -e .
الجزء 2 — عميل GitHub (10 دقائق)
REST API لـ GitHub بسيط. لا حاجة لـ SDK؛ httpx يكفي.
github_client.py:
import os, httpx, base64
from typing import Any
GITHUB_API = "https://api.github.com"
class GitHubClient:
def __init__(self, token: str, default_owner: str | None = None):
self._token = token
self._default_owner = default_owner
def _headers(self):
return {
"Authorization": f"Bearer {self._token}",
"Accept": "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
}
async def search_code(self, repo: str, query: str, limit: int = 10):
q = f"{query} repo:{self._resolve_repo(repo)}"
async with httpx.AsyncClient(timeout=15) as c:
r = await c.get(f"{GITHUB_API}/search/code",
params={"q": q, "per_page": limit},
headers=self._headers())
r.raise_for_status()
return [{
"path": i["path"],
"repo": i["repository"]["full_name"],
"url": i["html_url"],
"score": i["score"],
} for i in r.json().get("items", [])]
async def read_file(self, repo: str, path: str, ref: str = "HEAD") -> str:
repo = self._resolve_repo(repo)
async with httpx.AsyncClient(timeout=15) as c:
r = await c.get(f"{GITHUB_API}/repos/{repo}/contents/{path}",
params={"ref": ref}, headers=self._headers())
r.raise_for_status()
return base64.b64decode(r.json()["content"]).decode("utf-8", errors="replace")
async def list_issues(self, repo: str, state: str = "open", limit: int = 20):
repo = self._resolve_repo(repo)
async with httpx.AsyncClient(timeout=15) as c:
r = await c.get(f"{GITHUB_API}/repos/{repo}/issues",
params={"state": state, "per_page": limit},
headers=self._headers())
r.raise_for_status()
return [{
"number": i["number"], "title": i["title"],
"body": (i.get("body") or "")[:1000],
"labels": [l["name"] for l in i.get("labels", [])],
"url": i["html_url"],
} for i in r.json() if "pull_request" not in i]
async def create_issue(self, repo: str, title: str, body: str):
repo = self._resolve_repo(repo)
async with httpx.AsyncClient(timeout=15) as c:
r = await c.post(f"{GITHUB_API}/repos/{repo}/issues",
json={"title": title, "body": body},
headers=self._headers())
r.raise_for_status()
i = r.json()
return {"number": i["number"], "url": i["html_url"]}
def _resolve_repo(self, repo: str) -> str:
if "/" in repo:
return repo
if not self._default_owner:
raise ValueError(f"Repo '{repo}' بلا مالك و GITHUB_OWNER غير محدد")
return f"{self._default_owner}/{repo}"
لماذا لا PyGithub؟ لسببين: (1) خادم MCP يعمل في عملية stdio لـ Claude Desktop، والاعتمادات الأخف = بدء أسرع؛ (2) سطح REST API الذي نحتاجه هو 4 نقاط نهاية — إضافة SDK بحجم 200KB لذلك مبالغة.
الجزء 3 — خادم MCP (15 دقيقة)
server.py (انظر النسخة الإنجليزية أعلاه للكود الكامل — المحتوى متطابق عدا التعليقات).
النمط الأساسي:
@server.list_tools()
async def list_tools() -> list[Tool]:
return [Tool(name="search_code", description="...", inputSchema={...}), ...]
@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
try:
if name == "search_code":
results = await gh.search_code(...)
return [TextContent(type="text", text="\n".join(...))]
# ...
except Exception as e:
# أعد الأخطاء كمحتوى tool_result، ليس كاستثناءات Python تسقط الخادم
return [TextContent(type="text", text=f"Error: {type(e).__name__}: {e}")]
async def main():
async with stdio_server() as (read, write):
await server.run(read, write, server.create_initialization_options())
الأنماط الرئيسية من الوحدات السابقة:
- مزخرفات
list_tools+call_tool(م2 درس 1) - شكل إرجاع
TextContent(م2 درس 2) - الأخطاء تُعاد كـ tool_result، ليست مُثارة (م2 درس 3)
- نقل stdio لـ Claude Desktop المحلي (م4 درس 1)
الجزء 4 — الربط بـ Claude Desktop (5 دقائق)
عدّل ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) أو ما يعادله على Windows/Linux:
{
"mcpServers": {
"github": {
"command": "python",
"args": ["/absolute/path/to/github-mcp/server.py"],
"env": {
"GITHUB_TOKEN": "ghp_...",
"GITHUB_OWNER": "your-username"
}
}
}
}
أعد تشغيل Claude Desktop. يجب أن ترى رمز المطرقة يشير إلى اتصال خادم MCP.
فحص سريع: اسأل Claude: "استخدم أداة list_issues على مستودع my-blog خاصتي." إذا أعاد مشكلاتك الفعلية، المصافحة + النقل يعملان.
الجزء 5 — أضف قالب موجّه (5 دقائق)
MCP يكشف أيضاً الموجّهات — قوالب قابلة لإعادة الاستخدام يمكن لـ Claude عرضها على المستخدم. أضف واحداً لصياغة منشورات متابعة:
@server.list_prompts()
async def list_prompts():
return [
Prompt(
name="draft-followup-post",
description="صِغ منشور متابعة مبنياً على منشور موجود في مستودع",
arguments=[
PromptArgument(name="repo", required=True),
PromptArgument(name="original_path", required=True),
PromptArgument(name="angle", required=True),
],
),
]
الآن في Claude Desktop يمكن للمستخدم اختيار هذا الموجّه من الواجهة، ملء الحجج الثلاث، و Claude يفعل الباقي — قراءة الأصلي، توليد المتابعة — كله عبر خادمك.
الجزء 6 — تقوية الإنتاج (اختياري، 10 دقائق)
كل ما في الوحدة 5 دروس 1–3 ينطبق هنا:
- تحديد المعدل — REST API لـ GitHub يسمح بـ 5000 طلب/ساعة مع PAT. لاستخدام أثقل، أضف token bucket بسيط.
- التسجيل — وجّه stderr stdio إلى ملف.
- تقليل النطاق — لـ MCP للقراءة فقط، استخدم PAT بنطاق
public_repoفقط. - التخزين المؤقت — خزّن نتائج
read_fileلنفس(repo, path, ref)لمدة 5 دقائق.
الجزء 7 — قائمة استكشاف الأخطاء
| العرض | أول شيء تتحقق منه | السبب المعتاد |
|---|---|---|
| Claude Desktop يُظهر 0 أدوات | أعد تشغيل بعد تعديل التكوين | خطأ صياغة JSON أو مسار خاطئ |
401 Unauthorized | الرمز في env مقابل التكوين | منتهي أو بلا نطاق repo |
404 Not Found على read_file | اسم المستودع + المالك الافتراضي | GITHUB_OWNER غير معين |
| Claude يهلوس محتويات الملف | سجلات stderr stdio | الخادم سقط مبكراً |
search_code لا يُرجع شيئاً | فهرسة GitHub | انتظر فهرسة جديدة |
نقطة تحقق — أنجز هذا قبل ادعاء الشهادة
- شحن الخادم. Claude Desktop يُظهر 4 أدوات + موجّهاً واحداً عند النقر على رمز المطرقة.
- شحن استدعاء حقيقي. اسأل: "ابحث في
my-blogعن 'Kubernetes'." تأكد أن Claude يستدعيsearch_codeويُرجع مسارات ملفات فعلية. - شحن استدعاء متعدد الخطوات. اسأل: "اعثر على أقدم منشور حول MCP ولخّصه." يجب أن يسلسل Claude:
search_code→read_file→ تلخيص. - شحن مسار الكتابة. اطلب من Claude إنشاء مشكلة في مستودع تجريبي. تأكد من ظهور المشكلة على github.com.
- التقط Claude Desktop مع رمز المطرقة + استدعاء أداة ناجح. هذا إثبات عملك.
شحنت للتو خادم MCP يعطي Claude وصولاً منظماً ومصادقاً عليه إلى كودك. كل نمط من الوحدات الخمس السابقة يعمل الآن في الإنتاج.
التالي (اختياري): وسّع هذا الخادم بنقل ثانٍ (Streamable HTTP) ليعمل من دردشة مستضافة، ليس فقط Claude Desktop. :::