@exaudeus/memory-mcp 1.9.3 → 1.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/config.js CHANGED
@@ -251,18 +251,32 @@ export function getLobeConfigs() {
251
251
  process.stderr.write(`[memory-mcp] Failed to parse MEMORY_MCP_WORKSPACES: ${e}\n`);
252
252
  }
253
253
  }
254
- // 3. Fall back to single-repo default
255
- const repoRoot = process.env.MEMORY_MCP_REPO_ROOT ?? process.cwd();
256
- const explicitDir = process.env.MEMORY_MCP_DIR;
257
- const storageBudget = parseInt(process.env.MEMORY_MCP_BUDGET ?? '', 10) || DEFAULT_STORAGE_BUDGET_BYTES;
258
- configs.set('default', {
259
- repoRoot,
260
- memoryPath: resolveMemoryPath(repoRoot, 'default', explicitDir),
261
- storageBudgetBytes: storageBudget,
262
- alwaysInclude: false,
263
- embedder: autoEmbedder,
264
- });
265
- // No ensureAlwaysIncludeLobe here — single-repo default users have everything in one lobe
266
- process.stderr.write(`[memory-mcp] Using single-lobe default mode (cwd: ${repoRoot})\n`);
254
+ // 3. Single-repo fallback: only when MEMORY_MCP_REPO_ROOT is explicitly set
255
+ // (Using cwd() as a fallback caused cross-project contamination in shared MCP instances)
256
+ const explicitRepoRoot = process.env.MEMORY_MCP_REPO_ROOT;
257
+ if (explicitRepoRoot) {
258
+ const lobeName = path.basename(explicitRepoRoot);
259
+ const explicitDir = process.env.MEMORY_MCP_DIR;
260
+ const storageBudget = parseInt(process.env.MEMORY_MCP_BUDGET ?? '', 10) || DEFAULT_STORAGE_BUDGET_BYTES;
261
+ if (!lobeName || lobeName === path.sep || lobeName === '/') {
262
+ process.stderr.write(`[memory-mcp] MEMORY_MCP_REPO_ROOT="${explicitRepoRoot}" has an invalid basename — cannot derive a lobe name. ` +
263
+ `Use a path like "/path/to/my-project". Starting with zero lobes.\n`);
264
+ }
265
+ else {
266
+ configs.set(lobeName, {
267
+ repoRoot: explicitRepoRoot,
268
+ memoryPath: resolveMemoryPath(explicitRepoRoot, lobeName, explicitDir),
269
+ storageBudgetBytes: storageBudget,
270
+ alwaysInclude: false,
271
+ embedder: autoEmbedder,
272
+ });
273
+ process.stderr.write(`[memory-mcp] Single-lobe mode via MEMORY_MCP_REPO_ROOT: lobe="${lobeName}" root="${explicitRepoRoot}"\n`);
274
+ return { configs, origin: { source: 'default' }, embedder: autoEmbedder };
275
+ }
276
+ }
277
+ // No configuration found — start with zero lobes
278
+ // Agents will be guided to run memory_bootstrap() to create lobes
279
+ process.stderr.write(`[memory-mcp] No lobes configured. Agents will be prompted to run memory_bootstrap() when they first call a tool.\n` +
280
+ `[memory-mcp] To configure lobes: create memory-config.json, set MEMORY_MCP_WORKSPACES, or set MEMORY_MCP_REPO_ROOT.\n`);
267
281
  return { configs, origin: { source: 'default' }, embedder: autoEmbedder };
268
282
  }
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSche
8
8
  import { z } from 'zod';
9
9
  import path from 'path';
10
10
  import { readFile, writeFile } from 'fs/promises';
11
+ import { existsSync } from 'fs';
11
12
  import { MarkdownMemoryStore } from './store.js';
12
13
  import { parseTopicScope, parseTrustLevel } from './types.js';
13
14
  import { getLobeConfigs } from './config.js';
