Flutter와 AI의 만남: mcp_llm 소개

MCP Dev Studio·2025년 4월 30일

Dart와 Flutter에서 대규모 언어 모델(LLM)을 활용하는 가장 강력한 패키지, mcp_llm을 소개합니다. 이 글은 MCP 시리즈의 세 번째 포스트로, MCP 서버 구현하기와 MCP 클라이언트 구현하기에 이어 MCP 생태계의 핵심 시리즈를 완성합니다.

목차

Model Context Protocol과 LLM 통합

Model Context Protocol (MCP)은 AI 모델과 외부 환경 간의 통신을 표준화하는 프로토콜입니다. 이전 포스트에서 소개했던 mcp_clientmcp_server가 이 프로토콜의 클라이언트/서버 구현을 제공한다면, 오늘 소개할 mcp_llm은 대규모 언어 모델(LLM)을 MCP 생태계와 통합하는 패키지입니다.

MCP의 주요 목표는 AI 모델이 외부 환경의 도구, 리소스, 프롬프트 등에 접근할 수 있게 하는 것입니다. mcp_llm은 이 목표를 한 단계 더 발전시켜, 다양한 LLM 제공자(Claude, OpenAI, Together AI 등)를 통합하고, 이들의 기능을 Flutter 애플리케이션에서 활용할 수 있도록 합니다.

mcp_llm 패키지 소개

mcp_llm 패키지는 Dart와 Flutter 환경에서 다양한 LLM 제공자를 통합하고, MCP 프로토콜을 활용하여 확장 가능한 AI 솔루션을 구축할 수 있는 강력한 도구입니다.

주요 기능

  • 다중 LLM 제공자 지원: Claude, OpenAI, Together AI 등 다양한 LLM 제공자를 통합
  • 클라이언트/서버 측 구현: LlmClient와 LlmServer를 통한 양면 구현
  • MCP 통합: mcp_client 및 mcp_server와 원활한 연동
  • 플러그인 시스템: 도구, 리소스, 프롬프트 등을 위한 확장 가능한 플러그인 아키텍처
  • 병렬 처리: 여러 LLM에 동시에 쿼리하고 결과를 집계하는 기능
  • RAG 기능: Retrieval Augmented Generation을 위한 문서 저장소 및 벡터 검색 통합
  • 성능 모니터링: 요청 및 응답 시간, 성공률 등을 모니터링하는 도구

사용 사례

mcp_llm은 다음과 같은 다양한 사용 사례에 적합합니다:

  • 지능형 챗봇 및 가상 비서
  • 문서 분석 및 요약 시스템
  • 코드 생성 및 보조 도구
  • 지식 기반 질의응답 시스템
  • 멀티모달 콘텐츠 생성
  • 엔터프라이즈 데이터 통합 및 분석

핵심 아키텍처

mcp_llm의 핵심 아키텍처는 다음 주요 컴포넌트로 구성됩니다:

1. McpLlm

메인 클래스로, 전체 패키지의 진입점입니다. 프로바이더 등록, 클라이언트/서버 생성, 플러그인 관리 등 대부분의 주요 기능에 접근할 수 있습니다.

// McpLlm 인스턴스 생성
final mcpLlm = McpLlm();

// 프로바이더 등록
mcpLlm.registerProvider('claude', ClaudeProviderFactory());
mcpLlm.registerProvider('openai', OpenAiProviderFactory());

2. LlmClient

클라이언트 측 LLM 기능을 제공하며, mcp_client와 통합됩니다. AI 모델에 쿼리를 보내고, 응답을 받으며, 도구 호출을 처리합니다.

// LlmClient 생성
final client = await mcpLlm.createClient(
  providerName: 'claude',
  config: LlmConfiguration(
    apiKey: 'your-api-key',
    model: 'claude-3-haiku-20240307',
  ),
);

// AI와 채팅
final response = await client.chat("What's the weather like today?");

3. LlmServer

서버 측 LLM 기능을 제공하며, mcp_server와 통합됩니다. AI 기능을 서비스로 제공하고, 외부 클라이언트의 요청을 처리합니다.

// LlmServer 생성
final server = await mcpLlm.createServer(
  providerName: 'openai',
  config: LlmConfiguration(
    apiKey: 'your-api-key',
    model: 'gpt-4',
  ),
);

// 로컬 도구 등록
server.registerLocalTool(
  name: 'calculator',
  description: 'Performs calculations',
  inputSchema: {...},
  handler: calculatorHandler,
);

4. LLM 제공자

