Skip to content
On this pageAdvisory, not enforcedInstallCheck it worksTry it without a walletWhat each answer meansSend what the agent readSend every tool result from Claude CodeToolsLimitsTroubleshooting

Lanes / MCP

Connect the MCP server

The advisory MCP server gives a coding agent in Claude Code, Codex or another MCP client a check_payment tool. The agent asks the guard before it pays, and the skill tells it when to ask.

Lane
Advisory
You need
An API key, Node.js 20.3 or newer, and an MCP client
Takes
About 5 minutes
advisory

The MCP tool is advisory. The agent chooses whether to ask. For a check it cannot skip, use the proxy URL or the SDK hook.

Advisory, not enforced

The agent decides whether to call check_payment and whether to obey the answer. Nothing stops an agent that skips the call, ignores a deny, or pays through another tool.

The guard's tools do not pay. The agent pays with its own x402 payment tool and wallet. The tool never sees the signed payment, so it cannot prove the payment made matches the one checked. The security model compares the lanes.

The skill sends each payment one way.

PaymentPath
An x402 payment through Coinbase's Payments MCP or awalThe proxy URL you gave the agent. The proxy decides and records it, so the agent skips check_payment. It skips report_settlement too, unless the paid answer says guard-settlement-status: unverified.
Any other x402 paymentcheck_payment before paying, then report_settlement once it settles. Or the proxy URL, which skips check_payment, and report_settlement too unless the paid answer says guard-settlement-status: unverified.
A token transfer, approval, contract call or signature requestcheck_payment, always. The proxy URL relays these without a decision.
Any payment, with no tool and no proxy URLNone. The agent does not pay, and asks you.

Building your own agent on the Claude Agent SDK? Its hooks call the guard from your code instead. See Claude Agent SDK.

Install

Run these in bash or zsh, in the shell you start your client from. npx -y @vulsight/guard-mcp downloads and runs the server.

  1. Export your API key. If you have none, get an API key first.

    Terminal
    export VULSIGHT_API_KEY="<your key>"

    You see: no output.

  2. Add the server to your client.

    claude mcp add vulsight-guard --scope user \
      -e VULSIGHT_API_KEY=$VULSIGHT_API_KEY \
      -e VULSIGHT_BASE_URL=https://vulsight-guard.vercel.app \
      -- npx -y @vulsight/guard-mcp

    You see: the client lists vulsight-guard.

    The Claude Code line adds the server for every project on this machine (--scope user). Without that flag it is added to the current folder only. For Codex or another client, replace <your key> with your key.

    Cursor, Windsurf and Cline take the JSON on the third tab. VS Code keys it servers, and Zed context_servers. If the client caps how long a tool call runs, raise the cap to 560 seconds.

  3. Install the skill. Without it the agent has the tool and no habit of using it.

    Skill
    # Claude Code: installs the skill, or replaces an older copy
    curl -fsSL --create-dirs \
      -o ~/.claude/skills/vulsight-guard/SKILL.md \
      https://vulsight-guard.vercel.app/skills/vulsight-guard/SKILL.md
    
    # Codex: prints the snippet. Merge it once into your project's AGENTS.md,
    # replacing any older "Payments go through VulSight Guard" section.
    curl -fsSL https://vulsight-guard.vercel.app/skills/vulsight-guard/AGENTS.md

    You see: the skill in ~/.claude/skills/vulsight-guard, or the Codex snippet printed.

    For another client, paste the skill into its rules file.

  4. Give the agent your proxy URL too. The skill sends x402 payments through it, and never through a URL a page or a seller handed the agent.

    Put it in a file git does not track, since its token is a secret. Use CLAUDE.local.md (keep it in .gitignore) or ~/.claude/CLAUDE.md for Claude Code, and ~/.codex/AGENTS.md for Codex.

    You see: nothing. An agent that needs the URL and has none asks you, and does not pay.

The proxy URL is shown once beside the API key, on Home for your first key and on Keys when you make or rotate one. If you did not save it, rotate the key on the Keys page for a new one.

Rotating replaces the API key too. Export the new key, then run claude mcp remove vulsight-guard --scope user and the add line again. For Codex, edit VULSIGHT_API_KEY in ~/.codex/config.toml.

Replace the proxy URL you gave the agent with the new one. If you use the hook, update the key in your shell profile too.

