Skip to content

fix(bedrock): sanitize tool names to satisfy Bedrock Converse API constraint#1730

Open
mesutoezdil wants to merge 3 commits intokagent-dev:mainfrom
mesutoezdil:fix/bedrock-tool-name-sanitization
Open

fix(bedrock): sanitize tool names to satisfy Bedrock Converse API constraint#1730
mesutoezdil wants to merge 3 commits intokagent-dev:mainfrom
mesutoezdil:fix/bedrock-tool-name-sanitization

Conversation

@mesutoezdil
Copy link
Copy Markdown
Contributor

@mesutoezdil mesutoezdil commented Apr 23, 2026

Closes #1473.

MCP server tool names often contain dots, colons, or spaces (e.g. fetch.get_url or filesystem:read_file). Bedrock's Converse API rejects any tool name outside [a-zA-Z0-9_-]+, so agents wired up to MCP tools hit a hard validation error before the first call goes through.

Added _sanitize_tool_name() that replaces invalid characters with underscores and caches the mapping for the duration of a request, same pattern as the existing _sanitize_tool_id(). Wired it into _convert_tools_to_converse() so every toolSpec.name sent to Bedrock is clean. The same map is passed to _convert_content_to_converse_messages() so conversation history uses the sanitized names Bedrock expects. A reverse map is built from it so that when Bedrock echoes back a sanitized name in a toolUse response, we restore the original name before returning it in the LlmResponse, that way the ADK framework can still dispatch to the correctly registered tool.

Tests cover the sanitizer itself (dots, colons, spaces, hyphens, caching, empty input) and the end-to-end flow verifying that the tool spec reaches Bedrock with the sanitized name and the response comes back with the original.

Copilot AI review requested due to automatic review settings April 23, 2026 09:13
@github-actions github-actions Bot added the bug Something isn't working label Apr 23, 2026
Copy link
Copy Markdown
Contributor

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 updates the Bedrock Converse integration to sanitize MCP tool names to meet Bedrock’s [a-zA-Z0-9_-]+ constraint, while preserving original tool names in the ADK-facing responses.

Changes:

  • Add _sanitize_tool_name() and apply it when converting tool specs for Bedrock.
  • Thread a tool-name mapping through message-history conversion so prior toolUse blocks can use sanitized names.
  • Remap Bedrock-returned sanitized tool names back to original names in generate_content_async(), with accompanying unit/integration tests.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
python/packages/kagent-adk/src/kagent/adk/models/_bedrock.py Sanitizes tool spec names and remaps tool-use names in request/response paths.
python/packages/kagent-adk/tests/unittests/models/test_bedrock.py Adds unit tests for sanitization and conversion, plus an end-to-end test for request/response remapping.

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

Comment on lines +271 to +272
# Reverse map lets us restore original tool names from sanitized names in Bedrock responses.
reverse_name_map: dict[str, str] = {v: k for k, v in tool_name_map.items()}
Comment on lines +106 to 113
raw_name = part.function_call.name or ""
sanitized_name = tool_name_map.get(raw_name, raw_name) if tool_name_map else raw_name
blocks.append(
{
"toolUse": {
"toolUseId": _sanitize_tool_id(part.function_call.id or "", id_map, counter),
"name": part.function_call.name or "",
"name": sanitized_name,
"input": part.function_call.args or {},
Comment on lines +59 to +68
def test_fully_invalid_name_gets_synthetic(self):
name_map: dict = {}
counter = [0]
# After substitution "!!!" becomes "___" which still passes the regex,
# but a name that starts with digits after substitution is fine too.
# Test a name that after substitution yields an empty string (all chars removed
# if we had a stricter regex — here it will be underscores, which is valid).
result = _sanitize_tool_name("valid_after_sub", name_map, counter)
assert _sanitize_tool_name.__module__ # just ensure it ran without error
assert result == "valid_after_sub"
Comment on lines +258 to +259
from google.genai import types

@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Apr 23, 2026
MCP server tool names often contain dots, colons, or spaces
(e.g. "fetch.get_url", "filesystem:read_file") which are rejected
by Bedrock's tool name constraint ([a-zA-Z0-9_-]+).

Add _sanitize_tool_name() mirroring the existing _sanitize_tool_id()
logic, wire it into _convert_tools_to_converse() so tool specs sent
to Bedrock always have valid names, and pass the resulting name_map
to _convert_content_to_converse_messages() so conversation history
uses the same sanitized names. Build a reverse map from the sanitized
names back to originals so LlmResponse function_call.name reflects
the original tool name the ADK framework registered, not the
Bedrock-sanitized variant.

Fixes kagent-dev#1473

Signed-off-by: mesutoezdil <mesudozdil@gmail.com>
Signed-off-by: mesutoezdil <mesudozdil@gmail.com>
@mesutoezdil mesutoezdil force-pushed the fix/bedrock-tool-name-sanitization branch from 945d241 to 1663ca8 Compare April 23, 2026 10:00
@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Apr 23, 2026
@supreme-gg-gg
Copy link
Copy Markdown
Contributor

Hi @mesutoezdil thanks for the PR, can you also add it to Go runtime: https://github.com/kagent-dev/kagent/blob/main/go/adk/pkg/models/bedrock.go

Have you been able to test the fix with a MCP server that actually has invalid tool name for Bedrock? I'm pretty sure this issue is valid and this is the right fix, but just double checking since I haven't got the chance to reproduce it

@supreme-gg-gg supreme-gg-gg self-assigned this Apr 23, 2026
MCP tool names often contain dots, colons, or spaces (e.g. fetch.get_url,
filesystem:read_file) that Bedrock rejects because its Converse API requires
tool names to match [a-zA-Z0-9_-]+.

Added sanitizeBedrockToolName that replaces invalid characters with
underscores, caching the mapping so repeated calls for the same name are
consistent. convertGenaiToolsToBedrock now returns the original->sanitized
name map alongside the tool list.

The name map is threaded through GenerateContent so:
- conversation history FunctionCall blocks use the sanitized name Bedrock
  already knows about
- a reverse map restores the original name in both streaming and
  non-streaming responses so the ADK framework dispatches to the correctly
  registered tool

Tests cover: sanitizer basics (dots, colons, spaces, caching, empty input),
end-to-end tool spec name sanitization, and the name map contents.

Signed-off-by: mesutoezdil <mesudozdil@gmail.com>
@mesutoezdil mesutoezdil requested a review from ilackarms as a code owner April 24, 2026 08:31
@mesutoezdil
Copy link
Copy Markdown
Contributor Author

Go runtime is now updated. sanitizeBedrockToolName follows the same pattern as sanitizeBedrockToolID already in the file. The name map is built in convertGenaiToolsToBedrock, threaded through GenerateContent, applied to conversation history in convertGenaiContentsToBedrockMessages, and reversed in both generateNonStreaming and generateStreaming so the original tool name comes back to the ADK dispatcher.

On testing: I have not run this against a live MCP server with an invalid tool name. The fix mirrors the Python implementation exactly and is covered by unit tests (sanitizer, end-to-end tool spec conversion, name map contents). If you have a setup to test with fetch or filesystem MCP tools that expose names like fetch.get_url, that would give us the live confirmation.

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.

fix(bedrock): sanitize tool names for Bedrock API constraints

3 participants