@zktx.io/sui-mcp 0.5.0 → 0.6.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.
- package/README.md +21 -21
- package/dist/adapters/adapterPromptSurfaces.js +0 -20
- package/dist/adapters/deepbook/deepbookQuotePolicy.js +0 -4
- package/dist/adapters/deepbook/deepbookReviewEvidence.js +0 -9
- package/dist/adapters/deepbook/deepbookTransactionMaterialProducer.js +0 -9
- package/dist/adapters/intentPlanFactories.js +0 -6
- package/dist/core/action/contractNameRegistry.js +0 -53
- package/dist/core/action/forbiddenFields.js +0 -4
- package/dist/core/action/ptbVisualizationProducer.js +0 -3
- package/dist/core/action/receiptPtbGraph.js +0 -17
- package/dist/core/action/reviewTimeSimulationEvidence.js +0 -4
- package/dist/core/action/signableAdapterContract.js +0 -5
- package/dist/core/action/suiChainReceiptMapping.js +0 -18
- package/dist/core/action/suiChainReceiptReader.js +1 -21
- package/dist/core/action/suiChainReceiptVerifier.js +0 -10
- package/dist/core/action/transactionFunding.js +0 -7
- package/dist/core/action/types.js +0 -1
- package/dist/core/action/walletReviewContractAssembler.js +0 -2
- package/dist/core/activity/localDataService.js +65 -52
- package/dist/core/activity/localDataValidation.js +1 -3
- package/dist/core/activity/sqliteActivityStore.js +24 -24
- package/dist/core/activity/sqliteActivityStoreRows.js +0 -2
- package/dist/core/activity/sqliteActivityStoreSchema.js +81 -14
- package/dist/core/preferences/sqlitePreferencesRepository.js +5 -3
- package/dist/core/read/deepbookReadHelpers.js +0 -2
- package/dist/core/read/deepbookRegistry.js +0 -19
- package/dist/core/read/deepbookUsdcChartService.js +0 -2
- package/dist/core/read/readCardResult.js +0 -2
- package/dist/core/read/readService.js +0 -19
- package/dist/core/read/readServiceTypes.js +0 -2
- package/dist/core/session/cardSession.js +1 -5
- package/dist/core/session/cardSessionStore.js +54 -7
- package/dist/core/session/reviewValidity.js +0 -3
- package/dist/core/session/sessionRecordStore.js +0 -1
- package/dist/core/session/sessionStore.js +0 -32
- package/dist/core/session/settingsFileIdentity.js +9 -0
- package/dist/core/session/settingsFileProtocol.js +20 -0
- package/dist/core/session/settingsFileStore.js +337 -0
- package/dist/core/session/settingsInputError.js +32 -0
- package/dist/core/session/settingsOperation.js +220 -0
- package/dist/core/session/settingsPreparationSql.js +13 -0
- package/dist/core/session/settingsWorkflow.js +1183 -0
- package/dist/core/session/sqliteCardStore.js +0 -2
- package/dist/core/session/sqliteSessionStore.js +0 -51
- package/dist/core/session/sqliteSettingsWorkflowStore.js +453 -0
- package/dist/core/session/sqliteTransactionMaterialStore.js +0 -11
- package/dist/core/session/sqliteWalletWorkflowStore.js +160 -66
- package/dist/core/session/status.js +2 -4
- package/dist/core/session/transactionMaterialStore.js +0 -4
- package/dist/core/session/transactionRequest.js +0 -4
- package/dist/core/session/walletConnection.js +26 -10
- package/dist/core/session/walletWorkflow.js +70 -30
- package/dist/core/session/workflowState.js +14 -14
- package/dist/core/session/workflowView.js +18 -8
- package/dist/core/suiAddress.js +0 -5
- package/dist/core/suiEndpoint.js +0 -3
- package/dist/mcp/prompts.js +0 -12
- package/dist/mcp/responseGuidance.js +10 -5
- package/dist/mcp/schemas.js +0 -3
- package/dist/mcp/serverInfo.js +3 -4
- package/dist/mcp/toolNames.js +2 -2
- package/dist/mcp/tools/account/index.js +1 -19
- package/dist/mcp/tools/action/prepareSuiActionReview.js +0 -6
- package/dist/mcp/tools/read/readToolHelpers.js +5 -4
- package/dist/mcp/tools/read/serverStatusTools.js +0 -1
- package/dist/mcp/tools/session/executionResultTools.js +2 -2
- package/dist/mcp/tools/session/shared.js +1 -2
- package/dist/mcp/tools/session/statusTools.js +4 -5
- package/dist/mcp/tools/settings/index.js +8 -28
- package/dist/mcp-app/account.html +14 -14
- package/dist/mcp-app/account.notices.txt +1 -1
- package/dist/mcp-app/chart.html +9 -9
- package/dist/mcp-app/chart.notices.txt +1 -1
- package/dist/mcp-app/connect.html +14 -14
- package/dist/mcp-app/connect.notices.txt +1 -1
- package/dist/mcp-app/receipt.html +1298 -1463
- package/dist/mcp-app/receipt.notices.txt +226 -1204
- package/dist/mcp-app/review.html +1298 -1463
- package/dist/mcp-app/review.notices.txt +226 -1204
- package/dist/mcp-app/settings.html +3133 -0
- package/dist/mcp-app/settings.notices.txt +973 -0
- package/dist/mcp-ui/contracts.js +8 -2
- package/dist/mcp-ui/readCards.js +1 -1
- package/dist/mcp-ui/tools.js +34 -4
- package/dist/review-server/server.js +3 -51
- package/dist/runtime/application.js +41 -14
- package/dist/runtime/config.js +0 -2
- package/dist/runtime/localSettingsService.js +23 -2
- package/dist/runtime/logger.js +0 -2
- package/dist/runtime/reviewServerAcquire.js +1 -18
- package/dist/runtime/runtimeTermination.js +1 -4
- package/dist/runtime/shared/authenticatedFetch.js +2 -4
- package/dist/runtime/shared/control.js +4 -7
- package/dist/runtime/shared/dataAccess.js +0 -2
- package/dist/runtime/shared/http.js +14 -0
- package/dist/runtime/shared/mcpHttp.js +6 -6
- package/dist/runtime/shared/ownerLease.js +0 -4
- package/dist/runtime/shared/server.js +1 -2
- package/dist/runtime/shared/stdio.js +2 -5
- package/dist/runtime/smokeMainnetRead.js +0 -3
- package/dist/runtime/smokeReview.js +0 -4
- package/dist/runtime/start.js +0 -2
- package/dist/runtime/suiTransactionGraphqlMapping.js +0 -2
- package/dist/runtime/suiTransactionGraphqlQueries.js +0 -3
- package/dist/runtime/walletConnectConfig.js +0 -2
- package/dist/runtime/walletConnectStorage.js +2 -10
- package/dist/runtime/walletConnectTransport.js +0 -11
- package/dist/runtime/walletSdkChildRuntime.js +2 -6
- package/dist/runtime/walletSdkIpc.js +2 -1
- package/dist/runtime/walletSdkProcess.js +3 -8
- package/docs/AGENT_BEHAVIOR.md +45 -15
- package/docs/LOCAL_DB_ARCHITECTURE.md +145 -22
- package/docs/MCP_SETUP.md +99 -39
- package/docs/MCP_TOOLS.md +288 -24
- package/docs/SDK_API.md +63 -10
- package/docs/UTILITY_INDEX.md +9 -6
- package/docs/WALLET_CONNECTION.md +32 -10
- package/package.json +22 -8
- package/dist/core/activity/schemaVersion.js +0 -2
- package/dist/core/session/keyedRecordStore.js +0 -18
- package/dist/core/session/settingsSession.js +0 -1
- package/dist/core/session/settingsSessions.js +0 -45
- package/dist/review-app/brand-dark.svg +0 -1
- package/dist/review-app/brand-light.svg +0 -1
- package/dist/review-app/favicon.svg +0 -1
- package/dist/review-app/settings.css +0 -1
- package/dist/review-app/settings.js +0 -1
- package/dist/review-app/ui.css +0 -809
- package/dist/review-server/assets.js +0 -48
- package/dist/review-server/html.js +0 -45
- package/dist/review-server/http.js +0 -47
- package/dist/review-server/middleware/reviewToken.js +0 -7
- package/dist/review-server/settingsApi.js +0 -158
- package/docs/AGENT_DEVELOPMENT_POLICY.md +0 -815
- package/docs/FRONTEND_POLICY.md +0 -534
- package/docs/golden-scenarios/BEHAVIOR_MATRIX.md +0 -166
- package/docs/golden-scenarios/INTENT_EVIDENCE_GOLDEN_ANSWERS.md +0 -161
- package/docs/golden-scenarios/INTENT_EVIDENCE_MATRIX.md +0 -259
package/dist/mcp-ui/contracts.js
CHANGED
|
@@ -7,11 +7,13 @@ export const CARD_RESOURCE_URIS = {
|
|
|
7
7
|
receipt: "ui://sui-mcp/receipt.html",
|
|
8
8
|
chart: "ui://sui-mcp/chart.html",
|
|
9
9
|
connect: "ui://sui-mcp/connect.html",
|
|
10
|
-
review: "ui://sui-mcp/review.html"
|
|
10
|
+
review: "ui://sui-mcp/review.html",
|
|
11
|
+
settings: "ui://sui-mcp/settings.html"
|
|
11
12
|
};
|
|
12
13
|
export const CARD_METADATA_KEY = "sui-mcp/card";
|
|
13
14
|
export const CARD_DISPLAY_METADATA_KEY = "sui-mcp/receipt-display";
|
|
14
15
|
export const WALLET_DISPLAY_METADATA_KEY = "sui-mcp/wallet-display";
|
|
16
|
+
export const SETTINGS_FILE_METADATA_KEY = "sui-mcp/settings-file";
|
|
15
17
|
export const CARD_RESOURCE_PREFIX = "suimcp://cards/";
|
|
16
18
|
export const cardInputRequiredSchema = z.discriminatedUnion("kind", [
|
|
17
19
|
z.object({ kind: z.literal("account"), status: z.literal("input_required"), field: z.literal("account"), message: z.string().min(1) }).strict(),
|
|
@@ -25,7 +27,11 @@ export const cardReceiptDisplaySchema = receiptDisplaySchema.extend({
|
|
|
25
27
|
cardId: z.string().min(1), revision: z.number().int().nonnegative(), attemptId: z.string().min(1).optional()
|
|
26
28
|
});
|
|
27
29
|
export const cardWalletDisplaySchema = walletDisplaySchema.extend({ cardId: z.string().min(1), revision: z.number().int().nonnegative() });
|
|
30
|
+
export const settingsFileDisplaySchema = z.object({ attemptId: z.string().uuid().optional(), cardId: z.string().min(1), operationId: z.string().uuid(), fileId: z.string().uuid(),
|
|
31
|
+
offset: z.number().int().nonnegative().safe(), totalSize: z.number().int().nonnegative().safe(), sha256: z.string().regex(/^[a-f0-9]{64}$/),
|
|
32
|
+
chunkBase64: z.string() }).strict();
|
|
28
33
|
export const CARD_TOOLS = {
|
|
29
34
|
account: TOOL_NAMES.uiOpenAccount, receipt: TOOL_NAMES.uiOpenReceipt, chart: TOOL_NAMES.uiOpenChart,
|
|
30
|
-
read: TOOL_NAMES.uiReadCard, submit: TOOL_NAMES.uiSubmitCard, act: TOOL_NAMES.uiActCard
|
|
35
|
+
read: TOOL_NAMES.uiReadCard, submit: TOOL_NAMES.uiSubmitCard, act: TOOL_NAMES.uiActCard,
|
|
36
|
+
readSettingsFile: TOOL_NAMES.uiReadSettingsFile
|
|
31
37
|
};
|
package/dist/mcp-ui/readCards.js
CHANGED
|
@@ -2,7 +2,7 @@ import { CardStore } from "../core/session/cardSessionStore.js";
|
|
|
2
2
|
import { accountInputSchema, receiptInputSchema, chartInputSchema } from "../core/read/readCardInputs.js";
|
|
3
3
|
export function createReadCardStore(options) {
|
|
4
4
|
return new CardStore({
|
|
5
|
-
records: options.records, ownerId: options.ownerId, assertCurrent: options.assertCurrent, now: options.now, logger: options.logger, workflow: options.workflow,
|
|
5
|
+
records: options.records, ownerId: options.ownerId, assertCurrent: options.assertCurrent, now: options.now, logger: options.logger, workflow: options.workflow, settings: options.settings,
|
|
6
6
|
prepare: async (kind) => {
|
|
7
7
|
if (kind !== "chart")
|
|
8
8
|
return { status: "ready" };
|
package/dist/mcp-ui/tools.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { workflowActionSchema } from "../core/session/workflowView.js";
|
|
2
|
+
import { settingsActionSchema } from "../core/session/settingsOperation.js";
|
|
3
|
+
import { MAX_JSON_BODY_BYTES, settingsJsonSize, settingsRawChunkBudget } from "../core/session/settingsFileProtocol.js";
|
|
2
4
|
import { sessionDomainToolError } from "../mcp/toolErrors.js";
|
|
3
5
|
import { accountInputSchema, receiptInputSchema, chartOpenInputSchema } from "../core/read/readCardInputs.js";
|
|
4
6
|
import { readFile } from "node:fs/promises";
|
|
@@ -8,8 +10,8 @@ import { z } from "zod";
|
|
|
8
10
|
import { okToolResult, errorToolResult } from "../mcp/result.js";
|
|
9
11
|
import { CardError } from "../core/session/cardSessionStore.js";
|
|
10
12
|
import { resolveExplicitOrActiveAccount } from "../mcp/tools/read/readToolHelpers.js";
|
|
11
|
-
import { CARD_METADATA_KEY, CARD_DISPLAY_METADATA_KEY, WALLET_DISPLAY_METADATA_KEY, CARD_RESOURCE_PREFIX, CARD_RESOURCE_URIS, CARD_TOOLS, cardReceiptDisplaySchema, cardWalletDisplaySchema, cardReferenceSchema, cardSubmissionSchema, cardInputRequiredSchema } from "./contracts.js";
|
|
12
|
-
const cardActionSchema = cardReferenceSchema.extend({ revision: z.number().int().nonnegative(), input: workflowActionSchema }).strict();
|
|
13
|
+
import { CARD_METADATA_KEY, CARD_DISPLAY_METADATA_KEY, WALLET_DISPLAY_METADATA_KEY, SETTINGS_FILE_METADATA_KEY, CARD_RESOURCE_PREFIX, CARD_RESOURCE_URIS, CARD_TOOLS, cardReceiptDisplaySchema, cardWalletDisplaySchema, cardReferenceSchema, cardSubmissionSchema, cardInputRequiredSchema } from "./contracts.js";
|
|
14
|
+
const cardActionSchema = cardReferenceSchema.extend({ revision: z.number().int().nonnegative().safe(), input: z.union([workflowActionSchema, settingsActionSchema]) }).strict();
|
|
13
15
|
export const cardMetadata = (kind) => ({
|
|
14
16
|
ui: { resourceUri: CARD_RESOURCE_URIS[kind], visibility: ["model"] },
|
|
15
17
|
"openai/outputTemplate": CARD_RESOURCE_URIS[kind]
|
|
@@ -19,8 +21,9 @@ export function supportsCards(server) {
|
|
|
19
21
|
const capabilities = server.server.getClientCapabilities();
|
|
20
22
|
const { extensions, ...standard } = capabilities ?? {};
|
|
21
23
|
const current = capabilities === undefined ? undefined : { ...standard, ...(extensions === undefined ? {} : { extensions }) };
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
const ui = getUiCapability(current);
|
|
25
|
+
return ui !== null && typeof ui === "object" && !Array.isArray(ui) && Array.isArray(ui.mimeTypes) &&
|
|
26
|
+
ui.mimeTypes.every((mimeType) => typeof mimeType === "string") && ui.mimeTypes.includes(RESOURCE_MIME_TYPE);
|
|
24
27
|
}
|
|
25
28
|
export function cardToolResult(response, savedLink = false) {
|
|
26
29
|
const snapshot = response.snapshot;
|
|
@@ -131,4 +134,31 @@ export function registerReadCards(server, deps) {
|
|
|
131
134
|
}
|
|
132
135
|
});
|
|
133
136
|
}
|
|
137
|
+
const fileInput = cardReferenceSchema.extend({ attemptId: z.string().uuid().optional(), operationId: z.string().uuid(), fileId: z.string().uuid(), offset: z.number().int().nonnegative().safe(),
|
|
138
|
+
limit: z.number().int().positive().safe().optional() }).strict();
|
|
139
|
+
registerAppTool(server, CARD_TOOLS.readSettingsFile, { description: "Read a verified backup file for its current download attempt.", inputSchema: fileInput,
|
|
140
|
+
_meta: { ui: { visibility: ["app"] } }, annotations: { readOnlyHint: true, openWorldHint: false }
|
|
141
|
+
}, async (input, extra) => {
|
|
142
|
+
try {
|
|
143
|
+
if (!deps.cards)
|
|
144
|
+
throw new CardError("Card service is unavailable.");
|
|
145
|
+
const base = okToolResult({ status: "read", fileId: input.fileId, offset: input.offset, rangeSize: Number.MAX_SAFE_INTEGER });
|
|
146
|
+
const metadata = { ...(input.attemptId ? { attemptId: input.attemptId } : {}), cardId: input.cardId, operationId: input.operationId, fileId: input.fileId, offset: input.offset, totalSize: Number.MAX_SAFE_INTEGER,
|
|
147
|
+
sha256: "0".repeat(64), chunkBase64: "" };
|
|
148
|
+
base._meta = { [SETTINGS_FILE_METADATA_KEY]: metadata };
|
|
149
|
+
const budget = settingsRawChunkBudget({ jsonrpc: "2.0", id: extra.requestId, result: base });
|
|
150
|
+
if (!budget)
|
|
151
|
+
throw new CardError("The backup response cannot fit the app's message envelope.");
|
|
152
|
+
const read = await deps.cards.store.readSettingsFile(input, Math.min(input.limit ?? budget, budget));
|
|
153
|
+
const rangeSize = Buffer.from(read.chunkBase64, "base64").length;
|
|
154
|
+
const result = okToolResult({ status: "read", fileId: input.fileId, offset: input.offset, rangeSize });
|
|
155
|
+
result._meta = { [SETTINGS_FILE_METADATA_KEY]: { ...metadata, totalSize: read.file.totalSize, sha256: read.file.sha256, chunkBase64: read.chunkBase64 } };
|
|
156
|
+
if (settingsJsonSize({ jsonrpc: "2.0", id: extra.requestId, result }) > MAX_JSON_BODY_BYTES)
|
|
157
|
+
throw new CardError("The backup response exceeds its message limit.");
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return errorToolResult({ kind: "input_invalid", details: { reason: "This backup file could not be read or verified." } });
|
|
162
|
+
}
|
|
163
|
+
});
|
|
134
164
|
}
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
import { createServer } from "node:http";
|
|
2
1
|
import { validateHostOrigin } from "./middleware/hostOrigin.js";
|
|
3
|
-
import {
|
|
4
|
-
import { settingsHtml } from "./html.js";
|
|
5
|
-
import { HttpError, sendHtml, sendJson } from "./http.js";
|
|
2
|
+
import { sendJson } from "../runtime/shared/http.js";
|
|
6
3
|
import { ALLOWED_HOSTNAMES } from "./reviewServerPolicy.js";
|
|
7
|
-
import { routeSettingsApi } from "./settingsApi.js";
|
|
8
4
|
export function createReviewRequestHandler(options) {
|
|
9
5
|
return async (request, response) => {
|
|
10
6
|
try {
|
|
@@ -18,55 +14,11 @@ export function createReviewRequestHandler(options) {
|
|
|
18
14
|
sendJson(response, 400, { error: "token_query_not_supported" });
|
|
19
15
|
return;
|
|
20
16
|
}
|
|
21
|
-
const settings = /^\/settings\/([^/]+)$/.exec(url.pathname);
|
|
22
|
-
if (request.method === "GET" && settings?.[1]) {
|
|
23
|
-
sendHtml(response, settingsHtml(settings[1]), { "content-security-policy": [
|
|
24
|
-
"default-src 'none'", "base-uri 'none'", "connect-src 'self'", "script-src 'self'",
|
|
25
|
-
"style-src 'self'", "img-src 'self' data:", "form-action 'none'"
|
|
26
|
-
].join("; ") });
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
const asset = /^\/review-assets\/(.+)$/.exec(url.pathname);
|
|
30
|
-
if (request.method === "GET" && asset?.[1]) {
|
|
31
|
-
await serveReviewAsset(response, options.reviewAssetsDir ?? defaultReviewAssetsDir(), asset[1]);
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
const route = /^\/api\/settings\/([^/]+)(?:\/(.*))?$/.exec(url.pathname);
|
|
35
|
-
if (route?.[1]) {
|
|
36
|
-
const routes = {
|
|
37
|
-
"": "status", "clear-active-account": "clearActiveAccount", "sui-grpc-url": "setSuiGrpcUrl",
|
|
38
|
-
"sui-grpc-url/restore-default": "restoreDefaultSuiGrpcUrl", "sui-graphql-url": "setSuiGraphqlUrl",
|
|
39
|
-
"sui-graphql-url/restore-default": "restoreDefaultSuiGraphqlUrl", "local-data/export": "exportLocalData",
|
|
40
|
-
"local-data/import/preview": "previewImport", "local-data/import": "importLocalData", "local-data/reset": "resetLocalData"
|
|
41
|
-
};
|
|
42
|
-
const match = routes[route[2] ?? ""];
|
|
43
|
-
if (match) {
|
|
44
|
-
await routeSettingsApi(request, response, options, url, { [match]: route[1] });
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
17
|
sendJson(response, 404, { error: "not_found" });
|
|
49
18
|
}
|
|
50
|
-
catch
|
|
51
|
-
|
|
52
|
-
sendJson(response, error.status, { error: error.code });
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
options.logger.error("settings request failed", { stage: "settings_http" });
|
|
19
|
+
catch {
|
|
20
|
+
options.logger.error("HTTP routing failed", { stage: "http_routing" });
|
|
56
21
|
sendJson(response, 500, { error: "internal_error" });
|
|
57
22
|
}
|
|
58
23
|
};
|
|
59
24
|
}
|
|
60
|
-
export function createReviewHttpServer(options) {
|
|
61
|
-
const handler = createReviewRequestHandler(options);
|
|
62
|
-
const server = createServer((request, response) => { void handler(request, response); });
|
|
63
|
-
return {
|
|
64
|
-
start(port) {
|
|
65
|
-
return new Promise((resolve, reject) => {
|
|
66
|
-
server.once("error", reject);
|
|
67
|
-
server.listen(port, options.host, () => resolve({ host: options.host, port: server.address().port,
|
|
68
|
-
close: () => new Promise((done, failed) => { server.close((error) => error ? failed(error) : done()); }) }));
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
}
|
|
@@ -5,6 +5,8 @@ import { dirname } from "node:path";
|
|
|
5
5
|
import { WalletSdkProcess } from "./walletSdkProcess.js";
|
|
6
6
|
import { WALLETCONNECT_PROJECT_ID } from "./walletConnectConfig.js";
|
|
7
7
|
import { WalletWorkflow } from "../core/session/walletWorkflow.js";
|
|
8
|
+
import { SettingsWorkflow } from "../core/session/settingsWorkflow.js";
|
|
9
|
+
import { SettingsFileStore } from "../core/session/settingsFileStore.js";
|
|
8
10
|
import { randomUUID } from "node:crypto";
|
|
9
11
|
import { SqliteActivityStore } from "../core/activity/sqliteActivityStore.js";
|
|
10
12
|
import { validateSupportedAdapterLifecycle } from "../adapters/adapterLifecycleValidators.js";
|
|
@@ -33,6 +35,7 @@ export async function createRuntimeApplication(bootConfig, logger, ownerId = ran
|
|
|
33
35
|
let activityStore;
|
|
34
36
|
let cards;
|
|
35
37
|
let workflow;
|
|
38
|
+
let settings;
|
|
36
39
|
let chart;
|
|
37
40
|
try {
|
|
38
41
|
const store = new SqliteActivityStore({
|
|
@@ -68,10 +71,10 @@ export async function createRuntimeApplication(bootConfig, logger, ownerId = ran
|
|
|
68
71
|
bootSuiGrpcUrl: config.suiGrpcUrl,
|
|
69
72
|
bootSuiGraphqlUrl: config.suiGraphqlUrl
|
|
70
73
|
});
|
|
71
|
-
const workflowRecords = store.createWalletWorkflowStore(ownerId);
|
|
74
|
+
const workflowRecords = store.createWalletWorkflowStore(ownerId, () => new Date(), () => walletRuntime?.snapshot());
|
|
72
75
|
const localData = store.createLocalDataService({
|
|
73
76
|
advanceRequestDeadlines: (now) => workflowRecords.advanceRequestDeadlines(now),
|
|
74
|
-
onDataReplaced: () => { access.dataReplaced(); workflow?.dataReplaced(); chart?.clearCache(); },
|
|
77
|
+
onDataReplaced: () => { access.dataReplaced(); workflow?.dataReplaced(); settings?.dataReplaced(); chart?.clearCache(); },
|
|
75
78
|
suiGrpcUrl: DEFAULT_SUI_GRPC_URL,
|
|
76
79
|
suiGraphqlUrl: DEFAULT_SUI_GRAPHQL_URL,
|
|
77
80
|
verifySuiGrpcUrl: async (url) => {
|
|
@@ -95,8 +98,7 @@ export async function createRuntimeApplication(bootConfig, logger, ownerId = ran
|
|
|
95
98
|
validateAdapterLifecycle: validateSupportedAdapterLifecycle,
|
|
96
99
|
sessions: store.createSessionRecordStore(),
|
|
97
100
|
artifacts: store.createPrivateReviewArtifactStore(),
|
|
98
|
-
ownerId
|
|
99
|
-
settingsStore: store.createSettingsRecordStore()
|
|
101
|
+
ownerId
|
|
100
102
|
});
|
|
101
103
|
const readService = createSuiReadService({
|
|
102
104
|
client: suiClient,
|
|
@@ -118,9 +120,7 @@ export async function createRuntimeApplication(bootConfig, logger, ownerId = ran
|
|
|
118
120
|
}, input);
|
|
119
121
|
const reviewComputationDeps = createRuntimeReviewDependencies({ client: suiClient, chainIdentifier,
|
|
120
122
|
expectedChainIdentifier: config.expectedChainIdentifier, materialStore: transactionMaterialStore, readService });
|
|
121
|
-
const httpHandler = createReviewRequestHandler({
|
|
122
|
-
activityStore: store, localSettings, localData,
|
|
123
|
-
serverInfo: { name: SERVER_NAME, version: SERVER_VERSION, network: SERVER_NETWORK } });
|
|
123
|
+
const httpHandler = createReviewRequestHandler({ logger });
|
|
124
124
|
const cardRecords = store.createCardRecordStore();
|
|
125
125
|
let metadata;
|
|
126
126
|
try {
|
|
@@ -141,9 +141,26 @@ export async function createRuntimeApplication(bootConfig, logger, ownerId = ran
|
|
|
141
141
|
},
|
|
142
142
|
submitTransaction: (transaction, signature) => suiClient.core.executeTransaction({ transaction, signatures: [signature] })
|
|
143
143
|
});
|
|
144
|
+
const settingsRecords = store.createSettingsWorkflowStore(ownerId);
|
|
145
|
+
settings = new SettingsWorkflow({ records: settingsRecords, files: new SettingsFileStore(dirname(bootConfig.activityDatabasePath)),
|
|
146
|
+
activity: store, localData, preferences: preferencesRepository, wallet: workflow, assertCurrent: access.assertCurrent, runMaintenance: (work) => access.run(work),
|
|
147
|
+
resolveEndpoint: (input) => localSettings.resolveEndpointChange(input),
|
|
148
|
+
verifyEndpoint: (input) => localSettings.prepareEndpointChange(input),
|
|
149
|
+
interruptRun: (runId) => workflowRecords.invalidateWalletRun("wallet_service_restarted", new Date(), runId),
|
|
150
|
+
readFacts: async () => settingsRecords.transaction(() => {
|
|
151
|
+
const context = workflow.currentConnectionContext(), account = store.getActiveAccountSync();
|
|
152
|
+
const observedAt = context.walletObservation?.observedAt;
|
|
153
|
+
return { server: { name: SERVER_NAME, version: SERVER_VERSION, network: SERVER_NETWORK },
|
|
154
|
+
localSettings: localSettings.getLocalSettingsSync((key) => preferencesRepository.getLocalSettingSync(key)),
|
|
155
|
+
...(account ? { readAddress: account.address } : {}),
|
|
156
|
+
assetReadAccount: context.assetReadAccount, walletAvailability: context.walletAvailability,
|
|
157
|
+
...(context.connectionReconciliationRequired ? { connectionReconciliationRequired: true } : {}),
|
|
158
|
+
...(observedAt ? { observedAt } : {}), counts: localData.getDataCountsSync() };
|
|
159
|
+
}) });
|
|
160
|
+
settings.recoverOwner();
|
|
144
161
|
await workflow.start();
|
|
145
162
|
chart = createDeepbookUsdcChartService({ assertCurrent: access.assertCurrent });
|
|
146
|
-
cards = createReadCardStore({ readService, publicChainReceiptReader, chart, records: cardRecords, ownerId, assertCurrent: access.assertCurrent, logger, workflow });
|
|
163
|
+
cards = createReadCardStore({ readService, publicChainReceiptReader, chart, records: cardRecords, ownerId, assertCurrent: access.assertCurrent, logger, workflow, settings });
|
|
147
164
|
const transactionActivityService = new TransactionActivityService({
|
|
148
165
|
activityStore: store,
|
|
149
166
|
source: new GraphqlSuiTransactionActivitySource({
|
|
@@ -157,31 +174,41 @@ export async function createRuntimeApplication(bootConfig, logger, ownerId = ran
|
|
|
157
174
|
promptSurfaces: ADAPTER_PROMPT_SURFACES,
|
|
158
175
|
sessions,
|
|
159
176
|
activityStore: store,
|
|
160
|
-
reviewBaseUrl: `http://${config.reviewHost}:${config.reviewPort}`,
|
|
161
177
|
readService,
|
|
162
178
|
transactionActivityService,
|
|
163
179
|
localSettings,
|
|
164
180
|
logger
|
|
165
181
|
}));
|
|
166
182
|
access.ready();
|
|
183
|
+
void access.run(() => settings.cleanupAfterOwnerChange()).catch(() => {
|
|
184
|
+
try {
|
|
185
|
+
logger.error("Temporary settings file cleanup is incomplete", { stage: "settings_file_cleanup" });
|
|
186
|
+
}
|
|
187
|
+
catch { }
|
|
188
|
+
});
|
|
167
189
|
return {
|
|
168
190
|
handleMcp: (request, response) => access.run(() => mcp.handle(request, response)),
|
|
169
191
|
handleHttp: (request, response) => access.run(() => httpHandler(request, response)),
|
|
170
192
|
async close() {
|
|
171
193
|
workflow?.stop();
|
|
172
|
-
access.close();
|
|
173
194
|
cards?.stop();
|
|
174
195
|
chart?.clearCache();
|
|
175
196
|
try {
|
|
176
|
-
await
|
|
197
|
+
await settings.close();
|
|
177
198
|
}
|
|
178
199
|
finally {
|
|
200
|
+
access.close();
|
|
179
201
|
try {
|
|
180
|
-
await
|
|
202
|
+
await walletRuntime.close();
|
|
181
203
|
}
|
|
182
204
|
finally {
|
|
183
|
-
|
|
184
|
-
|
|
205
|
+
try {
|
|
206
|
+
await mcp.close();
|
|
207
|
+
}
|
|
208
|
+
finally {
|
|
209
|
+
store.close();
|
|
210
|
+
ownership.close();
|
|
211
|
+
}
|
|
185
212
|
}
|
|
186
213
|
}
|
|
187
214
|
}
|
package/dist/runtime/config.js
CHANGED
|
@@ -17,8 +17,6 @@ export function loadBootConfig(env = process.env) {
|
|
|
17
17
|
activityDatabasePath: resolveActivityDatabasePath(env)
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
|
-
// Existing shared-backend port: clients using the same data directory must
|
|
21
|
-
// agree on this port and configuration. It is not browser-wallet authority.
|
|
22
20
|
export const DEFAULT_REVIEW_PORT = 8765;
|
|
23
21
|
function parseReviewPort(value) {
|
|
24
22
|
if (value === undefined || value === "") {
|
|
@@ -33,14 +33,23 @@ export class RuntimeLocalSettingsService {
|
|
|
33
33
|
async getLocalSettings() {
|
|
34
34
|
const stored = await this.options.preferencesRepository.getSuiGrpcUrl();
|
|
35
35
|
const storedGraphql = await this.options.preferencesRepository.getSuiGraphqlUrl();
|
|
36
|
+
return this.settingsFromStored(stored?.value, storedGraphql?.value);
|
|
37
|
+
}
|
|
38
|
+
getLocalSettingsSync(read) {
|
|
39
|
+
const grpc = read("suiGrpcUrl"), graphql = read("suiGraphqlUrl");
|
|
40
|
+
if (grpc && typeof grpc.value !== "string" || graphql && typeof graphql.value !== "string")
|
|
41
|
+
throw new LocalSettingsError("internal_error", "Stored endpoints are invalid.");
|
|
42
|
+
return this.settingsFromStored(grpc?.value, graphql?.value);
|
|
43
|
+
}
|
|
44
|
+
settingsFromStored(stored, storedGraphql) {
|
|
36
45
|
const currentStoredView = resolveSuiGrpcUrlView({
|
|
37
46
|
defaultSuiGrpcUrl: this.defaultSuiGrpcUrl,
|
|
38
|
-
storedValue: stored
|
|
47
|
+
storedValue: stored,
|
|
39
48
|
envValue: this.env.SUI_GRPC_URL
|
|
40
49
|
});
|
|
41
50
|
const currentStoredGraphqlView = resolveSuiGraphqlUrlView({
|
|
42
51
|
defaultSuiGraphqlUrl: this.defaultSuiGraphqlUrl,
|
|
43
|
-
storedValue: storedGraphql
|
|
52
|
+
storedValue: storedGraphql,
|
|
44
53
|
envValue: this.env.SUI_GRAPHQL_URL
|
|
45
54
|
});
|
|
46
55
|
return {
|
|
@@ -48,6 +57,18 @@ export class RuntimeLocalSettingsService {
|
|
|
48
57
|
suiGraphqlUrl: withBootEffectiveEndpoint(currentStoredGraphqlView, this.bootSuiGraphqlUrl)
|
|
49
58
|
};
|
|
50
59
|
}
|
|
60
|
+
resolveEndpointChange(input) {
|
|
61
|
+
const value = input.kind === "restore_endpoint"
|
|
62
|
+
? input.endpoint === "grpc" ? this.defaultSuiGrpcUrl : this.defaultSuiGraphqlUrl
|
|
63
|
+
: input.endpoint === "grpc" ? parseEndpointForTool(input.value ?? "") : parseGraphqlEndpointForTool(input.value ?? "");
|
|
64
|
+
return { key: input.endpoint === "grpc" ? "suiGrpcUrl" : "suiGraphqlUrl", value,
|
|
65
|
+
status: input.kind === "restore_endpoint" ? "reset" : "saved" };
|
|
66
|
+
}
|
|
67
|
+
async prepareEndpointChange(input) {
|
|
68
|
+
const target = this.resolveEndpointChange(input);
|
|
69
|
+
await this.verifyEndpointForTool(target.value, input.endpoint);
|
|
70
|
+
return target;
|
|
71
|
+
}
|
|
51
72
|
async setSuiGrpcUrl(url) {
|
|
52
73
|
const normalized = parseEndpointForTool(url);
|
|
53
74
|
await this.verifyEndpointForTool(normalized, "grpc");
|
package/dist/runtime/logger.js
CHANGED
|
@@ -3,8 +3,6 @@ export function flushStderr() {
|
|
|
3
3
|
const failed = (error) => { process.stderr.off("error", failed); reject(error); };
|
|
4
4
|
process.stderr.once("error", failed);
|
|
5
5
|
process.stderr.write("", (error) => {
|
|
6
|
-
// A failed stream write also emits error; keep its handler through that
|
|
7
|
-
// event instead of allowing EPIPE to replace the intended exit code.
|
|
8
6
|
if (error) {
|
|
9
7
|
reject(error);
|
|
10
8
|
return;
|
|
@@ -4,8 +4,6 @@ function errorCode(error) {
|
|
|
4
4
|
? error.code
|
|
5
5
|
: undefined;
|
|
6
6
|
}
|
|
7
|
-
// Bind the port: returns the server on success, undefined when the port is already
|
|
8
|
-
// in use, and rethrows any other (non-EADDRINUSE) startup error.
|
|
9
7
|
async function tryStart(start, port) {
|
|
10
8
|
try {
|
|
11
9
|
return await start(port);
|
|
@@ -17,17 +15,6 @@ async function tryStart(start, port) {
|
|
|
17
15
|
throw error;
|
|
18
16
|
}
|
|
19
17
|
}
|
|
20
|
-
/**
|
|
21
|
-
* Bind the review server to its fixed port, or defer to a healthy peer already
|
|
22
|
-
* serving it.
|
|
23
|
-
*
|
|
24
|
-
* - Port free → bind and own the single review origin.
|
|
25
|
-
* - Port held by a separate healthy instance of our review server → defer (run no
|
|
26
|
-
* local server; the peer serves the shared database for every client) and watch for
|
|
27
|
-
* the owner to exit, then take the origin over. No peer process is signalled.
|
|
28
|
-
* - Port held by anything else (foreign, no identity answer, or our own pid) → clear
|
|
29
|
-
* error; the origin is never silently reassigned.
|
|
30
|
-
*/
|
|
31
18
|
export async function startOrDeferReviewServer(start, port, deps) {
|
|
32
19
|
const owned = await tryStart(start, port);
|
|
33
20
|
if (owned) {
|
|
@@ -58,14 +45,12 @@ export async function startOrDeferReviewServer(start, port, deps) {
|
|
|
58
45
|
next = await tryStart(start, port);
|
|
59
46
|
}
|
|
60
47
|
catch (error) {
|
|
61
|
-
// An authenticated prior owner may have closed its port while its SDK
|
|
62
|
-
// still owns private storage. Reuse the existing takeover observation.
|
|
63
48
|
if (typeof error === "object" && error !== null && "code" in error && error.code === "DATA_DIRECTORY_OWNED")
|
|
64
49
|
continue;
|
|
65
50
|
throw error;
|
|
66
51
|
}
|
|
67
52
|
if (!next) {
|
|
68
|
-
continue;
|
|
53
|
+
continue;
|
|
69
54
|
}
|
|
70
55
|
if (stopped) {
|
|
71
56
|
await next.close();
|
|
@@ -82,8 +67,6 @@ export async function startOrDeferReviewServer(start, port, deps) {
|
|
|
82
67
|
return {
|
|
83
68
|
deferred: true,
|
|
84
69
|
close: async () => {
|
|
85
|
-
// Stop watching; the loop's stopped-check closes any bind that lands in-flight,
|
|
86
|
-
// so we never await the (possibly mid-delay) watch loop here.
|
|
87
70
|
stopped = true;
|
|
88
71
|
if (acquired) {
|
|
89
72
|
await acquired.close();
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { SuiEndpointError } from "../core/suiEndpoint.js";
|
|
2
|
-
// Keep arbitrary SDK/server exception text, endpoint values and stacks private.
|
|
3
2
|
export function runtimeFailureReason(error, stage) {
|
|
4
3
|
if (error instanceof SuiEndpointError)
|
|
5
4
|
return `Sui mainnet endpoint verification failed (${error.kind}). Check the configured endpoint.`;
|
|
@@ -16,8 +15,6 @@ export function runtimeFailureReason(error, stage) {
|
|
|
16
15
|
return "The private runtime control identity could not be loaded. Check data directory ownership and permissions.";
|
|
17
16
|
return "The local runtime could not complete this stage. Check local data permissions and runtime configuration.";
|
|
18
17
|
}
|
|
19
|
-
// Bootstrap alone decides exit intent. Cleanup-induced onclose notifications
|
|
20
|
-
// join the same promise; they cannot replace a fatal or signal exit with EOF.
|
|
21
18
|
export class RuntimeTermination {
|
|
22
19
|
options;
|
|
23
20
|
closing;
|
|
@@ -34,7 +31,7 @@ export class RuntimeTermination {
|
|
|
34
31
|
try {
|
|
35
32
|
this.options.logger.error(message, { stage, reason: runtimeFailureReason(error, stage) });
|
|
36
33
|
}
|
|
37
|
-
catch {
|
|
34
|
+
catch { }
|
|
38
35
|
};
|
|
39
36
|
if (cause.kind === "failure")
|
|
40
37
|
report("fatal runtime error", cause.stage, cause.error);
|
|
@@ -2,10 +2,8 @@ import { Agent, request as httpRequest } from "node:http";
|
|
|
2
2
|
import { connect } from "node:net";
|
|
3
3
|
import { randomBytes } from "node:crypto";
|
|
4
4
|
import { Readable } from "node:stream";
|
|
5
|
-
import { MAX_JSON_BODY_BYTES } from "
|
|
5
|
+
import { MAX_JSON_BODY_BYTES } from "./http.js";
|
|
6
6
|
import { IDENTITY_CHALLENGE_HEADER, IDENTITY_PATH, IDENTITY_TIMEOUT_MS, INTERNAL_MCP_PATH, SERVER_INSTANCE_HEADER, verifyServerIdentity } from "./control.js";
|
|
7
|
-
// Proof and authenticated request use the same TCP socket. A closed socket is never
|
|
8
|
-
// transparently replaced: no credential or UI permission can reach a new listener.
|
|
9
7
|
async function authenticatedChannel(port, control, signal) {
|
|
10
8
|
signal?.throwIfAborted();
|
|
11
9
|
const socket = connect({ host: "127.0.0.1", port });
|
|
@@ -86,7 +84,7 @@ export function createAuthenticatedFetch(port, control, expectedInstanceId) {
|
|
|
86
84
|
headers.set("host", url.host);
|
|
87
85
|
headers.set("authorization", `Bearer ${control.key}`);
|
|
88
86
|
headers.set(SERVER_INSTANCE_HEADER, channel.identity.instanceId);
|
|
89
|
-
headers.set("connection", "
|
|
87
|
+
headers.set("connection", "keep-alive");
|
|
90
88
|
const body = init?.body;
|
|
91
89
|
if (body !== undefined && body !== null && typeof body !== "string") {
|
|
92
90
|
channel.close();
|
|
@@ -2,20 +2,17 @@ import { createHash, createHmac, randomBytes, randomUUID, timingSafeEqual } from
|
|
|
2
2
|
import { link, lstat, mkdir, readFile, realpath, unlink, writeFile } from "node:fs/promises";
|
|
3
3
|
import { basename, dirname, join } from "node:path";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
-
|
|
6
|
-
// Clients must not attach to an owner that serves the previous package contract.
|
|
7
|
-
export const INTERNAL_API_VERSION = 7;
|
|
5
|
+
import { SERVER_VERSION } from "../../mcp/serverInfo.js";
|
|
8
6
|
export const IDENTITY_PATH = "/__runtime/identity";
|
|
9
7
|
export const INTERNAL_MCP_PATH = "/__runtime/mcp";
|
|
10
8
|
export const IDENTITY_CHALLENGE_HEADER = "x-sui-mcp-identity-challenge";
|
|
11
9
|
export const SERVER_INSTANCE_HEADER = "x-sui-mcp-server-instance";
|
|
12
|
-
// The existing review-server identity probe allows one second for a local response.
|
|
13
10
|
export const IDENTITY_TIMEOUT_MS = 1000;
|
|
14
11
|
const secretSchema = z.string().regex(/^[A-Za-z0-9_-]{43}$/);
|
|
15
12
|
export const serverIdentitySchema = z.object({
|
|
16
13
|
service: z.literal("sui-mcp"),
|
|
17
14
|
role: z.literal("shared-server"),
|
|
18
|
-
|
|
15
|
+
packageVersion: z.literal(SERVER_VERSION),
|
|
19
16
|
databaseId: z.string().regex(/^[0-9a-f]{64}$/),
|
|
20
17
|
configurationId: z.string().regex(/^[0-9a-f]{64}$/),
|
|
21
18
|
instanceId: z.string().uuid(),
|
|
@@ -53,12 +50,12 @@ export async function loadControlIdentity(databasePath, configuration) {
|
|
|
53
50
|
};
|
|
54
51
|
}
|
|
55
52
|
function identityPayload(identity) {
|
|
56
|
-
return JSON.stringify([identity.service, identity.role, identity.
|
|
53
|
+
return JSON.stringify([identity.service, identity.role, identity.packageVersion, identity.databaseId,
|
|
57
54
|
identity.configurationId, identity.instanceId, identity.pid, identity.challenge]);
|
|
58
55
|
}
|
|
59
56
|
export function createServerIdentity(control, instanceId, challenge) {
|
|
60
57
|
secretSchema.parse(challenge);
|
|
61
|
-
const identity = { service: "sui-mcp", role: "shared-server",
|
|
58
|
+
const identity = { service: "sui-mcp", role: "shared-server", packageVersion: SERVER_VERSION,
|
|
62
59
|
databaseId: control.databaseId, configurationId: control.configurationId,
|
|
63
60
|
instanceId, pid: process.pid, challenge };
|
|
64
61
|
return { ...identity, proof: createHmac("sha256", control.key).update(identityPayload(identity)).digest("base64url") };
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
-
// An asynchronous read may finish after import/reset. Its original generation
|
|
3
|
-
// remains attached to the continuation and cannot read or write the replacement DB.
|
|
4
2
|
export class RuntimeDataAccess {
|
|
5
3
|
context = new AsyncLocalStorage();
|
|
6
4
|
generation = 0;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { MAX_JSON_BODY_BYTES } from "../../core/session/settingsFileProtocol.js";
|
|
2
|
+
export class HttpError extends Error {
|
|
3
|
+
status;
|
|
4
|
+
code;
|
|
5
|
+
constructor(status, code) {
|
|
6
|
+
super(code);
|
|
7
|
+
this.status = status;
|
|
8
|
+
this.code = code;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export function sendJson(response, status, body) {
|
|
12
|
+
response.writeHead(status, { "content-type": "application/json; charset=utf-8" });
|
|
13
|
+
response.end(JSON.stringify(body));
|
|
14
|
+
}
|
|
@@ -2,14 +2,13 @@ import { CARD_TOOLS } from "../../mcp-ui/contracts.js";
|
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
4
4
|
import { isJSONRPCRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
-
import { MAX_JSON_BODY_BYTES, sendJson } from "
|
|
5
|
+
import { MAX_JSON_BODY_BYTES, sendJson } from "./http.js";
|
|
6
|
+
import { settingsUploadMessageBudget } from "../../core/session/settingsFileProtocol.js";
|
|
6
7
|
export function createInternalMcpHandler(createServer) {
|
|
7
8
|
const sessions = new Map();
|
|
8
9
|
const connections = new Set();
|
|
9
10
|
return {
|
|
10
11
|
async handle(request, response) {
|
|
11
|
-
// This product has no unsolicited server requests. POST returns JSON, and
|
|
12
|
-
// clients do not create a reconnecting SSE channel or replay requests.
|
|
13
12
|
if (request.method === "GET") {
|
|
14
13
|
response.setHeader("Allow", "POST, DELETE");
|
|
15
14
|
sendJson(response, 405, { error: "method_not_allowed" });
|
|
@@ -48,12 +47,13 @@ export function createInternalMcpHandler(createServer) {
|
|
|
48
47
|
close: () => transport.close()
|
|
49
48
|
};
|
|
50
49
|
transport.onmessage = (message, extra) => {
|
|
51
|
-
// Preserve ordinary stdio MCP input contracts. The existing HTTP/UI
|
|
52
|
-
// body limit applies to card calls, including their JSON-RPC envelope.
|
|
53
50
|
if (isJSONRPCRequest(message) && message.method === "tools/call" &&
|
|
54
51
|
Object.values(CARD_TOOLS).some((name) => name === message.params?.name) &&
|
|
55
52
|
Buffer.byteLength(JSON.stringify(message)) > MAX_JSON_BODY_BYTES) {
|
|
56
|
-
|
|
53
|
+
const budget = settingsUploadMessageBudget(message);
|
|
54
|
+
void transport.send({ jsonrpc: "2.0", id: message.id, error: { code: -32600,
|
|
55
|
+
message: budget === undefined ? "Card request exceeds the JSON body limit." : "settings_payload_too_large",
|
|
56
|
+
...(budget === undefined ? {} : { data: { code: "settings_payload_too_large", maximumMessageSize: MAX_JSON_BODY_BYTES, rawChunkLimit: budget } }) } });
|
|
57
57
|
return;
|
|
58
58
|
}
|
|
59
59
|
connection.onmessage?.(message, extra);
|
|
@@ -4,8 +4,6 @@ import { dirname, join } from "node:path";
|
|
|
4
4
|
export class DataDirectoryOwnedError extends Error {
|
|
5
5
|
code = "DATA_DIRECTORY_OWNED";
|
|
6
6
|
}
|
|
7
|
-
/** An OS-released ownership lock, separate from the product database. No
|
|
8
|
-
* business transaction is held while waiting for a wallet or network reply. */
|
|
9
7
|
export function acquireDataDirectoryOwner(databasePath) {
|
|
10
8
|
const directory = dirname(databasePath);
|
|
11
9
|
mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
@@ -31,8 +29,6 @@ export function acquireDataDirectoryOwner(databasePath) {
|
|
|
31
29
|
process.umask(mask);
|
|
32
30
|
}
|
|
33
31
|
try {
|
|
34
|
-
// A second owner is refused immediately, including one using another port.
|
|
35
|
-
// SQLite retains the EXCLUSIVE lock after commit until this handle closes.
|
|
36
32
|
lock.pragma("locking_mode=EXCLUSIVE");
|
|
37
33
|
lock.exec("BEGIN EXCLUSIVE");
|
|
38
34
|
lock.exec("CREATE TABLE IF NOT EXISTS owner_lock (singleton INTEGER PRIMARY KEY CHECK (singleton=1))");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createServer } from "node:http";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
|
-
import { HttpError, sendJson } from "
|
|
3
|
+
import { HttpError, sendJson } from "./http.js";
|
|
4
4
|
import { createServerIdentity, IDENTITY_CHALLENGE_HEADER, IDENTITY_PATH, INTERNAL_MCP_PATH, singleHeader, validControlAuthorization, validInternalRequest } from "./control.js";
|
|
5
5
|
export async function startSharedServer(input) {
|
|
6
6
|
const instanceId = randomUUID();
|
|
@@ -64,7 +64,6 @@ export async function startSharedServer(input) {
|
|
|
64
64
|
server.listen(port, "127.0.0.1");
|
|
65
65
|
});
|
|
66
66
|
port = server.address().port;
|
|
67
|
-
// Only the process that owns the port opens SQLite or initializes services.
|
|
68
67
|
application = Promise.resolve().then(() => input.createApplication(instanceId));
|
|
69
68
|
const closeListener = async () => {
|
|
70
69
|
if (!server.listening)
|
|
@@ -5,9 +5,6 @@ import { CallToolRequestSchema, CallToolResultSchema, CompleteRequestSchema, Get
|
|
|
5
5
|
import { SERVER_INSTRUCTIONS, SERVER_NAME, SERVER_VERSION } from "../../mcp/serverInfo.js";
|
|
6
6
|
import { createAuthenticatedFetch, probeAuthenticatedServer } from "./authenticatedFetch.js";
|
|
7
7
|
import { INTERNAL_MCP_PATH } from "./control.js";
|
|
8
|
-
// The SDK owns request IDs, initialization, cancellation and response parsing.
|
|
9
|
-
// This proxy has no domain services or writable stores. A new request may open a
|
|
10
|
-
// new MCP session after ownership changes; an already dispatched call is never replayed.
|
|
11
8
|
export async function startSharedStdio(input) {
|
|
12
9
|
const server = new Server({ name: SERVER_NAME, version: SERVER_VERSION }, {
|
|
13
10
|
capabilities: { tools: {}, resources: {}, prompts: {}, completions: {} }, instructions: SERVER_INSTRUCTIONS
|
|
@@ -56,7 +53,7 @@ export async function startSharedStdio(input) {
|
|
|
56
53
|
close: async () => { try {
|
|
57
54
|
await http.terminateSession();
|
|
58
55
|
}
|
|
59
|
-
catch {
|
|
56
|
+
catch { } await http.close(); }
|
|
60
57
|
};
|
|
61
58
|
http.onmessage = (message) => { if (http.sessionId !== undefined)
|
|
62
59
|
transport.sessionId = http.sessionId; transport.onmessage?.(message); };
|
|
@@ -105,7 +102,7 @@ export async function startSharedStdio(input) {
|
|
|
105
102
|
try {
|
|
106
103
|
await connecting;
|
|
107
104
|
}
|
|
108
|
-
catch {
|
|
105
|
+
catch { }
|
|
109
106
|
await connection?.client.close();
|
|
110
107
|
connection = undefined;
|
|
111
108
|
}
|
|
@@ -122,12 +122,9 @@ async function main() {
|
|
|
122
122
|
validateAdapterLifecycle: validateSupportedAdapterLifecycle,
|
|
123
123
|
sessions: activityStore.createSessionRecordStore(),
|
|
124
124
|
artifacts: activityStore.createPrivateReviewArtifactStore(),
|
|
125
|
-
settingsStore: activityStore.createSettingsRecordStore()
|
|
126
125
|
});
|
|
127
126
|
const reviewDependencies = createRuntimeReviewDependencies({ client: suiClient, chainIdentifier,
|
|
128
127
|
expectedChainIdentifier: config.expectedChainIdentifier, materialStore: transactionMaterialStore, readService });
|
|
129
|
-
// Utility-only read context in this script's temporary DB. This does not
|
|
130
|
-
// create a live wallet session or prove address ownership.
|
|
131
128
|
await activityStore.setActiveAccount(smokeAddress, "wallet_connection", new Date(), { name: "Explicit smoke read fixture" });
|
|
132
129
|
server = createMcpServer({
|
|
133
130
|
sessions,
|