Spec Driven Development
MCP Server that facilitates spec-driven development workflows, not just Vibe Coding.
作者による説明(英語の原文)
Check tokens, pages and x402 payments before your agent trusts them. Offline verdicts.
この3つを1つのスコアにまとめることはしません。意見、測定、人気はそれぞれ別のものだからです。
このサーバーのやさしい解説はまだ書いていません。以下はレジストリから読み取った事実です。
使っているAIアプリを選んで、設定をコピーしてください。
WORMHOLE_API_KEY, WORMHOLE_VERIFY_URL, WORMHOLE_ADDRESS_LEDGER「秘密」と表示された値は、あなたのアカウントの鍵です。絶対に人に教えたり、チャットに貼り付けたりしないでください。
必要なもの
どのアプリを使っていますか?
上でアプリを選んでください。選んだアプリを覚えておくので、次からはボタンに「…にインストール」と表示されます。
Claude Desktop → Settings → Developer → Edit Config を開き、claude_desktop_config.json にこれを貼り付けます
{
"mcpServers": {
"wormhole-x402": {
"command": "npx",
"args": [
"-y",
"wormhole-x402"
],
"env": {
"WORMHOLE_API_KEY": "your-value",
"WORMHOLE_VERIFY_URL": "your-value",
"WORMHOLE_ADDRESS_LEDGER": "your-value"
}
}
}
}ターミナルでこれを実行します
claude mcp add wormhole-x402 --env WORMHOLE_API_KEY=your-value --env WORMHOLE_VERIFY_URL=your-value --env WORMHOLE_ADDRESS_LEDGER=your-value -- npx -y wormhole-x402動かす前に、空欄の値を入力してください。
プロジェクトの .cursor/mcp.json、または ~/.cursor/mcp.json に貼り付けます
{
"mcpServers": {
"wormhole-x402": {
"command": "npx",
"args": [
"-y",
"wormhole-x402"
],
"env": {
"WORMHOLE_API_KEY": "your-value",
"WORMHOLE_VERIFY_URL": "your-value",
"WORMHOLE_ADDRESS_LEDGER": "your-value"
}
}
}
}ブラウザがアプリを開いてよいか確認し、そのあとアプリが設定を表示して確認を求めます。キーの値は空欄のままなので、自分で入力してください。
プロジェクトの .vscode/mcp.json に貼り付けます
{
"servers": {
"wormhole-x402": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"wormhole-x402"
],
"env": {
"WORMHOLE_API_KEY": "your-value",
"WORMHOLE_VERIFY_URL": "your-value",
"WORMHOLE_ADDRESS_LEDGER": "your-value"
}
}
}
}ブラウザがアプリを開いてよいか確認し、そのあとアプリが設定を表示して確認を求めます。キーの値は空欄のままなので、自分で入力してください。
これはパソコンで動くサーバーですが、ChatGPTがつなげるのはインターネット上のサーバーだけです。ほかのアプリを選んでください。
選んだアプリは、この端末のCookieに保存されます。アカウントは不要です。
WORMHOLE_VERIFY_URL envWORMHOLE_ADDRESS_LEDGER envこの設定は、作者のレジストリ登録情報から作成しています。READMEと違う場合は、READMEに従ってください。
パソコンで動くサーバーです。インストールすると、ツール一覧がAIアプリに表示されます。
誰でも見られる情報をチェックしており、コードは読んでいません。当てはまらない項目は数に入れていません。 · 2026年11月6日にチェック
レジストリ名は、リポジトリを所有するGitHubアカウント runningoffcode で確認済みです
このリポジトリはまだGitHubから読み取っていません
このリポジトリはまだGitHubから読み取っていません
これはパソコンで動くため、インターネット上のエンドポイントはありません
パソコンで動き、代わりにローカルの設定を使います
パソコンで動くサーバーです。ツール一覧を読み取れるのは、インターネット上のエンドポイントがある場合だけです
レジストリでこのカテゴリにある有効なすべてのサーバーと比較しています。スターは、公開者がリポジトリの所有者である場合だけ数えます。
当てはまる基本チェック1項目のうち1項目に合格しました。コードは読んでいません。キーやトークンを追加する前に、サーバーがどんなアクセスを求めているかを確認し、必要な権限だけを持つキーを使ってください。
入力が必要なもの:WORMHOLE_API_KEY
あなたのパソコンで動きます。必要なときにAIアプリが起動し、データは作者のサーバーを通りません(サーバー自体がオンラインサービスを呼び出す場合を除きます)。
GitHubリポジトリの最終更新は–、最新バージョン(0.8.0)がレジストリに届いたのは2 か月前です。
Claude Desktop、Claude Code、Cursor、VS CodeなどのMCP対応アプリで使えます。パソコンにNode.jsも必要です。
同じカテゴリには:Spec Driven Development, Remote Desktop Commander, Symantec Cloud DLP AIops
同じカテゴリで、ほかの作者が作ったサーバー。スターの多い順。
カードの見方:★ GitHubスターi3つの柱:レビュー · チェック · 利用状況i更新=最新バージョンをレジストリに公開した日i
MCP Server that facilitates spec-driven development workflows, not just Vibe Coding.
Remote Desktop Commander: use files, terminal and processes on your own computers from any AI app.
Governed Symantec Cloud DLP — 14 MCP tools, read-only with audit.
作者のリポジトリから書かれたとおりにコピーしたものです。PopNx MCPは内容を確認・推奨していません。HTML、画像、スクリプトは取り除いています。リンクはこのサイトの外へ移動します。
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
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.
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.
| 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.
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 /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.
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.
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
npm install wormhole-x402 viem
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.
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
claude mcp add x402-guard -- npx -y wormhole-x402
Two tools:
expectedPayer) in; allow / refuse / abstain with findings out. Anything that is not allow means do-not-sign.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(
冒頭だけを表示しています。続きはリポジトリで読めます