Skip to main content
Glama

PyPI Python Docs License GitHub stars Discord

SLayer

An expressive, embeddable semantic layer for AI agents and humans.

SLayer enables AI-powered data analytics on top of your warehouse. Agents get a governed, shared surface through which they access your data and metrics and give you reliable answers.

SLayer handles database connectivity (read-only), SQL translation, common data transformations, and row-level security, so LLMs and humans don't have to. Adapt it to your workflows, not the other way around. Manage definitions easily with an agent or by yourself.

SLayer can be used as a standalone tool or imported as a Python library, easily embeddable into any Python app. Use it for powering analytical MCP servers or APIs or simply to query databases semantically.

SLayer is at the core of Motley and is maintained with ♡ by the same team.

How SLayer is different

Traditionally, semantic layers were a part of the BI stack, where every metric and its aggregation had to be predefined. Agents need more flexibility because users ask questions that involve metric combinations (like ratios), transforms (like time shifts), or different aggregations of the same metric (like average instead of sum).

SLayer allows to define a column revenue once and query it using expressions like revenue:sum, revenue:avg, revenue:sum / *:count, time_shift(revenue:sum, -1, 'year') etc.; multi-stage queries are also supported.

SLayer is focused on the common agentic search → inspect → query flow. It has a search tool for efficient discovery and a memory store for linking the relevant business context.

Agents, apps and humans can talk to SLayer via MCP, REST API, CLI, Python, Flight SQL, or Postgres-based SQL API. SLayer supports most popular databases.

SLayer fits next to your existing data stack. It also provides importers for dbt, Cube, and Ossie configs.

See docs for more.

Example

Question (run on the built-in demo Jaffle Shop database): "show monthly revenue by store, with month-over-month % change"

Side by side, here's LLM-generated SQL and the equivalent SLayer query.

Example SQL vs SLayer query

Related MCP server: Foggy Data MCP Bridge

Quickstart

We recommend using uv, especially if you don't work in a Python project.

uv tool install 'motley-slayer[all]'

If slayer isn't found on PATH afterwards, run uv tool update-shell and reopen your terminal.

Using demo dataset

# With the Jaffle Shop demo preloaded (zero-config quickstart)
claude mcp add slayer_demo -- slayer mcp --demo

Using your own data

Set up your datasource, substituting the correct database, username, hostname, and db_name.

slayer datasources create 'postgresql://user:${DB_PASSWORD}@hostname/db_name'

The password will be read by SLayer at init time, not saved to disk nor exposed to Claude.

Then add SLayer to Claude Code:

claude mcp add slayer -- slayer mcp --ingest-on-startup

Now SLayer MCP will be visible in Claude Code next time you start it. Make sure to launch Claude Code from a shell where DB_PASSWORD is exported — the MCP subprocess inherits its environment from the launching process.

Read more on how to get started with MCP, CLI, REST API, Python in the docs.

License

MIT

Available Tools

20 tools
create_datasourceA

Create a database connection, verify it, and auto-ingest models. Use ${ENV_VAR} syntax in credentials to reference environment variables.

Args: name: Unique datasource name. type: Database type — postgres, mysql, sqlite, bigquery, or snowflake. host: Database host (default: localhost). port: Database port (e.g. 5432 for Postgres). database: Database name. username: Database username. password: Database password. connection_string: Full connection string as alternative to individual fields. schema_name: Default schema name. Also used as the single schema for auto-ingestion. schemas: Comma-separated schemas to ingest. Mutually exclusive with schema_name / all_schemas. all_schemas: Ingest every non-system schema. Mutually exclusive with schema_name / schemas. auto_ingest: Automatically ingest models from the database schema (default: true). Set to false to skip.

Example: create_datasource(name="mydb", type="postgres", host="localhost", port=5432, database="app", username="user", password="pass")

ParametersJSON Schema
NameRequiredDescriptionDefault
hostNo
nameYes
portNo
typeYes
schemasNo
databaseNo
passwordNo
usernameNo
all_schemasNo
auto_ingestNo
schema_nameNo
connection_stringNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full behavioral burden. It discloses key side effects: verification of the connection, auto-ingestion of models, environment variable substitution in credentials, and defaults like auto_ingest. It stops short of explaining failure behavior, whether an existing datasource is overwritten, or how connection_string interacts with individual credentials.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The purpose is front-loaded in the first two sentences, followed by a compact Arg list and a useful invocation example. Every element earns its place: the summary, parameter explanations, constraints, defaults, and example. There is no redundant or filler content.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity, the description is nearly complete: all parameters are explained, constraints are noted, and a concrete example is provided. It does not need to explain return values because an output schema exists. Minor gaps remain around credential precedence when connection_string is used alongside individual fields and the exact behavior when auto_ingest is false.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, but the description compensates fully by documenting all 12 parameters with additional meaning: supported type values, defaults, an example port, schema mutual exclusivity, and connection_string as an alternative. This goes well beyond the bare input schema and gives an agent what it needs to construct valid arguments.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific action and resource: 'Create a database connection, verify it, and auto-ingest models.' This clearly distinguishes it from sibling tools like list_datasources, edit_datasource, and ingest_datasource_models. It also lists supported database types, making the tool's scope unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description clearly conveys when to use the tool: when creating a datasource and optionally ingesting models. It also provides parameter-level usage guidance, such as connection_string being an alternative to individual fields and schemas/schema_name/all_schemas being mutually exclusive. However, it does not explicitly name alternative tools or state when not to use it.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

create_modelA

Create a new semantic model, either from a database table or from a query.

Host a column/measure on the model whose row grain is 1:1 with what it describes — not merely one where its input columns live. Choose join keys by column Description (author intent); on ties take the shortest declared join path (long chains through lookup/log tables fan out rows). Encode definitions in dependency order, referencing already-defined entities by name rather than re-deriving them inline; in row-level SQL parenthesise weighted sums in comparisons ((a*w1 + b*w2) > t).

From a table or sql query (provide sql_table or sql): create_model(name="orders", sql_table="public.orders", data_source="mydb", columns=[...], measures=[...])

From a query (provide query): create_model(name="monthly_summary", query={"source_model": "orders", "measures": ["count(*)", "sum(amount)"], "time_dimensions": [{"dimension": "created_at", "granularity": "month"}]}) Columns are auto-introspected from the query result.

Args: name: Unique model name (lowercase, underscores). sql_table: Database table name, e.g. "public.orders". sql: Alternative to sql_table — a custom SQL expression for the model's source. data_source: Name of the datasource (from list_datasources). description: What this model represents. columns: List of column definitions. Each: {"name": "col", "sql": "col", "type": "string"}. Types: string, number, time, date, boolean. Optional fields: primary_key, unique (single-column uniqueness that is not the PK; primary_key already implies it), allowed_aggregations (whitelist), filter (CASE WHEN inside aggregation), label, description, hidden, meta. measures: List of named formula definitions on the model. Each: {"name": "aov", "formula": "sum(revenue) / count(*)", "label": "...", "description": "...", "meta": {...}}. Queries can reference these by bare name (e.g. {"formula": "aov"}). meta is an optional opaque dict for caller bookkeeping (e.g. linking the formula back to a source identifier). query: A SLayer query dict (or list of stage dicts for a multi-stage backing query). When provided, the query is saved as the model's source_queries and the model becomes query-backed. Mutually exclusive with sql_table, sql, columns, and measures. variables: Default values for {var} placeholders in the backing query. Saved as query_variables on the model. Only meaningful when query is provided.

ParametersJSON Schema
NameRequiredDescriptionDefault
sqlNo
nameYes
queryNo
columnsNo
measuresNo
sql_tableNo
variablesNo
data_sourceNo
descriptionNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden. It discloses that query becomes source_queries, that query is mutually exclusive with other source parameters, and that columns are auto-introspected. It also provides deep guidance on row grain and join key selection, which is exceptionally transparent for a creation tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but well-organized, with examples and a structured parameter list. It front-loads the core purpose and then elaborates. The opening paragraph on row grain is dense but directly relevant to correct usage, so it earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (9 params, two modes, mutual exclusivity), the description covers all necessary details: examples, parameter semantics, auto-introspection, and mutual exclusivity. An output schema exists, so return values are not needed. Nothing essential is missing for an agent to call this correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate fully. It does so by detailing every parameter: name conventions, types for columns and measures, mutual exclusivity, and the role of variables. Examples for both modes make the semantics concrete and actionable.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a precise statement: 'Create a new semantic model, either from a database table or from a query.' It immediately distinguishes two modes and provides concrete examples, making the tool's purpose unambiguous and clearly separated from siblings like edit_model and delete_model.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives two usage modes with examples and notes that data_source comes from list_datasources, implying a prerequisite. However, it does not explicitly state when not to use this tool or contrast it with edit_model, though the purpose makes the primary usage clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

