diff --git a/src/Azure.DataApiBuilder.Mcp/Core/McpProtocolDefaults.cs b/src/Azure.DataApiBuilder.Mcp/Core/McpProtocolDefaults.cs
index 8e307a7c0e..48b235f480 100644
--- a/src/Azure.DataApiBuilder.Mcp/Core/McpProtocolDefaults.cs
+++ b/src/Azure.DataApiBuilder.Mcp/Core/McpProtocolDefaults.cs
@@ -1,3 +1,4 @@
+using Azure.DataApiBuilder.Product;
using Microsoft.Extensions.Configuration;
namespace Azure.DataApiBuilder.Mcp.Core
@@ -7,6 +8,14 @@ namespace Azure.DataApiBuilder.Mcp.Core
///
public static class McpProtocolDefaults
{
+ ///
+ /// Default MCP server name advertised during initialization.
+ ///
+ public const string MCP_SERVER_NAME = "SQL MCP Server";
+ ///
+ /// Default MCP server version advertised during initialization.
+ ///
+ public static readonly string MCP_SERVER_VERSION = ProductInfo.GetProductVersion();
///
/// Default MCP protocol version advertised when no configuration override is provided.
///
diff --git a/src/Azure.DataApiBuilder.Mcp/Core/McpServerConfiguration.cs b/src/Azure.DataApiBuilder.Mcp/Core/McpServerConfiguration.cs
index 86cccd2aaf..d76af816bd 100644
--- a/src/Azure.DataApiBuilder.Mcp/Core/McpServerConfiguration.cs
+++ b/src/Azure.DataApiBuilder.Mcp/Core/McpServerConfiguration.cs
@@ -21,7 +21,7 @@ internal static IServiceCollection ConfigureMcpServer(this IServiceCollection se
{
services.AddMcpServer(options =>
{
- options.ServerInfo = new() { Name = "Data API builder MCP Server", Version = "1.0.0" };
+ options.ServerInfo = new() { Name = McpProtocolDefaults.MCP_SERVER_NAME, Version = McpProtocolDefaults.MCP_SERVER_VERSION };
options.Capabilities = new()
{
Tools = new()
diff --git a/src/Azure.DataApiBuilder.Mcp/Core/McpStdioServer.cs b/src/Azure.DataApiBuilder.Mcp/Core/McpStdioServer.cs
index 79ccf39356..6584e819af 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: McpStdioJsonRpcErrorCodes.INVALID_REQUEST, 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: McpStdioJsonRpcErrorCodes.PARSE_ERROR, 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: McpStdioJsonRpcErrorCodes.INTERNAL_ERROR, 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, McpStdioJsonRpcErrorCodes.INVALID_REQUEST, "Invalid Request");
continue;
}
@@ -133,13 +133,13 @@ public async Task RunAsync(CancellationToken cancellationToken)
return;
default:
- WriteError(id, -32601, $"Method not found: {method}");
+ WriteError(id, McpStdioJsonRpcErrorCodes.METHOD_NOT_FOUND, $"Method not found: {method}");
break;
}
}
catch (Exception)
{
- WriteError(id, -32603, "Internal error");
+ WriteError(id, McpStdioJsonRpcErrorCodes.INTERNAL_ERROR, "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 = McpProtocolDefaults.MCP_SERVER_NAME,
+ version = McpProtocolDefaults.MCP_SERVER_VERSION
}
};
- 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, McpStdioJsonRpcErrorCodes.INVALID_PARAMS, "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, McpStdioJsonRpcErrorCodes.INVALID_PARAMS, "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, McpStdioJsonRpcErrorCodes.INVALID_PARAMS, $"Tool not found: {toolName}");
return;
}
diff --git a/src/Azure.DataApiBuilder.Mcp/Model/McpStdioJsonRpcErrorCodes.cs b/src/Azure.DataApiBuilder.Mcp/Model/McpStdioJsonRpcErrorCodes.cs
new file mode 100644
index 0000000000..3bac194068
--- /dev/null
+++ b/src/Azure.DataApiBuilder.Mcp/Model/McpStdioJsonRpcErrorCodes.cs
@@ -0,0 +1,36 @@
+namespace Azure.DataApiBuilder.Mcp.Model
+{
+ ///
+ /// 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 McpStdioJsonRpcErrorCodes
+ {
+ ///
+ /// Invalid JSON was received by the server.
+ /// An error occurred on the server while parsing the JSON text.
+ ///
+ public const int PARSE_ERROR = -32700;
+
+ ///
+ /// The JSON sent is not a valid Request object.
+ ///
+ public const int INVALID_REQUEST = -32600;
+
+ ///
+ /// The method does not exist / is not available.
+ ///
+ public const int METHOD_NOT_FOUND = -32601;
+
+ ///
+ /// Invalid method parameter(s).
+ ///
+ public const int INVALID_PARAMS = -32602;
+
+ ///
+ /// Internal JSON-RPC error.
+ ///
+ public const int INTERNAL_ERROR = -32603;
+ }
+}
diff --git a/src/Service.Tests/SqlTests/SqlTestBase.cs b/src/Service.Tests/SqlTests/SqlTestBase.cs
index 5e90e77b85..16e804f117 100644
--- a/src/Service.Tests/SqlTests/SqlTestBase.cs
+++ b/src/Service.Tests/SqlTests/SqlTestBase.cs
@@ -514,7 +514,7 @@ protected static async Task SetupAndRunRestApiTest(
request.Headers.Add(AuthorizationResolver.CLIENT_ROLE_HEADER, clientRoleHeader);
// Detect runtime auth provider once per call
- var configProvider = _application.Services.GetRequiredService();
+ RuntimeConfigProvider configProvider = _application.Services.GetRequiredService();
string provider = configProvider.GetConfig().Runtime.Host.Authentication.Provider;
if (string.Equals(provider, nameof(EasyAuthType.AppService), StringComparison.OrdinalIgnoreCase))
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..043e9dd85d 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 defaults to anonymous.
+ /// 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();