كيف تبني خادم MCP بلغتي C# وJava: الأدوات ووسائل النقل والاختبار
ابنِ خادم MCP نفسه مرتين، بلغة C# باستخدام حزمة NuGet الرسمية، وبلغة Java مع Spring AI. أضف أداة لعدّ الكلمات وأخرى للصور تستدعي PicassoIA API، واختر stdio أو HTTP، ثم اختبر باستخدام MCP Inspector وعميل ذكاء اصطناعي حقيقي.
خادم MCP برنامج صغير ينتظر رسالة JSON-RPC، ثم ينفّذ دالة ويعيد النتيجة إلى عميل الذكاء الاصطناعي. هذه هي الفكرة كلها. يبدو الأمر معقّدًا في C# وJava فقط لأن البيئتين تأتيان مع قوالب مشاريع، وحقن الاعتماديات، والتعليقات التوضيحية (annotations)، وأدوات البناء. يُزيل هذا الدرس ذلك التعقيد. ستبني الأداتين نفسيهما مرتين: مرة بلغة C# باستخدام حزمة NuGet الرسمية ModelContextProtocol، ومرة بلغة Java باستخدام مشغّل MCP من Spring AI، ثم تختبر الاثنين باستخدام MCP Inspector وتربطهما بعميل حقيقي.
الأداتان بسيطتان عمدًا. word_count يثبت أن الربط يعمل، أما generate_image فيستدعي PicassoIA API، فيستطيع عميل ذكاء اصطناعي مثل Claude أن يطلب من كودك صورة ويحصل على رابط لها. يتبع الكود توثيق SDK الرسمي، لكن ثبّت إصدارات الحزم لديك، وشغّل الفحوص الواردة في قسم الاختبار قبل أن تثق بأي جزء منه.
ما الذي يفعله خادم MCP فعليًا
بروتوكول سياق النموذج (Model Context Protocol) معيار مفتوح يتيح لتطبيق الذكاء الاصطناعي (العميل) التواصل مع برامج خارجية (الخوادم) بطريقة واحدة ومتسقة. يتصل العميل، ويسأل الخادم عمّا يستطيع فعله، ثم يستدعي هذه القدرات حين يقرر النموذج الحاجة إليها. لا يتحدث خادمك مع النموذج مباشرة أبدًا. هو يجيب على الطلبات، والعميل هو من يدير الحوار.
الأدوات والموارد والقوالب
يستطيع الخادم أن يقدّم ثلاثة أنواع من القدرات:
الأدوات دوال يستطيع النموذج استدعاءها، مثل generate_image أو word_count. لكل أداة اسم ووصف ومخطط JSON لمدخلاتها.
الموارد بيانات للقراءة فقط يُشار إليها بعنوان URI، مثل ملف أو صف في قاعدة بيانات أو سجل.
القوالب رسائل قابلة لإعادة الاستخدام يستطيع المستخدم اختيارها من قائمة.
معظم الخوادم لا تقدّم إلا الأدوات، وهي ما يبنيه هذا الدرس. الوصف الذي تكتبه لكل أداة أهم من الكود الموجود داخلها، لأن النموذج يقرأ هذا النص ليقرر متى يستدعيها.
💡 نصيحة: اكتب أوصاف الأدوات كما تشرح مهمة لزميل جديد. اذكر ما تفعله الأداة، وما تحتاجه، وما تعيده.
بروتوكول واحد ومجموعتان من الأدوات
تُخفي كلتا حزمتي SDK تفاصيل JSON-RPC. تصرّح بالدالة وتصف معاملاتها، فتتولى المكتبة بناء المخطط ومعالجة دورة الطلب. هذه مقارنة بين المجموعتين:
الجانب
C#
Java
الحزمة الرئيسية
ModelContextProtocol
io.modelcontextprotocol.sdk:mcp أو مشغّلات Spring AI
تعريف الأداة
السمة [McpServerTool]
التعليق التوضيحي @Tool (Spring AI)
نقل stdio
WithStdioServerTransport()
spring-ai-starter-mcp-server
نقل HTTP
ModelContextProtocol.AspNetCore
spring-ai-starter-mcp-server-webmvc أو -webflux
بيئة التشغيل
.NET SDK
Java 17 أو أحدث
تُطوَّر حزمة C# SDK بالتعاون مع Microsoft، وأصبحت تكاملات Spring لحزمة Java SDK الآن جزءًا من Spring AI. وفي الحالتين تتعامل مع التعليقات التوضيحية وحقن الاعتماديات، لذلك يبدو الكود كأي خدمة أخرى في تلك اللغة.
إعداد .NET وJava
ثبّت ما تحتاجه مرة واحدة، وسيعمل البناءان على الحاسوب نفسه. ستحتاج أيضًا إلى Node.js، لأن MCP Inspector يعمل عبر npx.
متطلبات .NET
ثبّت .NET SDK وتحقق منه بالأمر dotnet --version. تحقق من صفحة الحزمة ModelContextProtocol على NuGet لمعرفة أقل إطار عمل تستهدفه، ثم اختر SDK يلبي ذلك. يساعد محرر الكود، لكن الطرفية البسيطة تكفي لهذا المشروع.
متطلبات Java
ثبّت JDK 17 أو أحدث، مع Maven أو Gradle. تعتبر حزمة Java SDK الإصدار 17 حدًّا أدنى. يستخدم هذا الدرس Maven ومشروع Spring Boot، لأن مشغّل Spring AI يزيل معظم كود النقل الذي كنت ستكتبه يدويًا.
يقرأ الخادمان بيانات اعتماد PicassoIA من متغير بيئة. أنشئ رمز API من picassoia.com/en/api (يبدأ بالبادئة pia_sk_) وصدّره باسم PICASSOIA_API_TOKEN. لا تلصقه في كود المصدر أبدًا، وراجع صفحة أسعار PicassoIA لمعرفة الخطط التي تتضمن وصول API.
بناء خادم C#
إنشاء المشروع
نفّذ هذه الأوامر في مجلد فارغ:
dotnet new console -n PicassoMcp
cd PicassoMcp
dotnet add package ModelContextProtocol
dotnet add package Microsoft.Extensions.Hosting
ثم استبدل محتوى Program.cs:
using System.Net.Http.Headers;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
var builder = Host.CreateApplicationBuilder(args);
// Standard output carries the protocol, so every log line goes to standard error.
builder.Logging.AddConsole(options =>
{
options.LogToStandardErrorThreshold = LogLevel.Trace;
});
builder.Services.AddSingleton(_ =>
{
var client = new HttpClient { BaseAddress = new Uri("https://api.picassoia.com/v1/") };
var token = Environment.GetEnvironmentVariable("PICASSOIA_API_TOKEN");
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
return client;
});
builder.Services
.AddMcpServer()
.WithStdioServerTransport()
.WithToolsFromAssembly();
await builder.Build().RunAsync();
هناك تفصيلان يستحقان الانتباه. يرسل المسجِّل كل شيء إلى الخطأ القياسي (standard error)، لأن الخرج القياسي مخصص للبروتوكول. كما أن WithToolsFromAssembly() يفحص مشروعك بحثًا عن الأصناف المعلَّمة كأنواع أدوات، فلا تسجّل الأدوات يدويًا أبدًا.
إضافة أول أداة
أنشئ PicassoTools.cs بجانب Program.cs. ابدأ بالأداة البسيطة:
using System.ComponentModel;
using ModelContextProtocol.Server;
[McpServerToolType]
public static class PicassoTools
{
[McpServerTool(Name = "word_count")]
[Description("Counts the words in a piece of text and returns the number.")]
public static int WordCount([Description("The text to count")] string text) =>
// A null separator splits on any whitespace.
text.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries).Length;
}
تقوم السمات الثلاث بكل العمل. تُعلّم [McpServerToolType] الصنف، وتُعلّم [McpServerTool] الدالة، ويتحول كل [Description] إلى نص يقرؤه النموذج. تتحول أسماء المعاملات وأنواعها إلى مخطط المدخلات تلقائيًا.
استدعاء PicassoIA API
PicassoIA API غير متزامن ويتبع نمط Replicate. تنشئ تنبؤًا بالأمر POST /v1/models/{owner}/{name}/predictions، ثم تستعلم GET /v1/predictions/{id} بشكل متكرر حتى تصبح الحالة succeeded. المصادقة برمز حامل (bearer token)، ويستطيع الحساب الواحد تشغيل 5 تنبؤات في الوقت نفسه. أضف هذه الدالة داخل الصنف نفسه، مع أسطر using الإضافية هذه في أعلى الملف:
using System.Net.Http.Json;
using System.Text.Json;
using ModelContextProtocol;
[McpServerTool(Name = "generate_image")]
[Description("Generates an image with PicassoIA and returns the finished prediction as JSON.")]
public static async Task<string> GenerateImage(
HttpClient client,
[Description("What the image should show")] string prompt,
[Description("Aspect ratio such as 16:9 or 1:1")] string aspectRatio = "16:9",
CancellationToken cancellationToken = default)
{
var created = await client.PostAsJsonAsync(
"models/picassoia/picassoia-image/predictions",
new { input = new { prompt, aspect_ratio = aspectRatio } },
cancellationToken);
created.EnsureSuccessStatusCode();
using var createdDoc = JsonDocument.Parse(
await created.Content.ReadAsStringAsync(cancellationToken));
var id = createdDoc.RootElement.GetProperty("id").GetString();
while (true)
{
await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken);
var json = await client.GetStringAsync($"predictions/{id}", cancellationToken);
using var doc = JsonDocument.Parse(json);
var status = doc.RootElement.GetProperty("status").GetString();
if (status == "succeeded") return json;
if (status is "failed" or "canceled")
throw new McpException($"The generation ended with status '{status}'.");
}
}
يُحلّ معامل HttpClient من خلال حقن التبعيات، لذا يُمرَّر معه التوكن الذي أعددته في Program.cs. ولأن الدالة تقبل CancellationToken، يمكن للعميل إيقاف توليد بطيء بدلًا من تركه معلّقًا. النموذج الذي يعمل خلف الاستدعاء هو PicassoIA Image، ويعمل النمط نفسه مع النماذج الأخرى التي تتيحها API.
بناء خادم Java
تملك نسخة Java الأداتين نفسيهما والسلوك نفسه. يتغير التغليف فقط.
إضافة مشغّل Spring AI
أنشئ مشروع Spring Boot واستورد قائمة المواد (BOM) الخاصة بإطار Spring AI، ثم أضف مشغّل stdio:
الأسطر الثلاثة الأولى مهمة بقدر أهمية الاعتمادية. هي تعطّل خادم الويب، وشعار البدء، ونمط سجل وحدة التحكم، لأن أي شيء يُطبع في الخرج القياسي يكسر تدفق stdio.
كتابة صنف الأداة
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
@Service
public class PicassoTools {
private final RestClient client;
public PicassoTools(RestClient.Builder builder,
@Value("${PICASSOIA_API_TOKEN}") String token) {
this.client = builder
.baseUrl("https://api.picassoia.com/v1")
.defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + token)
.build();
}
@Tool(description = "Counts the words in a piece of text and returns the number.")
public int wordCount(@ToolParam(description = "The text to count") String text) {
return text.isBlank() ? 0 : text.trim().split("\\s+").length;
}
@Tool(description = "Generates an image with PicassoIA and returns the finished prediction.")
public Map<String, Object> generateImage(
@ToolParam(description = "What the image should show") String prompt,
@ToolParam(description = "Aspect ratio such as 16:9 or 1:1", required = false)
String aspectRatio) throws InterruptedException {
String ratio = aspectRatio == null ? "16:9" : aspectRatio;
Map<String, Object> created = client.post()
.uri("/models/picassoia/picassoia-image/predictions")
.contentType(MediaType.APPLICATION_JSON)
.body(Map.of("input", Map.of("prompt", prompt, "aspect_ratio", ratio)))
.retrieve()
.body(new ParameterizedTypeReference<>() {});
String id = (String) created.get("id");
for (int attempt = 0; attempt < 60; attempt++) {
Thread.sleep(5000);
Map<String, Object> prediction = client.get()
.uri("/predictions/{id}", id)
.retrieve()
.body(new ParameterizedTypeReference<>() {});
String status = (String) prediction.get("status");
if ("succeeded".equals(status)) return prediction;
if ("failed".equals(status) || "canceled".equals(status)) {
throw new IllegalStateException("The generation ended with status " + status);
}
}
throw new IllegalStateException("The generation took longer than five minutes.");
}
}
يؤدي @Tool وكذلك @ToolParam الدور نفسه الذي تؤديه سمات C#. يحوّلهما Spring إلى اسم الأداة ووصفها ومخطط مدخلاتها. تعيد الدالة Map، ويُسلسله Spring AI إلى JSON للعميل.
لا تُعرض الدوال المعلَّمة بالتعليقات التوضيحية حتى تسجّلها. أضف مُكوِّنًا (bean) واحدًا إلى الصنف الرئيسي:
@SpringBootApplication
public class PicassoMcpApplication {
public static void main(String[] args) {
SpringApplication.run(PicassoMcpApplication.class, args);
}
@Bean
ToolCallbackProvider picassoTools(PicassoTools tools) {
return MethodToolCallbackProvider.builder().toolObjects(tools).build();
}
}
ابنِه بالأمر mvn package فتحصل على ملف JAR قابل للتشغيل.
استخدام Java SDK العادية
إذا لم يكن مشروعك يعتمد على Spring، فاعتمد على io.modelcontextprotocol.sdk:mcp واستورد mcp-bom لإبقاء إصدارات الوحدات متوافقة. الخادم نفسه بضعة أسطر:
StdioServerTransportProvider transport =
new StdioServerTransportProvider(McpJsonDefaults.getMapper());
McpSyncServer server = McpServer.sync(transport)
.serverInfo("picassoia-java", "1.0.0")
.capabilities(ServerCapabilities.builder().tools(true).build())
.build();
يتبع هذا المقتطف خط الإصدار 2.0 من SDK. تضيف كل أداة كأداة من نوع SyncToolSpecification باسم ووصف ومخطط JSON ومعالج استدعاء. قد تختلف تفاصيل البناء، مثل مُنشئ ناقل النقل (transport)، بين الإصدارات، لذلك انسخ مقتطف الأداة من README الذي يطابق إصدارك. يحصل مستخدمو Spring على كل هذا مجانًا، ولهذا يكون المشغّل هو الطريق الأقصر.
نقل stdio أو HTTP
يحدد النقل من يشغّل خادمك ومن يستطيع الوصول إليه.
وسيلة النقل
الاستخدام الأنسب
C#
Java
stdio
أدوات محلية يشغّلها العميل
WithStdioServerTransport()
spring-ai-starter-mcp-server
HTTP
خوادم بعيدة أو مشتركة
WithHttpTransport() مع MapMcp()
مشغّل WebMVC أو WebFlux
مع stdio، يشغّل العميل عمليتك ويتواصل معها عبر الإدخال والخرج القياسيين. هو أبسط خيار ولا يُعرض شيء منه على الشبكة، لذا ابدأ به. أما مع HTTP، فيخدم نشر واحد عملاء كثيرين، ما يعني أنك صرت مسؤولًا عن المصادقة، وأمان الاتصال (TLS)، وحدود معدل الطلبات.
في C#، انتقل إلى HTTP بإضافة حزمة ModelContextProtocol.AspNetCore وتغيير المضيف:
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddMcpServer()
.WithHttpTransport()
.WithToolsFromAssembly();
var app = builder.Build();
app.MapMcp();
app.Run();
في Java، استبدل المشغّل بالمشغّل spring-ai-starter-mcp-server-webmvc، أو بنسخة WebFlux للتطبيقات التفاعلية. يسرد توثيق Spring AI /sse مع /mcp/message كنقاط نهاية افتراضية، ويسرد spring.ai.mcp.server.stdio=true لتشغيل stdio إلى جانب HTTP.
⚠️ تحذير: يحتاج خادم HTTP الذي تستدعي أداته API مدفوعة أو محدودة المعدل إلى مصادقة قبل أن يُتاح على الإنترنت. أي شخص يستطيع الوصول إلى نقطة النهاية يمكنه استدعاء الأداة.
اختبار الخادم وتصحيح أخطائه
تشغيل MCP Inspector
MCP Inspector أداة متصفح تتصل بخادمك، وتعرض أدواته، وتتيح استدعاءها بمدخلات تكتبها يدويًا. ابنِ المشروع أولًا، ثم شغّله بأمر الخادم:
اتصل، وافتح تبويب Tools، واعرض الأدوات. يُفترض أن تظهر لك word_count وgenerate_image، ولكل منهما الوصف الذي كتبته. استدعِ word_count مع one two three وتوقّع 3. ثم استدعِ generate_image بأمر نصي قصير وانتظر عودة JSON الخاص بالتنبؤ.
الاتصال بعميل حقيقي
مع Claude Code، يسجّل أمر واحد خادم C#:
claude mcp add picassoia-dotnet -e PICASSOIA_API_TOKEN=pia_sk_your_token -- dotnet run --project ./PicassoMcp --no-build
يقرأ Claude Desktop ملف JSON بدلًا من ذلك. أضف الخادمين إلى claude_desktop_config.json:
أعد تشغيل العميل واطلب منه أن “يولّد صورة بنسبة 16:9 لمنارة عند غروب الشمس”. يُفترض أن يستدعي أداتك ويعيد التنبؤ مع رابط الصورة.
خمسة أخطاء تكسر الخوادم
الطباعة إلى الخرج القياسي. أي Console.WriteLine أو System.out.println عابر يفسد تدفق JSON-RPC. سجّل في الخطأ القياسي فقط.
الأوصاف الغامضة. إذا تشابهت أداتان في الاسم، يختار النموذج الخطأ. سمِّ المدخل والمخرج والآثار الجانبية.
الاستعلام المتكرر بلا حد. تتوقف حلقة C# عند الإلغاء، وتتوقف حلقة Java بعد 60 محاولة. اختر حدًا يناسب أبطأ نموذج لديك.
الأسرار المكتوبة في الكود. اقرأ الرموز من متغيرات البيئة، كما يفعل المثالان، ولا ترفعها إلى المستودع أبدًا.
قيم إرجاع ضخمة. أعد رابطًا أو ملخصًا قصيرًا، لا ميجابايتات من البيانات يضطر النموذج إلى قراءتها.
تذكّر سقف التزامن أيضًا. مع الحد الأقصى 5 تنبؤات متزامنة لكل حساب، ستظهر أخطاء لعميل ثرثار يطلق استدعاءات generate_image كثيرة، لذا ضع الاستدعاءات في طابور أو أعد رسالة واضحة.
كيفية استخدام Sonnet 5 على PicassoIA
كتابة الكود النمطي المتكرر هي المجال الذي يوفّر فيه نموذج الذكاء الاصطناعي أكبر قدر من الوقت، ويُعدّ Claude Sonnet 5 على PicassoIA مصمّمًا للبرمجة ومهام استخدام الأدوات متعددة الخطوات. استخدمه لصياغة أدوات جديدة، ثم تحقّق من كل اسم API في توثيق SDK.
افتح صفحة النموذج. انتقل إلى صفحة Claude Sonnet 5 على PicassoIA وابحث عن مربع الأمر النصي.
صف أداة واحدة. اذكر اللغة والحزمة والسلوك: “اكتب أداة MCP بلغة C# باستخدام حزمة ModelContextProtocol تحوّل درجة الحرارة بين المئوية والفهرنهايت.”
اضبط موجّه نظام مرة واحدة. شيء مثل “لا تكتب أبدًا إلى الخرج القياسي. استخدم السمة [McpServerTool] والسمة [Description]” يحافظ على اتساق كل الإجابات.
اختر مستوى الجهد. اتركه على low للكود المتكرر. ارفعه إلى high أو max عندما يمس الكود عدة ملفات أو يكون الخطأ عنيدًا.
أرفق لقطة شاشة عند الفشل. يقرأ النموذج الصور، لذا تعمل لقطة شاشة لخطأ Inspector بقدر النص الملصوق.
اختبر النتيجة. ألصق الكود في مشروعك وشغّله عبر Inspector قبل أن تثق به.
لديك الآن خادمان يعملان ويسلّمان توليد الصور إلى عميل ذكاء اصطناعي. الخطوة التالية هي أن ترى ما يستطيع النموذج الكامن وراءهما فعله وحده. افتح PicassoIA Image، واكتب الأمر النصي نفسه الذي أرسلته عبر أداتك، وقارن النتيجة. ثم غيّر نسبة العرض إلى الارتفاع، وأعد صياغة الإضاءة، وجرّب ثلاثة تنويعات لمشهد واحد. عندما يكون التصيير قريبًا لكنه غير صحيح، أرسله إلى PicassoIA Image Editor Pro وأصلح التفاصيل بدل البدء من جديد.
كلما جرّبت الأوامر النصية أكثر على Picasso IA، تحسّنت أوصاف أدواتك ومعاملاتها الافتراضية. أنشئ أول صورة لك اليوم، ثم ضع أفضل أمر نصي في خادمك ودع عميل الذكاء الاصطناعي يتولى الباقي.