Humanlearning commited on
Commit
b749aa6
·
1 Parent(s): ad25b37

feat: Introduce Gradio UI for interactive query and expiry sweep, add a multi-server MCP client, and simplify app startup to directly launch Gradio.

Browse files
app.py CHANGED
@@ -1,39 +1,10 @@
1
- import os
2
  import sys
3
- import asyncio
4
- from fastapi import FastAPI
5
- import gradio as gr
6
- import uvicorn
7
 
8
  # Add src to path so we can import the package
9
  sys.path.append(os.path.join(os.path.dirname(__file__), "src"))
10
 
11
- from credentialwatch_agent.main import demo, mcp_client
12
-
13
- from contextlib import asynccontextmanager
14
-
15
- @asynccontextmanager
16
- async def lifespan(app: FastAPI):
17
- """Manage application lifespan."""
18
- print("Connecting to MCP servers...")
19
- try:
20
- await mcp_client.connect()
21
- except Exception as e:
22
- print(f"Error connecting to MCP servers: {e}")
23
-
24
- yield
25
-
26
- print("Closing MCP connections...")
27
- await mcp_client.close()
28
-
29
- # Create FastAPI app with lifespan
30
- app = FastAPI(lifespan=lifespan)
31
-
32
- # Mount Gradio app
33
- # path="/" mounts it at the root
34
- app = gr.mount_gradio_app(app, demo, path="/")
35
 
36
  if __name__ == "__main__":
37
- # Run with uvicorn
38
- # This ensures everything runs on the same async loop
39
- uvicorn.run(app, host="0.0.0.0", port=7860)
 
 
1
  import sys
2
+ import os
 
 
 
3
 
4
  # Add src to path so we can import the package
5
  sys.path.append(os.path.join(os.path.dirname(__file__), "src"))
6
 
7
+ from credentialwatch_agent.main import demo
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
  if __name__ == "__main__":
10
+ demo.launch()
 
 
pyproject.toml CHANGED
@@ -11,9 +11,7 @@ dependencies = [
11
  "mcp>=0.1.0",
12
  "gradio[mcp]>=6.0.1",
13
  "python-dotenv>=1.0.0",
14
- "httpx>=0.25.0",
15
- "fastapi>=0.100.0",
16
- "uvicorn>=0.20.0"
17
  ]
18
 
19
  [build-system]
 
11
  "mcp>=0.1.0",
12
  "gradio[mcp]>=6.0.1",
13
  "python-dotenv>=1.0.0",
14
+ "httpx>=0.25.0"
 
 
15
  ]
16
 
17
  [build-system]
src/credentialwatch_agent/main.py CHANGED
@@ -16,6 +16,7 @@ async def run_expiry_sweep(window_days: int = 90) -> Dict[str, Any]:
16
  """
17
  Runs the expiry sweep workflow.
18
  """
 
19
  print(f"Starting expiry sweep for {window_days} days...")
20
  # Initialize state
