#
tokens: 49892/50000 15/376 files (page 4/25)
lines: off (toggle) GitHub
raw markdown copy
This is page 4 of 25. Use http://codebase.md/cloudflare/mcp-server-cloudflare?lines=false&page={x} to view the full context.

# Directory Structure

```
├── .changeset
│   ├── config.json
│   └── README.md
├── .dockerignore
├── .editorconfig
├── .eslintrc.cjs
├── .github
│   ├── actions
│   │   └── setup
│   │       └── action.yml
│   ├── ISSUE_TEMPLATE
│   │   └── bug_report.md
│   └── workflows
│       ├── branches.yml
│       ├── main.yml
│       └── release.yml
├── .gitignore
├── .npmrc
├── .prettierignore
├── .prettierrc.cjs
├── .syncpackrc.cjs
├── .vscode
│   ├── extensions.json
│   ├── launch.json
│   ├── settings.json
│   └── tasks.json
├── apps
│   ├── ai-gateway
│   │   ├── .dev.vars.example
│   │   ├── .eslintrc.cjs
│   │   ├── CHANGELOG.md
│   │   ├── CONTRIBUTING.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── ai-gateway.app.ts
│   │   │   ├── ai-gateway.context.ts
│   │   │   ├── tools
│   │   │   │   └── ai-gateway.tools.ts
│   │   │   └── types.ts
│   │   ├── tsconfig.json
│   │   ├── types.d.ts
│   │   ├── vitest.config.ts
│   │   ├── worker-configuration.d.ts
│   │   └── wrangler.jsonc
│   ├── auditlogs
│   │   ├── .dev.vars.example
│   │   ├── .eslintrc.cjs
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── auditlogs.app.ts
│   │   │   ├── auditlogs.context.ts
│   │   │   └── tools
│   │   │       └── auditlogs.tools.ts
│   │   ├── tsconfig.json
│   │   ├── types.d.ts
│   │   ├── vitest.config.ts
│   │   ├── worker-configuration.d.ts
│   │   └── wrangler.jsonc
│   ├── autorag
│   │   ├── .dev.vars.example
│   │   ├── .eslintrc.cjs
│   │   ├── CHANGELOG.md
│   │   ├── CONTRIBUTING.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── autorag.app.ts
│   │   │   ├── autorag.context.ts
│   │   │   ├── tools
│   │   │   │   └── autorag.tools.ts
│   │   │   └── types.ts
│   │   ├── tsconfig.json
│   │   ├── types.d.ts
│   │   ├── vitest.config.ts
│   │   ├── worker-configuration.d.ts
│   │   └── wrangler.jsonc
│   ├── browser-rendering
│   │   ├── .dev.vars.example
│   │   ├── .eslintrc.cjs
│   │   ├── CHANGELOG.md
│   │   ├── CONTRIBUTING.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── browser.app.ts
│   │   │   ├── browser.context.ts
│   │   │   └── tools
│   │   │       └── browser.tools.ts
│   │   ├── tsconfig.json
│   │   ├── types.d.ts
│   │   ├── vitest.config.ts
│   │   ├── worker-configuration.d.ts
│   │   └── wrangler.jsonc
│   ├── cloudflare-one-casb
│   │   ├── .dev.vars.example
│   │   ├── .eslintrc.cjs
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── cf1-casb.app.ts
│   │   │   ├── cf1-casb.context.ts
│   │   │   └── tools
│   │   │       └── integrations.tools.ts
│   │   ├── tsconfig.json
│   │   ├── types.d.ts
│   │   ├── vitest.config.ts
│   │   ├── worker-configuration.d.ts
│   │   └── wrangler.jsonc
│   ├── demo-day
│   │   ├── .eslintrc.cjs
│   │   ├── CHANGELOG.md
│   │   ├── frontend
│   │   │   ├── index.html
│   │   │   ├── public
│   │   │   │   ├── anthropic.svg
│   │   │   │   ├── asana.svg
│   │   │   │   ├── atlassian.svg
│   │   │   │   ├── canva.svg
│   │   │   │   ├── cloudflare_logo.svg
│   │   │   │   ├── cloudflare.svg
│   │   │   │   ├── dina.jpg
│   │   │   │   ├── favicon-16x16.png
│   │   │   │   ├── favicon-32x32.png
│   │   │   │   ├── favicon.ico
│   │   │   │   ├── favicon.png
│   │   │   │   ├── intercom.svg
│   │   │   │   ├── linear.svg
│   │   │   │   ├── matt.jpg
│   │   │   │   ├── mcp_demo_day.svg
│   │   │   │   ├── mcpog.png
│   │   │   │   ├── more.svg
│   │   │   │   ├── paypal.svg
│   │   │   │   ├── pete.jpeg
│   │   │   │   ├── sentry.svg
│   │   │   │   ├── special_guest.png
│   │   │   │   ├── square.svg
│   │   │   │   ├── stripe.svg
│   │   │   │   ├── sunil.jpg
│   │   │   │   └── webflow.svg
│   │   │   ├── script.js
│   │   │   └── styles.css
│   │   ├── package.json
│   │   ├── src
│   │   │   └── demo-day.app.ts
│   │   ├── tsconfig.json
│   │   ├── worker-configuration.d.ts
│   │   └── wrangler.json
│   ├── dex-analysis
│   │   ├── .dev.vars.example
│   │   ├── .eslintrc.cjs
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── dex-analysis.app.ts
│   │   │   ├── dex-analysis.context.ts
│   │   │   ├── tools
│   │   │   │   └── dex-analysis.tools.ts
│   │   │   └── warp_diag_reader.ts
│   │   ├── tsconfig.json
│   │   ├── types.d.ts
│   │   ├── vitest.config.ts
│   │   ├── worker-configuration.d.ts
│   │   └── wrangler.jsonc
│   ├── dns-analytics
│   │   ├── .dev.vars.example
│   │   ├── .eslintrc.cjs
│   │   ├── CHANGELOG.md
│   │   ├── CONTRIBUTING.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── dns-analytics.app.ts
│   │   │   ├── dns-analytics.context.ts
│   │   │   └── tools
│   │   │       └── dex-analytics.tools.ts
│   │   ├── tsconfig.json
│   │   ├── types.d.ts
│   │   ├── vitest.config.ts
│   │   ├── worker-configuration.d.ts
│   │   └── wrangler.jsonc
│   ├── docs-autorag
│   │   ├── .eslintrc.cjs
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── docs-autorag.app.ts
│   │   │   ├── docs-autorag.context.ts
│   │   │   └── tools
│   │   │       └── docs-autorag.tools.ts
│   │   ├── tsconfig.json
│   │   ├── vitest.config.ts
│   │   ├── worker-configuration.d.ts
│   │   └── wrangler.jsonc
│   ├── docs-vectorize
│   │   ├── .eslintrc.cjs
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── docs-vectorize.app.ts
│   │   │   └── docs-vectorize.context.ts
│   │   ├── tsconfig.json
│   │   ├── vitest.config.ts
│   │   ├── worker-configuration.d.ts
│   │   └── wrangler.jsonc
│   ├── graphql
│   │   ├── .eslintrc.cjs
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── graphql.app.ts
│   │   │   ├── graphql.context.ts
│   │   │   └── tools
│   │   │       └── graphql.tools.ts
│   │   ├── tsconfig.json
│   │   ├── types.d.ts
│   │   ├── worker-configuration.d.ts
│   │   └── wrangler.jsonc
│   ├── logpush
│   │   ├── .dev.vars.example
│   │   ├── .eslintrc.cjs
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── logpush.app.ts
│   │   │   ├── logpush.context.ts
│   │   │   └── tools
│   │   │       └── logpush.tools.ts
│   │   ├── tsconfig.json
│   │   ├── types.d.ts
│   │   ├── vitest.config.ts
│   │   ├── worker-configuration.d.ts
│   │   └── wrangler.jsonc
│   ├── radar
│   │   ├── .dev.vars.example
│   │   ├── .eslintrc.cjs
│   │   ├── CHANGELOG.md
│   │   ├── CONTRIBUTING.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── radar.app.ts
│   │   │   ├── radar.context.ts
│   │   │   ├── tools
│   │   │   │   ├── radar.tools.ts
│   │   │   │   └── url-scanner.tools.ts
│   │   │   ├── types
│   │   │   │   ├── radar.ts
│   │   │   │   └── url-scanner.ts
│   │   │   └── utils.ts
│   │   ├── tsconfig.json
│   │   ├── types.d.ts
│   │   ├── vitest.config.ts
│   │   ├── worker-configuration.d.ts
│   │   └── wrangler.jsonc
│   ├── sandbox-container
│   │   ├── .dev.vars.example
│   │   ├── .eslintrc.cjs
│   │   ├── CHANGELOG.md
│   │   ├── container
│   │   │   ├── fileUtils.spec.ts
│   │   │   ├── fileUtils.ts
│   │   │   ├── sandbox.container.app.ts
│   │   │   └── tsconfig.json
│   │   ├── CONTRIBUTING.md
│   │   ├── Dockerfile
│   │   ├── evals
│   │   │   ├── exec.eval.ts
│   │   │   ├── files.eval.ts
│   │   │   ├── initialize.eval.ts
│   │   │   └── utils.ts
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── server
│   │   │   ├── containerHelpers.ts
│   │   │   ├── containerManager.ts
│   │   │   ├── containerMcp.ts
│   │   │   ├── metrics.ts
│   │   │   ├── prompts.ts
│   │   │   ├── sandbox.server.app.ts
│   │   │   ├── sandbox.server.context.ts
│   │   │   ├── userContainer.ts
│   │   │   ├── utils.spec.ts
│   │   │   └── utils.ts
│   │   ├── shared
│   │   │   ├── consts.ts
│   │   │   └── schema.ts
│   │   ├── tsconfig.json
│   │   ├── types.d.ts
│   │   ├── vitest.config.evals.ts
│   │   ├── worker-configuration.d.ts
│   │   └── wrangler.jsonc
│   ├── workers-bindings
│   │   ├── .dev.vars.example
│   │   ├── .eslintrc.cjs
│   │   ├── .gitignore
│   │   ├── CHANGELOG.md
│   │   ├── CONTRIBUTING.md
│   │   ├── evals
│   │   │   ├── accounts.eval.ts
│   │   │   ├── hyperdrive.eval.ts
│   │   │   ├── kv_namespaces.eval.ts
│   │   │   ├── types.d.ts
│   │   │   └── utils.ts
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── bindings.app.ts
│   │   │   └── bindings.context.ts
│   │   ├── tsconfig.json
│   │   ├── vitest.config.evals.ts
│   │   ├── vitest.config.ts
│   │   ├── worker-configuration.d.ts
│   │   └── wrangler.jsonc
│   ├── workers-builds
│   │   ├── .dev.vars.example
│   │   ├── .eslintrc.cjs
│   │   ├── CHANGELOG.md
│   │   ├── CONTRIBUTING.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── tools
│   │   │   │   └── workers-builds.tools.ts
│   │   │   ├── workers-builds.app.ts
│   │   │   └── workers-builds.context.ts
│   │   ├── tsconfig.json
│   │   ├── types.d.ts
│   │   ├── vite.config.mts
│   │   ├── vitest.config.ts
│   │   ├── worker-configuration.d.ts
│   │   └── wrangler.jsonc
│   └── workers-observability
│       ├── .dev.vars.example
│       ├── .eslintrc.cjs
│       ├── CHANGELOG.md
│       ├── CONTRIBUTING.md
│       ├── package.json
│       ├── README.md
│       ├── src
│       │   ├── tools
│       │   │   └── workers-observability.tools.ts
│       │   ├── workers-observability.app.ts
│       │   └── workers-observability.context.ts
│       ├── tsconfig.json
│       ├── types.d.ts
│       ├── vitest.config.ts
│       ├── worker-configuration.d.ts
│       └── wrangler.jsonc
├── CONTRIBUTING.md
├── implementation-guides
│   ├── evals.md
│   ├── tools.md
│   └── type-validators.md
├── LICENSE
├── package.json
├── packages
│   ├── eslint-config
│   │   ├── CHANGELOG.md
│   │   ├── default.cjs
│   │   ├── package.json
│   │   └── README.md
│   ├── eval-tools
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── src
│   │   │   ├── runTask.ts
│   │   │   ├── scorers.ts
│   │   │   └── test-models.ts
│   │   ├── tsconfig.json
│   │   ├── worker-configuration.d.ts
│   │   └── wrangler.json
│   ├── mcp-common
│   │   ├── .eslintrc.cjs
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── api
│   │   │   │   ├── account.api.ts
│   │   │   │   ├── cf1-integration.api.ts
│   │   │   │   ├── workers-builds.api.ts
│   │   │   │   ├── workers-observability.api.ts
│   │   │   │   ├── workers.api.ts
│   │   │   │   └── zone.api.ts
│   │   │   ├── api-handler.ts
│   │   │   ├── api-token-mode.ts
│   │   │   ├── cloudflare-api.ts
│   │   │   ├── cloudflare-auth.ts
│   │   │   ├── cloudflare-oauth-handler.ts
│   │   │   ├── config.ts
│   │   │   ├── constants.ts
│   │   │   ├── durable-kv-store.ts
│   │   │   ├── durable-objects
│   │   │   │   └── user_details.do.ts
│   │   │   ├── env.ts
│   │   │   ├── format.spec.ts
│   │   │   ├── format.ts
│   │   │   ├── get-props.ts
│   │   │   ├── mcp-error.ts
│   │   │   ├── poll.ts
│   │   │   ├── prompts
│   │   │   │   └── docs-vectorize.prompts.ts
│   │   │   ├── scopes.ts
│   │   │   ├── sentry.ts
│   │   │   ├── server.ts
│   │   │   ├── tools
│   │   │   │   ├── account.tools.ts
│   │   │   │   ├── d1.tools.ts
│   │   │   │   ├── docs-vectorize.tools.ts
│   │   │   │   ├── hyperdrive.tools.ts
│   │   │   │   ├── kv_namespace.tools.ts
│   │   │   │   ├── r2_bucket.tools.ts
│   │   │   │   ├── worker.tools.ts
│   │   │   │   └── zone.tools.ts
│   │   │   ├── types
│   │   │   │   ├── cf1-integrations.types.ts
│   │   │   │   ├── cloudflare-mcp-agent.types.ts
│   │   │   │   ├── d1.types.ts
│   │   │   │   ├── hyperdrive.types.ts
│   │   │   │   ├── kv_namespace.types.ts
│   │   │   │   ├── r2_bucket.types.ts
│   │   │   │   ├── shared.types.ts
│   │   │   │   ├── tools.types.ts
│   │   │   │   ├── workers-builds.types.ts
│   │   │   │   ├── workers-logs.types.ts
│   │   │   │   └── workers.types.ts
│   │   │   ├── utils.spec.ts
│   │   │   ├── utils.ts
│   │   │   └── v4-api.ts
│   │   ├── tests
│   │   │   └── utils
│   │   │       └── cloudflare-mock.ts
│   │   ├── tsconfig.json
│   │   ├── types.d.ts
│   │   ├── vitest.config.ts
│   │   └── worker-configuration.d.ts
│   ├── mcp-observability
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── src
│   │   │   ├── analytics-engine.ts
│   │   │   ├── index.ts
│   │   │   └── metrics.ts
│   │   ├── tsconfig.json
│   │   └── worker-configuration.d.ts
│   ├── tools
│   │   ├── .eslintrc.cjs
│   │   ├── bin
│   │   │   ├── run-changeset-new
│   │   │   ├── run-eslint-workers
│   │   │   ├── run-fix-deps
│   │   │   ├── run-tsc
│   │   │   ├── run-turbo
│   │   │   ├── run-vitest
│   │   │   ├── run-vitest-ci
│   │   │   ├── run-wrangler-deploy
│   │   │   ├── run-wrangler-types
│   │   │   └── runx
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── bin
│   │   │   │   └── runx.ts
│   │   │   ├── changesets.spec.ts
│   │   │   ├── changesets.ts
│   │   │   ├── cmd
│   │   │   │   └── deploy-published-packages.ts
│   │   │   ├── proc.ts
│   │   │   ├── test
│   │   │   │   ├── fixtures
│   │   │   │   │   └── changesets
│   │   │   │   │       ├── empty
│   │   │   │   │       │   └── .gitkeep
│   │   │   │   │       ├── invalid-json
│   │   │   │   │       │   └── published-packages.json
│   │   │   │   │       ├── invalid-schema
│   │   │   │   │       │   └── published-packages.json
│   │   │   │   │       └── valid
│   │   │   │   │           └── published-packages.json
│   │   │   │   └── setup.ts
│   │   │   └── tsconfig.ts
│   │   ├── tsconfig.json
│   │   └── vitest.config.ts
│   └── typescript-config
│       ├── CHANGELOG.md
│       ├── package.json
│       ├── tools.json
│       ├── workers-lib.json
│       └── workers.json
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── README.md
├── server.json
├── tsconfig.json
├── turbo.json
└── vitest.workspace.ts
```

# Files

--------------------------------------------------------------------------------
/packages/mcp-common/src/tools/kv_namespace.tools.ts:
--------------------------------------------------------------------------------

```typescript
import { getCloudflareClient } from '../cloudflare-api'
import { MISSING_ACCOUNT_ID_RESPONSE } from '../constants'
import { getProps } from '../get-props'
import { type CloudflareMcpAgent } from '../types/cloudflare-mcp-agent.types'
import {
	KvNamespaceIdSchema,
	KvNamespacesListParamsSchema,
	KvNamespaceTitleSchema,
} from '../types/kv_namespace.types'

export const KV_NAMESPACE_TOOLS = {
	kv_namespaces_list: 'kv_namespaces_list',
	kv_namespace_create: 'kv_namespace_create',
	kv_namespace_delete: 'kv_namespace_delete',
	kv_namespace_get: 'kv_namespace_get',
	kv_namespace_update: 'kv_namespace_update',
}

export function registerKVTools(agent: CloudflareMcpAgent) {
	/**
	 * Tool to list KV namespaces.
	 */
	agent.server.tool(
		KV_NAMESPACE_TOOLS.kv_namespaces_list,
		`
			List all of the kv namespaces in your Cloudflare account.
			Use this tool when you need to list all of the kv namespaces in your Cloudflare account.
			Returns a list of kv namespaces with the following properties:
			- id: The id of the kv namespace.
			- title: The title of the kv namespace.
			`,
		{ params: KvNamespacesListParamsSchema.optional() },
		{
			title: 'List KV namespaces',
			annotations: {
				readOnlyHint: true,
			},
		},
		async ({ params }) => {
			const account_id = await agent.getActiveAccountId()
			if (!account_id) {
				return MISSING_ACCOUNT_ID_RESPONSE
			}
			try {
				const props = getProps(agent)
				const client = getCloudflareClient(props.accessToken)
				const response = await client.kv.namespaces.list({
					account_id,
					...params,
				})

				let namespaces = response.result ?? []
				namespaces = namespaces.map((namespace) => ({
					id: namespace.id,
					title: namespace.title,
				}))

				return {
					content: [
						{
							type: 'text',
							text: JSON.stringify({
								namespaces,
								count: namespaces.length,
							}),
						},
					],
				}
			} catch (error) {
				return {
					content: [
						{
							type: 'text',
							text: `Error listing KV namespaces: ${error instanceof Error ? error.message : String(error)}`,
						},
					],
				}
			}
		}
	)

	/**
	 * Tool to create a KV namespace.
	 */
	agent.server.tool(
		KV_NAMESPACE_TOOLS.kv_namespace_create,
		'Create a new kv namespace in your Cloudflare account',
		{
			title: KvNamespaceTitleSchema,
		},
		{
			title: 'Create KV namespace',
			annotations: {
				readOnlyHint: false,
				destructiveHint: false,
			},
		},
		async ({ title }) => {
			const account_id = await agent.getActiveAccountId()
			if (!account_id) {
				return MISSING_ACCOUNT_ID_RESPONSE
			}
			try {
				const props = getProps(agent)
				const client = getCloudflareClient(props.accessToken)
				const namespace = await client.kv.namespaces.create({ account_id, title })
				return {
					content: [
						{
							type: 'text',
							text: JSON.stringify(namespace),
						},
					],
				}
			} catch (error) {
				return {
					content: [
						{
							type: 'text',
							text: `Error creating KV namespace: ${error instanceof Error ? error.message : String(error)}`,
						},
					],
				}
			}
		}
	)

	/**
	 * Tool to delete a KV namespace.
	 */
	agent.server.tool(
		KV_NAMESPACE_TOOLS.kv_namespace_delete,
		'Delete a kv namespace in your Cloudflare account',
		{
			namespace_id: KvNamespaceIdSchema,
		},
		{
			title: 'Delete KV namespace',
			annotations: {
				readOnlyHint: false,
				destructiveHint: true,
			},
		},
		async ({ namespace_id }) => {
			const account_id = await agent.getActiveAccountId()
			if (!account_id) {
				return MISSING_ACCOUNT_ID_RESPONSE
			}
			try {
				const props = getProps(agent)
				const client = getCloudflareClient(props.accessToken)
				const result = await client.kv.namespaces.delete(namespace_id, { account_id })
				return {
					content: [
						{
							type: 'text',
							text: JSON.stringify(result ?? { success: true }),
						},
					],
				}
			} catch (error) {
				return {
					content: [
						{
							type: 'text',
							text: `Error deleting KV namespace: ${error instanceof Error ? error.message : String(error)}`,
						},
					],
				}
			}
		}
	)

	/**
	 * Tool to get details of a specific KV namespace.
	 */
	agent.server.tool(
		KV_NAMESPACE_TOOLS.kv_namespace_get,
		`Get details of a kv namespace in your Cloudflare account.
		Use this tool when you need to get details of a specific kv namespace in your Cloudflare account.
		Returns a kv namespace with the following properties:
			- id: The id of the kv namespace.
			- title: The title of the kv namespace.
			- supports_url_encoding: Whether the kv namespace supports url encoding.
			- beta: Whether the kv namespace is in beta.
		`,
		{
			namespace_id: KvNamespaceIdSchema,
		},
		{
			title: 'Get KV namespace',
			annotations: {
				readOnlyHint: true,
			},
		},
		async ({ namespace_id }) => {
			const account_id = await agent.getActiveAccountId()
			if (!account_id) {
				return MISSING_ACCOUNT_ID_RESPONSE
			}
			try {
				const props = getProps(agent)
				const client = getCloudflareClient(props.accessToken)
				const namespace = await client.kv.namespaces.get(namespace_id, { account_id })
				return {
					content: [
						{
							type: 'text',
							text: JSON.stringify(namespace),
						},
					],
				}
			} catch (error) {
				return {
					content: [
						{
							type: 'text',
							text: `Error getting KV namespace: ${error instanceof Error ? error.message : String(error)}`,
						},
					],
				}
			}
		}
	)

	/**
	 * Tool to update the title of a KV namespace.
	 */
	agent.server.tool(
		KV_NAMESPACE_TOOLS.kv_namespace_update,
		'Update the title of a kv namespace in your Cloudflare account',
		{
			namespace_id: KvNamespaceIdSchema,
			title: KvNamespaceTitleSchema,
		},
		{
			title: 'Update KV namespace',
			annotations: {
				readOnlyHint: false,
				destructiveHint: false,
			},
		},
		async ({ namespace_id, title }) => {
			const account_id = await agent.getActiveAccountId()
			if (!account_id) {
				return MISSING_ACCOUNT_ID_RESPONSE
			}
			try {
				const props = getProps(agent)
				const client = getCloudflareClient(props.accessToken)
				const result = await client.kv.namespaces.update(namespace_id, {
					account_id,
					title,
				})
				return {
					content: [
						{
							type: 'text',
							text: JSON.stringify(result ?? { success: true }),
						},
					],
				}
			} catch (error) {
				return {
					content: [
						{
							type: 'text',
							text: `Error updating KV namespace: ${error instanceof Error ? error.message : String(error)}`,
						},
					],
				}
			}
		}
	)
}

```

