Labsco
kyopark2014 logo

MCP RAG Server

โ˜… 40

from kyopark2014

A lightweight Python server for Retrieval-Augmented Generation (RAG) using AWS Lambda. It retrieves knowledge from external data sources like arXiv and PubMed.

๐Ÿ”ฅ๐Ÿ”ฅ๐Ÿ”ฅโœ“ VerifiedFreeAdvanced setup

MCP Application ๊ตฌํ˜„ํ•˜๊ธฐ

MCP(Model Context Protocol)์€ ์ƒ์„ฑํ˜• AI application์ด ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ๋ฅผ ํ™œ์šฉํ•˜๋Š” ์ฃผ์š”ํ•œ ์ธํ„ฐํŽ˜์ด์Šค๋กœ ๋น ๋ฅด๊ฒŒ ํ™•์‚ฐ๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. 2024๋…„ 11์›”์— Anthropic์˜ ์˜คํ”ˆ์†Œ์Šค ํ”„๋กœ์ ํŠธ๋กœ ์‹œ์ž‘๋˜์—ˆ๊ณ , ํ˜„์žฌ Cursor๋ฟ ์•„๋‹ˆ๋ผ OpenAI์—์„œ๋„ ์ง€์›ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—์„œ๋Š” MCP with LangChain์„ ์ด์šฉํ•˜์—ฌ LangGraph๋กœ ๋งŒ๋“  application์ด MCP๋ฅผ ํ™œ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ ๊ตฌํ˜„ํ•œ RAG๋Š” Amazon์˜ ์™„์ „๊ด€๋ฆฌํ˜• RAG ์„œ๋น„์Šค์ธ Knowledge base๋กœ ๊ตฌํ˜„๋˜์—ˆ์œผ๋ฏ€๋กœ, ๋ฌธ์„œ์˜ ํ…์ŠคํŠธ ์ถ”์ถœ, ๋™๊ธฐํ™”, chunking๊ณผ ๊ฐ™์€ ์ž‘์—…์„ ์†์‰ฝ๊ฒŒ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋ฉ€ํ‹ฐ๋ชจ๋‹ฌ์„ ์ด์šฉํ•ด ์ด๋ฏธ์ง€/ํ‘œ๋ฅผ ๋ถ„์„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—์„œ๋Š” MCP server์—์„œ RAG์— ์†์‰ฝ๊ฒŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก AWS Lambda๋ฅผ ์ด์šฉํ•ด API๋ฅผ ๊ตฌ์„ฑํ•˜์˜€์Šต๋‹ˆ๋‹ค.

์•„๋ž˜ architecture๋Š” AWS ํ™˜๊ฒฝ์—์„œ MCP๋ฅผ ํฌํ•จํ•œ Agent๋ฅผ ๊ตฌ์„ฑํ•˜๋Š”๊ฒƒ์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. Agent๋Š” MCP server/client ๊ตฌ์กฐ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์™ธ๋ถ€์˜ ๋ฐ์ดํ„ฐ ์†Œ์Šค๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. MCP client๋Š” MCP server์™€ JSON-RPC ํ”„๋กœํ† ์ฝœ์— ๊ธฐ๋ฐ˜ํ•˜์—ฌ stdio/SSE๋กœ ํ†ต์‹ ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. Stdio ์‚ฌ์šฉ์‹œ MCP Server๋Š” python, java์™€ ๊ฐ™์€ ์ฝ”๋“œ๋กœ ๊ตฌ์„ฑ์ด ๋˜๊ณ , client์—์„œ ์š”์ฒญ์ด ์˜ค๋ฉด RAG๋‚˜ ์ธํ„ฐ๋„ท๋“ฑ์„ ์ด์šฉํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ง‘ํ•˜๊ฑฐ๋‚˜ ์ „๋‹ฌํ•˜๋Š” ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. SSE๋กœ ํ•  ๊ฒฝ์šฐ์— MCP client์™€ server๋Š” IP๋กœ ํ†ต์‹ ์„ ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” Streamlit์„ ์ด์šฉํ•ด application์˜ UI๋ฅผ ๊ตฌ์„ฑํ•˜๊ณ , ์‚ฌ์šฉ์ž๋Š” ALB - CloudFront๋ฅผ ์ด์šฉํ•ด HTTPS ๋ฐฉ์‹์œผ๋กœ ๋ธŒ๋ผ์šฐ์ €๋ฅผ ํ†ตํ•ด application์„ ์ด์šฉํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ, ์—ฌ๊ธฐ์—์„œ๋Š” ์ปค์Šคํ„ฐ๋งˆ์ด์ง•์ด ์œ ๋ฆฌํ•œ LangGraph๋ฅผ ์ด์šฉํ•ด MCP ๊ธฐ๋ฐ˜์˜ application์„ ๊ฐœ๋ฐœํ•˜๋Š”๊ฒƒ์„ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.

MCP ํ™œ์šฉ

MCP Basic

์‚ฌ์šฉ์ž๋Š” ์ž์‹ ์˜ Computer์— ์„ค์น˜๋œ Claude Desktop, Cursor์™€ ๊ฐ™์€ AI ๋„๊ตฌ๋ฟ ์•„๋‹ˆ๋ผ ์ฃผ๋กœ Agentํ˜•ํƒœ๋กœ ๊ฐœ๋ฐœ๋œ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ํ†ตํ•ด MCP ์„œ๋ฒ„์— ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. MCP server๋Š” MCP client์˜ ์š”์ฒญ์— ์ž์‹ ์ด ํ• ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ capability๋กœ ์ œ๊ณตํ•˜๊ณ  client์˜ ์š”์ฒญ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. MCP server๋Š” local computer์˜ ํŒŒ์ผ์ด๋‚˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์กฐํšŒํ•  ์ˆ˜ ์žˆ์„๋ฟ ์•„๋‹ˆ๋ผ ์ธํ„ฐ๋„ท์— ์žˆ๋Š” ์™ธ๋ถ€ ์„œ๋ฒ„์˜ API๋ฅผ ์ด์šฉํ•ด ํ•„์š”ํ•œ ์ •๋ณด๋ฅผ ์กฐํšŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. MCP Client๋Š” Server์™€ JSON-RPC 2.0 ํ”„๋กœํ† ์ฝœ์„ ์ด์šฉํ•ด ์—ฐ๊ฒฐ๋˜๋Š”๋ฐ, stdio๋‚˜ SSE (Server-Sent Events)์„ ์„ ํƒํ•˜์—ฌ, Host์˜ ์š”์ฒญ์„ MCP์— ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๊ณ , ์‘๋‹ต์„ ๋ฐ›์•„์„œ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

