MCP C# SDK 2.0؛ ساخت سرور Stateless با ASP.NET Core

MCP C# SDK 2.0؛ ساخت سرور Stateless با ASP.NET Core

نسخه 2.0 از SDK رسمی MCP برای C#، مشخصات 2026-07-28 را پیاده سازی می کند؛ بزرگ ترین بازطراحی پروتکل از زمان معرفی آن. نتیجه برای توسعه دهنده .NET روشن است: MCP روی HTTP به صورت پیش فرض Stateless شده، Sticky Session دیگر الزام پروتکل نیست و ابزارهای تعاملی می توانند بدون Session طولانی از چند رفت وبرگشت استفاده کنند.

در یک نگاه

  • Handshakeهای initialize و initialized در Wire Format جدید حذف شده اند.
  • Header مربوط به Mcp-Session-Id دیگر جزء مسیر جدید نیست.
  • Version و Capability هر Request همراه همان Request ارسال می شوند.
  • Mcp-Method، Mcp-Name و Mcp-Param-* به Proxy، WAF و Router اجازه تصمیم گیری بدون خواندن Body می دهند.
  • Multi Round-Trip Requests یا MRTR تعامل هایی مانند تأیید کاربر، Sampling و Roots را بدون Session انجام می دهد.
  • Stateless در SDK 2.0 پیش فرض است، اما Stateful Mode همچنان به صورت Opt-in وجود دارد.
  • Apps و Tasks به عنوان Extensionهای اختیاری از Core جدا شده اند.

نسخه 2 و مشخصات 2026-07-28 چه چیزی را عوض کردند؟

نسخه های اولیه MCP روی Streamable HTTP برای شروع کار Handshake انجام می دادند. Server یک Mcp-Session-Id برمی گرداند و Client آن را در درخواست های بعدی حمل می کرد. این طراحی برای تعامل پیوسته قابل فهم بود، اما در مقیاس افقی هزینه داشت: Request بعدی باید به همان Instance برسد یا State Session در Storage مشترک نگه داری شود. Load Balancer به Sticky Session نیاز پیدا می کرد و Serverless یا Edge Deployment پیچیده تر می شد.

بازنگری 2026-07-28 مدل را به Requestهای Self-contained نزدیک می کند. Version، Capability و اطلاعات لازم در هر Request قرار می گیرند و مسیر اصلی دیگر به initialize و Session Header وابسته نیست. نتیجه این است که هر Instance سالم می تواند هر Request را پاسخ دهد. SDK C# 2.0 این رفتار را با Stateless=true به صورت پیش فرض ارائه می کند و از زیرساخت طبیعی ASP.NET Core مانند Routing، Middleware، Header، Load Balancing و Horizontal Scaling بهره می برد.

Backward Compatibility بخش مهم انتشار است. مایکروسافت صریحاً گفته ارتقا به SDK 2.0 کد پایدار نسخه 1 را مجبور به بازنویسی نمی کند و Client و Serverهای قبلی همچنان کار می کنند. بااین حال، «Compile شدن» با «استفاده از مسیر جدید» یکسان نیست. برای اینکه Client جدید مستقیماً پروتکل 2026-07-28 را بگیرد، Transport باید Stateless باشد و وابستگی پنهان به Session شناسایی شود.

Stateless پیش فرض چه اثری بر معماری دارد؟

Stateless Protocol به معنی Stateless Application نیست. یک ابزار مرورگر ممکن است Browser Context، یک ابزار خرید ممکن است Basket و یک ابزار پردازش فایل ممکن است Job طولانی داشته باشد. تفاوت این است که State نباید در Session حمل ونقل پنهان شود. ابزار نخست یک Handle مانند browserId یا basketId می سازد و مدل آن شناسه را در فراخوانی بعدی برمی گرداند. این طراحی State را قابل مشاهده، قابل ذخیره، قابل انقضا و مستقل از Instance می کند.

