Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 35 additions & 4 deletions lib/active_agent/providers/anthropic/transforms.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,18 @@ def normalize_params(params)
params[:tool_choice] = normalize_tool_choice(params[:tool_choice]) if params[:tool_choice]

# Handle mcps parameter (common format) -> transforms to mcp_servers (provider format)
if params[:mcps]
params[:mcp_servers] = normalize_mcp_servers(params.delete(:mcps))
elsif params[:mcp_servers]
params[:mcp_servers] = normalize_mcp_servers(params[:mcp_servers])
if params[:mcps] || params[:mcp_servers]
mcps = if params[:mcps]
params.delete(:mcps)
else
params[:mcp_servers]
end

params[:mcp_servers] = normalize_mcp_servers(mcps)

if params[:tools].nil? # If tools not already provided, extract from mcps
params[:tools] = normalize_mcp_tools(mcps)
end
end

params
Expand Down Expand Up @@ -105,6 +113,29 @@ def normalize_mcp_servers(mcp_servers)
end
end

def normalize_mcp_tools(mcp_servers)
return [] unless mcp_servers.is_a?(Array)

result = mcp_servers.map do |server|
server_hash = server.is_a?(Hash) ? server.deep_symbolize_keys : server

if server_hash[:allowed_tools].present?
{
type: "mcp_toolset",
mcp_server_name: server_hash[:name],
default_config: {
enabled: false
},
configs: server_hash[:allowed_tools].to_h { |tool|
[ tool[:name], { enabled: true } ]
}
}
end
end

result.compact.presence
end

# Normalizes tool_choice from common format to Anthropic gem model objects.
#
# The Anthropic gem expects tool_choice to be a model object (ToolChoiceAuto,
Expand Down
8 changes: 8 additions & 0 deletions lib/active_agent/providers/open_ai/responses/transforms.rb
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,14 @@ def normalize_mcp_servers(mcp_servers)
server_url: server_hash[:url] || server_hash[:server_url]
}

if server_hash[:require_approval]
result[:require_approval] = server_hash[:require_approval]
end

if server_hash[:allowed_tools]
result[:allowed_tools] = server_hash[:allowed_tools]
end

# Keep authorization field (OpenAI uses 'authorization', not 'authorization_token')
if server_hash[:authorization]
result[:authorization] = server_hash[:authorization]
Expand Down
190 changes: 190 additions & 0 deletions test/providers/anthropic/transforms_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -691,6 +691,196 @@ def transforms

assert_equal "not an array", result
end

# normalize_mcp_tools tests
test "normalize_mcp_tools converts allowed_tools to mcp_toolset format" do
mcp_servers = [
{
name: "stripe",
url: "https://mcp.stripe.com",
allowed_tools: [
{ name: "create_payment" },
{ name: "get_payment" }
]
}
]

result = transforms.normalize_mcp_tools(mcp_servers)

assert_equal 1, result.size
assert_equal "mcp_toolset", result[0][:type]
assert_equal "stripe", result[0][:mcp_server_name]
assert_equal false, result[0][:default_config][:enabled]
assert_equal 2, result[0][:configs].size
assert_equal true, result[0][:configs]["create_payment"][:enabled]
assert_equal true, result[0][:configs]["get_payment"][:enabled]
end

test "normalize_mcp_tools handles multiple servers with allowed_tools" do
mcp_servers = [
{
name: "stripe",
url: "https://mcp.stripe.com",
allowed_tools: [
{ name: "create_payment" }
]
},
{
name: "github",
url: "https://api.github.com",
allowed_tools: [
{ name: "search_repos" },
{ name: "create_issue" }
]
}
]

result = transforms.normalize_mcp_tools(mcp_servers)

assert_equal 2, result.size
assert_equal "stripe", result[0][:mcp_server_name]
assert_equal 1, result[0][:configs].size
assert_equal "github", result[1][:mcp_server_name]
assert_equal 2, result[1][:configs].size
end

test "normalize_mcp_tools skips servers without allowed_tools" do
mcp_servers = [
{
name: "stripe",
url: "https://mcp.stripe.com",
allowed_tools: [
{ name: "create_payment" }
]
},
{
name: "public",
url: "https://public.api.com"
# No allowed_tools
}
]

result = transforms.normalize_mcp_tools(mcp_servers)

assert_equal 1, result.size
assert_equal "stripe", result[0][:mcp_server_name]
end

test "normalize_mcp_tools handles empty allowed_tools array" do
mcp_servers = [
{
name: "stripe",
url: "https://mcp.stripe.com",
allowed_tools: []
}
]

result = transforms.normalize_mcp_tools(mcp_servers)

assert_nil result
end

test "normalize_mcp_tools returns nil for servers all without allowed_tools" do
mcp_servers = [
{
name: "public",
url: "https://public.api.com"
}
]

result = transforms.normalize_mcp_tools(mcp_servers)

assert_nil result
end

test "normalize_mcp_tools returns empty array for nil input" do
result = transforms.normalize_mcp_tools(nil)

assert_equal [], result
end

test "normalize_mcp_tools returns empty array for empty array" do
result = transforms.normalize_mcp_tools([])

assert_nil result
end

test "normalize_mcp_tools returns empty array for non-array input" do
result = transforms.normalize_mcp_tools("not an array")

assert_equal [], result
end

test "normalize_mcp_tools handles single tool" do
mcp_servers = [
{
name: "stripe",
url: "https://mcp.stripe.com",
allowed_tools: [
{ name: "create_payment" }
]
}
]

result = transforms.normalize_mcp_tools(mcp_servers)

assert_equal 1, result.size
assert_equal 1, result[0][:configs].size
assert_equal true, result[0][:configs]["create_payment"][:enabled]
end

# Integration test for normalize_params with allowed_tools
test "normalize_params extracts mcp_tools from mcps with allowed_tools" do
params = {
mcps: [
{
name: "stripe",
url: "https://mcp.stripe.com",
authorization: "sk_test_123",
allowed_tools: [
{ name: "create_payment" },
{ name: "get_payment" }
]
}
]
}

result = transforms.normalize_params(params)

# Should have mcp_servers
assert_equal 1, result[:mcp_servers].size
assert_equal "stripe", result[:mcp_servers][0][:name]

# Should have extracted tools from allowed_tools
assert result[:tools].present?
assert_equal 1, result[:tools].size
assert_equal "mcp_toolset", result[:tools][0][:type]
assert_equal "stripe", result[:tools][0][:mcp_server_name]
assert_equal 2, result[:tools][0][:configs].size
end

test "normalize_params does not override existing tools when extracting from mcps" do
params = {
tools: [
{ name: "existing_tool", input_schema: { type: "object" } }
],
mcps: [
{
name: "stripe",
url: "https://mcp.stripe.com",
allowed_tools: [
{ name: "create_payment" }
]
}
]
}

result = transforms.normalize_params(params)

# Should keep existing tools, not override with mcp tools
assert_equal 1, result[:tools].size
assert_equal "existing_tool", result[:tools][0][:name]
end
end
end
end
Loading