Claude Code SQL Server Setup (and Codex): Run Microsoft's SQL MCP Server on a Dev Copy Under a Read-Only Login

Claude Code SQL Server setup with Codex: a db_datareader login, Data API builder's SQL MCP Server with read-only entities, a refused-write test and rollback.

Microsoft Learn documentation page titled What is SQL MCP Server?, showing the site header and the start of the overview article.
The top of Microsoft Learn's 'What is SQL MCP Server?' overview as captured. The article it opens documents SQL MCP Server as part of Data API builder from version 1.7, with role-aware DML tools. Source: Microsoft Learn. · Original source

A Claude Code SQL Server setup usually fails in the same place. Someone copies a quickstart connection string that logs in as sa, wires it into an MCP config to see what happens, and the agent now holds every permission that login has, on a database that turns out to be shared. This guide builds the opposite arrangement. You will give Claude Code, plus Codex if your team uses it, a read path into a restored dev copy of your database through Microsoft’s SQL MCP Server, under a SQL login that can only read. The agent answers schema and data questions with tool calls and drafts T-SQL. You review that T-SQL and run it yourself in SSMS.

The prompt for this guide came from the SQL Server community. On Nov. 2, 2026, Rebecca Lewis (@sqlfingers on X) posted “Build the Fence Before Letting in the AI: The Complete sp_Blitz Series”, writing that she had spent a couple of weeks experimenting with Claude Code and SQL Server while building a small application around Brent Ozar’s sp_Blitz. The title is the right instinct. What follows is a fence you can build in an afternoon and test from both sides.

This is written for a SQL Server developer or DBA who wants an agent’s help reading a schema, finding rows and drafting queries, without the agent ever holding sa or touching production. If you run Postgres, the same pattern lives in our Postgres MCP read-only role guide; this one covers what is specific to SQL Server and Data API builder. Every command below is documented syntax from the linked Microsoft, Anthropic and OpenAI pages. Nothing here was captured from a live run, so each “expect” line describes what the documentation and the SQL Server permission model predict, not output we observed.

Why two fences instead of one

SQL MCP Server is not a separate download. Microsoft’s SQL MCP Server overview describes it as part of Data API builder (DAB) from version 1.7, recommends the latest 2.0 release, and says it is configured with one JSON file that defines how to connect, which tables, views or stored procedures to expose, and the permissions on each object. Two of its design choices make it a sensible first agent connection for a cautious DBA.

First, it deliberately does not do NL2SQL. Microsoft states that the server exposes a small family of DML tools (describe_entities, create_record, read_records, update_record, delete_record, execute_entity and aggregate_records) and that DAB’s query builder produces the T-SQL deterministically. The agent cannot hand the server free-form SQL, and DDL is outside the server’s design entirely. That is why the agent in this guide drafts T-SQL for you instead of running it.

Second, it is role-aware. Each entity in the config declares which roles may read, create, update or delete, and Microsoft says DAB only exposes the entities and operations the current role is permitted to access, applying those rules to every MCP tool. That is fence one: the API layer refuses writes for the role the agent runs as.

Fence two lives in SQL Server. DAB connects with a connection string, and the login in that string bounds everything DAB can ever do. If the login can only SELECT, a mistake in the DAB config, a teammate’s later edit, or a different MCP server pointed at the same string still cannot change a row. Each fence holds on its own, which is the point: you test each one separately, and either one failing alone does not expose your data.

Claude Code SQL Server diagram: Claude Code or Codex connects to SQL MCP Server (DAB) with read-only entities, then as the agent_reader login in db_datareader to ShopDb_Dev. A write attempt stops at Fence 1 (writes refused for anonymous) and anything past it stops at Fence 2 (SQL denies row changes). Drafted T-SQL goes to you, and you run it in SSMS under your own login.Claude Code SQL Server diagram: Claude Code or Codex connects to SQL MCP Server (DAB) with read-only entities, then as the agent_reader login in db_datareader to ShopDb_Dev. A write attempt stops at Fence 1 (writes refused for anonymous) and anything past it stops at Fence 2 (SQL denies row changes). Drafted T-SQL goes to you, and you run it in SSMS under your own login.
Each layer refuses writes on its own: DAB's entity permissions for the anonymous role, then the agent_reader login's database roles. Every change goes through you. Illustrative setup from this guide, based on Microsoft Learn's SQL MCP Server overview and Database-level roles pages, read Nov 4, 2026.