--------------------------------------------------------------------------------
/apps/workers-builds/src/tools/workers-builds.tools.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from 'zod'

import { getBuild, getBuildLogs, listBuilds } from '@repo/mcp-common/src/api/workers-builds.api'
import { fmt } from '@repo/mcp-common/src/format'
import { getProps } from '@repo/mcp-common/src/get-props'

import type { BuildsMCP } from '../workers-builds.app'

/**
 * Registers the Workers Builds tools with the MCP server
 * @param server The MCP server instance
 * @param accountId Cloudflare account ID
 * @param apiToken Cloudflare API token
 */
export function registerBuildsTools(agent: BuildsMCP) {
	agent.server.tool(
		'workers_builds_set_active_worker',
		fmt.trim(`
			Set the active Worker ID for subsequent calls.
			Use this tool to set the active worker for subsequent calls.

			Worker IDs are formatted similar to: db6a6421c2b046679a9daada1537088b
			If you are given a Worker name or script name, you can use workers_get_worker to get the Worker ID.
		`),
		{
			workerId: z.string().describe('The Worker ID to set as active.'),
		},
		async ({ workerId }) => {
			await agent.setActiveWorkerId(workerId)
			return {
				content: [
					{
						type: 'text',
						text: `Active worker set to ${workerId}`,
					},
				],
			}
		}
	)

	agent.server.tool(
		'workers_builds_list_builds',
		fmt.trim(`
			Use the Workers Builds API to list builds for a Cloudflare Worker.

			MUST provide a workerId or call workers_builds_set_active_worker first.
		`),
		{
			workerId: z.string().optional().describe('The Worker ID to list builds for.'),
			page: z.number().optional().default(1).describe('The page number to return.'),
			perPage: z.number().optional().default(10).describe('The number of builds per page.'),
		},
		async ({ workerId, page, perPage }) => {
			const accountId = await agent.getActiveAccountId()
			if (!accountId) {
				return {
					content: [
						{
							type: 'text',
							text: fmt.oneLine(`
								No currently active accountId. Try listing your accounts (accounts_list)
								and then setting an active account (set_active_account)
							`),
						},
					],
				}
			}

			if (!workerId) {
				const activeWorkerId = await agent.getActiveWorkerId()
				if (activeWorkerId) {
					workerId = activeWorkerId
				} else {
					return {
						content: [
							{
								type: 'text',
								text: fmt.oneLine(`
									No workerId provided and no active workerId.
									Either provide a workerId or call workers_builds_set_active_worker first.
								`),
							},
						],
					}
				}
			}

			try {
				const props = getProps(agent)
				const res = await listBuilds({
					apiToken: props.accessToken,
					accountId,
					workerId,
					page,
					perPage,
				})

				if (!res.result) {
					return {
						content: [
							{
								type: 'text',
								text: 'No builds found',
							},
						],
					}
				}

				const buildsFormatted = res.result
					.sort((a, b) => b.created_on.getTime() - a.created_on.getTime())
					.map((build) => ({
						buildUUID: build.build_uuid,
						createdOn: build.created_on.toISOString(),
						status: build.status,
						buildOutcome: build.build_outcome,
						branch: build.build_trigger_metadata.branch,
						commitHash: build.build_trigger_metadata.commit_hash,
						commitMessage: build.build_trigger_metadata.commit_message,
						commitAuthor: build.build_trigger_metadata.author,
					}))

				return {
					content: [
						{
							type: 'text',
							text: 'pagination_info:',
						},
						{
							type: 'text',
							text: await fmt.asTSV([res.result_info]),
						},
						{
							type: 'text',
							text: 'builds:',
						},
						{
							type: 'text',
							text: await fmt.asTSV(buildsFormatted),
						},
					],
				}
			} catch (error) {
				return {
					content: [
						{
							type: 'text',
							text: `Error: listing builds failed: ${error instanceof Error && error.message}`,
						},
					],
				}
			}
		}
	)

	agent.server.tool(
		'workers_builds_get_build',
		fmt.trim(`
			Get details for a specific build by its UUID.
			Includes build and deploy commands for the build (useful for debugging build failures).
		`),
		{
			buildUUID: z.string().describe('The build UUID to get details for.'),
		},
		async ({ buildUUID }) => {
			const accountId = await agent.getActiveAccountId()
			if (!accountId) {
				return {
					content: [
						{
							type: 'text',
							text: 'No currently active accountId. Set an active account first.',
						},
					],
				}
			}

			try {
				const props = getProps(agent)
				const { result: build } = await getBuild({
					apiToken: props.accessToken,
					accountId,
					buildUUID,
				})

				if (!build) {
					return {
						content: [
							{
								type: 'text',
								text: 'Build not found',
							},
						],
					}
				}

				const buildFormatted = {
					buildUUID: build.build_uuid,
					createdOn: build.created_on.toISOString(),
					status: build.status,
					buildOutcome: build.build_outcome,
					branch: build.build_trigger_metadata.branch,
					commitHash: build.build_trigger_metadata.commit_hash,
					commitMessage: build.build_trigger_metadata.commit_message,
					commitAuthor: build.build_trigger_metadata.author,
					buildCommand: build.build_trigger_metadata.build_command,
					deployCommand: build.build_trigger_metadata.deploy_command,
				}

				return {
					content: [
						{
							type: 'text',
							text: await fmt.asTSV([buildFormatted]),
						},
					],
				}
			} catch (error) {
				return {
					content: [
						{
							type: 'text',
							text: `Error: getting build failed: ${error instanceof Error && error.message}`,
						},
					],
				}
			}
		}
	)

	agent.server.tool(
		'workers_builds_get_build_logs',
		fmt.trim(`
			Get logs for a Cloudflare Workers build.
		`),
		{
			buildUUID: z.string().describe('The build UUID to get logs for.'),
		},
		async ({ buildUUID }) => {
			const accountId = await agent.getActiveAccountId()
			if (!accountId) {
				return {
					content: [
						{
							type: 'text',
							text: 'No currently active accountId. Set an active account first.',
						},
					],
				}
			}

			try {
				const props = getProps(agent)
				const logs = await getBuildLogs({
					apiToken: props.accessToken,
					accountId,
					buildUUID,
				})
				const logsFormatted = logs.map((log) => ({
					timestamp: `${log[0].getUTCHours()}:${log[0].getUTCMinutes()}:${log[0].getUTCSeconds()}`,
					message: log[1],
				}))
				return {
					content: [
						{
							type: 'text',
							text: await fmt.asTSV(logsFormatted),
						},
					],
				}
			} catch (error) {
				return {
					content: [
						{
							type: 'text',
							text: `Error: getting build logs failed: ${error instanceof Error && error.message}`,
						},
					],
				}
			}
		}
	)
}

```

--------------------------------------------------------------------------------
/implementation-guides/tools.md:
--------------------------------------------------------------------------------

```markdown
# MCP Tool Implementation Guide

This guide explains how to implement and register tools within an MCP (Model Context Protocol) server, enabling AI models to interact with external systems, APIs, or specific functionalities like Cloudflare services.

## Purpose of Tools

Tools are the mechanism by which an MCP agent (powered by an LLM) can perform actions beyond generating text. They allow the agent to accomplish many tasks, including:

- Interact with APIs (e.g., Cloudflare API, other REST APIs).
- Query databases or vector stores (like Autorag).
- Access environment resources (KV, R2, D1, Service Bindings).
- Perform specific computations or data transformations.

## Registering a Tool

Tools are registered using the `agent.server.tool()` method.

```typescript
// Import your Zod schemas
import { z } from 'zod'

import { getCloudflareClient } from '../cloudflare-api'
import { MISSING_ACCOUNT_ID_RESPONSE } from '../constants'
import { type CloudflareMcpAgent } from '../types/cloudflare-mcp-agent'
import { KvNamespaceIdSchema, KvNamespaceTitleSchema } from '../types/kv_namespace'

export function registerMyServiceTools(agent: CloudflareMcpAgent) {
	agent.server.tool(
		'tool_name', // String: Unique name for the tool
		'Detailed description', // String: Description for the LLM (CRITICAL!)
		{
			// Object: Parameter definitions using Zod schemas
			param1: MyParam1Schema,
			param2: MyParam2Schema.optional(),
			// ... other parameters
		},
		async (params) => {
			// Async Function: The implementation logic
			// params contains the validated parameters { param1, param2, ... }

			// --- Tool Logic Start ---
			try {
				// Access agent context if needed (e.g., account ID, credentials)
				const account_id = await agent.getActiveAccountId()
				if (!account_id) {
					return MISSING_ACCOUNT_ID_RESPONSE // Handle missing context
				}

				// Perform the action (e.g., call SDK, query DB)
				// const client = getCloudflareClient(agent.props.accessToken);
				// const result = await client.someService.someAction(...);

				// Format the successful response
				return {
					content: [
						{
							type: 'text',
							text: JSON.stringify({ success: true /*, result */ }),
						},
						// Or potentially EmbeddedResource for richer data
					],
				}
			} catch (error) {
				// Format the error response
				return {
					content: [
						{
							type: 'text',
							text: `Error performing action: ${error instanceof Error ? error.message : String(error)}`,
						},
					],
				}
			}
			// --- Tool Logic End ---
		}
	)

	// ... register other tools ...
}
```

### Key Components:

1.  **`toolName` (string):**

    - A unique identifier for the tool.
    - **Convention:** Use `snake_case`. Typically `service_noun_verb` (e.g., `kv_namespace_create`, `hyperdrive_config_list`, `docs_search`).

2.  **`description` (string - Max 1024 chars):**

    - **This is the MOST CRITICAL part for LLM interaction.** The LLM uses this description _exclusively_ to decide _when_ to use the tool and _what_ it does.
    - **A good description should include:**
      - **Core Purpose:** What does the tool _do_? (e.g., "List Hyperdrive configurations", "Search Cloudflare documentation").
      - **When to Use:** Provide clear scenarios or user intents that should trigger this tool. Use bullet points or clear instructions. (e.g., "Use this when a user asks to see their Hyperdrive setups", "Use this tool when: a user asks about Cloudflare products; you need info on a feature; you are unsure how to use Cloudflare functionality; you are writing Workers code and need docs").
      - **Inputs:** Briefly mention key inputs if not obvious from parameter names.
      - **Outputs:** Briefly describe what the tool returns (e.g., "Returns a list of namespace objects", "Returns search results as embedded resources").
      - **Example Workflows/Follow-ups (Optional but helpful):** Suggest how this tool fits into a larger task or what tools might be used next (e.g., "After creating a namespace with `kv_namespace_create`, you might bind it to a Worker.", "Use `hyperdrive_config_get` to view details before using `hyperdrive_config_edit`.").
    - **Be specific and unambiguous.** Avoid jargon unless it's essential domain terminology the LLM should understand.
    - **Keep it concise** while conveying necessary information.

3.  **`parameters` (object):**

    - An object mapping parameter names (keys) to their corresponding Zod schemas (values).
    - Follow the principles outlined in the `implementation-guides/type-validators.md` guide

4.  **`handlerFunction` (async function):**
    - The asynchronous function that executes the tool's logic.
    - It receives a single argument: an object (`params`) containing the validated parameters passed by the LLM, matching the keys defined in the `parameters` object.
    - **Implementation Details:**
      - **Access Context:** Use `agent.getActiveAccountId()`, `agent.props.accessToken`, `agent.env` (for worker bindings like AI, D1, R2) to get necessary credentials, environment variables, or bindings.
      - **Error Handling:** Wrap the core logic in a `try...catch` block to gracefully handle failures (e.g., API errors, network issues, invalid inputs not caught by Zod).
      - **Perform Action:** Interact with the relevant service (Cloudflare SDK, database, vector store, etc.).
      - **Format Response:** Return an object with a `content` property, which is an array of `ContentBlock` objects (usually `type: 'text'` or `type: 'resource'`).
        - For simple success/failure or structured data, `JSON.stringify` the result in a text block.
        - For richer data like search results, use `EmbeddedResource` (`type: 'resource'`) as seen in `docs-autorag.tools.ts`.
        - Return clear error messages in the `text` property of a content block upon failure.

## Best Practices

- **Clear Descriptions are Paramount:** Invest time in writing excellent tool descriptions. This has the biggest impact on the LLM's ability to use tools effectively.
- **Granular Tools:** Prefer smaller, focused tools over monolithic ones. (e.g., separate `_create`, `_list`, `_get`, `_update`, `_delete` tools for a resource).
- **Robust Error Handling:** Anticipate potential failures and return informative error messages to the LLM.
- **Consistent Naming:** Follow naming conventions for tools and parameters.
- **Use Zod Validators:** Leverage Zod for input validation as described in the validator guide.
- **Leverage Agent Context:** Use `agent.props`, `agent.env`, and helper methods like `agent.getActiveAccountId()` appropriately.
- **Statelessness:** Aim for tools to be stateless where possible. Rely on parameters and agent context for necessary information.
- **Security:** Be mindful of the actions tools perform, especially destructive ones (`delete`, `update`). Ensure proper authentication and authorization context is used (e.g., checking the active account ID).

```

--------------------------------------------------------------------------------
/apps/demo-day/frontend/public/mcp_demo_day.svg:
--------------------------------------------------------------------------------

```
<svg width="201" height="196" viewBox="0 0 201 196" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.57385 61.1384H16.0671L25.3485 83.7571H25.8165L35.098 61.1384H48.5912V101.072H37.9838V77.9854H37.6718L28.7803 100.76H22.3847L13.4932 77.8294H13.1812V101.072H2.57385V61.1384ZM90.4619 76.1135H79.4645C79.3865 75.2036 79.1785 74.3781 78.8405 73.6372C78.5156 72.8962 78.0606 72.2593 77.4756 71.7263C76.9037 71.1803 76.2082 70.7643 75.3892 70.4784C74.5703 70.1794 73.6408 70.0299 72.6009 70.0299C70.781 70.0299 69.2406 70.4719 67.9797 71.3558C66.7317 72.2398 65.7828 73.5072 65.1328 75.1581C64.4959 76.809 64.1774 78.7914 64.1774 81.1052C64.1774 83.5491 64.5024 85.5965 65.1523 87.2474C65.8153 88.8853 66.7707 90.1202 68.0187 90.9522C69.2666 91.7711 70.768 92.1806 72.5229 92.1806C73.5238 92.1806 74.4208 92.0571 75.2138 91.8101C76.0067 91.5501 76.6957 91.1797 77.2806 90.6987C77.8656 90.2177 78.3401 89.6392 78.704 88.9633C79.081 88.2743 79.3345 87.5009 79.4645 86.6429L90.4619 86.7209C90.3319 88.4108 89.8574 90.1332 89.0384 91.8881C88.2195 93.63 87.0561 95.2419 85.5481 96.7238C84.0532 98.1928 82.2008 99.3757 79.991 100.273C77.7811 101.17 75.2137 101.618 72.2889 101.618C68.6231 101.618 65.3343 100.832 62.4225 99.2587C59.5237 97.6858 57.2293 95.3719 55.5394 92.3171C53.8625 89.2623 53.024 85.525 53.024 81.1052C53.024 76.6595 53.882 72.9157 55.5979 69.8739C57.3138 66.8191 59.6277 64.5117 62.5395 62.9518C65.4513 61.3789 68.7011 60.5924 72.2889 60.5924C74.8108 60.5924 77.1311 60.9369 79.25 61.6259C81.3689 62.3148 83.2278 63.3223 84.8267 64.6482C86.4256 65.9611 87.7125 67.5795 88.6875 69.5034C89.6624 71.4273 90.2539 73.6307 90.4619 76.1135ZM94.8314 101.072V61.1384H112.068C115.032 61.1384 117.626 61.7234 119.848 62.8933C122.071 64.0632 123.8 65.7076 125.035 67.8265C126.27 69.9454 126.887 72.4218 126.887 75.2556C126.887 78.1154 126.251 80.5918 124.977 82.6847C123.716 84.7775 121.941 86.3894 119.653 87.5204C117.379 88.6513 114.72 89.2168 111.678 89.2168H101.383V80.7933H109.495C110.768 80.7933 111.854 80.5723 112.751 80.1303C113.661 79.6753 114.356 79.0319 114.837 78.1999C115.331 77.368 115.578 76.3865 115.578 75.2556C115.578 74.1117 115.331 73.1367 114.837 72.3308C114.356 71.5118 113.661 70.8878 112.751 70.4589C111.854 70.0169 110.768 69.7959 109.495 69.7959H105.673V101.072H94.8314Z" fill="white"/>
<path d="M17.939 148.516H2.57385V108.583H17.783C21.8907 108.583 25.4395 109.382 28.4293 110.981C31.4322 112.567 33.746 114.855 35.3709 117.845C37.0088 120.822 37.8278 124.39 37.8278 128.55C37.8278 132.709 37.0153 136.284 35.3904 139.274C33.7655 142.251 31.4647 144.539 28.4878 146.138C25.511 147.723 21.9947 148.516 17.939 148.516ZM13.4152 139.313H17.549C19.5249 139.313 21.2083 138.994 22.5992 138.357C24.0031 137.721 25.069 136.622 25.797 135.062C26.538 133.502 26.9084 131.331 26.9084 128.55C26.9084 125.768 26.5315 123.597 25.7775 122.037C25.0365 120.477 23.9446 119.379 22.5017 118.742C21.0718 118.105 19.3169 117.786 17.237 117.786H13.4152V139.313ZM42.2948 148.516V108.583H71.0751V117.318H53.1362V124.182H69.5932V132.917H53.1362V139.781H70.9971V148.516H42.2948ZM75.9028 108.583H89.3961L98.6775 131.201H99.1455L108.427 108.583H121.92V148.516H111.313V125.43H111.001L102.109 148.204H95.7137L86.8222 125.274H86.5102V148.516H75.9028V108.583ZM165.195 128.55C165.195 132.995 164.33 136.746 162.601 139.8C160.873 142.842 158.539 145.15 155.601 146.722C152.664 148.282 149.388 149.062 145.774 149.062C142.134 149.062 138.845 148.276 135.907 146.703C132.983 145.117 130.656 142.803 128.927 139.761C127.211 136.707 126.353 132.969 126.353 128.55C126.353 124.104 127.211 120.36 128.927 117.318C130.656 114.263 132.983 111.956 135.907 110.396C138.845 108.823 142.134 108.037 145.774 108.037C149.388 108.037 152.664 108.823 155.601 110.396C158.539 111.956 160.873 114.263 162.601 117.318C164.33 120.36 165.195 124.104 165.195 128.55ZM154.041 128.55C154.041 126.158 153.723 124.143 153.086 122.505C152.462 120.854 151.533 119.606 150.298 118.761C149.076 117.903 147.568 117.474 145.774 117.474C143.98 117.474 142.466 117.903 141.231 118.761C140.009 119.606 139.079 120.854 138.442 122.505C137.818 124.143 137.506 126.158 137.506 128.55C137.506 130.941 137.818 132.963 138.442 134.614C139.079 136.252 140.009 137.5 141.231 138.357C142.466 139.202 143.98 139.625 145.774 139.625C147.568 139.625 149.076 139.202 150.298 138.357C151.533 137.5 152.462 136.252 153.086 134.614C153.723 132.963 154.041 130.941 154.041 128.55Z" fill="white"/>
<path d="M17.939 195.961H2.57385V156.027H17.783C21.8907 156.027 25.4395 156.826 28.4293 158.425C31.4322 160.011 33.746 162.299 35.3709 165.289C37.0088 168.266 37.8278 171.834 37.8278 175.994C37.8278 180.154 37.0153 183.728 35.3904 186.718C33.7655 189.695 31.4647 191.983 28.4878 193.582C25.511 195.168 21.9947 195.961 17.939 195.961ZM13.4152 186.757H17.549C19.5249 186.757 21.2083 186.439 22.5992 185.802C24.0031 185.165 25.069 184.066 25.797 182.506C26.538 180.947 26.9084 178.776 26.9084 175.994C26.9084 173.212 26.5315 171.041 25.7775 169.481C25.0365 167.921 23.9446 166.823 22.5017 166.186C21.0718 165.549 19.3169 165.23 17.237 165.23H13.4152V186.757ZM50.148 195.961H38.4486L51.6299 156.027H66.449L79.6302 195.961H67.9309L59.1954 167.024H58.8835L50.148 195.961ZM47.9641 180.206H69.9588V188.317H47.9641V180.206ZM77.2141 156.027H89.3034L97.103 172.25H97.415L105.215 156.027H117.304L102.641 183.403V195.961H91.8773V183.403L77.2141 156.027Z" fill="white"/>
<path d="M81.2822 21.1399C80.9499 21.1399 80.6227 21.1515 80.2954 21.1631C80.2416 21.1659 80.1887 21.1782 80.1392 21.1997C80.0531 21.2286 79.9755 21.2786 79.9134 21.345C79.8514 21.4114 79.8068 21.4922 79.7837 21.5801L78.3783 26.4377C77.7719 28.526 77.9962 30.4547 79.0145 31.8701C79.9498 33.1809 81.5032 33.9484 83.3904 34.0381L91.014 34.4966C91.1214 34.5 91.2264 34.5287 91.3205 34.5804C91.4146 34.6321 91.4952 34.7053 91.5556 34.794C91.6177 34.8887 91.6569 34.9965 91.6701 35.109C91.6834 35.2214 91.6703 35.3354 91.632 35.4419C91.5701 35.6165 91.4591 35.7696 91.3123 35.8827C91.1656 35.9959 90.9894 36.0643 90.8047 36.0798L82.8837 36.5384C78.5809 36.7344 73.9509 40.2098 72.3262 44.4478L71.7547 45.9429C71.731 46.0051 71.7223 46.072 71.7295 46.1381C71.7367 46.2043 71.7594 46.2678 71.7959 46.3234C71.8324 46.379 71.8816 46.4252 71.9394 46.4581C71.9972 46.491 72.062 46.5098 72.1285 46.5127H99.4119C99.5699 46.5142 99.724 46.4637 99.8506 46.3692C99.9772 46.2747 100.069 46.1413 100.113 45.9894C100.597 44.2676 100.842 42.487 100.839 40.6982C100.839 29.8998 92.0906 21.1515 81.2955 21.1515" stroke="white" stroke-width="1.66129"/>
<path d="M69.3753 45.8848L69.8804 44.1289C70.4867 42.0389 70.2608 40.1119 69.2424 38.6948C68.3088 37.3857 66.7555 36.6182 64.8683 36.5284L29.1057 36.0699C28.9946 36.0679 28.8857 36.0398 28.7876 35.9878C28.6894 35.9359 28.6049 35.8616 28.5408 35.7709C28.4785 35.6763 28.439 35.5686 28.4255 35.4561C28.4119 35.3437 28.4247 35.2297 28.4627 35.123C28.5256 34.9478 28.6378 34.7946 28.7857 34.6817C28.9337 34.5688 29.1111 34.5011 29.2967 34.4867L65.3916 34.0282C69.6777 33.8322 74.3077 30.3568 75.9324 26.1188L77.9924 20.7362C78.0757 20.5077 78.0958 20.2609 78.0506 20.0219C75.7082 9.52417 66.3269 1.66129 55.1082 1.66129C44.77 1.66129 35.9983 8.33303 32.8485 17.6064C30.7224 16.0104 28.0717 15.277 25.4276 15.553C23.915 15.7086 22.4537 16.1886 21.1434 16.9601C19.8331 17.7317 18.7045 18.7766 17.8346 20.0238C16.9648 21.271 16.374 22.6911 16.1027 24.1873C15.8313 25.6835 15.8857 27.2206 16.2622 28.6938C8.16176 28.9347 1.66113 35.5715 1.66113 43.7252C1.66113 44.4644 1.71429 45.1904 1.82228 45.8981C1.84482 46.0649 1.92715 46.2178 2.05396 46.3284C2.18077 46.439 2.34344 46.4998 2.51171 46.4995H68.558C68.7431 46.4972 68.9225 46.4352 69.0695 46.3226C69.2165 46.21 69.3232 46.053 69.3737 45.8749" stroke="white" stroke-width="1.66129"/>
</svg>

