fix(publish): correct misleading org-namespace guidance in 403 and docs - #1483
Open
rdimitrov wants to merge 1 commit into
Open
fix(publish): correct misleading org-namespace guidance in 403 and docs#1483rdimitrov wants to merge 1 commit into
rdimitrov wants to merge 1 commit into
Conversation
The 403 returned for an unauthorized `io.github.*` publish advised making your GitHub organization membership public. That stopped being how org namespaces are authorized in #1383, which replaced the public-membership lookup with a check on the caller's organization *role*. The advice now sends reporters off to re-check GitHub settings that were never the problem -- see #1468, where it cost a week of back-and-forth. Replace it with the two real requirements (Owner role, plus a credential that can read that role) and name the credentials that actually can: GitHub Actions OIDC, or a PAT supplied via --token. Document the underlying limitation in authentication.mdx. The interactive device flow authenticates against a GitHub App, and GitHub App user tokens cannot read organization memberships -- GitHub returns 200 with an empty list, so the registry concludes you own no organizations. Verified against production: an OAuth token with read:org returns 16 orgs (8 as Owner) for the same account where the device-flow token returns 0. The page previously walked through the device flow and then described org publishing as though it worked, qualifying only the PAT cases. Authorization logic is untouched; this changes message and doc text only. Co-Authored-By: Claude <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR updates publish-time guidance and documentation to accurately explain why GitHub organization namespace publishes can be refused (especially for device-flow logins), without changing authorization behavior.
Changes:
- Updates the
/publish403 permission error hint forio.github.*publishes and centralizes the docs link inorgNamespaceDocsURL. - Adds in-package tests covering the updated permission error messaging behavior.
- Updates
authentication.mdxwith an explicit warning that the interactive device flow cannot currently grant org namespaces, and points to working alternatives.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| internal/api/handlers/v0/publish.go | Updates the GitHub-related 403 guidance and extracts the auth docs URL constant. |
| internal/api/handlers/v0/publish_internal_test.go | Adds unit tests validating the new error-message content and scoping. |
| docs/modelcontextprotocol-io/authentication.mdx | Documents the device-flow limitation for org namespaces and lists working auth paths. |
Comment on lines
+104
to
+108
| errorMsg += ". Publishing under a GitHub organization namespace requires both that you are an Owner of that organization" + | ||
| " and that you authenticate with a credential that can read your organization role." + | ||
| " The interactive 'mcp-publisher login github' flow cannot read organization roles; instead publish from GitHub Actions" + | ||
| " (which uses OIDC), or run 'mcp-publisher login github --token <classic PAT with the read:org scope>'." + | ||
| " See " + orgNamespaceDocsURL |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
When a publish is refused for an
io.github.*namespace, the 403 tells you to make your GitHub organization membership public:That advice has been wrong since #1383. It replaced the public-membership lookup (
GET /users/{login}/orgs) with a check on the caller's organization role (GET /user/memberships/orgs). Public membership no longer factors into the decision at all.The cost is not theoretical. In #1468 the reporter is an Owner of the org, and their membership is public — confirmed via
GET /orgs/qatouch/public_members. The message sent them, and a maintainer, through a week of re-checking GitHub settings that were never the problem.The docs have the mirror-image defect:
authentication.mdxwalks the reader throughmcp-publisher login github, then explains that org publishing requires Owner, and qualifies only the PAT cases — never saying that the interactive flow cannot grant an org namespace at all.Root cause of the underlying limitation
mcp-publisher login githubauthenticates against a GitHub App (Iv23liUydBbI7Z2Q9bOZ→mcp-registry-login-prod), not an OAuth App. GitHub Apps ignore the OAuthscopeparameter, so theread:org read:userrequested incmd/publisher/auth/github-at.go:144has no effect, and GitHub App user tokens cannot read organization memberships.Critically, GitHub signals this with
200 OKand an empty array, not a 403 — so none of the safeguards #1383 added (rate-limit detection,X-GitHub-SSO, the deliberate fail-closed path) ever execute. The registry simply concludes "owns no organizations."Verified against production, same account and same request within the same hour:
GET /user/memberships/orgs?state=active&per_page=100&page=1read:orgThe resulting prod JWT carried exactly one permission:
io.github.<user>/*.Consistent with that timeline:
io.github.qatouch/qatouchpublished successfully on 2026-07-03, and v1.8.0 (the first release containing #1383) reached production 2026-07-13.What this PR changes
Nothing about authorization. Message and documentation text only.
publish.go— the org hint now states the two real requirements (Owner role, and a credential that can read that role) and names the credentials that work: GitHub Actions OIDC, ormcp-publisher login github --token <classic PAT with read:org>. The docs URL is extracted toorgNamespaceDocsURLand points atmodelcontextprotocol.io/registry/authentication(verified 200).authentication.mdx— a<Warning>stating plainly that the device flow cannot publish to an org namespace, why, that it affects everyone regardless of role or membership visibility, and the two paths that do work.publish_internal_test.go— new in-package tests (_internal_test.goper the repo convention that satisfiestestpackage).Tests
Written test-first; both behaviour tests were watched failing against the old message before the fix:
Four tests now cover: the misleading advice is gone; the working auth paths are named; the attempted-vs-granted namespaces are still reported (the diagnostic core); and the GitHub guidance does not leak onto DNS/HTTP-verified namespaces.
go build ./...,go test ./internal/...(with PostgreSQL up) andgofmtare clean.golangci-lintreports no new findings — the only hit in a touched file is the pre-existinggoconstonpublish.go:35, which is also present onmain.Deliberately out of scope
modelcontextprotocolorg to create it plus agithubClientIdchange in the three Pulumi stacks. Still under discussion.fetchOrgMembershipsPagereturnsnil, nilon a 403 with no log line (github_at.go:270), which is why this was undiagnosable server-side. Worth fixing under any outcome; kept out to keep this reviewable, and I'll follow up with a separate PR.Closes nothing on its own — #1468 stays open until org publishing actually works.
🤖 Generated with Claude Code