@cmdoss/suipay-mcp 0.2.2-dev.4 → 0.2.2-dev.40

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.
@@ -3,8 +3,12 @@ import { McpAccessContext } from '@cmdoss/suipay-sdk/mcp/access-context';
3
3
  import { SettlementDTO } from '@cmdoss/suipay-sdk/buyer/settlement-dto';
4
4
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
5
5
  import { GetObjectJson } from '@cmdoss/suipay-sdk/agent-sdk/reads';
6
- import { DelegatedKeySigner, DelegatedPayerDeps } from '@cmdoss/suipay-sdk/delegated-signer/payer';
6
+ import { DelegatedKeySigner, DelegatedPayerDeps, GuardedSettleFields } from '@cmdoss/suipay-sdk/delegated-signer/payer';
7
7
  import { TraceRunResolver, TraceSink } from '@cmdoss/suipay-core/trace/types';
8
+ import { PaidHttpRequest } from '@cmdoss/suipay-sdk/protocol/paid-http-request';
9
+ import { Dialect } from '@cmdoss/suipay-sdk/protocol/types';
10
+ import { PayOperationDeps } from '@cmdoss/suipay-sdk/pay-operation';
11
+ import { PaymentState, NextAction } from '@cmdoss/suipay-core/errors';
8
12
 
9
13
  /**
10
14
  * Authenticated MCP request context (remote Streamable HTTP).
@@ -16,7 +20,7 @@ import { TraceRunResolver, TraceSink } from '@cmdoss/suipay-core/trace/types';
16
20
  *
17
21
  * Non-custodial: the gateway holds NO key that can spend. There is deliberately
18
22
  * no `decryptDelegateSeed` and no forwarded `sponsorAuthorization` here — the
19
- * `pay` tool only PREPARES an unsigned `spend_account::settle_policy_payment` transaction from the
23
+ * `pay` tool only PREPARES an unsigned `spend_account::settle_policy` / `settle_any_recipient` transaction from the
20
24
  * immutable grant snapshot (`prepareSpendAccountPayment`), and the client that
21
25
  * holds the delegate key signs locally and submits. Only signatures ever reach
22
26
  * the gateway. See docs/design/mcp-non-custodial-signing.md and ADR-0013 §3.
@@ -40,7 +44,7 @@ interface McpAuthContext {
40
44
  /** Public, descriptive policy context. Never used as payment authority. */
41
45
  access?: McpAccessContext;
42
46
  /**
43
- * When present, `pay` prepares unsigned spend_account::settle_policy_payment bytes for the
47
+ * When present, `pay` prepares unsigned spend_account::settle_policy / settle_any_recipient bytes for the
44
48
  * client-held delegate key to sign locally — no server-side decrypt or
45
49
  * signing. Stdio local V1 profile path remains when absent.
46
50
  */
@@ -55,7 +59,19 @@ interface McpAuthContext {
55
59
  listReceipts?: (challengeId?: string) => Promise<SettlementDTO[]>;
56
60
  }
57
61
 
