Skip to main content

mcp-registry-sdk

PyPI Python CI code style: Ruff typed: mypy strict License: MIT spec target

Typed, async Python SDK for the MCP Server Registry REST API. Import name: mcpreg.

Unaffiliated community implementation. This SDK is an independent, third-party project. It is not affiliated with, endorsed by, or sponsored by Anthropic, the Model Context Protocol team, or the maintainers of the official registry at registry.modelcontextprotocol.io. "Model Context Protocol", "MCP" and "MCP Server Registry" refer to the open specification this package targets; all trademarks remain with their owners. Spec quotations are for interoperability only.

Python is the gap the registry itself acknowledges: its community-clients list covers Go, TypeScript and Java, and the official mcp package on PyPI is the protocol SDK (JSON-RPC, transports, tools) — no registry code.

Highlights

Area What you get
🧱 Models All 15 server.json schema definitions, plus the ServerList/ServerResponse envelope. Lenient on parse, strict on construct; unknown fields round-trip.
🔎 Read client RegistryClient for the three GET endpoints with auto-paging, opaque-cursor handling, and a typed exception hierarchy.
✍️ Write client PublisherClient for the five write endpoints (POST /publish, PUT/DELETE version, PATCH status ×2). Credentials are a plain headers= dict — no auth class, no token acquisition.
🔄 Sync The updated_since + cursor loop aggregators otherwise rewrite: high-water mark from _meta.updatedAt, SyncState/SyncStateStore cursor persistence across restarts, reconcile() splitting deleted into a distinct outcome, resumable_scrape() and incremental_pull().
🛡️ Hardening Streaming response-body cap (max_response_bytes), method-aware retry (POST/PATCH never retried on a response-level failure; PUT/DELETE are), Retry-After honoured, identiable User-Agent.
🖥️ Server kernel mcpreg.server — the wire mechanics of answering as a registry: route resolution over percent-encoded paths, query validation, _meta envelope assembly, isLatest ordering, publish-body sanitisation. Pure functions — no ASGI, no framework, no httpx.
🧪 Testing mcpreg.testing.fixture_client — an httpx.MockTransport-backed stub serving captured responses. Plus InMemorySyncStateStore for trying the sync loop without writing a backend first.

Python 3.13 or newer is required. The registry itself is still in preview: breaking changes and data resets are on the table, and this package will stay on 0.x until that changes.

Install

pip install mcp-registry-sdk
# or
uv add mcp-registry-sdk

Schema validation of publish payloads (mcpreg.validation.validate_server_detail) pulls in jsonschema and is gated behind an optional extra — reading and writing the API never need it:

pip install 'mcp-registry-sdk[registry-tools]'
# or
uv add 'mcp-registry-sdk[registry-tools]'

Read the registry

import asyncio

from mcpreg import RegistryClient


async def main() -> None:
    async with RegistryClient() as client:
        # One page at a time — the cursor is opaque, pass it back verbatim
        page = await client.list_servers(search="filesystem", limit=20)
        for entry in page.servers:
            print(entry.server.name, entry.status)

        # Or walk every page in one go
        async for entry in client.iter_servers(search="filesystem"):
            print(entry.server.name, entry.server.version, entry.status)


asyncio.run(main())

RegistryClient() with no arguments targets the official instance; point it at any registry implementing the portable OpenAPI by passing RegistrySettings(base_url=...) or by setting MCP_REGISTRY_BASE_URL. The client targets the portable spec only — it works against subregistries and self-hosted instances, not only the official one.

A missing server raises a typed NotFoundError, not a generic HTTPError. Every error retains the raw response body so the message the registry actually sent is never lost.

Keep an index in sync

The sync layer is the reason this package exists rather than five lines of httpx. A scrape interrupted mid-run resumes with no re-read and no skip; a server that flips to deleted (a moderation takedown) is surfaced as a structurally distinct Reconciliation.removals, never silently filtered.

from mcpreg import RegistryClient
from mcpreg.sync import InMemorySyncStateStore, reconcile, resumable_scrape

# InMemorySyncStateStore is for a first run and for tests — it does NOT survive a process
# restart. Bring your own SyncStateStore (file, database, Redis) for anything unattended.
store = InMemorySyncStateStore()

async with RegistryClient() as client:
    async for changes in resumable_scrape(client, store):
        for entry in changes.upserts:
            index[entry.server.name] = entry
        for entry in changes.removals:  # status == "deleted"; impossible to ignore by design
            index.pop(entry.server.name, None)

incremental_pull() is the steady-state counterpart — it sources updated_since from the saved high-water mark (never the local clock; clock skew silently drops entries), and forces include_deleted=True so takedowns reach an incremental consumer at all.

Publish a server

import os

