diff --git a/demo-applications/cymbal-transit/pom.xml b/demo-applications/cymbal-transit/pom.xml
index 7c420d4..0897eaa 100644
--- a/demo-applications/cymbal-transit/pom.xml
+++ b/demo-applications/cymbal-transit/pom.xml
@@ -69,7 +69,7 @@
com.google.cloud.mcp
mcp-toolbox-sdk-java
- 0.2.0
+ 0.2.1-SNAPSHOT
diff --git a/demo-applications/cymbal-transit/src/main/java/cloudcode/cymbal/CymbalTransitApplication.java b/demo-applications/cymbal-transit/src/main/java/cloudcode/cymbal/CymbalTransitApplication.java
index 070c86e..a65ad96 100644
--- a/demo-applications/cymbal-transit/src/main/java/cloudcode/cymbal/CymbalTransitApplication.java
+++ b/demo-applications/cymbal-transit/src/main/java/cloudcode/cymbal/CymbalTransitApplication.java
@@ -40,6 +40,8 @@ public static void main(final String[] args) throws Exception {
// Start the Spring Boot application.
app.run(args);
logger.info(
- "Hello from Cloud Run! The container started successfully and is listening for HTTP requests on " + port);
+ "Hello from Cloud Run! The container started successfully and is listening for HTTP"
+ + " requests on "
+ + port);
}
}
diff --git a/demo-applications/cymbal-transit/src/main/java/cloudcode/cymbal/web/CymbalTransitController.java b/demo-applications/cymbal-transit/src/main/java/cloudcode/cymbal/web/CymbalTransitController.java
index 6cea16f..4ef044d 100644
--- a/demo-applications/cymbal-transit/src/main/java/cloudcode/cymbal/web/CymbalTransitController.java
+++ b/demo-applications/cymbal-transit/src/main/java/cloudcode/cymbal/web/CymbalTransitController.java
@@ -16,250 +16,272 @@
package cloudcode.cymbal.web;
-import com.google.cloud.mcp.McpToolboxClient;
import cloudcode.cymbal.CymbalTransitApplication;
-import com.google.cloud.mcp.AuthTokenGetter;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.auth.oauth2.IdTokenProvider;
-
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.web.bind.annotation.*;
-import org.springframework.http.ResponseEntity;
-import org.springframework.stereotype.Service;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.beans.factory.annotation.Value;
-
-import javax.annotation.PostConstruct;
-import javax.servlet.http.HttpSession;
-import java.io.FileInputStream;
-import java.util.Collections;
-import java.util.Map;
-import java.util.concurrent.CompletableFuture;
-
-// LangChain4j Imports for Agentic Routing & Gemini 3 Flash
+import com.google.cloud.mcp.McpToolboxClient;
+import com.google.cloud.mcp.auth.AuthTokenGetter;
+import dev.langchain4j.agent.tool.Tool;
+import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel;
-import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.service.AiServices;
-import dev.langchain4j.agent.tool.Tool;
-import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.MemoryId;
+import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
+import javax.annotation.PostConstruct;
+import javax.servlet.http.HttpSession;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.web.bind.annotation.*;
@SpringBootApplication
public class CymbalTransitController {
- public static void main(String[] args) {
- SpringApplication.run(CymbalTransitApplication.class, args);
- }
+ public static void main(String[] args) {
+ SpringApplication.run(CymbalTransitApplication.class, args);
+ }
}
/**
- * 1. AI AGENT CONFIGURATION
- * Configures Gemini 3 Flash and binds it to our LangChain4j Agent Interface.
+ * 1. AI AGENT CONFIGURATION Configures Gemini 3 Flash and binds it to our LangChain4j Agent
+ * Interface.
*/
@Configuration
class AgentConfiguration {
- @Value("${GCP_PROJECT_ID:fallback_project_id}")
- private String projectId;
-
- @Value("${GCP_REGION:fallback_region}")
- private String region;
-
- @Value("${GEMINI_MODEL_NAME:fallback_model}")
- private String modelName;
-
- @Bean
- ChatLanguageModel geminiChatModel() {
- return VertexAiGeminiChatModel.builder()
- .project(projectId)
- .location(region)
- .modelName(modelName) // Utilizing externalized parameters
- .build();
- }
-
- @Bean
- TransitAgent transitAgent(ChatLanguageModel chatLanguageModel, TransitAgentTools tools) {
- return AiServices.builder(TransitAgent.class)
- .chatLanguageModel(chatLanguageModel)
- .chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(20))
- .tools(tools) // Exposes our MCP tools to Gemini
- .build();
- }
+ @Value("${GCP_PROJECT_ID:fallback_project_id}")
+ private String projectId;
+
+ @Value("${GCP_REGION:fallback_region}")
+ private String region;
+
+ @Value("${GEMINI_MODEL_NAME:fallback_model}")
+ private String modelName;
+
+ @Bean
+ ChatLanguageModel geminiChatModel() {
+ return VertexAiGeminiChatModel.builder()
+ .project(projectId)
+ .location(region)
+ .modelName(modelName) // Utilizing externalized parameters
+ .build();
+ }
+
+ @Bean
+ TransitAgent transitAgent(ChatLanguageModel chatLanguageModel, TransitAgentTools tools) {
+ return AiServices.builder(TransitAgent.class)
+ .chatLanguageModel(chatLanguageModel)
+ .chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(20))
+ .tools(tools) // Exposes our MCP tools to Gemini
+ .build();
+ }
}
-/**
- * 2. THE AI AGENT INTERFACE
- * Declarative AI service handling routing logic via System Prompt.
- */
+/** 2. THE AI AGENT INTERFACE Declarative AI service handling routing logic via System Prompt. */
interface TransitAgent {
- @SystemMessage({
- "You are the Cymbal Transit Concierge.",
- "CRITICAL INSTRUCTION: On your very first interaction, you MUST use the 'findAllSchedules' tool to fetch and memorize the broad bus routes.",
- "Keep this data handy in your context. Answer general routing questions using this stored data. ",
- "If you have to list the route details to the user, show it along with the full UUID and with other details that are meaningful. If the user chooses to book ticket as the next step, prompt them to copy the correct UUID nad paste so the transaction can be confirmed.",
- "ONLY if the user asks a specifically narrowed-down question, asks for precise times, or assigns a booking task, or asks about policies should you route to the specific tools like 'querySchedules', 'bookTicket', 'searchPolicies'.",
- "Remember the tool 'querySchedules' is for finding schedules between cities, 'bookTicket' is for booking ticket actionable between 2 cities, 'searchPolicies' is for finding matching policies for this company.",
- "Be intuitive and intelligent in finding the context even when user has typos. Do no hallucinate and make up stuff though. USe only data from the tools. ",
- "Don't show any asterisks while listing results. Keep it formatted and numbered or bulleted. asterisks distract."
- })
- String chat(@MemoryId String sessionId, @UserMessage String userMessage);
+ @SystemMessage({
+ "You are the Cymbal Transit Concierge.",
+ "CRITICAL INSTRUCTION: On your very first interaction, you MUST use the 'findAllSchedules' tool"
+ + " to fetch and memorize the broad bus routes.",
+ "Keep this data handy in your context. Answer general routing questions using this stored data."
+ + " ",
+ "If you have to list the route details to the user, show it along with the full UUID and with"
+ + " other details that are meaningful. If the user chooses to book ticket as the next step,"
+ + " prompt them to copy the correct UUID nad paste so the transaction can be confirmed.",
+ "ONLY if the user asks a specifically narrowed-down question, asks for precise times, or"
+ + " assigns a booking task, or asks about policies should you route to the specific tools"
+ + " like 'querySchedules', 'bookTicket', 'searchPolicies'.",
+ "Remember the tool 'querySchedules' is for finding schedules between cities, 'bookTicket' is"
+ + " for booking ticket actionable between 2 cities, 'searchPolicies' is for finding"
+ + " matching policies for this company.",
+ "Be intuitive and intelligent in finding the context even when user has typos. Do no"
+ + " hallucinate and make up stuff though. USe only data from the tools. ",
+ "Don't show any asterisks while listing results. Keep it formatted and numbered or bulleted."
+ + " asterisks distract."
+ })
+ String chat(@MemoryId String sessionId, @UserMessage String userMessage);
}
/**
- * 3. THE TOOLBOX BRIDGE
- * Wraps our asynchronous MCP Client calls into synchronous @Tools that LangChain4j (Gemini) can execute.
+ * 3. THE TOOLBOX BRIDGE Wraps our asynchronous MCP Client calls into synchronous @Tools that
+ * LangChain4j (Gemini) can execute.
*/
@Service
class TransitAgentTools {
-
- private final McpToolboxService mcpService;
-
- public TransitAgentTools(McpToolboxService mcpService) {
- this.mcpService = mcpService;
- }
- @Tool("Fetches the initial, broad dataset of all available bus schedules and routes. Use this to build your context.")
- public String findAllSchedules() {
- return mcpService.findAllSchedules().join();
- }
-
- @Tool("Query specific schedules between an origin and destination city. Use only when the user narrows down their request.")
- public String querySchedules(String origin, String destination) {
- return mcpService.querySchedules(origin, destination).join();
- }
-
- @Tool("Book a ticket for a passenger using a specific trip ID.")
- public String bookTicket(String tripId, String passengerName) {
- return mcpService.bookTicket(tripId, passengerName).join();
- }
-
- @Tool("Semantic search for transit policies regarding luggage, pets, refunds, and general rules.")
- public String searchPolicies(String searchQuery) {
- return mcpService.searchPolicies(searchQuery).join();
- }
+ private final McpToolboxService mcpService;
+
+ public TransitAgentTools(McpToolboxService mcpService) {
+ this.mcpService = mcpService;
+ }
+
+ @Tool(
+ "Fetches the initial, broad dataset of all available bus schedules and routes. Use this to"
+ + " build your context.")
+ public String findAllSchedules() {
+ return mcpService.findAllSchedules().join();
+ }
+
+ @Tool(
+ "Query specific schedules between an origin and destination city. Use only when the user"
+ + " narrows down their request.")
+ public String querySchedules(String origin, String destination) {
+ return mcpService.querySchedules(origin, destination).join();
+ }
+
+ @Tool("Book a ticket for a passenger using a specific trip ID.")
+ public String bookTicket(String tripId, String passengerName) {
+ return mcpService.bookTicket(tripId, passengerName).join();
+ }
+
+ @Tool("Semantic search for transit policies regarding luggage, pets, refunds, and general rules.")
+ public String searchPolicies(String searchQuery) {
+ return mcpService.searchPolicies(searchQuery).join();
+ }
}
/**
- * 4. THE MCP TOOLBOX SERVICE
- * Handles the actual connection and execution against the AlloyDB backend.
+ * 4. THE MCP TOOLBOX SERVICE Handles the actual connection and execution against the AlloyDB
+ * backend.
*/
@Service
class McpToolboxService {
-
- private McpToolboxClient mcpClient;
- private String idToken;
-
- @Value("${MCP_TOOLBOX_URL:fallback_toolbox_url}")
- private String targetUrl;
-
- @PostConstruct
- public void init() {
- try {
- String tokenAudience = targetUrl;
-
- System.out.println("--- Initializing MCP Toolbox Client ---");
-
- GoogleCredentials credentials = GoogleCredentials.getApplicationDefault();
- if (!(credentials instanceof IdTokenProvider)) {
- throw new RuntimeException("Loaded credentials do not support ID Tokens.");
- }
-
- this.idToken = ((IdTokenProvider) credentials)
- .idTokenWithAudience(tokenAudience, Collections.emptyList())
- .getTokenValue();
-
- this.mcpClient = McpToolboxClient.builder()
- .baseUrl(targetUrl)
- .apiKey(idToken)
- .build();
-
- mcpClient.listTools().thenAccept(tools -> {
- System.out.println("Successfully discovered " + tools.size() + " tools.");
- }).join();
- } catch (Exception e) {
- System.err.println("Failed to initialize MCP Toolbox Client:");
- e.printStackTrace();
- }
- }
+ private McpToolboxClient mcpClient;
+ private String idToken;
- public CompletableFuture findAllSchedules() {
- return mcpClient.invokeTool("find-bus-schedules", Collections.emptyMap()).thenApply(result -> {
- if (result.isError() || result.content() == null || result.content().isEmpty()) return "No schedules found.";
- //return result.content().get(0).text();
- //return result.text();
- return result.content().stream()
- .map(content -> content.text())
- .collect(Collectors.joining(", ", "[", "]"));
- });
- }
+ @Value("${MCP_TOOLBOX_URL:fallback_toolbox_url}")
+ private String targetUrl;
- public CompletableFuture querySchedules(String origin, String destination) {
- java.util.Map params = new java.util.HashMap<>();
- params.put("origin", origin);
- params.put("destination", destination);
- return mcpClient.invokeTool("query-schedules", params).thenApply(result -> {
- if (result.isError() || result.content() == null || result.content().isEmpty()) return "No specific schedules found.";
- System.out.println(result);
- return result.content().stream()
- .map(content -> content.text())
- .collect(Collectors.joining(", ", "[", "]"));
- });
- }
+ @PostConstruct
+ public void init() {
+ try {
+ String tokenAudience = targetUrl;
+
+ System.out.println("--- Initializing MCP Toolbox Client ---");
+
+ GoogleCredentials credentials = GoogleCredentials.getApplicationDefault();
+ if (!(credentials instanceof IdTokenProvider)) {
+ throw new RuntimeException("Loaded credentials do not support ID Tokens.");
+ }
+
+ this.idToken =
+ ((IdTokenProvider) credentials)
+ .idTokenWithAudience(tokenAudience, Collections.emptyList())
+ .getTokenValue();
- public CompletableFuture bookTicket(String tripId, String passengerName) {
- AuthTokenGetter toolAuthGetter = () -> CompletableFuture.completedFuture(idToken);
- return mcpClient.loadTool("book-ticket", Collections.singletonMap("google_auth", toolAuthGetter))
- .thenCompose(tool -> {
- tool.bindParam("passenger_name", passengerName);
- return tool.execute(Collections.singletonMap("trip_id", tripId));
+ this.mcpClient = McpToolboxClient.builder().baseUrl(targetUrl).apiKey(idToken).build();
+
+ mcpClient
+ .listTools()
+ .thenAccept(
+ tools -> {
+ System.out.println("Successfully discovered " + tools.size() + " tools.");
+ })
+ .join();
+
+ } catch (Exception e) {
+ System.err.println("Failed to initialize MCP Toolbox Client:");
+ e.printStackTrace();
+ }
+ }
+
+ public CompletableFuture findAllSchedules() {
+ return mcpClient
+ .invokeTool("find-bus-schedules", Collections.emptyMap())
+ .thenApply(
+ result -> {
+ if (result.isError() || result.content() == null || result.content().isEmpty())
+ return "No schedules found.";
+ // return result.content().get(0).text();
+ // return result.text();
+ return result.content().stream()
+ .map(content -> content.text())
+ .collect(Collectors.joining(", ", "[", "]"));
+ });
+ }
+
+ public CompletableFuture querySchedules(String origin, String destination) {
+ java.util.Map params = new java.util.HashMap<>();
+ params.put("origin", origin);
+ params.put("destination", destination);
+ return mcpClient
+ .invokeTool("query-schedules", params)
+ .thenApply(
+ result -> {
+ if (result.isError() || result.content() == null || result.content().isEmpty())
+ return "No specific schedules found.";
+ System.out.println(result);
+ return result.content().stream()
+ .map(content -> content.text())
+ .collect(Collectors.joining(", ", "[", "]"));
+ });
+ }
+
+ public CompletableFuture bookTicket(String tripId, String passengerName) {
+ AuthTokenGetter toolAuthGetter = () -> CompletableFuture.completedFuture(idToken);
+ return mcpClient
+ .loadTool("book-ticket", Collections.singletonMap("google_auth", toolAuthGetter))
+ .thenCompose(
+ tool -> {
+ tool.bindParam("passenger_name", passengerName);
+ return tool.execute(Collections.singletonMap("trip_id", tripId));
})
- .thenApply(result -> {
- if (result.isError() || result.content() == null || result.content().isEmpty()) {
- System.err.println("Tool execution failed: " + result.content().get(0).text());
- return "Transaction failed.";
- }
- return result.content().get(0).text();
+ .thenApply(
+ result -> {
+ if (result.isError() || result.content() == null || result.content().isEmpty()) {
+ System.err.println("Tool execution failed: " + result.content().get(0).text());
+ return "Transaction failed.";
+ }
+ return result.content().get(0).text();
});
- }
-
- public CompletableFuture searchPolicies(String searchQuery) {
- return mcpClient.invokeTool("search-policies", Map.of("search_query", searchQuery))
- .thenApply(result -> {
- if (result.isError() || result.content() == null || result.content().isEmpty()) return "No policy information found.";
- return result.content().stream()
- .map(content -> content.text())
- .collect(Collectors.joining(", ", "[", "]"));
+ }
+
+ public CompletableFuture searchPolicies(String searchQuery) {
+ return mcpClient
+ .invokeTool("search-policies", Map.of("search_query", searchQuery))
+ .thenApply(
+ result -> {
+ if (result.isError() || result.content() == null || result.content().isEmpty())
+ return "No policy information found.";
+ return result.content().stream()
+ .map(content -> content.text())
+ .collect(Collectors.joining(", ", "[", "]"));
});
- }
+ }
}
/**
- * 5. THE REST CONTROLLER
- * Now radically simplified! No more manual if/else logic or JSON parsing.
+ * 5. THE REST CONTROLLER Now radically simplified! No more manual if/else logic or JSON parsing.
*/
@RestController
@RequestMapping("/api/agent")
class TransitAgentController {
- private final TransitAgent transitAgent;
+ private final TransitAgent transitAgent;
- public TransitAgentController(TransitAgent transitAgent) {
- this.transitAgent = transitAgent;
- }
+ public TransitAgentController(TransitAgent transitAgent) {
+ this.transitAgent = transitAgent;
+ }
- @PostMapping("/chat")
- public ResponseEntity handleUserChat(@RequestBody String userMessage, HttpSession session) {
- // We use the HTTP Session ID to tell LangChain4j which memory context to load
- String sessionId = session.getId();
-
- // Let Gemini 3 Flash handle the thinking, tool execution, and response generation!
- String agentResponse = transitAgent.chat(sessionId, userMessage);
-
- return ResponseEntity.ok(agentResponse);
- }
+ @PostMapping("/chat")
+ public ResponseEntity handleUserChat(
+ @RequestBody String userMessage, HttpSession session) {
+ // We use the HTTP Session ID to tell LangChain4j which memory context to load
+ String sessionId = session.getId();
+
+ // Let Gemini 3 Flash handle the thinking, tool execution, and response generation!
+ String agentResponse = transitAgent.chat(sessionId, userMessage);
+
+ return ResponseEntity.ok(agentResponse);
+ }
}
diff --git a/example/pom.xml b/example/pom.xml
index 53ca764..551141e 100644
--- a/example/pom.xml
+++ b/example/pom.xml
@@ -33,7 +33,7 @@
com.google.cloud.mcp
mcp-toolbox-sdk-java
- 0.2.0
+ 0.2.1-SNAPSHOT
diff --git a/example/src/main/java/cloudcode/helloworld/ExampleUsage.java b/example/src/main/java/cloudcode/helloworld/ExampleUsage.java
index 504886c..70e18e5 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.McpToolboxClient;
+import com.google.cloud.mcp.auth.AuthTokenGetter;
+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 = "YOUR_TOOLBOX_SERVICE_ENDPOINT";
-
- // Match the Service URL if using Cloud Run OIDC
- 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 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 = "YOUR_TOOLBOX_SERVICE_ENDPOINT";
+
+ // Match the Service URL if using Cloud Run OIDC
+ 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 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/example/src/main/java/cloudcode/helloworld/InputValidationTest.java b/example/src/main/java/cloudcode/helloworld/InputValidationTest.java
index b171ae7..4971e25 100644
--- a/example/src/main/java/cloudcode/helloworld/InputValidationTest.java
+++ b/example/src/main/java/cloudcode/helloworld/InputValidationTest.java
@@ -16,100 +16,101 @@
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 com.google.cloud.mcp.McpToolboxClient;
+import com.google.cloud.mcp.tool.Tool;
import java.io.FileInputStream;
import java.util.Collections;
-import java.util.Map;
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.
- // --------------------------------------------------------------------------------
+ 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";
+ String keyPath = "/YOUR_CREDENTIALS_JSON_FILE_PATH.json";
- System.out.println("--- Starting MCP Toolbox Input Validation Test ---");
+ 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();
+ 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();
+ // 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();
+ // 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));
+ // 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 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 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
- // --- 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());
- }
+ // 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 ---");
+ } 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/helloworld/StrictFlagTest.java
index ba32ff9..df01a8c 100644
--- a/example/src/main/java/cloudcode/helloworld/StrictFlagTest.java
+++ b/example/src/main/java/cloudcode/helloworld/StrictFlagTest.java
@@ -17,18 +17,16 @@
package cloudcode.helloworld;
import com.google.cloud.mcp.McpToolboxClient;
-import com.google.cloud.mcp.Tool;
-import java.util.Map;
+import com.google.cloud.mcp.tool.Tool;
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/main/java/com/google/cloud/mcp/JsonRpc.java b/src/main/java/com/google/cloud/mcp/JsonRpc.java
index 902ac2a..a4cd1ef 100644
--- a/src/main/java/com/google/cloud/mcp/JsonRpc.java
+++ b/src/main/java/com/google/cloud/mcp/JsonRpc.java
@@ -16,50 +16,53 @@
package com.google.cloud.mcp;
+import com.fasterxml.jackson.annotation.JsonInclude;
import java.util.Map;
import java.util.UUID;
-class JsonRpc {
- static class Request {
+public class JsonRpc {
+ public static class Request {
public String jsonrpc = "2.0";
public String id;
public String method;
public Object params;
- public Request(String method, Object params) {
+ public Request(final String method, final Object params) {
this.id = UUID.randomUUID().toString();
this.method = method;
this.params = params;
}
}
- static class Notification {
+ public static class Notification {
public String jsonrpc = "2.0";
public String method;
public Object params;
- public Notification(String method, Object params) {
+ public Notification(final String method, final Object params) {
this.method = method;
this.params = params;
}
}
- static class CallToolParams {
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ public static class CallToolParams {
public String name;
public Map arguments;
+ public Map _meta;
- public CallToolParams(String name, Map arguments) {
+ public CallToolParams(final String name, final Map arguments) {
this.name = name;
this.arguments = arguments;
}
}
- static class InitializeParams {
+ public static class InitializeParams {
public String protocolVersion;
public Map capabilities;
public Map clientInfo;
- public InitializeParams(String version, String clientName) {
+ public InitializeParams(final String version, final String clientName) {
this.protocolVersion = version;
this.capabilities = Map.of();
this.clientInfo = Map.of("name", clientName, "version", "1.0.0");
diff --git a/src/main/java/com/google/cloud/mcp/McpToolboxClient.java b/src/main/java/com/google/cloud/mcp/McpToolboxClient.java
index 472a6f3..ff79e04 100644
--- a/src/main/java/com/google/cloud/mcp/McpToolboxClient.java
+++ b/src/main/java/com/google/cloud/mcp/McpToolboxClient.java
@@ -16,6 +16,14 @@
package com.google.cloud.mcp;
+import com.google.cloud.mcp.auth.AuthTokenGetter;
+import com.google.cloud.mcp.auth.CredentialsProvider;
+import com.google.cloud.mcp.client.McpToolboxClientBuilder;
+import com.google.cloud.mcp.tool.Tool;
+import com.google.cloud.mcp.tool.ToolDefinition;
+import com.google.cloud.mcp.tool.ToolPostProcessor;
+import com.google.cloud.mcp.tool.ToolPreProcessor;
+import com.google.cloud.mcp.tool.ToolResult;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
diff --git a/src/main/java/com/google/cloud/mcp/ProtocolVersion.java b/src/main/java/com/google/cloud/mcp/ProtocolVersion.java
index ab661e5..f60869b 100644
--- a/src/main/java/com/google/cloud/mcp/ProtocolVersion.java
+++ b/src/main/java/com/google/cloud/mcp/ProtocolVersion.java
@@ -18,6 +18,9 @@
/** Supported protocol versions for the Model Context Protocol. */
public enum ProtocolVersion {
+ /** Protocol version 2026-06-18. */
+ VERSION_2026_06_18("2026-06-18", true, true, false),
+
/** Protocol version 2025-11-25. */
VERSION_2025_11_25("2025-11-25", true, true, false),
diff --git a/src/main/java/com/google/cloud/mcp/AuthMethods.java b/src/main/java/com/google/cloud/mcp/auth/AuthMethods.java
similarity index 98%
rename from src/main/java/com/google/cloud/mcp/AuthMethods.java
rename to src/main/java/com/google/cloud/mcp/auth/AuthMethods.java
index cb3815f..a287009 100644
--- a/src/main/java/com/google/cloud/mcp/AuthMethods.java
+++ b/src/main/java/com/google/cloud/mcp/auth/AuthMethods.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.google.cloud.mcp;
+package com.google.cloud.mcp.auth;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.auth.oauth2.IdTokenProvider;
diff --git a/src/main/java/com/google/cloud/mcp/AuthResolver.java b/src/main/java/com/google/cloud/mcp/auth/AuthResolver.java
similarity index 98%
rename from src/main/java/com/google/cloud/mcp/AuthResolver.java
rename to src/main/java/com/google/cloud/mcp/auth/AuthResolver.java
index 109c6ba..0fccadd 100644
--- a/src/main/java/com/google/cloud/mcp/AuthResolver.java
+++ b/src/main/java/com/google/cloud/mcp/auth/AuthResolver.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.google.cloud.mcp;
+package com.google.cloud.mcp.auth;
import java.util.HashMap;
import java.util.List;
diff --git a/src/main/java/com/google/cloud/mcp/AuthTokenGetter.java b/src/main/java/com/google/cloud/mcp/auth/AuthTokenGetter.java
similarity index 96%
rename from src/main/java/com/google/cloud/mcp/AuthTokenGetter.java
rename to src/main/java/com/google/cloud/mcp/auth/AuthTokenGetter.java
index 5d3f736..6067352 100644
--- a/src/main/java/com/google/cloud/mcp/AuthTokenGetter.java
+++ b/src/main/java/com/google/cloud/mcp/auth/AuthTokenGetter.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.google.cloud.mcp;
+package com.google.cloud.mcp.auth;
import java.util.concurrent.CompletableFuture;
diff --git a/src/main/java/com/google/cloud/mcp/CredentialsProvider.java b/src/main/java/com/google/cloud/mcp/auth/CredentialsProvider.java
similarity index 96%
rename from src/main/java/com/google/cloud/mcp/CredentialsProvider.java
rename to src/main/java/com/google/cloud/mcp/auth/CredentialsProvider.java
index eb428a0..a9c7ca4 100644
--- a/src/main/java/com/google/cloud/mcp/CredentialsProvider.java
+++ b/src/main/java/com/google/cloud/mcp/auth/CredentialsProvider.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.google.cloud.mcp;
+package com.google.cloud.mcp.auth;
import java.util.concurrent.CompletableFuture;
diff --git a/src/main/java/com/google/cloud/mcp/GoogleCredentialsProvider.java b/src/main/java/com/google/cloud/mcp/auth/GoogleCredentialsProvider.java
similarity index 98%
rename from src/main/java/com/google/cloud/mcp/GoogleCredentialsProvider.java
rename to src/main/java/com/google/cloud/mcp/auth/GoogleCredentialsProvider.java
index 11a7232..bb1490e 100644
--- a/src/main/java/com/google/cloud/mcp/GoogleCredentialsProvider.java
+++ b/src/main/java/com/google/cloud/mcp/auth/GoogleCredentialsProvider.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.google.cloud.mcp;
+package com.google.cloud.mcp.auth;
import com.google.auth.oauth2.GoogleCredentials;
import java.io.IOException;
diff --git a/src/main/java/com/google/cloud/mcp/ResolvedAuth.java b/src/main/java/com/google/cloud/mcp/auth/ResolvedAuth.java
similarity index 97%
rename from src/main/java/com/google/cloud/mcp/ResolvedAuth.java
rename to src/main/java/com/google/cloud/mcp/auth/ResolvedAuth.java
index 017d7e7..e79ecfb 100644
--- a/src/main/java/com/google/cloud/mcp/ResolvedAuth.java
+++ b/src/main/java/com/google/cloud/mcp/auth/ResolvedAuth.java
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.google.cloud.mcp;
+package com.google.cloud.mcp.auth;
+import com.google.cloud.mcp.tool.ToolDefinition;
import java.util.Map;
/** Represents a resolved set of authentication credentials for a tool execution. */
diff --git a/src/main/java/com/google/cloud/mcp/McpToolboxClientBuilder.java b/src/main/java/com/google/cloud/mcp/client/McpToolboxClientBuilder.java
similarity index 91%
rename from src/main/java/com/google/cloud/mcp/McpToolboxClientBuilder.java
rename to src/main/java/com/google/cloud/mcp/client/McpToolboxClientBuilder.java
index 7c2f765..0b66c13 100644
--- a/src/main/java/com/google/cloud/mcp/McpToolboxClientBuilder.java
+++ b/src/main/java/com/google/cloud/mcp/client/McpToolboxClientBuilder.java
@@ -14,8 +14,15 @@
* limitations under the License.
*/
-package com.google.cloud.mcp;
-
+package com.google.cloud.mcp.client;
+
+import com.google.cloud.mcp.McpToolboxClient;
+import com.google.cloud.mcp.ProtocolVersion;
+import com.google.cloud.mcp.auth.CredentialsProvider;
+import com.google.cloud.mcp.tool.ToolPostProcessor;
+import com.google.cloud.mcp.tool.ToolPreProcessor;
+import com.google.cloud.mcp.transport.HttpMcpTransport;
+import com.google.cloud.mcp.transport.Transport;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
diff --git a/src/main/java/com/google/cloud/mcp/McpToolboxClientImpl.java b/src/main/java/com/google/cloud/mcp/client/McpToolboxClientImpl.java
similarity index 81%
rename from src/main/java/com/google/cloud/mcp/McpToolboxClientImpl.java
rename to src/main/java/com/google/cloud/mcp/client/McpToolboxClientImpl.java
index c5d254b..603d255 100644
--- a/src/main/java/com/google/cloud/mcp/McpToolboxClientImpl.java
+++ b/src/main/java/com/google/cloud/mcp/client/McpToolboxClientImpl.java
@@ -14,10 +14,22 @@
* limitations under the License.
*/
-package com.google.cloud.mcp;
+package com.google.cloud.mcp.client;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.cloud.mcp.McpToolboxClient;
+import com.google.cloud.mcp.auth.AuthTokenGetter;
+import com.google.cloud.mcp.auth.CredentialsProvider;
+import com.google.cloud.mcp.tool.Tool;
+import com.google.cloud.mcp.tool.ToolDefinition;
+import com.google.cloud.mcp.tool.ToolPostProcessor;
+import com.google.cloud.mcp.tool.ToolPreProcessor;
+import com.google.cloud.mcp.tool.ToolResult;
+import com.google.cloud.mcp.transport.HttpMcpTransport;
+import com.google.cloud.mcp.transport.Transport;
+import com.google.cloud.mcp.transport.TransportManifest;
+import com.google.cloud.mcp.transport.TransportResponse;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -222,12 +234,14 @@ public CompletableFuture