Skip to main content
Glama
mortada7-11

MSSQL MCP Server

by mortada7-11

MSSQL MCP Server — Standalone

A self-contained Model Context Protocol (MCP) server that exposes SQL Server metadata and read-only query execution as a structured HTTP API. It is designed to be consumed by AI orchestration layers (e.g. LangGraph, custom agents) that need reliable, safety-gated access to a SQL Server database.


Contents


Related MCP server: MCP SQL Server

What It Does

The MCP server acts as a safe, policy-enforced gateway between an AI agent and a SQL Server instance. It provides:

Capability

Details

Schema introspection

List databases, schemas, tables, columns, and foreign keys

Read-only SQL execution

Run SELECT-only queries with bounded pagination

SQL safety validation

Deterministic risk scoring before any query is executed

Allowlist policy

Restrict accessible databases/schemas/tables via YAML config

Audit & feedback persistence

Record structured audit events and user feedback into SQL Server

Metadata caching

In-memory TTL cache reduces redundant introspection roundtrips

OpenTelemetry tracing

Optional OTLP export compatible with Arize Phoenix and Jaeger


Architecture

AI Agent / Orchestrator
        |
        |  HTTP (JSON)  POST /list_databases, etc.
        v
+----------------------------------+
|         mcpo REST proxy          |
|         (port 8081)              |
|                                  |
|  OpenAPI/REST  -->  MCP client   |
+----------------------------------+
        |
        |  MCP over SSE
        |  GET  /sse
        |  POST /messages/
        v
+----------------------------------+
|      FastMCP Server              |
|      (port 8082, internal)       |
|                                  |
|  10 tools  -->  MetadataService  |
|               +-- schema-        |
|               |   introspection  |
|               +-- sql-validator  |
|               +-- policy layer   |
|                                  |
|  6 MCP resources (schema, docs)  |
|  Settings loaded from env vars   |
+----------------------------------+
        |
        |  pyodbc / ODBC Driver 18
        v
   SQL Server (any edition)

mcpo is a lightweight proxy that wraps any MCP server and exposes its tools as a standard OpenAPI/REST API. This means you can call the server with plain HTTP POST requests and browse an interactive Swagger UI at http://localhost:8081/docs no MCP client library required.

Three internal packages are bundled alongside the server:

Package

Role

packages/shared-types

Pydantic request/response models shared across the surface

packages/schema-introspection

Low-level pyodbc SQL Server introspection queries

packages/sql-validator

Deterministic SQL risk scorer using sqlglot parse trees


Project Structure

mssql-mcp-server-standalone/
+-- app/
|   +-- main.py              # FastMCP server, all tools and resources, entry point
|   +-- config.py            # McpSettings (pydantic-settings, env prefix MCP_)
|   +-- metadata_service.py  # Business logic: wraps introspection + policy + cache
|   +-- metadata_cache.py    # In-memory TTL cache
|   +-- policy.py            # Allowlist policy loader and enforcement
|   +-- tracing.py           # OpenTelemetry setup
+-- packages/
|   +-- shared-types/        # Shared Pydantic models
|   +-- schema-introspection/# pyodbc-based SQL Server introspection
|   +-- sql-validator/       # sqlglot-based SQL risk scorer
+-- tests/
|   +-- test_validator_tools.py
+-- Dockerfile
+-- docker-compose.yml
+-- pyproject.toml
+-- .env.example
+-- allowlist-policy.example.yaml

Quick Start (Docker)

1. Configure environment

cp .env.example .env

Edit .env and set at minimum:

MCP_SQLSERVER_HOST=your-sql-server-host
MCP_SQLSERVER_DATABASE=YourDatabase
MCP_SQLSERVER_USER=readonly_user
MCP_SQLSERVER_PASSWORD=your-password

2. Build and run

docker compose up --build

Two containers start:

  • fastmcp-app — the FastMCP server on internal port 8082

  • mcpo-proxy — the REST proxy on port 8081 (public)

mcpo-proxy waits for fastmcp-app to pass its healthcheck before starting.

3. Verify

Browse the interactive Swagger UI:

http://localhost:8081/docs

Or call the health check tool directly:

curl -s -X POST http://localhost:8081/health_check \
  -H "Content-Type: application/json" \
  -d '{}'

Quick Start (Local Python)

Requires Python 3.11+ and Microsoft ODBC Driver 18 for SQL Server.

Note: Running locally starts the FastMCP server only (port 8082). To get the OpenAPI/REST layer, run mcpo separately pointing at http://localhost:8082/sse.

1. Create and activate a virtual environment

python -m venv .venv
# Windows
.venv\Scripts\activate
# Linux / macOS
source .venv/bin/activate

2. Install packages

pip install -e packages/shared-types -e packages/schema-introspection -e packages/sql-validator -e .

3. Set environment variables

# Windows PowerShell
$env:MCP_SQLSERVER_HOST = "localhost"
$env:MCP_SQLSERVER_DATABASE = "YourDatabase"
$env:MCP_SQLSERVER_USER = "readonly_user"
$env:MCP_SQLSERVER_PASSWORD = "your-password"

# Linux / macOS
export MCP_SQLSERVER_HOST=localhost
export MCP_SQLSERVER_DATABASE=YourDatabase
export MCP_SQLSERVER_USER=readonly_user
export MCP_SQLSERVER_PASSWORD=your-password

Or copy .env.example to .env and load it manually.

4. Run

python -m app.main

The server listens on 0.0.0.0:8082 using SSE transport (GET /sse, POST /messages/).


Configuration Reference

All settings use the MCP_ environment variable prefix.

SQL Server Connection

Variable

Default

Description

MCP_SQLSERVER_HOST

sqlserver-host

Hostname or IP of the SQL Server instance

MCP_SQLSERVER_PORT

1433

SQL Server port

MCP_SQLSERVER_DATABASE

master

Default database to connect to

MCP_SQLSERVER_USER

readonly_user

SQL login username

MCP_SQLSERVER_PASSWORD

change-me

SQL login password

MCP_SQLSERVER_CONNECT_TIMEOUT_SECONDS

10

Connection timeout (1-120)

MCP_SQLSERVER_QUERY_TIMEOUT_SECONDS

30

Query timeout (1-300)

Allowlist Policy

Variable

Default

Description

MCP_ALLOWLIST_ENABLED

false

Enable database/schema/table allowlist enforcement

MCP_ALLOWLIST_POLICY_PATH

(none)

Absolute path to allowlist YAML file (required when enabled)

Metadata Cache

Variable

Default

Description

MCP_METADATA_CACHE_TTL_SECONDS

300

TTL for cached introspection results (1-86400)

MCP_METADATA_MAX_TABLES

200

Max tables returned per listing call (1-10000)

MCP_METADATA_MAX_COLUMNS

1000

Max columns returned per table (1-50000)

MCP_METADATA_MAX_FOREIGN_KEYS

2000

Max foreign keys returned (1-100000)

Query Execution Limits

Variable

Default

Description

MCP_EXECUTION_DEFAULT_ROW_LIMIT

100

Default row limit when caller does not specify

MCP_EXECUTION_MAX_ROW_LIMIT

1000

Hard cap on rows returned per query (1-10000)

SQL Validator Risk Thresholds

Risk scores are integer values computed from the sqlglot parse tree. Thresholds map scores to risk levels:

Variable

Default

Description

MCP_VALIDATOR_MEDIUM_MIN_SCORE

3

Minimum score to classify as MEDIUM risk

MCP_VALIDATOR_HIGH_MIN_SCORE

5

Minimum score to classify as HIGH risk

MCP_VALIDATOR_CRITICAL_MIN_SCORE

8

Minimum score to classify as CRITICAL risk

MCP_VALIDATOR_REFUSE_AT_OR_ABOVE

critical

Refuse SQL execution at or above this risk level (low|medium|high|critical)

OpenTelemetry Tracing

Variable

Default

Description

MCP_TRACING_ENABLED

false

Enable OTLP trace export

MCP_TRACING_OTLP_ENDPOINT

http://phoenix:4317

OTLP gRPC exporter endpoint

MCP_TRACING_SERVICE_NAME

mssql-mcp-server

Service name tag in traces

MCP_TRACING_CAPTURE_HTTP_BODIES

false

Include request/response JSON in trace spans

MCP_TRACING_BODY_MAX_CHARACTERS

4096

Max characters captured per body attribute (128-50000)

MCP_TRACING_EXPORT_TIMEOUT_MILLISECONDS

10000

OTLP export timeout (100-120000)

MCP_TRACING_SAMPLE_RATIO

1.0

Trace sampling ratio (0.0-1.0)


Allowlist Policy

When MCP_ALLOWLIST_ENABLED=true, the server loads a YAML file that restricts which databases, schemas, and tables the server will expose. Any request targeting an object outside the policy returns an error.

Set MCP_ALLOWLIST_POLICY_PATH to the absolute path of your policy file.

Example policy file (allowlist-policy.example.yaml):

databases:
  - SalesDW
  - Reporting

schemas:
  SalesDW:
    - dbo
    - analytics
  Reporting:
    - dbo

tables:
  SalesDW.dbo:
    - FactSales
    - DimCustomer
  Reporting.dbo:
    - DailyKPI

Hierarchy rules:

  • If databases is empty, all databases are allowed.

  • If schemas has no entry for a database, all schemas in that database are allowed.

  • If tables has no entry for a database.schema, all tables in that schema are allowed.

When mounting the file in Docker:

# docker-compose.yml
volumes:
  - ./allowlist-policy.yaml:/etc/mcp/allowlist-policy.yaml:ro
# .env
MCP_ALLOWLIST_ENABLED=true
MCP_ALLOWLIST_POLICY_PATH=/etc/mcp/allowlist-policy.yaml

Endpoints

All tool endpoints are exposed by the mcpo proxy on port 8081 as POST /<tool_name> with a JSON body. Browse http://localhost:8081/docs for the interactive Swagger UI.

The FastMCP server itself listens on internal port 8082 (SSE transport) and is not directly reachable from outside the Docker network.


POST /health_check

Returns server configuration summary and cache statistics. No request body required.

Response (200)

{
  "status": "ok",
  "service": "mssql-mcp-server",
  "sqlserver_host": "localhost",
  "sqlserver_port": 1433,
  "sqlserver_database": "master",
  "tracing_enabled": false,
  "metadata_cache_ttl_seconds": 300,
  "metadata_cache_entries": 0,
  "validator_refuse_at_or_above": "critical"
}

POST /list_databases

Lists all accessible databases on the SQL Server instance.

Request body

{ "include_system": false }

Field

Type

Default

Description

include_system

bool

false

Include system databases (master, model, msdb, tempdb)

Response (200)

{
  "databases": [
    { "name": "SalesDW" },
    { "name": "Reporting" }
  ]
}

POST /list_tables

Lists tables in a database, optionally filtered by schema.

Request body

{
  "database": "SalesDW",
  "schema_name": "dbo"
}

Field

Type

Required

Description

database

string

Yes

Target database

schema_name

string

No

Filter to this schema

Response (200)

{
  "tables": [
    { "schema": "dbo", "name": "FactSales" }
  ]
}

POST /describe_table

Returns column metadata and foreign keys for a single table.

Request body

{
  "database": "SalesDW",
  "schema_name": "dbo",
  "table": "FactSales"
}

Field

Type

Required

Description

database

string

Yes

Target database

schema_name

string

Yes

Target schema

table

string

Yes

Target table

Response (200)

{
  "table": {
    "database": "SalesDW",
    "schema": "dbo",
    "name": "FactSales"
  },
  "columns": [
    {
      "name": "SalesID",
      "data_type": "int",
      "is_nullable": false,
      "is_primary_key": true,
      "max_length": null,
      "precision": 10,
      "scale": 0
    }
  ],
  "foreign_keys": [
    {
      "constraint_name": "FK_FactSales_DimCustomer",
      "column": "CustomerID",
      "referenced_database": "SalesDW",
      "referenced_schema": "dbo",
      "referenced_table": "DimCustomer",
      "referenced_column": "CustomerID"
    }
  ]
}