MCP์˜ ์ฃผ์š” ์š”์†Œ์˜ ์ •์˜์™€ ๋™์ž‘์€ ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • MCP Hosts: MCP ํ”„๋กœํ† ์ฝœ์„ ํ†ตํ•ด ๋ฐ์ดํ„ฐ์— ์ ‘๊ทผํ•˜๋Š” ํ”„๋กœ๊ทธ๋žจ/AI ๋„๊ตฌ๋กœ์„œ Claude Desktop, Cursor, User Agent Application์ด ํ•ด๋‹น๋ฉ๋‹ˆ๋‹ค.
  • MCP Clients: MCP Server์™€ 1:1๋กœ ์—ฐ๊ฒฐ์„ ์ˆ˜ํ–‰ํ•˜๋Š” Client๋กœ์„œ MCP Server์™€ stdio ๋˜๋Š” SSE ๋ฐฉ์‹์œผ๋กœ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • MCP Servers: ํ‘œ์ค€ํ™”๋œ MCP๋ฅผ ํ†ตํ•ด Client์— Tool์˜ Capability๋ฅผ ์•Œ๋ ค์ฃผ๋Š” ๊ฒฝ๋Ÿ‰ ํ”„๋กœ๊ทธ๋žจ์œผ๋กœ Local Computer์˜ ํŒŒ์ผ์ด๋‚˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์กฐํšŒํ•  ์ˆ˜ ์žˆ๊ณ , ์™ธ๋ถ€ API๋ฅผ ์ด์šฉํ•ด ์ •๋ณด๋ฅผ ์กฐํšŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • Local data sources: MCP ์„œ๋ฒ„๊ฐ€ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ๋กœ์ปฌ ๋ฐ์ดํ„ฐ
  • Remote services: API๋ฅผ ํ†ตํ•ด ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ์™ธ๋ถ€ ์‹œ์Šคํ…œ

MCP๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™์€ ์žฅ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

  • ํ‘œ์ค€ํ™”๋œ ๋ฐฉ์‹์œผ๋กœ ๋‹ค์–‘ํ•œ ๋ฐ์ดํ„ฐ ์†Œ์Šค์— ์ ‘๊ทผ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.
  • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ ๋ณ€๊ฒฝ ์—†์ด MCP ์„œ๋ฒ„ ์—…๋ฐ์ดํŠธ๋ฅผ ํ†ตํ•œ ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์กฐ์ง ์ „๋ฐ˜์— ๊ฑธ์ณ AI ์ง€์› ๋ฐ ํ™•์žฅ์ด ์šฉ์ดํ•ฉ๋‹ˆ๋‹ค.

MCP Server Components์—๋Š” ์•„๋ž˜์™€ ๊ฐ™์€ ํ•ญ๋ชฉ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

  • Tools (Model-controlled): LLM์ด ํŠน์ • ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์œ„ํ•ด ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ(๋„๊ตฌ)์œผ๋กœ์„œ, API์™€ ๊ฐ™์ด ํŠน์ •ํ•œ action์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
tools = await session.list_tools()
  • Resources (Application-controlled): ์ƒ์„ฑํ˜• AI ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์ ‘๊ทผ ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐ์ดํ„ฐ ์†Œ์Šค์ž…๋‹ˆ๋‹ค. ๋ณต์žกํ•œ ๊ณ„์‚ฐ(significant computation)์ด๋‚˜ ๋ถ€์ž‘์šฉ(side effect)์—†์ด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
resources = await session.list_resources()
  • Prompts (User-controlled): tool๋‚˜ resource๋ฅผ ์‚ฌ์šฉํ• ๋•Œ์— ์ด์šฉํ•˜๋Š” ์‚ฌ์ „ ์ •์˜๋œ ํ…œํ”Œ๋ ›์œผ๋กœ์„œ ์ถ”๋ก (inference)์ „์— ์„ ํƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
prompts = await session.list_prompts()

Operation Architecture

Streamlit UI(app.py)์—์„œ ๋Œ€ํ™” ํ˜•ํƒœยทAgent ํƒ€์ž…ยทMCP ์„œ๋ฒ„๋ฅผ ์„ ํƒํ•˜๋ฉด chat.py๊ฐ€ ๋ชจ๋“œ๋ณ„๋กœ ๋ผ์šฐํŒ…ํ•ฉ๋‹ˆ๋‹ค. Agent ๋ชจ๋“œ๋Š” LangGraph, Strands, Claude Agent SDK ์„ธ ๊ฐ€์ง€ ๊ตฌํ˜„์„ ์ง€์›ํ•˜๋ฉฐ, MCP ์„œ๋ฒ„ ์„ค์ •์€ mcp_config.py์—์„œ stdio / streamable HTTP transport๋กœ ๋กœ๋“œ๋ฉ๋‹ˆ๋‹ค.

flowchart TB
  subgraph UI["Streamlit (app.py)"]
    MODE["๋Œ€ํ™” ํ˜•ํƒœ / Agent ํƒ€์ž…"]
    MCPUI["MCP ์„œ๋ฒ„ ์„ ํƒ"]
  end

  subgraph Router["chat.py"]
    RAG[run_rag_with_knowledge_base]
    LG[run_langgraph_agent]
    ST[run_strands_agent]
    CA[run_claude_agent]
  end

  subgraph LLM["Amazon Bedrock"]
    BR[Bedrock Runtime]
    KB[Knowledge Base]
  end

  subgraph Agents["Agent ๊ตฌํ˜„"]
    LGA["LangGraph\nMultiServerMCPClient + built-in tools"]
    STA["Strands\nMCPClientManager + strands_tools"]
    CLA["Claude SDK\nClaudeSDKClient + MCP"]
  end

  subgraph MCPServers["MCP Servers (mcp_config.py)"]
    MCP["knowledge base ยท tavily ยท aws document ยท korea_weather ยท ..."]
  end

  subgraph Storage["Artifacts / S3"]
    ART[artifacts/]
    S3[(S3)]
  end

  MODE --> RAG
  MODE --> LG
  MODE --> ST
  MODE --> CA
  MCPUI --> MCPServers

  RAG --> KB --> BR

  LG --> LGA --> BR
  ST --> STA --> BR
  CA --> CLA --> BR

  LGA --> MCPServers
  STA --> MCPServers
  CLA --> MCPServers

  LGA --> ART
  STA --> ART
  LGA --> S3
  STA --> S3
๋ชจ๋“œ๋ชจ๋“ˆ์„ค๋ช…
์ผ์ƒ์ ์ธ ๋Œ€ํ™”chat.general_conversation๋Œ€ํ™” ์ด๋ ฅ + Bedrock Runtime invoke_model_with_response_stream ์ŠคํŠธ๋ฆฌ๋ฐ
RAGchat.run_rag_with_knowledge_baseBedrock Knowledge Base ๊ฒ€์ƒ‰(retrieve) ํ›„ Bedrock Runtime์œผ๋กœ ๋‹ต๋ณ€ ์ƒ์„ฑ
Agent / Agent (Chat) โ€” langgraphchat.run_langgraph_agentLangGraph StateGraph + LangChain MultiServerMCPClient + built-in tools
Agent / Agent (Chat) โ€” strandschat.run_strands_agentStrands SDK + strands_tools + MCPClientManager + built-in tools
Agent / Agent (Chat) โ€” claudeclaude_agent.run_claude_agentClaude Agent SDK(ClaudeSDKClient) + MCP (Bedrock ๋ฐฑ์—”๋“œ)
์ด๋ฏธ์ง€ ๋ถ„์„chat.summarize_image / chat.get_image_summarizationChatBedrock ๋ฉ€ํ‹ฐ๋ชจ๋‹ฌ (์ด๋ฏธ์ง€ + ํ…์ŠคํŠธ) ๋ถ„์„
๋ฒˆ์—ญํ•˜๊ธฐchat.translate_textํ•œโ†”์˜ ๋ฒˆ์—ญ
๋ฌธ๋ฒ• ๊ฒ€ํ† ํ•˜๊ธฐchat.check_grammerํ•œ๊ตญ์–ดยท์˜์–ด ๋ฌธ๋ฒ• ๊ฒ€ํ†  ๋ฐ ์ˆ˜์ • ์ œ์•ˆ

