We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/digital-duck/mcp_demo'
If you have feedback or need assistance with the MCP directory API, please join our Discord server
import asyncio
import json
import logging
import os
from typing import Dict, List, Any, Optional
from fastmcp import Client
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
# LLM Integration Options - Enhanced with Claude 3.5 Sonnet and GPT-4o-mini
LLM_MODELS = ["openai", "anthropic", "ollama", "google", "bedrock"]
LLM_PROVIDER = "google" # "anthropic" # Default to Claude 3.5 Sonnet
MCP_SERVER_PATH = "mcp_server.py"
# --- Enhanced LLM Query Parser ---
class LLMQueryParser:
"""Use LLM to parse natural language queries into tool calls"""
def __init__(self, provider: str = "anthropic"):
self.provider = provider
self.setup_llm_client()
def setup_llm_client(self):
"""Setup the appropriate LLM client based on provider"""
if self.provider == "openai":
try:
import openai
api_key = os.getenv("OPENAI_API_KEY")
if api_key:
self.client = openai.OpenAI(api_key=api_key)
# Test with a simple completion to verify API access
try:
test_response = self.client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "test"}],
max_tokens=1
)
logging.info("โ
OpenAI client initialized with GPT-4o-mini")
self.model_name = "gpt-4o-mini"
except Exception as e:
logging.warning(f"GPT-4o-mini not accessible, falling back to gpt-3.5-turbo: {e}")
self.model_name = "gpt-3.5-turbo"
else:
logging.error("โ OPENAI_API_KEY not set")
self.client = None
except ImportError:
logging.error("โ OpenAI library not installed. Run: pip install openai")
self.client = None
elif self.provider == "anthropic":
try:
import anthropic
api_key = os.getenv("ANTHROPIC_API_KEY")
if api_key:
self.client = anthropic.Anthropic(api_key=api_key)
# Test with Claude 3.5 Sonnet
try:
test_response = self.client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1,
messages=[{"role": "user", "content": "test"}]
)
logging.info("โ
Anthropic client initialized with Claude 3.5 Sonnet")
self.model_name = "claude-3-5-sonnet-20241022"
except Exception as e:
logging.warning(f"Claude 3.5 Sonnet not accessible, trying Claude 3 Haiku: {e}")
try:
test_response = self.client.messages.create(
model="claude-3-haiku-20240307",
max_tokens=1,
messages=[{"role": "user", "content": "test"}]
)
self.model_name = "claude-3-haiku-20240307"
logging.info("โ
Anthropic client initialized with Claude 3 Haiku")
except Exception as e2:
logging.error(f"โ Anthropic API error: {e2}")
self.client = None
else:
logging.error("โ ANTHROPIC_API_KEY not set")
self.client = None
except ImportError:
logging.error("โ Anthropic library not installed. Run: pip install anthropic")
self.client = None
elif self.provider == "ollama":
try:
import requests
# Test if Ollama is running
response = requests.get("http://localhost:11434/api/tags", timeout=5)
if response.status_code == 200:
logging.info("โ
Ollama server detected")
self.client = "ollama" # Use string indicator
self.model_name = "qwen2.5" # "llama3.2"
else:
logging.error("โ Ollama server not responding")
self.client = None
except Exception as e:
logging.error(f"โ Ollama connection failed: {e}")
logging.error("Make sure Ollama is installed and running: https://ollama.ai")
self.client = None
elif self.provider == "google":
try:
import google.generativeai as genai
api_key = os.getenv("GEMINI_API_KEY")
if api_key:
genai.configure(api_key=api_key)
# Updated Gemini models list
GEMINI_MODELS = [
"gemini-2.5-flash-preview-05-20",
"gemini-2.5-pro-preview-05-06",
"gemini-2.0-flash",
"gemini-1.5-flash",
"gemini-1.5-pro",
]
# Try models in order of preference
for model_name in GEMINI_MODELS:
try:
self.client = genai.GenerativeModel(model_name)
# Test the model
test_response = self.client.generate_content("test", generation_config={"max_output_tokens": 1})
self.model_name = model_name
logging.info(f"โ
Gemini client initialized with {model_name}")
break
except Exception as e:
logging.warning(f"Model {model_name} not available: {e}")
continue
else:
logging.error("โ No Gemini models available")
self.client = None
else:
logging.error("โ GEMINI_API_KEY not set")
self.client = None
except ImportError:
logging.error("โ Gemini library not installed. Run: pip install google-generativeai")
self.client = None
elif self.provider == "bedrock":
try:
import boto3
from botocore.exceptions import NoCredentialsError, PartialCredentialsError
region = os.getenv("AWS_DEFAULT_REGION", "us-east-1")
try:
self.client = boto3.client(
service_name='bedrock-runtime',
region_name=region
)
# Updated model ID for Claude 3.5 Sonnet v2
self.bedrock_model_id = "anthropic.claude-3-5-sonnet-20241022-v2:0"
self.model_name = self.bedrock_model_id
logging.info(f"โ
AWS Bedrock client initialized (region: {region})")
logging.info(f"๐ Using model: {self.bedrock_model_id}")
except (NoCredentialsError, PartialCredentialsError) as e:
logging.error(f"โ AWS credentials not configured: {e}")
self.client = None
except ImportError:
logging.error("โ Boto3 library not installed. Run: pip install boto3")
self.client = None
def get_system_prompt(self, available_tools: List[Dict], available_resources: List[Dict] = None) -> str:
"""Create system prompt with available tools and resources"""
tools_desc = "\n\nAvailable tools:\n" + "\n".join([
f"- {tool['name']}: {tool.get('description', 'No description')}"
for tool in available_tools
])
resources_desc = ""
if available_resources:
resources_desc = "\n\nAvailable resources:\n" + "\n".join([
f"- {resource['uri']}: {resource.get('description', 'No description')}"
for resource in available_resources
])
return f"""
You are a tool and resource selection assistant.
Given a user query, you must decide whether to use a tool, read a resource, or both.
{tools_desc}
{resources_desc}
For each user query, respond with ONLY a JSON object in this exact format:
{{
"action": "tool|resource|both",
"tool": "tool_name_or_null",
"resource_uri": "resource_uri_or_null",
"params": {{
"param1": "value1",
"param2": "value2"
}},
"confidence": 0.95,
"reasoning": "Brief explanation of why this action was chosen"
}}
Tool-specific parameter requirements:
- calculator: operation (add/subtract/multiply/divide/power), num1 (number), num2 (number)
- trig: operation (sine/cosine/tangent/arc sine/arc cosine/arc tangent), num1 (float), unit (degree/radian)
- stock_quote: ticker (stock symbol like AAPL, MSFT)
- health: no parameters needed
- echo: message (text to echo back)
Resource-specific patterns:
- info://server: Server information (no parameters)
- stock://{{ticker}}: Stock information for specific ticker
If you cannot determine which action to take, respond with:
{{
"action": null,
"tool": null,
"resource_uri": null,
"params": {{}},
"confidence": 0.0,
"reasoning": "Could not parse the query"
}}
Examples:
User: "What is 15 plus 27?"
Response: {{"action": "tool", "tool": "calculator", "resource_uri": null, "params": {{"operation": "add", "num1": 15, "num2": 27}}, "confidence": 0.98, "reasoning": "Clear arithmetic addition request"}}
User: "Find sine of 30 degrees"
Response: {{"action": "tool", "tool": "trig", "resource_uri": null, "params": {{"operation": "sine", "num1": 30, "unit": "degree"}}, "confidence": 0.95, "reasoning": "Trigonometric function request with angle in degrees"}}
User: "Tell me about Apple as a company"
Response: {{"action": "resource", "tool": null, "resource_uri": "stock://AAPL", "params": {{}}, "confidence": 0.90, "reasoning": "Request for Apple company information from stock resource"}}
User: "Get Apple stock price and company info"
Response: {{"action": "both", "tool": "stock_quote", "resource_uri": "stock://AAPL", "params": {{"ticker": "AAPL"}}, "confidence": 0.95, "reasoning": "Request for both current price and company information"}}
User: "server info"
Response: {{"action": "resource", "tool": null, "resource_uri": "info://server", "params": {{}}, "confidence": 0.95, "reasoning": "Request for server information"}}
Remember: Respond with ONLY the JSON object, no additional text.
"""
async def parse_query_with_llm(self, query: str, available_tools: List[Dict], available_resources: List[Dict] = None) -> Optional[Dict[str, Any]]:
"""Use LLM to parse the query"""
if not self.client:
logging.error("โ LLM client not available, falling back to rule-based parsing")
return None
system_prompt = self.get_system_prompt(available_tools, available_resources)
try:
if self.provider == "openai":
response = self.client.chat.completions.create(
model=self.model_name,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": query}
],
temperature=0.1,
max_tokens=300
)
llm_response = response.choices[0].message.content.strip()
elif self.provider == "anthropic":
response = self.client.messages.create(
model=self.model_name,
max_tokens=300,
temperature=0.1,
system=system_prompt,
messages=[{"role": "user", "content": query}]
)
llm_response = response.content[0].text.strip()
elif self.provider == "ollama":
import requests
payload = {
"model": self.model_name,
"prompt": f"{system_prompt}\n\nUser: {query}\nResponse:",
"stream": False,
"options": {
"temperature": 0.1,
"num_predict": 300
}
}
response = requests.post("http://localhost:11434/api/generate", json=payload, timeout=30)
if response.status_code == 200:
llm_response = response.json()["response"].strip()
else:
logging.error(f"Ollama API error: {response.status_code}")
return None
elif self.provider == "google":
response = self.client.generate_content(
f"{system_prompt}\n\nUser: {query}",
generation_config={
"temperature": 0.1,
"max_output_tokens": 300
}
)
llm_response = response.text.strip()
elif self.provider == "bedrock":
import json as json_lib
request_body = {
"anthropic_version": "bedrock-2023-05-31",
"max_tokens": 300,
"temperature": 0.1,
"system": system_prompt,
"messages": [
{
"role": "user",
"content": query
}
]
}
response = self.client.invoke_model(
modelId=self.bedrock_model_id,
body=json_lib.dumps(request_body),
contentType="application/json",
accept="application/json"
)
response_body = json_lib.loads(response['body'].read())
llm_response = response_body['content'][0]['text'].strip()
logging.debug(f"Bedrock response usage: {response_body.get('usage', {})}")
# Parse the JSON response
logging.debug(f"LLM Response: {llm_response}")
# Clean up the response - sometimes LLMs add extra text
if llm_response.startswith("```json"):
llm_response = llm_response.replace("```json", "").replace("```", "").strip()
elif llm_response.startswith("```"):
llm_response = llm_response.replace("```", "").strip()
# Handle case where LLM wraps response in additional text
try:
parsed_response = json.loads(llm_response)
except json.JSONDecodeError:
# Try to extract JSON from the response
import re
json_match = re.search(r'\{.*\}', llm_response, re.DOTALL)
if json_match:
parsed_response = json.loads(json_match.group())
else:
raise
# Validate the response structure
if not isinstance(parsed_response, dict):
logging.error("LLM response is not a dictionary")
return None
if parsed_response.get("action") is None:
logging.warning(f"LLM could not parse query: {parsed_response.get('reasoning', 'Unknown reason')}")
return None
confidence = parsed_response.get("confidence", 0.0)
if confidence < 0.5:
logging.warning(f"Low confidence ({confidence}) in LLM parsing: {parsed_response.get('reasoning')}")
return None
logging.info(f"๐ค LLM ({self.provider}:{getattr(self, 'model_name', 'unknown')}) parsed query: {parsed_response.get('reasoning')} (confidence: {confidence})")
return parsed_response
except json.JSONDecodeError as e:
logging.error(f"Failed to parse LLM JSON response: {e}")
logging.error(f"Raw LLM response: {llm_response}")
return None
except Exception as e:
logging.error(f"LLM query parsing error: {e}")
return None
# --- Enhanced Rule-Based Parser with Resource Support ---
class RuleBasedQueryParser:
"""Enhanced rule-based query parser with resource support"""
@staticmethod
def parse_calculator_query(query: str) -> Optional[Dict[str, Any]]:
import re
query = query.lower().strip()
patterns = [
("add", ["plus", "add", "+", "sum"]),
("subtract", ["minus", "subtract", "-", "difference"]),
("multiply", ["times", "multiply", "*", "ร", "product"]),
("divide", ["divide", "divided by", "/", "รท"]),
("power", ["power", "to the power", "raised to", "^", "**"]),
]
for operation, keywords in patterns:
for keyword in keywords:
if keyword in query:
numbers = re.findall(r'-?\d+(?:\.\d+)?', query)
if len(numbers) >= 2:
try:
return {
"action": "tool",
"tool": "calculator",
"resource_uri": None,
"params": {
"operation": operation,
"num1": float(numbers[0]),
"num2": float(numbers[1])
}
}
except ValueError:
pass
return None
@staticmethod
def parse_trig_query(query: str) -> Optional[Dict[str, Any]]:
import re
query_lower = query.lower().strip()
# Trigonometric operations
trig_patterns = [
("sine", ["sine", "sin"]),
("cosine", ["cosine", "cos"]),
("tangent", ["tangent", "tan"]),
("arc sine", ["arc sine", "arcsin", "asin", "inverse sine"]),
("arc cosine", ["arc cosine", "arccos", "acos", "inverse cosine"]),
("arc tangent", ["arc tangent", "arctan", "atan", "inverse tangent"]),
]
for operation, keywords in trig_patterns:
for keyword in keywords:
if keyword in query_lower:
# Extract number
numbers = re.findall(r'-?\d+(?:\.\d+)?', query)
if numbers:
# Determine unit
unit = "degree" # default
if any(word in query_lower for word in ["radian", "rad"]):
unit = "radian"
try:
return {
"action": "tool",
"tool": "trig",
"resource_uri": None,
"params": {
"operation": operation,
"num1": float(numbers[0]),
"unit": unit
}
}
except ValueError:
pass
return None
@staticmethod
def parse_stock_query(query: str) -> Optional[Dict[str, Any]]:
import re
query_lower = query.lower().strip()
# Check for company info requests
info_keywords = ["about", "company", "info", "information", "describe", "tell me about", "what is"]
is_info_request = any(keyword in query_lower for keyword in info_keywords)
# Check for price requests
price_keywords = ["stock", "price", "quote", "ticker", "share", "trading", "cost", "current"]
is_price_request = any(keyword in query_lower for keyword in price_keywords)
# Extract ticker symbols
tickers = re.findall(r'\b[A-Z]{2,5}\b', query.upper())
excluded_words = {"GET", "THE", "FOR", "AND", "BUT", "NOT", "YOU", "ALL", "CAN", "STOCK", "PRICE", "WHAT", "TELL", "ABOUT"}
valid_tickers = [t for t in tickers if t not in excluded_words]
# Check for common company names and map to tickers
company_mapping = {
"apple": "AAPL",
"google": "GOOGL",
"alphabet": "GOOGL",
"microsoft": "MSFT",
"tesla": "TSLA",
"amazon": "AMZN",
"meta": "META",
"facebook": "META",
"nvidia": "NVDA",
"netflix": "NFLX",
"salesforce": "CRM"
}
for company, ticker in company_mapping.items():
if company in query_lower:
valid_tickers.append(ticker)
if valid_tickers:
ticker = valid_tickers[0]
if is_info_request and is_price_request:
return {
"action": "both",
"tool": "stock_quote",
"resource_uri": f"stock://{ticker}",
"params": {"ticker": ticker}
}
elif is_info_request:
return {
"action": "resource",
"tool": None,
"resource_uri": f"stock://{ticker}",
"params": {}
}
elif is_price_request:
return {
"action": "tool",
"tool": "stock_quote",
"resource_uri": None,
"params": {"ticker": ticker}
}
return None
@staticmethod
def parse_server_info_query(query: str) -> Optional[Dict[str, Any]]:
query_lower = query.lower().strip()
server_keywords = ["server", "info", "information", "about server", "server status", "server details"]
if any(keyword in query_lower for keyword in server_keywords):
return {
"action": "resource",
"tool": None,
"resource_uri": "info://server",
"params": {}
}
return None
@staticmethod
def parse_query(query: str) -> Optional[Dict[str, Any]]:
# Health check
if any(word in query.lower() for word in ["health", "status", "ping", "alive"]):
return {
"action": "tool",
"tool": "health",
"resource_uri": None,
"params": {}
}
# Echo command
if query.lower().startswith("echo "):
message = query[5:].strip()
return {
"action": "tool",
"tool": "echo",
"resource_uri": None,
"params": {"message": message}
}
# Server info resource
server_result = RuleBasedQueryParser.parse_server_info_query(query)
if server_result:
return server_result
# Trigonometric functions
trig_result = RuleBasedQueryParser.parse_trig_query(query)
if trig_result:
return trig_result
# Calculator
calc_result = RuleBasedQueryParser.parse_calculator_query(query)
if calc_result:
return calc_result
# Stock queries (including resources)
stock_result = RuleBasedQueryParser.parse_stock_query(query)
if stock_result:
return stock_result
return None
# --- Resource Handling Functions ---
def extract_resource_data(result):
"""Extract data from resource result"""
try:
if isinstance(result, list) and len(result) > 0:
content_item = result[0]
if hasattr(content_item, 'text'):
return content_item.text
else:
return str(content_item)
elif hasattr(result, 'content') and result.content:
content_item = result.content[0]
if hasattr(content_item, 'text'):
return content_item.text
else:
return str(content_item)
else:
return str(result)
except Exception as e:
logging.error(f"Error extracting resource data: {e}")
return f"Could not parse resource: {e}"
def format_resource_result(resource_uri: str, content: str) -> str:
"""Format resource results for display"""
if resource_uri.startswith("info://server"):
return f"๐ฅ๏ธ Server Info: {content}"
elif resource_uri.startswith("stock://"):
ticker = resource_uri.split("://")[1]
return f"๐ข Company Info ({ticker}): {content}"
else:
return f"๐ Resource ({resource_uri}): {content}"
# --- Tool Result Extraction and Formatting ---
def extract_result_data(result):
"""Extract actual data from FastMCP result object"""
try:
if isinstance(result, list) and len(result) > 0:
content_item = result[0]
if hasattr(content_item, 'text'):
try:
return json.loads(content_item.text)
except json.JSONDecodeError:
return {"text": content_item.text}
else:
return {"content": str(content_item)}
elif hasattr(result, 'content') and result.content:
content_item = result.content[0]
if hasattr(content_item, 'text'):
try:
return json.loads(content_item.text)
except json.JSONDecodeError:
return {"text": content_item.text}
else:
return {"content": str(content_item)}
else:
if isinstance(result, dict):
return result
else:
return {"result": str(result)}
except Exception as e:
logging.error(f"Error extracting result data: {e}")
return {"error": f"Could not parse result: {e}"}
def format_result(tool_name: str, result: Dict) -> str:
"""Format tool results for display"""
if isinstance(result, dict) and "error" in result:
return f"โ Error: {result['error']}"
if tool_name == "calculator":
if "result" in result:
expression = result.get('expression')
if expression:
return f"๐งฎ {expression}"
else:
num1 = result.get('num1', '?')
num2 = result.get('num2', '?')
operation = result.get('operation', '?')
calc_result = result.get('result', '?')
return f"๐งฎ {num1} {operation} {num2} = {calc_result}"
elif "error" in result:
return f"โ Calculator Error: {result['error']}"
elif tool_name == "trig":
if "result" in result:
expression = result.get('expression')
if expression:
return f"๐ {expression}"
else:
operation = result.get('operation', '?')
num1 = result.get('num1', '?')
unit = result.get('unit', '?')
trig_result = result.get('result', '?')
return f"๐ {operation}({num1}ยฐ) = {trig_result} [{unit}]"
elif "error" in result:
return f"โ Trig Error: {result['error']}"
elif tool_name == "stock_quote":
if "current_price" in result:
ticker = result.get('ticker', 'Unknown')
name = result.get('company_name', ticker)
price = result.get('current_price', 0)
currency = result.get('currency', 'USD')
extra_info = []
if result.get('volume'):
extra_info.append(f"Vol: {result['volume']:,}")
if result.get('day_high') and result.get('day_low'):
extra_info.append(f"Range: {result['day_low']}-{result['day_high']}")
extra = f" ({', '.join(extra_info)})" if extra_info else ""
return f"๐ {name} ({ticker}): {currency} {price}{extra}"
elif "error" in result:
return f"โ Stock Error: {result['error']}"
elif tool_name == "health":
if isinstance(result, dict):
return f"โ
{result.get('message', 'Server is healthy')}"
elif tool_name == "echo":
if isinstance(result, dict):
return f"๐ {result.get('echo', result.get('message', str(result)))}"
try:
return f"โ
Result: {json.dumps(result, indent=2)}"
except (TypeError, ValueError):
return f"โ
Result: {str(result)}"
# --- Main Demo Function ---
async def run_enhanced_demo():
print("๐ Enhanced LLM-powered MCP client demo starting...")
print(f"๐ค Using LLM Provider: {LLM_PROVIDER}")
# Initialize parsers
llm_parser = LLMQueryParser(LLM_PROVIDER)
fallback_parser = RuleBasedQueryParser()
try:
print(f"๐ก Connecting to MCP server: {MCP_SERVER_PATH}")
async with Client(MCP_SERVER_PATH) as client:
print("โ
Connected to MCP server!")
# Discover available tools and resources
available_tools = []
available_resources = []
try:
# Get tools
tools = await client.list_tools()
if tools:
available_tools = [
{"name": tool.name, "description": tool.description} for tool in tools
]
print(f"โ
Found {len(available_tools)} tools")
# Get resources
try:
resources = await client.list_resources()
if resources:
available_resources = [
{"uri": resource.uri, "description": resource.description}
for resource in resources
]
print(f"โ
Found {len(available_resources)} resources")
except Exception as e:
logging.warning(f"Resource discovery failed: {e}")
available_resources = []
# Add known dynamic resources that don't appear in discovery
dynamic_resources = [
{"uri": "stock://{ticker}", "description": "Stock company information for any ticker"}
]
available_resources.extend(dynamic_resources)
if not available_tools:
# Fallback tool definitions
available_tools = [
{"name": "calculator", "description": "Perform arithmetic operations"},
{"name": "trig", "description": "Performs trigonometric operations"},
{"name": "stock_quote", "description": "Get stock price data"},
{"name": "health", "description": "Check server health"},
{"name": "echo", "description": "Echo back messages"}
]
if not available_resources:
# Fallback resource definitions
available_resources = [
{"uri": "info://server", "description": "Server information"},
{"uri": "stock://{ticker}", "description": "Stock company information for any ticker"}
]
except Exception as e:
logging.error(f"Tool/Resource discovery failed: {e}")
print(f"\n{'='*70}")
print("๐ฏ Enhanced LLM-Powered MCP Client Demo!")
print(f"๐ค Model: {getattr(llm_parser, 'model_name', 'Unknown')} ({LLM_PROVIDER})")
print("\n๐ Try natural language queries:")
print(" Tool examples:")
print(" โข 'What's fifteen plus twenty seven?'")
print(" โข 'Can you multiply 12 by 8?'")
print(" โข 'Find sine of 30 degrees'")
print(" โข 'Calculate cosine of pi/4 radians'")
print(" โข 'I need the current Apple stock price'")
print(" โข 'How much is Tesla trading for?'")
print(" โข 'Please echo back: Hello AI!'")
print(" โข 'Is the server working properly?'")
print("\n Resource examples:")
print(" โข 'Tell me about Apple as a company'")
print(" โข 'What is Microsoft?'")
print(" โข 'Give me server information'")
print(" โข 'Show me info about Tesla'")
print("\n Combined examples:")
print(" โข 'Get Apple stock price and company info'")
print(" โข 'I want both Tesla's price and company details'")
print("\n๐ก Commands:")
print(" โข 'tools' - List available tools")
print(" โข 'resources' - List available resources")
print(" โข 'switch' - Switch parsing mode")
print(" โข 'provider [name]' - Switch LLM provider")
print(" โข 'exit' - Quit the demo")
print(f"{'='*70}")
use_llm = llm_parser.client is not None
while True:
try:
user_input = input(f"\n๐ฌ Your query {'๐ค' if use_llm else '๐ง'}: ").strip()
if not user_input:
continue
if user_input.lower() in ('exit', "quit", "bye", "q"):
print("๐ Goodbye!")
break
if user_input.lower() == 'switch':
use_llm = not use_llm
mode = "LLM" if use_llm else "Rule-based"
print(f"๐ Switched to {mode} parsing mode")
continue
if user_input.lower().startswith('provider '):
new_provider = user_input[9:].strip()
if new_provider in LLM_MODELS:
print(f"๐ Switching to {new_provider}...")
llm_parser = LLMQueryParser(new_provider)
use_llm = llm_parser.client is not None
print(f"โ
Now using {new_provider}" if use_llm else f"โ Failed to initialize {new_provider}")
else:
print(f"โ Unknown provider. Available: {', '.join(LLM_MODELS)}")
continue
if user_input.lower() == 'tools':
print("\n๐ง Available tools:")
for tool in available_tools:
print(f" โข {tool['name']}: {tool['description']}")
continue
if user_input.lower() == 'resources':
print("\n๐ Available resources:")
for resource in available_resources:
print(f" โข {resource['uri']}: {resource['description']}")
continue
# Parse the query
parsed_query = None
if use_llm and llm_parser.client:
parsed_query = await llm_parser.parse_query_with_llm(user_input, available_tools, available_resources)
# Fallback to rule-based if LLM fails
if not parsed_query:
if use_llm:
print("๐ LLM parsing failed, trying rule-based parser...")
parsed_query = fallback_parser.parse_query(user_input)
if not parsed_query:
print("โ I couldn't understand your query. Try rephrasing or being more specific.")
continue
# Execute based on action type
action = parsed_query.get("action")
tool_name = parsed_query.get("tool")
resource_uri = parsed_query.get("resource_uri")
parameters = parsed_query.get("params", {})
print(f"๐ฏ Action: {action}")
if parsed_query.get("reasoning"):
print(f"๐ญ Reasoning: {parsed_query['reasoning']}")
# Execute tool call if needed
if action in ["tool", "both"] and tool_name:
print(f"๐ง Calling tool: {tool_name}")
if parameters:
print(f"๐ Parameters: {json.dumps(parameters, indent=2)}")
try:
tool_result = await client.call_tool(tool_name, parameters)
tool_data = extract_result_data(tool_result)
print(format_result(tool_name, tool_data))
except Exception as e:
print(f"โ Error calling tool: {e}")
logging.error(f"Tool call error: {e}", exc_info=True)
# Execute resource read if needed
if action in ["resource", "both"] and resource_uri:
print(f"๐ Reading resource: {resource_uri}")
try:
resource_result = await client.read_resource(resource_uri)
resource_content = extract_resource_data(resource_result)
print(format_resource_result(resource_uri, resource_content))
except Exception as e:
print(f"โ Error reading resource: {e}")
logging.error(f"Resource read error: {e}", exc_info=True)
# Handle case where no valid action was determined
if not action or action not in ["tool", "resource", "both"]:
print("โ Could not determine appropriate action for your query.")
except KeyboardInterrupt:
print("\n\n๐ Goodbye!")
break
except Exception as e:
logging.error(f"Unexpected error: {e}", exc_info=True)
print(f"โ Unexpected error: {e}")
except Exception as e:
print(f"โ Failed to connect to server: {e}")
print("\nMake sure the server file exists and FastMCP is installed:")
print(" pip install fastmcp yfinance")
print(f" Ensure {MCP_SERVER_PATH} exists in the current directory")
def main():
"""Run the enhanced LLM-powered async demo"""
print("๐ค Enhanced LLM-Powered MCP Client")
print("=" * 50)
print(f"Selected LLM Provider: {LLM_PROVIDER}")
print("\n๐ Setup Instructions:")
print("Available LLM providers:")
print(" โข OpenAI (GPT-4o-mini): Set OPENAI_API_KEY")
print(" โข Anthropic (Claude 3.5 Sonnet) - (Default): Set ANTHROPIC_API_KEY")
print(" โข Google Gemini: Set GEMINI_API_KEY")
print(" โข Ollama (Local): Install and run Ollama")
print(" โข AWS Bedrock: Configure AWS credentials")
print("=" * 50)
asyncio.run(run_enhanced_demo())
if __name__ == '__main__':
main()