@usewhisper/mcp-server 2.14.0 → 2.15.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 +363 -74
  2. package/package.json +1 -1
package/dist/server.js CHANGED
@@ -7,7 +7,7 @@ import { z } 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
@@ -4711,12 +4711,28 @@ async function ingestSessionWithSyncFallback(params) {
4711
4711
  return whisper.ingestSession(params);
4712
4712
  }
4713
4713
  }
4714
+ function apiKeyFingerprint(apiKey) {
4715
+ const value = String(apiKey || "").trim();
4716
+ if (!value) return "anon";
4717
+ return createHash("sha256").update(value).digest("hex").slice(0, 12);
4718
+ }
4714
4719
  function defaultMcpUserId(params) {
4715
4720
  const explicit = process.env.WHISPER_USER_ID?.trim();
4716
4721
  if (explicit) return explicit;
4722
+ let osUsername = process.env.USER?.trim() || process.env.USERNAME?.trim();
4723
+ if (!osUsername) {
4724
+ try {
4725
+ osUsername = userInfo().username?.trim();
4726
+ } catch {
4727
+ osUsername = "";
4728
+ }
4729
+ }
4730
+ if (!osUsername) {
4731
+ throw new Error("Unable to derive user identity. Set WHISPER_USER_ID or pass user_id.");
4732
+ }
4717
4733
  const workspacePath = params?.workspacePath || canonicalizeWorkspacePath(process.cwd());
4718
4734
  const projectRef = params?.project || DEFAULT_PROJECT || "default";
4719
- const seed = `${workspacePath}|${projectRef}|${API_KEY.slice(0, 12) || "anon"}`;
4735
+ const seed = `${workspacePath}|${projectRef}|${apiKeyFingerprint(API_KEY)}|${osUsername.toLowerCase()}`;
4720
4736
  return `mcp-user-${createHash("sha256").update(seed).digest("hex").slice(0, 12)}`;
4721
4737
  }
4722
4738
  function resolveMcpScope(params, options) {
@@ -4732,6 +4748,68 @@ function resolveMcpScope(params, options) {
4732
4748
  workspacePath
4733
4749
  };
4734
4750
  }
4751
+ function resolveMemoryScope(params) {
4752
+ const base = resolveMcpScope({
4753
+ project: params?.project,
4754
+ user_id: params?.user_id,
4755
+ session_id: params?.session_id,
4756
+ path: params?.path
4757
+ });
4758
+ const scopeMode = params?.scope === "shared_project" ? "shared_project" : "personal";
4759
+ if (scopeMode === "shared_project") {
4760
+ if (!base.project && !DEFAULT_PROJECT) {
4761
+ throw new Error("shared_project scope requires a project. Set WHISPER_PROJECT or pass project.");
4762
+ }
4763
+ const projectRef = base.project || DEFAULT_PROJECT || "default";
4764
+ return {
4765
+ ...base,
4766
+ userId: `shared-project:${projectRef}`,
4767
+ sessionId: void 0,
4768
+ actorUserId: base.userId,
4769
+ actorSessionId: base.sessionId,
4770
+ scopeMode,
4771
+ sharedProjectRef: projectRef
4772
+ };
4773
+ }
4774
+ return {
4775
+ ...base,
4776
+ actorUserId: base.userId,
4777
+ actorSessionId: base.sessionId,
4778
+ scopeMode,
4779
+ sharedProjectRef: null
4780
+ };
4781
+ }
4782
+ function sharedProjectMemoryUserId(projectRef) {
4783
+ return `shared-project:${projectRef}`;
4784
+ }
4785
+ function buildMemoryScopeMetadata(args) {
4786
+ const { memoryScope } = args;
4787
+ const metadata = {
4788
+ scope: memoryScope.scopeMode,
4789
+ ...memoryScope.sharedProjectRef ? { shared_project_ref: memoryScope.sharedProjectRef } : {}
4790
+ };
4791
+ if (memoryScope.scopeMode === "shared_project") {
4792
+ metadata.provenance = {
4793
+ writer_user_id: memoryScope.actorUserId,
4794
+ writer_session_id: memoryScope.actorSessionId || null,
4795
+ writer_agent_id: args.agent_id || process.env.WHISPER_AGENT_ID || null,
4796
+ written_at: (/* @__PURE__ */ new Date()).toISOString()
4797
+ };
4798
+ }
4799
+ return metadata;
4800
+ }
4801
+ function isMemoryInScope(args) {
4802
+ const memory = args.memory;
4803
+ if (!memory || typeof memory !== "object") return false;
4804
+ const memoryUserId = String(memory.userId || memory.user_id || "").trim();
4805
+ if (args.memoryScope.scopeMode === "shared_project") {
4806
+ const metadata = memory.metadata && typeof memory.metadata === "object" ? memory.metadata : {};
4807
+ const metaScope = String(metadata.scope || "").trim();
4808
+ const metaProjectRef = String(metadata.shared_project_ref || "").trim();
4809
+ return memoryUserId === args.memoryScope.userId || metaScope === "shared_project" && metaProjectRef === (args.memoryScope.sharedProjectRef || "");
4810
+ }
4811
+ return memoryUserId === args.memoryScope.userId;
4812
+ }
4735
4813
  function noteAutomaticSourceActivity(params) {
4736
4814
  if (!runtimeClient) return;
4737
4815
  const sourceIds = [...new Set((params.sourceIds || []).map((value) => String(value || "").trim()).filter(Boolean))];
@@ -5298,6 +5376,82 @@ function resolveForgetQueryCandidates(rawResults, query) {
5298
5376
  }
5299
5377
  return { memory_ids: [], resolved_by: "none", warning: "Query did not resolve to a reliable memory match. No memories were changed." };
5300
5378
  }