POST /get_foreign_keys

Returns foreign key constraints for a single table.

Request body

{
  "database": "SalesDW",
  "schema_name": "dbo",
  "table": "FactSales"
}

Response (200)

{
  "foreign_keys": [
    {
      "constraint_name": "FK_FactSales_DimCustomer",
      "column": "CustomerID",
      "referenced_database": "SalesDW",
      "referenced_schema": "dbo",
      "referenced_table": "DimCustomer",
      "referenced_column": "CustomerID"
    }
  ]
}

POST /validate_sql

Validates a SQL statement and returns a deterministic safety and risk analysis. Does not execute the query.

Request body

{
  "sql": "SELECT * FROM dbo.FactSales WHERE SalesDate > '2024-01-01'",
  "policy": null
}

Field

Type

Required

Description

sql

string

Yes

SQL statement to validate

policy

ValidatorPolicy

No

Override validator risk thresholds for this call

Response (200)

{
  "is_valid": true,
  "normalized_sql": "SELECT * FROM dbo.FactSales WHERE SalesDate > '2024-01-01'",
  "risk_level": "low",
  "risk_score": 1,
  "risk_factors": [],
  "blocked_rules": [],
  "refusal_reason": null
}

Field

Description

is_valid

true if the query is safe to execute under the current policy

normalized_sql

Normalized form of the SQL as parsed by sqlglot

risk_level

low | medium | high | critical

risk_score

Integer score (0 = safest)

risk_factors

List of risk signals found in the parse tree

blocked_rules

Rules that caused is_valid=false

refusal_reason

Human-readable reason if the query was refused


POST /estimate_query_risk

Identical to validate_sql — returns the same risk scoring. This alias is provided for semantic clarity in agent workflows where you want risk estimation as a separate reasoning step from the validation gate.

Request / Response: Same as POST /validate_sql.


POST /run_sql_query

Executes a read-only SQL query. The query is validated first; execution is denied if it fails the safety policy.

Request body

{
  "sql": "SELECT TOP 10 * FROM dbo.FactSales",
  "database": "SalesDW",
  "row_limit": 100,
  "offset": 0,
  "policy": null
}

Field

Type

Required

Description

sql

string

Yes

SQL SELECT statement to execute

database

string

No

Database context (overrides server default)

row_limit

int

No

Max rows to return (default 100, bounded by MCP_EXECUTION_MAX_ROW_LIMIT)

offset

int

No

Row offset for pagination (default 0)

policy

ValidatorPolicy

No

Override validator thresholds for this call

Response (200)

{
  "columns": ["SalesID", "CustomerID", "SalesDate", "Amount"],
  "rows": [
    { "SalesID": 1, "CustomerID": 42, "SalesDate": "2024-03-15", "Amount": 199.99 }
  ],
  "returned_rows": 1,
  "row_limit": 100,
  "offset": 0,
  "has_more": false,
  "next_offset": null,
  "execution_time_ms": 12
}

Important: Only SELECT statements pass validation. Any INSERT, UPDATE, DELETE, DROP, or other mutating statement will be refused.


POST /record_audit_event

Persists a structured audit event to the SQL Server audit store.

Request body

{
  "endpoint": "run_sql_query",
  "event_type": "QUERY_EXECUTED",
  "sequence_no": 1,
  "conversation_id": "conv-abc123",
  "correlation_id": "req-xyz456",
  "stage_name": "execution",
  "decision_type": "ALLOW",
  "refusal_reason": null,
  "duration_ms": 12,
  "event_utc": "2024-03-15T10:30:00Z",
  "payload": {}
}

Field

Type

Required

Description

endpoint

string

Yes

Tool name that triggered the event

event_type

string

Yes

Event classification string

sequence_no

