SQL Server MCP

Un servidor de Model Context Protocol (MCP) de solo lectura para Microsoft SQL Server, que permite el descubrimiento seguro de metadatos y consultas SELECT parametrizadas.

Documentación

MCP SQL Server

Build Status NuGet Version License: MIT

Un servidor de Model Context Protocol (MCP) de solo lectura por defecto para Microsoft SQL Server que proporciona descubrimiento de esquemas, consultas de solo selección, análisis de planes de ejecución, escrituras opcionales y acceso basado en perfiles a múltiples servidores desde una única implementación de herramientas.

Requisitos: runtime .NET 8.0 o posterior (la herramienta apunta a net8.0 y net10.0), SQL Server y una cadena de conexión.

Inicio rápido

Establezca MCPMSSQL_CONNECTION_STRING y ejecute el servidor de una de estas maneras:

# Option 1: Run from NuGet package (e.g. with MCP Inspector)
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
# Option 2: Install and run as a global tool
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
# Option 3: Run from source (clone repo, then)
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

Configuración

Un perfil es una conexión a SQL Server: una cadena de conexión, los límites de filas y tiempo de espera que se le aplican, y si se permiten escrituras. Un perfil llamado default siempre existe; cada herramienta acepta un profile opcional para llegar a otro, y list_profiles informa qué está configurado.

La configuración proviene de tres fuentes, fusionadas campo por campo, ganando la última:

  1. el appsettings.json a nivel de usuario — cualquier número de perfiles;
  2. variables de entorno McpMssql__Profiles__<NAME>__<FIELD> — cualquier número de perfiles;
  3. variables de entorno planas MCPMSSQL_<FIELD> — solo el perfil default.

Debido a que la fusión es por campo y no por perfil, un appsettings.json puede contener el conjunto completo mientras que un MCPMSSQL_CONNECTION_STRING plano redirige el perfil predeterminado a un servidor local, dejando sus otros campos intactos. No hay cadena de conexión de respaldo: un perfil sin una — incluido un default que nada configuró — falla al iniciar.

Cada configuración tiene un nombre de campo, escrito de tres maneras — la ruta JSON bajo McpMssql:Profiles:<NAME>, esa misma ruta con : reemplazado por __ como variable de entorno, o la forma plana:

CampoVariable planaPredeterminadoLímite máximo
ConnectionStringMCPMSSQL_CONNECTION_STRINGrequerido—
DescriptionMCPMSSQL_DESCRIPTIONninguno—
AllowWriteMCPMSSQL_ALLOW_WRITEfalse—
Query:MaxRowsMCPMSSQL_QUERY_MAX_ROWS5001 000
Query:CommandTimeoutSecondsMCPMSSQL_QUERY_COMMAND_TIMEOUT_SECONDS30300
Query:SnapshotMaxRowsMCPMSSQL_QUERY_SNAPSHOT_MAX_ROWS10 00050 000
Query:SnapshotCommandTimeoutSecondsMCPMSSQL_QUERY_SNAPSHOT_COMMAND_TIMEOUT_SECONDS120300
Analyze:CommandTimeoutSecondsMCPMSSQL_ANALYZE_COMMAND_TIMEOUT_SECONDS300600
Write:CommandTimeoutSecondsMCPMSSQL_WRITE_COMMAND_TIMEOUT_SECONDS60600

Los límites son por perfil. Un valor por encima de su máximo — o por debajo de 1 — se ajusta al iniciar y el ajuste se registra como advertencia en stderr; un valor plano que no sea un entero, o que no sea booleano para AllowWrite, se ignora, dejando lo que las otras fuentes establezcan.

Conexión única: las variables de entorno planas son el camino más corto.

# Connection string (required).
export MCPMSSQL_CONNECTION_STRING="Server=127.0.0.1;User ID=sa;Password=<YourStrong@Passw0rd>;Encrypt=True;TrustServerCertificate=True;"

# Optional description for the default profile (tooling/AI discovery).
export MCPMSSQL_DESCRIPTION="Primary connection"

