Skip to content

Commit 940c55a

Browse files
committed
Update python sdk. Added server instructions and automatic vdb download using lifespan
Signed-off-by: Prabhu Subramanian <prabhu@appthreat.com>
1 parent 76c4946 commit 940c55a

File tree

4 files changed

+40
-14
lines changed

4 files changed

+40
-14
lines changed

packages/mcp-server-vdb/README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,21 @@ Edit the file using VS code or any editor of your choice. `~/Library/Application
3636
}
3737
```
3838

39+
`nerdctl` example.
40+
41+
```json
42+
{
43+
"mcpServers": {
44+
"vdb": {
45+
"command": "nerdctl",
46+
"args": [
47+
"run", "-i", "--rm", "-e", "VDB_HOME=/db", "-v", "$HOME/vdb:/db:rw", "ghcr.io/appthreat/mcp-server-vdb:master"
48+
]
49+
}
50+
}
51+
}
52+
```
53+
3954
Restart the Claude Desktop application.
4055

4156
If you get `ENOENT` error, specify the full path to docker. On a mac, `/Applications/Docker.app/Contents/Resources/bin/docker`.

packages/mcp-server-vdb/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ classifiers = [
2424

2525
dependencies = [
2626
"appthreat-vulnerability-db[oras]",
27-
"mcp[cli]>=1.2.1",
27+
"mcp[cli]>=1.3.0",
2828
]
2929

3030
[build-system]

packages/mcp-server-vdb/src/mcp_server_vdb/server.py

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@
2525

2626
logger = logging.getLogger('mcp_server_vdb')
2727

28-
# Age in days before the database needs refreshing. 5 days
29-
VDB_AGE_DAYS = os.getenv("VDB_AGE_DAYS", "5")
28+
# Age in days before the database needs refreshing. 2 days
29+
VDB_AGE_DAYS = os.getenv("VDB_AGE_DAYS", "2")
3030
if VDB_AGE_DAYS.isdigit():
3131
VDB_AGE_DAYS = int(VDB_AGE_DAYS)
3232

@@ -42,6 +42,17 @@
4242
pass
4343

4444

45+
SERVER_INSTRUCTIONS = """
46+
This MCP server allows users to search for vulnerabilities and malware aggregated from sources such as AppThreat vuln-list, OSV, NVD, and GitHub. Vulnerability data is stored in SQLite-based storage with indexes for offline access and efficient searches. The database is downloaded upon the first run and can be refreshed every few days by simply restarting the MCP client, such as Claude Desktop.
47+
48+
## Key Features
49+
50+
- Query vulnerabilities by Package URLs (purl), CPE identifiers, or GitHub repository URLs
51+
- Retrieve detailed information about specific vulnerabilities using CVE or GHSA IDs
52+
- Access the latest reported malware information
53+
"""
54+
55+
4556
def print_results(results):
4657
if not results:
4758
return "No results found!"
@@ -71,7 +82,7 @@ def print_results(results):
7182
async def run():
7283

7384
@asynccontextmanager
74-
async def lifespan() -> AsyncIterator[dict]:
85+
async def server_lifespan(server: Server) -> AsyncIterator[dict]:
7586
"""Manage server startup and shutdown lifecycle."""
7687
global db_conn, index_conn
7788
try:
@@ -86,7 +97,7 @@ async def lifespan() -> AsyncIterator[dict]:
8697
)
8798
download_image(db_url, config.DATA_DIR)
8899
else:
89-
logger.debug(f"Vulnerability database will be loaded from {config.DATA_DIR}")
100+
logger.debug("Vulnerability database will be loaded from %s", config.DATA_DIR)
90101
db_conn, index_conn = db_lib.get()
91102
yield {"db_conn": db_conn, "index_conn": index_conn}
92103
finally:
@@ -95,7 +106,7 @@ async def lifespan() -> AsyncIterator[dict]:
95106
if index_conn:
96107
index_conn.close()
97108

98-
server = Server("appthreat-vulnerability-db", version="1.0.0")
109+
server = Server("appthreat-vulnerability-db", version="1.0.0", instructions=SERVER_INSTRUCTIONS, lifespan=server_lifespan)
99110

100111
@server.list_resources()
101112
async def handle_list_resources() -> list[mtypes.Resource]:
@@ -112,7 +123,7 @@ async def handle_list_resources() -> list[mtypes.Resource]:
112123
async def handle_read_resource(uri: AnyUrl) -> str:
113124
if uri.scheme != "cve":
114125
raise ValueError(f"Unsupported URI scheme: {uri.scheme}")
115-
path = str(uri).replace("cve://", "").split("/")[0]
126+
path = str(uri).replace("cve://", "").split("/", maxsplit=1)[0]
116127
if not path:
117128
raise ValueError(f"Unknown resource path: {path}")
118129
raw_results = search.search_by_cve(path, with_data=True)
@@ -250,7 +261,7 @@ async def handle_list_tools() -> list[mtypes.Tool]:
250261
return [
251262
mtypes.Tool(
252263
name="search_by_purl_like",
253-
description="Search for vulnerabilities for a purl-like string",
264+
description="Search for vulnerabilities for a Package URL purl like string",
254265
inputSchema={
255266
"type": "object",
256267
"properties": {
@@ -283,11 +294,11 @@ async def handle_list_tools() -> list[mtypes.Tool]:
283294
),
284295
mtypes.Tool(
285296
name="search_by_cve",
286-
description="Search for vulnerabilities for a CVE id",
297+
description="Search for vulnerabilities for a CVE or GHSA id",
287298
inputSchema={
288299
"type": "object",
289300
"properties": {
290-
"cve_id": {"type": "string", "description": "CVE id to search"},
301+
"cve_id": {"type": "string", "description": "CVE or GHSA id to search"},
291302
},
292303
"required": ["cve_id"]
293304
}

packages/mcp-server-vdb/uv.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)