Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
7e0a5c4
Enhance AI/BI dashboard skill with comprehensive widget specs
Mar 25, 2026
86b76b2
Add TOP-N + Other bucketing guidance for high-cardinality dimensions
Mar 25, 2026
a4cf4a2
Remove async deploy_dashboard function for consistency
Mar 25, 2026
5cd7189
Add genie_space_id parameter to dashboard creation
Mar 25, 2026
f29bbff
Add catalog and schema parameters to dashboard creation
Mar 25, 2026
93a91b2
Add comprehensive date range filtering documentation
Mar 25, 2026
618b2d4
Restructure AI/BI dashboard skill with improved organization
Mar 25, 2026
99b2307
Add back critical behavioral instructions for text widgets and filters
Mar 28, 2026
5d25ecf
Restore important behavioral instructions removed during restructure
Mar 28, 2026
09e71ae
Restore detailed guidance that was removed during restructure
Mar 28, 2026
ead5a54
Optimize MCP tool docstrings for token efficiency
Mar 30, 2026
f0032d1
Add parameter context for ambiguous docstring params
Mar 30, 2026
e7b6053
Consolidate MCP tools from 77 to 44 (43% reduction)
Mar 30, 2026
6e86c0f
Merge mcp-tool-token-optimization into dashboard-improvements
Mar 31, 2026
fb40af6
Add integration test infrastructure and fix tool bugs
Mar 31, 2026
75830b8
Change manage_dashboard to use file path instead of inline JSON
Mar 31, 2026
089aacb
Merge mcp-tool-token-optimization (with tests)
Mar 31, 2026
0db8152
Update dashboard tests for file-based approach
Mar 31, 2026
a73db19
Fix deploy_app to correctly handle SDK Wait[AppDeployment] return type
Mar 31, 2026
ad568e3
Fix deploy_app to correctly handle SDK Wait[AppDeployment] return type
Apr 1, 2026
7f37a3e
Clarify MCP tool usage in Genie skill documentation
Apr 1, 2026
fe7ea10
Fix typo in aibi_dashboards.py docstring
Apr 1, 2026
defaf89
Fix genie tools to use SDK methods instead of manager
Apr 1, 2026
3fb94b6
Improve integration test reliability and timeout handling
Apr 1, 2026
30617bf
Improve Unity Catalog tool docstrings with comprehensive parameter do…
Apr 1, 2026
4cf4133
Add CRITICAL validation steps to dashboard tool docstring
Apr 1, 2026
04f06d6
Add design best practices section and use relative file paths
Apr 1, 2026
92acf6f
Merge mcp-tool-token-optimization into dashboard-improvements
Apr 1, 2026
e6bf7bf
Improve AI/BI dashboard skill documentation with comprehensive examples
Apr 2, 2026
5147edb
Add skill reading requirement to dashboard MCP tool docstring
Apr 2, 2026
c31cce6
Fix MCP server crash on request cancellation
Apr 2, 2026
53b2b5e
Add structured_content to error responses for MCP SDK validation
Apr 2, 2026
2e6df6c
Migrate KA operations to Python SDK and fix name lookup issues
Apr 2, 2026
5483638
Fix knowledge source description requirement and test ordering
Apr 2, 2026
20ab575
Merge mcp-tool-token-optimization: SDK migration and bug fixes
Apr 2, 2026
1f4b1a9
Fix structured_content not populated for tools with return type annot…
Apr 2, 2026
0c8672d
Merge fix/mcp-cancellation-crash: cancellation and structured_content…
Apr 2, 2026
76006eb
fix(mcp): apply async wrapper on all platforms to prevent cancellatio…
Apr 2, 2026
1fc1354
Merge fix/mcp-cancellation-crash: cross-platform async wrapper fix
Apr 2, 2026
c300ded
Fix: don't set structured_content on error responses
Apr 2, 2026
6af8d7f
Merge fix/mcp-cancellation-crash: error validation fix
Apr 2, 2026
5846266
Improve dashboard skill structure based on error analysis
Apr 2, 2026
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
1 change: 1 addition & 0 deletions databricks-mcp-server/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.test-results/
22 changes: 22 additions & 0 deletions databricks-mcp-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,28 @@ Claude now has both:

The server is intentionally simple - each tool file just imports functions from `databricks-tools-core` and decorates them with `@mcp.tool`.

### Running Integration Tests

Integration tests run against a real Databricks workspace. Configure authentication first (see Step 3 above).

```bash
# Run all tests (excluding slow tests like cluster creation)
python tests/integration/run_tests.py

# Run all tests including slow tests
python tests/integration/run_tests.py --all

# Show report from the latest run
python tests/integration/run_tests.py --report

# Run with fewer parallel workers (default: 8)
python tests/integration/run_tests.py -j 4
```

Results are saved to `tests/integration/.test-results/<timestamp>/` with logs for each test folder.

See [tests/integration/README.md](tests/integration/README.md) for more details.

To add a new tool:

1. Add the function to `databricks-tools-core`
Expand Down
106 changes: 52 additions & 54 deletions databricks-mcp-server/databricks_mcp_server/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
Provides cross-cutting concerns like timeout and error handling for all MCP tool calls.
"""

import asyncio
import anyio
import json
import logging
import traceback
Expand Down Expand Up @@ -44,7 +44,27 @@ async def on_call_tool(
arguments = context.message.arguments

try:
return await call_next(context)
result = await call_next(context)

# Fix for FastMCP not populating structured_content automatically.
# When a tool has a return type annotation (e.g., -> Dict[str, Any]),
# FastMCP generates an outputSchema but doesn't set structured_content.
# MCP SDK then fails validation: "outputSchema defined but no structured output"
# We fix this by parsing the JSON text content and setting structured_content.
if result and not result.structured_content and result.content:
if len(result.content) == 1 and isinstance(result.content[0], TextContent):
try:
parsed = json.loads(result.content[0].text)
if isinstance(parsed, dict):
# Create new ToolResult with structured_content populated
result = ToolResult(
content=result.content,
structured_content=parsed,
)
except (json.JSONDecodeError, TypeError):
pass # Not valid JSON, leave as-is

return result

except TimeoutError as e:
# In Python 3.11+, asyncio.TimeoutError is an alias for TimeoutError,
Expand All @@ -53,47 +73,33 @@ async def on_call_tool(
"Tool '%s' timed out. Returning structured result.",
tool_name,
)
# Don't set structured_content for errors - it would be validated against
# the tool's outputSchema and fail (error dict doesn't match expected type)
return ToolResult(
content=[
TextContent(
type="text",
text=json.dumps(
{
"error": True,
"error_type": "timeout",
"tool": tool_name,
"message": str(e) or "Operation timed out",
"action_required": (
"Operation may still be in progress. "
"Do NOT retry the same call. "
"Use the appropriate get/status tool to check current state."
),
}
),
)
]
content=[TextContent(type="text", text=json.dumps({
"error": True,
"error_type": "timeout",
"tool": tool_name,
"message": str(e) or "Operation timed out",
"action_required": (
"Operation may still be in progress. "
"Do NOT retry the same call. "
"Use the appropriate get/status tool to check current state."
),
}))]
)

except asyncio.CancelledError:
except anyio.get_cancelled_exc_class():
# Re-raise CancelledError so MCP SDK's handler catches it and skips
# calling message.respond(). If we return a result here, the SDK will
# try to respond, but the request may already be marked as responded
# by the cancellation handler, causing an AssertionError crash.
# See: https://github.com/modelcontextprotocol/python-sdk/pull/1153
logger.warning(
"Tool '%s' was cancelled. Returning structured result.",
"Tool '%s' was cancelled. Re-raising to let MCP SDK handle cleanup.",
tool_name,
)
return ToolResult(
content=[
TextContent(
type="text",
text=json.dumps(
{
"error": True,
"error_type": "cancelled",
"tool": tool_name,
"message": "Operation was cancelled by the client",
}
),
)
]
)
raise

except Exception as e:
# Log the full traceback for debugging
Expand All @@ -104,22 +110,14 @@ async def on_call_tool(
traceback.format_exc(),
)

# Return a structured error response
error_message = str(e)
error_type = type(e).__name__

# Return error as text content only - don't set structured_content.
# Setting structured_content would cause MCP SDK to validate it against
# the tool's outputSchema, which fails (error dict doesn't match expected type).
return ToolResult(
content=[
TextContent(
type="text",
text=json.dumps(
{
"error": True,
"error_type": error_type,
"tool": tool_name,
"message": error_message,
}
),
)
]
content=[TextContent(type="text", text=json.dumps({
"error": True,
"error_type": type(e).__name__,
"tool": tool_name,
"message": str(e),
}))]
)
29 changes: 20 additions & 9 deletions databricks-mcp-server/databricks_mcp_server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,23 @@ async def async_wrapper(**kwargs):
_patch_subprocess_stdin()


def _patch_tool_decorator_for_windows():
"""Wrap sync tool functions in asyncio.to_thread() on Windows.
def _patch_tool_decorator_for_async():
"""Wrap sync tool functions in asyncio.to_thread() on all platforms.

FastMCP's FunctionTool.run() calls sync functions directly on the asyncio
event loop thread, which blocks the stdio transport's I/O tasks. On Windows
with ProactorEventLoop this causes a deadlock where all MCP tools hang.
event loop thread, which blocks the stdio transport's I/O tasks. This causes:

1. On Windows with ProactorEventLoop: deadlock where all MCP tools hang.

2. On ALL platforms: cancellation race conditions. When the MCP client
cancels a request (e.g., timeout), the event loop can't propagate the
CancelledError to blocking sync code. The sync function eventually
returns, but the MCP SDK has already responded to the cancellation,
causing "Request already responded to" assertion errors and crashes.

This patch intercepts @mcp.tool registration to wrap sync functions so they
run in a thread pool, yielding control back to the event loop for I/O.
run in a thread pool, yielding control back to the event loop for I/O and
enabling proper cancellation handling via anyio's task cancellation.
"""
original_tool = mcp.tool

Expand Down Expand Up @@ -132,11 +140,14 @@ async def _noop_lifespan(*args, **kwargs):
# Register middleware (see middleware.py for details on each)
mcp.add_middleware(TimeoutHandlingMiddleware())

# Apply async wrapper on Windows to prevent event loop deadlocks.
# Apply async wrapper on ALL platforms to:
# 1. Prevent event loop deadlocks (critical on Windows)
# 2. Enable proper cancellation handling (critical on all platforms)
# Without this, sync tools block the event loop, preventing CancelledError
# propagation and causing "Request already responded to" crashes.
# TODO: FastMCP 3.x automatically wraps sync functions in asyncio.to_thread().
# Test if this Windows-specific patch is still needed with FastMCP 3.x.
if sys.platform == "win32":
_patch_tool_decorator_for_windows()
# Test if this patch is still needed with FastMCP 3.x.
_patch_tool_decorator_for_async()

# Import and register all tools (side-effect imports: each module registers @mcp.tool decorators)
from .tools import ( # noqa: F401, E402
Expand Down
Loading