Agent (Chat) ๋ชจ๋“œ๋Š” Agent ๋ชจ๋“œ์™€ ๋™์ผํ•œ Agent ๊ตฌํ˜„์„ ์‚ฌ์šฉํ•˜๋˜, history_mode=Enable๋กœ ๋Œ€ํ™” ์ด๋ ฅ์„ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค. MCP ์„œ๋ฒ„๋Š” ์‚ฌ์ด๋“œ๋ฐ”์—์„œ ๋‹ค์ค‘ ์„ ํƒ ๊ฐ€๋Šฅํ•˜๋ฉฐ, mcp_config.load_selected_config()๋กœ stdio ๋˜๋Š” streamable HTTP ์„ค์ •์ด ๋ณ‘ํ•ฉ๋ฉ๋‹ˆ๋‹ค.

LangChain MCP Adapter

LangChain MCP Adapter๋Š” MCP๋ฅผ LangGraph agent์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๊ฒฝ๋Ÿ‰์˜ ๋žฉํผ(lightweight wrapper)๋กœ์„œ MIT ๊ธฐ๋ฐ˜์˜ ์˜คํ”ˆ์†Œ์Šค์ž…๋‹ˆ๋‹ค. MCP Adapter์˜ ์ฃผ๋œ ์—ญํ• ์€ MCP server๋ฅผ ์œ„ํ•œ tool๋“ค์„ ์ •์˜ํ•˜๊ณ , MCP client์—์„œ tools์˜ ์ •๋ณด๋ฅผ ์กฐํšŒํ•˜๊ณ  LangGraph์˜ tool node๋กœ ์ •์˜ํ•˜์—ฌ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ค๋‹ˆ๋‹ค.

์‚ฌ์ „ ์ค€๋น„

MCP์™€ LangChain MCP Adapter๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค.

pip install mcp langchain-mcp-adapters

MCP Server

RAG ๊ฒ€์ƒ‰์„ ์œ„ํ•œ MCP server๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Server์˜ transport๋ฅผ "stdio"๋กœ ์ง€์ •ํ•˜๋ฉด server๋ฅผ ์ง€์† ์‹คํ–‰์‹œํ‚ค์ง€ ์•Š๋”๋ผ๋„, client๊ฐ€ server์˜ python code๋ฅผ ์ง์ ‘ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์–ด์„œ ํŽธ๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

from mcp.server.fastmcp import FastMCP 

mcp = FastMCP(
    name = "retrieve"
) 

@mcp.tool()
def rag_search(keyword: str) -> str:
    "search keyword"

    return retrieve(keyword)

if __name__ =="__main__":
    mcp.run(transport="stdio")

Server๋Š” ์š”์ฒญ์ด ๋“ค์–ด์˜ค๋ฉด, retrieve_knowledge_base()๋กœ RAG ๊ฒ€์ƒ‰์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

bedrock_agent_runtime_client = boto3.client("bedrock-agent-runtime", region_name=bedrock_region)

def retrieve(query):
    response = bedrock_agent_runtime_client.retrieve(
        retrievalQuery={"text": query},
        knowledgeBaseId=knowledge_base_id,
        retrievalConfiguration={
            "vectorSearchConfiguration": {"numberOfResults": number_of_results},
        },
    )
    payload = json.load(output['Payload'])
    return payload['response'], []

Drug discovery์™€ ๊ด€๋ จํ•˜์—ฌ, arXiv, ChEMBL, ClinicalTrials.gov, PubMed์„ ์ง€์›ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

MCP Client

MCP client์ด ํ•˜๋‚˜์˜ MCP server๋งŒ ๋ณผ ๊ฒฝ์šฐ์—๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด stdio_client์™€ StdioServerParameters๋ฅผ ์ด์šฉํ•ด ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. MCP server์— ๋Œ€ํ•œ ์ •๋ณด๋Š” config.json์—์„œ ์ฝ์–ด์˜ค๊ฑฐ๋‚˜ streamlit์—์„œ ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ์ •๋ณด๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. load_mcp_server_parameters()์—์„œ๋Š” mcp_json์„ ์ฝ์–ด์™€์„œ StdioServerParameters์„ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.

from mcp import ClientSession, StdioServerParameters

def load_mcp_server_parameters():
    mcp_json = json.loads(mcp_config)
    mcpServers = mcp_json.get("mcpServers")

    command = ""
    args = []
    if mcpServers is not None:
        for server in mcpServers:
            config = mcpServers.get(server)
            if "command" in config:
                command = config["command"]
            if "args" in config:
                args = config["args"]
            break

    return StdioServerParameters(
        command=command,
        args=args
    )

์•„๋ž˜์™€ ๊ฐ™์ด MCP server์— ๋Œ€ํ•œ ์ •๋ณด๋กœ stdio_client๋ฅผ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ด๋•Œ tools์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ load_mcp_tools๋กœ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. Agent์—์„œ๋Š” tool ์ •๋ณด๋ฅผ bindํ•˜๊ณ  ainvoke๋ฅผ ์ด์šฉํ•ด ์š”์ฒญ๋œ ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

from mcp.client.stdio import stdio_client
from langchain_mcp_adapters.tools import load_mcp_tools

server_params = load_mcp_server_parameters()

client = MultiServerMCPClient(server_params)
tools = await client.get_tools()

app = buildAgent(tools)
config = {
    "recursion_limit": 50,
    "configurable": {"thread_id": user_id},
    "tools": tools,
    "system_prompt": None
}

inputs = {
    "messages": [HumanMessage(content=query)]
}

async for output in app.astream(inputs, config):
    for key, value in output.items():
        if isinstance(value, dict) and "messages" in value:
            final_output = value

if final_output and "messages" in final_output and len(final_output["messages"]) > 0:
    result = final_output["messages"][-1].content
else:
    result = "๋‹ต๋ณ€์„ ์ฐพ์ง€ ๋ชปํ•˜์˜€์Šต๋‹ˆ๋‹ค."

์—ฌ๊ธฐ์—์„œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ReAct ๋ฐฉ์‹์˜ LangGraph agent๋ฅผ ์ด์šฉํ•ฉ๋‹ˆ๋‹ค.

