Skip to main content
Glama
ahaugerud

sql-server-mcp

by ahaugerud

sql-server-mcp

A local MCP server for read-only SQL Server / MS Fabric queries. Runs as a single-user process over stdio, authenticated as you (delegated Entra ID identity, not a service principal) — one server process per tenant.

Prerequisites

  • Python 3.14+ and uv

  • Either the Azure CLI logged in to the target tenant (az login --tenant <tenant-id>), or just a browser available — the server falls back to an interactive browser login if there's no CLI session

  • An Entra ID account in the target tenant with read access to the target database (e.g. db_datareader)

Related MCP server: powerbi-mcp-proxy

Configuration

Each tenant gets its own set of environment variables:

Variable

Required

Description

MCP_SQL_SERVER_NAME

yes

SQL Server / Fabric SQL endpoint hostname, e.g. xyz-xyz.datawarehouse.fabric.microsoft.com

MCP_SQL_DATABASE_NAME

yes

Default database to connect to

MCP_AZURE_TENANT_ID

yes, unless using SQL login

The Entra ID tenant ID — pins auth to this directory even if your machine has other az login sessions active

MCP_SQL_USERNAME

no

SQL login username. If set, the server uses username + password authentication instead of Entra ID (and MCP_AZURE_TENANT_ID is not needed)

MCP_SQL_PASSWORD

with username

SQL login password. Must be set together with MCP_SQL_USERNAME

MCP_SQL_TRUST_SERVER_CERTIFICATE

no

Set to true to skip server certificate validation (needed for servers with self-signed or otherwise untrusted certificates, e.g. older on-prem SQL Server). Default: false

MCP_SQL_LEGACY_TLS

no

Set to true for old SQL Server hosts (e.g. SQL Server 2012) that reset the connection during the TLS handshake (TCP Provider: Error code 0x2746). Lowers OpenSSL's security level to 0 for this process (and its child processes), so it requires SQL login and is best avoided elsewhere. Default: false

MCP_MAX_ROWS

no

Max rows returned per tool call (default: 25)

Running it

The server is a uvx-runnable console script (sql-server-mcp), no manual install step needed - uvx builds an isolated environment for it on first run and reuses it afterwards:

uvx sql-server-mcp

To run from a local checkout instead (e.g. during development), point --from at the path:

uvx --from /path/to/sql-server-mcp sql-server-mcp

On startup it acquires a token (via az login if available, otherwise an interactive browser prompt) and runs over stdio. Database connectivity uses mssql-python, which bundles its own driver — no separate ODBC driver install is required.

Example MCP client config

One block per tenant:

{
  "mcpServers": {
    "tenant-a-sql": {
      "command": "uvx",
      "args": ["sql-server-mcp"],
      "env": {
        "MCP_SQL_SERVER_NAME": "tenant-a.datawarehouse.fabric.microsoft.com",
        "MCP_SQL_DATABASE_NAME": "wh_silver",
        "MCP_AZURE_TENANT_ID": "<tenant-a-tenant-id>"
      }
    },
    "tenant-b-sql": {
      "command": "uvx",
      "args": ["sql-server-mcp"],
      "env": {
        "MCP_SQL_SERVER_NAME": "tenant-b.datawarehouse.fabric.microsoft.com",
        "MCP_SQL_DATABASE_NAME": "wh_gold",
        "MCP_AZURE_TENANT_ID": "<tenant-b-tenant-id>"
      }
    }
  }
}

Tools

  • query(sql) — run a single read-only SELECT. Table references must be fully qualified as database.schema.table; there's no mutable "active database". Returns at most MCP_MAX_ROWS rows, as a Markdown table.

  • list_databases() — list user databases with status/metadata.

  • search_schema(target, name, databases?, schema?, use_wildcard?) — search table or column metadata via INFORMATION_SCHEMA across one or more databases (all of them if databases is omitted).

  • export_query(sql, path, format?) — run a single read-only SELECT and write the full, uncapped result to a local Parquet or CSV file instead of returning it. For large results or when the output feeds into other local tools (pandas, DuckDB, etc.).

Development

uv run pytest          # unit tests, no network/DB/Azure required
uv run python scripts/manual_smoke_test.py   # real end-to-end check against a live database

Available Tools

4 tools
export_queryA

Execute a single read-only SELECT statement and write the full, uncapped result to a local Parquet or CSV file instead of returning it. Use this instead of query for large results or when the output will be processed by other local tools.

ParametersJSON Schema
NameRequiredDescriptionDefault
sqlYes
pathYes
formatNoparquet

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the behavioral burden well: read-only, single statement, uncapped result, and that the result is written to a local file rather than returned. It stops short of stating whether an existing file at `path` is overwritten, whether directories are created, or what happens on write failure — meaningful gaps for a file-writing tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, both load-bearing: the first defines the operation and its scope, the second handles routing. The key differentiator (file output, uncapped) is front-loaded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return-value explanation is unnecessary, and the description covers the unconventional part — that output goes to a file. Only file-collision/overwrite semantics and path expectations are missing, which is a modest gap given the tool's simplicity.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate. It does partially: `sql` is constrained to a single read-only SELECT, and the output is a 'local' file in Parquet or CSV. But it never clarifies default format handling, whether `path` must be an existing directory, or expected path style, leaving the non-`sql` parameters thin.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb chain (execute a SELECT, write result to file) and an explicit scope ('single read-only SELECT', 'full, uncapped result'). It also names the sibling tool `query` and the condition that distinguishes the two, so the agent can route without opening either schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly tells the agent to use this instead of `query` for large results or when output feeds other local tools, giving both the when and the alternative. No inference is required.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_databasesA

List all user databases with status and metadata. Returns at most 25 rows.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.5/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations at all, the description carries the full burden, and it does disclose one valuable behavioral trait: results are capped at 25 rows. However, it says nothing about whether that cap means silent truncation, whether ordering is stable, or what access level is required, so a mutation-free read is only implied by 'List'.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, zero filler, with the resource scope leading and the result-size limit front-loaded immediately after. Nothing is repeated from the schema or title.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not be described, and with zero parameters there is little else to specify. The one remaining gap is that the 25-row cap is stated without explaining what an agent should do when it is hit, but given the tool has no pagination parameters this is a minor omission.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so there is nothing for the description to disambiguate beyond what the empty schema already shows. Baseline 4 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('List all user databases') plus the payload ('status and metadata'), and the 'user' qualifier signals a scope boundary. It does not name or distinguish itself from siblings like search_schema or query, so an agent must infer that this is the inventory entry point rather than a content-retrieval tool.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

There is no explicit when-to-use guidance and no alternatives named; the agent is not told to call this before search_schema or query, nor when to prefer those instead. Usage is only implied by the word 'List'.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

queryA

Execute a single read-only SELECT statement. Returns at most 25 rows. Table references must be fully qualified as database.schema.table - there is no mutable 'active database'.

ParametersJSON Schema
NameRequiredDescriptionDefault
sqlYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.9/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does disclose two operationally critical traits: results are truncated at 25 rows, and there is no mutable 'active database' so every table must be fully qualified. It omits timeout, permission, and error behavior, keeping it below a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three short sentences, front-loaded with the operation and its limits, then the qualification rule. Nothing is padded or redundant.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a one-parameter tool with an output schema covering return values, the description supplies the essential constraints (read-only, single statement, row cap, qualification rule). Minor gaps remain around error/failure behavior, but nothing an agent needs to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The single `sql` parameter has 0% schema description coverage, so the description must compensate. It does meaningfully: the value must be one read-only SELECT and must reference tables as database.schema.table, which is syntax the schema itself does not convey.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Execute a single read-only SELECT statement') with a clear read-only constraint and row cap. It implicitly separates this from export_query by capping results at 25 rows, but never explicitly names the sibling or contrasts with it.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The read-only/single-statement/25-row constraints imply when this tool is inappropriate (large extracts), but the agent must infer that export_query is the alternative. No explicit when-to-use vs when-not-to-use routing is provided.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_schemaB

Search table or column metadata across databases via INFORMATION_SCHEMA. Omit databases to search every database on the server. Returns at most 25 rows.

ParametersJSON Schema
NameRequiredDescriptionDefault
nameYes
schemaNo
targetYes
databasesNo
use_wildcardNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

B3.1/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden, and it does disclose one genuinely important trait: results are capped at 25 rows. However, it does not say whether that cap is truncation (with more results available) or a hard limit, nor does it confirm read-only behavior beyond the implication of 'Search' and INFORMATION_SCHEMA.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three short sentences, front-loaded with what the tool does, followed by the scoping behavior and the result cap. Every sentence adds information and none is redundant.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not be described. But for a five-parameter search tool with zero schema coverage, leaving `use_wildcard` and `schema` unexplained is a real gap an agent must guess around.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0% across five parameters, so the description must compensate. It explains `databases` (omit = all databases) and loosely maps to `target` and `name`, but `schema` and especially `use_wildcard` (defaulting to true, which materially changes matching behavior) are left entirely undocumented.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: searching table/column metadata via INFORMATION_SCHEMA. This is clearly distinguishable from siblings like query or export_query, which operate on data rather than metadata. It falls short of a 5 only because it never names the sibling it displaces.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

'Omit `databases` to search every database on the server' gives one operational hint, but there is no guidance on when to choose this over query or list_databases. An agent must infer that this is the metadata-discovery tool rather than the data-retrieval tool.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 4 tool updatesv0.5.2
    • First observedexport_query
    • First observedlist_databases
    • First observedquery
    • First observedsearch_schema

TDQS

A3.8/5.0

Scored across 4 tools

Disambiguation5/5

Each tool has a clearly distinct purpose: `query` for capped inline SELECTs, `export_query` for uncapped file output, `list_databases` for database enumeration, and `search_schema` for metadata lookup. The `query` vs `export_query` overlap is explicitly resolved by the description telling agents to prefer `export_query` for large results.

Naming Consistency4/5

Three tools follow a clear verb_noun pattern (`list_databases`, `search_schema`, `export_query`), while `query` is a bare verb/noun that slightly breaks the pattern. The deviation is minor and still readable in context.

Tool Count5/5

Four tools is well-scoped for a read-only SQL server interface, with each tool covering a distinct essential workflow: querying, exporting, listing databases, and searching schema metadata. Nothing feels redundant or missing at the count level.

Completeness4/5

The surface covers core read-only workflows well, including capped queries, large exports, database listing, and schema discovery via INFORMATION_SCHEMA. Minor gaps exist around dedicated table listing or describe-table convenience tools, but `search_schema` provides a workable path for those needs.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    F
    maintenance
    Read-only MCP server for SQL databases (SQL Server, Postgres, SQLite) with multi-server support and three-layer safety using AST validation and linting.
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    A self-hosted MCP server that proxies MCP clients to Power BI, using your own Entra tenant and Azure subscription. It enables DAX queries, workspace listing, and dataset management while preserving user-specific row-level security.
    2
    MIT