LangGraphμ Model Context Protocolμ νμ©ν μ§λ₯ν AI λν μμ€ν
μ΄ νλ‘μ νΈλ Model Context Protocol(MCP)μ μλ λ°©μμ μ΄ν΄νκΈ° μν κ΅μ‘μ© λ°λͺ¨ μμ€ν μ λλ€.
κΈ°λ³Έμ μΈ μΉ UI μ¬μ©λ²κ³Ό AIμμ λν κ³Όμ μ 보μ¬μ£Όλ λ°λͺ¨ μμμ λλ€.
ReAct ν¨ν΄μ ν΅ν 볡ν©μ μΈ λ μ¨ κ²μ λ° λ¦¬ν¬νΈ μμ± κ³Όμ μ 보μ¬μ£Όλ λ°λͺ¨ μμμ λλ€. Think β Act β Observe μ¬μ΄ν΄μ ν΅ν΄ λ¨κ³λ³λ‘ μ 보λ₯Ό μμ§νκ³ λΆμν©λλ€.
Phoenix UIλ₯Ό ν΅ν AI μ ν리μΌμ΄μ μΆμ λ° μ±λ₯ λΆμ κ³Όμ μ 보μ¬μ£Όλ λ°λͺ¨ μμμ λλ€.
MCPλ AI μ ν리μΌμ΄μ μ΄ λ€μν λ°μ΄ν° μμ€μ λꡬμ μμ νκ³ νμ€νλ λ°©μμΌλ‘ μ°κ²°ν μ μκ² ν΄μ£Όλ κ°λ°©ν νλ‘ν μ½μ λλ€. λ³Έ λ°λͺ¨λ₯Ό ν΅ν΄ λ€μμ νμ΅ν μ μμ΅λλ€:
- MCP νλ‘ν μ½ μ΄ν΄: ν΄λΌμ΄μΈνΈ-μλ² μν€ν μ²μ JSON-RPC ν΅μ λ°©μ
- μ€μ ꡬν 체ν: LangChain MCP Adaptersλ₯Ό νμ©ν μ€μ MCP ν΄λΌμ΄μΈνΈ ꡬν
- λ€μν MCP μλ² μ°λ: λλ―Έ μλ²(κ΅μ‘μ©)μ μ€μ μλ²(Context7) λΉκ΅ 체ν
- LangGraph μν¬νλ‘μ°: MCP λꡬλ₯Ό νμ©ν 볡μ‘ν AI μν¬νλ‘μ° κ΅¬μ±
- ReAct ν¨ν΄: λꡬ κΈ°λ° μΆλ‘ λ° νλ ν¨ν΄μ μ€μ μ μ©
- 3κ°μ MCP μλ²: 2κ° λλ―Έ μλ²(νμ΅μ©) + 1κ° μ€μ μλ²(μ€μ©μ±)
- λ¨κ³λ³ 볡μ‘λ: λ¨μ λꡬ νΈμΆ β ReAct ν¨ν΄ β λ³΅ν© μν¬νλ‘μ°
- μ€μκ° λͺ¨λν°λ§: Phoenixλ₯Ό ν΅ν AI μ ν리μΌμ΄μ λ΄λΆ λμ κ΄μ°°
- μμ ν μμ€μ½λ: λͺ¨λ ꡬν μΈλΆμ¬νμ νμ΅ν μ μλ μ€νμμ€
- π€ μ€μκ° AI λν: OpenAI GPT λͺ¨λΈμ νμ©ν μμ°μ€λ¬μ΄ λν
- β‘ SSE μ€μκ° μ€νΈλ¦¬λ°: ν ν° λ¨μ μλ΅ μ€νΈλ¦¬λ°μΌλ‘ μ¦μ λ°μνλ λν κ²½ν
- π¬ Multi-Turn λν: μΈμ κΈ°λ° μ»¨ν μ€νΈ μ μ§λ‘ μ°μμ μ΄κ³ μ§λ₯μ μΈ λν μ§μ
- π§ λ€μ€ λꡬ ν΅ν©: λ μ¨, νμΌ κ΄λ¦¬, λ¬Έμ κ²μ λ± λ€μν MCP μλ² μ°λ
- π LangChain MCP μ΄λν°:
langchain_mcp_adaptersλ₯Ό ν΅ν νμ€ MCP νλ‘ν μ½ μ§μ - π§ ReAct ν¨ν΄: 볡μ‘ν μμ²μ λν λ¨κ³λ³ μ¬κ³ λ° νλ μ²λ¦¬
- π λ°μν μΉ UI: μ€μκ° νμ΄ν ν¨κ³Όμ μν¬νλ‘μ° μΆμ μ΄ κ°λ₯ν νλμ μΈν°νμ΄μ€
- π μ§λ₯ν μΈμ κ΄λ¦¬: λν νμ€ν 리, 컨ν μ€νΈ μμ½, λꡬ νΈμΆ κ²°κ³Ό μΊμ±
- π LangGraph μν¬νλ‘μ°: μν κΈ°λ° μν¬νλ‘μ° μμ§μΌλ‘ 볡μ‘ν μμ μ²λ¦¬
- π Phoenix λͺ¨λν°λ§: AI μ ν리μΌμ΄μ μΆμ λ° μ±λ₯ λΆμ (μ νμ )
π μΈν°λν°λΈ μν¬νλ‘μ° λ€μ΄μ΄κ·Έλ¨ 보기
graph TD;
__start__([Start]):::first
parse_message(Parse)
call_mcp_tool(MCP tool call)
generate_response(Response)
react_think(ReAct: think)
react_act(ReAct: act)
react_observe(ReAct: observe)
react_finalize(ReAct: finalize)
__end__([End]):::last
__start__ --> parse_message
%% μ‘°κ±΄λΆ λΆκΈ°
parse_message -.-> call_mcp_tool
parse_message -.-> generate_response
parse_message -.-> react_think
%% μΌλ° μν¬νλ‘μ°
call_mcp_tool --> generate_response
generate_response --> __end__
%% ReAct μν¬νλ‘μ° (μν)
react_think -.-> react_act
react_think -.-> react_finalize
react_think -.-> generate_response
react_act -.-> react_observe
react_act -.-> react_finalize
react_observe -.-> react_think
react_observe -.-> react_finalize
%% μ΅μ’
μ’
λ£
react_finalize --> __end__
classDef default fill:#f2f0ff,line-height:1.2
classDef first fill:#e1f5fe
classDef last fill:#bfb6fc
LangGraph κΈ°λ° μν¬νλ‘μ° κ΅¬μ‘°: μ¬μ©μ μ λ ₯λΆν° μ΅μ’ μλ΅κΉμ§μ μ 체 μ²λ¦¬ νλ¦
- μ€μ (β): νμ μ€νλλ μ°κ²°
- μ μ (-.->): μ‘°κ±΄λΆ λΆκΈ° (μνμ λ°λΌ κ²°μ )
- ReAct μ¬μ΄ν΄: Think β Act β Observe μν ꡬ쑰
- μ μ μ’
λ£:
generate_responseλλreact_finalizeμμ λͺ μμ μΌλ‘ ENDλ‘ μ°κ²° - λΉμ μ μ’
λ£: κ° λ
Έλμμ
__end__λ‘ μ§μ μ°κ²°λλ κ²½μ°λ μμΈ μν©μ΄λ μ€λ₯ λ°μ μμ μμ μ₯μΉ
μ΄ μμ€ν μ μμ²μ 볡μ‘μ±μ λ°λΌ λ κ°μ§ λ€λ₯Έ λͺ¨λλ‘ λμν©λλ€:
- λ¨μν μμ²: "μμΈ λ μ¨ μλ €μ€", "νμΌ λͺ©λ‘ 보μ¬μ€"
- μ§μ μ²λ¦¬: LLMμ΄ λ°λ‘ μ μ ν λꡬλ₯Ό μ ννμ¬ ν λ²μ νΈμΆ
- λΉ λ₯Έ μλ΅: μ΅μνμ λ¨κ³λ‘ μ¦μ κ²°κ³Ό μ 곡
- μμ: λ¨μΌ μμΉ λ μ¨ μ‘°ν, νΉμ νμΌ μ 보 νμΈ
- 볡μ‘ν μμ²: "μμΈ, λΆμ°, λꡬ λ μ¨λ₯Ό λΉκ΅ν΄μ€"
- λ¨κ³λ³ μ¬κ³ : Think β Act β Observe μ¬μ΄ν΄ λ°λ³΅
- λ€μ€ λꡬ νΈμΆ: μ¬λ¬ λꡬλ₯Ό μμ°¨μ μΌλ‘ μ¬μ©νμ¬ μ 보 μμ§
- μ’ ν© λΆμ: μμ§λ μ 보λ₯Ό λ°νμΌλ‘ μ΅μ’ λ΅λ³ μμ±
- μμ: μ¬λ¬ μμΉ λΉκ΅, 볡ν©μ μΈ λΆμ μμ
μ΄ μμ€ν μ μ€μκ° ν ν° λ¨μ μ€νΈλ¦¬λ°μ ν΅ν΄ μμ°μ€λ¬μ΄ λν κ²½νμ μ 곡ν©λλ€:
- μ¦μ μλ΅ μμ: μ¬μ©μ λ©μμ§ μ μ‘ μ¦μ AI μλ΅μ΄ ν ν° λ¨μλ‘ μ€νΈλ¦¬λ° μμ
- μ€μκ° νμ΄ν ν¨κ³Ό: λ§μΉ AIκ° μ€μκ°μΌλ‘ νμ΄ννλ κ²μ²λΌ μμ°μ€λ¬μ΄ μλ΅ νμ
- μ€κ° μν νμ: ReAct ν¨ν΄μμ "μκ° μ€...", "λꡬ νΈμΆ μ€..." λ±μ μ€κ° μν μ€μκ° νμ
- μλ¬ μ²λ¦¬: μ°κ²° λκΉμ΄λ μ€λ₯ λ°μ μ μλ μ¬μ°κ²° λ° λ³΅κ΅¬
// ν΄λΌμ΄μΈνΈ μΈ‘ SSE μ°κ²°
const eventSource = new EventSource(`/api/stream/${sessionId}`);
eventSource.onmessage = function(event) {
const data = JSON.parse(event.data);
// μ€μκ° UI μ
λ°μ΄νΈ
};# μλ² μΈ‘ μ€νΈλ¦¬λ° μλ΅
async def stream_response():
async for chunk in workflow_stream:
yield f"data: {json.dumps(chunk)}\n\n"- μΈμ κΈ°λ° λ©λͺ¨λ¦¬: κ° μ¬μ©μ μΈμ λ³λ‘ λ 립μ μΈ λν νμ€ν 리 κ΄λ¦¬
- μ₯κΈ° 컨ν μ€νΈ: μ΄μ λν λ΄μ©μ κΈ°λ°μΌλ‘ ν μ°μμ μΈ λν μ§μ
- λꡬ νΈμΆ κΈ°λ‘: μ΄μ μ μ¬μ©ν λꡬμ κ²°κ³Όλ₯Ό κΈ°μ΅νμ¬ μ€λ³΅ νΈμΆ λ°©μ§
# λν μμ
μ¬μ©μ: "μμΈ λ μ¨ μλ €μ€"
AI: "μμΈμ νμ¬ λ μ¨λ λ§μ, κΈ°μ¨ 15Β°Cμ
λλ€."
μ¬μ©μ: "κ·ΈλΌ λΆμ°μ μ΄λ?" # μ΄μ 컨ν
μ€νΈ(λ μ¨ μ‘°ν) μλ μΈμ
AI: "λΆμ°μ νμ¬ λ μ¨λ₯Ό νμΈν΄λλ¦¬κ² μ΅λλ€..."
μ¬μ©μ: "λ λμ λΉκ΅ν΄μ€" # μμΈκ³Ό λΆμ° λ μ¨ λ°μ΄ν° νμ©
AI: "μμΈκ³Ό λΆμ°μ λ μ¨λ₯Ό λΉκ΅νλ©΄..."- λλͺ μ¬ μ²λ¦¬: "κ·Έκ²", "μ κΈ°", "μ΄μ κ²°κ³Ό" λ±μ λͺ¨νΈν μ°Έμ‘°λ₯Ό 컨ν μ€νΈ κΈ°λ°μΌλ‘ ν΄κ²°
- μμμ μμ²: λͺ μμ μΌλ‘ μΈκΈνμ§ μμλ μ΄μ λν νλ¦μ λ°νμΌλ‘ μλ νμ
- μ°κ΄ μ 보 νμ©: μ΄μ λꡬ νΈμΆ κ²°κ³Όλ₯Ό μλ‘μ΄ μμ²μ μλ νμ©
class ConversationSession:
"""Multi-turn λνλ₯Ό μν μΈμ
κ΄λ¦¬ ν΄λμ€"""
def __init__(self, session_id: str):
self.session_id = session_id
self.messages: List[BaseMessage] = [] # λν νμ€ν 리
self.tool_results: Dict[str, Any] = {} # λꡬ νΈμΆ κ²°κ³Ό μΊμ
self.context_summary: str = "" # μ₯κΈ° 컨ν
μ€νΈ μμ½
async def add_message(self, message: BaseMessage):
"""μ λ©μμ§ μΆκ° λ° μ»¨ν
μ€νΈ μ
λ°μ΄νΈ"""
self.messages.append(message)
await self._update_context()
async def get_relevant_context(self, query: str) -> str:
"""νμ¬ μΏΌλ¦¬μ κ΄λ ¨λ 컨ν
μ€νΈ μΆμΆ"""
# μλ―Έμ μ μ¬λ κΈ°λ° κ΄λ ¨ λν μΆμΆ
# μ΅κ·Ό Nκ° λ©μμ§ + κ΄λ ¨μ± λμ μ΄μ λν- ν ν° ν¨μ¨μ±: μ€μν μ λ³΄λ§ μ λ³νμ¬ μ»¨ν μ€νΈ μλμ° μ΅μ νμ©
- μμ½ κΈ°λ₯: κΈ΄ λνλ μλ μμ½νμ¬ ν΅μ¬ μ λ³΄λ§ μ μ§
- κ΄λ ¨μ± νν°λ§: νμ¬ μμ²κ³Ό κ΄λ ¨ μλ μ΄μ λνλ μ μΈ
- μ€μκ° νμ΄ν νμ: AI μλ΅ μμ± μ€ νμ΄ν μΈλμΌμ΄ν° νμ
- λ©μμ§ μν νμ: μ μ‘ μ€, μ²λ¦¬ μ€, μλ£ μν μκ°μ νμ
- μ€ν¬λ‘€ μλ μΆμ : μ λ©μμ§ λμ°© μ μλ μ€ν¬λ‘€
- μ°κ²° μν λͺ¨λν°λ§: SSE μ°κ²° μν μ€μκ° νμ
- λ¨κ³λ³ μ§ν νμ: ReAct ν¨ν΄μ Think β Act β Observe λ¨κ³ μ€μκ° νμ
- λꡬ νΈμΆ μκ°ν: μ΄λ€ MCP μλ²μ μ΄λ€ λκ΅¬κ° νΈμΆλλμ§ μ€μκ° νμ
- μ²λ¦¬ μκ° νμ: κ° λ¨κ³λ³ μμ μκ° μΈ‘μ λ° νμ
git clone <repository-url>
cd MCP_test# κ°λ° νκ²½ μ€μ (μμ‘΄μ± μ€μΉ + λλ ν 리 μμ±)
make dev
# νκ²½λ³μ μ€μ
cp .env_example .env
# .env νμΌμμ OPENAI_API_KEY μ€μ # κ°λ° λͺ¨λλ‘ μ€ν
make server
# λλ λ°±κ·ΈλΌμ΄λμμ μ€ν
make run-bgλΈλΌμ°μ μμ http://localhost:8000μΌλ‘ μ μνμ¬ AIμ λνλ₯Ό μμνμΈμ!
- Python 3.11+
- Node.js 18+ (Context7 MCP μλ²μ©)
- OpenAI API ν€
- uv (Python ν¨ν€μ§ κ΄λ¦¬μ)
- LangChain MCP Adapters (
langchain_mcp_adapters)
-
Python λ° Node.js νκ²½ μ€λΉ
# Python 3.11+ νμΈ python --version # Node.js 18+ νμΈ (Context7 μλ²μ©) node --version npm --version # uv μ€μΉ (μλ κ²½μ°) curl -LsSf https://astral.sh/uv/install.sh | sh
-
νλ‘μ νΈ μ€μ
# μμ‘΄μ± μ€μΉ make install # λλ μ§μ μ€μΉ uv pip install -r requirements.txt
-
νκ²½λ³μ μ€μ
cp .env_example .env
.envνμΌμμ λ€μ κ°λ€μ μ€μ νμΈμ:OPENAI_API_KEY=your_openai_api_key_here OPENAI_MODEL=gpt-4o-mini OPENAI_TEMPERATURE=0.1 OPENAI_MAX_TOKENS=1000 MCP_SERVERS_CONFIG=./mcp_servers.json # Phoenix λͺ¨λν°λ§ (μ νμ ) PHOENIX_ENABLED=true
- μλ² μ€ν:
make server - λΈλΌμ°μ μμ
http://localhost:8000μ μ - μ±ν μ°½μ λ©μμ§ μ λ ₯
- AIμ μ€μκ° μλ΅ νμΈ
- λ μ¨ μ‘°ν: "μμΈ λ μ¨ μλ €μ€" (λλ―Έ λ μ¨ μλ²)
- νμΌ κ΄λ¦¬: "νμΌ λͺ©λ‘ 보μ¬μ€" (λλ―Έ νμΌ μλ²)
- λ¬Έμ κ²μ: "React λΌμ΄λΈλ¬λ¦¬ μ 보 μ°Ύμμ€" (Context7 μ€μ μλ²)
- μμ€ν μ 보: "μλ² μν νμΈν΄μ€", "μ¬μ© κ°λ₯ν λꡬ λͺ©λ‘"
- λ³΅ν© λ μ¨ λΆμ: "μμΈ, λΆμ°, λꡬ λ μ¨λ₯Ό λΉκ΅ν΄μ€"
- λ€μ€ νμΌ μμ : "νλ‘μ νΈ νμΌλ€μ λΆμνκ³ κ΅¬μ‘°λ₯Ό μ€λͺ ν΄μ€"
- μ’ ν© μ 보 μμ§: "Reactμ Vue.js λΌμ΄λΈλ¬λ¦¬λ₯Ό λΉκ΅ λΆμν΄μ€"
μ΄ κ΅μ‘μ© λ°λͺ¨λ νμ΅ λ¨κ³λ³λ‘ μ€κ³λ 3κ°μ MCP μλ²λ‘ ꡬμ±λμ΄ μμ΅λλ€:
λͺ©μ : MCP νλ‘ν μ½μ κΈ°λ³Έ λμ μ리λ₯Ό μ΄ν΄νκΈ° μν λ¨μνλ μλ²
-
Weather Server (
examples/dummy_weather_server.py)- νμ΅ ν¬μΈνΈ: κΈ°λ³Έμ μΈ MCP μλ² κ΅¬ν λ°©λ², JSON-RPC ν΅μ
- κ°λ¨ν λ μ¨ μ 보 μ 곡 (νλμ½λ©λ λλ―Έ λ°μ΄ν°)
- λꡬ:
get_weather,get_forecast - κ΅μ‘μ κ°μΉ: FastMCP λΌμ΄λΈλ¬λ¦¬ μ¬μ©λ², λꡬ μ μ λ° κ΅¬ν
-
File Manager (
examples/dummy_file_server.py)- νμ΅ ν¬μΈνΈ: νμΌ μμ€ν μ°λ, 보μ κ³ λ €μ¬ν (μ½κΈ° μ μ©)
- κΈ°λ³Έμ μΈ νμΌ κ΄λ¦¬ κΈ°λ₯ (μμ ν μ½κΈ° μ μ© λͺ¨λ)
- λꡬ:
list_files,read_file,file_info - κ΅μ‘μ κ°μΉ: νκ²½λ³μ νμ©, μμ ν νμΌ μ κ·Ό ν¨ν΄
λͺ©μ : μ€μ μ΄μ νκ²½μμμ MCP μλ² νμ© μ¬λ‘ 체ν
- Context7 (NPM ν¨ν€μ§:
@upstash/context7-mcp)- νμ΅ ν¬μΈνΈ: μΈλΆ MCP μλ² μ°λ, μ€μ λ°μ΄ν° μ²λ¦¬
- μ€μ λΌμ΄λΈλ¬λ¦¬ λ¬Έμ κ²μ λ° μ 보 μ 곡
- μ¨λΌμΈ λ¬Έμ λ°μ΄ν°λ² μ΄μ€ μ°λ
- λꡬ: λΌμ΄λΈλ¬λ¦¬ κ²μ, λ¬Έμ μ‘°ν λ±
- κ΅μ‘μ κ°μΉ: μλνν° MCP μλ² νμ©, μ€μ API μ°λ ν¨ν΄
- Weather Server: MCP κΈ°λ³Έ κ°λ μ΄ν΄
- File Manager: λ‘컬 리μμ€ μ°λ λ°©λ² νμ΅
- Context7: μ€μ μΈλΆ μλΉμ€ μ°λ 체ν
- λ³΅ν© μλ리μ€: μ¬λ¬ μλ²λ₯Ό μ‘°ν©ν ReAct ν¨ν΄ μ€μ΅
π‘ μν¬νλ‘μ° λμ λ°©μ: μμ μν¬νλ‘μ° κ·Έλνμμ λ³Ό μ μλ―μ΄, 볡μ‘ν μμ²μ ReAct ν¨ν΄μ ν΅ν΄ λ¨κ³λ³λ‘ μ²λ¦¬λλ©°, κ° MCP μλ²μ λꡬλ€μ΄ νμμ λ°λΌ νΈμΆλ©λλ€.
curl -X POST "http://localhost:8000/api/chat" \
-H "Content-Type: application/json" \
-d '{"message": "μλ
νμΈμ", "session_id": "test-session"}'μλ΅ νμ:
{
"status": "success",
"session_id": "test-session",
"message_id": "msg_123",
"timestamp": "2024-01-01T12:00:00Z"
}μ€μκ° AI μλ΅μ λ°κΈ° μν Server-Sent Events μλν¬μΈνΈ:
// κΈ°λ³Έ SSE μ°κ²°
const eventSource = new EventSource('/api/stream/test-session');
// μ΄λ²€νΈ 리μ€λ μ€μ
eventSource.onmessage = function(event) {
const data = JSON.parse(event.data);
handleStreamData(data);
};
eventSource.onerror = function(event) {
console.error('SSE μ°κ²° μ€λ₯:', event);
// μλ μ¬μ°κ²° λ‘μ§
};μ€νΈλ¦¬λ° λ°μ΄ν° νμ:
{
"type": "token", // token, status, tool_call, error
"content": "μλ
νμΈμ", // μ€μ ν ν° λ΄μ©
"session_id": "test-session",
"message_id": "msg_123",
"timestamp": "2024-01-01T12:00:00.123Z",
"metadata": {
"node": "llm_response", // νμ¬ μ€ν μ€μΈ μν¬νλ‘μ° λ
Έλ
"step": 1, // ReAct ν¨ν΄μ λ¨κ³ λ²νΈ
"total_tokens": 150 // λμ ν ν° μ
}
}| νμ | μ€λͺ | μμ |
|---|---|---|
token |
AI μλ΅ ν ν° | {"type": "token", "content": "μλ
"} |
status |
μ²λ¦¬ μν μ λ°μ΄νΈ | {"type": "status", "content": "λꡬ νΈμΆ μ€..."} |
tool_call |
MCP λꡬ νΈμΆ μ 보 | {"type": "tool_call", "tool": "get_weather", "args": {...}} |
tool_result |
λꡬ μ€ν κ²°κ³Ό | {"type": "tool_result", "result": {...}} |
thinking |
ReAct μ¬κ³ κ³Όμ | {"type": "thinking", "content": "λ μ¨ μ 보λ₯Ό νμΈν΄μΌκ² λ€"} |
error |
μ€λ₯ λ°μ | {"type": "error", "message": "μ°κ²° μ€ν¨"} |
complete |
μλ΅ μλ£ | {"type": "complete", "final_response": "..."} |
class MCPChatClient {
constructor(sessionId) {
this.sessionId = sessionId;
this.eventSource = null;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
}
connect() {
this.eventSource = new EventSource(`/api/stream/${this.sessionId}`);
this.eventSource.onopen = () => {
console.log('SSE μ°κ²° μ±κ³΅');
this.reconnectAttempts = 0;
};
this.eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
this.handleMessage(data);
};
this.eventSource.onerror = () => {
this.handleReconnect();
};
}
handleMessage(data) {
switch(data.type) {
case 'token':
this.appendToken(data.content);
break;
case 'status':
this.updateStatus(data.content);
break;
case 'tool_call':
this.showToolCall(data.tool, data.args);
break;
case 'thinking':
this.showThinking(data.content);
break;
case 'complete':
this.onComplete(data.final_response);
break;
case 'error':
this.handleError(data.message);
break;
}
}
handleReconnect() {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
setTimeout(() => {
this.reconnectAttempts++;
this.connect();
}, 1000 * Math.pow(2, this.reconnectAttempts)); // μ§μ λ°±μ€ν
}
}
}# μ μΈμ
μμ±
curl -X POST "http://localhost:8000/api/sessions" \
-H "Content-Type: application/json" \
-d '{"user_id": "user123"}'
# μΈμ
μ 보 μ‘°ν
curl "http://localhost:8000/api/sessions/test-session"
# μΈμ
νμ€ν 리 μ‘°ν
curl "http://localhost:8000/api/sessions/test-session/history"
# μΈμ
μμ
curl -X DELETE "http://localhost:8000/api/sessions/test-session"# MCP μλ² μν νμΈ
curl "http://localhost:8000/api/status/mcp-servers"
# μ¬μ© κ°λ₯ν λꡬ λͺ©λ‘
curl "http://localhost:8000/api/tools"
# μμ€ν
ν¬μ€μ²΄ν¬
curl "http://localhost:8000/health"MCP_test/
βββ mcp_host/ # λ©μΈ ν¨ν€μ§
β βββ adapters/ # MCP ν΄λΌμ΄μΈνΈ μ΄λν°
β β βββ client.py # langchain_mcp_adapters κΈ°λ° ν΄λΌμ΄μΈνΈ
β βββ config/ # μ€μ κ΄λ¦¬
β βββ models/ # λ°μ΄ν° λͺ¨λΈ
β βββ services/ # λΉμ¦λμ€ λ‘μ§
β βββ sessions/ # μΈμ
κ΄λ¦¬
β βββ streaming/ # SSE μ€νΈλ¦¬λ°
β βββ visualize/ # μκ°ν λꡬ
β βββ workflows/ # LangGraph μν¬νλ‘μ°
β βββ nodes.py # μν¬νλ‘μ° λ
Έλ
β βββ llm_nodes.py # LLM κΈ°λ° λ
Έλ
β βββ react_nodes.py # ReAct ν¨ν΄ λ
Έλ
β βββ graph.py # μν¬νλ‘μ° κ·Έλν
βββ examples/ # MCP μλ² μμ
β βββ dummy_weather_server.py # λλ―Έ λ μ¨ μλ²
β βββ dummy_file_server.py # λλ―Έ νμΌ κ΄λ¦¬ μλ²
βββ static/ # μ μ νμΌ (CSS, JS)
βββ logs/ # λ‘κ·Έ νμΌ
βββ tests/ # ν
μ€νΈ μ½λ
βββ main.py # μλ² μ§μ
μ
βββ mcp_servers.json # MCP μλ² μ€μ (3κ° μλ²)
βββ Makefile # κ°λ° λꡬ
βββ requirements.txt # μμ‘΄μ±
# ν
μ€νΈ
make test-basic # κΈ°λ³Έ ν΅ν© ν
μ€νΈ
make test-pytest # μ 체 pytest μ€ν
# μλ² κ΄λ¦¬
make server # κ°λ° λͺ¨λ μ€ν
make run-bg # λ°±κ·ΈλΌμ΄λ μ€ν
make stop-bg # λ°±κ·ΈλΌμ΄λ μλ² μ€μ§
make status # μλ² μν νμΈ
make logs # λ‘κ·Έ νμΈ
# μ½λ νμ§
make check # μ 체 νμ§ κ²μ¬
make format # μ½λ ν¬λ§·ν
(Black)
make lint # λ¦°νΈ κ²μ¬ (Flake8)
# μ μ§λ³΄μ
make clean # μΊμ μ 리
make dev # κ°λ° νκ²½ μ€μ # λΉ λ₯Έ κΈ°λ³Έ ν
μ€νΈ
make test-basic
# μ 체 ν
μ€νΈ (pytest νμ)
make test-pytest# μ½λ ν¬λ§·ν
make format
# λ¦°νΈ κ²μ¬
make lint
# μ 체 νμ§ κ²μ¬ (ν¬λ§·ν
+ λ¦°νΈ + ν
μ€νΈ)
make checkνμ¬ νλ‘μ νΈμμ μ¬μ©νλ μ€μ μ€μ :
{
"weather": {
"command": "python",
"args": ["/path/to/MCP_test/examples/dummy_weather_server.py"],
"transport": "stdio"
},
"file-manager": {
"command": "python",
"args": ["/path/to/MCP_test/examples/dummy_file_server.py"],
"transport": "stdio",
"env": {
"PYTHONPATH": "/path/to/MCP_test",
"SAFE_MODE": "true"
}
},
"context7": {
"command": "npx",
"args": [
"-y",
"@upstash/context7-mcp@latest"
],
"transport": "stdio"
}
}- λλ―Έ μλ²λ€: Python FastMCPλ‘ κ΅¬ν, λ‘컬 νμΌ μ€ν
- Context7: NPM ν¨ν€μ§λ‘ μ€μΉ, μ€μ μ¨λΌμΈ μλΉμ€ μ°λ
| λ³μλͺ | μ€λͺ | κΈ°λ³Έκ° | νμ |
|---|---|---|---|
OPENAI_API_KEY |
OpenAI API ν€ | - | β |
OPENAI_MODEL |
μ¬μ©ν GPT λͺ¨λΈ | gpt-4o-mini |
β |
OPENAI_TEMPERATURE |
λͺ¨λΈ μ°½μμ± (0.0-2.0) | 0.1 |
β |
OPENAI_MAX_TOKENS |
μ΅λ ν ν° μ | 1000 |
β |
MCP_SERVERS_CONFIG |
MCP μλ² μ€μ νμΌ κ²½λ‘ | ./mcp_servers.json |
β |
PHOENIX_ENABLED |
Phoenix λͺ¨λν°λ§ νμ±ν | false |
β |
Error: OpenAI API key not found
ν΄κ²°λ°©λ²: .env νμΌμ μ¬λ°λ₯Έ OPENAI_API_KEY μ€μ
Error: Failed to connect to MCP server
ν΄κ²°λ°©λ²:
mcp_servers.jsonμ€μ νμΈ (κ²½λ‘κ° μ λκ²½λ‘λ‘ μ€μ λμ΄ μλμ§)- λλ―Έ μλ²λ€: Python νμΌμ΄ μ‘΄μ¬νκ³ μ€ν κ°λ₯νμ§ νμΈ
- Context7 μλ²: Node.jsμ
npxλͺ λ Ήμ΄κ° μ¬μ© κ°λ₯νκ³ μΈν°λ· μ°κ²°μ΄ λμ΄ μλμ§ νμΈ langchain_mcp_adaptersν¨ν€μ§κ° μ¬λ°λ₯΄κ² μ€μΉλμλμ§ νμΈ
Error: Port 8000 already in use
ν΄κ²°λ°©λ²:
- κΈ°μ‘΄ νλ‘μΈμ€ μ’
λ£:
make stop-bg - λλ λ€λ₯Έ ν¬νΈ μ¬μ©:
PORT=8001 python main.py
ModuleNotFoundError: No module named 'xxx'
ν΄κ²°λ°©λ²: make install λλ uv pip install -r requirements.txt
Error: npx command not found
ν΄κ²°λ°©λ²:
- Node.js 18+ μ€μΉ: https://nodejs.org/
- macOS:
brew install node - Ubuntu:
sudo apt install nodejs npm - Windows: Node.js 곡μ μ€μΉ νλ‘κ·Έλ¨ μ¬μ©
Phoenix UI λ§ν¬κ° λΉνμ±νλμ΄ μμ
ν΄κ²°λ°©λ²:
.envνμΌμμPHOENIX_ENABLED=trueμ€μ νμΈ- μλ² μ¬μμ ν 첫 λ²μ§Έ λν μμ (λ§ν¬κ° μλ νμ±νλ¨)
- Phoenix κ΄λ ¨ ν¨ν€μ§ μ€μΉ:
uv pip install arize-phoenix openinference-instrumentation-langchain
# μ€μκ° λ‘κ·Έ νμΈ
make logs
# λλ μ§μ νμΈ
tail -f logs/server.log- μ μ₯μ ν¬ν¬
- λ‘컬μ ν΄λ‘ :
git clone <your-fork> - κ°λ° νκ²½ μ€μ :
make dev - λΈλμΉ μμ±:
git checkout -b feature/your-feature
- ν¬λ§·ν : Black (120μ μ ν)
- λ¦°νΈ: Flake8
- νμ ννΈ: λͺ¨λ ν¨μμ νμ ννΈ μΆκ°
- λ¬Έμν: Docstring μμ± (Google μ€νμΌ)
- μ½λ νμ§ κ²μ¬ ν΅κ³Ό:
make check - ν
μ€νΈ μΆκ° λ° ν΅κ³Ό:
make test-basic - λͺ νν μ»€λ° λ©μμ§ μμ±
- PR μ€λͺ μ λ³κ²½μ¬ν μμΈ κΈ°μ
μ΄ νλ‘μ νΈλ MIT λΌμ΄μ μ€ νμ λ°°ν¬λ©λλ€. μμΈν λ΄μ©μ LICENSE νμΌμ μ°Έμ‘°νμΈμ.
- Model Context Protocol 곡μ λ¬Έμ
- LangChain MCP Adapters
- LangGraph λ¬Έμ
- FastAPI λ¬Έμ
- OpenAI API λ¬Έμ
- Arize Phoenix λ¬Έμ - AI μ ν리μΌμ΄μ λͺ¨λν°λ§
- λ¨κ³λ³ νν 리μΌ: MCP νλ‘ν μ½ νμ΅μ μν κ°μ΄λ λͺ¨λ
- μ½λ μ€λͺ μ€λ²λ μ΄: μ€ν μ€μΈ MCP ν΅μ κ³Όμ μ μ½λμ ν¨κ» μ€λͺ
- μΈν°λν°λΈ λ¬Έμ: μ€μ λμνλ μμ μ ν¨κ»νλ MCP κ°μ΄λ
- μλ² μν μμΈ λ³΄κΈ°: νμ¬ 'μλ² 3κ° - λꡬ 7κ°' νμλ₯Ό ν΄λ¦νλ©΄ μμΈ μ 보 νμΈ
- κ° MCP μλ²λ³ μ°κ²° μν λ° μ΄κΈ°ν μ§ν μν©
- μλ²λ³ μ¬μ© κ°λ₯ν λꡬ λͺ©λ‘κ³Ό μ€λͺ
- μ€μκ° μλ² ν¬μ€μ²΄ν¬ λ° μλ΅ μκ°
- λκ΅¬λ³ νΈμΆ νμ λ° μ±κ³΅/μ€ν¨ ν΅κ³
- νμ΅ κ΄μ : MCP μλ² μλͺ μ£ΌκΈ°μ μν κ΄λ¦¬ μ΄ν΄
- ν΅ν© κ°λ°μ λ·°: μ±λ΄ νλ©΄κ³Ό λμΌν μΈν°νμ΄μ€μμ λ°μ΄ν° νλ¦ νμΈ
- JSON-RPC μμ²/μλ΅ μ€μκ° νμ: MCP νλ‘ν μ½ λ©μμ§ κ΅¬μ‘° νμ΅
- MCP νλ‘ν μ½ λ©μμ§ μΆμ : νμ€ λ©μμ§ νμκ³Ό νλ¦ μ΄ν΄
- LangGraph μν¬νλ‘μ° μν λ³ν μκ°ν: μν κΈ°λ° μν¬νλ‘μ° νμ΅
- ReAct ν¨ν΄ μ¬κ³ κ³Όμ λ¨κ³λ³ νμ: AI μΆλ‘ κ³Όμ μκ°ν
- ν ν° μ¬μ©λ λ° API νΈμΆ λΉμ© μΆμ : μ€μ μ΄μ λΉμ© μ΄ν΄
- Phoenix ν΅ν© κ°ν: νμ¬ λ³λ μ°½μμ μ 곡λλ Phoenix UIλ₯Ό λ©μΈ μΈν°νμ΄μ€μ ν΅ν©
- 컀μ€ν MCP μλ² μΆκ°: μΉ UIμμ μλ‘μ΄ μλ² μ€μ λ° ν μ€νΈ (νμ΅μκ° μ§μ MCP μλ² κ΅¬μ± μ²΄ν)
- μν¬νλ‘μ° μΆμ κΈ°λ₯: μ€ν μ€μΈ LangGraph λ Έλμ νλ‘μ°λ₯Ό μ€μκ°μΌλ‘ μΆμ λ° μκ°ν
- λν νμ€ν 리 κ΄λ¦¬: μΈμ λ³ λν μ μ₯ λ° κ²μ
- MCP μλ² κ°λ° λꡬ: μλ‘μ΄ MCP μλ² κ°λ°μ μν ν νλ¦Ώκ³Ό ν μ€νΈ λꡬ
Made with β€οΈ using LangGraph and MCP