delete_datasourceB

Delete a datasource configuration.

Args: name: Datasource name to delete.

ParametersJSON Schema
NameRequiredDescriptionDefault
nameYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

B3.3/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden of behavioral disclosure. It states the deletion action but does not mention whether deletion is irreversible, whether it only removes configuration or also underlying data, whether it cascades to dependent models, or what happens if the datasource name does not exist. For a destructive tool, this is a meaningful gap.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is short and front-loaded with the core purpose. The Args section is brief but repeats what the schema already structurally conveys. It earns its place by adding parameter context, though it could be slightly tighter.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple one-parameter tool this is close to minimally viable, but the destructive nature of the operation requires more context. The description does not explain the scope of deletion, reversibility, side effects, or error behavior. Without annotations, an agent cannot fully assess the consequences of invoking this tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate for the bare 'name' property in the schema. The Args section clarifies that the name is the datasource name to delete, which provides actionable meaning beyond the schema's generic 'Title: Name'. It fully covers the single required parameter.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: 'Delete a datasource configuration.' This clearly distinguishes it from sibling tools like create_datasource, edit_datasource, and delete_model. The agent knows exactly what action this tool performs.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives no guidance on when to use this tool versus alternatives, such as edit_datasource or delete_model. There are no explicit conditions, prerequisites, or warnings about when deletion is appropriate. Usage is only implied by the tool's name and one-line description.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

delete_modelA

Delete a semantic model.

Args: name: Model name to delete. data_source: Datasource the model belongs to. Required when the same name exists in multiple datasources (otherwise the priority list / single-match rules apply).

ParametersJSON Schema
NameRequiredDescriptionDefault
nameYes
data_sourceNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.7/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries full responsibility for disclosing behavioral traits. It states that a semantic model is deleted, but it does not mention whether deletion is irreversible, whether it cascades to dependent objects, whether permissions are required, or what side effects may occur. This is a significant gap for a destructive operation.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The purpose is front-loaded in a single clear sentence, and the Args section adds only relevant parameter details. There is no filler or redundant wording; every line supports correct invocation.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description covers the tool's purpose and both parameters, and an output schema exists so return-value details need not be explained. However, for a destructive tool with no annotations, the absence of behavioral consequences and the vague priority-list reference leave the description incomplete for an agent that needs to invoke it safely in edge cases.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate, and it does. It explains that `name` identifies the model to delete and that `data_source` serves as a disambiguator when the same name exists in multiple datasources. The reference to priority list / single-match rules adds meaning, though those rules are not fully defined.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb and resource ('Delete a semantic model'), making it immediately clear what the tool does. It naturally distinguishes this from sibling tools like delete_datasource and create_model/edit_model, so there is no ambiguity about its core operation.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides useful conditional guidance for when `data_source` is required, but it does not explicitly address when to use delete_model versus alternatives such as delete_datasource. The intended usage is implied by the tool name and first sentence rather than backed by clear selection rules.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

describe_datasourceA

Show datasource details: connection status, available schemas, and (by default) the tables in the given or default schema.

Use this after create_datasource to verify the connection and explore what's queryable before calling ingest_datasource_models.

