ezstack ships a standalone Model Context Protocol server, ezs-mcp, that exposes the full stack workflow as MCP tools. Point Claude Code (or any MCP-compatible agent) at it and it can drive ezs directly — inspect, commit, sync, push, manage pull requests, navigate, and configure, all without leaving the agent loop.
Two commands: install the binary, then register it with Claude Code. Both are user-scope — one-time, every repo.
Already using ezs agent? Both commands run automatically on first launch — see Automatic MCP integration.
ezs under the hood.ezs-mcp is a separate binary that speaks JSON-RPC over stdio. Under the hood it invokes the same ezs command functions your shell uses — there is no second copy of the logic to drift. Your agent gets the identical behavior you'd get from the terminal, captured and handed back as structured tool results.
Read-only tools return JSON by default so the agent can reason about the stack shape, or pass decorated=true to get the terminal-styled output you'd see from ezs status. Pin the server to a specific repo with --repo so it behaves predictably when Claude launches it from a parent workspace directory.
# Agent asks: "what does the stack look like?"
→ ezstack_list { "all": true }
returns JSON: every stack, branch, parent, PR state, CI status
# Or request the pretty version:
→ ezstack_status { "decorated": true }
returns the same colored tree you see in a terminal
Tools that rewrite history or mutate remote state (ezstack_sync, ezstack_push, ezstack_delete, ezstack_pr_merge) carry the MCP destructive annotation, so clients like Claude Code can prompt the user before executing them. Read-only tools carry the read-only annotation and run without interruption.
# Claude Code sees the annotation and asks:
⚠ Tool ezstack_sync is marked destructive.
Run with { "all": true } ?
[Y]es / [N]o / [A]lways allow this tool
Branch-management tools like ezstack_goto, ezstack_new, ezstack_delete, and ezstack_reparent mark their positional arguments as Required in the tool schema, so the agent cannot call them with missing args — preventing fallthrough to fzf-backed interactive selection that would hang in a no-terminal context. Internal yes/no prompts are auto-accepted for destructive tools (the client already confirmed via the annotation).
# Schema rejects the call before it runs:
→ ezstack_delete {}
✗ error: missing required argument "branch"
# Properly formed call runs without waiting on a prompt:
→ ezstack_delete { "branch": "feature-part2" }
✓ deleted feature-part2 and its worktree
Every ezstack operation an agent needs — inspect, commit, sync, push, PR, configure — exposed with strict input schemas. Read-only tools default to JSON; destructive tools carry the MCP destructive annotation; tools that would normally launch an editor or an fzf picker have those paths closed off at the schema level so they can't hang the MCP transport.
| Tool | Annotation | Description |
|---|---|---|
ezstack_status |
read-only | Current stack with PR and CI status. all, decorated. |
ezstack_list |
read-only | List all stacks and branches. all, decorated. |
ezstack_diff |
read-only | Diff against parent branch as JSON numstat (default) or diffstat. branch, stat. |
ezstack_log |
read-only | Commits since parent as JSON (hash, message, author, ISO date). branch. |
ezstack_config_show |
read-only | Full ezstack configuration for the active repo. |
| Tool | Annotation | Description |
|---|---|---|
ezstack_goto |
— | Switch to a branch. branch (required). |
ezstack_new |
— | Create a new branch. name (required), parent. |
ezstack_delete |
destructive | Delete a branch and its worktree. branch (required). |
ezstack_reparent |
— | Move a branch to a new parent. branch and new_parent (both required). |
ezstack_stack |
— | Add a standalone branch to a stack. branch (required), parent or base. |
ezstack_unstack |
— | Remove a branch from ezstack tracking (leaves the git branch and worktree intact). branch (required). |
| Tool | Annotation | Description |
|---|---|---|
ezstack_commit |
destructive | Commit staged (or all) changes and auto-sync children. message (required), all, merge, rebase. Auto-pushes if the branch is already on the remote. |
ezstack_amend |
destructive | Amend the last commit and auto-sync children. Optional message (otherwise --no-edit), all, merge, rebase. Force-pushes if the branch is already on the remote. |
ezstack_sync |
destructive | Rebase (or merge) stack branches with their base. stack, all, current, parent, children, merge, dry_run, resume. |
ezstack_push |
destructive | Push current branch or entire stack. stack, force. |
| Tool | Annotation | Description |
|---|---|---|
ezstack_pr_create |
— | Create a pull request for the current branch. title, draft. |
ezstack_pr_update |
destructive | Push the latest commits and refresh the PR base branch and stack description. branch. |
ezstack_pr_merge |
destructive | Merge the pull request for the current branch. |
ezstack_pr_draft |
— | Toggle a PR between draft and ready-for-review. branch. |
ezstack_pr_stack |
— | Update every PR description in the stack with navigation links. |
| Tool | Annotation | Description |
|---|---|---|
ezstack_config_set |
— | Set a config value. key and value (both required). Valid keys: worktree_base_dir, default_base_branch, github_token, cd_after_new, use_worktrees, sync_strategy, agent_command. |
Install the ezs-mcp binary, then register it once with your MCP client. The registration command is repo-independent — ezs-mcp operates on whichever directory Claude Code launches it in, so one registration works for every repo you use.
Pick whichever matches your setup. All three install ezs and ezs-mcp side by side so the CLI and the MCP server stay in lock-step.
$ brew install KulkarniKaustubh/ezstack/ezstack
→ installs ezs and ezs-mcp into $(brew --prefix)/bin
$ go install github.com/KulkarniKaustubh/ezstack/v4/cmd/ezs@latest
$ go install github.com/KulkarniKaustubh/ezstack/v4/cmd/ezs-mcp@latest
→ installs both binaries into $(go env GOBIN) (usually ~/go/bin)
$ git clone https://github.com/KulkarniKaustubh/ezstack.git
$ cd ezstack
$ make install-all
→ builds ezs and ezs-mcp and drops them into ~/.local/bin
One registration, every repo. Claude Code launches MCP servers with the current project’s directory as their working directory, and ezs-mcp simply operates on that directory — so you do not want to bake a specific repo path into this command. Use --scope user to make it available in every project without re-registering.
$ claude mcp add ezstack --scope user -- ezs-mcp
✓ added MCP server "ezstack" (user scope)
Escape hatch — monorepo subdirectories. If you open Claude Code at a monorepo root but your ezstack-configured repo is a subdirectory, Claude will launch ezs-mcp with the monorepo root as cwd, and ezs-mcp won’t know which sub-repo to act on. In that specific case, pass --repo with an absolute path to pin this registration to one sub-repo:
$ claude mcp add ezstack-subrepo -- ezs-mcp --repo /abs/path/to/subrepo
→ register one server per sub-repo; give each a distinct name
ezs-mcp is minimal on purpose — one flag and one optional environment variable.
| Name | Type | Description |
|---|---|---|
--repo <path> |
flag | Absolute path to the git repository root. The server cds here at startup so every tool call operates against the same repo regardless of where the client launched the process from. |
EZSTACK_HOME |
env var | Override the ezstack config directory (defaults to ~/.ezstack). Useful for isolated test environments. |
MCP servers receive concurrent tool calls, and ezs operates on global process state (stdout/stderr, the ezstack ui.Backend, working directory). ezs-mcp is explicitly engineered to keep those invariants intact.
Every tool handler acquires a process-wide mutex before running, so two concurrent calls from the agent are serialized rather than racing on shared globals (stdout/stderr swaps, ui.Backend replacement, ui.YesMode). The race detector is clean under go test -race.
Stdout and stderr from each ezs invocation are captured via concurrent pipe drainers started before the command runs — so large outputs (ezs list --all against a huge repo, verbose ezs sync traces) can't block on the ~64 KB OS pipe buffer.
The release pipeline verifies go install github.com/.../ezs-mcp@<tag> resolves and the resulting binary is executable on every tagged release, and a stdio integration test boots the real binary, lists tools, and round-trips calls against a temp repo on every PR. If the install contract ever drifts, CI fails before users see it.