Skip to content

Commit 3bf650e

Browse files
authored
add completions support to the prompts example (#233)
Closes #220 After this, all major features have at least a basic example.
1 parent aed93b4 commit 3bf650e

File tree

3 files changed

+196
-85
lines changed

3 files changed

+196
-85
lines changed
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
/// A client that interacts with a server that provides prompts and completions
6+
/// for the prompt arguments.
7+
library;
8+
9+
import 'dart:async';
10+
import 'dart:io';
11+
12+
import 'package:dart_mcp/client.dart';
13+
import 'package:dart_mcp/stdio.dart';
14+
15+
void main() async {
16+
try {
17+
// Change the stdin mode so we can handle bytes one at a time, to intercept
18+
// the tab key for auto complete.
19+
stdin.echoMode = false;
20+
stdin.lineMode = false;
21+
22+
// Create the client, which is the top level object that manages all
23+
// server connections.
24+
final client = MCPClient(
25+
Implementation(name: 'example dart client', version: '0.1.0'),
26+
);
27+
print('connecting to server');
28+
29+
// Start the server as a separate process.
30+
final process = await Process.start('dart', [
31+
'run',
32+
'example/prompts_and_completions_server.dart',
33+
]);
34+
// Connect the client to the server.
35+
final server = client.connectServer(
36+
stdioChannel(input: process.stdout, output: process.stdin),
37+
);
38+
// When the server connection is closed, kill the process.
39+
unawaited(server.done.then((_) => process.kill()));
40+
print('server started');
41+
42+
// Initialize the server and let it know our capabilities.
43+
print('initializing server');
44+
final initializeResult = await server.initialize(
45+
InitializeRequest(
46+
protocolVersion: ProtocolVersion.latestSupported,
47+
capabilities: client.capabilities,
48+
clientInfo: client.implementation,
49+
),
50+
);
51+
print('initialized: $initializeResult');
52+
53+
// Ensure the server supports the prompts capability.
54+
if (initializeResult.capabilities.prompts == null) {
55+
await server.shutdown();
56+
throw StateError('Server doesn\'t support prompts!');
57+
}
58+
59+
// Ensure the server supports the completions capability.
60+
if (initializeResult.capabilities.completions == null) {
61+
await server.shutdown();
62+
throw StateError('Server doesn\'t support completions!');
63+
}
64+
65+
// Notify the server that we are initialized.
66+
server.notifyInitialized();
67+
print('sent initialized notification');
68+
69+
// List all the available prompts from the server.
70+
print('Listing prompts from server');
71+
final promptsResult = await server.listPrompts(ListPromptsRequest());
72+
73+
// Iterate each prompt and have the user fill in the arguments.
74+
for (final prompt in promptsResult.prompts) {
75+
print(
76+
'Found prompt ${prompt.name}, fill in the following arguments using '
77+
'tab to complete them:',
78+
);
79+
// For each argument, get a value from the user.
80+
final arguments = <String, Object?>{};
81+
for (var argument in prompt.arguments!) {
82+
stdout.write('${argument.name}: ');
83+
// The current user query.
84+
var current = '';
85+
// Read characters until we get an enter key.
86+
while (true) {
87+
final next = stdin.readByteSync();
88+
// User pressed tab, lets do an auto complete
89+
if (next == 9) {
90+
final completeResult = await server.requestCompletions(
91+
CompleteRequest(
92+
// The ref is the current prompt name.
93+
ref: PromptReference(name: prompt.name),
94+
argument: CompletionArgument(
95+
name: argument.name,
96+
value: current,
97+
),
98+
),
99+
);
100+
// Just auto-fill the first completion for this example
101+
if (completeResult.completion.values.isNotEmpty) {
102+
final firstResult = completeResult.completion.values.first;
103+
stdout.write(firstResult.substring(current.length));
104+
current = firstResult;
105+
}
106+
// If there are no completions, just do nothing.
107+
} else if (next == 10) {
108+
// Enter key, assign the argument and break the loop.
109+
arguments[argument.name] = current;
110+
stdout.writeln('');
111+
break;
112+
} else if (next == 127) {
113+
// Backspace keypress.
114+
if (current.isNotEmpty) {
115+
// Write a backspace followed by a space and then another
116+
// backspace to clear one character.
117+
stdout.write('\b \b');
118+
// Trim current by one.
119+
current = current.substring(0, current.length - 1);
120+
}
121+
} else {
122+
// A regular character, just add it to current and print it to the
123+
// console.
124+
final character = String.fromCharCode(next);
125+
current += character;
126+
stdout.write(character);
127+
}
128+
}
129+
}
130+
131+
// Now fetch the full prompt with the arguments filled in.
132+
final promptResult = await server.getPrompt(
133+
GetPromptRequest(name: prompt.name, arguments: arguments),
134+
);
135+
final promptText = promptResult.messages
136+
.map((m) => (m.content as TextContent).text)
137+
.join('');
138+
139+
// Finally, print the prompt to the user.
140+
print('Got full prompt `${prompt.name}`: "$promptText"');
141+
}
142+
143+
// Shutdown the client, which will also shutdown the server connection.
144+
await client.shutdown();
145+
} finally {
146+
// Reset the terminal modes.
147+
stdin.echoMode = true;
148+
stdin.lineMode = true;
149+
}
150+
}

pkgs/dart_mcp/example/prompts_server.dart renamed to pkgs/dart_mcp/example/prompts_and_completions_server.dart

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5-
/// A server that implements the prompts API using the [PromptsSupport] mixin.
5+
/// A server that implements the prompts API using the [PromptsSupport] mixin,
6+
/// as well as completions for the prompt arguments with the
7+
/// [CompletionsSupport] mixin
68
library;
79

10+
import 'dart:async';
811
import 'dart:io' as io;
912

1013
import 'package:dart_mcp/server.dart';
@@ -19,7 +22,11 @@ void main() {
1922
///
2023
/// This server uses the [PromptsSupport] mixin to provide prompts to the
2124
/// client.
22-
base class MCPServerWithPrompts extends MCPServer with PromptsSupport {
25+
///
26+
/// It also uses the [CompletionsSupport] mixin to provide support for auto
27+
/// completing prompt argument values.
28+
base class MCPServerWithPrompts extends MCPServer
29+
with PromptsSupport, CompletionsSupport {
2330
MCPServerWithPrompts(super.channel)
2431
: super.fromStreamChannel(
2532
implementation: Implementation(
@@ -60,10 +67,46 @@ base class MCPServerWithPrompts extends MCPServer with PromptsSupport {
6067
);
6168
}
6269

70+
@override
71+
/// Handles auto completing arguments based on the known [tags] and
72+
/// [platforms].
73+
FutureOr<CompleteResult> handleComplete(CompleteRequest request) {
74+
// Check that this is for the expected prompt reference. Prompts are
75+
// referenced by their name.
76+
if (!request.ref.isPrompt ||
77+
(request.ref as PromptReference).name != runTestsPrompt.name) {
78+
throw ArgumentError('Unrecognized reference ${request.ref}');
79+
}
80+
// Get the candidates.
81+
final candidates = switch (request.argument.name) {
82+
'tags' => tags,
83+
'platforms' => platforms,
84+
_ =>
85+
throw ArgumentError('Unrecognized argument ${request.argument.name}'),
86+
};
87+
// Return the result by filtering the candidates based on a simple prefix
88+
// match.
89+
return CompleteResult(
90+
completion: Completion(
91+
values: [
92+
for (final candidate in candidates)
93+
if (candidate.startsWith(request.argument.value)) candidate,
94+
],
95+
hasMore: false,
96+
),
97+
);
98+
}
99+
100+
/// The known tags we will autocomplete.
101+
static final tags = ['integration', 'unit', 'slow'];
102+
103+
/// The known platforms we will auto complete.
104+
static final platforms = ['vm', 'chrome'];
105+
63106
/// A prompt that can be used to run tests.
64107
///
65108
/// This prompt has two arguments, `tags` and `platforms`.
66-
final runTestsPrompt = Prompt(
109+
static final runTestsPrompt = Prompt(
67110
name: 'run_tests',
68111
description: 'Run your dart tests',
69112
arguments: [

pkgs/dart_mcp/example/prompts_client.dart

Lines changed: 0 additions & 82 deletions
This file was deleted.

0 commit comments

Comments
 (0)