MCP Server について
MCP Serverは、FlutterアプリでMCPサーバーを構築する開発者向けのDartプラグインです。複数のMCP仕様と基本機能に加え、各種トランスポート、認可、タスク処理、セッション監視に対応します。
主な機能
- 01リソース、ツール、プロンプト、補完に対応
- 02複数のMCP仕様バージョンに対応
- 03stdio、SSE、Streamable HTTPのトランスポート
- 04認可とスキーマ検証
- 05タスクとセッションの管理
- 06ヘルスチェックとメトリクス
活用シーン
- 01Flutterアプリのデータや操作をLLMクライアントに公開
- 02stdioまたはHTTPでMCPを提供
- 03長時間のツール呼び出しをタスクとして処理
MCP 対応
MCPサーバーを実装し、MCPクライアントにリソース、ツール、プロンプトを公開します。stdio、SSE、Streamable HTTPのトランスポートに対応します。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
このサーバーのパッケージやリモートエンドポイントはまだ取得できていません。プロジェクトの README の手順に従ってください。
ツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
MCP サーバー
Model Context Protocol(MCP)サーバーを実装するための Dart プラグインです。このプラグインを使用すると、Flutter アプリケーションからデータ、機能、インタラクションパターンを標準化された方法で大規模言語モデル(LLM)アプリケーションに公開できます。
機能
- すべての MCP リビジョン:2024-11-05 から 2026-07-28 まで、および 2026-07-28 のタスク拡張 — 詳細は以下の プロトコルバージョン を参照してください
- コアプリミティブ:
Resources、Tools、Prompts、およびCompletions(2025-06-18 以降) - サーバー起点のリクエスト:
requestClientSampling、requestClientRoots、requestClientElicitation。レスポンスのルーティングとタイムアウトに対応しています - 認可:すべての HTTP リクエストはディスパッチ前に判定されます(
WWW-Authenticateによる 401 / 403)。Protected Resource Metadata(RFC 9728)、オーディエンスとスコープのチェックに対応しています - 構造化されたツール出力(2025-06-18):
outputSchema+structuredContent+resource_linkコンテンツタイプ - アイコン + サンプリングツール呼び出し(2025-11-25):
Tool.icons/Resource.icons/Prompt.iconsに加え、sampling/createMessageの新しいtools/toolChoiceフィールド - リスト変更通知:仕様で定められた
notifications/{tools,resources,prompts}/list_changed - 進行状況 + キャンセル:送信側の
notifyProgress、受信側のnotifications/cancelledおよびnotifications/progressリスナー - セッションイベント:
onConnect/onDisconnectストリーム、セッションごとのプロトコルバージョン + ケイパビリティ - キャッシュヒント(2026-07-28):discover、lists、reads の
ttlMs/cacheScope(cacheTtl、publicLists) - ヘルス + メトリクス:
getHealth()、名前付きタイマー、カスタムカウンター - トランスポート:stdio、SSE(レガシー)、Streamable HTTP(2025-03-26 以降の標準)
- プラットフォーム:
dart:ioが利用可能な環境(Android、iOS、Linux、Windows、macOS)
プロトコルバージョン
Model Context Protocol のすべてのリビジョンを実装しています。各クライアントには、 接続時に指定されたバージョンが適用されます。
| バージョン | 備考 |
|---|---|
2024-11-05 | オリジナル。HTTP+SSE トランスポート |
2025-03-26 | Streamable HTTP、認可、JSON-RPC バッチ |
2025-06-18 | Elicitation、構造化されたツール出力、リソースリンク、MCP-Protocol-Version ヘッダー。バッチは廃止 |
2025-11-25 | アイコン、ツールを使ったサンプリング、URL Elicitation、タスク |
2026-07-28 | ステートレス:ハンドシェイクなし、server/discover、リクエストごとの _meta、subscriptions/listen、複数回のラウンドトリップリクエスト、キャッシュヒント。タスク拡張 |
initialize は McpProtocol.supportedVersions(2025-11-25 以前)からネゴシエーションします。2026-07-28 にはハンドシェイクがありません。stdio では、_meta でバージョンを指定したリクエストはそのリビジョンで処理されます。Streamable HTTP では、セッションを使用する各リビジョンと同じエンドポイントで処理されます。セッションごとの状態を保持するサーバーでは enableStateless: false(TransportConfig.streamableHttp または StreamableHttpServerConfig)を設定します。この場合、2026-07-28 のクライアントは initialize にフォールバックします。
2.x からの移行:MIGRATION-3.0.md。
タスク
ツールの execution.taskSupport(optional、required、forbidden)によって、呼び出しがタスクとして実行されるかどうかが決まります。
- 2025-11-25(リクエスト側が指定):次を宣言します。
ServerCapabilities(tasks: {'list': {}, 'cancel': {}, 'requests': {'tools': {'call': {}}}})。taskを含む呼び出しにはCreateTaskResultが返され、バックグラウンドで実行されます。その後、tasks/get、tasks/result(完了まで待機)、tasks/list、tasks/cancelを使用します。サーバーは、クライアントにsampling/createMessage/elicitation/createをタスクとして実行するよう依頼することもできます (requestClientTask、clientTaskResult、…)。 - 2026-07-28(タスク拡張機能、サーバー側が指定):
extensions: {tasksExtensionId: {}}を宣言します。この拡張機能を宣言したクライアントからの呼び出しは、ツールが許可していればタスクとして実行されます。その後、tasks/get、tasks/update、tasks/cancelを使用します。 - タスクの処理中に、
server.currentTask.requestInput({...})を呼び出すと、クライアントに入力を要求できます(2025-11-25 ではタグ付きリクエスト、拡張機能ではinputRequests)。
タスクは認証済みユーザーに属します。認証されていない場合(2025-11-25)は、タスクを作成したセッションに属します。他のユーザーはタスクの読み取り、一覧表示、キャンセルを行えません。どちらにも該当しない場合(認証なしの2026-07-28の方式)、タスクはその ID(ランダムな128ビット)のみで保護されます。タスクの結果が機密情報である場合は、認証を使用してください。制限は McpTaskStore(maxTtl:, defaultTtl:, maxTasksPerOwner:) で設定します。
JSON Schema
JsonSchemaValidator(このパッケージがエクスポートします)は、MCP が受け取るスキーマ、すなわちツールの inputSchema / outputSchema と、elicitation の requestedSchema を評価します。
- 方言:2020-12(スキーマに
$schemaがない場合に使用)、2019-09、draft-07、draft-06、draft-04。別の方言を指定するスキーマはJsonSchemaExceptionで拒否されます。 - 参照は、スキーマ内、
registryに渡されたドキュメント内、バンドルされたメタスキーマに対して解決されます。ネットワーク経由で取得することはありません。解決できない参照があるスキーマは拒否されます。 - 評価には上限があります(
JsonSchemaLimits:深さ、サブスキーマ数、評価回数、時間)。 assertFormat: trueを指定しない限り、formatは注釈として扱われます。assertFormat: trueの場合は、email、uri、date、date-timeが検証されます。- 適合性:公式の JSON-Schema-Test-Suite(5つの方言すべての必須テストに加え、上記機能の任意テスト)。
はじめに
基本的な使い方
import 'package:mcp_server/mcp_server.dart';
void main() {
// Create a server with simple boolean capabilities
final server = Server(
name: 'Example Server',
version: '1.0.0',
capabilities: ServerCapabilities.simple(
tools: true,
resources: true,
prompts: true,
),
);
// Add a simple calculator tool
server.addTool(
name: 'calculator',
description: 'Perform basic calculations',
inputSchema: {
'type': 'object',
'properties': {
'operation': {
'type': 'string',
'enum': ['add', 'subtract', 'multiply', 'divide'],
'description': 'Mathematical operation to perform'
},
'a': {
'type': 'number',
'description': 'First operand'
},
'b': {
'type': 'number',
'description': 'Second operand'
}
},
'required': ['operation', 'a', 'b']
},
handler: (arguments) async {
final operation = arguments['operation'] as String;
final a = (arguments['a'] is int) ? (arguments['a'] as int).toDouble() : arguments['a'] as double;
final b = (arguments['b'] is int) ? (arguments['b'] as int).toDouble() : arguments['b'] as double;
double result;
switch (operation) {
case 'add':
result = a + b;
break;
case 'subtract':
result = a - b;
break;
case 'multiply':
result = a * b;
break;
case 'divide':
if (b == 0) {
return CallToolResult(
content: [TextContent(text: 'Division by zero error')],
isError: true,
);
}
result = a / b;
break;
default:
return CallToolResult(
content: [TextContent(text: 'Unknown operation: $operation')],
isError: true,
);
}
return CallToolResult(content: [TextContent(text: 'Result: $result')]);
},
);
// Add a resource
server.addResource(
uri: 'time://current',
name: 'Current Time',
description: 'Get the current date and time',
mimeType: 'text/plain',
handler: (uri, params) async {
final now = DateTime.now().toString();
return ReadResourceResult(
contents: [
TextResourceContents(
uri: uri,
text: now,
mimeType: 'text/plain',
),
],
);
},
);
// Add a template prompt
server.addPrompt(
name: 'greeting',
description: 'Generate a customized greeting',
arguments: [
PromptArgument(
name: 'name',
description: 'Name to greet',
required: true,
),
PromptArgument(
name: 'formal',
description: 'Whether to use formal greeting style',
required: false,
),
],
handler: (arguments) async {
final name = arguments['name'] as String;
final formal = arguments['formal'] as bool? ?? false;
final String systemPrompt = formal
? 'You are a formal assistant. Address the user with respect and formality.'
: 'You are a friendly assistant. Be warm and casual in your tone.';
// Prompt messages are `user` or `assistant`; instructions go in the
// text (the specification has no system role here).
final messages = [
PromptMessage(
role: Role.user,
content: TextContent(text: '$systemPrompt\n\nPlease greet $name'),
),
];
return GetPromptResult(
description: 'A ${formal ? 'formal' : 'casual'} greeting for $name',
messages: messages,
);
},
);
// Connect to transport - old way (still supported)
final transportResult = McpServer.createStdioTransport();
final transport = transportResult.get();
server.connect(transport);
}
// NEW: Simplified unified API (recommended)
void simplifiedMain() async {
final serverResult = await McpServer.createAndStart(
config: McpServer.simpleConfig(
name: 'Example Server',
version: '1.0.0',
),
transportConfig: TransportConfig.stdio(),
);
await serverResult.fold(
(server) async {
// Add tools, resources, prompts as shown above
// Server is already running
await Future.delayed(const Duration(hours: 24));
},
(error) => print('Server failed: $error'),
);
}
トランスポートの設定
MCP Server は、統一されたトランスポート設定に対応しました。
// STDIO Transport
TransportConfig.stdio()
// SSE Transport
TransportConfig.sse(
host: 'localhost',
port: 8080,
endpoint: '/sse',
authToken: 'optional-token',
)
// Streamable HTTP Transport - SSE Streaming Mode (default)
TransportConfig.streamableHttp(
host: 'localhost',
port: 8081,
endpoint: '/mcp',
isJsonResponseEnabled: false, // SSE streaming mode (default)
)
// Streamable HTTP Transport - JSON Response Mode
TransportConfig.streamableHttp(
host: 'localhost',
port: 8081,
endpoint: '/mcp',
isJsonResponseEnabled: true, // JSON response mode
)
中核となる概念
サーバーの機能
MCP Server では、機能を2つの方法で設定できます。
シンプルなブール値による設定
基本的な使用やテストには、ServerCapabilities.simple() を使用します。
final server = Server(
name: 'Simple Server',
version: '1.0.0',
capabilities: ServerCapabilities.simple(
tools: true,
toolsListChanged: true,
resources: true,
resourcesSubscribe: true,
resourcesListChanged: true,
prompts: true,
promptsListChanged: true,
logging: true,
completions: true,
),
);
オブジェクトによる設定
スキーマの機能オブジェクトに加え、tasks と extensions を設定します。
final server = Server(
name: 'Advanced Server',
version: '1.0.0',
capabilities: ServerCapabilities(
tools: ToolsCapability(listChanged: true),
resources: ResourcesCapability(listChanged: true, subscribe: true),
prompts: PromptsCapability(listChanged: true),
logging: LoggingCapability(),
completions: CompletionsCapability(),
// 2025-11-25 tasks; 2026-07-28 tasks extension
tasks: {'list': {}, 'cancel': {}, 'requests': {'tools': {'call': {}}}},
extensions: {tasksExtensionId: {}},
),
);
Sampling、roots、elicitation はクライアントの機能です。クライアントがこれらを宣言している場合、サーバーはそれらを要求します(requestClientSampling、requestClientRoots、requestClientElicitation、または2026-07-28の McpMrtr 入力リクエスト)。
サーバー
Server クラスは、MCP プロトコルを扱うための中心的なインターフェースです。接続管理、プロトコルへの準拠、メッセージのルーティングを担います。
final server = Server(
name: 'My App',
version: '1.0.0',
capabilities: ServerCapabilities.simple(
tools: true,
resources: true,
prompts: true,
),
);
リソース
リソースを使うと、LLM にデータを公開できます。REST API の GET エンドポイントに似ており、データを提供しますが、重大な計算を実行したり、副作用を起こしたりするものではありません。
// Static resource
server.addResource(
uri: 'config://app',
name: 'App Configuration',
description: 'Application configuration data',
mimeType: 'text/plain',
handler: (uri, params) async {
final configData = "app_name=MyApp\nversion=1.0.0\ndebug=false";
return ReadResourceResult(
contents: [
TextResourceContents(
uri: uri,
text: configData,
mimeType: 'text/plain',
),
],
);
},
);
// Resource with URI template
server.addResource(
uri: 'file://{path}',
name: 'File Resource',
description: 'Access files on the system',
mimeType: 'application/octet-stream',
uriTemplate: {
'type': 'object',
'properties': {
'path': {
'type': 'string',
'description': 'Path to the file'
}
}
},
handler: (uri, params) async {
// Extract path and read file content
final path = params['path'] ?? uri.substring('file://'.length);
final content = await File(path).readAsString();
return ReadResourceResult(
contents: [
TextResourceContents(
uri: uri,
text: content,
mimeType: 'text/plain',
),
],
);
},
);
ツール
ツールを使うと、LLM はサーバーを通じてアクションを実行できます。リソースとは異なり、ツールは計算を実行し、副作用を起こすことが想定されています。
server.addTool(
name: 'currentDateTime',
description: 'Get the current date and time',
inputSchema: {
'type': 'object',
'properties': {
'format': {
'type': 'string',
'description': 'Output format (full, date, time)',
'default': 'full'
}
},
'required': []
},
handler: (args) async {
final format = args['format'] as String? ?? 'full';
final now = DateTime.now();
String result;
switch (format) {
case 'date':
result = '${now.year}-${now.month.toString().padLeft(2, '0')}-${now.day.toString().padLeft(2, '0')}';
break;
case 'time':
result = '${now.hour.toString().padLeft(2, '0')}:${now.minute.toString().padLeft(2, '0')}:${now.second.toString().padLeft(2, '0')}';
break;
case 'full':
default:
result = now.toIso8601String();
break;
}
return CallToolResult(content: [TextContent(text: result)]);
},
);
プロンプト
プロンプトは、LLM がサーバーを効果的に利用するための再利用可能なテンプレートです。
server.addPrompt(
name: 'codeReview',
description: 'Generate a code review for a code snippet',
arguments: [
PromptArgument(
name: 'code',
description: 'Code to review',
required: true,
),
PromptArgument(
name: 'language',
description: 'Programming language of the code',
required: true,
),
],
handler: (args) async {
final code = args['code'] as String;
final language = args['language'] as String;
final systemPrompt = '''
You are an expert code reviewer. Review the provided code with these guidelines:
1. Identify potential bugs or issues
2. Suggest optimizations for performance or readability
3. Highlight good practices used in the code
4. Provide constructive feedback for improvements
Be specific in your feedback and provide code examples when suggesting changes.
''';
final messages = [
PromptMessage(
role: Role.user,
content: TextContent(
text: '$systemPrompt\nPlease review this $language code:\n\n```$language\n$code\n```'),
),
];
return GetPromptResult(
description: 'Code review for $language code',
messages: messages,
);
},
);
セッション管理
MCP Server は、クライアントのセッションイベントを監視するための強力なストリームベースのシステムを備えています。
// Create a logger
final logger = Logger('mcp_server.sessions');
// Listen for client connections
server.onConnect.listen((session) {
logger.info('Client connected: ${session.id}');
// Initialize session resources
});
// Listen for client disconnections
server.onDisconnect.listen((session) {
logger.info('Client disconnected: ${session.id}');
// Clean up session resources
});
ClientSession オブジェクトには、次のような有用な情報が含まれています。
- セッション ID
- 接続時刻
- プロトコルのバージョン
- クライアントの機能
- クライアントのルートディレクトリ
セッション管理のベストプラクティス
-
両方のイベントを必ず処理します:セッションの整合性を保つため、接続イベントと切断イベントの両方を監視します。
-
ストリームのサブスクリプションにエラー処理を追加します:
server.onConnect.listen( (session) { // Normal event handling }, onError: (error) { logger.severe('Error in connection stream: $error'); }, ); -
不要になったサブスクリプションはキャンセルします:
final subscription = server.onConnect.listen((session) { /* ... */ }); // Later, when done: subscription.cancel(); -
状態管理にセッションイベントを使用します:セッションイベントに基づいてアプリケーションの状態を維持します。
-
監視用のメトリクスを追跡します:セッション数、継続時間、その他のメトリクスを記録します。
トランスポートレイヤー
標準入出力
コマンドラインツールおよび直接統合向け:
final transportResult = McpServer.createStdioTransport();
final transport = transportResult.get();
server.connect(transport);
Server-Sent Events(SSE)
HTTP ベースの通信向け:
final sseConfig = SseServerConfig(
endpoint: '/sse',
messagesEndpoint: '/messages',
port: 8080,
);
final transportResult = McpServer.createSseTransport(sseConfig);
final transport = transportResult.get();
server.connect(transport);
ストリーム可能な HTTP トランスポート
ストリーム可能な HTTP トランスポートは、2 つの応答モードをサポートします。
SSE ストリーミングモード(デフォルト)
応答をリアルタイムでストリーミングする場合:
final serverResult = await McpServer.createAndStart(
config: McpServer.simpleConfig(
name: 'My Server',
version: '1.0.0',
),
transportConfig: TransportConfig.streamableHttp(
host: 'localhost',
port: 8081,
endpoint: '/mcp',
isJsonResponseEnabled: false, // SSE streaming mode
),
);
JSON 応答モード
単一の JSON 応答を返す場合(よりシンプルですが、ストリーミングはできません):
final serverResult = await McpServer.createAndStart(
config: McpServer.simpleConfig(
name: 'My Server',
version: '1.0.0',
),
transportConfig: TransportConfig.streamableHttp(
host: 'localhost',
port: 8081,
endpoint: '/mcp',
isJsonResponseEnabled: true, // JSON response mode
),
);
重要な注意事項:
- 応答モードはサーバーの起動時に固定され、動的に変更することはできません
- サーバーのモードにかかわらず、クライアントは Accept ヘッダーに
application/jsonとtext/event-streamの両方を含める必要があります - SSE モードでは複数の応答をストリーミングできますが、JSON モードでは単一の応答を返します
ロギング
このパッケージには、レベルと書式をカスタマイズできる包括的なロギングユーティリティが含まれています。
// Create a named logger
final logger = Logger('mcp_server.example');
// Log at different levels
logger.severe('Connection failed: unable to connect to transport');
logger.warning('Resource cache is approaching capacity limit');
logger.info('Server started successfully on port 8080');
logger.fine('Session initialized with protocol version');
// Send structured logs to MCP client
server.sendLog(
McpLogLevel.info,
'Operation registered',
data: {
'operationId': 'op-123',
'sessionId': 'session-456',
'type': 'tool:calculator',
'timestamp': DateTime.now().toIso8601String(),
}
);
MCP プリミティブ
MCP プロトコルでは、サーバーが実装できる 3 つの主要なプリミティブを定義しています。
| プリミティブ | 制御主体 | 説明 | 使用例 |
|---|---|---|---|
| プロンプト | ユーザー制御 | ユーザーの選択によって呼び出される対話型テンプレート | スラッシュコマンド、メニュー項目 |
| リソース | アプリケーション制御 | クライアントアプリケーションが管理するコンテキストデータ | ファイルの内容、API 応答 |
| ツール | モデル制御 | LLM がアクションを実行するために公開される関数 | API 呼び出し、データ更新 |
その他の機能
進捗とキャンセル
進捗を必要とするクライアントは progressToken を送信し、サーバーは呼び出しが終了するまで、そのトークンに対して進捗を報告します(progressMinInterval によるレート制限あり)。
server.addToolWithProgress(
'longRunningOperation',
'Perform a long-running operation with progress reporting',
{'type': 'object'},
(args, {onProgress}) async {
for (var i = 0; i < 10; i++) {
// The client cancelled: stop (no response is sent for a cancelled call).
if (server.currentRequestCancelled) break;
onProgress?.call(i / 10, 'Step ${i + 1} of 10');
await Future<void>.delayed(const Duration(seconds: 1));
}
return CallToolResult(content: [TextContent(text: 'Done')]);
},
);
リソースの更新
// To the sessions subscribed to the URI (and, on 2026-07-28, the
// subscriptions/listen streams watching it)
server.notifyResourceUpdated('config://app');
キャッシュヒント(2026-07-28)
server/discover、4 つの list メソッド、および resources/read は、ttlMs
(Server(cacheTtl: ...))と cacheScope(publicLists)を持つため、クライアントは結果を再利用できます。cacheResource / getCachedResource / invalidateCache は別個のサーバー側キャッシュであり、アプリケーションはハンドラー内でこれを使用できます。
認可(HTTP)
server.enableAuthentication(
myTokenValidator, // TokenValidator: decides each token, reports its scopes and audience
requiredScopes: (method, params) =>
method == 'tools/call' && params?['name'] == 'delete' ? ['files:write'] : const [],
);
server.configureProtectedResource(
resource: 'https://api.example.com/mcp',
authorizationServers: ['https://auth.example.com'],
scopesSupported: ['files:read', 'files:write'],
);
すべての HTTP リクエストはディスパッチ前にチェックされます。トークンがない場合は 401、無効なトークンまたは別のリソース向けに発行されたトークンの場合は 401 invalid_token、スコープが不足している場合は 403 insufficient_scope が返されます。各応答には resource_metadata を含む
WWW-Authenticate が付与されます。ハンドラーは呼び出し元を
McpCaller.current として参照できます。同梱の OAuthTokenValidator(RFC 7662
イントロスペクション)は、イントロスペクションの結果に audience が含まれていないトークンを拒否します。
ヘルスモニタリング
final health = server.getHealth();
Logger('mcp_server.health').info('Running: ${health.isRunning}, '
'sessions: ${health.connectedSessions}, uptime: ${health.uptime}, '
'tools: ${health.registeredTools}, resources: ${health.registeredResources}');
server.incrementMetric('api_calls');
server.startTimer('operation_duration');
// ... perform operation
server.stopTimer('operation_duration');
エラー処理
ツールの失敗はプロトコルエラーではなく、結果として扱われます。例外をスローしたハンドラーには isError 結果が返され、inputSchema に適合しない引数にも同様に返されます(2025-11-25 以降。それ以前は -32602)。未知のツール、プロンプト、またはリソースは、ネゴシエートされたリビジョンで割り当てられたコードを持つプロトコルエラーです。ハンドラーからエラー結果そのものを返すこともできます。
try {
// Attempt an operation
final result = await performOperation();
return CallToolResult(content: [TextContent(text: result)]);
} catch (e) {
// Return an error result
return CallToolResult(
content: [TextContent(text: 'Operation failed: ${e.toString()}')],
isError: true,
);
}
例
完全なサンプルアプリケーションについては、サンプルディレクトリをご覧ください。
リソース
問題とフィードバック
問題、バグ、機能リクエストは、イシュートラッカーに報告してください。
ライセンス
このプロジェクトは MIT ライセンスの下でライセンスされています。詳細は LICENSE ファイルをご覧ください。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。