google-labs-jules[bot] commited on
Commit
54ea9d4
·
1 Parent(s): da86ac7

Update README.md with CredentialWatch system context and fix tests

Browse files

- Updated `README.md` to describe the full CredentialWatch architecture, including the 3 MCP servers, LangGraph agent, and Modal backend.
- Detailed the role of `cred_db_mcp_server` and its exposed tools.
- Added clear instructions for installation, configuration, and running with `uv`.
- Deleted obsolete `tests/test_cred_db_mcp.py` which referenced non-existent local models.
- Verified `tests/test_cred_db_mcp_server.py` passes and correctly mocks backend interactions.
- Added `pytest` as a dev dependency in `pyproject.toml`.

Files changed (4) hide show
  1. README.md +110 -34
  2. pyproject.toml +5 -0
  3. tests/test_cred_db_mcp.py +0 -152
  4. uv.lock +8 -0
README.md CHANGED
@@ -1,48 +1,124 @@
 
1
 
2
- # Usage of cred_db_mcp
3
 
4
- This server provides direct database access and logic for **CredentialWatch** via MCP-compliant tools exposed as HTTP endpoints.
5
 
6
- ## Agents Usage
 
 
7
 
8
- Agents can use this server to:
9
- 1. **Onboard new providers**: Call `sync_provider_from_npi` to fetch details from the NPI registry and create a local record.
10
- 2. **Manage Credentials**: Use `add_or_update_credential` to keep license data up to date.
11
- 3. **Monitor Compliance**: Use `list_expiring_credentials` to proactively find providers who need to renew licenses.
12
- 4. **Context Retrieval**: Use `get_provider_snapshot` to get all known data about a provider before answering user questions.
13
 
14
- ## Running the Server
15
 
16
- ```bash
17
- # Install dependencies
18
- pip install -e .
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
- # Run
21
- uvicorn src.cred_db_mcp.main:app --reload
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  ```
23
 
24
- ## Example Tool Call
25
 
26
- **Tool:** `list_expiring_credentials`
27
- **Endpoint:** `POST /mcp/tools/list_expiring_credentials`
28
- **Headers:** `Content-Type: application/json`
29
 
30
- **Body:**
31
- ```json
32
- {
33
- "window_days": 90,
34
- "dept": "Cardiology"
35
- }
36
  ```
37
 
38
- **Response:**
39
- ```json
40
- [
41
- {
42
- "provider": { "full_name": "Dr. Alice Smith", ... },
43
- "credential": { "type": "state_license", "expiry_date": "2023-12-01", ... },
44
- "days_to_expiry": 25,
45
- "risk_score": 3
46
- }
47
- ]
48
  ```
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # CredentialWatch
2
 
3
+ **CredentialWatch** is a proactive healthcare credential management system built for the **Hugging Face MCP 1st Birthday / Gradio Agents Hackathon**. It leverages the **Model Context Protocol (MCP)**, **LangGraph** agents, **Gradio**, and **Modal** to unify fragmented credential data and alert on upcoming expiries.
4
 
5
+ ## 🎯 Project Goal
6
 
7
+ CredentialWatch transforms messy, decentralized provider data (state licenses, board certifications, DEA numbers) into:
8
+ 1. **A unified, queryable source of truth.**
9
+ 2. **A proactive alerting system** for at-risk credentials.
10
 
11
+ The system is designed to solve the real-world problem of missed credential expiries leading to compliance issues, denied claims, and scheduling conflicts.
 
 
 
 
12
 
13
+ ## 🧩 Architecture
14
 