Args: name: Datasource name (from list_datasources). list_tables: If True (default), append a list of tables from the schema named by schema_name (or the dialect's default schema when empty). schema_name: Database schema to list tables from (e.g. "public"). Empty uses the dialect default. Ignored when list_tables=False.

ParametersJSON Schema
NameRequiredDescriptionDefault
nameYes
list_tablesNo
schema_nameNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It accurately explains the default behavior (tables are included by default), the logic for list_tables, and the handling of schema_name including the edge case 'Ignored when list_tables=False.' This goes beyond the schema and gives the agent meaningful behavioral expectations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured: a one-sentence summary, a one-sentence usage context, and a concise Args list. Every sentence adds necessary information, and the most important scoping detail (verify before ingest) is front-loaded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a describe tool with an output schema, the description covers all needed call context: what it returns at a high level, how to use it in the datasource workflow, and complete parameter semantics. The presence of an output schema lessens the need to document return values in prose, so nothing critical is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must fully compensate for the bare parameter titles. It does: 'name' is sourced from list_datasources, 'list_tables' is explained with its default and effect, and 'schema_name' gets an example ('public'), its default behavior, and its interaction with list_tables. All three parameters are richly documented.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Show datasource details: connection status, available schemas, and (by default) the tables.' It clearly distinguishes this from sibling tools like list_datasources and ingest_datasource_models by framing it as the verification/exploration step between them.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly states when to use the tool: 'Use this after create_datasource to verify the connection and explore what's queryable before calling ingest_datasource_models.' This gives an agent a clear workflow position and eliminates ambiguity about where this tool fits among siblings.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

edit_datasourceB

Update a datasource's metadata.

Args: name: Datasource name to update. description: New description for the datasource.

ParametersJSON Schema
NameRequiredDescriptionDefault
nameYes
descriptionNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

B3.4/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden of behavioral disclosure. It only says 'Update' and does not explain what happens when 'description' is omitted (leave unchanged vs clear to null), whether the datasource must already exist, or what side effects occur. This ambiguity is significant because the schema gives 'description' a default of null.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is short, front-loaded with the purpose, and includes only a compact Args list that adds necessary parameter semantics. There is no filler or redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple two-parameter tool with an output schema, the description is mostly adequate. However, it lacks guidance on optional parameter behavior, error cases, and when to prefer this tool over related datasource/model tools, leaving minor but real gaps for an agent deciding how to invoke it.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must define the parameters. It does add meaning: 'Datasource name to update' clarifies that 'name' is an identifier, and 'New description for the datasource' clarifies the intended value. It stops short of explaining null/omission semantics, but still compensates for the bare schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific action ('Update') on a resource ('a datasource's metadata') and the Args section clarifies that 'name' identifies the target while 'description' supplies the new value. It is clear enough to distinguish from create/delete/describe datasource tools, though 'metadata' is somewhat broad.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is implied: use this when you want to update an existing datasource's metadata. However, there is no explicit when-to-use vs alternatives, no mention of when not to use it, and no comparison to sibling tools like edit_model or delete_datasource.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

edit_modelA

Edit an existing model in a single call — update metadata, upsert columns/measures/aggregations/joins, manage filters, and remove entities.

Host a column/measure on the model whose row grain is 1:1 with what it describes — not merely one where its input columns live. Choose join keys by column Description (author intent); on ties take the shortest declared join path (long chains through lookup/log tables fan out rows). Encode definitions in dependency order, referencing already-defined entities by name rather than re-deriving them inline; in row-level SQL parenthesise weighted sums in comparisons ((a*w1 + b*w2) > t).

Args: model_name: Name of the model to edit. description: New model description. data_source: Lookup key — the datasource the model belongs to. Required when the same name exists in multiple datasources (otherwise the priority list / single-match rules apply). new_data_source: Move the model to a different datasource (rare; renames its storage location). Pass None (default) to leave the data_source unchanged. default_time_dimension: Default time dimension (a column of type date/time) for time-dependent transforms. sql_table: Database table name. Setting this clears sql and source_queries. sql: Custom SQL expression for the model source. Setting this clears sql_table and source_queries. source_queries: Replace the model's backing query with this list of stages. Each stage is a SlayerQuery dict; non-final stages must have a name. Setting this clears sql_table and sql, makes the model query-backed, and refreshes the cached columns and backing_query_sql. query_variables: Replace the model's default {var} placeholder values for its backing query. Pass null/None to clear. Only meaningful for query-backed models. hidden: Whether this model is hidden from discovery. meta: Arbitrary JSON metadata for the model (replaces existing meta). Pass null/None to clear. columns: Columns to create or update (upsert by name). Each dict: {"name": "col", "type": "string", "sql": "col", "description": "...", "primary_key": false, "unique": false, "hidden": false, "allowed_aggregations": ["sum", "avg"], "filter": "status = 'active'", "label": "..."}. If a column with this name exists, only the provided fields are updated. Types: string, number, time, date, boolean. unique marks single-column uniqueness that is not the primary key (primary_key already implies it); it is used to infer join cardinality. measures: Named formula measures to create or update (upsert by name). Each dict: {"name": "aov", "formula": "sum(revenue) / count(*)", "label": "...", "description": "...", "meta": {...}}. Queries can reference these by bare name (e.g. {"formula": "aov"}). meta is an optional opaque dict for caller bookkeeping. aggregations: Aggregations to create or update (upsert by name). Each dict: {"name": "weighted_avg", "formula": "SUM({value} * {weight}) / NULLIF(SUM({weight}), 0)", "params": [{"name": "weight", "sql": "quantity"}], "description": "...", "meta": {...}}. meta is an optional opaque dict for caller bookkeeping. joins: Joins to create or update (upsert by target_model). Each dict: {"target_model": "customers", "join_pairs": [["customer_id", "id"]], "cardinality": "many_to_one", "description": "...", "meta": {...}}. A composite key is one join with several join_pairs entries, not one join per column. cardinality is the join's arity read source->target, one of one_to_one / one_to_many / many_to_one / many_to_many; omit it when undetermined. It is descriptive metadata only — it changes neither join_type nor query results. add_filters: SQL filter strings to add (e.g. ["deleted_at IS NULL"]). Duplicates ignored. remove_filters: SQL filter strings to remove (exact match). remove: Named entities to delete, keyed by type: {"columns": ["col_name"], "measures": ["measure_name"], "aggregations": ["agg_name"], "joins": ["target_model_name"]}. Removals are processed before upserts.

Example — update a column and add a named measure: edit_model(model_name="orders", columns=[{"name": "status", "type": "string"}], measures=[{"name": "aov", "formula": "sum(revenue) / count(*)"}]) Example — remove a measure: edit_model(model_name="orders", remove={"measures": ["old_metric"]})

ParametersJSON Schema
NameRequiredDescriptionDefault
sqlNo
metaNo
joinsNo
hiddenNo
removeNo
columnsNo
measuresNo
sql_tableNo
model_nameYes
add_filtersNo
data_sourceNo
descriptionNo
aggregationsNo
remove_filtersNo
source_queriesNo
new_data_sourceNo
query_variablesNo
default_time_dimensionNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full behavioral burden, and it does so thoroughly. It discloses mutation semantics, upsert-by-name behavior, mutual clearing between sql_table/sql/source_queries, refresh of cached columns, removal-before-upsert ordering, and the fact that join cardinality is descriptive metadata only.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but earned: it covers 18 parameters, complex side effects, design guidance, and two examples in an organized, front-loaded structure. The purpose statement and key behavioral principles come first, followed by an Args list and examples.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a complex 18-parameter mutation tool with zero schema descriptions and no annotations, the description is exceptionally complete. It covers parameter semantics, ordering, side effects, disambiguation rules, design principles, and usage examples; the presence of an output schema means return-value documentation is not required.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must fully compensate. Every parameter is explained with real meaning: lookup rules for data_source, side effects for new_data_source, structure examples for columns/measures/aggregations/joins, and the exact clearing interactions among sql/sql_table/source_queries.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource, 'Edit an existing model in a single call,' and enumerates the full set of operations: update metadata, upsert columns/measures/aggregations/joins, manage filters, and remove entities. This clearly distinguishes it from siblings like create_model and delete_model.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The context is clear: this is the tool for editing an existing model, and the examples demonstrate typical use cases. It does not explicitly state when to prefer a sibling tool or when not to use it, but the 'edit existing model' framing makes the intended use unambiguous.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

forget_memoryA

Delete a memory by id.

Cascades: every other memory's memory:<id> reference to this id is automatically stripped from its entities list.

Args: id: The memory_id returned by save_memory. Accepts strings (the canonical form, including user-supplied "kb.policy"-style ids) as well as legacy ints (coerced to their decimal string form).

Raises a friendly error if the id is invalid or the memory does not exist.

ParametersJSON Schema
NameRequiredDescriptionDefault
idYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and it discloses the key side effect (cascading removal of memory:<id> references from entities lists) and the error behavior for invalid or missing ids. It does not discuss reversibility or permissions, but the destructive action and cascade are clearly stated.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the core action, then efficiently explains cascade behavior, parameter formats, and error handling. No sentence is wasted, and the structure is scannable.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a one-parameter deletion tool with an output schema, the description covers the operation, parameter semantics, side effects, and errors. The only minor gap is that it does not describe the output or return value, but that is already covered by the output schema.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0% and the description fully compensates: it defines id as the memory_id returned by save_memory, lists accepted forms (canonical strings including kb.policy-style ids and legacy ints), and explains int coercion. This is more precise than the bare 'Id' property in the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific action ('Delete a memory by id') with the exact resource and identifier. This clearly distinguishes it from sibling deletion tools like delete_model and delete_datasource because it targets memories.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description establishes clear context: use this when you have a memory_id from save_memory and need to remove that memory. It does not explicitly name alternatives or exclusions, but the resource distinction plus the save_memory relationship makes the appropriate use obvious.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_datasource_priorityA

Return the configured datasource priority list (most-preferred first), or [] if none is set.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does reasonably well: it states the return value, the ordering convention, and the empty-list default when unset. It does not explicitly say the operation is read-only, but 'Return' and the absence of side-effect language make that sufficiently clear.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single sentence with no wasted words. The core action and resource are front-loaded, and the ordering and default behavior are provided in a compact parenthetical.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a zero-parameter, getter-style tool with an output schema present, this description is complete. It communicates what is returned, the ordering, and the fallback value, leaving no meaningful ambiguity for an agent invoking the tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so the baseline of 4 applies. There are no parameter semantics to document, and the description does not need to compensate for any schema gaps.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb ('Return') and a well-defined resource ('configured datasource priority list'), and clarifies ordering as most-preferred first. It is clearly distinguishable from siblings like list_datasources and set_datasource_priority.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies the tool is used when the agent needs the current datasource priority ordering, but it does not explicitly mention when to prefer this over alternatives or any exclusions. The context is clear enough for a simple getter, but no alternative routing is provided.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

ingest_datasource_modelsA

Auto-discover tables in a database and create / additively update semantic models from them.

Idempotent (DEV-1356): re-runs are additive only. New columns and joins are appended to existing models; existing column / join definitions are never overwritten. After the additive pass, returns the pending validate_models deletes alongside the additions.

Args: datasource_name: Name of an existing datasource (from list_datasources). include_tables: Comma-separated list of table names to include. If empty, all tables are ingested. schema_name: A single database schema to inspect (e.g. "public"). Empty uses the default schema. schemas: Comma-separated schemas to inspect. Mutually exclusive with schema_name / all_schemas. all_schemas: Ingest every non-system schema. Mutually exclusive with schema_name / schemas.

ParametersJSON Schema
NameRequiredDescriptionDefault
schemasNo
all_schemasNo
schema_nameNo
include_tablesNo
datasource_nameYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the behavioral disclosure burden. It clearly states idempotence, that re-runs are additive only, that existing definitions are never overwritten, and that the result includes pending validate_models deletes. This is substantial transparency for a mutating tool, though it does not cover failure modes or permissions.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the core purpose and the crucial idempotence guarantee, followed by the necessary parameter documentation. The wording around 'pending validate_models deletes' is slightly awkward, but every sentence earns its place given the lack of schema descriptions.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with five parameters, mutual exclusions, and a mutating/additive behavior, the description provides enough context to invoke it correctly: source datasource, table filtering, schema selection, and the additive result. It does not explain return structure in detail, but an output schema exists and the behavioral guarantee is clear.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate with parameter details. The Args section explains each parameter, gives examples, defines empty-string behavior, and documents mutual exclusivity among schema_name, schemas, and all_schemas. This fully compensates for the missing schema descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific action ('Auto-discover tables in a database') and a clear outcome ('create / additively update semantic models'). It clearly differentiates from sibling tools like create_model and edit_model by focusing on automatic discovery rather than manual construction.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives clear context: it operates on an existing datasource from list_datasources and is additive/idempotent, so it is safe to re-run. It does not explicitly enumerate when to choose this over create_model/edit_model, but the auto-discovery language makes the intended use case clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

inspectA

Inspect EXACTLY one entity by reference and kind, a homogeneous BATCH when reference is a list — or the whole COLLECTION at a kind when reference is omitted / None.

A clean point-lookup: no fusion / ranking / cypher, and no bundled memories. Use search instead when you want an entity surfaced in context (with related memories and ranked neighbours).

Before using a column as a filter, projection, group-by, or join key, inspect it and read its Description: (the schema author's intent) and Sample values: (the stored literal forms — a top-N sample, indicative rather than exhaustive; build text predicates from these, never a guessed spelling). Never pick a column from its name alone.

Collection (DEV-1667): omit reference (or pass None / []) to list a whole kind. entity_type="model" lists all models grouped by datasource (compact=True: one terse line per model; compact=False: the full per-model tables). entity_type="datasource" lists all datasources. Only model / datasource support the collection view; other kinds raise. This subsumes models_summary / list_datasources.

Batch (DEV-1612): pass a list of references that all share the one entity_type. Returns one rendered block per id, in input order, each echoing its resolved canonical id (a ## <canonical> header in markdown; a JSON array under format="json"). Per-id resolution errors are isolated — one bad id does not sink the batch (in JSON it becomes a {"reference": ..., "error": ...} element). A single str keeps its byte-for-byte single output; a one-element list is still batch-framed.

Args: reference: The entity reference, or a list of references (batch). Accepts canonical forms (mydb, mydb.orders, mydb.orders.amount), bare names, join paths (orders.customers.region → resolved to the owning model), and memory:<id> for memories. Normalised via the shared resolver; the normalised canonical id is echoed in the JSON shape. entity_type: REQUIRED. One of datasource, model, column, measure, aggregation, memory. Disambiguates the 3-part canonical collision (a name shared by, e.g., a column and an aggregation) and asserts the resolved kind — a mismatch returns a detailed error. compact: When true (default): description-only for column/measure/aggregation/datasource/memory; for entity_type="model" a cheap schema skeleton (column / measure / aggregation names + join targets, zero DB calls). False returns the full render (and, for the datasource kind, a per-model skeleton for each visible model). format: "markdown" (default) or "json". num_rows: Sample-data rows for entity_type="model". Ignored (with a warning) for other kinds. show_sql: Include generated SQL for entity_type="model". Ignored (with a warning) for datasource/memory; a silent no-op for column/measure/aggregation. sections: Section subset for entity_type="model". Ignored (with a warning) for other kinds. descriptions_max_chars: Truncate description fields to this many characters. Applies to every kind.

ParametersJSON Schema
NameRequiredDescriptionDefault
formatNomarkdown
compactNo
num_rowsNo
sectionsNo
show_sqlNo
referenceNo
entity_typeYes
descriptions_max_charsNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure, and it does so thoroughly. It reveals the no-fusion/no-ranking/no-cypher nature, batch error isolation with per-id error elements, canonical-id echoing, ignored parameter warnings, compact-mode behavior, and the fact that collection view only works for model/datasource. This goes well beyond typical descriptions.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long, but it covers three modes, eight parameters, kind-specific behavior, and error semantics. It is well-structured with clear section headers for single, collection, batch, and Args. Each sentence carries useful information; the ticket references and examples are compact and relevant. The front-loading of the core behavior makes it easy to scan.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (three modes, eight parameters, many kind-specific exceptions), the description leaves little to inference. It explains return shaping for markdown and JSON, error isolation, canonical resolution, parameter warnings, and which entities support collection view. The presence of an output schema means the description does not need to enumerate every render field, and it focuses on the behavioral and selection guidance an agent needs.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has 0% description coverage, so the Args section is the only source of parameter meaning. It fully compensates by explaining reference forms (canonical, bare, join paths, memory), the required entity_type enum, the semantics of compact, format, num_rows, show_sql, sections, and descriptions_max_chars, including which parameters are ignored for which kinds. Every parameter is given practical, non-obvious context.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description leads with a precise statement: Inspect exactly one entity by reference and kind, a homogeneous batch when reference is a list, or a whole collection when reference is omitted. It also distinguishes itself from siblings by naming search as the alternative for in-context lookup and noting that it subsumes models_summary and list_datasources. This is a model of clear purpose and sibling differentiation.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly states when to use the tool vs. alternatives: 'Use search instead when you want an entity surfaced in context (with related memories and ranked neighbours).' It also provides concrete guidance for collection mode, batch mode, and warns which entity types support collection. The advice to inspect columns before using them in filters/joins adds valuable use-context instructions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

inspect_modelA

DEPRECATED: use the inspect tool. Return a complete-yet-compact view of a semantic model.

Always emitted (regardless of sections): model header + description, metadata bullets (data_source, sql_table, default_time_dimension, hidden, row_count), backing-query structure for query-backed models, and — when show_sql=True — the custom SQL block, model-level filters, and the cached backing-query SQL.

Section-gated parts (subset selectable via sections):

  • columns — unified row-level columns table with a sampled column (distinct values for string/boolean, min .. max for number/date/time, or top20 ... (N distinct) for high- cardinality categoricals).

  • measures — named-formula library.

  • aggregations — custom aggregation definitions. The formula column and the sql field of each params[] entry are gated by show_sql.

  • joins — join definitions.

  • samples — live sample-data query (COUNT(*) plus one aggregation per column).

  • learnings — learning-only memories whose canonical entities reference this model.

When a section is omitted from sections: columns, measures, aggregations and joins collapse to a one-line backticked CSV of names; samples and learnings are dropped entirely. A footer at the end of the response lists what was trimmed and how to fetch more.

Args: model_name: Name of the model to inspect. num_rows: Max sample-data rows (default: 3). show_sql: When true, include the generated SQL for the sample-data query, the custom SQL block, model-level filters, the cached backing-query SQL, and aggregation formulas/param SQL. format: Output format — "markdown" (default) or "json". Case-insensitive. sections: Subset of ["columns", "measures", "aggregations", "joins", "samples", "learnings"]. Default (None or empty list) renders all six. Unknown names are ignored with a warning line at the end of the response. A non-empty list of only unknown names resolves to no sections (not all six) — "all sections" is reserved for None/[] so a typo can't silently trigger the full expensive payload. descriptions_max_chars: When set, every description field (model, column, measure, aggregation) longer than this is truncated with a ... [truncated] suffix. Must be >= 0. None (default) means no truncation.

ParametersJSON Schema
NameRequiredDescriptionDefault
formatNomarkdown
compactNo
num_rowsNo
sectionsNo
show_sqlNo
model_nameYes
data_sourceNo
descriptions_max_charsNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full disclosure burden and does so thoroughly. It details always-emitted content, section-gated behavior, collapse behavior for omitted sections, the footer, unknown-section handling, SQL gating via `show_sql`, and truncation semantics. This gives the agent strong awareness of response shape and edge-case behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but tightly structured and front-loaded with the deprecation warning and core purpose. Every major behavior is organized under clear headers, defaults and edge cases are explicit, and there is no filler or vague wording.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For an 8-parameter introspection tool with no annotations and an output schema, the description is unusually complete: it explains output composition, section subsetting, collapse behavior, SQL inclusion, format, and truncation. The only real gap is the undocumented `compact` and `data_source` parameters, which prevents a perfect score.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The raw schema has no parameter descriptions, so the description compensates substantially for `model_name`, `num_rows`, `show_sql`, `format`, `sections`, and `descriptions_max_chars`, including a nuanced explanation of unknown `sections` values. However, `compact` and `data_source` are absent from the Args section, leaving their semantics to inference from titles and context.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb ('Return') and resource ('semantic model') and defines the output as a 'complete-yet-compact view.' It also explicitly distinguishes itself from the sibling `inspect` tool by flagging DEPRECATED and directing the agent to use `inspect` instead, leaving no ambiguity about what this tool does.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description is explicit about usage: it begins with 'DEPRECATED: use the inspect tool,' naming the alternative and telling the agent when not to choose this tool. It also gives clear guidance on section selection, defaults, and edge cases, so an agent knows exactly how to request the desired subset of output.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_datasourcesA

List all configured database connections (names and types only, credentials are not shown). Use describe_datasource for connection details and status.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the burden of disclosing behavior. It states that credentials are not shown, which is an important privacy/security behavior beyond the basic 'list' semantics. The read-only nature is implied by 'List' but not explicitly stated; a small gap, but the redaction disclosure adds meaningful transparency.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two concise sentences with no filler. The main purpose is front-loaded, and the second sentence adds routing guidance. Every word earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a parameterless list tool with an output schema present, the description fully covers what the agent needs to decide whether to call it and what to expect. It also gives a pointer to the appropriate sibling for more detail, making the context complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters, so there is no parameter documentation needed. The description focuses on the output content instead, clarifying that only names and types are returned, which is the relevant semantic information for an agent.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action ('List all configured database connections') and the scope of the result ('names and types only'), immediately distinguishing it from connection detail operations. It also explicitly differentiates from describe_datasource.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly tells the agent when to use this tool versus an alternative: use describe_datasource for connection details and status. This is clear, direct routing guidance with no ambiguity.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

models_summaryA

Brief summary of all (non-hidden) models in a datasource.

DEV-1549: compact-by-default rendering. Under compact=True each model section emits its name, description, the column count (Columns: N), the comma-separated measure NAMES (Measures: a, b, c) and the Joins to: list — no per-column table, no per-measure formula block. Pass compact=False to restore the verbose markdown / JSON shape with full column and measure payloads.

Args: datasource_name: Name of the datasource (from list_datasources). format: Output format — "markdown" (default, compact and LLM-friendly) or "json" (structured array of model summaries). Case-insensitive. compact: Default True — drop per-column / per-measure detail. Set False to surface the full per-model tables.

ParametersJSON Schema
NameRequiredDescriptionDefault
formatNomarkdown
compactNo
datasource_nameYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does well by detailing exactly what compact=True emits (name, description, column count, measure names, joins list) and what it omits. It also discloses the default behavior and the compact=False alternative. It doesn't discuss errors or side effects, but none are expected for a read-only summary tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured with a clear opening sentence followed by rendering details and an Args list. The DEV-1549 ticket reference is noise for an AI agent, but all other sentences earn their place by clarifying output behavior and parameter choices.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description fully covers the parameters and output rendering modes, and an output schema exists to define the structured return shape. It could improve by naming sibling alternatives or noting datasource-not-found behavior, but for a summary tool with this complexity it is largely complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate, and it does. Each parameter is explained in the Args section: datasource_name references list_datasources, format names valid values and case-insensitivity, and compact explains default and behavioral impact. This adds substantial meaning beyond the bare input schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly identifies the tool's purpose: 'Brief summary of all (non-hidden) models in a datasource.' It specifies the resource scope (all non-hidden models) and gives a distinct action, though it doesn't explicitly contrast with siblings like inspect_model or describe_datasource.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives clear context for when the tool would be useful—summarizing all models in a datasource—but it never explicitly states when to prefer this over siblings such as inspect_model or describe_datasource. It does explain usage for compact versus verbose output and format selection, but tool-alternative guidance is implied rather than stated.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

queryA

Query data from a semantic model. Call inspect(reference=".", entity_type="model") first to see available columns and measures, and search (with the entities you plan to use and/or a free-text question) to surface saved learnings and example queries before finalizing a query.

The query argument takes one of three forms:

  • Model name (string) — run a query-backed saved model by name, e.g. "monthly_revenue" (honors variables; every other setting comes from the stored query).

  • Query object (dict) — a single query; per-field documentation is on the SlayerQuery schema.

  • Multi-stage list (list of query objects) — a DAG of stages. Every entry except the last MUST carry a name; the last entry is the root whose rows are returned. Stages reference one another by that name — as a source_model or via a join in an inline ModelExtension — and the engine orders them topologically. An inner stage's result columns become plain columns of the outer stage (dotted paths flatten: stores.name -> stores__name); a stage may reference only what its own source defines or what a prior stage projected — define before you reference. Use stages when a whole result set must be re-queried, joined, or reused; single-query nesting and computed dimensions already cover re-aggregation.

Expressions — one language, used in measures, computed dimensions, filters, and order. The same expression returns its value as a measure, groups by it as a computed dimension, masks as a filter (routed automatically to WHERE / HAVING / post-aggregation), and sorts in order.

  • Aggregations are function calls over a column or a same-model scalar expression: count(*), sum(total), sum(amount - cost), percentile(price, p=0.95). Available: sum, avg (both take window='90d' for trailing time windows), min, max, count, count_distinct, count_distinct_approx, median, percentile(x, p=), weighted_avg(x, weight=col), stddev_samp, stddev_pop, var_samp, var_pop, corr(x, other=col), covar_samp(x, other=col), covar_pop(x, other=col), first(x[, time_col]) / last(x[, time_col]) (earliest/latest record's value per group), plus model-defined custom aggregations. Write count_distinct(x), never count(distinct x).

  • All aggregations support partition_by= (bare names: partition_by=region, partition_by=[region, city], partition_by=[] for the grand total), computing the aggregate at that coarser grain; the result is broadcast over the missing dimensions.

  • Combine aggregations with arithmetic and transforms — missing dimensions broadcast on both sides. E.g. with dimensions ["city", "region"], the measure {"formula": "sum(total) / sum(total, partition_by=region)", "name": "share_of_region"} is each city's share of its region's total.

  • Aggregations nest: "avg(sum(total, partition_by=[region, city]), partition_by=[region])" averages the per-city totals within each region. The top-level partition_by must be a subset of the query's dimensions; inner aggregations' partition_by need not be. An outer aggregation's parameters must be determined by the operand's grain — a cell value at that grain, e.g. weight=count(id, partition_by=[region, city]), or a column that grain fixes; any other row column is a typed error.

  • Transforms wrap aggregated expressions: cumsum(x); change(x) / change_pct(x) (period-over-period delta / % change — calendar-aware and partition-safe, prefer these for growth); time_shift(x, -1[, 'year']) (the shifted value itself, for custom arithmetic); lag(x, n) / lead(x, n) (row-position shift, NULL at edges); first(x) / last(x) (broadcast the earliest/latest bucket's value); consecutive_periods(predicate) (trailing run length; the predicate may be row-level, e.g. status = 'paid'); rank(x), dense_rank(x), percent_rank(x), ntile(x, n=N) (rank family — optional partition_by=, no time dimension needed). All other transforms require a time_dimensions entry. Transforms nest in either order (change(cumsum(x))). Not supported: a row-level column mixed into a composite or nested input of time_shift / change / change_pct, or mixed with another aggregation's value inside one aggregation source.

  • Cross-model: reference any joined model's field as model_name.field_name (or a longer dotted path) and the engine figures out the join paths, avoiding fan-outs and chasm traps — each aggregation computes over its own model's rows exactly once; ambiguous routes error naming the candidates, and result keys use the full routed path. An aggregation sliced by a dimension not attributable to it broadcasts its value with a warning — see to_many_handling to attribute or error instead.

Method — decompose the question into blocks first: every qualifier, projected column, filter, grouping, unit, rounding, and ordering hint is one block, and each must map to a named column/measure/filter/dimension. Never drop a qualifier because no entity matched — search for it, else encode it as an expression or an inline ModelExtension column; reference already-encoded quantities by name rather than re-deriving their logic. Pin explicitly rather than guessing: which aggregation ("typical" is not automatically avg vs median), the grouping column and raw-vs-standardized labels, each aggregate's scope (all rows vs a filtered subset), sort column + direction + tie-break, NULL handling, units and rounding, exact numeric constants. "How many / count of" -> a scalar count(*); "which / list / show" -> the rows. Project exactly the columns the question names — no extras, none missing.

Filter literals — build every ==/in/like predicate on a text column from that column's sampled values (inspect it), never a guessed spelling; samples are a top-N snapshot, so when a needed literal is absent verify it (e.g. a distinct-values query) rather than assume either way. Compare case/whitespace-insensitively in the FILTER position only, never on a projected, grouped, or join-key column; abbreviations that case-folding can't unify go in the IN-set. Apply only the transformations (TRIM/ROUND/CAST/dedup) the question or a governing definition requires.

Verify — run the exact final query and read the result (show_sql=true when unsure): row count plausible; no dimension-only GROUP BY when you wanted per-record rows (distinct_dimension_values: false); sort column + direction as asked; each aggregate's scope right; NULL behavior intended; string values carry the expected casing. On a wrong result, change ONE variable at a time — two changes per attempt make the outcome uninterpretable.

Query-object fields taking the functional time-granularity form gran(col)gran one of second, minute, hour, day, week, week_sunday, month, quarter, year: dimensions: group-by columns; a granularity call such as month(created_at) buckets that timestamp, equivalent to a time_dimensions entry (and orderable as month(created_at)). time_dimensions: time-bucketed group-bys — {"dimension": ..., "granularity": ...} dicts, or the string form month(created_at). main_time_dimension: which time dimension time-ordered transforms key off.

Top-level arguments (siblings of query, NOT fields inside it): variables: Values for {placeholder} substitutions in filters / model SQL. Also settable per query object; precedence: runtime (top-level) > named-stage > outer-query > model.query_variables. show_sql: When true, include the generated SQL in the response for debugging. dry_run: When true, generate and return the SQL without executing it. explain: When true, run EXPLAIN ANALYZE and return the query plan. format: Output format — "markdown" (default, compact) | "json" | "csv". Case-insensitive.

Without an explicit limit the response is capped at 20 rows with a truncation notice.

Example: query(query={"source_model": "orders", "dimensions": ["status"], "measures": [{"formula": "count(*)"}], "filters": ["status == 'completed'"]})

ParametersJSON Schema
NameRequiredDescriptionDefault
queryYes
formatNomarkdown
dry_runNo
explainNo
show_sqlNo
variablesNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full behavioral burden and does so exhaustively: default 20-row truncation, automatic WHERE/HAVING/post-aggregation filter routing, broadcast warnings for non-attributable dimensions, topological stage ordering, dotted-path flattening, and precedence rules for variables. It also discloses debug behaviors (show_sql, dry_run, explain) and verification expectations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long, but every section earns its place for a tool of this complexity: purpose and prerequisites are front-loaded, and bold headers with bullet lists make the expression rules, argument forms, filters, and verification steps scannable. The included example concretizes the abstract spec without redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity, an output schema, and zero annotation coverage, the description is essentially complete: it covers preconditions, input forms, expression syntax, filter construction, stage semantics, debug options, output format, row caps, and a verification protocol. Nothing an agent must know to invoke this tool safely and correctly is left undocumented.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0% for top-level parameters, and the description fully compensates by documenting all five top-level siblings (variables, show_sql, dry_run, explain, format) with precedence and behavior, plus the three forms of the query argument in depth. It adds meaning far beyond the schema, including the expression language, gran() forms, and per-field clarification of query-object semantics.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

"Query data from a semantic model" states a specific verb and resource, and the opening lines position this tool as the execution step after inspect and search, naming sibling tools explicitly. The three query argument forms and the worked example further pin down exactly what this tool does versus siblings like inspect_model or search.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly says to call inspect(...) first and search(...) before finalizing a query, giving an unambiguous workflow. It also states when to use multi-stage lists versus single-query nesting, and warns against using limit to trim plain lists — clear when-to-use and when-not-to-use guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

recommend_root_modelA

Recommend the root model (query source_model) for a set of model.column / model.metric items, and give each item's join-qualified reference path from that root.

Introspects the join graph and picks the model from which every requested item is reachable (LEFT joins are directional; INNER joins traverse both ways), minimizing total join hops. The returned paths are ready to drop into a query whose source_model is the recommended root — e.g. a joined column comes back as customers.regions.name and a root-owned one as status; aggregation spellings (sum(revenue) / revenue:sum) are preserved.

When no single model reaches everything, root_model is null and coverage lists the best partial roots so you can split the request into a multi-stage query.

Call this once your item list is final, not as a schema browser — explore with search / inspect first.

Args: items: entity references (orders.revenue, customers.name, orders.revenue:sum / sum(orders.revenue), bare aov for a saved metric...). data_source: optional datasource scope; when omitted, names resolve via the datasource-priority list. All items must resolve to a single datasource. root_hint: optional intended root — a bare model name or <data_source>.<model> within the resolved datasource. Honored when it reaches every item (overriding the min-hops pick, so you can force a bridge model that owns none of the items); otherwise the auto-pick is used and a warning explains why. Resolved after the datasource is determined, so it cannot pick the datasource. format: "markdown" (default) or "json".

ParametersJSON Schema
NameRequiredDescriptionDefault
itemsYes
formatNomarkdown
root_hintNo
data_sourceNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full behavioral burden and does so thoroughly: it discloses directional LEFT joins, bidirectional INNER joins, min-hop optimization, root_hint override semantics, the warning when the hint is ignored, and the null-root/coverage fallback. This goes well beyond a basic 'recommends a root' summary.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the core purpose, then methodically covers behavior, edge cases, usage timing, and parameters. Every section earns its place; the Args section adds semantic value beyond the schema rather than repeating it.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a complex join-graph tool with 0% schema parameter coverage, the description is complete: it covers when to use it, all parameter semantics, key edge cases, output examples, and interaction with sibling tools. The existing output schema handles detailed return-structure documentation, so nothing essential is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate — and it does. items gets concrete spellings and examples, data_source gets scope and single-datasource constraints, root_hint gets override behavior and resolution order, and format gets its allowed values and default.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb-and-resource statement: it recommends a root model for a set of items and returns join-qualified reference paths. It also distinguishes itself from schema-browsing siblings by explicitly saying it is not a schema browser and pointing to search/inspect for exploration.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly states when to call the tool: once the item list is final, not during exploration. It also names alternatives (search/inspect) and explains the fallback behavior when no single root reaches every item, which helps an agent decide whether to split the request.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

save_memoryA

Save an agent memory: a free-form note plus the SLayer entities it concerns.

linked_entities accepts either:

  • a list of entity reference strings — each item is resolved to the canonical <datasource>.<model>[.<leaf>] form. Bare names use the datasource priority list; ambiguous bare-column matches are rejected. memory:<id> is also valid here (cross-memory references; the target memory must exist).

  • a SlayerQuery (dict) — entities are auto-extracted from source_model, dimensions, time_dimensions, measures, and filters; resolution warnings are non-fatal. The query itself is stored alongside the learning, so the memory surfaces in search's example_queries list (vs the memories list for entity-list memories).

DEV-1428: id is an optional canonical memory id. Omit to auto-allocate a monotonic int-shaped id ("1", "2", ...); supply a string for a stable user-controlled id ("kb.policy.42"). Charset excludes :, /, ?, #, whitespace. Duplicate id → unconditional upsert, created_at preserved.

Returns the assigned memory_id (string), the canonical entities stored, and any non-fatal warnings.

Cascade-on-delete: when a model / datasource / measure is deleted, every memory:<id> and <ds>.<model>[.<leaf>] reference under it is automatically stripped from every other memory's entities list. Memories with zero entities after the strip are kept (the learning text stands alone).

Search is lenient: stale entity tags in saved memories are filtered out at retrieval time rather than raising.

Args: learning: The note text. Required, non-empty. linked_entities: List of entity strings, or an inline SlayerQuery payload. id: Optional canonical memory id (see above).

Examples: save_memory( learning="orders.is_returned in {0,1,NULL}; treat NULL as not returned", linked_entities=["orders.is_returned"], )

save_memory(
    learning="Paid revenue by status",
    linked_entities={
        "source_model": "orders",
        "measures": [{"formula": "sum(amount)"}],
        "filters": ["status = 'paid'"],
    },
    id="kb.paid-revenue",
)
ParametersJSON Schema
NameRequiredDescriptionDefault
idNo
learningYes
descriptionNo
linked_entitiesYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden, and it delivers extensively. It discloses upsert semantics, id auto-allocation rules, charset restrictions, cascade-on-delete behavior, lenient stale-tag filtering, and non-fatal resolution warnings. This is far beyond what an agent could infer from the schema alone.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but well-organized, with a leading one-sentence summary followed by clearly scoped sections and practical examples. The length is justified by the absence of annotations and schema descriptions, though a few sections could be tightened without losing value.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a complex tool with no annotations and rich schema, the description covers purpose, input forms, id behavior, deletion side effects, search integration, and return values. The only notable omission is the optional description parameter, but overall the agent has everything needed to invoke the tool correctly and anticipate consequences.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate, and it thoroughly explains learning, linked_entities, and id, including accepted formats, edge cases, and examples. However, the schema also exposes a 'description' parameter that the description never mentions, leaving one of the four parameters undocumented.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a precise verb and resource: 'Save an agent memory: a free-form note plus the SLayer entities it concerns.' It distinguishes the tool from siblings like search and forget_memory, and even explains where saved memories surface in search's results, making its role unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides strong context for when to save a memory, including two distinct input modes (entity list vs SlayerQuery) and explains that query-based memories appear in search's example_queries list. It does not explicitly name alternatives or state when not to use this tool, but the save-versus-search/forget relationship is clear from context.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

set_datasource_priorityA

Configure how SLayer disambiguates bare model names that exist in multiple datasources.

When two datasources both define a model named users, calling edit_model("users") (no data_source=) is ambiguous. SLayer walks this priority list and picks the first datasource that has the requested name. If none of the candidates appear in the list, an AmbiguousModelError is raised.

Args: priority: Datasource names, most-preferred first. Each entry must already exist (run list_datasources first). Pass an empty list to clear the priority.

ParametersJSON Schema
NameRequiredDescriptionDefault
priorityYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full behavioral disclosure and does so well: it explains the priority-list walk, first-match selection, AmbiguousModelError when no candidate matches, and empty-list clearing behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the core purpose, then uses a concrete ambiguous-model example to make the behavior intuitive. Every part—purpose, example, resolution rules, parameter semantics—earns its place without redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple one-parameter configuration tool, the description covers the scenario, prerequisites, error behavior, and clearing semantics. The output schema can handle return details, and no critical information is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema only specifies 'priority' as an array of strings, so the description is essential. It adds ordering semantics (most-preferred first), the requirement that names already exist, and the empty-list clearing behavior.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource: 'Configure how SLayer disambiguates bare model names.' The description clearly distinguishes this from sibling tools like get_datasource_priority and list_datasources by focusing on the configuration/set behavior.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides clear context for when this tool is needed—when bare model names exist in multiple datasources—and gives an actionable prerequisite (run list_datasources first). It does not explicitly contrast with get_datasource_priority, but the read/write pairing is implied by the sibling names.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

validate_modelsA

Diff persisted SLayer models against the live database schema(s).

Returns a JSON-serialized list of pending delete operations (column drops, measure drops, join drops, filter removals, whole models) needed to keep stored models valid against the current live state. Read-only — does not modify storage.

Args: data_source: Datasource name to validate. When omitted, every datasource is validated concurrently and results are concatenated.

ParametersJSON Schema
NameRequiredDescriptionDefault
data_sourceNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does so well: it explicitly states 'Read-only — does not modify storage', describes what the returned list contains, and discloses the behavior when data_source is omitted (validates all datasources concurrently and concatenates results).

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the core purpose, followed by return behavior, safety note, and parameter semantics. Every sentence earns its place; no filler or repetition.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with one optional parameter and an output schema, the description is complete: it explains the input, the output shape, the no-modification guarantee, and the default all-datasources behavior. Nothing needed to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate. It fully explains the single parameter: 'Datasource name to validate', including the omission behavior and result concatenation. This adds meaning well beyond the raw schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource: 'Diff persisted SLayer models against the live database schema(s)'. It also clarifies the concrete output—a JSON list of pending delete operations—which distinguishes it from siblings like query, inspect_model, and delete_model.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives clear context for when to use the tool: to validate persisted models against live schemas and preview required deletions. It does not explicitly name alternative tools or exclusions, but the purpose is specific enough that an agent can select it appropriately among the sibling tools.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 2 tool updatesv0.10.2
    • Changedquery27 fields changed
      • changedInput schema / $defs / Column / properties / db_type / description
        Previous value: -"Raw database type string (e.g. 'point', 'jsonb'), retained when the declared DataType loses information. Populated by ingestion for UNKNOWN (opaque) columns; None for mapped types, where the declared DataType already carries everything we need."New value: +"Raw database type string (e.g. 'point', 'DECIMAL(18, 2)'), retained when the declared DataType loses information. Populated by ingestion for UNKNOWN (opaque) and exact NUMERIC/DECIMAL columns; None when the mapped type carries everything needed."
      • addedInput schema / $defs / ColumnRef
        Added value: +{
        +  "description": "A column reference: bare name or dotted join path (``customers.regions.name``); a short form (``regions.name``) auto-routes when exactly one route exists.",
        +  "properties": {
        +    "label": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "default": null,
        +      "title": "Label"
        +    },
        +    "model": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "default": null,
        +      "title": "Model"
        +    },
        +    "name": {
        +      "title": "Name",
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "name"
        +  ],
        +  "title": "ColumnRef",
        +  "type": "object"
        +}
      • addedInput schema / $defs / ComputedDimension
        Added value: +{
        +  "additionalProperties": false,
        +  "description": "A dimension computed by an ``expression``; an aggregation inside must carry ``partition_by=`` to fix its grain. Best given an explicit ``name``.",
        +  "properties": {
        +    "expression": {
        +      "title": "Expression",
        +      "type": "string"
        +    },
        +    "name": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "default": null,
        +      "title": "Name"
        +    }
        +  },
        +  "required": [
        +    "expression"
        +  ],
        +  "title": "ComputedDimension",
        +  "type": "object"
        +}
      • removedInput schema / $defs / DataType / description
        Removed value: -"SLayer data types — values match sqlglot's ``exp.DataType.Type``\nbyte-for-byte so SQL generation can ``CAST`` to the declared type without\na translation map. (DEV-1361.)\n\n``UNKNOWN`` is the explicit *opaque* type: the column's database type was\ndetected but SLayer cannot operate on it — it has no default btree/hash\noperator class, which is exactly what ``GROUP BY`` / ``DISTINCT`` require\n(``json``, ``xml``, the geometric / PostGIS types, range types, ...).\nComparable types keep working as ``TEXT`` even when unmapped — ``jsonb``,\n``uuid``, ``bytea``, arrays and ``tsvector`` are all groupable and are\ndeliberately *not* opaque. ``slayer.engine.ingestion._OPAQUE_SA_TYPE_NAMES``\nis the source of truth for that classification. Such a column is\n**stored and displayed** — its raw DB type string is kept on\n``Column.db_type`` — but it is never used in ``GROUP BY``, ``DISTINCT``,\naggregation, or ``CAST``, because those operations fail at the database\n(e.g. \"could not identify an equality operator for type point\"). Use the\n:attr:`is_opaque` property rather than comparing against the member\ndirectly. ``UNKNOWN`` is also a real ``sqlglot`` type name, so the\nbyte-equality invariant above still holds."
      • addedInput schema / $defs / ModelExtension / additionalProperties
        Added value: +false
      • changedInput schema / $defs / ModelExtension / properties / columns / anyOf
        Previous value: -[
        -  {
        -    "items": {},
        -    "type": "array"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "items": {
        +      "$ref": "#/$defs/Column"
        +    },
        +    "type": "array"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • changedInput schema / $defs / ModelExtension / properties / joins / anyOf
        Previous value: -[
        -  {
        -    "items": {},
        -    "type": "array"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "items": {
        +      "$ref": "#/$defs/ModelJoin"
        +    },
        +    "type": "array"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • addedInput schema / $defs / ModelJoin / properties / name
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "title": "Name"
        +}
      • changedInput schema / $defs / ModelMeasure / description
        Previous value: -"A named formula evaluating to an aggregated value (grammar: ``slayer/core/formula.py``)."New value: +"A named aggregated value: ``formula`` is an aggregation expression — inline in a query's measures or saved on a model and referenced by bare name. ``name`` sets the result key, referenceable in filters and order by either the name or the formula text."
      • addedInput schema / $defs / OrderItem
        Added value: +{
        +  "additionalProperties": false,
        +  "description": "A sort key: ``column`` is a result column name or an expression string; ``direction`` asc|desc.",
        +  "properties": {
        +    "column": {
        +      "$ref": "#/$defs/ColumnRef"
        +    },
        +    "direction": {
        +      "default": "asc",
        +      "title": "Direction",
        +      "type": "string"
        +    },
        +    "raw_formula": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "default": null,
        +      "description": "Internal — captured automatically from expression strings; do not set.",
        +      "title": "Raw Formula"
        +    }
        +  },
        +  "required": [
        +    "column"
        +  ],
        +  "title": "OrderItem",
        +  "type": "object"
        +}
      • changedInput schema / $defs / SlayerModel / properties / version / default
        Previous value: -9New value: +10
      • addedInput schema / $defs / SlayerQuery
        Added value: +{
        +  "additionalProperties": false,
        +  "description": "User-facing query object — what to retrieve from a model, as names/references, no SQL.",
        +  "properties": {
        +    "dimensions": {
        +      "anyOf": [
        +        {
        +          "items": {
        +            "anyOf": [
        +              {
        +                "$ref": "#/$defs/ColumnRef"
        +              },
        +              {
        +                "$ref": "#/$defs/ComputedDimension"
        +              }
        +            ]
        +          },
        +          "type": "array"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "default": null,
        +      "description": "Group-by columns — names / dotted paths, or computed expressions ({\"expression\": ..., \"name\": ...}); one result row per distinct value combination.",
        +      "title": "Dimensions"
        +    },
        +    "distinct_dimension_values": {
        +      "default": true,
        +      "description": "Default true: dimension-only queries return distinct dimension combinations (GROUP BY the projected dimensions). Set false for raw per-record rows — requires empty `measures` and no measure reference in `filters`/`order`. For rows plus a count, keep the default and add `count(*)`.",
        +      "title": "Distinct Dimension Values",
        +      "type": "boolean"
        +    },
        +    "filters": {
        +      "anyOf": [
        +        {
        +          "items": {
        +            "type": "string"
        +          },
        +          "type": "array"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "default": null,
        +      "description": "Condition strings, AND-ed; each routes automatically to WHERE / HAVING / post-aggregation. May contain aggregations, transforms, and {variable} placeholders.",
        +      "title": "Filters"
        +    },
        +    "limit": {
        +      "anyOf": [
        +        {
        +          "type": "integer"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "default": null,
        +      "description": "Max rows to return. Use only for top-N / 'the single most X' requests — never to trim a plain list (an uncapped MCP response is truncated at 20 rows with an explicit notice).",
        +      "title": "Limit"
        +    },
        +    "main_time_dimension": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "default": null,
        +      "description": "Name of the time dimension that time-ordered transforms (change, lag, ...) key off; overrides auto-detection when the query has multiple time dimensions.",
        +      "title": "Main Time Dimension"
        +    },
        +    "measures": {
        +      "anyOf": [
        +        {
        +          "items": {
        +            "$ref": "#/$defs/ModelMeasure"
        +          },
        +          "type": "array"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "default": null,
        +      "description": "Values to return: aggregation-expression formulas (see the query tool description). A bare name references a saved model measure.",
        +      "title": "Measures"
        +    },
        +    "name": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "default": null,
        +      "description": "Stage name in a multi-stage list; other stages reference it as their source_model.",
        +      "title": "Name"
        +    },
        +    "offset": {
        +      "anyOf": [
        +        {
        +          "type": "integer"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "default": null,
        +      "description": "Rows to skip.",
        +      "title": "Offset"
        +    },
        +    "order": {
        +      "anyOf": [
        +        {
        +          "items": {
        +            "$ref": "#/$defs/OrderItem"
        +          },
        +          "type": "array"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "default": null,
        +      "description": "Sort keys; column is a result column name or an aggregation-bearing expression string.",
        +      "title": "Order"
        +    },
        +    "source_model": {
        +      "anyOf": [
        +        {
        +          "oneOf": [
        +            {
        +              "type": "string"
        +            },
        +            {
        +              "$ref": "#/$defs/ModelExtension"
        +            },
        +            {
        +              "$ref": "#/$defs/SlayerModel"
        +            }
        +          ]
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "default": null,
        +      "description": "The query's population: a saved model name, an inline ModelExtension ({\"source_name\": ..., plus optional \"columns\"/\"measures\"/\"joins\"}), or a full inline model. Omit to infer the smallest model determining every queried dimension, time dimension, and row-level filter column (the choice is reported in response metadata).",
        +      "title": "Source Model"
        +    },
        +    "time_dimensions": {
        +      "anyOf": [
        +        {
        +          "items": {
        +            "anyOf": [
        +              {
        +                "$ref": "#/$defs/TimeDimension"
        +              },
        +              {
        +                "type": "string"
        +              }
        +            ]
        +          },
        +          "type": "array"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "default": null,
        +      "description": "Time-bucketed group-bys — one result row per bucket. Each entry is a TimeDimension dict or the functional string gran(col), e.g. \"month(created_at)\".",
        +      "title": "Time Dimensions"
        +    },
        +    "to_many_handling": {
        +      "default": "broadcast",
        +      "description": "What happens when an aggregation is sliced by a dimension not attributable to it: broadcast (default — repeat the value across the cells, with a warning) | associate (aggregate per cell over the distinct associated entities) | error (refuse).",
        +      "enum": [
        +        "broadcast",
        +        "associate",
        +        "error"
        +      ],
        +      "title": "To Many Handling",
        +      "type": "string"
        +    },
        +    "variables": {
        +      "anyOf": [
        +        {
        +          "additionalProperties": true,
        +          "type": "object"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "default": null,
        +      "description": "{placeholder} values scoped to this query object / stage; the tool-level variables argument overrides.",
        +      "title": "Variables"
        +    },
        +    "version": {
        +      "default": 4,
        +      "title": "Version",
        +      "type": "integer"
        +    },
        +    "whole_periods_only": {
        +      "default": false,
        +      "description": "Snap date filters to whole time buckets and drop the current incomplete bucket.",
        +      "title": "Whole Periods Only",
        +      "type": "boolean"
        +    }
        +  },
        +  "title": "SlayerQuery",
        +  "type": "object"
        +}
      • addedInput schema / $defs / TimeDimension
        Added value: +{
        +  "description": "Group-by on ``dimension`` truncated to ``granularity``; optional ``date_range`` [start, end] (ISO dates).",
        +  "properties": {
        +    "date_range": {
        +      "anyOf": [
        +        {
        +          "items": {
        +            "type": "string"
        +          },
        +          "type": "array"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "default": null,
        +      "title": "Date Range"
        +    },
        +    "dimension": {
        +      "$ref": "#/$defs/ColumnRef"
        +    },
        +    "granularity": {
        +      "$ref": "#/$defs/TimeGranularity"
        +    },
        +    "label": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "default": null,
        +      "title": "Label"
        +    }
        +  },
        +  "required": [
        +    "dimension",
        +    "granularity"
        +  ],
        +  "title": "TimeDimension",
        +  "type": "object"
        +}
      • addedInput schema / $defs / TimeGranularity
        Added value: +{
        +  "enum": [
        +    "second",
        +    "minute",
        +    "hour",
        +    "day",
        +    "week",
        +    "week_sunday",
        +    "month",
        +    "quarter",
        +    "year"
        +  ],
        +  "title": "TimeGranularity",
        +  "type": "string"
        +}
      • removedInput schema / properties / dimensions
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "items": {
        -        "type": "string"
        -      },
        -      "type": "array"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "title": "Dimensions"
        -}
      • removedInput schema / properties / distinct_dimension_values
        Removed value: -{
        -  "default": true,
        -  "title": "Distinct Dimension Values",
        -  "type": "boolean"
        -}
      • removedInput schema / properties / filters
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "items": {
        -        "type": "string"
        -      },
        -      "type": "array"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "title": "Filters"
        -}
      • removedInput schema / properties / limit
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "type": "integer"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "title": "Limit"
        -}
      • removedInput schema / properties / measures
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "items": {
        -        "additionalProperties": {
        -          "type": "string"
        -        },
        -        "type": "object"
        -      },
        -      "type": "array"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "title": "Measures"
        -}
      • removedInput schema / properties / offset
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "type": "integer"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "title": "Offset"
        -}
      • removedInput schema / properties / order
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "items": {
        -        "additionalProperties": {
        -          "type": "string"
        -        },
        -        "type": "object"
        -      },
        -      "type": "array"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "title": "Order"
        -}
      • addedInput schema / properties / query
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "$ref": "#/$defs/SlayerQuery"
        +    },
        +    {
        +      "items": {
        +        "$ref": "#/$defs/SlayerQuery"
        +      },
        +      "type": "array"
        +    }
        +  ],
        +  "title": "Query"
        +}
      • removedInput schema / properties / source_model
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "type": "string"
        -    },
        -    {
        -      "$ref": "#/$defs/ModelExtension"
        -    },
        -    {
        -      "$ref": "#/$defs/SlayerModel"
        -    }
        -  ],
        -  "title": "Source Model"
        -}
      • removedInput schema / properties / strict
        Removed value: -{
        -  "default": false,
        -  "title": "Strict",
        -  "type": "boolean"
        -}
      • removedInput schema / properties / time_dimensions
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "items": {
        -        "additionalProperties": true,
        -        "type": "object"
        -      },
        -      "type": "array"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "title": "Time Dimensions"
        -}
      • removedInput schema / properties / whole_periods_only
        Removed value: -{
        -  "default": false,
        -  "title": "Whole Periods Only",
        -  "type": "boolean"
        -}
      • changedInput schema / required
        Previous value: -[
        -  "source_model"
        -]New value: +[
        +  "query"
        +]
    • Removedquery_nested
  2. 21 tool updatesv0.10.0
    • First observedcreate_datasource
    • First observedcreate_model
    • First observeddelete_datasource
    • First observeddelete_model
    • First observeddescribe_datasource
    • First observededit_datasource
    • First observededit_model
    • First observedforget_memory
    • First observedget_datasource_priority
    • First observedingest_datasource_models
    • First observedinspect
    • First observedinspect_model
    • First observedlist_datasources
    • First observedmodels_summary
    • First observedquery
    • First observedquery_nested
    • First observedrecommend_root_model
    • First observedsave_memory
    • First observedsearch
    • First observedset_datasource_priority
    • First observedvalidate_models

TDQS

A3.9/5.0

Scored across 20 tools

Disambiguation4/5

Most tools have clearly distinct purposes (datasource lifecycle, model CRUD, querying, search/memory). However, inspect_model and models_summary are explicitly superseded by the broader inspect tool, creating redundancy that could cause misselection. Descriptions clearly mark these overlaps, but the presence of deprecated tools adds mild ambiguity.

Naming Consistency4/5

The predominant pattern is verb_noun (create_model, delete_datasource, set_datasource_priority, etc.). Deviations include bare verbs like 'search' and 'inspect', and noun-led 'models_summary'. The deprecated 'inspect_model' also creates an inconsistency with 'inspect'. Overall, the convention is mostly consistent with a few outliers.

Tool Count3/5

At 20 tools, this is on the heavy side but reasonable for a server covering datasource management, model CRUD, querying, and memory/search. It sits at the borderline between well-scoped (3-15) and slightly over (16-25), and the breadth of functionality justifies the count, though a few redundant tools could be trimmed.

Completeness4/5

The surface covers datasource lifecycle (create/describe/edit/delete/priority), model lifecycle (create/edit/delete/inspect/validate/ingest), querying, and search/memory. Minor gaps include lack of datasource credential updates and model renaming, but these are workaroundable. Deprecated tools add redundancy but don't create functional dead ends.

Maintenance

ActivityActive
ResponsivenessWithin a week

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    A semantic layer query engine with MCP support, enabling AI assistants to query structured data through natural language and declarative interfaces.
    2
    Apache 2.0
  • A
    license
    Not graded
    quality
    D
    maintenance
    Turns your data model into a semantic layer for AI agents, automatically generating typed, discoverable tools with entity relationships and schema discovery.
    644
    Apache 2.0
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI agents to understand and query your database safely by providing a semantic layer of metadata, with tools to search, explain, validate, and generate safe SQL.
    2
    MIT