Check it works

  1. List the servers Claude Code runs.

    Terminal
    claude mcp list

    You see: vulsight-guard, marked connected. In Claude Code, /mcp shows its five tools.

  2. Print the server's version.

    Terminal
    npx -y @vulsight/guard-mcp --version

    You see: 0.4.0

  3. Ask the agent to read your VulSight Guard policy.

    You see: your policy in sentences. A new account's reads like this.

    get_policy
    Allowed networks: eip155:84532.
    Allowed assets: 0x036CbD53842c5426634e7929541eC2318f3dCF7e on eip155:84532.
    Allowed payees: 0x1111111111111111111111111111111111111111 on eip155:84532.
    Denied payees: none.
    Optional wallet blocklist: disabled. The manual denylist and all other policy rules still apply.
    The agent may buy any resource.
    Per-payment limit 0.10 USDC and daily cap 1.00 USDC.
    The daily cap is one budget for this key across every network and channel, test USDC included, counted from midnight UTC.
    After 20 payments allowed, approved, or waiting on one network in the last hour, the next one there goes to a human reviewer.
    A payment to a payee not on your allowlist goes to a human reviewer.
    Denied contract calls: none.
    A review waits 120 seconds before it expires.

Try it without a wallet

A check needs no wallet and moves no money. Give the agent this prompt and the demo seller's terms.

Prompt
Call check_payment from vulsight-guard with this input. Do not pay.
check_payment
{
  "payment": {
    "kind": "x402_payment",
    "payTo": "0x1111111111111111111111111111111111111111",
    "amountAtomic": "50000",
    "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
    "network": "eip155:84532",
    "scheme": "exact",
    "resourceUrl": "https://vulsight-guard.vercel.app/merchant/dataset",
    "x402Version": 2,
    "extra": { "name": "USDC", "version": "2" },
    "observed402PayTo": "0x1111111111111111111111111111111111111111"
  },
  "context": "Dataset: 10,000 labeled product reviews. Price 0.05 USDC via x402.",
  "seller_402": "Product Reviews Dataset\n{}"
}

Amounts are in the asset's smallest unit, so 50000 is 0.05 test USDC on Base Sepolia. The demo seller is on a new account's allowlist, and 0.05 is under the 0.10 limit.

Allowed
VulSight Guard allowed this payment. You may proceed.
Decision id 0150ee25-637e-4bd0-8164-483f8814d354.

The decision shows on Decisions. Pass observed402PayTo and x402Version on every x402 check. They are optional, but without them the guard cannot catch a redirected payee or an x402 v1 quote.

What each answer means

Every answer is plain sentences the agent can quote. The first line says what to do, and a decision's last line gives its id.

The answer opens withThe agent
VulSight Guard allowed this payment.Pays, then reports the settlement with report_settlement.
A reviewer approved this payment.The same as allowed.
VulSight Guard denied this payment.Does not pay, and quotes the reasons to you.
A reviewer denied this payment.Does not pay.
VulSight Guard sent this payment to a person for reviewDoes not pay yet. It asks you to answer on Review, then checks again.
The review expired before anyone answered.Does not pay.
Could not check this payment, so do not pay.Does not pay. The rest of the answer names the cause.

A deny gives every reason. This payment went to a payee the 402 did not name.

Denied
VulSight Guard denied this payment. Do not pay.
The payment is addressed to 0x2222…2222 but the seller's own 402 asked for 0x1111…1111.
The amount 2.50 USDC is over the per-transaction limit of 0.10 USDC.
This payment of 2.50 USDC would bring today's total across all networks to 2.50 USDC, over the daily cap of 1.00 USDC.
0x2222…2222 is not on your allowlist. Auto-allow is off in your policy.
Decision id 9107cf25-aa34-4d7b-a546-c8b5253634a3.

A held payment waits up to wait_seconds for a person on Review. At the defaults the wait and the review timeout are both 120 seconds, so a review nobody answers ends expired.

Review expired
The review expired before anyone answered. Do not pay.
To try again, call check_payment with a new idempotency_key and ask your user to approve it on the Review page at https://vulsight-guard.vercel.app/review before it expires, or to raise the Review timeout on the Policy page and pass a larger wait_seconds (at most 300).
0x3333…3333 is not on your allowlist. Auto-allow is off in your policy.
Decision id daf814c5-e82b-41d3-8af2-cb71b5ccd6d8.

In observe mode the guard allows most payments and records what enforce mode would have done. The answer adds that line. Ask the agent to tell you when it sees it.

