From b783c73094b8e05573b5e5cf001495c366909866 Mon Sep 17 00:00:00 2001 From: Anusha Kolan Date: Mon, 29 Dec 2025 17:20:04 -0800 Subject: [PATCH 1/2] Addressed comments --- .../Core/JsonRpcErrorCodes.cs | 36 +++++++++++++ .../Core/McpStdioServer.cs | 50 ++++++++----------- src/Service/Program.cs | 3 ++ src/Service/Utilities/McpStdioHelper.cs | 20 ++++---- 4 files changed, 70 insertions(+), 39 deletions(-) create mode 100644 src/Azure.DataApiBuilder.Mcp/Core/JsonRpcErrorCodes.cs diff --git a/src/Azure.DataApiBuilder.Mcp/Core/JsonRpcErrorCodes.cs b/src/Azure.DataApiBuilder.Mcp/Core/JsonRpcErrorCodes.cs new file mode 100644 index 0000000000..ce68ab6dcd --- /dev/null +++ b/src/Azure.DataApiBuilder.Mcp/Core/JsonRpcErrorCodes.cs @@ -0,0 +1,36 @@ +namespace Azure.DataApiBuilder.Mcp.Core +{ + /// + /// JSON-RPC 2.0 standard error codes used by the MCP stdio server. + /// These values come from the JSON-RPC 2.0 specification and are shared + /// so they are not hard-coded throughout the codebase. + /// + internal static class JsonRpcErrorCodes + { + /// + /// Invalid JSON was received by the server. + /// An error occurred on the server while parsing the JSON text. + /// + public const int PARSEERROR = -32700; + + /// + /// The JSON sent is not a valid Request object. + /// + public const int INVALIDREQUEST = -32600; + + /// + /// The method does not exist / is not available. + /// + public const int METHODNOTFOUND = -32601; + + /// + /// Invalid method parameter(s). + /// + public const int INVALIDPARAMS = -32602; + + /// + /// Internal JSON-RPC error. + /// + public const int INTERNALERROR = -32603; + } +} diff --git a/src/Azure.DataApiBuilder.Mcp/Core/McpStdioServer.cs b/src/Azure.DataApiBuilder.Mcp/Core/McpStdioServer.cs index 79ccf39356..46550d8ac5 100644 --- a/src/Azure.DataApiBuilder.Mcp/Core/McpStdioServer.cs +++ b/src/Azure.DataApiBuilder.Mcp/Core/McpStdioServer.cs @@ -65,7 +65,7 @@ public async Task RunAsync(CancellationToken cancellationToken) if (line.Length > MAX_LINE_LENGTH) { - WriteError(id: null, code: -32600, message: "Request too large"); + WriteError(id: null, code: JsonRpcErrorCodes.INVALIDREQUEST, message: "Request too large"); continue; } @@ -77,13 +77,13 @@ public async Task RunAsync(CancellationToken cancellationToken) catch (JsonException jsonEx) { Console.Error.WriteLine($"[MCP DEBUG] JSON parse error: {jsonEx.Message}"); - WriteError(id: null, code: -32700, message: "Parse error"); + WriteError(id: null, code: JsonRpcErrorCodes.PARSEERROR, message: "Parse error"); continue; } catch (Exception ex) { Console.Error.WriteLine($"[MCP DEBUG] Unexpected error parsing request: {ex.Message}"); - WriteError(id: null, code: -32603, message: "Internal error"); + WriteError(id: null, code: JsonRpcErrorCodes.INTERNALERROR, message: "Internal error"); continue; } @@ -99,7 +99,7 @@ public async Task RunAsync(CancellationToken cancellationToken) if (!root.TryGetProperty("method", out JsonElement methodEl)) { - WriteError(id, -32600, "Invalid Request"); + WriteError(id, JsonRpcErrorCodes.INVALIDREQUEST, "Invalid Request"); continue; } @@ -133,13 +133,13 @@ public async Task RunAsync(CancellationToken cancellationToken) return; default: - WriteError(id, -32601, $"Method not found: {method}"); + WriteError(id, JsonRpcErrorCodes.METHODNOTFOUND, $"Method not found: {method}"); break; } } catch (Exception) { - WriteError(id, -32603, "Internal error"); + WriteError(id, JsonRpcErrorCodes.INTERNALERROR, "Internal error"); } } } @@ -158,32 +158,22 @@ public async Task RunAsync(CancellationToken cancellationToken) /// private void HandleInitialize(JsonElement? id) { - // Extract the actual id value from the request - object? requestId = id.HasValue ? GetIdValue(id.Value) : null; - - // Create the initialize response - var response = new + var result = new { - jsonrpc = "2.0", - id = requestId, - result = new + protocolVersion = _protocolVersion, + capabilities = new { - protocolVersion = _protocolVersion, - capabilities = new - { - tools = new { listChanged = true }, - logging = new { } - }, - serverInfo = new - { - name = "Data API Builder", - version = "1.0.0" - } + tools = new { listChanged = true }, + logging = new { } + }, + serverInfo = new + { + name = "SQL MCP Server", + version = "1.0.0" } }; - string json = JsonSerializer.Serialize(response); - Console.Out.WriteLine(json); + WriteResult(id, result); } /// @@ -225,7 +215,7 @@ private async Task HandleCallToolAsync(JsonElement? id, JsonElement root, Cancel { if (!root.TryGetProperty("params", out JsonElement @params) || @params.ValueKind != JsonValueKind.Object) { - WriteError(id, -32602, "Missing params"); + WriteError(id, JsonRpcErrorCodes.INVALIDPARAMS, "Missing params"); return; } @@ -247,14 +237,14 @@ private async Task HandleCallToolAsync(JsonElement? id, JsonElement root, Cancel if (string.IsNullOrWhiteSpace(toolName)) { Console.Error.WriteLine("[MCP DEBUG] callTool → missing tool name."); - WriteError(id, -32602, "Missing tool name"); + WriteError(id, JsonRpcErrorCodes.INVALIDPARAMS, "Missing tool name"); return; } if (!_toolRegistry.TryGetTool(toolName!, out IMcpTool? tool) || tool is null) { Console.Error.WriteLine($"[MCP DEBUG] callTool → tool not found: {toolName}"); - WriteError(id, -32602, $"Tool not found: {toolName}"); + WriteError(id, JsonRpcErrorCodes.INVALIDPARAMS, $"Tool not found: {toolName}"); return; } diff --git a/src/Service/Program.cs b/src/Service/Program.cs index e0a74bd9d1..e23fb98cd9 100644 --- a/src/Service/Program.cs +++ b/src/Service/Program.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + using System; using System.CommandLine; using System.CommandLine.Parsing; diff --git a/src/Service/Utilities/McpStdioHelper.cs b/src/Service/Utilities/McpStdioHelper.cs index 9e337d0809..b8a9c04e98 100644 --- a/src/Service/Utilities/McpStdioHelper.cs +++ b/src/Service/Utilities/McpStdioHelper.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -15,9 +16,9 @@ internal static class McpStdioHelper /// Determines if MCP stdio mode should be run based on command line arguments. /// /// The command line arguments. - /// The role for MCP stdio mode, if specified. - /// - public static bool ShouldRunMcpStdio(string[] args, out string? mcpRole) + /// The role for MCP stdio mode. When this method returns true, the role is guaranteed to be non-null. + /// True when MCP stdio mode should be enabled; otherwise false. + public static bool ShouldRunMcpStdio(string[] args, [NotNullWhen(true)] out string? mcpRole) { mcpRole = null; @@ -43,6 +44,11 @@ public static bool ShouldRunMcpStdio(string[] args, out string? mcpRole) } } + // Ensure that when MCP stdio is enabled, mcpRole is always non-null. + // This matches the NotNullWhen(true) contract and avoids nullable warnings + // for callers while still allowing an implicit default when no role is provided. + mcpRole ??= "anonymous"; + return true; } @@ -76,17 +82,13 @@ public static bool RunMcpStdioHost(IHost host) foreach (Mcp.Model.IMcpTool tool in tools) { - _ = tool.GetToolMetadata(); registry.RegisterTool(tool); } - IServiceScopeFactory scopeFactory = - host.Services.GetRequiredService(); - using IServiceScope scope = scopeFactory.CreateScope(); IHostApplicationLifetime lifetime = - scope.ServiceProvider.GetRequiredService(); + host.Services.GetRequiredService(); Mcp.Core.IMcpStdioServer stdio = - scope.ServiceProvider.GetRequiredService(); + host.Services.GetRequiredService(); stdio.RunAsync(lifetime.ApplicationStopping).GetAwaiter().GetResult(); host.StopAsync().GetAwaiter().GetResult(); From 6979a8dbd4e4fb8e16a94abdef6ada23f0d54bca Mon Sep 17 00:00:00 2001 From: Anusha Kolan Date: Tue, 30 Dec 2025 14:14:03 -0800 Subject: [PATCH 2/2] Added copyright header for McpStdioHelper --- src/Service/Utilities/McpStdioHelper.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Service/Utilities/McpStdioHelper.cs b/src/Service/Utilities/McpStdioHelper.cs index b8a9c04e98..36f2b72807 100644 --- a/src/Service/Utilities/McpStdioHelper.cs +++ b/src/Service/Utilities/McpStdioHelper.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis;