#
tokens: 49618/50000 27/388 files (page 4/29)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 4 of 29. Use http://codebase.md/cloudflare/mcp-server-cloudflare?lines=true&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-ai-search
│   │   ├── .eslintrc.cjs
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── docs-ai-search.app.ts
│   │   │   └── docs-ai-search.context.ts
│   │   ├── tsconfig.json
│   │   ├── 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-ai-search.prompts.ts
│   │   │   │   └── docs-vectorize.prompts.ts
│   │   │   ├── scopes.ts
│   │   │   ├── sentry.ts
│   │   │   ├── server.ts
│   │   │   ├── tools
│   │   │   │   ├── account.tools.ts
│   │   │   │   ├── d1.tools.ts
│   │   │   │   ├── docs-ai-search.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
│   │   │   └── workers-oauth-utils.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/types/hyperdrive.types.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod'
 2 | 
 3 | import type { ConfigCreateParams } from 'cloudflare/resources/hyperdrive/configs.mjs'
 4 | 
 5 | // --- Base Field Schemas ---
 6 | 
 7 | /** Zod schema for a Hyperdrive config ID. */
 8 | export const HyperdriveConfigIdSchema = z
 9 | 	.string()
10 | 	.describe('The ID of the Hyperdrive configuration')
11 | 
12 | /** Zod schema for a Hyperdrive config name. */
13 | export const HyperdriveConfigNameSchema: z.ZodType<ConfigCreateParams['name']> = z
14 | 	.string()
15 | 	.min(1)
16 | 	.max(64)
17 | 	.regex(/^[a-zA-Z0-9_-]+$/)
18 | 	.describe('The name of the Hyperdrive configuration (alphanumeric, underscore, hyphen)')
19 | 
20 | // --- Origin Field Schemas ---
21 | 
22 | /** Zod schema for the origin database name. */
23 | export const HyperdriveOriginDatabaseSchema: z.ZodType<
24 | 	ConfigCreateParams.PublicDatabase['database']
25 | > = z.string().describe('The database name')
26 | /** Zod schema for the origin database host. */
27 | export const HyperdriveOriginHostSchema: z.ZodType<ConfigCreateParams.PublicDatabase['host']> = z
28 | 	.string()
29 | 	.describe('The database host address')
30 | /** Zod schema for the origin database port. */
31 | export const HyperdriveOriginPortSchema: z.ZodType<ConfigCreateParams.PublicDatabase['port']> = z
32 | 	.number()
33 | 	.int()
34 | 	.min(1)
35 | 	.max(65535)
36 | 	.describe('The database port')
37 | /** Zod schema for the origin database scheme. */
38 | export const HyperdriveOriginSchemeSchema: z.ZodType<ConfigCreateParams.PublicDatabase['scheme']> =
39 | 	z.enum(['postgresql']).describe('The database protocol')
40 | /** Zod schema for the origin database user. */
41 | export const HyperdriveOriginUserSchema: z.ZodType<ConfigCreateParams.PublicDatabase['user']> = z
42 | 	.string()
43 | 	.describe('The database user')
44 | /** Zod schema for the origin database password. */
45 | export const HyperdriveOriginPasswordSchema: z.ZodType<
46 | 	ConfigCreateParams.PublicDatabase['password']
47 | > = z.string().describe('The database password')
48 | 
49 | // --- Caching Field Schemas (Referencing ConfigCreateParams.HyperdriveHyperdriveCachingEnabled) ---
50 | 
51 | /** Zod schema for disabling caching. */
52 | export const HyperdriveCachingDisabledSchema: z.ZodType<
53 | 	ConfigCreateParams.HyperdriveHyperdriveCachingEnabled['disabled']
54 | > = z.boolean().optional().describe('Whether caching is disabled')
55 | /** Zod schema for the maximum cache age. */
56 | export const HyperdriveCachingMaxAgeSchema: z.ZodType<
57 | 	ConfigCreateParams.HyperdriveHyperdriveCachingEnabled['max_age']
58 | > = z.number().int().min(1).optional().describe('Maximum cache age in seconds')
59 | /** Zod schema for the stale while revalidate duration. */
60 | export const HyperdriveCachingStaleWhileRevalidateSchema: z.ZodType<
61 | 	ConfigCreateParams.HyperdriveHyperdriveCachingEnabled['stale_while_revalidate']
62 | > = z.number().int().min(1).optional().describe('Stale while revalidate duration in seconds')
63 | 
64 | // --- List Parameter Schemas (Cannot directly type against SDK ConfigListParams which only has account_id) ---
65 | 
66 | /** Zod schema for the list page number. */
67 | export const HyperdriveListParamPageSchema = z
68 | 	.number()
69 | 	.int()
70 | 	.positive()
71 | 	.optional()
72 | 	.describe('Page number of results')
73 | /** Zod schema for the list results per page. */
74 | export const HyperdriveListParamPerPageSchema = z
75 | 	.number()
76 | 	.int()
77 | 	.min(1)
78 | 	.max(100)
79 | 	.optional()
80 | 	.describe('Number of results per page')
81 | /** Zod schema for the list order field. */
82 | export const HyperdriveListParamOrderSchema = z
83 | 	.enum(['id', 'name'])
84 | 	.optional()
85 | 	.describe('Field to order by')
86 | /** Zod schema for the list order direction. */
87 | export const HyperdriveListParamDirectionSchema = z
88 | 	.enum(['asc', 'desc'])
89 | 	.optional()
90 | 	.describe('Direction to order')
91 | 
92 | // --- Tool Parameter Schemas ---
93 | 
```

--------------------------------------------------------------------------------
/packages/mcp-common/src/format.spec.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { describe, expect, it } from 'vitest'
  2 | 
  3 | import { fmt } from './format'
  4 | 
  5 | describe('fmt', () => {
  6 | 	describe('trim()', () => {
  7 | 		it('should return an empty string for an empty input', () => {
  8 | 			expect(fmt.trim('')).toBe('')
  9 | 		})
 10 | 
 11 | 		it('should trim leading and trailing spaces', () => {
 12 | 			expect(fmt.trim('  hello  ')).toBe('hello')
 13 | 		})
 14 | 
 15 | 		it('should trim leading and trailing newlines', () => {
 16 | 			expect(fmt.trim('\n\nhello\n\n')).toBe('hello')
 17 | 		})
 18 | 
 19 | 		it('should trim leading/trailing spaces and newlines from each line but not remove empty lines', () => {
 20 | 			const input = `
 21 |         line1
 22 |           line2
 23 | 
 24 |         line3
 25 |       `
 26 | 			const expected = `line1
 27 | line2
 28 | 
 29 | line3`
 30 | 			expect(fmt.trim(input)).toBe(expected)
 31 | 		})
 32 | 
 33 | 		it('should handle a string that is already trimmed', () => {
 34 | 			expect(fmt.trim('hello\nworld')).toBe('hello\nworld')
 35 | 		})
 36 | 
 37 | 		it('should handle a string with only spaces', () => {
 38 | 			expect(fmt.trim('   ')).toBe('')
 39 | 		})
 40 | 
 41 | 		it('should handle a string with only newlines', () => {
 42 | 			expect(fmt.trim('\n\n\n')).toBe('')
 43 | 		})
 44 | 
 45 | 		it('should preserve empty lines from the middle', () => {
 46 | 			expect(fmt.trim('hello\n\nworld')).toBe('hello\n\nworld')
 47 | 		})
 48 | 	})
 49 | 
 50 | 	describe('oneLine()', () => {
 51 | 		it('should return an empty string for an empty input', () => {
 52 | 			expect(fmt.oneLine('')).toBe('')
 53 | 		})
 54 | 
 55 | 		it('should convert a multi-line string to a single line', () => {
 56 | 			expect(fmt.oneLine('hello\nworld')).toBe('hello world')
 57 | 		})
 58 | 
 59 | 		it('should trim leading/trailing spaces and newlines before joining', () => {
 60 | 			expect(fmt.oneLine('  hello  \n  world  \n')).toBe('hello world')
 61 | 		})
 62 | 
 63 | 		it('should remove empty lines before joining', () => {
 64 | 			expect(fmt.oneLine('hello\n\nworld')).toBe('hello world')
 65 | 		})
 66 | 
 67 | 		it('should handle a string that is already a single line', () => {
 68 | 			expect(fmt.oneLine('hello world')).toBe('hello world')
 69 | 		})
 70 | 
 71 | 		it('should handle a string with only spaces and newlines', () => {
 72 | 			expect(fmt.oneLine('  \n   \n  ')).toBe('')
 73 | 		})
 74 | 	})
 75 | 
 76 | 	describe('asTSV()', () => {
 77 | 		it('should convert an empty array to an empty string', async () => {
 78 | 			expect(await fmt.asTSV([])).toBe('')
 79 | 		})
 80 | 
 81 | 		it('should convert an array of one object to a TSV string', async () => {
 82 | 			const data = [{ a: 1, b: 'hello' }]
 83 | 			expect(await fmt.asTSV(data)).toBe('a\tb\n1\thello')
 84 | 		})
 85 | 
 86 | 		it('should convert an array of multiple objects to a TSV string', async () => {
 87 | 			const data = [
 88 | 				{ a: 1, b: 'hello' },
 89 | 				{ a: 2, b: 'world' },
 90 | 			]
 91 | 			expect(await fmt.asTSV(data)).toBe('a\tb\n1\thello\n2\tworld')
 92 | 		})
 93 | 
 94 | 		it('should handle objects with different keys (using keys from the first object as headers)', async () => {
 95 | 			const data = [
 96 | 				{ a: 1, b: 'hello' },
 97 | 				{ a: 2, c: 'world' },
 98 | 			]
 99 | 			expect(await fmt.asTSV(data)).toBe('a\tb\n1\thello\n2\t')
100 | 			expect(await fmt.asTSV(data)).toMatchInlineSnapshot(`
101 | 				"a	b
102 | 				1	hello
103 | 				2	"
104 | 			`)
105 | 		})
106 | 
107 | 		it('should handle values with tabs and newlines (fast-csv should quote them)', async () => {
108 | 			const data = [{ name: 'John\tDoe', description: 'Line1\nLine2' }]
109 | 			expect(await fmt.asTSV(data)).toBe('name\tdescription\n"John\tDoe"\t"Line1\nLine2"')
110 | 			expect(await fmt.asTSV(data)).toMatchInlineSnapshot(`
111 | 				"name	description
112 | 				"John	Doe"	"Line1
113 | 				Line2""
114 | 			`)
115 | 		})
116 | 
117 | 		it('should handle values with quotes (fast-csv should escape them)', async () => {
118 | 			const data = [{ name: 'James "Jim" Raynor' }]
119 | 			expect(await fmt.asTSV(data)).toBe('name\n"James ""Jim"" Raynor"')
120 | 			expect(await fmt.asTSV(data)).toMatchInlineSnapshot(`
121 | 				"name
122 | 				"James ""Jim"" Raynor""
123 | 			`)
124 | 		})
125 | 	})
126 | })
127 | 
```

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

```typescript
  1 | import { z } from 'zod'
  2 | 
  3 | import { fetchCloudflareApi } from '@repo/mcp-common/src/cloudflare-api'
  4 | import { getProps } from '@repo/mcp-common/src/get-props'
  5 | 
  6 | import type { LogsMCP } from '../logpush.app'
  7 | 
  8 | const zJobIdentifier = z.number().int().min(1).optional().describe('Unique id of the job.')
  9 | const zEnabled = z.boolean().optional().describe('Flag that indicates if the job is enabled.')
 10 | const zName = z
 11 | 	.string()
 12 | 	.regex(/^[a-zA-Z0-9\-.]*$/)
 13 | 	.max(512)
 14 | 	.nullable()
 15 | 	.optional()
 16 | 	.describe('Optional human readable job name. Not unique.')
 17 | const zDataset = z
 18 | 	.string()
 19 | 	.regex(/^[a-zA-Z0-9_-]*$/)
 20 | 	.max(256)
 21 | 	.nullable()
 22 | 	.optional()
 23 | 	.describe('Name of the dataset.')
 24 | const zLastComplete = z
 25 | 	.string()
 26 | 	.datetime()
 27 | 	.nullable()
 28 | 	.optional()
 29 | 	.describe('Records the last time for which logs have been successfully pushed.')
 30 | const zLastError = z
 31 | 	.string()
 32 | 	.datetime()
 33 | 	.nullable()
 34 | 	.optional()
 35 | 	.describe('Records the last time the job failed.')
 36 | const zErrorMessage = z
 37 | 	.string()
 38 | 	.nullable()
 39 | 	.optional()
 40 | 	.describe('If not null, the job is currently failing.')
 41 | 
 42 | export const zLogpushJob = z
 43 | 	.object({
 44 | 		id: zJobIdentifier,
 45 | 		enabled: zEnabled,
 46 | 		name: zName,
 47 | 		dataset: zDataset,
 48 | 		last_complete: zLastComplete,
 49 | 		last_error: zLastError,
 50 | 		error_message: zErrorMessage,
 51 | 	})
 52 | 	.nullable()
 53 | 	.optional()
 54 | 
 55 | const zApiResponseCommon = z.object({
 56 | 	success: z.literal(true),
 57 | 	errors: z.array(z.object({ message: z.string() })).optional(),
 58 | })
 59 | 
 60 | const zLogPushJobResults = z.array(zLogpushJob).optional()
 61 | 
 62 | // The complete schema for zone_logpush_job_response_collection
 63 | export const zLogpushJobResponseCollection = zApiResponseCommon.extend({
 64 | 	result: zLogPushJobResults,
 65 | })
 66 | 
 67 | /**
 68 |  * Fetches available telemetry keys for a specified Cloudflare Worker
 69 |  * @param accountId Cloudflare account ID
 70 |  * @param apiToken Cloudflare API token
 71 |  * @returns List of telemetry keys available for the worker
 72 |  */
 73 | 
 74 | export async function handleGetAccountLogPushJobs(
 75 | 	accountId: string,
 76 | 	apiToken: string
 77 | ): Promise<z.infer<typeof zLogPushJobResults>> {
 78 | 	// Call the Public API
 79 | 	const data = await fetchCloudflareApi({
 80 | 		endpoint: `/logpush/jobs`,
 81 | 		accountId,
 82 | 		apiToken,
 83 | 		responseSchema: zLogpushJobResponseCollection,
 84 | 		options: {
 85 | 			method: 'GET',
 86 | 			headers: {
 87 | 				'Content-Type': 'application/json',
 88 | 				'portal-version': '2',
 89 | 			},
 90 | 		},
 91 | 	})
 92 | 
 93 | 	const res = data as z.infer<typeof zLogpushJobResponseCollection>
 94 | 	return (res.result ?? []).slice(0, 100)
 95 | }
 96 | 
 97 | /**
 98 |  * Registers the logs analysis tool with the MCP server
 99 |  * @param server The MCP server instance
100 |  * @param accountId Cloudflare account ID
101 |  * @param apiToken Cloudflare API token
102 |  */
103 | export function registerLogsTools(agent: LogsMCP) {
104 | 	// Register the worker logs analysis tool by worker name
105 | 	agent.server.tool(
106 | 		'logpush_jobs_by_account_id',
107 | 		`All Logpush jobs by Account ID.
108 | 
109 | 		You should use this tool when:
110 | 		- You have questions or wish to request information about their Cloudflare Logpush jobs by account
111 | 		- You want a condensed version for the output results of your account's Cloudflare Logpush job
112 | 
113 | 		This tool returns at most the first 100 jobs.
114 | 		`,
115 | 		{},
116 | 		async () => {
117 | 			const accountId = await agent.getActiveAccountId()
118 | 			if (!accountId) {
119 | 				return {
120 | 					content: [
121 | 						{
122 | 							type: 'text',
123 | 							text: 'No currently active accountId. Try listing your accounts (accounts_list) and then setting an active account (set_active_account)',
124 | 						},
125 | 					],
126 | 				}
127 | 			}
128 | 			try {
129 | 				const props = getProps(agent)
130 | 				const result = await handleGetAccountLogPushJobs(accountId, props.accessToken)
131 | 				return {
132 | 					content: [
133 | 						{
134 | 							type: 'text',
135 | 							text: JSON.stringify({
136 | 								result,
137 | 							}),
138 | 						},
139 | 					],
140 | 				}
141 | 			} catch (e) {
142 | 				agent.server.recordError(e)
143 | 				return {
144 | 					content: [
145 | 						{
146 | 							type: 'text',
147 | 							text: JSON.stringify({
148 | 								error: `Error analyzing logpush jobs: ${e instanceof Error && e.message}`,
149 | 							}),
150 | 						},
151 | 					],
152 | 				}
153 | 			}
154 | 		}
155 | 	)
156 | }
157 | 
```

