Skip to main content
Glama
README.md
# cloudflare-mcp

Cloudflare MCP server built from a reusable MCP architecture with:
- Vault-backed persistent multi-user Cloudflare credentials
- Postgres-backed persistent per-user configuration
- MCP admin key protection for mutating tools
- Dedicated Cloudflare tools plus full API coverage through a generic request tool
- Query suggestion and schema discovery tools for safer LLM workflows

This solution uses Vault-backed multi-user Cloudflare credentials and Postgres-backed per-user configuration as first-class persistence layers.

## Solution Guarantees

- All user credentials are multi-user and scoped by user ID in Vault.
- All secrets are persisted in Vault.
- All non-secret configuration is persisted in Postgres.
- Mutating tools support admin-gate enforcement via `MCP_ADMIN_AUTH_KEY`.
- API coverage is full through `cloudflare_api_request` (`method + path + query + body`) against Cloudflare API v4.

## Architecture

Runtime flow:
1. `src/index.js` (stdio) or `src/http/index.js` (HTTP) loads env, Postgres, Vault.
2. `src/config/env.js` validates Cloudflare, Vault, Postgres, and transport config.
3. `src/services/configStore.js` persists user config in Postgres table `<app>_config`.
4. `src/services/vault.js` persists user secrets and HTTP auth token indexes in Vault KV.
5. `src/services/targetService.js` is the Cloudflare client with Vault token resolution and API calls.
6. `src/mcp/server.js` registers Cloudflare MCP tools and wraps auth/error handling.
7. `src/http/server.js` exposes MCP over HTTP with auth, rate limit, allowlists, and access logs.

## Data Model

Vault per-user Cloudflare token path:
- `<APP_NAME>/users/<normalized-user-id>/cloudflare/tokens`

Vault token payload shape:
```json
{
  "userId": "team-a",
  "activeTokenId": "primary",
  "tokens": {
    "primary": {
      "tokenId": "primary",
      "tokenType": "api_token",
      "apiToken": "<secret>",
      "active": true,
      "notes": "prod",
      "createdAt": "2026-07-29T00:00:00.000Z",
      "updatedAt": "2026-07-29T00:00:00.000Z"
    }
  }
}
```

Postgres config scope:
- Table: `<APP_NAME>_config`
- PK: `(user_id, key)`
- Common keys:
  - `cloudflare.default.accountId`
  - `cloudflare.default.zoneId`
  - `cloudflare.default.tokenId`

## Environment

Copy `.env.example` to `.env` and set values.

Required runtime groups:
- Postgres: `POSTGRES_HOST`, `POSTGRES_PORT`, `POSTGRES_DB`, `POSTGRES_USER`, `POSTGRES_PASSWORD`
- Vault: `VAULT_ADDR`, `VAULT_TOKEN`, `VAULT_KV_MOUNT` (+ optional agent config)
- Cloudflare: `CLOUDFLARE_API_BASE_URL`, `CLOUDFLARE_API_TIMEOUT_MS`, `CLOUDFLARE_API_DOCS_INDEX_URL`
- MCP auth gate: `MCP_ADMIN_AUTH_KEY` (optional but recommended)

## Run

Install and run stdio:
```bash
npm ci
npm run start:stdio
```

Run HTTP transport:
```bash
npm run start:http
```

Run both:
```bash
npm run start:both
```

Run tests:
```bash
npm test
```

## Docker

Local full stack (app + Postgres + Vault):
```bash
docker compose up -d
```

External Services Mode (app only, external Vault/Postgres):
```bash
docker compose -f docker-compose.external.yml up -d
```

## Tool Catalog

Common response envelope (success):
```json
{
  "ok": true,
  "status": 200,
  "data": {}
}
```

Common error envelope (`isError=true`):
```json
{
  "ok": false,
  "status": 401,
  "error": "Unauthorized: invalid authorizationKey for mutating operation"
}
```

### cloudflare_connection_info

- Use when: you need runtime metadata and auth/scope state.
- Do not use when: you need actual Cloudflare resource data.
- Classification: read-only, low risk.
- Required permissions: none.
- Environment behavior: returns current app/user scope defaults and service connection info.
- Parameters: none.
- Response shape: `{ server, service }`.
- Common failures: unexpected runtime metadata serialization errors.
- Prerequisite tools: none.
- Follow-up tools: `cloudflare_scope_info`, `cloudflare_verify_token`.
- Example:
```json
{"name":"cloudflare_connection_info","arguments":{}}
```

