Back to Browse

Regionfetch MCP Server

Developer ToolsLow Risk9.7MCP RegistryLocal
Free

Server data from the Official MCP Registry

Fetch any public URL as a visitor in a chosen country, with a signed receipt of its origin.

About

Fetch any public URL as a visitor in a chosen country, with a signed receipt of its origin.

Security Report

9.7
Low Risk9.7Low Risk

Valid MCP server (3 strong, 2 medium validity signals). No known CVEs in dependencies. ⚠️ Package registry links to a different repository than scanned source. Imported from the Official MCP Registry. 1 finding(s) downgraded by scanner intelligence.

12 files analyzed · 1 issue found

Security scores are indicators to help you make informed decisions, not guarantees. Always review permissions before connecting any MCP server.

Permissions Required

This plugin requests these system permissions. Most are normal for its category.

env_vars

Check that this permission is expected for this type of plugin.

file_system

Check that this permission is expected for this type of plugin.

Shell Command Execution

Runs commands on your machine. Be cautious — only use if you trust this plugin.

What You'll Need

Set these up before or after installing:

Private key of a funded Base wallet. The server signs one fresh x402 authorization per tool call. Keep it in the host's env block; it is never a tool argument.Required

Environment variable: REGION_FETCH_WALLET_PRIVATE_KEY

Per-payment ceiling, for example "$0.10". Use "off" to disable the cap.Optional

Environment variable: REGION_FETCH_MAX_PAYMENT

Deployment origin.Optional

Environment variable: REGION_FETCH_BASE_URL

A single pre-made x402 authorization. Funds exactly one request, so this is for one-shot testing only; prefer the wallet key.Required

Environment variable: REGION_FETCH_PAYMENT_SIGNATURE

How to Install

Add this to your MCP configuration file:

{
  "mcpServers": {
    "dev-regionfetch-mcp": {
      "env": {
        "REGION_FETCH_BASE_URL": "your-region-fetch-base-url-here",
        "REGION_FETCH_MAX_PAYMENT": "your-region-fetch-max-payment-here",
        "REGION_FETCH_PAYMENT_SIGNATURE": "your-region-fetch-payment-signature-here",
        "REGION_FETCH_WALLET_PRIVATE_KEY": "your-region-fetch-wallet-private-key-here"
      },
      "args": [
        "-y",
        "regionfetch-mcp"
      ],
      "command": "npx"
    }
  }
}

Documentation

View on GitHub

From the project's GitHub README.

Region Fetch

CI License: MIT Node

Pre-release. Neither package is published to npm yet, so the install commands below do not work today — build from source (see Development). The hosted API at regionfetch.dev is live.

Fetch a public HTTPS URL as a visitor in a specific country would see it, and get a signed receipt proving where the retrieval actually came from.

One request. One x402 payment. One Ed25519-signed attestation of the exit country, supplier, final URL, and content hash.

This repository holds the public client packages:

PackagenpmWhat it is
regionfetchnpm i regionfetchTypeScript SDK — fetch, recover, verify
regionfetch-mcpnpx regionfetch-mcpMCP server exposing one regionfetch tool

The service itself runs at https://regionfetch.dev.


Quick start

npm install regionfetch
import { RegionFetchClient } from "regionfetch";

const client = new RegionFetchClient();

const result = await client.fetchUrl(
  { url: "https://example.com/pricing", country: "DE", mode: "browser" },
  { paymentSignature },
);

console.log(result.data.body);
console.log(result.receipt.attestation.payload.country); // "DE"

Supported countries and modes

Countries: US, DE, JP, BR, IN. Anything else is rejected locally before a payment is created.

ModeWhat it does
http (default)Direct proxied GET with a country-appropriate locale, user agent, and Accept-Language. Follows up to five redirects, revalidating each target.
browserHeadless Chromium through the regional proxy, with a country-appropriate locale, timezone, viewport, and user agent. Waits for domcontentloaded and returns rendered HTML.

Escalation tiers

max_tier caps how hard the service will work to retrieve a blocked page: L0 and L1 use the standard regional supplier, L2 permits escalation to a managed unblocker and costs more. Defaults to L1.

