manmo12 commited on
Commit
57e07be
·
1 Parent(s): ca4b3c5
Files changed (1) hide show
  1. server.py +64 -317
server.py CHANGED
@@ -1,343 +1,90 @@
1
- from fastapi import FastAPI, Request, HTTPException
2
- from authlib.integrations.starlette_client import OAuth
3
- from starlette.middleware.sessions import SessionMiddleware
4
  import os
5
- from typing import Dict, List
6
- from dotenv import load_dotenv
7
- from datetime import datetime
 
 
 
8
 
9
- load_dotenv()
10
 
11
- # --- Vérification variables d'environnement ---
12
- required_env_vars = ["CLIENT_ID", "CLIENT_SECRET"]
13
- missing_vars = [v for v in required_env_vars if not os.getenv(v)]
14
- if missing_vars:
15
- raise ValueError(f"Variables d'environnement manquantes: {', '.join(missing_vars)}")
 
16
 
17
- app = FastAPI(title="MCP IA Twitter Connector")
18
- app.add_middleware(SessionMiddleware, secret_key="SESSION_SECRET_KEY")
 
 
 
 
 
19
 
20
- # --- OAuth Twitter ---
 
 
 
 
 
 
21
  oauth = OAuth()
22
  oauth.register(
23
  name="twitter",
24
- client_id=os.getenv("CLIENT_ID"),
25
- client_secret=os.getenv("CLIENT_SECRET"),
26
- authorize_url="https://twitter.com/i/oauth2/authorize",
27
  access_token_url="https://api.twitter.com/2/oauth2/token",
28
- client_kwargs={
29
- "scope": "tweet.read tweet.write users.read offline.access",
30
- "token_endpoint_auth_method": "client_secret_post"
31
- }
32
  )
33
 
34
- # --- MCP simple ---
35
- mcp_tools_available = True
36
-
37
- async def hello() -> str:
38
- return "Hello depuis MCP IA 🎉"
39
-
40
- async def echo_message(message: str) -> Dict[str, str]:
41
- return {"status": "success", "echoed_message": message, "timestamp": datetime.now().isoformat()}
42
-
43
- async def post_tweet(message: str, token: Dict) -> Dict[str, str]:
44
- """Poste un tweet via Twitter API v2"""
45
- import httpx
46
- url = "https://api.twitter.com/2/tweets"
47
- headers = {"Authorization": f"Bearer {token['access_token']}"}
48
- json_data = {"text": message}
49
- async with httpx.AsyncClient() as client:
50
- resp = await client.post(url, headers=headers, json=json_data)
51
- if resp.status_code != 201:
52
- return {"error": resp.text}
53
- return resp.json()
54
-
55
- async def get_my_tweets(token: Dict, max_results: int = 5) -> List[Dict]:
56
- """Récupère les derniers tweets de l’utilisateur connecté"""
57
- import httpx
58
- url = "https://api.twitter.com/2/users/me"
59
- headers = {"Authorization": f"Bearer {token['access_token']}"}
60
- async with httpx.AsyncClient() as client:
61
- user_resp = await client.get(url, headers=headers)
62
- if user_resp.status_code != 200:
63
- raise HTTPException(status_code=400, detail="Impossible de récupérer l'utilisateur")
64
- user_id = user_resp.json()["data"]["id"]
65
- tweets_resp = await client.get(f"https://api.twitter.com/2/users/{user_id}/tweets?max_results={max_results}", headers=headers)
66
- if tweets_resp.status_code != 200:
67
- raise HTTPException(status_code=400, detail="Impossible de récupérer les tweets")
68
- return tweets_resp.json().get("data", [])
69
 
70
- # --- Routes OAuth Twitter ---
71
- @app.get("/login")
 
 
72
  async def login(request: Request):
73
  redirect_uri = request.url_for("auth_callback")
74
  return await oauth.twitter.authorize_redirect(request, redirect_uri)
75
 
76
- @app.get("/callback")
77
  async def auth_callback(request: Request):
78
  token = await oauth.twitter.authorize_access_token(request)
79
- request.session["token"] = token
80
- request.session["authenticated"] = True
81
- return {"message": "Authentifié sur Twitter avec succès"}
82
 
83
- @app.get("/logout")
84
  async def logout(request: Request):
