diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index c7c8571..846f297 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Checkout Repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up JDK 17 uses: actions/setup-java@v3 with: diff --git a/cloudbuild.yaml b/cloudbuild.yaml new file mode 100644 index 0000000..e07fe8c --- /dev/null +++ b/cloudbuild.yaml @@ -0,0 +1,32 @@ +# 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. + +steps: + - id: Install library requirements + name: 'maven:3.9.6-eclipse-temurin-17' + entrypoint: 'mvn' + args: ['clean', 'install', '-DskipTests'] + - id: Run integration tests + name: 'maven:3.9.6-eclipse-temurin-17' + entrypoint: 'mvn' + args: ['test'] + env: + - GOOGLE_CLOUD_PROJECT=$PROJECT_ID + - TOOLBOX_VERSION=${_TOOLBOX_VERSION} + - TOOLBOX_MANIFEST_VERSION=${_TOOLBOX_MANIFEST_VERSION} +substitutions: + _TOOLBOX_VERSION: '0.26.0' + _TOOLBOX_MANIFEST_VERSION: '34' +options: + logging: CLOUD_LOGGING_ONLY diff --git a/pom.xml b/pom.xml index ed21cef..5704c47 100644 --- a/pom.xml +++ b/pom.xml @@ -58,8 +58,9 @@ 17 2.15.2 1.23.0 + 0.35.0 true - java.header + java.header @@ -93,6 +94,20 @@ google-auth-library-oauth2-http + + + dev.langchain4j + langchain4j-core + ${langchain4j.version} + true + + + dev.langchain4j + langchain4j + ${langchain4j.version} + true + + org.junit.jupiter @@ -122,6 +137,14 @@ + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + 17 + + com.spotify.fmt fmt-maven-plugin diff --git a/src/main/java/com/google/cloud/mcp/LangChain4jTool.java b/src/main/java/com/google/cloud/mcp/LangChain4jTool.java new file mode 100644 index 0000000..d9d1c8a --- /dev/null +++ b/src/main/java/com/google/cloud/mcp/LangChain4jTool.java @@ -0,0 +1,60 @@ +/* + * 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.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import dev.langchain4j.agent.tool.ToolExecutionRequest; +import dev.langchain4j.agent.tool.ToolSpecification; +import dev.langchain4j.service.tool.ToolExecutor; +import java.util.Map; +import java.util.stream.Collectors; + +/** Adapter for LangChain4j Tools. */ +public class LangChain4jTool { + + private static final ObjectMapper mapper = new ObjectMapper(); + private final Tool tool; + + public LangChain4jTool(Tool tool) { + this.tool = tool; + } + + public ToolSpecification specification() { + return ToolSpecification.builder() + .name(tool.name()) + .description(tool.definition().description()) + // In a real implementation, we would map parameters here. + // For now, we assume dynamic arguments. + .build(); + } + + public ToolExecutor executor() { + return (request, memoryId) -> { + try { + Map arguments = + mapper.readValue(request.arguments(), new TypeReference>() {}); + ToolResult result = tool.execute(arguments).join(); + return result.content().stream() + .map(ToolResult.Content::text) + .collect(Collectors.joining("\n")); + } catch (Exception e) { + throw new RuntimeException("Failed to execute tool", e); + } + }; + } +} diff --git a/src/main/java/com/google/cloud/mcp/McpToolboxClient.java b/src/main/java/com/google/cloud/mcp/McpToolboxClient.java index 3a718ef..72134d8 100644 --- a/src/main/java/com/google/cloud/mcp/McpToolboxClient.java +++ b/src/main/java/com/google/cloud/mcp/McpToolboxClient.java @@ -100,6 +100,11 @@ CompletableFuture> loadToolset( CompletableFuture invokeTool( String toolName, Map arguments, Map extraHeaders); + /** Returns a synchronous version of this client. */ + default SyncMcpToolboxClient sync() { + return new SyncMcpToolboxClient(this); + } + /** * Builder pattern for creating client instances. * diff --git a/src/main/java/com/google/cloud/mcp/SyncMcpToolboxClient.java b/src/main/java/com/google/cloud/mcp/SyncMcpToolboxClient.java new file mode 100644 index 0000000..3676a82 --- /dev/null +++ b/src/main/java/com/google/cloud/mcp/SyncMcpToolboxClient.java @@ -0,0 +1,47 @@ +/* + * 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; + +/** + * Synchronous client for interacting with a Toolbox service. A wrapper around {@link + * McpToolboxClient} that blocks on operations. + */ +public class SyncMcpToolboxClient { + + private final McpToolboxClient asyncClient; + + public SyncMcpToolboxClient(McpToolboxClient asyncClient) { + this.asyncClient = asyncClient; + } + + /** Blocks and retrieves the list of tools from the server. */ + public Map listTools() { + return asyncClient.listTools().join(); + } + + /** Blocks and loads a tool definition. */ + public Tool loadTool(String toolName) { + return asyncClient.loadTool(toolName).join(); + } + + /** Blocks and invokes a tool. */ + public ToolResult invokeTool(String toolName, Map arguments) { + return asyncClient.invokeTool(toolName, arguments).join(); + } +} diff --git a/src/test/java/com/google/cloud/mcp/LangChain4jToolTest.java b/src/test/java/com/google/cloud/mcp/LangChain4jToolTest.java new file mode 100644 index 0000000..856e568 --- /dev/null +++ b/src/test/java/com/google/cloud/mcp/LangChain4jToolTest.java @@ -0,0 +1,41 @@ +/* + * 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 static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.junit.jupiter.api.Test; + +public class LangChain4jToolTest { + + @Test + public void testSpecification() { + Tool mockTool = mock(Tool.class); + ToolDefinition mockDef = mock(ToolDefinition.class); + when(mockTool.name()).thenReturn("test-tool"); + when(mockTool.definition()).thenReturn(mockDef); + when(mockDef.description()).thenReturn("test-description"); + + LangChain4jTool adapter = new LangChain4jTool(mockTool); + assertNotNull(adapter.specification()); + assertEquals("test-tool", adapter.specification().name()); + assertEquals("test-description", adapter.specification().description()); + } +}