@usewhisper/mcp-server 2.14.0 → 2.16.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.
Files changed (2) hide show
  1. package/dist/server.js +1153 -515
  2. package/package.json +1 -1
package/dist/server.js CHANGED
@@ -3,11 +3,11 @@
3
3
  // ../src/mcp/server.ts
4
4
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
5
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
- import { z } from "zod";
6
+ import { z as z2 } from "zod";
7
7
  import { execSync, spawnSync } from "child_process";
8
8
  import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync, appendFileSync } from "fs";
9
9
  import { join, relative, extname, normalize as normalizePath, resolve as resolvePath } from "path";
10
- import { homedir } from "os";
10
+ import { homedir, userInfo } from "os";
11
11
  import { createHash, randomUUID } from "crypto";
12
12
 
13
13
  // ../src/sdk/core/telemetry.ts
@@ -127,6 +127,8 @@ var RuntimeClientError = class extends Error {
127
127
  code;
128
128
  details;
129
129
  traceId;
130
+ hint;
131
+ requestId;
130
132
  constructor(args) {
131
133
  super(args.message);
132
134
  this.name = "RuntimeClientError";
@@ -135,6 +137,8 @@ var RuntimeClientError = class extends Error {
135
137
  this.code = args.code;
136
138
  this.details = args.details;
137
139
  this.traceId = args.traceId;
140
+ this.hint = args.hint;
141
+ this.requestId = args.requestId;
138
142
  }
139
143
  };
140
144
  var RuntimeClient = class {
@@ -339,14 +343,22 @@ var RuntimeClient = class {
339
343
  };
340
344
  }
341
345
  const message = toMessage(payload, response.status, response.statusText);
342
- const retryable = this.shouldRetryStatus(response.status);
346
+ const payloadObject = isObject(payload) ? payload : {};
347
+ const payloadCode = typeof payloadObject.code === "string" ? payloadObject.code : void 0;
348
+ const payloadHint = typeof payloadObject.hint === "string" ? payloadObject.hint : void 0;
349
+ const payloadRequestId = typeof payloadObject.requestId === "string" ? payloadObject.requestId : typeof payloadObject.request_id === "string" ? payloadObject.request_id : void 0;
350
+ const payloadRetryable = typeof payloadObject.retryable === "boolean" ? payloadObject.retryable : void 0;
351
+ const statusRetryable = this.shouldRetryStatus(response.status);
352
+ const retryable = payloadRetryable ?? statusRetryable;
343
353
  const error = new RuntimeClientError({
344
354
  message,
345
355
  status: response.status,
346
356
  retryable,
347
- code: response.status === 404 ? "NOT_FOUND" : "REQUEST_FAILED",
357
+ code: payloadCode || (response.status === 404 ? "NOT_FOUND" : "REQUEST_FAILED"),
348
358
  details: payload,
349
- traceId
359
+ traceId: payloadRequestId || traceId,
360
+ requestId: payloadRequestId || traceId,
361
+ hint: payloadHint
350
362
  });
351
363
  lastError = error;
352
364
  if (!retryable || attempt === maxAttempts - 1) {
@@ -360,7 +372,8 @@ var RuntimeClient = class {
360
372
  message: isAbort ? "Request timed out" : error instanceof Error ? error.message : "Network error",
361
373
  retryable: this.retryPolicy.retryOnNetworkError ?? true,
362
374
  code: isAbort ? "TIMEOUT" : "NETWORK_ERROR",
363
- traceId
375
+ traceId,
376
+ requestId: traceId
364
377
  });
365
378
  lastError = mapped;
366
379
  this.diagnostics.add({
@@ -391,6 +404,26 @@ var RuntimeClient = class {
391
404
  }
392
405
  };
393
406
 
407
+ // ../src/sdk/errors.ts
408
+ var WhisperError = class extends Error {
409
+ code;
410
+ status;
411
+ retryable;
412
+ hint;
413
+ requestId;
414
+ details;
415
+ constructor(args) {
416
+ super(args.message, args.cause ? { cause: args.cause } : void 0);
417
+ this.name = "WhisperError";
418
+ this.code = args.code;
419
+ this.status = args.status;
420
+ this.retryable = args.retryable ?? false;
421
+ this.hint = args.hint;
422
+ this.requestId = args.requestId;
423
+ this.details = args.details;
424
+ }
425
+ };
426
+
394
427
  // ../src/sdk/core/cache.ts
395
428
  var SearchResponseCache = class {
396
429
  ttlMs;
@@ -2186,12 +2219,44 @@ ${lines.join("\n")}`;
2186
2219
 
2187
2220
  // ../src/sdk/whisper.ts
2188
2221
  var PROJECT_CACHE_TTL_MS = 3e4;
2222
+ var IDENTITY_WARNINGS = /* @__PURE__ */ new Set();
2189
2223
  function isLikelyProjectId(projectRef) {
2190
2224
  return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(projectRef);
2191
2225
  }
2226
+ function randomRequestId() {
2227
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
2228
+ return crypto.randomUUID();
2229
+ }
2230
+ return `req_${Math.random().toString(36).slice(2, 11)}`;
2231
+ }
2232
+ function parseIdentityMode(value) {
2233
+ return value === "app-identity" ? "app-identity" : "demo-local";
2234
+ }
2235
+ function parseEnvironment(value) {
2236
+ if (value === "staging" || value === "production") return value;
2237
+ return "local";
2238
+ }
2239
+ function classifyRuntimeErrorCode(error) {
2240
+ if (error.code === "MISSING_PROJECT") return "MISSING_PROJECT";
2241
+ if (error.code === "TIMEOUT") return "TIMEOUT";
2242
+ if (error.code === "NETWORK_ERROR") return "NETWORK_ERROR";
2243
+ if (error.code === "VALIDATION_ERROR") return "VALIDATION_ERROR";
2244
+ if (error.status === 401 || error.status === 403) return "INVALID_API_KEY";
2245
+ if (error.status === 408) return "TIMEOUT";
2246
+ if (error.status === 429) return "RATE_LIMITED";
2247
+ if (error.status && error.status >= 500) return "TEMPORARY_UNAVAILABLE";
2248
+ if (error.code === "PROJECT_NOT_FOUND" || error.code === "NOT_FOUND") return "PROJECT_NOT_FOUND";
2249
+ return "REQUEST_FAILED";
2250
+ }
2192
2251
  var WhisperClient = class _WhisperClient {
2193
2252
  constructor(config) {
2194
2253
  this.config = config;
2254
+ const env = typeof process !== "undefined" ? process.env : {};
2255
+ this.identityMode = parseIdentityMode(config.identityMode || env.WHISPER_IDENTITY_MODE);
2256
+ this.environment = parseEnvironment(config.environment || env.WHISPER_ENV || (env.NODE_ENV === "production" ? "production" : "local"));
2257
+ this.strictIdentityMode = config.strictIdentityMode ?? env.WHISPER_DEMO_LOCAL_STRICT === "true";
2258
+ this.getIdentity = config.getIdentity;
2259
+ this.enforceIdentityModeGuardrail();
2195
2260
  this.diagnosticsStore = new DiagnosticsStore(config.telemetry?.maxEntries || 1e3);
2196
2261
  this.runtimeClient = new RuntimeClient(
2197
2262
  {
@@ -2301,26 +2366,29 @@ var WhisperClient = class _WhisperClient {
2301
2366
  status: () => this.writeQueue.status()
2302
2367
  };
2303
2368
  this.memory = {
2304
- add: (params) => this.memoryModule.add(params),
2305
- addBulk: (params) => this.memoryModule.addBulk(params),
2306
- search: (params) => this.memoryModule.search(params),
2307
- get: (memoryId) => this.memoryModule.get(memoryId),
2308
- getUserProfile: (params) => this.memoryModule.getUserProfile(params),
2309
- getSessionMemories: (params) => this.memoryModule.getSessionMemories(params),
2310
- update: (memoryId, params) => this.memoryModule.update(memoryId, params),
2311
- delete: (memoryId) => this.memoryModule.delete(memoryId),
2312
- flag: (params) => this.memoryModule.flag(params)
2369
+ add: (params) => this.runOrThrow(async () => this.memoryModule.add(await this.withIdentity(params))),
2370
+ addBulk: (params) => this.runOrThrow(async () => this.memoryModule.addBulk({
2371
+ ...params,
2372
+ memories: await Promise.all(params.memories.map((memory) => this.withIdentity(memory)))
2373
+ })),
2374
+ search: (params) => this.runOrThrow(async () => this.memoryModule.search(await this.withIdentity(params))),
2375
+ get: (memoryId) => this.runOrThrow(async () => this.memoryModule.get(memoryId)),
2376
+ getUserProfile: (params) => this.runOrThrow(async () => this.profileModule.getUserProfile(await this.withIdentity(params, true))),
2377
+ getSessionMemories: (params) => this.runOrThrow(async () => this.profileModule.getSessionMemories(await this.withIdentity(params))),
2378
+ update: (memoryId, params) => this.runOrThrow(async () => this.memoryModule.update(memoryId, params)),
2379
+ delete: (memoryId) => this.runOrThrow(async () => this.memoryModule.delete(memoryId)),
2380
+ flag: (params) => this.runOrThrow(async () => this.memoryModule.flag(params))
2313
2381
  };
2314
2382
  this.session = {
2315
- start: (params) => this.sessionModule.start(params),
2316
- event: (params) => this.sessionModule.event(params),
2317
- suspend: (params) => this.sessionModule.suspend(params),
2318
- resume: (params) => this.sessionModule.resume(params),
2319
- end: (params) => this.sessionModule.end(params)
2383
+ start: (params) => this.runOrThrow(async () => this.sessionModule.start(await this.withSessionIdentity(params))),
2384
+ event: (params) => this.runOrThrow(async () => this.sessionModule.event(params)),
2385
+ suspend: (params) => this.runOrThrow(async () => this.sessionModule.suspend(params)),
2386
+ resume: (params) => this.runOrThrow(async () => this.sessionModule.resume(params)),
2387
+ end: (params) => this.runOrThrow(async () => this.sessionModule.end(params))
2320
2388
  };
2321
2389
  this.profile = {
2322
- getUserProfile: (params) => this.profileModule.getUserProfile(params),
2323
- getSessionMemories: (params) => this.profileModule.getSessionMemories(params)
2390
+ getUserProfile: (params) => this.runOrThrow(async () => this.profileModule.getUserProfile(await this.withIdentity(params, true))),
2391
+ getSessionMemories: (params) => this.runOrThrow(async () => this.profileModule.getSessionMemories(await this.withIdentity(params)))
2324
2392
  };
2325
2393
  this.analytics = {
2326
2394
  diagnosticsSnapshot: () => this.analyticsModule.diagnosticsSnapshot(),
@@ -2342,18 +2410,158 @@ var WhisperClient = class _WhisperClient {
2342
2410
  profileModule;
2343
2411
  analyticsModule;
2344
2412
  projectRefToId = /* @__PURE__ */ new Map();
2413
+ identityMode;
2414
+ environment;
2415
+ strictIdentityMode;
2416
+ getIdentity;
2345
2417
  projectCache = [];
2346
2418
  projectCacheExpiresAt = 0;
2419
+ enforceIdentityModeGuardrail(requestId = randomRequestId()) {
2420
+ if (this.identityMode !== "demo-local") return;
2421
+ if (this.environment === "local") return;
2422
+ const message = "[Whisper SDK] WHISPER_IDENTITY_MODE=demo-local is intended only for local development. Switch to app-identity and provide getIdentity() or per-call user_id/session_id.";
2423
+ if (this.strictIdentityMode || this.environment === "production") {
2424
+ throw new WhisperError({
2425
+ code: "MISCONFIGURED_IDENTITY_MODE",
2426
+ message,
2427
+ retryable: false,
2428
+ hint: "Set identityMode: 'app-identity' and provide a getIdentity() function that returns { userId, sessionId? }. To override for testing, set environment: 'local' explicitly."
2429
+ });
2430
+ }
2431
+ const warningKey = `${this.environment}:${this.identityMode}`;
2432
+ if (!IDENTITY_WARNINGS.has(warningKey)) {
2433
+ IDENTITY_WARNINGS.add(warningKey);
2434
+ if (typeof console !== "undefined" && typeof console.warn === "function") {
2435
+ console.warn(`${message} requestId=${requestId}`);
2436
+ }
2437
+ }
2438
+ }
2439
+ toWhisperError(error, hint) {
2440
+ if (error instanceof WhisperError) return error;
2441
+ if (error instanceof RuntimeClientError) {
2442
+ return new WhisperError({
2443
+ code: classifyRuntimeErrorCode(error),
2444
+ message: error.message,
2445
+ status: error.status,
2446
+ retryable: error.retryable,
2447
+ hint: error.hint || hint,
2448
+ requestId: error.requestId || error.traceId,
2449
+ details: error.details,
2450
+ cause: error
2451
+ });
2452
+ }
2453
+ if (error instanceof Error) {
2454
+ return new WhisperError({
2455
+ code: "REQUEST_FAILED",
2456
+ message: error.message,
2457
+ retryable: false,
2458
+ hint,
2459
+ cause: error
2460
+ });
2461
+ }
2462
+ return new WhisperError({
2463
+ code: "REQUEST_FAILED",
2464
+ message: "Unknown SDK error",
2465
+ retryable: false,
2466
+ hint,
2467
+ details: error
2468
+ });
2469
+ }
2470
+ async runOrThrow(work, hint) {
2471
+ try {
2472
+ return await work();
2473
+ } catch (error) {
2474
+ throw this.toWhisperError(error, hint);
2475
+ }
2476
+ }
2477
+ async resolveIdentityOverride() {
2478
+ if (!this.getIdentity) return null;
2479
+ const resolved = await this.getIdentity();
2480
+ const userId = String(resolved?.userId || "").trim();
2481
+ const sessionId = resolved?.sessionId ? String(resolved.sessionId).trim() : void 0;
2482
+ if (!userId) {
2483
+ throw new WhisperError({
2484
+ code: "AUTH_IDENTITY_INVALID",
2485
+ message: "getIdentity() returned an invalid identity payload.",
2486
+ retryable: false,
2487
+ hint: "Return { userId, sessionId? } from getIdentity()."
2488
+ });
2489
+ }
2490
+ return {
2491
+ userId,
2492
+ sessionId: sessionId || void 0
2493
+ };
2494
+ }
2495
+ async withIdentity(params, requireUser = false) {
2496
+ const currentUser = params.user_id ? String(params.user_id).trim() : "";
2497
+ const currentSession = params.session_id ? String(params.session_id).trim() : "";
2498
+ if (currentUser) {
2499
+ return {
2500
+ ...params,
2501
+ user_id: currentUser,
2502
+ session_id: currentSession || params.session_id
2503
+ };
2504
+ }
2505
+ const resolved = await this.resolveIdentityOverride();
2506
+ if (resolved) {
2507
+ return {
2508
+ ...params,
2509
+ user_id: resolved.userId,
2510
+ session_id: currentSession || resolved.sessionId
2511
+ };
2512
+ }
2513
+ if (requireUser || this.identityMode === "app-identity") {
2514
+ throw new WhisperError({
2515
+ code: "AUTH_IDENTITY_REQUIRED",
2516
+ message: "A user identity is required in app-identity mode.",
2517
+ retryable: false,
2518
+ hint: "Provide user_id/session_id per call or configure getIdentity() in WhisperClient."
2519
+ });
2520
+ }
2521
+ return params;
2522
+ }
2523
+ async withSessionIdentity(params) {
2524
+ const userId = params.userId ? String(params.userId).trim() : "";
2525
+ const sessionId = params.sessionId ? String(params.sessionId).trim() : "";
2526
+ if (userId) {
2527
+ return {
2528
+ ...params,
2529
+ userId,
2530
+ sessionId: sessionId || params.sessionId
2531
+ };
2532
+ }
2533
+ const resolved = await this.resolveIdentityOverride();
2534
+ if (resolved?.userId) {
2535
+ return {
2536
+ ...params,
2537
+ userId: resolved.userId,
2538
+ sessionId: sessionId || resolved.sessionId
2539
+ };
2540
+ }
2541
+ throw new WhisperError({
2542
+ code: "AUTH_IDENTITY_REQUIRED",
2543
+ message: "Session operations require a user identity.",
2544
+ retryable: false,
2545
+ hint: "Pass userId explicitly or configure getIdentity() in WhisperClient."
2546
+ });
2547
+ }
2347
2548
  static fromEnv(overrides = {}) {
2348
2549
  const env = typeof process !== "undefined" ? process.env : {};
2349
2550
  const apiKey = overrides.apiKey || env.WHISPER_API_KEY || env.USEWHISPER_API_KEY || env.API_KEY;
2350
2551
  if (!apiKey) {
2351
- throw new Error("Missing API key. Set WHISPER_API_KEY / USEWHISPER_API_KEY / API_KEY.");
2552
+ throw new WhisperError({
2553
+ code: "INVALID_API_KEY",
2554
+ message: "Missing API key. Set WHISPER_API_KEY / USEWHISPER_API_KEY / API_KEY.",
2555
+ retryable: false
2556
+ });
2352
2557
  }
2353
2558
  return new _WhisperClient({
2354
2559
  apiKey,
2355
2560
  baseUrl: overrides.baseUrl || env.WHISPER_BASE_URL || env.API_BASE_URL || "https://context.usewhisper.dev",
2356
2561
  project: overrides.project || env.WHISPER_PROJECT || env.PROJECT,
2562
+ identityMode: overrides.identityMode || parseIdentityMode(env.WHISPER_IDENTITY_MODE),
2563
+ environment: overrides.environment || parseEnvironment(env.WHISPER_ENV || (env.NODE_ENV === "production" ? "production" : "local")),
2564
+ strictIdentityMode: overrides.strictIdentityMode ?? env.WHISPER_DEMO_LOCAL_STRICT === "true",
2357
2565
  ...overrides
2358
2566
  });
2359
2567
  }
@@ -2390,10 +2598,11 @@ var WhisperClient = class _WhisperClient {
2390
2598
  getRequiredProject(project) {
2391
2599
  const resolved = project || this.config.project;
2392
2600
  if (!resolved) {
2393
- throw new RuntimeClientError({
2601
+ throw new WhisperError({
2394
2602
  code: "MISSING_PROJECT",
2395
2603
  message: "Project is required",
2396
- retryable: false
2604
+ retryable: false,
2605
+ hint: "Pass project in the call or configure a default project in WhisperClient."
2397
2606
  });
2398
2607
  }
2399
2608
  return resolved;
@@ -2435,81 +2644,182 @@ var WhisperClient = class _WhisperClient {
2435
2644
  }
2436
2645
  }
2437
2646
  async resolveProject(projectRef) {
2438
- const resolvedRef = this.getRequiredProject(projectRef);
2439
- const cachedProjects = await this.refreshProjectCache(false);
2440
- const cachedProject = cachedProjects.find(
2441
- (project) => project.id === resolvedRef || project.slug === resolvedRef || project.name === resolvedRef
2442
- );
2443
- if (cachedProject) {
2444
- return cachedProject;
2647
+ return this.runOrThrow(async () => {
2648
+ const resolvedRef = this.getRequiredProject(projectRef);
2649
+ const cachedProjects = await this.refreshProjectCache(false);
2650
+ const cachedProject = cachedProjects.find(
2651
+ (project) => project.id === resolvedRef || project.slug === resolvedRef || project.name === resolvedRef
2652
+ );
2653
+ if (cachedProject) {
2654
+ return cachedProject;
2655
+ }
2656
+ const resolvedProject = await this.fetchResolvedProject(resolvedRef);
2657
+ if (resolvedProject) {
2658
+ this.projectRefToId.set(resolvedProject.id, resolvedProject.id);
2659
+ this.projectRefToId.set(resolvedProject.slug, resolvedProject.id);
2660
+ this.projectRefToId.set(resolvedProject.name, resolvedProject.id);
2661
+ this.projectCache = [
2662
+ ...this.projectCache.filter((project) => project.id !== resolvedProject.id),
2663
+ resolvedProject
2664
+ ];
2665
+ this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
2666
+ return resolvedProject;
2667
+ }
2668
+ if (isLikelyProjectId(resolvedRef)) {
2669
+ return {
2670
+ id: resolvedRef,
2671
+ orgId: "",
2672
+ name: resolvedRef,
2673
+ slug: resolvedRef,
2674
+ createdAt: (/* @__PURE__ */ new Date(0)).toISOString(),
2675
+ updatedAt: (/* @__PURE__ */ new Date(0)).toISOString()
2676
+ };
2677
+ }
2678
+ throw new WhisperError({
2679
+ code: "PROJECT_NOT_FOUND",
2680
+ message: `Project '${resolvedRef}' not found`,
2681
+ retryable: false
2682
+ });
2683
+ });
2684
+ }
2685
+ async preflight(options) {
2686
+ const requestId = randomRequestId();
2687
+ const checks = [];
2688
+ try {
2689
+ this.enforceIdentityModeGuardrail(requestId);
2690
+ } catch (error) {
2691
+ throw this.toWhisperError(error, "Update identity mode before running preflight.");
2692
+ }
2693
+ const apiKeyOk = typeof this.config.apiKey === "string" && this.config.apiKey.trim().length > 0;
2694
+ checks.push({
2695
+ check: "api_key",
2696
+ ok: apiKeyOk,
2697
+ message: apiKeyOk ? "API key is configured." : "Missing API key.",
2698
+ hint: apiKeyOk ? void 0 : "Set WHISPER_API_KEY or pass apiKey to WhisperClient."
2699
+ });
2700
+ try {
2701
+ await this.runtimeClient.request({
2702
+ endpoint: "/v1/projects",
2703
+ method: "GET",
2704
+ operation: "get",
2705
+ idempotent: true,
2706
+ traceId: requestId
2707
+ });
2708
+ checks.push({
2709
+ check: "api_connectivity",
2710
+ ok: true,
2711
+ message: "Connected to Whisper API."
2712
+ });
2713
+ } catch (error) {
2714
+ const mapped = this.toWhisperError(error, "Confirm WHISPER_BASE_URL and API key permissions.");
2715
+ checks.push({
2716
+ check: "api_connectivity",
2717
+ ok: false,
2718
+ message: mapped.message,
2719
+ hint: mapped.hint
2720
+ });
2445
2721
  }
2446
- const resolvedProject = await this.fetchResolvedProject(resolvedRef);
2447
- if (resolvedProject) {
2448
- this.projectRefToId.set(resolvedProject.id, resolvedProject.id);
2449
- this.projectRefToId.set(resolvedProject.slug, resolvedProject.id);
2450
- this.projectRefToId.set(resolvedProject.name, resolvedProject.id);
2451
- this.projectCache = [
2452
- ...this.projectCache.filter((project) => project.id !== resolvedProject.id),
2453
- resolvedProject
2454
- ];
2455
- this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
2456
- return resolvedProject;
2722
+ const projectRef = options?.project || this.config.project;
2723
+ if (projectRef) {
2724
+ try {
2725
+ await this.resolveProject(projectRef);
2726
+ checks.push({
2727
+ check: "project_access",
2728
+ ok: true,
2729
+ message: `Project '${projectRef}' is reachable.`
2730
+ });
2731
+ } catch (error) {
2732
+ const mapped = this.toWhisperError(error, "Create or grant access to the configured project.");
2733
+ checks.push({
2734
+ check: "project_access",
2735
+ ok: false,
2736
+ message: mapped.message,
2737
+ hint: mapped.hint
2738
+ });
2739
+ }
2740
+ } else {
2741
+ checks.push({
2742
+ check: "project_access",
2743
+ ok: true,
2744
+ message: "No default project configured (project will be required per call)."
2745
+ });
2457
2746
  }
2458
- if (isLikelyProjectId(resolvedRef)) {
2459
- return {
2460
- id: resolvedRef,
2461
- orgId: "",
2462
- name: resolvedRef,
2463
- slug: resolvedRef,
2464
- createdAt: (/* @__PURE__ */ new Date(0)).toISOString(),
2465
- updatedAt: (/* @__PURE__ */ new Date(0)).toISOString()
2466
- };
2747
+ if (options?.requireIdentity || this.identityMode === "app-identity") {
2748
+ try {
2749
+ const identity = await this.resolveIdentityOverride();
2750
+ const ok = Boolean(identity?.userId);
2751
+ checks.push({
2752
+ check: "identity_resolution",
2753
+ ok,
2754
+ message: ok ? "Identity resolver is configured." : "Identity resolver is missing.",
2755
+ hint: ok ? void 0 : "Provide getIdentity() or pass user_id/session_id per call."
2756
+ });
2757
+ } catch (error) {
2758
+ const mapped = this.toWhisperError(error, "Fix identity resolver output before production usage.");
2759
+ checks.push({
2760
+ check: "identity_resolution",
2761
+ ok: false,
2762
+ message: mapped.message,
2763
+ hint: mapped.hint
2764
+ });
2765
+ }
2467
2766
  }
2468
- throw new RuntimeClientError({
2469
- code: "PROJECT_NOT_FOUND",
2470
- message: `Project '${resolvedRef}' not found`,
2471
- retryable: false
2472
- });
2767
+ return {
2768
+ ok: checks.every((check) => check.ok),
2769
+ checks,
2770
+ requestId,
2771
+ identityMode: this.identityMode,
2772
+ environment: this.environment
2773
+ };
2473
2774
  }
2474
2775
  async query(params) {
2475
- const project = (await this.resolveProject(params.project)).id;
2476
- const response = await this.runtimeClient.request({
2477
- endpoint: "/v1/context/query",
2478
- method: "POST",
2479
- operation: "search",
2480
- body: {
2481
- ...params,
2482
- project
2483
- },
2484
- idempotent: true
2776
+ return this.runOrThrow(async () => {
2777
+ const identityParams = await this.withIdentity(params);
2778
+ const project = (await this.resolveProject(identityParams.project)).id;
2779
+ const response = await this.runtimeClient.request({
2780
+ endpoint: "/v1/context/query",
2781
+ method: "POST",
2782
+ operation: "search",
2783
+ body: {
2784
+ ...identityParams,
2785
+ project
2786
+ },
2787
+ idempotent: true
2788
+ });
2789
+ return response.data;
2485
2790
  });
2486
- return response.data;
2487
2791
  }
2488
2792
  async ingestSession(params) {
2489
- const project = (await this.resolveProject(params.project)).id;
2490
- const response = await this.runtimeClient.request({
2491
- endpoint: "/v1/memory/ingest/session",
2492
- method: "POST",
2493
- operation: "session",
2494
- body: {
2495
- ...params,
2496
- project
2497
- }
2793
+ return this.runOrThrow(async () => {
2794
+ const identityParams = await this.withIdentity(params);
2795
+ const project = (await this.resolveProject(identityParams.project)).id;
2796
+ const response = await this.runtimeClient.request({
2797
+ endpoint: "/v1/memory/ingest/session",
2798
+ method: "POST",
2799
+ operation: "session",
2800
+ body: {
2801
+ ...identityParams,
2802
+ project
2803
+ }
2804
+ });
2805
+ return response.data;
2498
2806
  });
2499
- return response.data;
2500
2807
  }
2501
2808
  async learn(params) {
2502
- const project = (await this.resolveProject(params.project)).id;
2503
- const response = await this.runtimeClient.request({
2504
- endpoint: "/v1/learn",
2505
- method: "POST",
2506
- operation: params.mode === "conversation" ? "session" : "bulk",
2507
- body: {
2508
- ...params,
2509
- project
2510
- }
2809
+ return this.runOrThrow(async () => {
2810
+ const identityParams = params.mode === "conversation" ? await this.withIdentity(params) : params;
2811
+ const project = (await this.resolveProject(identityParams.project)).id;
2812
+ const response = await this.runtimeClient.request({
2813
+ endpoint: "/v1/learn",
2814
+ method: "POST",
2815
+ operation: params.mode === "conversation" ? "session" : "bulk",
2816
+ body: {
2817
+ ...identityParams,
2818
+ project
2819
+ }
2820
+ });
2821
+ return response.data;
2511
2822
  });
2512
- return response.data;
2513
2823
  }
2514
2824
  createAgentRuntime(options = {}) {
2515
2825
  const baseContext = {
@@ -2571,26 +2881,60 @@ var WhisperClient = class _WhisperClient {
2571
2881
  diagnostics: base.diagnostics
2572
2882
  };
2573
2883
  }
2884
+ async deleteSource(sourceId) {
2885
+ return this.runOrThrow(async () => {
2886
+ const response = await this.runtimeClient.request({
2887
+ endpoint: `/v1/sources/${sourceId}`,
2888
+ method: "DELETE",
2889
+ operation: "writeAck"
2890
+ });
2891
+ return response.data;
2892
+ });
2893
+ }
2894
+ async extractMemories(params) {
2895
+ return this.runOrThrow(async () => {
2896
+ const project = (await this.resolveProject(params.project)).id;
2897
+ const response = await this.runtimeClient.request({
2898
+ endpoint: "/v1/memory/extract",
2899
+ method: "POST",
2900
+ operation: "writeAck",
2901
+ body: { project, message: params.message }
2902
+ });
2903
+ return response.data;
2904
+ });
2905
+ }
2574
2906
  async shutdown() {
2575
2907
  await this.writeQueue.stop();
2576
2908
  }
2577
2909
  };
2578
2910
 
2911
+ // ../src/sdk/adapters/tools.ts
2912
+ import { z } from "zod";
2913
+ var searchInputSchema = z.object({
2914
+ q: z.string().min(1),
2915
+ project: z.string().optional(),
2916
+ topK: z.number().int().positive().max(50).optional(),
2917
+ userId: z.string().optional(),
2918
+ sessionId: z.string().optional()
2919
+ });
2920
+ var rememberInputSchema = z.object({
2921
+ content: z.string().min(1),
2922
+ project: z.string().optional(),
2923
+ memoryType: z.enum([
2924
+ "factual",
2925
+ "preference",
2926
+ "event",
2927
+ "relationship",
2928
+ "opinion",
2929
+ "goal",
2930
+ "instruction"
2931
+ ]).optional(),
2932
+ userId: z.string().optional(),
2933
+ sessionId: z.string().optional(),
2934
+ metadata: z.record(z.unknown()).optional()
2935
+ });
2936
+
2579
2937
  // ../src/sdk/index.ts
2580
- var WhisperError = class extends Error {
2581
- code;
2582
- status;
2583
- retryable;
2584
- details;
2585
- constructor(args) {
2586
- super(args.message);
2587
- this.name = "WhisperError";
2588
- this.code = args.code;
2589
- this.status = args.status;
2590
- this.retryable = args.retryable ?? false;
2591
- this.details = args.details;
2592
- }
2593
- };
2594
2938
  var DEFAULT_MAX_ATTEMPTS = 3;
2595
2939
  var DEFAULT_BASE_DELAY_MS = 250;
2596
2940
  var DEFAULT_MAX_DELAY_MS = 2e3;
@@ -2961,7 +3305,10 @@ var WhisperContext = class _WhisperContext {
2961
3305
  message,
2962
3306
  status: error.status,
2963
3307
  retryable,
2964
- details: error.details
3308
+ hint: error.hint,
3309
+ requestId: error.requestId || error.traceId,
3310
+ details: error.details,
3311
+ cause: error
2965
3312
  });
2966
3313
  }
2967
3314
  }
@@ -4711,12 +5058,28 @@ async function ingestSessionWithSyncFallback(params) {
4711
5058
  return whisper.ingestSession(params);
4712
5059
  }
4713
5060
  }
5061
+ function apiKeyFingerprint(apiKey) {
5062
+ const value = String(apiKey || "").trim();
5063
+ if (!value) return "anon";
5064
+ return createHash("sha256").update(value).digest("hex").slice(0, 12);
5065
+ }
4714
5066
  function defaultMcpUserId(params) {
4715
5067
  const explicit = process.env.WHISPER_USER_ID?.trim();
4716
5068
  if (explicit) return explicit;
5069
+ let osUsername = process.env.USER?.trim() || process.env.USERNAME?.trim();
5070
+ if (!osUsername) {
5071
+ try {
5072
+ osUsername = userInfo().username?.trim();
5073
+ } catch {
5074
+ osUsername = "";
5075
+ }
5076
+ }
5077
+ if (!osUsername) {
5078
+ throw new Error("Unable to derive user identity. Set WHISPER_USER_ID or pass user_id.");
5079
+ }
4717
5080
  const workspacePath = params?.workspacePath || canonicalizeWorkspacePath(process.cwd());
4718
5081
  const projectRef = params?.project || DEFAULT_PROJECT || "default";
4719
- const seed = `${workspacePath}|${projectRef}|${API_KEY.slice(0, 12) || "anon"}`;
5082
+ const seed = `${workspacePath}|${projectRef}|${apiKeyFingerprint(API_KEY)}|${osUsername.toLowerCase()}`;
4720
5083
  return `mcp-user-${createHash("sha256").update(seed).digest("hex").slice(0, 12)}`;
4721
5084
  }
4722
5085
  function resolveMcpScope(params, options) {
@@ -4732,6 +5095,68 @@ function resolveMcpScope(params, options) {
4732
5095
  workspacePath
4733
5096
  };
4734
5097
  }
5098
+ function resolveMemoryScope(params) {
5099
+ const base = resolveMcpScope({
5100
+ project: params?.project,
5101
+ user_id: params?.user_id,
5102
+ session_id: params?.session_id,
5103
+ path: params?.path
5104
+ });
5105
+ const scopeMode = params?.scope === "shared_project" ? "shared_project" : "personal";
5106
+ if (scopeMode === "shared_project") {
5107
+ if (!base.project && !DEFAULT_PROJECT) {
5108
+ throw new Error("shared_project scope requires a project. Set WHISPER_PROJECT or pass project.");
5109
+ }
5110
+ const projectRef = base.project || DEFAULT_PROJECT || "default";
5111
+ return {
5112
+ ...base,
5113
+ userId: `shared-project:${projectRef}`,
5114
+ sessionId: void 0,
5115
+ actorUserId: base.userId,
5116
+ actorSessionId: base.sessionId,
5117
+ scopeMode,
5118
+ sharedProjectRef: projectRef
5119
+ };
5120
+ }
5121
+ return {
5122
+ ...base,
5123
+ actorUserId: base.userId,
5124
+ actorSessionId: base.sessionId,
5125
+ scopeMode,
5126
+ sharedProjectRef: null
5127
+ };
5128
+ }
5129
+ function sharedProjectMemoryUserId(projectRef) {
5130
+ return `shared-project:${projectRef}`;
5131
+ }
5132
+ function buildMemoryScopeMetadata(args) {
5133
+ const { memoryScope } = args;
5134
+ const metadata = {
5135
+ scope: memoryScope.scopeMode,
5136
+ ...memoryScope.sharedProjectRef ? { shared_project_ref: memoryScope.sharedProjectRef } : {}
5137
+ };
5138
+ if (memoryScope.scopeMode === "shared_project") {
5139
+ metadata.provenance = {
5140
+ writer_user_id: memoryScope.actorUserId,
5141
+ writer_session_id: memoryScope.actorSessionId || null,
5142
+ writer_agent_id: args.agent_id || process.env.WHISPER_AGENT_ID || null,
5143
+ written_at: (/* @__PURE__ */ new Date()).toISOString()
5144
+ };
5145
+ }
5146
+ return metadata;
5147
+ }
5148
+ function isMemoryInScope(args) {
5149
+ const memory = args.memory;
5150
+ if (!memory || typeof memory !== "object") return false;
5151
+ const memoryUserId = String(memory.userId || memory.user_id || "").trim();
5152
+ if (args.memoryScope.scopeMode === "shared_project") {
5153
+ const metadata = memory.metadata && typeof memory.metadata === "object" ? memory.metadata : {};
5154
+ const metaScope = String(metadata.scope || "").trim();
5155
+ const metaProjectRef = String(metadata.shared_project_ref || "").trim();
5156
+ return memoryUserId === args.memoryScope.userId || metaScope === "shared_project" && metaProjectRef === (args.memoryScope.sharedProjectRef || "");
5157
+ }
5158
+ return memoryUserId === args.memoryScope.userId;
5159
+ }
4735
5160
  function noteAutomaticSourceActivity(params) {
4736
5161
  if (!runtimeClient) return;
4737
5162
  const sourceIds = [...new Set((params.sourceIds || []).map((value) => String(value || "").trim()).filter(Boolean))];
@@ -5298,6 +5723,82 @@ function resolveForgetQueryCandidates(rawResults, query) {
5298
5723
  }
5299
5724
  return { memory_ids: [], resolved_by: "none", warning: "Query did not resolve to a reliable memory match. No memories were changed." };
5300
5725
  }
5726
+ async function searchMemoriesForContextQuery(args) {
5727
+ return whisper.searchMemoriesSOTA({
5728
+ project: args.project,
5729
+ query: args.query,
5730
+ user_id: args.user_id,
5731
+ session_id: args.session_id,
5732
+ top_k: args.top_k ?? 5,
5733
+ include_relations: false,
5734
+ include_pending: true
5735
+ });
5736
+ }
5737
+ async function runContextQueryMemoryRescue(args) {
5738
+ const scoped = await searchMemoriesForContextQuery(args);
5739
+ const personalResults = formatCanonicalMemoryResults(scoped);
5740
+ const sharedUserId = sharedProjectMemoryUserId(args.project);
5741
+ const shared = args.user_id === sharedUserId ? { results: [] } : await searchMemoriesForContextQuery({
5742
+ project: args.project,
5743
+ query: args.query,
5744
+ user_id: sharedUserId,
5745
+ top_k: args.top_k
5746
+ });
5747
+ const sharedResults = formatCanonicalMemoryResults(shared);
5748
+ const deduped = /* @__PURE__ */ new Set();
5749
+ const combined = [
5750
+ ...personalResults.map((result) => ({
5751
+ ...result,
5752
+ memory_scope: "personal",
5753
+ ranking_score: clamp012(Number(result.similarity ?? 0.5) + 0.08)
5754
+ })),
5755
+ ...sharedResults.map((result) => ({
5756
+ ...result,
5757
+ memory_scope: "shared_project",
5758
+ ranking_score: clamp012(Number(result.similarity ?? 0.5))
5759
+ }))
5760
+ ].filter((result) => {
5761
+ if (!result.id) return false;
5762
+ if (deduped.has(result.id)) return false;
5763
+ deduped.add(result.id);
5764
+ return true;
5765
+ }).sort((a, b) => b.ranking_score - a.ranking_score);
5766
+ const rescueMode = combined.length === 0 ? null : sharedResults.length > 0 ? "scoped_plus_shared" : "scoped_only";
5767
+ return {
5768
+ personal_results: personalResults,
5769
+ shared_results: sharedResults,
5770
+ combined_results: combined,
5771
+ rescue_mode: rescueMode
5772
+ };
5773
+ }
5774
+ function renderContextQueryMemoryRescue(args) {
5775
+ const lines = args.combined_results.map(
5776
+ (result, index) => `${index + 1}. [${result.memory_scope}, ${result.memory_type || "memory"}, score: ${result.ranking_score.toFixed(2)}] ${result.content}`
5777
+ );
5778
+ return `Found ${args.combined_results.length} memory result(s) (project=${args.project}, user=${args.scope.userId}, session=${args.scope.sessionId}, personal=${args.personal_results.length}, shared=${args.shared_results.length}, ordering=personal_then_shared_bias):
5779
+
5780
+ ${lines.join("\n\n")}`;
5781
+ }
5782
+ function memoryRescueResultsToEvidence(args) {
5783
+ return args.hits.map(
5784
+ (hit) => toEvidenceRef({
5785
+ id: `memory:${hit.id || randomUUID()}`,
5786
+ source: "memory",
5787
+ document: "memory",
5788
+ content: hit.content,
5789
+ score: hit.ranking_score,
5790
+ retrieval_source: "memory",
5791
+ metadata: {
5792
+ path: `memory://${hit.memory_scope}/${hit.id || "unknown"}`,
5793
+ line_start: 1,
5794
+ line_end: 1,
5795
+ score: hit.ranking_score,
5796
+ memory_scope: hit.memory_scope,
5797
+ memory_type: hit.memory_type || "factual"
5798
+ }
5799
+ }, args.workspaceId, "memory")
5800
+ );
5801
+ }
5301
5802
  function likelyEmbeddingFailure(error) {
5302
5803
  const message = String(error?.message || error || "").toLowerCase();
5303
5804
  return message.includes("embedding") || message.includes("vector") || message.includes("timeout") || message.includes("timed out") || message.includes("temporarily unavailable");
@@ -5974,9 +6475,9 @@ server.tool(
5974
6475
  "index.workspace_resolve",
5975
6476
  "Resolve workspace identity from path + API key and map to a project without mandatory dashboard setup.",
5976
6477
  {
5977
- path: z.string().optional().describe("Workspace path. Defaults to current working directory."),
5978
- workspace_id: z.string().optional(),
5979
- project: z.string().optional()
6478
+ path: z2.string().optional().describe("Workspace path. Defaults to current working directory."),
6479
+ workspace_id: z2.string().optional(),
6480
+ project: z2.string().optional()
5980
6481
  },
5981
6482
  async ({ path, workspace_id, project }) => {
5982
6483
  try {
@@ -6013,8 +6514,8 @@ server.tool(
6013
6514
  "index.workspace_status",
6014
6515
  "Check index freshness, coverage, commit, and pending changes before retrieval/edits.",
6015
6516
  {
6016
- workspace_id: z.string().optional(),
6017
- path: z.string().optional()
6517
+ workspace_id: z2.string().optional(),
6518
+ path: z2.string().optional()
6018
6519
  },
6019
6520
  async ({ workspace_id, path }) => {
6020
6521
  try {
@@ -6051,10 +6552,10 @@ server.tool(
6051
6552
  "index.workspace_run",
6052
6553
  "Refresh local workspace index metadata (coverage, commit, freshness) for trust checks. This does not upload files or create backend embeddings.",
6053
6554
  {
6054
- workspace_id: z.string().optional(),
6055
- path: z.string().optional(),
6056
- mode: z.enum(["full", "incremental"]).optional().default("incremental"),
6057
- max_files: z.number().optional().default(1500)
6555
+ workspace_id: z2.string().optional(),
6556
+ path: z2.string().optional(),
6557
+ mode: z2.enum(["full", "incremental"]).optional().default("incremental"),
6558
+ max_files: z2.number().optional().default(1500)
6058
6559
  },
6059
6560
  async ({ workspace_id, path, mode, max_files }) => {
6060
6561
  try {
@@ -6100,11 +6601,11 @@ server.tool(
6100
6601
  "index.local_scan_ingest",
6101
6602
  "Ingest local files into Whisper backend (chunk/embed/index) and persist incremental manifest. Requires WHISPER_MCP_MODE=auto or local.",
6102
6603
  {
6103
- project: z.string().optional().describe("Project name or slug"),
6104
- path: z.string().optional().describe("Local path to ingest. Defaults to current working directory."),
6105
- glob: z.string().optional().describe("Optional include glob"),
6106
- max_files: z.number().optional().default(200),
6107
- chunk_chars: z.number().optional().default(2e4)
6604
+ project: z2.string().optional().describe("Project name or slug"),
6605
+ path: z2.string().optional().describe("Local path to ingest. Defaults to current working directory."),
6606
+ glob: z2.string().optional().describe("Optional include glob"),
6607
+ max_files: z2.number().optional().default(200),
6608
+ chunk_chars: z2.number().optional().default(2e4)
6108
6609
  },
6109
6610
  async ({ project, path, glob, max_files, chunk_chars }) => {
6110
6611
  try {
@@ -6136,12 +6637,12 @@ server.tool(
6136
6637
  "index.auto_repair",
6137
6638
  "Diagnose and apply deterministic retrieval/setup repairs. This is the canonical zero-stress repair path.",
6138
6639
  {
6139
- path: z.string().optional(),
6140
- workspace_id: z.string().optional(),
6141
- project: z.string().optional(),
6142
- apply: z.boolean().optional().default(true),
6143
- repair_scope: z.enum(["safe", "full"]).optional().default("safe"),
6144
- allow_restart_hint: z.boolean().optional().default(true)
6640
+ path: z2.string().optional(),
6641
+ workspace_id: z2.string().optional(),
6642
+ project: z2.string().optional(),
6643
+ apply: z2.boolean().optional().default(true),
6644
+ repair_scope: z2.enum(["safe", "full"]).optional().default("safe"),
6645
+ allow_restart_hint: z2.boolean().optional().default(true)
6145
6646
  },
6146
6647
  async ({ path, workspace_id, project, apply, repair_scope, allow_restart_hint }) => {
6147
6648
  try {
@@ -6163,12 +6664,12 @@ server.tool(
6163
6664
  "fix",
6164
6665
  "Primary alias for zero-stress setup/retrieval repair. Delegates to index.auto_repair.",
6165
6666
  {
6166
- path: z.string().optional(),
6167
- workspace_id: z.string().optional(),
6168
- project: z.string().optional(),
6169
- apply: z.boolean().optional().default(true),
6170
- repair_scope: z.enum(["safe", "full"]).optional().default("safe"),
6171
- allow_restart_hint: z.boolean().optional().default(true)
6667
+ path: z2.string().optional(),
6668
+ workspace_id: z2.string().optional(),
6669
+ project: z2.string().optional(),
6670
+ apply: z2.boolean().optional().default(true),
6671
+ repair_scope: z2.enum(["safe", "full"]).optional().default("safe"),
6672
+ allow_restart_hint: z2.boolean().optional().default(true)
6172
6673
  },
6173
6674
  async ({ path, workspace_id, project, apply, repair_scope, allow_restart_hint }) => {
6174
6675
  try {
@@ -6244,6 +6745,7 @@ async function runUnifiedContextRetrieval(params) {
6244
6745
  let retrievalReadiness = preflight.retrieval_readiness;
6245
6746
  let retrievalRoute = preflight.retrieval_route;
6246
6747
  let latencyMs = 0;
6748
+ let memoryContribution = null;
6247
6749
  if (preflight.retrieval_route === "local_workspace_fallback") {
6248
6750
  const localRetrieval = await runLocalWorkspaceRetrieval({
6249
6751
  query: params.question,
@@ -6302,6 +6804,39 @@ async function runUnifiedContextRetrieval(params) {
6302
6804
  retrievalRoute = preflight.repo_grounded ? "project_repo" : "none";
6303
6805
  }
6304
6806
  }
6807
+ if (resolvedProject && params.include_memories === true && !preflight.repo_grounded) {
6808
+ try {
6809
+ memoryContribution = await runContextQueryMemoryRescue({
6810
+ project: resolvedProject,
6811
+ query: params.question,
6812
+ user_id: scope.userId,
6813
+ session_id: params.session_id,
6814
+ top_k: Math.max(4, Math.min(topK, 8))
6815
+ });
6816
+ if (memoryContribution.combined_results.length > 0) {
6817
+ const memoryEvidence = memoryRescueResultsToEvidence({
6818
+ workspaceId: preflight.trust_state.workspace_id,
6819
+ hits: memoryContribution.combined_results
6820
+ });
6821
+ const existingSourceIds = new Set(evidence.map((item) => item.source_id));
6822
+ const dedupedMemoryEvidence = memoryEvidence.filter((item) => !existingSourceIds.has(item.source_id));
6823
+ evidence = [...evidence, ...dedupedMemoryEvidence];
6824
+ const memoryContext = renderContextQueryMemoryRescue({
6825
+ project: resolvedProject,
6826
+ scope,
6827
+ personal_results: memoryContribution.personal_results,
6828
+ shared_results: memoryContribution.shared_results,
6829
+ combined_results: memoryContribution.combined_results
6830
+ });
6831
+ contextText = contextText.trim().length > 0 ? `${contextText}
6832
+
6833
+ ${memoryContext}` : memoryContext;
6834
+ if (retrievalRoute === "none") retrievalRoute = "memory_only";
6835
+ }
6836
+ } catch (error) {
6837
+ warnings.push(`Scoped memory contribution failed: ${String(error?.message || error)}`);
6838
+ }
6839
+ }
6305
6840
  recordSemanticAttempt(preflight.trust_state.workspace_id, semanticStatus === "failed");
6306
6841
  const semanticStats = getWorkspaceSemanticFailureStats(preflight.trust_state.workspace_id);
6307
6842
  const trustScore = computeTrustScore({
@@ -6374,6 +6909,14 @@ async function runUnifiedContextRetrieval(params) {
6374
6909
  user_id: scope.userId,
6375
6910
  session_id: params.session_id || null
6376
6911
  },
6912
+ memory: {
6913
+ included: resolvedProject ? params.include_memories === true && !preflight.repo_grounded : false,
6914
+ personal_count: memoryContribution?.personal_results.length || 0,
6915
+ shared_count: memoryContribution?.shared_results.length || 0,
6916
+ combined_count: memoryContribution?.combined_results.length || 0,
6917
+ shared_user_id: resolvedProject ? sharedProjectMemoryUserId(resolvedProject) : null,
6918
+ ordering: "personal_then_shared_bias"
6919
+ },
6377
6920
  retrieval_profile: retrievalProfile,
6378
6921
  trust_score: trustScore,
6379
6922
  semantic_failure_rate: semanticStats.rate,
@@ -6383,7 +6926,11 @@ async function runUnifiedContextRetrieval(params) {
6383
6926
  }
6384
6927
  };
6385
6928
  if (params.include_alias_warning) {
6386
- payload.warnings = Array.from(/* @__PURE__ */ new Set([...payload.warnings, "deprecated_alias_use_context.query"]));
6929
+ payload.warnings = Array.from(/* @__PURE__ */ new Set([
6930
+ ...payload.warnings,
6931
+ "deprecated_alias_use_context_query",
6932
+ "deprecated_alias_use_context.query"
6933
+ ]));
6387
6934
  }
6388
6935
  return payload;
6389
6936
  }
@@ -6391,17 +6938,17 @@ server.tool(
6391
6938
  "context.get_relevant",
6392
6939
  "Default grounded retrieval step for workspace/project questions. Returns ranked evidence with file:line citations and may abstain when workspace/repo trust is not ready.",
6393
6940
  {
6394
- question: z.string().describe("Task/question to retrieve context for"),
6395
- path: z.string().optional().describe("Workspace path. Defaults to current working directory."),
6396
- workspace_id: z.string().optional(),
6397
- project: z.string().optional(),
6398
- top_k: z.number().optional().default(12),
6399
- include_memories: z.boolean().optional().default(true),
6400
- include_graph: z.boolean().optional().default(true),
6401
- session_id: z.string().optional(),
6402
- user_id: z.string().optional(),
6403
- include_parent_content: z.boolean().optional().default(false),
6404
- retrieval_profile: z.enum(MCP_RETRIEVAL_PROFILE_VALUES).optional()
6941
+ question: z2.string().describe("Task/question to retrieve context for"),
6942
+ path: z2.string().optional().describe("Workspace path. Defaults to current working directory."),
6943
+ workspace_id: z2.string().optional(),
6944
+ project: z2.string().optional(),
6945
+ top_k: z2.number().optional().default(12),
6946
+ include_memories: z2.boolean().optional().default(true),
6947
+ include_graph: z2.boolean().optional().default(true),
6948
+ session_id: z2.string().optional(),
6949
+ user_id: z2.string().optional(),
6950
+ include_parent_content: z2.boolean().optional().default(false),
6951
+ retrieval_profile: z2.enum(MCP_RETRIEVAL_PROFILE_VALUES).optional()
6405
6952
  },
6406
6953
  async ({ question, path, workspace_id, project, top_k, include_memories, include_graph, session_id, user_id, include_parent_content, retrieval_profile }) => {
6407
6954
  try {
@@ -6429,12 +6976,12 @@ server.tool(
6429
6976
  "context.claim_verify",
6430
6977
  "Verify whether a claim is supported by retrieved context. Returns supported/partial/unsupported with evidence.",
6431
6978
  {
6432
- claim: z.string().describe("Claim to verify"),
6433
- path: z.string().optional().describe("Workspace path. Defaults to current working directory."),
6434
- workspace_id: z.string().optional(),
6435
- project: z.string().optional(),
6436
- context_ids: z.array(z.string()).optional(),
6437
- strict: z.boolean().optional().default(true)
6979
+ claim: z2.string().describe("Claim to verify"),
6980
+ path: z2.string().optional().describe("Workspace path. Defaults to current working directory."),
6981
+ workspace_id: z2.string().optional(),
6982
+ project: z2.string().optional(),
6983
+ context_ids: z2.array(z2.string()).optional(),
6984
+ strict: z2.boolean().optional().default(true)
6438
6985
  },
6439
6986
  async ({ claim, path, workspace_id, project, context_ids, strict }) => {
6440
6987
  try {
@@ -6504,20 +7051,20 @@ server.tool(
6504
7051
  "context.evidence_answer",
6505
7052
  "Answer a question only when evidence requirements are met. Fails closed with an abstain payload when not verifiable.",
6506
7053
  {
6507
- question: z.string(),
6508
- path: z.string().optional().describe("Workspace path. Defaults to current working directory."),
6509
- workspace_id: z.string().optional(),
6510
- project: z.string().optional(),
6511
- constraints: z.object({
6512
- require_citations: z.boolean().optional().default(true),
6513
- min_evidence_items: z.number().optional().default(2),
6514
- min_confidence: z.number().optional().default(0.65),
6515
- max_staleness_hours: z.number().optional().default(168)
7054
+ question: z2.string(),
7055
+ path: z2.string().optional().describe("Workspace path. Defaults to current working directory."),
7056
+ workspace_id: z2.string().optional(),
7057
+ project: z2.string().optional(),
7058
+ constraints: z2.object({
7059
+ require_citations: z2.boolean().optional().default(true),
7060
+ min_evidence_items: z2.number().optional().default(2),
7061
+ min_confidence: z2.number().optional().default(0.65),
7062
+ max_staleness_hours: z2.number().optional().default(168)
6516
7063
  }).optional(),
6517
- retrieval: z.object({
6518
- top_k: z.number().optional().default(12),
6519
- include_symbols: z.boolean().optional().default(true),
6520
- include_recent_decisions: z.boolean().optional().default(true)
7064
+ retrieval: z2.object({
7065
+ top_k: z2.number().optional().default(12),
7066
+ include_symbols: z2.boolean().optional().default(true),
7067
+ include_recent_decisions: z2.boolean().optional().default(true)
6521
7068
  }).optional()
6522
7069
  },
6523
7070
  async ({ question, path, workspace_id, project, constraints, retrieval }) => {
@@ -6630,18 +7177,18 @@ server.tool(
6630
7177
  "context.query",
6631
7178
  "Use this when answering from project knowledge rather than general model memory. Retrieves packed context and auto-falls back between repo, local, and memory routes based on trust/readiness.",
6632
7179
  {
6633
- project: z.string().optional().describe("Project name or slug (optional if WHISPER_PROJECT is set)"),
6634
- query: z.string().describe("What are you looking for?"),
6635
- path: z.string().optional().describe("Workspace path. Defaults to current working directory."),
6636
- top_k: z.number().optional().default(10).describe("Number of results"),
6637
- chunk_types: z.array(z.string()).optional().describe("Filter: code, function, class, documentation, api_spec, schema, config, text"),
6638
- include_memories: z.boolean().optional().describe("Include relevant memories. Omit to use automatic runtime defaults."),
6639
- include_graph: z.boolean().optional().default(false).describe("Include knowledge graph traversal"),
6640
- user_id: z.string().optional().describe("User ID for memory scoping"),
6641
- session_id: z.string().optional().describe("Session ID for memory scoping"),
6642
- max_tokens: z.number().optional().describe("Max tokens for packed context"),
6643
- include_parent_content: z.boolean().optional().default(false),
6644
- retrieval_profile: z.enum(MCP_RETRIEVAL_PROFILE_VALUES).optional()
7180
+ project: z2.string().optional().describe("Project name or slug (optional if WHISPER_PROJECT is set)"),
7181
+ query: z2.string().describe("What are you looking for?"),
7182
+ path: z2.string().optional().describe("Workspace path. Defaults to current working directory."),
7183
+ top_k: z2.number().optional().default(10).describe("Number of results"),
7184
+ chunk_types: z2.array(z2.string()).optional().describe("Filter: code, function, class, documentation, api_spec, schema, config, text"),
7185
+ include_memories: z2.boolean().optional().describe("Include relevant memories. Omit to use automatic runtime defaults."),
7186
+ include_graph: z2.boolean().optional().default(false).describe("Include knowledge graph traversal"),
7187
+ user_id: z2.string().optional().describe("User ID for memory scoping"),
7188
+ session_id: z2.string().optional().describe("Session ID for memory scoping"),
7189
+ max_tokens: z2.number().optional().describe("Max tokens for packed context"),
7190
+ include_parent_content: z2.boolean().optional().default(false),
7191
+ retrieval_profile: z2.enum(MCP_RETRIEVAL_PROFILE_VALUES).optional()
6645
7192
  },
6646
7193
  async ({ project, query, path, top_k, chunk_types, include_memories, include_graph, user_id, session_id, max_tokens, include_parent_content, retrieval_profile }) => {
6647
7194
  try {
@@ -6668,25 +7215,30 @@ server.tool(
6668
7215
  "memory.add",
6669
7216
  "Store a memory (fact, preference, decision) that persists across conversations. Memories can be scoped to a user, session, or agent.",
6670
7217
  {
6671
- project: z.string().optional().describe("Project name or slug"),
6672
- content: z.string().describe("The memory content to store"),
6673
- memory_type: z.enum(["factual", "preference", "event", "relationship", "opinion", "goal", "instruction"]).optional().default("factual"),
6674
- user_id: z.string().optional().describe("User this memory belongs to"),
6675
- session_id: z.string().optional().describe("Session scope"),
6676
- agent_id: z.string().optional().describe("Agent scope"),
6677
- importance: z.number().optional().default(0.5).describe("Importance 0-1")
7218
+ project: z2.string().optional().describe("Project name or slug"),
7219
+ content: z2.string().describe("The memory content to store"),
7220
+ memory_type: z2.enum(["factual", "preference", "event", "relationship", "opinion", "goal", "instruction"]).optional().default("factual"),
7221
+ scope: z2.enum(["personal", "shared_project"]).optional().default("personal"),
7222
+ user_id: z2.string().optional().describe("User this memory belongs to"),
7223
+ session_id: z2.string().optional().describe("Session scope"),
7224
+ agent_id: z2.string().optional().describe("Agent scope"),
7225
+ importance: z2.number().optional().default(0.5).describe("Importance 0-1")
6678
7226
  },
6679
- async ({ project, content, memory_type, user_id, session_id, agent_id, importance }) => {
7227
+ async ({ project, content, memory_type, scope, user_id, session_id, agent_id, importance }) => {
6680
7228
  try {
6681
- const scope = resolveMcpScope({ project, user_id, session_id });
7229
+ const memoryScope = resolveMemoryScope({ project, user_id, session_id, scope });
6682
7230
  const result = await whisper.addMemory({
6683
- project: scope.project,
7231
+ project: memoryScope.project,
6684
7232
  content,
6685
7233
  memory_type,
6686
- user_id: scope.userId,
6687
- session_id: scope.sessionId,
7234
+ user_id: memoryScope.userId,
7235
+ session_id: memoryScope.sessionId,
6688
7236
  agent_id,
6689
- importance
7237
+ importance,
7238
+ metadata: buildMemoryScopeMetadata({
7239
+ memoryScope,
7240
+ agent_id
7241
+ })
6690
7242
  });
6691
7243
  const memoryId = result?.memory_id || result.id;
6692
7244
  const jobId = result?.job_id;
@@ -6699,12 +7251,16 @@ server.tool(
6699
7251
  mode: mode || null,
6700
7252
  semantic_status: semanticStatus || null,
6701
7253
  memory_type: memory_type || "factual",
7254
+ scope: memoryScope.scopeMode,
6702
7255
  queued: mode === "async" || Boolean(jobId),
6703
7256
  diagnostics: {
6704
7257
  scope: {
6705
- project: scope.project || null,
6706
- user_id: scope.userId,
6707
- session_id: scope.sessionId || null
7258
+ project: memoryScope.project || null,
7259
+ user_id: memoryScope.userId,
7260
+ session_id: memoryScope.sessionId || null,
7261
+ scope: memoryScope.scopeMode,
7262
+ actor_user_id: memoryScope.actorUserId,
7263
+ actor_session_id: memoryScope.actorSessionId || null
6708
7264
  }
6709
7265
  }
6710
7266
  });
@@ -6717,30 +7273,31 @@ server.tool(
6717
7273
  "memory.search",
6718
7274
  "Call this before answering questions about user history (preferences, prior decisions, past tasks, or 'what did we discuss/search'). Returns memory context you would not otherwise know.",
6719
7275
  {
6720
- project: z.string().optional().describe("Project name or slug"),
6721
- query: z.string().describe("What to search for"),
6722
- user_id: z.string().optional().describe("Filter by user"),
6723
- session_id: z.string().optional().describe("Filter by session"),
6724
- top_k: z.number().optional().default(10).describe("Number of results"),
6725
- memory_types: z.array(z.enum(["factual", "preference", "event", "relationship", "opinion", "goal", "instruction"])).optional()
7276
+ project: z2.string().optional().describe("Project name or slug"),
7277
+ query: z2.string().describe("What to search for"),
7278
+ scope: z2.enum(["personal", "shared_project"]).optional().default("personal"),
7279
+ user_id: z2.string().optional().describe("Filter by user"),
7280
+ session_id: z2.string().optional().describe("Filter by session"),
7281
+ top_k: z2.number().optional().default(10).describe("Number of results"),
7282
+ memory_types: z2.array(z2.enum(["factual", "preference", "event", "relationship", "opinion", "goal", "instruction"])).optional()
6726
7283
  },
6727
- async ({ project, query, user_id, session_id, top_k, memory_types }) => {
7284
+ async ({ project, query, scope, user_id, session_id, top_k, memory_types }) => {
6728
7285
  try {
6729
- const scope = resolveMcpScope({ project, user_id, session_id });
7286
+ const memoryScope = resolveMemoryScope({ project, user_id, session_id, scope });
6730
7287
  const results = runtimeClient && (!memory_types || memory_types.length <= 1) ? await runtimeClient.memory.search({
6731
- project: scope.project,
7288
+ project: memoryScope.project,
6732
7289
  query,
6733
- user_id: scope.userId,
6734
- session_id: scope.sessionId,
7290
+ user_id: memoryScope.userId,
7291
+ session_id: memoryScope.sessionId,
6735
7292
  top_k,
6736
7293
  include_pending: true,
6737
7294
  profile: "balanced",
6738
7295
  ...memory_types?.length === 1 ? { memory_type: memory_types[0] } : {}
6739
7296
  }) : await whisper.searchMemoriesSOTA({
6740
- project: scope.project,
7297
+ project: memoryScope.project,
6741
7298
  query,
6742
- user_id: scope.userId,
6743
- session_id: scope.sessionId,
7299
+ user_id: memoryScope.userId,
7300
+ session_id: memoryScope.sessionId,
6744
7301
  top_k,
6745
7302
  memory_types
6746
7303
  });
@@ -6748,15 +7305,17 @@ server.tool(
6748
7305
  return primaryToolSuccess({
6749
7306
  tool: "memory.search",
6750
7307
  query,
6751
- user_id: scope.userId,
6752
- session_id: scope.sessionId,
7308
+ scope: memoryScope.scopeMode,
7309
+ user_id: memoryScope.userId,
7310
+ session_id: memoryScope.sessionId,
6753
7311
  results: normalizedResults,
6754
7312
  count: normalizedResults.length,
6755
7313
  diagnostics: {
6756
7314
  scope: {
6757
- project: scope.project || null,
6758
- user_id: scope.userId,
6759
- session_id: scope.sessionId || null
7315
+ project: memoryScope.project || null,
7316
+ user_id: memoryScope.userId,
7317
+ session_id: memoryScope.sessionId || null,
7318
+ scope: memoryScope.scopeMode
6760
7319
  }
6761
7320
  }
6762
7321
  });
@@ -6794,7 +7353,7 @@ server.tool(
6794
7353
  server.tool(
6795
7354
  "context.list_sources",
6796
7355
  "List all data sources connected to a project.",
6797
- { project: z.string().optional().describe("Project name or slug") },
7356
+ { project: z2.string().optional().describe("Project name or slug") },
6798
7357
  async ({ project }) => {
6799
7358
  try {
6800
7359
  const sourceData = await whisper.listSources(project || DEFAULT_PROJECT);
@@ -6810,38 +7369,38 @@ server.tool(
6810
7369
  "context.add_source",
6811
7370
  "Compatibility learning tool. Add a source to a project with normalized source contract and auto-index by default. Prefer `learn` for new integrations.",
6812
7371
  {
6813
- project: z.string().optional().describe("Project name or slug"),
6814
- type: z.enum(["github", "web", "playwright", "pdf", "local", "slack", "video"]).default("github"),
6815
- name: z.string().optional(),
6816
- auto_index: z.boolean().optional().default(true),
6817
- metadata: z.record(z.string()).optional(),
6818
- ingestion_profile: z.enum(["auto", "repo", "web_docs", "pdf_layout", "video_transcript", "plain_text"]).optional(),
6819
- strategy_override: z.enum(["fixed", "recursive", "semantic", "hierarchical", "adaptive"]).optional(),
6820
- profile_config: z.record(z.any()).optional(),
6821
- owner: z.string().optional(),
6822
- repo: z.string().optional(),
6823
- branch: z.string().optional(),
6824
- paths: z.array(z.string()).optional(),
6825
- url: z.string().url().optional(),
6826
- crawl_depth: z.number().optional(),
6827
- include_paths: z.array(z.string()).optional(),
6828
- exclude_paths: z.array(z.string()).optional(),
6829
- file_path: z.string().optional(),
6830
- path: z.string().optional(),
6831
- glob: z.string().optional(),
6832
- max_files: z.number().optional(),
6833
- max_pages: z.number().optional(),
6834
- extract_mode: z.enum(["text", "structured", "markdown"]).optional(),
6835
- workspace_id: z.string().optional(),
6836
- channel_ids: z.array(z.string()).optional(),
6837
- since: z.string().optional(),
6838
- token: z.string().optional(),
6839
- auth_ref: z.string().optional(),
6840
- platform: z.enum(["youtube", "loom", "generic"]).optional(),
6841
- language: z.string().optional(),
6842
- allow_stt_fallback: z.boolean().optional(),
6843
- max_duration_minutes: z.number().optional(),
6844
- max_chunks: z.number().optional()
7372
+ project: z2.string().optional().describe("Project name or slug"),
7373
+ type: z2.enum(["github", "web", "playwright", "pdf", "local", "slack", "video"]).default("github"),
7374
+ name: z2.string().optional(),
7375
+ auto_index: z2.boolean().optional().default(true),
7376
+ metadata: z2.record(z2.string()).optional(),
7377
+ ingestion_profile: z2.enum(["auto", "repo", "web_docs", "pdf_layout", "video_transcript", "plain_text"]).optional(),
7378
+ strategy_override: z2.enum(["fixed", "recursive", "semantic", "hierarchical", "adaptive"]).optional(),
7379
+ profile_config: z2.record(z2.any()).optional(),
7380
+ owner: z2.string().optional(),
7381
+ repo: z2.string().optional(),
7382
+ branch: z2.string().optional(),
7383
+ paths: z2.array(z2.string()).optional(),
7384
+ url: z2.string().url().optional(),
7385
+ crawl_depth: z2.number().optional(),
7386
+ include_paths: z2.array(z2.string()).optional(),
7387
+ exclude_paths: z2.array(z2.string()).optional(),
7388
+ file_path: z2.string().optional(),
7389
+ path: z2.string().optional(),
7390
+ glob: z2.string().optional(),
7391
+ max_files: z2.number().optional(),
7392
+ max_pages: z2.number().optional(),
7393
+ extract_mode: z2.enum(["text", "structured", "markdown"]).optional(),
7394
+ workspace_id: z2.string().optional(),
7395
+ channel_ids: z2.array(z2.string()).optional(),
7396
+ since: z2.string().optional(),
7397
+ token: z2.string().optional(),
7398
+ auth_ref: z2.string().optional(),
7399
+ platform: z2.enum(["youtube", "loom", "generic"]).optional(),
7400
+ language: z2.string().optional(),
7401
+ allow_stt_fallback: z2.boolean().optional(),
7402
+ max_duration_minutes: z2.number().optional(),
7403
+ max_chunks: z2.number().optional()
6845
7404
  },
6846
7405
  async (input) => {
6847
7406
  try {
@@ -6897,7 +7456,7 @@ server.tool(
6897
7456
  "context.source_status",
6898
7457
  "Get status and stage/progress details for a source sync job.",
6899
7458
  {
6900
- source_id: z.string().describe("Source id")
7459
+ source_id: z2.string().describe("Source id")
6901
7460
  },
6902
7461
  async ({ source_id }) => {
6903
7462
  try {
@@ -6912,12 +7471,12 @@ server.tool(
6912
7471
  "context.add_text",
6913
7472
  "Compatibility learning tool. Add text content to a project's knowledge base. Prefer `learn` for new integrations.",
6914
7473
  {
6915
- project: z.string().optional().describe("Project name or slug"),
6916
- title: z.string().describe("Title for this content"),
6917
- content: z.string().describe("The text content to index"),
6918
- ingestion_profile: z.enum(["auto", "repo", "web_docs", "pdf_layout", "video_transcript", "plain_text"]).optional(),
6919
- strategy_override: z.enum(["fixed", "recursive", "semantic", "hierarchical", "adaptive"]).optional(),
6920
- profile_config: z.record(z.any()).optional()
7474
+ project: z2.string().optional().describe("Project name or slug"),
7475
+ title: z2.string().describe("Title for this content"),
7476
+ content: z2.string().describe("The text content to index"),
7477
+ ingestion_profile: z2.enum(["auto", "repo", "web_docs", "pdf_layout", "video_transcript", "plain_text"]).optional(),
7478
+ strategy_override: z2.enum(["fixed", "recursive", "semantic", "hierarchical", "adaptive"]).optional(),
7479
+ profile_config: z2.record(z2.any()).optional()
6921
7480
  },
6922
7481
  async ({ project, title, content, ingestion_profile, strategy_override, profile_config }) => {
6923
7482
  try {
@@ -6945,18 +7504,18 @@ server.tool(
6945
7504
  "context.add_document",
6946
7505
  "Compatibility learning tool. Ingest a document into project knowledge. Supports plain text and video URLs. Prefer `learn` for new integrations.",
6947
7506
  {
6948
- project: z.string().optional().describe("Project name or slug"),
6949
- source_type: z.enum(["text", "video"]).default("text"),
6950
- title: z.string().optional().describe("Title for text documents"),
6951
- content: z.string().optional().describe("Text document content"),
6952
- url: z.string().url().optional().describe("Video URL when source_type=video"),
6953
- auto_sync: z.boolean().optional().default(true),
6954
- tags: z.array(z.string()).optional(),
6955
- platform: z.enum(["youtube", "loom", "generic"]).optional(),
6956
- language: z.string().optional(),
6957
- ingestion_profile: z.enum(["auto", "repo", "web_docs", "pdf_layout", "video_transcript", "plain_text"]).optional(),
6958
- strategy_override: z.enum(["fixed", "recursive", "semantic", "hierarchical", "adaptive"]).optional(),
6959
- profile_config: z.record(z.any()).optional()
7507
+ project: z2.string().optional().describe("Project name or slug"),
7508
+ source_type: z2.enum(["text", "video"]).default("text"),
7509
+ title: z2.string().optional().describe("Title for text documents"),
7510
+ content: z2.string().optional().describe("Text document content"),
7511
+ url: z2.string().url().optional().describe("Video URL when source_type=video"),
7512
+ auto_sync: z2.boolean().optional().default(true),
7513
+ tags: z2.array(z2.string()).optional(),
7514
+ platform: z2.enum(["youtube", "loom", "generic"]).optional(),
7515
+ language: z2.string().optional(),
7516
+ ingestion_profile: z2.enum(["auto", "repo", "web_docs", "pdf_layout", "video_transcript", "plain_text"]).optional(),
7517
+ strategy_override: z2.enum(["fixed", "recursive", "semantic", "hierarchical", "adaptive"]).optional(),
7518
+ profile_config: z2.record(z2.any()).optional()
6960
7519
  },
6961
7520
  async ({ project, source_type, title, content, url, auto_sync, tags, platform, language, ingestion_profile, strategy_override, profile_config }) => {
6962
7521
  try {
@@ -7011,23 +7570,24 @@ server.tool(
7011
7570
  "memory.search_sota",
7012
7571
  "SOTA memory search with temporal reasoning and relation graphs. Searches memories with support for temporal queries ('what did I say yesterday?'), type filtering, and knowledge graph traversal.",
7013
7572
  {
7014
- project: z.string().optional().describe("Project name or slug"),
7015
- query: z.string().describe("Search query (supports temporal: 'yesterday', 'last week')"),
7016
- user_id: z.string().optional().describe("Filter by user"),
7017
- session_id: z.string().optional().describe("Filter by session"),
7018
- question_date: z.string().optional().describe("ISO datetime for temporal grounding"),
7019
- memory_types: z.array(z.enum(["factual", "preference", "event", "relationship", "opinion", "goal", "instruction"])).optional(),
7020
- top_k: z.number().optional().default(10),
7021
- include_relations: z.boolean().optional().default(true).describe("Include related memories via knowledge graph")
7573
+ project: z2.string().optional().describe("Project name or slug"),
7574
+ query: z2.string().describe("Search query (supports temporal: 'yesterday', 'last week')"),
7575
+ scope: z2.enum(["personal", "shared_project"]).optional().default("personal"),
7576
+ user_id: z2.string().optional().describe("Filter by user"),
7577
+ session_id: z2.string().optional().describe("Filter by session"),
7578
+ question_date: z2.string().optional().describe("ISO datetime for temporal grounding"),
7579
+ memory_types: z2.array(z2.enum(["factual", "preference", "event", "relationship", "opinion", "goal", "instruction"])).optional(),
7580
+ top_k: z2.number().optional().default(10),
7581
+ include_relations: z2.boolean().optional().default(true).describe("Include related memories via knowledge graph")
7022
7582
  },
7023
- async ({ project, query, user_id, session_id, question_date, memory_types, top_k, include_relations }) => {
7583
+ async ({ project, query, scope, user_id, session_id, question_date, memory_types, top_k, include_relations }) => {
7024
7584
  try {
7025
- const scope = resolveMcpScope({ project, user_id, session_id });
7585
+ const memoryScope = resolveMemoryScope({ project, user_id, session_id, scope });
7026
7586
  const results = await whisper.searchMemoriesSOTA({
7027
- project: scope.project,
7587
+ project: memoryScope.project,
7028
7588
  query,
7029
- user_id: scope.userId,
7030
- session_id: scope.sessionId,
7589
+ user_id: memoryScope.userId,
7590
+ session_id: memoryScope.sessionId,
7031
7591
  question_date,
7032
7592
  memory_types,
7033
7593
  top_k,
@@ -7037,17 +7597,19 @@ server.tool(
7037
7597
  return primaryToolSuccess({
7038
7598
  tool: "memory.search_sota",
7039
7599
  query,
7040
- user_id: scope.userId,
7041
- session_id: scope.sessionId || null,
7600
+ scope: memoryScope.scopeMode,
7601
+ user_id: memoryScope.userId,
7602
+ session_id: memoryScope.sessionId || null,
7042
7603
  question_date: question_date || null,
7043
7604
  include_relations,
7044
7605
  results: normalizedResults,
7045
7606
  count: normalizedResults.length,
7046
7607
  diagnostics: {
7047
7608
  scope: {
7048
- project: scope.project || null,
7049
- user_id: scope.userId,
7050
- session_id: scope.sessionId || null
7609
+ project: memoryScope.project || null,
7610
+ user_id: memoryScope.userId,
7611
+ session_id: memoryScope.sessionId || null,
7612
+ scope: memoryScope.scopeMode
7051
7613
  }
7052
7614
  }
7053
7615
  });
@@ -7060,40 +7622,51 @@ server.tool(
7060
7622
  "memory.ingest_conversation",
7061
7623
  "Compatibility learning tool. Extract memories from a conversation session. Prefer `learn` for new integrations.",
7062
7624
  {
7063
- project: z.string().optional().describe("Project name or slug"),
7064
- session_id: z.string().describe("Session identifier"),
7065
- user_id: z.string().optional().describe("User identifier"),
7066
- messages: z.array(z.object({
7067
- role: z.string(),
7068
- content: z.string(),
7069
- timestamp: z.string()
7625
+ project: z2.string().optional().describe("Project name or slug"),
7626
+ session_id: z2.string().describe("Session identifier"),
7627
+ user_id: z2.string().optional().describe("User identifier"),
7628
+ messages: z2.array(z2.object({
7629
+ role: z2.string(),
7630
+ content: z2.string(),
7631
+ timestamp: z2.string()
7070
7632
  })).describe("Array of conversation messages with timestamps")
7071
7633
  },
7072
7634
  async ({ project, session_id, user_id, messages }) => {
7073
7635
  try {
7074
- const scope = resolveMcpScope({ project, user_id, session_id });
7636
+ const memoryScope = resolveMemoryScope({
7637
+ project,
7638
+ user_id,
7639
+ session_id,
7640
+ scope: "personal"
7641
+ });
7075
7642
  const normalizedMessages = messages.map((message) => ({
7076
7643
  role: message.role,
7077
7644
  content: message.content,
7078
7645
  timestamp: message.timestamp
7079
7646
  }));
7080
7647
  const result = await ingestSessionWithSyncFallback({
7081
- project: scope.project,
7082
- session_id: scope.sessionId,
7083
- user_id: scope.userId,
7648
+ project: memoryScope.project,
7649
+ session_id: memoryScope.sessionId,
7650
+ user_id: memoryScope.userId,
7084
7651
  messages: normalizedMessages
7085
7652
  });
7086
- return {
7087
- content: [{
7088
- type: "text",
7089
- text: `Processed ${normalizedMessages.length} messages:
7090
- - Scope ${scope.project || "auto-project"} / ${scope.userId} / ${scope.sessionId}
7091
- - Created ${result.memories_created} memories
7092
- - Detected ${result.relations_created} relations
7093
- - Updated ${result.memories_invalidated} outdated memories` + (result.errors && result.errors.length > 0 ? `
7094
- - Errors: ${result.errors.join(", ")}` : "")
7095
- }]
7096
- };
7653
+ return primaryToolSuccess({
7654
+ tool: "memory.ingest_conversation",
7655
+ messages_processed: normalizedMessages.length,
7656
+ memories_created: result.memories_created,
7657
+ relations_created: result.relations_created,
7658
+ memories_invalidated: result.memories_invalidated,
7659
+ errors: result.errors || [],
7660
+ scope: memoryScope.scopeMode,
7661
+ diagnostics: {
7662
+ scope: {
7663
+ project: memoryScope.project || null,
7664
+ user_id: memoryScope.userId,
7665
+ session_id: memoryScope.sessionId || null,
7666
+ scope: memoryScope.scopeMode
7667
+ }
7668
+ }
7669
+ });
7097
7670
  } catch (error) {
7098
7671
  return { content: [{ type: "text", text: `Error: ${error.message}` }] };
7099
7672
  }
@@ -7103,11 +7676,11 @@ server.tool(
7103
7676
  "research.oracle",
7104
7677
  "Oracle Research Mode - Tree-guided document navigation with multi-step reasoning. More precise than standard search, especially for bleeding-edge features.",
7105
7678
  {
7106
- project: z.string().optional().describe("Project name or slug"),
7107
- query: z.string().describe("Research question"),
7108
- mode: z.enum(["search", "research"]).optional().default("search").describe("'search' for tree-guided, 'research' for multi-step reasoning"),
7109
- max_results: z.number().optional().default(5),
7110
- max_steps: z.number().optional().default(5).describe("For research mode: max reasoning steps")
7679
+ project: z2.string().optional().describe("Project name or slug"),
7680
+ query: z2.string().describe("Research question"),
7681
+ mode: z2.enum(["search", "research"]).optional().default("search").describe("'search' for tree-guided, 'research' for multi-step reasoning"),
7682
+ max_results: z2.number().optional().default(5),
7683
+ max_steps: z2.number().optional().default(5).describe("For research mode: max reasoning steps")
7111
7684
  },
7112
7685
  async ({ project, query, mode, max_results, max_steps }) => {
7113
7686
  try {
@@ -7151,13 +7724,13 @@ server.tool(
7151
7724
  "index.autosubscribe_deps",
7152
7725
  "Automatically index a project's dependencies (package.json, requirements.txt, etc.). Resolves docs URLs and indexes documentation.",
7153
7726
  {
7154
- project: z.string().optional().describe("Project name or slug"),
7155
- source_type: z.enum(["github", "local"]).describe("Source location"),
7156
- github_owner: z.string().optional().describe("For GitHub: owner/org name"),
7157
- github_repo: z.string().optional().describe("For GitHub: repository name"),
7158
- local_path: z.string().optional().describe("For local: path to dependency file"),
7159
- dependency_file: z.enum(["package.json", "requirements.txt", "Cargo.toml", "go.mod", "Gemfile"]).optional(),
7160
- index_limit: z.number().optional().default(20).describe("Max dependencies to index")
7727
+ project: z2.string().optional().describe("Project name or slug"),
7728
+ source_type: z2.enum(["github", "local"]).describe("Source location"),
7729
+ github_owner: z2.string().optional().describe("For GitHub: owner/org name"),
7730
+ github_repo: z2.string().optional().describe("For GitHub: repository name"),
7731
+ local_path: z2.string().optional().describe("For local: path to dependency file"),
7732
+ dependency_file: z2.enum(["package.json", "requirements.txt", "Cargo.toml", "go.mod", "Gemfile"]).optional(),
7733
+ index_limit: z2.number().optional().default(20).describe("Max dependencies to index")
7161
7734
  },
7162
7735
  async ({ project, source_type, github_owner, github_repo, local_path, dependency_file, index_limit }) => {
7163
7736
  try {
@@ -7184,10 +7757,10 @@ server.tool(
7184
7757
  "context.share",
7185
7758
  "Create a shareable snapshot of a conversation with memories. Returns a URL that can be shared or resumed later.",
7186
7759
  {
7187
- project: z.string().optional().describe("Project name or slug"),
7188
- session_id: z.string().describe("Session to share"),
7189
- title: z.string().optional().describe("Title for the shared context"),
7190
- expiry_days: z.number().optional().default(30).describe("Days until expiry")
7760
+ project: z2.string().optional().describe("Project name or slug"),
7761
+ session_id: z2.string().describe("Session to share"),
7762
+ title: z2.string().optional().describe("Title for the shared context"),
7763
+ expiry_days: z2.number().optional().default(30).describe("Days until expiry")
7191
7764
  },
7192
7765
  async ({ project, session_id, title, expiry_days }) => {
7193
7766
  try {
@@ -7222,9 +7795,9 @@ server.tool(
7222
7795
  "memory.consolidate",
7223
7796
  "Find and merge duplicate memories to reduce bloat. Uses vector similarity + LLM merging.",
7224
7797
  {
7225
- project: z.string().optional().describe("Project name or slug"),
7226
- similarity_threshold: z.number().optional().default(0.95).describe("Similarity threshold (0-1)"),
7227
- dry_run: z.boolean().optional().default(false).describe("Preview without merging")
7798
+ project: z2.string().optional().describe("Project name or slug"),
7799
+ similarity_threshold: z2.number().optional().default(0.95).describe("Similarity threshold (0-1)"),
7800
+ dry_run: z2.boolean().optional().default(false).describe("Preview without merging")
7228
7801
  },
7229
7802
  async ({ project, similarity_threshold, dry_run }) => {
7230
7803
  try {
@@ -7265,8 +7838,8 @@ server.tool(
7265
7838
  "context.cost_summary",
7266
7839
  "Get cost tracking summary showing spending by model and task. Includes savings vs always-Opus.",
7267
7840
  {
7268
- project: z.string().optional().describe("Project name or slug (optional for org-wide)"),
7269
- days: z.number().optional().default(30).describe("Time period in days")
7841
+ project: z2.string().optional().describe("Project name or slug (optional for org-wide)"),
7842
+ days: z2.number().optional().default(30).describe("Time period in days")
7270
7843
  },
7271
7844
  async ({ project, days }) => {
7272
7845
  try {
@@ -7308,16 +7881,19 @@ server.tool(
7308
7881
  "memory.forget",
7309
7882
  "Delete or invalidate memories with immutable audit logging.",
7310
7883
  {
7311
- workspace_id: z.string().optional(),
7312
- project: z.string().optional(),
7313
- target: z.object({
7314
- memory_id: z.string().optional(),
7315
- query: z.string().optional()
7884
+ workspace_id: z2.string().optional(),
7885
+ project: z2.string().optional(),
7886
+ scope: z2.enum(["personal", "shared_project"]).optional().default("personal"),
7887
+ user_id: z2.string().optional(),
7888
+ session_id: z2.string().optional(),
7889
+ target: z2.object({
7890
+ memory_id: z2.string().optional(),
7891
+ query: z2.string().optional()
7316
7892
  }),
7317
- mode: z.enum(["delete", "invalidate"]).optional().default("invalidate"),
7318
- reason: z.string().optional()
7893
+ mode: z2.enum(["delete", "invalidate"]).optional().default("invalidate"),
7894
+ reason: z2.string().optional()
7319
7895
  },
7320
- async ({ workspace_id, project, target, mode, reason }) => {
7896
+ async ({ workspace_id, project, scope, user_id, session_id, target, mode, reason }) => {
7321
7897
  try {
7322
7898
  if (!target.memory_id && !target.query) {
7323
7899
  return { content: [{ type: "text", text: "Error: target.memory_id or target.query is required." }] };
@@ -7326,8 +7902,22 @@ server.tool(
7326
7902
  let queryResolution = null;
7327
7903
  const now = (/* @__PURE__ */ new Date()).toISOString();
7328
7904
  const actor = process.env.WHISPER_AGENT_ID || process.env.USERNAME || "api_key_principal";
7329
- const resolvedProject = await resolveProjectRef(project);
7905
+ const memoryScope = resolveMemoryScope({
7906
+ project,
7907
+ user_id,
7908
+ session_id,
7909
+ scope
7910
+ });
7911
+ const resolvedProject = await resolveProjectRef(memoryScope.project);
7912
+ async function assertScopedMemoryId(memoryId) {
7913
+ const memoryResult = await whisper.getMemory(memoryId);
7914
+ const memory = memoryResult?.memory || memoryResult;
7915
+ if (!isMemoryInScope({ memory, memoryScope })) {
7916
+ throw new Error(`memory_id ${memoryId} is outside the effective ${memoryScope.scopeMode} scope.`);
7917
+ }
7918
+ }
7330
7919
  async function applyToMemory(memoryId) {
7920
+ await assertScopedMemoryId(memoryId);
7331
7921
  if (mode === "delete") {
7332
7922
  await whisper.deleteMemory(memoryId);
7333
7923
  } else {
@@ -7342,7 +7932,10 @@ server.tool(
7342
7932
  project: resolvedProject,
7343
7933
  content: `Invalidated memory ${memoryId} at ${now}. Reason: ${reason || "No reason provided"}`,
7344
7934
  memory_type: "instruction",
7345
- importance: 1
7935
+ user_id: memoryScope.userId,
7936
+ session_id: memoryScope.sessionId,
7937
+ importance: 1,
7938
+ metadata: buildMemoryScopeMetadata({ memoryScope })
7346
7939
  });
7347
7940
  }
7348
7941
  }
@@ -7374,13 +7967,25 @@ server.tool(
7374
7967
  mode: audit2.mode,
7375
7968
  ...audit2.reason ? { reason: audit2.reason } : {}
7376
7969
  },
7377
- warning: "No project resolved for query-based forget. Nothing was changed."
7970
+ warning: "No project resolved for query-based forget. Nothing was changed.",
7971
+ diagnostics: {
7972
+ scope: {
7973
+ project: memoryScope.project || null,
7974
+ user_id: memoryScope.userId,
7975
+ session_id: memoryScope.sessionId || null,
7976
+ scope: memoryScope.scopeMode,
7977
+ actor_user_id: memoryScope.actorUserId,
7978
+ actor_session_id: memoryScope.actorSessionId || null
7979
+ }
7980
+ }
7378
7981
  };
7379
7982
  return { content: [{ type: "text", text: JSON.stringify(payload2, null, 2) }] };
7380
7983
  }
7381
7984
  const search = await whisper.searchMemoriesSOTA({
7382
7985
  project: resolvedProject,
7383
7986
  query: target.query || "",
7987
+ user_id: memoryScope.userId,
7988
+ session_id: memoryScope.sessionId,
7384
7989
  top_k: 25,
7385
7990
  include_relations: false,
7386
7991
  include_pending: true
@@ -7393,7 +7998,17 @@ server.tool(
7393
7998
  status: "completed",
7394
7999
  affected_ids: affectedIds,
7395
8000
  warning: resolved.warning || "Query did not resolve to a reliable memory match. No memories were changed.",
7396
- resolved_by: resolved.resolved_by
8001
+ resolved_by: resolved.resolved_by,
8002
+ diagnostics: {
8003
+ scope: {
8004
+ project: resolvedProject,
8005
+ user_id: memoryScope.userId,
8006
+ session_id: memoryScope.sessionId || null,
8007
+ scope: memoryScope.scopeMode,
8008
+ actor_user_id: memoryScope.actorUserId,
8009
+ actor_session_id: memoryScope.actorSessionId || null
8010
+ }
8011
+ }
7397
8012
  };
7398
8013
  return { content: [{ type: "text", text: JSON.stringify(payload2, null, 2) }] };
7399
8014
  }
@@ -7422,6 +8037,16 @@ server.tool(
7422
8037
  called_at: audit.called_at,
7423
8038
  mode: audit.mode,
7424
8039
  ...audit.reason ? { reason: audit.reason } : {}
8040
+ },
8041
+ diagnostics: {
8042
+ scope: {
8043
+ project: resolvedProject || null,
8044
+ user_id: memoryScope.userId,
8045
+ session_id: memoryScope.sessionId || null,
8046
+ scope: memoryScope.scopeMode,
8047
+ actor_user_id: memoryScope.actorUserId,
8048
+ actor_session_id: memoryScope.actorSessionId || null
8049
+ }
7425
8050
  }
7426
8051
  };
7427
8052
  return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
@@ -7434,8 +8059,8 @@ server.tool(
7434
8059
  "context.export_bundle",
7435
8060
  "Export project/workspace memory and context to a portable bundle with checksum.",
7436
8061
  {
7437
- workspace_id: z.string().optional(),
7438
- project: z.string().optional()
8062
+ workspace_id: z2.string().optional(),
8063
+ project: z2.string().optional()
7439
8064
  },
7440
8065
  async ({ workspace_id, project }) => {
7441
8066
  try {
@@ -7485,26 +8110,26 @@ server.tool(
7485
8110
  "context.import_bundle",
7486
8111
  "Import a portable context bundle with merge/replace modes and checksum verification.",
7487
8112
  {
7488
- workspace_id: z.string().optional(),
7489
- bundle: z.object({
7490
- bundle_version: z.string(),
7491
- workspace_id: z.string(),
7492
- project: z.string().optional(),
7493
- exported_at: z.string(),
7494
- contents: z.object({
7495
- memories: z.array(z.any()).default([]),
7496
- entities: z.array(z.any()).default([]),
7497
- decisions: z.array(z.any()).default([]),
7498
- failures: z.array(z.any()).default([]),
7499
- annotations: z.array(z.any()).default([]),
7500
- session_summaries: z.array(z.any()).default([]),
7501
- documents: z.array(z.any()).default([]),
7502
- index_metadata: z.record(z.any()).default({})
8113
+ workspace_id: z2.string().optional(),
8114
+ bundle: z2.object({
8115
+ bundle_version: z2.string(),
8116
+ workspace_id: z2.string(),
8117
+ project: z2.string().optional(),
8118
+ exported_at: z2.string(),
8119
+ contents: z2.object({
8120
+ memories: z2.array(z2.any()).default([]),
8121
+ entities: z2.array(z2.any()).default([]),
8122
+ decisions: z2.array(z2.any()).default([]),
8123
+ failures: z2.array(z2.any()).default([]),
8124
+ annotations: z2.array(z2.any()).default([]),
8125
+ session_summaries: z2.array(z2.any()).default([]),
8126
+ documents: z2.array(z2.any()).default([]),
8127
+ index_metadata: z2.record(z2.any()).default({})
7503
8128
  }),
7504
- checksum: z.string()
8129
+ checksum: z2.string()
7505
8130
  }),
7506
- mode: z.enum(["merge", "replace"]).optional().default("merge"),
7507
- dedupe_strategy: z.enum(["semantic", "id", "none"]).optional().default("semantic")
8131
+ mode: z2.enum(["merge", "replace"]).optional().default("merge"),
8132
+ dedupe_strategy: z2.enum(["semantic", "id", "none"]).optional().default("semantic")
7508
8133
  },
7509
8134
  async ({ workspace_id, bundle, mode, dedupe_strategy }) => {
7510
8135
  try {
@@ -7648,13 +8273,13 @@ server.tool(
7648
8273
  "context.diff",
7649
8274
  "Return deterministic context changes from an explicit anchor (session_id, timestamp, or commit).",
7650
8275
  {
7651
- workspace_id: z.string().optional(),
7652
- anchor: z.object({
7653
- type: z.enum(["session_id", "timestamp", "commit"]),
7654
- value: z.string()
8276
+ workspace_id: z2.string().optional(),
8277
+ anchor: z2.object({
8278
+ type: z2.enum(["session_id", "timestamp", "commit"]),
8279
+ value: z2.string()
7655
8280
  }).optional(),
7656
- scope: z.object({
7657
- include: z.array(z.enum(["decisions", "failures", "entities", "documents", "summaries"])).optional().default(["decisions", "failures", "entities", "documents", "summaries"])
8281
+ scope: z2.object({
8282
+ include: z2.array(z2.enum(["decisions", "failures", "entities", "documents", "summaries"])).optional().default(["decisions", "failures", "entities", "documents", "summaries"])
7658
8283
  }).optional()
7659
8284
  },
7660
8285
  async ({ workspace_id, anchor, scope }) => {
@@ -7765,12 +8390,12 @@ server.tool(
7765
8390
  "code.search_semantic",
7766
8391
  "Semantically search a local codebase without pre-indexing. Unlike grep/ripgrep, this understands meaning \u2014 so 'find authentication logic' finds auth code even if it doesn't literally say 'auth'. Uses vector embeddings via the Whisper API. Perfect for exploring unfamiliar codebases.",
7767
8392
  {
7768
- query: z.string().describe("Natural language description of what you're looking for. E.g. 'authentication and session management', 'database connection pooling', 'error handling middleware'"),
7769
- path: z.string().optional().describe("Absolute path to the codebase root. Defaults to current working directory."),
7770
- file_types: z.array(z.string()).optional().describe("Limit to specific extensions e.g. ['ts', 'py']. Defaults to all common code files."),
7771
- top_k: z.number().optional().default(10).describe("Number of most relevant files to return"),
7772
- threshold: z.number().optional().default(0.2).describe("Minimum similarity score 0-1. Lower = more results but less precise."),
7773
- max_files: z.number().optional().default(150).describe("Max files to scan. For large codebases, narrow with file_types instead of raising this.")
8393
+ query: z2.string().describe("Natural language description of what you're looking for. E.g. 'authentication and session management', 'database connection pooling', 'error handling middleware'"),
8394
+ path: z2.string().optional().describe("Absolute path to the codebase root. Defaults to current working directory."),
8395
+ file_types: z2.array(z2.string()).optional().describe("Limit to specific extensions e.g. ['ts', 'py']. Defaults to all common code files."),
8396
+ top_k: z2.number().optional().default(10).describe("Number of most relevant files to return"),
8397
+ threshold: z2.number().optional().default(0.2).describe("Minimum similarity score 0-1. Lower = more results but less precise."),
8398
+ max_files: z2.number().optional().default(150).describe("Max files to scan. For large codebases, narrow with file_types instead of raising this.")
7774
8399
  },
7775
8400
  async ({ query, path: searchPath, file_types, top_k, threshold, max_files }) => {
7776
8401
  try {
@@ -7845,13 +8470,13 @@ server.tool(
7845
8470
  "code.search_text",
7846
8471
  "Search files and content in a local directory without requiring pre-indexing. Uses ripgrep when available, falls back to Node.js. Great for finding files, functions, patterns, or any text across a codebase instantly.",
7847
8472
  {
7848
- query: z.string().describe("What to search for \u2014 natural language keyword, function name, pattern, etc."),
7849
- path: z.string().optional().describe("Absolute path to search in. Defaults to current working directory."),
7850
- mode: z.enum(["content", "filename", "both"]).optional().default("both").describe("Search file contents, filenames, or both"),
7851
- file_types: z.array(z.string()).optional().describe("Limit to these file extensions e.g. ['ts', 'js', 'py', 'go']"),
7852
- max_results: z.number().optional().default(20).describe("Max number of matching files to return"),
7853
- context_lines: z.number().optional().default(2).describe("Lines of context around each match"),
7854
- case_sensitive: z.boolean().optional().default(false)
8473
+ query: z2.string().describe("What to search for \u2014 natural language keyword, function name, pattern, etc."),
8474
+ path: z2.string().optional().describe("Absolute path to search in. Defaults to current working directory."),
8475
+ mode: z2.enum(["content", "filename", "both"]).optional().default("both").describe("Search file contents, filenames, or both"),
8476
+ file_types: z2.array(z2.string()).optional().describe("Limit to these file extensions e.g. ['ts', 'js', 'py', 'go']"),
8477
+ max_results: z2.number().optional().default(20).describe("Max number of matching files to return"),
8478
+ context_lines: z2.number().optional().default(2).describe("Lines of context around each match"),
8479
+ case_sensitive: z2.boolean().optional().default(false)
7855
8480
  },
7856
8481
  async ({ query, path: searchPath, mode, file_types, max_results, context_lines, case_sensitive }) => {
7857
8482
  const rootPath = searchPath || process.cwd();
@@ -7984,13 +8609,13 @@ server.tool(
7984
8609
  "code.semantic_documents",
7985
8610
  "Semantic vector search over provided documents. Uses embeddings to find semantically similar content. Perfect for AI code search, finding similar functions, or searching by meaning rather than keywords.",
7986
8611
  {
7987
- query: z.string().describe("What to search for semantically (e.g. 'authentication logic', 'database connection')"),
7988
- documents: z.array(z.object({
7989
- id: z.string().describe("Unique identifier (file path, URL, or any ID)"),
7990
- content: z.string().describe("The text content to search in")
8612
+ query: z2.string().describe("What to search for semantically (e.g. 'authentication logic', 'database connection')"),
8613
+ documents: z2.array(z2.object({
8614
+ id: z2.string().describe("Unique identifier (file path, URL, or any ID)"),
8615
+ content: z2.string().describe("The text content to search in")
7991
8616
  })).describe("Documents to search over"),
7992
- top_k: z.number().optional().default(5).describe("Number of results to return"),
7993
- threshold: z.number().optional().default(0.3).describe("Minimum similarity score 0-1")
8617
+ top_k: z2.number().optional().default(5).describe("Number of results to return"),
8618
+ threshold: z2.number().optional().default(0.3).describe("Minimum similarity score 0-1")
7994
8619
  },
7995
8620
  async ({ query, documents, top_k, threshold }) => {
7996
8621
  try {
@@ -8028,14 +8653,14 @@ server.tool(
8028
8653
  "search",
8029
8654
  "Primary retrieval alias. Call this whenever the user asks to find or recall context: use `query` for semantic retrieval, `id` for exact memory fetch, or both for hybrid recall.",
8030
8655
  {
8031
- project: z.string().optional().describe("Project name or slug"),
8032
- query: z.string().optional().describe("Semantic retrieval query"),
8033
- id: z.string().optional().describe("Exact memory id to fetch"),
8034
- top_k: z.number().optional().default(10),
8035
- include_memories: z.boolean().optional().default(false),
8036
- include_graph: z.boolean().optional().default(false),
8037
- user_id: z.string().optional(),
8038
- session_id: z.string().optional()
8656
+ project: z2.string().optional().describe("Project name or slug"),
8657
+ query: z2.string().optional().describe("Semantic retrieval query"),
8658
+ id: z2.string().optional().describe("Exact memory id to fetch"),
8659
+ top_k: z2.number().optional().default(10),
8660
+ include_memories: z2.boolean().optional().default(false),
8661
+ include_graph: z2.boolean().optional().default(false),
8662
+ user_id: z2.string().optional(),
8663
+ session_id: z2.string().optional()
8039
8664
  },
8040
8665
  async ({ project, query, id, top_k, include_memories, include_graph, user_id, session_id }) => {
8041
8666
  try {
@@ -8099,12 +8724,12 @@ server.tool(
8099
8724
  "search_code",
8100
8725
  "Semantically search a local codebase by meaning. Use this for questions like 'where is auth handled?' or 'find retry logic'.",
8101
8726
  {
8102
- query: z.string().describe("Natural-language code search query"),
8103
- path: z.string().optional().describe("Codebase root. Defaults to current working directory."),
8104
- file_types: z.array(z.string()).optional(),
8105
- top_k: z.number().optional().default(10),
8106
- threshold: z.number().optional().default(0.2),
8107
- max_files: z.number().optional().default(150)
8727
+ query: z2.string().describe("Natural-language code search query"),
8728
+ path: z2.string().optional().describe("Codebase root. Defaults to current working directory."),
8729
+ file_types: z2.array(z2.string()).optional(),
8730
+ top_k: z2.number().optional().default(10),
8731
+ threshold: z2.number().optional().default(0.2),
8732
+ max_files: z2.number().optional().default(150)
8108
8733
  },
8109
8734
  async ({ query, path, file_types, top_k, threshold, max_files }) => {
8110
8735
  try {
@@ -8125,11 +8750,11 @@ server.tool(
8125
8750
  "grep",
8126
8751
  "Regex or text search across a local codebase. Use this when you know the symbol, string, or pattern you want.",
8127
8752
  {
8128
- query: z.string().describe("Text or regex-like pattern to search for"),
8129
- path: z.string().optional().describe("Search root. Defaults to current working directory."),
8130
- file_types: z.array(z.string()).optional(),
8131
- max_results: z.number().optional().default(20),
8132
- case_sensitive: z.boolean().optional().default(false)
8753
+ query: z2.string().describe("Text or regex-like pattern to search for"),
8754
+ path: z2.string().optional().describe("Search root. Defaults to current working directory."),
8755
+ file_types: z2.array(z2.string()).optional(),
8756
+ max_results: z2.number().optional().default(20),
8757
+ case_sensitive: z2.boolean().optional().default(false)
8133
8758
  },
8134
8759
  async ({ query, path, file_types, max_results, case_sensitive }) => {
8135
8760
  const rootPath = path || process.cwd();
@@ -8177,9 +8802,9 @@ server.tool(
8177
8802
  "read",
8178
8803
  "Read a local file with optional line ranges. Use this after search or grep when you want the actual source.",
8179
8804
  {
8180
- path: z.string().describe("Absolute or relative path to the file to read."),
8181
- start_line: z.number().optional().default(1),
8182
- end_line: z.number().optional().default(200)
8805
+ path: z2.string().describe("Absolute or relative path to the file to read."),
8806
+ start_line: z2.number().optional().default(1),
8807
+ end_line: z2.number().optional().default(200)
8183
8808
  },
8184
8809
  async ({ path, start_line, end_line }) => {
8185
8810
  try {
@@ -8204,9 +8829,9 @@ server.tool(
8204
8829
  "explore",
8205
8830
  "Browse a repository tree or directory structure. Use this to orient yourself before reading files.",
8206
8831
  {
8207
- path: z.string().optional().describe("Root directory to inspect. Defaults to current working directory."),
8208
- max_depth: z.number().optional().default(3),
8209
- max_entries: z.number().optional().default(200)
8832
+ path: z2.string().optional().describe("Root directory to inspect. Defaults to current working directory."),
8833
+ max_depth: z2.number().optional().default(3),
8834
+ max_entries: z2.number().optional().default(200)
8210
8835
  },
8211
8836
  async ({ path, max_depth, max_entries }) => {
8212
8837
  try {
@@ -8235,11 +8860,11 @@ server.tool(
8235
8860
  "research",
8236
8861
  "Run deeper research over indexed sources. Use this when search is not enough and you want synthesis or multi-step investigation.",
8237
8862
  {
8238
- project: z.string().optional(),
8239
- query: z.string().describe("Research question"),
8240
- mode: z.enum(["search", "research"]).optional().default("research"),
8241
- max_results: z.number().optional().default(5),
8242
- max_steps: z.number().optional().default(5)
8863
+ project: z2.string().optional(),
8864
+ query: z2.string().describe("Research question"),
8865
+ mode: z2.enum(["search", "research"]).optional().default("research"),
8866
+ max_results: z2.number().optional().default(5),
8867
+ max_steps: z2.number().optional().default(5)
8243
8868
  },
8244
8869
  async ({ project, query, mode, max_results, max_steps }) => {
8245
8870
  try {
@@ -8261,27 +8886,27 @@ server.tool(
8261
8886
  "index",
8262
8887
  "Administrative indexing tool. Call this when retrieval is stale/missing or the user asks to connect new data. Use action='source' to add GitHub/web/pdf/local/slack/video, action='workspace' to refresh local workspace metadata.",
8263
8888
  {
8264
- action: z.enum(["source", "workspace"]).default("source"),
8265
- project: z.string().optional(),
8266
- type: z.enum(["github", "web", "playwright", "pdf", "local", "slack", "video"]).optional(),
8267
- name: z.string().optional(),
8268
- owner: z.string().optional(),
8269
- repo: z.string().optional(),
8270
- branch: z.string().optional(),
8271
- url: z.string().optional(),
8272
- file_path: z.string().optional(),
8273
- path: z.string().optional(),
8274
- glob: z.string().optional(),
8275
- max_files: z.number().optional(),
8276
- channel_ids: z.array(z.string()).optional(),
8277
- token: z.string().optional(),
8278
- workspace_id: z.string().optional(),
8279
- mode: z.enum(["full", "incremental"]).optional().default("incremental"),
8280
- platform: z.enum(["youtube", "loom", "generic"]).optional(),
8281
- language: z.string().optional(),
8282
- allow_stt_fallback: z.boolean().optional(),
8283
- max_duration_minutes: z.number().optional(),
8284
- max_chunks: z.number().optional()
8889
+ action: z2.enum(["source", "workspace"]).default("source"),
8890
+ project: z2.string().optional(),
8891
+ type: z2.enum(["github", "web", "playwright", "pdf", "local", "slack", "video"]).optional(),
8892
+ name: z2.string().optional(),
8893
+ owner: z2.string().optional(),
8894
+ repo: z2.string().optional(),
8895
+ branch: z2.string().optional(),
8896
+ url: z2.string().optional(),
8897
+ file_path: z2.string().optional(),
8898
+ path: z2.string().optional(),
8899
+ glob: z2.string().optional(),
8900
+ max_files: z2.number().optional(),
8901
+ channel_ids: z2.array(z2.string()).optional(),
8902
+ token: z2.string().optional(),
8903
+ workspace_id: z2.string().optional(),
8904
+ mode: z2.enum(["full", "incremental"]).optional().default("incremental"),
8905
+ platform: z2.enum(["youtube", "loom", "generic"]).optional(),
8906
+ language: z2.string().optional(),
8907
+ allow_stt_fallback: z2.boolean().optional(),
8908
+ max_duration_minutes: z2.number().optional(),
8909
+ max_chunks: z2.number().optional()
8285
8910
  },
8286
8911
  async (input) => {
8287
8912
  try {
@@ -8342,25 +8967,30 @@ server.tool(
8342
8967
  "remember",
8343
8968
  "Call this whenever the user states a durable preference, decision, instruction, or personal/project fact that should persist across sessions. Save proactively without waiting for an explicit 'remember this'.",
8344
8969
  {
8345
- project: z.string().optional(),
8346
- content: z.string().describe("Memory content"),
8347
- memory_type: z.enum(["factual", "preference", "event", "relationship", "opinion", "goal", "instruction"]).optional().default("factual"),
8348
- user_id: z.string().optional(),
8349
- session_id: z.string().optional(),
8350
- agent_id: z.string().optional(),
8351
- importance: z.number().optional().default(0.5)
8970
+ project: z2.string().optional(),
8971
+ content: z2.string().describe("Memory content"),
8972
+ memory_type: z2.enum(["factual", "preference", "event", "relationship", "opinion", "goal", "instruction"]).optional().default("factual"),
8973
+ scope: z2.enum(["personal", "shared_project"]).optional().default("personal"),
8974
+ user_id: z2.string().optional(),
8975
+ session_id: z2.string().optional(),
8976
+ agent_id: z2.string().optional(),
8977
+ importance: z2.number().optional().default(0.5)
8352
8978
  },
8353
- async ({ project, content, memory_type, user_id, session_id, agent_id, importance }) => {
8979
+ async ({ project, content, memory_type, scope, user_id, session_id, agent_id, importance }) => {
8354
8980
  try {
8355
- const scope = resolveMcpScope({ project, user_id, session_id });
8981
+ const memoryScope = resolveMemoryScope({ project, user_id, session_id, scope });
8356
8982
  const result = await whisper.addMemory({
8357
- project: scope.project,
8983
+ project: memoryScope.project,
8358
8984
  content,
8359
8985
  memory_type,
8360
- user_id: scope.userId,
8361
- session_id: scope.sessionId,
8986
+ user_id: memoryScope.userId,
8987
+ session_id: memoryScope.sessionId,
8362
8988
  agent_id,
8363
- importance
8989
+ importance,
8990
+ metadata: buildMemoryScopeMetadata({
8991
+ memoryScope,
8992
+ agent_id
8993
+ })
8364
8994
  });
8365
8995
  const memoryId = result?.memory_id || (result.mode === "sync" ? result.id : null);
8366
8996
  const jobId = result?.job_id || (result.mode === "async" ? result.id : null);
@@ -8371,13 +9001,17 @@ server.tool(
8371
9001
  job_id: jobId,
8372
9002
  mode: result?.mode || null,
8373
9003
  memory_type,
9004
+ scope: memoryScope.scopeMode,
8374
9005
  stored: result.success === true,
8375
9006
  queued: result?.mode === "async" || Boolean(jobId),
8376
9007
  diagnostics: {
8377
9008
  scope: {
8378
- project: scope.project || null,
8379
- user_id: scope.userId,
8380
- session_id: scope.sessionId || null
9009
+ project: memoryScope.project || null,
9010
+ user_id: memoryScope.userId,
9011
+ session_id: memoryScope.sessionId || null,
9012
+ scope: memoryScope.scopeMode,
9013
+ actor_user_id: memoryScope.actorUserId,
9014
+ actor_session_id: memoryScope.actorSessionId || null
8381
9015
  }
8382
9016
  }
8383
9017
  });
@@ -8390,17 +9024,17 @@ server.tool(
8390
9024
  "record",
8391
9025
  "Record what just happened in a session or conversation. Use this for temporal capture, not durable preference storage.",
8392
9026
  {
8393
- project: z.string().optional(),
8394
- session_id: z.string().describe("Session to record into"),
8395
- user_id: z.string().optional(),
8396
- messages: z.array(z.object({
8397
- role: z.string().optional(),
8398
- content: z.string(),
8399
- timestamp: z.string().optional()
9027
+ project: z2.string().optional(),
9028
+ session_id: z2.string().describe("Session to record into"),
9029
+ user_id: z2.string().optional(),
9030
+ messages: z2.array(z2.object({
9031
+ role: z2.string().optional(),
9032
+ content: z2.string(),
9033
+ timestamp: z2.string().optional()
8400
9034
  })).optional(),
8401
- role: z.string().optional(),
8402
- content: z.string().optional(),
8403
- timestamp: z.string().optional()
9035
+ role: z2.string().optional(),
9036
+ content: z2.string().optional(),
9037
+ timestamp: z2.string().optional()
8404
9038
  },
8405
9039
  async ({ project, session_id, user_id, messages, role, content, timestamp }) => {
8406
9040
  try {
@@ -8415,29 +9049,31 @@ server.tool(
8415
9049
  timestamp
8416
9050
  });
8417
9051
  const resolvedProject = await resolveProjectRef(project);
8418
- const scope = resolveMcpScope({
9052
+ const memoryScope = resolveMemoryScope({
8419
9053
  project: resolvedProject || project,
8420
9054
  user_id,
8421
- session_id
9055
+ session_id,
9056
+ scope: "personal"
8422
9057
  });
8423
9058
  const result = await ingestSessionWithSyncFallback({
8424
- project: scope.project,
8425
- session_id: scope.sessionId || session_id,
8426
- user_id: scope.userId,
9059
+ project: memoryScope.project,
9060
+ session_id: memoryScope.sessionId || session_id,
9061
+ user_id: memoryScope.userId,
8427
9062
  messages: normalizedMessages
8428
9063
  });
8429
9064
  return primaryToolSuccess({
8430
9065
  tool: "record",
8431
- session_id: scope.sessionId || session_id,
8432
- user_id: scope.userId,
9066
+ session_id: memoryScope.sessionId || session_id,
9067
+ user_id: memoryScope.userId,
8433
9068
  messages_recorded: normalizedMessages.length,
8434
9069
  memories_created: result.memories_created,
8435
9070
  relations_created: result.relations_created,
8436
9071
  diagnostics: {
8437
9072
  scope: {
8438
- project: scope.project || null,
8439
- user_id: scope.userId,
8440
- session_id: scope.sessionId || session_id
9073
+ project: memoryScope.project || null,
9074
+ user_id: memoryScope.userId,
9075
+ session_id: memoryScope.sessionId || session_id,
9076
+ scope: memoryScope.scopeMode
8441
9077
  }
8442
9078
  }
8443
9079
  });
@@ -8450,60 +9086,62 @@ server.tool(
8450
9086
  "learn",
8451
9087
  "Unified ingestion entrypoint. Call this when the user asks to import knowledge: mode='conversation' for chat logs, mode='text' for raw text, mode='source' for external sources to index. Prefer this over legacy compatibility tools.",
8452
9088
  {
8453
- mode: z.enum(["conversation", "text", "source"]).describe("What kind of learning to perform"),
8454
- project: z.string().optional(),
8455
- user_id: z.string().optional().describe("Optional end-user identity for conversation learning"),
8456
- session_id: z.string().optional().describe("Session identifier for conversation learning"),
8457
- messages: z.array(z.object({
8458
- role: z.string(),
8459
- content: z.string(),
8460
- timestamp: z.string().optional()
9089
+ mode: z2.enum(["conversation", "text", "source"]).describe("What kind of learning to perform"),
9090
+ project: z2.string().optional(),
9091
+ user_id: z2.string().optional().describe("Optional end-user identity for conversation learning"),
9092
+ session_id: z2.string().optional().describe("Session identifier for conversation learning"),
9093
+ messages: z2.array(z2.object({
9094
+ role: z2.string(),
9095
+ content: z2.string(),
9096
+ timestamp: z2.string().optional()
8461
9097
  })).optional().describe("Conversation messages to learn from"),
8462
- title: z.string().optional().describe("Title for text learning"),
8463
- content: z.string().optional().describe("Inline text content to ingest"),
8464
- metadata: z.record(z.any()).optional(),
8465
- tags: z.array(z.string()).optional(),
8466
- namespace: z.string().optional(),
8467
- type: z.enum(["github", "web", "playwright", "pdf", "local", "slack", "video"]).optional().describe("Source type when mode=source"),
8468
- url: z.string().optional().describe("URL to learn from"),
8469
- owner: z.string().optional().describe("GitHub owner"),
8470
- repo: z.string().optional().describe("GitHub repository"),
8471
- branch: z.string().optional(),
8472
- paths: z.array(z.string()).optional(),
8473
- path: z.string().optional().describe("Local path to learn from"),
8474
- file_path: z.string().optional().describe("Single file path to learn from"),
8475
- name: z.string().optional().describe("Optional source name"),
8476
- channel_ids: z.array(z.string()).optional(),
8477
- since: z.string().optional(),
8478
- token: z.string().optional(),
8479
- auth_ref: z.string().optional(),
8480
- platform: z.enum(["youtube", "loom", "generic"]).optional(),
8481
- language: z.string().optional(),
8482
- options: z.object({
8483
- async: z.boolean().optional(),
8484
- auto_index: z.boolean().optional(),
8485
- ingestion_profile: z.enum(["auto", "repo", "web_docs", "pdf_layout", "video_transcript", "plain_text"]).optional(),
8486
- strategy_override: z.enum(["fixed", "recursive", "semantic", "hierarchical", "adaptive"]).optional(),
8487
- profile_config: z.record(z.any()).optional(),
8488
- crawl_depth: z.number().optional(),
8489
- include_paths: z.array(z.string()).optional(),
8490
- exclude_paths: z.array(z.string()).optional(),
8491
- glob: z.string().optional(),
8492
- max_files: z.number().optional(),
8493
- max_pages: z.number().optional(),
8494
- extract_mode: z.enum(["text", "structured", "markdown"]).optional(),
8495
- workspace_id: z.string().optional(),
8496
- allow_stt_fallback: z.boolean().optional(),
8497
- max_duration_minutes: z.number().optional(),
8498
- max_chunks: z.number().optional()
9098
+ title: z2.string().optional().describe("Title for text learning"),
9099
+ content: z2.string().optional().describe("Inline text content to ingest"),
9100
+ metadata: z2.record(z2.any()).optional(),
9101
+ tags: z2.array(z2.string()).optional(),
9102
+ namespace: z2.string().optional(),
9103
+ type: z2.enum(["github", "web", "playwright", "pdf", "local", "slack", "video"]).optional().describe("Source type when mode=source"),
9104
+ url: z2.string().optional().describe("URL to learn from"),
9105
+ owner: z2.string().optional().describe("GitHub owner"),
9106
+ repo: z2.string().optional().describe("GitHub repository"),
9107
+ branch: z2.string().optional(),
9108
+ paths: z2.array(z2.string()).optional(),
9109
+ path: z2.string().optional().describe("Local path to learn from"),
9110
+ file_path: z2.string().optional().describe("Single file path to learn from"),
9111
+ name: z2.string().optional().describe("Optional source name"),
9112
+ channel_ids: z2.array(z2.string()).optional(),
9113
+ since: z2.string().optional(),
9114
+ token: z2.string().optional(),
9115
+ auth_ref: z2.string().optional(),
9116
+ platform: z2.enum(["youtube", "loom", "generic"]).optional(),
9117
+ language: z2.string().optional(),
9118
+ options: z2.object({
9119
+ async: z2.boolean().optional(),
9120
+ auto_index: z2.boolean().optional(),
9121
+ ingestion_profile: z2.enum(["auto", "repo", "web_docs", "pdf_layout", "video_transcript", "plain_text"]).optional(),
9122
+ strategy_override: z2.enum(["fixed", "recursive", "semantic", "hierarchical", "adaptive"]).optional(),
9123
+ profile_config: z2.record(z2.any()).optional(),
9124
+ crawl_depth: z2.number().optional(),
9125
+ include_paths: z2.array(z2.string()).optional(),
9126
+ exclude_paths: z2.array(z2.string()).optional(),
9127
+ glob: z2.string().optional(),
9128
+ max_files: z2.number().optional(),
9129
+ max_pages: z2.number().optional(),
9130
+ extract_mode: z2.enum(["text", "structured", "markdown"]).optional(),
9131
+ workspace_id: z2.string().optional(),
9132
+ allow_stt_fallback: z2.boolean().optional(),
9133
+ max_duration_minutes: z2.number().optional(),
9134
+ max_chunks: z2.number().optional()
8499
9135
  }).optional()
8500
9136
  },
8501
9137
  async (input) => {
8502
9138
  try {
8503
9139
  const result = await learnFromInput(input);
9140
+ const indexingNote = result.mode === "source" && result.index_started ? "Indexing has started in the background and usually completes in 1\u20132 minutes. Queries may return limited results until then. Run `index.workspace_status` or `context.source_status` to check progress." : void 0;
8504
9141
  return primaryToolSuccess({
8505
9142
  tool: "learn",
8506
- ...result
9143
+ ...result,
9144
+ ...indexingNote ? { _note: indexingNote } : {}
8507
9145
  });
8508
9146
  } catch (error) {
8509
9147
  return primaryToolError(error.message);
@@ -8514,10 +9152,10 @@ server.tool(
8514
9152
  "share_context",
8515
9153
  "Create a shareable snapshot of a session so another agent or teammate can continue with the same context.",
8516
9154
  {
8517
- project: z.string().optional(),
8518
- session_id: z.string().describe("Session to share"),
8519
- title: z.string().optional(),
8520
- expiry_days: z.number().optional().default(30)
9155
+ project: z2.string().optional(),
9156
+ session_id: z2.string().describe("Session to share"),
9157
+ title: z2.string().optional(),
9158
+ expiry_days: z2.number().optional().default(30)
8521
9159
  },
8522
9160
  async ({ project, session_id, title, expiry_days }) => {
8523
9161
  try {