diff --git a/src/main/java/com/google/genai/AfcUtil.java b/src/main/java/com/google/genai/AfcUtil.java index 413fa43f42f..8ff11ce0ac8 100644 --- a/src/main/java/com/google/genai/AfcUtil.java +++ b/src/main/java/com/google/genai/AfcUtil.java @@ -81,6 +81,9 @@ static ImmutableList findAfcIncompatibleToolIndexes(GenerateContentConf if (tool.functionDeclarations().isPresent() && !tool.functionDeclarations().get().isEmpty()) { incompatibleToolsIndexesBuilder.add(i); } + if (tool.mcpServers().isPresent() && !tool.mcpServers().get().isEmpty()) { + incompatibleToolsIndexesBuilder.add(i); + } } return incompatibleToolsIndexesBuilder.build(); diff --git a/src/main/java/com/google/genai/Batches.java b/src/main/java/com/google/genai/Batches.java index 9f1b4a06094..a7a584a9485 100644 --- a/src/main/java/com/google/genai/Batches.java +++ b/src/main/java/com/google/genai/Batches.java @@ -1862,6 +1862,13 @@ ObjectNode toolToMldev(JsonNode fromObject, ObjectNode parentObject) { Common.getValueByPath(fromObject, new String[] {"urlContext"})); } + if (Common.getValueByPath(fromObject, new String[] {"mcpServers"}) != null) { + Common.setValueByPath( + toObject, + new String[] {"mcpServers"}, + Common.getValueByPath(fromObject, new String[] {"mcpServers"})); + } + return toObject; } diff --git a/src/main/java/com/google/genai/Caches.java b/src/main/java/com/google/genai/Caches.java index ebaca35a322..6f7b3bdefe3 100644 --- a/src/main/java/com/google/genai/Caches.java +++ b/src/main/java/com/google/genai/Caches.java @@ -872,6 +872,13 @@ ObjectNode toolToMldev(JsonNode fromObject, ObjectNode parentObject) { Common.getValueByPath(fromObject, new String[] {"urlContext"})); } + if (Common.getValueByPath(fromObject, new String[] {"mcpServers"}) != null) { + Common.setValueByPath( + toObject, + new String[] {"mcpServers"}, + Common.getValueByPath(fromObject, new String[] {"mcpServers"})); + } + return toObject; } @@ -950,6 +957,10 @@ ObjectNode toolToVertex(JsonNode fromObject, ObjectNode parentObject) { Common.getValueByPath(fromObject, new String[] {"urlContext"})); } + if (!Common.isZero(Common.getValueByPath(fromObject, new String[] {"mcpServers"}))) { + throw new IllegalArgumentException("mcpServers parameter is not supported in Vertex AI."); + } + return toObject; } diff --git a/src/main/java/com/google/genai/LiveConverters.java b/src/main/java/com/google/genai/LiveConverters.java index b36c9bef435..7f2ae891eb2 100644 --- a/src/main/java/com/google/genai/LiveConverters.java +++ b/src/main/java/com/google/genai/LiveConverters.java @@ -1630,6 +1630,13 @@ ObjectNode toolToMldev(JsonNode fromObject, ObjectNode parentObject) { Common.getValueByPath(fromObject, new String[] {"urlContext"})); } + if (Common.getValueByPath(fromObject, new String[] {"mcpServers"}) != null) { + Common.setValueByPath( + toObject, + new String[] {"mcpServers"}, + Common.getValueByPath(fromObject, new String[] {"mcpServers"})); + } + return toObject; } @@ -1708,6 +1715,10 @@ ObjectNode toolToVertex(JsonNode fromObject, ObjectNode parentObject) { Common.getValueByPath(fromObject, new String[] {"urlContext"})); } + if (!Common.isZero(Common.getValueByPath(fromObject, new String[] {"mcpServers"}))) { + throw new IllegalArgumentException("mcpServers parameter is not supported in Vertex AI."); + } + return toObject; } diff --git a/src/main/java/com/google/genai/Models.java b/src/main/java/com/google/genai/Models.java index cd5f8aed644..26b46ef9078 100644 --- a/src/main/java/com/google/genai/Models.java +++ b/src/main/java/com/google/genai/Models.java @@ -4573,6 +4573,13 @@ ObjectNode toolToMldev(JsonNode fromObject, ObjectNode parentObject, JsonNode ro Common.getValueByPath(fromObject, new String[] {"urlContext"})); } + if (Common.getValueByPath(fromObject, new String[] {"mcpServers"}) != null) { + Common.setValueByPath( + toObject, + new String[] {"mcpServers"}, + Common.getValueByPath(fromObject, new String[] {"mcpServers"})); + } + return toObject; } @@ -4652,6 +4659,10 @@ ObjectNode toolToVertex(JsonNode fromObject, ObjectNode parentObject, JsonNode r Common.getValueByPath(fromObject, new String[] {"urlContext"})); } + if (!Common.isZero(Common.getValueByPath(fromObject, new String[] {"mcpServers"}))) { + throw new IllegalArgumentException("mcpServers parameter is not supported in Vertex AI."); + } + return toObject; } diff --git a/src/main/java/com/google/genai/TokensConverters.java b/src/main/java/com/google/genai/TokensConverters.java index a3fca5225b7..0dbfa1c84a2 100644 --- a/src/main/java/com/google/genai/TokensConverters.java +++ b/src/main/java/com/google/genai/TokensConverters.java @@ -611,6 +611,13 @@ ObjectNode toolToMldev(JsonNode fromObject, ObjectNode parentObject) { Common.getValueByPath(fromObject, new String[] {"urlContext"})); } + if (Common.getValueByPath(fromObject, new String[] {"mcpServers"}) != null) { + Common.setValueByPath( + toObject, + new String[] {"mcpServers"}, + Common.getValueByPath(fromObject, new String[] {"mcpServers"})); + } + return toObject; } } diff --git a/src/main/java/com/google/genai/types/McpServer.java b/src/main/java/com/google/genai/types/McpServer.java new file mode 100644 index 00000000000..7b6bd914b2f --- /dev/null +++ b/src/main/java/com/google/genai/types/McpServer.java @@ -0,0 +1,119 @@ +/* + * Copyright 2025 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 + * + * https://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. + */ + +// Auto-generated code. Do not edit. + +package com.google.genai.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import java.util.Optional; + +/** + * A MCPServer is a server that can be called by the model to perform actions. It is a server that + * implements the MCP protocol. Next ID: 5. This data type is not supported in Vertex AI. + */ +@AutoValue +@JsonDeserialize(builder = McpServer.Builder.class) +public abstract class McpServer extends JsonSerializable { + /** The name of the MCPServer. */ + @JsonProperty("name") + public abstract Optional name(); + + /** A transport that can stream HTTP requests and responses. */ + @JsonProperty("streamableHttpTransport") + public abstract Optional streamableHttpTransport(); + + /** Instantiates a builder for McpServer. */ + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_McpServer.Builder(); + } + + /** Creates a builder with the same values as this instance. */ + public abstract Builder toBuilder(); + + /** Builder for McpServer. */ + @AutoValue.Builder + public abstract static class Builder { + /** For internal usage. Please use `McpServer.builder()` for instantiation. */ + @JsonCreator + private static Builder create() { + return new AutoValue_McpServer.Builder(); + } + + /** + * Setter for name. + * + *

