mcpField
I
Integrating AI with Flutter: Creating AI Services with LlmServer and mcp_server

Integrating AI with Flutter: Creating AI Services with LlmServer and mcp_server

ローカル
github.com/MCP-Dev-StudioAI・エージェント078 週間前に更新

Integrating AI with Flutter: Creating AI Services with LlmServer and mcp_server について

Dart の `LlmServer` と `mcp_server` を組み合わせ、複数のクライアントアプリに AI サービスを提供する方法を解説する記事です。MCP ツールの登録、LLM プラグイン、自然言語の説明からのツール生成を扱います。

主な機能

  1. 01LLM 機能を MCP ツールとして公開
  2. 02カスタムツールプラグインの登録
  3. 03テキスト生成、ストリーミング、埋め込み、検索
  4. 04自然言語の説明からツールを生成
  5. 05サーバー動作のログ記録と監視

活用シーン

  1. 01複数のクライアントアプリに共通の AI サービスを提供
  2. 02MCP 経由で LLM 機能を利用可能にする
  3. 03自然言語の仕様からツールを作成

MCP 対応

`mcp_server` コンポーネントは MCP クライアントにツール、リソース、プロンプトテンプレートを公開します。`LlmServer` と接続し、LLM 機能を MCP ツールとして登録する方法を解説しています。

設定

ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。

インストール方法は未掲載です

このサーバーのパッケージやリモートエンドポイントはまだ取得できていません。プロジェクトの README の手順に従ってください。

リポジトリ

ツール

ツール一覧はまだ取得されていません

ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。

詳細

Flutter と AI の統合:LlmServer と mcp_server による AI サービスの作成

Flutter と AI の統合

はじめに

Flutter アプリケーションへの AI の統合に関するシリーズでは、これまで LlmClient と mcp_client を使ったクライアント側の統合について解説しました。今回は、LlmServer と mcp_server を使ったサーバー側の統合について詳しく見ていきます。この強力な組み合わせにより、複数のクライアントから利用できるサービスとして AI 機能を提供できます。

この記事では、Model Context Protocol(MCP)シリーズの第 5 回、そして mcp_llm に焦点を当てた第 3 回として、標準化された MCP プロトコルを活用する堅牢な AI サービスの構築方法を解説します。これにより、幅広いクライアントアプリケーションから AI 機能を利用できるようになります。

目次

  1. LlmServer と mcp_server について
  2. サーバー側統合アーキテクチャ
  3. 統合のセットアップ
  4. LLM 関数の MCP ツールとしての登録
  5. コア LLM プラグイン
  6. AI ベースのツール生成
  7. サーバーの監視と管理
  8. 次のステップ

LlmServer と mcp_server について

統合の詳細に入る前に、これら 2 つの主要コンポーネントの役割を確認しましょう。

LlmServer の役割

LlmServer は mcp_llm パッケージのコアコンポーネントで、AI 機能のサーバー側実装を担います。主な役割は次のとおりです。

  • Claude、GPT などの大規模言語モデル(LLM)との通信
  • AI を活用したツールの登録と管理
  • クエリの処理と応答の生成
  • プラグインと拡張機能の管理
  • パフォーマンスとリクエストの監視

AI 機能をクライアントアプリケーションに直接組み込むのではなく、サービスとして提供します。

mcp_server の役割

mcp_server はサーバー側で Model Context Protocol(MCP)を実装し、次の機能を提供します。

  • クライアントへのツールの登録と公開
  • リソース(データベース、API、ファイル)の管理
  • 標準化されたプロンプトテンプレートの提供
  • クライアント接続とセッションの管理
  • プロトコルに基づく通信
統合の価値

これらのコンポーネントを統合すると、次のことが可能になります。

  1. LLM の機能を標準化された MCP ツールとして公開する
  2. 複数のクライアント向けに AI 機能を一元管理する
  3. API キーと認証情報を 1 か所で安全に管理する
  4. 自然言語による説明に基づいて新しいツールを動的に生成する
  5. クライアントアプリケーションとは独立して AI 機能をスケーリングする

この統合により、クライアントインターフェースとバックエンドの AI 処理が明確に分離され、スケーラビリティ、セキュリティ、リソース管理が向上します。

サーバー側統合アーキテクチャ

この統合を実装するには、アーキテクチャを理解することが重要です。

統合モデル
[Client Apps] ← HTTP/SSE → [mcp_server] ↔ [LlmServer] ← HTTP → [LLM API]
                                ↓              ↓
                        [Tools/Resources]  [Plugin Management]
通信フロー

