@usewhisper/mcp-server 2.5.0 → 2.8.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 (3) hide show
  1. package/README.md +17 -0
  2. package/dist/server.js +549 -104
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -12,6 +12,13 @@ Whisper MCP is the universal context bridge for coding agents. It connects Claud
12
12
  - Scoped MCP config generator: `whisper-context-mcp scope ...`
13
13
  - Tool migration helper: `whisper-context-mcp --print-tool-map`
14
14
 
15
+ ## What's New in v2.6
16
+
17
+ - Automatic project, user, and session scope defaults for MCP retrieval and memory operations.
18
+ - `context.query` can use the automatic agent runtime by default when the caller has not requested manual-only filters.
19
+ - URL and domain-style retrieval requests now isolate matching sources better in shared projects.
20
+ - MCP memory operations now default to pending/session-aware reads and writes when compatible.
21
+
15
22
  ## Install
16
23
 
17
24
  ```text
@@ -132,6 +139,16 @@ All valid `search` calls return the same top-level payload shape:
132
139
  }
133
140
  ```
134
141
 
142
+ ### Automatic Defaults
143
+
144
+ When compatible with the caller’s requested options, Whisper MCP now behaves like an implicit context layer instead of a raw tool database:
145
+
146
+ - `context.query` auto-attaches default project, user, and session scope
147
+ - compatible retrieval calls use automatic runtime-backed pre-retrieval
148
+ - memory writes and session ingest calls attach stable default scope automatically
149
+
150
+ If the caller explicitly requests manual query controls such as `chunk_types`, explicit graph traversal, or other narrow filters, MCP preserves those manual semantics instead of forcing automatic mode.
151
+
135
152
  ## Source Contract (`context.add_source`)
136
153
 
137
154
  Input:
