Spaces:
Sleeping
Sleeping
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 +3 -32
- pyproject.toml +1 -3
- src/credentialwatch_agent/main.py +2 -0
- src/credentialwatch_agent/mcp_client.py +38 -33
app.py
CHANGED
|
@@ -1,39 +1,10 @@
|
|
| 1 |
-
import os
|
| 2 |
import sys
|
| 3 |
-
import
|
| 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
|
| 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 |
-
|
| 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 |
-
|
| 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 |
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."""
|