feat: add x402_fetch tool#12
Conversation
|
Warning Review limit reached
More reviews will be available in 34 minutes and 36 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (3)
📝 WalkthroughWalkthroughA new Changesx402_fetch MCP Tool
Sequence Diagram(s)sequenceDiagram
participant Caller
participant x402_fetch
participant WalletKeystore
participant x402Client
participant GatedAPI
Caller->>x402_fetch: { address, password, url }
x402_fetch->>WalletKeystore: unlock wallet (address, password)
WalletKeystore-->>x402_fetch: privateKey
x402_fetch->>x402Client: createInjectiveClient(privateKey)
x402Client-->>x402_fetch: client instance
x402_fetch->>GatedAPI: fetch(url)
GatedAPI-->>x402_fetch: 402 Payment Required
x402_fetch->>GatedAPI: sign & submit USDC payment, retry
GatedAPI-->>x402_fetch: 200 OK + body
x402_fetch-->>Caller: { status, url, data }
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
| address: injAddress.describe('The inj1... address of your trading wallet.'), | ||
| password: z.string().describe('Keystore password to decrypt the private key for signing.'), | ||
| url: z.string().url().describe('The URL of the x402-gated API endpoint.'), |
There was a problem hiding this comment.
in your PR for feat: add injective-x402-payment skill, there's meant to be "- maxAmount: (Optional) A safety limit on the maximum USDC amount you are willing to pay for this request."
see: https://github.com/InjectiveLabs/agent-skills/pull/14/changes#diff-36232111dee9b5d68812269d94f3c10a6c8b9aa93e6ecdc6b3fff44ddd1b67f8R31
but that's missing form here.
this is a feature that needs to be implemented deterministically, and therefore should be in the MCP server, not the agent skill. currently, it is implemented in neither.
| import { evm } from '../evm/index.js' | ||
| import { eip712 } from '../evm/eip712.js' | ||
| import { authz, TRADING_MSG_TYPES } from '../authz/index.js' | ||
| import { createInjectiveClient } from '@injectivelabs/x402/client' |
There was a problem hiding this comment.
The name of this method in this context is likely to confuse.
Better import as x402CreateClient or similar.
| }, | ||
| async ({ address, password, url }) => { | ||
| const privateKeyHex = wallets.unlock(address, password) | ||
| const client = createInjectiveClient({ privateKey: privateKeyHex as `0x${string}` }) |
There was a problem hiding this comment.
for clarity: rename client -> x402Client
There was a problem hiding this comment.
additionally, you will need to implement the maxAmount check around this point.
There was a problem hiding this comment.
Pull request overview
This PR introduces an MCP tool (x402_fetch) that can call x402-gated HTTP endpoints and automatically settle 402 Payment Required invoices by signing/submitting USDC payments using a locally decrypted Injective EVM private key.
Changes:
- Added
@injectivelabs/x402dependency to enable the x402 client wrapper. - Registered a new MCP tool
x402_fetchinsrc/mcp/server.tsthat performs the paid fetch flow and returns the response payload.
Reviewed changes
Copilot reviewed 2 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| src/mcp/server.ts | Adds the x402_fetch MCP tool and imports the x402 client wrapper. |
| package.json | Adds the @injectivelabs/x402 dependency. |
| package-lock.json | Locks @injectivelabs/x402@0.0.1 and its transitive dependencies/engine requirement. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let data; | ||
| try { | ||
| data = JSON.parse(text) | ||
| } catch { | ||
| data = text | ||
| } |
| const privateKeyHex = wallets.unlock(address, password) | ||
| const client = createInjectiveClient({ privateKey: privateKeyHex as `0x${string}` }) | ||
| const response = await client.fetch(url) | ||
|
|
||
| const text = await response.text() |
| 'x402_fetch', | ||
| 'Fetch data from an x402-gated API endpoint. Automatically handles 402 Payment Required ' + | ||
| 'responses by signing a USDC payment using the Injective EVM wallet, submitting it to the facilitator, ' + | ||
| 'and retrying the request. IMPORTANT: Real on-chain payment with real funds.', | ||
| { |
| server.tool( | ||
| 'x402_fetch', | ||
| 'Fetch data from an x402-gated API endpoint. Automatically handles 402 Payment Required ' + | ||
| 'responses by signing a USDC payment using the Injective EVM wallet, submitting it to the facilitator, ' + | ||
| 'and retrying the request. IMPORTANT: Real on-chain payment with real funds.', | ||
| { | ||
| address: injAddress.describe('The inj1... address of your trading wallet.'), | ||
| password: z.string().describe('Keystore password to decrypt the private key for signing. SECURITY: Never log, store, or echo this. Use secret inputs only.'), | ||
| url: z.string().url().describe('The URL of the x402-gated API endpoint.'), | ||
| }, |
| "@injectivelabs/networks": "^1.14.27", | ||
| "@injectivelabs/sdk-ts": "^1.14.27", | ||
| "@injectivelabs/utils": "^1.14.27", | ||
| "@injectivelabs/x402": "^0.0.1", |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/mcp/server.ts`:
- Around line 823-828: The x402 tool handler in server.ts currently accepts any
URL via z.string().url() and passes it directly into client.fetch, which leaves
a caller-controlled fetch target. Add a pre-fetch validation step in the async
handler that restricts the URL to an HTTPS-only allowlist or equivalent approved
egress policy before calling createInjectiveClient and client.fetch, and reject
any non-HTTPS or unapproved host values with a clear error.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 32787191-431b-4e3c-99b7-3019fe312ceb
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (2)
package.jsonsrc/mcp/server.ts
| url: z.string().url().describe('The URL of the x402-gated API endpoint.'), | ||
| }, | ||
| async ({ address, password, url }) => { | ||
| const privateKeyHex = wallets.unlock(address, password) | ||
| const client = createInjectiveClient({ privateKey: privateKeyHex as `0x${string}` }) | ||
| const response = await client.fetch(url) |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
Constrain the fetch target before calling the x402 client.
z.string().url() still allows a caller-controlled server-side fetch target. Because Line 828 sends that URL from the MCP server, this can become SSRF and can also allow non-HTTPS payment flows unless hosts/schemes are restricted. Add an HTTPS-only allowlist or equivalent egress policy before client.fetch.
🛡️ Possible direction
+const allowedX402Hosts = new Set(
+ (process.env.X402_ALLOWED_HOSTS ?? '')
+ .split(',')
+ .map((host) => host.trim().toLowerCase())
+ .filter(Boolean),
+)
+
+const x402EndpointUrl = z.string().url().refine((value) => {
+ const parsed = new URL(value)
+ return parsed.protocol === 'https:' && allowedX402Hosts.has(parsed.hostname.toLowerCase())
+}, 'Must be an HTTPS URL on an allowed x402 host')
+
server.tool(
@@
- url: z.string().url().describe('The URL of the x402-gated API endpoint.'),
+ url: x402EndpointUrl.describe('The URL of the x402-gated API endpoint.'),📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| url: z.string().url().describe('The URL of the x402-gated API endpoint.'), | |
| }, | |
| async ({ address, password, url }) => { | |
| const privateKeyHex = wallets.unlock(address, password) | |
| const client = createInjectiveClient({ privateKey: privateKeyHex as `0x${string}` }) | |
| const response = await client.fetch(url) | |
| const allowedX402Hosts = new Set( | |
| (process.env.X402_ALLOWED_HOSTS ?? '') | |
| .split(',') | |
| .map((host) => host.trim().toLowerCase()) | |
| .filter(Boolean), | |
| ) | |
| const x402EndpointUrl = z.string().url().refine((value) => { | |
| const parsed = new URL(value) | |
| return parsed.protocol === 'https:' && allowedX402Hosts.has(parsed.hostname.toLowerCase()) | |
| }, 'Must be an HTTPS URL on an allowed x402 host') | |
| url: x402EndpointUrl.describe('The URL of the x402-gated API endpoint.'), | |
| async ({ address, password, url }) => { | |
| const privateKeyHex = wallets.unlock(address, password) | |
| const client = createInjectiveClient({ privateKey: privateKeyHex as `0x${string}` }) | |
| const response = await client.fetch(url) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/mcp/server.ts` around lines 823 - 828, The x402 tool handler in server.ts
currently accepts any URL via z.string().url() and passes it directly into
client.fetch, which leaves a caller-controlled fetch target. Add a pre-fetch
validation step in the async handler that restricts the URL to an HTTPS-only
allowlist or equivalent approved egress policy before calling
createInjectiveClient and client.fetch, and reject any non-HTTPS or unapproved
host values with a clear error.
Overview
This PR adds native x402 payment capabilities to the Injective MCP server, allowing AI agents to fetch gated API
endpoints and seamlessly pay the
402 Payment Requiredinvoice using their local keystore.Changes Made
@injectivelabs/x402to utilize thecreateInjectiveClientHTTP wrapper.x402_fetchtool tosrc/mcp/server.ts.How it Works
When an agent calls
x402_fetch(address, password, url):1. The server decrypts the local EVM private key.
2. It initiates a fetch request to the gated endpoint.
3. If the server responds with a 402 and a price quote, the client automatically signs an EIP-3009 USDC
authorization.
4. The signed payment is submitted to the x402 facilitator to settle on Injective EVM.
5. The original request is retried with the payment receipt, successfully returning the underlying data back to
the AI.
Summary by CodeRabbit