```

--------------------------------------------------------------------------------
/apps/workers-observability/src/tools/workers-observability.tools.ts:
--------------------------------------------------------------------------------

```typescript
import { writeToString } from '@fast-csv/format'

import {
	handleWorkerLogsKeys,
	handleWorkerLogsValues,
	queryWorkersObservability,
} from '@repo/mcp-common/src/api/workers-observability.api'
import { getProps } from '@repo/mcp-common/src/get-props'
import {
	zKeysRequest,
	zQueryRunRequest,
	zValuesRequest,
} from '@repo/mcp-common/src/types/workers-logs.types'

import type { ObservabilityMCP } from '../workers-observability.app'

/**
 * Registers the logs analysis tool with the MCP server
 * @param server The MCP server instance
 * @param accountId Cloudflare account ID
 * @param apiToken Cloudflare API token
 */
export function registerObservabilityTools(agent: ObservabilityMCP) {
	// Register the worker logs analysis tool by worker name
	agent.server.tool(
		'query_worker_observability',
		`Query the Workers Observability API to analyze logs and metrics from your Cloudflare Workers.

	* A query typical query looks like this:
				{"view":"events","queryId":"workers-logs-events","limit":5,"dry":true,"parameters":{"datasets":["cloudflare-workers"],"filters":[{"id":"520","key":"message","operation":"eq","type":"string","value":"Clickhouse Statistics"},{"id":"2088","key":"statistics.elapsed","operation":"gt","type":"number","value":"0.269481519"}],"calculations":[],"groupBys":[],"havings":[]},"timeframe":{"to":"2025-04-30T20:53:15Z","from":" ""2025-04-30T19:53:15Z"}}
## Core Capabilities
This tool provides three primary views of your Worker data:
1. **List Events** - Browse individual request logs and errors
2. **Calculate Metrics** - Compute statistics across requests (avg, p99, etc.)
3. **Find Specific Invocations** - Locate individual requests matching criteria

## Filtering Best Practices
- Before applying filters, use the observability_keys and observability_values tools to confirm available filter fields and the correct filter value to add unless you have the data in a response from a previous query.
- Common filter fields:  $metadata.service, $metadata.trigger, $metadata.message, $metadata.level, $metadata.requestId,

## Calculation Best Practices
- Before applying calculations, use the observability_keys tools to confirm key that should be used for the calculation

## Troubleshooting
- If no results are returned, suggest broadening the time range or relaxing filters
- For errors about invalid fields, recommend using observability_keys to see available options
`,

		{
			query: zQueryRunRequest,
		},
		async ({ query }) => {
			const accountId = await agent.getActiveAccountId()
			if (!accountId) {
				return {
					content: [
						{
							type: 'text',
							text: 'No currently active accountId. Try listing your accounts (accounts_list) and then setting an active account (set_active_account)',
						},
					],
				}
			}
			try {
				const props = getProps(agent)
				const response = await queryWorkersObservability(props.accessToken, accountId, query)

				if (query.view === 'calculations') {
					let data = ''
					for (const calculation of response?.calculations || []) {
						const alias = calculation.alias || calculation.calculation
						const aggregates = calculation.aggregates.map((agg) => {
							const keys = agg.groups?.reduce(
								(acc, group) => {
									acc[`${group.key}`] = `${group.value}`
									return acc
								},
								{} as Record<string, string>
							)
							return {
								...keys,
								[alias]: agg.value,
							}
						})

						const aggregatesString = await writeToString(aggregates, {
							headers: true,
							delimiter: '\t',
						})

						const series = calculation.series.map(({ time, data }) => {
							return {
								time,
								...data.reduce(
									(acc, point) => {
										const key = point.groups?.reduce((acc, group) => {
											return `${acc} * ${group.value}`
										}, '')
										if (!key) {
											return {
												...acc,
												[alias]: point.value,
											}
										}
										return {
											...acc,
											key,
											[alias]: point.value,
										}
									},
									{} as Record<string, string | number | undefined>
								),
							}
						})
						const seriesString = await writeToString(series, { headers: true, delimiter: '\t' })
						data = data + '\n' + `## ${alias}`
						data = data + '\n' + `### Aggregation`
						data = data + '\n' + aggregatesString
						data = data + '\n' + `### Series`
						data = data + '\n' + seriesString
					}

					return {
						content: [
							{
								type: 'text',
								text: data,
							},
						],
					}
				}

				if (query.view === 'events') {
					const events = response?.events?.events
					return {
						content: [
							{
								type: 'text',
								text: JSON.stringify(events),
							},
						],
					}
				}

				if (query.view === 'invocations') {
					const invocations = Object.entries(response?.invocations || {}).map(([_, logs]) => {
						const invocationLog = logs.find((log) => log.$metadata.type === 'cf-worker-event')
						return invocationLog?.$metadata ?? logs[0]?.$metadata
					})

					const tsv = await writeToString(invocations, { headers: true, delimiter: '\t' })
					return {
						content: [
							{
								type: 'text',
								text: tsv,
							},
						],
					}
				}
				return {
					content: [
						{
							type: 'text',
							text: JSON.stringify(response),
						},
					],
				}
			} catch (error) {
				return {
					content: [
						{
							type: 'text',
							text: JSON.stringify({
								error: `Error analyzing worker logs: ${error instanceof Error && error.message}`,
							}),
						},
					],
				}
			}
		}
	)

	agent.server.tool(
		'observability_keys',
		`Find keys in the Workers Observability Data

## Best Practices
- Set a high limit (1000+) to ensure you see all available keys
- Add the $metadata.service filter to narrow results to a specific Worker

## Troubleshooting
- If expected fields are missing, verify the Worker is actively logging
- For empty results, try broadening your time range
`,
		{ keysQuery: zKeysRequest },
		async ({ keysQuery }) => {
			const accountId = await agent.getActiveAccountId()
			if (!accountId) {
				return {
					content: [
						{
							type: 'text',
							text: 'No currently active accountId. Try listing your accounts (accounts_list) and then setting an active account (set_active_account)',
						},
					],
				}
			}
			try {
				const props = getProps(agent)
				const result = await handleWorkerLogsKeys(props.accessToken, accountId, keysQuery)

				const tsv = await writeToString(
					result.map((key) => ({ type: key.type, key: key.key })),
					{ headers: true, delimiter: '\t' }
				)
				return {
					content: [
						{
							type: 'text',
							text: tsv,
						},
					],
				}
			} catch (error) {
				return {
					content: [
						{
							type: 'text',
							text: JSON.stringify({
								error: `Error retrieving worker telemetry keys: ${error instanceof Error && error.message}`,
							}),
						},
					],
				}
			}
		}
	)

	agent.server.tool(
		'observability_values',
		`Find values in the Workers Observability Data.

## Troubleshooting
- For no results, verify the field exists using observability_keys first
- If expected values are missing, try broadening your time range`,
		{ valuesQuery: zValuesRequest },
		async ({ valuesQuery }) => {
			const accountId = await agent.getActiveAccountId()
			if (!accountId) {
				return {
					content: [
						{
							type: 'text',
							text: 'No currently active accountId. Try listing your accounts (accounts_list) and then setting an active account (set_active_account)',
						},
					],
				}
			}
			try {
				const props = getProps(agent)
				const result = await handleWorkerLogsValues(props.accessToken, accountId, valuesQuery)
				const tsv = await writeToString(
					result?.map((value) => ({ type: value.type, value: value.value })) || [],
					{ headers: true, delimiter: '\t' }
				)
				return {
					content: [
						{
							type: 'text',
							text: tsv,
						},
					],
				}
			} catch (error) {
				return {
					content: [
						{
							type: 'text',
							text: JSON.stringify({
								error: `Error retrieving worker telemetry values: ${error instanceof Error && error.message}`,
							}),
						},
					],
				}
			}
		}
	)
}

```

--------------------------------------------------------------------------------
/implementation-guides/evals.md:
--------------------------------------------------------------------------------

```markdown
# Evaluation Implementation Guide

This guide explains how to create evaluation tests (`.eval.ts` files) for testing AI model interactions with specific tools or systems, such as Cloudflare Worker bindings or container environments.

## What are Evals?

Evals are automated tests designed to verify if an AI model correctly understands instructions and utilizes its available "tools" (functions, API calls, environment interactions) to achieve a desired outcome. They assess the model's ability to follow instructions, select appropriate tools, and provide correct arguments to those tools.

## Core Concepts

Evals are typically built using a testing framework like `vitest` combined with specialized evaluation libraries like `vitest-evals`. The main structure revolves around `describeEval`:

```typescript
import { expect } from 'vitest'
import { describeEval } from 'vitest-evals'

import { checkFactuality } from '@repo/eval-tools/src/scorers'
import { eachModel } from '@repo/eval-tools/src/test-models'

import { initializeClient, runTask } from './utils' // Helper functions

eachModel('$modelName', ({ model }) => {
	// Optional: Run tests for multiple models
	describeEval('A descriptive name for the evaluation suite', {
		data: async () => [
			/* Test cases */
		],
		task: async (input) => {
			/* Test logic */
		},
		scorers: [
			/* Scoring functions */
		],
		threshold: 1, // Passing score threshold
		timeout: 60000, // Test timeout
	})
})
```

### Key Parts:

1.  **`describeEval(name, options)`**: Defines a suite of evaluation tests.

    - `name`: A string describing the purpose of the eval suite.
    - `options`: An object containing the configuration for the eval:
      - **`data`**: An async function returning an array of test case objects. Each object typically contains:
        - `input`: (string) The instruction given to the AI model.
        - `expected`: (string) A natural language description of the _expected_ sequence of actions or outcome. This is used by scorers.
      - **`task`**: An async function that executes the actual test logic for a given `input`. It orchestrates the interaction with the AI/system and performs assertions.
      - **`scorers`**: An array of scoring functions (e.g., `checkFactuality`) that evaluate the test outcome based on the `promptOutput` from the `task` and the `expected` string from the `data`.
      - **`threshold`**: (number, usually between 0 and 1) The minimum score required from the scorers for the test case to pass. A threshold of `1` means a perfect score is required.
      - **`timeout`**: (number) Maximum time in milliseconds allowed for a single test case.

2.  **`task(input)` Function**: The heart of the eval. It typically involves:

    - **Setup**: Initializing a client or test environment (`initializeClient`). This prepares the system for the test, configuring available tools or connections.
    - **Execution**: Running the actual interaction (`runTask`). This function sends the `input` instruction to the AI model via the client and captures the results, which usually include:
      - `promptOutput`: The textual response from the AI model.
      - `toolCalls`: A structured list of the tools the AI invoked, along with the arguments passed to each tool.
    - **Assertions (`expect`)**: Using the testing framework's assertion library (`vitest`'s `expect` in the examples) to verify that the correct tools were called with the correct arguments based on the `toolCalls` data. Sometimes, this involves direct interaction with the system state (e.g., reading a file created by a tool) to confirm the outcome.
    - **Return Value**: The `task` function usually returns the `promptOutput` to be evaluated by the `scorers`.

3.  **Scoring (`checkFactuality`, etc.)**: Automated functions that compare the actual outcome (represented by the `promptOutput` and implicitly by the assertions passed within the `task`) against the `expected` description.

4.  **Helper Utilities (`./utils`)**:
    - `initializeClient()`: Sets up the testing environment, connects to the system under test, and configures the available tools for the AI model.
    - `runTask(client, model, input)`: Sends the input prompt to the specified AI model using the configured client, executes the model's reasoning and tool use, and returns the results (`promptOutput`, `toolCalls`).
    - `eachModel()`: (Optional) A utility to run the same evaluation suite against multiple different AI models.

## Steps to Implement Evals

1.  **Identify Tools:** Define the specific actions or functions (the "tools") that the AI should be able to use within the system you're testing (e.g., `kv_write`, `d1_query`, `container_exec`).
2.  **Create Helper Functions:** Implement your `initializeClient` and `runTask` (or similarly named) functions.
    - `initializeClient`: Should set up the necessary context, potentially using test environments like `vitest-environment-miniflare` for workers. It needs to make the defined tools available to the AI model simulation.
    - `runTask`: Needs to simulate the AI processing: take an input prompt, interact with an LLM (or a mock) configured with the tools, capture which tools are called and with what arguments, and capture the final text output.
3.  **Create Eval File (`*.eval.ts`):** Create a new file (e.g., `kv-operations.eval.ts`).
4.  **Import Dependencies:** Import `describeEval`, scorers, helpers, `expect`, etc.
5.  **Structure with `describeEval`:** Define your evaluation suite.
6.  **Define Test Cases (`data`):** Write specific test scenarios:
    - Provide clear, unambiguous `input` prompts that target the tools you want to test.
    - Write concise `expected` descriptions detailing the primary tool calls or outcomes anticipated.
7.  **Implement the `task` Function:**
    - Call `initializeClient`.
    - Call `runTask` with the `input`.
    - Write `expect` assertions to rigorously check:
      - Were the correct tools called? (`toolName`)
      - Were they called in the expected order (if applicable)?
      - Were the arguments passed to the tools correct? (`args`)
      - (Optional) Interact with the system state if necessary to verify side effects.
    - Return the `promptOutput`.
8.  **Configure Scorers and Threshold:** Choose appropriate scorers (often `checkFactuality`) and set a `threshold`.
9.  **Run Tests:** Execute the evals using your test runner (e.g., `vitest run`).

## Example Structure (Simplified)

```typescript
// my-feature.eval.ts
import { expect } from 'vitest'
import { describeEval } from 'vitest-evals'

import { checkFactuality } from '@repo/eval-tools/src/scorers'

import { initializeClient, runTask } from './utils'

describeEval('Tests My Feature Tool Interactions', {
	data: async () => [
		{
			input: 'Use my_tool to process the data "example"',
			expected: 'The my_tool tool was called with data set to "example"',
		},
		// ... more test cases
	],
	task: async (input) => {
		const client = await initializeClient() // Sets up environment with my_tool
		const { promptOutput, toolCalls } = await runTask(client, 'your-model', input)

		// Check if my_tool was called
		const myToolCall = toolCalls.find((call) => call.toolName === 'my_tool')
		expect(myToolCall).toBeDefined()

		// Check arguments passed to my_tool
		expect(myToolCall?.args).toEqual(
			expect.objectContaining({
				data: 'example',
				// ... other expected args
			})
		)

		return promptOutput // Return AI output for scoring
	},
	scorers: [checkFactuality],
	threshold: 1,
})
```

## Best Practices

- **Clear Inputs:** Write inputs as clear, actionable instructions.
- **Specific Expected Outcomes:** Make `expected` descriptions precise enough for scorers but focus on the key actions.
- **Targeted Assertions:** Use `expect` to verify the most critical aspects of tool calls (tool name, key arguments). Don't over-assert on trivial details unless necessary.
- **Isolate Tests:** Ensure each test case in `data` tests a specific interaction or a small sequence of interactions.
- **Helper Functions:** Keep `initializeClient` and `runTask` generic enough to be reused across different eval files for the same system.
- **Use `expect.objectContaining` or `expect.stringContaining`:** Often, you only need to verify _parts_ of the arguments, not the entire structure, making tests less brittle.
- **Descriptive Names:** Use clear names for `describeEval` blocks and meaningful `input`/`expected` strings.

```

--------------------------------------------------------------------------------
/packages/mcp-common/src/cloudflare-oauth-handler.ts:
--------------------------------------------------------------------------------

```typescript
import { zValidator } from '@hono/zod-validator'
import { Hono } from 'hono'
import { z } from 'zod'

import { AuthUser } from '../../mcp-observability/src'
import { getAuthorizationURL, getAuthToken, refreshAuthToken } from './cloudflare-auth'
import { McpError } from './mcp-error'
import { useSentry } from './sentry'
import { V4Schema } from './v4-api'

import type {
	OAuthHelpers,
	TokenExchangeCallbackOptions,
	TokenExchangeCallbackResult,
} from '@cloudflare/workers-oauth-provider'
import type { Context } from 'hono'
import type { MetricsTracker } from '../../mcp-observability/src'
import type { BaseHonoContext } from './sentry'

type AuthContext = {
	Bindings: {
		OAUTH_PROVIDER: OAuthHelpers
		CLOUDFLARE_CLIENT_ID: string
		CLOUDFLARE_CLIENT_SECRET: string
	}
} & BaseHonoContext

const AuthRequestSchema = z.object({
	responseType: z.string(),
	clientId: z.string(),
	redirectUri: z.string(),
	scope: z.array(z.string()),
	state: z.string(),
	codeChallenge: z.string().optional(),
	codeChallengeMethod: z.string().optional(),
})

// AuthRequest but with extra params that we use in our authentication logic
const AuthRequestSchemaWithExtraParams = AuthRequestSchema.merge(
	z.object({ codeVerifier: z.string() })
)

const AuthQuery = z.object({
	code: z.string().describe('OAuth code from CF dash'),
	state: z.string().describe('Value of the OAuth state'),
	scope: z.string().describe('OAuth scopes granted'),
})

type UserSchema = z.infer<typeof UserSchema>
const UserSchema = z.object({
	id: z.string(),
	email: z.string(),
})
const AccountSchema = z.object({
	name: z.string(),
	id: z.string(),
})
type AccountsSchema = z.infer<typeof AccountsSchema>
const AccountsSchema = z.array(AccountSchema)

const AccountAuthProps = z.object({
	type: z.literal('account_token'),
	accessToken: z.string(),
	account: AccountSchema,
})
const UserAuthProps = z.object({
	type: z.literal('user_token'),
	accessToken: z.string(),
	user: UserSchema,
	accounts: AccountsSchema,
	refreshToken: z.string().optional(),
})
export type AuthProps = z.infer<typeof AuthProps>
const AuthProps = z.discriminatedUnion('type', [AccountAuthProps, UserAuthProps])

export async function getUserAndAccounts(
	accessToken: string,
	devModeHeaders?: HeadersInit
): Promise<{ user: UserSchema | null; accounts: AccountsSchema }> {
	const headers = devModeHeaders
		? devModeHeaders
		: {
				Authorization: `Bearer ${accessToken}`,
			}

	// Fetch the user & accounts info from Cloudflare
	const [userResponse, accountsResponse] = await Promise.all([
		fetch('https://api.cloudflare.com/client/v4/user', {
			headers,
		}),
		fetch('https://api.cloudflare.com/client/v4/accounts', {
			headers,
		}),
	])

	const { result: user } = V4Schema(UserSchema).parse(await userResponse.json())
	const { result: accounts } = V4Schema(AccountsSchema).parse(await accountsResponse.json())
	if (!user || !userResponse.ok) {
		// If accounts is present, then assume that we have an account scoped token
		if (accounts !== null) {
			return { user: null, accounts }
		}
		console.log(user)
		throw new McpError('Failed to fetch user', 500, { reportToSentry: true })
	}
	if (!accounts || !accountsResponse.ok) {
		console.log(accounts)
		throw new McpError('Failed to fetch accounts', 500, { reportToSentry: true })
	}

	return { user, accounts }
}

/**
 * Exchanges an OAuth authorization code for access and refresh tokens, then fetches user and account details.
 *
 * @param c - Hono context containing OAuth environment variables (client ID/secret)
 * @param code - OAuth authorization code received from the authorization server
 * @param code_verifier - PKCE code verifier used to validate the authorization request
 * @returns Promise resolving to an object containing access token, refresh token, user profile, and accounts
 */
