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.

Metadata

Release files for semql-mcp 0.11.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.11.0
File Size Uploaded Provenance
semql_mcp-0.11.0.tar.gz 22.9 kB With provenance Details

Built distribution (wheel)

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

Total release size: 48.1 kB

Release files / semql_mcp-0.11.0.tar.gz

Download URL semql_mcp-0.11.0.tar.gz
Size 22.9 kB
Tags Source
SHA-256 checksum
How to use checksums
fa5ce8d5c4e78e6aefd8e45e37cdddf67f199abbb9d924bf809820a6b618a797
BLAKE2b-256 checksum
How to use checksums
839a60c6b6448f07684402c513b239b3d46245e88a26e58efdc2460cdbba704c
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 Oct 23, 2026.

Transparency log

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

Download URL semql_mcp-0.11.0-py3-none-any.whl
Size 25.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
95a8a8ff16b457582ace946a584a820367d8d80257d8bed5e793d72f09bb83fb
BLAKE2b-256 checksum
How to use checksums
cce69d0d14183ad41524bcebc08329516aad14d8dff998a42bf982042c422eae
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 Oct 23, 2026.

Transparency log

Security for semql-mcp 0.11.0

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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for semql_mcp-0.11.0.tar.gz

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 Oct 23, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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