1. 项目概述与核心价值最近在折腾AI Agent开发特别是想给Claude Desktop或者Cursor这类工具加上自定义的“工具箱”让它们能直接操作我的本地数据库、调用内部API或者读取特定格式的日志文件。市面上通用的MCPModel Context Protocol服务器要么功能太泛要么配置复杂得让人头疼。直到我发现了这个叫ali-kamali/Axon.MCP.Server的项目它就像是为.NET开发者量身打造的一块“乐高积木”让你能快速、优雅地构建自己的MCP服务器。简单来说MCP协议是Anthropic提出的一套标准旨在让大语言模型LLM能够安全、结构化地使用外部工具和资源。你可以把它想象成给AI模型装上了一双“手”和“眼睛”手可以执行你定义的操作比如运行一个脚本、发送HTTP请求眼睛可以读取你允许它访问的数据比如数据库表、文件内容。而Axon.MCP.Server就是一个基于.NET 8、遵循MCP协议规范的服务器端框架。它把协议底层复杂的通信、序列化、生命周期管理都封装好了你只需要关心最核心的业务逻辑“我要给AI提供什么工具Tools我能让AI读取什么资源Resources”这个项目的核心价值在于它的“开发者友好性”。它不是一个黑盒服务而是一个高度可扩展的框架。如果你熟悉ASP.NET Core的中间件Middleware模式或者依赖注入DI那么上手会非常快。它让你摆脱了从零实现协议细节的繁琐专注于定义有业务价值的ITool和IResource从而快速将企业内部系统、私有数据源安全地暴露给AI助手使用极大地提升了开发效率和应用想象力。2. 核心架构与设计思路拆解要理解怎么用好Axon.MCP.Server得先摸清它的设计脉络。整个框架是围绕MCP协议的核心概念构建的采用了清晰的分层和接口驱动设计。2.1 协议层抽象IServer与IClient框架最底层是对MCP协议本身的抽象。IServer接口定义了服务器端需要处理的所有协议方法例如列出工具 (list_tools)、调用工具 (call_tool)、读取资源 (read_resource) 等。而IClient则代表连接到服务器的客户端通常是AI助手的前端如Claude Desktop。框架内部已经实现了基于标准输入输出stdio或WebSocket的通信层这意味着你通常不需要直接和这些接口打交道除非你要实现非常自定义的传输方式。这种设计的好处是隔离了协议通信的复杂性。作为开发者你的注意力可以完全放在上层如何定义工具和资源。协议层的序列化JSON-RPC、消息路由、错误处理都被框架默默消化了。2.2 核心模型层Tool与Resource这是你需要投入最多精力的地方。框架定义了Tool和Resource这两个核心模型。Tool(工具)代表一个可执行的操作。每个工具必须有一个唯一的name一个清晰的description这个描述很重要AI靠它来理解工具用途以及一个定义输入参数的inputSchema采用JSON Schema格式。当AI决定调用某个工具时框架会找到对应的ITool实现并执行其ExecuteAsync方法。Resource(资源)代表一个可读的数据源。每个资源由一个URI (uri) 唯一标识并有一个MIME类型 (mimeType) 表明内容格式如text/plain,application/json。AI可以通过URI来请求读取资源的内容。你需要实现IResource接口来返回具体内容。框架鼓励你通过实现ITool和IResource接口来创建自己的工具和资源。这种接口驱动的方式使得单元测试变得非常容易。2.3 依赖注入与模块化集成Axon.MCP.Server深度集成了.NET的依赖注入系统。这是它优雅性的关键。你的工具和资源类可以通过构造函数注入任何已注册的服务比如DbContext访问数据库、HttpClient调用外部API、ILogger记录日志或者你自定义的业务服务。启动流程通常是在Program.cs中创建一个HostApplicationBuilder。调用builder.Services.AddMcpServer()来注册所有框架必需的核心服务。在这个方法中通过AddToolT和AddResourceT来注册你的具体实现。框架会自动发现和管理它们的生命周期。这种模式使得代码组织非常清晰你可以将不同的工具分类放在不同的类库中然后在主项目里按需注册实现了很好的解耦和可维护性。2.4 配置与扩展性框架提供了灵活的配置选项。你可以通过McpServerOptions来配置服务器行为例如设置服务器名称、版本或者自定义某些协议扩展。更重要的是它支持中间件管道。虽然MCP协议本身是固定的但你可以在工具调用或资源读取的前后插入自定义逻辑比如身份验证、授权检查、请求日志记录或性能监控。这通过实现IMcpMiddleware接口并注册到DI容器来完成。设计思路总结Axon.MCP.Server采用了“框架处理协议开发者专注业务”的设计哲学。它通过清晰的接口ITool,IResource定义了扩展点通过强大的依赖注入容器管理组件并通过配置和中间件提供必要的灵活性。这种设计让构建一个生产可用的MCP服务器变得像编写普通的.NET服务一样熟悉和可控。3. 从零开始构建你的第一个MCP工具理论说得再多不如动手写一行代码。我们来实现一个经典的“待办事项Todo”管理工具让AI助手能帮我们查看和添加任务。假设我们使用内存存储来简化。3.1 环境准备与项目搭建首先确保你安装了.NET 8 SDK或更高版本。然后创建一个新的控制台应用dotnet new console -n MyTodoMcpServer cd MyTodoMcpServer接下来添加Axon.MCP.Server的NuGet包引用。你需要查看项目的具体版本通常命令如下dotnet add package Axon.MCP.Server由于这是一个相对较新的项目你可能需要指定版本号或者查看作者ali-kamali的GitHub仓库获取最新的包信息。安装完成后打开Program.cs文件我们将从这里开始构建。3.2 定义数据模型与存储服务在开始写MCP工具之前我们先定义简单的领域模型和一个内存存储服务。创建一个TodoItem.cs文件namespace MyTodoMcpServer; public class TodoItem { public string Id { get; set; } Guid.NewGuid().ToString(); public string Title { get; set; } string.Empty; public string? Description { get; set; } public bool IsCompleted { get; set; } false; public DateTime CreatedAt { get; set; } DateTime.UtcNow; }然后创建一个简单的内存存储服务ITodoRepository.csnamespace MyTodoMcpServer; public interface ITodoRepository { TaskListTodoItem GetAllAsync(); TaskTodoItem? GetByIdAsync(string id); TaskTodoItem AddAsync(TodoItem item); Taskbool UpdateAsync(TodoItem item); Taskbool DeleteAsync(string id); } public class InMemoryTodoRepository : ITodoRepository { private readonly Dictionarystring, TodoItem _todos new(); public TaskListTodoItem GetAllAsync() Task.FromResult(_todos.Values.ToList()); public TaskTodoItem? GetByIdAsync(string id) Task.FromResult(_todos.GetValueOrDefault(id)); public TaskTodoItem AddAsync(TodoItem item) { _todos[item.Id] item; return Task.FromResult(item); } public Taskbool UpdateAsync(TodoItem item) { if (_todos.ContainsKey(item.Id)) { _todos[item.Id] item; return Task.FromResult(true); } return Task.FromResult(false); } public Taskbool DeleteAsync(string id) Task.FromResult(_todos.Remove(id)); }这个仓库服务将通过依赖注入提供给我们的MCP工具使用。3.3 实现第一个工具ListTodosTool现在我们来创建第一个MCP工具列出所有待办事项。创建一个新类ListTodosTool.csusing Axon.Mcp.Server.Models; using Axon.Mcp.Server.Tools; namespace MyTodoMcpServer.Tools; public class ListTodosTool : ITool { // 工具名称必须是唯一的 public string Name list_todos; // 工具描述AI根据这个决定是否以及如何调用它 public string Description 获取所有的待办事项列表。可以按完成状态过滤。; // 输入参数模式这里我们定义一个可选的过滤器参数 public JsonSchema InputSchema new JsonSchemaBuilder() .Type(SchemaValueType.Object) .Properties( (filter, new JsonSchemaBuilder() .Type(SchemaValueType.String) .Description(过滤条件可选值all(全部), active(未完成), completed(已完成)。默认是all。) .Enum([all, active, completed]) ) ) .Build(); private readonly ITodoRepository _repository; // 通过构造函数注入存储服务 public ListTodosTool(ITodoRepository repository) { _repository repository; } // 核心执行方法 public async TaskToolResult ExecuteAsync(JsonElement arguments, CancellationToken cancellationToken default) { // 1. 解析输入参数 string filter all; if (arguments.TryGetProperty(filter, out var filterProp) filterProp.ValueKind JsonValueKind.String) { filter filterProp.GetString()!; } // 2. 调用业务逻辑 var allTodos await _repository.GetAllAsync(); IEnumerableTodoItem filteredTodos filter switch { active allTodos.Where(t !t.IsCompleted), completed allTodos.Where(t t.IsCompleted), _ allTodos // 包括 all 和其他未识别的值 }; // 3. 格式化输出给AI // 将结果组织成清晰的文本AI更容易理解和总结 var outputText new StringBuilder(); outputText.AppendLine($找到 {filteredTodos.Count()} 个待办事项 (过滤条件: {filter}):); outputText.AppendLine(); foreach (var todo in filteredTodos) { var status todo.IsCompleted ? ✅ : ⏳; outputText.AppendLine($- [{status}] {todo.Title} (ID: {todo.Id})); if (!string.IsNullOrEmpty(todo.Description)) { outputText.AppendLine($ 描述: {todo.Description}); } outputText.AppendLine($ 创建于: {todo.CreatedAt:yyyy-MM-dd HH:mm}); outputText.AppendLine(); } // 4. 返回工具调用结果 // MCP协议期望返回一个 Content 数组这里我们返回纯文本内容 return new ToolResult( new ListContent { new Content(ContentType.Text, outputText.ToString()) } ); } }关键点解析InputSchema我们使用了JsonSchemaBuilder来定义输入参数。这里定义了一个枚举类型的filter参数。清晰的定义能帮助AI生成正确的调用参数。依赖注入工具类通过构造函数接收ITodoRepository这使得业务逻辑与数据访问分离易于测试。ExecuteAsync方法这是工具的核心。我们解析参数调用仓储服务然后将结果格式化成对人类和AI都友好的文本。ToolResult返回的结果需要包装成Content列表。目前我们返回Text类型你也可以返回Image或未来协议支持的其他类型。3.4 实现第二个工具CreateTodoTool再实现一个创建待办事项的工具CreateTodoTool.csusing Axon.Mcp.Server.Models; using Axon.Mcp.Server.Tools; using System.Text.Json; namespace MyTodoMcpServer.Tools; public class CreateTodoTool : ITool { public string Name create_todo; public string Description 创建一个新的待办事项。需要提供标题描述可选。; public JsonSchema InputSchema new JsonSchemaBuilder() .Type(SchemaValueType.Object) .Properties( (title, new JsonSchemaBuilder() .Type(SchemaValueType.String) .Description(待办事项的标题必填。) .MinLength(1) ), (description, new JsonSchemaBuilder() .Type(SchemaValueType.String) .Description(待办事项的详细描述可选。) ) ) .Required([title]) // 指定必填参数 .Build(); private readonly ITodoRepository _repository; private readonly ILoggerCreateTodoTool _logger; public CreateTodoTool(ITodoRepository repository, ILoggerCreateTodoTool logger) { _repository repository; _logger logger; } public async TaskToolResult ExecuteAsync(JsonElement arguments, CancellationToken cancellationToken) { // 参数验证 if (!arguments.TryGetProperty(title, out var titleProp) || titleProp.GetString()?.Length 0) { // 返回错误信息AI会接收到并可能提示用户 return new ToolResult( new ListContent { new Content(ContentType.Text, 错误标题(title)是必填参数且不能为空。) }, isError: true ); } string title titleProp.GetString()!; string? description null; if (arguments.TryGetProperty(description, out var descProp) descProp.ValueKind JsonValueKind.String) { description descProp.GetString(); } // 创建实体并保存 var newTodo new TodoItem { Title title, Description description }; try { var savedTodo await _repository.AddAsync(newTodo); _logger.LogInformation(创建了新的待办事项ID: {TodoId}, savedTodo.Id); // 返回成功信息 return new ToolResult( new ListContent { new Content(ContentType.Text, $✅ 成功创建待办事项\n标题{savedTodo.Title}\nID{savedTodo.Id}\n你可以使用ID来引用这个事项。) } ); } catch (Exception ex) { _logger.LogError(ex, 创建待办事项失败); return new ToolResult( new ListContent { new Content(ContentType.Text, $创建失败{ex.Message}) }, isError: true ); } } }这个工具演示了更完整的流程必填参数验证、业务逻辑执行、成功/错误结果返回以及日志记录。良好的错误处理能让AI助手更好地与用户交互。3.5 组装并运行服务器最后我们修改Program.cs将所有部分组装起来using Axon.Mcp.Server.Extensions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using MyTodoMcpServer; using MyTodoMcpServer.Tools; // 创建主机构建器 var builder Host.CreateApplicationBuilder(args); // 1. 注册业务服务 builder.Services.AddSingletonITodoRepository, InMemoryTodoRepository(); // 2. 注册MCP服务器并添加我们的工具 builder.Services.AddMcpServer(options { options.ServerInfo.Name MyTodoServer; options.ServerInfo.Version 1.0.0; }) .AddToolListTodosTool() // 注册工具 .AddToolCreateTodoTool(); // 注册另一个工具 // 3. 构建并运行主机 var host builder.Build(); await host.RunAsync();现在一个功能完整的MCP服务器就搭建好了。你可以使用dotnet run来启动它。默认情况下它会使用标准输入输出stdio进行通信这正是Claude Desktop等客户端所期望的。实操心得在第一次运行前确保你的开发环境能处理stdio。如果是在VS Code里调试你可能需要配置launch.json将console设置为internalConsole可能会遇到问题因为MCP客户端期望连接到真正的stdio。一个更可靠的方式是直接通过命令行dotnet run启动然后在另一个终端用测试客户端连接或者使用专门的MCP开发测试工具。4. 进阶实现资源Resources与更复杂的工具工具让AI可以“做事情”而资源让AI可以“读数据”。我们来实现一个资源让AI能读取某个特定待办事项的详细信息。4.1 实现资源提供器TodoResource创建一个TodoResource.cs文件using Axon.Mcp.Server.Models; using Axon.Mcp.Server.Resources; namespace MyTodoMcpServer.Resources; public class TodoResource : IResource { // 资源的URI模式。AI可以通过类似 todo://items/{id} 的URI来访问。 public string UriPattern todo://items/{id}; // 资源内容的MIME类型这里我们返回JSON格式。 public string MimeType application/json; private readonly ITodoRepository _repository; public TodoResource(ITodoRepository repository) { _repository repository; } public async TaskResourceResult ReadAsync(string uri, CancellationToken cancellationToken default) { // 从URI中提取ID。框架会根据 UriPattern 进行路由匹配。 // 例如对于 todo://items/abc123uri 参数就是这个完整字符串。 // 我们需要手动解析出 abc123。 var uriParts uri.Split(/); var id uriParts.LastOrDefault(); // 简单提取生产环境应用更健壮的解析 if (string.IsNullOrEmpty(id)) { return new ResourceResult(null, 无效的资源URI未提供ID。); } var todo await _repository.GetByIdAsync(id); if (todo null) { return new ResourceResult(null, $未找到ID为 {id} 的待办事项。); } // 将待办事项序列化为JSON字符串作为资源内容 var jsonContent JsonSerializer.Serialize(todo, new JsonSerializerOptions { WriteIndented true }); return new ResourceResult(new TextContent(jsonContent)); } }关键点UriPattern定义了资源的访问模式。{id}是一个占位符表示动态部分。ReadAsync根据传入的具体URI解析参数这里是id获取数据并返回格式化的内容。这里我们返回了美观格式化的JSONAI可以很好地解析这种结构化数据。错误处理如果资源不存在我们返回一个带有错误信息的ResourceResult。客户端AI会收到这个信息并可能告知用户。在Program.cs中注册这个资源builder.Services.AddMcpServer(options { ... }) .AddToolListTodosTool() .AddToolCreateTodoTool() .AddResourceTodoResource(); // 注册资源4.2 设计组合型工具利用资源进行复杂操作工具和资源可以组合使用。例如我们可以创建一个工具它先调用list_todos虽然目前是直接读仓库但未来可以是另一个工具然后根据用户选择生成访问某个待办事项详细信息的资源URI并引导AI去读取。假设我们想让AI在列出待办事项后能根据用户的问题如“告诉我第三个任务的详情”自动获取详情。这需要更智能的提示工程和AI的上下文理解能力。MCP服务器本身不负责这种逻辑编排它只提供原子化的工具和资源。编排逻辑由AI模型根据你的工具描述和用户指令自行决定。因此设计良好的工具描述 (Description) 和资源URI模式至关重要。例如你可以在ListTodosTool的输出中明确提示“每个事项的ID为{id}你可以通过访问资源todo://items/{id}来查看其完整详情。” 这样AI在后续对话中就可能主动使用这个资源。4.3 添加身份验证中间件在生产环境中安全是首要考虑。我们可能不希望任何人都能通过AI助手操作我们的待办事项系统。Axon.MCP.Server的中间件机制可以轻松实现这一点。创建一个AuthenticationMiddleware.csusing Axon.Mcp.Server.Middleware; using Axon.Mcp.Server.Models; namespace MyTodoMcpServer.Middleware; public class AuthenticationMiddleware : IMcpMiddleware { private readonly ILoggerAuthenticationMiddleware _logger; public AuthenticationMiddleware(ILoggerAuthenticationMiddleware logger) { _logger logger; } public async Task InvokeAsync(McpContext context, FuncTask next) { // 1. 检查请求头或上下文中的认证信息 // 这里只是一个示例实际可能从 context 中获取令牌或API Key var authHeader context.Headers?.FirstOrDefault(h h.Key.Equals(Authorization, StringComparison.OrdinalIgnoreCase)); var isValid authHeader.HasValue authHeader.Value.Value?.StartsWith(Bearer secret-token-) true; if (!isValid) { _logger.LogWarning(未授权的MCP请求被拒绝。); // 2. 如果认证失败可以设置错误响应并中断管道 context.SetErrorResponse(-32001, 未授权访问); return; // 不调用 next请求终止于此 } _logger.LogInformation(请求已通过认证。); // 3. 认证通过继续执行后续中间件和最终的工具/资源处理程序 await next(); } }然后在Program.cs中注册这个中间件。注意中间件的注册顺序很重要builder.Services.AddMcpServer(options { ... }) .AddToolListTodosTool() .AddToolCreateTodoTool() .AddResourceTodoResource() .AddMiddlewareAuthenticationMiddleware(); // 注册认证中间件现在所有来自客户端的请求都会先经过这个认证检查。具体的认证逻辑如验证JWT令牌、API Key需要你根据实际情况实现并与你的AI客户端配置相匹配例如在Claude Desktop的MCP服务器配置中添加认证头。5. 调试、测试与部署实战开发完成后如何验证我们的MCP服务器工作正常呢5.1 使用mcp-cli进行测试Anthropic官方提供了一个命令行工具mcp-cli非常适合用于测试和调试。首先安装它需要Node.jsnpm install -g modelcontextprotocol/cli然后创建一个服务器配置文件比如server-config.json{ mcpServers: { my-todo-server: { command: dotnet, args: [run, --project, /path/to/your/MyTodoMcpServer.csproj], env: { ASPNETCORE_ENVIRONMENT: Development } } } }运行测试客户端连接到你的服务器mcp-cli --config server-config.json my-todo-server如果连接成功你会进入一个交互式会话。你可以输入/list查看服务器提供的所有工具和资源然后尝试调用工具例如/call list_todos /call create_todo {title: 学习MCP协议}这能让你在不依赖完整AI客户端的情况下快速验证服务器的协议兼容性和工具功能。5.2 集成到 Claude Desktop要让你的服务器在Claude Desktop中工作需要编辑Claude的MCP配置文件。配置文件的位置通常如下macOS:~/Library/Application Support/Claude/claude_desktop_config.jsonWindows:%APPDATA%\Claude\claude_desktop_config.json在配置文件中添加你的服务器配置{ mcpServers: { my-todo-server: { command: dotnet, args: [/full/path/to/your/MyTodoMcpServer.dll], env: { DOTNET_ENVIRONMENT: Production } } } }保存配置并重启Claude Desktop。如果配置正确在Claude的输入框里你应该能看到一个新出现的“工具”图标点击它就能看到list_todos和create_todo等工具并可以直接使用。5.3 性能优化与监控考虑当工具被频繁调用时性能变得重要。异步与取消令牌确保你的ExecuteAsync和ReadAsync方法正确支持CancellationToken并在进行I/O操作如数据库查询、HTTP请求时传递它。这允许客户端在长时间操作时取消请求。依赖注入作用域如果你的工具依赖DbContext这类有作用域生命周期的服务确保工具本身注册为Scoped或Transient并在AddToolT时指定正确的生命周期。框架通常能很好地处理这一点。日志与指标在工具和中间件中注入ILogger记录关键操作、参数和耗时。考虑使用像IMetrics这样的接口来收集工具调用次数、成功失败率、延迟等指标便于监控。资源缓存对于不常变化的资源可以在IResource实现中加入简单的内存缓存但要注意缓存失效策略。5.4 打包与部署对于生产部署你需要将项目发布为自包含的可执行文件。dotnet publish -c Release -r win-x64 --self-contained true -o ./publish # 或针对Linux dotnet publish -c Release -r linux-x64 --self-contained true -o ./publish发布后publish目录下会生成一个可执行文件如MyTodoMcpServer.exe或MyTodoMcpServer。在Claude Desktop的配置中将command指向这个可执行文件的完整路径即可。如果服务器需要长期运行或作为系统服务可以考虑使用systemd(Linux) 或 Windows Service 来管理其生命周期确保稳定性和自动重启。6. 常见问题与排查技巧实录在实际开发和集成过程中我踩过不少坑这里总结一下最常见的问题和解决方法。6.1 连接与通信失败问题现象Claude Desktop无法连接服务器或者mcp-cli连接后立即断开。检查点1服务器是否成功启动并监听stdio单独运行你的可执行文件dotnet run或直接运行发布后的文件。如果程序立即退出说明可能有未处理的异常。在Program.cs的Main方法开头加try-catch并记录日志查看具体错误。确保你的程序是一个长时间运行的控制台应用而不是执行完任务就退出的。检查点2配置路径和参数是否正确Claude Desktop配置中的command和args必须绝对准确。特别是args如果指向DLLcommand必须是dotnet如果指向可执行文件args可以为空或包含必要的参数。路径中的空格和特殊字符如果路径包含空格务必用双引号包裹整个路径。在JSON配置中需要对双引号进行转义args: [\C:/My Projects/app.dll\]。检查点3环境变量和依赖项确保运行环境安装了正确版本的.NET运行时如果是框架依赖部署或所有依赖都已包含自包含部署。检查env配置是否正确设置了所需的环境变量如ASPNETCORE_ENVIRONMENT。6.2 工具/资源列表为空或找不到问题现象客户端能连接但/list命令显示工具和资源列表为空。检查点1工具和资源是否已正确注册确认在AddMcpServer()之后调用了.AddToolT()和.AddResourceT()。检查你的工具类是否实现了ITool接口资源类是否实现了IResource接口并且类不是abstract或static。检查点2依赖注入是否正常如果你的工具/资源类的构造函数需要其他服务如ITodoRepository请确保这些服务也已注册到DI容器中例如通过builder.Services.AddSingletonITodoRepository, InMemoryTodoRepository()。在Program.cs的builder.Build()之后、RunAsync()之前可以尝试临时获取服务并检查以排除DI问题。检查点3生命周期冲突避免将需要作用域服务的工具注册为Singleton。如果工具依赖DbContext通常应将工具注册为Scoped或Transient。AddToolT()方法通常有重载允许你指定生命周期。6.3 工具调用失败或返回意外错误问题现象能看到工具列表但调用时失败返回Internal error或自定义的错误信息。检查点1输入参数格式是否正确AI客户端会根据你定义的InputSchema生成参数。使用mcp-cli手动调用时确保传递的JSON参数完全符合Schema。一个常见的错误是JSON格式不正确或类型不匹配。在你的ExecuteAsync方法中使用JsonSerializer或手动JsonElement解析时做好防御性编程处理缺失或类型错误的属性。检查点2工具方法内部是否抛出未捕获的异常在ExecuteAsync方法内部用try-catch包裹核心逻辑并记录详细的异常信息到日志。框架可能会捕获异常并转化为Internal error但具体的错误信息对调试至关重要。检查是否有异步操作没有正确await导致返回了Task而不是实际结果。检查点3ToolResult构造是否正确确保返回的Content列表不为null。即使是错误结果也应通过isError: true参数明确标识。6.4 性能问题与超时问题现象工具调用响应缓慢或者客户端报告超时。检查点1工具执行是否包含长时间同步操作绝对避免在ExecuteAsync中进行长时间的CPU密集型同步计算或阻塞式I/O。这会使服务器线程卡住无法处理其他请求。将所有I/O操作文件、网络、数据库都改为异步版本Async后缀的方法并正确使用await。检查点2是否合理使用了CancellationToken将传入的cancellationToken传递给所有支持它的异步I/O操作。这允许客户端在等待过久时取消请求。在循环或长时间操作中定期检查cancellationToken.IsCancellationRequested并在被取消时优雅退出。检查点3外部依赖是否成为瓶颈如果工具调用外部API或查询复杂数据库这些外部服务的延迟会直接影响工具响应时间。考虑为工具实现缓存层或者优化查询。6.5 调试与日志记录技巧启用详细日志在appsettings.Development.json中将Logging:LogLevel:Default设置为Debug或Trace。Axon.MCP.Server框架本身可能也会输出有用的日志。控制台日志在开发时确保日志输出到了控制台stdio这样你才能在运行dotnet run或通过mcp-cli时看到它们。避免使用仅输出到文件的日志器。结构化日志使用ILogger的模板功能记录结构化信息如_logger.LogDebug(调用工具 {ToolName}参数: {Args}, Name, arguments);。这比字符串拼接更清晰且便于日志系统分析。使用调试器在VS Code或Visual Studio中你可以直接附加调试器到运行中的MCP服务器进程。在工具方法的开始设置断点然后通过客户端触发调用这是定位复杂逻辑问题最有效的方法。