Skip to main content

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:

.env
# 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_URL with 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:

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(): Fixes id=null issues
  • patch_list_tools_result(): Handles empty tool lists
  • patch_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:

  1. MCPHub is running at the specified URL
  2. The MCP is deployed in MCPHub
  3. Authentication credentials are correct

"Request took too many steps"​

The agent hit the recursion limit. Try:

  1. Increase RECURSION_LIMIT in .env
  2. Simplify your request
  3. Run with --debug to see what's happening

Connection Errors​

Verify:

  1. MCPHub URL is correct (format: http://host:port/tenantId/mcpName/mcp)
  2. MCPHub is accessible from your machine
  3. 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!