Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
66c002b
feat(mcp): add `gitnexus mcp --http` server with legacy SSE and Strea…
blueroseslol Jun 10, 2026
f398fc3
fix(mcp): update i18n description for mcp --http command
blueroseslol Jun 10, 2026
eac71fc
Merge branch 'main' into feat/mcp-http-sse-transport
magyargergo Jun 11, 2026
66c2f5e
Merge branch 'main' into feat/mcp-http-sse-transport
magyargergo Jun 12, 2026
3fe691b
fix(mcp-http): address all reviewer issues from PR #2141 review
blueroseslol Jun 13, 2026
be223be
refactor(mcp-http): fix layer inversion — move session factories into…
blueroseslol Jun 13, 2026
1c59d5a
Merge branch 'main' into feat/mcp-http-sse-transport
magyargergo Jun 13, 2026
60c9792
Merge branch 'main' into feat/mcp-http-sse-transport
magyargergo Jun 13, 2026
e3595d4
fix(mcp-http): close orphaned MCP Server when initialize is rejected …
magyargergo Jun 13, 2026
48211e3
fix(mcp-http): cap legacy SSE sessions at MAX_SESSIONS
magyargergo Jun 13, 2026
44abe2a
fix(mcp-http): return a JSON-RPC envelope for malformed/oversized JSON
magyargergo Jun 13, 2026
973037b
fix(mcp-http): accept IPv6 [::1] and the 127/8 block in the CORS loop…
magyargergo Jun 13, 2026
44dadca
fix(mcp-http): correct the PNA preflight header value and gate it on …
magyargergo Jun 13, 2026
5e00b24
fix(mcp-http): enable SDK DNS-rebinding protection for known bind hosts
magyargergo Jun 13, 2026
97a3ab2
fix(mcp-http): use POSIX shutdown exit codes (SIGINT 130, SIGTERM 143)
magyargergo Jun 13, 2026
303fdfa
feat(mcp-http): accept the auth token from GITNEXUS_MCP_AUTH_TOKEN
magyargergo Jun 13, 2026
2406998
fix(mcp-http): refuse to start on a non-loopback bind without a token
magyargergo Jun 13, 2026
aafa6f0
refactor(mcp-http): gate new sessions with the SDK isInitializeRequest
magyargergo Jun 13, 2026
f36ef48
refactor(mcp-http): drop the unreachable post-`has` null check
magyargergo Jun 13, 2026
17df715
refactor(mcp-http): extract a shared idle-session sweep helper
magyargergo Jun 13, 2026
c76f3b4
chore(autofix): apply prettier + eslint fixes via /autofix command
github-actions[bot] Jun 13, 2026
aafe22e
Merge branch 'main' into feat/mcp-http-sse-transport
magyargergo Jun 13, 2026
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
4 changes: 4 additions & 0 deletions gitnexus/src/cli/help-i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ const OPTION_DESCRIPTION_KEYS = {
'analyze|--embedding-device <device>': 'help.option.analyze.embeddingDevice',
'index|-f, --force': 'help.option.index.force',
'index|--allow-non-git': 'help.option.index.allowNonGit',
'mcp|--http': 'help.option.mcp.http',
'mcp|-p, --port <port>': 'help.option.port',
'mcp|--host <host>': 'help.option.mcp.host',
'mcp|--auth-token <token>': 'help.option.mcp.authToken',
'serve|-p, --port <port>': 'help.option.port',
'serve|--host <host>': 'help.option.serve.host',
'uninstall|-f, --force': 'help.option.uninstall.force',
Expand Down
8 changes: 7 additions & 1 deletion gitnexus/src/cli/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ export const en = {
'help.command.index.description':
'Register an existing .gitnexus/ folder into the global registry (no re-analysis needed)',
'help.command.serve.description': 'Start local HTTP server for web UI connection',
'help.command.mcp.description': 'Start MCP server (stdio) — serves all indexed repos',
'help.command.mcp.description':
'Start MCP server. Default: stdio. Use --http for a remote HTTP server (Streamable HTTP at POST /mcp + legacy SSE at GET /sse, POST /messages).',
'help.command.list.description': 'List all indexed repositories',
'help.command.status.description': 'Show index status for current repo',
'help.command.doctor.description':
Expand Down Expand Up @@ -199,6 +200,11 @@ export const en = {
'help.option.index.allowNonGit': 'Allow registering folders that are not Git repositories',
'help.option.port': 'Port number',
'help.option.serve.host': 'Bind address (default: 127.0.0.1, use 0.0.0.0 for remote access)',
'help.option.mcp.http': 'Serve MCP over HTTP instead of stdio (for remote clients)',
'help.option.mcp.host':
'HTTP bind address (only with --http). Default: 127.0.0.1 (loopback). Use 0.0.0.0 to expose to all interfaces.',
'help.option.mcp.authToken':
'Require this bearer token in the Authorization header (only with --http); may also be set via the GITNEXUS_MCP_AUTH_TOKEN env var. Required for a non-loopback bind (--host 0.0.0.0/::), which otherwise refuses to start.',
'help.option.force.confirmation': 'Skip confirmation prompt',
'help.option.uninstall.force': 'Apply the changes (default is a dry-run preview)',
'help.option.clean.all': 'Clean all indexed repos',
Expand Down
8 changes: 7 additions & 1 deletion gitnexus/src/cli/i18n/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,8 @@ export const zhCN = {
'help.command.analyze.description': '索引仓库(完整分析)',
'help.command.index.description': '将现有 .gitnexus/ 文件夹注册到全局注册表(无需重新分析)',
'help.command.serve.description': '启动供 Web UI 连接的本地 HTTP 服务器',
'help.command.mcp.description': '启动 MCP 服务器(stdio)— 提供所有已索引仓库',
'help.command.mcp.description':
'启动 MCP 服务器。默认为 stdio。使用 --http 启动远程 HTTP 服务器(Streamable HTTP: POST /mcp + 遗留 SSE: GET /sse, POST /messages)。',
'help.command.list.description': '列出所有已索引仓库',
'help.command.status.description': '显示当前仓库的索引状态',
'help.command.doctor.description': '显示运行平台能力和嵌入配置',
Expand Down Expand Up @@ -187,6 +188,11 @@ export const zhCN = {
'help.option.index.allowNonGit': '允许注册非 Git 仓库文件夹',
'help.option.port': '端口号',
'help.option.serve.host': '绑定地址(默认:127.0.0.1;远程访问可用 0.0.0.0)',
'help.option.mcp.http': '使用 HTTP 代替 stdio 提供 MCP 服务(适合远程客户端)',
'help.option.mcp.host':
'HTTP 绑定地址(仅与 --http 搭配使用)。默认:127.0.0.1(回环)。使用 0.0.0.0 向所有接口开放。',
'help.option.mcp.authToken':
'要求 Authorization 头携带此 Bearer Token(仅与 --http 搭配使用);也可通过 GITNEXUS_MCP_AUTH_TOKEN 环境变量设置。非回环绑定(--host 0.0.0.0/::)时必填,否则拒绝启动。',
'help.option.force.confirmation': '跳过确认提示',
'help.option.uninstall.force': '应用更改(默认仅为预演预览)',
'help.option.clean.all': '清理所有已索引仓库',
Expand Down
16 changes: 15 additions & 1 deletion gitnexus/src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,21 @@ program

program
.command('mcp')
.description('Start MCP server (stdio) — serves all indexed repos')
.description(
'Start MCP server. Default: stdio. Use --http for a remote HTTP server ' +
'(Streamable HTTP at POST /mcp + legacy SSE at GET /sse, POST /messages).',
)
.option('--http', 'Serve MCP over HTTP instead of stdio (for remote clients)')
.option('-p, --port <port>', 'HTTP port (only with --http). Default: 3000', '3000')
Comment thread
magyargergo marked this conversation as resolved.
.option(
'--host <host>',
'HTTP bind address (only with --http). Default: 127.0.0.1 (loopback). Use 0.0.0.0 to expose to all interfaces.',
'127.0.0.1',
)
.option(
'--auth-token <token>',
'Require this bearer token in the Authorization header (only with --http); may also be set via the GITNEXUS_MCP_AUTH_TOKEN env var. Required for a non-loopback bind (--host 0.0.0.0/::), which otherwise refuses to start.',
)
.action(createLbugLazyAction(() => import('./mcp.js'), 'mcpCommand'));

program
Expand Down
38 changes: 37 additions & 1 deletion gitnexus/src/cli/mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,12 @@

import { installGlobalStdoutSentinel } from '../mcp/stdio-context.js';

export const mcpCommand = async () => {
export const mcpCommand = async (options?: {
http?: boolean;
port?: string;
host?: string;
authToken?: string;
}) => {
// Install the global stdout sentinel as the very first thing — before
// ANY other module loads. The static-import closure above is leaf-only
// (stdio-context → stdio-capture, zero non-`node:` deps), so this is
Expand Down Expand Up @@ -80,6 +85,37 @@ export const mcpCommand = async () => {
);
}

// Start HTTP server or fall back to stdio (default).
if (options?.http) {
// Dynamically import the HTTP transport module AFTER the sentinel installs.
// http-transport.ts pulls in express/cors/MCP SDK HTTP transport; these must
// not load before installGlobalStdoutSentinel() runs (see module doc above).
const port = Number(options.port ?? 3000);
if (!Number.isInteger(port) || port < 1 || port > 65535) {
logger.error(
{ port: options.port },
`Invalid --port value: "${options.port ?? ''}". Must be an integer between 1 and 65535.`,
);
process.exit(1);
}
// Dynamic import keeps express/cors out of mcp.ts's static graph (stdio sentinel).
const { startMcpHttpServer, resolveAuthToken } = await import('../mcp/http-transport.js');
try {
await startMcpHttpServer(backend, {
port,
host: options.host ?? '127.0.0.1',
authToken: resolveAuthToken(options.authToken, process.env),
});
} catch (err) {
logger.error(
{ err: err instanceof Error ? err.message : err },
'Failed to start the MCP HTTP server',
);
process.exit(1);
}
return;
}

// Start MCP server (serves all repos, discovers new ones lazily)
await startMCPServer(backend);
};
Loading
Loading