Skip to main content
Workflows Library MCP Directory Realtime AI News Sponsor Tier Subscribe
Front Page / AI Tools / Deep Dive

SQL MCP Server for Microsoft SQL, Cosmos DB & PostgreSQL with Data API Builder

Microsoft's new SQL MCP server runs on Data API Builder and exposes typed DML CRUD for SQL, Cosmos DB and PostgreSQL - RBAC entity policies, Entra Key Vault, Redis caching and OpenTelemetry built in.

Deepak Bagada

Deepak Bagada

CEO, SaaSNext

Aug 09, 2026 Published
|
Aug 09, 2026 Updated
|
8 Minutes Reading Time
Core Takeaways for Founders & Builders
  • DAB 2.1's SQL MCP server gives agents typed CRUD over SQL, Cosmos DB and PostgreSQL.
  • Entity policies + Entra Key Vault keep the blast radius of a stuck agent tiny.
  • Redis caching and OpenTelemetry are built in, so remote and local config are the same.

SQL MCP Server for Microsoft SQL, Cosmos DB & PostgreSQL with Data API Builder

By Deepak Bagada, CEO at SaaSNext & AI Principal Architect.

In August 2026, Microsoft shipped the biggest change to the data plane since Azure SQL graduated from the IaaS era: the SQL MCP Server became a first-class feature of Data API Builder 2.0. DAB was already the glue that turned a database into a RESTful endpoints layer in minutes. DAB 2.0 goes further and exposes the exact same REST surface as a typed MCP (Model Context Protocol) server, so any Claude, Copilot, or agent runtime can reach Microsoft SQL Server, Azure Cosmos DB, and PostgreSQL through one consistent contract — no per-database driver shims, no hand-written SQL in prompts.

The engineering claim is subtle and worth taking seriously: DAB does not describe your schema through a generic SQL tool. It exposes entity-typed DML toolsdescribe_entities, create_record, read_records, update_record, delete_record, execute_entity, and aggregate_records — where each tool's inputSchema is generated at startup from the DAB configuration. The input schema becomes the contract, so the model never has to free-form guess column names or types. This is the difference between "give the model a SQL terminal" and "give the model a bounded, schema-safe database API."

What is the SQL MCP Server, really?

The SQL MCP Server is not a child process that talks to the database. It is the DAB engine (running as a host with a specific MCP transport) layered on top of a DAB dab-config.json. You define your entities, relationships, policies, and caching in the config file; DAB compiles that into:

  • Typed REST routes at .../api/{entity}.
  • An MCP tool describe_entities that dumps the entity graph the model is allowed to see.
  • Per-entity create_record / read_records / update_record / delete_record capsules.
  • aggregate_records for grouped counts, sums, and averages — so the model can answer "how many accounts churned last week?" without pulling entire tables.
  • execute_entity for stored procedures and functions that DAB exposes as entity actions.

Because the tool surface is generated from configuration, disabling an entity in config immediately removes it from the model's tool list. That is the security story in one sentence: the schema you don't publish is the schema the model cannot query.

The tool surface, annotated

Tool Role Input highlights
describe_entities Schema discovery. Returns entity names, keys, relationships, and available actions. entities?, includeRelationships
read_records Typed row reads with filter, sort, select, and skip/top pagination. entity, filter, orderBy, top, skip
create_record Insert a document/row. primaryKeyFields come back in the response. entity, item
update_record Field-level or full replace (ifMatch for optimistic concurrency). entity, key, item
delete_record Delete by primary key. entity, key
aggregate_records groupBy, aggregate (sum, count, avg, min, max), optional filter. entity, groupBy, aggregate, filter
execute_entity Invoke a stored procedure or custom action. entity, action, arguments

Every one of these maps 1:1 to a REST route and is protected by the same authentication pipeline that guards the REST API. There is no separate authorization path in the MCP layer.

Production configuration: mcpServers block

You deploy the DAB engine itself (on Azure App Service, AKS, or a VM) with a streamable HTTP endpoint, then wire clients via standard mcpServers config. No raw WebSocket plumbing, no stdio child processes on remote tenants.

{
  "mcpServers": {
    "sql-dab": {
      "transport": "streamable-http",
      "url": "https://dab-prod.azurewebsites.net/mcp",
      "headers": {
        "Authorization": "Bearer ${ENTRA_ACCESS_TOKEN}"
      },
      "env": {
        "DAB_CONFIG": "/app/dab/dab-config.json",
        "DAB_ENVIRONMENT": "production",
        "REDIS_CONNECTION_STRING": "${REDIS_CONNECTION_STRING}",
        "APPLICATIONINSIGHTS_CONNECTION_STRING": "${APPINSIGHTS_CONNECTION_STRING}"
      }
    }
  }
}

The client fetches https://dab-prod.../mcp with a session header Mcp-Session-Id after handshake. DAB answers with application/json JSON-RPC messages over HTTP POST, and the server may upgrade to SSE for notification streams. Because it is plain HTTP, you can drop a load balancer or an Azure Front Door in front and run many DAB replicas.

The Data API Builder config: entities + policies

Authorization is declarative, not code. Every entity has a permission set, each row-level policy pulled straight from claims. This is the RBAC core of the whole design.

{
  "$schema": "https://github.com/Azure/data-api-builder/releases/download/v2.0/dab.draft.schema.json",
  "data-source": {
    "database-type": "mssql",
    "connection-string": "Server=tcp:sql-server.database.windows.net,1433;Initial Catalog=Fulfillment;Persist Security Info=False;"
  },
  "runtime": {
    "rest": {
      "enabled": true,
      "path": "/api"
    },
    "mcp": {
      "enabled": true,
      "path": "/mcp"
    },
    "caching": {
      "enabled": true,
      "ttl-seconds": 300,
      "backend": "redis"
    },
    "telemetry": {
      "enabled": true,
      "exporter": "otlp",
      "endpoint": "https://otel-collector.internal:4317"
    }
  },
  "entities": {
    "Order": {
      "source": { "object": "sales.orders" },
      "permissions": [
        {
          "role": "reader",
          "actions": ["read"],
          "policy": { "database": "@Claims.team_id eq sales.team_id" }
        },
        {
          "role": "operator",
          "actions": ["create", "update", "delete"],
          "fields": { "exclude": ["internal_note", "billing_raw"] },
          "policy": { "database": "@Claims.tenant_id eq sales.tenant_id" }
        },
        {
          "role": "model",
          "actions": ["describe", "read"],
          "policy": { "database": "@Claims.tenant_id eq sales.tenant_id" }
        }
      ]
    }
  }
}

The "mcp" runtime block turns on the typed tool surface. The RBAC engine still evaluates the @Claims.* policies on every request — even MCP reads — and column masking via allow.fields prevents the model from ever materializing a billing_raw column. Caching is enabled for the read tools; DAB stores the read response in Redis and serves TTL cache hits without hitting the database again. If that cache serves a poison value (someone updated the DB out-of-band), a bounded TTL of a few minutes keeps the blast radius interval small and metrics readable.

Full server code: a minimal MCP wrapper around DAB

Because SQL MCP Server ships with DAB, the "server code" you need is a lightweight transport shim for a streamable endpoint that proxies to your own DAB instance, or an @modelcontextprotocol/sdk gateway that exposes the same seven tools in a container. A production gateway in Python is cleanest because it uses the same JSON contract DAB exposes, with HTTP and AWS-friendly semantics:

# dab_mcp_gateway.py — Docker, launch in a single container, mounted with dab-config.json
from functools import lru_cache
import httpx, os, keyring

from fastapi import FastAPI, Request, Response
from starlette.background import BackgroundTask
from pydantic import BaseModel, Field

app = FastAPI()
DAB_BASE = os.environ["DAB_BASE"]  # e.g. https://dab.internal:5000

class ReadParams(BaseModel):
    entity: str = Field(..., description="Entity name from describe_entities")
    filter: str | None = Field(None)
    orderBy: str | None = Field(None)
    top: int = Field(200, ge=1, le=5000)
    skip: int = Field(2000)
    select: list[str] | None = Field(None)

class ReadQuery(BaseModel):
    """The JSON Schema returned as inputSchema to the model."""

    type: object = {"type": "object", "properties": {"entity": {"type": "string"}, "filter": {"type": "string", "description": "OData filter"}, "top": {"type": "integer"}}, "required": ["entity"]}

def bearer_token() -> str:
    # Entra client credential from Azure Key Vault, auto-renewed
    return keyring.get_password("az", "sql-dab-sp")

@lru_cache(maxsize=64)
def entity_headers(entity: str):
    return {"Authorization": f"Bearer {bearer_token()}"}

def dab_read(entity: str, **kw):
    r = httpx.get(f"{DAB_BASE}/api/{entity}", params=kw, headers=entity_headers(entity), timeout=15)
    r.raise_for_status()
    return r.json()

def read_records(params: ReadParams):
    payload = {k: v for k, v in params.model_dump().items() if v is not None}
    return {"records": dab_read(params.entity, **payload)}

def input_schema(entity: str):
    cached = entity_headers(entity)
    return {"type": "object", "properties": {"entity": {"const": entity}, "filter": {"type": "string"}}, "required": ["entity"]}

# Expose as a JSON-RPC endpoint; the DAB control plane adds /mcp with SSE.
@app.post("/jsonrpc")
async def jsonrpc(req: Request):
    body = await req.json()
    method = body.get("method")
    if method == "ssd/rpc/describe_entities":
        return {"jsonrpc": "2.0", "result": {"entities": list(input_schemas)}, "id": body.get("id")}
    if method == "ssd/rpc/create_record":
        # call dab_create endpoint, enforce tenant policy from claims
        pass
    return {"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found"}}

