All files / src/channel-gateway main.ts

83.33% Statements 20/24
100% Branches 12/12
50% Functions 3/6
85% Lines 17/20

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67                            7x 7x       7x 947x 7x       7x 7x               7x 6x 6x           7x       7x             7x     7x 7x   7x     7x        
 
/**
 * Entry point for the gitlab-ci channel-gateway (issue #483).
 *
 * Launches the downstream gitlab-mcp (by default `node dist/src/main.js stdio`,
 * inheriting this process's environment so the token / API url / USE_* gates
 * carry through) and serves the channel protocol on stdio. Register it as a
 * channel:
 *
 *   .mcp.json:  { "mcpServers": { "gitlab-ci": {
 *                 "command": "node",
 *                 "args": ["dist/src/channel-gateway/main.js"] } } }
 *   launch:     claude --dangerously-load-development-channels server:gitlab-ci
 */
import { join } from 'node:path';
import { ChannelGateway } from './gateway';
 
/** Drop undefined-valued env entries so the child gets a clean string map. */
function cleanEnv(env: NodeJS.ProcessEnv): Record<string, string> {
  const out: Record<string, string> = {};
  for (const [k, v] of Object.entries(env)) if (typeof v === 'string') out[k] = v;
  return out;
}
 
/** Smallest poll interval we accept; below this a watch would hammer the API. */
const MIN_POLL_MS = 1_000;
const DEFAULT_POLL_MS = 10_000;
 
/**
 * Parse GATEWAY_POLL_MS into a sane interval. An unset / non-numeric / out-of-range
 * value falls back to the default rather than arming a watch with a NaN, zero, or
 * negative interval (which would busy-loop or never fire).
 */
function parsePollMs(raw: string | undefined): number {
  if (raw === undefined) return DEFAULT_POLL_MS;
  const n = Number(raw);
  return Number.isFinite(n) && n >= MIN_POLL_MS ? n : DEFAULT_POLL_MS;
}
 
async function main(): Promise<void> {
  // Resolve the downstream gitlab-mcp relative to this file so the gateway runs
  // from any working directory (a channel is launched with an arbitrary cwd).
  const args = process.env.GATEWAY_DOWNSTREAM_ARGS
    ? process.env.GATEWAY_DOWNSTREAM_ARGS.split(' ').filter(Boolean)
    : [join(__dirname, '..', 'main.js'), 'stdio'];
 
  const gateway = new ChannelGateway({
    downstreamCommand: process.env.GATEWAY_DOWNSTREAM_COMMAND ?? 'node',
    downstreamArgs: args,
    downstreamEnv: cleanEnv(process.env),
    pollMs: parsePollMs(process.env.GATEWAY_POLL_MS),
  });
 
  const stop = (): void => {
    void gateway.stop().finally(() => process.exit(0));
  };
  process.on('SIGINT', stop);
  process.on('SIGTERM', stop);
 
  await gateway.start();
}
 
main().catch((err: unknown) => {
  process.stderr.write(`channel-gateway failed: ${String(err)}\n`);
  process.exit(1);
});