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
6 changes: 3 additions & 3 deletions ballerina/Ballerina.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
org = "ballerina"
name = "mcp"
version = "1.0.2"
version = "1.0.3"
authors = ["Ballerina"]
keywords = ["mcp"]
repository = "https://github.com/ballerina-platform/module-ballerina-mcp"
Expand All @@ -15,5 +15,5 @@ graalvmCompatible = true
[[platform.java21.dependency]]
groupId = "io.ballerina.stdlib."
artifactId = "mcp-native"
version = "1.0.2"
path = "../native/build/libs/mcp-native-1.0.2.jar"
version = "1.0.3"
path = "../native/build/libs/mcp-native-1.0.3-SNAPSHOT.jar"
2 changes: 1 addition & 1 deletion ballerina/CompilerPlugin.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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.2.jar"
path = "../compiler-plugin/build/libs/mcp-compiler-plugin-1.0.3-SNAPSHOT.jar"

[[dependency]]
path = "../compiler-plugin/build/libs/ballerina-to-openapi-2.3.0.jar"
2 changes: 1 addition & 1 deletion ballerina/Dependencies.toml
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ dependencies = [
[[package]]
org = "ballerina"
name = "mcp"
version = "1.0.2"
version = "1.0.3"
dependencies = [
{org = "ballerina", name = "http"},
{org = "ballerina", name = "jballerina.java"},
Expand Down
16 changes: 10 additions & 6 deletions ballerina/types.bal
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,17 @@ public type ProgressToken string|int;
# An opaque token used to represent a cursor for pagination.
public type Cursor string;

# Optional metadata for requests
public type Meta record {
# If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress).
# The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications.
ProgressToken progressToken?;
};

# Parameters for the request
public type RequestParams record {
# Optional parameters for the request
record {
# If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress).
# The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications.
ProgressToken progressToken?;
} _meta?;
# Optional metadata for the request
Meta _meta?;
};

# Represents a generic request in the protocol
Expand Down Expand Up @@ -315,6 +318,7 @@ public type ListToolsResult record {

# The server's response to a tool call.
public type CallToolResult record {
*Result;
# The content of the tool call result
(TextContent|ImageContent|AudioContent|EmbeddedResource)[] content;
# Whether the tool call ended in an error.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import java.util.stream.Collectors;

import static io.ballerina.stdlib.mcp.plugin.RemoteFunctionAnalysisTask.EMPTY_STRING;
import static io.ballerina.stdlib.mcp.plugin.Utils.isMetaParameter;
import static io.ballerina.stdlib.mcp.plugin.Utils.isSessionType;

/**
Expand All @@ -61,7 +62,9 @@ public static String getParameterSchema(FunctionSymbol functionSymbol, SyntaxNod
TypeMapper typeMapper = new TypeMapperImpl(context);
for (ParameterSymbol parameterSymbol : parameterSymbolList) {
try {
if (isSessionType(parameterSymbol.typeDescriptor())) {
// Skip Session and Meta parameters - they are not part of the tool schema
if (isSessionType(parameterSymbol.typeDescriptor()) ||
isMetaParameter(parameterSymbol.typeDescriptor())) {
continue;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@
import io.ballerina.compiler.api.symbols.ServiceDeclarationSymbol;
import io.ballerina.compiler.api.symbols.Symbol;
import io.ballerina.compiler.api.symbols.SymbolKind;
import io.ballerina.compiler.api.symbols.TypeDescKind;
import io.ballerina.compiler.api.symbols.TypeSymbol;
import io.ballerina.compiler.api.symbols.UnionTypeSymbol;
import io.ballerina.compiler.api.values.ConstantValue;
import io.ballerina.compiler.syntax.tree.AnnotationNode;
import io.ballerina.compiler.syntax.tree.BasicLiteralNode;
Expand Down Expand Up @@ -58,6 +60,7 @@ public class Utils {
public static final String MCP_PACKAGE_NAME = "mcp";
public static final String MCP_BASIC_SERVICE_NAME = "Service";
public static final String SESSION_TYPE_NAME = "Session";
public static final String META_TYPE_NAME = "Meta";
public static final String UNKNOWN_SYMBOL = "unknown";
public static final String SERVICE_CONFIG_ANNOTATION_NAME = "ServiceConfig";
public static final String SESSION_MODE_FIELD = "sessionMode";
Expand Down Expand Up @@ -190,13 +193,15 @@ public static boolean validateParameterTypes(FunctionSymbol functionSymbol,

var parameterSymbolList = functionTypeSymbol.params().get();
boolean hasSessionParam = false;
boolean hasMetaParam = false;

for (int i = 0; i < parameterSymbolList.size(); i++) {
ParameterSymbol parameterSymbol = parameterSymbolList.get(i);
TypeSymbol parameterType = parameterSymbol.typeDescriptor();
String parameterName = parameterSymbol.getName().orElse(UNKNOWN_SYMBOL);

boolean isSessionType = isSessionType(parameterType);
boolean isMetaParam = isMetaParameter(parameterType);

if (isSessionType) {
if (hasSessionParam) {
Expand Down Expand Up @@ -227,6 +232,36 @@ public static boolean validateParameterTypes(FunctionSymbol functionSymbol,
}

hasSessionParam = true;
} else if (isMetaParam) {
if (hasMetaParam) {
Diagnostic diagnostic = CompilationDiagnostic.getDiagnostic(
CompilationDiagnostic.META_PARAM_MUST_BE_LAST,
parameterSymbol.getLocation().orElse(alternativeLocation),
functionName, parameterName);
context.reportDiagnostic(diagnostic);
return false;
}

if (i != parameterSymbolList.size() - 1) {
Diagnostic diagnostic = CompilationDiagnostic.getDiagnostic(
CompilationDiagnostic.META_PARAM_MUST_BE_LAST,
parameterSymbol.getLocation().orElse(alternativeLocation),
functionName, parameterName);
context.reportDiagnostic(diagnostic);
return false;
}

// Check if Meta parameter is optional
if (!isOptionalType(parameterType)) {
Diagnostic diagnostic = CompilationDiagnostic.getDiagnostic(
CompilationDiagnostic.META_PARAM_MUST_BE_OPTIONAL,
parameterSymbol.getLocation().orElse(alternativeLocation),
functionName, parameterName);
context.reportDiagnostic(diagnostic);
return false;
}

hasMetaParam = true;
} else if (!isAnydataType(parameterType, context)) {
Diagnostic diagnostic = CompilationDiagnostic.getDiagnostic(
CompilationDiagnostic.INVALID_PARAMETER_TYPE,
Expand All @@ -245,6 +280,58 @@ static boolean isSessionType(TypeSymbol typeSymbol) {
&& isMcpModuleSymbol(typeSymbol);
}

static boolean isMetaType(TypeSymbol typeSymbol) {
return META_TYPE_NAME.equals(typeSymbol.getName().orElse(""))
&& isMcpModuleSymbol(typeSymbol);
}

/**
* Check if a TypeSymbol is optional/nullable (e.g., mcp:Meta?).
* An optional type is a union type that includes NIL as one of its members.
*
* @param typeSymbol The type symbol to check
* @return true if the type is optional/nullable, false otherwise
*/
static boolean isOptionalType(TypeSymbol typeSymbol) {
if (typeSymbol.typeKind() != TypeDescKind.UNION) {
return false;
}

UnionTypeSymbol unionTypeSymbol = (UnionTypeSymbol) typeSymbol;
for (TypeSymbol memberType : unionTypeSymbol.memberTypeDescriptors()) {
if (memberType.typeKind() == TypeDescKind.NIL) {
return true;
}
}
return false;
}

/**
* Check if a parameter is of Meta type (either mcp:Meta or mcp:Meta?).
* Returns true if the parameter is Meta type, and also indicates if it's optional.
*
* @param typeSymbol The type symbol to check
* @return true if the parameter is a Meta type (optional or not)
*/
static boolean isMetaParameter(TypeSymbol typeSymbol) {
// Direct Meta type check
if (isMetaType(typeSymbol)) {
return true;
}

// Check if it's an optional Meta type (mcp:Meta?)
if (typeSymbol.typeKind() == TypeDescKind.UNION) {
UnionTypeSymbol unionTypeSymbol = (UnionTypeSymbol) typeSymbol;
for (TypeSymbol memberType : unionTypeSymbol.memberTypeDescriptors()) {
if (isMetaType(memberType)) {
return true;
}
}
}

return false;
}

private static SessionMode getSessionMode(FunctionDefinitionNode functionDefinitionNode,
SemanticModel semanticModel) {
ServiceDeclarationNode serviceNode = (ServiceDeclarationNode) functionDefinitionNode.parent();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ public enum CompilationDiagnostic {
UNABLE_TO_GENERATE_SCHEMA_FOR_FUNCTION(DiagnosticMessage.ERROR_101, DiagnosticCode.MCP_101, ERROR),
INVALID_PARAMETER_TYPE(DiagnosticMessage.ERROR_102, DiagnosticCode.MCP_102, ERROR),
SESSION_PARAM_MUST_BE_FIRST(DiagnosticMessage.ERROR_103, DiagnosticCode.MCP_103, ERROR),
SESSION_PARAM_NOT_ALLOWED_IN_STATELESS_MODE(DiagnosticMessage.ERROR_104, DiagnosticCode.MCP_104, ERROR);
SESSION_PARAM_NOT_ALLOWED_IN_STATELESS_MODE(DiagnosticMessage.ERROR_104, DiagnosticCode.MCP_104, ERROR),
META_PARAM_MUST_BE_LAST(DiagnosticMessage.ERROR_105, DiagnosticCode.MCP_105, ERROR),
META_PARAM_MUST_BE_OPTIONAL(DiagnosticMessage.ERROR_106, DiagnosticCode.MCP_106, ERROR);

private final String diagnostic;
private final String diagnosticCode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,7 @@ public enum DiagnosticCode {
MCP_101,
MCP_102,
MCP_103,
MCP_104
MCP_104,
MCP_105,
MCP_106
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@ public enum DiagnosticMessage {
ERROR_101("Failed to generate the parameter schema definition for the function ''{0}''." +
" Specify the parameter schema manually using the `@mcp:McpTool` annotation's parameter field."),
ERROR_102("Parameter ''{1}'' in function ''{0}'' must be of type 'anydata'. " +
"Only the first parameter can be of type 'mcp:Session'."),
"Only the first parameter can be of type 'mcp:Session' and only the last parameter can be of type " +
"'mcp:Meta'."),
ERROR_103("Session parameter ''{1}'' in function ''{0}'' must be the first parameter."),
ERROR_104("Session parameter ''{1}'' in function ''{0}'' is not allowed when sessionMode is 'STATELESS'.");
ERROR_104("Session parameter ''{1}'' in function ''{0}'' is not allowed when sessionMode is 'STATELESS'."),
ERROR_105("Meta parameter ''{1}'' in function ''{0}'' must be the last parameter."),
ERROR_106("Meta parameter ''{1}'' in function ''{0}'' must be optional (e.g., 'mcp:Meta?').");

private final String message;

Expand Down
2 changes: 1 addition & 1 deletion examples/clients/mcp-crypto-client/Ballerina.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ observabilityIncluded = true
[[dependency]]
org = "ballerina"
name = "mcp"
version = "1.0.2"
version = "1.0.3"
repository = "local"
11 changes: 7 additions & 4 deletions examples/clients/mcp-crypto-client/Dependencies.toml
Original file line number Diff line number Diff line change
Expand Up @@ -219,10 +219,9 @@ modules = [
[[package]]
org = "ballerina"
name = "mcp"
version = "1.0.2"
version = "1.0.3"
dependencies = [
{org = "ballerina", name = "http"},
{org = "ballerina", name = "io"},
{org = "ballerina", name = "jballerina.java"},
{org = "ballerina", name = "uuid"}
]
Expand All @@ -238,6 +237,7 @@ dependencies = [
{org = "ballerina", name = "io"},
{org = "ballerina", name = "log"},
{org = "ballerina", name = "mcp"},
{org = "ballerina", name = "uuid"},
{org = "ballerinai", name = "observe"}
]
modules = [
Expand Down Expand Up @@ -298,15 +298,15 @@ dependencies = [
[[package]]
org = "ballerina"
name = "time"
version = "2.7.0"
version = "2.8.0"
dependencies = [
{org = "ballerina", name = "jballerina.java"}
]

[[package]]
org = "ballerina"
name = "url"
version = "2.6.0"
version = "2.6.1"
dependencies = [
{org = "ballerina", name = "jballerina.java"}
]
Expand All @@ -321,6 +321,9 @@ dependencies = [
{org = "ballerina", name = "lang.int"},
{org = "ballerina", name = "time"}
]
modules = [
{org = "ballerina", packageName = "uuid", moduleName = "uuid"}
]

[[package]]
org = "ballerinai"
Expand Down
Loading