|
2 | 2 |
|
3 | 3 | WARNING: These APIs are experimental and may change without notice. |
4 | 4 |
|
5 | | -A server author advertises their MCP server by serving an AI Catalog from the |
6 | | -well-known path, with an entry pointing at the server's Server Card:: |
| 5 | +A server advertises its MCP server(s) by serving an AI Catalog from the |
| 6 | +well-known path, with one entry per Server Card:: |
7 | 7 |
|
8 | | - from mcp.server.experimental.ai_catalog import mount_ai_catalog, server_card_entry |
9 | | - from mcp.server.experimental.server_card import build_server_card, mount_server_card |
10 | | - from mcp.shared.experimental.ai_catalog import AICatalog |
| 8 | + catalog = AICatalog(entries=[server_card_entry(card, "https://example.com/server-card")]) |
| 9 | + mount_ai_catalog(server.streamable_http_app(), catalog) # GET /.well-known/ai-catalog.json |
11 | 10 |
|
12 | | - card = build_server_card(server, name="io.modelcontextprotocol.examples/dice-roller") |
13 | | -
|
14 | | - app = server.streamable_http_app() |
15 | | - mount_server_card(app, card, path="/server-card.json") |
16 | | - catalog = AICatalog(entries=[server_card_entry(card, "https://dice.example.com/server-card.json")]) |
17 | | - mount_ai_catalog(app, catalog) # GET /.well-known/ai-catalog.json |
18 | | -
|
19 | | -To write a catalog to a file instead, serialize it with |
| 11 | +To write a catalog to a file instead, use |
20 | 12 | ``catalog.model_dump_json(by_alias=True, exclude_none=True)``. |
21 | 13 | """ |
22 | 14 |
|
|
29 | 21 |
|
30 | 22 | from mcp.shared.experimental.ai_catalog.types import ( |
31 | 23 | AI_CATALOG_MEDIA_TYPE, |
| 24 | + AI_CATALOG_URN_PREFIX, |
32 | 25 | AI_CATALOG_WELL_KNOWN_PATH, |
33 | 26 | MCP_SERVER_CARD_MEDIA_TYPE, |
34 | | - MCP_SERVER_URN_PREFIX, |
35 | 27 | AICatalog, |
36 | 28 | CatalogEntry, |
37 | 29 | ) |
|
40 | 32 | __all__ = ["DISCOVERY_HEADERS", "server_card_entry", "ai_catalog_route", "mount_ai_catalog"] |
41 | 33 |
|
42 | 34 | #: Response headers for discovery endpoints (catalogs and the artifacts they |
43 | | -#: reference). Browser-based clients must be able to read them: the discovery |
44 | | -#: spec makes the CORS headers a MUST and the caching header a SHOULD. |
| 35 | +#: reference): CORS headers so browser clients can read them, plus a caching |
| 36 | +#: hint. |
45 | 37 | DISCOVERY_HEADERS = { |
46 | 38 | "Access-Control-Allow-Origin": "*", |
47 | 39 | "Access-Control-Allow-Methods": "GET", |
|
50 | 42 | } |
51 | 43 |
|
52 | 44 |
|
| 45 | +def _air_identifier(card_name: str) -> str: |
| 46 | + """Derive an AI Catalog ``urn:air:`` identifier from a Server Card name. |
| 47 | +
|
| 48 | + The card ``name`` is ``namespace/suffix`` in reverse-DNS form |
| 49 | + (``com.example/weather``); the namespace labels are reversed to forward-DNS |
| 50 | + (``com.example`` -> ``example.com``) and the suffix appended: |
| 51 | + ``urn:air:example.com:weather``. |
| 52 | + """ |
| 53 | + namespace, _, suffix = card_name.partition("/") |
| 54 | + publisher = ".".join(reversed(namespace.split("."))) |
| 55 | + return f"{AI_CATALOG_URN_PREFIX}{publisher}:{suffix}" |
| 56 | + |
| 57 | + |
53 | 58 | def server_card_entry(card: ServerCard, url: str) -> CatalogEntry: |
54 | 59 | """Build the catalog entry advertising ``card``, served at ``url``. |
55 | 60 |
|
56 | | - The entry's identifier is derived from the card's ``name`` per the MCP |
57 | | - discovery extension (``urn:mcp:server:<name>``); display name, description |
58 | | - and version are taken from the card. ``url`` should be the absolute URL |
59 | | - the card is retrievable from, since catalogs may be fetched cross-domain. |
| 61 | + The entry's identifier is derived from the card's ``name`` |
| 62 | + (``urn:air:{publisher}:{name}``); display name, description and version are |
| 63 | + taken from the card. ``url`` should be the absolute URL the card is |
| 64 | + retrievable from, since catalogs may be fetched cross-domain. |
60 | 65 | """ |
61 | 66 | return CatalogEntry( |
62 | | - identifier=f"{MCP_SERVER_URN_PREFIX}{card.name}", |
| 67 | + identifier=_air_identifier(card.name), |
63 | 68 | display_name=card.title or card.name, |
64 | 69 | media_type=MCP_SERVER_CARD_MEDIA_TYPE, |
65 | 70 | url=url, |
|
0 commit comments