All notable changes to this project are documented here. The format is based on Keep a Changelog and this project adheres to Semantic Versioning.
Each release section uses these headings when applicable: Added, Changed,
Deprecated, Removed, Fixed, Security. See STABILITY.md
for the pre-1.0 API stability policy and deprecation timeline.
- Tool-boundary rewrite of discontinued
wiki.edg.comlinks in MCP responses (get_pagebodies, optional search snippets, recent-change comments, and bundled meeting-session wikitext when a body is included). A link with no known successor keeps its URL followed by the literal marker(stale URL), chosen so the annotation cannot open a MediaWiki link sequence inside a wikitext field. Fetch/cache wikitext remains byte-for-byte (#98). - Optional tuning for legacy URL hygiene:
ISOCPP_WIKI_URL_HYGIENE_TIMEOUT_S(per-probe HTTP timeout cap, default 12 seconds) andISOCPP_WIKI_URL_REMAP_TTL(remap-cache TTL, default 604800 seconds / one week). See .env.example. WG21_HTTP_ALLOWED_HOSTS: comma-separated Host-header allow-list (exacthost:portorhost:*wildcard) required to bind an HTTP transport to a non-loopback host. See .env.example and docs/TRANSPORT-EVAL.md.
- Extended the
mwclientPyPI release-age hard-gate waiver to 2026-12-31 while #88 succession work continues (initial waiver lapsed 2026-08-13;0.11.0remains the latest PyPI release). - Refreshed
benchmarks/cache-baseline.jsonon CI (ubuntu-latest, Python 3.12) after adding theurl_remapstable to the shared SQLite cache schema. - Stabilized cache microbenchmarks (
tests/test_benchmark.py) with batched put/get and count loops so CI regression means are ms-scale instead of µs-scale timer noise. - Meeting-time benchmark regression gate now compares the median (new
--stat medianoption inscripts/check_benchmark_regression.py) instead of the mean. The cold concurrent composite runs warmup-off with a fresh cache per round, so a single shared-runner tail spike (one ~50 ms round out of five) inflated the mean and flaked the gate while the median stayed flat; the median still catches genuine (>=1.5x) regressions. The cache gate keeps using the mean. - Legacy-URL existence checks now run through the shared
PageFetcher(cache-first, single-flight) instead of a direct client fetch, and the probe budget, deadline, andrefreshflag are threaded per tool response rather than per sanitized field. - Migrate to MCP Python SDK v2 (
mcp>=2,<3):MCPServerreplacesFastMCP,MCPErrorreplacesMcpError, and HTTP transport bind settings are passed torun()instead ofmcp.settings. Lockfile regenerated for v2 transitive deps (mcp-types,httpx2, etc.). Closes #99. - Rolled the pending Dependabot dependency bumps into the v2 migration lockfile so
they land in one reviewable change instead of conflicting group PRs
(#103,
#102,
#96):
- Runtime transitive pins refreshed in
requirements-lock.txt:cffi2.1.1,charset-normalizer3.5.1,cryptography50.0.0,filelock3.32.3,soupsieve2.9.2,sse-starlette3.4.8,starlette1.6.0,typing-inspection0.4.4,uvicorn0.52.3 (plushttpx2/httpcore22.12.0,idna3.19,python-dotenv1.2.3,rpds-py2026.6.3). - Dev tooling:
pip-tools7.6.0 → 7.6.1, which supports pip 26.2+, so the CI lockfile job drops thepip<26.2pin workaround. - Pinned GitHub Actions:
sigstore/gh-action-sigstore-pythonv3.4.0 → v3.5.0 andpypa/gh-action-pypi-publishv1.14.1 → v1.14.2 inpublish.yml.
- Runtime transitive pins refreshed in
- HTTP transports now fail closed on a non-loopback bind: with
WG21_HTTP_HOSTset to anything other than127.0.0.1/localhost/::1, the server refuses to start unlessWG21_HTTP_ALLOWED_HOSTSsupplies a Host-header allow-list, and then appliesTransportSecuritySettings(DNS-rebinding protection on,allowed_hostsset) to bothsseandstreamable-http. Previouslymain()only logged a warning and bound anyway, leaving Host-header validation off beyond localhost. Operator action: anyone already runningWG21_HTTP_HOST=0.0.0.0(or another non-loopback host) must now also setWG21_HTTP_ALLOWED_HOSTSor the server will exit at startup; loopback binds are unaffected and keep the SDK's auto-enabled protection.
0.3.1 - 2026-07-31
- CI
canary (secrets)andlive (secrets)jobs reach the wiki over a TorGuard tunnel (scripts/ci/torguard_vpn.sh), because Cloudflare blocks GitHub-hosted runner address ranges. This is the infrastructure fix for the root cause found in #53, which was closed on the skip-on-WAF behaviour that kept the blocked runs from being mistaken for credential faults. The tunnel is split: onlywiki.isocpp.orgcrosses it, so runner traffic keeps its direct path. Runs only where secrets are readable, leaving fork PRs and local runs unaffected. Needs theTORGUARD_VPN_USERNAMEandTORGUARD_VPN_PASSWORDsecrets, both readable by thelive-wikienvironment, plusTORGUARD_VPN_LOCATION, which the workflow takes from a variable when one is set to a non-empty value and otherwise falls back to a secret of the same name; on failure the OpenVPN log is uploaded as an artifact. Rotation is documented in docs/RUNBOOK.md. shellchecklint step forscripts/ci/*.shin the ubuntu/py3.12testjob.- Dated CI waiver for the
mwclient24-month PyPI release-age hard gate (config/mwclient-release-age-waiver.json); tracks succession work in issue #88.scripts/check_mwclient_release_age.pyaccepts--waiver-file/--waiver-until. - Meeting-time composite latency benchmarks (
tests/test_meeting_time_benchmark.py), committed baselinebenchmarks/meeting-time-baseline.json, and a CI regression gate in thebenchmarkjob (50% mean threshold, same as cache). Regression checking usesscripts/check_benchmark_regression.py.get_meeting_overviewis out of scope for this baseline; onlyget_meeting_sessionswarm/cold paths.
- CI
canary (secrets)andlive (secrets)jobs fail loudly on protected CI (CI_REQUIRE_LIVE_CREDS=1) when wiki credentials are missing or the live read path is blocked (WAF edge or network unreachable); fork PRs and local runs without credentials continue to skip gracefully (#86). - A protected-CI edge block now names its own cause.
tests/live_support.pychecks for a tun interface and reports either a tunnel that dropped mid-run or an exit address that is itself blocked. The HTTP status is identical in both cases while the remedies differ.
WikiClient.api(): timedquerycalls no longer take the exclusive write lock, restoring concurrent read throughput (#83).
0.3.0 - 2026-07-24
log_safety.py: broaden credential-pattern redaction (Cookie,Set-Cookie,session=,api_key, bareBearer, multilineAuthorization); recursively scrub nested logargs,exc_info, and pre-formattedexc_text.log.py:_install_package_log_safety()inserts a_SilentHandlercarryingLogSafetyFilterfirst on thewg21_wiki_mcppackage logger, so propagated records from child loggers, including rawlogging.getLogger("wg21_wiki_mcp.*")callers, are redacted before any host handler emits.
pagination.py: addcursor_offset(); malformed or negative offsetovalues raiseMcpError(INVALID_PARAMS)fromsearch_wiki,get_page, andlist_meetingsinstead of mapping toFETCH_ERROR.wiki_client.py: close the priormwclient.Siteconnection on re-login; remove unreachable SAML retry tail and unused_http_timeout.meetings.py: narrowensure_freshcritical section (HTTP GET outside lock).config.py: defaultuser_agentbuilt from__version__(no manual drift).fetch.py: usecomposite_deadline()and sharedPAGE_FETCH_TIMEOUT_MSG;dataclasses.replacefor section outcomes; sharedDEFAULT_MAX_LOCK_ENTRIES.server.py: MCP tool byte-limit defaults referencetools.pyconstants.context.py: remove redundantcurrent_ttl()wrapper.- Documentation: trim and de-duplicate
README.md; fix install pins and@developbranch references; updateARCHITECTURE.mdcomponent map; markdocs/FIRST_PYPI_PUBLISH.mdas completed archive.
list_meetingsdocstring notes offset-pagination instability when meeting titles change between pages.- Tests for log-filter idempotency, stdlib logger redaction, cursor offset validation, and site replacement on re-login.
0.2.1 - 2026-07-23
config.py:Credentials.passwordis nowfield(repr=False)so the plaintext password is excluded fromrepr()/print()/debugger output; becauseConfignestsCredentials,repr(Config(...))no longer leaks it either. Complements the log-redaction layer, which does not coverrepr. No runtime behavior change; the value remains accessible via attribute access.log_safety.py: guard the module-level_redactionsregistry with a lock;sanitize_text()snapshots under the lock so concurrent register/clear cannot raise or skip redactions. Structural safety under free-threaded Python 3.13+; CI runs CPython 3.13 (GIL), notpython3.13t.
- CI enforcement: cache benchmark regression gate is now blocking (removed
continue-on-error); mwclient release-age check splits a 12-month advisory review signal (continue-on-error) from a blocking 24-month hard migration trigger aligned withdocs/DEPENDENCY-RISK.md. Benchmark baseline means refreshed to honest CI measurements and the regression tolerance widened to 50% (µs-scale microbenchmarks have shared-runner StdDev ~= mean); the gate re-runs once before failing to absorb one-off runner spikes. - SAML headless login (
_saml_login): configurable IdP field names (WIKI_SAML_USERNAME_FIELD,WIKI_SAML_PASSWORD_FIELD) and per-step timeout (WIKI_SAML_TIMEOUT_S); DOM-based field detection fallback; retry on transient HTTP errors; diagnosticAuthErrorcontext (url, status, field names); DEBUG step logging. - Tighten
mwclientruntime pin to>=0.11.0,<0.12so unexpected minor/major releases cannot land silently via Dependabot. docs/API.mdis now generated from source (scripts/build_api_docs.py) instead of hand-maintained per-tool tables.
- Generated MCP API reference (
scripts/build_api_docs.py,docs/API.md) built from tool docstrings, type hints, and Pydantic models; CI fails when the committed reference drifts from source. - Cache throughput benchmarks (
tests/test_benchmark.py,pytest-benchmark) with CI JSON artifacts and a regression gate againstbenchmarks/cache-baseline.json(scripts/check_cache_benchmark_regression.py). See the### Changedentry above for the gate's blocking status and tolerance. - Opt-in HTTP transports via
WG21_TRANSPORT(sse,streamable-http; defaultstdiounchanged). OptionalWG21_HTTP_HOST/WG21_HTTP_PORTfor bind address. Seedocs/TRANSPORT-EVAL.md. docs/DEPENDENCY-RISK.md: mwclient upstream assessment, alternatives evaluation, abstraction boundary, and succession plan; CI PyPI release-age check (scripts/check_mwclient_release_age.py,dependency healthjob).docs/FIRST_PYPI_PUBLISH.md: one-time Trusted Publisher checklist for the first PyPI release.- Meeting-time latency gate CI step on
ubuntu-latest/ Python 3.12 (pytest -m latency_gate). - Thread-safe
get_context()/ lifespan shutdown via_state_lockinserver.py. - PyPI publish workflow (
.github/workflows/publish.yml) on published GitHub Release via Trusted Publisher;requirements-lock.txtwith CI lockfile reproducibility gate; README PyPI install instructions and PyPI project URL inpyproject.toml. - Release supply-chain hardening: GitHub Actions and pre-commit hooks pinned to
full SHA digests; publish workflow generates a CycloneDX SBOM, signs
distributions with Sigstore, and attaches the SBOM plus
.sigstore.jsonbundles to the GitHub Release; Dependabotpin-actionsgroup keeps action SHA pins current. - CI canary smoke gate (
canary (secrets)) and live tier wired to thelive-wikiGitHub environment for personal secrets; per-module coverage floors, coverage XML artifact upload, and@pytest.mark.canarylive smoke tests. CODEOWNERS, Dependabot config (weekly pip + GitHub Actions updates), CI pre-commit gate, and governance/branch-protection docs in CONTRIBUTING.md.STABILITY.md: pre-1.0 API stability tiers, SemVer rules for 0.x, and deprecation process (minimum one minor version with warning before removal).wg21_wiki_mcp.deprecation.warn_deprecated():DeprecationWarninghelper for future deprecations.Development Status :: 3 - AlphaPyPI classifier and README versioning section.- Meeting-time stall mitigation: cached outlink discovery for composite meeting
tools,
PageFetcher.get_pages(max_wait_s=…)(default 30s forget_meeting_sessions), and optional per-callWikiClient.api(timeout=…). Documented in ARCHITECTURE.md and docs/RUNBOOK.md. - Debug log when stale outlink index is served after lock contention or discovery
timeout (
title_hashonly; no page titles in logs).
search_wiki: newinclude_snippetparameter (defaultFalse); snippets are omitted unless explicitly requested.SearchHit.snippet_warningremoved;SearchResults.include_snippetdocuments caller opt-in. Tool and server instructions strengthened to state snippets must not be cited as verbatim wiki content.CODEOWNERSandCONTRIBUTING.md: add@wpak-aias co-maintainer on all owned paths.CONTRIBUTING.md: document rulesetwg21-wiki-mcp-protection(replaces legacy branch-protection UI wording).
- In-process lock slots no longer leak their reference count when acquisition times
out on an exhausted deadline (
PageFetcher._acquire_inprocand the outlink lock intools.py). Previously a timed-out waiter leftslot.usersinflated, so the slot could never be evicted and the lock maps grew without bound under repeated timeouts. The reference-counted lock map is now a single shared implementation (wg21_wiki_mcp.locks.EvictableLockMap) used by both modules, so the fix cannot diverge. WikiClient.api(): replace the exclusive session lock with a reader-writer lock so concurrent read-onlyquerycalls are not serialized across network round-trips; login, re-login, and close still take an exclusive writer lock.WikiClient.api(): release the RLock before retry backoff sleep so concurrent tool invocations are not blocked for the full sleep duration.PageFetcher._resolve_network: revalidation and batched network fetch no longer hold cross-process file locks for every title at once; file locks are acquired per-title only during cache write, reducing lock convoys in composite tools likeget_meeting_sessions.
(none)
0.2.0 - 2026-06-22
errors.py: centralized error hierarchy (WikiMcpError,AuthError,PageNotFound,FetchError,ConfigError) with documented application error codes (PAGE_NOT_FOUND=1,AUTH_ERROR=2,FETCH_ERROR=3,CONFIG_ERROR=4) and ato_mcp_error()mapping function.server._wrap(): converts every domain / transport exception to a structuredMcpError/ErrorDataat the tool boundary; all nine tools go through it.- ARCHITECTURE.md "Error contract" section enumerating codes and safety invariants.
- Adversarial and property-based offline tests (Hypothesis) for pagination
cursors, UTF-8 chunking, wikitext slot parsing, and
WikiClientHTTP/API edge paths;hypothesisadded to thedevextra. log.py: library-style stdlib logging with a packageNullHandler.log_safety.py: centralized log redaction (register_redactions,sanitize_text,LogSafetyFilter) and safe auth-error message helpers.- Resource lifecycle:
Cache.close()(context-manager supported),WikiClient.close(),MeetingCalendar.close(), andServerContext.close()orchestrating teardown; wired into the FastMCP lifespanfinallyblock. - WARNING-level observability for calendar fetch/parse failure,
wiki_statuscache-count failure, and cross-process lock timeout (logs usetitle_hash, not page titles). tests/test_lifecycle.py: shutdown, lock-map, and swallowed-path log coverage.PageFetcher.get_page_section(): section reads use section-aware cache keys and the same cache-first / single-flight path as full-page fetches.- SAML/SSO offline tests (
tests/test_wiki_client_saml.py) with synthetic HTML fixtures undertests/fixtures/saml/andresponses-mocked HTTP flows. - CONTRIBUTING.md section on testing bot-password vs user SSO auth paths.
docs/RUNBOOK.md: operator and agent-consumer troubleshooting (MCP host config, common failures, tool-selection guidance).MeetingCalendar.window_for_meeting_title(): maps meeting titles to public calendar ISO date windows.
models.pyandconfig.pyre-export their error types fromerrors.py; existing import paths are unchanged.PageFetcherin-process single-flight locks: per-title user refcount with capacity-bounded eviction of idle slots (fixes premature delete-on-release under concurrent fetches of the same title).WikiClient.close()is terminal;login()/api()reject use-after-close.Cache.close()guards against a connect/close race that could orphan SQLite connections.ServerContext.close()attempts all resource teardown even when one step fails.get_page(section=...)routes throughPageFetcherinstead of calling the wiki client directly, restoring the architectural fetch chokepoint invariant.types-requestsadded to thedevextra for mypy parity with CI.list_pagespopulatesPageList.namespace_namefrom the namespaces API.list_meetingspopulatesMeetingRef.window_start/window_endfrom the public meeting calendar when a year-month match exists.- CHANGELOG 0.1.0 tool list corrected to include
list_namespaces.
0.1.0 - 2026-06-12
- Initial release: local stdio MCP server for the WG21 wiki.
- Auto-selecting authentication: bot password preferred, headless user SSO (SimpleSAMLphp) fallback, with the working path pinned for re-login.
- Cross-process shared SQLite cache in
~/.isocpp.wiki/with per-page locking and a meeting-aware TTL driven by the public meeting calendar. - Centralized
PageFetcherwith batched (titles=) coalescing, single-flight de-duplication, and bounded concurrency. - Tools:
search_wiki,get_page,list_pages,list_namespaces,list_meetings,get_meeting_overview,get_meeting_sessions,get_recent_changes,wiki_status, with opaque cursor pagination. - Verbatim, provenance-bearing responses (canonical +
oldidURLs,revid).