85
- request.session.clear()
86
- return {"message": "Déconnecté"}
87
-
88
- # --- MCP route POST pour le modèle IA ---
89
- @app.post("/mcp/api/call")
90
- async def mcp_call(request: Request):
91
- if not request.session.get("authenticated"):
92
- raise HTTPException(status_code=401, detail="Authentification requise")
93
-
94
- body = await request.json()
95
- tool = body.get("tool")
96
- params = body.get("params", {})
97
- token = request.session.get("token")
98
-
99
- if tool == "hello":
100
- return {"result": await hello()}
101
- elif tool == "echo_message":
102
- msg = params.get("message", "")
103
- return {"result": await echo_message(msg)}
104
- elif tool == "post_tweet":
105
- msg = params.get("message", "")
106
- return {"result": await post_tweet(msg, token)}
107
- elif tool == "get_my_tweets":
108
- max_res = params.get("max_results", 5)
109
- return {"result": await get_my_tweets(token, max_res)}
110
- else:
111
- raise HTTPException(status_code=400, detail=f"Outil inconnu: {tool}")
112
-
113
- # --- Health check ---
114
- @app.get("/health")
115
- async def health():
116
- return {"status": "ok", "mcp_tools_available": mcp_tools_available}
117
- from fastapi import FastAPI, Request, HTTPException
118
- from authlib.integrations.starlette_client import OAuth
119
- from starlette.middleware.sessions import SessionMiddleware
120
- import os
121
- from typing import Dict
122
- from dotenv import load_dotenv
123
- from datetime import datetime
124
-
125
- load_dotenv()
126
-
127
- # Validation des variables d'environnement
128
- required_env_vars = ["CLIENT_ID", "CLIENT_SECRET"]
129
- missing_vars = [var for var in required_env_vars if not os.getenv(var)]
130
- if missing_vars:
131
- raise ValueError(f"Variables d'environnement manquantes: {', '.join(missing_vars)}")
132
-
133
- app = FastAPI()
134
-
135
- # --- Config OAuth Twitter ---
136
- oauth = OAuth()
137
- oauth.register(
138
- name='twitter',
139
- client_id=os.getenv("CLIENT_ID"),
140
- client_secret=os.getenv("CLIENT_SECRET"),
141
- authorize_url="https://twitter.com/i/oauth2/authorize",
142
- access_token_url="https://api.twitter.com/2/oauth2/token",
143
- client_kwargs={
144
- "scope": "tweet.read users.read offline.access",
145
- "token_endpoint_auth_method": "client_secret_post"
146
- }
147
- )
148
-
149
- # --- FastMCP simple ---
150
- mcp_available = False
151
- try:
152
- from fastmcp import FastMCP
153
-
154
- mcp = FastMCP(name="TwitterMCP")
155
-
156
- @mcp.tool()
157
- async def hello() -> str:
158
- """Fonction de test MCP"""
159
- return "Hello depuis FastMCP 🎉"
160
-
161
- @mcp.tool()
162
- async def echo_message(message: str) -> Dict[str, str]:
163
- """Répète un message avec timestamp"""
164
- return {
165
- "status": "success",
166
- "echoed_message": message,
167
- "timestamp": datetime.now().isoformat()
168
- }
169
-
170
- @mcp.tool()
171
- async def get_server_info() -> Dict[str, str]:
172
- """Retourne des infos sur le serveur MCP"""
173
- return {
174
- "server": "TwitterMCP",
175
- "version": "1.0.0",
176
- "status": "running"
177
- }
178
-
179
- mcp_available = True
180
- print("FastMCP disponible et configuré")
181
-
182
- except ImportError:
183
- print("FastMCP non disponible, utilisation d'une implémentation basique")
184
- mcp = None
185
-
186
- # --- Routes OAuth ---
187
- @app.get("/login")
188
- async def login(request: Request):
189
- """Initie le processus d'authentification Twitter"""
190
- try:
191
- redirect_uri = request.url_for("auth_callback")
192
- return await oauth.twitter.authorize_redirect(request, redirect_uri)
193
- except Exception as e:
194
- raise HTTPException(status_code=500, detail=f"Erreur OAuth: {str(e)}")
195
-
196
- @app.get("/callback")
197
- async def auth_callback(request: Request) -> Dict[str, str]:
198
- """Gère le callback OAuth et stocke les infos utilisateur"""
199
- try:
200
- token = await oauth.twitter.authorize_access_token(request)
201
- user_req = await oauth.twitter.get(
202
- "https://api.twitter.com/2/users/me",
203
- token=token
204
- )
205
- if user_req.status_code != 200:
206
- raise HTTPException(status_code=400, detail="Impossible de récupérer les infos utilisateur")
207
- userinfo = user_req.json().get('data', {})
208
- if not userinfo:
209
- raise HTTPException(status_code=400, detail="Données utilisateur invalides")
210
-
211
- request.session['user'] = userinfo
212
- request.session['token'] = token
213
- request.session['authenticated'] = True
214
-
215
- return {"message": f"Connecté en tant que @{userinfo.get('username', 'utilisateur')}"}
216
-
217
- except HTTPException:
218
- raise
219
- except Exception as e:
220
- raise HTTPException(status_code=500, detail=f"Erreur lors de l'authentification: {str(e)}")
221
-
222
- @app.get("/logout")
223
- async def logout(request: Request):
224
- """Déconnecte l'utilisateur"""
225
- request.session.clear()
226
- return {"message": "Déconnecté avec succès"}
227
-
228
- @app.get("/profile")
229
- async def get_profile(request: Request):
230
- """Retourne le profil de l'utilisateur connecté"""
231
- if not request.session.get('authenticated'):
232
- raise HTTPException(status_code=401, detail="Non authentifié")
233
- return {"user": request.session.get('user')}
234
-
235
- # --- Routes MCP ---
236
- @app.get("/mcp/status")
237
- async def mcp_status(request: Request):
238
- """Statut du serveur MCP (nécessite authentification)"""
239
- if not request.session.get('authenticated'):
240
- raise HTTPException(status_code=401, detail="Authentification requise. Connectez-vous d'abord via /login")
241
-
242
- user = request.session.get('user', {})
243
- return {
244
- "mcp_server": "TwitterMCP",
245
- "status": "active",
246
- "authenticated_user": user.get('username', 'unknown'),
247
- "tools_available": ["hello", "echo_message", "get_server_info"],
248
- "mcp_available": mcp_available
249
- }
250
-
251
- @app.post("/mcp/api/call")
252
- async def mcp_call(request: Request):
253
- """Appel des outils MCP"""
254
- if not request.session.get('authenticated'):
255
- raise HTTPException(status_code=401, detail="Authentification requise")
256
-
257
- if not mcp_available:
258
- return {
259
- "error": "FastMCP non disponible",
260
- "available_tools": ["echo_message"],
261
- "note": "Implémentation basique"
262
- }
263
-
264
- try:
265
- body = await request.json()
266
- tool_name = body.get("tool")
267
- params = body.get("params", {})
268
-
269
- if tool_name == "hello":
270
- result = await hello()
271
- elif tool_name == "echo_message":
272
- message = params.get("message", "")
273
- result = await echo_message(message)
274
- elif tool_name == "get_server_info":
275
- result = await get_server_info()
276
- else:
277
- raise HTTPException(status_code=400, detail=f"Outil inconnu: {tool_name}")
278
-
279
- return {"result": result}
280
-
281
- except Exception as e:
282
- raise HTTPException(status_code=500, detail=f"Erreur lors de l'appel MCP: {str(e)}")
283
-
284
- # --- Routes MCP alternatives si FastMCP disponible ---
285
- if mcp_available and hasattr(mcp, 'router'):
286
- # Si FastMCP a un router, l'utiliser
287
- app.include_router(mcp.router, prefix="/mcp/fastapi")
288
- elif mcp_available:
289
- # Si FastMCP est disponible mais sans router, créer des routes manuellement
290
- @app.get("/mcp/tools")
291
- async def list_mcp_tools(request: Request):
292
- """Liste les outils MCP disponibles"""
293
- if not request.session.get('authenticated'):
294
- raise HTTPException(status_code=401, detail="Authentification requise")
295
-
296
- return {
297
- "tools": [
298
- {
299
- "name": "hello",
300
- "description": "Fonction de test MCP",
301
- "parameters": {}
302
- },
303
- {
304
- "name": "echo_message",
305
- "description": "Répète un message avec timestamp",
306
- "parameters": {"message": "string"}
307
- },
308
- {
309
- "name": "get_server_info",
310
- "description": "Retourne des infos sur le serveur MCP",
311
- "parameters": {}
312
- }
313
- ]
314
- }
315
-
316
- # --- Route d'information ---
317
- @app.get("/")
318
- async def root():
319
- return {
320
- "message": "Serveur FastAPI + OAuth Twitter + MCP",
321
- "authentication": "Visitez /login pour vous authentifier",
322
- "profile": "Visitez /profile après authentification",
323
- "mcp_status": "Visitez /mcp/status après authentification",
324
- "mcp_api": "Utilisez /mcp/api/call pour appeler les outils MCP",
325
- "health": "Visitez /health pour le statut du serveur",
326
- "mcp_available": mcp_available
327
- }
328
 
