A Model Context Protocol (MCP) server that provides safe, read-optimized access to SQL Server databases for AI assistants like Claude and Cursor.
- Safe Query Execution: Read-only access (SELECT) with automatic safety validation
- Schema Inspection: Retrieve database schemas, table definitions, and metadata
- Performance Analysis: Get execution plans, index suggestions, and performance metrics
- Security:
- Prevents SQL injection and dangerous commands
- Configurable row limits and timeouts
- Comprehensive audit logging for all operations
- Cloud Ready: Dockerized and ready for AWS Fargate deployment
- Multi-Database Support: Connect to multiple databases simultaneously
The project uses a centralized configuration approach with pydantic-settings:
- Environment Variables: All settings configurable via env vars
.envFiles: Support for.envand.env.localfiles- Settings Module: Single source of truth in
config/settings.py - No Hardcoded Values: All defaults are configurable
Security Note: Tools that could execute write operations (UPDATE, DELETE, INSERT) are disabled by default. This includes
execute_stored_procedureandexecute_sql_script_transactional. These are commented out in the code and can be re-enabled if proper validation and authentication are implemented.
execute_sql_query- Execute SELECT queries with safety validation2.DISABLED (can perform writes)execute_stored_procedure- Execute stored procedures3.DISABLED (can perform writes)execute_sql_script_transactional- Execute SQL scripts transactionallyget_execution_plan- Get query execution planparse_sql_script- Parse and validate SQL syntax without executing (safe)
get_slow_queries- Identify slow-running queriesget_query_statistics- Get query performance statisticsget_wait_statistics- Analyze database wait statisticsget_cache_hit_ratio- Monitor buffer cache efficiencyget_index_fragmentation- Check index fragmentation levels
get_schema- Get database schema informationget_table_metadata- Get detailed table metadataget_index_metadata_for_table- Get index informationget_object_definition- Get object definitions (views, procedures)get_table_size_statistics- Get table size and space usageget_row_count_for_table- Get row counts (approximate or exact)
get_index_usage_statistics- Analyze index usage patternsget_missing_index_suggestions- Get missing index recommendations
get_current_blocking_snapshot- Identify blocking sessionsget_recent_deadlocks- Retrieve recent deadlock informationget_sql_agent_jobs- List SQL Agent jobs
get_sql_agent_job_history- Get job execution historyget_recent_failed_jobs- Find recently failed jobsget_principals_and_roles- List database principals and rolesget_permissions_for_principal- Get permissions for a principal
get_recent_security_changes- Track security-related changesget_recent_schema_changes- Track schema changes (DDL)sample_query_results- Execute query and return sample resultssearch_objects- Search for database objects by name pattern
The project uses a simple, environment-based configuration system:
- Default Values - Defined in
config/settings.py - Local Development - Override defaults in
.env.local(tracked in git) - Production - Override via environment variables (Docker, Fargate, etc.)
Priority (highest to lowest):
Environment Variables > .env.local > Default Values in Settings
-
Review
.env.local:cat .env.local
This file contains safe defaults for local development. Customize as needed.
-
Start Docker SQL Server:
docker-compose up -d
-
Install dependencies:
python -m venv .venv source .venv/bin/activate # On Windows: .venv\Scripts\activate pip install -r requirements.txt
-
Run the server:
python server.py
| File | Purpose | Tracked in Git? |
|---|---|---|
config/settings.py |
Default values and validation | ✅ Yes |
.env.local |
Local development overrides | ✅ Yes (safe defaults only) |
| Environment variables | Production overrides | ❌ No (set in deployment) |
Important: Never commit real passwords or secrets! The .env.local file in git contains only safe defaults for local Docker.
Edit Claude Desktop config file:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"sql-server": {
"command": "/Users/shahar.wiener/repos/mcp-sql-server/.venv/bin/python",
"args": ["/Users/shahar.wiener/repos/mcp-sql-server/server.py"],
"env": {
"DB_CONN_LOCALDB": "Driver={ODBC Driver 18 for SQL Server};Server=127.0.0.1,1433;Database=LocalDB;UID=sa;PWD=YourStrong@Passw0rd;TrustServerCertificate=yes;Encrypt=yes;LoginTimeout=30"
}
}
}
}{
"mcpServers": {
"sql-server": {
"command": "/Users/shahar.wiener/repos/mcp-sql-server/.venv/bin/python",
"args": ["/Users/shahar.wiener/repos/mcp-sql-server/server.py"],
"env": {
"DB_CONN_MOBYDOM5": "Driver={ODBC Driver 18 for SQL Server};Server=127.0.0.1;Database=mobydom5;Uid=sa;Pwd=YourStrong!Passw0rd;TrustServerCertificate=yes;",
"DB_CONN_MCPAY": "Driver={ODBC Driver 18 for SQL Server};Server=127.0.0.1;Database=mcpay;Uid=sa;Pwd=YourStrong!Passw0rd;TrustServerCertificate=yes;",
"DB_CONN_BILLING": "Driver={ODBC Driver 18 for SQL Server};Server=127.0.0.1;Database=billing;Uid=sa;Pwd=YourStrong!Passw0rd;TrustServerCertificate=yes;"
}
}
}
}Key Points:
- ✅ Use absolute paths for
commandandargs - ✅ For multiple databases, use
DB_CONN_<NAME>pattern - ✅ Database name is derived from env var name (e.g.,
DB_CONN_MOBYDOM5→mobydom5)
- Quit Claude Desktop completely
- Relaunch Claude Desktop
- Look for the 🔌 (plug) icon in the bottom-right corner
- Click to enable "sql-server"
In Claude Desktop, try:
- "Show me all tables in the mobydom5 database"
- "Query the Users table and show me 5 records"
- "What's the structure of the accounts table?"
To use this MCP server with Cursor:
-
Open Cursor Settings → Features → MCP
-
Add New MCP Server:
- Name:
sql-server - Type:
SSE(if running in Docker/Cloud) orCommand(if local)
Option A: Local (Command)
- Command:
python /path/to/mcp-sql-server/server.py
Option B: Remote / Docker (SSE)
- URL:
http://localhost:9303/sse(or your cloud URL)
- Name:
# Build image
docker build -t mcp-sql-server .
# Tag for ECR
docker tag mcp-sql-server:latest ACCOUNT_ID.dkr.ecr.REGION.amazonaws.com/mcp-sql-server:latest
# Push to ECR
aws ecr get-login-password --region REGION | docker login --username AWS --password-stdin ACCOUNT_ID.dkr.ecr.REGION.amazonaws.com
docker push ACCOUNT_ID.dkr.ecr.REGION.amazonaws.com/mcp-sql-server:latestUse the provided task-definition.json as a template. Key configurations:
- Task Role: Ensure it has permissions for SSM Parameter Store (if using)
- Environment Variables: Set
PANGO_ENV=Prd,MCP_TRANSPORT=sse,MCP_HOST=0.0.0.0 - Secrets: Store
DB_CONNECTION_STRINGin AWS Secrets Manager or SSM Parameter Store - Networking: Deploy in a private subnet with access to your SQL Server
aws ecs create-service \
--cluster my-cluster \
--service-name mcp-sql-server \
--task-definition mcp-sql-server-task \
--desired-count 1 \
--launch-type FARGATE \
--network-configuration "awsvpcConfiguration={subnets=[subnet-xxx],securityGroups=[sg-xxx]}"If exposing via ALB:
- Target port:
9303 - Health check path:
/sse - Protocol: HTTP
| Variable | Description | Default | Required |
|---|---|---|---|
PANGO_ENV |
Environment name (Int, Stg, Prd) | Int |
No |
LOG_LEVEL |
Logging level | INFO |
No |
MCP_TRANSPORT |
stdio or sse |
stdio |
No |
MCP_HOST |
Host to bind to | 127.0.0.1 |
No |
MCP_PORT |
Port for SSE server | 9303 |
No |
DB_CONN_<NAME> |
Database connection strings | - | Yes* |
DB_NAME |
(Legacy) Default database name | - | No |
DB_CONNECTION_STRING |
(Legacy) ODBC Connection String | - | No |
MAX_QUERY_LENGTH |
Max query length | 10000 |
No |
MAX_RESULT_ROWS |
Max rows to return | 10000 |
No |
DEFAULT_CONNECTION_TIMEOUT |
Connection timeout (s) | 30 |
No |
DEFAULT_COMMAND_TIMEOUT |
Query timeout (s) | 300 |
No |
AUDIT_LOG_ENABLED |
Enable audit logging | True |
No |
AUDIT_LOG_PATH |
Audit log directory | ./logs/audit/ |
No |
AWS_REGION |
AWS Region for SSM | us-east-1 |
No |
SSM_PARAMETER_NAME |
SSM parameter for config | - | No |
* At least one DB_CONN_<NAME> is required. Legacy DB_NAME + DB_CONNECTION_STRING is also supported for backward compatibility.
- Local Development: Use
127.0.0.1(localhost only) - Docker/Cloud: Use
0.0.0.0(all interfaces) - Production: Set via environment variable, never hardcode
- Default:
9303 - Customizable: Set
MCP_PORTenvironment variable - Docker: Map container port to host port (e.g.,
-p 8080:9303)
The server enforces strict read-only access through multiple layers:
- Query Validation: All SQL queries are validated to ensure they only contain SELECT statements
- Disabled Write Tools: The following tools are disabled by default because they could execute write operations:
execute_stored_procedure- Stored procedures can contain UPDATE/DELETE/INSERTexecute_sql_script_transactional- Scripts could contain destructive operations
- SQL Injection Protection: Parameterized queries and input validation prevent injection attacks
- Dangerous Pattern Blocking: Blocks patterns like
xp_cmdshell,OPENROWSET, etc. - Safe Parsing:
parse_sql_scriptusesSET PARSEONLY ONto validate syntax without execution
If you need to enable write operations (e.g., for administrative tools):
- Uncomment the tools in
server.pyandservices/infrastructure/db_connection_service.py - Add validation to inspect stored procedure definitions before execution
- Implement authentication to restrict access to trusted users only
- Enable comprehensive audit logging to track all write operations
- Use dedicated write-only database users with minimal permissions
- Audit Logs: All queries are logged to
logs/audit/(if enabled) for compliance - Secrets: Never commit
.envfiles. Use AWS Secrets Manager or SSM Parameter Store in production - SQL Injection Protection: Parameterized queries and input validation
- Connection Security: Enforce encrypted connections in production
| Issue | Solution |
|---|---|
| Server not appearing in Claude Desktop | • Verify absolute paths in config • Restart Claude Desktop • Check Claude Desktop logs |
| Connection failures | • Verify database connectivity • Check connection string format • Test with SQL client |
| Wrong host/port | • Check MCP_HOST and MCP_PORT env vars• Use 0.0.0.0 for Docker/Cloud |
| Multiple databases not working | • Use DB_CONN_<NAME> pattern• Check env var names • Verify connection strings |
| Audit logs not appearing | • Check AUDIT_LOG_ENABLED=true• Verify log path is writable • Check file permissions |
Debug Logs:
- macOS:
~/Library/Logs/Claude/mcp*.log - Windows:
%APPDATA%\Claude\logs\mcp*.log
Once configured, ask Claude:
Query Examples:
- "Show me the last 10 records from the Users table in mobydom5"
- "Count how many active accounts are in the database"
- "Find all records where FirstName = 'John'"
Schema Examples:
- "What tables are available in the mcpay database?"
- "Show me the structure of the accounts table"
- "List all columns in the Users table with their data types"
Performance Examples:
- "Show me the slowest queries in the last hour"
- "What indexes are missing on the Users table?"
- "Check the fragmentation level of all indexes"
Multi-Database Examples:
- "Compare the Users table structure between mobydom5 and mcpay"
- "Show me tables from all configured databases"
Version 2.0 • Refactored Architecture • Cloud Ready • November 2024