Verifica pagos en blockchain y escanea el texto de los comercios para evitar que los agentes firmen transacciones fraudulentas o incorrectas.
También conocido como runningoffcode/agent-wormhole, wormhole-x402
- Stars
- 6
- Herramientas
- —
- Descargas, 30 días
- —
- Última versión
- 0.8.0
- Último commit
- 10 nov 2026
- Licencia
- Apache-2.0
¿Qué es Agent Wormhole MCP?
Agent Wormhole es un MCP server. Verifica pagos en blockchain y escanea el texto de los comercios para evitar que los agentes firmen transacciones fraudulentas o incorrectas.
Resumen generado a partir del README del proyecto
Acerca del MCP server de Agent Wormhole
Agent Wormhole proporciona una capa de seguridad para agentes autónomos que realizan transacciones en blockchain. Resuelve una vulnerabilidad específica en la que un agente podría firmar una transacción que es técnicamente válida pero que no coincide con el comercio o el monto previsto. El servidor funciona como un punto de control para garantizar que el payload del pago se alinee perfectamente con una cotización proporcionada, evitando que atacantes redirijan fondos o inflen costos.
Los desarrolladores pueden usar esto como un servidor de herramientas MCP local vía stdio o conectarse a un verificador alojado usando una WORMHOLE_API_KEY. El sistema es compatible con redes Solana y EVM. Ofrece dos herramientas principales: verify_payment, que compara una cotización de red con un payload de transacción, y scan_text, que inspecciona las descripciones de los comercios en busca de instrucciones maliciosas antes de que un modelo de IA las procese. Para mayor seguridad, los usuarios pueden configurar un WORMHOLE_VERIFY_PUBKEY para asegurar que los veredictos alojados se verifiquen criptográficamente contra una clave pública conocida.
Qué puedes hacer con él
- 1Prevenir destinos de pago fraudulentosUn agente de IA debe comprobar si una transacción se está enviando a un atacante en lugar del comercio cotizado.
- 2Detectar montos de transacción infladosVerifica que el monto que el agente está a punto de firmar coincida exactamente con el monto especificado en la cotización del comercio.
- 3Sanitizar descripciones de comerciosEscanea el texto de los comercios y las tarjetas de agentes en busca de instrucciones inyectadas que podrían manipular el comportamiento del modelo.
- 4Asegurar flujos de trabajo de agentes sin interfaz (headless)Proporciona un punto de control de seguridad para agentes que no cuentan con un humano en el proceso para aprobar transacciones.
Escrito a partir del README del proyecto. Cada caso de uso se verifica con una cita del mismo.
Cómo instalar Agent Wormhole MCP
Formatos verificados el 10 nov 2026Añadir a CursorAñadir a GooseInstalar Agent Wormhole MCP en Claude Code
$ claude mcp add --env 'WORMHOLE_API_KEY=<YOUR_WORMHOLE_API_KEY>' wormhole-x402 -- npx -y wormhole-x402Instalar Agent Wormhole MCP en Cursor
~/.cursor/mcp.json
{
"mcpServers": {
"wormhole-x402": {
"command": "npx",
"args": [
"-y",
"wormhole-x402"
],
"env": {
"WORMHOLE_API_KEY": "<YOUR_WORMHOLE_API_KEY>"
}
}
}
}Instalar Agent Wormhole MCP en VS Code
.vscode/mcp.json
{
"servers": {
"wormhole-x402": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"wormhole-x402"
],
"env": {
"WORMHOLE_API_KEY": "<YOUR_WORMHOLE_API_KEY>"
}
}
}
}Instalar Agent Wormhole MCP en VS Code CLI
$ code --add-mcp '{"name":"wormhole-x402","type":"stdio","command":"npx","args":["-y","wormhole-x402"],"env":{"WORMHOLE_API_KEY":"<YOUR_WORMHOLE_API_KEY>"}}'Instalar Agent Wormhole MCP en Claude Desktop
claude_desktop_config.json
{
"mcpServers": {
"wormhole-x402": {
"command": "npx",
"args": [
"-y",
"wormhole-x402"
],
"env": {
"WORMHOLE_API_KEY": "<YOUR_WORMHOLE_API_KEY>"
}
}
}
}Instalar Agent Wormhole MCP en Codex
$ codex mcp add wormhole-x402 --env 'WORMHOLE_API_KEY=<YOUR_WORMHOLE_API_KEY>' -- npx -y wormhole-x402Instalar Agent Wormhole MCP en Codex config.toml
~/.codex/config.toml
[mcp_servers.wormhole-x402] command = "npx" args = ["-y", "wormhole-x402"] [mcp_servers.wormhole-x402.env] WORMHOLE_API_KEY = "<YOUR_WORMHOLE_API_KEY>"
Instalar Agent Wormhole MCP en Gemini CLI
$ gemini mcp add -s user -e 'WORMHOLE_API_KEY=<YOUR_WORMHOLE_API_KEY>' wormhole-x402 npx -- -y wormhole-x402Instalar Agent Wormhole MCP en Gemini settings.json
~/.gemini/settings.json
{
"mcpServers": {
"wormhole-x402": {
"command": "npx",
"args": [
"-y",
"wormhole-x402"
],
"env": {
"WORMHOLE_API_KEY": "<YOUR_WORMHOLE_API_KEY>"
}
}
}
}Configuración
WORMHOLE_API_KEYSecretoOptional. Hosted-mode key from dashboard.agentwormhole.com: adds URL checks with change history, and the operator's spend policy (budgets, approvals, kill switch) on payments.
WORMHOLE_ADDRESS_LEDGEROptional. Path to the address-provenance ledger written by wormhole-guard's readguard hook (default ~/.wormhole/addresses.jsonl).
WORMHOLE_VERIFY_URLOptional. Override the hosted verifier endpoint (default https://dashboard.agentwormhole.com/api/v1/verify).
| Nombre | Descripción | Tipo |
|---|---|---|
| WORMHOLE_API_KEY | Optional. Hosted-mode key from dashboard.agentwormhole.com: adds URL checks with change history, and the operator's spend policy (budgets, approvals, kill switch) on payments. | Secreto |
| WORMHOLE_ADDRESS_LEDGER | Optional. Path to the address-provenance ledger written by wormhole-guard's readguard hook (default ~/.wormhole/addresses.jsonl). | |
| WORMHOLE_VERIFY_URL | Optional. Override the hosted verifier endpoint (default https://dashboard.agentwormhole.com/api/v1/verify). |
Verificaciones automáticas
Ejecutado 13 nov 2026Informe completo →Pasó nuestras verificaciones automáticas. Esto no es una auditoría de seguridad: lee el código y dale al servidor el acceso más limitado que necesite. Cómo funcionan las verificaciones
- Repositorio de origen vinculado y accesibleAprobado
- Licencia de código abierto (OSI)Aprobado
- Commit o lanzamiento en el último añoAprobado
- El nombre del paquete no es similar a un paquete popularAprobado
- El endpoint remoto usa HTTPSNo verificado
- Las herramientas destructivas están etiquetadasNo verificado
- Sin instrucciones ocultas para el modeloAprobado
- No hay avisos críticos en el paquete ni en sus dependenciasAprobado
README
Ver en GitHubwormhole-x402
Integration guide: payment safety, policy, confirmed top-ups and launch attestations.
Your agent is about to sign a payment. Is it the one it was quoted?
ALLOW pays the quoted merchant, quoted amount
REFUSE pays an ATTACKER instead
[X402-001] payment destination is not the account derived from the quote
REFUSE pays the merchant but 900x the amount
[X402-002] payment amount does not match the quoted amount
REFUSE pays correctly + silently approves a delegate
[X402-006] Approve — delegates spending authority over the account
ABSTAIN garbage bytes
could not deserialize — refusing to report it as safe
5 transactions checked in 5ms — no RPC, no network
The gap
Every transaction-security product answers the same question: what will this transaction do? Simulation, asset diffs, address reputation. All useful.
None of it helps an agent, because a payment to an attacker's address simulates perfectly. Correct balances. No revert. Clean verdict. The transaction is entirely valid — it is simply not the one that was asked for.
Nobody answers is this the transaction that was asked for?
In the leading provider's API that question is not merely unimplemented, it is
not expressible. Their Solana scan request carries origin — "DApp domain
proposing these transactions" — and has no field for the agent's instructions
at all. The stack is shaped around a human approving a website's request. A
headless agent has neither a website nor a human.
How it decides
One call, either chain
verify() takes a quote and the payment about to be signed, picks the rail from
the network string, and returns one verdict. It runs the quote-text scan first —
an injected instruction in a merchant's description is refused before its numbers
are ever treated as authoritative — then compares destination, asset and amount.
import { verify } from "wormhole-x402/verify";
const result = await verify(
{
network: "eip155:8453", // or "solana"
quote, // what the merchant's 402 said
payload, // what the agent is about to sign
},
{ quoteProvenance: "caller_asserted", issuedAt: new Date().toISOString() },
);
if (result.decision !== "allow") {
// refuse AND abstain both mean do not sign. "We could not check it" is
// never "it is fine" — that conflation is the bug this library exists for.
throw new Error(result.findings.map((f) => f.code).join(","));
}
Time is an input rather than read from the clock, so a verdict is a pure function of its request and replays identically later.
The other entry points
| Import | What it is |
|---|---|
wormhole-x402/verify |
verify() — the unified check, both rails |
wormhole-x402/client |
guardedPay() / guardedFetch() — one line in front of sign() |
wormhole-x402/receipt |
verifyReceipt() / replayMatches() — check a receipt offline |
wormhole-x402/server |
createVerifyHandler() — host it yourself |
wormhole-x402/metering |
usage accounting and cross-agent correlation |
wormhole-x402/mcp |
the verifier as an MCP tool server — npx wormhole-x402-mcp |
wormhole-x402/provenance |
X402-301 — was this payee introduced by anything legitimate? |
wormhole-x402/delivery |
X402-4xx — did the paid response deliver the quoted resource? |
wormhole-x402/tx |
X402-7xx — is this transaction the one the agent meant to send? |
guardedPay takes the transport as an argument rather than a URL, so the same
call runs against the local verifier or a remote one and the two cannot drift.
A verdict off a socket is a claim, not an answer
guardedPay and guardedFetch require an integrity option. It has no
default, because every default here is either a silent downgrade of your
security or a surprise change to your behaviour:
import { guardedPay } from "wormhole-x402/client";
import { publicKeyFromSpkiBase64 } from "wormhole-x402/receipt";
// The verifier publishes its key at GET <base>/v1/key
const publicKey = publicKeyFromSpkiBase64(publishedKeyBase64);
const res = await guardedPay({
network, quote, payload, transport,
integrity: { mode: "required", publicKey },
});
if (res.allow) await sign(payload);
Under mode: "required" a decisive verdict clears only if a receipt is present,
its signature verifies against your key, the receipt is bound by digest to the
request you actually sent, and the receipt's own decision agrees with the
envelope's. Anything else becomes an abstain — never an allow, and never a
refuse — with integrityFailure naming which check failed. res.verified tells
you whether the cryptography actually ran.
For an in-process transport that never crosses a network, say so explicitly:
integrity: { mode: "trusted_transport", reason: "in-process verify(), no socket" }
verified is then false, because nothing was checked. A verifier running
without a signing key produces abstains under required, which is the honest
answer: an unsigned verdict is one nobody can check.
The MCP server applies the same rule to hosted verdicts. Set
WORMHOLE_VERIFY_PUBKEY to the published key; without it a hosted allow is
downgraded to abstain unless you explicitly accept an unverifiable one with
WORMHOLE_ALLOW_UNSIGNED_HOSTED=1.
Receipts
A decisive verdict carries a receipt: the decision, the rule codes, a coarse amount band and a SHA-256 digest of the request — never the quote text, never the payment bytes. Signed, it is checkable by someone who does not trust you:
import { verifyReceipt, replayMatches } from "wormhole-x402/receipt";
verifyReceipt(receipt, signature, publicKey); // did we really say this?
replayMatches(receipt, request); // is it this exact request?
Neither call touches the network. That is the point of a receipt — an attestation you have to phone someone to check is just their word with extra steps.
Install
Every chain dependency is an optional peer, so you install only the one you
use — and you must install it. The entry point for each chain imports its own SDK
at the top level, so wormhole-x402 alone imports nothing useful:
# Solana
npm install wormhole-x402 @solana/web3.js @solana/spl-token
# EVM
npm install wormhole-x402 viem
# Text scanning only — no chain SDK needed
npm install wormhole-x402
Each entry point is independent. wormhole-x402/evm and
wormhole-x402/quotetext work with no Solana packages present, and
wormhole-x402 works with no viem. Importing an entry point whose SDK is not
installed is a module-resolution error, not a silent no-op.
As an MCP tool server
Agents that cannot be rewired — Claude Code, Cursor, any MCP host — get the
same checkpoint as a tool. Zero extra dependencies: the server is
newline-delimited JSON-RPC over stdio, spoken with Node's own readline.
# full install — verifies payments on both rails
claude mcp add x402-guard -- npx -y -p wormhole-x402 -p @solana/web3.js -p @solana/spl-token -p viem wormhole-x402-mcp
# lightest form — text scanning only; verify_payment abstains with install
# instructions until the chain SDKs for your rail are present
claude mcp add x402-guard -- npx -y wormhole-x402
Two tools:
verify_payment— network, quote, payload (and optionallyexpectedPayer) in;allow/refuse/abstainwith findings out. Anything that is notallowmeans do-not-sign.scan_text— a 402 body, a merchant listing, an agent card, a memo; scanned for injection shapes before the model reads it.
The server always starts and scan_text always works — the verify core
loads lazily, so a missing chain SDK is a per-call abstain naming the install
command, never a startup crash.
Set WORMHOLE_API_KEY and verification runs against the hosted verifier
instead, returning its answer verbatim — including any spend-policy decision
the operator's account enforces there. An unreachable hosted verifier abstains
rather than silently falling back to the local core: the operator configured
hosted mode to get their policy applied, kill switch included. Verdicts carry caller_asserted provenance — the server cannot see
where the quote came from, so its receipts say so rather than implying an
attestation nobody made.
import { guardSigner } from "wormhole-x402";
// `quote` is the PaymentRequirements from the server's HTTP 402 response.
const wallet = guardSigner(myWallet, () => currentQuote);
// Signing now throws unless the transaction matches the quote.
await wallet.signTransaction(tx);
On EVM the payment is a detached EIP-3009 authorization rather than a
transaction, so it has its own wrapper — same guarantee, async because domain
verification can read the chain:
import { guardEvmSigner } from "wormhole-x402/evm";
const signer = guardEvmSigner(myWalletClient, () => currentQuote);
// viem sends this single object; ethers sends the same parts positionally —
// signTypedData(domain, types, value) — and the wrapper recognises both. This
// is the call that CREATES the signature, so there is nothing to recover from
// yet. The wrapper checks the domain against the trusted table for (chainId,
// asset), requires the TransferWithAuthorization struct exactly as verified,
// and compares to, value, the validity window and the nonce to the quote.
await signer.signTypedData({ domain, types, primaryType, message });
The typed-data request is what the wallet signs, so it is the one checked, read where the signer reads it: viem's one request object, the first argument, or ethers' domain, types and value, the first three. A call that could be read either way, or that carries an already-signed payment beside a typed-data request, is refused, with the typed request's own findings when it has any: which one a wallet would sign is the wallet's choice, and one cannot be checked for the other.
Both wrappers are default-deny. Every function property is wrapped, not a
named subset: the ones the wrapper knows how to check are checked, and
everything else is refused rather than handed back. Object properties that
expose signing methods — viem's account, a wallet adapter's inner provider —
are wrapped too, because handing back the raw object is the same hole one level
down.
That means an unmodelled method is unusable rather than unchecked, which is the
trade worth making: "we did not recognise the call so we allowed it" is how
every bypass is written up afterwards. If a method cannot move funds, name it in
allow — an escape hatch with a name on it. Dotted paths work for nested
members (allow: ["account.sign"]).
No quote is a refusal, not a pass. Every optional security parameter with a permissive default ends up unset in production, and then the guard reports green on all traffic and nobody notices.
Or inspect without wrapping:
import { inspectPayment } from "wormhole-x402";
const verdict = inspectPayment(serializedTx, {
payTo: "merchant address from the 402 response",
asset: "token mint",
amount: "1000000", // base units, exact
});
// → { decision: "allow" | "refuse" | "abstain", findings: [...] }
The facilitator flow
Most agents never submit a transaction themselves — they build and partially
sign one (the facilitator pays the fee, which is what makes it feel gasless)
and ship it base64 inside the X-PAYMENT header. The client's key still
touches the bytes exactly once. These are those bytes:
import { inspectPaymentPayload, quoteFromRequirements } from "wormhole-x402";
// `accepts[0]` from the server's 402 response — never model-authored
const quote = quoteFromRequirements(paymentRequired.accepts[0]);
// the X-PAYMENT payload the client is about to sign/ship
const verdict = inspectPaymentPayload(xPaymentHeader, quote);
if (verdict.decision !== "allow") throw new Error(verdict.reason);
EVM (EIP-3009)
On EVM the agent does not sign a transaction — it signs an EIP-712 typed message authorizing the transfer, which a facilitator later submits. The same question applies to those bytes: does the authorization about to be signed match the quote? The EVM verifier lives at a separate entry point so a Solana-only install never pulls an EVM crypto library.
import { inspectAuthorization } from "wormhole-x402/evm";
// quote: { network: "eip155:8453", asset, payTo, amount } from the 402 response
// payload: { signature, authorization: { from, to, value, validAfter, validBefore, nonce } }
const verdict = await inspectAuthorization(quote, payload); // offline, no RPC
if (verdict.decision !== "allow") throw new Error(verdict.reason);
inspectAuthorization recovers the signer from the signature and confirms it is
the stated payer, then compares recipient, amount, token, and chain against the
quote. The EIP-712 domain is built from a curated (chainId, contract) table
of on-chain-verified values — never from the quote's extra, which is
attacker-influenceable. An unknown chain or token abstains rather than guesses.
It refuses standing-authority primitives — an Approve, an EIP-2612
Permit, a Permit2 allowance — that grant a spender ongoing access rather than
making a one-shot payment. This matters on EVM specifically: USDC on Base
implements EIP-2612, so a malicious unbounded Permit validates against the
same domain as a legitimate transfer; the only offline discriminator is the
EIP-712 type. viem is an optional peer dependency (npm install wormhole-x402 viem).
The validity window gets the same treatment as a standing authority, because a
long one is one: an authorization still valid next week can be collected next
week. Before signing (inspectTypedDataRequest, and so guardEvmSigner), the
window may run no longer than the quote's maxTimeoutSeconds plus ten minutes
of clock allowance, and never longer than a day; a quote that names no timeout
gets the day, and one whose timeout cannot be read gets no verdict. Past that,
X402-105 refuses. On a signed payment (inspectAuthorization, verify()), a
window over a day is reported as a medium X402-105 instead: the signature
already exists, and the payer's guard is the place to refuse it. The caller's
clockSkewSeconds widens the other window checks, never these caps.
evmQuoteFromRequirements carries the 402 entry's timeout as a whole number of
seconds, and leaves out any other value, so a converted quote's request digest
is what it was without it.
The one design constraint
Intent must never be something the agent states.
If intent is a field the model fills in, a compromised model fills in both sides of the comparison and validates its own forgery. That is worse than no check at all, because it manufactures confidence at the exact moment funds move irreversibly.
x402 is the case where this works cleanly. The recipient, amount and mint arrive as structured JSON in the server's HTTP 402 response — before the transaction exists, on a channel entirely separate from the model's context. That is a genuinely independent second input.
And the comparison is pure arithmetic: the destination token account derives
deterministically from (recipient, mint). No RPC required.
Preguntas sobre Agent Wormhole MCP
¿Cómo agrego Agent Wormhole MCP a Claude Code?
Ejecuta claude mcp add --env 'WORMHOLE_API_KEY=<YOUR_WORMHOLE_API_KEY>' wormhole-x402 -- npx -y wormhole-x402 en tu terminal. Añade --scope user para que esté disponible en todos los proyectos.
¿Cómo agrego Agent Wormhole MCP a Cursor?
Abre ~/.cursor/mcp.json y añade el servidor en mcpServers, usando la configuración de Cursor que se muestra en la sección de instalación de arriba.
¿Puedo usar esto para escanear descripciones de comercios?
Sí, la herramienta scan_text escanea cuerpos 402, listados de comercios o tarjetas de agentes en busca de patrones de inyección antes de que el modelo los lea.
¿El servidor requiere conexión a red para verificar transacciones?
No, puede realizar veredictos offline sin necesidad de llamadas RPC o de red.
¿Cómo gestiona el MCP server los veredictos alojados?
Aplica las mismas reglas que los veredictos locales; si no se ha configurado WORMHOLE_VERIFY_PUBKEY, un permiso (allow) alojado se degrada a abstención (abstain) a menos que se permita explícitamente.