329
- # --- Route de santé ---
330
- @app.get("/health")
331
- async def health_check():
332
- return {
333
- "status": "ok",
334
- "message": "Service opérationnel",
335
- "mcp_available": mcp_available
336
- }
337
 
 
 
 
338
  PORT = int(os.environ.get("PORT", "10000"))
339
 
340
- # --- Lancement serveur ---
341
  if __name__ == "__main__":
342
  import uvicorn
343
- uvicorn.run(app, host="0.0.0.0", port=PORT)
 
 
 
 
1
  import os
2
+ from fastapi import FastAPI, Request
3
+ from fastapi.responses import HTMLResponse, RedirectResponse
4
+ from fastapi.staticfiles import StaticFiles
5
+ from fastapi.templating import Jinja2Templates
6
+ from starlette.middleware.sessions import SessionMiddleware
7
+ from authlib.integrations.starlette_client import OAuth
8
 
9
+ from math_server import mcp as math_mcp
10
 
11
+ # -------------------------------
12
+ # Directories
13
+ # -------------------------------
14
+ BASE_DIR = os.path.dirname(__file__)
15
+ STATIC_DIR = os.path.join(BASE_DIR, "static")
16
+ TEMPLATES_DIR = os.path.join(BASE_DIR, "templates")
17
 
