Skip to content

Commit 2851756

Browse files
committed
requested changes updated for GraphQL MCP server
1 parent 27354a9 commit 2851756

7 files changed

Lines changed: 116 additions & 138 deletions

File tree

apps/graphql/package.json

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,30 @@
11
{
2-
"name": "graphql",
3-
"version": "0.0.0",
2+
"name": "graphql-mcp-server",
3+
"version": "0.0.1",
44
"private": true,
55
"scripts": {
66
"check:lint": "run-eslint-workers",
77
"check:types": "run-tsc",
8-
"deploy": "wrangler deploy",
8+
"deploy": "run-wrangler-deploy",
99
"dev": "wrangler dev",
1010
"start": "wrangler dev",
11-
"cf-typegen": "wrangler types",
11+
"types": "wrangler types --include-env=false",
1212
"test": "vitest run"
1313
},
1414
"dependencies": {
15-
"@cloudflare/workers-oauth-provider": "0.0.3",
15+
"@cloudflare/workers-oauth-provider": "0.0.5",
1616
"@hono/zod-validator": "0.4.3",
17-
"@modelcontextprotocol/sdk": "1.9.0",
17+
"@modelcontextprotocol/sdk": "1.10.2",
1818
"@repo/mcp-common": "workspace:*",
1919
"@repo/mcp-observability": "workspace:*",
20-
"agents": "0.0.62",
20+
"agents": "0.0.67",
2121
"cloudflare": "4.2.0",
2222
"hono": "4.7.6",
2323
"zod": "3.24.2",
2424
"lz-string": "1.5.0"
2525
},
2626
"devDependencies": {
2727
"@cloudflare/vitest-pool-workers": "0.8.14",
28-
"@cloudflare/workers-types": "4.20250410.0",
2928
"@types/node": "^22.15.0",
3029
"prettier": "3.5.3",
3130
"typescript": "5.5.4",
Lines changed: 38 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,20 @@ import {
55
createAuthHandlers,
66
handleTokenExchangeCallback,
77
} from '@repo/mcp-common/src/cloudflare-oauth-handler'
8-
import { getUserDetails, UserDetails } from '@repo/mcp-common/src/durable-objects/user_details'
8+
import { handleDevMode } from '@repo/mcp-common/src/dev-mode'
9+
import { getUserDetails, UserDetails } from '@repo/mcp-common/src/durable-objects/user_details.do'
910
import { getEnv } from '@repo/mcp-common/src/env'
11+
import { RequiredScopes } from '@repo/mcp-common/src/scopes'
1012
import { initSentryWithUser } from '@repo/mcp-common/src/sentry'
1113
import { CloudflareMCPServer } from '@repo/mcp-common/src/server'
12-
import { registerAccountTools } from '@repo/mcp-common/src/tools/account'
13-
import { registerZoneTools } from '@repo/mcp-common/src/tools/zone'
14+
import { registerAccountTools } from '@repo/mcp-common/src/tools/account.tools'
15+
import { registerZoneTools } from '@repo/mcp-common/src/tools/zone.tools'
1416
import { MetricsTracker } from '@repo/mcp-observability'
1517

16-
import { registerGraphQLTools } from './tools/graphql'
18+
import { registerGraphQLTools } from './tools/graphql.tools'
1719

18-
import type { AccountSchema, UserSchema } from '@repo/mcp-common/src/cloudflare-oauth-handler'
19-
import type { Env } from './context'
20+
import type { AuthProps } from '@repo/mcp-common/src/cloudflare-oauth-handler'
21+
import type { Env } from './graphql.context'
2022

2123
export { UserDetails }
2224

@@ -29,13 +31,8 @@ const metrics = new MetricsTracker(env.MCP_METRICS, {
2931

3032
// Context from the auth process, encrypted & stored in the auth token
3133
// and provided to the DurableMCP as this.props
32-
export type Props = {
33-
accessToken: string
34-
user: UserSchema['result']
35-
accounts: AccountSchema['result']
36-
}
37-
38-
export type State = { activeAccountId: string | null }
34+
type Props = AuthProps
35+
type State = { activeAccountId: string | null }
3936

4037
export class GraphQLMCP extends McpAgent<Env, State, Props> {
4138
_server: CloudflareMCPServer | undefined
@@ -51,10 +48,6 @@ export class GraphQLMCP extends McpAgent<Env, State, Props> {
5148
return this._server
5249
}
5350

54-
initialState: State = {
55-
activeAccountId: null,
56-
}
57-
5851
constructor(ctx: DurableObjectState, env: Env) {
5952
super(ctx, env)
6053
}
@@ -103,23 +96,35 @@ export class GraphQLMCP extends McpAgent<Env, State, Props> {
10396
}
10497

10598
const GraphQLScopes = {
99+
...RequiredScopes,
106100
'account:read': 'See your account info such as account details, analytics, and memberships.',
107-
'user:read': 'See your user info such as name, email address, and account memberships.',
108101
'zone:read': 'See zone data such as settings, analytics, and DNS records.',
109-
offline_access: 'Grants refresh tokens for long-lived access.',
110102
} as const
111103

112-
export default new OAuthProvider({
113-
apiRoute: '/sse',
114-
// @ts-ignore
115-
apiHandler: GraphQLMCP.mount('/sse'),
116-
// @ts-ignore
117-
defaultHandler: createAuthHandlers({ scopes: GraphQLScopes, metrics }),
118-
authorizeEndpoint: '/oauth/authorize',
119-
tokenEndpoint: '/token',
120-
tokenExchangeCallback: (options) =>
121-
handleTokenExchangeCallback(options, env.CLOUDFLARE_CLIENT_ID, env.CLOUDFLARE_CLIENT_SECRET),
122-
// Cloudflare access token TTL
123-
accessTokenTTL: 3600,
124-
clientRegistrationEndpoint: '/register',
125-
})
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+
}
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import type { UserDetails } from '@repo/mcp-common/src/durable-objects/user_details'
2-
import type { GraphQLMCP } from './index'
1+
import type { GraphQLMCP, UserDetails } from './graphql.app'
32

43
export interface Env {
54
OAUTH_KV: KVNamespace
@@ -15,4 +14,7 @@ export interface Env {
1514
SENTRY_ACCESS_CLIENT_SECRET: string
1615
GIT_HASH: string
1716
SENTRY_DSN: string
17+
DEV_DISABLE_OAUTH: string
18+
DEV_CLOUDFLARE_API_TOKEN: string
19+
DEV_CLOUDFLARE_EMAIL: string
1820
}
Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { z } from 'zod'
22
import * as LZString from 'lz-string'
33

4-
import type { GraphQLMCP } from '../index'
4+
import type { GraphQLMCP } from '../graphql.app'
55

66
// GraphQL API endpoint
77
const CLOUDFLARE_GRAPHQL_ENDPOINT = 'https://api.cloudflare.com/client/v4/graphql'
@@ -63,6 +63,23 @@ interface TypeDetailsResponse {
6363
}
6464
}
6565

66+
// Define the structure of a single error
67+
const graphQLErrorSchema = z.object({
68+
message: z.string(),
69+
path: z.array(z.union([z.string(), z.number()])),
70+
extensions: z.object({
71+
code: z.string(),
72+
timestamp: z.string(),
73+
ray_id: z.string(),
74+
}),
75+
});
76+
77+
// Define the overall GraphQL response schema
78+
const graphQLResponseSchema = z.object({
79+
data: z.union([z.record(z.unknown()), z.null()]),
80+
errors: z.union([z.array(graphQLErrorSchema), z.null()]),
81+
});
82+
6683
/**
6784
* Fetches the high-level overview of the GraphQL schema
6885
* @param apiToken Cloudflare API token
@@ -179,7 +196,7 @@ async function executeGraphQLRequest<T>(query: string, apiToken: string): Promis
179196
throw new Error(`Failed to execute GraphQL request: ${response.statusText}`)
180197
}
181198

182-
const data = (await response.json()) as any
199+
const data = graphQLResponseSchema.parse(await response.json())
183200

184201
// Check for GraphQL errors in the response
185202
if (data && data.errors && Array.isArray(data.errors) && data.errors.length > 0) {
@@ -222,7 +239,7 @@ async function executeGraphQLQuery(query: string, variables: any, apiToken: stri
222239
throw new Error(`Failed to execute GraphQL query: ${response.statusText}`)
223240
}
224241

225-
const result = (await response.json()) as any
242+
const result = graphQLResponseSchema.parse(await response.json())
226243

227244
// Check for GraphQL errors in the response
228245
if (result && result.errors && Array.isArray(result.errors) && result.errors.length > 0) {

apps/graphql/types.d.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
1-
export interface Env {
2-
MCP_OBJECT: DurableObjectNamespace
3-
MCP_SERVER_NAME: string
4-
MCP_SERVER_VERSION: string
5-
MCP_METRICS: string
6-
CLOUDFLARE_CLIENT_ID: string
7-
CLOUDFLARE_CLIENT_SECRET: string
1+
import type { TestEnv } from './vitest.config'
2+
3+
declare module 'cloudflare:test' {
4+
interface ProvidedEnv extends TestEnv {}
85
}

0 commit comments

Comments
 (0)