The operating idea behind this is old: treat the agent like any other privileged principal and give it the narrowest identity that does the job, as argued in our guide to treating agents as privileged users.

What you build and what you need first

Gather these before you touch a config file. The checklist doubles as the scope statement for the whole exercise.

  • [ ] A SQL Server 2016 or later instance you control. Microsoft’s VS Code quickstart lists Developer, Express, LocalDB and SQL Server in Docker as working options.
  • [ ] A restored dev copy of the database, never the production database. This guide calls it ShopDb_Dev and assumes a dbo.Products table shaped like the quickstart’s sample (Id, Name, Inventory, Price, Cost) with its ten seed rows. If you have no copy handy, run the quickstart’s create-and-seed script and name the database ShopDb_Dev.
  • [ ] sysadmin, or the right to create logins, on that dev instance only.
  • [ ] .NET 9 or later; dotnet --version should report 9.0 or higher.
  • [ ] The DAB CLI at version 1.7 or later, preferably the current 2.0 release.
  • [ ] Claude Code, Codex CLI, or both, installed and signed in.
  • [ ] SSMS or another SQL client where you run T-SQL under your own login.

The quickstart installs DAB as a .NET tool in a local tool manifest. Run this in the folder that will hold your config:

dotnet new tool-manifest
dotnet tool install microsoft.dataapibuilder
dotnet tool restore

By the end you will have five things:

  1. A SQL login agent_reader and matching database user in db_datareader, with db_denydatawriter as a belt, on ShopDb_Dev only.
  2. A dab-config.json whose connection string comes from an environment variable and whose single entity grants read.
  3. SQL MCP Server registered in Claude Code and in Codex, with its tools visible in each.
  4. A database rules block in CLAUDE.md and AGENTS.md.
  5. A review loop in which the agent drafts T-SQL into a file and you decide whether it runs.

Choose the login before the connection string

The connection string decides which identity DAB uses, and that identity is fence two. Picking the authentication method is therefore a security decision, not a convenience setting.

The quickstart’s example .env uses Trusted_Connection=True, which works on Windows. Integrated authentication connects as the Windows account running dab, and on your own workstation that is almost certainly you, a sysadmin on your own dev instance. That quietly removes fence two. The quickstart’s SQL authentication example logs in as sa, which removes it loudly. Both are fine for a five-minute demo and wrong for an agent you plan to keep.

Where dab runs Login to use Connection string shape Why
Windows, same machine as the instance SQL login agent_reader Server=localhost;Database=ShopDb_Dev;User Id=agent_reader;Password=<secret>;TrustServerCertificate=True Trusted_Connection would run as your own Windows account
Docker container on Mac, Linux or Windows SQL login agent_reader Server=localhost,1433;Database=ShopDb_Dev;User Id=agent_reader;Password=<secret>;TrustServerCertificate=True The quickstart assumes the container maps port 1433 to localhost
WSL or a Mac reaching an instance on another host SQL login agent_reader Server=<address-that-reaches-the-host>,1433;Database=ShopDb_Dev;User Id=agent_reader;Password=<secret>;TrustServerCertificate=True Integrated authentication is the Windows path; prove the address works from the same shell first
A dedicated Windows account, advanced Windows login created FROM WINDOWS Server=<host>;Database=ShopDb_Dev;Trusted_Connection=True;TrustServerCertificate=True Only works if dab actually runs as that account, not as you

Two cautions apply to every row. SQL authentication only works if the instance allows it: the CREATE LOGIN reference says the server’s authentication mode must match the login type. And TrustServerCertificate=True, which the quickstart uses for local instances, tells the client not to validate the server certificate. Keep it on a dev box with a self-signed certificate; do not carry it into a connection string for a shared server.

Step 1. Create the read-only login on the dev copy

This script creates the only identity the agent will ever use. Run it in SSMS, as sysadmin, against the dev instance.

  1. Connect to the dev instance and run SELECT @@SERVERNAME AS server_name;. Read the result before going further. If it names anything other than your dev instance, stop.
  2. Generate a long, unique password and store it in your password manager. Microsoft’s reference says SQL login passwords are case-sensitive, 8 to 128 characters, and cannot contain single quotes or the login name.
  3. Run the script below, replacing the placeholder password.
  4. Run the verification query that follows it.
