|
85 | 85 | var method = request.GetProperty("method").GetString(); |
86 | 86 | var id = request.TryGetProperty("id", out var idProp) ? idProp : (JsonElement?)null; |
87 | 87 | var paramsElement = request.TryGetProperty("params", out var paramsProp) ? paramsProp : (JsonElement?)null; |
88 | | - |
89 | | - await Console.Error.WriteLineAsync($"[DEBUG] Method: {method}, ID: {id}"); |
90 | | - |
| 88 | + |
| 89 | + // JSON-RPC 2.0 §4.1: a Notification is a Request object without |
| 90 | + // an "id" member. The Server MUST NOT reply to a Notification. |
| 91 | + // Also: any method name starting with "notifications/" is a |
| 92 | + // notification per the MCP spec (e.g. "notifications/initialized"), |
| 93 | + // even if some clients buggily include an id. Detect both. |
| 94 | + var isNotification = id is null |
| 95 | + || (method?.StartsWith("notifications/", StringComparison.Ordinal) == true); |
| 96 | + |
| 97 | + await Console.Error.WriteLineAsync($"[DEBUG] Method: {method}, ID: {id}, Notification: {isNotification}"); |
| 98 | + |
91 | 99 | object? result = null; |
92 | 100 | object? error = null; |
93 | | - |
| 101 | + |
94 | 102 | try |
95 | 103 | { |
96 | 104 | // Handle MCP methods |
|
99 | 107 | "initialize" => mcpHandler.Initialize(paramsElement?.Deserialize<object>()), |
100 | 108 | "tools/list" => mcpHandler.ToolsList(), |
101 | 109 | "tools/call" => mcpHandler.ToolsCall(paramsElement ?? new JsonElement()), |
102 | | - "notifications/initialized" => new { }, // Handle initialization notification |
103 | | - "prompts/list" => new { prompts = new object[] { } }, // Empty prompts list |
| 110 | + "notifications/initialized" => null, // no response for notifications |
| 111 | + "notifications/cancelled" => null, |
| 112 | + "prompts/list" => new { prompts = new object[] { } }, |
| 113 | + "resources/list" => new { resources = new object[] { } }, |
| 114 | + _ when isNotification => null, // any other notification: no-op |
104 | 115 | _ => throw new Exception($"Unknown method: {method}") |
105 | 116 | }; |
106 | | - |
| 117 | + |
107 | 118 | await Console.Error.WriteLineAsync($"[DEBUG] Method executed successfully"); |
108 | 119 | } |
109 | 120 | catch (Exception ex) |
110 | 121 | { |
111 | 122 | error = new { code = -32000, message = ex.Message }; |
112 | 123 | await Console.Error.WriteLineAsync($"[DEBUG] Method execution failed: {ex.Message}"); |
113 | 124 | } |
114 | | - |
115 | | - // Send response |
116 | | - var response = new |
| 125 | + |
| 126 | + // Notifications never get a response (spec requirement). |
| 127 | + if (isNotification) |
117 | 128 | { |
118 | | - jsonrpc = "2.0", |
119 | | - id = id?.Deserialize<object>(), |
120 | | - result = error == null ? result : null, |
121 | | - error = error |
122 | | - }; |
123 | | - |
124 | | - var responseJson = JsonSerializer.Serialize(response); |
| 129 | + await Console.Error.WriteLineAsync("[DEBUG] Notification — not sending a response"); |
| 130 | + continue; |
| 131 | + } |
| 132 | + |
| 133 | + // Build a spec-compliant JSON-RPC 2.0 response: MUST contain |
| 134 | + // either "result" OR "error", never both. |
| 135 | + object response = error == null |
| 136 | + ? new |
| 137 | + { |
| 138 | + jsonrpc = "2.0", |
| 139 | + id = id?.Deserialize<object>(), |
| 140 | + result = result |
| 141 | + } |
| 142 | + : new |
| 143 | + { |
| 144 | + jsonrpc = "2.0", |
| 145 | + id = id?.Deserialize<object>(), |
| 146 | + error = error |
| 147 | + }; |
| 148 | + |
| 149 | + var responseJson = JsonSerializer.Serialize(response, new JsonSerializerOptions |
| 150 | + { |
| 151 | + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull |
| 152 | + }); |
125 | 153 | await Console.Error.WriteLineAsync($"[DEBUG] Sending response: {responseJson}"); |
126 | 154 | await writer.WriteLineAsync(responseJson); |
127 | 155 | } |
|
0 commit comments