File size: 9,058 Bytes
d884bf1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0fe0e3f
d884bf1
 
0fe0e3f
d884bf1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197b873
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
#!/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()