برای مقیاس افقی، این تغییر ارزش زیادی دارد. Pod جدید بدون دانستن Session قبلی Request را می گیرد، Auto Scaling ساده تر می شود و Failover کمتر به Affinity وابسته است. همچنین تست Load و Disaster Recovery روشن تر است؛ State در Database، Cache یا سرویس اختصاصی قرار دارد و Lifecycle آن صریح مدیریت می شود. از سوی دیگر، تیم باید Idempotency، Expiration، Ownership و Authorization Handleها را درست طراحی کند. Stateless بودن Transport، این مسئولیت ها را حذف نمی کند؛ فقط جای درست آن ها را آشکار می کند.

using ModelContextProtocol.Server;
using System.ComponentModel;

var builder = WebApplication.CreateBuilder(args);

builder.Services
  .AddMcpServer()
  .WithHttpTransport()
  .WithToolsFromAssembly();

var app = builder.Build();

app.MapMcp();
app.Run("http://localhost:3001");

[McpServerToolType]
public static class EchoTools
{
  [McpServerTool, Description("Returns the supplied message.")]
  public static string Echo(string message)
  {
    return $"hello {message}";
  }
}

در SDK 2، WithHttpTransport به صورت پیش فرض مسیر Stateless را فعال می کند. این Server را می توان پشت Round-robin Load Balancer قرار داد و Instanceها نیازی به Sync Session پروتکل ندارند. برای Production، HTTPS، Authentication، Rate Limit و Observability را به Pipeline اضافه کنید؛ نمونه مینیمال فقط شکل پایه Transport و Tool Discovery را نشان می دهد.

Headerهای استاندارد و زیرساخت HTTP

مشخصات جدید Headerهایی را استاندارد کرده که اطلاعات لازم برای زیرساخت میانی را بازتاب می دهند. فراخوانی tools/call می تواند Mcp-Method: tools/call و Mcp-Name: get_order_status داشته باشد. پارامتر انتخابی نیز با Attribute به Mcp-Param-* ارتقا پیدا می کند. Load Balancer، API Gateway، WAF و سامانه Observability می توانند بدون Parse کردن JSON-RPC Body روی نام Tool، Region یا Tenant تصمیم بگیرند.

[McpServerTool(Name = "get_order_status")]
[Description("Gets the order status from a regional service.")]
public static async Task<string> GetOrderStatus(
  OrdersServiceClient orders,
  [McpHeader("Region")] string region,
  string orderId)
{
  return await orders.GetStatusAsync(region, orderId);
}

Attribute مربوط به Header پارامتر را در Schema علامت می زند تا Client بداند آن مقدار را در Header نیز منعکس کند. Body همچنان Source of Truth است. اگر Header و Body ناسازگار باشند، Server Request را رد می کند و حدس نمی زند کدام درست است. این اصل از Routing اشتباه، دست کاری Header و اختلاف Interpretation جلوگیری می کند. برای مقدارهای غیر ASCII نیز Encoding تعریف شده است تا Header معتبر بماند.

این قابلیت برای Multi-region Deployment بسیار عملی است. فرض کنید Tool وضعیت سفارش باید به Region محل داده Route شود. Router با Mcp-Param-Region Request را به Cluster درست می فرستد، بدون اینکه Body را باز کند. WAF می تواند Rule متفاوت برای Tool حساس اعمال کند و Metricها می توانند Latency هر Tool را جدا نمایش دهند. البته قرار دادن داده حساس در Header مناسب نیست؛ Headerها معمولاً در Log، Trace و Proxy قابل مشاهده ترند. فقط پارامتر لازم برای Routing یا Policy را Promote کنید.

Multi Round-Trip Requests چگونه کار می کند؟

بعضی Toolها در یک رفت وبرگشت کامل نمی شوند. بستن تیکت نیاز به تأیید کاربر دارد، خلاصه سازی ممکن است از LLM Client کمک بخواهد و ابزار فایل باید Workspace Rootهای مجاز را بداند. در مدل قدیمی، Server وسط Call به Client درخواست می داد و Session باز می ماند. MRTR این Interaction را به Result و Retry تبدیل می کند: Server می گوید Input لازم است، Client آن را تهیه می کند و همان Tool Call را با پاسخ و requestState تکرار می کند.