# Optional caps, defaults shown.
export MCPMSSQL_QUERY_MAX_ROWS="500"
export MCPMSSQL_QUERY_COMMAND_TIMEOUT_SECONDS="30"
export MCPMSSQL_QUERY_SNAPSHOT_MAX_ROWS="10000"
export MCPMSSQL_QUERY_SNAPSHOT_COMMAND_TIMEOUT_SECONDS="120"
export MCPMSSQL_ANALYZE_COMMAND_TIMEOUT_SECONDS="300"

# Optional write access, off by default; also controls whether run_command
# is advertised at all. A soft guard, not a database permission — prefer a
# db_datareader login for a hard read-only guarantee.
export MCPMSSQL_ALLOW_WRITE="false"
export MCPMSSQL_WRITE_COMMAND_TIMEOUT_SECONDS="60"

Conexiones múltiples: use el appsettings.json a nivel de usuario, que mantiene las credenciales fuera del entorno de proceso del host.

  • Similar a Unix: ~/.config/mcp-mssql/appsettings.json
  • Windows: %USERPROFILE%\.config\mcp-mssql\appsettings.json
{
  "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
        }
      }
    }
  }
}

Los nombres de perfil no distinguen entre mayúsculas y minúsculas, y la forma de entorno estructurado se divide en __, por lo que McpMssql__Profiles__WAREHOUSE__ConnectionString es el perfil warehouse, campo ConnectionString. Un appsettings.json faltante está bien — el servidor se inicia con las fuentes restantes — pero uno que no sea JSON válido falla al iniciar.

Desarrollo local: almacene la cadena de conexión en secretos de usuario, luego ejecute con DOTNET_ENVIRONMENT=Development para que los secretos y un appsettings.json del directorio de trabajo se carguen como fuentes adicionales.

dotnet user-secrets set "MCPMSSQL_CONNECTION_STRING" "..." --project src/Alyio.McpMssql
npx -y @modelcontextprotocol/inspector -e DOTNET_ENVIRONMENT=Development dotnet run --project src/Alyio.McpMssql

Sintaxis de cadena de conexión: las palabras clave habituales de Server=host,port;Database=db;User ID=...;Password=...;Encrypt=True; de Microsoft.Data.SqlClient, que también admite autenticación de Microsoft Entra (Azure AD): establezca Authentication a un modo compatible (por ejemplo, Active Directory Default, Active Directory Managed Identity o Active Directory Interactive) al conectarse a Azure SQL. Consulte Conectarse a Azure SQL con autenticación de Microsoft Entra y SqlClient para todos los modos y detalles.

Herramientas y recursos

Todas las herramientas aceptan un profile opcional; cuando se omite, se usa el perfil predeterminado.

Herramientas

HerramientaDescripciónParámetros clave
list_profilesLista los perfiles de conexión configurados. Llame primero al elegir un perfil no predeterminado. Devuelve name, description y allow_write por perfil.—
get_objectObtiene metadatos para una relación (columnas, índices, restricciones, relaciones) o rutina (definición). name acepta Users, dbo.Users o [dbo].[Users]. includes omitido → columns. Las relaciones también llevan un row_count aproximado.kind, name, profile, catalog, schema, includes
run_queryEjecuta SELECT de T-SQL de solo lectura; solo se permite SELECT (sin DML/DDL). Devuelve resultados como CSV en el campo data (en línea) o un URI de recurso de instantánea cuando snapshot=true. Límite en línea: 500 filas (máximo 1000). Límite de instantánea: 10 000 filas (máximo 50 000). Prefiera analyze_query para ajuste de planes.sql, profile, catalog, parameters, snapshot
analyze_queryAnaliza el plan de ejecución para un SELECT de solo lectura. Devuelve un resumen JSON compacto (costo, operadores, cardinalidad, advertencias, missing_indexes, esperas, estadísticas); sin filas de resultados, XML completo en plan_uri.sql, profile, catalog, parameters, estimated
run_commandEjecuta T-SQL de escritura (DDL/DML). Se anuncia solo cuando algún perfil establece AllowWrite=true (desactivado por defecto); aún se rechaza en el momento de la llamada cuando el profile objetivo está bloqueado. El llamador gestiona las transacciones. Devuelve rows_affected (−1 para DDL) y messages del servidor. Marcado como destructivo; destinado a uso supervisado por humanos.sql, profile, catalog, parameters
  • kind — relation o routine.
  • includes — Matriz de secciones de detalle: columns, indexes, constraints, relationships (solo relaciones), definition (solo rutinas). relationships devuelve claves foráneas en ambas direcciones.

