Skip to content

Commit 92238ac

Browse files
committed
feat: add Cloudflare IAM MCP server
1 parent 162239f commit 92238ac

12 files changed

Lines changed: 8133 additions & 0 deletions

apps/iam/.dev.vars.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
ENVIRONMENT=development
2+
DEV_DISABLE_OAUTH=true
3+
DEV_CLOUDFLARE_API_TOKEN=your_token
4+
DEV_CLOUDFLARE_EMAIL=your_email@cloudflare.com

apps/iam/README.md

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# Cloudflare Identity Management MCP Server 🔐
2+
3+
A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) server that enables external customers to manage Cloudflare identity and access through natural language prompts.
4+
5+
## Features
6+
7+
This MCP server provides tools for managing:
8+
9+
- **API Tokens**: Create, update, delete, roll, and verify API tokens
10+
- **Account Members**: Add, remove, and manage account member access
11+
- **Roles**: View available roles and their permissions
12+
13+
## 🔨 Available Tools
14+
15+
### API Token Management
16+
17+
| Tool | Description |
18+
| ---------------------------- | ------------------------------------------------- |
19+
| `api_token_list` | List all API tokens for the authenticated user |
20+
| `api_token_get` | Get detailed information about a specific token |
21+
| `api_token_create` | Create a new API token with specified permissions |
22+
| `api_token_update` | Update an existing token's settings |
23+
| `api_token_delete` | Delete an API token permanently |
24+
| `api_token_roll` | Rotate a token's secret value |
25+
| `api_token_verify` | Verify the current token's validity |
26+
| `api_permission_groups_list` | List available permission groups |
27+
28+
### Account Member Management
29+
30+
| Tool | Description |
31+
| ----------------------- | --------------------------------------- |
32+
| `account_members_list` | List all members of an account |
33+
| `account_member_get` | Get detailed information about a member |
34+
| `account_member_add` | Invite a new member to the account |
35+
| `account_member_update` | Update a member's roles or status |
36+
| `account_member_remove` | Remove a member from the account |
37+
38+
### Role Management
39+
40+
| Tool | Description |
41+
| -------------------- | ---------------------------------------------- |
42+
| `account_roles_list` | List all available roles for the account |
43+
| `account_role_get` | Get detailed information about a specific role |
44+
45+
## Access the MCP Server
46+
47+
### From MCP Clients
48+
49+
If your MCP client supports remote MCP servers, use this URL:
50+
51+
```
52+
https://iam.mcp.cloudflare.com/mcp
53+
```
54+
55+
### Using mcp-remote
56+
57+
For clients that don't support remote servers natively:
58+
59+
```json
60+
{
61+
"mcpServers": {
62+
"cloudflare-identity": {
63+
"command": "npx",
64+
"args": ["mcp-remote", "https://iam.mcp.cloudflare.com/mcp"]
65+
}
66+
}
67+
}
68+
```
69+
70+
## Prompt Examples
71+
72+
- "Show me all my API tokens"
73+
- "Create a new token with DNS edit permissions for zone example.com"
74+
- "When does my current token expire?"
75+
- "Who has access to my account?"
76+
- "Invite user@example.com as an Administrator"
77+
- "Remove john@example.com from the account"
78+
- "What roles are available in my account?"
79+
- "What permissions does the Administrator role have?"
80+
81+
## Development
82+
83+
### Prerequisites
84+
85+
- Node.js 18+
86+
- pnpm
87+
88+
### Setup
89+
90+
```bash
91+
# Install dependencies
92+
pnpm install
93+
94+
# Generate types
95+
pnpm types
96+
97+
# Run tests
98+
pnpm test
99+
100+
# Start development server
101+
pnpm dev
102+
```
103+
104+
### Environment Variables
105+
106+
Create a `.dev.vars` file for local development:
107+
108+
```
109+
ENVIRONMENT=development
110+
DEV_DISABLE_OAUTH=true
111+
DEV_CLOUDFLARE_API_TOKEN=your_token_here
112+
DEV_CLOUDFLARE_EMAIL=your_email@example.com
113+
```
114+
115+
## Architecture
116+
117+
This server follows the simplified MCP server architecture (like `apps/auditlogs`):
118+
119+
- **Main App** (`iam.app.ts`): MCP Agent class with OAuth handling
120+
- **Tools** (`tools/iam.tools.ts`): All tools, schemas, and API functions in one file
121+
122+
## File Structure
123+
124+
```
125+
src/
126+
├── iam.app.ts # Main MCP Agent class
127+
├── iam.context.ts # Environment types
128+
└── tools/
129+
└── iam.tools.ts # All 13 tools, schemas, and API functions
130+
```
131+
132+
## Contributing
133+
134+
Contributions are welcome! Please follow the existing code patterns and add tests for new functionality.
135+
136+
## License
137+
138+
Apache-2.0

