mseep-fastapi-mcp-sse 0.1.2__py3-none-any.whl → 0.1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- app.py +57 -0
- {mseep_fastapi_mcp_sse-0.1.2.dist-info → mseep_fastapi_mcp_sse-0.1.3.dist-info}/METADATA +4 -9
- mseep_fastapi_mcp_sse-0.1.3.dist-info/RECORD +10 -0
- {mseep_fastapi_mcp_sse-0.1.2.dist-info → mseep_fastapi_mcp_sse-0.1.3.dist-info}/WHEEL +1 -1
- mseep_fastapi_mcp_sse-0.1.3.dist-info/top_level.txt +4 -0
- routes.py +40 -0
- server.py +16 -0
- weather.py +96 -0
- mseep_fastapi_mcp_sse-0.1.2.dist-info/RECORD +0 -6
- mseep_fastapi_mcp_sse-0.1.2.dist-info/top_level.txt +0 -1
- {mseep_fastapi_mcp_sse-0.1.2.dist-info → mseep_fastapi_mcp_sse-0.1.3.dist-info}/entry_points.txt +0 -0
- {mseep_fastapi_mcp_sse-0.1.2.dist-info → mseep_fastapi_mcp_sse-0.1.3.dist-info}/licenses/LICENSE +0 -0
app.py
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
from fastapi import FastAPI, Request
|
2
|
+
from mcp.server.sse import SseServerTransport
|
3
|
+
from starlette.routing import Mount
|
4
|
+
from weather import mcp
|
5
|
+
|
6
|
+
# Create FastAPI application with metadata
|
7
|
+
app = FastAPI(
|
8
|
+
title="FastAPI MCP SSE",
|
9
|
+
description="A demonstration of Server-Sent Events with Model Context "
|
10
|
+
"Protocol integration",
|
11
|
+
version="0.1.0",
|
12
|
+
)
|
13
|
+
|
14
|
+
# Create SSE transport instance for handling server-sent events
|
15
|
+
sse = SseServerTransport("/messages/")
|
16
|
+
|
17
|
+
# Mount the /messages path to handle SSE message posting
|
18
|
+
app.router.routes.append(Mount("/messages", app=sse.handle_post_message))
|
19
|
+
|
20
|
+
|
21
|
+
# Add documentation for the /messages endpoint
|
22
|
+
@app.get("/messages", tags=["MCP"], include_in_schema=True)
|
23
|
+
def messages_docs():
|
24
|
+
"""
|
25
|
+
Messages endpoint for SSE communication
|
26
|
+
|
27
|
+
This endpoint is used for posting messages to SSE clients.
|
28
|
+
Note: This route is for documentation purposes only.
|
29
|
+
The actual implementation is handled by the SSE transport.
|
30
|
+
"""
|
31
|
+
pass # This is just for documentation, the actual handler is mounted above
|
32
|
+
|
33
|
+
|
34
|
+
@app.get("/sse", tags=["MCP"])
|
35
|
+
async def handle_sse(request: Request):
|
36
|
+
"""
|
37
|
+
SSE endpoint that connects to the MCP server
|
38
|
+
|
39
|
+
This endpoint establishes a Server-Sent Events connection with the client
|
40
|
+
and forwards communication to the Model Context Protocol server.
|
41
|
+
"""
|
42
|
+
# Use sse.connect_sse to establish an SSE connection with the MCP server
|
43
|
+
async with sse.connect_sse(request.scope, request.receive, request._send) as (
|
44
|
+
read_stream,
|
45
|
+
write_stream,
|
46
|
+
):
|
47
|
+
# Run the MCP server with the established streams
|
48
|
+
await mcp._mcp_server.run(
|
49
|
+
read_stream,
|
50
|
+
write_stream,
|
51
|
+
mcp._mcp_server.create_initialization_options(),
|
52
|
+
)
|
53
|
+
|
54
|
+
|
55
|
+
# Import routes at the end to avoid circular imports
|
56
|
+
# This ensures all routes are registered to the app
|
57
|
+
import routes # noqa
|
@@ -1,20 +1,15 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: mseep-fastapi-mcp-sse
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.3
|
4
4
|
Summary: A working example to create a FastAPI server with SSE-based MCP support
|
5
|
-
Home-page:
|
6
|
-
Author: mseep
|
7
5
|
Author-email: mseep <support@skydeck.ai>
|
8
|
-
|
9
|
-
|
10
|
-
Description-Content-Type: text/markdown
|
6
|
+
Requires-Python: >=3.13
|
7
|
+
Description-Content-Type: text/plain
|
11
8
|
License-File: LICENSE
|
12
9
|
Requires-Dist: fastapi>=0.115.11
|
13
10
|
Requires-Dist: httpx>=0.28.1
|
14
11
|
Requires-Dist: mcp[cli]>=1.3.0
|
15
12
|
Requires-Dist: unicorn>=2.1.3
|
16
|
-
Dynamic: author
|
17
13
|
Dynamic: license-file
|
18
|
-
Dynamic: requires-python
|
19
14
|
|
20
|
-
|
15
|
+
Package managed by MseeP.ai
|
@@ -0,0 +1,10 @@
|
|
1
|
+
app.py,sha256=fMeFF-d12bqR74Tk3kIXkUgGeXK8ArLDSNaIB6qtObg,1888
|
2
|
+
routes.py,sha256=TCpBJgXnsRjLoj9KVx54mMJ-fyDpv46jlt8SIUS1k98,1119
|
3
|
+
server.py,sha256=qIYfgFLHMId2vw0j0tnpOEFncZABuNV2-WjCZAUQFh0,318
|
4
|
+
weather.py,sha256=L7ObmiVZO0jmlUmsCAnA7cjFt-gzJe7w9NibSiNyzHI,2942
|
5
|
+
mseep_fastapi_mcp_sse-0.1.3.dist-info/licenses/LICENSE,sha256=3jx_ovGaWR_Y9wL1s0njbZj7IyL5klS_5VvxO9EGbiE,1065
|
6
|
+
mseep_fastapi_mcp_sse-0.1.3.dist-info/METADATA,sha256=spERKmuqErrAtlLFisfO-GYqErW_qdlFoGRqPn3NnMc,444
|
7
|
+
mseep_fastapi_mcp_sse-0.1.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
8
|
+
mseep_fastapi_mcp_sse-0.1.3.dist-info/entry_points.txt,sha256=lWiP8hdv1rciwJn9kTxOBAVugY9fgMmwBgBVvp79EVg,37
|
9
|
+
mseep_fastapi_mcp_sse-0.1.3.dist-info/top_level.txt,sha256=xpnmJRuceitZrR4gZafC8URF4ChnCe3hSmHapNAlox8,26
|
10
|
+
mseep_fastapi_mcp_sse-0.1.3.dist-info/RECORD,,
|
routes.py
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
from fastapi.responses import HTMLResponse, JSONResponse, PlainTextResponse
|
2
|
+
from fastapi import APIRouter
|
3
|
+
from app import app
|
4
|
+
|
5
|
+
# Create a router with a general tag for API documentation organization
|
6
|
+
router = APIRouter(tags=["General"])
|
7
|
+
|
8
|
+
|
9
|
+
@router.get("/")
|
10
|
+
async def homepage():
|
11
|
+
"""Root endpoint that returns a simple HTML welcome page"""
|
12
|
+
html_content = (
|
13
|
+
"<h1>FastAPI MCP SSE</h1>"
|
14
|
+
"<p>Welcome to the SSE demo with MCP integration.</p>"
|
15
|
+
)
|
16
|
+
return HTMLResponse(html_content)
|
17
|
+
|
18
|
+
|
19
|
+
@router.get("/about")
|
20
|
+
async def about():
|
21
|
+
"""About endpoint that returns information about the application"""
|
22
|
+
return PlainTextResponse(
|
23
|
+
"About FastAPI MCP SSE: A demonstration of Server-Sent Events "
|
24
|
+
"with Model Context Protocol integration."
|
25
|
+
)
|
26
|
+
|
27
|
+
|
28
|
+
@router.get("/status")
|
29
|
+
async def status():
|
30
|
+
"""Status endpoint that returns the current server status"""
|
31
|
+
status_info = {
|
32
|
+
"status": "running",
|
33
|
+
"server": "FastAPI MCP SSE",
|
34
|
+
"version": "0.1.0",
|
35
|
+
}
|
36
|
+
return JSONResponse(status_info)
|
37
|
+
|
38
|
+
|
39
|
+
# Include the router in the main application
|
40
|
+
app.include_router(router)
|
server.py
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
import uvicorn
|
2
|
+
import os
|
3
|
+
from app import app
|
4
|
+
|
5
|
+
# Environment variable configuration
|
6
|
+
HOST = os.getenv("HOST", "0.0.0.0")
|
7
|
+
PORT = int(os.getenv("PORT", "8000"))
|
8
|
+
|
9
|
+
|
10
|
+
def run():
|
11
|
+
"""Start the FastAPI server with uvicorn"""
|
12
|
+
uvicorn.run(app, host=HOST, port=PORT, log_level="info")
|
13
|
+
|
14
|
+
|
15
|
+
if __name__ == "__main__":
|
16
|
+
run()
|
weather.py
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
from typing import Any
|
2
|
+
import httpx
|
3
|
+
from mcp.server.fastmcp import FastMCP
|
4
|
+
|
5
|
+
# Initialize FastMCP server
|
6
|
+
mcp = FastMCP("weather")
|
7
|
+
|
8
|
+
# Constants
|
9
|
+
NWS_API_BASE = "https://api.weather.gov"
|
10
|
+
USER_AGENT = "weather-app/1.0"
|
11
|
+
|
12
|
+
|
13
|
+
async def make_nws_request(url: str) -> dict[str, Any] | None:
|
14
|
+
"""Make a request to the NWS API with proper error handling."""
|
15
|
+
headers = {"User-Agent": USER_AGENT, "Accept": "application/geo+json"}
|
16
|
+
async with httpx.AsyncClient() as client:
|
17
|
+
try:
|
18
|
+
response = await client.get(url, headers=headers, timeout=30.0)
|
19
|
+
response.raise_for_status()
|
20
|
+
return response.json()
|
21
|
+
except Exception:
|
22
|
+
return None
|
23
|
+
|
24
|
+
|
25
|
+
def format_alert(feature: dict) -> str:
|
26
|
+
"""Format an alert feature into a readable string."""
|
27
|
+
props = feature["properties"]
|
28
|
+
return f"""
|
29
|
+
Event: {props.get('event', 'Unknown')}
|
30
|
+
Area: {props.get('areaDesc', 'Unknown')}
|
31
|
+
Severity: {props.get('severity', 'Unknown')}
|
32
|
+
Description: {props.get('description', 'No description available')}
|
33
|
+
Instructions: {props.get('instruction', 'No specific instructions provided')}
|
34
|
+
"""
|
35
|
+
|
36
|
+
|
37
|
+
@mcp.tool()
|
38
|
+
async def get_alerts(state: str) -> str:
|
39
|
+
"""Get weather alerts for a US state.
|
40
|
+
|
41
|
+
Args:
|
42
|
+
state: Two-letter US state code (e.g. CA, NY)
|
43
|
+
"""
|
44
|
+
url = f"{NWS_API_BASE}/alerts/active/area/{state}"
|
45
|
+
data = await make_nws_request(url)
|
46
|
+
|
47
|
+
if not data or "features" not in data:
|
48
|
+
return "Unable to fetch alerts or no alerts found."
|
49
|
+
|
50
|
+
if not data["features"]:
|
51
|
+
return "No active alerts for this state."
|
52
|
+
|
53
|
+
alerts = [format_alert(feature) for feature in data["features"]]
|
54
|
+
return "\n---\n".join(alerts)
|
55
|
+
|
56
|
+
|
57
|
+
@mcp.tool()
|
58
|
+
async def get_forecast(latitude: float, longitude: float) -> str:
|
59
|
+
"""Get weather forecast for a location.
|
60
|
+
|
61
|
+
Args:
|
62
|
+
latitude: Latitude of the location
|
63
|
+
longitude: Longitude of the location
|
64
|
+
"""
|
65
|
+
# First get the forecast grid endpoint
|
66
|
+
points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}"
|
67
|
+
points_data = await make_nws_request(points_url)
|
68
|
+
|
69
|
+
if not points_data:
|
70
|
+
return "Unable to fetch forecast data for this location."
|
71
|
+
|
72
|
+
# Get the forecast URL from the points response
|
73
|
+
forecast_url = points_data["properties"]["forecast"]
|
74
|
+
forecast_data = await make_nws_request(forecast_url)
|
75
|
+
|
76
|
+
if not forecast_data:
|
77
|
+
return "Unable to fetch detailed forecast."
|
78
|
+
|
79
|
+
# Format the periods into a readable forecast
|
80
|
+
periods = forecast_data["properties"]["periods"]
|
81
|
+
forecasts = []
|
82
|
+
for period in periods[:5]: # Only show next 5 periods
|
83
|
+
forecast = f"""
|
84
|
+
{period['name']}:
|
85
|
+
Temperature: {period['temperature']}°{period['temperatureUnit']}
|
86
|
+
Wind: {period['windSpeed']} {period['windDirection']}
|
87
|
+
Forecast: {period['detailedForecast']}
|
88
|
+
"""
|
89
|
+
forecasts.append(forecast)
|
90
|
+
|
91
|
+
return "\n---\n".join(forecasts)
|
92
|
+
|
93
|
+
|
94
|
+
if __name__ == "__main__":
|
95
|
+
# Initialize and run the server
|
96
|
+
mcp.run(transport="sse")
|
@@ -1,6 +0,0 @@
|
|
1
|
-
mseep_fastapi_mcp_sse-0.1.2.dist-info/licenses/LICENSE,sha256=3jx_ovGaWR_Y9wL1s0njbZj7IyL5klS_5VvxO9EGbiE,1065
|
2
|
-
mseep_fastapi_mcp_sse-0.1.2.dist-info/METADATA,sha256=N9MQi6W4DKUFZ_q_k2x2mBVCsFuxk9OsZkkmGUpWdnU,560
|
3
|
-
mseep_fastapi_mcp_sse-0.1.2.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
|
4
|
-
mseep_fastapi_mcp_sse-0.1.2.dist-info/entry_points.txt,sha256=lWiP8hdv1rciwJn9kTxOBAVugY9fgMmwBgBVvp79EVg,37
|
5
|
-
mseep_fastapi_mcp_sse-0.1.2.dist-info/top_level.txt,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
6
|
-
mseep_fastapi_mcp_sse-0.1.2.dist-info/RECORD,,
|
@@ -1 +0,0 @@
|
|
1
|
-
|
{mseep_fastapi_mcp_sse-0.1.2.dist-info → mseep_fastapi_mcp_sse-0.1.3.dist-info}/entry_points.txt
RENAMED
File without changes
|
{mseep_fastapi_mcp_sse-0.1.2.dist-info → mseep_fastapi_mcp_sse-0.1.3.dist-info}/licenses/LICENSE
RENAMED
File without changes
|