"""Host-only MCP bindings: declared in REST contracts even when MCP is disabled.""" from fastapi import APIRouter, Header, HTTPException, Request, Response from psycopg.errors import UndefinedTable, InvalidSchemaName from app.mcp.models import BindInput, BindResponse from app.infra.db.postgresql.mcp_queries import McpQueryRepository router = APIRouter(tags=["MCP Query Context"]) def runtime_for(request: Request): runtime = getattr(request.app.state, "mcp_runtime", None) if runtime is None: raise HTTPException(503, "Product MCP is disabled") return runtime @router.post("/mcp-query-contexts", response_model=BindResponse) async def create_context( command: BindInput, request: Request, x_project_id: str = Header(alias="X-Project-Id"), ) -> BindResponse: runtime = runtime_for(request) principal = await runtime.identity(request) from app.mcp.contexts import bind_context try: async with runtime.scope_factory(principal, x_project_id) as scope: binding = await bind_context(scope, principal, command, runtime.settings) except (UndefinedTable, InvalidSchemaName): raise HTTPException( 503, "MCP project storage has not been provisioned" ) from None return BindResponse( context_handle=binding.context_handle, expires_at=binding.expires_at, task_id=binding.task_id, run_id=binding.run_id, ) @router.delete("/mcp-query-contexts/{context_handle}", status_code=204) async def delete_context( context_handle: str, request: Request, x_project_id: str = Header(alias="X-Project-Id"), ) -> Response: runtime = runtime_for(request) principal = await runtime.identity(request) try: async with runtime.scope_factory(principal, x_project_id) as scope: await McpQueryRepository(scope.connection, scope.project.project_id).revoke( context_handle, principal.subject, principal.client_id ) except (UndefinedTable, InvalidSchemaName): raise HTTPException( 503, "MCP project storage has not been provisioned" ) from None return Response(status_code=204)