Skip to content

Commit d966233

Browse files
committed
Add Radar MCP server
1 parent 48ca0f2 commit d966233

File tree

18 files changed

+6606
-11
lines changed

18 files changed

+6606
-11
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,6 @@ yarn-error.log*
4848
.sentryclirc.lock/
4949
tmp.json
5050
tmp.ts
51+
.idea
5152

52-
apps/sandbox-container/workdir
53+
apps/sandbox-container/workdir

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ Some features may require a paid Cloudflare Workers plan. Ensure your Cloudflare
5555
### Apps
5656

5757
- [workers-observability](apps/workers-observability/): The Workers Observability MCP server
58+
- [radar](apps/radar/): The Cloudflare Radar MCP server
5859

5960
### Packages
6061

apps/radar/.dev.vars.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
CLOUDFLARE_CLIENT_ID=
2+
CLOUDFLARE_CLIENT_SECRET=
3+
URL_SCANNER_API_TOKEN=

apps/radar/.eslintrc.cjs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/** @type {import("eslint").Linter.Config} */
2+
module.exports = {
3+
root: true,
4+
extends: ['@repo/eslint-config/default.cjs'],
5+
}

apps/radar/README.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Cloudflare Radar MCP Server 📡
2+
3+
This is a [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) server that supports remote MCP
4+
connections, with Cloudflare OAuth built-in.
5+
6+
It integrates tools powered by the [Cloudflare Radar API](https://developers.cloudflare.com/radar/) to provide global
7+
Internet traffic insights, trends and other utilities.
8+
9+
## 🔨 Available Tools
10+
11+
Currently available tools:
12+
13+
| **Category** | **Tool** | **Description** |
14+
| ---------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
15+
| **HTTP Requests** | `get_http_requests_data` | Fetches HTTP request data (timeseries, summaries, and grouped timeseries across dimensions like `deviceType`, `botClass`) |
16+
| **Autonomous Systems** | `list_autonomous_systems` | Lists ASes; filter by location and sort by population size |
17+
| | `get_as_details` | Retrieves detailed info for a specific ASN |
18+
| **IP Addresses** | `get_ip_details` | Provides details about a specific IP address |
19+
| **Traffic Anomalies** | `get_traffic_anomalies` | Lists traffic anomalies; filter by AS, location, start date, and end date |
20+
| **URL Scanner** | `scan_url` | Scans a URL via [Cloudflare’s URL Scanner](https://developers.cloudflare.com/radar/investigate/url-scanner/) |
21+
22+
This MCP server is still a work in progress, and we plan to add more tools in the future.
23+
24+
### Prompt Examples
25+
26+
- `What are the most used operating systems?`
27+
- `What are the top 5 ASes in Portugal?`
28+
- `Get information about ASN 13335.`
29+
- `What are the details of IP address 1.1.1.1?`
30+
- `List me traffic anomalies in Brazil over the last year.`
31+
- `Scan https://example.com.`
32+
33+
## Getting Started
34+
35+
#### Secrets
36+
37+
Set secrets via Wrangler:
38+
39+
```bash
40+
npx wrangler secret put CLOUDFLARE_CLIENT_ID -e <ENVIRONMENT>
41+
npx wrangler secret put CLOUDFLARE_CLIENT_SECRET -e <ENVIRONMENT>
42+
npx wrangler secret put URL_SCANNER_API_TOKEN -e <ENVIRONMENT>
43+
```
44+
45+
#### Set up a KV namespace
46+
47+
Create the KV namespace:
48+
49+
```bash
50+
npx wrangler kv namespace create "OAUTH_KV"
51+
```
52+
53+
Then, update the Wrangler file with the generated KV namespace ID.
54+
55+
#### Deploy & Test
56+
57+
Deploy the MCP server to make it available on your workers.dev domain:
58+
59+
```bash
60+
npx wrangler deploy -e <ENVIRONMENT>
61+
```
62+
63+
Test the remote server using [Inspector](https://modelcontextprotocol.io/docs/tools/inspector):
64+
65+
```
66+
npx @modelcontextprotocol/inspector@latest
67+
```
68+
69+
## Local Development
70+
71+
If you'd like to iterate and test your MCP server, you can do so in local development.
72+
This will require you to create another OAuth App on Cloudflare:
73+
74+
1. Create a `.dev.vars` file in your project root with:
75+
76+
```
77+
CLOUDFLARE_CLIENT_ID=your_development_cloudflare_client_id
78+
CLOUDFLARE_CLIENT_SECRET=your_development_cloudflare_client_secret
79+
URL_SCANNER_API_TOKEN=your_development_url_scanner_api_token
80+
```
81+
82+
2. Start the local development server:
83+
84+
```bash
85+
npx wrangler dev
86+
```
87+
88+
3. To test locally, open Inspector, and connect to `http://localhost:8788/sse`.
89+
Once you follow the prompts, you'll be able to "List Tools".
90+
91+
You can also connect to Claude Desktop.

apps/radar/package.json

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"name": "cloudflare-radar-mcp-server",
3+
"version": "0.0.1",
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+
"cf-typegen": "wrangler types",
12+
"test": "vitest run"
13+
},
14+
"dependencies": {
15+
"@cloudflare/workers-oauth-provider": "0.0.2",
16+
"@hono/zod-validator": "0.4.3",
17+
"@modelcontextprotocol/sdk": "1.9.0",
18+
"@repo/mcp-common": "workspace:*",
19+
"@repo/mcp-observability": "workspace:*",
20+
"agents": "0.0.62",
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+
"@cloudflare/workers-types": "4.20250410.0",
28+
"@types/node": "^22.14.1",
29+
"prettier": "3.5.3",
30+
"typescript": "5.5.4",
31+
"vitest": "3.0.9",
32+
"wrangler": "4.10.0"
33+
}
34+
}

apps/radar/src/index.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import OAuthProvider from '@cloudflare/workers-oauth-provider'
2+
import { McpAgent } from 'agents/mcp'
3+
import { env } from 'cloudflare:workers'
4+
5+
import {
6+
createAuthHandlers,
7+
handleTokenExchangeCallback,
8+
} from '@repo/mcp-common/src/cloudflare-oauth-handler'
9+
import { CloudflareMCPServer } from '@repo/mcp-common/src/server'
10+
import { MetricsTracker } from '@repo/mcp-observability'
11+
12+
import { registerRadarTools } from './tools/radar'
13+
import { registerUrlScannerTools } from './tools/url-scanner'
14+
15+
import type { UserSchema } from '@repo/mcp-common/src/cloudflare-oauth-handler'
16+
17+
const metrics = new MetricsTracker(env.MCP_METRICS, {
18+
name: env.MCP_SERVER_NAME,
19+
version: env.MCP_SERVER_VERSION,
20+
})
21+
22+
// Context from the auth process, encrypted & stored in the auth token
23+
// and provided to the DurableMCP as this.props
24+
export type Props = {
25+
accessToken: string
26+
user: UserSchema['result']
27+
}
28+
29+
export type State = never
30+
31+
export class RadarMCP extends McpAgent<Env, State, Props> {
32+
_server: CloudflareMCPServer | undefined
33+
set server(server: CloudflareMCPServer) {
34+
this._server = server
35+
}
36+
37+
get server(): CloudflareMCPServer {
38+
if (!this._server) {
39+
throw new Error('Tried to access server before it was initialized')
40+
}
41+
42+
return this._server
43+
}
44+
45+
constructor(ctx: DurableObjectState, env: Env) {
46+
super(ctx, env)
47+
}
48+
49+
async init() {
50+
this.server = new CloudflareMCPServer(this.props.user.id, this.env.MCP_METRICS, {
51+
name: this.env.MCP_SERVER_NAME,
52+
version: this.env.MCP_SERVER_VERSION,
53+
})
54+
55+
registerRadarTools(this)
56+
registerUrlScannerTools(this)
57+
}
58+
}
59+
60+
const RadarScopes = {
61+
'user:read': 'See your user info such as name, email address, and account memberships.',
62+
offline_access: 'Grants refresh tokens for long-lived access.',
63+
} as const
64+
65+
export default new OAuthProvider({
66+
apiRoute: '/sse',
67+
// @ts-ignore
68+
apiHandler: RadarMCP.mount('/sse'),
69+
// @ts-ignore
70+
defaultHandler: createAuthHandlers({ scopes: RadarScopes, metrics }),
71+
authorizeEndpoint: '/oauth/authorize',
72+
tokenEndpoint: '/token',
73+
tokenExchangeCallback: (options) =>
74+
handleTokenExchangeCallback(options, env.CLOUDFLARE_CLIENT_ID, env.CLOUDFLARE_CLIENT_SECRET),
75+
// Cloudflare access token TTL
76+
accessTokenTTL: 3600,
77+
clientRegistrationEndpoint: '/register',
78+
})

0 commit comments

Comments
 (0)