Reference
MCP tools
Your agent talks to Passback through a small MCP server on your machine. npx passback init --apply writes it into .mcp.json as npx passback dev; npx passback init without --apply only prints the configuration it proposes. Your agent starts it; you never run it yourself. It speaks to the agent over standard input and output and gives it three tools.
In Claude Code the tools carry the server's prefix, for example mcp__passback__passback_wait.
passback_wait
Waits until you leave comments in the browser, then returns them.
| Input | Value | Default |
|---|---|---|
timeoutSec |
a whole number from 1 to 600 | 120 |
It returns { "comments": [...], "marks": [...] }: at most 20 comments per call, oldest first. marks holds the same list as comments and is a deprecated alias. A comment it returns counts as picked up and is not returned again. When the time runs out with no new comment, both lists are empty, and the agent simply calls passback_wait again.
Each comment in the list has:
| Field | What it holds |
|---|---|
id |
The comment's id. The agent passes it to passback_status. |
comment |
Your text. |
route |
The address of the page, from the path on. |
selector |
A selector for the element you clicked. |
file |
Where the element comes from in your code, as path:line:column. Only when known: for React code in .jsx and .tsx files. |
buildId |
The build the comment was left on, when known. |
inboxFile |
The path of the comment's own file in .passback/inbox. |
screenshot |
The path of a screenshot, when there is one. |
timeline |
What happened on the page just before the comment, when it was recorded. |
reopened |
{ iteration, comment }, present only on a comment the reporter sent back after the agent fixed it. |
One comment returned
One comment, as passback_wait returns it right after pickup. This is the tool's JSON content, not the MCP transport envelope around it. Paths are relative to the example project root. comments is the current response key; marks is a deprecated alias that holds the same list.
{
"comments": [
{
"id": "35353535-3535-4353-8353-353535353535",
"comment": "Price sits 4px right of the title's left edge; should line up with it.",
"route": "/products/:id",
"selector": "[data-testid=\"price\"]",
"file": "src/App.tsx:21:5",
"buildId": "3f2a9c1",
"inboxFile": ".passback/inbox/35353535-3535-4353-8353-353535353535.md",
"timeline": [
{
"t": -21430,
"kind": "navigation",
"from": "/",
"to": "/products/42"
},
{
"t": -8120,
"kind": "action",
"type": "scroll",
"delta": [0, 360]
},
{
"t": -1650,
"kind": "action",
"type": "click",
"selector": "[data-testid=\"price\"]"
}
]
}
],
"marks": [
{
"id": "35353535-3535-4353-8353-353535353535",
"comment": "Price sits 4px right of the title's left edge; should line up with it.",
"route": "/products/:id",
"selector": "[data-testid=\"price\"]",
"file": "src/App.tsx:21:5",
"buildId": "3f2a9c1",
"inboxFile": ".passback/inbox/35353535-3535-4353-8353-353535353535.md",
"timeline": [
{
"t": -21430,
"kind": "navigation",
"from": "/",
"to": "/products/42"
},
{
"t": -8120,
"kind": "action",
"type": "scroll",
"delta": [0, 360]
},
{
"t": -1650,
"kind": "action",
"type": "click",
"selector": "[data-testid=\"price\"]"
}
]
}
]
}passback_status
Reports progress on one comment, so its pin and its box update in the browser.
| Input | Value |
|---|---|
commentId |
The id of the comment, as passback_wait returned it. Preferred. |
markId |
Deprecated alias of commentId. |
state |
running when the agent starts, then done or failed. queued is also accepted. |
message |
Optional. With done, one line on what changed; with failed, the reason. The box shows the reason of a failure. |
prUrl |
Optional. The address of a pull request; the box shows it as a link. |
Pass commentId or markId; at least one is required. Passing both is accepted when they are equal. Two different values are refused with an error that names both fields, and nothing is written. The aliases are deprecated and remain available for one release.
It returns { "written": ..., "proposed": ... }. written is the path of the status file it wrote into .passback/outbox. proposed is true when a done status added the agent's message to the comment's file as its reply.
A commentId or markId that is not a comment id, or a prUrl that is not an address, is refused with an error that names the field that was passed.
passback_open
Opens your app in your browser.
| Input | Value | Default |
|---|---|---|
url |
an http or https address |
the PASSBACK_URL environment variable, else http://localhost:5173 |
It returns { "opened": ... } with the address it opened. Any other kind of address is refused.
An explicit url always wins over PASSBACK_URL. For a detected Next app, init sets PASSBACK_URL to http://localhost:3000 in newly generated MCP server entries. For a single Vite app, init sets it to http://localhost plus the literal server.port in the Vite config, when the config exports an object (directly or through defineConfig({ ... })) and the port is a plain integer from 1 to 65535. Init only reads the config text; it never runs it. A comment, a string, preview.port, an expression, a variable, a function config, a spread or a duplicate property gives no URL, so passback_open keeps the http://localhost:5173 fallback. The same holds for several apps, no detected app or an unknown framework.
Init never changes a Passback entry that already exists, including its URL; edit that entry yourself. To set the address by hand, add PASSBACK_URL to the server's environment: "env": { "PASSBACK_URL": "http://localhost:4000" } in .mcp.json or .cursor/mcp.json, or a [mcp_servers.passback.env] table with PASSBACK_URL = "http://localhost:4000" in .codex/config.toml.
passback_status.