flutter_mcp_server 0.1.0
flutter_mcp_server: ^0.1.0 copied to clipboard
Flutter plugin for implementing Model Context Protocol (MCP) servers. Provides tools to build MCP servers that can expose data, functionality, and interaction patterns to LLM applications.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:flutter_mcp_server/flutter_mcp_server.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _platformVersion = 'Unknown';
bool _serverRunning = false;
String _statusMessage = 'Server not running';
List<String> _logs = [];
McpServer? _server;
@override
void initState() {
super.initState();
initPlatformState();
}
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initPlatformState() async {
String platformVersion;
// Platform messages may fail, so we use a try/catch PlatformException.
// We also handle the message potentially returning null.
try {
platformVersion =
await FlutterMcpServer.getPlatformVersion() ?? 'Unknown platform version';
} on PlatformException {
platformVersion = 'Failed to get platform version.';
}
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
setState(() {
_platformVersion = platformVersion;
});
}
void _startServer() {
// Create MCP server
_server = FlutterMcpServer.createServer(
name: 'Example MCP Server',
version: '1.0.0',
capabilities: ServerCapabilities(
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'],
},
'a': {'type': 'number'},
'b': {'type': 'number'},
},
'required': ['operation', 'a', 'b'],
},
handler: (arguments) async {
final operation = arguments['operation'] as String;
final a = arguments['a'] as num;
final b = arguments['b'] as num;
_addLog('Calculator tool called: $operation $a $b');
double result;
switch (operation) {
case 'add':
result = (a + b).toDouble();
break;
case 'subtract':
result = (a - b).toDouble();
break;
case 'multiply':
result = (a * b).toDouble();
break;
case 'divide':
if (b == 0) {
return CallToolResult(
content: [TextContent(text: 'Division by zero error')],
isError: true,
);
}
result = (a / b).toDouble();
break;
default:
return CallToolResult(
content: [TextContent(text: 'Unknown operation: $operation')],
isError: true,
);
}
return CallToolResult(
content: [TextContent(text: result.toString())],
);
},
);
// Add a time resource
_server!.addResource(
uri: 'time://current',
name: 'Current Time',
description: 'Get the current date and time',
mimeType: 'text/plain',
handler: (uri, params) async {
_addLog('Time resource accessed: $uri');
final now = DateTime.now().toString();
return ReadResourceResult(
contents: [
ResourceContent(
resource: Resource(
uri: uri.toString(),
name: 'Current Time',
mimeType: 'text/plain',
),
),
],
);
},
);
// Add a greeting prompt
_server!.addPrompt(
name: 'greeting',
description: 'Generate a customized greeting',
arguments: [
PromptArgument(
name: 'name',
description: 'Name to greet',
required: true,
),
PromptArgument(
name: 'language',
description: 'Language for greeting',
required: false,
),
],
handler: (arguments) async {
final name = arguments?['name'] as String? ?? 'User';
final language = arguments?['language'] as String? ?? 'English';
_addLog('Greeting prompt called: $name in $language');
String greeting;
switch (language.toLowerCase()) {
case 'spanish':
greeting = '¡Hola, $name!';
break;
case 'french':
greeting = 'Bonjour, $name!';
break;
case 'german':
greeting = 'Guten Tag, $name!';
break;
default:
greeting = 'Hello, $name!';
}
return GetPromptResult(
description: 'A greeting in $language',
messages: [
Message(
role: MessageRole.user,
content: TextContent(text: greeting),
),
],
);
},
);
// Connect to transport
final transport = FlutterMcpServer.createStdioTransport();
_server!.connect(transport).then((_) {
_addLog('Server connected to stdio transport');
setState(() {
_serverRunning = true;
_statusMessage = 'Server running on stdio transport';
});
}).catchError((e) {
_addLog('Error connecting server: $e');
setState(() {
_statusMessage = 'Error: $e';
});
});
}
void _stopServer() {
if (_server != null) {
_server!.disconnect().then((_) {
_addLog('Server disconnected');
setState(() {
_serverRunning = false;
_statusMessage = 'Server stopped';
_server = null;
});
}).catchError((e) {
_addLog('Error disconnecting server: $e');
});
}
}
void _addLog(String message) {
setState(() {
_logs.add('${DateTime.now().toString()}: $message');
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Flutter MCP Server Example'),
),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: Text('Running on: $_platformVersion'),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: _serverRunning ? null : _startServer,
child: const Text('Start Server'),
),
const SizedBox(width: 16),
ElevatedButton(
onPressed: _serverRunning ? _stopServer : null,
child: const Text('Stop Server'),
),
],
),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
_statusMessage,
style: TextStyle(
fontWeight: FontWeight.bold,
color: _serverRunning ? Colors.green : Colors.red,
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Server Logs:',
style: TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Expanded(
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(8),
),
padding: const EdgeInsets.all(8),
child: ListView.builder(
itemCount: _logs.length,
itemBuilder: (context, index) {
return Text(_logs[index]);
},
),
),
),
],
),
),
),
],
),
),
);
}
}