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