Describe the bug
When running Data API Builder in MCP stdio mode, the process still attempts to bind an HTTP port via Kestrel. If the port is unavailable, the process exits with code -1 before the MCP handshake can complete.
This is unexpected because --mcp-stdio communicates over stdin/stdout and should not depend on an HTTP port.
Two scenarios trigger the crash:
ASPNETCORE_URLS is set to an occupied port.
ASPNETCORE_URLS is not set — in my local test, DAB appeared to bind to http://127.0.0.1:5000 and crashed when port 5000 was occupied.
Environment
- OS: Windows
- Data API Builder version: Microsoft.DataApiBuilder 2.0.8
- Database: SQL Server (via
@env() connection string)
- MCP client: Codex (stdio transport)
- Command:
dab start --mcp-stdio --config dab-config.json
Expected behavior
dab start --mcp-stdio should communicate with the MCP client over stdin/stdout and should not require an HTTP port. It should either:
- Skip Kestrel port binding in stdio mode, or
- Ignore
ASPNETCORE_URLS in stdio mode, or
- Produce a clear error when the port is occupied instead of exiting silently.
Actual behavior
If the target port is occupied (either explicitly via ASPNETCORE_URLS or implicitly via the default port 5000), the DAB process exits with code -1 and produces an AddressInUseException in stderr.
The MCP client only sees a generic handshake failure: "connection closed: initialize response".
Reproduction steps
-
Occupy a local port (e.g. start a DAB REST instance on port 5155, or have another process on port 5000).
-
Configure DAB MCP stdio:
[mcp_servers.dab_sql]
command = "dab"
args = ["start", "--mcp-stdio", "--config", 'C:\path\to\dab-config.json']
[mcp_servers.dab_sql.env]
ASPNETCORE_URLS = "http://127.0.0.1:5155"
DAB_SQL_CONNECTION_STRING = "<connection string>"
- Start the MCP client. The DAB process exits before the handshake.
What I verified (Node.js MCP stdio client)
With ASPNETCORE_URLS set to a free port, all MCP methods work correctly:
initialize → serverInfo, capabilities, instructions returned
tools/list → 6 tools: describe_entities, create_record, read_records, update_record, execute_entity, aggregate_records
Also confirmed:
@env(DAB_SQL_CONNECTION_STRING) resolves correctly in stdio mode.
- All configured entities (tables and views) load successfully.
- Schema introspection works for both tables and views.
- The issue is not caused by SQL Server connectivity, entity configuration, or MCP protocol incompatibility.
Workaround
Set ASPNETCORE_URLS to a known free port:
ASPNETCORE_URLS = "http://127.0.0.1:5199"
Note: removing ASPNETCORE_URLS is not a reliable workaround, because DAB will then default to port 5000, which may also be occupied.
Suggested improvement
In --mcp-stdio mode, DAB should not attempt to bind an HTTP port. If the current behavior is intentional (e.g. Kestrel lifecycle is required even in stdio mode), DAB should bind to a random available port (port 0) or the documentation should explicitly warn that ASPNETCORE_URLS or the default port 5000 must be free.
Describe the bug
When running Data API Builder in MCP stdio mode, the process still attempts to bind an HTTP port via Kestrel. If the port is unavailable, the process exits with code -1 before the MCP handshake can complete.
This is unexpected because
--mcp-stdiocommunicates over stdin/stdout and should not depend on an HTTP port.Two scenarios trigger the crash:
ASPNETCORE_URLSis set to an occupied port.ASPNETCORE_URLSis not set — in my local test, DAB appeared to bind tohttp://127.0.0.1:5000and crashed when port 5000 was occupied.Environment
@env()connection string)dab start --mcp-stdio --config dab-config.jsonExpected behavior
dab start --mcp-stdioshould communicate with the MCP client over stdin/stdout and should not require an HTTP port. It should either:ASPNETCORE_URLSin stdio mode, orActual behavior
If the target port is occupied (either explicitly via
ASPNETCORE_URLSor implicitly via the default port 5000), the DAB process exits with code -1 and produces anAddressInUseExceptionin stderr.The MCP client only sees a generic handshake failure: "connection closed: initialize response".
Reproduction steps
Occupy a local port (e.g. start a DAB REST instance on port 5155, or have another process on port 5000).
Configure DAB MCP stdio:
What I verified (Node.js MCP stdio client)
With
ASPNETCORE_URLSset to a free port, all MCP methods work correctly:initialize→ serverInfo, capabilities, instructions returnedtools/list→ 6 tools:describe_entities,create_record,read_records,update_record,execute_entity,aggregate_recordsAlso confirmed:
@env(DAB_SQL_CONNECTION_STRING)resolves correctly in stdio mode.Workaround
Set
ASPNETCORE_URLSto a known free port:Note: removing
ASPNETCORE_URLSis not a reliable workaround, because DAB will then default to port 5000, which may also be occupied.Suggested improvement
In
--mcp-stdiomode, DAB should not attempt to bind an HTTP port. If the current behavior is intentional (e.g. Kestrel lifecycle is required even in stdio mode), DAB should bind to a random available port (port 0) or the documentation should explicitly warn thatASPNETCORE_URLSor the default port 5000 must be free.