From 66fa921e5af2054b9274100039f4a2cefef7964a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=82osz=20Sobczyk?= Date: Thu, 9 Jul 2026 07:14:46 -0700 Subject: [PATCH] fix(mcp): guard empty tool parameters in `adkToMcpToolType` `adkToMcpToolType` called `Optional.get()` on `parameters()` unguarded, so a present declaration with no parameters (a valid no-argument tool) threw `NoSuchElementException`. Build the MCP tool without an input schema when parameters are absent. Also make the method static so the utility class (`final`, `private` constructor) is actually callable, and add `ConversionUtilsTest`. PiperOrigin-RevId: 945099824 --- .../google/adk/tools/mcp/ConversionUtils.java | 9 +- .../adk/tools/mcp/ConversionUtilsTest.java | 88 +++++++++++++++++++ 2 files changed, 92 insertions(+), 5 deletions(-) create mode 100644 core/src/test/java/com/google/adk/tools/mcp/ConversionUtilsTest.java diff --git a/core/src/main/java/com/google/adk/tools/mcp/ConversionUtils.java b/core/src/main/java/com/google/adk/tools/mcp/ConversionUtils.java index c115cfca0..0b3da2700 100644 --- a/core/src/main/java/com/google/adk/tools/mcp/ConversionUtils.java +++ b/core/src/main/java/com/google/adk/tools/mcp/ConversionUtils.java @@ -29,16 +29,15 @@ public final class ConversionUtils { private static final McpJsonMapper jsonMapper = McpJsonDefaults.getMapper(); - public McpSchema.Tool adkToMcpToolType(BaseTool tool) { - Optional toolDeclaration = tool.declaration(); - if (toolDeclaration.isEmpty()) { + public static McpSchema.Tool adkToMcpToolType(BaseTool tool) { + Optional parameters = tool.declaration().flatMap(FunctionDeclaration::parameters); + if (parameters.isEmpty()) { return McpSchema.Tool.builder().name(tool.name()).description(tool.description()).build(); } - Schema geminiSchema = toolDeclaration.get().parameters().get(); return McpSchema.Tool.builder() .name(tool.name()) .description(tool.description()) - .inputSchema(jsonMapper, geminiSchema.toJson()) + .inputSchema(jsonMapper, parameters.get().toJson()) .build(); } diff --git a/core/src/test/java/com/google/adk/tools/mcp/ConversionUtilsTest.java b/core/src/test/java/com/google/adk/tools/mcp/ConversionUtilsTest.java new file mode 100644 index 000000000..0b3749269 --- /dev/null +++ b/core/src/test/java/com/google/adk/tools/mcp/ConversionUtilsTest.java @@ -0,0 +1,88 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.adk.tools.mcp; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.adk.tools.BaseTool; +import com.google.genai.types.FunctionDeclaration; +import com.google.genai.types.Schema; +import io.modelcontextprotocol.spec.McpSchema; +import java.util.Optional; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class ConversionUtilsTest { + + /** Minimal {@link BaseTool} whose declaration is supplied by the test. */ + private static final class FakeTool extends BaseTool { + private final Optional declaration; + + FakeTool(String name, String description, Optional declaration) { + super(name, description); + this.declaration = declaration; + } + + @Override + public Optional declaration() { + return declaration; + } + } + + @Test + public void adkToMcpToolType_declarationWithParameters_setsInputSchema() { + FunctionDeclaration declaration = + FunctionDeclaration.builder() + .name("withParams") + .parameters(Schema.builder().type("OBJECT").build()) + .build(); + BaseTool tool = new FakeTool("withParams", "has params", Optional.of(declaration)); + + McpSchema.Tool result = ConversionUtils.adkToMcpToolType(tool); + + assertThat(result.name()).isEqualTo("withParams"); + assertThat(result.description()).isEqualTo("has params"); + assertThat(result.inputSchema()).isNotNull(); + } + + @Test + public void adkToMcpToolType_declarationWithoutParameters_omitsInputSchema() { + // A present declaration with no parameters is a valid no-argument tool. Before the fix this + // threw NoSuchElementException from an unguarded Optional.get() on parameters(). + FunctionDeclaration declaration = FunctionDeclaration.builder().name("noParams").build(); + BaseTool tool = new FakeTool("noParams", "no params", Optional.of(declaration)); + + McpSchema.Tool result = ConversionUtils.adkToMcpToolType(tool); + + assertThat(result.name()).isEqualTo("noParams"); + assertThat(result.description()).isEqualTo("no params"); + assertThat(result.inputSchema()).isNull(); + } + + @Test + public void adkToMcpToolType_noDeclaration_omitsInputSchema() { + BaseTool tool = new FakeTool("bare", "no declaration", Optional.empty()); + + McpSchema.Tool result = ConversionUtils.adkToMcpToolType(tool); + + assertThat(result.name()).isEqualTo("bare"); + assertThat(result.description()).isEqualTo("no declaration"); + assertThat(result.inputSchema()).isNull(); + } +}