-- Run on the DEV instance only, as sysadmin.
USE master;
GO
CREATE LOGIN agent_reader
    WITH PASSWORD = N'<long-unique-password>',
    DEFAULT_DATABASE = ShopDb_Dev,
    CHECK_POLICY = ON;
GO

USE ShopDb_Dev;
GO
CREATE USER agent_reader FOR LOGIN agent_reader;
GO
ALTER ROLE db_datareader ADD MEMBER agent_reader;
GO
-- Optional belt: an explicit deny on row changes.
ALTER ROLE db_denydatawriter ADD MEMBER agent_reader;
GO

Each line is there for a reason. Microsoft’s reference says a new login is enabled with server-level CONNECT SQL and otherwise only the permissions of the public role; it cannot reach a database until you create a database user for it. Without DEFAULT_DATABASE, the login defaults to master. CHECK_POLICY = ON is already the default and applies the host’s Windows password policy; it is spelled out so a reviewer sees it. There is no MUST_CHANGE, because a service login has nobody to change the password at first sign-in.

The database-level roles reference defines db_datareader as GRANT SELECT ON DATABASE, which reads all user tables and views. db_denydatawriter is DENY INSERT, DENY UPDATE and DENY DELETE on the database; its members cannot add, modify or delete data. Adding it costs nothing and turns a future accidental db_datawriter membership into a non-event.

Note that db_datareader is database-wide. If the dev copy holds tables the agent should never see even through SQL, the same reference says user-defined roles can be shaped with GRANT, DENY and REVOKE; it also warns against adding user-defined roles as members of fixed roles, because that can enable privilege escalation. For most dev copies, the narrowing happens in DAB, where you list the entities the agent may reach.

Microsoft Learn documentation page titled Database-level roles for SQL Server, showing the site header and the start of the article.
The top of Microsoft Learn's 'Database-level roles' page as captured, the reference behind the db_datareader and db_denydatawriter memberships in Step 1. Source: Microsoft Learn. · Original source

Now confirm the membership, using a filtered version of Microsoft’s role-membership example:

USE ShopDb_Dev;
GO
SELECT roles.name AS role_name,
       members.name AS member_name
FROM sys.database_role_members AS drm
INNER JOIN sys.database_principals AS roles
    ON drm.role_principal_id = roles.principal_id
INNER JOIN sys.database_principals AS members
    ON drm.member_principal_id = members.principal_id
WHERE members.name = N'agent_reader';
GO

Expect exactly two rows: db_datareader and db_denydatawriter. If db_datawriter, db_ddladmin or db_owner appears, remove that membership before going further. The chart below shows why each of those three is wrong for an agent.

Claude Code SQL Server permissions matrix of five fixed database roles against four kinds of access. db_datareader, highlighted as the agent role, grants read rows only. db_denydatawriter denies changing rows. db_datawriter grants changing rows. db_ddladmin grants changing schema. db_owner grants all four. Other cells read not granted.Claude Code SQL Server permissions matrix of five fixed database roles against four kinds of access. db_datareader, highlighted as the agent role, grants read rows only. db_denydatawriter denies changing rows. db_datawriter grants changing rows. db_ddladmin grants changing schema. db_owner grants all four. Other cells read not granted.
Only db_datareader gives an agent login SELECT without any write or schema power, and db_denydatawriter adds an explicit deny on row changes. Source: Microsoft Learn, Database-level roles (SQL Server), page updated Sep 21, 2026.

Step 2. Prove the SQL fence by hand

Test fence two before any agent or MCP server exists. If it fails here, nothing later in this guide is safe.

  1. Open a new SSMS connection with SQL Server Authentication as agent_reader. Expect the connection to land in ShopDb_Dev.
  2. Run the positive test: SELECT TOP (5) Id, Name, Inventory FROM dbo.Products ORDER BY Id;. Expect five rows, starting with Action Figure if you used the quickstart seed data.
  3. Run the negative test below. Expect a permission-denied error that names the INSERT permission on dbo.Products.
  4. Repeat with UPDATE dbo.Products SET Inventory = Inventory WHERE Id = 1; and expect a permission-denied error naming UPDATE.
  5. Try CREATE TABLE dbo.FenceTest (Id INT); and expect a denial, because the login holds no DDL permission.
  6. Write the three refusals into your change note with the date. They are your evidence that fence two exists.
