Skip to content

Commit 09daf6f

Browse files
authored
Merge pull request #2 from ntk148v/sse
feat(transport): support SSE transport
2 parents 625bde5 + 82e4680 commit 09daf6f

File tree

5 files changed

+127
-21
lines changed

5 files changed

+127
-21
lines changed

Makefile

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
.PHONY: setup run dev install deploy test clean format type-check
2+
3+
# Set the Python version from cookiecutter or default to 3.12
4+
PYTHON_VERSION := 3.12
5+
6+
# Setup with uv
7+
setup:
8+
# Check if uv is installed, install if not
9+
@which uv >/dev/null || (curl -LsSf https://astral.sh/uv/install.sh | sh)
10+
# Create a virtual environment
11+
uv venv
12+
# Install dependencies with development extras
13+
uv pip install -e ".[dev]"
14+
@echo "✅ Environment setup complete. Activate it with 'source .venv/bin/activate' (Unix/macOS) or '.venv\\Scripts\activate' (Windows)"
15+
16+
# Run the server directly
17+
run:
18+
uv run src/alertmanager-mcp-server/server.py
19+
20+
# Run in development mode with MCP inspector
21+
dev:
22+
mcp dev src/alertmanager-mcp-server/server.py
23+
24+
# Install in Claude desktop
25+
install:
26+
mcp install src/alertmanager-mcp-server/server.py
27+
28+
# Docker build
29+
docker-build:
30+
docker build -t kiennt26/alertmanager-mcp-server:latest .
31+
32+
# Run with Docker
33+
docker-run:
34+
docker run -p 8000:8000 kiennt26/alertmanager-mcp-server:latest

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ version = "1.0.0"
44
description = "MCP Server for Prometheus Alertmanager integration"
55
readme = "README.md"
66
requires-python = ">=3.12"
7-
dependencies = ["mcp[cli]>=1.8.1", "requests>=2.32.3"]
7+
dependencies = [
8+
"mcp[cli]>=1.8.1",
9+
"requests>=2.32.3",
10+
]
811

912
[project.scripts]
1013
alertmanager-mcp-server = "alertmanager_mcp_server.server:run_server"

src/alertmanager_mcp_server/server.py

Lines changed: 88 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,17 @@
22
import os
33
from dataclasses import dataclass
44
from typing import Any, Dict, Optional, List
5-
import sys
65

7-
import dotenv
8-
import requests
6+
from mcp.server import Server
97
from mcp.server.fastmcp import FastMCP
8+
from mcp.server.sse import SseServerTransport
109
from requests.compat import urljoin
10+
from starlette.applications import Starlette
11+
from starlette.requests import Request
12+
from starlette.routing import Mount, Route
13+
import dotenv
14+
import requests
15+
import uvicorn
1116

1217
dotenv.load_dotenv()
1318
mcp = FastMCP("Alertmanager MCP")
@@ -279,17 +284,90 @@ def setup_environment():
279284
return True
280285

281286

287+
def create_starlette_app(mcp_server: Server, *, debug: bool = False) -> Starlette:
288+
"""Create a Starlette application that can serve the provided MCP server with SSE.
289+
290+
Sets up a Starlette web application with routes for SSE (Server-Sent Events)
291+
communication with the MCP server.
292+
293+
Args:
294+
mcp_server: The MCP server instance to connect
295+
debug: Whether to enable debug mode for the Starlette app
296+
297+
Returns:
298+
A configured Starlette application
299+
"""
300+
# Create an SSE transport with a base path for messages
301+
sse = SseServerTransport("/messages/")
302+
303+
async def handle_sse(request: Request) -> None:
304+
"""Handler for SSE connections.
305+
306+
Establishes an SSE connection and connects it to the MCP server.
307+
308+
Args:
309+
request: The incoming HTTP request
310+
"""
311+
# Connect the SSE transport to the request
312+
async with sse.connect_sse(
313+
request.scope,
314+
request.receive,
315+
request._send, # noqa: SLF001
316+
) as (read_stream, write_stream):
317+
# Run the MCP server with the SSE streams
318+
await mcp_server.run(
319+
read_stream,
320+
write_stream,
321+
mcp_server.create_initialization_options(),
322+
)
323+
324+
# Create and return the Starlette application with routes
325+
return Starlette(
326+
debug=debug,
327+
routes=[
328+
Route("/sse", endpoint=handle_sse), # Endpoint for SSE connections
329+
# Endpoint for posting messages
330+
Mount("/messages/", app=sse.handle_post_message),
331+
],
332+
)
333+
334+
282335
def run_server():
283336
"""Main entry point for the Prometheus Alertmanager MCP Server"""
284-
# Setup environment
285-
if not setup_environment():
286-
sys.exit(1)
287-
337+
setup_environment()
338+
# Get the underlying MCP server from the FastMCP instance
339+
mcp_server = mcp._mcp_server # noqa: WPS437
340+
341+
import argparse
342+
343+
# Set up command-line argument parsing
344+
parser = argparse.ArgumentParser(
345+
description='Run MCP server with configurable transport')
346+
# Allow choosing between stdio and SSE transport modes
347+
parser.add_argument('--transport', choices=['stdio', 'sse'], default='stdio',
348+
help='Transport mode (stdio or sse)')
349+
# Host configuration for SSE mode
350+
parser.add_argument('--host', default='0.0.0.0',
351+
help='Host to bind to (for SSE mode)')
352+
# Port configuration for SSE mode
353+
parser.add_argument('--port', type=int, default=8000,
354+
help='Port to listen on (for SSE mode)')
355+
args = parser.parse_args()
288356
print("\nStarting Prometheus Alertmanager MCP Server...")
289-
print("Running server in standard mode...")
290357

291-
# Run the server with the stdio transport
292-
mcp.run(transport="stdio")
358+
# Launch the server with the selected transport mode
359+
if args.transport == 'stdio':
360+
print("Running server with stdio transport (default)")
361+
# Run with stdio transport (default)
362+
# This mode communicates through standard input/output
363+
mcp.run(transport='stdio')
364+
else:
365+
print("Running server with SSE transport (web-based)")
366+
# Run with SSE transport (web-based)
367+
# Create a Starlette app to serve the MCP server
368+
starlette_app = create_starlette_app(mcp_server, debug=True)
369+
# Start the web server with the configured host and port
370+
uvicorn.run(starlette_app, host=args.host, port=args.port)
293371

294372

295373
if __name__ == "__main__":

tests/test_server.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -266,12 +266,3 @@ def test_run_server_success(mock_mcp, mock_setup_env):
266266
mock_mcp.run.assert_called_once_with(transport="stdio")
267267
assert any("Starting Prometheus Alertmanager MCP Server" in str(call)
268268
for call in mock_print.call_args_list)
269-
270-
271-
@patch("alertmanager_mcp_server.server.setup_environment", return_value=False)
272-
def test_run_server_exit(mock_setup_env):
273-
with patch("sys.exit", side_effect=SystemExit) as mock_exit:
274-
with pytest.raises(SystemExit):
275-
server.run_server()
276-
mock_setup_env.assert_called_once()
277-
mock_exit.assert_called_once_with(1)

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)