apps/iam/package.json

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"name": "iam",
3+
"version": "1.0.0",
4+
"private": true,
5+
"scripts": {
6+
"check:lint": "run-eslint-workers",
7+
"check:types": "run-tsc",
8+
"deploy": "run-wrangler-deploy",
9+
"dev": "wrangler dev",
10+
"start": "wrangler dev",
11+
"types": "wrangler types --include-env=false",
12+
"test": "vitest run"
13+
},
14+
"dependencies": {
15+
"@cloudflare/workers-oauth-provider": "0.4.0",
16+
"@hono/zod-validator": "0.4.3",
17+
"@modelcontextprotocol/sdk": "1.20.2",
18+
"@repo/mcp-common": "workspace:*",
19+
"@repo/mcp-observability": "workspace:*",
20+
"agents": "0.2.19",
21+
"cloudflare": "4.2.0",
22+
"hono": "4.7.6",
23+
"zod": "3.24.2"
24+
},
25+
"devDependencies": {
26+
"@cloudflare/vitest-pool-workers": "0.8.14",
27+
"@types/node": "22.14.1",
28+
"prettier": "3.5.3",
29+
"typescript": "5.5.4",
30+
"vitest": "3.0.9",
31+
"wrangler": "4.10.0"
32+
}
33+
}