async function getTokenAndUserDetails(
	c: Context<AuthContext>,
	code: string,
	code_verifier: string
): Promise<{
	accessToken: string
	refreshToken: string
	user: UserSchema
	accounts: AccountsSchema
}> {
	// Exchange the code for an access token
	const { access_token: accessToken, refresh_token: refreshToken } = await getAuthToken({
		client_id: c.env.CLOUDFLARE_CLIENT_ID,
		client_secret: c.env.CLOUDFLARE_CLIENT_SECRET,
		redirect_uri: new URL('/oauth/callback', c.req.url).href,
		code,
		code_verifier,
	})

	const { user, accounts } = await getUserAndAccounts(accessToken)
	// User cannot be null for OAuth flow
	if (user === null) {
		throw new McpError('Failed to fetch user', 500, { reportToSentry: true })
	}

	return { accessToken, refreshToken, user, accounts }
}

export async function handleTokenExchangeCallback(
	options: TokenExchangeCallbackOptions,
	clientId: string,
	clientSecret: string
): Promise<TokenExchangeCallbackResult | undefined> {
	// options.props contains the current props
	if (options.grantType === 'refresh_token') {
		const props = AuthProps.parse(options.props)
		if (props.type === 'account_token') {
			// Refreshing an account_token should not be possible, as we only do this for user tokens
			throw new McpError('Internal Server Error', 500)
		}
		if (!props.refreshToken) {
			throw new McpError('Missing refreshToken', 500)
		}

		// handle token refreshes
		const {
			access_token: accessToken,
			refresh_token: refreshToken,
			expires_in,
		} = await refreshAuthToken({
			client_id: clientId,
			client_secret: clientSecret,
			refresh_token: props.refreshToken,
		})

		return {
			newProps: {
				...options.props,
				accessToken,
				refreshToken,
			} satisfies AuthProps,
			accessTokenTTL: expires_in,
		}
	}
}

/**
 * Creates a Hono app with OAuth routes for a specific Cloudflare worker
 *
 * @param scopes optional subset of scopes to request when handling authorization requests
 * @param metrics MetricsTracker which is used to track auth metrics
 * @returns a Hono app with configured OAuth routes
 */
export function createAuthHandlers({
	scopes,
	metrics,
}: {
	scopes: Record<string, string>
	metrics: MetricsTracker
}) {
	{
		const app = new Hono<AuthContext>()
		app.use(useSentry)
		// TODO: Add useOnError middleware rather than handling errors in each handler
		// app.onError(useOnError)
		/**
		 * OAuth Authorization Endpoint
		 *
		 * This route initiates the Cloudflare OAuth flow when a user wants to log in.
		 * It creates a random state parameter to prevent CSRF attacks and stores the
		 * original OAuth request information in KV storage for later retrieval.
		 * Then it redirects the user to Cloudflare's authorization page with the appropriate
		 * parameters so the user can authenticate and grant permissions.
		 */
		app.get(`/oauth/authorize`, async (c) => {
			try {
				const oauthReqInfo = await c.env.OAUTH_PROVIDER.parseAuthRequest(c.req.raw)
				oauthReqInfo.scope = Object.keys(scopes)
				if (!oauthReqInfo.clientId) {
					return c.text('Invalid request', 400)
				}
				const res = await getAuthorizationURL({
					client_id: c.env.CLOUDFLARE_CLIENT_ID,
					redirect_uri: new URL('/oauth/callback', c.req.url).href,
					state: oauthReqInfo,
					scopes,
				})

				return Response.redirect(res.authUrl, 302)
			} catch (e) {
				c.var.sentry?.recordError(e)
				if (e instanceof Error) {
					metrics.logEvent(
						new AuthUser({
							errorMessage: `Authorize Error: ${e.name}: ${e.message}`,
						})
					)
				}
				if (e instanceof McpError) {
					return c.text(e.message, { status: e.code })
				}
				console.error(e)
				return c.text('Internal Error', 500)
			}
		})

		/**
		 * OAuth Callback Endpoint
		 *
		 * This route handles the callback from Cloudflare after user authentication.
		 * It exchanges the temporary code for an access token, then stores some
		 * user metadata & the auth token as part of the 'props' on the token passed
		 * down to the client. It ends by redirecting the client back to _its_ callback URL
		 */
		app.get(`/oauth/callback`, zValidator('query', AuthQuery), async (c) => {
			try {
				const { state, code } = c.req.valid('query')
				const oauthReqInfo = AuthRequestSchemaWithExtraParams.parse(JSON.parse(atob(state)))
				// Get the oathReqInfo out of KV
				if (!oauthReqInfo.clientId) {
					throw new McpError('Invalid State', 400)
				}

				const [{ accessToken, refreshToken, user, accounts }] = await Promise.all([
					getTokenAndUserDetails(c, code, oauthReqInfo.codeVerifier),
					c.env.OAUTH_PROVIDER.createClient({
						clientId: oauthReqInfo.clientId,
						tokenEndpointAuthMethod: 'none',
					}),
				])

				// TODO: Implement auth restriction in staging
				// if (
				// 	!user.email.endsWith("@cloudflare.com") &&
				// 	!(c.env.PERMITTED_USERS ?? []).includes(user.email)
				// ) {
				// 	throw new McpError(
				// 		`This user ${user.email} is not allowed to access this restricted MCP server`,
				// 		401,
				// 	);
				// }

				// Return back to the MCP client a new token
				const { redirectTo } = await c.env.OAUTH_PROVIDER.completeAuthorization({
					request: oauthReqInfo,
					userId: user.id,
					metadata: {
						label: user.email,
					},
					scope: oauthReqInfo.scope,
					props: {
						type: 'user_token',
						user,
						accounts,
						accessToken,
						refreshToken,
					} satisfies AuthProps,
				})

				metrics.logEvent(
					new AuthUser({
						userId: user.id,
					})
				)

				return Response.redirect(redirectTo, 302)
			} catch (e) {
				c.var.sentry?.recordError(e)
				if (e instanceof Error) {
					console.error(e)
					metrics.logEvent(
						new AuthUser({
							errorMessage: `Callback Error: ${e.name}: ${e.message}`,
						})
					)
				}
				if (e instanceof McpError) {
					return c.text(e.message, { status: e.code })
				}
				return c.text('Internal Error', 500)
			}
		})

		return app
	}
}

```

--------------------------------------------------------------------------------
/apps/radar/src/types/radar.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * This file contains the validators for the radar tools.
 */
import { z } from 'zod'

import type { HTTPTimeseriesParams, RankingTopParams } from 'cloudflare/resources/radar'
import type { ASNListParams } from 'cloudflare/resources/radar/entities'

export const AsnParam = z
	.number()
	.positive()
	.describe('Autonomous System Number (ASN), must be a positive number.')

export const IpParam = z.string().ip().describe('IPv4 or IPv6 address in standard notation.')

export const DomainParam = z
	.string()
	.regex(
		/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])$/
	)
	.describe(
		'A valid domain name, e.g., example.com or sub.domain.co.uk. Must follow standard domain formatting rules without protocol or path.'
	)

export const DomainRankingTypeParam: z.ZodType<RankingTopParams['rankingType']> = z
	.enum(['POPULAR', 'TRENDING_RISE', 'TRENDING_STEADY'])
	.describe('The ranking type.')

export const InternetServicesCategoryParam = z
	.array(
		z.enum([
			'Generative AI',
			'E-commerce',
			'Cryptocurrency Services',
			'Email',
			'Fast Fashion',
			'Financial Services',
			'News',
			'Social Media',
			'Weather',
			'Jobs',
			'Low cost E-commerce',
			'Messaging',
			'Metaverse & Gaming',
		])
	)
	.describe('Filters results by Internet service category.')

export const DateParam = z.string().date().describe('Filters results by date.')

export const DateListParam = z.array(DateParam).describe('Filters results by date.')

export const DateRangeParam = z
	.string()
	.toLowerCase()
	.regex(
		/^((([1-9]|[1-9][0-9]|[1-2][0-9][0-9]|3[0-5][0-9]|36[0-4])d(control)?)|(([1-9]|[1-4][0-9]|5[0-2])w(control)?))$/,
		'Invalid Date Range'
	)
	.describe(
		'Filters results by date range. ' +
			'For example, use `7d` and `7dcontrol` to compare this week with the previous week. ' +
			'Use this parameter or set specific start and end dates (`dateStart` and `dateEnd` parameters).'
	)

export const DateRangeArrayParam: z.ZodType<HTTPTimeseriesParams['dateRange']> = z
	.array(DateRangeParam)
	.describe(
		'Filters results by date range. ' +
			'For example, use `7d` and `7dcontrol` to compare this week with the previous week. ' +
			'Use this parameter or set specific start and end dates (`dateStart` and `dateEnd` parameters).'
	)

export const DateStartParam = z
	.string()
	.datetime()
	.describe(
		'Start date in ISO 8601 format (e.g., 2023-04-01T00:00:00Z). ' +
			'Either use this parameter together with `dateEnd`, or use `dateRange`.'
	)

export const DateStartArrayParam: z.ZodType<HTTPTimeseriesParams['dateStart']> = z
	.array(DateStartParam)
	.describe(
		'Start of the date range. ' +
			'Either use this parameter together with `dateEnd` or use `dateRange`.'
	)

export const DateEndParam = z
	.string()
	.datetime()
	.describe(
		'End date in ISO 8601 format (e.g., 2023-04-30T23:59:59Z). ' +
			'Either use this parameter together with `dateStart`, or use `dateRange`.'
	)

export const DateEndArrayParam: z.ZodType<HTTPTimeseriesParams['dateEnd']> = z
	.array(DateEndParam)
	.describe(
		'End of the date range. ' +
			'Either use this parameter together with `dateStart`, or use `dateRange`.'
	)

export const LocationParam = z
	.string()
	.regex(/^[a-zA-Z]{2}$/, {
		message:
			'Invalid location code. Must be a valid alpha-2 location code (two letters, case insensitive).',
	})
	.describe('Filters results by location. Specify a valid alpha-2 location code.')

export const LocationListParam: z.ZodType<RankingTopParams['location']> = z
	.array(LocationParam)
	.describe(
		'Filters results by location. Specify a comma-separated list of alpha-2 location codes.'
	)

export const LocationArrayParam: z.ZodType<HTTPTimeseriesParams['location']> = z
	.array(
		z.string().regex(/^(-?[a-zA-Z]{2})$/, {
			message: 'Each value must be a valid alpha-2 location code, optionally prefixed with `-`.',
		})
	)
	.optional()
	.describe(
		'Filters results by location. Provide an array of alpha-2 country codes (e.g., "US", "PT"). ' +
			'Prefix a code with `-` to exclude it (e.g., ["-US", "PT"] excludes the US and includes Portugal).'
	)

export const ContinentArrayParam: z.ZodType<HTTPTimeseriesParams['continent']> = z
	.array(
		z.string().regex(/^(-?[a-zA-Z]{2})$/, {
			message: 'Each value must be a valid alpha-2 continent code, optionally prefixed with `-`.',
		})
	)
	.optional()
	.describe(
		'Filters results by continent. Provide an array of alpha-2 continent codes (e.g., "EU", "NA"). ' +
			'Prefix a code with `-` to exclude it (e.g., ["-EU", "NA"] excludes Europe and includes North America).'
	)

export const AsnArrayParam: z.ZodType<HTTPTimeseriesParams['asn']> = z
	.array(z.string().refine((val) => val !== '0', { message: 'ASN cannot be 0' }))
	.optional()
	.describe(
		'Filters results by ASN. Provide an array of ASN strings. ' +
			'Prefix with `-` to exclude (e.g., ["-174", "3356"] excludes AS174 and includes AS3356). '
	)

export const AsOrderByParam: z.ZodType<ASNListParams['orderBy']> = z
	.enum(['ASN', 'POPULATION'])
	.optional()
	.describe('Optional order by parameter: "ASN" or "POPULATION".')

export const HttpDimensionParam = z
	.enum([
		'timeseries',
		'summary/deviceType',
		'summary/httpProtocol',
		'summary/httpVersion',
		'summary/botClass',
		'summary/ipVersion',
		'summary/tlsVersion',
		'summary/os',
		'summary/postQuantum',
		'top/browser', // TODO replace with "summary/browser" and "summary/browserFamily" once available on the lib
		'top/browserFamily',
		'timeseriesGroups/deviceType',
		'timeseriesGroups/httpProtocol',
		'timeseriesGroups/httpVersion',
		'timeseriesGroups/botClass',
		'timeseriesGroups/ipVersion',
		'timeseriesGroups/tlsVersion',
		'timeseriesGroups/os',
		'timeseriesGroups/postQuantum',
		'timeseriesGroups/browser',
		'timeseriesGroups/browserFamily',
		'top/locations',
		'top/ases',
	])
	.describe('Dimension indicating the type and format of HTTP data to retrieve.')

export const DnsDimensionParam = z
	.enum([
		'timeseries',
		'summary/ipVersion',
		'summary/cacheHit',
		'summary/dnssec',
		'summary/dnssecAware',
		'summary/matchingAnswer',
		'summary/protocol',
		'summary/queryType',
		'summary/responseCode',
		'summary/responseTTL',
		'timeseriesGroups/ipVersion',
		'timeseriesGroups/cacheHit',
		'timeseriesGroups/dnssecAware',
		'timeseriesGroups/matchingAnswer',
		'timeseriesGroups/protocol',
		'timeseriesGroups/queryType',
		'timeseriesGroups/responseCode',
		'timeseriesGroups/responseTTL',
		'top/locations',
		'top/ases',
	])
	.describe('Dimension indicating the type and format of DNS data to retrieve.')

export const L7AttackDimensionParam = z
	.enum([
		'timeseries',
		'summary/httpMethod',
		'summary/httpVersion',
		'summary/ipVersion',
		'summary/managedRules',
		'summary/mitigationProduct',
		'top/vertical', // TODO replace with "summary/vertical" and "summary/industry" once available on the lib
		'top/industry',
		'timeseriesGroups/httpMethod',
		'timeseriesGroups/httpVersion',
		'timeseriesGroups/ipVersion',
		'timeseriesGroups/managedRules',
		'timeseriesGroups/mitigationProduct',
		'timeseriesGroups/vertical',
		'timeseriesGroups/industry',
		'top/locations/origin',
		'top/locations/target',
		'top/ases/origin',
		'top/attacks',
	])
	.describe('Dimension indicating the type and format of L7 attack data to retrieve.')

export const L3AttackDimensionParam = z
	.enum([
		'timeseries',
		'summary/protocol',
		'summary/ipVersion',
		'summary/vector',
		'summary/bitrate',
		'summary/duration',
		'top/vertical', // TODO replace with "summary/vertical" and "summary/industry" once available on the lib
		'top/industry',
		'timeseriesGroups/protocol',
		'timeseriesGroups/ipVersion',
		'timeseriesGroups/vector',
		'timeseriesGroups/bitrate',
		'timeseriesGroups/duration',
		'timeseriesGroups/vertical',
		'timeseriesGroups/industry',
		'top/locations/origin',
		'top/locations/target',
		'top/attacks',
	])
	.describe('Dimension indicating the type and format of L3 attack data to retrieve.')

export const EmailRoutingDimensionParam = z
	.enum([
		'summary/ipVersion',
		'summary/encrypted',
		'summary/arc',
		'summary/dkim',
		'summary/dmarc',
		'summary/spf',
		'timeseriesGroups/ipVersion',
		'timeseriesGroups/encrypted',
		'timeseriesGroups/arc',
		'timeseriesGroups/dkim',
		'timeseriesGroups/dmarc',
		'timeseriesGroups/spf',
	])
	.describe('Dimension indicating the type and format of Email Routing data to retrieve.')

export const EmailSecurityDimensionParam = z
	.enum([
		'summary/spam',
		'summary/malicious',
		'summary/spoof',
		'summary/threatCategory',
		'summary/arc',
		'summary/dkim',
		'summary/dmarc',
		'summary/spf',
		'summary/tlsVersion',
		'timeseriesGroups/spam',
		'timeseriesGroups/malicious',
		'timeseriesGroups/spoof',
		'timeseriesGroups/threatCategory',
		'timeseriesGroups/arc',
		'timeseriesGroups/dkim',
		'timeseriesGroups/dmarc',
		'timeseriesGroups/spf',
		'timeseriesGroups/tlsVersion',
		'top/tlds',
	])
	.describe('Dimension indicating the type and format of Email Security data to retrieve.')

export const AiDimensionParam = z
	.enum([
		'bots/summary/userAgent',
		'bots/timeseriesGroups/userAgent',
		'inference/summary/model',
		'inference/summary/task',
		'inference/timeseriesGroups/model',
		'inference/timeseriesGroups/task',
	])
	.describe('Dimension indicating the type and format of AI data to retrieve.')

export const InternetSpeedDimensionParam = z
	.enum(['summary', 'top/locations', 'top/ases'])
	.describe('Dimension indicating the type and format of Internet speed data to retrieve.')

export const InternetSpeedOrderByParam = z
	.enum([
		'BANDWIDTH_DOWNLOAD',
		'BANDWIDTH_UPLOAD',
		'LATENCY_IDLE',
		'LATENCY_LOADED',
		'JITTER_IDLE',
		'JITTER_LOADED',
	])
	.describe('Specifies the metric to order the results by. Only allowed for top locations and ASes')

export const InternetQualityMetricParam = z
	.enum(['BANDWIDTH', 'DNS', 'LATENCY'])
	.describe('Specifies which metric to return (bandwidth, latency, or DNS response time).')

```

--------------------------------------------------------------------------------
/packages/mcp-common/src/tools/hyperdrive.tools.ts:
--------------------------------------------------------------------------------

