Blog · AI Agents

Give Your AI Agent an API Instead of a Browser: HAR to MCP Server in 10 Minutes

By Supril Singh 9 min read

An MCP server built from a HAR file gives an AI agent direct access to the same API calls a website’s own frontend makes. Instead of launching a headless browser, screenshotting a page, and asking a vision model where to click, the agent calls a typed tool that issues one HTTP request. The result is faster, costs a fraction as much per action, and does not break the next time someone ships a CSS change.

This post walks the whole path: capture a session, find the real endpoints, verify them, and expose them to Claude Code as MCP tools.

Why browser automation is the wrong default for agents

Most agent frameworks reach for Playwright, Puppeteer, or a hosted browser. That made sense when the only way to reach a site was to render it. It is a poor fit for agents for three concrete reasons.

Every action costs a round trip through a vision model. A browser agent typically takes a screenshot, sends it to the model, gets back a click target, performs the click, and screenshots again. A workflow with 15 steps can burn 30 to 50 screenshots. At current pricing, teams routinely report a dollar or more for a task that amounts to “log in and download a CSV.”

Latency compounds. Published comparisons of direct API calls against browser-driven equivalents put API round trips in the sub-second range against roughly 3+ seconds for the browser path — per step. Across a multi-step workflow the gap is the difference between a responsive agent and one that times out.

Selectors rot. A DOM-driven script encodes assumptions about markup that no one on the target team promised to keep stable. A redesign that changes nothing functional will still break it. The underlying JSON API tends to be far more stable, because the site’s own apps depend on it.

The API underneath is the interface the site actually uses. It is versioned, it returns structured data, and it changes on a deploy cadence rather than a design cadence.

Step 1: Capture the session

A HAR file is a JSON record of every request a browser made during a session, including headers, request bodies, responses, and timings. Every major browser exports one.

  1. Open DevTools and go to the Network tab.
  2. Enable Preserve log so navigations do not clear it.
  3. Perform the workflow you want your agent to reproduce — log in, filter a list, open a record, export a file.
  4. Click the download arrow and save the .har.

A few practical notes. Do the workflow deliberately and only once; a clean trace is far easier to read than a noisy one. Uncheck any request filters so you capture XHR and fetch alongside documents. And be aware of what you are holding: a HAR from an authenticated session contains live credentials — session cookies, bearer tokens, sometimes API keys in headers. Treat the file like a secret, and see the security section below.

Step 2: Find the endpoints that matter

A modest session produces hundreds of requests. Most are analytics beacons, fonts, images, and telemetry. You want the handful that carry the data.

Reading it by hand means filtering to XHR/fetch, sorting by response size, and opening candidates one at a time to find which returned the JSON you care about. It works, and it is tedious.

This is the part TraceMiner automates: you upload the HAR and describe the flow in plain language — “the request that returns the list of orders,” “the call that refreshes the auth token” — and it isolates the request chain, including the dependencies you would otherwise miss, like the token exchange that has to happen before the data call succeeds.

Either way, what you are looking for per endpoint is:

  • Method and URL, including which path and query parameters vary
  • Which request headers are load-bearing versus incidental
  • The shape of the request body
  • The shape of the response
  • What has to happen before this call for it to return 200

That last one is where most attempts fail, and it is worth its own post — see reverse-engineering auth flows.

Step 3: Verify before you build

Do not skip this. An endpoint that looks right in a trace can fail in practice for reasons the trace does not show: a header you assumed was decorative turns out to be a signature, a cookie was set by a prior redirect you did not capture, the server rejects requests without a plausible Origin.

Replay each call in isolation and confirm it returns what you expect. TraceMiner has a sandbox for exactly this, but curl is fine too. The rule is simple: never hand an agent a call you have not watched succeed. Debugging a failing request inside an agent loop is dramatically harder than debugging it standalone.

Step 4: Wrap the endpoints as MCP tools

The Model Context Protocol is how Claude Code and other agent runtimes discover and call external tools. An MCP server is a small program that advertises a set of tools and executes them on request.

Here is a minimal TypeScript server exposing one verified endpoint:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const server = new McpServer({ name: "orders-api", version: "1.0.0" });

