You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET 9 C#自定义MCP Server与Claude桌面端集成后意外退出

问题:.NET 9开发的MCP服务器初始化后连接被意外关闭

我使用.NET 9开发C#自定义MCP(Model Context Protocol)服务器,用于与Claude桌面AI工具集成。服务器可正常启动,通过stdin/stdout发送了合法的JSON-RPC 2.0初始化响应,但连接随即被关闭。

Claude端日志

2025-05-31T04:27:53.587Z [masood-mcpsrv] [info] Initializing server...
2025-05-31T04:27:53.740Z [masood-mcpsrv] [info] Server started and connected successfully
2025-05-31T04:27:53.812Z [masood-mcpsrv] [info] Message from client: {"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"claude-ai","version":"0.1.0"}},"jsonrpc":"2.0","id":0}
2025-05-31T04:27:54.121Z [masood-mcpsrv] [info] Message from server: {"jsonrpc":"2.0","id":0,"result":{"serverInfo":{"name":"SimpleMCP","version":"0.1.0"}}}
2025-05-31T04:27:54.126Z [masood-mcpsrv] [info] Client transport closed
2025-05-31T04:27:54.127Z [masood-mcpsrv] [info] Server transport closed
2025-05-31T04:27:54.127Z [masood-mcpsrv] [info] Client transport closed
2025-05-31T04:27:54.127Z [masood-mcpsrv] [info] Server transport closed unexpectedly, this is likely due to the process exiting early. If you are developing this MCP server you can add output to stderr (i.e. `console.error('...')` in JavaScript, `print('...', file=sys.stderr)` in python) and it will appear in this log.
2025-05-31T04:27:54.127Z [masood-mcpsrv] [error] Server disconnected. For troubleshooting guidance, please visit our [debugging documentation](https://modelcontextprotocol.io/docs/tools/debugging) {"context":"connection"}
2025-05-31T04:27:54.138Z [masood-mcpsrv] [info] Server transport closed
2025-05-31T04:27:54.139Z [masood-mcpsrv] [info] Client transport closed

服务器代码

using System.Text.Json;
using System.Text;
using System.Text.Json.Nodes;
using static System.Runtime.InteropServices.JavaScript.JSType;

Console.OutputEncoding = Encoding.UTF8;
Console.InputEncoding = Encoding.UTF8;

while (true)
{
    try
    {
        string? line = Console.ReadLine();

        // If input is closed, wait instead of exiting
        if (line == null)
        {
            Thread.Sleep(100); // Prevent CPU spin
            continue;
        }

        var request = JsonNode.Parse(line);
        var id = request?["id"]?.GetValue<int>();
        var method = request?["method"]?.GetValue<string>();

        if (method == "initialize")
        {
            var result = new
            {
                jsonrpc = "2.0",
                id,
                result = new
                {
                    serverInfo = new
                    {
                        name = "SimpleMCP",
                        version = "0.1.0"
                    }
                }
            };
            WriteJsonResponse(result);
        }
        else if (method == "getModelContext")
        {
            var result = new
            {
                jsonrpc = "2.0",
                id,
                result = new
                {
                    modelContext = new
                    {
                        kind = "modelContext",
                        name = "SampleProduct",
                        properties = new[]
                        {
                            new { name = "id", type = "string" },
                            new { name = "name", type = "string" },
                            new { name = "price", type = "number" }
                        }
                    }
                }
            };
            WriteJsonResponse(result);
        }
        else
        {
            var error = new
            {
                jsonrpc = "2.0",
                id,
                error = new { code = -32601, message = "Method not found" }
            };
            WriteJsonResponse(error);
        }
    }
    catch (Exception ex)
    {
        var error = new
        {
            jsonrpc = "2.0",
            error = new { code = -32603, message = ex.Message }
        };
        WriteJsonResponse(error);
    }
}
static void WriteJsonResponse(object response)
{
    // Serialize with no extra formatting (compact)
    string json = JsonSerializer.Serialize(response, new JsonSerializerOptions
    {
        WriteIndented = false // compact, no pretty print
    });

    // Trim just in case, to remove any accidental whitespace/newlines
    json = json.Trim();

    // Write single line JSON to stdout
    Console.WriteLine(json);

    // Console.WriteLine flushes automatically, but if you want to be explicit:
    Console.Out.Flush();
}

排查原因

  1. 初始化响应不符合MCP协议要求:日志显示客户端发送的initialize请求包含protocolVersion和capabilities字段,但服务器返回的响应仅包含serverInfo。根据MCP协议规范,initialize响应必须包含capabilities字段(即使是空对象),缺少该字段会导致Claude客户端验证失败,直接关闭连接。
  2. 异常处理违反JSON-RPC 2.0规范:当捕获异常时,返回的错误响应未包含id字段。JSON-RPC 2.0要求所有响应(包括错误)必须匹配请求的id,若无法获取id需设为null,否则客户端会判定响应无效。

解决方案

1. 补全initialize响应的capabilities字段

在initialize响应中添加capabilities字段,根据协议版本2024-11-05返回对应结构,示例中可先返回空对象满足基本要求。

2. 修复异常处理的错误响应

确保异常捕获时返回的错误响应包含id字段,若请求解析失败无法获取id,则设为null。

3. 优化输入读取逻辑

将同步的Console.ReadLine()改为异步读取,避免使用Thread.Sleep浪费资源,同时更优雅地处理stdin关闭情况。

修改后的完整代码

using System.Text.Json;
using System.Text;
using System.Text.Json.Nodes;

Console.OutputEncoding = Encoding.UTF8;
Console.InputEncoding = Encoding.UTF8;

// 使用异步读取替代同步读取,避免Thread.Sleep
while (true)
{
    try
    {
        string? line = await Console.In.ReadLineAsync();

        if (line == null)
        {
            // 输入流关闭时,短暂等待后退出循环
            await Task.Delay(100);
            continue;
        }

        var request = JsonNode.Parse(line);
        var idNode = request?["id"];
        // 兼容不同类型的id(JSON-RPC允许id为字符串、数字或null)
        object? id = idNode?.GetValue<object>();
        var method = request?["method"]?.GetValue<string>();

        if (method == "initialize")
        {
            var result = new
            {
                jsonrpc = "2.0",
                id,
                result = new
                {
                    serverInfo = new
                    {
                        name = "SimpleMCP",
                        version = "0.1.0"
                    },
                    // 补全必填的capabilities字段,根据实际支持的能力填充
                    capabilities = new {}
                }
            };
            await WriteJsonResponseAsync(result);
        }
        else if (method == "getModelContext")
        {
            var result = new
            {
                jsonrpc = "2.0",
                id,
                result = new
                {
                    modelContext = new
                    {
                        kind = "modelContext",
                        name = "SampleProduct",
                        properties = new[]
                        {
                            new { name = "id", type = "string" },
                            new { name = "name", type = "string" },
                            new { name = "price", type = "number" }
                        }
                    }
                }
            };
            await WriteJsonResponseAsync(result);
        }
        else
        {
            var error = new
            {
                jsonrpc = "2.0",
                id,
                error = new { code = -32601, message = "Method not found" }
            };
            await WriteJsonResponseAsync(error);
        }
    }
    catch (Exception ex)
    {
        // 异常时返回包含id(null)的错误响应
        var error = new
        {
            jsonrpc = "2.0",
            id = (object?)null,
            error = new { code = -32603, message = ex.Message }
        };
        await WriteJsonResponseAsync(error);
    }
}

static async Task WriteJsonResponseAsync(object response)
{
    string json = JsonSerializer.Serialize(response, new JsonSerializerOptions
    {
        WriteIndented = false
    });
    json = json.Trim();
    await Console.Out.WriteLineAsync(json);
    await Console.Out.FlushAsync();
}

内容的提问来源于stack exchange,提问作者Masood Ahmad

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 00:17:04