Exposes Marea's entire command tree as a Model Context Protocol server, so AI assistants (Claude Code, etc.) can drive builds, deploys, Helm operations and AWS calls through a structured, auditable interface instead of shelling out to marea ….

Always loaded (part of @base_plugins). No marea.yaml opt-in required.

Why an MCP server

When an AI assistant runs Marea directly via the shell, every command goes through whatever sandbox the assistant runs under — broad permissions, no per-command granularity, no machine-readable schema for the available operations. With Marea exposed as an MCP server:

  • Every marea subcommand becomes an MCP tool with a JSON-Schema-typed inputSchema derived from its Optimus options and flags.
  • The host (marea mcp serve) runs outside the assistant's sandbox, with whatever filesystem and network access Marea normally needs.
  • The assistant interacts only through JSON-RPC tool calls, which the client can allowlist per tool. Destructive operations (helm delete, helm rollback, AWS deletes) carry destructiveHint: true so the client can require explicit confirmation; read-only ones carry readOnlyHint: true and can be auto-approved.

Commands

marea mcp
 serve
 tools
 call  --tool NAME --json JSON

marea mcp serve

Runs a stdio JSON-RPC 2.0 server. Newline-delimited JSON on stdin/stdout, diagnostics on stderr.

Register it with Claude Code:

claude mcp add marea -- marea mcp serve

Then allowlist tools in ~/.claude/settings.json:

{
  "permissions": {
    "allow": [
      "mcp__marea__helm_list",
      "mcp__marea__helm_history",
      "mcp__marea__helm_values",
      "mcp__marea__aws_ecr_list",
      "mcp__marea__aws_route53_list"
    ]
  }
}

Destructive tools (mcp__marea__helm_delete, mcp__marea__helm_rollback, mcp__marea__aws_*_delete) will prompt for confirmation per call, backed by the destructiveHint annotation in their descriptors.

marea mcp tools

Prints the full JSON descriptor of every tool the server would expose — name, description, inputSchema, annotations, and the internal subcommand path. Useful for inspecting how a given marea.yaml + plugin set maps to MCP tools, without starting a real client.

$ marea mcp tools | jq '.[] | .name'
"setup_init_release"
"build_local"
"build_release"
"build_docker"
"build_helm"
"run_local"
"run_release"
"run_docker"
"helm_chart"
"helm_list"
"helm_history"
"helm_values"
"helm_template"
"helm_upgrade"
"helm_delete"
"helm_rollback"

marea mcp call --tool NAME --json JSON

Invokes one tool with the given JSON arguments object and prints the captured output. Same execution path as a real tools/call request — useful for end-to-end debugging without writing JSON-RPC by hand:

$ marea mcp call --tool helm_list --json '{"deploy": "staging"}'

How tools are derived

The Marea.Plugins.Base.marea_config_args/1 chain produces a %{options:, flags:, subcommands:} map. Marea.Mcp.Tools.list/0 walks that map and emits one tool per leaf subcommand:

Optimus specJSON Schema
leaf path [:helm, :upgrade]tool name "helm_upgrade"
about: "..."description
options: [release: [long: "--release", parser: :integer, required: true, help: "...", default: ...]]property release with type (integer/number/string), description, default, and entry in required
flags: [debug: [long: "--debug", help: "..."]]property debug: {type: "boolean", default: false}
global: true options/flagspropagated into every descendant leaf

Branch nodes (helm, aws, build) are not callable directly — only leaves are tools. The mcp, schema and help namespaces are excluded so they don't show up as user-facing tools.

Execution model

tools/call is realised by Marea.Mcp.Runner.run/2:

  1. Stops any currently running Marea.Service.
  2. Spawns a Task whose group leader is swapped for a StringIO, so every byte that would normally hit stdout/stderr is captured.
  3. Sets Marea.Lib.set_stop_mode(:raise) so Marea.Lib.stop/1 raises Marea.Stop instead of calling System.halt/1 — long running servers can't tolerate a VM halt.
  4. Synthesizes argv from the MCP arguments map (each option becomes [--long, value], each true flag becomes [--long]) and calls Marea.main_inline/1.
  5. If the command produced a deferred {:exec, cmd} (Run / Build), the shell command is executed inline with System.shell/2, stderr_to_stdout: true, output appended to the capture.
  6. Returns {captured_output, :ok | {:error, reason}} to the server, which packages it as a single text content block and sets isError appropriately.

The service is stopped again after each call, so each invocation sees a fresh marea.yaml and a clean Config.

stdio discipline

The server keeps its original group leader (so IO.read(:stdio, :line) reads real stdin) and emits responses with IO.puts(:standard_io, …). Diagnostics use :standard_error. The per-call StringIO swap in the runner contains all command output, so nothing the underlying plugins print can corrupt the JSON-RPC stream.

Caching

The tool list is computed once at server startup (read marea.yaml, load plugins, walk the args tree). Changing marea.yaml or plugin code requires restarting the server. Per-call execution always re- reads marea.yaml and re-parses CLI args, so config edits between two calls of the same tool are picked up.

Interactive commands

A handful of commands replace the parent shell with an interactive TTY-attached process via Marea's deferred-shell mechanism: marea run local / release / docker, marea k8s console / shell / remote. These are exposed as MCP tools — their argv synthesis and schema work fine — but calling them through marea mcp serve captures their output rather than producing a real interactive session. For an actual IEx prompt or pod shell, use the CLI.

Non-interactive commands (builds, Helm chart/list/history/values/ template/upgrade/delete/rollback, AWS operations, K8s describe/logs/ restart) work cleanly via MCP: their output is captured and returned as a single text content block.

Source