Skip to content

fix: remove overly broad endsWith guard that double-starts MCP server#137

Open
SyntaxSmith wants to merge 3 commits intosteipete:mainfrom
SyntaxSmith:fix/mcp-double-start
Open

fix: remove overly broad endsWith guard that double-starts MCP server#137
SyntaxSmith wants to merge 3 commits intosteipete:mainfrom
SyntaxSmith:fix/mcp-double-start

Conversation

@SyntaxSmith
Copy link
Copy Markdown

Summary

  • When oracle-mcp is invoked via nvm/npm bin symlink, process.argv[1] resolves to a path like .../bin/oracle-mcp which matches the endsWith("oracle-mcp") guard in server.ts:43
  • This triggers startMcpServer() twice — once from bin/oracle-mcp.ts and once from the module-level guard — creating two MCP server instances on the same stdin/stdout
  • Every JSON-RPC request gets two identical responses, violating the protocol and causing MCP clients (Claude Code, etc.) to immediately disconnect
  • Fix: remove the endsWith("oracle-mcp") fallback. The import.meta.url check alone covers the standalone-script case, and bin/oracle-cli.ts (lines 9–12) already handles npx @steipete/oracle oracle-mcp

Test plan

  • echo '<init>' | oracle-mcp returns 1 response line (was 2 before fix)
  • echo '<init>' | oracle oracle-mcp still returns 1 response line (unaffected)
  • pnpm test:mcp:unit — 5 files, 7 tests passed

🤖 Generated with Claude Code

When `oracle-mcp` is invoked via an nvm/npm bin symlink, `process.argv[1]`
resolves to a path like `.../bin/oracle-mcp` which matches the
`endsWith("oracle-mcp")` guard in server.ts. This causes `startMcpServer()`
to be called twice — once by `bin/oracle-mcp.ts` and once by the guard —
resulting in two MCP server instances sharing stdin/stdout. Every JSON-RPC
request gets two responses, which violates the protocol and causes Claude
Code (and other MCP clients) to immediately disconnect.

The `endsWith` fallback was originally added for `npx @steipete/oracle
oracle-mcp`, but that path is already handled by `bin/oracle-cli.ts`
(lines 9-12) which checks `process.argv[2] === "oracle-mcp"` and calls
`startMcpServer()` directly. The `import.meta.url` check alone is
sufficient for the case when `server.ts` is run as a standalone script.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 16, 2026 01:52
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a double-start condition for the oracle-mcp MCP stdio server when invoked via npm/nvm symlinks, which previously resulted in duplicate JSON-RPC responses and client disconnects.

Changes:

  • Removed the process.argv[1]?.endsWith("oracle-mcp") fallback that could trigger startMcpServer() from module scope even when imported by a bin entrypoint.
  • Kept the “run when executed directly” guard based solely on the import.meta.url check.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/mcp/server.ts
}

if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith("oracle-mcp")) {
if (import.meta.url === `file://${process.argv[1]}`) {
SyntaxSmith and others added 2 commits April 16, 2026 10:06
Replace the single-flight busy guard in `oracle serve` with a FIFO queue
(max 8 pending). When a run arrives while another is active, the server
now holds the HTTP connection open (200 + ndjson stream) and sends a
"Queued at position N" log event. When the active run finishes, the next
queued request is woken and executed immediately.

This lets multiple Claude Code sessions (or other MCP clients) share one
`oracle serve` instance without getting spurious "busy" errors that
previously required manual retry or complex polling workarounds.

Queue overflow (>8 pending) returns 429 with `queue_full` instead of 409.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add `mcp` section to `~/.oracle/config.json` that lets operators
constrain what callers can pass to the `consult` MCP tool:

- `allowedThinkingTimes`: reject or auto-correct disallowed
  browserThinkingTime values (e.g., block "heavy" when ChatGPT UI
  only shows Standard/Extended)
- `thinkingTimeFallback`: value to substitute when a disallowed
  thinking time is passed (instead of hard-rejecting)
- `defaultModel`: model to use when caller omits it
- `maxFiles`: cap on attached files to prevent upload issues
- `toolHint`: free-text string prepended to the consult tool
  description so LLM callers always see operator constraints

Policy is enforced server-side in consult.ts before any session is
created, so even callers that skip reading skill documentation get
hard guardrails.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants