用C#构建简易MCP服务器与客户端

什么是MCP?

模型上下文协议(Model Context Protocol,简称MCP)是一种标准化方式,用于让AI系统访问和与各种外部资源(如文件系统、数据库、API及其他服务)进行交互。MCP提供了一个统一的接口,任何符合该协议的系统都可以使用,从而避免了为每个集成编写独特连接器的需求。

通过使用MCP,开发者能够使AI代理具备发现、检索和处理信息的能力,而无需关心底层技术的具体实现。这种抽象化使得开发者可以更专注于系统的功能实现,而非数据连接的技术细节。

MCP核心基础组件

MCP围绕三个核心概念构建:

1. 资源(Resources)  

这些代表AI代理可能需要访问的任何外部数据,例如文档、图像、文件、数据库等。

  • 统一访问:代理通过统一的接口请求任何资源。
  • 实时更新:系统确保代理能够获取最新的信息。
  • 复杂性抽象:开发者无需为每个数据源编写自定义代码。

2. 工具(Tools)

工具代表AI代理可以调用以执行特定任务的功能或操作,例如获取数据、编辑文件、处理内容等。

  • 面向操作:代理不仅能读取数据,还能修改数据或触发工作流。
  • 动态选择:代理可根据上下文动态发现并选择合适的工具。

3. 提示词(Prompts)  

提示词是结构化的模板或指令,用于定义AI代理如何与工具和资源进行交互。

  • 引导式交互:提示词确保所有请求都以正确的格式发出。  
  • 上下文管理:代理在多个操作之间保持连贯的交互逻辑。
  • 明确性:标准化的提示词减少了歧义和误解。

这三个基础组件共同作用,使代理能够在模块化系统中智能且自主地运行。

MCP架构:客户端-服务器模型

MCP采用客户端-服务器模型设计:

  • 客户端(通常是AI代理)发送结构化请求,其中包含提示词(prompts)和资源标识符。
  • MCP服务器负责处理这些请求,维护上下文,并返回结果。

这种分离设计确保了系统在各种应用场景下具备良好的可扩展性、安全性和灵活性。

C# MCP SDK

为支持 .NET 开发者,我们已在 GitHub 上发布了 C# SDK:

👉 MCP C# SDK

该 SDK 简化了将 AI 代理与 MCP 服务器集成的过程,自动处理序列化、请求/响应格式以及上下文管理。

示例解决方案:简易 MCP 服务器与客户端

为了演示 MCP 在实际中的工作方式,我创建了一个极简的 C# MCP 解决方案:

📁 GitHub 仓库:petersaktor/mcp

该解决方案包含服务器端和客户端两部分,展示了如何实现以下功能:

  • 在服务器端定义并暴露工具(Tools)
  • 发送提示词(Prompts)并与资源进行交互
  • 记录并处理来自 MCP 服务器的返回结果

MCP Server

Program.cs

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

Console.WriteLine("Hello, MCP Simple server!");

var builder = Host.CreateApplicationBuilder(args);
builder.Logging.AddConsole(consoleLogOptions =>
{
    // Configure all logs to go to stderr
    consoleLogOptions.LogToStandardErrorThreshold = LogLevel.Trace;
});

builder.Services
    .AddMcpServer()
    .WithStdioServerTransport()
    .WithPromptsFromAssembly()
    .WithToolsFromAssembly()
    .WithResourcesFromAssembly();

await builder.Build()
    .RunAsync();

SimplePrompt.cs

using Microsoft.Extensions.AI;
using ModelContextProtocol.Server;
using System.ComponentModel;

namespace SimpleServer.Prompts;

[McpServerPromptType]
public static class SimplePrompt
{
    [McpServerPrompt, Description("Creates a prompt to summarize the provided message.")]
    public static ChatMessage Summarize(string content) =>
        new(ChatRole.User, $"Please summarize this content into a single sentence: {content}");
}

SimpleTool.cs

using ModelContextProtocol.Server;
using System.ComponentModel;

namespace SimpleServer.Prompts;

