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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,15 +216,19 @@ public class App {
For a detailed example, check the ExampleUsage.java file in the example folder of this repo.

> [!NOTE]
>
> The SDK is Async-First, using Java's `CompletableFuture` to bridge both patterns naturally.
> - Asynchronous: Chain methods using `.thenCompose()`, `.thenAccept()`, and `.exceptionally()` for non-blocking execution.
> - If you prefer synchronous execution, simply call `.join()` on the result to block until completion.
> The SDK is async-first, but provides a pure synchronous blocking experience using `McpToolboxSyncClient`.
> - **Asynchronous Client (`McpToolboxClient`):** Chain methods using `.thenCompose()`, `.thenAccept()`, and `.exceptionally()`.
> - **Synchronous Client (`McpToolboxSyncClient`):** Call blocking methods directly. Under the hood, it manages threads safely and translates checked/execution exceptions into unchecked `McpToolboxException`s.

```java
// Async (Non-blocking)
client.invokeTool("tool-name", args).thenAccept(result -> ...);
// Sync (Blocking)
ToolResult result = client.invokeTool("tool-name", args).join();
// Create a Synchronous Client
McpToolboxSyncClient client = McpToolboxClient.builder()
.baseUrl("https://my-toolbox-service.a.run.app/mcp")
.buildSync();

// Invoke a tool synchronously
ToolResult result = client.invokeTool("get-toy-price", Map.of("description", "plush dinosaur"));
System.out.println("Output: " + result.content().get(0).text());
```

## Authentication
Expand Down
4 changes: 2 additions & 2 deletions integration.cloudbuild.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ steps:
- id: Install library requirements
name: 'maven:3.9.6-eclipse-temurin-17'
entrypoint: 'mvn'
args: ['clean', 'install', '-DskipTests']
args: ['-B', '-ntp', 'clean', 'install', '-DskipTests']
- id: Run integration tests
name: 'maven:3.9.6-eclipse-temurin-17'
entrypoint: 'mvn'
args: ['test']
args: ['-B', '-ntp', 'test']
env:
- GOOGLE_CLOUD_PROJECT=$PROJECT_ID
- TOOLBOX_VERSION=${_TOOLBOX_VERSION}
Expand Down
31 changes: 31 additions & 0 deletions src/main/java/com/google/cloud/mcp/McpToolboxClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,22 @@ interface Builder {
*/
Builder protocolVersion(ProtocolVersion protocolVersion);

/**
* Sets the connect timeout for the underlying HttpClient.
*
* @param connectTimeout The connect timeout.
* @return The builder instance.
*/
Builder connectTimeout(java.time.Duration connectTimeout);

/**
* Sets the request timeout for every HTTP request.
*
* @param requestTimeout The request timeout.
* @return The builder instance.
*/
Builder requestTimeout(java.time.Duration requestTimeout);

/**
* Sets a custom {@link java.net.http.HttpClient} for connection management.
*
Expand All @@ -191,11 +207,26 @@ interface Builder {
*/
Builder executor(java.util.concurrent.Executor executor);

/**
* Sets a custom Logger for telemetry and logs.
*
* @param logger The custom Logger.
* @return The builder instance.
*/
Builder logger(java.util.logging.Logger logger);

/**
* Builds and returns a new {@link McpToolboxClient} instance.
*
* @return The new client instance.
*/
McpToolboxClient build();

/**
* Builds and returns a new {@link McpToolboxSyncClient} instance.
*
* @return The new synchronous client instance.
*/
McpToolboxSyncClient buildSync();
}
}
112 changes: 112 additions & 0 deletions src/main/java/com/google/cloud/mcp/McpToolboxSyncClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
* 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;

import com.google.cloud.mcp.auth.AuthTokenGetter;
import com.google.cloud.mcp.exception.McpToolboxException;
import com.google.cloud.mcp.tool.Tool;
import com.google.cloud.mcp.tool.ToolDefinition;
import com.google.cloud.mcp.tool.ToolResult;
import java.util.Map;