@@ -17,7 +18,7 @@ import { buildCrashReport, writeCrashReport, writeCrashReportSync, readLatestCra
17
18
  import { formatStaleSection, formatConflictWarning, formatStats, formatBehaviorConfigSection, mergeTagFrequencies, buildQueryFooter, buildBriefingTagPrimerSections, formatSearchMode, formatLootDrop } from './formatters.js';
18
19
  import { parseFilter, extractTitle } from './text-analyzer.js';
19
20
  import { VOCABULARY_ECHO_LIMIT, WARN_SEPARATOR } from './thresholds.js';
20
- import { matchRootsToLobeNames, buildLobeResolution } from './lobe-resolution.js';
21
+ import { matchRootsToLobeNames, buildLobeResolution, isPathPrefixOf } from './lobe-resolution.js';
21
22
  let serverMode = { kind: 'running' };
22
23
  const lobeHealth = new Map();
23
24
  const serverStartTime = Date.now();
@@ -77,10 +78,22 @@ const ALWAYS_INCLUDE_WRITE_TOPICS = new Set(['user', 'preferences']);
77
78
  * After this call, consumers use ctx.label — the raw lobe is not in scope. */
78
79
  function resolveToolContext(rawLobe) {
79
80
  const lobeNames = configManager.getLobeNames();
81
+ // Zero lobes configured — guide agent to bootstrap
82
+ if (lobeNames.length === 0) {
83
+ return {
84
+ ok: false,
85
+ error: `No lobes configured. Run memory_bootstrap(lobe: 'your-project-name', root: '/absolute/path/to/repo') to create one. ` +
86
+ `After bootstrapping, retry your call with the lobe name you chose.`,
87
+ };
88
+ }
80
89
  // Default to single lobe when omitted
81
90
  const lobe = rawLobe || (lobeNames.length === 1 ? lobeNames[0] : undefined);
82
91
  if (!lobe) {
83
- return { ok: false, error: `Lobe is required. Available: ${lobeNames.join(', ')}` };
92
+ return {
93
+ ok: false,
94
+ error: `Lobe is required. Available: ${lobeNames.join(', ')}. ` +
95
+ `If this is a new project, run memory_bootstrap(lobe: 'your-project-name', root: '/absolute/path/to/repo') to create a lobe for it.`,
96
+ };
84
97
  }
85
98
  // Check if lobe is degraded
86
99
  const health = configManager.getLobeHealth(lobe);
@@ -134,8 +147,10 @@ function inferLobeFromPaths(paths) {
134
147
  const config = configManager.getLobeConfig(lobeName);
135
148
  if (!config)
136
149
  continue;
137
- // Check if the file path starts with or is inside the repo root
138
- if (resolved.startsWith(config.repoRoot) || resolved.startsWith(path.basename(config.repoRoot))) {
150
+ // Check if the file path starts with or is inside the repo root (path-boundary aware)
151
+ const normalizedResolved = path.resolve(resolved);
152
+ const normalizedRoot = path.resolve(config.repoRoot);
153
+ if (isPathPrefixOf(normalizedRoot, normalizedResolved)) {
139
154
  matchedLobes.add(lobeName);
140
155
  }
141
156
  }
@@ -384,7 +399,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
384
399
  // but are hidden from tool discovery. Agents use the new v2 tools above.
385
400
  {
386
401
  name: 'memory_bootstrap',
387
- description: 'First time with a new codebase scans repo structure, README, and build system to seed initial memory. Run once per project. Example: {"lobe": "my-project"} or {"lobe": "new-project", "root": "/path/to/repo"} to auto-create lobe.',
402
+ description: 'When no lobe exists for a projectrun this before any other tool. Scans repo structure, README, and build system to create a named lobe and seed initial memory. Required param: "root" (absolute path to repo) when the lobe does not yet exist. Example: {"lobe": "my-project", "root": "/absolute/path/to/repo"}. After bootstrapping, all other tools become available for that lobe.',
388
403
  inputSchema: {
389
404
  type: 'object',
390
405
  properties: {
@@ -1566,16 +1581,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1566
1581
  }
1567
1582
  // Tool-specific hints based on which field failed
1568
1583
  let hint = '';
1584
+ const exampleLobe = lobeNames[0] ?? '<your-lobe>';
1585
+ const lobeHint = lobeNames.length > 0 ? `Available lobes: ${lobeList}` : 'No lobes configured — run memory_bootstrap with a "root" path to create one.';
1569
1586
  const v2ToolHints = {
1570
- brief: `brief takes an optional lobe. Example: {"lobe": "${lobeNames[0] ?? 'default'}"}. Available lobes: ${lobeList}`,
1571
- recall: `recall requires "context" (describe what you are working on). Example: {"context": "implementing auth flow", "lobe": "${lobeNames[0] ?? 'default'}"}. Available lobes: ${lobeList}`,
1572
- gotcha: `gotcha requires "lobe" and "observation". Example: {"lobe": "${lobeNames[0] ?? 'default'}", "observation": "Gradle cache breaks after Tuist changes"}`,
1573
- convention: `convention requires "lobe" and "observation". Example: {"lobe": "${lobeNames[0] ?? 'default'}", "observation": "All ViewModels use StateFlow"}`,
1574
- learn: `learn requires "lobe" and "observation". Example: {"lobe": "${lobeNames[0] ?? 'default'}", "observation": "Messaging uses MVVM with FlowCoordinator"}`,
1575
- prefer: `prefer requires "rule". Example: {"rule": "Always suggest the simplest solution first"}`,
1587
+ brief: `brief takes an optional lobe. Example: {"lobe": "${exampleLobe}"}. ${lobeHint}`,
1588
+ recall: `recall requires "context" (the area you need knowledge about). Example: {"context": "auth token refresh", "lobe": "${exampleLobe}"}. ${lobeHint}`,
1589
+ gotcha: `gotcha requires "lobe" and "observation". Example: {"lobe": "${exampleLobe}", "observation": "Gradle cache breaks after Tuist changes"}. ${lobeHint}`,
1590
+ convention: `convention requires "lobe" and "observation". Example: {"lobe": "${exampleLobe}", "observation": "All ViewModels use StateFlow"}. ${lobeHint}`,
1591
+ learn: `learn requires "lobe" and "observation". Example: {"lobe": "${exampleLobe}", "observation": "Messaging uses MVVM with FlowCoordinator"}. ${lobeHint}`,
1592
+ prefer: `prefer requires "rule". Example: {"rule": "Always suggest the simplest solution first"}. Omit lobe for global; add "lobe" to scope to a project.`,
1576
1593
  fix: `fix requires "id". Optional: "correction" (new text; omit to delete). Example: {"id": "gotcha-3f7a", "correction": "updated text"}`,
1577
- gotchas: `gotchas takes optional "lobe" and "area". Example: {"lobe": "${lobeNames[0] ?? 'default'}", "area": "auth"}. Available lobes: ${lobeList}`,
1578
- conventions: `conventions takes optional "lobe" and "area". Example: {"lobe": "${lobeNames[0] ?? 'default'}", "area": "testing"}. Available lobes: ${lobeList}`,
1594
+ gotchas: `gotchas takes optional "lobe" and "area". Example: {"lobe": "${exampleLobe}", "area": "auth"}. ${lobeHint}`,
1595
+ conventions: `conventions takes optional "lobe" and "area". Example: {"lobe": "${exampleLobe}", "area": "testing"}. ${lobeHint}`,
1579
1596
  };
1580
1597
  if (name in v2ToolHints) {
1581
1598
  hint = `\n\nUsage: ${v2ToolHints[name]}`;
@@ -1768,6 +1785,26 @@ async function main() {
1768
1785
  }
1769
1786
  // Initialize ConfigManager with current config state
1770
1787
  configManager = new ConfigManager(configPath, { configs: lobeConfigs, origin: configOrigin }, stores, lobeHealth);
1788
+ // Warn if a 'default' lobe data directory exists but no 'default' lobe is configured.
1789
+ // This indicates a user who was relying on the old zero-config default mode.
1790
+ if (!lobeConfigs.has('default')) {
1791
+ const orphanCandidates = [
1792
+ path.join(process.cwd(), '.memory', 'default'),
1793
+ ...(process.env.MEMORY_MCP_DIR ? [path.join(process.env.MEMORY_MCP_DIR, 'default')] : []),
1794
+ ];
1795
+ for (const candidate of orphanCandidates) {
1796
+ try {
1797
+ if (existsSync(candidate)) {
1798
+ process.stderr.write(`[memory-mcp] WARNING: Found orphaned 'default' lobe data at ${candidate} but no 'default' lobe is configured.\n` +
1799
+ `[memory-mcp] This data was created by the old zero-config default mode.\n` +
1800
+ `[memory-mcp] To recover: run memory_bootstrap(lobe: 'your-project-name', root: '/path/to/repo') to create a named lobe,\n` +
1801
+ `[memory-mcp] then manually move files from ${candidate}/ to the new lobe's memory directory.\n`);
1802
+ break;
1803
+ }
1804
+ }
1805
+ catch { /* ignore fs errors during startup check */ }
1806
+ }
1807
+ }
1771
1808
  const transport = new StdioServerTransport();
1772
1809
  // Handle transport errors — journal and exit
1773
1810
  transport.onerror = (error) => {
@@ -16,6 +16,9 @@ export interface LobeRootConfig {
16
16
  readonly name: string;
17
17
  readonly repoRoot: string;
18
18
  }
19
+ /** Check if `child` is equal to or nested under `parent` with path-boundary awareness.
20
+ * Prevents false matches like "/projects/zillow-tools" matching "/projects/zillow". */
21
+ export declare function isPathPrefixOf(parent: string, child: string): boolean;
19
22
  /** Match MCP client workspace root URIs against known lobe repo roots.
20
23
  * Returns matched lobe names, or empty array if none match.
21
24
  *
@@ -10,7 +10,7 @@
10
10
  import path from 'path';
11
11
  /** Check if `child` is equal to or nested under `parent` with path-boundary awareness.
12
12
  * Prevents false matches like "/projects/zillow-tools" matching "/projects/zillow". */
13
- function isPathPrefixOf(parent, child) {
13
+ export function isPathPrefixOf(parent, child) {
14
14
  if (child === parent)
15
15
  return true;
16
16
  // Ensure the prefix ends at a path separator boundary
@@ -81,9 +81,16 @@ export function buildLobeResolution(allLobeNames, matchedLobes, alwaysIncludeLob
81
81
  };
82
82
  }
83
83
  // Fallback — no lobes could be determined
84
+ if (allLobeNames.length === 0) {
85
+ return {
86
+ kind: 'global-only',
87
+ hint: `No lobes configured. Run memory_bootstrap(lobe: 'your-project-name', root: '/absolute/path/to/repo') to create one.`,
88
+ };
89
+ }
84
90
  return {
85
91
  kind: 'global-only',
86
92
  hint: `Multiple lobes available (${allLobeNames.join(', ')}) but none could be inferred from client workspace roots. ` +
87
- `Specify lobe parameter for lobe-specific results.`,
93
+ `Specify lobe parameter for lobe-specific results. ` +
94
+ `If this is a new project not in the list, run memory_bootstrap(lobe: 'your-project-name', root: '/absolute/path/to/repo') to add it.`,
88
95
  };
89
96
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exaudeus/memory-mcp",
3
- "version": "1.9.3",
3
+ "version": "1.9.5",
4
4
  "description": "Codebase memory MCP server - persistent, evolving knowledge for AI coding agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",