diff --git a/src/cli/commands/mcp.ts b/src/cli/commands/mcp.ts index b2a66c0..f718427 100644 --- a/src/cli/commands/mcp.ts +++ b/src/cli/commands/mcp.ts @@ -19,12 +19,19 @@ import { flagValue, hasFlag } from '../flags.js'; const SERVER_NAME = 'memoryd'; -/** The config entry every client receives. */ -const SERVER_ENTRY = { - command : SERVER_NAME, - args : ['serve', '--stdio'], - env : {}, -}; +/** Build the config entry, prompting for password if needed. */ +function serverEntry(): Record { + const password = process.env.MEMORYD_PASSWORD; + const env: Record = {}; + if (password) { + env.MEMORYD_PASSWORD = password; + } + return { + command : SERVER_NAME, + args : ['serve', '--stdio'], + env, + }; +} // --------------------------------------------------------------------------- // Client definitions @@ -93,7 +100,7 @@ function writeJsonConfig(path: string, config: McpConfig): void { function addServerToConfig(path: string): void { const config = readJsonConfig(path); config.mcpServers = config.mcpServers ?? {}; - config.mcpServers[SERVER_NAME] = SERVER_ENTRY; + config.mcpServers[SERVER_NAME] = serverEntry(); writeJsonConfig(path, config); } @@ -178,12 +185,28 @@ function uninstallProject(): void { function printConfig(): void { const config = { mcpServers: { - [SERVER_NAME]: SERVER_ENTRY, + [SERVER_NAME]: serverEntry(), }, }; console.log(JSON.stringify(config, null, 2)); } +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +/** Warn if MEMORYD_PASSWORD is not set after install. */ +function warnIfNoPassword(): void { + if (!process.env.MEMORYD_PASSWORD) { + console.log(''); + console.log( + 'Warning: MEMORYD_PASSWORD is not set. Add it to your MCP client\'s env ' + + 'config or memoryd will prompt for a password on stdin ' + + '(incompatible with stdio transport).', + ); + } +} + // --------------------------------------------------------------------------- // Public command // --------------------------------------------------------------------------- @@ -225,6 +248,7 @@ async function mcpInstall(args: string[]): Promise { const scope = flagValue(args, '--scope'); if (scope === 'project') { installProject(); + warnIfNoPassword(); return; } @@ -241,6 +265,7 @@ async function mcpInstall(args: string[]): Promise { } client.install(); console.log(`Installed memoryd in ${client.label}.`); + warnIfNoPassword(); return; } @@ -264,6 +289,8 @@ async function mcpInstall(args: string[]): Promise { console.error(`Failed to install in ${client.label}: ${msg}`); } } + + warnIfNoPassword(); } // --------------------------------------------------------------------------- diff --git a/src/cli/main.ts b/src/cli/main.ts index 35534e3..4514ddf 100644 --- a/src/cli/main.ts +++ b/src/cli/main.ts @@ -153,13 +153,20 @@ async function main(): Promise { const passwordFlag = flagValue(rest, '--password'); const password = await getPassword(passwordFlag); - const { resolveProfile, profileDataPath } = await import('../profiles/config.js'); + const { resolveProfile, profileDataPath, profilesDir } = await import('../profiles/config.js'); const profileFlag = flagValue(rest, '--profile'); const profileName = resolveProfile(profileFlag); const dataPath = profileName ? profileDataPath(profileName) : undefined; + // Derive a per-profile sidecar path so different identities don't + // cross-contaminate the search index. + const { resolveConfig } = await import('../config.js'); + const config = profileName + ? resolveConfig({ sidecarPath: join(profilesDir(), profileName, 'index.db') }) + : resolveConfig(); + const { connectAgent } = await import('./agent.js'); - const ctx = await connectAgent({ password, dataPath }); + const ctx = await connectAgent({ password, dataPath, config }); const json = hasFlag(rest, '--json'); switch (command) { diff --git a/tests/cli/mcp.spec.ts b/tests/cli/mcp.spec.ts index 897f284..014c4ca 100644 --- a/tests/cli/mcp.spec.ts +++ b/tests/cli/mcp.spec.ts @@ -60,6 +60,68 @@ describe('mcp install/uninstall', () => { }); }); + // ------------------------------------------------------------------------- + // password in env + // ------------------------------------------------------------------------- + + describe('password inclusion', () => { + const origPassword = process.env.MEMORYD_PASSWORD; + + afterEach(() => { + if (origPassword !== undefined) { + process.env.MEMORYD_PASSWORD = origPassword; + } else { + delete process.env.MEMORYD_PASSWORD; + } + }); + + it('includes MEMORYD_PASSWORD in config when set', async () => { + process.env.MEMORYD_PASSWORD = 'test-secret'; + const { mcpCommand } = await import('../../src/cli/commands/mcp.js'); + const lines = await captureLog(() => mcpCommand(['install', '--print'])); + const json = JSON.parse(lines.join('\n')); + expect(json.mcpServers.memoryd.env.MEMORYD_PASSWORD).toBe('test-secret'); + }); + + it('omits MEMORYD_PASSWORD from config when not set', async () => { + delete process.env.MEMORYD_PASSWORD; + const { mcpCommand } = await import('../../src/cli/commands/mcp.js'); + const lines = await captureLog(() => mcpCommand(['install', '--print'])); + const json = JSON.parse(lines.join('\n')); + expect(json.mcpServers.memoryd.env.MEMORYD_PASSWORD).toBeUndefined(); + }); + + it('warns when MEMORYD_PASSWORD is not set after install', async () => { + delete process.env.MEMORYD_PASSWORD; + const origCwd = process.cwd(); + const projectDir = join(TEST_DIR, 'pw-warn-project'); + mkdirSync(projectDir, { recursive: true }); + process.chdir(projectDir); + try { + const { mcpCommand } = await import('../../src/cli/commands/mcp.js'); + const lines = await captureLog(() => mcpCommand(['install', '--scope', 'project'])); + expect(lines.some(l => l.includes('Warning: MEMORYD_PASSWORD is not set'))).toBe(true); + } finally { + process.chdir(origCwd); + } + }); + + it('does not warn when MEMORYD_PASSWORD is set', async () => { + process.env.MEMORYD_PASSWORD = 'test-secret'; + const origCwd = process.cwd(); + const projectDir = join(TEST_DIR, 'pw-nowarn-project'); + mkdirSync(projectDir, { recursive: true }); + process.chdir(projectDir); + try { + const { mcpCommand } = await import('../../src/cli/commands/mcp.js'); + const lines = await captureLog(() => mcpCommand(['install', '--scope', 'project'])); + expect(lines.some(l => l.includes('Warning: MEMORYD_PASSWORD is not set'))).toBe(false); + } finally { + process.chdir(origCwd); + } + }); + }); + // ------------------------------------------------------------------------- // --scope project // ------------------------------------------------------------------------- diff --git a/tests/config.spec.ts b/tests/config.spec.ts index 0632300..d8ffdff 100644 --- a/tests/config.spec.ts +++ b/tests/config.spec.ts @@ -103,4 +103,14 @@ describe('resolveConfig', () => { expect(config.port).toBe(5000); expect(config.host).toBe('0.0.0.0'); }); + + it('accepts per-profile sidecar path override', () => { + const config = resolveConfig({ + sidecarPath: '/home/user/.enbox/profiles/alice/index.db', + }); + expect(config.sidecarPath).toBe('/home/user/.enbox/profiles/alice/index.db'); + // Other defaults still apply. + expect(config.embedding.provider).toBe('noop'); + expect(config.port).toBe(3200); + }); });