Open-source semantic layer · Apache-2.0

Business metrics, defined in code. Queried by agents.

Semantic Rails is an open-source semantic layer for AI agents. You define metrics, dimensions and join paths once, in YAML. Agents then find those definitions, validate a query and compile SQL through an MCP server, a CLI or an HTTP API, instead of writing joins and metric formulas by hand.

Try it · needs only uv
uvx semantic-rails ask --package jaffle_shop "revenue by store" --run
Apache-2.0 Python 3.11+ DuckDB built in Snowflake + 7 connectors MCP server No telemetry Beta
recorded session · semantic-rails 0.3.0 · jaffle_shop
transcript
# A recorded MCP session: discover, plan, inspect, validate, compile and execute against the bundled jaffle_shop package. The transcript link above has every request and response.
discover plan inspect validate compile execute

Why agents need a semantic layer.

An agent writing SQL against raw tables has to guess join paths and metric formulas, and a wrong join returns a plausible number, not an error. With Semantic Rails, the definitions live in a versioned package, and the agent works through separate tools: it finds governed IDs, drafts Query IR, validates it and compiles SQL whose join paths it can inspect. The plain-English planner is rule-based and can miss part of a question, so every draft comes back as Query IR you can check before it runs.

Pipeline 5 MCP tools (of 6)
Semantic Rails: 7-station pipeline A horizontal schematic rail with seven stations for five MCP tools, including two execute modes. Each station is clickable to reveal details. 0+00 2+00 4+00 6+00 8+00 MCP discover01 · ORIENT inspect02 · DISCOVER plan03 · DRAFT valid-values04 · BUILD mode=validate05 · GATE mode=sql06 · EMIT execute07 · RUN

Scope classification runs inline on discover and plan - out-of-scope intents come back as structured blocks before the planner touches the warehouse. The sixth tool, segment, validates, explains and previews package-authored segments.

Compile shows its work.

The compiler walks the entity graph, checks direction and cardinality at every hop, and reports the route it chose in a hop_profile next to the SQL. A shape it can't rewrite safely stops with a typed error and a hint instead of SQL. These are compile responses from semantic-rails 0.3.0 for hand-built Query IR on the bundled jaffle_shop package.

Hop trace package jaffle_shop
Compile responses from semantic-rails 0.3.0 for hand-built Query IR
compile · "item revenue by customer type" measure `item_revenue_usd` lives on order_items · dimension `customer_type` lives on customers · 2 hops apart
Storefront session session_id Product product_type Item item_revenue_usd Order order_id Customer customer_type · lifetime_spend_usd Store store_name hop 1 · N:1 · safe ✓ hop 2 · N:1 · safe ✓ hop 2 · N:1 · safe ✓ reverse · requires_rewrite ⚠ reverse ⚠ ✕ refused before SQL
hop_profileroot_entity: entity.jaffle_item · hop_limit: 4
targets.entity.jaffle_customer · hop_count: 2 · alternates_considered: 1
relationship.order_items_order item → order forward · N:1 · safe
relationship.orders_customer order → customer forward · N:1 · safe
max_hop_count: 2 · long_hop_targets: [] · nothing flagged for acceleration
hop_profileroot_entity: entity.jaffle_item · hop_limit: 4
targets.entity.jaffle_store · hop_count: 2 · alternates_considered: 2
relationship.order_items_order item → order forward · N:1 · safe
relationship.orders_store order → store forward · N:1 · safe
max_hop_count: 2 · long_hop_targets: [] · nothing flagged for acceleration
errorcode: MIXED_GRAIN_INVALID
"Path to 'entity.jaffle_product' requires a rewrite that is not supported for group_by"
recovery_hints[0] · kind: replace_measure · "Measure 'measure.jaffle.item_revenue_usd' aggregates at a grain that can group by 'dimension.jaffle_product_type'"
No SQL was generated. The next call is already in the hint.
Expressive data that follows your rules.
rendered_sql from semantic-rails 0.3.0 for examples/jaffle_shop_revenue_by_store.json, line breaks adjusted for display
compile · @examples/jaffle_shop_revenue_by_store.json → status: ok
WITH leaf_1 AS (
pre_aggregation_grain.leaf_1: dimension.jaffle_store_name
SELECT
jaffle_store.store_name AS g1,
alias_resolution.group_by: dimension.jaffle_store_name
SUM(jaffle_order.order_total_cents / 100.0) AS m1
measure: measure.jaffle.revenue_usd · expr from the package
FROM jaffle_order
hop_profile.root_entity: entity.jaffle_order
INNER JOIN jaffle_store
chosen_paths: 4 candidates → relationship.orders_store
ON jaffle_order.store_id = jaffle_store.store_id
contract: fk_to_pk · N:1 · safe
GROUP BY jaffle_store.store_name
)
SELECT
base.g1 AS "dimension.jaffle_store_name",
output_columns: stable object ids
base.m1 AS revenue_usd
FROM leaf_1 AS base
ORDER BY revenue_usd DESC
LIMIT 5
from the request · checked by validate
Every clause traces to a package object or to the request. The same response carries rendered_sql · logical_plan · sql_plan · hop_profile · explain.alias_resolution · explain.chosen_paths · explain.fanout_strategy

