Skip to main content

semql-mcp

An MCP server that wraps a semql Catalog and exposes its compiler / validator / prompt-renderer surfaces as tools any MCP client can call. Built on FastMCP.

Two modes

By default the server is compile-only. semql is a pure compiler — no I/O — and this server keeps that contract. Tools return the emitted SQL and bound parameters; the caller runs the SQL against whatever backend they own.

Pass an executor at construction to opt into exec mode. A query_execute tool registers in addition to the compile-only tools; it runs the SQL against your executor and returns both the SQL/params envelope and the resulting rows.

Install

pip install semql-mcp

Quick start — compile-only

from semql import Dialect, Catalog, Cube, Dimension, Measure
from semql_mcp import MCPServer

catalog = Catalog([
    Cube(
        name="orders",
        dialect=Dialect.POSTGRES,
        table="orders",
        alias="o",
        measures=[Measure(name="revenue", sql="{o}.amount", agg="sum", unit="currency")],
        dimensions=[Dimension(name="region", sql="{o}.region", type="string")],
    ),
])

server = MCPServer(catalog)
server.run(transport="stdio")  # speak JSON-RPC over stdin/stdout

Quick start — exec mode

Bring your own database driver and adapt its row shape to a list of dicts:

import psycopg
from psycopg.rows import dict_row

from semql_mcp import MCPServer


def executor(sql: str, params: dict) -> list[dict]:
    with psycopg.connect("postgresql://...", row_factory=dict_row) as conn:
        with conn.cursor() as cur:
            cur.execute(sql, params)
            return list(cur.fetchall())


server = MCPServer(catalog, executor=executor)
server.run(transport="stdio")

The MCP server never imports a database driver. Whatever you wire in is what gets called; semql-mcp just hands it (sql, params) and expects list[dict] back.

Tools

Always registered:

Tool Description
query_semantic(spec, context?) Compile a SemanticQuery; return {dialect, sql, params, columns}.
validate(spec) Collect-all static validation; returns list[ValidationError]. Empty when the query would compile cleanly.
explain(spec, context?) Compile and return just the SQL string.
catalog_prompt(include_introspection=False) Render the planner prompt fragment, scoped to the resolved viewer's authorized surface.

Registered when executor is supplied:

Tool Description
query_execute(spec, context?) Compile + run. Returns the query_semantic shape plus rows: list[dict]. Errors carry the SQL we tried to run so callers can replay / inspect it.

Auto-generated per-cube tools

For each expose_in_prompt=True (non-META) cube, the server also registers a query_<cube_name> tool whose measures, dimensions, order (and time_window.dimension, when applicable) parameters are Literal-typed enums of the cube's actual fields. The planner sees a JSON Schema with explicit allowed values rather than the bare list[str] query_semantic accepts.

Field names are bare (no cube prefix); the tool auto-qualifies as it builds the SemanticQuery:

// query_orders
{
  "measures": ["revenue"],
  "dimensions": ["region"],
  "filters": [{"dimension": "status", "op": "eq", "values": ["paid"]}],
  "time_window": {
    "dimension": "created_at",
    "granularity": "day",
    "range": ["2026-01-01", "2026-02-01"]
  },
  "limit": 100
}

Multi-cube queries (joins across cubes) still go through query_semantic — the per-cube tools are scoped to a single cube by construction. When executor is configured, the per-cube tools return rows too.

Authorization and metadata disclosure

Per-call execution is authorized and fails closed. Pass a viewer_provider and every tool — query_*, validate, catalog_prompt, lookup, and entity tools — resolves the viewer and refuses cubes, fields, lookups, and saved queries it isn't allowed to see. An unauthorized call returns a structured error, never data or SQL.

Accepted limitation — tool listing is not viewer-filtered. Tools are registered once when the server is constructed, before any client connects, so there is no request identity at registration time. A client that lists tools therefore sees the names, descriptions, and field enums of every role-gated cube, saved query, and entity in the catalog — including ones it cannot execute. This exposes catalog structure (names and shapes), never row data or SQL, and execution remains authorized. Treat tool names and descriptions as non-secret: don't encode confidential facts in cube / field / saved-query names if untrusted clients can list tools. To gate listing itself, run a per-tenant server (one catalog per trust boundary) or front it with a transport that filters the advertised tool set per connection.

In-process testing

FastMCP's Client connects to a FastMCP instance without a transport — useful for end-to-end testing of your catalog + planner together:

import asyncio
from fastmcp import Client
from semql_mcp import MCPServer

server = MCPServer(catalog)

async def smoke() -> None:
    async with Client(server.mcp) as c:
        tools = await c.list_tools()
        print([t.name for t in tools])
        result = await c.call_tool("explain", {"spec": {"measures": ["orders.revenue"]}})
        print(result.data)

asyncio.run(smoke())

Status

Early development. The tool surface is stable.

Release files for semql-mcp 0.7.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for semql-mcp 0.7.0
File Size Uploaded
semql_mcp-0.7.0.tar.gz 23.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for semql-mcp 0.7.0
File Interpreter ABI Platform
semql_mcp-0.7.0-py3-none-any.whl Python 3 none any Details

Total release size: 49.0 kB

Release files / semql_mcp-0.7.0.tar.gz

Download URL semql_mcp-0.7.0.tar.gz
Size 23.3 kB
Tags Source
SHA-256 checksum
How to use checksums
d04f9326338c8a5c44a06dfa5223b0c3774efb51d973a2ee759cf7fbad5b896c
BLAKE2b-256 checksum
How to use checksums
a0c3d79e9b3cb808371cbfbcfa551ddac5c9084b8ed279a25ba95eee25b514bb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 21, 2026.

Transparency log

Release files / semql_mcp-0.7.0-py3-none-any.whl

Download URL semql_mcp-0.7.0-py3-none-any.whl
Size 25.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
769615ad67c62269be1d434a15c803ca92b5c3291485d02d8857fa2908ccdb33
BLAKE2b-256 checksum
How to use checksums
d6c5b2343c927d347e3921a6492f04befa9d95ccaecb785ac116fe5444931c83
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 21, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.7.0 This release

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page