Server یک InputRequiredResult با resultType برابر input_required، مجموعه Input Request و یک requestState مبهم برمی گرداند. Client می تواند کاربر را Prompt کند، Sampling انجام دهد یا Roots را بخواند. سپس inputResponses و همان requestState را در Call بعدی می فرستد. چند دور امکان پذیر است و Continuity داخل Payload حرکت می کند، نه Session پنهان. requestState باید کمینه، غیرحساس و در برابر دست کاری محافظت شده باشد؛ برای State پیچیده بهتر است یک شناسه کوتاه به Storage Server اشاره کند.

if (string.IsNullOrWhiteSpace(closeReason))
{
  throw new InputRequiredException(
    inputRequests: new Dictionary<string, InputRequest>
    {
      ["closeReason"] = InputRequest.ForElicitation(
        new ElicitRequestParams
        {
          Message = "Please confirm the close reason."
        })
    },
    requestState: ticketId.ToString());
}

return $"Closed ticket {ticketId}: {closeReason}";

SDK برای Clientهای قدیمی نیز Bridge دارد. اگر Client قدیمی Sessionدار باشد، همان Exception می تواند به Elicitation قبلی تبدیل شود. اگر Client قدیمی و بدون Session باشد، Tool باید Fallback داشته باشد؛ مثلاً پیام راهنما برگرداند و از Caller بخواهد closeReason را در Call بعدی صریح بفرستد. Propertyهایی مانند IsMrtrSupported کمک می کنند Tool مسیر مناسب را انتخاب کند.

سازگاری با Clientهای قدیمی

نوع ClientServer Stateless v2تعامل چندمرحله ایاقدام
Client 2026-07-28NativeMRTR Nativeمسیر پیشنهادی
Client قدیمی با SessionFallback سازگارBridge به Elicitationتست Compatibility
Client قدیمی بدون SessionCall پایه قابل پشتیبانینیازمند Argument صریح یا پیام راهنماFallback طراحی کنید
Server v1 موجودبا Client سازگار طبق مسیر Legacyرفتار قبلیمهاجرت مرحله ای

سازگاری دوطرفه باید در Matrix تست شود، نه فقط از متن Release برداشت شود. نسخه Client، Transport و Capabilityها را ثبت کنید. Toolهای ساده، Tool دارای Handle و Tool MRTR را جدا آزمایش کنید. اگر Diagnosticهای MCP9004 یا MCP9006 ظاهر شدند، احتمالاً به Endpoint یا Option وابسته به Stateful Mode تکیه دارید. تصمیم بگیرید آن رفتار واقعاً لازم است یا می تواند با Handle و Request مستقل بازطراحی شود.

ساخت یک MCP Server با ASP.NET Core

  1. یک پروژه ASP.NET Core مینیمال با نسخه .NET پشتیبانی شده ایجاد کنید.
  2. پکیج های SDK رسمی MCP نسخه 2 را اضافه و Version را Pin کنید.
  3. AddMcpServer، WithHttpTransport و Discovery ابزارها را تنظیم کنید.
  4. Toolهای کوچک، Typed و بدون Side Effect ناخواسته طراحی کنید.
  5. برای عملیات نوشتن، Authorization، Idempotency و Confirmation را صریح کنید.
  6. Health Check، Structured Logging، Trace و Metric سطح Tool اضافه کنید.
  7. Server را پشت HTTPS، Rate Limit و WAF یا Gateway قرار دهید.
  8. Client جدید و قدیمی را در Integration Test پوشش دهید.

Tool Contract باید مانند API عمومی طراحی شود. Description مبهم، پارامتر آزاد و خروجی متنی نامنظم باعث می شود مدل Call نادرست بسازد و مصرف کننده نتواند Error را تشخیص دهد. از Typeهای محدود، Enum، Validation و Error Code ساختاریافته استفاده کنید. Side Effect را در نام و Description روشن کنید. Toolی با نام get_status نباید چیزی را تغییر دهد. عملیات خطرناک مانند حذف، پرداخت یا Deploy باید Confirmation، Scope محدود و Audit Trail داشته باشند.

Dependency Injection ASP.NET Core برای Clientهای Backend، DbContext، Authorization Service و Telemetry مناسب است. اما Lifetimeها را رعایت کنید. Tool Static در نمونه ساده است، ولی سرویس Production ممکن است Scoped Dependency بخواهد. Timeout و CancellationToken را تا I/O پایین دست Propagate کنید. اگر Backend کند شد، Request MCP نباید بی نهایت باز بماند. Retry فقط برای عملیات Idempotent یا دارای Idempotency Key امن است.