62
+ type McpTarget = 'dev' | 'local';
63
+ /**
64
+ * Public suipay-dev origins (Railway). `local` is the laptop stack from
65
+ * `pnpm demo:testnet`. Explicit `SUIPAY_GATEWAY_URL` / `SUIPAY_CONSOLE_URL`
66
+ * still win over the preset.
67
+ */
68
+ declare const MCP_TARGET_PRESETS: Record<McpTarget, {
69
+ consoleUrl: string;
70
+ gatewayUrl: string;
71
+ }>;
58
72
  interface McpConfig {
73
+ /** Preset used when console/gateway env is unset. Set by `loadMcpConfig`. */
74
+ target?: McpTarget;
59
75
  consoleUrl: string;
60
76
  gatewayUrl: string;
61
77
  rpcUrl: string;
@@ -67,8 +83,23 @@ interface McpConfig {
67
83
  minFunded: bigint;
68
84
  minMaxPerPayment: bigint;
69
85
  label: string;
86
+ /**
87
+ * Optional override of the gateway's published `ExclusionDirectory` id.
88
+ * When null, pay reads `GET /v1/delegate/deployment` and turns the resolver
89
+ * on only if that response names one. The id here does not choose the
90
+ * directory a settle uses — the offer's `registryIdentity` does.
91
+ */
92
+ exclusionDirectoryObjectId: string | null;
70
93
  }
71
- declare function loadMcpConfig(env?: NodeJS.ProcessEnv): McpConfig;
94
+ /**
95
+ * CLI flags: `--local` / `-local`, `--dev` / `-dev`. Unknown args are ignored
96
+ * so an MCP host can pass extra argv without breaking stdio.
97
+ */
98
+ declare function parseMcpTargetFlag(argv: readonly string[]): McpTarget | undefined;
99
+ declare function resolveMcpTarget(env?: NodeJS.ProcessEnv, argv?: readonly string[]): McpTarget;
100
+ /** Same public origin, or the same laptop loopback port (`localhost` ≡ `127.0.0.1`). */
101
+ declare function sameMcpGateway(a: string, b: string): boolean;
102
+ declare function loadMcpConfig(env?: NodeJS.ProcessEnv, argv?: readonly string[]): McpConfig;
72
103
 
73
104
  /**
74
105
  * Agent-held settlement wiring for the `pay` tool.
@@ -229,6 +260,244 @@ interface TraceMcpHttpRequestInput {
229
260
  */
230
261
  declare function traceMcpHttpRequest(input: TraceMcpHttpRequestInput): Promise<Response>;
231
262
 
263
+ /**
264
+ * Shared pay-request helpers.
265
+ *
266
+ * The V1 allowance sponsored payer (ADR-0010) is retired; only the
267
+ * rail-neutral request surface the SpendAccount payer depends on survives here:
268
+ * the `PayResult` shape every payer returns. `paidRequestInit` is the SDK helper
269
+ * that builds the one RequestInit replayed byte for byte on both calls.
270
+ */
271
+
272
+ type PayResult = {
273
+ status: 'ok' | 'failed' | 'ambiguous';
274
+ /**
275
+ * `true` — settlement confirmed. `false` — money did not leave the
276
+ * account. `'unknown'` — execute/finality did not resolve; a retry can
277
+ * pay twice.
278
+ */
279
+ paid: boolean | 'unknown';
280
+ /** Whether money moved; always agrees with `paid`. */
281
+ paymentState?: PaymentState;
282
+ body?: unknown;
283
+ receipt?: unknown;
284
+ /** Registry code on a failed or ambiguous result. */
285
+ code?: string;
286
+ retryable?: boolean;
287
+ nextAction?: NextAction;
288
+ supportCode?: string;
289
+ detail?: string;
290
+ txDigest?: string;
291
+ paymentId?: string;
292
+ challengeId?: string;
293
+ httpStatus?: number;
294
+ responseClass?: 'unpaid_ok' | 'rejected' | 'unavailable';
295
+ };
296
+
297
+ /**
298
+ * SpendAccount PREPARE-only client (non-custodial MCP).
299
+ *
300
+ * The remote MCP `pay` tool runs inside the gateway, which holds NO key that can
301
+ * spend. So this module does not sign or submit: it PREPARES a payment and hands
302
+ * back an unsigned `spend_account::settle_policy` / `settle_any_recipient` transaction plus everything the client
303
+ * needs. The client — which holds the delegate key — sponsors, signs locally,
304
+ * and submits through the same personal-message challenge → local-sign → submit flow the
305
+ * REST/SDK delegate path uses. Only signatures ever reach the gateway; the
306
+ * private key is never placed in an HTTP body, a bearer, or any header.
307
+ *
308
+ * Flow: 402 Offer → request-binding → package/network SSOT →
309
+ * resolveGrantTarget(snapshot, offer.targetHash) (pause/revoke gate) → cap/coin
310
+ * checks → assertLive (fresh authority) → build unsigned kind bytes → return the
311
+ * signing request. There is deliberately no sponsor call, no decrypt, no
312
+ * signature, and no forwarded bearer here.
313
+ *
314
+ * See docs/design/mcp-non-custodial-signing.md and ADR-0013 §3.
315
+ */
316
+
317
+ type SharedObjectReader = (objectId: string) => Promise<{
318
+ owner?: unknown;
319
+ } | null | undefined>;
320
+ /**
321
+ * Read/prepare-only session for the non-custodial MCP pay tool. It carries the
322
+ * immutable grant snapshot and an optional prepare-time live gate — and no key,
323
+ * by construction. There is no `decryptDelegateSeed` and no `sponsorAuthorization`.
324
+ */
325
+ interface SpendAccountPrepareSession {
326
+ snapshot: GrantSnapshot;
327
+ /** SpendAccount package id (V2). */
328
+ packageId: string;
329
+ /**
330
+ * Re-assert the grant is still live (not paused/revoked/expired) against fresh
331
+ * canonical state, invoked immediately before a signing request is handed back.
332
+ * Throw to abort. Optional; when absent, only the request-time snapshot gates
333
+ * the prepare (on-chain `spend_account::settle_policy` remains the final backstop for any
334
+ * spend the client later submits).
335
+ */
336
+ assertLive?: () => Promise<void>;
337
+ }
338
+ interface SpendAccountPayDeps {
339
+ fetchImpl?: typeof fetch;
340
+ /**
341
+ * Build only-transaction-kind bytes for the canonical guarded debit
342
+ * (`spend_account::settle_policy` / `settle_any_recipient`).
343
+ *
344
+ * The guarded-settle fields ({@link GuardedSettleFields}) are required on the
345
+ * fresh lineage: every delegate settle consumes the shared exclusion marker.
346
+ * Dropping them fails closed rather than building an unguarded call that no
347
+ * longer exists on chain.
348
+ */
349
+ buildPayKind?: (input: {
350
+ delegate: string;
351
+ coinType: string;
352
+ poolObjectId: string;
353
+ grantObjectId: string;
354
+ policyId: number | string;
355
+ targetHash: string;
356
+ amount: string;
357
+ paymentIdHash: Uint8Array;
358
+ termsHash: Uint8Array;
359
+ recipient?: string;
360
+ accessMode?: 'scoped' | 'open';
361
+ } & GuardedSettleFields) => Promise<{
362
+ kindBytes: string;
363
+ }>;
364
+ /**
365
+ * Resolve the per-recipient `RecipientRegistry` bound in the offer's exclusion
366
+ * directory (guarded offers only). Injected seam for tests; production
367
+ * leaves it unset and the prepare wires a live gRPC-backed resolver from the
368
+ * configured `ExclusionDirectory` id when present, else fails closed.
369
+ */
370
+ resolveExclusionRegistry?: PayOperationDeps['resolveExclusionRegistry'];
371
+ /**
372
+ * Read a shared object's owner so prepare binds the real initial version.
373
+ * Missing owner fails the prepare; version 1 is not a substitute.
374
+ */
375
+ readSharedObject?: SharedObjectReader;
376
+ /**
377
+ * Live trace for this prepare. Purely observational: emission records what the
378
+ * canonical read decisions already returned — an event is written after a check
379
+ * passes and can never be the thing that lets the next step run.
380
+ */
381
+ trace?: SpendAccountPayTrace;
382
+ }
383
+ interface SpendAccountPayTrace {
384
+ sink: TraceSink;
385
+ traceId: string;
386
+ requestId: string | null;
387
+ buyerAccountId: string;
388
+ connectionId: string;
389
+ /** HMAC secret for signed cross-request context. Absent disables signing. */
390
+ secret?: string | null;
391
+ /** Injected clock, for tests. */
392
+ now?: () => number;
393
+ }
394
+ /** The unsigned payment the client signs locally and submits. */
395
+ interface SpendAccountSigningRequest {
396
+ /**
397
+ * Unsigned `spend_account::settle_policy` / `settle_any_recipient` transaction-kind bytes (base64), sender set to
398
+ * `delegateAddress`. The client sponsors these for gas, signs with the delegate
399
+ * key, and submits — the gateway never receives the key.
400
+ */
401
+ unsignedKindBytes: string;
402
+ delegateAddress: string;
403
+ packageId: string;
404
+ poolObjectId: string;
405
+ grantObjectId: string;
406
+ /** On-chain policy id the pay is bound to. */
407
+ policyId: number | string;
408
+ coinType: string;
409
+ targetHash: string;
410
+ amount: string;
411
+ /** Hex (no 0x) of the payment-id hash bound into the transaction. */
412
+ paymentIdHashHex: string;
413
+ /** Hex (no 0x) of the terms hash bound into the transaction. */
414
+ termsHashHex: string;
415
+ challengeId: string;
416
+ /** The exact paid request to replay after the client submits. */
417
+ resource: {
418
+ url: string;
419
+ method: string;
420
+ };
421
+ dialect: Dialect;
422
+ /** Recipient the agent requested (offer.payTo). */
423
+ recipient: string;
424
+ /** Where the client (holding the key) sponsors and submits. */
425
+ submit: {
426
+ sponsorUrl: string;
427
+ executeUrl: string;
428
+ moveCallTarget: string;
429
+ };
430
+ /**
431
+ * ISO-8601 timestamp when this prepared operation expires. After this time the
432
+ * unsigned bytes are no longer valid and the client must re-call pay() for a
433
+ * fresh prepare. Derived from the offer TTL.
434
+ */
435
+ expiresAt: string;
436
+ /** Restated hard rule for the agent host. */
437
+ note: string;
438
+ }
439
+ interface PreparedPayment {
440
+ status: 'prepared';
441
+ paid: false;
442
+ signingRequest: SpendAccountSigningRequest;
443
+ }
444
+ /** Prepared signing request, or a canonical failure (never a spend). */
445
+ type SpendAccountPrepareResult = PreparedPayment | PayResult;
446
+ /** Default kind builder (no chain client — pure PTB). */
447
+ declare function buildSpendAccountPayKind(input: {
448
+ packageId: string;
449
+ delegate: string;
450
+ coinType: string;
451
+ poolObjectId: string;
452
+ grantObjectId: string;
453
+ policyId: number | string;
454
+ targetHash: string;
455
+ amount: string;
456
+ paymentIdHash: Uint8Array;
457
+ termsHash: Uint8Array;
458
+ poolInitialSharedVersion?: string;
459
+ grantInitialSharedVersion?: string;
460
+ clockInitialSharedVersion?: string;
461
+ } & GuardedSettleFields): Promise<{
462
+ kindBytes: string;
463
+ }>;
464
+ /**
465
+ * Refuse blind signing: a client validating a sponsored transaction must be able
466
+ * to prove the sponsored bytes reconstruct the intended spend_account::settle_policy. Kept
467
+ * here (and exported) so both the client signer and its tests share one check.
468
+ */
469
+ declare function verifySpendAccountPayReconstructs(bytes: string, expected: {
470
+ kindBytes: string;
471
+ packageId: string;
472
+ delegate: string;
473
+ poolObjectId: string;
474
+ grantObjectId: string;
475
+ amount: string;
476
+ }): void;
477
+ /**
478
+ * Prepare a delegate payment WITHOUT signing. Reads the 402 offer, binds it,
479
+ * gates on the immutable grant snapshot + a fresh authority check, builds the
480
+ * unsigned transaction, and returns a signing request. The client holding the
481
+ * delegate key signs locally and submits. This function never decrypts, signs,
482
+ * sponsors, or submits.
483
+ *
484
+ * Uses `resolvePayment` from the SDK pay-operation module for the
485
+ * shared offer-acquisition + authority-resolution pipeline (steps 1–5 + assertLive
486
+ * + hash derivation + buildPayKind). This module wraps that with MCP-specific
487
+ * concerns: trace events, dialect-mode ceiling check, and signing-request
488
+ * construction.
489
+ */
490
+ declare function prepareSpendAccountPayment(args: {
491
+ url: string;
492
+ /** The one normalized paid request, replayed byte for byte after signing. */
493
+ request?: PaidHttpRequest;
494
+ session: SpendAccountPrepareSession;
495
+ cfg: McpConfig;
496
+ deps?: SpendAccountPayDeps;
497
+ /** Preferred dialect when the 402 advertises both (default mpp). */
498
+ preferredDialect?: Dialect;
499
+ }): Promise<SpendAccountPrepareResult>;
500
+
232
501
  /**
233
502
  * Transport-neutral SuiPay MCP server + tool registry.
234
503
  * Used by stdio (index.ts) and Streamable HTTP (http.ts).
@@ -262,6 +531,14 @@ declare const TOOLS: readonly [{
262
531
  readonly type: "object";
263
532
  readonly description: "JSON request body for a POST. Sent as application/json and bound to the payment.";
264
533
  };
534
+ readonly file: {
535
+ readonly type: "string";
536
+ readonly description: "Absolute path under SUIPAY_MCP_FILE_ROOT (unset disables attach). Stdio POST only; requires fileField. Regular non-empty file whose decoded size fits the paid-body ceiling. Not a URL, FIFO, device, or path under ~/.suipay. Bytes fill json[fileField] as canonical base64.";
537
+ };
538
+ readonly fileField: {
539
+ readonly type: "string";
540
+ readonly description: "JSON key to set from file (e.g. contentBase64). Required with file. Pattern [A-Za-z][A-Za-z0-9_]{0,63}. Must not already be present on json.";
541
+ };
265
542
  readonly recipient: {
266
543
  readonly type: "string";
267
544
  readonly description: "Sui address you expect to be paid. The payment is refused, unsigned, if the prepared transaction pays anyone else.";
@@ -275,7 +552,7 @@ declare const TOOLS: readonly [{
275
552
  };
276
553
  }, {
277
554
  readonly name: "discover";
278
- readonly description: "Search SuiPay gateway resources.";
555
+ readonly description: "Search SuiPay gateway resources. After login, each row includes name, path, url, catalog price, and whether the current session can cover one call at that price (descriptive only — pay still enforces the grant).";
279
556
  readonly inputSchema: {
280
557
  readonly type: "object";
281
558
  readonly properties: {
@@ -299,7 +576,7 @@ declare const TOOLS: readonly [{
299
576
  };
300
577
  }, {
301
578
  readonly name: "suipay_login";
302
- readonly description: "Mint or reuse the local delegate key (version-2, mode 0600) and open the console with the public address only so the owner can bind a spend-account grant. Stdio only; hosted /mcp never holds a spendable key.";
579
+ readonly description: "Mint or reuse the local delegate key (version-2, mode 0600) and return a clickable console URL immediately so the owner can bind a spend-account grant. Does not wait for the wallet. Default target is Railway suipay-dev; pass target=\"local\" (or start the binary with --local) for the laptop stack. Stdio only; hosted /mcp never holds a spendable key.";
303
580
  readonly inputSchema: {
304
581
  readonly type: "object";
305
582
  readonly properties: {
@@ -307,6 +584,11 @@ declare const TOOLS: readonly [{
307
584
  readonly type: "string";
308
585
  readonly description: "Label shown in SuiPay console.";
309
586
  };
587
+ readonly target: {
588
+ readonly type: "string";
589
+ readonly enum: readonly ["dev", "local"];
590
+ readonly description: "Where to bind the grant. Default is Railway suipay-dev. Pass \"local\" for localhost:3000 / 127.0.0.1:4340.";
591
+ };
310
592
  readonly gatewayResourceUrl: {
311
593
  readonly type: "string";
312
594
  readonly description: "Gateway resource URL whose 402 defines recipient, asset, and package.";
@@ -345,6 +627,13 @@ interface McpServerOptions {
345
627
  * does not already authorize.
346
628
  */
347
629
  resolveAgentHeld?: AgentHeldResolver;
630
+ /** Override the exclusion-registry resolver (tests / offline). */
631
+ resolveExclusionRegistry?: SpendAccountPayDeps['resolveExclusionRegistry'];
632
+ /**
633
+ * Wire that created this server. Hosted HTTP never forwards `file` into `pay`.
634
+ * Stdio is the default so existing tests stay local.
635
+ */
636
+ transport?: 'stdio' | 'http';
348
637
  }
