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


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

-
license - not tested
-
quality - not tested
-
maintenance - not tested

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mortada7-11/mssql-mcpo-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server