@exaudeus/memory-mcp 1.9.4 → 1.9.6
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-manager.d.ts +6 -0
- package/dist/config-manager.js +11 -0
- package/dist/config.js +27 -13
- package/dist/index.js +80 -25
- package/dist/lobe-resolution.d.ts +3 -0
- package/dist/lobe-resolution.js +9 -2
- package/package.json +1 -1
package/dist/config-manager.d.ts
CHANGED
|
@@ -35,6 +35,12 @@ export declare class ConfigManager {
|
|
|
35
35
|
constructor(configPath: string, initial: LoadedConfig, initialStores: Map<string, MarkdownMemoryStore>, initialHealth: Map<string, LobeHealth>);
|
|
36
36
|
/** Derive alwaysInclude lobe names from config — pure, no side effects. */
|
|
37
37
|
private static computeAlwaysIncludeLobes;
|
|
38
|
+
/**
|
|
39
|
+
* Adopt a newly created config file that didn't exist at startup.
|
|
40
|
+
* Used by memory_bootstrap when it creates memory-config.json from scratch.
|
|
41
|
+
* Switches the manager from default/env mode to file-based mode and reloads.
|
|
42
|
+
*/
|
|
43
|
+
adoptNewConfigFile(filePath: string): Promise<void>;
|
|
38
44
|
/**
|
|
39
45
|
* Ensure config is fresh. Call at the start of every tool handler.
|
|
40
46
|
* Stats config file, reloads if mtime changed. Graceful on all errors.
|
package/dist/config-manager.js
CHANGED
|
@@ -36,6 +36,17 @@ export class ConfigManager {
|
|
|
36
36
|
.filter(([, config]) => config.alwaysInclude === true)
|
|
37
37
|
.map(([name]) => name);
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Adopt a newly created config file that didn't exist at startup.
|
|
41
|
+
* Used by memory_bootstrap when it creates memory-config.json from scratch.
|
|
42
|
+
* Switches the manager from default/env mode to file-based mode and reloads.
|
|
43
|
+
*/
|
|
44
|
+
async adoptNewConfigFile(filePath) {
|
|
45
|
+
this.configPath = filePath;
|
|
46
|
+
this.configOrigin = { source: 'file', path: filePath };
|
|
47
|
+
this.configMtime = 0; // Force reload on next ensureFresh
|
|
48
|
+
await this.ensureFresh();
|
|
49
|
+
}
|
|
39
50
|
/**
|
|
40
51
|
* Ensure config is fresh. Call at the start of every tool handler.
|
|
41
52
|
* Stats config file, reloads if mtime changed. Graceful on all errors.
|
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.
|
|
255
|
-
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
}
|
|
@@ -186,6 +201,13 @@ async function resolveLobesForRead(isFirstMemoryToolCall = true) {
|
|
|
186
201
|
/** Build the shared lobe property for tool schemas — called on each ListTools request
|
|
187
202
|
* so the description and enum stay in sync after a hot-reload adds or removes lobes. */
|
|
188
203
|
function buildLobeProperty(currentLobeNames) {
|
|
204
|
+
if (currentLobeNames.length === 0) {
|
|
205
|
+
return {
|
|
206
|
+
type: 'string',
|
|
207
|
+
description: 'Memory lobe name. No lobes configured yet — run memory_bootstrap(lobe: "your-project", root: "/absolute/path/to/repo") first.',
|
|
208
|
+
enum: undefined,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
189
211
|
const isSingle = currentLobeNames.length === 1;
|
|
190
212
|
return {
|
|
191
213
|
type: 'string',
|
|
@@ -384,7 +406,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
384
406
|
// but are hidden from tool discovery. Agents use the new v2 tools above.
|
|
385
407
|
{
|
|
386
408
|
name: 'memory_bootstrap',
|
|
387
|
-
description: '
|
|
409
|
+
description: 'When no lobe exists for a project — run 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
410
|
inputSchema: {
|
|
389
411
|
type: 'object',
|
|
390
412
|
properties: {
|
|
@@ -548,6 +570,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
548
570
|
sections.push(crashSection);
|
|
549
571
|
if (degradedSection)
|
|
550
572
|
sections.push(degradedSection);
|
|
573
|
+
// Zero lobes — guide agent to bootstrap before anything else
|
|
574
|
+
if (configManager.getLobeNames().length === 0) {
|
|
575
|
+
sections.push('## No Lobes Configured\n\n' +
|
|
576
|
+
'No memory lobes exist yet. Run **memory_bootstrap** to create one for this project:\n\n' +
|
|
577
|
+
'```\nmemory_bootstrap(lobe: "your-project-name", root: "/absolute/path/to/repo")\n```\n\n' +
|
|
578
|
+
'After bootstrapping, call **brief** again to load project context.');
|
|
579
|
+
return { content: [{ type: 'text', text: sections.join('\n\n---\n\n') }] };
|
|
580
|
+
}
|
|
551
581
|
// Collect briefing across all lobes (or specified lobe + alwaysInclude)
|
|
552
582
|
const briefingLobeNames = allBriefingLobes;
|
|
553
583
|
const allStale = [];
|
|
@@ -1477,39 +1507,44 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1477
1507
|
root: z.string().optional(),
|
|
1478
1508
|
budgetMB: z.number().positive().optional(),
|
|
1479
1509
|
}).parse(args);
|
|
1480
|
-
// Auto-create lobe: if the lobe is unknown AND root is provided
|
|
1481
|
-
//
|
|
1482
|
-
//
|
|
1510
|
+
// Auto-create lobe: if the lobe is unknown AND root is provided, write the new
|
|
1511
|
+
// lobe entry into memory-config.json and hot-reload. If no config file exists yet,
|
|
1512
|
+
// create one at cwd()/memory-config.json — this is the zero-config bootstrap path.
|
|
1483
1513
|
if (rawLobe && root && !configManager.getStore(rawLobe)) {
|
|
1484
1514
|
const origin = configManager.getConfigOrigin();
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
}],
|
|
1492
|
-
isError: true,
|
|
1493
|
-
};
|
|
1494
|
-
}
|
|
1515
|
+
// Determine the config file path to write to:
|
|
1516
|
+
// - If already file-based, use the existing file
|
|
1517
|
+
// - Otherwise, create memory-config.json in cwd() (the server's working directory)
|
|
1518
|
+
const targetConfigPath = origin.source === 'file'
|
|
1519
|
+
? origin.path
|
|
1520
|
+
: path.join(process.cwd(), 'memory-config.json');
|
|
1495
1521
|
try {
|
|
1496
|
-
|
|
1497
|
-
|
|
1522
|
+
let config = {};
|
|
1523
|
+
if (origin.source === 'file') {
|
|
1524
|
+
const raw = await readFile(targetConfigPath, 'utf-8');
|
|
1525
|
+
config = JSON.parse(raw);
|
|
1526
|
+
}
|
|
1498
1527
|
if (!config.lobes || typeof config.lobes !== 'object')
|
|
1499
1528
|
config.lobes = {};
|
|
1500
1529
|
config.lobes[rawLobe] = { root, budgetMB: budgetMB ?? 2 };
|
|
1501
|
-
await writeFile(
|
|
1502
|
-
process.stderr.write(`[memory-mcp] Auto-added lobe "${rawLobe}" (root: ${root}) to
|
|
1530
|
+
await writeFile(targetConfigPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
1531
|
+
process.stderr.write(`[memory-mcp] Auto-added lobe "${rawLobe}" (root: ${root}) to ${targetConfigPath}\n`);
|
|
1503
1532
|
}
|
|
1504
1533
|
catch (err) {
|
|
1505
1534
|
const message = err instanceof Error ? err.message : String(err);
|
|
1506
1535
|
return {
|
|
1507
|
-
content: [{ type: 'text', text: `Failed to
|
|
1536
|
+
content: [{ type: 'text', text: `Failed to write lobe "${rawLobe}" to ${targetConfigPath}: ${message}` }],
|
|
1508
1537
|
isError: true,
|
|
1509
1538
|
};
|
|
1510
1539
|
}
|
|
1511
|
-
// Reload config to pick up the new lobe
|
|
1512
|
-
|
|
1540
|
+
// Reload config to pick up the new lobe.
|
|
1541
|
+
// If the file was just created from scratch, adopt it as the new config source.
|
|
1542
|
+
if (origin.source !== 'file') {
|
|
1543
|
+
await configManager.adoptNewConfigFile(targetConfigPath);
|
|
1544
|
+
}
|
|
1545
|
+
else {
|
|
1546
|
+
await configManager.ensureFresh();
|
|
1547
|
+
}
|
|
1513
1548
|
}
|
|
1514
1549
|
// Resolve store — after this point, rawLobe is never used again
|
|
1515
1550
|
const ctx = resolveToolContext(rawLobe);
|
|
@@ -1770,6 +1805,26 @@ async function main() {
|
|
|
1770
1805
|
}
|
|
1771
1806
|
// Initialize ConfigManager with current config state
|
|
1772
1807
|
configManager = new ConfigManager(configPath, { configs: lobeConfigs, origin: configOrigin }, stores, lobeHealth);
|
|
1808
|
+
// Warn if a 'default' lobe data directory exists but no 'default' lobe is configured.
|
|
1809
|
+
// This indicates a user who was relying on the old zero-config default mode.
|
|
1810
|
+
if (!lobeConfigs.has('default')) {
|
|
1811
|
+
const orphanCandidates = [
|
|
1812
|
+
path.join(process.cwd(), '.memory', 'default'),
|
|
1813
|
+
...(process.env.MEMORY_MCP_DIR ? [path.join(process.env.MEMORY_MCP_DIR, 'default')] : []),
|
|
1814
|
+
];
|
|
1815
|
+
for (const candidate of orphanCandidates) {
|
|
1816
|
+
try {
|
|
1817
|
+
if (existsSync(candidate)) {
|
|
1818
|
+
process.stderr.write(`[memory-mcp] WARNING: Found orphaned 'default' lobe data at ${candidate} but no 'default' lobe is configured.\n` +
|
|
1819
|
+
`[memory-mcp] This data was created by the old zero-config default mode.\n` +
|
|
1820
|
+
`[memory-mcp] To recover: run memory_bootstrap(lobe: 'your-project-name', root: '/path/to/repo') to create a named lobe,\n` +
|
|
1821
|
+
`[memory-mcp] then manually move files from ${candidate}/ to the new lobe's memory directory.\n`);
|
|
1822
|
+
break;
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
catch { /* ignore fs errors during startup check */ }
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1773
1828
|
const transport = new StdioServerTransport();
|
|
1774
1829
|
// Handle transport errors — journal and exit
|
|
1775
1830
|
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
|
*
|
package/dist/lobe-resolution.js
CHANGED
|
@@ -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
|
}
|