Skip to content
Closed
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
15 changes: 9 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,16 @@ void main() async {
server.tool(
"calculate",
description: 'Perform basic arithmetic operations',
inputSchemaProperties: {
'operation': {
'type': 'string',
'enum': ['add', 'subtract', 'multiply', 'divide'],
inputSchema: {
'properties': {
'operation': {
'type': 'string',
'enum': ['add', 'subtract', 'multiply', 'divide'],
},
'a': {'type': 'number'},
'b': {'type': 'number'},
},
'a': {'type': 'number'},
'b': {'type': 'number'},
'required': ['operation', 'a', 'b'],
Comment on lines +48 to +56
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should make it aligned with this https://github.com/modelcontextprotocol/modelcontextprotocol/blob/e7999bf7c268f786df89b0ffb694bdd62cc84827/schema/2025-03-26/schema.ts#L800

It means that we may want to define InputSchemaProperty type that has type, properties, and required And the properties is also the same type.(recursion)

Probably we could also leverage sealed class since it can be string or object type.

},
callback: ({args, extra}) async {
final operation = args!['operation'];
Expand Down
10 changes: 5 additions & 5 deletions lib/src/server/mcp.dart
Original file line number Diff line number Diff line change
Expand Up @@ -82,17 +82,17 @@ class ResourceTemplateRegistration {

class _RegisteredTool {
final String? description;
final Map<String, dynamic>? inputSchemaProperties;
final Map<String, dynamic>? inputSchema;
final ToolCallback callback;

const _RegisteredTool({
this.description,
this.inputSchemaProperties,
this.inputSchema,
required this.callback,
});

Tool toTool(String name) {
final schema = ToolInputSchema(properties: inputSchemaProperties);
final schema = ToolInputSchema.fromJson(inputSchema ?? {});
return Tool(name: name, description: description, inputSchema: schema);
}
}
Expand Down Expand Up @@ -561,15 +561,15 @@ class McpServer {
void tool(
String name, {
String? description,
Map<String, dynamic>? inputSchemaProperties,
Map<String, dynamic>? inputSchema,
required ToolCallback callback,
}) {
if (_registeredTools.containsKey(name)) {
throw ArgumentError("Tool name '$name' already registered.");
}
_registeredTools[name] = _RegisteredTool(
description: description,
inputSchemaProperties: inputSchemaProperties,
inputSchema: inputSchema,
callback: callback,
);
_ensureToolHandlersInitialized();
Expand Down
23 changes: 17 additions & 6 deletions lib/src/types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1756,33 +1756,44 @@ class JsonRpcPromptListChangedNotification extends JsonRpcNotification {

/// Describes the input schema for a tool, based on JSON Schema.
class ToolInputSchema {
/// Must be "object".
final String type = "object";
/// Must be "object" at the top level.
final String type;

/// JSON Schema properties definition.
final Map<String, dynamic>? properties;
final ToolInputSchema? properties;

/// Optional list of required properties.
final List<String>? required;

/// Additional JSON Schema properties (e.g., required).
/// Additional JSON Schema properties.
final Map<String, dynamic> additionalProperties;

const ToolInputSchema({
required this.type,
this.properties,
this.required,
this.additionalProperties = const {},
});

factory ToolInputSchema.fromJson(Map<String, dynamic> json) {
final rest = Map<String, dynamic>.from(json)
..remove('type')
..remove('properties');
..remove('properties')
..remove('required');
return ToolInputSchema(
properties: json['properties'] as Map<String, dynamic>?,
type: json['type'] ?? "object",
properties: json['properties'] != null
? ToolInputSchema.fromJson(json['properties'])
: null,
required: (json['required'] as List<dynamic>?)?.cast<String>(),
additionalProperties: rest,
);
}

Map<String, dynamic> toJson() => {
'type': type,
if (properties != null) 'properties': properties,
if (required != null) 'required': required,
...additionalProperties,
};
}
Expand Down