La exploración del catálogo se deja a run_query sobre sys.objects, sys.schemas y sys.databases. get_object acepta el missing_indexes[].table de analyze_query tal cual.

Recursos

Plantilla de URIDescripción
mssql://profilesLista los perfiles de conexión configurados, incluido allow_write. Mismos datos que list_profiles.
mssql://plans/{id}Recupera el plan de ejecución XML completo por ID de analyze_query; las entradas caducan después de 7 días.
mssql://snapshots/{id}Recupera el resultado de consulta completo como CSV por ID de run_query (snapshot=true); las entradas caducan después de 7 días.

Los planes y las instantáneas se escriben en disco, bajo ~/.cache/mcp-mssql/plans/ y ~/.cache/mcp-mssql/snapshots/ (%USERPROFILE%\.cache\mcp-mssql\ en Windows). Los archivos caducados se eliminan la primera vez que el servidor toca el almacenamiento.

Seguridad

Las herramientas de consulta (run_query, analyze_query) son de solo lectura (solo SELECT) y usan enlace @paramName parametrizado. Use variables de entorno, archivo de configuración o secretos de usuario para las cadenas de conexión — nunca confíe secretos.

Qué cuenta como solo lectura. El SQL se analiza con ScriptDom y debe ser exactamente una declaración SELECT en un solo lote — no simplemente texto que comience con SELECT. Los scripts de múltiples declaraciones y separados por GO se rechazan, y también estos, a pesar de ser sintácticamente SELECTs:

RechazadoRazón
SELECT ... INTOMaterializa una nueva tabla.
SELECT @v = ...Asigna una variable, mutando el estado de la sesión.
NEXT VALUE FORAvanza una secuencia.
OPENQUERY, OPENDATASOURCE, OPENROWSET, OPENROWSET(BULK ...)Lee a través de una fuente de datos externa ad-hoc.
UPDLOCK, XLOCK, TABLOCK, TABLOCKX, HOLDLOCK, SERIALIZABLE, REPEATABLEREADToman bloqueos que impiden escritores concurrentes.

Las sugerencias que no adquieren bloqueos adicionales, como NOLOCK, ROWLOCK y READPAST, permanecen permitidas. La entrada de más de 64 KB o anidada más de 100 paréntesis de profundidad también se rechaza, lo que mantiene el analizador de descenso recursivo libre de desbordamiento de pila.

Como AllowWrite a continuación, esto restringe lo que este servidor enviará — no es un permiso de base de datos.

Las escrituras son opcionales e invisibles hasta entonces. La herramienta run_command ejecuta T-SQL arbitrario. A menos que al menos un perfil configurado establezca AllowWrite=true (predeterminado false), la herramienta no se registra en absoluto — nunca aparece en tools/list, por lo que una implementación de solo lectura no gasta contexto en ella y no ofrece superficie de escritura para que un agente sea persuadido. Una vez que cualquier perfil opta, la herramienta se anuncia en todo el servidor y aún rechaza en el momento de la llamada en perfiles que permanecen bloqueados; list_profiles informa allow_write por perfil para que un agente pueda elegir uno escribible.

AllowWrite es una protección suave a nivel de aplicación, no un límite de seguridad — restringe este servidor, no la base de datos. Para una garantía genuina de solo lectura, conéctese con un inicio de sesión restringido a db_datareader, y mantenga los perfiles habilitados para escritura apuntando a credenciales limitadas solo a lo que necesitan. run_command está marcado como destructive mediante anotaciones de herramientas MCP para que los hosts puedan protegerlo detrás de confirmación, pero honre esas anotaciones a discreción del host.

