@haven_ai/mcp 0.0.0-dev.202609252136.3257a06 → 0.0.0-dev.202609260843.267f647
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 +3 -0
- package/dist/cli.cjs +162 -8
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +163 -9
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +162 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -2
- package/dist/index.d.ts +23 -2
- package/dist/index.js +163 -9
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -117,6 +117,9 @@ 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
124
|
### `idempotencyKey` is deprecated — send `idempotency_key`
|
|
122
125
|
|
package/dist/cli.cjs
CHANGED
|
@@ -246,7 +246,9 @@ var toolSchemas = {
|
|
|
246
246
|
amount: v3.z.string().min(1),
|
|
247
247
|
idempotency_key: v3.z.string().optional(),
|
|
248
248
|
/** Legacy spelling, accepted during the #2366 window. Warns; do not use. */
|
|
249
|
-
idempotencyKey: v3.z.string().optional()
|
|
249
|
+
idempotencyKey: v3.z.string().optional(),
|
|
250
|
+
/** #3329: spend against an open task budget instead of the agent's period budget. */
|
|
251
|
+
task_budget_id: v3.z.string().min(1).optional()
|
|
250
252
|
},
|
|
251
253
|
haven_pay_mcp_tool: {
|
|
252
254
|
merchant_url: v3.z.string().url(),
|
|
@@ -269,7 +271,9 @@ var toolSchemas = {
|
|
|
269
271
|
quote: v3.z.unknown(),
|
|
270
272
|
idempotency_key: v3.z.string().optional(),
|
|
271
273
|
/** Legacy spelling, accepted during the #2366 window. Warns; do not use. */
|
|
272
|
-
idempotencyKey: v3.z.string().optional()
|
|
274
|
+
idempotencyKey: v3.z.string().optional(),
|
|
275
|
+
/** #3329: spend against an open task budget instead of the agent's period budget. */
|
|
276
|
+
task_budget_id: v3.z.string().min(1).optional()
|
|
273
277
|
},
|
|
274
278
|
haven_pay_x402: {
|
|
275
279
|
url: v3.z.string().url(),
|
|
@@ -278,7 +282,9 @@ var toolSchemas = {
|
|
|
278
282
|
body: v3.z.string().optional(),
|
|
279
283
|
idempotency_key: v3.z.string().optional(),
|
|
280
284
|
/** Legacy spelling, accepted during the #2366 window. Warns; do not use. */
|
|
281
|
-
idempotencyKey: v3.z.string().optional()
|
|
285
|
+
idempotencyKey: v3.z.string().optional(),
|
|
286
|
+
/** #3329: spend against an open task budget instead of the agent's period budget. */
|
|
287
|
+
task_budget_id: v3.z.string().min(1).optional()
|
|
282
288
|
},
|
|
283
289
|
haven_resume_x402_payment: {
|
|
284
290
|
payment_id: v3.z.string().optional(),
|
|
@@ -310,9 +316,56 @@ var toolSchemas = {
|
|
|
310
316
|
},
|
|
311
317
|
haven_verify_receipt: {
|
|
312
318
|
receipt: v3.z.unknown()
|
|
319
|
+
},
|
|
320
|
+
// #3329: a budget for one task that ends by itself — a short-lived child of
|
|
321
|
+
// the agent's own budget, capped and time-boxed independently of the period
|
|
322
|
+
// reset.
|
|
323
|
+
haven_open_task_budget: {
|
|
324
|
+
max_amount_human: v3.z.string().min(1),
|
|
325
|
+
ttl_minutes: v3.z.number().int().min(1).max(1440),
|
|
326
|
+
recipient: v3.z.string().optional(),
|
|
327
|
+
label: v3.z.string().max(120).optional(),
|
|
328
|
+
token: v3.z.string().optional()
|
|
329
|
+
},
|
|
330
|
+
haven_close_task_budget: {
|
|
331
|
+
task_budget_id: v3.z.string().min(1)
|
|
332
|
+
},
|
|
333
|
+
// #3329: relays a signature from the local signer — either the open/close
|
|
334
|
+
// signature for a task budget, or (schema parity with the hosted surface)
|
|
335
|
+
// a direct-payment signature by payment_id. Exactly one of task_budget_id /
|
|
336
|
+
// payment_id, never both or neither.
|
|
337
|
+
haven_submit: {
|
|
338
|
+
task_budget_id: v3.z.string().min(1).optional(),
|
|
339
|
+
payment_id: v3.z.string().min(1).optional(),
|
|
340
|
+
signature: v3.z.string().regex(/^0x[0-9a-fA-F]+$/, "signature must be a 0x-prefixed hex string")
|
|
313
341
|
}
|
|
314
342
|
// #3101: keys survive on the type (see the hosted server's contracts.ts).
|
|
315
343
|
};
|
|
344
|
+
var OPEN_TASK_BUDGET_DESCRIPTION = [
|
|
345
|
+
"Open a budget for one task that ends by itself: a spending cap, good for at most ttl_minutes,",
|
|
346
|
+
"reserved out of the agent's own budget and separate from its period reset.",
|
|
347
|
+
'Pass max_amount_human (whole tokens, e.g. "5" for 5 USDC), ttl_minutes (1-1440), and optionally',
|
|
348
|
+
"recipient (pins every payment against this task budget to one address), label, and token",
|
|
349
|
+
'(defaults to USDC). Returns { task_budget, next_action: "sign", next_tool, next_arguments } \u2014',
|
|
350
|
+
"call next_tool with next_arguments EXACTLY as given to get a signature, then relay it with",
|
|
351
|
+
"haven_submit. Spending anything above the reserved amount, past the deadline, or to a",
|
|
352
|
+
"different recipient than the one pinned here is declined on the spot \u2014 nothing is queued."
|
|
353
|
+
].join(" ");
|
|
354
|
+
var CLOSE_TASK_BUDGET_DESCRIPTION = [
|
|
355
|
+
"End a task budget early, before its deadline, releasing whatever of its cap was unspent back",
|
|
356
|
+
"to the agent's own budget. Pass task_budget_id. If the budget was never signed (still pending),",
|
|
357
|
+
'this ends it immediately with { task_budget, status: "closed" } and nothing was ever reserved',
|
|
358
|
+
'on-chain. Otherwise it returns a signature request: { task_budget, next_action: "sign",',
|
|
359
|
+
"next_tool, next_arguments } \u2014 call next_tool with next_arguments EXACTLY as given, then relay",
|
|
360
|
+
"the signature with haven_submit. A task budget past its own deadline closes immediately, the",
|
|
361
|
+
"same as the pending case."
|
|
362
|
+
].join(" ");
|
|
363
|
+
var SUBMIT_DESCRIPTION = [
|
|
364
|
+
"Relay a signature from the local signer. Pass exactly one of task_budget_id (from",
|
|
365
|
+
"haven_open_task_budget or haven_close_task_budget) or payment_id \u2014 never both, never neither \u2014",
|
|
366
|
+
"plus signature. For a task budget this opens or closes it on-chain and returns { task_budget,",
|
|
367
|
+
'status }; a close in progress may return { task_budget, status: "closed", close_tx_hash }.'
|
|
368
|
+
].join(" ");
|
|
316
369
|
var toolDescriptions = {
|
|
317
370
|
haven_send: sdk.composeDescription(sdk.toolDescriptions.send),
|
|
318
371
|
haven_pay_mcp_tool: sdk.composeDescription(sdk.toolDescriptions.payMcpTool),
|
|
@@ -328,7 +381,10 @@ var toolDescriptions = {
|
|
|
328
381
|
haven_discover_tools: sdk.composeDescription(sdk.toolDescriptions.discoverTools),
|
|
329
382
|
haven_submit_catalog_entry: sdk.composeDescription(sdk.toolDescriptions.submitCatalogEntry),
|
|
330
383
|
haven_list_receipts: sdk.composeDescription(sdk.toolDescriptions.listReceipts),
|
|
331
|
-
haven_verify_receipt: sdk.composeDescription(sdk.toolDescriptions.verifyReceipt)
|
|
384
|
+
haven_verify_receipt: sdk.composeDescription(sdk.toolDescriptions.verifyReceipt),
|
|
385
|
+
haven_open_task_budget: OPEN_TASK_BUDGET_DESCRIPTION,
|
|
386
|
+
haven_close_task_budget: CLOSE_TASK_BUDGET_DESCRIPTION,
|
|
387
|
+
haven_submit: SUBMIT_DESCRIPTION
|
|
332
388
|
};
|
|
333
389
|
function createToolHandlers(haven) {
|
|
334
390
|
return {
|
|
@@ -344,7 +400,10 @@ function createToolHandlers(haven) {
|
|
|
344
400
|
to: args.recipient,
|
|
345
401
|
// #1207: was accepted by the schema but silently dropped — now
|
|
346
402
|
// carried to the backend's replay contract.
|
|
347
|
-
idempotencyKey: args.idempotencyKey
|
|
403
|
+
idempotencyKey: args.idempotencyKey,
|
|
404
|
+
// #3329: spend against an open task budget instead of the
|
|
405
|
+
// agent's period budget, when the caller names one.
|
|
406
|
+
...typeof args.task_budget_id === "string" ? { taskBudgetId: args.task_budget_id } : {}
|
|
348
407
|
});
|
|
349
408
|
return {
|
|
350
409
|
payment_id: result.paymentId,
|
|
@@ -442,7 +501,10 @@ function createToolHandlers(haven) {
|
|
|
442
501
|
);
|
|
443
502
|
}
|
|
444
503
|
return runTool(async () => {
|
|
445
|
-
const response = await haven.payX402Quote(args.quote, {
|
|
504
|
+
const response = await haven.payX402Quote(args.quote, {
|
|
505
|
+
idempotencyKey: args.idempotencyKey,
|
|
506
|
+
...typeof args.task_budget_id === "string" ? { taskBudgetId: args.task_budget_id } : {}
|
|
507
|
+
});
|
|
446
508
|
return responsePayload(response);
|
|
447
509
|
}, warnings);
|
|
448
510
|
},
|
|
@@ -451,7 +513,10 @@ function createToolHandlers(haven) {
|
|
|
451
513
|
if ("success" in pf) return pf;
|
|
452
514
|
const { args, warnings } = pf;
|
|
453
515
|
return runTool(async () => {
|
|
454
|
-
const response = await haven.fetch(args.url, requestInit(args), {
|
|
516
|
+
const response = await haven.fetch(args.url, requestInit(args), {
|
|
517
|
+
idempotencyKey: args.idempotencyKey,
|
|
518
|
+
...typeof args.task_budget_id === "string" ? { taskBudgetId: args.task_budget_id } : {}
|
|
519
|
+
});
|
|
455
520
|
return responsePayload(response);
|
|
456
521
|
}, warnings);
|
|
457
522
|
},
|
|
@@ -552,6 +617,73 @@ function createToolHandlers(haven) {
|
|
|
552
617
|
haven_verify_receipt: async (input) => {
|
|
553
618
|
const args = objectInput("haven_verify_receipt", input);
|
|
554
619
|
return runTool(async () => sdk.verifyPaymentReceipt(args.receipt));
|
|
620
|
+
},
|
|
621
|
+
haven_open_task_budget: async (input) => {
|
|
622
|
+
const args = objectInput("haven_open_task_budget", input);
|
|
623
|
+
return runTool(async () => {
|
|
624
|
+
const token = await resolveTaskBudgetToken(haven, typeof args.token === "string" ? args.token : void 0);
|
|
625
|
+
const maxAmountAtomic = humanToAtomicOrNull(args.max_amount_human, token.decimals);
|
|
626
|
+
if (maxAmountAtomic === null) {
|
|
627
|
+
throw new sdk.HavenApiError(
|
|
628
|
+
`max_amount_human ("${args.max_amount_human}") is not a valid decimal amount for ${token.symbol} (${token.decimals} decimal places). Nothing was reserved.`,
|
|
629
|
+
400
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
const result = await haven.openTaskBudget({
|
|
633
|
+
tokenAddress: token.address,
|
|
634
|
+
maxAmountAtomic,
|
|
635
|
+
ttlSeconds: Number(args.ttl_minutes) * 60,
|
|
636
|
+
recipientAddress: typeof args.recipient === "string" ? args.recipient : void 0,
|
|
637
|
+
label: typeof args.label === "string" ? args.label : void 0
|
|
638
|
+
});
|
|
639
|
+
return {
|
|
640
|
+
task_budget: result.taskBudget,
|
|
641
|
+
next_action: "sign",
|
|
642
|
+
next_tool: "mcp__haven-signer__haven_sign",
|
|
643
|
+
next_arguments: { task_budget_id: result.taskBudget.id }
|
|
644
|
+
};
|
|
645
|
+
});
|
|
646
|
+
},
|
|
647
|
+
haven_close_task_budget: async (input) => {
|
|
648
|
+
const args = objectInput("haven_close_task_budget", input);
|
|
649
|
+
return runTool(async () => {
|
|
650
|
+
const result = await haven.closeTaskBudget(args.task_budget_id);
|
|
651
|
+
if (result.status === "closed") {
|
|
652
|
+
return { task_budget: result.taskBudget, status: "closed" };
|
|
653
|
+
}
|
|
654
|
+
return {
|
|
655
|
+
task_budget: result.taskBudget,
|
|
656
|
+
next_action: "sign",
|
|
657
|
+
next_tool: "mcp__haven-signer__haven_sign",
|
|
658
|
+
next_arguments: { task_budget_id: args.task_budget_id }
|
|
659
|
+
};
|
|
660
|
+
});
|
|
661
|
+
},
|
|
662
|
+
haven_submit: async (input) => {
|
|
663
|
+
const args = objectInput("haven_submit", input);
|
|
664
|
+
const hasTaskBudget = typeof args.task_budget_id === "string" && args.task_budget_id.length > 0;
|
|
665
|
+
const hasPayment = typeof args.payment_id === "string" && args.payment_id.length > 0;
|
|
666
|
+
if (hasTaskBudget === hasPayment) {
|
|
667
|
+
return {
|
|
668
|
+
success: false,
|
|
669
|
+
code: "INVALID_INPUT",
|
|
670
|
+
message: "haven_submit takes exactly one of task_budget_id or payment_id, never both or neither. Nothing was relayed."
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
return runTool(async () => {
|
|
674
|
+
if (hasTaskBudget) {
|
|
675
|
+
const result = await haven.submitTaskBudget(args.task_budget_id, args.signature);
|
|
676
|
+
return {
|
|
677
|
+
task_budget: result.taskBudget,
|
|
678
|
+
status: result.status,
|
|
679
|
+
...result.closeTxHash !== void 0 ? { close_tx_hash: result.closeTxHash } : {}
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
throw new sdk.HavenApiError(
|
|
683
|
+
"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.",
|
|
684
|
+
400
|
|
685
|
+
);
|
|
686
|
+
});
|
|
555
687
|
}
|
|
556
688
|
};
|
|
557
689
|
async function resumeState(args, rail) {
|
|
@@ -643,6 +775,28 @@ async function responsePayload(response) {
|
|
|
643
775
|
body: parseMaybeJson(text)
|
|
644
776
|
};
|
|
645
777
|
}
|
|
778
|
+
async function resolveTaskBudgetToken(haven, symbolOrAddress) {
|
|
779
|
+
const wanted = (symbolOrAddress ?? "USDC").toLowerCase();
|
|
780
|
+
const { allowances } = await haven.getAllowances();
|
|
781
|
+
const isAddress = /^0x[0-9a-fA-F]{40}$/.test(wanted);
|
|
782
|
+
const match = isAddress ? allowances.find((a) => a.tokenAddress.toLowerCase() === wanted) : allowances.find((a) => (a.tokenSymbol ?? "").toLowerCase() === wanted);
|
|
783
|
+
if (!match) {
|
|
784
|
+
const known = allowances.map((a) => `${a.tokenSymbol} (${a.tokenAddress})`);
|
|
785
|
+
throw new sdk.HavenApiError(
|
|
786
|
+
`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.`,
|
|
787
|
+
400
|
|
788
|
+
);
|
|
789
|
+
}
|
|
790
|
+
const resolved = sdk.resolveTokenFromAddress(match.tokenAddress);
|
|
791
|
+
return { address: match.tokenAddress, symbol: match.tokenSymbol, decimals: resolved?.decimals ?? 6 };
|
|
792
|
+
}
|
|
793
|
+
function humanToAtomicOrNull(human, decimals) {
|
|
794
|
+
if (!/^[0-9]+(\.[0-9]+)?$/.test(human)) return null;
|
|
795
|
+
const [whole, frac = ""] = human.split(".");
|
|
796
|
+
if (frac.length > decimals) return null;
|
|
797
|
+
const atomic = BigInt(whole || "0") * 10n ** BigInt(decimals) + BigInt(frac.padEnd(decimals, "0") || "0");
|
|
798
|
+
return atomic.toString();
|
|
799
|
+
}
|
|
646
800
|
function parseMaybeJson(text) {
|
|
647
801
|
if (!text) return null;
|
|
648
802
|
try {
|
|
@@ -954,7 +1108,7 @@ async function resolveHavenClient(options = {}) {
|
|
|
954
1108
|
return { client, credentials };
|
|
955
1109
|
}
|
|
956
1110
|
var MCP_NAME = "@haven_ai/mcp";
|
|
957
|
-
var MCP_VERSION = "0.0.0-dev.
|
|
1111
|
+
var MCP_VERSION = "0.0.0-dev.202609260843.267f647";
|
|
958
1112
|
var MCP_INSTRUCTIONS = [
|
|
959
1113
|
"Haven local MCP server: signs in-process with the delegate key it holds on",
|
|
960
1114
|
"this machine \u2014 the key never leaves this process. Call haven_get_agent",
|