Skip to content

Conversation

@anushakolan
Copy link
Contributor

Why make this change?

This change is made to cherry pick the mcp-stdio changes to the release 1.7 branch.

What is this change?

This change includes cherry picking #2983 to the release 1.7 branch.

How was this tested?

Integration-like manual testing via MCP clients against Engine-based MCP server.

## Why make this change?

- Add MCP stdio support to Data API Builder and wire it through both the
engine and CLI so DAB can be used as a Model Context Protocol (MCP)
server.
- Ensures MCP sessions can run under a specific DAB authorization role,
making it possible to test and use MCP tools with permissions from
`dab-config.json`.

## What is this change?

Service entrypoint
- Detects `--mcp-stdio` early, configures stdin/stdout encodings, and
redirects all non‑MCP output to STDERR to keep STDOUT clean for MCP
JSON.
- Parses an optional `role:<name>` argument (e.g. role:anonymous,
role:authenticated) and injects it into configuration as `MCP:Role`,
defaulting to `anonymous` when omitted.
- In MCP stdio mode, forces `Runtime:Host:Authentication:Provider =
"Simulator"` via in‑memory configuration so the requested role is always
available during MCP sessions.
- Starts the full ASP.NET Core host, registers all MCP tools from DI,
and runs the MCP stdio loop instead of the normal HTTP `host.Run(`).

CLI Integration
- Adds `--mcp-stdio` to `dab start` to launch the engine in MCP stdio
mode.
- Adds an optional positional `role` argument (e.g. `role:anonymous`)
captured as `StartOptions.McpRole`.
 - Keeps existing behavior for non‑MCP `dab start` unchanged.

Note
- `ExecuteEntityTool` now looks for MCP tool inputs under arguments (the
standard MCP field) and falls back to the legacy parameters property
only if arguments is missing. This aligns our server with how current
MCP clients (like VS Code) actually send tool arguments, and preserves
backward compatibility for any older clients that still use parameters.

## How was this tested?

Integration-like manual testing via MCP clients against:
- Engine-based MCP server: `dotnet Azure.DataApiBuilder.Service.dll
--mcp-stdio role:authenticated`.
 - CLI-based MCP server: `dab start --mcp-stdio role:authenticated`.

Manual verification of all MCP tools:
- `describe_entities` shows correct entities and effective permissions
for the active role.
- `read_records`, `create_record`, `update_record`, `delete_record`,
`execute_entity` succeed when the role has the appropriate permissions.

## Sample Request(s)

1. MCP server via CLI (dab)

`
{
  "mcpServers": {
    "dab-with-exe": {
"command":
"C:\\DAB\\data-api-builder\\out\\publish\\Debug\\net8.0\\win-x64\\dab\\Microsoft.DataApiBuilder.exe",
      "args": ["start", "--mcp-stdio", "role:authenticated", "--config",
				"C:\\DAB\\data-api-builder\\dab-config.json"],
      "env": {
        "DAB_ENVIRONMENT": "Development"
      }
  }
}
`
2. MCP server via engine DLL

`
{
  "mcpServers": {
    "dab": {
			"command": "dotnet",
			"args": [

"C:\\DAB\\data-api-builder\\out\\publish\\Debug\\net8.0\\win-x64\\dab\\Azure.DataApiBuilder.Service.dll",
				"--mcp-stdio",
				"role:authenticated",
				"--config",
				"C:\\DAB\\data-api-builder\\dab-config.json"
			],
			"type": "stdio"
		}
  }
}
`
@anushakolan
Copy link
Contributor Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 6 pipeline(s).

