mcp-clickhousex

Un servidor MCP de solo lectura para ClickHouse que admite descubrimiento de metadatos, consultas parametrizadas y análisis de consultas.

Documentación

Servidor MCP ClickHouse

CI PyPI Python 3.13+ License: MIT

Un servidor Model Context Protocol (MCP) para ClickHouse, de solo lectura por defecto, que proporciona descubrimiento de esquemas, consultas de solo lectura, análisis de planes de ejecución, escrituras opcionales y acceso basado en perfiles a múltiples servidores desde un único despliegue de herramientas.

La restricción de solo lectura la aplica el motor, no la coincidencia de texto SQL: los clientes de las herramientas de consulta llevan el readonly=1 de ClickHouse, por lo que las escrituras, las funciones de tabla externas y el SETTINGS a nivel de consulta son rechazados por el servidor consultado. Las escrituras viven detrás de una herramienta separada que no se registra en absoluto hasta que un perfil lo solicita.

Requisitos: Python 3.13+, una instancia de ClickHouse en ejecución y detalles de conexión mediante variables de entorno o un archivo de configuración.

Inicio rápido

Establezca un DSN y ejecute el servidor con 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

Configuración

Un perfil es una conexión de ClickHouse: un DSN más los límites de filas y tiempo de espera que se le aplican. Un perfil llamado default siempre existe; cada herramienta acepta un profile opcional para llegar a otro, y list_profiles informa de lo que está configurado.

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

  1. el config.json a nivel de usuario — cualquier número de perfiles;
  2. las variables de entorno MCP_CLICKHOUSE_PROFILES_<NAME>_<FIELD> — cualquier número de perfiles;
  3. las variables de entorno planas MCP_CLICKHOUSE_<FIELD> — solo el perfil default.

Debido a que la fusión es por campo y no por perfil, un config.json puede llevar el conjunto completo mientras que un MCP_CLICKHOUSE_DSN plano redirige el perfil predeterminado a un servidor local, dejando sus otros campos intactos. Con ninguna de las tres presentes, default recurre a http://default:@localhost:8123/default.

Cada ajuste tiene un nombre de campo, escrito de tres maneras — MCP_CLICKHOUSE_<FIELD>, MCP_CLICKHOUSE_PROFILES_<NAME>_<FIELD>, o el campo en minúsculas como clave JSON:

CampoPredeterminadoLímite máximo
DSNhttp://default:@localhost:8123/default—
DESCRIPTIONninguno—
QUERY_MAX_ROWS5001 000
QUERY_COMMAND_TIMEOUT_SECONDS30300
SNAPSHOT_MAX_ROWS10 00050 000
SNAPSHOT_COMMAND_TIMEOUT_SECONDS120300
ALLOW_WRITEfalse—
WRITE_COMMAND_TIMEOUT_SECONDS60600

Los límites son por perfil. Un valor por encima de su techo se ajusta al inicio; un valor que no es un entero recurre al predeterminado, y un valor que no es booleano deja ALLOW_WRITE desactivado.

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

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

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

  • Unix-like: ~/.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 impide que un perfil lea y escriba, pero un perfil separado habilitado para escritura es la forma que vale la pena copiar: le da a las escrituras su propio DSN, de modo que las credenciales detrás de ellas pueden limitarse a lo que realmente necesitan, mientras que los perfiles de lectura permanecen en un inicio de sesión cuyos permisos se detienen en SELECT.

Los nombres de perfil no distinguen entre mayúsculas y minúsculas y deben ser alfanuméricos — sin guiones bajos ni guiones, ya que la forma de entorno estructurado se divide en _ (MCP_CLICKHOUSE_PROFILES_WAREHOUSE_DSN es el perfil warehouse, campo DSN). Un nombre que rompe la regla se omite, al igual que un config.json que falta, no se puede leer o no tiene la forma {"profiles": {…}}; el servidor se inicia con las fuentes restantes en lugar de fallar.

Sintaxis DSN: scheme://user:password@host:port/database. Un esquema https:// o clickhouses:// habilita TLS, y los parámetros de la cadena de consulta llegan al controlador (?connect_timeout=10) — excepto readonly, que el servidor siempre aplica al final, desde el ALLOW_WRITE del perfil.

Los caracteres reservados de URL en el nombre de usuario o la contraseña deben estar codificados en porcentaje — # → %23, ? → %3F, / → %2F, @ → %40, % → %25. El nombre de usuario admin@org con la contraseña p#ss? se convierte en http://admin%40org:p%23ss%3F@host:8123/database.

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.—
run_queryEjecuta una declaración SELECT de solo lectura (CTEs permitidos) o SHOW. Devuelve filas en línea como CSV, o un URI chx://snapshots/{id} cuando snapshot=true. Límite en línea: 500 filas (techo máximo 1 000). Límite de instantánea: 10 000 filas (techo máximo 50 000). Sin INTO OUTFILE.sql, parameters, database, profile, snapshot
analyze_queryEXPLAIN un SELECT de solo lectura; devuelve plan, canalización o sintaxis, sin filas de resultado. SHOW no es un objetivo de EXPLAIN.sql, parameters, database, profile, types
run_commandEjecuta una declaración de escritura (DDL/DML). Se anuncia solo cuando algún perfil establece ALLOW_WRITE (desactivado por defecto); aún se rechaza en el momento de la llamada cuando el profile objetivo está bloqueado. Devuelve written_rows, written_bytes y query_id. Marcada como destructiva; destinada a uso supervisado por humanos.sql, parameters, database, profile
  • types — variantes de EXPLAIN: plan (índices), pipeline, syntax. Predeterminado a plan y pipeline.
  • parameters — Parámetros nombrados para marcadores de posición del controlador, %(name)s o {name:Type}.
  • database — Base de datos predeterminada de la sesión para nombres no calificados; de lo contrario, califique como db.table.

