@cmdoss/suipay-mcp 1.0.1

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.
@@ -0,0 +1,427 @@
1
+ /**
2
+ * Resolve which grant policy target may pay a seller offer's targetHash (WS7.6).
3
+ *
4
+ * SINGLE SOURCE — consent validation and MCP pay MUST use this helper.
5
+ * Never inline "find policy by hash" in tools or consent UI.
6
+ *
7
+ * Seller offer carries targetHash only (no buyer policyId). Buyer authority
8
+ * is the OAuth grant snapshot of policies/targets at consent time.
9
+ */
10
+ type GrantLifecycleStatus = 'pending' | 'active' | 'paused' | 'revoked' | 'expired';
11
+ type PolicyLifecycleStatus = 'draft' | 'active' | 'paused' | 'revoked';
12
+ interface GrantTargetBinding {
13
+ /** 32-byte hex target hash. */
14
+ targetHash: string;
15
+ /** Optional per-target ceiling (atomic units). */
16
+ maxPerPayment?: string;
17
+ }
18
+ interface GrantPolicyBinding {
19
+ /** Off-chain policy id (for audit). */
20
+ policyId: string;
21
+ /** On-chain policy id (u64) inside SharedPool. */
22
+ onChainPolicyId: number | string;
23
+ status: PolicyLifecycleStatus;
24
+ targets: readonly GrantTargetBinding[];
25
+ }
26
+ /**
27
+ * Immutable snapshot of buyer authority for one OAuth grant.
28
+ * Pool/grant object ids come from finalized on-chain objects (or fake in WS6B).
29
+ */
30
+ interface GrantSnapshot {
31
+ grantId: string;
32
+ status: GrantLifecycleStatus;
33
+ grantObjectId: string | null;
34
+ poolObjectId: string;
35
+ coinType: string;
36
+ delegateAddress: string;
37
+ policies: readonly GrantPolicyBinding[];
38
+ /**
39
+ * `open` = unrestricted capped (no allowlist); `scoped` / omit = policy-bound.
40
+ */
41
+ accessMode?: 'scoped' | 'open';
42
+ /** Grant-level max per payment (atomic); used for open and as a grant ceiling. */
43
+ maxPerPayment?: string | null;
44
+ /** Grant-level session cap (atomic); open requires a positive value. */
45
+ sessionCap?: string | null;
46
+ }
47
+ type ResolveGrantTargetResult = {
48
+ ok: true;
49
+ policyId: string;
50
+ onChainPolicyId: number | string;
51
+ targetHash: string;
52
+ maxPerPayment?: string;
53
+ poolObjectId: string;
54
+ grantObjectId: string;
55
+ coinType: string;
56
+ delegateAddress: string;
57
+ } | {
58
+ ok: false;
59
+ reason: string;
60
+ };
61
+ /**
62
+ * Pick the policy target that matches offer.targetHash under an active grant.
63
+ * Pause/revoke of grant or policy fails closed without decrypting the delegate.
64
+ */
65
+ declare function resolveGrantTarget(snapshot: GrantSnapshot, offerTargetHash: string): ResolveGrantTargetResult;
66
+
67
+ /**
68
+ * Public authorization facts exposed to authenticated MCP clients.
69
+ *
70
+ * This shape is descriptive only. Payment authorization continues to use the
71
+ * immutable on-chain GrantSnapshot; callers must never derive spend authority
72
+ * from this object.
73
+ */
74
+ interface McpAccessProfile {
75
+ id: string;
76
+ name: string;
77
+ status: 'active' | 'disabled';
78
+ }
79
+ interface McpAccessTarget {
80
+ serviceId: string;
81
+ /** Friendly seller service name (live catalog), or serviceId if unresolved. */
82
+ name: string;
83
+ endpointId: string;
84
+ method: string;
85
+ path: string;
86
+ targetHash: string;
87
+ maxPerPayment: string;
88
+ }
89
+ interface McpAccessPolicy {
90
+ id: string;
91
+ name: string;
92
+ status: 'draft' | 'active' | 'paused' | 'revoked';
93
+ asset: string;
94
+ coinType: string;
95
+ totalCap: string;
96
+ spent: string;
97
+ maxPerPayment: string;
98
+ expiresAt: string | null;
99
+ targets: McpAccessTarget[];
100
+ }
101
+ interface McpAccessContext {
102
+ accessMode: 'scoped' | 'open';
103
+ grant: {
104
+ id: string;
105
+ status: 'pending' | 'active' | 'paused' | 'revoked' | 'expired';
106
+ sessionCap: string | null;
107
+ sessionSpent: string;
108
+ expiresAt: string | null;
109
+ };
110
+ profiles: McpAccessProfile[];
111
+ policies: McpAccessPolicy[];
112
+ }
113
+
114
+ /**
115
+ * Shared vocabulary for both 402 dialects.
116
+ *
117
+ * The central claim of this PoC is that MPP and x402 are two encodings of one
118
+ * payment offer, not two payment systems. Everything below the dialect codecs
119
+ * therefore speaks in these types; only `mpp.ts` and `x402.ts` know about
120
+ * headers and JSON bodies.
121
+ */
122
+
123
+ /** Which encoding a message used. Recorded so the provider can answer in kind. */
124
+ type Dialect = 'mpp' | 'x402';
125
+
126
+ /**
127
+ * Canonical live-trace contract.
128
+ *
129
+ * One owner module for every observable MCP / payment / chain fact the live
130
+ * audience view renders. No emitter invents its own event shape: emitters call
131
+ * TraceSink.emit with a TraceEventInput, the dispatcher sanitizes it, and the
132
+ * store persists an append-only TraceEventRecord.
133
+ *
134
+ * Two rules shape everything here:
135
+ *
136
+ * 1. A trace event is *observation*, never authority. It is emitted after a
137
+ * canonical decision returns and can never authorize the next step.
138
+ * 2. The database event id is the canonical display order and is a bigserial.
139
+ * Outside the store it is always a decimal string — a JavaScript number
140
+ * silently loses precision past 2^53 and would corrupt SSE resume.
141
+ */
142
+
143
+ type TraceSource = 'mcp_wire' | 'suipay' | 'sui';
144
+ type TraceStatus = 'started' | 'ok' | 'warning' | 'error' | 'ambiguous';
145
+ type TraceEventKind = 'mcp_connected' | 'tools_listed' | 'tool_called' | 'tool_result' | 'tool_error' | 'challenge_received' | 'authority_matched' | 'authority_revalidated' | 'sponsor_requested' | 'transaction_sponsored' | 'delegate_signed' | 'transaction_submitted' | 'payment_finalized' | 'proof_presented' | 'upstream_delivered' | 'upstream_delivery_failed' | 'settlement_projected' | 'settlement_projection_failed' | 'receipt_issued' | 'payment_failed' | 'payment_ambiguous';
146
+ interface TraceEventInput {
147
+ traceId: string;
148
+ requestId: string | null;
149
+ buyerAccountId: string;
150
+ connectionId: string;
151
+ at: string;
152
+ source: TraceSource;
153
+ kind: TraceEventKind;
154
+ status: TraceStatus;
155
+ summary: string;
156
+ publicPayload: Record<string, unknown>;
157
+ technicalPayload?: Record<string, unknown>;
158
+ }
159
+ /**
160
+ * Correlation constraints an emitter can attach to one event.
161
+ *
162
+ * `requireSoleActiveRunForBuyer` is the server-side (ADR-0011) fallback's
163
+ * atomicity guard: the event is persisted only if, at append time, that buyer
164
+ * still has exactly one active run. It closes the window between selecting the
165
+ * sole run and appending to it — a second run opening in between makes the
166
+ * append fail closed rather than land on a run that is no longer sole.
167
+ */
168
+ interface TraceEmitOptions {
169
+ requireSoleActiveRunForBuyer?: string;
170
+ }
171
+ interface TraceSink {
172
+ emit(event: TraceEventInput, options?: TraceEmitOptions): Promise<void>;
173
+ }
174
+ type TraceRunEndReason = 'completed' | 'idle' | 'superseded' | 'event_cap' | 'byte_cap' | 'shutdown';
175
+ interface TraceRun {
176
+ id: string;
177
+ buyerAccountId: string;
178
+ connectionId: string;
179
+ clientName: string | null;
180
+ startedAt: string;
181
+ lastEventAt: string;
182
+ endedAt: string | null;
183
+ endReason: TraceRunEndReason | null;
184
+ /** Persisted events accepted into this run. */
185
+ eventCount: number;
186
+ /** Serialized sanitized payload bytes accepted into this run. */
187
+ payloadBytes: number;
188
+ }
189
+ /** Run selection for one authenticated OAuth connection (implemented in context.ts). */
190
+ interface TraceRunResolver {
191
+ resolveRun(input: {
192
+ buyerAccountId: string;
193
+ connectionId: string;
194
+ jsonRpcMethod: string | null;
195
+ clientName: string | null;
196
+ now: number;
197
+ }): Promise<TraceRun>;
198
+ }
199
+
200
+ /**
201
+ * The canonical `/v1/settlements` wire row. Both settlement sources - the live
202
+ * in-memory feed and the durable read model - normalize to this shape so the
203
+ * response is byte-stable regardless of whether a read model (DATABASE_URL) is
204
+ * configured. Consumers (MCP `receipts` tool, buyer/seller console) read
205
+ * `receipt.txDigest`; a source that returns its own native shape breaks them.
206
+ */
207
+ interface SettlementDTO {
208
+ at: string;
209
+ resourceId?: string;
210
+ recipient: string;
211
+ dialect?: Dialect;
212
+ receipt: {
213
+ challengeId: string;
214
+ txDigest: string;
215
+ network: string;
216
+ payer: string;
217
+ amount: string;
218
+ asset: string;
219
+ status: 'settled';
220
+ };
221
+ /**
222
+ * Present only on the live in-memory feed. The read model dedups on
223
+ * (network, digest), so a durable row is never a replay - the field is
224
+ * omitted there rather than asserted false. Consumers must treat absent as
225
+ * not-replayed.
226
+ */
227
+ replayed?: boolean;
228
+ }
229
+
230
+ /**
231
+ * Authenticated MCP request context (remote Streamable HTTP).
232
+ * Bearer is required on /mcp; tools enforce scope from the resolved grant.
233
+ */
234
+
235
+ interface McpSharedPoolPayAuth {
236
+ snapshot: GrantSnapshot;
237
+ packageId: string;
238
+ /** Brief decrypt only after pause/revoke checks (paySharedPoolResource). */
239
+ decryptDelegateSeed: () => Promise<Buffer | Uint8Array>;
240
+ /**
241
+ * Re-verify the paying authority against a FRESH graph + injected clock,
242
+ * immediately before decrypt. Throws on drift/expiry so the pay tool aborts
243
+ * (paySharedPoolResource :459). Optional: absent for legacy/local paths.
244
+ */
245
+ assertLive?: () => Promise<void>;
246
+ /**
247
+ * Authorization header value (e.g. `Bearer <token>`) the server-side payer
248
+ * forwards to the gateway's own `/api/sponsor` so the sponsor guard can
249
+ * authenticate the delegate. On `/mcp` the pay runs inside the gateway, so
250
+ * the sponsor call otherwise carries no caller credential and the guard 401s.
251
+ */
252
+ sponsorAuthorization?: string;
253
+ }
254
+ interface McpAuthContext {
255
+ grantId: string;
256
+ buyerAccountId: string;
257
+ /** Space-separated scopes from the access token. */
258
+ scope: string;
259
+ /** Public, descriptive policy context. Never used as payment authority. */
260
+ access?: McpAccessContext;
261
+ /**
262
+ * When present, `pay` uses paySharedPoolResource (server-loaded snapshot +
263
+ * KEK-bound delegate). Stdio local V1 profile path remains when absent.
264
+ */
265
+ sharedPoolPay?: McpSharedPoolPayAuth;
266
+ /**
267
+ * Server-scoped receipts reader, built by the /mcp route from the verified
268
+ * bearer's connection. The `receipts` tool calls this instead of hitting the
269
+ * global settlement feed with a forwarded credential — the bearer never
270
+ * enters the tool surface, and scoping is enforced server-side. Absent for
271
+ * local stdio (no connection, no settlement source).
272
+ */
273
+ listReceipts?: (challengeId?: string) => Promise<SettlementDTO[]>;
274
+ }
275
+
276
+ interface McpConfig {
277
+ consoleUrl: string;
278
+ gatewayUrl: string;
279
+ rpcUrl: string;
280
+ network: 'testnet' | 'mainnet' | 'devnet' | 'localnet';
281
+ packageId: string;
282
+ recipient: string;
283
+ coinType: string;
284
+ sponsorUrl: string;
285
+ minFunded: bigint;
286
+ minMaxPerPayment: bigint;
287
+ label: string;
288
+ }
289
+ declare function loadMcpConfig(env?: NodeJS.ProcessEnv): McpConfig;
290
+
291
+ /**
292
+ * Transparent MCP wire proxy.
293
+ *
294
+ * This module is the *only* place in the system that parses MCP methods and
295
+ * JSON-RPC ids. The gateway authenticates and hands over a run manager and a
296
+ * sink; it never sniffs an MCP body a second time. Payment code emits domain
297
+ * facts, never wire facts. That single-owner rule is what stops two subsystems
298
+ * from disagreeing about which tool call a payment belongs to.
299
+ *
300
+ * Transparency is the top invariant, and it is structural rather than
301
+ * best-effort:
302
+ *
303
+ * - With no runtime, this file is a pass-through and does not touch the
304
+ * request or the response at all.
305
+ * - With a runtime, observation reads `Request.clone()` and `Response.clone()`
306
+ * only. The exact `Response` instance the handler produced is what the caller
307
+ * receives, so byte equality is by construction, not by careful copying.
308
+ * - Response observation is started and deliberately not awaited. A trace
309
+ * store that hangs cannot hold an MCP response open.
310
+ * - The request side cannot be fire-and-forget — the proxy must know the run
311
+ * and the JSON-RPC ids before the handler runs — so it is bounded instead:
312
+ * one deadline over envelope parsing plus run selection, and the same byte
313
+ * cap the response observer uses. A hung store or an oversized body costs
314
+ * the trace, never the request.
315
+ * - Every observation path is wrapped: a hostile body, a failing run manager or
316
+ * a throwing sink degrades to "no trace", never to a changed response.
317
+ *
318
+ * Volume control lives here too. One `tool_called` per JSON-RPC id and one
319
+ * terminal `tool_result` / `tool_error` per id — never one row per SSE frame.
320
+ */
321
+
322
+ /** Everything the proxy needs to attribute wire facts to one owned run. */
323
+ interface McpTraceRuntime {
324
+ buyerAccountId: string;
325
+ connectionId: string;
326
+ runs: TraceRunResolver;
327
+ sink: TraceSink;
328
+ /**
329
+ * HMAC secret for signed cross-request context. Supplied by the composition
330
+ * root so a published SDK never reads a gateway-only environment variable.
331
+ * Absent means internal hops carry no correlation and the gateway simply
332
+ * emits no linked events.
333
+ */
334
+ secret?: string | null;
335
+ /** Injected clock; tests pin it, production leaves it out. */
336
+ now?: () => number;
337
+ }
338
+ /** The proxy-resolved context handed down to payment code (Task 5 seams). */
339
+ interface McpTraceContext {
340
+ traceId: string;
341
+ requestId: string | null;
342
+ }
343
+ interface McpTraceHooks {
344
+ runtime: McpTraceRuntime;
345
+ context: McpTraceContext;
346
+ sink: TraceSink;
347
+ }
348
+ /** One JSON-RPC message read off the request side. */
349
+ interface McpEnvelopeEntry {
350
+ method: string | null;
351
+ jsonRpcId: string | number | null;
352
+ /** Canonical, header-safe request id. `null` for notifications. */
353
+ requestId: string | null;
354
+ toolName: string | null;
355
+ toolArguments: unknown;
356
+ clientName: string | null;
357
+ clientVersion: string | null;
358
+ protocolVersion: string | null;
359
+ }
360
+ interface McpEnvelope {
361
+ batch: boolean;
362
+ entries: McpEnvelopeEntry[];
363
+ /** First entry's method — what run selection keys on. */
364
+ method: string | null;
365
+ requestId: string | null;
366
+ clientName: string | null;
367
+ }
368
+ /**
369
+ * Canonical MCP request id.
370
+ *
371
+ * JSON-RPC allows both `7` and `"7"` as distinct ids, and they must stay
372
+ * distinct: correlating a payment to the wrong tool call is worse than not
373
+ * correlating it. Numbers keep their JSON form under `n:`; strings are
374
+ * base64url-encoded under `s:` so an id containing newlines, colons or quotes
375
+ * survives being carried in an HTTP header without escaping games.
376
+ */
377
+ declare function canonicalMcpRequestId(id: unknown): string | null;
378
+ /**
379
+ * Read the JSON-RPC envelope from a request **clone**.
380
+ *
381
+ * The caller owns cloning; this consumes the body it is given. An unparsable,
382
+ * bodiless or over-cap request yields an empty envelope rather than an error — a
383
+ * malformed request is the transport's problem to answer, not tracing's problem
384
+ * to block.
385
+ */
386
+ declare function inspectMcpEnvelope(request: Request, maxBytes?: number): Promise<McpEnvelope>;
387
+ interface TraceMcpHttpRequestInput {
388
+ request: Request;
389
+ /**
390
+ * The real MCP handler. Receives the untouched original request, plus the
391
+ * resolved trace context when tracing is live.
392
+ */
393
+ handle: (request: Request, hooks?: McpTraceHooks) => Promise<Response>;
394
+ runtime?: McpTraceRuntime | null;
395
+ /** Cap on bytes read for observation, request and response. Never unbounded. */
396
+ maxObservedBytes?: number;
397
+ /**
398
+ * Deadline for envelope parsing plus run selection, the only trace work that
399
+ * happens before the handler. Past it the exchange proceeds untraced.
400
+ */
401
+ runSetupTimeoutMs?: number;
402
+ }
403
+ /**
404
+ * Wrap one MCP HTTP exchange in observation.
405
+ *
406
+ * The response returned here is the exact object `handle` produced. Nothing on
407
+ * the observation path can change it, delay it, or prevent it.
408
+ */
409
+ declare function traceMcpHttpRequest(input: TraceMcpHttpRequestInput): Promise<Response>;
410
+
411
+ /**
412
+ * Streamable HTTP transport for SuiPay MCP.
413
+ * Stateless per-request (sessionIdGenerator undefined) — same public tools as stdio.
414
+ * Auth context is required for remote gateway /mcp (bearer resolved upstream).
415
+ *
416
+ * The optional trace runtime turns on live observation. It is a pure wrapper:
417
+ * `traceMcpHttpRequest` reads clones and returns the transport's own Response,
418
+ * so passing a runtime cannot change what an MCP client sees.
419
+ */
420
+
421
+ /**
422
+ * Handle one MCP Streamable HTTP request (initialize, tools/list, tools/call, …).
423
+ * Creates a fresh transport+server pair per request (stateless mode).
424
+ */
425
+ declare function handleSuipayMcpHttpRequest(req: Request, cfg: McpConfig, auth?: McpAuthContext, trace?: McpTraceRuntime | null): Promise<Response>;
426
+
427
+ export { type Dialect as D, type GrantSnapshot as G, type McpConfig as M, type TraceSink as T, type McpAuthContext as a, type McpTraceHooks as b, type McpEnvelope as c, type McpEnvelopeEntry as d, type McpTraceContext as e, type McpTraceRuntime as f, canonicalMcpRequestId as g, handleSuipayMcpHttpRequest as h, inspectMcpEnvelope as i, loadMcpConfig as l, resolveGrantTarget as r, traceMcpHttpRequest as t };
package/dist/http.d.ts ADDED
@@ -0,0 +1 @@
1
+ export { h as handleSuipayMcpHttpRequest } from './http-B5iGH5mf.js';
package/dist/http.js ADDED
@@ -0,0 +1,6 @@
1
+ import {
2
+ handleSuipayMcpHttpRequest
3
+ } from "./chunk-772CHNGT.js";
4
+ export {
5
+ handleSuipayMcpHttpRequest
6
+ };