@haven_ai/mcp 0.6.0-alpha.0 → 0.8.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 +19 -20
- package/dist/cli.cjs +222 -53
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +223 -54
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +225 -53
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +82 -10
- package/dist/index.d.ts +82 -10
- package/dist/index.js +224 -55
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
3
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
-
import { composeDescription, toolDescriptions as toolDescriptions$1, HavenClient, havenClientIdentity, isSupportedNodeVersion, unsupportedNodeVersionMessage,
|
|
4
|
+
import { composeDescription, toolDescriptions as toolDescriptions$1, HavenClient, havenClientIdentity, isSupportedNodeVersion, unsupportedNodeVersionMessage, resolveTokenBySymbol, formatTokenAmount, HavenApiError, HavenPaymentStateError, HavenSigningError, AgentPaymentNextAction, HavenError, AgentPaymentFailureCode, verifyPaymentReceipt, discoverMerchantMcpUrl, sameUrl, resolveTokenFromAddress, TASK_SIGN_CONTEXT_VERSION, connectorUpgradeCommand } from '@haven_ai/sdk';
|
|
5
5
|
import { readFile, mkdir, writeFile, stat } from 'fs/promises';
|
|
6
6
|
import { z } from 'zod/v3';
|
|
7
7
|
import { createHash } from 'crypto';
|
|
@@ -236,6 +236,17 @@ async function warnIfCredentialFilePermissive(path, log = (message) => process.s
|
|
|
236
236
|
);
|
|
237
237
|
}
|
|
238
238
|
}
|
|
239
|
+
var TASK_BUDGET_MIN_SIGNER_VERSION = "0.6.0-alpha.0";
|
|
240
|
+
var SIGNER_CAPABILITY_KEY = "haven/signer-compatibility";
|
|
241
|
+
function taskSignerCompatibilityNotice() {
|
|
242
|
+
return {
|
|
243
|
+
task_sign_context_version: TASK_SIGN_CONTEXT_VERSION,
|
|
244
|
+
min_signer_version: TASK_BUDGET_MIN_SIGNER_VERSION,
|
|
245
|
+
signer_capability: SIGNER_CAPABILITY_KEY,
|
|
246
|
+
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 \`${connectorUpgradeCommand()}\` and the repair line it prints, then open the budget again.`,
|
|
247
|
+
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 \`${connectorUpgradeCommand()}\` and the repair line it prints, then open the budget again. There is no relay fallback for this signing context.`
|
|
248
|
+
};
|
|
249
|
+
}
|
|
239
250
|
var headersSchema = z.record(z.string(), z.string()).optional();
|
|
240
251
|
var toolSchemas = {
|
|
241
252
|
haven_send: {
|
|
@@ -243,18 +254,24 @@ var toolSchemas = {
|
|
|
243
254
|
recipient: z.string().min(1),
|
|
244
255
|
amount: z.string().min(1),
|
|
245
256
|
idempotency_key: z.string().optional(),
|
|
246
|
-
/**
|
|
247
|
-
idempotencyKey: z.string().optional(),
|
|
257
|
+
/** REMOVED (#3411): declared only so the resolver can refuse it by name, never accepted. */
|
|
258
|
+
idempotencyKey: z.string().optional().describe("REMOVED (#3411): refused \u2014 send idempotency_key"),
|
|
248
259
|
/** #3329: spend against an open task budget instead of the agent's period budget. */
|
|
249
|
-
task_budget_id: z.string().min(1).optional()
|
|
260
|
+
task_budget_id: z.string().min(1).optional(),
|
|
261
|
+
/**
|
|
262
|
+
* #3330: spend against an open sub-budget this agent HOLDS (it is the
|
|
263
|
+
* sub-agent B) instead of the agent's own budget. Mutually exclusive
|
|
264
|
+
* with task_budget_id — the backend refuses a body naming both.
|
|
265
|
+
*/
|
|
266
|
+
sub_budget_id: z.string().min(1).optional()
|
|
250
267
|
},
|
|
251
268
|
haven_pay_mcp_tool: {
|
|
252
269
|
merchant_url: z.string().url(),
|
|
253
270
|
tool_name: z.string().min(1),
|
|
254
271
|
arguments: z.record(z.string(), z.unknown()).optional(),
|
|
255
272
|
idempotency_key: z.string().optional(),
|
|
256
|
-
/**
|
|
257
|
-
idempotencyKey: z.string().optional()
|
|
273
|
+
/** REMOVED (#3411): declared only so the resolver can refuse it by name, never accepted. */
|
|
274
|
+
idempotencyKey: z.string().optional().describe("REMOVED (#3411): refused \u2014 send idempotency_key")
|
|
258
275
|
},
|
|
259
276
|
haven_quote_x402: {
|
|
260
277
|
url: z.string().url(),
|
|
@@ -262,16 +279,18 @@ var toolSchemas = {
|
|
|
262
279
|
headers: headersSchema,
|
|
263
280
|
body: z.string().optional(),
|
|
264
281
|
idempotency_key: z.string().optional(),
|
|
265
|
-
/**
|
|
266
|
-
idempotencyKey: z.string().optional()
|
|
282
|
+
/** REMOVED (#3411): declared only so the resolver can refuse it by name, never accepted. */
|
|
283
|
+
idempotencyKey: z.string().optional().describe("REMOVED (#3411): refused \u2014 send idempotency_key")
|
|
267
284
|
},
|
|
268
285
|
haven_pay_x402_quote: {
|
|
269
286
|
quote: z.unknown(),
|
|
270
287
|
idempotency_key: z.string().optional(),
|
|
271
|
-
/**
|
|
272
|
-
idempotencyKey: z.string().optional(),
|
|
288
|
+
/** REMOVED (#3411): declared only so the resolver can refuse it by name, never accepted. */
|
|
289
|
+
idempotencyKey: z.string().optional().describe("REMOVED (#3411): refused \u2014 send idempotency_key"),
|
|
273
290
|
/** #3329: spend against an open task budget instead of the agent's period budget. */
|
|
274
|
-
task_budget_id: z.string().min(1).optional()
|
|
291
|
+
task_budget_id: z.string().min(1).optional(),
|
|
292
|
+
/** #3330: spend against an open sub-budget this agent holds (it is sub-agent B). Mutually exclusive with task_budget_id. */
|
|
293
|
+
sub_budget_id: z.string().min(1).optional()
|
|
275
294
|
},
|
|
276
295
|
haven_pay_x402: {
|
|
277
296
|
url: z.string().url(),
|
|
@@ -279,10 +298,12 @@ var toolSchemas = {
|
|
|
279
298
|
headers: headersSchema,
|
|
280
299
|
body: z.string().optional(),
|
|
281
300
|
idempotency_key: z.string().optional(),
|
|
282
|
-
/**
|
|
283
|
-
idempotencyKey: z.string().optional(),
|
|
301
|
+
/** REMOVED (#3411): declared only so the resolver can refuse it by name, never accepted. */
|
|
302
|
+
idempotencyKey: z.string().optional().describe("REMOVED (#3411): refused \u2014 send idempotency_key"),
|
|
284
303
|
/** #3329: spend against an open task budget instead of the agent's period budget. */
|
|
285
|
-
task_budget_id: z.string().min(1).optional()
|
|
304
|
+
task_budget_id: z.string().min(1).optional(),
|
|
305
|
+
/** #3330: spend against an open sub-budget this agent holds (it is sub-agent B). Mutually exclusive with task_budget_id. */
|
|
306
|
+
sub_budget_id: z.string().min(1).optional()
|
|
286
307
|
},
|
|
287
308
|
haven_resume_x402_payment: {
|
|
288
309
|
payment_id: z.string().optional(),
|
|
@@ -310,7 +331,9 @@ var toolSchemas = {
|
|
|
310
331
|
haven_list_receipts: {
|
|
311
332
|
limit: z.number().int().min(1).max(100).optional(),
|
|
312
333
|
/** #3128: the previous page's next_cursor (a receipt id). */
|
|
313
|
-
cursor: z.string().min(1).optional()
|
|
334
|
+
cursor: z.string().min(1).optional(),
|
|
335
|
+
/** #3423: drop each row's payload echoes; the default shape is unchanged. */
|
|
336
|
+
compact: z.boolean().optional().describe("true drops challengePayload, selectedPayment and protocolReceiptPayload from each row; omit for the full row.")
|
|
314
337
|
},
|
|
315
338
|
haven_verify_receipt: {
|
|
316
339
|
receipt: z.unknown()
|
|
@@ -328,12 +351,19 @@ var toolSchemas = {
|
|
|
328
351
|
haven_close_task_budget: {
|
|
329
352
|
task_budget_id: z.string().min(1)
|
|
330
353
|
},
|
|
331
|
-
// #
|
|
332
|
-
//
|
|
333
|
-
//
|
|
334
|
-
|
|
354
|
+
// #3518: read ONE task budget by id, any status — the status check a close
|
|
355
|
+
// refusal's "re-check the budget's status" points at, on the local surface
|
|
356
|
+
// too (schema and wording parity with the hosted read).
|
|
357
|
+
haven_get_task_budget: {
|
|
358
|
+
task_budget_id: z.string().min(1)
|
|
359
|
+
},
|
|
360
|
+
// #3329 / #3506: relays a signature from the local signer — the open/close
|
|
361
|
+
// signature for a task budget or a sub-budget row, or (schema parity with
|
|
362
|
+
// the hosted surface) a direct-payment signature by payment_id. Exactly one
|
|
363
|
+
// of task_budget_id / sub_budget_id / payment_id, never two or none.
|
|
335
364
|
haven_submit: {
|
|
336
365
|
task_budget_id: z.string().min(1).optional(),
|
|
366
|
+
sub_budget_id: z.string().min(1).optional(),
|
|
337
367
|
payment_id: z.string().min(1).optional(),
|
|
338
368
|
signature: z.string().regex(/^0x[0-9a-fA-F]+$/, "signature must be a 0x-prefixed hex string")
|
|
339
369
|
}
|
|
@@ -361,11 +391,19 @@ var CLOSE_TASK_BUDGET_DESCRIPTION = [
|
|
|
361
391
|
"the signature with haven_submit. A task budget past its own deadline closes immediately, the",
|
|
362
392
|
"same as the pending case."
|
|
363
393
|
].join(" ");
|
|
394
|
+
var GET_TASK_BUDGET_DESCRIPTION = [
|
|
395
|
+
"Read one task budget by id, any status.",
|
|
396
|
+
"Returns { task_budget } with status (pending | open | closing | closed), isExpired, maxDisplay, recipientAddress, label and expiresAt \u2014",
|
|
397
|
+
"the check to run after a close or submit refusal says to re-check the budget's status.",
|
|
398
|
+
"haven_get_agent lists live task budgets only; this reads any one, closed or expired included."
|
|
399
|
+
].join(" ");
|
|
364
400
|
var SUBMIT_DESCRIPTION = [
|
|
365
401
|
"Relay a signature from the local signer. Pass exactly one of task_budget_id (from",
|
|
366
|
-
"haven_open_task_budget or haven_close_task_budget)
|
|
367
|
-
"
|
|
368
|
-
|
|
402
|
+
"haven_open_task_budget or haven_close_task_budget), sub_budget_id (from haven_get_agent",
|
|
403
|
+
"pendingSubBudgetSignatures[]) or payment_id \u2014 never two, never none \u2014 plus signature.",
|
|
404
|
+
"For a task budget this opens or closes it on-chain and returns { task_budget,",
|
|
405
|
+
'status }; a close in progress may return { task_budget, status: "closed", close_tx_hash }.',
|
|
406
|
+
"A sub_budget_id returns { sub_budget, status } with the next signer step, if one is owed."
|
|
369
407
|
].join(" ");
|
|
370
408
|
var toolDescriptions = {
|
|
371
409
|
haven_send: composeDescription(toolDescriptions$1.send),
|
|
@@ -385,8 +423,101 @@ var toolDescriptions = {
|
|
|
385
423
|
haven_verify_receipt: composeDescription(toolDescriptions$1.verifyReceipt),
|
|
386
424
|
haven_open_task_budget: OPEN_TASK_BUDGET_DESCRIPTION,
|
|
387
425
|
haven_close_task_budget: CLOSE_TASK_BUDGET_DESCRIPTION,
|
|
426
|
+
haven_get_task_budget: GET_TASK_BUDGET_DESCRIPTION,
|
|
388
427
|
haven_submit: SUBMIT_DESCRIPTION
|
|
389
428
|
};
|
|
429
|
+
function subBudgetSignStep(subBudgetId) {
|
|
430
|
+
return {
|
|
431
|
+
next_action: "sign",
|
|
432
|
+
next_tool: "mcp__haven-signer__haven_sign",
|
|
433
|
+
next_arguments: { sub_budget_id: subBudgetId }
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
async function submitSubBudgetTool(haven, subBudgetId, signature) {
|
|
437
|
+
let result;
|
|
438
|
+
try {
|
|
439
|
+
result = await haven.submitSubBudget(subBudgetId, signature);
|
|
440
|
+
} catch (err) {
|
|
441
|
+
const code = err instanceof HavenApiError ? err.body?.error_code : void 0;
|
|
442
|
+
if (code === "close_needs_reprepare") {
|
|
443
|
+
let fresh;
|
|
444
|
+
try {
|
|
445
|
+
fresh = await haven.closeSubBudget(subBudgetId);
|
|
446
|
+
} catch (closeErr) {
|
|
447
|
+
return normalizeError(closeErr);
|
|
448
|
+
}
|
|
449
|
+
if (fresh.status === "closed") {
|
|
450
|
+
return {
|
|
451
|
+
success: true,
|
|
452
|
+
data: {
|
|
453
|
+
sub_budget: fresh.subBudget,
|
|
454
|
+
status: "closed",
|
|
455
|
+
next_action: "none",
|
|
456
|
+
next_tool_omitted_reason: "the sub-budget is closed on-chain; nothing further to sign or submit for it"
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
return {
|
|
461
|
+
success: false,
|
|
462
|
+
code: "CLOSE_NEEDS_REPREPARE",
|
|
463
|
+
message: "The stored close operation for this sub-budget was stale, so nothing was relayed. Haven prepared a fresh one: sign it with haven_sign, then call haven_submit again with the new signature.",
|
|
464
|
+
statusCode: 409,
|
|
465
|
+
...subBudgetSignStep(subBudgetId)
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
if (code === "close_outcome_unconfirmed") {
|
|
469
|
+
return {
|
|
470
|
+
success: false,
|
|
471
|
+
code: "CLOSE_OUTCOME_UNCONFIRMED",
|
|
472
|
+
message: "The close operation was sent but its outcome is not confirmed yet (on Base that can take tens of minutes). Wait, then call haven_submit again with the same arguments: it reports closed once the chain has finalised the disable, or prepares a fresh operation to sign if the earlier one did not land.",
|
|
473
|
+
statusCode: 502,
|
|
474
|
+
next_action: "check_status_later",
|
|
475
|
+
next_tool: "haven_submit",
|
|
476
|
+
next_arguments: { sub_budget_id: subBudgetId, signature }
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
return normalizeError(err);
|
|
480
|
+
}
|
|
481
|
+
return runTool(async () => {
|
|
482
|
+
const submitted = {
|
|
483
|
+
sub_budget: result.subBudget,
|
|
484
|
+
status: result.status,
|
|
485
|
+
...result.closeTxHash !== void 0 ? { close_tx_hash: result.closeTxHash } : {}
|
|
486
|
+
};
|
|
487
|
+
if (result.status === "closed") {
|
|
488
|
+
return {
|
|
489
|
+
...submitted,
|
|
490
|
+
next_action: "none",
|
|
491
|
+
next_tool_omitted_reason: "the sub-budget is closed on-chain; nothing further to sign or submit for it"
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
let pending;
|
|
495
|
+
try {
|
|
496
|
+
pending = await haven.listPendingSubBudgetSignatures();
|
|
497
|
+
} catch {
|
|
498
|
+
pending = null;
|
|
499
|
+
}
|
|
500
|
+
if (pending === null) {
|
|
501
|
+
return {
|
|
502
|
+
...submitted,
|
|
503
|
+
next_action: "check_status_later",
|
|
504
|
+
next_tool: "haven_get_agent",
|
|
505
|
+
next_arguments: {},
|
|
506
|
+
reason: "This sub-budget row is open, but Haven could not say whether its tree has another row awaiting your signature. Call haven_get_agent and sign any pendingSubBudgetSignatures row."
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
const tree = result.subBudget.parentSubBudgetId ?? result.subBudget.id;
|
|
510
|
+
const sibling = pending.find(
|
|
511
|
+
(row) => row.purpose === "open" && row.subBudgetId !== result.subBudget.id && (row.parentSubBudgetId ?? row.subBudgetId) === tree
|
|
512
|
+
);
|
|
513
|
+
if (sibling) return { ...submitted, ...subBudgetSignStep(sibling.subBudgetId) };
|
|
514
|
+
return {
|
|
515
|
+
...submitted,
|
|
516
|
+
next_action: "none",
|
|
517
|
+
next_tool_omitted_reason: "no other row of this sub-budget awaits your signature, so the sub-budget is fully open and the sub-agent can spend through its grant"
|
|
518
|
+
};
|
|
519
|
+
});
|
|
520
|
+
}
|
|
390
521
|
function createToolHandlers(haven) {
|
|
391
522
|
return {
|
|
392
523
|
haven_send: async (input) => {
|
|
@@ -399,12 +530,15 @@ function createToolHandlers(haven) {
|
|
|
399
530
|
token: args.asset,
|
|
400
531
|
amount: args.amount,
|
|
401
532
|
to: args.recipient,
|
|
402
|
-
// #1207: was accepted by the schema but silently dropped — now
|
|
403
|
-
// carried to the backend's replay contract.
|
|
404
|
-
idempotencyKey: args.
|
|
533
|
+
// #1207/#3411: was accepted by the schema but silently dropped — now
|
|
534
|
+
// carried to the backend's replay contract via the resolved carrier.
|
|
535
|
+
idempotencyKey: args.resolvedIdempotencyKey,
|
|
405
536
|
// #3329: spend against an open task budget instead of the
|
|
406
537
|
// agent's period budget, when the caller names one.
|
|
407
|
-
...typeof args.task_budget_id === "string" ? { taskBudgetId: args.task_budget_id } : {}
|
|
538
|
+
...typeof args.task_budget_id === "string" ? { taskBudgetId: args.task_budget_id } : {},
|
|
539
|
+
// #3330: spend through a sub-budget this agent holds (it is
|
|
540
|
+
// sub-agent B). Exactly one of the two ids, never both.
|
|
541
|
+
...typeof args.sub_budget_id === "string" ? { subBudgetId: args.sub_budget_id } : {}
|
|
408
542
|
});
|
|
409
543
|
return {
|
|
410
544
|
payment_id: result.paymentId,
|
|
@@ -442,7 +576,7 @@ function createToolHandlers(haven) {
|
|
|
442
576
|
body: JSON.stringify(envelope)
|
|
443
577
|
};
|
|
444
578
|
let merchantUrl = args.merchant_url;
|
|
445
|
-
const idempotencyKey = args.
|
|
579
|
+
const idempotencyKey = args.resolvedIdempotencyKey;
|
|
446
580
|
const attempt = () => haven.fetch(merchantUrl, init, { idempotencyKey });
|
|
447
581
|
let response = await attempt();
|
|
448
582
|
if (!response.ok) {
|
|
@@ -476,7 +610,7 @@ function createToolHandlers(haven) {
|
|
|
476
610
|
if ("success" in pf) return pf;
|
|
477
611
|
const { args, warnings } = pf;
|
|
478
612
|
try {
|
|
479
|
-
const data = await haven.quoteX402(args.url, requestInit(args), { idempotencyKey: args.
|
|
613
|
+
const data = await haven.quoteX402(args.url, requestInit(args), { idempotencyKey: args.resolvedIdempotencyKey });
|
|
480
614
|
return warnings.length > 0 ? { success: true, data, warnings } : { success: true, data };
|
|
481
615
|
} catch (err) {
|
|
482
616
|
return normalizeError(err);
|
|
@@ -503,8 +637,9 @@ function createToolHandlers(haven) {
|
|
|
503
637
|
}
|
|
504
638
|
return runTool(async () => {
|
|
505
639
|
const response = await haven.payX402Quote(args.quote, {
|
|
506
|
-
idempotencyKey: args.
|
|
507
|
-
...typeof args.task_budget_id === "string" ? { taskBudgetId: args.task_budget_id } : {}
|
|
640
|
+
idempotencyKey: args.resolvedIdempotencyKey,
|
|
641
|
+
...typeof args.task_budget_id === "string" ? { taskBudgetId: args.task_budget_id } : {},
|
|
642
|
+
...typeof args.sub_budget_id === "string" ? { subBudgetId: args.sub_budget_id } : {}
|
|
508
643
|
});
|
|
509
644
|
return responsePayload(response);
|
|
510
645
|
}, warnings);
|
|
@@ -515,8 +650,9 @@ function createToolHandlers(haven) {
|
|
|
515
650
|
const { args, warnings } = pf;
|
|
516
651
|
return runTool(async () => {
|
|
517
652
|
const response = await haven.fetch(args.url, requestInit(args), {
|
|
518
|
-
idempotencyKey: args.
|
|
519
|
-
...typeof args.task_budget_id === "string" ? { taskBudgetId: args.task_budget_id } : {}
|
|
653
|
+
idempotencyKey: args.resolvedIdempotencyKey,
|
|
654
|
+
...typeof args.task_budget_id === "string" ? { taskBudgetId: args.task_budget_id } : {},
|
|
655
|
+
...typeof args.sub_budget_id === "string" ? { subBudgetId: args.sub_budget_id } : {}
|
|
520
656
|
});
|
|
521
657
|
return responsePayload(response);
|
|
522
658
|
}, warnings);
|
|
@@ -537,7 +673,17 @@ function createToolHandlers(haven) {
|
|
|
537
673
|
const args = objectInput("haven_get_resume_state", input);
|
|
538
674
|
return runTool(async () => haven.getResumeState(args.payment_id));
|
|
539
675
|
},
|
|
540
|
-
haven_get_agent: async () => runTool(async () =>
|
|
676
|
+
haven_get_agent: async () => runTool(async () => {
|
|
677
|
+
const summary = await haven.getAgentSummary();
|
|
678
|
+
if (!Array.isArray(summary.pendingSubBudgetSignatures)) return summary;
|
|
679
|
+
return {
|
|
680
|
+
...summary,
|
|
681
|
+
pendingSubBudgetSignatures: summary.pendingSubBudgetSignatures.map((row) => ({
|
|
682
|
+
...row,
|
|
683
|
+
...subBudgetSignStep(row.subBudgetId)
|
|
684
|
+
}))
|
|
685
|
+
};
|
|
686
|
+
}),
|
|
541
687
|
haven_get_allowances: async () => runTool(async () => haven.getAllowances()),
|
|
542
688
|
haven_sweep_delegate: async () => runTool(async () => haven.sweepDelegate()),
|
|
543
689
|
haven_discover_tools: async (input) => {
|
|
@@ -613,7 +759,9 @@ function createToolHandlers(haven) {
|
|
|
613
759
|
},
|
|
614
760
|
haven_list_receipts: async (input) => {
|
|
615
761
|
const args = objectInput("haven_list_receipts", input);
|
|
616
|
-
return runTool(
|
|
762
|
+
return runTool(
|
|
763
|
+
async () => haven.listReceiptsPage({ limit: args.limit, cursor: args.cursor, compact: args.compact === true })
|
|
764
|
+
);
|
|
617
765
|
},
|
|
618
766
|
haven_verify_receipt: async (input) => {
|
|
619
767
|
const args = objectInput("haven_verify_receipt", input);
|
|
@@ -641,7 +789,8 @@ function createToolHandlers(haven) {
|
|
|
641
789
|
task_budget: result.taskBudget,
|
|
642
790
|
next_action: "sign",
|
|
643
791
|
next_tool: "mcp__haven-signer__haven_sign",
|
|
644
|
-
next_arguments: { task_budget_id: result.taskBudget.id }
|
|
792
|
+
next_arguments: { task_budget_id: result.taskBudget.id },
|
|
793
|
+
signer_compatibility: taskSignerCompatibilityNotice()
|
|
645
794
|
};
|
|
646
795
|
});
|
|
647
796
|
},
|
|
@@ -656,21 +805,37 @@ function createToolHandlers(haven) {
|
|
|
656
805
|
task_budget: result.taskBudget,
|
|
657
806
|
next_action: "sign",
|
|
658
807
|
next_tool: "mcp__haven-signer__haven_sign",
|
|
659
|
-
next_arguments: { task_budget_id: args.task_budget_id }
|
|
808
|
+
next_arguments: { task_budget_id: args.task_budget_id },
|
|
809
|
+
signer_compatibility: taskSignerCompatibilityNotice()
|
|
660
810
|
};
|
|
661
811
|
});
|
|
662
812
|
},
|
|
813
|
+
// #3518: read ONE task budget by id, any status — the status check a
|
|
814
|
+
// close/submit refusal's "re-check the budget's status" points at. The
|
|
815
|
+
// SDK relays `GET /task-budgets/:id` (the backend route answers any
|
|
816
|
+
// status) and a 404 carries the backend's own message through runTool.
|
|
817
|
+
haven_get_task_budget: async (input) => {
|
|
818
|
+
const args = objectInput("haven_get_task_budget", input);
|
|
819
|
+
return runTool(async () => {
|
|
820
|
+
const taskBudget = await haven.getTaskBudget(args.task_budget_id);
|
|
821
|
+
return { task_budget: taskBudget };
|
|
822
|
+
});
|
|
823
|
+
},
|
|
663
824
|
haven_submit: async (input) => {
|
|
664
825
|
const args = objectInput("haven_submit", input);
|
|
665
826
|
const hasTaskBudget = typeof args.task_budget_id === "string" && args.task_budget_id.length > 0;
|
|
827
|
+
const hasSubBudget = typeof args.sub_budget_id === "string" && args.sub_budget_id.length > 0;
|
|
666
828
|
const hasPayment = typeof args.payment_id === "string" && args.payment_id.length > 0;
|
|
667
|
-
if (hasTaskBudget
|
|
829
|
+
if ([hasTaskBudget, hasSubBudget, hasPayment].filter(Boolean).length !== 1) {
|
|
668
830
|
return {
|
|
669
831
|
success: false,
|
|
670
832
|
code: "INVALID_INPUT",
|
|
671
|
-
message: "haven_submit takes exactly one of task_budget_id or
|
|
833
|
+
message: "haven_submit takes exactly one of payment_id, task_budget_id or sub_budget_id, never two or none. Nothing was relayed."
|
|
672
834
|
};
|
|
673
835
|
}
|
|
836
|
+
if (hasSubBudget) {
|
|
837
|
+
return submitSubBudgetTool(haven, args.sub_budget_id, args.signature);
|
|
838
|
+
}
|
|
674
839
|
return runTool(async () => {
|
|
675
840
|
if (hasTaskBudget) {
|
|
676
841
|
const result = await haven.submitTaskBudget(args.task_budget_id, args.signature);
|
|
@@ -681,7 +846,7 @@ function createToolHandlers(haven) {
|
|
|
681
846
|
};
|
|
682
847
|
}
|
|
683
848
|
throw new HavenApiError(
|
|
684
|
-
"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
|
|
849
|
+
"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 or sub_budget_id to relay a budget open/close signature.",
|
|
685
850
|
400
|
|
686
851
|
);
|
|
687
852
|
});
|
|
@@ -713,28 +878,20 @@ function preflight(name, input) {
|
|
|
713
878
|
}
|
|
714
879
|
const idem = resolveIdempotencyKey(args);
|
|
715
880
|
if ("success" in idem) return idem;
|
|
716
|
-
if (idem.key !== void 0) args = { ...args,
|
|
881
|
+
if (idem.key !== void 0) args = { ...args, resolvedIdempotencyKey: idem.key };
|
|
717
882
|
return { args, warnings: idem.warnings };
|
|
718
883
|
}
|
|
719
884
|
function resolveIdempotencyKey(args) {
|
|
720
885
|
const modern = typeof args.idempotency_key === "string" ? args.idempotency_key : void 0;
|
|
721
886
|
const legacy = typeof args.idempotencyKey === "string" ? args.idempotencyKey : void 0;
|
|
722
|
-
if (
|
|
887
|
+
if (legacy !== void 0) {
|
|
723
888
|
return {
|
|
724
889
|
success: false,
|
|
725
|
-
code: "
|
|
726
|
-
message: "
|
|
890
|
+
code: "IDEMPOTENCY_KEY_RENAMED",
|
|
891
|
+
message: "idempotencyKey has been renamed to idempotency_key and is no longer accepted. Send idempotency_key instead. Nothing was contacted or spent."
|
|
727
892
|
};
|
|
728
893
|
}
|
|
729
894
|
if (modern !== void 0) return { key: modern, warnings: [] };
|
|
730
|
-
if (legacy !== void 0) {
|
|
731
|
-
return {
|
|
732
|
-
key: legacy,
|
|
733
|
-
warnings: [
|
|
734
|
-
"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."
|
|
735
|
-
]
|
|
736
|
-
};
|
|
737
|
-
}
|
|
738
895
|
return { warnings: [] };
|
|
739
896
|
}
|
|
740
897
|
function buildMcpToolsCallEnvelope(toolName, args) {
|
|
@@ -965,7 +1122,7 @@ function renderConsentBlock(input, hash) {
|
|
|
965
1122
|
lines.push("signed delegation, not by Haven):");
|
|
966
1123
|
for (const a of input.allowanceSummary) {
|
|
967
1124
|
const reset = a.resetMinutes ? ` per ${a.resetMinutes} min` : " (no reset)";
|
|
968
|
-
lines.push(` \u2022 up to ${a
|
|
1125
|
+
lines.push(` \u2022 up to ${describeBudgetAmount(a, input.chainId)}${reset}`);
|
|
969
1126
|
}
|
|
970
1127
|
}
|
|
971
1128
|
lines.push("");
|
|
@@ -1062,7 +1219,14 @@ async function consentInputFromClient(haven, seed, toolNames) {
|
|
|
1062
1219
|
}
|
|
1063
1220
|
const liveAllowanceSummary = list.map((a) => ({
|
|
1064
1221
|
token: a.tokenSymbol ?? "UNKNOWN",
|
|
1065
|
-
|
|
1222
|
+
// #3410: `onchain.amount` is the atomic budget (`budget_atomic` on the
|
|
1223
|
+
// backend). The old `a.configuredAmount` fallback here was unreachable
|
|
1224
|
+
// — `onchain` is required in the SDK types and dereferenced
|
|
1225
|
+
// unconditionally, so a response without it throws into the seed path —
|
|
1226
|
+
// and it was the one HUMAN-decimal value in this chain; a human number
|
|
1227
|
+
// must never reach an atomic field. '0' keeps the (defensive) shape for
|
|
1228
|
+
// a malformed onchain object without inventing a unit.
|
|
1229
|
+
amount: a.onchain?.amount ?? "0",
|
|
1066
1230
|
resetMinutes: typeof a.onchain?.resetTimeMin === "number" ? a.onchain.resetTimeMin : typeof a.resetPeriodMin === "number" ? a.resetPeriodMin : null
|
|
1067
1231
|
}));
|
|
1068
1232
|
allowanceSummary = liveAllowanceSummary;
|
|
@@ -1085,6 +1249,11 @@ function isAllowanceSummary(value) {
|
|
|
1085
1249
|
function derivePrefix(apiKey) {
|
|
1086
1250
|
return apiKey.slice(0, 12);
|
|
1087
1251
|
}
|
|
1252
|
+
function describeBudgetAmount(a, chainId) {
|
|
1253
|
+
const token = chainId === void 0 ? null : resolveTokenBySymbol(chainId, a.token);
|
|
1254
|
+
if (!token) return `${a.amount} ${a.token} (atomic units)`;
|
|
1255
|
+
return `${formatTokenAmount(a.amount, token.decimals)} ${a.token}`;
|
|
1256
|
+
}
|
|
1088
1257
|
function registeredToolNames() {
|
|
1089
1258
|
return Object.keys(toolSchemas);
|
|
1090
1259
|
}
|
|
@@ -1109,7 +1278,7 @@ async function resolveHavenClient(options = {}) {
|
|
|
1109
1278
|
return { client, credentials };
|
|
1110
1279
|
}
|
|
1111
1280
|
var MCP_NAME = "@haven_ai/mcp";
|
|
1112
|
-
var MCP_VERSION = "0.
|
|
1281
|
+
var MCP_VERSION = "0.8.0-alpha.0";
|
|
1113
1282
|
var MCP_INSTRUCTIONS = [
|
|
1114
1283
|
"Haven local MCP server: signs in-process with the delegate key it holds on",
|
|
1115
1284
|
"this machine \u2014 the key never leaves this process. Call haven_get_agent",
|