Skip to content

Add access.modelcontextprotocol.io: GitHub sign-in for Google Workspace - #230

Open
sambhav wants to merge 7 commits into
mainfrom
sambhav/access-site
Open

sambhav wants to merge 7 commits into
mainfrom
sambhav/access-site

Conversation

@sambhav

@sambhav sambhav commented Oct 24, 2026 •

Copy link
Copy Markdown
Member

Summary

Maintainers sign in to MCP Google Workspace with GitHub instead of a Google password.

Google stays the IdP for everything. Workspace's built-in "SSO with a third-party IdP" feature hands off only the password step:

App → Google → access.modelcontextprotocol.io → GitHub

access-site/ is a small Cloudflare Worker (Hono, jose, D1) that acts as the OIDC provider for Google's custom OIDC SSO profile. It signs the user in with GitHub and returns an ID token whose subject is their numeric GitHub user ID and whose email is their Workspace address.

Accounts stay infrastructure as code. The address comes from users.ts (googleEmailPrefix), using the same provisionUser rule Pulumi uses to create the account. The Worker bundles src/config and redeploys on every config change, so removing someone from users.ts stops their sign-in on the next deploy. It holds no Google credentials and creates nothing in Google. GitHub users without an account get a page explaining how to request one.

Provisioning is in GitHub Actions too: on merge to main, access-site/scripts/deploy.sh finds or creates the D1 database, applies migrations and deploys the Worker with secrets from the production environment. Non-secret settings are committed in access-site/wrangler.toml. Until the secrets exist, the deploy job is skipped with a notice, so this can merge before setup.

Security

  • GitHub logins are pinned to GitHub user IDs on first sign-in, so a login freed by a rename and re-registered by someone else is refused
  • Authorization codes are single-use (D1 DELETE ... RETURNING), stored hashed, 2 minute TTL; PKCE enforced when sent and downgrades rejected (RFC 9700); exact redirect URI match; client credentials form-decoded (RFC 6749) and compared in constant time
  • Per-IP rate limit (30/min) on /authorize and /login
  • The GitHub user token is revoked right after the user is read; the GitHub App has no repository or organization permissions
  • The Worker holds no Google credentials; D1 holds only short-lived sign-in state and the login pins
  • CI: actions pinned to commit SHAs, Git credentials not persisted, deploy secrets exposed only to the deploy step; npm audit reports 0
  • Super admins are exempt from the SSO profile and remain break-glass; the GitHub org already requires 2FA
  • Merging a googleEmailPrefix change can map a GitHub account to a Workspace address, so CODEOWNERS (core maintainers) review is the control there

Testing

  • 19 Worker tests: sign-in flow, users.ts mapping, refusal without an account, pilot allowlist and its config, identity pin, rate limit, PKCE (with, without, wrong verifier, downgrade), form-encoded credentials, auth_time, token revocation, code replay, bad client secret, unregistered redirect URI and key rotation; key checks mutation-tested
  • Pass on Node 22 and Node 24 (CI now uses Node 24 LTS), and in a clean checkout without the root node_modules
  • deploy.sh dry-run against a stubbed Wrangler: creates D1 on first run, reuses it after, uploads only the secrets that are set
  • Existing npm run validate and npm test pass; Prettier clean; dry-run wrangler deploy builds with the rate-limit binding (47 KB gzipped)

Rollout

Sign-in starts as a pilot for one person: PILOT_GITHUB_LOGINS = "sambhav" in wrangler.toml means only @sambhav (sambhav@modelcontextprotocol.io) can get a token, even if the Google SSO profile is assigned more widely. A test checks every pilot login maps to an account. Widening the pilot, or removing the line to open sign-in to everyone in users.ts, is a PR.

Setup

Secrets go straight from the command line into GitHub secrets (needs openssl and gh), so none pass between people. This PR can merge first: the deploy job skips until the secrets exist.

@sambhav

  1. Cloudflare token: on the MCP Domain Account, scoped to Workers Scripts edit, D1 edit, and Workers Routes and DNS edit on modelcontextprotocol.io. Store as CLOUDFLARE_ACCESS_SITE_TOKEN in the production environment.
  2. OIDC signing key:
    openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 \
      | gh secret set ACCESS_SITE_OIDC_SIGNING_KEY --env production -R modelcontextprotocol/access
  3. After David's steps 1 and 2: PR the GitHub App client ID into wrangler.toml, merge, and check https://access.modelcontextprotocol.io/.well-known/openid-configuration.
  4. After David's step 3: PR the redirect URI into wrangler.toml, then test sign-in.

@dsp-ant (org owner and Workspace super admin)

  1. GitHub App at https://github.com/organizations/modelcontextprotocol/settings/apps/new:
    • Name: anything unique, e.g. "MCP Workspace Sign-in"
    • Homepage URL https://access.modelcontextprotocol.io
    • Callback URL https://access.modelcontextprotocol.io/login; leave "Expire user authorization tokens" on; device flow off
    • Webhook: untick Active
    • Permissions: Account permissions → Email addresses: Read-only. Nothing else.
    • "Only on this account"; it doesn't need installing
    • Then Generate a new client secret and store it:
      gh secret set ACCESS_SITE_GITHUB_CLIENT_SECRET --env production -R modelcontextprotocol/access
    • Post the Client ID (not secret) on this PR
  2. OIDC client secret (you keep it, for step 3):
    openssl rand -base64 32 | tr '+/' '-_' | tr -d '=\n' > oidc-client-secret.txt
    gh secret set ACCESS_SITE_OIDC_CLIENT_SECRET --env production -R modelcontextprotocol/access < oidc-client-secret.txt
  3. Google SSO profile, once the discovery URL above is live: in the Admin console, Security → Authentication → SSO with third-party IdP → add a custom OIDC profile:
    • Issuer URL https://access.modelcontextprotocol.io
    • Client ID google-workspace; client secret from oidc-client-secret.txt (delete the file after)
    • Change password URL https://github.com/settings/security
    • Post the Redirect URI Google shows on this PR
    • Create a pilot sub-OU under Model Context Protocol containing only sambhav@, and assign the profile to it. Super admins are exempt, so your login is unaffected.

Once the pilot works, assign the profile to Model Context Protocol and remove PILOT_GITHUB_LOGINS.

Runbook, including key rotation: access-site/README.md.

https://claude.ai/code/session_01EwaAHtQd625Lwv121Y6PTn


Generated by Claude Code

A Cloudflare Worker (Hono, jose, D1) in access-site/ that acts as the OIDC
provider for Google Workspace's custom OIDC SSO profile, keyed on the
numeric GitHub user ID, and lets eligible maintainers create or link their
own @modelcontextprotocol.io account. Eligibility comes from the same
provisionUser rule Pulumi uses. A nightly job refreshes the account cache
and suspends accounts of people who left eligible teams (report-only by
default, capped per run, fail-safe on lookup errors).

Also:
- scripts/backfill-github-ids.ts: stamp existing accounts with GitHub IDs
- scripts/sync-team-groups.ts + workflow: mirror GitHub teams as Google groups
- src/google.ts: retainOnDelete on Workspace users (suspend, never delete)
- src/config/utils.ts: githubTeamsFor, shared by the Worker and the sync
- .github/workflows/access-site.yml: test on PR, migrate and deploy on main

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwaAHtQd625Lwv121Y6PTn
@sambhav
sambhav requested a review from a team as a code owner October 24, 2026 16:34
@sambhav
sambhav deployed to production October 24, 2026 16:34 — with GitHub Actions Active
@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Pulumi Preview