5379
+ async function searchMemoriesForContextQuery(args) {
5380
+ return whisper.searchMemoriesSOTA({
5381
+ project: args.project,
5382
+ query: args.query,
5383
+ user_id: args.user_id,
5384
+ session_id: args.session_id,
5385
+ top_k: args.top_k ?? 5,
5386
+ include_relations: false,
5387
+ include_pending: true
5388
+ });
5389
+ }
5390
+ async function runContextQueryMemoryRescue(args) {
5391
+ const scoped = await searchMemoriesForContextQuery(args);
5392
+ const personalResults = formatCanonicalMemoryResults(scoped);
5393
+ const sharedUserId = sharedProjectMemoryUserId(args.project);
5394
+ const shared = args.user_id === sharedUserId ? { results: [] } : await searchMemoriesForContextQuery({
5395
+ project: args.project,
5396
+ query: args.query,
5397
+ user_id: sharedUserId,
5398
+ top_k: args.top_k
5399
+ });
5400
+ const sharedResults = formatCanonicalMemoryResults(shared);
5401
+ const deduped = /* @__PURE__ */ new Set();
5402
+ const combined = [
5403
+ ...personalResults.map((result) => ({
5404
+ ...result,
5405
+ memory_scope: "personal",
5406
+ ranking_score: clamp012(Number(result.similarity ?? 0.5) + 0.08)
5407
+ })),
5408
+ ...sharedResults.map((result) => ({
5409
+ ...result,
5410
+ memory_scope: "shared_project",
5411
+ ranking_score: clamp012(Number(result.similarity ?? 0.5))
5412
+ }))
5413
+ ].filter((result) => {
5414
+ if (!result.id) return false;
5415
+ if (deduped.has(result.id)) return false;
5416
+ deduped.add(result.id);
5417
+ return true;
5418
+ }).sort((a, b) => b.ranking_score - a.ranking_score);
5419
+ const rescueMode = combined.length === 0 ? null : sharedResults.length > 0 ? "scoped_plus_shared" : "scoped_only";
5420
+ return {
5421
+ personal_results: personalResults,
5422
+ shared_results: sharedResults,
5423
+ combined_results: combined,
5424
+ rescue_mode: rescueMode
5425
+ };
5426
+ }
5427
+ function renderContextQueryMemoryRescue(args) {
5428
+ const lines = args.combined_results.map(
5429
+ (result, index) => `${index + 1}. [${result.memory_scope}, ${result.memory_type || "memory"}, score: ${result.ranking_score.toFixed(2)}] ${result.content}`
5430
+ );
5431
+ 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):
5432
+
5433
+ ${lines.join("\n\n")}`;
5434
+ }
5435
+ function memoryRescueResultsToEvidence(args) {
5436
+ return args.hits.map(
5437
+ (hit) => toEvidenceRef({
5438
+ id: `memory:${hit.id || randomUUID()}`,
5439
+ source: "memory",
5440
+ document: "memory",
5441
+ content: hit.content,
5442
+ score: hit.ranking_score,
5443
+ retrieval_source: "memory",
5444
+ metadata: {
5445
+ path: `memory://${hit.memory_scope}/${hit.id || "unknown"}`,
5446
+ line_start: 1,
5447
+ line_end: 1,
5448
+ score: hit.ranking_score,
5449
+ memory_scope: hit.memory_scope,
5450
+ memory_type: hit.memory_type || "factual"
5451
+ }
5452
+ }, args.workspaceId, "memory")
5453
+ );
5454
+ }
5301
5455
  function likelyEmbeddingFailure(error) {
5302
5456
  const message = String(error?.message || error || "").toLowerCase();
5303
5457
  return message.includes("embedding") || message.includes("vector") || message.includes("timeout") || message.includes("timed out") || message.includes("temporarily unavailable");
@@ -6244,6 +6398,7 @@ async function runUnifiedContextRetrieval(params) {
6244
6398
  let retrievalReadiness = preflight.retrieval_readiness;
6245
6399
  let retrievalRoute = preflight.retrieval_route;
6246
6400
  let latencyMs = 0;
6401
+ let memoryContribution = null;
6247
6402
  if (preflight.retrieval_route === "local_workspace_fallback") {
6248
6403
  const localRetrieval = await runLocalWorkspaceRetrieval({
6249
6404
  query: params.question,
@@ -6302,6 +6457,39 @@ async function runUnifiedContextRetrieval(params) {
6302
6457
  retrievalRoute = preflight.repo_grounded ? "project_repo" : "none";
6303
6458
  }
6304
6459
  }
6460
+ if (resolvedProject && params.include_memories === true && !preflight.repo_grounded) {
6461
+ try {
6462
+ memoryContribution = await runContextQueryMemoryRescue({
6463
+ project: resolvedProject,
6464
+ query: params.question,
6465
+ user_id: scope.userId,
6466
+ session_id: params.session_id,
6467
+ top_k: Math.max(4, Math.min(topK, 8))
6468
+ });
6469
+ if (memoryContribution.combined_results.length > 0) {
6470
+ const memoryEvidence = memoryRescueResultsToEvidence({
6471
+ workspaceId: preflight.trust_state.workspace_id,
6472
+ hits: memoryContribution.combined_results
6473
+ });
6474
+ const existingSourceIds = new Set(evidence.map((item) => item.source_id));
6475
+ const dedupedMemoryEvidence = memoryEvidence.filter((item) => !existingSourceIds.has(item.source_id));
6476
+ evidence = [...evidence, ...dedupedMemoryEvidence];
6477
+ const memoryContext = renderContextQueryMemoryRescue({
6478
+ project: resolvedProject,
6479
+ scope,
6480
+ personal_results: memoryContribution.personal_results,
6481
+ shared_results: memoryContribution.shared_results,
6482
+ combined_results: memoryContribution.combined_results
6483
+ });
6484
+ contextText = contextText.trim().length > 0 ? `${contextText}
6485
+
6486
+ ${memoryContext}` : memoryContext;
6487
+ if (retrievalRoute === "none") retrievalRoute = "memory_only";
6488
+ }
6489
+ } catch (error) {
6490
+ warnings.push(`Scoped memory contribution failed: ${String(error?.message || error)}`);
6491
+ }
6492
+ }
6305
6493
  recordSemanticAttempt(preflight.trust_state.workspace_id, semanticStatus === "failed");
6306
6494
  const semanticStats = getWorkspaceSemanticFailureStats(preflight.trust_state.workspace_id);
6307
6495
  const trustScore = computeTrustScore({
@@ -6374,6 +6562,14 @@ async function runUnifiedContextRetrieval(params) {
6374
6562
  user_id: scope.userId,
6375
6563
  session_id: params.session_id || null
6376
6564
  },
6565
+ memory: {
6566
+ included: resolvedProject ? params.include_memories === true && !preflight.repo_grounded : false,
6567
+ personal_count: memoryContribution?.personal_results.length || 0,
6568
+ shared_count: memoryContribution?.shared_results.length || 0,
6569
+ combined_count: memoryContribution?.combined_results.length || 0,
6570
+ shared_user_id: resolvedProject ? sharedProjectMemoryUserId(resolvedProject) : null,
6571
+ ordering: "personal_then_shared_bias"
6572
+ },
6377
6573
  retrieval_profile: retrievalProfile,
6378
6574
  trust_score: trustScore,
6379
6575
  semantic_failure_rate: semanticStats.rate,
@@ -6383,7 +6579,11 @@ async function runUnifiedContextRetrieval(params) {
6383
6579
  }
6384
6580
  };
6385
6581
  if (params.include_alias_warning) {
6386
- payload.warnings = Array.from(/* @__PURE__ */ new Set([...payload.warnings, "deprecated_alias_use_context.query"]));
6582
+ payload.warnings = Array.from(/* @__PURE__ */ new Set([
6583
+ ...payload.warnings,
6584
+ "deprecated_alias_use_context_query",
6585
+ "deprecated_alias_use_context.query"
6586
+ ]));
6387
6587
  }
6388
6588
  return payload;
6389
6589
  }
@@ -6671,22 +6871,27 @@ server.tool(
6671
6871
  project: z.string().optional().describe("Project name or slug"),
6672
6872
  content: z.string().describe("The memory content to store"),
6673
6873
  memory_type: z.enum(["factual", "preference", "event", "relationship", "opinion", "goal", "instruction"]).optional().default("factual"),
6874
+ scope: z.enum(["personal", "shared_project"]).optional().default("personal"),
6674
6875
  user_id: z.string().optional().describe("User this memory belongs to"),
6675
6876
  session_id: z.string().optional().describe("Session scope"),
6676
6877
  agent_id: z.string().optional().describe("Agent scope"),
6677
6878
  importance: z.number().optional().default(0.5).describe("Importance 0-1")
6678
6879
  },
6679
- async ({ project, content, memory_type, user_id, session_id, agent_id, importance }) => {
6880
+ async ({ project, content, memory_type, scope, user_id, session_id, agent_id, importance }) => {
6680
6881
  try {
6681
- const scope = resolveMcpScope({ project, user_id, session_id });
6882
+ const memoryScope = resolveMemoryScope({ project, user_id, session_id, scope });
6682
6883
  const result = await whisper.addMemory({
6683
- project: scope.project,
6884
+ project: memoryScope.project,
6684
6885
  content,
6685
6886
  memory_type,
6686
- user_id: scope.userId,
6687
- session_id: scope.sessionId,
6887
+ user_id: memoryScope.userId,
6888
+ session_id: memoryScope.sessionId,
6688
6889
  agent_id,
6689
- importance
6890
+ importance,
6891
+ metadata: buildMemoryScopeMetadata({
6892
+ memoryScope,
6893
+ agent_id
6894
+ })
6690
6895
  });
6691
6896
  const memoryId = result?.memory_id || result.id;
6692
6897
  const jobId = result?.job_id;
@@ -6699,12 +6904,16 @@ server.tool(
6699
6904
  mode: mode || null,
6700
6905
  semantic_status: semanticStatus || null,
6701
6906
  memory_type: memory_type || "factual",
6907
+ scope: memoryScope.scopeMode,
6702
6908
  queued: mode === "async" || Boolean(jobId),
6703
6909
  diagnostics: {
6704
6910
  scope: {
6705
- project: scope.project || null,
6706
- user_id: scope.userId,
6707
- session_id: scope.sessionId || null
6911
+ project: memoryScope.project || null,
6912
+ user_id: memoryScope.userId,
6913
+ session_id: memoryScope.sessionId || null,
6914
+ scope: memoryScope.scopeMode,
6915
+ actor_user_id: memoryScope.actorUserId,
6916
+ actor_session_id: memoryScope.actorSessionId || null
6708
6917
  }
6709
6918
  }
6710
6919
  });
@@ -6719,28 +6928,29 @@ server.tool(
6719
6928
  {
6720
6929
  project: z.string().optional().describe("Project name or slug"),
6721
6930
  query: z.string().describe("What to search for"),
6931
+ scope: z.enum(["personal", "shared_project"]).optional().default("personal"),
6722
6932
  user_id: z.string().optional().describe("Filter by user"),
6723
6933
  session_id: z.string().optional().describe("Filter by session"),
6724
6934
  top_k: z.number().optional().default(10).describe("Number of results"),
6725
6935
  memory_types: z.array(z.enum(["factual", "preference", "event", "relationship", "opinion", "goal", "instruction"])).optional()
6726
6936
  },
6727
- async ({ project, query, user_id, session_id, top_k, memory_types }) => {
6937
+ async ({ project, query, scope, user_id, session_id, top_k, memory_types }) => {
6728
6938
  try {
6729
- const scope = resolveMcpScope({ project, user_id, session_id });
6939
+ const memoryScope = resolveMemoryScope({ project, user_id, session_id, scope });
6730
6940
  const results = runtimeClient && (!memory_types || memory_types.length <= 1) ? await runtimeClient.memory.search({
6731
- project: scope.project,
6941
+ project: memoryScope.project,
6732
6942
  query,
6733
- user_id: scope.userId,
6734
- session_id: scope.sessionId,
6943
+ user_id: memoryScope.userId,
6944
+ session_id: memoryScope.sessionId,
6735
6945
  top_k,
6736
6946
  include_pending: true,
6737
6947
  profile: "balanced",
6738
6948
  ...memory_types?.length === 1 ? { memory_type: memory_types[0] } : {}
6739
6949
  }) : await whisper.searchMemoriesSOTA({
6740
- project: scope.project,
6950
+ project: memoryScope.project,
6741
6951
  query,
6742
- user_id: scope.userId,
6743
- session_id: scope.sessionId,
6952
+ user_id: memoryScope.userId,
6953
+ session_id: memoryScope.sessionId,
6744
6954
  top_k,
6745
6955
  memory_types
6746
6956
  });
@@ -6748,15 +6958,17 @@ server.tool(
6748
6958
  return primaryToolSuccess({
6749
6959
  tool: "memory.search",
6750
6960
  query,
6751
- user_id: scope.userId,
6752
- session_id: scope.sessionId,
6961
+ scope: memoryScope.scopeMode,
6962
+ user_id: memoryScope.userId,
6963
+ session_id: memoryScope.sessionId,
6753
6964
  results: normalizedResults,
6754
6965
  count: normalizedResults.length,
6755
6966
  diagnostics: {
6756
6967
  scope: {
6757
- project: scope.project || null,
6758
- user_id: scope.userId,
6759
- session_id: scope.sessionId || null
6968
+ project: memoryScope.project || null,
6969
+ user_id: memoryScope.userId,
6970
+ session_id: memoryScope.sessionId || null,
6971
+ scope: memoryScope.scopeMode
6760
6972
  }
6761
6973
  }
6762
6974
  });
@@ -7013,6 +7225,7 @@ server.tool(
7013
7225
  {
7014
7226
  project: z.string().optional().describe("Project name or slug"),
7015
7227
  query: z.string().describe("Search query (supports temporal: 'yesterday', 'last week')"),
7228
+ scope: z.enum(["personal", "shared_project"]).optional().default("personal"),
7016
7229
  user_id: z.string().optional().describe("Filter by user"),
7017
7230
  session_id: z.string().optional().describe("Filter by session"),
7018
7231
  question_date: z.string().optional().describe("ISO datetime for temporal grounding"),
@@ -7020,14 +7233,14 @@ server.tool(
7020
7233
  top_k: z.number().optional().default(10),
7021
7234
  include_relations: z.boolean().optional().default(true).describe("Include related memories via knowledge graph")
7022
7235
  },
7023
- async ({ project, query, user_id, session_id, question_date, memory_types, top_k, include_relations }) => {
7236
+ async ({ project, query, scope, user_id, session_id, question_date, memory_types, top_k, include_relations }) => {
7024
7237
  try {
7025
- const scope = resolveMcpScope({ project, user_id, session_id });
7238
+ const memoryScope = resolveMemoryScope({ project, user_id, session_id, scope });
7026
7239
  const results = await whisper.searchMemoriesSOTA({
7027
- project: scope.project,
7240
+ project: memoryScope.project,
7028
7241
  query,
7029
- user_id: scope.userId,
7030
- session_id: scope.sessionId,
7242
+ user_id: memoryScope.userId,
7243
+ session_id: memoryScope.sessionId,
7031
7244
  question_date,
7032
7245
  memory_types,
7033
7246
  top_k,
@@ -7037,17 +7250,19 @@ server.tool(
7037
7250
  return primaryToolSuccess({
7038
7251
  tool: "memory.search_sota",
7039
7252
  query,
7040
- user_id: scope.userId,
7041
- session_id: scope.sessionId || null,
7253
+ scope: memoryScope.scopeMode,
7254
+ user_id: memoryScope.userId,
7255
+ session_id: memoryScope.sessionId || null,
7042
7256
  question_date: question_date || null,
7043
7257
  include_relations,
7044
7258
  results: normalizedResults,
7045
7259
  count: normalizedResults.length,
7046
7260
  diagnostics: {
7047
7261
  scope: {
7048
- project: scope.project || null,
7049
- user_id: scope.userId,
7050
- session_id: scope.sessionId || null
7262
+ project: memoryScope.project || null,
7263
+ user_id: memoryScope.userId,
7264
+ session_id: memoryScope.sessionId || null,
7265
+ scope: memoryScope.scopeMode
7051
7266
  }
7052
7267
  }
7053
7268
  });
@@ -7071,29 +7286,40 @@ server.tool(
7071
7286
  },
7072
7287
  async ({ project, session_id, user_id, messages }) => {
7073
7288
  try {
7074
- const scope = resolveMcpScope({ project, user_id, session_id });
7289
+ const memoryScope = resolveMemoryScope({
7290
+ project,
7291
+ user_id,
7292
+ session_id,
7293
+ scope: "personal"
7294
+ });
7075
7295
  const normalizedMessages = messages.map((message) => ({
7076
7296
  role: message.role,
7077
7297
  content: message.content,
7078
7298
  timestamp: message.timestamp
7079
7299
  }));
7080
7300
  const result = await ingestSessionWithSyncFallback({
7081
- project: scope.project,
7082
- session_id: scope.sessionId,
7083
- user_id: scope.userId,
7301
+ project: memoryScope.project,
7302
+ session_id: memoryScope.sessionId,
7303
+ user_id: memoryScope.userId,
7084
7304
  messages: normalizedMessages
7085
7305
  });
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
- };
7306
+ return primaryToolSuccess({
7307
+ tool: "memory.ingest_conversation",
7308
+ messages_processed: normalizedMessages.length,
7309
+ memories_created: result.memories_created,
7310
+ relations_created: result.relations_created,
7311
+ memories_invalidated: result.memories_invalidated,
7312
+ errors: result.errors || [],
7313
+ scope: memoryScope.scopeMode,
7314
+ diagnostics: {
7315
+ scope: {
7316
+ project: memoryScope.project || null,
7317
+ user_id: memoryScope.userId,
7318
+ session_id: memoryScope.sessionId || null,
7319
+ scope: memoryScope.scopeMode
7320
+ }
7321
+ }
7322
+ });
7097
7323
  } catch (error) {
7098
7324
  return { content: [{ type: "text", text: `Error: ${error.message}` }] };
7099
7325
  }
@@ -7310,6 +7536,9 @@ server.tool(
7310
7536
  {
7311
7537
  workspace_id: z.string().optional(),
7312
7538
  project: z.string().optional(),
7539
+ scope: z.enum(["personal", "shared_project"]).optional().default("personal"),
7540
+ user_id: z.string().optional(),
7541
+ session_id: z.string().optional(),
7313
7542
  target: z.object({
7314
7543
  memory_id: z.string().optional(),
7315
7544
  query: z.string().optional()
@@ -7317,7 +7546,7 @@ server.tool(
7317
7546
  mode: z.enum(["delete", "invalidate"]).optional().default("invalidate"),
7318
7547
  reason: z.string().optional()
7319
7548
  },
7320
- async ({ workspace_id, project, target, mode, reason }) => {
7549
+ async ({ workspace_id, project, scope, user_id, session_id, target, mode, reason }) => {
7321
7550
  try {
7322
7551
  if (!target.memory_id && !target.query) {
7323
7552
  return { content: [{ type: "text", text: "Error: target.memory_id or target.query is required." }] };
@@ -7326,8 +7555,22 @@ server.tool(
7326
7555
  let queryResolution = null;
7327
7556
  const now = (/* @__PURE__ */ new Date()).toISOString();
7328
7557
  const actor = process.env.WHISPER_AGENT_ID || process.env.USERNAME || "api_key_principal";
7329
- const resolvedProject = await resolveProjectRef(project);
7558
+ const memoryScope = resolveMemoryScope({
7559
+ project,
7560
+ user_id,
7561
+ session_id,
7562
+ scope
7563
+ });
7564
+ const resolvedProject = await resolveProjectRef(memoryScope.project);
7565
+ async function assertScopedMemoryId(memoryId) {
7566
+ const memoryResult = await whisper.getMemory(memoryId);
7567
+ const memory = memoryResult?.memory || memoryResult;
7568
+ if (!isMemoryInScope({ memory, memoryScope })) {
7569
+ throw new Error(`memory_id ${memoryId} is outside the effective ${memoryScope.scopeMode} scope.`);
7570
+ }
7571
+ }
7330
7572
  async function applyToMemory(memoryId) {
7573
+ await assertScopedMemoryId(memoryId);
7331
7574
  if (mode === "delete") {
7332
7575
  await whisper.deleteMemory(memoryId);
7333
7576
  } else {
@@ -7342,7 +7585,10 @@ server.tool(
7342
7585
  project: resolvedProject,
7343
7586
  content: `Invalidated memory ${memoryId} at ${now}. Reason: ${reason || "No reason provided"}`,
7344
7587
  memory_type: "instruction",
7345
- importance: 1
7588
+ user_id: memoryScope.userId,
7589
+ session_id: memoryScope.sessionId,
7590
+ importance: 1,
7591
+ metadata: buildMemoryScopeMetadata({ memoryScope })
7346
7592
  });
7347
7593
  }
7348
7594
  }
@@ -7374,13 +7620,25 @@ server.tool(
7374
7620
  mode: audit2.mode,
7375
7621
  ...audit2.reason ? { reason: audit2.reason } : {}
7376
7622
  },
7377
- warning: "No project resolved for query-based forget. Nothing was changed."
7623
+ warning: "No project resolved for query-based forget. Nothing was changed.",
7624
+ diagnostics: {
7625
+ scope: {
7626
+ project: memoryScope.project || null,
7627
+ user_id: memoryScope.userId,
7628
+ session_id: memoryScope.sessionId || null,
7629
+ scope: memoryScope.scopeMode,
7630
+ actor_user_id: memoryScope.actorUserId,
7631
+ actor_session_id: memoryScope.actorSessionId || null
7632
+ }
7633
+ }
7378
7634
  };
7379
7635
  return { content: [{ type: "text", text: JSON.stringify(payload2, null, 2) }] };
7380
7636
  }
7381
7637
  const search = await whisper.searchMemoriesSOTA({
7382
7638
  project: resolvedProject,
7383
7639
  query: target.query || "",
7640
+ user_id: memoryScope.userId,
7641
+ session_id: memoryScope.sessionId,
7384
7642
  top_k: 25,
7385
7643
  include_relations: false,
7386
7644
  include_pending: true
@@ -7393,7 +7651,17 @@ server.tool(
7393
7651
  status: "completed",
7394
7652
  affected_ids: affectedIds,
7395
7653
  warning: resolved.warning || "Query did not resolve to a reliable memory match. No memories were changed.",
7396
- resolved_by: resolved.resolved_by
7654
+ resolved_by: resolved.resolved_by,
7655
+ diagnostics: {
7656
+ scope: {
7657
+ project: resolvedProject,
7658
+ user_id: memoryScope.userId,
7659
+ session_id: memoryScope.sessionId || null,
7660
+ scope: memoryScope.scopeMode,
7661
+ actor_user_id: memoryScope.actorUserId,
7662
+ actor_session_id: memoryScope.actorSessionId || null
7663
+ }
7664
+ }
7397
7665
  };
7398
7666
  return { content: [{ type: "text", text: JSON.stringify(payload2, null, 2) }] };
7399
7667
  }
@@ -7422,6 +7690,16 @@ server.tool(
7422
7690
  called_at: audit.called_at,
7423
7691
  mode: audit.mode,
7424
7692
  ...audit.reason ? { reason: audit.reason } : {}
7693
+ },
7694
+ diagnostics: {
7695
+ scope: {
7696
+ project: resolvedProject || null,
7697
+ user_id: memoryScope.userId,
7698
+ session_id: memoryScope.sessionId || null,
7699
+ scope: memoryScope.scopeMode,
7700
+ actor_user_id: memoryScope.actorUserId,
7701
+ actor_session_id: memoryScope.actorSessionId || null
7702
+ }
7425
7703
  }
7426
7704
  };
7427
7705
  return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
@@ -8345,22 +8623,27 @@ server.tool(
8345
8623
  project: z.string().optional(),
8346
8624
  content: z.string().describe("Memory content"),
8347
8625
  memory_type: z.enum(["factual", "preference", "event", "relationship", "opinion", "goal", "instruction"]).optional().default("factual"),
8626
+ scope: z.enum(["personal", "shared_project"]).optional().default("personal"),
8348
8627
  user_id: z.string().optional(),
8349
8628
  session_id: z.string().optional(),
8350
8629
  agent_id: z.string().optional(),
8351
8630
  importance: z.number().optional().default(0.5)
8352
8631
  },
8353
- async ({ project, content, memory_type, user_id, session_id, agent_id, importance }) => {
8632
+ async ({ project, content, memory_type, scope, user_id, session_id, agent_id, importance }) => {
8354
8633
  try {
8355
- const scope = resolveMcpScope({ project, user_id, session_id });
8634
+ const memoryScope = resolveMemoryScope({ project, user_id, session_id, scope });
8356
8635
  const result = await whisper.addMemory({
8357
- project: scope.project,
8636
+ project: memoryScope.project,
8358
8637
  content,
8359
8638
  memory_type,
8360
- user_id: scope.userId,
8361
- session_id: scope.sessionId,
8639
+ user_id: memoryScope.userId,
8640
+ session_id: memoryScope.sessionId,
8362
8641
  agent_id,
8363
- importance
8642
+ importance,
8643
+ metadata: buildMemoryScopeMetadata({
8644
+ memoryScope,
8645
+ agent_id
8646
+ })
8364
8647
  });
8365
8648
  const memoryId = result?.memory_id || (result.mode === "sync" ? result.id : null);
8366
8649
  const jobId = result?.job_id || (result.mode === "async" ? result.id : null);
@@ -8371,13 +8654,17 @@ server.tool(
8371
8654
  job_id: jobId,
8372
8655
  mode: result?.mode || null,
8373
8656
  memory_type,
8657
+ scope: memoryScope.scopeMode,
8374
8658
  stored: result.success === true,
8375
8659
  queued: result?.mode === "async" || Boolean(jobId),
8376
8660
  diagnostics: {
8377
8661
  scope: {
8378
- project: scope.project || null,
8379
- user_id: scope.userId,
8380
- session_id: scope.sessionId || null
8662
+ project: memoryScope.project || null,
8663
+ user_id: memoryScope.userId,
8664
+ session_id: memoryScope.sessionId || null,
8665
+ scope: memoryScope.scopeMode,
8666
+ actor_user_id: memoryScope.actorUserId,
8667
+ actor_session_id: memoryScope.actorSessionId || null
8381
8668
  }
8382
8669
  }
8383
8670
  });
@@ -8415,29 +8702,31 @@ server.tool(
8415
8702
  timestamp
8416
8703
  });
8417
8704
  const resolvedProject = await resolveProjectRef(project);
8418
- const scope = resolveMcpScope({
8705
+ const memoryScope = resolveMemoryScope({
8419
8706
  project: resolvedProject || project,
8420
8707
  user_id,
8421
- session_id
8708
+ session_id,
8709
+ scope: "personal"
8422
8710
  });
8423
8711
  const result = await ingestSessionWithSyncFallback({
8424
- project: scope.project,
8425
- session_id: scope.sessionId || session_id,
8426
- user_id: scope.userId,
8712
+ project: memoryScope.project,
8713
+ session_id: memoryScope.sessionId || session_id,
8714
+ user_id: memoryScope.userId,
8427
8715
  messages: normalizedMessages
8428
8716
  });
8429
8717
  return primaryToolSuccess({
8430
8718
  tool: "record",
8431
- session_id: scope.sessionId || session_id,
8432
- user_id: scope.userId,
8719
+ session_id: memoryScope.sessionId || session_id,
8720
+ user_id: memoryScope.userId,
8433
8721
  messages_recorded: normalizedMessages.length,
8434
8722
  memories_created: result.memories_created,
8435
8723
  relations_created: result.relations_created,
8436
8724
  diagnostics: {
8437
8725
  scope: {
8438
- project: scope.project || null,
8439
- user_id: scope.userId,
8440
- session_id: scope.sessionId || session_id
8726
+ project: memoryScope.project || null,
8727
+ user_id: memoryScope.userId,
8728
+ session_id: memoryScope.sessionId || session_id,
8729
+ scope: memoryScope.scopeMode
8441
8730
  }
8442
8731
  }
8443
8732
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usewhisper/mcp-server",
3
- "version": "2.14.0",
3
+ "version": "2.15.0",
4
4
  "whisperContractVersion": "2026.03.11",
5
5
  "scripts": {
6
6
  "build": "tsup ../src/mcp/server.ts --format esm --out-dir dist",