diff --git a/src/main/java/com/google/cloud/mcp/exception/McpProtocolException.java b/src/main/java/com/google/cloud/mcp/exception/McpProtocolException.java new file mode 100644 index 0000000..c944ecf --- /dev/null +++ b/src/main/java/com/google/cloud/mcp/exception/McpProtocolException.java @@ -0,0 +1,40 @@ +/* + * 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.exception; + +/** Exception thrown when JSON-RPC or protocol level errors occur. */ +public class McpProtocolException extends McpToolboxException { + + /** + * Constructs a new McpProtocolException with the specified detail message. + * + * @param message The detail message. + */ + public McpProtocolException(final String message) { + super(message); + } + + /** + * Constructs a new McpProtocolException with the specified message and cause. + * + * @param message The detail message. + * @param cause The cause of the exception. + */ + public McpProtocolException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/com/google/cloud/mcp/exception/McpToolboxException.java b/src/main/java/com/google/cloud/mcp/exception/McpToolboxException.java new file mode 100644 index 0000000..5d9242e --- /dev/null +++ b/src/main/java/com/google/cloud/mcp/exception/McpToolboxException.java @@ -0,0 +1,49 @@ +/* + * 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.exception; + +/** Base exception class for all MCP Toolbox SDK errors. */ +public class McpToolboxException extends RuntimeException { + + /** + * Constructs a new McpToolboxException with the specified detail message. + * + * @param message The detail message. + */ + public McpToolboxException(final String message) { + super(message); + } + + /** + * Constructs a new McpToolboxException with the specified message and cause. + * + * @param message The detail message. + * @param cause The cause of the exception. + */ + public McpToolboxException(final String message, final Throwable cause) { + super(message, cause); + } + + /** + * Constructs a new McpToolboxException with the specified cause. + * + * @param cause The cause of the exception. + */ + public McpToolboxException(final Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/com/google/cloud/mcp/exception/McpTransportException.java b/src/main/java/com/google/cloud/mcp/exception/McpTransportException.java new file mode 100644 index 0000000..16a0270 --- /dev/null +++ b/src/main/java/com/google/cloud/mcp/exception/McpTransportException.java @@ -0,0 +1,76 @@ +/* + * 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.exception; + +/** Exception thrown when communication or transport level errors occur. */ +public class McpTransportException extends McpToolboxException { + /** The HTTP status code associated with this error, or -1 if not applicable. */ + private final int statusCode; + + /** + * Constructs a new McpTransportException with the specified detail message. + * + * @param message The detail message. + */ + public McpTransportException(final String message) { + super(message); + this.statusCode = -1; + } + + /** + * Constructs a new McpTransportException with message and status code. + * + * @param message The detail message. + * @param statusCode The HTTP status code. + */ + public McpTransportException(final String message, final int statusCode) { + super(message); + this.statusCode = statusCode; + } + + /** + * Constructs a new McpTransportException with message and cause. + * + * @param message The detail message. + * @param cause The cause of the exception. + */ + public McpTransportException(final String message, final Throwable cause) { + super(message, cause); + this.statusCode = -1; + } + + /** + * Constructs a new McpTransportException with message, status code, and cause. + * + * @param message The detail message. + * @param statusCode The HTTP status code. + * @param cause The cause of the exception. + */ + public McpTransportException(final String message, final int statusCode, final Throwable cause) { + super(message, cause); + this.statusCode = statusCode; + } + + /** + * Returns the HTTP status code associated with this error, or -1 if not applicable. + * + * @return The HTTP status code. + */ + public int getStatusCode() { + return statusCode; + } +} diff --git a/src/main/java/com/google/cloud/mcp/exception/ToolExecutionException.java b/src/main/java/com/google/cloud/mcp/exception/ToolExecutionException.java new file mode 100644 index 0000000..e8525f2 --- /dev/null +++ b/src/main/java/com/google/cloud/mcp/exception/ToolExecutionException.java @@ -0,0 +1,40 @@ +/* + * 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.exception; + +/** Exception thrown when tool lookup, validation, or invocation fails. */ +public class ToolExecutionException extends McpToolboxException { + + /** + * Constructs a new ToolExecutionException with the specified detail message. + * + * @param message The detail message. + */ + public ToolExecutionException(final String message) { + super(message); + } + + /** + * Constructs a new ToolExecutionException with the specified message/cause. + * + * @param message The detail message. + * @param cause The cause of the exception. + */ + public ToolExecutionException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/com/google/cloud/mcp/tool/Tool.java b/src/main/java/com/google/cloud/mcp/tool/Tool.java index 173c58a..a5ab8c5 100644 --- a/src/main/java/com/google/cloud/mcp/tool/Tool.java +++ b/src/main/java/com/google/cloud/mcp/tool/Tool.java @@ -19,6 +19,7 @@ import com.google.cloud.mcp.McpToolboxClient; import com.google.cloud.mcp.auth.AuthResolver; import com.google.cloud.mcp.auth.AuthTokenGetter; +import com.google.cloud.mcp.exception.McpToolboxException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -33,13 +34,25 @@ * resolution, and input validation. */ public class Tool { + /** The name of the tool. */ private final String name; + + /** The definition of the tool. */ private final ToolDefinition definition; + + /** The client used to invoke the tool. */ private final McpToolboxClient client; + /** The bound parameters. */ private final Map boundParameters; + + /** The auth token getters. */ private final Map authGetters; + + /** The pre-processors. */ private final List preProcessors; + + /** The post-processors. */ private final List postProcessors; /** @@ -216,6 +229,32 @@ public Tool addPostProcessor(final ToolPostProcessor processor) { newPost); } + /** + * Synchronously executes the tool with the provided arguments. + * + * @param args The arguments for the tool invocation. + * @return The result of the tool execution. + * @throws McpToolboxException if execution fails. + */ + public ToolResult executeSync(final Map args) { + try { + return execute(args).join(); + } catch (java.util.concurrent.CompletionException + | java.util.concurrent.CancellationException e) { + Throwable cause = e.getCause(); + if (cause instanceof McpToolboxException) { + throw (McpToolboxException) cause; + } + if (cause instanceof IllegalArgumentException) { + throw (IllegalArgumentException) cause; + } + if (cause != null) { + throw new McpToolboxException(cause.getMessage(), cause); + } + throw new McpToolboxException(e); + } + } + /** * Executes the tool with the provided arguments, applying any bound parameters and resolving * authentication tokens. diff --git a/src/main/java/com/google/cloud/mcp/transport/JsonRpc.java b/src/main/java/com/google/cloud/mcp/transport/JsonRpc.java index 9ec7719..4f85713 100644 --- a/src/main/java/com/google/cloud/mcp/transport/JsonRpc.java +++ b/src/main/java/com/google/cloud/mcp/transport/JsonRpc.java @@ -19,13 +19,30 @@ import java.util.Map; import java.util.UUID; -class JsonRpc { - static class Request { +/** Namespace for JSON-RPC 2.0 MC Protocol data structures. */ +public class JsonRpc { + private JsonRpc() {} + + /** Represents a JSON-RPC request. */ + public static class Request { + /** The JSON-RPC version. */ public String jsonrpc = "2.0"; + + /** The request ID. */ public String id; + + /** The method name. */ public String method; + + /** The parameters. */ public Object params; + /** + * Constructs a new Request. + * + * @param method The method name. + * @param params The parameters. + */ public Request(final String method, final Object params) { this.id = UUID.randomUUID().toString(); this.method = method; @@ -33,32 +50,66 @@ public Request(final String method, final Object params) { } } - static class Notification { + /** Represents a JSON-RPC notification. */ + public static class Notification { + /** The JSON-RPC version. */ public String jsonrpc = "2.0"; + + /** The method name. */ public String method; + + /** The parameters. */ public Object params; + /** + * Constructs a new Notification. + * + * @param method The method name. + * @param params The parameters. + */ public Notification(final String method, final Object params) { this.method = method; this.params = params; } } - static class CallToolParams { + /** Parameters for calling a tool. */ + public static class CallToolParams { + /** The name of the tool to call. */ public String name; + + /** The arguments for the tool call. */ public Map arguments; + /** + * Constructs a new CallToolParams. + * + * @param name The name of the tool. + * @param arguments The arguments. + */ public CallToolParams(final String name, final Map arguments) { this.name = name; this.arguments = arguments; } } - static class InitializeParams { + /** Parameters for initializing the connection. */ + public static class InitializeParams { + /** The protocol version. */ public String protocolVersion; + + /** The client capabilities. */ public Map capabilities; + + /** The client info. */ public Map clientInfo; + /** + * Constructs a new InitializeParams. + * + * @param version The protocol version. + * @param clientName The client name. + */ public InitializeParams(final String version, final String clientName) { this.protocolVersion = version; this.capabilities = Map.of(); diff --git a/src/test/java/com/google/cloud/mcp/client/McpToolboxClientBuilderTest.java b/src/test/java/com/google/cloud/mcp/client/McpToolboxClientBuilderTest.java index b5b1168..c0308e1 100644 --- a/src/test/java/com/google/cloud/mcp/client/McpToolboxClientBuilderTest.java +++ b/src/test/java/com/google/cloud/mcp/client/McpToolboxClientBuilderTest.java @@ -27,6 +27,10 @@ import com.google.cloud.mcp.ProtocolVersion; import com.google.cloud.mcp.auth.CredentialsProvider; import com.google.cloud.mcp.exception.McpException; +import com.google.cloud.mcp.exception.McpProtocolException; +import com.google.cloud.mcp.exception.McpToolboxException; +import com.google.cloud.mcp.exception.McpTransportException; +import com.google.cloud.mcp.exception.ToolExecutionException; import com.google.cloud.mcp.tool.ToolPostProcessor; import com.google.cloud.mcp.tool.ToolPreProcessor; import com.google.cloud.mcp.transport.Transport; @@ -191,6 +195,9 @@ void testMcpExceptionConstructor() { McpException ex = new McpException("error message", cause); assertEquals("error message", ex.getMessage()); assertSame(cause, ex.getCause()); + + McpException exMsg = new McpException("only message"); + assertEquals("only message", exMsg.getMessage()); } @Test @@ -199,4 +206,47 @@ void testProtocolVersionFromString() { assertNull(ProtocolVersion.fromString("invalid-version")); assertEquals(ProtocolVersion.VERSION_2025_11_25, ProtocolVersion.fromString("2025-11-25")); } + + @Test + void testMcpTransportExceptionConstructors() { + McpTransportException ex1 = new McpTransportException("msg1"); + assertEquals("msg1", ex1.getMessage()); + assertEquals(-1, ex1.getStatusCode()); + + McpTransportException ex2 = new McpTransportException("msg2", 404); + assertEquals("msg2", ex2.getMessage()); + assertEquals(404, ex2.getStatusCode()); + + RuntimeException cause = new RuntimeException("root"); + McpTransportException ex3 = new McpTransportException("msg3", cause); + assertEquals("msg3", ex3.getMessage()); + assertSame(cause, ex3.getCause()); + assertEquals(-1, ex3.getStatusCode()); + + McpTransportException ex4 = new McpTransportException("msg4", 500, cause); + assertEquals("msg4", ex4.getMessage()); + assertSame(cause, ex4.getCause()); + assertEquals(500, ex4.getStatusCode()); + } + + @Test + void testOtherExceptionConstructors() { + RuntimeException cause = new RuntimeException("root"); + McpToolboxException tb1 = new McpToolboxException("tb msg"); + McpToolboxException tb2 = new McpToolboxException("tb msg", cause); + McpToolboxException tb3 = new McpToolboxException(cause); + assertEquals("tb msg", tb1.getMessage()); + assertEquals("tb msg", tb2.getMessage()); + assertSame(cause, tb3.getCause()); + + McpProtocolException proto1 = new McpProtocolException("proto msg"); + McpProtocolException proto2 = new McpProtocolException("proto msg", cause); + assertEquals("proto msg", proto1.getMessage()); + assertSame(cause, proto2.getCause()); + + ToolExecutionException exec1 = new ToolExecutionException("exec msg"); + ToolExecutionException exec2 = new ToolExecutionException("exec msg", cause); + assertEquals("exec msg", exec1.getMessage()); + assertSame(cause, exec2.getCause()); + } } diff --git a/src/test/java/com/google/cloud/mcp/transport/HttpMcpTransportTest.java b/src/test/java/com/google/cloud/mcp/transport/HttpMcpTransportTest.java index 0953558..dda59d0 100644 --- a/src/test/java/com/google/cloud/mcp/transport/HttpMcpTransportTest.java +++ b/src/test/java/com/google/cloud/mcp/transport/HttpMcpTransportTest.java @@ -476,9 +476,11 @@ void testListTools_ParsesComplexToolsCorrectly() throws Exception { } @Test - void testJsonRpcInstantiation() { - // Instantiate package-private JsonRpc namespace to cover its default constructor - JsonRpc rpc = new JsonRpc(); + void testJsonRpcInstantiation() throws Exception { + // Instantiate private JsonRpc namespace to cover its constructor + java.lang.reflect.Constructor constructor = JsonRpc.class.getDeclaredConstructor(); + constructor.setAccessible(true); + JsonRpc rpc = constructor.newInstance(); assertNotNull(rpc); } }