diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index 4d9807f..49db0da 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -1,7 +1,7 @@ [package] org = "ballerina" name = "mcp" -version = "1.0.0" +version = "1.0.1" authors = ["Ballerina"] keywords = ["mcp"] repository = "https://github.com/ballerina-platform/module-ballerina-mcp" @@ -15,5 +15,5 @@ graalvmCompatible = true [[platform.java21.dependency]] groupId = "io.ballerina.stdlib." artifactId = "mcp-native" -version = "1.0.0" -path = "../native/build/libs/mcp-native-1.0.0.jar" +version = "1.0.1" +path = "../native/build/libs/mcp-native-1.0.1.jar" diff --git a/ballerina/CompilerPlugin.toml b/ballerina/CompilerPlugin.toml index 242e3b3..3e06f65 100644 --- a/ballerina/CompilerPlugin.toml +++ b/ballerina/CompilerPlugin.toml @@ -3,7 +3,7 @@ id = "mcp-compiler-plugin" class = "io.ballerina.stdlib.mcp.plugin.McpCompilerPlugin" [[dependency]] -path = "../compiler-plugin/build/libs/mcp-compiler-plugin-1.0.0.jar" +path = "../compiler-plugin/build/libs/mcp-compiler-plugin-1.0.1.jar" [[dependency]] path = "../compiler-plugin/build/libs/ballerina-to-openapi-2.3.0.jar" diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index cabf323..5bf7414 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -229,7 +229,7 @@ dependencies = [ [[package]] org = "ballerina" name = "mcp" -version = "1.0.0" +version = "1.0.1" dependencies = [ {org = "ballerina", name = "http"}, {org = "ballerina", name = "jballerina.java"}, diff --git a/ballerina/dispatcher_service.bal b/ballerina/dispatcher_service.bal index 5d0f709..f0d657d 100644 --- a/ballerina/dispatcher_service.bal +++ b/ballerina/dispatcher_service.bal @@ -318,13 +318,18 @@ isolated function getDispatcherService(http:HttpServiceConfig httpServiceConfig) return error DispatcherError("MCP Service is not attached"); } - private isolated function executeOnCallTool(CallToolParams params, Session? session) returns CallToolResult|Error { + private isolated function executeOnCallTool(CallToolParams params, Session? session) + returns CallToolResult|Error { Service|AdvancedService mcpService = check getMcpServiceFromDispatcher(self); if mcpService is AdvancedService { return invokeOnCallTool(mcpService, params.cloneReadOnly(), session); } if mcpService is Service { - return callToolForRemoteFunctions(mcpService, params.cloneReadOnly(), session); + CallToolResult|error result = callToolForRemoteFunctions(mcpService, params.cloneReadOnly(), session); + if result is error { + return error DispatcherError(result.message()); + } + return result; } return error DispatcherError("MCP Service is not attached"); } diff --git a/ballerina/native_listener_helper.bal b/ballerina/native_listener_helper.bal index 8f6fd22..0e61e54 100644 --- a/ballerina/native_listener_helper.bal +++ b/ballerina/native_listener_helper.bal @@ -32,7 +32,7 @@ isolated function listToolsForRemoteFunctions(Service 'service, typedesc t = <>) returns t|Error = @java:Method { + typedesc t = <>) returns t|error = @java:Method { 'class: "io.ballerina.stdlib.mcp.McpServiceMethodHelper" } external; diff --git a/ballerina/streamable_http.bal b/ballerina/streamable_http.bal index 8f792dc..a8e09a1 100644 --- a/ballerina/streamable_http.bal +++ b/ballerina/streamable_http.bal @@ -199,7 +199,13 @@ isolated class StreamableHttpClientTransport { returns JsonRpcMessage|StreamableHttpTransportError { do { json payload = check response.getJsonPayload(); - return check payload.cloneWithType(); + JsonRpcMessage|http:ErrorPayload result = check payload.cloneWithType(); + if result is JsonRpcMessage { + return result; + } + return error HttpClientError( + string `Received error response from server: ${result.toJsonString()}` + ); } on fail error e { return error ResponseParsingError( string `Unable to parse JSON response: ${e.message()}` diff --git a/ballerina/types.bal b/ballerina/types.bal index b3a9434..582743d 100644 --- a/ballerina/types.bal +++ b/ballerina/types.bal @@ -17,7 +17,7 @@ import ballerina/http; # Refers to any valid JSON-RPC object that can be decoded off the wire, or encoded to be sent. -public type JsonRpcMessage JsonRpcRequest|JsonRpcNotification|JsonRpcResponse; +public type JsonRpcMessage JsonRpcRequest|JsonRpcNotification|JsonRpcError|JsonRpcResponse; public const LATEST_PROTOCOL_VERSION = "2025-03-26"; public const SUPPORTED_PROTOCOL_VERSIONS = [ @@ -143,7 +143,7 @@ public const NOT_ACCEPTABLE = -32001; public const UNSUPPORTED_MEDIA_TYPE = -32002; # A response to a request that indicates an error occurred. -public type JsonRpcError record { +public type JsonRpcError record {| # The JSON-RPC protocol version JSONRPC_VERSION jsonrpc; # Identifier of the request @@ -157,7 +157,7 @@ public type JsonRpcError record { # Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.). anydata data?; } 'error; -}; +|}; # This request is sent from the client to the server when it first connects, asking it to begin initialization. type InitializeRequest record {| diff --git a/ballerina/utils.bal b/ballerina/utils.bal index a25a94d..55793f9 100644 --- a/ballerina/utils.bal +++ b/ballerina/utils.bal @@ -70,5 +70,8 @@ isolated function extractResultFromMessage(JsonRpcMessage message) returns Serve if message is JsonRpcResponse { return message.result; } + if message is JsonRpcError { + return error ServerResponseError(string `Received JSON-RPC error from server: ${message.toJsonString()}`); + } return error InvalidMessageTypeError("Received message from server is not a valid JsonRpcResponse."); } diff --git a/examples/clients/mcp-crypto-client/Ballerina.toml b/examples/clients/mcp-crypto-client/Ballerina.toml index 26e1f2f..1bc6f4f 100644 --- a/examples/clients/mcp-crypto-client/Ballerina.toml +++ b/examples/clients/mcp-crypto-client/Ballerina.toml @@ -10,5 +10,5 @@ observabilityIncluded = true [[dependency]] org = "ballerina" name = "mcp" -version = "1.0.0" +version = "1.0.1" repository = "local" diff --git a/examples/clients/mcp-crypto-client/Dependencies.toml b/examples/clients/mcp-crypto-client/Dependencies.toml index 948cec4..2b64268 100644 --- a/examples/clients/mcp-crypto-client/Dependencies.toml +++ b/examples/clients/mcp-crypto-client/Dependencies.toml @@ -50,7 +50,7 @@ dependencies = [ [[package]] org = "ballerina" name = "data.jsondata" -version = "1.1.2" +version = "1.1.3" dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "lang.object"} @@ -116,7 +116,7 @@ version = "0.0.0" [[package]] org = "ballerina" name = "jwt" -version = "2.15.0" +version = "2.15.1" dependencies = [ {org = "ballerina", name = "cache"}, {org = "ballerina", name = "crypto"}, @@ -205,7 +205,7 @@ dependencies = [ [[package]] org = "ballerina" name = "log" -version = "2.13.0" +version = "2.14.0" dependencies = [ {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, @@ -219,7 +219,7 @@ modules = [ [[package]] org = "ballerina" name = "mcp" -version = "1.0.0" +version = "1.0.1" dependencies = [ {org = "ballerina", name = "http"}, {org = "ballerina", name = "jballerina.java"}, @@ -278,7 +278,7 @@ dependencies = [ [[package]] org = "ballerina" name = "os" -version = "1.10.0" +version = "1.10.1" dependencies = [ {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"} diff --git a/examples/clients/mcp-shopping-client/Ballerina.toml b/examples/clients/mcp-shopping-client/Ballerina.toml index 4cfe606..7321ea2 100644 --- a/examples/clients/mcp-shopping-client/Ballerina.toml +++ b/examples/clients/mcp-shopping-client/Ballerina.toml @@ -10,5 +10,5 @@ observabilityIncluded = true [[dependency]] org = "ballerina" name = "mcp" -version = "1.0.0" +version = "1.0.1" repository = "local" diff --git a/examples/clients/mcp-shopping-client/Dependencies.toml b/examples/clients/mcp-shopping-client/Dependencies.toml index 79bc441..2a6c34e 100644 --- a/examples/clients/mcp-shopping-client/Dependencies.toml +++ b/examples/clients/mcp-shopping-client/Dependencies.toml @@ -50,7 +50,7 @@ dependencies = [ [[package]] org = "ballerina" name = "data.jsondata" -version = "1.1.2" +version = "1.1.3" dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "lang.object"} @@ -116,7 +116,7 @@ version = "0.0.0" [[package]] org = "ballerina" name = "jwt" -version = "2.15.0" +version = "2.15.1" dependencies = [ {org = "ballerina", name = "cache"}, {org = "ballerina", name = "crypto"}, @@ -205,7 +205,7 @@ dependencies = [ [[package]] org = "ballerina" name = "log" -version = "2.13.0" +version = "2.14.0" dependencies = [ {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, @@ -219,7 +219,7 @@ modules = [ [[package]] org = "ballerina" name = "mcp" -version = "1.0.0" +version = "1.0.1" dependencies = [ {org = "ballerina", name = "http"}, {org = "ballerina", name = "jballerina.java"}, @@ -278,7 +278,7 @@ dependencies = [ [[package]] org = "ballerina" name = "os" -version = "1.10.0" +version = "1.10.1" dependencies = [ {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"} diff --git a/examples/clients/mcp-weather-client/Ballerina.toml b/examples/clients/mcp-weather-client/Ballerina.toml index 9f9bd68..268ef67 100644 --- a/examples/clients/mcp-weather-client/Ballerina.toml +++ b/examples/clients/mcp-weather-client/Ballerina.toml @@ -10,5 +10,5 @@ observabilityIncluded = true [[dependency]] org = "ballerina" name = "mcp" -version = "1.0.0" +version = "1.0.1" repository = "local" diff --git a/examples/clients/mcp-weather-client/Dependencies.toml b/examples/clients/mcp-weather-client/Dependencies.toml index af5e226..0cc0189 100644 --- a/examples/clients/mcp-weather-client/Dependencies.toml +++ b/examples/clients/mcp-weather-client/Dependencies.toml @@ -50,7 +50,7 @@ dependencies = [ [[package]] org = "ballerina" name = "data.jsondata" -version = "1.1.2" +version = "1.1.3" dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "lang.object"} @@ -116,7 +116,7 @@ version = "0.0.0" [[package]] org = "ballerina" name = "jwt" -version = "2.15.0" +version = "2.15.1" dependencies = [ {org = "ballerina", name = "cache"}, {org = "ballerina", name = "crypto"}, @@ -205,7 +205,7 @@ dependencies = [ [[package]] org = "ballerina" name = "log" -version = "2.13.0" +version = "2.14.0" dependencies = [ {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, @@ -219,7 +219,7 @@ modules = [ [[package]] org = "ballerina" name = "mcp" -version = "1.0.0" +version = "1.0.1" dependencies = [ {org = "ballerina", name = "http"}, {org = "ballerina", name = "jballerina.java"}, @@ -278,7 +278,7 @@ dependencies = [ [[package]] org = "ballerina" name = "os" -version = "1.10.0" +version = "1.10.1" dependencies = [ {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"} diff --git a/examples/servers/mcp-crypto-server/Ballerina.toml b/examples/servers/mcp-crypto-server/Ballerina.toml index 988b7eb..ff4eec0 100644 --- a/examples/servers/mcp-crypto-server/Ballerina.toml +++ b/examples/servers/mcp-crypto-server/Ballerina.toml @@ -10,5 +10,5 @@ observabilityIncluded = true [[dependency]] org = "ballerina" name = "mcp" -version = "1.0.0" +version = "1.0.1" repository = "local" diff --git a/examples/servers/mcp-crypto-server/Dependencies.toml b/examples/servers/mcp-crypto-server/Dependencies.toml index 77e3950..7a95f88 100644 --- a/examples/servers/mcp-crypto-server/Dependencies.toml +++ b/examples/servers/mcp-crypto-server/Dependencies.toml @@ -53,7 +53,7 @@ modules = [ [[package]] org = "ballerina" name = "data.jsondata" -version = "1.1.2" +version = "1.1.3" dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "lang.object"} @@ -116,7 +116,7 @@ version = "0.0.0" [[package]] org = "ballerina" name = "jwt" -version = "2.15.0" +version = "2.15.1" dependencies = [ {org = "ballerina", name = "cache"}, {org = "ballerina", name = "crypto"}, @@ -208,7 +208,7 @@ dependencies = [ [[package]] org = "ballerina" name = "log" -version = "2.13.0" +version = "2.14.0" dependencies = [ {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, @@ -222,7 +222,7 @@ modules = [ [[package]] org = "ballerina" name = "mcp" -version = "1.0.0" +version = "1.0.1" dependencies = [ {org = "ballerina", name = "http"}, {org = "ballerina", name = "jballerina.java"}, @@ -282,7 +282,7 @@ dependencies = [ [[package]] org = "ballerina" name = "os" -version = "1.10.0" +version = "1.10.1" dependencies = [ {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"} diff --git a/examples/servers/mcp-shopping-server/Ballerina.toml b/examples/servers/mcp-shopping-server/Ballerina.toml index cba1a57..070a019 100644 --- a/examples/servers/mcp-shopping-server/Ballerina.toml +++ b/examples/servers/mcp-shopping-server/Ballerina.toml @@ -10,5 +10,5 @@ observabilityIncluded = true [[dependency]] org = "ballerina" name = "mcp" -version = "1.0.0" +version = "1.0.1" repository = "local" diff --git a/examples/servers/mcp-shopping-server/Dependencies.toml b/examples/servers/mcp-shopping-server/Dependencies.toml index 350cf3f..e70e416 100644 --- a/examples/servers/mcp-shopping-server/Dependencies.toml +++ b/examples/servers/mcp-shopping-server/Dependencies.toml @@ -50,7 +50,7 @@ dependencies = [ [[package]] org = "ballerina" name = "data.jsondata" -version = "1.1.2" +version = "1.1.3" dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "lang.object"} @@ -113,7 +113,7 @@ version = "0.0.0" [[package]] org = "ballerina" name = "jwt" -version = "2.15.0" +version = "2.15.1" dependencies = [ {org = "ballerina", name = "cache"}, {org = "ballerina", name = "crypto"}, @@ -202,7 +202,7 @@ dependencies = [ [[package]] org = "ballerina" name = "log" -version = "2.13.0" +version = "2.14.0" dependencies = [ {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, @@ -216,7 +216,7 @@ modules = [ [[package]] org = "ballerina" name = "mcp" -version = "1.0.0" +version = "1.0.1" dependencies = [ {org = "ballerina", name = "http"}, {org = "ballerina", name = "jballerina.java"}, @@ -275,7 +275,7 @@ dependencies = [ [[package]] org = "ballerina" name = "os" -version = "1.10.0" +version = "1.10.1" dependencies = [ {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"} diff --git a/examples/servers/mcp-weather-server/Ballerina.toml b/examples/servers/mcp-weather-server/Ballerina.toml index f8dd752..fb2b05c 100644 --- a/examples/servers/mcp-weather-server/Ballerina.toml +++ b/examples/servers/mcp-weather-server/Ballerina.toml @@ -10,5 +10,5 @@ observabilityIncluded = true [[dependency]] org = "ballerina" name = "mcp" -version = "1.0.0" +version = "1.0.1" repository = "local" diff --git a/examples/servers/mcp-weather-server/Dependencies.toml b/examples/servers/mcp-weather-server/Dependencies.toml index 7de09f7..219ab66 100644 --- a/examples/servers/mcp-weather-server/Dependencies.toml +++ b/examples/servers/mcp-weather-server/Dependencies.toml @@ -50,7 +50,7 @@ dependencies = [ [[package]] org = "ballerina" name = "data.jsondata" -version = "1.1.2" +version = "1.1.3" dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "lang.object"} @@ -113,7 +113,7 @@ version = "0.0.0" [[package]] org = "ballerina" name = "jwt" -version = "2.15.0" +version = "2.15.1" dependencies = [ {org = "ballerina", name = "cache"}, {org = "ballerina", name = "crypto"}, @@ -202,7 +202,7 @@ dependencies = [ [[package]] org = "ballerina" name = "log" -version = "2.13.0" +version = "2.14.0" dependencies = [ {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, @@ -216,7 +216,7 @@ modules = [ [[package]] org = "ballerina" name = "mcp" -version = "1.0.0" +version = "1.0.1" dependencies = [ {org = "ballerina", name = "http"}, {org = "ballerina", name = "jballerina.java"}, @@ -276,7 +276,7 @@ dependencies = [ [[package]] org = "ballerina" name = "os" -version = "1.10.0" +version = "1.10.1" dependencies = [ {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"} diff --git a/gradle.properties b/gradle.properties index 3f5383e..31c4d22 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ org.gradle.caching=true group=io.ballerina.stdlib -version=1.0.1-SNAPSHOT +version=1.0.2-SNAPSHOT ballerinaLangVersion=2201.12.0 ballerinaGradlePluginVersion=2.3.0 diff --git a/native/src/main/java/io/ballerina/stdlib/mcp/McpServiceMethodHelper.java b/native/src/main/java/io/ballerina/stdlib/mcp/McpServiceMethodHelper.java index ff7f037..0a28374 100644 --- a/native/src/main/java/io/ballerina/stdlib/mcp/McpServiceMethodHelper.java +++ b/native/src/main/java/io/ballerina/stdlib/mcp/McpServiceMethodHelper.java @@ -27,8 +27,10 @@ import io.ballerina.runtime.api.types.RemoteMethodType; import io.ballerina.runtime.api.types.ServiceType; import io.ballerina.runtime.api.types.Type; +import io.ballerina.runtime.api.types.TypeTags; import io.ballerina.runtime.api.types.UnionType; import io.ballerina.runtime.api.values.BArray; +import io.ballerina.runtime.api.values.BError; import io.ballerina.runtime.api.values.BMap; import io.ballerina.runtime.api.values.BObject; import io.ballerina.runtime.api.values.BString; @@ -138,8 +140,14 @@ public static Object callToolForRemoteFunctions(Environment env, BObject mcpServ .createError("RemoteMethodType with name '" + toolName.getValue() + "' not found"); } - Object[] args = + Object argsOrError = buildArgsForMethod(method.get(), (BMap) params.get(fromString(ARGUMENTS_FIELD_NAME)), session); + + if (argsOrError instanceof BError) { + return argsOrError; + } + + Object[] args = (Object[]) argsOrError; Object result = env.getRuntime().callMethod(mcpService, toolName.getValue(), null, args); return createCallToolResult(typed, result); @@ -195,7 +203,7 @@ private static BMap createToolRecord(ArrayType toolsArrayType, return tool; } - private static Object[] buildArgsForMethod(RemoteMethodType method, BMap arguments, Object session) { + private static Object buildArgsForMethod(RemoteMethodType method, BMap arguments, Object session) { List params = List.of(method.getParameters()); Object[] args = new Object[params.size()]; for (int i = 0; i < params.size(); i++) { @@ -205,15 +213,36 @@ private static Object[] buildArgsForMethod(RemoteMethodType method, BMap a args[i] = session; } else { String paramName = param.name; - args[i] = arguments == null ? null : arguments.get(fromString(paramName)); + Object argValue = arguments == null ? null : arguments.get(fromString(paramName)); + + // Check if the parameter is required (non-optional) but the value is null + if (argValue == null && !isOptionalParameter(param)) { + return ModuleUtils.createError( + "Missing required argument '" + paramName + "' for parameter of type '" + + param.type.getName() + "'"); + } + + args[i] = argValue; } } return args; } + private static boolean isOptionalParameter(Parameter param) { + Type paramType = param.type; + + if (paramType instanceof UnionType unionType) { + return unionType.getMemberTypes().stream() + .anyMatch(type -> type.getTag() == TypeTags.NULL_TAG); + } + + return false; + } + private static boolean isSessionParameter(Parameter param) { Type paramType = param.type; - return MCP_PACKAGE_NAME.equals(paramType.getPackage().getName()) + return paramType.getPackage() != null + && MCP_PACKAGE_NAME.equals(paramType.getPackage().getName()) && SESSION_TYPE_NAME.equals(paramType.getName()); }