diff --git a/.mcp.json b/.mcp.json index 0ae7b42..eab346c 100644 --- a/.mcp.json +++ b/.mcp.json @@ -1,11 +1,10 @@ { "mcpServers": { "knowledge-graph": { - "command": "npx", - "args": ["tsx", "${CLAUDE_PLUGIN_ROOT}/src/mcp/index.ts"], + "command": "${CLAUDE_PLUGIN_ROOT}/node_modules/.bin/tsx", + "args": ["${CLAUDE_PLUGIN_ROOT}/src/mcp/index.ts"], "env": { - "KG_VAULT_PATH": "${KG_VAULT_PATH}", - "KG_DATA_DIR": "${KG_DATA_DIR}" + "KG_VAULT_PATH": "${KG_VAULT_PATH}" } } } diff --git a/src/lib/config.ts b/src/lib/config.ts index c2c37b4..05ed0b8 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -1,5 +1,5 @@ import { homedir } from 'os'; -import { join } from 'path'; +import { isAbsolute, join } from 'path'; export interface KGConfig { vaultPath: string; @@ -12,9 +12,34 @@ export interface ConfigOverrides { dataDir?: string; } +const UNRESOLVED_TEMPLATE = /^\$\{[A-Z0-9_]+\}$/; + +/** True when an MCP host left a ${VAR} placeholder unexpanded. */ +export function isUnresolvedTemplate(value: string | undefined): boolean { + return !!value && UNRESOLVED_TEMPLATE.test(value); +} + +function resolveEnvValue(value: string | undefined): string | undefined { + if (!value || isUnresolvedTemplate(value)) return undefined; + return value; +} + +function resolveEnvDataDir( + value: string | undefined, + defaultDataDir: string, +): string { + const resolved = resolveEnvValue(value); + if (!resolved || !isAbsolute(resolved)) return defaultDataDir; + return resolved; +} + export function resolveConfig(overrides: ConfigOverrides): KGConfig { + const xdgData = process.env.XDG_DATA_HOME + ?? join(homedir(), '.local', 'share'); + const defaultDataDir = join(xdgData, 'knowledge-graph'); + const vaultPath = overrides.vaultPath - ?? process.env.KG_VAULT_PATH; + ?? resolveEnvValue(process.env.KG_VAULT_PATH); if (!vaultPath) { throw new Error( @@ -22,12 +47,8 @@ export function resolveConfig(overrides: ConfigOverrides): KGConfig { ); } - const xdgData = process.env.XDG_DATA_HOME - ?? join(homedir(), '.local', 'share'); - const dataDir = overrides.dataDir - ?? process.env.KG_DATA_DIR - ?? join(xdgData, 'knowledge-graph'); + ?? resolveEnvDataDir(process.env.KG_DATA_DIR, defaultDataDir); return { vaultPath, diff --git a/test/config.test.ts b/test/config.test.ts index 4cda375..eda8f6a 100644 --- a/test/config.test.ts +++ b/test/config.test.ts @@ -47,4 +47,26 @@ describe('resolveConfig', () => { const config = resolveConfig({}); expect(config.dataDir).toContain('.local/share/knowledge-graph'); }); + + it('ignores unexpanded KG_DATA_DIR template and uses XDG default', () => { + process.env.KG_VAULT_PATH = '/tmp/vault'; + process.env.XDG_DATA_HOME = '/tmp/xdg'; + process.env.KG_DATA_DIR = '${KG_DATA_DIR}'; + const config = resolveConfig({}); + expect(config.dataDir).toBe('/tmp/xdg/knowledge-graph'); + }); + + it('throws when KG_VAULT_PATH is an unexpanded template', () => { + process.env.KG_VAULT_PATH = '${KG_VAULT_PATH}'; + delete process.env.KG_DATA_DIR; + expect(() => resolveConfig({})).toThrow(/vault/i); + }); + + it('ignores relative KG_DATA_DIR env paths and uses XDG default', () => { + process.env.KG_VAULT_PATH = '/tmp/vault'; + process.env.XDG_DATA_HOME = '/tmp/xdg'; + process.env.KG_DATA_DIR = 'relative/path'; + const config = resolveConfig({}); + expect(config.dataDir).toBe('/tmp/xdg/knowledge-graph'); + }); });