--------------------------------------------------------------------------------
/apps/browser-rendering/src/tools/browser.tools.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { z } from 'zod'
  2 | 
  3 | import { getCloudflareClient } from '@repo/mcp-common/src/cloudflare-api'
  4 | import { getProps } from '@repo/mcp-common/src/get-props'
  5 | 
  6 | import type { BrowserMCP } from '../browser.app'
  7 | 
  8 | export function registerBrowserTools(agent: BrowserMCP) {
  9 | 	agent.server.tool(
 10 | 		'get_url_html_content',
 11 | 		'Get page HTML content',
 12 | 		{
 13 | 			url: z.string().url(),
 14 | 		},
 15 | 		async (params) => {
 16 | 			const accountId = await agent.getActiveAccountId()
 17 | 			if (!accountId) {
 18 | 				return {
 19 | 					content: [
 20 | 						{
 21 | 							type: 'text',
 22 | 							text: 'No currently active accountId. Try listing your accounts (accounts_list) and then setting an active account (set_active_account)',
 23 | 						},
 24 | 					],
 25 | 				}
 26 | 			}
 27 | 			try {
 28 | 				const props = getProps(agent)
 29 | 				const client = getCloudflareClient(props.accessToken)
 30 | 				const r = await client.browserRendering.content.create({
 31 | 					account_id: accountId,
 32 | 					url: params.url,
 33 | 				})
 34 | 
 35 | 				return {
 36 | 					content: [
 37 | 						{
 38 | 							type: 'text',
 39 | 							text: JSON.stringify({
 40 | 								result: r,
 41 | 							}),
 42 | 						},
 43 | 					],
 44 | 				}
 45 | 			} catch (error) {
 46 | 				return {
 47 | 					content: [
 48 | 						{
 49 | 							type: 'text',
 50 | 							text: `Error getting page html: ${error instanceof Error && error.message}`,
 51 | 						},
 52 | 					],
 53 | 				}
 54 | 			}
 55 | 		}
 56 | 	)
 57 | 
 58 | 	agent.server.tool(
 59 | 		'get_url_markdown',
 60 | 		'Get page converted into Markdown',
 61 | 		{
 62 | 			url: z.string().url(),
 63 | 		},
 64 | 		async (params) => {
 65 | 			const accountId = await agent.getActiveAccountId()
 66 | 			if (!accountId) {
 67 | 				return {
 68 | 					content: [
 69 | 						{
 70 | 							type: 'text',
 71 | 							text: 'No currently active accountId. Try listing your accounts (accounts_list) and then setting an active account (set_active_account)',
 72 | 						},
 73 | 					],
 74 | 				}
 75 | 			}
 76 | 			try {
 77 | 				const props = getProps(agent)
 78 | 				const client = getCloudflareClient(props.accessToken)
 79 | 				const r = (await client.post(`/accounts/${accountId}/browser-rendering/markdown`, {
 80 | 					body: {
 81 | 						url: params.url,
 82 | 					},
 83 | 				})) as { result: string }
 84 | 
 85 | 				return {
 86 | 					content: [
 87 | 						{
 88 | 							type: 'text',
 89 | 							text: JSON.stringify({
 90 | 								result: r.result,
 91 | 							}),
 92 | 						},
 93 | 					],
 94 | 				}
 95 | 			} catch (error) {
 96 | 				return {
 97 | 					content: [
 98 | 						{
 99 | 							type: 'text',
100 | 							text: `Error getting page in markdown: ${error instanceof Error && error.message}`,
101 | 						},
102 | 					],
103 | 				}
104 | 			}
105 | 		}
106 | 	)
107 | 
108 | 	agent.server.tool(
109 | 		'get_url_screenshot',
110 | 		'Get page screenshot',
111 | 		{
112 | 			url: z.string().url(),
113 | 			viewport: z
114 | 				.object({
115 | 					height: z.number().default(600),
116 | 					width: z.number().default(800),
117 | 				})
118 | 				.optional(),
119 | 		},
120 | 		async (params) => {
121 | 			const accountId = await agent.getActiveAccountId()
122 | 			if (!accountId) {
123 | 				return {
124 | 					content: [
125 | 						{
126 | 							type: 'text',
127 | 							text: 'No currently active accountId. Try listing your accounts (accounts_list) and then setting an active account (set_active_account)',
128 | 						},
129 | 					],
130 | 				}
131 | 			}
132 | 			try {
133 | 				const props = getProps(agent)
134 | 				// Cf client appears to be broken, so we use the raw API instead.
135 | 				// const client = getCloudflareClient(props.accessToken)
136 | 				// const r = await client.browserRendering.screenshot.create({
137 | 				// 	account_id: accountId,
138 | 				// 	url: params.url,
139 | 				// 	viewport: params.viewport,
140 | 				// })
141 | 
142 | 				const r = await fetch(
143 | 					`https://api.cloudflare.com/client/v4/accounts/${accountId}/browser-rendering/screenshot`,
144 | 					{
145 | 						method: 'POST',
146 | 						headers: {
147 | 							'Content-Type': 'application/json',
148 | 							Authorization: `Bearer ${props.accessToken}`,
149 | 						},
150 | 						body: JSON.stringify({
151 | 							url: params.url,
152 | 							viewport: params.viewport,
153 | 						}),
154 | 					}
155 | 				)
156 | 
157 | 				const arrayBuffer = await r.arrayBuffer()
158 | 				const base64Image = Buffer.from(arrayBuffer).toString('base64')
159 | 
160 | 				return {
161 | 					content: [
162 | 						{
163 | 							type: 'image',
164 | 							mimeType: 'image/png',
165 | 							data: base64Image,
166 | 						},
167 | 					],
168 | 				}
169 | 			} catch (error) {
170 | 				return {
171 | 					content: [
172 | 						{
173 | 							type: 'text',
174 | 							text: `Error getting page in markdown: ${error instanceof Error && error.message}`,
175 | 						},
176 | 					],
177 | 				}
178 | 			}
179 | 		}
180 | 	)
181 | }
182 | 
```

--------------------------------------------------------------------------------
/apps/cloudflare-one-casb/src/cf1-casb.app.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import OAuthProvider from '@cloudflare/workers-oauth-provider'
  2 | import { McpAgent } from 'agents/mcp'
  3 | 
  4 | import { handleApiTokenMode, isApiTokenRequest } from '@repo/mcp-common/src/api-token-mode'
  5 | import {
  6 | 	createAuthHandlers,
  7 | 	handleTokenExchangeCallback,
  8 | } from '@repo/mcp-common/src/cloudflare-oauth-handler'
  9 | import { getUserDetails, UserDetails } from '@repo/mcp-common/src/durable-objects/user_details.do'
 10 | import { getEnv } from '@repo/mcp-common/src/env'
 11 | import { getProps } from '@repo/mcp-common/src/get-props'
 12 | import { RequiredScopes } from '@repo/mcp-common/src/scopes'
 13 | import { CloudflareMCPServer } from '@repo/mcp-common/src/server'
 14 | import { registerAccountTools } from '@repo/mcp-common/src/tools/account.tools'
 15 | 
 16 | import { MetricsTracker } from '../../../packages/mcp-observability/src'
 17 | import { registerIntegrationsTools } from './tools/integrations.tools'
 18 | 
 19 | import type { AuthProps } from '@repo/mcp-common/src/cloudflare-oauth-handler'
 20 | import type { Env } from './cf1-casb.context'
 21 | 
 22 | export { UserDetails }
 23 | 
 24 | const env = getEnv<Env>()
 25 | 
 26 | const metrics = new MetricsTracker(env.MCP_METRICS, {
 27 | 	name: env.MCP_SERVER_NAME,
 28 | 	version: env.MCP_SERVER_VERSION,
 29 | })
 30 | 
 31 | // Context from the auth process, encrypted & stored in the auth token
 32 | // and provided to the DurableMCP as this.props
 33 | type Props = AuthProps
 34 | 
 35 | type State = { activeAccountId: string | null }
 36 | export class CASBMCP extends McpAgent<Env, State, Props> {
 37 | 	_server: CloudflareMCPServer | undefined
 38 | 	set server(server: CloudflareMCPServer) {
 39 | 		this._server = server
 40 | 	}
 41 | 
 42 | 	get server(): CloudflareMCPServer {
 43 | 		if (!this._server) {
 44 | 			throw new Error('Tried to access server before it was initialized')
 45 | 		}
 46 | 
 47 | 		return this._server
 48 | 	}
 49 | 
 50 | 	constructor(ctx: DurableObjectState, env: Env) {
 51 | 		super(ctx, env)
 52 | 	}
 53 | 
 54 | 	async init() {
 55 | 		// TODO: Probably we'll want to track account tokens usage through an account identifier at some point
 56 | 		const props = getProps(this)
 57 | 		const userId = props.type === 'user_token' ? props.user.id : undefined
 58 | 
 59 | 		this.server = new CloudflareMCPServer({
 60 | 			userId,
 61 | 			wae: this.env.MCP_METRICS,
 62 | 			serverInfo: {
 63 | 				name: this.env.MCP_SERVER_NAME,
 64 | 				version: this.env.MCP_SERVER_VERSION,
 65 | 			},
 66 | 		})
 67 | 
 68 | 		registerAccountTools(this)
 69 | 		registerIntegrationsTools(this)
 70 | 	}
 71 | 
 72 | 	async getActiveAccountId() {
 73 | 		try {
 74 | 			const props = getProps(this)
 75 | 			// account tokens are scoped to one account
 76 | 			if (props.type === 'account_token') {
 77 | 				return props.account.id
 78 | 			}
 79 | 			// Get UserDetails Durable Object based off the userId and retrieve the activeAccountId from it
 80 | 			// we do this so we can persist activeAccountId across sessions
 81 | 			const userDetails = getUserDetails(env, props.user.id)
 82 | 			return await userDetails.getActiveAccountId()
 83 | 		} catch (e) {
 84 | 			this.server.recordError(e)
 85 | 			return null
 86 | 		}
 87 | 	}
 88 | 
 89 | 	async setActiveAccountId(accountId: string) {
 90 | 		try {
 91 | 			const props = getProps(this)
 92 | 			// account tokens are scoped to one account
 93 | 			if (props.type === 'account_token') {
 94 | 				return
 95 | 			}
 96 | 			const userDetails = getUserDetails(env, props.user.id)
 97 | 			await userDetails.setActiveAccountId(accountId)
 98 | 		} catch (e) {
 99 | 			this.server.recordError(e)
100 | 		}
101 | 	}
102 | }
103 | const CloudflareOneCasbScopes = {
104 | 	...RequiredScopes,
105 | 	'account:read': 'See your account info such as account details, analytics, and memberships.',
106 | 	'teams:read': 'See Cloudflare One Resources',
107 | } as const
108 | 
109 | export default {
110 | 	fetch: async (req: Request, env: Env, ctx: ExecutionContext) => {
111 | 		if (await isApiTokenRequest(req, env)) {
112 | 			return await handleApiTokenMode(CASBMCP, req, env, ctx)
113 | 		}
114 | 
115 | 		return new OAuthProvider({
116 | 			apiHandlers: {
117 | 				'/mcp': CASBMCP.serve('/mcp'),
118 | 				'/sse': CASBMCP.serveSSE('/sse'),
119 | 			},
120 | 			// @ts-ignore
121 | 			defaultHandler: createAuthHandlers({ scopes: CloudflareOneCasbScopes, metrics }),
122 | 			authorizeEndpoint: '/oauth/authorize',
123 | 			tokenEndpoint: '/token',
124 | 			tokenExchangeCallback: (options) =>
125 | 				handleTokenExchangeCallback(
126 | 					options,
127 | 					env.CLOUDFLARE_CLIENT_ID,
128 | 					env.CLOUDFLARE_CLIENT_SECRET
129 | 				),
130 | 			// Cloudflare access token TTL
131 | 			accessTokenTTL: 3600,
132 | 			clientRegistrationEndpoint: '/register',
133 | 		}).fetch(req, env, ctx)
134 | 	},
135 | }
136 | 
```

--------------------------------------------------------------------------------
/apps/autorag/src/autorag.app.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import OAuthProvider from '@cloudflare/workers-oauth-provider'
  2 | import { McpAgent } from 'agents/mcp'
  3 | 
  4 | import { handleApiTokenMode, isApiTokenRequest } from '@repo/mcp-common/src/api-token-mode'
  5 | import {
  6 | 	createAuthHandlers,
  7 | 	handleTokenExchangeCallback,
  8 | } from '@repo/mcp-common/src/cloudflare-oauth-handler'
  9 | import { getUserDetails, UserDetails } from '@repo/mcp-common/src/durable-objects/user_details.do'
 10 | import { getEnv } from '@repo/mcp-common/src/env'
 11 | import { getProps } from '@repo/mcp-common/src/get-props'
 12 | import { RequiredScopes } from '@repo/mcp-common/src/scopes'
 13 | import { CloudflareMCPServer } from '@repo/mcp-common/src/server'
 14 | import { registerAccountTools } from '@repo/mcp-common/src/tools/account.tools'
 15 | 
 16 | import { MetricsTracker } from '../../../packages/mcp-observability/src'
 17 | import { registerAutoRAGTools } from './tools/autorag.tools'
 18 | 
 19 | import type { AuthProps } from '@repo/mcp-common/src/cloudflare-oauth-handler'
 20 | import type { Env } from './autorag.context'
 21 | 
 22 | const env = getEnv<Env>()
 23 | 
 24 | export { UserDetails }
 25 | 
 26 | const metrics = new MetricsTracker(env.MCP_METRICS, {
 27 | 	name: env.MCP_SERVER_NAME,
 28 | 	version: env.MCP_SERVER_VERSION,
 29 | })
 30 | 
 31 | // Context from the auth process, encrypted & stored in the auth token
 32 | // and provided to the DurableMCP as this.props
 33 | type Props = AuthProps
 34 | type State = { activeAccountId: string | null }
 35 | 
 36 | export class AutoRAGMCP extends McpAgent<Env, State, Props> {
 37 | 	_server: CloudflareMCPServer | undefined
 38 | 	set server(server: CloudflareMCPServer) {
 39 | 		this._server = server
 40 | 	}
 41 | 	get server(): CloudflareMCPServer {
 42 | 		if (!this._server) {
 43 | 			throw new Error('Tried to access server before it was initialized')
 44 | 		}
 45 | 
 46 | 		return this._server
 47 | 	}
 48 | 
 49 | 	constructor(ctx: DurableObjectState, env: Env) {
 50 | 		super(ctx, env)
 51 | 	}
 52 | 
 53 | 	async init() {
 54 | 		// TODO: Probably we'll want to track account tokens usage through an account identifier at some point
 55 | 		const props = getProps(this)
 56 | 		const userId = props.type === 'user_token' ? props.user.id : undefined
 57 | 
 58 | 		this.server = new CloudflareMCPServer({
 59 | 			userId,
 60 | 			wae: this.env.MCP_METRICS,
 61 | 			serverInfo: {
 62 | 				name: this.env.MCP_SERVER_NAME,
 63 | 				version: this.env.MCP_SERVER_VERSION,
 64 | 			},
 65 | 		})
 66 | 
 67 | 		registerAccountTools(this)
 68 | 
 69 | 		// Register Cloudflare Log Push tools
 70 | 		registerAutoRAGTools(this)
 71 | 	}
 72 | 
 73 | 	async getActiveAccountId() {
 74 | 		try {
 75 | 			const props = getProps(this)
 76 | 			// account tokens are scoped to one account
 77 | 			if (props.type === 'account_token') {
 78 | 				return props.account.id
 79 | 			}
 80 | 			// Get UserDetails Durable Object based off the userId and retrieve the activeAccountId from it
 81 | 			// we do this so we can persist activeAccountId across sessions
 82 | 			const userDetails = getUserDetails(env, props.user.id)
 83 | 			return await userDetails.getActiveAccountId()
 84 | 		} catch (e) {
 85 | 			this.server.recordError(e)
 86 | 			return null
 87 | 		}
 88 | 	}
 89 | 
 90 | 	async setActiveAccountId(accountId: string) {
 91 | 		try {
 92 | 			const props = getProps(this)
 93 | 			// account tokens are scoped to one account
 94 | 			if (props.type === 'account_token') {
 95 | 				return
 96 | 			}
 97 | 			const userDetails = getUserDetails(env, props.user.id)
 98 | 			await userDetails.setActiveAccountId(accountId)
 99 | 		} catch (e) {
100 | 			this.server.recordError(e)
101 | 		}
102 | 	}
103 | }
104 | 
105 | const LogPushScopes = {
106 | 	...RequiredScopes,
107 | 	'account:read': 'See your account info such as account details, analytics, and memberships.',
108 | 	'rag:write': 'Grants write level access to AutoRag.',
109 | } as const
110 | 
111 | export default {
112 | 	fetch: async (req: Request, env: Env, ctx: ExecutionContext) => {
113 | 		if (await isApiTokenRequest(req, env)) {
114 | 			return await handleApiTokenMode(AutoRAGMCP, req, env, ctx)
115 | 		}
116 | 
117 | 		return new OAuthProvider({
118 | 			apiHandlers: {
119 | 				'/mcp': AutoRAGMCP.serve('/mcp'),
120 | 				'/sse': AutoRAGMCP.serveSSE('/sse'),
121 | 			},
122 | 			// @ts-ignore
123 | 			defaultHandler: createAuthHandlers({ scopes: LogPushScopes, metrics }),
124 | 			authorizeEndpoint: '/oauth/authorize',
125 | 			tokenEndpoint: '/token',
126 | 			tokenExchangeCallback: (options) =>
127 | 				handleTokenExchangeCallback(
128 | 					options,
129 | 					env.CLOUDFLARE_CLIENT_ID,
130 | 					env.CLOUDFLARE_CLIENT_SECRET
131 | 				),
132 | 			// Cloudflare access token TTL
133 | 			accessTokenTTL: 3600,
134 | 			clientRegistrationEndpoint: '/register',
135 | 		}).fetch(req, env, ctx)
136 | 	},
137 | }
138 | 
```

--------------------------------------------------------------------------------
/apps/browser-rendering/src/browser.app.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import OAuthProvider from '@cloudflare/workers-oauth-provider'
  2 | import { McpAgent } from 'agents/mcp'
  3 | 
  4 | import { handleApiTokenMode, isApiTokenRequest } from '@repo/mcp-common/src/api-token-mode'
  5 | import {
  6 | 	createAuthHandlers,
  7 | 	handleTokenExchangeCallback,
  8 | } from '@repo/mcp-common/src/cloudflare-oauth-handler'
  9 | import { getUserDetails, UserDetails } from '@repo/mcp-common/src/durable-objects/user_details.do'
 10 | import { getEnv } from '@repo/mcp-common/src/env'
 11 | import { getProps } from '@repo/mcp-common/src/get-props'
 12 | import { RequiredScopes } from '@repo/mcp-common/src/scopes'
 13 | import { CloudflareMCPServer } from '@repo/mcp-common/src/server'
 14 | import { registerAccountTools } from '@repo/mcp-common/src/tools/account.tools'
 15 | 
 16 | import { MetricsTracker } from '../../../packages/mcp-observability/src'
 17 | import { registerBrowserTools } from './tools/browser.tools'
 18 | 
 19 | import type { AuthProps } from '@repo/mcp-common/src/cloudflare-oauth-handler'
 20 | import type { Env } from './browser.context'
 21 | 
 22 | const env = getEnv<Env>()
 23 | 
 24 | export { UserDetails }
 25 | 
 26 | const metrics = new MetricsTracker(env.MCP_METRICS, {
 27 | 	name: env.MCP_SERVER_NAME,
 28 | 	version: env.MCP_SERVER_VERSION,
 29 | })
 30 | 
 31 | // Context from the auth process, encrypted & stored in the auth token
 32 | // and provided to the DurableMCP as this.props
 33 | type Props = AuthProps
 34 | type State = { activeAccountId: string | null }
 35 | 
 36 | export class BrowserMCP extends McpAgent<Env, State, Props> {
 37 | 	_server: CloudflareMCPServer | undefined
 38 | 	set server(server: CloudflareMCPServer) {
 39 | 		this._server = server
 40 | 	}
 41 | 	get server(): CloudflareMCPServer {
 42 | 		if (!this._server) {
 43 | 			throw new Error('Tried to access server before it was initialized')
 44 | 		}
 45 | 
 46 | 		return this._server
 47 | 	}
 48 | 
 49 | 	constructor(ctx: DurableObjectState, env: Env) {
 50 | 		super(ctx, env)
 51 | 	}
 52 | 
 53 | 	async init() {
 54 | 		// TODO: Probably we'll want to track account tokens usage through an account identifier at some point
 55 | 		const props = getProps(this)
 56 | 		const userId = props.type === 'user_token' ? props.user.id : undefined
 57 | 
 58 | 		this.server = new CloudflareMCPServer({
 59 | 			userId,
 60 | 			wae: this.env.MCP_METRICS,
 61 | 			serverInfo: {
 62 | 				name: this.env.MCP_SERVER_NAME,
 63 | 				version: this.env.MCP_SERVER_VERSION,
 64 | 			},
 65 | 		})
 66 | 
 67 | 		registerAccountTools(this)
 68 | 
 69 | 		// Register Cloudflare Log Push tools
 70 | 		registerBrowserTools(this)
 71 | 	}
 72 | 
 73 | 	async getActiveAccountId() {
 74 | 		try {
 75 | 			const props = getProps(this)
 76 | 			// account tokens are scoped to one account
 77 | 			if (props.type === 'account_token') {
 78 | 				return props.account.id
 79 | 			}
 80 | 			// Get UserDetails Durable Object based off the userId and retrieve the activeAccountId from it
 81 | 			// we do this so we can persist activeAccountId across sessions
 82 | 			const userDetails = getUserDetails(env, props.user.id)
 83 | 			return await userDetails.getActiveAccountId()
 84 | 		} catch (e) {
 85 | 			this.server.recordError(e)
 86 | 			return null
 87 | 		}
 88 | 	}
 89 | 
 90 | 	async setActiveAccountId(accountId: string) {
 91 | 		try {
 92 | 			const props = getProps(this)
 93 | 			// account tokens are scoped to one account
 94 | 			if (props.type === 'account_token') {
 95 | 				return
 96 | 			}
 97 | 			const userDetails = getUserDetails(env, props.user.id)
 98 | 			await userDetails.setActiveAccountId(accountId)
 99 | 		} catch (e) {
100 | 			this.server.recordError(e)
101 | 		}
102 | 	}
103 | }
104 | 
105 | const BrowserScopes = {
106 | 	...RequiredScopes,
107 | 	'account:read': 'See your account info such as account details, analytics, and memberships.',
108 | 	'browser:write': 'Grants write level access to Browser Rendering.',
109 | } as const
110 | 
111 | export default {
112 | 	fetch: async (req: Request, env: Env, ctx: ExecutionContext) => {
113 | 		if (await isApiTokenRequest(req, env)) {
114 | 			return await handleApiTokenMode(BrowserMCP, req, env, ctx)
115 | 		}
116 | 
117 | 		return new OAuthProvider({
118 | 			apiHandlers: {
119 | 				'/mcp': BrowserMCP.serve('/mcp'),
120 | 				'/sse': BrowserMCP.serveSSE('/sse'),
121 | 			},
122 | 			// @ts-ignore
123 | 			defaultHandler: createAuthHandlers({ scopes: BrowserScopes, metrics }),
124 | 			authorizeEndpoint: '/oauth/authorize',
125 | 			tokenEndpoint: '/token',
126 | 			tokenExchangeCallback: (options) =>
127 | 				handleTokenExchangeCallback(
128 | 					options,
129 | 					env.CLOUDFLARE_CLIENT_ID,
130 | 					env.CLOUDFLARE_CLIENT_SECRET
131 | 				),
132 | 			// Cloudflare access token TTL
133 | 			accessTokenTTL: 3600,
134 | 			clientRegistrationEndpoint: '/register',
135 | 		}).fetch(req, env, ctx)
136 | 	},
137 | }
138 | 
```

