fix(mcp): truncate tool names exceeding 64-char provider limit#24871
fix(mcp): truncate tool names exceeding 64-char provider limit#24871georgeglarson wants to merge 2 commits intoanomalyco:devfrom
Conversation
OpenAI's tool-use API rejects tools[].function.name >64 chars. When an MCP server's `<server>_<tool>` overflows, the provider returns 400 and opencode silently stops because the error is DEBUG-logged but never surfaces to the TUI. Add buildToolName() next to sanitize: <=64 chars passes through; over truncates to prefix(55) + "_" + Hash.fast(combined).slice(0, 8). Logs a warn on truncation. Applied at tools() only -- the call site that feeds function.name to providers. fetchFromClient()'s ':'-separated keys for prompts/resources are not subject to the 64-char limit. Closes anomalyco#3523 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
The following comment was made by an LLM, it may be inaccurate: Potential duplicate found:
Why it's related: |
There was a problem hiding this comment.
Pull request overview
Fixes MCP tool registration against OpenAI’s 64-character tools[].function.name limit by introducing a deterministic truncation strategy that preserves uniqueness, preventing provider 400s that previously caused MCP processing to stop silently.
Changes:
- Add
buildToolName()helper to cap<server>_<tool>names at 64 chars using a readable prefix plus an 8-hex hash suffix on overflow. - Apply
buildToolName()inMCP.tools()when constructing the tool map passed to the provider. - Add lifecycle tests covering truncation behavior and collision avoidance, plus a short-name “unchanged” case.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| packages/opencode/src/mcp/index.ts | Introduces buildToolName() and uses it when registering tools to enforce the 64-char provider constraint. |
| packages/opencode/test/mcp/lifecycle.test.ts | Adds tests ensuring long tool names are truncated to ≤64 with unique hashed suffixes, and short names remain unchanged. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (combined.length <= MAX_TOOL_NAME) return combined | ||
| const hash = Hash.fast(combined).slice(0, 8) | ||
| const truncated = combined.slice(0, MAX_TOOL_NAME - hash.length - 1) + "_" + hash | ||
| log.warn("MCP tool name exceeds 64 chars, truncating with hash suffix", { |
Avoids drift if the limit changes. Suggested by Copilot review on PR. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Issue for this PR
Closes #3523
Type of change
What does this PR do?
OpenAI's tool-use API rejects
tools[].function.namestrings longer than 64 characters. When an MCP server's combined<sanitized-server>_<sanitized-tool>overflows, the provider returns 400 and opencode silently stops processing because the error is logged at DEBUG but never surfaces to the TUI — exactly the symptom in the issue.buildToolName()(new helper next tosanitizeinpackages/opencode/src/mcp/index.ts) returns combined names ≤64 chars unchanged. Overflows truncate toprefix(55) + "_" + hash(8)wherehashisHash.fast(combined).slice(0, 8). That preserves 55 chars of readable prefix while keeping keys distinct when two tools' first 64 chars would otherwise collide. Every truncation logs alog.warn.Applied at
tools()only — the call site feedingfunction.nameto the provider. The:-separated keys infetchFromClient()(prompts/resources) aren't subject to the 64-char limit, so I left them alone.PR #15595 attempted this but was opened against the pre-Effect
namespace MCPcode, so that diff is rebase-incompatible regardless of approach. It also uses blind.slice(0, 64), which silently drops one of any two tools whose first 64 chars match — the hash suffix here prevents that.How did you verify your code works?
Two new tests in
packages/opencode/test/mcp/lifecycle.test.ts:chrome-devtools-aaaaaaaaaaaaaaaaaaa(the issue's reproducer) with two tools whose combined names share a 55-char prefix. Asserts both end up in the registry, both ≤64 chars, both end with_[0-9a-f]{8}, and the keys are distinct.<server>_<tool>.Demonstrating the bug catch with the source change reverted (tests kept):
expect(<= 64), got 82— fail_<8hex>suffixFull run:
bun test test/mcp/lifecycle.test.ts→ 21 pass, 0 fail (19 existing + 2 new).bun run typecheck→ clean.Screenshots / recordings
N/A — no UI change.
Checklist