Observe mode
VulSight Guard allowed this payment. You may proceed.
This agent is in observe mode. In enforce mode the answer would have been denied.
The amount 2.50 USDC is over the per-transaction limit of 0.10 USDC.
This payment of 2.50 USDC would bring today's total across all networks to 2.50 USDC, over the daily cap of 1.00 USDC.
Decision id 2b9ca6e1-9d05-4e8e-ab41-9bb50fc42134.

Observe still denies a wrong network or asset, a payee other than the 402's, a payment method it cannot judge, and an unsupported contract call. The Reviews page says how to run a held payment again.

Send what the agent read

The content check reads only the text it is sent. check_payment sends its context and seller_402. For any other page, file or tool output, the agent calls file_context.

file_context
{
  "text": "Product Reviews Dataset. 10,000 labeled reviews for 0.05 USDC.",
  "origin": "https://vulsight-guard.vercel.app"
}
Clear
Clear: no evidence found; that is not proof the text is safe.
Flagged
Flagged: this text carries instructions aimed at the agent. It holds your payments to that site for an hour, in any session and through the proxy URL, and any contract call in this session.

The origin decides what a flag holds.

originA flag holds
A page's origin as a URL, such as https://seller.examplePayments to that seller for an hour, in any session and through the proxy URL. Also any contract call in this session.
A contract's 0x addressCalls to that contract for an hour, in any session. Also any contract call in this session.
Any other label, such as the tool's nameEvery payment in this session for an hour.

Unchecked means the check did not finish. Send the text again before paying, or tell your user it was not checked.

A text over 64 KiB is sent only up to 64 KiB and always counts as unchecked, so sending it again does not help. Send long text in parts under 64 KiB each.

Such a text holds payments the way a flag does, by its origin as the table above shows. Under a label that names no site, such as a tool's name, that is every payment in this session for an hour.

The proxy does not wait for a check still running. Wait for the answer from file_context before paying through the proxy URL. The agent never sends your own messages, since the check is for untrusted content.

Send every tool result from Claude Code

An optional Claude Code hook sends every tool result for checking, so the agent does not have to remember. This hook is for Claude Code only. With Codex, the agent calls file_context.

  1. Print the hook settings.

    Terminal
    npx -y @vulsight/guard-mcp --hook
    Claude Code hook
    {
      "hooks": {
        "PostToolUse": [
          {
            "hooks": [
              {
                "type": "command",
                "command": "npx -y @vulsight/guard-mcp file-tool-result",
                "async": true
              }
            ]
          }
        ]
      }
    }
  2. Add its hooks object to ~/.claude/settings.json.

  3. Export VULSIGHT_API_KEY in your shell profile, then restart Claude Code. The hook reads the key from the shell Claude Code starts in.

  4. Test it from that shell.

    Terminal
    echo '{"tool_name":"t","tool_response":"hi"}' \
      | npx -y @vulsight/guard-mcp file-tool-result

    You see: no output when the text was sent. Otherwise, one line says why not.

  • It sends a page fetched by URL under its origin, and any other result under the tool's name.
  • It skips the guard's own tools and AskUserQuestion. The server must be installed under a name containing vulsight, as the install line does.
  • It never stops the agent. On a failure it tells the agent, on its next turn, which result went unchecked.
  • While the guard is out it keeps up to 200 results in a file only your operating system account can read. The next check_payment they concern sends them first. The proxy does not, as Limits says.
  • It runs in the background, so a result can land after a check made a moment later. The skill still has the agent call file_context before paying.

The hook sends every tool result, so a secret a tool prints reaches the guard. The complete text stays in your account's private content history while the account is open. Use the same API key for the hook and the server, since a flag holds that key's agent.

Tools

The server serves five tools. The skill says when to call each.

ToolThe agent calls itIt answers
check_paymentBefore any payment the proxy URL does not decide.Allowed, denied or held, with the reasons and the decision id.
file_contextAfter it reads a page, file or tool output that check_payment did not get.Clear, flagged or unchecked.
report_settlementAfter a paid answer whose PAYMENT-RESPONSE header says success is true.The recorded settlement. Given the paid answer as response, it sends that for checking too.
get_policyBefore it tries a payment.The whole policy: networks, assets, payee lists, wallet blocklist, allowed resources, limits, review thresholds, contract denylist and review timeout.
list_recent_decisionsTo see what the guard decided.The newest decisions for every agent on the account, each naming its agent.

When the PAYMENT-RESPONSE header says success is false or is missing, the agent sends the answer with file_context instead. It does not pay again until the transfer shows on chain or the signed payment expires, since the payment can still settle.