--------------------------------------------------------------------------------
/apps/ai-gateway/src/ai-gateway.app.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import OAuthProvider from '@cloudflare/workers-oauth-provider'
  2 | import { McpAgent } from 'agents/mcp'
  3 | 
  4 | import { handleApiTokenMode, isApiTokenRequest } from '@repo/mcp-common/src/api-token-mode'
  5 | import {
  6 | 	createAuthHandlers,
  7 | 	handleTokenExchangeCallback,
  8 | } from '@repo/mcp-common/src/cloudflare-oauth-handler'
  9 | import { getUserDetails, UserDetails } from '@repo/mcp-common/src/durable-objects/user_details.do'
 10 | import { getEnv } from '@repo/mcp-common/src/env'
 11 | import { getProps } from '@repo/mcp-common/src/get-props'
 12 | import { RequiredScopes } from '@repo/mcp-common/src/scopes'
 13 | import { CloudflareMCPServer } from '@repo/mcp-common/src/server'
 14 | import { registerAccountTools } from '@repo/mcp-common/src/tools/account.tools'
 15 | 
 16 | import { MetricsTracker } from '../../../packages/mcp-observability/src'
 17 | import { registerAIGatewayTools } from './tools/ai-gateway.tools'
 18 | 
 19 | import type { AuthProps } from '@repo/mcp-common/src/cloudflare-oauth-handler'
 20 | import type { Env } from './ai-gateway.context'
 21 | 
 22 | const env = getEnv<Env>()
 23 | 
 24 | export { UserDetails }
 25 | 
 26 | const metrics = new MetricsTracker(env.MCP_METRICS, {
 27 | 	name: env.MCP_SERVER_NAME,
 28 | 	version: env.MCP_SERVER_VERSION,
 29 | })
 30 | 
 31 | // Context from the auth process, encrypted & stored in the auth token
 32 | // and provided to the DurableMCP as this.props
 33 | type Props = AuthProps
 34 | type State = { activeAccountId: string | null }
 35 | 
 36 | export class AIGatewayMCP extends McpAgent<Env, State, Props> {
 37 | 	_server: CloudflareMCPServer | undefined
 38 | 	set server(server: CloudflareMCPServer) {
 39 | 		this._server = server
 40 | 	}
 41 | 	get server(): CloudflareMCPServer {
 42 | 		if (!this._server) {
 43 | 			throw new Error('Tried to access server before it was initialized')
 44 | 		}
 45 | 
 46 | 		return this._server
 47 | 	}
 48 | 
 49 | 	constructor(ctx: DurableObjectState, env: Env) {
 50 | 		super(ctx, env)
 51 | 	}
 52 | 
 53 | 	async init() {
 54 | 		// TODO: Probably we'll want to track account tokens usage through an account identifier at some point
 55 | 		const props = getProps(this)
 56 | 		const userId = props.type === 'user_token' ? props.user.id : undefined
 57 | 
 58 | 		this.server = new CloudflareMCPServer({
 59 | 			userId,
 60 | 			wae: this.env.MCP_METRICS,
 61 | 			serverInfo: {
 62 | 				name: this.env.MCP_SERVER_NAME,
 63 | 				version: this.env.MCP_SERVER_VERSION,
 64 | 			},
 65 | 		})
 66 | 
 67 | 		registerAccountTools(this)
 68 | 
 69 | 		// Register Cloudflare Log Push tools
 70 | 		registerAIGatewayTools(this)
 71 | 	}
 72 | 
 73 | 	async getActiveAccountId() {
 74 | 		try {
 75 | 			const props = getProps(this)
 76 | 			// account tokens are scoped to one account
 77 | 			if (props.type === 'account_token') {
 78 | 				return props.account.id
 79 | 			}
 80 | 			// Get UserDetails Durable Object based off the userId and retrieve the activeAccountId from it
 81 | 			// we do this so we can persist activeAccountId across sessions
 82 | 			const userDetails = getUserDetails(env, props.user.id)
 83 | 			return await userDetails.getActiveAccountId()
 84 | 		} catch (e) {
 85 | 			this.server.recordError(e)
 86 | 			return null
 87 | 		}
 88 | 	}
 89 | 
 90 | 	async setActiveAccountId(accountId: string) {
 91 | 		try {
 92 | 			const props = getProps(this)
 93 | 			// account tokens are scoped to one account
 94 | 			if (props.type === 'account_token') {
 95 | 				return
 96 | 			}
 97 | 			const userDetails = getUserDetails(env, props.user.id)
 98 | 			await userDetails.setActiveAccountId(accountId)
 99 | 		} catch (e) {
100 | 			this.server.recordError(e)
101 | 		}
102 | 	}
103 | }
104 | 
105 | const AIGatewayScopes = {
106 | 	...RequiredScopes,
107 | 	'account:read': 'See your account info such as account details, analytics, and memberships.',
108 | 	'aig:read': 'Grants read level access to AI Gateway.',
109 | } as const
110 | 
111 | export default {
112 | 	fetch: async (req: Request, env: Env, ctx: ExecutionContext) => {
113 | 		if (await isApiTokenRequest(req, env)) {
114 | 			return await handleApiTokenMode(AIGatewayMCP, req, env, ctx)
115 | 		}
116 | 
117 | 		return new OAuthProvider({
118 | 			apiHandlers: {
119 | 				'/mcp': AIGatewayMCP.serve('/mcp'),
120 | 				'/sse': AIGatewayMCP.serveSSE('/sse'),
121 | 			},
122 | 			// @ts-ignore
123 | 			defaultHandler: createAuthHandlers({ scopes: AIGatewayScopes, metrics }),
124 | 			authorizeEndpoint: '/oauth/authorize',
125 | 			tokenEndpoint: '/token',
126 | 			tokenExchangeCallback: (options) =>
127 | 				handleTokenExchangeCallback(
128 | 					options,
129 | 					env.CLOUDFLARE_CLIENT_ID,
130 | 					env.CLOUDFLARE_CLIENT_SECRET
131 | 				),
132 | 			// Cloudflare access token TTL
133 | 			accessTokenTTL: 3600,
134 | 			clientRegistrationEndpoint: '/register',
135 | 		}).fetch(req, env, ctx)
136 | 	},
137 | }
138 | 
```

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

```typescript
  1 | import OAuthProvider from '@cloudflare/workers-oauth-provider'
  2 | import { McpAgent } from 'agents/mcp'
  3 | 
  4 | import { handleApiTokenMode, isApiTokenRequest } from '@repo/mcp-common/src/api-token-mode'
  5 | import {
  6 | 	createAuthHandlers,
  7 | 	handleTokenExchangeCallback,
  8 | } from '@repo/mcp-common/src/cloudflare-oauth-handler'
  9 | import { getUserDetails, UserDetails } from '@repo/mcp-common/src/durable-objects/user_details.do'
 10 | import { getEnv } from '@repo/mcp-common/src/env'
 11 | import { getProps } from '@repo/mcp-common/src/get-props'
 12 | import { RequiredScopes } from '@repo/mcp-common/src/scopes'
 13 | import { CloudflareMCPServer } from '@repo/mcp-common/src/server'
 14 | import { registerAccountTools } from '@repo/mcp-common/src/tools/account.tools'
 15 | 
 16 | import { MetricsTracker } from '../../../packages/mcp-observability/src'
 17 | import { registerAuditLogTools } from './tools/auditlogs.tools'
 18 | 
 19 | import type { AuthProps } from '@repo/mcp-common/src/cloudflare-oauth-handler'
 20 | import type { Env } from './auditlogs.context'
 21 | 
 22 | const env = getEnv<Env>()
 23 | 
 24 | export { UserDetails }
 25 | 
 26 | const metrics = new MetricsTracker(env.MCP_METRICS, {
 27 | 	name: env.MCP_SERVER_NAME,
 28 | 	version: env.MCP_SERVER_VERSION,
 29 | })
 30 | 
 31 | // Context from the auth process, encrypted & stored in the auth token
 32 | // and provided to the DurableMCP as this.props
 33 | type Props = AuthProps
 34 | 
 35 | export type State = { activeAccountId: string | null }
 36 | 
 37 | export class AuditlogMCP extends McpAgent<Env, State, Props> {
 38 | 	_server: CloudflareMCPServer | undefined
 39 | 	set server(server: CloudflareMCPServer) {
 40 | 		this._server = server
 41 | 	}
 42 | 	get server(): CloudflareMCPServer {
 43 | 		if (!this._server) {
 44 | 			throw new Error('Tried to access server before it was initialized')
 45 | 		}
 46 | 
 47 | 		return this._server
 48 | 	}
 49 | 
 50 | 	constructor(ctx: DurableObjectState, env: Env) {
 51 | 		super(ctx, env)
 52 | 	}
 53 | 
 54 | 	async init() {
 55 | 		// TODO: Probably we'll want to track account tokens usage through an account identifier at some point
 56 | 		const props = getProps(this)
 57 | 		const userId = props.type === 'user_token' ? props.user.id : undefined
 58 | 
 59 | 		this.server = new CloudflareMCPServer({
 60 | 			userId,
 61 | 			wae: this.env.MCP_METRICS,
 62 | 			serverInfo: {
 63 | 				name: this.env.MCP_SERVER_NAME,
 64 | 				version: this.env.MCP_SERVER_VERSION,
 65 | 			},
 66 | 		})
 67 | 		registerAccountTools(this)
 68 | 
 69 | 		// Register Cloudflare Audit Log tools
 70 | 		registerAuditLogTools(this)
 71 | 	}
 72 | 
 73 | 	async getActiveAccountId() {
 74 | 		try {
 75 | 			const props = getProps(this)
 76 | 			// account tokens are scoped to one account
 77 | 			if (props.type === 'account_token') {
 78 | 				return props.account.id
 79 | 			}
 80 | 			// Get UserDetails Durable Object based off the userId and retrieve the activeAccountId from it
 81 | 			// we do this so we can persist activeAccountId across sessions
 82 | 			const userDetails = getUserDetails(env, props.user.id)
 83 | 			return await userDetails.getActiveAccountId()
 84 | 		} catch (e) {
 85 | 			this.server.recordError(e)
 86 | 			return null
 87 | 		}
 88 | 	}
 89 | 
 90 | 	async setActiveAccountId(accountId: string) {
 91 | 		try {
 92 | 			const props = getProps(this)
 93 | 			// account tokens are scoped to one account
 94 | 			if (props.type === 'account_token') {
 95 | 				return
 96 | 			}
 97 | 			const userDetails = getUserDetails(env, props.user.id)
 98 | 			await userDetails.setActiveAccountId(accountId)
 99 | 		} catch (e) {
100 | 			this.server.recordError(e)
101 | 		}
102 | 	}
103 | }
104 | 
105 | const AuditlogScopes = {
106 | 	...RequiredScopes,
107 | 	'account:read': 'See your account info such as account details, analytics, and memberships.',
108 | 	'auditlogs:read': 'See your resource configuration changes.',
109 | } as const
110 | 
111 | export default {
112 | 	fetch: async (req: Request, env: Env, ctx: ExecutionContext) => {
113 | 		if (await isApiTokenRequest(req, env)) {
114 | 			return await handleApiTokenMode(AuditlogMCP, req, env, ctx)
115 | 		}
116 | 
117 | 		return new OAuthProvider({
118 | 			apiHandlers: {
119 | 				'/mcp': AuditlogMCP.serve('/mcp'),
120 | 				'/sse': AuditlogMCP.serveSSE('/sse'),
121 | 			},
122 | 			// @ts-ignore
123 | 			defaultHandler: createAuthHandlers({ scopes: AuditlogScopes, metrics }),
124 | 			authorizeEndpoint: '/oauth/authorize',
125 | 			tokenEndpoint: '/token',
126 | 			tokenExchangeCallback: (options) =>
127 | 				handleTokenExchangeCallback(
128 | 					options,
129 | 					env.CLOUDFLARE_CLIENT_ID,
130 | 					env.CLOUDFLARE_CLIENT_SECRET
131 | 				),
132 | 			// Cloudflare access token TTL
133 | 			accessTokenTTL: 3600,
134 | 			clientRegistrationEndpoint: '/register',
135 | 		}).fetch(req, env, ctx)
136 | 	},
137 | }
138 | 
```

--------------------------------------------------------------------------------
/packages/mcp-common/src/tools/docs-vectorize.tools.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { z } from 'zod'
  2 | 
  3 | import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
  4 | 
  5 | interface RequiredEnv {
  6 | 	AI: Ai
  7 | 	VECTORIZE: VectorizeIndex
  8 | }
  9 | 
 10 | // Always return 10 results for simplicity, don't make it configurable
 11 | const TOP_K = 10
 12 | 
 13 | /**
 14 |  * Registers the docs search tool with the MCP server
 15 |  * @param server The MCP server instance
 16 |  */
 17 | export function registerDocsTools(server: McpServer, env: RequiredEnv) {
 18 | 	server.tool(
 19 | 		'search_cloudflare_documentation',
 20 | 		`Search the Cloudflare documentation.
 21 | 
 22 | 		This tool should be used to answer any question about Cloudflare products or features, including:
 23 | 		- Workers, Pages, R2, Images, Stream, D1, Durable Objects, KV, Workflows, Hyperdrive, Queues
 24 | 		- AI Search, Workers AI, Vectorize, AI Gateway, Browser Rendering
 25 | 		- Zero Trust, Access, Tunnel, Gateway, Browser Isolation, WARP, DDOS, Magic Transit, Magic WAN
 26 | 		- CDN, Cache, DNS, Zaraz, Argo, Rulesets, Terraform, Account and Billing
 27 | 
 28 | 		Results are returned as semantically similar chunks to the query.
 29 | 		`,
 30 | 		{
 31 | 			query: z.string(),
 32 | 		},
 33 | 		{
 34 | 			title: 'Search Cloudflare docs',
 35 | 			annotations: {
 36 | 				readOnlyHint: true,
 37 | 			},
 38 | 		},
 39 | 		async ({ query }) => {
 40 | 			const results = await queryVectorize(env.AI, env.VECTORIZE, query, TOP_K)
 41 | 			const resultsAsXml = results
 42 | 				.map((result) => {
 43 | 					return `<result>
 44 | <url>${result.url}</url>
 45 | <title>${result.title}</title>
 46 | <text>
 47 | ${result.text}
 48 | </text>
 49 | </result>`
 50 | 				})
 51 | 				.join('\n')
 52 | 			return {
 53 | 				content: [{ type: 'text', text: resultsAsXml }],
 54 | 			}
 55 | 		}
 56 | 	)
 57 | 
 58 | 	// Note: this is a tool instead of a prompt because
 59 | 	// prompt support is much less common than tools.
 60 | 	server.tool(
 61 | 		'migrate_pages_to_workers_guide',
 62 | 		`ALWAYS read this guide before migrating Pages projects to Workers.`,
 63 | 		{},
 64 | 		{
 65 | 			title: 'Get Pages migration guide',
 66 | 			annotations: {
 67 | 				readOnlyHint: true,
 68 | 			},
 69 | 		},
 70 | 		async () => {
 71 | 			const res = await fetch(
 72 | 				'https://developers.cloudflare.com/workers/prompts/pages-to-workers.txt',
 73 | 				{
 74 | 					cf: { cacheEverything: true, cacheTtl: 3600 },
 75 | 				}
 76 | 			)
 77 | 
 78 | 			if (!res.ok) {
 79 | 				return {
 80 | 					content: [{ type: 'text', text: 'Error: Failed to fetch guide. Please try again.' }],
 81 | 				}
 82 | 			}
 83 | 
 84 | 			return {
 85 | 				content: [
 86 | 					{
 87 | 						type: 'text',
 88 | 						text: await res.text(),
 89 | 					},
 90 | 				],
 91 | 			}
 92 | 		}
 93 | 	)
 94 | }
 95 | 
 96 | async function queryVectorize(ai: Ai, vectorizeIndex: VectorizeIndex, query: string, topK: number) {
 97 | 	// Recommendation from: https://ai.google.dev/gemma/docs/embeddinggemma/model_card#prompt_instructions
 98 | 	const [queryEmbedding] = await getEmbeddings(ai, ['task: search result | query: ' + query])
 99 | 
100 | 	const { matches } = await vectorizeIndex.query(queryEmbedding, {
101 | 		topK,
102 | 		returnMetadata: 'all',
103 | 		returnValues: false,
104 | 	})
105 | 
106 | 	return matches.map((match, _i) => ({
107 | 		similarity: Math.min(match.score, 1),
108 | 		id: match.id,
109 | 		url: sourceToUrl(String(match.metadata?.filePath ?? '')),
110 | 		title: String(match.metadata?.title ?? ''),
111 | 		text: String(match.metadata?.text ?? ''),
112 | 	}))
113 | }
114 | 
115 | const TOP_DIR = 'src/content/docs'
116 | function sourceToUrl(path: string) {
117 | 	const prefix = `${TOP_DIR}/`
118 | 	return (
119 | 		'https://developers.cloudflare.com/' +
120 | 		(path.startsWith(prefix) ? path.slice(prefix.length) : path)
121 | 			.replace(/index\.mdx$/, '')
122 | 			.replace(/\.mdx$/, '')
123 | 	)
124 | }
125 | 
126 | async function getEmbeddings(ai: Ai, strings: string[]): Promise<number[][]> {
127 | 	const response = await doWithRetries(() =>
128 | 		// @ts-expect-error embeddinggemma not in types yet
129 | 		ai.run('@cf/google/embeddinggemma-300m', {
130 | 			text: strings,
131 | 		})
132 | 	)
133 | 
134 | 	// @ts-expect-error embeddinggemma not in types yet
135 | 	return response.data
136 | }
137 | 
138 | /**
139 |  * @template T
140 |  * @param {() => Promise<T>} action
141 |  */
142 | async function doWithRetries<T>(action: () => Promise<T>) {
143 | 	const NUM_RETRIES = 10
144 | 	const INIT_RETRY_MS = 50
145 | 	for (let i = 0; i <= NUM_RETRIES; i++) {
146 | 		try {
147 | 			return await action()
148 | 		} catch (e) {
149 | 			// TODO: distinguish between user errors (4xx) and system errors (5xx)
150 | 			console.error(e)
151 | 			if (i === NUM_RETRIES) {
152 | 				throw e
153 | 			}
154 | 			// Exponential backoff with full jitter
155 | 			await scheduler.wait(Math.random() * INIT_RETRY_MS * Math.pow(2, i))
156 | 		}
157 | 	}
158 | 	// Should never reach here – last loop iteration should return
159 | 	throw new Error('An unknown error occurred')
160 | }
161 | 
```

