feat(validator): add bare FastAgent + Pallas validator for Mnemosyne MCP
A self-contained sub-project under validator/ that wraps Mnemosyne's MCP server in a single FastAgent. Use it to confirm — outside of Daedalus — that Mnemosyne's MCP transport works, every tool registers, args/responses round-trip, and an LLM can actually drive the tools. The validator is its own Pallas-consuming project with its own pyproject (pallas-mcp + fast-agent-mcp), agents.yaml, and fastagent.config.yaml — matching the pattern used by Iolaus and other Pallas consumers. It does not import Mnemosyne Python code; it only speaks MCP over HTTP. The agent never sets workspace_id, so all calls run against the global scope (libraries with workspace_id IS NULL). Workspace-scoped validation will come once Daedalus's chat path is wired (Daedalus injects workspace_id server-side, force-overwriting whatever the LLM produces). Default model is openai.Qwen3.5-35B-A3B-UD-Q4_K_XL.gguf served by llama.cpp at nyx.helu.ca:22079/v1. Token provisioning via `python manage.py create_mcp_token --user <u> --name validator`. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
9
validator/.env.example
Normal file
9
validator/.env.example
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Mnemosyne Validator — environment variables
|
||||||
|
#
|
||||||
|
# Copy to .env and adjust as needed. Loaded by Pallas at startup
|
||||||
|
# (without overwriting already-set vars).
|
||||||
|
#
|
||||||
|
# OPENAI_BASE_URL is also set in fastagent.config.yaml; this is here for
|
||||||
|
# parity with the standard Pallas-consumer layout.
|
||||||
|
|
||||||
|
OPENAI_BASE_URL=http://nyx.helu.ca:22079/v1
|
||||||
6
validator/.gitignore
vendored
Normal file
6
validator/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
fastagent.secrets.yaml
|
||||||
|
.env
|
||||||
|
.venv/
|
||||||
|
*.egg-info/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
99
validator/README.md
Normal file
99
validator/README.md
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
# Mnemosyne Validator
|
||||||
|
|
||||||
|
A bare [FastAgent](https://github.com/evalstate/fast-agent) + [Pallas](https://git.helu.ca/r/pallas) project whose only purpose is to exercise Mnemosyne's MCP server end-to-end. Use it to confirm the transport works, every MCP tool registers, args/responses round-trip, and the local LLM can actually drive the tools.
|
||||||
|
|
||||||
|
This is **not** a production agent. It does not represent the long-term Daedalus integration — when Daedalus ships, it will inject `workspace_id` server-side. The validator never sets `workspace_id`, meaning all calls run against the global scope (libraries with `workspace_id IS NULL`).
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
validator/
|
||||||
|
├── pyproject.toml # pallas-mcp + fast-agent-mcp deps
|
||||||
|
├── agents.yaml # one-agent Pallas topology
|
||||||
|
├── fastagent.config.yaml # default_model + mnemosyne MCP server
|
||||||
|
├── fastagent.secrets.yaml.example # template for the bearer token
|
||||||
|
├── .env.example # OPENAI_BASE_URL etc.
|
||||||
|
└── agents/
|
||||||
|
└── mnemosyne_validator.py # the FastAgent definition
|
||||||
|
```
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd validator/
|
||||||
|
uv venv .venv
|
||||||
|
source .venv/bin/activate
|
||||||
|
uv pip install -e .
|
||||||
|
```
|
||||||
|
|
||||||
|
Copy and fill the secrets/env templates:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp fastagent.secrets.yaml.example fastagent.secrets.yaml
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
## Provision an MCP bearer token
|
||||||
|
|
||||||
|
Mnemosyne requires a bearer token when `MCP_REQUIRE_AUTH=True` (the default). Generate one for your user:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ../mnemosyne
|
||||||
|
python manage.py create_mcp_token --user <username> --name validator
|
||||||
|
```
|
||||||
|
|
||||||
|
The command prints the token **once** — paste it into `validator/fastagent.secrets.yaml` under `mcp.servers.mnemosyne.headers.Authorization` (keep the `Bearer ` prefix).
|
||||||
|
|
||||||
|
## Start Mnemosyne's MCP server
|
||||||
|
|
||||||
|
The validator hits the Mnemosyne ASGI endpoint, so Mnemosyne's MCP server must be running. From the Mnemosyne project:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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`.
|
||||||
|
|
||||||
|
## Run the validator
|
||||||
|
|
||||||
|
The `mnemosyne-validator` script is a thin alias for `pallas`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start with the registry (Pallas mode):
|
||||||
|
mnemosyne-validator
|
||||||
|
|
||||||
|
# Or run the agent directly (no registry):
|
||||||
|
mnemosyne-validator --agent mnemosyne_validator
|
||||||
|
```
|
||||||
|
|
||||||
|
To chat with the agent directly without spinning up a Pallas registry, use the `fast-agent` CLI (provided by `fast-agent-mcp`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
fast-agent go --config-path fastagent.config.yaml --url http://localhost:24301/mcp mnemosyne_validator
|
||||||
|
```
|
||||||
|
|
||||||
|
## What to test
|
||||||
|
|
||||||
|
These prompts exercise every Mnemosyne MCP tool. After each, the agent should call the named tool and surface the result.
|
||||||
|
|
||||||
|
| Prompt | Tool | What to verify |
|
||||||
|
|--------|------|----------------|
|
||||||
|
| "Run a health check on Mnemosyne." | `get_health` | Returns `status: ok` if Neo4j + S3 + embedding model are all reachable. `degraded` if one is down. |
|
||||||
|
| "List all libraries." | `list_libraries` | Returns the libraries seeded by `load_library_types`, each with `library_type` set. |
|
||||||
|
| "List collections in library `<uid>`." | `list_collections` | Returns collections inside the named library. |
|
||||||
|
| "List items in collection `<uid>`." | `list_items` | Returns items with `chunk_count` and `embedding_status`. |
|
||||||
|
| "Search the technical libraries for `<query>`." | `search` | Returns ranked candidates with `chunk_uid`, `score`, `text_preview`, `library_type`. |
|
||||||
|
| "Fetch the full text of chunk `<chunk_uid>`." | `get_chunk` | Returns the full chunk text from S3. |
|
||||||
|
|
||||||
|
If a call errors, the agent surfaces it verbatim — that's the failure mode you want.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
**"Invalid MCP token"** — token wasn't provisioned, was provisioned for a different user, or got mangled when pasted. Re-run `create_mcp_token` and paste again. Tokens are SHA-256 hashed at rest and can't be retrieved later.
|
||||||
|
|
||||||
|
**"Couldn't connect to Mnemosyne"** — the ASGI server isn't running, or it's bound to a different host/port than `mcp.servers.mnemosyne.url` says. Check `curl http://localhost:22091/mcp/health` returns `{"status":"ok"}`.
|
||||||
|
|
||||||
|
**"No system embedding model configured" in `get_health`** — `LLMModel.get_system_embedding_model()` returns nothing. Configure the embedding model via the Mnemosyne admin or `manage.py` before searches will work.
|
||||||
|
|
||||||
|
**Search returns zero candidates with no error** — Mnemosyne is reachable but has no embedded content yet. Upload an item and run `embed_item`, or use the Daedalus ingest endpoint, before re-testing search.
|
||||||
17
validator/agents.yaml
Normal file
17
validator/agents.yaml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Mnemosyne Validator — Pallas deployment topology
|
||||||
|
#
|
||||||
|
# A single-agent Pallas project whose only purpose is to validate the
|
||||||
|
# Mnemosyne MCP server end-to-end. Not a production deployment.
|
||||||
|
|
||||||
|
name: mnemosyne-validator
|
||||||
|
version: "0.1.0"
|
||||||
|
host: localhost
|
||||||
|
namespace: ca.helu.mnemosyne-validator
|
||||||
|
registry_port: 24300
|
||||||
|
|
||||||
|
agents:
|
||||||
|
mnemosyne_validator:
|
||||||
|
module: agents.mnemosyne_validator
|
||||||
|
port: 24301
|
||||||
|
title: Mnemosyne Validator
|
||||||
|
description: "Exercises Mnemosyne's MCP tools: search, get_chunk, list_*, get_health"
|
||||||
0
validator/agents/__init__.py
Normal file
0
validator/agents/__init__.py
Normal file
56
validator/agents/mnemosyne_validator.py
Normal file
56
validator/agents/mnemosyne_validator.py
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
"""
|
||||||
|
Mnemosyne Validator Agent
|
||||||
|
|
||||||
|
A bare FastAgent that wraps the Mnemosyne MCP server. Exists solely to
|
||||||
|
exercise the Mnemosyne MCP transport, tool registration, and round-trip
|
||||||
|
serialization — no production role.
|
||||||
|
|
||||||
|
Drive it from the CLI to confirm:
|
||||||
|
- search works against the running Mnemosyne (vector + fulltext + graph)
|
||||||
|
- get_chunk fetches full chunk text from S3
|
||||||
|
- list_libraries / list_collections / list_items return the expected shape
|
||||||
|
- get_health returns ok/degraded with the right dependency breakdown
|
||||||
|
|
||||||
|
When the Daedalus integration ships, the workspace_id parameter will be
|
||||||
|
injected by Daedalus's chat path (force-overwritten before the call leaves
|
||||||
|
Daedalus). This validator never sets it — meaning all calls go to the
|
||||||
|
GLOBAL scope (libraries with workspace_id IS NULL).
|
||||||
|
"""
|
||||||
|
from fast_agent import FastAgent
|
||||||
|
|
||||||
|
fast = FastAgent("Mnemosyne Validator", parse_cli_args=False)
|
||||||
|
|
||||||
|
|
||||||
|
@fast.agent(
|
||||||
|
name="mnemosyne_validator",
|
||||||
|
instruction="""You are a validator for the Mnemosyne knowledge base. Your
|
||||||
|
job is to exercise its MCP tools when asked, report what you saw, and surface
|
||||||
|
errors clearly.
|
||||||
|
|
||||||
|
You have direct access to Mnemosyne via these tools:
|
||||||
|
|
||||||
|
- search(query, library_uid?, library_type?, collection_uid?, limit?, rerank?, include_images?, search_types?)
|
||||||
|
Hybrid retrieval. Returns ranked chunks with text_preview (~500 chars),
|
||||||
|
chunk_uid, item_uid, item_title, library_type, score, source.
|
||||||
|
- get_chunk(chunk_uid)
|
||||||
|
Fetch the full text of a chunk by uid (typically obtained from search).
|
||||||
|
- list_libraries(limit?, offset?)
|
||||||
|
List libraries (uid, name, library_type, description).
|
||||||
|
- list_collections(library_uid?, limit?, offset?)
|
||||||
|
List collections, optionally filtered by parent library.
|
||||||
|
- list_items(collection_uid?, library_uid?, limit?, offset?)
|
||||||
|
List items (documents) with chunk_count, embedding_status, etc.
|
||||||
|
- get_health()
|
||||||
|
Health check: {status: ok|degraded|error, checks: {neo4j, s3, embedding}}.
|
||||||
|
|
||||||
|
When the user asks "what libraries exist", call list_libraries and report.
|
||||||
|
When they ask a research question, call search and surface chunk_uid + score
|
||||||
|
+ item_title for each candidate. If they want full text, call get_chunk.
|
||||||
|
Show raw structured output, not flowery prose — this is a validation tool,
|
||||||
|
not a chat assistant.
|
||||||
|
|
||||||
|
If a tool errors, paste the error message verbatim.""",
|
||||||
|
servers=["mnemosyne"],
|
||||||
|
)
|
||||||
|
async def mnemosyne_validator():
|
||||||
|
pass
|
||||||
32
validator/fastagent.config.yaml
Normal file
32
validator/fastagent.config.yaml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Mnemosyne Validator — FastAgent + MCP configuration
|
||||||
|
#
|
||||||
|
# Secrets (api_key, MCP bearer tokens) live in fastagent.secrets.yaml
|
||||||
|
# (gitignored) and merge with this file at runtime.
|
||||||
|
|
||||||
|
# Local llama.cpp on Nyx (OpenAI-compatible). Override via
|
||||||
|
# fastagent.secrets.yaml if you want to point at a different model server.
|
||||||
|
default_model: openai.Qwen3.5-35B-A3B-UD-Q4_K_XL.gguf
|
||||||
|
|
||||||
|
# Capabilities for the model — Pallas registers it with fast-agent's
|
||||||
|
# ModelDatabase using these values. vision: true so we can validate image
|
||||||
|
# round-trip later (search returns image candidates by default).
|
||||||
|
model_capabilities:
|
||||||
|
vision: true
|
||||||
|
context_window: 192000
|
||||||
|
max_output_tokens: 16384
|
||||||
|
|
||||||
|
# ── LLM Providers ───────────────────────────────────────────────────────────
|
||||||
|
openai:
|
||||||
|
base_url: "http://nyx.helu.ca:22079/v1"
|
||||||
|
|
||||||
|
# ── MCP Servers ─────────────────────────────────────────────────────────────
|
||||||
|
mcp:
|
||||||
|
servers:
|
||||||
|
# Mnemosyne MCP server — Streamable HTTP at /mcp.
|
||||||
|
# Default assumes the validator runs on the same host as Mnemosyne;
|
||||||
|
# override the URL in fastagent.secrets.yaml or via Ansible if remote.
|
||||||
|
mnemosyne:
|
||||||
|
transport: http
|
||||||
|
url: "http://localhost:22091/mcp"
|
||||||
|
# Bearer token in fastagent.secrets.yaml (provisioned via
|
||||||
|
# `python manage.py create_mcp_token <user>`).
|
||||||
22
validator/fastagent.secrets.yaml.example
Normal file
22
validator/fastagent.secrets.yaml.example
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Mnemosyne Validator — secrets template
|
||||||
|
#
|
||||||
|
# Copy to fastagent.secrets.yaml and fill in real values. The .yaml is
|
||||||
|
# gitignored; the .yaml.example is committed.
|
||||||
|
|
||||||
|
# ── LLM provider keys ───────────────────────────────────────────────────────
|
||||||
|
# Local llama.cpp doesn't authenticate, but fast-agent requires the key field
|
||||||
|
# to be present. "0000" or any non-empty string is fine.
|
||||||
|
openai:
|
||||||
|
api_key: "0000"
|
||||||
|
|
||||||
|
# ── MCP server bearer tokens ────────────────────────────────────────────────
|
||||||
|
mcp:
|
||||||
|
servers:
|
||||||
|
mnemosyne:
|
||||||
|
headers:
|
||||||
|
# Mnemosyne MCP server requires a bearer token when MCP_REQUIRE_AUTH=True.
|
||||||
|
# Provision one with:
|
||||||
|
# cd ../mnemosyne
|
||||||
|
# python manage.py create_mcp_token --user <username> --name validator
|
||||||
|
# then paste the printed token here (it is shown once and not retrievable).
|
||||||
|
Authorization: "Bearer paste-mcp-token-here"
|
||||||
23
validator/pyproject.toml
Normal file
23
validator/pyproject.toml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
[project]
|
||||||
|
name = "mnemosyne-validator"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "FastAgent + Pallas validator that talks to Mnemosyne's MCP server end-to-end"
|
||||||
|
requires-python = ">=3.13"
|
||||||
|
dependencies = [
|
||||||
|
"pallas-mcp @ git+ssh://git@git.helu.ca:22022/r/pallas.git",
|
||||||
|
"fast-agent-mcp>=0.6.10",
|
||||||
|
"pyyaml>=6.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
mnemosyne-validator = "pallas.server:main"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["hatchling"]
|
||||||
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
|
[tool.hatch.metadata]
|
||||||
|
allow-direct-references = true
|
||||||
|
|
||||||
|
[tool.hatch.build.targets.wheel]
|
||||||
|
packages = ["agents"]
|
||||||
Reference in New Issue
Block a user