Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,14 @@ On Windows in this repo use `.venv/Scripts/python -m pytest` etc. — the venv w
## Architecture

- `src/patch_tuesday_mcp/server.py` — FastMCP app + `main()`. stdio by default; `MCP_TRANSPORT=http` serves `/mcp` + `/health` with the ASGI app from `build_http_app()` (middleware innermost→outermost: client-cleanup lifespan wrapper → body limit → rate limit → CORS; the factory exists so tests can exercise the exact production composition). uvicorn runs with `MCP_LIMIT_CONCURRENCY` (default 40) and `timeout_keep_alive=15`; `MCP_LOG_LEVEL` controls root logging (stderr). The startup settings log reports pinned trusted proxies as a count only — never echo `MCP_TRUSTED_PROXIES` values (CodeQL flags clear-text logging of them; issue #10, pinned by a test).
- `tools/search.py` — the single `msrc_search` tool and all its routing: CVE fast path (cross-month lookup), KB fast path (single KB or a batched `kb=[...]` list capped at `MAX_KB_BATCH = 30` returning grouped per-KB results, with optional supersedence chain walk), `list_months=True` catalog fast path, single-month filtered search, and historical trend search (`months_back` / `start_month`+`end_month`, capped at `MAX_TREND_MONTHS = 12`). A top-level catch-all converts unexpected exceptions to `error_kind="internal"` — `msrc_search` never raises.
- `tools/search.py` — the single `msrc_search` tool and all its routing: CVE fast path (cross-month lookup), KB fast path (single KB or a batched `kb=[...]` list capped at `MAX_KB_BATCH = 30` returning grouped per-KB results, with optional supersedence chain walk and optional `include_known_issues=True` known-issues decoration — attached even to `not_found`/`upstream` KB results since preview-only updates aren't in MSRC data, but never to `invalid_input`), `list_months=True` catalog fast path, single-month filtered search, and historical trend search (`months_back` / `start_month`+`end_month`, capped at `MAX_TREND_MONTHS = 12`). A top-level catch-all converts unexpected exceptions to `error_kind="internal"` — `msrc_search` never raises.
- `tools/formatters.py` — optional `format="markdown"|"csv"` triage renderings; JSON is always included and unchanged.
- `tools/profiles.py` — named product watchlists for `msrc_search` (`product_profile=`, plus ad-hoc `products=[...]`/`product_families=[...]`). Built-ins (`identity-core`, `endpoint`, `server-infrastructure`) merged under a `MSRC_PROFILES_PATH` JSON override with strict validation; unknown/invalid → `invalid_input`, never an unscoped fallback. Matching is local substring (union: any product OR family); profile contents are never sent upstream or to telemetry.
- `tools/prompts.py` — the `monthly_triage` MCP prompt (registered in `server.py` via `mcp.prompt`), a guided analyst workflow built entirely on `msrc_search`; optional `product_profile`/`month` args. Portable copies live in `prompts/` (plain-text prompt) and `skills/patch-tuesday-triage/` (agent skill) — keep them in sync with `tools/prompts.py` when the workflow changes.
- `feeds/http_client.py` — shared httpx client: `follow_redirects=False` (hardcoded hosts; redirects are never followed) and `get_bounded()` which streams responses with a byte cap instead of buffering unbounded bodies.
- `feeds/http_client.py` — shared httpx client: `follow_redirects=False` (hardcoded hosts; redirects are never followed automatically), `get_bounded()` which streams responses with a byte cap instead of buffering unbounded bodies, and `get_location()` which returns a redirect's status+Location without reading the body so a caller can follow one hop manually after validating the target.
- `feeds/msrc_api.py` — MSRC index + monthly CVRF fetch with in-process TTL caches (`MAX_FULL_MONTHS_CACHED = 12` — matches `MAX_TREND_MONTHS` so a max trend doesn't evict its own months; 40 slim), LRU eviction (hits refresh recency), per-month asyncio locks, `FETCH_CONCURRENCY = 3` semaphore, `force_refresh` bypass, freshness metadata. Response bodies capped via `MCP_MSRC_MAX_RESPONSE_BYTES` (64 MiB default).
- `feeds/enrichment.py` — KEV catalog + EPSS fetches (batches of 100, fetched concurrently with `EPSS_FETCH_CONCURRENCY = 3`), cached; EPSS cache capped at `MAX_EPSS_CACHE_ENTRIES = 50_000`; failures return empty ({}) — enrichment must never break a search. Bodies capped via `MCP_ENRICHMENT_MAX_RESPONSE_BYTES` (32 MiB default).
- `feeds/known_issues.py` — Microsoft-confirmed known issues per KB (`include_known_issues=True`), scraped best-effort from the public support.microsoft.com KB page (no keyless API exists; the Graph windowsupdates API needs AAD). Honest three-way status: `published` / `none_published` / `unavailable` — a fetch/parse failure is never reported as "none". Coverage boundary: known-issues sections exist mainly for Windows OS cumulative/preview updates; Office/SharePoint/SQL/.NET pages usually have none → `none_published`. Source quirks handled: `/help/{kb}` answers one same-host redirect (followed manually via `get_location()` after validating the target host), and bogus KB numbers redirect to *unrelated* articles that still echo the requested id in an analytics meta tag, so a landing page is only trusted if its URL slug or title names the requested KB (pages that never self-reference, like some .NET update pages, conservatively report no per-KB page). TTL cache 6 h (`MAX_CACHE_ENTRIES = 500`, LRU; failures aren't cached), `FETCH_CONCURRENCY = 3` semaphore shared by batch prefetch, bodies capped via `MCP_KNOWN_ISSUES_MAX_RESPONSE_BYTES` (4 MiB default). Parser is stdlib-only (regex slicing + `html.parser`); on layout drift it degrades to `unavailable` with the source URL — the `--run-live` smoke test is the drift canary.
- `models/vulnerability.py` — CVRF parsing into `Vulnerability`; numeric CVRF enums are documented constants (remediation types: 0=workaround, 1=mitigation, 2=vendor fix/KB, 4=will-not-fix). `to_summary_dict()` vs `to_detail_dict()` control output size; new fields are opt-in flags (`include_references`, `include_kb_details`, `include_kev_details`, `include_temporal`, filter-triggered `cwe`/`exploitation_assessment`).
- `models/cvss.py` — lenient CVSS v3.x vector parser; fails open to `None`, never raises.
- `middleware/` — per-IP token-bucket rate limit and request body cap, both with telemetry callbacks (`on_request`/`on_throttled`, `on_rejected`). X-Forwarded-For is honored only from private/loopback peers or `MCP_TRUSTED_PROXIES` members — a public direct peer can never forge it.
Expand All @@ -43,10 +44,9 @@ On Windows in this repo use `.venv/Scripts/python -m pytest` etc. — the venv w

1. **awesome-mcp-servers PR** — merged yet? `gh pr view 9879 --repo punkpeye/awesome-mcp-servers --json state,mergedAt` (all bot requirements met: Glama listing, badge, score A 4.8/5.0).
2. **Docker MCP Catalog PR** — CI green / review status? `gh pr view 4400 --repo docker/mcp-registry --json state,statusCheckRollup` (their CI builds the image itself; `server.yaml` pins `MCP_TRANSPORT=stdio` via config.env — if CI fails, fix in the fork branch `jonnybottles/mcp-registry:add-patch-tuesday`).
3. **Glama hosted connector** — approved yet? Check https://glama.ai/mcp/connectors?query=patch-tuesday for the ACA endpoint entry (the open-source *server* listing is already live and scored).
4. **PulseMCP auto-listing** — expected ~1 week after the 2026-07-11 registry publish: check https://www.pulsemcp.com/servers?q=patch-tuesday (they ingest the official MCP Registry; if absent after 2026-07-20, email hello@pulsemcp.com).
5. **Smithery deployment** — healthy and listed? https://smithery.ai/servers/xxbutler86xx/patch-tuesday (first deployment was still processing at submission time).
6. **Monthly draft routine** (only in the week after a Patch Tuesday) — did the Wednesday run open a `briefing/YYYY-MM` PR and email drafts to the user? Routine: https://claude.ai/code/routines/trig_01X24fvnRGhC6Lop3NRjVaJh
3. **PulseMCP auto-listing** — expected ~1 week after the 2026-07-11 registry publish: check https://www.pulsemcp.com/servers?q=patch-tuesday (they ingest the official MCP Registry; if absent after 2026-07-20, email hello@pulsemcp.com).
4. **Smithery deployment** — healthy and listed? https://smithery.ai/servers/xxbutler86xx/patch-tuesday (first deployment was still processing at submission time).
5. **Monthly draft routine** (only in the week after a Patch Tuesday) — did the Wednesday run open a `briefing/YYYY-MM` PR and email drafts to the user? Routine: https://claude.ai/code/routines/trig_01X24fvnRGhC6Lop3NRjVaJh

Standing follow-ups (no check needed, do when convenient): upload a social-preview image (repo Settings → Social preview); bump the PyPI classifier to `Development Status :: 4 - Beta` with the next release (requires version bump + `uv lock` + full deploy verification).

Expand Down
Loading