/** The synchronous blocking client for interacting with MCP Toolbox. */
public interface McpToolboxSyncClient {

/**
* Connects to the MCP Server and retrieves the list of all available tools.
*
* @return The map of Tool definitions (Key: Tool Name).
* @throws McpToolboxException if any error occurs.
*/
Map<String, ToolDefinition> listTools();

/**
* Loads the toolset from the MCP Server. Alias for {@link #listTools()}.
*
* @return The map of Tool definitions (Key: Tool Name).
* @throws McpToolboxException if any error occurs.
*/
default Map<String, ToolDefinition> loadToolset() {
return listTools();
}

/**
* Loads a specific toolset by name (if supported by server).
*
* @param toolsetName The name of the toolset to load.
* @return The map of Tool definitions (Key: Tool Name).
* @throws McpToolboxException if any error occurs.
*/
Map<String, ToolDefinition> loadToolset(String toolsetName);

/**
* Loads a toolset (or all tools if toolsetName is null) and applies bindings.
*
* @param toolsetName Name of the toolset to load (or null for all).
* @param paramBinds Map of Tool Name -> (Parameter Name -> Value).
* @param authBinds Map of Tool Name -> (Service -> Token Getter).
* @param strict Throws exception if bindings refer to non-existent tools.
* @return A Map of ready-to-use Tool objects.
* @throws McpToolboxException if any error occurs.
*/
Map<String, Tool> loadToolset(
String toolsetName,
Map<String, Map<String, Object>> paramBinds,
Map<String, Map<String, AuthTokenGetter>> authBinds,
boolean strict);

/**
* Loads a specific tool definition and returns a smart Tool object.
*
* @param toolName The name of the tool to load.
* @return The Tool object.
* @throws McpToolboxException if any error occurs.
*/
Tool loadTool(String toolName);

/**
* Loads a specific tool and registers authentication getters immediately.
*
* @param toolName The name of the tool.
* @param authTokenGetters A map of Service Name -> Token Getter Function.
* @return The Tool object.
* @throws McpToolboxException if any error occurs.
*/
Tool loadTool(String toolName, Map<String, AuthTokenGetter> authTokenGetters);

/**
* Low-level invocation method.
*
* @param toolName The name of the tool to invoke.
* @param arguments The arguments to pass to the tool.
* @return The result of the tool invocation.
* @throws McpToolboxException if any error occurs.
*/
ToolResult invokeTool(String toolName, Map<String, Object> arguments);

/**
* Low-level invocation method with explicit headers.
*
* @param toolName The name of the tool to invoke.
* @param arguments The arguments to pass to the tool.
* @param extraHeaders Additional HTTP headers to include in the request.
* @return The result of the tool invocation.
* @throws McpToolboxException if any error occurs.
*/
ToolResult invokeTool(
String toolName, Map<String, Object> arguments, Map<String, String> extraHeaders);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/*
* 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.client;

import com.google.cloud.mcp.McpToolboxClient;
import com.google.cloud.mcp.McpToolboxSyncClient;
import com.google.cloud.mcp.auth.AuthTokenGetter;
import com.google.cloud.mcp.exception.McpToolboxException;
import com.google.cloud.mcp.tool.Tool;
import com.google.cloud.mcp.tool.ToolDefinition;
import com.google.cloud.mcp.tool.ToolResult;
import java.util.Map;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletionException;

/**
* Synchronous client wrapper that delegates to an async {@link McpToolboxClient} and blocks the
* calling thread, unwrapping exceptions.
*/
public final class HttpMcpToolboxSyncClient implements McpToolboxSyncClient {
/** The underlying asynchronous client to delegate to. */
private final McpToolboxClient delegate;

/**
* Constructs a new HttpMcpToolboxSyncClient wrapping the given async client.
*
* @param client The async client to delegate to.
*/
public HttpMcpToolboxSyncClient(final McpToolboxClient client) {
if (client == null) {
throw new IllegalArgumentException("Delegate client cannot be null");
}
this.delegate = client;
}

@Override
public Map<String, ToolDefinition> listTools() {
try {
return delegate.listTools().join();
} catch (CompletionException | CancellationException e) {
throw unwrapException(e);
}
}

@Override
public Map<String, ToolDefinition> loadToolset(final String toolsetName) {
try {
return delegate.loadToolset(toolsetName).join();
} catch (CompletionException | CancellationException e) {
throw unwrapException(e);
}
}

@Override
public Map<String, Tool> loadToolset(
final String toolsetName,
final Map<String, Map<String, Object>> paramBinds,
final Map<String, Map<String, AuthTokenGetter>> authBinds,
final boolean strict) {
try {
return delegate.loadToolset(toolsetName, paramBinds, authBinds, strict).join();
} catch (CompletionException | CancellationException e) {
throw unwrapException(e);
}
}

@Override
public Tool loadTool(final String toolName) {
try {
return delegate.loadTool(toolName).join();
} catch (CompletionException | CancellationException e) {
throw unwrapException(e);
}
}

@Override
public Tool loadTool(final String toolName, final Map<String, AuthTokenGetter> authTokenGetters) {
try {
return delegate.loadTool(toolName, authTokenGetters).join();
} catch (CompletionException | CancellationException e) {
throw unwrapException(e);
}
}

@Override
public ToolResult invokeTool(final String toolName, final Map<String, Object> arguments) {
try {
return delegate.invokeTool(toolName, arguments).join();
} catch (CompletionException | CancellationException e) {
throw unwrapException(e);
}
}

@Override
public ToolResult invokeTool(
final String toolName,
final Map<String, Object> arguments,
final Map<String, String> extraHeaders) {
try {
return delegate.invokeTool(toolName, arguments, extraHeaders).join();
} catch (CompletionException | CancellationException e) {
throw unwrapException(e);
}
}

private RuntimeException unwrapException(final Throwable e) {
final Throwable cause = e.getCause();
if (cause == null) {
return new McpToolboxException(e);
}
if (cause instanceof McpToolboxException) {
return (McpToolboxException) cause;
}
if (cause instanceof IllegalArgumentException) {
return (IllegalArgumentException) cause;
}
return new McpToolboxException(cause.getMessage(), cause);
}
}
Loading
Loading