Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changes/unreleased/Bug Fix-20260519-142146.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
kind: Bug Fix
body: 'Fix get_dimension_values: stringify dimension values to maintain list[str] type, omit nulls, add error handling, add limit >= 1 validation'
time: 2026-05-19T14:21:46.951552+02:00
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
kind: Enhancement or New Feature
body: Add get_dimension_values tool to Semantic Layer toolset to retrieve distinct values for a dimension
time: 2026-05-19T14:14:00.872546+02:00
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Tools for executing and generating SQL on dbt Platform infrastructure.
### Semantic Layer

To learn more about the dbt Semantic Layer, click [here](https://docs.getdbt.com/docs/use-dbt-semantic-layer/dbt-sl).
- `get_dimension_values`: Gets distinct values for a dimension; option to scope to specific metrics.
- `get_dimensions`: Gets dimensions for specified metrics.
- `get_entities`: Gets entities for specified metrics.
- `get_metrics_compiled_sql`: Returns compiled SQL for metrics without executing the query.
Expand Down
34 changes: 34 additions & 0 deletions src/dbt_mcp/prompts/semantic_layer/get_dimension_values.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<instructions>
Get the distinct values for a given dimension, optionally scoped to specific metrics.

Use this tool to discover what values exist for a dimension before building a `where`
filter in `query_metrics`. For example, to filter by country you first call this tool
to see which country values exist, then use those values in the `where` clause.

If the response includes `truncated: true`, there are more values than the current
`limit`. Increase `limit` to retrieve more, or add `metrics` to narrow the scope.
</instructions>

<examples>
<example>
Question: "I want to filter revenue for a specific country — what countries are available?"
Thinking step-by-step:
- The user wants to know valid values for a country dimension
- I should scope to the "revenue" metric for accurate results
Parameters:
dimension="customer__country"
metrics=["revenue"]
limit=100
</example>

<example>
Question: "What are the possible order statuses?"
Thinking step-by-step:
- The user wants all distinct values for an order status dimension
- No specific metric context needed
Parameters:
dimension="order__status"
metrics=null
limit=100
</example>
</examples>
39 changes: 39 additions & 0 deletions src/dbt_mcp/semantic_layer/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from dbt_mcp.semantic_layer.gql.gql_request import submit_request
from dbt_mcp.semantic_layer.types import (
DimensionToolResponse,
DimensionValuesResponse,
EntityToolResponse,
GetMetricsCompiledSqlError,
GetMetricsCompiledSqlResult,
Expand Down Expand Up @@ -112,6 +113,12 @@ def compile_sql(
read_cache: bool = True,
) -> str: ...

def dimension_values(
self,
metrics: list[str],
group_by: str,
) -> Any: ...
Comment thread
b-per marked this conversation as resolved.


class SemanticLayerClientProvider(Protocol):
async def get_client(
Expand Down Expand Up @@ -350,6 +357,38 @@ async def get_entities(
self.entities_cache[metrics_key] = entities
return self.entities_cache[metrics_key]

async def get_dimension_values(
self,
config: SemanticLayerConfig,
dimension: str,
metrics: list[str] | None = None,
limit: int = 100,
) -> DimensionValuesResponse:
try:
sl_client = await self.client_provider.get_client(config=config)
with sl_client.session():
raw_table: Any = await asyncio.to_thread(
Comment thread
b-per marked this conversation as resolved.
Outdated
sl_client.dimension_values,
metrics=metrics or [],
group_by=dimension,
)
# SDK doesn't support server-side limiting; truncation is applied client-side.
# SDK returns column names in uppercase; match case-insensitively.
schema_names = raw_table.schema.names
column_name = next(
(n for n in schema_names if n.lower() == dimension.lower()),
dimension,
)
raw: list[str] = [
str(v)
for v in raw_table.column(column_name).to_pylist()
if v is not None
]
Comment thread
b-per marked this conversation as resolved.
truncated = len(raw) > limit
return DimensionValuesResponse(values=raw[:limit], truncated=truncated)
except Exception as e:
raise RuntimeError(self._format_semantic_layer_error(e)) from e

def _format_semantic_layer_error(self, error: Exception) -> str:
"""Format semantic layer errors by cleaning up common error message patterns."""
error_str = str(error)
Expand Down
11 changes: 11 additions & 0 deletions src/dbt_mcp/semantic_layer/param_descriptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,14 @@
"Semantic Layer filter; use {{ Dimension('name') }}, "
"{{ TimeDimension('name', 'grain') }}, {{ Entity('name') }}; dates as yyyy-mm-dd"
)

SEMANTIC_DIMENSION = (
"The dimension name to retrieve values for "
"(e.g. `customer__country`, `order__status`)"
)

SEMANTIC_DIMENSION_VALUES_LIMIT = (
"Maximum number of distinct values to return (default 100). "
"If the result is truncated, the response `truncated` field will be True — "
"increase this limit to retrieve more values."
)
28 changes: 28 additions & 0 deletions src/dbt_mcp/semantic_layer/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
)
from dbt_mcp.semantic_layer.param_descriptions import (
QUERY_RESULT_LIMIT,
SEMANTIC_DIMENSION,
SEMANTIC_DIMENSION_VALUES_LIMIT,
SEMANTIC_GROUP_BY,
SEMANTIC_METRICS,
SEMANTIC_ORDER_BY,
Expand All @@ -28,6 +30,7 @@
)
from dbt_mcp.semantic_layer.types import (
DimensionToolResponse,
DimensionValuesResponse,
EntityToolResponse,
GetMetricsCompiledSqlSuccess,
ListMetricsResponse,
Comment thread
b-per marked this conversation as resolved.
Expand Down Expand Up @@ -201,6 +204,30 @@ async def get_entities(
)


@dbt_mcp_tool(
description=get_prompt("semantic_layer/get_dimension_values"),
title="Get Dimension Values",
read_only_hint=True,
destructive_hint=False,
idempotent_hint=True,
)
async def get_dimension_values(
context: SemanticLayerToolContext,
dimension: Annotated[str, Field(description=SEMANTIC_DIMENSION)],
metrics: Annotated[list[str] | None, Field(description=SEMANTIC_METRICS)] = None,
limit: Annotated[
int, Field(ge=1, description=SEMANTIC_DIMENSION_VALUES_LIMIT)
] = 100,
) -> DimensionValuesResponse:
config = await context.config_provider.get_config()
return await context.semantic_layer_fetcher.get_dimension_values(
config=config,
dimension=dimension,
metrics=metrics,
limit=limit,
)


@dbt_mcp_tool(
description=get_prompt("semantic_layer/query_metrics"),
title="Query Metrics",
Expand Down Expand Up @@ -274,6 +301,7 @@ async def get_metrics_compiled_sql(
list_saved_queries,
get_dimensions,
get_entities,
get_dimension_values,
query_metrics,
get_metrics_compiled_sql,
]
Expand Down
31 changes: 31 additions & 0 deletions src/dbt_mcp/semantic_layer/tools_multiproject.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
)
from dbt_mcp.semantic_layer.param_descriptions import (
QUERY_RESULT_LIMIT,
SEMANTIC_DIMENSION,
SEMANTIC_DIMENSION_VALUES_LIMIT,
SEMANTIC_GROUP_BY,
SEMANTIC_LAYER_PROJECT_ID,
SEMANTIC_METRICS,
Expand All @@ -32,6 +34,7 @@
from dbt_mcp.semantic_layer.tools import metrics_to_csv
from dbt_mcp.semantic_layer.types import (
DimensionToolResponse,
DimensionValuesResponse,
EntityToolResponse,
GetMetricsCompiledSqlSuccess,
OrderByParam,
Expand Down Expand Up @@ -216,13 +219,41 @@ async def get_metrics_compiled_sql(
return result.error


@dbt_mcp_tool(
description=get_prompt("semantic_layer/get_dimension_values"),
title="Get Dimension Values",
read_only_hint=True,
destructive_hint=False,
idempotent_hint=True,
)
async def get_dimension_values(
context: MultiProjectSemanticLayerToolContext,
project_id: Annotated[int, Field(description=SEMANTIC_LAYER_PROJECT_ID)],
dimension: Annotated[str, Field(description=SEMANTIC_DIMENSION)],
metrics: Annotated[list[str] | None, Field(description=SEMANTIC_METRICS)] = None,
limit: Annotated[
int, Field(ge=1, description=SEMANTIC_DIMENSION_VALUES_LIMIT)
] = 100,
) -> DimensionValuesResponse:
config = await context.semantic_layer_config_provider.get_config(project_id)
return await SemanticLayerFetcher(
client_provider=context.client_provider
).get_dimension_values(
config=config,
dimension=dimension,
metrics=metrics,
limit=limit,
)


MULTIPROJECT_SEMANTIC_LAYER_TOOLS: list[GenericToolDefinition[ToolName]] = [
list_metrics,
list_saved_queries,
get_dimensions,
get_entities,
query_metrics,
get_metrics_compiled_sql,
get_dimension_values,
]


Expand Down
6 changes: 6 additions & 0 deletions src/dbt_mcp/semantic_layer/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,9 @@ class GetMetricsCompiledSqlError:


GetMetricsCompiledSqlResult = GetMetricsCompiledSqlSuccess | GetMetricsCompiledSqlError


@dataclass
class DimensionValuesResponse:
values: list[str]
truncated: bool
1 change: 1 addition & 0 deletions src/dbt_mcp/tools/readme_mappings.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
ToolName.LIST_SAVED_QUERIES: "Retrieves all saved queries.",
ToolName.GET_DIMENSIONS: "Gets dimensions for specified metrics.",
ToolName.GET_ENTITIES: "Gets entities for specified metrics.",
ToolName.GET_DIMENSION_VALUES: "Gets distinct values for a dimension; option to scope to specific metrics.",
ToolName.QUERY_METRICS: "Executes metric queries with filtering and grouping options.",
ToolName.GET_METRICS_COMPILED_SQL: "Returns compiled SQL for metrics without executing the query.",
# Discovery tools
Expand Down
1 change: 1 addition & 0 deletions src/dbt_mcp/tools/tool_names.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class ToolName(Enum):
LIST_SAVED_QUERIES = "list_saved_queries"
GET_DIMENSIONS = "get_dimensions"
GET_ENTITIES = "get_entities"
GET_DIMENSION_VALUES = "get_dimension_values"
QUERY_METRICS = "query_metrics"
GET_METRICS_COMPILED_SQL = "get_metrics_compiled_sql"

Expand Down
1 change: 1 addition & 0 deletions src/dbt_mcp/tools/toolsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class Toolset(Enum):
ToolName.LIST_SAVED_QUERIES,
ToolName.GET_DIMENSIONS,
ToolName.GET_ENTITIES,
ToolName.GET_DIMENSION_VALUES,
Comment thread
b-per marked this conversation as resolved.
ToolName.QUERY_METRICS,
ToolName.GET_METRICS_COMPILED_SQL,
},
Expand Down
Loading
Loading