createmcps.com

Troubleshooting

mcp-publisher authentication failed — how to fix it

Last verified: 10 August 2026 · spec 2026-07-28 · ruleset 1.4.2

Nearly every 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.

CommandGrantsProof
login githubio.github.name/*Device flow
login github-oidcio.github.name/*CI OIDC token
login dnscom.example.*/*TXT record on the apex
login httpcom.example.*/*/.well-known file
Verified 10 August 2026 against the registry's own authentication documentation and CLI reference. Both are the primary sources for this page.

DNS login fails with a signature error, but your key is correct

DNS authentication failed:
no valid MCP public keys found in DNS TXT records

This 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”.

Wrong vs right
# ✗ 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

Move the record to the apex. In most DNS control panels that means setting the record Name or Host field to @ — the convention for “the domain itself” — rather than typing a subdomain. Then confirm it is actually visible:
Verify before retrying
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.
Why so many guides get this wrong: The published documentation described selector placement for a period, and the maintainers corrected it after registry issue #385, where the answer was explicitly “instead of the subdomain_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

Remove the old record at the same time you add the new one. Treat rotation as replace, never append:
Check for leftovers
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

Query an authoritative nameserver directly rather than trusting your resolver's cache, and only retry the login once it answers:
Bypass your local cache
# 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>
If the authoritative nameserver has it and public resolvers do not, you are waiting on TTL and nothing else needs fixing.

openssl: Algorithm Ed25519 not found

openssl genpkey -algorithm Ed25519 -out key.pem
Algorithm Ed25519 not found

You 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

Either install OpenSSL 3 and call it by full path:
Option A — OpenSSL 3
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.pem
or use ECDSA P-384, which works on LibreSSL:
Option B — ECDSA P-384
openssl 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}"
Note the 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

Either have an org Owner run the publish, get promoted to Owner, or publish under your personal namespace instead. This is a deliberate anti-abuse rule, so there is no flag to bypass it.

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:org scope.
  • 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.
A fine-grained PAT is bound to a single resource owner, so it can only ever see the organization it was created for — creating one under your personal account will never grant an org namespace, no matter which permissions you tick.
Or skip tokens entirely in GitHub Actions
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

Host a file at https://example.com/.well-known/mcp-registry-auth containing exactly the record value, with no v=MCPv1 wrapper differences:
File contents, then login
# /.well-known/mcp-registry-auth
v=MCPv1; k=ed25519; p=${PUBLIC_KEY}
Then
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}"
The file must be served over HTTPS at the apex domain, and must not redirect to a different host.

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.

These must agree
# 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.
MCP-REG-002 MAY · spec 2026-07-28

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.