def call_model(state: State, config):
    system = (
        "๋‹น์‹ ์˜ ์ด๋ฆ„์€ ์„œ์—ฐ์ด๊ณ , ์งˆ๋ฌธ์— ์นœ๊ทผํ•œ ๋ฐฉ์‹์œผ๋กœ ๋Œ€๋‹ตํ•˜๋„๋ก ์„ค๊ณ„๋œ ๋Œ€ํ™”ํ˜• AI์ž…๋‹ˆ๋‹ค."
        "์ƒํ™ฉ์— ๋งž๋Š” ๊ตฌ์ฒด์ ์ธ ์„ธ๋ถ€ ์ •๋ณด๋ฅผ ์ถฉ๋ถ„ํžˆ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค."
        "๋ชจ๋ฅด๋Š” ์งˆ๋ฌธ์„ ๋ฐ›์œผ๋ฉด ์†”์งํžˆ ๋ชจ๋ฅธ๋‹ค๊ณ  ๋งํ•ฉ๋‹ˆ๋‹ค."
        "ํ•œ๊ตญ์–ด๋กœ ๋‹ต๋ณ€ํ•˜์„ธ์š”."
    )
    try:
        prompt = ChatPromptTemplate.from_messages(
            [
                ("system", system),
                MessagesPlaceholder(variable_name="messages"),
            ]
        )
        chain = prompt | model                
        response = chain.invoke(state["messages"])
    return {"messages": [response]}

def should_continue(state: State) -> Literal["continue", "end"]:
    messages = state["messages"]    
    last_message = messages[-1]
    if isinstance(last_message, AIMessage) and last_message.tool_calls:
        return "continue"        
    else:
        return "end"

def buildAgent(tools):
    tool_node = ToolNode(tools)

    workflow = StateGraph(State)

    workflow.add_node("agent", call_model)
    workflow.add_node("action", tool_node)
    workflow.add_edge(START, "agent")
    workflow.add_conditional_edges(
        "agent",
        should_continue,
        {
            "continue": "action",
            "end": END,
        },
    )
    workflow.add_edge("action", "agent")

    return workflow.compile()

MCP client๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๋น„๋™๊ธฐ์ ์œผ๋กœ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•ด์„œ asyncio๋ฅผ ์ด์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค.

asyncio.run(mcp_agent(query, st))

์—ฌ๊ธฐ์„œ๋Š” customize๊ฐ€ ์šฉ์ดํ•˜๋„๋ก agent๋ฅผ ์ •์˜ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

MCP Servers์˜ ํ™œ์šฉ

Model Context Protocol servers์—์„œ๋„ ์•„๋ž˜์™€ ๊ฐ™์€ ์„œ๋ฒ„๋“ค์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

Smithery์—์„œ MCP server๋ฅผ ์ฐพ์•„๋ณด๊ณ  ํ•„์š”ํ•œ ์„œ๋ฒ„๋ฅผ ์ฐพ์œผ๋ฉด ์ ‘์†ํ•  ์ˆ˜ ์žˆ๋Š” MCP ์„œ๋ฒ„ ์ •๋ณด๋ฅผ JSON ํ˜•ํƒœ๋กœ ์กฐํšŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Smithery - Google Search Server์—์„œ ํ™•์ธํ•œ ๊ตฌ๊ธ€ ๊ฒ€์ƒ‰์šฉ MCP ์„œ๋ฒ„ ์ •๋ณด๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๊ฒ€์ƒ‰์—”์ง„ ID์™€ API Key๋ฅผ ํ•„์š”๋กœ ํ•ฉ๋‹ˆ๋‹ค.

{
  "mcpServers": {
    "google-search-mcp-server": {
      "command": "npx",
      "args": [
        "-y",
        "@smithery/cli@latest",
        "run",
        "@gradusnikov/google-search-mcp-server",
        "--config",
        "{\"googleCseId\":\"b5cd8c527fbd64b72\",\"googleApiKey\":\"AIzbSyDQlYpck8-9TbBSuxoew1luOGVB6unRPNk\"}"
      ]
    }
  }
}

์•„๋ž˜์™€ ๊ฐ™์ด json ํ˜•์‹์˜ ์„œ๋ฒ„์ •๋ณด๋ฅผ ์—…๋ฐ์ดํŠธ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๋ž˜์—์„œ๋Š” mcp-server.py์—์„œ ์ •์˜ํ•œ search๋ฅผ ์ด์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

{
  "mcpServers": {
    "search": {
      "command": "python",
      "args": [
        "application/mcp-server.py"
      ]
    }
  }
}

AWS Cost Analysis

MCP tool๋กœ์„œ ์•„๋ž˜์™€ ๊ฐ™์ด AWS cost ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์™€์„œ ๋ถ„์„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

from datetime import datetime, timedelta
import pandas as pd

def get_cost_analysis(days: str=30):
    end_date = datetime.now()
    start_date = end_date - timedelta(days=days)    
    ce = boto3.client('ce')
    service_response = ce.get_cost_and_usage(
        TimePeriod={
            'Start': start_date.strftime('%Y-%m-%d'),
            'End': end_date.strftime('%Y-%m-%d')
        },
        Granularity='MONTHLY',
        Metrics=['UnblendedCost'],
        GroupBy=[{'Type': 'DIMENSION', 'Key': 'SERVICE'}]
    )        
    service_costs = pd.DataFrame([
        {
            'SERVICE': group['Keys'][0],
            'cost': float(group['Metrics']['UnblendedCost']['Amount'])
        }
        for group in service_response['ResultsByTime'][0]['Groups']
    ])
        
    # region cost
    region_response = ce.get_cost_and_usage(
        TimePeriod={
            'Start': start_date.strftime('%Y-%m-%d'),
            'End': end_date.strftime('%Y-%m-%d')
        },
        Granularity='MONTHLY',
        Metrics=['UnblendedCost'],
        GroupBy=[{'Type': 'DIMENSION', 'Key': 'REGION'}]
    )
    region_costs = pd.DataFrame([
        {
            'REGION': group['Keys'][0],
            'cost': float(group['Metrics']['UnblendedCost']['Amount'])
        }
        for group in region_response['ResultsByTime'][0]['Groups']
    ])
        
    # Daily Cost
    daily_response = ce.get_cost_and_usage(
        TimePeriod={
            'Start': start_date.strftime('%Y-%m-%d'),
            'End': end_date.strftime('%Y-%m-%d')
        },
        Granularity='DAILY',
        Metrics=['UnblendedCost'],
        GroupBy=[{'Type': 'DIMENSION', 'Key': 'SERVICE'}]
    )    
    daily_costs = []
    for time_period in daily_response['ResultsByTime']:
        date = time_period['TimePeriod']['Start']
        for group in time_period['Groups']:
            daily_costs.append({
                'date': date,
                'SERVICE': group['Keys'][0],
                'cost': float(group['Metrics']['UnblendedCost']['Amount'])
            })    
    daily_costs_df = pd.DataFrame(daily_costs)
        
    return {
        'service_costs': service_costs,
        'region_costs': region_costs,
        'daily_costs': daily_costs_df
    }

cost์˜ ์ƒ๊ธฐ 3๊ฐ€์ง€ ๊ฒฐ๊ณผ๋ฅผ ๊ทธ๋ž˜ํ”„๋กœ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์•„๋ž˜ ํŒจํ‚ค์ง€ ์„ค์น˜๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

pip install -U kaleido

MCP Image Generation

mcp_server_image_generation.py๊ณผ ๊ฐ™์ด ์ด๋ฏธ์ง€ ์ƒ์„ฑํ•˜๋Š” tool์„ ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค.

MCP config๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. mcp_config.py์„ ์ฐธ์กฐํ•ฉ๋‹ˆ๋‹ค.

