در معماریهای توزیعشده و مقیاسپذیر، نگهداری وضعیت نشست (Session State) یکی از بزرگترین چالشهای توسعهدهندگان است. در نسخههای مدرن پروتکل MCP (نسخهی 2026-07-28)، سرور دیگر نیازی به ذخیرهی وضعیت دستدادن (Handshake) ندارد و هر درخواست با داشتن متادیتای کامل، بهصورت مستقل پردازش میشود. این طراحی مانع از تداخل دادهها میان ورکرها و کلاینتهای مختلف در محیطهای ابری میشود.
اگر میخواهید ابزارهای مدلهای زبانی را بهشکل استاندارد و بدون باگهای رایج نشستهای قدیمی پیادهسازی کنید، یادگیری قراردادهای مدرن MCP، متد اجباری server/discover و مدیریت اعتبارسنجی مستقل برای هر درخواست حیاتی است. در این درس یاد میگیرید چگونه با استفاده از صرفاً کتابخانههای استاندارد Python و TypeScript یک سرور امن، قطعی و بیوضعیت بسازید.
یک سرور مدرن MCP هیچ دستدادنی (Handshake) را به خاطر نمیسپارد؛ متادیتا را در تکتک درخواستها اعتبارسنجی میکند، یک گرداننده (Handler) را اجرا میکند و یک نتیجهی نوعدار برمیگرداند.
اهداف یادگیری
- پیادهسازی متد اجباری
server/discoverبرای نسخهی2026-07-28پروتکل MCP. - اعتبارسنجی نسخهی پروتکل و قابلیتهای کلاینت در هر درخواست.
- ارائهی ابزارها، منابع و پرامپتها با ترتیب قطعی در فهرستها.
- بازگرداندن
resultType، هویت سرور و راهنماهای حافظهی موقت (Cache hints) روی نتایج درست. - ارائهی همین قرارداد بیوضعیت (Stateless) روی stdio تفکیکشده با خط جدید در Python و TypeScript.
صورت مسئله
ساخت سروری که قابلیتهای کلاینت را بعد از پیام اول ذخیره میکند ساده است، اما اجرا و نگهداری آن در محیط عملیاتی دردسرساز خواهد بود. یک فرایند (Process) واحد ممکن است به چند کلاینت متوالی پاسخ دهد؛ یک درخواست از راه دور ممکن است روی ورکر متفاوتی بنشیند؛ و اعلام قابلیتهای منسوخ میتواند رفتار سیستم را فراتر از مرزهای مجاز نشت دهد.
نسخهی 2026-07-28 پروتکل MCP این بخش از مسئله را در لایهی پروتکل حل میکند: هر درخواست باید خودتوصیفگر باشد. برنامهی شما همچنان میتواند یادداشتهای پایدار، جابها یا هندلهای صریح وضعیت را نگه دارد؛ چیزی که حق نگهداری آن را ندارد، وضعیت پنهان پروتکل است؛ وضعیتی که نحوهی رمزگشایی (Decode) درخواستهای بعدی را تغییر دهد.
در این درس یک سرور یادداشت را دو بار میسازیم. نسخههای Python و TypeScript برای هستهی پروتکل فقط از کتابخانههای استاندارد خود استفاده میکنند. هر دو نسخه متدهای یکسانی ارائه میدهند و قرارداد انتقال یکسانی را اعمال میکنند.
مفاهیم پایه
حلقهی مدرن توزیع درخواست (Dispatch loop)
read one JSON-RPC line
parse the envelope
if it is a notification, do not respond
validate params._meta for this request
route by method
wrap success with resultType and serverInfo
write one JSON-RPC response line
forget request-scoped metadata
سه قاعدهی stdio همچنان اهمیت دارند:
- فقط پیامهای JSON-RPC را در stdout بنویسید؛ اطلاعات تشخیصی (Diagnostics) را به stderr بفرستید.
- پیامها را با خط جدید جدا کنید و خروجی هر پاسخ را خالی (Flush) کنید.
- با رسیدن stdin به انتهای فایل (EOF)، بلافاصله خارج شوید.
طول عمر فرایند، همان طول عمر بستر انتقال (Transport) است، نه یک نشست مدرن MCP.
اعتبارسنجی درخواست
هر درخواست باید شامل این موارد باشد:
{
"params": {
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {},
"io.modelcontextprotocol/clientInfo": {
"name": "notes-client",
"version": "1.0.0"
}
}
}
}
دو فیلد اول اجباری هستند؛ ارائهی clientInfo توصیه میشود. ساختار هویتی ارائهشده را اعتبارسنجی کنید، اما آن را احراز هویت تلقی نکنید.
اگر نسخه پشتیبانی نمیشود، کد خطای -32022 را همراه با مقادیر requested و supported برگردانید. نبود متادیتای درخواست بهمعنای پارامترهای نامعتبر است (کد -32602). هرگز فیلدهای خالی را بر اساس فراخوانیهای قبلی پر نکنید.
متد اجباری Discover
سرورهای مدرن حتماً باید server/discover را پیادهسازی کنند. یک نتیجهی کامل discovery شامل نسخههای مدرن پشتیبانیشده، قابلیتها، دستورالعملهای اختیاری، راهنماهای حافظهی موقت، و هویت سرور در فیلد _meta است:
{
"resultType": "complete",
"supportedVersions": ["2026-07-28"],
"capabilities": {
"tools": {"listChanged": false},
"resources": {"listChanged": false, "subscribe": false},
"prompts": {"listChanged": false}
},
"ttlMs": 3600000,
"cacheScope": "public",
"_meta": {
"io.modelcontextprotocol/serverInfo": {
"name": "notes-server",
"version": "2.0.0"
}
}
}
متد Discovery قفل سرور را باز نمیکند. کلاینت میتواند بدون فراخوانی Discovery مستقیماً tools/list را صدا بزند، چون tools/list هماکنون نیز همان متادیتای درخواست را همراه خود دارد.
ابزارها
متد tools/list فهرستی قطعی از توصیفکنندههای ابزار را برمیگرداند. ترتیب پایدار آیتمها کش پاسخها را بهبود میدهد و کانتکست مدل را پایدار نگه میدارد. نتیجهی این متد به ttlMs و cacheScope هم نیاز دارد.
متد tools/call بلوکهای محتوا و مقدار isError را برمیگرداند. وقتی انولوپ پروتکل یا پارامترهای متد نامعتبر هستند، از خطای JSON-RPC استفاده کنید. اما زمانی که فراخوانی ابزار معتبر است ولی خود ابزار با شکست مواجه میشود، مقدار isError: true را برگردانید.
یادداشتهای توصیفی ابزارها (Tool annotations) صرفاً راهنما هستند، نه سازوکار اعمال اجبار:
readOnlyHintdestructiveHintidempotentHintopenWorldHint
میزبان (Host) باید از این فیلدها برای تأیید گرفتن از کاربر و نحوهی نمایش رابط کاربری استفاده کند. خود سرور همچنان موظف است اعطای مجوز واقعی را اعمال کند.
منابع
متد resources/list توصیفکنندههای پایدار URI را برمیگرداند. متد resources/read محتواهای نوعدار را تحویل میدهد. هر دو متد در نسخهی 2026-07-28 کشپذیر هستند؛ بنابراین هر دو شامل ttlMs و cacheScope میشوند.
برای دادههای یادداشتِ مختص کاربر، از "cacheScope: "private استفاده کنید. یک حافظهی موقت اشتراکی نباید پاسخی با دامنهی خصوصی را در دیگر بسترهای احراز دسترسی استفاده کند.
ارسال تغییرات در ساختار مدرن از resources/subscribe استفاده نمیکند. کلاینت استریم subscriptions/listen را باز میکند و دستهبندیهای resourceSubscriptions یا تغییرات فهرست را درخواست میدهد. این روند را در درس 10 پیادهسازی میکنیم.
پرامپتها
متد prompts/list کشپذیر و قطعی است. متد prompts/get یک پرامپت نامگذاریشده را با آرگومانهای ارسالی رندر میکند. نتیجهی رندر پرامپت کامل است، اما جزو نتایج خواندنی یا فهرستی کشپذیر نیست که به راهنماهای کش نیاز داشته باشد.
تمام نتایج موفق نوعدار هستند
در این مثالها برای تمام پاسخهای موفق از یک پوششدهنده (Wrapper) مشترک استفاده میکنیم:
def complete(payload):
return {
"resultType": "complete",
**payload,
"_meta": {SERVER_INFO_KEY: SERVER_INFO},
}
گردانندههای List، Read و Discovery مقادیر ttlMs و cacheScope را هم اضافه میکنند. متمرکز کردن این پوششدهنده جلوی این را میگیرد که یک گرداننده فیلدهای مدرن پاسخ را بیسرصدا از قلم بیندازد.
ممنوعیت درخواستهای آغازشده از سمت سرور
یک سرور مدرن میتواند اعلانهای (Notifications) مربوط به درخواست کلاینت را ارسال کند، یا روی استریم subscriptions/listen که کلاینت باز کرده اعلان بفرستد؛ اما خودش هرگز نباید یک درخواست مستقل JSON-RPC ایجاد کند.
وقتی گرداننده به نمونهبرداری (Sampling)، استخراج داده (Elicitation) یا ورودیهای Roots نیاز دارد، یک پاسخ input_required برمیگرداند. کلاینت درخواستهای ورودیِ تعبیهشده را برآورده میکند و متد اصلی را با شناسهی درخواست (Request id) جدید دوباره فراخوانی میکند. در درس 11 این الگوی درخواست چندرفتوبرگشتی (Multi Round-Trip Request) را بررسی خواهیم کرد.
سازگاری صریح با نسخههای قدیمی
سروری که از هر دو دوره پشتیبانی میکند، میتواند دستدادن نسخهی 2025-11-25 را روی یک branch کاملاً جداگانه برای کدهای قدیمی پیادهسازی کند. این سرور وقتی فیلدهای اجباری _meta مدرن حضور دارند رفتار مدرن را پیش میگیرد، و وقتی پیام initialize را دریافت میکند به سراغ رفتار قدیمی میرود.
درخواستهای 2026-07-28 را به مسیر دستدادن قدیمی هدایت نکنید. همچنین فیلدهای مدرن resultType را روی نتایج مقداردهی اولیهی قدیمی قرار ندهید. کد این درس عمداً فقط بهصورت مدرن نوشته شده است تا اصول تغییرناپذیر آن واضح و مشهود بماند.
استفاده از ابزارهای واقعی
دموی محدود و تستهای سرور Python را اجرا کنید:
cd code
python3 main.py --demo
python3 -m unittest discover tests -v
نسخهی TypeScript را با یک رانر TypeScript اجرا کنید:
npx tsx main.ts --demo
این دمو متد server/discover را صدا میزند، تکتک بخشهای پایه را فهرست میکند، ابزارها را فرامیخواند و نحوهی نمایش خطای عدم پشتیبانی از نسخه را نشان میدهد. هر درخواست مدرن متادیتا را تکرار میکند و هر پاسخ موفق، هویت سرور را به همراه دارد.
خروجی این درس
خروجی این درس فایل outputs/skill-mcp-server-scaffolder.md است. این فایل ساختار سروری مدرن را همراه با قرارداد Discovery، اعتبارسنجی در تکتک درخواستها، فهرستهای قطعیِ کشپذیر و یک تطبیقدهندهی (Adapter) اختیاری و مجزا برای نسخههای قدیمی تولید میکند.
تمرینها
- قابلیتها را از یک درخواست حذف کنید و ثابت کنید سرور ادعای مطرحشده در درخواست قبلی را بازیافت نمیکند.
- ترتیب درج در
TOOLS،PROMPTSو یادداشتها را معکوس کنید؛ سپس تأیید کنید خروجی همهی فهرستها همچنان ثابت و پایدار باقی میماند. - یک ابزار تخریبی به نام
notes_deleteاضافه کنید و داخل مجری (Executor) آن یک بررسی اعطای مجوز قرار دهید. فیلدdestructiveHintرا صرفاً به عنوان یک راهنمای رابط کاربری نگه دارید. - متد
resources/templates/listرا همراه باttlMs،cacheScopeو ترتیب قطعی اضافه کنید. - یک تطبیقدهندهی جداگانه برای نسخهی قدیمی
2025-11-25بسازید و با نوشتن تست ثابت کنید که درخواست مدرن هرگز وارد این مسیر نمیشود.
اصطلاحات کلیدی
| اصطلاح | معنا |
|---|---|
| سرور بیوضعیت (Stateless server) | هر درخواست را بر اساس متادیتای خودش پردازش میکند، بدون اینکه وضعیت نشست پروتکل را به خاطر بسپارد |
server/discover |
متد اجباری نسخههای مدرن که نسخهها و قابلیتها را اعلام میکند |
| نتیجهی کامل (Complete result) | پاسخ موفق مدرن با مقدار "resultType: "complete |
| نتیجهی کشپذیر (Cacheable result) | نتیجهی متدهای Discovery، List یا Resource-read همراه با ttlMs و cacheScope |
| فهرست قطعی (Deterministic list) | رجیستری منطقی یکسان که همیشه آیتمها را با همان ترتیب ثابت تولید میکند |
| هویت سرور (Server identity) | فیلد توصیهشدهی io.modelcontextprotocol/serverInfo در بخش _metaی نتیجه |
| خطای ابزار (Tool error) | فراخوانی معتبر ابزار که محتوا را همراه با isError: true برمیگرداند |
| خطای پروتکل (Protocol error) | درخواست نامعتبر در لایهی JSON-RPC یا MCP که در قالب شیء error برگردانده میشود |