int

No

Ordering sequence within a conversation

conversation_id

string

No

Correlates events in a single agent conversation

correlation_id

string

No

Correlates events across services

stage_name

string

No

Pipeline stage (e.g. validation, execution)

decision_type

string

No

ALLOW or DENY

refusal_reason

string

No

Populated when decision_type=DENY

duration_ms

int

No

Elapsed time of the operation

event_utc

string

No

ISO 8601 timestamp (defaults to server time)

payload

object

No

Arbitrary extra data

Response (200)

{
  "accepted": true,
  "event_id": 42
}

POST /record_feedback

Persists user feedback for a SQL copilot interaction.

Request body

{
  "conversation_id": "conv-abc123",
  "sql": "SELECT * FROM dbo.FactSales",
  "label": "correct",
  "rating": 5,
  "comments": "Exactly what I needed."
}

Response (200)

{
  "accepted": true,
  "feedback_id": 7
}

MCP Resources

In addition to tools, the server exposes 6 MCP resources accessible to MCP clients that connect directly to the SSE endpoint (http://localhost:8082/sse). Resources are read-only, cacheable views and are not exposed as REST endpoints through mcpo.

Resource URI

Description

schema://index

Hierarchical catalog (databases -> schemas -> tables), system DBs excluded

schema://index/with-system

Same catalog including system databases

schema://table/{database}/{schema_name}/{table_name}

Full column + FK definition for one table

schema://relationships/{database}/{schema_name}/{table_name}

Foreign key relationships for one table

mcp://guidelines

Operational guidelines for safe use of this server

mcp://policy

Snapshot of the currently active allowlist policy


Error Handling

When a tool encounters an error (SQL Server connection failure, policy violation, object not found, etc.), it raises a runtime error with a structured code prefix. mcpo propagates the error message as a string in the response.

Common error codes embedded in error messages:

Code

Description

CONNECTION_FAILED

Cannot connect to SQL Server

TIMEOUT

Query or connection timed out

POLICY_VIOLATION

Request blocked by allowlist policy

ACCESS_DENIED

Insufficient database permissions

TABLE_NOT_FOUND

Requested table does not exist

SCHEMA_NOT_FOUND

Requested schema does not exist

INVALID_REQUEST

Malformed request or invalid SQL

UNKNOWN_ERROR

Unexpected internal error

SQL safety refusals (from validate_sql / run_sql_query) are returned inline in the response body with is_valid: false and a refusal_reason string — they are not errors at the HTTP level.


OpenTelemetry Tracing

When MCP_TRACING_ENABLED=true, the server exports traces via OTLP/gRPC to MCP_TRACING_OTLP_ENDPOINT. Every tool call becomes a span under the service name MCP_TRACING_SERVICE_NAME.

Compatible receivers include:

  • Arize Phoenix (default endpoint: http://phoenix:4317)

  • Jaeger (http://jaeger:4317)

  • OpenTelemetry Collector


Running Tests

The test suite uses pytest with direct unit tests — no live SQL Server or MCP client required.

pip install pytest
pytest tests/ packages/sql-validator/tests/ -v

Tests cover:

  • SQL validation logic and risk scoring (direct validate_sql_query() calls)

  • run_sql_query execution guard (mocked metadata service)

  • Audit event and feedback recording stubs

  • Allowlist policy enforcement

Related MCP Connectors

Related MCP Servers

  • A
    license
    B
    quality
    D
    maintenance
    A Model Context Protocol server that enables secure interaction with Microsoft SQL Server databases, allowing AI assistants to list tables, read data, and execute SQL queries through a controlled interface.
    1
    388
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    A Model Context Protocol server that allows Large Language Models like Claude to execute SQL queries, explore database schemas, and maintain persistent connections to SQL Server databases.
    -
  • A
    license
    A
    quality
    C
    maintenance
    Read-only Model Context Protocol server for Microsoft SQL Server, enabling safe schema discovery, profiling, and querying with zero risk of data modification.
    14
    549 npm
    8
    MIT
  • 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