{
    "mcpServers": {
        "imageGeneration": {
            "command": "python",
            "args": [
                "application/mcp_server_image_generation.py"
            ]
        }
    }
}

์ดํ›„ mcp_nova_canvas.py์™€ ๊ฐ™์ด ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

async def mcp_generate_image(ctx, prompt, negative_prompt, filename, width, height, quality, cfg_scale, seed, number_of_images):
    """Generate an image using Amazon Nova Canvas with text prompt."""
    
    response = await generate_image_with_text(
        prompt=prompt,
        negative_prompt=negative_prompt,
        filename=filename,
        width=width,
        height=height,
        quality=quality,
        cfg_scale=cfg_scale,
        seed=seed,
        number_of_images=number_of_images
    )

    return {
        "url": [f'{path}' for path in response.paths]
    } 

MCP์˜ image_generation๋กœ ๋ถ€ํ„ฐ ์–ป์€ ๊ฒฐ๊ณผ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. ์ƒ์„ธํ•œ ๋‚ด์šฉ์€ chat.py์„ ์ฐธ๊ณ ํ•˜์„ธ์š”.

def show_status_message(response, st):
    image_url = []
    for i, re in enumerate(response):
        logger.info(f"message[{i}]: {re}")
        if i==len(response)-1:
            break
        if isinstance(re, ToolMessage):
          tool_result = json.loads(re.content)
          logger.info(f"tool_result: {tool_result}")

          if "url" in tool_result:
              st.info(f"URL: {tool_result['url']}")

              urls = tool_result['url']
              for url in urls:
                  image_url.append(url)
                  st.image(url)
    return image_url

MCP AWS Diagram

AWS Diagram MCP Server์„ ์ด์šฉํ•˜๋ฉด AWS Diagram์„ ๊ทธ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ƒ์„ธํ•œ ๋‚ด์šฉ์€ mcp_config.py์„ ์ฐธ์กฐํ•ฉ๋‹ˆ๋‹ค.

์ด๋•Œ ์‚ฌ์šฉํ•˜๋Š” MCP Config๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

{
    "mcpServers": {
        "awslabs.aws-diagram-mcp-server": {
            "command": "uvx",
            "args": ["awslabs.aws-diagram-mcp-server"],
            "env": {
                "FASTMCP_LOG_LEVEL": "ERROR"
            },
        }
    }
}

Diagram์„ ๊ทธ๋ฆฌ๊ธฐ ์œ„ํ•ด์„œ๋Š” Graphviz๋ฅผ ๋”ฐ๋ผ์„œ graphviz๋ฅผ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค. Mac์—์„œ๋Š” ์•„๋ž˜ ๋ช…๋ น์–ด๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

brew install graphviz

MCP AWS Documentation

AWS Documentation MCP Server์„ ์ด์šฉํ•˜์—ฌ AWS ๋ฌธ์„œ๋“ค์„ ์กฐํšŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋•Œ ์‚ฌ์šฉํ•˜๋Š” MCP config๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์ƒ์„ธํ•œ ๋‚ด์šฉ์€ mcp_config.py์„ ์ฐธ์กฐํ•ฉ๋‹ˆ๋‹ค.

{
    "mcpServers": {
        "awslabs.aws-documentation-mcp-server": {
            "command": "uvx",
            "args": ["awslabs.aws-documentation-mcp-server@latest"],
            "env": {
                "FASTMCP_LOG_LEVEL": "ERROR"
            }
        }
    }
}

๋ฆฌ์ „๋ณ„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋ธ์˜ ํ™•์ธ ๋ฐฉ๋ฒ•

์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋ธ์˜ ํ™•์ธ ๋ฐฉ๋ฒ•์€ ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

aws bedrock list-foundation-models --region=us-west-2 --by-provider anthropic --query "modelSummaries[*].modelId"

Message Trim

LangGraph ์—์ด์ „ํŠธ(application/langgraph_agent.py์˜ call_model)๋Š” LLM ํ˜ธ์ถœ ์ง์ „์— HumanMessage ๊ธฐ์ค€ ์ตœ๊ทผ Nํ„ด๋งŒ ๋‚จ๊น๋‹ˆ๋‹ค. LangGraph state์˜ messages๋Š” checkpointer์— ๊ทธ๋Œ€๋กœ ๋‘๊ณ , ๋ชจ๋ธ์— ๋„˜๊ธฐ๋Š” ๋ฉ”์‹œ์ง€๋งŒ trimํ•ฉ๋‹ˆ๋‹ค. history_mode=Enable/Disable ๋ชจ๋‘ ๋™์ผํ•˜๊ฒŒ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.

๊ธฐ๋ณธ๊ฐ’: MAX_CONTEXT_TURNS = 5 (์ผ๋ฐ˜ ์ฑ„ํŒ…์˜ SimpleMemory(k=5)์™€ ๋™์ผํ•œ โ€œ์ตœ๊ทผ 5ํ„ดโ€ ์˜๋„)

์„ค์ • ๋ณ€๊ฒฝ:

  • application/langgraph_agent.py์˜ MAX_CONTEXT_TURNS ์ƒ์ˆ˜ ์ˆ˜์ •
  • ๋˜๋Š” application/chat.py์˜ create_agent()์—์„œ config์˜ max_turns / configurable.max_turns ์ง€์ •
  • max_turns=0์ด๋ฉด trim ๋น„ํ™œ์„ฑํ™”

์ƒ์ˆ˜์™€ trim ํ•จ์ˆ˜๋Š” langgraph_agent.py์— ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.

# application/langgraph_agent.py
MAX_CONTEXT_TURNS = 5

def trim_messages_by_human_turns(messages: list, max_turns: int) -> list:
    """Keep messages from the last N HumanMessage turns (inclusive)."""
    if max_turns <= 0 or not messages:
        return messages

    human_indices = [i for i, msg in enumerate(messages) if isinstance(msg, HumanMessage)]
    if len(human_indices) <= max_turns:
        return messages

    return messages[human_indices[-max_turns]:]

call_model์—์„œ๋Š” ToolMessage content ์ •๊ทœํ™” ํ›„ trim์„ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.

# application/langgraph_agent.py โ€” call_model() ๋‚ด๋ถ€
        max_turns = (
            config.get("configurable", {}).get("max_turns")
            or config.get("max_turns")
            or MAX_CONTEXT_TURNS
        )
        trimmed = trim_messages_by_human_turns(messages, max_turns)
        if len(trimmed) < len(messages):
            logger.info(
                f"trimmed messages from {len(messages)} to {len(trimmed)} "
                f"(max_turns={max_turns})"
            )
            messages = trimmed

        prompt = ChatPromptTemplate.from_messages(
            [
                ("system", system),
                MessagesPlaceholder(variable_name="messages"),
            ]
        )
        chain = prompt | model
        async for chunk in chain.astream({"messages": messages}):
            ...

์—์ด์ „ํŠธ config๋Š” chat.py์˜ create_agent()์—์„œ ์ƒ์„ฑํ•˜๋ฉฐ, history_mode์™€ ๊ด€๊ณ„์—†์ด max_turns๋ฅผ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.

