@haven_ai/mcp 0.1.34-alpha.0 → 0.1.36-alpha.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 CHANGED
@@ -16,6 +16,17 @@ The server is intentionally local-only:
16
16
  - Haven's backend receives API identity plus signed payloads. It never receives
17
17
  the delegate key.
18
18
 
19
+ ## Are you an AI agent whose user has no Haven account yet?
20
+
21
+ Read **`/for-agents.md`** on the Haven host your user gave you — or
22
+ [the copy in this repository](https://github.com/d-hinders/Haven-AI/blob/dev/packages/frontend/public/for-agents.md)
23
+ if you do not have that host yet.
24
+
25
+ Your user creates the account and the passkey: those are theirs, they need a
26
+ human, and you should never ask for their password. You can do everything else
27
+ — including running the connector command from the setup prompt they paste you,
28
+ and managing the account from the shell with `@haven_ai/cli`.
29
+
19
30
  ## Credential file
20
31
 
21
32
  Create a private JSON file from the values in the Haven agent handoff:
@@ -35,7 +46,7 @@ Create a private JSON file from the values in the Haven agent handoff:
35
46
  The Haven connector may also write split credentials:
36
47
 
37
48
  ```sh
38
- npx @haven_ai/mcp --identity ~/.haven/agents/<agent-id>/identity.json --signer ~/.haven/agents/<agent-id>/signer.json
49
+ npx @haven_ai/mcp@alpha --identity ~/.haven/agents/<agent-id>/identity.json --signer ~/.haven/agents/<agent-id>/signer.json
39
50
  ```
40
51
 
41
52
  `identity.json` holds the local API key and setup metadata. `signer.json` holds
@@ -99,6 +110,22 @@ Environment variable form:
99
110
  - `haven_submit_catalog_entry`
100
111
  - `haven_list_receipts`
101
112
 
113
+ ### `idempotencyKey` is deprecated — send `idempotency_key`
114
+
115
+ The tools that take an idempotency key (`haven_send`, `haven_pay_mcp_tool`,
116
+ `haven_quote_x402`, `haven_pay_x402_quote`, `haven_pay_x402`) now accept
117
+ **`idempotency_key`**, the spelling the hosted Haven MCP surface and every other
118
+ Haven wire contract use. `idempotencyKey` still works and returns a deprecation
119
+ warning; it will be removed in a future release.
120
+
121
+ Send **one** of them. Sending both with different values is refused
122
+ (`AMBIGUOUS_IDEMPOTENCY_KEY`) with nothing contacted or spent — the key decides
123
+ whether a retry is the same payment or a second one, so Haven will not guess
124
+ which scope you meant. Sending both with the same value is fine.
125
+
126
+ The warning arrives in an optional `warnings` array on the success result. It is
127
+ additive: a caller that ignores it sees the response it always saw.
128
+
102
129
  ## First-launch consent
103
130
 
104
131
  The first time the MCP server runs against a credential file it refuses to
@@ -137,7 +164,7 @@ Acknowledge in one of two ways:
137
164
  - **Sidecar file (recommended).** Re-run once with `--ack`:
138
165
 
139
166
  ```sh
140
- npx @haven_ai/mcp --credentials /absolute/path/to/haven-agent.json --ack
167
+ npx @haven_ai/mcp@alpha --credentials /absolute/path/to/haven-agent.json --ack
141
168
  ```
142
169
 
143
170
  This writes `haven-agent.json.ack.json` next to your credential. Future
@@ -192,6 +219,6 @@ flow above instead.
192
219
  ## Non-custodial invariant
193
220
 
194
221
  Do not run this as a hosted multi-tenant signer. The expected deployment is
195
- `npx @haven_ai/mcp` running beside the agent runtime that owns the credential
222
+ `npx @haven_ai/mcp@alpha` running beside the agent runtime that owns the credential
196
223
  file. Revoking the agent on-chain disables spending even if this MCP server is
197
224
  still running.
package/dist/cli.cjs CHANGED
@@ -222,12 +222,16 @@ var toolSchemas = {
222
222
  asset: v3.z.enum(["ETH", "USDC"]),
223
223
  recipient: v3.z.string().min(1),
224
224
  amount: v3.z.string().min(1),
225
+ idempotency_key: v3.z.string().optional(),
226
+ /** Legacy spelling, accepted during the #2366 window. Warns; do not use. */
225
227
  idempotencyKey: v3.z.string().optional()
226
228
  },
227
229
  haven_pay_mcp_tool: {
228
230
  merchant_url: v3.z.string().url(),
229
231
  tool_name: v3.z.string().min(1),
230
232
  arguments: v3.z.record(v3.z.string(), v3.z.unknown()).optional(),
233
+ idempotency_key: v3.z.string().optional(),
234
+ /** Legacy spelling, accepted during the #2366 window. Warns; do not use. */
231
235
  idempotencyKey: v3.z.string().optional()
232
236
  },
233
237
  haven_quote_x402: {
@@ -235,10 +239,14 @@ var toolSchemas = {
235
239
  method: v3.z.string().optional(),
236
240
  headers: headersSchema,
237
241
  body: v3.z.string().optional(),
242
+ idempotency_key: v3.z.string().optional(),
243
+ /** Legacy spelling, accepted during the #2366 window. Warns; do not use. */
238
244
  idempotencyKey: v3.z.string().optional()
239
245
  },
240
246
  haven_pay_x402_quote: {
241
247
  quote: v3.z.unknown(),
248
+ idempotency_key: v3.z.string().optional(),
249
+ /** Legacy spelling, accepted during the #2366 window. Warns; do not use. */
242
250
  idempotencyKey: v3.z.string().optional()
243
251
  },
244
252
  haven_pay_x402: {
@@ -246,6 +254,8 @@ var toolSchemas = {
246
254
  method: v3.z.string().optional(),
247
255
  headers: headersSchema,
248
256
  body: v3.z.string().optional(),
257
+ idempotency_key: v3.z.string().optional(),
258
+ /** Legacy spelling, accepted during the #2366 window. Warns; do not use. */
249
259
  idempotencyKey: v3.z.string().optional()
250
260
  },
251
261
  haven_resume_x402_payment: {
@@ -298,8 +308,10 @@ var toolDescriptions = {
298
308
  function createToolHandlers(haven) {
299
309
  return {
300
310
  haven_send: async (input) => {
311
+ const pf = preflight("haven_send", input);
312
+ if ("success" in pf) return pf;
313
+ const { args, warnings } = pf;
301
314
  return runTool(async () => {
302
- const args = objectInput("haven_send", input);
303
315
  try {
304
316
  const result = await haven.pay({
305
317
  token: args.asset,
@@ -307,7 +319,7 @@ function createToolHandlers(haven) {
307
319
  to: args.recipient,
308
320
  // #1207: was accepted by the schema but silently dropped — now
309
321
  // carried to the backend's replay contract.
310
- idempotencyKey: typeof args.idempotencyKey === "string" ? args.idempotencyKey : void 0
322
+ idempotencyKey: args.idempotencyKey
311
323
  });
312
324
  return {
313
325
  payment_id: result.paymentId,
@@ -331,11 +343,13 @@ function createToolHandlers(haven) {
331
343
  }
332
344
  throw err;
333
345
  }
334
- });
346
+ }, warnings);
335
347
  },
336
348
  haven_pay_mcp_tool: async (input) => {
349
+ const pf = preflight("haven_pay_mcp_tool", input);
350
+ if ("success" in pf) return pf;
351
+ const { args, warnings } = pf;
337
352
  return runTool(async () => {
338
- const args = objectInput("haven_pay_mcp_tool", input);
339
353
  const envelope = buildMcpToolsCallEnvelope(args.tool_name, args.arguments);
340
354
  const init = {
341
355
  method: "POST",
@@ -366,18 +380,23 @@ function createToolHandlers(haven) {
366
380
  merchant_url: merchantUrl,
367
381
  ...merchantUrl !== args.merchant_url ? { merchant_url_discovered_from: args.merchant_url } : {}
368
382
  };
369
- });
383
+ }, warnings);
370
384
  },
371
385
  haven_quote_x402: async (input) => {
372
- const args = objectInput("haven_quote_x402", input);
386
+ const pf = preflight("haven_quote_x402", input);
387
+ if ("success" in pf) return pf;
388
+ const { args, warnings } = pf;
373
389
  try {
374
- return { success: true, data: await haven.quoteX402(args.url, requestInit(args), { idempotencyKey: args.idempotencyKey }) };
390
+ const data = await haven.quoteX402(args.url, requestInit(args), { idempotencyKey: args.idempotencyKey });
391
+ return warnings.length > 0 ? { success: true, data, warnings } : { success: true, data };
375
392
  } catch (err) {
376
393
  return normalizeError(err);
377
394
  }
378
395
  },
379
396
  haven_pay_x402_quote: async (input) => {
380
- const args = objectInput("haven_pay_x402_quote", input);
397
+ const pf = preflight("haven_pay_x402_quote", input);
398
+ if ("success" in pf) return pf;
399
+ const { args, warnings } = pf;
381
400
  const quote = args.quote;
382
401
  if (!quote || typeof quote !== "object") {
383
402
  return wrongTool(
@@ -396,14 +415,16 @@ function createToolHandlers(haven) {
396
415
  return runTool(async () => {
397
416
  const response = await haven.payX402Quote(args.quote, { idempotencyKey: args.idempotencyKey });
398
417
  return responsePayload(response);
399
- });
418
+ }, warnings);
400
419
  },
401
420
  haven_pay_x402: async (input) => {
402
- const args = objectInput("haven_pay_x402", input);
421
+ const pf = preflight("haven_pay_x402", input);
422
+ if ("success" in pf) return pf;
423
+ const { args, warnings } = pf;
403
424
  return runTool(async () => {
404
425
  const response = await haven.fetch(args.url, requestInit(args), { idempotencyKey: args.idempotencyKey });
405
426
  return responsePayload(response);
406
- });
427
+ }, warnings);
407
428
  },
408
429
  haven_resume_x402_payment: async (input) => {
409
430
  const args = objectInput("haven_resume_x402_payment", input);
@@ -501,6 +522,39 @@ function isPendingApproval(status) {
501
522
  function wrongTool(code, message, suggested_tool) {
502
523
  return { success: false, code, message, suggested_tool };
503
524
  }
525
+ function preflight(name, input) {
526
+ let args;
527
+ try {
528
+ args = objectInput(name, input);
529
+ } catch (err) {
530
+ return normalizeError(err);
531
+ }
532
+ const idem = resolveIdempotencyKey(args);
533
+ if ("success" in idem) return idem;
534
+ if (idem.key !== void 0) args = { ...args, idempotencyKey: idem.key };
535
+ return { args, warnings: idem.warnings };
536
+ }
537
+ function resolveIdempotencyKey(args) {
538
+ const modern = typeof args.idempotency_key === "string" ? args.idempotency_key : void 0;
539
+ const legacy = typeof args.idempotencyKey === "string" ? args.idempotencyKey : void 0;
540
+ if (modern !== void 0 && legacy !== void 0 && modern !== legacy) {
541
+ return {
542
+ success: false,
543
+ code: "AMBIGUOUS_IDEMPOTENCY_KEY",
544
+ message: "Both idempotency_key and idempotencyKey were sent with different values. Nothing was contacted or spent. Send exactly one \u2014 idempotency_key is the current spelling; idempotencyKey is deprecated and will be removed."
545
+ };
546
+ }
547
+ if (modern !== void 0) return { key: modern, warnings: [] };
548
+ if (legacy !== void 0) {
549
+ return {
550
+ key: legacy,
551
+ warnings: [
552
+ "idempotencyKey is deprecated and will be removed in a future release of @haven_ai/mcp. Send idempotency_key instead \u2014 it is the spelling the hosted Haven MCP surface and every other Haven wire contract use."
553
+ ]
554
+ };
555
+ }
556
+ return { warnings: [] };
557
+ }
504
558
  function buildMcpToolsCallEnvelope(toolName, args) {
505
559
  return {
506
560
  jsonrpc: "2.0",
@@ -523,9 +577,10 @@ function requestInit(input) {
523
577
  body: input.body
524
578
  };
525
579
  }
526
- async function runTool(fn) {
580
+ async function runTool(fn, warnings = []) {
527
581
  try {
528
- return { success: true, data: await fn() };
582
+ const data = await fn();
583
+ return warnings.length > 0 ? { success: true, data, warnings } : { success: true, data };
529
584
  } catch (err) {
530
585
  return normalizeError(err);
531
586
  }
@@ -796,7 +851,7 @@ async function resolveHavenClient(options = {}) {
796
851
  return { client, credentials };
797
852
  }
798
853
  var MCP_NAME = "@haven_ai/mcp";
799
- var MCP_VERSION = "0.1.34-alpha.0";
854
+ var MCP_VERSION = "0.1.36-alpha.0";
800
855
  var MCP_INSTRUCTIONS = [
801
856
  "Haven local MCP server: signs in-process with the delegate key it holds on",
802
857
  "this machine \u2014 the key never leaves this process. Call haven_get_agent",