다양한 LLM API와 통신하는 구현체입니다. 각 제공자는 LlmInterface를 구현하며, 특정 LLM 서비스와의 통신을 담당합니다.

// 지원되는 제공자들
mcpLlm.registerProvider('claude', ClaudeProviderFactory());
mcpLlm.registerProvider('openai', OpenAiProviderFactory());
mcpLlm.registerProvider('together', TogetherProviderFactory());

5. 플러그인 시스템

확장 기능을 제공하는 플러그인 시스템으로, 도구, 프롬프트, 리소스 등을 등록하고 관리합니다.

// 플러그인 등록
await mcpLlm.registerPlugin(myToolPlugin);

6. RAG 컴포넌트

문서 저장, 임베딩 관리, 벡터 검색 등 Retrieval Augmented Generation 기능을 제공합니다.

// 검색 관리자 생성
final retrievalManager = mcpLlm.createRetrievalManager(
  providerName: 'openai',
  documentStore: documentStore,
);

시작하기: 환경 설정

설치

프로젝트의 pubspec.yaml 파일에 mcp_llm 종속성을 추가합니다:

dependencies:
  mcp_llm: ^0.2.2
  flutter:
    sdk: flutter

또는 명령줄에서 다음과 같이 실행합니다:

flutter pub add mcp_llm

API 키 설정

사용할 LLM 제공자에 따라 API 키를 준비해야 합니다. 다음은 주요 제공자의 API 키 설정 방법입니다:

// API 키 설정 방법
final claudeConfig = LlmConfiguration(
  apiKey: 'your-claude-api-key',
  model: 'claude-3-haiku-20240307',
);

final openAiConfig = LlmConfiguration(
  apiKey: 'your-openai-api-key',
  model: 'gpt-4',
);

final togetherConfig = LlmConfiguration(
  apiKey: 'your-together-api-key',
  model: 'llama-3',
);

API 키는 보안을 위해 환경 변수나 보안 저장소에서 불러오는 것이 좋습니다:

// 환경 변수에서 API 키 로드
final apiKey = Platform.environment['CLAUDE_API_KEY'] ??
  await secureStorage.read(key: 'claude_api_key');

McpLlm 인스턴스 및 기본 사용법

인스턴스 생성 및 프로바이더 등록

McpLlm 인스턴스를 생성하고 사용할 LLM 제공자를 등록합니다:

import 'package:mcp_llm/mcp_llm.dart';

void main() async {
  // McpLlm 인스턴스 생성
  final mcpLlm = McpLlm();

  // 로깅 설정 (선택 사항)
  final logger = Logger.getLogger('mcp_llm.main');
  logger.setLevel(LogLevel.debug);

  // 프로바이더 등록
  mcpLlm.registerProvider('claude', ClaudeProviderFactory());
  mcpLlm.registerProvider('openai', OpenAiProviderFactory());

  // 사용 가능한 프로바이더 목록 및 기능 확인
  final capabilities = mcpLlm.getProviderCapabilities();
  logger.info('Available providers: ${capabilities.keys.join(', ')}');

  // 자원 정리
  await mcpLlm.shutdown();
}

LlmClient 생성 및 사용

AI 모델과 통신하기 위한 클라이언트를 생성합니다:

// LlmClient 생성
final client = await mcpLlm.createClient(
  providerName: 'claude',
  config: LlmConfiguration(
    apiKey: 'your-claude-api-key',
    model: 'claude-3-haiku-20240307',
    options: {
      'temperature': 0.7,
      'max_tokens': 1500,
    },
  ),
  systemPrompt: 'You are a helpful assistant specialized in Flutter development.',
);

// 채팅
final response = await client.chat(
  "What's the best state management solution for Flutter?",
);

print('AI Response: ${response.text}');

스트리밍 응답 처리

실시간 응답을 스트리밍으로 받아 처리할 수 있습니다:

// 스트리밍 응답
final responseStream = client.streamChat(
  "Explain how Flutter's widget tree works",
);

// 응답 청크 처리
await for (final chunk in responseStream) {
  // 응답 청크 처리
  print('Chunk: ${chunk.textChunk}');

  // 완료 여부 확인
  if (chunk.isDone) {
    print('Response completed');
    break;
  }
}

LlmServer 생성 및 사용

AI 기능을 제공하는 서버를 생성합니다:

// LlmServer 생성
final server = await mcpLlm.createServer(
  providerName: 'openai',
  config: LlmConfiguration(
    apiKey: 'your-openai-api-key',
    model: 'gpt-4',
  ),
);

