diff --git a/packages/openapi/src/gen-mcp/index.ts b/packages/openapi/src/gen-mcp/index.ts index fcfe3308..69014858 100644 --- a/packages/openapi/src/gen-mcp/index.ts +++ b/packages/openapi/src/gen-mcp/index.ts @@ -1,4 +1,4 @@ -import { mkdir, writeFile } from 'node:fs/promises'; +import { mkdir, writeFile, chmod } from 'node:fs/promises'; import { join } from 'node:path'; import type { ApiIR, Operation, Parameter } from '../core/types.js'; @@ -23,7 +23,15 @@ export async function generateMcpServer(ir: ApiIR, opts: GenerateMcpOptions): Pr await mkdir(opts.outDir, { recursive: true }); for (const f of files) { await mkdir(join(opts.outDir, dirOf(f.path)), { recursive: true }); - await writeFile(join(opts.outDir, f.path), f.contents, 'utf8'); + const dest = join(opts.outDir, f.path); + await writeFile(dest, f.contents, 'utf8'); + // Make the bin entry executable so `npm i -g` installs a runnable + // script. Without 0755, the OS refuses to exec it and the user sees + // a confusing EACCES or "import statement" shell error instead of a + // running MCP server. Only needed for the entry-point declared in bin. + if (f.path === 'index.js') { + await chmod(dest, 0o755).catch(() => {}); + } } return files; } @@ -35,7 +43,8 @@ function render(ir: ApiIR, opts: GenerateMcpOptions): GeneratedFile[] { const tools = ir.operations.map(toolDescriptor).join(',\n'); const handlers = ir.operations.map(toolHandler).join('\n\n'); - const index = `// AUTO-GENERATED by @profullstack/sh1pt-openapi/gen-mcp. + const index = `#!/usr/bin/env node +// AUTO-GENERATED by @profullstack/sh1pt-openapi/gen-mcp. // Source: ${ir.title} v${ir.version}. Do not edit by hand. // // Stdio MCP server: each upstream API operation is exposed as one tool.