Skip to main content

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/runs validates the recipe, records a run and hands it to an executor. The only executor this hub can drive is worker (M7-RL): a lucen worker run process that a human starts on their own GPU machine, with a train key 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 stays queued, 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 to dry_run: true (it returns the request body without sending it); estimate_training prices the same recipe first, and an idempotency_key makes a retried submit return the same run.
  • rollout_in_sim (M8) queues a battery — command cells × seeds × a horizon ≤ 30 s — and, like submit_training, defaults to dry_run. Nothing runs on the hub: a rollout worker (lucen rollout worker, the CLI installed as lucen-cli[rollout], on a human's own CPU machine) claims it, pulls the policy and the robot's MJCF + meshes, refuses an io_contract mismatch, 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]) into scorecard.json. Name a run_id and 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_status reads 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.

  • There is no key management. /v1/keys is session-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 leaked write key 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_deploy files an approval request ("model X onto device Y for T minutes") that a person must approve in the web app; approvals are session-only, so no API key — including this server's — can do it. Once approved, the robot-side lucen-device driver 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_status shows the request, the approval and the driver's started / heartbeat / stopped / refused trail. Goal commands and teleop through an agent are not built (v1+), and the server's instructions say 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 train scope (it grants: read, write)" — by asking /v1/auth/me on 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/me returns the list as auth.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_KEY server-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)

Source distribution for lucen-mcp 0.1.0
File Size Uploaded
lucen_mcp-0.1.0.tar.gz 61.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for lucen-mcp 0.1.0
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page