Try it in about a minute.

Every command uses the published package from PyPI. You need uv; it fetches Python 3.11 or newer if your system Python is older. Run the Terminal commands first, in the directory you'll use: the Claude Code, Cursor, Claude Desktop and Codex tabs connect the my_package they create. The commands are for macOS and Linux shells; Windows isn't covered by our checks yet.

your own package
uvx semantic-rails init my_package --yes
uvx semantic-rails project validate --path ./my_package
uvx semantic-rails ask --path ./my_package "total amount by event type" --run

init writes a runnable starter package: YAML models, metrics, examples, tests and CSV data. Pass --path, or run commands inside the package directory; without either, and without a saved profile, commands stop with no_package_selected.

from the directory that holds my_package
claude mcp add semantic-rails -- uvx semantic-rails mcp stdio --path "$PWD/my_package"

Install the command and print the two absolute paths the config needs, then add the server to .cursor/mcp.json.

from the directory that holds my_package
uv tool install semantic-rails
echo "$(uv tool dir --bin)/semantic-rails"
echo "$PWD/my_package"
.cursor/mcp.json
{
  "mcpServers": {
    "semantic-rails": {
      "command": "/absolute/path/to/semantic-rails",
      "args": ["mcp", "stdio", "--path", "/absolute/path/to/my_package"]
    }
  }
}
install once, then write the client config
uv tool install semantic-rails
"$(uv tool dir --bin)/semantic-rails" mcp setup --path "$PWD/my_package" --client claude --mcp query --install --yes

Run mcp setup without --install --yes first to preview the change. uv tool dir --bin finds the command even when uv's tool directory isn't on your PATH yet; uv tool update-shell adds it for new terminals.

from the directory that holds my_package
codex mcp add semantic-rails -- uvx semantic-rails mcp stdio --path "$PWD/my_package"
public Streamable HTTP endpoint
claude mcp add --transport http semantic-rails-demo https://semantic-rails.com/mcp
codex mcp add semantic-rails-demo --url https://semantic-rails.com/mcp

The hosted endpoint serves synthetic Jaffle Shop data only. It is anonymous and rate-limited, and it can't load your package. See the demo policy.

Why not dbt's Semantic Layer, Cube, LookML or Malloy?

They may be the better choice for you. They're more mature and connect to far more BI tools. Semantic Rails is narrower: an engine built around the agent loop, which you can run locally or embed.

Where they win

  • Warehouse reach: Cube alone connects to Redshift, SQL Server, Microsoft Fabric, MySQL and Trino, which Semantic Rails doesn't support yet.
  • BI integrations, caching and pre-aggregation (dbt, Cube, Looker).
  • Managed MCP servers (dbt and Snowflake; Looker in preview) and embedded analytics (Cube).

What Semantic Rails focuses on

  • An MCP tool for each step of the agent loop, so an agent can stop, branch or back up between steps.
  • Typed errors with recovery hints the next call can use.
  • An explain payload on compile (over MCP, execute in sql mode; at compact or full verbosity) that shows the join route it chose and the other candidates it considered.

The comparison pack shows how each layer expresses the same questions. It doesn't show that Semantic Rails is better at them.

Evidence you can check.

The comparison pack asks the same 16 questions of six semantic layers and records how each one expresses them. It measures capability, not speed. One published rubric labels all six; the page explains its modeling limits.

  • The five layers checked on current data match an independent answer key on all 16 questions within 1e-6. Cube's captured SQL was re-executed, not Cube itself. Snowflake Semantic Views has a separate April capture on older data: 14 of 16 match, with q07 and q16 differing.
  • 9 of the 16 questions were chosen to exercise primitives Semantic Rails ships, so the page scores them separately from the 7 shared questions and doesn't rank them.
  • The answer key and executable labeling rubric are published. Matching the key is weak evidence for some rules because the current data does not exercise every edge case.

Project status.

Semantic Rails is beta software. This is what works today, what doesn't yet, and what is being built.

Supported

The open-source runtime, the CLI, MCP over stdio and HTTP, the /api/v1/* HTTP API, DuckDB and Snowflake execution, package authoring, validation, compile and explain. Postgres, BigQuery, Databricks, Athena, ClickHouse, MotherDuck and DuckLake are supported with guardrails; they're tested against live warehouses on demand, not in every CI run.

Known limitations

  • The planner can return a draft that validates but leaves out or misreads part of a question, such as a year filter. Check the Query IR before you rely on the numbers.
  • Currency measures print floating-point noise.
  • Windows isn't covered by CI yet.

In progress

  • One-step agent setup: Claude Code and Codex plugins and a Claude Desktop bundle.
  • A flagship dbt example on an open dataset.
  • Apache Ossie import and export.

The README and the docs have the details.

Open source, with a hosted option.

The engine, CLI, MCP servers and HTTP API are Apache-2.0, with no gated features and no telemetry, and you don't need anything else to use them. SR Cloud is a separate hosted product in early access: connect your warehouse, build shared semantic models, and invite teammates. Explore metrics together and serve governed answers through API and MCP.

Questions, bugs and pull requests are welcome.

If Semantic Rails doesn't fit the way your agent works, tell us why.