Skip to content

fix(codex): keep direct MCP tools visible for routed models - #1529

Merged
lidge-jun merged 1 commit into
devfrom
agent/fix-1522-routed-direct-mcp
Aug 12, 2026
Merged

fix(codex): keep direct MCP tools visible for routed models#1529
lidge-jun merged 1 commit into
devfrom
agent/fix-1522-routed-direct-mcp

Conversation

@Ingwannu

@Ingwannu Ingwannu commented Aug 12, 2026

Copy link
Copy Markdown
Owner

Summary

  • default routed Codex catalog rows to supports_search_tool: false so Codex App keeps compatible direct MCP/plugin tools visible
  • preserve the independent sidecar-backed web_search_tool_type: "text_and_image" capability for non-Cursor routes
  • keep the runtime tool_search parser and bridge intact for clients that explicitly send that wire shape
  • document why deferred discovery and hosted web search must remain separate capability decisions

Closes #1522

Verification

  • bun scripts/test.ts tests/catalog-cursor-search.test.ts tests/codex-catalog.test.ts tests/e2e-style/phase100-native-parity.test.ts (176 passed, 0 failed)
  • env -u OPENCODEX_API_AUTH_TOKEN bun scripts/test.ts tests/codex-shim.test.ts (67 passed, 0 failed; confirms the unrelated full-suite host-token interference is environmental)
  • bun run typecheck
  • bun run privacy:scan
  • git diff --check
  • A full bun run test progressed through the affected catalog and broad integration suites without a product assertion failure, but the long-running process was externally terminated with SIGTERM/143 before completion; exact-head repository CI remains required before merge.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.

Summary by CodeRabbit

  • Bug Fixes

    • Corrected search capability reporting for routed models.
    • Routed models now consistently indicate that direct search-tool support is unavailable while preserving hosted web search metadata where applicable.
    • Cursor-routed models no longer advertise hosted web search metadata.
  • Documentation

    • Added guidance on routed tool discovery and hosted search behavior.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Routed catalog entries now set supports_search_tool to false. Non-Cursor entries retain hosted web-search metadata, while Cursor entries omit it. Catalog tests and documentation now reflect this capability split.

Changes

Routed search capability

Layer / File(s) Summary
Catalog capability metadata
src/codex/catalog/parsing.ts, src/codex/catalog/sync.ts
Routed entries and fallback entries disable deferred search-tool discovery. Non-Cursor entries retain web_search_tool_type: "text_and_image", while Cursor entries omit that metadata.
Catalog validation and documentation
tests/codex-catalog.test.ts, tests/catalog-cursor-search.test.ts, tests/e2e-style/phase100-native-parity.test.ts, structure/03_catalog-and-subagents.md
Tests now expect disabled supports_search_tool values. Documentation describes routed tool discovery and hosted search metadata.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested labels: documentation

