Skip to content

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:

sh
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:

FilePurpose
.mcp.json, opencode.json, .cursor/mcp.json, .codex/config.tomlStart 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.jsonDeny 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:

yaml
agents:
  "*": view # every environment not listed
  local: { level: deploy, exec: true }

Each environment takes a level, and optionally exec:

SettingAllows
viewstatus, services, routes, jobs, redacted logs, and plans of any change
operateview plus run, cancel, and retry jobs, and run a schedule now
deployoperate plus apply configuration edits, builds, and deployments, reconcile schedules, and enable or disable them
exec: truerun 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 includes exec; grant it separately.
  • mm.overrides.yaml cannot set agents. To give the block its own reviewers, run mm config split agents and list .config/mm/agents.yaml in CODEOWNERS.
  • mm config validate checks the block.

A longer example:

yaml
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:

sh
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:

json
{
  "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:

sh
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 docker or podman directly. The block, the trust step, and the refusal to run mm stop 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.
  • exec output is not redacted. A command in a container can print what the service can read, including its secrets. Grant exec only 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:

json
{
  "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

ToolNeedsDoes
mm_accessnothingLists environments and what the agent may do in each
mm_readviewOne category: project, environments, targets, plans, services, routes, health, operations, jobs, schedules, gates
mm_logsviewUp to 200 redacted log records of one service
mm_execexec for the serviceRuns 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_planviewReviews a change without making it
mm_job_applyoperate or deployRuns, cancels, or retries a job (operate); reconciles or toggles schedules (deploy)
mm_config_apply, mm_build_apply, mm_deploy_applydeployApplies 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.