Skip to content

Commit 25a70eb

Browse files
readme
1 parent 7599967 commit 25a70eb

File tree

4 files changed

+45
-156
lines changed

4 files changed

+45
-156
lines changed

README.md

Lines changed: 18 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -6,32 +6,6 @@ A Python utility package for building Model Context Protocol (MCP) servers.
66

77

88

9-
## Table of Contents
10-
11-
- [mcp-utils](#mcp-utils)
12-
- [Table of Contents](#table-of-contents)
13-
- [Overview](#overview)
14-
- [Key Features](#key-features)
15-
- [Installation](#installation)
16-
- [Requirements](#requirements)
17-
- [Optional Dependencies](#optional-dependencies)
18-
- [Usage](#usage)
19-
- [Basic MCP Server](#basic-mcp-server)
20-
- [Flask Example](#flask-example)
21-
- [SQLAlchemy Transaction Handling Example](#sqlalchemy-transaction-handling-example)
22-
- [Running with Gunicorn](#running-with-gunicorn)
23-
- [Connecting with MCP Clients](#connecting-with-mcp-clients)
24-
- [Cursor](#cursor)
25-
- [Claude Desktop](#claude-desktop)
26-
- [Installing via Smithery](#installing-via-smithery)
27-
- [Installing via PyPI](#installing-via-pypi)
28-
- [Contributing](#contributing)
29-
- [Related Projects](#related-projects)
30-
- [License](#license)
31-
- [Testing with MCP Inspector](#testing-with-mcp-inspector)
32-
- [Installation](#installation-1)
33-
- [Usage](#usage-1)
34-
359
## Overview
3610

3711
`mcp-utils` provides utilities and helpers for building MCP-compliant servers in Python, with a focus on synchronous implementations using Flask. This package is designed for developers who want to implement MCP servers in their existing Python applications without the complexity of asynchronous code.
@@ -43,39 +17,37 @@ A Python utility package for building Model Context Protocol (MCP) servers.
4317
- Simple decorators for MCP endpoints
4418
- Synchronous implementation
4519
- HTTP protocol support
46-
- Redis response queue
47-
- Comprehensive Pydantic models for MCP schema
20+
- SQLite response queue
21+
- Comprehensive msgspec models for MCP schema
4822
- Built-in validation and documentation
4923

5024
## Installation
5125

52-
```bash
53-
pip install mcp-utils
54-
```
26+
Install from this repo
5527

5628
## Requirements
5729

5830
- Python 3.10+
59-
- Pydantic 2
31+
- msgspec >= 0.18
6032

6133
### Optional Dependencies
6234

6335
- Flask (for web server)
64-
- Gunicorn (for production deployment)
65-
- Redis (for response queue)
36+
6637

6738
## Usage
6839

6940
### Basic MCP Server
7041

71-
Here's a simple example of creating an MCP server:
42+
Here's a simple example of creating an MCP server (using the built-in SQLite queue):
7243

7344
```python
7445
from mcp_utils.core import MCPServer
46+
from mcp_utils.queue import SQLiteResponseQueue
7547
from mcp_utils.schema import GetPromptResult, Message, TextContent, CallToolResult
7648

77-
# Create a basic MCP server
78-
mcp = MCPServer("example", "1.0")
49+
# Create a basic MCP server with SQLite-backed queue
50+
mcp = MCPServer("example", "1.0", response_queue=SQLiteResponseQueue("responses.db"))
7951

8052
@mcp.prompt()
8153
def get_weather_prompt(city: str) -> GetPromptResult:
@@ -104,55 +76,20 @@ from version 2025-06-18.
10476

10577

10678
```python
107-
from flask import Flask, Response, url_for, request
79+
from flask import Flask, jsonify, request
80+
import msgspec
81+
from mcp_utils.core import MCPServer
82+
from mcp_utils.queue import SQLiteResponseQueue
10883

109-
# Create Flask app and MCP server with Redis queue
84+
# Create Flask app and MCP server with SQLite-backed queue
11085
app = Flask(__name__)
111-
mcp = MCPServer(
112-
"example",
113-
"1.0",
114-
)
86+
mcp = MCPServer("example", "1.0", response_queue=SQLiteResponseQueue("responses.db"))
11587

11688
@app.route("/mcp", methods=["POST"])
11789
def mcp_route():
11890
response = mcp.handle_message(request.get_json())
119-
return jsonify(response.model_dump(exclude_none=True))
120-
121-
122-
if __name__ == "__main__":
123-
app.run(debug=True)
124-
```
125-
126-
### SQLAlchemy Transaction Handling Example
127-
128-
For production use, you can integrate the MCP server with Flask, Redis, and SQLAlchemy for better message handling and database transaction management:
129-
130-
```python
131-
from flask import Flask, request
132-
from sqlalchemy.orm import Session
133-
from sqlalchemy import create_engine
134-
135-
# Create engine for PostgreSQL database
136-
engine = create_engine("postgresql://user:pass@localhost/dbname")
137-
138-
# Create Flask app and MCP server with Redis queue
139-
app = Flask(__name__)
140-
mcp = MCPServer(
141-
"example",
142-
"1.0",
143-
)
144-
145-
@app.route("/mcp", methods=["POST"])
146-
def mcp_route():
147-
with Session(engine) as session:
148-
try:
149-
response = mcp.handle_message(request.get_json())
150-
session.commit()
151-
except:
152-
session.rollback()
153-
raise
154-
else:
155-
return jsonify(response.model_dump(exclude_none=True))
91+
# Convert msgspec Struct to builtin types for jsonify
92+
return jsonify(msgspec.to_builtins(response))
15693

15794

15895
if __name__ == "__main__":
@@ -201,69 +138,13 @@ if __name__ == "__main__":
201138
FlaskApplication(app, options).run()
202139
```
203140

204-
## Connecting with MCP Clients
205-
206-
### Cursor
207-
208-
* Edit MCP settings and add to configuration
209-
210-
```json
211-
{
212-
"mcpServers": {
213-
"server-name": {
214-
"url": "http://localhost:9000/mcp"
215-
}
216-
}
217-
}
218-
```
219-
220-
### Claude Desktop
221-
222-
As of this writing, Claude Desktop does not support MCP through SSE and only supports stdio. To connect Claude Desktop with an MCP server, you'll need to use [mcp-proxy](https://github.com/sparfenyuk/mcp-proxy).
223-
224-
Configuration example for Claude Desktop:
225-
226-
```json
227-
{
228-
"mcpServers": {
229-
"weather": {
230-
"command": "/Users/yourname/.local/bin/mcp-proxy",
231-
"args": ["http://127.0.0.1:9000/sse"]
232-
}
233-
}
234-
}
235-
```
236-
237-
#### Installing via Smithery
238-
239-
To install MCP Proxy for Claude Desktop automatically via [Smithery](https://smithery.ai/server/mcp-proxy):
240-
241-
```bash
242-
npx -y @smithery/cli install mcp-proxy --client claude
243-
```
244-
245-
#### Installing via PyPI
246-
247-
The stable version of the package is available on the PyPI repository. You can install it using the following command:
248-
249-
```bash
250-
# Option 1: With uv (recommended)
251-
uv tool install mcp-proxy
252-
253-
# Option 2: With pipx (alternative)
254-
pipx install mcp-proxy
255-
```
256-
257-
Once installed, you can run the server using the `mcp-proxy` command.
258-
259-
## Contributing
260141

261-
Contributions are welcome! Please feel free to submit a Pull Request.
262142

263143
## Related Projects
264144

265145
- [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk) - The official async Python SDK for MCP
266146
- [mcp-proxy](https://github.com/sparfenyuk/mcp-proxy) - A proxy tool to connect Claude Desktop with MCP servers
147+
- [mcp-utils](https://github.com/fulfilio/mcp-utils) - Original version with Pydantic support
267148

268149
## License
269150

examples/flask_app.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,26 @@
11
"""
22
Example Flask application implementing an MCP server.
33
4-
`pip install mcp-utils flask gunicorn redis`
4+
`pip install mcp-utils flask gunicorn`
55
6-
* Needs Redis to be running
7-
* Needs Gunicorn to be installed
8-
9-
In addition to the requirements for the simple example, this example also:
6+
This example also:
107
118
* Demonstrates logging setup
129
* Demonstrates session management
1310
* Demonstrates SSE stream handling
11+
12+
Uses the built-in SQLite-backed response queue.
1413
"""
1514

1615
import logging
1716
import sys
1817

1918
from flask import Flask, jsonify, request
19+
import msgspec
2020
from gunicorn.app.base import BaseApplication
2121

2222
from mcp_utils.core import MCPServer
23+
from mcp_utils.queue import SQLiteResponseQueue
2324
from mcp_utils.schema import (
2425
CallToolResult,
2526
CompletionValues,
@@ -29,14 +30,14 @@
2930
)
3031

3132
app = Flask(__name__)
32-
mcp = MCPServer("weather", "1.0")
33+
mcp = MCPServer("weather", "1.0", response_queue=SQLiteResponseQueue("responses.db"))
3334

3435
logger = logging.getLogger("mcp_utils")
3536
logger.setLevel(logging.DEBUG)
3637

3738

3839
@mcp.tool()
39-
def get_weather(city: str) -> CallToolResult:
40+
def get_weather(city: str) -> str:
4041
return "sunny"
4142

4243

@@ -80,7 +81,8 @@ def get_cities(city_name: str) -> CompletionValues:
8081
@app.route("/mcp", methods=["POST"])
8182
def mcp_route():
8283
response = mcp.handle_message(request.get_json())
83-
return jsonify(response.model_dump(exclude_none=True))
84+
# Convert msgspec Struct to builtin types for jsonify
85+
return jsonify(msgspec.to_builtins(response))
8486

8587

8688
class FlaskApplication(BaseApplication):

src/mcp_utils/core.py

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -509,17 +509,6 @@ def handle_message(
509509
def _handle_message(
510510
self, message: dict, session_id: str | None = None
511511
) -> MCPResponse | None:
512-
try:
513-
message_id = message["id"]
514-
except KeyError:
515-
return MCPResponse(
516-
jsonrpc="2.0",
517-
id=0,
518-
error=ErrorResponse(
519-
code=-32600,
520-
message="Missing message id",
521-
),
522-
)
523512
try:
524513
mcp_request = msgspec.convert({**message}, MCPRequest)
525514
except Exception as e:
@@ -556,6 +545,7 @@ def _handle_message(
556545
if handler := handlers.get(mcp_request.method):
557546
return handler(mcp_request)
558547
else:
548+
message_id = message["id"]
559549
return MCPResponse(
560550
jsonrpc="2.0",
561551
id=message_id,

tests/test_core.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,3 +470,19 @@ def test_pagination_helper() -> None:
470470
page_items, next_page = get_page_of_items(items, page=0, page_size=3)
471471
assert page_items == []
472472
assert next_page == "1"
473+
474+
475+
def test_notifications_initialized_is_ignored(server: MCPServer) -> None:
476+
"""Server ignores `notifications/initialized` and queues no response."""
477+
session_id = server.generate_session_id()
478+
message = {
479+
"jsonrpc": "2.0",
480+
"id": "init-1",
481+
"method": "notifications/initialized",
482+
"params": None,
483+
}
484+
485+
rv = server.handle_message(message, session_id=session_id)
486+
assert rv is None
487+
# No response should be queued for notifications
488+
assert server.wait_for_queued_response(session_id, timeout=0.1) is None

0 commit comments

Comments
 (0)