name: The name of the MCPServer. + */ + @JsonProperty("name") + public abstract Builder name(String name); + + @ExcludeFromGeneratedCoverageReport + abstract Builder name(Optional name); + + /** Clears the value of name field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearName() { + return name(Optional.empty()); + } + + /** + * Setter for streamableHttpTransport. + * + *

streamableHttpTransport: A transport that can stream HTTP requests and responses. + */ + @JsonProperty("streamableHttpTransport") + public abstract Builder streamableHttpTransport( + StreamableHttpTransport streamableHttpTransport); + + /** + * Setter for streamableHttpTransport builder. + * + *

streamableHttpTransport: A transport that can stream HTTP requests and responses. + */ + @CanIgnoreReturnValue + public Builder streamableHttpTransport( + StreamableHttpTransport.Builder streamableHttpTransportBuilder) { + return streamableHttpTransport(streamableHttpTransportBuilder.build()); + } + + @ExcludeFromGeneratedCoverageReport + abstract Builder streamableHttpTransport( + Optional streamableHttpTransport); + + /** Clears the value of streamableHttpTransport field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearStreamableHttpTransport() { + return streamableHttpTransport(Optional.empty()); + } + + public abstract McpServer build(); + } + + /** Deserializes a JSON string to a McpServer object. */ + @ExcludeFromGeneratedCoverageReport + public static McpServer fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, McpServer.class); + } +} diff --git a/src/main/java/com/google/genai/types/StreamableHttpTransport.java b/src/main/java/com/google/genai/types/StreamableHttpTransport.java new file mode 100644 index 00000000000..1337e16dffc --- /dev/null +++ b/src/main/java/com/google/genai/types/StreamableHttpTransport.java @@ -0,0 +1,174 @@ +/* + * Copyright 2025 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 + * + * https://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. + */ + +// Auto-generated code. Do not edit. + +package com.google.genai.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import java.time.Duration; +import java.util.Map; +import java.util.Optional; + +/** + * A transport that can stream HTTP requests and responses. Next ID: 6. This data type is not + * supported in Vertex AI. + */ +@AutoValue +@JsonDeserialize(builder = StreamableHttpTransport.Builder.class) +public abstract class StreamableHttpTransport extends JsonSerializable { + /** Optional: Fields for authentication headers, timeouts, etc., if needed. */ + @JsonProperty("headers") + public abstract Optional> headers(); + + /** Timeout for SSE read operations. */ + @JsonProperty("sseReadTimeout") + public abstract Optional sseReadTimeout(); + + /** Whether to close the client session when the transport closes. */ + @JsonProperty("terminateOnClose") + public abstract Optional terminateOnClose(); + + /** HTTP timeout for regular operations. */ + @JsonProperty("timeout") + public abstract Optional timeout(); + + /** The full URL for the MCPServer endpoint. Example: "https://api.example.com/mcp". */ + @JsonProperty("url") + public abstract Optional url(); + + /** Instantiates a builder for StreamableHttpTransport. */ + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_StreamableHttpTransport.Builder(); + } + + /** Creates a builder with the same values as this instance. */ + public abstract Builder toBuilder(); + + /** Builder for StreamableHttpTransport. */ + @AutoValue.Builder + public abstract static class Builder { + /** For internal usage. Please use `StreamableHttpTransport.builder()` for instantiation. */ + @JsonCreator + private static Builder create() { + return new AutoValue_StreamableHttpTransport.Builder(); + } + + /** + * Setter for headers. + * + *

