Skip to content

fix: parse MCP server names with underscores#2746

Closed
puneetdixit200 wants to merge 1 commit into
Hmbown:mainfrom
puneetdixit200:fix-mcp-underscore-server-names
Closed

fix: parse MCP server names with underscores#2746
puneetdixit200 wants to merge 1 commit into
Hmbown:mainfrom
puneetdixit200:fix-mcp-underscore-server-names

Conversation

@puneetdixit200

@puneetdixit200 puneetdixit200 commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Closes #2744.

This resolves prefixed MCP tool names against the longest known server-name prefix before falling back to the legacy single-underscore split. That keeps existing mcp_server_tool names working while allowing servers like my_db to route mcp_my_db_execute_sql to server my_db and tool execute_sql.

AI-assisted with human review.

Verification

  • Red before fix: CARGO_HOME=/tmp/codewhale-2744-cargo-home CARGO_TARGET_DIR=/tmp/codewhale-2744-target cargo test -p codewhale-tui mcp_pool_call_tool_preserves_server_names_with_underscores -- --nocapture failed with Failed to find MCP server: my
  • CARGO_HOME=/tmp/codewhale-2744-cargo-home CARGO_TARGET_DIR=/tmp/codewhale-2744-target cargo test -p codewhale-tui mcp_pool_call_tool_preserves_server_names_with_underscores -- --nocapture
  • CARGO_HOME=/tmp/codewhale-2744-cargo-home CARGO_TARGET_DIR=/tmp/codewhale-2744-target cargo test -p codewhale-tui mcp_pool_call_tool_preserves -- --nocapture
  • CARGO_HOME=/tmp/codewhale-2744-cargo-home CARGO_TARGET_DIR=/tmp/codewhale-2744-target cargo fmt --check
  • git diff --check

Greptile Summary

This PR fixes MCP tool routing for server names that contain underscores (e.g. my_db) by replacing the naive first-underscore split with a loop that matches the longest known server-name prefix before falling back to the legacy behaviour.

  • parse_prefixed_name now iterates every _ position in the suffix, checks each candidate prefix against self.connections and self.config.servers, and keeps the last (longest) match — making mcp_my_db_execute_sql route to server my_db and tool execute_sql rather than server my.
  • An integration test (mcp_pool_call_tool_preserves_server_names_with_underscores) exercises the new path end-to-end with a scripted transport.
  • The legacy single-underscore fallback is preserved for server names not found in the pool, keeping backward compatibility.

Confidence Score: 4/5

Safe to merge; the core logic correctly implements longest-prefix matching and the existing test suite exercises the primary scenario.

The fix handles the target case correctly and preserves backward compatibility through the legacy fallback. The main gap is that no test covers the overlapping-server-name scenario (both my and my_db registered simultaneously), so the longest-prefix tie-breaking is untested and could be broken by a future refactor without a test failure. The silent re-routing risk when server names share a prefix is also undocumented.

crates/tui/src/mcp.rs — specifically the new loop in parse_prefixed_name and its interaction with overlapping server-name prefixes.

Important Files Changed

Filename Overview
crates/tui/src/mcp.rs Adds longest-prefix server-name resolution to parse_prefixed_name, with a companion integration test; logic is correct but silent re-routing when overlapping server names exist is undocumented

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["parse_prefixed_name(prefixed_name)"] --> B{"starts_with('mcp_')?"}
    B -- No --> ERR1["bail! Invalid MCP tool name"]
    B -- Yes --> C["rest = prefixed_name[4..]"]
    C --> D["resolved = None"]
    D --> E["for each '_' position idx in rest"]
    E --> F["server = rest[..idx], tool = rest[idx+1..]"]
    F --> G{"server or tool empty?"}
    G -- Yes --> E
    G -- No --> H{"server in connections or config?"}
    H -- No --> E
    H -- Yes --> I["resolved = Some((server, tool)) // overwrites: longest prefix wins"]
    I --> E
    E -- done --> J{"resolved is Some?"}
    J -- Yes --> K["return Ok((server, tool)) // longest-prefix match"]
    J -- No --> L["rest.split_once('_')"]
    L -- None --> ERR2["bail! Invalid MCP tool name format"]
    L -- Some --> M["return Ok((server, tool)) // legacy first-underscore fallback"]
