fix(mcp): route workspace-qualified memory urls (#790)

Signed-off-by: phernandez <paul@basicmachines.co>
This commit is contained in:
Paul Hernandez
2026-05-03 18:18:10 -05:00
committed by GitHub
parent 0a72d81bb3
commit 05adda1502
20 changed files with 1931 additions and 59 deletions
+37 -1
View File
@@ -4,6 +4,7 @@ from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException, Request
from fastapi.exception_handlers import http_exception_handler
from fastapi.responses import JSONResponse
from fastapi.routing import APIRouter
from loguru import logger
@@ -29,6 +30,12 @@ import logfire
from basic_memory.config import init_api_logging
from basic_memory.services.exceptions import EntityAlreadyExistsError
from basic_memory.services.initialization import initialize_app
from basic_memory.workspace_context import (
WORKSPACE_SLUG_HEADER,
WORKSPACE_TYPE_HEADER,
workspace_permalink_context_validation_error,
workspace_permalink_context,
)
@asynccontextmanager
@@ -87,6 +94,32 @@ app = FastAPI(
lifespan=lifespan,
)
@app.middleware("http")
async def workspace_permalink_context_middleware(request: Request, call_next):
"""Populate workspace permalink context from request headers."""
workspace_slug = request.headers.get(WORKSPACE_SLUG_HEADER)
workspace_type = request.headers.get(WORKSPACE_TYPE_HEADER)
validation_error = workspace_permalink_context_validation_error(workspace_slug, workspace_type)
if validation_error is not None:
return JSONResponse(
status_code=400,
content={"detail": validation_error},
)
if not workspace_slug:
return await call_next(request)
# ContextVar state remains active across the awaited downstream handler while
# this context manager is open, so entity creation can see request metadata.
with workspace_permalink_context(
workspace_slug=workspace_slug,
workspace_type=workspace_type,
):
return await call_next(request)
# Include v2 routers FIRST (more specific paths must match before /{project} catch-all)
app.include_router(v2_knowledge, prefix="/v2/projects/{project_id}")
app.include_router(v2_memory, prefix="/v2/projects/{project_id}")
@@ -146,4 +179,7 @@ async def exception_handler(request, exc): # pragma: no cover
error_type=type(exc).__name__,
error=str(exc),
)
return await http_exception_handler(request, HTTPException(status_code=500, detail=str(exc)))
return await http_exception_handler(
request,
HTTPException(status_code=500, detail="Internal server error"),
)