-
Notifications
You must be signed in to change notification settings - Fork 301
Cherry pick stdio support, change default auth provider, bug fix for nested entities 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
Cherry pick stdio support, change default auth provider, bug fix for nested entities to release 1.7 #3034
Conversation
|
/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.
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!
Aniruddh25
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.
Commits should be verified. You need to sign them before pushing using GPG signatures.
|
/azp run |
|
Azure Pipelines successfully started running 6 pipeline(s). |
a691974 to
123c3e4
Compare
|
/azp run |
|
Azure Pipelines successfully started running 6 pipeline(s). |
|
/azp run |
|
Azure Pipelines successfully started running 6 pipeline(s). |
## 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"
}
}
}
`
- Closes [#2943](#2644) Change default auth provider to AppService from StaticWebApps. Azure Static Web Apps EasyAuth is being deprecated, so DAB should no longer default to [StaticWebApps](vscode-file://vscode-app/c:/Program%20Files/Microsoft%20VS%20Code/resources/app/out/vs/code/electron-browser/workbench/workbench.html) as its authentication provider. - Moving the default to `AppService` aligns DAB with the long‑term supported `EasyAuth` path while keeping behavior equivalent for existing workloads. `StaticWebApps` remains supported when explicitly configured, but new configurations and `dab init` flows should guide users toward `AppService` instead of a deprecated option. -Config and runtime behavior - Changed the default authentication provider from `Static Web Apps` to `App Service` in the core configuration model and JSON schema. - Added validation that logs a warning when Static Web Apps is explicitly selected (since it’s deprecated as a default). -CLI and `dab init` - Updated `dab init` so that, when no auth provider is specified, it now generates configs using App Service as the provider instead of Static Web Apps. - Adjusted CLI configuration generation and option handling so any “default provider” usage now points to App Service. - Updated end-to-end CLI tests and initialization tests so their expected configurations and arguments reference App Service as the default. -Schema, samples, and built‑in configs - Updated the JSON schema to set the default of the `authentication.provider` property to `AppService`. - Updated sample configuration snippets in the main documentation to show App Service as the provider. - Updated the built‑in `dab-config` JSON files (for all supported databases and multi‑DAB scenarios) so their runtime host sections use App Service. -Engine tests and helpers - Updated test helpers to generate EasyAuth principals appropriate for the configured provider, and to treat App Service as the default in REST and GraphQL integration tests. - Adjusted configuration and health‑endpoint tests to no longer assume Static Web Apps as the implicit provider and to accept App Service as the default. -Snapshots and expected outputs - updated a large set of snapshot files (CLI snapshots, configuration snapshots, entity update/add snapshots) so that anywhere the authentication section previously showed Static Web Apps as the provider, it now shows App Service. -Note We updated `AddEnvDetectedEasyAuth` so that it always registers both the `App Service` and `Static Web Apps` `EasyAuth` schemes in development mode, instead of only adding App Service when certain environment variables are present. This aligns with the new default of using App Service as the primary `EasyAuth` provider and makes dev/test/CI behavior deterministic, while still letting configuration (runtime.host.authentication.provider) choose which scheme is actually used. - [x] Integration Tests - [x] Unit Tests `dab init --database-type mssql --connection-string "<conn-string>"` Generates, `"runtime": { "host": { "authentication": { "provider": "AppService" } } }` Users who still want Static Web Apps can override: `dab init --database-type mssql --connection-string "<conn-string>" --auth.provider StaticWebApps` --------- Co-authored-by: Aniruddh Munde <anmunde@microsoft.com> Co-authored-by: Souvik Ghosh <souvikofficial04@gmail.com> Co-authored-by: aaronburtle <93220300+aaronburtle@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Anusha Kolan <anushakolan@microsoft.com> (cherry picked from commit 0e1b3c2)
) ## Why make this change? This change adds documented instructions on integrating the DAB MCP with AI Foundry using an Azure Container Instance. <img width="2398" height="2178" alt="image" src="https://github.com/user-attachments/assets/a7c1ae33-28ea-4c48-b474-12abfc3f263b" /> --------- Co-authored-by: Aniruddh Munde <anmunde@microsoft.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> (cherry picked from commit 725bf8c)
## Why make this change? This is a minor PR to add architecture diagram to the AI Foundry Integration documentation. --------- Co-authored-by: Anusha Kolan <anushakolan@microsoft.com> (cherry picked from commit 5b43e2b)
…#3029) ## Why make this change? We are addressing 2 related issues in this PR: Issue #2374 – Nested sibling relationships under books (websiteplacement, reviews, authors) **Problem**: A nested query on books where a parent has multiple sibling relationships (for example, websiteplacement, reviews, and authors) could throw a `KeyNotFoundException` when RBAC or shape changes were involved. Pagination metadata was stored using only the root and the depth in the path, so different sibling relationships at the same depth could overwrite each other or look up the wrong entry. **Solution**: We now key pagination metadata by both depth and the full relationship path (for example, “books → items → reviews” vs “books → items → authors”), so each sibling branch gets its own unique entry. Reads use the same full-path key, and if metadata for a branch is missing, we return an “empty” `PaginationMetadata` instead of throwing. This prevents collisions between sibling relationships and avoids runtime errors when a particular branch has no metadata. Issue #3026 – Person's graph (AddressType / PhoneNumberType) **Problem**: In the persons graph, a query selecting persons → addresses.items.AddressType and persons → phoneNumbers.items.PhoneNumberType could also throw a `KeyNotFoundException`. In some cases (for example, when RBAC removes a relationship or when that relationship is not paginated at all), there is legitimately no pagination metadata for that nested field, but the code assumed it always existed and indexed into the dictionary directly. **Solution**: Metadata handling is now defensive in two places: In the GraphQL execution helper, metadata lookups for object and list fields use safe TryGet-style access; if an entry isn’t present, we fall back to an empty PaginationMetadata instead of failing. In the SQL query engine’s object resolver, we first check whether there is a subquery metadata entry for the field. If there isn’t, we treat the field as non‑paginated and return the JSON as-is rather than throwing. Together, these changes fix both issues by (a) using full path-based keys, so sibling branches don’t conflict, (b) treating missing metadata as “no pagination here” rather than as a fatal error. ## What is this change? 1. In `SqlQueryEngine.ResolveObject`, instead of always doing `parentMetadata.Subqueries[fieldName]` (which crashed when RBAC caused that entry to be missing), it now uses `TryGetValue` and: - If metadata exists and `IsPaginated` is true -> wrap the JSON as a pagination connection. - If metadata is missing -> just return the JSON as-is (no exception). 2. Introduced `GetRelationshipPathSuffix(HotChocolate.Path path)` to build a relationship path suffix like: - `rel1` for `/entity/items[0]/rel1` - `rel1::nested` for `/entity/items[0]/rel1/nested` 3. `SetNewMetadataChildren`, now stores child metadata under keys of the form - `root_PURE_RESOLVER_CTX::depth::relationshipPath`, ensuring siblings at the same depth get distinct entries. 5. `GetMetadata` (used for list items fields): - For `Selection.ResponseName == "items"` and non-root paths, now looks up: a. `GetMetadataKey(context.Path) + "::" + context.Path.Parent.Depth()` plus the relationship suffix from `GetRelationshipPathSuffix(context.Path.Parent)`. b. Uses `ContextData.TryGetValue(...)` and falls back to `PaginationMetadata.MakeEmptyPaginationMetadata()` when metadata is missing (e.g. Cosmos, pruned relationships). 6. `GetMetadataObjectField` (used for object fields like addresses, AddressType, PhoneNumberType): Updated all branches (indexer, nested non-root, root) to: - Append the relationship suffix to the base key (so keys align with `SetNewMetadataChildren`). - Use `ContextData.TryGetValue(...)` instead of direct indexing, return `PaginationMetadata.MakeEmptyPaginationMetadata()` when no metadata exists, instead of throwing. 7. Added a new test case in `MsSqlGraphQLQueryTests`, an integration test which queries books with multiple sibling nested relationships (websiteplacement, reviews, authors) under the authenticated role to: - Assert no KeyNotFoundException, - Verify all nested branches return data. ## How was this tested? Tested both manually and added an integration test (NestedReviewsConnection_WithSiblings_PaginatesMoreThanHundredItems). Manually if we run this query without the bug fix: `query { persons { items { PersonID FirstName LastName addresses { items { AddressID City AddressType { AddressTypeID TypeName } } } phoneNumbers { items { PhoneNumberID PhoneNumber PhoneNumberType { PhoneNumberTypeID TypeName } } } } } }` We get the following response: `{ "errors": [ { "message": "The given key 'AddressType' was not present in the dictionary.", "locations": [ { "line": 11, "column": 11 } ], "path": [ "persons", "items", 0, "addresses", "items", 1, "AddressType" ] }, { "message": "The given key 'AddressType' was not present in the dictionary.", "locations": [ { "line": 11, "column": 11 } ], "path": [ "persons", "items", 0, "addresses", "items", 0, "AddressType" ] }, { "message": "The given key 'AddressType' was not present in the dictionary.", "locations": [ { "line": 11, "column": 11 } ], "path": [ "persons", "items", 1, "addresses", "items", 0, "AddressType" ] } ], "data": { "persons": { "items": [ { "PersonID": 1, "FirstName": "John", "LastName": "Doe", "addresses": { "items": [ { "AddressID": 1, "City": "New York", "AddressType": null }, { "AddressID": 2, "City": "New York", "AddressType": null } ] }, "phoneNumbers": { "items": [ { "PhoneNumberID": 1, "PhoneNumber": "123-456-7890", "PhoneNumberType": { "PhoneNumberTypeID": 1, "TypeName": "Mobile" } }, { "PhoneNumberID": 2, "PhoneNumber": "111-222-3333", "PhoneNumberType": { "PhoneNumberTypeID": 3, "TypeName": "Work" } } ] } }, { "PersonID": 2, "FirstName": "Jane", "LastName": "Smith", "addresses": { "items": [ { "AddressID": 3, "City": "Los Angeles", "AddressType": null } ] }, "phoneNumbers": { "items": [ { "PhoneNumberID": 3, "PhoneNumber": "987-654-3210", "PhoneNumberType": { "PhoneNumberTypeID": 2, "TypeName": "Home" } } ] } } ] } } }` After the bug fix, we get, `{ "data": { "persons": { "items": [ { "PersonID": 1, "FirstName": "John", "LastName": "Doe", "addresses": { "items": [ { "AddressID": 1, "City": "New York", "AddressType": { "AddressTypeID": 1, "TypeName": "Home" } }, { "AddressID": 2, "City": "New York", "AddressType": { "AddressTypeID": 2, "TypeName": "Work" } } ] }, "phoneNumbers": { "items": [ { "PhoneNumberID": 1, "PhoneNumber": "123-456-7890", "PhoneNumberType": { "PhoneNumberTypeID": 1, "TypeName": "Mobile" } }, { "PhoneNumberID": 2, "PhoneNumber": "111-222-3333", "PhoneNumberType": { "PhoneNumberTypeID": 3, "TypeName": "Work" } } ] } }, { "PersonID": 2, "FirstName": "Jane", "LastName": "Smith", "addresses": { "items": [ { "AddressID": 3, "City": "Los Angeles", "AddressType": { "AddressTypeID": 1, "TypeName": "Home" } } ] }, "phoneNumbers": { "items": [ { "PhoneNumberID": 3, "PhoneNumber": "987-654-3210", "PhoneNumberType": { "PhoneNumberTypeID": 2, "TypeName": "Home" } } ] } } ] } } }` ## Sample Request(s) - Example REST and/or GraphQL request to demonstrate modifications - Example of CLI usage to demonstrate modifications --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: RubenCerna2079 <32799214+RubenCerna2079@users.noreply.github.com> (cherry picked from commit ee2ce9e)
…velope, and add default anonymous role for stdio mode. (#3035) ## Why make this change? This is a follow up PR for, #2983 to address some of the newer comments. ## What is this change? 1. Centralized JSON‑RPC error handling: all standard JSON‑RPC error codes are now defined in one place and used consistently throughout the MCP server (for things like parse errors, invalid requests, unknown methods, invalid parameters, and internal errors). 2. Standardized how responses are written: both successful results and errors now go through shared helpers, so every MCP response has a consistent JSON‑RPC envelope and is easy to change or audit. 3. Added an implicit default role to be `anonymous` when no role is provided in `mcp-stdio` mode. ## How was this tested? Tested manually by calling all the CRUD tools and execute tool. Added an MCP server in VS Code in `C:\DAB\data-api-builder\.vscode\mcp.json` `"dab-with-exe": { "command": "C:\\DAB\\data-api-builder\\out\\publish\\Debug\\net8.0\\win-x64\\dab\\Microsoft.DataApiBuilder.exe", "args": ["start", "--mcp-stdio", "--config", "C:\\DAB\\data-api-builder\\dab-config.json"], "env": { "DAB_ENVIRONMENT": "Development" } }` 1. When calling tools with `Authenticated` all tools pass. 2. When calling tools with `Anonymous`, all tools except `describe-entities` fail with permission denied error. 5. When calling tools without any role parameter, all tools except `describe-entities` fail with permission denied error. (cherry picked from commit de8f074)
66b0e32 to
beba6df
Compare
|
/azp run |
|
Azure Pipelines successfully started running 6 pipeline(s). |
[MCP] Added support for --mcp-stdio flag to dab start (#2983) to release 1.7
Why make this change?
This change cherry-picks the commits that add support for running DAB as an MCP stdio server via the --mcp-stdio flag, align our default authentication behavior with App Service instead of SWA, and improve and expand the documentation for DAB MCP and AI Foundry integration (including an architecture diagram). It also pulls in a targeted bug fix for nested-entity pagination that previously resulted in key-not-found errors. It also adds changes to centralize MCP JSON-RPC error handling, standardize the MCP response envelope, and add a default anonymous role for stdio mode
What is this change?
Cherry picked PRs:
How was this tested?
The PRs in question were tested against the regular test suite and had tests added to cover new code changes, as well as being manually tested.
Sample Request(s)
N/A