@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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +37 -10
  3. 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. Pass your payer wallet as `agent_id`.
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. Pass your payer wallet as agent_id. Costs $0.002. Seed history free with firewall_record.",
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: ["agent_id", "payment"],
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). Pass your payer wallet as agent_id.",
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: ["agent_id", "payment"],
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 (!RAW_PK || RAW_PK.startsWith("0xYour"))
215
+ if (!PK)
197
216
  return null;
198
- const pk = (RAW_PK.startsWith("0x") ? RAW_PK : `0x${RAW_PK}`);
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.5.0" }, { capabilities: { tools: {} } });
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: args }));
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(args ?? {}),
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.5.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" },