Skip to content

Commit ecbeee8

Browse files
authored
GraphQL MCP Server (#158)
* GraphQL MCP Server * pnpm updated * updated README.md for graphql MCP server * requested changes updated for GraphQL MCP server * pnpm lock file changed * small bug fix * fixed node version of graphql mcp package * updated README to include graphql MCP server / pnpm update * fixing CI build errors
1 parent bdb5b89 commit ecbeee8

File tree

12 files changed

+7324
-51
lines changed

12 files changed

+7324
-51
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ The following servers are included in this repository:
2222
| [**DNS Analytics server**](/apps/dns-analytics) | Optimize DNS performance and debug issues based on current set up | `https://dns-analytics.mcp.cloudflare.com/sse` |
2323
| [**Digital Experience Monitoring server**](/apps/dex-analysis) | Get quick insight on critical applications for your organization | `https://dex.mcp.cloudflare.com/sse` |
2424
| [**Cloudflare One CASB server**](/apps/cloudflare-one-casb) | Quickly identify any security misconfigurations for SaaS applications to safeguard users & data | `https://casb.mcp.cloudflare.com/sse` |
25+
| [**GraphQL server**](/apps/graphql/) | Get analytics data using Cloudflare’s GraphQL API | `https://graphql.mcp.cloudflare.com/sse` |
2526

2627
## Access the remote MCP server from any MCP client
2728

apps/graphql/.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/graphql/README.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Cloudflare GraphQL 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. It integrates tools powered by the [Cloudflare GraphQL API](https://developers.cloudflare.com/analytics/graphql-api/) to provide insights and utilities for your Cloudflare account.
5+
6+
## Available Tools
7+
8+
Currently available tools:
9+
10+
| **Category** | **Tool** | **Description** |
11+
| --------------------------- | ------------------------- | ----------------------------------------------------------------------------------------------- |
12+
| **GraphQL Schema Search** | `graphql_schema_search` | Search the Cloudflare GraphQL API schema for types, fields, and enum values matching a keyword |
13+
| **GraphQL Schema Overview** | `graphql_schema_overview` | Fetch the high-level overview of the Cloudflare GraphQL API schema |
14+
| **GraphQL Type Details** | `graphql_type_details` | Fetch detailed information about a specific GraphQL type |
15+
| **GraphQL Complete Schema** | `graphql_complete_schema` | Fetch the complete Cloudflare GraphQL API schema (combines overview and important type details) |
16+
| **GraphQL Query Execution** | `graphql_query` | Execute a GraphQL query against the Cloudflare API |
17+
| **GraphQL API Explorer** | `graphql_api_explorer` | Generate a Cloudflare [GraphQL API Explorer](https://graphql.cloudflare.com/explorer) link |
18+
19+
### Prompt Examples
20+
21+
- `Show me HTTP traffic for the last 7 days for example.com`
22+
- `Show me which GraphQL datatype I need to use to query firewall events`
23+
- `Can you generate a link to the Cloudflare GraphQL API Explorer with a pre-populated query and variables?`
24+
- `I need to monitor HTTP requests and responses for a specific domain. Can you help me with that using the Cloudflare GraphQL API?`
25+
26+
## Access the remote MCP server from Claude Desktop
27+
28+
If your MCP client has first class support for remote MCP servers, the client will provide a way to accept the server URL (`https://graphql.mcp.cloudflare.com/sse`) directly within its interface (for example in [Cloudflare AI Playground](https://playground.ai.cloudflare.com/)).
29+
30+
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.
31+
32+
Replace the content with the following configuration:
33+
34+
```json
35+
{
36+
"mcpServers": {
37+
"cloudflare": {
38+
"command": "npx",
39+
"args": ["mcp-remote", "https://graphql.mcp.cloudflare.com/sse"]
40+
}
41+
}
42+
}
43+
```
44+
45+
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.

apps/graphql/package.json

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"name": "graphql-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+
"types": "wrangler types --include-env=false",
12+
"test": "vitest run"
13+
},
14+
"dependencies": {
15+
"@cloudflare/workers-oauth-provider": "0.0.5",
16+
"@hono/zod-validator": "0.4.3",
17+
"@modelcontextprotocol/sdk": "1.10.2",
18+
"@repo/mcp-common": "workspace:*",
19+
"@repo/mcp-observability": "workspace:*",
20+
"agents": "0.0.67",
21+
"cloudflare": "4.2.0",
22+
"hono": "4.7.6",
23+
"zod": "3.24.2",
24+
"lz-string": "1.5.0"
25+
},
26+
"devDependencies": {
27+
"@cloudflare/vitest-pool-workers": "0.8.14",
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/graphql/src/graphql.app.ts

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import OAuthProvider from '@cloudflare/workers-oauth-provider'
2+
import { McpAgent } from 'agents/mcp'
3+
4+
import {
5+
createAuthHandlers,
6+
handleTokenExchangeCallback,
7+
} from '@repo/mcp-common/src/cloudflare-oauth-handler'
8+
import { handleDevMode } from '@repo/mcp-common/src/dev-mode'
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 { RequiredScopes } from '@repo/mcp-common/src/scopes'
12+
import { initSentryWithUser } from '@repo/mcp-common/src/sentry'
13+
import { CloudflareMCPServer } from '@repo/mcp-common/src/server'
14+
import { registerAccountTools } from '@repo/mcp-common/src/tools/account.tools'
15+
import { registerZoneTools } from '@repo/mcp-common/src/tools/zone.tools'
16+
import { MetricsTracker } from '@repo/mcp-observability'
17+
18+
import { registerGraphQLTools } from './tools/graphql.tools'
19+
20+
import type { AuthProps } from '@repo/mcp-common/src/cloudflare-oauth-handler'
21+
import type { Env } from './graphql.context'
22+
23+
export { UserDetails }
24+
25+
const env = getEnv<Env>()
26+
27+
const metrics = new MetricsTracker(env.MCP_METRICS, {
28+
name: env.MCP_SERVER_NAME,
29+
version: env.MCP_SERVER_VERSION,
30+
})
31+
32+
// Context from the auth process, encrypted & stored in the auth token
33+
// and provided to the DurableMCP as this.props
34+
type Props = AuthProps
35+
type State = { activeAccountId: string | null }
36+
37+
export class GraphQLMCP extends McpAgent<Env, State, Props> {
38+
_server: CloudflareMCPServer | undefined
39+
set server(server: CloudflareMCPServer) {
40+
this._server = server
41+
}
42+
43+
get server(): CloudflareMCPServer {
44+
if (!this._server) {
45+
throw new Error('Tried to access server before it was initialized')
46+
}
47+
48+
return this._server
49+
}
50+
51+
constructor(ctx: DurableObjectState, env: Env) {
52+
super(ctx, env)
53+
}
54+
55+
async init() {
56+
this.server = new CloudflareMCPServer({
57+
userId: this.props.user.id,
58+
wae: this.env.MCP_METRICS,
59+
serverInfo: {
60+
name: this.env.MCP_SERVER_NAME,
61+
version: this.env.MCP_SERVER_VERSION,
62+
},
63+
sentry: initSentryWithUser(env, this.ctx, this.props.user.id),
64+
})
65+
66+
// Register account tools
67+
registerAccountTools(this)
68+
69+
// Register zone tools
70+
registerZoneTools(this)
71+
72+
// Register GraphQL tools
73+
registerGraphQLTools(this)
74+
}
75+
76+
async getActiveAccountId() {
77+
try {
78+
// Get UserDetails Durable Object based off the userId and retrieve the activeAccountId from it
79+
// we do this so we can persist activeAccountId across sessions
80+
const userDetails = getUserDetails(env, this.props.user.id)
81+
return await userDetails.getActiveAccountId()
82+
} catch (e) {
83+
this.server.recordError(e)
84+
return null
85+
}
86+
}
87+
88+
async setActiveAccountId(accountId: string) {
89+
try {
90+
const userDetails = getUserDetails(env, this.props.user.id)
91+
await userDetails.setActiveAccountId(accountId)
92+
} catch (e) {
93+
this.server.recordError(e)
94+
}
95+
}
96+
}
97+
98+
const GraphQLScopes = {
99+
...RequiredScopes,
100+
'account:read': 'See your account info such as account details, analytics, and memberships.',
101+
'zone:read': 'See zone data such as settings, analytics, and DNS records.',
102+
} as const
103+
104+
export default {
105+
fetch: async (req: Request, env: Env, ctx: ExecutionContext) => {
106+
if (env.ENVIRONMENT === 'development' && env.DEV_DISABLE_OAUTH === 'true') {
107+
return await handleDevMode(GraphQLMCP, req, env, ctx)
108+
}
109+
110+
return new OAuthProvider({
111+
apiHandlers: {
112+
'/mcp': GraphQLMCP.serve('/mcp'),
113+
'/sse': GraphQLMCP.serveSSE('/sse'),
114+
},
115+
// @ts-ignore
116+
defaultHandler: createAuthHandlers({ scopes: GraphQLScopes, metrics }),
117+
authorizeEndpoint: '/oauth/authorize',
118+
tokenEndpoint: '/token',
119+
tokenExchangeCallback: (options) =>
120+
handleTokenExchangeCallback(
121+
options,
122+
env.CLOUDFLARE_CLIENT_ID,
123+
env.CLOUDFLARE_CLIENT_SECRET
124+
),
125+
// Cloudflare access token TTL
126+
accessTokenTTL: 3600,
127+
clientRegistrationEndpoint: '/register',
128+
}).fetch(req, env, ctx)
129+
},
130+
}

apps/graphql/src/graphql.context.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import type { GraphQLMCP, UserDetails } from './graphql.app'
2+
3+
export interface Env {
4+
OAUTH_KV: KVNamespace
5+
ENVIRONMENT: 'development' | 'staging' | 'production'
6+
MCP_SERVER_NAME: string
7+
MCP_SERVER_VERSION: string
8+
CLOUDFLARE_CLIENT_ID: string
9+
CLOUDFLARE_CLIENT_SECRET: string
10+
MCP_OBJECT: DurableObjectNamespace<GraphQLMCP>
11+
USER_DETAILS: DurableObjectNamespace<UserDetails>
12+
MCP_METRICS: AnalyticsEngineDataset
13+
SENTRY_ACCESS_CLIENT_ID: string
14+
SENTRY_ACCESS_CLIENT_SECRET: string
15+
GIT_HASH: string
16+
SENTRY_DSN: string
17+
DEV_DISABLE_OAUTH: string
18+
DEV_CLOUDFLARE_API_TOKEN: string
19+
DEV_CLOUDFLARE_EMAIL: string
20+
}

0 commit comments

Comments
 (0)