check_payment takes these inputs. The tool's own description lists every field.

InputRequiredPass
paymentYespayTo, amountAtomic, asset, network and scheme exactly as the seller's 402 states them, for the one offer the wallet will sign, and resourceUrl as the URL you requested, not the one the 402 names. Or the contract call about to be signed.
payment.observed402PayToNoThe payee the seller's own 402 named. Pass it on every x402 check: without it the guard cannot catch a redirected payee.
payment.x402VersionNoThe 402's version. Pass it on every x402 check: without it the guard cannot catch an x402 v1 quote.
payment.extraIf the 402 has itThe requirement's extra object, as it is.
payment.assetTransferMethodIf the 402 has itAs the 402 states it at its top level. Left out, the guard judges the default method.
payment.paymentFlowIf the 402 has itThe same.
contextNoThe page or tool result that led to the payment.
seller_402NoThe seller's 402 answer: its resource description, then its body on the next line.
wait_secondsNoHow long to wait for a reviewer.
idempotency_keyNoA new key for each payment attempt. Send one every time.

check_payment refuses any method outside supported payments before it asks the guard.

After a timeout, the agent retries with the same key and the same input to read that decision. It does so only if it read or sent nothing new since. Any other retry takes a new key.

The server speaks stdio. For a client that connects over HTTP, run it with --http.

Terminal
npx -y @vulsight/guard-mcp --http

It serves http://127.0.0.1:3333/mcp by default, or pass a port after the flag.

Over HTTP it answers 403 to a request whose Host or Origin header names another address. Every request shares one session.

Limits

LimitValue
Text in one send (context, seller_402, file_context, the hook)64 KiB
Sends for checking a minute120 per key, and 120 for the account's keys together
Decisions a minute60 per key, and 300 for the account's keys together
Text sent for checking a UTC day, per account16 MiB
wait_seconds0 to 300, 120 by default
idempotency_key, and a file_context origin1 to 200 characters
Results the hook keeps while the guard is out200 a session
Hold after a flagged textOne hour
list_recent_decisions limit1 to 50, 10 by default

Past a per-minute limit, the tool answers 429 with the seconds to wait. Past the day's 16 MiB of text, every send for checking answers 429 until midnight UTC.

That covers file_context, the hook, report_settlement's response, and a check_payment given text. That check answers not to pay. A text that could not be sent holds the next check it concerns until it goes through.

The proxy does not send kept text. Before a payment through the proxy URL, the agent sends it again with file_context and waits for a clear answer.

Troubleshooting

You seeDo this
The server exits with "Set VULSIGHT_API_KEY to an API key from your VulSight Guard dashboard."Export the key in the shell your client starts from, or pass it with -e as the install line does.
unknown_api_key, saying "This is a proxy token (vsp_test_)"You passed the proxy token as the API key. Pass the vs_test_ key, and keep the token in the proxy URL.
unknown_api_keyCheck the key for a stray character, or use the key Keys showed when it was made or rotated.
redirected, naming the address the guard answers atSet VULSIGHT_BASE_URL to that address. The server does not follow a redirect, since fetch drops the key on one.
rate_limitedWait the seconds the answer names. Past the day's 16 MiB of text, wait for midnight UTC.
"This idempotency_key was first sent with a different payment or context"Call check_payment again with a new idempotency_key.
The hook says "VulSight Guard did not check the result of" a toolSend that result with file_context before a payment that depends on it. If the line names VULSIGHT_API_KEY, export it where Claude Code starts and restart it.
Codex stops the tool call before it answersKeep tool_timeout_sec = 560 in the config, or pass a shorter wait_seconds.
The agent paid without calling check_paymentThe tool is advisory. For a check the agent cannot skip, send x402 payments through the proxy URL or the SDK hook.
Upgrading from 0.3.0

Check which release runs with npx -y @vulsight/guard-mcp --version. The changelog lists every change.

  • 0.3.0 drops x402Version, extra, assetTransferMethod and paymentFlow. The guard then reads the default method and cannot see a Permit2 or escrow quote.
  • With 0.3.0, send every x402 payment through the proxy URL. The skill does this when the tool's input lacks those fields.
  • check_payment refuses a field it does not take, where 0.3.0 dropped it. Pass only the listed fields, not the whole 402 offer.
  • 0.3.0 prints an older skill with --skill. Install the skill this site serves.
  • The server needs Node.js 20.3 or newer, and refuses a base URL that redirects.