Semaphor
MCP

Discover and Query

How agents find the right data in your semantic model, then run governed analytics, Matrix windows or SQL.

Before it queries, an agent finds out what's available. Semaphor is semantic first: the agent starts from your business-friendly semantic model and falls back to raw database tables only when it has to. Every data question starts with semaphor_get_analysis_context, which recommends the path.

Analysis contextrecommends a pathand the next toolSEMANTIC PATH · PREFERREDPHYSICAL PATH · SQL ACCESSDomainswhat you can seeDatasetsin a domainSchemafields and joinsQueryanalyze or matrixConnectionswith SQL accessSchemasas each needsTablesor name candidatesSQL queryquery_sql_advanced
semaphor_get_analysis_context recommends the path and the next tool. Callers without SQL access never see the physical path.

The Analysis Context

Call semaphor_get_analysis_context first. Interactive sessions pass a projectId; token sessions use the token's project. It returns:

FieldWhat it tells the agent
recommendedPathsemantic, physical, restricted (the token's domain access excludes every domain) or unavailable
recommendedNextToolThe tool to call next
semanticDomainsThe domains this caller can see
fallbackConnectionsConnections for physical discovery, when there are no domains and the caller has SQL access

Semantic Path

When semantic domains exist, the agent works down from domain to field:

  1. semaphor_list_semantic_domains: the domains this caller can see
  2. semaphor_list_datasets: the datasets in a domain
  3. semaphor_get_dataset_schema: measures, dimensions and date fields, plus a preview of related datasets with example field refs
  4. semaphor_get_domain_relationships: how datasets join (optional, useful for multi-dataset questions)

Then it queries with semaphor_analyze or semaphor_matrix.

Starting from a dashboard? Call semaphor_get_dashboard_analysis_context before broad discovery. It returns the dashboard's cards, filters, authored measures, dimensions and date fields, and query inputs the agent can reuse with semaphor_analyze.

Physical Path

When there are no semantic domains, callers with SQL access browse physical tables:

  1. semaphor_list_connections: connections and their discovery capabilities
  2. semaphor_list_databases and semaphor_list_schemas: the levels each connection calls for
  3. semaphor_list_tables: tables, or exact coordinates from nameCandidates
  4. semaphor_get_dataset_schema with connection coordinates (connectionId, tableName, and databaseName and schemaName as needed)

Then it queries with semaphor_query_sql_advanced. Viewers and tokens minted with "sql": false don't see the physical tools and stay on the semantic path.

Semantic pathPhysical path
JoinsFrom the semantic model, automaticallyWritten by hand in SQL
Field labelsBusiness-friendly names and descriptionsRaw column names
Who can use itEvery callerCallers with SQL access
Best forBusiness questions and governed analyticsSQL analysis and tables no domain represents

Choose a Query Tool

Governed analyticssemaphor_analyze

The default. Measure by dimension, top-N, time series, filters, period comparisons and driver analysis, with joins and aggregates from the semantic model.

Matrix windowssemaphor_matrix

Pivot-style row and column hierarchies with totals, subtotals, scoped sorting, and Top or Bottom N with Others.

Advanced SQLsemaphor_query_sql_advanced

For questions the semantic tools can't express. Needs SQL access, and runs under the same security policies.

Recovery planningsemaphor_plan_analytics_recovery

When a question still has unanswered parts, plans the next governed tool calls from the schema evidence. It plans; it does not run queries.

semaphor_analyzesemaphor_matrixsemaphor_query_sql_advanced
Query formatSemantic fields and analytics intentMatrix definition and windowSQL
JoinsAutomatic and governedAutomatic and governedWritten by hand
AggregatesFrom the semantic model, with overridesFrom the Matrix definitionWhatever the SQL says
Who can use itEvery callerEvery callerCallers with SQL access

Governed Analytics

semaphor_analyze resolves safe joins through semantic relationships, refuses known fan-out shapes instead of returning inflated numbers, and applies the caller's security policies. It returns records, columns, the generated SQL, relationship diagnostics, and a typed executionResult that says whether the question was answered.

Top suppliers by modeled purchase value
{
  "domainId": "dom_abc",
  "datasetName": "fact_purchase_line",
  "measures": [{ "datasetName": "fact_purchase_line", "name": "net_purchase_value" }],
  "primaryMeasure": { "datasetName": "fact_purchase_line", "name": "net_purchase_value" },
  "dimensions": [{ "datasetName": "dim_supplier", "name": "supplier_name" }],
  "limit": 10
}

If net_purchase_value is modeled with aggregate: "SUM", Semaphor generates a summed top-N query: the semantic model owns that default. To count rows when the model has no count measure, use the "__count" measure.

For a comparison, add { "kind": "previous_period" }, { "kind": "previous_year" } or { "kind": "target", "targetValue": 100 }. It uses the query's own date field and time window. See Metric Comparisons.

Time Windows

Pass a timeWindow with a dateField to bound the query:

Revenue by week, this quarter
{
  "domainId": "dom_abc",
  "datasetName": "orders",
  "measures": [{ "datasetName": "orders", "name": "revenue" }],
  "dateField": { "datasetName": "orders", "name": "order_date" },
  "timeGrain": "week",
  "timeWindow": { "kind": "calendar", "unit": "quarter", "period": "this" }
}

Rolling windows such as { "unit": "month", "value": 6 } count back from the latest date in the data by default (anchor: "latest_available"). Use anchor: "now" to count back from today. See Tools Reference for every window shape.

Aggregate Overrides

The same measure can be asked about with a different rollup: total margin and average margin are both valid questions. Add aggregate to the measure for one call:

Average margin by material
{
  "domainId": "dom_abc",
  "datasetName": "fact_sales_line",
  "measures": [{ "datasetName": "fact_sales_line", "name": "gross_margin_value", "aggregate": "AVG" }],
  "primaryMeasure": { "datasetName": "fact_sales_line", "name": "gross_margin_value", "aggregate": "AVG" },
  "dimensions": [{ "datasetName": "dim_material", "name": "material_family" }],
  "limit": 10
}

The response marks the override in fieldsUsed with a measure_aggregate_override warning, so you can audit it. Omit aggregate to use the semantic model.

Matrix Windows

semaphor_matrix runs a governed Matrix: multi-level row and column hierarchies, totals and subtotals, scoped sorting, and Top or Bottom N with Others. You ask for a bounded window, and page cursors load more. A window is never a complete export. See Tools Reference.

Advanced SQL

semaphor_query_sql_advanced runs SQL against a connection, under the same security policies. Use it for custom CTEs, window functions, relationships the semantic model doesn't define, fields no domain exposes, raw-row inspection, or SQL debugging. It's available to organization users with an Author role or higher, and to end-user tokens unless SQL is turned off.

Every call states why semaphor_analyze can't answer, needs a connectionId from semaphor_list_connections, and can post-process rows with optional pythonCode.

Month-over-month delta
{
  "connectionId": "conn_abc123",
  "analyzeFallbackReason": "unsupported_sql_construct",
  "analyzeFallbackExplanation": "Month-over-month delta needs a LAG window function over a CTE.",
  "sql": "WITH monthly AS (SELECT date_trunc('month', order_date) AS month, SUM(total_amount) AS revenue FROM orders GROUP BY 1) SELECT month, revenue, revenue - LAG(revenue) OVER (ORDER BY month) AS delta FROM monthly ORDER BY month LIMIT 100"
}

Response Format

semaphor_analyze and semaphor_query_sql_advanced accept responseFormat: "json" (the default) returns records, metadata, diagnostics and generated SQL, also as structured content; "markdown" returns a readable summary. semaphor_matrix always returns JSON.

Tips

  • List before you query. Call semaphor_list_datasets before semaphor_get_dataset_schema, so the agent never guesses dataset names.
  • Use source-bearing refs. Include datasetName on each field ref when selecting fields from joined datasets.
  • Let the model choose aggregates. Omit aggregate unless the question asks for a different rollup.
  • Check executionResult. Its status and coverage say whether the question was answered, not the surrounding text.
  • Use SQL as the escape hatch. It's fully governed, but it shouldn't be the first choice for ordinary analytics.

For more on modeling, see Semantic Domains.

On this page