Suggested reviewers: lidge-jun

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the primary change: preserving direct MCP tool visibility for routed models.
Linked Issues check ✅ Passed The changes satisfy [#1522] by disabling unverified routed search-tool support while preserving direct MCP visibility and the verified tool_search path.
Out of Scope Changes check ✅ Passed The implementation, tests, and documentation changes directly support the linked issue and contain no unrelated scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent/fix-1522-routed-direct-mcp

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

Deterministic PR hygiene checks passed.

@Ingwannu

Copy link
Copy Markdown
Owner Author

Exact-head GitHub CI is now fully green on 29fd4f02484250ebaf6ec256f18a7f8a31b25dd6: all four Linux shards, gates/typecheck/privacy, macOS, storage/API, keyring, npm-global, React Doctor, hygiene, and target checks completed successfully.

The PR remains draft pending independent maintainer review. I am not self-approving or self-merging it.

@lidge-jun
lidge-jun marked this pull request as ready for review August 12, 2026 16:24
@lidge-jun
lidge-jun self-requested a review as a code owner August 12, 2026 16:24

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/codex/catalog/sync.ts`:
- Line 316: Update the fallback catalog-row construction around the isRouted
spread so Cursor routes do not receive web_search_tool_type or
supports_search_tool. Reuse normalizeRoutedCatalogEntry if applicable, or gate
the hosted-search fields on !slug.startsWith("cursor/"), while preserving the
shared provider capability contract. Add a regression test covering a Cursor
fallback entry.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 31808470-360e-46b3-b101-c10bd042376b

📥 Commits

Reviewing files that changed from the base of the PR and between cbbfdd8 and 29fd4f0.

📒 Files selected for processing (6)
  • src/codex/catalog/parsing.ts
  • src/codex/catalog/sync.ts
  • structure/03_catalog-and-subagents.md
  • tests/catalog-cursor-search.test.ts
  • tests/codex-catalog.test.ts
  • tests/e2e-style/phase100-native-parity.test.ts

Comment thread src/codex/catalog/sync.ts
shell_type: "shell_command", visibility: "list", supported_in_api: true,
priority, base_instructions: "You are a helpful coding assistant.",
...(isRouted ? { web_search_tool_type: "text_and_image", supports_search_tool: true } : {}),
...(isRouted ? { web_search_tool_type: "text_and_image", supports_search_tool: false } : {}),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Do not advertise hosted search on Cursor fallback rows.

If template === null and model describes a cursor/... route, this spread adds web_search_tool_type: "text_and_image". The normal path in src/codex/catalog/parsing.ts Lines 395 through 405 removes that field for Cursor entries. Cursor bypasses the hosted-search sidecar, so the fallback catalog can advertise an unavailable capability.

Reuse normalizeRoutedCatalogEntry in this fallback path, or condition web_search_tool_type on !slug.startsWith("cursor/"). Add a regression test for a Cursor fallback entry.

As per path instructions, this src/** change must preserve the shared provider capability contract. The supplied PR objective defines Cursor rows as sidecar-free.

🧰 Tools
🪛 ast-grep (0.45.1)

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { execFileSync } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/codex/catalog/sync.ts` at line 316, Update the fallback catalog-row
construction around the isRouted spread so Cursor routes do not receive
web_search_tool_type or supports_search_tool. Reuse normalizeRoutedCatalogEntry
if applicable, or gate the hosted-search fields on !slug.startsWith("cursor/"),
while preserving the shared provider capability contract. Add a regression test
covering a Cursor fallback entry.

Source: Path instructions

@lidge-jun
lidge-jun merged commit 87a01bc into dev Aug 12, 2026
30 checks passed
@Wibias
Wibias deleted the agent/fix-1522-routed-direct-mcp branch August 12, 2026 21:34
luvs01 pushed a commit to luvs01/opencodex that referenced this pull request Aug 13, 2026
supports_search_tool=false (lidge-jun#1529) fixed plugin visibility by paying a
measured 2.7x turn-1 payload tax: under tool_mode=code_mode_only every MCP
declaration renders into exec.description (16,965 -> 175,849 chars,
total 96,699 -> 258,929). Reachability never needed the tax: code mode keeps
Deferred MCP tools callable via exec's tools global / ALL_TOOLS without a
tool_search round-trip (upstream code_mode suite; live canary 2026-08-13,
routed kimi/k3 executed tools.mcp__node_repl__js).

- parsing.ts: non-Cursor routed rows advertise supports_search_tool=true;
  Cursor stays false (runTurn bypasses the sidecar)
- sync.ts deriveEntry fallback: same split (Cursor fallback keeps false and
  drops web_search_tool_type)
- tests: dual-seam pair fence (code_mode_only + search=true) on template and
  template-less paths, plus Cursor fallback inverse
- structure/03: decision log updated

Measurement + canary: devlog/_plan/260813_tool_catalog_deferral/010,020
lidge-jun added a commit that referenced this pull request Aug 13, 2026
…P3 gaps

A second audit round found that my own "closures" were partly overstated. Fixed
by doing the work, not by rewording it.

Malformed-load warning (devlog 020, previously missing entirely). The schema
degrades these fields with `.catch(undefined)` so a typo cannot cost a user
their providers or credentials, but silent degradation is its own failure: an
operator whose emergency escape hatch was dropped would never learn it is
inactive — the #1529 observability failure at smaller scope. Adds
`warnDegradedRoutedToolDiscoveryForLoad`, wired into both load paths beside the
existing `retryOn429` sanitizer and following its conventions: runs before
schema validation, redacts secret-shaped provider/model names, and logs only the
received TYPE, never the value, since provider config can hold secrets.

Backward compatibility, previously claimed but not proven. The old tests called
`validateConfigCandidate()`, which cannot demonstrate a file-level promise. They
now drive the real on-disk round trip through `loadConfig`/`saveConfig` against a
temp `OPENCODEX_HOME`:
  - a pre-field config gains no persisted fields on read, and an unrelated save
    does not introduce them;
  - a config carrying both fields survives an unrelated save, which is the
    `.passthrough()` downgrade contract.
Ablation: with the fields removed from the schema AND `.passthrough()` switched
to `.strict()`, the downgrade test fails. It is not vacuous.

Combo alias coverage (devlog 025), which the audit correctly refused to let me
defer since it sits inside WP3's declared combo scope. A direct policy is now
asserted through every alias shape — default `combo/<id>`, bare, slashed, and
explicit native alias — because the alias is what Codex sees, and a
shape-dependent policy is exactly the defect class the unified Cursor fence
already had to fix once.

029 exit gate corrected. It had been edited to claim the `020` differential was
the only open item. That was false: the `023` prior-build comparison and the
`025` forcing-member diagnostic remain open too. The retracted claim is left
visible in the note on purpose — this unit exists because a plan asserted more
verification than it had, and quietly editing that away would repeat it.

Verification: bun x tsc --noEmit clean; 427 pass / 0 fail across 9 suites (49 in
the focused file); privacy:scan passed; SHA256SUMS regenerated and verified.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants