Libraries created through the API are now stamped with the name of the
UserToken that created them (Library.managed_by), on both the workspace
and plain create endpoints; web-session creates stay null/unmanaged. The
idempotent workspace re-POST lazily backfills null managed_by, and a
one-off backfill_managed_by command labels pre-existing rows (workspace
inference: kairos-mail-* → Kairos, else Daedalus; Spelunker via ingest
job provenance).
UI badges and warnings now render "Managed by <app>" via
managed_by_display (inference fallback keeps legacy rows accurate before
backfill). The list scope filter becomes all/managed/unmanaged with the
old daedalus/global values aliased for bookmarks. The plain create
endpoint also gains an explicit 409 name_conflict (previously a raw
UniqueProperty 500) reporting the existing library's uid and manager.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Rename MCPToken to UserToken across models, views, and tests
- Update URL names from mcp-token-* to token-*
- Add Daedalus/Pallas integration design doc (v2)
- Switch docker-compose to build local mnemosyne:local image via shared
build config instead of pulling from git.helu.ca
Introduce x-logging anchor with json-file driver, size/file caps, and
container name tagging so Alloy on puck can reliably tail every service
through the Docker socket. Apply to all services and inject
MNEMOSYNE_COMPONENT env vars (init/app/mcp/worker) for consistent log
attribution both in Loki and via `docker logs`.
Also update mnemosyne_integration.md to reflect the shift from per-turn
JWTs to long-lived team JWTs for workspace-scoped MCP access.
Team JWTs include `aud=mnemosyne` while per-turn JWTs omit `aud`
entirely. Since `iss` + `typ` already partition the two token
populations, explicitly skip audience verification to avoid rejecting
valid tokens.
Also expand test coverage for the MCP auth surface to exercise all
three credential types (opaque MCPToken, per-turn JWT, team JWT),
including replay cache behavior and Neo4j-backed library resolution
via mocked cypher queries.
Both helpers were load-bearing during the Pallas<->Mnemosyne shakedown:
* _extract_tool_name: covers the current FastMCP shape
(context.message.name directly), the legacy .params.name fallback,
prefer-direct behaviour, and every None-producing path. Includes a
contract test against the real mcp.types.CallToolRequestParams which
skips if the mcp package isn't importable.
* _extract_token: covers Bearer/bearer schemes, Authorization/
authorization header casing, whitespace stripping, missing/empty/
non-Bearer headers, RuntimeError degrading to None (outside an
HTTP dispatch), and non-RuntimeError propagating loudly.
Uses SimpleTestCase (no DB) with unittest.mock.patch on
mcp_server.auth.get_http_request to avoid pulling in FastMCP internals.
Run as part of mnemosyne's mcp_server suite:
TEST_NEO4J_ENABLED=0 python manage.py test mcp_server \
--testrunner=test_db_manager.django_integration.PostgreSQLTestRunner
17 new tests, all green; total mcp_server suite 59 tests passing.
Workspace scoping is the integration's security-critical property: an
agent in workspace A must never see content from workspace B or from
any global library, regardless of what the calling LLM tries.
Adds `workspace_id` to SearchRequest with __post_init__ normalization
that converts empty strings to None — so "" cannot slip through as a
truthy filter at the Cypher boundary. Extracts the workspace scope
clause to a single string and appends it to all five search queries
(vector, fulltext-chunk, fulltext-concept, graph, image):
($workspace_id IS NULL AND lib.workspace_id IS NULL
OR lib.workspace_id = $workspace_id)
Either workspace-only or global-only — never both — and the operator
precedence is bracketed so a refactor can't accidentally widen it. A
test verifies the literal clause string for that exact reason.
Adds `workspace_id` as a parameter to every MCP tool (`search`,
`get_chunk`, `list_libraries`, `list_collections`, `list_items`).
Deliberately undocumented in tool docstrings so the calling LLM is never
told the parameter exists — it is system-injected by Daedalus's chat
path and force-overwritten before reaching Mnemosyne. Mnemosyne also
validates the value but the security guarantee is enforced upstream.
Adds the `get_health` MCP tool per the Pallas health spec: returns
ok / degraded / error after probing Neo4j, S3, and the embedding
model registration. Used by Daedalus's existing health poller.
Updates the server INSTRUCTIONS string to advertise the new tool and
the two new library types (business, finance).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace plaintext token storage with SHA-256 hashes so leaked database
contents cannot be used to authenticate. Plaintext is generated, shown
once at creation time, and never persisted.
- Add `hash_token()` helper and `MCPTokenManager.create_token()` that
returns `(instance, plaintext)`.
- Replace `token` field with indexed `token_hash`; look up bearers by
hashing the incoming value.
- Update dashboard, management command, and admin to surface plaintext
only at creation. Disable admin "add" since it cannot reveal plaintext.
- Migration drops the old `token` column and adds `token_hash`;
pre-existing tokens are invalidated and must be reissued.
- Remove Phase 4 RAG pipeline in favor of retrieval-only architecture
- Add FastMCP server exposing search, get_chunk, list_libraries tools
- Mount MCP endpoints (streamable HTTP + SSE) via Starlette in ASGI config
- Update README to clarify Mnemosyne is a retrieval engine, not RAG
- Let calling LLMs drive synthesis and iterative retrieval themselves