طراحی Production، امنیت و Observability

Stateless Transport اجازه می دهد از الگوهای عادی Web استفاده کنید، اما MCP یک API معمولی با Caller کاملاً قابل پیش بینی نیست. مدل ممکن است Tool را با ترتیب یا پارامتر غیرمنتظره صدا بزند. Schema Validation، Authorization مبتنی بر User و Resource، Quota و Budget ضروری اند. Authentication و Authorization End-to-End هنوز یکی از تمرکزهای ادامه خط 2.x است؛ بنابراین وضعیت قابلیت های SDK و Spec را هنگام پیاده سازی بررسی و از فرض امنیت Turnkey خودداری کنید.

برای Observability، حداقل نام Tool، نتیجه، Duration، Error Class، نسخه Protocol، Client Type و Correlation ID را ثبت کنید. پارامترهای حساس را Log نکنید. Mcp-Method و Mcp-Name برای Metric و Routing مفیدند، اما Header پارامتر می تواند داده وارد Log Proxy کند. Sampling Log را کنترل و Redaction را در چند لایه اعمال کنید. Trace باید Call MCP را به Backend Call متصل کند تا Latency و Failure واقعی پیدا شود.

Rate Limiting را فقط بر IP بنا نکنید. یک Client Agent می تواند چند User یا Tenant را نمایندگی کند و NAT نیز چند کاربر را یک IP نشان دهد. ترکیبی از User، Tenant، Tool و Cost بهتر است. Tool ارزان خواندنی با Tool سنگین جست وجو یا عملیات پردازشی سقف یکسان ندارد. Circuit Breaker و Bulkhead جلوی سرایت شکست Backend به همه Toolها را می گیرند.

چک لیست مهاجرت از SDK نسخه 1

  1. همه استفاده های Session ID، initialize، SSE Legacy و Optionهای Stateful را جست وجو کنید.
  2. مشخص کنید State واقعاً Transport State است یا Business State پنهان.
  3. Business State را به Handle صریح و Storage مستقل منتقل کنید.
  4. SDK را به نسخه 2 ارتقا دهید و Warning و Diagnosticها را صفر کنید.
  5. Transport Stateless پیش فرض را در محیط آزمایشی فعال کنید.
  6. برای Interactionهای میانی، MRTR و Fallback Client قدیمی طراحی کنید.
  7. Routing، WAF و Metric را با Headerهای استاندارد به روزرسانی کنید.
  8. Load Test بدون Sticky Session و Failover میان Instanceها اجرا کنید.
  9. Rollout را Canary کنید و Compatibility Matrix را در Production رصد کنید.

اولین مهاجرت بهتر است Tool ساده و خواندنی باشد. از ابزار دارای Browser Session یا عملیات چندمرحله ای شروع نکنید. وقتی مسیر Stateless، Authentication، Logging و Deployment تثبیت شد، Toolهای Stateدار را با Handle منتقل کنید. هر Handle باید Owner، TTL، Scope و Cleanup داشته باشد. اگر Handle به Resource حساس اشاره می کند، حدس ناپذیر بودن کافی نیست؛ Server باید Authorization را در هر Call دوباره کنترل کند.

چه زمانی Session را نگه داریم؟

Session هنوز برای Unsolicited Server-to-Client Message یا State واقعاً وابسته به Transport کاربرد دارد. اگر Server باید بدون Request تازه Event را Push کند، Stateful Mode ممکن است لازم باشد. اما بسیاری از سناریوهایی که قبلاً Session می خواستند با MRTR، Polling کنترل شده یا Handle حل می شوند. هزینه Session شامل Affinity، Store، Cleanup، Reconnect، Failover و Debug پیچیده است؛ آن را فقط با نیاز قابل اندازه گیری بپذیرید.

نیازStateless + Handle/MRTRSession Opt-in
CRUD و Query عادیانتخاب اصلیلازم نیست
تأیید کاربر وسط عملیاتMRTRفقط برای Client قدیمی Bridge
Job طولانیHandle + Status Toolمعمولاً لازم نیست
Browser ContextbrowserId در Storageدر صورت وابستگی Transport
Push بدون Request ClientمحدودSession ممکن است لازم باشد
مقیاس Serverless/Edgeبسیار مناسبپیچیده تر

