Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 5 additions & 2 deletions src/dedalus_mcp/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -405,8 +405,11 @@ def _get_connections(self, runtime: Mapping[str, Any]) -> dict[str, str]:
"""Get connection mapping from JWT claims (ddls:connections)."""
auth_context = self.auth_context
if auth_context is None:
msg = """DEDALUS_DISPATCH_URL not found.
Dispatch is only available through the Dedalus-hosted MCP servers."""
msg = (
"Authorization context is None. "
"Ensure MCPServer has authorization=AuthorizationConfig() configured "
"and the client sends a valid JWT in the Authorization header."
)
raise RuntimeError(msg)

claims = getattr(auth_context, "claims", None)
Expand Down
13 changes: 11 additions & 2 deletions src/dedalus_mcp/server/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,9 +241,18 @@ def __init__(
self._connector_params = connector_params
self._auth_methods = auth_methods

# Auto-enable authorization when connections are defined (they require JWT claims)
if authorization is not None:
auth_config = authorization
elif connections:
# Connections require auth to resolve name → handle from JWT
auth_config = AuthorizationConfig(enabled=True)
else:
auth_config = AuthorizationConfig()

self._authorization_manager: AuthorizationManager | None = None
if authorization and authorization.enabled:
self._authorization_manager = AuthorizationManager(authorization)
if auth_config.enabled:
self._authorization_manager = AuthorizationManager(auth_config)

self._transport_factories: dict[str, TransportFactory] = {}
self.register_transport("stdio", lambda server: StdioTransport(server))
Expand Down
13 changes: 6 additions & 7 deletions tests/test_context_dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,12 +205,11 @@ async def test_dispatch_no_backend_raises(self):
await ctx.dispatch('ddls:conn:01ABC-github', request)

@pytest.mark.asyncio
async def test_dispatch_no_auth_context_still_validates_format(self, backend):
"""Dispatch without auth context should still validate handle format."""
async def test_dispatch_no_auth_context_raises_error(self, backend):
"""Dispatch without auth context should raise RuntimeError."""
mock_request_ctx = MockRequestContext(
lifespan_context={'dedalus_mcp.runtime': {
'dispatch_backend': backend,
'connection_handles': {'github': 'ddls:conn:01ABC-github'}
}}
)
mock_request = MagicMock()
Expand All @@ -219,10 +218,10 @@ async def test_dispatch_no_auth_context_still_validates_format(self, backend):

ctx = Context(
_request_context=mock_request_ctx,
runtime={'dispatch_backend': backend, 'connection_handles': {'github': 'ddls:conn:01ABC-github'}}
runtime={'dispatch_backend': backend}
)
request = HttpRequest(method=HttpMethod.POST, path="/repos")

# Valid handle should succeed
result = await ctx.dispatch('github', request)
assert result.success is True
# Without auth context, dispatch fails (can't look up connections from JWT)
with pytest.raises(RuntimeError, match='Authorization context is None'):
await ctx.dispatch('github', request)
Loading