SQL MCP Server: agentic access to SQL without NL2SQL
When an AI agent needs to read or write data in a production database, the usual options are bad: hand it raw SQL (NL2SQL — non-deterministic, hallucinates joins, leaks schema), or hand-build a REST API per table (slow, brittle, doubles the security surface). SQL MCP Server, shipped as a feature of Data API builder (DAB) 2.0, is the third path: a config-driven container that exposes any combination of Azure SQL, SQL Server, PostgreSQL, MySQL, and Cosmos DB through a small, fixed Model Context Protocol tool surface — with RBAC, caching, and telemetry enforced at the API layer.
Concept
DAB is the engine. SQL MCP Server is one of three protocols DAB can speak from the same configuration file:
- REST at
/api - GraphQL at
/graphql - MCP at
/mcp
All three honor the same entity definitions, role-based permissions, field exclusions, and policies. So the same config that powers your line-of-business app's REST endpoints can also expose those entities to a Foundry agent or to GitHub Copilot in VS Code — without writing a second authorization layer.
The MCP tool surface is fixed and small: 7 DML tools, regardless of how many tables you expose. This keeps the agent's context window focused on reasoning rather than tool definitions.
| Tool | Purpose |
|---|---|
describe_entities | Discovers entities and their operations |
read_records | SELECT against tables and views |
create_record | INSERT |
update_record | UPDATE |
delete_record | DELETE |
aggregate_records | GROUP BY / SUM / COUNT / AVG / MIN / MAX |
execute_entity | Runs a stored procedure (or your own custom tool) |
Each tool can be enabled or disabled per server, and stored procedures can be promoted as custom tools when you want to encapsulate business logic instead of exposing raw CRUD.
Architecture
❓ How does an MCP tool call become deterministic T-SQL?
Why DBAs care
This is not a generic "AI plugin." It addresses the specific failure modes that make DBAs nervous about agents touching production:
- No NL2SQL. The agent never writes T-SQL. It calls typed tools; DAB's Query Builder generates parameterized, deterministic T-SQL. Same input → same query → same result.
- Schema is hidden. The entity abstraction layer aliases names, excludes columns, and limits operations per role. The agent sees
Customer.email, notdbo.SLT_CUST.EMAIL_ADDR_NVCHAR. - RBAC at the API layer, in addition to whatever you have at the database layer. Defense in depth — a misconfigured DB grant can't bypass the API contract.
- Caching included. L1 in-memory and L2 Redis (or Azure Managed Redis) prevent request stampedes from chatty agents.
- Telemetry first-class. Logs and metrics flow to Application Insights, Azure Log Analytics, and OpenTelemetry collectors out of the box.
When to pick it
| Option | When to pick it | Trade-off | Don't use it for |
|---|---|---|---|
| SQL MCP Server (DAB) | You want agents to query existing relational data with deterministic, governed access | One more container to host; learn DAB's config schema | Schema design or DDL operations |
| NL2SQL inside the model | One-off exploration on a sandbox; the agent is the only user; data is non-sensitive | Hallucinated joins, no RBAC, fragile under schema change | Anything production or multi-tenant |
| Hand-rolled REST + tool wrapper | You already have a mature REST API and just need to wrap a few endpoints as MCP tools | Every new entity = code + tests + auth + cache | Greenfield agent enablement on a database |
Pick SQL MCP Server if the data lives in a SQL/Postgres/Cosmos/MySQL store and more than one agent or surface needs governed access. Pick NL2SQL only on throwaway sandboxes. Pick hand-rolled wrappers only when an established REST contract already encodes business rules you can't move into the database layer.
How to configure it
Three CLI commands take you from zero to a running MCP server pointed at AdventureWorksLT.
# 1. Install the DAB CLI (one-time)
dotnet tool install -g Microsoft.DataApiBuilder
# 2. Initialize the config
$env:SQL_CONN = "Server=tcp:myserver.database.windows.net;Database=AdventureWorksLT;Authentication=Active Directory Default;Encrypt=True;"
dab init --database-type mssql --connection-string "@env('SQL_CONN')" --host-mode Development
# 3. Add entities with role-scoped permissions
dab add Customer `
--source SalesLT.Customer `
--permissions "agent:read,aggregate"
dab add Order `
--source SalesLT.SalesOrderHeader `
--permissions "agent:read,aggregate"
# 4. Promote a stored procedure as a custom MCP tool
dab add CustomerOrders `
--source SalesLT.uspGetCustomerOrders `
--source.type stored-procedure `
--source.params "CustomerID:1" `
--permissions "agent:execute"
# 5. Add semantic descriptions so the agent picks the right entity
dab update Customer --description "Retail customers who place orders. Use for lookups by name, email, or company."
# 6. Run it
dab start
The generated dab-config.json (excerpt) is the only artifact you ship:
{
"data-source": { "database-type": "mssql", "connection-string": "@env('SQL_CONN')" },
"runtime": {
"mcp": {
"enabled": true,
"path": "/mcp",
"dml-tools": {
"describe-entities": true,
"read-records": true,
"aggregate-records": true,
"execute-entity": true,
"create-record": false,
"update-record": false,
"delete-record": false
}
},
"cache": { "enabled": true, "ttl-seconds": 30 }
},
"entities": {
"Customer": {
"source": { "object": "SalesLT.Customer" },
"description": "Retail customers...",
"permissions": [
{ "role": "agent", "actions": [
{ "action": "read", "fields": { "exclude": ["PasswordHash","PasswordSalt","rowguid"] } },
{ "action": "aggregate" }
]}
]
}
}
}
Then wire it into VS Code by adding to .vscode/mcp.json:
{
"servers": {
"adventureworks": {
"url": "http://localhost:5000/mcp",
"headers": { "X-MS-API-ROLE": "agent" }
}
}
}
GitHub Copilot in VS Code now has a typed view of your database, and writes are blocked because the role only grants read and aggregate.
Oracle parallel: SQL MCP Server is the agentic descendant of Oracle REST Data Services (ORDS). Both expose database objects through declarative configuration rather than hand-written endpoints, both layer their own RBAC on top of the database, and both let you promote PL/SQL packages (Oracle) or stored procedures (SQL) as named operations. The mapping breaks down at the protocol level: ORDS speaks REST and OAuth; SQL MCP Server adds MCP for native AI agent consumption and ships GraphQL alongside REST in the same config.
Common pitfalls / exam traps
- ❌ Treating MCP as a synonym for "the model writes SQL." MCP is a tool-calling protocol; SQL MCP Server's whole point is that the model doesn't write SQL.
- ❌ Granting
delete-recordon the globaldml-toolsblock "to keep things simple." Disable destructive tools at the runtime level and re-enable only on entities that genuinely need them, scoped by role. - ❌ Putting connection strings as literal values in
dab-config.json. Use@env('VAR')or Key Vault references — the config file ends up in source control. - ❌ Skipping descriptions. Without them the agent sees
dbo.t_cust_2024and guesses; with them it sees "Active retail customers as of 2024 reorg" and picks correctly. - ✓ Right mental model: DAB is the engine, MCP is one of three protocols, entity config is the contract, role-scoped permissions are the security boundary.
References
- Introducing SQL MCP Server (Azure SQL Dev Blog)
- Data API builder docs
- DAB auto-configuration
- Model Context Protocol specification
- DAB and MCP — questions answered (follow-up post)