├── .env.example ├── .gitignore ├── README.md ├── agent.py ├── requirements.txt └── servers ├── math.py ├── tavily.py ├── weather.py └── yt_transcript.py /.env.example: -------------------------------------------------------------------------------- 1 | OPENAI_API_KEY = 2 | TAVILY_API_KEY = -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # UV 98 | # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | #uv.lock 102 | 103 | # poetry 104 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 105 | # This is especially recommended for binary packages to ensure reproducibility, and is more 106 | # commonly ignored for libraries. 107 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 108 | #poetry.lock 109 | 110 | # pdm 111 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 112 | #pdm.lock 113 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 114 | # in version control. 115 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 116 | .pdm.toml 117 | .pdm-python 118 | .pdm-build/ 119 | 120 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 121 | __pypackages__/ 122 | 123 | # Celery stuff 124 | celerybeat-schedule 125 | celerybeat.pid 126 | 127 | # SageMath parsed files 128 | *.sage.py 129 | 130 | # Environments 131 | .env 132 | .venv 133 | env/ 134 | venv/ 135 | ENV/ 136 | env.bak/ 137 | venv.bak/ 138 | 139 | # Spyder project settings 140 | .spyderproject 141 | .spyproject 142 | 143 | # Rope project settings 144 | .ropeproject 145 | 146 | # mkdocs documentation 147 | /site 148 | 149 | # mypy 150 | .mypy_cache/ 151 | .dmypy.json 152 | dmypy.json 153 | 154 | # Pyre type checker 155 | .pyre/ 156 | 157 | # pytype static type analyzer 158 | .pytype/ 159 | 160 | # Cython debug symbols 161 | cython_debug/ 162 | 163 | # PyCharm 164 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 165 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 166 | # and can be added to the global gitignore or merged into this file. For a more nuclear 167 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 168 | #.idea/ 169 | 170 | # Ruff stuff: 171 | .ruff_cache/ 172 | 173 | # PyPI configuration file 174 | .pypirc 175 | 176 | # additional files 177 | backup.txt -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # LangGraph Agent with MCP 3 | This project is designed to efficiently integrate Model Context Protocol (MCP) with a LangGraph Agent, allowing it to dynamically access external tools, data sources, and APIs. 4 | 5 | Using this project, you can connect a LangGraph agent to MCP servers and use predefined tools to perform various tasks, such as web searches and summarizing YouTube videos. You can also add additional servers as needed. 6 | 7 | This integration enables automatic tool discovery and multi-server support, making AI systems more modular and powerful. It allows AI systems to automatically find tools and connect to multiple servers, increasing their flexibility and efficiency. 8 | 9 | ![image](https://github.com/user-attachments/assets/3540530a-7eed-4a47-87cf-00ee46fa5024) 10 | 11 | 12 | ## What is MCP and Why It Matters? 13 | 14 | The Model Context Protocol (MCP) is an open standard that provides a structured way for AI applications to interact with external data, tools, and APIs. MCP was developed by Anthropic to address the challenge of dynamically connecting LLMs to external data sources without requiring custom integrations for each tool. 15 | 16 | MCP is important because it helps AI systems share and access data easily, removing barriers between different tools. This makes AI more connected and efficient. It also allows developers to build smarter AI systems that can work with many different tools and grow easily. 17 | 18 | ## Project Structure 19 | 20 | ```bash 21 | langgraph_mcp/ 22 | │-- agent.py 23 | │-- servers/ 24 | │ ├── tavily.py 25 | │ ├── yt_transcript.py 26 | │ ├── math_server.py 27 | │ ├──weather.py 28 | │-- .env 29 | │-- requirements.txt 30 | 31 | ``` 32 | 33 | ## Installation 34 | 35 | 1. Clone the Repository: 36 | ```bash 37 | git clone https://github.com/your-repo/langgraph-mcp.git 38 | cd langgraph-mcp 39 | ``` 40 | 41 | 2. Install Dependencies 42 | ```bash 43 | pip install -r requirements.txt 44 | ``` 45 | 3. Create a .env file and add: 46 | ```bash 47 | TAVILY_API_KEY= 48 | OPENAI_API_KEY= 49 | ``` 50 | 51 | ## How It Works 52 | To start, run ```servers/server.py``` in your terminal. This will start the MCP server. Then, in a new terminal, run ```agent.py```. The agent will connect to the server via the MCP client and execute your query, as shown in the demo. 53 | 54 | https://github.com/user-attachments/assets/c65255a1-62af-4711-8f10-ebfdacd9b3c6 55 | -------------------------------------------------------------------------------- /agent.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from langchain_mcp_adapters.client import MultiServerMCPClient 3 | from langgraph.prebuilt import create_react_agent 4 | from langchain_core.messages import SystemMessage, HumanMessage 5 | from langchain_openai import ChatOpenAI 6 | from dotenv import load_dotenv 7 | load_dotenv() 8 | 9 | # Example query 10 | # "What is weather in newyork" 11 | # "What is FastMCP?" 12 | # "summarize this youtube video in 50 words, here is a video link: https://www.youtube.com/watch?v=2f3K43FHRKo" 13 | query = input("Query:") 14 | 15 | # Define llm 16 | model = ChatOpenAI(model="gpt-4o") 17 | 18 | # Define MCP servers 19 | async def run_agent(): 20 | async with MultiServerMCPClient( 21 | { 22 | "tavily": { 23 | "command": "python", 24 | "args": ["servers/tavily.py"], 25 | "transport": "stdio", 26 | }, 27 | "youtube_transcript": { 28 | "command": "python", 29 | "args": ["servers/yt_transcript.py"], 30 | "transport": "stdio", 31 | }, 32 | "math": { 33 | "command": "python", 34 | "args": ["servers/math.py"], 35 | "transport": "stdio", 36 | }, 37 | # "weather": { 38 | # "url": "http://localhost:8000/sse", # start your weather server on port 8000 39 | # "transport": "sse", 40 | # } 41 | } 42 | ) as client: 43 | # Load available tools 44 | tools = client.get_tools() 45 | agent = create_react_agent(model, tools) 46 | 47 | # Add system message 48 | system_message = SystemMessage(content=( 49 | "You have access to multiple tools that can help answer queries. " 50 | "Use them dynamically and efficiently based on the user's request. " 51 | )) 52 | 53 | # Process the query 54 | agent_response = await agent.ainvoke({"messages": [system_message, HumanMessage(content=query)]}) 55 | 56 | # # Print each message for debugging 57 | # for m in agent_response["messages"]: 58 | # m.pretty_print() 59 | 60 | return agent_response["messages"][-1].content 61 | 62 | # Run the agent 63 | if __name__ == "__main__": 64 | response = asyncio.run(run_agent()) 65 | print("\nFinal Response:", response) -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | langchain_mcp_adapters 2 | langgraph 3 | langchain_openai 4 | youtube-transcript-api -------------------------------------------------------------------------------- /servers/math.py: -------------------------------------------------------------------------------- 1 | from mcp.server.fastmcp import FastMCP 2 | 3 | mcp =FastMCP("Math") 4 | 5 | @mcp.tool() 6 | def add(a: int, b: int) -> int: 7 | return a + b 8 | 9 | @mcp.tool() 10 | def multiply(a: int, b: int) -> int: 11 | return a * b 12 | 13 | if __name__ =="__main__": 14 | mcp.run(transport="stdio") -------------------------------------------------------------------------------- /servers/tavily.py: -------------------------------------------------------------------------------- 1 | import os 2 | import httpx 3 | from dotenv import load_dotenv 4 | from mcp.server.fastmcp import FastMCP 5 | 6 | # Load environment variables 7 | load_dotenv() 8 | 9 | # Initialize FastMCP 10 | mcp = FastMCP("tavily_search") 11 | 12 | # Tavily API details 13 | TAVILY_API_KEY = os.getenv("TAVILY_API_KEY") 14 | TAVILY_SEARCH_URL = "https://api.tavily.com/search" 15 | 16 | async def search_tavily(query: str) -> dict: 17 | """Performs a Tavily web search and returns 5 results.""" 18 | if not TAVILY_API_KEY: 19 | return {"error": "Tavily API key is missing. Set it in your .env file."} 20 | 21 | payload = { 22 | "query": query, 23 | "topic": "general", 24 | "search_depth": "basic", 25 | "chunks_per_source": 3, 26 | "max_results": 5, # Fixed 27 | "time_range": None, 28 | "days": 3, 29 | "include_answer": True, 30 | "include_raw_content": False, 31 | "include_images": False, 32 | "include_image_descriptions": False, 33 | "include_domains": [], 34 | "exclude_domains": [] 35 | } 36 | 37 | headers = { 38 | "Authorization": f"Bearer {TAVILY_API_KEY}", 39 | "Content-Type": "application/json" 40 | } 41 | 42 | async with httpx.AsyncClient() as client: 43 | response = await client.post(TAVILY_SEARCH_URL, json=payload, headers=headers, timeout=30.0) 44 | response.raise_for_status() 45 | return response.json() 46 | 47 | @mcp.tool() 48 | async def get_tavily_results(query: str): 49 | """Fetches Tavily search results for a given query.""" 50 | results = await search_tavily(query) 51 | 52 | if isinstance(results, dict): 53 | return {"results": results.get("results", [])} # Ensure always returning a dictionary 54 | else: 55 | return {"error": "Unexpected Tavily response format"} 56 | 57 | if __name__ == "__main__": 58 | mcp.run(transport="stdio") 59 | -------------------------------------------------------------------------------- /servers/weather.py: -------------------------------------------------------------------------------- 1 | from mcp.server.fastmcp import FastMCP 2 | 3 | mcp = FastMCP("Weather") 4 | 5 | @mcp.tool() 6 | async def get_weather(location: str) -> str: 7 | """Get weather for location.""" 8 | return "It's always sunny in New York" 9 | 10 | if __name__ == "__main__": 11 | mcp.run(transport="sse") -------------------------------------------------------------------------------- /servers/yt_transcript.py: -------------------------------------------------------------------------------- 1 | import re 2 | from mcp.server.fastmcp import FastMCP 3 | from youtube_transcript_api import YouTubeTranscriptApi 4 | 5 | mcp = FastMCP("youtube_transcript") 6 | 7 | @mcp.tool() 8 | def get_youtube_transcript(url: str) -> dict: 9 | """Fetches transcript from a given YouTube URL.""" 10 | video_id_match = re.search(r"(?:v=|\/)([0-9A-Za-z_-]{11}).*", url) 11 | if not video_id_match: 12 | return {"error": "Invalid YouTube URL"} 13 | 14 | video_id = video_id_match.group(1) 15 | 16 | try: 17 | transcript = YouTubeTranscriptApi.get_transcript(video_id) 18 | transcript_text = "\n".join([entry["text"] for entry in transcript]) 19 | return {"transcript": transcript_text} 20 | except Exception as e: 21 | return {"error": str(e)} 22 | 23 | if __name__ == "__main__": 24 | mcp.run(transport="stdio") 25 | --------------------------------------------------------------------------------