--------------------------------------------------------------------------------
/apps/dex-analysis/src/dex-analysis.app.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import OAuthProvider from '@cloudflare/workers-oauth-provider'
  2 | import { McpAgent } from 'agents/mcp'
  3 | 
  4 | import { handleApiTokenMode, isApiTokenRequest } from '@repo/mcp-common/src/api-token-mode'
  5 | import {
  6 | 	createAuthHandlers,
  7 | 	handleTokenExchangeCallback,
  8 | } from '@repo/mcp-common/src/cloudflare-oauth-handler'
  9 | import { getUserDetails, UserDetails } from '@repo/mcp-common/src/durable-objects/user_details.do'
 10 | import { getEnv } from '@repo/mcp-common/src/env'
 11 | import { getProps } from '@repo/mcp-common/src/get-props'
 12 | import { RequiredScopes } from '@repo/mcp-common/src/scopes'
 13 | import { CloudflareMCPServer } from '@repo/mcp-common/src/server'
 14 | import { registerAccountTools } from '@repo/mcp-common/src/tools/account.tools'
 15 | import { MetricsTracker } from '@repo/mcp-observability'
 16 | 
 17 | import { registerDEXTools } from './tools/dex-analysis.tools'
 18 | 
 19 | import type { AuthProps } from '@repo/mcp-common/src/cloudflare-oauth-handler'
 20 | import type { Env } from './dex-analysis.context'
 21 | 
 22 | export { UserDetails }
 23 | export { WarpDiagReader } from './warp_diag_reader'
 24 | 
 25 | const env = getEnv<Env>()
 26 | 
 27 | const metrics = new MetricsTracker(env.MCP_METRICS, {
 28 | 	name: env.MCP_SERVER_NAME,
 29 | 	version: env.MCP_SERVER_VERSION,
 30 | })
 31 | 
 32 | // Context from the auth process, encrypted & stored in the auth token
 33 | // and provided to the DurableMCP as this.props
 34 | type Props = AuthProps
 35 | 
 36 | type State = { activeAccountId: string | null }
 37 | 
 38 | export class CloudflareDEXMCP extends McpAgent<Env, State, Props> {
 39 | 	_server: CloudflareMCPServer | undefined
 40 | 	set server(server: CloudflareMCPServer) {
 41 | 		this._server = server
 42 | 	}
 43 | 
 44 | 	get server(): CloudflareMCPServer {
 45 | 		if (!this._server) {
 46 | 			throw new Error('Tried to access server before it was initialized')
 47 | 		}
 48 | 
 49 | 		return this._server
 50 | 	}
 51 | 
 52 | 	constructor(ctx: DurableObjectState, env: Env) {
 53 | 		super(ctx, env)
 54 | 	}
 55 | 
 56 | 	async init() {
 57 | 		// TODO: Probably we'll want to track account tokens usage through an account identifier at some point
 58 | 		const props = getProps(this)
 59 | 		const userId = props.type === 'user_token' ? props.user.id : undefined
 60 | 
 61 | 		this.server = new CloudflareMCPServer({
 62 | 			userId,
 63 | 			wae: this.env.MCP_METRICS,
 64 | 			serverInfo: {
 65 | 				name: this.env.MCP_SERVER_NAME,
 66 | 				version: this.env.MCP_SERVER_VERSION,
 67 | 			},
 68 | 		})
 69 | 
 70 | 		registerAccountTools(this)
 71 | 		registerDEXTools(this)
 72 | 	}
 73 | 
 74 | 	async getActiveAccountId() {
 75 | 		try {
 76 | 			const props = getProps(this)
 77 | 			// account tokens are scoped to one account
 78 | 			if (props.type === 'account_token') {
 79 | 				return props.account.id
 80 | 			}
 81 | 			// Get UserDetails Durable Object based off the userId and retrieve the activeAccountId from it
 82 | 			// we do this so we can persist activeAccountId across sessions
 83 | 			const userDetails = getUserDetails(env, props.user.id)
 84 | 			return await userDetails.getActiveAccountId()
 85 | 		} catch (e) {
 86 | 			this.server.recordError(e)
 87 | 			return null
 88 | 		}
 89 | 	}
 90 | 
 91 | 	async setActiveAccountId(accountId: string) {
 92 | 		try {
 93 | 			const props = getProps(this)
 94 | 			// account tokens are scoped to one account
 95 | 			if (props.type === 'account_token') {
 96 | 				return
 97 | 			}
 98 | 			const userDetails = getUserDetails(env, props.user.id)
 99 | 			await userDetails.setActiveAccountId(accountId)
100 | 		} catch (e) {
101 | 			this.server.recordError(e)
102 | 		}
103 | 	}
104 | }
105 | 
106 | const DexScopes = {
107 | 	...RequiredScopes,
108 | 	'account:read': 'See your account info such as account details, analytics, and memberships.',
109 | 	'dex:write':
110 | 		'Grants write level access to DEX resources like tests, fleet status, and remote captures.',
111 | } as const
112 | 
113 | export default {
114 | 	fetch: async (req: Request, env: Env, ctx: ExecutionContext) => {
115 | 		if (await isApiTokenRequest(req, env)) {
116 | 			return await handleApiTokenMode(CloudflareDEXMCP, req, env, ctx)
117 | 		}
118 | 
119 | 		return new OAuthProvider({
120 | 			apiHandlers: {
121 | 				'/mcp': CloudflareDEXMCP.serve('/mcp'),
122 | 				'/sse': CloudflareDEXMCP.serveSSE('/sse'),
123 | 			},
124 | 			// @ts-ignore
125 | 			defaultHandler: createAuthHandlers({ scopes: DexScopes, metrics }),
126 | 			authorizeEndpoint: '/oauth/authorize',
127 | 			tokenEndpoint: '/token',
128 | 			tokenExchangeCallback: (options) =>
129 | 				handleTokenExchangeCallback(
130 | 					options,
131 | 					env.CLOUDFLARE_CLIENT_ID,
132 | 					env.CLOUDFLARE_CLIENT_SECRET
133 | 				),
134 | 			// Cloudflare access token TTL
135 | 			accessTokenTTL: 3600,
136 | 			clientRegistrationEndpoint: '/register',
137 | 		}).fetch(req, env, ctx)
138 | 	},
139 | }
140 | 
```

--------------------------------------------------------------------------------
/packages/mcp-common/src/sentry.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { APIError } from 'cloudflare'
  2 | import { Toucan, zodErrorsIntegration } from 'toucan-js'
  3 | 
  4 | import { McpError } from './mcp-error'
  5 | 
  6 | import type { BaseTransportOptions, Client, ClientOptions, Event, EventHint } from '@sentry/types'
  7 | import type { Context, Next } from 'hono'
  8 | import type { Context as SentryContext } from 'toucan-js/dist/types'
  9 | import type { MCPEnvironment } from './config'
 10 | 
 11 | function is5xxError(status: number): boolean {
 12 | 	return status >= 500 && status <= 599
 13 | }
 14 | 
 15 | export class SentryClient {
 16 | 	private sentry: Toucan
 17 | 	constructor(sentry: Toucan) {
 18 | 		this.sentry = sentry
 19 | 	}
 20 | 
 21 | 	public recordError(e: unknown) {
 22 | 		if (this.sentry) {
 23 | 			// ignore errors from McpError and APIError (cloudflare) that have reportToSentry = false, or aren't 5xx errors
 24 | 			if (e instanceof McpError) {
 25 | 				if (e.reportToSentry === false) {
 26 | 					return
 27 | 				}
 28 | 			} else if (e instanceof APIError) {
 29 | 				if (!is5xxError(e.status)) {
 30 | 					return
 31 | 				}
 32 | 			}
 33 | 			this.sentry.captureException(e)
 34 | 		}
 35 | 	}
 36 | 
 37 | 	public setUser(userId: string) {
 38 | 		this.sentry.setUser({ ...this.sentry.getUser(), user_id: userId })
 39 | 	}
 40 | }
 41 | 
 42 | interface BaseBindings {
 43 | 	ENVIRONMENT: MCPEnvironment
 44 | 	GIT_HASH: string
 45 | 	SENTRY_DSN: string
 46 | 	SENTRY_ACCESS_CLIENT_ID: string
 47 | 	SENTRY_ACCESS_CLIENT_SECRET: string
 48 | }
 49 | 
 50 | export interface BaseHonoContext {
 51 | 	Bindings: BaseBindings
 52 | 	Variables: {
 53 | 		sentry?: SentryClient
 54 | 	}
 55 | }
 56 | 
 57 | export function initSentry<T extends BaseBindings>(
 58 | 	env: T,
 59 | 	ctx: SentryContext,
 60 | 	req?: Request<unknown, CfProperties>
 61 | ): SentryClient {
 62 | 	const sentry = new Toucan({
 63 | 		dsn: env.SENTRY_DSN,
 64 | 		request: req,
 65 | 		environment: env.ENVIRONMENT,
 66 | 		context: ctx,
 67 | 		release: env.GIT_HASH,
 68 | 		requestDataOptions: {
 69 | 			allowedHeaders: [
 70 | 				'user-agent',
 71 | 				'cf-challenge',
 72 | 				'accept-encoding',
 73 | 				'accept-language',
 74 | 				'cf-ray',
 75 | 				'content-length',
 76 | 				'content-type',
 77 | 				'host',
 78 | 			],
 79 | 			// Allow ONLY the “scope” param in order to avoid recording jwt, code, state and any other callback params
 80 | 			allowedSearchParams: /^scope$/,
 81 | 		},
 82 | 		integrations: [
 83 | 			zodErrorsIntegration({ saveAttachments: true }),
 84 | 			{
 85 | 				name: 'mcp-api-errors',
 86 | 				processEvent(
 87 | 					event: Event,
 88 | 					_hint: EventHint,
 89 | 					_client: Client<ClientOptions<BaseTransportOptions>>
 90 | 				): Event {
 91 | 					const processedEvent = applyMcpErrorsToEvent(event)
 92 | 					return processedEvent
 93 | 				},
 94 | 			},
 95 | 		],
 96 | 		transportOptions: {
 97 | 			headers: {
 98 | 				'CF-Access-Client-ID': env.SENTRY_ACCESS_CLIENT_ID,
 99 | 				'CF-Access-Client-Secret': env.SENTRY_ACCESS_CLIENT_SECRET,
100 | 			},
101 | 		},
102 | 	})
103 | 	return new SentryClient(sentry)
104 | }
105 | 
106 | export function initSentryWithUser<T extends BaseBindings>(
107 | 	env: T,
108 | 	ctx: SentryContext,
109 | 	userId: string,
110 | 	req?: Request<unknown, CfProperties>
111 | ): SentryClient {
112 | 	const sentryClient = initSentry(env, ctx, req)
113 | 	sentryClient.setUser(userId)
114 | 	return sentryClient
115 | }
116 | 
117 | export async function useSentry<T extends BaseHonoContext>(
118 | 	c: Context<T>,
119 | 	next: Next
120 | ): Promise<void> {
121 | 	c.set('sentry', initSentry(c.env, c.executionCtx, c.req.raw))
122 | 	await next()
123 | }
124 | 
125 | export function setSentryRequestHeaders(sentry: Toucan, req: Request<unknown, CfProperties>) {
126 | 	const colo: string = req.cf && typeof req.cf.colo === 'string' ? req.cf.colo : 'UNKNOWN'
127 | 	sentry.setTag('colo', colo)
128 | 
129 | 	const ip_address = req.headers.get('cf-connecting-ip') ?? ''
130 | 	const userAgent = req.headers.get('user-agent') ?? ''
131 | 	sentry.setUser({
132 | 		...sentry.getUser(),
133 | 		ip_address,
134 | 		userAgent,
135 | 		colo,
136 | 	})
137 | }
138 | 
139 | function applyMcpErrorsToEvent(event: Event): Event {
140 | 	if (event.exception === undefined || event.exception.values === undefined) {
141 | 		return event
142 | 	}
143 | 
144 | 	if (event.exception instanceof McpError) {
145 | 		try {
146 | 			return {
147 | 				...event,
148 | 				extra: {
149 | 					...event.extra,
150 | 					statusCode: event.exception.code,
151 | 					internalMessage: event.exception.internalMessage,
152 | 				},
153 | 			}
154 | 		} catch (e) {
155 | 			// Hopefully we never throw errors here, but record it
156 | 			// with the event just in case.
157 | 			return {
158 | 				...event,
159 | 				extra: {
160 | 					...event.extra,
161 | 					'McpError sentry integration parse error': {
162 | 						message: `an exception was thrown while processing McpError within applyMcpErrorsToEvent()`,
163 | 						error: e instanceof Error ? `${e.name}: ${e.cause}\n${e.stack}` : 'unknown',
164 | 					},
165 | 				},
166 | 			}
167 | 		}
168 | 	}
169 | 
170 | 	return event
171 | }
172 | 
```

--------------------------------------------------------------------------------
/apps/logpush/src/logpush.app.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import OAuthProvider from '@cloudflare/workers-oauth-provider'
  2 | import { McpAgent } from 'agents/mcp'
  3 | 
  4 | import { handleApiTokenMode, isApiTokenRequest } from '@repo/mcp-common/src/api-token-mode'
  5 | import {
  6 | 	createAuthHandlers,
  7 | 	handleTokenExchangeCallback,
  8 | } from '@repo/mcp-common/src/cloudflare-oauth-handler'
  9 | import { getUserDetails, UserDetails } from '@repo/mcp-common/src/durable-objects/user_details.do'
 10 | import { getEnv } from '@repo/mcp-common/src/env'
 11 | import { getProps } from '@repo/mcp-common/src/get-props'
 12 | import { RequiredScopes } from '@repo/mcp-common/src/scopes'
 13 | import { CloudflareMCPServer } from '@repo/mcp-common/src/server'
 14 | import { registerAccountTools } from '@repo/mcp-common/src/tools/account.tools'
 15 | 
 16 | import { MetricsTracker } from '../../../packages/mcp-observability/src'
 17 | import { registerLogsTools } from './tools/logpush.tools'
 18 | 
 19 | import type { AuthProps } from '@repo/mcp-common/src/cloudflare-oauth-handler'
 20 | import type { Env } from './logpush.context'
 21 | 
 22 | const env = getEnv<Env>()
 23 | 
 24 | export { UserDetails }
 25 | 
 26 | const metrics = new MetricsTracker(env.MCP_METRICS, {
 27 | 	name: env.MCP_SERVER_NAME,
 28 | 	version: env.MCP_SERVER_VERSION,
 29 | })
 30 | 
 31 | // Context from the auth process, encrypted & stored in the auth token
 32 | // and provided to the DurableMCP as this.props
 33 | type Props = AuthProps
 34 | type State = { activeAccountId: string | null }
 35 | 
 36 | export class LogsMCP extends McpAgent<Env, State, Props> {
 37 | 	_server: CloudflareMCPServer | undefined
 38 | 	set server(server: CloudflareMCPServer) {
 39 | 		this._server = server
 40 | 	}
 41 | 	get server(): CloudflareMCPServer {
 42 | 		if (!this._server) {
 43 | 			throw new Error('Tried to access server before it was initialized')
 44 | 		}
 45 | 
 46 | 		return this._server
 47 | 	}
 48 | 
 49 | 	constructor(ctx: DurableObjectState, env: Env) {
 50 | 		super(ctx, env)
 51 | 	}
 52 | 
 53 | 	async init() {
 54 | 		// TODO: Probably we'll want to track account tokens usage through an account identifier at some point
 55 | 		const props = getProps(this)
 56 | 		const userId = props.type === 'user_token' ? props.user.id : undefined
 57 | 
 58 | 		this.server = new CloudflareMCPServer({
 59 | 			userId,
 60 | 			wae: this.env.MCP_METRICS,
 61 | 			serverInfo: {
 62 | 				name: this.env.MCP_SERVER_NAME,
 63 | 				version: this.env.MCP_SERVER_VERSION,
 64 | 			},
 65 | 		})
 66 | 
 67 | 		registerAccountTools(this)
 68 | 
 69 | 		// Register Cloudflare Log Push tools
 70 | 		registerLogsTools(this)
 71 | 	}
 72 | 
 73 | 	async getActiveAccountId() {
 74 | 		try {
 75 | 			const props = getProps(this)
 76 | 			// account tokens are scoped to one account
 77 | 			if (props.type === 'account_token') {
 78 | 				return props.account.id
 79 | 			}
 80 | 			// Get UserDetails Durable Object based off the userId and retrieve the activeAccountId from it
 81 | 			// we do this so we can persist activeAccountId across sessions
 82 | 			const userDetails = getUserDetails(env, props.user.id)
 83 | 			return await userDetails.getActiveAccountId()
 84 | 		} catch (e) {
 85 | 			this.server.recordError(e)
 86 | 			return null
 87 | 		}
 88 | 	}
 89 | 
 90 | 	async setActiveAccountId(accountId: string) {
 91 | 		try {
 92 | 			const props = getProps(this)
 93 | 			// account tokens are scoped to one account
 94 | 			if (props.type === 'account_token') {
 95 | 				return
 96 | 			}
 97 | 			const userDetails = getUserDetails(env, props.user.id)
 98 | 			await userDetails.setActiveAccountId(accountId)
 99 | 		} catch (e) {
100 | 			this.server.recordError(e)
101 | 		}
102 | 	}
103 | }
104 | 
105 | const LogPushScopes = {
106 | 	...RequiredScopes,
107 | 	'account:read': 'See your account info such as account details, analytics, and memberships.',
108 | 	'logpush:write':
109 | 		'Grants read and write access to Logpull and Logpush, and read access to Instant Logs. Note that all Logpush API operations require Logs: Write permission because Logpush jobs contain sensitive information.',
110 | } as const
111 | 
112 | export default {
113 | 	fetch: async (req: Request, env: Env, ctx: ExecutionContext) => {
114 | 		if (await isApiTokenRequest(req, env)) {
115 | 			return await handleApiTokenMode(LogsMCP, req, env, ctx)
116 | 		}
117 | 
118 | 		return new OAuthProvider({
119 | 			apiHandlers: {
120 | 				'/mcp': LogsMCP.serve('/mcp'),
121 | 				'/sse': LogsMCP.serveSSE('/sse'),
122 | 			},
123 | 			// @ts-ignore
124 | 			defaultHandler: createAuthHandlers({ scopes: LogPushScopes, metrics }),
125 | 			authorizeEndpoint: '/oauth/authorize',
126 | 			tokenEndpoint: '/token',
127 | 			tokenExchangeCallback: (options) =>
128 | 				handleTokenExchangeCallback(
129 | 					options,
130 | 					env.CLOUDFLARE_CLIENT_ID,
131 | 					env.CLOUDFLARE_CLIENT_SECRET
132 | 				),
133 | 			// Cloudflare access token TTL
134 | 			accessTokenTTL: 3600,
135 | 			clientRegistrationEndpoint: '/register',
136 | 		}).fetch(req, env, ctx)
137 | 	},
138 | }
139 | 
```

--------------------------------------------------------------------------------
/apps/workers-bindings/evals/kv_namespaces.eval.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { expect } from 'vitest'
  2 | import { describeEval } from 'vitest-evals'
  3 | 
  4 | import { runTask } from '@repo/eval-tools/src/runTask'
  5 | import { checkFactuality } from '@repo/eval-tools/src/scorers'
  6 | import { eachModel } from '@repo/eval-tools/src/test-models'
  7 | import { KV_NAMESPACE_TOOLS } from '@repo/mcp-common/src/tools/kv_namespace.tools'
  8 | 
  9 | import { initializeClient } from './utils' // Assuming utils.ts will exist here
 10 | 
 11 | eachModel('$modelName', ({ model }) => {
 12 | 	describeEval('Create Cloudflare KV Namespace', {
 13 | 		data: async () => [
 14 | 			{
 15 | 				input: 'Create a new Cloudflare KV Namespace called "my-test-namespace".',
 16 | 				expected: `The ${KV_NAMESPACE_TOOLS.kv_namespace_create} tool should be called to create a new kv namespace.`,
 17 | 			},
 18 | 		],
 19 | 		task: async (input: string) => {
 20 | 			const client = await initializeClient(/* Pass necessary mocks/config */)
 21 | 			const { promptOutput, toolCalls } = await runTask(client, model, input)
 22 | 			const toolCall = toolCalls.find(
 23 | 				(call) => call.toolName === KV_NAMESPACE_TOOLS.kv_namespace_create
 24 | 			)
 25 | 			expect(toolCall, 'Tool kv_namespace_create was not called').toBeDefined()
 26 | 
 27 | 			return promptOutput
 28 | 		},
 29 | 		scorers: [checkFactuality],
 30 | 		threshold: 1,
 31 | 		timeout: 60000, // 60 seconds
 32 | 	})
 33 | 	describeEval('List Cloudflare KV Namespaces', {
 34 | 		data: async () => [
 35 | 			{
 36 | 				input: 'List all my Cloudflare KV Namespaces.',
 37 | 				expected: `The ${KV_NAMESPACE_TOOLS.kv_namespaces_list} tool should be called to retrieve the list of kv namespaces. There should be at least one kv namespace in the list.`,
 38 | 			},
 39 | 		],
 40 | 		task: async (input: string) => {
 41 | 			const client = await initializeClient(/* Pass necessary mocks/config */)
 42 | 			const { promptOutput, toolCalls } = await runTask(client, model, input)
 43 | 			const toolCall = toolCalls.find(
 44 | 				(call) => call.toolName === KV_NAMESPACE_TOOLS.kv_namespaces_list
 45 | 			)
 46 | 			expect(toolCall, 'Tool kv_namespaces_list was not called').toBeDefined()
 47 | 
 48 | 			return promptOutput
 49 | 		},
 50 | 		scorers: [checkFactuality],
 51 | 		threshold: 1,
 52 | 		timeout: 60000, // 60 seconds
 53 | 	})
 54 | 	describeEval('Rename Cloudflare KV Namespace', {
 55 | 		data: async () => [
 56 | 			{
 57 | 				input: 'Rename my Cloudflare KV Namespace with ID 1234 to "my-new-test-namespace".',
 58 | 				expected: `The ${KV_NAMESPACE_TOOLS.kv_namespace_update} tool should be called to rename the kv namespace.`,
 59 | 			},
 60 | 		],
 61 | 		task: async (input: string) => {
 62 | 			const client = await initializeClient(/* Pass necessary mocks/config */)
 63 | 			const { promptOutput, toolCalls } = await runTask(client, model, input)
 64 | 			const toolCall = toolCalls.find(
 65 | 				(call) => call.toolName === KV_NAMESPACE_TOOLS.kv_namespace_update
 66 | 			)
 67 | 			expect(toolCall, 'Tool kv_namespace_update was not called').toBeDefined()
 68 | 
 69 | 			return promptOutput
 70 | 		},
 71 | 		scorers: [checkFactuality],
 72 | 		threshold: 1,
 73 | 		timeout: 60000, // 60 seconds
 74 | 	})
 75 | 	describeEval('Get Cloudflare KV Namespace Details', {
 76 | 		data: async () => [
 77 | 			{
 78 | 				input: 'Get details of my Cloudflare KV Namespace with ID 1234.',
 79 | 				expected: `The ${KV_NAMESPACE_TOOLS.kv_namespace_get} tool should be called to retrieve the details of the kv namespace.`,
 80 | 			},
 81 | 		],
 82 | 		task: async (input: string) => {
 83 | 			const client = await initializeClient(/* Pass necessary mocks/config */)
 84 | 			const { promptOutput, toolCalls } = await runTask(client, model, input)
 85 | 			const toolCall = toolCalls.find(
 86 | 				(call) => call.toolName === KV_NAMESPACE_TOOLS.kv_namespace_get
 87 | 			)
 88 | 			expect(toolCall, 'Tool kv_namespace_get was not called').toBeDefined()
 89 | 
 90 | 			return promptOutput
 91 | 		},
 92 | 		scorers: [checkFactuality],
 93 | 		threshold: 1,
 94 | 		timeout: 60000, // 60 seconds
 95 | 	})
 96 | 	describeEval('Delete Cloudflare KV Namespace', {
 97 | 		data: async () => [
 98 | 			{
 99 | 				input: 'Delete the kv namespace with ID 1234.',
100 | 				expected: `The ${KV_NAMESPACE_TOOLS.kv_namespace_delete} tool should be called to delete the kv namespace.`,
101 | 			},
102 | 		],
103 | 		task: async (input: string) => {
104 | 			const client = await initializeClient(/* Pass necessary mocks/config */)
105 | 			const { promptOutput, toolCalls } = await runTask(client, model, input)
106 | 			const toolCall = toolCalls.find(
107 | 				(call) => call.toolName === KV_NAMESPACE_TOOLS.kv_namespace_delete
108 | 			)
109 | 			expect(toolCall, 'Tool kv_namespace_delete was not called').toBeDefined()
110 | 
111 | 			return promptOutput
112 | 		},
113 | 		scorers: [checkFactuality],
114 | 		threshold: 1,
115 | 		timeout: 60000, // 60 seconds
116 | 	})
117 | })
118 | 
```

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

