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

npm CI License: MIT Node.js MCP

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
AgenteArchivo de configuración
Claude Desktop (Windows)%APPDATA%\Claude\claude_desktop_config.json
Claude Desktop (macOS)~/Library/Application Support/Claude/claude_desktop_config.json
Claude Codeclaude 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 únicoEste
Instancias por entrada MCP1todas
Organizado por cliente o entornoconnectionGroup
Añadir o cambiar una conexióneditar configuración del agente, reiniciareditar un archivo, reload_connections
¿Qué servidor respondió?asumesen el metadata de cada respuesta
Más allá de SELECTplanes 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
    }
  ]
}
CampoObligatorioNotas
nameÚnico; esto es lo que le dices al agente
serverHostname, IP o host\instance
connectionGroupnoCliente, proyecto o entorno. Agrupa el listado
descriptionnoSe muestra en list_connections y en cada respuesta
databasenoPor defecto, la base de datos predeterminada del login
user, passwordnoOmítelos para autenticación de dominio o Entra ID
portnoPor defecto, 1433
encrypt, trustServerCertificatenoencrypt por defecto es true
readOnlynoRechaza 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:

  1. --connections <path>
  2. $MSSQL_MCP_CONNECTIONS — una ruta
  3. $MSSQL_MCP_CONNECTIONS_JSON — el JSON en sí, en línea
  4. ./connections.json en el directorio de trabajo
  5. ~/.mcp-sqlserver/connections.json
  6. connections.json junto 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

HerramientaArgumentosQué hace
list_connectionsCada conexión, agrupada
reload_connectionsReleer el archivo, cerrar pools abiertos
queryconnection, sqlEjecutar una consulta
get_schemaconnection, table?Columnas, tipos, nulabilidad, valores predeterminados
get_indexesconnection, tableÍndices, tipos, columnas clave e incluidas
get_execution_planconnection, sqlSHOWPLAN_XML — el plan, sin ejecutar la consulta
get_stored_procedureconnection, nameCó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_procedure para el código fuente, get_execution_plan para el plan, get_indexes para lo que falta.
  • "Compara el esquema de Orders entre acme-prod y acme-qa"get_schema en ambos, el agente los compara.
  • "¿Qué índices de esta tabla nunca cubren nada?"get_indexes má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.json contiene credenciales. Mantenlo fuera del control de versiones — el .gitignore incluido cubre connections*.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.json en otros sistemas.
  • La herramienta query ejecuta 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

Issues · Changelog · Sponsor