Let an AI agent work on your project
Connect a coding tool to mm's MCP server and set what agents may do in each environment.
An AI agent works with mm through mm mcp serve, a Model Context Protocol (MCP)
server that your coding tool starts. The project's agents: block sets what an
agent may do in each environment. For anything else, the agent gets the exact
command and asks you to run it.
For example: an agent deploys and runs commands in local, restarts jobs in
staging, and only reads status and logs in production.
Connect a coding tool
Run this in the project, review the changes it lists, then run it with --yes:
mm agent setup
mm agent setup --yes
Without --client, mm connects the tools it finds on PATH. Name one with
--client claude-code, opencode, codex, or cursor. Commit the files it
writes:
| File | Purpose |
|---|---|
.mcp.json, opencode.json, .cursor/mcp.json, .codex/config.toml | Start mm mcp serve for this project |
AGENTS.md (and CLAUDE.md importing it) | Tell agents to use the mm tools and never run mm |
.claude/settings.json | Deny Claude Code running mm and mmd directly |
mm agent setup --user --yes registers the server in each tool's own
configuration instead, for every project. ./install.sh does this for Claude
Code and OpenCode when they are installed.
The server uses standard input and output and opens no network port. It serves the project in the directory the tool starts it in.
Set what agents may do
Add an agents: block to .config/mm.yaml. mm init writes this one:
agents:
"*": view # every environment not listed
local: { level: deploy, exec: true }
Each environment takes a level, and optionally exec:
| Setting | Allows |
|---|---|
view | status, services, routes, jobs, redacted logs, and plans of any change |
operate | view plus run, cancel, and retry jobs, and run a schedule now |
deploy | operate plus apply configuration edits, builds, and deployments, reconcile schedules, and enable or disable them |
exec: true | run a command in any service container |
exec: [api, …] | run a command in the listed services only |
Rules:
- Without a block, and for an environment the block does not cover, agents get
view. - Agents are never
admin. No level includesexec; grant it separately. mm.overrides.yamlcannot setagents. To give the block its own reviewers, runmm config split agentsand list.config/mm/agents.yamlinCODEOWNERS.mm config validatechecks the block.
A longer example:
agents:
"*": view
local: { level: deploy, exec: true }
staging: { level: operate, exec: [api] }
production: view
Trust the block
An agent can edit files in your checkout, including this block. So mm applies the block only after you trust it on your machine:
mm agent trust # shows the block and its hash
mm agent trust --plan HASH --yes
Until then, and again after any edit to the block, agents have view
everywhere. mm init trusts the block it writes. mm agent status shows what
the block grants, what is in force, and whether it is trusted.
When the agent may not do something
A tool call outside the grant changes nothing. It returns MM_AGENT_HANDOFF
with the commands for you, and the agent shows them to you:
{
"error": {
"code": "MM_AGENT_HANDOFF",
"message": "Agents have 'view' in environment 'production'; this needs exec. The user can run it instead.",
"handoff": {
"reason": "not_granted",
"environment": "production",
"granted": "view",
"requires": "exec",
"user_commands": [
"mm --environment production service exec api -- rails db:migrate"
]
}
}
}
Run the command in your own terminal, outside the agent session. Plans work
the same way: an agent with view can plan a production deployment, and the
plan says "applied_by": "user" with the mm deploy commands for you.
mm refuses to run for an agent
Coding tools set environment variables for the commands their agents run. mm
checks for CLAUDECODE, OPENCODE, CODEX_CI, CODEX_THREAD_ID,
CODEX_SANDBOX, CODEX_SANDBOX_NETWORK_DISABLED, CURSOR_AGENT,
GEMINI_CLI, AI_AGENT, and AGENT. When one is set, every mm command
except mm mcp serve exits with code 3 and points the agent at the MCP tools.
Set MM_AGENT=1 to get the same behavior in a tool mm does not know.
This also applies to commands you type into the agent's own shell. Use a separate terminal.
Remote environments
A remote host limits agents itself, so a change in someone's checkout cannot widen what an agent does there. The MCP server reaches a remote host only with a separate agent key:
mm key create --agent
mm team join --agent # reviewed edit of your users entry
mm team join --agent --plan HASH --yes
Commit the change and have it merged. After the environment is deployed or
synced, the host gives the agent key your access limited by the agents: block
of the commit it deployed. mm team sync plans show the agent as its own
line, such as alice (agent): none -> view, +1 key(s). See
remote access.
Without an agent key, tools for a remote environment return a handoff with these commands.
What this does and does not protect
- Remote environments are enforced by the host. An agent key gets only what
the deployed commit's
agents:block allows, whatever the checkout or the agent says. - Your own key must stay out of the agent's reach. An agent that can read
your key file can act as you. Use a security key (
mm key create --security-key), which needs a touch for every connection, or run the agent where it cannot read your mm home. - Local environments are a guardrail. An agent with a shell on your machine
can call
dockerorpodmandirectly. The block, the trust step, and the refusal to runmmstop an agent that follows its instructions; they do not contain one that works around them. Restrict the agent's shell in your coding tool if you need that. execoutput is not redacted. A command in a container can print what the service can read, including its secrets. Grantexeconly where the agent may see that.
Private limits on your machine
To cap agents on one machine below what a project grants, create mcp.json in
your mm home (normally ~/.mm), owned by you with mode 0600:
{
"schema_version": 2,
"ceiling": { "*": "operate", "production": "view" }
}
The file is optional and can only narrow the project's block. It also accepts
principal (the name in audit records, default agent),
maximum_page_size, maximum_response_bytes, and request_timeout_ms.
A version 1 file, which listed environments and capabilities, is rejected:
delete it or replace it with the form above.
Tools
| Tool | Needs | Does |
|---|---|---|
mm_access | nothing | Lists environments and what the agent may do in each |
mm_read | view | One category: project, environments, targets, plans, services, routes, health, operations, jobs, schedules, gates |
mm_logs | view | Up to 200 redacted log records of one service |
mm_exec | exec for the service | Runs an exact argv in a service container; no shell, TTY, or stdin; up to 300 seconds and 256 KiB of output |
mm_config_plan, mm_build_plan, mm_deploy_plan, mm_job_plan | view | Reviews a change without making it |
mm_job_apply | operate or deploy | Runs, cancels, or retries a job (operate); reconciles or toggles schedules (deploy) |
mm_config_apply, mm_build_apply, mm_deploy_apply | deploy | Applies the plan just reviewed |
Only the environment is required in a tool's scope; the target and runtime
default to what the project and your machine select.
An apply tool takes the plan hash, the plan's single-use token, and the
confirmation apply:<plan_hash>. Tokens expire after ten minutes. If apply
reports stale state, request a new plan.
Not available through MCP: an interactive shell, port forwards, secret values, edits to the users file, and target, key, and host administration. Every call that names an environment, including each refusal, is recorded in the local audit journal with its operation and outcome, and without its arguments.
Generated snapshots are available for the
tool contract,
resource contract,
project contract, and
private limits. They come from the MCP
router and the Rust types through ./scripts/generate-artifacts.sh and must
not be edited directly.