Prefix compressed MCP namespaces so tool names start with a letter - #287
Merged
Merged
Conversation
anticomputer
requested review from
JarLob,
Kwstubbs,
kevinbackhouse,
p- and
sylwia-budzynska
as code owners
July 30, 2026 20:08
Contributor
There was a problem hiding this comment.
Pull request overview
This PR fixes an interoperability bug in MCP tool-name namespacing where compress_name() could produce a namespace starting with a digit (hex digest prefix), which can yield invalid tool/function names for some providers and cause entire requests to be rejected.
Changes:
- Prefix
compress_name()output with a fixed"ns"so namespaced tool names always start with a letter. - Add unit tests covering
compress_name()leading-character safety, stability/distinctness, and headroom under the 64-character tool-name limit.
Show a summary per file
| File | Description |
|---|---|
src/seclab_taskflow_agent/mcp_utils.py |
Adds a constant prefix to compressed namespaces so generated tool names begin with a letter. |
tests/test_mcp_utils.py |
Adds regression/unit coverage for compress_name() properties relevant to provider tool-name validation. |
Review details
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 2/2 changed files
- Comments generated: 0
- Review effort level: Low
anticomputer
force-pushed
the
anticomputer/mcp-tool-name-leading-digit
branch
from
July 30, 2026 20:12
e7f17dc to
ffa58e7
Compare
compress_name returned a bare sha256 prefix, so the namespace prepended
to every tool of a server began with a digit ten times in sixteen,
producing names like `8fb2adfa03efcontainer_shell_exec`.
Gemini documents that a function name must start with a letter or an
underscore and enforces it by rejecting the whole request with 400
invalid_request_body, so one unlucky server name disabled every tool in
the run rather than that server's alone. OpenAI and Anthropic document
the laxer ^[a-zA-Z0-9_-]{1,64}$ and accept a leading digit, which is why
this looked like an intermittent, model-specific failure.
Prefix the digest with a fixed "ns". Adds compress_name tests for the
leading character, stability, distinctness and the 64-character budget.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dadec5f9-3bf8-449c-84d6-db45be19bb6a
anticomputer
force-pushed
the
anticomputer/mcp-tool-name-leading-digit
branch
from
July 30, 2026 20:33
ffa58e7 to
b8f6b78
Compare
Contributor
There was a problem hiding this comment.
Review details
Comments suppressed due to low confidence (2)
src/seclab_taskflow_agent/mcp_utils.py:66
- The docstring says this function returns a "hex digest", but it now returns a prefixed string (e.g. "ns" + hex). Tweaking the wording will keep the documentation accurate and avoid confusion for callers/tests that read it literally.
Returns:
A lowercase hex digest of ``COMPRESSED_NAME_LENGTH`` characters, behind
a fixed prefix so the result always starts with a letter.
tests/test_mcp_utils.py:154
- This test asserts the first character is a letter, but the documented Gemini constraint is "letter or underscore". Allowing "_" keeps the test aligned with the stated requirement and avoids an unnecessary future test break if the prefix ever changes to an underscore (still valid for Gemini).
assert compress_name(server_name)[0].isalpha()
- Files reviewed: 2/2 changed files
- Comments generated: 0 new
- Review effort level: Low
p-
approved these changes
Jul 31, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
compress_namereturns a bare sha256 prefix, and that value is prepended to the name of every tool an MCP server exposes:A hex digest starts with a digit ten times in sixteen, so a server whose name hashes unluckily produces tool names like
8fb2adfa03efcontainer_shell_exec.Gemini documents that a function name "must start with a letter or an underscore", and enforces it by rejecting the entire request. So one unlucky server name disables every tool in the run, not just that server's.
Probed against CAPI, sending the same minimal request twice and changing only the tool name:
8fb2adfa03ef…ns8fb2adfa03ef…gemini-3.6-flashinvalid_request_bodyclaude-sonnet-5gpt-5-minigpt-5.4gpt-5.6-solgrok-4.5Only Gemini rejects it. OpenAI and Anthropic document the laxer
^[a-zA-Z0-9_-]{1,64}$, which permits a leading digit, and they do accept it in practice.That narrowness is what makes this expensive to diagnose. The same taskflow works on most models and fails on one; the error names no tool and no server; and whether you hit it at all depends on the sha256 of a server name nobody chose for its hash. I found it on a multi-model task where the Gemini branch was the only one that died, and the obvious readings — entitlements, a malformed tool schema, the wrong endpoint — were all wrong.
The fix
Prefix the digest with a fixed
nsso the result always starts with a letter. The namespace is opaque and is stripped withremoveprefix, so nothing else changes; the name grows from 12 characters to 14, well inside the 64-character limit the compression exists to respect.compress_nameis also used for handoff agent names inrunner.py, which get the same treatment.Conforming to the strictest of the three documented patterns costs two characters and removes a class of failure that is invisible until a specific server name meets a specific provider.
Tests
Adds
compress_namecoverage for the leading character (includingContainerShell, one of the names that actually triggers this), stability, distinctness, and the 64-character budget. Full suite passes: 556 passed, 2 skipped.