feat(server): integrate MCP queries and investigation messaging
Add project-bound OAuth MCP queries and controlled analysis with scoped evidence, limits, timeouts and a nearby-model spatial query using EPSG:4326 inputs. Default nearby searches to 200 meters and five results. Integrate investigation messaging/presentation, health/readiness and typed history; remove duplicate web search and fix online simulation and compressed baseline writes. Include project migrations 023-025; keep local credentials and demo seeding excluded. Validation: 319 focused pytest checks; real PostGIS temporary-table regression; read-only Jinqian bridge radius comparison; cross-language MCP discovery/calls.
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user