基于.NET 8构建MCP服务器:为AI助手打造安全的外部工具集成
2026/5/13 0:51:29 网站建设 项目流程

1. 项目概述与核心价值

最近在折腾AI应用开发,特别是想给自家的聊天机器人加点“超能力”,让它能直接读取我电脑里的文件、查查数据库,甚至控制一下智能家居。这听起来像是要写一大堆复杂的插件和集成代码,对吧?一开始我也这么觉得,直到我深入研究了mehrandvd/tutorial-mcp-server-dotnet这个项目。简单来说,这是一个用 .NET 8 构建模型上下文协议(Model Context Protocol, MCP)服务器的实战教程。MCP 是 Anthropic 提出的一种开放协议,旨在让 AI 助手(比如 Claude)能够安全、标准化地访问外部工具和数据源,而无需为每个助手单独开发集成。

这个教程的价值在于,它没有停留在概念层面,而是手把手带你从零开始,用 C# 和 .NET 8 构建一个功能完整的 MCP 服务器。无论你是 .NET 后端开发者,想为你的 AI 应用添加智能体能力,还是对 AI 与现有系统集成感兴趣的探索者,这个项目都提供了一个绝佳的切入点。它解决的正是“如何让 AI 安全、可控地使用我们已有的系统和数据”这个核心痛点。通过完成这个教程,你不仅能掌握 MCP 协议的核心机制,更能获得一套可复用的 .NET 项目模板,快速为你自己的业务系统打造专属的 AI “手和脚”。

2. MCP 协议核心机制与 .NET 实现选型

在动手写代码之前,我们必须先搞清楚 MCP 到底是什么,以及为什么选择 .NET 8 作为实现平台。这决定了我们整个项目的架构设计思路。

2.1 MCP 协议:AI 的“标准化插座”

你可以把 MCP 想象成给 AI 模型用的“USB 接口”或“标准化插座”。在没有 MCP 之前,每个 AI 应用(如 Claude Desktop、Cursor)如果想连接外部资源(如文件系统、数据库、API),都需要开发者为其定制开发专用的“插头”和“电线”,工作重复且难以维护。MCP 定义了一套标准的“插座”规格(即协议),包括:

  1. 资源(Resources):AI 可以读取的“只读”数据源,例如文件、数据库查询结果、API 返回的静态数据。
  2. 工具(Tools):AI 可以调用的“可执行”操作,例如执行一个命令、写入一个文件、调用一个函数。
  3. 提示(Prompts):预定义的、参数化的文本模板,AI 可以获取并用于生成更符合上下文的回复。

协议规定了服务器(我们即将构建的)与客户端(如 Claude Desktop)之间通过JSON-RPC over stdio(标准输入输出)或 SSE(服务器发送事件)进行通信。所有交互,无论是列出可用工具,还是调用某个工具并返回结果,都遵循严格的 JSON 格式。这种设计使得客户端和服务器完全解耦,一个 MCP 服务器可以同时为多个兼容 MCP 的客户端提供服务。

2.2 为什么选择 .NET 8 进行实现?