@anushakolan anushakolan changed the title [MCP] Added support for --mcp-stdio flag to dab start (#2983) Cherry pick [MCP] Added support for --mcp-stdio flag to dab start (#2983) to release 1.7 Dec 23, 2025
@anushakolan anushakolan changed the title Cherry pick [MCP] Added support for --mcp-stdio flag to dab start (#2983) to release 1.7 Cherry pick [MCP] Added support for --mcp-stdio flag to dab start (#2983) to release 1.7 Dec 23, 2025
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR cherry-picks MCP (Model Context Protocol) stdio support from PR #2983 to the release 1.7 branch. It adds the --mcp-stdio flag to the dab start command, enabling Data API Builder to run as an MCP server that communicates via standard input/output using JSON-RPC protocol.

Key changes:

  • Adds MCP stdio server implementation that handles JSON-RPC requests (initialize, listTools, callTool) via stdin/stdout
  • Introduces --mcp-stdio flag and optional role parameter for the CLI start command
  • Configures authentication to use Simulator mode when running in MCP stdio mode

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
src/Service/Utilities/McpStdioHelper.cs New helper class for MCP stdio mode detection, configuration, and host execution
src/Azure.DataApiBuilder.Mcp/IMcpStdioServer.cs New interface defining the MCP stdio server contract
src/Azure.DataApiBuilder.Mcp/Core/McpStdioServer.cs New implementation of MCP stdio server handling JSON-RPC protocol
src/Azure.DataApiBuilder.Mcp/Core/McpProtocolDefaults.cs New class defining default protocol version and configuration keys
src/Service/Program.cs Updated entry point to detect MCP stdio mode, configure UTF-8 encoding, and route to appropriate host
src/Service/Startup.cs Modified service configuration to force Simulator authentication in MCP stdio mode
src/Cli/Commands/StartOptions.cs Added McpStdio flag and McpRole parameter to start command options
src/Cli/ConfigGenerator.cs Updated to pass MCP stdio arguments to the engine when starting
src/Cli/Exporter.cs Updated StartOptions instantiation to include new MCP parameters

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1 to +7
namespace Azure.DataApiBuilder.Mcp.Core
{
public interface IMcpStdioServer
{
Task RunAsync(CancellationToken cancellationToken);
}
}
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The interface is missing necessary using directives. The Task type is used in the method signature but the System.Threading.Tasks namespace is not imported. Additionally, CancellationToken requires System.Threading namespace.

Copilot uses AI. Check for mistakes.
[Option("mcp-stdio", Required = false, HelpText = "Run Data API Builder in MCP stdio mode while starting the engine.")]
public bool McpStdio { get; }

[Value(0, MetaName = "role", Required = false, HelpText = "Optional MCP permissions role, e.g. role:anonymous. If omitted, defaults to anonymous.")]
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Value attribute with index 0 makes "role" a positional argument, but the help text suggests it should be prefixed with "role:" (e.g., "role:anonymous"). This conflicts with how positional arguments work in command-line parsing. Either change this to an Option attribute if it should be "--role" or "role:value" format, or update the help text to clarify the expected format for positional arguments.

Suggested change
[Value(0, MetaName = "role", Required = false, HelpText = "Optional MCP permissions role, e.g. role:anonymous. If omitted, defaults to anonymous.")]
[Value(0, MetaName = "role", Required = false, HelpText = "Optional MCP permissions role name, e.g. anonymous. If omitted, defaults to anonymous.")]

Copilot uses AI. Check for mistakes.
serverInfo = new
{
name = "Data API Builder",
version = "1.0.0"
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hard-coded server version "1.0.0" should be replaced with the actual product version. Consider using ProductInfo.GetProductVersion() or a similar mechanism to ensure the version number reflects the actual DAB version being run.

Copilot uses AI. Check for mistakes.
Comment on lines +267 to +271
builder.ClearProviders();
builder.AddConsole(options =>
{
options.LogToStandardErrorThreshold = LogLevel.Trace;
});
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling ClearProviders() on line 267 will remove all logging providers including any that were configured earlier in the method (such as Application Insights, Serilog, etc.). Then immediately adding only the Console provider means that in stdio mode, all other logging configuration will be lost. Consider preserving other providers or selectively removing only the console provider and re-adding it with stderr configuration, rather than clearing all providers.

Copilot uses AI. Check for mistakes.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The copyright header comment was removed from this file. All source files in the repository should maintain consistent copyright headers for legal and attribution purposes.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree on this

host.Services.GetServices<Mcp.Model.IMcpTool>();

foreach (Mcp.Model.IMcpTool tool in tools)
{
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method call to tool.GetToolMetadata() is made but its return value is discarded using the discard operator. If this is intentionally calling the method for side effects only (such as ensuring lazy initialization), this should be documented with a comment explaining why. Otherwise, if the metadata is not needed, consider whether this call is necessary.

Suggested change
{
{
// Ensure the tool's metadata is initialized/validated before registration.
// The return value is intentionally discarded; only side effects are required here.

Copilot uses AI. Check for mistakes.
: options.McpRole;

args.Add("--mcp-stdio");
args.Add(effectiveRole);
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The argument being added should be prefixed with "role:" to match the parsing logic in McpStdioHelper.ShouldRunMcpStdio which expects the role argument to start with "role:". Currently, only the effectiveRole value is being added, but the parsing code on line 35 of McpStdioHelper.cs looks for arguments starting with "role:".

Suggested change
args.Add(effectiveRole);
args.Add($"role:{effectiveRole}");

Copilot uses AI. Check for mistakes.
Comment on lines +117 to +119
config: options.Config!,
mcpStdio: false,
mcpRole: null);
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The StartOptions constructor parameter ordering is inconsistent with how the parameters are being passed. The constructor signature expects parameters in the order (verbose, logLevel, isHttpsRedirectionDisabled, mcpStdio, mcpRole, config), but the call is using named parameters which makes the positional parameter 'config' at the end confusing. Consider making all parameters named for clarity, or reorder the constructor parameters to have 'config' before the new mcpStdio/mcpRole parameters to maintain backward compatibility patterns.

Suggested change
config: options.Config!,
mcpStdio: false,
mcpRole: null);
mcpStdio: false,
mcpRole: null,
config: options.Config!);

Copilot uses AI. Check for mistakes.
Comment on lines +140 to +141
catch (Exception)
{
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exception details are being swallowed in the catch block. While the error is logged to STDERR and a generic error is returned to the client, the exception message is not included. Consider logging the exception details for debugging purposes, such as: Console.Error.WriteLine($"[MCP DEBUG] Unexpected error: {ex.Message}");

Suggested change
catch (Exception)
{
catch (Exception ex)
{
Console.Error.WriteLine($"[MCP DEBUG] Unexpected error handling method '{method}': {ex}");

Copilot uses AI. Check for mistakes.
return;
}

JsonDocument? argsDoc = null;
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This variable is manually disposed in a finally block - consider a C# using statement as a preferable resource management technique.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

@RubenCerna2079 RubenCerna2079 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants