mcp-clickhousex

Um servidor MCP somente leitura para ClickHouse que suporta descoberta de metadados, consultas parametrizadas e análise de consultas.

Documentação

Ferramenta MCP ClickHouse

CI PyPI Python 3.13+ License: MIT

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

Somente leitura é imposto pelo mecanismo, não pela correspondência de texto SQL: os clientes das ferramentas de consulta carregam o readonly=1 do ClickHouse, portanto gravações, funções de tabela externa e SETTINGS em nível de consulta são recusados pelo servidor consultado. As gravações ficam atrás de uma ferramenta separada que não é registrada até que um perfil a solicite.

Requisitos: Python 3.13+, uma instância ClickHouse em execução e detalhes de conexão por meio de variáveis de ambiente ou um arquivo de configuração.

Início rápido

Defina um DSN e execute o servidor com o MCP Inspector:

# Option 1: Run directly with uvx (no clone needed)
export MCP_CLICKHOUSE_DSN="http://default:@localhost:8123/default"
npx -y @modelcontextprotocol/inspector@latest uvx mcp-clickhousex
# Option 2: Run from source (clone repo, then)
export MCP_CLICKHOUSE_DSN="http://default:@localhost:8123/default"
npx -y @modelcontextprotocol/inspector@latest uv run mcp-clickhousex

Configuração

Um perfil é uma conexão ClickHouse: um DSN mais os limites de linha e tempo limite que se aplicam a ele. Um perfil chamado default sempre existe; toda ferramenta aceita um profile opcional para alcançar outro, e list_profiles relata o que está configurado.

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

  1. o config.json com escopo de usuário — qualquer número de perfis;
  2. variáveis de ambiente MCP_CLICKHOUSE_PROFILES_<NAME>_<FIELD> — qualquer número de perfis;
  3. variáveis de ambiente MCP_CLICKHOUSE_<FIELD> simples — apenas o perfil default.

Como a mesclagem é por campo e não por perfil, um config.json pode carregar o conjunto completo enquanto um MCP_CLICKHOUSE_DSN simples redireciona o perfil padrão para um servidor local, deixando seus outros campos intactos. Com nenhuma das três presentes, default recai em http://default:@localhost:8123/default.

Cada configuração tem um nome de campo, escrito de três maneiras — MCP_CLICKHOUSE_<FIELD>, MCP_CLICKHOUSE_PROFILES_<NAME>_<FIELD> ou o campo em minúsculas como chave JSON:

CampoPadrãoLimite máximo
DSNhttp://default:@localhost:8123/default—
DESCRIPTIONnenhum—
QUERY_MAX_ROWS5001 000
QUERY_COMMAND_TIMEOUT_SECONDS30300
SNAPSHOT_MAX_ROWS10 00050 000
SNAPSHOT_COMMAND_TIMEOUT_SECONDS120300
ALLOW_WRITEfalse—
WRITE_COMMAND_TIMEOUT_SECONDS60600

Os limites são por perfil. Um valor acima do teto é limitado na inicialização; um valor que não é um inteiro recai no padrão, e um valor que não é booleano deixa ALLOW_WRITE desligado.

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

# Connection DSN.
export MCP_CLICKHOUSE_DSN="http://user:password@host:8123/database"

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

# Optional caps, defaults shown.
export MCP_CLICKHOUSE_QUERY_MAX_ROWS="500"
export MCP_CLICKHOUSE_QUERY_COMMAND_TIMEOUT_SECONDS="30"
export MCP_CLICKHOUSE_SNAPSHOT_MAX_ROWS="10000"
export MCP_CLICKHOUSE_SNAPSHOT_COMMAND_TIMEOUT_SECONDS="120"

# Optional write access, off by default; also controls whether run_command
# is advertised at all.
export MCP_CLICKHOUSE_ALLOW_WRITE="false"
export MCP_CLICKHOUSE_WRITE_COMMAND_TIMEOUT_SECONDS="60"

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

  • Semelhante a Unix: ~/.config/mcp-clickhousex/config.json
  • Windows: %USERPROFILE%\.config\mcp-clickhousex\config.json
{
  "profiles": {
    "default": {
      "dsn": "http://default:@localhost:8123/default",
      "description": "Primary",
      "query_max_rows": 500,
      "query_command_timeout_seconds": 60,
      "snapshot_max_rows": 10000,
      "snapshot_command_timeout_seconds": 120
    },
    "warehouse": {
      "dsn": "http://user:pass@warehouse:8123/analytics",
      "description": "Warehouse"
    },
    "writer": {
      "dsn": "http://etl:pass@warehouse:8123/analytics",
      "description": "Warehouse, write-enabled",
      "allow_write": true,
      "write_command_timeout_seconds": 120
    }
  }
}

Nada impede que um perfil leia e grave, mas um perfil separado habilitado para gravação é a forma que vale a pena copiar: dá às gravações seu próprio DSN, para que as credenciais por trás delas possam ser limitadas ao que realmente precisam, enquanto os perfis de leitura permanecem em um login cujas concessões param em SELECT.

Os nomes de perfil não diferenciam maiúsculas de minúsculas e devem ser alfanuméricos — sem sublinhados ou hífens, pois a forma de ambiente estruturada divide em _ (MCP_CLICKHOUSE_PROFILES_WAREHOUSE_DSN é o perfil warehouse, campo DSN). Um nome que quebra a regra é ignorado, assim como um config.json que está ausente, ilegível ou não tem a forma {"profiles": {…}}; o servidor inicia com as fontes restantes em vez de falhar.

Sintaxe DSN: scheme://user:password@host:port/database. Um esquema https:// ou clickhouses:// habilita TLS, e os parâmetros da string de consulta alcançam o driver (?connect_timeout=10) — exceto readonly, que o servidor sempre aplica por último, a partir do ALLOW_WRITE do perfil.

Caracteres reservados de URL no nome de usuário ou senha devem ser codificados em percentual — # → %23, ? → %3F, / → %2F, @ → %40, % → %25. Nome de usuário admin@org com senha p#ss? torna-se http://admin%40org:p%23ss%3F@host:8123/database.

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.—
run_queryExecuta uma instrução SELECT somente leitura (CTEs permitidos) ou SHOW. Retorna linhas inline como CSV, ou um URI chx://snapshots/{id} quando snapshot=true. Limite inline: 500 linhas (teto máximo 1 000). Limite de snapshot: 10 000 linhas (teto máximo 50 000). Sem INTO OUTFILE.sql, parameters, database, profile, snapshot
analyze_queryEXPLAIN um SELECT somente leitura; retorna plano, pipeline ou sintaxe, sem linhas de resultado. SHOW não é um alvo EXPLAIN.sql, parameters, database, profile, types
run_commandExecuta uma instrução de gravação (DDL/DML). Anunciada apenas quando algum perfil define ALLOW_WRITE (desligado por padrão); ainda recusada no momento da chamada quando o profile de destino está bloqueado. Retorna written_rows, written_bytes e query_id. Marcada como destrutiva; destinada ao uso supervisionado por humanos.sql, parameters, database, profile
  • types — variantes EXPLAIN: plan (índices), pipeline, syntax. Padrão para plan e pipeline.
  • parameters — Parâmetros nomeados para placeholders do driver, %(name)s ou {name:Type}.
  • database — Banco de dados padrão da sessão para nomes não qualificados; caso contrário, qualifique como db.table.

A descoberta de catálogo não tem ferramenta dedicada: liste bancos de dados, tabelas e colunas — e leia tamanhos (total_rows, total_bytes) e chaves (primary_key, sorting_key, partition_key) — com run_query sobre system.databases, system.tables e system.columns, que aceitam predicados WHERE comuns onde SHOW aceita apenas LIKE. SHOW ganha seu lugar para DDL que uma listagem não pode fornecer — SHOW CREATE TABLE/VIEW/DICTIONARY para codecs, TTLs e a lista completa de colunas.

Os resultados são CSV RFC 4180: a primeira linha é o cabeçalho, o restante são dados. NULL é escrito como \N, a representação nula CSV do próprio ClickHouse, para que permaneça distinto da string vazia.

A seção Indexes de um plano não é autoritativa sobre as chaves de uma tabela: ela nomeia apenas as colunas de chave que a consulta usou, então uma consulta que pula a coluna de chave principal relata uma chave mais curta do que a tabela tem. Confirme a partir de system.tables, que responde em algumas dezenas de tokens onde SHOW CREATE TABLE gasta várias centenas para dizer o mesmo.

Os limites de linha aplicados a uma chamada chegam com seu resultado como truncated e row_limit.

Recursos

URIDescrição
chx://profilesLista os perfis de conexão configurados, incluindo allow_write (application/json). Mesmos dados que list_profiles.
chx://snapshots/{id}Busca um snapshot de resultado de consulta como CSV; id vem do snapshot_uri que run_query retorna. Expira após 7 dias.

Segurança