The client never assumes a price — it authorizes exactly the amount the deployment returns in its challenge, so a dearer tier needs no client change. L3 is not a supported capability and is rejected before any payment is created.

L2 is priced higher than L0/L1 — $0.05 against $0.02 at the time of writing — and the price comes from the challenge, never from the client.

Pricing

The default list price is $0.02 USDC per request on Base (eip155:8453). Price, network, timeout, and response-size limits are deployment-controlled — read them from the 402 challenge rather than hardcoding them.


Paying

Use the scoped @x402/* packages, not x402 or x402-fetch

This matters more than it looks. The deployment speaks x402 protocol v2:

  • CAIP-2 networks (eip155:8453), not chain names (base)
  • an amount field, not maxAmountRequired
  • the challenge in a PAYMENT-REQUIRED response header, with an empty body
  • a PAYMENT-SIGNATURE request header, not X-PAYMENT

The unscoped x402 and x402-fetch packages on npm are v1 only. They parse the challenge from the response body, hardcode x402Versions = [1], reject a CAIP-2 network at the schema, and send X-PAYMENT. They cannot pay this API.

Install the v2 stack:

npm install regionfetch @x402/core @x402/evm viem

Wallet mode — a fresh authorization per request

import { RegionFetchClient } from "regionfetch";
import { createX402PaymentProvider } from "regionfetch/x402";

const paymentProvider = await createX402PaymentProvider({
  privateKey: process.env.WALLET_KEY,
  maxAmountPerPayment: "$0.10", // per-payment ceiling; default is $0.10
});

const client = new RegionFetchClient({ paymentProvider });
const result = await client.fetchUrl({ url: "https://example.com/", country: "JP" });

The SDK sends one unpaid probe, reads the challenge from the header, asks the provider for exactly one authorization, and retries once. It will never mint a second payment for one logical fetch — if the paid attempt is still refused, you get an error rather than a second charge.

Explicit mode — you already hold an authorization

await client.fetchUrl(input, { paymentSignature });

One authorization funds one durable request. Reusing it for a different body is rejected. Resending the identical body with the identical authorization after a dropped connection is the correct recovery — see below.

Bring your own wallet

paymentProvider is a one-method interface, so you are not tied to viem or to any particular custody model:

const paymentProvider = {
  async createPayment({ paymentRequired, resourceUrl }) {
    return myWalletService.authorize(paymentRequired, resourceUrl);
  },
};

Verifying receipts

Every terminal outcome — success and paid failure — carries a signed receipt.

import { verifyReceipt } from "regionfetch";

const key = await client.getAttestationKey();

const verification = verifyReceipt(result.receipt, {
  expectedKeyId: key.keyId,
  expectedPublicKeyPem: key.publicKeyPem,
  responseBody: result.data.body,
});

verification.valid; // true only if every check that ran passed

Four independent checks, because a valid signature alone proves less than it appears to:

CheckWhat it rules out
signatureValidTampering with the signed bytes
payloadMatchesA payload edited after signing, leaving payloadCanonical intact
receiptFieldsMatchConvenience fields at the receipt root disagreeing with what was signed
bodyHashMatchesA body swapped in transit
keyMatchesExpectationA self-consistent receipt minted with someone else's key

Two things the implementation is strict about:

  • Verification uses payloadCanonical verbatim. Re-serializing attestation.payload can produce different bytes and either fails spuriously or, worse, masks a mismatch.
  • The key's real type is checked, not its label. attestation.algorithm is attacker-controlled text; an RSA key presented as "Ed25519" is rejected.

Without a pinned or independently retrieved key, verification proves internal consistency only — anyone can mint a self-consistent receipt with their own key. Pass expectedKeyId / expectedPublicKeyPem when you need origin trust.


Errors and recovery

import { RegionFetchApiError } from "regionfetch";

try {
  await client.fetchUrl(input, { paymentSignature });
} catch (error) {
  if (error instanceof RegionFetchApiError && error.isPaidFailure) {
    // Payment settled; the attempt failed terminally. Keep the receipt —
    // it is the evidence that the paid attempt happened.
    await store(error.receipt);
  }
}
StatusMeaningWhat the SDK does
400Invalid body, country, mode, or targetThrows. Do not retry unchanged.
402Payment absent or unverifiedThrows RegionFetchPaymentRequiredError with the decoded challenge
409Same payment already processingRetries the identical request with the same authorization
429Rate limitedRetries with exponential backoff, honouring Retry-After
502 / 504Terminal paid failureThrows with the signed receipt attached. Never retried.
503Service or dependency unavailableThrows. Not retried by default.

Default policy: two retries, exponential backoff with jitter, Retry-After respected. Configure with retry: { maxRetries, baseDelayMs, maxDelayMs }.

After an ambiguous failure

If the connection drops after you sent a payment, do not create a second payment — but be aware that resending the identical authorization will not recover the result either. x402 exact uses EIP-3009, whose nonce is spent on-chain at settlement, so a settled payment is refused with 402 rather than replayed. Resending is harmless (it cannot charge twice), it just will not work.

The reliable recovery is the status endpoint, so capture requestId from every response you do receive:

const state = await client.getRequest(requestId);
state.statusMeaning
succeeded / failedTerminal. A stored failure arrives as HTTP 200, with the detail in the receipt.
settling / executingStill in flight.
unresolvedExecution may have happened but was never persisted. The server will not re-run it — that could duplicate an external effect after payment. Reconcile against the settlement transaction.

Local validation is load-bearing

The deployment's payment gate runs ahead of body validation: an unpaid request with country: "ZZ" still answers 402, not 400. So the SDK validates input before touching the network — otherwise you would mint an authorization for a request the server then rejects.

Checked locally: HTTPS-only, URL length ≤ 2048, known country, known mode, no unknown fields, and obvious loopback/private targets. The server remains authoritative for DNS resolution and the real public-target policy.


MCP server

npx -y regionfetch-mcp
{
  "mcpServers": {
    "regionfetch": {
      "command": "npx",
      "args": ["-y", "regionfetch-mcp"],
      "env": {
        "REGION_FETCH_BASE_URL": "https://regionfetch.dev",
        "REGION_FETCH_WALLET_PRIVATE_KEY": "0x...",
        "REGION_FETCH_MAX_PAYMENT": "$0.10"
      }
    }
  }
}

Exposes exactly one tool, regionfetch, taking url, country, and optional mode. Results use the { status, ok, response } envelope, with the upstream JSON body preserved verbatim and isError: true on any non-2xx — including paid failures, whose signed receipts survive intact.

See packages/mcp/README.md for configuration, payment modes, and the full environment variable list.


Repository layout

packages/sdk/     regionfetch      — client, verification, x402 provider
packages/mcp/     regionfetch-mcp  — stdio MCP server
examples/                          — runnable integration examples
openapi/                           — client-side snapshot of the HTTP contract
test/                              — opt-in live compatibility smoke test

Development

npm install
npm run build
npm test

Live checks against production are gated and never run in CI:

REGION_FETCH_LIVE_SMOKE=1 npm run smoke                      # free
REGION_FETCH_LIVE_SMOKE_SPEND=1 \
  REGION_FETCH_WALLET_PRIVATE_KEY=0x... npm run smoke        # spends USDC

Security

Never pass a private key as a fetch argument or an MCP tool input. Payment signatures are bearer credentials — the SDK redacts them from every diagnostic path and the MCP server keeps them off both stdout and stderr.

See SECURITY.md.

Contract notes

openapi/regionfetch.openapi.yaml is the SDK's snapshot of the HTTP contract, verified against the live API. An authoritative document published by the service will supersede it.

CONTRACT.md records the behaviours the client is built around — including why the x402 challenge is read from a header rather than the response body, and why input is validated before any payment is created.

Contributing

Issues and pull requests are welcome. Please run npm run build && npm test && npm run typecheck before opening a PR, and add a test for any behaviour change — the contract tests in packages/sdk/test are the guard against silently drifting away from the deployed API.

For anything security-sensitive, follow SECURITY.md rather than opening a public issue.

Versioning

SemVer. A new country or mode is an additive minor; removing one, renaming a field, or changing payment or receipt-signing semantics is a major. The receipt payload version (currently "1") is versioned independently of the package.

License

MIT

Reviews

No reviews yet

Be the first to review this server!