fix mcp --index store selection
This commit is contained in:
parent
e8229d8bfb
commit
b775592230
@ -13,6 +13,7 @@
|
||||
- MCP: seed llama.cpp/GGML quiet env vars before launching `qmd mcp` so native logs cannot pollute stdio JSON-RPC framing. #593
|
||||
- CLI: remove CommonJS `require()` calls from ESM index path normalization so `qmd --index <path>` no longer crashes with `ERR_AMBIGUOUS_MODULE_SYNTAX` on Node 22+. #634
|
||||
- Windows CUDA: serialize llama.cpp embedding/reranking contexts by default to avoid intermittent `ggml-cuda.cu:98` crashes in `qmd query`; set `QMD_EMBED_PARALLELISM` to opt back into parallel contexts if your driver is stable. #519
|
||||
- MCP: make `qmd mcp --index <name>` use the selected index for both foreground and daemon HTTP servers instead of falling back to the default store. #343
|
||||
- GPU: respect explicit `QMD_LLAMA_GPU=metal|vulkan|cuda` backend overrides instead of always using auto GPU selection. #529
|
||||
- Fix: preserve original filename case in `handelize()`. The previous
|
||||
`.toLowerCase()` call made indexed paths unreachable on case-sensitive
|
||||
|
||||
@ -3253,9 +3253,10 @@ if (isMain) {
|
||||
const logPath = resolve(cacheDir, "mcp.log");
|
||||
const logFd = openSync(logPath, "w"); // truncate — fresh log per daemon run
|
||||
const selfPath = fileURLToPath(import.meta.url);
|
||||
const indexArgs = cli.values.index ? ["--index", String(cli.values.index)] : [];
|
||||
const spawnArgs = selfPath.endsWith(".ts")
|
||||
? ["--import", pathJoin(dirname(selfPath), "..", "..", "node_modules", "tsx", "dist", "esm", "index.mjs"), selfPath, "mcp", "--http", "--port", String(port)]
|
||||
: [selfPath, "mcp", "--http", "--port", String(port)];
|
||||
? ["--import", pathJoin(dirname(selfPath), "..", "..", "node_modules", "tsx", "dist", "esm", "index.mjs"), selfPath, ...indexArgs, "mcp", "--http", "--port", String(port)]
|
||||
: [selfPath, ...indexArgs, "mcp", "--http", "--port", String(port)];
|
||||
const child = nodeSpawn(process.execPath, spawnArgs, {
|
||||
stdio: ["ignore", logFd, logFd],
|
||||
detached: true,
|
||||
@ -3275,7 +3276,7 @@ if (isMain) {
|
||||
process.removeAllListeners("SIGINT");
|
||||
const { startMcpHttpServer } = await import("../mcp/server.js");
|
||||
try {
|
||||
await startMcpHttpServer(port);
|
||||
await startMcpHttpServer(port, { dbPath: getDbPath() });
|
||||
} catch (e: any) {
|
||||
if (e?.code === "EADDRINUSE") {
|
||||
console.error(`Port ${port} already in use. Try a different port with --port.`);
|
||||
@ -3286,7 +3287,7 @@ if (isMain) {
|
||||
} else {
|
||||
// Default: stdio transport
|
||||
const { startMcpServer } = await import("../mcp/server.js");
|
||||
await startMcpServer();
|
||||
await startMcpServer({ dbPath: getDbPath() });
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@ -538,7 +538,11 @@ Intent-aware lex (C++ performance, not sports):
|
||||
// Transport: stdio (default)
|
||||
// =============================================================================
|
||||
|
||||
export async function startMcpServer(): Promise<void> {
|
||||
export type McpStartupOptions = {
|
||||
dbPath?: string;
|
||||
};
|
||||
|
||||
export async function startMcpServer(options: McpStartupOptions = {}): Promise<void> {
|
||||
// Opt into production mode when the MCP server is actually started, not
|
||||
// when this module is merely imported for its exports. Importing the module
|
||||
// at the top level flipped the global production flag and broke test
|
||||
@ -547,7 +551,7 @@ export async function startMcpServer(): Promise<void> {
|
||||
enableProductionMode();
|
||||
const configPath = getConfigPath();
|
||||
const store = await createStore({
|
||||
dbPath: getDefaultDbPath(),
|
||||
dbPath: options.dbPath ?? getDefaultDbPath(),
|
||||
...(existsSync(configPath) ? { configPath } : {}),
|
||||
});
|
||||
const server = await createMcpServer(store);
|
||||
@ -569,14 +573,17 @@ export type HttpServerHandle = {
|
||||
* Start MCP server over Streamable HTTP (JSON responses, no SSE).
|
||||
* Binds to localhost only. Returns a handle for shutdown and port discovery.
|
||||
*/
|
||||
export async function startMcpHttpServer(port: number, options?: { quiet?: boolean }): Promise<HttpServerHandle> {
|
||||
export async function startMcpHttpServer(
|
||||
port: number,
|
||||
options: ({ quiet?: boolean } & McpStartupOptions) = {},
|
||||
): Promise<HttpServerHandle> {
|
||||
// See startMcpServer() for the rationale — flip production mode here so the
|
||||
// HTTP transport resolves the real database path, without leaking state into
|
||||
// callers that only import this module for its exports (e.g. tests).
|
||||
enableProductionMode();
|
||||
const configPath = getConfigPath();
|
||||
const store = await createStore({
|
||||
dbPath: getDefaultDbPath(),
|
||||
dbPath: options.dbPath ?? getDefaultDbPath(),
|
||||
...(existsSync(configPath) ? { configPath } : {}),
|
||||
});
|
||||
|
||||
|
||||
@ -1403,13 +1403,17 @@ describe("mcp http daemon", () => {
|
||||
}
|
||||
|
||||
/** Spawn a foreground HTTP server (non-blocking) and return the process */
|
||||
function spawnHttpServer(port: number): import("child_process").ChildProcess {
|
||||
const proc = spawn(tsxBin, [qmdScript, "mcp", "--http", "--port", String(port)], {
|
||||
function spawnHttpServer(
|
||||
port: number,
|
||||
options: { args?: string[]; env?: Record<string, string> } = {},
|
||||
): import("child_process").ChildProcess {
|
||||
const proc = spawn(tsxBin, [qmdScript, ...(options.args ?? []), "mcp", "--http", "--port", String(port)], {
|
||||
cwd: fixturesDir,
|
||||
env: {
|
||||
...process.env,
|
||||
INDEX_PATH: daemonDbPath,
|
||||
QMD_CONFIG_DIR: daemonConfigDir,
|
||||
...options.env,
|
||||
},
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
@ -1481,11 +1485,62 @@ describe("mcp http daemon", () => {
|
||||
const body = await res.json();
|
||||
expect(body.status).toBe("ok");
|
||||
} finally {
|
||||
const closed = new Promise(r => proc.once("close", r));
|
||||
proc.kill("SIGTERM");
|
||||
await new Promise(r => proc.on("close", r));
|
||||
await closed;
|
||||
}
|
||||
});
|
||||
|
||||
test("foreground HTTP server honors --index when selecting the store", async () => {
|
||||
const customIndex = "mcp-alt-index";
|
||||
const customCacheDir = join(daemonTestDir, `cache-index-${Date.now()}-${Math.random().toString(16).slice(2)}`);
|
||||
const customConfigDir = join(daemonTestDir, `config-index-${Date.now()}-${Math.random().toString(16).slice(2)}`);
|
||||
await mkdir(customCacheDir, { recursive: true });
|
||||
await mkdir(customConfigDir, { recursive: true });
|
||||
|
||||
const addResult = await runQmd(
|
||||
["--index", customIndex, "collection", "add", fixturesDir, "--name", "mcp-fixtures"],
|
||||
{
|
||||
dbPath: daemonDbPath,
|
||||
configDir: customConfigDir,
|
||||
env: {
|
||||
INDEX_PATH: "",
|
||||
XDG_CACHE_HOME: customCacheDir,
|
||||
},
|
||||
},
|
||||
);
|
||||
expect(addResult.exitCode).toBe(0);
|
||||
|
||||
const port = randomPort();
|
||||
const proc = spawnHttpServer(port, {
|
||||
args: ["--index", customIndex],
|
||||
env: {
|
||||
INDEX_PATH: "",
|
||||
XDG_CACHE_HOME: customCacheDir,
|
||||
QMD_CONFIG_DIR: customConfigDir,
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
const ready = await waitForServer(port);
|
||||
expect(ready).toBe(true);
|
||||
|
||||
const res = await fetch(`http://localhost:${port}/query`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ searches: [{ type: "lex", query: "authentication" }], limit: 5 }),
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
const body = await res.json();
|
||||
const files = body.results.map((r: { file: string }) => r.file);
|
||||
expect(files.some((file: string) => file.includes("mcp-fixtures/notes/meeting.md"))).toBe(true);
|
||||
} finally {
|
||||
const closed = new Promise(r => proc.once("close", r));
|
||||
proc.kill("SIGTERM");
|
||||
await closed;
|
||||
}
|
||||
}, 10000);
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Daemon lifecycle
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
Loading…
Reference in New Issue
Block a user