```typescript
import { getCloudflareClient } from '../cloudflare-api'
import { MISSING_ACCOUNT_ID_RESPONSE } from '../constants'
import { getProps } from '../get-props'
import { type CloudflareMcpAgent } from '../types/cloudflare-mcp-agent.types'
import {
	HyperdriveCachingDisabledSchema,
	HyperdriveCachingMaxAgeSchema,
	HyperdriveCachingStaleWhileRevalidateSchema,
	HyperdriveConfigIdSchema,
	HyperdriveConfigNameSchema,
	HyperdriveListParamDirectionSchema,
	HyperdriveListParamOrderSchema,
	HyperdriveListParamPageSchema,
	HyperdriveListParamPerPageSchema,
	HyperdriveOriginDatabaseSchema,
	HyperdriveOriginHostSchema,
	HyperdriveOriginPortSchema,
	HyperdriveOriginSchemeSchema,
	HyperdriveOriginUserSchema,
} from '../types/hyperdrive.types'

export const HYPERDRIVE_TOOLS = {
	hyperdrive_configs_list: 'hyperdrive_configs_list',
	hyperdrive_config_create: 'hyperdrive_config_create',
	hyperdrive_config_delete: 'hyperdrive_config_delete',
	hyperdrive_config_get: 'hyperdrive_config_get',
	hyperdrive_config_edit: 'hyperdrive_config_edit',
}

/**
 * Registers Hyperdrive tools with the Cloudflare MCP Agent.
 * @param agent The Cloudflare MCP Agent instance.
 */
export function registerHyperdriveTools(agent: CloudflareMcpAgent) {
	/**
	 * Tool to list Hyperdrive configurations.
	 */
	agent.server.tool(
		HYPERDRIVE_TOOLS.hyperdrive_configs_list,
		'List Hyperdrive configurations in your Cloudflare account',
		{
			page: HyperdriveListParamPageSchema.nullable(),
			per_page: HyperdriveListParamPerPageSchema.nullable(),
			order: HyperdriveListParamOrderSchema.nullable(),
			direction: HyperdriveListParamDirectionSchema.nullable(),
		},
		{
			title: 'List Hyperdrive configs',
			annotations: {
				readOnlyHint: true,
			},
		},
		async ({ page, per_page, order, direction }) => {
			const account_id = await agent.getActiveAccountId()
			if (!account_id) {
				return MISSING_ACCOUNT_ID_RESPONSE
			}
			try {
				const props = getProps(agent)
				const client = getCloudflareClient(props.accessToken)
				const response = await client.hyperdrive.configs.list({
					account_id,
					...(page && { page }),
					...(per_page && { per_page }),
					...(order && { order }),
					...(direction && { direction }),
				})

				const configs = response.result ?? []

				return {
					content: [
						{
							type: 'text',
							text: JSON.stringify({
								configs,
								count: configs.length,
							}),
						},
					],
				}
			} catch (error) {
				return {
					content: [
						{
							type: 'text',
							text: `Error listing Hyperdrive configs: ${error instanceof Error ? error.message : String(error)}`,
						},
					],
				}
			}
		}
	)

	// TODO: Once elicitation is available in MCP as a way to securely pass parameters, re-enable this tool. See: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/382
	/**
	 * Tool to create a Hyperdrive configuration.
	 */
	// agent.server.tool(
	// 	HYPERDRIVE_TOOLS.hyperdrive_config_create,
	// 	'Create a new Hyperdrive configuration in your Cloudflare account',
	// 	{
	// 		name: HyperdriveConfigNameSchema,
	// 		database: HyperdriveOriginDatabaseSchema,
	// 		host: HyperdriveOriginHostSchema,
	// 		port: HyperdriveOriginPortSchema,
	// 		scheme: HyperdriveOriginSchemeSchema,
	// 		user: HyperdriveOriginUserSchema,
	// 		password: HyperdriveOriginPasswordSchema,
	// 		caching_disabled: HyperdriveCachingDisabledSchema.nullable(),
	// 		caching_max_age: HyperdriveCachingMaxAgeSchema.nullable(),
	// 		caching_stale_while_revalidate: HyperdriveCachingStaleWhileRevalidateSchema.nullable(),
	// 	},
	// 	async ({
	// 		name,
	// 		database,
	// 		host,
	// 		port,
	// 		scheme,
	// 		user,
	// 		password,
	// 		caching_disabled = undefined,
	// 		caching_max_age = undefined,
	// 		caching_stale_while_revalidate = undefined,
	// 	}) => {
	// 		const account_id = await agent.getActiveAccountId()
	// 		if (!account_id) {
	// 			return MISSING_ACCOUNT_ID_RESPONSE
	// 		}
	// 		try {
	// 			const origin = { database, host, port, scheme, user, password }
	// 			const caching: Record<string, any> = {}
	// 			if (caching_disabled !== undefined) caching.disabled = caching_disabled
	// 			if (caching_max_age !== undefined) caching.max_age = caching_max_age
	// 			if (caching_stale_while_revalidate !== undefined)
	// 				caching.stale_while_revalidate = caching_stale_while_revalidate

	// 			const client = getCloudflareClient(props.accessToken)
	// 			const hyperdriveConfig = await client.hyperdrive.configs.create({
	// 				account_id,
	// 				name,
	// 				origin,
	// 				...(Object.keys(caching).length > 0 && { caching }),
	// 			})
	// 			return {
	// 				content: [
	// 					{
	// 						type: 'text',
	// 						text: JSON.stringify(hyperdriveConfig),
	// 					},
	// 				],
	// 			}
	// 		} catch (error) {
	// 			return {
	// 				content: [
	// 					{
	// 						type: 'text',
	// 						text: `Error creating Hyperdrive config: ${error instanceof Error ? error.message : String(error)}`,
	// 					},
	// 				],
	// 			}
	// 		}
	// 	}
	// )

	/**
	 * Tool to delete a Hyperdrive configuration.
	 */
	agent.server.tool(
		HYPERDRIVE_TOOLS.hyperdrive_config_delete,
		'Delete a Hyperdrive configuration in your Cloudflare account',
		{
			hyperdrive_id: HyperdriveConfigIdSchema,
		},
		{
			title: 'Delete Hyperdrive config',
			annotations: {
				readOnlyHint: false,
				destructiveHint: true,
			},
		},
		async ({ hyperdrive_id }) => {
			const account_id = await agent.getActiveAccountId()
			if (!account_id) {
				return MISSING_ACCOUNT_ID_RESPONSE
			}
			try {
				const props = getProps(agent)
				const client = getCloudflareClient(props.accessToken)
				await client.hyperdrive.configs.delete(hyperdrive_id, { account_id })
				return {
					content: [
						{
							type: 'text',
							text: JSON.stringify({ success: true, hyperdrive_id: hyperdrive_id }),
						},
					],
				}
			} catch (error) {
				return {
					content: [
						{
							type: 'text',
							text: `Error deleting Hyperdrive config ${hyperdrive_id}: ${error instanceof Error ? error.message : String(error)}`,
						},
					],
				}
			}
		}
	)

	/**
	 * Tool to get a specific Hyperdrive configuration.
	 */
	agent.server.tool(
		HYPERDRIVE_TOOLS.hyperdrive_config_get,
		'Get details of a specific Hyperdrive configuration in your Cloudflare account',
		{
			hyperdrive_id: HyperdriveConfigIdSchema,
		},
		{
			title: 'Get Hyperdrive config',
			annotations: {
				readOnlyHint: true,
			},
		},
		async ({ hyperdrive_id }) => {
			const account_id = await agent.getActiveAccountId()
			if (!account_id) {
				return MISSING_ACCOUNT_ID_RESPONSE
			}
			try {
				const props = getProps(agent)
				const client = getCloudflareClient(props.accessToken)
				const hyperdriveConfig = await client.hyperdrive.configs.get(hyperdrive_id, {
					account_id,
				})
				return {
					content: [
						{
							type: 'text',
							text: JSON.stringify(hyperdriveConfig),
						},
					],
				}
			} catch (error) {
				return {
					content: [
						{
							type: 'text',
							text: `Error getting Hyperdrive config ${hyperdrive_id}: ${error instanceof Error ? error.message : String(error)}`,
						},
					],
				}
			}
		}
	)

	/**
	 * Tool to edit (PATCH) a Hyperdrive configuration.
	 */
	agent.server.tool(
		HYPERDRIVE_TOOLS.hyperdrive_config_edit,
		'Edit (patch) a Hyperdrive configuration in your Cloudflare account',
		{
			hyperdrive_id: HyperdriveConfigIdSchema,
			name: HyperdriveConfigNameSchema.optional().nullable(),
			database: HyperdriveOriginDatabaseSchema.optional().nullable(),
			host: HyperdriveOriginHostSchema.optional().nullable(),
			port: HyperdriveOriginPortSchema.optional().nullable(),
			scheme: HyperdriveOriginSchemeSchema.optional().nullable(),
			user: HyperdriveOriginUserSchema.optional().nullable(),
			caching_disabled: HyperdriveCachingDisabledSchema.optional().nullable(),
			caching_max_age: HyperdriveCachingMaxAgeSchema.optional().nullable(),
			caching_stale_while_revalidate:
				HyperdriveCachingStaleWhileRevalidateSchema.optional().nullable(),
		},
		{
			title: 'Edit Hyperdrive config',
			annotations: {
				readOnlyHint: false,
				destructiveHint: false,
			},
		},
		async ({
			hyperdrive_id,
			name,
			database,
			host,
			port,
			scheme,
			user,
			caching_disabled,
			caching_max_age,
			caching_stale_while_revalidate,
		}) => {
			const account_id = await agent.getActiveAccountId()
			if (!account_id) {
				return MISSING_ACCOUNT_ID_RESPONSE
			}
			try {
				const props = getProps(agent)
				const originPatch: Record<string, any> = {}
				if (database) originPatch.database = database
				if (host) originPatch.host = host
				if (port) originPatch.port = port
				if (scheme) originPatch.scheme = scheme
				if (user) originPatch.user = user

				const cachingPatch: Record<string, any> = {}
				if (caching_disabled) cachingPatch.disabled = caching_disabled
				if (caching_max_age) cachingPatch.max_age = caching_max_age
				if (caching_stale_while_revalidate)
					cachingPatch.stale_while_revalidate = caching_stale_while_revalidate

				const editData: Record<string, any> = {}
				if (name) editData.name = name
				if (Object.keys(originPatch).length > 0) editData.origin = originPatch
				if (Object.keys(cachingPatch).length > 0) editData.caching = cachingPatch

				if (Object.keys(editData).length === 0) {
					return {
						content: [
							{
								type: 'text',
								text: 'Error: No fields provided to edit.',
							},
						],
					}
				}

				const client = getCloudflareClient(props.accessToken)
				const updatedConfig = await client.hyperdrive.configs.edit(hyperdrive_id, {
					account_id,
					...editData,
				})
				return {
					content: [
						{
							type: 'text',
							text: JSON.stringify(updatedConfig),
						},
					],
				}
			} catch (error) {
				return {
					content: [
						{
							type: 'text',
							text: `Error editing Hyperdrive config ${hyperdrive_id}: ${error instanceof Error ? error.message : String(error)}`,
						},
					],
				}
			}
		}
	)
}

```

--------------------------------------------------------------------------------
/server.json:
--------------------------------------------------------------------------------

```json
{
	"$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json",
	"name": "com.cloudflare.mcp/mcp",
	"description": "Cloudflare MCP servers",
	"status": "active",
	"repository": {
		"url": "https://github.com/cloudflare/mcp-server-cloudflare",
		"source": "github"
	},
	"version": "1.0.0",
	"remotes": [
		{
			"url": "https://docs.mcp.cloudflare.com/mcp",
			"type": "streamable-http"
		},
		{
			"url": "https://observability.mcp.cloudflare.com/mcp",
			"type": "streamable-http",
			"headers": [
				{
					"name": "Authentication",
					"description": "Optional Cloudflare API key for authentication if not using OAuth. Can use User or Account owned tokens as a Bearer token.",
					"is_required": false,
					"is_secret": true
				}
			]
		},
		{
			"url": "https://bindings.mcp.cloudflare.com/mcp",
			"type": "streamable-http",
			"headers": [
				{
					"name": "Authentication",
					"description": "Optional Cloudflare API key for authentication if not using OAuth. Can use User or Account owned tokens as a Bearer token.",
					"is_required": false,
					"is_secret": true
				}
			]
		},
		{
			"url": "https://builds.mcp.cloudflare.com/mcp",
			"type": "streamable-http",
			"headers": [
				{
					"name": "Authentication",
					"description": "Optional Cloudflare API key for authentication if not using OAuth. Can use User or Account owned tokens as a Bearer token.",
					"is_required": false,
					"is_secret": true
				}
			]
		},
		{
			"url": "https://radar.mcp.cloudflare.com/mcp",
			"type": "streamable-http",
			"headers": [
				{
					"name": "Authentication",
					"description": "Optional Cloudflare API key for authentication if not using OAuth. Can use User or Account owned tokens as a Bearer token.",
					"is_required": false,
					"is_secret": true
				}
			]
		},
		{
			"url": "https://containers.mcp.cloudflare.com/mcp",
			"type": "streamable-http",
			"headers": [
				{
					"name": "Authentication",
					"description": "Optional Cloudflare API key for authentication if not using OAuth. Can use User or Account owned tokens as a Bearer token.",
					"is_required": false,
					"is_secret": true
				}
			]
		},
		{
			"url": "https://browser.mcp.cloudflare.com/mcp",
			"type": "streamable-http",
			"headers": [
				{
					"name": "Authentication",
					"description": "Optional Cloudflare API key for authentication if not using OAuth. Can use User or Account owned tokens as a Bearer token.",
					"is_required": false,
					"is_secret": true
				}
			]
		},
		{
			"url": "https://logs.mcp.cloudflare.com/mcp",
			"type": "streamable-http",
			"headers": [
				{
					"name": "Authentication",
					"description": "Optional Cloudflare API key for authentication if not using OAuth. Can use User or Account owned tokens as a Bearer token.",
					"is_required": false,
					"is_secret": true
				}
			]
		},
		{
			"url": "https://ai-gateway.mcp.cloudflare.com/mcp",
			"type": "streamable-http",
			"headers": [
				{
					"name": "Authentication",
					"description": "Optional Cloudflare API key for authentication if not using OAuth. Can use User or Account owned tokens as a Bearer token.",
					"is_required": false,
					"is_secret": true
				}
			]
		},
		{
			"url": "https://autorag.mcp.cloudflare.com/mcp",
			"type": "streamable-http",
			"headers": [
				{
					"name": "Authentication",
					"description": "Optional Cloudflare API key for authentication if not using OAuth. Can use User or Account owned tokens as a Bearer token.",
					"is_required": false,
					"is_secret": true
				}
			]
		},
		{
			"url": "https://auditlogs.mcp.cloudflare.com/mcp",
			"type": "streamable-http",
			"headers": [
				{
					"name": "Authentication",
					"description": "Optional Cloudflare API key for authentication if not using OAuth. Can use User or Account owned tokens as a Bearer token.",
					"is_required": false,
					"is_secret": true
				}
			]
		},
		{
			"url": "https://dns-analytics.mcp.cloudflare.com/mcp",
			"type": "streamable-http",
			"headers": [
				{
					"name": "Authentication",
					"description": "Optional Cloudflare API key for authentication if not using OAuth. Can use User or Account owned tokens as a Bearer token.",
					"is_required": false,
					"is_secret": true
				}
			]
		},
		{
			"url": "https://dex.mcp.cloudflare.com/mcp",
			"type": "streamable-http",
			"headers": [
				{
					"name": "Authentication",
					"description": "Optional Cloudflare API key for authentication if not using OAuth. Can use User or Account owned tokens as a Bearer token.",
					"is_required": false,
					"is_secret": true
				}
			]
		},
		{
			"url": "https://casb.mcp.cloudflare.com/mcp",
			"type": "streamable-http",
			"headers": [
				{
					"name": "Authentication",
					"description": "Optional Cloudflare API key for authentication if not using OAuth. Can use User or Account owned tokens as a Bearer token.",
					"is_required": false,
					"is_secret": true
				}
			]
		},
		{
			"url": "https://graphql.mcp.cloudflare.com/mcp",
			"type": "streamable-http",
			"headers": [
				{
					"name": "Authentication",
					"description": "Optional Cloudflare API key for authentication if not using OAuth. Can use User or Account owned tokens as a Bearer token.",
					"is_required": false,
					"is_secret": true
				}
			]
		},

		{
			"url": "https://docs.mcp.cloudflare.com/sse",
			"type": "sse"
		},
		{
			"url": "https://observability.mcp.cloudflare.com/sse",
			"type": "sse",
			"headers": [
				{
					"name": "Authentication",
					"description": "Optional Cloudflare API key for authentication if not using OAuth. Can use User or Account owned tokens as a Bearer token.",
					"is_required": false,
					"is_secret": true
				}
			]
		},
		{
			"url": "https://bindings.mcp.cloudflare.com/sse",
			"type": "sse",
			"headers": [
				{
					"name": "Authentication",
					"description": "Optional Cloudflare API key for authentication if not using OAuth. Can use User or Account owned tokens as a Bearer token.",
					"is_required": false,
					"is_secret": true
				}
			]
		},
		{
			"url": "https://builds.mcp.cloudflare.com/sse",
			"type": "sse",
			"headers": [
				{
					"name": "Authentication",
					"description": "Optional Cloudflare API key for authentication if not using OAuth. Can use User or Account owned tokens as a Bearer token.",
					"is_required": false,
					"is_secret": true
				}
			]
		},
		{
			"url": "https://radar.mcp.cloudflare.com/sse",
			"type": "sse",
			"headers": [
				{
					"name": "Authentication",
					"description": "Optional Cloudflare API key for authentication if not using OAuth. Can use User or Account owned tokens as a Bearer token.",
					"is_required": false,
					"is_secret": true
				}
			]
		},
		{
			"url": "https://containers.mcp.cloudflare.com/sse",
			"type": "sse",
			"headers": [
				{
					"name": "Authentication",
					"description": "Optional Cloudflare API key for authentication if not using OAuth. Can use User or Account owned tokens as a Bearer token.",
					"is_required": false,
					"is_secret": true
				}
			]
		},
		{
			"url": "https://browser.mcp.cloudflare.com/sse",
			"type": "sse",
			"headers": [
				{
					"name": "Authentication",
					"description": "Optional Cloudflare API key for authentication if not using OAuth. Can use User or Account owned tokens as a Bearer token.",
					"is_required": false,
					"is_secret": true
				}
			]
		},
		{
			"url": "https://logs.mcp.cloudflare.com/sse",
			"type": "sse",
			"headers": [
				{
					"name": "Authentication",
					"description": "Optional Cloudflare API key for authentication if not using OAuth. Can use User or Account owned tokens as a Bearer token.",
					"is_required": false,
					"is_secret": true
				}
			]
		},
		{
			"url": "https://ai-gateway.mcp.cloudflare.com/sse",
			"type": "sse",
			"headers": [
				{
					"name": "Authentication",
					"description": "Optional Cloudflare API key for authentication if not using OAuth. Can use User or Account owned tokens as a Bearer token.",
					"is_required": false,
					"is_secret": true
				}
			]
		},
		{
			"url": "https://autorag.mcp.cloudflare.com/sse",
			"type": "sse",
			"headers": [
				{
					"name": "Authentication",
					"description": "Optional Cloudflare API key for authentication if not using OAuth. Can use User or Account owned tokens as a Bearer token.",
					"is_required": false,
					"is_secret": true
				}
			]
		},
		{
			"url": "https://auditlogs.mcp.cloudflare.com/sse",
			"type": "sse",
			"headers": [
				{
					"name": "Authentication",
					"description": "Optional Cloudflare API key for authentication if not using OAuth. Can use User or Account owned tokens as a Bearer token.",
					"is_required": false,
					"is_secret": true
				}
			]
		},
		{
			"url": "https://dns-analytics.mcp.cloudflare.com/sse",
			"type": "sse",
			"headers": [
				{
					"name": "Authentication",
					"description": "Optional Cloudflare API key for authentication if not using OAuth. Can use User or Account owned tokens as a Bearer token.",
					"is_required": false,
					"is_secret": true
				}
			]
		},
		{
			"url": "https://dex.mcp.cloudflare.com/sse",
			"type": "sse",
			"headers": [
				{
					"name": "Authentication",
					"description": "Optional Cloudflare API key for authentication if not using OAuth. Can use User or Account owned tokens as a Bearer token.",
					"is_required": false,
					"is_secret": true
				}
			]
		},
		{
			"url": "https://casb.mcp.cloudflare.com/sse",
			"type": "sse",
			"headers": [
				{
					"name": "Authentication",
					"description": "Optional Cloudflare API key for authentication if not using OAuth. Can use User or Account owned tokens as a Bearer token.",
					"is_required": false,
					"is_secret": true
				}
			]
		},
		{
			"url": "https://graphql.mcp.cloudflare.com/sse",
			"type": "sse",
			"headers": [
				{
					"name": "Authentication",
					"description": "Optional Cloudflare API key for authentication if not using OAuth. Can use User or Account owned tokens as a Bearer token.",
					"is_required": false,
					"is_secret": true
				}
			]
		}
	]
}

```

--------------------------------------------------------------------------------
/apps/auditlogs/src/tools/auditlogs.tools.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from 'zod'

import { fetchCloudflareApi } from '@repo/mcp-common/src/cloudflare-api'
import { getProps } from '@repo/mcp-common/src/get-props'

import type { AuditlogMCP } from '../auditlogs.app'

export const actionResults = z.enum(['success', 'failure', ''])
export const actionTypes = z.enum(['create', 'delete', 'view', 'update', 'login'])
export const actorContexts = z.enum(['api_key', 'api_token', 'dash', 'oauth', 'origin_ca_key'])
export const actorTypes = z.enum(['cloudflare_admin', 'account', 'user', 'system'])
export const resourceScopes = z.enum(['memberships', 'accounts', 'user', 'zones'])
export const sortDirections = z.enum(['desc', 'asc'])

export const auditLogsQuerySchema = z.object({
	account_name: z.string().optional().describe('The account name to filter audit logs by.'),
	action_result: actionResults.optional().describe('Whether the action was a success or failure.'),
	action_type: actionTypes.optional().describe('The type of action that was performed.'),
	actor_context: actorContexts.optional().describe('The context in which the actor was operating.'),
	actor_email: z
		.string()
		.email()
		.optional()
		.describe('The email of the actor who triggered the event.'),
	actor_id: z.string().optional().describe('The unique identifier of the actor.'),
	actor_ip_address: z.string().optional().describe('The IP address of the actor.'),
	actor_token_id: z.string().optional().describe('The API token ID used by the actor.'),
	actor_token_name: z.string().optional().describe('The name of the API token used by the actor.'),
	actor_type: actorTypes.optional().describe('The type of actor (e.g., user, token).'),
	audit_log_id: z.string().optional().describe('The unique identifier of the audit log entry.'),
	raw_cf_ray_id: z
		.string()
		.optional()
		.describe('The Cloudflare Ray ID associated with the request.'),
	raw_method: z
		.string()
		.optional()
		.describe('The HTTP method used in the request (e.g., GET, POST).'),
	raw_status_code: z.number().optional().describe('The HTTP status code returned by the request.'),
	raw_uri: z.string().optional().describe('The URI accessed in the request.'),
	resource_id: z.string().optional().describe('The unique identifier of the resource affected.'),
	resource_product: z
		.string()
		.optional()
		.describe('The Cloudflare product related to the resource.'),
	resource_type: z.string().optional().describe('The type of resource affected.'),
	resource_scope: resourceScopes
		.optional()
		.describe('The scope of the resource (e.g., account, zone).'),
	zone_id: z.string().optional().describe('The ID of the zone associated with the log.'),
	zone_name: z.string().optional().describe('The name of the zone associated with the log.'),
	since: z
		.string()
		.describe(
			'The start of the time slice to look at. Can be YYYY-MM-DD or YYYY-MM-DDTHH:mm:ss.sssZ'
		)
		.regex(
			/^(\d{4}-\d{2}-\d{2}|(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z))$/,
			'Date must be in YYYY-MM-DD or ISO 8601 format with milliseconds (e.g., YYYY-MM-DDTHH:mm:ss.sssZ)'
		),
	before: z
		.string()
		.describe('The end of the time slice to look at. Can be YYYY-MM-DD or YYYY-MM-DDTHH:mm:ss.sssZ')
		.regex(
			/^(\d{4}-\d{2}-\d{2}|(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z))$/,
			'Date must be in YYYY-MM-DD or ISO 8601 format with milliseconds (e.g., YYYY-MM-DDTHH:mm:ss.sssZ)'
		),
	direction: sortDirections.optional().describe('The sort direction of the logs (asc or desc).'),
	limit: z
		.number()
		.min(1)
		.max(1000)
		.optional()
		.describe('The number of results to return (max 1000).'),
	cursor: z.string().optional().describe('Pagination cursor for fetching the next set of results.'),
})

// Core schema for one audit log entry
const auditLogEntrySchema = z.object({
	id: z.string().max(36).describe('Unique identifier for the audit log entry'),

	account: z
		.object({
			id: z.string().describe('The ID of the account'),
			name: z.string().describe('The name of the account'),
		})
		.describe('Account information associated with the audit log'),

	action: z
		.object({
			description: z.string().optional().describe('Description of the action taken'),
			result: actionResults.describe('Result of the action'),
			time: z.string().datetime().describe('Timestamp of when the action occurred'),
			type: actionTypes.describe('Type of action performed'),
		})
		.describe('Details of the action performed in the audit log'),

	actor: z
		.object({
			context: actorContexts.optional().describe('Context associated with the actor'),
			email: z.string().email().optional().describe('Email of the actor'),
			id: z.string().optional().describe('ID of the actor'),
			ip_address: z.string().optional().describe('IP address of the actor'),
			type: actorTypes.optional().describe('Type of the actor'),
			token_id: z.string().optional().describe('Token ID if available'),
			token_name: z.string().optional().describe('Token name if available'),
		})
		.optional()
		.describe('Information about the actor who performed the action'),

	resource: z
		.object({
			id: z.string().optional().describe('Resource ID involved in the action'),
			product: z.string().optional().describe('Product related to the action'),
			request: z.record(z.unknown()).optional().describe('Request details of the action'),
			response: z.record(z.unknown()).optional().describe('Response details of the action'),
			scope: z
				.union([z.string(), z.object({})])
				.optional()
				.describe('Scope of the resource, e.g., "accounts"'),
			type: z.string().optional().describe('Type of resource involved'),
		})
		.optional()
		.describe('Details of the resource involved in the action'),

	raw: z
		.object({
			cf_ray_id: z.string().optional().describe('Cloudflare Ray ID associated with the request'),
			method: z.string().optional().describe('HTTP method used for the request'),
			status_code: z.number().optional().describe('HTTP status code of the response'),
			uri: z.string().optional().describe('URI of the request'),
			user_agent: z.string().optional().describe('User-Agent header of the request'),
		})
		.optional()
		.describe('Raw data related to the request made during the action'),

	zone: z
		.object({
			id: z.string().optional().describe('ID of the zone involved in the action'),
			name: z.string().optional().describe('Name of the zone involved in the action'),
		})
		.optional()
		.describe('Zone information related to the action'),
})

// Wrapper schema for response
export const resultInfoSchema = z.object({
	count: z.number(),
	cursor: z.string().optional(),
})

export const auditLogsResponseSchema = z.object({
	success: z.literal(true),
	errors: z.array(z.object({ message: z.string() })).optional(),
	result: z.array(auditLogEntrySchema).optional(),
	result_info: resultInfoSchema,
})

export const trimmedAuditLog = z.object({
	description: z.string().optional().describe('Description of the action taken'),
	time: z.string().datetime().describe('Timestamp of when the action occurred'),
	product: z.string().optional().describe('Product related to the action'),
	type: z.string().optional().describe('Type of resource involved'),
	actor_email: z.string().email().optional().describe('Email of the actor'),
	actor_token_name: z.string().optional().describe('Token name if available'),
})