-- Run while connected as agent_reader.
BEGIN TRANSACTION;
INSERT INTO dbo.Products (Id, Name, Inventory, Price, Cost)
VALUES (999, N'Fence test', 1, 1.00, 1.00);
ROLLBACK TRANSACTION;

The transaction wrapper is a safety net for the test itself: if the insert unexpectedly succeeds, the rollback removes the row. A success is still a failed test. Go back to Step 1 and look for an extra role membership or a direct grant.

Step 3. Write the DAB config with read-only entities

With the identity proven, build the config that defines fence one. Set the connection string in your shell first so the password never lands in the file.

mkdir -p ~/sql-agent && cd ~/sql-agent
export MSSQL_CONNECTION_STRING='Server=localhost,1433;Database=ShopDb_Dev;User Id=agent_reader;Password=<long-unique-password>;TrustServerCertificate=True'

dab init --database-type mssql \
  --connection-string "@env('MSSQL_CONNECTION_STRING')" \
  --host-mode development \
  --mcp.enabled true \
  --config dab-config.json

dab add Products \
  --source dbo.Products \
  --source.type table \
  --permissions "anonymous:read" \
  --description "Toy store products with inventory, price, and cost."

dab update Products --fields.name Id --fields.primary-key true --fields.description "Product Id"
dab update Products --fields.name Name --fields.description "Product name"
dab update Products --fields.name Inventory --fields.description "Units in stock"
dab update Products --fields.name Price --fields.description "Retail price"
dab update Products --fields.name Cost --fields.description "Store cost"

On Windows PowerShell, set the variable with $env:MSSQL_CONNECTION_STRING = '<same string>', and in Command Prompt replace the trailing backslashes with ^, as the dab init reference shows.

Why these choices work:

  • --connection-string "@env('MSSQL_CONNECTION_STRING')": the init reference says this option supports @env(), so the file records a reference, not the secret.
  • dab init overwrites an existing config with no merge, according to the same reference. Keep dab-config.json in version control so a second init cannot silently erase your permissions.
  • --host-mode development matches the quickstart and suits a dev copy; the default is Production.
  • --mcp.enabled true is already the default. Stating it matters because the quickstart notes that the stdio transport requires "mcp": { "enabled": true } in the runtime section.
  • --permissions "anonymous:read" is fence one. Microsoft’s overview says stdio defaults to the anonymous role when none is given, and the init reference says that under the default Unauthenticated provider all HTTP requests run as anonymous. Granting anonymous only read means writes are refused in both transports.
  • The field descriptions come straight from the quickstart, which warns that without them agents see only entity names and may guess column names incorrectly.

Open the file and tick through it before starting anything:

  • [ ] data-source shows "connection-string": "@env('MSSQL_CONNECTION_STRING')", with no password anywhere in the file.
  • [ ] The runtime section has MCP enabled.
  • [ ] Host mode is development.
  • [ ] Products lists one role, anonymous, with the single action read.
  • [ ] No other entities exist. An entity you never add is an entity the agent never sees.

If you later add an entity for REST or GraphQL that agents should not reach, the overview documents an entity-level switch that removes it from MCP while leaving the other surfaces alone:

"entities": {
  "SensitiveData": {
    "mcp": {
      "dml-tools": false
    }
  }
}

Microsoft Learn documentation page titled What is SQL MCP Server?, showing the site header and the start of the overview article.
The top of Microsoft Learn's 'What is SQL MCP Server?' overview as captured. The article it opens documents SQL MCP Server as part of Data API builder from version 1.7, with role-aware DML tools. Source: Microsoft Learn. · Original source

Step 4. Start SQL MCP Server and inspect its tools

SQL MCP Server speaks two transports, streamable HTTP and stdio, according to Microsoft’s overview. Pick one per client; you can use different ones for Claude Code and Codex.

Question stdio HTTP
Who starts dab The agent CLI starts it as a child process You start dab start --config dab-config.json in a terminal
Which role applies The role:anonymous argument; anonymous is also the default anonymous, under the default Unauthenticated provider
Documented limits Simulator authentication; requests limited to 1 MB Endpoint printed at startup; the quickstart assumes http://localhost:5000/mcp
Where the env var must exist In the environment the agent passes to the child process In the terminal where you run dab start
Best fit One developer, one client, nothing to keep running Two clients sharing one server, or inspecting tools by hand

