-
Notifications
You must be signed in to change notification settings - Fork 297
Cherry pick [MCP] Added support for --mcp-stdio flag to dab start (#2983) to release 1.7
#3034
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: release/1.7
Are you sure you want to change the base?
Conversation
## 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"
}
}
}
`
|
/azp run |
|
Azure Pipelines successfully started running 6 pipeline(s). |
--mcp-stdio flag to dab start (#2983)[MCP] Added support for --mcp-stdio flag to dab start (#2983) to release 1.7
[MCP] Added support for --mcp-stdio flag to dab start (#2983) to release 1.7[MCP] Added support for --mcp-stdio flag to dab start (#2983) to release 1.7
There was a problem hiding this 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-stdioflag 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.
| namespace Azure.DataApiBuilder.Mcp.Core | ||
| { | ||
| public interface IMcpStdioServer | ||
| { | ||
| Task RunAsync(CancellationToken cancellationToken); | ||
| } | ||
| } |
Copilot
AI
Dec 23, 2025
There was a problem hiding this comment.
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.
| [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.")] |
Copilot
AI
Dec 23, 2025
There was a problem hiding this comment.
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.
| [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.")] |
| serverInfo = new | ||
| { | ||
| name = "Data API Builder", | ||
| version = "1.0.0" |
Copilot
AI
Dec 23, 2025
There was a problem hiding this comment.
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.
| builder.ClearProviders(); | ||
| builder.AddConsole(options => | ||
| { | ||
| options.LogToStandardErrorThreshold = LogLevel.Trace; | ||
| }); |
Copilot
AI
Dec 23, 2025
There was a problem hiding this comment.
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.
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using System; |
Copilot
AI
Dec 23, 2025
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) | ||
| { |
Copilot
AI
Dec 23, 2025
There was a problem hiding this comment.
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.
| { | |
| { | |
| // Ensure the tool's metadata is initialized/validated before registration. | |
| // The return value is intentionally discarded; only side effects are required here. |
| : options.McpRole; | ||
|
|
||
| args.Add("--mcp-stdio"); | ||
| args.Add(effectiveRole); |
Copilot
AI
Dec 23, 2025
There was a problem hiding this comment.
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:".
| args.Add(effectiveRole); | |
| args.Add($"role:{effectiveRole}"); |
| config: options.Config!, | ||
| mcpStdio: false, | ||
| mcpRole: null); |
Copilot
AI
Dec 23, 2025
There was a problem hiding this comment.
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.
| config: options.Config!, | |
| mcpStdio: false, | |
| mcpRole: null); | |
| mcpStdio: false, | |
| mcpRole: null, | |
| config: options.Config!); |
| catch (Exception) | ||
| { |
Copilot
AI
Dec 23, 2025
There was a problem hiding this comment.
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}");
| catch (Exception) | |
| { | |
| catch (Exception ex) | |
| { | |
| Console.Error.WriteLine($"[MCP DEBUG] Unexpected error handling method '{method}': {ex}"); |
| return; | ||
| } | ||
|
|
||
| JsonDocument? argsDoc = null; |
Copilot
AI
Dec 23, 2025
There was a problem hiding this comment.
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.
RubenCerna2079
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
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.