Skip to content

Commit de44efc

Browse files
committed
chore: quality improvements from Oracle architecture review
- Add MIT LICENSE file - Add py.typed marker for type checker support - Fix stale about() resource: 17 tools → 24 tools, 9 sources → 11 sources - Replace 3x silent 'except Exception: pass' with debug logging - Reuse httpx.AsyncClient in NVDClient (was creating per-request) - Add disclaimer footers to assess_mcp_security and threat_model outputs - 224/224 tests pass
1 parent dc420d1 commit de44efc

5 files changed

Lines changed: 53 additions & 22 deletions

File tree

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 zer0-kr
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

src/owasp_mcp/nvd.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ def __init__(self, api_key: str | None = None) -> None:
1616
self.api_key = api_key
1717
self._last_request = 0.0
1818
self._min_interval = 0.6 if api_key else 6.0
19+
self._client: httpx.AsyncClient | None = None
20+
21+
async def _ensure_client(self) -> httpx.AsyncClient:
22+
if self._client is None or self._client.is_closed:
23+
self._client = httpx.AsyncClient(follow_redirects=True, timeout=30)
24+
return self._client
1925

2026
async def _get(self, endpoint: str, params: dict) -> dict:
2127
now = time.monotonic()
@@ -27,16 +33,15 @@ async def _get(self, endpoint: str, params: dict) -> dict:
2733
if self.api_key:
2834
headers["apiKey"] = self.api_key
2935

30-
async with httpx.AsyncClient(follow_redirects=True) as client:
31-
resp = await client.get(
32-
f"{self.BASE}/{endpoint}",
33-
params=params,
34-
headers=headers,
35-
timeout=30,
36-
)
37-
resp.raise_for_status()
38-
self._last_request = time.monotonic()
39-
return resp.json()
36+
client = await self._ensure_client()
37+
resp = await client.get(
38+
f"{self.BASE}/{endpoint}",
39+
params=params,
40+
headers=headers,
41+
)
42+
resp.raise_for_status()
43+
self._last_request = time.monotonic()
44+
return resp.json()
4045

4146
async def search_cves(
4247
self,

src/owasp_mcp/py.typed

Whitespace-only changes.

src/owasp_mcp/server.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,16 @@ def about() -> str:
4545
f"- **Database available:** {'Yes' if info['exists'] else 'No'}\n"
4646
f"- **Database built:** {info.get('built_at', 'never')}\n"
4747
f"- **Database path:** `{info['path']}`\n\n"
48-
"## Available Tools (17)\n\n"
48+
"## Available Tools (24)\n\n"
4949
"- `list_projects` / `search_projects` / `get_project`\n"
50-
"- `search_owasp` — Cross-source search (9 data sources)\n"
51-
"- `get_top10` / `get_api_top10` / `get_llm_top10`\n"
50+
"- `search_owasp` — Cross-source search (11 data sources)\n"
51+
"- `get_top10` / `get_api_top10` / `get_llm_top10` / `get_mcp_top10`\n"
5252
"- `get_asvs` / `get_wstg` / `get_masvs`\n"
53-
"- `get_proactive_controls` / `get_cheatsheet`\n"
54-
"- `cross_reference` / `assess_stack` / `generate_checklist`\n"
53+
"- `get_proactive_controls` / `get_cheatsheet` / `get_cwe`\n"
54+
"- `cross_reference` / `compliance_map`\n"
55+
"- `assess_stack` / `generate_checklist`\n"
56+
"- `assess_mcp_security` / `threat_model`\n"
57+
"- `search_cve` / `get_cve_detail`\n"
5558
"- `update_database` / `database_status`\n\n"
5659
"## Available Prompts (4)\n\n"
5760
"- `security_review` — Guided security review workflow\n"

src/owasp_mcp/tools/owasp_tools.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -495,8 +495,8 @@ async def cross_reference(
495495
for row in asvs_results:
496496
lines.append(f"- {_fmt_asvs(row)}")
497497
sections.append("\n".join(lines))
498-
except Exception:
499-
pass
498+
except Exception as exc:
499+
log.debug("ASVS cross-reference search failed: %s", exc)
500500

501501
try:
502502
wstg_results, _ = db.search_fts(db_path, "wstg", cwe_num, limit=10)
@@ -520,8 +520,8 @@ async def cross_reference(
520520
for row in wstg_results:
521521
lines.append(f"- {_fmt_wstg(row)}")
522522
sections.append("\n".join(lines))
523-
except Exception:
524-
pass
523+
except Exception as exc:
524+
log.debug("WSTG cross-reference search failed: %s", exc)
525525

526526
if top10_id:
527527
id_upper = top10_id.strip().upper()
@@ -738,8 +738,8 @@ async def assess_stack(
738738
if cs_results:
739739
names = [r.get("name", "") for r in cs_results]
740740
sections.append(f"### Related Cheat Sheets for \"{term}\"\n" + "\n".join(f"- {n}" for n in names))
741-
except Exception:
742-
pass
741+
except Exception as exc:
742+
log.debug("Cheat sheet search in assess_stack failed: %s", exc)
743743

744744
header = f"## Security Assessment: {stack}\n"
745745
if not sections:
@@ -1016,6 +1016,7 @@ async def assess_mcp_security(
10161016
lines.append("2. Implement authentication (MCP07) and audit logging (MCP08) as baseline controls")
10171017
lines.append("3. Pin and verify all tool/plugin dependencies (MCP03, MCP04)")
10181018
lines.append("4. Scope agent permissions to minimum required (MCP02)")
1019+
lines.append("\n_Note: This assessment is based on keyword analysis of your description. For a comprehensive review, examine each MCP Top 10 item individually using `get_mcp_top10`._")
10191020

10201021
return "\n".join(lines)
10211022

@@ -1097,7 +1098,8 @@ async def threat_model(
10971098
if not sections:
10981099
return f"{header}\nNo significant threats identified from the description. Provide more detail about components, data flows, and trust boundaries."
10991100

1100-
return header + "\n\n".join(sections)
1101+
footer = "\n\n_Note: This threat model is based on keyword analysis. For comprehensive STRIDE analysis, provide detailed architecture documentation and review each category with domain-specific tools._"
1102+
return header + "\n\n".join(sections) + footer
11011103

11021104
@mcp.tool(annotations=ToolAnnotations(readOnlyHint=True, idempotentHint=True))
11031105
async def get_cwe(

0 commit comments

Comments
 (0)