diff --git a/src/cli/qmd.ts b/src/cli/qmd.ts index 50ae7648..82f475fe 100755 --- a/src/cli/qmd.ts +++ b/src/cli/qmd.ts @@ -2519,6 +2519,7 @@ function parseCLI() { http: { type: "boolean" }, daemon: { type: "boolean" }, port: { type: "string" }, + host: { type: "string" }, }, allowPositionals: true, strict: false, // Allow unknown options to pass through @@ -3216,6 +3217,7 @@ if (isMain) { if (cli.values.http) { const port = Number(cli.values.port) || 8181; + const host = (cli.values.host as string) || undefined; if (cli.values.daemon) { // Guard: check if already running @@ -3234,9 +3236,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 hostArgs = host ? ["--host", host] : []; 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, "mcp", "--http", "--port", String(port), ...hostArgs] + : [selfPath, "mcp", "--http", "--port", String(port), ...hostArgs]; const child = nodeSpawn(process.execPath, spawnArgs, { stdio: ["ignore", logFd, logFd], detached: true, @@ -3256,7 +3259,7 @@ if (isMain) { process.removeAllListeners("SIGINT"); const { startMcpHttpServer } = await import("../mcp/server.js"); try { - await startMcpHttpServer(port); + await startMcpHttpServer(port, { host }); } catch (e: any) { if (e?.code === "EADDRINUSE") { console.error(`Port ${port} already in use. Try a different port with --port.`); diff --git a/src/db.ts b/src/db.ts index 5fe7ab47..3e72de98 100644 --- a/src/db.ts +++ b/src/db.ts @@ -68,6 +68,7 @@ export function openDatabase(path: string): Database { export interface Database { exec(sql: string): void; prepare(sql: string): Statement; + transaction any>(fn: T): T; loadExtension(path: string): void; close(): void; } diff --git a/src/mcp/server.ts b/src/mcp/server.ts index 0cfb6070..9b9b3446 100644 --- a/src/mcp/server.ts +++ b/src/mcp/server.ts @@ -563,9 +563,12 @@ 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. + * Binds to the address specified by `host` (default: localhost). + * Set `host` to `"0.0.0.0"` to accept connections from other hosts + * (required for Kubernetes / container deployments). + * The `QMD_HOST` environment variable overrides the default. */ -export async function startMcpHttpServer(port: number, options?: { quiet?: boolean }): Promise { +export async function startMcpHttpServer(port: number, options?: { quiet?: boolean; host?: string }): Promise { const configPath = getConfigPath(); const store = await createStore({ dbPath: getDefaultDbPath(), @@ -796,9 +799,10 @@ export async function startMcpHttpServer(port: number, options?: { quiet?: boole } }); + const bindHost = options?.host ?? process.env.QMD_HOST ?? "localhost"; await new Promise((resolve, reject) => { httpServer.on("error", reject); - httpServer.listen(port, "localhost", () => resolve()); + httpServer.listen(port, bindHost, () => resolve()); }); const actualPort = (httpServer.address() as import("net").AddressInfo).port; @@ -826,7 +830,7 @@ export async function startMcpHttpServer(port: number, options?: { quiet?: boole process.exit(0); }); - log(`QMD MCP server listening on http://localhost:${actualPort}/mcp`); + log(`QMD MCP server listening on http://${bindHost}:${actualPort}/mcp`); return { httpServer, port: actualPort, stop }; }