diff --git a/src/dedalus_mcp/context.py b/src/dedalus_mcp/context.py index 72d5b22..6e449ca 100644 --- a/src/dedalus_mcp/context.py +++ b/src/dedalus_mcp/context.py @@ -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) diff --git a/src/dedalus_mcp/server/core.py b/src/dedalus_mcp/server/core.py index 4f1d71f..fdd8140 100644 --- a/src/dedalus_mcp/server/core.py +++ b/src/dedalus_mcp/server/core.py @@ -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)) diff --git a/tests/test_context_dispatch.py b/tests/test_context_dispatch.py index 97cd5cc..4bfe68e 100644 --- a/tests/test_context_dispatch.py +++ b/tests/test_context_dispatch.py @@ -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() @@ -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)