mehrandvd/tutorial-mcp-server-dotnet项目选择 .NET 8 作为实现基础,这是一个非常务实且高效的选择,背后有几点关键考量:

  1. 强大的生态系统与生产力:.NET 8 是微软最新的长期支持版本,提供了出色的性能、跨平台支持和丰富的库生态系统。对于需要与文件系统、数据库、网络服务、本地进程打交道的 MCP 服务器来说,.NET 的基础类库(BCL)和 NuGet 包生态能极大提升开发效率。例如,使用System.IO处理文件、用System.Text.Json高效序列化协议数据、用Microsoft.Extensions系列包管理配置和依赖注入,都是开箱即用的成熟方案。
  2. 面向 AI 应用开发的现代特性:.NET 8 在性能(尤其是原生 AOT)、容器化支持、云原生开发方面有显著增强。构建的 MCP 服务器可以轻松打包成轻量级 Docker 镜像,部署在任何环境。此外,.NET 社区对 AI 的支持日益增长,与 ML.NET、Semantic Kernel 等框架的集成也为未来扩展提供了可能。
  3. 类型安全与可维护性:C# 是一门强类型语言,结合 .NET 8 的现代语法(如记录类型、顶级语句、模式匹配),可以让我们构建出结构清晰、类型安全、易于测试和维护的协议实现。这对于处理具有复杂结构的 JSON-RPC 消息尤为重要,能有效减少运行时错误。
  4. 教程作者的匠心:该项目作者提供了完整的、循序渐进的指南,从项目初始化、协议基础类型定义,到实现具体工具和资源,最后完成集成测试。这对于 .NET 开发者来说,学习曲线平缓,能够快速上手并理解 MCP 的核心。

注意:虽然教程使用 .NET 8,但 MCP 协议本身是语言无关的。理解其核心概念后,你可以用 Python、Go、Rust 等任何语言实现。选择 .NET 主要是基于现有技术栈和开发效率的权衡。

3. 项目结构与环境搭建实战

接下来,我们进入实战环节,跟随教程一步步搭建开发环境并初始化项目。我会补充一些教程中可能未详述的细节和避坑点。

3.1 开发环境与工具链准备

首先,确保你的开发机器上已经安装了以下软件:

  • .NET 8 SDK:这是核心。可以从微软官网或使用命令行工具安装。安装后,在终端运行dotnet --version确认版本为 8.0.x 或更高。
  • 代码编辑器:强烈推荐Visual Studio 2022(社区版免费)或Visual Studio Code。VS 提供了对 .NET 项目最完整的支持,而 VS Code 搭配 C# 扩展同样非常强大,且更轻量。
  • Git:用于克隆教程仓库和管理你的代码版本。
  • 一个 MCP 客户端:用于测试。最常用的是Claude Desktop。你需要在其设置中配置 MCP 服务器。另一种选择是使用MCP 客户端模拟器或编写简单的测试脚本,教程后期会涉及。

3.2 初始化项目与核心依赖解析

教程通常从创建一个新的控制台应用程序开始。我们打开终端,执行以下命令:

# 创建一个新的控制台应用项目 dotnet new console -n MyFirstMcpServer -f net8.0 cd MyFirstMcpServer

接下来,我们需要添加实现 MCP 协议所需的核心 NuGet 包。虽然你可以完全从零实现 JSON-RPC 的序列化和通信,但使用社区库能事半功倍。教程可能会推荐或使用特定的辅助库。一个常见的选择是StreamJsonRpcJsonRpc.Net来处理 JSON-RPC 通信层,但为了更贴近 MCP 语义,我们更关注定义协议模型。

实际上,更直接的方法是参考 Anthropic 官方提供的TypeScript/JavaScript SDK的类型定义,在 C# 中创建对应的模型类(Record)。不过,社区已经有一些先行者。我们可以手动创建这些模型,这本身也是一个学习过程。

首先,添加对System.Text.Json的显式引用(虽然默认包含),并确保其版本合适。然后,我们创建项目核心结构:

MyFirstMcpServer/ ├── Models/ │ ├── McpMessage.cs # JSON-RPC 基础消息模型 │ ├── Request.cs # 请求模型(如 `initialize`, `tools/list`) │ ├── Response.cs # 响应模型 │ ├── Notification.cs # 通知模型 │ └── Types/ # 协议中使用的各种类型定义 │ ├── Tool.cs │ ├── Resource.cs │ ├── Prompt.cs │ └── ... ├── Services/ │ ├── IMcpServer.cs # 服务器接口 │ └── McpServer.cs # 服务器核心实现,处理 stdio 通信 ├── Tools/ # 具体工具的实现 │ └── FileReadTool.cs ├── Resources/ # 具体资源的实现 │ └── ... ├── Program.cs # 应用程序入口 └── MyFirstMcpServer.csproj

