@mysten-incubation/suipay-mcp 0.3.0-dev.1 → 0.3.0-dev.10
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 +24 -4
- package/dist/bin/suipay.js +2 -2
- package/dist/{chunk-L7OTT6U5.js → chunk-D6LFU4VE.js} +1 -1
- package/dist/{chunk-WPRFEMBU.js → chunk-RD34GGSA.js} +290 -7
- package/dist/{http-BFEAurkF.d.ts → http-ZG_iISm1.d.ts} +77 -0
- package/dist/http.d.ts +1 -1
- package/dist/http.js +1 -1
- package/dist/index.d.ts +28 -3
- package/dist/index.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ buyer-funded spend account. The buyer owns the account. The agent holds the
|
|
|
5
5
|
delegate key and can spend only under the on-chain grant (recipient bind,
|
|
6
6
|
per-payment cap, budget).
|
|
7
7
|
|
|
8
|
-
Pin `@mysten-incubation/suipay-mcp@0.3.0-dev.
|
|
8
|
+
Pin `@mysten-incubation/suipay-mcp@0.3.0-dev.10` in MCP config for the `latest` dist-tag.
|
|
9
9
|
`npx -y @mysten-incubation/suipay-mcp@dev` tracks the `dev` prerelease line.
|
|
10
10
|
`1.0.1` remains on the registry as a historical release; do not install it
|
|
11
11
|
for new agents.
|
|
@@ -13,7 +13,7 @@ for new agents.
|
|
|
13
13
|
## Install
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
claude mcp add --scope user suipay -- npx -y @mysten-incubation/suipay-mcp@0.3.0-dev.
|
|
16
|
+
claude mcp add --scope user suipay -- npx -y @mysten-incubation/suipay-mcp@0.3.0-dev.10
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
`@dev` installs the Railway-dev gateway/console defaults. A workspace
|
|
@@ -155,10 +155,10 @@ Default is Railway **suipay-dev**. `--local` / `target: "local"` is laptop-only.
|
|
|
155
155
|
|
|
156
156
|
```bash
|
|
157
157
|
# Railway suipay-dev (default)
|
|
158
|
-
npx @mysten-incubation/suipay-mcp@0.3.0-dev.
|
|
158
|
+
npx @mysten-incubation/suipay-mcp@0.3.0-dev.10
|
|
159
159
|
|
|
160
160
|
# Laptop stack
|
|
161
|
-
npx @mysten-incubation/suipay-mcp@0.3.0-dev.
|
|
161
|
+
npx @mysten-incubation/suipay-mcp@0.3.0-dev.10 --local
|
|
162
162
|
```
|
|
163
163
|
|
|
164
164
|
`SUIPAY_MCP_TARGET=local|dev` selects the same preset. Explicit
|
|
@@ -169,12 +169,32 @@ credential is bound yet. After a successful login, `credentials.json`
|
|
|
169
169
|
Rebind with `suipay_login({ target: "dev" | "local" })` (same key, new consent
|
|
170
170
|
if that stack has no live grant).
|
|
171
171
|
|
|
172
|
+
### Isolate connection environments
|
|
173
|
+
|
|
174
|
+
`SUIPAY_MCP_PROFILE` selects a separate agent-held key file at
|
|
175
|
+
`~/.suipay/profiles/<sha256-of-trimmed-profile>/credentials.json`. Unset or
|
|
176
|
+
blank keeps the existing `~/.suipay/credentials.json`. The profile is a local
|
|
177
|
+
storage selector, not an authorization grant or gateway override: set
|
|
178
|
+
`SUIPAY_GATEWAY_URL` and `SUIPAY_CONSOLE_URL` as well. Use the same stable profile
|
|
179
|
+
for the same environment. Login, status, discovery, pay, and logout all use
|
|
180
|
+
that profile; logout removes only its file. Files stay mode `0600` inside a
|
|
181
|
+
`0700` profile directory. Existing files are never copied, rebound, or deleted
|
|
182
|
+
when selecting a profile. A new profile requires fresh owner authorization.
|
|
183
|
+
|
|
184
|
+
The console's Connect Agent prompt uses its public gateway URL as the profile.
|
|
185
|
+
Install an MCP package that supports profiles before using this prompt, and
|
|
186
|
+
verify the returned login gateway matches the configured environment before
|
|
187
|
+
continuing. An older package may ignore the variable and reuse legacy
|
|
188
|
+
credentials. Stop on a mismatch; do not pay or delete the existing key.
|
|
189
|
+
|
|
172
190
|
## Client environment
|
|
173
191
|
|
|
174
192
|
```bash
|
|
175
193
|
# Optional. Unset = Railway suipay-dev. --local uses localhost:3000 / 127.0.0.1:4340.
|
|
176
194
|
SUIPAY_GATEWAY_URL=https://gateway.example
|
|
177
195
|
SUIPAY_CONSOLE_URL=https://console.example
|
|
196
|
+
# Optional. Unset/blank retains the legacy credentials file.
|
|
197
|
+
SUIPAY_MCP_PROFILE=https://gateway.example
|
|
178
198
|
SUIPAY_RPC_URL=https://your-testnet-rpc.example
|
|
179
199
|
SUIPAY_NETWORK=testnet
|
|
180
200
|
SUIPAY_MCP_TARGET=dev
|
package/dist/bin/suipay.js
CHANGED
|
@@ -448,6 +448,14 @@ var SERVICE_STATE_TRANSITIONS = Object.freeze([
|
|
|
448
448
|
to: "payout_change",
|
|
449
449
|
actors: ["system"],
|
|
450
450
|
requires: ["reason_code"]
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
id: "T25",
|
|
454
|
+
dimension: "suspension",
|
|
455
|
+
from: ["binding_changed"],
|
|
456
|
+
to: "none",
|
|
457
|
+
actors: ["system"],
|
|
458
|
+
requires: ["reason_code"]
|
|
451
459
|
}
|
|
452
460
|
]);
|
|
453
461
|
|
|
@@ -949,6 +957,70 @@ function createSpendAccountChain(options) {
|
|
|
949
957
|
});
|
|
950
958
|
return kindBytes(tx);
|
|
951
959
|
},
|
|
960
|
+
async buildCreatePolicyAndGrantKind(input) {
|
|
961
|
+
if (!input.policy.targets?.length) {
|
|
962
|
+
throw new Error("create_spend_policy requires at least one target");
|
|
963
|
+
}
|
|
964
|
+
if (!input.policy.totalCap || BigInt(input.policy.totalCap) <= 0n) {
|
|
965
|
+
throw new Error("create_spend_policy requires a non-zero total_cap");
|
|
966
|
+
}
|
|
967
|
+
if (!input.policy.maxPerPayment || BigInt(input.policy.maxPerPayment) <= 0n) {
|
|
968
|
+
throw new Error("create_spend_policy requires a non-zero max_per_payment");
|
|
969
|
+
}
|
|
970
|
+
if (!input.grant.sessionCap || BigInt(input.grant.sessionCap) <= 0n) {
|
|
971
|
+
throw new Error("create_grant requires a non-zero session_cap");
|
|
972
|
+
}
|
|
973
|
+
if (!input.grant.maxPerPayment || BigInt(input.grant.maxPerPayment) <= 0n) {
|
|
974
|
+
throw new Error("create_grant requires a non-zero max_per_payment");
|
|
975
|
+
}
|
|
976
|
+
const tx = new Transaction2();
|
|
977
|
+
tx.setSender(input.owner);
|
|
978
|
+
const targetResults = input.policy.targets.map((t) => {
|
|
979
|
+
const hash = targetHashBytes(t.targetHash);
|
|
980
|
+
return tx.moveCall({
|
|
981
|
+
target: target("new_policy_target"),
|
|
982
|
+
arguments: [
|
|
983
|
+
tx.pure.vector("u8", Array.from(hash)),
|
|
984
|
+
tx.pure.address(t.recipient),
|
|
985
|
+
tx.pure.u64(BigInt(t.maxPerPayment))
|
|
986
|
+
]
|
|
987
|
+
});
|
|
988
|
+
});
|
|
989
|
+
const targetsVec = tx.makeMoveVec({
|
|
990
|
+
type: `${packageId}::${MODULE}::PolicyTarget`,
|
|
991
|
+
elements: targetResults
|
|
992
|
+
});
|
|
993
|
+
const policyExpires = input.policy.expiresAtMs === void 0 || input.policy.expiresAtMs === null ? 0n : BigInt(input.policy.expiresAtMs);
|
|
994
|
+
const policyIdResult = tx.moveCall({
|
|
995
|
+
target: target("create_spend_policy"),
|
|
996
|
+
typeArguments: [input.coinType],
|
|
997
|
+
arguments: [
|
|
998
|
+
tx.object(input.poolObjectId),
|
|
999
|
+
targetsVec,
|
|
1000
|
+
tx.pure.u64(BigInt(input.policy.totalCap)),
|
|
1001
|
+
tx.pure.u64(BigInt(input.policy.maxPerPayment)),
|
|
1002
|
+
tx.pure.u64(policyExpires)
|
|
1003
|
+
]
|
|
1004
|
+
});
|
|
1005
|
+
const policyIdsVec = tx.makeMoveVec({
|
|
1006
|
+
type: "u64",
|
|
1007
|
+
elements: [policyIdResult]
|
|
1008
|
+
});
|
|
1009
|
+
const grantExpires = input.grant.expiresAtMs === void 0 || input.grant.expiresAtMs === null ? 0n : BigInt(input.grant.expiresAtMs);
|
|
1010
|
+
tx.moveCall({
|
|
1011
|
+
target: target("create_policy_grant"),
|
|
1012
|
+
typeArguments: [input.coinType],
|
|
1013
|
+
arguments: [
|
|
1014
|
+
tx.object(input.poolObjectId),
|
|
1015
|
+
tx.pure.address(input.delegate),
|
|
1016
|
+
tx.pure.option("u64", BigInt(input.grant.sessionCap)),
|
|
1017
|
+
tx.pure.option("u64", BigInt(input.grant.maxPerPayment)),
|
|
1018
|
+
tx.pure.u64(grantExpires),
|
|
1019
|
+
policyIdsVec
|
|
1020
|
+
]
|
|
1021
|
+
});
|
|
1022
|
+
return kindBytes(tx);
|
|
1023
|
+
},
|
|
952
1024
|
async buildPausePolicyKind(input) {
|
|
953
1025
|
const tx = new Transaction2();
|
|
954
1026
|
tx.setSender(input.owner);
|
|
@@ -3846,6 +3918,17 @@ var INDEXER_CODES = {
|
|
|
3846
3918
|
user: null,
|
|
3847
3919
|
logOnly: true
|
|
3848
3920
|
},
|
|
3921
|
+
"indexer.native_settlement_deferred": {
|
|
3922
|
+
surfaces: ["indexer"],
|
|
3923
|
+
status: null,
|
|
3924
|
+
title: "Native settlement is deferred; holding the checkpoint",
|
|
3925
|
+
paymentState: "not_charged",
|
|
3926
|
+
retry: "after_delay",
|
|
3927
|
+
retryAfterSeconds: 5,
|
|
3928
|
+
nextAction: "retry_later",
|
|
3929
|
+
user: null,
|
|
3930
|
+
logOnly: true
|
|
3931
|
+
},
|
|
3849
3932
|
"indexer.config_invalid": {
|
|
3850
3933
|
surfaces: ["indexer"],
|
|
3851
3934
|
status: null,
|
|
@@ -4460,6 +4543,55 @@ var REQUEST_CODES = {
|
|
|
4460
4543
|
nextAction: "fix_request",
|
|
4461
4544
|
user: { title: "Address not found", message: "That web address doesn't exist. Check the spelling and try again." }
|
|
4462
4545
|
},
|
|
4546
|
+
"request.recipient_changed": {
|
|
4547
|
+
surfaces: ["gateway"],
|
|
4548
|
+
status: 409,
|
|
4549
|
+
title: "Pay-to address changed while its registry was being checked",
|
|
4550
|
+
paymentState: "not_charged",
|
|
4551
|
+
retry: "never",
|
|
4552
|
+
nextAction: "fix_request",
|
|
4553
|
+
user: {
|
|
4554
|
+
title: "Retry this publish",
|
|
4555
|
+
message: "The payout address changed while its payment registry was being checked. Retry so the current address is checked."
|
|
4556
|
+
}
|
|
4557
|
+
},
|
|
4558
|
+
"request.recipient_registry_invalid": {
|
|
4559
|
+
surfaces: ["gateway"],
|
|
4560
|
+
status: 409,
|
|
4561
|
+
title: "Payment registry does not verify for this deployment",
|
|
4562
|
+
paymentState: "not_charged",
|
|
4563
|
+
retry: "never",
|
|
4564
|
+
nextAction: "fix_request",
|
|
4565
|
+
user: {
|
|
4566
|
+
title: "Registry does not match",
|
|
4567
|
+
message: "The payout address's payment registry does not match this deployment. Fix it, then publish again."
|
|
4568
|
+
}
|
|
4569
|
+
},
|
|
4570
|
+
"request.recipient_registry_missing": {
|
|
4571
|
+
surfaces: ["gateway"],
|
|
4572
|
+
status: 409,
|
|
4573
|
+
title: "Pay-to address has no payment registry",
|
|
4574
|
+
paymentState: "not_charged",
|
|
4575
|
+
retry: "never",
|
|
4576
|
+
nextAction: "fix_request",
|
|
4577
|
+
user: {
|
|
4578
|
+
title: "Create a payment registry",
|
|
4579
|
+
message: "This payout address has no payment registry on this network yet. Create it in the seller console, then publish again."
|
|
4580
|
+
}
|
|
4581
|
+
},
|
|
4582
|
+
"request.recipient_registry_unreadable": {
|
|
4583
|
+
surfaces: ["gateway"],
|
|
4584
|
+
status: 503,
|
|
4585
|
+
title: "Payment registry could not be read",
|
|
4586
|
+
paymentState: "not_charged",
|
|
4587
|
+
retry: "after_delay",
|
|
4588
|
+
retryAfterSeconds: 5,
|
|
4589
|
+
nextAction: "retry_later",
|
|
4590
|
+
user: {
|
|
4591
|
+
title: "Registry unread",
|
|
4592
|
+
message: "The payment registry could not be read just now. Wait a moment and try again."
|
|
4593
|
+
}
|
|
4594
|
+
},
|
|
4463
4595
|
"request.redirect_refused": {
|
|
4464
4596
|
surfaces: ["console_server", "sdk"],
|
|
4465
4597
|
status: 422,
|
|
@@ -4924,6 +5056,19 @@ var SERVICE_CODES = {
|
|
|
4924
5056
|
user: null,
|
|
4925
5057
|
logOnly: true
|
|
4926
5058
|
},
|
|
5059
|
+
// A readiness gauge moved while the process is still serving. Not a degraded
|
|
5060
|
+
// boot: `service.config_degraded` is reserved for that, and for the sample
|
|
5061
|
+
// that actually fails closed.
|
|
5062
|
+
"service.readiness": {
|
|
5063
|
+
surfaces: ["gateway"],
|
|
5064
|
+
status: null,
|
|
5065
|
+
title: "Readiness gauge updated",
|
|
5066
|
+
paymentState: "not_charged",
|
|
5067
|
+
retry: "never",
|
|
5068
|
+
nextAction: "none",
|
|
5069
|
+
user: null,
|
|
5070
|
+
logOnly: true
|
|
5071
|
+
},
|
|
4927
5072
|
// A best-effort write (a trace event, a receipt, a delivery record) failed.
|
|
4928
5073
|
// The request it belongs to keeps its answer.
|
|
4929
5074
|
"service.bookkeeping_failed": {
|
|
@@ -5751,6 +5896,11 @@ var CONTEXT_ALLOWLIST = /* @__PURE__ */ new Set([
|
|
|
5751
5896
|
"rails",
|
|
5752
5897
|
"shard",
|
|
5753
5898
|
"metric",
|
|
5899
|
+
// Gateway readiness gauge. Fixed marks only: 0 or 1, and up, down, skipped, unknown.
|
|
5900
|
+
"ready",
|
|
5901
|
+
"grpc",
|
|
5902
|
+
"enoki",
|
|
5903
|
+
"postgres",
|
|
5754
5904
|
"alarm",
|
|
5755
5905
|
"alert",
|
|
5756
5906
|
"from",
|
|
@@ -9849,6 +9999,7 @@ import { bytesToHex as bytesToHex4 } from "@noble/hashes/utils.js";
|
|
|
9849
9999
|
|
|
9850
10000
|
// src/config.ts
|
|
9851
10001
|
import { homedir } from "os";
|
|
10002
|
+
import { createHash as createHash4 } from "crypto";
|
|
9852
10003
|
import { join as join2 } from "path";
|
|
9853
10004
|
import { SuiGrpcClient as SuiGrpcClient2 } from "@mysten/sui/grpc";
|
|
9854
10005
|
import { normalizeSuiAddress as normalizeSuiAddress7 } from "@mysten/sui/utils";
|
|
@@ -9915,6 +10066,11 @@ function configError(detail) {
|
|
|
9915
10066
|
}
|
|
9916
10067
|
function mcpCredentialsPath() {
|
|
9917
10068
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? homedir();
|
|
10069
|
+
const profile2 = process.env.SUIPAY_MCP_PROFILE?.trim();
|
|
10070
|
+
if (profile2) {
|
|
10071
|
+
const id = createHash4("sha256").update(profile2).digest("hex");
|
|
10072
|
+
return join2(home, ".suipay", "profiles", id, "credentials.json");
|
|
10073
|
+
}
|
|
9918
10074
|
return join2(home, ".suipay", "credentials.json");
|
|
9919
10075
|
}
|
|
9920
10076
|
var MCP_TARGET_PRESETS = {
|
|
@@ -13043,6 +13199,13 @@ function receiptChallengeId(receipt) {
|
|
|
13043
13199
|
return typeof id === "string" && id.length > 0 ? id : void 0;
|
|
13044
13200
|
}
|
|
13045
13201
|
var MCP_SAVE_ERROR = "delivery file was not written; do not call pay again";
|
|
13202
|
+
var MCP_EMPTY_DELIVERY = "the seller returned an empty delivery body; no file was written; do not call pay again";
|
|
13203
|
+
function deliverySaveError(cause, result) {
|
|
13204
|
+
const code = cause?.code;
|
|
13205
|
+
const reason = typeof code === "string" && /^E[A-Z0-9_]+$/.test(code) ? code : "file-system operation failed";
|
|
13206
|
+
const recovery = result.paid === true && result.txDigest ? `the body was not saved locally and cannot be re-fetched here; transaction digest ${result.txDigest} identifies the settlement for support` : "no confirmed transaction digest is available";
|
|
13207
|
+
return `${MCP_SAVE_ERROR}; cause: ${reason}; ${recovery}`;
|
|
13208
|
+
}
|
|
13046
13209
|
function paidResult(result) {
|
|
13047
13210
|
if (!isBinaryBody(result.body)) return text(result);
|
|
13048
13211
|
const body = result.body;
|
|
@@ -13052,6 +13215,7 @@ function paidResult(result) {
|
|
|
13052
13215
|
byteLength: body.byteLength
|
|
13053
13216
|
};
|
|
13054
13217
|
let savedTo;
|
|
13218
|
+
let saveError = MCP_EMPTY_DELIVERY;
|
|
13055
13219
|
try {
|
|
13056
13220
|
savedTo = saveDeliveredBody({
|
|
13057
13221
|
bytes: body.bytes,
|
|
@@ -13059,8 +13223,8 @@ function paidResult(result) {
|
|
|
13059
13223
|
digest: result.txDigest,
|
|
13060
13224
|
challengeId: receiptChallengeId(result.receipt)
|
|
13061
13225
|
});
|
|
13062
|
-
} catch {
|
|
13063
|
-
|
|
13226
|
+
} catch (cause) {
|
|
13227
|
+
saveError = deliverySaveError(cause, result);
|
|
13064
13228
|
}
|
|
13065
13229
|
const payload = savedTo ? {
|
|
13066
13230
|
savedTo,
|
|
@@ -13069,7 +13233,7 @@ function paidResult(result) {
|
|
|
13069
13233
|
body: shape
|
|
13070
13234
|
} : {
|
|
13071
13235
|
savedTo: null,
|
|
13072
|
-
saveError
|
|
13236
|
+
saveError,
|
|
13073
13237
|
saveErrorCode: "client.delivery_file_not_saved",
|
|
13074
13238
|
...result,
|
|
13075
13239
|
body: shape
|
|
@@ -13819,7 +13983,7 @@ function grantObjectIdFromBootstrap(grants, nowMs) {
|
|
|
13819
13983
|
}
|
|
13820
13984
|
if (live.length === 1) return { kind: "live", grantObjectId: live[0] };
|
|
13821
13985
|
if (live.length > 1) return { kind: "ambiguous" };
|
|
13822
|
-
if (named > 0) return { kind: "
|
|
13986
|
+
if (named > 0) return { kind: "expired" };
|
|
13823
13987
|
if (grants.length > 0) return { kind: "unknown", detail: "delegate grants list was not usable" };
|
|
13824
13988
|
return { kind: "absent" };
|
|
13825
13989
|
}
|
|
@@ -14266,7 +14430,8 @@ async function suipayLogin(args, cfg, deps = {}) {
|
|
|
14266
14430
|
target: target ?? cfg.target,
|
|
14267
14431
|
delegateAddress: identity.delegateAddress,
|
|
14268
14432
|
gatewayUrl: endpoints.gatewayUrl,
|
|
14269
|
-
|
|
14433
|
+
nextAction: "poll suipay_login_status to verify authorization before retrying payment",
|
|
14434
|
+
message: "Credentials are already saved for this agent. The existing grant could not be checked just now, so no new sign-in was started and the key file was left unchanged. Poll suipay_login_status in a moment."
|
|
14270
14435
|
});
|
|
14271
14436
|
}
|
|
14272
14437
|
}
|
|
@@ -14301,7 +14466,7 @@ async function suipayLogin(args, cfg, deps = {}) {
|
|
|
14301
14466
|
``,
|
|
14302
14467
|
`1. Use the browser window that just opened, or open the URL above`,
|
|
14303
14468
|
`2. Connect the owner wallet, create a key, then Authorize on the review step`,
|
|
14304
|
-
`3.
|
|
14469
|
+
`3. Poll \`suipay_login_status\` to confirm authorization \u2014 credentials at \`~/.suipay/credentials.json\` are active once approved`,
|
|
14305
14470
|
``,
|
|
14306
14471
|
`_The login link stays valid for 5 minutes. If it expires, call \`suipay_login\` again._`,
|
|
14307
14472
|
``,
|
|
@@ -14311,11 +14476,113 @@ async function suipayLogin(args, cfg, deps = {}) {
|
|
|
14311
14476
|
delegateAddress: identity.delegateAddress,
|
|
14312
14477
|
connectUrl: url,
|
|
14313
14478
|
consoleUrl: endpoints.consoleUrl,
|
|
14314
|
-
gatewayUrl: endpoints.gatewayUrl
|
|
14479
|
+
gatewayUrl: endpoints.gatewayUrl,
|
|
14480
|
+
nextAction: "poll suipay_login_status to verify authorization before retrying payment"
|
|
14315
14481
|
})
|
|
14316
14482
|
].join("\n")
|
|
14317
14483
|
);
|
|
14318
14484
|
}
|
|
14485
|
+
function defaultKeyName(delegateAddress) {
|
|
14486
|
+
const clean = delegateAddress.trim().toLowerCase();
|
|
14487
|
+
const hex = clean.startsWith("0x") ? clean.slice(2) : clean;
|
|
14488
|
+
const prefix = hex.slice(0, 8).padEnd(8, "0");
|
|
14489
|
+
return `agent-${prefix}`;
|
|
14490
|
+
}
|
|
14491
|
+
function safeRetryDetail(rawDetail) {
|
|
14492
|
+
if (!rawDetail) {
|
|
14493
|
+
return "Grant status could not be verified on chain. Retry shortly.";
|
|
14494
|
+
}
|
|
14495
|
+
let stripped = rawDetail.replace(/https?:\/\/[^\s"'<>]+/gi, "[redacted]");
|
|
14496
|
+
if (stripped.includes("ECONNREFUSED") || stripped.includes("ETIMEDOUT") || stripped.includes("fetch failed")) {
|
|
14497
|
+
return "Network connection to gateway or chain node failed. Retry shortly.";
|
|
14498
|
+
}
|
|
14499
|
+
return stripped;
|
|
14500
|
+
}
|
|
14501
|
+
var loginStatusCache = /* @__PURE__ */ new Map();
|
|
14502
|
+
var DEFAULT_LOGIN_STATUS_RETRY_AFTER_MS = 2e3;
|
|
14503
|
+
var DEFAULT_LOGIN_STATUS_CACHE_TTL_MS = 1500;
|
|
14504
|
+
async function suipayLoginStatus(_args = {}, cfg, deps = {}) {
|
|
14505
|
+
if (deps.auth) {
|
|
14506
|
+
return mcpFailure(
|
|
14507
|
+
"request.local_only",
|
|
14508
|
+
"suipay_login_status is for local stdio only; hosted /mcp never holds a spendable key"
|
|
14509
|
+
);
|
|
14510
|
+
}
|
|
14511
|
+
const loadKeypair = deps.loadLocalKeypair ?? loadLocalKeypair;
|
|
14512
|
+
const keypair = loadKeypair();
|
|
14513
|
+
if (!keypair) {
|
|
14514
|
+
return text({
|
|
14515
|
+
status: "not_started",
|
|
14516
|
+
nextAction: "run suipay_login"
|
|
14517
|
+
});
|
|
14518
|
+
}
|
|
14519
|
+
const delegateAddress = keypair.toSuiAddress();
|
|
14520
|
+
const stored = loadLocalSigningKeyRecord();
|
|
14521
|
+
const gatewayUrl = stored?.gatewayUrl ?? cfg.gatewayUrl;
|
|
14522
|
+
const cacheKey = `${gatewayUrl}:${delegateAddress}`;
|
|
14523
|
+
const now = Date.now();
|
|
14524
|
+
const cacheTtlMs = deps.cacheTtlMs ?? DEFAULT_LOGIN_STATUS_CACHE_TTL_MS;
|
|
14525
|
+
if (!deps.bypassCache) {
|
|
14526
|
+
const cached = loginStatusCache.get(cacheKey);
|
|
14527
|
+
if (cached && now - cached.timestamp < cacheTtlMs) {
|
|
14528
|
+
return cached.result;
|
|
14529
|
+
}
|
|
14530
|
+
}
|
|
14531
|
+
const checkFn = deps.checkLiveDelegateGrant ?? checkLiveDelegateGrant;
|
|
14532
|
+
let live;
|
|
14533
|
+
try {
|
|
14534
|
+
live = await checkFn({
|
|
14535
|
+
gatewayUrl,
|
|
14536
|
+
delegateAddress,
|
|
14537
|
+
signPersonalMessage: async (bytes) => {
|
|
14538
|
+
const { signature } = await keypair.signPersonalMessage(bytes);
|
|
14539
|
+
return signature;
|
|
14540
|
+
},
|
|
14541
|
+
...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
|
|
14542
|
+
});
|
|
14543
|
+
} catch (err) {
|
|
14544
|
+
const rawMessage = err instanceof Error ? err.message : String(err);
|
|
14545
|
+
live = {
|
|
14546
|
+
kind: "unknown",
|
|
14547
|
+
detail: rawMessage
|
|
14548
|
+
};
|
|
14549
|
+
}
|
|
14550
|
+
let result;
|
|
14551
|
+
if (live.kind === "live") {
|
|
14552
|
+
result = text({
|
|
14553
|
+
status: "connected",
|
|
14554
|
+
grantObjectId: live.grantObjectId,
|
|
14555
|
+
delegateAddress
|
|
14556
|
+
});
|
|
14557
|
+
} else if (live.kind === "expired") {
|
|
14558
|
+
result = text({
|
|
14559
|
+
status: "expired",
|
|
14560
|
+
delegateAddress,
|
|
14561
|
+
detail: "Grant has expired. Run suipay_login to reconnect.",
|
|
14562
|
+
nextAction: "run suipay_login"
|
|
14563
|
+
});
|
|
14564
|
+
} else if (live.kind === "absent") {
|
|
14565
|
+
result = text({
|
|
14566
|
+
status: "pending",
|
|
14567
|
+
delegateAddress,
|
|
14568
|
+
defaultKeyName: defaultKeyName(delegateAddress)
|
|
14569
|
+
});
|
|
14570
|
+
} else if (live.kind === "ambiguous") {
|
|
14571
|
+
result = text({
|
|
14572
|
+
status: "ambiguous",
|
|
14573
|
+
delegateAddress,
|
|
14574
|
+
detail: "Multiple live grants found for this delegate key. Revoke the duplicate grants and reconnect."
|
|
14575
|
+
});
|
|
14576
|
+
} else {
|
|
14577
|
+
result = text({
|
|
14578
|
+
status: "retry",
|
|
14579
|
+
retryAfterMs: DEFAULT_LOGIN_STATUS_RETRY_AFTER_MS,
|
|
14580
|
+
detail: safeRetryDetail(live.detail)
|
|
14581
|
+
});
|
|
14582
|
+
}
|
|
14583
|
+
loginStatusCache.set(cacheKey, { timestamp: Date.now(), result });
|
|
14584
|
+
return result;
|
|
14585
|
+
}
|
|
14319
14586
|
async function logout(args, _cfg, deps = {}) {
|
|
14320
14587
|
if (args.force) {
|
|
14321
14588
|
clearCreds();
|
|
@@ -14419,6 +14686,14 @@ var TOOLS = [
|
|
|
14419
14686
|
}
|
|
14420
14687
|
}
|
|
14421
14688
|
},
|
|
14689
|
+
{
|
|
14690
|
+
name: "suipay_login_status",
|
|
14691
|
+
description: `Check whether the local delegate key has an approved, active spend-account grant, read from the gateway's delegate-grant state. Read-only and pollable (does not mint keys, write credentials, or sign transactions). Returns one of five states: (1) "not_started" if no local key exists yet (call suipay_login first); (2) "connected" if a live grant exists for this local delegate key (note: "connected" means a live grant exists for this delegate key, which may be pre-existing from a prior session); (3) "pending" if the key exists but no grant has been approved yet (includes delegateAddress and a defaultKeyName of format "agent-<first-8-hex-chars-of-address-without-0x>"; note that if the owner already has a live key with this normalized name, creating it may yield 409 CONFLICT); (4) "ambiguous" (terminal) if several live grants exist for this delegate key \u2014 stop polling and revoke the duplicates; (5) "retry" if the status could not be verified (includes retryAfterMs floor and safe detail). Stdio only; hosted /mcp never holds a spendable key.`,
|
|
14692
|
+
inputSchema: {
|
|
14693
|
+
type: "object",
|
|
14694
|
+
properties: {}
|
|
14695
|
+
}
|
|
14696
|
+
},
|
|
14422
14697
|
{
|
|
14423
14698
|
name: "suipay_logout",
|
|
14424
14699
|
description: "Remove the local delegate-key credentials file. Missing file is success. Force clears a corrupt file without printing the seed.",
|
|
@@ -14566,6 +14841,14 @@ function createServer2(cfg, auth, trace, options = {}) {
|
|
|
14566
14841
|
}
|
|
14567
14842
|
);
|
|
14568
14843
|
}
|
|
14844
|
+
case "suipay_login_status":
|
|
14845
|
+
if (auth) {
|
|
14846
|
+
return mcpFailure(
|
|
14847
|
+
"request.local_only",
|
|
14848
|
+
"suipay_login_status is for local stdio only; hosted /mcp never holds a spendable key"
|
|
14849
|
+
);
|
|
14850
|
+
}
|
|
14851
|
+
return await suipayLoginStatus(args, cfg, { auth });
|
|
14569
14852
|
case "suipay_logout":
|
|
14570
14853
|
if (auth) {
|
|
14571
14854
|
return mcpFailure(
|
|
@@ -865,6 +865,17 @@ declare const ERRORS: {
|
|
|
865
865
|
readonly user: null;
|
|
866
866
|
readonly logOnly: true;
|
|
867
867
|
};
|
|
868
|
+
readonly 'indexer.native_settlement_deferred': {
|
|
869
|
+
readonly surfaces: readonly ["indexer"];
|
|
870
|
+
readonly status: null;
|
|
871
|
+
readonly title: "Native settlement is deferred; holding the checkpoint";
|
|
872
|
+
readonly paymentState: "not_charged";
|
|
873
|
+
readonly retry: "after_delay";
|
|
874
|
+
readonly retryAfterSeconds: 5;
|
|
875
|
+
readonly nextAction: "retry_later";
|
|
876
|
+
readonly user: null;
|
|
877
|
+
readonly logOnly: true;
|
|
878
|
+
};
|
|
868
879
|
readonly 'indexer.config_invalid': {
|
|
869
880
|
readonly surfaces: readonly ["indexer"];
|
|
870
881
|
readonly status: null;
|
|
@@ -1267,6 +1278,16 @@ declare const ERRORS: {
|
|
|
1267
1278
|
readonly user: null;
|
|
1268
1279
|
readonly logOnly: true;
|
|
1269
1280
|
};
|
|
1281
|
+
readonly 'service.readiness': {
|
|
1282
|
+
readonly surfaces: readonly ["gateway"];
|
|
1283
|
+
readonly status: null;
|
|
1284
|
+
readonly title: "Readiness gauge updated";
|
|
1285
|
+
readonly paymentState: "not_charged";
|
|
1286
|
+
readonly retry: "never";
|
|
1287
|
+
readonly nextAction: "none";
|
|
1288
|
+
readonly user: null;
|
|
1289
|
+
readonly logOnly: true;
|
|
1290
|
+
};
|
|
1270
1291
|
readonly 'service.bookkeeping_failed': {
|
|
1271
1292
|
readonly surfaces: readonly ["gateway"];
|
|
1272
1293
|
readonly status: null;
|
|
@@ -3504,6 +3525,55 @@ declare const ERRORS: {
|
|
|
3504
3525
|
readonly message: "That web address doesn't exist. Check the spelling and try again.";
|
|
3505
3526
|
};
|
|
3506
3527
|
};
|
|
3528
|
+
readonly 'request.recipient_changed': {
|
|
3529
|
+
readonly surfaces: readonly ["gateway"];
|
|
3530
|
+
readonly status: 409;
|
|
3531
|
+
readonly title: "Pay-to address changed while its registry was being checked";
|
|
3532
|
+
readonly paymentState: "not_charged";
|
|
3533
|
+
readonly retry: "never";
|
|
3534
|
+
readonly nextAction: "fix_request";
|
|
3535
|
+
readonly user: {
|
|
3536
|
+
readonly title: "Retry this publish";
|
|
3537
|
+
readonly message: "The payout address changed while its payment registry was being checked. Retry so the current address is checked.";
|
|
3538
|
+
};
|
|
3539
|
+
};
|
|
3540
|
+
readonly 'request.recipient_registry_invalid': {
|
|
3541
|
+
readonly surfaces: readonly ["gateway"];
|
|
3542
|
+
readonly status: 409;
|
|
3543
|
+
readonly title: "Payment registry does not verify for this deployment";
|
|
3544
|
+
readonly paymentState: "not_charged";
|
|
3545
|
+
readonly retry: "never";
|
|
3546
|
+
readonly nextAction: "fix_request";
|
|
3547
|
+
readonly user: {
|
|
3548
|
+
readonly title: "Registry does not match";
|
|
3549
|
+
readonly message: "The payout address's payment registry does not match this deployment. Fix it, then publish again.";
|
|
3550
|
+
};
|
|
3551
|
+
};
|
|
3552
|
+
readonly 'request.recipient_registry_missing': {
|
|
3553
|
+
readonly surfaces: readonly ["gateway"];
|
|
3554
|
+
readonly status: 409;
|
|
3555
|
+
readonly title: "Pay-to address has no payment registry";
|
|
3556
|
+
readonly paymentState: "not_charged";
|
|
3557
|
+
readonly retry: "never";
|
|
3558
|
+
readonly nextAction: "fix_request";
|
|
3559
|
+
readonly user: {
|
|
3560
|
+
readonly title: "Create a payment registry";
|
|
3561
|
+
readonly message: "This payout address has no payment registry on this network yet. Create it in the seller console, then publish again.";
|
|
3562
|
+
};
|
|
3563
|
+
};
|
|
3564
|
+
readonly 'request.recipient_registry_unreadable': {
|
|
3565
|
+
readonly surfaces: readonly ["gateway"];
|
|
3566
|
+
readonly status: 503;
|
|
3567
|
+
readonly title: "Payment registry could not be read";
|
|
3568
|
+
readonly paymentState: "not_charged";
|
|
3569
|
+
readonly retry: "after_delay";
|
|
3570
|
+
readonly retryAfterSeconds: 5;
|
|
3571
|
+
readonly nextAction: "retry_later";
|
|
3572
|
+
readonly user: {
|
|
3573
|
+
readonly title: "Registry unread";
|
|
3574
|
+
readonly message: "The payment registry could not be read just now. Wait a moment and try again.";
|
|
3575
|
+
};
|
|
3576
|
+
};
|
|
3507
3577
|
readonly 'request.redirect_refused': {
|
|
3508
3578
|
readonly surfaces: readonly ["console_server", "sdk"];
|
|
3509
3579
|
readonly status: 422;
|
|
@@ -4912,6 +4982,13 @@ declare const TOOLS: readonly [{
|
|
|
4912
4982
|
};
|
|
4913
4983
|
};
|
|
4914
4984
|
};
|
|
4985
|
+
}, {
|
|
4986
|
+
readonly name: "suipay_login_status";
|
|
4987
|
+
readonly description: string;
|
|
4988
|
+
readonly inputSchema: {
|
|
4989
|
+
readonly type: "object";
|
|
4990
|
+
readonly properties: {};
|
|
4991
|
+
};
|
|
4915
4992
|
}, {
|
|
4916
4993
|
readonly name: "suipay_logout";
|
|
4917
4994
|
readonly description: "Remove the local delegate-key credentials file. Missing file is success. Force clears a corrupt file without printing the seed.";
|
package/dist/http.d.ts
CHANGED
package/dist/http.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { S as SpendAccountSigningRequest, P as PaidHttpRequest, D as DelegatedKeySigner, G as GetObjectJson, a as DelegatedPayerSession, b as DelegatedPayerConfig, c as DelegatedPayerDeps, d as SpendIntent, e as DelegatedPayResult, f as DelegationScope, C as CodeFor, N as NextAction, g as GrantPolicyBinding, h as PaymentAttemptLedger, O as OfferPublicKeySource, i as DelegatedPayerLog, j as PreparedSettlementTerms, X as X402PayProgress, k as Offer, M as McpConfig, l as McpAuthContext, m as McpTraceHooks, n as SpendAccountPayDeps, o as createReader } from './http-BFEAurkF.js';
|
|
2
|
-
export { A as AgentHeldResolver, p as AgentHeldSettlement, q as MCP_TARGET_PRESETS, r as McpEnvelope, s as McpEnvelopeEntry, t as McpServerOptions, u as McpTarget, v as McpTraceContext, w as McpTraceRuntime, x as PACKAGE_NAME, y as PreparedPayment, z as SpendAccountPrepareResult, B as SpendAccountPrepareSession, T as TOOLS, E as buildSpendAccountPayKind, F as canonicalMcpRequestId, H as createServer, I as handleSuipayMcpHttpRequest, J as inspectMcpEnvelope, K as loadMcpConfig, L as parseMcpTargetFlag, Q as prepareSpendAccountPayment, R as resetAgentHeldProcessLedger, U as resolveAgentHeldSettlement, V as resolveGrantTarget, W as resolveMcpTarget, Y as sameMcpGateway, Z as traceMcpHttpRequest, _ as verifySpendAccountPayReconstructs } from './http-BFEAurkF.js';
|
|
3
|
-
import { SuiGrpcClient } from '@mysten/sui/grpc';
|
|
4
1
|
import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
|
|
2
|
+
import { S as SpendAccountSigningRequest, P as PaidHttpRequest, D as DelegatedKeySigner, G as GetObjectJson, a as DelegatedPayerSession, b as DelegatedPayerConfig, c as DelegatedPayerDeps, d as SpendIntent, e as DelegatedPayResult, f as DelegationScope, C as CodeFor, N as NextAction, g as GrantPolicyBinding, h as PaymentAttemptLedger, O as OfferPublicKeySource, i as DelegatedPayerLog, j as PreparedSettlementTerms, X as X402PayProgress, k as Offer, M as McpConfig, l as McpAuthContext, m as McpTraceHooks, n as SpendAccountPayDeps, o as createReader } from './http-ZG_iISm1.js';
|
|
3
|
+
export { A as AgentHeldResolver, p as AgentHeldSettlement, q as MCP_TARGET_PRESETS, r as McpEnvelope, s as McpEnvelopeEntry, t as McpServerOptions, u as McpTarget, v as McpTraceContext, w as McpTraceRuntime, x as PACKAGE_NAME, y as PreparedPayment, z as SpendAccountPrepareResult, B as SpendAccountPrepareSession, T as TOOLS, E as buildSpendAccountPayKind, F as canonicalMcpRequestId, H as createServer, I as handleSuipayMcpHttpRequest, J as inspectMcpEnvelope, K as loadMcpConfig, L as parseMcpTargetFlag, Q as prepareSpendAccountPayment, R as resetAgentHeldProcessLedger, U as resolveAgentHeldSettlement, V as resolveGrantTarget, W as resolveMcpTarget, Y as sameMcpGateway, Z as traceMcpHttpRequest, _ as verifySpendAccountPayReconstructs } from './http-ZG_iISm1.js';
|
|
4
|
+
import { SuiGrpcClient } from '@mysten/sui/grpc';
|
|
5
5
|
import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
6
6
|
import '@modelcontextprotocol/sdk/server/index.js';
|
|
7
7
|
import 'node:crypto';
|
|
@@ -65,6 +65,8 @@ declare function loadLocalSigner(): {
|
|
|
65
65
|
signTransaction: (txBytes: string) => Promise<string>;
|
|
66
66
|
signPersonalMessage: (message: Uint8Array) => Promise<string>;
|
|
67
67
|
} | null;
|
|
68
|
+
/** Ed25519 keypair from the persisted seed. Null when this process holds no key. */
|
|
69
|
+
declare function loadLocalKeypair(): Ed25519Keypair | null;
|
|
68
70
|
|
|
69
71
|
/**
|
|
70
72
|
* Client-side completion of an MCP prepared payment.
|
|
@@ -581,6 +583,10 @@ interface ToolDeps {
|
|
|
581
583
|
createPayer?: typeof createPayer;
|
|
582
584
|
createReader?: typeof createReader;
|
|
583
585
|
startStdioLogin?: typeof startStdioLogin;
|
|
586
|
+
loadLocalKeypair?: typeof loadLocalKeypair;
|
|
587
|
+
checkLiveDelegateGrant?: typeof checkLiveDelegateGrant;
|
|
588
|
+
bypassCache?: boolean;
|
|
589
|
+
cacheTtlMs?: number;
|
|
584
590
|
}
|
|
585
591
|
declare function pay(args: {
|
|
586
592
|
url: string;
|
|
@@ -591,6 +597,25 @@ declare function pay(args: {
|
|
|
591
597
|
recipient?: unknown;
|
|
592
598
|
maxAmount?: unknown;
|
|
593
599
|
}, cfg: McpConfig, deps?: ToolDeps): Promise<ToolResult>;
|
|
600
|
+
type LiveGrantCheck = {
|
|
601
|
+
kind: 'live';
|
|
602
|
+
grantObjectId: string;
|
|
603
|
+
} | {
|
|
604
|
+
kind: 'ambiguous';
|
|
605
|
+
} | {
|
|
606
|
+
kind: 'absent';
|
|
607
|
+
} | {
|
|
608
|
+
kind: 'expired';
|
|
609
|
+
} | {
|
|
610
|
+
kind: 'unknown';
|
|
611
|
+
detail: string;
|
|
612
|
+
};
|
|
613
|
+
declare function checkLiveDelegateGrant(args: {
|
|
614
|
+
gatewayUrl: string;
|
|
615
|
+
delegateAddress: string;
|
|
616
|
+
signPersonalMessage: (message: Uint8Array) => Promise<string>;
|
|
617
|
+
fetchImpl?: typeof fetch;
|
|
618
|
+
}): Promise<LiveGrantCheck>;
|
|
594
619
|
declare function receipts(args: {
|
|
595
620
|
challengeId?: string;
|
|
596
621
|
}, cfg: McpConfig, deps?: ToolDeps): Promise<ToolResult>;
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
loadBootProfile,
|
|
3
3
|
main
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-D6LFU4VE.js";
|
|
5
5
|
import {
|
|
6
6
|
MCP_TARGET_PRESETS,
|
|
7
7
|
PACKAGE_NAME,
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
settlePreparedMcpPayment,
|
|
27
27
|
traceMcpHttpRequest,
|
|
28
28
|
verifySpendAccountPayReconstructs
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-RD34GGSA.js";
|
|
30
30
|
export {
|
|
31
31
|
MCP_TARGET_PRESETS,
|
|
32
32
|
PACKAGE_NAME,
|
package/package.json
CHANGED