@mysten-incubation/memwal-mcp 0.0.1-dev.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/dist/bridge.js ADDED
@@ -0,0 +1,330 @@
1
+ import { credsPath } from "./auth.js";
2
+ import { log, note } from "./logger.js";
3
+ async function openSseStream(relayerUrl, creds) {
4
+ const url = `${relayerUrl.replace(/\/+$/, "")}/api/mcp/sse`;
5
+ const controller = new AbortController();
6
+ const resp = await fetch(url, {
7
+ method: "GET",
8
+ headers: {
9
+ authorization: `Bearer ${creds.delegatePrivateKey}`,
10
+ "x-memwal-account-id": creds.accountId,
11
+ accept: "text/event-stream",
12
+ "cache-control": "no-cache",
13
+ },
14
+ signal: controller.signal,
15
+ });
16
+ if (resp.status === 401) {
17
+ controller.abort();
18
+ log.warn("bridge.unauthorized", { url });
19
+ // DO NOT wipe creds here. A 401 from the relayer is *evidence* of
20
+ // a problem but not *proof* the saved seed is the cause. Possible
21
+ // sources: revoked delegate key (genuine), transient WAF / rate
22
+ // limit (false positive), http_proxy interposed somewhere on the
23
+ // path, or — on `--local` — local malware racing the relayer port.
24
+ // Auto-wiping the seed turns any one of those into a permanent
25
+ // outage that forces re-login. Force-fail loud instead; the user
26
+ // runs `memwal-mcp login` if they want to actually rotate.
27
+ throw new Error("MemWal relayer rejected credentials (HTTP 401). " +
28
+ "Delegate key may have been revoked, the relayer may be " +
29
+ "rate-limiting, or a proxy may be interposed. Saved " +
30
+ `credentials at ${credsPath()} were NOT modified. ` +
31
+ "Run `memwal-mcp login` if you need to rotate the key.");
32
+ }
33
+ if (!resp.ok || !resp.body) {
34
+ const body = resp.body ? await resp.text() : "";
35
+ controller.abort();
36
+ throw new Error(`MemWal relayer SSE handshake failed: HTTP ${resp.status} ${body.slice(0, 200)}`);
37
+ }
38
+ const ct = resp.headers.get("content-type") ?? "";
39
+ if (!ct.includes("event-stream")) {
40
+ controller.abort();
41
+ throw new Error(`MemWal relayer returned unexpected content-type "${ct}" for SSE endpoint`);
42
+ }
43
+ const reader = resp.body.getReader();
44
+ const decoder = new TextDecoder();
45
+ let buf = "";
46
+ let endpointResolved = false;
47
+ let endpointPath = "";
48
+ let streamEnded = false;
49
+ let streamError = null;
50
+ const events = [];
51
+ let queueResolver = null;
52
+ function wake() {
53
+ const r = queueResolver;
54
+ if (r) {
55
+ queueResolver = null;
56
+ r();
57
+ }
58
+ }
59
+ function pushEvent(ev) {
60
+ events.push(ev);
61
+ wake();
62
+ }
63
+ // Pump the SSE stream in the background.
64
+ const pump = (async () => {
65
+ try {
66
+ while (true) {
67
+ const { done, value } = await reader.read();
68
+ if (done)
69
+ break;
70
+ buf += decoder.decode(value, { stream: true });
71
+ let sep;
72
+ while ((sep = buf.indexOf("\n\n")) >= 0) {
73
+ const chunk = buf.slice(0, sep);
74
+ buf = buf.slice(sep + 2);
75
+ const lines = chunk.split("\n");
76
+ const event = lines
77
+ .find((l) => l.startsWith("event:"))
78
+ ?.slice("event:".length)
79
+ .trim();
80
+ const data = lines
81
+ .filter((l) => l.startsWith("data:"))
82
+ .map((l) => l.slice("data:".length).replace(/^\s/, ""))
83
+ .join("\n");
84
+ if (event === "endpoint" && !endpointResolved) {
85
+ endpointPath = data.trim();
86
+ endpointResolved = true;
87
+ wake();
88
+ continue;
89
+ }
90
+ if (event === "message" || (!event && data)) {
91
+ try {
92
+ const parsed = JSON.parse(data);
93
+ pushEvent(parsed);
94
+ }
95
+ catch {
96
+ log.warn("bridge.sse_parse_failed", { data: data.slice(0, 120) });
97
+ }
98
+ }
99
+ }
100
+ }
101
+ }
102
+ catch (err) {
103
+ if (!controller.signal.aborted) {
104
+ const msg = err instanceof Error ? err.message : String(err);
105
+ streamError = msg;
106
+ // `terminated` is undici's keep-alive idle drop — happens on
107
+ // long-idle SSE in manual tests. The MCP client wrapping us
108
+ // (Cursor / Claude Desktop) will re-spawn the process if it
109
+ // needs the bridge again, so a clean exit is fine.
110
+ if (msg === "terminated" || msg.includes("ECONNRESET")) {
111
+ log.warn("bridge.sse_idle_closed", { reason: msg });
112
+ }
113
+ else {
114
+ log.error("bridge.sse_pump_error", { err: msg });
115
+ }
116
+ }
117
+ }
118
+ finally {
119
+ streamEnded = true;
120
+ // Wake any waiter so they see EOF.
121
+ wake();
122
+ }
123
+ })();
124
+ // Wait for the `endpoint` event (or first message) before returning.
125
+ while (!endpointResolved) {
126
+ if (streamEnded) {
127
+ controller.abort();
128
+ throw new Error(`MemWal relayer SSE handshake ended before endpoint event${streamError ? `: ${streamError}` : ""}`);
129
+ }
130
+ await new Promise((r) => (queueResolver = r));
131
+ }
132
+ const iter = {
133
+ async next() {
134
+ while (events.length === 0) {
135
+ if (controller.signal.aborted)
136
+ return { value: undefined, done: true };
137
+ await new Promise((r) => (queueResolver = r));
138
+ }
139
+ return { value: events.shift(), done: false };
140
+ },
141
+ };
142
+ // `endpointPath` may be relative (`/api/mcp/messages?sessionId=...`) or
143
+ // absolute. Make it absolute for `fetch()`.
144
+ const postUrl = endpointPath.startsWith("http")
145
+ ? endpointPath
146
+ : `${relayerUrl.replace(/\/+$/, "")}${endpointPath}`;
147
+ return {
148
+ postUrl,
149
+ iter,
150
+ abort: () => {
151
+ controller.abort();
152
+ void pump; // suppress unused warning
153
+ },
154
+ };
155
+ }
156
+ async function postMessage(postUrl, msg) {
157
+ const resp = await fetch(postUrl, {
158
+ method: "POST",
159
+ headers: { "content-type": "application/json" },
160
+ body: JSON.stringify(msg),
161
+ });
162
+ if (!resp.ok && resp.status !== 202) {
163
+ const body = await resp.text();
164
+ log.warn("bridge.post_non_ok", { status: resp.status, body: body.slice(0, 200) });
165
+ }
166
+ return resp.status;
167
+ }
168
+ function readStdinLines(onLine) {
169
+ return new Promise((resolve) => {
170
+ let buf = "";
171
+ process.stdin.setEncoding("utf8");
172
+ process.stdin.on("data", (chunk) => {
173
+ buf += chunk;
174
+ let nl;
175
+ while ((nl = buf.indexOf("\n")) >= 0) {
176
+ const line = buf.slice(0, nl).replace(/\r$/, "");
177
+ buf = buf.slice(nl + 1);
178
+ if (line.length > 0)
179
+ onLine(line);
180
+ }
181
+ });
182
+ process.stdin.on("end", () => resolve());
183
+ process.stdin.on("close", () => resolve());
184
+ });
185
+ }
186
+ function writeStdoutMessage(msg) {
187
+ process.stdout.write(JSON.stringify(msg) + "\n");
188
+ }
189
+ /**
190
+ * Open the SSE bridge and forward stdio ↔ relayer until stdin closes.
191
+ *
192
+ * On SSE drop (idle timeout in the Rust proxy / undici keep-alive / network
193
+ * blip), we transparently reopen the stream — the relayer issues a fresh
194
+ * sessionId, we route subsequent POSTs there. stdin stays open the whole
195
+ * time, so the MCP client (Cursor / Claude Desktop / etc.) never sees the
196
+ * reconnection.
197
+ */
198
+ export async function runBridge(creds) {
199
+ note(`Connecting to ${creds.relayerUrl}...`);
200
+ log.info("bridge.connecting", {
201
+ relayer: creds.relayerUrl,
202
+ accountId: creds.accountId,
203
+ delegate: creds.delegateAddress,
204
+ });
205
+ // Live handle to the current SSE stream — replaced whenever we reconnect.
206
+ let sse = await openSseStream(creds.relayerUrl, creds);
207
+ note(`Connected. Bridging stdio MCP ↔ ${creds.relayerUrl}`);
208
+ log.info("bridge.connected", { relayer: creds.relayerUrl });
209
+ let stdinClosed = false;
210
+ let reconnecting = false;
211
+ let reconnectAttempt = 0;
212
+ // In-flight requests pending a response. We replay them after a forced
213
+ // reconnect so a server-side session swap doesn't strand a tool call
214
+ // forever waiting for a reply that will never come. Notifications
215
+ // (no id) and responses (no method) are not tracked.
216
+ const inFlight = new Map();
217
+ async function reconnect(reason) {
218
+ if (stdinClosed || reconnecting)
219
+ return;
220
+ reconnecting = true;
221
+ try {
222
+ sse.abort();
223
+ }
224
+ catch {
225
+ /* already dead */
226
+ }
227
+ const backoff = Math.min(15_000, 500 * Math.pow(2, reconnectAttempt));
228
+ reconnectAttempt += 1;
229
+ log.warn("bridge.reconnecting", { reason, backoffMs: backoff, attempt: reconnectAttempt });
230
+ await new Promise((r) => setTimeout(r, backoff));
231
+ try {
232
+ sse = await openSseStream(creds.relayerUrl, creds);
233
+ reconnectAttempt = 0;
234
+ log.info("bridge.reconnected", {
235
+ relayer: creds.relayerUrl,
236
+ replayCount: inFlight.size,
237
+ });
238
+ // Replay any requests that haven't been answered yet against the
239
+ // fresh session. Iterate over a snapshot — postMessage is async
240
+ // and the SSE pump may delete entries concurrently as replies
241
+ // start arriving on the new session.
242
+ for (const [id, msg] of Array.from(inFlight.entries())) {
243
+ try {
244
+ const status = await postMessage(sse.postUrl, msg);
245
+ log.info("bridge.replayed", { id, status });
246
+ }
247
+ catch (err) {
248
+ log.error("bridge.replay_failed", {
249
+ id,
250
+ err: err instanceof Error ? err.message : String(err),
251
+ });
252
+ }
253
+ }
254
+ }
255
+ catch (err) {
256
+ log.error("bridge.reconnect_failed", {
257
+ err: err instanceof Error ? err.message : String(err),
258
+ });
259
+ // Try again on the next stdin message rather than spinning.
260
+ }
261
+ finally {
262
+ reconnecting = false;
263
+ }
264
+ }
265
+ // Server → client: stream SSE messages to stdout. Loop forever, restart
266
+ // pump on stream end (which means SSE got cut → we already reconnected).
267
+ const serverPump = (async () => {
268
+ while (!stdinClosed) {
269
+ try {
270
+ while (true) {
271
+ const { value, done } = await sse.iter.next();
272
+ if (done)
273
+ break;
274
+ // Clear in-flight tracking once the response lands.
275
+ if (value &&
276
+ (value.result !== undefined || value.error !== undefined) &&
277
+ value.id !== undefined &&
278
+ value.id !== null) {
279
+ inFlight.delete(value.id);
280
+ }
281
+ writeStdoutMessage(value);
282
+ }
283
+ }
284
+ catch (err) {
285
+ log.error("bridge.server_pump_error", {
286
+ err: err instanceof Error ? err.message : String(err),
287
+ });
288
+ }
289
+ if (stdinClosed)
290
+ break;
291
+ // Stream ended unexpectedly — reconnect and resume.
292
+ await reconnect("server-pump-eof");
293
+ }
294
+ })();
295
+ // Client → server: forward stdin lines as POST messages. On 404 (the
296
+ // relayer doesn't know our sessionId — happens right after a reconnect
297
+ // if the message races the new handshake), trigger another reconnect.
298
+ const clientPump = readStdinLines((line) => {
299
+ void (async () => {
300
+ try {
301
+ const msg = JSON.parse(line);
302
+ // Track requests (have both method and id) so we can replay
303
+ // them on reconnect. Notifications and responses are not
304
+ // tracked.
305
+ if (msg.method !== undefined &&
306
+ msg.id !== undefined &&
307
+ msg.id !== null) {
308
+ inFlight.set(msg.id, msg);
309
+ }
310
+ const status = await postMessage(sse.postUrl, msg);
311
+ if (status === 404) {
312
+ log.warn("bridge.session_stale", { sessionUrl: sse.postUrl });
313
+ // reconnect() itself replays in-flight against the fresh
314
+ // session, so no explicit per-message retry is needed.
315
+ await reconnect("post-404");
316
+ }
317
+ }
318
+ catch {
319
+ log.warn("bridge.stdin_parse_failed", { line: line.slice(0, 120) });
320
+ }
321
+ })();
322
+ }).then(() => {
323
+ stdinClosed = true;
324
+ sse.abort();
325
+ });
326
+ await Promise.race([serverPump, clientPump]);
327
+ sse.abort();
328
+ log.info("bridge.closed", {});
329
+ }
330
+ //# sourceMappingURL=bridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ declare function hex(bytes: Uint8Array): string;
2
+ declare function fromHex(s: string): Uint8Array;
3
+ export interface Keypair {
4
+ privateKeyHex: string;
5
+ publicKeyHex: string;
6
+ suiAddress: string;
7
+ }
8
+ /** Generate a fresh Ed25519 keypair and derive its Sui address. */
9
+ export declare function generateKeypair(): Promise<Keypair>;
10
+ /**
11
+ * Sui Ed25519 address: blake2b-256(0x00 || pubkey).
12
+ * The `0x00` is the Ed25519 scheme flag byte.
13
+ */
14
+ export declare function deriveSuiAddress(pubKey: Uint8Array): string;
15
+ export { hex as bytesToHex, fromHex as hexToBytes };
16
+ //# sourceMappingURL=crypto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAMA,iBAAS,GAAG,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAItC;AAED,iBAAS,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,UAAU,CAOtC;AAED,MAAM,WAAW,OAAO;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACtB;AAED,mEAAmE;AACnE,wBAAsB,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC,CAQxD;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAM3D;AAED,OAAO,EAAE,GAAG,IAAI,UAAU,EAAE,OAAO,IAAI,UAAU,EAAE,CAAC"}
package/dist/crypto.js ADDED
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Ed25519 helpers — pure-JS via @noble/ed25519.
3
+ */
4
+ import { getPublicKeyAsync, utils } from "@noble/ed25519";
5
+ import { blake2b } from "@noble/hashes/blake2.js";
6
+ function hex(bytes) {
7
+ return Array.from(bytes)
8
+ .map((b) => b.toString(16).padStart(2, "0"))
9
+ .join("");
10
+ }
11
+ function fromHex(s) {
12
+ const clean = s.startsWith("0x") ? s.slice(2) : s;
13
+ const out = new Uint8Array(clean.length / 2);
14
+ for (let i = 0; i < out.length; i++) {
15
+ out[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
16
+ }
17
+ return out;
18
+ }
19
+ /** Generate a fresh Ed25519 keypair and derive its Sui address. */
20
+ export async function generateKeypair() {
21
+ const seed = utils.randomPrivateKey(); // 32 bytes
22
+ const pub = await getPublicKeyAsync(seed);
23
+ return {
24
+ privateKeyHex: hex(seed),
25
+ publicKeyHex: hex(pub),
26
+ suiAddress: deriveSuiAddress(pub),
27
+ };
28
+ }
29
+ /**
30
+ * Sui Ed25519 address: blake2b-256(0x00 || pubkey).
31
+ * The `0x00` is the Ed25519 scheme flag byte.
32
+ */
33
+ export function deriveSuiAddress(pubKey) {
34
+ const buf = new Uint8Array(1 + pubKey.length);
35
+ buf[0] = 0x00;
36
+ buf.set(pubKey, 1);
37
+ const digest = blake2b.create({ dkLen: 32 }).update(buf).digest();
38
+ return "0x" + hex(digest);
39
+ }
40
+ export { hex as bytesToHex, fromHex as hexToBytes };
41
+ //# sourceMappingURL=crypto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAElD,SAAS,GAAG,CAAC,KAAiB;IAC1B,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;SACnB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;SAC3C,IAAI,CAAC,EAAE,CAAC,CAAC;AAClB,CAAC;AAED,SAAS,OAAO,CAAC,CAAS;IACtB,MAAM,KAAK,GAAG,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAClD,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC7C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAClC,GAAG,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACzD,CAAC;IACD,OAAO,GAAG,CAAC;AACf,CAAC;AAQD,mEAAmE;AACnE,MAAM,CAAC,KAAK,UAAU,eAAe;IACjC,MAAM,IAAI,GAAG,KAAK,CAAC,gBAAgB,EAAE,CAAC,CAAC,WAAW;IAClD,MAAM,GAAG,GAAG,MAAM,iBAAiB,CAAC,IAAI,CAAC,CAAC;IAC1C,OAAO;QACH,aAAa,EAAE,GAAG,CAAC,IAAI,CAAC;QACxB,YAAY,EAAE,GAAG,CAAC,GAAG,CAAC;QACtB,UAAU,EAAE,gBAAgB,CAAC,GAAG,CAAC;KACpC,CAAC;AACN,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAkB;IAC/C,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IAC9C,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IACd,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IACnB,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC;IAClE,OAAO,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC;AAC9B,CAAC;AAED,OAAO,EAAE,GAAG,IAAI,UAAU,EAAE,OAAO,IAAI,UAAU,EAAE,CAAC"}
@@ -0,0 +1,6 @@
1
+ export declare function main(argv?: string[]): Promise<void>;
2
+ export { loadCreds, saveCreds, clearCreds, credsPath } from "./auth.js";
3
+ export { loginFlow } from "./login.js";
4
+ export { runBridge } from "./bridge.js";
5
+ export type { MemWalCredentials } from "./auth.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA2FA,wBAAsB,IAAI,CAAC,IAAI,GAAE,MAAM,EAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CA2GhF;AA+DD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACxE,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,YAAY,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,240 @@
1
+ /**
2
+ * MemWal MCP — orchestrator.
3
+ *
4
+ * Boot sequence:
5
+ * 1. If `--logout` flag → wipe credentials.json and exit.
6
+ * 2. Load credentials from `~/.memwal/credentials.json`.
7
+ * 3. If missing → run `loginFlow()` (browser-based wallet sign-in).
8
+ * 4. Bridge stdio MCP ↔ remote SSE relayer using the loaded credentials.
9
+ * 5. On 401 (revoked key), the bridge wipes credentials before throwing
10
+ * — the next process spawn will re-trigger login.
11
+ */
12
+ import { clearCreds, credsPath, loadCreds } from "./auth.js";
13
+ import { runAuthRequiredServer } from "./auth-required.js";
14
+ import { runBridge } from "./bridge.js";
15
+ import { loginFlow } from "./login.js";
16
+ import { log, note } from "./logger.js";
17
+ /** Per-environment URL shortcuts. `--dev`/`--staging`/`--local` set both
18
+ * relayer + web in one flag. Explicit `--relayer` / `--web-url` override. */
19
+ const ENV_PRESETS = {
20
+ prod: { relayer: "https://relayer.memwal.ai", web: "https://memwal.ai" },
21
+ dev: { relayer: "https://relayer.dev.memwal.ai", web: "https://dev.memwal.ai" },
22
+ staging: { relayer: "https://relayer.staging.memwal.ai", web: "https://staging.memwal.ai" },
23
+ local: { relayer: "http://127.0.0.1:8000", web: "http://localhost:5173" },
24
+ };
25
+ function parseArgs(argv) {
26
+ const out = { help: false, logout: false, forceLogin: false };
27
+ for (let i = 0; i < argv.length; i++) {
28
+ const a = argv[i];
29
+ const next = () => argv[++i];
30
+ switch (a) {
31
+ case "--help":
32
+ case "-h":
33
+ out.help = true;
34
+ break;
35
+ case "--logout":
36
+ out.logout = true;
37
+ break;
38
+ case "--login":
39
+ case "login":
40
+ out.forceLogin = true;
41
+ break;
42
+ case "--prod":
43
+ case "--dev":
44
+ case "--staging":
45
+ case "--local": {
46
+ const preset = ENV_PRESETS[a.slice(2)];
47
+ if (preset) {
48
+ out.relayerUrl ??= preset.relayer;
49
+ out.webUrl ??= preset.web;
50
+ }
51
+ break;
52
+ }
53
+ case "--relayer":
54
+ case "--relayer-url":
55
+ out.relayerUrl = next();
56
+ break;
57
+ case "--web-url":
58
+ case "--web":
59
+ out.webUrl = next();
60
+ break;
61
+ case "--label":
62
+ out.label = next();
63
+ break;
64
+ default:
65
+ // Allow `--relayer=URL` and `--web-url=URL` forms too.
66
+ if (a?.startsWith("--relayer="))
67
+ out.relayerUrl = a.split("=", 2)[1];
68
+ else if (a?.startsWith("--web-url="))
69
+ out.webUrl = a.split("=", 2)[1];
70
+ else if (a?.startsWith("--label="))
71
+ out.label = a.split("=", 2)[1];
72
+ // Unknown flag: ignore silently.
73
+ break;
74
+ }
75
+ }
76
+ return out;
77
+ }
78
+ export async function main(argv = process.argv.slice(2)) {
79
+ const args = parseArgs(argv);
80
+ if (args.help) {
81
+ printHelp();
82
+ return;
83
+ }
84
+ if (args.logout) {
85
+ clearCreds();
86
+ note(`Credentials removed (${credsPath()}).`);
87
+ return;
88
+ }
89
+ if (args.forceLogin) {
90
+ clearCreds();
91
+ }
92
+ // Resolve URLs: CLI > env > default.
93
+ const relayerUrl = args.relayerUrl ?? process.env.MEMWAL_SERVER_URL ?? "https://relayer.memwal.ai";
94
+ const webUrl = args.webUrl ?? process.env.MEMWAL_WEB_URL ?? "https://memwal.ai";
95
+ // Label is the on-chain delegate-key name shown in the dashboard. We
96
+ // default to a generic value at registration time — the bridge updates
97
+ // it to the actual client's `clientInfo.name` ("Cursor", "Claude",
98
+ // "Antigravity", ...) after the first MCP `initialize` request. User
99
+ // can rename anytime from the dashboard.
100
+ const label = args.label ?? process.env.MEMWAL_CLIENT_LABEL ?? "MCP Client";
101
+ let creds = loadCreds();
102
+ if (creds && args.relayerUrl && creds.relayerUrl !== args.relayerUrl) {
103
+ // Caller wants a different relayer than what's saved. NEVER silently
104
+ // mutate the saved relayerUrl — a malicious config snippet (e.g.
105
+ // copy-pasted from a forum) carrying `--relayer https://attacker`
106
+ // would otherwise rewrite the saved creds so even subsequent runs
107
+ // without the flag ship the seed to the attacker (audit H4).
108
+ //
109
+ // In-memory override is fine for THIS process — the saved file is
110
+ // left untouched, so the next spawn falls back to the saved URL.
111
+ log.warn("creds.relayer_override.transient_only", {
112
+ saved: creds.relayerUrl,
113
+ override: args.relayerUrl,
114
+ });
115
+ note(`--relayer flag (${args.relayerUrl}) overrides saved relayer ` +
116
+ `(${creds.relayerUrl}) for THIS process only. The saved file ` +
117
+ `is not modified. To rotate the saved relayer, run ` +
118
+ `\`memwal-mcp login --logout\` then a fresh login.`);
119
+ creds = { ...creds, relayerUrl: args.relayerUrl };
120
+ }
121
+ const wasLoggedIn = !!creds;
122
+ if (!creds) {
123
+ if (!process.stdin.isTTY) {
124
+ // Spawned by an MCP client (Cursor / Claude Desktop / etc.).
125
+ // Instead of exiting — which makes the client UI show "Failed to
126
+ // start" with no actionable next step — boot a minimal stdio MCP
127
+ // server that responds to `initialize` and `tools/list` but
128
+ // returns an `isError: true` envelope on every `tools/call` with
129
+ // a friendly login instruction. The user sees the message
130
+ // INLINE in their chat, not buried in stderr logs.
131
+ //
132
+ // Phase B.5 (see plans/memwal-mcp-package-with-login.md) will
133
+ // replace this with the MCP OAuth flow so the client's host
134
+ // drives the browser dance and retries the tool call
135
+ // automatically — no client restart required.
136
+ log.warn("creds.missing_at_spawn.serving_auth_required", {
137
+ credsPath: credsPath(),
138
+ });
139
+ await runAuthRequiredServer();
140
+ return;
141
+ }
142
+ // TTY = manual invocation. Block on the browser flow as before.
143
+ note("MemWal MCP is not authorized yet — opening browser to connect your Sui wallet.");
144
+ creds = await loginFlow({ relayerUrl, webUrl, label });
145
+ note(`Authorized as ${creds.walletAddress.slice(0, 10)}...`);
146
+ }
147
+ else {
148
+ log.info("creds.loaded", {
149
+ accountId: creds.accountId,
150
+ delegateAddress: creds.delegateAddress,
151
+ label: creds.label,
152
+ relayerUrl: creds.relayerUrl,
153
+ });
154
+ }
155
+ // Manual invocation from a real terminal: print status and exit.
156
+ // Bridge mode only makes sense when an MCP client is attached on the
157
+ // other end of stdin (Cursor / Claude Desktop / ...). A TTY means the
158
+ // user is the one looking at stdout — there's no MCP client to bridge
159
+ // with, so hanging the process is the wrong default.
160
+ if (process.stdin.isTTY) {
161
+ note(``);
162
+ if (wasLoggedIn) {
163
+ note(`✅ Already authorized as ${creds.walletAddress.slice(0, 10)}...${creds.walletAddress.slice(-6)}`);
164
+ note(` Account: ${creds.accountId}`);
165
+ note(` Relayer: ${creds.relayerUrl}`);
166
+ }
167
+ else {
168
+ note(`✅ Login complete. Credentials saved to ${credsPath()}`);
169
+ }
170
+ note(``);
171
+ note(`Next: add this package to your MCP client config (Cursor / Claude Desktop / etc).`);
172
+ note(`See \`memwal-mcp --help\` for ready-to-paste snippets.`);
173
+ return;
174
+ }
175
+ await runBridge(creds);
176
+ }
177
+ function printHelp() {
178
+ const help = [
179
+ "memwal-mcp — MemWal Model Context Protocol client",
180
+ "",
181
+ "Usage:",
182
+ " memwal-mcp Run the MCP stdio server (default).",
183
+ " Triggers a one-time browser login",
184
+ " if ~/.memwal/credentials.json is",
185
+ " missing.",
186
+ " memwal-mcp login Force re-authentication (wipes",
187
+ " existing credentials and opens",
188
+ " browser).",
189
+ " memwal-mcp --logout Delete saved credentials without",
190
+ " re-running login.",
191
+ " memwal-mcp --help Show this help.",
192
+ "",
193
+ "Options:",
194
+ " --relayer <url> Override the relayer base URL.",
195
+ " Default: https://relayer.memwal.ai",
196
+ " (or saved value from credentials).",
197
+ " --web-url <url> Override the dashboard URL the",
198
+ " browser opens during login.",
199
+ " Default: https://memwal.ai",
200
+ " --label <text> Friendly delegate-key label",
201
+ " registered on-chain. Default:",
202
+ ' "MemWal MCP"',
203
+ "",
204
+ "Environment (equivalent to options):",
205
+ " MEMWAL_SERVER_URL same as --relayer",
206
+ " MEMWAL_WEB_URL same as --web-url",
207
+ " MEMWAL_CLIENT_LABEL same as --label",
208
+ " MEMWAL_MCP_DEBUG=1 Verbose stderr logging.",
209
+ "",
210
+ "Minimal MCP client config (Cursor, Claude Desktop, etc.):",
211
+ " {",
212
+ ' "mcpServers": {',
213
+ ' "memwal": {',
214
+ ' "command": "npx",',
215
+ ' "args": ["-y", "@mysten-incubation/memwal-mcp"]',
216
+ " }",
217
+ " }",
218
+ " }",
219
+ "",
220
+ "With explicit relayer override (e.g. dev environment):",
221
+ " {",
222
+ ' "mcpServers": {',
223
+ ' "memwal": {',
224
+ ' "command": "npx",',
225
+ ' "args": [',
226
+ ' "-y", "@mysten-incubation/memwal-mcp",',
227
+ ' "--relayer", "https://relayer.dev.memwal.ai"',
228
+ " ]",
229
+ " }",
230
+ " }",
231
+ " }",
232
+ "",
233
+ ].join("\n");
234
+ process.stderr.write(help + "\n");
235
+ }
236
+ // Re-exports — handy if someone wants to embed this in another tool.
237
+ export { loadCreds, saveCreds, clearCreds, credsPath } from "./auth.js";
238
+ export { loginFlow } from "./login.js";
239
+ export { runBridge } from "./bridge.js";
240
+ //# sourceMappingURL=index.js.map