-
-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathserver.py
More file actions
172 lines (149 loc) · 5.08 KB
/
Copy pathserver.py
File metadata and controls
172 lines (149 loc) · 5.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# server.py
import argparse
import logging
import os
import sys
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from app.api_client import close_client
from app.prompts import register_all as register_all_prompts
from app.resources import register_all as register_all_resources
from app.tools import register_all as register_all_tools
from dotenv import load_dotenv
from fastmcp import FastMCP
def build_parser() -> argparse.ArgumentParser:
p = argparse.ArgumentParser(description="EODHD MCP Server")
# Transport selection (mutually exclusive):
# - default is HTTP unless --stdio or --sse is provided
transport = p.add_mutually_exclusive_group()
transport.add_argument(
"--http",
action="store_true",
help="Run HTTP transport (default if no other transport is selected).",
)
transport.add_argument(
"--stdio",
action="store_true",
help="Run STDIO transport (only if explicitly requested).",
)
transport.add_argument(
"--sse",
action="store_true",
help="Run SSE transport (Server-Sent Events).",
)
# Defaults come from environment variables (loaded from .env), but CLI overrides env
p.add_argument(
"--host",
default=os.getenv("MCP_HOST", "127.0.0.1"),
help="HTTP/SSE host (default: 127.0.0.1 or $MCP_HOST).",
)
p.add_argument(
"--port",
type=int,
default=int(os.getenv("MCP_PORT", "8000")),
help="HTTP/SSE port (default: 8000 or $MCP_PORT).",
)
p.add_argument(
"--path",
default=os.getenv("MCP_PATH", "/mcp"),
help="HTTP path for streamable-http (default: /mcp or $MCP_PATH).",
)
p.add_argument(
"--log-level",
default=os.getenv("LOG_LEVEL", "INFO"),
help="Logging level (default: INFO or $LOG_LEVEL).",
)
p.add_argument(
"--apikey",
"--api-key",
dest="api_key",
help="EODHD API key",
)
return p
def main(argv: list[str] | None = None) -> int:
# Load .env before parsing so env defaults are available to argparse
load_dotenv()
parser = build_parser()
args, unknown = parser.parse_known_args(argv)
# If provided, override env so make_request() picks it up
if args.api_key:
os.environ["EODHD_API_KEY"] = args.api_key
logging.basicConfig(
level=getattr(logging, args.log_level.upper(), logging.INFO),
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
stream=sys.stderr,
)
logger = logging.getLogger("eodhd-mcp")
if unknown:
logger.warning("Ignoring extra args from client: %d item(s)", len(unknown))
if not os.environ.get("EODHD_API_KEY"):
logger.warning(
"No EODHD_API_KEY set. Set EODHD_API_KEY env var or pass --apikey to use your key.",
)
@asynccontextmanager
async def _lifespan(_mcp: FastMCP) -> AsyncIterator[None]:
"""Startup / shutdown hook running inside the server's event loop."""
yield
# ── shutdown ──
logger.info("Closing shared HTTP client…")
try:
await close_client()
except Exception:
logger.exception("Failed to close shared HTTP client.")
mcp: FastMCP = FastMCP("eodhd-datasets", lifespan=_lifespan)
register_all_tools(mcp)
register_all_resources(mcp)
register_all_prompts(mcp)
# Determine transport:
# - If --stdio: stdio
# - If --sse: SSE
# - Else: HTTP (streamable-http)
run_stdio = args.stdio
run_sse = args.sse
run_http = args.http or not (args.stdio or args.sse)
try:
if run_stdio:
logger.info("Starting EODHD MCP (STDIO)...")
mcp.run(transport="stdio")
logger.info("STDIO server stopped.")
return 0
if run_sse:
logger.info(
"Starting EODHD MCP **SSE** Server on http://%s:%s ...",
args.host,
args.port,
)
# Note: SSE transport does not use the 'path' argument in your standalone version,
# so we keep that behavior here.
mcp.run(
transport="sse",
host=args.host,
port=args.port,
)
logger.info("SSE server stopped.")
return 0
if run_http:
logger.info(
"Starting EODHD MCP HTTP Server on http://%s:%s%s ...",
args.host,
args.port,
args.path,
)
mcp.run(
transport="streamable-http",
host=args.host,
port=args.port,
path=args.path,
)
logger.info("HTTP server stopped.")
return 0
logger.error("No transport selected.")
return 2
except KeyboardInterrupt:
logger.info("Shutdown requested (Ctrl+C).")
return 0
except Exception:
logger.exception("Fatal error while running MCP server.")
return 1
if __name__ == "__main__":
sys.exit(main())