Mergepatrol

MCP server and API

From an agent, or the API

Mergepatrol 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: Mergepatrol 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 mergepatrol https://mergepatrol.ai/mcp

The client gets a Mergepatrol session, and the GitHub token stays on the server. Mergepatrol 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. Mergepatrol 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 mergepatrol https://mergepatrol.ai/mcp \
  --header "Authorization: Bearer $(gh auth token)"

A GitHub token reaches only the repos GitHub lets it read: Mergepatrol 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.

ToolDoesSign-in
get_changelogMergepatrol’s newest changelog entries
get_sessionWho you are signed in as, and your workspaces
get_inboxYour PRs, and the PRs that ask for your review, across workspacesYes
list_queueThe queues, in the order they take PRs: a repo’s, or the workspace’sWithout repo
list_eventsA repo’s queue events, newest first
get_fixThe diff of a commit the fix agent pushed
list_historyThe PRs that left the queue: merged, ejected, or removedYes
get_history_statsThe queue’s numbers over a windowYes
list_reposThe workspace’s repos, and whether each queuesYes
set_repoTurns a repo’s queue on or off, or back to following its config fileYes
list_open_pullsA repo’s open PRs that are not queued
get_pullA PR’s description, labels, checks, and timeline
get_pull_diffA PR’s changes, or one commit’s
pull_statusWhere a PR is, as /api/v1 below says, and its latest events
queue_pullQueues a PR, or its stack. priority puts it at the front
dequeue_pullTakes a PR, and its stack, out of the queue
close_pullCloses a PR
review_pullApproves a PR, asks for changes, or comments, then queues it if asked
get_agent_reviewThe agent’s review of a queued PR: what it found, and what it asks a human
answer_agent_reviewAnswers what the agent’s review asks, as your review on GitHub
ask_agent_about_reviewAsks the agent a question about one of its findings
ask_agent_review_reviewersAsks others to answer the agent’s review
mark_pull_seenMarks a PR read in your inboxYes
snooze_pullPuts a PR out of your inbox for a whileYes
unsnooze_pullPuts a snoozed PR back in your inboxYes
nudge_reviewersMentions reviewers in a comment, asking them to review
get_repo_configA repo’s queue settings
propose_repo_configOpens a PR that changes a repo’s queue settingsYes
get_settingsWhich model API keys and ChatGPT login the workspace hasYes
set_api_keySets a model provider’s API keyYes
delete_api_keyRemoves a model provider’s API keyYes
start_chatgpt_loginStarts a ChatGPT sign-in: a code for the user to enterYes
delete_chatgpt_loginRemoves the ChatGPT loginYes
poll_chatgpt_loginFinishes the ChatGPT sign-in once the user entered the codeYes
get_review_settingsThe agent’s review settings, and its track recordYes
set_review_settingsTurns the agent’s review on or off, and sets how sure it must beYes
get_billingThe workspace’s plan and seatsYes
start_checkoutA Stripe page to subscribeYes
open_billing_portalA Stripe page to manage the subscriptionYes
free_seatFrees a seatYes

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://mergepatrol.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 Mergepatrol 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 Mergepatrol 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 Mergepatrol does not run there.

curl -H "Authorization: Bearer $(gh auth token)" \
  https://mergepatrol.ai/api/v1/repos/acme/widgets/pulls/42
curl -H "Authorization: Bearer $(gh auth token)" \
  "https://mergepatrol.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://mergepatrol.ai/acme/widgets/pull/42",
  "mergepatrol": {
    "state": "on",
    "installed": true,
    "has_config": true,
    "override": null,
    "config_error": 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
}
FieldSays
urlThe PR in the dashboard. It opens the PR’s lane while queued, its history once merged or ejected, the repo’s settings while Mergepatrol is off there, and the repo’s open PRs otherwise
mergepatrol.stateon, 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.overrideon or off when turned so in the dashboard, whatever the config file says. null follows the file
queuenull 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.reasondraft, 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) or review_changes (a human answering it asked for changes, and it waits for a push). layer is the stack’s PR that holds it
merged, ejectedHow 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