mcp-server-cloudflare
Official
by cloudflare
import { Tool } from '@modelcontextprotocol/sdk/types.js'
import { fetch } from 'undici'
import { config } from '../utils/helpers'
import { ToolHandlers } from '../utils/types'
const ANALYTICS_GET_TOOL: Tool = {
name: 'analytics_get',
description: 'Get analytics data from Cloudflare',
inputSchema: {
type: 'object',
properties: {
zoneId: {
type: 'string',
description: 'The zone ID to get analytics for',
},
since: {
type: 'string',
description: 'Start time for analytics (ISO string)',
},
until: {
type: 'string',
description: 'End time for analytics (ISO string)',
},
},
required: ['zoneId'],
},
}
const WORKERS_ANALYTICS_SEARCH_TOOL: Tool = {
name: 'workers_analytics_search',
description: 'Search Workers analytics data for a specific time period',
inputSchema: {
type: 'object',
properties: {
accountId: {
type: 'string',
description: 'The Cloudflare account ID',
},
scriptName: {
type: 'string',
description: 'The name of the Worker script to search for (optional)',
},
startTime: {
type: 'string',
description: 'Start time for analytics search (ISO string)',
},
endTime: {
type: 'string',
description: 'End time for analytics search (ISO string)',
},
limit: {
type: 'number',
description: 'Maximum number of results to return (default: 100)',
},
status: {
type: 'string',
description: 'Filter by status (e.g., "success", "error")',
},
},
required: ['accountId'],
},
}
export const ANALYTICS_TOOLS = [ANALYTICS_GET_TOOL, WORKERS_ANALYTICS_SEARCH_TOOL]
interface GraphQLResponse {
data?: any
errors?: Array<{
message: string
locations?: Array<{
line: number
column: number
}>
path?: string[]
extensions?: Record<string, any>
}>
}
export const ANALYTICS_HANDLERS: ToolHandlers = {
analytics_get: async (request) => {
const { zoneId, since, until } = request.params.arguments as {
zoneId: string
since?: string
until?: string
}
const date = since ? new Date(since).toISOString().split('T')[0] : new Date().toISOString().split('T')[0]
const graphqlQuery = {
query: `query {
viewer {
zones(filter: {zoneTag: "${zoneId}"}) {
httpRequests1dGroups(
limit: 1,
filter: {date: "${date}"},
orderBy: [date_DESC]
) {
dimensions {
date
}
sum {
requests
bytes
threats
pageViews
}
uniq {
uniques
}
}
}
}
}`,
}
const analyticsResponse = await fetch('https://api.cloudflare.com/client/v4/graphql', {
method: 'POST',
headers: {
Authorization: `Bearer ${config.apiToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(graphqlQuery),
})
if (!analyticsResponse.ok) {
throw new Error(`Analytics API error: ${await analyticsResponse.text()}`)
}
const analyticsData = (await analyticsResponse.json()) as GraphQLResponse
// Check for GraphQL errors
if (analyticsData.errors) {
throw new Error(`GraphQL error: ${JSON.stringify(analyticsData.errors)}`)
}
return {
content: [
{
type: 'text',
text: JSON.stringify(analyticsData, null, 2),
},
],
metadata: {},
}
},
workers_analytics_search: async (request) => {
const { accountId, scriptName, startTime, endTime, limit, status } = request.params.arguments as {
accountId: string
scriptName?: string
startTime?: string
endTime?: string
limit?: number
status?: string
}
// Set default time range if not provided (last 24 hours)
const now = new Date()
const defaultEndTime = now.toISOString()
const defaultStartTime = new Date(now.getTime() - 24 * 60 * 60 * 1000).toISOString()
const datetimeStart = startTime || defaultStartTime
const datetimeEnd = endTime || defaultEndTime
const resultLimit = limit || 100
// Build filter object for the GraphQL query
const filter: Record<string, any> = {
datetime_geq: datetimeStart,
datetime_leq: datetimeEnd,
}
// Add optional filters if provided
if (scriptName) {
filter.scriptName = scriptName
}
if (status) {
filter.status = status
}
// Construct the GraphQL query
const graphqlQuery = {
query: `
query GetWorkersAnalytics($accountTag: String!, $limit: Int!, $filter: WorkersInvocationsAdaptiveFilter_InputObject!) {
viewer {
accounts(filter: {accountTag: $accountTag}) {
workersInvocationsAdaptive(limit: $limit, filter: $filter) {
sum {
subrequests
requests
errors
}
quantiles {
cpuTimeP50
cpuTimeP99
}
dimensions {
datetime
scriptName
status
}
}
}
}
}
`,
variables: {
accountTag: accountId,
limit: resultLimit,
filter: filter,
},
}
try {
const analyticsResponse = await fetch('https://api.cloudflare.com/client/v4/graphql', {
method: 'POST',
headers: {
Authorization: `Bearer ${config.apiToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(graphqlQuery),
})
if (!analyticsResponse.ok) {
throw new Error(`Workers Analytics API error: ${await analyticsResponse.text()}`)
}
const analyticsData = (await analyticsResponse.json()) as GraphQLResponse
// Check for GraphQL errors
if (analyticsData.errors) {
throw new Error(`GraphQL error: ${JSON.stringify(analyticsData.errors)}`)
}
return {
toolResult: {
content: [
{
type: 'text',
text: JSON.stringify(analyticsData, null, 2),
},
],
},
}
} catch (error) {
return {
toolResult: {
content: [
{
type: 'text',
text: `Error fetching Workers analytics: ${error instanceof Error ? error.message : String(error)}`,
},
],
isError: true,
},
}
}
},
}