from mcpreg import PublisherClient, RegistrySettings, ServerDetail

server_detail = ServerDetail(
    name="io.github.example/weather",
    description="A weather server.",
    version="1.0.0",
)

async with PublisherClient(
    headers={"Authorization": f"Bearer {os.environ['MCP_REGISTRY_TOKEN']}"},
    settings=RegistrySettings(base_url="https://registry.example.com"),
) as pub:
    await pub.publish(server_detail)

headers= is a plain HTTP concept, not an auth abstraction: the SDK has no opinion about what scheme a caller uses (Bearer, basic auth, a custom API-key header, none at all for a local registry). Auth acquisition is out of scope — bring a token you already have.

For read-after-write, share the publisher's connection pool rather than paying for a second one:

async with RegistryClient(settings, http=pub.http) as reader:
    current = await reader.get_version(server_detail.name, "latest")

pub.http is borrowed, not owned: RegistryClient will not close it on exit.

Serve a registry

mcpreg.server is the inverse of the client — the wire mechanics of answering as a registry, for a backend author. Pure functions over the same wire models, no ASGI, no framework, no httpx, so it composes with FastAPI, Starlette, Litestar or Django with no adapter:

from mcpreg.server import Operation, RegistryError, ListQuery, page, resolve, to_wire

# `request` is whatever framework object carries the method, the raw (still-encoded) path,
# and the query string. Starlette: request.method / request.scope["raw_path"] / request.query_params.
route = resolve("GET", "/v0.1/servers")
if route is None:
    raise RegistryError(404, "no such endpoint")
if route.operation is Operation.LIST_SERVERS:
    query = ListQuery.from_params({"limit": "10"})   # include_deleted already correct
    # `my_entries(query)` is the backend's own lookup — whatever the storage layer is
    window: list = []
    body = to_wire(page(window, next_cursor=None))

It never stores, authenticates, or verifies namespace ownership — see docs/04-server.md. The trust model rests on that last one, so an SDK that appeared to enforce it while actually rubber-stamping would be worse than one that never offered.

Scope

In: the portable OpenAPI's three read endpoints, the five write endpoints, typed models for all fifteen schema definitions, the incremental-sync loop, registry-author helpers (schema validation, the error-envelope builder, the write-side wire models), and the mcpreg.server kernel.

Out: connecting to MCP servers (the mcp package's job), building or running install commands, curation, and auth acquisition. And — even inside mcpreg.server — storage, authentication, and namespace-ownership verification: the three things that make a registry a product rather than a wire format.

Full reasoning in docs/00-scope.md.

Documentation

Development

uv sync
uv run pytest                   # default run: no network
REGISTRY_LIVE=1 uv run pytest -m live   # opt-in: hits the real registry
uv run ruff check src tests
uv run ruff format --check src tests
uv run mypy src tests
uv run lint-imports

Every Python code sample in this README is executed by tests/test_readme.py, so the README cannot silently drift from the actual API.

Issue tracking is beads: bd ready for available work.

Release

Releases are built from a clean main commit that has passed CI. The package version and source tag must agree: version 0.1.0 is tagged v0.1.0.

uv build
uv run --with twine twine check dist/*
uv publish dist/*

After publication, verify the supported boundary from a clean environment by installing the version range downstream applications will use: mcp-registry-sdk>=0.1,<0.2.

MIT licensed.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mcp_registry_sdk-0.1.0.tar.gz (157.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mcp_registry_sdk-0.1.0-py3-none-any.whl (81.7 kB view details)

Uploaded Python 3

File details

Details for the file mcp_registry_sdk-0.1.0.tar.gz.

File metadata

  • Download URL: mcp_registry_sdk-0.1.0.tar.gz
  • Upload date:
  • Size: 157.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.19 {"installer":{"name":"uv","version":"0.11.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for mcp_registry_sdk-0.1.0.tar.gz
Algorithm Hash digest
SHA256 833a30f1e297a7876f374175cf7840c96d215fba2e0c86de7e17be65c1d2b8c3
MD5 5ef0a4d1677fdd39264be83a23fc5101
BLAKE2b-256 f4d76e876cf255a80f54934853b59219731466d992a8f4ff4a6d973b76b9f4b9

See more details on using hashes here.

File details

Details for the file mcp_registry_sdk-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: mcp_registry_sdk-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 81.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.19 {"installer":{"name":"uv","version":"0.11.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for mcp_registry_sdk-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 35b4bba1905b31527fcc50cb26e4c77e09d99eeec35e8371acab215783180233
MD5 33ce5aaaf13779e51a4c6c01ab9176b3
BLAKE2b-256 506fc427c388492e2f583c927cc1979cb020aeb990aa318e9fe3d8401441dd12

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page