El descubrimiento de catálogo no tiene una herramienta dedicada: liste bases de datos, tablas y columnas — y lea tamaños (total_rows, total_bytes) y claves (primary_key, sorting_key, partition_key) — con run_query sobre system.databases, system.tables y system.columns, que aceptan predicados WHERE ordinarios donde SHOW solo acepta LIKE. SHOW gana su lugar para DDL que un listado no puede darle — SHOW CREATE TABLE/VIEW/DICTIONARY para códecs, TTLs y la lista completa de columnas.

Los resultados son CSV RFC 4180: la primera fila es el encabezado, el resto son datos. NULL se escribe como \N, la representación CSV nula de ClickHouse, por lo que permanece distinta de la cadena vacía.

La sección Indexes de un plan no es autoritativa sobre las claves de una tabla: solo nombra las columnas clave que la consulta usó, por lo que una consulta que omite la columna clave principal informa una clave más corta de lo que la tabla tiene. Confirme desde system.tables, que responde en unas pocas docenas de tokens donde SHOW CREATE TABLE gasta varios cientos para decir lo mismo.

Los límites de filas que se aplicaron a una llamada llegan con su resultado como truncated y row_limit.

Recursos

URIDescripción
chx://profilesLista los perfiles de conexión configurados, incluido allow_write (application/json). Mismos datos que list_profiles.
chx://snapshots/{id}Obtiene una instantánea de resultado de consulta como CSV; id proviene del snapshot_uri que run_query devuelve. Expira después de 7 días.

Seguridad

Cada cliente que este servidor abre lleva el readonly=1 propio de ClickHouse, por lo que el motor — no solo las comprobaciones SQL del servidor — rechaza:

  • escrituras de cualquier tipo (INSERT, DDL, ALTER … UPDATE, SYSTEM, GRANT);
  • las funciones de tabla externas url(), s3(), remote(), mysql() y similares, para que una consulta no pueda alcanzar un host fuera del perfil configurado;
  • el SETTINGS a nivel de consulta, para que los límites de filas y tiempo no puedan aumentarse mediante el SQL que un agente proporciona, y INTO OUTFILE se rechaza.

readonly=2 se usa deliberadamente: permite cambios de SETTINGS, lo que haría que esos límites fueran solo recomendaciones. La compensación es que el ajuste benigno por consulta (SETTINGS max_threads = …) también se rechaza.

Además de eso, run_query acepta SELECT / WITH … SELECT / SHOW y analyze_query solo los dos primeros, una declaración por llamada. Las consultas interactivas aplican un límite de filas estricto (predeterminado 500, techo máximo 1 000); para extracciones más grandes use snapshot=true (predeterminado 10 000, techo máximo 50 000).

Las escrituras son opcionales e invisibles hasta entonces. run_command se ejecuta en un cliente que lleva readonly=0, por lo que ejecuta DDL y DML arbitrarios — y, con readonly levantado, las funciones de tabla externas también vuelven. A menos que al menos un perfil configurado establezca ALLOW_WRITE (predeterminado false), la herramienta no se registra en absoluto: nunca aparece en tools/list, por lo que un despliegue de solo lectura no gasta contexto en ella y no ofrece ninguna superficie de escritura en la que un agente pueda ser persuadido. Una vez que cualquier perfil opta, la herramienta se anuncia en todo el servidor y aún se 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.

ALLOW_WRITE 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 cuyos permisos de ClickHouse se detengan en SELECT, y mantenga los perfiles habilitados para escritura apuntando a credenciales limitadas solo a lo que necesitan. run_command lleva anotaciones de herramienta destructive y openWorld para que los hosts puedan protegerla detrás de confirmación, pero honrar esas anotaciones es elección del host. ClickHouse no tiene transacción que revertir aquí: una declaración que llega, permanece.

Use variables de entorno o el archivo de configuración para las credenciales de conexión — nunca confíe secretos.

Ejemplos de hosts MCP

Los fragmentos usan uvx mcp-clickhousex (sin necesidad de clonar; asegúrese de que uv esté en su PATH). Reemplace los detalles de conexión según sea necesario; el bloque env es innecesario cuando el DSN ya proviene de config.json o del entorno.

Claude Code y Cursor leen la misma forma mcpServers:

{
  "mcpServers": {
    "clickhouse": {
      "command": "uvx",
      "args": ["mcp-clickhousex"],
      "env": {
        "MCP_CLICKHOUSE_DSN": "http://default:@localhost:8123/default"
      }
    }
  }
}
Codex, OpenCode y 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"
      }
    }
  }
}

Pruebas

Las pruebas requieren una instancia de ClickHouse en ejecución; la suite crea una tabla de muestra en la base de datos predeterminada, la siembra y la elimina después.

uv run pytest tests/ -v

El arnés localiza la instancia a través de MCP_TEST_CLICKHOUSE_DSN, recurriendo a http://admin:password123@localhost:8123/default. Configúrelo para apuntar las pruebas a otro servidor sin tocar su MCP_CLICKHOUSE_DSN de producción.

La suite configura dos perfiles en esa única instancia — un default de solo lectura y un writable habilitado para escritura — para que ambas mitades de la puerta de escritura se ejerciten: run_command se anuncia porque un perfil opta, y aún se rechaza contra el perfil que no lo hace.

Contribuciones

Abra problemas o PRs; siga el estilo existente y agregue pruebas donde corresponda.

Licencia

MIT