コンポーネント間の通信は、次の流れで行われます。

  1. mcp_server がクライアント(Flutter アプリ、Web クライアントなど)からリクエストを受け取ります
  2. リクエストに LLM ツールが関係する場合、LlmServer に転送します
  3. LlmServer がリクエストを処理し、LLM プロバイダーと通信します
  4. LLM の応答が LlmServer に返されます
  5. LlmServer が応答を処理し、結果を mcp_server に返します
  6. mcp_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 サーバーをセットアップしています。

  1. 環境設定:環境変数から API キー、ポート設定、認証トークンを読み込みます
  2. McpLlm とプロバイダーのセットアップ:メインインスタンスを作成し、LLM プロバイダーを登録します
  3. MCP サーバーの作成:必要な機能を備えた MCP サーバーをセットアップします
  4. ツールの登録:Echo や Calculator などの基本的なツールを登録します
  5. LlmServer の作成:LLM サーバーを作成し、MCP サーバーに接続します
  6. コアプラグインの登録:基本的な AI 機能をツールとして登録します
  7. AI ベースのツール生成:LLM を使用して、感情分析ツールを自動的に生成します
  8. トランスポートのセットアップ:クライアントとの通信に SSE トランスポートをセットアップします

LLM 関数を MCP ツールとして登録する

このインテグレーションの重要な要素の 1 つは、クライアントが利用できる MCP ツールとして LLM の機能を公開することです。提供されているコードでは、ツールを登録する 2 つの方法を示しています。

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 ベースのツール生成

コードで示されている最も革新的な機能の 1 つは、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 メソッドを使用して、次の処理を行います。

  1. 自然言語によるツールの説明を LLM に送信します
  2. LLM にツールの入力スキーマと実装を設計させます
  3. ツールの機能を実現する実行可能なコードを生成します
  4. 生成されたツールをサーバーに登録します

この強力な機能により、開発者は自然言語で説明するだけで複雑なツールを作成でき、開発を大幅に加速できます。

サーバーの監視と管理

サンプルコードには、サーバーを効果的に監視、管理するための機能がいくつか含まれています。

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 の基本的な統合を実装した後は、次のような高度なトピックを検討できます。

  1. 複数の LLM プロバイダーとの統合:さまざまな AI モデル(Claude、GPT など)を MCP エコシステムに統合します
  2. MCP プラグインシステムの開発:カスタムのツールやリソースをプラグインとして構築します
  3. 分散型 MCP 環境:複数の MCP クライアントとサーバーを使用して、複雑なシステムを構築します
  4. MCP ツールによる並列処理:MCP ツールを使った並列タスク実行を実装します
  5. MCP ベースの RAG システムの構築:ドキュメント検索を使ったナレッジベースのシステムを実装します

LlmServer と mcp_server を統合すると、幅広いクライアントアプリケーションから利用できる AI 搭載サービスを構築するための可能性が大きく広がります。

まとめ

この記事では、強力な AI サービスを作成するための LlmServer と mcp_server の統合について説明しました。このサーバーサイド統合には、次のような多くの利点があります。

  1. 標準化された API:標準化されたプロトコルを通じて AI 機能を公開できます
  2. リソースの最適化:負荷の高い AI ワークロードをサーバー上で処理し、クライアントのリソースを節約できます
  3. 一元管理:すべての AI 機能とツールを一元的に管理できます
  4. スケーラビリティ:クライアント側を変更することなく、新しい AI 機能やツールを簡単に追加できます
  5. 複数クライアントのサポート:単一のサーバーでさまざまなクライアントプラットフォームをサポートできます
  6. セキュリティの強化:機密性の高い API キーや認証情報をサーバー上に保持できます

LLM 自体を使って新しいツールを自動生成できることは、特に注目すべき点です。コードを書く代わりに自然言語で説明することで、複雑な AI 搭載ツールを作成できます。

これまでの記事で取り上げたクライアントサイド統合と、この記事で紹介したサーバーサイド統合を組み合わせることで、Model Context Protocol の力を活用した、完全でスケーラブルな AI エコシステムを構築できます。


リソース


開発者を支援する

この記事が役に立った場合は、Patreon を通じて、今後も無料コンテンツを開発できるようご支援をご検討ください。皆さまのご支援は大きな力になります!

Patreon で支援する

タグ:#Flutter #AI #MCP #LLM #Dart #OpenAI #ModelContextProtocol #AIIntegration #mcp_server #LlmServer

コメント

MCP は初めてですか?

プロトコルの概要から最初のサーバーの導入まで、よくある疑問にまとめて答えます。

AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。