MSSQL MCP Server
Compatible with Jaeger as a tracing backend via OpenTelemetry OTLP export.
Provides integration with OpenTelemetry for distributed tracing, allowing export of trace data to OTLP-compatible backends.
Exposes an interactive Swagger UI for the REST API via the mcpo proxy, allowing exploration and testing of MCP tools.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@MSSQL MCP Serverlist tables in the Sales database"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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 |
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 |
| Pydantic request/response models shared across the surface |
| Low-level pyodbc SQL Server introspection queries |
| 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.yamlQuick Start (Docker)
1. Configure environment
cp .env.example .envEdit .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-password2. Build and run
docker compose up --buildTwo 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/docsOr 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/activate2. 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-passwordOr copy .env.example to .env and load it manually.
4. Run
python -m app.mainThe 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 |
|
| Hostname or IP of the SQL Server instance |
|
| SQL Server port |
|
| Default database to connect to |
|
| SQL login username |
|
| SQL login password |
|
| Connection timeout (1-120) |
|
| Query timeout (1-300) |
Allowlist Policy
Variable | Default | Description |
|
| Enable database/schema/table allowlist enforcement |
| (none) | Absolute path to allowlist YAML file (required when enabled) |
Metadata Cache
Variable | Default | Description |
|
| TTL for cached introspection results (1-86400) |
|
| Max tables returned per listing call (1-10000) |
|
| Max columns returned per table (1-50000) |
|
| Max foreign keys returned (1-100000) |
Query Execution Limits
Variable | Default | Description |
|
| Default row limit when caller does not specify |
|
| 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 |
|
| Minimum score to classify as MEDIUM risk |
|
| Minimum score to classify as HIGH risk |
|
| Minimum score to classify as CRITICAL risk |
|
| Refuse SQL execution at or above this risk level ( |
OpenTelemetry Tracing
Variable | Default | Description |
|
| Enable OTLP trace export |
|
| OTLP gRPC exporter endpoint |
|
| Service name tag in traces |
|
| Include request/response JSON in trace spans |
|
| Max characters captured per body attribute (128-50000) |
|
| OTLP export timeout (100-120000) |
|
| 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:
- DailyKPIHierarchy rules:
If
databasesis empty, all databases are allowed.If
schemashas no entry for a database, all schemas in that database are allowed.If
tableshas no entry for adatabase.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.yamlEndpoints
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 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 |
|
| Yes | Target database |
|
| 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 |
|
| Yes | Target database |
|
| Yes | Target schema |
|
| 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 |
|
| Yes | SQL statement to validate |
|
| 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 |
|
|
| Normalized form of the SQL as parsed by sqlglot |
|
|
| Integer score (0 = safest) |
| List of risk signals found in the parse tree |
| Rules that caused |
| 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 |
|
| Yes | SQL SELECT statement to execute |
|
| No | Database context (overrides server default) |
|
| No | Max rows to return (default 100, bounded by |
|
| No | Row offset for pagination (default 0) |
|
| 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 |
|
| Yes | Tool name that triggered the event |
|
| Yes | Event classification string |
|
| No | Ordering sequence within a conversation |
|
| No | Correlates events in a single agent conversation |
|
| No | Correlates events across services |
|
| No | Pipeline stage (e.g. |
|
| No |
|
|
| No | Populated when |
|
| No | Elapsed time of the operation |
|
| No | ISO 8601 timestamp (defaults to server time) |
|
| 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 |
| Hierarchical catalog (databases -> schemas -> tables), system DBs excluded |
| Same catalog including system databases |
| Full column + FK definition for one table |
| Foreign key relationships for one table |
| Operational guidelines for safe use of this server |
| 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 |
| Cannot connect to SQL Server |
| Query or connection timed out |
| Request blocked by allowlist policy |
| Insufficient database permissions |
| Requested table does not exist |
| Requested schema does not exist |
| Malformed request or invalid SQL |
| 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/ -vTests cover:
SQL validation logic and risk scoring (direct
validate_sql_query()calls)run_sql_queryexecution guard (mocked metadata service)Audit event and feedback recording stubs
Allowlist policy enforcement
This server cannot be installed
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