Before any model sees the tools, inspect them yourself over HTTP:

  1. In a terminal where MSSQL_CONNECTION_STRING is set, run dab start --config dab-config.json. Expect the terminal to print its listening URLs.
  2. In a second terminal, run npx -y @modelcontextprotocol/inspector http://localhost:5000/mcp. Microsoft recommends this proxy mode because it avoids browser CORS and Mcp-Session-Id header problems.
  3. List the tools. Expect tools such as describe_entities and read_records; the quickstart notes that names can vary with configuration.
  4. Call describe_entities. Expect Products as the only entity, with your field descriptions.
  5. Call read_records against Products. Expect the ten seed rows if you used the quickstart data.
  6. If a write tool such as create_record is listed, call it against Products. Expect DAB to refuse it for the anonymous role. That is fence one, tested without a model in the loop.
  7. Stop the server with Ctrl+C, or leave it running if you will register clients over HTTP.

Step 5. Register the server in Claude Code

Anthropic’s Claude Code MCP reference documents both shapes. For stdio, Claude Code starts dab itself:

claude mcp add --env MSSQL_CONNECTION_STRING="$MSSQL_CONNECTION_STRING" --transport stdio sqldev \
  -- dab start --mcp-stdio role:anonymous --loglevel error --config /home/you/sql-agent/dab-config.json

The order is deliberate. The reference says --env accepts several KEY=value pairs and will read a server name placed directly after it as another pair, so put another option such as --transport stdio between them. Everything after -- goes to dab untouched. Use an absolute --config path so the server starts correctly whatever directory Claude Code launches it from. The arguments mirror Microsoft’s stdio client definition.

Without --scope, the server is local scope: stored in ~/.claude.json under this project and private to you. With --env, the reader password sits in that file too. For a dev-only login that can only SELECT, that is a reasonable trade; never move this entry to project scope, because .mcp.json is meant to be committed.

For HTTP, with dab start already running in a terminal:

claude mcp add --transport http sqldev http://localhost:5000/mcp

Then verify, in this order:

  1. claude mcp list. Expect sqldev with ✔ Connected. A ✘ Failed to connect status carries the failure detail on the same line.
  2. claude mcp get sqldev. On failure, read the Issue: line.
  3. Inside a session, run /mcp. Expect a tool count next to sqldev.
  4. Ask: “Using the sqldev tools, which products have an inventory under 30?” Expect a read_records call. With the seed data the answer is Building Blocks (25), Board Game (20), Doll House (10), Art Kit (28) and RC Helicopter (12).
  5. Run the negative test: “Using sqldev, set the inventory of product 4 to 0.” Expect the agent to report that the write was refused or unavailable. Then check in SSMS that Toy Car still shows 50.

If you want the general mechanics of scopes and approvals, our Claude Code MCP setup guide covers them in depth.

Step 6. Register the same server in Codex

OpenAI’s Codex MCP documentation stores servers in ~/.codex/config.toml, shared by the Codex CLI, the IDE extension and the ChatGPT desktop app on the same host. Codex adds two controls Claude Code’s CLI command does not: an environment forwarding list and per-server tool filters.

[mcp_servers.sqldev]
command = "dab"
args = ["start", "--mcp-stdio", "role:anonymous", "--loglevel", "error",
        "--config", "/home/you/sql-agent/dab-config.json"]
env_vars = ["MSSQL_CONNECTION_STRING"]
startup_timeout_sec = 30
disabled_tools = ["create_record", "update_record", "delete_record", "execute_entity"]
default_tools_approval_mode = "prompt"

env_vars forwards the variable from the environment Codex runs in, so the password stays out of config.toml. startup_timeout_sec raises the 10-second default to give .NET room to start. disabled_tools is a deny list; it is a third, client-side layer, not a replacement for the two fences. default_tools_approval_mode = "prompt" asks before every call during your first week; the documented writes mode later prompts only for tools not marked read-only.

For the HTTP transport, replace command, args and env_vars with url = "http://localhost:5000/mcp" and keep the filters.

  • [ ] Export MSSQL_CONNECTION_STRING in the shell that launches codex.
  • [ ] Run codex mcp list and expect sqldev in the list.
  • [ ] In the codex TUI, run /mcp and expect sqldev among the active servers.
  • [ ] Compare the tool names /mcp shows with disabled_tools; if your DAB version names them differently, update the list.
  • [ ] Repeat the inventory question and the product 4 negative test from Step 5.