Loading

Fix All in Codex Fix All in Claude Code Fix All in Cursor

Reviews (1): Last reviewed commit: "fix: parse MCP server names with undersc..." | Re-trigger Greptile

Greptile also left 2 inline comments on this PR.

@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown

Thanks @puneetdixit200 for taking the time to contribute.

This repository is currently observing a maintainer-managed contribution gate in dry-run mode, so this pull request is staying open. When enforcement is enabled, pull requests from contributors who are not listed in .github/APPROVED_CONTRIBUTORS will be closed automatically.

Please read CONTRIBUTING.md for the expected contribution shape. A maintainer can grant PR access by commenting /lgtm on a pull request.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request updates the MCP tool name resolution logic in crates/tui/src/mcp.rs to correctly handle server names containing underscores by matching prefixes against known connections or configured servers, and adds a corresponding unit test. The reviewer suggested optimizing this lookup by using rmatch_indices instead of match_indices to iterate from right to left, which finds the longest matching server name first and allows for an early return.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread crates/tui/src/mcp.rs
Comment on lines +2248 to +2261
let mut resolved = None;
for (idx, _) in rest.match_indices('_') {
let server = &rest[..idx];
let tool = &rest[idx + 1..];
if server.is_empty() || tool.is_empty() {
continue;
}
if self.connections.contains_key(server) || self.config.servers.contains_key(server) {
resolved = Some((server, tool));
}
}
if let Some((server, tool)) = resolved {
return Ok((server, tool));
}

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.

medium

Since we want to resolve the tool name against the longest known server-name prefix, we can optimize this lookup by iterating backwards using rmatch_indices instead of match_indices.

By searching from right to left, the first match we find that corresponds to a known server is guaranteed to be the longest prefix. This allows us to return early immediately, avoiding redundant map lookups and eliminating the need for the temporary resolved variable.

        for (idx, _) in rest.rmatch_indices('_') {
            let server = &rest[..idx];
            let tool = &rest[idx + 1..];
            if server.is_empty() || tool.is_empty() {
                continue;
            }
            if self.connections.contains_key(server) || self.config.servers.contains_key(server) {
                return Ok((server, tool));
            }
        }