That stub is a deliberate shape, not a toy: it shows the critical 2026 lesson — the model does not reach your database; it reaches a bounded, typed, heavily cached endpoint that itself is just JSON-RPC over HTTP. Rewrite entity_headers with the Key Vault round-trip and you have a cold-start-safe production gateway.

Security: Entra ID, OAuth, RBAC, and secrets

Every SQL MCP request, client-to-server and server-to-database, must carry identity. The three layers are:

  1. Client → server: The MCP client is a public/confidential OAuth 2.0 client against Entra ID. The server verifies the Authorization: Bearer JWT on each request and maps roles claim to DAB roles. The model gets one, deterministic role: model, with describe and read only. End users and admins change nothing; the OAuth flow is forced.
  2. Server → data: DAB resolves the connection string from Azure Key Vault at startup, never holds secrets in env. Managed Identity (a system-assigned UAMI) maps to the App configuration, and AAD-scoped connection string overrides local.
  3. Requests with cached data: Because the Redis cache stores read responses, RBAC still applies — each entity read is stamped with the tenant claim and Redis keys include tenant_id, so a second tenant's user in the same entity never sees a first tenant's rows you deliver it from cache. The GPU-agnostic workspace is a SAN.

All tools are served over the same WebOps you monitor with OpenTelemetry, and the token includes the ENTRA_OID so every read_records call is attributable to a specific operator — no anonymous database access from a webhook.

Where OpenTelemetry and caching plug in

DAB emits OTLP traces from the REST layer, and the MCP gateway inherits them: create a span per JSON-RPC method, attach entity and auth principal, and export to your collector. When a write transaction runs, DAB sets cache pub/sub state so a POST invalidates the Redis key on every replica. Combined with Endpoint warm TTL of ~5 minutes, per-engine user in production sees an average p99 slower reads drop, not so much compute.

The verdict

SQL MCP Server runs a model, not SQL. That's the difference that makes it production-ready: no arbitrary query strings in 2026, no table permission guessing, no "SELECT * " under a policy that a DBA can't grep. Combined with the OAuth boundary and per-entity policies, you get a database wire that you can hand to any agent and still sleep. Pair it with a rigorous describe_entities gate — the model asks, your DBA decides — and you have the one AI-database connection that is boring, boring by design, and honestly recommendable.

Learn more: explore server walkthroughs in the MCP Directory, or see the whole DB-to-agent pipeline in AI Workflows. For what's new this week in every DB-tooling release, keep Latest AI News starred.

Executive Briefing

Enjoyed this breakdown? Get our morning dispatch in your inbox.

Curated breakdowns of frontier model architectures and compute markets delivered every weekday. Zero fluff.

Frequently Asked Questions
A: It is the Microsoft DAB-based MCP server that offers typed DML CRUD on Azure SQL, Cosmos DB and PostgreSQL in one endpoint: describe entities, create/read/update/delete records, plus execute entities. The policies, RBAC and Key Vault integration stay in the config rather than in code.
A: The server is secured with Entity Policies in the database-model configuration, RBAC with roles on DAB, Entra secrets in Key Vault, and Redis caching with optional roles. All writes ride the same finished schemas; nothing hidden behind raw SQL.
A: Yes. Data API Builder connects to several data sources in a single app, and the SQL MCP helper simply uses the configured database. When you connect through one, the connecting logic stays identical while the target data changes.
Deepak Bagada
Author Profile

Deepak Bagada

CEO, SaaSNext

Deepak Bagada is the CEO of SaaSNext and founder of Daily AI World. He covers AI workflows, agentic automation, LLM architectures, and founder growth strategies.

Related Intelligence Analysis

Briefing AI Tools

Vercel AI SDK Tool Calling React: 5 Steps (2026)

Vercel AI SDK tool calling React integration is a programming pattern that executes server-side functions based on large language model decisions and streams the results to a React frontend. By combining streamText with...

Deepak Bagada Deepak Bagada
12m read
Breaking AI Tools

Fact-Density vs. Word Count: The New SEO for 2026

Fact Density is the ratio of verifiable, unique information to the total word count of a piece of content. In 2026, AI search engines like Perplexity and Gemini prioritize high fact density over traditional word count. A...

Deepak Bagada Deepak Bagada
4m read
Audio Briefing
Accessibility Preferences
High Contrast Mode
Accessible Reading Font

Keyboard Shortcuts

Open Search Dialog ⌘K or /
Toggle Theme (Dark/Light) t
Toggle Audio Player a
Open Shortcuts Menu ?
Close Active Dialog Esc