[Med] fix(openapi): add shebang and chmod +x to gen-mcp generated index.js#715
[Med] fix(openapi): add shebang and chmod +x to gen-mcp generated index.js#715katnisscalls99 wants to merge 1 commit into
Conversation
generateMcpServer() emits a package.json with a bin entry pointing to
index.js, but the generated index.js starts with a comment line — no
shebang. When the user runs 'npm i -g @generated/foo-mcp' and then
invokes 'foo-mcp' on the shell, the OS tries to exec index.js as a
native script. Without #!/usr/bin/env node it sees 'import' as the first
token and fails with ENOEXEC on Linux or a cryptic syntax error on macOS,
neither of which suggests the real problem.
Two fixes:
1. Prepend '#!/usr/bin/env node' to the generated index template so the
file is self-describing as a Node.js script.
2. chmod(dest, 0o755) the emitted index.js at write time (only that file,
not package.json) so 'npm link' / global install symlinks can exec it.
Uses .catch(() => {}) so the write step doesn't fail on Windows or
read-only filesystems where the mode change is irrelevant.
The bug affects every MCP server generated by gen-mcp when installed
globally, which is the documented primary use case per the emitted README.
Greptile SummaryThis PR fixes two related gaps in the MCP server code generator: the generated
Confidence Score: 4/5Safe to merge; the two changes are narrowly scoped to the MCP generator and fix a real usability gap for global installs. Both fixes are correct and solve the documented bug. The only notable rough edge is that packages/openapi/src/gen-mcp/index.ts — the chmod error handling and the duplicated entry-point path literal are both worth a second look. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[generateMcpServer called] --> B[render IR → GeneratedFile list]
B --> C{for each file}
C --> D[mkdir outDir/dirname]
D --> E[writeFile dest contents]
E --> F{f.path === 'index.js'?}
F -- Yes --> G[chmod dest 0o755]
G --> H{chmod error?}
H -- ENOSYS / EPERM / ENOTSUP --> I[swallow silently]
H -- unexpected error --> I
H -- success --> C
I --> C
F -- No --> C
C -- done --> J[return GeneratedFile list]
|
| if (f.path === 'index.js') { | ||
| await chmod(dest, 0o755).catch(() => {}); | ||
| } |
There was a problem hiding this comment.
The
.catch(() => {}) swallows every possible chmod error, not only the platform-specific ones that are expected (e.g. ENOSYS on Windows, EPERM on a read-only volume). If chmod fails for an unexpected reason after a successful writeFile — for instance a race with another process renaming the file — the generator returns silently, the file is left non-executable, and the caller has no idea. Filtering to known "not applicable" codes preserves the intent while letting real errors bubble.
| if (f.path === 'index.js') { | |
| await chmod(dest, 0o755).catch(() => {}); | |
| } | |
| if (f.path === 'index.js') { | |
| await chmod(dest, 0o755).catch((e: NodeJS.ErrnoException) => { | |
| // chmod is a no-op on Windows (ENOSYS) and may be refused on | |
| // read-only volumes (EPERM/ENOTSUP). Swallow only those cases | |
| // so that unexpected errors (e.g. the file was removed) still | |
| // surface to the caller. | |
| if (!['ENOSYS', 'EPERM', 'ENOTSUP'].includes(e.code ?? '')) throw e; | |
| }); | |
| } |
|
🤖 Auto-rebase: The branch was rebased successfully locally but could not be pushed to the fork. Please enable 'Allow edits from maintainers' in the PR settings, or rebase manually: |
8 similar comments
|
🤖 Auto-rebase: The branch was rebased successfully locally but could not be pushed to the fork. Please enable 'Allow edits from maintainers' in the PR settings, or rebase manually: |
|
🤖 Auto-rebase: The branch was rebased successfully locally but could not be pushed to the fork. Please enable 'Allow edits from maintainers' in the PR settings, or rebase manually: |
|
🤖 Auto-rebase: The branch was rebased successfully locally but could not be pushed to the fork. Please enable 'Allow edits from maintainers' in the PR settings, or rebase manually: |
|
🤖 Auto-rebase: The branch was rebased successfully locally but could not be pushed to the fork. Please enable 'Allow edits from maintainers' in the PR settings, or rebase manually: |
|
🤖 Auto-rebase: The branch was rebased successfully locally but could not be pushed to the fork. Please enable 'Allow edits from maintainers' in the PR settings, or rebase manually: |
|
🤖 Auto-rebase: The branch was rebased successfully locally but could not be pushed to the fork. Please enable 'Allow edits from maintainers' in the PR settings, or rebase manually: |
|
🤖 Auto-rebase: The branch was rebased successfully locally but could not be pushed to the fork. Please enable 'Allow edits from maintainers' in the PR settings, or rebase manually: |
|
🤖 Auto-rebase: The branch was rebased successfully locally but could not be pushed to the fork. Please enable 'Allow edits from maintainers' in the PR settings, or rebase manually: |
generateMcpServer() emits a package.json with a bin entry pointing to index.js, but the generated index.js starts with a comment line — no shebang. When the user runs
npm i -g @generated/foo-mcpand then invokesfoo-mcpon the shell, the OS tries to exec index.js as a native script. Without#!/usr/bin/env nodeit seesimportas the first token and fails with ENOEXEC on Linux or a cryptic syntax error on macOS, neither of which suggests the real problem.Two fixes:
Prepend
#!/usr/bin/env nodeto the generated index template so the file is self-describing as a Node.js script.chmod(dest, 0o755)the emitted index.js at write time (only that file, not package.json) sonpm link/ global install symlinks can exec it. Uses.catch(() => {})so the write step doesn't fail on Windows or read-only filesystems where the mode change is irrelevant.The bug affects every MCP server generated by gen-mcp when installed globally, which is the documented primary use case per the emitted README.
Severity: Medium