Todo cliente que este servidor abre carrega o próprio readonly=1 do ClickHouse, então o mecanismo — não apenas as verificações SQL do servidor — recusa:

  • gravações de qualquer tipo (INSERT, DDL, ALTER … UPDATE, SYSTEM, GRANT);
  • as funções de tabela externa url(), s3(), remote(), mysql() e similares, para que uma consulta não possa alcançar um host fora do perfil configurado;
  • SETTINGS em nível de consulta, para que os limites de linha e tempo não possam ser aumentados pelo SQL que um agente fornece, e INTO OUTFILE é recusado.

readonly=2 é deliberadamente não usado: permite alterações SETTINGS, o que tornaria esses limites consultivos. A compensação é que ajustes benignos por consulta (SETTINGS max_threads = …) também são recusados.

Além disso, run_query aceita SELECT / WITH … SELECT / SHOW e analyze_query apenas os dois primeiros, uma instrução por chamada. Consultas interativas impõem um limite de linha apertado (padrão 500, teto máximo 1 000); para extrações maiores use snapshot=true (padrão 10 000, teto máximo 50 000).

Gravações são opcionais e invisíveis até então. run_command roda em um cliente que carrega readonly=0, então executa DDL e DML arbitrários — e, com readonly levantado, as funções de tabela externa também voltam. A menos que pelo menos um perfil configurado defina ALLOW_WRITE (padrão false), a ferramenta não é registrada: 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 que um agente possa ser persuadido a usar. Uma vez que qualquer perfil opta, a ferramenta é anunciada em todo o servidor e ainda é recusada 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.

ALLOW_WRITE é uma proteção suave, em nível de aplicação, não um limite de segurança — restringe este servidor, não o banco de dados. Para uma garantia genuína de somente leitura, conecte-se com um login cujas concessões do ClickHouse param em SELECT, e mantenha perfis habilitados para gravação apontados para credenciais limitadas apenas ao que precisam. run_command carrega anotações de ferramenta destructive e openWorld para que os hosts possam bloqueá-la atrás de confirmação, mas honrar essas anotações é escolha do host. O ClickHouse não tem transação para reverter aqui: uma instrução que chega, permanece.

Use variáveis de ambiente ou o arquivo de configuração para credenciais de conexão — nunca confirme segredos.

Exemplos de host MCP

Os trechos usam uvx mcp-clickhousex (sem necessidade de clone; garanta que uv esteja no seu PATH). Substitua os detalhes de conexão conforme necessário; o bloco env é desnecessário quando o DSN já vem de config.json ou do ambiente.

Claude Code e Cursor leem a mesma forma mcpServers:

{
  "mcpServers": {
    "clickhouse": {
      "command": "uvx",
      "args": ["mcp-clickhousex"],
      "env": {
        "MCP_CLICKHOUSE_DSN": "http://default:@localhost:8123/default"
      }
    }
  }
}
Codex, OpenCode e GitHub Copilot

Codex (TOML):

[mcp_servers.clickhouse]
command = "uvx"
args = ["mcp-clickhousex"]

[mcp_servers.clickhouse.env]
MCP_CLICKHOUSE_DSN = "http://default:@localhost:8123/default"

OpenCode:

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "clickhouse": {
      "type": "local",
      "enabled": true,
      "command": ["uvx", "mcp-clickhousex"],
      "environment": {
        "MCP_CLICKHOUSE_DSN": "http://default:@localhost:8123/default"
      }
    }
  }
}

GitHub Copilot:

{
  "inputs": [],
  "servers": {
    "clickhouse": {
      "type": "stdio",
      "command": "uvx",
      "args": ["mcp-clickhousex"],
      "env": {
        "MCP_CLICKHOUSE_DSN": "http://default:@localhost:8123/default"
      }
    }
  }
}

Testes

Os testes exigem uma instância ClickHouse em execução; a suíte cria uma tabela de exemplo no banco de dados padrão, a popula e a remove depois.

uv run pytest tests/ -v

O harness localiza a instância por meio de MCP_TEST_CLICKHOUSE_DSN, recaindo em http://admin:password123@localhost:8123/default. Defina-o para apontar os testes para outro servidor sem tocar no seu MCP_CLICKHOUSE_DSN de produção.

A suíte configura dois perfis nessa única instância — um default somente leitura e um writable habilitado para gravação — para que ambas as metades do portão de gravação sejam exercitadas: run_command é anunciado porque um perfil opta, e ainda é recusado contra o perfil que não opta.

Contribuindo

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

Licença

MIT