developmentTechArticle

Building a ClickHouse MCP Server

Query a ClickHouse columnar warehouse from an MCP tool, with an enforced row limit and read-only guardrails.

Quick Answer / TL;DR

A ClickHouse MCP server uses the official @clickhouse/client package to run read-only SQL over the HTTP interface, with an enforced LIMIT so a model-generated query can't return an unbounded result set.

Key Takeaways

  • ClickHouse is column-oriented - selecting only the columns actually needed matters far more for performance than in a row-oriented database.
  • Append a LIMIT to any query that doesn't already have one before executing it, rather than trusting the model to include one.
  • Reject INSERT/ALTER/DROP the same way a read-only Postgres tool would - ingestion belongs in a dedicated pipeline, not a query tool.

Why not just copy a reference implementation?

The official MCP servers repository states its own scope plainly: "The servers in this repository are intended as reference implementations to demonstrate MCP features and SDK usage... not as production-ready solutions." A tool built by following that same educational pattern against ClickHouse typically has no row cap and no rejection of write statements - both are added explicitly below, not inherited for free from the SDK.

A row-limited query tool

The @clickhouse/client package's query() method returns a stream-like result you resolve to JSON; wrapping every query with a LIMIT check before execution keeps a single bad query from returning millions of rows to the model.

typescript
import { createClient } from "@clickhouse/client";

const client = createClient({
  url: process.env.CLICKHOUSE_URL ?? "http://localhost:8123",
  username: process.env.CLICKHOUSE_USER,
  password: process.env.CLICKHOUSE_PASSWORD,
});

async function runQuery(sql: string, maxRows = 500) {
  if (/\b(INSERT|ALTER|DROP|TRUNCATE)\b/i.test(sql)) {
    throw new Error("Only SELECT queries are permitted through this tool.");
  }
  const bounded = /LIMIT\s+\d+/i.test(sql) ? sql : `${sql} LIMIT ${maxRows}`;

  const resultSet = await client.query({ query: bounded, format: "JSONEachRow" });
  return resultSet.json();
}

Building a ClickHouse MCP Server FAQs

Direct answers for developers, operators, and Indian teams evaluating MCP.

M
MCPserver Team

MCP documentation and protocol implementation team

Published: 2026-07-21
Updated: 2026-07-21