From 8929c9caa95680cc6d56b7ce07c28cad97289aaf Mon Sep 17 00:00:00 2001 From: Stenal P Jolly Date: Tue, 23 Jun 2026 17:33:02 +0530 Subject: [PATCH 1/7] feat: Add local integration verification test with actual MCP Toolbox server --- .gitignore | 3 + example/README.md | 18 +++ .../cloudcode/helloworld/ExampleUsage.java | 6 +- .../e2e/ToolboxActualServerVerifyTest.java | 152 ++++++++++++++++++ src/test/resources/verify_schema.sql | 25 +++ src/test/resources/verify_tools.yaml | 22 +++ 6 files changed, 223 insertions(+), 3 deletions(-) create mode 100644 src/test/java/com/google/cloud/mcp/e2e/ToolboxActualServerVerifyTest.java create mode 100644 src/test/resources/verify_schema.sql create mode 100644 src/test/resources/verify_tools.yaml diff --git a/.gitignore b/.gitignore index afd0bdc..810cc18 100644 --- a/.gitignore +++ b/.gitignore @@ -51,4 +51,7 @@ crash.*.log # Language Specific target/ +src/test/resources/toolbox +src/test/resources/verification.db + diff --git a/example/README.md b/example/README.md index 672a4d5..1e00c38 100644 --- a/example/README.md +++ b/example/README.md @@ -58,3 +58,21 @@ In any case remember to change the `YOUR_TOOLBOX_SERVICE_ENDPOINT` placeholder i mvn clean compile exec:java -Dexec.mainClass="cloudcode.helloworld.ExampleUsage" ``` +## Integration Verification Testing + +To ensure the example remains fully functional and correct as the SDK evolves, the repository includes an automated integration test: `ToolboxActualServerVerifyTest`. + +### Purpose of the Test +This test validates the `ExampleUsage` client flow against the actual `toolbox` server binary using a local SQLite database. It compiles `ExampleUsage.java` dynamically and executes its `main` method, asserting that: +- Server discovery works and returns correct tools schema. +- Parameters can be dynamically queried, validated, and bound. +- Executing SQL queries via MCP Toolbox against SQLite returns the exact expected rows and prices. + +### How to Run the Verification Test Locally +To run this verification test: +1. Ensure the `toolbox` server binary for your architecture is placed at: + `src/test/resources/toolbox` +2. Run: + ```bash + mvn test -Dtest=ToolboxActualServerVerifyTest -Dnet.bytebuddy.experimental=true + ``` diff --git a/example/src/main/java/cloudcode/helloworld/ExampleUsage.java b/example/src/main/java/cloudcode/helloworld/ExampleUsage.java index 504886c..97830d6 100644 --- a/example/src/main/java/cloudcode/helloworld/ExampleUsage.java +++ b/example/src/main/java/cloudcode/helloworld/ExampleUsage.java @@ -32,10 +32,10 @@ public class ExampleUsage { public static void main(String[] args) { // CONFIGURATION - String targetUrl = "YOUR_TOOLBOX_SERVICE_ENDPOINT"; + String targetUrl = System.getProperty("toolbox.url", "YOUR_TOOLBOX_SERVICE_ENDPOINT"); // Match the Service URL if using Cloud Run OIDC - String tokenAudience = targetUrl; + String tokenAudience = System.getProperty("toolbox.tokenAudience", targetUrl); // -------------------------------------------------------------------------------- // AUTHENTICATION SETUP @@ -44,7 +44,7 @@ public static void main(String[] args) { // FOR PRODUCTION (Cloud Run): Comment out the 'keyPath' logic and use ADC directly. // -------------------------------------------------------------------------------- - String keyPath = "YOUR_CREDENTIALS_JSON_FILE_PATH.json"; + String keyPath = System.getProperty("toolbox.keyPath", "YOUR_CREDENTIALS_JSON_FILE_PATH.json"); System.out.println("--- Starting MCP Toolbox Integration Test ---"); System.out.println("Target Server: " + targetUrl); diff --git a/src/test/java/com/google/cloud/mcp/e2e/ToolboxActualServerVerifyTest.java b/src/test/java/com/google/cloud/mcp/e2e/ToolboxActualServerVerifyTest.java new file mode 100644 index 0000000..207e403 --- /dev/null +++ b/src/test/java/com/google/cloud/mcp/e2e/ToolboxActualServerVerifyTest.java @@ -0,0 +1,152 @@ +/* + * 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.cloud.mcp.e2e; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.google.auth.oauth2.GoogleCredentials; +import com.google.auth.oauth2.IdToken; +import com.google.auth.oauth2.IdTokenProvider; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Method; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.ToolProvider; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +public class ToolboxActualServerVerifyTest { + private static Process serverProcess; + private static final int PORT = 8099; + private static final String DB_PATH = "src/test/resources/verification.db"; + + @BeforeAll + public static void startActualServer() throws Exception { + // 1. Clean up any previous DB file + Files.deleteIfExists(Paths.get(DB_PATH)); + + // 2. Initialize local SQLite database + System.out.println("Initializing SQLite database at " + DB_PATH + "..."); + ProcessBuilder pbDb = new ProcessBuilder("/usr/bin/sqlite3", DB_PATH); + pbDb.redirectInput(new File("src/test/resources/verify_schema.sql")); + Process pDb = pbDb.start(); + boolean finished = pDb.waitFor(10, TimeUnit.SECONDS); + if (!finished || pDb.exitValue() != 0) { + throw new RuntimeException("Failed to initialize SQLite database. Exit value: " + pDb.exitValue()); + } + System.out.println("Database initialized."); + + // 3. Start actual MCP Toolbox server + System.out.println("Starting actual MCP Toolbox server on port " + PORT + "..."); + ProcessBuilder pbServer = new ProcessBuilder( + "src/test/resources/toolbox", + "--config", "src/test/resources/verify_tools.yaml", + "--port", String.valueOf(PORT) + ); + pbServer.inheritIO(); + serverProcess = pbServer.start(); + + // Wait a few seconds for server to start up + Thread.sleep(3000); + + if (!serverProcess.isAlive()) { + throw new RuntimeException("Toolbox server process died immediately."); + } + System.out.println("Actual Toolbox server started successfully."); + } + + @AfterAll + public static void stopActualServer() throws Exception { + System.out.println("Stopping actual MCP Toolbox server..."); + if (serverProcess != null) { + serverProcess.destroy(); + if (!serverProcess.waitFor(5, TimeUnit.SECONDS)) { + serverProcess.destroyForcibly(); + } + } + Files.deleteIfExists(Paths.get(DB_PATH)); + System.out.println("Server stopped and database cleaned."); + } + + @Test + public void testActualServerExampleFlow() throws Exception { + // Set System properties to configure ExampleUsage to hit our local port 8099 + System.setProperty("toolbox.url", "http://localhost:" + PORT + "/mcp"); + System.setProperty("toolbox.keyPath", ""); // Empty string to bypass file checking and use ADC + + // 1. Programmatically compile ExampleUsage.java on the fly using test classpath + File sourceFile = new File("example/src/main/java/cloudcode/helloworld/ExampleUsage.java"); + assertTrue(sourceFile.exists(), "ExampleUsage.java does not exist at " + sourceFile.getAbsolutePath()); + + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + assertNotNull(compiler, "System Java compiler is not available. Please run tests using a JDK, not a JRE."); + + StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null); + String classpath = System.getProperty("java.class.path"); + + List optionList = new ArrayList<>(); + optionList.add("-classpath"); + optionList.add(classpath); + optionList.add("-d"); + optionList.add("target/test-classes"); + + Iterable compilationUnits = fileManager.getJavaFileObjectsFromFiles(Arrays.asList(sourceFile)); + boolean compileSuccess = compiler.getTask(null, fileManager, null, optionList, null, compilationUnits).call(); + fileManager.close(); + + assertTrue(compileSuccess, "Compilation of ExampleUsage.java failed."); + System.out.println("ExampleUsage.java compiled successfully."); + + // 2. Load the compiled ExampleUsage class + Class clazz = Class.forName("cloudcode.helloworld.ExampleUsage"); + + // Mock GoogleCredentials statically to return a dummy IdTokenProvider + try (MockedStatic mockedCredentials = Mockito.mockStatic(GoogleCredentials.class)) { + GoogleCredentials mockCreds = Mockito.mock( + GoogleCredentials.class, + Mockito.withSettings().extraInterfaces(IdTokenProvider.class) + ); + IdToken mockToken = Mockito.mock(IdToken.class); + Mockito.when(mockToken.getTokenValue()).thenReturn("dummy-token"); + Mockito.when(((IdTokenProvider) mockCreds).idTokenWithAudience( + Mockito.anyString(), Mockito.anyList() + )).thenReturn(mockToken); + + mockedCredentials.when(GoogleCredentials::getApplicationDefault).thenReturn(mockCreds); + + // Execute the actual example class's main method directly! + System.out.println("Executing ExampleUsage.main..."); + clazz.getMethod("main", String[].class).invoke(null, (Object) new String[0]); + System.out.println("ExampleUsage.main completed successfully."); + } finally { + System.clearProperty("toolbox.url"); + System.clearProperty("toolbox.keyPath"); + } + } +} diff --git a/src/test/resources/verify_schema.sql b/src/test/resources/verify_schema.sql new file mode 100644 index 0000000..103a126 --- /dev/null +++ b/src/test/resources/verify_schema.sql @@ -0,0 +1,25 @@ +CREATE TABLE IF NOT EXISTS apparels ( + id INTEGER PRIMARY KEY, + content TEXT, + uri TEXT, + category TEXT, + sub_category TEXT, + color TEXT, + gender TEXT +); + +INSERT INTO apparels (content, uri, category, sub_category, color, gender) VALUES +('Red T-Shirt', 'http://example.com/red-tshirt', 'apparel', 'tshirt', 'red', 'unisex'), +('Blue Jeans', 'http://example.com/blue-jeans', 'apparel', 'jeans', 'blue', 'unisex'); + +CREATE TABLE IF NOT EXISTS toys ( + id INTEGER PRIMARY KEY, + name TEXT, + description TEXT, + price REAL +); + +INSERT INTO toys (name, description, price) VALUES +('Barbie', 'barbie doll', 19.99), +('Teddy Bear', 'soft toy teddy bear', 14.99), +('Lego Set', 'construction blocks lego', 49.99); diff --git a/src/test/resources/verify_tools.yaml b/src/test/resources/verify_tools.yaml new file mode 100644 index 0000000..b9d18b7 --- /dev/null +++ b/src/test/resources/verify_tools.yaml @@ -0,0 +1,22 @@ +kind: source +name: local-sqlite +type: sqlite +database: "/Users/stenalpjolly/github/Google/opensource/mcp-toolbox-sdk-java/src/test/resources/verification.db" +--- +kind: tool +name: get-retail-facet-filters +type: sqlite-sql +source: local-sqlite +description: "Get the list of facet filter values from the retail dataset." +statement: "SELECT id, content, uri, category, sub_category, color, gender FROM apparels;" +--- +kind: tool +name: get-toy-price +type: sqlite-sql +source: local-sqlite +description: "Get the price of a toy based on a description." +parameters: + - name: description + type: string + description: "A description of the toy to search for." +statement: "SELECT price FROM toys WHERE lower(description) LIKE '%' || lower(?) || '%' LIMIT 1;" From 71e65570537e12777a079c27be7df9810b8c0226 Mon Sep 17 00:00:00 2001 From: Stenal P Jolly Date: Tue, 23 Jun 2026 17:34:57 +0530 Subject: [PATCH 2/7] feat: Add BulkToolsetUsage example showcasing advanced bulk loading and pre-binding API --- .../helloworld/BulkToolsetUsage.java | 132 ++++++++++++++++++ .../e2e/ToolboxActualServerVerifyTest.java | 53 +++++++ 2 files changed, 185 insertions(+) create mode 100644 example/src/main/java/cloudcode/helloworld/BulkToolsetUsage.java diff --git a/example/src/main/java/cloudcode/helloworld/BulkToolsetUsage.java b/example/src/main/java/cloudcode/helloworld/BulkToolsetUsage.java new file mode 100644 index 0000000..68bfa8f --- /dev/null +++ b/example/src/main/java/cloudcode/helloworld/BulkToolsetUsage.java @@ -0,0 +1,132 @@ +/* + * 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 cloudcode.helloworld; + +import com.google.auth.oauth2.GoogleCredentials; +import com.google.auth.oauth2.IdTokenProvider; +import com.google.cloud.mcp.McpToolboxClient; +import com.google.cloud.mcp.AuthTokenGetter; +import com.google.cloud.mcp.Tool; +import java.io.FileInputStream; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +/** + * Example demonstrating how to use the advanced Bulk Toolset Loading API. + * This showcases how to pre-bind parameters and credentials to multiple tools + * at startup, retrieving a map of fully configured and ready-to-use Tool objects. + */ +public class BulkToolsetUsage { + public static void main(String[] args) { + // CONFIGURATION + String targetUrl = System.getProperty("toolbox.url", "YOUR_TOOLBOX_SERVICE_ENDPOINT"); + String tokenAudience = System.getProperty("toolbox.tokenAudience", targetUrl); + String keyPath = System.getProperty("toolbox.keyPath", "YOUR_CREDENTIALS_JSON_FILE_PATH.json"); + + System.out.println("--- Starting MCP Toolbox Bulk Toolset Example ---"); + System.out.println("Target Server: " + targetUrl); + + try { + System.out.println(" [Init] Fetching ID Token..."); + GoogleCredentials credentials; + if (keyPath != null && !keyPath.isEmpty() && !keyPath.contains("YOUR_CREDENTIALS_JSON_FILE_PATH")) { + System.out.println(" [Auth] Using Service Account Key File: " + keyPath); + credentials = GoogleCredentials.fromStream(new FileInputStream(keyPath)); + } else { + System.out.println(" [Auth] Using Application Default Credentials (ADC)"); + credentials = GoogleCredentials.getApplicationDefault(); + } + + if (!(credentials instanceof IdTokenProvider)) { + throw new RuntimeException("Loaded credentials do not support ID Tokens."); + } + + String idToken = ((IdTokenProvider) credentials).idTokenWithAudience(tokenAudience, Collections.emptyList()).getTokenValue(); + + // 1. Initialize Client + McpToolboxClient client = McpToolboxClient.builder() + .baseUrl(targetUrl) + .apiKey(idToken) + .build(); + + // 2. Prepare Bulk Parameter Bindings + // Map structure: Tool Name -> (Parameter Name -> Parameter Value) + Map> paramBinds = new HashMap<>(); + + Map toyParams = new HashMap<>(); + toyParams.put("description", "teddy bear"); // Pre-bind "description" to "teddy bear" for "get-toy-price" + paramBinds.put("get-toy-price", toyParams); + + // 3. Prepare Bulk Auth Bindings + // Map structure: Tool Name -> (Service Name -> Auth Token Getter) + Map> authBinds = new HashMap<>(); + + Map toyAuth = new HashMap<>(); + AuthTokenGetter toolAuthGetter = () -> CompletableFuture.completedFuture("dummy-auth-token-from-provider"); + toyAuth.put("google_auth", toolAuthGetter); // Pre-bind "google_auth" service for "get-toy-price" + authBinds.put("get-toy-price", toyAuth); + + // 4. Load the entire toolset and apply all bindings at once + System.out.println(" [Init] Loading and binding toolset in bulk..."); + client.loadToolset(null, paramBinds, authBinds, true) + .thenAccept(boundTools -> { + System.out.println("\n[1] Toolset loaded in bulk. Total tools bound: " + boundTools.size()); + + // Let's verify and execute "get-retail-facet-filters" (simple tool, no pre-bindings) + Tool filterTool = boundTools.get("get-retail-facet-filters"); + if (filterTool != null) { + System.out.println("\n[2] Executing 'get-retail-facet-filters'..."); + filterTool.execute(Map.of()) + .thenAccept(result -> { + System.out.println(" -> Result: " + result.content().get(0).text()); + }).join(); + } else { + System.err.println("Tool 'get-retail-facet-filters' not found in toolset!"); + } + + // Let's verify and execute "get-toy-price" (pre-bound to "teddy bear") + Tool priceTool = boundTools.get("get-toy-price"); + if (priceTool != null) { + System.out.println("\n[3] Executing 'get-toy-price' (using pre-bound parameter 'teddy bear')..."); + // We run execute with empty map, so it falls back to the bound 'teddy bear' + priceTool.execute(Map.of()) + .thenAccept(result -> { + System.out.println(" -> Result (Teddy Bear): " + result.content().get(0).text()); + }).join(); + + System.out.println("\n[4] Attempting to override parameter at runtime to 'barbie' (should be ignored/overridden by bound value)..."); + // We pass description at runtime but it will be overridden by bound "teddy bear" + priceTool.execute(Map.of("description", "barbie")) + .thenAccept(result -> { + System.out.println(" -> Result (Actual): " + result.content().get(0).text() + " (Expect price for teddy bear: 14.99)"); + }).join(); + } else { + System.err.println("Tool 'get-toy-price' not found in toolset!"); + } + }) + .join(); + + } catch (Exception e) { + e.printStackTrace(); + System.exit(1); + } + System.out.println("\n--- Bulk Toolset Example Complete ---"); + System.exit(0); + } +} diff --git a/src/test/java/com/google/cloud/mcp/e2e/ToolboxActualServerVerifyTest.java b/src/test/java/com/google/cloud/mcp/e2e/ToolboxActualServerVerifyTest.java index 207e403..60ad85c 100644 --- a/src/test/java/com/google/cloud/mcp/e2e/ToolboxActualServerVerifyTest.java +++ b/src/test/java/com/google/cloud/mcp/e2e/ToolboxActualServerVerifyTest.java @@ -149,4 +149,57 @@ public void testActualServerExampleFlow() throws Exception { System.clearProperty("toolbox.keyPath"); } } + + @Test + public void testBulkToolsetExampleFlow() throws Exception { + // Set System properties to configure BulkToolsetUsage to hit our local port 8099 + System.setProperty("toolbox.url", "http://localhost:" + PORT + "/mcp"); + System.setProperty("toolbox.keyPath", ""); + + // 1. Programmatically compile BulkToolsetUsage.java on the fly + File sourceFile = new File("example/src/main/java/cloudcode/helloworld/BulkToolsetUsage.java"); + assertTrue(sourceFile.exists(), "BulkToolsetUsage.java does not exist"); + + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null); + String classpath = System.getProperty("java.class.path"); + + List optionList = new ArrayList<>(); + optionList.add("-classpath"); + optionList.add(classpath); + optionList.add("-d"); + optionList.add("target/test-classes"); + + Iterable compilationUnits = fileManager.getJavaFileObjectsFromFiles(Arrays.asList(sourceFile)); + boolean compileSuccess = compiler.getTask(null, fileManager, null, optionList, null, compilationUnits).call(); + fileManager.close(); + + assertTrue(compileSuccess, "Compilation of BulkToolsetUsage.java failed."); + + // 2. Load the compiled BulkToolsetUsage class + Class clazz = Class.forName("cloudcode.helloworld.BulkToolsetUsage"); + + // Mock GoogleCredentials statically to return a dummy IdTokenProvider + try (MockedStatic mockedCredentials = Mockito.mockStatic(GoogleCredentials.class)) { + GoogleCredentials mockCreds = Mockito.mock( + GoogleCredentials.class, + Mockito.withSettings().extraInterfaces(IdTokenProvider.class) + ); + IdToken mockToken = Mockito.mock(IdToken.class); + Mockito.when(mockToken.getTokenValue()).thenReturn("dummy-token"); + Mockito.when(((IdTokenProvider) mockCreds).idTokenWithAudience( + Mockito.anyString(), Mockito.anyList() + )).thenReturn(mockToken); + + mockedCredentials.when(GoogleCredentials::getApplicationDefault).thenReturn(mockCreds); + + // Execute the actual example class's main method directly! + System.out.println("Executing BulkToolsetUsage.main..."); + clazz.getMethod("main", String[].class).invoke(null, (Object) new String[0]); + System.out.println("BulkToolsetUsage.main completed successfully."); + } finally { + System.clearProperty("toolbox.url"); + System.clearProperty("toolbox.keyPath"); + } + } } From cff27d16d216d7f694f8e1c36e2495194fbccc68 Mon Sep 17 00:00:00 2001 From: Stenal P Jolly Date: Tue, 23 Jun 2026 17:37:13 +0530 Subject: [PATCH 3/7] style: Format Java example and verify test source files using google-java-format --- .../helloworld/BulkToolsetUsage.java | 228 ++++++------ .../cloudcode/helloworld/ExampleUsage.java | 285 ++++++++------- .../e2e/ToolboxActualServerVerifyTest.java | 326 +++++++++--------- 3 files changed, 451 insertions(+), 388 deletions(-) diff --git a/example/src/main/java/cloudcode/helloworld/BulkToolsetUsage.java b/example/src/main/java/cloudcode/helloworld/BulkToolsetUsage.java index 68bfa8f..28f9ffa 100644 --- a/example/src/main/java/cloudcode/helloworld/BulkToolsetUsage.java +++ b/example/src/main/java/cloudcode/helloworld/BulkToolsetUsage.java @@ -18,8 +18,8 @@ import com.google.auth.oauth2.GoogleCredentials; import com.google.auth.oauth2.IdTokenProvider; -import com.google.cloud.mcp.McpToolboxClient; import com.google.cloud.mcp.AuthTokenGetter; +import com.google.cloud.mcp.McpToolboxClient; import com.google.cloud.mcp.Tool; import java.io.FileInputStream; import java.util.Collections; @@ -28,105 +28,135 @@ import java.util.concurrent.CompletableFuture; /** - * Example demonstrating how to use the advanced Bulk Toolset Loading API. - * This showcases how to pre-bind parameters and credentials to multiple tools - * at startup, retrieving a map of fully configured and ready-to-use Tool objects. + * Example demonstrating how to use the advanced Bulk Toolset Loading API. This showcases how to + * pre-bind parameters and credentials to multiple tools at startup, retrieving a map of fully + * configured and ready-to-use Tool objects. */ public class BulkToolsetUsage { - public static void main(String[] args) { - // CONFIGURATION - String targetUrl = System.getProperty("toolbox.url", "YOUR_TOOLBOX_SERVICE_ENDPOINT"); - String tokenAudience = System.getProperty("toolbox.tokenAudience", targetUrl); - String keyPath = System.getProperty("toolbox.keyPath", "YOUR_CREDENTIALS_JSON_FILE_PATH.json"); - - System.out.println("--- Starting MCP Toolbox Bulk Toolset Example ---"); - System.out.println("Target Server: " + targetUrl); - - try { - System.out.println(" [Init] Fetching ID Token..."); - GoogleCredentials credentials; - if (keyPath != null && !keyPath.isEmpty() && !keyPath.contains("YOUR_CREDENTIALS_JSON_FILE_PATH")) { - System.out.println(" [Auth] Using Service Account Key File: " + keyPath); - credentials = GoogleCredentials.fromStream(new FileInputStream(keyPath)); - } else { - System.out.println(" [Auth] Using Application Default Credentials (ADC)"); - credentials = GoogleCredentials.getApplicationDefault(); - } - - if (!(credentials instanceof IdTokenProvider)) { - throw new RuntimeException("Loaded credentials do not support ID Tokens."); - } - - String idToken = ((IdTokenProvider) credentials).idTokenWithAudience(tokenAudience, Collections.emptyList()).getTokenValue(); - - // 1. Initialize Client - McpToolboxClient client = McpToolboxClient.builder() - .baseUrl(targetUrl) - .apiKey(idToken) - .build(); - - // 2. Prepare Bulk Parameter Bindings - // Map structure: Tool Name -> (Parameter Name -> Parameter Value) - Map> paramBinds = new HashMap<>(); - - Map toyParams = new HashMap<>(); - toyParams.put("description", "teddy bear"); // Pre-bind "description" to "teddy bear" for "get-toy-price" - paramBinds.put("get-toy-price", toyParams); - - // 3. Prepare Bulk Auth Bindings - // Map structure: Tool Name -> (Service Name -> Auth Token Getter) - Map> authBinds = new HashMap<>(); - - Map toyAuth = new HashMap<>(); - AuthTokenGetter toolAuthGetter = () -> CompletableFuture.completedFuture("dummy-auth-token-from-provider"); - toyAuth.put("google_auth", toolAuthGetter); // Pre-bind "google_auth" service for "get-toy-price" - authBinds.put("get-toy-price", toyAuth); - - // 4. Load the entire toolset and apply all bindings at once - System.out.println(" [Init] Loading and binding toolset in bulk..."); - client.loadToolset(null, paramBinds, authBinds, true) - .thenAccept(boundTools -> { - System.out.println("\n[1] Toolset loaded in bulk. Total tools bound: " + boundTools.size()); - - // Let's verify and execute "get-retail-facet-filters" (simple tool, no pre-bindings) - Tool filterTool = boundTools.get("get-retail-facet-filters"); - if (filterTool != null) { - System.out.println("\n[2] Executing 'get-retail-facet-filters'..."); - filterTool.execute(Map.of()) - .thenAccept(result -> { - System.out.println(" -> Result: " + result.content().get(0).text()); - }).join(); - } else { - System.err.println("Tool 'get-retail-facet-filters' not found in toolset!"); - } - - // Let's verify and execute "get-toy-price" (pre-bound to "teddy bear") - Tool priceTool = boundTools.get("get-toy-price"); - if (priceTool != null) { - System.out.println("\n[3] Executing 'get-toy-price' (using pre-bound parameter 'teddy bear')..."); - // We run execute with empty map, so it falls back to the bound 'teddy bear' - priceTool.execute(Map.of()) - .thenAccept(result -> { - System.out.println(" -> Result (Teddy Bear): " + result.content().get(0).text()); - }).join(); - - System.out.println("\n[4] Attempting to override parameter at runtime to 'barbie' (should be ignored/overridden by bound value)..."); - // We pass description at runtime but it will be overridden by bound "teddy bear" - priceTool.execute(Map.of("description", "barbie")) - .thenAccept(result -> { - System.out.println(" -> Result (Actual): " + result.content().get(0).text() + " (Expect price for teddy bear: 14.99)"); - }).join(); - } else { - System.err.println("Tool 'get-toy-price' not found in toolset!"); - } - }) - .join(); - - } catch (Exception e) { - e.printStackTrace(); - System.exit(1); - } - System.out.println("\n--- Bulk Toolset Example Complete ---"); - System.exit(0); + public static void main(String[] args) { + // CONFIGURATION + String targetUrl = System.getProperty("toolbox.url", "YOUR_TOOLBOX_SERVICE_ENDPOINT"); + String tokenAudience = System.getProperty("toolbox.tokenAudience", targetUrl); + String keyPath = System.getProperty("toolbox.keyPath", "YOUR_CREDENTIALS_JSON_FILE_PATH.json"); + + System.out.println("--- Starting MCP Toolbox Bulk Toolset Example ---"); + System.out.println("Target Server: " + targetUrl); + + try { + System.out.println(" [Init] Fetching ID Token..."); + GoogleCredentials credentials; + if (keyPath != null + && !keyPath.isEmpty() + && !keyPath.contains("YOUR_CREDENTIALS_JSON_FILE_PATH")) { + System.out.println(" [Auth] Using Service Account Key File: " + keyPath); + credentials = GoogleCredentials.fromStream(new FileInputStream(keyPath)); + } else { + System.out.println(" [Auth] Using Application Default Credentials (ADC)"); + credentials = GoogleCredentials.getApplicationDefault(); + } + + if (!(credentials instanceof IdTokenProvider)) { + throw new RuntimeException("Loaded credentials do not support ID Tokens."); + } + + String idToken = + ((IdTokenProvider) credentials) + .idTokenWithAudience(tokenAudience, Collections.emptyList()) + .getTokenValue(); + + // 1. Initialize Client + McpToolboxClient client = + McpToolboxClient.builder().baseUrl(targetUrl).apiKey(idToken).build(); + + // 2. Prepare Bulk Parameter Bindings + // Map structure: Tool Name -> (Parameter Name -> Parameter Value) + Map> paramBinds = new HashMap<>(); + + Map toyParams = new HashMap<>(); + toyParams.put( + "description", + "teddy bear"); // Pre-bind "description" to "teddy bear" for "get-toy-price" + paramBinds.put("get-toy-price", toyParams); + + // 3. Prepare Bulk Auth Bindings + // Map structure: Tool Name -> (Service Name -> Auth Token Getter) + Map> authBinds = new HashMap<>(); + + Map toyAuth = new HashMap<>(); + AuthTokenGetter toolAuthGetter = + () -> CompletableFuture.completedFuture("dummy-auth-token-from-provider"); + toyAuth.put( + "google_auth", toolAuthGetter); // Pre-bind "google_auth" service for "get-toy-price" + authBinds.put("get-toy-price", toyAuth); + + // 4. Load the entire toolset and apply all bindings at once + System.out.println(" [Init] Loading and binding toolset in bulk..."); + client + .loadToolset(null, paramBinds, authBinds, true) + .thenAccept( + boundTools -> { + System.out.println( + "\n[1] Toolset loaded in bulk. Total tools bound: " + boundTools.size()); + + // Let's verify and execute "get-retail-facet-filters" (simple tool, no + // pre-bindings) + Tool filterTool = boundTools.get("get-retail-facet-filters"); + if (filterTool != null) { + System.out.println("\n[2] Executing 'get-retail-facet-filters'..."); + filterTool + .execute(Map.of()) + .thenAccept( + result -> { + System.out.println(" -> Result: " + result.content().get(0).text()); + }) + .join(); + } else { + System.err.println("Tool 'get-retail-facet-filters' not found in toolset!"); + } + + // Let's verify and execute "get-toy-price" (pre-bound to "teddy bear") + Tool priceTool = boundTools.get("get-toy-price"); + if (priceTool != null) { + System.out.println( + "\n" + + "[3] Executing 'get-toy-price' (using pre-bound parameter 'teddy" + + " bear')..."); + // We run execute with empty map, so it falls back to the bound 'teddy bear' + priceTool + .execute(Map.of()) + .thenAccept( + result -> { + System.out.println( + " -> Result (Teddy Bear): " + result.content().get(0).text()); + }) + .join(); + + System.out.println( + "\n" + + "[4] Attempting to override parameter at runtime to 'barbie' (should be" + + " ignored/overridden by bound value)..."); + // We pass description at runtime but it will be overridden by bound "teddy bear" + priceTool + .execute(Map.of("description", "barbie")) + .thenAccept( + result -> { + System.out.println( + " -> Result (Actual): " + + result.content().get(0).text() + + " (Expect price for teddy bear: 14.99)"); + }) + .join(); + } else { + System.err.println("Tool 'get-toy-price' not found in toolset!"); + } + }) + .join(); + + } catch (Exception e) { + e.printStackTrace(); + System.exit(1); } + System.out.println("\n--- Bulk Toolset Example Complete ---"); + System.exit(0); + } } diff --git a/example/src/main/java/cloudcode/helloworld/ExampleUsage.java b/example/src/main/java/cloudcode/helloworld/ExampleUsage.java index 97830d6..0935b4b 100644 --- a/example/src/main/java/cloudcode/helloworld/ExampleUsage.java +++ b/example/src/main/java/cloudcode/helloworld/ExampleUsage.java @@ -16,145 +16,166 @@ package cloudcode.helloworld; -import java.util.Map; -import java.util.Collections; -import java.util.concurrent.CompletableFuture; -import java.io.FileInputStream; -import com.google.cloud.mcp.McpToolboxClient; -import com.google.cloud.mcp.AuthTokenGetter; import com.google.auth.oauth2.GoogleCredentials; import com.google.auth.oauth2.IdTokenProvider; +import com.google.cloud.mcp.AuthTokenGetter; +import com.google.cloud.mcp.McpToolboxClient; +import java.io.FileInputStream; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.CompletableFuture; /** - * Sample Application to demostrate the usage of the MCP Toolbox Java SDK. - * Covers: Global Auth, Parameterized Auth, Discovery, Simple Tool, Authenticated Tool, Parameter Binding. + * Sample Application to demostrate the usage of the MCP Toolbox Java SDK. Covers: Global Auth, + * Parameterized Auth, Discovery, Simple Tool, Authenticated Tool, Parameter Binding. */ public class ExampleUsage { - public static void main(String[] args) { - // CONFIGURATION - String targetUrl = System.getProperty("toolbox.url", "YOUR_TOOLBOX_SERVICE_ENDPOINT"); - - // Match the Service URL if using Cloud Run OIDC - String tokenAudience = System.getProperty("toolbox.tokenAudience", targetUrl); - - // -------------------------------------------------------------------------------- - // AUTHENTICATION SETUP - // -------------------------------------------------------------------------------- - // FOR LOCAL DEVELOPMENT: Use a Service Account Key JSON file. - // FOR PRODUCTION (Cloud Run): Comment out the 'keyPath' logic and use ADC directly. - // -------------------------------------------------------------------------------- - - String keyPath = System.getProperty("toolbox.keyPath", "YOUR_CREDENTIALS_JSON_FILE_PATH.json"); - - System.out.println("--- Starting MCP Toolbox Integration Test ---"); - System.out.println("Target Server: " + targetUrl); - -try { - System.out.println(" [Init] Fetching ID Token..."); - - GoogleCredentials credentials; - - // --- OPTION A: LOCAL DEV (Explicit Key File) --- - if (keyPath != null && !keyPath.isEmpty()) { - System.out.println(" [Auth] Using Service Account Key File: " + keyPath); - credentials = GoogleCredentials.fromStream(new FileInputStream(keyPath)); - } - // --- OPTION B: PRODUCTION (ADC) --- - else { - System.out.println(" [Auth] Using Application Default Credentials (ADC)"); - credentials = GoogleCredentials.getApplicationDefault(); - } - - if (!(credentials instanceof IdTokenProvider)) { - throw new RuntimeException("Loaded credentials do not support ID Tokens."); - } - - // Generate Token for the specified Audience - String idToken = ((IdTokenProvider) credentials).idTokenWithAudience(tokenAudience, Collections.emptyList()).getTokenValue(); - System.out.println(" [Debug] Token Generated."); - - // Initialize Client with Global Auth (Applies to ALL calls - Gate 1) - McpToolboxClient client = McpToolboxClient.builder() - .baseUrl(targetUrl) - .apiKey(idToken) - .build(); - - // STEP 1: TEST DISCOVERY METHODS - client.listTools() - .thenCompose(tools -> { - System.out.println("\n[1] listTools(): Success. Found " + tools.size() + " tools."); - return client.loadToolset(); - }) - .thenCompose(tools -> { - System.out.println("[2] loadToolset() (Alias): Success."); - return client.loadToolset("retail") - .handle((res, ex) -> { - if (ex == null) System.out.println("[3] loadToolset('retail'): Found " + res.size() + " tools."); - else System.out.println("[3] loadToolset('retail'): Skipped (Not configured on server)."); - return null; + public static void main(String[] args) { + // CONFIGURATION + String targetUrl = System.getProperty("toolbox.url", "YOUR_TOOLBOX_SERVICE_ENDPOINT"); + + // Match the Service URL if using Cloud Run OIDC + String tokenAudience = System.getProperty("toolbox.tokenAudience", targetUrl); + + // -------------------------------------------------------------------------------- + // AUTHENTICATION SETUP + // -------------------------------------------------------------------------------- + // FOR LOCAL DEVELOPMENT: Use a Service Account Key JSON file. + // FOR PRODUCTION (Cloud Run): Comment out the 'keyPath' logic and use ADC directly. + // -------------------------------------------------------------------------------- + + String keyPath = System.getProperty("toolbox.keyPath", "YOUR_CREDENTIALS_JSON_FILE_PATH.json"); + + System.out.println("--- Starting MCP Toolbox Integration Test ---"); + System.out.println("Target Server: " + targetUrl); + + try { + System.out.println(" [Init] Fetching ID Token..."); + + GoogleCredentials credentials; + + // --- OPTION A: LOCAL DEV (Explicit Key File) --- + if (keyPath != null && !keyPath.isEmpty()) { + System.out.println(" [Auth] Using Service Account Key File: " + keyPath); + credentials = GoogleCredentials.fromStream(new FileInputStream(keyPath)); + } + // --- OPTION B: PRODUCTION (ADC) --- + else { + System.out.println(" [Auth] Using Application Default Credentials (ADC)"); + credentials = GoogleCredentials.getApplicationDefault(); + } + + if (!(credentials instanceof IdTokenProvider)) { + throw new RuntimeException("Loaded credentials do not support ID Tokens."); + } + + // Generate Token for the specified Audience + String idToken = + ((IdTokenProvider) credentials) + .idTokenWithAudience(tokenAudience, Collections.emptyList()) + .getTokenValue(); + System.out.println(" [Debug] Token Generated."); + + // Initialize Client with Global Auth (Applies to ALL calls - Gate 1) + McpToolboxClient client = + McpToolboxClient.builder().baseUrl(targetUrl).apiKey(idToken).build(); + + // STEP 1: TEST DISCOVERY METHODS + client + .listTools() + .thenCompose( + tools -> { + System.out.println("\n[1] listTools(): Success. Found " + tools.size() + " tools."); + return client.loadToolset(); + }) + .thenCompose( + tools -> { + System.out.println("[2] loadToolset() (Alias): Success."); + return client + .loadToolset("retail") + .handle( + (res, ex) -> { + if (ex == null) + System.out.println( + "[3] loadToolset('retail'): Found " + res.size() + " tools."); + else + System.out.println( + "[3] loadToolset('retail'): Skipped (Not configured on server)."); + return null; }); - }) - .thenCompose(ignore -> { - - // STEP 2: INVOKE TOOL WITHOUT EXTRA AUTH - System.out.println("\n[4] Testing Simple Tool: 'get-retail-facet-filters'..."); - return client.invokeTool("get-retail-facet-filters", Map.of()); - }) - .thenCompose(result -> { - System.out.println(" -> Result: " + (result.content() != null ? "Received Data" : "Empty")); - - // STEP 3: INVOKE TOOL WITH AUTHENTICATED PARAMETERS - System.out.println("\n[5] Testing Authenticated Tool: 'get-toy-price'..."); - - // Define the getter for the 'google_auth' service - AuthTokenGetter toolAuthGetter = () -> CompletableFuture.completedFuture(idToken); - - // Load using the sophisticated overload - return client.loadTool("get-toy-price", Map.of("google_auth", toolAuthGetter)); - }) - .thenCompose(tool -> { - System.out.println(" -> Loaded Tool: " + tool.definition().description()); - - // STEP 4: TEST BINDING PARAMETERS SEQUENTIALLY - System.out.println("\n[A] Executing UNBOUND (Runtime arg: 'barbie')..."); - - return tool.execute(Map.of("description", "barbie")) - .thenCompose(result1 -> { - if (result1.content() != null && !result1.content().isEmpty()) { - System.out - .println(" -> Result (Unbound): " + result1.content().get(0).text()); - } - - // NOW bind the parameter - System.out.println("\n[B] Binding 'description' to 'soft toy'..."); - tool.bindParam("description", "soft toy"); - - System.out.println(" -> Executing BOUND (Runtime arg: 'barbie' - should be IGNORED)..."); - // We pass 'barbie', but expecting 'soft toy' price because of binding override - return tool.execute(Map.of("description", "barbie")); + }) + .thenCompose( + ignore -> { + + // STEP 2: INVOKE TOOL WITHOUT EXTRA AUTH + System.out.println("\n[4] Testing Simple Tool: 'get-retail-facet-filters'..."); + return client.invokeTool("get-retail-facet-filters", Map.of()); + }) + .thenCompose( + result -> { + System.out.println( + " -> Result: " + (result.content() != null ? "Received Data" : "Empty")); + + // STEP 3: INVOKE TOOL WITH AUTHENTICATED PARAMETERS + System.out.println("\n[5] Testing Authenticated Tool: 'get-toy-price'..."); + + // Define the getter for the 'google_auth' service + AuthTokenGetter toolAuthGetter = () -> CompletableFuture.completedFuture(idToken); + + // Load using the sophisticated overload + return client.loadTool("get-toy-price", Map.of("google_auth", toolAuthGetter)); + }) + .thenCompose( + tool -> { + System.out.println(" -> Loaded Tool: " + tool.definition().description()); + + // STEP 4: TEST BINDING PARAMETERS SEQUENTIALLY + System.out.println("\n[A] Executing UNBOUND (Runtime arg: 'barbie')..."); + + return tool.execute(Map.of("description", "barbie")) + .thenCompose( + result1 -> { + if (result1.content() != null && !result1.content().isEmpty()) { + System.out.println( + " -> Result (Unbound): " + result1.content().get(0).text()); + } + + // NOW bind the parameter + System.out.println("\n[B] Binding 'description' to 'soft toy'..."); + tool.bindParam("description", "soft toy"); + + System.out.println( + " -> Executing BOUND (Runtime arg: 'barbie' - should be" + + " IGNORED)..."); + // We pass 'barbie', but expecting 'soft toy' price because of binding + // override + return tool.execute(Map.of("description", "barbie")); }); - }) - .thenAccept(result -> { - System.out.println("\n[6] Final Result (Bound):"); - if (result.isError()) { - System.err.println("Tool execution failed: " + result.content().get(0).text()); - } else if (result.content() != null && !result.content().isEmpty()) { - String output = result.content().get(0).text(); - System.out.println(" " + output.substring(0, Math.min(output.length(), 200)) + "..."); - } else { - System.out.println(" Empty Response"); - } - }) - .exceptionally(ex -> { - System.err.println("\n!!! TEST FAILED !!!"); - ex.printStackTrace(); - return null; - }) - .join(); - - } catch (Exception e) { - e.printStackTrace(); - } - System.out.println("\n--- Test Suite Complete ---"); + }) + .thenAccept( + result -> { + System.out.println("\n[6] Final Result (Bound):"); + if (result.isError()) { + System.err.println("Tool execution failed: " + result.content().get(0).text()); + } else if (result.content() != null && !result.content().isEmpty()) { + String output = result.content().get(0).text(); + System.out.println( + " " + output.substring(0, Math.min(output.length(), 200)) + "..."); + } else { + System.out.println(" Empty Response"); + } + }) + .exceptionally( + ex -> { + System.err.println("\n!!! TEST FAILED !!!"); + ex.printStackTrace(); + return null; + }) + .join(); + + } catch (Exception e) { + e.printStackTrace(); } + System.out.println("\n--- Test Suite Complete ---"); + } } diff --git a/src/test/java/com/google/cloud/mcp/e2e/ToolboxActualServerVerifyTest.java b/src/test/java/com/google/cloud/mcp/e2e/ToolboxActualServerVerifyTest.java index 60ad85c..be9a64d 100644 --- a/src/test/java/com/google/cloud/mcp/e2e/ToolboxActualServerVerifyTest.java +++ b/src/test/java/com/google/cloud/mcp/e2e/ToolboxActualServerVerifyTest.java @@ -23,8 +23,6 @@ import com.google.auth.oauth2.IdToken; import com.google.auth.oauth2.IdTokenProvider; import java.io.File; -import java.io.IOException; -import java.lang.reflect.Method; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; @@ -42,164 +40,178 @@ import org.mockito.Mockito; public class ToolboxActualServerVerifyTest { - private static Process serverProcess; - private static final int PORT = 8099; - private static final String DB_PATH = "src/test/resources/verification.db"; - - @BeforeAll - public static void startActualServer() throws Exception { - // 1. Clean up any previous DB file - Files.deleteIfExists(Paths.get(DB_PATH)); - - // 2. Initialize local SQLite database - System.out.println("Initializing SQLite database at " + DB_PATH + "..."); - ProcessBuilder pbDb = new ProcessBuilder("/usr/bin/sqlite3", DB_PATH); - pbDb.redirectInput(new File("src/test/resources/verify_schema.sql")); - Process pDb = pbDb.start(); - boolean finished = pDb.waitFor(10, TimeUnit.SECONDS); - if (!finished || pDb.exitValue() != 0) { - throw new RuntimeException("Failed to initialize SQLite database. Exit value: " + pDb.exitValue()); - } - System.out.println("Database initialized."); - - // 3. Start actual MCP Toolbox server - System.out.println("Starting actual MCP Toolbox server on port " + PORT + "..."); - ProcessBuilder pbServer = new ProcessBuilder( - "src/test/resources/toolbox", - "--config", "src/test/resources/verify_tools.yaml", - "--port", String.valueOf(PORT) - ); - pbServer.inheritIO(); - serverProcess = pbServer.start(); - - // Wait a few seconds for server to start up - Thread.sleep(3000); - - if (!serverProcess.isAlive()) { - throw new RuntimeException("Toolbox server process died immediately."); - } - System.out.println("Actual Toolbox server started successfully."); + private static Process serverProcess; + private static final int PORT = 8099; + private static final String DB_PATH = "src/test/resources/verification.db"; + + @BeforeAll + public static void startActualServer() throws Exception { + // 1. Clean up any previous DB file + Files.deleteIfExists(Paths.get(DB_PATH)); + + // 2. Initialize local SQLite database + System.out.println("Initializing SQLite database at " + DB_PATH + "..."); + ProcessBuilder pbDb = new ProcessBuilder("/usr/bin/sqlite3", DB_PATH); + pbDb.redirectInput(new File("src/test/resources/verify_schema.sql")); + Process pDb = pbDb.start(); + boolean finished = pDb.waitFor(10, TimeUnit.SECONDS); + if (!finished || pDb.exitValue() != 0) { + throw new RuntimeException( + "Failed to initialize SQLite database. Exit value: " + pDb.exitValue()); } + System.out.println("Database initialized."); - @AfterAll - public static void stopActualServer() throws Exception { - System.out.println("Stopping actual MCP Toolbox server..."); - if (serverProcess != null) { - serverProcess.destroy(); - if (!serverProcess.waitFor(5, TimeUnit.SECONDS)) { - serverProcess.destroyForcibly(); - } - } - Files.deleteIfExists(Paths.get(DB_PATH)); - System.out.println("Server stopped and database cleaned."); + // 3. Start actual MCP Toolbox server + System.out.println("Starting actual MCP Toolbox server on port " + PORT + "..."); + ProcessBuilder pbServer = + new ProcessBuilder( + "src/test/resources/toolbox", + "--config", + "src/test/resources/verify_tools.yaml", + "--port", + String.valueOf(PORT)); + pbServer.inheritIO(); + serverProcess = pbServer.start(); + + // Wait a few seconds for server to start up + Thread.sleep(3000); + + if (!serverProcess.isAlive()) { + throw new RuntimeException("Toolbox server process died immediately."); } - - @Test - public void testActualServerExampleFlow() throws Exception { - // Set System properties to configure ExampleUsage to hit our local port 8099 - System.setProperty("toolbox.url", "http://localhost:" + PORT + "/mcp"); - System.setProperty("toolbox.keyPath", ""); // Empty string to bypass file checking and use ADC - - // 1. Programmatically compile ExampleUsage.java on the fly using test classpath - File sourceFile = new File("example/src/main/java/cloudcode/helloworld/ExampleUsage.java"); - assertTrue(sourceFile.exists(), "ExampleUsage.java does not exist at " + sourceFile.getAbsolutePath()); - - JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); - assertNotNull(compiler, "System Java compiler is not available. Please run tests using a JDK, not a JRE."); - - StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null); - String classpath = System.getProperty("java.class.path"); - - List optionList = new ArrayList<>(); - optionList.add("-classpath"); - optionList.add(classpath); - optionList.add("-d"); - optionList.add("target/test-classes"); - - Iterable compilationUnits = fileManager.getJavaFileObjectsFromFiles(Arrays.asList(sourceFile)); - boolean compileSuccess = compiler.getTask(null, fileManager, null, optionList, null, compilationUnits).call(); - fileManager.close(); - - assertTrue(compileSuccess, "Compilation of ExampleUsage.java failed."); - System.out.println("ExampleUsage.java compiled successfully."); - - // 2. Load the compiled ExampleUsage class - Class clazz = Class.forName("cloudcode.helloworld.ExampleUsage"); - - // Mock GoogleCredentials statically to return a dummy IdTokenProvider - try (MockedStatic mockedCredentials = Mockito.mockStatic(GoogleCredentials.class)) { - GoogleCredentials mockCreds = Mockito.mock( - GoogleCredentials.class, - Mockito.withSettings().extraInterfaces(IdTokenProvider.class) - ); - IdToken mockToken = Mockito.mock(IdToken.class); - Mockito.when(mockToken.getTokenValue()).thenReturn("dummy-token"); - Mockito.when(((IdTokenProvider) mockCreds).idTokenWithAudience( - Mockito.anyString(), Mockito.anyList() - )).thenReturn(mockToken); - - mockedCredentials.when(GoogleCredentials::getApplicationDefault).thenReturn(mockCreds); - - // Execute the actual example class's main method directly! - System.out.println("Executing ExampleUsage.main..."); - clazz.getMethod("main", String[].class).invoke(null, (Object) new String[0]); - System.out.println("ExampleUsage.main completed successfully."); - } finally { - System.clearProperty("toolbox.url"); - System.clearProperty("toolbox.keyPath"); - } + System.out.println("Actual Toolbox server started successfully."); + } + + @AfterAll + public static void stopActualServer() throws Exception { + System.out.println("Stopping actual MCP Toolbox server..."); + if (serverProcess != null) { + serverProcess.destroy(); + if (!serverProcess.waitFor(5, TimeUnit.SECONDS)) { + serverProcess.destroyForcibly(); + } } - - @Test - public void testBulkToolsetExampleFlow() throws Exception { - // Set System properties to configure BulkToolsetUsage to hit our local port 8099 - System.setProperty("toolbox.url", "http://localhost:" + PORT + "/mcp"); - System.setProperty("toolbox.keyPath", ""); - - // 1. Programmatically compile BulkToolsetUsage.java on the fly - File sourceFile = new File("example/src/main/java/cloudcode/helloworld/BulkToolsetUsage.java"); - assertTrue(sourceFile.exists(), "BulkToolsetUsage.java does not exist"); - - JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); - StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null); - String classpath = System.getProperty("java.class.path"); - - List optionList = new ArrayList<>(); - optionList.add("-classpath"); - optionList.add(classpath); - optionList.add("-d"); - optionList.add("target/test-classes"); - - Iterable compilationUnits = fileManager.getJavaFileObjectsFromFiles(Arrays.asList(sourceFile)); - boolean compileSuccess = compiler.getTask(null, fileManager, null, optionList, null, compilationUnits).call(); - fileManager.close(); - - assertTrue(compileSuccess, "Compilation of BulkToolsetUsage.java failed."); - - // 2. Load the compiled BulkToolsetUsage class - Class clazz = Class.forName("cloudcode.helloworld.BulkToolsetUsage"); - - // Mock GoogleCredentials statically to return a dummy IdTokenProvider - try (MockedStatic mockedCredentials = Mockito.mockStatic(GoogleCredentials.class)) { - GoogleCredentials mockCreds = Mockito.mock( - GoogleCredentials.class, - Mockito.withSettings().extraInterfaces(IdTokenProvider.class) - ); - IdToken mockToken = Mockito.mock(IdToken.class); - Mockito.when(mockToken.getTokenValue()).thenReturn("dummy-token"); - Mockito.when(((IdTokenProvider) mockCreds).idTokenWithAudience( - Mockito.anyString(), Mockito.anyList() - )).thenReturn(mockToken); - - mockedCredentials.when(GoogleCredentials::getApplicationDefault).thenReturn(mockCreds); - - // Execute the actual example class's main method directly! - System.out.println("Executing BulkToolsetUsage.main..."); - clazz.getMethod("main", String[].class).invoke(null, (Object) new String[0]); - System.out.println("BulkToolsetUsage.main completed successfully."); - } finally { - System.clearProperty("toolbox.url"); - System.clearProperty("toolbox.keyPath"); - } + Files.deleteIfExists(Paths.get(DB_PATH)); + System.out.println("Server stopped and database cleaned."); + } + + @Test + public void testActualServerExampleFlow() throws Exception { + // Set System properties to configure ExampleUsage to hit our local port 8099 + System.setProperty("toolbox.url", "http://localhost:" + PORT + "/mcp"); + System.setProperty("toolbox.keyPath", ""); // Empty string to bypass file checking and use ADC + + // 1. Programmatically compile ExampleUsage.java on the fly using test classpath + File sourceFile = new File("example/src/main/java/cloudcode/helloworld/ExampleUsage.java"); + assertTrue( + sourceFile.exists(), "ExampleUsage.java does not exist at " + sourceFile.getAbsolutePath()); + + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + assertNotNull( + compiler, + "System Java compiler is not available. Please run tests using a JDK, not a JRE."); + + StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null); + String classpath = System.getProperty("java.class.path"); + + List optionList = new ArrayList<>(); + optionList.add("-classpath"); + optionList.add(classpath); + optionList.add("-d"); + optionList.add("target/test-classes"); + + Iterable compilationUnits = + fileManager.getJavaFileObjectsFromFiles(Arrays.asList(sourceFile)); + boolean compileSuccess = + compiler.getTask(null, fileManager, null, optionList, null, compilationUnits).call(); + fileManager.close(); + + assertTrue(compileSuccess, "Compilation of ExampleUsage.java failed."); + System.out.println("ExampleUsage.java compiled successfully."); + + // 2. Load the compiled ExampleUsage class + Class clazz = Class.forName("cloudcode.helloworld.ExampleUsage"); + + // Mock GoogleCredentials statically to return a dummy IdTokenProvider + try (MockedStatic mockedCredentials = + Mockito.mockStatic(GoogleCredentials.class)) { + GoogleCredentials mockCreds = + Mockito.mock( + GoogleCredentials.class, + Mockito.withSettings().extraInterfaces(IdTokenProvider.class)); + IdToken mockToken = Mockito.mock(IdToken.class); + Mockito.when(mockToken.getTokenValue()).thenReturn("dummy-token"); + Mockito.when( + ((IdTokenProvider) mockCreds) + .idTokenWithAudience(Mockito.anyString(), Mockito.anyList())) + .thenReturn(mockToken); + + mockedCredentials.when(GoogleCredentials::getApplicationDefault).thenReturn(mockCreds); + + // Execute the actual example class's main method directly! + System.out.println("Executing ExampleUsage.main..."); + clazz.getMethod("main", String[].class).invoke(null, (Object) new String[0]); + System.out.println("ExampleUsage.main completed successfully."); + } finally { + System.clearProperty("toolbox.url"); + System.clearProperty("toolbox.keyPath"); + } + } + + @Test + public void testBulkToolsetExampleFlow() throws Exception { + // Set System properties to configure BulkToolsetUsage to hit our local port 8099 + System.setProperty("toolbox.url", "http://localhost:" + PORT + "/mcp"); + System.setProperty("toolbox.keyPath", ""); + + // 1. Programmatically compile BulkToolsetUsage.java on the fly + File sourceFile = new File("example/src/main/java/cloudcode/helloworld/BulkToolsetUsage.java"); + assertTrue(sourceFile.exists(), "BulkToolsetUsage.java does not exist"); + + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null); + String classpath = System.getProperty("java.class.path"); + + List optionList = new ArrayList<>(); + optionList.add("-classpath"); + optionList.add(classpath); + optionList.add("-d"); + optionList.add("target/test-classes"); + + Iterable compilationUnits = + fileManager.getJavaFileObjectsFromFiles(Arrays.asList(sourceFile)); + boolean compileSuccess = + compiler.getTask(null, fileManager, null, optionList, null, compilationUnits).call(); + fileManager.close(); + + assertTrue(compileSuccess, "Compilation of BulkToolsetUsage.java failed."); + + // 2. Load the compiled BulkToolsetUsage class + Class clazz = Class.forName("cloudcode.helloworld.BulkToolsetUsage"); + + // Mock GoogleCredentials statically to return a dummy IdTokenProvider + try (MockedStatic mockedCredentials = + Mockito.mockStatic(GoogleCredentials.class)) { + GoogleCredentials mockCreds = + Mockito.mock( + GoogleCredentials.class, + Mockito.withSettings().extraInterfaces(IdTokenProvider.class)); + IdToken mockToken = Mockito.mock(IdToken.class); + Mockito.when(mockToken.getTokenValue()).thenReturn("dummy-token"); + Mockito.when( + ((IdTokenProvider) mockCreds) + .idTokenWithAudience(Mockito.anyString(), Mockito.anyList())) + .thenReturn(mockToken); + + mockedCredentials.when(GoogleCredentials::getApplicationDefault).thenReturn(mockCreds); + + // Execute the actual example class's main method directly! + System.out.println("Executing BulkToolsetUsage.main..."); + clazz.getMethod("main", String[].class).invoke(null, (Object) new String[0]); + System.out.println("BulkToolsetUsage.main completed successfully."); + } finally { + System.clearProperty("toolbox.url"); + System.clearProperty("toolbox.keyPath"); } + } } From 0870c1f6bb91a352ec8c19f7510d9e6211b5a72c Mon Sep 17 00:00:00 2001 From: Stenal P Jolly Date: Tue, 23 Jun 2026 17:54:55 +0530 Subject: [PATCH 4/7] feat: Restructure examples into separate folders and packages for copy-paste convenience --- example/README.md | 14 ++- .../BulkToolsetUsage.java | 4 +- .../helloworld/InputValidationTest.java | 115 ----------------- .../{helloworld => simple}/ExampleUsage.java | 4 +- .../validation/InputValidationTest.java | 116 ++++++++++++++++++ .../StrictFlagTest.java | 13 +- .../e2e/ToolboxActualServerVerifyTest.java | 8 +- 7 files changed, 141 insertions(+), 133 deletions(-) rename example/src/main/java/cloudcode/{helloworld => bulk}/BulkToolsetUsage.java (98%) delete mode 100644 example/src/main/java/cloudcode/helloworld/InputValidationTest.java rename example/src/main/java/cloudcode/{helloworld => simple}/ExampleUsage.java (98%) create mode 100644 example/src/main/java/cloudcode/validation/InputValidationTest.java rename example/src/main/java/cloudcode/{helloworld => validation}/StrictFlagTest.java (89%) diff --git a/example/README.md b/example/README.md index 1e00c38..cd4bd70 100644 --- a/example/README.md +++ b/example/README.md @@ -53,9 +53,19 @@ In any case remember to change the `YOUR_TOOLBOX_SERVICE_ENDPOINT` placeholder i ```bash mvn compile ``` - Now run the example class: + Now run any of the example classes: ```bash - mvn clean compile exec:java -Dexec.mainClass="cloudcode.helloworld.ExampleUsage" + # Simple Async SDK Client usage + mvn clean compile exec:java -Dexec.mainClass="cloudcode.simple.ExampleUsage" + + # Advanced Bulk Toolset pre-binding usage + mvn clean compile exec:java -Dexec.mainClass="cloudcode.bulk.BulkToolsetUsage" + + # Input validation behavior testing + mvn clean compile exec:java -Dexec.mainClass="cloudcode.validation.InputValidationTest" + + # Strict bindings behavior testing + mvn clean compile exec:java -Dexec.mainClass="cloudcode.validation.StrictFlagTest" ``` ## Integration Verification Testing diff --git a/example/src/main/java/cloudcode/helloworld/BulkToolsetUsage.java b/example/src/main/java/cloudcode/bulk/BulkToolsetUsage.java similarity index 98% rename from example/src/main/java/cloudcode/helloworld/BulkToolsetUsage.java rename to example/src/main/java/cloudcode/bulk/BulkToolsetUsage.java index 28f9ffa..33d2c84 100644 --- a/example/src/main/java/cloudcode/helloworld/BulkToolsetUsage.java +++ b/example/src/main/java/cloudcode/bulk/BulkToolsetUsage.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package cloudcode.helloworld; +package cloudcode.bulk; import com.google.auth.oauth2.GoogleCredentials; import com.google.auth.oauth2.IdTokenProvider; @@ -154,9 +154,7 @@ public static void main(String[] args) { } catch (Exception e) { e.printStackTrace(); - System.exit(1); } System.out.println("\n--- Bulk Toolset Example Complete ---"); - System.exit(0); } } diff --git a/example/src/main/java/cloudcode/helloworld/InputValidationTest.java b/example/src/main/java/cloudcode/helloworld/InputValidationTest.java deleted file mode 100644 index b171ae7..0000000 --- a/example/src/main/java/cloudcode/helloworld/InputValidationTest.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * 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 cloudcode.helloworld; - -import com.google.cloud.mcp.McpToolboxClient; -import com.google.cloud.mcp.Tool; -import com.google.auth.oauth2.GoogleCredentials; -import com.google.auth.oauth2.IdTokenProvider; -import java.io.FileInputStream; -import java.util.Collections; -import java.util.Map; -import java.util.HashMap; -import java.util.concurrent.CompletableFuture; - -public class InputValidationTest { - public static void main(String[] args) { - String targetUrl = "YOUR_TOOLBOX_SERVICE_ENDPOINT"; - String tokenAudience = targetUrl; - // -------------------------------------------------------------------------------- - // AUTHENTICATION SETUP - // -------------------------------------------------------------------------------- - // FOR LOCAL DEVELOPMENT: Use a Service Account Key JSON file. - // FOR PRODUCTION (Cloud Run): Comment out the 'keyPath' logic and use ADC directly. - // -------------------------------------------------------------------------------- - - String keyPath = "/YOUR_CREDENTIALS_JSON_FILE_PATH.json"; - - System.out.println("--- Starting MCP Toolbox Input Validation Test ---"); - - try { - // 1. Setup Auth (Same as before) - System.out.println(" [Init] Fetching ID Token..."); - GoogleCredentials credentials = GoogleCredentials.fromStream(new FileInputStream(keyPath)); - if (!(credentials instanceof IdTokenProvider)) { - throw new RuntimeException("Loaded credentials do not support ID Tokens."); - } - String idToken = ((IdTokenProvider) credentials).idTokenWithAudience(tokenAudience, Collections.emptyList()).getTokenValue(); - - // 2. Initialize Client - McpToolboxClient client = McpToolboxClient.builder() - .baseUrl(targetUrl) - .build(); - - // 3. Load the Tool - // We MUST use loadTool() because validation relies on the ToolDefinition fetched from the server. - System.out.println(" [Init] Loading tool 'get-toy-price'..."); - Tool tool = client.loadTool("get-toy-price").join(); - - // 4. Register Auth - // We manually register the token getter so the Tool object can inject the header. - tool.addAuthTokenGetter("google_auth", () -> CompletableFuture.completedFuture(idToken)); - - - // --- Test Case A: Valid Input --- - System.out.println("\n[Test A] Sending VALID input (String)..."); - try { - Map validArgs = Map.of("description", "barbie"); - var result = tool.execute(validArgs).join(); - System.out.println(" ✅ Success! Output: " + (result.content().isEmpty() ? "Empty" : result.content().get(0).text())); - } catch (Exception e) { - System.err.println(" ❌ Unexpected failure: " + e.getMessage()); - e.printStackTrace(); - } - - - // --- Test Case B: Invalid Type (Int instead of String) --- - System.out.println("\n[Test B] Sending INVALID input (Integer instead of String)..."); - try { - // The 'description' parameter is defined as type: string. We pass an Integer. - Map invalidArgs = Map.of("description", 12345); - - tool.execute(invalidArgs).join(); - System.err.println(" ❌ FAILED: Validation did not catch the error!"); - } catch (Exception e) { - // We expect a RuntimeException wrapping IllegalArgumentException - Throwable cause = e.getCause(); - System.out.println(" ✅ Caught Expected Error: " + cause.getMessage()); - } - - - // --- Test Case C: Null Value (Filtering) --- - System.out.println("\n[Test C] Sending NULL value (should be filtered)..."); - try { - // We use a HashMap because Map.of doesn't allow nulls - Map nullArgs = new HashMap<>(); - nullArgs.put("description", "barbie"); // Valid param - nullArgs.put("some_optional_param", null); // Null param - - // If validation works, 'some_optional_param' will be removed before sending - var result = tool.execute(nullArgs).join(); - System.out.println(" ✅ Success! Null value was filtered and request succeeded."); - } catch (Exception e) { - System.out.println(" ❌ Result: " + e.getCause().getMessage()); - } - - } catch (Exception e) { - e.printStackTrace(); - } - System.out.println("\n--- Done ---"); - } -} diff --git a/example/src/main/java/cloudcode/helloworld/ExampleUsage.java b/example/src/main/java/cloudcode/simple/ExampleUsage.java similarity index 98% rename from example/src/main/java/cloudcode/helloworld/ExampleUsage.java rename to example/src/main/java/cloudcode/simple/ExampleUsage.java index 0935b4b..885ef11 100644 --- a/example/src/main/java/cloudcode/helloworld/ExampleUsage.java +++ b/example/src/main/java/cloudcode/simple/ExampleUsage.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package cloudcode.helloworld; +package cloudcode.simple; import com.google.auth.oauth2.GoogleCredentials; import com.google.auth.oauth2.IdTokenProvider; @@ -26,7 +26,7 @@ import java.util.concurrent.CompletableFuture; /** - * Sample Application to demostrate the usage of the MCP Toolbox Java SDK. Covers: Global Auth, + * Sample Application to demonstrate the usage of the MCP Toolbox Java SDK. Covers: Global Auth, * Parameterized Auth, Discovery, Simple Tool, Authenticated Tool, Parameter Binding. */ public class ExampleUsage { diff --git a/example/src/main/java/cloudcode/validation/InputValidationTest.java b/example/src/main/java/cloudcode/validation/InputValidationTest.java new file mode 100644 index 0000000..9db5e6f --- /dev/null +++ b/example/src/main/java/cloudcode/validation/InputValidationTest.java @@ -0,0 +1,116 @@ +/* + * 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 cloudcode.validation; + +import com.google.auth.oauth2.GoogleCredentials; +import com.google.auth.oauth2.IdTokenProvider; +import com.google.cloud.mcp.McpToolboxClient; +import com.google.cloud.mcp.Tool; +import java.io.FileInputStream; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +public class InputValidationTest { + public static void main(String[] args) { + String targetUrl = "YOUR_TOOLBOX_SERVICE_ENDPOINT"; + String tokenAudience = targetUrl; + // -------------------------------------------------------------------------------- + // AUTHENTICATION SETUP + // -------------------------------------------------------------------------------- + // FOR LOCAL DEVELOPMENT: Use a Service Account Key JSON file. + // FOR PRODUCTION (Cloud Run): Comment out the 'keyPath' logic and use ADC directly. + // -------------------------------------------------------------------------------- + + String keyPath = "/YOUR_CREDENTIALS_JSON_FILE_PATH.json"; + + System.out.println("--- Starting MCP Toolbox Input Validation Test ---"); + + try { + // 1. Setup Auth (Same as before) + System.out.println(" [Init] Fetching ID Token..."); + GoogleCredentials credentials = GoogleCredentials.fromStream(new FileInputStream(keyPath)); + if (!(credentials instanceof IdTokenProvider)) { + throw new RuntimeException("Loaded credentials do not support ID Tokens."); + } + String idToken = + ((IdTokenProvider) credentials) + .idTokenWithAudience(tokenAudience, Collections.emptyList()) + .getTokenValue(); + + // 2. Initialize Client + McpToolboxClient client = McpToolboxClient.builder().baseUrl(targetUrl).build(); + + // 3. Load the Tool + // We MUST use loadTool() because validation relies on the ToolDefinition fetched from the + // server. + System.out.println(" [Init] Loading tool 'get-toy-price'..."); + Tool tool = client.loadTool("get-toy-price").join(); + + // 4. Register Auth + // We manually register the token getter so the Tool object can inject the header. + tool.addAuthTokenGetter("google_auth", () -> CompletableFuture.completedFuture(idToken)); + + // --- Test Case A: Valid Input --- + System.out.println("\n[Test A] Sending VALID input (String)..."); + try { + Map validArgs = Map.of("description", "barbie"); + var result = tool.execute(validArgs).join(); + System.out.println( + " ✅ Success! Output: " + + (result.content().isEmpty() ? "Empty" : result.content().get(0).text())); + } catch (Exception e) { + System.err.println(" ❌ Unexpected failure: " + e.getMessage()); + e.printStackTrace(); + } + + // --- Test Case B: Invalid Type (Int instead of String) --- + System.out.println("\n[Test B] Sending INVALID input (Integer instead of String)..."); + try { + // The 'description' parameter is defined as type: string. We pass an Integer. + Map invalidArgs = Map.of("description", 12345); + + tool.execute(invalidArgs).join(); + System.err.println(" ❌ FAILED: Validation did not catch the error!"); + } catch (Exception e) { + // We expect a RuntimeException wrapping IllegalArgumentException + Throwable cause = e.getCause(); + System.out.println(" ✅ Caught Expected Error: " + cause.getMessage()); + } + + // --- Test Case C: Null Value (Filtering) --- + System.out.println("\n[Test C] Sending NULL value (should be filtered)..."); + try { + // We use a HashMap because Map.of doesn't allow nulls + Map nullArgs = new HashMap<>(); + nullArgs.put("description", "barbie"); // Valid param + nullArgs.put("some_optional_param", null); // Null param + + // If validation works, 'some_optional_param' will be removed before sending + var result = tool.execute(nullArgs).join(); + System.out.println(" ✅ Success! Null value was filtered and request succeeded."); + } catch (Exception e) { + System.out.println(" ❌ Result: " + e.getCause().getMessage()); + } + + } catch (Exception e) { + e.printStackTrace(); + } + System.out.println("\n--- Done ---"); + } +} diff --git a/example/src/main/java/cloudcode/helloworld/StrictFlagTest.java b/example/src/main/java/cloudcode/validation/StrictFlagTest.java similarity index 89% rename from example/src/main/java/cloudcode/helloworld/StrictFlagTest.java rename to example/src/main/java/cloudcode/validation/StrictFlagTest.java index ba32ff9..f502ef2 100644 --- a/example/src/main/java/cloudcode/helloworld/StrictFlagTest.java +++ b/example/src/main/java/cloudcode/validation/StrictFlagTest.java @@ -14,21 +14,19 @@ * limitations under the License. */ -package cloudcode.helloworld; +package cloudcode.validation; import com.google.cloud.mcp.McpToolboxClient; import com.google.cloud.mcp.Tool; -import java.util.Map; import java.util.HashMap; +import java.util.Map; public class StrictFlagTest { public static void main(String[] args) { String targetUrl = "YOUR_TOOLBOX_SERVICE_ENDPOINT"; System.out.println("--- Starting MCP Toolbox Strict Flag Test ---"); - McpToolboxClient client = McpToolboxClient.builder() - .baseUrl(targetUrl) - .build(); + McpToolboxClient client = McpToolboxClient.builder().baseUrl(targetUrl).build(); // Prepare bindings for a NON-EXISTENT tool Map> paramBinds = new HashMap<>(); @@ -38,7 +36,8 @@ public static void main(String[] args) { System.out.println("\n[Test 1] Loading with Strict = FALSE..."); try { Map tools = client.loadToolset(null, paramBinds, null, false).join(); - System.out.println(" ✅ Success! Loaded " + tools.size() + " tools. Unknown binding was ignored."); + System.out.println( + " ✅ Success! Loaded " + tools.size() + " tools. Unknown binding was ignored."); } catch (Exception e) { System.err.println(" ❌ Failed unexpectedly: " + e.getMessage()); } @@ -60,4 +59,4 @@ public static void main(String[] args) { System.out.println("\n--- Done ---"); } -} \ No newline at end of file +} diff --git a/src/test/java/com/google/cloud/mcp/e2e/ToolboxActualServerVerifyTest.java b/src/test/java/com/google/cloud/mcp/e2e/ToolboxActualServerVerifyTest.java index be9a64d..66cf5ce 100644 --- a/src/test/java/com/google/cloud/mcp/e2e/ToolboxActualServerVerifyTest.java +++ b/src/test/java/com/google/cloud/mcp/e2e/ToolboxActualServerVerifyTest.java @@ -102,7 +102,7 @@ public void testActualServerExampleFlow() throws Exception { System.setProperty("toolbox.keyPath", ""); // Empty string to bypass file checking and use ADC // 1. Programmatically compile ExampleUsage.java on the fly using test classpath - File sourceFile = new File("example/src/main/java/cloudcode/helloworld/ExampleUsage.java"); + File sourceFile = new File("example/src/main/java/cloudcode/simple/ExampleUsage.java"); assertTrue( sourceFile.exists(), "ExampleUsage.java does not exist at " + sourceFile.getAbsolutePath()); @@ -130,7 +130,7 @@ public void testActualServerExampleFlow() throws Exception { System.out.println("ExampleUsage.java compiled successfully."); // 2. Load the compiled ExampleUsage class - Class clazz = Class.forName("cloudcode.helloworld.ExampleUsage"); + Class clazz = Class.forName("cloudcode.simple.ExampleUsage"); // Mock GoogleCredentials statically to return a dummy IdTokenProvider try (MockedStatic mockedCredentials = @@ -165,7 +165,7 @@ public void testBulkToolsetExampleFlow() throws Exception { System.setProperty("toolbox.keyPath", ""); // 1. Programmatically compile BulkToolsetUsage.java on the fly - File sourceFile = new File("example/src/main/java/cloudcode/helloworld/BulkToolsetUsage.java"); + File sourceFile = new File("example/src/main/java/cloudcode/bulk/BulkToolsetUsage.java"); assertTrue(sourceFile.exists(), "BulkToolsetUsage.java does not exist"); JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); @@ -187,7 +187,7 @@ public void testBulkToolsetExampleFlow() throws Exception { assertTrue(compileSuccess, "Compilation of BulkToolsetUsage.java failed."); // 2. Load the compiled BulkToolsetUsage class - Class clazz = Class.forName("cloudcode.helloworld.BulkToolsetUsage"); + Class clazz = Class.forName("cloudcode.bulk.BulkToolsetUsage"); // Mock GoogleCredentials statically to return a dummy IdTokenProvider try (MockedStatic mockedCredentials = From 7b2e090fd0ee4d9caad6e78c3bb2cd8d22ae3bf4 Mon Sep 17 00:00:00 2001 From: Stenal P Jolly Date: Tue, 23 Jun 2026 18:02:25 +0530 Subject: [PATCH 5/7] feat: Simplify ExampleUsage to basic connection, listing, and invocation; extract parameter bindings to ParameterBindingUsage --- .../java/cloudcode/simple/ExampleUsage.java | 188 ++++-------------- .../simple/ParameterBindingUsage.java | 132 ++++++++++++ .../e2e/ToolboxActualServerVerifyTest.java | 57 ++++++ 3 files changed, 224 insertions(+), 153 deletions(-) create mode 100644 example/src/main/java/cloudcode/simple/ParameterBindingUsage.java diff --git a/example/src/main/java/cloudcode/simple/ExampleUsage.java b/example/src/main/java/cloudcode/simple/ExampleUsage.java index 885ef11..334e93b 100644 --- a/example/src/main/java/cloudcode/simple/ExampleUsage.java +++ b/example/src/main/java/cloudcode/simple/ExampleUsage.java @@ -16,166 +16,48 @@ package cloudcode.simple; -import com.google.auth.oauth2.GoogleCredentials; -import com.google.auth.oauth2.IdTokenProvider; -import com.google.cloud.mcp.AuthTokenGetter; import com.google.cloud.mcp.McpToolboxClient; -import java.io.FileInputStream; -import java.util.Collections; import java.util.Map; -import java.util.concurrent.CompletableFuture; /** - * Sample Application to demonstrate the usage of the MCP Toolbox Java SDK. Covers: Global Auth, - * Parameterized Auth, Discovery, Simple Tool, Authenticated Tool, Parameter Binding. + * A minimal example demonstrating client initialization, tool discovery, and invocation. */ public class ExampleUsage { public static void main(String[] args) { - // CONFIGURATION String targetUrl = System.getProperty("toolbox.url", "YOUR_TOOLBOX_SERVICE_ENDPOINT"); - - // Match the Service URL if using Cloud Run OIDC - String tokenAudience = System.getProperty("toolbox.tokenAudience", targetUrl); - - // -------------------------------------------------------------------------------- - // AUTHENTICATION SETUP - // -------------------------------------------------------------------------------- - // FOR LOCAL DEVELOPMENT: Use a Service Account Key JSON file. - // FOR PRODUCTION (Cloud Run): Comment out the 'keyPath' logic and use ADC directly. - // -------------------------------------------------------------------------------- - - String keyPath = System.getProperty("toolbox.keyPath", "YOUR_CREDENTIALS_JSON_FILE_PATH.json"); - - System.out.println("--- Starting MCP Toolbox Integration Test ---"); - System.out.println("Target Server: " + targetUrl); - - try { - System.out.println(" [Init] Fetching ID Token..."); - - GoogleCredentials credentials; - - // --- OPTION A: LOCAL DEV (Explicit Key File) --- - if (keyPath != null && !keyPath.isEmpty()) { - System.out.println(" [Auth] Using Service Account Key File: " + keyPath); - credentials = GoogleCredentials.fromStream(new FileInputStream(keyPath)); - } - // --- OPTION B: PRODUCTION (ADC) --- - else { - System.out.println(" [Auth] Using Application Default Credentials (ADC)"); - credentials = GoogleCredentials.getApplicationDefault(); - } - - if (!(credentials instanceof IdTokenProvider)) { - throw new RuntimeException("Loaded credentials do not support ID Tokens."); - } - - // Generate Token for the specified Audience - String idToken = - ((IdTokenProvider) credentials) - .idTokenWithAudience(tokenAudience, Collections.emptyList()) - .getTokenValue(); - System.out.println(" [Debug] Token Generated."); - - // Initialize Client with Global Auth (Applies to ALL calls - Gate 1) - McpToolboxClient client = - McpToolboxClient.builder().baseUrl(targetUrl).apiKey(idToken).build(); - - // STEP 1: TEST DISCOVERY METHODS - client - .listTools() - .thenCompose( - tools -> { - System.out.println("\n[1] listTools(): Success. Found " + tools.size() + " tools."); - return client.loadToolset(); - }) - .thenCompose( - tools -> { - System.out.println("[2] loadToolset() (Alias): Success."); - return client - .loadToolset("retail") - .handle( - (res, ex) -> { - if (ex == null) - System.out.println( - "[3] loadToolset('retail'): Found " + res.size() + " tools."); - else - System.out.println( - "[3] loadToolset('retail'): Skipped (Not configured on server)."); - return null; - }); - }) - .thenCompose( - ignore -> { - - // STEP 2: INVOKE TOOL WITHOUT EXTRA AUTH - System.out.println("\n[4] Testing Simple Tool: 'get-retail-facet-filters'..."); - return client.invokeTool("get-retail-facet-filters", Map.of()); - }) - .thenCompose( - result -> { - System.out.println( - " -> Result: " + (result.content() != null ? "Received Data" : "Empty")); - - // STEP 3: INVOKE TOOL WITH AUTHENTICATED PARAMETERS - System.out.println("\n[5] Testing Authenticated Tool: 'get-toy-price'..."); - - // Define the getter for the 'google_auth' service - AuthTokenGetter toolAuthGetter = () -> CompletableFuture.completedFuture(idToken); - - // Load using the sophisticated overload - return client.loadTool("get-toy-price", Map.of("google_auth", toolAuthGetter)); - }) - .thenCompose( - tool -> { - System.out.println(" -> Loaded Tool: " + tool.definition().description()); - - // STEP 4: TEST BINDING PARAMETERS SEQUENTIALLY - System.out.println("\n[A] Executing UNBOUND (Runtime arg: 'barbie')..."); - - return tool.execute(Map.of("description", "barbie")) - .thenCompose( - result1 -> { - if (result1.content() != null && !result1.content().isEmpty()) { - System.out.println( - " -> Result (Unbound): " + result1.content().get(0).text()); - } - - // NOW bind the parameter - System.out.println("\n[B] Binding 'description' to 'soft toy'..."); - tool.bindParam("description", "soft toy"); - - System.out.println( - " -> Executing BOUND (Runtime arg: 'barbie' - should be" - + " IGNORED)..."); - // We pass 'barbie', but expecting 'soft toy' price because of binding - // override - return tool.execute(Map.of("description", "barbie")); - }); - }) - .thenAccept( - result -> { - System.out.println("\n[6] Final Result (Bound):"); - if (result.isError()) { - System.err.println("Tool execution failed: " + result.content().get(0).text()); - } else if (result.content() != null && !result.content().isEmpty()) { - String output = result.content().get(0).text(); - System.out.println( - " " + output.substring(0, Math.min(output.length(), 200)) + "..."); - } else { - System.out.println(" Empty Response"); - } - }) - .exceptionally( - ex -> { - System.err.println("\n!!! TEST FAILED !!!"); - ex.printStackTrace(); - return null; - }) - .join(); - - } catch (Exception e) { - e.printStackTrace(); - } - System.out.println("\n--- Test Suite Complete ---"); + String apiKey = System.getProperty("toolbox.apiKey", "YOUR_API_KEY"); + + System.out.println("--- Starting MCP Toolbox Simple Example ---"); + System.out.println("Connecting to: " + targetUrl); + + // Initialize the client + McpToolboxClient client = + McpToolboxClient.builder().baseUrl(targetUrl).apiKey(apiKey).build(); + + // 1. List available tools + client + .listTools() + .thenAccept( + tools -> { + System.out.println("Available tools: " + tools.size()); + for (String toolName : tools.keySet()) { + System.out.println(" - " + toolName); + } + + // 2. Invoke a simple tool + if (tools.containsKey("get-retail-facet-filters")) { + System.out.println("\nInvoking 'get-retail-facet-filters'..."); + client + .invokeTool("get-retail-facet-filters", Map.of()) + .thenAccept( + result -> { + System.out.println("Result: " + result.content().get(0).text()); + }) + .join(); + } + }) + .join(); + + System.out.println("\n--- Simple Example Complete ---"); } } diff --git a/example/src/main/java/cloudcode/simple/ParameterBindingUsage.java b/example/src/main/java/cloudcode/simple/ParameterBindingUsage.java new file mode 100644 index 0000000..4c9f0ce --- /dev/null +++ b/example/src/main/java/cloudcode/simple/ParameterBindingUsage.java @@ -0,0 +1,132 @@ +/* + * 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 cloudcode.simple; + +import com.google.auth.oauth2.GoogleCredentials; +import com.google.auth.oauth2.IdTokenProvider; +import com.google.cloud.mcp.AuthTokenGetter; +import com.google.cloud.mcp.McpToolboxClient; +import java.io.FileInputStream; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +/** Example demonstrating how to use parameter bindings and authenticated tool methods. */ +public class ParameterBindingUsage { + public static void main(String[] args) { + // CONFIGURATION + String targetUrl = System.getProperty("toolbox.url", "YOUR_TOOLBOX_SERVICE_ENDPOINT"); + + // Match the Service URL if using Cloud Run OIDC + String tokenAudience = System.getProperty("toolbox.tokenAudience", targetUrl); + + // -------------------------------------------------------------------------------- + // AUTHENTICATION SETUP + // -------------------------------------------------------------------------------- + // FOR LOCAL DEVELOPMENT: Use a Service Account Key JSON file. + // FOR PRODUCTION (Cloud Run): Comment out the 'keyPath' logic and use ADC directly. + // -------------------------------------------------------------------------------- + + String keyPath = System.getProperty("toolbox.keyPath", "YOUR_CREDENTIALS_JSON_FILE_PATH.json"); + + System.out.println("--- Starting MCP Toolbox Parameter Binding Example ---"); + System.out.println("Target Server: " + targetUrl); + + try { + System.out.println(" [Init] Fetching ID Token..."); + + GoogleCredentials credentials; + + // --- OPTION A: LOCAL DEV (Explicit Key File) --- + if (keyPath != null && !keyPath.isEmpty()) { + System.out.println(" [Auth] Using Service Account Key File: " + keyPath); + credentials = GoogleCredentials.fromStream(new FileInputStream(keyPath)); + } + // --- OPTION B: PRODUCTION (ADC) --- + else { + System.out.println(" [Auth] Using Application Default Credentials (ADC)"); + credentials = GoogleCredentials.getApplicationDefault(); + } + + if (!(credentials instanceof IdTokenProvider)) { + throw new RuntimeException("Loaded credentials do not support ID Tokens."); + } + + // Generate Token for the specified Audience + String idToken = + ((IdTokenProvider) credentials) + .idTokenWithAudience(tokenAudience, Collections.emptyList()) + .getTokenValue(); + System.out.println(" [Debug] Token Generated."); + + // Initialize Client + McpToolboxClient client = + McpToolboxClient.builder().baseUrl(targetUrl).apiKey(idToken).build(); + + // STEP 1: LOAD TOOL WITH AUTH PROVIDERS + System.out.println("\n[1] Testing Authenticated Tool: 'get-toy-price'..."); + + // Define the getter for the 'google_auth' service + AuthTokenGetter toolAuthGetter = () -> CompletableFuture.completedFuture(idToken); + + client + .loadTool("get-toy-price", Map.of("google_auth", toolAuthGetter)) + .thenCompose( + tool -> { + System.out.println(" -> Loaded Tool: " + tool.definition().description()); + + // STEP 2: TEST BINDING PARAMETERS SEQUENTIALLY + System.out.println("\n[A] Executing UNBOUND (Runtime arg: 'barbie')..."); + + return tool.execute(Map.of("description", "barbie")) + .thenCompose( + result1 -> { + if (result1.content() != null && !result1.content().isEmpty()) { + System.out.println( + " -> Result (Unbound): " + result1.content().get(0).text()); + } + + // NOW bind the parameter + System.out.println("\n[B] Binding 'description' to 'soft toy'..."); + tool.bindParam("description", "soft toy"); + + System.out.println( + " -> Executing BOUND (Runtime arg: 'barbie' - should be" + + " IGNORED)..."); + // We pass 'barbie', but expecting 'soft toy' price because of binding + // override + return tool.execute(Map.of("description", "barbie")); + }); + }) + .thenAccept( + result -> { + System.out.println("\n[2] Final Result (Bound):"); + if (result.isError()) { + System.err.println("Tool execution failed: " + result.content().get(0).text()); + } else if (result.content() != null && !result.content().isEmpty()) { + String output = result.content().get(0).text(); + System.out.println(" Result content: " + output); + } + }) + .join(); + + } catch (Exception e) { + e.printStackTrace(); + } + System.out.println("\n--- Parameter Binding Example Complete ---"); + } +} diff --git a/src/test/java/com/google/cloud/mcp/e2e/ToolboxActualServerVerifyTest.java b/src/test/java/com/google/cloud/mcp/e2e/ToolboxActualServerVerifyTest.java index 66cf5ce..d772fa6 100644 --- a/src/test/java/com/google/cloud/mcp/e2e/ToolboxActualServerVerifyTest.java +++ b/src/test/java/com/google/cloud/mcp/e2e/ToolboxActualServerVerifyTest.java @@ -214,4 +214,61 @@ public void testBulkToolsetExampleFlow() throws Exception { System.clearProperty("toolbox.keyPath"); } } + + @Test + public void testParameterBindingExampleFlow() throws Exception { + // Set System properties to configure ParameterBindingUsage to hit our local port 8099 + System.setProperty("toolbox.url", "http://localhost:" + PORT + "/mcp"); + System.setProperty("toolbox.keyPath", ""); + + // 1. Programmatically compile ParameterBindingUsage.java on the fly + File sourceFile = new File("example/src/main/java/cloudcode/simple/ParameterBindingUsage.java"); + assertTrue(sourceFile.exists(), "ParameterBindingUsage.java does not exist"); + + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null); + String classpath = System.getProperty("java.class.path"); + + List optionList = new ArrayList<>(); + optionList.add("-classpath"); + optionList.add(classpath); + optionList.add("-d"); + optionList.add("target/test-classes"); + + Iterable compilationUnits = + fileManager.getJavaFileObjectsFromFiles(Arrays.asList(sourceFile)); + boolean compileSuccess = + compiler.getTask(null, fileManager, null, optionList, null, compilationUnits).call(); + fileManager.close(); + + assertTrue(compileSuccess, "Compilation of ParameterBindingUsage.java failed."); + + // 2. Load the compiled ParameterBindingUsage class + Class clazz = Class.forName("cloudcode.simple.ParameterBindingUsage"); + + // Mock GoogleCredentials statically to return a dummy IdTokenProvider + try (MockedStatic mockedCredentials = + Mockito.mockStatic(GoogleCredentials.class)) { + GoogleCredentials mockCreds = + Mockito.mock( + GoogleCredentials.class, + Mockito.withSettings().extraInterfaces(IdTokenProvider.class)); + IdToken mockToken = Mockito.mock(IdToken.class); + Mockito.when(mockToken.getTokenValue()).thenReturn("dummy-token"); + Mockito.when( + ((IdTokenProvider) mockCreds) + .idTokenWithAudience(Mockito.anyString(), Mockito.anyList())) + .thenReturn(mockToken); + + mockedCredentials.when(GoogleCredentials::getApplicationDefault).thenReturn(mockCreds); + + // Execute the actual example class's main method directly! + System.out.println("Executing ParameterBindingUsage.main..."); + clazz.getMethod("main", String[].class).invoke(null, (Object) new String[0]); + System.out.println("ParameterBindingUsage.main completed successfully."); + } finally { + System.clearProperty("toolbox.url"); + System.clearProperty("toolbox.keyPath"); + } + } } From 7af7293c7f5478826a54354c7bb0dc9b1eace85b Mon Sep 17 00:00:00 2001 From: Stenal P Jolly Date: Tue, 23 Jun 2026 18:03:52 +0530 Subject: [PATCH 6/7] docs: Add ParameterBindingUsage to example README runner guide --- example/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/example/README.md b/example/README.md index cd4bd70..1a1347d 100644 --- a/example/README.md +++ b/example/README.md @@ -58,6 +58,9 @@ In any case remember to change the `YOUR_TOOLBOX_SERVICE_ENDPOINT` placeholder i # Simple Async SDK Client usage mvn clean compile exec:java -Dexec.mainClass="cloudcode.simple.ExampleUsage" + # Sequential parameter bindings and authentication token provider usage + mvn clean compile exec:java -Dexec.mainClass="cloudcode.simple.ParameterBindingUsage" + # Advanced Bulk Toolset pre-binding usage mvn clean compile exec:java -Dexec.mainClass="cloudcode.bulk.BulkToolsetUsage" From 04ed5d55eb275fd68b88274e4719fcf0d6f7f2b8 Mon Sep 17 00:00:00 2001 From: Stenal P Jolly Date: Tue, 23 Jun 2026 18:13:22 +0530 Subject: [PATCH 7/7] feat: Flatten ExampleUsage and ParameterBindingUsage to use synchronous flat style, removing verbose logs --- .../java/cloudcode/simple/ExampleUsage.java | 41 +++------- .../simple/ParameterBindingUsage.java | 81 +++---------------- 2 files changed, 21 insertions(+), 101 deletions(-) diff --git a/example/src/main/java/cloudcode/simple/ExampleUsage.java b/example/src/main/java/cloudcode/simple/ExampleUsage.java index 334e93b..8189c79 100644 --- a/example/src/main/java/cloudcode/simple/ExampleUsage.java +++ b/example/src/main/java/cloudcode/simple/ExampleUsage.java @@ -17,47 +17,24 @@ package cloudcode.simple; import com.google.cloud.mcp.McpToolboxClient; +import com.google.cloud.mcp.ToolResult; import java.util.Map; -/** - * A minimal example demonstrating client initialization, tool discovery, and invocation. - */ +/** A minimal example demonstrating client initialization, tool discovery, and invocation. */ public class ExampleUsage { public static void main(String[] args) { String targetUrl = System.getProperty("toolbox.url", "YOUR_TOOLBOX_SERVICE_ENDPOINT"); String apiKey = System.getProperty("toolbox.apiKey", "YOUR_API_KEY"); - System.out.println("--- Starting MCP Toolbox Simple Example ---"); - System.out.println("Connecting to: " + targetUrl); - // Initialize the client - McpToolboxClient client = - McpToolboxClient.builder().baseUrl(targetUrl).apiKey(apiKey).build(); - - // 1. List available tools - client - .listTools() - .thenAccept( - tools -> { - System.out.println("Available tools: " + tools.size()); - for (String toolName : tools.keySet()) { - System.out.println(" - " + toolName); - } + McpToolboxClient client = McpToolboxClient.builder().baseUrl(targetUrl).apiKey(apiKey).build(); - // 2. Invoke a simple tool - if (tools.containsKey("get-retail-facet-filters")) { - System.out.println("\nInvoking 'get-retail-facet-filters'..."); - client - .invokeTool("get-retail-facet-filters", Map.of()) - .thenAccept( - result -> { - System.out.println("Result: " + result.content().get(0).text()); - }) - .join(); - } - }) - .join(); + // 1. List available tools synchronously + Map tools = client.listTools().join(); + System.out.println("Available tools: " + tools.keySet()); - System.out.println("\n--- Simple Example Complete ---"); + // 2. Invoke a simple tool synchronously + ToolResult result = client.invokeTool("get-retail-facet-filters", Map.of()).join(); + System.out.println("Result: " + result.content().get(0).text()); } } diff --git a/example/src/main/java/cloudcode/simple/ParameterBindingUsage.java b/example/src/main/java/cloudcode/simple/ParameterBindingUsage.java index 4c9f0ce..e4f703e 100644 --- a/example/src/main/java/cloudcode/simple/ParameterBindingUsage.java +++ b/example/src/main/java/cloudcode/simple/ParameterBindingUsage.java @@ -20,6 +20,8 @@ import com.google.auth.oauth2.IdTokenProvider; import com.google.cloud.mcp.AuthTokenGetter; import com.google.cloud.mcp.McpToolboxClient; +import com.google.cloud.mcp.Tool; +import com.google.cloud.mcp.ToolResult; import java.io.FileInputStream; import java.util.Collections; import java.util.Map; @@ -28,37 +30,15 @@ /** Example demonstrating how to use parameter bindings and authenticated tool methods. */ public class ParameterBindingUsage { public static void main(String[] args) { - // CONFIGURATION String targetUrl = System.getProperty("toolbox.url", "YOUR_TOOLBOX_SERVICE_ENDPOINT"); - - // Match the Service URL if using Cloud Run OIDC String tokenAudience = System.getProperty("toolbox.tokenAudience", targetUrl); - - // -------------------------------------------------------------------------------- - // AUTHENTICATION SETUP - // -------------------------------------------------------------------------------- - // FOR LOCAL DEVELOPMENT: Use a Service Account Key JSON file. - // FOR PRODUCTION (Cloud Run): Comment out the 'keyPath' logic and use ADC directly. - // -------------------------------------------------------------------------------- - String keyPath = System.getProperty("toolbox.keyPath", "YOUR_CREDENTIALS_JSON_FILE_PATH.json"); - System.out.println("--- Starting MCP Toolbox Parameter Binding Example ---"); - System.out.println("Target Server: " + targetUrl); - try { - System.out.println(" [Init] Fetching ID Token..."); - GoogleCredentials credentials; - - // --- OPTION A: LOCAL DEV (Explicit Key File) --- if (keyPath != null && !keyPath.isEmpty()) { - System.out.println(" [Auth] Using Service Account Key File: " + keyPath); credentials = GoogleCredentials.fromStream(new FileInputStream(keyPath)); - } - // --- OPTION B: PRODUCTION (ADC) --- - else { - System.out.println(" [Auth] Using Application Default Credentials (ADC)"); + } else { credentials = GoogleCredentials.getApplicationDefault(); } @@ -66,67 +46,30 @@ public static void main(String[] args) { throw new RuntimeException("Loaded credentials do not support ID Tokens."); } - // Generate Token for the specified Audience String idToken = ((IdTokenProvider) credentials) .idTokenWithAudience(tokenAudience, Collections.emptyList()) .getTokenValue(); - System.out.println(" [Debug] Token Generated."); // Initialize Client McpToolboxClient client = McpToolboxClient.builder().baseUrl(targetUrl).apiKey(idToken).build(); - // STEP 1: LOAD TOOL WITH AUTH PROVIDERS - System.out.println("\n[1] Testing Authenticated Tool: 'get-toy-price'..."); - - // Define the getter for the 'google_auth' service + // 1. Load the tool with authentication providers AuthTokenGetter toolAuthGetter = () -> CompletableFuture.completedFuture(idToken); + Tool tool = client.loadTool("get-toy-price", Map.of("google_auth", toolAuthGetter)).join(); - client - .loadTool("get-toy-price", Map.of("google_auth", toolAuthGetter)) - .thenCompose( - tool -> { - System.out.println(" -> Loaded Tool: " + tool.definition().description()); - - // STEP 2: TEST BINDING PARAMETERS SEQUENTIALLY - System.out.println("\n[A] Executing UNBOUND (Runtime arg: 'barbie')..."); - - return tool.execute(Map.of("description", "barbie")) - .thenCompose( - result1 -> { - if (result1.content() != null && !result1.content().isEmpty()) { - System.out.println( - " -> Result (Unbound): " + result1.content().get(0).text()); - } - - // NOW bind the parameter - System.out.println("\n[B] Binding 'description' to 'soft toy'..."); - tool.bindParam("description", "soft toy"); + // 2. Execute unbound (with explicit runtime argument) + ToolResult resultUnbound = tool.execute(Map.of("description", "barbie")).join(); + System.out.println("Result (unbound): " + resultUnbound.content().get(0).text()); - System.out.println( - " -> Executing BOUND (Runtime arg: 'barbie' - should be" - + " IGNORED)..."); - // We pass 'barbie', but expecting 'soft toy' price because of binding - // override - return tool.execute(Map.of("description", "barbie")); - }); - }) - .thenAccept( - result -> { - System.out.println("\n[2] Final Result (Bound):"); - if (result.isError()) { - System.err.println("Tool execution failed: " + result.content().get(0).text()); - } else if (result.content() != null && !result.content().isEmpty()) { - String output = result.content().get(0).text(); - System.out.println(" Result content: " + output); - } - }) - .join(); + // 3. Bind the parameter and execute bound (runtime arg will be overridden by binding) + tool.bindParam("description", "soft toy"); + ToolResult resultBound = tool.execute(Map.of("description", "barbie")).join(); + System.out.println("Result (bound): " + resultBound.content().get(0).text()); } catch (Exception e) { e.printStackTrace(); } - System.out.println("\n--- Parameter Binding Example Complete ---"); } }