Models/Types/Tool.cs中,我们定义工具的描述结构,这需要严格遵循 MCP 协议规范:

// Models/Types/Tool.cs using System.Text.Json.Serialization; namespace MyFirstMcpServer.Models.Types; public record Tool( [property: JsonPropertyName("name")] string Name, [property: JsonPropertyName("description")] string Description, [property: JsonPropertyName("inputSchema")] InputSchema InputSchema ); public record InputSchema( [property: JsonPropertyName("type")] string Type, // 固定为 "object" [property: JsonPropertyName("properties")] Dictionary<string, PropertySchema> Properties, [property: JsonPropertyName("required")] List<string>? Required = null ); public record PropertySchema( [property: JsonPropertyName("type")] string Type, // "string", "number", "integer", "boolean" [property: JsonPropertyName("description")] string? Description = null, [property: JsonPropertyName("enum")] List<string>? Enum = null );

实操心得:使用 C# 9.0 引入的record类型来定义这些模型是绝佳选择。record默认提供值语义的相等比较,并且与System.Text.Json的序列化/反序列化配合得天衣无缝。务必使用[JsonPropertyName]特性来确保 JSON 属性名与 MCP 协议规范完全一致,这是跨语言通信不出错的关键。

4. 核心通信层与服务器主循环实现

MCP 服务器本质上是一个遵循 JSON-RPC 规范、通过标准输入输出(stdio)与客户端通信的进程。实现一个健壮的主循环是项目的核心。

4.1 标准输入输出(Stdio)通信模式

MCP 最常见的传输方式是 stdio。这意味着我们的控制台应用将从Console.In(标准输入)读取客户端发送的 JSON-RPC 请求,并将处理后的响应写入Console.Out(标准输出)。这种模式简单、通用,几乎被所有 MCP 客户端支持。

Services/McpServer.cs中,我们需要实现一个持续运行的循环:

// Services/McpServer.cs using System.Text.Json; using MyFirstMcpServer.Models; namespace MyFirstMcpServer.Services; public class McpServer { private readonly Dictionary<string, Func<JsonElement, Task<object?>>> _methodHandlers; public McpServer() { _methodHandlers = new Dictionary<string, Func<JsonElement, Task<object?>>> { ["initialize"] = HandleInitializeAsync, ["tools/list"] = HandleToolsListAsync, ["tools/call"] = HandleToolsCallAsync, // ... 注册其他方法处理器 }; } public async Task RunAsync(CancellationToken cancellationToken = default) { // 使用 Console.OpenStandardInput/Output 获取流,支持异步读写 using var inputStream = Console.OpenStandardInput(); using var outputStream = Console.OpenStandardOutput(); var reader = new StreamReader(inputStream); var writer = new StreamWriter(outputStream) { AutoFlush = true }; while (!cancellationToken.IsCancellationRequested) { try { var line = await reader.ReadLineAsync(cancellationToken); if (line == null) break; // 流结束 if (string.IsNullOrWhiteSpace(line)) continue; // 反序列化 JSON-RPC 请求 var request = JsonSerializer.Deserialize<JsonRpcRequest>(line); if (request == null || request.Method == null) continue; // 根据方法名分发给对应的处理器 if (_methodHandlers.TryGetValue(request.Method, out var handler)) { var result = await handler(request.Params ?? default(JsonElement)); var response = new JsonRpcSuccessResponse { Id = request.Id, Result = result }; var responseJson = JsonSerializer.Serialize(response); await writer.WriteLineAsync(responseJson); } else { // 方法未找到,返回错误 var errorResponse = new JsonRpcErrorResponse { /* ... 构造错误信息 */ }; await writer.WriteLineAsync(JsonSerializer.Serialize(errorResponse)); } } catch (Exception ex) { // 记录日志,并返回一个 JSON-RPC 错误 // 注意:不能因为单个请求异常而让整个服务器崩溃 Console.Error.WriteLine($"Error processing request: {ex.Message}"); } } } private async Task<object?> HandleInitializeAsync(JsonElement @params) { // 返回服务器能力信息,如协议版本、支持的特性等 return new { protocolVersion = "2024-11-05", capabilities = new { /* ... */ }, serverInfo = new { name = "MyFirstMcpServer", version = "1.0.0" } }; } private async Task<object?> HandleToolsListAsync(JsonElement @params) { // 返回服务器提供的所有工具列表 var tools = new List<Tool> { new Tool("read_file", "Reads the contents of a file", new InputSchema(...)), // ... 其他工具 }; return new { tools }; } private async Task<object?> HandleToolsCallAsync(JsonElement @params) { // 解析参数,调用具体的工具逻辑,并返回结果 // @params 应包含 `name` 和 `arguments` var toolName = @params.GetProperty("name").GetString(); var arguments = @params.GetProperty("arguments"); // 根据 toolName 分发到具体的工具执行器 // ... return new { /* 工具执行结果 */ }; } }

