neozip-mcp 0.1.0-beta
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/.cursor/mcp.json.global.example +10 -0
- package/CHANGELOG.md +16 -0
- package/DOCUMENTATION.md +40 -0
- package/LICENSE +16 -0
- package/README.md +223 -0
- package/SECURITY.md +37 -0
- package/dist/account/account-state.js +86 -0
- package/dist/account/format-account-status.js +37 -0
- package/dist/account/identity-provision.js +75 -0
- package/dist/account/identity-wrap.js +69 -0
- package/dist/account/profile-crypto.js +47 -0
- package/dist/account/profile-store.js +108 -0
- package/dist/account/require-account.js +29 -0
- package/dist/account/token-service-identity.js +395 -0
- package/dist/account/types.js +2 -0
- package/dist/account/wallet-evm.js +39 -0
- package/dist/archive/blockchain-status.js +303 -0
- package/dist/archive/crypto-self.js +114 -0
- package/dist/archive/detect-text.js +56 -0
- package/dist/archive/embed-metadata.js +283 -0
- package/dist/archive/encryption.js +166 -0
- package/dist/archive/extract-entry-buffer.js +18 -0
- package/dist/archive/find-entry.js +21 -0
- package/dist/archive/grep-content.js +141 -0
- package/dist/archive/identity-key.js +176 -0
- package/dist/archive/manifest.js +55 -0
- package/dist/archive/merkle.js +31 -0
- package/dist/archive/metadata-paths.js +14 -0
- package/dist/archive/mint-archive.js +61 -0
- package/dist/archive/open-archive.js +23 -0
- package/dist/archive/read-entry-buffer.js +11 -0
- package/dist/archive/read-entry-content.js +51 -0
- package/dist/archive/recipient-access.js +26 -0
- package/dist/archive/recipient-decrypt.js +21 -0
- package/dist/archive/recipient-lookup.js +55 -0
- package/dist/archive/timestamp-network.js +54 -0
- package/dist/config/capabilities.js +37 -0
- package/dist/config/index.js +74 -0
- package/dist/connect-cli.js +312 -0
- package/dist/connection/coordinator.js +74 -0
- package/dist/connection/credentials.js +29 -0
- package/dist/connection/crypto.js +56 -0
- package/dist/connection/dump.js +79 -0
- package/dist/connection/incomplete-setup.js +81 -0
- package/dist/connection/interactive.js +814 -0
- package/dist/connection/legacy-profile-reader.js +47 -0
- package/dist/connection/magic-link.js +138 -0
- package/dist/connection/migrate.js +76 -0
- package/dist/connection/onboarding.js +524 -0
- package/dist/connection/origin.js +63 -0
- package/dist/connection/phase.js +93 -0
- package/dist/connection/phone.js +20 -0
- package/dist/connection/promote-active.js +53 -0
- package/dist/connection/reset.js +20 -0
- package/dist/connection/setup-guidance.js +154 -0
- package/dist/connection/status-report.js +40 -0
- package/dist/connection/store.js +352 -0
- package/dist/connection/token-auth.js +42 -0
- package/dist/connection/types.js +2 -0
- package/dist/connection/wallet-setup.js +70 -0
- package/dist/constants/wallet-identity.js +11 -0
- package/dist/index.js +47 -0
- package/dist/load-env.js +16 -0
- package/dist/neozipkit-node.js +11 -0
- package/dist/register/resources.js +14 -0
- package/dist/register/tools.js +77 -0
- package/dist/resources/zip-resource.js +40 -0
- package/dist/resources/zip-uri.js +23 -0
- package/dist/security/auth.js +28 -0
- package/dist/security/capabilities.js +85 -0
- package/dist/security/rate-limiter.js +43 -0
- package/dist/security/resource-limiter.js +44 -0
- package/dist/security/sandbox.js +61 -0
- package/dist/server.js +32 -0
- package/dist/startup-account-gate.js +101 -0
- package/dist/startup-summary.js +40 -0
- package/dist/token-service/require-configured.js +23 -0
- package/dist/tools/account.js +504 -0
- package/dist/tools/compress.js +237 -0
- package/dist/tools/connect-status.js +143 -0
- package/dist/tools/extract.js +62 -0
- package/dist/tools/grep-entries.js +42 -0
- package/dist/tools/identity-status.js +157 -0
- package/dist/tools/info.js +147 -0
- package/dist/tools/list.js +118 -0
- package/dist/tools/lookup-recipient.js +37 -0
- package/dist/tools/mint.js +41 -0
- package/dist/tools/read-entry.js +35 -0
- package/dist/tools/search-entries.js +71 -0
- package/dist/tools/stamp.js +60 -0
- package/dist/tools/test.js +90 -0
- package/dist/tools/token-service-account.js +143 -0
- package/dist/tools/upgrade.js +60 -0
- package/dist/tools/verify.js +75 -0
- package/dist/tools/wallet-config-status.js +119 -0
- package/dist/tools/wallet-info.js +64 -0
- package/dist/translators/index.js +106 -0
- package/dist/types/index.js +7 -0
- package/dist/util/mask.js +30 -0
- package/dist/util/token-service-fetch.js +23 -0
- package/dist/vendor/neozipkit-pro.js +3 -0
- package/docs/NEOZIP_CONNECTION_STORE.md +238 -0
- package/docs/NEOZIP_CONNECT_CLI.md +185 -0
- package/docs/OPERATIONS.md +992 -0
- package/docs/examples/CLAUDE.md.example +22 -0
- package/docs/examples/claude/skills/neozip-mcp/SKILL.md +54 -0
- package/docs/examples/claude/skills/neozip-notarization/SKILL.md +75 -0
- package/docs/examples/mcp.json.claude.example +11 -0
- package/docs/examples/neozip-mcp-cursor-rule.mdc +31 -0
- package/docs/installation-guides/INSTALL_CLAUDE_CODE.md +286 -0
- package/docs/installation-guides/INSTALL_CLAUDE_WORKSPACE.md +301 -0
- package/docs/installation-guides/README.md +76 -0
- package/package.json +99 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { buildAccountStatusReport } from "./account/account-state.js";
|
|
2
|
+
import { formatAccountStatusText } from "./account/format-account-status.js";
|
|
3
|
+
import { hasLegacyMcpProfiles, migrateFromMcpProfileStore } from "./connection/migrate.js";
|
|
4
|
+
import { nextConnectCommand, resolveConnectionPhase } from "./connection/phase.js";
|
|
5
|
+
import { promoteReadyActiveConnection } from "./connection/promote-active.js";
|
|
6
|
+
import { getActiveConnection, readActiveConnectionSecrets, readConnectionPublic, } from "./connection/store.js";
|
|
7
|
+
import { formatTokenReauthGuidance, isAccessTokenExpired, } from "./connection/token-auth.js";
|
|
8
|
+
function readConnectionPublicLabel(connectionId) {
|
|
9
|
+
return readConnectionPublic(connectionId)?.label ?? connectionId;
|
|
10
|
+
}
|
|
11
|
+
function accountRequirementFlag() {
|
|
12
|
+
return (process.env.NEOZIP_MCP_REQUIRE_ACCOUNT?.trim() ||
|
|
13
|
+
process.env.NEOZIP_REQUIRE_ACCOUNT?.trim());
|
|
14
|
+
}
|
|
15
|
+
export function shouldRequireAccountAtStartup(config) {
|
|
16
|
+
const flag = accountRequirementFlag()?.toLowerCase();
|
|
17
|
+
if (flag === "false" || flag === "0" || flag === "off") {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
if (flag === "true" || flag === "ready" || flag === "1") {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
if (config.capabilities.includes("account") ||
|
|
24
|
+
config.capabilities.includes("blockchain")) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
export function formatInitAppHint() {
|
|
30
|
+
return [
|
|
31
|
+
"Run NeoZip Connect in a terminal (stdio / installed MCP):",
|
|
32
|
+
" neozip-connect",
|
|
33
|
+
"",
|
|
34
|
+
"The interactive wizard detects incomplete setups and offers to reset if needed.",
|
|
35
|
+
"Subcommands (register, verify, finish) remain for scripting with --json.",
|
|
36
|
+
"",
|
|
37
|
+
"When setup finishes (phase: ready), reload MCP in Cursor Settings.",
|
|
38
|
+
].join("\n");
|
|
39
|
+
}
|
|
40
|
+
export async function assertAccountReadyForStartup(config) {
|
|
41
|
+
if (!shouldRequireAccountAtStartup(config)) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (hasLegacyMcpProfiles()) {
|
|
45
|
+
migrateFromMcpProfileStore();
|
|
46
|
+
}
|
|
47
|
+
const promoted = promoteReadyActiveConnection({
|
|
48
|
+
preferredTokenServiceOrigin: config.tokenServiceUrl ?? undefined,
|
|
49
|
+
});
|
|
50
|
+
if (promoted.promoted) {
|
|
51
|
+
const previous = promoted.previousConnectionId
|
|
52
|
+
? readConnectionPublicLabel(promoted.previousConnectionId)
|
|
53
|
+
: "incomplete account";
|
|
54
|
+
console.error(`[neozip-mcp] Active connection was not ready (${previous}). Using ready account "${promoted.label}".`);
|
|
55
|
+
}
|
|
56
|
+
const phase = await resolveConnectionPhase(config.tokenServiceUrl ?? undefined);
|
|
57
|
+
if (phase === "ready") {
|
|
58
|
+
const secrets = readActiveConnectionSecrets();
|
|
59
|
+
if (isAccessTokenExpired(secrets)) {
|
|
60
|
+
const meta = getActiveConnection();
|
|
61
|
+
const lines = [
|
|
62
|
+
"[neozip-mcp] Token Service access token expired.",
|
|
63
|
+
"[neozip-mcp]",
|
|
64
|
+
...formatTokenReauthGuidance(meta?.email)
|
|
65
|
+
.split("\n")
|
|
66
|
+
.map((line) => (line ? `[neozip-mcp] ${line}` : "[neozip-mcp]")),
|
|
67
|
+
];
|
|
68
|
+
console.error(lines.join("\n"));
|
|
69
|
+
throw new Error("Token Service access token expired");
|
|
70
|
+
}
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const report = await buildAccountStatusReport({ probeTokenService: true });
|
|
74
|
+
const meta = getActiveConnection();
|
|
75
|
+
const nextCmd = nextConnectCommand(phase, {
|
|
76
|
+
email: meta?.email,
|
|
77
|
+
phoneE164: meta?.phoneE164,
|
|
78
|
+
});
|
|
79
|
+
const lines = [
|
|
80
|
+
"[neozip-mcp] NeoZip account is not initialized.",
|
|
81
|
+
`[neozip-mcp] Phase: ${report.phase === "ready" ? phase : report.phase}`,
|
|
82
|
+
];
|
|
83
|
+
if (report.nextSteps.length) {
|
|
84
|
+
lines.push(`[neozip-mcp] Remaining steps: ${report.nextSteps.join(", ")}`);
|
|
85
|
+
}
|
|
86
|
+
if (nextCmd) {
|
|
87
|
+
lines.push(`[neozip-mcp] Next: ${nextCmd}`);
|
|
88
|
+
}
|
|
89
|
+
lines.push("[neozip-mcp] Installed MCP (stdio) will not start until setup is complete.", "[neozip-mcp]", ...formatInitAppHint()
|
|
90
|
+
.split("\n")
|
|
91
|
+
.map((line) => (line ? `[neozip-mcp] ${line}` : "[neozip-mcp]")));
|
|
92
|
+
if (process.env.NEOZIP_MCP_STARTUP_SUMMARY !== "false") {
|
|
93
|
+
lines.push("[neozip-mcp]", "[neozip-mcp] Current status:");
|
|
94
|
+
for (const line of formatAccountStatusText(report).split("\n")) {
|
|
95
|
+
lines.push(`[neozip-mcp] ${line}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
console.error(lines.join("\n"));
|
|
99
|
+
throw new Error(`Account not ready (phase: ${phase})`);
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=startup-account-gate.js.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { buildAccountStatusReport } from "./account/account-state.js";
|
|
2
|
+
import { formatAccountStatusText } from "./account/format-account-status.js";
|
|
3
|
+
import { getActiveProfileId, listProfiles } from "./account/profile-store.js";
|
|
4
|
+
import { AuthGuard } from "./security/auth.js";
|
|
5
|
+
import { buildWalletConfigStatusReport, formatWalletConfigStatusText, } from "./tools/wallet-config-status.js";
|
|
6
|
+
function startupSummaryEnabled() {
|
|
7
|
+
return process.env.NEOZIP_MCP_STARTUP_SUMMARY !== "false";
|
|
8
|
+
}
|
|
9
|
+
/** Log wallet + account summary to stderr on every MCP start (stdio-safe). */
|
|
10
|
+
export async function logStartupSummary(config) {
|
|
11
|
+
if (!startupSummaryEnabled())
|
|
12
|
+
return;
|
|
13
|
+
const auth = new AuthGuard(config);
|
|
14
|
+
const hasAccountCapability = config.capabilities.includes("account");
|
|
15
|
+
const hasProfiles = listProfiles().length > 0 || getActiveProfileId() != null;
|
|
16
|
+
const blocks = [
|
|
17
|
+
"[neozip-mcp] Startup configuration summary",
|
|
18
|
+
"======================================",
|
|
19
|
+
formatWalletConfigStatusText(buildWalletConfigStatusReport(config, auth)),
|
|
20
|
+
];
|
|
21
|
+
if (hasAccountCapability || hasProfiles) {
|
|
22
|
+
const accountReport = await buildAccountStatusReport({
|
|
23
|
+
probeTokenService: true,
|
|
24
|
+
});
|
|
25
|
+
blocks.push(formatAccountStatusText(accountReport));
|
|
26
|
+
if (accountReport.phase !== "ready") {
|
|
27
|
+
if (hasAccountCapability) {
|
|
28
|
+
blocks.push("Account setup: run `neozip-connect` (interactive wizard) in a terminal, or use account_initialize / nextSteps tools after MCP is running (if NEOZIP_MCP_REQUIRE_ACCOUNT=false).");
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
blocks.push("Account setup tools are disabled. Add 'account' to NEOZIP_MCP_CAPABILITIES, set NEOZIP_MCP_REQUIRE_ACCOUNT=true, run `neozip-connect`, then reload MCP.");
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
blocks.push("NeoZip connection: none (~/.neozip/connection/). Run `neozip-connect` before starting MCP, or add capability 'account'. With default `auto` capabilities, blockchain tools appear after connect is ready and MCP is reloaded.");
|
|
37
|
+
}
|
|
38
|
+
console.error(blocks.join("\n\n"));
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=startup-summary.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { formatConnectSetupError, isTokenServiceConfigured } from "../connection/setup-guidance.js";
|
|
2
|
+
import { getActiveConnection, readActiveConnectionSecrets, } from "../connection/store.js";
|
|
3
|
+
import { formatTokenReauthGuidance, isAccessTokenExpired, } from "../connection/token-auth.js";
|
|
4
|
+
export { isTokenServiceConfigured, tokenServiceInfoUrl } from "../connection/setup-guidance.js";
|
|
5
|
+
export function formatTokenServiceRequiredMessage(ability, config) {
|
|
6
|
+
return formatConnectSetupError(ability, config);
|
|
7
|
+
}
|
|
8
|
+
export function requireTokenServiceConfigured(ability, config) {
|
|
9
|
+
const secrets = readActiveConnectionSecrets();
|
|
10
|
+
const meta = getActiveConnection();
|
|
11
|
+
if (secrets?.accessToken?.trim() && isAccessTokenExpired(secrets)) {
|
|
12
|
+
return [
|
|
13
|
+
`${ability} requires a valid Token Service access token.`,
|
|
14
|
+
"",
|
|
15
|
+
formatTokenReauthGuidance(meta?.email),
|
|
16
|
+
].join("\n");
|
|
17
|
+
}
|
|
18
|
+
if (isTokenServiceConfigured(config))
|
|
19
|
+
return null;
|
|
20
|
+
return formatTokenServiceRequiredMessage(ability, config);
|
|
21
|
+
}
|
|
22
|
+
export { requireWalletForBlockchain } from "../connection/setup-guidance.js";
|
|
23
|
+
//# sourceMappingURL=require-configured.js.map
|
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { buildAccountStatusReport, computeAccountPhase } from "../account/account-state.js";
|
|
3
|
+
import { formatAccountStatusText } from "../account/format-account-status.js";
|
|
4
|
+
import { provisionWalletIdentityKey } from "../account/identity-provision.js";
|
|
5
|
+
import { applyActiveProfileToEnv, createProfile, getActiveProfile, getActiveProfileId, listProfiles, readActiveSecrets, removeProfile, saveAccessToken, saveEvmWallet, saveIdentityProvision, savePhoneVerified, saveWalletLink, selectProfile, logoutProfile, clearProfileCredentialsFromEnv, } from "../account/profile-store.js";
|
|
6
|
+
import { registerEmailApp, requestPhoneOtp, runWalletEnsureAndAttach, verifyEmailAndExtractToken, verifyPhoneOtp, } from "../account/token-service-identity.js";
|
|
7
|
+
import { needsPhoneVerification, validatePhoneE164 } from "../connection/phone.js";
|
|
8
|
+
import { getActiveConnection } from "../connection/store.js";
|
|
9
|
+
import { evmIdentityFromMnemonic, evmIdentityFromPrivateKey, generateEvmIdentity, } from "../account/wallet-evm.js";
|
|
10
|
+
import { maskEmail } from "../util/mask.js";
|
|
11
|
+
import { successResult, errorResult } from "../types/index.js";
|
|
12
|
+
const formatOptions = z
|
|
13
|
+
.object({
|
|
14
|
+
format: z.enum(["text", "json"]).optional(),
|
|
15
|
+
serverUrl: z.string().optional(),
|
|
16
|
+
})
|
|
17
|
+
.optional();
|
|
18
|
+
function formatOutput(data, format) {
|
|
19
|
+
if (format === "json") {
|
|
20
|
+
return JSON.stringify(data, null, 2);
|
|
21
|
+
}
|
|
22
|
+
if (typeof data === "string")
|
|
23
|
+
return data;
|
|
24
|
+
return JSON.stringify(data, null, 2);
|
|
25
|
+
}
|
|
26
|
+
function requireActiveProfile() {
|
|
27
|
+
const profile = getActiveProfile();
|
|
28
|
+
if (!profile) {
|
|
29
|
+
throw new Error("No active profile. Run account_create or account_select first.");
|
|
30
|
+
}
|
|
31
|
+
return profile;
|
|
32
|
+
}
|
|
33
|
+
function requireActiveSecrets() {
|
|
34
|
+
const secrets = readActiveSecrets();
|
|
35
|
+
if (!secrets) {
|
|
36
|
+
throw new Error("Active profile secrets missing.");
|
|
37
|
+
}
|
|
38
|
+
return secrets;
|
|
39
|
+
}
|
|
40
|
+
export const accountStatusParameters = z.object({
|
|
41
|
+
options: formatOptions,
|
|
42
|
+
});
|
|
43
|
+
export async function executeAccountStatus(args) {
|
|
44
|
+
try {
|
|
45
|
+
const report = await buildAccountStatusReport();
|
|
46
|
+
const format = args.options?.format ?? "text";
|
|
47
|
+
if (format === "json") {
|
|
48
|
+
return successResult(formatOutput(report, "json"));
|
|
49
|
+
}
|
|
50
|
+
return successResult(formatAccountStatusText(report));
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
return errorResult(err instanceof Error ? err.message : String(err));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export const accountListParameters = z.object({
|
|
57
|
+
options: formatOptions,
|
|
58
|
+
});
|
|
59
|
+
export async function executeAccountList(args) {
|
|
60
|
+
try {
|
|
61
|
+
const active = getActiveProfileId();
|
|
62
|
+
const profiles = listProfiles().map((p) => ({
|
|
63
|
+
...p,
|
|
64
|
+
email: p.email ? maskEmail(p.email) : null,
|
|
65
|
+
active: p.id === active,
|
|
66
|
+
}));
|
|
67
|
+
return successResult(formatOutput({ success: true, profiles, activeProfileId: active }, args.options?.format));
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
return errorResult(err instanceof Error ? err.message : String(err));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export const accountCreateParameters = z.object({
|
|
74
|
+
label: z.string().optional(),
|
|
75
|
+
options: formatOptions,
|
|
76
|
+
});
|
|
77
|
+
export async function executeAccountCreate(args) {
|
|
78
|
+
try {
|
|
79
|
+
const profile = createProfile({
|
|
80
|
+
label: args.label || "default",
|
|
81
|
+
tokenServiceUrl: args.options?.serverUrl,
|
|
82
|
+
});
|
|
83
|
+
return successResult(formatOutput({
|
|
84
|
+
success: true,
|
|
85
|
+
profileId: profile.id,
|
|
86
|
+
label: profile.label,
|
|
87
|
+
tokenServiceUrl: profile.tokenServiceUrl,
|
|
88
|
+
phase: computeAccountPhase(),
|
|
89
|
+
nextSteps: ["account_register_email"],
|
|
90
|
+
}, args.options?.format));
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
return errorResult(err instanceof Error ? err.message : String(err));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
export const accountSelectParameters = z.object({
|
|
97
|
+
profileId: z.string(),
|
|
98
|
+
options: formatOptions,
|
|
99
|
+
});
|
|
100
|
+
export async function executeAccountSelect(args) {
|
|
101
|
+
try {
|
|
102
|
+
const profile = selectProfile(args.profileId);
|
|
103
|
+
applyActiveProfileToEnv();
|
|
104
|
+
return successResult(formatOutput({
|
|
105
|
+
success: true,
|
|
106
|
+
profileId: profile.id,
|
|
107
|
+
phase: computeAccountPhase(),
|
|
108
|
+
reloadRequired: true,
|
|
109
|
+
message: "Reload MCP to load this profile into the session.",
|
|
110
|
+
}, args.options?.format));
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
return errorResult(err instanceof Error ? err.message : String(err));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
export const accountRegisterEmailParameters = z.object({
|
|
117
|
+
email: z.string().email(),
|
|
118
|
+
options: formatOptions,
|
|
119
|
+
});
|
|
120
|
+
export async function executeAccountRegisterEmail(args, resourceLimiter) {
|
|
121
|
+
try {
|
|
122
|
+
const profile = requireActiveProfile();
|
|
123
|
+
const result = await resourceLimiter.withTimeout(async () => {
|
|
124
|
+
return registerEmailApp(profile.tokenServiceUrl, args.email);
|
|
125
|
+
}, "account_register_email");
|
|
126
|
+
if (!result.success) {
|
|
127
|
+
throw new Error(result.error || "Registration failed");
|
|
128
|
+
}
|
|
129
|
+
return successResult(formatOutput({
|
|
130
|
+
success: true,
|
|
131
|
+
email: maskEmail(args.email),
|
|
132
|
+
message: result.message,
|
|
133
|
+
nextSteps: ["account_verify_email"],
|
|
134
|
+
}, args.options?.format));
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
return errorResult(err instanceof Error ? err.message : String(err));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
export const accountVerifyEmailParameters = z.object({
|
|
141
|
+
email: z.string().email(),
|
|
142
|
+
code: z.string().min(1),
|
|
143
|
+
options: formatOptions,
|
|
144
|
+
});
|
|
145
|
+
export async function executeAccountVerifyEmail(args, resourceLimiter) {
|
|
146
|
+
try {
|
|
147
|
+
const profile = requireActiveProfile();
|
|
148
|
+
const result = await resourceLimiter.withTimeout(async () => {
|
|
149
|
+
return verifyEmailAndExtractToken(profile.tokenServiceUrl, args.email, args.code);
|
|
150
|
+
}, "account_verify_email");
|
|
151
|
+
saveAccessToken(profile.id, result.accessToken, result.email, result.expiresAt);
|
|
152
|
+
applyActiveProfileToEnv();
|
|
153
|
+
const connection = getActiveConnection();
|
|
154
|
+
const phoneRequired = connection != null &&
|
|
155
|
+
(await needsPhoneVerification(connection, profile.tokenServiceUrl));
|
|
156
|
+
return successResult(formatOutput({
|
|
157
|
+
success: true,
|
|
158
|
+
email: maskEmail(result.email),
|
|
159
|
+
phase: computeAccountPhase(),
|
|
160
|
+
nextSteps: phoneRequired
|
|
161
|
+
? ["account_request_phone_otp", "account_verify_phone"]
|
|
162
|
+
: ["account_create_wallet"],
|
|
163
|
+
reloadRequired: true,
|
|
164
|
+
message: phoneRequired
|
|
165
|
+
? "Email verified. Verify phone via account_request_phone_otp and account_verify_phone."
|
|
166
|
+
: "Bearer token saved to encrypted profile store. Reload MCP, then create/link wallet.",
|
|
167
|
+
}, args.options?.format));
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
return errorResult(err instanceof Error ? err.message : String(err));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
export const accountRequestPhoneOtpParameters = z.object({
|
|
174
|
+
phoneE164: z.string().min(8),
|
|
175
|
+
options: formatOptions,
|
|
176
|
+
});
|
|
177
|
+
export async function executeAccountRequestPhoneOtp(args, resourceLimiter) {
|
|
178
|
+
try {
|
|
179
|
+
const profile = requireActiveProfile();
|
|
180
|
+
const secrets = requireActiveSecrets();
|
|
181
|
+
if (!secrets.accessToken) {
|
|
182
|
+
throw new Error("Verify email first (account_verify_email).");
|
|
183
|
+
}
|
|
184
|
+
validatePhoneE164(args.phoneE164);
|
|
185
|
+
const result = await resourceLimiter.withTimeout(() => requestPhoneOtp(profile.tokenServiceUrl, secrets.accessToken, args.phoneE164), "account_request_phone_otp");
|
|
186
|
+
if (!result.success) {
|
|
187
|
+
throw new Error(result.error ?? "Failed to send phone verification SMS.");
|
|
188
|
+
}
|
|
189
|
+
return successResult(formatOutput({
|
|
190
|
+
success: true,
|
|
191
|
+
phoneE164: args.phoneE164.trim(),
|
|
192
|
+
nextSteps: ["account_verify_phone"],
|
|
193
|
+
message: "SMS verification code sent.",
|
|
194
|
+
}, args.options?.format));
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
return errorResult(err instanceof Error ? err.message : String(err));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
export const accountVerifyPhoneParameters = z.object({
|
|
201
|
+
phoneE164: z.string().min(8),
|
|
202
|
+
code: z.string().min(1),
|
|
203
|
+
options: formatOptions,
|
|
204
|
+
});
|
|
205
|
+
export async function executeAccountVerifyPhone(args, resourceLimiter) {
|
|
206
|
+
try {
|
|
207
|
+
const profile = requireActiveProfile();
|
|
208
|
+
const secrets = requireActiveSecrets();
|
|
209
|
+
if (!secrets.accessToken) {
|
|
210
|
+
throw new Error("Verify email first (account_verify_email).");
|
|
211
|
+
}
|
|
212
|
+
validatePhoneE164(args.phoneE164);
|
|
213
|
+
const result = await resourceLimiter.withTimeout(() => verifyPhoneOtp(profile.tokenServiceUrl, secrets.accessToken, args.phoneE164, args.code), "account_verify_phone");
|
|
214
|
+
if (!result.success) {
|
|
215
|
+
throw new Error(result.error ?? "Phone verification failed.");
|
|
216
|
+
}
|
|
217
|
+
savePhoneVerified(profile.id, args.phoneE164);
|
|
218
|
+
applyActiveProfileToEnv();
|
|
219
|
+
return successResult(formatOutput({
|
|
220
|
+
success: true,
|
|
221
|
+
phoneE164: args.phoneE164.trim(),
|
|
222
|
+
phase: computeAccountPhase(),
|
|
223
|
+
nextSteps: ["account_create_wallet"],
|
|
224
|
+
reloadRequired: true,
|
|
225
|
+
message: "Phone verified and saved to encrypted profile store.",
|
|
226
|
+
}, args.options?.format));
|
|
227
|
+
}
|
|
228
|
+
catch (err) {
|
|
229
|
+
return errorResult(err instanceof Error ? err.message : String(err));
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
export const accountCreateWalletParameters = z.object({
|
|
233
|
+
options: formatOptions,
|
|
234
|
+
});
|
|
235
|
+
export async function executeAccountCreateWallet(args) {
|
|
236
|
+
try {
|
|
237
|
+
const profile = requireActiveProfile();
|
|
238
|
+
const secrets = requireActiveSecrets();
|
|
239
|
+
if (!secrets.accessToken) {
|
|
240
|
+
throw new Error("Verify email first (account_verify_email).");
|
|
241
|
+
}
|
|
242
|
+
const evm = generateEvmIdentity();
|
|
243
|
+
saveEvmWallet(profile.id, evm.privateKey, evm.address);
|
|
244
|
+
applyActiveProfileToEnv();
|
|
245
|
+
return successResult(formatOutput({
|
|
246
|
+
success: true,
|
|
247
|
+
evmAddress: evm.address,
|
|
248
|
+
phase: computeAccountPhase(),
|
|
249
|
+
nextSteps: ["account_link_wallet"],
|
|
250
|
+
message: "Wallet generated and stored in encrypted profile (private key not shown).",
|
|
251
|
+
}, args.options?.format));
|
|
252
|
+
}
|
|
253
|
+
catch (err) {
|
|
254
|
+
return errorResult(err instanceof Error ? err.message : String(err));
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
export const accountImportWalletParameters = z.object({
|
|
258
|
+
mnemonic: z.string().optional(),
|
|
259
|
+
privateKey: z.string().optional(),
|
|
260
|
+
options: formatOptions,
|
|
261
|
+
});
|
|
262
|
+
export async function executeAccountImportWallet(args) {
|
|
263
|
+
try {
|
|
264
|
+
const profile = requireActiveProfile();
|
|
265
|
+
const secrets = requireActiveSecrets();
|
|
266
|
+
if (!secrets.accessToken) {
|
|
267
|
+
throw new Error("Verify email first (account_verify_email).");
|
|
268
|
+
}
|
|
269
|
+
if (!args.mnemonic && !args.privateKey) {
|
|
270
|
+
throw new Error("Provide mnemonic or privateKey.");
|
|
271
|
+
}
|
|
272
|
+
const evm = args.mnemonic
|
|
273
|
+
? evmIdentityFromMnemonic(args.mnemonic)
|
|
274
|
+
: evmIdentityFromPrivateKey(args.privateKey);
|
|
275
|
+
saveEvmWallet(profile.id, evm.privateKey, evm.address);
|
|
276
|
+
applyActiveProfileToEnv();
|
|
277
|
+
return successResult(formatOutput({
|
|
278
|
+
success: true,
|
|
279
|
+
evmAddress: evm.address,
|
|
280
|
+
phase: computeAccountPhase(),
|
|
281
|
+
nextSteps: ["account_link_wallet"],
|
|
282
|
+
warning: "Imported secrets were stored locally; avoid pasting mnemonics/keys in chat when possible.",
|
|
283
|
+
}, args.options?.format));
|
|
284
|
+
}
|
|
285
|
+
catch (err) {
|
|
286
|
+
return errorResult(err instanceof Error ? err.message : String(err));
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
export const accountLinkWalletParameters = z.object({
|
|
290
|
+
options: formatOptions,
|
|
291
|
+
});
|
|
292
|
+
export async function executeAccountLinkWallet(args, resourceLimiter) {
|
|
293
|
+
try {
|
|
294
|
+
const profile = requireActiveProfile();
|
|
295
|
+
const secrets = requireActiveSecrets();
|
|
296
|
+
if (!secrets.accessToken || !secrets.evmPrivateKey || !profile.evmAddress) {
|
|
297
|
+
throw new Error("Complete email verification and create/import wallet first.");
|
|
298
|
+
}
|
|
299
|
+
const linked = await resourceLimiter.withTimeout(() => runWalletEnsureAndAttach(profile.tokenServiceUrl, secrets.accessToken, secrets.evmPrivateKey, profile.evmAddress), "account_link_wallet");
|
|
300
|
+
saveWalletLink(profile.id, linked.walletId, linked.linkId, linked.evmAddress);
|
|
301
|
+
applyActiveProfileToEnv();
|
|
302
|
+
return successResult(formatOutput({
|
|
303
|
+
success: true,
|
|
304
|
+
walletId: linked.walletId,
|
|
305
|
+
linkId: linked.linkId,
|
|
306
|
+
evmAddress: linked.evmAddress,
|
|
307
|
+
phase: computeAccountPhase(),
|
|
308
|
+
nextSteps: ["account_provision_identity"],
|
|
309
|
+
}, args.options?.format));
|
|
310
|
+
}
|
|
311
|
+
catch (err) {
|
|
312
|
+
return errorResult(err instanceof Error ? err.message : String(err));
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
export const accountProvisionIdentityParameters = z.object({
|
|
316
|
+
options: formatOptions,
|
|
317
|
+
});
|
|
318
|
+
export async function executeAccountProvisionIdentity(args, resourceLimiter) {
|
|
319
|
+
try {
|
|
320
|
+
const profile = requireActiveProfile();
|
|
321
|
+
const secrets = requireActiveSecrets();
|
|
322
|
+
if (!secrets.accessToken ||
|
|
323
|
+
!secrets.evmPrivateKey ||
|
|
324
|
+
!profile.evmAddress ||
|
|
325
|
+
profile.walletId == null) {
|
|
326
|
+
throw new Error("Link wallet on Token Service before provisioning identity.");
|
|
327
|
+
}
|
|
328
|
+
const out = await resourceLimiter.withTimeout(() => provisionWalletIdentityKey({
|
|
329
|
+
baseUrl: profile.tokenServiceUrl,
|
|
330
|
+
accessToken: secrets.accessToken,
|
|
331
|
+
walletId: profile.walletId,
|
|
332
|
+
evmPrivateKey: secrets.evmPrivateKey,
|
|
333
|
+
evmAddress: profile.evmAddress,
|
|
334
|
+
}), "account_provision_identity");
|
|
335
|
+
const updated = saveIdentityProvision(profile.id, out.identityKeyId, out.x25519PublicKey);
|
|
336
|
+
applyActiveProfileToEnv();
|
|
337
|
+
return successResult(formatOutput({
|
|
338
|
+
success: true,
|
|
339
|
+
identityKeyId: out.identityKeyId,
|
|
340
|
+
x25519Fingerprint: updated.x25519Fingerprint,
|
|
341
|
+
provisioned: out.provisioned,
|
|
342
|
+
phase: computeAccountPhase(),
|
|
343
|
+
reloadRequired: true,
|
|
344
|
+
message: "Identity key provisioned. Reload MCP to use recipient encryption and decrypt.",
|
|
345
|
+
}, args.options?.format));
|
|
346
|
+
}
|
|
347
|
+
catch (err) {
|
|
348
|
+
return errorResult(err instanceof Error ? err.message : String(err));
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
export const accountLogoutParameters = z.object({
|
|
352
|
+
profileId: z.string().optional(),
|
|
353
|
+
options: formatOptions,
|
|
354
|
+
});
|
|
355
|
+
export async function executeAccountLogout(args) {
|
|
356
|
+
try {
|
|
357
|
+
const result = logoutProfile(args.profileId);
|
|
358
|
+
clearProfileCredentialsFromEnv();
|
|
359
|
+
const phase = computeAccountPhase();
|
|
360
|
+
const payload = {
|
|
361
|
+
success: true,
|
|
362
|
+
loggedOutProfileId: result.profileId,
|
|
363
|
+
label: result.label,
|
|
364
|
+
wasActive: result.wasActive,
|
|
365
|
+
phase,
|
|
366
|
+
nextSteps: ["account_select", "account_register_email", "account_initialize"],
|
|
367
|
+
reloadRequired: true,
|
|
368
|
+
message: "Logged out: cleared Bearer token, wallet key, and identity material from the profile store. " +
|
|
369
|
+
"Reload MCP to clear credentials from this session. " +
|
|
370
|
+
"Credentials in mcp.json env are unchanged. Use account_remove to delete the profile entirely.",
|
|
371
|
+
};
|
|
372
|
+
const format = args.options?.format ?? "text";
|
|
373
|
+
if (format === "json") {
|
|
374
|
+
return successResult(formatOutput(payload, "json"));
|
|
375
|
+
}
|
|
376
|
+
return successResult([
|
|
377
|
+
"NeoZip account logged out",
|
|
378
|
+
`Profile: ${result.label} (${result.profileId})`,
|
|
379
|
+
`Phase: ${phase}`,
|
|
380
|
+
`Next: ${payload.nextSteps.join(", ")}`,
|
|
381
|
+
payload.message,
|
|
382
|
+
].join("\n"));
|
|
383
|
+
}
|
|
384
|
+
catch (err) {
|
|
385
|
+
return errorResult(err instanceof Error ? err.message : String(err));
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
export const accountRemoveParameters = z.object({
|
|
389
|
+
profileId: z.string(),
|
|
390
|
+
confirm: z.boolean(),
|
|
391
|
+
options: formatOptions,
|
|
392
|
+
});
|
|
393
|
+
export async function executeAccountRemove(args) {
|
|
394
|
+
try {
|
|
395
|
+
removeProfile(args.profileId, args.confirm === true);
|
|
396
|
+
return successResult(formatOutput({ success: true, removed: args.profileId }, args.options?.format));
|
|
397
|
+
}
|
|
398
|
+
catch (err) {
|
|
399
|
+
return errorResult(err instanceof Error ? err.message : String(err));
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
export const accountInitializeParameters = z.object({
|
|
403
|
+
step: z
|
|
404
|
+
.enum(["auto", "status", "email", "phone", "wallet", "link", "identity"])
|
|
405
|
+
.optional()
|
|
406
|
+
.default("auto"),
|
|
407
|
+
email: z.string().email().optional(),
|
|
408
|
+
code: z.string().optional(),
|
|
409
|
+
phoneE164: z.string().optional(),
|
|
410
|
+
phoneCode: z.string().optional(),
|
|
411
|
+
label: z.string().optional(),
|
|
412
|
+
mnemonic: z.string().optional(),
|
|
413
|
+
privateKey: z.string().optional(),
|
|
414
|
+
options: formatOptions,
|
|
415
|
+
});
|
|
416
|
+
export async function executeAccountInitialize(args, resourceLimiter) {
|
|
417
|
+
try {
|
|
418
|
+
const step = args.step ?? "auto";
|
|
419
|
+
if (step === "status") {
|
|
420
|
+
return executeAccountStatus({ options: args.options });
|
|
421
|
+
}
|
|
422
|
+
let phase = computeAccountPhase();
|
|
423
|
+
if (phase === "no_profile") {
|
|
424
|
+
await executeAccountCreate({ label: args.label, options: args.options });
|
|
425
|
+
phase = computeAccountPhase();
|
|
426
|
+
}
|
|
427
|
+
if (step === "auto" || step === "email") {
|
|
428
|
+
if (phase === "email_unverified") {
|
|
429
|
+
if (!args.email) {
|
|
430
|
+
return errorResult("account_initialize (email): provide email and code after account_register_email, or run account_register_email then account_verify_email.");
|
|
431
|
+
}
|
|
432
|
+
if (!args.code) {
|
|
433
|
+
const reg = await executeAccountRegisterEmail({ email: args.email, options: args.options }, resourceLimiter);
|
|
434
|
+
if (reg.content[0]?.text.startsWith("Error:"))
|
|
435
|
+
return reg;
|
|
436
|
+
return successResult(`${reg.content[0]?.text}\n\nCheck your inbox for the 6-digit code, then call account_initialize again with the same email and code.`);
|
|
437
|
+
}
|
|
438
|
+
const ver = await executeAccountVerifyEmail({ email: args.email, code: args.code, options: args.options }, resourceLimiter);
|
|
439
|
+
if (ver.content[0]?.text.startsWith("Error:"))
|
|
440
|
+
return ver;
|
|
441
|
+
phase = computeAccountPhase();
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
const activeProfile = getActiveProfile();
|
|
445
|
+
const activeConnection = getActiveConnection();
|
|
446
|
+
const phoneRequired = activeProfile != null &&
|
|
447
|
+
activeConnection != null &&
|
|
448
|
+
(await needsPhoneVerification(activeConnection, activeProfile.tokenServiceUrl));
|
|
449
|
+
if ((step === "auto" || step === "phone") && phoneRequired) {
|
|
450
|
+
if (!args.phoneE164) {
|
|
451
|
+
return errorResult("account_initialize (phone): provide phoneE164 and phoneCode, or run account_request_phone_otp then account_verify_phone.");
|
|
452
|
+
}
|
|
453
|
+
if (!args.phoneCode) {
|
|
454
|
+
const req = await executeAccountRequestPhoneOtp({ phoneE164: args.phoneE164, options: args.options }, resourceLimiter);
|
|
455
|
+
if (req.content[0]?.text.startsWith("Error:"))
|
|
456
|
+
return req;
|
|
457
|
+
return successResult(`${req.content[0]?.text}\n\nCheck your phone for the SMS code, then call account_initialize again with the same phoneE164 and phoneCode.`);
|
|
458
|
+
}
|
|
459
|
+
const phoneVer = await executeAccountVerifyPhone({
|
|
460
|
+
phoneE164: args.phoneE164,
|
|
461
|
+
code: args.phoneCode,
|
|
462
|
+
options: args.options,
|
|
463
|
+
}, resourceLimiter);
|
|
464
|
+
if (phoneVer.content[0]?.text.startsWith("Error:"))
|
|
465
|
+
return phoneVer;
|
|
466
|
+
phase = computeAccountPhase();
|
|
467
|
+
}
|
|
468
|
+
if ((step === "auto" || step === "wallet") && phase === "no_wallet") {
|
|
469
|
+
if (args.mnemonic || args.privateKey) {
|
|
470
|
+
const imp = await executeAccountImportWallet({
|
|
471
|
+
mnemonic: args.mnemonic,
|
|
472
|
+
privateKey: args.privateKey,
|
|
473
|
+
options: args.options,
|
|
474
|
+
});
|
|
475
|
+
if (imp.content[0]?.text.startsWith("Error:"))
|
|
476
|
+
return imp;
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
const cw = await executeAccountCreateWallet({ options: args.options });
|
|
480
|
+
if (cw.content[0]?.text.startsWith("Error:"))
|
|
481
|
+
return cw;
|
|
482
|
+
}
|
|
483
|
+
phase = computeAccountPhase();
|
|
484
|
+
}
|
|
485
|
+
if ((step === "auto" || step === "link") && phase === "wallet_unlinked") {
|
|
486
|
+
const link = await executeAccountLinkWallet({ options: args.options }, resourceLimiter);
|
|
487
|
+
if (link.content[0]?.text.startsWith("Error:"))
|
|
488
|
+
return link;
|
|
489
|
+
phase = computeAccountPhase();
|
|
490
|
+
}
|
|
491
|
+
if ((step === "auto" || step === "identity") && phase === "identity_missing") {
|
|
492
|
+
const prov = await executeAccountProvisionIdentity({ options: args.options }, resourceLimiter);
|
|
493
|
+
if (prov.content[0]?.text.startsWith("Error:"))
|
|
494
|
+
return prov;
|
|
495
|
+
phase = computeAccountPhase();
|
|
496
|
+
}
|
|
497
|
+
const report = await buildAccountStatusReport();
|
|
498
|
+
return successResult(formatOutput(report, args.options?.format ?? "json"));
|
|
499
|
+
}
|
|
500
|
+
catch (err) {
|
|
501
|
+
return errorResult(err instanceof Error ? err.message : String(err));
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
//# sourceMappingURL=account.js.map
|