### cloudflare_scope_info

- Use when: you need exact Vault/Postgres scope details for a user.
- Do not use when: you only need API data.
- Classification: read-only, low risk.
- Required permissions: none.
- Environment behavior: defaults `userId` to `MCP_CONFIG_DEFAULT_USER_ID`.
- Parameters:
  - `userId?: string`
- Response shape: `{ appName, userId, postgres, vault }`.
- Common failures: normalization/path construction errors.
- Prerequisite tools: `cloudflare_connection_info`.
- Follow-up tools: token/config tools.
- Example:
```json
{"name":"cloudflare_scope_info","arguments":{"userId":"team-a"}}
```

### cloudflare_schema_discovery

- Use when: you need API operation discovery and docs links.
- Do not use when: path/method are already known.
- Classification: read-only, low risk.
- Required permissions: outbound network to docs URL.
- Environment behavior: loads from `CLOUDFLARE_API_DOCS_INDEX_URL`, with caching.
- Parameters:
  - `categoryFilter?: string`
  - `actionFilter?: string`
  - `limit?: integer(1..1000)`
  - `forceRefresh?: boolean`
- Response shape: `{ totalOperations, filteredOperations, operations[] }`.
- Common failures: docs fetch unavailable; markdown structure changes.
- Prerequisite tools: `cloudflare_connection_info`.
- Follow-up tools: `cloudflare_query_suggestion`, `cloudflare_api_request`.
- Safety warnings: discovery only, no mutations.
- Example:
```json
{"name":"cloudflare_schema_discovery","arguments":{"categoryFilter":"Zones","limit":25}}
```

### cloudflare_query_suggestion

- Use when: you want tool sequencing and safer invocation hints.
- Do not use when: you are already executing a known workflow.
- Classification: advisory/read-only, low risk.
- Required permissions: none.
- Environment behavior: infers mutating behavior from method/operation type and reports admin-key requirement.
- Parameters:
  - `intent?: string`
  - `operationType?: discover|read|mutate|token|config|dns`
  - `method?: string`
  - `path?: string`
  - `includeExamples?: boolean`
  - `includeToolSchemas?: boolean`
- Response shape: `{ summary, recommendedOrder, safetyChecks, toolSchemas?, examples? }`.
- Common failures: recommendation logic failures.
- Prerequisite tools: none.
- Follow-up tools: whichever sequence is returned.
- Example:
```json
{"name":"cloudflare_query_suggestion","arguments":{"intent":"rotate token and patch dns","operationType":"mutate"}}
```

### cloudflare_token_list

- Use when: you need token metadata for a user.
- Do not use when: you need to create/rotate credentials.
- Classification: read-only, medium risk (metadata exposure).
- Required permissions: none.
- Environment behavior: reads user-scoped Vault path.
- Parameters:
  - `userId?: string`
- Response shape: `{ userId, activeTokenId, tokens[] }` (secret values redacted).
- Common failures: Vault read errors.
- Prerequisite tools: `cloudflare_scope_info`.
- Follow-up tools: `cloudflare_token_set_active`, `cloudflare_verify_token`.
- Example:
```json
{"name":"cloudflare_token_list","arguments":{"userId":"default"}}
```

### cloudflare_token_upsert

- Use when: you need to create or rotate Cloudflare credentials for a user.
- Do not use when: you only need metadata read.
- Classification: mutating, high risk.
- Required permissions/prerequisites:
  - If `MCP_ADMIN_AUTH_KEY` is set, `authorizationKey` must match.
  - `tokenType=api_token`: requires `apiToken`.
  - `tokenType=global_api_key`: requires `apiKey` and `email`.
- Environment behavior: writes to user-scoped Vault path; optional active-token switch.
- Parameters:
  - `userId?: string`
  - `tokenId?: string` (defaults to `primary`)
  - `tokenType?: api_token|global_api_key`
  - `apiToken?: string`
  - `apiKey?: string`
  - `email?: string`
  - `setActive?: boolean` (default `true`)
  - `notes?: string`
  - `authorizationKey?: string`
