# Quickstart (/docs/mcp/quickstart)

By the end of this page an AI agent will answer questions about one of your ClickHouse tables by calling tools you
defined, not by writing SQL. You define a dataset, add it to your API, and run `hypequery mcp`. The same command serves
it over HTTP when you're ready to share it.

You need a ClickHouse server you can query, an MCP client (Claude Code, Claude Desktop or Cursor), and Node.js 20+ or
Python 3.11+. The examples use an `orders` table with `id`, `status`, `country`, `amount` and `created_at` columns;
swap in your own table and columns.

## 1. Install [#1-install]

<Tabs groupId="language" items={['TypeScript', 'Python']} persist>
  <Tab value="TypeScript">
    <CodeBlock>
      ```bash
      npm install @hypequery/clickhouse @hypequery/datasets @hypequery/serve @hypequery/mcp
      npm install -D @hypequery/cli
      ```
    </CodeBlock>
  </Tab>

  <Tab value="Python">
    <CodeBlock>
      ```bash
      pip install "hypequery[clickhouse,mcp]"
      ```
    </CodeBlock>
  </Tab>
</Tabs>

## 2. Point it at ClickHouse [#2-point-it-at-clickhouse]

Create `.env` in your project folder. `hypequery mcp` reads it from the folder it runs in.

<Tabs groupId="language" items={['TypeScript', 'Python']} persist>
  <Tab value="TypeScript">
    <CodeBlock>
      ```bash
      CLICKHOUSE_URL=https://your-host.clickhouse.cloud:8443
      CLICKHOUSE_USERNAME=default
      CLICKHOUSE_PASSWORD=your-password
      CLICKHOUSE_DATABASE=default
      ```
    </CodeBlock>
  </Tab>

  <Tab value="Python">
    <CodeBlock>
      ```bash
      CLICKHOUSE_URL=https://default:your-password@your-host.clickhouse.cloud:8443/default
      ```
    </CodeBlock>

    Or set `CLICKHOUSE_HOST`, `_PORT`, `_DATABASE`, `_USERNAME`, `_PASSWORD` and `_SECURE` instead of the URL.
  </Tab>
</Tabs>

## 3. Define what the agent may query [#3-define-what-the-agent-may-query]

The dataset names the table, the columns an agent can group and filter by, and the measures it can ask for. Labels and
descriptions are what the agent reads to choose fields, so write them for a reader. Nothing outside the dataset is
reachable.

<Tabs groupId="language" items={['TypeScript', 'Python']} persist>
  <Tab value="TypeScript">
    <CodeBlock>
      ```typescript
      // analytics/api.ts
      import { createQueryBuilder } from '@hypequery/clickhouse';
      import { dataset, dimension, measure } from '@hypequery/datasets';
      import { initServe } from '@hypequery/serve';

      const db = createQueryBuilder({
        url: process.env.CLICKHOUSE_URL!,
        username: process.env.CLICKHOUSE_USERNAME!,
        password: process.env.CLICKHOUSE_PASSWORD!,
        database: process.env.CLICKHOUSE_DATABASE!,
      });

      const Orders = dataset('orders', {
        source: 'orders',
        description: 'One row per order.',
        dimensions: {
          status: dimension.string({ label: 'Status', description: 'completed, refunded or pending' }),
          country: dimension.string({ label: 'Country' }),
          createdAt: dimension.timestamp({ column: 'created_at', label: 'Created at' }),
        },
        measures: {
          revenue: measure.sum('amount', { label: 'Revenue' }),
          orders: measure.count('id', { label: 'Orders' }),
        },
      });

      const { serve } = initServe({ context: () => ({ db }) });

      export const api = serve({
        queryBuilder: db,
        datasets: { orders: Orders },
      });
      ```
    </CodeBlock>

    `hypequery mcp` finds `analytics/api.ts` on its own. If you already followed the [TypeScript quick
    start](/docs/quick-start#from-route-2-serve-datasets), the API file you have works as it is.
  </Tab>

  <Tab value="Python">
    <CodeBlock>
      ```python
      # app.py
      from hypequery.datasets import ClientQueryProfile, create_dataset_client, dataset, dimension, measure
      from hypequery.execution import ClickHouseConnection, create_clickhouse_executor
      from hypequery.serve import Principal, add_client_contract_endpoint, create_api, create_dataset_endpoint, from_context

      orders = dataset(
          "orders",
          source="orders",
          description="One row per order.",
          dimensions={
              "status": dimension.string(label="Status", description="completed, refunded or pending"),
              "country": dimension.string(label="Country"),
              "createdAt": dimension.timestamp(column="created_at", label="Created at"),
          },
          measures={
              "revenue": measure.sum("amount", label="Revenue"),
              "orders": measure.count("id", label="Orders"),
          },
      )

      client = create_dataset_client(executor=create_clickhouse_executor(ClickHouseConnection.from_env()))


      def developer(request):
          # Every caller queries as one local developer. Replace with real auth before deploying.
          return Principal(subject="developer")


      api = create_api(auth=from_context(developer, same_origin=False))
      create_dataset_endpoint(dataset=orders, client=client, client_profile=ClientQueryProfile()).install(
          api, "/datasets/orders/query"
      )
      add_client_contract_endpoint(api)  # the catalog hypequery mcp serves to agents
      ```
    </CodeBlock>

    A project from `hypequery init` (the [Python quick start](/docs/python/quick-start)) already has all of this: use
    its `app:api` below.
  </Tab>
</Tabs>

## 4. Check it [#4-check-it]

<Tabs groupId="language" items={['TypeScript', 'Python']} persist>
  <Tab value="TypeScript">
    <CodeBlock>
      ```bash
      npx hypequery mcp --self-test
      ```
    </CodeBlock>
  </Tab>

  <Tab value="Python">
    <CodeBlock>
      ```bash
      hypequery mcp app:api --self-test
      ```
    </CodeBlock>
  </Tab>
</Tabs>

It loads your datasets and reports what it would serve, without starting a server or running a query.

## 5. Connect your agent [#5-connect-your-agent]

The client starts the server itself, over stdio, so there is nothing to keep running.

<Tabs groupId="language" items={['TypeScript', 'Python']} persist>
  <Tab value="TypeScript">
    **Claude Code**, from your project folder:

    <CodeBlock>
      ```bash
      claude mcp add hypequery -- sh -c "cd '$PWD' && npx hypequery mcp"
      ```
    </CodeBlock>

    **Claude Desktop** (Settings → Developer → Edit Config) or **Cursor** (`.cursor/mcp.json`), with your project's
    absolute path:

    <CodeBlock>
      ```json
      {
        "mcpServers": {
          "hypequery": {
            "command": "sh",
            "args": ["-c", "cd /absolute/path/to/your-project && npx hypequery mcp"]
          }
        }
      }
      ```
    </CodeBlock>
  </Tab>

  <Tab value="Python">
    **Claude Code**, from your project folder with its virtual environment active:

    <CodeBlock>
      ```bash
      claude mcp add hypequery -- "$(command -v python)" -m hypequery mcp app:api --project "$PWD"
      ```
    </CodeBlock>

    **Claude Desktop** (Settings → Developer → Edit Config) or **Cursor** (`.cursor/mcp.json`): print the entry, with
    your interpreter and project as absolute paths, and paste it in:

    <CodeBlock>
      ```bash
      hypequery mcp app:api --print-config
      ```
    </CodeBlock>
  </Tab>
</Tabs>

Restart Claude Desktop or reload Cursor after changing its config. Windows and other clients: see
[Clients](/docs/mcp/clients).

## 6. Ask a question [#6-ask-a-question]

<CodeBlock>
```text
What was revenue by country for completed orders?
```
</CodeBlock>

The agent calls `list_datasets`, `get_dataset_schema` and `query_dataset`, and answers from the rows hypequery returns.
If it can't see the tools, run the self-test again, check the path in your client config, and restart the client.

## 7. Share it over HTTP [#7-share-it-over-http]

Over stdio, only the client on your machine can use the server. Add `--http` to serve the same datasets at a URL, behind
a shared key:

<Tabs groupId="language" items={['TypeScript', 'Python']} persist>
  <Tab value="TypeScript">
    <CodeBlock>
      ```bash
      export HYPEQUERY_MCP_KEY=$(openssl rand -hex 32)
      npx hypequery mcp --http
      ```
    </CodeBlock>
  </Tab>

  <Tab value="Python">
    <CodeBlock>
      ```bash
      export HYPEQUERY_MCP_KEY=$(openssl rand -hex 32)
      hypequery mcp app:api --http
      ```
    </CodeBlock>
  </Tab>
</Tabs>

It listens on `http://127.0.0.1:3333/mcp`, and a request without the key gets `401`. Connect Claude Code with the key:

<CodeBlock>
```bash
claude mcp add --transport http hypequery-http http://127.0.0.1:3333/mcp \
  --header "Authorization: Bearer $HYPEQUERY_MCP_KEY"
```
</CodeBlock>

Everyone with the key is the same caller and sees the same data. To open it to a network, give each person their own
sign-in, or connect Claude or ChatGPT, see [Remote MCP over HTTP](/docs/mcp/remote).

<Callout type="info">
  Rather not run the server yourself? [hypequery Cloud](/docs/cloud/mcp) hosts the MCP endpoint for every release,
  with OAuth sign-in for your users.
</Callout>

## Next steps [#next-steps]

- [Overview](/docs/mcp/overview): every way to run the server, and when to use each.
- [Tools](/docs/mcp/tools): what each tool accepts and returns.
- [Safety model](/docs/mcp/safety): what MCP constrains, and how tenants work.
- [Remote MCP over HTTP](/docs/mcp/remote): per-user sign-in, OAuth, TLS and deployment.
