@mysten-incubation/memwal-mcp 0.0.13 → 0.0.14-dev.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/dist/auth-required.d.ts +330 -0
- package/dist/auth-required.d.ts.map +1 -1
- package/dist/auth-required.js +83 -5
- package/dist/auth-required.js.map +1 -1
- package/dist/auth.d.ts +60 -0
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +135 -0
- package/dist/auth.js.map +1 -1
- package/dist/bridge.d.ts +4 -0
- package/dist/bridge.d.ts.map +1 -1
- package/dist/bridge.js +195 -22
- package/dist/bridge.js.map +1 -1
- package/dist/crypto.d.ts +9 -0
- package/dist/crypto.d.ts.map +1 -1
- package/dist/crypto.js +13 -1
- package/dist/crypto.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -1
- package/dist/index.js.map +1 -1
- package/dist/instructions.d.ts +4 -1
- package/dist/instructions.d.ts.map +1 -1
- package/dist/instructions.js +12 -1
- package/dist/instructions.js.map +1 -1
- package/dist/login.d.ts +5 -1
- package/dist/login.d.ts.map +1 -1
- package/dist/login.js +78 -11
- package/dist/login.js.map +1 -1
- package/dist/messages.d.ts +5 -0
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js +11 -4
- package/dist/messages.js.map +1 -1
- package/dist/recovery.d.ts +61 -0
- package/dist/recovery.d.ts.map +1 -0
- package/dist/recovery.js +227 -0
- package/dist/recovery.js.map +1 -0
- package/dist/streamable.d.ts +48 -0
- package/dist/streamable.d.ts.map +1 -0
- package/dist/streamable.js +136 -0
- package/dist/streamable.js.map +1 -0
- package/package.json +1 -1
package/dist/auth.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|
package/dist/auth.js
CHANGED
|
@@ -13,6 +13,7 @@ import { homedir } from "node:os";
|
|
|
13
13
|
import { randomUUID } from "node:crypto";
|
|
14
14
|
import { join, dirname, basename } from "node:path";
|
|
15
15
|
import { mkdirSync, readFileSync, writeFileSync, renameSync, unlinkSync, existsSync, } from "node:fs";
|
|
16
|
+
import { log } from "./logger.js";
|
|
16
17
|
const CREDS_FILE = "credentials.json";
|
|
17
18
|
/** Global, per-machine location. Always the fallback, and the only location
|
|
18
19
|
* before project-scoping existed — a machine with no project-local file keeps
|
|
@@ -334,4 +335,138 @@ function isValid(obj) {
|
|
|
334
335
|
typeof c.createdAt === "string" &&
|
|
335
336
|
c.version === 1);
|
|
336
337
|
}
|
|
338
|
+
/* ------------------------------------------------------------------------- *
|
|
339
|
+
* Pending login — write-ahead for the delegate keypair (WALM-332).
|
|
340
|
+
*
|
|
341
|
+
* The browser registers our delegate public key on-chain, which costs gas and
|
|
342
|
+
* cannot be undone, and only afterwards POSTs the callback that makes us save
|
|
343
|
+
* the matching private key. Losing this process in that window used to destroy
|
|
344
|
+
* the only copy of the key, stranding a paid registration nobody could use.
|
|
345
|
+
*
|
|
346
|
+
* So the keypair is written here BEFORE the browser is given the connect URL,
|
|
347
|
+
* and cleared once `saveCreds` has the key safely in `credentials.json`. A
|
|
348
|
+
* record that outlives its flow is recovered on next start.
|
|
349
|
+
* ------------------------------------------------------------------------- */
|
|
350
|
+
const PENDING_FILE = "login-pending.json";
|
|
351
|
+
/**
|
|
352
|
+
* How long a stranded record stays recoverable.
|
|
353
|
+
*
|
|
354
|
+
* Deliberately far longer than the 5-minute login timeout: the whole point is
|
|
355
|
+
* to survive a client restart, and a user who quits for the evening and comes
|
|
356
|
+
* back tomorrow is exactly the case worth covering. The cost of holding it is
|
|
357
|
+
* an unregistered key on disk, which grants nothing.
|
|
358
|
+
*/
|
|
359
|
+
export const PENDING_LOGIN_TTL_MS = 24 * 60 * 60_000;
|
|
360
|
+
/** Sits beside whichever credentials file `credsPath()` resolves to, so a
|
|
361
|
+
* project-local sign-in recovers into that same project. */
|
|
362
|
+
export function pendingLoginPath() {
|
|
363
|
+
return join(dirname(credsPath()), PENDING_FILE);
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Persist the pending keypair. Throws if it cannot.
|
|
367
|
+
*
|
|
368
|
+
* Deliberately NOT best-effort. The invariant this record exists to hold is
|
|
369
|
+
* that the delegate private key is on disk before its public half can reach a
|
|
370
|
+
* browser that will pay gas to register it. Swallowing the error would publish
|
|
371
|
+
* the connect URL while claiming a durability that does not exist — the
|
|
372
|
+
* original WALM-332 loss, now silent.
|
|
373
|
+
*
|
|
374
|
+
* Failing the login costs the user nothing: this file sits beside
|
|
375
|
+
* `credentials.json`, so a directory that cannot take it cannot take the
|
|
376
|
+
* credentials either. The same login would have failed at the callback anyway,
|
|
377
|
+
* one on-chain `add_delegate_key` later.
|
|
378
|
+
*/
|
|
379
|
+
export function savePendingLogin(pending) {
|
|
380
|
+
const path = pendingLoginPath();
|
|
381
|
+
try {
|
|
382
|
+
// The record holds the same plaintext private key as `credentials.json`,
|
|
383
|
+
// so it gets the same fresh-inode write.
|
|
384
|
+
writeSecretFile(path, JSON.stringify(pending, null, 2));
|
|
385
|
+
}
|
|
386
|
+
catch (err) {
|
|
387
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
388
|
+
log.error("login.pending.write_failed", { path, msg });
|
|
389
|
+
throw new Error(`Could not write the login write-ahead record at ${path}: ${msg}. ` +
|
|
390
|
+
`Refusing to start a sign-in that could register a delegate key on-chain ` +
|
|
391
|
+
`without being able to save it.`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* A pending record this login can adopt instead of minting a new keypair.
|
|
396
|
+
*
|
|
397
|
+
* `loginFlow` used to generate a fresh keypair every call and overwrite the
|
|
398
|
+
* record unconditionally. Recovery only runs at process start and is skipped
|
|
399
|
+
* for `--login` / `forceLogin`, so a timed-out login followed by `memwal_login`
|
|
400
|
+
* in the same process replaced the only copy of a key the browser may already
|
|
401
|
+
* have paid to register. Reusing the record keeps that key reclaimable.
|
|
402
|
+
*
|
|
403
|
+
* Scoped to the same relayer: a key registered against one relayer's account
|
|
404
|
+
* proves nothing to another, and `recovery` must never repoint. TTL and shape
|
|
405
|
+
* are already enforced by {@link loadPendingLogin}.
|
|
406
|
+
*/
|
|
407
|
+
export function reusablePendingLogin(relayerUrl) {
|
|
408
|
+
const pending = loadPendingLogin();
|
|
409
|
+
if (!pending)
|
|
410
|
+
return null;
|
|
411
|
+
if (pending.relayerUrl !== relayerUrl) {
|
|
412
|
+
log.warn("login.pending.relayer_changed", {
|
|
413
|
+
publicKey: pending.delegatePublicKeyHex,
|
|
414
|
+
from: pending.relayerUrl,
|
|
415
|
+
to: relayerUrl,
|
|
416
|
+
});
|
|
417
|
+
return null;
|
|
418
|
+
}
|
|
419
|
+
return pending;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Load a pending record, or null if there is none, it is malformed, or it has
|
|
423
|
+
* aged out. An expired record is deleted on read rather than left to linger.
|
|
424
|
+
*/
|
|
425
|
+
export function loadPendingLogin() {
|
|
426
|
+
const path = pendingLoginPath();
|
|
427
|
+
if (!existsSync(path))
|
|
428
|
+
return null;
|
|
429
|
+
let parsed;
|
|
430
|
+
try {
|
|
431
|
+
parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
432
|
+
}
|
|
433
|
+
catch {
|
|
434
|
+
clearPendingLogin();
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
if (!isValidPending(parsed)) {
|
|
438
|
+
clearPendingLogin();
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
const age = Date.now() - Date.parse(parsed.createdAt);
|
|
442
|
+
if (!Number.isFinite(age) || age > PENDING_LOGIN_TTL_MS) {
|
|
443
|
+
clearPendingLogin();
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
return parsed;
|
|
447
|
+
}
|
|
448
|
+
/** Remove the pending record. Safe to call when there isn't one. */
|
|
449
|
+
export function clearPendingLogin() {
|
|
450
|
+
try {
|
|
451
|
+
const path = pendingLoginPath();
|
|
452
|
+
if (existsSync(path))
|
|
453
|
+
unlinkSync(path);
|
|
454
|
+
}
|
|
455
|
+
catch {
|
|
456
|
+
/* best effort */
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
function isValidPending(obj) {
|
|
460
|
+
if (!obj || typeof obj !== "object")
|
|
461
|
+
return false;
|
|
462
|
+
const p = obj;
|
|
463
|
+
return (typeof p.delegatePrivateKey === "string" &&
|
|
464
|
+
/^[0-9a-fA-F]{64}$/.test(p.delegatePrivateKey) &&
|
|
465
|
+
typeof p.delegatePublicKeyHex === "string" &&
|
|
466
|
+
/^[0-9a-fA-F]{64}$/.test(p.delegatePublicKeyHex) &&
|
|
467
|
+
typeof p.delegateAddress === "string" &&
|
|
468
|
+
typeof p.relayerUrl === "string" &&
|
|
469
|
+
typeof p.createdAt === "string" &&
|
|
470
|
+
p.version === 1);
|
|
471
|
+
}
|
|
337
472
|
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EACH,SAAS,EACT,YAAY,EACZ,aAAa,EACb,UAAU,EACV,UAAU,EACV,UAAU,GACb,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|
package/dist/bridge.d.ts
CHANGED
|
@@ -59,6 +59,10 @@ export declare function annotateHealthResult(result: {
|
|
|
59
59
|
isError?: unknown;
|
|
60
60
|
}, relayerUrl: string): void;
|
|
61
61
|
export declare function applyDefaultNamespace(msg: RpcMessage, namespace?: string): RpcMessage;
|
|
62
|
+
/** Reply for a `tools/call` naming a tool the connected relayer does not
|
|
63
|
+
* serve. Names what IS on offer, because the agent's next move is to pick one
|
|
64
|
+
* of those — "unknown tool" alone leaves it guessing or retrying. */
|
|
65
|
+
export declare function unknownToolText(name: string, available: string[]): string;
|
|
62
66
|
interface RpcMessage {
|
|
63
67
|
jsonrpc: "2.0";
|
|
64
68
|
id?: number | string | null;
|
package/dist/bridge.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAanD,OAAO,EAIH,KAAK,gBAAgB,EACxB,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAanD,OAAO,EAIH,KAAK,gBAAgB,EACxB,MAAM,eAAe,CAAC;AAIvB;;gFAEgF;AAChF,MAAM,WAAW,YAAY;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd;;;8BAG0B;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAeD;;;;;;;;;;GAUG;AACH;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAChC,MAAM,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,EAChD,UAAU,EAAE,MAAM,GACnB,IAAI,CAaN;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,UAAU,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,UAAU,CAerF;AA6FD;;qEAEqE;AACrE,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,MAAM,CAOzE;AAsOD,UAAU,UAAU;IAChB,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;CACnB;AAokBD,oEAAoE;AACpE,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAEpE;AA8BD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,SAAS,CAC3B,YAAY,EAAE,iBAAiB,EAC/B,MAAM,EAAE,YAAY;AACpB;;;;yEAIyE;AACzE,YAAY,GAAE,MAAM,EAAO,GAC5B,OAAO,CAAC,IAAI,CAAC,CA4lDf"}
|
package/dist/bridge.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { clearCreds, credsPath, loadCreds } from "./auth.js";
|
|
1
|
+
import { clearCreds, clearPendingLogin, credsPath, loadCreds } from "./auth.js";
|
|
2
2
|
import { TOOL_DEFINITIONS } from "./auth-required.js";
|
|
3
3
|
import { clientInfoHeaders, lastClientInfoHeaders, rememberInitializeClientInfo, } from "./client-info.js";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
@@ -7,6 +7,7 @@ import { PROACTIVE_INSTRUCTIONS } from "./instructions.js";
|
|
|
7
7
|
import { startOrReuseLoginFlow, resolveLoginTimeoutMs } from "./login.js";
|
|
8
8
|
import { log, note } from "./logger.js";
|
|
9
9
|
import { loginPrompt, loginSuccessNotice, loginSuccessNotification, } from "./messages.js";
|
|
10
|
+
import { openStreamableSession, resolveTransport } from "./streamable.js";
|
|
10
11
|
import { MEMWAL_MCP_VERSION } from "./version.js";
|
|
11
12
|
/** Memory tools that take a `namespace` argument. `memwal_remember`,
|
|
12
13
|
* `memwal_remember_bulk`, `memwal_recall`, and `memwal_analyze` treat it as
|
|
@@ -151,6 +152,15 @@ const SIGNED_OUT_FAILURE = {
|
|
|
151
152
|
* "connection dropped, please retry", which is advice that cannot work. Name
|
|
152
153
|
* the cause and the way back in instead (GH #365 / WALM-602). */
|
|
153
154
|
const UNAUTHORIZED_TEXT = "❌ Walrus Memory rejected the saved credentials (HTTP 401). The delegate key may have been revoked or is no longer registered on this account. Call `memwal_login` to sign in again — saved credentials were NOT modified.";
|
|
155
|
+
/** Reply for a `tools/call` naming a tool the connected relayer does not
|
|
156
|
+
* serve. Names what IS on offer, because the agent's next move is to pick one
|
|
157
|
+
* of those — "unknown tool" alone leaves it guessing or retrying. */
|
|
158
|
+
export function unknownToolText(name, available) {
|
|
159
|
+
return (`❌ \`${name}\` is not a tool this Walrus Memory server offers. ` +
|
|
160
|
+
`Available: ${available.join(", ")}. ` +
|
|
161
|
+
`Your tool list is stale — re-read \`tools/list\` and use one of those instead. ` +
|
|
162
|
+
`Nothing ran, so nothing was saved or changed.`);
|
|
163
|
+
}
|
|
154
164
|
/** `failRequest` options for every credentials-rejected refusal, so one refused
|
|
155
165
|
* at handshake time and one refused on arrival afterwards read identically. */
|
|
156
166
|
const UNAUTHORIZED_FAILURE = {
|
|
@@ -174,6 +184,10 @@ const LOCAL_TOOLS_LIST = {
|
|
|
174
184
|
...LOCAL_TOOL_DEFINITIONS,
|
|
175
185
|
],
|
|
176
186
|
};
|
|
187
|
+
/** Names advertised at cold start (baseline relayer + locally-served). A
|
|
188
|
+
* `tools/call` for anything else is refused until an upstream `tools/list`
|
|
189
|
+
* has been seen — fail-closed, not fail-open. */
|
|
190
|
+
const COLD_START_TOOL_NAMES = new Set(LOCAL_TOOLS_LIST.tools.map((t) => t.name));
|
|
177
191
|
const URL_READY_TIMEOUT_MS = 5_000;
|
|
178
192
|
/** Maximum silence we tolerate on the SSE stream before assuming the
|
|
179
193
|
* relayer-side session has gone dead. The relayer sends keepalive events
|
|
@@ -202,6 +216,56 @@ const SLOWEST_SERVER_TOOL_MS = 180_000;
|
|
|
202
216
|
const DEFAULT_CALL_TIMEOUT_MS = SLOWEST_SERVER_TOOL_MS + 60_000;
|
|
203
217
|
/** An override below this is a mistake, not an intent. */
|
|
204
218
|
const MIN_CALL_TIMEOUT_MS = 1_000;
|
|
219
|
+
/** Longest a given tool can legitimately take server-side, keyed by tool name.
|
|
220
|
+
*
|
|
221
|
+
* `DEFAULT_CALL_TIMEOUT_MS` is sized for `memwal_analyze`, the slowest tool
|
|
222
|
+
* there is. Applying that one number to every call means a request whose reply
|
|
223
|
+
* is lost — the relayer answered, the stream dropped before it arrived — keeps
|
|
224
|
+
* the agent blocked for 240s even when the tool could not still be working.
|
|
225
|
+
* Users read that as a hang and reload the client.
|
|
226
|
+
*
|
|
227
|
+
* Each entry is the ceiling the matching tool enforces on itself in
|
|
228
|
+
* `services/server/scripts/mcp/tools/`: `MAX_REMEMBER_WAIT_MS` for
|
|
229
|
+
* `memwal_remember` (its default wait is 0 — it returns at accept — but an
|
|
230
|
+
* operator can raise `MEMWAL_MCP_REMEMBER_WAIT_MS` up to that cap),
|
|
231
|
+
* `MAX_STATUS_WAIT_MS` for `memwal_remember_status`, and the fixed `timeoutMs`
|
|
232
|
+
* the bulk and analyze tools pass to the SDK. Keep them in lockstep: a value
|
|
233
|
+
* below a tool's real ceiling abandons healthy work. Unlisted tools keep the
|
|
234
|
+
* default. */
|
|
235
|
+
const TOOL_DEADLINE_MS = {
|
|
236
|
+
memwal_remember: 90_000,
|
|
237
|
+
memwal_remember_status: 60_000,
|
|
238
|
+
memwal_remember_bulk: 120_000,
|
|
239
|
+
memwal_analyze: SLOWEST_SERVER_TOOL_MS,
|
|
240
|
+
};
|
|
241
|
+
/** Absorbs relayer + transport overhead on top of a tool's own ceiling. The
|
|
242
|
+
* sidecar answers at its deadline with a result or an error envelope rather
|
|
243
|
+
* than going quiet, so the reply is one network hop behind it; 30s is many
|
|
244
|
+
* times that. Cutting a merely-late reply off early is the expensive mistake —
|
|
245
|
+
* the agent would retry a write that actually landed. */
|
|
246
|
+
const ORPHAN_HEADROOM_MS = 30_000;
|
|
247
|
+
/** Tool name for a `tools/call`, or null for any other JSON-RPC method. */
|
|
248
|
+
function toolNameOf(msg) {
|
|
249
|
+
if (msg.method !== "tools/call")
|
|
250
|
+
return null;
|
|
251
|
+
const params = msg.params;
|
|
252
|
+
if (params == null || typeof params !== "object")
|
|
253
|
+
return null;
|
|
254
|
+
const name = params.name;
|
|
255
|
+
return typeof name === "string" ? name : null;
|
|
256
|
+
}
|
|
257
|
+
/** Deadline for one tracked request. A tool with a known ceiling gets that
|
|
258
|
+
* plus headroom; everything else keeps the global default. An explicit
|
|
259
|
+
* `MEMWAL_MCP_CALL_TIMEOUT_MS` pins every call, so tests still drive expiry
|
|
260
|
+
* from one knob. */
|
|
261
|
+
function resolveDeadlineMs(msg) {
|
|
262
|
+
const fallback = resolveCallTimeoutMs();
|
|
263
|
+
if (process.env.MEMWAL_MCP_CALL_TIMEOUT_MS)
|
|
264
|
+
return fallback;
|
|
265
|
+
const tool = toolNameOf(msg);
|
|
266
|
+
const ceiling = tool === null ? undefined : TOOL_DEADLINE_MS[tool];
|
|
267
|
+
return ceiling === undefined ? fallback : ceiling + ORPHAN_HEADROOM_MS;
|
|
268
|
+
}
|
|
205
269
|
/** Without a cap, a long deadline drifts by a third of itself. */
|
|
206
270
|
const MAX_ORPHAN_SWEEP_MS = 5_000;
|
|
207
271
|
/** How long one request might sit unanswered. The idle watchdog above only sees
|
|
@@ -329,6 +393,23 @@ function mcpAuthHeaders(creds, extra = {}) {
|
|
|
329
393
|
...extra,
|
|
330
394
|
};
|
|
331
395
|
}
|
|
396
|
+
/**
|
|
397
|
+
* Open a relayer session on the configured transport.
|
|
398
|
+
*
|
|
399
|
+
* `MEMWAL_MCP_TRANSPORT=http` dials the Streamable HTTP endpoint, which
|
|
400
|
+
* answers a call on the same request instead of splitting POST from reply.
|
|
401
|
+
* Default stays SSE until the new path has production mileage.
|
|
402
|
+
*/
|
|
403
|
+
async function openRelaySession(relayerUrl, creds, extraHeaders = {}) {
|
|
404
|
+
if (resolveTransport(process.env.MEMWAL_MCP_TRANSPORT) === "http") {
|
|
405
|
+
// `postUrl` is logging-only on this path; the session owns its
|
|
406
|
+
// endpoint. A plain cast, not `as unknown as` — the two shapes must
|
|
407
|
+
// stay structurally compatible, and a widening cast would hide it if
|
|
408
|
+
// they ever stopped being.
|
|
409
|
+
return (await openStreamableSession(relayerUrl, creds, extraHeaders));
|
|
410
|
+
}
|
|
411
|
+
return openSseStream(relayerUrl, creds, extraHeaders);
|
|
412
|
+
}
|
|
332
413
|
async function openSseStream(relayerUrl, creds, extraHeaders = {}) {
|
|
333
414
|
const connectTimeoutMs = resolveConnectTimeoutMs();
|
|
334
415
|
// One shared budget for the WHOLE attempt: the compatibility check (GET
|
|
@@ -576,6 +657,7 @@ async function openSseStream(relayerUrl, creds, extraHeaders = {}) {
|
|
|
576
657
|
: `${relayerUrl.replace(/\/+$/, "")}${endpointPath}`;
|
|
577
658
|
return {
|
|
578
659
|
postUrl,
|
|
660
|
+
send: (msg, sendCreds, extra) => postMessage(postUrl, msg, sendCreds, extra),
|
|
579
661
|
iter,
|
|
580
662
|
abort: () => {
|
|
581
663
|
controller.abort();
|
|
@@ -660,7 +742,13 @@ async function handleLocalLogin(config, onCredentials) {
|
|
|
660
742
|
}),
|
|
661
743
|
},
|
|
662
744
|
});
|
|
663
|
-
},
|
|
745
|
+
},
|
|
746
|
+
// This tool call has already returned "here is your URL, go sign in",
|
|
747
|
+
// so a later failure has no response left to ride home on. Without an
|
|
748
|
+
// out-of-band notification the agent sits waiting on a flow that is
|
|
749
|
+
// already dead. MCP logging notifications are fire-and-forget and safe
|
|
750
|
+
// to emit at any point in the session.
|
|
751
|
+
(err) => {
|
|
664
752
|
const msg = err instanceof Error ? err.message : String(err);
|
|
665
753
|
log.warn("memwal_login.bridge.failed", { msg });
|
|
666
754
|
writeStdoutMessage({
|
|
@@ -669,7 +757,14 @@ async function handleLocalLogin(config, onCredentials) {
|
|
|
669
757
|
params: {
|
|
670
758
|
level: "warning",
|
|
671
759
|
logger: "memwal-mcp",
|
|
672
|
-
|
|
760
|
+
// The reclaim is only possible because of the write-ahead
|
|
761
|
+
// record (WALM-332): a key the browser already paid to
|
|
762
|
+
// register is no longer lost with the process. A retry
|
|
763
|
+
// cannot help that key, since it reuses it and the
|
|
764
|
+
// dashboard cannot register it twice.
|
|
765
|
+
data: `Walrus Memory sign-in did not complete: ${msg}. Existing credentials are ` +
|
|
766
|
+
`unchanged. If you approved the wallet step, the next start reclaims that ` +
|
|
767
|
+
`key; otherwise call memwal_login again to retry.`,
|
|
673
768
|
},
|
|
674
769
|
});
|
|
675
770
|
});
|
|
@@ -702,6 +797,15 @@ async function handleLocalLogin(config, onCredentials) {
|
|
|
702
797
|
function handleLocalLogout() {
|
|
703
798
|
try {
|
|
704
799
|
const cleared = clearCreds();
|
|
800
|
+
// Explicit sign-out discards the write-ahead record too. Without this
|
|
801
|
+
// an interrupted re-login leaves `login-pending.json` behind, and the
|
|
802
|
+
// next start's `recoverPendingLogin` signs the user straight back in.
|
|
803
|
+
//
|
|
804
|
+
// Kept out of `clearCreds()` so only a deliberate sign-out discards a
|
|
805
|
+
// key that may still be reclaimable. `clearCreds` is exported, and a
|
|
806
|
+
// 401 deliberately does NOT wipe credentials (see the relayer-401
|
|
807
|
+
// handling above), so the two are not the same decision.
|
|
808
|
+
clearPendingLogin();
|
|
705
809
|
log.info("memwal_logout.bridge.success", {
|
|
706
810
|
removedPath: cleared.removedPath ?? null,
|
|
707
811
|
fallbackPath: cleared.fallbackPath ?? null,
|
|
@@ -917,7 +1021,7 @@ pendingLines = []) {
|
|
|
917
1021
|
postChain = run.then(() => undefined, () => undefined);
|
|
918
1022
|
return run;
|
|
919
1023
|
}
|
|
920
|
-
function postIfCurrent(epoch,
|
|
1024
|
+
function postIfCurrent(epoch, send, msg, postCreds) {
|
|
921
1025
|
if (epoch !== sessionEpoch)
|
|
922
1026
|
return Promise.resolve(0);
|
|
923
1027
|
// Marked before the await, not after. Once the POST is issued we can
|
|
@@ -929,7 +1033,7 @@ pendingLines = []) {
|
|
|
929
1033
|
if (tracked)
|
|
930
1034
|
tracked.sent = true;
|
|
931
1035
|
}
|
|
932
|
-
return
|
|
1036
|
+
return send(msg, postCreds, extraHeaders).then((status) => {
|
|
933
1037
|
// 404 is the relayer saying that session does not exist, so the
|
|
934
1038
|
// message was discarded rather than routed: it provably did not
|
|
935
1039
|
// run, and the request goes back to being never-sent.
|
|
@@ -1062,6 +1166,13 @@ pendingLines = []) {
|
|
|
1062
1166
|
* locally-served `memwal_login` + `memwal_logout` tools so the MCP
|
|
1063
1167
|
* client surfaces them in its tool palette. */
|
|
1064
1168
|
const pendingListIds = new Set();
|
|
1169
|
+
/** Tool names the CONNECTED relayer advertised on its last `tools/list`,
|
|
1170
|
+
* minus the ones we serve locally. Empty until the client has listed tools
|
|
1171
|
+
* at least once over the *current* session. `reconnect()` clears it: a
|
|
1172
|
+
* stale allow would re-forward a tool the new relayer does not serve
|
|
1173
|
+
* (GH #928), and a stale deny would refuse a tool it does. Until the
|
|
1174
|
+
* next upstream list, the gate allows only the cold-start floor. */
|
|
1175
|
+
const upstreamToolNames = new Set();
|
|
1065
1176
|
/** IDs of forwarded `memwal_health` calls, each against the relayer URL the
|
|
1066
1177
|
* call went out on. Captured at send time rather than read at reply time so
|
|
1067
1178
|
* a reconnect that swapped credentials mid-flight cannot label the answer
|
|
@@ -1152,7 +1263,7 @@ pendingLines = []) {
|
|
|
1152
1263
|
// with. Belt-and-braces against `loggedOut` alone.
|
|
1153
1264
|
if (!openingCreds)
|
|
1154
1265
|
break;
|
|
1155
|
-
const candidate = await
|
|
1266
|
+
const candidate = await openRelaySession(openingCreds.relayerUrl, openingCreds, connectHeaders());
|
|
1156
1267
|
// Logout can also land mid-handshake. Same reasoning as the
|
|
1157
1268
|
// stale-credentials case below, except there is no new key
|
|
1158
1269
|
// to reconnect with — drop the session and stop.
|
|
@@ -1182,6 +1293,15 @@ pendingLines = []) {
|
|
|
1182
1293
|
throttleNoticed = false;
|
|
1183
1294
|
clearHandshakeFailure();
|
|
1184
1295
|
endConnectEpisode();
|
|
1296
|
+
// This session has not advertised anything yet. Keep the
|
|
1297
|
+
// previous set and a login that swapped relayerUrl would
|
|
1298
|
+
// re-forward a tool the new one does not serve, or refuse
|
|
1299
|
+
// one it does, until the client happened to re-list.
|
|
1300
|
+
upstreamToolNames.clear();
|
|
1301
|
+
writeStdoutMessage({
|
|
1302
|
+
jsonrpc: "2.0",
|
|
1303
|
+
method: "notifications/tools/list_changed",
|
|
1304
|
+
});
|
|
1185
1305
|
// An accepted handshake retires any earlier rejection —
|
|
1186
1306
|
// `memwal_login` re-registers a key and lands here, not on
|
|
1187
1307
|
// the background connect's publish path, so clearing only
|
|
@@ -1236,8 +1356,8 @@ pendingLines = []) {
|
|
|
1236
1356
|
expectSuppressedReply(msg.id);
|
|
1237
1357
|
}
|
|
1238
1358
|
const epoch = sessionEpoch;
|
|
1239
|
-
const
|
|
1240
|
-
const status = await enqueuePost(() => postIfCurrent(epoch,
|
|
1359
|
+
const send = sse.send;
|
|
1360
|
+
const status = await enqueuePost(() => postIfCurrent(epoch, send, msg, openingCreds));
|
|
1241
1361
|
log.info("bridge.replayed", { id, status });
|
|
1242
1362
|
}
|
|
1243
1363
|
catch (err) {
|
|
@@ -1550,6 +1670,16 @@ pendingLines = []) {
|
|
|
1550
1670
|
// duplicate tool name. Mirrors LOCAL_TOOLS_LIST.
|
|
1551
1671
|
const upstream = result.tools.filter((t) => !LOCAL_TOOL_NAMES.has(t.name ?? ""));
|
|
1552
1672
|
result.tools = [...upstream, ...LOCAL_TOOL_DEFINITIONS];
|
|
1673
|
+
// Record what this relayer actually serves. A
|
|
1674
|
+
// later call for a name absent here is answered
|
|
1675
|
+
// locally instead of being forwarded into a wait
|
|
1676
|
+
// no reply will ever end.
|
|
1677
|
+
upstreamToolNames.clear();
|
|
1678
|
+
for (const t of upstream) {
|
|
1679
|
+
if (typeof t.name === "string" && t.name !== "") {
|
|
1680
|
+
upstreamToolNames.add(t.name);
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1553
1683
|
}
|
|
1554
1684
|
}
|
|
1555
1685
|
if (value &&
|
|
@@ -1720,6 +1850,45 @@ pendingLines = []) {
|
|
|
1720
1850
|
failRequest(msg, "credentials rejected", UNAUTHORIZED_FAILURE);
|
|
1721
1851
|
return;
|
|
1722
1852
|
}
|
|
1853
|
+
// Version skew: this bridge ships on npm and updates itself,
|
|
1854
|
+
// while a relayer ships per environment and does not, so the
|
|
1855
|
+
// bridge is routinely newer than the server it dials. A tool
|
|
1856
|
+
// named in initialize instructions or a cached list can be
|
|
1857
|
+
// missing from the session that actually came up —
|
|
1858
|
+
// `memwal_remember_status` against a prod relayer, which is
|
|
1859
|
+
// GH #928. Forwarding that call parks it in `inFlight` until
|
|
1860
|
+
// the orphan sweeper's deadline (60s + 30s headroom for that
|
|
1861
|
+
// tool), and the user reads the 90s as a hang.
|
|
1862
|
+
//
|
|
1863
|
+
// Fail closed. Before any upstream `tools/list`, only the
|
|
1864
|
+
// cold-start floor is callable. After one, only what that
|
|
1865
|
+
// relayer advertised (plus locally-served tools). A stale
|
|
1866
|
+
// tool list is not a transport fault: say so now, while the
|
|
1867
|
+
// agent can still act on it.
|
|
1868
|
+
if (msg.method === "tools/call" && msg.id != null) {
|
|
1869
|
+
const called = msg.params?.name;
|
|
1870
|
+
if (typeof called === "string" && !LOCAL_TOOL_NAMES.has(called)) {
|
|
1871
|
+
const known = upstreamToolNames.size > 0
|
|
1872
|
+
? upstreamToolNames
|
|
1873
|
+
: COLD_START_TOOL_NAMES;
|
|
1874
|
+
if (!known.has(called)) {
|
|
1875
|
+
const available = [
|
|
1876
|
+
...(upstreamToolNames.size > 0
|
|
1877
|
+
? [...upstreamToolNames, ...LOCAL_TOOL_NAMES]
|
|
1878
|
+
: COLD_START_TOOL_NAMES),
|
|
1879
|
+
].sort();
|
|
1880
|
+
log.warn("bridge.tool_not_served", {
|
|
1881
|
+
tool: called,
|
|
1882
|
+
window: upstreamToolNames.size > 0 ? "upstream" : "cold-start",
|
|
1883
|
+
});
|
|
1884
|
+
failRequest(msg, "tool not served", {
|
|
1885
|
+
toolText: unknownToolText(called, available),
|
|
1886
|
+
errorMessage: `${called} is not served by this Walrus Memory relayer`,
|
|
1887
|
+
});
|
|
1888
|
+
return;
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1723
1892
|
// Fill in the configured default namespace for memory tool
|
|
1724
1893
|
// calls that didn't pass one. Mutates msg in place so the
|
|
1725
1894
|
// forwarded — and any replayed-on-reconnect — copy carries it.
|
|
@@ -1742,7 +1911,11 @@ pendingLines = []) {
|
|
|
1742
1911
|
if (msg.method !== undefined &&
|
|
1743
1912
|
msg.id !== undefined &&
|
|
1744
1913
|
msg.id !== null) {
|
|
1745
|
-
inFlight.set(msg.id, {
|
|
1914
|
+
inFlight.set(msg.id, {
|
|
1915
|
+
msg,
|
|
1916
|
+
startedAt: Date.now(),
|
|
1917
|
+
deadlineMs: resolveDeadlineMs(msg),
|
|
1918
|
+
});
|
|
1746
1919
|
}
|
|
1747
1920
|
// Relayer session not up yet, OR the post-connect flush is still
|
|
1748
1921
|
// draining — buffer so this request stays behind everything that
|
|
@@ -1782,9 +1955,9 @@ pendingLines = []) {
|
|
|
1782
1955
|
return;
|
|
1783
1956
|
}
|
|
1784
1957
|
const epoch = sessionEpoch;
|
|
1785
|
-
const
|
|
1958
|
+
const send = sse.send;
|
|
1786
1959
|
const postCreds = creds;
|
|
1787
|
-
const status = await enqueuePost(() => postIfCurrent(epoch,
|
|
1960
|
+
const status = await enqueuePost(() => postIfCurrent(epoch, send, msg, postCreds));
|
|
1788
1961
|
if (status === 404) {
|
|
1789
1962
|
log.warn("bridge.session_stale", { sessionUrl: sse.postUrl });
|
|
1790
1963
|
// reconnect() itself replays in-flight against the fresh
|
|
@@ -1822,9 +1995,9 @@ pendingLines = []) {
|
|
|
1822
1995
|
const msg = pendingForward.shift();
|
|
1823
1996
|
try {
|
|
1824
1997
|
const epoch = sessionEpoch;
|
|
1825
|
-
const
|
|
1998
|
+
const send = sse.send;
|
|
1826
1999
|
const postCreds = creds;
|
|
1827
|
-
const status = await enqueuePost(() => postIfCurrent(epoch,
|
|
2000
|
+
const status = await enqueuePost(() => postIfCurrent(epoch, send, msg, postCreds));
|
|
1828
2001
|
if (status === 404) {
|
|
1829
2002
|
// Stale session right after connect. EVERY id-bearing
|
|
1830
2003
|
// request is in `inFlight`, and ANY reconnect — this
|
|
@@ -1984,13 +2157,6 @@ pendingLines = []) {
|
|
|
1984
2157
|
"memwal_remember_bulk",
|
|
1985
2158
|
"memwal_analyze",
|
|
1986
2159
|
]);
|
|
1987
|
-
/** Name of the tool a tracked request was calling, when it was one. */
|
|
1988
|
-
function toolNameOf(msg) {
|
|
1989
|
-
if (msg.method !== "tools/call")
|
|
1990
|
-
return null;
|
|
1991
|
-
const params = msg.params;
|
|
1992
|
-
return typeof params?.name === "string" ? params.name : null;
|
|
1993
|
-
}
|
|
1994
2160
|
function expiredRequestReport(neverSent, now, tool) {
|
|
1995
2161
|
if (!neverSent) {
|
|
1996
2162
|
// The request reached the relayer. What is missing is the reply,
|
|
@@ -2074,7 +2240,14 @@ pendingLines = []) {
|
|
|
2074
2240
|
// keeps the full deadline, because there a premature failure
|
|
2075
2241
|
// invites a duplicate write.
|
|
2076
2242
|
const handshakeIsStalled = handshakeStalledMs !== null && handshakeStalledMs > stalledHandshakeMs;
|
|
2077
|
-
|
|
2243
|
+
// `entry.deadlineMs` is this tool's own ceiling plus headroom, not
|
|
2244
|
+
// the global one sized for the slowest tool — so a `memwal_remember`
|
|
2245
|
+
// whose reply is lost is answered at 120s instead of 240s. The
|
|
2246
|
+
// stalled-handshake shortcut still wins when it is tighter, but can
|
|
2247
|
+
// never extend a tool past its own deadline.
|
|
2248
|
+
const deadlineMs = neverSent && handshakeIsStalled
|
|
2249
|
+
? Math.min(stalledHandshakeMs, entry.deadlineMs)
|
|
2250
|
+
: entry.deadlineMs;
|
|
2078
2251
|
if (elapsedMs <= deadlineMs)
|
|
2079
2252
|
continue;
|
|
2080
2253
|
// Built only for what actually expired: this walks `pendingForward`
|
|
@@ -2148,7 +2321,7 @@ pendingLines = []) {
|
|
|
2148
2321
|
}
|
|
2149
2322
|
const openingGeneration = credentialGeneration;
|
|
2150
2323
|
try {
|
|
2151
|
-
const candidate = await
|
|
2324
|
+
const candidate = await openRelaySession(creds.relayerUrl, creds, connectHeaders());
|
|
2152
2325
|
if (stdinClosed) {
|
|
2153
2326
|
candidate.abort();
|
|
2154
2327
|
break;
|