#!/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()