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

ما هو بروتوكول سياق النموذج؟
بروتوكول سياق النموذج (MCP) هو بروتوكول مفتوح تم تقديمه من قبل Anthropic لتوحيد طريقة توصيل مساعدي الذكاء الاصطناعي مع الأنظمة الخارجية. بدلًا من بناء تكاملات مخصصة لكل مصدر بيانات، يوفر MCP واجهة موحدة تسمح بـ:
- مساعدي الذكاء الاصطناعي (مثل Claude، ChatGPT أو تطبيقات LLM المخصصة) للاكتشاف واستخدام الأدوات
- المطورين لعرض مصادر البيانات والأدوات والنصوص عبر بروتوكول معياري
- التكامل السلس دون إعادة اختراع العجلة لكل حالة استخدام
يعمل البروتوكول على بنية تحتية للعميل-الخادم حيث:
- خوادم MCP (مساعدي الذكاء الاصطناعي) تكتشف وتستخدم القدرات
- خوادم MCP تكشف عن الموارد والأدوات والنصوص
- التواصل يحدث عبر JSON-RPC عبر stdio أو HTTP/SSE
إذا كنت مهتمًا بتنفيذ خوادم MCP في لغات أخرى، تحقق من دليلنا حول تنفيذ خادم MCP في Go، الذي يغطي مواصفات البروتوكول وبنية الرسالة بالتفصيل.
لماذا بناء خوادم MCP في Python؟
Python هو خيار ممتاز لتطوير خوادم MCP لأن:
- البيئة الغنية: المكتبات مثل
requests،beautifulsoup4،seleniumوplaywrightتجعل التنقيب عبر الإنترنت سهلًا - مكتبة MCP: المكتبة الرسمية لـ Python (
mcp) توفر دعمًا قويًا لتنفيذ الخوادم - تطوير سريع: بساطة Python تسمح بتطوير النماذج الأولية والتحديثات بسرعة
- التكامل مع الذكاء الاصطناعي / التعلم الآلي: سهولة التكامل مع مكتبات الذكاء الاصطناعي مثل
langchain،openaiوأدوات معالجة البيانات - دعم المجتمع: مجتمع كبير مع وثائق واسعة ومثالات
إعداد بيئة التطوير
أولًا، أنشئ بيئة افتراضية وثيّب المكتبات المطلوبة. استخدام البيئات الافتراضية ضروري لعزل مشاريع Python - إذا كنت بحاجة إلى تذكير، تحقق من دليلنا حول دليل venv للحصول على الأوامر التفصيلية وال أفضل الممارسات.
# إنشاء وتفعيل بيئة افتراضية
python -m venv mcp-env
source mcp-env/bin/activate # على Windows: mcp-env\Scripts\activate
# تثبيت مكتبة MCP ومحررات التنقيب عبر الإنترنت
pip install mcp requests beautifulsoup4 playwright lxml
playwright install # تثبيت محركات المتصفح لـ Playwright
البديل الحديث: إذا كنت تفضل حلولًا أسرع لحل الاعتماديات وتثبيتها، ففكر في استخدام uv - مدير حزم وبيئات Python الحديث، والتي يمكن أن تكون أسرع بكثير من pip للمشاريع الكبيرة.
بناء خادم MCP أساسي
لنبدأ ببنية خادم MCP بسيطة. إذا كنت جديدًا على Python أو بحاجة إلى مرجع سريع للقواعد النحوية والأنماط الشائعة، فدليلنا حول دليل Python يوفر لمحة شاملة حول أساسيات Python.
import asyncio
from mcp.server import Server
from mcp.types import Tool, TextContent
import mcp.server.stdio
# إنشاء خادم
app = Server("websearch-scraper")
@app.list_tools()
async def list_tools() -> list[Tool]:
"""تحديد الأدوات المتاحة"""
return [
Tool(
name="search_web",
description="البحث عبر الإنترنت عن المعلومات",
inputSchema={
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "استعلام البحث"
},
"max_results": {
"type": "number",
"description": "عدد النتائج القصوى",
"default": 5
}
},
"required": ["query"]
}
)
]
@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
"""معالجة تنفيذ الأدوات"""
if name == "search_web":
query = arguments["query"]
max_results = arguments.get("max_results", 5)
# تنفيذ منطق البحث هنا
results = await perform_web_search(query, max_results)
return [TextContent(
type="text",
text=f"نتائج البحث عن '{query}':\n\n{results}"
)]
raise ValueError(f"أداة غير معروفة: {name}")
async def perform_web_search(query: str, max_results: int) -> str:
"""مُحلّف لتنفيذ البحث الفعلي"""
return f"تم العثور على {max_results} نتائج لـ: {query}"
async def main():
"""تشغيل الخادم"""
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
await app.run(
read_stream,
write_stream,
app.create_initialization_options()
)
if __name__ == "__main__":
asyncio.run(main())
تنفيذ وظائف البحث عبر الإنترنت
الآن دعنا نقوم بتنفيذ أداة بحث عبر الإنترنت حقيقية باستخدام DuckDuckGo (التي لا تتطلب مفاتيح API):
import asyncio
import requests
from bs4 import BeautifulSoup
from urllib.parse import quote_plus
async def search_duckduckgo(query: str, max_results: int = 5) -> list[dict]:
"""البحث في DuckDuckGo وتحليل النتائج"""
url = f"https://html.duckduckgo.com/html/?q={quote_plus(query)}"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}
try:
response = requests.get(url, headers=headers, timeout=10)
response.raise_for_status()
soup = BeautifulSoup(response.text, 'html.parser')
results = []
for result in soup.select('.result')[:max_results]:
title_elem = result.select_one('.result__title')
snippet_elem = result.select_one('.result__snippet')
url_elem = result.select_one('.result__url')
if title_elem and snippet_elem:
results.append({
"title": title_elem.get_text(strip=True),
"snippet": snippet_elem.get_text(strip=True),
"url": url_elem.get_text(strip=True) if url_elem else "غير متوفر"
})
return results
except Exception as e:
raise Exception(f"فشل البحث: {str(e)}")
def format_search_results(results: list[dict]) -> str:
"""تنسيق نتائج البحث للعرض"""
if not results:
return "لم يتم العثور على نتائج."
formatted = []
for i, result in enumerate(results, 1):
formatted.append(
f"{i}. {result['title']}\n"
f" {result['snippet']}\n"
f" الرابط: {result['url']}\n"
)
return "\n".join(formatted)
إضافة قدرات التنقيب عبر الإنترنت
دعنا نضيف أداة لتنقيب واستخراج المحتوى من صفحات الويب. عند التنقيب عبر HTML للمستخدم مع LLMs، قد ترغب أيضًا في تحويله إلى تنسيق Markdown لتحسين المعالجة. لهذا الغرض، تحقق من دليلنا الشامل حول تحويل HTML إلى Markdown باستخدام Python، الذي يقارن 6 مكتبات مختلفة مع معايير الأداء وتوصيات عملية.
from playwright.async_api import async_playwright
import asyncio
async def scrape_webpage(url: str, selector: str = None) -> dict:
"""تنقيب المحتوى من صفحة ويب باستخدام Playwright"""
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
page = await browser.new_page()
try:
await page.goto(url, timeout=30000)
# انتظر لتحميل المحتوى
await page.wait_for_load_state('networkidle')
if selector:
# استخراج عنصر محدد
element = await page.query_selector(selector)
content = await element.inner_text() if element else "لم يتم العثور على المحدد"
else:
# استخراج المحتوى الرئيسي
content = await page.inner_text('body')
title = await page.title()
return {
"title": title,
"content": content[:5000], # تقييد طول المحتوى
"url": url,
"success": True
}
except Exception as e:
return {
"error": str(e),
"url": url,
"success": False
}
finally:
await browser.close()
# إضافة أداة التنقيب إلى خادم MCP
@app.list_tools()
async def list_tools() -> list[Tool]:
return [
Tool(
name="search_web",
description="البحث عبر الإنترنت باستخدام DuckDuckGo",
inputSchema={
"type": "object",
"properties": {
"query": {"type": "string", "description": "استعلام البحث"},
"max_results": {"type": "number", "default": 5}
},
"required": ["query"]
}
),
Tool(
name="scrape_webpage",
description="تنقيب المحتوى من صفحة ويب",
inputSchema={
"type": "object",
"properties": {
"url": {"type": "string", "description": "الرابط لتنقيبه"},
"selector": {
"type": "string",
"description": "محدد CSS خاص للمحتوى"
}
},
"required": ["url"]
}
)
]
تنفيذ خادم MCP الكامل
إليك تنفيذ خادم MCP كاملًا جاهزًا للإنتاج مع قدرات البحث والتنقيب:
#!/usr/bin/env python3
"""
خادم MCP لبحث وتنقيب الويب
يوفر أدوات للبحث عبر الإنترنت واستخراج المحتوى من الصفحات
"""
import asyncio
import logging
from typing import Any
import requests
from bs4 import BeautifulSoup
from urllib.parse import quote_plus
from playwright.async_api import async_playwright
from mcp.server import Server
from mcp.types import Tool, TextContent, ImageContent, EmbeddedResource
import mcp.server.stdio
# تكوين السجل
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("websearch-scraper")
# إنشاء خادم
app = Server("websearch-scraper")
# تنفيذ البحث
async def search_web(query: str, max_results: int = 5) -> str:
"""البحث في DuckDuckGo وإرجاع النتائج المُنسقة"""
url = f"https://html.duckduckgo.com/html/?q={quote_plus(query)}"
headers = {"User-Agent": "Mozilla/5.0"}
try:
response = requests.get(url, headers=headers, timeout=10)
soup = BeautifulSoup(response.text, 'html.parser')
results = []
for result in soup.select('.result')[:max_results]:
title = result.select_one('.result__title')
snippet = result.select_one('.result__snippet')
link = result.select_one('.result__url')
if title and snippet:
results.append({
"title": title.get_text(strip=True),
"snippet": snippet.get_text(strip=True),
"url": link.get_text(strip=True) if link else ""
})
# تنسيق النتائج
if not results:
return "لم يتم العثور على نتائج."
formatted = [f"تم العثور على {len(results)} نتائج لـ '{query}':\n"]
for i, r in enumerate(results, 1):
formatted.append(f"\n{i}. **{r['title']}**")
formatted.append(f" {r['snippet']}")
formatted.append(f" {r['url']}")
return "\n".join(formatted)
except Exception as e:
logger.error(f"فشل البحث: {e}")
return f"خطأ في البحث: {str(e)}"
# تنفيذ التنقيب
async def scrape_page(url: str, selector: str = None) -> str:
"""تنقيب محتوى الصفحة باستخدام Playwright"""
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
page = await browser.new_page()
try:
await page.goto(url, timeout=30000)
await page.wait_for_load_state('networkidle')
title = await page.title()
if selector:
element = await page.query_selector(selector)
content = await element.inner_text() if element else "لم يتم العثور على المحدد"
else:
content = await page.inner_text('body')
# تقييد طول المحتوى
content = content[:8000] + "..." if len(content) > 8000 else content
result = f"**{title}**\n\nالرابط: {url}\n\n{content}"
return result
except Exception as e:
logger.error(f"فشل التنقيب: {e}")
return f"خطأ في التنقيب: {str(e)}"
finally:
await browser.close()
# تعريف أدوات MCP
@app.list_tools()
async def list_tools() -> list[Tool]:
"""قائمة الأدوات المتاحة في MCP"""
return [
Tool(
name="search_web",
description="البحث عبر الإنترنت باستخدام DuckDuckGo. يعيد العناوين، المقتطفات، والروابط.",
inputSchema={
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "الاستعلام البحثي"
},
"max_results": {
"type": "number",
"description": "عدد النتائج القصوى (الافتراضي: 5)",
"default": 5
}
},
"required": ["query"]
}
),
Tool(
name="scrape_webpage",
description="استخراج محتوى من صفحة ويب. يمكن استهداف عناصر محددة باستخدام محددات CSS.",
inputSchema={
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "الرابط لتنقيبه"
},
"selector": {
"type": "string",
"description": "محدد CSS خاص لاستخراج محتوى محدد"
}
},
"required": ["url"]
}
)
]
@app.call_tool()
async def call_tool(name: str, arguments: Any) -> list[TextContent]:
"""معالجة تنفيذ الأدوات"""
try:
if name == "search_web":
query = arguments["query"]
max_results = arguments.get("max_results", 5)
result = await search_web(query, max_results)
return [TextContent(type="text", text=result)]
elif name == "scrape_webpage":
url = arguments["url"]
selector = arguments.get("selector")
result = await scrape_page(url, selector)
return [TextContent(type="text", text=result)]
else:
raise ValueError(f"أداة غير معروفة: {name}")
except Exception as e:
logger.error(f"فشل تنفيذ الأداة: {e}")
return [TextContent(
type="text",
text=f"خطأ في تنفيذ {name}: {str(e)}"
)]
async def main():
"""تشغيل خادم MCP"""
logger.info("تشغيل خادم WebSearch-Scraper MCP")
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
await app.run(
read_stream,
write_stream,
app.create_initialization_options()
)
if __name__ == "__main__":
asyncio.run(main())
تكوين خادم MCP
للاستخدام مع Claude Desktop أو أجهزة MCP الأخرى، أنشئ ملف تكوين:
لـ Claude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"websearch-scraper": {
"command": "python",
"args": [
"/path/to/your/mcp_server.py"
],
"env": {}
}
}
}
الموقع:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json - Linux:
~/.config/Claude/claude_desktop_config.json
اختبار خادم MCP
أنشئ نسخة اختبارية لتأكيد الوظائف:
import asyncio
import json
import sys
from io import StringIO
async def test_mcp_server():
"""اختبار خادم MCP محليًا"""
# اختبار البحث
print("اختبار البحث عبر الإنترنت...")
results = await search_web("دورة تدريبية MCP لـ Python", 3)
print(results)
# اختبار التنقيب
print("\n\nاختبار مُحلّل الصفحة...")
content = await scrape_page("https://example.com")
print(content[:500])
if __name__ == "__main__":
asyncio.run(test_mcp_server())
الميزات المتقدمة وال أفضل الممارسات
1. التحكم في معدل الطلب
قم بتطبيق التحكم في معدل الطلب لتجنب إرهاق الخوادم المستهدفة:
import time
from collections import defaultdict
class RateLimiter:
def __init__(self, max_requests: int = 10, time_window: int = 60):
self.max_requests = max_requests
self.time_window = time_window
self.requests = defaultdict(list)
async def acquire(self, key: str):
now = time.time()
self.requests[key] = [
t for t in self.requests[key]
if now - t < self.time_window
]
if len(self.requests[key]) >= self.max_requests:
raise Exception("تم تجاوز حد الطلب")
self.requests[key].append(now)
limiter = RateLimiter(max_requests=10, time_window=60)
2. التخزين المؤقت
أضف التخزين المؤقت لتحسين الأداء:
from functools import lru_cache
import hashlib
@lru_cache(maxsize=100)
async def cached_search(query: str, max_results: int):
return await search_web(query, max_results)
3. معالجة الأخطاء
قم بتطبيق معالجة الأخطاء الشاملة:
from enum import Enum
class ErrorType(Enum):
NETWORK_ERROR = "network_error"
PARSE_ERROR = "parse_error"
RATE_LIMIT = "rate_limit_exceeded"
INVALID_INPUT = "invalid_input"
def handle_error(error: Exception, error_type: ErrorType) -> str:
logger.error(f"{error_type.value}: {str(error)}")
return f"خطأ ({error_type.value}): {str(error)}"
4. التحقق من المدخلات
تحقق من المدخلات من المستخدم قبل المعالجة:
from urllib.parse import urlparse
def validate_url(url: str) -> bool:
try:
result = urlparse(url)
return all([result.scheme, result.netloc])
except:
return False
def validate_query(query: str) -> bool:
return len(query.strip()) > 0 and len(query) < 500
اعتبارات التوزيع
استخدام نقل SSE لتطبيقات الويب
لتطبيقات الويب، استخدم نقل SSE (Server-Sent Events). إذا كنت تفكر في توزيع خادمless، فقد تكون مهتمًا بمقارنة أداء AWS Lambda عبر JavaScript، Python، وGolang لاتخاذ قرار مستنير حول بيئة التشغيل الخاصة بك:
import mcp.server.sse
async def main_sse():
"""تشغيل الخادم مع نقل SSE"""
from starlette.applications import Starlette
from starlette.routing import Mount
sse = mcp.server.sse.SseServerTransport("/messages")
starlette_app = Starlette(
routes=[
Mount("/mcp", app=sse.get_server())
]
)
import uvicorn
await uvicorn.Server(
config=uvicorn.Config(starlette_app, host="0.0.0.0", port=8000)
).serve()
توزيع AWS Lambda
يمكن أيضًا توزيع خوادم MCP كوظائف AWS Lambda، خاصة عند استخدام نقل SSE. للحصول على دليل شامل حول توزيع Lambda:
- تطوير Lambda باستخدام AWS SAM + AWS SQS + Python PowerTools - تعلم أفضل الممارسات لتطوير Lambda في Python
- بناء Lambda ثنائي النمط مع Python وTerraform على AWS - نهج شامل لـ Infrastructure-as-Code
توزيع Docker
أنشئ ملف Dockerfile للنشر المُعبأ:
FROM python:3.11-slim
WORKDIR /app
# تثبيت الاعتماديات النظامية
RUN apt-get update && apt-get install -y \
wget \
&& rm -rf /var/lib/apt/lists/*
# تثبيت الاعتماديات لـ Python
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
RUN playwright install chromium
RUN playwright install-deps
# نسخ التطبيق
COPY mcp_server.py .
CMD ["python", "mcp_server.py"]
تحسين الأداء
العمليات غير المتزامنة
استخدم asyncio للعمليات المتزامنة:
async def search_multiple_queries(queries: list[str]) -> list[str]:
"""البحث في عدة استعلامات بشكل متزامن"""
tasks = [search_web(query) for query in queries]
results = await asyncio.gather(*tasks)
return results
إعادة استخدام الاتصالات
إعادة استخدام الاتصالات لتحسين الأداء:
import aiohttp
session = None
async def get_session():
global session
if session is None:
session = aiohttp.ClientSession()
return session
async def fetch_url(url: str) -> str:
session = await get_session()
async with session.get(url) as response:
return await response.text()
أفضل الممارسات في الأمان
- تعقيم المدخلات: قم دائمًا بتأكيد وتعقيم مدخلات المستخدم
- قائمة بيضاء للعناوين: تأكد من تنفيذ قائمة بيضاء للعناوين عند الاستكشاف
- التحكم في المهلة: ضع مهلة مناسبة لمنع استنفاد الموارد
- الحد من المحتوى: قم بتحديد حجم المحتوى المستخرج
- الاعتماد: قم بتنفيذ الاعتماد عند النشر
- HTTPS: استخدم HTTPS لنقل SSE في الإنتاج
العمل مع مزودي LLM المختلفين
على الرغم من أن MCP تم تطويره بواسطة Anthropic لـ Claude، فإن البروتوكول تم تصميمه ليكون مناسبًا لأي نموذج LLM. إذا كنت تبني خوادم MCP تتفاعل مع مزودي الذكاء الاصطناعي المتعددين وتتطلب مخرجات منظمة، فعليك مراجعة مقارنة المخرجات المنظمة عبر مزودي LLM الشائعة بما في ذلك OpenAI، Gemini، Anthropic، Mistral، وAWS Bedrock.
روابط مفيدة وموارد
- مستندات MCP الرسمية
- مكتبة MCP لـ Python على GitHub
- مواصفات MCP
- مستودع خوادم MCP الخاصة بـ Anthropic
- مستندات Playwright لـ Python
- مستندات BeautifulSoup
- أمثلة MCP المجتمعية
الموارد ذات الصلة
MCP وتنفيذ البروتوكول
- تنفيذ خادم MCP في Go - تعلّم كيفية تنفيذ MCP في Go مع هيكل الرسائل ومواصفات البروتوكول
تطوير Python
- قائمة مصطلحات Python - مرجع سريع لقواعد Python والأنماط
- قائمة مصطلحات venv - أوامر إدارة البيئات الافتراضية
- uv - مدير حزم Python - بديل حديث أسرع لـ pip
الاستكشاف عبر الويب ومعالجة المحتوى
- تحويل HTML إلى Markdown باستخدام Python - ضروري لمعالجة المحتوى المستخرج لاستخدامه من قبل LLM
موارد النشر بدون خادم
- مقارنة أداء AWS Lambda: JavaScript مقابل Python مقابل Golang - اختر وقت التشغيل المناسب لنشر MCP بدون خادم
- Lambda مع AWS SAM + SQS + Python PowerTools - أنماط تطوير Lambda جاهزة للإنتاج
- Lambda مزدوجة على AWS مع Python وTerraform - نهج بناء البنية ككود
دمج LLM
- مقارنة المخرجات المنظمة عبر مزودي LLM - OpenAI، Gemini، Anthropic، Mistral، وAWS Bedrock
الخاتمة
بناء خوادم MCP في Python يفتح إمكانات قوية لتوسيع مساعدي الذكاء الاصطناعي مع أدوات ومصادر بيانات مخصصة. القدرات المُقدمة هنا في البحث عبر الإنترنت والاستكشاف هي مجرد بداية - يمكنك توسيع هذه البنية لدمج قواعد البيانات، وواجهات برمجة التطبيقات، والأنظمة الملفية، وتقريبًا أي نظام خارجي.
ما زال بروتوكول سياق النموذج يتطور، ولكن نهجه المعياري لدمج أدوات الذكاء الاصطناعي يجعله تقنية مثيرة للتطوير لمحوّلي البرامج الذين يبنون الجيل القادم من التطبيقات المدعومة بالذكاء الاصطناعي. سواء كنت تبني أدوات داخلية لشركةك أو تبني خوادم MCP عامة للمجتمع، فإن Python يوفر أساسًا ممتازًا للتطوير والنشر السريع.