1+ """API routes for MCP server management."""
2+ from typing import List , Any
3+ from fastapi import APIRouter , HTTPException , Depends
4+ from sqlmodel import Session , select
5+ from app .api .deps import get_current_active_user , get_current_active_superuser , get_db
6+ from app .models import User , MCPServer , MCPServerCreate , MCPServerUpdate , MCPServerPublic
7+ from app .services .mcp_manager import mcp_manager
8+ import logging
9+
10+ logger = logging .getLogger (__name__ )
11+
12+ router = APIRouter ()
13+
14+
15+ @router .get ("/servers" , response_model = List [MCPServerPublic ])
16+ async def list_mcp_servers (
17+ * ,
18+ session : Session = Depends (get_db ),
19+ current_user : User = Depends (get_current_active_user ),
20+ ) -> List [MCPServerPublic ]:
21+ """List all MCP servers."""
22+ servers = session .exec (select (MCPServer )).all ()
23+
24+ # Update runtime data from manager
25+ result = []
26+ for server in servers :
27+ server_dict = server .model_dump ()
28+ if server .name in mcp_manager .connections :
29+ connection = mcp_manager .connections [server .name ]
30+ server_dict ["status" ] = connection .status
31+ server_dict ["error_message" ] = connection .error_message
32+ else :
33+ server_dict ["status" ] = MCPServerStatus .DISCONNECTED
34+ server_dict ["error_message" ] = None
35+ result .append (MCPServerPublic (** server_dict ))
36+
37+ return result
38+
39+
40+ @router .post ("/servers" , response_model = MCPServerPublic )
41+ async def create_mcp_server (
42+ * ,
43+ session : Session = Depends (get_db ),
44+ current_user : User = Depends (get_current_active_superuser ),
45+ server_in : MCPServerCreate ,
46+ ) -> MCPServerPublic :
47+ """Create a new MCP server configuration."""
48+ # Check if server with same name exists
49+ existing = session .exec (select (MCPServer ).where (MCPServer .name == server_in .name )).first ()
50+ if existing :
51+ raise HTTPException (status_code = 400 , detail = "Server with this name already exists" )
52+
53+ server = MCPServer .model_validate (server_in )
54+ session .add (server )
55+ session .commit ()
56+ session .refresh (server )
57+
58+ # Auto-connect if enabled
59+ if server .is_enabled :
60+ try :
61+ await mcp_manager .connect_server (server )
62+ except Exception as e :
63+ logger .error (f"Failed to connect to server { server .name } : { e } " )
64+
65+ return server
66+
67+
68+ @router .get ("/servers/{server_id}" , response_model = MCPServerPublic )
69+ async def get_mcp_server (
70+ * ,
71+ session : Session = Depends (get_db ),
72+ current_user : User = Depends (get_current_active_user ),
73+ server_id : str ,
74+ ) -> MCPServerPublic :
75+ """Get MCP server by ID."""
76+ server = session .get (MCPServer , server_id )
77+ if not server :
78+ raise HTTPException (status_code = 404 , detail = "Server not found" )
79+
80+ # Update runtime data from manager
81+ server_dict = server .model_dump ()
82+ if server .name in mcp_manager .connections :
83+ connection = mcp_manager .connections [server .name ]
84+ server_dict ["status" ] = connection .status
85+ server_dict ["error_message" ] = connection .error_message
86+ else :
87+ server_dict ["status" ] = MCPServerStatus .DISCONNECTED
88+ server_dict ["error_message" ] = None
89+
90+ return MCPServerPublic (** server_dict )
91+
92+
93+ @router .patch ("/servers/{server_id}" , response_model = MCPServerPublic )
94+ async def update_mcp_server (
95+ * ,
96+ session : Session = Depends (get_db ),
97+ current_user : User = Depends (get_current_active_superuser ),
98+ server_id : str ,
99+ server_in : MCPServerUpdate ,
100+ ) -> MCPServerPublic :
101+ """Update MCP server configuration."""
102+ server = session .get (MCPServer , server_id )
103+ if not server :
104+ raise HTTPException (status_code = 404 , detail = "Server not found" )
105+
106+ # Disconnect if connected
107+ if server .name in mcp_manager .connections :
108+ await mcp_manager .disconnect_server (server .name )
109+
110+ # Update server
111+ update_data = server_in .model_dump (exclude_unset = True )
112+ for key , value in update_data .items ():
113+ setattr (server , key , value )
114+
115+ session .add (server )
116+ session .commit ()
117+ session .refresh (server )
118+
119+ # Reconnect if enabled
120+ if server .is_enabled :
121+ try :
122+ await mcp_manager .connect_server (server )
123+ except Exception as e :
124+ logger .error (f"Failed to connect to server { server .name } : { e } " )
125+
126+ return server
127+
128+
129+ @router .delete ("/servers/{server_id}" )
130+ async def delete_mcp_server (
131+ * ,
132+ session : Session = Depends (get_db ),
133+ current_user : User = Depends (get_current_active_superuser ),
134+ server_id : str ,
135+ ) -> dict :
136+ """Delete MCP server."""
137+ server = session .get (MCPServer , server_id )
138+ if not server :
139+ raise HTTPException (status_code = 404 , detail = "Server not found" )
140+
141+ # Disconnect if connected
142+ if server .name in mcp_manager .connections :
143+ await mcp_manager .disconnect_server (server .name )
144+
145+ session .delete (server )
146+ session .commit ()
147+
148+ return {"message" : "Server deleted successfully" }
149+
150+
151+ @router .post ("/servers/{server_id}/connect" )
152+ async def connect_mcp_server (
153+ * ,
154+ session : Session = Depends (get_db ),
155+ current_user : User = Depends (get_current_active_user ),
156+ server_id : str ,
157+ ) -> dict :
158+ """Connect to an MCP server."""
159+ server = session .get (MCPServer , server_id )
160+ if not server :
161+ raise HTTPException (status_code = 404 , detail = "Server not found" )
162+
163+ if not server .is_enabled :
164+ raise HTTPException (status_code = 400 , detail = "Server is disabled" )
165+
166+ try :
167+ success = await mcp_manager .connect_server (server )
168+ if success :
169+ # Update server with discovered capabilities
170+ session .add (server )
171+ session .commit ()
172+ return {"message" : "Connected successfully" , "status" : mcp_manager .connections [server .name ].status .value }
173+ else :
174+ connection = mcp_manager .connections .get (server .name )
175+ error_msg = connection .error_message if connection else "Failed to connect"
176+ raise HTTPException (status_code = 500 , detail = error_msg )
177+ except Exception as e :
178+ raise HTTPException (status_code = 500 , detail = str (e ))
179+
180+
181+ @router .post ("/servers/{server_id}/disconnect" )
182+ async def disconnect_mcp_server (
183+ * ,
184+ session : Session = Depends (get_db ),
185+ current_user : User = Depends (get_current_active_user ),
186+ server_id : str ,
187+ ) -> dict :
188+ """Disconnect from an MCP server."""
189+ server = session .get (MCPServer , server_id )
190+ if not server :
191+ raise HTTPException (status_code = 404 , detail = "Server not found" )
192+
193+ if server .name not in mcp_manager .connections :
194+ raise HTTPException (status_code = 400 , detail = "Server not connected" )
195+
196+ await mcp_manager .disconnect_server (server .name )
197+ return {"message" : "Disconnected successfully" }
198+
199+
200+ @router .post ("/servers/{server_id}/tools/{tool_name}/call" )
201+ async def call_mcp_tool (
202+ * ,
203+ session : Session = Depends (get_db ),
204+ current_user : User = Depends (get_current_active_user ),
205+ server_id : str ,
206+ tool_name : str ,
207+ arguments : dict [str , Any ],
208+ ) -> Any :
209+ """Call a tool on an MCP server."""
210+ server = session .get (MCPServer , server_id )
211+ if not server :
212+ raise HTTPException (status_code = 404 , detail = "Server not found" )
213+
214+ if server .name not in mcp_manager .connections :
215+ raise HTTPException (status_code = 400 , detail = "Server not connected" )
216+
217+ try :
218+ result = await mcp_manager .call_tool (server .name , tool_name , arguments )
219+ return result
220+ except Exception as e :
221+ raise HTTPException (status_code = 500 , detail = str (e ))
222+
223+
224+ @router .get ("/servers/{server_id}/resources/{uri:path}" )
225+ async def get_mcp_resource (
226+ * ,
227+ session : Session = Depends (get_db ),
228+ current_user : User = Depends (get_current_active_user ),
229+ server_id : str ,
230+ uri : str ,
231+ ) -> Any :
232+ """Get a resource from an MCP server."""
233+ server = session .get (MCPServer , server_id )
234+ if not server :
235+ raise HTTPException (status_code = 404 , detail = "Server not found" )
236+
237+ if server .name not in mcp_manager .connections :
238+ raise HTTPException (status_code = 400 , detail = "Server not connected" )
239+
240+ try :
241+ result = await mcp_manager .get_resource (server .name , uri )
242+ return result
243+ except Exception as e :
244+ raise HTTPException (status_code = 500 , detail = str (e ))
245+
246+
247+ @router .post ("/servers/{server_id}/prompts/{prompt_name}" )
248+ async def get_mcp_prompt (
249+ * ,
250+ session : Session = Depends (get_db ),
251+ current_user : User = Depends (get_current_active_user ),
252+ server_id : str ,
253+ prompt_name : str ,
254+ arguments : dict [str , Any ],
255+ ) -> Any :
256+ """Get a prompt from an MCP server."""
257+ server = session .get (MCPServer , server_id )
258+ if not server :
259+ raise HTTPException (status_code = 404 , detail = "Server not found" )
260+
261+ if server .name not in mcp_manager .connections :
262+ raise HTTPException (status_code = 400 , detail = "Server not connected" )
263+
264+ try :
265+ result = await mcp_manager .get_prompt (server .name , prompt_name , arguments )
266+ return result
267+ except Exception as e :
268+ raise HTTPException (status_code = 500 , detail = str (e ))
0 commit comments