```typescript
  1 | import OAuthProvider from '@cloudflare/workers-oauth-provider'
  2 | import { McpAgent } from 'agents/mcp'
  3 | 
  4 | import { handleApiTokenMode, isApiTokenRequest } from '@repo/mcp-common/src/api-token-mode'
  5 | import {
  6 | 	createAuthHandlers,
  7 | 	handleTokenExchangeCallback,
  8 | } from '@repo/mcp-common/src/cloudflare-oauth-handler'
  9 | import { getUserDetails, UserDetails } from '@repo/mcp-common/src/durable-objects/user_details.do'
 10 | import { getEnv } from '@repo/mcp-common/src/env'
 11 | import { getProps } from '@repo/mcp-common/src/get-props'
 12 | import { RequiredScopes } from '@repo/mcp-common/src/scopes'
 13 | import { CloudflareMCPServer } from '@repo/mcp-common/src/server'
 14 | import { registerAccountTools } from '@repo/mcp-common/src/tools/account.tools'
 15 | import { MetricsTracker } from '@repo/mcp-observability'
 16 | 
 17 | import { BASE_INSTRUCTIONS } from './radar.context'
 18 | import { registerRadarTools } from './tools/radar.tools'
 19 | import { registerUrlScannerTools } from './tools/url-scanner.tools'
 20 | 
 21 | import type { AuthProps } from '@repo/mcp-common/src/cloudflare-oauth-handler'
 22 | import type { Env } from './radar.context'
 23 | 
 24 | const env = getEnv<Env>()
 25 | 
 26 | export { UserDetails }
 27 | 
 28 | const metrics = new MetricsTracker(env.MCP_METRICS, {
 29 | 	name: env.MCP_SERVER_NAME,
 30 | 	version: env.MCP_SERVER_VERSION,
 31 | })
 32 | 
 33 | // Context from the auth process, encrypted & stored in the auth token
 34 | // and provided to the DurableMCP as this.props
 35 | type Props = AuthProps
 36 | type State = { activeAccountId: string | null }
 37 | 
 38 | export class RadarMCP extends McpAgent<Env, State, Props> {
 39 | 	_server: CloudflareMCPServer | undefined
 40 | 	set server(server: CloudflareMCPServer) {
 41 | 		this._server = server
 42 | 	}
 43 | 	get server(): CloudflareMCPServer {
 44 | 		if (!this._server) {
 45 | 			throw new Error('Tried to access server before it was initialized')
 46 | 		}
 47 | 
 48 | 		return this._server
 49 | 	}
 50 | 
 51 | 	constructor(ctx: DurableObjectState, env: Env) {
 52 | 		super(ctx, env)
 53 | 	}
 54 | 
 55 | 	async init() {
 56 | 		// TODO: Probably we'll want to track account tokens usage through an account identifier at some point
 57 | 		const props = getProps(this)
 58 | 		const userId = props.type === 'user_token' ? props.user.id : undefined
 59 | 
 60 | 		this.server = new CloudflareMCPServer({
 61 | 			userId,
 62 | 			wae: this.env.MCP_METRICS,
 63 | 			serverInfo: {
 64 | 				name: this.env.MCP_SERVER_NAME,
 65 | 				version: this.env.MCP_SERVER_VERSION,
 66 | 			},
 67 | 			options: { instructions: BASE_INSTRUCTIONS },
 68 | 		})
 69 | 
 70 | 		registerAccountTools(this)
 71 | 		registerRadarTools(this)
 72 | 		registerUrlScannerTools(this)
 73 | 	}
 74 | 
 75 | 	async getActiveAccountId() {
 76 | 		try {
 77 | 			const props = getProps(this)
 78 | 			// account tokens are scoped to one account
 79 | 			if (props.type === 'account_token') {
 80 | 				return props.account.id
 81 | 			}
 82 | 			// Get UserDetails Durable Object based off the userId and retrieve the activeAccountId from it
 83 | 			// we do this so we can persist activeAccountId across sessions
 84 | 			const userDetails = getUserDetails(env, props.user.id)
 85 | 			return await userDetails.getActiveAccountId()
 86 | 		} catch (e) {
 87 | 			this.server.recordError(e)
 88 | 			return null
 89 | 		}
 90 | 	}
 91 | 
 92 | 	async setActiveAccountId(accountId: string) {
 93 | 		try {
 94 | 			const props = getProps(this)
 95 | 			// account tokens are scoped to one account
 96 | 			if (props.type === 'account_token') {
 97 | 				return
 98 | 			}
 99 | 			const userDetails = getUserDetails(env, props.user.id)
100 | 			await userDetails.setActiveAccountId(accountId)
101 | 		} catch (e) {
102 | 			this.server.recordError(e)
103 | 		}
104 | 	}
105 | }
106 | 
107 | const RadarScopes = {
108 | 	...RequiredScopes,
109 | 	'account:read': 'See your account info such as account details, analytics, and memberships.',
110 | 	'radar:read': 'Grants access to read Cloudflare Radar data.',
111 | 	'url_scanner:write': 'Grants write level access to URL Scanner',
112 | } as const
113 | 
114 | export default {
115 | 	fetch: async (req: Request, env: Env, ctx: ExecutionContext) => {
116 | 		if (await isApiTokenRequest(req, env)) {
117 | 			return await handleApiTokenMode(RadarMCP, req, env, ctx)
118 | 		}
119 | 
120 | 		return new OAuthProvider({
121 | 			apiHandlers: {
122 | 				'/mcp': RadarMCP.serve('/mcp'),
123 | 				'/sse': RadarMCP.serveSSE('/sse'),
124 | 			},
125 | 			// @ts-ignore
126 | 			defaultHandler: createAuthHandlers({ scopes: RadarScopes, metrics }),
127 | 			authorizeEndpoint: '/oauth/authorize',
128 | 			tokenEndpoint: '/token',
129 | 			tokenExchangeCallback: (options) =>
130 | 				handleTokenExchangeCallback(
131 | 					options,
132 | 					env.CLOUDFLARE_CLIENT_ID,
133 | 					env.CLOUDFLARE_CLIENT_SECRET
134 | 				),
135 | 			// Cloudflare access token TTL
136 | 			accessTokenTTL: 3600,
137 | 			clientRegistrationEndpoint: '/register',
138 | 		}).fetch(req, env, ctx)
139 | 	},
140 | }
141 | 
```

--------------------------------------------------------------------------------
/packages/mcp-observability/src/analytics-engine.ts:
--------------------------------------------------------------------------------

```typescript
  1 | export type MetricsBindings = {
  2 | 	MCP_METRICS: AnalyticsEngineDataset
  3 | }
  4 | 
  5 | /**
  6 |  * Generic metrics event utilities
  7 |  * @description Wrapper for RA binding
  8 |  */
  9 | export class MetricsTracker {
 10 | 	constructor(
 11 | 		private wae: AnalyticsEngineDataset,
 12 | 		private mcpServerInfo: {
 13 | 			name: string
 14 | 			version: string
 15 | 		}
 16 | 	) {}
 17 | 
 18 | 	logEvent(event: MetricsEvent): void {
 19 | 		try {
 20 | 			event.serverInfo = this.mcpServerInfo
 21 | 			let dataPoint = event.toDataPoint()
 22 | 			this.wae.writeDataPoint(dataPoint)
 23 | 		} catch (e) {
 24 | 			console.error(`Failed to log metrics event, ${e}`)
 25 | 		}
 26 | 	}
 27 | }
 28 | 
 29 | /**
 30 |  * MetricsEvent
 31 |  *
 32 |  * Each event type is stored with a different indexId and has an associated class which
 33 |  * maps a more ergonomic event object to a ReadyAnalyticsEvent
 34 |  */
 35 | export abstract class MetricsEvent {
 36 | 	public _serverInfo: { name: string; version: string } | undefined
 37 | 	set serverInfo(serverInfo: { name: string; version: string }) {
 38 | 		this._serverInfo = serverInfo
 39 | 	}
 40 | 
 41 | 	get serverInfo(): { name: string; version: string } {
 42 | 		if (!this._serverInfo) {
 43 | 			throw new Error('Server info not set')
 44 | 		}
 45 | 		return this._serverInfo
 46 | 	}
 47 | 
 48 | 	/**
 49 | 	 * Output a valid AnalyticsEngineDataPoint. Use `mapBlobs` and `mapDoubles` to write well defined
 50 | 	 * analytics engine datapoints. The first and second blob entries are reserved for the MCP server name and
 51 | 	 * MCP server version.
 52 | 	 */
 53 | 	abstract toDataPoint(): AnalyticsEngineDataPoint
 54 | 
 55 | 	mapBlobs(blobs: Blobs): Array<string | null> {
 56 | 		if (blobs.blob1 || blobs.blob2) {
 57 | 			throw new MetricsError(
 58 | 				'Failed to map blobs, blob1 and blob2 are reserved for MCP server info'
 59 | 			)
 60 | 		}
 61 | 		// add placeholder blobs, filled in by the MetricsTracker later
 62 | 		blobs.blob1 = this.serverInfo.name
 63 | 		blobs.blob2 = this.serverInfo.version
 64 | 		const blobsArray = new Array(Object.keys(blobs).length)
 65 | 		for (const [key, value] of Object.entries(blobs)) {
 66 | 			const match = key.match(/^blob(\d+)$/)
 67 | 			if (match === null || match.length < 2) {
 68 | 				// we should never hit this because of the typedefinitions above,
 69 | 				// but this error is for safety
 70 | 				throw new MetricsError('Failed to map blobs, invalid key')
 71 | 			}
 72 | 			const index = parseInt(match[1], 10)
 73 | 			if (isNaN(index)) {
 74 | 				// we should never hit this because of the typedefinitions above,
 75 | 				// but this esrror is for safety
 76 | 				throw new MetricsError('Failed to map blobs, invalid index')
 77 | 			}
 78 | 			if (index - 1 >= blobsArray.length) {
 79 | 				throw new MetricsError('Failed to map blobs, missing blob')
 80 | 			}
 81 | 			blobsArray[index - 1] = value
 82 | 		}
 83 | 		return blobsArray
 84 | 	}
 85 | 
 86 | 	mapDoubles(doubles: Doubles): number[] {
 87 | 		const doublesArray = new Array(Object.keys(doubles).length)
 88 | 		for (const [key, value] of Object.entries(doubles)) {
 89 | 			const match = key.match(/^double(\d+)$/)
 90 | 			if (match === null || match.length < 2) {
 91 | 				// we should never hit this because of the typedefinitions above,
 92 | 				// but this error is for safety
 93 | 				throw new MetricsError(': Failed to map doubles, invalid key')
 94 | 			}
 95 | 			const index = parseInt(match[1], 10)
 96 | 			if (isNaN(index)) {
 97 | 				// we should never hit this because of the typedefinitions above,
 98 | 				// but this error is for safety
 99 | 				throw new MetricsError('Failed to map doubles, invalid index')
100 | 			}
101 | 			if (index - 1 >= doublesArray.length) {
102 | 				throw new MetricsError('Failed to map doubles, missing blob')
103 | 			}
104 | 			doublesArray[index - 1] = value
105 | 		}
106 | 		return doublesArray
107 | 	}
108 | }
109 | 
110 | export enum MetricsEventIndexIds {
111 | 	AUTH_USER = 'auth_user',
112 | 	SESSION_START = 'session_start',
113 | 	TOOL_CALL = 'tool_call',
114 | 	CONTAINER_MANAGER = 'container_manager',
115 | }
116 | 
117 | /**
118 |  * Utility functions to map named blob/double objects to an array
119 |  * We do this so we don't have to annotate `blob1`, `blob2`, etc in comments.
120 |  *
121 |  * I prefer this to just writing it in an array because it'll be easier to reference
122 |  * later when we are writing ready analytics queries.
123 |  *
124 |  * IMO named tuples and raw arrays aren't as ergonomic to work with, but they require less of this code below
125 |  */
126 | type Range1To20 =
127 | 	| 1
128 | 	| 2
129 | 	| 3
130 | 	| 4
131 | 	| 5
132 | 	| 6
133 | 	| 7
134 | 	| 8
135 | 	| 9
136 | 	| 10
137 | 	| 11
138 | 	| 12
139 | 	| 13
140 | 	| 14
141 | 	| 15
142 | 	| 16
143 | 	| 17
144 | 	| 18
145 | 	| 19
146 | 	| 20
147 | 
148 | // blob1 and blob2 are reserved for server name and version
149 | type Blobs = {
150 | 	[key in `blob${Range1To20}`]?: string | null
151 | }
152 | 
153 | type Doubles = {
154 | 	[key in `double${Range1To20}`]?: number
155 | }
156 | 
157 | export class MetricsError extends Error {
158 | 	constructor(message: string) {
159 | 		super(message)
160 | 		this.name = 'MetricsError'
161 | 	}
162 | }
163 | 
```

