Building Resilient AI Agents That Survive Website Blocks in 2026: TypeScript and MCP Patterns
Websites are blocking AI agents by default. Here is how to build production TypeScript agents that prefer official APIs and MCP tools, identify themselves correctly, handle blocks gracefully, and stay reliable for Next.js and Node.js teams.
By Mussawar Hayat
The open web is starting to refuse agents
In October 2026 the conversation on X shifted from “can agents browse” to “why did the agent get blocked again?” Meta’s Muse, OpenAI’s Dots, xAI’s Grok, and other computer-use agents are hitting deliberate blocks from Amazon, Delta, JPMorgan Chase, Yelp, and others. Cloudflare’s Agent category and site-level bot policies make the default more hostile than it was a year ago.
This is not a model problem. It is an access and design problem. Agents that treat every website as a free browser session will keep failing. Agents that prefer official APIs, well-scoped MCP tools, proper identification, and graceful fallbacks stay useful.
What you will implement
- A decision rule: official API or MCP first, browser only as last resort
- A TypeScript MCP tool that fetches structured data with identification headers and timeout budgets
- Block detection, retry policy, and human-in-the-loop escalation
- Production guardrails so an agent never silently scrapes past a robots.txt or terms violation
1. The problem
Sites block agents for three overlapping reasons.
- Policy. The business does not want third-party agents completing purchases, bookings, or account actions. Amazon’s block of Meta Muse and Delta’s refusal of third-party booking agents are examples.
- Infrastructure defaults. Cloudflare classifies traffic into Search, Training, and Agent. New ad-monetized domains and some migrated zones block Agent traffic on pages that display ads. Anti-bot systems also flag headless browsers, datacenter IPs, and missing cookies.
- Security. Banks and brokerages (JPMorgan Chase, Charles Schwab) explicitly block agents from account access.
An agent that only knows “open the browser and click” will hit these walls. Users see failed tasks and lose trust. Your logs fill with 403s and CAPTCHA challenges.
2. Prefer structured access over page scraping
The durable pattern is the same one that made APIs win over screen-scraping in earlier automation waves:
- If an official API, webhook, or partner integration exists, use it.
- If the site exposes an MCP server or WebMCP tools, call those.
- Only fall back to a controlled browser session when no structured path exists and the user has explicitly authorized the action.
Model Context Protocol (MCP) gives you a standard way to expose tools. An agent client can list tools, call them with typed arguments, and receive structured results without parsing HTML. Official docs live at modelcontextprotocol.io.
Cloudflare’s WebMCP preview and the emerging document.modelContext surface are further signs that sites will prefer to expose tools rather than be scraped. Prefer those when available.
3. Identification and policy checks
Even when you must fetch a page, identify the agent clearly.
- Set a descriptive User-Agent that includes the product name and a contact URL or email.
- Honor
robots.txt. Do not fetch paths disallowed for your agent user-agent. - Respect rate limits and Cache-Control.
- Never store or replay session cookies from a user’s browser without explicit consent and short-lived scoping.
In code, centralize the fetch wrapper so every tool uses the same policy.
// lib/agent-fetch.ts
export async function agentFetch(url: string, init: RequestInit = {}) {
const headers = new Headers(init.headers)
headers.set('User-Agent', 'MussawarAgent/1.0 (+https://mussawarhayat.site/contact)')
headers.set('Accept', 'application/json, text/html;q=0.9')
const controller = new AbortController()
const timeout = setTimeout(() => controller.abort(), 12_000)
try {
const res = await fetch(url, { ...init, headers, signal: controller.signal, redirect: 'follow' })
if (res.status === 403 || res.status === 429) {
throw new AgentBlockedError(res.status, url)
}
return res
} finally {
clearTimeout(timeout)
}
}
export class AgentBlockedError extends Error {
constructor(public status: number, public url: string) {
super(`Agent blocked or rate-limited (${status}) on ${url}`)
}
}4. MCP tool example
Expose a narrow tool rather than a generic “browse any URL” tool. The tool should declare its purpose, input schema, and annotations so the model knows it is read-only and open-world.
// mcp/tools/search-docs.ts
import { z } from 'zod'
import { agentFetch } from '../lib/agent-fetch'
export const searchDocsTool = {
name: 'search_official_docs',
description: 'Search official documentation for a product. Prefer this over generic web search.',
inputSchema: z.object({
product: z.enum(['nextjs', 'prisma', 'node']),
query: z.string().min(2).max(200),
}),
annotations: { readOnlyHint: true, openWorldHint: true },
async execute({ product, query }: { product: string; query: string }) {
const base = {
nextjs: 'https://nextjs.org/docs',
prisma: 'https://www.prisma.io/docs',
node: 'https://nodejs.org/docs',
}[product]
// In production call the site’s search API or a curated index, not raw HTML
const url = `${base}?q=${encodeURIComponent(query)}`
const res = await agentFetch(url)
if (!res.ok) throw new Error(`Docs fetch failed: ${res.status}`)
// Parse or return structured excerpts — keep the result small
return { content: [{ type: 'text', text: await res.text().then(t => t.slice(0, 4000)) }] }
},
}Register the tool on your MCP server using the official TypeScript SDK. See the production MCP server guide on this site for the full stdio / Streamable HTTP setup.
5. Block handling and escalation
When a fetch returns 403, 429, or a known block page signature:
- Log the URL, status, and agent task id. Do not retry immediately.
- Check whether an official API or alternative MCP tool exists for the same goal.
- If the action is user-initiated and high-value (booking, purchase), surface a clear message and hand control back to the user or a human reviewer.
- Never instruct the agent to solve a CAPTCHA or spoof a browser fingerprint. That path is fragile and often against terms of service.
A small state machine in the agent loop is enough:
type AccessResult =
| { kind: 'ok'; data: unknown }
| { kind: 'blocked'; reason: string; fallback?: string }
| { kind: 'needs_human'; message: string }
async function tryStructuredThenBrowser(goal: string): Promise {
try {
return { kind: 'ok', data: await callOfficialApi(goal) }
} catch (e) {
if (e instanceof AgentBlockedError) {
return { kind: 'blocked', reason: e.message, fallback: 'Ask the user to complete the step' }
}
throw e
}
} 6. Best practices
- Keep tool surfaces narrow. One tool per well-defined action beats a single “do anything on the web” tool.
- Cache successful structured responses with short TTLs. Agents re-fetch the same product page far more often than humans do.
- Record every external call with the tool name, status, and latency so you can see which sites are becoming hostile.
- Version your agent’s User-Agent and policy. When a site starts blocking you, you can correlate the change.
- Prefer partner programs or data licenses (Yelp’s model is an example) over adversarial scraping when the data has commercial value.
7. Security considerations
- Never let an agent tool accept an arbitrary URL from the model without an allowlist or domain policy.
- Sanitize and size-limit any HTML or JSON you return to the model. Large pages inflate context and can contain prompt-injection payloads.
- Separate the credentials used by agent tools from user session cookies. Short-lived, scoped tokens only.
- Audit logs must not contain full page bodies that include personal data.
- Review the terms of every site your agent is allowed to touch. A block is often the site telling you the action is not permitted.
8. Performance notes
Browser automation is slower and more expensive than an API call. Measure wall-clock time and token use per successful task. Cap concurrent browser sessions. Prefer headless fetches with the wrapper above for public documentation; reserve full browser use for flows that truly require it and that you have tested against the target site’s current bot policy.
When Cloudflare or a similar edge blocks Agent traffic, the request fails fast. Design the agent loop to treat that as a terminal signal for that path rather than burning more steps.
9. Real use cases
- A support agent that looks up order status via the merchant’s official API instead of logging into the customer portal.
- A documentation assistant that calls Next.js or Prisma docs through a curated MCP tool rather than a generic web search that may be blocked.
- A booking assistant that checks partner availability endpoints and falls back to “please complete this step yourself” when the airline blocks third-party agents.
- An internal ops agent that only talks to your own MCP servers and never touches the public web.
10. Common mistakes
- Giving the agent a full browser and hoping anti-bot systems will not notice.
- Retrying a 403 with a different User-Agent or proxy. This usually makes the block stricter.
- Parsing arbitrary HTML and feeding the whole page into the model context.
- Ignoring robots.txt or site terms because “the user asked for it.”
- Building one giant “web” tool instead of small, auditable tools.
- Failing to surface the block to the user so they can finish the task manually.
11. FAQ
Does Cloudflare block all AI agents by default?
No. Cloudflare distinguishes Search, Training, and Agent. New ad-monetized domains and some existing configurations block Agent traffic on pages that display ads. Search remains allowed by default. Site owners can change the settings. See Cloudflare’s bot documentation for the current controls.
Should I spoof a normal browser User-Agent?
No. Clear identification plus official APIs or partner access is more durable. Spoofing increases the chance of hard blocks and violates many sites’ terms.
Can MCP solve the blocking problem?
MCP does not magically open closed sites, but it gives you a standard way to expose and consume the structured tools that sites are willing to offer. Prefer MCP and official APIs over raw browsing.
What should the agent do when a site blocks it?
Stop that path, log the event, try any official alternative, and tell the user clearly. Do not loop or attempt to bypass the block.
Is browser use still useful?
Yes, for internal tools, sites that explicitly allow it, or short-lived user-authorized sessions. Treat it as the exception, not the default.
12. Summary
Websites blocking AI agents is a structural shift, not a temporary glitch. Production agents in 2026 stay reliable by preferring official APIs and MCP tools, identifying themselves, respecting policy, detecting blocks quickly, and handing control back to humans when needed. The TypeScript patterns above—centralized fetch policy, narrow MCP tools, and explicit escalation—are enough to ship an agent that does not surprise you with silent failures.
Key takeaway
Design for the structured path first. Browser automation is a fallback, not a strategy.
Building agents that need reliable external access?
I design MCP servers, tool contracts, and guardrails for Next.js and Node.js teams that cannot afford flaky browser loops. Get in touch or see full-stack and AI development services.
Related reading: Build a production MCP server in TypeScript and Postgres write guardrails for agents.
Related guides
How to run Octomind AI agents against a Next.js 16 app in production. Covers agent-generated Playwright tests, TypeScript config, CI integration on preview deploys, auth handling, flake control, and when autonomous e2e agents beat hand-written suites.
Claude Code Mods in TypeScript: Production Guardrails for Next.js Teams (2026)Anthropic shipped Claude Code mods on October 1, 2026. Build a TypeScript plugin that blocks force-pushes, redacts secrets from tool output, and asks before destructive shell commands, without replacing human review.
Production Evals for Coding Agents in TypeScript and Next.js (2026)Code generation is cheap. Knowing the agent is right is not. This guide shows how to score TypeScript and Next.js coding agents with fixture tasks, deterministic checks, LLM judges used only where needed, merge gates, and a CI harness you can run without a human watching every diff.
