diff --git a/mnemosyne/mnemosyne/asgi.py b/mnemosyne/mnemosyne/asgi.py index 675c974..3c0472f 100644 --- a/mnemosyne/mnemosyne/asgi.py +++ b/mnemosyne/mnemosyne/asgi.py @@ -20,7 +20,7 @@ django.setup() from django.core.asgi import get_asgi_application # noqa: E402 from starlette.applications import Starlette # noqa: E402 -from starlette.responses import JSONResponse # noqa: E402 +from starlette.responses import JSONResponse, RedirectResponse # noqa: E402 from starlette.routing import Mount, Route # noqa: E402 from mcp_server.server import mcp # noqa: E402 @@ -35,6 +35,13 @@ async def health(request): return JSONResponse({"status": "ok"}) +async def mcp_redirect(request): + """Bare /mcp → /mcp/ — MCP clients that omit the trailing slash hit + Starlette's Mount which only matches /mcp/* paths, falling through to + Django and returning 404. A 307 preserves the POST body.""" + return RedirectResponse(url="/mcp/", status_code=307) + + @asynccontextmanager async def lifespan(app): async with mcp_http_app.lifespan(app), mcp_sse_app.lifespan(app): @@ -44,6 +51,7 @@ async def lifespan(app): app = Starlette( routes=[ Route("/mcp/health", health), + Route("/mcp", mcp_redirect, methods=["GET", "POST"]), Mount("/mcp/sse", app=mcp_sse_app), Mount("/mcp", app=mcp_http_app), Mount("/", app=application), diff --git a/validator/.gitignore b/validator/.gitignore index a7e961a..092fa9a 100644 --- a/validator/.gitignore +++ b/validator/.gitignore @@ -1,4 +1,5 @@ fastagent.secrets.yaml +fastagent.jsonl .venv/ *.egg-info/ __pycache__/ diff --git a/validator/README.md b/validator/README.md index 0aaa211..8c1658e 100644 --- a/validator/README.md +++ b/validator/README.md @@ -55,7 +55,7 @@ cd mnemosyne/ uvicorn mnemosyne.asgi:app --host 0.0.0.0 --port 22091 --workers 1 ``` -By default the validator points at `http://localhost:22091/mcp`. If your Mnemosyne is on another host, override `mcp.servers.mnemosyne.url` in `fastagent.secrets.yaml`. +By default the validator points at `http://localhost:22091/mcp/` (note the trailing slash — Starlette's `Mount` only matches paths under the mount prefix, so `/mcp` without the slash falls through to Django). If your Mnemosyne is on another host, override `mcp.servers.mnemosyne.url` in `fastagent.secrets.yaml`. ## Run the validator diff --git a/validator/fastagent.config.yaml b/validator/fastagent.config.yaml index bd6a6e7..b0b07f5 100644 --- a/validator/fastagent.config.yaml +++ b/validator/fastagent.config.yaml @@ -27,6 +27,6 @@ mcp: # override the URL in fastagent.secrets.yaml or via Ansible if remote. mnemosyne: transport: http - url: "http://localhost:22091/mcp" + url: "http://localhost:22091/mcp/" # Bearer token in fastagent.secrets.yaml (provisioned via # `python manage.py create_mcp_token `).