export const trimmedAuditLogsResponseSchema = z.object({
	logs: z.array(trimmedAuditLog),
	result_info: resultInfoSchema,
})

export type AuditLogOptions = z.infer<typeof auditLogsQuerySchema>

export async function handleGetAuditLogs(
	accountId: string,
	apiToken: string,
	options: AuditLogOptions
): Promise<z.infer<typeof trimmedAuditLogsResponseSchema>> {
	// Default to just getting the first 10
	if (!options.limit) {
		options.limit = 10
	}

	// Validate and parse query parameters using Zod
	const validatedParams = auditLogsQuerySchema.parse(options)

	// Build query string from validated parameters
	const queryParams = new URLSearchParams()
	for (const [key, value] of Object.entries(validatedParams)) {
		if (value !== undefined && value !== null) {
			queryParams.append(key, String(value)) // Ensure everything is converted to string
		}
	}

	// Call the Public API
	const data = await fetchCloudflareApi({
		endpoint: `/logs/audit?${queryParams.toString()}`,
		accountId,
		apiToken,
		responseSchema: auditLogsResponseSchema,
		options: {
			method: 'GET',
			headers: {
				'Content-Type': 'application/json',
				'portal-version': '2',
			},
		},
	})

	// Trim down the results to relevant information
	const results = (data.result || []).map((res) => {
		return {
			description: res.action.description || '',
			time: res.action.time,
			actor_email: res.actor?.email,
			actor_token_name: res.actor?.token_name,
			product: res.resource?.product,
			type: res.resource?.type,
		} as z.infer<typeof trimmedAuditLog>
	})

	return {
		logs: results,
		result_info: data.result_info,
	}
}

/**
 * Registers the audit log tool with the MCP server
 * @param server The MCP server instance
 * @param accountId Cloudflare account ID
 * @param apiToken Cloudflare API token
 */
export function registerAuditLogTools(agent: AuditlogMCP) {
	// Register the audit log tool by account
	agent.server.tool(
		'auditlogs_by_account_id',
		`Find all audit logs (a list of who made what change when) for a Cloudflare Account by ID.
		This can be used to query activity on your Cloudflare account at a particular time.
		Since and before are required to look at a slice of time and are dates with or without a time up to millisecond precision e.g YYYY-MM-DDTHH:mm:ss.sssZ.
		There can be more than one page of results and they can be paginated using the returned cursor`,
		auditLogsQuerySchema.shape,
		async (params) => {
			const accountId = await agent.getActiveAccountId()
			if (!accountId) {
				return {
					content: [
						{
							type: 'text',
							text: 'No currently active accountId. Try listing your accounts (accounts_list) and then setting an active account (set_active_account)',
						},
					],
				}
			}
			try {
				const props = getProps(agent)
				const result = await handleGetAuditLogs(accountId, props.accessToken, params)
				return {
					content: [
						{
							type: 'text',
							text: JSON.stringify(result),
						},
					],
				}
			} catch (error) {
				return {
					content: [
						{
							type: 'text',
							text: JSON.stringify({
								error: `Error reading audit logs: ${error instanceof Error && error.message}`,
							}),
						},
					],
				}
			}
		}
	)
}

```

--------------------------------------------------------------------------------
/packages/mcp-common/src/types/workers-logs.types.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from 'zod'

import { nowISO, parseRelativeTime } from '../utils'

export const numericalOperations = ['eq', 'neq', 'gt', 'gte', 'lt', 'lte'] as const

export const queryOperations = [
	// applies only to strings
	'includes',
	'not_includes',

	// string operations
	'starts_with',
	'regex',

	// existence check
	'exists',
	'is_null',

	// right hand side must be a string with comma separated values
	'in',
	'not_in',

	// numerica
	...numericalOperations,
] as const

export const queryOperators = [
	'uniq',
	'count',
	'max',
	'min',
	'sum',
	'avg',
	'median',
	'p001',
	'p01',
	'p05',
	'p10',
	'p25',
	'p75',
	'p90',
	'p95',
	'p99',
	'p999',
	'stddev',
	'variance',
] as const

export const zQueryOperator = z.enum(queryOperators)
export const zQueryOperation = z.enum(queryOperations)
export const zQueryNumericalOperations = z.enum(numericalOperations)

export const zOffsetDirection = z.enum(['next', 'prev'])
export const zFilterCombination = z.enum(['and', 'or', 'AND', 'OR'])

export const zPrimitiveUnion = z.union([z.string(), z.number(), z.boolean()])

export const zQueryFilter = z.object({
	key: z.string().describe(`Filter field name. IMPORTANT:

    • DO NOT guess keys - always use verified keys from either:
      - Previous query results
      - The observability_keys response

    • PREFERRED KEYS (faster & always available):
      - $metadata.service: Worker service name
			- $metadata.origin: Trigger type (e.g., fetch, scheduled, etc.)
			- $metadata.trigger: Trigger type (e.g., GET /users, POST /orders, etc.)
      - $metadata.message: Log message text (present in nearly all logs)
      - $metadata.error: Error message (when applicable)
`),
	operation: zQueryOperation,
	value: zPrimitiveUnion.optional().describe(`Filter comparison value. IMPORTANT:

    • MUST match actual values in your logs
    • VERIFY using either:
      - Actual values from previous query results
      - The '/values' endpoint with your selected key

    • TYPE MATCHING:
      - Ensure value type (string/number/boolean) matches the field type
      - String comparisons are case-sensitive unless using specific operations

    • PATTERN USAGE:
      - For 'contains', use simple wildcard patterns
      - For 'regex', MUST use ClickHouse regex syntax:
        - Uses RE2 syntax (not PCRE/JavaScript)
        - No lookaheads/lookbehinds
        - Examples: '^5\\d{2}$' for HTTP 5xx codes, '\\bERROR\\b' for word boundary
        - Escape backslashes with double backslash`),
	type: z.enum(['string', 'number', 'boolean']),
}).describe(`
	## Filtering Best Practices
- Before applying filters, use the observability_keys and observability_values queries to confirm available filter fields and values.
- If the query is asking to find something you should check that it exists. I.e. to requests with errors filter for $metadata.error exists.
	`)

export const zQueryCalculation = z.object({
	key: z.string().optional()
		.describe(`The key to use for the calculation. This key must exist in the logs.
Use the Keys endpoint to confirm that this key exists

• DO NOT guess keys - always use verified keys from either:
- Previous query results
- The observability_keys response`),
	keyType: z.enum(['string', 'number', 'boolean']).optional(),
	operator: zQueryOperator,
	alias: z.string().optional(),
})
export const zQueryGroupBy = z.object({
	type: z.enum(['string', 'number', 'boolean']),
	value: z.string(),
})

export const zSearchNeedle = z.object({
	value: zPrimitiveUnion,
	isRegex: z.boolean().optional(),
	matchCase: z.boolean().optional(),
})

const zViews = z.enum(['events', 'calculations', 'invocations'])

export const zAggregateResult = z.object({
	groups: z.array(z.object({ key: z.string(), value: zPrimitiveUnion })).optional(),
	value: z.number(),
	count: z.number(),
	interval: z.number(),
	sampleInterval: z.number(),
})

export const zQueryRunCalculationsV2 = z.array(
	z.object({
		alias: z
			.string()
			.transform((val) => (val === '' ? undefined : val))
			.optional(),
		calculation: z.string(),
		aggregates: z.array(zAggregateResult),
		series: z.array(
			z.object({
				time: z.string(),
				data: z.array(zAggregateResult),
			})
		),
	})
)

export const zStatistics = z.object({
	elapsed: z.number(),
	rows_read: z.number(),
	bytes_read: z.number(),
})

export const zTimeframeAbsolute = z
	.object({
		to: z.string(),
		from: z.string(),
	})
	.describe(
		`An absolute timeframe for your query (ISO-8601 format).

  • Current server time: ${nowISO()}
  • Default: Last hour from current time
  • Maximum range: Last 7 days
  • Format: "YYYY-MM-DDTHH:MM:SSZ" (e.g., "2025-04-29T14:30:00Z")

  Examples:
  - Between April 1st and 5th: from="2025-04-01T00:00:00Z", to="2025-04-05T23:59:59Z"

  Note: Narrower timeframes provide faster responses and more specific results.
  Omit this parameter entirely to use the default (last hour).`
	)

export const zTimeframeRelative = z
	.object({
		reference: z.string(),
		offset: z.string(),
	})
	.describe(
		`Relative timeframe for your query, composed of a reference time and an offset.

  • Current server time: ${nowISO()}
  • Default: Last hour from current time
  • Maximum range: Last 7 days
  • Reference time format: "YYYY-MM-DDTHH:MM:SSZ" (ISO-8601) (e.g., "2025-04-29T14:30:00Z")
  • Offset format: Must start with a '+' or '-' sign, which indicates whether the offset is in the past or future, followed by one or more time units (e.g., '+5d', '-2h', '+6h20m').
		Units: s (seconds), m (minutes), h (hours), d (days), w (weeks).
	• You should not use a future looking offset in combination with the current server time as the reference time, as this will yield no results. (e.g. "the next 20 minutes")

  Examples:
  - Last 30 minutes: reference="${nowISO()}", offset="-30m"
  - Yesterday: reference="${nowISO()}", offset="-1d"

  Note: Narrower timeframes provide faster responses and more specific results.
  Omit this parameter entirely to use the default (last hour).`
	)
	.transform((val) => {
		const referenceTime = new Date(val.reference).getTime() / 1000

		if (isNaN(referenceTime)) {
			throw new Error(`Invalid reference time: ${val.reference}`)
		}

		const offsetSeconds = parseRelativeTime(val.offset)

		const from = new Date(Math.min(referenceTime + offsetSeconds, referenceTime) * 1000)
		const to = new Date(Math.max(referenceTime + offsetSeconds, referenceTime) * 1000)

		return {
			from: from.toISOString(),
			to: to.toISOString(),
		}
	})

export const zTimeframe = z.union([zTimeframeAbsolute, zTimeframeRelative]).describe(
	`Timeframe for your query, which can be either absolute or relative.

  • Absolute timeframe: Specify exact start and end times in ISO-8601 format (e.g., "2025-04-29T14:30:00Z").
  • Relative timeframe: Specify a reference time and an offset (e.g., reference="2025-04-29T14:30:00Z", offset="-30m").

  Examples:
  - Absolute: from="2025-04-01T00:00:00Z", to="2025-04-05T23:59:59Z"
  - Relative: reference="2025-04-29T14:30:00Z", offset="-30m"

  Note: Narrower timeframes provide faster responses and more specific results.`
)

const zCloudflareMiniEventDetailsRequest = z.object({
	url: z.string().optional(),
	method: z.string().optional(),
	path: z.string().optional(),
	search: z.record(z.any()).optional(),
})

const zCloudflareMiniEventDetailsResponse = z.object({
	status: z.number().optional(),
})

const zCloudflareMiniEventDetails = z.object({
	request: zCloudflareMiniEventDetailsRequest.optional(),
	response: zCloudflareMiniEventDetailsResponse.optional(),
	rpcMethod: z.string().optional(),
	rayId: z.string().optional(),
	executionModel: z.string().optional(),
})

export const zCloudflareMiniEvent = z.object({
	event: zCloudflareMiniEventDetails,
	scriptName: z.string(),
	outcome: z.string(),
	eventType: z.enum([
		'fetch',
		'scheduled',
		'alarm',
		'cron',
		'queue',
		'email',
		'tail',
		'rpc',
		'websocket',
		'unknown',
	]),
	entrypoint: z.string().optional(),
	scriptVersion: z
		.object({
			id: z.string().optional(),
			tag: z.string().optional(),
			message: z.string().optional(),
		})
		.optional(),
	truncated: z.boolean().optional(),
	executionModel: z.enum(['durableObject', 'stateless']).optional(),
	requestId: z.string(),
	cpuTimeMs: z.number().optional(),
	wallTimeMs: z.number().optional(),
})

export const zCloudflareEvent = zCloudflareMiniEvent.extend({
	diagnosticsChannelEvents: z
		.array(
			z.object({
				timestamp: z.number(),
				channel: z.string(),
				message: z.string(),
			})
		)
		.optional(),
	dispatchNamespace: z.string().optional(),
	wallTimeMs: z.number(),
	cpuTimeMs: z.number(),
})

const zSourceSchema = z.object({
	exception: z
		.object({
			stack: z.string().optional(),
			name: z.string().optional(),
			message: z.string().optional(),
			timestamp: z.number().optional(),
		})
		.optional(),
})

export const zReturnedTelemetryEvent = z.object({
	dataset: z.string(),
	timestamp: z.number().int().positive(),
	source: z.union([z.string(), zSourceSchema]),
	$workers: z.union([zCloudflareMiniEvent, zCloudflareEvent]).optional(),
	$metadata: z.object({
		id: z.string(),
		requestId: z.string().optional(),
		traceId: z.string().optional(),
		spanId: z.string().optional(),
		trigger: z.string().optional(),
		parentSpanId: z.string().optional(),
		service: z.string().optional(),
		level: z.string().optional(),
		duration: z.number().positive().int().optional(),
		statusCode: z.number().positive().int().optional(),
		traceDuration: z.number().positive().int().optional(),
		error: z.string().optional(),
		message: z.string().optional(),
		spanName: z.string().optional(),
		url: z.string().optional(),
		region: z.string().optional(),
		account: z.string().optional(),
		provider: z.string().optional(),
		type: z.string().optional(),
		fingerprint: z.string().optional(),
		origin: z.string().optional(),
		metricName: z.string().optional(),
		stackId: z.string().optional(),
		coldStart: z.number().positive().int().optional(),
		cost: z.number().positive().int().optional(),
		cloudService: z.string().optional(),
		messageTemplate: z.string().optional(),
		errorTemplate: z.string().optional(),
	}),
})

export type zReturnedQueryRunEvents = z.infer<typeof zReturnedQueryRunEvents>
export const zReturnedQueryRunEvents = z.object({
	events: z.array(zReturnedTelemetryEvent).optional(),
	fields: z
		.array(
			z.object({
				key: z.string(),
				type: z.string(),
			})
		)
		.optional(),
	count: z.number().optional(),
})

/**
 * The request to run a query
 */
export const zQueryRunRequest = z.object({
	// TODO: Fix these types
	queryId: z.string(),
	view: zViews.optional().default('calculations').describe(`## Examples by View Type
		### Events View
		- "Show me all errors for the worker api-proxy in the last 30 minutes"
		- "Show events from worker auth-service where the path contains /login"

		### Calculation View
		- "What is the p99 of wall time for worker api-proxy?"
		- "What's the count of requests by status code for worker cdn-router?"

		### Invocation View
		- "Find a request to worker api-proxy that resulted in a 500 error"
		- "List successful requests for the image-resizer worker with status code 200"
				`),
	parameters: z.object({
		datasets: z
			.array(z.string())
			.optional()
			.describe('Leave this empty to use the default datasets'),
		filters: z.array(zQueryFilter).optional(),
		filterCombination: zFilterCombination.optional(),
		calculations: z.array(zQueryCalculation).optional(),
		groupBys: z.array(zQueryGroupBy).optional().describe(`Only valid when doing a Calculation`),
		orderBy: z
			.object({
				value: z.string().describe('This must be the alias of a calculation'),
				order: z.enum(['asc', 'desc']).optional(),
			})
			.optional()
			.describe('Order By only workers when a group by is present'),
		limit: z
			.number()
			.int()
			.nonnegative()
			.max(100)
			.optional()
			.describe(
				'Use this limit when view is calculation and a group by is present. 10 is a sensible default'
			),
		needle: zSearchNeedle.optional(),
	}),
	timeframe: zTimeframe,
	granularity: z
		.number()
		.optional()
		.describe(
			'This is only used when the view is calculations - by leaving it empty workers observability will detect the correct granularity'
		),
	limit: z
		.number()
		.max(100)
		.optional()
		.default(5)
		.describe(
			'Use this limit to limit the number of events returned when the view is events. 5 is a sensible default'
		),

	dry: z.boolean().optional().default(true),
	offset: z
		.string()
		.optional()
		.describe(
			'The offset to use for pagination. Use the $metadata.id field to get the next offset.'
		),
	offsetBy: z.number().optional(),
	offsetDirection: z
		.string()
		.optional()
		.describe('The direction to use for pagination. Use "next" or "prev".'),
})

/**
 * The response from the API
 */
export type ReturnedQueryRunResult = z.infer<typeof zReturnedQueryRunResult>
export const zReturnedQueryRunResult = z.object({
	// run: zQueryRunRequest,
	calculations: zQueryRunCalculationsV2.optional(),
	compare: zQueryRunCalculationsV2.optional(),
	events: zReturnedQueryRunEvents.optional(),
	invocations: z.record(z.string(), z.array(zReturnedTelemetryEvent)).optional(),
	statistics: zStatistics,
})

/**
 * Keys Request
 */
export const zKeysRequest = z.object({
	timeframe: zTimeframe,
	datasets: z
		.array(z.string())
		.default([])
		.describe('Leave this empty to use the default datasets'),
	filters: z.array(zQueryFilter).default([]),
	limit: z.number().optional().describe(`
    • ADVANCED USAGE:
      set limit=1000+ to retrieve comprehensive key options without needing additional filtering`),
	needle: zSearchNeedle.optional(),
	keyNeedle: zSearchNeedle.optional()
		.describe(`If the user makes a suggestion for a key, use this to narrow down the list of keys returned.
		Make sure match case is fals to avoid case sensitivity issues.`),
})

/**
 * Keys Response
 */
export type zKeysResponse = z.infer<typeof zKeysResponse>
export const zKeysResponse = z.array(
	z.object({
		key: z.string(),
		type: z.enum(['string', 'boolean', 'number']),
		lastSeenAt: z.number(),
	})
)

/**
 * Values Request
 */
export const zValuesRequest = z.object({
	timeframe: zTimeframe,
	key: z.string(),
	type: z.enum(['string', 'boolean', 'number']),
	datasets: z
		.array(z.string())
		.default([])
		.describe('Leave this empty to use the default datasets'),
	filters: z.array(zQueryFilter).default([]),
	limit: z.number().default(50),
	needle: zSearchNeedle.optional(),
})

/** Values Response */
export const zValuesResponse = z.array(
	z.object({
		key: z.string(),
		type: z.enum(['string', 'boolean', 'number']),
		value: z.union([z.string(), z.number(), z.boolean()]),
		dataset: z.string(),
	})
)

```

--------------------------------------------------------------------------------
/packages/mcp-common/src/types/r2_bucket.types.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * This file contains the validators for the r2 bucket tools.
 */
import { z } from 'zod'

import type {
	CORSDeleteParams,
	CORSUpdateParams,
	EventNotificationDeleteParams,
	EventNotificationGetParams,
	EventNotificationUpdateParams,
	LockGetParams,
	LockUpdateParams,
	SippyGetParams,
	SippyUpdateParams,
} from 'cloudflare/resources/r2/buckets.mjs'
import type {
	CustomCreateParams,
	CustomDeleteParams,
	CustomListParams,
	CustomUpdateParams,
} from 'cloudflare/resources/r2/buckets/domains.mjs'
import type {
	ProviderParam,
	Sippy,
	SippyDeleteParams,
} from 'cloudflare/resources/r2/buckets/sippy.mjs'
import type { CustomGetParams } from 'cloudflare/resources/zero-trust/devices/policies.mjs'
import type {
	BucketCreateParams,
	TemporaryCredentialCreateParams,
} from 'cloudflare/src/resources/r2.js'
import type { CORSGetParams } from 'cloudflare/src/resources/r2/buckets.js'
import type { CustomGetResponse } from 'cloudflare/src/resources/r2/buckets/domains.js'

export const BucketNameSchema: z.ZodType<BucketCreateParams['name']> = z
	.string()
	.describe('The name of the r2 bucket')

export const BucketListCursorParam = z
	.string()
	.nullable()
	.optional()
	.describe(
		'Query param: Pagination cursor received during the last List Buckets call. R2 buckets are paginated using cursors instead of page numbers.'
	)
export const BucketListDirectionParam = z
	.enum(['asc', 'desc'])
	.nullable()
	.optional()
	.describe('Direction to order buckets')
export const BucketListNameContainsParam = z
	.string()
	.nullable()
	.optional()
	.describe(
		'Bucket names to filter by. Only buckets with this phrase in their name will be returned.'
	)
export const BucketListStartAfterParam = z
	.string()
	.nullable()
	.optional()
	.describe('Bucket name to start searching after. Buckets are ordered lexicographically.')

export const AllowedMethodsEnum: z.ZodType<CORSUpdateParams.Rule['allowed']['methods']> = z.array(
	z.union([
		z.literal('GET'),
		z.literal('PUT'),
		z.literal('POST'),
		z.literal('DELETE'),
		z.literal('HEAD'),
	])
)
export const JurisdictionEnum: z.ZodType<CORSUpdateParams['jurisdiction']> = z
	.enum(['default', 'eu', 'fedramp'])
	.describe(
		'Use Jurisdictional Restrictions when you need to ensure data is stored and processed within a jurisdiction to meet data residency requirements, including local regulations such as the GDPR or FedRAMP.'
	)

// CORS ZOD SCHEMAS
export const CorsAllowedSchema: z.ZodType<CORSUpdateParams.Rule['allowed']> = z
	.object({
		methods: AllowedMethodsEnum.describe(
			'Specifies the value for the Access-Control-Allow-Methods header'
		),
		origins: z
			.array(z.string())
			.describe('Specifies the value for the Access-Control-Allow-Origin header'),
		headers: z
			.array(z.string())
			.optional()
			.describe('Specifies the value for the Access-Control-Allow-Headers header'),
	})
	.describe('Object specifying allowed origins, methods and headers for this CORS rule')

export const CorsRuleSchema: z.ZodType<CORSUpdateParams.Rule> = z
	.object({
		allowed: CorsAllowedSchema,
		id: z.string().optional().describe('Identifier for this rule'),
		exposeHeaders: z
			.array(z.string())
			.optional()
			.describe('Headers that can be exposed back and accessed by JavaScript'),
		maxAgeSeconds: z
			.number()
			.optional()
			.describe('Time in seconds browsers can cache CORS preflight responses (max 86400)'),
	})
	.describe('Object specifying allowed origins, methods and headers for this CORS rule')

export const CorsRulesSchema: z.ZodType<Omit<CORSUpdateParams, 'account_id'>> = z
	.object({
		rules: z.array(CorsRuleSchema).optional().describe('Array of CORS rules for the bucket'),
		jurisdiction: JurisdictionEnum.optional(),
	})
	.describe('CORS configuration for the bucket')

