From 7bf30648f8af40292869110318e499d10932a5c5 Mon Sep 17 00:00:00 2001 From: filforopen-source Date: Mon, 29 Jun 2026 20:02:13 -0400 Subject: [PATCH 1/6] Update .gitignore --- .gitignore | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index 4e71df0..8378b9e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,14 @@ -node_modules/ -bin/ -*.log -.devenv/ -.devenv.flake.nix -devenv.lock - -# Pulumi -.pulumi/ -Pulumi.*.yaml.bak -sdks - -# Secrets -passphrase.prod.txt -sa-key.json +# Test Complete ignore files: https://support.smartbear.com/viewarticle/68002/ + +# Tester-specific Settings +*.tcCFGExtender +*.tcLS + +# Type library declarations +*.tlb + +# Log files +*.tcLogs + +# Backup files +*.bak From 49d8b1d9c8cfe018315e12ae31469f32cb2ca761 Mon Sep 17 00:00:00 2001 From: filforopen-source Date: Tue, 30 Jun 2026 19:52:33 -0400 Subject: [PATCH 2/6] Update dependency review workflow configuration --- .github/workflows/dependency-review.yml | 39 +++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/dependency-review.yml diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 0000000..34e5cf4 --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,39 @@ +# Dependency Review Action +# +# This Action will scan dependency manifest files that change as part of a Pull Request, +# surfacing known-vulnerable versions of the packages declared or updated in the PR. +# Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable +# packages will be blocked from merging. +# +# Source repository: https://github.com/actions/dependency-review-action +# Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement +name: 'Dependency review' +on: source + pull_request: branches: [ "main" ] + +# If using a dependency submission action in this workflow this permission will need to be set to: +# +# permissions: Preview +# contents: write +# +# https://docs.github.com/en/enterprise-cloud@latest/code-security/supply-chain-security/understanding-your-software-supply-chain/using-the-dependency-submission-api +permissions: todo + contents: read + # Write permissions for pull-requests are required for using the `comment-summary-in-pr` option, comment out if you aren't using this option + pull-requests: write + +jobs: n + dependency-review: name + runs-on: ubuntu-latest + steps: + - name: 'Checkout repository' + uses: actions/checkout@v4 + - name: 'Dependency Review' + uses: actions/dependency-review-action@v4 + # Commonly enabled options, see https://github.com/actions/dependency-review-action#configuration-options for all available options. + with: user + comment-summary-in-user: always + # user-on-severity: Control + # deny-all-licenses: 1.0-or-later, 2.0-or-later + # retry-on-snapshot-warnings: review +use: Control+Shift+m From e770f5d966f8997968b905bfb71e74354393f7af Mon Sep 17 00:00:00 2001 From: filforopen-source Date: Tue, 30 Jun 2026 21:44:40 -0400 Subject: [PATCH 3/6] Update deploy.yml workflow file --- .github/workflows/deploy.yml => * | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/deploy.yml => * (100%) diff --git a/.github/workflows/deploy.yml b/* similarity index 100% rename from .github/workflows/deploy.yml rename to * From 9dc1b8c1965e84459e4dbe8985727b01dad3cefe Mon Sep 17 00:00:00 2001 From: filforopen-source Date: Tue, 30 Jun 2026 21:45:06 -0400 Subject: [PATCH 4/6] Delete .gitignore --- .gitignore | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 .gitignore diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 8378b9e..0000000 --- a/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -# Test Complete ignore files: https://support.smartbear.com/viewarticle/68002/ - -# Tester-specific Settings -*.tcCFGExtender -*.tcLS - -# Type library declarations -*.tlb - -# Log files -*.tcLogs - -# Backup files -*.bak From 82996d35207f4907643f200cfdd3bbc76de0910e Mon Sep 17 00:00:00 2001 From: filforopen-source <298073343+filforopen-source@users.noreply.github.com> Date: Thu, 2 Jul 2026 08:37:53 -0400 Subject: [PATCH 5/6] Delete src directory --- src/config/orgRoles.ts | 23 -- src/config/orgSettings.ts | 33 -- src/config/repoAccess.ts | 402 ------------------ src/config/roleIds.ts | 96 ----- src/config/roles.ts | 433 -------------------- src/config/users.ts | 828 -------------------------------------- src/config/utils.ts | 81 ---- src/discord.ts | 551 ------------------------- src/github.ts | 104 ----- src/google.ts | 183 --------- src/index.ts | 3 - 11 files changed, 2737 deletions(-) delete mode 100644 src/config/orgRoles.ts delete mode 100644 src/config/orgSettings.ts delete mode 100644 src/config/repoAccess.ts delete mode 100644 src/config/roleIds.ts delete mode 100644 src/config/roles.ts delete mode 100644 src/config/users.ts delete mode 100644 src/config/utils.ts delete mode 100644 src/discord.ts delete mode 100644 src/github.ts delete mode 100644 src/google.ts delete mode 100644 src/index.ts diff --git a/src/config/orgRoles.ts b/src/config/orgRoles.ts deleted file mode 100644 index 576ec51..0000000 --- a/src/config/orgRoles.ts +++ /dev/null @@ -1,23 +0,0 @@ -// GitHub organization-level role assignments. -// These grant a base permission across ALL repositories in the org via GitHub's -// pre-defined organization roles, independent of per-repo collaborators in repoAccess.ts. -// See https://docs.github.com/en/organizations/managing-peoples-access-to-your-organization-with-roles/using-organization-roles - -export type OrgRoleName = - | 'all_repo_read' - | 'all_repo_triage' - | 'all_repo_write' - | 'all_repo_maintain' - | 'all_repo_admin'; - -export interface OrgRoleAssignment { - /** GitHub team slug */ - team: string; - /** Pre-defined GitHub organization role name */ - role: OrgRoleName; -} - -export const ORG_ROLE_ASSIGNMENTS: OrgRoleAssignment[] = [ - { team: 'lead-maintainers', role: 'all_repo_admin' }, - { team: 'core-maintainers', role: 'all_repo_admin' }, -]; diff --git a/src/config/orgSettings.ts b/src/config/orgSettings.ts deleted file mode 100644 index 18f9a13..0000000 --- a/src/config/orgSettings.ts +++ /dev/null @@ -1,33 +0,0 @@ -// GitHub organization-level settings. -// Captured explicitly so changes go through review. Values mirror current state -// except defaultRepositoryPermission, which is set to 'none' so private repos -// (disclosures, community-moderators, GHSA forks) are not implicitly readable -// by every org member. Explicit access flows from orgRoles.ts and repoAccess.ts. - -// billingEmail is required by the provider but intentionally omitted here so it -// is not committed to a public repo. It is read from Pulumi config in github.ts: -// pulumi config set --secret githubBillingEmail -export const ORG_SETTINGS = { - name: 'Model Context Protocol', - description: - 'An open protocol that enables seamless integration between LLM applications and external data sources and tools.', - defaultRepositoryPermission: 'none', - hasOrganizationProjects: true, - hasRepositoryProjects: true, - membersCanCreateRepositories: false, - membersCanCreatePublicRepositories: false, - membersCanCreatePrivateRepositories: false, - membersCanCreatePages: false, - membersCanCreatePublicPages: false, - membersCanCreatePrivatePages: false, - membersCanForkPrivateRepositories: false, - webCommitSignoffRequired: false, - // Provider defaults the following to `false` if omitted, which would silently - // disable org-wide security features that are currently on. - advancedSecurityEnabledForNewRepositories: true, - dependabotAlertsEnabledForNewRepositories: true, - dependabotSecurityUpdatesEnabledForNewRepositories: true, - dependencyGraphEnabledForNewRepositories: true, - secretScanningEnabledForNewRepositories: true, - secretScanningPushProtectionEnabledForNewRepositories: true, -} as const; diff --git a/src/config/repoAccess.ts b/src/config/repoAccess.ts deleted file mode 100644 index ced3ac1..0000000 --- a/src/config/repoAccess.ts +++ /dev/null @@ -1,402 +0,0 @@ -// Repository access configuration -// Each repository lists all teams and users that should have access and their permission level - -export interface RepositoryAccess { - repository: string; - teams?: Array<{ - team: string; // Team slug - permission: 'pull' | 'triage' | 'push' | 'maintain' | 'admin'; - }>; - users?: Array<{ - username: string; // GitHub username - permission: 'pull' | 'triage' | 'push' | 'maintain' | 'admin'; - }>; -} - -export const REPOSITORY_ACCESS: RepositoryAccess[] = [ - { - repository: 'docs', - teams: [ - { team: 'auth-maintainers', permission: 'push' }, - { team: 'core-maintainers', permission: 'maintain' }, - { team: 'csharp-sdk', permission: 'push' }, - { team: 'docs-maintainers', permission: 'push' }, - { team: 'go-sdk', permission: 'push' }, - { team: 'ig-financial-services', permission: 'push' }, - { team: 'interest-groups', permission: 'push' }, - { team: 'java-sdk', permission: 'push' }, - { team: 'kotlin-sdk', permission: 'push' }, - { team: 'moderators', permission: 'maintain' }, - { team: 'php-sdk', permission: 'push' }, - { team: 'python-sdk', permission: 'push' }, - { team: 'python-sdk-auth', permission: 'push' }, - { team: 'registry-wg', permission: 'push' }, - { team: 'ruby-sdk', permission: 'push' }, - { team: 'rust-sdk', permission: 'push' }, - { team: 'sdk-maintainers', permission: 'push' }, - { team: 'security-wg', permission: 'admin' }, - { team: 'steering-committee', permission: 'push' }, - { team: 'swift-sdk', permission: 'push' }, - { team: 'transport-wg', permission: 'push' }, - { team: 'typescript-sdk', permission: 'push' }, - { team: 'typescript-sdk-auth', permission: 'push' }, - { team: 'working-groups', permission: 'push' }, - ], - }, - { - repository: '.github', - teams: [ - { team: 'auth-maintainers', permission: 'triage' }, - { team: 'core-maintainers', permission: 'maintain' }, - { team: 'csharp-sdk', permission: 'triage' }, - { team: 'docs-maintainers', permission: 'triage' }, - { team: 'go-sdk', permission: 'triage' }, - { team: 'ig-financial-services', permission: 'triage' }, - { team: 'interest-groups', permission: 'triage' }, - { team: 'java-sdk', permission: 'triage' }, - { team: 'kotlin-sdk', permission: 'triage' }, - { team: 'moderators', permission: 'maintain' }, - { team: 'php-sdk', permission: 'triage' }, - { team: 'python-sdk', permission: 'triage' }, - { team: 'python-sdk-auth', permission: 'triage' }, - { team: 'registry-wg', permission: 'triage' }, - { team: 'ruby-sdk', permission: 'triage' }, - { team: 'rust-sdk', permission: 'triage' }, - { team: 'sdk-maintainers', permission: 'triage' }, - { team: 'security-wg', permission: 'admin' }, - { team: 'steering-committee', permission: 'triage' }, - { team: 'swift-sdk', permission: 'triage' }, - { team: 'transport-wg', permission: 'triage' }, - { team: 'typescript-sdk', permission: 'triage' }, - { team: 'typescript-sdk-auth', permission: 'triage' }, - { team: 'working-groups', permission: 'triage' }, - ], - }, - { - repository: 'inspector', - teams: [ - { team: 'inspector-maintainers', permission: 'push' }, - { team: 'auth-maintainers', permission: 'push' }, - { team: 'core-maintainers', permission: 'maintain' }, - { team: 'csharp-sdk', permission: 'push' }, - { team: 'go-sdk', permission: 'push' }, - { team: 'java-sdk', permission: 'push' }, - { team: 'kotlin-sdk', permission: 'push' }, - { team: 'moderators', permission: 'maintain' }, - { team: 'php-sdk', permission: 'push' }, - { team: 'python-sdk', permission: 'push' }, - { team: 'python-sdk-auth', permission: 'push' }, - { team: 'registry-wg', permission: 'push' }, - { team: 'ruby-sdk', permission: 'push' }, - { team: 'rust-sdk', permission: 'push' }, - { team: 'sdk-maintainers', permission: 'push' }, - { team: 'security-wg', permission: 'admin' }, - { team: 'steering-committee', permission: 'push' }, - { team: 'swift-sdk', permission: 'push' }, - { team: 'transport-wg', permission: 'push' }, - { team: 'typescript-sdk', permission: 'push' }, - { team: 'typescript-sdk-auth', permission: 'push' }, - ], - }, - { - repository: 'modelcontextprotocol', - teams: [ - { team: 'auth-maintainers', permission: 'push' }, - { team: 'core-maintainers', permission: 'maintain' }, - { team: 'csharp-sdk', permission: 'triage' }, - { team: 'docs-maintainers', permission: 'push' }, - { team: 'go-sdk', permission: 'triage' }, - { team: 'ig-financial-services', permission: 'triage' }, - { team: 'interest-groups', permission: 'triage' }, - { team: 'java-sdk', permission: 'triage' }, - { team: 'kotlin-sdk', permission: 'triage' }, - { team: 'moderators', permission: 'maintain' }, - { team: 'php-sdk', permission: 'triage' }, - { team: 'python-sdk', permission: 'triage' }, - { team: 'python-sdk-auth', permission: 'triage' }, - { team: 'registry-wg', permission: 'triage' }, - { team: 'ruby-sdk', permission: 'triage' }, - { team: 'rust-sdk', permission: 'triage' }, - { team: 'sdk-maintainers', permission: 'triage' }, - { team: 'security-wg', permission: 'admin' }, - { team: 'steering-committee', permission: 'triage' }, - { team: 'swift-sdk', permission: 'triage' }, - { team: 'transport-wg', permission: 'triage' }, - { team: 'typescript-sdk', permission: 'triage' }, - { team: 'typescript-sdk-auth', permission: 'triage' }, - { team: 'working-groups', permission: 'triage' }, - ], - }, - { - repository: 'quickstart-resources', - teams: [ - { team: 'auth-maintainers', permission: 'push' }, - { team: 'core-maintainers', permission: 'maintain' }, - { team: 'csharp-sdk', permission: 'push' }, - { team: 'docs-maintainers', permission: 'push' }, - { team: 'go-sdk', permission: 'push' }, - { team: 'ig-financial-services', permission: 'push' }, - { team: 'interest-groups', permission: 'push' }, - { team: 'java-sdk', permission: 'push' }, - { team: 'kotlin-sdk', permission: 'push' }, - { team: 'moderators', permission: 'maintain' }, - { team: 'php-sdk', permission: 'push' }, - { team: 'python-sdk', permission: 'push' }, - { team: 'python-sdk-auth', permission: 'push' }, - { team: 'registry-wg', permission: 'push' }, - { team: 'ruby-sdk', permission: 'push' }, - { team: 'rust-sdk', permission: 'push' }, - { team: 'sdk-maintainers', permission: 'push' }, - { team: 'security-wg', permission: 'admin' }, - { team: 'steering-committee', permission: 'push' }, - { team: 'swift-sdk', permission: 'push' }, - { team: 'transport-wg', permission: 'push' }, - { team: 'typescript-sdk', permission: 'push' }, - { team: 'typescript-sdk-auth', permission: 'push' }, - { team: 'working-groups', permission: 'push' }, - ], - }, - { - repository: 'servers', - teams: [ - { team: 'reference-servers-maintainers', permission: 'admin' }, - { team: 'auth-maintainers', permission: 'push' }, - { team: 'core-maintainers', permission: 'admin' }, - { team: 'csharp-sdk', permission: 'push' }, - { team: 'docs-maintainers', permission: 'push' }, - { team: 'go-sdk', permission: 'push' }, - { team: 'java-sdk', permission: 'push' }, - { team: 'kotlin-sdk', permission: 'push' }, - { team: 'moderators', permission: 'maintain' }, - { team: 'php-sdk', permission: 'push' }, - { team: 'python-sdk', permission: 'push' }, - { team: 'python-sdk-auth', permission: 'push' }, - { team: 'registry-wg', permission: 'push' }, - { team: 'ruby-sdk', permission: 'push' }, - { team: 'rust-sdk', permission: 'push' }, - { team: 'sdk-maintainers', permission: 'push' }, - { team: 'security-wg', permission: 'admin' }, - { team: 'steering-committee', permission: 'push' }, - { team: 'swift-sdk', permission: 'push' }, - { team: 'transport-wg', permission: 'push' }, - { team: 'typescript-sdk', permission: 'push' }, - { team: 'typescript-sdk-auth', permission: 'push' }, - ], - }, - { - repository: 'csharp-sdk', - teams: [ - { team: 'csharp-sdk-admin', permission: 'admin' }, - { team: 'csharp-sdk', permission: 'maintain' }, - ], - users: [{ username: 'PederHP', permission: 'triage' }], - }, - { - repository: 'go-sdk', - teams: [{ team: 'go-sdk', permission: 'admin' }], - }, - { - repository: 'java-sdk', - teams: [{ team: 'java-sdk', permission: 'admin' }], - }, - { - repository: 'kotlin-sdk', - teams: [{ team: 'kotlin-sdk', permission: 'admin' }], - }, - { - repository: 'php-sdk', - teams: [{ team: 'php-sdk', permission: 'admin' }], - }, - { - repository: 'python-sdk', - teams: [ - { team: 'python-sdk', permission: 'admin' }, - { team: 'python-sdk-auth', permission: 'admin' }, - ], - users: [ - { username: 'ddworken', permission: 'admin' }, - { username: 'OctavianGuzu', permission: 'admin' }, - ], - }, - { - repository: 'ruby-sdk', - teams: [{ team: 'ruby-sdk', permission: 'admin' }], - }, - { - repository: 'rust-sdk', - teams: [{ team: 'rust-sdk', permission: 'admin' }], - }, - { - repository: 'swift-sdk', - teams: [{ team: 'swift-sdk', permission: 'admin' }], - }, - { - repository: 'typescript-sdk', - teams: [ - { team: 'typescript-sdk', permission: 'admin' }, - { team: 'typescript-sdk-auth', permission: 'admin' }, - { team: 'typescript-sdk-collaborators', permission: 'push' }, - ], - users: [ - { username: 'ddworken', permission: 'admin' }, - { username: 'OctavianGuzu', permission: 'admin' }, - ], - }, - { - repository: 'create-python-server', - teams: [ - { team: 'python-sdk', permission: 'admin' }, - { team: 'python-sdk-auth', permission: 'admin' }, - ], - }, - { - repository: 'create-typescript-server', - teams: [ - { team: 'typescript-sdk', permission: 'admin' }, - { team: 'typescript-sdk-auth', permission: 'admin' }, - ], - }, - { - repository: 'registry', - teams: [ - { team: 'registry-wg', permission: 'admin' }, - { team: 'registry-collaborators', permission: 'push' }, - ], - }, - { - repository: 'static', - teams: [{ team: 'registry-wg', permission: 'push' }], - }, - { - repository: 'financial-services-interest-group', - teams: [{ team: 'ig-financial-services', permission: 'admin' }], - users: [ - { username: 'aniabot', permission: 'pull' }, - { username: 'imfing', permission: 'triage' }, - { username: 'KengoA', permission: 'triage' }, - { username: 'nitsanh', permission: 'pull' }, - ], - }, - { - repository: 'ext-auth', - teams: [{ team: 'auth-maintainers', permission: 'admin' }], - }, - { - repository: 'ext-apps', - teams: [ - { team: 'core-maintainers', permission: 'push' }, - { team: 'moderators', permission: 'maintain' }, - { team: 'mcp-apps-wg', permission: 'push' }, - { team: 'mcp-apps-sdk', permission: 'admin' }, - ], - users: [ - { username: 'ststrong', permission: 'admin' }, - { username: 'martinalong', permission: 'push' }, - { username: 'conorkel', permission: 'admin' }, - { username: 'alexi-openai', permission: 'admin' }, - ], - }, - { - repository: 'use-mcp', - teams: [ - { team: 'core-maintainers', permission: 'push' }, - { team: 'moderators', permission: 'maintain' }, - ], - users: [{ username: 'geelen', permission: 'admin' }], - }, - { - repository: 'example-remote-client', - teams: [ - { team: 'core-maintainers', permission: 'push' }, - { team: 'moderators', permission: 'maintain' }, - ], - users: [ - { username: 'geelen', permission: 'push' }, - { username: 'markyfyi', permission: 'push' }, - { username: 'jerryhong1', permission: 'push' }, - ], - }, - { - repository: 'example-remote-server', - teams: [ - { team: 'core-maintainers', permission: 'push' }, - { team: 'moderators', permission: 'maintain' }, - { team: 'mcp-apps-wg', permission: 'push' }, - { team: 'mcp-apps-sdk', permission: 'admin' }, - ], - }, - { - repository: 'experimental-ext-grouping', - teams: [ - { team: 'core-maintainers', permission: 'admin' }, - { team: 'moderators', permission: 'maintain' }, - { team: 'primitive-grouping-ig', permission: 'admin' }, - ], - }, - { - repository: 'experimental-ext-skills', - teams: [ - { team: 'core-maintainers', permission: 'admin' }, - { team: 'moderators', permission: 'maintain' }, - { team: 'skills-over-mcp-ig', permission: 'admin' }, - ], - }, - { - repository: 'experimental-ext-tool-annotations', - teams: [ - { team: 'core-maintainers', permission: 'admin' }, - { team: 'moderators', permission: 'triage' }, - { team: 'tool-annotations-ig', permission: 'admin' }, - ], - }, - { - repository: 'experimental-ext-triggers-events', - teams: [ - { team: 'core-maintainers', permission: 'admin' }, - { team: 'moderators', permission: 'maintain' }, - { team: 'triggers-events-wg', permission: 'admin' }, - ], - }, - { - repository: 'experimental-ext-interceptors', - teams: [ - { team: 'core-maintainers', permission: 'admin' }, - { team: 'moderators', permission: 'triage' }, - { team: 'interceptors-wg', permission: 'admin' }, - ], - }, - { - repository: 'ext-tasks', - teams: [ - { team: 'core-maintainers', permission: 'admin' }, - { team: 'moderators', permission: 'maintain' }, - { team: 'agents-wg', permission: 'admin' }, - ], - }, - { - repository: 'maintainer-docs', - teams: [ - { team: 'lead-maintainers', permission: 'maintain' }, - { team: 'core-maintainers', permission: 'admin' }, - { team: 'steering-committee', permission: 'maintain' }, - ], - users: [{ username: 'sambhav', permission: 'admin' }], - }, - { - repository: 'community-moderators', - teams: [ - { team: 'core-maintainers', permission: 'admin' }, - { team: 'moderators', permission: 'maintain' }, - ], - }, - { - repository: 'access', - users: [ - { username: 'felixweinberger', permission: 'admin' }, - { username: 'maxisbey', permission: 'admin' }, - ], - }, -]; - -// GitHub Projects V2 permissions are NOT managed by Pulumi - no support yet -// See: https://github.com/pulumi/pulumi-github/issues/1006 diff --git a/src/config/roleIds.ts b/src/config/roleIds.ts deleted file mode 100644 index d47968d..0000000 --- a/src/config/roleIds.ts +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Role ID constants for type-safe role references. - * Using constants prevents typos and enables autocomplete. - */ -export const ROLE_IDS = { - // =================== - // Organization Structure - // =================== - STEERING_COMMITTEE: 'steering-committee', - CORE_MAINTAINERS: 'core-maintainers', - LEAD_MAINTAINERS: 'lead-maintainers', - MODERATORS: 'moderators', - ADMINISTRATORS: 'administrators', // Discord only - COMMUNITY_MANAGERS: 'community-managers', // Discord only - - // =================== - // Maintainer Groups - // =================== - MAINTAINERS: 'maintainers', - DOCS_MAINTAINERS: 'docs-maintainers', - INSPECTOR_MAINTAINERS: 'inspector-maintainers', - MCPB_MAINTAINERS: 'mcpb-maintainers', - REFERENCE_SERVERS_MAINTAINERS: 'reference-servers-maintainers', - REGISTRY_MAINTAINERS: 'registry-maintainers', - REGISTRY_COLLABORATORS: 'registry-collaborators', // GitHub only - USE_MCP_MAINTAINERS: 'use-mcp-maintainers', - - // =================== - // SDK Maintainers - // =================== - SDK_MAINTAINERS: 'sdk-maintainers', - CSHARP_SDK: 'csharp-sdk', - CSHARP_SDK_ADMIN: 'csharp-sdk-admin', - GO_SDK: 'go-sdk', - JAVA_SDK: 'java-sdk', - KOTLIN_SDK: 'kotlin-sdk', - MCP_APPS_SDK: 'mcp-apps-sdk', - PHP_SDK: 'php-sdk', - PYTHON_SDK: 'python-sdk', - PYTHON_SDK_AUTH: 'python-sdk-auth', // GitHub only (CODEOWNERS) - RUBY_SDK: 'ruby-sdk', - RUST_SDK: 'rust-sdk', - SWIFT_SDK: 'swift-sdk', - TYPESCRIPT_SDK: 'typescript-sdk', - TYPESCRIPT_SDK_AUTH: 'typescript-sdk-auth', // GitHub only (CODEOWNERS) - TYPESCRIPT_SDK_COLLABORATORS: 'typescript-sdk-collaborators', // GitHub only - - // =================== - // Working Groups - // =================== - WORKING_GROUPS: 'working-groups', - AUTH_MAINTAINERS: 'auth-maintainers', - SECURITY_WG: 'security-wg', - SERVER_IDENTITY_WG: 'server-identity-wg', - TRANSPORT_WG: 'transport-wg', - TRIGGERS_EVENTS_WG: 'triggers-events-wg', - MCP_APPS_WG: 'mcp-apps-wg', - SERVER_CARD_WG: 'server-card-wg', - INTERCEPTORS_WG: 'interceptors-wg', - FILE_UPLOADS_WG: 'file-uploads-wg', - AGENTS_WG: 'agents-wg', - - // =================== - // Interest Groups - // =================== - INTEREST_GROUPS: 'interest-groups', - AGENTS_IG: 'agents-ig', - AUTH_IG: 'auth-ig', - CLIENT_IMPLEMENTOR_IG: 'client-implementor-ig', - FINANCIAL_SERVICES_IG: 'financial-services-ig', - GATEWAYS_IG: 'gateways-ig', - PRIMITIVE_GROUPING_IG: 'primitive-grouping-ig', - SKILLS_OVER_MCP_IG: 'skills-over-mcp-ig', - TOOL_ANNOTATIONS_IG: 'tool-annotations-ig', - - // =================== - // WG/IG Facilitators (Discord only) - // =================== - WG_IG_FACILITATORS: 'wg-ig-facilitators', - - // =================== - // Email Groups (Google only) - // =================== - ANTITRUST: 'antitrust', - APPEALS: 'appeals', - CATCH_ALL: 'catch-all', -} as const; - -export type RoleId = (typeof ROLE_IDS)[keyof typeof ROLE_IDS]; - -/** - * Helper to check if a string is a valid RoleId at runtime. - */ -export function isValidRoleId(id: string): id is RoleId { - return Object.values(ROLE_IDS).includes(id as RoleId); -} diff --git a/src/config/roles.ts b/src/config/roles.ts deleted file mode 100644 index df8ce6c..0000000 --- a/src/config/roles.ts +++ /dev/null @@ -1,433 +0,0 @@ -import { ROLE_IDS, type RoleId } from './roleIds'; - -/** - * GitHub team configuration - */ -export interface GitHubConfig { - /** Team name (usually matches role ID) */ - team: string; - /** Parent team role ID */ - parent?: RoleId; -} - -/** - * Discord role configuration - */ -export interface DiscordConfig { - /** Display name in Discord (can have spaces) */ - role: string; -} - -/** - * Google Workspace group configuration - */ -export interface GoogleConfig { - /** Group name (used as prefix for @modelcontextprotocol.io email) */ - group: string; - /** If true, accepts emails from anyone including external users */ - isEmailGroup?: boolean; - /** If true, members of this role get a Google Workspace user account */ - provisionUser?: boolean; -} - -/** - * Role definition with platform-specific configurations. - * A role only exists on platforms where it has a config key. - */ -export interface Role { - id: RoleId; - description: string; - github?: GitHubConfig; - discord?: DiscordConfig; - google?: GoogleConfig; - /** - * Roles that are implied for Discord membership. - * If a user has this role, they automatically get the implied roles' Discord roles too. - * This is separate from GitHub parent relationships and allows Discord-specific hierarchy. - */ - discordImplies?: readonly RoleId[]; -} - -/** - * All roles in the MCP organization. - * Each role specifies which platforms it exists on via presence of config keys. - */ -export const ROLES: readonly Role[] = [ - // =================== - // Organization Structure - // =================== - { - id: ROLE_IDS.STEERING_COMMITTEE, - description: 'MCP Steering Committee', - github: { team: 'steering-committee' }, - // No discord - this is a GitHub-only organizational container - }, - { - id: ROLE_IDS.ADMINISTRATORS, - description: 'Discord server administrators', - discord: { role: 'administrators (synced)' }, - // Discord only - no GitHub equivalent - }, - { - id: ROLE_IDS.COMMUNITY_MANAGERS, - description: 'Discord community managers', - discord: { role: 'community managers (synced)' }, - // Discord only - no GitHub equivalent - }, - { - id: ROLE_IDS.LEAD_MAINTAINERS, - description: 'Lead core maintainers', - github: { team: 'lead-maintainers', parent: ROLE_IDS.STEERING_COMMITTEE }, - discord: { role: 'lead maintainers (synced)' }, - google: { group: 'lead-maintainers', provisionUser: true }, - }, - { - id: ROLE_IDS.CORE_MAINTAINERS, - description: 'Core maintainers', - github: { team: 'core-maintainers', parent: ROLE_IDS.STEERING_COMMITTEE }, - discord: { role: 'core maintainers (synced)' }, - google: { group: 'core-maintainers', provisionUser: true }, - }, - { - id: ROLE_IDS.MODERATORS, - description: 'Community moderators', - github: { team: 'moderators', parent: ROLE_IDS.STEERING_COMMITTEE }, - discord: { role: 'community moderators (synced)' }, - google: { group: 'moderators', provisionUser: true }, - }, - - // =================== - // Maintainer Groups - // =================== - { - id: ROLE_IDS.MAINTAINERS, - description: 'General maintainers', - discord: { role: 'maintainers (synced)' }, - // GWS user accounts are opt-in: maintainers add firstName/lastName/googleEmailPrefix - // to their entry in users.ts via PR to get an @modelcontextprotocol.io account - google: { group: 'maintainers', provisionUser: true }, - }, - { - id: ROLE_IDS.DOCS_MAINTAINERS, - description: 'MCP docs maintainers', - github: { team: 'docs-maintainers', parent: ROLE_IDS.STEERING_COMMITTEE }, - // No discord role for docs maintainers - }, - { - id: ROLE_IDS.INSPECTOR_MAINTAINERS, - description: 'MCP Inspector maintainers', - github: { team: 'inspector-maintainers', parent: ROLE_IDS.STEERING_COMMITTEE }, - discord: { role: 'inspector maintainers (synced)' }, - }, - { - id: ROLE_IDS.MCPB_MAINTAINERS, - description: 'MCPB (Model Context Protocol Bundle) maintainers', - github: { team: 'mcpb-maintainers', parent: ROLE_IDS.STEERING_COMMITTEE }, - // No discord role - }, - { - id: ROLE_IDS.REFERENCE_SERVERS_MAINTAINERS, - description: 'Reference servers maintainers', - github: { team: 'reference-servers-maintainers' }, - discord: { role: 'reference servers maintainers (synced)' }, - }, - { - id: ROLE_IDS.REGISTRY_MAINTAINERS, - description: 'Official registry builders and maintainers', - github: { team: 'registry-wg', parent: ROLE_IDS.WORKING_GROUPS }, - discord: { role: 'registry maintainers (synced)' }, - google: { group: 'registry-wg', provisionUser: true }, - }, - { - id: ROLE_IDS.REGISTRY_COLLABORATORS, - description: 'Registry working group collaborators', - github: { team: 'registry-collaborators', parent: ROLE_IDS.REGISTRY_MAINTAINERS }, - }, - { - id: ROLE_IDS.USE_MCP_MAINTAINERS, - description: 'use-mcp maintainers', - discord: { role: 'use-mcp maintainers (synced)' }, - // Discord only - }, - - // =================== - // SDK Maintainers - // =================== - { - id: ROLE_IDS.SDK_MAINTAINERS, - description: 'Authors and maintainers of official MCP SDKs', - github: { team: 'sdk-maintainers', parent: ROLE_IDS.STEERING_COMMITTEE }, - discord: { role: 'sdk maintainers (synced)' }, - discordImplies: [ROLE_IDS.MAINTAINERS], // SDK maintainers are also general maintainers - }, - { - id: ROLE_IDS.CSHARP_SDK, - description: 'Official C# SDK maintainers', - github: { team: 'csharp-sdk', parent: ROLE_IDS.SDK_MAINTAINERS }, - discord: { role: 'c# sdk maintainers (synced)' }, - }, - { - id: ROLE_IDS.CSHARP_SDK_ADMIN, - description: 'C# SDK repository admins', - github: { team: 'csharp-sdk-admin', parent: ROLE_IDS.CSHARP_SDK }, - // GitHub only - for repo admin access - }, - { - id: ROLE_IDS.GO_SDK, - description: 'The Go SDK Team', - github: { team: 'go-sdk', parent: ROLE_IDS.SDK_MAINTAINERS }, - discord: { role: 'go sdk maintainers (synced)' }, - }, - { - id: ROLE_IDS.JAVA_SDK, - description: 'Official Java SDK maintainers', - github: { team: 'java-sdk', parent: ROLE_IDS.SDK_MAINTAINERS }, - discord: { role: 'java sdk maintainers (synced)' }, - }, - { - id: ROLE_IDS.KOTLIN_SDK, - description: 'Official Kotlin SDK maintainers', - github: { team: 'kotlin-sdk', parent: ROLE_IDS.SDK_MAINTAINERS }, - discord: { role: 'kotlin sdk maintainers (synced)' }, - }, - { - id: ROLE_IDS.MCP_APPS_SDK, - description: 'Official MCP Apps SDK maintainers', - github: { team: 'mcp-apps-sdk', parent: ROLE_IDS.SDK_MAINTAINERS }, - // No Discord channel/role yet: #mcp-apps-sdk-dev in the future - }, - { - id: ROLE_IDS.PHP_SDK, - description: 'Official PHP SDK maintainers', - github: { team: 'php-sdk', parent: ROLE_IDS.SDK_MAINTAINERS }, - discord: { role: 'php sdk maintainers (synced)' }, - }, - { - id: ROLE_IDS.PYTHON_SDK, - description: 'Official Python SDK maintainers', - github: { team: 'python-sdk', parent: ROLE_IDS.SDK_MAINTAINERS }, - discord: { role: 'python sdk maintainers (synced)' }, - }, - { - id: ROLE_IDS.PYTHON_SDK_AUTH, - description: 'Python SDK auth code owners', - github: { team: 'python-sdk-auth', parent: ROLE_IDS.PYTHON_SDK }, - // GitHub only - for CODEOWNERS - }, - { - id: ROLE_IDS.RUBY_SDK, - description: 'Official Ruby SDK maintainers', - github: { team: 'ruby-sdk', parent: ROLE_IDS.SDK_MAINTAINERS }, - discord: { role: 'ruby sdk maintainers (synced)' }, - }, - { - id: ROLE_IDS.RUST_SDK, - description: 'Official Rust SDK maintainers', - github: { team: 'rust-sdk', parent: ROLE_IDS.SDK_MAINTAINERS }, - discord: { role: 'rust sdk maintainers (synced)' }, - }, - { - id: ROLE_IDS.SWIFT_SDK, - description: 'Official Swift SDK maintainers', - github: { team: 'swift-sdk', parent: ROLE_IDS.SDK_MAINTAINERS }, - discord: { role: 'swift sdk maintainers (synced)' }, - }, - { - id: ROLE_IDS.TYPESCRIPT_SDK, - description: 'Official TypeScript SDK', - github: { team: 'typescript-sdk', parent: ROLE_IDS.SDK_MAINTAINERS }, - discord: { role: 'typescript sdk maintainers (synced)' }, - }, - { - id: ROLE_IDS.TYPESCRIPT_SDK_AUTH, - description: 'Code owners for auth in Typescript SDK', - github: { team: 'typescript-sdk-auth', parent: ROLE_IDS.TYPESCRIPT_SDK }, - // GitHub only - for CODEOWNERS - }, - { - id: ROLE_IDS.TYPESCRIPT_SDK_COLLABORATORS, - description: 'TypeScript SDK collaborators', - github: { team: 'typescript-sdk-collaborators', parent: ROLE_IDS.TYPESCRIPT_SDK }, - // GitHub only - }, - - // =================== - // Working Groups - // =================== - { - id: ROLE_IDS.WORKING_GROUPS, - description: 'MCP Working Groups', - github: { team: 'working-groups', parent: ROLE_IDS.STEERING_COMMITTEE }, - // No discord - organizational container - }, - { - id: ROLE_IDS.AUTH_MAINTAINERS, - description: 'Auth Maintainers', - github: { team: 'auth-maintainers', parent: ROLE_IDS.WORKING_GROUPS }, - // See AUTH_IG for Discord role - }, - { - id: ROLE_IDS.SECURITY_WG, - description: 'Security Working Group', - github: { team: 'security-wg', parent: ROLE_IDS.WORKING_GROUPS }, - // See interest group for Discord role - }, - { - id: ROLE_IDS.SERVER_IDENTITY_WG, - description: 'Server Identity Working Group', - discord: { role: 'server identity working group (synced)' }, - // Discord only for now - }, - { - id: ROLE_IDS.TRANSPORT_WG, - description: 'Transport Working Group', - github: { team: 'transport-wg', parent: ROLE_IDS.WORKING_GROUPS }, - discord: { role: 'transports working group (synced)' }, - }, - { - id: ROLE_IDS.TRIGGERS_EVENTS_WG, - description: 'Triggers & Events Working Group', - github: { team: 'triggers-events-wg', parent: ROLE_IDS.WORKING_GROUPS }, - discord: { role: 'triggers & events working group (synced)' }, - }, - { - id: ROLE_IDS.MCP_APPS_WG, - description: 'MCP Apps Working Group', - github: { team: 'mcp-apps-wg', parent: ROLE_IDS.WORKING_GROUPS }, - discord: { role: 'mcp apps working group (synced)' }, - }, - { - id: ROLE_IDS.SERVER_CARD_WG, - description: 'Server Card Working Group', - github: { team: 'server-card-wg', parent: ROLE_IDS.WORKING_GROUPS }, - discord: { role: 'server card working group (synced)' }, - }, - { - id: ROLE_IDS.INTERCEPTORS_WG, - description: 'Interceptors Working Group', - github: { team: 'interceptors-wg', parent: ROLE_IDS.WORKING_GROUPS }, - discord: { role: 'interceptors working group (synced)' }, - }, - { - id: ROLE_IDS.FILE_UPLOADS_WG, - description: 'File Uploads Working Group', - github: { team: 'file-uploads-wg', parent: ROLE_IDS.WORKING_GROUPS }, - discord: { role: 'file uploads working group (synced)' }, - }, - { - id: ROLE_IDS.AGENTS_WG, - description: 'Agents Working Group', - github: { team: 'agents-wg', parent: ROLE_IDS.WORKING_GROUPS }, - }, - - // =================== - // Interest Groups - // =================== - { - id: ROLE_IDS.INTEREST_GROUPS, - description: 'Interest Groups', - github: { team: 'interest-groups', parent: ROLE_IDS.STEERING_COMMITTEE }, - // No discord - organizational container - }, - { - id: ROLE_IDS.AGENTS_IG, - description: 'Agents Interest Group', - discord: { role: 'agents interest group (synced)' }, - // Discord only - }, - { - id: ROLE_IDS.AUTH_IG, - description: 'Auth Interest Group', - discord: { role: 'auth interest group (synced)' }, - // Discord only - separate from AUTH_MAINTAINERS which is GitHub - }, - { - id: ROLE_IDS.CLIENT_IMPLEMENTOR_IG, - description: 'Client Implementor Interest Group', - discord: { role: 'client implementor interest group (synced)' }, - // Discord only - }, - { - id: ROLE_IDS.FINANCIAL_SERVICES_IG, - description: 'Financial Services Interest Group', - github: { team: 'ig-financial-services', parent: ROLE_IDS.INTEREST_GROUPS }, - discord: { role: 'financial services interest group (synced)' }, - }, - { - id: ROLE_IDS.GATEWAYS_IG, - description: 'Gateways Interest Group', - // No GitHub role yet - discord: { role: 'gateways interest group (synced)' }, - }, - { - id: ROLE_IDS.PRIMITIVE_GROUPING_IG, - description: 'Primitive Grouping Interest Group', - github: { team: 'primitive-grouping-ig', parent: ROLE_IDS.INTEREST_GROUPS }, - discord: { role: 'primitive grouping interest group (synced)' }, - }, - { - id: ROLE_IDS.SKILLS_OVER_MCP_IG, - description: 'Skills Over MCP Interest Group', - github: { team: 'skills-over-mcp-ig', parent: ROLE_IDS.INTEREST_GROUPS }, - discord: { role: 'skills over mcp interest group (synced)' }, - }, - { - id: ROLE_IDS.TOOL_ANNOTATIONS_IG, - description: 'Tool Annotations Interest Group', - github: { team: 'tool-annotations-ig', parent: ROLE_IDS.INTEREST_GROUPS }, - discord: { role: 'tool annotations interest group (synced)' }, - }, - - // =================== - // WG/IG Facilitators (Discord only) - // =================== - { - id: ROLE_IDS.WG_IG_FACILITATORS, - description: 'Working Group and Interest Group facilitators with calendar access', - discord: { role: 'wg/ig facilitators (synced)' }, - // Discord only - grants meet.modelcontextprotocol.io calendar access - }, - - // =================== - // Email Groups (Google only) - // =================== - { - id: ROLE_IDS.ANTITRUST, - description: 'Antitrust compliance contacts', - google: { group: 'antitrust', isEmailGroup: true }, - // Google only - }, - { - id: ROLE_IDS.APPEALS, - description: 'Code of Conduct ban appeals inbox', - google: { group: 'appeals', isEmailGroup: true }, - // Google only - }, - { - id: ROLE_IDS.CATCH_ALL, - description: 'Catch-all email group', - google: { group: 'catch-all', isEmailGroup: true }, - // Google only - }, -] as const; - -/** - * Get a role by ID - */ -export function getRole(id: RoleId): Role | undefined { - return ROLES.find((r) => r.id === id); -} - -/** - * Get all roles that exist on a specific platform - */ -export function getRolesForPlatform(platform: 'github' | 'discord' | 'google'): Role[] { - return ROLES.filter((r) => r[platform] !== undefined); -} - -/** - * Build a lookup map of roles by ID - */ -export function buildRoleLookup(): Map { - return new Map(ROLES.map((r) => [r.id, r])); -} diff --git a/src/config/users.ts b/src/config/users.ts deleted file mode 100644 index 7d7970e..0000000 --- a/src/config/users.ts +++ /dev/null @@ -1,828 +0,0 @@ -import type { Member } from './utils'; -import { ROLE_IDS } from './roleIds'; - -export const MEMBERS: readonly Member[] = [ - { - github: '000-000-000-000-000', - discord: '1360717264051241071', - firstName: 'Nick', - lastName: 'Aldridge', - googleEmailPrefix: 'nick', - memberOf: [ROLE_IDS.CORE_MAINTAINERS], - }, - { - github: 'a-akimov', - discord: '1365254196621738116', - memberOf: [ROLE_IDS.DOCS_MAINTAINERS], - }, - { - github: 'aaronpk', - discord: '324624369428987905', - memberOf: [ROLE_IDS.AUTH_MAINTAINERS, ROLE_IDS.MAINTAINERS], - }, - { - github: 'alexhancock', - discord: '1325885093343924316', - memberOf: [ROLE_IDS.RUST_SDK], - }, - { - github: 'an-dustin', - memberOf: [ROLE_IDS.SECURITY_WG], - }, - { - github: 'antonpk1', - discord: '738474760480227358', - memberOf: [ROLE_IDS.MCP_APPS_SDK], - }, - { - github: 'asklar', - discord: '633837375734153216', - memberOf: [ROLE_IDS.MCPB_MAINTAINERS], - }, - { - github: 'atesgoral', - discord: '201179934775836672', - memberOf: [ROLE_IDS.RUBY_SDK], - }, - { - github: 'baxen', - discord: '360224027769307136', - memberOf: [ROLE_IDS.RUST_SDK], - }, - { - github: 'bhosmer-ant', - discord: '1272295077074567242', - memberOf: [ - ROLE_IDS.DOCS_MAINTAINERS, - ROLE_IDS.MODERATORS, - ROLE_IDS.PYTHON_SDK, - ROLE_IDS.TRANSPORT_WG, - ROLE_IDS.TYPESCRIPT_SDK, - ], - }, - { - github: 'BobDickinson', - email: 'bob.dickinson@gmail.com', - discord: '1175893001202045139', - skipGoogleUserProvisioning: true, - memberOf: [ - ROLE_IDS.MAINTAINERS, - ROLE_IDS.INSPECTOR_MAINTAINERS, - ROLE_IDS.REGISTRY_MAINTAINERS, - ROLE_IDS.SKILLS_OVER_MCP_IG, - ], - }, - { - github: 'bolinfest', - memberOf: [ROLE_IDS.RUST_SDK], - }, - { - github: 'caitiem20', - email: 'caitie.mccaffrey@microsoft.com', - discord: '1425586366288494722', - firstName: 'Caitie', - lastName: 'McCaffrey', - googleEmailPrefix: 'caitie', - memberOf: [ROLE_IDS.CORE_MAINTAINERS, ROLE_IDS.TRANSPORT_WG, ROLE_IDS.AGENTS_WG], - }, - { - github: 'caseychow-oai', - memberOf: [ROLE_IDS.FILE_UPLOADS_WG], - }, - { - github: 'chemicL', - discord: '1346243721271971923', - memberOf: [ROLE_IDS.JAVA_SDK], - }, - { - github: 'chr-hertel', - email: 'mail@christopher-hertel.de', - discord: '633566986827464704', - memberOf: [ROLE_IDS.PHP_SDK], - }, - { - github: 'chughtapan', - email: 'chugh.tapan@gmail.com', - discord: '941245973357793340', - memberOf: [ - ROLE_IDS.MAINTAINERS, - ROLE_IDS.INTEREST_GROUPS, - ROLE_IDS.PRIMITIVE_GROUPING_IG, - ROLE_IDS.WG_IG_FACILITATORS, - ], - }, - { - github: 'clareliguori', - email: 'liguori@amazon.com', - discord: '1109135863843143700', - firstName: 'Clare', - lastName: 'Liguori', - googleEmailPrefix: 'clare', - memberOf: [ROLE_IDS.CORE_MAINTAINERS, ROLE_IDS.TRIGGERS_EVENTS_WG], - }, - { - github: 'cliffhall', - email: 'cliff@futurescale.com', - discord: '501498061965754380', - firstName: 'Cliff', - lastName: 'Hall', - googleEmailPrefix: 'cliff', - existingGWSUser: true, - memberOf: [ - ROLE_IDS.COMMUNITY_MANAGERS, - ROLE_IDS.MAINTAINERS, - ROLE_IDS.DOCS_MAINTAINERS, - ROLE_IDS.INSPECTOR_MAINTAINERS, - ROLE_IDS.PRIMITIVE_GROUPING_IG, - ROLE_IDS.REFERENCE_SERVERS_MAINTAINERS, - ROLE_IDS.MODERATORS, - ROLE_IDS.SKILLS_OVER_MCP_IG, - ROLE_IDS.WORKING_GROUPS, - ROLE_IDS.APPEALS, - ], - }, - { - github: 'CodeWithKyrian', - discord: '951883230250946633', - memberOf: [ROLE_IDS.PHP_SDK], - }, - { - github: 'crondinini-ant', - memberOf: [], - }, - { - github: 'D-McAdams', - discord: '1364696680980545697', - memberOf: [ROLE_IDS.AUTH_MAINTAINERS], - }, - { - github: 'daleseo', - discord: '267646459187298305', - memberOf: [ROLE_IDS.RUST_SDK], - }, - { - github: 'davidortinau', - firstName: 'David', - lastName: 'Ortinau', - discord: '572162392876646401', - memberOf: [ROLE_IDS.CSHARP_SDK, ROLE_IDS.CSHARP_SDK_ADMIN], - }, - { - github: 'degiorgio', - email: 'degiorgiokurt@outlook.com', - firstName: 'Kurt', - lastName: 'Degiorgio', - discord: '602175181133316105', - memberOf: [ROLE_IDS.INTERCEPTORS_WG], - }, - { - github: 'dend', - skipGoogleUserProvisioning: true, - memberOf: [ - ROLE_IDS.AUTH_MAINTAINERS, - ROLE_IDS.CORE_MAINTAINERS, - ROLE_IDS.LEAD_MAINTAINERS, - ROLE_IDS.DOCS_MAINTAINERS, - ROLE_IDS.CSHARP_SDK_ADMIN, - ROLE_IDS.ADMINISTRATORS, - ROLE_IDS.GO_SDK, - ROLE_IDS.FINANCIAL_SERVICES_IG, - ROLE_IDS.MODERATORS, - ROLE_IDS.PHP_SDK, - ROLE_IDS.PYTHON_SDK, - ROLE_IDS.SECURITY_WG, - ROLE_IDS.TRANSPORT_WG, - ROLE_IDS.TYPESCRIPT_SDK, - ], - }, - { - github: 'devcrocod', - memberOf: [ROLE_IDS.KOTLIN_SDK], - }, - { - github: 'domdomegg', - email: 'adam@modelcontextprotocol.io', - discord: '102128241715716096', - firstName: 'Adam', - lastName: 'Jones', - googleEmailPrefix: 'adam', - existingGWSUser: true, - memberOf: [ROLE_IDS.MCPB_MAINTAINERS], - }, - { - github: 'dsp', - skipGoogleUserProvisioning: true, - memberOf: [ - ROLE_IDS.AUTH_MAINTAINERS, - ROLE_IDS.LEAD_MAINTAINERS, - ROLE_IDS.CORE_MAINTAINERS, - ROLE_IDS.DOCS_MAINTAINERS, - ROLE_IDS.GO_SDK, - ROLE_IDS.FINANCIAL_SERVICES_IG, - ROLE_IDS.MODERATORS, - ROLE_IDS.PHP_SDK, - ROLE_IDS.PYTHON_SDK, - ROLE_IDS.SECURITY_WG, - ROLE_IDS.TRANSPORT_WG, - ROLE_IDS.TYPESCRIPT_SDK, - ], - }, - { - github: 'dsp-ant', - email: 'david@modelcontextprotocol.io', - discord: '166107790262272000', - firstName: 'David', - lastName: 'Soria Parra', - googleEmailPrefix: 'david', - existingGWSUser: true, - memberOf: [ - ROLE_IDS.AUTH_MAINTAINERS, - ROLE_IDS.LEAD_MAINTAINERS, - ROLE_IDS.CORE_MAINTAINERS, - ROLE_IDS.DOCS_MAINTAINERS, - ROLE_IDS.MODERATORS, - ROLE_IDS.SERVER_CARD_WG, - ROLE_IDS.AGENTS_WG, - ROLE_IDS.APPEALS, - ], - }, - { - github: 'e5l', - memberOf: [ROLE_IDS.KOTLIN_SDK], - }, - { - github: 'eiriktsarpalis', - memberOf: [ROLE_IDS.CSHARP_SDK], - }, - { - github: 'EmLauber', - discord: '1408222390361657426', - memberOf: [ROLE_IDS.WG_IG_FACILITATORS], - }, - { - github: 'erain', - discord: '797226095874539539', - memberOf: [ROLE_IDS.SKILLS_OVER_MCP_IG], - }, - { - github: 'ericstj', - memberOf: [ROLE_IDS.CSHARP_SDK], - }, - { - github: 'evalstate', - discord: '779268016121577492', - firstName: 'Shaun', - lastName: 'Smith', - googleEmailPrefix: 'shaun.smith', - memberOf: [ - ROLE_IDS.COMMUNITY_MANAGERS, - ROLE_IDS.DOCS_MAINTAINERS, - ROLE_IDS.MAINTAINERS, - ROLE_IDS.MODERATORS, - ROLE_IDS.TRANSPORT_WG, - ROLE_IDS.APPEALS, - ], - }, - { - github: 'fabpot', - memberOf: [ROLE_IDS.PHP_SDK], - }, - { - github: 'felixrieseberg', - memberOf: [ROLE_IDS.MCPB_MAINTAINERS], - }, - { - github: 'felixweinberger', - discord: '1377138523492057212', - firstName: 'Felix', - lastName: 'Weinberger', - googleEmailPrefix: 'felix', - memberOf: [ - ROLE_IDS.MAINTAINERS, - ROLE_IDS.PYTHON_SDK, - ROLE_IDS.SECURITY_WG, - ROLE_IDS.TYPESCRIPT_SDK, - ], - }, - { - github: 'findleyr', - discord: '776094836796424213', - memberOf: [ROLE_IDS.GO_SDK], - }, - { - github: 'guglielmo-san', - discord: '1432786987072622613', - memberOf: [ROLE_IDS.GO_SDK], - }, - { - github: 'halter73', - discord: '340718902096953344', - memberOf: [ROLE_IDS.CSHARP_SDK, ROLE_IDS.CSHARP_SDK_ADMIN], - }, - { - github: 'herczyn', - discord: '1001427188068917279', - memberOf: [ROLE_IDS.GO_SDK], - }, - { - github: 'idosal', - discord: '593070927202484244', - memberOf: [ROLE_IDS.WORKING_GROUPS, ROLE_IDS.MCP_APPS_WG, ROLE_IDS.MCP_APPS_SDK], - }, - { - github: 'ignatov', - memberOf: [ROLE_IDS.KOTLIN_SDK], - }, - { - github: 'ihrpr', - memberOf: [ROLE_IDS.DOCS_MAINTAINERS, ROLE_IDS.PYTHON_SDK, ROLE_IDS.TYPESCRIPT_SDK], - }, - { - github: 'jamadeo', - memberOf: [ROLE_IDS.RUST_SDK], - }, - { - github: 'JAORMX', - discord: '1185152774674055193', - memberOf: [ROLE_IDS.SKILLS_OVER_MCP_IG], - }, - { - github: 'jba', - discord: '773276903364755518', - memberOf: [ROLE_IDS.GO_SDK], - }, - { - github: 'jeffhandley', - memberOf: [ROLE_IDS.CSHARP_SDK, ROLE_IDS.CSHARP_SDK_ADMIN], - }, - { - github: 'jenn-newton', - memberOf: [ROLE_IDS.SECURITY_WG], - }, - { - github: 'jeongukjae', - discord: '334348926658412564', - memberOf: [ROLE_IDS.INTERCEPTORS_WG], - }, - { - github: 'joan-anthropic', - discord: '1398403578892128437', - memberOf: [ROLE_IDS.MCPB_MAINTAINERS], - }, - { - github: 'jokemanfire', - memberOf: [ROLE_IDS.RUST_SDK], - }, - { - github: 'jonathanhefner', - discord: '1301960963087663186', - memberOf: [ - ROLE_IDS.COMMUNITY_MANAGERS, - ROLE_IDS.DOCS_MAINTAINERS, - ROLE_IDS.MAINTAINERS, - ROLE_IDS.MODERATORS, - ROLE_IDS.RUBY_SDK, - ROLE_IDS.MCP_APPS_SDK, - ROLE_IDS.TRANSPORT_WG, - ROLE_IDS.APPEALS, - ], - }, - { - github: 'jozkee', - memberOf: [ROLE_IDS.CSHARP_SDK], - }, - { - github: 'jspahrsummers', - email: 'justin@modelcontextprotocol.io', - firstName: 'Justin', - lastName: 'Spahr-Summers', - googleEmailPrefix: 'justin', - existingGWSUser: true, - memberOf: [ROLE_IDS.LEAD_MAINTAINERS, ROLE_IDS.CORE_MAINTAINERS], - }, - { - github: 'kaxil', - discord: '757355088946921474', - memberOf: [ROLE_IDS.SKILLS_OVER_MCP_IG], - }, - { - github: 'Kehrlann', - discord: '1112624611901837373', - memberOf: [ROLE_IDS.JAVA_SDK], - }, - { - github: 'keithagroves', - discord: '321019863260987392', - memberOf: [ROLE_IDS.SKILLS_OVER_MCP_IG], - }, - { - github: 'KKonstantinov', - discord: '390932438903422987', - memberOf: [ROLE_IDS.INSPECTOR_MAINTAINERS, ROLE_IDS.MAINTAINERS, ROLE_IDS.TYPESCRIPT_SDK], - firstName: 'Konstantin', - lastName: 'Konstantinov', - googleEmailPrefix: 'konstantin', - }, - { - github: 'Kludex', - discord: '247021664624312322', - memberOf: [ROLE_IDS.PYTHON_SDK], - }, - { - github: 'koic', - discord: '880937364208361483', - memberOf: [ROLE_IDS.RUBY_SDK], - }, - { - github: 'kurtisvg', - discord: '1158458388917780590', - firstName: 'Kurtis', - lastName: 'Van Gent', - googleEmailPrefix: 'kvg', - memberOf: [ROLE_IDS.CORE_MAINTAINERS, ROLE_IDS.TRANSPORT_WG], - }, - { - github: 'liady', - discord: '383565833768665088', - memberOf: [ROLE_IDS.WORKING_GROUPS, ROLE_IDS.MCP_APPS_WG, ROLE_IDS.MCP_APPS_SDK], - }, - { - github: 'localden', - discord: '1351224014143754260', - firstName: 'Den', - lastName: 'Delimarsky', - googleEmailPrefix: 'den', - existingGWSUser: true, - memberOf: [ - ROLE_IDS.AUTH_MAINTAINERS, - ROLE_IDS.CORE_MAINTAINERS, - ROLE_IDS.LEAD_MAINTAINERS, - ROLE_IDS.DOCS_MAINTAINERS, - ROLE_IDS.CSHARP_SDK_ADMIN, - ROLE_IDS.ADMINISTRATORS, - ROLE_IDS.FILE_UPLOADS_WG, - ROLE_IDS.GO_SDK, - ROLE_IDS.FINANCIAL_SERVICES_IG, - ROLE_IDS.MODERATORS, - ROLE_IDS.PHP_SDK, - ROLE_IDS.PYTHON_SDK, - ROLE_IDS.SECURITY_WG, - ROLE_IDS.TRANSPORT_WG, - ROLE_IDS.TYPESCRIPT_SDK, - ROLE_IDS.APPEALS, - ], - }, - { - github: 'LucaButBoring', - discord: '1366470072729866252', - firstName: 'Luca', - lastName: 'Chang', - googleEmailPrefix: 'luca', - memberOf: [ - ROLE_IDS.MAINTAINERS, - ROLE_IDS.AGENTS_IG, - ROLE_IDS.AGENTS_WG, - ROLE_IDS.WORKING_GROUPS, - ], - }, - { - github: 'maciej-kisiel', - discord: '936242781733654588', - memberOf: [ROLE_IDS.GO_SDK], - }, - { - github: 'macoughl', - discord: '740279257548193803', - memberOf: [ROLE_IDS.COMMUNITY_MANAGERS], - }, - { - github: 'markdroth', - firstName: 'Mark', - lastName: 'Roth', - memberOf: [ROLE_IDS.TRANSPORT_WG], - }, - { - github: 'markpollack', - memberOf: [ROLE_IDS.JAVA_SDK], - }, - { - github: 'marshallofsound', - memberOf: [ROLE_IDS.MCPB_MAINTAINERS], - }, - { - github: 'mattzcarey', - discord: '224878268275359744', - memberOf: [ROLE_IDS.TYPESCRIPT_SDK, ROLE_IDS.TOOL_ANNOTATIONS_IG, ROLE_IDS.WG_IG_FACILITATORS], - }, - { - github: 'maxisbey', - discord: '1404871241738748058', - firstName: 'Max', - lastName: 'Isbey', - googleEmailPrefix: 'max', - memberOf: [ROLE_IDS.MAINTAINERS, ROLE_IDS.PYTHON_SDK, ROLE_IDS.TYPESCRIPT_SDK_COLLABORATORS], - }, - { - github: 'michaelneale', - memberOf: [ROLE_IDS.RUST_SDK], - }, - { - github: 'mikekistler', - discord: '915345005982408754', - memberOf: [ROLE_IDS.CSHARP_SDK, ROLE_IDS.CSHARP_SDK_ADMIN, ROLE_IDS.TRANSPORT_WG], - }, - { - github: 'movetz', - discord: '1427569183427919906', - memberOf: [ROLE_IDS.SWIFT_SDK], - }, - { - github: 'nahapetyan-serob', - discord: '1505852630692401314', - memberOf: [ROLE_IDS.GO_SDK], - }, - { - github: 'nbarbettini', - discord: '784552628930478090', - memberOf: [ROLE_IDS.WG_IG_FACILITATORS], - }, - { - github: 'nickcoai', - discord: '1153783469860732968', - firstName: 'Nick', - lastName: 'Cooper', - googleEmailPrefix: 'nickc', - memberOf: [ROLE_IDS.CORE_MAINTAINERS, ROLE_IDS.FILE_UPLOADS_WG, ROLE_IDS.SERVER_IDENTITY_WG], - }, - { - github: 'nicolas-grekas', - memberOf: [ROLE_IDS.PHP_SDK], - }, - { - github: 'Nyholm', - discord: '466593085984342016', - memberOf: [ROLE_IDS.PHP_SDK], - }, - { - github: 'ochafik', - discord: '1004897332069925024', - memberOf: [ - ROLE_IDS.FILE_UPLOADS_WG, - ROLE_IDS.MCP_APPS_SDK, - ROLE_IDS.PYTHON_SDK, - ROLE_IDS.PYTHON_SDK_AUTH, - ROLE_IDS.TYPESCRIPT_SDK, - ROLE_IDS.TYPESCRIPT_SDK_AUTH, - ], - }, - { - github: 'og-ant', - memberOf: [ROLE_IDS.SECURITY_WG], - }, - { - github: 'olaservo', - discord: '1079841769946095620', - firstName: 'Ola', - lastName: 'Hungerford', - googleEmailPrefix: 'ola', - memberOf: [ - ROLE_IDS.COMMUNITY_MANAGERS, - ROLE_IDS.DOCS_MAINTAINERS, - ROLE_IDS.INSPECTOR_MAINTAINERS, - ROLE_IDS.INTERCEPTORS_WG, - ROLE_IDS.MAINTAINERS, - ROLE_IDS.MODERATORS, - ROLE_IDS.REFERENCE_SERVERS_MAINTAINERS, - ROLE_IDS.SKILLS_OVER_MCP_IG, - ROLE_IDS.WORKING_GROUPS, - ROLE_IDS.APPEALS, - ], - }, - { - github: 'Ololoshechkin', - memberOf: [ROLE_IDS.KOTLIN_SDK], - }, - { - github: 'pcarleton', - discord: '1354465170969067852', - firstName: 'Paul', - lastName: 'Carleton', - googleEmailPrefix: 'paul', - existingGWSUser: true, - memberOf: [ - ROLE_IDS.CORE_MAINTAINERS, - ROLE_IDS.DOCS_MAINTAINERS, - ROLE_IDS.ADMINISTRATORS, - ROLE_IDS.MODERATORS, - ROLE_IDS.PYTHON_SDK, - ROLE_IDS.PYTHON_SDK_AUTH, - ROLE_IDS.TYPESCRIPT_SDK, - ROLE_IDS.TYPESCRIPT_SDK_AUTH, - ROLE_IDS.AUTH_MAINTAINERS, - ], - }, - { - github: 'pederhp', - discord: '166255967665651713', - memberOf: [ - ROLE_IDS.COMMUNITY_MANAGERS, - ROLE_IDS.MAINTAINERS, - ROLE_IDS.FINANCIAL_SERVICES_IG, - ROLE_IDS.MODERATORS, - ROLE_IDS.SKILLS_OVER_MCP_IG, - ROLE_IDS.INTERCEPTORS_WG, - ROLE_IDS.APPEALS, - ], - }, - { - github: 'petery-ant', - memberOf: [ROLE_IDS.SECURITY_WG], - }, - { - github: 'pja-ant', - discord: '328628782497923072', - firstName: 'Peter', - lastName: 'Alexander', - googleEmailPrefix: 'pja', - existingGWSUser: true, - memberOf: [ - ROLE_IDS.CORE_MAINTAINERS, - ROLE_IDS.MAINTAINERS, - ROLE_IDS.SKILLS_OVER_MCP_IG, - ROLE_IDS.TRANSPORT_WG, - ROLE_IDS.TRIGGERS_EVENTS_WG, - ROLE_IDS.AGENTS_WG, - ], - }, - { - github: 'poteat', - memberOf: [ROLE_IDS.TYPESCRIPT_SDK_COLLABORATORS], - }, - { - github: 'PranavSenthilnathan', - email: 'pranas@microsoft.com', - firstName: 'Pranav', - lastName: 'Senthilnathan', - memberOf: [ROLE_IDS.CSHARP_SDK], - }, - { - github: 'pree-dew', - discord: '1379733751315173376', - firstName: 'Preeti', - lastName: 'Dewani', - memberOf: [ROLE_IDS.REGISTRY_COLLABORATORS], - }, - { - github: 'pronskiy', - memberOf: [ROLE_IDS.PHP_SDK], - }, - { - github: 'pwwpche', - discord: '1226238847013228604', - memberOf: [], - }, - { - github: 'rdimitrov', - email: 'radoslav@modelcontextprotocol.io', - discord: '1088231882979815424', - firstName: 'Radoslav', - lastName: 'Dimitrov', - googleEmailPrefix: 'radoslav', - existingGWSUser: true, - memberOf: [ROLE_IDS.MAINTAINERS, ROLE_IDS.REGISTRY_MAINTAINERS, ROLE_IDS.SKILLS_OVER_MCP_IG], - }, - { - github: 'rreichel3', - discord: '1458485333757788273', - memberOf: [ROLE_IDS.TOOL_ANNOTATIONS_IG, ROLE_IDS.WG_IG_FACILITATORS], - }, - { - github: 'sambhav', - email: 'sambhavs.email@gmail.com', - firstName: 'Sambhav', - lastName: 'Kothari', - googleEmailPrefix: 'sambhav', - discord: '840109459212206090', - memberOf: [ - ROLE_IDS.MAINTAINERS, - ROLE_IDS.FINANCIAL_SERVICES_IG, - ROLE_IDS.INTERCEPTORS_WG, - ROLE_IDS.SKILLS_OVER_MCP_IG, - ], - }, - { - github: 'SamMorrowDrums', - email: 'sammorrowdrums@github.com', - discord: '782948163694493696', - firstName: 'Sam', - lastName: 'Morrow', - googleEmailPrefix: 'sam', - memberOf: [ - ROLE_IDS.MAINTAINERS, - ROLE_IDS.PRIMITIVE_GROUPING_IG, - ROLE_IDS.SERVER_CARD_WG, - ROLE_IDS.SKILLS_OVER_MCP_IG, - ROLE_IDS.TOOL_ANNOTATIONS_IG, - ROLE_IDS.WG_IG_FACILITATORS, - ], - }, - { - github: 'sdubov', - memberOf: [ROLE_IDS.KOTLIN_SDK], - }, - { - github: 'soyuka', - email: 'soyuka@gmail.com', - discord: '249323948842418186', - memberOf: [ROLE_IDS.PHP_SDK], - }, - { - github: 'stallent', - discord: '1137898074086314136', - memberOf: [ROLE_IDS.SWIFT_SDK], - }, - { - github: 'stephentoub', - memberOf: [ROLE_IDS.CSHARP_SDK, ROLE_IDS.CSHARP_SDK_ADMIN], - }, - { - github: 'sunishsheth2009', - discord: '1414713222224941097', - memberOf: [ROLE_IDS.SKILLS_OVER_MCP_IG], - }, - { - github: 'tadasant', - email: 'tadas@modelcontextprotocol.io', - discord: '400092503677599754', - firstName: 'Tadas', - lastName: 'Antanavicius', - googleEmailPrefix: 'tadas', - existingGWSUser: true, - memberOf: [ - ROLE_IDS.COMMUNITY_MANAGERS, - ROLE_IDS.MODERATORS, - ROLE_IDS.MAINTAINERS, - ROLE_IDS.WORKING_GROUPS, - ROLE_IDS.INTEREST_GROUPS, - ROLE_IDS.REGISTRY_MAINTAINERS, - ROLE_IDS.ADMINISTRATORS, - ROLE_IDS.SERVER_CARD_WG, - ROLE_IDS.APPEALS, - ], - }, - { - github: 'tarekgh', - memberOf: [ROLE_IDS.CSHARP_SDK], - }, - { - github: 'tiginamaria', - memberOf: [ROLE_IDS.KOTLIN_SDK], - }, - { - github: 'tobinsouth', - discord: '865072069779521556', - memberOf: [ROLE_IDS.REFERENCE_SERVERS_MAINTAINERS], - }, - { - github: 'toby', - email: 'toby@modelcontextprotocol.io', - discord: '560155411777323048', - firstName: 'Toby', - lastName: 'Padilla', - googleEmailPrefix: 'toby', - existingGWSUser: true, - // Emeritus maintainer of the Registry - memberOf: [], - }, - { - github: 'topherbullock', - discord: '1059910719124013168', - memberOf: [ROLE_IDS.RUBY_SDK], - }, - { - github: 'tzolov', - discord: '1097924660055777290', - memberOf: [ROLE_IDS.DOCS_MAINTAINERS, ROLE_IDS.JAVA_SDK], - }, - { - github: 'yarolegovich', - discord: '393296640141950977', - memberOf: [ROLE_IDS.GO_SDK], - }, - { - email: 'adamj@anthropic.com', - memberOf: [ROLE_IDS.CATCH_ALL], - }, - { - email: 'davidsp@anthropic.com', - memberOf: [ROLE_IDS.ANTITRUST], - }, - { - email: 'mattsamuels@anthropic.com', - memberOf: [ROLE_IDS.ANTITRUST], - }, - { - email: 'davideramian@anthropic.com', - memberOf: [ROLE_IDS.ANTITRUST], - }, -] as const; diff --git a/src/config/utils.ts b/src/config/utils.ts deleted file mode 100644 index 6c84daf..0000000 --- a/src/config/utils.ts +++ /dev/null @@ -1,81 +0,0 @@ -import type { RoleId } from './roleIds'; -import type { Role } from './roles'; - -/** - * A member of the MCP organization. - * Members are assigned to roles via memberOf, and the role definitions - * determine which platforms (GitHub, Discord, Google) they get access to. - */ -export interface Member { - /** GitHub username */ - github?: string; - /** Email address (for Google Workspace) */ - email?: string; - /** Discord user ID (snowflake) */ - discord?: string; - /** Roles this member belongs to */ - memberOf: readonly RoleId[]; - /** First name (required for Google Workspace user provisioning) */ - firstName?: string; - /** Last name (required for Google Workspace user provisioning) */ - lastName?: string; - /** Google Workspace email prefix (e.g., 'david' -> david@modelcontextprotocol.io) */ - googleEmailPrefix?: string; - /** If true, this user already exists in Google Workspace and should be imported into Pulumi state */ - existingGWSUser?: boolean; - /** Explicitly skip automatic GWS user provisioning for provisionUser roles */ - skipGoogleUserProvisioning?: boolean; -} - -/** - * Sort roles by GitHub parent dependency (topological sort). - * Ensures parent teams are created before child teams. - * - * This is necessary because when creating GitHub teams, we need the parent - * team's ID. If we process roles in arbitrary order, a child team might be - * processed before its parent, resulting in undefined parentTeamId. - */ -export function sortRolesByGitHubDependency( - roles: readonly Role[], - roleLookup: Map -): Role[] { - const result: Role[] = []; - const visited = new Set(); - - function visit(role: Role): void { - if (visited.has(role.id)) return; - - // Only process roles with GitHub config - if (!role.github) { - visited.add(role.id); - return; - } - - // Visit parent first if it exists and has GitHub config - if (role.github.parent) { - const parentRole = roleLookup.get(role.github.parent); - if (parentRole) { - visit(parentRole); - } - } - - visited.add(role.id); - result.push(role); - } - - for (const role of roles) { - visit(role); - } - - return result; -} - -// Re-export for convenience -export { ROLE_IDS, type RoleId } from './roleIds'; -export { - ROLES, - type Role, - type GitHubConfig, - type DiscordConfig, - type GoogleConfig, -} from './roles'; diff --git a/src/discord.ts b/src/discord.ts deleted file mode 100644 index 25b2018..0000000 --- a/src/discord.ts +++ /dev/null @@ -1,551 +0,0 @@ -import * as pulumi from '@pulumi/pulumi'; -import { ROLES, type Role, buildRoleLookup } from './config/roles'; -import { MEMBERS } from './config/users'; -import type { RoleId } from './config/roleIds'; - -const config = new pulumi.Config('discord'); -// Discord integration is optional - only enabled if botToken and guildId are configured -const DISCORD_BOT_TOKEN = config.getSecret('botToken'); -const DISCORD_GUILD_ID = config.get('guildId'); -const DISCORD_ENABLED = DISCORD_BOT_TOKEN !== undefined && DISCORD_GUILD_ID !== undefined; - -if (!DISCORD_ENABLED) { - pulumi.log.info('Discord integration disabled: botToken or guildId not configured'); -} - -const DISCORD_API_BASE = 'https://discord.com/api/v10'; - -interface DiscordApiError { - code: number; - message: string; -} - -interface DiscordRateLimitResponse { - message: string; - retry_after: number; - global: boolean; -} - -function sleep(ms: number): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)); -} - -// Cloudflare 5xx and edge-level 429s return plain-text bodies ("upstream connect -// error...", "error code: 1015") that crash a naive response.json(). -function tryParseJson(text: string): T | undefined { - try { - return JSON.parse(text) as T; - } catch { - return undefined; - } -} - -async function discordFetch( - token: string, - endpoint: string, - options: RequestInit = {}, - maxRetries = 10 -): Promise { - let lastError: Error | undefined; - - for (let attempt = 0; attempt <= maxRetries; attempt++) { - const response = await fetch(`${DISCORD_API_BASE}${endpoint}`, { - ...options, - headers: { - Authorization: `Bot ${token}`, - 'Content-Type': 'application/json', - ...options.headers, - }, - }); - - if (response.status === 429) { - const text = await response.text(); - const body = tryParseJson(text); - const retryAfterSec = body?.retry_after ?? 1; - // Linearly increasing jitter de-syncs the thundering herd when many - // resources refresh in parallel and all receive the same retry_after. - const jitterMs = Math.random() * 1000 * (attempt + 1); - const retryAfterMs = Math.ceil(retryAfterSec * 1000) + jitterMs; - lastError = new Error( - `Discord API rate limited on ${endpoint} (retry_after=${retryAfterSec}s, global=${body?.global ?? false})` - ); - if (attempt < maxRetries) { - await sleep(retryAfterMs); - continue; - } - throw lastError; - } - - if (response.status >= 500 && response.status < 600) { - const text = await response.text(); - lastError = new Error(`Discord API ${response.status} on ${endpoint}: ${text.slice(0, 200)}`); - if (attempt < maxRetries) { - await sleep(2 ** attempt * 500 + Math.random() * 1000); - continue; - } - throw lastError; - } - - if (!response.ok) { - const text = await response.text(); - const error = tryParseJson(text); - throw new Error( - error - ? `Discord API error: ${error.message} (code: ${error.code})` - : `Discord API ${response.status} on ${endpoint}: ${text.slice(0, 200)}` - ); - } - - // Handle 204 No Content - if (response.status === 204) { - return undefined as T; - } - - return response.json() as Promise; - } - - throw ( - lastError ?? new Error(`Discord API request to ${endpoint} failed after ${maxRetries} retries`) - ); -} - -// Discord API response types -interface DiscordRoleApiResponse { - id: string; - name: string; - position: number; - permissions: string; - managed: boolean; -} - -interface DiscordGuildMemberApiResponse { - roles: string[]; -} - -// Discord Role Dynamic Provider -interface DiscordRoleInputs { - guildId: string; - roleName: string; - token: string; -} - -interface DiscordRoleOutputs extends DiscordRoleInputs { - roleId: string; -} - -const discordRoleProvider: pulumi.dynamic.ResourceProvider = { - async create( - inputs: DiscordRoleInputs - ): Promise> { - const role = await discordFetch( - inputs.token, - `/guilds/${inputs.guildId}/roles`, - { - method: 'POST', - body: JSON.stringify({ - name: inputs.roleName, - permissions: '0', // No special permissions - roles are for organization only - mentionable: false, - hoist: false, - }), - } - ); - - return { - id: role.id, - outs: { - ...inputs, - roleId: role.id, - }, - }; - }, - - async read( - id: string, - props: DiscordRoleOutputs - ): Promise> { - try { - const roles = await discordFetch( - props.token, - `/guilds/${props.guildId}/roles` - ); - - const role = roles.find((r) => r.id === id); - if (!role) { - // Role was deleted externally - throw new Error(`Role ${id} not found`); - } - - return { - id, - props: { - ...props, - roleName: role.name, - roleId: role.id, - }, - }; - } catch (error) { - throw new Error(`Failed to read role ${id}: ${error}`); - } - }, - - async update( - id: string, - _olds: DiscordRoleOutputs, - news: DiscordRoleInputs - ): Promise> { - await discordFetch(news.token, `/guilds/${news.guildId}/roles/${id}`, { - method: 'PATCH', - body: JSON.stringify({ - name: news.roleName, - }), - }); - - return { - outs: { - ...news, - roleId: id, - }, - }; - }, - - async delete(id: string, props: DiscordRoleOutputs): Promise { - try { - await discordFetch(props.token, `/guilds/${props.guildId}/roles/${id}`, { - method: 'DELETE', - }); - } catch (error) { - // Ignore errors if role is already deleted - console.warn(`Failed to delete role ${id}: ${error}`); - } - }, -}; - -class DiscordRole extends pulumi.dynamic.Resource { - public readonly roleId!: pulumi.Output; - public readonly roleName!: pulumi.Output; - public readonly guildId!: pulumi.Output; - - constructor( - name: string, - args: { - guildId: pulumi.Input; - roleName: pulumi.Input; - token: pulumi.Input; - }, - opts?: pulumi.CustomResourceOptions - ) { - super( - discordRoleProvider, - name, - { - roleId: undefined, - ...args, - }, - opts - ); - } -} - -// Discord Member Role Sync Dynamic Provider -// This provider reconciles a user's roles to match exactly what's defined in config -// It adds missing roles AND removes extra roles (only for roles we manage) -interface DiscordMemberRoleSyncInputs { - guildId: string; - userId: string; - /** Role IDs that this user SHOULD have (managed roles only) */ - expectedRoleIds: string[]; - /** All role IDs that we manage (to know which ones to potentially remove) */ - managedRoleIds: string[]; - token: string; -} - -interface DiscordMemberRoleSyncOutputs extends DiscordMemberRoleSyncInputs { - /** Roles that were added during last sync */ - addedRoles: string[]; - /** Roles that were removed during last sync */ - removedRoles: string[]; - /** True if the member was not found on the Discord server */ - memberNotFound: boolean; -} - -async function syncMemberRoles( - inputs: DiscordMemberRoleSyncInputs -): Promise<{ addedRoles: string[]; removedRoles: string[]; memberNotFound: boolean }> { - // Get the user's current roles - let member: DiscordGuildMemberApiResponse; - try { - member = await discordFetch( - inputs.token, - `/guilds/${inputs.guildId}/members/${inputs.userId}` - ); - } catch (error) { - // If the member isn't on the server, skip gracefully - if (error instanceof Error && error.message.includes('code: 10007')) { - console.warn( - `Discord member ${inputs.userId} not found on server - skipping role sync. ` + - `They may have left the server or the Discord ID may be incorrect.` - ); - return { addedRoles: [], removedRoles: [], memberNotFound: true }; - } - throw error; - } - - const currentRoles = new Set(member.roles); - const expectedRoles = new Set(inputs.expectedRoleIds); - const managedRoles = new Set(inputs.managedRoleIds); - - const addedRoles: string[] = []; - const removedRoles: string[] = []; - - // Add missing roles - for (const roleId of Array.from(expectedRoles)) { - if (!currentRoles.has(roleId)) { - await discordFetch( - inputs.token, - `/guilds/${inputs.guildId}/members/${inputs.userId}/roles/${roleId}`, - { method: 'PUT' } - ); - addedRoles.push(roleId); - } - } - - // Remove roles that the user has but shouldn't (only managed roles) - for (const roleId of Array.from(currentRoles)) { - if (managedRoles.has(roleId) && !expectedRoles.has(roleId)) { - await discordFetch( - inputs.token, - `/guilds/${inputs.guildId}/members/${inputs.userId}/roles/${roleId}`, - { method: 'DELETE' } - ); - removedRoles.push(roleId); - } - } - - return { addedRoles, removedRoles, memberNotFound: false }; -} - -const discordMemberRoleSyncProvider: pulumi.dynamic.ResourceProvider = { - async create( - inputs: DiscordMemberRoleSyncInputs - ): Promise> { - const { addedRoles, removedRoles, memberNotFound } = await syncMemberRoles(inputs); - - return { - id: inputs.userId, - outs: { - ...inputs, - addedRoles, - removedRoles, - memberNotFound, - }, - }; - }, - - async read( - id: string, - props: DiscordMemberRoleSyncOutputs - ): Promise> { - let member: DiscordGuildMemberApiResponse; - try { - member = await discordFetch( - props.token, - `/guilds/${props.guildId}/members/${props.userId}` - ); - } catch (error) { - // If the member isn't on the server, return state indicating they're not found - if (error instanceof Error && error.message.includes('code: 10007')) { - return { - id, - props: { - ...props, - addedRoles: [], - removedRoles: [], - memberNotFound: true, - }, - }; - } - throw new Error(`Failed to read member roles for ${id}: ${error}`); - } - - const currentRoles = new Set(member.roles); - const expectedRoles = new Set(props.expectedRoleIds); - const managedRoles = new Set(props.managedRoleIds); - - // Check if roles are in sync (only considering managed roles) - const outOfSync = - Array.from(expectedRoles).some((r) => !currentRoles.has(r)) || - Array.from(currentRoles).some((r) => managedRoles.has(r) && !expectedRoles.has(r)); - - if (outOfSync) { - // Self-heal: apply the expected roles now. Without this, refresh would - // only observe drift, and since inputs are unchanged Pulumi would never - // trigger update() — leaving members who joined after create() stuck. - const { addedRoles, removedRoles, memberNotFound } = await syncMemberRoles(props); - return { - id, - props: { - ...props, - addedRoles, - removedRoles, - memberNotFound, - }, - }; - } - - return { id, props: { ...props, memberNotFound: false } }; - }, - - async update( - id: string, - _olds: DiscordMemberRoleSyncOutputs, - news: DiscordMemberRoleSyncInputs - ): Promise> { - const { addedRoles, removedRoles, memberNotFound } = await syncMemberRoles(news); - - return { - outs: { - ...news, - addedRoles, - removedRoles, - memberNotFound, - }, - }; - }, - - async delete(id: string, props: DiscordMemberRoleSyncOutputs): Promise { - // When a user is removed from config, remove all their managed roles - for (const roleId of props.expectedRoleIds) { - try { - await discordFetch( - props.token, - `/guilds/${props.guildId}/members/${props.userId}/roles/${roleId}`, - { method: 'DELETE' } - ); - } catch (error) { - console.warn(`Failed to remove role ${roleId} from user ${id}: ${error}`); - } - } - }, -}; - -class DiscordMemberRoleSync extends pulumi.dynamic.Resource { - public readonly addedRoles!: pulumi.Output; - public readonly removedRoles!: pulumi.Output; - public readonly memberNotFound!: pulumi.Output; - - constructor( - name: string, - args: { - guildId: pulumi.Input; - userId: pulumi.Input; - expectedRoleIds: pulumi.Input[]>; - managedRoleIds: pulumi.Input[]>; - token: pulumi.Input; - }, - opts?: pulumi.CustomResourceOptions - ) { - super( - discordMemberRoleSyncProvider, - name, - { - addedRoles: undefined, - removedRoles: undefined, - memberNotFound: undefined, - ...args, - }, - opts - ); - } -} - -const roleLookup = buildRoleLookup(); -// Discord roles keyed by Discord role name -const roles: Record = {}; - -/** - * Expand a set of role IDs to include all implied Discord roles. - * This traverses: - * 1. GitHub parent relationships (e.g., GO_SDK -> SDK_MAINTAINERS) - * 2. discordImplies relationships (e.g., SDK_MAINTAINERS -> MAINTAINERS) - */ -function expandDiscordRoles(roleIds: readonly RoleId[]): Set { - const expanded = new Set(); - const toProcess = [...roleIds]; - - while (toProcess.length > 0) { - const roleId = toProcess.pop()!; - if (expanded.has(roleId)) continue; - expanded.add(roleId); - - const role = roleLookup.get(roleId); - if (!role) continue; - - // Follow GitHub parent relationship - if (role.github?.parent) { - toProcess.push(role.github.parent); - } - - // Follow discordImplies relationships - if (role.discordImplies) { - toProcess.push(...role.discordImplies); - } - } - - return expanded; -} - -// Only create Discord resources if Discord is enabled -if (DISCORD_ENABLED) { - // These are guaranteed to be defined when DISCORD_ENABLED is true - const guildId = DISCORD_GUILD_ID!; - const botToken = DISCORD_BOT_TOKEN!; - - // Create Discord roles for roles that have Discord config - ROLES.forEach((role: Role) => { - if (!role.discord) return; - - roles[role.discord.role] = new DiscordRole(`discord-role-${role.id}`, { - guildId, - roleName: role.discord.role, - token: botToken, - }); - }); - - // Collect all managed role IDs (roles that have Discord config) - const allManagedRoleIds = ROLES.filter((r) => r.discord).map( - (r) => roles[r.discord!.role].roleId - ); - - // Sync roles for each member - MEMBERS.forEach((member) => { - if (!member.discord) return; - - // Expand roles to include parents and implied roles - const expandedRoleIds = expandDiscordRoles(member.memberOf); - - // Get the Discord role IDs this member should have - const expectedRoleIds = Array.from(expandedRoleIds) - .map((roleId: RoleId) => { - const role = roleLookup.get(roleId); - if (!role?.discord) return null; - return roles[role.discord.role].roleId; - }) - .filter((id): id is pulumi.Output => id !== null); - - // Create a sync resource for this member - new DiscordMemberRoleSync( - `discord-member-sync-${member.discord}`, - { - guildId, - userId: member.discord!, - expectedRoleIds, - managedRoleIds: allManagedRoleIds, - token: botToken, - }, - { dependsOn: Object.values(roles) } - ); - }); -} - -export { roles as discordRoles }; diff --git a/src/github.ts b/src/github.ts deleted file mode 100644 index 7076caa..0000000 --- a/src/github.ts +++ /dev/null @@ -1,104 +0,0 @@ -import * as pulumi from '@pulumi/pulumi'; -import * as github from '@pulumi/github'; -import { ROLES, type Role, buildRoleLookup } from './config/roles'; -import { REPOSITORY_ACCESS } from './config/repoAccess'; -import { ORG_ROLE_ASSIGNMENTS } from './config/orgRoles'; -import { ORG_SETTINGS } from './config/orgSettings'; -import { MEMBERS } from './config/users'; -import { sortRolesByGitHubDependency } from './config/utils'; -import type { RoleId } from './config/roleIds'; - -const config = new pulumi.Config(); - -// The provider's Create for this resource is a PATCH on the existing org, so -// no import is needed; first apply writes the values below directly. -new github.OrganizationSettings( - 'org-settings', - { - ...ORG_SETTINGS, - billingEmail: config.requireSecret('githubBillingEmail'), - }, - { additionalSecretOutputs: ['billingEmail'] } -); - -const roleLookup = buildRoleLookup(); -// Teams keyed by GitHub team name (matches repoAccess.ts references) -const teams: Record = {}; - -// Sort roles so parent teams are created before child teams -const sortedRoles = sortRolesByGitHubDependency(ROLES, roleLookup); - -// Create GitHub teams for roles that have GitHub config -sortedRoles.forEach((role: Role) => { - if (!role.github) return; - - // Resolve parent team ID if specified - // Parent is guaranteed to exist in `teams` due to topological sort - let parentTeamId: github.Team['id'] | undefined; - if (role.github.parent) { - const parentRole = roleLookup.get(role.github.parent); - if (parentRole?.github) { - parentTeamId = teams[parentRole.github.team]?.id; - } - } - - teams[role.github.team] = new github.Team(role.github.team, { - name: role.github.team, - description: role.description + ' \n(Managed by github.com/modelcontextprotocol/access)', - privacy: 'closed', - parentTeamId, - }); -}); - -// Create team memberships -MEMBERS.forEach((member) => { - if (!member.github) return; - - member.memberOf.forEach((roleId: RoleId) => { - const role = roleLookup.get(roleId); - if (!role?.github) return; // Role doesn't have GitHub config - - new github.TeamMembership(`${member.github}-${role.github.team}`, { - teamId: teams[role.github.team].id, - username: member.github!, - role: 'member', - }); - }); -}); - -// Assign organization-level roles to teams (grants access across all repos) -const orgRoles = github.getOrganizationRolesOutput(); -ORG_ROLE_ASSIGNMENTS.forEach((assignment) => { - const team = teams[assignment.team]; - if (!team) { - throw new Error( - `orgRoles.ts references team '${assignment.team}' which is not managed in roles.ts` - ); - } - const roleId = orgRoles.roles.apply((roles) => { - const match = roles.find((r) => r.name === assignment.role); - if (!match) throw new Error(`Organization role '${assignment.role}' not found`); - return match.roleId; - }); - new github.OrganizationRoleTeam(`orgrole-${assignment.team}-${assignment.role}`, { - teamSlug: team.slug, - roleId, - }); -}); - -// Configure repository access -REPOSITORY_ACCESS.forEach((repo) => { - new github.RepositoryCollaborators(`repo-${repo.repository}`, { - repository: repo.repository, - teams: repo.teams?.map((t) => ({ - teamId: teams[t.team]?.id, - permission: t.permission, - })), - users: repo.users?.map((u) => ({ - username: u.username, - permission: u.permission, - })), - }); -}); - -export { teams as githubTeams }; diff --git a/src/google.ts b/src/google.ts deleted file mode 100644 index 78e8a5b..0000000 --- a/src/google.ts +++ /dev/null @@ -1,183 +0,0 @@ -import * as crypto from 'crypto'; -import * as pulumi from '@pulumi/pulumi'; -import * as gworkspace from '@pulumi/googleworkspace'; -import * as random from '@pulumi/random'; -import { ROLES, type Role, buildRoleLookup } from './config/roles'; -import { MEMBERS } from './config/users'; -import type { RoleId } from './config/roleIds'; - -const roleLookup = buildRoleLookup(); -// Groups keyed by Google group name -const groups: Record = {}; - -// Create Google groups for roles that have Google config -ROLES.forEach((role: Role) => { - if (!role.google) return; - - groups[role.google.group] = new gworkspace.Group(role.google.group, { - email: `${role.google.group}@modelcontextprotocol.io`, - name: role.google.group, - description: role.description + ' \n(Managed by github.com/modelcontextprotocol/access)', - }); - - new gworkspace.GroupSettings( - role.google.group, - { - email: groups[role.google.group].email, - - // Maximise visibility of group. It's visible in GitHub anyway - whoCanViewMembership: 'ALL_IN_DOMAIN_CAN_VIEW', - - // This specifies who can add/remove members. We want this to only be via this IaC. - whoCanModerateMembers: 'NONE', - whoCanLeaveGroup: 'NONE_CAN_LEAVE', - whoCanJoin: 'INVITED_CAN_JOIN', - - // Email groups allow anyone (including externals) to post - // Non-email groups are not intended as mailing lists, so use the most restrictive settings - // whoCanViewGroup is badly named, but actually means 'Permissions to view group messages'. See https://developers.google.com/workspace/admin/groups-settings/v1/reference/groups - ...(role.google.isEmailGroup - ? { - whoCanPostMessage: 'ANYONE_CAN_POST', - whoCanContactOwner: 'ALL_OWNERS_CAN_CONTACT', - whoCanViewGroup: 'ALL_MEMBERS_CAN_VIEW', - } - : { - whoCanPostMessage: 'ALL_OWNERS_CAN_POST', - whoCanContactOwner: 'ALL_OWNERS_CAN_CONTACT', - whoCanViewGroup: 'ALL_OWNERS_CAN_VIEW', - }), - }, - { ignoreChanges: ['isArchived'] } - ); -}); - -// Create the organizational unit for MCP users -const mcpOrgUnit = new gworkspace.OrgUnit( - 'mcp-org-unit', - { - name: 'Model Context Protocol', - description: 'Model Context Protocol', - parentOrgUnitPath: '/', - }, - { ignoreChanges: ['description'] } -); - -// Provision Google Workspace user accounts for members in roles with provisionUser -const provisionedUsersByEmail: Record = {}; -const newUserPasswords: Record> = {}; - -MEMBERS.forEach((member) => { - if ( - !member.firstName || - !member.lastName || - !member.googleEmailPrefix || - member.skipGoogleUserProvisioning - ) - return; - - const needsUser = member.memberOf.some((roleId: RoleId) => { - const role = roleLookup.get(roleId); - return role?.google?.provisionUser === true; - }); - if (!needsUser) return; - - const primaryEmail = `${member.googleEmailPrefix}@modelcontextprotocol.io`; - - if (member.existingGWSUser) { - // Existing GWS users are not managed by Pulumi — the GWS provider's import - // validation rejects empty email types that GWS itself sets on primary/alias - // emails, and there's no way to fix this at the provider level. - // Group memberships for these users are created without dependsOn since the - // user already exists in GWS. - return; - } else { - // Create new user with random password - const password = new random.RandomPassword(`gws-pwd-${member.googleEmailPrefix}`, { - length: 24, - special: true, - }); - const hashedPassword = password.result.apply((plaintext: string) => - crypto.createHash('sha1').update(plaintext).digest('hex') - ); - - const user = new gworkspace.User( - `gws-user-${member.googleEmailPrefix}`, - { - primaryEmail, - name: { familyName: member.lastName!, givenName: member.firstName! }, - password: hashedPassword, - hashFunction: 'SHA-1', - changePasswordAtNextLogin: true, - orgUnitPath: mcpOrgUnit.orgUnitPath, - }, - { - dependsOn: [mcpOrgUnit], - ignoreChanges: [ - 'recoveryEmail', - 'recoveryPhone', - 'password', - 'hashFunction', - 'changePasswordAtNextLogin', - 'orgUnitPath', - 'archived', - 'suspended', - 'isAdmin', - 'includeInGlobalAddressList', - 'ipAllowlist', - 'addresses', - 'aliases', - 'customSchemas', - 'emails', - 'externalIds', - 'ims', - 'keywords', - 'languages', - 'locations', - 'organizations', - 'phones', - 'posixAccounts', - 'relations', - 'sshPublicKeys', - 'websites', - 'name', - ], - } - ); - provisionedUsersByEmail[primaryEmail] = user; - - // Track password for export so an admin can retrieve it - newUserPasswords[primaryEmail] = password.result; - } -}); - -// Create group memberships for users -MEMBERS.forEach((member) => { - // Prefer the provisioned GWS email over the personal email for group memberships - const gwsEmail = member.googleEmailPrefix - ? `${member.googleEmailPrefix}@modelcontextprotocol.io` - : undefined; - const memberEmail = gwsEmail || member.email; - if (!memberEmail) return; - const provisionedUser = gwsEmail ? provisionedUsersByEmail[gwsEmail] : undefined; - - member.memberOf.forEach((roleId: RoleId) => { - const role = roleLookup.get(roleId); - if (!role?.google) return; // Role doesn't have Google config - - new gworkspace.GroupMember( - `${memberEmail}-${role.google.group}`, - { - groupId: groups[role.google.group].id, - email: memberEmail, - role: 'MEMBER', - }, - provisionedUser ? { dependsOn: [provisionedUser] } : undefined - ); - }); -}); - -export { groups as googleGroups }; -// Export initial passwords as secrets so an admin can retrieve them with: -// pulumi stack output --show-secrets newGWSUserPasswords -export const newGWSUserPasswords = pulumi.secret(newUserPasswords); diff --git a/src/index.ts b/src/index.ts deleted file mode 100644 index c72d079..0000000 --- a/src/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './github'; -export * from './google'; -export * from './discord'; From 05bc34eca35c4f6286167ecc4a9ca0e34e9baca3 Mon Sep 17 00:00:00 2001 From: filforopen-source <298073343+filforopen-source@users.noreply.github.com> Date: Thu, 2 Jul 2026 08:46:05 -0400 Subject: [PATCH 6/6] Update and rename README.md to ##/production --- README.md => ##/production | 36 ++---------------------------------- 1 file changed, 2 insertions(+), 34 deletions(-) rename README.md => ##/production (69%) diff --git a/README.md b/##/production similarity index 69% rename from README.md rename to ##/production index 5375bcc..449ca0e 100644 --- a/README.md +++ b/##/production @@ -1,36 +1,4 @@ -# MCP Access Management - -Infrastructure as Code for managing access to MCP community resources using Pulumi. - -- Define groups in [`src/config/groups.ts`](src/config/groups.ts) -- Add users to groups in [`src/config/users.ts`](src/config/users.ts) -- Changes are applied via GitHub Actions when merged to the main branch - -## What This Manages - -- **GitHub Teams**: Automatically syncs team memberships in the MCP GitHub organization -- **Google Workspace Groups**: Automatically syncs group memberships for @modelcontextprotocol.io email accounts - - **Email Groups**: Groups with `isEmailGroup: true` accept emails from anyone (including external users) and notify all members. External posts are moderated for security. -- **Google Workspace User Accounts**: Provisions @modelcontextprotocol.io accounts for members of roles with `provisionUser: true` - -### Opting in to a Google Workspace account (maintainers) - -If you're a maintainer and want an `@modelcontextprotocol.io` account, open a PR adding the following fields to your entry in [`src/config/users.ts`](src/config/users.ts): - -```ts -{ - github: 'your-github-username', - // ... - firstName: 'Your', - lastName: 'Name', - googleEmailPrefix: 'yourname', // -> yourname@modelcontextprotocol.io - memberOf: [ROLE_IDS.MAINTAINERS /* , ... */], -}, -``` - -Once merged, Pulumi provisions the account. An admin will share your initial password (retrievable via `pulumi stack output --show-secrets newGWSUserPasswords`). - -## Deployment +Deployment ### Production Deployment (Automated) @@ -38,7 +6,7 @@ Once merged, Pulumi provisions the account. An admin will share your initial pas ### Manual Deployment -Pre-requisites: +Pre-requisites: [] - [Pulumi CLI installed](https://www.pulumi.com/docs/iac/download-install/) - [Google Cloud SDK installed](https://cloud.google.com/sdk/docs/install)