Skip to content

Commit 7db3b24

Browse files
authored
Add support for Python debugger (#391)
## Summary Adds Python debugger support by implementing `streamable-http` transport alongside the default `stdio` transport. - Maintains the behavior of being able to use `task inspector` command to test tools via the MCP Inspector without debugging - Adds support to run the VS Code debugger with MCP Inspector - Adds support to attach a debugger in general to MCP Inspector ## What Changed - Added `MCP_TRANSPORT` environment variable support to switch between `stdio` (default for prod) and `streamable-http` (default for local dev using `task` or VS Code debugger) transports - Updated `task dev` and `task inspector` to work directly with the Python interpreter by default for debugger compatibility (`mcp run` defaults to `stdio` transport) - Added `validate_transport` in `src/dbt_mcp/config/transport.py` to properly handle transport types Note: - `CONTRIBUTING.md` documents the three different options/workflows enabled for local dev! - This is a development-only change. Production usage remains unchanged (the server defaults to `stdio` transport when `MCP_TRANSPORT` is not set) ## Why The `stdio` transport doesn't support Python debuggers because stdin/stdout are all used up for MCP communication. With `streamable-http` transport as an option, devs can now use full debugger capabilities while testing with MCP Inspector. ## Related Issues Closes #361 ## Checklist - [x] I have performed a self-review of my code - [ ] I have made corresponding changes to the documentation (in https://github.com/dbt-labs/docs.getdbt.com) if required - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes ## Additional Notes ### Scenarios / Workflows Tested: #### 1. Option 1: `task inspector` to simply use MCP Inspector (with `stdio` transport) to run tools without debugging MCP Inspector using STDIO transport: <img width="1917" height="1263" alt="Screenshot 2025-10-07 at 3 40 08 PM" src="https://github.com/user-attachments/assets/a2eea430-d35d-4171-951f-7b7e10ea1ab6" /> #### 2. Option 2: VS Code Debugger + MCP Inspector (`streamable-http` transport) MCP Inspector using Streamable HTTP transport: <img width="1920" height="1280" alt="Screenshot 2025-10-07 at 3 37 32 PM" src="https://github.com/user-attachments/assets/d90ad85b-b3a2-4e26-95a8-a3dfc9bfb7dd" /> VS Code debugger with breakpoint set (triggered by call in MCP Inspector above ^^): <img width="1683" height="1248" alt="Screenshot 2025-10-07 at 3 38 59 PM" src="https://github.com/user-attachments/assets/b06d112e-d4ef-4c04-b195-1eb241759718" /> #### 3. Claude Desktop to ensure production server initialize still works properly with `stdio` transport Claude Desktop successfully uses my dev dbt MCP instance via STDIO transport: <img width="1224" height="861" alt="Screenshot 2025-10-07 at 8 53 03 AM" src="https://github.com/user-attachments/assets/e8da6405-7f52-417a-9b2d-850da7ae69a4" /> Admittedly, I haven't yet tested "Option 3" from the `CONTRIBUTING.md` file which involves manually attaching a debugger to the server that's run with `task dev`. It's the same exact underlying principals as using the VS Code debugger (which I have verified), but with more manual configuration and use. I will try and put some time into that here soon.
1 parent 8af89bd commit 7db3b24

7 files changed

Lines changed: 87 additions & 6 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: Add support for Python debugger
3+
time: 2025-10-07T15:57:17.329501-07:00

.vscode/launch.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
{
22
"configurations": [
33
{
4+
"console": "integratedTerminal",
5+
"env": {
6+
"MCP_TRANSPORT": "streamable-http"
7+
},
48
"name": "debug dbt-mcp",
59
"program": "${workspaceFolder}/src/dbt_mcp/main.py",
610
"request": "launch",

CONTRIBUTING.md

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,32 @@ Every PR requires a changelog entry. [Install changie](https://changie.dev/) and
7676

7777
## Debugging
7878

79-
If you encounter any problems. You can try running `task run` to see errors in your terminal.
79+
The dbt-mcp server runs with `stdio` transport by default which does not allow for Python debugger support. For debugging with breakpoints, use `streamable-http` transport.
80+
81+
### Option 1: MCP Inspector Only (No Breakpoints)
82+
1. Run `task inspector` - this starts both the server and inspector automatically
83+
2. Open MCP Inspector UI
84+
3. Use "STDIO" Transport Type to connect
85+
4. Test tools interactively in the inspector UI (uses `stdio` transport, no debugger support)
86+
87+
### Option 2: VS Code Debugger with Breakpoints (Recommended for Debugging)
88+
1. Set breakpoints in your code
89+
2. Press `F5` or select "debug dbt-mcp" from the Run menu
90+
3. Open MCP Inspector UI via `npx @modelcontextprotocol/inspector`
91+
4. Connect to `http://localhost:8000/mcp/v1` using "Streamable HTTP" transport and "Via Proxy" connection type
92+
5. Call tools from Inspector - your breakpoints will trigger
93+
94+
### Option 3: Manual Debugging with `task dev`
95+
1. Run `task dev` - this starts the server with `streamable-http` transport on `http://localhost:8000`
96+
2. Set breakpoints in your code
97+
3. Attach your debugger manually (see [debugpy documentation](https://github.com/microsoft/debugpy#debugpy) for examples)
98+
4. Open MCP Inspector via `npx @modelcontextprotocol/inspector`
99+
5. Connect to `http://localhost:8000/mcp/v1` using "Streamable HTTP" transport and "Via Proxy" connection type
100+
6. Call tools from Inspector - your breakpoints will trigger
101+
102+
**Note:** `task dev` uses `streamable-http` by default. The `streamable-http` transport allows the debugger and MCP Inspector to work simultaneously without conflicts. To override, use `MCP_TRANSPORT=stdio task dev`.
103+
104+
If you encounter any problems, you can try running `task run` to see errors in your terminal.
80105

81106
## Release
82107

Taskfile.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,16 +59,18 @@ tasks:
5959
- uv run src/dbt_mcp/main.py
6060

6161
dev:
62-
desc: "Run the dbt-mcp server in development mode"
62+
desc: "Run the dbt-mcp server in development mode (with debugger support)"
63+
env:
64+
MCP_TRANSPORT: streamable-http
6365
cmds:
6466
- (cd ui && pnpm install && pnpm build)
65-
- ./.venv/bin/mcp dev ./src/dbt_mcp/main.py
67+
- ./.venv/bin/python ./src/dbt_mcp/main.py
6668

6769
inspector:
6870
desc: "Run the dbt-mcp server with MCP inspector"
6971
cmds:
7072
- (cd ui && pnpm install && pnpm build)
71-
- npx @modelcontextprotocol/inspector ./.venv/bin/mcp run src/dbt_mcp/main.py
73+
- npx @modelcontextprotocol/inspector ./.venv/bin/python src/dbt_mcp/main.py
7274

7375
test:
7476
desc: "Run the tests"

src/dbt_mcp/config/transport.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import logging
2+
from typing import Literal
3+
4+
logger = logging.getLogger(__name__)
5+
6+
TransportType = Literal["stdio", "sse", "streamable-http"]
7+
VALID_TRANSPORTS: set[TransportType] = {"stdio", "sse", "streamable-http"}
8+
9+
10+
def validate_transport(transport: str) -> TransportType:
11+
"""Validate and return the MCP transport type."""
12+
transport = transport.strip().lower()
13+
14+
if transport not in VALID_TRANSPORTS:
15+
valid_options = ", ".join(sorted(VALID_TRANSPORTS))
16+
raise ValueError(
17+
f"Invalid MCP_TRANSPORT: '{transport}'. Must be one of: {valid_options}"
18+
)
19+
20+
logger.debug(f"Using MCP transport: {transport}")
21+
return transport # type: ignore[return-value]

src/dbt_mcp/main.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
import asyncio
2+
import os
23

34
from dbt_mcp.config.config import load_config
5+
from dbt_mcp.config.transport import validate_transport
46
from dbt_mcp.mcp.server import create_dbt_mcp
57

68

79
def main() -> None:
810
config = load_config()
9-
asyncio.run(create_dbt_mcp(config)).run()
11+
server = asyncio.run(create_dbt_mcp(config))
12+
transport = validate_transport(os.environ.get("MCP_TRANSPORT", "stdio"))
13+
server.run(transport=transport)
1014

1115

12-
main()
16+
if __name__ == "__main__":
17+
main()
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import pytest
2+
3+
from dbt_mcp.config.transport import validate_transport
4+
5+
6+
class TestValidateTransport:
7+
def test_valid_transports(self):
8+
assert validate_transport("stdio") == "stdio"
9+
assert validate_transport("sse") == "sse"
10+
assert validate_transport("streamable-http") == "streamable-http"
11+
12+
def test_case_insensitive_and_whitespace(self):
13+
assert validate_transport(" STDIO ") == "stdio"
14+
assert validate_transport("SSE") == "sse"
15+
16+
def test_invalid_transport_raises_error(self):
17+
with pytest.raises(ValueError) as exc_info:
18+
validate_transport("invalid")
19+
20+
assert "Invalid MCP_TRANSPORT: 'invalid'" in str(exc_info.value)
21+
assert "sse, stdio, streamable-http" in str(exc_info.value)

0 commit comments

Comments
 (0)