export const CorsGetParamsSchema: z.ZodType<Omit<CORSGetParams, 'account_id'>> = z
	.object({
		jurisdiction: JurisdictionEnum.optional(),
	})
	.describe('Params for getting CORS configuration for an R2 bucket')

export const CorsDeleteParamsSchema: z.ZodType<Omit<CORSDeleteParams, 'account_id'>> = z
	.object({
		jurisdiction: JurisdictionEnum.optional(),
	})
	.describe('Params for deleting CORS configuration for an R2 bucket')

// TEMPORARY CREDENTIALS ZOD SCHEMAS
export const TemporaryCredentialsCreateParamsSchema: z.ZodType<
	Omit<TemporaryCredentialCreateParams, 'account_id'>
> = z
	.object({
		bucket: BucketNameSchema,
		ttlSeconds: z.number().describe('The time to live for the temporary credentials'),
		permission: z
			.enum(['admin-read-write', 'admin-read-only', 'object-read-write', 'object-read-only'])
			.describe('The permission for the temporary credentials'),
		objects: z
			.array(z.string())
			.optional()
			.describe('The objects to scope the temporary credentials to'),
		prefixes: z
			.array(z.string())
			.optional()
			.describe('The prefixes to scope the temporary credentials to'),
		parentAccessKeyId: z.string().describe('The parent access key id to use for signing'),
	})
	.describe('Temporary credentials for the bucket')

// CUSTOM DOMAIN ZOD SCHEMAS
export const CustomDomainListParamsSchema: z.ZodType<Omit<CustomListParams, 'account_id'>> = z
	.object({
		jurisdiction: JurisdictionEnum.optional(),
	})
	.describe('Params for listing custom domains for an R2 bucket')

export const CustomDomainNameSchema: z.ZodType<CustomGetResponse['domain']> = z
	.string()
	.describe('The custom domain')

export const CustomDomainGetParamsSchema: z.ZodType<Omit<CustomGetParams, 'account_id'>> = z
	.object({
		jurisdiction: JurisdictionEnum.optional(),
	})
	.describe('Params for getting a custom domain for an R2 bucket')

export const CustomDomainCreateParamsSchema: z.ZodType<Omit<CustomCreateParams, 'account_id'>> = z
	.object({
		domain: CustomDomainNameSchema,
		enabled: z
			.boolean()
			.describe(
				'Whether to enable public bucket access at the custom domain. If undefined, the domain will be enabled.'
			),
		zoneId: z.string().describe('The zone id of the custom domain'),
		minTLS: z
			.enum(['1.0', '1.1', '1.2', '1.3'])
			.optional()
			.describe(
				'The minimum TLS version the custom domain will accept for incoming connections. If not set, defaults to 1.0.'
			),
		jurisdiction: JurisdictionEnum.optional(),
	})
	.describe('Params for creating a custom domain for an R2 bucket')

export const CustomDomainDeleteParamsSchema: z.ZodType<Omit<CustomDeleteParams, 'account_id'>> = z
	.object({
		jurisdiction: JurisdictionEnum.optional(),
	})
	.describe('Params for deleting a custom domain for an R2 bucket')

export const CustomDomainUpdateParamsSchema: z.ZodType<Omit<CustomUpdateParams, 'account_id'>> = z
	.object({
		enabled: z
			.boolean()
			.describe(
				'Whether to enable public bucket access at the custom domain. If undefined, the domain will be enabled.'
			),
		zoneId: z.string().describe('The zone id of the custom domain'),
		minTLS: z
			.enum(['1.0', '1.1', '1.2', '1.3'])
			.optional()
			.describe(
				'The minimum TLS version the custom domain will accept for incoming connections. If not set, defaults to 1.0.'
			),
		jurisdiction: JurisdictionEnum.optional(),
	})
	.describe('Params for updating a custom domain for an R2 bucket')

// EVENT NOTIFICATION ZOD SCHEMAS
export const QueueIdSchema = z.string().describe('The queue id of the event notification')

const EventActionEnum = z.enum([
	'PutObject',
	'CopyObject',
	'DeleteObject',
	'CompleteMultipartUpload',
	'LifecycleDeletion',
])

export const EventNotificationRuleSchema: z.ZodType<EventNotificationUpdateParams.Rule> = z
	.object({
		actions: z
			.array(EventActionEnum)
			.describe('Array of R2 object actions that will trigger notifications'),
		description: z
			.string()
			.optional()
			.describe(
				'A description that can be used to identify the event notification rule after creation'
			),
		prefix: z
			.string()
			.optional()
			.describe('Notifications will be sent only for objects with this prefix'),
		suffix: z
			.string()
			.optional()
			.describe('Notifications will be sent only for objects with this suffix'),
	})
	.describe('Rule configuration for event notifications')

// Main EventNotificationUpdateParams schema
export const EventNotificationUpdateParamsSchema: z.ZodType<
	Omit<EventNotificationUpdateParams, 'account_id'>
> = z
	.object({
		rules: z
			.array(EventNotificationRuleSchema)
			.optional()
			.describe('Array of rules to drive notifications'),
		jurisdiction: JurisdictionEnum.optional(),
	})
	.describe('Parameters for updating event notification configuration')

export const EventNotificationGetParamsSchema: z.ZodType<
	Omit<EventNotificationGetParams, 'account_id'>
> = z
	.object({
		jurisdiction: JurisdictionEnum.optional(),
	})
	.describe('Params for getting event notifications for an R2 bucket')

export const EventNotificationDeleteParamsSchema: z.ZodType<
	Omit<EventNotificationDeleteParams, 'account_id'>
> = z
	.object({
		jurisdiction: JurisdictionEnum.optional(),
	})
	.describe('Params for deleting event notifications for an R2 bucket')

// LOCK ZOD SCHEMAS
export const LockGetParamsSchema: z.ZodType<Omit<LockGetParams, 'account_id'>> = z
	.object({
		jurisdiction: JurisdictionEnum.optional(),
	})
	.describe('Params for getting locks for an R2 bucket')

// Condition: Age-based
const R2LockRuleAgeCondition: z.ZodType<LockUpdateParams.Rule.R2LockRuleAgeCondition> = z
	.object({
		maxAgeSeconds: z
			.number()
			.describe('Condition to apply a lock rule to an object for how long in seconds'),
		type: z.literal('Age').describe('Age-based condition'),
	})
	.describe('Condition to apply a lock rule to an object for how long in seconds')

// Condition: Date-based
const R2LockRuleDateCondition: z.ZodType<LockUpdateParams.Rule.R2LockRuleDateCondition> = z
	.object({
		date: z.string().describe('Condition to apply a lock rule to an object until a specific date'),
		type: z.literal('Date').describe('Date-based condition'),
	})
	.describe('Condition to apply a lock rule to an object until a specific date')

// Condition: Indefinite
const R2LockRuleIndefiniteCondition: z.ZodType<LockUpdateParams.Rule.R2LockRuleIndefiniteCondition> =
	z
		.object({
			type: z.literal('Indefinite').describe('Indefinite condition'),
		})
		.describe('Condition to apply a lock rule indefinitely')

// Union of all possible condition types
const LockRuleCondition = z
	.union([R2LockRuleAgeCondition, R2LockRuleDateCondition, R2LockRuleIndefiniteCondition])
	.describe('Condition to apply a lock rule to an object')

export const LockRuleSchema: z.ZodType<LockUpdateParams.Rule> = z
	.object({
		id: z.string().describe('Unique identifier for this rule'),
		condition: LockRuleCondition,
		enabled: z.boolean().describe('Whether or not this rule is in effect'),
		prefix: z
			.string()
			.optional()
			.describe(
				'Rule will only apply to objects/uploads in the bucket that start with the given prefix; an empty prefix can be provided to scope rule to all objects/uploads'
			),
	})
	.describe('Lock rule definition')

// Main schema
export const LockUpdateParamsSchema: z.ZodType<Omit<LockUpdateParams, 'account_id'>> = z
	.object({
		rules: z.array(LockRuleSchema).optional().describe('Body param: Optional list of lock rules'),
		jurisdiction: JurisdictionEnum.optional(),
	})
	.describe('Lock update parameters')

// SIPPY ZOD SCHEMAS
export const SippyGetParamsSchema: z.ZodType<Omit<SippyGetParams, 'account_id'>> = z
	.object({
		jurisdiction: JurisdictionEnum.optional(),
	})
	.describe('Params for getting sippy configuration for an R2 bucket')

const ProviderParam: z.ZodType<ProviderParam> = z.literal('r2').describe('Provider parameter')

// Common destination schema for reuse
const BaseDestination: z.ZodType<Sippy.Destination> = z.object({
	accessKeyId: z
		.string()
		.optional()
		.describe(
			'ID of a Cloudflare API token. This is the value labelled "Access Key ID" when creating an API token from the R2 dashboard. Sippy will use this token when writing objects to R2, so it is best to scope this token to the bucket you\'re enabling Sippy for.'
		),
	secretAccessKey: z
		.string()
		.optional()
		.describe(
			'Value of a Cloudflare API token. This is the value labelled "Secret Access Key" when creating an API token from the R2 dashboard. Sippy will use this token when writing objects to R2, so it is best to scope this token to the bucket you\'re enabling Sippy for.'
		),
	provider: ProviderParam,
})

// AWS Source schema
const AwsSourceSchema: z.ZodType<SippyUpdateParams.R2EnableSippyAws.Source> = z
	.object({
		accessKeyId: z
			.string()
			.optional()
			.describe('Access Key ID of an IAM credential (ideally scoped to a single S3 bucket)'),
		secretAccessKey: z
			.string()
			.optional()
			.describe('Secret Access Key of an IAM credential (ideally scoped to a single S3 bucket)'),
		bucket: z.string().optional().describe('Name of the AWS S3 bucket'),
		region: z.string().optional().describe('Name of the AWS availability zone'),
		provider: z.literal('aws').optional().describe('AWS provider indicator'),
	})
	.describe('AWS S3 bucket to copy objects from')

// GCS Source schema
const GcsSourceSchema: z.ZodType<SippyUpdateParams.R2EnableSippyGcs.Source> = z
	.object({
		bucket: z.string().optional().describe('Name of the GCS bucket'),
		clientEmail: z
			.string()
			.optional()
			.describe('Client email of an IAM credential (ideally scoped to a single GCS bucket)'),
		privateKey: z
			.string()
			.optional()
			.describe('Private Key of an IAM credential (ideally scoped to a single GCS bucket)'),
		provider: z.literal('gcs').optional().describe('GCS provider indicator'),
	})
	.describe('GCS bucket to copy objects from')

// R2EnableSippyAws schema
const R2EnableSippyAwsSchema: z.ZodType<Omit<SippyUpdateParams.R2EnableSippyAws, 'account_id'>> = z
	.object({
		destination: BaseDestination.describe('R2 bucket to copy objects to').optional(),
		source: AwsSourceSchema.optional(),
		jurisdiction: JurisdictionEnum.optional(),
	})
	.describe('Parameters to enable Sippy with AWS as source')

// R2EnableSippyGcs schema
const R2EnableSippyGcsSchema: z.ZodType<Omit<SippyUpdateParams.R2EnableSippyGcs, 'account_id'>> = z
	.object({
		destination: BaseDestination.describe('R2 bucket to copy objects to').optional(),
		source: GcsSourceSchema.optional(),
		jurisdiction: JurisdictionEnum.optional(),
	})
	.describe('Parameters to enable Sippy with GCS as source')

// Combined SippyUpdateParams namespace schema
export const SippyUpdateParamsSchema = z.union([R2EnableSippyAwsSchema, R2EnableSippyGcsSchema])

export const SippyDeleteParamsSchema: z.ZodType<Omit<SippyDeleteParams, 'account_id'>> = z
	.object({
		jurisdiction: JurisdictionEnum.optional(),
	})
	.describe('Parameters to delete Sippy for an R2 bucket')

```

--------------------------------------------------------------------------------
/apps/demo-day/frontend/index.html:
--------------------------------------------------------------------------------

```html
<!doctype html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />

		<!-- Primary Meta Tags -->
		<title>MCP Demo Day - Preview the Future of Agentic Software</title>
		<meta
			name="description"
			content="Join us on May 1st, 2025 to witness groundbreaking demos from companies like Atlassian, Linear, Stripe, and more. Experience the next evolution of AI-powered software."
		/>

		<!-- Open Graph / Facebook -->
		<meta property="og:type" content="website" />
		<meta property="og:url" content="https://demo-day.mcp.cloudflare.com/" />
		<meta property="og:title" content="MCP Demo Day - Preview the Future of Agentic Software" />
		<meta
			property="og:description"
			content="Join us on May 1st, 2025 to witness groundbreaking demos from companies like Atlassian, Linear, Stripe, and more. Experience the next evolution of AI-powered software."
		/>
		<meta property="og:image" content="https://demo-day.mcp.cloudflare.com/public/mcpog.png" />

		<!-- Twitter -->
		<meta name="twitter:card" content="summary_large_image" />
		<meta name="twitter:title" content="MCP Demo Day - Preview the Future of Agentic Software" />
		<meta
			name="twitter:description"
			content="Join us on May 1st, 2025 to witness groundbreaking demos from companies like Atlassian, Linear, Stripe, and more. Experience the next evolution of AI-powered software."
		/>
		<meta name="twitter:image" content="https://demo-day.mcp.cloudflare.com/public/mcpog.png" />
		<link rel="icon" type="image/png" href="public/favicon.ico" />
		<link rel="icon" type="image/png" sizes="32x32" href="public/favicon-32x32.png" />
		<link rel="icon" type="image/png" sizes="16x16" href="public/favicon-16x16.png" />

		<link rel="stylesheet" href="styles.css" />
		<link rel="preconnect" href="https://fonts.googleapis.com" />
		<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
		<link
			href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
			rel="stylesheet"
		/>
	</head>

	<body>
		<div class="page-wrapper">
			<main class="container">
				<section class="left-panel">
					<div class="header">
						<div class="logo">
							<img src="public/mcp_demo_day.svg" alt="MCP Logo" class="cloud-logo" />
						</div>
						<div class="date-time" onclick="document.getElementById('calendarDialog').showModal()">
							<button class="calendar-trigger" aria-label="Add to calendar">
								<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
									<path
										d="M6 5V3M14 5V3M5 9H15M5 7.5H15M4.2 17H15.8C16.4627 17 17 16.4627 17 15.8V5.2C17 4.53726 16.4627 4 15.8 4H4.2C3.53726 4 3 4.53726 3 5.2V15.8C3 16.4627 3.53726 17 4.2 17Z"
										stroke="currentColor"
										stroke-width="1.5"
										stroke-linecap="round"
										stroke-linejoin="round"
									/>
								</svg>
							</button>
							<div class="date-time-text">
								<h2>MAY 1, 2025</h2>
								<h3>ONLINE 10:00 AM PT / 1 PM ET</h3>
							</div>
						</div>
					</div>

					<div class="content">
						<h1>Preview the Future<br />of Agentic Software</h1>

						<p class="description">
							Come see how the world's most innovative platforms have connected agents to their
							services with MCP to build a new class of product experiences.
						</p>

						<div class="input-group">
							<a class="notify-btn" href="https://www.youtube.com/watch?v=njBGqr-BU54">
								Watch the stream
								<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
									<path
										d="M2.5 8H13.5M13.5 8L9 3.5M13.5 8L9 12.5"
										stroke="currentColor"
										stroke-width="1.5"
										stroke-linecap="round"
										stroke-linejoin="round"
									/>
								</svg>
							</a>
							<div class="success-message">
								<div class="success-text">
									<svg width="18" height="18" viewBox="0 0 18 18" fill="none">
										<path
											d="M15 5L7 13L3 9"
											stroke="currentColor"
											stroke-width="2"
											stroke-linecap="round"
											stroke-linejoin="round"
										/>
									</svg>
									You're in! See you on May 1st
								</div>
								<div class="calendar-actions">
									<button class="calendar-action" data-calendar-type="google">
										<svg
											xmlns="http://www.w3.org/2000/svg"
											x="0px"
											y="0px"
											width="100"
											height="100"
											viewBox="0,0,256,256"
											style="fill: #ffffff"
										>
											<g
												fill="#ffffff"
												fill-rule="nonzero"
												stroke="none"
												stroke-width="1"
												stroke-linecap="butt"
												stroke-linejoin="miter"
												stroke-miterlimit="10"
												stroke-dasharray=""
												stroke-dashoffset="0"
												font-family="none"
												font-weight="none"
												font-size="none"
												text-anchor="none"
												style="mix-blend-mode: normal"
											>
												<g transform="scale(5.12,5.12)">
													<path
														d="M25.99609,48c-12.68359,0 -23.00391,-10.31641 -23.00391,-23c0,-12.68359 10.32031,-23 23.00391,-23c5.74609,0 11.24609,2.12891 15.49219,5.99609l0.77344,0.70703l-7.58594,7.58594l-0.70312,-0.60156c-2.22656,-1.90625 -5.05859,-2.95703 -7.97656,-2.95703c-6.76562,0 -12.27344,5.50391 -12.27344,12.26953c0,6.76563 5.50781,12.26953 12.27344,12.26953c4.87891,0 8.73438,-2.49219 10.55078,-6.73828h-11.55078v-10.35547l22.55078,0.03125l0.16797,0.79297c1.17578,5.58203 0.23438,13.79297 -4.53125,19.66797c-3.94531,4.86328 -9.72656,7.33203 -17.1875,7.33203z"
													></path>
												</g>
											</g>
										</svg>
										Google Calendar
									</button>
									<button class="calendar-action" data-calendar-type="outlook">
										<svg
											xmlns="http://www.w3.org/2000/svg"
											x="0px"
											y="0px"
											width="100"
											height="100"
											viewBox="0,0,256,256"
											style="fill: #ffffff"
										>
											<g
												fill="#ffffff"
												fill-rule="nonzero"
												stroke="none"
												stroke-width="1"
												stroke-linecap="butt"
												stroke-linejoin="miter"
												stroke-miterlimit="10"
												stroke-dasharray=""
												stroke-dashoffset="0"
												font-family="none"
												font-weight="none"
												font-size="none"
												text-anchor="none"
												style="mix-blend-mode: normal"
											>
												<g transform="scale(5.12,5.12)">
													<path
														d="M5,4c-0.552,0 -1,0.447 -1,1v19h20v-20zM26,4v20h20v-19c0,-0.553 -0.448,-1 -1,-1zM4,26v19c0,0.553 0.448,1 1,1h19v-20zM26,26v20h19c0.552,0 1,-0.447 1,-1v-19z"
													></path>
												</g>
											</g>
										</svg>
										Outlook
									</button>
									<button class="calendar-action" data-calendar-type="ics">
										<svg
											width="20"
											height="20"
											viewBox="0 0 24 24"
											fill="none"
											stroke="currentColor"
										>
											<path
												d="M12 15V3m0 12l-4-4m4 4l4-4M3 17l.6 2.6c.2.8.8 1.4 1.6 1.4h13.6c.8 0 1.4-.6 1.6-1.4l.6-2.6"
												stroke-width="2"
												stroke-linecap="round"
												stroke-linejoin="round"
											/>
										</svg>
										Download .ics
									</button>
								</div>
							</div>
						</div>
						<div class="attendees">
							<div class="attendee-avatars">
								<div class="attendee-avatar" data-tooltip="Special Guest">
									<img src="public/special_guest.png" alt="Special Guest" />
								</div>
								<div class="attendee-avatar" data-tooltip="Sunil Pai">
									<img src="public/sunil.jpg" alt="Sunil Pai" />
								</div>
								<div class="attendee-avatar" data-tooltip="Dina Kozlov">
									<img src="public/dina.jpg" alt="Dina Kozlov" />
								</div>
							</div>
							<span class="attendee-count"><strong>+ all the other cool kids</strong> went</span>
						</div>
					</div>
				</section>

				<section class="right-panel">
					<div class="demos-section">
						<h4>DEMOS FROM</h4>
						<ul class="demo-companies">
							<li>Asana</li>
							<li>Atlassian</li>
							<li>Cloudflare</li>
							<li>Intercom</li>
							<li>Linear</li>
							<li>Paypal</li>
							<li>Sentry</li>
							<li>Square</li>
							<li>Stripe</li>
							<li>Webflow</li>
							<li>+ More</li>
						</ul>
					</div>
				</section>
			</main>

			<footer>
				<div class="footer-left">
					<img src="public/cloudflare_logo.svg" alt="Cloudflare" class="cloudflare-logo" />
					<div class="footer-links"></div>
				</div>
				<a
					href="https://developers.cloudflare.com/agents/guides/remote-mcp-server/?utm_source=website&utm_medium=reg+page&utm_campaign=MCP+Demo+Day"
					target="_blank"
					class="build-btn"
				>
					<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="24" height="24">
						<path d="M8.16 23h21.177v-5.86l-4.023-2.307-.694-.3-16.46.113z" fill="transparent" />
						<path
							d="M22.012 22.222c.197-.675.122-1.294-.206-1.754-.3-.422-.807-.666-1.416-.694l-11.545-.15c-.075 0-.14-.038-.178-.094s-.047-.13-.028-.206c.038-.113.15-.197.272-.206l11.648-.15c1.38-.066 2.88-1.182 3.404-2.55l.666-1.735a.38.38 0 0 0 .02-.225c-.75-3.395-3.78-5.927-7.4-5.927-3.34 0-6.17 2.157-7.184 5.15-.657-.488-1.5-.75-2.392-.666-1.604.16-2.9 1.444-3.048 3.048a3.58 3.58 0 0 0 .084 1.191A4.84 4.84 0 0 0 0 22.1c0 .234.02.47.047.703.02.113.113.197.225.197H21.58a.29.29 0 0 0 .272-.206l.16-.572z"
							fill="currentColor"
						/>
						<path
							d="M25.688 14.803l-.32.01c-.075 0-.14.056-.17.13l-.45 1.566c-.197.675-.122 1.294.206 1.754.3.422.807.666 1.416.694l2.457.15c.075 0 .14.038.178.094s.047.14.028.206c-.038.113-.15.197-.272.206l-2.56.15c-1.388.066-2.88 1.182-3.404 2.55l-.188.478c-.038.094.028.188.13.188h8.797a.23.23 0 0 0 .225-.169A6.41 6.41 0 0 0 32 21.106a6.32 6.32 0 0 0-6.312-6.302"
							fill="currentColor"
						/>
					</svg>
					Build An MCP Server
				</a>
			</footer>
		</div>
		<dialog id="calendarDialog" class="calendar-popover">
			<h4>Add to Calendar</h4>
			<button class="close-button" onclick="document.getElementById('calendarDialog').close()">
				<svg
					width="16"
					height="16"
					viewBox="0 0 24 24"
					fill="none"
					stroke="currentColor"
					stroke-width="1"
				>
					<path d="M6 18L18 6M6 6l12 12" stroke-linecap="round" stroke-linejoin="round" />
				</svg>
			</button>
			<div class="calendar-options">
				<button class="calendar-option" data-calendar-type="google">
					<svg
						xmlns="http://www.w3.org/2000/svg"
						x="0px"
						y="0px"
						width="100"
						height="100"
						viewBox="0,0,256,256"
						style="fill: #ffffff"
					>
						<g
							fill="#ffffff"
							fill-rule="nonzero"
							stroke="none"
							stroke-width="1"
							stroke-linecap="butt"
							stroke-linejoin="miter"
							stroke-miterlimit="10"
							stroke-dasharray=""
							stroke-dashoffset="0"
							font-family="none"
							font-weight="none"
							font-size="none"
							text-anchor="none"
							style="mix-blend-mode: normal"
						>
							<g transform="scale(5.12,5.12)">
								<path
									d="M25.99609,48c-12.68359,0 -23.00391,-10.31641 -23.00391,-23c0,-12.68359 10.32031,-23 23.00391,-23c5.74609,0 11.24609,2.12891 15.49219,5.99609l0.77344,0.70703l-7.58594,7.58594l-0.70312,-0.60156c-2.22656,-1.90625 -5.05859,-2.95703 -7.97656,-2.95703c-6.76562,0 -12.27344,5.50391 -12.27344,12.26953c0,6.76563 5.50781,12.26953 12.27344,12.26953c4.87891,0 8.73438,-2.49219 10.55078,-6.73828h-11.55078v-10.35547l22.55078,0.03125l0.16797,0.79297c1.17578,5.58203 0.23438,13.79297 -4.53125,19.66797c-3.94531,4.86328 -9.72656,7.33203 -17.1875,7.33203z"
								></path>
							</g>
						</g>
					</svg>
					<span>Google Calendar</span>
				</button>
				<button class="calendar-option" data-calendar-type="outlook">
					<svg
						xmlns="http://www.w3.org/2000/svg"
						x="0px"
						y="0px"
						width="100"
						height="100"
						viewBox="0,0,256,256"
						style="fill: #ffffff"
					>
						<g
							fill="#ffffff"
							fill-rule="nonzero"
							stroke="none"
							stroke-width="1"
							stroke-linecap="butt"
							stroke-linejoin="miter"
							stroke-miterlimit="10"
							stroke-dasharray=""
							stroke-dashoffset="0"
							font-family="none"
							font-weight="none"
							font-size="none"
							text-anchor="none"
							style="mix-blend-mode: normal"
						>
							<g transform="scale(5.12,5.12)">
								<path
									d="M5,4c-0.552,0 -1,0.447 -1,1v19h20v-20zM26,4v20h20v-19c0,-0.553 -0.448,-1 -1,-1zM4,26v19c0,0.553 0.448,1 1,1h19v-20zM26,26v20h19c0.552,0 1,-0.447 1,-1v-19z"
								></path>
							</g>
						</g>
					</svg>
					<span>Outlook Calendar</span>
				</button>
				<button class="calendar-option" data-calendar-type="apple">
					<svg
						xmlns="http://www.w3.org/2000/svg"
						x="0px"
						y="0px"
						width="100"
						height="100"
						viewBox="0,0,256,256"
						style="fill: #ffffff"
					>
						<g
							fill="#ffffff"
							fill-rule="nonzero"
							stroke="none"
							stroke-width="1"
							stroke-linecap="butt"
							stroke-linejoin="miter"
							stroke-miterlimit="10"
							stroke-dasharray=""
							stroke-dashoffset="0"
							font-family="none"
							font-weight="none"
							font-size="none"
							text-anchor="none"
							style="mix-blend-mode: normal"
						>
							<g transform="scale(5.12,5.12)">
								<path
									d="M44.52734,34.75c-1.07812,2.39453 -1.59766,3.46484 -2.98437,5.57813c-1.94141,2.95313 -4.67969,6.64063 -8.0625,6.66406c-3.01172,0.02734 -3.78906,-1.96484 -7.87891,-1.92969c-4.08594,0.01953 -4.9375,1.96875 -7.95312,1.9375c-3.38672,-0.03125 -5.97656,-3.35156 -7.91797,-6.30078c-5.42969,-8.26953 -6.00391,-17.96484 -2.64844,-23.12109c2.375,-3.65625 6.12891,-5.80469 9.65625,-5.80469c3.59375,0 5.85156,1.97266 8.82031,1.97266c2.88281,0 4.63672,-1.97656 8.79297,-1.97656c3.14063,0 6.46094,1.71094 8.83594,4.66406c-7.76562,4.25781 -6.50391,15.34766 1.33984,18.31641zM31.19531,8.46875c1.51172,-1.94141 2.66016,-4.67969 2.24219,-7.46875c-2.46484,0.16797 -5.34766,1.74219 -7.03125,3.78125c-1.52734,1.85938 -2.79297,4.61719 -2.30078,7.28516c2.69141,0.08594 5.47656,-1.51953 7.08984,-3.59766z"
								></path>
							</g>
						</g>
					</svg>
					<span>Apple Calendar</span>
				</button>
				<button class="calendar-option" data-calendar-type="ics">
					<svg
						width="20"
						height="20"
						viewBox="0 0 24 24"
						fill="none"
						stroke="currentColor"
						stroke-width="1"
					>
						<path
							d="M12 15V3m0 12l-4-4m4 4l4-4M2 17l.621 2.485A2 2 0 004.561 21h14.878a2 2 0 001.94-1.515L22 17"
							stroke-linecap="round"
							stroke-linejoin="round"
						/>
					</svg>
					<span>Download .ics File</span>
				</button>
			</div>
		</dialog>
		<script src="script.js"></script>
	</body>
