lax-mcp-server / tree / src/cli.ts ยท raw (this view has old GitHub links rewritten for the mirror; raw is byte-exact)
#!/usr/bin/env node
// lax-mcp: stdio by default, --http for Streamable HTTP. Logs go to stderr only; stdout carries MCP frames.
import { realpathSync } from 'node:fs';
import { createServer, type IncomingMessage } from 'node:http';
import { fileURLToPath } from 'node:url';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import { createLaxMcpServer, DEFAULT_BASE_URL, VERSION } from './server.js';

const log = (line: string) => process.stderr.write(`[lax-mcp] ${line}\n`);

export function parseArgs(argv: string[], env: NodeJS.ProcessEnv = process.env) {
  let http = false;
  let port = Number(env.PORT ?? 3333);
  let baseUrl = env.LAX_BASE_URL ?? DEFAULT_BASE_URL;
  for (let i = 0; i < argv.length; i++) {
    const a = argv[i];
    if (a === '--http') http = true;
    else if (a === '--port') port = Number(argv[++i]);
    else if (a.startsWith('--port=')) port = Number(a.slice(7));
    else if (a === '--base-url') baseUrl = argv[++i];
    else if (a === '--help' || a === '-h') return { help: true as const, http, port, baseUrl };
  }
  if (!Number.isInteger(port) || port < 0 || port > 65535) throw new Error('port must be 0 to 65535');
  return { help: false as const, http, port, baseUrl };
}

async function readJson(req: IncomingMessage): Promise<unknown> {
  const chunks: Buffer[] = [];
  let size = 0;
  for await (const c of req) {
    size += (c as Buffer).length;
    if (size > 1_000_000) throw new Error('body too large');
    chunks.push(c as Buffer);
  }
  const raw = Buffer.concat(chunks).toString('utf8');
  return raw ? JSON.parse(raw) : undefined;
}

export function startHttp(port: number, baseUrl: string) {
  // Stateless Streamable HTTP: a fresh server and transport per POST, so nothing is shared between clients.
  const srv = createServer(async (req, res) => {
    const path = (req.url ?? '/').split('?')[0];
    if (path === '/healthz') {
      res.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify({ ok: true, version: VERSION }));
      return;
    }
    if (path !== '/mcp') {
      res.writeHead(404).end();
      return;
    }
    if (req.method !== 'POST') {
      res.writeHead(405, { allow: 'POST' }).end(JSON.stringify({ jsonrpc: '2.0', error: { code: -32000, message: 'Method not allowed' }, id: null }));
      return;
    }
    try {
      const body = await readJson(req);
      const server = createLaxMcpServer({ baseUrl, log });
      const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, enableJsonResponse: true });
      res.on('close', () => {
        void transport.close();
        void server.close();
      });
      await server.connect(transport);
      await transport.handleRequest(req, res, body);
    } catch {
      log('http request failed');
      if (!res.headersSent) res.writeHead(400, { 'content-type': 'application/json' }).end(JSON.stringify({ jsonrpc: '2.0', error: { code: -32700, message: 'Bad request' }, id: null }));
    }
  });
  return new Promise<typeof srv>((resolve) => srv.listen(port, () => resolve(srv)));
}

async function main() {
  const opts = parseArgs(process.argv.slice(2));
  if (opts.help) {
    process.stderr.write('Usage: lax-mcp [--http] [--port N] [--base-url URL]\n  stdio by default; --http serves Streamable HTTP on /mcp (port from --port, PORT or 3333).\n');
    return;
  }
  if (opts.http) {
    const srv = await startHttp(opts.port, opts.baseUrl);
    const addr = srv.address();
    log(`Streamable HTTP on http://localhost:${typeof addr === 'object' && addr ? addr.port : opts.port}/mcp`);
  } else {
    const server = createLaxMcpServer({ baseUrl: opts.baseUrl, log });
    await server.connect(new StdioServerTransport());
    log('ready on stdio');
  }
}

function isMain(): boolean {
  if (!process.argv[1]) return false;
  try {
    return realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
  } catch {
    return false;
  }
}
if (isMain()) main().catch((e) => {
  log(`fatal: ${e instanceof Error ? e.message : String(e)}`);
  process.exit(1);
});

agentgate-source: code mirror on Cloudflare Pages. All repos