21
  initial_state = {
@@ -45,6 +46,7 @@ async def run_chat_turn(message: str, history: List[List[str]]) -> str:
45
  """
46
  Runs a turn of the interactive query agent.
47
  """
 
48
  # Convert history to LangChain format
49
  messages = []
50
  for item in history:
 
16
  """
17
  Runs the expiry sweep workflow.
18
  """
19
+ await mcp_client.connect()
20
  print(f"Starting expiry sweep for {window_days} days...")
21
  # Initialize state
22
  initial_state = {
 
46
  """
47
  Runs a turn of the interactive query agent.
48
  """
49
+ await mcp_client.connect()
50
  # Convert history to LangChain format
51
  messages = []
52
  for item in history:
src/credentialwatch_agent/mcp_client.py CHANGED
@@ -20,43 +20,48 @@ class MCPClient:
20
 
21
  self._exit_stack = AsyncExitStack()
22
  self._sessions: Dict[str, ClientSession] = {}
 
 
23
 
24
  async def connect(self):
25
- """Establishes connections to all MCP servers."""
26
- # In a real app, we might want to connect lazily or in parallel.
27
- # For simplicity, we'll try to connect to all.
28
-
29
- # Connect to NPI MCP
30
- try:
31
- # Note: mcp.client.sse.sse_client is a context manager that yields (read_stream, write_stream)
32
- # We need to keep the context open.
33
- npi_transport = await self._exit_stack.enter_async_context(sse_client(self.npi_url))
34
- self._sessions["npi"] = await self._exit_stack.enter_async_context(
35
- ClientSession(npi_transport[0], npi_transport[1])
36
- )
37
- await self._sessions["npi"].initialize()
38
- except Exception as e:
39
- print(f"Warning: Failed to connect to NPI MCP at {self.npi_url}. Using mock data. Error: {e}")
 
40
 
41
- # Connect to Cred DB MCP
42
- try:
43
- cred_transport = await self._exit_stack.enter_async_context(sse_client(self.cred_db_url))
44
- self._sessions["cred_db"] = await self._exit_stack.enter_async_context(
45
- ClientSession(cred_transport[0], cred_transport[1])
46
- )
47
- await self._sessions["cred_db"].initialize()
48
- except Exception as e:
49
- print(f"Warning: Failed to connect to Cred DB MCP at {self.cred_db_url}. Using mock data. Error: {e}")
50
 
51
- # Connect to Alert MCP
52
- try:
53
- alert_transport = await self._exit_stack.enter_async_context(sse_client(self.alert_url))
54
- self._sessions["alert"] = await self._exit_stack.enter_async_context(
55
- ClientSession(alert_transport[0], alert_transport[1])
56
- )
57
- await self._sessions["alert"].initialize()
58
- except Exception as e:
59
- print(f"Warning: Failed to connect to Alert MCP at {self.alert_url}. Using mock data. Error: {e}")
 
 
60
 
61
  async def close(self):
62
  """Closes all connections."""
 
20
 
21
  self._exit_stack = AsyncExitStack()
22
  self._sessions: Dict[str, ClientSession] = {}
23
+ self._connected = False
24
+ self._connect_lock = asyncio.Lock()
25
 
26
  async def connect(self):
27
+ """Establishes connections to all MCP servers. Idempotent."""
28
+ async with self._connect_lock:
29
+ if self._connected:
30
+ return
31
+
32
+ # Connect to NPI MCP
33
+ try:
34
+ # Note: mcp.client.sse.sse_client is a context manager that yields (read_stream, write_stream)
35
+ # We need to keep the context open.
36
+ npi_transport = await self._exit_stack.enter_async_context(sse_client(self.npi_url))
37
+ self._sessions["npi"] = await self._exit_stack.enter_async_context(
38
+ ClientSession(npi_transport[0], npi_transport[1])
39
+ )
40
+ await self._sessions["npi"].initialize()
41
+ except Exception as e:
42
+ print(f"Warning: Failed to connect to NPI MCP at {self.npi_url}. Using mock data. Error: {e}")
43
 
44
+ # Connect to Cred DB MCP
45
+ try:
46
+ cred_transport = await self._exit_stack.enter_async_context(sse_client(self.cred_db_url))
47
+ self._sessions["cred_db"] = await self._exit_stack.enter_async_context(
48
+ ClientSession(cred_transport[0], cred_transport[1])
49
+ )
50
+ await self._sessions["cred_db"].initialize()
51
+ except Exception as e:
52
+ print(f"Warning: Failed to connect to Cred DB MCP at {self.cred_db_url}. Using mock data. Error: {e}")
53
 
54
+ # Connect to Alert MCP
55
+ try:
56
+ alert_transport = await self._exit_stack.enter_async_context(sse_client(self.alert_url))
57
+ self._sessions["alert"] = await self._exit_stack.enter_async_context(
58
+ ClientSession(alert_transport[0], alert_transport[1])
59
+ )
60
+ await self._sessions["alert"].initialize()
61
+ except Exception as e:
62
+ print(f"Warning: Failed to connect to Alert MCP at {self.alert_url}. Using mock data. Error: {e}")
63
+
64
+ self._connected = True
65
 
66
  async def close(self):
67
  """Closes all connections."""