- Response shape: `{ userId, activeTokenId, token(metadata) }`.
- Common failures: admin auth mismatch, missing required token fields, Vault write failures.
- Prerequisite tools: `cloudflare_scope_info`.
- Follow-up tools: `cloudflare_token_list`, `cloudflare_verify_token`.
- Safety warnings: changes live credentials for API execution.
- Example:
```json
{
  "name":"cloudflare_token_upsert",
  "arguments":{
    "userId":"team-a",
    "tokenId":"primary",
    "tokenType":"api_token",
    "apiToken":"<token>",
    "setActive":true,
    "authorizationKey":"<admin-key-if-required>"
  }
}
```

### cloudflare_token_set_active

- Use when: multiple tokens exist and you need to switch active token.
- Do not use when: token is missing; create it first.
- Classification: mutating, high risk.
- Required permissions: admin key when configured.
- Parameters:
  - `userId?: string`
  - `tokenId: string`
  - `authorizationKey?: string`
- Response shape: `{ userId, activeTokenId }`.
- Common failures: token not found, Vault write failure, auth mismatch.

### cloudflare_token_delete

- Use when: decommissioning a user token.
- Do not use when: uncertain if token is in active use.
- Classification: mutating, high risk.
- Required permissions: admin key when configured.
- Parameters:
  - `userId?: string`
  - `tokenId: string`
  - `authorizationKey?: string`
- Response shape: `{ deleted, userId, tokenId, activeTokenId? }`.
- Common failures: Vault write failure; auth mismatch.
- Safety warnings: irreversible token removal.

### cloudflare_config_list

- Use when: you need all or prefixed config rows in Postgres.
- Do not use when: you need one key only.
- Classification: read-only, low risk.
- Required permissions: none.
- Parameters:
  - `userId?: string`
  - `prefix?: string`
- Response shape: `{ userId, configs[] }`.

### cloudflare_config_get

- Use when: you need one config value.
- Do not use when: you need bulk config listing.
- Classification: read-only, low risk.
- Required permissions: none.
- Parameters:
  - `userId?: string`
  - `key: string`
- Response shape: `row|null`.

### cloudflare_config_set

- Use when: setting default account/zone/token or custom config.
- Do not use when: no persisted config change is needed.
- Classification: mutating, medium risk.
- Required permissions: admin key when configured.
- Parameters:
  - `userId?: string`
  - `key: string`
  - `value: any JSON`
  - `authorizationKey?: string`
- Response shape: upserted row.

### cloudflare_config_delete

- Use when: removing obsolete config keys.
- Do not use when: key may still be required for production defaults.
- Classification: mutating, medium risk.
- Required permissions: admin key when configured.
- Parameters:
  - `userId?: string`
  - `key: string`
  - `authorizationKey?: string`
- Response shape: `{ deleted: boolean }`.

### cloudflare_verify_token

- Use when: validate selected credential health before operations.
- Do not use when: token validity is already confirmed in current workflow.
- Classification: read-only, low risk.
- Required permissions: valid Cloudflare token in Vault for selected scope.
- Parameters:
  - `userId?: string`
  - `tokenId?: string`
- Response shape: proxied `/user/tokens/verify` payload.
- Common failures: missing/invalid token, Cloudflare auth errors.

### cloudflare_list_accounts

- Use when: discover accessible accounts.
- Do not use when: account ID already known and not needed.
- Classification: read-only, low risk.
- Parameters:
  - `userId?: string`
  - `tokenId?: string`
  - `page?: integer`
  - `perPage?: integer`

### cloudflare_list_zones

- Use when: discover zones, optionally per account/name.
- Do not use when: exact zone ID already known and no listing required.
- Classification: read-only, low risk.
- Parameters:
  - `userId?: string`
  - `tokenId?: string`
  - `accountId?: string`
  - `name?: string`
  - `page?: integer`
  - `perPage?: integer`

### cloudflare_list_dns_records

