چیتشیت
ساخت یک سرور MCP
Building an MCP Server
اعتبارسنجی، توزیع (dispatch)، کپسولهسازی و کش سرور بیوضعیت MCP نسخه 2026-07-28. ابزارها، منابع، پرامپتها، خطمشی.
فهرست مطالب
اجرای سرورهای درس
کار و کد| وظیفه | کد |
|---|---|
| سرویسدهی روی stdio | python3 main.py |
| اجرای دموی متناهی | python3 main.py --demo |
| اجرای پورت TypeScript | npx tsx main.ts --demo |
| اجرای تستهای درس | python3 -m unittest discover tests -v |
ساخت یک درخواست به همراه _meta | make_request(2, "tools/list") |
| اجرای مدل پروژهی پایانی (capstone) | python3 code/main.py |
| بررسی TypeScript پروژهی پایانی | npm run typecheck |
حلقهی توزیع (dispatch loop)
گامبهگام- serve_stdioخواندن یک خط از stdin. ساختار JSON نامعتبر کد
-32700میدهد. - dispatchنبود
idیعنی اعلان است. سرور پاسخی ارسال نمیکند. - validate_requestبررسی
params._metaبرای نسخه و قابلیتها. - HANDLERSمسیریابی بر اساس
method. متد ناشناخته کد-32601میدهد. - completeاضافه کردن
resultTypeو serverInfo به نتیجهی کنترلکننده. - serve_stdioنوشتن یک خط JSON روی stdout، فلاش کردن، و فراموشی متادیتا.
اعتبارسنجی و کپسولهسازی تکتک درخواستها
کدpython
meta = params.get("_meta") # کد -32602 در صورت مفقود بودن
requested = meta.get(VERSION_KEY) # کلید protocolVersion
if requested not in SUPPORTED_VERSIONS:
raise RpcProblem(-32022, "Unsupported protocol version",
{"requested": requested, "supported": SUPPORTED_VERSIONS.copy()})
if not isinstance(meta.get(CAPABILITIES_KEY), dict):
raise RpcProblem(-32602, f"{CAPABILITIES_KEY} is required")
result = {"resultType": "complete", **payload,
"_meta": {SERVER_INFO_KEY: SERVER_INFO.copy()}}
if ttl_ms is not None: # لیستها، خواندنها، شناسایی
result["ttlMs"] = ttl_ms
result["cacheScope"] = cache_scope # مقدار پیشفرض "private"
ثبت یک ابزار و مجری آن
کدpython
TOOL_EXECUTORS = {"notes_search": exec_notes_search} # نام به تابع
def handle_tools_list(params):
return complete({"tools": sorted(TOOLS, key=lambda tool: tool["name"])},
ttl_ms=60_000, cache_scope="public")
def handle_tools_call(params):
executor = TOOL_EXECUTORS.get(name)
if executor is None: # نتیجه، نه خطای RPC
return complete({"content": [...], "isError": True})
try:
return complete({"content": executor(arguments), "isError": False})
except (KeyError, TypeError, ValueError) as exc:
return complete({"content": [...], "isError": True})
ابزار، منبع یا پرامپت
مقایسه| عامل | ابزار | منبع | پرامپت |
|---|---|---|---|
| نیت | انجام یک عملیات | خواندن محتوا در یک URI | شروع یک جریان کاری پیام |
| انتخابکننده | مدل یا برنامه کاربردی | میزبان، برنامه کاربردی یا کاربر | کاربر از طریق واسط کاربری میزبان |
| نتیجه | نتیجهی ساختاریافتهی اکشن | محتوای متنی یا باینری | پیامهای پرامپت |
| متدها | tools/list, tools/call | resources/list, resources/read | prompts/list, prompts/get |
| مثال | delete_note | notes://note-1 | review_note |
نکات
نکتهها- استفاده از
cacheScope: "private"برای دادههای کاربر. استفاده ازttlMs: 0برای اسرار. - رد کردن توصیفگرهایی که مشابه
password،secret،tokenیاauthorizationهستند. - محدود کردن بدنه درخواستها. سرور حافظه برای بالای 50000 بایت کد 413 ارسال میکند.
راهنماهای کش در درسها
پیشفرضها| تنظیم | پیشفرض | یادداشت |
|---|---|---|
`server/discover` ttl | 3_600_000 | میلیثانیه، cacheScope عمومی (public) |
`tools/list` ttl | 60_000 | عمومی (public). پروژهی پایانی از خصوصی (private) استفاده میکند |
`resources/list` ttl | 10_000 | درس 07، خصوصی (private) |
`resources/read` ttl | 5_000 | درس 07، خصوصی (private) |
`prompts/list` ttl | 600_000 | درس 10، عمومی (public) |
cache_scope | private | پیشفرض complete() در درس 07 |
`limit` of `notes_search` | 10 | عدد صحیح از 1 تا 50 |
MAX_TOOL_LIST_PAGES | 100 | سقف صفحهبندی کلاینت درس 28 |
کدهای خطا
عیبیابیخطای تجزیه
-32700- علت
- خط موردنظر JSON نیست
- راهحل
- ارسال دقیقاً یک شیء JSON در هر خط
-32600درخواست نامعتبر- علت
- نبود
jsonrpcنسخه 2.0 یا نبود متد - راهحل
- اصلاح ساختار پوششی (envelope)
-32602پارامترهای نامعتبر- علت
- مفقود بودن
params._meta - راهحل
- ارسال نسخه و قابلیتها در هر نوبت
-32022- علت
- رشتهی نسخهی پشتیبانینشده
- راهحل
- تلاش مجدد با نسخهای از
supported
-32601- علت
- متد در
HANDLERSنیست - راهحل
- افزودن کنترلکننده یا اصلاح نام آن
-32602رویresources/read- علت
- URI ناشناخته یا نامعتبر
- راهحل
- هرگز یک خواندن خالی برنگردانید
HTTP
400و-32020- علت
- هدر
Mcp-Param-*با بدنه همخوانی ندارد - راهحل
- رد کردن قبل از توزیع (dispatch)
شکست در تجزیه کلاینت
- علت
- لاگها روی stdout
- راهحل
- نوشتن پیامهای تشخیصی روی stderr
قراردادهای ابزار
مفهومها- inputSchema
- اسکیمای JSON. در صورت نبود
$schema، گویش پیشفرض 2020-12 است. - بدون آرگومان
{"type": "object", "additionalProperties": false}.- outputSchema
- اختیاری. پس از انتشار، هر نتیجه شامل
structuredContentمنطبق خواهد بود. - structuredContent
- هر مقدار معتبر JSON. همچنین ارسال JSON به عنوان یک بلوک متنی.
- بلوکهای محتوا
text,image,audio,resource_link,resource.- x-mcp-header
- نگاشت یک آرگومان سطح بالا به
Mcp-Param-{name}. فقط رشته، عدد صحیح یا بولین. - انکودینگ هدر
- مقادیر غیر ASCII یا دارای فاصلهگذاری از
=?base64?{Base64UTF8}?=استفاده میکنند. - مکاننماها (Cursors)
- مات و نامشخص (Opaque). مقدار خالی برای
nextCursorمعتبر است. توقف فقط باNone.
پروژهی سرور حافظه
دستورها| دستور | عملکرد |
|---|---|
project_test.py --init <workspace> | میز کار شما را میسازد. یک کد اولیهی دستنخورده شکست میخورد |
project_test.py --all --path <workspace> --strict | نمرهدهی به هر چهار مرحله |
cli.ts --data-dir <dir> --put <file> | افزودن نسخهی بازنگری 1 به لاگ JSONL |
cli.ts --data-dir <dir> --query "<text>" | امتیاز: 0.6 واژگانی بهعلاوه 0.4 کسینوسی |
cli.ts --data-dir <dir> --history <id> | تکتک نسخههای بازنگری به همراه منبع آنها |
--put <file> --revision 1 | افزودن نسخهی بازنگری 2. نسخهی 0 دارای تداخل است |
cli.ts --data-dir <dir> --serve --port 8788 | سرویسهای REST و MCP روی یک منبع ذخیرهسازی مشترک. نیازمند MEMORY_TOKEN |
گیت خطمشی پروژهی پایانی
معماریسؤالات متداول
پرسش و پاسخ- خطای ابزار یا خطای پروتکل؟
- یک فراخوانی معتبرِ ناموفق
isError: trueبرمیگرداند. یک ساختار پوششی نامعتبر، خطای JSON-RPC میدهد. - آیا شناسایی (discovery) قفل سرور را باز میکند؟
- خیر.
tools/listنیز متادیتای درخواست مشابهی دارد. - آیا سرور میتواند از کلاینت ورودی بخواهد؟
- پاسخ
input_requiredبرمیگرداند. خودش هرگز مستقیماً درخواستی ارسال نمیکند. - کلاینت چطور متوجه تغییر یک منبع میشود؟
- سابسکریپشن
subscriptions/listenباز میکند. سپس دوبارهresources/readرا فراخوانی میکند. - آیا حاشیهنویسیها (annotations) دسترسی را احراز میکنند؟
- خیر.
readOnlyHintوdestructiveHintصرفاً راهنما هستند.