Click to expand preview output
Previewing update (prod):
  pulumi:pulumi:Stack: (same)
    [urn=urn:pulumi:prod::mcp-access::pulumi:pulumi:Stack::mcp-access-prod]
    ~ googleworkspace:index/orgUnit:OrgUnit: (update)
        [id=id:03ph8a2z0nc6rsr]
        [urn=urn:pulumi:prod::mcp-access::googleworkspace:index/orgUnit:OrgUnit::mcp-org-unit]
        [provider=urn:pulumi:prod::mcp-access::pulumi:providers:googleworkspace::default_0_11_2::2a78e29b-bbb9-425e-a8de-1a161a6a7d9f]
        description      : "Model Context Protocol"
        name             : "Model Context Protocol"
        parentOrgUnitPath: "/"
    ~ googleworkspace:index/groupSettings:GroupSettings: (update)
        [id=lead-maintainers@modelcontextprotocol.io]
        [urn=urn:pulumi:prod::mcp-access::googleworkspace:index/groupSettings:GroupSettings::lead-maintainers]
        [provider=urn:pulumi:prod::mcp-access::pulumi:providers:googleworkspace::default_0_11_2::2a78e29b-bbb9-425e-a8de-1a161a6a7d9f]
        allowExternalMembers : false
        email                : "lead-maintainers@modelcontextprotocol.io"
        whoCanContactOwner   : "ALL_OWNERS_CAN_CONTACT"
        whoCanJoin           : "INVITED_CAN_JOIN"
        whoCanLeaveGroup     : "NONE_CAN_LEAVE"
        whoCanModerateMembers: "NONE"
        whoCanPostMessage    : "ALL_OWNERS_CAN_POST"
        whoCanViewGroup      : "ALL_OWNERS_CAN_VIEW"
        whoCanViewMembership : "ALL_IN_DOMAIN_CAN_VIEW"
    ~ googleworkspace:index/groupSettings:GroupSettings: (update)
        [id=moderators@modelcontextprotocol.io]
        [urn=urn:pulumi:prod::mcp-access::googleworkspace:index/groupSettings:GroupSettings::moderators]
        [provider=urn:pulumi:prod::mcp-access::pulumi:providers:googleworkspace::default_0_11_2::2a78e29b-bbb9-425e-a8de-1a161a6a7d9f]
        allowExternalMembers : false
        email                : "moderators@modelcontextprotocol.io"
        whoCanContactOwner   : "ALL_OWNERS_CAN_CONTACT"
        whoCanJoin           : "INVITED_CAN_JOIN"
        whoCanLeaveGroup     : "NONE_CAN_LEAVE"
        whoCanModerateMembers: "NONE"
        whoCanPostMessage    : "ALL_OWNERS_CAN_POST"
        whoCanViewGroup      : "ALL_OWNERS_CAN_VIEW"
        whoCanViewMembership : "ALL_IN_DOMAIN_CAN_VIEW"
    ~ googleworkspace:index/groupSettings:GroupSettings: (update)
        [id=antitrust@modelcontextprotocol.io]
        [urn=urn:pulumi:prod::mcp-access::googleworkspace:index/groupSettings:GroupSettings::antitrust]
        [provider=urn:pulumi:prod::mcp-access::pulumi:providers:googleworkspace::default_0_11_2::2a78e29b-bbb9-425e-a8de-1a161a6a7d9f]
        allowExternalMembers : true
        email                : "antitrust@modelcontextprotocol.io"
        whoCanContactOwner   : "ALL_OWNERS_CAN_CONTACT"
        whoCanJoin           : "INVITED_CAN_JOIN"
        whoCanLeaveGroup     : "NONE_CAN_LEAVE"
        whoCanModerateMembers: "NONE"
        whoCanPostMessage    : "ANYONE_CAN_POST"
        whoCanViewGroup      : "ALL_MEMBERS_CAN_VIEW"
        whoCanViewMembership : "ALL_IN_DOMAIN_CAN_VIEW"
    ~ googleworkspace:index/groupSettings:GroupSettings: (update)
        [id=core-maintainers@modelcontextprotocol.io]
        [urn=urn:pulumi:prod::mcp-access::googleworkspace:index/groupSettings:GroupSettings::core-maintainers]
        [provider=urn:pulumi:prod::mcp-access::pulumi:providers:googleworkspace::default_0_11_2::2a78e29b-bbb9-425e-a8de-1a161a6a7d9f]
        allowExternalMembers : false
        email                : "core-maintainers@modelcontextprotocol.io"
        whoCanContactOwner   : "ALL_OWNERS_CAN_CONTACT"
        whoCanJoin           : "INVITED_CAN_JOIN"
        whoCanLeaveGroup     : "NONE_CAN_LEAVE"
        whoCanModerateMembers: "NONE"
        whoCanPostMessage    : "ALL_OWNERS_CAN_POST"
        whoCanViewGroup      : "ALL_OWNERS_CAN_VIEW"
        whoCanViewMembership : "ALL_IN_DOMAIN_CAN_VIEW"
    ~ googleworkspace:index/groupSettings:GroupSettings: (update)
        [id=registry-wg@modelcontextprotocol.io]
        [urn=urn:pulumi:prod::mcp-access::googleworkspace:index/groupSettings:GroupSettings::registry-wg]
        [provider=urn:pulumi:prod::mcp-access::pulumi:providers:googleworkspace::default_0_11_2::2a78e29b-bbb9-425e-a8de-1a161a6a7d9f]
        allowExternalMembers : true
        email                : "registry-wg@modelcontextprotocol.io"
        whoCanContactOwner   : "ALL_OWNERS_CAN_CONTACT"
        whoCanJoin           : "INVITED_CAN_JOIN"
        whoCanLeaveGroup     : "NONE_CAN_LEAVE"
        whoCanModerateMembers: "NONE"
        whoCanPostMessage    : "ALL_OWNERS_CAN_POST"
        whoCanViewGroup      : "ALL_OWNERS_CAN_VIEW"
        whoCanViewMembership : "ALL_IN_DOMAIN_CAN_VIEW"
    ~ googleworkspace:index/groupSettings:GroupSettings: (update)
        [id=catch-all@modelcontextprotocol.io]
        [urn=urn:pulumi:prod::mcp-access::googleworkspace:index/groupSettings:GroupSettings::catch-all]
        [provider=urn:pulumi:prod::mcp-access::pulumi:providers:googleworkspace::default_0_11_2::2a78e29b-bbb9-425e-a8de-1a161a6a7d9f]
        allowExternalMembers : true
        email                : "catch-all@modelcontextprotocol.io"
        whoCanContactOwner   : "ALL_OWNERS_CAN_CONTACT"
        whoCanJoin           : "INVITED_CAN_JOIN"
        whoCanLeaveGroup     : "NONE_CAN_LEAVE"
        whoCanModerateMembers: "NONE"
        whoCanPostMessage    : "ANYONE_CAN_POST"
        whoCanViewGroup      : "ALL_MEMBERS_CAN_VIEW"
        whoCanViewMembership : "ALL_IN_DOMAIN_CAN_VIEW"
    ~ googleworkspace:index/groupSettings:GroupSettings: (update)
        [id=appeals@modelcontextprotocol.io]
        [urn=urn:pulumi:prod::mcp-access::googleworkspace:index/groupSettings:GroupSettings::appeals]
        [provider=urn:pulumi:prod::mcp-access::pulumi:providers:googleworkspace::default_0_11_2::2a78e29b-bbb9-425e-a8de-1a161a6a7d9f]
        allowExternalMembers : false
        email                : "appeals@modelcontextprotocol.io"
        whoCanContactOwner   : "ALL_OWNERS_CAN_CONTACT"
        whoCanJoin           : "INVITED_CAN_JOIN"
        whoCanLeaveGroup     : "NONE_CAN_LEAVE"
        whoCanModerateMembers: "NONE"
        whoCanPostMessage    : "ANYONE_CAN_POST"
        whoCanViewGroup      : "ALL_MEMBERS_CAN_VIEW"
        whoCanViewMembership : "ALL_IN_DOMAIN_CAN_VIEW"
    ~ googleworkspace:index/groupSettings:GroupSettings: (update)
        [id=maintainers@modelcontextprotocol.io]
        [urn=urn:pulumi:prod::mcp-access::googleworkspace:index/groupSettings:GroupSettings::maintainers]
        [provider=urn:pulumi:prod::mcp-access::pulumi:providers:googleworkspace::default_0_11_2::2a78e29b-bbb9-425e-a8de-1a161a6a7d9f]
        allowExternalMembers : true
        email                : "maintainers@modelcontextprotocol.io"
        whoCanContactOwner   : "ALL_OWNERS_CAN_CONTACT"
        whoCanJoin           : "INVITED_CAN_JOIN"
        whoCanLeaveGroup     : "NONE_CAN_LEAVE"
        whoCanModerateMembers: "NONE"
        whoCanPostMessage    : "ALL_OWNERS_CAN_POST"
        whoCanViewGroup      : "ALL_OWNERS_CAN_VIEW"
        whoCanViewMembership : "ALL_IN_DOMAIN_CAN_VIEW"
    --outputs:--
  ~ cloudflareAccessPolicies : {
      ~ security-room-maintainers: {
            accountId      : "c39d6379ccfdb20a3627eeddffa80ec9"
            appCount       : 1
            connectionRules: {
                rdp: {}
            }
            createdAt      : "2026-08-30T07:04:32Z"
            decision       : "allow"
            id             : "11a6814e-bb85-4ea2-aa12-6b081b09aa56"
            includes       : [
                [0]: {
                    githubOrganization: {
                        identityProviderId: "1e53bc11-a504-4572-92d1-4c8d56fc198b"
                        name              : "modelcontextprotocol"
                        team              : "core-maintainers"
                    }
                }
                [1]: {
                    githubOrganization: {
                        identityProviderId: "1e53bc11-a504-4572-92d1-4c8d56fc198b"
                        name              : "modelcontextprotocol"
                        team              : "lead-maintainers"
                    }
                }
                [2]: {
                    githubOrganization: {
                        identityProviderId: "1e53bc11-a504-4572-92d1-4c8d56fc198b"
                        name              : "modelcontextprotocol"
                        team              : "security-managers"
                    }
                }
                [3]: {
                    githubOrganization: {
                        identityProviderId: "1e53bc11-a504-4572-92d1-4c8d56fc198b"
                        name              : "modelcontextprotocol"
                        team              : "security-team"
                    }
                }
            ]
            name           : "Maintainers"
            reusable       : true
            updatedAt      : "2026-09-29T22:44:09Z"
            urn            : "urn:pulumi:prod::mcp-access::cloudflare:index/zeroTrustAccessPolicy:ZeroTrustAccessPolicy::access-policy-security-room-maintainers"
        }
    }
  ~ discordChannels          : {
      ~ thread-only-test: {
            channel  : {
                defaultAutoArchiveDuration: 10080
                defaultForumLayout        : "list"
                defaultSortOrder          : "latest_activity"
                name                      : "thread-only-test"
                parentId                  : "1358869848138059967"
                topic                     : "Test channel for Pulumi-managed thread-only (forum) channels. Every post opens a thread."
                type                      : "forum"
            }
            channelId: "1557027565502341181"
            guildId  : "1358869848138059966"
            id       : "1557027565502341181"
            state    : {
                availableTags                : []
                defaultAutoArchiveDuration   : 10080
                defaultForumLayout           : "list"
                defaultReactionEmoji         : <null>
                defaultSortOrder             : "latest_activity"
                defaultThreadRateLimitPerUser: 0
                flags                        : 0
                name                         : "thread-only-test"
                nsfw                         : false
                parentId                     : "1358869848138059967"
                position                     : 1
                rateLimitPerUser             : 0
                requireTag                   : false
                topic                        : "Test channel for Pulumi-managed thread-only (forum) channels. Every post opens a thread."
                type                         : "forum"
            }
            token    : [secret]
            urn      : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-channel-thread-only-test"
        }
    }
  ~ discordRoles             : {
      ~ administrators (synced)                                 : {
            guildId : "1358869848138059966"
            id      : "1460760673498959945"
            roleId  : "1460760673498959945"
            roleName: "administrators (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-administrators"
        }
      ~ agents working group (synced)                           : {
            guildId : "1358869848138059966"
            id      : "1539652800403996692"
            roleId  : "1539652800403996692"
            roleName: "agents working group (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-agents-wg"
        }
      ~ auth fine-grained authorization working group (synced)  : {
            guildId : "1358869848138059966"
            id      : "1539652788047577128"
            roleId  : "1539652788047577128"
            roleName: "auth fine-grained authorization working group (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-auth-wg-fine-grained-authz"
        }
      ~ auth interest group (synced)                            : {
            guildId : "1358869848138059966"
            id      : "1460760779107209370"
            roleId  : "1460760779107209370"
            roleName: "auth interest group (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-auth-ig"
        }
      ~ auth tool scopes working group (synced)                 : {
            guildId : "1358869848138059966"
            id      : "1539652784092090369"
            roleId  : "1539652784092090369"
            roleName: "auth tool scopes working group (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-auth-wg-tool-scopes"
        }
      ~ c# sdk maintainers (synced)                             : {
            guildId : "1358869848138059966"
            id      : "1460760658848252036"
            roleId  : "1460760658848252036"
            roleName: "c# sdk maintainers (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-csharp-sdk"
        }
      ~ client implementor interest group (synced)              : {
            guildId : "1358869848138059966"
            id      : "1460760806584094783"
            roleId  : "1460760806584094783"
            roleName: "client implementor interest group (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-client-implementor-ig"
        }
      ~ community managers (synced)                             : {
            guildId : "1358869848138059966"
            id      : "1461488567162503189"
            roleId  : "1461488567162503189"
            roleName: "community managers (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-community-managers"
        }
      ~ community moderators (synced)                           : {
            guildId : "1358869848138059966"
            id      : "1460760656226680903"
            roleId  : "1460760656226680903"
            roleName: "community moderators (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-moderators"
        }
      ~ core maintainers (synced)                               : {
            guildId : "1358869848138059966"
            id      : "1460760682076307516"
            roleId  : "1460760682076307516"
            roleName: "core maintainers (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-core-maintainers"
        }
      ~ core primitive working group (synced)                   : {
            guildId : "1358869848138059966"
            id      : "1540128486218539018"
            roleId  : "1540128486218539018"
            roleName: "core primitive working group (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-core-primitive-wg"
        }
      ~ enterprise-managed authorization interest group (synced): {
            guildId : "1358869848138059966"
            id      : "1539652812743381082"
            roleId  : "1539652812743381082"
            roleName: "enterprise-managed authorization interest group (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-enterprise-managed-authorization-ig"
        }
      ~ file uploads working group (synced)                     : {
            guildId : "1358869848138059966"
            id      : "1497125934359249018"
            roleId  : "1497125934359249018"
            roleName: "file uploads working group (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-file-uploads-wg"
        }
      ~ filesystems working group (synced)                      : {
            guildId : "1358869848138059966"
            id      : "1547367326343630958"
            roleId  : "1547367326343630958"
            roleName: "filesystems working group (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-filesystems-wg"
        }
      ~ financial services interest group (synced)              : {
            guildId : "1358869848138059966"
            id      : "1460760782605123726"
            roleId  : "1460760782605123726"
            roleName: "financial services interest group (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-financial-services-ig"
        }
      ~ gateways interest group (synced)                        : {
            guildId : "1358869848138059966"
            id      : "1461148028646854859"
            roleId  : "1461148028646854859"
            roleName: "gateways interest group (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-gateways-ig"
        }
      ~ go sdk maintainers (synced)                             : {
            guildId : "1358869848138059966"
            id      : "1460760804134752443"
            roleId  : "1460760804134752443"
            roleName: "go sdk maintainers (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-go-sdk"
        }
      ~ governance working group (synced)                       : {
            guildId : "1358869848138059966"
            id      : "1561817661711720488"
            roleId  : "1561817661711720488"
            roleName: "governance working group (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-governance-wg"
        }
      ~ infrastructure working group (synced)                   : {
            guildId : "1358869848138059966"
            id      : "1551964002974830692"
            roleId  : "1551964002974830692"
            roleName: "infrastructure working group (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-infrastructure-wg"
        }
      ~ inspector maintainers (synced)                          : {
            guildId : "1358869848138059966"
            id      : "1460760676367597740"
            roleId  : "1460760676367597740"
            roleName: "inspector maintainers (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-inspector-maintainers"
        }
      ~ interceptors working group (synced)                     : {
            guildId : "1358869848138059966"
            id      : "1496277766046355539"
            roleId  : "1496277766046355539"
            roleName: "interceptors working group (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-interceptors-wg"
        }
      ~ java sdk maintainers (synced)                           : {
            guildId : "1358869848138059966"
            id      : "1460760695821041767"
            roleId  : "1460760695821041767"
            roleName: "java sdk maintainers (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-java-sdk"
        }
      ~ kotlin sdk maintainers (synced)                         : {
            guildId : "1358869848138059966"
            id      : "1460760698316652557"
            roleId  : "1460760698316652557"
            roleName: "kotlin sdk maintainers (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-kotlin-sdk"
        }
      ~ lead maintainers (synced)                               : {
            guildId : "1358869848138059966"
            id      : "1460760679064535238"
            roleId  : "1460760679064535238"
            roleName: "lead maintainers (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-lead-maintainers"
        }
      ~ maintainers (synced)                                    : {
            guildId : "1358869848138059966"
            id      : "1460760670395039755"
            roleId  : "1460760670395039755"
            roleName: "maintainers (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-maintainers"
        }
      ~ mcp apps working group (synced)                         : {
            guildId : "1358869848138059966"
            id      : "1461149104573907161"
            roleId  : "1461149104573907161"
            roleName: "mcp apps working group (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-mcp-apps-wg"
        }
      ~ php sdk maintainers (synced)                            : {
            guildId : "1358869848138059966"
            id      : "1460760711796887821"
            roleId  : "1460760711796887821"
            roleName: "php sdk maintainers (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-php-sdk"
        }
      ~ primitive grouping interest group (synced)              : {
            guildId : "1358869848138059966"
            id      : "1471155142358339685"
            roleId  : "1471155142358339685"
            roleName: "primitive grouping interest group (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-primitive-grouping-ig"
        }
      ~ python sdk maintainers (synced)                         : {
            guildId : "1358869848138059966"
            id      : "1460760717434163281"
            roleId  : "1460760717434163281"
            roleName: "python sdk maintainers (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-python-sdk"
        }
      ~ reference servers maintainers (synced)                  : {
            guildId : "1358869848138059966"
            id      : "1460760661985464506"
            roleId  : "1460760661985464506"
            roleName: "reference servers maintainers (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-reference-servers-maintainers"
        }
      ~ registry maintainers (synced)                           : {
            guildId : "1358869848138059966"
            id      : "1460760690611720404"
            roleId  : "1460760690611720404"
            roleName: "registry maintainers (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-registry-maintainers"
        }
      ~ ruby sdk maintainers (synced)                           : {
            guildId : "1358869848138059966"
            id      : "1460760769758101595"
            roleId  : "1460760769758101595"
            roleName: "ruby sdk maintainers (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-ruby-sdk"
        }
      ~ rust sdk maintainers (synced)                           : {
            guildId : "1358869848138059966"
            id      : "1460760775705493770"
            roleId  : "1460760775705493770"
            roleName: "rust sdk maintainers (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-rust-sdk"
        }
      ~ sdk maintainers (synced)                                : {
            guildId : "1358869848138059966"
            id      : "1460760687444758623"
            roleId  : "1460760687444758623"
            roleName: "sdk maintainers (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-sdk-maintainers"
        }
      ~ server card working group (synced)                      : {
            guildId : "1358869848138059966"
            id      : "1493598979357671554"
            roleId  : "1493598979357671554"
            roleName: "server card working group (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-server-card-wg"
        }
      ~ skills over mcp working group (synced)                  : {
            guildId : "1358869848138059966"
            id      : "1539652804610621562"
            roleId  : "1539652804610621562"
            roleName: "skills over mcp working group (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-skills-over-mcp-wg"
        }
      ~ swift sdk maintainers (synced)                          : {
            guildId : "1358869848138059966"
            id      : "1460760701344813136"
            roleId  : "1460760701344813136"
            roleName: "swift sdk maintainers (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-swift-sdk"
        }
      ~ tool annotations interest group (synced)                : {
            guildId : "1358869848138059966"
            id      : "1483420327001329685"
            roleId  : "1483420327001329685"
            roleName: "tool annotations interest group (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-tool-annotations-ig"
        }
      ~ transports working group (synced)                       : {
            guildId : "1358869848138059966"
            id      : "1460760773134651505"
            roleId  : "1460760773134651505"
            roleName: "transports working group (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-transport-wg"
        }
      ~ triggers & events working group (synced)                : {
            guildId : "1358869848138059966"
            id      : "1486488186405392518"
            roleId  : "1486488186405392518"
            roleName: "triggers & events working group (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-triggers-events-wg"
        }
      ~ typescript sdk maintainers (synced)                     : {
            guildId : "1358869848138059966"
            id      : "1460760714607071465"
            roleId  : "1460760714607071465"
            roleName: "typescript sdk maintainers (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-typescript-sdk"
        }
      ~ use-mcp maintainers (synced)                            : {
            guildId : "1358869848138059966"
            id      : "1460760684643221556"
            roleId  : "1460760684643221556"
            roleName: "use-mcp maintainers (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-use-mcp-maintainers"
        }
      ~ wg/ig facilitators (synced)                             : {
            guildId : "1358869848138059966"
            id      : "1466097565048246467"
            roleId  : "1466097565048246467"
            roleName: "wg/ig facilitators (synced)"
            token   : [secret]
            urn     : "urn:pulumi:prod::mcp-access::pulumi-nodejs:dynamic:Resource::discord-role-wg-ig-facilitators"
        }
    }
  ~ githubRepositories       : {
      ~ experimental-ext-file-uploads: {
            allowAutoMerge                     : false
            allowForking                       : true
            allowMergeCommit                   : false
            allowRebaseMerge                   : false
            allowSquashMerge                   : true
            allowUpdateBranch                  : true
            archiveOnDestroy                   : true
            archived                           : false
            autoInit                           : true
            defaultBranch                      : "main"
            deleteBranchOnMerge                : true
            description                        : "Experimental MCP extension for uploading files to tools over MCP. Maintained by the File Uploads Working Group."
            etag                               : "W/\"558e4fe3b4e02108c8a0ec9817496f1144fc6e310900229ac81529163e80a469\""
            fork                               : "false"
            fullName                           : "modelcontextprotocol/experimental-ext-file-uploads"
            gitCloneUrl                        : "git://github.com/modelcontextprotocol/experimental-ext-file-uploads.git"
            hasDiscussions                     : false
            hasDownloads                       : false
            hasIssues                          : true
            hasProjects                        : false
            hasWiki                            : false
            htmlUrl                            : "https://github.com/modelcontextprotocol/experimental-ext-file-uploads"
            httpCloneUrl                       : "https://github.com/modelcontextprotocol/experimental-ext-file-uploads.git"
            id                                 : "experimental-ext-file-uploads"
            ignoreVulnerabilityAlertsDuringRead: false
            isTemplate                         : false
            licenseTemplate                    : "apache-2.0"
            mergeCommitMessage                 : "PR_TITLE"
            mergeCommitTitle                   : "MERGE_MESSAGE"
            name                               : "experimental-ext-file-uploads"
            nodeId                             : "R_kgDOVIvM3A"
            private                            : false
            repoId                             : 1418448092
            securityAndAnalysis                : {
                advancedSecurity                 : <null>
                codeSecurity                     : <null>
                secretScanning                   : {
                    status: "enabled"
                }
                secretScanningAiDetection        : <null>
                secretScanningNonProviderPatterns: <null>
                secretScanningPushProtection     : {
                    status: "enabled"
                }
            }
            squashMergeCommitMessage           : "PR_BODY"
            squashMergeCommitTitle             : "PR_TITLE"
            sshCloneUrl                        : "git@github.com:modelcontextprotocol/experimental-ext-file-uploads.git"
            svnUrl                             : "https://github.com/modelcontextprotocol/experimental-ext-file-uploads"
            urn                                : "urn:pulumi:prod::mcp-access::github:index/repository:Repository::repository-experimental-ext-file-uploads"
            visibility                         : "public"
            vulnerabilityAlerts                : true
            webCommitSignoffRequired           : false
        }
      ~ experimental-ext-filesystems : {
            allowAutoMerge                     : false
            allowForking                       : true
            allowMergeCommit                   : false
            allowRebaseMerge                   : false
            allowSquashMerge                   : true
            allowUpdateBranch                  : true
            archiveOnDestroy                   : true
            archived                           : false
            autoInit                           : true
            defaultBranch                      : "main"
            deleteBranchOnMerge                : true
            description                        : "Experimental MCP extension for filesystem operations on Resources. Maintained by the Filesystems Working Group."
            etag                               : "W/\"eb23d7e32f02ab7f316f22bb86aa1a7f7600902eb9c69fc0a4348961eb928aee\""
            fork                               : "false"
            fullName                           : "modelcontextprotocol/experimental-ext-filesystems"
            gitCloneUrl                        : "git://github.com/modelcontextprotocol/experimental-ext-filesystems.git"
            hasDiscussions                     : false
            hasDownloads                       : false
            hasIssues                          : true
            hasProjects                        : false
            hasWiki                            : false
            htmlUrl                            : "https://github.com/modelcontextprotocol/experimental-ext-filesystems"
            httpCloneUrl                       : "https://github.com/modelcontextprotocol/experimental-ext-filesystems.git"
            id                                 : "experimental-ext-filesystems"
            ignoreVulnerabilityAlertsDuringRead: false
            isTemplate                         : false
            licenseTemplate                    : "apache-2.0"
            mergeCommitMessage                 : "PR_TITLE"
            mergeCommitTitle                   : "MERGE_MESSAGE"
            name                               : "experimental-ext-filesystems"
            nodeId                             : "R_kgDOU_4rAQ"
            private                            : false
            repoId                             : 1409166081
            securityAndAnalysis                : {
                advancedSecurity                 : <null>
                codeSecurity                     : <null>
                secretScanning                   : {
                    status: "enabled"
                }
                secretScanningAiDetection        : <null>
                secretScanningNonProviderPatterns: <null>
                secretScanningPushProtection     : {
                    status: "enabled"
                }
            }
            squashMergeCommitMessage           : "PR_BODY"
            squashMergeCommitTitle             : "PR_TITLE"
            sshCloneUrl                        : "git@github.com:modelcontextprotocol/experimental-ext-filesystems.git"
            svnUrl                             : "https://github.com/modelcontextprotocol/experimental-ext-filesystems"
            urn                                : "urn:pulumi:prod::mcp-access::github:index/repository:Repository::repository-experimental-ext-filesystems"
            visibility                         : "public"
            vulnerabilityAlerts                : true
            webCommitSignoffRequired           : false
        }
      ~ governance-wg                : {
            allowAutoMerge                     : false
            allowForking                       : true
            allowMergeCommit                   : false
            allowRebaseMerge                   : false
            allowSquashMerge                   : true
            allowUpdateBranch                  : true
            archiveOnDestroy                   : true
            archived                           : false
            autoInit                           : true
            defaultBranch                      : "main"
            deleteBranchOnMerge                : true
            description                        : "Governance documentation and working materials for the MCP Governance Working Group."
            etag                               : "W/\"7726087ece6255cc3ad62ed9c2abb12c9be9ea1a0cc1f43514107240f5f96fee\""
            fork                               : "false"
            fullName                           : "modelcontextprotocol/governance-wg"
            gitCloneUrl                        : "git://github.com/modelcontextprotocol/governance-wg.git"
            hasDiscussions                     : false
            hasDownloads                       : false
            hasIssues                          : true
            hasProjects                        : false
            hasWiki                            : false
            htmlUrl                            : "https://github.com/modelcontextprotocol/governance-wg"
            httpCloneUrl                       : "https://github.com/modelcontextprotocol/governance-wg.git"
            id                                 : "governance-wg"
            ignoreVulnerabilityAlertsDuringRead: false
            isTemplate                         : false
            licenseTemplate                    : "apache-2.0"
            mergeCommitMessage                 : "PR_TITLE"
            mergeCommitTitle                   : "MERGE_MESSAGE"
            name                               : "governance-wg"
            nodeId                             : "R_kgDOVQAxJQ"
            private                            : false
            repoId                             : 1426075941
            securityAndAnalysis                : {
                advancedSecurity                 : <null>
                codeSecurity                     : <null>
                secretScanning                   : {
                    status: "enabled"
                }
                secretScanningAiDetection        : <null>
                secretScanningNonProviderPatterns: <null>
                secretScanningPushProtection     : {
                    status: "enabled"
                }
            }
            squashMergeCommitMessage           : "PR_BODY"
            squashMergeCommitTitle             : "PR_TITLE"
            sshCloneUrl                        : "git@github.com:modelcontextprotocol/governance-wg.git"
            svnUrl                             : "https://github.com/modelcontextprotocol/governance-wg"
            urn                                : "urn:pulumi:prod::mcp-access::github:index/repository:Repository::repository-governance-wg"
            visibility                         : "public"
            vulnerabilityAlerts                : true
            webCommitSignoffRequired           : false
        }
    }
  ~ githubTeams              : {
      ~ agents-wg                    : {
            createDefaultMaintainer: false
            description            : "Agents Working Group \n(Managed by github.com/modelcontextprotocol/access)"
            etag                   : "W/\"0b2a9a0f98b49ee1f69c73ed3642df259859644f13ecbfdf7bae4ac046e8ecec\""
            id                     : "17337325"
            membersCount           : 5
            name                   : "agents-wg"
            nodeId                 : "T_kwDOCt2Azc4BCIvt"
            notificationSetting    : "notifications_enabled"
            parentTeamId           : "14435868"
            parentTeamReadId       : "14435868"
            parentTeamReadSlug     : "working-groups"
            privacy                : "closed"
            slug                   : "agents-wg"
            urn                    : "urn:pulumi:prod::mcp-access::github:index/team:Team::agents-wg"
        }
      ~ auth-maintainers             : {
            createDefaultMaintainer: false
            description            : "Auth Maintainers \n(Managed by github.com/modelcontextprotocol/access)"
            etag                   : "W/\"3a39afe88f8b72daaf46dff72f5ab999b72008f368c960d20316b9cc88e62d0a\""
            id                     : "16083315"
            membersCount           : 7
            name                   : "auth-maintainers"
            nodeId                 : "T_kwDOCt2Azc4A9Wlz"
            notificationSetting    : "notifications_enabled"
            parentTeamId           : "14435868"
            parentTeamReadId       : "14435868"
            parentTeamReadSlug     : "working-groups"
            privacy                : "closed"
            slug                   : "auth-maintainers"
            urn                    : "urn:pulumi:prod::mcp-access::github:index/team:Team::auth-maintainers"
        }
      ~ auth-wg-fine-grained-authz   : {
            createDefaultMaintainer: false
            description            : "Auth Fine-Grained Authorization Working Group \n(Managed by github.com/modelcontextprotocol/access)"
            etag                   : "W/\"216e468fa771c274791e3de46f68087796501250ad964c7978e05209bef1f8d0\""
            id                     : "19051555"
            membersCount           : 0
            name                   : "auth-wg-fine-grained-authz"
            nodeId                 : "T_kwDOCt2Azc4BIrQj"
            notificationSetting    : "notifications_enabled"
            parentTeamId           : "14435868"
            parentTeamReadId       : "14435868"
            parentTeamReadSlug     : "working-groups"
            privacy                : "closed"
            slug                   : "auth-wg-fine-grained-authz"
            urn                    : "urn:pulumi:prod::mcp-access::github:index/team:Team::auth-wg-fine-grained-authz"
        }
      ~ auth-wg-tool-scopes          : {
            createDefaultMaintainer: false
            description            : "Auth Tool Scopes Working Group \n(Managed by github.com/modelcontextprotocol/access)"
            etag                   : "W/\"fd6de23b92af3437906e350c34a9756391340722f5ac823eaa168a77caa94df7\""
            id                     : "19051557"
            membersCount           : 0
            name                   : "auth-wg-tool-scopes"
            nodeId                 : "T_kwDOCt2Azc4BIrQl"
            notificationSetting    : "notifications_enabled"
            parentTeamId           : "14435868"
            parentTeamReadId       : "14435868"
            parentTeamReadSlug     : "working-groups"
            privacy                : "closed"
            slug                   : "auth-wg-tool-scopes"
            urn                    : "urn:pulumi:prod::mcp-access::github:index/team:Team::auth-wg-tool-scopes"
        }
      ~ conformance-auth             : {
            createDefaultMaintainer: false
            description            : "Conformance authorization-server code owners \n(Managed by github.com/modelcontextprotocol/access)"
            etag                   : "W/\"f277eba3c9957ba2aa1950e8dff44a0b781bfd3f52c4ec19432dfddc075a6f9a\""
            id                     : "19337897"
            membersCount           : 2
            name                   : "conformance-auth"
            nodeId                 : "T_kwDOCt2Azc4BJxKp"
            notificationSetting    : "notifications_enabled"
            privacy                : "closed"
            slug                   : "conformance-auth"
            urn                    : "urn:pulumi:prod::mcp-access::github:index/team:Team::conformance-auth"
        }
      ~ core-maintainers             : {
            createDefaultMaintainer: false
            description            : "Core maintainers \n(Managed by github.com/modelcontextprotocol/access)"
            etag                   : "W/\"119bf241f5e9296e9d33682ec97e598ed00382ed16348391fd3a080f94cda682\""
            id                     : "14435865"
            membersCount           : 12
            name                   : "core-maintainers"
            nodeId                 : "T_kwDOCt2Azc4A3EYZ"
            notificationSetting    : "notifications_enabled"
            parentTeamId           : "14435861"
            parentTeamReadId       : "14435861"
            parentTeamReadSlug     : "steering-committee"
            privacy                : "closed"
            slug                   : "core-maintainers"
            urn                    : "urn:pulumi:prod::mcp-access::github:index/team:Team::core-maintainers"
        }
      ~ core-primitive-wg            : {
            createDefaultMaintainer: false
            description            : "Core Primitive Working Group \n(Managed by github.com/modelcontextprotocol/access)"
            etag                   : "W/\"930320ee29ccf0a2b923fd9ed28abc75f4159c728f9510a47330c34063610f72\""
            id                     : "19087061"
            membersCount           : 2
            name                   : "core-primitive-wg"
            nodeId                 : "T_kwDOCt2Azc4BIz7V"
            notificationSetting    : "notifications_enabled"
            parentTeamId           : "14435868"
            parentTeamReadId       : "14435868"
            parentTeamReadSlug     : "working-groups"
            privacy                : "closed"
            slug                   : "core-primitive-wg"
            urn                    : "urn:pulumi:prod::mcp-access::github:index/team:Team::core-primitive-wg"
        }
      ~ csharp-sdk                   : {
            createDefaultMaintainer: false
            description            : "Official C# SDK maintainers \n(Managed by github.com/modelcontextprotocol/access)"
            etag                   : "W/\"cf34c140babbcb5e76a1260f3ea518542fb3d89228c03dc78c4d72551576d4e5\""
            id                     : "14435898"
            membersCount           : 10
            name                   : "csharp-sdk"
            nodeId                 : "T_kwDOCt2Azc4A3EY6"
            notificationSetting    : "notifications_enabled"
            parentTeamId           : "14435871"
            parentTeamReadId       : "14435871"
            parentTeamReadSlug     : "sdk-maintainers"
            privacy                : "closed"
            slug                   : "csharp-sdk"
            urn                    : "urn:pulumi:prod::mcp-access::github:index/team:Team::csharp-sdk"
        }
      ~ csharp-sdk-admin             : {
            createDefaultMaintainer: false
            description            : "C# SDK repository admins \n(Managed by github.com/modelcontextprotocol/access)"
            etag                   : "W/\"db712d10abe1cd76911fbb88c95f3705c508a51b798a95bdc1a037cc124b59dc\""
            id                     : "16644883"
            membersCount           : 8
            name                   : "csharp-sdk-admin"
            nodeId                 : "T_kwDOCt2Azc4A_fsT"
            notificationSetting    : "notifications_enabled"
            parentTeamId           : "14435898"
            parentTeamReadId       : "14435898"
            parentTeamReadSlug     : "csharp-sdk"
            privacy                : "closed"
            slug                   : "csharp-sdk-admin"
            urn                    : "urn:pulumi:prod::mcp-access::github:index/team:Team::csharp-sdk-admin"
        }
      ~ docs-maintainers             : {
            createDefaultMaintainer: false
            description            : "MCP docs maintainers \n(Managed by github.com/modelcontextprotocol/access)"
            etag                   : "W/\"2b7aeb7bc9ea3902491c16909c1f9ca4125d9e0a789d97b244ea5b5c0ebb5c10\""
            id                     : "15811679"
            membersCount           : 13
            name                   : "docs-maintainers"
            nodeId                 : "T_kwDOCt2Azc4A8URf"
            notificationSetting    : "notifications_enabled"
            parentTeamId           : "14435861"
            parentTeamReadId       : "14435861"
            parentTeamReadSlug     : "steering-committee"
            privacy                : "closed"
            slug                   : "docs-maintainers"
            urn                    : "urn:pulumi:prod::mcp-access::github:index/team:Team::docs-maintainers"
        }
      ~ file-uploads-wg              : {
            createDefaultMaintainer: false
            description            : "File Uploads Working Group \n(Managed by github.com/modelcontextprotocol/access)"
            etag                   : "W/\"6d00b32e860f803e289f663a74cce606ce268579ab7fc60b79988aacf0816084\""
            id                     : "17260661"
            membersCount           : 6
            name                   : "file-uploads-wg"
            nodeId                 : "T_kwDOCt2Azc4BB2B1"
            notificationSetting    : "notifications_enabled"
            parentTeamId           : "14435868"
            parentTeamReadId       : "14435868"
            parentTeamReadSlug     : "working-groups"
            privacy                : "closed"
            slug                   : "file-uploads-wg"
            urn                    : "urn:pulumi:prod::mcp-access::github:index/team:Team::file-uploads-wg"
        }
      ~ filesystems-wg               : {
            createDefaultMaintainer: false
            description            : "Filesystems Working Group \n(Managed by github.com/modelcontextprotocol/access)"
            etag                   : "W/\"3b19bd3f445fb531b0a4eb2c6fb18cee1c29663ac2047ad2a615e572beca04ba\""
            id                     : "19423521"
            membersCount           : 7
            name                   : "filesystems-wg"
            nodeId                 : "T_kwDOCt2Azc4BKGEh"
            notificationSetting    : "notifications_enabled"
            parentTeamId           : "14435868"
            parentTeamReadId       : "14435868"
            parentTeamReadSlug     : "working-groups"
            privacy                : "closed"
            slug                   : "filesystems-wg"
            urn                    : "urn:pulumi:prod::mcp-access::github:index/team:Team::filesystems-wg"
        }
      ~ filesystems-wg-leads         : {
            createDefaultMaintainer: false
            description            : "Filesystems Working Group leads \n(Managed by github.com/modelcontextprotocol/access)"
            etag                   : "W/\"461d67bbcc7b9372a19bbf2a7daca258e7d2e71633287b73508da3ce10b77ca8\""
            id                     : "20140141"
            membersCount           : 3
            name                   : "filesystems-wg-leads"
            nodeId                 : "T_kwDOCt2Azc4BM1Bt"
            notificationSetting    : "notifications_enabled"
            parentTeamId           : "19423521"
            parentTeamReadId       : "19423521"
            parentTeamReadSlug     : "filesystems-wg"
            privacy                : "closed"
            slug                   : "filesystems-wg-leads"
            urn                    : "urn:pulumi:prod::mcp-access::github:index/team:Team::filesystems-wg-leads"
        }
      ~ go-sdk                       : {
            createDefaultMaintainer: false
            description            : "The Go SDK Team \n(Managed by github.com/modelcontextprotocol/access)"
            etag                   : "W/\"b3174b3d096eb7821f201d04850c2d6f6b2e2850a7aa2681ccb55d573486e4c7\""
            id                     : "14435896"
            membersCount           : 10
            name                   : "go-sdk"
            nodeId                 : "T_kwDOCt2Azc4A3EY4"
            notificationSetting    : "notifications_enabled"
            parentTeamId           : "14435871"
            parentTeamReadId       : "14435871"
            parentTeamReadSlug     : "sdk-maintainers"
            privacy                : "closed"
            slug                   : "go-sdk"
            urn                    : "urn:pulumi:prod::mcp-access::github:index/team:Team::go-sdk"
        }
      ~ governance-wg                : {
            createDefaultMaintainer: false
            description            : "Governance Working Group \n(Managed by github.com/modelcontextprotocol/access)"
            etag                   : "W/\"e740bb9ed35eab1219f051da9d9115a223a97534a1fbd691d595a174ab1c3a82\""
            id                     : "20197338"
            membersCount           : 4
            name                   : "governance-wg"
            nodeId                 : "T_kwDOCt2Azc4BNC_a"
            notificationSetting    : "notifications_enabled"
            parentTeamId           : "14435868"
            parentTeamReadId       : "14435868"
            parentTeamReadSlug     : "working-groups"
            privacy                : "closed"
            slug                   : "governance-wg"
            urn                    : "urn:pulumi:prod::mcp-access::github:index/team:Team::governance-wg"
        }
      ~ ig-financial-services        : {
            createDefaultMaintainer: false
            description            : "Financial Services Interest Group \n(Managed by github.com/modelcontextprotocol/access)"
            etag                   : "W/\"b1286be7d16f0258e00d780999a084773d5d5cb097d0aa605d410c347b35534c\""
            id                     : "14435884"
            membersCount           : 6
            name                   : "ig-financial-services"
            nodeId                 : "T_kwDOCt2Azc4A3EYs"
            notificationSetting    : "notifications_enabled"
            parentTeamId           : "14435869"
            parentTeamReadId       : "14435869"
            parentTeamReadSlug     : "interest-groups"
            privacy                : "closed"
            slug                   : "ig-financial-services"
            urn                    : "urn:pulumi:prod::mcp-access::github:index/team:Team::ig-financial-services"
        }
      ~ infrastructure-wg            : {
            createDefaultMaintainer: false
            description            : "Infrastructure Working Group \n(Managed by github.com/modelcontextprotocol/access)"
            etag                   : "W/\"044ee613f851f9bfb29aaed026d46069b2e78e18956d45b076aa9966fcf598d7\""
            id                     : "19662960"
            membersCount           : 4
            name                   : "infrastructure-wg"
            nodeId                 : "T_kwDOCt2Azc4BLAhw"
            notificationSetting    : "notifications_enabled"
            parentTeamId           : "14435868"
            parentTeamReadId       : "14435868"
            parentTeamReadSlug     : "working-groups"
            privacy                : "closed"
            slug                   : "infrastructure-wg"
            urn                    : "urn:pulumi:prod::mcp-access::github:index/team:Team::infrastructure-wg"
        }
      ~ inspector-maintainers        : {
            createDefaultMaintainer: false
            description            : "MCP Inspector maintainers \n(Managed by github.com/modelcontextprotocol/access)"
            etag                   : "W/\"4f84aaafc3131d403836244354154bd51eea544ab5ac7e5fda7ec11e0cabbec4\""
            id                     : "14642559"
            membersCount           : 4
            name                   : "inspector-maintainers"
            nodeId                 : "T_kwDOCt2Azc4A321_"
            notificationSetting    : "notifications_enabled"
            parentTeamId           : "14435861"
            parentTeamReadId       : "14435861"
            parentTeamReadSlug     : "steering-committee"
            privacy                : "closed"
            slug                   : "inspector-maintainers"
            urn                    : "urn:pulumi:prod::mcp-access::github:index/team:Team::inspector-maintainers"
        }
      ~ interceptors-wg              : {
            createDefaultMaintainer: false
            description            : "Interceptors Working Group \n(Managed by github.com/modelcontextprotocol/access)"
            etag                   : "W/\"d559f8254e0a35c87f665ae8d7bfdb7d3894632c152ed416f0cf3d97bc61b003\""
            id                     : "17218506"
            membersCount           : 10
            name                   : "interceptors-wg"
            nodeId                 : "T_kwDOCt2Azc4BBrvK"
            notificationSetting    : "notifications_enabled"
            parentTeamId           : "14435868"
            parentTeamReadId       : "14435868"
            parentTeamReadSlug     : "working-groups"
            privacy                : "closed"
            slug                   : "interceptors-wg"
            urn                    : "urn:pulumi:prod::mcp-access::github:index/team:Team::interceptors-wg"
        }
      ~ interest-groups              : {
            createDefaultMaintainer: false
            description            : "Interest Groups \n(Managed by github.com/modelcontextprotocol/access)"
            etag                   : "W/\"77b8ad22072e00dd051341ae54e2799875d378813971109173fdd6309b9882ef\""
            id                     : "14435869"
            membersCount           : 19
            name                   : "interest-groups"
            nodeId                 : "T_kwDOCt2Azc4A3EYd"
            notificationSetting    : "notifications_enabled"
            parentTeamId           : "14435861"
            parentTeamReadId       : "14435861"
            parentTeamReadSlug     : "steering-committee"
            privacy                : "closed"
            slug                   : "interest-groups"
            urn                    : "urn:pulumi:prod::mcp-access::github:index/team:Team::interest-groups"
        }
      ~ java-sdk                     : {
            createDefaultMaintainer: false
            description            : "Official Java SDK maintainers \n(Managed by github.com/modelcontextprotocol/access)"
            etag                   : "W/\"55a48b54ef99fb0dc2fa14581e70e97d7e26aa6281975eca6bab8285058ef122\""
            id                     : "14435899"
            membersCount           : 4
            name                   : "java-sdk"
            nodeId                 : "T_kwDOCt2Azc4A3EY7"
            notificationSetting    : "notifications_enabled"
            parentTeamId           : "14435871"
            parentTeamReadId       : "14435871"
            parentTeamReadSlug     : "sdk-maintainers"
            privacy                : "closed"
            slug                   : "java-sdk"
            urn                    : "urn:pulumi:prod::mcp-access::github:index/team:Team::java-sdk"
        }
      ~ kotlin-sdk                   : {
            createDefaultMaintainer: false
            description            : "Official Kotlin SDK maintainers \n(Managed by github.com/modelcontextprotocol/access)"
            etag                   : "W/\"58805861b0c90cba1fe916b919b0652e4a0d403fea89d7fb35d00805e6ddccf3\""
            id                     : "14435895"
            membersCount           : 6
            name                   : "kotlin-sdk"
            nodeId                 : "T_kwDOCt2Azc4A3EY3"
            notificationSetting    : "notifications_enabled"
            parentTeamId           : "14435871"
            parentTeamReadId       : "14435871"
            parentTeamReadSlug     : "sdk-maintainers"
            privacy                : "closed"
            slug                   : "kotlin-sdk"
            urn                    : "urn:pulumi:prod::mcp-access::github:i

... (truncated)

Account creations, links, suspensions and the nightly report are now
JSON log lines (Workers Logs enabled in wrangler.toml) instead of a D1
audit_log table and an optional Discord webhook.

Also declare @types/node in access-site so the tests typecheck in a clean
CI checkout without the root node_modules.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwaAHtQd625Lwv121Y6PTn
@sambhav
sambhav deployed to production October 24, 2026 16:45 — with GitHub Actions Active
…of truth

Per Infra WG discussion, account creation and mappings stay infrastructure
as code. The Worker now only signs people in: it maps the GitHub login to
the googleEmailPrefix in users.ts (same provisionUser rule as Pulumi) and
issues the OIDC ID token Google needs. It holds no Google credentials.

Removed: self-service account page, Directory API client, D1 account
cache, nightly offboarding, backfill script, team-group sync, and the
src/google.ts / src/config/utils.ts changes. D1 keeps only short-lived
pending requests and single-use codes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwaAHtQd625Lwv121Y6PTn
@sambhav
sambhav deployed to production October 24, 2026 16:55 — with GitHub Actions Active
On merge to main, scripts/deploy.sh finds or creates the D1 database,
applies migrations and deploys the Worker, uploading secrets from the
production environment. Non-secret settings (account, GitHub App client
ID, Google redirect URI) are committed in wrangler.toml. The deploy job
skips with a notice until the secrets exist, so merging early is safe.

Replaces the manual generate-keys script; signing key rotation now goes
through the ACCESS_SITE_OIDC_PREVIOUS_JWK secret, and the Worker ignores a
retired "{}" value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwaAHtQd625Lwv121Y6PTn
@sambhav
sambhav deployed to production October 24, 2026 17:09 — with GitHub Actions Active
Node: CI on Node 24 (current LTS), engines >=24, @types/node 24.

Security review fixes:
- Pin GitHub logins to GitHub user IDs on first sign-in; refuse a login
  that comes back from a different account (freed and re-registered name)
- Per-IP rate limit on /authorize and /login (Workers rate limiting
  binding) so anonymous traffic can't exhaust D1 writes; clean up expired
  rows on 5% of sign-ins instead of every one
- Reject a code_verifier when no PKCE challenge was sent (RFC 9700)
- Form-decode Basic client credentials (RFC 6749 2.3.1)
- Stamp auth_time at GitHub sign-in, not at token exchange
- Revoke the GitHub user token right after reading the user; clear the
  OAuth state cookie
- CI: pin actions to commit SHAs, don't persist Git credentials, expose
  deploy secrets only to the deploy step (never to npm ci)
- Override dev-only undici to the patched 7.30.1 (npm audit: 0)
- Test that users.ts has no case-insensitive duplicate logins or prefixes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwaAHtQd625Lwv121Y6PTn
@sambhav
sambhav deployed to production October 24, 2026 17:18 — with GitHub Actions Active
Google's custom OIDC profile needs a change password URL; point it at
GitHub's password and authentication settings. Replace the 'verify on
first deploy' notes with what Google documents: the redirect URI is
per-profile (set by PR after setup), and PKCE is handled either way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwaAHtQd625Lwv121Y6PTn
@sambhav
sambhav deployed to production October 24, 2026 17:27 — with GitHub Actions Active
PILOT_GITHUB_LOGINS in wrangler.toml limits sign-in to the listed GitHub
logins; it starts as just "sambhav" (sambhav@modelcontextprotocol.io via
users.ts). A test checks every pilot login maps to an account. Remove the
line by PR to open sign-in to everyone in users.ts.

Setup is reordered so each secret is generated by the person who uses it:
the OIDC client secret is created by whoever sets up the Google SSO
profile, and goes straight into GitHub secrets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwaAHtQd625Lwv121Y6PTn
@sambhav
sambhav deployed to production October 24, 2026 17:34 — with GitHub Actions Active

sambhav commented Oct 24, 2026

Copy link
Copy Markdown
Member Author

@dsp-ant this is ready for review. Three things need you, all in the Setup → @dsp-ant section of the description:

  1. Create the GitHub App (needs an org owner) and post its client ID here
  2. Generate the OIDC client secret into GitHub secrets (one command; you keep the file for step 3)
  3. Once the discovery URL is live, add the custom OIDC SSO profile in the Admin console, assign it to a pilot sub-OU with only sambhav@, and post the redirect URI here

Sign-in starts as a pilot for me only (PILOT_GITHUB_LOGINS = "sambhav"), so nothing changes for anyone else until we widen it. Super admins are exempt from the SSO profile, so your login is unaffected.

The PR can merge before any of this; the deploy job skips until the secrets exist.


Generated by Claude Code

dsp-ant commented Oct 26, 2026

Copy link
Copy Markdown
Member

Automated review (Claude-generated)

Heads-up: everything below was produced by Claude Code's /code-review running over this PR's diff (two passes, merged and de-duplicated here). The severity labels are also Claude's judgement. The top items were spot-checked against the code, but none of this has been verified by a human yet — please treat each item as a claim to confirm, not an established fact.

Severity key: 🔴 High · 🟠 Medium · 🟡 Low · ⚪ Nit


🔴 High

1. existingGWSUser accounts are locked out — access-site/src/eligibility.ts:30
workspaceEmailFor() requires hasProvisionUserRole(...), but scripts/validate-config.ts and src/google.ts both treat existingGWSUser: true entries as real, kept accounts that are exempt from the role rule. There are ~14 such entries in users.ts today (several with memberOf: []). Once the OIDC profile is assigned to the whole OU, those people hit no_account → 403 and are told to "add a googleEmailPrefix" they already have, with no password fallback for non-super-admins. The README's "same rule Pulumi uses" claim is not currently true.
Suggested fix: export one shared "has a Workspace account" predicate from src/config/utils.ts and use it in google.ts, validate-config.ts, and the Worker (this also closes the firstName/lastName divergence noted under #9).

2. Identity is trust-on-first-use of a mutable GitHub login — access-site/src/db.ts:110
pinGithubIdentity binds login→GitHub ID on first sign-in. On day one the pin table is empty, so any users.ts login whose owner has since renamed their GitHub account can be registered by a stranger, who then completes /login and is issued an ID token for that person's @modelcontextprotocol.io mailbox; the real owner is afterwards refused with identity_mismatch. Impact is full mailbox/Drive access, so rating this High even though it needs a stale login to exist.
Suggested fix: anchor on something GitHub tracks by immutable ID — record the numeric GitHub user ID in users.ts (validated in CI), or require org/team membership at sign-in — rather than TOFU on the login string.

🟠 Medium

3. GitHub error callback causes a redirect loop — access-site/src/index.ts:86
/login?error=access_denied&state=… (user clicks Cancel, app suspended, temporarily_unavailable) has no code, so githubAuth treats it as a fresh start and 302s straight back to GitHub. The denial is never surfaced, Google never gets an error response, nothing is logged, and for non-interactive errors it's a tight loop until the browser gives up or the rate limiter 429s.
Suggested fix: check c.req.query('error') before invoking githubAuth and render an error page / redirect back to Google with error=access_denied.

4. OAuth state cookie is not __Host- and not bound to the pending authorization — access-site/src/index.ts:94
The state cookie from @hono/oauth-providers is a plain host cookie. Anyone who can run script on any *.modelcontextprotocol.io origin can toss a Domain=modelcontextprotocol.io; Path=/login state cookie and splice their own unredeemed GitHub code into a victim's pending Google sign-in (login-CSRF into the attacker's Workspace account). Preconditions are narrow (subdomain script execution), hence Medium rather than High.
Suggested fix: use a __Host- prefixed state cookie and/or bind state to the __Host-mcp_pending value.

5. Key-rotation procedure can break verification for up to the JWKS cache TTL — access-site/README.md:79, access-site/src/oidc.ts:91
/jwks is served with max-age=300, and the documented rotation starts signing with the new key in the same deploy that first publishes it (only a previous slot exists). A relying party holding a cached JWKS sees an unknown kid for up to 5 minutes. Google usually refetches on kid miss, but that isn't guaranteed.
Suggested fix: document "publish new public key via OIDC_PREVIOUS_JWK first → wait past TTL → swap SIGNING_KEY → move old key to previous", or add a NEXT_JWK slot.

6. Deploy gate doesn't require the GitHub client secret — .github/workflows/access-site.yml:68
ready=true needs the Cloudflare token, signing key and OIDC client secret, but not ACCESS_SITE_GITHUB_CLIENT_SECRET. A first deploy with steps 1/2/4 done but not 3 ships a live issuer where every sign-in fails at the GitHub token exchange ("Sign-in was interrupted"), and revokeGitHubToken sends Basic <id>:undefined.
Suggested fix: add HAS_GITHUB_SECRET to the gate (or have deploy.sh require it when no secret exists yet).

7. Test fixtures are derived from live src/config and gate the deploy job — access-site/test/helpers.ts:99
memberWithoutAccount() / ineligibleMember() throw if users.ts happens not to contain a member of that shape. Since the workflow runs these tests on every src/config/** change and deploy has needs: test, an unrelated, valid users.ts PR can break CI and stop the Worker redeploying (so removed users keep signing in) until someone edits the tests.
Suggested fix: use synthetic logins / injected MEMBERS for the negative cases.

🟡 Low

8. Cross-Origin-Opener-Policy: same-origin on the redirect responses — access-site/src/index.ts:25
secureHeaders() defaults add COOP to every response including the 302s on /authorize and /login. If Google ever runs this federated hop inside a popup (GIS popup mode, re-auth popups), the opener is severed and the result can't be posted back. Workspace SSO is normally full-page, so Low, but the fix is one line: crossOriginOpenerPolicy: false.

9. Eligibility predicate is re-implemented instead of shared — access-site/src/eligibility.ts:27
Beyond #1, the Worker's copy also drops the firstName/lastName requirement that src/google.ts applies, so it can assert email_verified: true for an address Pulumi never created. test/helpers.ts copies the predicate a third time. One exported helper in src/config/utils.ts fixes all of these.

10. no_account conflates several distinct situations and is checked before the pilot gate — access-site/src/oidc.ts:168
"Not in users.ts", "opted out via skipGoogleUserProvisioning", "alias entry without a prefix" and "no eligible role" all render noAccountPage telling the user to open a PR adding googleEmailPrefix, which is wrong advice for most of those cases and will prompt bogus config PRs during the pilot.

11. onError turns the deliberate 502 into a generic 500 — access-site/src/index.ts:143
Only HTTPExceptions with status < 500 keep their status/message, so HTTPException(502, 'GitHub did not return a user.') is logged as unexpected and returned as 500 "Something went wrong", making upstream GitHub failures indistinguishable from Worker bugs.

12. pinGithubIdentity does two sequential D1 round-trips — access-site/src/db.ts:116
INSERT OR IGNORE then SELECT on every successful sign-in. One statement (INSERT … ON CONFLICT(login) DO UPDATE SET login=login RETURNING github_id) or a db.batch([...]) halves hot-path latency, and avoids a read-after-write surprise if D1 read replication is ever enabled.

⚪ Nit

13. Hand-rolled base64url — access-site/src/crypto.ts:13: jose (already a dependency) exports base64url.encode(Uint8Array).

14. /authorize is GET-only — access-site/src/index.ts:56: OIDC Core §3.1.2.1 says the OP must accept GET and POST. Google uses GET, so this is spec-compliance only; app.on(['GET','POST'], '/authorize', …) if you want it.


Generated by Claude Code

This branch was successfully deployed

1 active deployment
production — da6b5e2e Deployed Oct 24, 2026 by sambhav via Preview Changes #376
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants