refresh_cache
Refresh cached policy documents from the ASF website by re-fetching them in parallel, bypassing the 30-day cache.
Instructions
Re-fetch policy documents from the ASF website in parallel, bypassing the 30-day cache.
Omit keys to refresh all policies.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| keys | No |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| result | Yes |
Implementation Reference
- src/asf_policy_mcp/tools.py:129-162 (handler)The `refresh_cache` tool handler function. It takes an optional list of policy keys, loads the existing cache from disk, fetches each policy's text from the ASF website in parallel using a ThreadPoolExecutor with 8 workers, updates the in-memory cache dict, and persists it back to disk. Returns a summary of refreshed policies and any errors.
def refresh_cache(keys: list[str] | None = None) -> str: """Re-fetch policy documents from the ASF website in parallel, bypassing the 30-day cache. Omit keys to refresh all policies. """ unknown = [k for k in (keys or []) if k not in POLICY_SOURCES] targets = [k for k in (keys or list(POLICY_SOURCES.keys())) if k in POLICY_SOURCES] # Load once; each worker writes its own entry then we merge and save once at the end cache = fetcher.load_cache() def fetch_one(key: str) -> tuple[str, str]: text = fetcher.fetch_page_text(POLICY_SOURCES[key]["url"]) return key, text refreshed: list[str] = [] errors: list[str] = [f"Unknown key: `{k}`" for k in unknown] with ThreadPoolExecutor(max_workers=8) as pool: futures = {pool.submit(fetch_one, k): k for k in targets} for future in as_completed(futures): key, text = future.result() if text.startswith("[Error"): errors.append(f"{key}: {text}") else: cache[key] = {"text": text, "fetched_at": time.time(), "url": POLICY_SOURCES[key]["url"]} refreshed.append(key) fetcher.save_cache(cache) msg = f"Refreshed {len(refreshed)} policies: {', '.join(sorted(refreshed))}" if errors: msg += "\n\nErrors:\n" + "\n".join(errors) return msg - src/asf_policy_mcp/tools.py:128-128 (registration)The `@mcp.tool()` decorator on `refresh_cache` registers it as a FastMCP tool. The `mcp` instance is created at line 14 and run from server.py.
@mcp.tool() - src/asf_policy_mcp/fetcher.py:36-44 (helper)`fetch_page_text` is the helper called by `refresh_cache`'s inner `fetch_one` to download the HTML and convert it to plain text.
def fetch_page_text(url: str) -> str: """Fetch *url* and return its content as plain text, or an error string.""" try: with httpx.Client(follow_redirects=True, timeout=30) as client: resp = client.get(url, headers={"User-Agent": "asf-policy-mcp/0.1.0"}) resp.raise_for_status() return html_to_text(resp.text) except Exception as exc: return f"[Error fetching {url}: {exc}]" - src/asf_policy_mcp/fetcher.py:57-60 (helper)`save_cache` is called by `refresh_cache` after all fetches complete to persist the updated cache dict to disk.
def save_cache(cache: dict[str, Any]) -> None: """Persist the cache dict to disk, creating parent directories as needed.""" CACHE_FILE.parent.mkdir(parents=True, exist_ok=True) CACHE_FILE.write_text(json.dumps(cache, indent=2), encoding="utf-8") - src/asf_policy_mcp/sources.py:5-126 (schema)`POLICY_SOURCES` is the schema/registry of valid policy keys, used by `refresh_cache` to validate keys and look up URLs.
POLICY_SOURCES: dict[str, dict[str, str]] = { "pmc": { "title": "PMC Guide (Mailing Lists, Committers, PMC Members)", "url": "https://www.apache.org/dev/pmc.html", "section": "Community And Project Oversight", "description": "Rules for mailing lists, adding committers, and managing PMC membership", }, "project_independence": { "title": "Project Independence", "url": "https://community.apache.org/projectIndependence.html", "section": "Independence", "description": "Guidelines for operating independently and for the public good", }, "board_reporting": { "title": "Board Reporting Requirements", "url": "https://www.apache.org/foundation/board/reporting", "section": "Reporting", "description": "Quarterly board status report requirements for PMCs", }, "release_policy": { "title": "Release Policy", "url": "https://www.apache.org/legal/release-policy", "section": "Release", "description": "Apache software release policy - what constitutes a valid release", }, "voting": { "title": "Apache Voting Process", "url": "https://www.apache.org/foundation/voting.html", "section": "Release", "description": "Apache voting process for releases and other decisions", }, "release_distribution": { "title": "Release Distribution Policy", "url": "https://www.apache.org/dev/release-distribution", "section": "Release", "description": "Policy for how and where Apache releases are distributed", }, "security": { "title": "Security Team Guidance", "url": "https://www.apache.org/security/", "section": "Security", "description": "Security notification and disclosure procedures", }, "security_committers": { "title": "Vulnerability Handling for Committers", "url": "https://www.apache.org/security/committers.html", "section": "Security", "description": "How committers should handle reported security vulnerabilities", }, "licenses": { "title": "Contributor License Agreements (CLAs)", "url": "https://www.apache.org/licenses/", "section": "Licensing", "description": "CLA requirements and Apache license versions", }, "source_headers": { "title": "Apache Source Headers", "url": "https://www.apache.org/legal/src-headers.html", "section": "Licensing", "description": "Required Apache license headers in source files", }, "resolved_licenses": { "title": "Approved/Resolved Third-Party Licenses", "url": "https://www.apache.org/legal/resolved.html", "section": "Licensing", "description": "Which third-party licenses are Category A (allowed), B (limited), or X (forbidden)", }, "branding": { "title": "Project Branding Requirements", "url": "https://www.apache.org/foundation/marks/pmcs", "section": "Branding", "description": "Trademark and branding requirements for PMC projects", }, "trademark_maintenance": { "title": "Trademark Maintenance Responsibilities", "url": "https://www.apache.org/foundation/marks/responsibility.html", "section": "Branding", "description": "PMC responsibilities for maintaining Apache trademarks", }, "website_linking": { "title": "Website Linking Policy", "url": "https://www.apache.org/foundation/marks/linking", "section": "Branding", "description": "Policy on linking to and from Apache project websites", }, "repo_policy": { "title": "Repository Policy", "url": "https://infra.apache.org/project-repo-policy.html", "section": "Infrastructure", "description": "Policy for project source code repositories", }, "website_policy": { "title": "Website Policy", "url": "https://infra.apache.org/project-site-policy.html", "section": "Infrastructure", "description": "Policy for project websites and hosting", }, "press": { "title": "Press & Marketing Policy", "url": "https://www.apache.org/press/", "section": "Press", "description": "Guidelines for press releases and marketing coordination", }, "sponsorship": { "title": "Sponsorship Requirements", "url": "https://www.apache.org/foundation/sponsorship.html", "section": "Fundraising", "description": "Fundraising and sponsorship policies", }, "privacy": { "title": "Privacy Policy", "url": "https://privacy.apache.org/policies/privacy-policy-public.html", "section": "Privacy", "description": "ASF privacy policy", }, "incubator": { "title": "Incubator Podling Policies", "url": "https://incubator.apache.org/policy/incubation.html", "section": "Incubator", "description": "Policies governing Apache Incubator podlings", }, }