Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions src/mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -560,9 +560,11 @@ 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 localhost by default. Use `options.host` to override (e.g. "0.0.0.0"
* to allow access from Docker containers or other network hosts).
* 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; host?: string }): Promise<HttpServerHandle> {
const store = createStore();

// Session map: each client gets its own McpServer + Transport pair (MCP spec requirement).
Expand Down Expand Up @@ -787,7 +789,8 @@ export async function startMcpHttpServer(port: number, options?: { quiet?: boole

await new Promise<void>((resolve, reject) => {
httpServer.on("error", reject);
httpServer.listen(port, "localhost", () => resolve());
const bindHost = options?.host ?? "localhost";
httpServer.listen(port, bindHost, () => resolve());
});

const actualPort = (httpServer.address() as import("net").AddressInfo).port;
Expand Down Expand Up @@ -816,7 +819,8 @@ export async function startMcpHttpServer(port: number, options?: { quiet?: boole
process.exit(0);
});

log(`QMD MCP server listening on http://localhost:${actualPort}/mcp`);
const displayHost = options?.host ?? "localhost";
log(`QMD MCP server listening on http://${displayHost}:${actualPort}/mcp`);
return { httpServer, port: actualPort, stop };
}

Expand Down
12 changes: 8 additions & 4 deletions src/qmd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2395,6 +2395,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
Expand Down Expand Up @@ -2474,6 +2475,7 @@ function showHelp(): void {
console.log(" qmd get <file>[:line] [-l N] - Show a single document, optional line slice");
console.log(" qmd multi-get <pattern> - Batch fetch via glob or comma-separated list");
console.log(" qmd mcp - Start the MCP server (stdio transport for AI agents)");
console.log(" qmd mcp --http [--port N] [--host H] - HTTP transport (default: localhost:8181)");
console.log("");
console.log("Collections & context:");
console.log(" qmd collection add/list/remove/rename/show - Manage indexed folders");
Expand Down Expand Up @@ -2927,6 +2929,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
Expand All @@ -2945,9 +2948,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,
Expand All @@ -2956,7 +2960,7 @@ if (isMain) {
closeSync(logFd); // parent's copy; child inherited the fd

writeFileSync(pidPath, String(child.pid));
console.log(`Started on http://localhost:${port}/mcp (PID ${child.pid})`);
console.log(`Started on http://${host ?? "localhost"}:${port}/mcp (PID ${child.pid})`);
console.log(`Logs: ${logPath}`);
process.exit(0);
}
Expand All @@ -2967,7 +2971,7 @@ if (isMain) {
process.removeAllListeners("SIGINT");
const { startMcpHttpServer } = await import("./mcp.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.`);
Expand Down