Ejemplos de hosts MCP

Reemplace la cadena de conexión con la suya; asegúrese de que dotnet esté en su PATH. El bloque env es innecesario cuando la cadena de conexión ya proviene de appsettings.json o del entorno.

Claude Code, Cursor y Gemini leen la misma forma mcpServers:

{
  "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;"
      }
    }
  }
}
Codex, Open Code y GitHub Copilot

Codex (TOML):

[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;"

Open Code:

{
  "$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;"
      }
    }
  }
}

GitHub Copilot:

{
  "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;"
      }
    }
  }
}

Pruebas de integración

Las pruebas usan un SQL Server real y el perfil default (MCPMSSQL_CONNECTION_STRING de variables de entorno o secretos de usuario). La suite espera una base de datos llamada McpMssqlTest: la cadena de conexión debe incluir Initial Catalog=McpMssqlTest. La infraestructura de prueba crea, siembra y elimina esta base de datos. Establezca el secreto para el proyecto de prueba:

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

Un framework a la vez. La única base de datos McpMssqlTest es compartida por cada prueba, y los fixtures la eliminan y recrean en la inicialización. Dentro de un proceso de prueba esto es seguro — la colección SqlServer desactiva la paralelización. Entre procesos no lo es: el proyecto de prueba apunta tanto a net8.0 como a net10.0, y dotnet test ejecuta los dos módulos de framework en paralelo, por lo que compiten por esa única base de datos. No hay bloqueo entre procesos, así que ejecute un solo framework a la vez:

dotnet test --framework net8.0
dotnet test --framework net10.0

CI hace lo mismo, iterando sobre TARGET_FRAMEWORKS secuencialmente.

¿Por qué esto en lugar de Data API Builder?

Data API Builder (DAB) es una API REST/GraphQL completa con CRUD y autenticación. Este proyecto es un pequeño servidor MCP de solo lectura para agentes: stdio, SELECT parametrizado únicamente, superficie mínima. Elija esto para flujos de trabajo de agentes y baja sobrecarga operativa; elija DAB para CRUD, REST/GraphQL y políticas ricas.

Hoja de ruta

Extensión de tareas MCP (SEP-2663). Las consultas de instantánea y el análisis de planes de ejecución se ejecutan bajo tiempos de espera largos (120 s y 300 s por defecto), que es la forma para la que existe la extensión de tareas: el servidor devuelve un identificador de tarea duradero en lugar de bloquear, y el cliente consulta tasks/get hasta que el trabajo alcanza un estado terminal. El ajuste es bueno; la adopción es el obstáculo. Tasks es una extensión opcional (io.modelcontextprotocol/tasks) que un servidor solo puede usar cuando el cliente declara soporte en sus capacidades por solicitud, y ningún cliente lo lista actualmente en la matriz de soporte de extensiones. Se pospone hasta que los clientes implementen soporte.

Compatibilidad de esquema

Los miembros anulables emiten un tipo de unión JSON Schema — "type": ["string", "null"] — porque eso es lo que System.Text.Json produce para string? y similares. Es JSON Schema 2020-12 válido y permitido por la especificación MCP. MCP Inspector advierte sobre la forma, argumentando que algunos clientes MCP leen type como una sola cadena; si esa regla aún tiene evidencia detrás está bajo revisión upstream. Reescribir a anyOf no es una victoria clara: OpenAI documenta la forma de unión para parámetros opcionales, Anthropic admite anyOf y no arreglos de tipos, y Cursor, Gemini y Azure AI Foundry rechazan anyOf.

No se pierde nada al ignorar la rama nula. Este servidor nunca serializa null — los miembros ausentes se omiten en lugar de enviarse como null — y ningún miembro anulable aparece en una lista required, por lo que un cliente que lee solo el primer tipo de la unión obtiene el contrato exacto. Se deja como lo emite el SDK; se revisará si el SDK cambia o la regla se establece.

Contribuciones

Abre issues o PRs; sigue el estilo existente y añade pruebas donde corresponda.

Licencia

MIT