@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/README.md
CHANGED
|
@@ -119,23 +119,18 @@ Environment variable form:
|
|
|
119
119
|
- `haven_list_receipts`
|
|
120
120
|
- `haven_open_task_budget` (#3329)
|
|
121
121
|
- `haven_close_task_budget` (#3329)
|
|
122
|
-
- `haven_submit` (#3329 — relays a local signer signature by `task_budget_id
|
|
122
|
+
- `haven_submit` (#3329 — relays a local signer signature by `task_budget_id`, or (#3506) by `sub_budget_id` for a sub-budget this agent delegates (listed in `haven_get_agent`'s `pendingSubBudgetSignatures[]`); a `payment_id` is refused on this runtime, which signs and submits payments inline)
|
|
123
123
|
|
|
124
|
-
### `idempotencyKey` is
|
|
124
|
+
### `idempotencyKey` is removed — send `idempotency_key`
|
|
125
125
|
|
|
126
126
|
The tools that take an idempotency key (`haven_send`, `haven_pay_mcp_tool`,
|
|
127
|
-
`haven_quote_x402`, `haven_pay_x402_quote`, `haven_pay_x402`)
|
|
128
|
-
**`idempotency_key`**, the spelling the hosted Haven MCP surface
|
|
129
|
-
Haven wire contract use. `idempotencyKey` still works and returns a deprecation
|
|
130
|
-
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.
|
|
131
129
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
The warning arrives in an optional `warnings` array on the success result. It is
|
|
138
|
-
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.
|
|
139
134
|
|
|
140
135
|
## First-launch consent
|
|
141
136
|
|
|
@@ -159,7 +154,7 @@ Tools this server will expose to your agent runtime:
|
|
|
159
154
|
|
|
160
155
|
On-chain budget (the real spend gate — enforced by the agent's
|
|
161
156
|
signed delegation, not by Haven):
|
|
162
|
-
• up to
|
|
157
|
+
• up to 25 USDC per 1440 min
|
|
163
158
|
|
|
164
159
|
Anything above the on-chain budget is declined before any money
|
|
165
160
|
moves — it is not queued, and no one is asked to review it. If the
|
|
@@ -195,13 +190,17 @@ live.
|
|
|
195
190
|
## Audit log
|
|
196
191
|
|
|
197
192
|
Every MCP tool invocation tags the underlying Haven API call with
|
|
198
|
-
`X-Haven-MCP-Tool: <tool_name>`. The backend records
|
|
199
|
-
`agent_tool_invocations` row
|
|
200
|
-
|
|
193
|
+
`X-Haven-MCP-Tool: <tool_name>`. The backend records an
|
|
194
|
+
`agent_tool_invocations` row (tool name, payment id when present, result
|
|
195
|
+
status, nextAction, error code, HTTP status, timestamp) for the tools on its
|
|
196
|
+
allowlist (`MCP_TOOL_NAMES` in the backend's `agentToolAudit.ts`):
|
|
197
|
+
`haven_quote_x402`, `haven_pay_x402_quote`, `haven_resume_x402_payment`,
|
|
198
|
+
`haven_get_payment_status`, `haven_get_resume_state`, `haven_get_agent`,
|
|
199
|
+
`haven_get_allowances` and `haven_list_receipts`. Every other tool, including
|
|
200
|
+
`haven_send`, `haven_pay_x402` and `haven_submit`, leaves no row. The agent's
|
|
201
201
|
activity feed in the Haven dashboard surfaces these rows alongside payments,
|
|
202
|
-
so the wallet owner can see
|
|
203
|
-
|
|
204
|
-
money.
|
|
202
|
+
so the wallet owner can see which of those tools the agent called and what
|
|
203
|
+
happened — including the read-only calls that don't move money.
|
|
205
204
|
|
|
206
205
|
The audit log is informational. The agent's signed budget delegation — its
|
|
207
206
|
on-chain caveat enforcers — remains the only thing that can stop a spend;
|
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,18 +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"),
|
|
250
261
|
/** #3329: spend against an open task budget instead of the agent's period budget. */
|
|
251
|
-
task_budget_id: v3.z.string().min(1).optional()
|
|
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()
|
|
252
269
|
},
|
|
253
270
|
haven_pay_mcp_tool: {
|
|
254
271
|
merchant_url: v3.z.string().url(),
|
|
255
272
|
tool_name: v3.z.string().min(1),
|
|
256
273
|
arguments: v3.z.record(v3.z.string(), v3.z.unknown()).optional(),
|
|
257
274
|
idempotency_key: v3.z.string().optional(),
|
|
258
|
-
/**
|
|
259
|
-
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")
|
|
260
277
|
},
|
|
261
278
|
haven_quote_x402: {
|
|
262
279
|
url: v3.z.string().url(),
|
|
@@ -264,16 +281,18 @@ var toolSchemas = {
|
|
|
264
281
|
headers: headersSchema,
|
|
265
282
|
body: v3.z.string().optional(),
|
|
266
283
|
idempotency_key: v3.z.string().optional(),
|
|
267
|
-
/**
|
|
268
|
-
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")
|
|
269
286
|
},
|
|
270
287
|
haven_pay_x402_quote: {
|
|
271
288
|
quote: v3.z.unknown(),
|
|
272
289
|
idempotency_key: v3.z.string().optional(),
|
|
273
|
-
/**
|
|
274
|
-
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"),
|
|
275
292
|
/** #3329: spend against an open task budget instead of the agent's period budget. */
|
|
276
|
-
task_budget_id: v3.z.string().min(1).optional()
|
|
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()
|
|
277
296
|
},
|
|
278
297
|
haven_pay_x402: {
|
|
279
298
|
url: v3.z.string().url(),
|
|
@@ -281,10 +300,12 @@ var toolSchemas = {
|
|
|
281
300
|
headers: headersSchema,
|
|
282
301
|
body: v3.z.string().optional(),
|
|
283
302
|
idempotency_key: v3.z.string().optional(),
|
|
284
|
-
/**
|
|
285
|
-
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"),
|
|
286
305
|
/** #3329: spend against an open task budget instead of the agent's period budget. */
|
|
287
|
-
task_budget_id: v3.z.string().min(1).optional()
|
|
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()
|
|
288
309
|
},
|
|
289
310
|
haven_resume_x402_payment: {
|
|
290
311
|
payment_id: v3.z.string().optional(),
|
|
@@ -312,7 +333,9 @@ var toolSchemas = {
|
|
|
312
333
|
haven_list_receipts: {
|
|
313
334
|
limit: v3.z.number().int().min(1).max(100).optional(),
|
|
314
335
|
/** #3128: the previous page's next_cursor (a receipt id). */
|
|
315
|
-
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.")
|
|
316
339
|
},
|
|
317
340
|
haven_verify_receipt: {
|
|
318
341
|
receipt: v3.z.unknown()
|
|
@@ -330,12 +353,19 @@ var toolSchemas = {
|
|
|
330
353
|
haven_close_task_budget: {
|
|
331
354
|
task_budget_id: v3.z.string().min(1)
|
|
332
355
|
},
|
|
333
|
-
// #
|
|
334
|
-
//
|
|
335
|
-
//
|
|
336
|
-
|
|
356
|
+
// #3518: read ONE task budget by id, any status — the status check a close
|
|
357
|
+
// refusal's "re-check the budget's status" points at, on the local surface
|
|
358
|
+
// too (schema and wording parity with the hosted read).
|
|
359
|
+
haven_get_task_budget: {
|
|
360
|
+
task_budget_id: v3.z.string().min(1)
|
|
361
|
+
},
|
|
362
|
+
// #3329 / #3506: relays a signature from the local signer — the open/close
|
|
363
|
+
// signature for a task budget or a sub-budget row, or (schema parity with
|
|
364
|
+
// the hosted surface) a direct-payment signature by payment_id. Exactly one
|
|
365
|
+
// of task_budget_id / sub_budget_id / payment_id, never two or none.
|
|
337
366
|
haven_submit: {
|
|
338
367
|
task_budget_id: v3.z.string().min(1).optional(),
|
|
368
|
+
sub_budget_id: v3.z.string().min(1).optional(),
|
|
339
369
|
payment_id: v3.z.string().min(1).optional(),
|
|
340
370
|
signature: v3.z.string().regex(/^0x[0-9a-fA-F]+$/, "signature must be a 0x-prefixed hex string")
|
|
341
371
|
}
|
|
@@ -363,11 +393,19 @@ var CLOSE_TASK_BUDGET_DESCRIPTION = [
|
|
|
363
393
|
"the signature with haven_submit. A task budget past its own deadline closes immediately, the",
|
|
364
394
|
"same as the pending case."
|
|
365
395
|
].join(" ");
|
|
396
|
+
var GET_TASK_BUDGET_DESCRIPTION = [
|
|
397
|
+
"Read one task budget by id, any status.",
|
|
398
|
+
"Returns { task_budget } with status (pending | open | closing | closed), isExpired, maxDisplay, recipientAddress, label and expiresAt \u2014",
|
|
399
|
+
"the check to run after a close or submit refusal says to re-check the budget's status.",
|
|
400
|
+
"haven_get_agent lists live task budgets only; this reads any one, closed or expired included."
|
|
401
|
+
].join(" ");
|
|
366
402
|
var SUBMIT_DESCRIPTION = [
|
|
367
403
|
"Relay a signature from the local signer. Pass exactly one of task_budget_id (from",
|
|
368
|
-
"haven_open_task_budget or haven_close_task_budget)
|
|
369
|
-
"
|
|
370
|
-
|
|
404
|
+
"haven_open_task_budget or haven_close_task_budget), sub_budget_id (from haven_get_agent",
|
|
405
|
+
"pendingSubBudgetSignatures[]) or payment_id \u2014 never two, never none \u2014 plus signature.",
|
|
406
|
+
"For a task budget this opens or closes it on-chain and returns { task_budget,",
|
|
407
|
+
'status }; a close in progress may return { task_budget, status: "closed", close_tx_hash }.',
|
|
408
|
+
"A sub_budget_id returns { sub_budget, status } with the next signer step, if one is owed."
|
|
371
409
|
].join(" ");
|
|
372
410
|
var toolDescriptions = {
|
|
373
411
|
haven_send: sdk.composeDescription(sdk.toolDescriptions.send),
|
|
@@ -387,8 +425,101 @@ var toolDescriptions = {
|
|
|
387
425
|
haven_verify_receipt: sdk.composeDescription(sdk.toolDescriptions.verifyReceipt),
|
|
388
426
|
haven_open_task_budget: OPEN_TASK_BUDGET_DESCRIPTION,
|
|
389
427
|
haven_close_task_budget: CLOSE_TASK_BUDGET_DESCRIPTION,
|
|
428
|
+
haven_get_task_budget: GET_TASK_BUDGET_DESCRIPTION,
|
|
390
429
|
haven_submit: SUBMIT_DESCRIPTION
|
|
391
430
|
};
|
|
431
|
+
function subBudgetSignStep(subBudgetId) {
|
|
432
|
+
return {
|
|
433
|
+
next_action: "sign",
|
|
434
|
+
next_tool: "mcp__haven-signer__haven_sign",
|
|
435
|
+
next_arguments: { sub_budget_id: subBudgetId }
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
async function submitSubBudgetTool(haven, subBudgetId, signature) {
|
|
439
|
+
let result;
|
|
440
|
+
try {
|
|
441
|
+
result = await haven.submitSubBudget(subBudgetId, signature);
|
|
442
|
+
} catch (err) {
|
|
443
|
+
const code = err instanceof sdk.HavenApiError ? err.body?.error_code : void 0;
|
|
444
|
+
if (code === "close_needs_reprepare") {
|
|
445
|
+
let fresh;
|
|
446
|
+
try {
|
|
447
|
+
fresh = await haven.closeSubBudget(subBudgetId);
|
|
448
|
+
} catch (closeErr) {
|
|
449
|
+
return normalizeError(closeErr);
|
|
450
|
+
}
|
|
451
|
+
if (fresh.status === "closed") {
|
|
452
|
+
return {
|
|
453
|
+
success: true,
|
|
454
|
+
data: {
|
|
455
|
+
sub_budget: fresh.subBudget,
|
|
456
|
+
status: "closed",
|
|
457
|
+
next_action: "none",
|
|
458
|
+
next_tool_omitted_reason: "the sub-budget is closed on-chain; nothing further to sign or submit for it"
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
return {
|
|
463
|
+
success: false,
|
|
464
|
+
code: "CLOSE_NEEDS_REPREPARE",
|
|
465
|
+
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.",
|
|
466
|
+
statusCode: 409,
|
|
467
|
+
...subBudgetSignStep(subBudgetId)
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
if (code === "close_outcome_unconfirmed") {
|
|
471
|
+
return {
|
|
472
|
+
success: false,
|
|
473
|
+
code: "CLOSE_OUTCOME_UNCONFIRMED",
|
|
474
|
+
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.",
|
|
475
|
+
statusCode: 502,
|
|
476
|
+
next_action: "check_status_later",
|
|
477
|
+
next_tool: "haven_submit",
|
|
478
|
+
next_arguments: { sub_budget_id: subBudgetId, signature }
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
return normalizeError(err);
|
|
482
|
+
}
|
|
483
|
+
return runTool(async () => {
|
|
484
|
+
const submitted = {
|
|
485
|
+
sub_budget: result.subBudget,
|
|
486
|
+
status: result.status,
|
|
487
|
+
...result.closeTxHash !== void 0 ? { close_tx_hash: result.closeTxHash } : {}
|
|
488
|
+
};
|
|
489
|
+
if (result.status === "closed") {
|
|
490
|
+
return {
|
|
491
|
+
...submitted,
|
|
492
|
+
next_action: "none",
|
|
493
|
+
next_tool_omitted_reason: "the sub-budget is closed on-chain; nothing further to sign or submit for it"
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
let pending;
|
|
497
|
+
try {
|
|
498
|
+
pending = await haven.listPendingSubBudgetSignatures();
|
|
499
|
+
} catch {
|
|
500
|
+
pending = null;
|
|
501
|
+
}
|
|
502
|
+
if (pending === null) {
|
|
503
|
+
return {
|
|
504
|
+
...submitted,
|
|
505
|
+
next_action: "check_status_later",
|
|
506
|
+
next_tool: "haven_get_agent",
|
|
507
|
+
next_arguments: {},
|
|
508
|
+
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."
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
const tree = result.subBudget.parentSubBudgetId ?? result.subBudget.id;
|
|
512
|
+
const sibling = pending.find(
|
|
513
|
+
(row) => row.purpose === "open" && row.subBudgetId !== result.subBudget.id && (row.parentSubBudgetId ?? row.subBudgetId) === tree
|
|
514
|
+
);
|
|
515
|
+
if (sibling) return { ...submitted, ...subBudgetSignStep(sibling.subBudgetId) };
|
|
516
|
+
return {
|
|
517
|
+
...submitted,
|
|
518
|
+
next_action: "none",
|
|
519
|
+
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"
|
|
520
|
+
};
|
|
521
|
+
});
|
|
522
|
+
}
|
|
392
523
|
function createToolHandlers(haven) {
|
|
393
524
|
return {
|
|
394
525
|
haven_send: async (input) => {
|
|
@@ -401,12 +532,15 @@ function createToolHandlers(haven) {
|
|
|
401
532
|
token: args.asset,
|
|
402
533
|
amount: args.amount,
|
|
403
534
|
to: args.recipient,
|
|
404
|
-
// #1207: was accepted by the schema but silently dropped — now
|
|
405
|
-
// carried to the backend's replay contract.
|
|
406
|
-
idempotencyKey: args.
|
|
535
|
+
// #1207/#3411: was accepted by the schema but silently dropped — now
|
|
536
|
+
// carried to the backend's replay contract via the resolved carrier.
|
|
537
|
+
idempotencyKey: args.resolvedIdempotencyKey,
|
|
407
538
|
// #3329: spend against an open task budget instead of the
|
|
408
539
|
// agent's period budget, when the caller names one.
|
|
409
|
-
...typeof args.task_budget_id === "string" ? { taskBudgetId: args.task_budget_id } : {}
|
|
540
|
+
...typeof args.task_budget_id === "string" ? { taskBudgetId: args.task_budget_id } : {},
|
|
541
|
+
// #3330: spend through a sub-budget this agent holds (it is
|
|
542
|
+
// sub-agent B). Exactly one of the two ids, never both.
|
|
543
|
+
...typeof args.sub_budget_id === "string" ? { subBudgetId: args.sub_budget_id } : {}
|
|
410
544
|
});
|
|
411
545
|
return {
|
|
412
546
|
payment_id: result.paymentId,
|
|
@@ -444,7 +578,7 @@ function createToolHandlers(haven) {
|
|
|
444
578
|
body: JSON.stringify(envelope)
|
|
445
579
|
};
|
|
446
580
|
let merchantUrl = args.merchant_url;
|
|
447
|
-
const idempotencyKey = args.
|
|
581
|
+
const idempotencyKey = args.resolvedIdempotencyKey;
|
|
448
582
|
const attempt = () => haven.fetch(merchantUrl, init, { idempotencyKey });
|
|
449
583
|
let response = await attempt();
|
|
450
584
|
if (!response.ok) {
|
|
@@ -478,7 +612,7 @@ function createToolHandlers(haven) {
|
|
|
478
612
|
if ("success" in pf) return pf;
|
|
479
613
|
const { args, warnings } = pf;
|
|
480
614
|
try {
|
|
481
|
-
const data = await haven.quoteX402(args.url, requestInit(args), { idempotencyKey: args.
|
|
615
|
+
const data = await haven.quoteX402(args.url, requestInit(args), { idempotencyKey: args.resolvedIdempotencyKey });
|
|
482
616
|
return warnings.length > 0 ? { success: true, data, warnings } : { success: true, data };
|
|
483
617
|
} catch (err) {
|
|
484
618
|
return normalizeError(err);
|
|
@@ -505,8 +639,9 @@ function createToolHandlers(haven) {
|
|
|
505
639
|
}
|
|
506
640
|
return runTool(async () => {
|
|
507
641
|
const response = await haven.payX402Quote(args.quote, {
|
|
508
|
-
idempotencyKey: args.
|
|
509
|
-
...typeof args.task_budget_id === "string" ? { taskBudgetId: args.task_budget_id } : {}
|
|
642
|
+
idempotencyKey: args.resolvedIdempotencyKey,
|
|
643
|
+
...typeof args.task_budget_id === "string" ? { taskBudgetId: args.task_budget_id } : {},
|
|
644
|
+
...typeof args.sub_budget_id === "string" ? { subBudgetId: args.sub_budget_id } : {}
|
|
510
645
|
});
|
|
511
646
|
return responsePayload(response);
|
|
512
647
|
}, warnings);
|
|
@@ -517,8 +652,9 @@ function createToolHandlers(haven) {
|
|
|
517
652
|
const { args, warnings } = pf;
|
|
518
653
|
return runTool(async () => {
|
|
519
654
|
const response = await haven.fetch(args.url, requestInit(args), {
|
|
520
|
-
idempotencyKey: args.
|
|
521
|
-
...typeof args.task_budget_id === "string" ? { taskBudgetId: args.task_budget_id } : {}
|
|
655
|
+
idempotencyKey: args.resolvedIdempotencyKey,
|
|
656
|
+
...typeof args.task_budget_id === "string" ? { taskBudgetId: args.task_budget_id } : {},
|
|
657
|
+
...typeof args.sub_budget_id === "string" ? { subBudgetId: args.sub_budget_id } : {}
|
|
522
658
|
});
|
|
523
659
|
return responsePayload(response);
|
|
524
660
|
}, warnings);
|
|
@@ -539,7 +675,17 @@ function createToolHandlers(haven) {
|
|
|
539
675
|
const args = objectInput("haven_get_resume_state", input);
|
|
540
676
|
return runTool(async () => haven.getResumeState(args.payment_id));
|
|
541
677
|
},
|
|
542
|
-
haven_get_agent: async () => runTool(async () =>
|
|
678
|
+
haven_get_agent: async () => runTool(async () => {
|
|
679
|
+
const summary = await haven.getAgentSummary();
|
|
680
|
+
if (!Array.isArray(summary.pendingSubBudgetSignatures)) return summary;
|
|
681
|
+
return {
|
|
682
|
+
...summary,
|
|
683
|
+
pendingSubBudgetSignatures: summary.pendingSubBudgetSignatures.map((row) => ({
|
|
684
|
+
...row,
|
|
685
|
+
...subBudgetSignStep(row.subBudgetId)
|
|
686
|
+
}))
|
|
687
|
+
};
|
|
688
|
+
}),
|
|
543
689
|
haven_get_allowances: async () => runTool(async () => haven.getAllowances()),
|
|
544
690
|
haven_sweep_delegate: async () => runTool(async () => haven.sweepDelegate()),
|
|
545
691
|
haven_discover_tools: async (input) => {
|
|
@@ -615,7 +761,9 @@ function createToolHandlers(haven) {
|
|
|
615
761
|
},
|
|
616
762
|
haven_list_receipts: async (input) => {
|
|
617
763
|
const args = objectInput("haven_list_receipts", input);
|
|
618
|
-
return runTool(
|
|
764
|
+
return runTool(
|
|
765
|
+
async () => haven.listReceiptsPage({ limit: args.limit, cursor: args.cursor, compact: args.compact === true })
|
|
766
|
+
);
|
|
619
767
|
},
|
|
620
768
|
haven_verify_receipt: async (input) => {
|
|
621
769
|
const args = objectInput("haven_verify_receipt", input);
|
|
@@ -643,7 +791,8 @@ function createToolHandlers(haven) {
|
|
|
643
791
|
task_budget: result.taskBudget,
|
|
644
792
|
next_action: "sign",
|
|
645
793
|
next_tool: "mcp__haven-signer__haven_sign",
|
|
646
|
-
next_arguments: { task_budget_id: result.taskBudget.id }
|
|
794
|
+
next_arguments: { task_budget_id: result.taskBudget.id },
|
|
795
|
+
signer_compatibility: taskSignerCompatibilityNotice()
|
|
647
796
|
};
|
|
648
797
|
});
|
|
649
798
|
},
|
|
@@ -658,21 +807,37 @@ function createToolHandlers(haven) {
|
|
|
658
807
|
task_budget: result.taskBudget,
|
|
659
808
|
next_action: "sign",
|
|
660
809
|
next_tool: "mcp__haven-signer__haven_sign",
|
|
661
|
-
next_arguments: { task_budget_id: args.task_budget_id }
|
|
810
|
+
next_arguments: { task_budget_id: args.task_budget_id },
|
|
811
|
+
signer_compatibility: taskSignerCompatibilityNotice()
|
|
662
812
|
};
|
|
663
813
|
});
|
|
664
814
|
},
|
|
815
|
+
// #3518: read ONE task budget by id, any status — the status check a
|
|
816
|
+
// close/submit refusal's "re-check the budget's status" points at. The
|
|
817
|
+
// SDK relays `GET /task-budgets/:id` (the backend route answers any
|
|
818
|
+
// status) and a 404 carries the backend's own message through runTool.
|
|
819
|
+
haven_get_task_budget: async (input) => {
|
|
820
|
+
const args = objectInput("haven_get_task_budget", input);
|
|
821
|
+
return runTool(async () => {
|
|
822
|
+
const taskBudget = await haven.getTaskBudget(args.task_budget_id);
|
|
823
|
+
return { task_budget: taskBudget };
|
|
824
|
+
});
|
|
825
|
+
},
|
|
665
826
|
haven_submit: async (input) => {
|
|
666
827
|
const args = objectInput("haven_submit", input);
|
|
667
828
|
const hasTaskBudget = typeof args.task_budget_id === "string" && args.task_budget_id.length > 0;
|
|
829
|
+
const hasSubBudget = typeof args.sub_budget_id === "string" && args.sub_budget_id.length > 0;
|
|
668
830
|
const hasPayment = typeof args.payment_id === "string" && args.payment_id.length > 0;
|
|
669
|
-
if (hasTaskBudget
|
|
831
|
+
if ([hasTaskBudget, hasSubBudget, hasPayment].filter(Boolean).length !== 1) {
|
|
670
832
|
return {
|
|
671
833
|
success: false,
|
|
672
834
|
code: "INVALID_INPUT",
|
|
673
|
-
message: "haven_submit takes exactly one of task_budget_id or
|
|
835
|
+
message: "haven_submit takes exactly one of payment_id, task_budget_id or sub_budget_id, never two or none. Nothing was relayed."
|
|
674
836
|
};
|
|
675
837
|
}
|
|
838
|
+
if (hasSubBudget) {
|
|
839
|
+
return submitSubBudgetTool(haven, args.sub_budget_id, args.signature);
|
|
840
|
+
}
|
|
676
841
|
return runTool(async () => {
|
|
677
842
|
if (hasTaskBudget) {
|
|
678
843
|
const result = await haven.submitTaskBudget(args.task_budget_id, args.signature);
|
|
@@ -683,7 +848,7 @@ function createToolHandlers(haven) {
|
|
|
683
848
|
};
|
|
684
849
|
}
|
|
685
850
|
throw new sdk.HavenApiError(
|
|
686
|
-
"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
|
|
851
|
+
"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.",
|
|
687
852
|
400
|
|
688
853
|
);
|
|
689
854
|
});
|
|
@@ -715,28 +880,20 @@ function preflight(name, input) {
|
|
|
715
880
|
}
|
|
716
881
|
const idem = resolveIdempotencyKey(args);
|
|
717
882
|
if ("success" in idem) return idem;
|
|
718
|
-
if (idem.key !== void 0) args = { ...args,
|
|
883
|
+
if (idem.key !== void 0) args = { ...args, resolvedIdempotencyKey: idem.key };
|
|
719
884
|
return { args, warnings: idem.warnings };
|
|
720
885
|
}
|
|
721
886
|
function resolveIdempotencyKey(args) {
|
|
722
887
|
const modern = typeof args.idempotency_key === "string" ? args.idempotency_key : void 0;
|
|
723
888
|
const legacy = typeof args.idempotencyKey === "string" ? args.idempotencyKey : void 0;
|
|
724
|
-
if (
|
|
889
|
+
if (legacy !== void 0) {
|
|
725
890
|
return {
|
|
726
891
|
success: false,
|
|
727
|
-
code: "
|
|
728
|
-
message: "
|
|
892
|
+
code: "IDEMPOTENCY_KEY_RENAMED",
|
|
893
|
+
message: "idempotencyKey has been renamed to idempotency_key and is no longer accepted. Send idempotency_key instead. Nothing was contacted or spent."
|
|
729
894
|
};
|
|
730
895
|
}
|
|
731
896
|
if (modern !== void 0) return { key: modern, warnings: [] };
|
|
732
|
-
if (legacy !== void 0) {
|
|
733
|
-
return {
|
|
734
|
-
key: legacy,
|
|
735
|
-
warnings: [
|
|
736
|
-
"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."
|
|
737
|
-
]
|
|
738
|
-
};
|
|
739
|
-
}
|
|
740
897
|
return { warnings: [] };
|
|
741
898
|
}
|
|
742
899
|
function buildMcpToolsCallEnvelope(toolName, args) {
|
|
@@ -967,7 +1124,7 @@ function renderConsentBlock(input, hash) {
|
|
|
967
1124
|
lines.push("signed delegation, not by Haven):");
|
|
968
1125
|
for (const a of input.allowanceSummary) {
|
|
969
1126
|
const reset = a.resetMinutes ? ` per ${a.resetMinutes} min` : " (no reset)";
|
|
970
|
-
lines.push(` \u2022 up to ${a
|
|
1127
|
+
lines.push(` \u2022 up to ${describeBudgetAmount(a, input.chainId)}${reset}`);
|
|
971
1128
|
}
|
|
972
1129
|
}
|
|
973
1130
|
lines.push("");
|
|
@@ -1064,7 +1221,14 @@ async function consentInputFromClient(haven, seed, toolNames) {
|
|
|
1064
1221
|
}
|
|
1065
1222
|
const liveAllowanceSummary = list.map((a) => ({
|
|
1066
1223
|
token: a.tokenSymbol ?? "UNKNOWN",
|
|
1067
|
-
|
|
1224
|
+
// #3410: `onchain.amount` is the atomic budget (`budget_atomic` on the
|
|
1225
|
+
// backend). The old `a.configuredAmount` fallback here was unreachable
|
|
1226
|
+
// — `onchain` is required in the SDK types and dereferenced
|
|
1227
|
+
// unconditionally, so a response without it throws into the seed path —
|
|
1228
|
+
// and it was the one HUMAN-decimal value in this chain; a human number
|
|
1229
|
+
// must never reach an atomic field. '0' keeps the (defensive) shape for
|
|
1230
|
+
// a malformed onchain object without inventing a unit.
|
|
1231
|
+
amount: a.onchain?.amount ?? "0",
|
|
1068
1232
|
resetMinutes: typeof a.onchain?.resetTimeMin === "number" ? a.onchain.resetTimeMin : typeof a.resetPeriodMin === "number" ? a.resetPeriodMin : null
|
|
1069
1233
|
}));
|
|
1070
1234
|
allowanceSummary = liveAllowanceSummary;
|
|
@@ -1087,6 +1251,11 @@ function isAllowanceSummary(value) {
|
|
|
1087
1251
|
function derivePrefix(apiKey) {
|
|
1088
1252
|
return apiKey.slice(0, 12);
|
|
1089
1253
|
}
|
|
1254
|
+
function describeBudgetAmount(a, chainId) {
|
|
1255
|
+
const token = chainId === void 0 ? null : sdk.resolveTokenBySymbol(chainId, a.token);
|
|
1256
|
+
if (!token) return `${a.amount} ${a.token} (atomic units)`;
|
|
1257
|
+
return `${sdk.formatTokenAmount(a.amount, token.decimals)} ${a.token}`;
|
|
1258
|
+
}
|
|
1090
1259
|
function registeredToolNames() {
|
|
1091
1260
|
return Object.keys(toolSchemas);
|
|
1092
1261
|
}
|
|
@@ -1111,7 +1280,7 @@ async function resolveHavenClient(options = {}) {
|
|
|
1111
1280
|
return { client, credentials };
|
|
1112
1281
|
}
|
|
1113
1282
|
var MCP_NAME = "@haven_ai/mcp";
|
|
1114
|
-
var MCP_VERSION = "0.
|
|
1283
|
+
var MCP_VERSION = "0.8.0-alpha.0";
|
|
1115
1284
|
var MCP_INSTRUCTIONS = [
|
|
1116
1285
|
"Haven local MCP server: signs in-process with the delegate key it holds on",
|
|
1117
1286
|
"this machine \u2014 the key never leaves this process. Call haven_get_agent",
|