flutter_mcp_client_lib 1.0.3 copy "flutter_mcp_client_lib: ^1.0.3" to clipboard
flutter_mcp_client_lib: ^1.0.3 copied to clipboard

A Flutter implementation of the Model Context Protocol (MCP) for integrating with AI tools like Windsurf, Cursor, and Claude.

example/lib/main.dart

// Example of using the Flutter MCP Client Library
import 'dart:convert';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter_mcp_client_lib/flutter_mcp_client_lib.dart';
import 'package:logging/logging.dart';

void main() {
  // Set up logging
  Logger.root.level = Level.INFO;
  Logger.root.onRecord.listen((record) {
    debugPrint('${record.level.name}: ${record.time}: ${record.message}');
  });

  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter MCP Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        useMaterial3: true,
      ),
      home: const McpDemoPage(title: 'Flutter MCP Demo'),
    );
  }
}

class McpDemoPage extends StatefulWidget {
  const McpDemoPage({super.key, required this.title});

  final String title;

  @override
  State<McpDemoPage> createState() => _McpDemoPageState();
}

class _McpDemoPageState extends State<McpDemoPage> {
  final TextEditingController _serverUrlController = TextEditingController(
    text: 'ws://localhost:8080/mcp',
  );

  McpClient? _client;
  bool _isConnected = false;
  bool _isLoading = false;
  List<ResourceInfo> _resources = [];
  List<ToolInfo> _tools = [];
  List<PromptInfo> _prompts = [];
  String _statusMessage = '';
  Process? _serverProcess;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            TextField(
              controller: _serverUrlController,
              decoration: const InputDecoration(
                labelText: 'MCP Server URL',
                hintText: 'ws://localhost:8080/mcp',
              ),
            ),
            const SizedBox(height: 16),
            Row(
              children: [
                ElevatedButton(
                  onPressed: _isLoading || _isConnected ? null : _connect,
                  child: const Text('Connect'),
                ),
                const SizedBox(width: 16),
                ElevatedButton(
                  onPressed: _isLoading || !_isConnected ? null : _disconnect,
                  child: const Text('Disconnect'),
                ),
                const SizedBox(width: 16),
                ElevatedButton(
                  onPressed: _isLoading ? null : _startServer,
                  style: ElevatedButton.styleFrom(
                    backgroundColor: Colors.green,
                    foregroundColor: Colors.white,
                  ),
                  child: const Text('Start Server'),
                ),
              ],
            ),
            const SizedBox(height: 16),
            Text(
              _statusMessage,
              style: TextStyle(
                color: _isConnected ? Colors.green : Colors.red,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 16),
            if (_isConnected) ...[
              DefaultTabController(
                length: 3,
                child: Expanded(
                  child: Column(
                    children: [
                      TabBar(
                        labelColor: Theme.of(context).primaryColor,
                        tabs: const [
                          Tab(text: 'Resources'),
                          Tab(text: 'Tools'),
                          Tab(text: 'Prompts'),
                        ],
                      ),
                      Expanded(
                        child: TabBarView(
                          children: [
                            // Resources Tab
                            ListView.builder(
                              itemCount: _resources.length,
                              itemBuilder: (context, index) {
                                final resource = _resources[index];
                                return ListTile(
                                  title: Text(resource.name),
                                  subtitle: Text(resource.description ??
                                      'No description available'),
                                  trailing: IconButton(
                                    icon: const Icon(Icons.visibility),
                                    onPressed: () => _readResource(resource),
                                  ),
                                );
                              },
                            ),

                            // Tools Tab
                            ListView.builder(
                              itemCount: _tools.length,
                              itemBuilder: (context, index) {
                                final tool = _tools[index];
                                return ListTile(
                                  title: Text(tool.name),
                                  subtitle: Text(tool.description ??
                                      'No description available'),
                                );
                              },
                            ),

                            // Prompts Tab
                            ListView.builder(
                              itemCount: _prompts.length,
                              itemBuilder: (context, index) {
                                final prompt = _prompts[index];
                                return ListTile(
                                  title: Text(prompt.name),
                                  subtitle: Text(prompt.description ??
                                      'No description available'),
                                );
                              },
                            ),
                          ],
                        ),
                      ),
                    ],
                  ),
                ),
              ),
            ],
          ],
        ),
      ),
    );
  }

  Future<void> _connect() async {
    setState(() {
      _isLoading = true;
      _statusMessage = 'Connecting...';
    });

    try {
      final serverUrl = _serverUrlController.text;

      _client = McpClient(
        name: 'Flutter MCP Demo',
        version: '1.0.0',
        capabilities: const ClientCapabilities(
          sampling: SamplingCapabilityConfig(sample: true),
          resources: ResourceCapabilityConfig(list: true, read: true),
          tools: ToolCapabilityConfig(list: true, call: true),
          prompts: PromptCapabilityConfig(list: true, get: true),
        ),
      );

      final transport = McpWebSocketClientTransport(Uri.parse(serverUrl));
      await _client!.connect(transport);

      // Load resources
      final resources = await _client!.listResources();

      // Load tools
      final tools = await _client!.listTools();

      // Load prompts
      final prompts = await _client!.listPrompts();

      setState(() {
        _isConnected = true;
        _resources = resources;
        _tools = tools;
        _prompts = prompts;
        _statusMessage = 'Connected to $serverUrl';
      });
    } catch (e) {
      setState(() {
        _statusMessage = 'Connection failed: $e';
      });
      _client = null;

      // Show a dialog with instructions on how to start the server
      if (!mounted) return;

      showDialog(
        context: context,
        builder: (context) => AlertDialog(
          title: const Text('Connection Failed'),
          content: SingleChildScrollView(
            child: Column(
              mainAxisSize: MainAxisSize.min,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                const Text(
                  'Could not connect to the MCP server. Make sure the server is running.',
                  style: TextStyle(fontWeight: FontWeight.bold),
                ),
                const SizedBox(height: 16),
                const Text('To start the server, run:'),
                Container(
                  padding: const EdgeInsets.all(8),
                  color: Colors.grey[200],
                  child: const Text(
                    'dart run example/mcp_server_example.dart',
                    style: TextStyle(fontFamily: 'monospace'),
                  ),
                ),
                const SizedBox(height: 16),
                const Text(
                  'The server will start on localhost:8080/mcp by default.',
                ),
              ],
            ),
          ),
          actions: [
            TextButton(
              onPressed: () => Navigator.of(context).pop(),
              child: const Text('OK'),
            ),
          ],
        ),
      );
    } finally {
      setState(() {
        _isLoading = false;
      });
    }
  }

  Future<void> _disconnect() async {
    setState(() {
      _isLoading = true;
      _statusMessage = 'Disconnecting...';
    });

    try {
      await _client!.disconnect();

      setState(() {
        _isConnected = false;
        _resources = [];
        _tools = [];
        _prompts = [];
        _statusMessage = 'Disconnected';
      });

      _client = null;
    } catch (e) {
      setState(() {
        _statusMessage = 'Disconnection failed: $e';
      });
    } finally {
      setState(() {
        _isLoading = false;
      });
    }
  }

  Future<void> _readResource(ResourceInfo resource) async {
    setState(() {
      _isLoading = true;
      _statusMessage = 'Reading resource ${resource.name}...';
    });

    try {
      // For template URIs, we need to replace parameters with actual values
      // This is a simple example that replaces {name} with 'Flutter'
      final uri = resource.uriTemplate.replaceAll('{name}', 'Flutter');

      final contents = await _client!.readResource(uri);

      // Show the resource contents in a dialog
      if (!mounted) return;

      showDialog(
        context: context,
        builder: (context) => AlertDialog(
          title: Text('Resource: ${resource.name}'),
          content: SingleChildScrollView(
            child: Column(
              mainAxisSize: MainAxisSize.min,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text('URI: $uri'),
                const SizedBox(height: 8),
                const Text('Contents:'),
                ...contents.map((content) => Text('- ${content.text}')),
              ],
            ),
          ),
          actions: [
            TextButton(
              onPressed: () => Navigator.of(context).pop(),
              child: const Text('Close'),
            ),
          ],
        ),
      );
    } catch (e) {
      setState(() {
        _statusMessage = 'Failed to read resource: $e';
      });
    } finally {
      setState(() {
        _isLoading = false;
      });
    }
  }

  Future<void> _startServer() async {
    setState(() {
      _isLoading = true;
      _statusMessage = 'Starting server...';
    });

    try {
      // Start the server process
      _serverProcess = await Process.start(
        'dart',
        ['run', 'example/mcp_server_example.dart'],
        workingDirectory: '/Users/user/StudioProjects/mcp',
      );

      // Listen for server output
      _serverProcess!.stdout.transform(const Utf8Decoder()).listen((data) {
        debugPrint('Server output: $data');
        if (data.contains('Server listening')) {
          setState(() {
            _statusMessage = 'Server started on localhost:8080';
          });
        }
      });

      // Listen for server errors
      _serverProcess!.stderr.transform(const Utf8Decoder()).listen((data) {
        debugPrint('Server error: $data');
        setState(() {
          _statusMessage = 'Server error: $data';
        });
      });

      // Wait a bit for the server to start
      await Future.delayed(const Duration(seconds: 2));

      setState(() {
        _statusMessage = 'Server started. You can now connect.';
      });

      // Show a success dialog
      if (!mounted) return;

      showDialog(
        context: context,
        builder: (context) => AlertDialog(
          title: const Text('Server Started'),
          content: const Text(
            'The MCP server has been started on localhost:8080.\n\n'
            'You can now click "Connect" to connect to it.',
          ),
          actions: [
            TextButton(
              onPressed: () {
                Navigator.of(context).pop();
                _connect(); // Auto-connect
              },
              child: const Text('Connect Now'),
            ),
            TextButton(
              onPressed: () => Navigator.of(context).pop(),
              child: const Text('Close'),
            ),
          ],
        ),
      );
    } catch (e) {
      setState(() {
        _statusMessage = 'Failed to start server: $e';
      });

      // Show an error dialog
      if (!mounted) return;

      showDialog(
        context: context,
        builder: (context) => AlertDialog(
          title: const Text('Server Start Failed'),
          content: Text('Failed to start the MCP server: $e'),
          actions: [
            TextButton(
              onPressed: () => Navigator.of(context).pop(),
              child: const Text('OK'),
            ),
          ],
        ),
      );
    } finally {
      setState(() {
        _isLoading = false;
      });
    }
  }

  @override
  void dispose() {
    _serverUrlController.dispose();
    _serverProcess?.kill();
    super.dispose();
  }
}
1
likes
160
points
32
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A Flutter implementation of the Model Context Protocol (MCP) for integrating with AI tools like Windsurf, Cursor, and Claude.

Repository (GitHub)
View/report issues
Contributing

License

MIT (license)

Dependencies

cupertino_icons, equatable, flutter, http, json_annotation, logging, meta, stream_channel, uuid, web_socket_channel

More

Packages that depend on flutter_mcp_client_lib