← mcp atlas

publish your MCP server

the atlas doesn't have a database of servers — it crawls tech.waow.mcp.server records that authors publish on their own atproto PDS. one record, on your identity, and every crawl finds you. no signup, no approval.

1get an app password

any atproto account works — a Bluesky account included. create an app password at settings → app passwords (never your real password). your handle (like alice.bsky.social) is the other thing you need.

2publish the record

edit the server details, then run it. the record key is your server's name, so re-running updates in place — safe to wire into your deploy.

# publish.py — run with: uv run publish.py
# /// script
# requires-python = ">=3.11"
# dependencies = ["httpx"]
# ///
import os, re
from datetime import datetime, timezone

import httpx

SERVER = {
    "$type": "tech.waow.mcp.server",
    "name": "my-server",
    "description": "what your server does, one paragraph.",
    "transport": "http",              # or "stdio" for run-locally servers
    "url": "https://my-server.example.com/mcp",   # omit for stdio
    "repo": "https://github.com/you/my-server",
    "language": "python",
    "tools": [
        {"name": "my_tool", "description": "what it does, one line."},
    ],
    "createdAt": datetime.now(timezone.utc).isoformat(),
}

# export BSKY_HANDLE=you.bsky.social BSKY_APP_PASSWORD=xxxx-xxxx-xxxx-xxxx
handle = os.environ["BSKY_HANDLE"]
password = os.environ["BSKY_APP_PASSWORD"]

# handle → DID → PDS
did = httpx.get(
    "https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle",
    params={"handle": handle},
).json()["did"]
doc = httpx.get(f"https://plc.directory/{did}").json()
pds = next(s["serviceEndpoint"] for s in doc["service"] if s["id"] == "#atproto_pds")

# sign in and put the record on YOUR pds
login = httpx.post(
    f"{pds}/xrpc/com.atproto.server.createSession",
    json={"identifier": handle, "password": password},
)
login.raise_for_status()
session = login.json()
rkey = re.sub(r"[^a-z0-9-]+", "-", SERVER["name"].lower()).strip("-")
resp = httpx.post(
    f"{pds}/xrpc/com.atproto.repo.putRecord",
    headers={"Authorization": f"Bearer {session['accessJwt']}"},
    json={
        "repo": did,
        "collection": "tech.waow.mcp.server",
        "rkey": rkey,
        "record": SERVER,
    },
)
resp.raise_for_status()
print(resp.json()["uri"])

3you're on the map

the atlas refreshes every few minutes — new records reach it off the firehose via microcosm's UFOs index, with a full relay sweep every 6 hours reconciling the long tail. hosted servers get a liveness probe against their url. inspect your record any time at pdsls.dev/at://<your-did>/tech.waow.mcp.server.

record reference

full schema lives on the network as com.atproto.lexicon.schema. the useful fields:

fieldmeaning
name *short name, also used as the record key
description *what the server does — this powers english search
createdAt *ISO timestamp
transporthttp (hosted at url) or stdio (run locally from repo)
urlremote endpoint for hosted servers
reposource repository
languagepython, typescript, javascript, go, rust, zig
toolslist of {name, description} — also powers search
environmentlist of {name, required, description} env vars clients must set
packageslist of {registry, identifier, version}pypi, npm, oci, …