--------------------------------------------------------------------------------
/apps/dns-analytics/src/dns-analytics.app.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import OAuthProvider from '@cloudflare/workers-oauth-provider'
  2 | import { McpAgent } from 'agents/mcp'
  3 | 
  4 | import { handleApiTokenMode, isApiTokenRequest } from '@repo/mcp-common/src/api-token-mode'
  5 | import {
  6 | 	createAuthHandlers,
  7 | 	handleTokenExchangeCallback,
  8 | } from '@repo/mcp-common/src/cloudflare-oauth-handler'
  9 | import { getUserDetails, UserDetails } from '@repo/mcp-common/src/durable-objects/user_details.do'
 10 | import { getEnv } from '@repo/mcp-common/src/env'
 11 | import { getProps } from '@repo/mcp-common/src/get-props'
 12 | import { RequiredScopes } from '@repo/mcp-common/src/scopes'
 13 | import { CloudflareMCPServer } from '@repo/mcp-common/src/server'
 14 | import { registerAccountTools } from '@repo/mcp-common/src/tools/account.tools'
 15 | import { registerZoneTools } from '@repo/mcp-common/src/tools/zone.tools'
 16 | 
 17 | import { MetricsTracker } from '../../../packages/mcp-observability/src'
 18 | import { registerAnalyticTools } from './tools/dex-analytics.tools'
 19 | 
 20 | import type { AuthProps } from '@repo/mcp-common/src/cloudflare-oauth-handler'
 21 | import type { Env } from './dns-analytics.context'
 22 | 
 23 | export { UserDetails }
 24 | 
 25 | const env = getEnv<Env>()
 26 | 
 27 | const metrics = new MetricsTracker(env.MCP_METRICS, {
 28 | 	name: env.MCP_SERVER_NAME,
 29 | 	version: env.MCP_SERVER_VERSION,
 30 | })
 31 | 
 32 | // Context from the auth process, encrypted & stored in the auth token
 33 | // and provided to the DurableMCP as this.props
 34 | export type Props = AuthProps
 35 | 
 36 | export type State = { activeAccountId: string | null }
 37 | 
 38 | export class DNSAnalyticsMCP extends McpAgent<Env, State, Props> {
 39 | 	_server: CloudflareMCPServer | undefined
 40 | 	set server(server: CloudflareMCPServer) {
 41 | 		this._server = server
 42 | 	}
 43 | 
 44 | 	get server(): CloudflareMCPServer {
 45 | 		if (!this._server) {
 46 | 			throw new Error('Tried to access server before it was initialized')
 47 | 		}
 48 | 
 49 | 		return this._server
 50 | 	}
 51 | 
 52 | 	constructor(ctx: DurableObjectState, env: Env) {
 53 | 		super(ctx, env)
 54 | 	}
 55 | 
 56 | 	async init() {
 57 | 		// TODO: Probably we'll want to track account tokens usage through an account identifier at some point
 58 | 		const props = getProps(this)
 59 | 		const userId = props.type === 'user_token' ? props.user.id : undefined
 60 | 
 61 | 		this.server = new CloudflareMCPServer({
 62 | 			userId,
 63 | 			wae: this.env.MCP_METRICS,
 64 | 			serverInfo: {
 65 | 				name: this.env.MCP_SERVER_NAME,
 66 | 				version: this.env.MCP_SERVER_VERSION,
 67 | 			},
 68 | 		})
 69 | 
 70 | 		registerAccountTools(this)
 71 | 
 72 | 		// Register Cloudflare DNS Analytics tools
 73 | 		registerAnalyticTools(this)
 74 | 
 75 | 		registerZoneTools(this)
 76 | 	}
 77 | 
 78 | 	async getActiveAccountId() {
 79 | 		try {
 80 | 			const props = getProps(this)
 81 | 			// account tokens are scoped to one account
 82 | 			if (props.type === 'account_token') {
 83 | 				return props.account.id
 84 | 			}
 85 | 			// Get UserDetails Durable Object based off the userId and retrieve the activeAccountId from it
 86 | 			// we do this so we can persist activeAccountId across sessions
 87 | 			const userDetails = getUserDetails(env, props.user.id)
 88 | 			return await userDetails.getActiveAccountId()
 89 | 		} catch (e) {
 90 | 			this.server.recordError(e)
 91 | 			return null
 92 | 		}
 93 | 	}
 94 | 
 95 | 	async setActiveAccountId(accountId: string) {
 96 | 		try {
 97 | 			const props = getProps(this)
 98 | 			// account tokens are scoped to one account
 99 | 			if (props.type === 'account_token') {
100 | 				return
101 | 			}
102 | 			const userDetails = getUserDetails(env, props.user.id)
103 | 			await userDetails.setActiveAccountId(accountId)
104 | 		} catch (e) {
105 | 			this.server.recordError(e)
106 | 		}
107 | 	}
108 | }
109 | 
110 | const AnalyticsScopes = {
111 | 	...RequiredScopes,
112 | 	'account:read': 'See your account info such as account details, analytics, and memberships.',
113 | 	'zone:read': 'See your zones',
114 | 	'dns_settings:read': 'See your DNS settings',
115 | 	'dns_analytics:read': 'See your DNS analytics',
116 | } as const
117 | 
118 | export default {
119 | 	fetch: async (req: Request, env: Env, ctx: ExecutionContext) => {
120 | 		if (await isApiTokenRequest(req, env)) {
121 | 			return await handleApiTokenMode(DNSAnalyticsMCP, req, env, ctx)
122 | 		}
123 | 
124 | 		return new OAuthProvider({
125 | 			apiHandlers: {
126 | 				'/mcp': DNSAnalyticsMCP.serve('/mcp'),
127 | 				'/sse': DNSAnalyticsMCP.serveSSE('/sse'),
128 | 			},
129 | 			// @ts-ignore
130 | 			defaultHandler: createAuthHandlers({ scopes: AnalyticsScopes, metrics }),
131 | 			authorizeEndpoint: '/oauth/authorize',
132 | 			tokenEndpoint: '/token',
133 | 			tokenExchangeCallback: (options) =>
134 | 				handleTokenExchangeCallback(
135 | 					options,
136 | 					env.CLOUDFLARE_CLIENT_ID,
137 | 					env.CLOUDFLARE_CLIENT_SECRET
138 | 				),
139 | 			// Cloudflare access token TTL
140 | 			accessTokenTTL: 3600,
141 | 			clientRegistrationEndpoint: '/register',
142 | 		}).fetch(req, env, ctx)
143 | 	},
144 | }
145 | 
```

--------------------------------------------------------------------------------
/apps/sandbox-container/container/sandbox.container.app.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { exec } from 'node:child_process'
  2 | import * as fs from 'node:fs/promises'
  3 | import path from 'node:path'
  4 | import { serve } from '@hono/node-server'
  5 | import { zValidator } from '@hono/zod-validator'
  6 | import { Hono } from 'hono'
  7 | import { streamText } from 'hono/streaming'
  8 | import mime from 'mime'
  9 | 
 10 | import { ExecParams, FileWrite } from '../shared/schema.ts'
 11 | import {
 12 | 	DIRECTORY_CONTENT_TYPE,
 13 | 	get_file_name_from_path,
 14 | 	get_mime_type,
 15 | 	list_files_in_directory,
 16 | } from './fileUtils.ts'
 17 | 
 18 | import type { FileList } from '../shared/schema.ts'
 19 | 
 20 | process.chdir('workdir')
 21 | 
 22 | const app = new Hono()
 23 | 
 24 | app.get('/ping', (c) => c.text('pong!'))
 25 | 
 26 | /**
 27 |  * GET /files/ls
 28 |  *
 29 |  * Gets all files in a directory
 30 |  */
 31 | app.get('/files/ls', async (c) => {
 32 | 	const directoriesToRead = ['.']
 33 | 	const files: FileList = { resources: [] }
 34 | 
 35 | 	while (directoriesToRead.length > 0) {
 36 | 		const curr = directoriesToRead.pop()
 37 | 		if (!curr) {
 38 | 			throw new Error('Popped empty stack, error while listing directories')
 39 | 		}
 40 | 		const fullPath = path.join(process.cwd(), curr)
 41 | 		const dir = await fs.readdir(fullPath, { withFileTypes: true })
 42 | 		for (const dirent of dir) {
 43 | 			const relPath = path.relative(process.cwd(), `${fullPath}/${dirent.name}`)
 44 | 			if (dirent.isDirectory()) {
 45 | 				directoriesToRead.push(dirent.name)
 46 | 				files.resources.push({
 47 | 					uri: `file:///${relPath}`,
 48 | 					name: dirent.name,
 49 | 					mimeType: 'inode/directory',
 50 | 				})
 51 | 			} else {
 52 | 				const mimeType = mime.getType(dirent.name)
 53 | 				files.resources.push({
 54 | 					uri: `file:///${relPath}`,
 55 | 					name: dirent.name,
 56 | 					mimeType: mimeType ?? undefined,
 57 | 				})
 58 | 			}
 59 | 		}
 60 | 	}
 61 | 
 62 | 	return c.json(files)
 63 | })
 64 | 
 65 | /**
 66 |  * GET /files/contents/{filepath}
 67 |  *
 68 |  * Get the contents of a file or directory
 69 |  */
 70 | app.get('/files/contents/*', async (c) => {
 71 | 	const reqPath = await get_file_name_from_path(c.req.path)
 72 | 	try {
 73 | 		const mimeType = await get_mime_type(reqPath)
 74 | 		const headers = mimeType ? { 'Content-Type': mimeType } : undefined
 75 | 		const contents = await fs.readFile(path.join(process.cwd(), reqPath))
 76 | 		return c.newResponse(contents, 200, headers)
 77 | 	} catch (e: any) {
 78 | 		if (e.code) {
 79 | 			if (e.code === 'EISDIR') {
 80 | 				const files = await list_files_in_directory(reqPath)
 81 | 				return c.newResponse(files.join('\n'), 200, {
 82 | 					'Content-Type': DIRECTORY_CONTENT_TYPE,
 83 | 				})
 84 | 			}
 85 | 			if (e.code === 'ENOENT') {
 86 | 				return c.notFound()
 87 | 			}
 88 | 		}
 89 | 
 90 | 		throw e
 91 | 	}
 92 | })
 93 | 
 94 | /**
 95 |  * POST /files/contents
 96 |  *
 97 |  * Create or update file contents
 98 |  */
 99 | app.post('/files/contents', zValidator('json', FileWrite), async (c) => {
100 | 	const file = c.req.valid('json')
101 | 	const reqPath = await get_file_name_from_path(file.path)
102 | 
103 | 	try {
104 | 		await fs.writeFile(reqPath, file.text)
105 | 		return c.newResponse(null, 200)
106 | 	} catch (e) {
107 | 		return c.newResponse(`Error: ${e}`, 400)
108 | 	}
109 | })
110 | 
111 | /**
112 |  * DELETE /files/contents/{filepath}
113 |  *
114 |  * Delete a file or directory
115 |  */
116 | app.delete('/files/contents/*', async (c) => {
117 | 	const reqPath = await get_file_name_from_path(c.req.path)
118 | 
119 | 	try {
120 | 		await fs.rm(path.join(process.cwd(), reqPath), { recursive: true })
121 | 		return c.newResponse('ok', 200)
122 | 	} catch (e: any) {
123 | 		if (e.code) {
124 | 			if (e.code === 'ENOENT') {
125 | 				return c.notFound()
126 | 			}
127 | 		}
128 | 
129 | 		throw e
130 | 	}
131 | })
132 | 
133 | /**
134 |  * POST /exec
135 |  *
136 |  * Execute a command in a shell
137 |  */
138 | app.post('/exec', zValidator('json', ExecParams), (c) => {
139 | 	const execParams = c.req.valid('json')
140 | 	const proc = exec(execParams.args)
141 | 	return streamText(c, async (stream) => {
142 | 		return new Promise((resolve, reject) => {
143 | 			if (proc.stdout) {
144 | 				// Stream data from stdout
145 | 				proc.stdout.on('data', async (data) => {
146 | 					await stream.write(data.toString())
147 | 				})
148 | 			} else {
149 | 				void stream.write('WARNING: no stdout stream for process')
150 | 			}
151 | 
152 | 			if (execParams.streamStderr) {
153 | 				if (proc.stderr) {
154 | 					proc.stderr.on('data', async (data) => {
155 | 						await stream.write(data.toString())
156 | 					})
157 | 				} else {
158 | 					void stream.write('WARNING: no stderr stream for process')
159 | 				}
160 | 			}
161 | 
162 | 			// Handle process exit
163 | 			proc.on('exit', async (code) => {
164 | 				await stream.write(`Process exited with code: ${code}`)
165 | 				if (code === 0) {
166 | 					await stream.close()
167 | 					resolve()
168 | 				} else {
169 | 					console.error(`Process exited with code ${code}`)
170 | 					reject(new Error(`Process failed with code ${code}`))
171 | 				}
172 | 			})
173 | 
174 | 			proc.on('error', (err) => {
175 | 				console.error('Error with process: ', err)
176 | 				reject(err)
177 | 			})
178 | 		})
179 | 	})
180 | })
181 | 
182 | serve({
183 | 	fetch: app.fetch,
184 | 	port: 8080,
185 | })
186 | 
```

--------------------------------------------------------------------------------
/apps/graphql/src/graphql.app.ts:
--------------------------------------------------------------------------------

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

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

```typescript
  1 | import { V4PagePaginationArray } from 'cloudflare/src/pagination.js'
  2 | import { z } from 'zod'
  3 | 
  4 | import { getCloudflareClient } from '@repo/mcp-common/src/cloudflare-api'
  5 | import { getProps } from '@repo/mcp-common/src/get-props'
  6 | 
  7 | import { pageParam, perPageParam } from '../types'
  8 | 
  9 | import type { AutoRAGMCP } from '../autorag.app'
 10 | 
 11 | export function registerAutoRAGTools(agent: AutoRAGMCP) {
 12 | 	agent.server.tool(
 13 | 		'list_rags',
 14 | 		'List AutoRAGs (vector stores)',
 15 | 		{
 16 | 			page: pageParam,
 17 | 			per_page: perPageParam,
 18 | 		},
 19 | 		async (params) => {
 20 | 			const accountId = await agent.getActiveAccountId()
 21 | 			if (!accountId) {
 22 | 				return {
 23 | 					content: [
 24 | 						{
 25 | 							type: 'text',
 26 | 							text: 'No currently active accountId. Try listing your accounts (accounts_list) and then setting an active account (set_active_account)',
 27 | 						},
 28 | 					],
 29 | 				}
 30 | 			}
 31 | 			try {
 32 | 				const props = getProps(agent)
 33 | 				const client = getCloudflareClient(props.accessToken)
 34 | 				const r = (await client.getAPIList(
 35 | 					`/accounts/${accountId}/autorag/rags`,
 36 | 					// @ts-ignore
 37 | 					V4PagePaginationArray,
 38 | 					{ query: { page: params.page, per_page: params.per_page } }
 39 | 				)) as unknown as {
 40 | 					result: Array<{ id: string; source: string; paused: boolean }>
 41 | 					result_info: { total_count: number }
 42 | 				}
 43 | 
 44 | 				return {
 45 | 					content: [
 46 | 						{
 47 | 							type: 'text',
 48 | 							text: JSON.stringify({
 49 | 								autorags: r.result.map((obj) => {
 50 | 									return {
 51 | 										id: obj.id,
 52 | 										source: obj.source,
 53 | 										paused: obj.paused,
 54 | 									}
 55 | 								}),
 56 | 								total_count: r.result_info.total_count,
 57 | 							}),
 58 | 						},
 59 | 					],
 60 | 				}
 61 | 			} catch (error) {
 62 | 				return {
 63 | 					content: [
 64 | 						{
 65 | 							type: 'text',
 66 | 							text: `Error listing rags: ${error instanceof Error && error.message}`,
 67 | 						},
 68 | 					],
 69 | 				}
 70 | 			}
 71 | 		}
 72 | 	)
 73 | 
 74 | 	agent.server.tool(
 75 | 		'search',
 76 | 		'Search Documents using AutoRAG (vector store)',
 77 | 		{
 78 | 			rag_id: z.string().describe('ID of the AutoRAG to search'),
 79 | 			query: z.string().describe('Query to search for. Can be a URL, a title, or a snippet.'),
 80 | 		},
 81 | 		async (params) => {
 82 | 			try {
 83 | 				const accountId = await agent.getActiveAccountId()
 84 | 				if (!accountId) {
 85 | 					return {
 86 | 						content: [
 87 | 							{
 88 | 								type: 'text',
 89 | 								text: 'No currently active accountId. Try listing your accounts (accounts_list) and then setting an active account (set_active_account)',
 90 | 							},
 91 | 						],
 92 | 					}
 93 | 				}
 94 | 
 95 | 				const props = getProps(agent)
 96 | 				const client = getCloudflareClient(props.accessToken)
 97 | 				const r = (await client.post(
 98 | 					`/accounts/${accountId}/autorag/rags/${params.rag_id}/search`,
 99 | 					{
100 | 						body: {
101 | 							query: params.query,
102 | 							max_num_results: 5,
103 | 						},
104 | 					}
105 | 				)) as { result: { data: Array<{ filename: string; content: Array<{ text: string }> }> } }
106 | 
107 | 				const chunks = r.result.data
108 | 					.map((item) => {
109 | 						const data = item.content
110 | 							.map((content) => {
111 | 								return content.text
112 | 							})
113 | 							.join('\n\n')
114 | 
115 | 						return `<file name="${item.filename}">${data}</file>`
116 | 					})
117 | 					.join('\n\n')
118 | 
119 | 				return {
120 | 					content: [
121 | 						{
122 | 							type: 'text',
123 | 							text: chunks,
124 | 						},
125 | 					],
126 | 				}
127 | 			} catch (error) {
128 | 				return {
129 | 					content: [
130 | 						{
131 | 							type: 'text',
132 | 							text: `Error searching rag: ${error instanceof Error && error.message}`,
133 | 						},
134 | 					],
135 | 				}
136 | 			}
137 | 		}
138 | 	)
139 | 
140 | 	agent.server.tool(
141 | 		'ai_search',
142 | 		'AI Search Documents using AutoRAG (vector store)',
143 | 		{
144 | 			rag_id: z.string().describe('ID of the AutoRAG to search'),
145 | 			query: z.string().describe('Query to search for. Can be a URL, a title, or a snippet.'),
146 | 		},
147 | 		async (params) => {
148 | 			try {
149 | 				const accountId = await agent.getActiveAccountId()
150 | 				if (!accountId) {
151 | 					return {
152 | 						content: [
153 | 							{
154 | 								type: 'text',
155 | 								text: 'No currently active accountId. Try listing your accounts (accounts_list) and then setting an active account (set_active_account)',
156 | 							},
157 | 						],
158 | 					}
159 | 				}
160 | 
161 | 				const props = getProps(agent)
162 | 				const client = getCloudflareClient(props.accessToken)
163 | 				const r = (await client.post(
164 | 					`/accounts/${accountId}/autorag/rags/${params.rag_id}/ai-search`,
165 | 					{
166 | 						body: {
167 | 							query: params.query,
168 | 							max_num_results: 10, // Limit can be bigger here, since llm is only getting the end response and not individual chunks
169 | 						},
170 | 					}
171 | 				)) as { result: { response: string } }
172 | 
173 | 				return {
174 | 					content: [
175 | 						{
176 | 							type: 'text',
177 | 							text: r.result.response,
178 | 						},
179 | 					],
180 | 				}
181 | 			} catch (error) {
182 | 				return {
183 | 					content: [
184 | 						{
185 | 							type: 'text',
186 | 							text: `Error searching rag: ${error instanceof Error && error.message}`,
187 | 						},
188 | 					],
189 | 				}
190 | 			}
191 | 		}
192 | 	)
193 | }
194 | 
```

--------------------------------------------------------------------------------
/packages/mcp-common/src/cloudflare-auth.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { z } from 'zod'
  2 | 
  3 | import { McpError } from './mcp-error'
  4 | 
  5 | import type { AuthRequest } from '@cloudflare/workers-oauth-provider'
  6 | 
  7 | // Constants
  8 | const PKCE_CHARSET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~'
  9 | const RECOMMENDED_CODE_VERIFIER_LENGTH = 96
 10 | function base64urlEncode(value: string): string {
 11 | 	let base64 = btoa(value)
 12 | 	base64 = base64.replace(/\+/g, '-')
 13 | 	base64 = base64.replace(/\//g, '_')
 14 | 	base64 = base64.replace(/=/g, '')
 15 | 	return base64
 16 | }
 17 | 
 18 | interface PKCECodes {
 19 | 	codeChallenge: string
 20 | 	codeVerifier: string
 21 | }
 22 | export async function generatePKCECodes(): Promise<PKCECodes> {
 23 | 	const output = new Uint32Array(RECOMMENDED_CODE_VERIFIER_LENGTH)
 24 | 	crypto.getRandomValues(output)
 25 | 	const codeVerifier = base64urlEncode(
 26 | 		Array.from(output)
 27 | 			.map((num: number) => PKCE_CHARSET[num % PKCE_CHARSET.length])
 28 | 			.join('')
 29 | 	)
 30 | 	const buffer = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(codeVerifier))
 31 | 	const hash = new Uint8Array(buffer)
 32 | 	let binary = ''
 33 | 	const hashLength = hash.byteLength
 34 | 	for (let i = 0; i < hashLength; i++) {
 35 | 		binary += String.fromCharCode(hash[i])
 36 | 	}
 37 | 	const codeChallenge = base64urlEncode(binary) //btoa(binary);
 38 | 	return { codeChallenge, codeVerifier }
 39 | }
 40 | 
 41 | function generateAuthUrl({
 42 | 	client_id,
 43 | 	redirect_uri,
 44 | 	state,
 45 | 	code_challenge,
 46 | 	scopes,
 47 | }: {
 48 | 	client_id: string
 49 | 	redirect_uri: string
 50 | 	code_challenge: string
 51 | 	state: string
 52 | 	scopes: Record<string, string>
 53 | }) {
 54 | 	const params = new URLSearchParams({
 55 | 		response_type: 'code',
 56 | 		client_id,
 57 | 		redirect_uri,
 58 | 		state,
 59 | 		code_challenge,
 60 | 		code_challenge_method: 'S256',
 61 | 		scope: Object.keys(scopes).join(' '),
 62 | 	})
 63 | 
 64 | 	const upstream = new URL(`https://dash.cloudflare.com/oauth2/auth?${params.toString()}`)
 65 | 	return upstream.href
 66 | }
 67 | 
 68 | /**
 69 |  * Constructs an authorization URL for Cloudflare.
 70 |  *
 71 |  * @param {Object} options
 72 |  * @param {string} options.client_id - The client ID of the application.
 73 |  * @param {string} options.redirect_uri - The redirect URI of the application.
 74 |  * @param {string} [options.state] - The state parameter.
 75 |  *
 76 |  * @returns {string} The authorization URL.
 77 |  */
 78 | export async function getAuthorizationURL({
 79 | 	client_id,
 80 | 	redirect_uri,
 81 | 	state,
 82 | 	scopes,
 83 | 	codeChallenge,
 84 | }: {
 85 | 	client_id: string
 86 | 	redirect_uri: string
 87 | 	state: AuthRequest
 88 | 	scopes: Record<string, string>
 89 | 	codeChallenge: string
 90 | }): Promise<{ authUrl: string }> {
 91 | 	return {
 92 | 		authUrl: generateAuthUrl({
 93 | 			client_id,
 94 | 			redirect_uri,
 95 | 			state: btoa(JSON.stringify(state)),
 96 | 			code_challenge: codeChallenge,
 97 | 			scopes,
 98 | 		}),
 99 | 	}
100 | }
101 | 
102 | type AuthorizationToken = z.infer<typeof AuthorizationToken>
103 | const AuthorizationToken = z.object({
104 | 	access_token: z.string(),
105 | 	expires_in: z.number(),
106 | 	refresh_token: z.string(),
107 | 	scope: z.string(),
108 | 	token_type: z.string(),
109 | })
110 | /**
111 |  * Fetches an authorization token from Cloudflare.
112 |  *
113 |  * @param {Object} options
114 |  * @param {string} options.client_id - The client ID of the application.
115 |  * @param {string} options.client_secret - The client secret of the application.
116 |  * @param {string} options.code - The authorization code.
117 |  * @param {string} options.redirect_uri - The redirect URI of the application.
118 |  *
119 |  * @returns {Promise<[string, null] | [null, Response]>} A promise that resolves to an array containing the access token or an error response.
120 |  */
121 | export async function getAuthToken({
122 | 	client_id,
123 | 	client_secret,
124 | 	redirect_uri,
125 | 	code_verifier,
126 | 	code,
127 | }: {
128 | 	client_id: string
129 | 	client_secret: string
130 | 	redirect_uri: string
131 | 	code_verifier: string
132 | 	code: string
133 | }): Promise<AuthorizationToken> {
134 | 	if (!code) {
135 | 		throw new McpError('Missing code', 400)
136 | 	}
137 | 
138 | 	const params = new URLSearchParams({
139 | 		grant_type: 'authorization_code',
140 | 		client_id,
141 | 		redirect_uri,
142 | 		code,
143 | 		code_verifier,
144 | 	}).toString()
145 | 	const resp = await fetch('https://dash.cloudflare.com/oauth2/token', {
146 | 		method: 'POST',
147 | 		headers: {
148 | 			Authorization: `Basic ${btoa(`${client_id}:${client_secret}`)}`,
149 | 			'Content-Type': 'application/x-www-form-urlencoded',
150 | 		},
151 | 		body: params,
152 | 	})
153 | 
154 | 	if (!resp.ok) {
155 | 		console.log(await resp.text())
156 | 		throw new McpError('Failed to get OAuth token', 500, { reportToSentry: true })
157 | 	}
158 | 
159 | 	return AuthorizationToken.parse(await resp.json())
160 | }
161 | 
162 | export async function refreshAuthToken({
163 | 	client_id,
164 | 	client_secret,
165 | 	refresh_token,
166 | }: {
167 | 	client_id: string
168 | 	client_secret: string
169 | 	refresh_token: string
170 | }): Promise<AuthorizationToken> {
171 | 	const params = new URLSearchParams({
172 | 		grant_type: 'refresh_token',
173 | 		client_id,
174 | 		refresh_token,
175 | 	})
176 | 
177 | 	const resp = await fetch('https://dash.cloudflare.com/oauth2/token', {
178 | 		method: 'POST',
179 | 		body: params.toString(),
180 | 		headers: {
181 | 			Authorization: `Basic ${btoa(`${client_id}:${client_secret}`)}`,
182 | 			'Content-Type': 'application/x-www-form-urlencoded',
183 | 		},
184 | 	})
185 | 	if (!resp.ok) {
186 | 		console.log(await resp.text())
187 | 		throw new McpError('Failed to get OAuth token', 500, { reportToSentry: true })
188 | 	}
189 | 
190 | 	return AuthorizationToken.parse(await resp.json())
191 | }
192 | 
```

--------------------------------------------------------------------------------
/apps/sandbox-container/server/userContainer.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { DurableObject } from 'cloudflare:workers'
  2 | 
  3 | import { OPEN_CONTAINER_PORT } from '../shared/consts'
  4 | import { MAX_CONTAINERS, proxyFetch, startAndWaitForPort } from './containerHelpers'
  5 | import { getContainerManager } from './containerManager'
  6 | import { fileToBase64 } from './utils'
  7 | 
  8 | import type { ExecParams, FileList, FileWrite } from '../shared/schema'
  9 | import type { Env } from './sandbox.server.context'
 10 | 
 11 | export class UserContainer extends DurableObject<Env> {
 12 | 	constructor(
 13 | 		public ctx: DurableObjectState,
 14 | 		public env: Env
 15 | 	) {
 16 | 		console.log('creating user container DO')
 17 | 		super(ctx, env)
 18 | 	}
 19 | 
 20 | 	async destroyContainer(): Promise<void> {
 21 | 		await this.ctx.container?.destroy()
 22 | 	}
 23 | 
 24 | 	async killContainer(): Promise<void> {
 25 | 		console.log('Reaping container')
 26 | 		const containerManager = getContainerManager(this.env)
 27 | 		const active = await containerManager.listActive()
 28 | 		if (this.ctx.id.toString() in active) {
 29 | 			console.log('killing container')
 30 | 			await this.destroyContainer()
 31 | 			await containerManager.killContainer(this.ctx.id.toString())
 32 | 		}
 33 | 	}
 34 | 
 35 | 	async container_initialize(): Promise<string> {
 36 | 		// kill container
 37 | 		await this.killContainer()
 38 | 
 39 | 		// try to cleanup cleanup old containers
 40 | 		const containerManager = getContainerManager(this.env)
 41 | 
 42 | 		// if more than half of our containers are being used, let's try reaping
 43 | 		if ((await containerManager.listActive()).length >= MAX_CONTAINERS / 2) {
 44 | 			await containerManager.tryKillOldContainers()
 45 | 			if ((await containerManager.listActive()).length >= MAX_CONTAINERS) {
 46 | 				throw new Error(
 47 | 					`Unable to reap enough containers. There are ${MAX_CONTAINERS} active container sandboxes, please wait`
 48 | 				)
 49 | 			}
 50 | 		}
 51 | 
 52 | 		// start container
 53 | 		let startedContainer = false
 54 | 		await this.ctx.blockConcurrencyWhile(async () => {
 55 | 			startedContainer = await startAndWaitForPort(
 56 | 				this.env.ENVIRONMENT,
 57 | 				this.ctx.container,
 58 | 				OPEN_CONTAINER_PORT
 59 | 			)
 60 | 		})
 61 | 		if (!startedContainer) {
 62 | 			throw new Error('Failed to start container')
 63 | 		}
 64 | 
 65 | 		// track and manage lifecycle
 66 | 		await containerManager.trackContainer(this.ctx.id.toString())
 67 | 
 68 | 		return `Created new container`
 69 | 	}
 70 | 
 71 | 	async container_ping(): Promise<string> {
 72 | 		const res = await proxyFetch(
 73 | 			this.env.ENVIRONMENT,
 74 | 			this.ctx.container,
 75 | 			new Request(`http://host:${OPEN_CONTAINER_PORT}/ping`),
 76 | 			OPEN_CONTAINER_PORT
 77 | 		)
 78 | 		if (!res || !res.ok) {
 79 | 			throw new Error(`Request to container failed: ${await res.text()}`)
 80 | 		}
 81 | 		return await res.text()
 82 | 	}
 83 | 
 84 | 	async container_exec(params: ExecParams): Promise<string> {
 85 | 		const res = await proxyFetch(
 86 | 			this.env.ENVIRONMENT,
 87 | 			this.ctx.container,
 88 | 			new Request(`http://host:${OPEN_CONTAINER_PORT}/exec`, {
 89 | 				method: 'POST',
 90 | 				body: JSON.stringify(params),
 91 | 				headers: {
 92 | 					'content-type': 'application/json',
 93 | 				},
 94 | 			}),
 95 | 			OPEN_CONTAINER_PORT
 96 | 		)
 97 | 		if (!res || !res.ok) {
 98 | 			throw new Error(`Request to container failed: ${await res.text()}`)
 99 | 		}
100 | 		const txt = await res.text()
101 | 		return txt
102 | 	}
103 | 
104 | 	async container_ls(): Promise<FileList> {
105 | 		const res = await proxyFetch(
106 | 			this.env.ENVIRONMENT,
107 | 			this.ctx.container,
108 | 			new Request(`http://host:${OPEN_CONTAINER_PORT}/files/ls`),
109 | 			OPEN_CONTAINER_PORT
110 | 		)
111 | 		if (!res || !res.ok) {
112 | 			throw new Error(`Request to container failed: ${await res.text()}`)
113 | 		}
114 | 		const json = (await res.json()) as FileList
115 | 		return json
116 | 	}
117 | 
118 | 	async container_file_delete(filePath: string): Promise<boolean> {
119 | 		const res = await proxyFetch(
120 | 			this.env.ENVIRONMENT,
121 | 			this.ctx.container,
122 | 			new Request(`http://host:${OPEN_CONTAINER_PORT}/files/contents/${filePath}`, {
123 | 				method: 'DELETE',
124 | 			}),
125 | 			OPEN_CONTAINER_PORT
126 | 		)
127 | 		return res.ok
128 | 	}
129 | 	async container_file_read(
130 | 		filePath: string
131 | 	): Promise<
132 | 		| { type: 'text'; textOutput: string; mimeType: string | undefined }
133 | 		| { type: 'base64'; base64Output: string; mimeType: string | undefined }
134 | 	> {
135 | 		const res = await proxyFetch(
136 | 			this.env.ENVIRONMENT,
137 | 			this.ctx.container,
138 | 			new Request(`http://host:${OPEN_CONTAINER_PORT}/files/contents/${filePath}`),
139 | 			OPEN_CONTAINER_PORT
140 | 		)
141 | 		if (!res || !res.ok) {
142 | 			throw new Error(`Request to container failed: ${await res.text()}`)
143 | 		}
144 | 
145 | 		const mimeType = res.headers.get('Content-Type') ?? undefined
146 | 		const blob = await res.blob()
147 | 
148 | 		if (mimeType && mimeType.startsWith('text')) {
149 | 			return {
150 | 				type: 'text',
151 | 				textOutput: await blob.text(),
152 | 				mimeType,
153 | 			}
154 | 		} else {
155 | 			return {
156 | 				type: 'base64',
157 | 				base64Output: await fileToBase64(blob),
158 | 				mimeType,
159 | 			}
160 | 		}
161 | 	}
162 | 
163 | 	async container_file_write(file: FileWrite): Promise<string> {
164 | 		const res = await proxyFetch(
165 | 			this.env.ENVIRONMENT,
166 | 			this.ctx.container,
167 | 			new Request(`http://host:${OPEN_CONTAINER_PORT}/files/contents`, {
168 | 				method: 'POST',
169 | 				body: JSON.stringify(file),
170 | 				headers: {
171 | 					'content-type': 'application/json',
172 | 				},
173 | 			}),
174 | 			OPEN_CONTAINER_PORT
175 | 		)
176 | 		if (!res || !res.ok) {
177 | 			throw new Error(`Request to container failed: ${await res.text()}`)
178 | 		}
179 | 		return `Wrote file: ${file.path}`
180 | 	}
181 | }
182 | 
```

--------------------------------------------------------------------------------
/apps/workers-bindings/src/bindings.app.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import OAuthProvider from '@cloudflare/workers-oauth-provider'
  2 | import { McpAgent } from 'agents/mcp'
  3 | 
  4 | import { handleApiTokenMode, isApiTokenRequest } from '@repo/mcp-common/src/api-token-mode'
  5 | import {
  6 | 	createAuthHandlers,
  7 | 	handleTokenExchangeCallback,
  8 | } from '@repo/mcp-common/src/cloudflare-oauth-handler'
  9 | import { getUserDetails, UserDetails } from '@repo/mcp-common/src/durable-objects/user_details.do'
 10 | import { getEnv } from '@repo/mcp-common/src/env'
 11 | import { getProps } from '@repo/mcp-common/src/get-props'
 12 | import { registerPrompts } from '@repo/mcp-common/src/prompts/docs-ai-search.prompts'
 13 | import { RequiredScopes } from '@repo/mcp-common/src/scopes'
 14 | import { CloudflareMCPServer } from '@repo/mcp-common/src/server'
 15 | import { registerAccountTools } from '@repo/mcp-common/src/tools/account.tools'
 16 | import { registerD1Tools } from '@repo/mcp-common/src/tools/d1.tools'
 17 | import { registerDocsTools } from '@repo/mcp-common/src/tools/docs-ai-search.tools'
 18 | import { registerHyperdriveTools } from '@repo/mcp-common/src/tools/hyperdrive.tools'
 19 | import { registerKVTools } from '@repo/mcp-common/src/tools/kv_namespace.tools'
 20 | import { registerR2BucketTools } from '@repo/mcp-common/src/tools/r2_bucket.tools'
 21 | import { registerWorkersTools } from '@repo/mcp-common/src/tools/worker.tools'
 22 | import { MetricsTracker } from '@repo/mcp-observability'
 23 | 
 24 | import type { AuthProps } from '@repo/mcp-common/src/cloudflare-oauth-handler'
 25 | import type { Env } from './bindings.context'
 26 | 
 27 | export { UserDetails }
 28 | 
 29 | const env = getEnv<Env>()
 30 | 
 31 | const metrics = new MetricsTracker(env.MCP_METRICS, {
 32 | 	name: env.MCP_SERVER_NAME,
 33 | 	version: env.MCP_SERVER_VERSION,
 34 | })
 35 | 
 36 | export type WorkersBindingsMCPState = { activeAccountId: string | null }
 37 | 
 38 | // Context from the auth process, encrypted & stored in the auth token
 39 | // and provided to the DurableMCP as this.props
 40 | type Props = AuthProps
 41 | 
 42 | export class WorkersBindingsMCP extends McpAgent<Env, WorkersBindingsMCPState, Props> {
 43 | 	_server: CloudflareMCPServer | undefined
 44 | 	set server(server: CloudflareMCPServer) {
 45 | 		this._server = server
 46 | 	}
 47 | 
 48 | 	get server(): CloudflareMCPServer {
 49 | 		if (!this._server) {
 50 | 			throw new Error('Tried to access server before it was initialized')
 51 | 		}
 52 | 
 53 | 		return this._server
 54 | 	}
 55 | 
 56 | 	initialState: WorkersBindingsMCPState = {
 57 | 		activeAccountId: null,
 58 | 	}
 59 | 
 60 | 	constructor(ctx: DurableObjectState, env: Env) {
 61 | 		super(ctx, env)
 62 | 	}
 63 | 
 64 | 	async init() {
 65 | 		// TODO: Probably we'll want to track account tokens usage through an account identifier at some point
 66 | 		const props = getProps(this)
 67 | 		const userId = props.type === 'user_token' ? props.user.id : undefined
 68 | 
 69 | 		this.server = new CloudflareMCPServer({
 70 | 			userId,
 71 | 			wae: this.env.MCP_METRICS,
 72 | 			serverInfo: {
 73 | 				name: this.env.MCP_SERVER_NAME,
 74 | 				version: this.env.MCP_SERVER_VERSION,
 75 | 			},
 76 | 		})
 77 | 
 78 | 		registerAccountTools(this)
 79 | 		registerKVTools(this)
 80 | 		registerWorkersTools(this)
 81 | 		registerR2BucketTools(this)
 82 | 		registerD1Tools(this)
 83 | 		registerHyperdriveTools(this)
 84 | 
 85 | 		// Add docs tools
 86 | 		registerDocsTools(this.server, this.env)
 87 | 		registerPrompts(this.server)
 88 | 	}
 89 | 
 90 | 	async getActiveAccountId() {
 91 | 		try {
 92 | 			const props = getProps(this)
 93 | 			// account tokens are scoped to one account
 94 | 			if (props.type === 'account_token') {
 95 | 				return props.account.id
 96 | 			}
 97 | 			// Get UserDetails Durable Object based off the userId and retrieve the activeAccountId from it
 98 | 			// we do this so we can persist activeAccountId across sessions
 99 | 			const userDetails = getUserDetails(env, props.user.id)
100 | 			return await userDetails.getActiveAccountId()
101 | 		} catch (e) {
102 | 			this.server.recordError(e)
103 | 			return null
104 | 		}
105 | 	}
106 | 
107 | 	async setActiveAccountId(accountId: string) {
108 | 		try {
109 | 			const props = getProps(this)
110 | 			// account tokens are scoped to one account
111 | 			if (props.type === 'account_token') {
112 | 				return
113 | 			}
114 | 			const userDetails = getUserDetails(env, props.user.id)
115 | 			await userDetails.setActiveAccountId(accountId)
116 | 		} catch (e) {
117 | 			this.server.recordError(e)
118 | 		}
119 | 	}
120 | }
121 | 
122 | const BindingsScopes = {
123 | 	...RequiredScopes,
124 | 	'account:read': 'See your account info such as account details, analytics, and memberships.',
125 | 	'workers:write':
126 | 		'See and change Cloudflare Workers data such as zones, KV storage, namespaces, scripts, and routes.',
127 | 	'd1:write': 'Create, read, and write to D1 databases',
128 | } as const
129 | 
130 | export default {
131 | 	fetch: async (req: Request, env: Env, ctx: ExecutionContext) => {
132 | 		if (await isApiTokenRequest(req, env)) {
133 | 			console.log('is token mode')
134 | 			return await handleApiTokenMode(WorkersBindingsMCP, req, env, ctx)
135 | 		}
136 | 
137 | 		return new OAuthProvider({
138 | 			apiHandlers: {
139 | 				'/mcp': WorkersBindingsMCP.serve('/mcp'),
140 | 				'/sse': WorkersBindingsMCP.serveSSE('/sse'),
141 | 			},
142 | 			// @ts-ignore
143 | 			defaultHandler: createAuthHandlers({ scopes: BindingsScopes, metrics }),
144 | 			authorizeEndpoint: '/oauth/authorize',
145 | 			tokenEndpoint: '/token',
146 | 			tokenExchangeCallback: (options) =>
147 | 				handleTokenExchangeCallback(
148 | 					options,
149 | 					env.CLOUDFLARE_CLIENT_ID,
150 | 					env.CLOUDFLARE_CLIENT_SECRET
151 | 				),
152 | 			// Cloudflare access token TTL
153 | 			accessTokenTTL: 3600,
154 | 			clientRegistrationEndpoint: '/register',
155 | 		}).fetch(req, env, ctx)
156 | 	},
157 | }
158 | 
```

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