注意事项:JSON-RPC 要求每个请求都有一个id,响应必须携带相同的id。务必在响应中正确设置。此外,通信是全双工的,但基于行的 JSON 消息。确保每条消息以换行符结束,并且使用AutoFlush或手动调用FlushAsync来立即发送数据,避免客户端等待。

4.2 依赖注入与配置管理

为了构建一个可测试、可扩展的服务器,我们应该利用 .NET 的依赖注入容器。修改Program.cs并使用Host.CreateDefaultBuilder

// Program.cs using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using MyFirstMcpServer.Services; using MyFirstMcpServer.Tools; var host = Host.CreateDefaultBuilder(args) .ConfigureServices((context, services) => { // 注册服务器为单例 services.AddSingleton<McpServer>(); // 注册所有工具 services.AddSingleton<IFileSystemTool, FileReadTool>(); // 可以在这里添加配置 IConfiguration }) .Build(); // 从容器中获取服务器并运行 var server = host.Services.GetRequiredService<McpServer>(); await server.RunAsync();

这样,我们可以在McpServer的构造函数中注入所有需要的工具服务,使代码更清晰、更易于单元测试。

5. 具体工具实现:以文件读取为例

理论讲完了,我们来实现一个最实用、最基础的工具:读取本地文件。这个工具将允许 AI 助手(在获得用户明确许可和路径后)查看你指定文件的内容。

5.1 定义工具接口与实现类

首先,创建一个通用的工具接口,便于管理:

// Tools/ITool.cs namespace MyFirstMcpServer.Tools; public interface ITool { string Name { get; } string Description { get; } InputSchema InputSchema { get; } Task<object?> ExecuteAsync(JsonElement arguments, CancellationToken cancellationToken); }

然后,实现文件读取工具:

