@haven_ai/mcp 0.5.0-alpha.1 → 0.7.0-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 +11 -13
- package/dist/cli.cjs +228 -35
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +229 -36
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +231 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +99 -10
- package/dist/index.d.ts +99 -10
- package/dist/index.js +230 -37
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -117,22 +117,20 @@ Environment variable form:
|
|
|
117
117
|
- `haven_discover_tools`
|
|
118
118
|
- `haven_submit_catalog_entry`
|
|
119
119
|
- `haven_list_receipts`
|
|
120
|
+
- `haven_open_task_budget` (#3329)
|
|
121
|
+
- `haven_close_task_budget` (#3329)
|
|
122
|
+
- `haven_submit` (#3329 — relays a local signer signature by `task_budget_id`; a `payment_id` is refused on this runtime, which signs and submits payments inline)
|
|
120
123
|
|
|
121
|
-
### `idempotencyKey` is
|
|
124
|
+
### `idempotencyKey` is removed — send `idempotency_key`
|
|
122
125
|
|
|
123
126
|
The tools that take an idempotency key (`haven_send`, `haven_pay_mcp_tool`,
|
|
124
|
-
`haven_quote_x402`, `haven_pay_x402_quote`, `haven_pay_x402`)
|
|
125
|
-
**`idempotency_key`**, the spelling the hosted Haven MCP surface
|
|
126
|
-
Haven wire contract use. `idempotencyKey` still works and returns a deprecation
|
|
127
|
-
warning; it will be removed in a future release.
|
|
127
|
+
`haven_quote_x402`, `haven_pay_x402_quote`, `haven_pay_x402`) take
|
|
128
|
+
**`idempotency_key`**, the spelling the hosted Haven MCP surface uses.
|
|
128
129
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
The warning arrives in an optional `warnings` array on the success result. It is
|
|
135
|
-
additive: a caller that ignores it sees the response it always saw.
|
|
130
|
+
The legacy `idempotencyKey` spelling is **refused**, not silently accepted or
|
|
131
|
+
dropped: any call that sets it — alone or alongside `idempotency_key` — fails
|
|
132
|
+
with `IDEMPOTENCY_KEY_RENAMED` before anything is contacted or spent. Send
|
|
133
|
+
`idempotency_key` instead.
|
|
136
134
|
|
|
137
135
|
## First-launch consent
|
|
138
136
|
|
|
@@ -156,7 +154,7 @@ Tools this server will expose to your agent runtime:
|
|
|
156
154
|
|
|
157
155
|
On-chain budget (the real spend gate — enforced by the agent's
|
|
158
156
|
signed delegation, not by Haven):
|
|
159
|
-
• up to
|
|
157
|
+
• up to 25 USDC per 1440 min
|
|
160
158
|
|
|
161
159
|
Anything above the on-chain budget is declined before any money
|
|
162
160
|
moves — it is not queued, and no one is asked to review it. If the
|
package/dist/cli.cjs
CHANGED
|
@@ -238,6 +238,17 @@ async function warnIfCredentialFilePermissive(path, log = (message) => process.s
|
|
|
238
238
|
);
|
|
239
239
|
}
|
|
240
240
|
}
|
|
241
|
+
var TASK_BUDGET_MIN_SIGNER_VERSION = "0.6.0-alpha.0";
|
|
242
|
+
var SIGNER_CAPABILITY_KEY = "haven/signer-compatibility";
|
|
243
|
+
function taskSignerCompatibilityNotice() {
|
|
244
|
+
return {
|
|
245
|
+
task_sign_context_version: sdk.TASK_SIGN_CONTEXT_VERSION,
|
|
246
|
+
min_signer_version: TASK_BUDGET_MIN_SIGNER_VERSION,
|
|
247
|
+
signer_capability: SIGNER_CAPABILITY_KEY,
|
|
248
|
+
check: `Call next_tool with next_arguments EXACTLY as given \u2014 the signer fetches the exact bytes by task_budget_id. If haven_sign answers SIGNING_ERROR with a message starting "Pass payment_id (preferred for delegation-rail x402", the signer predates task budgets and signed nothing: first close this budget with haven_close_task_budget { task_budget_id } to release it, then update the connector by running \`${sdk.connectorUpgradeCommand()}\` and the repair line it prints, then open the budget again.`,
|
|
249
|
+
fallback: `haven_sign answered SIGNING_ERROR with a message starting "Pass payment_id (preferred for delegation-rail x402" \u2014 the signer predates task budgets and signed nothing: call haven_close_task_budget { task_budget_id } to release the pending budget, then update the connector by running \`${sdk.connectorUpgradeCommand()}\` and the repair line it prints, then open the budget again. There is no relay fallback for this signing context.`
|
|
250
|
+
};
|
|
251
|
+
}
|
|
241
252
|
var headersSchema = v3.z.record(v3.z.string(), v3.z.string()).optional();
|
|
242
253
|
var toolSchemas = {
|
|
243
254
|
haven_send: {
|
|
@@ -245,16 +256,24 @@ var toolSchemas = {
|
|
|
245
256
|
recipient: v3.z.string().min(1),
|
|
246
257
|
amount: v3.z.string().min(1),
|
|
247
258
|
idempotency_key: v3.z.string().optional(),
|
|
248
|
-
/**
|
|
249
|
-
idempotencyKey: v3.z.string().optional()
|
|
259
|
+
/** REMOVED (#3411): declared only so the resolver can refuse it by name, never accepted. */
|
|
260
|
+
idempotencyKey: v3.z.string().optional().describe("REMOVED (#3411): refused \u2014 send idempotency_key"),
|
|
261
|
+
/** #3329: spend against an open task budget instead of the agent's period budget. */
|
|
262
|
+
task_budget_id: v3.z.string().min(1).optional(),
|
|
263
|
+
/**
|
|
264
|
+
* #3330: spend against an open sub-budget this agent HOLDS (it is the
|
|
265
|
+
* sub-agent B) instead of the agent's own budget. Mutually exclusive
|
|
266
|
+
* with task_budget_id — the backend refuses a body naming both.
|
|
267
|
+
*/
|
|
268
|
+
sub_budget_id: v3.z.string().min(1).optional()
|
|
250
269
|
},
|
|
251
270
|
haven_pay_mcp_tool: {
|
|
252
271
|
merchant_url: v3.z.string().url(),
|
|
253
272
|
tool_name: v3.z.string().min(1),
|
|
254
273
|
arguments: v3.z.record(v3.z.string(), v3.z.unknown()).optional(),
|
|
255
274
|
idempotency_key: v3.z.string().optional(),
|
|
256
|
-
/**
|
|
257
|
-
idempotencyKey: v3.z.string().optional()
|
|
275
|
+
/** REMOVED (#3411): declared only so the resolver can refuse it by name, never accepted. */
|
|
276
|
+
idempotencyKey: v3.z.string().optional().describe("REMOVED (#3411): refused \u2014 send idempotency_key")
|
|
258
277
|
},
|
|
259
278
|
haven_quote_x402: {
|
|
260
279
|
url: v3.z.string().url(),
|
|
@@ -262,14 +281,18 @@ var toolSchemas = {
|
|
|
262
281
|
headers: headersSchema,
|
|
263
282
|
body: v3.z.string().optional(),
|
|
264
283
|
idempotency_key: v3.z.string().optional(),
|
|
265
|
-
/**
|
|
266
|
-
idempotencyKey: v3.z.string().optional()
|
|
284
|
+
/** REMOVED (#3411): declared only so the resolver can refuse it by name, never accepted. */
|
|
285
|
+
idempotencyKey: v3.z.string().optional().describe("REMOVED (#3411): refused \u2014 send idempotency_key")
|
|
267
286
|
},
|
|
268
287
|
haven_pay_x402_quote: {
|
|
269
288
|
quote: v3.z.unknown(),
|
|
270
289
|
idempotency_key: v3.z.string().optional(),
|
|
271
|
-
/**
|
|
272
|
-
idempotencyKey: v3.z.string().optional()
|
|
290
|
+
/** REMOVED (#3411): declared only so the resolver can refuse it by name, never accepted. */
|
|
291
|
+
idempotencyKey: v3.z.string().optional().describe("REMOVED (#3411): refused \u2014 send idempotency_key"),
|
|
292
|
+
/** #3329: spend against an open task budget instead of the agent's period budget. */
|
|
293
|
+
task_budget_id: v3.z.string().min(1).optional(),
|
|
294
|
+
/** #3330: spend against an open sub-budget this agent holds (it is sub-agent B). Mutually exclusive with task_budget_id. */
|
|
295
|
+
sub_budget_id: v3.z.string().min(1).optional()
|
|
273
296
|
},
|
|
274
297
|
haven_pay_x402: {
|
|
275
298
|
url: v3.z.string().url(),
|
|
@@ -277,8 +300,12 @@ var toolSchemas = {
|
|
|
277
300
|
headers: headersSchema,
|
|
278
301
|
body: v3.z.string().optional(),
|
|
279
302
|
idempotency_key: v3.z.string().optional(),
|
|
280
|
-
/**
|
|
281
|
-
idempotencyKey: v3.z.string().optional()
|
|
303
|
+
/** REMOVED (#3411): declared only so the resolver can refuse it by name, never accepted. */
|
|
304
|
+
idempotencyKey: v3.z.string().optional().describe("REMOVED (#3411): refused \u2014 send idempotency_key"),
|
|
305
|
+
/** #3329: spend against an open task budget instead of the agent's period budget. */
|
|
306
|
+
task_budget_id: v3.z.string().min(1).optional(),
|
|
307
|
+
/** #3330: spend against an open sub-budget this agent holds (it is sub-agent B). Mutually exclusive with task_budget_id. */
|
|
308
|
+
sub_budget_id: v3.z.string().min(1).optional()
|
|
282
309
|
},
|
|
283
310
|
haven_resume_x402_payment: {
|
|
284
311
|
payment_id: v3.z.string().optional(),
|
|
@@ -306,13 +333,65 @@ var toolSchemas = {
|
|
|
306
333
|
haven_list_receipts: {
|
|
307
334
|
limit: v3.z.number().int().min(1).max(100).optional(),
|
|
308
335
|
/** #3128: the previous page's next_cursor (a receipt id). */
|
|
309
|
-
cursor: v3.z.string().min(1).optional()
|
|
336
|
+
cursor: v3.z.string().min(1).optional(),
|
|
337
|
+
/** #3423: drop each row's payload echoes; the default shape is unchanged. */
|
|
338
|
+
compact: v3.z.boolean().optional().describe("true drops challengePayload, selectedPayment and protocolReceiptPayload from each row; omit for the full row.")
|
|
310
339
|
},
|
|
311
340
|
haven_verify_receipt: {
|
|
312
341
|
receipt: v3.z.unknown()
|
|
342
|
+
},
|
|
343
|
+
// #3329: a budget for one task that ends by itself — a short-lived child of
|
|
344
|
+
// the agent's own budget, capped and time-boxed independently of the period
|
|
345
|
+
// reset.
|
|
346
|
+
haven_open_task_budget: {
|
|
347
|
+
max_amount_human: v3.z.string().min(1),
|
|
348
|
+
ttl_minutes: v3.z.number().int().min(1).max(1440),
|
|
349
|
+
recipient: v3.z.string().optional(),
|
|
350
|
+
label: v3.z.string().max(120).optional(),
|
|
351
|
+
token: v3.z.string().optional()
|
|
352
|
+
},
|
|
353
|
+
haven_close_task_budget: {
|
|
354
|
+
task_budget_id: v3.z.string().min(1)
|
|
355
|
+
},
|
|
356
|
+
// #3329: relays a signature from the local signer — either the open/close
|
|
357
|
+
// signature for a task budget, or (schema parity with the hosted surface)
|
|
358
|
+
// a direct-payment signature by payment_id. Exactly one of task_budget_id /
|
|
359
|
+
// payment_id, never both or neither.
|
|
360
|
+
haven_submit: {
|
|
361
|
+
task_budget_id: v3.z.string().min(1).optional(),
|
|
362
|
+
payment_id: v3.z.string().min(1).optional(),
|
|
363
|
+
signature: v3.z.string().regex(/^0x[0-9a-fA-F]+$/, "signature must be a 0x-prefixed hex string")
|
|
313
364
|
}
|
|
314
365
|
// #3101: keys survive on the type (see the hosted server's contracts.ts).
|
|
315
366
|
};
|
|
367
|
+
var OPEN_TASK_BUDGET_DESCRIPTION = [
|
|
368
|
+
"Open a budget for one task that ends by itself: a spending cap, good for at most ttl_minutes,",
|
|
369
|
+
"reserved out of the agent's own budget and separate from its period reset.",
|
|
370
|
+
'Pass max_amount_human (whole tokens, e.g. "5" for 5 USDC), ttl_minutes (1-1440), and optionally',
|
|
371
|
+
"recipient (pins every payment against this task budget to one address), label, and token",
|
|
372
|
+
'(defaults to USDC). Returns { task_budget, next_action: "sign", next_tool, next_arguments } \u2014',
|
|
373
|
+
"call next_tool with next_arguments EXACTLY as given to get a signature, then relay it with",
|
|
374
|
+
"haven_submit. Spending anything above the reserved amount, past the deadline, or to a",
|
|
375
|
+
"different recipient than the one pinned here is declined on the spot \u2014 nothing is queued.",
|
|
376
|
+
"A recipient pin is checked against where each payment first goes: haven_pay_x402 and",
|
|
377
|
+
"haven_pay_x402_quote first move funds into the agent's own wallet, so a budget pinned to a",
|
|
378
|
+
"merchant is declined there \u2014 pin only budgets meant for haven_send."
|
|
379
|
+
].join(" ");
|
|
380
|
+
var CLOSE_TASK_BUDGET_DESCRIPTION = [
|
|
381
|
+
"End a task budget early, before its deadline, releasing whatever of its cap was unspent back",
|
|
382
|
+
"to the agent's own budget. Pass task_budget_id. If the budget was never signed (still pending),",
|
|
383
|
+
'this ends it immediately with { task_budget, status: "closed" } and nothing was ever reserved',
|
|
384
|
+
'on-chain. Otherwise it returns a signature request: { task_budget, next_action: "sign",',
|
|
385
|
+
"next_tool, next_arguments } \u2014 call next_tool with next_arguments EXACTLY as given, then relay",
|
|
386
|
+
"the signature with haven_submit. A task budget past its own deadline closes immediately, the",
|
|
387
|
+
"same as the pending case."
|
|
388
|
+
].join(" ");
|
|
389
|
+
var SUBMIT_DESCRIPTION = [
|
|
390
|
+
"Relay a signature from the local signer. Pass exactly one of task_budget_id (from",
|
|
391
|
+
"haven_open_task_budget or haven_close_task_budget) or payment_id \u2014 never both, never neither \u2014",
|
|
392
|
+
"plus signature. For a task budget this opens or closes it on-chain and returns { task_budget,",
|
|
393
|
+
'status }; a close in progress may return { task_budget, status: "closed", close_tx_hash }.'
|
|
394
|
+
].join(" ");
|
|
316
395
|
var toolDescriptions = {
|
|
317
396
|
haven_send: sdk.composeDescription(sdk.toolDescriptions.send),
|
|
318
397
|
haven_pay_mcp_tool: sdk.composeDescription(sdk.toolDescriptions.payMcpTool),
|
|
@@ -328,7 +407,10 @@ var toolDescriptions = {
|
|
|
328
407
|
haven_discover_tools: sdk.composeDescription(sdk.toolDescriptions.discoverTools),
|
|
329
408
|
haven_submit_catalog_entry: sdk.composeDescription(sdk.toolDescriptions.submitCatalogEntry),
|
|
330
409
|
haven_list_receipts: sdk.composeDescription(sdk.toolDescriptions.listReceipts),
|
|
331
|
-
haven_verify_receipt: sdk.composeDescription(sdk.toolDescriptions.verifyReceipt)
|
|
410
|
+
haven_verify_receipt: sdk.composeDescription(sdk.toolDescriptions.verifyReceipt),
|
|
411
|
+
haven_open_task_budget: OPEN_TASK_BUDGET_DESCRIPTION,
|
|
412
|
+
haven_close_task_budget: CLOSE_TASK_BUDGET_DESCRIPTION,
|
|
413
|
+
haven_submit: SUBMIT_DESCRIPTION
|
|
332
414
|
};
|
|
333
415
|
function createToolHandlers(haven) {
|
|
334
416
|
return {
|
|
@@ -342,9 +424,15 @@ function createToolHandlers(haven) {
|
|
|
342
424
|
token: args.asset,
|
|
343
425
|
amount: args.amount,
|
|
344
426
|
to: args.recipient,
|
|
345
|
-
// #1207: was accepted by the schema but silently dropped — now
|
|
346
|
-
// carried to the backend's replay contract.
|
|
347
|
-
idempotencyKey: args.
|
|
427
|
+
// #1207/#3411: was accepted by the schema but silently dropped — now
|
|
428
|
+
// carried to the backend's replay contract via the resolved carrier.
|
|
429
|
+
idempotencyKey: args.resolvedIdempotencyKey,
|
|
430
|
+
// #3329: spend against an open task budget instead of the
|
|
431
|
+
// agent's period budget, when the caller names one.
|
|
432
|
+
...typeof args.task_budget_id === "string" ? { taskBudgetId: args.task_budget_id } : {},
|
|
433
|
+
// #3330: spend through a sub-budget this agent holds (it is
|
|
434
|
+
// sub-agent B). Exactly one of the two ids, never both.
|
|
435
|
+
...typeof args.sub_budget_id === "string" ? { subBudgetId: args.sub_budget_id } : {}
|
|
348
436
|
});
|
|
349
437
|
return {
|
|
350
438
|
payment_id: result.paymentId,
|
|
@@ -382,7 +470,7 @@ function createToolHandlers(haven) {
|
|
|
382
470
|
body: JSON.stringify(envelope)
|
|
383
471
|
};
|
|
384
472
|
let merchantUrl = args.merchant_url;
|
|
385
|
-
const idempotencyKey = args.
|
|
473
|
+
const idempotencyKey = args.resolvedIdempotencyKey;
|
|
386
474
|
const attempt = () => haven.fetch(merchantUrl, init, { idempotencyKey });
|
|
387
475
|
let response = await attempt();
|
|
388
476
|
if (!response.ok) {
|
|
@@ -416,7 +504,7 @@ function createToolHandlers(haven) {
|
|
|
416
504
|
if ("success" in pf) return pf;
|
|
417
505
|
const { args, warnings } = pf;
|
|
418
506
|
try {
|
|
419
|
-
const data = await haven.quoteX402(args.url, requestInit(args), { idempotencyKey: args.
|
|
507
|
+
const data = await haven.quoteX402(args.url, requestInit(args), { idempotencyKey: args.resolvedIdempotencyKey });
|
|
420
508
|
return warnings.length > 0 ? { success: true, data, warnings } : { success: true, data };
|
|
421
509
|
} catch (err) {
|
|
422
510
|
return normalizeError(err);
|
|
@@ -442,7 +530,11 @@ function createToolHandlers(haven) {
|
|
|
442
530
|
);
|
|
443
531
|
}
|
|
444
532
|
return runTool(async () => {
|
|
445
|
-
const response = await haven.payX402Quote(args.quote, {
|
|
533
|
+
const response = await haven.payX402Quote(args.quote, {
|
|
534
|
+
idempotencyKey: args.resolvedIdempotencyKey,
|
|
535
|
+
...typeof args.task_budget_id === "string" ? { taskBudgetId: args.task_budget_id } : {},
|
|
536
|
+
...typeof args.sub_budget_id === "string" ? { subBudgetId: args.sub_budget_id } : {}
|
|
537
|
+
});
|
|
446
538
|
return responsePayload(response);
|
|
447
539
|
}, warnings);
|
|
448
540
|
},
|
|
@@ -451,7 +543,11 @@ function createToolHandlers(haven) {
|
|
|
451
543
|
if ("success" in pf) return pf;
|
|
452
544
|
const { args, warnings } = pf;
|
|
453
545
|
return runTool(async () => {
|
|
454
|
-
const response = await haven.fetch(args.url, requestInit(args), {
|
|
546
|
+
const response = await haven.fetch(args.url, requestInit(args), {
|
|
547
|
+
idempotencyKey: args.resolvedIdempotencyKey,
|
|
548
|
+
...typeof args.task_budget_id === "string" ? { taskBudgetId: args.task_budget_id } : {},
|
|
549
|
+
...typeof args.sub_budget_id === "string" ? { subBudgetId: args.sub_budget_id } : {}
|
|
550
|
+
});
|
|
455
551
|
return responsePayload(response);
|
|
456
552
|
}, warnings);
|
|
457
553
|
},
|
|
@@ -547,11 +643,82 @@ function createToolHandlers(haven) {
|
|
|
547
643
|
},
|
|
548
644
|
haven_list_receipts: async (input) => {
|
|
549
645
|
const args = objectInput("haven_list_receipts", input);
|
|
550
|
-
return runTool(
|
|
646
|
+
return runTool(
|
|
647
|
+
async () => haven.listReceiptsPage({ limit: args.limit, cursor: args.cursor, compact: args.compact === true })
|
|
648
|
+
);
|
|
551
649
|
},
|
|
552
650
|
haven_verify_receipt: async (input) => {
|
|
553
651
|
const args = objectInput("haven_verify_receipt", input);
|
|
554
652
|
return runTool(async () => sdk.verifyPaymentReceipt(args.receipt));
|
|
653
|
+
},
|
|
654
|
+
haven_open_task_budget: async (input) => {
|
|
655
|
+
const args = objectInput("haven_open_task_budget", input);
|
|
656
|
+
return runTool(async () => {
|
|
657
|
+
const token = await resolveTaskBudgetToken(haven, typeof args.token === "string" ? args.token : void 0);
|
|
658
|
+
const maxAmountAtomic = humanToAtomicOrNull(args.max_amount_human, token.decimals);
|
|
659
|
+
if (maxAmountAtomic === null) {
|
|
660
|
+
throw new sdk.HavenApiError(
|
|
661
|
+
`max_amount_human ("${args.max_amount_human}") is not a valid decimal amount for ${token.symbol} (${token.decimals} decimal places). Nothing was reserved.`,
|
|
662
|
+
400
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
const result = await haven.openTaskBudget({
|
|
666
|
+
tokenAddress: token.address,
|
|
667
|
+
maxAmountAtomic,
|
|
668
|
+
ttlSeconds: Number(args.ttl_minutes) * 60,
|
|
669
|
+
recipientAddress: typeof args.recipient === "string" ? args.recipient : void 0,
|
|
670
|
+
label: typeof args.label === "string" ? args.label : void 0
|
|
671
|
+
});
|
|
672
|
+
return {
|
|
673
|
+
task_budget: result.taskBudget,
|
|
674
|
+
next_action: "sign",
|
|
675
|
+
next_tool: "mcp__haven-signer__haven_sign",
|
|
676
|
+
next_arguments: { task_budget_id: result.taskBudget.id },
|
|
677
|
+
signer_compatibility: taskSignerCompatibilityNotice()
|
|
678
|
+
};
|
|
679
|
+
});
|
|
680
|
+
},
|
|
681
|
+
haven_close_task_budget: async (input) => {
|
|
682
|
+
const args = objectInput("haven_close_task_budget", input);
|
|
683
|
+
return runTool(async () => {
|
|
684
|
+
const result = await haven.closeTaskBudget(args.task_budget_id);
|
|
685
|
+
if (result.status === "closed") {
|
|
686
|
+
return { task_budget: result.taskBudget, status: "closed" };
|
|
687
|
+
}
|
|
688
|
+
return {
|
|
689
|
+
task_budget: result.taskBudget,
|
|
690
|
+
next_action: "sign",
|
|
691
|
+
next_tool: "mcp__haven-signer__haven_sign",
|
|
692
|
+
next_arguments: { task_budget_id: args.task_budget_id },
|
|
693
|
+
signer_compatibility: taskSignerCompatibilityNotice()
|
|
694
|
+
};
|
|
695
|
+
});
|
|
696
|
+
},
|
|
697
|
+
haven_submit: async (input) => {
|
|
698
|
+
const args = objectInput("haven_submit", input);
|
|
699
|
+
const hasTaskBudget = typeof args.task_budget_id === "string" && args.task_budget_id.length > 0;
|
|
700
|
+
const hasPayment = typeof args.payment_id === "string" && args.payment_id.length > 0;
|
|
701
|
+
if (hasTaskBudget === hasPayment) {
|
|
702
|
+
return {
|
|
703
|
+
success: false,
|
|
704
|
+
code: "INVALID_INPUT",
|
|
705
|
+
message: "haven_submit takes exactly one of task_budget_id or payment_id, never both or neither. Nothing was relayed."
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
return runTool(async () => {
|
|
709
|
+
if (hasTaskBudget) {
|
|
710
|
+
const result = await haven.submitTaskBudget(args.task_budget_id, args.signature);
|
|
711
|
+
return {
|
|
712
|
+
task_budget: result.taskBudget,
|
|
713
|
+
status: result.status,
|
|
714
|
+
...result.closeTxHash !== void 0 ? { close_tx_hash: result.closeTxHash } : {}
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
throw new sdk.HavenApiError(
|
|
718
|
+
"haven_submit with payment_id is not supported on the local MCP surface: haven_send signs and submits a direct payment in one call, so there is no separate relay step. Use haven_submit with task_budget_id to relay a task-budget open/close signature.",
|
|
719
|
+
400
|
|
720
|
+
);
|
|
721
|
+
});
|
|
555
722
|
}
|
|
556
723
|
};
|
|
557
724
|
async function resumeState(args, rail) {
|
|
@@ -580,28 +747,20 @@ function preflight(name, input) {
|
|
|
580
747
|
}
|
|
581
748
|
const idem = resolveIdempotencyKey(args);
|
|
582
749
|
if ("success" in idem) return idem;
|
|
583
|
-
if (idem.key !== void 0) args = { ...args,
|
|
750
|
+
if (idem.key !== void 0) args = { ...args, resolvedIdempotencyKey: idem.key };
|
|
584
751
|
return { args, warnings: idem.warnings };
|
|
585
752
|
}
|
|
586
753
|
function resolveIdempotencyKey(args) {
|
|
587
754
|
const modern = typeof args.idempotency_key === "string" ? args.idempotency_key : void 0;
|
|
588
755
|
const legacy = typeof args.idempotencyKey === "string" ? args.idempotencyKey : void 0;
|
|
589
|
-
if (
|
|
756
|
+
if (legacy !== void 0) {
|
|
590
757
|
return {
|
|
591
758
|
success: false,
|
|
592
|
-
code: "
|
|
593
|
-
message: "
|
|
759
|
+
code: "IDEMPOTENCY_KEY_RENAMED",
|
|
760
|
+
message: "idempotencyKey has been renamed to idempotency_key and is no longer accepted. Send idempotency_key instead. Nothing was contacted or spent."
|
|
594
761
|
};
|
|
595
762
|
}
|
|
596
763
|
if (modern !== void 0) return { key: modern, warnings: [] };
|
|
597
|
-
if (legacy !== void 0) {
|
|
598
|
-
return {
|
|
599
|
-
key: legacy,
|
|
600
|
-
warnings: [
|
|
601
|
-
"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."
|
|
602
|
-
]
|
|
603
|
-
};
|
|
604
|
-
}
|
|
605
764
|
return { warnings: [] };
|
|
606
765
|
}
|
|
607
766
|
function buildMcpToolsCallEnvelope(toolName, args) {
|
|
@@ -643,6 +802,28 @@ async function responsePayload(response) {
|
|
|
643
802
|
body: parseMaybeJson(text)
|
|
644
803
|
};
|
|
645
804
|
}
|
|
805
|
+
async function resolveTaskBudgetToken(haven, symbolOrAddress) {
|
|
806
|
+
const wanted = (symbolOrAddress ?? "USDC").toLowerCase();
|
|
807
|
+
const { allowances } = await haven.getAllowances();
|
|
808
|
+
const isAddress = /^0x[0-9a-fA-F]{40}$/.test(wanted);
|
|
809
|
+
const match = isAddress ? allowances.find((a) => a.tokenAddress.toLowerCase() === wanted) : allowances.find((a) => (a.tokenSymbol ?? "").toLowerCase() === wanted);
|
|
810
|
+
if (!match) {
|
|
811
|
+
const known = allowances.map((a) => `${a.tokenSymbol} (${a.tokenAddress})`);
|
|
812
|
+
throw new sdk.HavenApiError(
|
|
813
|
+
`token "${symbolOrAddress ?? "USDC"}" is not the symbol or address of any allowance this agent holds${known.length > 0 ? ` (it holds: ${known.join(", ")})` : " (it holds none)"}. Nothing was reserved.`,
|
|
814
|
+
400
|
|
815
|
+
);
|
|
816
|
+
}
|
|
817
|
+
const resolved = sdk.resolveTokenFromAddress(match.tokenAddress);
|
|
818
|
+
return { address: match.tokenAddress, symbol: match.tokenSymbol, decimals: resolved?.decimals ?? 6 };
|
|
819
|
+
}
|
|
820
|
+
function humanToAtomicOrNull(human, decimals) {
|
|
821
|
+
if (!/^[0-9]+(\.[0-9]+)?$/.test(human)) return null;
|
|
822
|
+
const [whole, frac = ""] = human.split(".");
|
|
823
|
+
if (frac.length > decimals) return null;
|
|
824
|
+
const atomic = BigInt(whole || "0") * 10n ** BigInt(decimals) + BigInt(frac.padEnd(decimals, "0") || "0");
|
|
825
|
+
return atomic.toString();
|
|
826
|
+
}
|
|
646
827
|
function parseMaybeJson(text) {
|
|
647
828
|
if (!text) return null;
|
|
648
829
|
try {
|
|
@@ -810,7 +991,7 @@ function renderConsentBlock(input, hash) {
|
|
|
810
991
|
lines.push("signed delegation, not by Haven):");
|
|
811
992
|
for (const a of input.allowanceSummary) {
|
|
812
993
|
const reset = a.resetMinutes ? ` per ${a.resetMinutes} min` : " (no reset)";
|
|
813
|
-
lines.push(` \u2022 up to ${a
|
|
994
|
+
lines.push(` \u2022 up to ${describeBudgetAmount(a, input.chainId)}${reset}`);
|
|
814
995
|
}
|
|
815
996
|
}
|
|
816
997
|
lines.push("");
|
|
@@ -907,7 +1088,14 @@ async function consentInputFromClient(haven, seed, toolNames) {
|
|
|
907
1088
|
}
|
|
908
1089
|
const liveAllowanceSummary = list.map((a) => ({
|
|
909
1090
|
token: a.tokenSymbol ?? "UNKNOWN",
|
|
910
|
-
|
|
1091
|
+
// #3410: `onchain.amount` is the atomic budget (`budget_atomic` on the
|
|
1092
|
+
// backend). The old `a.configuredAmount` fallback here was unreachable
|
|
1093
|
+
// — `onchain` is required in the SDK types and dereferenced
|
|
1094
|
+
// unconditionally, so a response without it throws into the seed path —
|
|
1095
|
+
// and it was the one HUMAN-decimal value in this chain; a human number
|
|
1096
|
+
// must never reach an atomic field. '0' keeps the (defensive) shape for
|
|
1097
|
+
// a malformed onchain object without inventing a unit.
|
|
1098
|
+
amount: a.onchain?.amount ?? "0",
|
|
911
1099
|
resetMinutes: typeof a.onchain?.resetTimeMin === "number" ? a.onchain.resetTimeMin : typeof a.resetPeriodMin === "number" ? a.resetPeriodMin : null
|
|
912
1100
|
}));
|
|
913
1101
|
allowanceSummary = liveAllowanceSummary;
|
|
@@ -930,6 +1118,11 @@ function isAllowanceSummary(value) {
|
|
|
930
1118
|
function derivePrefix(apiKey) {
|
|
931
1119
|
return apiKey.slice(0, 12);
|
|
932
1120
|
}
|
|
1121
|
+
function describeBudgetAmount(a, chainId) {
|
|
1122
|
+
const token = chainId === void 0 ? null : sdk.resolveTokenBySymbol(chainId, a.token);
|
|
1123
|
+
if (!token) return `${a.amount} ${a.token} (atomic units)`;
|
|
1124
|
+
return `${sdk.formatTokenAmount(a.amount, token.decimals)} ${a.token}`;
|
|
1125
|
+
}
|
|
933
1126
|
function registeredToolNames() {
|
|
934
1127
|
return Object.keys(toolSchemas);
|
|
935
1128
|
}
|
|
@@ -954,7 +1147,7 @@ async function resolveHavenClient(options = {}) {
|
|
|
954
1147
|
return { client, credentials };
|
|
955
1148
|
}
|
|
956
1149
|
var MCP_NAME = "@haven_ai/mcp";
|
|
957
|
-
var MCP_VERSION = "0.
|
|
1150
|
+
var MCP_VERSION = "0.7.0-alpha.0";
|
|
958
1151
|
var MCP_INSTRUCTIONS = [
|
|
959
1152
|
"Haven local MCP server: signs in-process with the delegate key it holds on",
|
|
960
1153
|
"this machine \u2014 the key never leaves this process. Call haven_get_agent",
|