Comment thread crates/tui/src/mcp.rs
Comment on lines +3656 to +3657
#[tokio::test]
async fn mcp_pool_call_tool_preserves_server_names_with_underscores() {

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.

P2 The loop overwrites resolved on every match, so the last (longest-prefix) match wins. This is the stated intent, but there is no test that verifies the tie-breaking when two registered servers share an underscore prefix (e.g. both my and my_db are in the pool and the call is mcp_my_db_execute_sql). Without that test, a future refactor that switches the loop to pick the first match would silently change routing for users with overlapping server names. Consider adding a dedicated test for this boundary.

Suggested change
#[tokio::test]
async fn mcp_pool_call_tool_preserves_server_names_with_underscores() {
/// Verify longest-prefix wins when two server names overlap.
#[tokio::test]
async fn mcp_pool_parse_prefixed_name_longest_prefix_wins() {
// Server "my" is registered; "my_db" is also registered.
// mcp_my_db_execute_sql must route to "my_db", not "my".
let pool = McpPool::new(McpConfig {
timeouts: McpTimeouts::default(),
servers: {
let mut m = HashMap::new();
m.insert("my".to_string(), Default::default());
m.insert("my_db".to_string(), Default::default());
m
},
});
let (server, tool) = pool.parse_prefixed_name("mcp_my_db_execute_sql").unwrap();
assert_eq!(server, "my_db");
assert_eq!(tool, "execute_sql");
}
#[tokio::test]
async fn mcp_pool_call_tool_preserves_server_names_with_underscores() {

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Codex Fix in Claude Code Fix in Cursor

Comment thread crates/tui/src/mcp.rs
Comment on lines +2248 to +2261
let mut resolved = None;
for (idx, _) in rest.match_indices('_') {
let server = &rest[..idx];
let tool = &rest[idx + 1..];
if server.is_empty() || tool.is_empty() {
continue;
}
if self.connections.contains_key(server) || self.config.servers.contains_key(server) {
resolved = Some((server, tool));
}
}
if let Some((server, tool)) = resolved {
return Ok((server, tool));
}

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.

P2 Silent re-routing when server names share a prefix

Adding a new server whose name is a prefix-extension of an existing one (e.g. registering my_db after my already exists) silently re-routes any mcp_my_db_* tool call that was previously landing on server my. The tool on my named db_<anything> becomes unreachable through the prefixed name the moment my_db is added to the pool. The longest-match semantics are correct per the PR description, but this behavioral change is invisible at the call site — callers get a successful response from a different server, or an "unknown tool" error if my_db doesn't expose that tool. A doc comment on parse_prefixed_name stating the longest-prefix rule and its consequence for overlapping server names would help future maintainers.

Fix in Codex Fix in Claude Code Fix in Cursor

@Hmbown

Hmbown commented Jun 5, 2026

Copy link
Copy Markdown
Owner

Thanks @puneetdixit200 — this correctly identified and fixed the MCP server-name underscore parsing bug from #2744.

The v0.9.0 integration branch (codex/v0.9.0-stewardship) landed the equivalent fix through #2747 as 9e29c221b, choosing that branch because it included the longest-registered-server tie-break coverage for overlapping server names like my and my_db. Your #2746 work is explicitly credited in that commit and in the execution map; the commit includes your GitHub-mappable co-author trailer as well as @cyq1017's.

Verification recorded for the landed fix: cargo test -p codewhale-tui --bin codewhale-tui --locked mcp_pool_call_tool (3 pass).

Closing this PR as superseded by the credited integration-branch harvest, not because the report or fix was ignored.

@Hmbown Hmbown closed this Jun 5, 2026
Hmbown added a commit that referenced this pull request Jun 5, 2026
Update the execution map after closing harvested/superseded PRs #2746, #2747, #2750, #2756, #2757, and #2760, and refresh the live PR count.
timothybrush pushed a commit to timothybrush/DeepSeek-TUI that referenced this pull request Jun 8, 2026
parse_prefixed_name now matches the qualified mcp_<server>_<tool> name against
the set of registered server names (connections + configured servers) and
prefers the longest matching server name, instead of naively splitting on the
first underscore. Tools on servers whose names contain underscores (e.g.
"my_db") are now reachable, and an overlapping pair like "my" and "my_db"
routes to the correct server. Falls back to the legacy first-underscore split
when no registered server matches, preserving backward compatibility.

Harvested from PR Hmbown#2747 by @cyq1017; supersedes the equivalent fix in PR Hmbown#2746
by @puneetdixit200. Both contributors diagnosed and fixed issue Hmbown#2744; Hmbown#2747
landed for its longest-match tie-break test coverage. Fixes Hmbown#2744.

Co-authored-by: cyq1017 <61975706+cyq1017@users.noreply.github.com>
Co-authored-by: puneetdixit200 <236133619+puneetdixit200@users.noreply.github.com>
timothybrush pushed a commit to timothybrush/DeepSeek-TUI that referenced this pull request Jun 8, 2026
…mbown#2757 harvests and Hmbown#2742/Hmbown#2751/Hmbown#2755 dispositions

Log the new community-PR harvests in CHANGELOG.md and crates/tui/CHANGELOG.md
(MCP underscore server names, Xiaomi MiMo pricing, hydrated deferred-tool
render, Token Plan region docs) with contributor credit, and update
docs/V0_9_0_EXECUTION_MAP.md with evidence-backed dispositions for the
newly-reviewed PRs, including the deferred Hmbown#2742 and forwarded Hmbown#2751/Hmbown#2755.
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.

MCP tool name parsing breaks when server name contains underscores

2 participants