flutter_mcp_server 0.1.2
flutter_mcp_server: ^0.1.2 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.
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]);
},
),
),
),
],
),
),
),
],
),
),
);
}
}