PullPulse
docs · mcp · 5 min

PullPulse MCP server

PullPulse exposes its review state over the Model Context Protocol so your coding agents can read what the humans see: which pull requests are waiting, why each

PullPulse exposes its review state over the Model Context Protocol so your coding agents can read what the humans see: which pull requests are waiting, why each one costs to review, who should review it, and how it will be routed into Slack.

It is a first-class product surface, not a debug endpoint. Everything the Slack app knows, an agent with a token can ask for.


Endpoint

POST   https://<pullpulse-host>/mcp
DELETE https://<pullpulse-host>/mcp     # session termination
GET    https://<pullpulse-host>/mcp     # 405; there is no server-initiated stream

<pullpulse-host> is the PullPulse service host. You do not have to look it up: the exact URL is printed next to the token in the message /pullpulse mcp sends you.

Protocol versions

The server is dual-era. It implements the revision the specification currently names, 2026-07-28, and it also speaks the initialization-based revisions every shipping client still uses:

Revision How a client selects it Sessions
2026-07-28 Send _meta["io.modelcontextprotocol/protocolVersion"] on every request, mirrored into the MCP-Protocol-Version, Mcp-Method and Mcp-Name headers None
2025-11-25, 2025-06-18, 2025-03-26 Open with initialize Mcp-Session-Id, issued on initialize, required afterwards, released with DELETE

You do not have to choose: point any MCP client at the URL and it will negotiate. Modern clients get a stateless endpoint; initialize gets a session id back in the Mcp-Session-Id response header.

Sessions live in KV for 24 hours and are bound to the token that created them — a session id presented with a different token is treated as unknown. An expired or unknown session returns 404 so the client re-initializes rather than retrying forever.

Responses


Authentication

Every request needs a workspace-scoped bearer token:

Authorization: Bearer pp_<64 hex characters>

Tokens are minted from Slack:

/pullpulse mcp                 → list tokens and mint a new one
/pullpulse mcp revoke <name>   → revoke one

Only the SHA-256 of a token is stored, so it is shown exactly once, at mint time. Copy it then.

Scopes are read (everything except post_pr_note) and write (adds post_pr_note). Mint a read-only token for anything you would not want writing to GitHub — which is most agents.

A missing or rejected token gets 401 with a WWW-Authenticate: Bearer realm="pullpulse", … header; error="invalid_request" means you sent none, error="invalid_token" means yours is unknown or revoked.

Tenancy. Every tool is scoped to the workspace that issued the token, and no argument can widen that scope. Asking for another workspace's repository returns "not tracked", never data.


Client configuration

Claude Code

claude mcp add --transport http pullpulse https://<pullpulse-host>/mcp \
  --header "Authorization: Bearer pp_your_token_here"

Or in .mcp.json, checked into the repository so the whole team gets it (keep the token in the environment, not in the file):

{
  "mcpServers": {
    "pullpulse": {
      "type": "http",
      "url": "https://<pullpulse-host>/mcp",
      "headers": { "Authorization": "Bearer ${PULLPULSE_MCP_TOKEN}" }
    }
  }
}

Cursor

~/.cursor/mcp.json for every project, or .cursor/mcp.json for one:

{
  "mcpServers": {
    "pullpulse": {
      "url": "https://<pullpulse-host>/mcp",
      "headers": { "Authorization": "Bearer pp_your_token_here" }
    }
  }
}

Slackbot MCP client

In Slack, open Settings → Slackbot → Connectors → Add MCP server and supply:

Mint a dedicated read-only token for this one: it is shared by everyone in the workspace who talks to Slackbot.

Anything else

Any Streamable HTTP client works. The endpoint is a single URL, the credential is a bearer token, and there is no OAuth dance to implement.

curl -sS https://<pullpulse-host>/mcp \
  -H "Authorization: Bearer $PULLPULSE_MCP_TOKEN" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize",
       "params":{"protocolVersion":"2025-11-25","capabilities":{},
                 "clientInfo":{"name":"curl","version":"8"}}}'

Tools

All eight return both a human-readable text summary and a complete structuredContent. Text is hard-capped at 24 KB — narrow a query with repo or limit rather than paginating.

Tool Arguments Answers
list_review_queue githubLogin?, repo?, limit? What is waiting on review, ranked by review effort, with attention flags and estimated review minutes
get_pr_context repo, number Everything about one PR: its complexity tier and attention flags, CODEOWNERS, reviewers, checks, cached AI brief, read-first file ordering, how it was routed
find_similar_prs query, repo?, limit? Precedent, over the same embeddings the enrichment pipeline writes
suggest_reviewers repo, number Who should review, with the evidence — CODEOWNERS plus real review history
get_repo_policy repo Effective .pullpulse.yml, and which rule would match a PR here
list_agent_prs repo?, agent?, limit? Pull requests opened by agent accounts, with CI state and whether a human has looked
review_stats sinceHours?, repo? Median time to first human review, open count, agent share, stale count
post_pr_note repo, number, markdown write scope. Posts a comment on the PR through the GitHub App

post_pr_note appends an <!-- pullpulse:relay --> marker so PullPulse's own Slack↔GitHub sync does not echo the comment back into Slack as if a human had written it.

The tool descriptions returned by tools/list are the real documentation — they state when to reach for each tool and how to read its result. This table is a map, not a substitute.

Resources

URI Contents
pullpulse://review-queue The workspace review queue as JSON, attachable without a tool call
pullpulse://policy/{repo} Effective routing policy for a watched repository

resources/list enumerates the queue plus one policy resource per watched repository; resources/templates/list returns the {repo} template.

Prompts

Name Arguments What it does
review-this-pr repo, number Inlines the PR's live context, then walks a review procedure: read-first ordering, then what each attention flag obliges you to verify, then a verdict with citations
triage-agent-queue repo?, agent? Inlines the live agent queue and sorts it into back-to-the-agent / merge candidate / needs a human / close, with the decision rule for each

Both expand against live data at prompts/get time, so the model starts from facts rather than from a plan to go and fetch them.


Errors

Standard JSON-RPC codes, plus the ones the MCP specification reserves:

Code Meaning
-32700 Malformed JSON
-32600 Invalid request envelope, or a batch sent to 2026-07-28
-32601 Unknown method (HTTP 404 in the modern era)
-32602 Invalid params, unknown tool, unknown prompt, unknown resource
-32603 Internal error
-32020 A mirrored header disagrees with the request body
-32021 A required client capability was not declared
-32022 Unsupported protocol version; data.supported lists what to retry with

Error messages are deliberately mapped, never raw: an internal failure returns "Internal error" rather than a stack, a query or anything else that would leak into an agent's context.

Business-logic failures — an untracked pull request, a repository that is not watched, a read-only token calling post_pr_note — come back as a normal result with isError: true and an explanatory structuredContent, because those are things a model can react to.

Observability

Every call writes an mcp.call point to Analytics Engine, indexed by workspace, with the tool name (or method) as the detail dimension. That is how you find out which tools your agents actually use.