apps/iam/src/iam.app.ts

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import OAuthProvider from '@cloudflare/workers-oauth-provider'
2+
import { McpAgent } from 'agents/mcp'
3+
4+
import { handleApiTokenMode, isApiTokenRequest } from '@repo/mcp-common/src/api-token-mode'
5+
import {
6+
createAuthHandlers,
7+
handleTokenExchangeCallback,
8+
} from '@repo/mcp-common/src/cloudflare-oauth-handler'
9+
import { getUserDetails, UserDetails } from '@repo/mcp-common/src/durable-objects/user_details.do'
10+
import { getEnv } from '@repo/mcp-common/src/env'
11+
import { getProps } from '@repo/mcp-common/src/get-props'
12+
import { RequiredScopes } from '@repo/mcp-common/src/scopes'
13+
import { CloudflareMCPServer } from '@repo/mcp-common/src/server'
14+
import { registerAccountTools } from '@repo/mcp-common/src/tools/account.tools'
15+
16+
import { MetricsTracker } from '../../../packages/mcp-observability/src'
17+
import { registerIAMTools } from './tools/iam.tools'
18+
19+
import type { AuthProps } from '@repo/mcp-common/src/cloudflare-oauth-handler'
20+
import type { Env } from './iam.context'
21+
22+
const env = getEnv<Env>()
23+
24+
export { UserDetails }
25+
26+
const metrics = new MetricsTracker(env.MCP_METRICS, {
27+
name: env.MCP_SERVER_NAME,
28+
version: env.MCP_SERVER_VERSION,
29+
})
30+
31+
// Context from the auth process, encrypted & stored in the auth token
32+
// and provided to the DurableMCP as this.props
33+
type Props = AuthProps
34+
35+
export type State = { activeAccountId: string | null }
36+
37+
export class IAMMCP extends McpAgent<Env, State, Props> {
38+
_server: CloudflareMCPServer | undefined
39+
set server(server: CloudflareMCPServer) {
40+
this._server = server
41+
}
42+
get server(): CloudflareMCPServer {
43+
if (!this._server) {
44+
throw new Error('Tried to access server before it was initialized')
45+
}
46+
47+
return this._server
48+
}
49+
50+
constructor(ctx: DurableObjectState, env: Env) {
51+
super(ctx, env)
52+
}
53+
54+
async init() {
55+
// TODO: Probably we'll want to track account tokens usage through an account identifier at some point
56+
const props = getProps(this)
57+
const userId = props.type === 'user_token' ? props.user.id : undefined
58+
59+
this.server = new CloudflareMCPServer({
60+
userId,
61+
wae: this.env.MCP_METRICS,
62+
serverInfo: {
63+
name: this.env.MCP_SERVER_NAME,
64+
version: this.env.MCP_SERVER_VERSION,
65+
},
66+
})
67+
registerAccountTools(this)
68+
69+
// Register IAM tools
70+
registerIAMTools(this)
71+
}
72+
73+
async getActiveAccountId() {
74+
try {
75+
const props = getProps(this)
76+
// account tokens are scoped to one account
77+
if (props.type === 'account_token') {
78+
return props.account.id
79+
}
80+
// Get UserDetails Durable Object based off the userId and retrieve the activeAccountId from it
81+
// we do this so we can persist activeAccountId across sessions
82+
const userDetails = getUserDetails(env, props.user.id)
83+
return await userDetails.getActiveAccountId()
84+
} catch (e) {
85+
this.server.recordError(e)
86+
return null
87+
}
88+
}
89+
90+
async setActiveAccountId(accountId: string) {
91+
try {
92+
const props = getProps(this)
93+
// account tokens are scoped to one account
94+
if (props.type === 'account_token') {
95+
return
96+
}
97+
const userDetails = getUserDetails(env, props.user.id)
98+
await userDetails.setActiveAccountId(accountId)
99+
} catch (e) {
100+
this.server.recordError(e)
101+
}
102+
}
103+
}
104+
105+
const IdentityManagementScopes = {
106+
...RequiredScopes,
107+
// User-level scopes for token management
108+
'user:token:read': 'See your API tokens and their permissions.',
109+
'user:token:write': 'Create, edit, and delete your API tokens.',
110+
// Account-level scopes for member management
111+
'account:read': 'See your account info such as account details, analytics, and memberships.',
112+
'account:member:read': 'See account members and their roles.',
113+
'account:member:write': 'Add, update, and remove account members.',
114+
'account:role:read': 'See available roles and permissions for your account.',
115+
} as const
116+
117+
export default {
118+
fetch: async (req: Request, env: Env, ctx: ExecutionContext) => {
119+
if (await isApiTokenRequest(req, env)) {
120+
return await handleApiTokenMode(IAMMCP, req, env, ctx)
121+
}
122+
123+
return new OAuthProvider({
124+
apiHandlers: {
125+
'/mcp': IAMMCP.serve('/mcp'),
126+
'/sse': IAMMCP.serveSSE('/sse'),
127+
},
128+
defaultHandler: createAuthHandlers({ scopes: IdentityManagementScopes, metrics }),
129+
authorizeEndpoint: '/oauth/authorize',
130+
tokenEndpoint: '/token',
131+
tokenExchangeCallback: (options) =>
132+
handleTokenExchangeCallback(
133+
options,
134+
env.CLOUDFLARE_CLIENT_ID,
135+
env.CLOUDFLARE_CLIENT_SECRET
136+
),
137+
// Cloudflare access token TTL
138+
accessTokenTTL: 3600,
139+
refreshTokenTTL: 2592000, // 30 days
140+
// TODO: Remove after 2026-05-01 — all pre-0.4.0 grants will have expired by then
141+
resourceMatchOriginOnly: true,
142+
clientRegistrationEndpoint: '/register',
143+
}).fetch(req, env, ctx)
144+
},
145+
}

apps/iam/src/iam.context.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type { UserDetails } from '@repo/mcp-common/src/durable-objects/user_details.do'
2+
import type { IAMMCP } from './iam.app'
3+
4+
export interface Env {
5+
OAUTH_KV: KVNamespace
6+
MCP_COOKIE_ENCRYPTION_KEY: string
7+
ENVIRONMENT: 'development' | 'staging' | 'production'
8+
MCP_SERVER_NAME: string
9+
MCP_SERVER_VERSION: string
10+
CLOUDFLARE_ACCESS_TOKEN: string
11+
CLOUDFLARE_CLIENT_ID: string
12+
CLOUDFLARE_CLIENT_SECRET: string
13+
MCP_OBJECT: DurableObjectNamespace<IAMMCP>
14+
USER_DETAILS: DurableObjectNamespace<UserDetails>
15+
MCP_METRICS: AnalyticsEngineDataset
16+
DEV_DISABLE_OAUTH: string
17+
DEV_CLOUDFLARE_API_TOKEN: string
18+
DEV_CLOUDFLARE_EMAIL: string
19+
}

0 commit comments

Comments
 (0)