Skip to content

Commit a9eb685

Browse files
authored
add an example of using progress tokens to the tools example (#270)
Initially created this just for testing Gemini CLI support for progress tokens, but it makes sense to land.
1 parent f167ace commit a9eb685

File tree

4 files changed

+73
-20
lines changed

4 files changed

+73
-20
lines changed

pkgs/dart_mcp/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.3.4-wip
2+
3+
- Update the tool calling example to include progress notifications.
4+
15
## 0.3.3
26

37
- Fix `PingRequest` handling when it is sent from a non-Dart client.

pkgs/dart_mcp/example/tools_client.dart

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -62,20 +62,36 @@ void main() async {
6262
// sees fit. To keep this example simple and not require any API keys, we
6363
// just manually call the `concat` tool.
6464
if (tool.name == 'concat') {
65-
print('Calling `${tool.name}` tool');
66-
// Should return "abcd".
67-
final result = await server.callTool(
68-
CallToolRequest(
69-
name: tool.name,
70-
arguments: {
71-
'parts': ['a', 'b', 'c', 'd'],
72-
},
73-
),
65+
const delayMs = 2000;
66+
print(
67+
'Calling `${tool.name}` tool, with an artificial ${delayMs}ms second '
68+
'delay',
69+
);
70+
final request = CallToolRequest(
71+
name: tool.name,
72+
arguments: {
73+
'parts': ['a', 'b', 'c', 'd'],
74+
'delay': delayMs,
75+
},
76+
// Note that in the real world you need unique tokens, either a UUID
77+
// or auto-incrementing int would suffice.
78+
meta: MetaWithProgressToken(progressToken: ProgressToken(1)),
7479
);
80+
// Make sure to listen before awaiting the response - you could listen
81+
// after sending the request but before awaiting the result as well.
82+
server.onProgress(request).listen((progress) {
83+
stdout.write(
84+
'${eraseLine}Progress: ${progress.progress}/${progress.total}: '
85+
'${progress.message}',
86+
);
87+
});
88+
// Should return "abcd".
89+
final result = await server.callTool(request);
90+
7591
if (result.isError == true) {
7692
throw StateError('Tool call failed: ${result.content}');
7793
} else {
78-
print('Tool call succeeded: ${result.content}');
94+
print('${eraseLine}Tool call succeeded: ${result.content}');
7995
}
8096
} else {
8197
throw ArgumentError('Unexpected tool ${tool.name}');
@@ -85,3 +101,5 @@ void main() async {
85101
// Shutdown the client, which will also shutdown the server connection.
86102
await client.shutdown();
87103
}
104+
105+
const eraseLine = '\x1b[2K\r';

pkgs/dart_mcp/example/tools_server.dart

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,19 +39,50 @@ base class MCPServerWithTools extends MCPServer with ToolsSupport {
3939
description: 'The parts to concatenate together',
4040
items: Schema.string(),
4141
),
42+
'delay': Schema.int(
43+
description:
44+
'Duration in milliseconds to delay the response, if passed '
45+
'progress events will be sent every 100ms',
46+
),
4247
},
4348
required: ['parts'],
4449
),
4550
);
4651

4752
/// The implementation of the `concat` tool.
48-
FutureOr<CallToolResult> _concat(CallToolRequest request) => CallToolResult(
49-
content: [
50-
TextContent(
51-
text: (request.arguments!['parts'] as List<dynamic>)
52-
.cast<String>()
53-
.join(''),
54-
),
55-
],
56-
);
53+
FutureOr<CallToolResult> _concat(CallToolRequest request) async {
54+
if (request.arguments!['delay'] case final int delay?) {
55+
Timer? timer;
56+
if (request.meta?.progressToken case final progressToken?) {
57+
final watch = Stopwatch()..start();
58+
timer = Timer.periodic(const Duration(milliseconds: 100), (timer) {
59+
io.stderr.write('Tick ${timer.tick}');
60+
notifyProgress(
61+
ProgressNotification(
62+
progressToken: progressToken,
63+
progress: watch.elapsedMilliseconds,
64+
total: delay,
65+
message:
66+
'Calculating.... ${delay - watch.elapsedMilliseconds}ms left',
67+
),
68+
);
69+
});
70+
} else {
71+
io.stderr.writeln('No progress token');
72+
}
73+
await Future<void>.delayed(Duration(milliseconds: delay));
74+
timer?.cancel();
75+
} else {
76+
io.stderr.writeln('No delay given');
77+
}
78+
return CallToolResult(
79+
content: [
80+
TextContent(
81+
text: (request.arguments!['parts'] as List<dynamic>)
82+
.cast<String>()
83+
.join(''),
84+
),
85+
],
86+
);
87+
}
5788
}

pkgs/dart_mcp/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: dart_mcp
2-
version: 0.3.3
2+
version: 0.3.4-wip
33
description: A package for making MCP servers and clients.
44
repository: https://github.com/dart-lang/ai/tree/main/pkgs/dart_mcp
55
issue_tracker: https://github.com/dart-lang/ai/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Adart_mcp

0 commit comments

Comments
 (0)