Initial commit
This commit is contained in:
commit
e3f0af2f94
16 changed files with 3857 additions and 0 deletions
28
.github/workflows/ci.yml
vendored
Normal file
28
.github/workflows/ci.yml
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and Typecheck
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
51
.github/workflows/deploy.yml
vendored
Normal file
51
.github/workflows/deploy.yml
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
name: Deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
PULUMI_VERSION: "3.197.0"
|
||||
|
||||
jobs:
|
||||
deploy-production:
|
||||
name: Deploy to Production
|
||||
runs-on: ubuntu-latest
|
||||
environment: production
|
||||
concurrency:
|
||||
group: deploy-production
|
||||
cancel-in-progress: false
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Setup Pulumi
|
||||
uses: pulumi/actions@v6
|
||||
with:
|
||||
pulumi-version: ${{ env.PULUMI_VERSION }}
|
||||
|
||||
- name: Authenticate to Google Cloud
|
||||
uses: google-github-actions/auth@v2
|
||||
with:
|
||||
credentials_json: ${{ secrets.GCP_PROD_SERVICE_ACCOUNT_KEY }}
|
||||
|
||||
- name: Deploy to Production
|
||||
run: |
|
||||
echo "${{ secrets.PULUMI_PROD_PASSPHRASE }}" > passphrase.prod.txt
|
||||
make up
|
||||
12
.gitignore
vendored
Normal file
12
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
node_modules/
|
||||
bin/
|
||||
*.log
|
||||
|
||||
# Pulumi
|
||||
.pulumi/
|
||||
Pulumi.*.yaml.bak
|
||||
sdks
|
||||
|
||||
# Secrets
|
||||
passphrase.prod.txt
|
||||
sa-key.json
|
||||
16
Makefile
Normal file
16
Makefile
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
.PHONY: help login preview up
|
||||
|
||||
# Default target
|
||||
help: ## Show this help message
|
||||
@echo "Available targets:"
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " %-20s %s\n", $$1, $$2}'
|
||||
|
||||
# Production stack commands
|
||||
login: ## Login to Pulumi backend (GCS)
|
||||
pulumi login gs://mcp-access-prod-pulumi-state
|
||||
|
||||
preview: login ## Preview infrastructure changes
|
||||
PULUMI_CONFIG_PASSPHRASE_FILE=passphrase.prod.txt pulumi preview --stack prod
|
||||
|
||||
up: login ## Deploy infrastructure
|
||||
PULUMI_CONFIG_PASSPHRASE_FILE=passphrase.prod.txt pulumi up --yes --stack prod
|
||||
8
Pulumi.prod.yaml
Normal file
8
Pulumi.prod.yaml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
encryptionsalt: v1:N7wjHl1hvH8=:v1:VT8UbYOIS0kNADr5:fSnvGX+is6htXDjLS03RDObRnzGEVA==
|
||||
config:
|
||||
googleworkspace:customerId: C01dja0mo
|
||||
googleworkspace:credentials:
|
||||
secure: v1:Ntr2su0kgwp0oAh3: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
|
||||
github:owner: modelcontextprotocol
|
||||
github:token:
|
||||
secure: v1:RKjKUcq1e0V7kVNf:5PP+vWbnRzgX8MmhF/yp52Fh78cTMBYz798JNc9LiyrubSt4AAZH/y/UA6kErP0FmXfFLRpdDIMmrR3RiNQlCe7nbkB6hf0CsV8mg8U7oB0EkA5wnRlqrZaKBri5l5puy2ZBFWjLhHr6oC714A==
|
||||
9
Pulumi.yaml
Normal file
9
Pulumi.yaml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
name: mcp-access
|
||||
runtime: nodejs
|
||||
description: Infrastructure as Code for MCP access management
|
||||
packages:
|
||||
googleworkspace:
|
||||
source: terraform-provider
|
||||
version: 0.14.0
|
||||
parameters:
|
||||
- hashicorp/googleworkspace
|
||||
107
README.md
Normal file
107
README.md
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
# 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
|
||||
|
||||
## Deployment
|
||||
|
||||
### Production Deployment (Automated)
|
||||
|
||||
**Note:** Production deployment is automatically handled by GitHub Actions. All merges to the `main` branch trigger an automatic deployment via [the configured GitHub Actions workflow](.github/workflows/deploy.yml).
|
||||
|
||||
### Manual Deployment
|
||||
|
||||
Pre-requisites:
|
||||
- [Pulumi CLI installed](https://www.pulumi.com/docs/iac/download-install/)
|
||||
- [Google Cloud SDK installed](https://cloud.google.com/sdk/docs/install)
|
||||
- Access to GCP project and GCS bucket
|
||||
- Required credentials and secrets
|
||||
|
||||
1. Authenticate with GCP: `gcloud auth application-default login`
|
||||
2. Get the passphrase file `passphrase.prod.txt` from the maintainers
|
||||
3. Preview changes: `make preview`
|
||||
4. Deploy changes: `make up`
|
||||
|
||||
## Key Management
|
||||
|
||||
### Required GitHub Secrets (for CI/CD)
|
||||
|
||||
The following secrets must be configured in GitHub Actions for automated deployments:
|
||||
|
||||
- **`GCP_PROD_SERVICE_ACCOUNT_KEY`**: GCP service account key
|
||||
- Used to authenticate with Google Cloud Storage for Pulumi state (`gs://mcp-access-prod-pulumi-state`)
|
||||
- Should be a JSON key file for a service account with Storage Admin permissions
|
||||
- See "Setting Up GCS Backend" below for setup instructions
|
||||
|
||||
- **`PULUMI_PROD_PASSPHRASE`**: Passphrase for encrypting Pulumi state
|
||||
- Used to decrypt encrypted values in Pulumi stack configuration
|
||||
- Keep this secure - if lost, you cannot decrypt your Pulumi state
|
||||
|
||||
## Initial Setup
|
||||
|
||||
If setting up this infrastructure for the first time:
|
||||
|
||||
### 1. Set Up Service Account
|
||||
|
||||
```bash
|
||||
# Create project and enable APIs
|
||||
gcloud projects create mcp-access-prod
|
||||
gcloud config set project mcp-access-prod
|
||||
gcloud services enable storage.googleapis.com
|
||||
gcloud services enable admin.googleapis.com
|
||||
gcloud services enable groupssettings.googleapis.com
|
||||
|
||||
# Create service account
|
||||
gcloud iam service-accounts create pulumi-svc \
|
||||
--display-name="MCP Access Management Service Account" \
|
||||
--description="Service account for Pulumi state and Google Workspace management"
|
||||
|
||||
# Grant storage admin permissions (for Pulumi state)
|
||||
gcloud projects add-iam-policy-binding mcp-access-prod \
|
||||
--member="serviceAccount:pulumi-svc@mcp-access-prod.iam.gserviceaccount.com" \
|
||||
--role="roles/storage.admin"
|
||||
|
||||
# Create key
|
||||
gcloud iam service-accounts keys create sa-key.json \
|
||||
--iam-account=pulumi-svc@mcp-access-prod.iam.gserviceaccount.com
|
||||
|
||||
# Create GCS bucket for Pulumi state
|
||||
gsutil mb gs://mcp-access-prod-pulumi-state
|
||||
```
|
||||
|
||||
Then:
|
||||
1. In Google Workspace Admin Console, go to **Account** → **Admin roles**
|
||||
2. Select **Groups Admin** role (or create a custom role with these privileges):
|
||||
- Read, create, update, and delete groups
|
||||
- Read and update group members
|
||||
3. Click **Assign service accounts**
|
||||
4. Add your service account email: `pulumi-svc@mcp-access-prod.iam.gserviceaccount.com`
|
||||
|
||||
### 2. Initialize Pulumi Stack
|
||||
|
||||
```bash
|
||||
# Login to Pulumi backend (GCS)
|
||||
pulumi login gs://mcp-access-prod-pulumi-state
|
||||
|
||||
# Create production stack
|
||||
export PULUMI_CONFIG_PASSPHRASE_FILE=passphrase.prod.txt
|
||||
pulumi stack init prod
|
||||
|
||||
# Configure application secrets in Pulumi
|
||||
pulumi config set --secret googleworkspace:credentials "$(cat sa-key.json)"
|
||||
pulumi config set --secret github:token "ghp_your_github_token_here"
|
||||
```
|
||||
|
||||
### 3. Configure GitHub Actions Secrets
|
||||
|
||||
Add the CI/CD secrets to GitHub Actions (repository settings → Secrets and variables → Actions):
|
||||
- `GCP_PROD_SERVICE_ACCOUNT_KEY`: Content of `sa-key.json`
|
||||
- `PULUMI_PROD_PASSPHRASE`: The passphrase you set above
|
||||
3456
package-lock.json
generated
Normal file
3456
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
19
package.json
Normal file
19
package.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "@mcp/access",
|
||||
"version": "1.0.0",
|
||||
"description": "Infrastructure as Code for MCP access management",
|
||||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
"build": "tsc"
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@pulumi/github": "^6.7.3",
|
||||
"@pulumi/googleworkspace": "file:sdks/googleworkspace",
|
||||
"@pulumi/pulumi": "^3.197.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.18.6",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
15
src/config/groups.ts
Normal file
15
src/config/groups.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { defineGroups } from './utils';
|
||||
|
||||
// NOTE: For GitHub teams, only the first memberOf will be used as the parent team.
|
||||
// GitHub only supports one parent team per team.
|
||||
export const GROUPS = defineGroups([
|
||||
{
|
||||
name: 'test-parent',
|
||||
description: 'All maintainers. Users should not be added directly to this group.',
|
||||
},
|
||||
{
|
||||
name: 'test-child',
|
||||
description: 'Registry maintainers',
|
||||
memberOf: ['test-parent'],
|
||||
},
|
||||
] as const);
|
||||
9
src/config/users.ts
Normal file
9
src/config/users.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import type { Member } from './utils';
|
||||
|
||||
export const MEMBERS: readonly Member[] = [
|
||||
{
|
||||
github: 'domdomegg',
|
||||
email: 'adam@modelcontextprotocol.io',
|
||||
memberOf: ['test-child'],
|
||||
},
|
||||
] as const;
|
||||
37
src/config/utils.ts
Normal file
37
src/config/utils.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import type { GROUPS } from './groups';
|
||||
|
||||
function isValidGroupName(name: string): boolean {
|
||||
return /^[a-z][a-z0-9-]*[a-z]$/.test(name);
|
||||
}
|
||||
|
||||
export function defineGroups<
|
||||
const T extends readonly {
|
||||
name: Lowercase<string>;
|
||||
description: string;
|
||||
memberOf?: readonly (T[number]['name'])[];
|
||||
}[]
|
||||
>(groups: T) {
|
||||
for (const group of groups) {
|
||||
if (!isValidGroupName(group.name)) {
|
||||
throw new Error(
|
||||
`Invalid group name: ${group.name}. Must be lowercase alphanumeric with dashes, starting with a letter.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
export type GroupKey = (typeof GROUPS)[number]['name'];
|
||||
|
||||
export interface Group {
|
||||
name: GroupKey;
|
||||
description: string;
|
||||
memberOf?: readonly GroupKey[];
|
||||
}
|
||||
|
||||
export interface Member {
|
||||
github?: string;
|
||||
email?: `${string}@modelcontextprotocol.io`;
|
||||
memberOf: readonly GroupKey[];
|
||||
}
|
||||
27
src/github.ts
Normal file
27
src/github.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import * as github from '@pulumi/github';
|
||||
import { GROUPS } from './config/groups';
|
||||
import type { Group } from './config/utils';
|
||||
import { MEMBERS } from './config/users';
|
||||
|
||||
const teams: Record<string, github.Team> = {};
|
||||
|
||||
GROUPS.forEach((group: Group) => {
|
||||
teams[group.name] = new github.Team(group.name, {
|
||||
name: group.name,
|
||||
description: group.description + ' \n(Managed by github.com/modelcontextprotocol/access)',
|
||||
privacy: 'closed',
|
||||
parentTeamId: group.memberOf?.[0] ? teams[group.memberOf[0]].id : undefined,
|
||||
});
|
||||
});
|
||||
|
||||
MEMBERS.forEach((member) => {
|
||||
if (!member.github) return;
|
||||
|
||||
member.memberOf.forEach((teamKey) => {
|
||||
new github.TeamMembership(`${member.github}-${teamKey}`, {
|
||||
teamId: teams[teamKey].id,
|
||||
username: member.github!,
|
||||
role: 'member',
|
||||
});
|
||||
});
|
||||
});
|
||||
45
src/google.ts
Normal file
45
src/google.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import * as gworkspace from '@pulumi/googleworkspace';
|
||||
import { GROUPS } from './config/groups';
|
||||
import type { Group } from './config/utils';
|
||||
import { MEMBERS } from './config/users';
|
||||
|
||||
const groups: Record<string, gworkspace.Group> = {};
|
||||
|
||||
GROUPS.forEach((group: Group) => {
|
||||
groups[group.name] = new gworkspace.Group(group.name, {
|
||||
email: `${group.name}@modelcontextprotocol.io`,
|
||||
name: group.name,
|
||||
description: group.description + ' \n(Managed by github.com/modelcontextprotocol/access)',
|
||||
});
|
||||
|
||||
new gworkspace.GroupSettings(group.name, {
|
||||
email: groups[group.name].email,
|
||||
whoCanContactOwner: 'ALL_OWNERS_CAN_CONTACT',
|
||||
whoCanJoin: 'INVITED_CAN_JOIN',
|
||||
whoCanLeaveGroup: 'NONE_CAN_LEAVE',
|
||||
whoCanModerateMembers: 'OWNERS_ONLY',
|
||||
whoCanPostMessage: 'ALL_OWNERS_CAN_POST',
|
||||
whoCanViewGroup: 'ALL_MEMBERS_CAN_VIEW',
|
||||
whoCanViewMembership: 'ALL_IN_DOMAIN_CAN_VIEW',
|
||||
});
|
||||
|
||||
group.memberOf?.forEach((parentGroupKey) => {
|
||||
new gworkspace.GroupMember(`${group.name}-in-${parentGroupKey}`, {
|
||||
groupId: groups[parentGroupKey].id,
|
||||
email: groups[group.name].email,
|
||||
role: 'MEMBER',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
MEMBERS.forEach((member) => {
|
||||
if (!member.email) return;
|
||||
|
||||
member.memberOf.forEach((teamKey) => {
|
||||
new gworkspace.GroupMember(`${member.email}-${teamKey}`, {
|
||||
groupId: groups[teamKey].id,
|
||||
email: member.email!,
|
||||
role: 'MEMBER',
|
||||
});
|
||||
});
|
||||
});
|
||||
2
src/index.ts
Normal file
2
src/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export * from './github';
|
||||
export * from './google';
|
||||
16
tsconfig.json
Normal file
16
tsconfig.json
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "commonjs",
|
||||
"lib": ["ES2020"],
|
||||
"moduleResolution": "node",
|
||||
"noEmit": true,
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
Loading…
Reference in a new issue