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..36f2b72807 100644
--- a/src/Service/Utilities/McpStdioHelper.cs
+++ b/src/Service/Utilities/McpStdioHelper.cs
@@ -1,5 +1,9 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@@ -15,9 +19,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 +47,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 +85,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();