This guide covers how to deploy the MSSQL MCP server in enterprise environments, including network topologies, bastion/jump host scenarios, container deployments, and security hardening.
MCP servers run locally as child processes of the IDE/client (Windsurf, Claude Desktop, VS Code, Cursor). They communicate via stdio (stdin/stdout), not network sockets. This means:
- The server must be able to reach SQL Server over TCP from wherever the MCP client is running
- There is no HTTP endpoint to expose or firewall rule to open for the MCP protocol itself
- Credentials are passed via environment variables to the child process
┌─────────────┐ TCP 1433 ┌────────────┐
│ IDE + MCP │◄──────────────────────►│ SQL Server │
│ (your PC) │ │ │
└─────────────┘ └────────────┘
When to use: SQL Server is on localhost, your LAN, or reachable via VPN.
Setup:
{
"mcpServers": {
"mssql": {
"command": "npx",
"args": ["@connorbritain/mssql-mcp-server@latest"],
"env": {
"SERVER_NAME": "sqlserver.corp.local",
"DATABASE_NAME": "AppDB",
"SQL_AUTH_MODE": "windows",
"SQL_USERNAME": "CORP\\myuser",
"SQL_PASSWORD": "...",
"READONLY": "true"
}
}
}
}Network requirements:
- Outbound TCP to SQL Server on port 1433 (or custom port)
- If using Azure AD auth: outbound HTTPS to
login.microsoftonline.com
┌─────────────┐ VPN tunnel ┌──────────┐ TCP 1433 ┌────────────┐
│ IDE + MCP │◄──────────────────────►│ VPN GW │◄───────────────►│ SQL Server │
│ (your PC) │ │ │ │ │
└─────────────┘ └──────────┘ └────────────┘
When to use: SQL Server is in a private network, but your VPN routes database traffic. Check with your network team whether port 1433 is routable through the VPN — many configurations only allow HTTP/HTTPS.
Setup: Same as Pattern A, but use the private IP or hostname that's reachable through the VPN.
┌─────────────┐ SSH (port 22) ┌─────────────┐ TCP 1433 ┌────────────┐
│ IDE + MCP │◄──────────────────────►│ Jump Host │◄──────────────►│ SQL Server │
│ (your PC) │ tunnel 1433→1433 │ (bastion) │ │ │
└─────────────┘ └─────────────┘ └────────────┘
When to use: You have SSH access to a host that can reach SQL Server, but your workstation cannot reach SQL Server directly.
Setup:
-
Open the tunnel:
ssh -L 1433:sql-server-hostname:1433 user@jump-host -N
-
Configure MCP to connect to
localhost:1433:{ "mcpServers": { "mssql": { "command": "npx", "args": ["@connorbritain/mssql-mcp-server@latest"], "env": { "SERVER_NAME": "127.0.0.1", "DATABASE_NAME": "AppDB", "SQL_AUTH_MODE": "sql", "SQL_USERNAME": "readonly_user", "SQL_PASSWORD": "...", "TRUST_SERVER_CERTIFICATE": "true", "READONLY": "true" } } } }
Tips:
- Use
-Nto prevent opening a shell (tunnel only) - Add
-fto background the tunnel - If port 1433 is already in use locally, map to a different port:
-L 14330:sql-server:1433and setSQL_PORT=14330 - For persistent tunnels, consider
autosshor a systemd unit (see below)
┌─────────────────────────────────────────────────┐
│ Jump Host │
│ ┌─────────────┐ TCP 1433 ┌────────────┐ │
│ │ IDE + MCP │◄──────────────►│ SQL Server │ │
│ │ (Windsurf) │ │ │ │
│ └─────────────┘ └────────────┘ │
└─────────────────────────────────────────────────┘
▲
│ RDP / VDI
┌────────┴────┐
│ Your PC │
└─────────────┘
When to use: No direct or tunneled access to SQL Server from your workstation. This is common in regulated environments where database access is only permitted from specific machines.
Setup:
- RDP to the jump host
- Install Node.js and the MCP client (Windsurf, Claude Desktop, etc.)
- Configure the MCP server with direct connection to SQL Server
- SQL Server is reachable from the jump host's network
Trade-offs:
- Requires installing IDE tooling on the jump host
- RDP latency affects the editing experience
- But: no special network configuration needed, and the MCP server has direct connectivity
┌─────────────┐ stdio ┌──────────────────┐ TCP 1433 ┌────────────┐
│ IDE │◄────────────────►│ MCP Container │◄──────────────►│ SQL Server │
│ (your PC) │ │ (mssql-mcp-srv) │ │ │
└─────────────┘ └──────────────────┘ └────────────┘
When to use: You want environment isolation, reproducible builds, or are deploying into Kubernetes. Also useful for air-gapped environments where you pre-build the image.
Dockerfile:
FROM node:20-slim
RUN npm install -g @connorbritain/mssql-mcp-server@latest
ENTRYPOINT ["mssql-mcp-server"]Docker Compose:
services:
mssql-mcp:
build: .
stdin_open: true
environment:
- ENVIRONMENTS_CONFIG_PATH=/config/environments.json
- READONLY=true
volumes:
- ./environments.json:/config/environments.json:ro
- ./logs:/app/logsMCP client config (Docker):
{
"mcpServers": {
"mssql": {
"command": "docker",
"args": ["run", "-i", "--rm",
"-e", "SERVER_NAME=host.docker.internal",
"-e", "DATABASE_NAME=AppDB",
"-e", "SQL_AUTH_MODE=sql",
"-e", "SQL_USERNAME=sa",
"-e", "SQL_PASSWORD=...",
"-e", "TRUST_SERVER_CERTIFICATE=true",
"-e", "READONLY=true",
"mssql-mcp-server:latest"
]
}
}
}Tips:
- Use
host.docker.internal(Docker Desktop) or--network host(Linux) to reach SQL Server on the host - Mount
environments.jsonas a read-only volume - For secrets, use Docker secrets or pass via environment variables (not baked into the image)
- For air-gapped deployments, export the image with
docker saveand import on the target machine
For always-on scenarios (shared jump hosts, CI/CD integration), you can run the MCP server as a systemd service.
# /etc/systemd/system/mssql-mcp-server.service
[Unit]
Description=MSSQL MCP Server
After=network.target
[Service]
Type=simple
User=mcp-service
Group=mcp-service
ExecStart=/usr/bin/npx @connorbritain/mssql-mcp-server@latest
Environment=ENVIRONMENTS_CONFIG_PATH=/etc/mssql-mcp/environments.json
Environment=AUDIT_LOG_PATH=/var/log/mssql-mcp/audit.jsonl
Environment=READONLY=true
Restart=on-failure
RestartSec=5
StandardInput=socket
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload
sudo systemctl enable mssql-mcp-server
sudo systemctl start mssql-mcp-serverNote: MCP servers communicate via stdio, so a systemd service is primarily useful when paired with a socket activation or proxy setup. For typical desktop use, the MCP client manages the server lifecycle automatically.
- SQL Server listens only on private/internal interfaces (not
0.0.0.0) - Firewall rules restrict port 1433 to known source IPs
- TLS encryption enabled for SQL connections (
Encrypt=true) - If using self-signed certs, only set
TRUST_SERVER_CERTIFICATE=truein dev/test - SSH tunnels use key-based auth, not passwords
- Never hardcode passwords in MCP client configs (use
${secret:NAME}placeholders) - Use dedicated SQL accounts with least-privilege permissions
- Separate accounts per environment (don't reuse prod credentials in dev)
- Rotate credentials on a schedule; use Azure Key Vault / HashiCorp Vault for automated rotation
-
.envfiles are in.gitignoreand have restrictive file permissions (chmod 600)
- Use
READONLY=truefor production environments - Use the reader package (
@connorbritain/mssql-mcp-reader) in prod — it physically cannot execute write operations - Set
allowedToolsto whitelist only necessary tools per environment - Set
deniedSchemasto block access to sensitive tables (e.g.,audit.*,security.*,*.password*) - Set
maxRowsDefaultto limit result sizes (e.g., 100 for prod, 10000 for dev) - Set
requireApproval: truefor any environment that touches real data - Set
auditLevel: "verbose"for production environments
- Audit logging enabled (
AUDIT_LOGGING=true, the default) - Audit log path points to a persistent, backed-up location
- Sensitive parameter redaction enabled (
AUDIT_REDACT_SENSITIVE=true, the default) - Audit logs are reviewed periodically or forwarded to a SIEM
- Environment names in audit logs match your CMDB/asset inventory
For unattended or shared deployments, use a dedicated service account:
SQL Server login: mcp_readonly_svc
Permissions: db_datareader on target databases only
Password: Managed via Azure Key Vault / secret store
Connection: SQL auth mode with ${secret:MCP_SVC_PASSWORD}
Avoid granting db_owner, sysadmin, or ALTER permissions to service accounts used by the MCP server.
For managed service providers supporting multiple clients, each on their own SQL Server:
┌─────────────┐
│ IDE + MCP │
└──────┬──────┘
│ environments.json
├──► client-a-prod (10.0.1.100:1433, Windows auth, readonly)
├──► client-a-dev (10.0.1.101:1433, SQL auth, full access)
├──► client-b-prod (vpn → 192.168.50.10:1433, readonly)
└──► internal-dev (localhost:1433, SQL auth, full access)
Use environments.json with per-client environments, each with its own auth, policies, and audit level. See environments.example.json for a complete multi-client configuration.
┌──────────────────┐
│ Central Audit │
│ (SIEM / Splunk) │
└────────▲─────────┘
│ log shipping (future)
┌─────────────┐ │
│ IDE + MCP │──────────────┤
└─────────────┘ │
│ │
├──► prod-sql ────────┤ auditLevel: verbose
├──► staging-sql ─────┤ auditLevel: basic
└──► dev-sql ─────────┘ auditLevel: none
Today, audit logs are written to local JSONL files. External log shipping (Syslog, Azure Monitor, Splunk) is planned — see ROADMAP.md for status.
| Symptom | Likely Cause | Fix |
|---|---|---|
ESOCKET / connection timeout |
SQL Server not reachable on network | Check firewall, VPN, port 1433 is open |
ELOGIN / login failed |
Wrong credentials or auth mode | Verify SQL_AUTH_MODE, username, password |
SELF_SIGNED_CERT_IN_CHAIN |
TLS cert not trusted | Set TRUST_SERVER_CERTIFICATE=true (dev only) |
EALREADY / pool exhaustion |
Too many concurrent connections | Restart the MCP server; check for connection leaks |
| Connection works in SSMS but not MCP | MCP runs as child process, different user context | Check that credentials are available to the spawned process (not just your shell) |
| Azure AD auth opens browser repeatedly | Token cache not persisting | Ensure the process has write access to its working directory |
Use test_connection to verify an environment is reachable:
> Use test_connection for the prod environment
This returns server version, latency, and auth mode — useful for diagnosing whether the issue is network, auth, or configuration.
See also: README.md for configuration reference, SECRETS+AUTH.md for credential management details.