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
33 changes: 33 additions & 0 deletions src/main/java/com/google/cloud/mcp/HttpMcpToolboxClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ public class HttpMcpToolboxClient implements McpToolboxClient {
private final ObjectMapper objectMapper;
private boolean initialized = false;
private final String protocolVersion = "2025-11-25";
private final List<ToolPreProcessor> preProcessors;
private final List<ToolPostProcessor> postProcessors;

/**
* Constructs a new HttpMcpToolboxClient.
Expand All @@ -52,10 +54,29 @@ public class HttpMcpToolboxClient implements McpToolboxClient {
* @param apiKey The API key for authentication (optional).
*/
public HttpMcpToolboxClient(String baseUrl, String apiKey) {
this(baseUrl, apiKey, Collections.emptyList(), Collections.emptyList());
}

/**
* Constructs a new HttpMcpToolboxClient with processors.
*
* @param baseUrl The base URL of the MCP Toolbox Server.
* @param apiKey The API key for authentication (optional).
* @param preProcessors The pre-processors to apply.
* @param postProcessors The post-processors to apply.
*/
public HttpMcpToolboxClient(
String baseUrl,
String apiKey,
List<ToolPreProcessor> preProcessors,
List<ToolPostProcessor> postProcessors) {
this.baseUrl = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
this.apiKey = apiKey;
this.httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
this.objectMapper = new ObjectMapper();
this.preProcessors = preProcessors != null ? new ArrayList<>(preProcessors) : new ArrayList<>();
this.postProcessors =
postProcessors != null ? new ArrayList<>(postProcessors) : new ArrayList<>();
}

private synchronized CompletableFuture<Void> ensureInitialized(String authHeader) {
Expand Down Expand Up @@ -177,6 +198,12 @@ public CompletableFuture<Map<String, Tool>> loadToolset(
if (authBinds != null && authBinds.containsKey(toolName)) {
authBinds.get(toolName).forEach(tool::addAuthTokenGetter);
}
for (ToolPreProcessor preProcessor : this.preProcessors) {
tool.addPreProcessor(preProcessor);
}
for (ToolPostProcessor postProcessor : this.postProcessors) {
tool.addPostProcessor(postProcessor);
}
tools.put(toolName, tool);
}
return tools;
Expand All @@ -201,6 +228,12 @@ public CompletableFuture<Tool> loadTool(
if (authTokenGetters != null) {
authTokenGetters.forEach(tool::addAuthTokenGetter);
}
for (ToolPreProcessor preProcessor : this.preProcessors) {
tool.addPreProcessor(preProcessor);
}
for (ToolPostProcessor postProcessor : this.postProcessors) {
tool.addPostProcessor(postProcessor);
}
return tool;
});
}
Expand Down
16 changes: 16 additions & 0 deletions src/main/java/com/google/cloud/mcp/McpToolboxClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,22 @@ interface Builder {
*/
Builder apiKey(String apiKey);

/**
* Adds a global pre-processor that will be applied to all tools loaded by this client.
*
* @param preProcessor The pre-processor to add.
* @return The builder instance.
*/
Builder preProcessor(ToolPreProcessor preProcessor);

/**
* Adds a global post-processor that will be applied to all tools loaded by this client.
*
* @param postProcessor The post-processor to add.
* @return The builder instance.
*/
Builder postProcessor(ToolPostProcessor postProcessor);

/**
* Builds and returns a new {@link McpToolboxClient} instance.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,15 @@

package com.google.cloud.mcp;

import java.util.ArrayList;
import java.util.List;

/** Implementation of the {@link McpToolboxClient.Builder} interface. */
public class McpToolboxClientBuilder implements McpToolboxClient.Builder {
private String baseUrl;
private String apiKey;
private final List<ToolPreProcessor> preProcessors = new ArrayList<>();
private final List<ToolPostProcessor> postProcessors = new ArrayList<>();

/** Constructs a new McpToolboxClientBuilder. */
public McpToolboxClientBuilder() {}
Expand All @@ -36,6 +41,22 @@ public McpToolboxClient.Builder apiKey(String apiKey) {
return this;
}

@Override
public McpToolboxClient.Builder preProcessor(ToolPreProcessor preProcessor) {
if (preProcessor != null) {
this.preProcessors.add(preProcessor);
}
return this;
}

@Override
public McpToolboxClient.Builder postProcessor(ToolPostProcessor postProcessor) {
if (postProcessor != null) {
this.postProcessors.add(postProcessor);
}
return this;
}

@Override
public McpToolboxClient build() {
if (baseUrl == null || baseUrl.isEmpty()) {
Expand All @@ -45,6 +66,6 @@ public McpToolboxClient build() {
if (baseUrl.endsWith("/")) {
baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
}
return new HttpMcpToolboxClient(baseUrl, apiKey);
return new HttpMcpToolboxClient(baseUrl, apiKey, preProcessors, postProcessors);
}
}
135 changes: 90 additions & 45 deletions src/main/java/com/google/cloud/mcp/Tool.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@

package com.google.cloud.mcp;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
Expand All @@ -33,6 +35,8 @@ public class Tool {

private final Map<String, Object> boundParameters = new HashMap<>();
private final Map<String, AuthTokenGetter> authGetters = new HashMap<>();
private final List<ToolPreProcessor> preProcessors = new ArrayList<>();
private final List<ToolPostProcessor> postProcessors = new ArrayList<>();

/**
* Constructs a new Tool.
Expand Down Expand Up @@ -101,6 +105,28 @@ public Tool addAuthTokenGetter(String serviceName, AuthTokenGetter getter) {
return this;
}

/**
* Adds a pre-processor to the tool.
*
* @param processor The pre-processor to add.
* @return The tool instance.
*/
public Tool addPreProcessor(ToolPreProcessor processor) {
this.preProcessors.add(processor);
return this;
}

/**
* Adds a post-processor to the tool.
*
* @param processor The post-processor to add.
* @return The tool instance.
*/
public Tool addPostProcessor(ToolPostProcessor processor) {
this.postProcessors.add(processor);
return this;
}

/**
* Executes the tool with the provided arguments, applying any bound parameters and resolving
* authentication tokens.
Expand All @@ -109,55 +135,74 @@ public Tool addAuthTokenGetter(String serviceName, AuthTokenGetter getter) {
* @return A CompletableFuture containing the result of the tool execution.
*/
public CompletableFuture<ToolResult> execute(Map<String, Object> args) {
Map<String, Object> finalArgs = new HashMap<>(args);
Map<String, String> extraHeaders = new HashMap<>();

// 1. Apply Bound Parameters
for (Map.Entry<String, Object> entry : boundParameters.entrySet()) {
Object val = entry.getValue();
if (val instanceof Supplier) {
finalArgs.put(entry.getKey(), ((Supplier<?>) val).get());
} else {
finalArgs.put(entry.getKey(), val);
}
CompletableFuture<Map<String, Object>> argsFuture =
CompletableFuture.completedFuture(new HashMap<>(args));

for (ToolPreProcessor preProcessor : preProcessors) {
argsFuture = argsFuture.thenCompose(currentArgs -> preProcessor.process(name, currentArgs));
}

// 2. Resolve Auth Tokens
return CompletableFuture.allOf(
authGetters.entrySet().stream()
.map(
entry -> {
String serviceName = entry.getKey();
return entry
.getValue()
.getToken()
.thenAccept(
token -> {
// A. Check if mapped to a Parameter (Authenticated Parameters)
String paramName = findParameterForService(serviceName);
if (paramName != null) {
finalArgs.put(paramName, token);
}

// B. Always add to Headers to support Authorized Invocation
// 1. Standard OIDC Header (Cloud Run)
extraHeaders.put("Authorization", "Bearer " + token);

// 2. SDK Convention Header (Framework Compatibility)
extraHeaders.put(serviceName + "_token", token);
});
})
.toArray(CompletableFuture[]::new))
.thenCompose(
v -> {
try {
// 3. Validation & Cleanup
validateAndSanitizeArgs(finalArgs);
return client.invokeTool(name, finalArgs, extraHeaders);
} catch (Exception e) {
return CompletableFuture.failedFuture(e);
CompletableFuture<ToolResult> resultFuture =
argsFuture.thenCompose(
processedArgs -> {
Map<String, Object> finalArgs = new HashMap<>(processedArgs);
Map<String, String> extraHeaders = new HashMap<>();

// 1. Apply Bound Parameters
for (Map.Entry<String, Object> entry : boundParameters.entrySet()) {
Object val = entry.getValue();
if (val instanceof Supplier) {
finalArgs.put(entry.getKey(), ((Supplier<?>) val).get());
} else {
finalArgs.put(entry.getKey(), val);
}
}

// 2. Resolve Auth Tokens
return CompletableFuture.allOf(
authGetters.entrySet().stream()
.map(
entry -> {
String serviceName = entry.getKey();
return entry
.getValue()
.getToken()
.thenAccept(
token -> {
// A. Check if mapped to a Parameter (Authenticated
// Parameters)
String paramName = findParameterForService(serviceName);
if (paramName != null) {
finalArgs.put(paramName, token);
}

// B. Always add to Headers to support Authorized
// Invocation
// 1. Standard OIDC Header (Cloud Run)
extraHeaders.put("Authorization", "Bearer " + token);

// 2. SDK Convention Header (Framework Compatibility)
extraHeaders.put(serviceName + "_token", token);
});
})
.toArray(CompletableFuture[]::new))
.thenCompose(
v -> {
try {
// 3. Validation & Cleanup
validateAndSanitizeArgs(finalArgs);
return client.invokeTool(name, finalArgs, extraHeaders);
} catch (Exception e) {
return CompletableFuture.failedFuture(e);
}
});
});

for (ToolPostProcessor postProcessor : postProcessors) {
resultFuture = resultFuture.thenCompose(res -> postProcessor.process(name, res));
}

return resultFuture;
}

private String findParameterForService(String serviceName) {
Expand Down
33 changes: 33 additions & 0 deletions src/main/java/com/google/cloud/mcp/ToolPostProcessor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* 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 java.util.concurrent.CompletableFuture;

/** A functional interface for post-processing tool results after invocation. */
@FunctionalInterface
public interface ToolPostProcessor {

/**
* Processes the result of a tool after it has been invoked.
*
* @param toolName The name of the tool that was invoked.
* @param result The original tool result.
* @return A CompletableFuture containing the processed tool result.
*/
CompletableFuture<ToolResult> process(String toolName, ToolResult result);
}
34 changes: 34 additions & 0 deletions src/main/java/com/google/cloud/mcp/ToolPreProcessor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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 java.util.Map;
import java.util.concurrent.CompletableFuture;

/** A functional interface for pre-processing tool inputs before invocation. */
@FunctionalInterface
public interface ToolPreProcessor {

/**
* Processes the input arguments for a tool before it is invoked.
*
* @param toolName The name of the tool being invoked.
* @param arguments The original arguments provided to the tool.
* @return A CompletableFuture containing the processed arguments.
*/
CompletableFuture<Map<String, Object>> process(String toolName, Map<String, Object> arguments);
}
Loading
Loading