// Credentials come from the environment, never from the HAR itself.
const BASE = process.env.ORDERS_API_BASE!;
const TOKEN = process.env.ORDERS_API_TOKEN!;

server.tool(
"list_orders",
"List orders for the authenticated account, optionally filtered by status.",
{
  status: z.enum(["open", "shipped", "cancelled"]).optional(),
  limit: z.number().int().min(1).max(100).default(25),
},
async ({ status, limit }) => {
  const url = new URL("/api/v2/orders", BASE);
  if (status) url.searchParams.set("status", status);
  url.searchParams.set("limit", String(limit));

  const res = await fetch(url, {
    headers: {
      authorization: `Bearer ${TOKEN}`,
      accept: "application/json",
    },
  });

  if (!res.ok) {
    return {
      content: [{ type: "text", text: `Request failed: ${res.status} ${res.statusText}` }],
      isError: true,
    };
  }

  return { content: [{ type: "text", text: JSON.stringify(await res.json(), null, 2) }] };
}
);

await server.connect(new StdioServerTransport());

Register it with Claude Code in .mcp.json:

{
"mcpServers": {
  "orders-api": {
    "command": "node",
    "args": ["./mcp-servers/orders/dist/index.js"],
    "env": {
      "ORDERS_API_BASE": "https://example.com",
      "ORDERS_API_TOKEN": "${ORDERS_API_TOKEN}"
    }
  }
}
}

Your agent can now call list_orders directly. No browser, no screenshots, no selectors.

What makes an MCP tool good rather than merely working

The difference between an agent that uses your tools well and one that flails is mostly tool design.

Describe tools in terms of intent, not transport. list_orders with a description of what an order is beats get_api_v2_orders. The model routes on the description.

Constrain inputs with the schema. An enum for status prevents a whole class of failed calls that the model would otherwise have to discover by trial.

Return errors as text with isError, not exceptions. The agent can read a message like 401 Unauthorized — token may have expired and react. An unhandled throw just kills the turn.

Keep responses small. Returning a 2 MB JSON blob floods the context window. Filter server-side and return the fields the agent needs.

One tool per user-meaningful action. Resist exposing forty endpoints because you found forty. Every additional tool makes routing harder.

Security and scope — read this part

The technique is neutral; how you point it is not.

Your HAR file contains live credentials. Never commit one to a repository, never paste one into a chat you do not control, and scrub it before sharing. Pull secrets from environment variables in the generated server, as above — never bake a token from the trace into code.

Be clear about what you are entitled to automate. The defensible cases are unambiguous: your own product’s API, your own authenticated account, data that is already public, and systems you have been engaged to test. Automating someone else’s platform against its terms of service is a different activity with real consequences — account termination at minimum, and platforms have pursued vendors as well as users. Check the terms and check them for the specific thing you intend to do.

Respect operational limits. Honour rate limits and Retry-After. An agent that can issue a request every 50 ms will find a rate limit faster than any human, and hammering an endpoint is both rude and self-defeating.

Frequently asked questions

Do I need the site’s permission to read my own HAR file? The HAR records traffic your own browser already sent and received. Capturing and reading it is ordinary debugging. The question that matters is what you do with the endpoints afterwards, and that is governed by the service’s terms.

What if the API is protected by request signing? Then you need to reproduce the signing logic, which usually lives in the site’s JavaScript. This is meaningfully harder than replaying a bearer token and is often a deliberate signal that programmatic access is not wanted. Treat it as a stop sign on third-party services; on your own systems it is just an implementation detail.

How stable are these endpoints? More stable than DOM selectors, less stable than a documented public API. Internal APIs change without notice. Build in error handling and expect to re-capture occasionally.

Can I do this for a site that requires login? Yes — that is the common case, and it is why the auth chain matters so much. Capture the session while logged in, and understand how the token is obtained and refreshed rather than copying a single expiring value.


TraceMiner takes the HAR and does the discovery, dependency mapping, and sandbox verification, so the part you write by hand is the tool layer. Start with a HAR or book a demo.

Published blog cards

Browse other published topics from the blog library.