Quick Reference
July 31, 2026 ยท View on GitHub
Use this page for common mcp_dart calls. The server guide,
client guide, tools guide, and
transport guide explain the full APIs and edge cases.
Install and import
dependencies:
mcp_dart: ^2.4.0
import 'package:mcp_dart/mcp_dart.dart';
The 2.4.0 SDK requires Dart 3.4 or later. The stable 0.2.0 CLI requires Dart 3.12 or later.
Protocol profile
The 2.4.0 SDK defaults to McpProtocol.stable: try MCP 2026-07-28, then
fall back to legacy initialization when needed. Body-only
discovery probes are bounded to five seconds; HTTP retains its normal request
timeout.
const legacyClientOptions = McpClientOptions(protocol: McpProtocol.legacy);
const strictServerOptions = McpServerOptions(
protocol: McpProtocol.require2026,
);
See the MCP 2026-07-28 transition guide before depending on protocol-specific behavior.
Server
Create and connect
final server = McpServer(
const Implementation(name: 'example-server', version: '1.0.0'),
options: const McpServerOptions(
capabilities: ServerCapabilities(
tools: ServerCapabilitiesTools(listChanged: true),
resources: ServerCapabilitiesResources(
subscribe: true,
listChanged: true,
),
prompts: ServerCapabilitiesPrompts(listChanged: true),
),
),
);
await server.connect(StdioServerTransport());
Use StreamableMcpServer for a high-level HTTP server:
final httpServer = StreamableMcpServer(
serverFactory: (_) => McpServer(
const Implementation(name: 'remote-server', version: '1.0.0'),
),
host: '127.0.0.1',
port: 3000,
path: '/mcp',
allowedHosts: {'localhost', '127.0.0.1'},
allowedOrigins: {'http://localhost:5173'},
);
await httpServer.start();
Keep DNS rebinding protection enabled and use exact host/origin allowlists for browser or remote deployments. See Streamable HTTP.
Register a tool
server.registerTool(
'add',
description: 'Add two numbers',
inputSchema: JsonSchema.object(
properties: {
'a': JsonSchema.number(),
'b': JsonSchema.number(),
},
required: ['a', 'b'],
),
callback: (arguments, extra) async {
final a = arguments['a'] as num;
final b = arguments['b'] as num;
return CallToolResult(
content: [TextContent(text: '${a + b}')],
);
},
);
Return CallToolResult(isError: true, ...) for an expected tool failure. Throw
McpError for a protocol-level failure. See Tools.
Register a resource
server.registerResource(
'Status',
'status://current',
(description: 'Current service status', mimeType: 'application/json'),
(uri, extra) async => ReadResourceResult(
contents: [
TextResourceContents(
uri: uri.toString(),
mimeType: 'application/json',
text: '{"status":"ok"}',
),
],
),
);
Use registerResourceTemplate for parameterized URIs and declare
resources.subscribe only when supporting legacy MCP 2025-11-25 resource
subscriptions. MCP 2026-07-28 sends resource updates through
subscriptions/listen.
Register a prompt
server.registerPrompt(
'review',
description: 'Review a code change',
argsSchema: {
'diff': PromptArgumentDefinition(
description: 'Patch to review',
required: true,
),
},
callback: (arguments, extra) async {
final diff = arguments?['diff'] as String? ?? '';
return GetPromptResult(
messages: [
PromptMessage(
role: PromptMessageRole.user,
content: TextContent(text: diff),
),
],
);
},
);
For tasks, MCP Apps metadata, completions, and advanced resource templates, see the server guide.
Client
Connect
final client = McpClient(
const Implementation(name: 'example-client', version: '1.0.0'),
);
await client.connect(
StdioClientTransport(
const StdioServerParameters(
command: 'dart',
args: ['run', 'bin/server.dart'],
),
),
);
Remote or browser client:
await client.connect(
StreamableHttpClientTransport(Uri.parse('https://mcp.example.com/mcp')),
);
Always close the client:
try {
// Use the client.
} finally {
await client.close();
}
Discover and use primitives
final tools = await client.listTools();
final toolResult = await client.callTool(
const CallToolRequest(
name: 'add',
arguments: {'a': 2, 'b': 3},
),
);
final resources = await client.listResources();
final resource = await client.readResource(
const ReadResourceRequest(uri: 'status://current'),
);
final prompts = await client.listPrompts();
final prompt = await client.getPrompt(
const GetPromptRequest(
name: 'review',
arguments: {'diff': '...'},
),
);
Check advertised capabilities before optional operations:
final updates = client.listenSubscriptions(
const SubscriptionsListenRequest(
notifications: SubscriptionFilter(
resourceSubscriptions: ['status://current'],
),
),
);
await updates.acknowledged;
For MCP 2025-11-25 stateful peers, check resources.subscribe before using
the legacy subscribeResource/unsubscribeResource methods.
See the client guide for progress, sampling, roots, elicitation, completions, tasks, reconnect behavior, and subscriptions.
Content types
TextContent(text: 'hello');
ImageContent(
data: base64Data,
mimeType: 'image/png',
);
AudioContent(
data: base64Audio,
mimeType: 'audio/wav',
);
ResourceLink(
uri: 'file:///report.txt',
name: 'Report',
mimeType: 'text/plain',
);
EmbeddedResource(
resource: TextResourceContents(
uri: 'memo://1',
text: 'embedded text',
mimeType: 'text/plain',
),
);
Tool results can contain multiple content items. MCP 2026-07-28 also supports the documented structured-content helpers.
Prompts use the same content types. For example, a prompt callback can return
an ImageContent in a PromptMessage when the selected prompt needs to supply
an image to the model:
return GetPromptResult(
messages: [
PromptMessage(
role: PromptMessageRole.user,
content: ImageContent(
data: base64Data,
mimeType: 'image/png',
),
),
],
);
JSON Schema
final schema = JsonSchema.object(
properties: {
'query': JsonSchema.string(description: 'Search text'),
'limit': JsonSchema.integer(minimum: 1, maximum: 100),
'tags': JsonSchema.array(items: JsonSchema.string()),
'mode': JsonSchema.string(enumValues: ['fast', 'thorough']),
},
required: ['query'],
);
The SDK validates JSON Schema Draft 2020-12 by default and accepts an explicitly
declared Draft 7 schema for MCP 2025-11-25 compatibility. Same-document $ref
and $dynamicRef references are supported; unresolved relative and network
references and unsupported dialects are rejected. Custom vocabularies are
preserved but not interpreted. Validate business rules in the callback as well
as describing inputs in the schema.
Errors
// API or business-logic failure
return CallToolResult(
isError: true,
content: [TextContent(text: 'The requested record was not found.')],
);
// Input validation failure
return const CallToolResult(
isError: true,
content: [TextContent(text: 'Expected a non-empty query.')],
);
Use tool error results for input validation, API, and business-logic failures so
the model can correct and retry. JSON-RPC errors are reserved for protocol-level
problems such as malformed requests, unknown tools, and server failures.
Common JSON-RPC codes are available through ErrorCode, including
parseError, invalidRequest, methodNotFound, invalidParams, and
internalError.
Notifications and logging
For MCP 2026-07-28, acknowledge the caller's subscriptions/listen filter
before sending correlated notifications on that request stream:
server.server.setRequestHandler<JsonRpcSubscriptionsListenRequest>(
Method.subscriptionsListen,
(request, extra) async {
final acknowledged = request.listenParams.notifications.acknowledgedBy(
server.server.getCapabilities(),
);
await extra.sendSubscriptionAcknowledged(acknowledged);
if (acknowledged.toolsListChanged == true) {
await extra.sendSubscriptionNotification(
const JsonRpcToolListChangedNotification(),
);
}
if (acknowledged.promptsListChanged == true) {
await extra.sendSubscriptionNotification(
const JsonRpcPromptListChangedNotification(),
);
}
return const EmptyResult();
},
(id, params, meta) => JsonRpcSubscriptionsListenRequest(
id: id,
listenParams: SubscriptionsListenRequest.fromJson(params!),
meta: meta,
),
);
MCP 2026-07-28 deprecates protocol logging. The compatibility API belongs inside a request handler and emits only messages allowed by that request's log level:
await server.sendStatelessLoggingMessage(
const LoggingMessageNotification(
level: LoggingLevel.info,
data: 'Working on the request',
),
requestMeta: extra.meta,
requestId: extra.requestId,
);
The request ID is mandatory for routing on Streamable HTTP, and the server must
send only notification kinds present in the acknowledged filter. Legacy MCP
2025-11-25 peers instead use global capability-gated methods such as
sendToolListChanged, sendPromptListChanged, sendResourceUpdated, and
logging/setLevel.
Stdio servers must reserve stdout for MCP frames; send application logs to
stderr. Configure internal SDK logs with setMcpLogHandler,
silenceMcpLogs, or resetMcpLogHandler.
Basic utilities
Use ping to verify that a connected peer is still responsive. Clients can
ping any connected server; server-to-client ping is available only in the
legacy stateful profile:
await client.ping();
// MCP 2025-11-25 only:
await server.server.ping();
List operations use opaque cursors. Pass each nextCursor back unchanged and
stop when it is absent. Guard against a faulty peer repeating a cursor:
String? cursor;
final seen = <String>{};
do {
final page = await client.listTools(
params: cursor == null ? null : ListToolsRequest(cursor: cursor),
);
for (final tool in page.tools) {
print(tool.name);
}
cursor = page.nextCursor;
if (cursor != null && !seen.add(cursor)) {
throw StateError('tools/list repeated cursor "$cursor"');
}
} while (cursor != null);
For cancellation, pass a BasicAbortController.signal in RequestOptions and
abort it when the caller no longer needs the result. Long-running handlers
should observe extra.signal.aborted and stop promptly:
final controller = BasicAbortController();
final pending = client.callTool(
const CallToolRequest(name: 'long-running'),
options: RequestOptions(signal: controller.signal),
);
controller.abort('No longer needed');
try {
await pending;
} on AbortError {
// Expected: the caller no longer needs this result.
}
See Tools: progress and cancellation for server-side progress and cleanup patterns.
Testing and verification
- Use IO stream/custom transports for in-process unit tests.
- Use
mcp_dart inspect-serverorinspect-clientfor a live target. - Use
mcp_dart conformancefor this repository's built-in regression cases; it is not a certification tool for arbitrary peers. - Run the linked interop fixtures for cross-SDK claims.
Platform reminders
| Target | Recommended transport |
|---|---|
| Dart VM / desktop helper | Stdio or Streamable HTTP |
| Browser / Flutter Web | Streamable HTTP client |
| Flutter mobile | Remote Streamable HTTP; app-managed helper only for local IPC |
| Unit tests / in-process | IO stream or custom transport |
See Flutter recipes for lifecycle and secure-storage guidance.