Skip to content

Commit 5200e89

Browse files
committed
Rename Remote Tools to SQL Tools
1 parent fd8e9c3 commit 5200e89

13 files changed

Lines changed: 76 additions & 57 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
kind: Under the Hood
2+
body: Rename Remote Tools to SQL Tools
3+
time: 2025-07-29T16:32:33.198341-05:00

.task/checksum/d2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
31337af97fd86e997bc2f565c952abab
1+
783a7ad93470e77eac8085bb455e12e

README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ This MCP (Model Context Protocol) server provides tools to interact with dbt. Re
3737
* `get_model_parents` - Gets parent nodes of a specific model
3838
* `get_model_children` - Gets children modes of a specific model
3939

40-
### Remote
40+
### SQL
4141
* `text_to_sql` - Generate SQL from natural language requests
4242
* `execute_sql` - Execute SQL on dbt Cloud's backend infrastructure with support for Semantic Layer SQL syntax. Note: using a PAT instead of a service token for `DBT_TOKEN` is required for this tool.
4343

@@ -50,25 +50,25 @@ This MCP (Model Context Protocol) server provides tools to interact with dbt. Re
5050

5151
The MCP server takes the following environment variable configuration:
5252

53-
### Tool Groups
53+
### Tools
5454
| Name | Default | Description |
5555
| ------------------------ | ------- | ------------------------------------------------------------------------------- |
5656
| `DISABLE_DBT_CLI` | `false` | Set this to `true` to disable dbt Core, dbt Cloud CLI, and dbt Fusion MCP tools |
57-
| `DISABLE_SEMANTIC_LAYER` | `false` | Set this to `true` to disable dbt Semantic Layer MCP objects |
58-
| `DISABLE_DISCOVERY` | `false` | Set this to `true` to disable dbt Discovery API MCP objects |
59-
| `DISABLE_REMOTE` | `true` | Set this to `false` to enable remote MCP objects |
57+
| `DISABLE_SEMANTIC_LAYER` | `false` | Set this to `true` to disable dbt Semantic Layer MCP tools |
58+
| `DISABLE_DISCOVERY` | `false` | Set this to `true` to disable dbt Discovery API MCP tools |
59+
| `DISABLE_SQL` | `true` | Set this to `false` to enable SQL MCP tools |
6060
| `DISABLE_TOOLS` | "" | Set this to a list of tool names delimited by a `,` to disable certain tools |
6161

6262