headers: Optional: Fields for authentication headers, timeouts, etc., if needed. + */ + @JsonProperty("headers") + public abstract Builder headers(Map headers); + + @ExcludeFromGeneratedCoverageReport + abstract Builder headers(Optional> headers); + + /** Clears the value of headers field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearHeaders() { + return headers(Optional.empty()); + } + + /** + * Setter for sseReadTimeout. + * + *

sseReadTimeout: Timeout for SSE read operations. + */ + @JsonProperty("sseReadTimeout") + public abstract Builder sseReadTimeout(Duration sseReadTimeout); + + @ExcludeFromGeneratedCoverageReport + abstract Builder sseReadTimeout(Optional sseReadTimeout); + + /** Clears the value of sseReadTimeout field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearSseReadTimeout() { + return sseReadTimeout(Optional.empty()); + } + + /** + * Setter for terminateOnClose. + * + *

terminateOnClose: Whether to close the client session when the transport closes. + */ + @JsonProperty("terminateOnClose") + public abstract Builder terminateOnClose(boolean terminateOnClose); + + @ExcludeFromGeneratedCoverageReport + abstract Builder terminateOnClose(Optional terminateOnClose); + + /** Clears the value of terminateOnClose field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearTerminateOnClose() { + return terminateOnClose(Optional.empty()); + } + + /** + * Setter for timeout. + * + *

timeout: HTTP timeout for regular operations. + */ + @JsonProperty("timeout") + public abstract Builder timeout(Duration timeout); + + @ExcludeFromGeneratedCoverageReport + abstract Builder timeout(Optional timeout); + + /** Clears the value of timeout field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearTimeout() { + return timeout(Optional.empty()); + } + + /** + * Setter for url. + * + *

url: The full URL for the MCPServer endpoint. Example: "https://api.example.com/mcp". + */ + @JsonProperty("url") + public abstract Builder url(String url); + + @ExcludeFromGeneratedCoverageReport + abstract Builder url(Optional url); + + /** Clears the value of url field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearUrl() { + return url(Optional.empty()); + } + + public abstract StreamableHttpTransport build(); + } + + /** Deserializes a JSON string to a StreamableHttpTransport object. */ + @ExcludeFromGeneratedCoverageReport + public static StreamableHttpTransport fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, StreamableHttpTransport.class); + } +} diff --git a/src/main/java/com/google/genai/types/Tool.java b/src/main/java/com/google/genai/types/Tool.java index 82e438cba86..d9e5ab2af41 100644 --- a/src/main/java/com/google/genai/types/Tool.java +++ b/src/main/java/com/google/genai/types/Tool.java @@ -101,6 +101,10 @@ public abstract class Tool extends JsonSerializable { @JsonProperty("urlContext") public abstract Optional urlContext(); + /** Optional. MCP Servers to connect to. This field is not supported in Vertex AI. */ + @JsonProperty("mcpServers") + public abstract Optional> mcpServers(); + /** Instantiates a builder for Tool. */ @ExcludeFromGeneratedCoverageReport public static Builder builder() { @@ -473,6 +477,47 @@ public Builder clearUrlContext() { return urlContext(Optional.empty()); } + /** + * Setter for mcpServers. + * + *

mcpServers: Optional. MCP Servers to connect to. This field is not supported in Vertex AI. + */ + @JsonProperty("mcpServers") + public abstract Builder mcpServers(List mcpServers); + + /** + * Setter for mcpServers. + * + *

mcpServers: Optional. MCP Servers to connect to. This field is not supported in Vertex AI. + */ + @CanIgnoreReturnValue + public Builder mcpServers(McpServer... mcpServers) { + return mcpServers(Arrays.asList(mcpServers)); + } + + /** + * Setter for mcpServers builder. + * + *

mcpServers: Optional. MCP Servers to connect to. This field is not supported in Vertex AI. + */ + @CanIgnoreReturnValue + public Builder mcpServers(McpServer.Builder... mcpServersBuilders) { + return mcpServers( + Arrays.asList(mcpServersBuilders).stream() + .map(McpServer.Builder::build) + .collect(toImmutableList())); + } + + @ExcludeFromGeneratedCoverageReport + abstract Builder mcpServers(Optional> mcpServers); + + /** Clears the value of mcpServers field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearMcpServers() { + return mcpServers(Optional.empty()); + } + public abstract Tool build(); } diff --git a/src/test/java/com/google/genai/AfcUtilTest.java b/src/test/java/com/google/genai/AfcUtilTest.java index 6a5e9992789..a1c0397f87c 100644 --- a/src/test/java/com/google/genai/AfcUtilTest.java +++ b/src/test/java/com/google/genai/AfcUtilTest.java @@ -32,6 +32,7 @@ import com.google.genai.types.GoogleMaps; import com.google.genai.types.GoogleSearch; import com.google.genai.types.GoogleSearchRetrieval; +import com.google.genai.types.McpServer; import com.google.genai.types.Part; import com.google.genai.types.Retrieval; import com.google.genai.types.Schema; @@ -579,4 +580,39 @@ void findAfcIncompatibleToolIndexes_withMixedTools_returnsOnlyIncompatibleIndexe assertEquals(2, result.get(0).intValue()); assertEquals(10, result.get(1).intValue()); } + + @Test + void findAfcIncompatibleToolIndexes_withSercerSideMcpTools_returnsOnlyIncompatibleIndexes() + throws NoSuchMethodException { + // Arrange + Method testMethod1 = AfcUtilTest.class.getMethod("testFunction1", String.class); + GenerateContentConfig config = + GenerateContentConfig.builder() + .tools( + Tool.builder().functions(testMethod1).build(), + Tool.builder().googleSearch(GoogleSearch.builder()).build(), + Tool.builder().functionDeclarations(testFunctionDeclaration1).build(), + Tool.builder().googleSearchRetrieval(GoogleSearchRetrieval.builder()).build(), + Tool.builder() + .mcpServers(ImmutableList.of(McpServer.builder().name("test").build())) + .build(), + Tool.builder().googleMaps(GoogleMaps.builder()).build(), + Tool.builder().urlContext(UrlContext.builder()).build(), + Tool.builder().codeExecution(ToolCodeExecution.builder()).build(), + Tool.builder().computerUse(ComputerUse.builder()).build(), + Tool.builder().enterpriseWebSearch(EnterpriseWebSearch.builder()).build(), + Tool.builder() + .functionDeclarations(testFunctionDeclaration1, testFunctionDeclaration2) + .build()) + .build(); + + // Act + ImmutableList result = AfcUtil.findAfcIncompatibleToolIndexes(config); + + // Assert + assertEquals(3, result.size()); + assertEquals(2, result.get(0).intValue()); + assertEquals(4, result.get(1).intValue()); + assertEquals(10, result.get(2).intValue()); + } } diff --git a/src/test/java/com/google/genai/AsyncModelsTest.java b/src/test/java/com/google/genai/AsyncModelsTest.java index 99fd6f7aa52..09672d002c0 100644 --- a/src/test/java/com/google/genai/AsyncModelsTest.java +++ b/src/test/java/com/google/genai/AsyncModelsTest.java @@ -45,6 +45,7 @@ import com.google.genai.types.ListModelsConfig; import com.google.genai.types.MaskReferenceConfig; import com.google.genai.types.MaskReferenceImage; +import com.google.genai.types.McpServer; import com.google.genai.types.Model; import com.google.genai.types.Part; import com.google.genai.types.PersonGeneration; @@ -53,6 +54,7 @@ import com.google.genai.types.RawReferenceImage; import com.google.genai.types.Retrieval; import com.google.genai.types.SafetyFilterLevel; +import com.google.genai.types.StreamableHttpTransport; import com.google.genai.types.Tool; import com.google.genai.types.ToolCodeExecution; import com.google.genai.types.UpdateModelConfig; @@ -906,4 +908,40 @@ public void testDeleteModelAsync(boolean vertexAI) throws Exception { assertTrue(exception.getCause().getMessage().contains("404")); } } + + @ParameterizedTest + @ValueSource(booleans = {false}) + public void testGenerateContentStream_withServerSideMcpAsync(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, + "tests/models/generate_content_tools/test_server_side_mcp_only_async." + + suffix + + ".json"); + + // Act + GenerateContentConfig config = + GenerateContentConfig.builder() + .tools( + Tool.builder() + .mcpServers( + McpServer.builder() + .name("get_weather") + .streamableHttpTransport( + StreamableHttpTransport.builder() + .url("https://gemini-api-demos.uc.r.appspot.com/mcp") + .headers( + ImmutableMap.of("AUTHORIZATION", "Bearer github_pat_XXXX")) + .build()) + .build()) + .build()) + .build(); + CompletableFuture responseFuture = + client.async.models.generateContent( + "gemini-2.5-flash", "What is the weather like in New York on 02/02/2026?", config); + GenerateContentResponse response = responseFuture.join(); + assertNotNull(response.text()); + } } diff --git a/src/test/java/com/google/genai/ModelsTest.java b/src/test/java/com/google/genai/ModelsTest.java index eb15b29f3e2..3bd8097fefa 100644 --- a/src/test/java/com/google/genai/ModelsTest.java +++ b/src/test/java/com/google/genai/ModelsTest.java @@ -40,13 +40,16 @@ import com.google.genai.types.ListModelsConfig; import com.google.genai.types.MaskReferenceConfig; import com.google.genai.types.MaskReferenceImage; +import com.google.genai.types.McpServer; import com.google.genai.types.Model; import com.google.genai.types.Part; import com.google.genai.types.RawReferenceImage; +import com.google.genai.types.StreamableHttpTransport; import com.google.genai.types.StyleReferenceConfig; import com.google.genai.types.StyleReferenceImage; import com.google.genai.types.SubjectReferenceConfig; import com.google.genai.types.SubjectReferenceImage; +import com.google.genai.types.Tool; import java.net.URL; import java.nio.file.Path; import java.nio.file.Paths; @@ -574,4 +577,87 @@ public void testEditImage_withStyleTransfer(boolean vertexAI) throws Exception { "This method is only supported in the Vertex AI client.", exception.getMessage()); } } + + @ParameterizedTest + @ValueSource(booleans = {false}) + public void testGenerateContent_withServerSideMcp(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, + "tests/models/generate_content_tools/test_server_side_mcp_only." + suffix + ".json"); + + // Act + GenerateContentConfig config = + GenerateContentConfig.builder() + .tools( + Tool.builder() + .mcpServers( + McpServer.builder() + .name("get_weather") + .streamableHttpTransport( + StreamableHttpTransport.builder() + .url("https://gemini-api-demos.uc.r.appspot.com/mcp") + .headers( + ImmutableMap.of("AUTHORIZATION", "Bearer github_pat_XXXX")) + .build()) + .build()) + .build()) + .build(); + GenerateContentResponse response = + client.models.generateContent( + "gemini-2.5-pro", + Content.fromParts( + Part.fromText("What is the weather like in New York (NY) on 02/02/2026?")), + config); + + // Assert + assertNotNull(response.text()); + assertNotNull(response.sdkHttpResponse().get().headers()); + } + + @ParameterizedTest + @ValueSource(booleans = {false}) + public void testGenerateContentStream_withServerSideMcp(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, + "tests/models/generate_content_tools/test_server_side_mcp_only_stream." + + suffix + + ".json"); + + // Act + GenerateContentConfig config = + GenerateContentConfig.builder() + .tools( + Tool.builder() + .mcpServers( + McpServer.builder() + .name("get_weather") + .streamableHttpTransport( + StreamableHttpTransport.builder() + .url("https://gemini-api-demos.uc.r.appspot.com/mcp") + .headers( + ImmutableMap.of("AUTHORIZATION", "Bearer github_pat_XXXX")) + .build()) + .build()) + .build()) + .build(); + ResponseStream responseStream = + client.models.generateContentStream( + "gemini-2.5-pro", "What is the weather like in New York (NY) on 02/02/2026?", config); + + // Assert + int chunks = 0; + for (GenerateContentResponse response : responseStream) { + chunks++; + assertNotNull(response.text()); + assertNotNull(response.sdkHttpResponse().get().headers()); + } + assertTrue(chunks > 2); + assertTrue(responseStream.isConsumed()); + } }