Skip to content

Commit 0eac795

Browse files
authored
Merge pull request #33 from csells/required-fields-fix
Preserve required fields in ToolInputSchema
2 parents a3c602e + 1f08bd5 commit 0eac795

File tree

7 files changed

+577
-101
lines changed

7 files changed

+577
-101
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.5.2
2+
3+
- Preserve required fields in ToolInputSchema
4+
15
## 0.5.1
26

37
- Add support for OutputScheme (<https://modelcontextprotocol.io/specification/draft/server/tools#output-schema>)

example/iostream-client-server/simple.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import 'dart:async';
22

33
import 'package:mcp_dart/mcp_dart.dart';
44
import 'server_iostream.dart';
5+
56
/// Creates and returns a client with custom stream transport connected to a server.
67
Future<void> main() async {
78
// Create a client
@@ -15,7 +16,7 @@ Future<void> main() async {
1516
// Create custom streams for transport
1617
final serverToClientStreamController = StreamController<List<int>>();
1718
final clientToServerStreamController = StreamController<List<int>>();
18-
19+
1920
// Create transport using custom streams
2021
final clientTransport = IOStreamTransport(
2122
stream: serverToClientStreamController.stream,

example/required_fields_demo.dart

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import 'package:mcp_dart/mcp_dart.dart';
2+
3+
void main() {
4+
// Create a tool with required fields - this simulates what an MCP server would send
5+
final calculatorTool = Tool(
6+
name: 'calculate',
7+
description: 'Performs mathematical calculations',
8+
inputSchema: ToolInputSchema(
9+
properties: {
10+
'operation': {
11+
'type': 'string',
12+
'enum': ['add', 'subtract', 'multiply', 'divide'],
13+
'description': 'The mathematical operation to perform'
14+
},
15+
'a': {'type': 'number', 'description': 'First number'},
16+
'b': {'type': 'number', 'description': 'Second number'},
17+
'precision': {
18+
'type': 'integer',
19+
'description': 'Number of decimal places (optional)',
20+
'default': 2
21+
}
22+
},
23+
required: ['operation', 'a', 'b'], // ← This is now preserved!
24+
),
25+
outputSchema: ToolOutputSchema(
26+
properties: {
27+
'result': {'type': 'number', 'description': 'The calculation result'},
28+
'equation': {
29+
'type': 'string',
30+
'description': 'The equation that was calculated'
31+
}
32+
},
33+
required: ['result'], // ← Output required fields also preserved!
34+
),
35+
);
36+
37+
print('=== MCP Tool Schema Demo ===');
38+
print('Tool: ${calculatorTool.name}');
39+
print('Description: ${calculatorTool.description}');
40+
41+
// Serialize to JSON (what MCP client receives)
42+
final toolJson = calculatorTool.toJson();
43+
print('\n=== Serialized Tool JSON ===');
44+
print('Input required fields: ${toolJson['inputSchema']['required']}');
45+
print('Output required fields: ${toolJson['outputSchema']['required']}');
46+
47+
// This demonstrates the fix - required fields are preserved in JSON
48+
print('\n=== Full Input Schema ===');
49+
final inputSchema = toolJson['inputSchema'] as Map<String, dynamic>;
50+
print('Type: ${inputSchema['type']}');
51+
print('Required: ${inputSchema['required']}');
52+
print('Properties: ${(inputSchema['properties'] as Map).keys.join(', ')}');
53+
54+
// Deserialize back from JSON (roundtrip test)
55+
final deserializedTool = Tool.fromJson(toolJson);
56+
print('\n=== Roundtrip Test ===');
57+
print('Original required: ${calculatorTool.inputSchema.required}');
58+
print('Deserialized required: ${deserializedTool.inputSchema.required}');
59+
print(
60+
'Match: ${_listsEqual(calculatorTool.inputSchema.required, deserializedTool.inputSchema.required)}');
61+
62+
// Convert to OpenAI function calling format
63+
print('\n=== OpenAI Function Format ===');
64+
final openaiFunction = <String, dynamic>{
65+
'type': 'function',
66+
'function': <String, dynamic>{
67+
'name': calculatorTool.name,
68+
'description': calculatorTool.description,
69+
'parameters': calculatorTool.inputSchema.toJson(),
70+
}
71+
};
72+
73+
final functionObj = openaiFunction['function'] as Map<String, dynamic>;
74+
final parameters = functionObj['parameters'] as Map<String, dynamic>;
75+
print('Function name: ${functionObj['name']}');
76+
print('Required parameters: ${parameters['required']}');
77+
print('✓ Ready for LLM integration!');
78+
79+
// Convert to Anthropic Claude format
80+
print('\n=== Anthropic Claude Format ===');
81+
final anthropicTool = <String, dynamic>{
82+
'name': calculatorTool.name,
83+
'description': calculatorTool.description,
84+
'input_schema': calculatorTool.inputSchema.toJson(),
85+
};
86+
87+
final claudeSchema = anthropicTool['input_schema'] as Map<String, dynamic>;
88+
print('Tool name: ${anthropicTool['name']}');
89+
print('Required parameters: ${claudeSchema['required']}');
90+
print('✓ Ready for Claude integration!');
91+
92+
// Simulate a real MCP server response
93+
print('\n=== Simulated MCP Server Response ===');
94+
final listToolsResult = ListToolsResult(tools: [calculatorTool]);
95+
final serverResponse = listToolsResult.toJson();
96+
97+
print('Server response preserves required fields:');
98+
final tools = serverResponse['tools'] as List;
99+
final firstTool = tools[0] as Map<String, dynamic>;
100+
final firstToolInputSchema = firstTool['inputSchema'] as Map<String, dynamic>;
101+
print(' Tool: ${firstTool['name']}');
102+
print(' Required: ${firstToolInputSchema['required']}');
103+
print('✓ MCP server integration works!');
104+
}
105+
106+
bool _listsEqual(List<String>? a, List<String>? b) {
107+
if (a == null && b == null) return true;
108+
if (a == null || b == null) return false;
109+
if (a.length != b.length) return false;
110+
for (int i = 0; i < a.length; i++) {
111+
if (a[i] != b[i]) return false;
112+
}
113+
return true;
114+
}

lib/src/types.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1626,19 +1626,25 @@ class ToolInputSchema {
16261626
/// JSON Schema properties definition.
16271627
final Map<String, dynamic>? properties;
16281628

1629+
/// List of required property names.
1630+
final List<String>? required;
1631+
16291632
const ToolInputSchema({
16301633
this.properties,
1634+
this.required,
16311635
});
16321636

16331637
factory ToolInputSchema.fromJson(Map<String, dynamic> json) {
16341638
return ToolInputSchema(
16351639
properties: json['properties'] as Map<String, dynamic>?,
1640+
required: (json['required'] as List<dynamic>?)?.cast<String>(),
16361641
);
16371642
}
16381643

16391644
Map<String, dynamic> toJson() => {
16401645
'type': type,
16411646
if (properties != null) 'properties': properties,
1647+
if (required != null && required!.isNotEmpty) 'required': required,
16421648
};
16431649
}
16441650

@@ -1650,19 +1656,25 @@ class ToolOutputSchema {
16501656
/// JSON Schema properties definition.
16511657
final Map<String, dynamic>? properties;
16521658

1659+
/// List of required property names.
1660+
final List<String>? required;
1661+
16531662
const ToolOutputSchema({
16541663
this.properties,
1664+
this.required,
16551665
});
16561666

16571667
factory ToolOutputSchema.fromJson(Map<String, dynamic> json) {
16581668
return ToolOutputSchema(
16591669
properties: json['properties'] as Map<String, dynamic>?,
1670+
required: (json['required'] as List<dynamic>?)?.cast<String>(),
16601671
);
16611672
}
16621673

16631674
Map<String, dynamic> toJson() => {
16641675
'type': type,
16651676
if (properties != null) 'properties': properties,
1677+
if (required != null && required!.isNotEmpty) 'required': required,
16661678
};
16671679
}
16681680

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: mcp_dart
22
description: Dart Implementation of Model Context Protocol (MCP) SDK.
3-
version: 0.5.1
3+
version: 0.5.2
44
repository: https://github.com/leehack/mcp_dart
55

66
environment:

0 commit comments

Comments
 (0)