63-
### Configuration for Discovery, Semantic Layer, and Remote Tools
63+
### Configuration for Discovery, Semantic Layer, and SQL Tools
6464
| Name | Default | Description |
6565
| -------------------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
6666
| `DBT_HOST` | `cloud.getdbt.com` | Your dbt Cloud instance hostname. This will look like an `Access URL` found [here](https://docs.getdbt.com/docs/cloud/about-cloud/access-regions-ip-addresses). If you are using Multi-cell, do not include the `ACCOUNT_PREFIX` here |
6767
| `MULTICELL_ACCOUNT_PREFIX` | - | If you are using Multi-cell, set this to your `ACCOUNT_PREFIX`. If you are not using Multi-cell, do not set this environment variable. You can learn more [here](https://docs.getdbt.com/docs/cloud/about-cloud/access-regions-ip-addresses) |
6868
| `DBT_TOKEN` | - | Your personal access token or service token. Note: a service token is required when using the Semantic Layer and this service token should have at least `Semantic Layer Only`, `Metadata Only`, and `Developer` permissions. |
6969
| `DBT_PROD_ENV_ID` | - | Your dbt Cloud production environment ID |
7070

71-
### Configuration for Remote Tools
71+
### Configuration for SQL Tools
7272
| Name | Description |
7373
| ---------------- | ----------------------------------------- |
7474
| `DBT_DEV_ENV_ID` | Your dbt Cloud development environment ID |

docs/d2.png

-8.69 KB
Loading

docs/diagram.d2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ tools: Tools {
5252
query_metrics
5353
}
5454

55-
remote: Remote tools {
55+
sql: SQL tools {
5656
label.near: outside-right-center
5757
class: container
5858
text_to_sql

src/dbt_mcp/config/config.py

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class DbtCliConfig(BaseModel):
3838
dbt_cli_timeout: int
3939

4040

41-
class RemoteConfig(BaseModel):
41+
class SqlConfig(BaseModel):
4242
multicell_account_prefix: str | None = None
4343
host: str
4444
user_id: int
@@ -72,7 +72,8 @@ class DbtMcpSettings(BaseSettings):
7272
disable_dbt_cli: bool = Field(False, alias="DISABLE_DBT_CLI")
7373
disable_semantic_layer: bool = Field(False, alias="DISABLE_SEMANTIC_LAYER")
7474
disable_discovery: bool = Field(False, alias="DISABLE_DISCOVERY")
75-
disable_remote: bool = Field(True, alias="DISABLE_REMOTE")
75+
disable_remote: bool | None = Field(None, alias="DISABLE_REMOTE")
76+
disable_sql: bool | None = Field(None, alias="DISABLE_SQL")
7677
disable_tools: Annotated[list[ToolName] | None, NoDecode] = Field(
7778
None, alias="DISABLE_TOOLS"
7879
)
@@ -87,6 +88,14 @@ def actual_host(self) -> str | None:
8788
def actual_prod_environment_id(self) -> int | None:
8889
return self.dbt_prod_env_id or self.dbt_env_id
8990

91+
@property
92+
def actual_disable_sql(self) -> bool:
93+
if self.disable_sql is not None:
94+
return self.disable_sql
95+
if self.disable_remote is not None:
96+
return self.disable_remote
97+
return True
98+
9099
@field_validator("disable_tools", mode="before")
91100
@classmethod
92101
def parse_disable_tools(cls, env_var: str | None) -> list[ToolName]:
@@ -112,7 +121,7 @@ def parse_disable_tools(cls, env_var: str | None) -> list[ToolName]:
112121

113122
class Config(BaseModel):
114123
tracking_config: TrackingConfig
115-
remote_config: RemoteConfig | None = None
124+
sql_config: SqlConfig | None = None
116125
dbt_cli_config: DbtCliConfig | None = None
117126
discovery_config: DiscoveryConfig | None = None
118127
semantic_layer_config: SemanticLayerConfig | None = None
@@ -133,19 +142,19 @@ def load_config() -> Config:
133142
if (
134143
not settings.disable_semantic_layer
135144
or not settings.disable_discovery
136-
or not settings.disable_remote
145+
or not settings.actual_disable_sql
137146
):
138147
if not settings.actual_host:
139148
errors.append(
140-
"DBT_HOST environment variable is required when semantic layer, discovery, or remote tools are enabled."
149+
"DBT_HOST environment variable is required when semantic layer, discovery, or SQL tools are enabled."
141150
)
142151
if not settings.actual_prod_environment_id:
143152
errors.append(
144-
"DBT_PROD_ENV_ID environment variable is required when semantic layer, discovery, or remote tools are enabled."
153+
"DBT_PROD_ENV_ID environment variable is required when semantic layer, discovery, or SQL tools are enabled."
145154
)
146155
if not settings.dbt_token:
147156
errors.append(
148-
"DBT_TOKEN environment variable is required when semantic layer, discovery, or remote tools are enabled."
157+
"DBT_TOKEN environment variable is required when semantic layer, discovery, or SQL tools are enabled."
149158
)
150159
if settings.actual_host and (
151160
settings.actual_host.startswith("metadata")
@@ -154,14 +163,14 @@ def load_config() -> Config:
154163
errors.append(
155164
"DBT_HOST must not start with 'metadata' or 'semantic-layer'."
156165
)
157-
if not settings.disable_remote:
166+
if not settings.actual_disable_sql:
158167
if not settings.dbt_dev_env_id:
159168
errors.append(
160-
"DBT_DEV_ENV_ID environment variable is required when remote tools are enabled."
169+
"DBT_DEV_ENV_ID environment variable is required when SQL tools are enabled."
161170
)
162171
if not settings.dbt_user_id:
163172
errors.append(
164-
"DBT_USER_ID environment variable is required when remote tools are enabled."
173+
"DBT_USER_ID environment variable is required when SQL tools are enabled."
165174
)
166175
if not settings.disable_dbt_cli:
167176
if not settings.dbt_project_dir:
@@ -177,16 +186,16 @@ def load_config() -> Config:
177186
raise ValueError("Errors found in configuration:\n\n" + "\n".join(errors))
178187

179188
# Build configurations
180-
remote_config = None
189+
sql_config = None
181190
if (
182-
not settings.disable_remote
191+
not settings.actual_disable_sql
183192
and settings.dbt_user_id
184193
and settings.dbt_token
185194
and settings.dbt_dev_env_id
186195
and settings.actual_prod_environment_id
187196
and settings.actual_host
188197
):
189-
remote_config = RemoteConfig(
198+
sql_config = SqlConfig(
190199
multicell_account_prefix=settings.multicell_account_prefix,
191200
user_id=settings.dbt_user_id,
192201
token=settings.dbt_token,
@@ -270,7 +279,7 @@ def load_config() -> Config:
270279
dbt_cloud_user_id=settings.dbt_user_id,
271280
local_user_id=local_user_id,
272281
),
273-
remote_config=remote_config,
282+
sql_config=sql_config,
274283
dbt_cli_config=dbt_cli_config,
275284
discovery_config=discovery_config,
276285
semantic_layer_config=semantic_layer_config,

src/dbt_mcp/mcp/server.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
from dbt_mcp.config.config import Config
1717
from dbt_mcp.dbt_cli.tools import register_dbt_cli_tools
1818
from dbt_mcp.discovery.tools import register_discovery_tools
19-
from dbt_mcp.remote.tools import register_remote_tools
2019
from dbt_mcp.semantic_layer.tools import register_sl_tools
20+
from dbt_mcp.sql.tools import register_sql_tools
2121
from dbt_mcp.tracking.tracking import UsageTracker
2222

2323
logger = logging.getLogger(__name__)
@@ -112,8 +112,8 @@ async def create_dbt_mcp(config: Config):
112112
logger.info("Registering dbt cli tools")
113113
register_dbt_cli_tools(dbt_mcp, config.dbt_cli_config, config.disable_tools)
114114

115-
if config.remote_config:
116-
logger.info("Registering remote tools")
117-
await register_remote_tools(dbt_mcp, config.remote_config, config.disable_tools)
115+
if config.sql_config:
116+
logger.info("Registering SQL tools")
117+
await register_sql_tools(dbt_mcp, config.sql_config, config.disable_tools)
118118

119119
return dbt_mcp
Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from httpx import Client
99
from mcp import CallToolRequest, JSONRPCResponse, ListToolsResult
1010
from mcp.server.fastmcp import FastMCP
11-
from mcp.server.fastmcp.tools.base import Tool
11+
from mcp.server.fastmcp.tools.base import Tool as InternalTool
1212
from mcp.server.fastmcp.utilities.func_metadata import (
1313
ArgModelBase,
1414
FuncMetadata,
@@ -19,20 +19,20 @@
1919
CallToolResult,
2020
ContentBlock,
2121
TextContent,
22+
Tool,
2223
)
23-
from mcp.types import Tool as RemoteTool
2424
from pydantic import Field, ValidationError, WithJsonSchema, create_model
2525
from pydantic.fields import FieldInfo
2626
from pydantic_core import PydanticUndefined
2727

28-
from dbt_mcp.config.config import RemoteConfig
28+
from dbt_mcp.config.config import SqlConfig
2929
from dbt_mcp.tools.tool_names import ToolName
3030

3131
logger = logging.getLogger(__name__)
3232

3333

3434
# Based on this: https://github.com/modelcontextprotocol/python-sdk/blob/9ae4df85fbab97bf476ddd160b766ca4c208cd13/src/mcp/server/fastmcp/utilities/func_metadata.py#L105
35-
def get_remote_tool_fn_metadata(tool: RemoteTool) -> FuncMetadata:
35+
def get_remote_tool_fn_metadata(tool: Tool) -> FuncMetadata:
3636
dynamic_pydantic_model_params: dict[str, Any] = {}
3737
for key in tool.inputSchema["properties"].keys():
3838
# Remote tools shouldn't have type annotations or default values
@@ -58,23 +58,29 @@ def get_remote_tool_fn_metadata(tool: RemoteTool) -> FuncMetadata:
5858
)
5959

6060

61-
def _get_remote_tools(base_url: str, headers: dict[str, str]) -> list[RemoteTool]:
61+
def _get_sql_tools(base_url: str, headers: dict[str, str]) -> list[Tool]:
6262
try:
6363
with Client(base_url=base_url, headers=headers) as client:
6464
list_tools_response = JSONRPCResponse.model_validate_json(
6565
client.get("/tools/list").text
6666
)
6767
return ListToolsResult.model_validate(list_tools_response.result).tools
6868
except Exception as e:
69-
logger.error(f"Error getting remote tools: {e}")
69+
logger.error(f"Error getting SQL tools: {e}")
7070
return []
7171

7272

73-
async def register_remote_tools(
73+
async def register_sql_tools(
7474
dbt_mcp: FastMCP,
75-
config: RemoteConfig,
75+
config: SqlConfig,
7676
exclude_tools: Sequence[ToolName] = [],
7777
) -> None:
78+
"""
79+
Register SQL MCP tools.
80+
81+
SQL tools are hosted remotely, so their definitions aren't found in this repo.
82+
"""
83+
7884
is_local = config.host and config.host.startswith("localhost")
7985
path = "/mcp" if is_local else "/api/ai/mcp"
8086
scheme = "http://" if is_local else "https://"
@@ -88,11 +94,11 @@ async def register_remote_tools(
8894
"x-dbt-dev-environment-id": str(config.dev_environment_id),
8995
"x-dbt-user-id": str(config.user_id),
9096
}
91-
remote_tools = _get_remote_tools(base_url=base_url, headers=headers)
97+
sql_tools = _get_sql_tools(base_url=base_url, headers=headers)
9298
logger.info(
93-
f"Loaded remote tools: {', '.join([tool.name for tool in remote_tools])}",
99+
f"Loaded sql tools: {', '.join([tool.name for tool in sql_tools])}",
94100
)
95-
for tool in remote_tools:
101+
for tool in sql_tools:
96102
if tool.name.lower() in [tool.value.lower() for tool in exclude_tools]:
97103
continue
98104

@@ -151,7 +157,7 @@ async def tool_function(*args, **kwargs) -> Sequence[ContentBlock]:
151157
return tool_function
152158

153159
new_tool = create_tool_function(tool.name)
154-
dbt_mcp._tool_manager._tools[tool.name] = Tool(
160+
dbt_mcp._tool_manager._tools[tool.name] = InternalTool(
155161
fn=new_tool,
156162
title=tool.title,
157163
name=tool.name,

src/dbt_mcp/tools/tool_names.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class ToolName(Enum):
3232
GET_MODEL_PARENTS = "get_model_parents"
3333
GET_MODEL_CHILDREN = "get_model_children"
3434

35-
# Remote tools
35+
# SQL tools
3636
TEXT_TO_SQL = "text_to_sql"
3737
EXECUTE_SQL = "execute_sql"
3838

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
from mcp.server.fastmcp import FastMCP
22

33
from dbt_mcp.config.config import load_config
4-
from dbt_mcp.remote.tools import register_remote_tools
4+
from dbt_mcp.sql.tools import register_sql_tools
55

66

7-
async def test_remote_tool_execute_sql():
7+
async def test_sql_tool_execute_sql():
88
config = load_config()
99
dbt_mcp = FastMCP("Test")
10-
await register_remote_tools(dbt_mcp, config.remote_config)
10+
await register_sql_tools(dbt_mcp, config.sql_config)
1111
tools = await dbt_mcp.list_tools()
1212
print(tools)
1313
result = await dbt_mcp.call_tool("execute_sql", {"sql": "SELECT 1"})
1414
assert len(result) == 1
1515
assert "1" in result[0].text
1616

1717

18-
async def test_remote_tool_text_to_sql():
18+
async def test_sql_tool_text_to_sql():
1919
config = load_config()
2020
dbt_mcp = FastMCP("Test")
21-
await register_remote_tools(dbt_mcp, config.remote_config)
21+
await register_sql_tools(dbt_mcp, config.sql_config)
2222
result = await dbt_mcp.call_tool("text_to_sql", {"text": "SELECT 1"})
2323
assert len(result) == 1
2424
assert "SELECT 1" in result[0].text

0 commit comments

Comments
 (0)