// 로컬 도구 등록
server.registerLocalTool(
  name: 'calculator',
  description: 'Performs basic arithmetic operations',
  inputSchema: {
    'type': 'object',
    'properties': {
      'operation': {
        'type': 'string',
        'enum': ['add', 'subtract', 'multiply', 'divide'],
      },
      'a': {'type': 'number'},
      'b': {'type': 'number'},
    },
    'required': ['operation', 'a', 'b'],
  },
  handler: (args) async {
    final operation = args['operation'] as String;
    final a = args['a'] as num;
    final b = args['b'] as num;

    switch (operation) {
      case 'add': return {'result': a + b};
      case 'subtract': return {'result': a - b};
      case 'multiply': return {'result': a * b};
      case 'divide': return {'result': a / b};
      default: throw ArgumentError('Unknown operation: $operation');
    }
  },
);

// 쿼리 처리
final result = await server.processQuery(
  query: "What's 25 + 17?",
  useLocalTools: true,
);

print('AI Response: ${result.text}');

간단한 AI 채팅 앱 만들기

이제 mcp_llm을 사용하여 간단한 AI 채팅 앱을 만들어 보겠습니다. 이 예제는 Flutter를 사용하여 기본적인 채팅 인터페이스를 구현하고, Claude API를 통해 AI 응답을 생성합니다.

1. 프로젝트 설정

새로운 Flutter 프로젝트를 생성하고 필요한 종속성을 추가합니다:

flutter create ai_chat_app
cd ai_chat_app
flutter pub add mcp_llm

2. 환경 변수 설정

API 키를 안전하게 관리하기 위해 .env 파일과 flutter_dotenv 패키지를 사용합니다:

flutter pub add flutter_dotenv

프로젝트 루트에 .env 파일을 생성합니다:

CLAUDE_API_KEY=your-claude-api-key

pubspec.yaml에 환경 파일을 에셋으로 추가합니다:

flutter:
  assets:
    - .env

3. 채팅 앱 구현

이제 lib/main.dart 파일을 다음과 같이 수정합니다:

import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:mcp_llm/mcp_llm.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await dotenv.load();
  runApp(const MyApp());
}

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

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'AI Chat App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: const ChatScreen(),
    );
  }
}

class ChatScreen extends StatefulWidget {
  const ChatScreen({super.key});

  
  _ChatScreenState createState() => _ChatScreenState();
}

class _ChatScreenState extends State<ChatScreen> {
  final TextEditingController _textController = TextEditingController();
  final List<ChatMessage> _messages = [];
  late McpLlm _mcpLlm;
  LlmClient? _client;
  bool _isTyping = false;

  
  void initState() {
    super.initState();
    _initializeLlm();
  }

  Future<void> _initializeLlm() async {
    _mcpLlm = McpLlm();
    _mcpLlm.registerProvider('claude', ClaudeProviderFactory());

    final apiKey = dotenv.env['CLAUDE_API_KEY'] ?? '';
    if (apiKey.isEmpty) {
      _showError('API key not found. Please check your .env file.');
      return;
    }

    try {
      _client = await _mcpLlm.createClient(
        providerName: 'claude',
        config: LlmConfiguration(
          apiKey: apiKey,
          model: 'claude-3-haiku-20240307',
          options: {
            'temperature': 0.7,
            'max_tokens': 1500,
          },
        ),
        systemPrompt: 'You are a helpful assistant. Be concise and friendly.',
      );
    } catch (e) {
      _showError('Failed to initialize AI: $e');
    }
  }

