import { describe, expect, it, vi } from 'vitest'; import { parseGenerateFlags } from '../src/cli/generate/flags.js'; import { inferNameFromCommand } from '../src/cli/generate/name-utils.js'; import { normalizeDefinition } from '../src/cli/generate/definition.js'; import { buildGenerateCliCommand } from '../src/cli/generate/template-data.js'; import { printGenerateCliHelp } from '../src/cli/generate-cli-runner.js'; import { serializeDefinition } from '../src/cli-metadata.js'; import type { SerializedServerDefinition } from '../src/cli-metadata.js'; describe('generate-cli runner internals', () => { it('parses generate-cli flags including bundle/compile toggles', () => { const args = [ '--server', 'linear', '--command', 'https://example.com/mcp.getComponents()', '--bundle', '--compile', '--minify', ]; const parsed = parseGenerateFlags([...args]); expect(parsed.server).toBe('linear'); expect(parsed.command).toBe('https://example.com/mcp'); expect(parsed.bundle).toBe(true); expect(parsed.compile).toBe(true); expect(parsed.minify).toBe(true); }); it('documents timeout and minify flags in generate-cli help', () => { const spy = vi.spyOn(console, 'error').mockImplementation(() => {}); let output = ''; try { printGenerateCliHelp(); output = String(spy.mock.calls[0]?.[0] ?? ''); } finally { spy.mockRestore(); } expect(output).toContain('--timeout '); expect(output).toContain('--minify / --no-minify'); }); it('normalizes inferred names from URLs', () => { const args = ['--command', 'https://api.linear.app/mcp.getComponents']; const parsed = parseGenerateFlags([...args]); expect(parsed.command).toContain('https://'); const inferred = inferNameFromCommand(parsed.command ?? ''); expect(inferred).toBe('linear'); }); it('splits stdio commands and infers names from args', () => { const args = ['--command', 'npx -y chrome-devtools-mcp@latest']; const parsed = parseGenerateFlags([...args]); expect(parsed.command).toBeDefined(); expect(typeof parsed.command).toBe('object'); const spec = parsed.command as { command: string; args?: string[] }; expect(spec.command).toBe('npx'); expect(spec.args).toEqual(['-y', 'chrome-devtools-mcp@latest']); const inferred = parsed.command !== undefined ? inferNameFromCommand(parsed.command) : undefined; expect(inferred).toContain('chrome-devtools'); }); it('parses local script commands with extra args', () => { const args = ['--command', 'bun run ./servers/local-cli.ts --stdio --name local']; const parsed = parseGenerateFlags([...args]); const spec = parsed.command as { command: string; args?: string[] }; expect(spec.command).toBe('bun'); expect(spec.args).toEqual(['run', './servers/local-cli.ts', '--stdio', '--name', 'local']); const inferred = parsed.command !== undefined ? inferNameFromCommand(parsed.command) : undefined; expect(inferred).toBe('local-cli'); }); it('infers package names from scoped arguments', () => { const args = ['--command', 'npx -y @demo/tools@latest serve']; const parsed = parseGenerateFlags([...args]); const spec = parsed.command as { command: string; args?: string[] }; expect(spec.args).toEqual(['-y', '@demo/tools@latest', 'serve']); const inferred = parsed.command !== undefined ? inferNameFromCommand(parsed.command) : undefined; expect(inferred).toBe('demo-tools'); }); it('infers npm package names without version specifiers in inline commands', () => { const args = ['--command', 'npx -y chrome-devtools-mcp']; const parsed = parseGenerateFlags([...args]); const spec = parsed.command as { command: string; args?: string[] }; expect(spec.args).toEqual(['-y', 'chrome-devtools-mcp']); const inferred = parsed.command !== undefined ? inferNameFromCommand(parsed.command) : undefined; expect(inferred).toBe('chrome-devtools-mcp'); }); it('normalizes scheme-less HTTP selectors passed to --command', () => { const args = ['--command', 'shadcn.io/api/mcp.getComponents']; const parsed = parseGenerateFlags([...args]); expect(typeof parsed.command).toBe('string'); expect((parsed.command as string).startsWith('https://')).toBe(true); const inferred = parsed.command !== undefined ? inferNameFromCommand(parsed.command) : undefined; expect(inferred).toBe('shadcn'); }); it('preserves HTTP fetch compatibility metadata in generated CLI definitions', () => { const definition = normalizeDefinition({ name: 'sunsama', command: 'https://api.sunsama.com/mcp', httpFetch: 'node-http1', }); expect(definition.httpFetch).toBe('node-http1'); expect(serializeDefinition(definition).httpFetch).toBe('node-http1'); }); it('preserves refreshable bearer metadata in generated CLI definitions', () => { const definition = normalizeDefinition({ name: 'stdio-refresh', command: 'node', args: ['server.js'], auth: 'refreshable_bearer', refresh: { token_endpoint: 'https://auth.example.com/token', access_token_env: 'EXAMPLE_ACCESS_TOKEN', }, }); expect(definition.auth).toBe('refreshable_bearer'); expect(definition.refresh).toEqual({ tokenEndpoint: 'https://auth.example.com/token', accessTokenEnv: 'EXAMPLE_ACCESS_TOKEN', }); expect(serializeDefinition(definition).refresh).toEqual(definition.refresh); }); it('wraps single-token stdio commands when passed via --command', () => { const args = ['--command', './scripts/mcp-server.ts']; const parsed = parseGenerateFlags([...args]); expect(parsed.command).toBeDefined(); const spec = parsed.command as { command: string; args?: string[] }; expect(spec).toEqual({ command: './scripts/mcp-server.ts' }); const inferred = parsed.command !== undefined ? inferNameFromCommand(parsed.command) : undefined; expect(inferred).toBe('mcp-server'); }); it('treats positional inline commands as generate-cli targets', () => { const args = ['npx -y chrome-devtools-mcp@latest']; const parsed = parseGenerateFlags([...args]); expect(parsed.command).toBeDefined(); expect(parsed.server).toBeUndefined(); const spec = parsed.command as { command: string; args?: string[] }; expect(spec.command).toBe('npx'); expect(spec.args).toEqual(['-y', 'chrome-devtools-mcp@latest']); }); it('keeps bare names positional when no whitespace is present', () => { const args = ['linear']; const parsed = parseGenerateFlags([...args]); expect(parsed.server).toBe('linear'); expect(parsed.command).toBeUndefined(); }); it('handles inline commands with extra interior whitespace', () => { const args = [' bun run ./cli.ts --stdio ']; const parsed = parseGenerateFlags([...args]); const spec = parsed.command as { command: string; args?: string[] }; expect(spec.command).toBe('bun'); expect(spec.args).toEqual(['run', './cli.ts', '--stdio']); }); it('treats positional HTTPS URLs as ad-hoc servers and infers names', () => { const args = ['https://mcp.context7.com/mcp']; const parsed = parseGenerateFlags([...args]); expect(parsed.command).toBe('https://mcp.context7.com/mcp'); expect(parsed.server).toBeUndefined(); const inferred = parsed.command !== undefined ? inferNameFromCommand(parsed.command) : undefined; expect(inferred).toBe('context7'); }); it('builds regenerate commands honoring global flags and invocation overrides', () => { const definition: SerializedServerDefinition = { name: 'demo', description: 'Demo server', command: { kind: 'http', url: 'https://demo.mcp' }, }; const invocation = { outputPath: 'out.ts', runtime: 'bun' as const, timeoutMs: 45_000, minify: true, bundle: 'out.bundle.js', includeTools: ['alpha', 'beta'], }; const command = buildGenerateCliCommand(invocation, definition, { '--config': '/tmp/mcporter.json' }); expect(command).toContain('--config /tmp/mcporter.json'); expect(command).toContain('--server demo'); expect(command).toContain('--bundle out.bundle.js'); expect(command).toContain('--timeout 45000'); expect(command).toContain('--minify'); expect(command).toContain("--include-tools 'alpha,beta'"); }); it('parses include/exclude tool lists', () => { const args = ['--include-tools', 'a,b', '--include-tools', 'b,c', '--exclude-tools', 'x, y']; const parsed = parseGenerateFlags([...args]); expect(parsed.includeTools).toEqual(['a', 'b', 'c']); expect(parsed.excludeTools).toEqual(['x', 'y']); }); });