// Tools/FileReadTool.cs using System.Text.Json; using MyFirstMcpServer.Models.Types; namespace MyFirstMcpServer.Tools; public class FileReadTool : ITool { public string Name => "read_file"; public string Description => "Reads the contents of a text file from the local filesystem. The user must provide the full path."; public InputSchema InputSchema => new InputSchema( type: "object", properties: new Dictionary<string, PropertySchema> { ["path"] = new PropertySchema( type: "string", description: "The absolute path to the file to read." ), ["encoding"] = new PropertySchema( type: "string", description: "The text encoding to use (e.g., 'utf-8', 'ascii'). Defaults to 'utf-8'.", @enum: new List<string> { "utf-8", "ascii", "utf-16" } ) }, required: new List<string> { "path" } ); public async Task<object?> ExecuteAsync(JsonElement arguments, CancellationToken cancellationToken) { // 1. 参数验证与解析 if (!arguments.TryGetProperty("path", out var pathElement) || string.IsNullOrWhiteSpace(pathElement.GetString())) { throw new ArgumentException("The 'path' argument is required and cannot be empty."); } var filePath = pathElement.GetString()!; var encodingName = "utf-8"; if (arguments.TryGetProperty("encoding", out var encodingElement)) { encodingName = encodingElement.GetString() ?? encodingName; } Encoding encoding = encodingName.ToLowerInvariant() switch { "ascii" => Encoding.ASCII, "utf-16" => Encoding.Unicode, _ => Encoding.UTF8 // 默认 UTF-8 }; // 2. 安全检查(至关重要!) // 绝对禁止访问系统关键目录或进行路径遍历攻击 var fullPath = Path.GetFullPath(filePath); var safeBaseDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); // 示例:限制在用户目录下 if (!fullPath.StartsWith(safeBaseDirectory, StringComparison.OrdinalIgnoreCase)) { throw new UnauthorizedAccessException($"Access to path '{filePath}' is not allowed. Files must be within the user directory."); } // 3. 文件操作与异常处理 try { if (!File.Exists(fullPath)) { return new { error = $"File not found: {fullPath}" }; } // 对于大文件,考虑流式读取或限制大小 var fileInfo = new FileInfo(fullPath); const long maxFileSize = 10 * 1024 * 1024; // 10 MB 限制 if (fileInfo.Length > maxFileSize) { return new { error = $"File is too large ({fileInfo.Length} bytes). Maximum allowed size is {maxFileSize} bytes." }; } // 4. 读取文件内容 var content = await File.ReadAllTextAsync(fullPath, encoding, cancellationToken); return new { content, filePath = fullPath, size = fileInfo.Length }; } catch (IOException ioEx) { return new { error = $"IO Error reading file: {ioEx.Message}" }; } catch (UnauthorizedAccessException authEx) { return new { error = $"Permission denied: {authEx.Message}" }; } catch (Exception ex) { // 记录内部日志,返回用户友好信息 Console.Error.WriteLine($"Tool '{Name}' error: {ex}"); return new { error = "An internal error occurred while reading the file." }; } } }

5.2 工具注册与调用分发

现在,我们需要修改McpServer类,使其能够管理并调用这些工具。在构造函数中注入一个IEnumerable<ITool>

// Services/McpServer.cs (部分修改) public class McpServer { private readonly Dictionary<string, ITool> _tools; public McpServer(IEnumerable<ITool> tools) { _tools = tools.ToDictionary(t => t.Name, t => t); // ... 其他初始化 } private async Task<object?> HandleToolsListAsync(JsonElement @params) { var toolList = _tools.Values.Select(t => new { name = t.Name, description = t.Description, inputSchema = t.InputSchema }).ToList(); return new { tools = toolList }; } private async Task<object?> HandleToolsCallAsync(JsonElement @params) { var toolName = @params.GetProperty("name").GetString(); var arguments = @params.GetProperty("arguments"); if (!_tools.TryGetValue(toolName!, out var tool)) { throw new InvalidOperationException($"Tool '{toolName}' not found."); } var result = await tool.ExecuteAsync(arguments, CancellationToken.None); return new { content = new[] { new { type = "text", text = JsonSerializer.Serialize(result) } } }; } }

核心安全提醒:文件读取工具的实现中,路径安全验证是重中之重。永远不要相信客户端传入的路径。必须:

  1. 使用Path.GetFullPath解析完整路径,避免相对路径(如../../../etc/passwd)攻击。
  2. 定义一个安全的根目录(如用户文档目录、项目工作区),并验证解析后的完整路径是否位于此目录下。
  3. 考虑文件大小限制,防止服务器内存被大文件耗尽。
  4. 做好异常处理,返回给客户端的错误信息应友好,避免泄露内部路径或系统信息。

6. 集成测试与客户端配置

