Coding agents are good at editing an API handler and bad at finding out whether the edit worked.

The reason is boring. Claude Code, Cursor and Codex run in a terminal. The session for the app lives in a browser tab. So after the agent changes GET /api/orders/{id}, it has two options: trust its own reading of the code, or ask a person to go and check.

The usual workaround is to paste a token or a "Copy as cURL" command into the chat. That works, with three problems:

  • The token is now in a transcript and on its way to a model provider.
  • It expires, usually in the middle of the task.
  • Many apps use HttpOnly session cookies and CSRF tokens, so there is no clean token to paste anyway.

Netraptor is a Chrome extension that records the API calls a web app makes and serves them to coding agents through a local MCP server. Until now that server could only read. In 0.7.0 we added one tool that sends a request, call_endpoint, and this post is about how it is built and what it refuses to do.

The idea: move the request, not the credential

The usual approaches move the credential to where the agent is. We went the other way. The agent says which request it wants, and the request runs in the tab that is already logged in.

agent (terminal) │ call_endpoint { tabId, requestId, url? } ▼ netraptor-mcp (127.0.0.1, stdio) │ paired local WebSocket ▼ extension ──► your logged-in tab ──► fetch() ──► your API │ redacted response ◄─────────────────┘ 

The agent's whole input looks like this:

{ "tabId": 123, "browserInstanceId": "<from list_sessions>", "requestId": 42, "url": "https://app.example.com/api/orders/1042" } 

requestId points at a request the app already made and Netraptor recorded. There is no headers field and no body field. The agent cannot supply either one.

Inside the tab, the extension runs this, trimmed from the real source:

if (location.origin !== input.origin) { return { ok: false, error: "The tab is on another site." }; } const response = await fetch(input.url, { method: input.method, headers: input.headers, // from the recording, never from the agent body: input.body, credentials: "include", redirect: "manual", }); 

The browser attaches the cookies. The recorded headers, including any Authorization header the page set, are replayed from the extension's local storage. The agent gets back a status, headers and a body, with credential-like values redacted by name.

What it refuses

A tool that sends real requests as the logged-in user needs a short list of things it can do. Ours:

  • Reads only. GET, HEAD, and GraphQL queries sent by POST. Other methods are refused. So are GraphQL mutations, subscriptions, batches, and persisted queries with no query text to inspect.
  • Recorded endpoints only. The agent can change path and query parameters, but the URL has to stay on the recorded origin and match the endpoint's path template. /api/orders/{id} accepts another ID. It does not accept /api/admin.
  • No redirects. A redirect could lead to an endpoint or origin that was never recorded, carrying the recorded headers. redirect: "manual", and a redirect response is a failure.
  • Same tab, same site. The origin check runs inside the document that sends the request. If the tab navigated somewhere else a moment earlier, the recorded headers do not go with it.
  • Off by default. The switch is in the extension's settings. Nothing sent over MCP can turn it on, and if it is off the tool tells the agent to ask you and not to work around it.
  • 8 second timeout.

Two details I would rather tell you than have you find.

The GraphQL check is a keyword scan, not a parser. A query with the word mutation anywhere in its text, including a field name or a comment, is refused. Telling comments from strings properly needs a real lexer, and a wrong guess there would let a write through. So it fails closed, and some harmless queries get rejected.

Path matching also rejects encoded slashes (%2F). Some servers decode them after routing, which would turn one {id} segment into several.

Every call shows up in your session

The call goes through the page's own fetch, in the page's main world. That was a deliberate choice. Netraptor's recorder hooks fetch, so a live call is recorded like any other request the app makes.

You can open the session and see everything an agent sent, next to your own traffic. The agent sees it too, through watch_requests.

What the loop looks like

  1. You tell the agent that total comes back as a string on the order page.
  2. It uses search and get_endpoint to find GET /api/orders/{id} and a recorded example.
  3. It edits the handler. The dev server reloads.
  4. It calls call_endpoint with that example's request ID.
  5. It reads the fresh response and tells you what came back.

Step 4 used to be a person switching windows.

It works on localhost, staging or production, wherever the tab is logged in.

How this compares to browser MCP servers

Claude in Chrome, Chrome DevTools MCP and Playwright MCP give an agent a browser to drive. They click, type, read the DOM, and can do anything a user can do on the page. For a user flow or a visual check, that is the right tool.

call_endpoint is much narrower. One HTTP request, JSON back, no navigation and no screenshots. The two fit together. When the question is "what does this endpoint return now?", a single tool call is cheaper than driving a page.

What it does not protect you from

  • A live call is a real request, sent as you.
  • GET is supposed to be safe. If your app has a GET route that changes data, and the app called it while you were recording, an agent can call it again.
  • Redaction works on names. A secret under an unusual field name comes back as is.
  • The response body goes to your MCP client, which may send it to its model provider.

Use a test account where you can. Leave the switch off for apps where a read is sensitive.

Writes are not in this release. They are deferred until there is an approval step inside the extension, where the agent cannot reach it.

Try it

You need Netraptor 0.7.0 and the matching MCP package. The local MCP server is part of Pro, which is a one-time purchase. Recording and exports are free.

claude mcp add --scope user netraptor -- npx -y @branch42-labs/netraptor-mcp@latest 

Then open Netraptor's settings, go to Local MCP server, and turn on "Let agents call recorded endpoints".

If you have built something similar, I would like to know where you drew the line on writes.