abhigyanpatwari/GitNexus · error
Refusing to start the MCP HTTP server on a non-loopback host
Error message
Refusing to start the MCP HTTP server on a non-loopback host (${host}) without authentication — it would expose all indexed repos to anyone who can reach it. Pass --auth-token (or set GITNEXUS_MCP_AUTH_TOKEN), or bind --host 127.0.0.1. This applies to --host 0.0.0.0 and --host :: as well. What it means
startMcpHttpServer refuses to bind a non-loopback host unless an auth token is configured — a deliberate security guard so exposing the HTTP MCP server cannot silently publish every indexed repo to anyone on the network. Loopback binds (127.0.0.1, ::1) stay open by default; 0.0.0.0, ::, LAN IPs, and hostnames all require --auth-token / GITNEXUS_MCP_AUTH_TOKEN.
Source
Thrown at gitnexus/src/mcp/http-transport.ts:477
* - POST /mcp — Streamable HTTP (modern clients)
* - GET /sse — legacy SSE stream (old clients)
* - POST /messages — legacy SSE message endpoint
*
* @param backend LocalBackend instance
* @param options Server configuration
* @returns The listening http.Server
*/
export async function startMcpHttpServer(
backend: LocalBackend,
options: McpHttpOptions,
): Promise<HttpServer> {
const { port, host, authToken } = options;
// Refuse to start an unauthenticated server on a non-loopback interface — that
// would silently expose every indexed repo to anyone who can reach the host.
// Loopback binds stay open by default; non-loopback binds require a token.
if (!authToken && !isLoopbackHost(host)) {
throw new Error(
`Refusing to start the MCP HTTP server on a non-loopback host (${host}) without ` +
'authentication — it would expose all indexed repos to anyone who can reach it. ' +
'Pass --auth-token (or set GITNEXUS_MCP_AUTH_TOKEN), or bind --host 127.0.0.1. ' +
'This applies to --host 0.0.0.0 and --host :: as well.',
);
}
const repositoryPolicy = options.repositoryPolicy ?? (await createMcpRepositoryPolicy(backend));
const app: Express = express();
// Suppress X-Powered-By to reduce information leakage.
app.disable('x-powered-by');
// PNA (Chrome 130+ Private Network Access) preflight support.
// The browser sends `Access-Control-Request-Private-Network: true` ONLY on the
// CORS preflight (an OPTIONS request); emit the matching allow header only then,
// never on actual GET/POST responses. Runs before cors() so the header survivesView on GitHub (pinned to aac7515d2a)
Solutions
- Provide a token: start with --auth-token <strong-secret> or export GITNEXUS_MCP_AUTH_TOKEN, and have clients send it per the server's auth scheme.
- Or keep binding loopback: --host 127.0.0.1, and put an authenticating reverse proxy (nginx/Traefik) in front for remote access.
- In Docker, bind 127.0.0.1 inside the container only if you use a proxy sidecar; otherwise pass the token and publish the port deliberately.
- Never widen the bind to work around the error without also adding auth — the guard exists because the endpoint exposes all indexed repos.
Example fix
# before: unauthenticated non-loopback bind (refuses to start) $ gitnexus serve --host 0.0.0.0 --port 4747 # → Refusing to start the MCP HTTP server on a non-loopback host ... # after: authenticated non-loopback bind $ GITNEXUS_MCP_AUTH_TOKEN=$(openssl rand -hex 32) gitnexus serve --host 0.0.0.0 --port 4747 # or stay loopback behind an auth proxy: $ gitnexus serve --host 127.0.0.1 --port 4747
Defensive patterns
Strategy: validation
Validate before calling
// Resolve and validate bind config before starting the server
function resolveMcpBind(opts: { host?: string; authToken?: string }): { host: string; authToken?: string } {
const host = opts.host ?? '127.0.0.1';
const authToken = opts.authToken ?? process.env.GITNEXUS_MCP_AUTH_TOKEN;
const loopback = ['127.0.0.1', '::1', 'localhost'].includes(host);
if (!loopback && !authToken) {
throw new Error('Refusing non-loopback bind without GITNEXUS_MCP_AUTH_TOKEN / --auth-token');
}
return { host, authToken };
} Try / catch
try {
await startMcpHttpServer(backend, options);
} catch (err) {
if (err instanceof Error && err.message.includes('without authentication')) {
// intentional guard: fail deployment loudly, never bind quietly
failDeployment('MCP HTTP requires auth for non-loopback binds');
}
throw err;
} Prevention
- Make GITNEXUS_MCP_AUTH_TOKEN a required env var in any deployment config that binds non-loopback.
- Default to --host 127.0.0.1 and put an authenticating reverse proxy in front for remote access.
- Add a config lint step that rejects 0.0.0.0/:: binds lacking a token.
- Generate tokens with `openssl rand -hex 32`; never reuse dev secrets.
When it happens
Trigger: Starting the MCP HTTP server with --host 0.0.0.0 (common in Docker, where 127.0.0.1 makes the port unreachable from outside the container), --host ::, a LAN IP, or a resolvable hostname, without passing --auth-token or setting GITNEXUS_MCP_AUTH_TOKEN.
Common situations: Dockerizing GitNexus and switching the bind to 0.0.0.0 for port mapping; sharing an MCP instance across a team LAN; copying a loopback dev config into a deployment; Kubernetes sidecar deployments exposing the port.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Path traversal blocked: ${filePath}
- [gitnexus serve] Bound to a wildcard address (${boundHost});
- ${source} entry "${trimmed}" must be an identifier or member
- Refusing to start eval-server on non-loopback host ${host} w
- ${TRUSTED_CACHE_DIRECTORY_ENV} must not traverse symbolic li
AI-assisted analysis of abhigyanpatwari/GitNexus@aac7515d2a (2026-08-20).
Data as JSON: /api/errors/94c6c5637ed61940.
Report an issue: GitHub.