-
Notifications
You must be signed in to change notification settings - Fork 289
GraphQL MCP Server #158
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
Merged
Merged
GraphQL MCP Server #158
Changes from 3 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
4a97d22
GraphQL MCP Server
cf-ypark 01871be
pnpm updated
cf-ypark 21da6e0
updated README.md for graphql MCP server
cf-ypark 27354a9
Merge branch 'cloudflare:main' into graphql-mcp
cf-ypark 2851756
requested changes updated for GraphQL MCP server
cf-ypark 8a61d3f
pnpm lock file changed
cf-ypark 4e05b6e
small bug fix
cf-ypark 887bc5c
fixed node version of graphql mcp package
cf-ypark 04453ee
updated README to include graphql MCP server / pnpm update
cf-ypark d5f41ee
fixing CI build errors
cf-ypark 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,45 @@ | ||
| # Cloudflare GraphQL 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 GraphQL API](https://developers.cloudflare.com/analytics/graphql-api/) to provide insights and utilities for your Cloudflare account. | ||
|
|
||
| ## Available Tools | ||
|
|
||
| Currently available tools: | ||
|
|
||
| | **Category** | **Tool** | **Description** | | ||
| | ---------------------- | ------------------------------- | -------------------------------------------------------------------------------------------------------------------- | | ||
| | **GraphQL Schema Search** | `graphql_schema_search` | Search the Cloudflare GraphQL API schema for types, fields, and enum values matching a keyword | | ||
| | **GraphQL Schema Overview** | `graphql_schema_overview` | Fetch the high-level overview of the Cloudflare GraphQL API schema | | ||
| | **GraphQL Type Details** | `graphql_type_details` | Fetch detailed information about a specific GraphQL type | | ||
| | **GraphQL Complete Schema** | `graphql_complete_schema` | Fetch the complete Cloudflare GraphQL API schema (combines overview and important type details)| | ||
| | **GraphQL Query Execution** | `graphql_query` | Execute a GraphQL query against the Cloudflare API | | ||
| | **GraphQL API Explorer** | `graphql_api_explorer` | Generate a Cloudflare [GraphQL API Explorer](https://graphql.cloudflare.com/explorer) link | | ||
|
|
||
| ### Prompt Examples | ||
|
|
||
| - `Show me HTTP traffic for the last 7 days for example.com` | ||
| - `Show me which GraphQL datatype I need to use to query firewall events` | ||
| - `Can you generate a link to the Cloudflare GraphQL API Explorer with a pre-populated query and variables?` | ||
| - `I need to monitor HTTP requests and responses for a specific domain. Can you help me with that using the Cloudflare GraphQL API?` | ||
|
|
||
| ## Access the remote MCP server from Claude Desktop | ||
|
|
||
| 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/)). | ||
|
|
||
| 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://graphql.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. |
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,35 @@ | ||
| { | ||
| "name": "graphql", | ||
| "version": "0.0.0", | ||
| "private": true, | ||
| "scripts": { | ||
| "check:lint": "run-eslint-workers", | ||
| "check:types": "run-tsc", | ||
| "deploy": "wrangler deploy", | ||
| "dev": "wrangler dev", | ||
| "start": "wrangler dev", | ||
| "cf-typegen": "wrangler types", | ||
| "test": "vitest run" | ||
| }, | ||
| "dependencies": { | ||
| "@cloudflare/workers-oauth-provider": "0.0.3", | ||
cf-ypark marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| "@hono/zod-validator": "0.4.3", | ||
| "@modelcontextprotocol/sdk": "1.9.0", | ||
| "@repo/mcp-common": "workspace:*", | ||
| "@repo/mcp-observability": "workspace:*", | ||
| "agents": "0.0.62", | ||
| "cloudflare": "4.2.0", | ||
| "hono": "4.7.6", | ||
| "zod": "3.24.2", | ||
| "lz-string": "1.5.0" | ||
| }, | ||
| "devDependencies": { | ||
| "@cloudflare/vitest-pool-workers": "0.8.14", | ||
| "@cloudflare/workers-types": "4.20250410.0", | ||
| "@types/node": "^22.15.0", | ||
| "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,18 @@ | ||
| import type { UserDetails } from '@repo/mcp-common/src/durable-objects/user_details' | ||
Maximo-Guk marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| import type { GraphQLMCP } 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<GraphQLMCP> | ||
| USER_DETAILS: DurableObjectNamespace<UserDetails> | ||
| MCP_METRICS: AnalyticsEngineDataset | ||
| SENTRY_ACCESS_CLIENT_ID: string | ||
| SENTRY_ACCESS_CLIENT_SECRET: string | ||
| GIT_HASH: string | ||
| SENTRY_DSN: 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,125 @@ | ||
| import OAuthProvider from '@cloudflare/workers-oauth-provider' | ||
cf-ypark marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| import { McpAgent } from 'agents/mcp' | ||
|
|
||
| import { | ||
| createAuthHandlers, | ||
| handleTokenExchangeCallback, | ||
| } from '@repo/mcp-common/src/cloudflare-oauth-handler' | ||
| import { getUserDetails, UserDetails } from '@repo/mcp-common/src/durable-objects/user_details' | ||
| import { getEnv } from '@repo/mcp-common/src/env' | ||
| import { initSentryWithUser } from '@repo/mcp-common/src/sentry' | ||
| import { CloudflareMCPServer } from '@repo/mcp-common/src/server' | ||
| import { registerAccountTools } from '@repo/mcp-common/src/tools/account' | ||
| import { registerZoneTools } from '@repo/mcp-common/src/tools/zone' | ||
| import { MetricsTracker } from '@repo/mcp-observability' | ||
|
|
||
| import { registerGraphQLTools } from './tools/graphql' | ||
|
|
||
| import type { AccountSchema, UserSchema } from '@repo/mcp-common/src/cloudflare-oauth-handler' | ||
| import type { Env } from './context' | ||
|
|
||
| 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 = { | ||
cf-ypark marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| accessToken: string | ||
| user: UserSchema['result'] | ||
| accounts: AccountSchema['result'] | ||
| } | ||
|
|
||
| export type State = { activeAccountId: string | null } | ||
|
|
||
| export class GraphQLMCP 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 | ||
| } | ||
|
|
||
| initialState: State = { | ||
| activeAccountId: null, | ||
cf-ypark marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| 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, | ||
| }, | ||
| sentry: initSentryWithUser(env, this.ctx, this.props.user.id), | ||
| }) | ||
|
|
||
| // Register account tools | ||
| registerAccountTools(this) | ||
|
|
||
| // Register zone tools | ||
| registerZoneTools(this) | ||
|
|
||
| // Register GraphQL tools | ||
| registerGraphQLTools(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 GraphQLScopes = { | ||
| 'account:read': 'See your account info such as account details, analytics, and memberships.', | ||
| 'user:read': 'See your user info such as name, email address, and account memberships.', | ||
| 'zone:read': 'See zone data such as settings, analytics, and DNS records.', | ||
cf-ypark marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| offline_access: 'Grants refresh tokens for long-lived access.', | ||
| } as const | ||
|
|
||
| export default new OAuthProvider({ | ||
| apiRoute: '/sse', | ||
| // @ts-ignore | ||
cf-ypark marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| apiHandler: GraphQLMCP.mount('/sse'), | ||
| // @ts-ignore | ||
| defaultHandler: createAuthHandlers({ scopes: GraphQLScopes, 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', | ||
| }) | ||
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.