-
Notifications
You must be signed in to change notification settings - Fork 288
Add DNS Analytic MCP #121
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Add DNS Analytic MCP #121
Changes from 8 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
da30069
Add dns analytic mcp.
axiapubsub e8e9515
Add zone list call.
axiapubsub 987412b
Format code and update README.
axiapubsub ef8e255
Merge remote-tracking branch 'upstream/main'
axiapubsub 6ce0ffc
Merge branch 'main' into main
Maximo-Guk d9de749
Rebase and prepare for oauth scope.
axiapubsub e7b868c
Update wrangler.jsonc for staging and dev env.
axiapubsub c80b58d
Address comments.
axiapubsub 8ea85a9
Merge branch 'main' into main
Maximo-Guk df0f973
Address comments.
axiapubsub 0900ede
Add TODOs.
axiapubsub File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| CLOUDFLARE_CLIENT_ID= | ||
| CLOUDFLARE_CLIENT_SECRET= | ||
| DEV_CLOUDFLARE_API_TOKEN= |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| /** @type {import("eslint").Linter.Config} */ | ||
| module.exports = { | ||
| root: true, | ||
| extends: ['@repo/eslint-config/default.cjs'], | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| # Cloudflare Radar MCP Server 📡 | ||
|
|
||
| This is a [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) server that supports remote MCP | ||
| connections, with Cloudflare OAuth built-in. | ||
|
|
||
| It integrates tools powered by the [Cloudflare DNS Analytics API](https://developers.cloudflare.com/api/resources/dns/) to provide insights on DNS analytics and optimization. | ||
|
|
||
| ## 🔨 Available Tools | ||
|
|
||
| Currently available tools: | ||
|
|
||
| | **Category** | **Tool** | **Description** | | ||
| | ---------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | ||
| | **DNS Analytics** | `dns-report` | Fetch the DNS Report for a given zone over a given time frame. | | ||
| | **Account DNS Setting**| `show-account-dns-settings`| Fetch the DNS setting for the current active account. | | ||
| | **Zone DNS Setting** | `show-zone-dns-settings` | Fetch the DNS setting for a given zone. | | ||
| | **Zone Information** | `list-zones-under-account`| List zones under the current active account. | | ||
|
|
||
| This MCP server is still a work in progress, and we plan to add more tools in the future. | ||
|
|
||
| ### Prompt Examples | ||
|
|
||
| - `List zones under my Cloudflare account.` | ||
| - `What are the DNS Settings for my account?` | ||
| - `Show me the zones under my account and fetch DNS Report for them.` | ||
| - `How can I optimize my DNS Setting based on my DNS Report?` | ||
| - `Which of my zones has the highest traffic?` | ||
| - `Read Cloudflare's documentation on managing DNS records and tell me how to optimize my DNS settings.` | ||
| - `Show me DNS Report for https://example.com in the last X days.` | ||
|
|
||
| ## Access the remote MCP server from from any MCP Client | ||
|
|
||
| If your MCP client has first class support for remote MCP servers, the client will provide a way to accept the server URL (`https://dns-analytics.mcp.cloudflare.com`) directly within its interface (for example in [Cloudflare AI Playground](https://playground.ai.cloudflare.com/)). | ||
|
|
||
| If your client does not yet support remote MCP servers, you will need to set up its respective configuration file using [mcp-remote](https://www.npmjs.com/package/mcp-remote) to specify which servers your client can access. | ||
|
|
||
| Replace the content with the following configuration: | ||
|
|
||
| ```json | ||
| { | ||
| "mcpServers": { | ||
| "cloudflare": { | ||
| "command": "npx", | ||
| "args": ["mcp-remote", "https://dns-analytics.mcp.cloudflare.com/sse"] | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| Once you've set up your configuration file, restart MCP client and a browser window will open showing your OAuth login page. Proceed through the authentication flow to grant the client access to your MCP server. After you grant access, the tools will become available for you to use. | ||
|
|
||
| Interested in contributing, and running this server locally? See [CONTRIBUTING.md](CONTRIBUTING.md) to get started. | ||
Maximo-Guk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| { | ||
| "name": "dns-analytics", | ||
| "version": "0.0.1", | ||
| "private": true, | ||
| "scripts": { | ||
| "check:lint": "run-eslint-workers", | ||
| "check:types": "run-tsc", | ||
| "deploy": "wrangler deploy", | ||
| "dev": "wrangler dev", | ||
| "start": "wrangler dev", | ||
| "types": "wrangler types --include-env=false", | ||
| "test": "vitest run" | ||
| }, | ||
| "dependencies": { | ||
| "@cloudflare/workers-oauth-provider": "0.0.5", | ||
| "@hono/zod-validator": "0.4.3", | ||
| "@modelcontextprotocol/sdk": "1.10.2", | ||
| "@repo/mcp-common": "workspace:*", | ||
| "@repo/mcp-observability": "workspace:*", | ||
| "agents": "0.0.67", | ||
| "cloudflare": "4.2.0", | ||
| "hono": "4.7.6", | ||
| "zod": "3.24.2" | ||
| }, | ||
| "devDependencies": { | ||
| "@cloudflare/vitest-pool-workers": "0.8.14", | ||
| "@types/node": "22.14.1", | ||
| "prettier": "3.5.3", | ||
| "typescript": "5.5.4", | ||
| "vitest": "3.0.9", | ||
| "wrangler": "4.10.0" | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| import type { UserDetails } from '@repo/mcp-common/src/durable-objects/user_details' | ||
| import type { DNSAnalyticsMCP } from './index' | ||
|
|
||
| export interface Env { | ||
| OAUTH_KV: KVNamespace | ||
| ENVIRONMENT: 'development' | 'staging' | 'production' | ||
| MCP_SERVER_NAME: string | ||
| MCP_SERVER_VERSION: string | ||
| CLOUDFLARE_CLIENT_ID: string | ||
| CLOUDFLARE_CLIENT_SECRET: string | ||
| MCP_OBJECT: DurableObjectNamespace<DNSAnalyticsMCP> | ||
| USER_DETAILS: DurableObjectNamespace<UserDetails> | ||
| MCP_METRICS: AnalyticsEngineDataset | ||
| SENTRY_ACCESS_CLIENT_ID: string | ||
| SENTRY_ACCESS_CLIENT_SECRET: string | ||
| GIT_HASH: string | ||
| SENTRY_DSN: string | ||
| DEV_DISABLE_OAUTH: string | ||
| DEV_CLOUDFLARE_API_TOKEN: string | ||
| DEV_CLOUDFLARE_EMAIL: string | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| import OAuthProvider from '@cloudflare/workers-oauth-provider' | ||
| import { McpAgent } from 'agents/mcp' | ||
|
|
||
| import { | ||
| createAuthHandlers, | ||
| handleTokenExchangeCallback, | ||
| } from '@repo/mcp-common/src/cloudflare-oauth-handler' | ||
| import { CloudflareMCPServer } from '@repo/mcp-common/src/server' | ||
| import { registerAccountTools } from '@repo/mcp-common/src/tools/account' | ||
| import { getEnv } from '@repo/mcp-common/src/env' | ||
| import { RequiredScopes } from '@repo/mcp-common/src/scopes' | ||
| import { handleDevMode } from '@repo/mcp-common/src/dev-mode' | ||
|
|
||
| import { MetricsTracker } from '../../../packages/mcp-observability/src' | ||
| import { registerAnalyticTools } from './tools/analytics' | ||
|
|
||
| import type { AuthProps } from '@repo/mcp-common/src/cloudflare-oauth-handler' | ||
| import type { Env } from './context' | ||
| import { getUserDetails, UserDetails } from '@repo/mcp-common/src/durable-objects/user_details' | ||
|
|
||
| export { UserDetails } | ||
|
|
||
| const env = getEnv<Env>() | ||
|
|
||
| const metrics = new MetricsTracker(env.MCP_METRICS, { | ||
| name: env.MCP_SERVER_NAME, | ||
| version: env.MCP_SERVER_VERSION, | ||
| }) | ||
|
|
||
| // Context from the auth process, encrypted & stored in the auth token | ||
| // and provided to the DurableMCP as this.props | ||
| export type Props = AuthProps | ||
|
|
||
| export type State = { activeAccountId: string | null } | ||
|
|
||
| export class DNSAnalyticsMCP extends McpAgent<Env, State, Props> { | ||
| _server: CloudflareMCPServer | undefined | ||
| set server(server: CloudflareMCPServer) { | ||
| this._server = server | ||
| } | ||
|
|
||
| get server(): CloudflareMCPServer { | ||
| if (!this._server) { | ||
| throw new Error('Tried to access server before it was initialized') | ||
| } | ||
|
|
||
| return this._server | ||
| } | ||
|
|
||
| constructor(ctx: DurableObjectState, env: Env) { | ||
| super(ctx, env) | ||
| } | ||
|
|
||
| async init() { | ||
| this.server = new CloudflareMCPServer({ | ||
| userId: this.props.user.id, | ||
| wae: this.env.MCP_METRICS, | ||
| serverInfo: { | ||
| name: this.env.MCP_SERVER_NAME, | ||
| version: this.env.MCP_SERVER_VERSION, | ||
| }, | ||
| }) | ||
|
|
||
| registerAccountTools(this) | ||
|
|
||
| // Register Cloudflare DNS Analytic tools | ||
| registerAnalyticTools(this) | ||
| } | ||
|
|
||
| async getActiveAccountId() { | ||
| try { | ||
| // Get UserDetails Durable Object based off the userId and retrieve the activeAccountId from it | ||
| // we do this so we can persist activeAccountId across sessions | ||
| const userDetails = getUserDetails(env, this.props.user.id) | ||
| return await userDetails.getActiveAccountId() | ||
| } catch (e) { | ||
| this.server.recordError(e) | ||
| return null | ||
| } | ||
| } | ||
|
|
||
| async setActiveAccountId(accountId: string) { | ||
| try { | ||
| const userDetails = getUserDetails(env, this.props.user.id) | ||
| await userDetails.setActiveAccountId(accountId) | ||
| } catch (e) { | ||
| this.server.recordError(e) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| const AnalyticsScopes = { | ||
| ...RequiredScopes, | ||
| 'account:read': 'See your account info such as account details, analytics, and memberships.', | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe can add todo comment to add dns scopes here so we don't forget
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
| } as const | ||
|
|
||
| export default { | ||
| fetch: async (req: Request, env: Env, ctx: ExecutionContext) => { | ||
| if (env.ENVIRONMENT === 'development' && env.DEV_DISABLE_OAUTH === 'true') { | ||
| return await handleDevMode(DNSAnalyticsMCP, req, env, ctx) | ||
| } | ||
|
|
||
| return new OAuthProvider({ | ||
| apiHandlers: { | ||
| '/mcp': DNSAnalyticsMCP.serve('/mcp'), | ||
| '/sse': DNSAnalyticsMCP.serveSSE('/sse'), | ||
| }, | ||
| // @ts-ignore | ||
| defaultHandler: createAuthHandlers({ scopes: AnalyticsScopes, metrics }), | ||
| authorizeEndpoint: '/oauth/authorize', | ||
| tokenEndpoint: '/token', | ||
| tokenExchangeCallback: (options) => | ||
| handleTokenExchangeCallback( | ||
| options, | ||
| env.CLOUDFLARE_CLIENT_ID, | ||
| env.CLOUDFLARE_CLIENT_SECRET | ||
| ), | ||
| // Cloudflare access token TTL | ||
| accessTokenTTL: 3600, | ||
| clientRegistrationEndpoint: '/register', | ||
| }).fetch(req, env, ctx) | ||
| }, | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.