پرسش های متداول

آیا SDK 2.0 کد نسخه 1 را می شکند؟

هدف رسمی Backward Compatibility است و کد پایدار نسخه 1 باید Compile و اجرا شود. بااین حال Optionهای Stateful-only و Endpointهای Legacy ممکن است Diagnostic بدهند و برای بهره گیری از Wire Format جدید باید Stateless را انتخاب کنید.

Stateless یعنی نمی توان State داشت؟

خیر. State برنامه در Database، Cache یا سرویس جدا نگه داری می شود و با Handle صریح میان Tool Callها حرکت می کند. فقط State پنهان Session حمل ونقل پیش فرض نیست.

MRTR چه مشکلی را حل می کند؟

تعامل هایی را که وسط Tool Call به ورودی کاربر، Sampling یا Roots نیاز دارند، به چند Request مستقل تبدیل می کند. Continuity در inputResponses و requestState حمل می شود و Session طولانی لازم نیست.

آیا Headerهای MCP امن اند؟

برای Routing و Policy مفیدند، اما ممکن است در Proxy و Log دیده شوند. داده حساس را Promote نکنید، Body را Source of Truth نگه دارید و عدم تطابق Header و Body را رد کنید.

آیا Authentication کامل شده است؟

SDK و Spec در این نسخه هم راستایی بیشتری با OAuth و OpenID Connect دارند، اما تیم C# اعلام کرده ساده سازی End-to-End احراز هویت و مجوز همچنان تمرکز خط 2.x است. طراحی امنیت را به فرض خودکاربودن نسپارید.

جمع بندی

MCP C# SDK 2.0 پروتکل را به شکل طبیعی تری از Web نزدیک می کند: Request مستقل، Routing با Header، مقیاس افقی و Interaction چندمرحله ای بدون Session. برای پروژه تازه، Stateless را مبنا بگذارید، State بیزینسی را با Handle صریح مدیریت کنید و Toolها را مانند API حساس با Authorization، Validation، Idempotency و Observability بسازید. برای پروژه نسخه 1، مهاجرت را از Tool ساده آغاز و Compatibility Clientها را با تست واقعی اثبات کنید.

منابع رسمی

مطالب پیشنهادی

آسیب پذیری بحرانی WordPress 7.0.2؛ راهنمای Patch و بررسی نفوذ

آسیب پذیری بحرانی WordPress 7.0.2؛ راهنمای Patch و بررسی نفوذ

راهنمای فوری WordPress 7.0.2 برای رفع SQL Injection و RCE بحرانی؛ نسخه های امن، Patch با WP-CLI، کنترل WAF، تأیید Checksum و چک لیست بررسی نفوذ.

مهاجرت به TypeScript 7؛ سرعت 10 برابری، تغییرات مهم و دام های سازگاری

مهاجرت به TypeScript 7؛ سرعت 10 برابری، تغییرات مهم و دام های سازگاری

راهنمای مهاجرت TypeScript 7 با تمرکز بر سرعت Native، Defaultهای جدید، حذف گزینه های قدیمی، اجرای همزمان نسخه 6 و 7 و سازگاری Frameworkها در Editor و CI.

آپدیت امنیتی Node.js ژوئیه 2026؛ چه نسخه هایی را فوراً نصب کنیم؟

آپدیت امنیتی Node.js ژوئیه 2026؛ چه نسخه هایی را فوراً نصب کنیم؟

راهنمای عملی آپدیت امنیتی Node.js در ژوئیه 2026؛ نسخه های امن، تحلیل CVEها، ارتقای Docker و CI/CD و کنترل های لازم برای جلوگیری از قطعی و بازگشت نسخه آسیب پذیر.

محافظت جدید GitHub Actions؛ Workflow مشکوک چرا برای تأیید متوقف می شود؟

محافظت جدید GitHub Actions؛ Workflow مشکوک چرا برای تأیید متوقف می شود؟

GitHub Actions اکنون بعضی Workflowهای مشکوک Repository عمومی را پیش از اجرا نگه می دارد تا همکار دارای دسترسی Write آن را بررسی و تأیید کند.