Read-only-by-default SQL Server: schema, parameterized SELECT, execution plans; writes opt-in.
A read-only-by-default Model Context Protocol (MCP) server for Microsoft SQL Server. Enables AI agents to discover schemas, execute parameterized SELECT queries, analyze execution plans, and optionally execute write operations (DDL/DML).
Query tools enforce SELECT-only statements (no DML/DDL mutations). An optional run_command tool can execute arbitrary write T-SQL, but only on profiles that explicitly opt in via AllowWrite configuration (disabled by default for safety).
Requirements: .NET 8.0+ runtime (targets net8.0 and net10.0), SQL Server instance, and a connection string. Building from source requires .NET 10.0 SDK.
Set the MCPMSSQL_CONNECTION_STRING environment variable and choose a deployment method:
export MCPMSSQL_CONNECTION_STRING="Server=127.0.0.1;User ID=sa;Password=<YourStrong@Passw0rd>;Encrypt=True;TrustServerCertificate=True;"
npx -y @modelcontextprotocol/inspector@latest dotnet dnx Alyio.McpMssql --prerelease
dotnet tool install --global Alyio.McpMssql --prerelease
export MCPMSSQL_CONNECTION_STRING="Server=127.0.0.1;User ID=sa;Password=<YourStrong@Passw0rd>;Encrypt=True;TrustServerCertificate=True;"
npx -y @modelcontextprotocol/inspector@latest mcp-mssql
export MCPMSSQL_CONNECTION_STRING="Server=127.0.0.1;User ID=sa;Password=<YourStrong@Passw0rd>;Encrypt=True;TrustServerCertificate=True;"
npx -y @modelcontextprotocol/inspector@latest dotnet run --project src/Alyio.McpMssql -f net10.0
Use --prerelease flag for pre-release builds.
All settings use the MCPMSSQL prefix. Flat environment variables (e.g., MCPMSSQL_CONNECTION_STRING) configure the default profile for single-connection setups. For multiple connections, use a configuration file.
# Connection string (required)
export MCPMSSQL_CONNECTION_STRING="Server=127.0.0.1;User ID=sa;Password=<YourStrong@Passw0rd>;Encrypt=True;TrustServerCertificate=True;"
# Description for the default profile (optional, used for tooling/AI discovery)
export MCPMSSQL_DESCRIPTION="Primary connection"
# Max rows per interactive query (default: 500, hard ceiling: 1000)
export MCPMSSQL_QUERY_MAX_ROWS="500"
# Query timeout in seconds (default: 30)
export MCPMSSQL_QUERY_COMMAND_TIMEOUT_SECONDS="60"
# Max rows for snapshot queries (default: 10000, hard ceiling: 50000)
export MCPMSSQL_QUERY_SNAPSHOT_MAX_ROWS="10000"
# Snapshot query timeout in seconds (default: 120)
export MCPMSSQL_QUERY_SNAPSHOT_COMMAND_TIMEOUT_SECONDS="120"
# Execution plan analysis timeout in seconds (default: 300, hard ceiling: 600)
export MCPMSSQL_ANALYZE_COMMAND_TIMEOUT_SECONDS="300"
# Enable write commands via run_command (default: false, soft guard only)
# For hard read-only guarantee, connect with a db_datareader login
export MCPMSSQL_ALLOW_WRITE="false"
# Write command timeout in seconds (default: 60, hard ceiling: 600)
export MCPMSSQL_WRITE_COMMAND_TIMEOUT_SECONDS="60"
Use the user-scoped appsettings.json file (recommended for multiple profiles). Environment variables also work via .NET host conventions (e.g., MCPMSSQL__PROFILES__<NAME>__CONNECTIONSTRING).
File locations:
~/.config/mcp-mssql/appsettings.json%USERPROFILE%\.config\mcp-mssql\appsettings.jsonExample configuration:
{
"McpMssql": {
"Profiles": {
"default": {
"ConnectionString": "Server=...;User ID=...;Password=...;",
"Description": "Primary connection",
"Query": {
"MaxRows": 500,
"CommandTimeoutSeconds": 60,
"SnapshotMaxRows": 10000,
"SnapshotCommandTimeoutSeconds": 120
},
"Analyze": {
"CommandTimeoutSeconds": 300
}
},
"warehouse": {
"ConnectionString": "Server=warehouse.example.com;...",
"Description": "Warehouse read-only"
},
"migrations": {
"ConnectionString": "Server=...;User ID=...;Password=...;",
"Description": "Write-enabled profile for schema changes",
"AllowWrite": true,
"Write": {
"CommandTimeoutSeconds": 60
}
}
}
}
}
Configuration values exceeding hard ceilings are automatically clamped at startup with warnings logged to stderr.
Store sensitive connection strings in .NET user-secrets:
dotnet user-secrets set "MCPMSSQL_CONNECTION_STRING" "Server=localhost,1433;..." --project src/Alyio.McpMssql
npx -y @modelcontextprotocol/inspector -e DOTNET_ENVIRONMENT=Development dotnet run --project src/Alyio.McpMssql
This server uses Microsoft.Data.SqlClient, which supports Microsoft Entra (Azure AD) authentication. Provide connection strings using Entra credentials or managed identities as documented by SqlClient.
All tools accept an optional profile parameter; when omitted, the default profile is used.
| Tool | Description | Key Parameters |
|---|---|---|
list_profiles | List all configured connection profiles. | — |
get_object | Retrieve metadata for a table/view (columns, indexes, constraints, relationships) or routine definition. Accepts names like Users, dbo.Users, or [dbo].[Users]. | name, kind (relation/routine), includes (columns, indexes, constraints, relationships, definition) |
run_query | Execute a read-only SELECT query with parameterized binding. Returns results as CSV inline (up to limit) or as a snapshot resource URI. | sql, params, snapshot, profile |
analyze_query | Analyze a SELECT query's execution plan without fetching results. Returns compact JSON with cost, operators, cardinality, warnings, missing indexes, waits, and stats. Full XML plan available via resource URI. | sql, params, profile |
run_command | Execute write T-SQL (DDL/DML). Rejected unless AllowWrite=true for the target profile. Caller manages transactions. | sql, params, profile |
| URI Template | Description |
|---|---|
mssql://profiles | List configured connection profiles (same as list_profiles tool). |
mssql://plans/{id} | Retrieve full XML execution plan by ID from analyze_query. Plans expire after 7 days. |
mssql://snapshots/{id} | Retrieve full query results as CSV by ID from run_query with snapshot=true. Results expire after 1 day. |
Query tools (run_query, analyze_query) enforce strict SELECT-only semantics using SQL ScriptDom parsing. The SQL must be exactly one SELECT statement in a single batch — not merely text starting with SELECT. Multi-statement batches and GO separators are rejected.
Rejected patterns:
| Pattern | Reason |
|---|---|
SELECT ... INTO | Creates a new table (DDL). |
SELECT @v = ... | Mutates session state via variable assignment. |
NEXT VALUE FOR | Advances sequences. |
OPENQUERY, OPENDATASOURCE, OPENROWSET(BULK ...) | Ad-hoc external data source access. |
UPDLOCK, XLOCK, TABLOCK, TABLOCKX, HOLDLOCK, SERIALIZABLE, REPEATABLEREAD | Acquire locks that impede concurrent writers. |
Allowed hints: Concurrency-safe hints like NOLOCK, ROWLOCK, and READPAST are permitted.
Input limits: SQL longer than 64 KB or nested more than 100 parentheses deep is rejected.
All query parameters use named @paramName binding to prevent SQL injection. Provide connection strings and credentials via environment variables, configuration files, or .NET user-secrets — never hardcode them.
The run_command tool is rejected by default. Enable it only by setting AllowWrite: true in a profile's configuration.
Important: AllowWrite is a soft, application-level guard, not a security boundary. It constrains this server's behavior, not database permissions. For a genuine read-only guarantee, connect with a database login restricted to db_datareader role.
Snippets for popular MCP clients. Replace the connection string with your own and ensure dotnet is on your PATH. The env block is optional if the connection string is already configured via appsettings.json.
{
"mcpServers": {
"mssql": {
"command": "dotnet",
"args": ["dnx", "Alyio.McpMssql", "--prerelease", "--yes"],
"env": {
"MCPMSSQL_CONNECTION_STRING": "Server=127.0.0.1;User ID=sa;Password=<YourStrong@Passw0rd>;Encrypt=True;TrustServerCertificate=True;"
}
}
}
}
{
"mcpServers": {
"mssql": {
"command": "dotnet",
"args": ["dnx", "Alyio.McpMssql", "--prerelease", "--yes"],
"env": {
"MCPMSSQL_CONNECTION_STRING": "Server=127.0.0.1;User ID=sa;Password=<YourStrong@Passw0rd>;Encrypt=True;TrustServerCertificate=True;"
}
}
}
}
[mcp_servers.mssql]
command = "dotnet"
args = ["dnx", "Alyio.McpMssql", "--prerelease", "--yes"]
[mcp_servers.mssql.env]
MCPMSSQL_CONNECTION_STRING = "Server=127.0.0.1;User ID=sa;Password=<YourStrong@Passw0rd>;Encrypt=True;TrustServerCertificate=True;"
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"mssql": {
"type": "local",
"enabled": true,
"command": ["dotnet", "dnx", "Alyio.McpMssql", "--prerelease", "--yes"],
"environment": {
"MCPMSSQL_CONNECTION_STRING": "Server=127.0.0.1;User ID=sa;Password=<YourStrong@Passw0rd>;Encrypt=True;TrustServerCertificate=True;"
}
}
}
}
{
"mcpServers": {
"mssql": {
"command": "dotnet",
"args": ["dnx", "Alyio.McpMssql", "--prerelease", "--yes"],
"env": {
"MCPMSSQL_CONNECTION_STRING": "Server=127.0.0.1;User ID=sa;Password=<YourStrong@Passw0rd>;Encrypt=True;TrustServerCertificate=True;"
}
}
}
}
{
"inputs": [],
"servers": {
"mssql": {
"type": "stdio",
"command": "dotnet",
"args": ["dnx", "Alyio.McpMssql", "--prerelease", "--yes"],
"env": {
"MCPMSSQL_CONNECTION_STRING": "Server=127.0.0.1;User ID=sa;Password=<YourStrong@Passw0rd>;Encrypt=True;TrustServerCertificate=True;"
}
}
}
}
Integration tests use a real SQL Server instance and expect a database named McpMssqlTest. Configure the test connection string via .NET user-secrets:
dotnet user-secrets set "MCPMSSQL_CONNECTION_STRING" \
"Server=localhost,1433;User ID=sa;Password=...;TrustServerCertificate=True;Encrypt=True;Initial Catalog=McpMssqlTest;" \
--project test/Alyio.McpMssql.Tests
Run tests for a single framework:
dotnet test --framework net8.0
dotnet test --framework net10.0
Note: The test fixtures drop and recreate the shared McpMssqlTest database on each initialization. This is safe within a single test process but requires sequential framework execution in CI.
| Aspect | MCP SQL Server | Data API Builder |
|---|---|---|
| Purpose | Lightweight MCP server for AI agents | Full REST/GraphQL CRUD API |
| Transport | Standard input/output (stdio) | HTTP/REST or GraphQL |
| Query Support | Parameterized SELECT only | CRUD, relationships, subscriptions |
| Authentication | Database login via connection string | API-level auth (Azure AD, JWT, etc.) |
| Use Case | Agent-driven schema discovery and analytics | Public/internal APIs, data applications |
Choose this project for agent-based SQL analysis with minimal surface area; choose Data API Builder for production APIs.
Support for MCP Tasks extension (SEP-2663) is planned. Snapshot queries and execution-plan analysis currently run under long timeouts (120 s and 300 s, respectively) but would benefit from Tasks' structured long-running operation model.
Tasks is an opt-in extension (io.modelcontextprotocol/tasks) that a server uses only when the client declares support in its per-request capabilities. Adoption remains the key blocker.
Nullable members emit JSON Schema union types — "type": ["string", "null"] — matching System.Text.Json's behavior for string? and similar nullable types. This is valid in JSON Schema 2020-12.
Note: This server never serializes null values; absent members are omitted from responses. No nullable member appears in a required list, so clients can safely ignore the null branch when needed.
We welcome issues and pull requests. Please follow the existing code style and add tests for new features.
MIT. See LICENSE.
This listing does not have a supported local package template. Use the maintainer’s documentation for its hosted endpoint, authentication, and client-specific setup. No install command has been inferred.
Microsoft SQL Server works with any MCP-compatible client. Copy the config snippet from the Configuration section above and add it to the file shown for your client, then restart the application.
~/Library/Application Support/Claude/claude_desktop_config.jsonRestart Claude Desktop completely for changes to take effect.~/.cursor/mcp.jsonRestart Cursor for changes to take effect..vscode/mcp.jsonReload VS Code window for changes to take effect.~/.codeium/windsurf/mcp_config.jsonRestart Windsurf for changes to take effect..mcp.jsonSave at the project root, then start Claude Code in that project and review the MCP server approval prompt. Keep real credentials out of shared files.