Skip to content

Conversation

@anushakolan
Copy link
Contributor

@anushakolan anushakolan commented Dec 23, 2025

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:

  1. [MCP] Added support for --mcp-stdio flag to dab start. #2983
  2. Changed the default auth provider from SWA to AppService. #2943
  3. Added documentation for DAB MCP and AI Foundry integration setup. #2971
  4. Added architecture diagram to the AI Foundry Integration doc. #3036
  5. Bug fix for pagination nested entities resulting key not found error. #3029
  6. [MCP] Centralize JSON‑RPC error handling, standardize MCP response envelope, and add default anonymous role for stdio mode. #3035

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

@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.

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!

Copy link
Collaborator

@Aniruddh25 Aniruddh25 left a 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.

@anushakolan
Copy link
Contributor Author

/azp run

@anushakolan anushakolan enabled auto-merge (squash) January 16, 2026 03:18
@azure-pipelines
Copy link

Azure Pipelines successfully started running 6 pipeline(s).

@anushakolan anushakolan force-pushed the dev/anushakolan/cherry-pick-stdio-changes branch from a691974 to 123c3e4 Compare January 16, 2026 03:54
@anushakolan
Copy link
Contributor Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 6 pipeline(s).

@anushakolan
Copy link
Contributor Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 6 pipeline(s).

anushakolan and others added 4 commits January 16, 2026 08:53
## 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)
anushakolan and others added 2 commits January 16, 2026 08:53
…#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)
@anushakolan anushakolan force-pushed the dev/anushakolan/cherry-pick-stdio-changes branch from 66b0e32 to beba6df Compare January 16, 2026 16:55
@anushakolan
Copy link
Contributor Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 6 pipeline(s).

@Aniruddh25 Aniruddh25 changed the title Cherry pick [MCP] Added support for --mcp-stdio flag to dab start (#2983) to release 1.7 Cherry pick stdio support, change default auth provider, bug fix for nested entities to release 1.7 Jan 16, 2026
@anushakolan anushakolan merged commit 346db29 into release/1.7 Jan 16, 2026
11 checks passed
@anushakolan anushakolan deleted the dev/anushakolan/cherry-pick-stdio-changes branch January 16, 2026 19:13
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.

4 participants