Troubleshooting
mcp-publisher authentication failed — how to fix it
Last verified: 10 August 2026 · spec 2026-07-28 · ruleset 1.4.2
mcp-publisher login failure is one of five things: a DNS TXT record on a selector instead of the apex, a stale key left behind after rotation, LibreSSL refusing Ed25519 on macOS, a GitHub org namespace that needs Owner role, or a PAT missing read:org. Find your error below.Which login are you using?
The registry grants different namespaces through different proofs, and the failures do not overlap — so start by confirming which one you are actually running.
| Command | Grants | Proof |
|---|---|---|
| login github | io.github.name/* | Device flow |
| login github-oidc | io.github.name/* | CI OIDC token |
| login dns | com.example.*/* | TXT record on the apex |
| login http | com.example.*/* | /.well-known file |
DNS login fails with a signature error, but your key is correct
DNS authentication failed:
no valid MCP public keys found in DNS TXT recordsThis is the most common mcp-publisher failure, and the error message actively misleads you. It reads like a key problem. It is almost always a placement problem.
MCP DNS authentication follows SPF-style placement — the record goes on the apex of your domain. It is not DKIM-style selector placement. A record at _mcp-registry.example.com or _mcp-auth.example.com is never read, and the registry reports a generic signature failure rather than “no record found”.
# ✗ Never read — selector placement
_mcp-registry.example.com. IN TXT "v=MCPv1; k=ed25519; p=…"
_mcp-auth.example.com. IN TXT "v=MCPv1; k=ed25519; p=…"
# ✓ Apex placement
example.com. IN TXT "v=MCPv1; k=ed25519; p=…"The fix
@ — the convention for “the domain itself” — rather than typing a subdomain. Then confirm it is actually visible:dig +short TXT example.com | grep MCPv1
# expect: "v=MCPv1; k=ed25519; p=…"
# If this returns nothing, the record is not on the apex,
# whatever your control panel shows._mcp-registry.letta.com, you should put the TXT record on letta.com”. Third-party tutorials written during that window still show the selector form. If you followed a guide and it did not work, this is probably why.Authentication broke after you rotated keys
A previous TXT record left on the apex is tried first, and its failure ends the attempt — the registry does not fall through to your new key.
The fix
dig +short TXT example.com | grep MCPv1
# More than one MCPv1 line = the stale one is shadowing the new one.The record looks right but login still fails
DNS changes are not instant. The record must propagate before the registry can read it, and community reports describe waits of up to a couple of hours depending on the provider and the previous record's TTL.
The fix
# Ask a public resolver
dig +short TXT example.com @1.1.1.1 | grep MCPv1
# Ask your domain's own authoritative nameserver — the ground truth
dig +short NS example.com
dig +short TXT example.com @<one-of-those-nameservers>openssl: Algorithm Ed25519 not found
openssl genpkey -algorithm Ed25519 -out key.pem
Algorithm Ed25519 not foundYou are running LibreSSL, not OpenSSL 3. macOS ships LibreSSL as the system openssl binary and it does not implement Ed25519 in genpkey. The Ed25519 codepath requires OpenSSL 3.0 or later.
The fix
brew install openssl@3
# Apple Silicon
/opt/homebrew/opt/openssl@3/bin/openssl genpkey -algorithm Ed25519 -out key.pem
# Intel
/usr/local/opt/openssl@3/bin/openssl genpkey -algorithm Ed25519 -out key.pemopenssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:secp384r1 -out key.pem
PUBLIC_KEY="$(openssl ec -in key.pem -text -noout -conv_form compressed \
| grep -A4 "pub:" | tail -n +2 | tr -d ' :\n' | xxd -r -p | base64)"
echo "${MY_DOMAIN}. IN TXT \"v=MCPv1; k=ecdsap384; p=${PUBLIC_KEY}\""
PRIVATE_KEY="$(openssl ec -in key.pem -noout -text \
| grep -A4 "priv:" | tail -n +2 | tr -d ' :\n')"
mcp-publisher login dns --algorithm ecdsap384 \
--domain "${MY_DOMAIN}" --private-key "${PRIVATE_KEY}"k= value in the TXT record changes to ecdsap384 and the login command needs --algorithm ecdsap384. Mismatching those two is its own signature failure.GitHub login succeeds but the org namespace is refused
You authenticated fine, and you are still only granted io.github.yourusername/* rather than io.github.yourorg/*.
Publishing under an organization namespace requires Owner role in that organization. Ordinary membership is not enough: the registry checks your membership role and grants the org namespace only to admins, so that anyone who merely belongs to an org cannot publish — or overwrite — servers under the org's name.
The fix
Works locally, fails in CI with no org membership
The device flow you ran locally carried your full identity. A Personal Access Token in CI does not, unless it was granted the right scope.
The fix
- Classic PAT — grant the
read:orgscope. - Fine-grained PAT — grant Organization permissions → Members → Read-only, the fine-grained equivalent. Without it GitHub returns no organization membership for the token and you get your personal namespace only.
mcp-publisher login github-oidc
# Uses the workflow's OIDC token — nothing to store,
# nothing to rotate, and no PAT scope to get wrong.You can't add DNS records — use HTTP verification
HTTP verification proves the same thing and grants the same com.example.*/* namespace. It is the better choice when DNS is managed by someone else, or when you want the proof to live in the same repo as the code.
The fix
https://example.com/.well-known/mcp-registry-auth containing exactly the record value, with no v=MCPv1 wrapper differences:# /.well-known/mcp-registry-auth
v=MCPv1; k=ed25519; p=${PUBLIC_KEY}PRIVATE_KEY="$(openssl pkey -in key.pem -noout -text \
| grep -A3 "priv:" | tail -n +2 | tr -d ' :\n')"
mcp-publisher login http --domain "${MY_DOMAIN}" --private-key "${PRIVATE_KEY}"Last check: the namespace must match server.json
Authentication grants you a namespace. Publishing then fails separately if server.json asks for a different one — and that failure often gets read as another auth problem.
# You logged in with:
mcp-publisher login dns --domain "example.com" → grants com.example.*/*
# So server.json must name a server inside it:
{ "name": "com.example/weather-server" } ✓
{ "name": "io.github.you/weather-server" } ✗ different namespace
{ "name": "com.example.tools/weather-server" } ✓ subdomain form“Publishers must verify ownership of their namespace through GitHub, DNS, or HTTP challenges, preventing arbitrary spam submissions.”
Our validator checks this from the outside as MCP-REG-002 — whether a published server's namespace ownership is actually verifiable — alongside MCP-REG-001 for server.json validity and MCP-REG-003 for version format.
Check the server you're publishing
Once publishing succeeds, validate the endpoint itself. A server can be published correctly and still fail the protocol rules that clients depend on.
Validate a server →Frequently asked
Why does mcp-publisher login dns fail with a signature error?
The most common cause is placing the DNS TXT record under a selector such as _mcp-registry.example.com instead of on the domain apex. MCP DNS authentication uses SPF-style placement, on the apex, not DKIM-style selector placement. A record under a selector is never read, and the registry reports a generic signature failure rather than a missing-record error, which makes it look like a key problem.
Where exactly does the MCP registry TXT record go?
On the apex of your domain — example.com itself, not a subdomain and not a selector. The record value is v=MCPv1; k=ed25519; p=YOUR_PUBLIC_KEY, using k=ecdsap384 if you generated an ECDSA P-384 key instead.
I rotated my key and now authentication fails. Why?
A stale TXT record left on the apex is tried first and causes verification to fail. Removing the old record when you add the new one is a required step, not a tidy-up.
Why does openssl say 'Algorithm Ed25519 not found'?
You are running LibreSSL rather than OpenSSL 3. macOS ships LibreSSL as the system openssl binary and it does not implement Ed25519 in genpkey. Install OpenSSL 3 with brew install openssl@3 and invoke it by full path, or use the ECDSA P-384 codepath, which works on LibreSSL.
Why can't I publish under my GitHub organization's namespace?
Publishing under io.github.orgname/* requires Owner role in that organization. Ordinary membership is not sufficient — the registry checks your membership role and grants the org namespace only to admins, so that anyone who merely belongs to an org cannot publish or overwrite servers under the org's name.
Which GitHub token scopes does mcp-publisher need in CI?
A classic PAT needs the read:org scope. A fine-grained PAT needs Organization permissions → Members → Read-only. Without it GitHub returns no organization membership for the token and you get only your personal namespace. A fine-grained PAT is also bound to a single resource owner, so it can only see the organization it was created for.