@kaditang/402sentinel-mcp 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.js +37 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ Tools — vet the **seller**:
|
|
|
14
14
|
- `report_outcome` (free) — after paying, report delivery to train the reliability flywheel
|
|
15
15
|
|
|
16
16
|
Tools — vet the **payment itself** (buyer-side):
|
|
17
|
-
- `firewall` ($0.002) — should YOUR agent make THIS payment now? Catches fraudulent routing (payTo swapped vs the address you usually pay), drain velocity, overcharge, and injection-sourced instructions.
|
|
17
|
+
- `firewall` ($0.002) — should YOUR agent make THIS payment now? Catches fraudulent routing (payTo swapped vs the address you usually pay), drain velocity, overcharge, and injection-sourced instructions. `agent_id` + a wallet-ownership signature are attached automatically from your configured wallet — trusted routing history with no extra steps.
|
|
18
18
|
- `firewall_record` (free) — seed your agent's payment history so the firewall has a behavioural baseline.
|
|
19
19
|
- `firewall_outcome` (free) — after a verdict, report what actually happened (fraud / legit / …) so the firewall learns which signals are predictive and downweights noisy ones (safety signals stay deterministic).
|
|
20
20
|
|
package/dist/index.js
CHANGED
|
@@ -24,8 +24,27 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
|
24
24
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
25
25
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
26
26
|
import { GatewayClient } from "@circle-fin/x402-batching/client";
|
|
27
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
27
28
|
const BASE = (process.env.SENTINEL_URL ?? "https://402sentinel.com").replace(/\/$/, "");
|
|
28
29
|
const RAW_PK = process.env.CLIENT_PRIVATE_KEY ?? "";
|
|
30
|
+
const PK = (!RAW_PK || RAW_PK.startsWith("0xYour"))
|
|
31
|
+
? null
|
|
32
|
+
: (RAW_PK.startsWith("0x") ? RAW_PK : `0x${RAW_PK}`);
|
|
33
|
+
const account = PK ? privateKeyToAccount(PK) : null;
|
|
34
|
+
// Tools whose writes build TRUSTED history → auto-attach a wallet-ownership proof.
|
|
35
|
+
// SAFETY: we sign ONLY this exact namespaced template (never arbitrary or server-
|
|
36
|
+
// supplied content — no blind signing). It's an EIP-191 message signature, NOT a
|
|
37
|
+
// transaction: it can't move funds, isn't replayable as a tx, and only proves the
|
|
38
|
+
// signer controls its OWN agent_id. Scoped, harmless.
|
|
39
|
+
const SIGN_TOOLS = new Set(["firewall", "firewall_record", "firewall_outcome"]);
|
|
40
|
+
async function ownerProof(action) {
|
|
41
|
+
if (!account)
|
|
42
|
+
return {};
|
|
43
|
+
const owner_ts = Math.floor(Date.now() / 1000);
|
|
44
|
+
const agent = account.address.toLowerCase();
|
|
45
|
+
const owner_sig = await account.signMessage({ message: `402sentinel:${action}:${agent}:${owner_ts}` });
|
|
46
|
+
return { owner_sig, owner_ts };
|
|
47
|
+
}
|
|
29
48
|
const targetSchema = {
|
|
30
49
|
type: "object",
|
|
31
50
|
required: ["payto_address"],
|
|
@@ -113,10 +132,10 @@ const TOOLS = [
|
|
|
113
132
|
},
|
|
114
133
|
{
|
|
115
134
|
name: "firewall",
|
|
116
|
-
description: "Buyer-side payment firewall: should YOUR agent make THIS payment now? Where assess_counterparty vets the seller, this vets the payment instruction in the context of your agent's own history + provenance. Returns allow/hold/block + signals: routing_anomaly (payTo swapped vs the address you usually pay = fraudulent routing), velocity_anomaly (drain), amount_anomaly (overcharge), provenance_flag, counterparty_risk, injection_destination (if the payTo appears in the untrusted page/tool-output you're acting on, the destination was injected — pass it as context.untrusted_text), intent_mismatch (pass context.intended={payto,max_amount} so a mid-flight redirect is caught), new_counterparty_burst, recurring_flagged (poisoned-memory loop). STRONGLY recommended: pass untrusted_text + intended to catch prompt-injection payments.
|
|
135
|
+
description: "Buyer-side payment firewall: should YOUR agent make THIS payment now? Where assess_counterparty vets the seller, this vets the payment instruction in the context of your agent's own history + provenance. Returns allow/hold/block + signals: routing_anomaly (payTo swapped vs the address you usually pay = fraudulent routing), velocity_anomaly (drain), amount_anomaly (overcharge), provenance_flag, counterparty_risk, injection_destination (if the payTo appears in the untrusted page/tool-output you're acting on, the destination was injected — pass it as context.untrusted_text), intent_mismatch (pass context.intended={payto,max_amount} so a mid-flight redirect is caught), new_counterparty_burst, recurring_flagged (poisoned-memory loop). STRONGLY recommended: pass untrusted_text + intended to catch prompt-injection payments. agent_id and a wallet-ownership signature are attached AUTOMATICALLY from your configured wallet — you don't pass agent_id, and your routing history is trusted with no extra steps. Costs $0.002. Seed history free with firewall_record.",
|
|
117
136
|
inputSchema: {
|
|
118
137
|
type: "object",
|
|
119
|
-
required: ["
|
|
138
|
+
required: ["payment"],
|
|
120
139
|
properties: {
|
|
121
140
|
agent_id: { type: "string", description: "stable id for your agent — use your payer wallet address" },
|
|
122
141
|
payment: {
|
|
@@ -156,10 +175,10 @@ const TOOLS = [
|
|
|
156
175
|
},
|
|
157
176
|
{
|
|
158
177
|
name: "firewall_record",
|
|
159
|
-
description: "FREE. Seed your agent's payment history so the firewall has a behavioural baseline (record past/known-good payments).
|
|
178
|
+
description: "FREE. Seed your agent's payment history so the firewall has a behavioural baseline (record past/known-good payments). agent_id and the wallet-ownership signature are attached automatically from your configured wallet, so the seeded history is trusted.",
|
|
160
179
|
inputSchema: {
|
|
161
180
|
type: "object",
|
|
162
|
-
required: ["
|
|
181
|
+
required: ["payment"],
|
|
163
182
|
properties: {
|
|
164
183
|
agent_id: { type: "string", description: "use your payer wallet address" },
|
|
165
184
|
payment: {
|
|
@@ -193,13 +212,12 @@ const TOOLS = [
|
|
|
193
212
|
},
|
|
194
213
|
];
|
|
195
214
|
function clientOrNull() {
|
|
196
|
-
if (!
|
|
215
|
+
if (!PK)
|
|
197
216
|
return null;
|
|
198
|
-
|
|
199
|
-
return new GatewayClient({ chain: "base", privateKey: pk });
|
|
217
|
+
return new GatewayClient({ chain: "base", privateKey: PK });
|
|
200
218
|
}
|
|
201
219
|
async function main() {
|
|
202
|
-
const server = new Server({ name: "402sentinel", version: "0.
|
|
220
|
+
const server = new Server({ name: "402sentinel", version: "0.6.0" }, { capabilities: { tools: {} } });
|
|
203
221
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
204
222
|
tools: TOOLS.map(({ name, description, inputSchema }) => ({ name, description, inputSchema })),
|
|
205
223
|
}));
|
|
@@ -210,6 +228,15 @@ async function main() {
|
|
|
210
228
|
return { content: [{ type: "text", text: `unknown tool: ${name}` }], isError: true };
|
|
211
229
|
}
|
|
212
230
|
try {
|
|
231
|
+
// Build the request body; auto-attach the ownership proof + set agent_id = own
|
|
232
|
+
// wallet for the signable tools, so the agent's history is trusted automatically.
|
|
233
|
+
let body = (args ?? {});
|
|
234
|
+
if (account && SIGN_TOOLS.has(tool.name)) {
|
|
235
|
+
body = { ...body, ...(await ownerProof(tool.name)) };
|
|
236
|
+
if (tool.name === "firewall" || tool.name === "firewall_record") {
|
|
237
|
+
body = { ...body, agent_id: account.address }; // the wallet IS the agent identity
|
|
238
|
+
}
|
|
239
|
+
}
|
|
213
240
|
let data;
|
|
214
241
|
if (tool.paid) {
|
|
215
242
|
const client = clientOrNull();
|
|
@@ -224,14 +251,14 @@ async function main() {
|
|
|
224
251
|
isError: true,
|
|
225
252
|
};
|
|
226
253
|
}
|
|
227
|
-
({ data } = await client.pay(`${BASE}${tool.endpoint}`, { method: "POST", body
|
|
254
|
+
({ data } = await client.pay(`${BASE}${tool.endpoint}`, { method: "POST", body }));
|
|
228
255
|
}
|
|
229
256
|
else {
|
|
230
257
|
// free endpoint — plain POST, no payment
|
|
231
258
|
const res = await fetch(`${BASE}${tool.endpoint}`, {
|
|
232
259
|
method: "POST",
|
|
233
260
|
headers: { "Content-Type": "application/json" },
|
|
234
|
-
body: JSON.stringify(
|
|
261
|
+
body: JSON.stringify(body),
|
|
235
262
|
});
|
|
236
263
|
data = await res.json().catch(() => ({}));
|
|
237
264
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kaditang/402sentinel-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "MCP tools for x402 payment safety — vet the counterparty (risk score, allow/review/block, spending policy) AND vet the payment itself (buyer-side firewall: routing/drain/injection). Thin client for 402sentinel.com.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": { "402sentinel-mcp": "./dist/index.js" },
|