- Use when: review DNS records before mutating operations.
- Do not use when: only direct create/update/delete is needed and target IDs are verified.
- Classification: read-only, medium risk.
- Parameters:
  - `userId?: string`
  - `tokenId?: string`
  - `zoneId?: string` (falls back to `cloudflare.default.zoneId`)
  - `type?: string`
  - `name?: string`
  - `page?: integer`
  - `perPage?: integer`

### cloudflare_create_dns_record

- Use when: adding a DNS record.
- Do not use when: uncertain about current zone state; list first.
- Classification: mutating, high risk.
- Required permissions: admin key when configured.
- Parameters:
  - `userId?: string`
  - `tokenId?: string`
  - `zoneId?: string`
  - `record: object` (Cloudflare DNS record create payload)
  - `authorizationKey?: string`
- Safety warnings: writes production DNS.

### cloudflare_update_dns_record

- Use when: patching/replacing an existing record.
- Do not use when: record ID is not validated.
- Classification: mutating, high risk.
- Required permissions: admin key when configured.
- Parameters:
  - `userId?: string`
  - `tokenId?: string`
  - `zoneId?: string`
  - `dnsRecordId: string`
  - `record: object`
  - `replace?: boolean` (`false` = PATCH, `true` = PUT)
  - `authorizationKey?: string`

### cloudflare_delete_dns_record

- Use when: deleting DNS records intentionally.
- Do not use when: rollback plan is missing.
- Classification: mutating, high risk.
- Required permissions: admin key when configured.
- Parameters:
  - `userId?: string`
  - `tokenId?: string`
  - `zoneId?: string`
  - `dnsRecordId: string`
  - `authorizationKey?: string`
- Safety warnings: destructive and irreversible.

### cloudflare_api_request

- Use when: any Cloudflare endpoint access is needed, including APIs without dedicated tools.
- Do not use when: dedicated tools already cover the operation and are safer.
- Classification: read-only or mutating depending on `method`; variable risk.
- Required permissions/prerequisites:
  - Active user-scoped Vault token.
  - If mutating (`POST|PUT|PATCH|DELETE`) and `MCP_ADMIN_AUTH_KEY` is set, `authorizationKey` is required.
- Environment behavior:
  - Normalizes path to Cloudflare v4 route under `/client/v4`.
  - Resolves default token from user config and active token state.
- Parameters:
  - `method: string`
  - `path: string`
  - `query?: object<string, string|number|boolean>`
  - `body?: json`
  - `headers?: object<string,string>`
  - `userId?: string`
  - `tokenId?: string`
  - `authorizationKey?: string`
- Expected response shape:
  - `{ method, path, url, status, tokenId, userId, data }`
- Common failure conditions:
  - Missing token in Vault
  - Invalid Cloudflare credentials
  - Cloudflare API 4xx/5xx
  - Admin auth key mismatch for mutating operations
- Recommended prerequisite tools:
  - `cloudflare_verify_token`
  - `cloudflare_schema_discovery`
- Recommended follow-up tools:
  - `cloudflare_config_set` (to persist defaults)
  - `cloudflare_query_suggestion` (for workflow refinement)
- Safety warnings:
  - Mutating calls can alter/delete production resources.
- Short valid invocation examples:
```json
{"name":"cloudflare_api_request","arguments":{"method":"GET","path":"/zones","userId":"default"}}
```
```json
{"name":"cloudflare_api_request","arguments":{"method":"PATCH","path":"/zones/<zone>/settings/cache_level","body":{"value":"aggressive"},"authorizationKey":"<admin-key-if-required>"}}
```

## Suggested Playbooks

1. Bootstrap new user credentials
   - `cloudflare_scope_info`
   - `cloudflare_token_upsert`
   - `cloudflare_verify_token`
2. Configure user defaults
   - `cloudflare_config_set` (`cloudflare.default.accountId`)
   - `cloudflare_config_set` (`cloudflare.default.zoneId`)
   - `cloudflare_config_set` (`cloudflare.default.tokenId`)
3. Safe DNS mutation
   - `cloudflare_list_dns_records`
   - `cloudflare_create_dns_record` or `cloudflare_update_dns_record` or `cloudflare_delete_dns_record`
4. Full API call flow
   - `cloudflare_schema_discovery`
   - `cloudflare_query_suggestion`
   - `cloudflare_api_request`

## License

MIT. See `LICENSE`.