SQL Server MCP

Um servidor de protocolo de contexto de modelo (MCP) somente leitura para Microsoft SQL Server, permitindo descoberta segura de metadados e consultas SELECT parametrizadas.

Documentação

MCP SQL Server

Build Status NuGet Version License: MIT

Um servidor Model Context Protocol (MCP) somente-leitura por padrão para Microsoft SQL Server que fornece descoberta de esquema, consultas somente SELECT, análise de planos de execução, gravações opcionais e acesso baseado em perfis a múltiplos servidores a partir de uma única implantação do conjunto de ferramentas.

Requisitos: runtime .NET 8.0 ou posterior (a ferramenta tem como alvo net8.0 e net10.0), SQL Server e uma string de conexão.

Início rápido

Defina MCPMSSQL_CONNECTION_STRING e execute o servidor de uma das seguintes maneiras:

# 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

Configuração

Um perfil é uma conexão SQL Server: uma string de conexão, os limites de linhas e tempo limite que se aplicam a ela e se gravações são permitidas. Um perfil chamado default sempre existe; toda ferramenta aceita um profile opcional para alcançar outro, e list_profiles informa o que está configurado.

As configurações vêm de três fontes, mescladas campo a campo, vencendo a última:

  1. o appsettings.json no escopo do usuário — qualquer número de perfis;
  2. variáveis de ambiente McpMssql__Profiles__<NAME>__<FIELD> — qualquer número de perfis;
  3. variáveis de ambiente MCPMSSQL_<FIELD> simples — apenas o perfil default.

Como a mesclagem é por campo e não por perfil, um appsettings.json pode conter o conjunto completo enquanto um MCPMSSQL_CONNECTION_STRING simples redireciona o perfil padrão para um servidor local, deixando seus outros campos intactos. Não há string de conexão de fallback: um perfil sem uma — incluindo um default que nada configurou — falha na inicialização.

Cada configuração tem um nome de campo, escrito de três maneiras — o caminho JSON sob McpMssql:Profiles:<NAME>, esse mesmo caminho com : substituído por __ como variável de ambiente, ou a forma simples:

CampoVariável simplesPadrãoLimite máximo
ConnectionStringMCPMSSQL_CONNECTION_STRINGobrigatório—
DescriptionMCPMSSQL_DESCRIPTIONnenhum—
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

Os limites são por perfil. Um valor acima do teto — ou abaixo de 1 — é ajustado na inicialização e o ajuste é registrado como aviso em stderr; um valor simples que não seja um inteiro, ou não seja booleano para AllowWrite, é ignorado, deixando o que as outras fontes definiram.

Conexão única: variáveis de ambiente simples são o caminho mais curto.

# 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"

Múltiplas conexões: use o appsettings.json no escopo do usuário, que mantém as credenciais fora do ambiente de processo do host.

  • Unix-like: ~/.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
        }
      }
    }
  }
}

Os nomes de perfil não diferenciam maiúsculas de minúsculas, e a forma de ambiente estruturada divide em __, então McpMssql__Profiles__WAREHOUSE__ConnectionString é o perfil warehouse, campo ConnectionString. Um appsettings.json ausente é aceitável — o servidor inicia com as fontes restantes — mas um que não seja JSON válido falha na inicialização.

Desenvolvimento local: armazene a string de conexão em segredos do usuário e execute com DOTNET_ENVIRONMENT=Development para que segredos e um appsettings.json do diretório de trabalho sejam carregados como fontes extras.

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

Sintaxe da string de conexão: as palavras-chave usuais Server=host,port;Database=db;User ID=...;Password=...;Encrypt=True; do Microsoft.Data.SqlClient, que também suporta autenticação Microsoft Entra (Azure AD): defina Authentication para um modo suportado (por exemplo, Active Directory Default, Active Directory Managed Identity ou Active Directory Interactive) ao conectar ao Azure SQL. Consulte Connect to Azure SQL with Microsoft Entra authentication and SqlClient para todos os modos e detalhes.

Ferramentas e recursos

Todas as ferramentas aceitam um profile opcional; quando omitido, o perfil padrão é usado.

Ferramentas

FerramentaDescriçãoParâmetros principais
list_profilesLista os perfis de conexão configurados. Chame primeiro ao escolher um perfil não padrão. Retorna name, description e allow_write por perfil.—
get_objectObtém metadados para uma relação (colunas, índices, restrições, relacionamentos) ou rotina (definição). name aceita Users, dbo.Users ou [dbo].[Users]. includes omitido → columns. Relações também carregam um row_count aproximado.kind, name, profile, catalog, schema, includes
run_queryExecuta SELECT T-SQL somente leitura; apenas SELECT é permitido (sem DML/DDL). Retorna resultados como CSV no campo data (inline) ou um URI de recurso de snapshot quando snapshot=true. Limite inline: 500 linhas (teto máximo 1000). Limite de snapshot: 10 000 linhas (teto máximo 50 000). Prefira analyze_query para ajuste de plano.sql, profile, catalog, parameters, snapshot
analyze_queryAnalisa o plano de execução para um SELECT somente leitura. Retorna resumo JSON compacto (custo, operadores, cardinalidade, avisos, missing_indexes, esperas, estatísticas); sem linhas de resultado, XML completo em plan_uri.sql, profile, catalog, parameters, estimated
run_commandExecuta T-SQL de gravação (DDL/DML). Anunciado apenas quando algum perfil define AllowWrite=true (desativado por padrão); ainda rejeitado no momento da chamada quando o profile alvo está bloqueado. O chamador gerencia transações. Retorna rows_affected (−1 para DDL) e messages do servidor. Marcado como destrutivo; destinado ao uso supervisionado por humanos.sql, profile, catalog, parameters
  • kind — relation ou routine.
  • includes — Matriz de seções de detalhes: columns, indexes, constraints, relationships (somente relações), definition (somente rotinas). relationships retorna chaves estrangeiras em ambas as direções.

A navegação do catálogo é deixada para run_query sobre sys.objects, sys.schemas e sys.databases. get_object aceita o missing_indexes[].table de analyze_query como está.

Recursos

Modelo de URIDescrição
mssql://profilesLista os perfis de conexão configurados, incluindo allow_write. Mesmos dados que list_profiles.
mssql://plans/{id}Recupera o plano de execução XML completo por ID de analyze_query; as entradas expiram após 7 dias.
mssql://snapshots/{id}Recupera o resultado completo da consulta como CSV por ID de run_query (snapshot=true); as entradas expiram após 7 dias.

Planos e snapshots são gravados em disco, sob ~/.cache/mcp-mssql/plans/ e ~/.cache/mcp-mssql/snapshots/ (%USERPROFILE%\.cache\mcp-mssql\ no Windows). Arquivos expirados são varridos na primeira vez que o servidor toca o armazenamento.

Segurança

As ferramentas de consulta (run_query, analyze_query) são somente leitura (apenas SELECT) e usam vinculação @paramName parametrizada. Use variáveis de ambiente, arquivo de configuração ou segredos do usuário para strings de conexão — nunca comprometa segredos.

O que conta como somente leitura. O SQL é analisado com ScriptDom e deve ser exatamente uma instrução SELECT em um único lote — não apenas texto que começa com SELECT. Scripts de múltiplas instruções e separados por GO são rejeitados, e também estes, apesar de serem sintaticamente SELECTs:

RejeitadoMotivo
SELECT ... INTOMaterializa uma nova tabela.
SELECT @v = ...Atribui uma variável, mutando o estado da sessão.
NEXT VALUE FORAvança uma sequência.
OPENQUERY, OPENDATASOURCE, OPENROWSET, OPENROWSET(BULK ...)Lê através de uma fonte de dados externa ad-hoc.
UPDLOCK, XLOCK, TABLOCK, TABLOCKX, HOLDLOCK, SERIALIZABLE, REPEATABLEREADAdquire bloqueios que impedem escritores concorrentes.

Dicas que não adquirem bloqueios extras, como NOLOCK, ROWLOCK e READPAST, permanecem permitidas. Entrada maior que 64 KB ou aninhada mais de 100 parênteses de profundidade também é recusada, o que mantém o parser descendente recursivo longe de estouro de pilha.

Como AllowWrite abaixo, isso restringe o que este servidor enviará — não é uma permissão de banco de dados.

Gravações são opcionais e invisíveis até então. A ferramenta run_command executa T-SQL arbitrário. A menos que pelo menos um perfil configurado defina AllowWrite=true (padrão false), a ferramenta não é registrada — ela nunca aparece em tools/list, então uma implantação somente leitura não gasta contexto com ela e não oferece superfície de gravação para um agente ser persuadido. Uma vez que qualquer perfil opta, a ferramenta é anunciada em todo o servidor e ainda rejeita no momento da chamada em perfis que permanecem bloqueados; list_profiles relata allow_write por perfil para que um agente possa escolher um gravável.

AllowWrite é um guarda suave, de nível de aplicação, não um limite de segurança — ele restringe este servidor, não o banco de dados. Para uma garantia genuína de somente leitura, conecte com um login restrito a db_datareader, e mantenha perfis habilitados para gravação apontados para credenciais com escopo apenas para o que precisam. run_command é marcado como destructive via anotações de ferramenta MCP para que hosts possam bloqueá-lo atrás de confirmação, mas honre essas anotações a critério do host.

Exemplos de host MCP

Substitua a string de conexão pela sua; garanta que dotnet esteja no seu PATH. O bloco env é desnecessário quando a string de conexão já vem de appsettings.json ou do ambiente.

Claude Code, Cursor e Gemini todos leem a mesma 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 e 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;"
      }
    }
  }
}

Testes de integração

Os testes usam um SQL Server real e o perfil default (MCPMSSQL_CONNECTION_STRING de variáveis de ambiente ou segredos do usuário). A suíte espera um banco de dados chamado McpMssqlTest: a string de conexão deve incluir Initial Catalog=McpMssqlTest. A infraestrutura de teste cria, popula e descarta este banco de dados. Defina o segredo para o projeto de teste:

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

Um framework por vez. O único banco de dados McpMssqlTest é compartilhado por todos os testes, e os fixtures o descartam e recriam na inicialização. Dentro de um processo de teste isso é seguro — a coleção SqlServer desativa a paralelização. Entre processos não é: o projeto de teste tem como alvo tanto net8.0 quanto net10.0, e dotnet test executa os dois módulos de framework em paralelo, então eles competem nesse único banco de dados. Não há bloqueio entre processos, então execute um único framework por vez:

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

CI faz o mesmo, iterando sobre TARGET_FRAMEWORKS sequencialmente.

Por que isso em vez de Data API Builder?

Data API Builder (DAB) é uma API REST/GraphQL completa com CRUD e autenticação. Este projeto é um pequeno servidor MCP somente leitura para agentes: stdio, SELECT parametrizado apenas, superfície mínima. Escolha isso para fluxos de trabalho de agente e baixa sobrecarga operacional; escolha DAB para CRUD, REST/GraphQL e políticas ricas.

Roadmap

Extensão MCP Tasks (SEP-2663). Consultas de snapshot e análise de plano de execução rodam sob timeouts longos (120 s e 300 s por padrão), que é a forma para a qual a extensão Tasks existe: o servidor retorna um identificador de tarefa durável em vez de bloquear, e o cliente consulta tasks/get até que o trabalho alcance um estado terminal. O ajuste é bom; a adoção é o bloqueio. Tasks é uma extensão opcional (io.modelcontextprotocol/tasks) que um servidor só pode usar quando o cliente declara suporte em suas capacidades por solicitação, e nenhum cliente atualmente a lista na matriz de suporte a extensões. Adiado até que os clientes implementem suporte.

Compatibilidade de esquema

Membros anuláveis emitem um tipo de união JSON Schema — "type": ["string", "null"] — porque é isso que System.Text.Json produz para string? e similares. É JSON Schema 2020-12 válido e permitido pela especificação MCP. O MCP Inspector emite um aviso sobre o formato, com base no argumento de que alguns clientes MCP leem type como uma única string; se essa regra ainda tem evidências por trás dela está sob revisão upstream. Reescrever para anyOf não é uma vitória clara: a OpenAI documenta o formato de união para parâmetros opcionais, a Anthropic suporta anyOf e não arrays de tipos, e Cursor, Gemini e Azure AI Foundry rejeitam anyOf.

Nada se perde ao ignorar o ramo nulo. Este servidor nunca serializa null — membros ausentes são omitidos em vez de enviados como null — e nenhum membro anulável aparece em uma lista required, então um cliente que lê apenas o primeiro tipo da união obtém o contrato exato. Mantido como o SDK emite; revisitar se o SDK mudar ou a regra for definida.

Contribuindo

Abra issues ou PRs; siga o estilo existente e adicione testes quando apropriado.

Licença

MIT