package/dist/server.js CHANGED
@@ -1365,6 +1365,29 @@ ${lines.join("\n")}`;
1365
1365
  function compactWhitespace(value) {
1366
1366
  return value.replace(/\s+/g, " ").trim();
1367
1367
  }
1368
+ function normalizeSummary(value) {
1369
+ return compactWhitespace(String(value || "").toLowerCase());
1370
+ }
1371
+ function tokenize(value) {
1372
+ return normalizeSummary(value).split(/[^a-z0-9_./-]+/i).map((token) => token.trim()).filter(Boolean);
1373
+ }
1374
+ function jaccardOverlap(left, right) {
1375
+ const leftTokens = new Set(tokenize(left));
1376
+ const rightTokens = new Set(tokenize(right));
1377
+ if (leftTokens.size === 0 || rightTokens.size === 0) return 0;
1378
+ let intersection = 0;
1379
+ for (const token of leftTokens) {
1380
+ if (rightTokens.has(token)) intersection += 1;
1381
+ }
1382
+ const union = (/* @__PURE__ */ new Set([...leftTokens, ...rightTokens])).size;
1383
+ return union > 0 ? intersection / union : 0;
1384
+ }
1385
+ function clamp01(value) {
1386
+ if (!Number.isFinite(value)) return 0;
1387
+ if (value < 0) return 0;
1388
+ if (value > 1) return 1;
1389
+ return value;
1390
+ }
1368
1391
  function withTimeout(promise, timeoutMs) {
1369
1392
  return new Promise((resolve, reject) => {
1370
1393
  const timeout = setTimeout(() => {
@@ -1398,39 +1421,76 @@ function extractTimestamp(metadata) {
1398
1421
  }
1399
1422
  return 0;
1400
1423
  }
1401
- function salienceBoost(metadata) {
1402
- const value = metadata?.salience;
1403
- if (value === "high") return 0.12;
1404
- if (value === "medium") return 0.06;
1405
- return 0;
1406
- }
1424
+ var DEFAULT_RANK_WEIGHTS = {
1425
+ focusedPassBonus: 0.2,
1426
+ sourceMatchBonus: 0.18,
1427
+ touchedFileBonus: 0.12,
1428
+ clientMatchBonus: 0.1,
1429
+ highSalienceBonus: 0.12,
1430
+ mediumSalienceBonus: 0.06,
1431
+ staleBroadPenalty: -0.1,
1432
+ unrelatedClientPenalty: -0.18,
1433
+ lowSaliencePenalty: -0.12
1434
+ };
1435
+ var DEFAULT_SOURCE_ACTIVITY = {
1436
+ maxTurns: 10,
1437
+ maxIdleMs: 30 * 60 * 1e3,
1438
+ decayAfterTurns: 5,
1439
+ decayAfterIdleMs: 15 * 60 * 1e3,
1440
+ evictOnTaskSwitch: true
1441
+ };
1407
1442
  var WhisperAgentRuntime = class {
1408
1443
  constructor(args) {
1409
1444
  this.args = args;
1410
1445
  this.bindingStore = createBindingStore(args.options.bindingStorePath);
1411
- this.topK = args.options.topK ?? 6;
1446
+ const retrieval = args.options.retrieval || {};
1447
+ this.focusedTopK = retrieval.focusedTopK ?? args.options.topK ?? 6;
1448
+ this.broadTopK = retrieval.broadTopK ?? Math.max(args.options.topK ?? 6, 10);
1412
1449
  this.maxTokens = args.options.maxTokens ?? 4e3;
1413
1450
  this.targetRetrievalMs = args.options.targetRetrievalMs ?? 2500;
1414
1451
  this.hardRetrievalTimeoutMs = args.options.hardRetrievalTimeoutMs ?? 4e3;
1415
1452
  this.recentWorkLimit = args.options.recentWorkLimit ?? 40;
1416
1453
  this.baseContext = args.baseContext;
1417
1454
  this.clientName = args.baseContext.clientName || "whisper-agent-runtime";
1455
+ this.minFocusedResults = retrieval.minFocusedResults ?? 3;
1456
+ this.minFocusedTopScore = retrieval.minFocusedTopScore ?? 0.55;
1457
+ this.minProjectScore = retrieval.minProjectScore ?? 0.5;
1458
+ this.minMemoryScore = retrieval.minMemoryScore ?? 0.6;
1459
+ this.rankWeights = { ...DEFAULT_RANK_WEIGHTS, ...retrieval.rankWeights || {} };
1460
+ this.sourceActivityOptions = { ...DEFAULT_SOURCE_ACTIVITY, ...retrieval.sourceActivity || {} };
1418
1461
  }
1419
1462
  bindingStore;
1420
- topK;
1463
+ focusedTopK;
1464
+ broadTopK;
1421
1465
  maxTokens;
1422
1466
  targetRetrievalMs;
1423
1467
  hardRetrievalTimeoutMs;
1424
1468
  recentWorkLimit;
1425
1469
  baseContext;
1426
1470
  clientName;
1471
+ minFocusedResults;
1472
+ minFocusedTopScore;
1473
+ minProjectScore;
1474
+ minMemoryScore;
1475
+ rankWeights;
1476
+ sourceActivityOptions;
1427
1477
  bindings = null;
1428
1478
  touchedFiles = [];
1429
1479
  recentWork = [];
1480
+ recentSourceActivity = [];
1430
1481
  bufferedLowSalience = [];
1431
1482
  lastPreparedTurn = null;
1432
1483
  mergedCount = 0;
1433
1484
  droppedCount = 0;
1485
+ focusedPassHits = 0;
1486
+ fallbackTriggers = 0;
1487
+ floorDroppedCount = 0;
1488
+ injectedItemCount = 0;
1489
+ sourceScopedTurns = 0;
1490
+ broadScopedTurns = 0;
1491
+ totalTurns = 0;
1492
+ currentTurn = 0;
1493
+ lastTaskSummary = "";
1434
1494
  lastScope = {};
1435
1495
  async getBindings() {
1436
1496
  if (!this.bindings) {
@@ -1448,6 +1508,64 @@ var WhisperAgentRuntime = class {
1448
1508
  pushWorkEvent(event) {
1449
1509
  this.recentWork = [...this.recentWork, event].slice(-this.recentWorkLimit);
1450
1510
  }
1511
+ noteSourceActivity(sourceIds) {
1512
+ const now = Date.now();
1513
+ for (const sourceId of [...new Set((sourceIds || []).map((value) => String(value || "").trim()).filter(Boolean))]) {
1514
+ this.recentSourceActivity = [
1515
+ ...this.recentSourceActivity.filter((entry) => entry.sourceId !== sourceId),
1516
+ { sourceId, turn: this.currentTurn, at: now }
1517
+ ].slice(-24);
1518
+ }
1519
+ }
1520
+ refreshTaskSummary(taskSummary) {
1521
+ const next = normalizeSummary(taskSummary);
1522
+ if (!next) return;
1523
+ if (this.sourceActivityOptions.evictOnTaskSwitch && this.lastTaskSummary && this.lastTaskSummary !== next && jaccardOverlap(this.lastTaskSummary, next) < 0.6) {
1524
+ this.recentSourceActivity = [];
1525
+ }
1526
+ this.lastTaskSummary = next;
1527
+ }
1528
+ activeSourceIds() {
1529
+ const now = Date.now();
1530
+ const active = /* @__PURE__ */ new Map();
1531
+ const maxTurns = this.sourceActivityOptions.maxTurns;
1532
+ const maxIdleMs = this.sourceActivityOptions.maxIdleMs;
1533
+ const decayAfterTurns = this.sourceActivityOptions.decayAfterTurns;
1534
+ const decayAfterIdleMs = this.sourceActivityOptions.decayAfterIdleMs;
1535
+ const fresh = [];
1536
+ for (const entry of this.recentSourceActivity) {
1537
+ const turnDelta = this.currentTurn - entry.turn;
1538
+ const idleDelta = now - entry.at;
1539
+ if (turnDelta > maxTurns || idleDelta > maxIdleMs) continue;
1540
+ fresh.push(entry);
1541
+ let weight = 1;
1542
+ if (turnDelta > decayAfterTurns || idleDelta > decayAfterIdleMs) {
1543
+ weight = 0.5;
1544
+ }
1545
+ const current = active.get(entry.sourceId) || 0;
1546
+ active.set(entry.sourceId, Math.max(current, weight));
1547
+ }
1548
+ this.recentSourceActivity = fresh.slice(-24);
1549
+ return [...active.entries()].sort((left, right) => right[1] - left[1]).map(([sourceId]) => sourceId).slice(0, 4);
1550
+ }
1551
+ focusedScope(input) {
1552
+ const sourceIds = this.activeSourceIds();
1553
+ const fileHints = [...new Set([
1554
+ ...input.touchedFiles || [],
1555
+ ...this.touchedFiles,
1556
+ ...this.recentWork.flatMap((event) => event.filePaths || [])
1557
+ ].map((value) => String(value || "").trim()).filter(Boolean))].slice(-4);
1558
+ return {
1559
+ sourceIds,
1560
+ fileHints,
1561
+ clientName: this.clientName || void 0
1562
+ };
1563
+ }
1564
+ exactFileMetadataFilter(fileHints) {
1565
+ const exact = fileHints.find((value) => /[\\/]/.test(value));
1566
+ if (!exact) return void 0;
1567
+ return { filePath: exact };
1568
+ }
1451
1569
  makeTaskFrameQuery(input) {
1452
1570
  const task = compactWhitespace(input.taskSummary || "");
1453
1571
  const salient = this.recentWork.filter((event) => event.salience === "high").slice(-3).map((event) => `${event.kind}: ${event.summary}`);
@@ -1524,23 +1642,29 @@ var WhisperAgentRuntime = class {
1524
1642
  };
1525
1643
  }
1526
1644
  }
1527
- contextItems(result, sourceQuery) {
1645
+ contextItems(result, sourceQuery, pass) {
1646
+ const sourceScope = result.meta?.source_scope;
1647
+ if (sourceScope?.mode === "auto" || sourceScope?.mode === "explicit") {
1648
+ this.noteSourceActivity(sourceScope.source_ids || []);
1649
+ }
1528
1650
  return (result.results || []).map((item) => ({
1529
1651
  id: item.id,
1530
1652
  content: item.content,
1531
1653
  type: "project",
1532
1654
  score: item.score ?? 0,
1533
1655
  sourceQuery,
1656
+ pass,
1534
1657
  metadata: item.metadata || {}
1535
1658
  }));
1536
1659
  }
1537
- memoryItems(result, sourceQuery) {
1660
+ memoryItems(result, sourceQuery, pass) {
1538
1661
  return (result.results || []).map((item, index) => ({
1539
1662
  id: item.memory?.id || item.chunk?.id || `${sourceQuery}_memory_${index}`,
1540
1663
  content: item.chunk?.content || item.memory?.content || "",
1541
1664
  type: "memory",
1542
1665
  score: item.similarity ?? 0,
1543
1666
  sourceQuery,
1667
+ pass,
1544
1668
  metadata: {
1545
1669
  ...item.chunk?.metadata || {},
1546
1670
  ...item.memory?.temporal || {},
@@ -1548,22 +1672,99 @@ var WhisperAgentRuntime = class {
1548
1672
  }
1549
1673
  })).filter((item) => item.content);
1550
1674
  }
1551
- rerank(items) {
1675
+ stableItemKey(item) {
1676
+ const metadata = item.metadata || {};
1677
+ const sourceId = String(metadata.source_id || "");
1678
+ const documentId = String(metadata.document_id || metadata.documentId || "");
1679
+ const chunkId = String(metadata.chunk_id || metadata.chunkId || item.id || "");
1680
+ return stableHash(`${sourceId}|${documentId}|${chunkId}|${item.content.slice(0, 256)}`);
1681
+ }
1682
+ metadataStrings(item) {
1683
+ const metadata = item.metadata || {};
1684
+ return [
1685
+ metadata.filePath,
1686
+ metadata.file_path,
1687
+ metadata.path,
1688
+ metadata.section_path,
1689
+ metadata.parent_section_path,
1690
+ metadata.web_url,
1691
+ metadata.url
1692
+ ].map((value) => String(value || "").toLowerCase()).filter(Boolean);
1693
+ }
1694
+ hasSourceMatch(item, scope) {
1695
+ const sourceId = String(item.metadata?.source_id || "");
1696
+ return Boolean(sourceId && scope.sourceIds.includes(sourceId));
1697
+ }
1698
+ hasFileMatch(item, scope) {
1699
+ if (scope.fileHints.length === 0) return false;
1700
+ const metadata = this.metadataStrings(item);
1701
+ const lowerHints = scope.fileHints.map((hint) => hint.toLowerCase());
1702
+ return lowerHints.some((hint) => {
1703
+ const base = pathBase(hint).toLowerCase();
1704
+ return metadata.some((value) => value.includes(hint) || value.endsWith(base));
1705
+ });
1706
+ }
1707
+ hasClientMatch(item, scope) {
1708
+ const itemClient = String(item.metadata?.client_name || "");
1709
+ return Boolean(scope.clientName && itemClient && itemClient === scope.clientName);
1710
+ }
1711
+ salienceAdjustment(item) {
1712
+ const salience = item.metadata?.salience;
1713
+ if (salience === "high") return this.rankWeights.highSalienceBonus;
1714
+ if (salience === "medium") return this.rankWeights.mediumSalienceBonus;
1715
+ if (salience === "low") return this.rankWeights.lowSaliencePenalty;
1716
+ return 0;
1717
+ }
1718
+ narrowFocusedMemories(items, scope) {
1719
+ const hasSignals = scope.sourceIds.length > 0 || scope.fileHints.length > 0 || Boolean(scope.clientName);
1720
+ if (!hasSignals) return items;
1721
+ const narrowed = items.filter((item) => {
1722
+ const matchesClient = this.hasClientMatch(item, scope);
1723
+ const matchesFile = this.hasFileMatch(item, scope);
1724
+ const matchesSource = this.hasSourceMatch(item, scope);
1725
+ const salience = item.metadata?.salience;
1726
+ if (scope.clientName && item.metadata?.client_name && !matchesClient) {
1727
+ return false;
1728
+ }
1729
+ if (salience === "low" && !matchesFile && !matchesSource) {
1730
+ return false;
1731
+ }
1732
+ return matchesClient || matchesFile || matchesSource || !scope.clientName;
1733
+ });
1734
+ return narrowed.length > 0 ? narrowed : items;
1735
+ }
1736
+ applyRelevanceFloor(items) {
1737
+ const filtered = items.filter(
1738
+ (item) => item.type === "project" ? item.score >= this.minProjectScore : item.score >= this.minMemoryScore
1739
+ );
1740
+ return { items: filtered, dropped: Math.max(0, items.length - filtered.length) };
1741
+ }
1742
+ rerank(items, scope) {
1552
1743
  const deduped = /* @__PURE__ */ new Map();
1553
1744
  for (const item of items) {
1554
- const key = `${item.id}:${item.content.toLowerCase()}`;
1745
+ const key = this.stableItemKey(item);
1555
1746
  const recency = extractTimestamp(item.metadata) > 0 ? 0.04 : 0;
1556
1747
  const queryBonus = item.sourceQuery === "primary" ? 0.08 : item.sourceQuery === "task_frame" ? 0.04 : 0.03;
1748
+ const sourceMatch = this.hasSourceMatch(item, scope);
1749
+ const fileMatch = this.hasFileMatch(item, scope);
1750
+ const clientMatch = this.hasClientMatch(item, scope);
1751
+ const broadPenalty = item.pass === "broad" && !sourceMatch && !fileMatch && !clientMatch ? this.rankWeights.staleBroadPenalty : 0;
1752
+ const clientPenalty = scope.clientName && item.metadata?.client_name && !clientMatch ? this.rankWeights.unrelatedClientPenalty : 0;
1557
1753
  const next = {
1558
1754
  ...item,
1559
- score: item.score + queryBonus + salienceBoost(item.metadata) + recency
1755
+ score: clamp01(
1756
+ item.score + queryBonus + recency + (item.pass === "focused" ? this.rankWeights.focusedPassBonus : 0) + (sourceMatch ? this.rankWeights.sourceMatchBonus : 0) + (fileMatch ? this.rankWeights.touchedFileBonus : 0) + (clientMatch ? this.rankWeights.clientMatchBonus : 0) + this.salienceAdjustment(item) + broadPenalty + clientPenalty
1757
+ )
1560
1758
  };
1561
1759
  const existing = deduped.get(key);
1562
1760
  if (!existing || next.score > existing.score) {
1563
1761
  deduped.set(key, next);
1564
1762
  }
1565
1763
  }
1566
- return [...deduped.values()].sort((left, right) => right.score - left.score);
1764
+ return {
1765
+ items: [...deduped.values()].sort((left, right) => right.score - left.score),
1766
+ dedupedCount: Math.max(0, items.length - deduped.size)
1767
+ };
1567
1768
  }
1568
1769
  buildContext(items) {
1569
1770
  const maxChars = this.maxTokens * 4;
@@ -1602,7 +1803,7 @@ ${lines.join("\n")}`;
1602
1803
  this.runBranch("project_rules", () => this.args.adapter.query({
1603
1804
  project: scope.project,
1604
1805
  query: "project rules instructions constraints conventions open threads",
1605
- top_k: this.topK,
1806
+ top_k: this.focusedTopK,
1606
1807
  include_memories: false,
1607
1808
  user_id: scope.userId,
1608
1809
  session_id: scope.sessionId,
@@ -1620,7 +1821,7 @@ ${lines.join("\n")}`;
1620
1821
  continue;
1621
1822
  }
1622
1823
  if (branch.name === "project_rules") {
1623
- items.push(...this.contextItems(branch.value, "bootstrap"));
1824
+ items.push(...this.contextItems(branch.value, "bootstrap", "bootstrap"));
1624
1825
  continue;
1625
1826
  }
1626
1827
  const records = branch.value.memories || [];
@@ -1630,10 +1831,12 @@ ${lines.join("\n")}`;
1630
1831
  type: "memory",
1631
1832
  score: 0.4,
1632
1833
  sourceQuery: "bootstrap",
1834
+ pass: "bootstrap",
1633
1835
  metadata: memory
1634
1836
  })).filter((item) => item.content));
