@zktx.io/sui-mcp 0.5.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/LICENSE +21 -0
- package/LICENSES/@mysten-bcs-2.0.5-Apache-2.0.txt +205 -0
- package/LICENSES/@mysten-sui-2.17.0-Apache-2.0.txt +201 -0
- package/LICENSES/@mysten-utils-0.3.3-Apache-2.0.txt +205 -0
- package/LICENSES/fancy-canvas-2.1.0-MIT.txt +7 -0
- package/LICENSES/lightweight-charts-5.2.0-NOTICE.txt +2 -0
- package/README.md +288 -0
- package/dist/adapters/adapterLifecycleValidators.js +5 -0
- package/dist/adapters/adapterPromptSurfaces.js +60 -0
- package/dist/adapters/deepbook/deepbookHumanReviewProducer.js +178 -0
- package/dist/adapters/deepbook/deepbookQuotePolicy.js +112 -0
- package/dist/adapters/deepbook/deepbookReviewEvidence.js +507 -0
- package/dist/adapters/deepbook/deepbookReviewLifecycle.js +85 -0
- package/dist/adapters/deepbook/deepbookSwapIntent.js +79 -0
- package/dist/adapters/deepbook/deepbookTransactionMaterialProducer.js +254 -0
- package/dist/adapters/intentPlanFactories.js +51 -0
- package/dist/adapters/reviewAdapters.js +47 -0
- package/dist/core/action/adapterLifecycleValidation.js +12 -0
- package/dist/core/action/contractNameRegistry.js +151 -0
- package/dist/core/action/forbiddenFields.js +43 -0
- package/dist/core/action/humanReadableReviewEvidence.js +193 -0
- package/dist/core/action/humanReadableReviewProjectionVerifier.js +29 -0
- package/dist/core/action/ptbVisualizationProducer.js +71 -0
- package/dist/core/action/receiptPtbGraph.js +29 -0
- package/dist/core/action/reviewCheckResults.js +6 -0
- package/dist/core/action/reviewStateValidation.js +11 -0
- package/dist/core/action/reviewTimeSimulationEvidence.js +470 -0
- package/dist/core/action/schemas.js +491 -0
- package/dist/core/action/signableAdapterContract.js +998 -0
- package/dist/core/action/suiChainReceiptEvidence.js +106 -0
- package/dist/core/action/suiChainReceiptMapping.js +107 -0
- package/dist/core/action/suiChainReceiptReader.js +211 -0
- package/dist/core/action/suiChainReceiptVerifier.js +178 -0
- package/dist/core/action/swapHumanReadableReviewProjection.js +130 -0
- package/dist/core/action/swapQuotePolicyEvidence.js +271 -0
- package/dist/core/action/transactionFunding.js +244 -0
- package/dist/core/action/transactionObjectOwnershipEvidence.js +256 -0
- package/dist/core/action/transactionObjectOwnershipProducer.js +323 -0
- package/dist/core/action/types.js +33 -0
- package/dist/core/action/walletReviewContractAssembler.js +286 -0
- package/dist/core/activity/accountAssetTimeline.js +157 -0
- package/dist/core/activity/accountAssetTimelineUsdcReferences.js +120 -0
- package/dist/core/activity/activityStore.js +16 -0
- package/dist/core/activity/externalActivityCoverage.js +76 -0
- package/dist/core/activity/externalActivityTransactionStream.js +82 -0
- package/dist/core/activity/localDataService.js +296 -0
- package/dist/core/activity/localDataTypes.js +11 -0
- package/dist/core/activity/localDataValidation.js +439 -0
- package/dist/core/activity/schemaVersion.js +2 -0
- package/dist/core/activity/sqliteActivityStore.js +1068 -0
- package/dist/core/activity/sqliteActivityStoreRows.js +389 -0
- package/dist/core/activity/sqliteActivityStoreSchema.js +367 -0
- package/dist/core/activity/sqliteActivityStoreTypes.js +5 -0
- package/dist/core/activity/suiFunctionTarget.js +43 -0
- package/dist/core/activity/transactionActivityAccountEffects.js +186 -0
- package/dist/core/activity/transactionActivityAnalysis.js +295 -0
- package/dist/core/activity/transactionActivityClassifier.js +306 -0
- package/dist/core/activity/transactionActivityDetails.js +229 -0
- package/dist/core/activity/transactionActivityProtocolRules.js +218 -0
- package/dist/core/activity/transactionActivityScanPolicy.js +170 -0
- package/dist/core/activity/transactionActivityService.js +379 -0
- package/dist/core/activity/transactionActivityTypes.js +18 -0
- package/dist/core/eventlog/sink.js +35 -0
- package/dist/core/evidence/settlementFamilies.js +85 -0
- package/dist/core/evidence/userAnswerUse.js +1 -0
- package/dist/core/numeric/rawU64.js +63 -0
- package/dist/core/preferences/preferencesStore.js +26 -0
- package/dist/core/preferences/sqlitePreferencesRepository.js +136 -0
- package/dist/core/proposal/externalProposalReview.js +347 -0
- package/dist/core/proposal/schemas.js +208 -0
- package/dist/core/proposal/types.js +35 -0
- package/dist/core/read/amounts.js +14 -0
- package/dist/core/read/coinMetadata.js +60 -0
- package/dist/core/read/deepbookOfficialIndexerSource.js +373 -0
- package/dist/core/read/deepbookRawQuoteClient.js +86 -0
- package/dist/core/read/deepbookReadHelpers.js +321 -0
- package/dist/core/read/deepbookRegistry.js +157 -0
- package/dist/core/read/deepbookSourceOwners.js +159 -0
- package/dist/core/read/deepbookUsdcChartService.js +342 -0
- package/dist/core/read/intentEvidenceResponseFormatting.js +228 -0
- package/dist/core/read/readCardInputs.js +19 -0
- package/dist/core/read/readCardResult.js +40 -0
- package/dist/core/read/readResponseGuidance.js +402 -0
- package/dist/core/read/readService.js +1639 -0
- package/dist/core/read/readServiceTypes.js +88 -0
- package/dist/core/read/settlementParityFormatting.js +82 -0
- package/dist/core/read/walletReadHelpers.js +99 -0
- package/dist/core/review/reviewChecks.js +45 -0
- package/dist/core/review/reviewComputation.js +38 -0
- package/dist/core/review/reviewComputationResult.js +87 -0
- package/dist/core/session/cardSession.js +31 -0
- package/dist/core/session/cardSessionStore.js +204 -0
- package/dist/core/session/keyedRecordStore.js +18 -0
- package/dist/core/session/liveReviewSessionContract.js +3 -0
- package/dist/core/session/localSession.js +31 -0
- package/dist/core/session/privateReviewArtifacts.js +75 -0
- package/dist/core/session/reviewValidity.js +49 -0
- package/dist/core/session/sessionErrors.js +9 -0
- package/dist/core/session/sessionRecordStore.js +40 -0
- package/dist/core/session/sessionStore.js +655 -0
- package/dist/core/session/settingsSession.js +1 -0
- package/dist/core/session/settingsSessions.js +45 -0
- package/dist/core/session/sqliteCardStore.js +88 -0
- package/dist/core/session/sqliteSessionStore.js +213 -0
- package/dist/core/session/sqliteTransactionMaterialStore.js +64 -0
- package/dist/core/session/sqliteWalletWorkflowStore.js +821 -0
- package/dist/core/session/status.js +60 -0
- package/dist/core/session/transactionMaterialStore.js +221 -0
- package/dist/core/session/transactionRequest.js +72 -0
- package/dist/core/session/wait.js +79 -0
- package/dist/core/session/walletConnection.js +92 -0
- package/dist/core/session/walletRuntime.js +38 -0
- package/dist/core/session/walletWorkflow.js +608 -0
- package/dist/core/session/workflowState.js +80 -0
- package/dist/core/session/workflowView.js +150 -0
- package/dist/core/suiAddress.js +18 -0
- package/dist/core/suiEndpoint.js +72 -0
- package/dist/mcp/activeAccountResponse.js +24 -0
- package/dist/mcp/prompts.js +146 -0
- package/dist/mcp/registerTool.js +19 -0
- package/dist/mcp/resources.js +72 -0
- package/dist/mcp/responseGuidance.js +326 -0
- package/dist/mcp/result.js +17 -0
- package/dist/mcp/schemas.js +11 -0
- package/dist/mcp/server.js +32 -0
- package/dist/mcp/serverInfo.js +131 -0
- package/dist/mcp/toolErrors.js +118 -0
- package/dist/mcp/toolNames.js +58 -0
- package/dist/mcp/tools/account/index.js +44 -0
- package/dist/mcp/tools/action/prepareSuiActionReview.js +126 -0
- package/dist/mcp/tools/read/commonSchemas.js +43 -0
- package/dist/mcp/tools/read/deepbookReadTools.js +686 -0
- package/dist/mcp/tools/read/index.js +14 -0
- package/dist/mcp/tools/read/readToolHelpers.js +97 -0
- package/dist/mcp/tools/read/reviewActivityTools.js +179 -0
- package/dist/mcp/tools/read/serverStatusTools.js +100 -0
- package/dist/mcp/tools/read/transactionActivityOutput.js +371 -0
- package/dist/mcp/tools/read/transactionActivityTools.js +858 -0
- package/dist/mcp/tools/read/walletReadTools.js +734 -0
- package/dist/mcp/tools/session/executionResultTools.js +44 -0
- package/dist/mcp/tools/session/index.js +8 -0
- package/dist/mcp/tools/session/shared.js +36 -0
- package/dist/mcp/tools/session/statusTools.js +69 -0
- package/dist/mcp/tools/session/walletConnectionTools.js +69 -0
- package/dist/mcp/tools/settings/index.js +66 -0
- package/dist/mcp-app/account.html +3133 -0
- package/dist/mcp-app/account.notices.txt +973 -0
- package/dist/mcp-app/chart.html +3354 -0
- package/dist/mcp-app/chart.notices.txt +1194 -0
- package/dist/mcp-app/connect.html +3179 -0
- package/dist/mcp-app/connect.notices.txt +1012 -0
- package/dist/mcp-app/receipt.html +8430 -0
- package/dist/mcp-app/receipt.notices.txt +3549 -0
- package/dist/mcp-app/review.html +8430 -0
- package/dist/mcp-app/review.notices.txt +3549 -0
- package/dist/mcp-ui/contracts.js +31 -0
- package/dist/mcp-ui/readCards.js +28 -0
- package/dist/mcp-ui/tools.js +134 -0
- package/dist/review-app/brand-dark.svg +1 -0
- package/dist/review-app/brand-light.svg +1 -0
- package/dist/review-app/favicon.svg +1 -0
- package/dist/review-app/settings.css +1 -0
- package/dist/review-app/settings.js +1 -0
- package/dist/review-app/ui.css +809 -0
- package/dist/review-server/assets.js +48 -0
- package/dist/review-server/html.js +45 -0
- package/dist/review-server/http.js +47 -0
- package/dist/review-server/middleware/hostOrigin.js +48 -0
- package/dist/review-server/middleware/reviewToken.js +7 -0
- package/dist/review-server/reviewServerPolicy.js +1 -0
- package/dist/review-server/server.js +72 -0
- package/dist/review-server/settingsApi.js +158 -0
- package/dist/runtime/application.js +203 -0
- package/dist/runtime/config.js +97 -0
- package/dist/runtime/localSettingsService.js +198 -0
- package/dist/runtime/logger.js +66 -0
- package/dist/runtime/reviewDependencies.js +58 -0
- package/dist/runtime/reviewServerAcquire.js +93 -0
- package/dist/runtime/runtimeTermination.js +60 -0
- package/dist/runtime/shared/authenticatedFetch.js +127 -0
- package/dist/runtime/shared/control.js +93 -0
- package/dist/runtime/shared/dataAccess.js +66 -0
- package/dist/runtime/shared/mcpHttp.js +84 -0
- package/dist/runtime/shared/ownerLease.js +50 -0
- package/dist/runtime/shared/server.js +98 -0
- package/dist/runtime/shared/stdio.js +113 -0
- package/dist/runtime/smokeMainnetRead.js +391 -0
- package/dist/runtime/smokeMainnetReadAssertions.js +306 -0
- package/dist/runtime/smokeReview.js +40 -0
- package/dist/runtime/start.js +90 -0
- package/dist/runtime/suiEndpoint.js +97 -0
- package/dist/runtime/suiTransactionGraphqlMapping.js +200 -0
- package/dist/runtime/suiTransactionGraphqlQueries.js +231 -0
- package/dist/runtime/suiTransactionGraphqlSource.js +148 -0
- package/dist/runtime/walletConnectConfig.js +3 -0
- package/dist/runtime/walletConnectStorage.js +99 -0
- package/dist/runtime/walletConnectTransport.js +128 -0
- package/dist/runtime/walletSdkChild.js +3 -0
- package/dist/runtime/walletSdkChildRuntime.js +160 -0
- package/dist/runtime/walletSdkIpc.js +26 -0
- package/dist/runtime/walletSdkProcess.js +358 -0
- package/docs/AGENT_BEHAVIOR.md +524 -0
- package/docs/AGENT_DEVELOPMENT_POLICY.md +815 -0
- package/docs/FRONTEND_POLICY.md +534 -0
- package/docs/LOCAL_DB_ARCHITECTURE.md +381 -0
- package/docs/MCP_SETUP.md +742 -0
- package/docs/MCP_TOOLS.md +1318 -0
- package/docs/SDK_API.md +206 -0
- package/docs/SIGNABLE_ADAPTER_CONTRACT.md +396 -0
- package/docs/TRANSACTION_ACTIVITY_LOG.md +194 -0
- package/docs/UTILITY_INDEX.md +275 -0
- package/docs/WALLET_CONNECTION.md +238 -0
- package/docs/golden-scenarios/BEHAVIOR_MATRIX.md +166 -0
- package/docs/golden-scenarios/INTENT_EVIDENCE_GOLDEN_ANSWERS.md +161 -0
- package/docs/golden-scenarios/INTENT_EVIDENCE_MATRIX.md +259 -0
- package/package.json +79 -0
- package/protocols/deepbook-margin.md +15 -0
- package/protocols/deepbook-v3.md +24 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { walletConnectionSelection } from "./walletConnection.js";
|
|
2
|
+
import { isInitialChainObservation } from "./transactionRequest.js";
|
|
3
|
+
export const workflowActions = ["connect", "disconnect", "use_account", "stop_connection", "prepare_review",
|
|
4
|
+
"request_signature", "cancel", "stop_waiting", "read_result"];
|
|
5
|
+
// Read context may change; an existing review's account binding may not.
|
|
6
|
+
// Both evaluated choices and admission consume this relation with current facts.
|
|
7
|
+
export function reviewPreparationAccount(boundAccount, activeAccount, selectedAccount = activeAccount) {
|
|
8
|
+
if (!activeAccount)
|
|
9
|
+
return { allowed: false, message: "No account from a connected wallet is selected for this review." };
|
|
10
|
+
if (boundAccount && boundAccount !== activeAccount)
|
|
11
|
+
return { allowed: false,
|
|
12
|
+
message: `This review uses ${boundAccount}, but the currently selected account is ${activeAccount}.` };
|
|
13
|
+
if (selectedAccount !== activeAccount)
|
|
14
|
+
return { allowed: false,
|
|
15
|
+
message: "The selected account has changed since this request was made." };
|
|
16
|
+
return { allowed: true, account: activeAccount };
|
|
17
|
+
}
|
|
18
|
+
// Shared domain eligibility. The DB supplies current facts at its decision
|
|
19
|
+
// boundary. Returned choices are advisory; admission still binds concrete input.
|
|
20
|
+
export function workflowEligibility(facts) {
|
|
21
|
+
const { record, session, request, authority, walletAvailability } = facts;
|
|
22
|
+
const at = Date.parse(facts.evaluatedAt);
|
|
23
|
+
const cardRemaining = record?.ownerId === facts.ownerId ? Math.max(0, Date.parse(record.state.expiresAt) - at) : 0;
|
|
24
|
+
// A later-created input card cannot extend its review session. Admitted
|
|
25
|
+
// requests and management cards retain their separate observation authority.
|
|
26
|
+
const remaining = record?.scope === "review" && session && !request
|
|
27
|
+
? Math.min(cardRemaining, Math.max(0, Date.parse(session.expiresAt) - at)) : cardRemaining;
|
|
28
|
+
const inputAvailable = remaining > 0 && record?.state.state === "ready" && record.acceptedInput === undefined;
|
|
29
|
+
const allowedActions = [];
|
|
30
|
+
const selection = walletConnectionSelection(facts.connections, at, facts.activeAccount
|
|
31
|
+
? { address: facts.activeAccount, walletId: facts.activeAccountConnectionId } : undefined);
|
|
32
|
+
const usableConnectionId = walletAvailability.status === "available" && facts.walletDependent !== false
|
|
33
|
+
? selection.connection?.connectionId : undefined;
|
|
34
|
+
const assetReadAccount = facts.walletDependent === false ? undefined
|
|
35
|
+
: usableConnectionId ? selection.assetReadAccount : { status: "address_required" };
|
|
36
|
+
let preparationIssue;
|
|
37
|
+
if (record?.scope === "connect") {
|
|
38
|
+
if (inputAvailable) {
|
|
39
|
+
allowedActions.push("cancel");
|
|
40
|
+
if (walletAvailability.status === "available") {
|
|
41
|
+
if (["connect", "manage"].includes(String(record.state.input.intent)) && selection.pairingAllowed)
|
|
42
|
+
allowedActions.push("connect");
|
|
43
|
+
if (facts.connections.some((item) => item.status === "connected" && !item.pendingAction))
|
|
44
|
+
allowedActions.push("disconnect");
|
|
45
|
+
if (selection.connection)
|
|
46
|
+
allowedActions.push("use_account");
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (remaining > 0 && facts.connection?.status === "awaiting_approval")
|
|
50
|
+
allowedActions.push("stop_connection");
|
|
51
|
+
}
|
|
52
|
+
if (session && inputAvailable && record?.scope === "review" && session.status !== "expired" && Date.parse(session.expiresAt) > at && !session.preparationId) {
|
|
53
|
+
allowedActions.push("cancel");
|
|
54
|
+
if (walletAvailability.status === "available" && selection.connection && !session.plans[0]?.reviewModel) {
|
|
55
|
+
const account = reviewPreparationAccount(session.account, assetReadAccount?.status === "available" ? assetReadAccount.account : undefined);
|
|
56
|
+
if (!account.allowed)
|
|
57
|
+
preparationIssue = account.message;
|
|
58
|
+
else if (!facts.busyForAccount)
|
|
59
|
+
allowedActions.push("prepare_review");
|
|
60
|
+
if (!facts.busyForAccount && session.status === "ready_for_wallet_review" && session.reviewState?.transactionReviewData &&
|
|
61
|
+
(!request || request.reviewRevision !== session.reviewRevision))
|
|
62
|
+
allowedActions.push("request_signature");
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (record && session && remaining > 0 && request && request.reviewSessionId === session.id &&
|
|
66
|
+
authority?.owner_id === facts.ownerId &&
|
|
67
|
+
(record.operationId === request.attemptId || record.scope === "review_manage" && record.state.input.attemptId === request.attemptId)) {
|
|
68
|
+
if (["awaiting_signature", "submitting", "awaiting_chain_result"].includes(request.requestStatus))
|
|
69
|
+
allowedActions.push("stop_waiting");
|
|
70
|
+
if (isInitialChainObservation(request.requestStatus) || request.requestStatus === "outcome_unknown")
|
|
71
|
+
allowedActions.push("read_result");
|
|
72
|
+
}
|
|
73
|
+
const expiry = session && inputAvailable && record?.scope === "review" && !session.preparationId &&
|
|
74
|
+
session.status === "ready_for_wallet_review" && (!request || request.reviewRevision !== session.reviewRevision)
|
|
75
|
+
? session.reviewState?.humanReadableReview?.freshness.expiresAt : undefined;
|
|
76
|
+
const nextRead = expiry && session ? Math.min(remaining, Date.parse(session.expiresAt) - at, Date.parse(expiry) - at) : undefined;
|
|
77
|
+
return { allowedActions, preparationIssue, usableConnectionId, assetReadAccount, connectionConflict: selection.conflict,
|
|
78
|
+
actionRemainingMs: remaining, inputRemainingMs: inputAvailable ? remaining : 0,
|
|
79
|
+
...(nextRead !== undefined && nextRead > 0 ? { nextStateReadAfterMs: nextRead } : {}) };
|
|
80
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { workflowActions } from "./workflowState.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { suiAddressStringSchema, parseSuiAddress } from "../suiAddress.js";
|
|
4
|
+
import { actionPlanSchema, internalSessionStatusSchema, reviewStateOutputSchema } from "../action/schemas.js";
|
|
5
|
+
import { walletConnectionSchema, walletAvailabilitySchema, workflowProgressSchema, WALLET_CONNECTION_POLL_SECONDS, connectionConflictSchema, assetReadAccountSchema } from "./walletConnection.js";
|
|
6
|
+
import { transactionRequestSchema } from "./transactionRequest.js";
|
|
7
|
+
import { workflowProgress } from "./walletConnection.js";
|
|
8
|
+
import { reviewProgress, EXECUTION_POLLING_INTERVAL_SECONDS } from "./status.js";
|
|
9
|
+
import { walletRunIdSchema, walletRecoverySchema, walletObservationSchema } from "./walletRuntime.js";
|
|
10
|
+
export const CONNECT_BOUNDARY = "A wallet connection provides account context; it is not a transaction approval or proof of address ownership.";
|
|
11
|
+
export const REVIEW_BOUNDARY = "Review evidence is not a safety guarantee. Only an explicit card action and wallet approval may authorize this exact transaction.";
|
|
12
|
+
// A projection of an admitted card operation, not a new wallet connection state.
|
|
13
|
+
export const connectionViewSchema = walletConnectionSchema.extend({ pendingAction: z.literal("disconnect").optional() });
|
|
14
|
+
export { assetReadAccountSchema } from "./walletConnection.js";
|
|
15
|
+
export const pendingConnectionStatusSchema = z.enum(["input_required", "awaiting_approval", "disconnect_pending"]);
|
|
16
|
+
const connectAction = z.object({ action: z.literal("connect"), walletRunId: walletRunIdSchema }).strict();
|
|
17
|
+
const disconnectAction = z.object({ action: z.literal("disconnect"), walletRunId: walletRunIdSchema, connectionId: z.string().min(1) }).strict();
|
|
18
|
+
const accountAction = z.object({ action: z.literal("use_account"), walletRunId: walletRunIdSchema, connectionId: z.string().min(1), account: suiAddressStringSchema }).strict();
|
|
19
|
+
const reviewAction = z.object({ action: z.enum(["prepare_review", "request_signature"]), connectionId: z.string().min(1),
|
|
20
|
+
walletRunId: walletRunIdSchema, account: suiAddressStringSchema, reviewRevision: z.number().int().nonnegative() }).strict();
|
|
21
|
+
const simpleAction = z.object({ action: z.enum(["cancel", "stop_connection", "stop_waiting", "read_result"]) }).strict();
|
|
22
|
+
export const workflowActionSchema = z.union([connectAction, disconnectAction, accountAction, reviewAction, simpleAction]);
|
|
23
|
+
export const automaticWorkflowActionSchema = z.union([connectAction, reviewAction.extend({ action: z.literal("prepare_review") })]);
|
|
24
|
+
export function parseWorkflowAction(input) {
|
|
25
|
+
const action = workflowActionSchema.parse(input);
|
|
26
|
+
return "account" in action ? { ...action, account: parseSuiAddress(action.account) } : action;
|
|
27
|
+
}
|
|
28
|
+
export const workflowViewSchema = z.object({
|
|
29
|
+
kind: z.enum(["connect", "review"]),
|
|
30
|
+
mode: z.enum(["connect", "review", "review_manage"]),
|
|
31
|
+
allowedActions: z.array(z.enum(workflowActions)),
|
|
32
|
+
actionRemainingMs: z.number().int().nonnegative(),
|
|
33
|
+
nextStateReadAfterMs: z.number().int().nonnegative().optional(),
|
|
34
|
+
automaticAction: automaticWorkflowActionSchema.optional(),
|
|
35
|
+
observe: z.boolean(),
|
|
36
|
+
walletAvailability: walletAvailabilitySchema,
|
|
37
|
+
walletObservation: walletObservationSchema.optional(),
|
|
38
|
+
runtimeRecovery: walletRecoverySchema.optional(),
|
|
39
|
+
connectionConflict: connectionConflictSchema.optional(),
|
|
40
|
+
usableConnectionId: z.string().min(1).optional(),
|
|
41
|
+
assetReadAccount: assetReadAccountSchema.optional(),
|
|
42
|
+
acceptedWalletRunId: walletRunIdSchema.optional(),
|
|
43
|
+
progress: workflowProgressSchema,
|
|
44
|
+
boundary: z.enum([CONNECT_BOUNDARY, REVIEW_BOUNDARY]),
|
|
45
|
+
connections: z.array(connectionViewSchema),
|
|
46
|
+
activeAccount: z.string().optional(),
|
|
47
|
+
connection: connectionViewSchema.optional(),
|
|
48
|
+
connectionAction: z.enum(["connect", "disconnect"]).optional(),
|
|
49
|
+
review: z.object({
|
|
50
|
+
reviewSessionId: z.string(), plan: actionPlanSchema, reviewRevision: z.number().int().nonnegative(),
|
|
51
|
+
status: internalSessionStatusSchema, account: z.string().optional(), preparing: z.boolean(),
|
|
52
|
+
accountRequestPending: z.literal(true).optional(),
|
|
53
|
+
error: z.string().optional(),
|
|
54
|
+
state: reviewStateOutputSchema.optional()
|
|
55
|
+
}).strict().optional(),
|
|
56
|
+
request: transactionRequestSchema.optional(),
|
|
57
|
+
receipt: z.unknown().optional(),
|
|
58
|
+
netGasMist: z.string().regex(/^-?[0-9]+$/).optional(),
|
|
59
|
+
observationStopped: z.boolean().optional()
|
|
60
|
+
}).strict().superRefine((view, context) => {
|
|
61
|
+
const connection = view.connections.find((item) => item.connectionId === view.usableConnectionId);
|
|
62
|
+
if (view.usableConnectionId && (!connection || connection.status !== "connected" ||
|
|
63
|
+
view.walletAvailability.status !== "available" || view.connectionConflict ||
|
|
64
|
+
view.connections.some((item) => item.pendingAction || item.status === "awaiting_approval"))) {
|
|
65
|
+
context.addIssue({ code: "custom", path: ["usableConnectionId"], message: "The usable connection must match current connection facts." });
|
|
66
|
+
}
|
|
67
|
+
if (view.assetReadAccount?.status === "available" && (!connection ||
|
|
68
|
+
!connection.accounts.includes(view.assetReadAccount.account) || view.activeAccount !== view.assetReadAccount.account)) {
|
|
69
|
+
context.addIssue({ code: "custom", path: ["assetReadAccount"], message: "The default address must belong to the usable connection and stored selection." });
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
// Automatic preparation uses the same current selection and typed admission as
|
|
73
|
+
// a deliberate retry. A failed computation is not a reason to compute in a loop.
|
|
74
|
+
function automaticReviewAction(input) {
|
|
75
|
+
const { record, session, request } = input;
|
|
76
|
+
if (!session || record?.scope !== "review" || request || session.preparationError ||
|
|
77
|
+
!input.allowedActions.includes("prepare_review"))
|
|
78
|
+
return undefined;
|
|
79
|
+
const needsPreparation = !session.reviewState && session.reviewRevision === 0 ||
|
|
80
|
+
session.reviewState?.status === "refresh_required" && session.reviewState.refreshReason === "review_evidence_stale";
|
|
81
|
+
if (!needsPreparation || input.assetReadAccount?.status !== "available")
|
|
82
|
+
return undefined;
|
|
83
|
+
const account = input.assetReadAccount.account;
|
|
84
|
+
const candidates = reviewWalletChoices(input, account);
|
|
85
|
+
if (candidates.length !== 1)
|
|
86
|
+
return undefined;
|
|
87
|
+
return { action: "prepare_review", walletRunId: input.walletAvailability.walletRunId, connectionId: candidates[0].connectionId,
|
|
88
|
+
account, reviewRevision: session.reviewRevision };
|
|
89
|
+
}
|
|
90
|
+
// Method support filters wallet choices; it does not authorize a signature.
|
|
91
|
+
export function reviewWalletChoices(selection, account, requireSigningMethod = false) {
|
|
92
|
+
return selection.connections.filter((item) => item.connectionId === selection.usableConnectionId && item.accounts.includes(account) &&
|
|
93
|
+
(!requireSigningMethod || item.methods.includes("sui_signTransaction")));
|
|
94
|
+
}
|
|
95
|
+
function nextStateRead(input) {
|
|
96
|
+
const walletInput = input.record?.state.state === "ready" && input.record.scope !== "review_manage" && !input.request &&
|
|
97
|
+
!input.session?.plans[0]?.reviewModel && input.inputRemainingMs > 0;
|
|
98
|
+
const startup = walletInput && ["initializing", "recovering"].includes(input.walletAvailability.status);
|
|
99
|
+
const delays = [input.nextStateReadAfterMs, startup ? WALLET_CONNECTION_POLL_SECONDS * 1000 : undefined,
|
|
100
|
+
walletInput && input.record?.scope === "review" && input.busyForAccount ? EXECUTION_POLLING_INTERVAL_SECONDS * 1000 : undefined]
|
|
101
|
+
.filter((value) => value !== undefined && value > 0);
|
|
102
|
+
return delays.length ? Math.min(...delays) : undefined;
|
|
103
|
+
}
|
|
104
|
+
// These projections do not read clocks, storage, or admission rules. The
|
|
105
|
+
// evaluated facts and choices were collected together by the database owner.
|
|
106
|
+
export function projectConnectionView(input) {
|
|
107
|
+
const { connection, walletAvailability } = input;
|
|
108
|
+
const readAfter = nextStateRead(input);
|
|
109
|
+
const progress = input.runtimeRecovery ? { status: "phase" in input.runtimeRecovery ? "waiting" : "idle" } :
|
|
110
|
+
workflowProgress(connection?.status === "awaiting_approval" || connection?.pendingAction === "disconnect", true, walletAvailability);
|
|
111
|
+
return workflowViewSchema.parse({ kind: "connect", mode: "connect", allowedActions: input.allowedActions,
|
|
112
|
+
actionRemainingMs: input.actionRemainingMs, observe: progress.status === "waiting", progress, walletAvailability,
|
|
113
|
+
...(input.record?.state.input.intent === "connect" && input.allowedActions.includes("connect")
|
|
114
|
+
? { automaticAction: { action: "connect", walletRunId: walletAvailability.walletRunId } } : {}),
|
|
115
|
+
runtimeRecovery: input.runtimeRecovery, walletObservation: input.walletObservation,
|
|
116
|
+
connectionConflict: input.connectionConflict, usableConnectionId: input.usableConnectionId, assetReadAccount: input.assetReadAccount,
|
|
117
|
+
connectionAction: ["connect", "disconnect"].includes(String(input.record?.acceptedInput?.action)) ? input.record?.acceptedInput?.action : undefined,
|
|
118
|
+
acceptedWalletRunId: input.record?.acceptedInput?.walletRunId,
|
|
119
|
+
...(readAfter === undefined ? {} : { nextStateReadAfterMs: readAfter }),
|
|
120
|
+
connections: input.connections, activeAccount: input.activeAccount, connection, boundary: CONNECT_BOUNDARY });
|
|
121
|
+
}
|
|
122
|
+
export function projectReviewView(input) {
|
|
123
|
+
const { record, session, request, authority, walletAvailability, boundReview } = input;
|
|
124
|
+
if (!record || !session)
|
|
125
|
+
throw new Error("Review projection requires an evaluated card and session.");
|
|
126
|
+
const progress = reviewProgress(!boundReview && !!session.preparationId, request, walletAvailability, {
|
|
127
|
+
stopped: authority?.observation_stopped === 1, pending: authority?.lookup_pending === 1
|
|
128
|
+
});
|
|
129
|
+
const displayedReview = boundReview ?? session.reviewState;
|
|
130
|
+
const gas = displayedReview?.simulation?.gasCostSummary;
|
|
131
|
+
const automaticAction = automaticReviewAction(input);
|
|
132
|
+
const readAfter = nextStateRead(input);
|
|
133
|
+
return workflowViewSchema.parse({ kind: "review", mode: record.scope === "review_manage" ? "review_manage" : "review",
|
|
134
|
+
allowedActions: input.allowedActions, actionRemainingMs: input.actionRemainingMs, observe: progress.status === "waiting", progress, walletAvailability,
|
|
135
|
+
connections: input.connections, connectionConflict: input.connectionConflict, usableConnectionId: input.usableConnectionId,
|
|
136
|
+
assetReadAccount: input.assetReadAccount, activeAccount: input.activeAccount, boundary: REVIEW_BOUNDARY, receipt: input.receipt,
|
|
137
|
+
walletObservation: input.walletObservation,
|
|
138
|
+
acceptedWalletRunId: record.acceptedInput?.walletRunId,
|
|
139
|
+
...(readAfter === undefined ? {} : { nextStateReadAfterMs: readAfter }),
|
|
140
|
+
...(automaticAction ? { automaticAction } : {}),
|
|
141
|
+
netGasMist: gas ? (BigInt(gas.computationCostRaw) + BigInt(gas.storageCostRaw) - BigInt(gas.storageRebateRaw)).toString() : undefined,
|
|
142
|
+
review: { reviewSessionId: session.id, plan: session.plans[0], reviewRevision: boundReview && request ? request.reviewRevision : session.reviewRevision,
|
|
143
|
+
status: boundReview?.status ?? session.status, account: boundReview?.account ?? session.account,
|
|
144
|
+
...(!request && !session.plans[0]?.reviewModel && input.busyForAccount ? { accountRequestPending: true } : {}),
|
|
145
|
+
preparing: !boundReview && !!session.preparationId,
|
|
146
|
+
error: !boundReview ? [input.preparationIssue && `Current account selection: ${input.preparationIssue}`,
|
|
147
|
+
session.preparationError && `Earlier review message: “${session.preparationError}”`].filter(Boolean).join("\n\n") || undefined : undefined,
|
|
148
|
+
state: displayedReview },
|
|
149
|
+
request, observationStopped: authority ? authority.observation_stopped === 1 : undefined });
|
|
150
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { isValidSuiAddress, isValidTransactionDigest, normalizeSuiAddress } from "@mysten/sui/utils";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
// Use the string-only schema at MCP/HTTP output boundaries because zod transforms
|
|
4
|
+
// cannot be represented in MCP JSON Schema. Use the normalized schema for values
|
|
5
|
+
// before storing them as local session state.
|
|
6
|
+
export const suiAddressStringSchema = z
|
|
7
|
+
.string()
|
|
8
|
+
.refine(isValidSuiAddress, "Expected a 32-byte Sui address");
|
|
9
|
+
// Shared Sui transaction digest schema (pinned SDK source of truth). Used at
|
|
10
|
+
// MCP/HTTP boundaries and by the signable-adapter commitment fields.
|
|
11
|
+
export const suiTransactionDigestSchema = z
|
|
12
|
+
.string()
|
|
13
|
+
.refine(isValidTransactionDigest, "Expected a Sui transaction digest");
|
|
14
|
+
export const normalizedSuiAddressSchema = suiAddressStringSchema.transform((value) => normalizeSuiAddress(value));
|
|
15
|
+
export function parseSuiAddress(value) {
|
|
16
|
+
const parsed = normalizedSuiAddressSchema.safeParse(value);
|
|
17
|
+
return parsed.success ? parsed.data : undefined;
|
|
18
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export class SuiEndpointError extends Error {
|
|
2
|
+
kind;
|
|
3
|
+
details;
|
|
4
|
+
constructor(kind, message, details = {}) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.kind = kind;
|
|
7
|
+
this.details = details;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export const MAX_SUI_GRPC_URL_LENGTH = 512;
|
|
11
|
+
export const MAX_SUI_GRAPHQL_URL_LENGTH = 512;
|
|
12
|
+
export function parseGrpcUrl(value) {
|
|
13
|
+
let parsed;
|
|
14
|
+
if (value.length > MAX_SUI_GRPC_URL_LENGTH) {
|
|
15
|
+
throw new SuiEndpointError("invalid_url", `Sui gRPC URL must be ${MAX_SUI_GRPC_URL_LENGTH} characters or fewer`);
|
|
16
|
+
}
|
|
17
|
+
const authority = getUrlAuthority(value);
|
|
18
|
+
try {
|
|
19
|
+
parsed = new URL(value);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
throw new SuiEndpointError("invalid_url", "Sui gRPC URL must be a valid http(s) URL with an explicit port");
|
|
23
|
+
}
|
|
24
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
25
|
+
throw new SuiEndpointError("invalid_url", "Sui gRPC URL must use http or https");
|
|
26
|
+
}
|
|
27
|
+
if (!authority || !hasExplicitPort(authority)) {
|
|
28
|
+
throw new SuiEndpointError("invalid_url", "Sui gRPC URL must include an explicit port, for example :443 or :9000");
|
|
29
|
+
}
|
|
30
|
+
if (authority.includes("@")) {
|
|
31
|
+
throw new SuiEndpointError("invalid_url", "Sui gRPC URL must not include credentials");
|
|
32
|
+
}
|
|
33
|
+
if (parsed.pathname !== "/" || parsed.search || parsed.hash) {
|
|
34
|
+
throw new SuiEndpointError("invalid_url", "Sui gRPC URL must include only scheme, host, and explicit port");
|
|
35
|
+
}
|
|
36
|
+
return `${parsed.protocol}//${authority}`;
|
|
37
|
+
}
|
|
38
|
+
// GraphQL endpoints commonly include a path such as /graphql and may omit an
|
|
39
|
+
// explicit port. gRPC endpoints stay stricter because the pinned gRPC client
|
|
40
|
+
// runtime expects only scheme, host, and explicit port.
|
|
41
|
+
export function parseGraphqlUrl(value) {
|
|
42
|
+
let parsed;
|
|
43
|
+
if (value.length > MAX_SUI_GRAPHQL_URL_LENGTH) {
|
|
44
|
+
throw new SuiEndpointError("invalid_url", `Sui GraphQL URL must be ${MAX_SUI_GRAPHQL_URL_LENGTH} characters or fewer`);
|
|
45
|
+
}
|
|
46
|
+
const authority = getUrlAuthority(value);
|
|
47
|
+
try {
|
|
48
|
+
parsed = new URL(value);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
throw new SuiEndpointError("invalid_url", "Sui GraphQL URL must be a valid https URL");
|
|
52
|
+
}
|
|
53
|
+
if (parsed.protocol !== "https:") {
|
|
54
|
+
throw new SuiEndpointError("invalid_url", "Sui GraphQL URL must use https");
|
|
55
|
+
}
|
|
56
|
+
if (!authority) {
|
|
57
|
+
throw new SuiEndpointError("invalid_url", "Sui GraphQL URL must include a host");
|
|
58
|
+
}
|
|
59
|
+
if (authority.includes("@")) {
|
|
60
|
+
throw new SuiEndpointError("invalid_url", "Sui GraphQL URL must not include credentials");
|
|
61
|
+
}
|
|
62
|
+
if (parsed.search || parsed.hash) {
|
|
63
|
+
throw new SuiEndpointError("invalid_url", "Sui GraphQL URL must not include a query string or fragment");
|
|
64
|
+
}
|
|
65
|
+
return parsed.toString();
|
|
66
|
+
}
|
|
67
|
+
function getUrlAuthority(value) {
|
|
68
|
+
return value.match(/^[a-z][a-z0-9+.-]*:\/\/([^/?#]+)/i)?.[1];
|
|
69
|
+
}
|
|
70
|
+
function hasExplicitPort(authority) {
|
|
71
|
+
return /\]:\d+$/.test(authority) || /^[^[]*:\d+$/.test(authority);
|
|
72
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const activeAccountResponseSchema = z.discriminatedUnion("status", [
|
|
3
|
+
z.object({
|
|
4
|
+
status: z.literal("set"),
|
|
5
|
+
account: z.string(),
|
|
6
|
+
source: z.literal("wallet_connection"),
|
|
7
|
+
setAt: z.string(),
|
|
8
|
+
boundary: z.literal("read_context_only_not_signing_authorization")
|
|
9
|
+
}),
|
|
10
|
+
z.object({
|
|
11
|
+
status: z.literal("none")
|
|
12
|
+
})
|
|
13
|
+
]);
|
|
14
|
+
export function activeAccountResponse(active) {
|
|
15
|
+
return active
|
|
16
|
+
? {
|
|
17
|
+
status: "set",
|
|
18
|
+
account: active.address,
|
|
19
|
+
source: active.source,
|
|
20
|
+
setAt: active.setAt,
|
|
21
|
+
boundary: "read_context_only_not_signing_authorization"
|
|
22
|
+
}
|
|
23
|
+
: { status: "none" };
|
|
24
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { completable } from "@modelcontextprotocol/sdk/server/completable.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { actionGroups, promptNameFor } from "../adapters/adapterPromptSurfaces.js";
|
|
4
|
+
export const MCP_PROMPTS = [
|
|
5
|
+
{
|
|
6
|
+
name: "inspect-supported-sui-actions",
|
|
7
|
+
title: "Inspect Supported Sui Actions",
|
|
8
|
+
description: "Review current supported mainnet surfaces and tool status.",
|
|
9
|
+
text: [
|
|
10
|
+
"Inspect the Sui MCP server status and supported mainnet Sui protocol surfaces.",
|
|
11
|
+
"Use read.get_server_status first, then read.list_supported_protocols.",
|
|
12
|
+
"Report implemented tools separately from unavailable or blocked surfaces."
|
|
13
|
+
].join("\n")
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: "prepare-reviewable-sui-action",
|
|
17
|
+
title: "Prepare Reviewable Sui Action",
|
|
18
|
+
description: "Prepare a local review session for a supported action proposal.",
|
|
19
|
+
text: [
|
|
20
|
+
"Prepare a reviewable Sui action only through Sui MCP's review-session flow.",
|
|
21
|
+
"Use action.prepare_external_proposal_review when the input is a structured external payment or Sui action proposal.",
|
|
22
|
+
"External proposals get read-only local review and never become signing material. Use action.prepare_sui_action_review for a natural-language swap intent; after a wallet account is connected, account-bound review can reach ready_for_wallet_review, where an explicit Review card action can request approval in the user’s wallet through the backend.",
|
|
23
|
+
"Present the internal Review card and summarize the returned review checks. This MCP response never contains signing data, transaction bytes, or signing readiness; only an explicit UI action and wallet approval authorize the backend request; request status is separate from server-verified chain execution facts."
|
|
24
|
+
].join("\n")
|
|
25
|
+
}
|
|
26
|
+
];
|
|
27
|
+
// Platform-owned boundary language appended to every adapter prompt surface.
|
|
28
|
+
// Adapters contribute copy about their own action only; they cannot weaken
|
|
29
|
+
// or omit these lines.
|
|
30
|
+
const PLATFORM_PROMPT_BOUNDARY_LINES = [
|
|
31
|
+
"Present the internal Review card and summarize the returned review checks.",
|
|
32
|
+
"This MCP response never contains signing data, transaction bytes, or signing readiness; only an explicit UI action and wallet approval authorize the backend request; request status is separate from server-verified chain execution facts."
|
|
33
|
+
];
|
|
34
|
+
function surfacePromptText(surface, intent) {
|
|
35
|
+
return [
|
|
36
|
+
`Prepare a reviewable Sui mainnet ${surface.action} for this intent: "${intent}".`,
|
|
37
|
+
`Parse the source amount, source symbol, and target symbol from the intent (any language) and call ${surface.toolName} with them and intent.protocol set to "${surface.protocolSlug}".`,
|
|
38
|
+
...PLATFORM_PROMPT_BOUNDARY_LINES
|
|
39
|
+
].join("\n");
|
|
40
|
+
}
|
|
41
|
+
function registerSurfacePrompt(server, name, surface, description) {
|
|
42
|
+
server.registerPrompt(name, {
|
|
43
|
+
title: surface.title,
|
|
44
|
+
description,
|
|
45
|
+
argsSchema: {
|
|
46
|
+
intent: z.string().describe(surface.intentArgDescription)
|
|
47
|
+
}
|
|
48
|
+
}, async ({ intent }) => ({
|
|
49
|
+
description,
|
|
50
|
+
messages: [
|
|
51
|
+
{
|
|
52
|
+
role: "user",
|
|
53
|
+
content: {
|
|
54
|
+
type: "text",
|
|
55
|
+
text: surfacePromptText(surface, intent)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
export function registerMcpPrompts(server, surfaces = []) {
|
|
62
|
+
for (const prompt of MCP_PROMPTS) {
|
|
63
|
+
server.registerPrompt(prompt.name, {
|
|
64
|
+
title: prompt.title,
|
|
65
|
+
description: prompt.description
|
|
66
|
+
}, async () => ({
|
|
67
|
+
description: prompt.description,
|
|
68
|
+
messages: [
|
|
69
|
+
{
|
|
70
|
+
role: "user",
|
|
71
|
+
content: {
|
|
72
|
+
type: "text",
|
|
73
|
+
text: prompt.text
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
// Adapter prompt surfaces: action-first names (`swap-deep`) with one
|
|
80
|
+
// free-text intent argument so clients can pass the whole request in one
|
|
81
|
+
// line (Claude Code: /mcp__sui-mcp__swap-deep 10 sui to usdc;
|
|
82
|
+
// Claude Desktop shows a single input field). The model parses the
|
|
83
|
+
// intent; this server never does.
|
|
84
|
+
for (const surface of surfaces) {
|
|
85
|
+
registerSurfacePrompt(server, promptNameFor(surface), surface, surface.description);
|
|
86
|
+
}
|
|
87
|
+
// Bare action prompts are always registered. One protocol: straight to it.
|
|
88
|
+
// Several protocols: an optional `protocol` argument (completion suggests
|
|
89
|
+
// the slugs) and explicit instructions to ask the user - the venue is the
|
|
90
|
+
// user's choice, never the server's.
|
|
91
|
+
for (const [action, group] of actionGroups(surfaces)) {
|
|
92
|
+
registerBareActionPrompt(server, action, group);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
export function bareActionPromptText(action, group, intent, protocol) {
|
|
96
|
+
const chosen = group.length === 1 ? group[0] : group.find((surface) => surface.protocolSlug === (protocol ?? "").trim());
|
|
97
|
+
if (chosen) {
|
|
98
|
+
return surfacePromptText(chosen, intent);
|
|
99
|
+
}
|
|
100
|
+
const options = group
|
|
101
|
+
.map((surface) => `${surface.protocolSlug} (${surface.title}, tool: ${surface.toolName})`)
|
|
102
|
+
.join("; ");
|
|
103
|
+
return [
|
|
104
|
+
`Several protocols support the ${action} action: ${options}.`,
|
|
105
|
+
`The user's intent: "${intent}".`,
|
|
106
|
+
"List these protocol options to the user and ask which one to use. Do not pick a protocol on your own.",
|
|
107
|
+
"Once the user names a protocol, parse the source amount, source symbol, and target symbol from the intent (any language) and call that protocol's tool with them and intent.protocol set to the chosen slug.",
|
|
108
|
+
...PLATFORM_PROMPT_BOUNDARY_LINES
|
|
109
|
+
].join("\n");
|
|
110
|
+
}
|
|
111
|
+
function registerBareActionPrompt(server, action, group) {
|
|
112
|
+
const single = group.length === 1 ? group[0] : undefined;
|
|
113
|
+
const first = group[0];
|
|
114
|
+
if (!first) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const description = single
|
|
118
|
+
? `${single.description} Shorthand for ${promptNameFor(single)}.`
|
|
119
|
+
: `Prepare a reviewable Sui ${action} - several protocols support it (${group
|
|
120
|
+
.map((surface) => surface.protocolSlug)
|
|
121
|
+
.join(", ")}); you pick which.`;
|
|
122
|
+
const slugs = group.map((surface) => surface.protocolSlug);
|
|
123
|
+
server.registerPrompt(action, {
|
|
124
|
+
title: single ? single.title : `${action} (choose protocol)`,
|
|
125
|
+
description,
|
|
126
|
+
argsSchema: {
|
|
127
|
+
intent: completable(z.string().describe(first.intentArgDescription), (value) => value ? [] : group.flatMap((surface) => surface.exampleIntents)),
|
|
128
|
+
...(single
|
|
129
|
+
? {}
|
|
130
|
+
: {
|
|
131
|
+
protocol: completable(z.string().optional().describe(`Protocol slug (optional): ${slugs.join(" | ")}`), (value) => slugs.filter((slug) => slug.startsWith((value ?? "").toLowerCase())))
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
}, async (args) => ({
|
|
135
|
+
description,
|
|
136
|
+
messages: [
|
|
137
|
+
{
|
|
138
|
+
role: "user",
|
|
139
|
+
content: {
|
|
140
|
+
type: "text",
|
|
141
|
+
text: bareActionPromptText(action, group, String(args.intent ?? ""), args.protocol)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
]
|
|
145
|
+
}));
|
|
146
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { assertValidToolName } from "./toolNames.js";
|
|
2
|
+
const DEFAULT_TOOL_ANNOTATIONS = {
|
|
3
|
+
openWorldHint: false
|
|
4
|
+
};
|
|
5
|
+
function mergedAnnotations(defaultAnnotations, annotations) {
|
|
6
|
+
return {
|
|
7
|
+
...DEFAULT_TOOL_ANNOTATIONS,
|
|
8
|
+
...(defaultAnnotations ?? {}),
|
|
9
|
+
...(annotations ?? {})
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export function registerSuiMcpTool(server, name, config, callback) {
|
|
13
|
+
assertValidToolName(name);
|
|
14
|
+
const { defaultAnnotations, annotations, ...sdkConfig } = config;
|
|
15
|
+
return server.registerTool(name, {
|
|
16
|
+
...sdkConfig,
|
|
17
|
+
annotations: mergedAnnotations(defaultAnnotations, annotations)
|
|
18
|
+
}, callback);
|
|
19
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
|
|
5
|
+
export const MCP_RESOURCES = [
|
|
6
|
+
{
|
|
7
|
+
name: "readme",
|
|
8
|
+
uri: "suimcp://docs/readme",
|
|
9
|
+
title: "README",
|
|
10
|
+
description: "Public entry document: product purpose, current release boundary, setup path, and documentation map.",
|
|
11
|
+
path: "README.md"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
name: "mcp-setup",
|
|
15
|
+
uri: "suimcp://docs/mcp-setup",
|
|
16
|
+
title: "MCP Setup",
|
|
17
|
+
description: "Setup guide: installation, MCP client connection, first-use flow, settings, and troubleshooting.",
|
|
18
|
+
path: "docs/MCP_SETUP.md"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: "mcp-tools",
|
|
22
|
+
uri: "suimcp://docs/mcp-tools",
|
|
23
|
+
title: "MCP Tools",
|
|
24
|
+
description: "API reference: tool contracts, response fields, statuses, follow-up fields, and output boundaries.",
|
|
25
|
+
path: "docs/MCP_TOOLS.md"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: "wallet-connection",
|
|
29
|
+
uri: "suimcp://docs/wallet-connection",
|
|
30
|
+
title: "Wallet Connection",
|
|
31
|
+
description: "Wallet connection reference: active read context, private SDK ownership and user-approved transaction requests.",
|
|
32
|
+
path: "docs/WALLET_CONNECTION.md"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: "agent-behavior",
|
|
36
|
+
uri: "suimcp://docs/agent-behavior",
|
|
37
|
+
title: "Agent Behavior Reference",
|
|
38
|
+
description: "Answer playbook: user-question flows, tool selection, and response wording boundaries.",
|
|
39
|
+
path: "docs/AGENT_BEHAVIOR.md"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: "deepbook-v3",
|
|
43
|
+
uri: "suimcp://protocols/deepbook-v3",
|
|
44
|
+
title: "DeepBookV3",
|
|
45
|
+
description: "Protocol reference only; use MCP tool responses and read.list_supported_protocols for current support.",
|
|
46
|
+
path: "protocols/deepbook-v3.md"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: "deepbook-margin",
|
|
50
|
+
uri: "suimcp://protocols/deepbook-margin",
|
|
51
|
+
title: "DeepBook Margin",
|
|
52
|
+
description: "Protocol reference only; no margin MCP read tools or signable actions are exposed in this release.",
|
|
53
|
+
path: "protocols/deepbook-margin.md"
|
|
54
|
+
}
|
|
55
|
+
];
|
|
56
|
+
export function registerMcpResources(server) {
|
|
57
|
+
for (const resource of MCP_RESOURCES) {
|
|
58
|
+
server.registerResource(resource.name, resource.uri, {
|
|
59
|
+
title: resource.title,
|
|
60
|
+
description: resource.description,
|
|
61
|
+
mimeType: "text/markdown"
|
|
62
|
+
}, async () => ({
|
|
63
|
+
contents: [
|
|
64
|
+
{
|
|
65
|
+
uri: resource.uri,
|
|
66
|
+
mimeType: "text/markdown",
|
|
67
|
+
text: await readFile(resolve(packageRoot, resource.path), "utf8")
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
}
|