15
+ The system follows a microservice-like architecture with strict separation of concerns, orchestrated by a LangGraph agent.
16
+
17
+ ### Components
18
+
19
+ 1. **Agent UI (Gradio + LangGraph)**: The user interface where users interact with the agent (chat or sweep triggers).
20
+ 2. **Three MCP Servers**:
21
+ * **`npi_mcp`**: Read-only access to the public NPPES NPI Registry.
22
+ * **`cred_db_mcp`**: (Hosted in this repo) Internal provider & credential database operations.
23
+ * **`alert_mcp`**: Alert logging and resolution management.
24
+ 3. **Modal Backend**: Hosting FastAPI microservices and the SQLite database (`credentialwatch.db`).
25
+
26
+ ### Data Flow
27
+
28
+ ```
29
+ User <-> Gradio UI <-> LangGraph Agent
30
+ |
31
+ -----------------------------------
32
+ | | |
33
+ [npi_mcp] [cred_db_mcp] [alert_mcp]
34
+ | | |
35
+ (NPI_API) (CRED_API) (ALERT_API)
36
+ | | |
37
+ NPPES [SQLite DB] [SQLite DB]
38
+ ```
39
+
40
+ ## 📂 Repository Contents: `cred_db_mcp_server`
41
+
42
+ This repository currently hosts the **Credential Database MCP Server** (`cred_db_mcp`). This component provides the tools necessary for the agent to read from and write to the internal provider/credential database.
43
+
44
+ ### Exposed Tools
45
+
46
+ The server exposes the following MCP tools (via Gradio's MCP support):
47
+
48
+ * **`sync_provider_from_npi(npi)`**:
49
+ * Syncs a provider's data from the NPI registry (via backend) to the local database.
50
+ * *Usage*: Onboarding new providers.
51
+ * **`add_or_update_credential(provider_id, type, issuer, number, expiry_date)`**:
52
+ * Upserts a credential record for a provider.
53
+ * *Usage*: Keeping license data current.
54
+ * **`list_expiring_credentials(window_days, dept?, location?)`**:
55
+ * Returns a list of credentials expiring within the specified window (e.g., 90 days).
56
+ * *Usage*: Proactive monitoring and sweeps.
57
+ * **`get_provider_snapshot(provider_id?, npi?)`**:
58
+ * Returns provider info + all credentials + alerts.
59
+ * *Usage*: Context retrieval for user queries.
60
+
61
+ ## 🚀 Getting Started
62
+
63
+ ### Prerequisites
64
 
65
+ * **Python 3.11+**
66
+ * **uv** (recommended) or `pip`
67
+ * Access to the **Credential API Backend** (running locally or on Modal).
68
+
69
+ ### Installation
70
+
71
+ 1. Clone the repository:
72
+ ```bash
73
+ git clone https://github.com/your-username/credential-watch-cred-db-mcp.git
74
+ cd credential-watch-cred-db-mcp
75
+ ```
76
+
77
+ 2. Install dependencies using `uv`:
78
+ ```bash
79
+ uv sync
80
+ ```
81
+ Or with `pip`:
82
+ ```bash
83
+ pip install -e .
84
+ ```
85
+
86
+ ### Configuration
87
+
88
+ The server requires the backend API URL to be configured. Create a `.env` file in the root directory:
89
+
90
+ ```env
91
+ # URL of the backend Credential API service
92
+ CRED_API_BASE_URL=http://localhost:8000
93
  ```
94
 
95
+ ### Running the Server
96
 
97
+ Start the Gradio MCP server:
 
 
98
 
99
+ ```bash
100
+ uv run src/cred_db_mcp_server/main.py
 
 
 
 
101
  ```
102
 
103
+ The server will launch and:
104
+ 1. Open a Gradio UI in your browser (usually `http://127.0.0.1:7860`) where you can manually test the tools.
105
+ 2. Expose the MCP endpoints via SSE for the agent to connect to.
106
+
107
+ ## 🧪 Testing
108
+
109
+ Run the unit tests using `pytest`:
110
+
111
+ ```bash
112
+ uv run pytest
113
  ```
114
+
115
+ ## 🛠 Tech Stack
116
+
117
+ * **Python 3.11**
118
+ * **Gradio 5+** (Frontend & MCP Server)
119
+ * **FastAPI / HTTPX** (Networking)
120
+ * **Pydantic** (Data validation)
121
+ * **uv** (Package management)
122
+
123
+ ---
124
+ *Built for the Hugging Face MCP 1st Birthday Hackathon.*
pyproject.toml CHANGED
@@ -29,3 +29,8 @@ packages = ["src/cred_db_mcp_server"]
29
  [tool.pytest.ini_options]
30
  pythonpath = "src"
31
  testpaths = ["tests"]
 
 
 
 
 
 
29
  [tool.pytest.ini_options]
30
  pythonpath = "src"
31
  testpaths = ["tests"]
32
+
33
+ [dependency-groups]
34
+ dev = [
35
+ "pytest>=9.0.1",
36
+ ]
tests/test_cred_db_mcp.py DELETED
@@ -1,152 +0,0 @@
1
- import pytest
2
- from fastapi.testclient import TestClient
3
- from sqlalchemy import create_engine
4
- from sqlalchemy.orm import sessionmaker
5
- from sqlalchemy.pool import StaticPool
6
- from datetime import date, timedelta
7
- import os
8
-
9
- from src.cred_db_mcp.main import app, get_db
10
- from src.cred_db_mcp.db import Base
11
- from src.cred_db_mcp.models import Provider, Credential
12
-
13
- # Setup in-memory DB for tests
14
- # Use StaticPool to share the in-memory DB across multiple sessions/connections
15
- SQLALCHEMY_DATABASE_URL = "sqlite:///:memory:"
16
-
17
- engine = create_engine(
18
- SQLALCHEMY_DATABASE_URL,
19
- connect_args={"check_same_thread": False},
20
- poolclass=StaticPool
21
- )
22
- TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
23
-
24
- def override_get_db():
25
- try:
26
- db = TestingSessionLocal()
27
- yield db
28
- finally:
29
- db.close()
30
-
31
- app.dependency_overrides[get_db] = override_get_db
32
-
33
- @pytest.fixture(scope="module")
34
- def test_client():
35
- # Create tables once for the module
36
- Base.metadata.create_all(bind=engine)
37
- client = TestClient(app)
38
- yield client
39
- Base.metadata.drop_all(bind=engine)
40
-
41
- @pytest.fixture(autouse=True)
42
- def clean_tables():
43
- # Optional: Clear data between tests if needed, but for now just appending is fine
44
- # as long as IDs/NPIs don't clash or we don't care about accumulation.
45
- # To be safer, we can delete all data.
46
- with engine.connect() as conn:
47
- conn.execute(Credential.__table__.delete())
48
- conn.execute(Provider.__table__.delete())
49
- conn.commit()
50
-
51
- @pytest.fixture
52
- def db_session():
53
- db = TestingSessionLocal()
54
- yield db
55
- db.close()
56
-
57
- def test_read_root(test_client):
58
- response = test_client.get("/")
59
- assert response.status_code == 200
60
- assert response.json()["service"] == "cred_db_mcp"
61
-
62
- def test_add_and_snapshot_provider(test_client, db_session):
63
- prov = Provider(
64
- npi="999999", full_name="Test Doc", primary_specialty="General", is_active=True
65
- )
66
- db_session.add(prov)
67
- db_session.commit()
68
-
69
- response = test_client.post(
70
- "/mcp/tools/get_provider_snapshot",
71
- json={"npi": "999999"}
72
- )
73
- assert response.status_code == 200
74
- data = response.json()
75
- assert data["provider"]["full_name"] == "Test Doc"
76
- assert len(data["credentials"]) == 0
77
-
78
- def test_add_credential(test_client, db_session):
79
- # Seed provider
80
- prov = Provider(
81
- npi="888888", full_name="Credential Doc", primary_specialty="Surgery", is_active=True
82
- )
83
- db_session.add(prov)
84
- db_session.commit()
85
- db_session.refresh(prov)
86
-
87
- # Add Credential via tool
88
- expiry = (date.today() + timedelta(days=100)).strftime("%Y-%m-%d")
89
- response = test_client.post(
90
- "/mcp/tools/add_or_update_credential",
91
- json={
92
- "provider_id": prov.id,
93
- "type": "board_cert",
94
- "issuer": "ABMS",
95
- "number": "XYZ123",
96
- "expiry_date": expiry
97
- }
98
- )
99
- assert response.status_code == 200
100
- data = response.json()
101
- assert data["number"] == "XYZ123"
102
- assert data["status"] == "active"
103
-
104
- def test_list_expiring(test_client, db_session):
105
- # Seed provider
106
- prov = Provider(
107
- npi="777777", full_name="Expiring Doc", dept="ER", location="NYC", is_active=True
108
- )
109
- db_session.add(prov)
110
- db_session.commit()
111
- db_session.refresh(prov)
112
-
113
- # Add expiring credential (in 10 days)
114
- # expiry date must be a date object for the model
115
- expiry_1 = date.today() + timedelta(days=10)
116
- cred = Credential(
117
- provider_id=prov.id,
118
- type="license",
119
- issuer="State",
120
- number="L1",
121
- expiry_date=expiry_1,
122
- status="active"
123
- )
124
- db_session.add(cred)
125
-
126
- # Add non-expiring credential (in 100 days)
127
- expiry_2 = date.today() + timedelta(days=100)
128
- cred2 = Credential(
129
- provider_id=prov.id,
130
- type="license",
131
- issuer="State",
132
- number="L2",
133
- expiry_date=expiry_2,
134
- status="active"
135
- )
136
- db_session.add(cred2)
137
- db_session.commit()
138
-
139
- # Test tool
140
- response = test_client.post(
141
- "/mcp/tools/list_expiring_credentials",
142
- json={
143
- "window_days": 30,
144
- "dept": "ER"
145
- }
146
- )
147
- assert response.status_code == 200
148
- data = response.json()
149
- assert len(data) == 1
150
- assert data[0]["credential"]["number"] == "L1"
151
- assert data[0]["days_to_expiry"] == 10
152
- assert data[0]["risk_score"] == 3 # < 30 days
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
uv.lock CHANGED
@@ -201,6 +201,11 @@ test = [
201
  { name = "pytest-asyncio" },
202
  ]
203
 
 
 
 
 
 
204
  [package.metadata]
205
  requires-dist = [
206
  { name = "fastapi", specifier = ">=0.100.0" },
@@ -215,6 +220,9 @@ requires-dist = [
215
  ]
216
  provides-extras = ["test"]
217
 
 
 
 
218
  [[package]]
219
  name = "fastapi"
220
  version = "0.123.0"
 
201
  { name = "pytest-asyncio" },
202
  ]
203
 
204
+ [package.dev-dependencies]
205
+ dev = [
206
+ { name = "pytest" },
207
+ ]
208
+
209
  [package.metadata]
210
  requires-dist = [
211
  { name = "fastapi", specifier = ">=0.100.0" },
 
220
  ]
221
  provides-extras = ["test"]
222
 
223
+ [package.metadata.requires-dev]
224
+ dev = [{ name = "pytest", specifier = ">=9.0.1" }]
225
+
226
  [[package]]
227
  name = "fastapi"
228
  version = "0.123.0"