The approval modes and sandbox settings that sit around MCP calls are covered in our Codex approval modes guide.

Put the database rules where both agents read them

Fences enforce; instructions shape behavior inside them. Put the same block in CLAUDE.md for Claude Code and AGENTS.md for Codex, at the repository root.

## Database rules (SQL Server)
- The only database you may query is ShopDb_Dev, through the sqldev MCP server. It is a dev copy, never production.
- Call describe_entities before guessing table or column names.
- Never ask for, suggest or configure a login with write, DDL or sysadmin rights.
- Never edit dab-config.json, its permissions or any connection string.
- Never run T-SQL through any other route: no sqlcmd, scripts or shell commands.
- Write proposed T-SQL to sql/drafts/<yyyy-mm-dd>-<topic>.sql and stop.
- Wrap every proposed data change in BEGIN TRANSACTION ... ROLLBACK TRANSACTION,
  state the expected @@ROWCOUNT, and include SELECTs showing the rows before and after.
- Propose DDL only when explicitly asked, and only for ShopDb_Dev.
- If a tool call is refused, report the refusal. Do not look for a workaround.

How the rules earn their place:

  1. The sqlcmd rule closes the gap the fences do not cover. A coding agent can run shell commands, and a shell can reach SQL Server with whatever credentials your machine holds. Pair the rule with permission settings, as described in our restricted-mode fleet policy guide.
  2. The drafts folder turns every proposal into a file with a name, a date and a diff in version control, which is what you review.
  3. The transaction and row-count requirements make the agent state its own expected result, so a mismatch is visible before anything commits.
  4. “Report the refusal” keeps a blocked agent from spending a session probing for another way in, which is the behavior you would most want to see in a log.

Worked example from question to reviewed T-SQL

Here is the whole loop on the sample data. The scenario: you want every product under 30 units restocked to 30, and you want the agent to do the analysis and drafting, not the writing.

  1. Ask: “Using sqldev, list products with inventory under 30, with their cost.” Expect a read_records call returning the five products above. Cost comes back because Products exposes every column; if Cost should be private, you would narrow fields in DAB.
  2. Ask: “Draft the T-SQL to restock those products to 30 units. Follow the database rules.” Expect a new file in sql/drafts/ and no tool call that writes.
  3. Open the draft. A rules-following draft looks like this:
-- sql/drafts/2026-11-04-restock-under-30.sql
-- Expected rows updated: 5
USE ShopDb_Dev;
BEGIN TRANSACTION;

SELECT Id, Name, Inventory FROM dbo.Products WHERE Inventory < 30 ORDER BY Id;

UPDATE dbo.Products
SET Inventory = 30
WHERE Inventory < 30;

SELECT @@ROWCOUNT AS rows_updated;

SELECT Id, Name, Inventory FROM dbo.Products WHERE Inventory = 30 ORDER BY Id;

ROLLBACK TRANSACTION;  -- change to COMMIT only after the checks match
  1. Review before running. The @@ROWCOUNT line should read 5. The final SELECT will show six rows, not five, because Puzzle 500 pcs already had exactly 30 units. That is the kind of detail a reviewer catches and a hurried commit does not: the after-check filters on the new value, so it includes a row the update never touched.
  2. Run the file in SSMS under your own login, on the dev instance, as written with ROLLBACK. Compare the results with the expectations in the header.
  3. Only if everything matches, change ROLLBACK to COMMIT and run it again. The agent never executes the statement, and the login it uses could not if it tried.
  4. Ask the agent to re-read the table through sqldev and confirm the five products now show 30. The read confirms your change through the same fence the agent uses.

DAB, a community server or no MCP at all

SQL MCP Server is not the only way to give an agent SQL Server context. This table compares the three realistic options for a dev copy, from the evidence above and the documented behavior of each approach.

Option What the agent can see Write risk Setup cost Choose it when
SQL MCP Server via DAB Only entities you add, with descriptions you write Two fences: DAB role permissions and the SQL login Moderate: .NET, DAB CLI, a config per database You want live reads with no free-form SQL from the agent
A community SQL MCP server that executes queries Whatever the login can read, usually the whole catalog One fence: the login; the agent writes the SQL that runs Low: one package and a connection string You need ad hoc analytical SQL and accept that the login is the only control
No MCP: the agent writes scripts, you run them Only what you paste or export, such as a schema script None from the agent; you execute everything Lowest Tight data rules, or a schema where reading rows is not needed

