Fast lookup guide for common MCP Dart SDK operations.
# pubspec.yaml
dependencies:
mcp_dart: ^2.1.0dart pub get # or: flutter pub getimport 'package:mcp_dart/mcp_dart.dart';final server = McpServer(
Implementation(
name: 'server-name',
version: '1.0.0',
),
options: McpServerOptions(
capabilities: ServerCapabilities(
tools: ServerCapabilitiesTools(),
),
),
);final server = StreamableMcpServer(
serverFactory: (sessionId) => McpServer(
Implementation(name: 'server', version: '1.0.0'),
),
host: '0.0.0.0',
port: 3000,
path: '/mcp',
// Optional hardening for browser-accessible deployments
enableDnsRebindingProtection: true,
allowedHosts: {'localhost', 'api.example.com'},
allowedOrigins: {'https://app.example.com'},
);
await server.start();final transport = StreamableHTTPServerTransport(
options: StreamableHTTPServerTransportOptions(
sessionIdGenerator: () =>
'session-${DateTime.now().millisecondsSinceEpoch}',
eventStore: InMemoryEventStore(),
enableDnsRebindingProtection: true,
allowedHosts: {'localhost'},
allowedOrigins: {'http://localhost:5173'},
// Defaults are strict; set to false only for compatibility rollouts.
strictProtocolVersionHeaderValidation: true,
rejectBatchJsonRpcPayloads: true,
),
);
await transport.start();
await server.connect(transport);server.registerTool(
'tool-name',
description: 'What it does',
inputSchema: ToolInputSchema(
properties: {
'param': JsonSchema.string(),
},
required: ['param'],
),
callback: (args, extra) async {
return CallToolResult(
content: [TextContent(text: 'result')],
);
},
);server.registerResource(
'Resource Name',
'resource://example',
null,
(uri, extra) async {
return ReadResourceResult(
contents: [
TextResourceContents(
uri: uri.toString(),
text: 'content',
mimeType: 'text/plain',
),
],
);
},
);server.registerResourceTemplate(
'User Profile',
ResourceTemplateRegistration(
'users://{userId}/profile',
listCallback: null,
),
null,
(uri, vars, extra) async {
final userId = vars['userId'];
return ReadResourceResult(
contents: [
TextResourceContents(
uri: uri.toString(),
text: jsonEncode(await getUser(userId)),
mimeType: 'application/json',
),
],
);
},
);const resourceUri = 'ui://dashboard/view.html';
registerAppTool(
server,
'dashboard_show',
McpUiAppToolConfig(
meta: const {
'ui': {
'resourceUri': resourceUri,
},
},
),
(args, extra) async => const CallToolResult(
content: [TextContent(text: 'ok')],
),
);
registerAppResource(
server,
'Dashboard UI',
resourceUri,
const McpUiAppResourceConfig(
meta: {
'ui': {
'prefersBorder': true,
},
},
),
(uri, extra) async => ReadResourceResult(
contents: [
TextResourceContents(
uri: uri.toString(),
mimeType: mcpUiResourceMimeType,
text: '<!doctype html><html></html>',
meta: const McpUiResourceMeta(prefersBorder: true).toMeta(),
),
],
),
);server.registerPrompt(
'prompt-name',
description: 'Prompt description',
argsSchema: {
'arg1': PromptArgumentDefinition(
type: String,
description: 'Argument description',
required: true,
),
},
callback: (args, extra) async {
return GetPromptResult(
messages: [
PromptMessage(
role: PromptMessageRole.user,
content: TextContent(text: 'Prompt with ${args["arg1"]}'),
),
],
);
},
);server.experimental.onListTasks((extra) async => ListTasksResult(tasks: []));
server.experimental.onCancelTask((taskId, extra) async { /* cancel */ });
server.experimental.onGetTask((taskId, extra) async { /* get */ });
server.experimental.onTaskResult((taskId, extra) async { /* result */ });// Stdio
final transport = StdioServerTransport();
await server.connect(transport);
// HTTP
final transport = StreamableHTTPServerTransport(
request: httpRequest,
response: httpResponse,
);
await server.connect(transport);final client = McpClient(
Implementation(
name: 'client-name',
version: '1.0.0',
),
);// Stdio - Dart server
final transport = StdioClientTransport(
StdioServerParameters(
command: 'dart',
args: ['run', 'server.dart'],
),
);
await client.connect(transport);
// Stdio - Node.js server
final transport = StdioClientTransport(
StdioServerParameters(
command: 'node',
args: ['server.js'],
),
);
await client.connect(transport);
// HTTP
final transport = StreamableHTTPClientTransport(
Uri.parse('http://localhost:3000'),
);
await client.connect(transport);final result = await client.listTools();
for (final tool in result.tools) {
print('${tool.name}: ${tool.description}');
}final result = await client.callTool(
CallToolRequest(
name: 'tool-name',
arguments: {'param': 'value'},
),
);
print(result.content.first.text);final result = await client.listResources();
for (final resource in result.resources) {
print('${resource.name}: ${resource.uri}');
print('lastModified: ${resource.annotations?.lastModified}');
}final result = await client.readResource(ReadResourceRequest(
uri: 'resource://example',
));
print(result.contents.first.text);final result = await client.listPrompts(ListPromptsRequest());
for (final prompt in result.prompts) {
print('${prompt.name}: ${prompt.description}');
}final result = await client.getPrompt(GetPromptRequest(
name: 'prompt-name',
arguments: {'arg1': 'value'},
));
for (final message in result.messages) {
print('${message.role}: ${message.content.text}');
}await client.close();server.registerTool(
'echo',
inputSchema: ToolInputSchema(
properties: {
'message': JsonSchema.string(),
},
),
callback: (args, extra) async => CallToolResult(
content: [TextContent(text: args['message'] as String)],
),
);server.registerTool(
'divide',
inputSchema: ToolInputSchema(
properties: {
'a': JsonSchema.number(),
'b': JsonSchema.number(),
},
),
callback: (args, extra) async {
final a = args['a'] as num;
final b = args['b'] as num;
if (b == 0) {
return CallToolResult(
isError: true,
content: [TextContent(text: 'Division by zero')],
);
}
return CallToolResult(
content: [TextContent(text: '${a / b}')],
);
},
);// Read-only
// Read-only
server.registerTool(
'get-data',
inputSchema: ToolInputSchema(properties: {}),
annotations: ToolAnnotations(readOnly: true), // Updated for annotations
callback: (args, extra) async => CallToolResult(content: []),
);
// Destructive
server.registerTool(
'delete-all',
inputSchema: ToolInputSchema(properties: {}),
description: 'Delete all data', // hints deprecated?
callback: (args, extra) async => CallToolResult(content: []),
);
// Note: hints were part of deprecated signature. Use ToolAnnotations!TextContent(text: 'Hello, world!')ImageContent(
data: base64Encode(bytes),
mimeType: 'image/png',
theme: 'dark', // optional: 'light' | 'dark'
)ResourceLink(
uri: 'file:///docs/architecture.md',
name: 'architecture',
mimeType: 'text/markdown',
icons: [
McpIcon(
src: 'https://example.com/icon.png',
mimeType: 'image/png',
theme: IconTheme.dark,
),
],
)EmbeddedResource(
resource: ResourceReference(
uri: 'file:///path',
type: 'resource',
),
)CallToolResult(
content: [
TextContent(text: 'Summary'),
ImageContent(data: chart, mimeType: 'image/png'),
TextContent(text: 'Details'),
],
)return CallToolResult(
isError: true,
content: [TextContent(text: 'Error message')],
);throw McpError(
ErrorCode.invalidParams,
'Invalid parameters',
);ErrorCode.parseError // -32700
ErrorCode.invalidRequest // -32600
ErrorCode.methodNotFound // -32601
ErrorCode.invalidParams // -32602
ErrorCode.internalError // -32603try {
final result = await client.callTool(request);
} on McpError catch (e) {
print('MCP Error: ${e.message} (${e.code})');
} on TimeoutException {
print('Request timed out');
} catch (e) {
print('Unexpected error: $e');
}'name': JsonSchema.string(
minLength: 1,
maxLength: 100,
pattern: r'^[a-zA-Z]+$',
)'age': JsonSchema.number(
minimum: 0,
maximum: 150,
)'count': JsonSchema.integer(
minimum: 1,
)'enabled': JsonSchema.boolean()'status': JsonSchema.string(
enumValues: ['active', 'inactive', 'pending'],
)'tags': JsonSchema.array(
items: JsonSchema.string(),
minItems: 1,
maxItems: 10,
)'config': JsonSchema.object(
properties: {
'key': JsonSchema.string(),
'value': JsonSchema.number(),
},
required: ['key'],
)await server.sendLoggingMessage(
LoggingMessageNotification(
level: LoggingLevel.info,
data: 'Processing started',
),
);await server.sendResourceUpdated('resource://uri');await server.sendToolListChanged();
await server.sendResourceListChanged();
await server.sendPromptListChanged();final server = McpServer(
Implementation(name: 'server', version: '1.0.0'),
// Capabilities auto-detected from registrations
options: McpServerOptions(
capabilities: ServerCapabilities(
tools: ServerCapabilitiesTools(),
),
),
);final client = McpClient(
Implementation(name: 'client', version: '1.0.0'),
options: McpClientOptions(
capabilities: ClientCapabilities(
sampling: ClientCapabilitiesSampling(tools: true),
roots: ClientCapabilitiesRoots(listChanged: true),
elicitation: ClientElicitation(
form: ClientElicitationForm(applyDefaults: true),
),
),
),
);await client.setLoggingLevel(SetLevelRequest(
level: LoggingLevel.debug,
));LoggingLevel.debug
LoggingLevel.info
LoggingLevel.notice
LoggingLevel.warning
LoggingLevel.error
LoggingLevel.critical
LoggingLevel.alert
LoggingLevel.emergencyclient.setNotificationHandler<JsonRpcLoggingMessageNotification>(
Method.notificationsMessage,
(notification) async {
print('[${notification.logParams.level}] ${notification.logParams.data}');
},
(params, meta) => JsonRpcLoggingMessageNotification(
logParams: LoggingMessageNotification.fromJson(params ?? {}),
),
);import 'package:logging/logging.dart' as app_log;
import 'package:mcp_dart/mcp_dart.dart' as mcp;
final appLogger = app_log.Logger('app.mcp');
mcp.setMcpLogHandler((name, level, message) {
final mapped = switch (level) {
mcp.LogLevel.debug => app_log.Level.FINE,
mcp.LogLevel.info => app_log.Level.INFO,
mcp.LogLevel.warn => app_log.Level.WARNING,
mcp.LogLevel.error => app_log.Level.SEVERE,
};
appLogger.log(mapped, '[$name] $message');
});
// Silence SDK runtime logs.
mcp.silenceMcpLogs();
// Restore default SDK log output.
mcp.resetMcpLogHandler();await client.subscribeResource(SubscribeRequest(
uri: 'resource://uri',
));client.setNotificationHandler<JsonRpcResourceUpdatedNotification>(
Method.notificationsResourcesUpdated,
(notification) async {
print('Updated: ${notification.updatedParams.uri}');
// Re-read resource
},
(params, meta) => JsonRpcResourceUpdatedNotification(
updatedParams: ResourceUpdatedNotification.fromJson(params ?? {}),
),
);await client.unsubscribeResource(UnsubscribeRequest(
uri: 'resource://uri',
));final result = await client.complete(CompleteRequest(
ref: CompletionReference(
type: CompletionReferenceType.resourceRef,
uri: 'users://{userId}/profile',
),
argument: CompletionArgument(
name: 'userId',
value: 'al', // Partial
),
));
for (final value in result.completion.values) {
print(value); // alice, alex, alan, ...
}final result = await client.complete(CompleteRequest(
ref: CompletionReference(
type: CompletionReferenceType.promptRef,
name: 'translate',
),
argument: CompletionArgument(
name: 'language',
value: 'Spa',
),
));// Core SDK
import 'package:mcp_dart/mcp_dart.dart';
// For HTTP servers (VM only)
import 'dart:io';
// For async operations
import 'dart:async';
// For JSON encoding
import 'dart:convert';
// For base64 encoding
import 'dart:convert' show base64Encode, base64Decode;test('example', () async {
final s2c = StreamController<String>();
final c2s = StreamController<String>();
final server = McpServer(...);
await server.connect(IOStreamTransport(
stream: c2s.stream,
sink: s2c.sink,
));
final client = McpClient(...);
await client.connect(IOStreamTransport(
stream: s2c.stream,
sink: c2s.sink,
));
// Test operations
final result = await client.callTool(...);
expect(result.content.first.text, 'expected');
// Cleanup
await client.close();
await server.close();
});import 'dart:io' show Platform;
if (Platform.isWeb) {
// Web-specific code
} else {
// VM-specific code
}- ✅ Always close clients:
await client.close() - ✅ Validate tool inputs with JSON schema
- ✅ Handle all error cases (McpError, TimeoutException)
- ✅ Use type-safe argument access:
args['key'] as Type - ✅ Provide clear descriptions for tools/resources/prompts
- ✅ Use appropriate tool hints (readOnly, destructive, etc.)
- ✅ Check server capabilities before using features
- ✅ Sanitize and validate user inputs for security
- ✅ Use meaningful error messages
- Main README: See ../README.md for overview and platform support
- Getting Started: See getting-started.md
- Examples: See examples.md