MCP server and API
From an agent, or the API
MergeLoop serves an MCP server at /mcp, so a coding agent can do anything the dashboard does: queue a PR, take it out, read where it is, review it, read the queue’s history, change a repo’s settings. Every call acts as a GitHub user or bot: MergeLoop puts the labels on, comments, and reviews with their token, so GitHub checks they may.
An MCP client that supports OAuth signs in with GitHub in the browser, as the dashboard does:
claude mcp add --transport http mergeloop https://mergeloop.ai/mcp
The client gets a MergeLoop session, and the GitHub token stays on the server. MergeLoop sends the sign-in back only to a loopback address, where native clients such as Claude Code, Codex and Cursor listen.
An agent without a browser, such as one in CI, passes a GitHub token instead: one from gh auth token, a personal access token, or a workflow’s GITHUB_TOKEN. A fine-grained token also needs write access to issues or pull requests. MergeLoop never stores the token. It remembers its hash, in memory for 5 minutes, so calls in a row do not each ask GitHub again.
claude mcp add --transport http mergeloop https://mergeloop.ai/mcp \
--header "Authorization: Bearer $(gh auth token)"
A GitHub token reaches only the repos GitHub lets it read: MergeLoop cannot tell which of its workspaces such a token belongs to. The tools marked Sign-in work across a workspace, so they need the sign-in. Each tool takes the repo as repo, owner/name, or the workspace as workspace, its installation ID, which get_session lists.
| Tool | Does | Sign-in |
|---|---|---|
get_changelog | MergeLoop’s newest changelog entries | |
get_session | Who you are signed in as, and your workspaces | |
get_inbox | Your PRs, and the PRs that ask for your review, across workspaces | Yes |
list_queue | The queues, in the order they take PRs: a repo’s, or the workspace’s | Without repo |
list_events | A repo’s queue events, newest first | |
get_fix | The diff of a commit the fix agent pushed | |
list_history | The PRs that left the queue: merged, ejected, or removed | Yes |
get_history_stats | The queue’s numbers over a window | Yes |
list_repos | The workspace’s repos, and whether each queues | Yes |
set_repo | Turns a repo’s queue on or off, or back to following its config file | Or a token with admin on the repo |
list_open_pulls | A repo’s open PRs that are not queued | |
get_pull | A PR’s description, labels, checks, and timeline | |
get_pull_diff | A PR’s changes, or one commit’s | |
pull_status | Where a PR is, as /api/v1 below says, and its latest events | |
queue_pull | Queues a PR, or its stack. priority puts it at the front. Says when it goes on hold whatever the PR, or the agent has no model key | |
dequeue_pull | Takes a PR, and its stack, out of the queue | |
close_pull | Closes a PR | |
ready_pull | Publishes a draft PR: marks it ready for review | |
review_pull | Approves a PR, asks for changes, or comments, then queues it if asked | |
get_agent_review | The agent’s review of a queued PR: what it found, what it sent back to the author, and what it asks a human | |
answer_agent_review | Answers what the agent’s review asks, as your review on GitHub | |
ask_agent_about_review | Asks the agent about one of its findings, or replies to one it sent back | |
ask_agent_review_reviewers | Asks others to answer the agent’s review | |
mark_pull_seen | Marks a PR read in your inbox | Yes |
snooze_pull | Puts a PR out of your inbox for a while | Yes |
unsnooze_pull | Puts a snoozed PR back in your inbox | Yes |
nudge_reviewers | Mentions reviewers in a comment, asking them to review | |
get_repo_config | A repo’s queue settings | |
propose_repo_config | Opens a PR that changes a repo’s queue settings | Yes |
get_repo_skill | Whether a repo has the current mergepatrol agent skill | |
update_repo_skill | Opens a PR that updates a repo’s mergepatrol agent skill | Yes |
get_settings | Which model API keys and ChatGPT login the workspace has | Yes |
set_api_key | Sets a model provider’s API key | Yes |
delete_api_key | Removes a model provider’s API key | Yes |
start_chatgpt_login | Starts a ChatGPT sign-in: a code for the user to enter | Yes |
delete_chatgpt_login | Removes the ChatGPT login | Yes |
poll_chatgpt_login | Finishes the ChatGPT sign-in once the user entered the code | Yes |
get_review_settings | The agent’s review settings, and its track record | Yes |
set_review_settings | Turns the agent’s review on or off, and sets how sure it must be | Yes |
get_billing | The workspace’s plan and seats | Yes |
start_checkout | A Stripe page to subscribe | Yes |
open_billing_portal | A Stripe page to manage the subscription | Yes |
free_seat | Frees a seat | Yes |
Each tool calls a route of the JSON API under /api, which the dashboard uses. A script can call the API itself with the same Authorization: Bearer header:
curl -H "Authorization: Bearer $(gh auth token)" \
https://mergeloop.ai/api/w/<installation ID>/repos/<owner>/<name>/pulls/<number>/status
Read a PR’s state from another app
An app that shows PRs, such as a PR list in an editor, reads what MergeLoop knows of them from /api/v1, instead of working it out from labels and the config file. It authenticates as the API above does: Authorization: Bearer with the viewer’s GitHub token (OAuth, personal access token, or an App installation token) or a MergeLoop session. GitHub decides what the token can read. A repo it cannot read is a 404, the same as a repo that does not exist. A repo it can read is a 200, also when MergeLoop does not run there.
curl -H "Authorization: Bearer $(gh auth token)" \
https://mergeloop.ai/api/v1/repos/acme/widgets/pulls/42
curl -H "Authorization: Bearer $(gh auth token)" \
"https://mergeloop.ai/api/v1/repos/acme/widgets/pulls?numbers=42,43,44"
The second form reads up to 100 PRs of one repo, as { "pulls": [...] } in the order asked. Each PR reads:
{
"repo": "acme/widgets",
"number": 42,
"url": "https://mergeloop.ai/acme/widgets/pull/42",
"mergepatrol": {
"state": "on",
"installed": true,
"has_config": true,
"override": null,
"config_error": null,
"hold": null,
"missing_key": null
},
"queue": {
"trunk": "main",
"state": "held",
"hold": {
"reason": "review_required",
"layer": null,
"message": "waiting for a required review"
},
"priority": false,
"position": 3,
"stack": null,
"batch_pr": null
},
"merged": null,
"ejected": null
}
| Field | Says |
|---|---|
url | The PR in the dashboard. It opens the PR’s lane while queued, its history once merged or ejected, the repo’s settings while MergeLoop is off there, and the repo’s open PRs otherwise |
mergepatrol.state | on, off (no App on the repo, no config file, or turned off), or paused (the config file does not parse: config_error says why) |
mergepatrol.override | on or off when turned so in the dashboard, whatever the config file says. null follows the file |
mergepatrol.hold | Why any PR queued now goes on hold, before it is queued: { reason, layer, message } as in queue.hold, with reason no_subscription (a private repo on the hosted MergeLoop, without a subscription) or fair_use_limit. null when nothing about the repo holds it |
mergepatrol.missing_key | The agent’s tasks on this repo that the workspace has no model key for: { tasks, message, settings_url }, each task { task, provider } with task rebase (resolving a rebase’s conflicts), fix (fixing failed checks) or review. Without the key, a conflict or a failed check ejects the PR. settings_url is where to add the key. null when the workspace has a key for each task |
queue | null unless queued. state is waiting, running or held. position counts from 1 in the order the queue takes PRs. stack is { number, layers } for a stack. batch_pr is the draft PR testing it, while it runs in a batch |
queue.hold.reason | draft, review_required, changes_requested, workflow_approval (a fork PR’s runs wait for approval), fork_edits_off (a fork PR behind its trunk that does not allow edits by maintainers), no_queue (it targets a branch without a queue), no_subscription, fair_use_limit, reviewing (the agent’s automatic review runs), review_asks (that review waits for a human’s answers) review_changes (it sent findings back to the author, or a human answering it asked for changes, and it waits for the author) or unresolved_threads (review threads on it are unresolved, and the gate waits for them all). layer is the stack’s PR that holds it |
merged, ejected | How it last left the queue: { at, sha } or { at, reason }. Both null while it is queued, and when it left otherwise |
Every response has an ETag: send it back as If-None-Match for a 304 when nothing changed. Each caller, by its token, has 5,000 calls an hour, a 304 included, which x-ratelimit-limit, x-ratelimit-remaining, x-ratelimit-used and x-ratelimit-reset (Unix seconds) report. Past the limit, a 429 has a retry-after, in seconds. So does a 503 when GitHub’s own rate limit is used up.
Docs
- Get started with MergeLoop
Sign in with GitHub, install the App, merge the setup PR, add a model key, and label a pull request to queue it.
- How the merge queue works
What a queued PR needs, the agent that resolves conflicts and fixes failed checks, automatic review, branches, batches, stacks and PRs from forks.
- Configuration: .mergepatrol/mergepatrol.yml
Every setting in a repo's .mergepatrol/mergepatrol.yml: the label, batch size, the agent's model, verify commands, CI fixes, extra branches and review bars.
- Pricing
MergeLoop is free on public repos. Private repos cost $15 a seat a month after a 14-day trial with no card. Self-hosting is free.
- Self-host MergeLoop
Run the open-source MergeLoop yourself with Docker Compose, configure it, bring your own GitHub App, and isolate its jobs in gVisor sandboxes.