[McpServerToolType]
public static class SimpleTool
{
    [McpServerTool, Description("Returns the current time for the specified time zone.")]
    public static string GetCurrentTime(string timeZone)
    {
        try
        {
            var tz = TimeZoneInfo.FindSystemTimeZoneById(timeZone);
            var now = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, tz);
            return now.ToString("o"); // ISO 8601 format
        }
        catch (TimeZoneNotFoundException)
        {
            return $"Time zone '{timeZone}' not found.";
        }
        catch (InvalidTimeZoneException)
        {
            return $"Time zone '{timeZone}' is invalid.";
        }
    }
}

SimpleResource.cs

using ModelContextProtocol.Server;
using System.ComponentModel;

namespace SimpleServer.Prompts;

[McpServerResourceType]
public static class SimpleResource
{
    [McpServerResource, Description("Returns the Bing endpoint URL.")]
    public static string GetBingEndpoint() => "https://bing.com";
}

MCP Client

Program.cs

using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol;

Console.WriteLine($"Hello, MCP Simple client!");

// Create the stdio transport (using current process's stdin/stdout)
var transport = new StdioClientTransport(new StdioClientTransportOptions
{
    Name = "MCP Simple Client",
    Command = "..\\..\\..\\..\\SimpleServer\\bin\\Debug\\net8.0\\SimpleServer.exe",
    // The command to start the MCP server process
    // If the server is already running, you can use the same command as the server
    // If you want to use a different command, specify it here
});

try
{
    // Create the MCP client
    var client = await McpClientFactory.CreateAsync(cancellationToken: default, clientTransport: transport);

    // List available prompts from the server
    foreach (var pr in await client.ListPromptsAsync())
    {
        Console.WriteLine($"Available prompt: {pr.Name} - {pr.Description}");
    }

    // Specify the prompt you want to use
    var promptName = "Summarize";

    // Create a dictionary of arguments for the prompt
    IReadOnlyDictionary<string, object> promptArguments = new Dictionary<string, object>()
    {
       { "content", "ModelContextProtocol enables structured communication." }
    };

    // Get the prompt from the server using the specified name and arguments
    var prompt = await client.GetPromptAsync(promptName, promptArguments!);
    if (prompt == null)
    {
        Console.WriteLine($"Prompt '{promptName}' not found.");
        return;
    }

    // Print the prompt details
    foreach (var message in prompt.Messages)
    {
        Console.WriteLine($"Message Role: {message.Role}, Content: {message.Content?.Text}");
    }

    // List available tools from the server
    foreach (var to in await client.ListToolsAsync())
    {
        Console.WriteLine($"Available tool: {to.Name} - {to.Description}");
    }

    // Specify the tool you want to use
    var toolName = "GetCurrentTime";

    // Create a dictionary of arguments for the tool
    IReadOnlyDictionary<string, object> toolArguments = new Dictionary<string, object>()
    {
        { "timeZone", "Pacific Standard Time" } // Example time zone
    };

    // Call tool from the server using the specified name and arguments
    var result = await client.CallToolAsync(toolName, toolArguments!);
    if (result == null)
    {
        Console.WriteLine($"Tool '{toolName}' not found.");
        return;
    }

    // Print the tool result
    foreach (var res in result.Content)
    {
        Console.WriteLine($"Tool Result: {res.Text}");
    }

    // List available resources from the server
    foreach (var res in await client.ListResourcesAsync())
    {
        Console.WriteLine($"Available resource: {res.Name} - {res.Description}");
    }

    // Specify the resource you want to use
    var resourceName = "resource://GetBingEndpoint";

    // Get the resource from the server using the specified name
    var resource = await client.ReadResourceAsync(resourceName);
    if (resource == null)
    {
        Console.WriteLine($"Resource '{resourceName}' not found.");
        return;
    }

    // Print the resource details
    foreach (var res in resource.Contents)
    {
        Console.WriteLine($"Resource Content: {((TextResourceContents)res)?.Text}");
    }
}
catch (Exception ex)
{
    Console.WriteLine("Error creating MCP client: " + ex.Message);
    return;
}

总结

MCP 为 AI 集成带来了更高水平的一致性和模块化能力。通过使用资源(Resources)、提示词(Prompts)和工具(Tools)这一简洁而强大的抽象机制,结合清晰的客户端-服务器模型,开发者可以构建出能够与外部世界无缝交互的智能代理。

了解更多:

- C# SDK  
- 示例解决方案

posted @ 2025-09-11 19:00  菜鸟吊思  阅读(9)  评论(0)    收藏  举报