@haven_ai/mcp 0.1.7-alpha → 0.1.10-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/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, HavenPaymentStateError, HavenSigningError, HavenApiError, AgentPaymentNextAction, HavenError } from '@haven_ai/sdk';
4
+ import { composeDescription, toolDescriptions as toolDescriptions$1, HavenClient, HavenApiError, HavenPaymentStateError, HavenSigningError, AgentPaymentNextAction, HavenError } 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';
@@ -216,6 +216,18 @@ async function warnIfCredentialFilePermissive(path, log = (message) => process.s
216
216
  }
217
217
  var headersSchema = z.record(z.string(), z.string()).optional();
218
218
  var toolSchemas = {
219
+ haven_send: {
220
+ asset: z.enum(["ETH", "USDC"]),
221
+ recipient: z.string().min(1),
222
+ amount: z.string().min(1),
223
+ idempotencyKey: z.string().optional()
224
+ },
225
+ haven_pay_mcp_tool: {
226
+ merchant_url: z.string().url(),
227
+ tool_name: z.string().min(1),
228
+ arguments: z.record(z.string(), z.unknown()).optional(),
229
+ idempotencyKey: z.string().optional()
230
+ },
219
231
  haven_quote_x402: {
220
232
  url: z.string().url(),
221
233
  method: z.string().optional(),
@@ -262,11 +274,18 @@ var toolSchemas = {
262
274
  },
263
275
  haven_get_agent: {},
264
276
  haven_get_allowances: {},
277
+ haven_sweep_delegate: {},
278
+ haven_discover_tools: {
279
+ category: z.string().optional(),
280
+ rail: z.enum(["x402", "mpp"]).optional()
281
+ },
265
282
  haven_list_receipts: {
266
283
  limit: z.number().int().min(1).max(100).optional()
267
284
  }
268
285
  };
269
286
  var toolDescriptions = {
287
+ haven_send: composeDescription(toolDescriptions$1.send),
288
+ haven_pay_mcp_tool: composeDescription(toolDescriptions$1.payMcpTool),
270
289
  haven_quote_x402: composeDescription(toolDescriptions$1.quoteX402),
271
290
  haven_pay_x402_quote: composeDescription(toolDescriptions$1.payX402),
272
291
  haven_pay_x402: composeDescription(toolDescriptions$1.payX402OneShot),
@@ -278,16 +297,98 @@ var toolDescriptions = {
278
297
  haven_get_resume_state: composeDescription(toolDescriptions$1.getResumeState),
279
298
  haven_get_agent: composeDescription(toolDescriptions$1.getAgent),
280
299
  haven_get_allowances: composeDescription(toolDescriptions$1.getAllowances),
300
+ haven_sweep_delegate: composeDescription(toolDescriptions$1.sweep_delegate),
301
+ haven_discover_tools: composeDescription(toolDescriptions$1.discoverTools),
281
302
  haven_list_receipts: composeDescription(toolDescriptions$1.listReceipts)
282
303
  };
283
304
  function createToolHandlers(haven) {
284
305
  return {
306
+ haven_send: async (input) => {
307
+ return runTool(async () => {
308
+ const args = objectInput("haven_send", input);
309
+ try {
310
+ const result = await haven.pay({
311
+ token: args.asset,
312
+ amount: args.amount,
313
+ to: args.recipient
314
+ });
315
+ return {
316
+ payment_id: result.paymentId,
317
+ status: result.status,
318
+ tx_hash: result.txHash ?? null,
319
+ asset: args.asset,
320
+ amount: args.amount,
321
+ recipient: args.recipient
322
+ };
323
+ } catch (err) {
324
+ if (err instanceof HavenPaymentStateError && isPendingApproval(err.status)) {
325
+ return {
326
+ payment_id: err.paymentId,
327
+ status: "pending_approval",
328
+ asset: args.asset,
329
+ amount: args.amount,
330
+ recipient: args.recipient
331
+ };
332
+ }
333
+ throw err;
334
+ }
335
+ });
336
+ },
337
+ haven_pay_mcp_tool: async (input) => {
338
+ return runTool(async () => {
339
+ const args = objectInput("haven_pay_mcp_tool", input);
340
+ const envelope = buildMcpToolsCallEnvelope(args.tool_name, args.arguments);
341
+ const response = await haven.fetch(
342
+ args.merchant_url,
343
+ {
344
+ method: "POST",
345
+ headers: { "Content-Type": "application/json" },
346
+ body: JSON.stringify(envelope)
347
+ },
348
+ { idempotencyKey: args.idempotencyKey }
349
+ );
350
+ return responsePayload(response);
351
+ });
352
+ },
285
353
  haven_quote_x402: async (input) => {
286
354
  const args = objectInput("haven_quote_x402", input);
287
- return runTool(async () => haven.quoteX402(args.url, requestInit(args), { idempotencyKey: args.idempotencyKey }));
355
+ try {
356
+ return { success: true, data: await haven.quoteX402(args.url, requestInit(args), { idempotencyKey: args.idempotencyKey }) };
357
+ } catch (err) {
358
+ if (err instanceof HavenApiError && err.message.includes("quoteX402 only supports standard x402")) {
359
+ return wrongTool(
360
+ "WRONG_RAIL",
361
+ "The URL responds with an MPP machine-payment challenge, not an x402 payment. Use haven_quote_mpp to inspect this merchant.",
362
+ "haven_quote_mpp"
363
+ );
364
+ }
365
+ return normalizeError(err);
366
+ }
288
367
  },
289
368
  haven_pay_x402_quote: async (input) => {
290
369
  const args = objectInput("haven_pay_x402_quote", input);
370
+ const quote = args.quote;
371
+ if (!quote || typeof quote !== "object") {
372
+ return wrongTool(
373
+ "WRONG_TOOL",
374
+ "The quote argument is missing or is not a valid x402 quote object. Call haven_quote_x402 first to obtain a quote, or use haven_pay_x402 to handle the full probe \u2192 pay \u2192 retry round trip automatically.",
375
+ "haven_quote_x402"
376
+ );
377
+ }
378
+ if (!quote.paymentRequired && quote.rail === "mpp") {
379
+ return wrongTool(
380
+ "WRONG_TOOL",
381
+ "The quote is for the MPP rail. Use haven_pay_mpp_challenge to pay an MPP quote.",
382
+ "haven_pay_mpp_challenge"
383
+ );
384
+ }
385
+ if (!quote.paymentRequired) {
386
+ return wrongTool(
387
+ "WRONG_TOOL",
388
+ "The quote is missing the required paymentRequired field. Call haven_quote_x402 first to obtain a valid x402 quote.",
389
+ "haven_quote_x402"
390
+ );
391
+ }
291
392
  return runTool(async () => {
292
393
  const response = await haven.payX402Quote(args.quote, { idempotencyKey: args.idempotencyKey });
293
394
  return responsePayload(response);
@@ -302,6 +403,16 @@ function createToolHandlers(haven) {
302
403
  },
303
404
  haven_resume_x402_payment: async (input) => {
304
405
  const args = objectInput("haven_resume_x402_payment", input);
406
+ if (args.resume_state && typeof args.resume_state === "object") {
407
+ const stateRail = args.resume_state.rail;
408
+ if (stateRail && stateRail !== "x402") {
409
+ return wrongTool(
410
+ "WRONG_TOOL",
411
+ `The resume state is for the '${stateRail}' rail, not x402. Use haven_resume_mpp_payment instead.`,
412
+ "haven_resume_mpp_payment"
413
+ );
414
+ }
415
+ }
305
416
  return runTool(async () => {
306
417
  const state = await resumeState(args, "x402");
307
418
  const response = await haven.resumeX402Payment(state);
@@ -310,20 +421,54 @@ function createToolHandlers(haven) {
310
421
  },
311
422
  haven_quote_mpp: async (input) => {
312
423
  const args = objectInput("haven_quote_mpp", input);
313
- return runTool(async () => {
424
+ try {
314
425
  if (args.challenge) {
315
- return haven.quoteMpp(args.challenge, requestInit(args), {
316
- idempotencyKey: args.idempotencyKey
317
- });
426
+ return {
427
+ success: true,
428
+ data: await haven.quoteMpp(args.challenge, requestInit(args), {
429
+ idempotencyKey: args.idempotencyKey
430
+ })
431
+ };
318
432
  }
319
433
  if (!args.url) {
320
- throw new HavenApiError("haven_quote_mpp requires either url or challenge.", 400);
434
+ return normalizeError(new HavenApiError("haven_quote_mpp requires either url or challenge.", 400));
321
435
  }
322
- return haven.quoteMpp(args.url, requestInit(args), { idempotencyKey: args.idempotencyKey });
323
- });
436
+ return { success: true, data: await haven.quoteMpp(args.url, requestInit(args), { idempotencyKey: args.idempotencyKey }) };
437
+ } catch (err) {
438
+ if (err instanceof Error && err.message.includes("No MACHINE-PAYMENT-CHALLENGE header found")) {
439
+ return wrongTool(
440
+ "WRONG_RAIL",
441
+ "The URL responds with an x402 payment requirement, not an MPP machine-payment challenge. Use haven_quote_x402 to inspect this merchant.",
442
+ "haven_quote_x402"
443
+ );
444
+ }
445
+ return normalizeError(err);
446
+ }
324
447
  },
325
448
  haven_pay_mpp_challenge: async (input) => {
326
449
  const args = objectInput("haven_pay_mpp_challenge", input);
450
+ const quote = args.quote;
451
+ if (!quote || typeof quote !== "object") {
452
+ return wrongTool(
453
+ "WRONG_TOOL",
454
+ "The quote argument is missing or is not a valid MPP quote object. Call haven_quote_mpp first to obtain a quote.",
455
+ "haven_quote_mpp"
456
+ );
457
+ }
458
+ if (quote.paymentRequired || quote.rail === "x402") {
459
+ return wrongTool(
460
+ "WRONG_TOOL",
461
+ "The quote is for the x402 rail. Use haven_pay_x402_quote to pay an x402 quote.",
462
+ "haven_pay_x402_quote"
463
+ );
464
+ }
465
+ if (!quote.challenge) {
466
+ return wrongTool(
467
+ "WRONG_TOOL",
468
+ "The quote is missing the required challenge field. Call haven_quote_mpp first to obtain a valid MPP quote.",
469
+ "haven_quote_mpp"
470
+ );
471
+ }
327
472
  return runTool(async () => {
328
473
  const response = await haven.payMppChallenge(args.quote, { idempotencyKey: args.idempotencyKey });
329
474
  return responsePayload(response);
@@ -331,6 +476,16 @@ function createToolHandlers(haven) {
331
476
  },
332
477
  haven_resume_mpp_payment: async (input) => {
333
478
  const args = objectInput("haven_resume_mpp_payment", input);
479
+ if (args.resume_state && typeof args.resume_state === "object") {
480
+ const stateRail = args.resume_state.rail;
481
+ if (stateRail && stateRail !== "mpp") {
482
+ return wrongTool(
483
+ "WRONG_TOOL",
484
+ `The resume state is for the '${stateRail}' rail, not mpp. Use haven_resume_x402_payment instead.`,
485
+ "haven_resume_x402_payment"
486
+ );
487
+ }
488
+ }
334
489
  return runTool(async () => {
335
490
  const state = await resumeState(args, "mpp");
336
491
  const response = await haven.resumeMppPayment(state);
@@ -347,6 +502,34 @@ function createToolHandlers(haven) {
347
502
  },
348
503
  haven_get_agent: async () => runTool(async () => haven.getAgent()),
349
504
  haven_get_allowances: async () => runTool(async () => haven.getAllowances()),
505
+ haven_sweep_delegate: async () => runTool(async () => haven.sweepDelegate()),
506
+ haven_discover_tools: async (input) => {
507
+ const args = objectInput("haven_discover_tools", input);
508
+ return runTool(async () => {
509
+ const entries = await haven.discoverTools({
510
+ category: typeof args.category === "string" ? args.category : void 0,
511
+ rail: args.rail === "x402" || args.rail === "mpp" ? args.rail : void 0
512
+ });
513
+ return entries.map((entry) => ({
514
+ id: entry.id,
515
+ name: entry.name,
516
+ description: entry.description,
517
+ category: entry.category,
518
+ resource_url: entry.resourceUrl,
519
+ rail: entry.rail,
520
+ protocol: entry.protocol,
521
+ tool_name: entry.toolName,
522
+ price_display: entry.priceDisplay,
523
+ price_atomic: entry.priceAtomic,
524
+ asset: entry.asset,
525
+ network: entry.network,
526
+ status: entry.status,
527
+ verified_at: entry.verifiedAt,
528
+ // Which Haven pay tool reaches this entry from the local MCP surface.
529
+ suggested_tool: entry.protocol === "mcp" ? "haven_pay_mcp_tool" : entry.rail === "x402" ? "haven_pay_x402" : "haven_quote_mpp"
530
+ }));
531
+ });
532
+ },
350
533
  haven_list_receipts: async (input) => {
351
534
  const args = objectInput("haven_list_receipts", input);
352
535
  return runTool(async () => haven.listReceipts({ limit: args.limit }));
@@ -363,6 +546,23 @@ function createToolHandlers(haven) {
363
546
  return state;
364
547
  }
365
548
  }
549
+ function isPendingApproval(status) {
550
+ return status === "pending" || status === "pending_approval";
551
+ }
552
+ function wrongTool(code, message, suggested_tool) {
553
+ return { success: false, code, message, suggested_tool };
554
+ }
555
+ function buildMcpToolsCallEnvelope(toolName, args) {
556
+ return {
557
+ jsonrpc: "2.0",
558
+ id: `haven-mcp-${Date.now()}`,
559
+ method: "tools/call",
560
+ params: {
561
+ name: toolName,
562
+ arguments: args ?? {}
563
+ }
564
+ };
565
+ }
366
566
  function objectInput(name, input) {
367
567
  return z.object(toolSchemas[name]).parse(input ?? {});
368
568
  }
@@ -638,7 +838,7 @@ async function resolveHavenClient(options = {}) {
638
838
  return { client, credentials };
639
839
  }
640
840
  var MCP_NAME = "@haven_ai/mcp";
641
- var MCP_VERSION = "0.1.7-alpha";
841
+ var MCP_VERSION = "0.1.10-alpha.0";
642
842
  function buildMcpServer(haven) {
643
843
  const server = new McpServer({
644
844
  name: MCP_NAME,