</html>

```

--------------------------------------------------------------------------------
/apps/radar/src/tools/radar.tools.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from 'zod'

import { getCloudflareClient } from '@repo/mcp-common/src/cloudflare-api'
import { getProps } from '@repo/mcp-common/src/get-props'
import {
	PaginationLimitParam,
	PaginationOffsetParam,
} from '@repo/mcp-common/src/types/shared.types'

import {
	AiDimensionParam,
	AsnArrayParam,
	AsnParam,
	AsOrderByParam,
	ContinentArrayParam,
	DateEndArrayParam,
	DateEndParam,
	DateListParam,
	DateRangeArrayParam,
	DateRangeParam,
	DateStartArrayParam,
	DateStartParam,
	DnsDimensionParam,
	DomainParam,
	DomainRankingTypeParam,
	EmailRoutingDimensionParam,
	EmailSecurityDimensionParam,
	HttpDimensionParam,
	InternetQualityMetricParam,
	InternetServicesCategoryParam,
	InternetSpeedDimensionParam,
	InternetSpeedOrderByParam,
	IpParam,
	L3AttackDimensionParam,
	L7AttackDimensionParam,
	LocationArrayParam,
	LocationListParam,
	LocationParam,
} from '../types/radar'
import { resolveAndInvoke } from '../utils'

import type { RadarMCP } from '../radar.app'

export function registerRadarTools(agent: RadarMCP) {
	agent.server.tool(
		'list_autonomous_systems',
		'List Autonomous Systems',
		{
			limit: PaginationLimitParam,
			offset: PaginationOffsetParam,
			location: LocationParam.optional(),
			orderBy: AsOrderByParam,
		},
		async ({ limit, offset, location, orderBy }) => {
			try {
				const props = getProps(agent)
				const client = getCloudflareClient(props.accessToken)
				const r = await client.radar.entities.asns.list({
					limit,
					offset,
					location,
					orderBy,
				})

				return {
					content: [
						{
							type: 'text',
							text: JSON.stringify({
								result: r.asns,
							}),
						},
					],
				}
			} catch (error) {
				return {
					content: [
						{
							type: 'text',
							text: `Error listing ASes: ${error instanceof Error && error.message}`,
						},
					],
				}
			}
		}
	)

	agent.server.tool(
		'get_as_details',
		'Get Autonomous System details by ASN',
		{
			asn: AsnParam,
		},
		async ({ asn }) => {
			try {
				const props = getProps(agent)
				const client = getCloudflareClient(props.accessToken)
				const r = await client.radar.entities.asns.get(asn)

				return {
					content: [
						{
							type: 'text',
							text: JSON.stringify({
								result: r.asn,
							}),
						},
					],
				}
			} catch (error) {
				return {
					content: [
						{
							type: 'text',
							text: `Error getting AS details: ${error instanceof Error && error.message}`,
						},
					],
				}
			}
		}
	)

	agent.server.tool(
		'get_ip_details',
		'Get IP address information',
		{
			ip: IpParam,
		},
		async ({ ip }) => {
			try {
				const props = getProps(agent)
				const client = getCloudflareClient(props.accessToken)
				const r = await client.radar.entities.get({ ip })

				return {
					content: [
						{
							type: 'text',
							text: JSON.stringify({
								result: r.ip,
							}),
						},
					],
				}
			} catch (error) {
				return {
					content: [
						{
							type: 'text',
							text: `Error getting IP details: ${error instanceof Error && error.message}`,
						},
					],
				}
			}
		}
	)

	agent.server.tool(
		'get_traffic_anomalies',
		'Get traffic anomalies and outages',
		{
			limit: PaginationLimitParam,
			offset: PaginationOffsetParam,
			asn: AsnParam.optional(),
			location: LocationParam.optional(),
			dateRange: DateRangeParam.optional(),
			dateStart: DateStartParam.optional(),
			dateEnd: DateEndParam.optional(),
		},
		async ({ limit, offset, asn, location, dateStart, dateEnd, dateRange }) => {
			try {
				const props = getProps(agent)
				const client = getCloudflareClient(props.accessToken)
				const r = await client.radar.trafficAnomalies.get({
					limit,
					offset,
					asn,
					location,
					dateRange,
					dateStart,
					dateEnd,
					status: 'VERIFIED',
				})

				return {
					content: [
						{
							type: 'text',
							text: JSON.stringify({
								result: r.trafficAnomalies,
							}),
						},
					],
				}
			} catch (error) {
				return {
					content: [
						{
							type: 'text',
							text: `Error getting IP details: ${error instanceof Error && error.message}`,
						},
					],
				}
			}
		}
	)

	agent.server.tool(
		'get_internet_services_ranking',
		'Get top Internet services',
		{
			limit: PaginationLimitParam,
			date: DateListParam.optional(),
			serviceCategory: InternetServicesCategoryParam.optional(),
		},
		async ({ limit, date, serviceCategory }) => {
			try {
				const props = getProps(agent)
				const client = getCloudflareClient(props.accessToken)
				const r = await client.radar.ranking.internetServices.top({
					limit,
					date,
					serviceCategory,
				})

				return {
					content: [
						{
							type: 'text',
							text: JSON.stringify({
								result: r,
							}),
						},
					],
				}
			} catch (error) {
				return {
					content: [
						{
							type: 'text',
							text: `Error getting Internet services ranking: ${error instanceof Error && error.message}`,
						},
					],
				}
			}
		}
	)

	agent.server.tool(
		'get_domains_ranking',
		'Get top or trending domains',
		{
			limit: PaginationLimitParam,
			date: DateListParam.optional(),
			location: LocationListParam.optional(),
			rankingType: DomainRankingTypeParam.optional(),
		},
		async ({ limit, date, location, rankingType }) => {
			try {
				const props = getProps(agent)
				const client = getCloudflareClient(props.accessToken)
				const r = await client.radar.ranking.top({
					limit,
					date,
					location,
					rankingType,
				})

				return {
					content: [
						{
							type: 'text',
							text: JSON.stringify({
								result: r,
							}),
						},
					],
				}
			} catch (error) {
				return {
					content: [
						{
							type: 'text',
							text: `Error getting domains ranking: ${error instanceof Error && error.message}`,
						},
					],
				}
			}
		}
	)

	agent.server.tool(
		'get_domain_rank_details',
		'Get domain rank details',
		{
			domain: DomainParam,
			date: DateListParam.optional(),
		},
		async ({ domain, date }) => {
			try {
				const props = getProps(agent)
				const client = getCloudflareClient(props.accessToken)
				const r = await client.radar.ranking.domain.get(domain, { date })

				return {
					content: [
						{
							type: 'text',
							text: JSON.stringify({
								result: r,
							}),
						},
					],
				}
			} catch (error) {
				return {
					content: [
						{
							type: 'text',
							text: `Error getting domain ranking details: ${error instanceof Error && error.message}`,
						},
					],
				}
			}
		}
	)

	agent.server.tool(
		'get_http_data',
		'Retrieve HTTP traffic trends.',
		{
			dateRange: DateRangeArrayParam.optional(),
			dateStart: DateStartArrayParam.optional(),
			dateEnd: DateEndArrayParam.optional(),
			asn: AsnArrayParam,
			continent: ContinentArrayParam,
			location: LocationArrayParam,
			dimension: HttpDimensionParam,
		},
		async ({ dateStart, dateEnd, dateRange, asn, location, continent, dimension }) => {
			try {
				const props = getProps(agent)
				const client = getCloudflareClient(props.accessToken)
				const r = await resolveAndInvoke(client.radar.http, dimension, {
					asn,
					continent,
					location,
					dateRange,
					dateStart,
					dateEnd,
				})

				return {
					content: [
						{
							type: 'text',
							text: JSON.stringify({
								result: r,
							}),
						},
					],
				}
			} catch (error) {
				return {
					content: [
						{
							type: 'text',
							text: `Error getting HTTP data: ${error instanceof Error && error.message}`,
						},
					],
				}
			}
		}
	)

	agent.server.tool(
		'get_dns_queries_data',
		'Retrieve trends in DNS queries to the 1.1.1.1 resolver.',
		{
			dateRange: DateRangeArrayParam.optional(),
			dateStart: DateStartArrayParam.optional(),
			dateEnd: DateEndArrayParam.optional(),
			asn: AsnArrayParam,
			continent: ContinentArrayParam,
			location: LocationArrayParam,
			dimension: DnsDimensionParam,
		},
		async ({ dateStart, dateEnd, dateRange, asn, location, continent, dimension }) => {
			try {
				const props = getProps(agent)
				const client = getCloudflareClient(props.accessToken)
				const r = await resolveAndInvoke(client.radar.dns, dimension, {
					asn,
					continent,
					location,
					dateRange,
					dateStart,
					dateEnd,
				})

				return {
					content: [
						{
							type: 'text',
							text: JSON.stringify({
								result: r,
							}),
						},
					],
				}
			} catch (error) {
				return {
					content: [
						{
							type: 'text',
							text: `Error getting DNS data: ${error instanceof Error && error.message}`,
						},
					],
				}
			}
		}
	)

	agent.server.tool(
		'get_l7_attack_data',
		'Retrieve application layer (L7) attack trends.',
		{
			dateRange: DateRangeArrayParam.optional(),
			dateStart: DateStartArrayParam.optional(),
			dateEnd: DateEndArrayParam.optional(),
			asn: AsnArrayParam,
			continent: ContinentArrayParam,
			location: LocationArrayParam,
			dimension: L7AttackDimensionParam,
		},
		async ({ dateStart, dateEnd, dateRange, asn, location, continent, dimension }) => {
			try {
				const props = getProps(agent)
				const client = getCloudflareClient(props.accessToken)
				const r = await resolveAndInvoke(client.radar.attacks.layer7, dimension, {
					asn,
					continent,
					location,
					dateRange,
					dateStart,
					dateEnd,
				})

				return {
					content: [
						{
							type: 'text',
							text: JSON.stringify({
								result: r,
							}),
						},
					],
				}
			} catch (error) {
				return {
					content: [
						{
							type: 'text',
							text: `Error getting L7 attack data: ${error instanceof Error && error.message}`,
						},
					],
				}
			}
		}
	)

	agent.server.tool(
		'get_l3_attack_data',
		'Retrieve application layer (L3) attack trends.',
		{
			dateRange: DateRangeArrayParam.optional(),
			dateStart: DateStartArrayParam.optional(),
			dateEnd: DateEndArrayParam.optional(),
			asn: AsnArrayParam,
			continent: ContinentArrayParam,
			location: LocationArrayParam,
			dimension: L3AttackDimensionParam,
		},
		async ({ dateStart, dateEnd, dateRange, asn, location, continent, dimension }) => {
			try {
				const props = getProps(agent)
				const client = getCloudflareClient(props.accessToken)
				const r = await resolveAndInvoke(client.radar.attacks.layer3, dimension, {
					asn,
					continent,
					location,
					dateRange,
					dateStart,
					dateEnd,
				})

				return {
					content: [
						{
							type: 'text',
							text: JSON.stringify({
								result: r,
							}),
						},
					],
				}
			} catch (error) {
				return {
					content: [
						{
							type: 'text',
							text: `Error getting L3 attack data: ${error instanceof Error && error.message}`,
						},
					],
				}
			}
		}
	)

	agent.server.tool(
		'get_email_routing_data',
		'Retrieve Email Routing trends.',
		{
			dateRange: DateRangeArrayParam.optional(),
			dateStart: DateStartArrayParam.optional(),
			dateEnd: DateEndArrayParam.optional(),
			dimension: EmailRoutingDimensionParam,
		},
		async ({ dateStart, dateEnd, dateRange, dimension }) => {
			try {
				const props = getProps(agent)
				const client = getCloudflareClient(props.accessToken)
				const r = await resolveAndInvoke(client.radar.email.routing, dimension, {
					dateRange,
					dateStart,
					dateEnd,
				})

				return {
					content: [
						{
							type: 'text',
							text: JSON.stringify({
								result: r,
							}),
						},
					],
				}
			} catch (error) {
				return {
					content: [
						{
							type: 'text',
							text: `Error getting Email Routing data: ${error instanceof Error && error.message}`,
						},
					],
				}
			}
		}
	)

	agent.server.tool(
		'get_email_security_data',
		'Retrieve Email Security trends.',
		{
			dateRange: DateRangeArrayParam.optional(),
			dateStart: DateStartArrayParam.optional(),
			dateEnd: DateEndArrayParam.optional(),
			dimension: EmailSecurityDimensionParam,
		},
		async ({ dateStart, dateEnd, dateRange, dimension }) => {
			try {
				const props = getProps(agent)
				const client = getCloudflareClient(props.accessToken)
				const r = await resolveAndInvoke(client.radar.email.security, dimension, {
					dateRange,
					dateStart,
					dateEnd,
				})

				return {
					content: [
						{
							type: 'text',
							text: JSON.stringify({
								result: r,
							}),
						},
					],
				}
			} catch (error) {
				return {
					content: [
						{
							type: 'text',
							text: `Error getting Email Security data: ${error instanceof Error && error.message}`,
						},
					],
				}
			}
		}
	)

	agent.server.tool(
		'get_internet_speed_data',
		'Retrieve summary of bandwidth, latency, jitter, and packet loss, from the previous 90 days of Cloudflare Speed Test.',
		{
			dateEnd: DateEndArrayParam.optional(),
			asn: AsnArrayParam,
			continent: ContinentArrayParam,
			location: LocationArrayParam,
			dimension: InternetSpeedDimensionParam,
			orderBy: InternetSpeedOrderByParam.optional(),
		},
		async ({ dateEnd, asn, location, continent, dimension, orderBy }) => {
			if (orderBy && dimension === 'summary') {
				throw new Error('Order by is only allowed for top locations and ASes')
			}

			try {
				const props = getProps(agent)
				const client = getCloudflareClient(props.accessToken)
				const r = await resolveAndInvoke(client.radar.quality.speed, dimension, {
					asn,
					continent,
					location,
					dateEnd,
				})

				return {
					content: [
						{
							type: 'text',
							text: JSON.stringify({
								result: r,
							}),
						},
					],
				}
			} catch (error) {
				return {
					content: [
						{
							type: 'text',
							text: `Error getting Internet speed data: ${error instanceof Error && error.message}`,
						},
					],
				}
			}
		}
	)

	agent.server.tool(
		'get_internet_quality_data',
		'Retrieves a summary or time series of bandwidth, latency, or DNS response time percentiles from the Radar Internet Quality Index (IQI).',
		{
			dateRange: DateRangeArrayParam.optional(),
			dateStart: DateStartArrayParam.optional(),
			dateEnd: DateEndArrayParam.optional(),
			asn: AsnArrayParam,
			continent: ContinentArrayParam,
			location: LocationArrayParam,
			format: z.enum(['summary', 'timeseriesGroups']),
			metric: InternetQualityMetricParam,
		},
		async ({ dateRange, dateStart, dateEnd, asn, location, continent, format, metric }) => {
			try {
				const props = getProps(agent)
				const client = getCloudflareClient(props.accessToken)
				const r = await client.radar.quality.iqi[format]({
					asn,
					continent,
					location,
					dateRange,
					dateStart,
					dateEnd,
					metric,
				})

				return {
					content: [
						{
							type: 'text',
							text: JSON.stringify({
								result: r,
							}),
						},
					],
				}
			} catch (error) {
				return {
					content: [
						{
							type: 'text',
							text: `Error getting Internet quality data: ${error instanceof Error && error.message}`,
						},
					],
				}
			}
		}
	)

	agent.server.tool(
		'get_ai_data',
		'Retrieves AI-related data, including traffic from AI user agents, as well as popular models and model tasks specifically from Cloudflare Workers AI.',
		{
			dateRange: DateRangeArrayParam.optional(),
			dateStart: DateStartArrayParam.optional(),
			dateEnd: DateEndArrayParam.optional(),
			asn: AsnArrayParam,
			continent: ContinentArrayParam,
			location: LocationArrayParam,
			dimension: AiDimensionParam,
		},
		async ({ dateRange, dateStart, dateEnd, asn, location, continent, dimension }) => {
			try {
				const props = getProps(agent)
				const client = getCloudflareClient(props.accessToken)
				const r = await resolveAndInvoke(client.radar.ai, dimension, {
					asn,
					continent,
					location,
					dateRange,
					dateStart,
					dateEnd,
				})

				return {
					content: [
						{
							type: 'text',
							text: JSON.stringify({
								result: r,
							}),
						},
					],
				}
			} catch (error) {
				return {
					content: [
						{
							type: 'text',
							text: `Error getting AI data: ${error instanceof Error && error.message}`,
						},
					],
				}
			}
		}
	)
}

```
Page 4/25FirstPrevNextLast