  void _showError(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(message)),
    );
  }

  void _handleSubmitted(String text) async {
    if (text.trim().isEmpty) return;

    _textController.clear();

    setState(() {
      _messages.add(ChatMessage(
        text: text,
        isUser: true,
      ));
      _isTyping = true;
    });

    if (_client == null) {
      _showError('AI client not initialized');
      setState(() {
        _isTyping = false;
      });
      return;
    }

    try {
      final response = await _client!.chat(text);

      setState(() {
        _messages.add(ChatMessage(
          text: response.text,
          isUser: false,
        ));
        _isTyping = false;
      });
    } catch (e) {
      _showError('Error getting AI response: $e');
      setState(() {
        _isTyping = false;
      });
    }
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('AI Chat App'),
      ),
      body: Column(
        children: [
          Flexible(
            child: ListView.builder(
              padding: const EdgeInsets.all(8.0),
              reverse: true,
              itemCount: _messages.length,
              itemBuilder: (_, index) => _messages[_messages.length - 1 - index],
            ),
          ),
          if (_isTyping)
            const Padding(
              padding: EdgeInsets.all(8.0),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.start,
                children: [
                  CircularProgressIndicator(),
                  SizedBox(width: 8),
                  Text('AI is typing...'),
                ],
              ),
            ),
          const Divider(height: 1.0),
          Container(
            decoration: BoxDecoration(
              color: Theme.of(context).cardColor,
            ),
            child: _buildTextComposer(),
          ),
        ],
      ),
    );
  }

  Widget _buildTextComposer() {
    return IconTheme(
      data: IconThemeData(color: Theme.of(context).colorScheme.primary),
      child: Container(
        margin: const EdgeInsets.symmetric(horizontal: 8.0),
        child: Row(
          children: [
            Flexible(
              child: TextField(
                controller: _textController,
                onSubmitted: _handleSubmitted,
                decoration: const InputDecoration.collapsed(
                  hintText: 'Send a message',
                ),
              ),
            ),
            Container(
              margin: const EdgeInsets.symmetric(horizontal: 4.0),
              child: IconButton(
                icon: const Icon(Icons.send),
                onPressed: () => _handleSubmitted(_textController.text),
              ),
            ),
          ],
        ),
      ),
    );
  }

  
  void dispose() {
    _mcpLlm.shutdown();
    _textController.dispose();
    super.dispose();
  }
}

class ChatMessage extends StatelessWidget {
  final String text;
  final bool isUser;

  const ChatMessage({
    super.key,
    required this.text,
    required this.isUser,
  });

  
  Widget build(BuildContext context) {
    return Container(
      margin: const EdgeInsets.symmetric(vertical: 10.0),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Container(
            margin: const EdgeInsets.only(right: 16.0),
            child: CircleAvatar(
              child: Text(isUser ? 'You' : 'AI'),
            ),
          ),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  isUser ? 'You' : 'AI Assistant',
                  style: Theme.of(context).textTheme.titleMedium,
                ),
                Container(
                  margin: const EdgeInsets.only(top: 5.0),
                  child: Text(text),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

4. 애플리케이션 실행

이제 애플리케이션을 실행하고 AI와 채팅해 보세요:

flutter run

이 간단한 채팅 앱은 다음과 같은 기능을 제공합니다:

  • Claude AI와 대화할 수 있는 채팅 인터페이스
  • 메시지 목록 표시 및 스크롤
  • AI가 응답 중일 때 로딩 표시
  • 오류 처리 및 알림

다음 단계

이 포스트에서는 mcp_llm 패키지의 기본 개념과 사용법을 소개했습니다. 다음 단계에서는 더 고급 기능을 탐색할 수 있습니다:

  1. LlmClient 심층 탐구: 스트리밍 응답, 도구 통합, mcp_client 연동 등
  2. LlmServer 구현: AI API 서버 구축, 도구 등록, mcp_server 연동 등
  3. 다양한 LLM 제공자 활용: Claude, OpenAI, Together AI 등 다양한 제공자 설정 및 전환
  4. 플러그인 시스템 활용: 도구 플러그인, 프롬프트 플러그인 등 개발 및 등록
  5. 멀티 클라이언트/서버 관리: 여러 LLM 클라이언트/서버 관리 및 라우팅
  6. 병렬 처리 및 성능 최적화: 병렬 쿼리, 성능 모니터링 등
  7. RAG 시스템 구현: 문서 검색 및 AI 응답 생성 통합

결론

mcp_llm은 Dart와 Flutter에서 대규모 언어 모델을 통합하고 활용할 수 있는 강력한 패키지입니다. MCP 생태계와의 통합을 통해 다양한 AI 모델과 외부 도구, 리소스를 연결하여 더 풍부한 AI 애플리케이션을 구축할 수 있습니다.

이 패키지는 mcp_clientmcp_server를 보완하며, 세 패키지를 함께 사용하면 완전한 MCP 생태계를 구축할 수 있습니다. 다음 포스트에서는 LlmClient의 고급 기능과 mcp_client 연동에 대해 더 자세히 살펴보겠습니다.

앞으로의 시리즈를 기대해 주세요!


참고 자료


개발자 후원하기

이 글이 도움이 되셨다면, 패트론을 통해 개발 활동을 지원해 주세요. 여러분의 후원은 더 많은 무료 콘텐츠를 만드는 데 큰 힘이 됩니다.

태그: #Flutter #AI #MCP #LLM #Dart #Claude #OpenAI #ModelContextProtocol #AIIntegration

0개의 댓글