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
Original file line number Diff line number Diff line change
Expand Up @@ -230,10 +230,9 @@ public CompletableFuture<String> bookTicket(String tripId, String passengerName)
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));
})
tool ->
tool.bindParam("passenger_name", passengerName)
.execute(Collections.singletonMap("trip_id", tripId)))
.thenApply(
result -> {
if (result.isError() || result.content() == null || result.content().isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,14 +144,14 @@ public static void main(String[] args) {

// 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"));
return tool.bindParam("description", "soft toy")
.execute(Map.of("description", "barbie"));
});
})
.thenAccept(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,16 +279,20 @@ public CompletableFuture<Map<String, Tool>> loadToolset(
String toolName = entry.getKey();
Tool tool = new Tool(toolName, entry.getValue(), this);
if (paramBinds != null && paramBinds.containsKey(toolName)) {
paramBinds.get(toolName).forEach(tool::bindParam);
for (Map.Entry<String, Object> bind : paramBinds.get(toolName).entrySet()) {
tool = tool.bindParam(bind.getKey(), bind.getValue());
}
}
if (authBinds != null && authBinds.containsKey(toolName)) {
authBinds.get(toolName).forEach(tool::addAuthTokenGetter);
for (Map.Entry<String, AuthTokenGetter> bind : authBinds.get(toolName).entrySet()) {
tool = tool.addAuthTokenGetter(bind.getKey(), bind.getValue());
}
}
for (ToolPreProcessor preProcessor : this.preProcessors) {
tool.addPreProcessor(preProcessor);
tool = tool.addPreProcessor(preProcessor);
}
for (ToolPostProcessor postProcessor : this.postProcessors) {
tool.addPostProcessor(postProcessor);
tool = tool.addPostProcessor(postProcessor);
}
tools.put(toolName, tool);
}
Expand Down Expand Up @@ -317,13 +321,15 @@ public CompletableFuture<Tool> loadTool(
}
Tool tool = new Tool(toolName, tools.get(toolName), this);
if (authTokenGetters != null) {
authTokenGetters.forEach(tool::addAuthTokenGetter);
for (Map.Entry<String, AuthTokenGetter> entry : authTokenGetters.entrySet()) {
tool = tool.addAuthTokenGetter(entry.getKey(), entry.getValue());
}
}
for (ToolPreProcessor preProcessor : this.preProcessors) {
tool.addPreProcessor(preProcessor);
tool = tool.addPreProcessor(preProcessor);
}
for (ToolPostProcessor postProcessor : this.postProcessors) {
tool.addPostProcessor(postProcessor);
tool = tool.addPostProcessor(postProcessor);
}
return tool;
});
Expand Down
170 changes: 133 additions & 37 deletions src/main/java/com/google/cloud/mcp/tool/Tool.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.google.cloud.mcp.auth.AuthResolver;
import com.google.cloud.mcp.auth.AuthTokenGetter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand All @@ -36,22 +37,46 @@ public class Tool {
private final ToolDefinition definition;
private final McpToolboxClient client;

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<>();
private final Map<String, Object> boundParameters;
private final Map<String, AuthTokenGetter> authGetters;
private final List<ToolPreProcessor> preProcessors;
private final List<ToolPostProcessor> postProcessors;

/**
* Constructs a new Tool.
*
* @param name The name of the tool.
* @param definition The definition of the tool.
* @param client The client used to invoke the tool.
* @param toolName The name of the tool.
* @param toolDefinition The definition of the tool.
* @param toolboxClient The client used to invoke the tool.
*/
public Tool(String name, ToolDefinition definition, McpToolboxClient client) {
this.name = name;
this.definition = definition;
this.client = client;
public Tool(
final String toolName,
final ToolDefinition toolDefinition,
final McpToolboxClient toolboxClient) {
this.name = toolName;
this.definition = toolDefinition;
this.client = toolboxClient;
this.boundParameters = Collections.emptyMap();
this.authGetters = Collections.emptyMap();
this.preProcessors = Collections.emptyList();
this.postProcessors = Collections.emptyList();
}

private Tool(
final String toolName,
final ToolDefinition toolDefinition,
final McpToolboxClient toolboxClient,
final Map<String, Object> initialBoundParameters,
final Map<String, AuthTokenGetter> initialAuthGetters,
final List<ToolPreProcessor> initialPreProcessors,
final List<ToolPostProcessor> initialPostProcessors) {
this.name = toolName;
this.definition = toolDefinition;
this.client = toolboxClient;
this.boundParameters = Collections.unmodifiableMap(new HashMap<>(initialBoundParameters));
this.authGetters = Collections.unmodifiableMap(new HashMap<>(initialAuthGetters));
this.preProcessors = Collections.unmodifiableList(new ArrayList<>(initialPreProcessors));
this.postProcessors = Collections.unmodifiableList(new ArrayList<>(initialPostProcessors));
}

/**
Expand All @@ -77,35 +102,80 @@ public ToolDefinition definition() {
*
* @param key The parameter name.
* @param value The value to bind.
* @return The tool instance.
* @return A new tool instance with the parameter bound and pruned from definition.
*/
public Tool bindParam(String key, Object value) {
this.boundParameters.put(key, value);
return this;
public Tool bindParam(final String key, final Object value) {
Map<String, Object> newBound = new HashMap<>(this.boundParameters);
newBound.put(key, value);
ToolDefinition newDef = pruneParameter(this.definition, key);
return new Tool(
this.name,
newDef,
this.client,
newBound,
this.authGetters,
this.preProcessors,
this.postProcessors);
}

/**
* Binds a dynamic value supplier to a parameter.
*
* @param key The parameter name.
* @param valueSupplier The supplier that provides the value at execution time.
* @return The tool instance.
* @return A new tool instance with the parameter bound and pruned from definition.
*/
public Tool bindParam(String key, Supplier<Object> valueSupplier) {
this.boundParameters.put(key, valueSupplier);
return this;
public Tool bindParam(final String key, final Supplier<Object> valueSupplier) {
Map<String, Object> newBound = new HashMap<>(this.boundParameters);
newBound.put(key, valueSupplier);
ToolDefinition newDef = pruneParameter(this.definition, key);
return new Tool(
this.name,
newDef,
this.client,
newBound,
this.authGetters,
this.preProcessors,
this.postProcessors);
}

/**
* Registers an authentication token getter for a specific service.
*
* @param serviceName The name of the service.
* @param getter The token getter.
* @return The tool instance.
* @return A new tool instance with the token getter registered.
*/
public Tool addAuthTokenGetter(String serviceName, AuthTokenGetter getter) {
this.authGetters.put(serviceName, getter);
return this;
public Tool addAuthTokenGetter(final String serviceName, final AuthTokenGetter getter) {
Map<String, AuthTokenGetter> newAuth = new HashMap<>(this.authGetters);
newAuth.put(serviceName, getter);
return new Tool(
this.name,
this.definition,
this.client,
this.boundParameters,
newAuth,
this.preProcessors,
this.postProcessors);
}

private static ToolDefinition pruneParameter(
final ToolDefinition original, final String paramName) {
if (original.parameters() == null) {
return original;
}
List<ToolDefinition.Parameter> newParams = new ArrayList<>();
for (ToolDefinition.Parameter param : original.parameters()) {
if (!param.name().equals(paramName)) {
newParams.add(param);
}
}
return new ToolDefinition(
original.description(),
newParams,
original.authRequired(),
original.readOnlyHint(),
original.destructiveHint());
}

/**
Expand All @@ -114,20 +184,39 @@ public Tool addAuthTokenGetter(String serviceName, AuthTokenGetter getter) {
* @param processor The pre-processor to add.
* @return The tool instance.
*/
public Tool addPreProcessor(ToolPreProcessor processor) {
this.preProcessors.add(processor);
return this;
public Tool addPreProcessor(final ToolPreProcessor processor) {
List<ToolPreProcessor> newPre =
new ArrayList<>(this.preProcessors != null ? this.preProcessors : Collections.emptyList());
newPre.add(processor);
return new Tool(
this.name,
this.definition,
this.client,
this.boundParameters,
this.authGetters,
newPre,
this.postProcessors);
}

/**
* Adds a post-processor to the tool.
*
* @param processor The post-processor to add.
* @return The tool instance.
* @return A new tool instance with the post-processor added.
*/
public Tool addPostProcessor(ToolPostProcessor processor) {
this.postProcessors.add(processor);
return this;
public Tool addPostProcessor(final ToolPostProcessor processor) {
List<ToolPostProcessor> newPost =
new ArrayList<>(
this.postProcessors != null ? this.postProcessors : Collections.emptyList());
newPost.add(processor);
return new Tool(
this.name,
this.definition,
this.client,
this.boundParameters,
this.authGetters,
this.preProcessors,
newPost);
}

/**
Expand All @@ -137,7 +226,7 @@ public Tool addPostProcessor(ToolPostProcessor processor) {
* @param args The arguments for the tool invocation.
* @return A CompletableFuture containing the result of the tool execution.
*/
public CompletableFuture<ToolResult> execute(Map<String, Object> args) {
public CompletableFuture<ToolResult> execute(final Map<String, Object> args) {
CompletableFuture<Map<String, Object>> argsFuture =
CompletableFuture.completedFuture(new HashMap<>(args));

Expand Down Expand Up @@ -168,7 +257,8 @@ public CompletableFuture<ToolResult> execute(Map<String, Object> args) {
.thenCompose(
resolvedAuth -> {
try {
// Apply credential parameter bindings and extra headers
// Apply credential parameter bindings and extra
// headers
resolvedAuth.applyTo(finalArgs, extraHeaders, definition);

// Validation & Cleanup
Expand All @@ -187,12 +277,18 @@ public CompletableFuture<ToolResult> execute(Map<String, Object> args) {
return resultFuture;
}

/** Validates arguments against the tool definition and removes null values. */
private void validateAndSanitizeArgs(Map<String, Object> args) {
/**
* Validates arguments against the tool definition and removes null values.
*
* @param args The arguments to validate.
*/
private void validateAndSanitizeArgs(final Map<String, Object> args) {
// Remove nulls first (filtering none values)
args.values().removeIf(Objects::isNull);

if (definition.parameters() == null) return;
if (definition.parameters() == null) {
return;
}

for (ToolDefinition.Parameter param : definition.parameters()) {
Object value = args.get(param.name());
Expand Down Expand Up @@ -221,7 +317,7 @@ private void validateAndSanitizeArgs(Map<String, Object> args) {
}
}

private Object deepCopy(Object value) {
private Object deepCopy(final Object value) {
if (value instanceof Map) {
Map<?, ?> map = (Map<?, ?>) value;
Map<Object, Object> copy = new HashMap<>();
Expand All @@ -240,7 +336,7 @@ private Object deepCopy(Object value) {
return value;
}

private boolean isTypeMatch(Object value, String type) {
private boolean isTypeMatch(final Object value, final String type) {
switch (type.toLowerCase()) {
case "string":
return value instanceof String;
Expand Down
Loading
Loading