Skip to content

Commit 191be4d

Browse files
committed
Finished refactor to FastMCP and use more best practices
1 parent 6faca81 commit 191be4d

20 files changed

+827
-310
lines changed

.pre-commit-config.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
repos:
2+
- repo: https://github.com/astral-sh/ruff-pre-commit
3+
rev: v0.7.3
4+
hooks:
5+
- id: ruff
6+
args: [--fix]
7+
- id: ruff-format
8+
9+
- repo: https://github.com/pre-commit/pre-commit-hooks
10+
rev: v4.5.0
11+
hooks:
12+
- id: trailing-whitespace
13+
- id: end-of-file-fixer
14+
- id: check-yaml
15+
- id: check-added-large-files
16+
- id: check-toml

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [2.0.0] - 2025-04-30
9+
10+
### Added
11+
- Initial release of RabbitMQ MCP Server
12+
- Support for basic queue and exchange operations
13+
- FastMCP integration for improved performance and maintainability
14+
- Ruff linting and formatting configuration
15+
- Testing infrastructure with pytest

README.md

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,15 @@
33

44
A [Model Context Protocol](https://www.anthropic.com/news/model-context-protocol) server implementation for RabbitMQ. Enabling MCP client to interact with queues and topics hosted in a RabbitMQ instance.
55

6+
## Features
7+
8+
- Connect to RabbitMQ instances
9+
- Publish messages to queues and exchanges
10+
- List queues and exchanges
11+
- Get detailed information about queues and exchanges
12+
- Delete queues and exchanges
13+
- Purge messages from queues
14+
615
## Running locally with the Claude desktop app
716

817
### Installing via Smithery
@@ -22,7 +31,7 @@ https://smithery.ai/server/@kenliao94/mcp-server-rabbitmq
2231
- On MacOS: `~/Library/Application\ Support/Claude/claude_desktop_config.json`
2332
- On Windows: `%APPDATA%/Claude/claude_desktop_config.json`
2433

25-
```
34+
```json
2635
{
2736
"mcpServers": {
2837
"rabbitmq": {
@@ -49,3 +58,38 @@ https://smithery.ai/server/@kenliao94/mcp-server-rabbitmq
4958
```
5059
4. Install and open the [Claude desktop app](https://claude.ai/download).
5160
5. Try asking Claude to do a read/write operation of some sort to confirm the setup (e.g. ask it to publish a message to a queue). If there are issues, use the Debugging tools provided in the MCP documentation [here](https://modelcontextprotocol.io/docs/tools/debugging).
61+
62+
## Development
63+
64+
### Setup Development Environment
65+
66+
```bash
67+
# Clone the repository
68+
git clone https://github.com/kenliao94/mcp-server-rabbitmq.git
69+
cd mcp-server-rabbitmq
70+
71+
# Install pre-commit hooks
72+
pre-commit install
73+
```
74+
75+
### Running Tests
76+
77+
```bash
78+
pytest
79+
```
80+
81+
### Code Quality
82+
83+
This project uses ruff for linting and formatting:
84+
85+
```bash
86+
# Run linter
87+
ruff check .
88+
89+
# Run formatter
90+
ruff format .
91+
```
92+
93+
## License
94+
95+
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.

mcp_server_rabbitmq/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""RabbitMQ MCP Server package."""

src/mcp_server_rabbitmq/admin.py renamed to mcp_server_rabbitmq/admin.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import requests
2-
from typing import Optional, List, Dict
31
import base64
2+
from typing import Dict, List, Optional
3+
4+
import requests
5+
46

57
class RabbitMQAdmin:
68
def __init__(self, host: str, port: int, username: str, password: str, use_tls: bool):
@@ -30,37 +32,37 @@ def list_exchanges(self) -> List[Dict]:
3032

3133
def get_queue_info(self, queue: str, vhost: str = "/") -> Dict:
3234
"""Get detailed information about a specific queue"""
33-
vhost_encoded = requests.utils.quote(vhost, safe='')
35+
vhost_encoded = requests.utils.quote(vhost, safe="")
3436
response = self._make_request("GET", f"queues/{vhost_encoded}/{queue}")
3537
return response.json()
3638

3739
def delete_queue(self, queue: str, vhost: str = "/") -> None:
3840
"""Delete a queue"""
3941
validate_rabbitmq_name(queue, "Queue name")
40-
vhost_encoded = requests.utils.quote(vhost, safe='')
42+
vhost_encoded = requests.utils.quote(vhost, safe="")
4143
self._make_request("DELETE", f"queues/{vhost_encoded}/{queue}")
4244

4345
def purge_queue(self, queue: str, vhost: str = "/") -> None:
4446
"""Remove all messages from a queue"""
4547
validate_rabbitmq_name(queue, "Queue name")
46-
vhost_encoded = requests.utils.quote(vhost, safe='')
48+
vhost_encoded = requests.utils.quote(vhost, safe="")
4749
self._make_request("DELETE", f"queues/{vhost_encoded}/{queue}/contents")
4850

4951
def get_exchange_info(self, exchange: str, vhost: str = "/") -> Dict:
5052
"""Get detailed information about a specific exchange"""
51-
vhost_encoded = requests.utils.quote(vhost, safe='')
53+
vhost_encoded = requests.utils.quote(vhost, safe="")
5254
response = self._make_request("GET", f"exchanges/{vhost_encoded}/{exchange}")
5355
return response.json()
5456

5557
def delete_exchange(self, exchange: str, vhost: str = "/") -> None:
5658
"""Delete an exchange"""
5759
validate_rabbitmq_name(exchange, "Exchange name")
58-
vhost_encoded = requests.utils.quote(vhost, safe='')
60+
vhost_encoded = requests.utils.quote(vhost, safe="")
5961
self._make_request("DELETE", f"exchanges/{vhost_encoded}/{exchange}")
6062

6163
def get_bindings(self, queue: Optional[str] = None, exchange: Optional[str] = None, vhost: str = "/") -> List[Dict]:
6264
"""Get bindings, optionally filtered by queue or exchange"""
63-
vhost_encoded = requests.utils.quote(vhost, safe='')
65+
vhost_encoded = requests.utils.quote(vhost, safe="")
6466
if queue:
6567
validate_rabbitmq_name(queue, "Queue name")
6668
response = self._make_request("GET", f"queues/{vhost_encoded}/{queue}/bindings")

src/mcp_server_rabbitmq/connection.py renamed to mcp_server_rabbitmq/connection.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import pika
21
import ssl
3-
from typing import Optional
2+
3+
import pika
4+
45

56
class RabbitMQConnection:
67
def __init__(self, host: str, port: int, username: str, password: str, use_tls: bool):
@@ -10,7 +11,7 @@ def __init__(self, host: str, port: int, username: str, password: str, use_tls:
1011

1112
if use_tls:
1213
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
13-
ssl_context.set_ciphers('ECDHE+AESGCM:!ECDSA')
14+
ssl_context.set_ciphers("ECDHE+AESGCM:!ECDSA")
1415
self.parameters.ssl_options = pika.SSLOptions(context=ssl_context)
1516

1617
def get_channel(self) -> tuple[pika.BlockingConnection, pika.channel.Channel]:
@@ -22,7 +23,7 @@ def validate_rabbitmq_name(name: str, field_name: str) -> None:
2223
"""Validate RabbitMQ queue/exchange names"""
2324
if not name or not name.strip():
2425
raise ValueError(f"{field_name} cannot be empty")
25-
if not all(c.isalnum() or c in '-_.:' for c in name):
26+
if not all(c.isalnum() or c in "-_.:" for c in name):
2627
raise ValueError(f"{field_name} can only contain letters, digits, hyphen, underscore, period, or colon")
2728
if len(name) > 255:
2829
raise ValueError(f"{field_name} must be less than 255 characters")

mcp_server_rabbitmq/constant.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"""Constants for the RabbitMQ MCP server."""
2+
3+
MCP_SERVER_VERSION = "2.0.0"

src/mcp_server_rabbitmq/handlers.py renamed to mcp_server_rabbitmq/handlers.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
from .connection import RabbitMQConnection
2-
from .admin import RabbitMQAdmin
31
from typing import List
42

3+
from .admin import RabbitMQAdmin
4+
from .connection import RabbitMQConnection
5+
6+
57
def handle_enqueue(rabbitmq: RabbitMQConnection, queue: str, message: str):
68
connection, channel = rabbitmq.get_channel()
79
channel.queue_declare(queue)
@@ -16,11 +18,11 @@ def handle_fanout(rabbitmq: RabbitMQConnection, exchange: str, message: str):
1618

1719
def handle_list_queues(rabbitmq_admin: RabbitMQAdmin) -> List[str]:
1820
result = rabbitmq_admin.list_queues()
19-
return [queue['name'] for queue in result]
21+
return [queue["name"] for queue in result]
2022

2123
def handle_list_exchanges(rabbitmq_admin: RabbitMQAdmin) -> List[str]:
2224
result = rabbitmq_admin.list_exchanges()
23-
return [exchange['name'] for exchange in result]
25+
return [exchange["name"] for exchange in result]
2426

2527
def handle_get_queue_info(rabbitmq_admin: RabbitMQAdmin, queue: str, vhost: str = "/") -> dict:
2628
return rabbitmq_admin.get_queue_info(queue, vhost)

0 commit comments

Comments
 (0)