Using MCPHub with LangChain
Learn how to integrate your MCPHub-deployed MCP servers with LangChain for powerful AI applications using Python.
Overview
This guide shows you how to:
- Set up a Python environment with LangChain and LangGraph
- Connect to your MCPHub-deployed MCP servers
- Use MCP tools through the LangChain MCP adapter
- Build interactive AI agents that can call your APIs
Prerequisites
- MCPHub running with deployed MCPs (Quick Install)
- Python 3.8+ installed
- OpenAI API key
- Basic familiarity with Python and async programming
Step 1: Install Required Packages
Create a new Python project and install dependencies:
# Create project directory
mkdir mcphub-langchain-agent
cd mcphub-langchain-agent
# Create virtual environment
python -m venv venv
# Activate virtual environment
# On Windows:
venv\Scripts\activate
# On macOS/Linux:
source venv/bin/activate
# Install required packages
pip install langchain-mcp-adapters langchain-openai langgraph
Step 2: Set Up Environment Variables
Create a .env file in your project directory:
# MCP Server URL - update with your MCPHub server endpoint
MCP_URL=http://localhost:3001/system/weather-gov-api/mcp
# Authentication - choose the method your MCP server requires
# For Basic Auth (username:password encoded in base64):
BASIC_AUTH=YWRtaW46eW91cl9wYXNzd29yZA==
# For JWT token:
# JWT_TOKEN=your_jwt_token_here
# For OAuth2 token:
# OAUTH_TOKEN=your_oauth2_token_here
# OpenAI API Key - required for the AI agent
OPENAI_API_KEY=sk-proj-your_openai_api_key_here
# Enable MCP debug logging to see HTTP requests (optional)
DEBUG_MCP=false
# System message to guide the LLM (optional)
SYSTEM_MESSAGE=When calling tools: 1) Check the tool schema for REQUIRED parameters and always include them. 2) If a tool call fails with an error, DO NOT retry the same call - instead report the error to the user. 3) Only use 'fields' parameter if the user explicitly asks for specific fields.
# Recursion limit for agent (optional, default: 25)
RECURSION_LIMIT=25
Note on Authentication:
- To generate
BASIC_AUTH:echo -n "username:password" | base64 - Replace
MCP_URLwith your actual MCPHub deployment URL (format:http://host:port/tenantId/mcpName/mcp) - Get your OpenAI API key from OpenAI Platform
Step 3: Create the Interactive Agent
Create a file named interactive_agent.py:
#!/usr/bin/env python3
"""
Interactive MCP agent using LangChain and LangGraph
"""
import asyncio
import os
import logging
import sys
from pathlib import Path
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent
# Set logging to INFO level only
logging.basicConfig(level=logging.INFO, format='%(name)s - %(levelname)s - %(message)s')
# Load environment variables from .env file
def load_env():
"""Load environment variables from .env file"""
env_path = Path(__file__).parent / '.env'
if env_path.exists():
with open(env_path) as f:
for line in f:
line = line.strip()
if line and not line.startswith('#') and '=' in line:
key, value = line.split('=', 1)
os.environ[key] = value
print(f"📄 Loaded configuration from {env_path}")
else:
print(f"⚠️ No .env file found at {env_path}")
# Load .env on import
load_env()
# Check if debug mode is enabled (after loading .env)
DEBUG_MCP = os.getenv("DEBUG_MCP", "false").lower() == "true"
if DEBUG_MCP:
# Enable detailed HTTP and MCP logging
logging.getLogger("httpx").setLevel(logging.DEBUG)
logging.getLogger("httpcore").setLevel(logging.DEBUG)
logging.getLogger("mcp").setLevel(logging.DEBUG)
print("🔍 MCP Debug logging enabled")
else:
# Reduce HTTP noise in normal mode
logging.getLogger("httpx").setLevel(logging.WARNING)
logging.getLogger("httpcore").setLevel(logging.WARNING)
# Apply the patches for JSON-RPC compatibility
def patch_jsonrpc_validation():
"""Patch JSON-RPC message validation to fix id=null issues"""
try:
from mcp.types import JSONRPCMessage
original_model_validate_json = JSONRPCMessage.model_validate_json
@classmethod
def patched_model_validate_json(cls, json_data, **kwargs):
if isinstance(json_data, (str, bytes)):
import json
data = json.loads(json_data)
else:
data = json_data
# Handle empty dict responses (notifications that return {})
if isinstance(data, dict) and not data:
# These are notification acknowledgments - create a minimal valid response
data = {"jsonrpc": "2.0", "result": {}, "id": 0}
elif isinstance(data, dict) and data.get('jsonrpc') == '2.0':
if 'result' in data and data.get('id') is None:
data['id'] = 1
elif 'method' in data and data.get('id') is None:
data['id'] = 1
fixed_json = json.dumps(data) if not isinstance(json_data, str) else json.dumps(data)
return original_model_validate_json(fixed_json, **kwargs)
JSONRPCMessage.model_validate_json = patched_model_validate_json
except ImportError:
pass
def patch_list_tools_result():
try:
from mcp.types import ListToolsResult
original_model_validate = ListToolsResult.model_validate
@classmethod
def patched_model_validate(cls, obj, **kwargs):
if isinstance(obj, dict) and not obj:
obj = {"tools": []}
elif isinstance(obj, dict) and "tools" not in obj:
obj["tools"] = []
return original_model_validate(obj, **kwargs)
ListToolsResult.model_validate = patched_model_validate
except ImportError:
pass
def patch_output_schema_validation():
"""Disable output schema validation to accept text responses"""
try:
from mcp.client.session import ClientSession
# Replace the validation method with a no-op
async def patched_validate_tool_result(self, name: str, result) -> None:
"""Skip output schema validation"""
pass
ClientSession._validate_tool_result = patched_validate_tool_result
print("✅ Disabled output schema validation")
except ImportError as e:
print(f"⚠️ Could not patch output schema validation: {e}")
pass
# Apply patches
patch_jsonrpc_validation()
patch_list_tools_result()
patch_output_schema_validation()
# Global variables to cache tools and agent
cached_tools = None
cached_agent = None
cached_client = None
async def initialize_agent():
"""Initialize the MCP client, tools, and agent once at startup"""
global cached_tools, cached_agent, cached_client
# Get configuration from environment (loaded from .env)
mcp_url = os.getenv("MCP_URL", "http://localhost:3001/system/weather-gov-api/mcp")
openai_key = os.getenv("OPENAI_API_KEY")
if not openai_key:
print("❌ OpenAI API key not found in .env file")
print(" Please set OPENAI_API_KEY in your .env file")
return False
print(f"🎯 Target: {mcp_url}")
try:
print("1. Connecting to MCPHub and discovering tools...")
# Get authentication if available
jwt_token = os.getenv("JWT_TOKEN")
oauth_token = os.getenv("OAUTH_TOKEN")
basic_auth = os.getenv("BASIC_AUTH")
# Configure MCP client with authentication headers
headers = {}
if jwt_token:
headers["Authorization"] = f"Bearer {jwt_token}"
elif oauth_token:
headers["Authorization"] = f"Bearer {oauth_token}"
elif basic_auth:
headers["Authorization"] = f"Basic {basic_auth}"
client_config = {
"transport": "streamable_http",
"url": mcp_url
}
if headers:
client_config["headers"] = headers
cached_client = MultiServerMCPClient({
"mcphub_server": client_config
})
# Get tools once
cached_tools = await cached_client.get_tools()
if not cached_tools:
print(" No tools available")
return False
print(f" ✅ Found {len(cached_tools)} tools")
print("\n2. Initializing OpenAI LLM...")
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0, api_key=openai_key)
print("3. Creating React agent...")
cached_agent = create_react_agent(llm, cached_tools)
print("✅ Agent initialized and ready!")
return True
except Exception as e:
print(f"❌ Failed to initialize agent: {e}")
return False
async def execute_user_request(user_input, system_message=None, debug=False):
"""Execute a user request using the cached MCP agent"""
global cached_agent
if not cached_agent:
print("❌ Agent not initialized")
return
try:
print(f"🔄 Processing request: {user_input}")
print("-" * 60)
# Prepare messages with optional system message
messages = []
if system_message:
messages.append({"role": "system", "content": system_message})
messages.append({"role": "user", "content": user_input})
# Execute the request with configurable recursion limit (default: 25)
recursion_limit = int(os.getenv("RECURSION_LIMIT", "25"))
result = await cached_agent.ainvoke(
{"messages": messages},
{"recursion_limit": recursion_limit}
)
if debug:
print("\n📋 Agent Response:")
print("=" * 60)
# Display the conversation with step counter
step_num = 0
for message in result["messages"]:
message_type = message.__class__.__name__
if message_type == 'HumanMessage':
print(f"👤 USER: {message.content}\n")
elif message_type == 'AIMessage':
step_num += 1
if hasattr(message, 'tool_calls') and message.tool_calls:
print(f"🤖 STEP {step_num} - ASSISTANT is calling tools:")
for tool_call in message.tool_calls:
import json
tool_name = tool_call.get('name', 'unknown')
tool_args = tool_call.get('args', {})
tool_id = tool_call.get('id', 'unknown')
print(f" 📞 TOOL CALL: {tool_name}")
print(f" ID: {tool_id}")
# Show the full MCP request that will be made
print(f" 🔍 MCP REQUEST:")
print(f" Method: tools/call")
print(f" Tool: {tool_name}")
print(f" Arguments:")
for key, value in tool_args.items():
print(f" {key}: {json.dumps(value, indent=15)}")
print()
elif message.content:
print(f"🤖 STEP {step_num} - ASSISTANT: {message.content}\n")
elif message_type == 'ToolMessage':
print(f"🔧 TOOL RESULT:")
print(f" Tool: {getattr(message, 'name', 'unknown')}")
# Check if result indicates an error
content = message.content
if isinstance(content, str):
if 'error' in content.lower() or 'exception' in content.lower() or 'failed' in content.lower():
print(f" ⚠️ ERROR DETECTED:")
print(f" {content[:500]}")
else:
print(f" ✅ SUCCESS:")
print(f" {content[:500]}")
else:
print(f" {content}")
print()
print("=" * 60)
else:
# Compact output - just show final response
for message in result["messages"]:
message_type = message.__class__.__name__
if message_type == 'AIMessage' and message.content and not (hasattr(message, 'tool_calls') and message.tool_calls):
print(f"\n🤖 {message.content}")
print("✅ Request completed!")
except Exception as e:
if "recursion_limit" in str(e).lower():
print(f"\n⚠️ Request took too many steps to complete (limit: {recursion_limit})")
if debug:
print(" This usually means the agent is stuck in a loop.")
print("\n🔍 Check the TOOL RESULTS above for errors")
else:
print(" Run with --debug flag to see detailed step-by-step output")
else:
print(f"❌ Agent failed with error: {e}")
if debug:
import traceback
print("\n🔍 Full traceback:")
traceback.print_exc()
async def cleanup():
"""Clean up resources"""
global cached_client
if cached_client:
try:
await cached_client.aclose()
except AttributeError:
pass
async def interactive_mode(debug=False):
"""Run in interactive mode"""
print("🤖 MCPHub Interactive Agent")
print("=" * 50)
# Initialize agent once at startup
if not await initialize_agent():
return
# Check for custom system message
system_message = os.getenv("SYSTEM_MESSAGE")
if system_message:
print(f"📋 Using custom system message: {system_message[:100]}{'...' if len(system_message) > 100 else ''}")
if debug:
print("🐛 DEBUG MODE ENABLED")
print("\n💡 Type your request and press Enter")
print(" Type 'exit' or 'quit' to end")
print("=" * 50)
print("\n📝 Example requests:")
print(" - What are the current weather alerts for California?")
print(" - Get weather forecast for New York")
print(" - Search for persons named Smith")
print("=" * 50)
try:
while True:
print("\n")
user_input = input("👤 You: ").strip()
if user_input.lower() in ['exit', 'quit']:
print("👋 Goodbye!")
break
elif not user_input:
continue
await execute_user_request(user_input, system_message, debug)
except KeyboardInterrupt:
print("\n\n👋 Session interrupted. Goodbye!")
finally:
await cleanup()
async def main():
"""Main entry point"""
# Check for --debug flag
debug = '--debug' in sys.argv
if debug:
sys.argv.remove('--debug')
if len(sys.argv) > 1:
# If command line argument provided, execute it and exit
user_input = ' '.join(sys.argv[1:])
print(f"📝 Executing: {user_input}")
# Initialize agent for single command
if await initialize_agent():
system_message = os.getenv("SYSTEM_MESSAGE")
await execute_user_request(user_input, system_message, debug)
await cleanup()
else:
# Interactive mode
await interactive_mode(debug)
if __name__ == "__main__":
asyncio.run(main())
Step 4: Run the Interactive Agent
Interactive Mode
Run the agent in interactive mode to ask multiple questions:
python interactive_agent.py
You'll see output like:
📄 Loaded configuration from .env
✅ Disabled output schema validation
🤖 MCPHub Interactive Agent
==================================================
🎯 Target: http://localhost:3001/system/weather-gov-api/mcp
1. Connecting to MCPHub and discovering tools...
✅ Found 8 tools
2. Initializing OpenAI LLM...
3. Creating React agent...
✅ Agent initialized and ready!
💡 Type your request and press Enter
Type 'exit' or 'quit' to end
==================================================
📝 Example requests:
- What are the current weather alerts for California?
- Get weather forecast for New York
- Search for persons named Smith
==================================================
👤 You:
Single Command Mode
Execute a single command and exit:
python interactive_agent.py "What are the weather alerts for Texas?"
Debug Mode
Enable detailed step-by-step output:
python interactive_agent.py --debug
Or for single command:
python interactive_agent.py --debug "Get weather forecast for Seattle"
Sample Interactions
Example 1: Weather Alerts
👤 You: What are the current weather alerts for California?
🔄 Processing request: What are the current weather alerts for California?
------------------------------------------------------------
🤖 There are currently 23 active weather alerts for California. Here are some of the alerts:
1. **Red Flag Warning** - Valid until Oct 19, 10:00 PM PDT
- Areas: Northern and Southern Salinas Valley
2. **Wind Advisory** - Valid until Oct 19, 8:00 PM PDT
- Areas: San Francisco Bay Area
3. **High Surf Advisory** - Valid until Oct 20, 3:00 AM PDT
- Areas: San Luis Obispo County Beaches
✅ Request completed!
Example 2: Person Search (if using person MCP)
👤 You: Create a person named Alice Johnson, age 28
🔄 Processing request: Create a person named Alice Johnson, age 28
------------------------------------------------------------
🤖 I've successfully created a person record for Alice Johnson, age 28.
✅ Request completed!
Understanding the Code
Key Components
1. Environment Configuration
The agent loads configuration from .env file, supporting:
- MCP server URLs
- Multiple authentication methods (Basic, JWT, OAuth2)
- OpenAI API configuration
- Debug settings
2. JSON-RPC Patches
The code includes patches to handle MCPHub's JSON-RPC responses:
patch_jsonrpc_validation(): Fixesid=nullissuespatch_list_tools_result(): Handles empty tool listspatch_output_schema_validation(): Accepts text responses
3. Agent Initialization
- Connects to MCP server once at startup
- Discovers available tools
- Creates a React agent with LangGraph
4. Request Execution
- Supports both interactive and single-command modes
- Configurable recursion limit
- Optional debug mode for detailed output
Configuration Options
Debug Mode
Enable detailed logging:
# In .env
DEBUG_MCP=true
Or use the --debug flag when running.
System Message
Guide the LLM's behavior:
# In .env
SYSTEM_MESSAGE=You are a helpful assistant. Always be concise and accurate.
Recursion Limit
Control how many steps the agent can take:
# In .env
RECURSION_LIMIT=50 # Default is 25
Troubleshooting
"OpenAI API key not found"
Make sure your .env file has:
OPENAI_API_KEY=sk-proj-your_actual_key_here
"No tools available"
Check:
- MCPHub is running at the specified URL
- The MCP is deployed in MCPHub
- Authentication credentials are correct
"Request took too many steps"
The agent hit the recursion limit. Try:
- Increase
RECURSION_LIMITin.env - Simplify your request
- Run with
--debugto see what's happening
Connection Errors
Verify:
- MCPHub URL is correct (format:
http://host:port/tenantId/mcpName/mcp) - MCPHub is accessible from your machine
- Authentication headers are properly configured
Advanced Usage
Multiple MCP Servers
Modify the client configuration to connect to multiple MCPs:
cached_client = MultiServerMCPClient({
"weather_api": {
"transport": "streamable_http",
"url": "http://localhost:3001/system/weather-gov-api/mcp",
"headers": {"Authorization": f"Basic {basic_auth}"}
},
"person_api": {
"transport": "streamable_http",
"url": "http://localhost:3001/system/person-rest/mcp",
"headers": {"Authorization": f"Basic {basic_auth}"}
}
})
Custom Models
Change the OpenAI model:
llm = ChatOpenAI(model="gpt-4", temperature=0, api_key=openai_key)
Error Handling
The agent includes comprehensive error handling:
- Network errors
- Authentication failures
- Tool execution errors
- Recursion limit exceeded
The combination of MCPHub and LangChain provides a powerful foundation for building AI applications that can interact with any API through natural language!