```typescript
  1 | import { z } from 'zod'
  2 | 
  3 | import {
  4 | 	handleGetWorkersService,
  5 | 	handleWorkerScriptDownload,
  6 | 	handleWorkersList,
  7 | } from '../api/workers.api'
  8 | import { getCloudflareClient } from '../cloudflare-api'
  9 | import { fmt } from '../format'
 10 | import { getProps } from '../get-props'
 11 | 
 12 | import type { CloudflareMcpAgent } from '../types/cloudflare-mcp-agent.types'
 13 | 
 14 | /**
 15 |  * Registers the workers tools with the MCP server
 16 |  * @param server The MCP server instance
 17 |  * @param accountId Cloudflare account ID
 18 |  * @param apiToken Cloudflare API token
 19 |  */
 20 | // Define the scriptName parameter schema
 21 | const workerNameParam = z.string().describe('The name of the worker script to retrieve')
 22 | 
 23 | export function registerWorkersTools(agent: CloudflareMcpAgent) {
 24 | 	// Tool to list all workers
 25 | 	agent.server.tool(
 26 | 		'workers_list',
 27 | 		fmt.trim(`
 28 | 			List all Workers in your Cloudflare account.
 29 | 
 30 | 			If you only need details of a single Worker, use workers_get_worker.
 31 | 		`),
 32 | 		{},
 33 | 		{
 34 | 			title: 'List Workers',
 35 | 			annotations: {
 36 | 				readOnlyHint: true,
 37 | 				destructiveHint: false,
 38 | 			},
 39 | 		},
 40 | 		async () => {
 41 | 			const accountId = await agent.getActiveAccountId()
 42 | 			if (!accountId) {
 43 | 				return {
 44 | 					content: [
 45 | 						{
 46 | 							type: 'text',
 47 | 							text: 'No currently active accountId. Try listing your accounts (accounts_list) and then setting an active account (set_active_account)',
 48 | 						},
 49 | 					],
 50 | 				}
 51 | 			}
 52 | 
 53 | 			try {
 54 | 				const props = getProps(agent)
 55 | 				const results = await handleWorkersList({
 56 | 					client: getCloudflareClient(props.accessToken),
 57 | 					accountId,
 58 | 				})
 59 | 				// Extract worker details and sort by created_on date (newest first)
 60 | 				const workers = results
 61 | 					.map((worker) => ({
 62 | 						name: worker.id,
 63 | 						// The API client doesn't know tag exists. The tag is needed in other places such as Workers Builds
 64 | 						id: z.object({ tag: z.string() }).parse(worker),
 65 | 						modified_on: worker.modified_on || null,
 66 | 						created_on: worker.created_on || null,
 67 | 					}))
 68 | 					// order by created_on desc ( newest first )
 69 | 					.sort((a, b) => {
 70 | 						if (!a.created_on) return 1
 71 | 						if (!b.created_on) return -1
 72 | 						return new Date(b.created_on).getTime() - new Date(a.created_on).getTime()
 73 | 					})
 74 | 
 75 | 				return {
 76 | 					content: [
 77 | 						{
 78 | 							type: 'text',
 79 | 							text: JSON.stringify({
 80 | 								workers,
 81 | 								count: workers.length,
 82 | 							}),
 83 | 						},
 84 | 					],
 85 | 				}
 86 | 			} catch (e) {
 87 | 				agent.server.recordError(e)
 88 | 				return {
 89 | 					content: [
 90 | 						{
 91 | 							type: 'text',
 92 | 							text: `Error listing workers: ${e instanceof Error && e.message}`,
 93 | 						},
 94 | 					],
 95 | 				}
 96 | 			}
 97 | 		}
 98 | 	)
 99 | 
100 | 	// Tool to get a specific worker's script details
101 | 	agent.server.tool(
102 | 		'workers_get_worker',
103 | 		'Get the details of the Cloudflare Worker.',
104 | 		{
105 | 			scriptName: workerNameParam,
106 | 		},
107 | 		{
108 | 			title: 'Get Worker details',
109 | 			annotations: {
110 | 				readOnlyHint: true,
111 | 				destructiveHint: false,
112 | 			},
113 | 		},
114 | 		async (params) => {
115 | 			const accountId = await agent.getActiveAccountId()
116 | 			if (!accountId) {
117 | 				return {
118 | 					content: [
119 | 						{
120 | 							type: 'text',
121 | 							text: 'No currently active accountId. Try listing your accounts (accounts_list) and then setting an active account (set_active_account)',
122 | 						},
123 | 					],
124 | 				}
125 | 			}
126 | 
127 | 			try {
128 | 				const props = getProps(agent)
129 | 				const { scriptName } = params
130 | 				const res = await handleGetWorkersService({
131 | 					apiToken: props.accessToken,
132 | 					scriptName,
133 | 					accountId,
134 | 				})
135 | 
136 | 				if (!res.result) {
137 | 					return {
138 | 						content: [
139 | 							{
140 | 								type: 'text',
141 | 								text: 'Worker not found',
142 | 							},
143 | 						],
144 | 					}
145 | 				}
146 | 
147 | 				return {
148 | 					content: [
149 | 						{
150 | 							type: 'text',
151 | 							text: await fmt.asTSV([
152 | 								{
153 | 									name: res.result.id,
154 | 									id: res.result.default_environment.script_tag,
155 | 								},
156 | 							]),
157 | 						},
158 | 					],
159 | 				}
160 | 			} catch (e) {
161 | 				agent.server.recordError(e)
162 | 				return {
163 | 					content: [
164 | 						{
165 | 							type: 'text',
166 | 							text: `Error retrieving worker script: ${e instanceof Error && e.message}`,
167 | 						},
168 | 					],
169 | 				}
170 | 			}
171 | 		}
172 | 	)
173 | 
174 | 	// Tool to get a specific worker's script content
175 | 	agent.server.tool(
176 | 		'workers_get_worker_code',
177 | 		'Get the source code of a Cloudflare Worker. Note: This may be a bundled version of the worker.',
178 | 		{ scriptName: workerNameParam },
179 | 		{
180 | 			title: 'Get Worker code',
181 | 			annotations: {
182 | 				readOnlyHint: true,
183 | 				destructiveHint: false,
184 | 			},
185 | 		},
186 | 		async (params) => {
187 | 			const accountId = await agent.getActiveAccountId()
188 | 			if (!accountId) {
189 | 				return {
190 | 					content: [
191 | 						{
192 | 							type: 'text',
193 | 							text: 'No currently active accountId. Try listing your accounts (accounts_list) and then setting an active account (set_active_account)',
194 | 						},
195 | 					],
196 | 				}
197 | 			}
198 | 
199 | 			try {
200 | 				const props = getProps(agent)
201 | 				const { scriptName } = params
202 | 				const scriptContent = await handleWorkerScriptDownload({
203 | 					client: getCloudflareClient(props.accessToken),
204 | 					scriptName,
205 | 					accountId,
206 | 				})
207 | 				return {
208 | 					content: [
209 | 						{
210 | 							type: 'text',
211 | 							text: scriptContent,
212 | 						},
213 | 					],
214 | 				}
215 | 			} catch (e) {
216 | 				agent.server.recordError(e)
217 | 				return {
218 | 					content: [
219 | 						{
220 | 							type: 'text',
221 | 							text: `Error retrieving worker script: ${e instanceof Error && e.message}`,
222 | 						},
223 | 					],
224 | 				}
225 | 			}
226 | 		}
227 | 	)
228 | }
229 | 
```

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

