lucen-mcp — MouseMouse, as an MCP server
The hub's tools over the MouseMouse REST API, for an agent. Every hub tool is one
operation in the hub's OpenAPI contract (served at /openapi.yaml, browsable at
mousemouse.ai/docs/api), called through the
generated Python client lucen-sdk; nothing here reimplements a query, a
validation rule or an access check.
The reason to attach this server is the Training Coach: a corpus of 171 experience cards and 22 numbered doctrine rules distilled from real robot-learning runs, searchable with no API key and no cost. Everything else here is a hub you could also browse.
Install
From PyPI, as a tool on your PATH (uv, or pipx install lucen-mcp):
uv tool install lucen-mcp
That puts a lucen-mcp executable on your PATH. It speaks MCP over stdio and
takes no arguments. (A MouseMouse hub also serves its own copy of these wheels:
curl -fsSL <hub API>/install.sh | sh -s -- mcp installs the version built
from the hub's own commit; see the hub's docs page Your own AI.)
The two local robot tools (init_robot, publish_robot, M17a) compile and
simulate a model on this machine, so they need the CLI's rollout extra
(MuJoCo 3.12.0) in the same environment; without it they refuse and name this
line. Every other tool works with the plain install above.
uv tool install --with "lucen-cli[rollout]" lucen-mcp
Configure
Claude Code
Project-scoped, in .mcp.json at the root of the project you want it in:
{
"mcpServers": {
"lucen": {
"command": "lucen-mcp",
"env": {
"LUCEN_API_URL": "http://127.0.0.1:8000",
"LUCEN_API_KEY": "lucen_sk_..."
}
}
}
}
Or from the terminal, which writes the same entry:
claude mcp add lucen --env LUCEN_API_URL=http://127.0.0.1:8000 --env LUCEN_API_KEY=lucen_sk_... -- lucen-mcp
Claude Desktop
~/Library/Application Support/Claude/claude_desktop_config.json on macOS,
%APPDATA%\Claude\claude_desktop_config.json on Windows:
{
"mcpServers": {
"lucen": {
"command": "lucen-mcp",
"env": {
"LUCEN_API_URL": "http://127.0.0.1:8000",
"LUCEN_API_KEY": "lucen_sk_..."
}
}
}
}
If lucen-mcp is not on the launcher's PATH (Claude Desktop does not read
your shell profile), use the absolute path uv tool install printed — typically
~/.local/bin/lucen-mcp.
Leave LUCEN_API_KEY out entirely and the server still runs: every tool
whose scope is none (repo search, repo and robot-card reads, dataset
episodes, the whole coach corpus) works anonymously against public data.
Environment
| Variable | Default | Meaning |
|---|---|---|
LUCEN_API_URL |
(required) | The hub's API URL. There is no built-in default: without it the server refuses to start and says so on stderr. http://127.0.0.1:8000 for a local stack (make dev-local). |
LUCEN_API_KEY |
(unset) | A scoped key, lucen_sk_.... Optional. |
LUCEN_MCP_TIMEOUT_S |
180 |
Per-request timeout. A coach call runs a model server-side; do not set this below ~120. |
The same two names the CLI and the SDKs read, so one exported key serves every surface.
The key is only ever an environment variable. No tool takes a credential as
an argument (it would land in the model's transcript) and there is no
command-line flag for one (it would land in the process table and your shell
history). On a local stack, mint one with
tools/dev_api_key.py in the hub's source repository; in a deployment, mint one
in the web app under Settings → API keys.
Tools
Scopes nest: read ⊂ write ⊂ train. none means the tool works with no
key at all, against public data.
| Tool | Scope | What it does |
|---|---|---|
search_repos |
none | Search datasets, models and robot repos (substring + tags + kind). |
get_repo |
none | One repo with its dataset meta / model meta / robot card. |
get_robot_card |
none | A robot's DoF, mass, actuators, joint limits and mjcf_path. |
get_dataset_episodes |
none | The episodes in a LeRobot-format dataset repo. |
search_experience |
none | IDF-ranked search over the 171 experience cards. Free. |
get_coach_doctrine |
none | The 22 numbered doctrine rules a report cites by id. Free. |
get_training_advice |
train |
Diagnose a run: diagnosis, cited proposals, experiment plan. Costs money. |
get_training_plan |
train |
Plan a programme from a task brief, before any run exists. Costs money. |
get_run_status |
read |
One training run: status, executor, who claimed it, lineage, artifacts. |
get_run_logs |
read |
The last N lines of a run's log plus every metrics line, replayed once; ANSI escapes stripped unless strip_ansi=false. |
get_run_config |
read |
A run's resolved config grouped by RL structure (the hub's one rule table), the diff against its parent — cross_shape when the parent is an imported manifest: only the mapped key pairs compared, the rest "no record", never "unchanged" — and a round's declared keys. Readable while the run trains. |
get_run_gates |
read |
The sim gates a run's checkpoints got while it trained: step × cells × verdict, posters, the tripwire. |
cancel_training |
train |
Cancel a run: queued ends now; running is stopped by its runner within seconds. |
estimate_training |
train |
Price a recipe before submitting it. Creates nothing. |
recommend_training |
read |
Which GPU tier a recipe needs — the hub's deterministic planner, with the arithmetic in why[]. Creates nothing. |
submit_training |
train |
Queue a run for a self-hosted lucen worker — dry run by default. |
rollout_in_sim |
train |
Queue a sim rollout — the policy in MuJoCo on the robot's MJCF, scored PASS/FAIL per command cell by the hub — for a rollout worker (lucen rollout worker). Dry run by default. |
get_rollout_status |
read |
One rollout: status, who claimed it, per-cell verdicts, presigned video / trajectory / scorecard URLs. |
| request_deploy | write | Ask for a policy on a robot — a human must approve in the web app; starts nothing |
| get_device_status | read | A device's pending requests, approvals and the driver's signed trail |
| serve_model | train | Create a hub inference endpoint for a skill-layer model (a reflex model is the hub's 409); waits for a lucen serve you start. Dry run by default. No tool opens a session. |
| get_endpoint_status | read | One endpoint: status, tier, server, and per-session metering (chunks, GPU-seconds, service and round-trip p50/p95, cost). |
| plan_task | train | The hub's planner (M16) decomposes a task on a device into a closed step vocabulary and files the approval requests it needs. A human approves each handoff; the plan never approves, never opens a session, never names a joint. Costs money per decision. |
| get_plan_status | read | One plan: status, steps, the timeline with each decision's cost, and what it is waiting on. |
| create_repo | write | Create an empty dataset, model or robot repo (M17a). |
| validate_robot | read | The hub checks a robot card against the repo's MJCF without writing — checks[] with a stable code, the offending names and a fix hint (M17a). |
| set_robot_card | write | Store a robot card (full replace); a refusal is one line per failed check and nothing is written (M17a). |
| init_robot | local | lucen robot init on a local MJCF/URDF: compiles with MuJoCo, drafts robot_card.json with a needs_you list. Needs lucen-cli[rollout] in this environment (M17a). |
| publish_robot | local, then write | lucen robot validate + publish on a local directory; dry run by default (M17a). |
What is real, and what is not
An agent that calls a tool which silently does nothing is worse than one that is told "not available yet", so the tools say exactly where things stand:
submit_training.POST /v1/runsvalidates the recipe, records a run and hands it to an executor. The only executor this hub can drive isworker(M7-RL): alucen worker runprocess that a human starts on their own GPU machine, with atrainkey for the owner and the trainer installed, claims the run, streams its logs, publishes the output model repo and reports the result. The hub does not track connected runners, so the tool cannot say whether one is listening — a run nobody claims staysqueued, with no weights and no bill, and the result says so.modal, the hub's own GPU backend, runs only where the deployment names a deployed Modal app; elsewhere the hub answers 503 naming that setting and records nothing. The tool defaults todry_run: true(it returns the request body without sending it);estimate_trainingprices the same recipe first, and anidempotency_keymakes a retried submit return the same run.rollout_in_sim(M8) queues a battery — command cells × seeds × a horizon ≤ 30 s — and, likesubmit_training, defaults todry_run. Nothing runs on the hub: a rollout worker (lucen rollout worker, the CLI installed aslucen-cli[rollout], on a human's own CPU machine) claims it, pulls the policy and the robot's MJCF + meshes, refuses anio_contractmismatch, runs the cells, renders an mp4 per cell where it can, and reports measurements; the hub scores them (PASS = every seed upright and every commanded axis inside[0.85, 1.15]) intoscorecard.json. Name arun_idand the scorecard is attached to that run, which is how the Coach's "PASS gates become constraints" happens without a human copying anything. The hub does not track workers — a queued rollout is queued, not running — and the hosted (Modal) path is not built.get_rollout_statusreads it back, with the per-cell verdicts and presigned playback URLs once it is scored.
get_run_logs reads the real stream (GET /v1/runs/{id}/logs, replayed with
follow=false because a tool call has to end): logs is the last tail
lines as {ts, stream, line} — stderr marks a notice the hub wrote into
the log itself, such as a cancel request or a lease re-queue — metrics is
every metrics.jsonl line, and status_at_end is the run's status when the
stream closed. A queued run answers an empty list with a reason ("nothing
yet"); an imported run answers logs_available: false because it has no
log by contract. cancel_training returns immediate: true for a queued
run (it is canceled now) and false for a running one (the runner kills
the template on its next report, a few seconds later — poll
get_run_status).
The working v0 loop is: train on your own hardware → lucen runs import
→ get_training_advice.
One deliberate absence, and one consent-gated handoff
- There is no key management.
/v1/keysissession-scoped, so no API key of any scope may mint, list or revoke a key; a server whose only credential is an API key could never succeed at those operations, and the rule they encode (a leakedwritekey cannot promote itself) is worth keeping visibly intact. - There is no tool that starts, stops or steers a robot — but there is a way
to ask (M13).
request_deployfiles an approval request ("model X onto device Y for T minutes") that a person must approve in the web app; approvals aresession-only, so no API key — including this server's — can do it. Once approved, the robot-sidelucen-devicedriver verifies the signed approval offline, pulls the exact bytes, refuses a contract mismatch, runs for the window and stops on its own clock;get_device_statusshows the request, the approval and the driver'sstarted/heartbeat/stopped/refusedtrail. Goal commands and teleop through an agent are not built (v1+), and the server'sinstructionssay all of this to the model at connect.
Errors
A tool failure comes back as an MCP error result with a sentence the model can act on, never a bare status code:
- 403 names the missing scope and what the key does grant —
"this API key is missing the
trainscope (it grants: read, write)" — by asking/v1/auth/meon the error path only. - 403 from a key limited to repos (
forbidden.key_limits) or from an AI agent's key asking for paid hosted work beyond the free allowance (forbidden.agent_key_spend) is relayed in the hub's own words. A limited key works only on the repos in its list, runs for a robot in it under an owner it names whole (owner/*), and sim tests of a policy and a robot both in it;/v1/auth/mereturns the list asauth.limited_to. - 401 for an expired key says when it expired.
- 404 is always "not found, or not visible to this credential", in the same words for a private resource and an absent one. That is the hub's rule, not a vagueness: a 403 would confirm the resource exists.
- 429 distinguishes the per-minute rate limit from the coach's daily quota.
- 502 from the coach carries the guarantees its own report broke.
- 503 on a coach call usually means the hub has no model backend configured
(
ANTHROPIC_API_KEYserver-side) — a deployment gap, not something a client can fix.
Develop
make install # uv sync --all-packages
uv run --no-sync pytest packages/mcp/tests
tests/test_protocol.py launches the shipped lucen-mcp console script as a
subprocess and speaks MCP over its stdin/stdout against a live local API
(uvicorn + SQLite + in-process moto S3, with the coach on M14b's recorded
fixtures). Nothing in the test suite needs a secret.
Apache-2.0. © 2026 Lucen Robotics.
Metadata
Release files for lucen-mcp 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| lucen_mcp-0.1.0.tar.gz | 61.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| lucen_mcp-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 131.0 kB
Release files / lucen_mcp-0.1.0.tar.gz
| Download URL | lucen_mcp-0.1.0.tar.gz |
|---|---|
| Size | 61.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f1e17f63095d1a8ba2c38a21f1a217a1538292d2f4d5a62e1086b458a1568ab3
|
|
BLAKE2b-256 checksum How to use checksums |
d1c84a98cb636b4635b9f42295e9abee553365ed84069e73ae7196426d5e58bf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.
Transparency logRelease files / lucen_mcp-0.1.0-py3-none-any.whl
| Download URL | lucen_mcp-0.1.0-py3-none-any.whl |
|---|---|
| Size | 69.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
738871a1fb80fb13efb408ed153e2c8fe1a35b47e30f58d9f4bb0411c7e5ae16
|
|
BLAKE2b-256 checksum How to use checksums |
5213e169033361327fce3ad24efcce4ce78a54b85c3a3e6f1b768e9eb8b01333
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.
Transparency log