349
638
  declare function createServer(cfg: McpConfig, auth?: McpAuthContext, trace?: McpTraceHooks, options?: McpServerOptions): Server;
350
639
 
@@ -364,4 +653,4 @@ declare function createServer(cfg: McpConfig, auth?: McpAuthContext, trace?: Mcp
364
653
  */
365
654
  declare function handleSuipayMcpHttpRequest(req: Request, cfg: McpConfig, auth?: McpAuthContext, trace?: McpTraceRuntime | null, options?: McpServerOptions): Promise<Response>;
366
655
 
367
- export { type AgentHeldResolver as A, type McpConfig as M, PACKAGE_NAME as P, TOOLS as T, type McpAuthContext as a, type McpTraceHooks as b, type AgentHeldSettlement as c, type McpEnvelope as d, type McpEnvelopeEntry as e, type McpServerOptions as f, type McpTraceContext as g, type McpTraceRuntime as h, canonicalMcpRequestId as i, createServer as j, handleSuipayMcpHttpRequest as k, inspectMcpEnvelope as l, loadMcpConfig as m, resolveAgentHeldSettlement as n, resetAgentHeldProcessLedger as r, traceMcpHttpRequest as t };
656
+ export { type AgentHeldResolver as A, traceMcpHttpRequest as B, verifySpendAccountPayReconstructs as C, type McpConfig as M, PACKAGE_NAME as P, type SpendAccountSigningRequest as S, TOOLS as T, type McpAuthContext as a, type McpTraceHooks as b, type SpendAccountPayDeps as c, type AgentHeldSettlement as d, MCP_TARGET_PRESETS as e, type McpEnvelope as f, type McpEnvelopeEntry as g, type McpServerOptions as h, type McpTarget as i, type McpTraceContext as j, type McpTraceRuntime as k, type PreparedPayment as l, type SpendAccountPrepareResult as m, type SpendAccountPrepareSession as n, buildSpendAccountPayKind as o, canonicalMcpRequestId as p, createServer as q, handleSuipayMcpHttpRequest as r, inspectMcpEnvelope as s, loadMcpConfig as t, parseMcpTargetFlag as u, prepareSpendAccountPayment as v, resetAgentHeldProcessLedger as w, resolveAgentHeldSettlement as x, resolveMcpTarget as y, sameMcpGateway as z };
package/dist/http.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { k as handleSuipayMcpHttpRequest } from './http-C8xW9F1c.js';
1
+ export { r as handleSuipayMcpHttpRequest } from './http-DvnvTrb3.js';
2
2
  import '@cmdoss/suipay-core/policy/grant-target';
3
3
  import '@cmdoss/suipay-sdk/mcp/access-context';
4
4
  import '@cmdoss/suipay-sdk/buyer/settlement-dto';
@@ -6,3 +6,7 @@ import '@modelcontextprotocol/sdk/server/index.js';
6
6
  import '@cmdoss/suipay-sdk/agent-sdk/reads';
7
7
  import '@cmdoss/suipay-sdk/delegated-signer/payer';
8
8
  import '@cmdoss/suipay-core/trace/types';
9
+ import '@cmdoss/suipay-sdk/protocol/paid-http-request';
10
+ import '@cmdoss/suipay-sdk/protocol/types';
11
+ import '@cmdoss/suipay-sdk/pay-operation';
12
+ import '@cmdoss/suipay-core/errors';
package/dist/http.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  handleSuipayMcpHttpRequest
3
- } from "./chunk-KDOGSB5I.js";
3
+ } from "./chunk-YMOMYFKL.js";
4
4
  export {
5
5
  handleSuipayMcpHttpRequest
6
6
  };