18
+ # -------------------------------
19
+ # App FastAPI
20
+ # -------------------------------
21
+ app = FastAPI()
22
+
23
+ # Session middleware nécessaire pour OAuth
24
+ app.add_middleware(SessionMiddleware, secret_key=os.environ.get("SESSION_SECRET", "UN_SECRET_123"))
25
 
26
+ # Static files
27
+ app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
28
+ templates = Jinja2Templates(directory=TEMPLATES_DIR)
29
+
30
+ # -------------------------------
31
+ # Twitter OAuth configuration
32
+ # -------------------------------
33
  oauth = OAuth()
34
  oauth.register(
35
  name="twitter",
36
+ client_id=os.environ.get("TWITTER_CLIENT_ID"),
37
+ client_secret=os.environ.get("TWITTER_CLIENT_SECRET"),
38
+ request_token_url=None,
39
  access_token_url="https://api.twitter.com/2/oauth2/token",
40
+ authorize_url="https://twitter.com/i/oauth2/authorize",
41
+ client_kwargs={"scope": "tweet.read users.read offline.access"},
 
 
42
  )
43
 
44
+ # -------------------------------
45
+ # Endpoint racine
46
+ # -------------------------------
47
+ @app.get("/", response_class=HTMLResponse)
48
+ async def index(request: Request):
49
+ space_host = os.environ.get("SPACE_HOST")
50
+ if space_host and space_host.strip():
51
+ base_url = space_host.strip().rstrip("/")
52
+ else:
53
+ base_url = f"{request.url.scheme}://{request.url.netloc}"
54
+
55
+ user = request.session.get("user")
56
+ return templates.TemplateResponse("index.html", {"request": request, "base_url": base_url, "user": user})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
 
58
+ # -------------------------------
59
+ # Twitter OAuth endpoints
60
+ # -------------------------------
61
+ @app.get("/auth/twitter/login")
62
  async def login(request: Request):
63
  redirect_uri = request.url_for("auth_callback")
64
  return await oauth.twitter.authorize_redirect(request, redirect_uri)
65
 
66
+ @app.get("/auth/twitter/callback")
67
  async def auth_callback(request: Request):
68
  token = await oauth.twitter.authorize_access_token(request)
69
+ user_info = await oauth.twitter.parse_id_token(request, token)
70
+ request.session["user"] = {"id": user_info.get("sub"), "username": user_info.get("name")}
71
+ return RedirectResponse(url="/")
72
 
73
+ @app.get("/auth/logout")
74
  async def logout(request: Request):
75
+ request.session.pop("user", None)
76
+ return RedirectResponse(url="/")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
 
78
+ # -------------------------------
79
+ # Endpoint MCP math
80
+ # -------------------------------
81
+ app.mount("/math", math_mcp.http_app()) # juste le HTTP app, pas besoin de run()
 
 
 
 
82
 
83
+ # -------------------------------
84
+ # Lancer le serveur
85
+ # -------------------------------
86
  PORT = int(os.environ.get("PORT", "10000"))
87
 
 
88
  if __name__ == "__main__":
89
  import uvicorn
90
+ uvicorn.run(app, host="0.0.0.0", port=PORT)