# application/chat.py โ€” create_agent()
    if history_mode == "Enable":
        app = langgraph_agent.buildChatAgentWithHistory(tools)
        config = {
            "recursion_limit": 50,
            "configurable": {
                "thread_id": user_id,
                "tools": tools,
                "system_prompt": None,
            },
            "max_turns": langgraph_agent.MAX_CONTEXT_TURNS,
        }
    else:
        app = langgraph_agent.buildChatAgent(tools)
        config = {
            "recursion_limit": 50,
            "configurable": {
                "thread_id": user_id,
                "tools": tools,
                "system_prompt": None,
            },
            "max_turns": langgraph_agent.MAX_CONTEXT_TURNS,
        }

max_turns=5์˜ ์˜๋ฏธ

  • ์‚ฌ์šฉ์ž HumanMessage 5๊ฐœ์™€, ๊ฐ ํ„ด์— ์ด์–ด์ง„ ๋ชจ๋“  ํ›„์† ๋ฉ”์‹œ์ง€๋ฅผ ์œ ์ง€
  • 1ํ„ด = HumanMessage 1๊ฐœ + ๊ทธ ๋’ค์˜ AIMessage, ToolMessage, ๋„๊ตฌ feedback loop ์ „์ฒด
  • ๋„๊ตฌ๋ฅผ ์—ฌ๋Ÿฌ ๋ฒˆ ํ˜ธ์ถœํ•ด๋„ ๊ฐ™์€ ์‚ฌ์šฉ์ž ์งˆ๋ฌธ์ด๋ฉด 1ํ„ด์œผ๋กœ ์นด์šดํŠธ

์˜ˆ (๋„๊ตฌ ์‚ฌ์šฉ ํฌํ•จ)

Human(Q1) โ†’ AI(tool_calls) โ†’ ToolMessage โ†’ AI(A1)
Human(Q2) โ†’ AI(A2)
Human(Q3) โ†’ AI(tool_calls) โ†’ ToolMessage โ†’ AI(A3)

max_turns=2์ด๋ฉด Q2๋ถ€ํ„ฐ ์œ ์ง€:

Human(Q2) โ†’ AI(A2) โ†’ Human(Q3) โ†’ AI(tool_calls) โ†’ ToolMessage โ†’ AI(A3)

๋ฉ”์‹œ์ง€ ๊ฐœ์ˆ˜ trim๊ณผ์˜ ์ฐจ์ด

๋ฐฉ์‹N=5์ผ ๋•Œ
์ด์ „ (๋ฉ”์‹œ์ง€ ๊ฐœ์ˆ˜)๋ฉ”์‹œ์ง€ ๊ฐ์ฒด 5๊ฐœ๋งŒ ์œ ์ง€ โ†’ ๋„๊ตฌ ๋ฃจํ”„ ๋•Œ๋ฌธ์— ์‚ฌ์šฉ์ž ํ„ด ์ˆ˜๊ฐ€ ๋ถˆ๊ทœ์น™
ํ˜„์žฌ (HumanMessage ํ„ด)์‚ฌ์šฉ์ž ์งˆ๋ฌธ 5๊ฐœ + ๊ฐ ํ„ด์˜ AI/Tool ์‘๋‹ต ์ „์ฒด ์œ ์ง€

Checkpointer์™€์˜ ๊ด€๊ณ„

  • history_mode=Enable์ผ ๋•Œ MemorySaver checkpointer์—๋Š” ์ „์ฒด ๋Œ€ํ™” ์ด๋ ฅ์ด ์ €์žฅ๋ฉ๋‹ˆ๋‹ค.
  • trim์€ LLM ์ปจํ…์ŠคํŠธ ์œˆ๋„์šฐ ๊ด€๋ฆฌ์šฉ์ด๋ฉฐ, ์ €์žฅ๋œ history๋ฅผ ์‚ญ์ œํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • CloudWatch(/ecs/...) ๋˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋กœ๊ทธ์—์„œ trimmed messages from X to Y (max_turns=5)๋กœ trim ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฐฐํฌํ•˜๊ธฐ

EC2๋กœ ๋ฐฐํฌํ•˜๊ธฐ

AWS console์˜ EC2๋กœ ์ ‘์†ํ•˜์—ฌ Launch an instance๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค. [Launch instance]๋ฅผ ์„ ํƒํ•œ ํ›„์— ์ ๋‹นํ•œ Name์„ ์ž…๋ ฅํ•ฉ๋‹ˆ๋‹ค. (์˜ˆ: es) key pair์€ "Proceed without key pair"์„ ์„ ํƒํ•˜๊ณ  ๋„˜์–ด๊ฐ‘๋‹ˆ๋‹ค.

ec2แ„‹แ…ตแ„…แ…ณแ†ทแ„‹แ…ตแ†ธแ„…แ…งแ†จ

Instance๊ฐ€ ์ค€๋น„๋˜๋ฉด [Connet] - [EC2 Instance Connect]๋ฅผ ์„ ํƒํ•˜์—ฌ ์•„๋ž˜์ฒ˜๋Ÿผ ์ ‘์†ํ•ฉ๋‹ˆ๋‹ค.

image

์ดํ›„ ์•„๋ž˜์™€ ๊ฐ™์ด python, pip, git, boto3๋ฅผ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค.

sudo yum install python3 python3-pip git docker -y
pip install boto3

Workshop์˜ ๊ฒฝ์šฐ์— ์•„๋ž˜ ํ˜•ํƒœ๋กœ ๋œ Credential์„ ๋ณต์‚ฌํ•˜์—ฌ EC2 ํ„ฐ๋ฏธ๋„์— ์ž…๋ ฅํ•ฉ๋‹ˆ๋‹ค.

credential

์•„๋ž˜์™€ ๊ฐ™์ด git source๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.

git clone https://github.com/kyopark2014/es-us-project

์•„๋ž˜์™€ ๊ฐ™์ด installer.py๋ฅผ ์ด์šฉํ•ด ์„ค์น˜๋ฅผ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.

cd es-us-project && python3 installer.py

API ๊ตฌํ˜„์— ํ•„์š”ํ•œ credential์€ secret์œผ๋กœ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์„ค์น˜์‹œ ํ•„์š”ํ•œ credential ์ž…๋ ฅ์ด ํ•„์š”ํ•œ๋ฐ ์•„๋ž˜์™€ ๊ฐ™์€ ๋ฐฉ์‹์„ ํ™œ์šฉํ•˜์—ฌ ๋ฏธ๋ฆฌ credential์„ ์ค€๋น„ํ•ฉ๋‹ˆ๋‹ค.

  • ์ผ๋ฐ˜ ์ธํ„ฐ๋„ท ๊ฒ€์ƒ‰: Tavily Search์— ์ ‘์†ํ•˜์—ฌ ๊ฐ€์ž… ํ›„ API Key๋ฅผ ๋ฐœ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ tvly-๋กœ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.
  • ๋‚ ์”จ ๊ฒ€์ƒ‰: openweathermap์— ์ ‘์†ํ•˜์—ฌ API Key๋ฅผ ๋ฐœ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค. ์ด๋•Œ price plan์€ "Free"๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.

์„ค์น˜๊ฐ€ ์™„๋ฃŒ๋˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™์€ CloudFront๋กœ ์ ‘์†ํ•˜์—ฌ ๋™์ž‘์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

