关于 Integrating AI with Flutter: Creating AI Services with LlmServer and mcp_server
这篇文章介绍如何组合 Dart 的 `LlmServer` 与 `mcp_server`,为多个客户端应用提供 AI 服务。内容涵盖 MCP 工具注册、LLM 插件,以及根据自然语言描述生成工具。
主要功能
- 01将 LLM 能力作为 MCP 工具提供
- 02注册自定义工具插件
- 03文本生成、流式响应、嵌入与检索
- 04根据自然语言描述生成工具
- 05记录并监控服务运行
适用场景
- 01为多个客户端应用提供共享 AI 服务
- 02通过 MCP 提供 LLM 功能
- 03根据自然语言规范创建工具
MCP 相关
`mcp_server` 组件向 MCP 客户端提供工具、资源和提示词模板。文章介绍将其与 `LlmServer` 连接,并注册 LLM 能力作为 MCP 工具。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
我们还没有获取到这个服务的安装包或远程地址,请按项目 README 中的说明安装。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
将 AI 集成到 Flutter:使用 LlmServer 和 mcp_server 创建 AI 服务
简介
在我们持续介绍将 AI 集成到 Flutter 应用的系列文章中,我们此前探讨了使用 LlmClient 和 mcp_client 进行客户端集成。现在,我们将深入了解如何使用 LlmServer 和 mcp_server 进行服务端集成。这种强大的组合让你能够将 AI 能力作为服务提供,供多个客户端使用。
本文是 Model Context Protocol (MCP) 系列的第五篇,也是聚焦于 mcp_llm 的第三篇,将介绍如何构建稳健的 AI 服务,利用标准化的 MCP 协议,让各种客户端应用都能使用 AI 能力。
目录
了解 LlmServer 和 mcp_server
在深入了解集成之前,我们先明确这两个关键组件的作用:
LlmServer 的作用
LlmServer 是 mcp_llm 包中的核心组件,负责在服务端实现 AI 能力。它的职责包括:
- 与 Claude、GPT 等大模型通信
- 注册和管理 AI 驱动的工具
- 处理查询并生成响应
- 管理插件和扩展
- 监控性能和请求
它本质上是将 AI 能力作为服务提供,而不是将其直接嵌入客户端应用。
mcp_server 的作用
mcp_server 在服务端实现 Model Context Protocol (MCP),支持:
- 向客户端注册并提供工具
- 管理资源(数据库、API、文件)
- 提供标准化的提示词模板
- 管理客户端连接和会话
- 基于协议进行通信
集成的价值
集成这两个组件后,你可以:
- 将大模型能力作为标准化 MCP 工具提供
- 为多个客户端集中管理 AI 功能
- 在一个位置安全地管理 API 密钥和凭据
- 根据自然语言描述动态生成新工具
- 独立于客户端应用扩展 AI 能力
这种集成通过明确区分客户端界面与后端 AI 处理,改变了 AI 开发方式,使扩展、安全和资源管理更加出色。
服务端集成架构
要实现集成,理解其架构至关重要:
集成模型
[Client Apps] ← HTTP/SSE → [mcp_server] ↔ [LlmServer] ← HTTP → [LLM API]
↓ ↓
[Tools/Resources] [Plugin Management]
通信流程
组件之间的通信遵循以下流程:
mcp_server接收来自客户端(Flutter 应用、Web 客户端等)的请求- 如果请求涉及 LLM 工具,则将其转发给
LlmServer LlmServer处理请求并与 LLM 提供商通信- LLM 的响应返回给
LlmServer LlmServer处理响应,并将结果返回给mcp_servermcp_server将最终结果发送回客户端
这种分层架构明确划分了职责,并支持高效扩展。
设置集成
下面介绍如何使用 LlmServer 和 mcp_server 设置集成式 AI 服务:
import 'dart:async';
import 'dart:io';
import 'package:mcp_llm/mcp_llm.dart';
import 'package:mcp_server/mcp_server.dart' as mcp;
import 'package:dotenv/dotenv.dart';
Future<void> main() async {
// Logger setup - utility for structured server logs
final logger = Logger.getLogger('mcp_llm.server');
try {
// Load environment variables - server configuration settings
final env = DotEnv()..load();
final apiKey = env['OPENAI_API_KEY'] ?? '';
final serverPort = int.tryParse(env['MCP_SERVER_PORT'] ?? '8999') ?? 8999;
final authToken = env['MCP_AUTH_TOKEN'] ?? 'test_token';
final logLevelStr = env['LOG_LEVEL'] ?? 'info';
// Set log level - various levels of detail for logging
final LogLevel logLevel;
switch (logLevelStr.toLowerCase()) {
case 'trace': logLevel = LogLevel.trace; break;
case 'debug': logLevel = LogLevel.debug; break;
case 'info': logLevel = LogLevel.info; break;
case 'warning': logLevel = LogLevel.warning; break;
case 'error': logLevel = LogLevel.error; break;
default: logLevel = LogLevel.info;
}
logger.setLevel(logLevel);
// Verify API key - required setting for server startup
if (apiKey.isEmpty) {
logger.error('OPENAI_API_KEY is not set.');
exit(1);
}
logger.info('Starting AI service server...');
// Create McpLlm instance - entry point for all LLM functionality
final mcpLlm = McpLlm();
// Register LLM provider - support for various AI models
mcpLlm.registerProvider('openai', OpenAiProviderFactory());
logger.debug('OpenAI provider registered.');
// Create MCP server - Model Context Protocol server configuration
final mcpServer = mcp.McpServer.createServer(
name: 'ai_service',
version: '1.0.0',
capabilities: mcp.ServerCapabilities(
tools: true,
toolsListChanged: true,
resources: true,
resourcesListChanged: true,
prompts: true,
promptsListChanged: true,
sampling: true,
),
);
// Register custom tools - add specific functionality tools
final pluginManager = PluginManager();
await pluginManager.registerPlugin(EchoToolPlugin());
await pluginManager.registerPlugin(CalculatorToolPlugin());
// Create LlmServer - server for providing AI functionality as a service
final llmServer = await mcpLlm.createServer(
providerName: 'openai', // LLM provider to use
config: LlmConfiguration(
apiKey: apiKey,
model: 'gpt-4o', // Model to use
options: {
'temperature': 0.3, // Control response randomness/creativity (0-1)
'max_tokens': 2000, // Maximum output token limit
},
),
storageManager: MemoryStorage(),
pluginManager: pluginManager,
mcpServer: mcpServer, // MCP server integration
);
logger.info('LlmServer has been created.');
// Register core LLM plugins - enable basic AI functionality
await llmServer.registerCoreLlmPlugins(
registerCompletionTool: true, // Text generation tool
registerStreamingTool: true, // Streaming response tool
registerEmbeddingTool: true, // Embedding generation tool
registerRetrievalTools: true, // Retrieval-related tools
registerWithServer: true, // Auto-register with MCP server
);
logger.info('Core LLM plugins registered.');
// Add auto-generated tool - LLM designs/implements tool automatically
// Executing asynchronously to avoid delaying server startup
_generateAutomaticTool(llmServer, logger);
// Create SSE transport - communication channel between server and clients
final transport = mcp.McpServer.createSseTransport(
endpoint: '/sse',
messagesEndpoint: '/message',
port: serverPort,
authToken: authToken, // Token for client authentication
);
// Connect MCP server with transport
mcpServer.connect(transport);
logger.info('MCP server connected to transport.');
// Output server information
logger.info('AI service running on port $serverPort.');
logger.info('Server URL: http://localhost:$serverPort/sse');
// Verify auth token setting
if (authToken.isNotEmpty) {
logger.debug('Auth token set: $authToken');
}
// Output available tools
final tools = mcpServer.getTools();
logger.info('');
logger.info('Available tools:');
for (final tool in tools) {
logger.info('- ${tool.name}: ${tool.description}');
}
// Handle server shutdown - for Ctrl+C or other termination
ProcessSignal.sigint.watch().listen((_) async {
logger.info('Shutting down server...');
await mcpLlm.shutdown();
exit(0);
});
// Maintain event loop - keep server running
try {
logger.info('Server ready to process requests. Press Ctrl+C to shut down.');
await Future.delayed(Duration(days: 365));
} catch (e) {
logger.error('Error during server execution: $e');
}
} catch (e, stack) {
logger.error('Error during server startup: $e');
logger.debug('Stack trace: $stack');
exit(1);
}
}
// AI-based tool generation function - LLM designs and implements tools automatically
void _generateAutomaticTool(LlmServer server, Logger logger) async {
try {
logger.info('Generating sentiment analysis tool...');
// Tool description - LLM uses this description to generate the tool
final toolDescription = """
Create a tool that analyzes the sentiment of text. This tool should provide:
1. Sentiment analysis of input text (positive, negative, neutral)
2. Sentiment score (-1.0 to 1.0, where -1 is most negative, 1 is most positive)
3. Extraction of key sentiment words
4. Analysis confidence (0.0 to 1.0)
It should support text in multiple languages and provide results in either text or JSON format.
The tool name must be sentiment_analyzer.
""";
// Attempt LLM-based tool generation
try {
logger.info('Attempting LLM-based tool generation...');
// Generate tool
final success = await server.generateAndRegisterTool(
toolDescription,
registerWithServer: true, // Auto-register with server
);
logger.info('Automatic tool generation result: ${success ? "Success" : "Failed"}');
if (!success) {
logger.info('Automatic generation failed...');
}
} catch (e) {
logger.error('Error during tool generation: $e');
// Handle error if direct registration is needed
logger.info('Error...');
}
} catch (e) {
logger.error('Error during sentiment analyzer tool setup: $e');
}
}
class EchoToolPlugin extends BaseToolPlugin {
EchoToolPlugin() : super(
name: 'echo',
version: '1.0.0',
description: 'Echoes back the input message with optional transformation',
inputSchema: {
'type': 'object',
'properties': {
'message': {
'type': 'string',
'description': 'Message to echo back'
},
'uppercase': {
'type': 'boolean',
'description': 'Whether to convert to uppercase',
'default': false
}
},
'required': ['message']
},
);
@override
Future<LlmCallToolResult> onExecute(Map<String, dynamic> arguments) async {
final message = arguments['message'] as String;
final uppercase = arguments['uppercase'] as bool? ?? false;
final result = uppercase ? message.toUpperCase() : message;
Logger.getLogger('LlmServerDemo').debug(message);
return LlmCallToolResult([
LlmTextContent(text: result),
]);
}
}
class CalculatorToolPlugin extends BaseToolPlugin {
CalculatorToolPlugin() : super(
name: 'calculator',
version: '1.0.0',
description: 'Performs basic arithmetic operations',
inputSchema: {
'type': 'object',
'properties': {
'operation': {
'type': 'string',
'description': 'The operation to perform (add, subtract, multiply, divide)',
'enum': ['add', 'subtract', 'multiply', 'divide']
},
'a': {
'type': 'number',
'description': 'First number'
},
'b': {
'type': 'number',
'description': 'Second number'
}
},
'required': ['operation', 'a', 'b']
},
);
@override
Future<LlmCallToolResult> onExecute(Map<String, dynamic> arguments) async {
final operation = arguments['operation'] as String;
final a = (arguments['a'] as num).toDouble();
final b = (arguments['b'] as num).toDouble();
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) {
throw Exception('Division by zero');
}
result = a / b;
break;
default:
throw Exception('Unknown operation: $operation');
}
Logger.getLogger('LlmServerDemo').debug('$result');
return LlmCallToolResult([
LlmTextContent(text: result.toString()),
]);
}
}
上述代码使用以下几个关键组件设置了集成式 AI 服务:
- 环境配置:从环境变量中加载 API 密钥、端口设置和身份验证令牌
- McpLlm 和提供商设置:创建主实例并注册 LLM 提供商
- MCP 服务创建:设置具备所需功能的 MCP 服务
- 工具注册:注册 Echo 和 Calculator 等基础工具
- LlmServer 创建:创建 LLM 服务并将其连接到 MCP 服务
- 核心插件注册:将基础 AI 功能注册为工具
- 基于 AI 的工具生成:使用 LLM 自动生成情感分析工具
- 传输设置:设置 SSE 传输以与客户端通信
将 LLM 函数注册为 MCP 工具
此集成的一个关键方面是将 LLM 功能作为 MCP 工具公开,供客户端访问。提供的代码展示了两种工具注册方式:
1. 插件注册
示例代码展示了如何通过扩展 BaseToolPlugin 类创建工具插件,并通过 PluginManager 注册:
// Register custom tools through PluginManager
final pluginManager = PluginManager();
await pluginManager.registerPlugin(EchoToolPlugin());
await pluginManager.registerPlugin(CalculatorToolPlugin());
EchoToolPlugin 和 CalculatorToolPlugin 类展示了如何通过以下方式实现自定义工具:
- 定义工具名称、版本和描述
- 将输入架构指定为 JSON Schema
- 实现
onExecute方法以处理工具调用
2. 核心 LLM 插件
registerCoreLlmPlugins 方法会自动将一组核心 LLM 功能注册为工具:
// Register core LLM plugins
await llmServer.registerCoreLlmPlugins(
registerCompletionTool: true, // Text generation
registerStreamingTool: true, // Streaming responses
registerEmbeddingTool: true, // Embedding generation
registerRetrievalTools: true, // Retrieval-related tools
registerWithServer: true, // Auto-register with MCP server
);
这些核心插件将基本的 LLM 功能(文本生成、嵌入等)作为标准化工具公开,使客户端能够通过 MCP 协议使用这些功能。
核心 LLM 插件
通过 registerCoreLlmPlugins 注册的核心 LLM 插件提供以下功能:
- CompletionTool:根据提示词生成文本
- StreamingTool:提供流式响应,实现实时更新
- EmbeddingTool:为文本生成向量嵌入
- RetrievalTools:提供检索增强生成能力
这些工具会自动注册到 MCP 服务,使所有已连接的客户端都能使用它们。
基于 AI 的工具生成
代码展示的一项创新功能是直接使用 LLM 自动生成新工具。该功能在 _generateAutomaticTool 函数中实现:
void _generateAutomaticTool(LlmServer server, Logger logger) async {
try {
logger.info('Generating sentiment analysis tool...');
// Tool description - LLM uses this description to generate the tool
final toolDescription = """
Create a tool that analyzes the sentiment of text. This tool should provide:
1. Sentiment analysis of input text (positive, negative, neutral)
2. Sentiment score (-1.0 to 1.0, where -1 is most negative, 1 is most positive)
3. Extraction of key sentiment words
4. Analysis confidence (0.0 to 1.0)
It should support text in multiple languages and provide results in either text or JSON format.
The tool name must be sentiment_analyzer.
""";
// Attempt LLM-based tool generation
try {
logger.info('Attempting LLM-based tool generation...');
// Generate tool
final success = await server.generateAndRegisterTool(
toolDescription,
registerWithServer: true, // Auto-register with server
);
logger.info('Automatic tool generation result: ${success ? "Success" : "Failed"}');
if (!success) {
logger.info('Automatic generation failed...');
}
} catch (e) {
logger.error('Error during tool generation: $e');
logger.info('Error...');
}
} catch (e) {
logger.error('Error during sentiment analyzer tool setup: $e');
}
}
此函数使用 generateAndRegisterTool 方法来:
- 向 LLM 发送自然语言工具描述
- 让 LLM 设计工具的输入架构和实现
- 为工具功能生成可执行代码
- 将生成的工具注册到服务
这项强大的功能让开发者只需用自然语言描述,就能创建复杂工具,大幅加快开发速度。
服务监控与管理
示例代码包含多项功能,可有效监控和管理服务:
1. 日志系统
代码设置了一个包含不同级别的全面日志系统:
final logger = Logger.getLogger('mcp_llm.server');
// Set log level based on environment
final LogLevel logLevel;
switch (logLevelStr.toLowerCase()) {
case 'trace': logLevel = LogLevel.trace; break;
case 'debug': logLevel = LogLevel.debug; break;
case 'info': logLevel = LogLevel.info; break;
case 'warning': logLevel = LogLevel.warning; break;
case 'error': logLevel = LogLevel.error; break;
default: logLevel = LogLevel.info;
}
logger.setLevel(logLevel);
此日志系统有助于跟踪服务操作、错误和重要事件。
2. 工具清单
代码会在注册后列出所有可用工具:
// Output available tools
final tools = mcpServer.getTools();
logger.info('');
logger.info('Available tools:');
for (final tool in tools) {
logger.info('- ${tool.name}: ${tool.description}');
}
这可以清晰地展示客户端可使用的功能。
3. 平稳关闭
代码实现了平稳关闭机制,以确保妥善清理资源:
// Handle server shutdown - for Ctrl+C or other termination
ProcessSignal.sigint.watch().listen((_) async {
logger.info('Shutting down server...');
await mcpLlm.shutdown();
exit(0);
});
这可确保服务终止时妥善释放所有资源。
后续步骤
在实现 LlmServer 与 mcp_server 的基本集成后,你还可以探索以下几个进阶主题:
- 集成多个大模型提供商:将不同的 AI 模型(Claude、GPT 等)接入 MCP 生态系统
- 开发 MCP 插件系统:将自定义工具和资源构建为插件
- 分布式 MCP 环境:使用多个 MCP 客户端和服务创建复杂系统
- 使用 MCP 工具进行并行处理:使用 MCP 工具实现并行任务执行
- 构建基于 MCP 的 RAG 系统:通过文档检索实现基于知识的系统
将 LlmServer 与 mcp_server 集成,可以为构建 AI 驱动的服务开辟众多可能性,让各种客户端应用都能使用这些服务。
结论
本文探讨了如何集成 LlmServer 与 mcp_server,以创建强大的 AI 服务。这种服务端集成有许多优点:
- 标准化 API:通过标准化协议提供 AI 能力
- 资源优化:在服务端处理繁重的 AI 工作负载,节省客户端资源
- 集中管理:在一个中心位置管理所有 AI 功能和工具
- 可扩展性:无需修改客户端,即可轻松添加新的 AI 功能或工具
- 支持多个客户端:通过单个服务支持各种客户端平台
- 增强安全性:将敏感的 API 密钥和凭证保留在服务端
尤其令人振奋的是,能够使用大模型本身自动生成新工具。这让开发者可以通过自然语言描述,而非编写代码,来创建复杂的 AI 驱动工具。
将前文讨论的客户端集成与本文介绍的服务端集成相结合,你就能构建完整、可扩展的 AI 生态系统,充分发挥 Model Context Protocol 的强大能力。
资源
支持开发者
如果你觉得本文有所帮助,请考虑通过 Patreon 支持更多免费内容的创作。你的支持意义重大!
标签:#Flutter #AI #MCP #LLM #Dart #OpenAI #ModelContextProtocol #AIIntegration #mcp_server #LlmServer
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。