1635
1837
  }
1636
- const ranked = this.rerank(items).slice(0, this.topK * 2);
1838
+ const reranked = this.rerank(items, { sourceIds: [], fileHints: [], clientName: this.clientName });
1839
+ const ranked = reranked.items.slice(0, this.broadTopK * 2);
1637
1840
  const prepared = {
1638
1841
  scope,
1639
1842
  retrieval: {
@@ -1645,7 +1848,14 @@ ${lines.join("\n")}`;
1645
1848
  durationMs: Date.now() - startedAt,
1646
1849
  targetBudgetMs: this.targetRetrievalMs,
1647
1850
  hardTimeoutMs: this.hardRetrievalTimeoutMs,
1648
- branchStatus
1851
+ branchStatus,
1852
+ focusedScopeApplied: false,
1853
+ focusedSourceIds: [],
1854
+ focusedFileHints: [],
1855
+ clientScoped: false,
1856
+ fallbackUsed: false,
1857
+ droppedBelowFloor: 0,
1858
+ dedupedCount: reranked.dedupedCount
1649
1859
  },
1650
1860
  context: this.buildContext(ranked),
1651
1861
  items: ranked
@@ -1654,100 +1864,195 @@ ${lines.join("\n")}`;
1654
1864
  return prepared;
1655
1865
  }
1656
1866
  async beforeTurn(input, context = {}) {
1867
+ this.currentTurn += 1;
1657
1868
  this.pushTouchedFiles(input.touchedFiles);
1869
+ this.refreshTaskSummary(input.taskSummary);
1658
1870
  const { scope, warning } = await this.resolveScope(context);
1659
1871
  const primaryQuery = compactWhitespace(input.userMessage);
1660
1872
  const taskFrameQuery = this.makeTaskFrameQuery(input);
1873
+ const focusedScope = this.focusedScope(input);
1874
+ const focusedMetadataFilter = this.exactFileMetadataFilter(focusedScope.fileHints);
1875
+ const focusedScopeApplied = focusedScope.sourceIds.length > 0 || focusedScope.fileHints.length > 0 || Boolean(focusedScope.clientName);
1661
1876
  const warnings = warning ? [warning] : [];
1662
1877
  const startedAt = Date.now();
1663
- const branches = await Promise.all([
1664
- this.runBranch("context_primary", () => this.args.adapter.query({
1878
+ const branchStatus = {};
1879
+ const collectFromBranches = (branches, pass) => {
1880
+ const collected = [];
1881
+ let okCount = 0;
1882
+ for (const branch of branches) {
1883
+ branchStatus[branch.name] = branch.status;
1884
+ if (branch.status !== "ok") {
1885
+ if (branch.status !== "skipped" && branch.reason) warnings.push(`${branch.name}:${branch.reason}`);
1886
+ continue;
1887
+ }
1888
+ okCount += 1;
1889
+ if (branch.name.startsWith("context")) {
1890
+ collected.push(...this.contextItems(
1891
+ branch.value,
1892
+ branch.name.includes("task_frame") ? "task_frame" : "primary",
1893
+ pass
1894
+ ));
1895
+ } else {
1896
+ const memoryItems = this.memoryItems(
1897
+ branch.value,
1898
+ branch.name.includes("task_frame") ? "task_frame" : "primary",
1899
+ pass
1900
+ );
1901
+ collected.push(...pass === "focused" ? this.narrowFocusedMemories(memoryItems, focusedScope) : memoryItems);
1902
+ }
1903
+ }
1904
+ return { collected, okCount };
1905
+ };
1906
+ const focusedBranches = await Promise.all([
1907
+ this.runBranch("context_primary_focused", () => this.args.adapter.query({
1665
1908
  project: scope.project,
1666
1909
  query: primaryQuery,
1667
- top_k: this.topK,
1910
+ top_k: this.focusedTopK,
1668
1911
  include_memories: false,
1669
1912
  user_id: scope.userId,
1670
1913
  session_id: scope.sessionId,
1914
+ source_ids: focusedScope.sourceIds.length > 0 ? focusedScope.sourceIds : void 0,
1915
+ metadata_filter: focusedMetadataFilter,
1671
1916
  max_tokens: this.maxTokens,
1672
1917
  compress: true,
1673
1918
  compression_strategy: "adaptive"
1674
1919
  })),
1675
- this.runBranch("memory_primary", () => this.args.adapter.searchMemories({
1920
+ this.runBranch("memory_primary_focused", () => this.args.adapter.searchMemories({
1676
1921
  project: scope.project,
1677
1922
  query: primaryQuery,
1678
1923
  user_id: scope.userId,
1679
1924
  session_id: scope.sessionId,
1680
- top_k: this.topK,
1925
+ top_k: this.focusedTopK,
1681
1926
  include_pending: true,
1682
1927
  profile: "balanced"
1683
1928
  })),
1684
- taskFrameQuery ? this.runBranch("context_task_frame", () => this.args.adapter.query({
1929
+ taskFrameQuery ? this.runBranch("context_task_frame_focused", () => this.args.adapter.query({
1685
1930
  project: scope.project,
1686
1931
  query: taskFrameQuery,
1687
- top_k: this.topK,
1932
+ top_k: this.focusedTopK,
1688
1933
  include_memories: false,
1689
1934
  user_id: scope.userId,
1690
1935
  session_id: scope.sessionId,
1936
+ source_ids: focusedScope.sourceIds.length > 0 ? focusedScope.sourceIds : void 0,
1937
+ metadata_filter: focusedMetadataFilter,
1691
1938
  max_tokens: this.maxTokens,
1692
1939
  compress: true,
1693
1940
  compression_strategy: "adaptive"
1694
- })) : Promise.resolve({
1695
- name: "context_task_frame",
1696
- status: "skipped",
1697
- durationMs: 0
1698
- }),
1699
- taskFrameQuery ? this.runBranch("memory_task_frame", () => this.args.adapter.searchMemories({
1941
+ })) : Promise.resolve({ name: "context_task_frame_focused", status: "skipped", durationMs: 0 }),
1942
+ taskFrameQuery ? this.runBranch("memory_task_frame_focused", () => this.args.adapter.searchMemories({
1700
1943
  project: scope.project,
1701
1944
  query: taskFrameQuery,
1702
1945
  user_id: scope.userId,
1703
1946
  session_id: scope.sessionId,
1704
- top_k: this.topK,
1947
+ top_k: this.focusedTopK,
1705
1948
  include_pending: true,
1706
1949
  profile: "balanced"
1707
- })) : Promise.resolve({
1708
- name: "memory_task_frame",
1709
- status: "skipped",
1710
- durationMs: 0
1711
- })
1950
+ })) : Promise.resolve({ name: "memory_task_frame_focused", status: "skipped", durationMs: 0 })
1712
1951
  ]);
1713
- const branchStatus = {};
1714
- const collected = [];
1715
- let okCount = 0;
1716
- for (const branch of branches) {
1952
+ const focusedCollected = collectFromBranches(focusedBranches, "focused");
1953
+ const focusedRanked = this.rerank(focusedCollected.collected, focusedScope);
1954
+ const focusedFloored = this.applyRelevanceFloor(focusedRanked.items);
1955
+ let allCollected = [...focusedFloored.items];
1956
+ let totalOkCount = focusedCollected.okCount;
1957
+ let dedupedCount = focusedRanked.dedupedCount;
1958
+ let droppedBelowFloor = focusedFloored.dropped;
1959
+ const focusedTopScore = focusedFloored.items[0]?.score ?? 0;
1960
+ const fallbackUsed = focusedFloored.items.length < this.minFocusedResults || focusedTopScore < this.minFocusedTopScore;
1961
+ if (focusedScopeApplied) {
1962
+ this.sourceScopedTurns += 1;
1963
+ }
1964
+ if (!fallbackUsed) {
1965
+ this.focusedPassHits += 1;
1966
+ }
1967
+ const broadBranches = fallbackUsed ? await Promise.all([
1968
+ this.runBranch("context_primary_broad", () => this.args.adapter.query({
1969
+ project: scope.project,
1970
+ query: primaryQuery,
1971
+ top_k: this.broadTopK,
1972
+ include_memories: false,
1973
+ user_id: scope.userId,
1974
+ session_id: scope.sessionId,
1975
+ max_tokens: this.maxTokens,
1976
+ compress: true,
1977
+ compression_strategy: "adaptive"
1978
+ })),
1979
+ this.runBranch("memory_primary_broad", () => this.args.adapter.searchMemories({
1980
+ project: scope.project,
1981
+ query: primaryQuery,
1982
+ user_id: scope.userId,
1983
+ session_id: scope.sessionId,
1984
+ top_k: this.broadTopK,
1985
+ include_pending: true,
1986
+ profile: "balanced"
1987
+ })),
1988
+ taskFrameQuery ? this.runBranch("context_task_frame_broad", () => this.args.adapter.query({
1989
+ project: scope.project,
1990
+ query: taskFrameQuery,
1991
+ top_k: this.broadTopK,
1992
+ include_memories: false,
1993
+ user_id: scope.userId,
1994
+ session_id: scope.sessionId,
1995
+ max_tokens: this.maxTokens,
1996
+ compress: true,
1997
+ compression_strategy: "adaptive"
1998
+ })) : Promise.resolve({ name: "context_task_frame_broad", status: "skipped", durationMs: 0 }),
1999
+ taskFrameQuery ? this.runBranch("memory_task_frame_broad", () => this.args.adapter.searchMemories({
2000
+ project: scope.project,
2001
+ query: taskFrameQuery,
2002
+ user_id: scope.userId,
2003
+ session_id: scope.sessionId,
2004
+ top_k: this.broadTopK,
2005
+ include_pending: true,
2006
+ profile: "balanced"
2007
+ })) : Promise.resolve({ name: "memory_task_frame_broad", status: "skipped", durationMs: 0 })
2008
+ ]) : [
2009
+ { name: "context_primary_broad", status: "skipped", durationMs: 0 },
2010
+ { name: "memory_primary_broad", status: "skipped", durationMs: 0 },
2011
+ { name: "context_task_frame_broad", status: "skipped", durationMs: 0 },
2012
+ { name: "memory_task_frame_broad", status: "skipped", durationMs: 0 }
2013
+ ];
2014
+ const broadCollected = collectFromBranches(broadBranches, "broad");
2015
+ totalOkCount += broadCollected.okCount;
2016
+ if (fallbackUsed) {
2017
+ this.fallbackTriggers += 1;
2018
+ this.broadScopedTurns += 1;
2019
+ allCollected = [...allCollected, ...broadCollected.collected];
2020
+ }
2021
+ const ranked = this.rerank(allCollected, focusedScope);
2022
+ dedupedCount += ranked.dedupedCount;
2023
+ const floored = this.applyRelevanceFloor(ranked.items);
2024
+ droppedBelowFloor += floored.dropped;
2025
+ this.floorDroppedCount += droppedBelowFloor;
2026
+ this.droppedCount += droppedBelowFloor;
2027
+ const finalItems = floored.items.slice(0, this.broadTopK);
2028
+ this.injectedItemCount += finalItems.length;
2029
+ this.totalTurns += 1;
2030
+ const executedBranches = [...focusedBranches, ...broadBranches].filter((branch) => branch.status !== "skipped");
2031
+ for (const branch of [...focusedBranches, ...broadBranches]) {
1717
2032
  branchStatus[branch.name] = branch.status;
1718
- if (branch.status !== "ok") {
1719
- if (branch.status !== "skipped" && branch.reason) warnings.push(`${branch.name}:${branch.reason}`);
1720
- continue;
1721
- }
1722
- okCount += 1;
1723
- if (branch.name.startsWith("context")) {
1724
- collected.push(...this.contextItems(
1725
- branch.value,
1726
- branch.name.includes("task_frame") ? "task_frame" : "primary"
1727
- ));
1728
- } else {
1729
- collected.push(...this.memoryItems(
1730
- branch.value,
1731
- branch.name.includes("task_frame") ? "task_frame" : "primary"
1732
- ));
1733
- }
1734
2033
  }
1735
- const ranked = this.rerank(collected).slice(0, this.topK * 2);
1736
2034
  const prepared = {
1737
2035
  scope,
1738
2036
  retrieval: {
1739
2037
  primaryQuery,
1740
2038
  taskFrameQuery,
1741
2039
  warnings,
1742
- degraded: okCount < branches.filter((branch) => branch.status !== "skipped").length,
1743
- degradedReason: okCount === 0 ? "all_retrieval_failed" : warnings.length > 0 ? "partial_retrieval_failed" : void 0,
2040
+ degraded: totalOkCount < executedBranches.length,
2041
+ degradedReason: totalOkCount === 0 ? "all_retrieval_failed" : warnings.length > 0 ? "partial_retrieval_failed" : void 0,
1744
2042
  durationMs: Date.now() - startedAt,
1745
2043
  targetBudgetMs: this.targetRetrievalMs,
1746
2044
  hardTimeoutMs: this.hardRetrievalTimeoutMs,
1747
- branchStatus
2045
+ branchStatus,
2046
+ focusedScopeApplied,
2047
+ focusedSourceIds: focusedScope.sourceIds,
2048
+ focusedFileHints: focusedScope.fileHints.map((value) => pathBase(value)),
2049
+ clientScoped: Boolean(focusedScope.clientName),
2050
+ fallbackUsed,
2051
+ droppedBelowFloor,
2052
+ dedupedCount
1748
2053
  },
1749
- context: this.buildContext(ranked),
1750
- items: ranked
2054
+ context: this.buildContext(finalItems),
2055
+ items: finalItems
1751
2056
  };
1752
2057
  this.lastPreparedTurn = prepared.retrieval;
1753
2058
  return prepared;
@@ -1842,7 +2147,14 @@ ${lines.join("\n")}`;
1842
2147
  counters: {
1843
2148
  mergedCount: this.mergedCount,
1844
2149
  droppedCount: this.droppedCount,
1845
- bufferedLowSalience: this.bufferedLowSalience.length
2150
+ bufferedLowSalience: this.bufferedLowSalience.length,
2151
+ focusedPassHits: this.focusedPassHits,
2152
+ fallbackTriggers: this.fallbackTriggers,
2153
+ floorDroppedCount: this.floorDroppedCount,
2154
+ injectedItemCount: this.injectedItemCount,
2155
+ sourceScopedTurns: this.sourceScopedTurns,
2156
+ broadScopedTurns: this.broadScopedTurns,
2157
+ totalTurns: this.totalTurns
1846
2158
  }
1847
2159
  };
1848
2160
  }
@@ -2757,6 +3069,19 @@ var WhisperContext = class _WhisperContext {
2757
3069
  }
2758
3070
  ]);
2759
3071
  }
3072
+ async listMemories(params) {
3073
+ const projectRef = this.getRequiredProject(params.project);
3074
+ return this.withProjectRefFallback(projectRef, async (project) => {
3075
+ const query = new URLSearchParams({
3076
+ project,
3077
+ ...params.user_id ? { user_id: params.user_id } : {},
3078
+ ...params.session_id ? { session_id: params.session_id } : {},
3079
+ ...params.agent_id ? { agent_id: params.agent_id } : {},
3080
+ limit: String(Math.min(Math.max(params.limit ?? 200, 1), 200))
3081
+ });
3082
+ return this.request(`/v1/memories?${query.toString()}`, { method: "GET" });
3083
+ });
3084
+ }
2760
3085
  async addMemory(params) {
2761
3086
  const projectRef = this.getRequiredProject(params.project);
2762
3087
  return this.withProjectRefFallback(projectRef, async (project) => {
@@ -3421,9 +3746,10 @@ function createWhisperMcpRuntimeClient(options) {
3421
3746
  }
3422
3747
  var whisper = createWhisperMcpClient();
3423
3748
  var runtimeClient = createWhisperMcpRuntimeClient();
3749
+ var runtimeSessions = /* @__PURE__ */ new Map();
3424
3750
  var server = new McpServer({
3425
3751
  name: "whisper-context",
3426
- version: "0.2.8"
3752
+ version: "0.3.0"
3427
3753
  });
3428
3754
  function createMcpServer() {
3429
3755
  return server;
@@ -3485,7 +3811,7 @@ function getWorkspaceIdForPath(path, workspaceId) {
3485
3811
  const seed = `${path}|${DEFAULT_PROJECT || "default"}|${API_KEY.slice(0, 12)}`;
3486
3812
  return createHash("sha256").update(seed).digest("hex").slice(0, 20);
3487
3813
  }
3488
- function clamp01(value) {
3814
+ function clamp012(value) {
3489
3815
  if (Number.isNaN(value)) return 0;
3490
3816
  if (value < 0) return 0;
3491
3817
  if (value > 1) return 1;
@@ -3531,7 +3857,7 @@ function toEvidenceRef(source, workspaceId, methodFallback) {
3531
3857
  line_start: lineStart,
3532
3858
  ...lineEnd ? { line_end: lineEnd } : {},
3533
3859
  snippet: String(source.content || metadata.snippet || "").slice(0, 500),
3534
- score: clamp01(Number(source.score ?? metadata.score ?? 0)),
3860
+ score: clamp012(Number(source.score ?? metadata.score ?? 0)),
3535
3861
  retrieval_method: retrievalMethod,
3536
3862
  indexed_at: String(metadata.indexed_at || (/* @__PURE__ */ new Date()).toISOString()),
3537
3863
  ...metadata.commit ? { commit: String(metadata.commit) } : {},
@@ -3606,6 +3932,21 @@ async function resolveProjectRef(explicit) {
3606
3932
  return void 0;
3607
3933
  }
3608
3934
  }
3935
+ async function ingestSessionWithSyncFallback(params) {
3936
+ try {
3937
+ return await whisper.ingestSession({
3938
+ ...params,
3939
+ async: false,
3940
+ write_mode: "sync"
3941
+ });
3942
+ } catch (error) {
3943
+ const message = String(error?.message || error || "").toLowerCase();
3944
+ if (!message.includes("sync_write_restricted")) {
3945
+ throw error;
3946
+ }
3947
+ return whisper.ingestSession(params);
3948
+ }
3949
+ }
3609
3950
  function defaultMcpUserId() {
3610
3951
  const explicit = process.env.WHISPER_USER_ID?.trim();
3611
3952
  if (explicit) return explicit;
@@ -3625,18 +3966,55 @@ async function prepareAutomaticQuery(params) {
3625
3966
  throw new Error("Whisper runtime client unavailable.");
3626
3967
  }
3627
3968
  const scope = resolveMcpScope(params);
3628
- const runtime = runtimeClient.createAgentRuntime({
3629
- project: params.project,
3630
- userId: scope.userId,
3631
- sessionId: scope.sessionId,
3632
- workspacePath: scope.workspacePath,
3633
- topK: params.top_k,
3634
- clientName: "whisper-mcp"
3635
- });
3969
+ const key = [
3970
+ params.project || DEFAULT_PROJECT || "",
3971
+ scope.userId,
3972
+ scope.sessionId,
3973
+ scope.workspacePath || process.cwd(),
3974
+ String(params.top_k || 10)
3975
+ ].join("|");
3976
+ let runtime = runtimeSessions.get(key);
3977
+ if (!runtime) {
3978
+ runtime = runtimeClient.createAgentRuntime({
3979
+ project: params.project,
3980
+ userId: scope.userId,
3981
+ sessionId: scope.sessionId,
3982
+ workspacePath: scope.workspacePath,
3983
+ topK: params.top_k,
3984
+ clientName: "whisper-mcp"
3985
+ });
3986
+ runtimeSessions.set(key, runtime);
3987
+ }
3636
3988
  return runtime.beforeTurn({
3637
3989
  userMessage: params.query
3638
3990
  });
3639
3991
  }
3992
+ function noteAutomaticSourceActivity(params) {
3993
+ if (!runtimeClient) return;
3994
+ const sourceIds = [...new Set((params.sourceIds || []).map((value) => String(value || "").trim()).filter(Boolean))];
3995
+ if (sourceIds.length === 0) return;
3996
+ const scope = resolveMcpScope(params);
3997
+ const key = [
3998
+ params.project || DEFAULT_PROJECT || "",
3999
+ scope.userId,
4000
+ scope.sessionId,
4001
+ scope.workspacePath || process.cwd(),
4002
+ String(params.top_k || 10)
4003
+ ].join("|");
4004
+ let runtime = runtimeSessions.get(key);
4005
+ if (!runtime) {
4006
+ runtime = runtimeClient.createAgentRuntime({
4007
+ project: params.project,
4008
+ userId: scope.userId,
4009
+ sessionId: scope.sessionId,
4010
+ workspacePath: scope.workspacePath,
4011
+ topK: params.top_k,
4012
+ clientName: "whisper-mcp"
4013
+ });
4014
+ runtimeSessions.set(key, runtime);
4015
+ }
4016
+ runtime.noteSourceActivity(sourceIds);
4017
+ }
3640
4018
  function buildAbstain(args) {
3641
4019
  return {
3642
4020
  status: "abstained",
@@ -4476,29 +4854,67 @@ server.tool(
4476
4854
  }
4477
4855
  const automaticMode = include_memories !== false && include_graph !== true && !(chunk_types && chunk_types.length > 0) && max_tokens === void 0 && runtimeClient;
4478
4856
  if (automaticMode) {
4479
- const prepared = await prepareAutomaticQuery({
4480
- project: resolvedProject,
4481
- query,
4482
- top_k,
4483
- user_id,
4484
- session_id
4485
- });
4486
- if (!prepared.items.length) {
4487
- return { content: [{ type: "text", text: "No relevant context found." }] };
4488
- }
4489
- const warnings = prepared.retrieval.warnings.length ? `
4857
+ try {
4858
+ const prepared = await prepareAutomaticQuery({
4859
+ project: resolvedProject,
4860
+ query,
4861
+ top_k,
4862
+ user_id,
4863
+ session_id
4864
+ });
4865
+ if (!prepared.items.length) {
4866
+ return { content: [{ type: "text", text: "No relevant context found." }] };
4867
+ }
4868
+ const warnings = prepared.retrieval.warnings.length ? `
4490
4869
 
4491
4870
  [automatic_runtime]
4492
4871
  ${prepared.retrieval.warnings.join("\n")}` : "";
4493
- const scope2 = `project=${prepared.scope.project} user=${prepared.scope.userId} session=${prepared.scope.sessionId}`;
4494
- return {
4495
- content: [{
4496
- type: "text",
4497
- text: `Found ${prepared.items.length} runtime-ranked items (${prepared.retrieval.durationMs}ms, ${scope2}):
4872
+ const diagnostics = [
4873
+ `focused_scope=${prepared.retrieval.focusedScopeApplied}`,
4874
+ `fallback_used=${prepared.retrieval.fallbackUsed}`,
4875
+ `deduped=${prepared.retrieval.dedupedCount}`,
4876
+ `dropped_below_floor=${prepared.retrieval.droppedBelowFloor}`
4877
+ ].join(" ");
4878
+ const scope2 = `project=${prepared.scope.project} user=${prepared.scope.userId} session=${prepared.scope.sessionId}`;
4879
+ return {
4880
+ content: [{
4881
+ type: "text",
4882
+ text: `Found ${prepared.items.length} runtime-ranked items (${prepared.retrieval.durationMs}ms, ${scope2}, ${diagnostics}):
4498
4883
 
4499
4884
  ${prepared.context}${warnings}`
4500
- }]
4501
- };
4885
+ }]
4886
+ };
4887
+ } catch (error) {
4888
+ const automaticWarning = `Automatic runtime unavailable: ${error.message}. Falling back to broad/manual query path.`;
4889
+ const queryResult2 = await queryWithDegradedFallback({
4890
+ project: resolvedProject,
4891
+ query,
4892
+ top_k,
4893
+ include_memories: include_memories === true,
4894
+ include_graph,
4895
+ user_id: user_id || resolveMcpScope({ user_id }).userId,
4896
+ session_id: session_id || resolveMcpScope({ session_id }).sessionId
4897
+ });
4898
+ const response2 = queryResult2.response;
4899
+ if (response2.results.length === 0) {
4900
+ return { content: [{ type: "text", text: `No relevant context found.
4901
+
4902
+ [automatic_runtime]
4903
+ ${automaticWarning}` }] };
4904
+ }
4905
+ const scope2 = resolveMcpScope({ user_id, session_id });
4906
+ const header2 = `Found ${response2.meta.total} results (${response2.meta.latency_ms}ms${response2.meta.cache_hit ? ", cached" : ""}, project=${resolvedProject}, user=${scope2.userId}, session=${scope2.sessionId}):
4907
+
4908
+ `;
4909
+ const suffix2 = queryResult2.degraded_mode ? `
4910
+
4911
+ [degraded_mode=true] ${queryResult2.degraded_reason}
4912
+ Recommendation: ${queryResult2.recommendation}` : "";
4913
+ return { content: [{ type: "text", text: `${header2}${response2.context}
4914
+
4915
+ [automatic_runtime]
4916
+ ${automaticWarning}${suffix2}` }] };
4917
+ }
4502
4918
  }
4503
4919
  const queryResult = await queryWithDegradedFallback({
4504
4920
  project: resolvedProject,
@@ -4722,6 +5138,10 @@ server.tool(
4722
5138
  max_duration_minutes: input.max_duration_minutes,
4723
5139
  max_chunks: input.max_chunks
4724
5140
  });
5141
+ noteAutomaticSourceActivity({
5142
+ project: resolvedProject,
5143
+ sourceIds: [result.source_id]
5144
+ });
4725
5145
  return toTextResult(result);
4726
5146
  } catch (error) {
4727
5147
  return { content: [{ type: "text", text: `Error: ${error.message}` }] };
@@ -4756,8 +5176,12 @@ server.tool(
4756
5176
  },
4757
5177
  async ({ project, title, content, ingestion_profile, strategy_override, profile_config }) => {
4758
5178
  try {
5179
+ const resolvedProject = await resolveProjectRef(project);
5180
+ if (!resolvedProject) {
5181
+ return { content: [{ type: "text", text: "Error: No project resolved. Set WHISPER_PROJECT or provide project." }] };
5182
+ }
4759
5183
  await whisper.addContext({
4760
- project,
5184
+ project: resolvedProject,
4761
5185
  title,
4762
5186
  content,
4763
5187
  metadata: {
@@ -4810,6 +5234,11 @@ server.tool(
4810
5234
  strategy_override,
4811
5235
  profile_config
4812
5236
  });
5237
+ const sourceId = result?.source_id || result?.id;
5238
+ noteAutomaticSourceActivity({
5239
+ project: resolvedProject,
5240
+ sourceIds: sourceId ? [String(sourceId)] : []
5241
+ });
4813
5242
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
4814
5243
  }
4815
5244
  if (!content?.trim()) {
@@ -4893,7 +5322,7 @@ server.tool(
4893
5322
  content: message.content,
4894
5323
  timestamp: message.timestamp
4895
5324
  }));
4896
- const result = await whisper.ingestSession({
5325
+ const result = await ingestSessionWithSyncFallback({
4897
5326
  project: scope.project,
4898
5327
  session_id: scope.sessionId,
4899
5328
  user_id: scope.userId,
@@ -5007,8 +5436,12 @@ server.tool(
5007
5436
  },
5008
5437
  async ({ project, session_id, title, expiry_days }) => {
5009
5438
  try {
5439
+ const resolvedProject = await resolveProjectRef(project);
5440
+ if (!resolvedProject) {
5441
+ return { content: [{ type: "text", text: "Error: No project resolved. Set WHISPER_PROJECT or provide project." }] };
5442
+ }
5010
5443
  const result = await whisper.createSharedContext({
5011
- project,
5444
+ project: resolvedProject,
5012
5445
  session_id,
5013
5446
  title,
5014
5447
  expiry_days
@@ -5263,13 +5696,11 @@ server.tool(
5263
5696
  let memories = [];
5264
5697
  if (resolvedProject) {
5265
5698
  try {
5266
- const m = await whisper.searchMemoriesSOTA({
5699
+ const listed = await whisper.listMemories({
5267
5700
  project: resolvedProject,
5268
- query: "memory",
5269
- top_k: 100,
5270
- include_relations: true
5701
+ limit: 200
5271
5702
  });
5272
- memories = (m.results || []).map((r) => r.memory || r);
5703
+ memories = Array.isArray(listed?.memories) ? listed.memories : [];
5273
5704
  } catch {
5274
5705
  memories = [];
5275
5706
  }
@@ -5360,7 +5791,7 @@ server.tool(
5360
5791
  session_summaries: 0,
5361
5792
  documents: 0
5362
5793
  };
5363
- const resolvedProject = await resolveProjectRef(bundle.project);
5794
+ const resolvedProject = await resolveProjectRef(bundle.project || cachedProjectRef || DEFAULT_PROJECT || void 0);
5364
5795
  async function shouldSkipImportedMemory(memory) {
5365
5796
  if (dedupe_strategy === "none" || !resolvedProject) return false;
5366
5797
  const exactId = normalizeString2(memory?.id);
@@ -6244,6 +6675,10 @@ server.tool(
6244
6675
  max_chunks: input.max_chunks,
6245
6676
  auto_index: true
6246
6677
  });
6678
+ noteAutomaticSourceActivity({
6679
+ project: resolvedProject,
6680
+ sourceIds: [result.source_id]
6681
+ });
6247
6682
  return toTextResult(result);
6248
6683
  } catch (error) {
6249
6684
  return { content: [{ type: "text", text: `Error: ${error.message}` }] };
@@ -6310,7 +6745,13 @@ server.tool(
6310
6745
  content,
6311
6746
  timestamp
6312
6747
  });
6313
- const result = await whisper.ingestSession({ project, session_id, user_id, messages: normalizedMessages });
6748
+ const resolvedProject = await resolveProjectRef(project);
6749
+ const result = await ingestSessionWithSyncFallback({
6750
+ project: resolvedProject,
6751
+ session_id,
6752
+ user_id,
6753
+ messages: normalizedMessages
6754
+ });
6314
6755
  return primaryToolSuccess({
6315
6756
  tool: "record",
6316
6757
  session_id,
@@ -6368,7 +6809,11 @@ server.tool(
6368
6809
  },
6369
6810
  async ({ project, session_id, title, expiry_days }) => {
6370
6811
  try {
6371
- const result = await whisper.createSharedContext({ project, session_id, title, expiry_days });
6812
+ const resolvedProject = await resolveProjectRef(project);
6813
+ if (!resolvedProject) {
6814
+ return primaryToolError("No project resolved. Set WHISPER_PROJECT or provide project.");
6815
+ }
6816
+ const result = await whisper.createSharedContext({ project: resolvedProject, session_id, title, expiry_days });
6372
6817
  return primaryToolSuccess({
6373
6818
  tool: "share_context",
6374
6819
  share_id: result.share_id,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@usewhisper/mcp-server",
3
- "version": "2.5.0",
4
- "whisperContractVersion": "2026.03.09",
3
+ "version": "2.8.0",
4
+ "whisperContractVersion": "2026.03.10",
5
5
  "scripts": {
6
6
  "build": "tsup ../src/mcp/server.ts --format esm --out-dir dist",
7
7
  "prepublishOnly": "npm run build"