```typescript
  1 | import OAuthProvider from '@cloudflare/workers-oauth-provider'
  2 | import { McpAgent } from 'agents/mcp'
  3 | 
  4 | import { handleApiTokenMode, isApiTokenRequest } from '@repo/mcp-common/src/api-token-mode'
  5 | import {
  6 | 	createAuthHandlers,
  7 | 	handleTokenExchangeCallback,
  8 | } from '@repo/mcp-common/src/cloudflare-oauth-handler'
  9 | import { getUserDetails, UserDetails } from '@repo/mcp-common/src/durable-objects/user_details.do'
 10 | import { getEnv } from '@repo/mcp-common/src/env'
 11 | import { getProps } from '@repo/mcp-common/src/get-props'
 12 | import { registerPrompts } from '@repo/mcp-common/src/prompts/docs-ai-search.prompts'
 13 | import { RequiredScopes } from '@repo/mcp-common/src/scopes'
 14 | import { initSentryWithUser } from '@repo/mcp-common/src/sentry'
 15 | import { CloudflareMCPServer } from '@repo/mcp-common/src/server'
 16 | import { registerAccountTools } from '@repo/mcp-common/src/tools/account.tools'
 17 | import { registerDocsTools } from '@repo/mcp-common/src/tools/docs-ai-search.tools'
 18 | import { registerWorkersTools } from '@repo/mcp-common/src/tools/worker.tools'
 19 | 
 20 | import { MetricsTracker } from '../../../packages/mcp-observability/src'
 21 | import { registerObservabilityTools } from './tools/workers-observability.tools'
 22 | 
 23 | import type { AuthProps } from '@repo/mcp-common/src/cloudflare-oauth-handler'
 24 | import type { Env } from './workers-observability.context'
 25 | 
 26 | export { UserDetails }
 27 | 
 28 | const env = getEnv<Env>()
 29 | 
 30 | const metrics = new MetricsTracker(env.MCP_METRICS, {
 31 | 	name: env.MCP_SERVER_NAME,
 32 | 	version: env.MCP_SERVER_VERSION,
 33 | })
 34 | 
 35 | // Context from the auth process, encrypted & stored in the auth token
 36 | // and provided to the DurableMCP as this.props
 37 | type Props = AuthProps
 38 | 
 39 | type State = { activeAccountId: string | null }
 40 | 
 41 | export class ObservabilityMCP extends McpAgent<Env, State, Props> {
 42 | 	_server: CloudflareMCPServer | undefined
 43 | 	set server(server: CloudflareMCPServer) {
 44 | 		this._server = server
 45 | 	}
 46 | 	get server(): CloudflareMCPServer {
 47 | 		if (!this._server) {
 48 | 			throw new Error('Tried to access server before it was initialized')
 49 | 		}
 50 | 
 51 | 		return this._server
 52 | 	}
 53 | 
 54 | 	async init() {
 55 | 		// TODO: Probably we'll want to track account tokens usage through an account identifier at some point
 56 | 		const props = getProps(this)
 57 | 		const userId = props.type === 'user_token' ? props.user.id : undefined
 58 | 		const sentry =
 59 | 			props.type === 'user_token' ? initSentryWithUser(env, this.ctx, props.user.id) : undefined
 60 | 
 61 | 		this.server = new CloudflareMCPServer({
 62 | 			userId,
 63 | 			wae: this.env.MCP_METRICS,
 64 | 			serverInfo: {
 65 | 				name: this.env.MCP_SERVER_NAME,
 66 | 				version: this.env.MCP_SERVER_VERSION,
 67 | 			},
 68 | 			sentry,
 69 | 			options: {
 70 | 				instructions: `# Cloudflare Workers Observability Tool
 71 | 				* A cloudflare worker is a serverless function
 72 | 				* Workers Observability is the tool to inspect the logs for your cloudflare Worker
 73 | 				* Each log is a structured JSON payload with keys and values
 74 | 
 75 | 
 76 | 				This server allows you to analyze your Cloudflare Workers logs and metrics.
 77 | 				`,
 78 | 			},
 79 | 		})
 80 | 
 81 | 		registerAccountTools(this)
 82 | 
 83 | 		// Register Cloudflare Workers tools
 84 | 		registerWorkersTools(this)
 85 | 
 86 | 		// Register Cloudflare Workers logs tools
 87 | 		registerObservabilityTools(this)
 88 | 
 89 | 		// Add docs tools
 90 | 		registerDocsTools(this.server, this.env)
 91 | 		registerPrompts(this.server)
 92 | 	}
 93 | 
 94 | 	async getActiveAccountId() {
 95 | 		try {
 96 | 			const props = getProps(this)
 97 | 			// account tokens are scoped to one account
 98 | 			if (props.type === 'account_token') {
 99 | 				return props.account.id
100 | 			}
101 | 			// Get UserDetails Durable Object based off the userId and retrieve the activeAccountId from it
102 | 			// we do this so we can persist activeAccountId across sessions
103 | 			const userDetails = getUserDetails(env, props.user.id)
104 | 			return await userDetails.getActiveAccountId()
105 | 		} catch (e) {
106 | 			this.server.recordError(e)
107 | 			return null
108 | 		}
109 | 	}
110 | 
111 | 	async setActiveAccountId(accountId: string) {
112 | 		try {
113 | 			const props = getProps(this)
114 | 			// account tokens are scoped to one account
115 | 			if (props.type === 'account_token') {
116 | 				return
117 | 			}
118 | 			const userDetails = getUserDetails(env, props.user.id)
119 | 			await userDetails.setActiveAccountId(accountId)
120 | 		} catch (e) {
121 | 			this.server.recordError(e)
122 | 		}
123 | 	}
124 | }
125 | 
126 | const ObservabilityScopes = {
127 | 	...RequiredScopes,
128 | 	'account:read': 'See your account info such as account details, analytics, and memberships.',
129 | 	'workers:read':
130 | 		'See and change Cloudflare Workers data such as zones, KV storage, namespaces, scripts, and routes.',
131 | 	'workers_observability:read': 'See observability logs for your account',
132 | } as const
133 | 
134 | export default {
135 | 	fetch: async (req: Request, env: Env, ctx: ExecutionContext) => {
136 | 		if (await isApiTokenRequest(req, env)) {
137 | 			return await handleApiTokenMode(ObservabilityMCP, req, env, ctx)
138 | 		}
139 | 
140 | 		return new OAuthProvider({
141 | 			apiHandlers: {
142 | 				'/mcp': ObservabilityMCP.serve('/mcp'),
143 | 				'/sse': ObservabilityMCP.serveSSE('/sse'),
144 | 			},
145 | 			// @ts-ignore
146 | 			defaultHandler: createAuthHandlers({ scopes: ObservabilityScopes, metrics }),
147 | 			authorizeEndpoint: '/oauth/authorize',
148 | 			tokenEndpoint: '/token',
149 | 			tokenExchangeCallback: (options) =>
150 | 				handleTokenExchangeCallback(
151 | 					options,
152 | 					env.CLOUDFLARE_CLIENT_ID,
153 | 					env.CLOUDFLARE_CLIENT_SECRET
154 | 				),
155 | 			// Cloudflare access token TTL
156 | 			accessTokenTTL: 3600,
157 | 			clientRegistrationEndpoint: '/register',
158 | 		}).fetch(req, env, ctx)
159 | 	},
160 | }
161 | 
```

--------------------------------------------------------------------------------
/apps/sandbox-container/server/containerMcp.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { McpAgent } from 'agents/mcp'
  2 | 
  3 | import { getProps } from '@repo/mcp-common/src/get-props'
  4 | import { CloudflareMCPServer } from '@repo/mcp-common/src/server'
  5 | 
  6 | import { ExecParams, FilePathParam, FileWrite } from '../shared/schema'
  7 | import { BASE_INSTRUCTIONS } from './prompts'
  8 | import { stripProtocolFromFilePath } from './utils'
  9 | 
 10 | import type { Props, UserContainer } from './sandbox.server.app'
 11 | import type { Env } from './sandbox.server.context'
 12 | 
 13 | export class ContainerMcpAgent extends McpAgent<Env, never, Props> {
 14 | 	_server: CloudflareMCPServer | undefined
 15 | 	set server(server: CloudflareMCPServer) {
 16 | 		this._server = server
 17 | 	}
 18 | 
 19 | 	get server(): CloudflareMCPServer {
 20 | 		if (!this._server) {
 21 | 			throw new Error('Tried to access server before it was initialized')
 22 | 		}
 23 | 
 24 | 		return this._server
 25 | 	}
 26 | 
 27 | 	get userContainer(): DurableObjectStub<UserContainer> {
 28 | 		const props = getProps(this)
 29 | 		// TODO: Support account scoped tokens?
 30 | 		if (props.type === 'account_token') {
 31 | 			throw new Error('Container server does not currently support account scoped tokens')
 32 | 		}
 33 | 		const userContainer = this.env.USER_CONTAINER.idFromName(props.user.id)
 34 | 		return this.env.USER_CONTAINER.get(userContainer)
 35 | 	}
 36 | 
 37 | 	constructor(
 38 | 		public ctx: DurableObjectState,
 39 | 		public env: Env
 40 | 	) {
 41 | 		console.log('creating container DO')
 42 | 		super(ctx, env)
 43 | 	}
 44 | 
 45 | 	async init() {
 46 | 		const props = getProps(this)
 47 | 		// TODO: Probably we'll want to track account tokens usage through an account identifier at some point
 48 | 		const userId = props.type === 'user_token' ? props.user.id : undefined
 49 | 
 50 | 		this.server = new CloudflareMCPServer({
 51 | 			userId,
 52 | 			wae: this.env.MCP_METRICS,
 53 | 			serverInfo: {
 54 | 				name: this.env.MCP_SERVER_NAME,
 55 | 				version: this.env.MCP_SERVER_VERSION,
 56 | 			},
 57 | 			options: { instructions: BASE_INSTRUCTIONS },
 58 | 		})
 59 | 
 60 | 		this.server.tool(
 61 | 			'container_initialize',
 62 | 			`Start or restart the container.
 63 | 			Use this tool to initialize a container before running any python or node.js code that the user requests ro run.`,
 64 | 			async () => {
 65 | 				const props = getProps(this)
 66 | 				if (props.type === 'account_token') {
 67 | 					return {
 68 | 						// TODO: Support account scoped tokens?
 69 | 						// we'll need to add support for an account blocklist in that case
 70 | 						content: [
 71 | 							{
 72 | 								type: 'text',
 73 | 								text: 'Container server does not currently support account scoped tokens.',
 74 | 							},
 75 | 						],
 76 | 					}
 77 | 				}
 78 | 
 79 | 				const userInBlocklist = await this.env.USER_BLOCKLIST.get(props.user.id)
 80 | 				if (userInBlocklist) {
 81 | 					return {
 82 | 						content: [{ type: 'text', text: 'Blocked from intializing container.' }],
 83 | 					}
 84 | 				}
 85 | 				return {
 86 | 					content: [{ type: 'text', text: await this.userContainer.container_initialize() }],
 87 | 				}
 88 | 			}
 89 | 		)
 90 | 
 91 | 		this.server.tool(
 92 | 			'container_ping',
 93 | 			`Ping the container for liveliness. Use this tool to check if the container is running.`,
 94 | 			async () => {
 95 | 				return {
 96 | 					content: [{ type: 'text', text: await this.userContainer.container_ping() }],
 97 | 				}
 98 | 			}
 99 | 		)
100 | 		this.server.tool(
101 | 			'container_exec',
102 | 			`Run a command in a container and return the results from stdout.
103 | 			If necessary, set a timeout. To debug, stream back standard error.
104 | 			If you're using python, ALWAYS use python3 alongside pip3`,
105 | 			{ args: ExecParams },
106 | 			async ({ args }) => {
107 | 				return {
108 | 					content: [{ type: 'text', text: await this.userContainer.container_exec(args) }],
109 | 				}
110 | 			}
111 | 		)
112 | 		this.server.tool(
113 | 			'container_file_delete',
114 | 			'Delete file in the working directory',
115 | 			{ args: FilePathParam },
116 | 			async ({ args }) => {
117 | 				const path = await stripProtocolFromFilePath(args.path)
118 | 				const deleted = await this.userContainer.container_file_delete(path)
119 | 				return {
120 | 					content: [{ type: 'text', text: `File deleted: ${deleted}.` }],
121 | 				}
122 | 			}
123 | 		)
124 | 		this.server.tool(
125 | 			'container_file_write',
126 | 			'Create a new file with the provided contents in the working direcotry, overwriting the file if it already exists',
127 | 			{ args: FileWrite },
128 | 			async ({ args }) => {
129 | 				args.path = await stripProtocolFromFilePath(args.path)
130 | 				return {
131 | 					content: [{ type: 'text', text: await this.userContainer.container_file_write(args) }],
132 | 				}
133 | 			}
134 | 		)
135 | 		this.server.tool(
136 | 			'container_files_list',
137 | 			'List working directory file tree. This just reads the contents of the current working directory',
138 | 			async () => {
139 | 				// Begin workaround using container read rather than ls:
140 | 				const readFile = await this.userContainer.container_file_read('.')
141 | 				return {
142 | 					content: [
143 | 						{
144 | 							type: 'resource',
145 | 							resource: {
146 | 								text: readFile.type === 'text' ? readFile.textOutput : readFile.base64Output,
147 | 								uri: `file://`,
148 | 								mimeType: readFile.mimeType,
149 | 							},
150 | 						},
151 | 					],
152 | 				}
153 | 			}
154 | 		)
155 | 		this.server.tool(
156 | 			'container_file_read',
157 | 			'Read a specific file or directory. Use this tool if you would like to read files or display them to the user. This allow you to get a displayable image for the user if there is an image file.',
158 | 			{ args: FilePathParam },
159 | 			async ({ args }) => {
160 | 				const path = await stripProtocolFromFilePath(args.path)
161 | 				const readFile = await this.userContainer.container_file_read(path)
162 | 
163 | 				return {
164 | 					content: [
165 | 						{
166 | 							type: 'resource',
167 | 							resource: {
168 | 								...(readFile.type === 'text'
169 | 									? { text: readFile.textOutput }
170 | 									: { blob: readFile.base64Output }),
171 | 								uri: `file://${path}`,
172 | 								mimeType: readFile.mimeType,
173 | 							},
174 | 						},
175 | 					],
176 | 				}
177 | 			}
178 | 		)
179 | 	}
180 | }
181 | 
```

--------------------------------------------------------------------------------
/packages/mcp-common/src/api/cf1-integration.api.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { fetchCloudflareApi } from '../cloudflare-api'
  2 | import {
  3 | 	AssetCategoriesResponse,
  4 | 	AssetDetail,
  5 | 	AssetsResponse,
  6 | 	IntegrationResponse,
  7 | 	IntegrationsResponse,
  8 | } from '../types/cf1-integrations.types'
  9 | import { V4Schema } from '../v4-api'
 10 | 
 11 | import type { z } from 'zod'
 12 | import type {
 13 | 	zReturnedAssetCategoriesResult,
 14 | 	zReturnedAssetsResult,
 15 | 	zReturnedIntegrationResult,
 16 | 	zReturnedIntegrationsResult,
 17 | } from '../types/cf1-integrations.types'
 18 | 
 19 | interface BaseParams {
 20 | 	accountId: string
 21 | 	apiToken: string
 22 | }
 23 | 
 24 | interface PaginationParams {
 25 | 	page?: number
 26 | 	pageSize?: number
 27 | }
 28 | 
 29 | type IntegrationParams = BaseParams & { integrationIdParam: string }
 30 | type AssetCategoryParams = BaseParams & { type?: string; vendor?: string }
 31 | type AssetSearchParams = BaseParams & { searchTerm: string } & PaginationParams
 32 | type AssetByIdParams = BaseParams & { assetId: string }
 33 | type AssetByCategoryParams = BaseParams & { categoryId: string } & PaginationParams
 34 | type AssetByIntegrationParams = BaseParams & { integrationId: string } & PaginationParams
 35 | 
 36 | const buildParams = (baseParams: Record<string, string>, pagination?: PaginationParams) => {
 37 | 	const params = new URLSearchParams(baseParams)
 38 | 	if (pagination?.page) params.append('page', String(pagination.page))
 39 | 	if (pagination?.pageSize) params.append('page_size', String(pagination.pageSize))
 40 | 	return params
 41 | }
 42 | 
 43 | const buildIntegrationEndpoint = (integrationId: string) => `/casb/integrations/${integrationId}`
 44 | const buildAssetEndpoint = (assetId?: string) =>
 45 | 	assetId ? `/casb/assets/${assetId}` : '/casb/assets'
 46 | const buildAssetCategoryEndpoint = () => '/casb/asset_categories'
 47 | 
 48 | const makeApiCall = async <T>({
 49 | 	endpoint,
 50 | 	accountId,
 51 | 	apiToken,
 52 | 	responseSchema,
 53 | 	params,
 54 | }: {
 55 | 	endpoint: string
 56 | 	accountId: string
 57 | 	apiToken: string
 58 | 	responseSchema: z.ZodType<any>
 59 | 	params?: URLSearchParams
 60 | }): Promise<T> => {
 61 | 	try {
 62 | 		const fullEndpoint = params ? `${endpoint}?${params.toString()}` : endpoint
 63 | 		const data = await fetchCloudflareApi({
 64 | 			endpoint: fullEndpoint,
 65 | 			accountId,
 66 | 			apiToken,
 67 | 			responseSchema,
 68 | 			options: {
 69 | 				method: 'GET',
 70 | 				headers: { 'Content-Type': 'application/json' },
 71 | 			},
 72 | 		})
 73 | 		return data.result as T
 74 | 	} catch (error) {
 75 | 		console.error(`API call failed for ${endpoint}:`, error)
 76 | 		throw error
 77 | 	}
 78 | }
 79 | 
 80 | // Resource-specific API call handlers
 81 | const makeIntegrationCall = <T>(params: IntegrationParams, responseSchema: z.ZodType<any>) =>
 82 | 	makeApiCall<T>({
 83 | 		endpoint: buildIntegrationEndpoint(params.integrationIdParam),
 84 | 		accountId: params.accountId,
 85 | 		apiToken: params.apiToken,
 86 | 		responseSchema,
 87 | 	})
 88 | 
 89 | const makeAssetCall = <T>(
 90 | 	params: BaseParams & PaginationParams,
 91 | 	responseSchema: z.ZodType<any>,
 92 | 	assetId?: string,
 93 | 	additionalParams?: Record<string, string>
 94 | ) =>
 95 | 	makeApiCall<T>({
 96 | 		endpoint: buildAssetEndpoint(assetId),
 97 | 		accountId: params.accountId,
 98 | 		apiToken: params.apiToken,
 99 | 		responseSchema,
100 | 		params: buildParams(additionalParams || {}, params),
101 | 	})
102 | 
103 | const makeAssetCategoryCall = <T>(params: AssetCategoryParams, responseSchema: z.ZodType<any>) =>
104 | 	makeApiCall<T>({
105 | 		endpoint: buildAssetCategoryEndpoint(),
106 | 		accountId: params.accountId,
107 | 		apiToken: params.apiToken,
108 | 		responseSchema,
109 | 		params: buildParams({
110 | 			...(params.vendor && { vendor: params.vendor }),
111 | 			...(params.type && { type: params.type }),
112 | 		}),
113 | 	})
114 | 
115 | // Integration handlers
116 | export async function handleIntegrationById(
117 | 	params: IntegrationParams
118 | ): Promise<{ integration: zReturnedIntegrationResult | null }> {
119 | 	const integration = await makeIntegrationCall<zReturnedIntegrationResult>(
120 | 		params,
121 | 		V4Schema(IntegrationResponse)
122 | 	)
123 | 	return { integration }
124 | }
125 | 
126 | export async function handleIntegrations(
127 | 	params: BaseParams
128 | ): Promise<{ integrations: zReturnedIntegrationsResult | null }> {
129 | 	const integrations = await makeApiCall<zReturnedIntegrationsResult>({
130 | 		endpoint: '/casb/integrations',
131 | 		accountId: params.accountId,
132 | 		apiToken: params.apiToken,
133 | 		responseSchema: V4Schema(IntegrationsResponse),
134 | 	})
135 | 	return { integrations }
136 | }
137 | 
138 | // Asset category handlers
139 | export async function handleAssetCategories(
140 | 	params: AssetCategoryParams
141 | ): Promise<{ categories: zReturnedAssetCategoriesResult | null }> {
142 | 	const categories = await makeAssetCategoryCall<zReturnedAssetCategoriesResult>(
143 | 		params,
144 | 		V4Schema(AssetCategoriesResponse)
145 | 	)
146 | 	return { categories }
147 | }
148 | 
149 | // Asset handlers
150 | export async function handleAssets(params: BaseParams & PaginationParams) {
151 | 	const assets = await makeAssetCall<zReturnedAssetsResult>(params, V4Schema(AssetsResponse))
152 | 	return { assets }
153 | }
154 | 
155 | export async function handleAssetsByIntegrationId(params: AssetByIntegrationParams) {
156 | 	const assets = await makeAssetCall<zReturnedAssetsResult>(
157 | 		params,
158 | 		V4Schema(AssetsResponse),
159 | 		undefined,
160 | 		{ integration_id: params.integrationId }
161 | 	)
162 | 	return { assets }
163 | }
164 | 
165 | export async function handleAssetById(params: AssetByIdParams) {
166 | 	const asset = await makeAssetCall<zReturnedAssetsResult>(
167 | 		params,
168 | 		V4Schema(AssetDetail),
169 | 		params.assetId
170 | 	)
171 | 	return { asset }
172 | }
173 | 
174 | export async function handleAssetsByAssetCategoryId(params: AssetByCategoryParams) {
175 | 	const assets = await makeAssetCall<zReturnedAssetsResult>(
176 | 		params,
177 | 		V4Schema(AssetsResponse),
178 | 		undefined,
179 | 		{ category_id: params.categoryId }
180 | 	)
181 | 	return { assets }
182 | }
183 | 
184 | export async function handleAssetsSearch(params: AssetSearchParams) {
185 | 	const assets = await makeAssetCall<zReturnedAssetsResult>(
186 | 		params,
187 | 		V4Schema(AssetsResponse),
188 | 		undefined,
189 | 		{ search: params.searchTerm }
190 | 	)
191 | 	return { assets }
192 | }
193 | 
```
Page 4/29FirstPrevNextLast