MCP Testbed
This project demonstrates sentiment analysis using the Model Context Protocol (MCP) and is based on the Hugging Face MCP Course. Instead of using the gradio
library in the HuggingFace course, this project utilizes the fastmcp
library to implement an MCP server and client.
Main Components
Architecture Diagram
+-------------------+ +-------------------+
| | | |
| mcp_client_stdio |<----------------->| |
| | stdio | |
+-------------------+ | |
| |
+-------------------+ | |
| |<----------------->| |
| mcp_client_sse |-----/sse--------->| MCP Server |
| | | (app_fastmcp.py) |
+-------------------+ | |
| |
+-------------------+ | |
| |<----------------->| |
| mcp_client_stream-|----/mcp---------->| |
| able.py | | |
+-------------------+ +-------------------+
Each client module communicates with the MCP server using a different transport protocol:
mcp_client_stdio.py
uses stdiomcp_client_sse.py
uses SSE (Server-Sent Events) at /sse
mcp_client_streamable.py
uses streamable-http at /mcp
Server
- mcp-sentiment/app_fastmcp.py
Implements the MCP server using FastMCP. Exposes a sentiment_analysis
tool that uses TextBlob to analyze the sentiment of input text and returns polarity, subjectivity, and an overall assessment as JSON.
Clients
- mcp-sentiment/mcp_client_stdio.py
MCP client using stdio transport. Connects to the server via subprocess, lists available tools, and sends text for sentiment analysis. Uses argparse for CLI. - mcp-sentiment/mcp_client_sse.py
MCP client using Server-Sent Events (SSE) transport. Connects to the server via HTTP SSE endpoint, lists tools, and sends text for analysis. Uses argparse for CLI. - mcp-sentiment/mcp_client_streamable.py
MCP client using streamable-http transport. Connects to the server via HTTP with bidirectional streaming, lists tools, and sends text for analysis. Uses argparse for CLI.
Tests
- tests/test_sentiment_analysis.py
Unit tests for the sentiment analysis tool, checking various input cases and error handling. - tests/test_client_stdio.py
Tests for the stdio client, including unit tests for request handling and integration tests for end-to-end sentiment analysis. - tests/test_client_sse.py
Integration tests for the SSE client, including server process management and sentiment analysis checks. - tests/test_client_streamable.py
Integration tests for the streamable-http client, including server process management and sentiment analysis checks.
Directory Structure
mcp_testlab/
├── mcp-sentiment/
│ ├── app_fastmcp.py # MCP server exposing sentiment_analysis tool
│ ├── mcp_client_stdio.py # MCP client (stdio transport)
│ ├── mcp_client_sse.py # MCP client (SSE transport)
│ └── mcp_client_streamable.py # MCP client (streamable-http transport)
├── tests/
│ ├── test_sentiment_analysis.py # Unit tests for sentiment_analysis tool
│ ├── test_client_stdio.py # Tests for stdio client
│ ├── test_client_sse.py # Tests for SSE client
│ └── test_client_streamable.py # Tests for streamable-http client
├── requirements.txt # Python dependencies
├── README.md # Project documentation and usage
├── LICENSE # License file
├── .gitignore # Git ignore rules
└── .gitattributes # Git LFS attributes
How It Works
The application uses Model Context Protocol (MCP) to facilitate communication between a client and a sentiment analysis server. When you run the client with a text string, it:
- Connects to the server script (
app_fastmcp.py
) or sse
/streamable-http
endpoint - Sends your text for sentiment analysis
- Returns the sentiment result (positive, negative, or neutral)
Server (app_fastmcp.py)
The app_fastmcp.py
file implements an MCP server using FastMCP that:
- Exposes a sentiment analysis tool via the Model Context Protocol
- Uses TextBlob library to analyze sentiment of provided text
- Returns JSON results with:
- Polarity score (-1 to 1, negative to positive)
- Subjectivity score (0 to 1, objective to subjective)
- Overall assessment (positive, negative, or neutral)
- Supports
stdio
or sse
or streamable-http
transport for communication with clients
Client (mcp_client_stdio.py)
The mcp_client_stdio.py
file implements an MCP client that:
- Uses the argparse library for command-line argument handling
- Accepts text input as a required positional argument
- Accepts an optional
--server
parameter to specify the server script path - Accepts an optional
--verbose
flag to display detailed tool information - Establishes a connection to the MCP server using stdio transport
- Lists available tools on the connected server
- Sends the input text to the sentiment_analysis tool
- Displays formatted sentiment results showing polarity, subjectivity, and assessment
- Properly manages resources with async context managers and AsyncExitStack
Client (mcp_client_sse.py)
The mcp_client_sse.py
file implements an MCP client that:
- Uses the argparse library for command-line argument handling
- Accepts text input as a required positional argument
- Accepts an optional
--url
parameter to specify the server endpoint (default: http://localhost:8000/sse) - Accepts an optional
--verbose
flag to display detailed tool information - Establishes a connection to the MCP server using SSE transport
- Lists available tools on the connected server
- Sends the input text to the sentiment_analysis tool
- Displays formatted sentiment results showing polarity, subjectivity, and assessment
- Properly manages resources with async context managers and AsyncExitStack
Client (mcp_client_streamable.py)
The mcp_client_streamable.py
file implements an MCP client that:
- Uses the argparse library for command-line argument handling
- Accepts text input as a required positional argument
- Accepts an optional
--url
parameter to specify the server endpoint (default: http://localhost:8000/mcp) - Accepts an optional
--verbose
flag to display detailed tool information - Establishes a connection to the MCP server using streamable-http transport
- Displays the session ID when available
- Lists available tools on the connected server
- Sends the input text to the sentiment_analysis tool
- Displays formatted sentiment results showing polarity, subjectivity, and assessment
- Properly manages resources with async context managers and AsyncExitStack
Example Usage with stdio
Transport
Command-line Arguments
((venv) ) Mac:jim mcp_testlab[529]$ python mcp-sentiment/mcp_client_stdio.py --help
usage: mcp_client_stdio.py [-h] [--server SERVER] [--verbose] text
MCP Client for sentiment analysis using stdio transport
positional arguments:
text Text to analyze for sentiment
options:
-h, --help show this help message and exit
--server SERVER Path to the MCP server script (default: mcp-sentiment/app_fastmcp.py)
--verbose, -v Display detailed information about available tools
**Client**
```bash
((venv) ) Mac:jim mcp_testlab[512]$ python mcp-sentiment/mcp_client_stdio.py "i love mcp"
Connected to MCP server at mcp-sentiment/app_fastmcp.py
Listing available tools...
[07/29/25 13:45:16] INFO Processing request of type ListToolsRequest server.py:619
Connected to MCP server. Listing available tools...
INFO Processing request of type ListToolsRequest server.py:619
Available tools: ['sentiment_analysis']
Analyzing sentiment for: 'i love mcp'
INFO Processing request of type CallToolRequest server.py:619
{'polarity': 0.5, 'subjectivity': 0.6, 'assessment': 'positive'}
Sentiment Analysis Result:
Polarity: 0.5 (-1=negative, 1=positive)
Subjectivity: 0.6 (0=objective, 1=subjective)
Assessment: positive
Sentiment Analysis Result: (0.5, 0.6, 'positive')
((venv) ) Mac:jim mcp_testlab[513]$ python mcp-sentiment/mcp_client_stdio.py "i hate java"
Connected to MCP server at mcp-sentiment/app_fastmcp.py
Listing available tools...
[07/29/25 13:45:38] INFO Processing request of type ListToolsRequest server.py:619
Connected to MCP server. Listing available tools...
INFO Processing request of type ListToolsRequest server.py:619
Available tools: ['sentiment_analysis']
Analyzing sentiment for: 'i hate java'
INFO Processing request of type CallToolRequest server.py:619
{'polarity': -0.8, 'subjectivity': 0.9, 'assessment': 'negative'}
Sentiment Analysis Result:
Polarity: -0.8 (-1=negative, 1=positive)
Subjectivity: 0.9 (0=objective, 1=subjective)
Assessment: negative
Sentiment Analysis Result: (-0.8, 0.9, 'negative')
((venv) ) Mac:jim mcp_testlab[514]$ python mcp-sentiment/mcp_client_stdio.py "i really like python" -v
Connected to MCP server at mcp-sentiment/app_fastmcp.py
Listing available tools...
[07/29/25 13:46:35] INFO Processing request of type ListToolsRequest server.py:619
Connected to MCP server. Listing available tools...
INFO Processing request of type ListToolsRequest server.py:619
sentiment_analysis:
Description:
Analyze the sentiment of the given text.
Args:
text (str): The text to analyze
Returns:
str: A JSON string containing polarity, subjectivity, and assessment
Annotations: None
Inputschema: {'properties': {'text': {'title': 'Text', 'type': 'string'}}, 'required': ['text'], 'title': 'sentiment_analysisArguments', 'type': 'object'}
Meta: None
/Users/jim/Desktop/modelcontextprotocol/mcp_testlab/mcp-sentiment/mcp_client_stdio.py:154: PydanticDeprecatedSince211: Accessing the 'model_computed_fields' attribute on the instance is deprecated. Instead, you should access this attribute from the model class. Deprecated in Pydantic V2.11 to be removed in V3.0.
value = getattr(tool, attr)
Model_computed_fields: {}
Model_config: {'extra': 'allow'}
Model_extra: {}
/Users/jim/Desktop/modelcontextprotocol/mcp_testlab/mcp-sentiment/mcp_client_stdio.py:154: PydanticDeprecatedSince211: Accessing the 'model_fields' attribute on the instance is deprecated. Instead, you should access this attribute from the model class. Deprecated in Pydantic V2.11 to be removed in V3.0.
value = getattr(tool, attr)
Model_fields: {'name': FieldInfo(annotation=str, required=True), 'title': FieldInfo(annotation=Union[str, NoneType], required=False, default=None), 'description': FieldInfo(annotation=Union[str, NoneType], required=False, default=None), 'inputSchema': FieldInfo(annotation=dict[str, Any], required=True), 'outputSchema': FieldInfo(annotation=Union[dict[str, Any], NoneType], required=False, default=None), 'annotations': FieldInfo(annotation=Union[ToolAnnotations, NoneType], required=False, default=None), 'meta': FieldInfo(annotation=Union[dict[str, Any], NoneType], required=False, default=None, alias='_meta', alias_priority=2)}
Model_fields_set: {'description', 'inputSchema', 'outputSchema', 'name'}
Outputschema: {'properties': {'result': {'title': 'Result', 'type': 'string'}}, 'required': ['result'], 'title': 'sentiment_analysisOutput', 'type': 'object'}
Title: None
Analyzing sentiment for: 'i really like python'
INFO Processing request of type CallToolRequest server.py:619
{'polarity': 0.2, 'subjectivity': 0.2, 'assessment': 'positive'}
Sentiment Analysis Result:
Polarity: 0.2 (-1=negative, 1=positive)
Subjectivity: 0.2 (0=objective, 1=subjective)
Assessment: positive
Sentiment Analysis Result: (0.2, 0.2, 'positive')
Example Usage with sse
Transport
Command-line Arguments
((venv) ) Mac:jim mcp_testlab[532]$ python mcp-sentiment/mcp_client_sse.py --help
usage: mcp_client_sse.py [-h] [--url URL] [--verbose] text_to_test
MCP SSE Client for Sentiment Analysis
positional arguments:
text_to_test Text to analyze for sentiment
options:
-h, --help show this help message and exit
--url URL URL of the SSE server endpoint (default: http://localhost:8000/sse)
--verbose, -v Display detailed information about available tools
Client
python mcp-sentiment/mcp_client_sse.py "i really like python"
Connecting to MCP server at http://localhost:8000/sse...
Connected to MCP server. Listing available tools...
Available tools: ['sentiment_analysis']
Analyzing sentiment for: 'i really like python'
Sentiment Analysis Result:
Polarity: 0.2 (-1=negative, 1=positive)
Subjectivity: 0.2 (0=objective, 1=subjective)
Assessment: positive
SSE Server
((venv) ) Mac:jim mcp_testlab[506]$ python mcp-sentiment/app_fastmcp.py --transport sse
INFO: Started server process [8908]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: 127.0.0.1:49538 - "GET /sse HTTP/1.1" 200 OK
INFO: 127.0.0.1:49540 - "POST /messages/?session_id=fb1a89915f0b40e98f44385af5b6db58 HTTP/1.1" 202 Accepted
INFO: 127.0.0.1:49540 - "POST /messages/?session_id=fb1a89915f0b40e98f44385af5b6db58 HTTP/1.1" 202 Accepted
INFO: 127.0.0.1:49540 - "POST /messages/?session_id=fb1a89915f0b40e98f44385af5b6db58 HTTP/1.1" 202 Accepted
[07/29/25 06:13:08] INFO Processing request of type ListToolsRequest server.py:619
INFO: 127.0.0.1:49540 - "POST /messages/?session_id=fb1a89915f0b40e98f44385af5b6db58 HTTP/1.1" 202 Accepted
INFO Processing request of type CallToolRequest server.py:619
INFO: 127.0.0.1:49543 - "GET /sse HTTP/1.1" 200 OK
INFO: 127.0.0.1:49545 - "POST /messages/?session_id=632a380ef69344eab4bf145158e05051 HTTP/1.1" 202 Accepted
INFO: 127.0.0.1:49545 - "POST /messages/?session_id=632a380ef69344eab4bf145158e05051 HTTP/1.1" 202 Accepted
INFO: 127.0.0.1:49545 - "POST /messages/?session_id=632a380ef69344eab4bf145158e05051 HTTP/1.1" 202 Accepted
[07/29/25 06:13:46] INFO Processing request of type ListToolsRequest server.py:619
INFO: 127.0.0.1:49545 - "POST /messages/?session_id=632a380ef69344eab4bf145158e05051 HTTP/1.1" 202 Accepted
INFO Processing request of type CallToolRequest server.py:619
INFO: 127.0.0.1:49547 - "GET /sse HTTP/1.1" 200 OK
INFO: 127.0.0.1:49549 - "POST /messages/?session_id=f6a51e126c12412398bacc85753174a3 HTTP/1.1" 202 Accepted
INFO: 127.0.0.1:49549 - "POST /messages/?session_id=f6a51e126c12412398bacc85753174a3 HTTP/1.1" 202 Accepted
INFO: 127.0.0.1:49549 - "POST /messages/?session_id=f6a51e126c12412398bacc85753174a3 HTTP/1.1" 202 Accepted
[07/29/25 06:14:17] INFO Processing request of type ListToolsRequest server.py:619
INFO: 127.0.0.1:49549 - "POST /messages/?session_id=f6a51e126c12412398bacc85753174a3 HTTP/1.1" 202 Accepted
INFO Processing request of type CallToolRequest server.py:619
INFO: Shutting down
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
INFO: Finished server process [8908]
Terminated: 15 python mcp-sentiment/app_fastmcp.py --transport sse
Example Usage with streamable-http
Transport
Command-line Arguments
((venv) ) Mac:jim mcp_testlab[533]$ python mcp-sentiment/mcp_client_streamable.py --help
usage: mcp_client_streamable.py [-h] [--url URL] [--verbose] text_to_test
MCP Streamable Client for Sentiment Analysis
positional arguments:
text_to_test Text to analyze for sentiment
options:
-h, --help show this help message and exit
--url URL URL of the streamable-http server endpoint (default: http://localhost:8000/mcp)
--verbose, -v Display detailed information about available tools
Client
# Using the default URL
((venv) ) Mac:jim mcp_testlab[517]$ python mcp-sentiment/mcp_client_streamable.py "MCP is the best"
Connecting to MCP server at http://localhost:8000/mcp...
Session ID: fdc3c721f04441a1ae4c22cadebc9226
Connected to MCP server. Listing available tools...
Available tools: ['sentiment_analysis']
Analyzing sentiment for: 'MCP is the best'
Sentiment Analysis Result:
Polarity: 1.0 (-1=negative, 1=positive)
Subjectivity: 0.3 (0=objective, 1=subjective)
Assessment: positive
Streamable-HTTP Server
((venv) ) Mac:jim mcp_testlab[507]$ python mcp-sentiment/app_fastmcp.py --transport streamable-http
INFO: Started server process [2701]
INFO: Waiting for application startup.
[07/28/25 22:48:20] INFO StreamableHTTP session manager started streamable_http_manager.py:112
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: 127.0.0.1:54049 - "POST /mcp HTTP/1.1" 307 Temporary Redirect
[07/28/25 22:48:29] INFO Created new transport with session ID: 6a17c2206e6e478b830bd0da73771b8b streamable_http_manager.py:229
INFO: 127.0.0.1:54049 - "POST /mcp/ HTTP/1.1" 200 OK
INFO: 127.0.0.1:54052 - "POST /mcp HTTP/1.1" 307 Temporary Redirect
INFO: 127.0.0.1:54053 - "GET /mcp HTTP/1.1" 307 Temporary Redirect
INFO: 127.0.0.1:54052 - "POST /mcp/ HTTP/1.1" 202 Accepted
INFO: 127.0.0.1:54053 - "GET /mcp/ HTTP/1.1" 200 OK
INFO: 127.0.0.1:54055 - "POST /mcp HTTP/1.1" 307 Temporary Redirect
INFO: 127.0.0.1:54055 - "POST /mcp/ HTTP/1.1" 200 OK
INFO Processing request of type ListToolsRequest server.py:619
INFO: 127.0.0.1:54057 - "POST /mcp HTTP/1.1" 307 Temporary Redirect
INFO: 127.0.0.1:54057 - "POST /mcp/ HTTP/1.1" 200 OK
INFO Processing request of type CallToolRequest server.py:619
INFO: 127.0.0.1:54059 - "DELETE /mcp HTTP/1.1" 307 Temporary Redirect
INFO Terminating session: 6a17c2206e6e478b830bd0da73771b8b streamable_http.py:633
INFO: 127.0.0.1:54059 - "DELETE /mcp/ HTTP/1.1" 200 OK
INFO: Shutting down
INFO: Waiting for application shutdown.
[07/28/25 22:48:42] INFO StreamableHTTP session manager shutting down streamable_http_manager.py:116
INFO: Application shutdown complete.
INFO: Finished server process [2701]
Terminated: 15 python mcp-sentiment/app_fastmcp.py --transport streamable-http
Unit Tests
Unit tests are provided to ensure the functionality of the MCP server and client. To run the tests, use the following command:
Sample output:

MCP Inspector
The MCP Inspector is a tool for exploring and interacting with Model Context Protocol (MCP) servers. It provides a user-friendly interface for:
- Discovering available tools and their capabilities
- Sending requests to tools and viewing responses
- Debugging and testing MCP interactions
Running MCP Inspector with stdio
Transport
To run the MCP Inspector for server using stdio
transport, use the following command:
mcp dev mcp-sentiment/app_fastmcp.py
Sample output will show the available tools and their descriptions, allowing you to interact with the sentiment analysis tool.

MCP Inspector Testing Sentiment Analysis

Running MCP Inspector with sse
Transport
Starting the sse
Server for testing
python mcp-sentiment/app_fastmcp.py --transport sse

To run the MCP Inspector for server using sse
or streamable-http
transport, use the following command:
npx @modelcontextprotocol/inspector
Connecting to the MCP Inspector
Open the browser to access the MCP Inspector interface, change from http
to https
if necessary. Once the MCP Inspector is running, configure "Transport Type" for sse
or streamable-http
and set the server URL to point to your running MCP server (e.g., http://localhost:8000/sse
or http://localhost:8000/mcp
) and click "Connect" button.
sse
Server URL: http://localhost:8000/sse


Starting the streamable-http
Server for testing
python mcp-sentiment/app_fastmcp.py --transport streamable-http
streamable-http
Server URL: http://localhost:8000/mcp

Requirements
- Python 3.12+
- Dependencies:
pip install -r requirements.txt
- Required NLP libraries for sentiment analysis
- Required version of
node
> v20.x to run MCP Inspector (see GH Issue on unexpected token)