SQL Server MCP
Una entrada MCP para cada SQL Server que administres. Las conexiones viven en un único archivo de configuración agrupadas por cliente o entorno, se recargan en caliente sin reiniciar el agente, y cada respuesta indica qué servidor respondió. Además de ejecutar consultas, devuelve planes de ejecución, diseños de índices y el código fuente de procedimientos almacenados. Diseñado para DBAs y consultores que gestionan decenas de instancias en lugar de una sola base de datos. Instalación con: npx -y @cevelas/mcp-sqlserver
Documentación
SQL Server MCP para quienes administran docenas de instancias
Una entrada MCP, todos los SQL Server que administras. Las conexiones viven en un único
connections.json, agrupadas por cliente o entorno, recargadas en caliente sin
reiniciar tu agente de IA — además de planes de ejecución, auditorías de índices y
análisis de procedimientos almacenados.
Construido para DBAs y consultores, no para una demo contra una única base de datos localhost.
Instalación
Añade esto a la configuración MCP de tu agente de IA:
{
"mcpServers": {
"sqlserver": {
"command": "npx",
"args": ["-y", "@cevelas/mcp-sqlserver"]
}
}
}
Luego crea tu archivo de conexiones y reinicia el agente:
npx -y @cevelas/mcp-sqlserver --init
Eso escribe ~/.mcp-sqlserver/connections.json a partir de una plantilla comentada. Edítalo
y pide a tu agente que "liste todas las conexiones SQL Server".
Dónde cada agente guarda su configuración MCP
| Agente | Archivo de configuración |
|---|---|
| Claude Desktop (Windows) | %APPDATA%\Claude\claude_desktop_config.json |
| Claude Desktop (macOS) | ~/Library/Application Support/Claude/claude_desktop_config.json |
| Claude Code | claude mcp add sqlserver -- npx -y @cevelas/mcp-sqlserver |
| VS Code / Copilot | .vscode/mcp.json |
| Cursor | ~/.cursor/mcp.json |
Cualquier agente compatible con MCP funciona — ChatGPT, Gemini, Copilot, Cline, Zed y
otros aceptan el mismo par command / args.
Por qué este
La mayoría de los servidores MCP de SQL Server toman una única cadena de conexión. Eso está bien para una base de datos. Se desmorona cuando administras treinta en ocho clientes, porque cada instancia necesita su propia entrada en la configuración del agente, sus propias credenciales y su propio reinicio cuando algo cambia.
| Servidores de DSN único | Este | |
|---|---|---|
| Instancias por entrada MCP | 1 | todas |
| Organizado por cliente o entorno | — | connectionGroup |
| Añadir o cambiar una conexión | editar configuración del agente, reiniciar | editar un archivo, reload_connections |
| ¿Qué servidor respondió? | asumes | en el metadata de cada respuesta |
Más allá de SELECT | — | planes de ejecución, diseño de índices, código fuente de SP |
| Barrera de seguridad de solo lectura | — | "readOnly": true por conexión |
Conexiones
{
"connections": [
{
"name": "acme-prod",
"connectionGroup": "Acme Corp",
"description": "Production - head office",
"server": "192.168.1.10\\SQLEXPRESS",
"database": "AcmeDB_Prod",
"user": "app_reader",
"password": "${env:ACME_PROD_PASSWORD}",
"port": 1433,
"encrypt": true,
"trustServerCertificate": false,
"readOnly": true
}
]
}
| Campo | Obligatorio | Notas |
|---|---|---|
name | sí | Único; esto es lo que le dices al agente |
server | sí | Hostname, IP o host\instance |
connectionGroup | no | Cliente, proyecto o entorno. Agrupa el listado |
description | no | Se muestra en list_connections y en cada respuesta |
database | no | Por defecto, la base de datos predeterminada del login |
user, password | no | Omítelos para autenticación de dominio o Entra ID |
port | no | Por defecto, 1433 |
encrypt, trustServerCertificate | no | encrypt por defecto es true |
readOnly | no | Rechaza sentencias de escritura — ver más abajo |
Cualquier otra cosa que pongas aquí se pasa directamente a mssql,
así que requestTimeout, connectionTimeout, pool, authentication y un objeto
options anidado funcionan todos.
Mantener las contraseñas fuera del archivo
Cualquier cadena puede hacer referencia a una variable de entorno:
"password": "${env:ACME_PROD_PASSWORD}"
Una conexión que referencia una variable que no has definido está deshabilitada, y
list_connections nombra tanto la conexión como la variable faltante. Dejar
el literal en su lugar solo movería el fallo al momento de conectar, donde
llega como Login failed for user y no te dice nada.
También puedes omitir el archivo por completo y pasar todo a través de la configuración del agente, lo que mantiene las credenciales en un solo lugar junto con el resto de tus secretos MCP:
{
"mcpServers": {
"sqlserver": {
"command": "npx",
"args": ["-y", "@cevelas/mcp-sqlserver"],
"env": {
"MSSQL_MCP_CONNECTIONS_JSON": "{\"connections\":[{\"name\":\"prod\",\"server\":\"10.0.0.1\",\"database\":\"App\",\"user\":\"reader\",\"password\":\"...\"}]}"
}
}
}
}
Dónde se busca el archivo
En orden, gana la primera coincidencia:
--connections <path>$MSSQL_MCP_CONNECTIONS— una ruta$MSSQL_MCP_CONNECTIONS_JSON— el JSON en sí, en línea./connections.jsonen el directorio de trabajo~/.mcp-sqlserver/connections.jsonconnections.jsonjunto al paquete instalado
Una ruta dada explícitamente mediante 1 o 2 que no existe es un error — el servidor no retrocederá silenciosamente a un archivo diferente y hablará con la base de datos equivocada.
Autenticación de dominio Windows y Entra ID
El controlador tedious incluido soporta NTLM y la familia Entra ID (Azure AD).
Añade domain para NTLM:
{
"name": "warehouse",
"server": "dwh.corp.local",
"database": "DWH",
"domain": "CORP",
"user": "svc_analytics",
"password": "${env:DWH_PASSWORD}"
}
{
"name": "azure-sql",
"server": "myserver.database.windows.net",
"database": "reporting",
"encrypt": true,
"authentication": {
"type": "azure-active-directory-password",
"options": { "userName": "${env:AZURE_USER}", "password": "${env:AZURE_PASSWORD}" }
}
}
La autenticación totalmente integrada — una conexión de confianza sin contraseña — necesita
el controlador nativo msnodesqlv8, que no está incluido porque rompería
la instalación de una línea en máquinas sin cadena de herramientas de compilación. NTLM con
una cuenta de servicio explícita es la vía soportada.
Herramientas
| Herramienta | Argumentos | Qué hace |
|---|---|---|
list_connections | — | Cada conexión, agrupada |
reload_connections | — | Releer el archivo, cerrar pools abiertos |
query | connection, sql | Ejecutar una consulta |
get_schema | connection, table? | Columnas, tipos, nulabilidad, valores predeterminados |
get_indexes | connection, table | Índices, tipos, columnas clave e incluidas |
get_execution_plan | connection, sql | SHOWPLAN_XML — el plan, sin ejecutar la consulta |
get_stored_procedure | connection, name | Código fuente de un procedimiento almacenado |
Cada respuesta lleva la conexión de la que proviene:
{
"metadata": {
"connection": "acme-prod",
"connectionGroup": "Acme Corp",
"description": "Production - head office",
"server": "192.168.1.10\\SQLEXPRESS",
"database": "AcmeDB_Prod"
},
"data": [ ... ]
}
Con treinta conexiones en juego, esa línea es lo que te dice que la respuesta vino del cliente que querías.
Qué te aporta esto
Cosas que son tediosas a mano y se convierten en una frase para el agente:
- "¿Por qué este procedimiento almacenado es lento?" —
get_stored_procedurepara el código fuente,get_execution_planpara el plan,get_indexespara lo que falta. - "Compara el esquema de Orders entre acme-prod y acme-qa" —
get_schemaen ambos, el agente los compara. - "¿Qué índices de esta tabla nunca cubren nada?" —
get_indexesmás las consultas que te importan. - "Añadí un cliente a connections.json" —
reload_connections, sin reiniciar.
Conexiones de solo lectura
"readOnly": true
Rechaza INSERT, UPDATE, DELETE, MERGE, DROP, TRUNCATE, ALTER,
CREATE, GRANT, EXEC, BACKUP, DBCC, OPENQUERY, DISABLE/ENABLE
y similares antes de que la consulta salga de tu máquina. También requiere que el lote
comience con algo que lea — SELECT, WITH, DECLARE, SET, IF
y así sucesivamente — porque T-SQL te permite llamar a un procedimiento sin EXEC, y
sp_rename 'dbo.Users','Users_old' no contiene ninguna palabra clave bloqueada.
Los literales de cadena, comentarios e identificadores entre corchetes se ignoran, así que
WHERE note = 'please delete this', SELECT [delete] FROM [Audit] y
DECLARE @Create DATETIME pasan todos. get_execution_plan sigue funcionando, porque
SHOWPLAN_XML devuelve el plan sin ejecutar nada.
Cualquier cosa que no sea un false explícito activa la protección — un "readOnly": "false"
escrito a mano bloquea la conexión en lugar de abrirla silenciosamente,
y lo indica en list_connections.
Esto es una barrera de seguridad, no un límite de seguridad. Evita que un agente
"ayude" arreglando una fila en producción. No detendrá a alguien decidido a
escribir. La protección real es un login SQL que solo tenga db_datareader:
CREATE LOGIN mcp_reader WITH PASSWORD = '...';
CREATE USER mcp_reader FOR LOGIN mcp_reader;
ALTER ROLE db_datareader ADD MEMBER mcp_reader;
GRANT VIEW DEFINITION TO mcp_reader; -- for get_stored_procedure
GRANT SHOWPLAN TO mcp_reader; -- for get_execution_plan
Usa ambos.
Notas de seguridad
connections.jsoncontiene credenciales. Mantenlo fuera del control de versiones — el.gitignoreincluido cubreconnections*.json.- Prefiere
${env:VAR}sobre contraseñas literales. - Da a cada conexión el menor privilegio que necesite. No uses
sa. - Restringe los permisos de archivo:
icacls connections.json /inheritance:r /grant:r "%USERNAME%:F"en Windows,chmod 600 connections.jsonen otros sistemas. - La herramienta
queryejecuta cualquier SQL que el agente escriba. Ese es el propósito de la herramienta — trata los permisos de la conexión como el límite, no la herramienta.
Interfaz web
web/connections.html es una página independiente para editar connections.json
sin escribir JSON a mano: arrastrar y soltar entre grupos, duplicar una
conexión, selector de grupo con autocompletado y guardado automático mediante la API
File System Access en Chrome y Edge. Sin compilación, sin dependencias, totalmente
opcional. Ver web/README.md.
Instalación con un clic en Claude Desktop
Toma el paquete .mcpb de la
última versión
y arrástralo sobre la configuración de extensiones de Claude Desktop. Te pedirá la ruta
a tu connections.json y configurará todo.
Actualización desde 2.x
Tu connections.json existente funciona sin cambios — cada campo nuevo es opcional
y las herramientas aceptan los mismos argumentos. Dos cosas que vale la pena saber:
- La multi-conexión estaba rota antes de 3.0. El servidor usaba el pool de conexiones
global
mssql, que ignora la configuración que recibe una vez que una conexión ya está abierta. En la práctica, cada conexión después de la primera reutilizaba silenciosamente el servidor y la base de datos de la primera. Si dependías de resultados de más de una conexión en una sesión, es posible que no vinieran de donde pensabas. Corregido en 3.0 con un pool por conexión. - Si la configuración de tu agente apunta a
node C:\path\to\index.js, eso sigue funcionando. El orden de resolución de archivos ahora verifica esa ruta al final, así que nada se mueve.
Desarrollo
npm install
npm test # unit tests, no database needed
node .github/scripts/smoke.mjs # packs, installs and speaks MCP to the tarball
Contra un servidor real, nombra una conexión de tu propio archivo:
MSSQL_TEST_CONNECTION=local npm run test:integration
El controlador mssql se inyecta, así que las pruebas unitarias simulan solo ese límite —
todo lo demás es la ruta de código real. test/contract.test.js congela los nombres
de herramientas y argumentos para que una refactorización no pueda cambiar la superficie MCP por accidente.
Licencia
MIT — ver LICENSE.
Autor
Christian Velasquez — @cvelasquez