The second row is where Anthropic’s own reference points for Postgres, where it advises using a read-only database user in the connection string so the queries Claude runs cannot modify data. That advice carries over to SQL Server: if you choose a query-executing server, agent_reader from Step 1 is still the login to give it. For broader MCP hardening across a team, see our MCP security hardening guide.

Troubleshooting the connection and the fences

Most failures land in one of these rows. Work through the cause before changing permissions; widening access is never the first fix.

Symptom Likely cause Fix Expect after the fix
Login failed for agent_reader Wrong password, the instance does not allow SQL authentication, or the login was created in a rolled-back transaction Reset the password; confirm the authentication mode matches the login type; rerun Step 1 outside any open transaction Step 2 connects
Certificate or encryption error on connect The client rejects the dev instance’s self-signed certificate On a dev box only, keep TrustServerCertificate=True in the string Connection succeeds; never copy this to a shared server
✘ Failed to connect in claude mcp list dab not found by Claude Code, a bad --config path, or the env var missing from the child process Run the exact stdio command in a terminal first; use absolute paths; re-add with --env ✔ Connected and a tool count in /mcp
Codex lists no sqldev tools Startup timeout, or MSSQL_CONNECTION_STRING not exported in the shell that launched Codex Raise startup_timeout_sec; export the variable and restart Codex sqldev active in /mcp
Products missing from describe_entities Entity not added, or the role in the client args differs from the entity’s role Check entities in the config and role:anonymous in the args Products listed with field descriptions
Agent guesses wrong column names No field descriptions Run the dab update --fields.* commands Queries use real column names
A write succeeds anywhere The entity grants more than read, or the login has db_datawriter, db_owner or a direct grant Stop the server; fix the entity permission; rerun the Step 1 membership query and Step 2 tests Both negative tests refused again
WSL cannot reach an instance on Windows The address you used from WSL does not reach the Windows host’s SQL listener Prove connectivity from the WSL shell with a SQL client first, or run the agent CLI and dab on the Windows side with the instance A SQL client in the same shell connects before DAB does

Roll it back

Removal should be as deliberate as setup. Do it in this order so nothing is left holding a connection when you drop the login.

  1. Remove the server from Claude Code: claude mcp remove sqldev. If you added it at another scope, include --scope with that scope.
  2. Remove the [mcp_servers.sqldev] table from ~/.codex/config.toml, or set enabled = false to keep it for later.
  3. Stop any running dab start process with Ctrl+C. Stdio servers stop when their client session ends.
  4. Drop the user and login on the dev instance:
USE ShopDb_Dev;
GO
DROP USER agent_reader;
GO
USE master;
GO
DROP LOGIN agent_reader;
GO
  1. Unset MSSQL_CONNECTION_STRING and delete it from any shell profile where you saved it.
  2. Keep dab-config.json in version control if you may come back; it holds no secret.
  3. Confirm: the Step 1 membership query returns zero rows, claude mcp list no longer shows sqldev, and codex mcp list no longer shows it either.

The setup is done when three things are true: a schema question was answered through a tool call, the product 4 write was refused through the agent, and the INSERT was refused in SSMS under agent_reader. It has failed if any connection string in the chain names sa, your own login or a production server, or if any row changed without you running the statement.

Sources

  1. Rebecca Lewis (@sqlfingers) on X, “Build the Fence Before Letting in the AI: The Complete sp_Blitz Series”, Nov. 2, 2026
  2. Microsoft Learn, What is SQL MCP Server?
  3. Microsoft Learn, Quickstart: Use SQL MCP Server with Visual Studio Code locally
  4. Microsoft Learn, Initialize a config with the DAB CLI (dab init)
  5. Microsoft Learn, Database-level roles
  6. Microsoft Learn, CREATE LOGIN (Transact-SQL)
  7. Anthropic, Connect Claude Code to tools via MCP
  8. OpenAI, Model Context Protocol (Codex)
YOU'RE THROUGH THIS ONE.

Keep connecting the dots.

Back to the library

Many assistants.
One place to remember.

Bring your AI sessions, context, and usage together with Automater.

Meet Automater