The extension spec's CORS section requires
'Access-Control-Allow-Headers: Content-Type, If-None-Match' and
'Access-Control-Expose-Headers: ETag' on hosted card and catalog
endpoints; the served responses allowed only Content-Type and exposed
nothing, which would stop a browser-based client from reading the ETag
or sending If-None-Match for a cross-origin 304 revalidation. Both the
explicit discovery_response headers and the CORSMiddleware preflight
config now emit the full set, with tests asserting the headers on the
card response and on a browser preflight requesting If-None-Match.
- Replace build_server_card() with the ServerCard.from_server() classmethod,
matching the SDK's from_* alternate-constructor idiom; the _ServerIdentity
protocol moves to mcp.shared.experimental.server_card alongside it.
- Make DiscoveryResult iterable over its listings (__iter__/__len__), so
'for listing in result:' works without the .listings attribute hop.
- Remove the client-side server_card_url() helper: card URLs must come from
an AI Catalog entry per the discovery spec, never be constructed by the
client. fetch_server_card's docstring now says so.
- Explain the RFC 6598 shared address space constant in the SSRF guard and
rename it _CGNAT_NETWORK -> _SHARED_ADDRESS_SPACE; ipaddress reports these
addresses as neither private nor global, so the guard names them explicitly.
All symbols are experimental (no deprecation cycle), so the removals are clean.
New Advanced page covering serving a card, publishing on a brand
domain, static publishing, discovery and connect, ETag revalidation,
and the security model (advisory cards, endpoint-keyed dedup, host
scoped consent, SSRF policy defaults). Tutorials are pyright-checked
docs_src modules proved against the real SDK by
tests/docs_src/test_server_cards.py.