cloudfront_address

์ธํ”„๋ผ๊ฐ€ ๋”์ด์ƒ ํ•„์š”์—†์„ ๋•Œ์—๋Š” uninstaller.py๋ฅผ ์ด์šฉํ•ด ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค.

python uninstaller.py

๋ฐฐํฌ๋œ Application ์—…๋ฐ์ดํŠธ ํ•˜๊ธฐ

AWS console์˜ EC2๋กœ ์ ‘์†ํ•˜์—ฌ Launch an instance๋ฅผ ์„ ํƒํ•˜์—ฌ ์•„๋ž˜์™€ ๊ฐ™์ด ์•„๋ž˜์™€ ๊ฐ™์ด "app-for-es-us"๋ผ๋Š” ์ด๋ฆ„์„ ๊ฐ€์ง€๋Š” instance id๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.

image

[connect]๋ฅผ ์„ ํƒํ•œ ํ›„์— Session Manager๋ฅผ ์„ ํƒํ•˜์—ฌ ์ ‘์†ํ•ฉ๋‹ˆ๋‹ค.

image

์ดํ›„ ์•„๋ž˜์™€ ๊ฐ™์ด ์—…๋ฐ์ดํŠธํ•œ ํ›„์— ๋‹ค์‹œ ๋ธŒ๋ผ์šฐ์ €์—์„œ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

cd ~/es-us-project/ && sudo ./update.sh

์‹คํ–‰ ๋กœ๊ทธ ํ™•์ธ

EC2 console์—์„œ "app-for-es-us"๋ผ๋Š” ์ด๋ฆ„์„ ๊ฐ€์ง€๋Š” instance id๋ฅผ ์„ ํƒ ํ•œ ํ›„์—, EC2์˜ Session Manager๋ฅผ ์ด์šฉํ•ด ์ ‘์†ํ•ฉ๋‹ˆ๋‹ค.

๋จผ์ € ์•„๋ž˜์™€ ๊ฐ™์ด ํ˜„์žฌ docker container ID๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

sudo docker ps

์ดํ›„ ์•„๋ž˜์™€ ๊ฐ™์ด container ID๋ฅผ ์ด์šฉํ•ด ๋กœ๊ทธ๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

sudo docker logs [container ID]

์‹ค์ œ ์‹คํ–‰์‹œ ๊ฒฐ๊ณผ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

Local์—์„œ ์‹คํ–‰ํ•˜๊ธฐ

AWS ํ™˜๊ฒฝ์„ ์ž˜ ํ™œ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” AWS CLI๋ฅผ ์„ค์น˜ํ•˜์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค. EC2์—์„œ ๋ฐฐํฌํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” ๋ณ„๋„๋กœ ์„ค์น˜๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. Local์— ์„ค์น˜์‹œ๋Š” ์•„๋ž˜ ๋ช…๋ น์–ด๋ฅผ ์ฐธ์กฐํ•ฉ๋‹ˆ๋‹ค.

curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" 
unzip awscliv2.zip
sudo ./aws/install

AWS credential์„ ์•„๋ž˜์™€ ๊ฐ™์ด AWS CLI๋ฅผ ์ด์šฉํ•ด ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค.

aws configure

์„ค์น˜ํ•˜๋‹ค๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฐ์ข… ๋ฌธ์ œ๋Š” Kiro-cli๋ฅผ ์ด์šฉํ•ด ๋น ๋ฅด๊ฒŒ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค. ์•„๋ž˜์™€ ๊ฐ™์ด ์„ค์น˜ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, Windows์—์„œ๋Š” Kiro ์„ค์น˜์—์„œ ๋‹ค์šด๋กœ๋“œ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค. ์‹คํ–‰์‹œ๋Š” ์…€์—์„œ "kiro-cli"๋ผ๊ณ  ์ž…๋ ฅํ•ฉ๋‹ˆ๋‹ค.

curl -fsSL https://cli.kiro.dev/install | bash

venv๋กœ ํ™˜๊ฒฝ์„ ๊ตฌ์„ฑํ•˜๋ฉด ํŽธ๋ฆฌํ•˜๊ฒŒ ํŒจํ‚ค์ง€๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ์•„๋ž˜์™€ ๊ฐ™์ด ํ™˜๊ฒฝ์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

python -m venv .venv
source .venv/bin/activate

์ดํ›„ ๋‹ค์šด๋กœ๋“œ ๋ฐ›์€ github ํด๋”๋กœ ์ด๋™ํ•œ ํ›„์— ์•„๋ž˜์™€ ๊ฐ™์ด ํ•„์š”ํ•œ ํŒจํ‚ค์ง€๋ฅผ ์ถ”๊ฐ€๋กœ ์„ค์น˜ ํ•ฉ๋‹ˆ๋‹ค.

pip install -r requirements.txt

์ดํ›„ ์•„๋ž˜์™€ ๊ฐ™์€ ๋ช…๋ น์–ด๋กœ streamlit์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

streamlit run application/app.py

MCP Inspector

Development Mode์—์„œ mcp server๋ฅผ ํ…Œ์ŠคํŠธ ํ•˜๊ธฐ ์œ„ํ•ด MCP inspector๋ฅผ ์ด์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๋ž˜์™€ ๊ฐ™์ด cli๋ฅผ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค.

pip install 'mcp[cli]'

์ดํ›„ ์•„๋ž˜์™€ ๊ฐ™์ด ์‹คํ–‰ํ•˜๋ฉด ์‰ฝ๊ฒŒ mcp-server.py์˜ ๋™์ž‘์„ ํ…Œ์ŠคํŠธ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‹คํ–‰์‹œ http://localhost:5173 ์™€ ๊ฐ™์€ URL์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

mcp dev mcp-server.py

์‹คํ–‰ ๊ฒฐ๊ณผ

MCP๋กœ RAG๋ฅผ ์กฐํšŒํ•˜์—ฌ ํ™œ์šฉํ•˜๊ธฐ

error_code.pdf์„ ๋‹ค์šด๋กœ๋“œ ํ•œ ํ›„์— ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค. ์ดํ›„ ์•„๋ž˜์™€ ๊ฐ™์ด "๋ณด์ผ๋Ÿฌ ์—๋Ÿฌ์ค‘ ์ˆ˜์••๊ณผ ๊ด€๋ จ๋œ ์—๋Ÿฌ ์ฝ”๋“œ๋ฅผ ๊ฒ€์ƒ‰ํ•ด์ฃผ์„ธ์š”."์™€ ๊ฐ™์ด ์ž…๋ ฅํ•˜๋ฉด mcp๋ฅผ ์ด์šฉํ•ด tool์˜ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ , search tool๋กœ ์–ป์–ด์ง„ ์ •๋ณด๋ฅผ ์ด์šฉํ•ด ์•„๋ž˜์™€ ๊ฐ™์€ ์ •๋ณด๋ฅผ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋•Œ search tool์€ lambda๋ฅผ ์‹คํ–‰ํ•˜๋Š”๋ฐ lambda์—์„œ๋Š” ์™„์ „ ๊ด€๋ฆฌํ˜• RAG ์„œ๋น„์Šค์ธ knowledge base๋ฅผ ์ด์šฉํ•˜์—ฌ ๊ฒ€์ƒ‰์–ด๋ฅผ ์กฐํšŒํ•˜๊ณ  ๊ด€๋ จ์„ฑ์„ ํ‰๊ฐ€ํ•œ ํ›„์— ๊ด€๋ จ๋œ ๋ฌธ์„œ๋งŒ์„ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. Agent๋Š” RAG๋ฅผ ์กฐํšŒํ•˜์—ฌ ์–ป์–ด์ง„ ์ •๋ณด๋กœ ๋‹ต๋ณ€์„ ์•„๋ž˜์™€ ๊ฐ™์ด ๊ตฌํ•ฉ๋‹ˆ๋‹ค.