服务器写好了,怎么知道它能不能用呢?我们需要进行测试。有两种主要方式:使用真实的 MCP 客户端(如 Claude Desktop)进行集成测试,或者编写一个简单的测试客户端脚本。

6.1 使用 Claude Desktop 进行端到端测试

这是最直观的测试方法。假设我们的项目编译后生成的可执行文件是MyFirstMcpServer.exe(Windows)或MyFirstMcpServer(Linux/macOS)。

  1. 找到 Claude Desktop 的配置目录

    • macOS:~/Library/Application Support/Claude/claude_desktop_config.json
    • Windows:%APPDATA%\Claude\claude_desktop_config.json
    • Linux:~/.config/Claude/claude_desktop_config.json
  2. 编辑配置文件:在mcpServers部分添加我们的服务器配置。配置格式如下:

{ "mcpServers": { "my-file-server": { "command": "dotnet", "args": [ "/absolute/path/to/your/MyFirstMcpServer.dll" ], "env": { // 可选的环境变量 } } } }

如果你将项目发布为自包含的可执行文件,command可以直接指向那个可执行文件,无需dotnetargs

  1. 重启 Claude Desktop:保存配置文件后,完全退出并重新启动 Claude Desktop。

  2. 验证连接:在 Claude 的聊天窗口中,你可以尝试输入诸如“你能用什么工具?”或者直接说“请读取/Users/YourName/Documents/note.txt文件的内容”。如果配置正确,Claude 应该能识别出read_file工具,并在获得你确认后执行它。

6.2 编写简易测试脚本进行调试

在开发过程中,频繁重启 Claude Desktop 可能比较慢。我们可以编写一个简单的 C# 脚本或控制台程序来模拟客户端,直接向我们的服务器进程发送 JSON-RPC 请求并打印响应。这有助于快速调试协议逻辑。

// TestClient.cs (一个单独的控制台项目) using System.Diagnostics; using System.Text; using System.Text.Json; var serverProcess = new Process { StartInfo = new ProcessStartInfo { FileName = "dotnet", Arguments = "run --project ../MyFirstMcpServer/MyFirstMcpServer.csproj", RedirectStandardInput = true, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true } }; serverProcess.Start(); var stdOut = serverProcess.StandardOutput; var stdIn = serverProcess.StandardInput; // 1. 发送 initialize 请求 var initRequest = new { jsonrpc = "2.0", id = 1, method = "initialize", @params = new { protocolVersion = "2024-11-05", clientInfo = new { name = "TestClient", version = "1.0" } } }; await SendRequestAsync(stdIn, initRequest); var initResponse = await ReadResponseAsync(stdOut); Console.WriteLine($"Init Response: {initResponse}"); // 2. 发送 tools/list 请求 var listRequest = new { jsonrpc = "2.0", id = 2, method = "tools/list" }; await SendRequestAsync(stdIn, listRequest); var listResponse = await ReadResponseAsync(stdOut); Console.WriteLine($"Tools List: {listResponse}"); // 3. 发送 tools/call 请求 (调用 read_file) var callRequest = new { jsonrpc = "2.0", id = 3, method = "tools/call", @params = new { name = "read_file", arguments = new { path = "/tmp/test.txt" } // 测试一个存在的文件 } }; await SendRequestAsync(stdIn, callRequest); var callResponse = await ReadResponseAsync(stdOut); Console.WriteLine($"Tool Call Result: {callResponse}"); serverProcess.Kill(); static async Task SendRequestAsync(StreamWriter writer, object request) { var json = JsonSerializer.Serialize(request); await writer.WriteLineAsync(json); await writer.FlushAsync(); } static async Task<string> ReadResponseAsync(StreamReader reader) { var line = await reader.ReadLineAsync(); return line ?? string.Empty; }

运行这个测试客户端,你可以看到服务器返回的原始 JSON 数据,从而验证协议通信是否正常,工具列表是否正确,以及工具调用逻辑是否符合预期。

7. 进阶功能与扩展思路

一个基本的文件读取 MCP 服务器已经完成。但mehrandvd/tutorial-mcp-server-dotnet项目的价值在于它提供了一个可扩展的框架。你可以在此基础上,轻松添加更多强大的工具和资源。

7.1 实现更多实用工具

  1. 文件搜索工具:接收一个目录路径和关键词,返回匹配的文件列表。这需要用到System.IO.Directory.EnumerateFiles和简单的字符串匹配或正则表达式。
  2. 执行 Shell 命令工具(需极度谨慎):允许 AI 在受控环境下执行简单的系统命令(如git status,ls -la)。必须严格限制可执行的命令白名单,并做好参数过滤和超时控制,这是安全风险最高的工具之一。
  3. 数据库查询工具:连接到一个预设的数据库(如 SQLite、PostgreSQL),允许 AI 执行安全的 SELECT 查询(严禁执行 UPDATE/DELETE)。可以使用 Dapper 或 Entity Framework Core 来简化数据库操作。
  4. HTTP 请求工具:让 AI 能够调用外部 API。你需要处理 URL 验证、请求头、超时和 JSON 解析。

7.2 实现资源(Resources)

除了工具,MCP 还定义了资源。资源是只读的、可通过 URI 寻址的数据片段。例如,你可以实现一个file://资源,让 AI 通过resources/read请求直接获取文件内容,这与read_file工具功能重叠但协议路径不同。实现资源需要处理resources/listresources/read请求。

7.3 添加配置与日志

一个生产可用的服务器需要良好的配置和日志。

  • 配置:使用appsettings.jsonIConfiguration来管理安全根目录、允许的命令列表、数据库连接字符串等。
  • 日志:集成Microsoft.Extensions.Logging,将服务器运行状态、收到的请求、工具调用详情和错误信息记录到文件或控制台,便于调试和监控。

7.4 性能与稳定性优化

  • 异步处理:确保所有 I/O 操作(文件读写、网络请求、数据库查询)都是异步的,避免阻塞主线程。
  • 取消令牌:在长时间运行的工具方法中支持CancellationToken,允许客户端取消操作。
  • 请求限流:防止客户端在短时间内发送大量请求导致服务器过载。
  • 进程生命周期管理:确保服务器在收到退出信号或客户端断开连接时能优雅关闭,释放所有资源。

8. 部署与持续集成

最后,当我们开发完成一个功能丰富的 MCP 服务器后,可以考虑如何交付和部署。

  1. 发布为独立可执行文件:使用dotnet publish -c Release -r win-x64 --self-contained命令将项目发布为特定平台的自包含包。这样,最终用户无需安装 .NET 运行时即可运行。
  2. Docker 容器化:创建Dockerfile,将服务器打包成 Docker 镜像。这简化了在不同环境中的部署,也便于与 Claude Desktop 的 Docker 支持集成。
  3. CI/CD 管道:在 GitHub Actions 或 Azure DevOps 中设置自动化构建、测试和发布流程。每次推送代码到主分支,自动运行单元测试、集成测试,并发布新的可执行文件或 Docker 镜像。
  4. 创建安装脚本或包:对于非技术用户,可以编写简单的安装脚本(如 Shell 脚本或 PowerShell 脚本),自动下载最新版本并配置到 Claude Desktop。

通过mehrandvd/tutorial-mcp-server-dotnet这个项目,我们不仅学会了如何构建一个 .NET MCP 服务器,更重要的是掌握了一种让 AI 安全、可控地与真实世界交互的标准方法。这套模式可以无限扩展,无论是连接内部业务系统、物联网设备,还是复杂的云服务,你都可以通过定义相应的 MCP 工具和资源,为 AI 助手赋予强大的“行动力”。从今天开始,尝试为你最常使用的系统或数据源创建一个 MCP 接口吧,你会发现一个全新的、高效的 AI 协作方式正在眼前展开。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询