3333# setup for "Transport Type": "SSE"
3434# "Server URL" to "http://localhost:8000/sse"
3535
36- EMAIL = os .getenv ('EMAIL' )
37- PASSWORD = os .getenv ('PASSWORD' )
3836
39- ACCEPTED_TOKEN = os .environ .get ("MCP_TOKEN" , "spendee-token" )
40- PORT = int (os .environ .get ("MCP_PORT" , 8000 ))
4137DEBUG_MODE = os .environ .get ("DEBUG_MODE" , "" ) != ""
42- TRANSFER_MODE = os .environ .get ("TRANSFER_MODE" , "sse" ).lower ()
43-
44-
45- if TRANSFER_MODE not in ["sse" , "streamable-http" ]:
46- raise ValueError ("TRANSFER_MODE must be either 'sse' or 'streamable-http'" )
47-
48- if not EMAIL or not PASSWORD :
49- raise ValueError ('Please set EMAIL and PASSWORD as an ENV variable.' )
5038
5139logging .basicConfig (level = logging .DEBUG )
5240logger = logging .getLogger (__name__ )
5341
54- mcp = FastMCP ("spendee" , host = "0.0.0.0" , port = PORT )
5542
56- spendee = SpendeeFirestore (EMAIL , PASSWORD )
57-
58- # Automatically register all MCP tools from the client
59- for name , func in MCP_TOOLS .items ():
60- # Bind the method to the spendee instance if it's a class method
61- bound_func = getattr (spendee , name )
62- mcp .tool ()(bound_func )
63-
64- def main ():
65- #debug_secret(ACCEPTED_TOKEN, "MCP_TOKEN")
66- #debug_secret(PASSWORD, "PASSWORD")
43+ def run (email = None , password = None , port = 8000 , accepted_token = "spendee-token" , transfer_mode = "sse" ):
44+
45+ if transfer_mode not in ["sse" , "streamable-http" ]:
46+ raise ValueError ("TRANSFER_MODE must be either 'sse' or 'streamable-http'" )
47+
48+ if not email or not password :
49+ raise ValueError ('Please set EMAIL and PASSWORD as an ENV variable.' )
50+
51+ mcp = FastMCP ("spendee" , host = "0.0.0.0" , port = port )
52+
53+ spendee = SpendeeFirestore (EMAIL , PASSWORD )
54+
55+ # Automatically register all MCP tools from the client
56+ for name , func in MCP_TOOLS .items ():
57+ # Bind the method to the spendee instance if it's a class method
58+ bound_func = getattr (spendee , name )
59+ mcp .tool ()(bound_func )
60+
61+ async def check_bearer_auth (request , error_response = None ):
62+ if accepted_token == "disabled" :
63+ logger .info ("Bearer token authentication is disabled." )
64+ return None
65+
66+ if DEBUG_MODE :
67+ logger .debug (f"Incoming request: method={ request .method } , url={ request .url } " )
68+ logger .debug (f"Request headers: { dict (request .headers )} " )
69+
70+ auth_header = request .headers .get ("authorization" )
71+ if not auth_header or not auth_header .lower ().startswith ("bearer " ):
72+ logger .warning ("Missing or invalid Authorization header." )
73+ if error_response :
74+ return await error_response ("Missing or invalid Authorization header." , 401 )
75+ raise HTTPException (401 , "Missing or invalid Authorization header." )
76+
77+ token = auth_header .split (" " , 1 )[1 ]
78+ if token != accepted_token :
79+ logger .warning ("Invalid token." )
80+ if error_response :
81+ return await error_response ("Invalid token." , 401 )
82+ raise HTTPException (401 , "Invalid token." )
83+ return None
6784
6885 logger .info ("Starting Spendee MCP Server" )
6986 # I failed to unify both transfer modes, because of some lifecycle issues
70- if TRANSFER_MODE == "streamable-http" :
87+ if transfer_mode == "streamable-http" :
7188 logger .info ("Using Streamable HTTP transport" )
72- streaming_server ()
89+ streaming_server (mcp , port , check_bearer_auth )
7390 else :
7491 logger .info ("Using SSE transport" )
75- sse_server ()
92+ sse_server (mcp , port , check_bearer_auth )
7693
7794# Authentication middleware and server setup
7895
@@ -82,32 +99,8 @@ def debug_secret(secret, name):
8299 logger .debug (f"sha256('{ salt } ' + token): { token_hash } " )
83100 logger .debug (f"You can verify with: echo -n \" { salt } ${ name } \" | sha256sum" )
84101
85- async def check_bearer_auth (request , error_response = None ):
86- if ACCEPTED_TOKEN == "disabled" :
87- logger .info ("Bearer token authentication is disabled." )
88- return None
89-
90- if DEBUG_MODE :
91- logger .debug (f"Incoming request: method={ request .method } , url={ request .url } " )
92- logger .debug (f"Request headers: { dict (request .headers )} " )
93-
94- auth_header = request .headers .get ("authorization" )
95- if not auth_header or not auth_header .lower ().startswith ("bearer " ):
96- logger .warning ("Missing or invalid Authorization header." )
97- if error_response :
98- return await error_response ("Missing or invalid Authorization header." , 401 )
99- raise HTTPException (401 , "Missing or invalid Authorization header." )
100-
101- token = auth_header .split (" " , 1 )[1 ]
102- if token != ACCEPTED_TOKEN :
103- logger .warning ("Invalid token." )
104- if error_response :
105- return await error_response ("Invalid token." , 401 )
106- raise HTTPException (401 , "Invalid token." )
107- return None
108-
109102
110- def streaming_server ():
103+ def streaming_server (mcp , port , check_bearer_auth ):
111104 # maybe this would be simpler: https://gofastmcp.com/deployment/self-hosted#environment-variables
112105 session_manager = StreamableHTTPSessionManager (
113106 app = mcp ._mcp_server ,
@@ -130,12 +123,12 @@ async def lifespan(app: FastAPI):
130123 app = FastAPI (lifespan = lifespan )
131124 app .mount ("/mcp" , auth_middleware )
132125
133- logger .info (f"Starting Spendee MCP Server with Bearer Token Authentication on port { PORT } " )
134- logger .info (f"Access the MCP endpoint at http://0.0.0.0:{ PORT } /mcp" )
135- uvicorn .run (app , host = "0.0.0.0" , port = PORT )
126+ logger .info (f"Starting Spendee MCP Server with Bearer Token Authentication on port { port } " )
127+ logger .info (f"Access the MCP endpoint at http://0.0.0.0:{ port } /mcp" )
128+ uvicorn .run (app , host = "0.0.0.0" , port = port )
136129
137130
138- def sse_server ():
131+ def sse_server (mcp , port , check_bearer_auth ):
139132 sse_transport = SseServerTransport ("/messages/" )
140133
141134 async def error_response (msg , status ):
@@ -169,10 +162,15 @@ async def sse_auth_middleware(scope: Scope, receive: Receive, send: Send):
169162 Mount ("/" , sse_auth_middleware ),
170163 ],
171164 )
172- uvicorn .run (app , host = "0.0.0.0" , port = PORT )
165+ uvicorn .run (app , host = "0.0.0.0" , port = port )
173166
174167
175168if __name__ == "__main__" :
176- main ()
177- else :
178- main ()
169+
170+ EMAIL = os .getenv ('EMAIL' )
171+ PASSWORD = os .getenv ('PASSWORD' )
172+ ACCEPTED_TOKEN = os .environ .get ("MCP_TOKEN" , "spendee-token" )
173+ PORT = int (os .environ .get ("MCP_PORT" , 8000 ))
174+ TRANSFER_MODE = os .environ .get ("TRANSFER_MODE" , "sse" ).lower ()
175+
176+ run (email = EMAIL , password = PASSWORD , port = PORT , accepted_token = ACCEPTED_TOKEN , transfer_mode = TRANSFER_MODE )
0 commit comments