├── .dockerignore ├── .env.example ├── .gitignore ├── .streamlit └── config.toml ├── Dockerfile.node ├── README.md ├── assets ├── Add-Smithery-Tool.png ├── Add-Tools.png ├── List-Tools.png ├── MCP-Agents-TeddyFlow.png ├── MCP-Agents-Tools.png ├── Project-Overview.png ├── System-Prompt.png ├── Tools-Settings.png ├── smithery-copy-json.png ├── teddyflow-code.png ├── teddyflow-custom-logo.png ├── teddyflow-guide-01.png └── teddyflow-guide-02.png ├── docker-compose.yml ├── langgraph.json ├── mcp-config └── mcp_config.json ├── prompts └── system_prompt.yaml ├── pyproject.toml ├── requirements-streamlit.txt ├── resources └── mcp_server_time.py ├── streamlit.Dockerfile ├── toolsettings.py └── uv.lock /.dockerignore: -------------------------------------------------------------------------------- 1 | # Environment variables 2 | .env 3 | .env.* 4 | 5 | .langgraph_api/ 6 | .venv/ 7 | mcp-config/ 8 | prompts/ 9 | resources/ 10 | 11 | # Python 12 | __pycache__/ 13 | *.py[cod] 14 | *$py.class 15 | *.so 16 | .Python 17 | env/ 18 | build/ 19 | develop-eggs/ 20 | dist/ 21 | downloads/ 22 | eggs/ 23 | .eggs/ 24 | lib/ 25 | lib64/ 26 | parts/ 27 | sdist/ 28 | var/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | 33 | # Version control 34 | .git 35 | .gitignore 36 | .github 37 | 38 | # IDE 39 | .idea/ 40 | .vscode/ 41 | *.swp 42 | *.swo 43 | 44 | # OS specific 45 | .DS_Store 46 | Thumbs.db 47 | 48 | # Testing 49 | .coverage 50 | htmlcov/ 51 | .pytest_cache/ 52 | .tox/ 53 | 54 | # Virtual environments 55 | venv/ 56 | .venv/ 57 | ENV/ 58 | 59 | # Logs 60 | *.log 61 | logs/ 62 | 63 | # Node modules (if any) 64 | node_modules/ 65 | 66 | # Docker 67 | Dockerfile* 68 | docker-compose* -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | OPENAI_API_KEY=your_openai_api_key 2 | AZURE_OPENAI_API_KEY=your_azure_openai_api_key 3 | AZURE_OPENAI_ENDPOINT=your_azure_openai_endpoint 4 | ANTHROPIC_API_KEY=your_anthropic_api_key 5 | 6 | LANGSMITH_TRACING=true 7 | LANGSMITH_ENDPOINT=https://api.smith.langchain.com 8 | LANGSMITH_API_KEY=your_langsmith_api_key 9 | LANGSMITH_PROJECT=LangGraph-Dynamic-MCP-Chat 10 | 11 | LLM_PROVIDER=OPENAI 12 | LLM_MODEL_NAME=gpt-4.1 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .langgraph_api/ 2 | .venv/ 3 | .streamlit/secrets.toml 4 | 5 | .env 6 | *.egg-info/ 7 | *.pyc 8 | .DS_Store -------------------------------------------------------------------------------- /.streamlit/config.toml: -------------------------------------------------------------------------------- 1 | [client] 2 | showErrorDetails = "none" 3 | 4 | [theme] 5 | base="light" 6 | primaryColor="navy" 7 | backgroundColor="#FFFFFF" 8 | secondaryBackgroundColor="#F0F2F6" 9 | textColor="#31333F" 10 | font="sans serif" 11 | 12 | [server] 13 | port = 2025 -------------------------------------------------------------------------------- /Dockerfile.node: -------------------------------------------------------------------------------- 1 | FROM teddylee777/langgraph-dynamic-mcp-agents:latest 2 | 3 | # Install Node.js and npm 4 | RUN apt-get update && \ 5 | apt-get install -y curl && \ 6 | curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \ 7 | apt-get install -y nodejs && \ 8 | npm install -g npm@latest 9 | 10 | # Verify installation 11 | RUN node --version && npm --version -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LangGraph Dynamic MCP Agents 2 | 3 | ![Python](https://img.shields.io/badge/Python-3.11+-blue.svg) 4 | ![LangChain](https://img.shields.io/badge/LangChain-0.3.23+-green.svg) 5 | ![LangGraph](https://img.shields.io/badge/LangGraph-0.3.25+-orange.svg) 6 | [![Open in - LangGraph Studio](https://img.shields.io/badge/Open_in-LangGraph_Studio-00324d.svg?logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NS4zMzMiIGhlaWdodD0iODUuMzMzIiB2ZXJzaW9uPSIxLjAiIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHBhdGggZD0iTTEzIDcuOGMtNi4zIDMuMS03LjEgNi4zLTYuOCAyNS43LjQgMjQuNi4zIDI0LjUgMjUuOSAyNC41QzU3LjUgNTggNTggNTcuNSA1OCAzMi4zIDU4IDcuMyA1Ni43IDYgMzIgNmMtMTIuOCAwLTE2LjEuMy0xOSAxLjhtMzcuNiAxNi42YzIuOCAyLjggMy40IDQuMiAzLjQgNy42cy0uNiA0LjgtMy40IDcuNkw0Ny4yIDQzSDE2LjhsLTMuNC0zLjRjLTQuOC00LjgtNC44LTEwLjQgMC0xNS4ybDMuNC0zLjRoMzAuNHoiLz48cGF0aCBkPSJNMTguOSAyNS42Yy0xLjEgMS4zLTEgMS43LjQgMi41LjkuNiAxLjcgMS44IDEuNyAyLjcgMCAxIC43IDIuOCAxLjYgNC4xIDEuNCAxLjkgMS40IDIuNS4zIDMuMi0xIC42LS42LjkgMS40LjkgMS41IDAgMi43LS41IDIuNy0xIDAtLjYgMS4xLS44IDIuNi0uNGwyLjYuNy0xLjgtMi45Yy01LjktOS4zLTkuNC0xMi4zLTExLjUtOS44TTM5IDI2YzAgMS4xLS45IDIuNS0yIDMuMi0yLjQgMS41LTIuNiAzLjQtLjUgNC4yLjguMyAyIDEuNyAyLjUgMy4xLjYgMS41IDEuNCAyLjMgMiAyIDEuNS0uOSAxLjItMy41LS40LTMuNS0yLjEgMC0yLjgtMi44LS44LTMuMyAxLjYtLjQgMS42LS41IDAtLjYtMS4xLS4xLTEuNS0uNi0xLjItMS42LjctMS43IDMuMy0yLjEgMy41LS41LjEuNS4yIDEuNi4zIDIuMiAwIC43LjkgMS40IDEuOSAxLjYgMi4xLjQgMi4zLTIuMy4yLTMuMi0uOC0uMy0yLTEuNy0yLjUtMy4xLTEuMS0zLTMtMy4zLTMtLjUiLz48L3N2Zz4=)](https://langgraph-studio.vercel.app/templates/open?githubUrl=https://github.com/langchain-ai/react-agent) 7 | 8 | ## 프로젝트 개요 9 | 10 | > 채팅 인터페이스 11 | 12 | ![Project Overview](./assets/Project-Overview.png) 13 | 14 | `LangGraph Dynamic MCP Agents` 은 Model Context Protocol(MCP)을 통해 다양한 외부 도구와 데이터 소스에 접근할 수 있는 ReAct 에이전트를 구현한 프로젝트입니다. 이 프로젝트는 LangGraph 의 ReAct 에이전트를 기반으로 하며, MCP 도구를 쉽게 추가하고 구성할 수 있는 인터페이스를 제공합니다. 15 | 16 | ![Project Demo](./assets/MCP-Agents-TeddyFlow.png) 17 | 18 | ### 주요 기능 19 | 20 | **동적 방식으로 도구 설정 대시보드** 21 | 22 | `http://localhost:2025` 에 접속하여 도구 설정 대시보드를 확인할 수 있습니다. 23 | 24 | ![Tool Settings](./assets/Tools-Settings.png) 25 | 26 | **도구 추가** 탭에서 [Smithery](https://smithery.io) 에서 사용할 MCP 도구의 JSON 구성을 복사 붙여넣기 하여 도구를 추가할 수 있습니다. 27 | 28 | ![Tool Settings](./assets/Add-Tools.png) 29 | 30 | ---- 31 | 32 | **실시간 반영** 33 | 34 | 도구 설정 대시보드에서 도구를 추가하거나 수정하면 실시간으로 반영됩니다. 35 | 36 | ![List Tools](./assets/List-Tools.png) 37 | 38 | **시스템 프롬프트 설정** 39 | 40 | `prompts/system_prompt.yaml` 파일을 수정하여 시스템 프롬프트를 설정할 수 있습니다. 41 | 42 | 이 또한 동적으로 바로 반영되는 형태입니다. 43 | 44 | ![System Prompt](./assets/System-Prompt.png) 45 | 46 | 만약, 에이전트에 설정되는 시스템프롬프트를 수정하고 싶다면 `prompts/system_prompt.yaml` 파일의 내용을 수정하면 됩니다. 47 | 48 | ---- 49 | 50 | ### 주요 기능 51 | 52 | * **LangGraph ReAct 에이전트**: LangGraph를 기반으로 하는 ReAct 에이전트 53 | * **실시간 동적 도구 관리**: MCP 도구를 쉽게 추가, 제거, 구성 가능 (Smithery JSON 형식 지원) 54 | * **실시간 동적 시스템 프롬프트 설정**: 시스템 프롬프트를 쉽게 수정 가능 (동적 반영) 55 | * **대화 기록**: 에이전트와의 대화 내용 추적 및 관리 56 | * **TeddyFlow 연동**: 채팅 인터페이스 연동 57 | * **Docker 이미지 빌드**: Docker 이미지 빌드 가능 58 | * **localhost 지원**: localhost 로 실행 가능(채팅 인터페이스 연동 가능) 59 | 60 | ## 설치 방법 61 | 62 | 1. 저장소 복제하기 63 | 64 | ```bash 65 | git clone https://github.com/teddynote-lab/langgraph-dynamic-mcp-agents 66 | cd langgraph-dynamic-mcp-agents 67 | ``` 68 | 69 | 2. `.env` 파일 설정하기 70 | 71 | `.env.example` 파일을 `.env`로 복사하고 필요한 API 키를 추가합니다. 72 | 73 | ```bash 74 | cp .env.example .env 75 | ``` 76 | 77 | `.env` 파일에서 `LLM_PROVIDER` 를 설정합니다. 78 | 79 | 선택 가능(택 1): `ANTHROPIC`, `OPENAI`, `AZURE_OPENAI` 80 | 81 | ``` 82 | LLM_PROVIDER=AZURE_OPENAI 83 | ``` 84 | 85 | 아래는 필요한 API 키 목록입니다. (선택한 `LLM_PROVIDER` 에 따라 설정합니다) 86 | 87 | `Anthropic`, `OpenAI`, `Azure OpenAI` 에서 사용할 API 키를 설정합니다.(반드시 하나의 모델은 설정되어야 합니다.) 88 | 89 | - `ANTHROPIC_API_KEY`: Anthropic API 키 90 | - `OPENAI_API_KEY`: OpenAI API 키 91 | - `AZURE_OPENAI_API_KEY`: Azure OpenAI API 키 92 | - `AZURE_OPENAI_ENDPOINT`: Azure OpenAI 엔드포인트 93 | 94 | 95 | 3. MCP 도구 설정 96 | 97 | `mcp-config` 폴더에 있는 `mcp_config.json` 파일을 기준으로 모델이 사용할 MCP 도구를 설정합니다. 98 | 99 | 따라서, 미리 사용하고자 하는 MCP 도구를 JSON 형식으로 설정해 둘 수 있습니다. 100 | 이 과정은 도구 설정 대시보드에서도 설정이 가능합니다. 101 | 102 | 아래는 샘플로 작성된 예시입니다. 103 | 104 | ```json 105 | { 106 | "mcpServers": { 107 | "perplexity-search": { 108 | "command": "npx", 109 | "args": [ 110 | "-y", 111 | "@smithery/cli@latest", 112 | "run", 113 | "@arjunkmrm/perplexity-search", 114 | "--key", 115 | "SMITHERY_API_KEY 를 입력하세요" 116 | ], 117 | "transport": "stdio" 118 | }, 119 | "get_current_time": { 120 | "command": "python", 121 | "args": [ 122 | "/app/resources/mcp_server_time.py" 123 | ], 124 | "transport": "stdio" 125 | } 126 | } 127 | } 128 | ``` 129 | 130 | 4. .py 파일을 MCP `stdio` 서버로 추가 131 | 132 | - (참고) `resources` 폴더에 있는 `mcp_server_time.py` 파일을 참고하시기 바랍니다. 133 | 134 | 1. 사용하고자 하는 커스텀 작성된 .py 파일을 `resources` 폴더에 추가합니다. 그리고 `stdio` 서버로 실행할 수 있도록 코드를 작성합니다. 135 | 136 | 2. `mcp-config/mcp_config.json` 에 추가할 때 파일 경로를 수정합니다. 137 | 138 | **규칙** 139 | 140 | `./resources/파일명.py` > `/app/resources/파일명.py` 141 | 142 | 예를 들어, `./resources/mcp_server_time.py` 파일을 추가하고자 한다면 `/app/resources/mcp_server_time.py` 로 설정합니다. 143 | 144 | ```json 145 | "get_current_time": { 146 | "command": "python", 147 | "args": [ 148 | "/app/resources/mcp_server_time.py" 149 | ], 150 | "transport": "stdio" 151 | } 152 | ``` 153 | 154 | 6. Smithery 에 등록된 도구 추가 155 | 156 | [Smithery](https://smithery.ai/) 에서 사용할 MCP 도구의 JSON 구성을 가져와 도구 대시보드에서 쉽게 추가할 수 있습니다. 157 | 158 | 1. [Smithery](https://smithery.io) 웹사이트를 방문하여 사용하고 싶은 도구를 선택합니다. 159 | 2. 도구 페이지에서 오른쪽의 'COPY' 버튼을 클릭하여 JSON 구성을 복사합니다. 160 | 161 | ![Smithery Copy JSON](./assets/smithery-copy-json.png) 162 | 163 | 3. `mcp_config.json` 파일을 열고 복사한 JSON을 추가합니다. 164 | 165 | > 복사한 내용을 붙여넣기 합니다. 166 | 167 | ![Add Smithery Tool](./assets/Add-Smithery-Tool.png) 168 | 169 | ## 애플리케이션 실행 170 | 171 | 모든 설정이 완료되었다면, 다음 명령어로 실행할 수 있습니다. 172 | 173 | > Windows(PowerShell) 174 | 175 | ```bash 176 | docker compose build --no-cache; docker-compose up -d 177 | ``` 178 | 179 | > Mac / Linux 180 | 181 | ```bash 182 | docker compose build --no-cache && docker-compose up -d 183 | ``` 184 | 185 | **접속 주소** 186 | 187 | - TeddyFlow 연동: https://teddyflow.com/ 188 | - 채팅 인터페이스: `http://localhost:2024` 189 | - 도구 설정 대시보드: `http://localhost:2025` 190 | 191 | ## teddyflow.com 연결 방법 192 | 193 | 194 | 1. teddyflow.com 에서 회원가입을 합니다. 195 | 196 | 회원가입시 "베타 키" 에 `teddynote-youtube` 를 입력하면 승인 없이 바로 가입이 가능합니다. 197 | 198 | ![teddyflow-code](./assets/teddyflow-code.png) 199 | 200 | 2. 로그인 후 "새로운 앱 연결" 버튼을 클릭합니다. 201 | 202 | ![teddyflow-guide-01](./assets/teddyflow-guide-01.png) 203 | 204 | 3. 앱 이름을 입력하고 "연결" 버튼을 클릭합니다. 205 | 4. 탭에서 "LangGraph" 를 선택한 뒤 다음의 정보를 입력합니다. 206 | - Endpoint: `http://localhost:2024` 207 | - Graph: `agent` 208 | 209 | ![teddyflow-guide-02](./assets/teddyflow-guide-02.png) 210 | 211 | 5. 연결 설정이 완료되면 "저장" 버튼을 클릭합니다. 212 | 213 | 6. "앱 연결하기" 버튼을 눌러 저장합니다. 214 | 215 | ## 회사명 / 커뮤니티 로고 및 브랜딩 적용 216 | 217 | 회사명 / 커뮤니티를 위한 커스텀 기능을 출시하였습니다. 218 | 219 | ![teddyflow-company](./assets/teddyflow-custom-logo.png) 220 | 221 | 도입을 희망하신다면 service@brain-crew.com 으로 문의주시면 도움을 드리겠습니다. 222 | 223 | 224 | ## 라이센스 225 | 226 | Apache License 2.0 ([LICENSE](LICENSE)) -------------------------------------------------------------------------------- /assets/Add-Smithery-Tool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teddynote-lab/langgraph-dynamic-mcp-agents/8f08e45b864458c899ec3717e8843fc55b7b2973/assets/Add-Smithery-Tool.png -------------------------------------------------------------------------------- /assets/Add-Tools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teddynote-lab/langgraph-dynamic-mcp-agents/8f08e45b864458c899ec3717e8843fc55b7b2973/assets/Add-Tools.png -------------------------------------------------------------------------------- /assets/List-Tools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teddynote-lab/langgraph-dynamic-mcp-agents/8f08e45b864458c899ec3717e8843fc55b7b2973/assets/List-Tools.png -------------------------------------------------------------------------------- /assets/MCP-Agents-TeddyFlow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teddynote-lab/langgraph-dynamic-mcp-agents/8f08e45b864458c899ec3717e8843fc55b7b2973/assets/MCP-Agents-TeddyFlow.png -------------------------------------------------------------------------------- /assets/MCP-Agents-Tools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teddynote-lab/langgraph-dynamic-mcp-agents/8f08e45b864458c899ec3717e8843fc55b7b2973/assets/MCP-Agents-Tools.png -------------------------------------------------------------------------------- /assets/Project-Overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teddynote-lab/langgraph-dynamic-mcp-agents/8f08e45b864458c899ec3717e8843fc55b7b2973/assets/Project-Overview.png -------------------------------------------------------------------------------- /assets/System-Prompt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teddynote-lab/langgraph-dynamic-mcp-agents/8f08e45b864458c899ec3717e8843fc55b7b2973/assets/System-Prompt.png -------------------------------------------------------------------------------- /assets/Tools-Settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teddynote-lab/langgraph-dynamic-mcp-agents/8f08e45b864458c899ec3717e8843fc55b7b2973/assets/Tools-Settings.png -------------------------------------------------------------------------------- /assets/smithery-copy-json.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teddynote-lab/langgraph-dynamic-mcp-agents/8f08e45b864458c899ec3717e8843fc55b7b2973/assets/smithery-copy-json.png -------------------------------------------------------------------------------- /assets/teddyflow-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teddynote-lab/langgraph-dynamic-mcp-agents/8f08e45b864458c899ec3717e8843fc55b7b2973/assets/teddyflow-code.png -------------------------------------------------------------------------------- /assets/teddyflow-custom-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teddynote-lab/langgraph-dynamic-mcp-agents/8f08e45b864458c899ec3717e8843fc55b7b2973/assets/teddyflow-custom-logo.png -------------------------------------------------------------------------------- /assets/teddyflow-guide-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teddynote-lab/langgraph-dynamic-mcp-agents/8f08e45b864458c899ec3717e8843fc55b7b2973/assets/teddyflow-guide-01.png -------------------------------------------------------------------------------- /assets/teddyflow-guide-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teddynote-lab/langgraph-dynamic-mcp-agents/8f08e45b864458c899ec3717e8843fc55b7b2973/assets/teddyflow-guide-02.png -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | volumes: 2 | langgraph-data: 3 | driver: local 4 | mcp-configs: 5 | driver: local 6 | services: 7 | langgraph-redis: 8 | image: redis:6 9 | healthcheck: 10 | test: redis-cli ping 11 | interval: 5s 12 | timeout: 1s 13 | retries: 5 14 | langgraph-postgres: 15 | image: postgres:16 16 | ports: 17 | - "5433:5432" 18 | environment: 19 | POSTGRES_DB: postgres 20 | POSTGRES_USER: postgres 21 | POSTGRES_PASSWORD: postgres 22 | volumes: 23 | - langgraph-data:/var/lib/postgresql/data 24 | healthcheck: 25 | test: pg_isready -U postgres 26 | start_period: 10s 27 | timeout: 1s 28 | retries: 5 29 | interval: 5s 30 | langgraph-api: 31 | build: 32 | context: . 33 | dockerfile: Dockerfile.node 34 | ports: 35 | - "2024:8000" 36 | depends_on: 37 | langgraph-redis: 38 | condition: service_healthy 39 | langgraph-postgres: 40 | condition: service_healthy 41 | env_file: 42 | - .env 43 | environment: 44 | REDIS_URI: redis://langgraph-redis:6379 45 | LANGSMITH_API_KEY: ${LANGSMITH_API_KEY} 46 | POSTGRES_URI: postgres://postgres:postgres@langgraph-postgres:5432/postgres?sslmode=disable 47 | volumes: 48 | - ./mcp-config:/app/mcp-config 49 | - ./resources:/app/resources 50 | - ./prompts:/app/prompts 51 | streamlit-app: 52 | build: 53 | context: . 54 | dockerfile: streamlit.Dockerfile 55 | ports: 56 | - "2025:8501" 57 | volumes: 58 | - ./src:/app/src 59 | - ./mcp-config:/app/mcp-config 60 | - ./resources:/app/resources 61 | - ./prompts:/app/prompts 62 | restart: unless-stopped 63 | -------------------------------------------------------------------------------- /langgraph.json: -------------------------------------------------------------------------------- 1 | { 2 | "dockerfile_lines": [], 3 | "dependencies": ["."], 4 | "graphs": { 5 | "agent": "./src/react_agent/graph.py:graph" 6 | }, 7 | "env": ".env" 8 | } 9 | -------------------------------------------------------------------------------- /mcp-config/mcp_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "get_current_time": { 4 | "command": "python", 5 | "args": [ 6 | "/app/resources/mcp_server_time.py" 7 | ], 8 | "transport": "stdio" 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /prompts/system_prompt.yaml: -------------------------------------------------------------------------------- 1 | type: prompt 2 | template: | 3 | You are a smart agent with an ability to use various tools. Your mission is to respond to the user's REQUEST as helpful as possible by leveraging the provided tools. 4 | 5 | ---- 6 | 7 | ## Overall Guidelines 8 | 9 | Please follow the following guidelines strictly: 10 | - First, carefully read and analyze user's request. 11 | - Think step by step and write a plan to respond to the user's request. 12 | - Write a response to the user's request based on the plan. 13 | - Use given tools to solve user's request. 14 | - Write in a friendly tone(possibly with emojis). 15 | 16 | ## Initial Conversation 17 | 18 | You must introduce user about your ability and how to use you. 19 | Answer in bullet points. Introduce only ONCE in the beginning. 20 | 21 | Print out list of EXACT TOOL NAMES(without `functions`) and TOOL DESCRIPTIONS: 22 | Example: 23 | - `name of the tool`: `description of the tool` 24 | - `name of the tool`: `description of the tool` 25 | - `name of the tool`: `description of the tool` 26 | - ... 27 | 28 | ## Example of INITIAL INTRODUCTION 29 | 30 | 안녕하세요! 😊 저는 다양한 MCP 도구를 활용해 여러분의 질문에 빠르고 정확하게 답변해드릴 수 있는 에이전트입니다. 31 | 32 | ✅ 주요 기능 33 | - (description of jobs1) 34 | - (description of jobs2) 35 | - (description of jobs3) 36 | - ... 37 | 38 | 💡 사용 방법 39 | (description of how to use the tool) 40 | - 궁금한 점이나 찾고 싶은 정보를 자연스럽게 질문해 주세요! 41 | - 예시: "Model Context Protocol 개념에 대해 검색해줘", "최신 뉴스에 대해 검색해줘" 등 42 | (important: change the example to the tool you have) 43 | 44 | 🛠️ 사용 가능한 도구 목록(use backticks for tool names) 45 | - (list of tools: for example, `list_of_langchain_documents`: LangChain 및 LangGraph 공식 문서 목록 제공) 46 | - `TOOL_NAME`: `description of the tool` 47 | - ... 48 | 49 | 궁금한 점이 있으시면 언제든 말씀해 주세요! 😊 50 | 51 | [IMPORTANT] 52 | - Final answer should be written in Korean. 53 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "LangGraph-Dynamic-MCP-Agents" 3 | version = "0.0.1" 4 | description = "ReAct Agents with Dynamic MCP Tools" 5 | authors = [ 6 | { name = "Teddy Lee", email = "teddylee777@gmail.com" }, 7 | ] 8 | readme = "README.md" 9 | license = { text = "Apache-2.0" } 10 | requires-python = ">=3.11,<4.0" 11 | dependencies = [ 12 | "langgraph>=0.3.25", 13 | "langchain-openai>=0.3.12", 14 | "langchain-anthropic>=0.3.10", 15 | "langchain>=0.3.23", 16 | "langchain-community>=0.3.21", 17 | "langchain-mcp-adapters>=0.0.7", 18 | "tavily-python>=0.5.4", 19 | "python-dotenv>=1.0.1", 20 | "aiofiles>=24.1.0", 21 | "streamlit>=1.44.1", 22 | "mcp[cli]>=1.6.0", 23 | ] 24 | 25 | 26 | [project.optional-dependencies] 27 | dev = ["mypy>=1.11.1", "ruff>=0.6.1"] 28 | 29 | [build-system] 30 | requires = ["setuptools>=73.0.0", "wheel"] 31 | build-backend = "setuptools.build_meta" 32 | 33 | [tool.setuptools] 34 | packages = ["langgraph.templates.react_agent", "react_agent"] 35 | [tool.setuptools.package-dir] 36 | "langgraph.templates.react_agent" = "src/react_agent" 37 | "react_agent" = "src/react_agent" 38 | 39 | 40 | [tool.setuptools.package-data] 41 | "*" = ["py.typed"] 42 | 43 | [tool.ruff] 44 | lint.select = [ 45 | "E", # pycodestyle 46 | "F", # pyflakes 47 | "I", # isort 48 | "D", # pydocstyle 49 | "D401", # First line should be in imperative mood 50 | "T201", 51 | "UP", 52 | ] 53 | lint.ignore = [ 54 | "UP006", 55 | "UP007", 56 | # We actually do want to import from typing_extensions 57 | "UP035", 58 | # Relax the convention by _not_ requiring documentation for every function parameter. 59 | "D417", 60 | "E501", 61 | ] 62 | [tool.ruff.lint.per-file-ignores] 63 | "tests/*" = ["D", "UP"] 64 | [tool.ruff.lint.pydocstyle] 65 | convention = "google" 66 | 67 | [dependency-groups] 68 | dev = [ 69 | "langgraph-cli[inmem]>=0.1.89", 70 | ] 71 | -------------------------------------------------------------------------------- /requirements-streamlit.txt: -------------------------------------------------------------------------------- 1 | streamlit==1.31.1 2 | pathlib -------------------------------------------------------------------------------- /resources/mcp_server_time.py: -------------------------------------------------------------------------------- 1 | from mcp.server.fastmcp import FastMCP 2 | from datetime import datetime 3 | import pytz 4 | from typing import Optional 5 | 6 | # Initialize FastMCP server with configuration 7 | mcp = FastMCP( 8 | "TimeService", # Name of the MCP server 9 | instructions="You are a time assistant that can provide the current time for different timezones.", # Instructions for the LLM on how to use this tool 10 | host="0.0.0.0", # Host address (0.0.0.0 allows connections from any IP) 11 | port=8005, # Port number for the server 12 | ) 13 | 14 | 15 | @mcp.tool() 16 | async def get_current_time(timezone: Optional[str] = "Asia/Seoul") -> str: 17 | """ 18 | Get current time information for the specified timezone. 19 | 20 | This function returns the current system time for the requested timezone. 21 | 22 | Args: 23 | timezone (str, optional): The timezone to get current time for. Defaults to "Asia/Seoul". 24 | 25 | Returns: 26 | str: A string containing the current time information for the specified timezone 27 | """ 28 | try: 29 | # Get the timezone object 30 | tz = pytz.timezone(timezone) 31 | 32 | # Get current time in the specified timezone 33 | current_time = datetime.now(tz) 34 | 35 | # Format the time as a string 36 | formatted_time = current_time.strftime("%Y-%m-%d %H:%M:%S %Z") 37 | 38 | return f"Current time in {timezone} is: {formatted_time}" 39 | except pytz.exceptions.UnknownTimeZoneError: 40 | return f"Error: Unknown timezone '{timezone}'. Please provide a valid timezone." 41 | except Exception as e: 42 | return f"Error getting time: {str(e)}" 43 | 44 | 45 | if __name__ == "__main__": 46 | # Start the MCP server with stdio transport 47 | # stdio transport allows the server to communicate with clients 48 | # through standard input/output streams, making it suitable for 49 | # local development and testing 50 | mcp.run(transport="stdio") 51 | -------------------------------------------------------------------------------- /streamlit.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10-slim 2 | 3 | WORKDIR /app 4 | 5 | COPY toolsettings.py . 6 | COPY requirements-streamlit.txt . 7 | 8 | RUN pip install --no-cache-dir -r requirements-streamlit.txt 9 | 10 | EXPOSE 8501 11 | 12 | CMD ["streamlit", "run", "toolsettings.py", "--server.address=0.0.0.0"] -------------------------------------------------------------------------------- /toolsettings.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | import glob 3 | import json 4 | import os 5 | from pathlib import Path 6 | from copy import deepcopy 7 | 8 | st.set_page_config( 9 | page_title="Tool 설정", 10 | layout="wide", 11 | initial_sidebar_state="expanded", 12 | ) 13 | 14 | # Define the mcp-config directory 15 | MCP_CONFIG_DIR = "mcp-config" 16 | # Create directory if it doesn't exist 17 | os.makedirs(MCP_CONFIG_DIR, exist_ok=True) 18 | 19 | # 사이드바 최상단에 저자 정보 추가 (다른 사이드바 요소보다 먼저 배치) 20 | st.sidebar.markdown("### ✍️ Made by [테디노트](https://youtube.com/c/teddynote) 🚀") 21 | st.sidebar.markdown( 22 | "### 💻 [Project Page](https://github.com/teddynote-lab/langgraph-dynamic-mcp-agents)" 23 | ) 24 | 25 | # --- Sidebar for File Selection & Save --- 26 | with st.sidebar: 27 | st.header("📂 설정 파일 선택 & 저장") 28 | # JSON 파일 목록 29 | json_paths = glob.glob(f"{MCP_CONFIG_DIR}/*.json") 30 | # If no JSON files found, add a default mcp_config.json option 31 | if not json_paths and not os.path.exists(f"{MCP_CONFIG_DIR}/mcp_config.json"): 32 | default_config = {"mcpServers": {}} 33 | with open(f"{MCP_CONFIG_DIR}/mcp_config.json", "w", encoding="utf-8") as f: 34 | json.dump(default_config, f, indent=2, ensure_ascii=False) 35 | json_paths = [f"{MCP_CONFIG_DIR}/mcp_config.json"] 36 | 37 | tools_list = [{"name": Path(p).stem, "path": p} for p in json_paths] 38 | selected_name = st.selectbox("설정 파일 선택", [t["name"] for t in tools_list]) 39 | 40 | # Load 설정 41 | if st.button("📥 선택된 파일 Load", key="load", use_container_width=True): 42 | selected = next(t for t in tools_list if t["name"] == selected_name) 43 | with open(selected["path"], encoding="utf-8") as f: 44 | st.session_state.tool_config = json.load(f) 45 | st.session_state.file_path = selected["path"] 46 | st.session_state.loaded = True 47 | st.success(f"Loaded: {selected_name}.json") 48 | 49 | # Save 변경사항 50 | if st.session_state.get("loaded", False): 51 | if st.button("💾 저장", key="save", use_container_width=True): 52 | with open(st.session_state.file_path, "w", encoding="utf-8") as f: 53 | json.dump(st.session_state.tool_config, f, indent=2, ensure_ascii=False) 54 | st.session_state.saved_msg = ( 55 | f"저장 완료: {Path(st.session_state.file_path).name}" 56 | ) 57 | st.rerun() 58 | 59 | # --- Main Area --- 60 | st.title("🔧 MCP Agents Tool 설정") 61 | 62 | if not st.session_state.get("loaded", False): 63 | st.info("사이드바에서 JSON 파일을 로드하세요.") 64 | else: 65 | # 탭 구성: 목록, 추가, JSON 미리보기, Cursor AI, Claude Desktop 66 | tab1, tab2, tab3, tab4, tab5 = st.tabs( 67 | [ 68 | "📝 Tool 목록", 69 | "➕ 도구 추가", 70 | "🔍 JSON 미리보기", 71 | "💻 Cursor AI", 72 | "🤖 Claude Desktop", 73 | ] 74 | ) 75 | 76 | # Tab1: Tool List 77 | with tab1: 78 | mcp = st.session_state.tool_config.get("mcpServers", {}) 79 | if not mcp: 80 | st.warning("등록된 도구가 없습니다.") 81 | else: 82 | for name in list(mcp.keys()): 83 | col1, col2 = st.columns([9, 1]) 84 | with col1: 85 | st.write(f"• **{name}**") 86 | with col2: 87 | if st.button("삭제", key=f"del_{name}"): 88 | del st.session_state.tool_config["mcpServers"][name] 89 | st.success(f"도구 '{name}' 삭제됨") 90 | st.rerun() 91 | 92 | # Tab2: Add Tool 93 | with tab2: 94 | st.markdown("🔍 [Smithery 바로가기](https://smithery.ai/)") 95 | hint = """{ 96 | "mcpServers": { 97 | "perplexity-search": { 98 | "command": "npx", 99 | "args": [ 100 | "-y", 101 | "@smithery/cli@latest", 102 | "run", 103 | "@arjunkmrm/perplexity-search", 104 | "--key", 105 | "SMITHERY_API_KEY" 106 | ] 107 | } 108 | } 109 | } 110 | """ 111 | new_tool_text = st.text_area("도구 JSON 입력", hint, height=260) 112 | if st.button("추가", key="add_tool"): 113 | text = new_tool_text.strip() 114 | try: 115 | new_tool = json.loads(text) 116 | except json.JSONDecodeError: 117 | try: 118 | new_tool = json.loads(f"{{{text}}}") 119 | except json.JSONDecodeError as e: 120 | st.error(f"JSON 파싱 오류: {e}") 121 | new_tool = None 122 | if new_tool is not None: 123 | if "mcpServers" in new_tool and isinstance( 124 | new_tool["mcpServers"], dict 125 | ): 126 | tools_data = new_tool["mcpServers"] 127 | else: 128 | tools_data = new_tool 129 | for name, cfg in tools_data.items(): 130 | if "transport" not in cfg: 131 | cfg["transport"] = "sse" if "url" in cfg else "stdio" 132 | st.session_state.tool_config.setdefault("mcpServers", {})[ 133 | name 134 | ] = cfg 135 | added = ", ".join(tools_data.keys()) 136 | st.success(f"도구 '{added}' 추가됨") 137 | st.rerun() 138 | 139 | # Tab3: JSON Preview 140 | with tab3: 141 | st.code( 142 | json.dumps(st.session_state.tool_config, indent=2, ensure_ascii=False), 143 | language="json", 144 | ) 145 | 146 | # Tab4: Cursor AI JSON Preview without transport 147 | with tab4: 148 | preview = deepcopy(st.session_state.tool_config) 149 | servers = preview.get("mcpServers", {}) 150 | for cfg in servers.values(): 151 | if isinstance(cfg, dict) and "transport" in cfg: 152 | del cfg["transport"] 153 | st.code(json.dumps(preview, indent=2, ensure_ascii=False), language="json") 154 | 155 | # Tab5: Claude Desktop JSON Preview without transport and URL 156 | with tab5: 157 | preview_cd = deepcopy(st.session_state.tool_config) 158 | servers_cd = preview_cd.get("mcpServers", {}) 159 | # URL 파라미터가 있는 엔트리 확인 및 제거 160 | invalid = [ 161 | name 162 | for name, cfg in servers_cd.items() 163 | if isinstance(cfg, dict) and "url" in cfg 164 | ] 165 | if invalid: 166 | st.error( 167 | f"Claude Desktop에서 지원하지 않는 'url' 파라미터가 포함되어 다음 도구를 제외했습니다: {', '.join(invalid)}" 168 | ) 169 | for name in invalid: 170 | del servers_cd[name] 171 | # transport 제거 172 | for cfg in servers_cd.values(): 173 | if isinstance(cfg, dict) and "transport" in cfg: 174 | del cfg["transport"] 175 | st.code(json.dumps(preview_cd, indent=2, ensure_ascii=False), language="json") 176 | 177 | # 하단 저장 메시지 출력 178 | with st.sidebar: 179 | if st.session_state.get("saved_msg"): 180 | st.success(st.session_state.pop("saved_msg")) 181 | --------------------------------------------------------------------------------