mcp-server / app.py
Peter Yang
support gradio mcp version
197b873
#!/usr/bin/env python3
"""
MCP Sentiment Analysis Server with Docker Support
This is a Model Context Protocol (MCP) server that provides sentiment analysis
capabilities using TextBlob. It's designed to run in Docker containers and
can be deployed to Hugging Face Spaces.
The server provides the following tools:
- analyze_sentiment: Analyze the sentiment of text
- get_sentiment_score: Get numerical sentiment score
- classify_emotion: Classify text into emotion categories
"""
import gradio as gr
from textblob import TextBlob
import json
import re
from typing import Dict, Any, List, Tuple
def improved_sentiment_analysis(text: str) -> Tuple[float, float]:
"""
Improved sentiment analysis that handles negations better than TextBlob alone.
Returns:
tuple: (polarity, subjectivity)
"""
# Get TextBlob's analysis
blob = TextBlob(text)
original_polarity = blob.sentiment.polarity
subjectivity = blob.sentiment.subjectivity
# Define negation patterns
negation_patterns = [
r"\b(don't|dont|do not|doesn't|doesnt|does not|didn't|didnt|did not)\b",
r"\b(won't|wont|will not|wouldn't|wouldnt|would not|can't|cant|cannot)\b",
r"\b(shouldn't|shouldnt|should not|isn't|isnt|is not|aren't|arent|are not)\b",
r"\b(wasn't|wasnt|was not|weren't|werent|were not|haven't|havent|have not)\b",
r"\b(hasn't|hasnt|has not|hadn't|hadnt|had not|never|no|not)\b",
r"\b(hate|dislike|terrible|awful|horrible|bad|worst|disgusting)\b"
]
# Check for negations
text_lower = text.lower()
negation_found = any(re.search(pattern, text_lower) for pattern in negation_patterns)
# Adjust polarity if negation is found and TextBlob gave a positive result
if negation_found and original_polarity > 0:
# Flip the polarity for negated positive statements
adjusted_polarity = -abs(original_polarity) * 0.8 # Make it negative but slightly less extreme
else:
adjusted_polarity = original_polarity
return adjusted_polarity, subjectivity
def analyze_sentiment(text: str) -> str:
"""
Analyze the sentiment of the given text.
Args:
text: The text to analyze
Returns:
A detailed sentiment analysis including polarity and subjectivity
"""
if not text or not text.strip():
return "Error: Please provide text to analyze"
# Use improved sentiment analysis
polarity, subjectivity = improved_sentiment_analysis(text)
# Also get TextBlob's original analysis for comparison
blob = TextBlob(text)
original_polarity = blob.sentiment.polarity
# Determine sentiment category with more conservative thresholds
if polarity > 0.05:
sentiment = "Positive"
elif polarity < -0.05:
sentiment = "Negative"
else:
sentiment = "Neutral"
# Determine subjectivity level
if subjectivity > 0.6:
subjectivity_level = "Highly subjective"
elif subjectivity > 0.3:
subjectivity_level = "Moderately subjective"
else:
subjectivity_level = "Objective"
# Add debug information to help understand the analysis
debug_info = f"Original TextBlob: {original_polarity:.3f} -> Improved: {polarity:.3f}, subjectivity={subjectivity:.3f}"
result = {
"text": text,
"sentiment": sentiment,
"polarity": round(polarity, 3),
"subjectivity": round(subjectivity, 3),
"subjectivity_level": subjectivity_level,
"interpretation": f"The text is {sentiment.lower()} with a polarity of {polarity:.3f} and is {subjectivity_level.lower()}.",
"debug": debug_info
}
return json.dumps(result, indent=2)
def get_sentiment_score(text: str) -> str:
"""
Get a numerical sentiment score for the text.
Args:
text: The text to score
Returns:
A numerical score between -1 (very negative) and 1 (very positive)
"""
if not text or not text.strip():
return "Error: Please provide text to analyze"
# Use improved sentiment analysis
score, _ = improved_sentiment_analysis(text)
result = {
"text": text,
"sentiment_score": round(score, 3),
"scale": "Score ranges from -1 (very negative) to 1 (very positive)"
}
return json.dumps(result, indent=2)
def classify_emotion(text: str) -> str:
"""
Classify the text into basic emotion categories.
Args:
text: The text to classify
Returns:
Emotion classification based on sentiment analysis
"""
if not text or not text.strip():
return "Error: Please provide text to analyze"
blob = TextBlob(text)
polarity = blob.sentiment.polarity
subjectivity = blob.sentiment.subjectivity
# Simple emotion classification based on polarity and subjectivity
if polarity > 0.5:
emotion = "Joy"
elif polarity > 0.1:
emotion = "Contentment"
elif polarity < -0.5:
emotion = "Anger" if subjectivity > 0.5 else "Sadness"
elif polarity < -0.1:
emotion = "Disappointment"
else:
if subjectivity > 0.7:
emotion = "Confusion"
else:
emotion = "Neutral"
confidence = abs(polarity) if abs(polarity) > 0.1 else 0.1
result = {
"text": text,
"emotion": emotion,
"confidence": round(confidence, 3),
"polarity": round(polarity, 3),
"subjectivity": round(subjectivity, 3)
}
return json.dumps(result, indent=2)
def batch_analyze(texts: str) -> str:
"""
Analyze multiple texts at once (one per line).
Args:
texts: Multiple texts separated by newlines
Returns:
Batch analysis results
"""
if not texts or not texts.strip():
return "Error: Please provide texts to analyze (one per line)"
lines = [line.strip() for line in texts.split('\n') if line.strip()]
if not lines:
return "Error: No valid text lines found"
results = []
for i, text in enumerate(lines, 1):
# Use improved sentiment analysis
polarity, _ = improved_sentiment_analysis(text)
# Use the same conservative thresholds as analyze_sentiment
if polarity > 0.05:
sentiment = "Positive"
elif polarity < -0.05:
sentiment = "Negative"
else:
sentiment = "Neutral"
results.append({
"line": i,
"text": text,
"sentiment": sentiment,
"polarity": round(polarity, 3)
})
summary = {
"total_texts": len(results),
"positive": len([r for r in results if r["sentiment"] == "Positive"]),
"negative": len([r for r in results if r["sentiment"] == "Negative"]),
"neutral": len([r for r in results if r["sentiment"] == "Neutral"]),
"average_polarity": round(sum(r["polarity"] for r in results) / len(results), 3)
}
return json.dumps({
"summary": summary,
"results": results
}, indent=2)
# Create the main MCP interface - using TabbedInterface to expose all functions as MCP tools
demo = gr.TabbedInterface(
[
gr.Interface(
fn=analyze_sentiment,
inputs=gr.Textbox(placeholder="Enter text to analyze...", label="Text"),
outputs=gr.JSON(label="Analysis Result"),
title="Sentiment Analysis",
description="Analyze the sentiment of text using TextBlob"
),
gr.Interface(
fn=get_sentiment_score,
inputs=gr.Textbox(placeholder="Enter text to score...", label="Text"),
outputs=gr.JSON(label="Score Result"),
title="Sentiment Score",
description="Get numerical sentiment score (-1 to 1)"
),
gr.Interface(
fn=classify_emotion,
inputs=gr.Textbox(placeholder="Enter text to classify...", label="Text"),
outputs=gr.JSON(label="Emotion Result"),
title="Emotion Classification",
description="Classify text into emotion categories"
),
gr.Interface(
fn=batch_analyze,
inputs=gr.Textbox(placeholder="Enter multiple texts, one per line...", label="Texts", lines=5),
outputs=gr.JSON(label="Batch Results"),
title="Batch Analysis",
description="Analyze multiple texts at once"
)
],
tab_names=["Sentiment Analysis", "Sentiment Score", "Emotion Classification", "Batch Analysis"],
title="MCP Sentiment Analysis Server"
)
if __name__ == "__main__":
# Launch the Gradio app
# Note: MCP support may require gradio>=5.30.0
try:
demo.launch(mcp_server=True)
except TypeError:
# Fallback for older Gradio versions without MCP support
print("⚠️ MCP server parameter not supported in this Gradio version")
print(" The web interface will work, but MCP endpoint may not be available")
demo.launch()