MCP๋กœ ์ธํ„ฐ๋„ท ๊ฒ€์ƒ‰์„ ํ•˜์—ฌ ํ™œ์šฉํ•˜๊ธฐ

smithery-Tavily์— ์ ‘์†ํ•˜์—ฌ ํ™˜๊ฒฝ์— ๋งž๋Š” ์„ค์ •๊ฐ’์„ ์–ป์–ด์˜ต๋‹ˆ๋‹ค. ์•„๋ž˜๋Š” Mac/Linux์˜ JSON format์˜ ์ ‘์† ์ •๋ณด์ž…๋‹ˆ๋‹ค.

{
  "mcpServers": {
    "mcp-tavily": {
      "command": "npx",
      "args": [
        "-y",
        "@smithery/cli@latest",
        "run",
        "mcp-tavily",
        "--key",
        "132c5abd-6f2e-4e42-89a1-d0b1fcb75613"
      ]
    }
  }
}

์•„๋ž˜๋Š” ๊ธฐ๋ณธ ์„ค์ •๋œ RAG๋ฅผ ์œ„ํ•œ ์ •๋ณด์ž…๋‹ˆ๋‹ค.

{
  "mcpServers": {
    "search": {
      "command": "python",
      "args": [
        "application/mcp-server.py"
      ]
    }
  }
}

์•„๋ž˜๋Š” multiple mcp server๋ฅผ ์„ค์ •์‹œ config ์ž…๋‹ˆ๋‹ค.

{
   "mcpServers":{
      "RAG":{
         "command":"python",
         "args":[
            "application/mcp-server.py"
         ]
      },
      "mcp-tavily":{
         "command":"npx",
         "args":[
            "-y",
            "@smithery/cli@latest",
            "run",
            "mcp-tavily",
            "--key",
            "132c5abd-6f2e-4e42-89a1-d0b1fcb75613"
         ]
      }
   }
}

์ด ์ •๋ณด๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ์™ผ์ชฝ ๋ฉ”๋‰ด์˜ MCP Config์— ๋ณต์‚ฌํ•ฉ๋‹ˆ๋‹ค.

์ดํ›„ ๋ฉ”๋‰ด์—์„œ "Agent"๋ฅผ ์„ ํƒํ›„์— ์•„๋ž˜์™€ ๊ฐ™์ด "๊ฐ•๋‚จ์—ญ ๋ง›์ง‘์€?"๋ผ๊ณ  ์ž…๋ ฅํ›„ ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

AWS Cost Analysis

"์ง€๋‚œ ํ•œ๋‹ฌ๊ฐ„์˜ AWS ๋น„์šฉ์„ ์š”์•ฝํ•ด์ฃผ์„ธ์š”." ์ž…๋ ฅํ›„์— ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

์ด๋ฏธ์ง€ ์ƒ์„ฑ

์™ผ์ชฝ ๋ฉ”๋‰ด์—์„œ Agent๋ฅผ ์„ ํƒํ•˜๊ณ , MCP config๋กœ "image generation"์„ ์„ ํƒํ•œ ํ›„์— "๋…ธ์„์ด ์ง€๋Š” ์•„๋ฆ„๋‹ค์šด ๊ฐ•๋ณ€์„ ๋‹ฌ๋ฆฌ๊ธฐํ•˜๋Š” ์‚ฌ๋žŒ์„ ๊ทธ๋ ค์ฃผ์„ธ์š”."๋ผ๊ณ  ์ž…๋ ฅ ํ›„์— ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

AWS Architecture ๊ทธ๋ฆฌ๊ธฐ

๋ฉ”๋‰ด์—์„œ "Agent(Chat)"๋ฅผ ์„ ํƒํ•˜๊ณ  MCP๋กœ "aws diagram"๋ฅผ ๊ณ ๋ฅธ ํ›„, "Amazon S3๋กœ web hosting์„ ํ•˜๊ธฐ ์œ„ํ•œ architecture๋ฅผ ์ถ”์ฒœํ•ด์ฃผ์„ธ์š”."์™€ "Cognito๋ฅผ ์ด์šฉํ•ด ์ธ์ฆํ• ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ฃผ์„ธ์š”."์œผ๋กœ ์ˆœ์ฐจ์ ์œผ๋กœ ๋ช…๋ น์„ ํ•˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™์€ ๊ฒฐ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

noname

Code Interpreter์˜ ํ™œ์šฉ

"strawberry์˜ r์˜ ๊ฐฏ์ˆ˜๋Š”?"๋กœ ์งˆ๋ฌธํ•˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด Tools ๋ฆฌ์ŠคํŠธ์—์„œ "repl_coder"๊ฐ€ ์„ ํƒ๋˜์–ด ํ™œ์šฉ๋ฉ๋‹ˆ๋‹ค.

Storage

Tool์—์„œ "aws storage"๋ฅผ ์„ ํƒํ•˜๊ณ , "๋‚ด s3 ์ „์ฒด ์‚ฌ์šฉ๋Ÿ‰์€?"์ด๋ผ๊ณ  ์งˆ๋ฌธํ•ฉ๋‹ˆ๋‹ค. ์ด๋•Œ์˜ ๊ฒฐ๊ณผ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

"๋‚ด aws strorage ์‚ฌ์šฉ๋Ÿ‰์€?"์ด๋ผ๊ณ  ์งˆ๋ฌธํ•˜๋ฉด, S3, EFS, EBS์˜ ์šฉ๋Ÿ‰์„ ํ™•์ธํ•˜์—ฌ ์•„๋ž˜์™€ ๊ฐ™์ด ๋‹ต๋ณ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Reference

MCP Python SDK

Using MCP with LangGraph agents

MCP From Scratch

Understanding MCP From Scratch

LangChain MCP Adapters

"Vibe Coding" LangGraph apps with llms.txt and MCP

MCP LLMS-TXT Documentation Server

MCP - For Server Developers

Model Context Protocol (MCP) and Amazon Bedrock

Model Context Protocol servers

Langchain.js MCP Adapter

Using LangChain With Model Context Protocol (MCP)

Desktop Commander MCP

Smithery

Cursor AI ๋ง๊ณ , ๋‚˜๋งŒ์˜ #MCP ์—์ด์ „ํŠธ ์•ฑ ๋งŒ๋“ค์–ด ๋ณด๊ธฐ!

The Top 7 MCP-Supported AI Frameworks

Drug Discovery Agent based on Amazon Bedrock

Price Per Token

Claude Code Subagents Collection

Top 5 Open-Source Agentic Frameworks