#
tokens: 49596/50000 40/376 files (page 3/27)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 3 of 27. 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-autorag
│   │   ├── .eslintrc.cjs
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── docs-autorag.app.ts
│   │   │   ├── docs-autorag.context.ts
│   │   │   └── tools
│   │   │       └── docs-autorag.tools.ts
│   │   ├── tsconfig.json
│   │   ├── vitest.config.ts
│   │   ├── worker-configuration.d.ts
│   │   └── wrangler.jsonc
│   ├── docs-vectorize
│   │   ├── .eslintrc.cjs
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── docs-vectorize.app.ts
│   │   │   └── docs-vectorize.context.ts
│   │   ├── tsconfig.json
│   │   ├── vitest.config.ts
│   │   ├── worker-configuration.d.ts
│   │   └── wrangler.jsonc
│   ├── graphql
│   │   ├── .eslintrc.cjs
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── graphql.app.ts
│   │   │   ├── graphql.context.ts
│   │   │   └── tools
│   │   │       └── graphql.tools.ts
│   │   ├── tsconfig.json
│   │   ├── types.d.ts
│   │   ├── worker-configuration.d.ts
│   │   └── wrangler.jsonc
│   ├── logpush
│   │   ├── .dev.vars.example
│   │   ├── .eslintrc.cjs
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── logpush.app.ts
│   │   │   ├── logpush.context.ts
│   │   │   └── tools
│   │   │       └── logpush.tools.ts
│   │   ├── tsconfig.json
│   │   ├── types.d.ts
│   │   ├── vitest.config.ts
│   │   ├── worker-configuration.d.ts
│   │   └── wrangler.jsonc
│   ├── radar
│   │   ├── .dev.vars.example
│   │   ├── .eslintrc.cjs
│   │   ├── CHANGELOG.md
│   │   ├── CONTRIBUTING.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── radar.app.ts
│   │   │   ├── radar.context.ts
│   │   │   ├── tools
│   │   │   │   ├── radar.tools.ts
│   │   │   │   └── url-scanner.tools.ts
│   │   │   ├── types
│   │   │   │   ├── radar.ts
│   │   │   │   └── url-scanner.ts
│   │   │   └── utils.ts
│   │   ├── tsconfig.json
│   │   ├── types.d.ts
│   │   ├── vitest.config.ts
│   │   ├── worker-configuration.d.ts
│   │   └── wrangler.jsonc
│   ├── sandbox-container
│   │   ├── .dev.vars.example
│   │   ├── .eslintrc.cjs
│   │   ├── CHANGELOG.md
│   │   ├── container
│   │   │   ├── fileUtils.spec.ts
│   │   │   ├── fileUtils.ts
│   │   │   ├── sandbox.container.app.ts
│   │   │   └── tsconfig.json
│   │   ├── CONTRIBUTING.md
│   │   ├── Dockerfile
│   │   ├── evals
│   │   │   ├── exec.eval.ts
│   │   │   ├── files.eval.ts
│   │   │   ├── initialize.eval.ts
│   │   │   └── utils.ts
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── server
│   │   │   ├── containerHelpers.ts
│   │   │   ├── containerManager.ts
│   │   │   ├── containerMcp.ts
│   │   │   ├── metrics.ts
│   │   │   ├── prompts.ts
│   │   │   ├── sandbox.server.app.ts
│   │   │   ├── sandbox.server.context.ts
│   │   │   ├── userContainer.ts
│   │   │   ├── utils.spec.ts
│   │   │   └── utils.ts
│   │   ├── shared
│   │   │   ├── consts.ts
│   │   │   └── schema.ts
│   │   ├── tsconfig.json
│   │   ├── types.d.ts
│   │   ├── vitest.config.evals.ts
│   │   ├── worker-configuration.d.ts
│   │   └── wrangler.jsonc
│   ├── workers-bindings
│   │   ├── .dev.vars.example
│   │   ├── .eslintrc.cjs
│   │   ├── .gitignore
│   │   ├── CHANGELOG.md
│   │   ├── CONTRIBUTING.md
│   │   ├── evals
│   │   │   ├── accounts.eval.ts
│   │   │   ├── hyperdrive.eval.ts
│   │   │   ├── kv_namespaces.eval.ts
│   │   │   ├── types.d.ts
│   │   │   └── utils.ts
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── bindings.app.ts
│   │   │   └── bindings.context.ts
│   │   ├── tsconfig.json
│   │   ├── vitest.config.evals.ts
│   │   ├── vitest.config.ts
│   │   ├── worker-configuration.d.ts
│   │   └── wrangler.jsonc
│   ├── workers-builds
│   │   ├── .dev.vars.example
│   │   ├── .eslintrc.cjs
│   │   ├── CHANGELOG.md
│   │   ├── CONTRIBUTING.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── tools
│   │   │   │   └── workers-builds.tools.ts
│   │   │   ├── workers-builds.app.ts
│   │   │   └── workers-builds.context.ts
│   │   ├── tsconfig.json
│   │   ├── types.d.ts
│   │   ├── vite.config.mts
│   │   ├── vitest.config.ts
│   │   ├── worker-configuration.d.ts
│   │   └── wrangler.jsonc
│   └── workers-observability
│       ├── .dev.vars.example
│       ├── .eslintrc.cjs
│       ├── CHANGELOG.md
│       ├── CONTRIBUTING.md
│       ├── package.json
│       ├── README.md
│       ├── src
│       │   ├── tools
│       │   │   └── workers-observability.tools.ts
│       │   ├── workers-observability.app.ts
│       │   └── workers-observability.context.ts
│       ├── tsconfig.json
│       ├── types.d.ts
│       ├── vitest.config.ts
│       ├── worker-configuration.d.ts
│       └── wrangler.jsonc
├── CONTRIBUTING.md
├── implementation-guides
│   ├── evals.md
│   ├── tools.md
│   └── type-validators.md
├── LICENSE
├── package.json
├── packages
│   ├── eslint-config
│   │   ├── CHANGELOG.md
│   │   ├── default.cjs
│   │   ├── package.json
│   │   └── README.md
│   ├── eval-tools
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── src
│   │   │   ├── runTask.ts
│   │   │   ├── scorers.ts
│   │   │   └── test-models.ts
│   │   ├── tsconfig.json
│   │   ├── worker-configuration.d.ts
│   │   └── wrangler.json
│   ├── mcp-common
│   │   ├── .eslintrc.cjs
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── api
│   │   │   │   ├── account.api.ts
│   │   │   │   ├── cf1-integration.api.ts
│   │   │   │   ├── workers-builds.api.ts
│   │   │   │   ├── workers-observability.api.ts
│   │   │   │   ├── workers.api.ts
│   │   │   │   └── zone.api.ts
│   │   │   ├── api-handler.ts
│   │   │   ├── api-token-mode.ts
│   │   │   ├── cloudflare-api.ts
│   │   │   ├── cloudflare-auth.ts
│   │   │   ├── cloudflare-oauth-handler.ts
│   │   │   ├── config.ts
│   │   │   ├── constants.ts
│   │   │   ├── durable-kv-store.ts
│   │   │   ├── durable-objects
│   │   │   │   └── user_details.do.ts
│   │   │   ├── env.ts
│   │   │   ├── format.spec.ts
│   │   │   ├── format.ts
│   │   │   ├── get-props.ts
│   │   │   ├── mcp-error.ts
│   │   │   ├── poll.ts
│   │   │   ├── prompts
│   │   │   │   └── docs-vectorize.prompts.ts
│   │   │   ├── scopes.ts
│   │   │   ├── sentry.ts
│   │   │   ├── server.ts
│   │   │   ├── tools
│   │   │   │   ├── account.tools.ts
│   │   │   │   ├── d1.tools.ts
│   │   │   │   ├── docs-vectorize.tools.ts
│   │   │   │   ├── hyperdrive.tools.ts
│   │   │   │   ├── kv_namespace.tools.ts
│   │   │   │   ├── r2_bucket.tools.ts
│   │   │   │   ├── worker.tools.ts
│   │   │   │   └── zone.tools.ts
│   │   │   ├── types
│   │   │   │   ├── cf1-integrations.types.ts
│   │   │   │   ├── cloudflare-mcp-agent.types.ts
│   │   │   │   ├── d1.types.ts
│   │   │   │   ├── hyperdrive.types.ts
│   │   │   │   ├── kv_namespace.types.ts
│   │   │   │   ├── r2_bucket.types.ts
│   │   │   │   ├── shared.types.ts
│   │   │   │   ├── tools.types.ts
│   │   │   │   ├── workers-builds.types.ts
│   │   │   │   ├── workers-logs.types.ts
│   │   │   │   └── workers.types.ts
│   │   │   ├── utils.spec.ts
│   │   │   ├── utils.ts
│   │   │   └── v4-api.ts
│   │   ├── tests
│   │   │   └── utils
│   │   │       └── cloudflare-mock.ts
│   │   ├── tsconfig.json
│   │   ├── types.d.ts
│   │   ├── vitest.config.ts
│   │   └── worker-configuration.d.ts
│   ├── mcp-observability
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── src
│   │   │   ├── analytics-engine.ts
│   │   │   ├── index.ts
│   │   │   └── metrics.ts
│   │   ├── tsconfig.json
│   │   └── worker-configuration.d.ts
│   ├── tools
│   │   ├── .eslintrc.cjs
│   │   ├── bin
│   │   │   ├── run-changeset-new
│   │   │   ├── run-eslint-workers
│   │   │   ├── run-fix-deps
│   │   │   ├── run-tsc
│   │   │   ├── run-turbo
│   │   │   ├── run-vitest
│   │   │   ├── run-vitest-ci
│   │   │   ├── run-wrangler-deploy
│   │   │   ├── run-wrangler-types
│   │   │   └── runx
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── bin
│   │   │   │   └── runx.ts
│   │   │   ├── changesets.spec.ts
│   │   │   ├── changesets.ts
│   │   │   ├── cmd
│   │   │   │   └── deploy-published-packages.ts
│   │   │   ├── proc.ts
│   │   │   ├── test
│   │   │   │   ├── fixtures
│   │   │   │   │   └── changesets
│   │   │   │   │       ├── empty
│   │   │   │   │       │   └── .gitkeep
│   │   │   │   │       ├── invalid-json
│   │   │   │   │       │   └── published-packages.json
│   │   │   │   │       ├── invalid-schema
│   │   │   │   │       │   └── published-packages.json
│   │   │   │   │       └── valid
│   │   │   │   │           └── published-packages.json
│   │   │   │   └── setup.ts
│   │   │   └── tsconfig.ts
│   │   ├── tsconfig.json
│   │   └── vitest.config.ts
│   └── typescript-config
│       ├── CHANGELOG.md
│       ├── package.json
│       ├── tools.json
│       ├── workers-lib.json
│       └── workers.json
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── README.md
├── server.json
├── tsconfig.json
├── turbo.json
└── vitest.workspace.ts
```

# Files

--------------------------------------------------------------------------------
/apps/sandbox-container/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 | 	"name": "containers-mcp",
 3 | 	"version": "0.2.6",
 4 | 	"private": true,
 5 | 	"type": "module",
 6 | 	"scripts": {
 7 | 		"check:types": "run-tsc",
 8 | 		"check:lint": "run-eslint-workers",
 9 | 		"deploy": "run-wrangler-deploy",
10 | 		"dev": "concurrently \"tsx container/sandbox.container.app.ts\" \"wrangler dev --var \"ENVIRONMENT:dev\"\"",
11 | 		"build:container": "docker build --platform linux/amd64 --tag sandbox-container:$(git rev-parse --short HEAD) -f Dockerfile ../../ && wrangler containers push sandbox-container:$(git rev-parse --short HEAD)",
12 | 		"start": "wrangler dev",
13 | 		"start:container": "tsx container/sandbox.container.app.ts",
14 | 		"postinstall": "mkdir -p workdir",
15 | 		"test": "vitest",
16 | 		"types": "wrangler types --include-env=false",
17 | 		"eval:dev": "start-server-and-test --expect 404 eval:server http://localhost:8976 'vitest --testTimeout=60000 --config vitest.config.evals.ts'",
18 | 		"eval:server": "concurrently \"tsx container/sandbox.container.app.ts\" \"wrangler dev --var ENVIRONMENT:test --var DEV_DISABLE_OAUTH:true --var DEV_CLOUDFLARE_EMAIL:[email protected]\"",
19 | 		"eval:ci": "start-server-and-test --expect 404 eval:server http://localhost:8976 'vitest run --testTimeout=60000 --config vitest.config.evals.ts'"
20 | 	},
21 | 	"dependencies": {
22 | 		"@cloudflare/workers-oauth-provider": "0.0.5",
23 | 		"@hono/node-server": "1.13.8",
24 | 		"@hono/zod-validator": "0.4.3",
25 | 		"@modelcontextprotocol/sdk": "1.18.2",
26 | 		"@n8n/json-schema-to-zod": "1.1.0",
27 | 		"@repo/eval-tools": "workspace:*",
28 | 		"@repo/mcp-common": "workspace:*",
29 | 		"@repo/mcp-observability": "workspace:*",
30 | 		"agents": "0.2.7",
31 | 		"cron-schedule": "5.0.4",
32 | 		"esbuild": "0.25.1",
33 | 		"hono": "4.7.6",
34 | 		"mime": "4.0.6",
35 | 		"simple-git-hooks": "2.12.1",
36 | 		"tsx": "4.19.3",
37 | 		"vitest-evals": "0.1.4",
38 | 		"zod": "3.24.2"
39 | 	},
40 | 	"devDependencies": {
41 | 		"@cloudflare/vitest-pool-workers": "0.8.14",
42 | 		"@types/mock-fs": "4.13.4",
43 | 		"@types/node": "22.14.1",
44 | 		"ai": "4.3.10",
45 | 		"concurrently": "9.1.2",
46 | 		"mock-fs": "5.5.0",
47 | 		"start-server-and-test": "2.0.11",
48 | 		"wrangler": "4.10.0"
49 | 	}
50 | }
51 | 
```

--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: Release
 2 | 
 3 | on:
 4 |   push:
 5 |     branches: ['main']
 6 | 
 7 | env:
 8 |   FORCE_COLOR: 1
 9 | 
10 | jobs:
11 |   create-release-pr:
12 |     name: Create Release PR
13 |     runs-on: ubuntu-24.04
14 |     permissions:
15 |       contents: write
16 |       pull-requests: write
17 |     timeout-minutes: 5
18 |     concurrency: ${{ github.workflow }}-create-release-pr
19 |     outputs:
20 |       published: ${{ steps.create-release-pr.outputs.published }}
21 |     steps:
22 |       - name: Checkout Repo
23 |         uses: actions/checkout@v4
24 |       - uses: ./.github/actions/setup
25 |       - name: Create Release PR
26 |         id: create-release-pr
27 |         uses: changesets/action@v1
28 |         with:
29 |           publish: pnpm changeset publish
30 |         env:
31 |           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
32 |       - name: Save Published Packages
33 |         if: steps.create-release-pr.outputs.published == 'true'
34 |         run: |
35 |           echo '${{steps.create-release-pr.outputs.publishedPackages}}' \
36 |             > ${{ github.workspace }}/published-packages.json
37 |       - name: Upload Published Packages
38 |         if: steps.create-release-pr.outputs.published == 'true'
39 |         uses: actions/upload-artifact@v4
40 |         with:
41 |           name: published-packages
42 |           path: ${{ github.workspace }}/published-packages.json
43 | 
44 |   deploy-production:
45 |     name: Deploy (production)
46 |     needs: create-release-pr
47 |     if: needs.create-release-pr.outputs.published == 'true'
48 |     runs-on: ubuntu-24.04
49 |     timeout-minutes: 10
50 |     concurrency: ${{ github.workflow }}-deploy-production
51 |     permissions:
52 |       contents: read
53 |     steps:
54 |       - name: Checkout Repo
55 |         uses: actions/checkout@v4
56 |       - name: Download published packages
57 |         uses: actions/download-artifact@v4
58 |         with:
59 |           name: published-packages
60 |           path: ${{ runner.temp }}
61 |       - uses: ./.github/actions/setup
62 |       - name: Deploy Published Workers (production)
63 |         run: pnpm runx deploy-published-workers --env production
64 |         env:
65 |           CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
66 |           CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
67 | 
```

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

```typescript
 1 | import OAuthProvider from '@cloudflare/workers-oauth-provider'
 2 | 
 3 | import { createApiHandler } from '@repo/mcp-common/src/api-handler'
 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 { getEnv } from '@repo/mcp-common/src/env'
10 | import { RequiredScopes } from '@repo/mcp-common/src/scopes'
11 | import { MetricsTracker } from '@repo/mcp-observability'
12 | 
13 | import { ContainerManager } from './containerManager'
14 | import { ContainerMcpAgent } from './containerMcp'
15 | import { UserContainer } from './userContainer'
16 | 
17 | import type { AuthProps } from '@repo/mcp-common/src/cloudflare-oauth-handler'
18 | import type { Env } from './sandbox.server.context'
19 | 
20 | export { ContainerManager, ContainerMcpAgent, UserContainer }
21 | 
22 | const env = getEnv<Env>()
23 | 
24 | const metrics = new MetricsTracker(env.MCP_METRICS, {
25 | 	name: env.MCP_SERVER_NAME,
26 | 	version: env.MCP_SERVER_VERSION,
27 | })
28 | 
29 | // Context from the auth process, encrypted & stored in the auth token
30 | // and provided to the DurableMCP as this.props
31 | export type Props = AuthProps
32 | 
33 | const ContainerScopes = {
34 | 	...RequiredScopes,
35 | 	'account:read': 'See your account info such as account details, analytics, and memberships.',
36 | } as const
37 | 
38 | export default {
39 | 	fetch: async (req: Request, env: Env, ctx: ExecutionContext) => {
40 | 		if (await isApiTokenRequest(req, env)) {
41 | 			return await handleApiTokenMode(ContainerMcpAgent, req, env, ctx)
42 | 		}
43 | 
44 | 		return new OAuthProvider({
45 | 			apiRoute: ['/mcp', '/sse'],
46 | 			apiHandler: createApiHandler(ContainerMcpAgent),
47 | 			// @ts-ignore
48 | 			defaultHandler: createAuthHandlers({ scopes: ContainerScopes, metrics }),
49 | 			authorizeEndpoint: '/oauth/authorize',
50 | 			tokenEndpoint: '/token',
51 | 			tokenExchangeCallback: (options) =>
52 | 				handleTokenExchangeCallback(
53 | 					options,
54 | 					env.CLOUDFLARE_CLIENT_ID,
55 | 					env.CLOUDFLARE_CLIENT_SECRET
56 | 				),
57 | 			// Cloudflare access token TTL
58 | 			accessTokenTTL: 3600,
59 | 			clientRegistrationEndpoint: '/register',
60 | 		}).fetch(req, env, ctx)
61 | 	},
62 | }
63 | 
```

--------------------------------------------------------------------------------
/apps/docs-vectorize/CHANGELOG.md:
--------------------------------------------------------------------------------

```markdown
  1 | # docs-vectorize
  2 | 
  3 | ## 0.4.1
  4 | 
  5 | ### Patch Changes
  6 | 
  7 | - 43f493d: Update agent + modelcontextprotocol deps
  8 | - Updated dependencies [43f493d]
  9 |   - @repo/[email protected]
 10 |   - @repo/[email protected]
 11 | 
 12 | ## 0.4.0
 13 | 
 14 | ### Minor Changes
 15 | 
 16 | - dee0a7b: Updated the model for docs search to embeddinggemma-300m
 17 | 
 18 | ## 0.3.3
 19 | 
 20 | ### Patch Changes
 21 | 
 22 | - 24dd872: feat: Add MCP tool titles and hints to all Cloudflare tools
 23 | - Updated dependencies [24dd872]
 24 |   - @repo/[email protected]
 25 | 
 26 | ## 0.3.2
 27 | 
 28 | ### Patch Changes
 29 | 
 30 | - 7422e71: Update MCP sdk
 31 | - Updated dependencies [7422e71]
 32 |   - @repo/[email protected]
 33 |   - @repo/[email protected]
 34 | 
 35 | ## 0.3.1
 36 | 
 37 | ### Patch Changes
 38 | 
 39 | - cc6d41f: Update agents deps & modelcontextprotocol
 40 | - Updated dependencies [1833c6d]
 41 | - Updated dependencies [cc6d41f]
 42 |   - @repo/[email protected]
 43 |   - @repo/[email protected]
 44 | 
 45 | ## 0.3.0
 46 | 
 47 | ### Minor Changes
 48 | 
 49 | - f885d07: Add search docs tool to bindings and obs servers
 50 | 
 51 | ### Patch Changes
 52 | 
 53 | - Updated dependencies [f885d07]
 54 |   - @repo/[email protected]
 55 | 
 56 | ## 0.2.1
 57 | 
 58 | ### Patch Changes
 59 | 
 60 | - Updated dependencies [83e2d19]
 61 |   - @repo/[email protected]
 62 | 
 63 | ## 0.2.0
 64 | 
 65 | ### Minor Changes
 66 | 
 67 | - 89bfaf4: feat: add Pages to Workers migration guide to docs-vectorize MCP server
 68 | 
 69 | ## 0.1.0
 70 | 
 71 | ### Minor Changes
 72 | 
 73 | - 6cf52a6: Support AOT tokens
 74 | 
 75 | ### Patch Changes
 76 | 
 77 | - 0fc4439: Update agents and modelcontext dependencies
 78 | - Updated dependencies [6cf52a6]
 79 | - Updated dependencies [0fc4439]
 80 |   - @repo/[email protected]
 81 |   - @repo/[email protected]
 82 | 
 83 | ## 0.0.4
 84 | 
 85 | ### Patch Changes
 86 | 
 87 | - 3677a18: Remove extraneous log
 88 | - Updated dependencies [3677a18]
 89 |   - @repo/[email protected]
 90 | 
 91 | ## 0.0.3
 92 | 
 93 | ### Patch Changes
 94 | 
 95 | - Updated dependencies [86c2e4f]
 96 |   - @repo/[email protected]
 97 | 
 98 | ## 0.0.2
 99 | 
100 | ### Patch Changes
101 | 
102 | - cf3771b: chore: add suffixes to common files in apps and packages
103 | 
104 |   It can be confusing switching between 16 files named 'index.ts', or 3 files named workers.ts. This change renames common files to have suffixes such as .types.ts, .api.ts, etc. to make it easier to work across files in the monorepo.
105 | 
106 | - Updated dependencies [cf3771b]
107 |   - @repo/[email protected]
108 |   - @repo/[email protected]
109 | 
```

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

```typescript
 1 | import { Cloudflare } from 'cloudflare'
 2 | import { env } from 'cloudflare:workers'
 3 | 
 4 | import type { z } from 'zod'
 5 | 
 6 | export function getCloudflareClient(apiToken: string) {
 7 | 	// @ts-expect-error We don't have actual env in this package
 8 | 	if (env.DEV_DISABLE_OAUTH) {
 9 | 		return new Cloudflare({
10 | 			// @ts-expect-error We don't have actual env in this package, but we know this is defined because the initial Oauth handshake will fail without it
11 | 			apiEmail: env.DEV_CLOUDFLARE_EMAIL,
12 | 			// @ts-expect-error We don't have actual env in this package, but we know this is defined because the initial Oauth handshake will fail without it
13 | 			apiKey: env.DEV_CLOUDFLARE_API_TOKEN,
14 | 		})
15 | 	}
16 | 
17 | 	return new Cloudflare({ apiToken })
18 | }
19 | 
20 | /**
21 |  * Makes a request to the Cloudflare API
22 |  * @param endpoint API endpoint path (without the base URL)
23 |  * @param accountId Cloudflare account ID
24 |  * @param apiToken Cloudflare API token
25 |  * @param options Additional fetch options
26 |  * @returns The API response
27 |  */
28 | export async function fetchCloudflareApi<T>({
29 | 	endpoint,
30 | 	accountId,
31 | 	apiToken,
32 | 	responseSchema,
33 | 	options = {},
34 | }: {
35 | 	endpoint: string
36 | 	accountId: string
37 | 	apiToken: string
38 | 	responseSchema?: z.ZodType<T>
39 | 	options?: RequestInit
40 | }): Promise<T> {
41 | 	const url = `https://api.cloudflare.com/client/v4/accounts/${accountId}${endpoint}`
42 | 
43 | 	// @ts-expect-error We don't have actual env in this package
44 | 	if (env.DEV_DISABLE_OAUTH) {
45 | 		options.headers = {
46 | 			...options.headers,
47 | 			// @ts-expect-error We don't have actual env in this package
48 | 			'X-Auth-Email': env.DEV_CLOUDFLARE_EMAIL,
49 | 			// @ts-expect-error We don't have actual env in this package
50 | 			'X-Auth-Key': env.DEV_CLOUDFLARE_API_TOKEN,
51 | 		}
52 | 	}
53 | 	const response = await fetch(url, {
54 | 		...options,
55 | 		headers: {
56 | 			Authorization: `Bearer ${apiToken}`,
57 | 			...(options.headers || {}),
58 | 		},
59 | 	})
60 | 
61 | 	if (!response.ok) {
62 | 		const error = await response.text()
63 | 		throw new Error(`Cloudflare API request failed: ${error}`)
64 | 	}
65 | 
66 | 	const data = await response.json()
67 | 
68 | 	// If a schema is provided, validate the response
69 | 	if (responseSchema) {
70 | 		return responseSchema.parse(data)
71 | 	}
72 | 
73 | 	return data as T
74 | }
75 | 
```

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

```typescript
 1 | export const MAX_CONTAINERS = 50
 2 | export async function startAndWaitForPort(
 3 | 	environment: 'dev' | 'prod' | 'test',
 4 | 	container: Container | undefined,
 5 | 	portToAwait: number,
 6 | 	maxTries = 10
 7 | ): Promise<boolean> {
 8 | 	if (environment === 'dev' || environment === 'test') {
 9 | 		console.log('Running in dev, assuming locally running container')
10 | 		return true
11 | 	}
12 | 
13 | 	if (!container) {
14 | 		throw new Error('Error: ctx.container is undefined. Does this DO support containers?')
15 | 	}
16 | 
17 | 	const port = container.getTcpPort(portToAwait)
18 | 	// promise to make sure the container does not exit
19 | 	let monitor
20 | 
21 | 	for (let i = 0; i < maxTries; i++) {
22 | 		try {
23 | 			if (!container.running) {
24 | 				console.log('starting container')
25 | 				container.start({
26 | 					enableInternet: true,
27 | 				})
28 | 
29 | 				// force DO to keep track of running state
30 | 				monitor = container.monitor()
31 | 				void monitor.then(() => console.log('Container exited'))
32 | 			}
33 | 
34 | 			const conn = await port.connect(`10.0.0.1:${portToAwait}`)
35 | 			await conn.close()
36 | 			console.log('Connected')
37 | 			return true
38 | 		} catch (err: any) {
39 | 			if (!(err instanceof Error)) {
40 | 				throw err
41 | 			}
42 | 
43 | 			console.error('Error connecting to the container on', i, 'try', err)
44 | 
45 | 			if (err.message.includes('listening')) {
46 | 				await new Promise((res) => setTimeout(res, 300))
47 | 				continue
48 | 			}
49 | 
50 | 			// no container yet
51 | 			if (err.message.includes('there is no container instance that can be provided')) {
52 | 				await new Promise((res) => setTimeout(res, 300))
53 | 				continue
54 | 			}
55 | 
56 | 			console.log(err)
57 | 			return false
58 | 		}
59 | 	}
60 | 
61 | 	return false
62 | }
63 | 
64 | export async function proxyFetch(
65 | 	environment: 'dev' | 'prod' | 'test',
66 | 	container: Container | undefined,
67 | 	request: Request,
68 | 	portNumber: number
69 | ): Promise<Response> {
70 | 	if (environment === 'dev' || environment === 'test') {
71 | 		const url = request.url
72 | 			.replace('https://', 'http://')
73 | 			.replace('http://host', 'http://localhost')
74 | 		return fetch(url, request.clone() as Request)
75 | 	}
76 | 
77 | 	if (!container) {
78 | 		throw new Error('Error: ctx.container is undefined. Does this DO support containers?')
79 | 	}
80 | 
81 | 	return await container
82 | 		.getTcpPort(portNumber)
83 | 		.fetch(request.url.replace('https://', 'http://'), request.clone() as Request)
84 | }
85 | 
```

--------------------------------------------------------------------------------
/packages/mcp-common/src/api-token-mode.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { getUserAndAccounts } from './cloudflare-oauth-handler'
 2 | 
 3 | import type { McpAgent } from 'agents/mcp'
 4 | import type { AuthProps } from './cloudflare-oauth-handler'
 5 | 
 6 | interface RequiredEnv {
 7 | 	DEV_CLOUDFLARE_API_TOKEN: string
 8 | 	DEV_CLOUDFLARE_EMAIL: string
 9 | 	DEV_DISABLE_OAUTH: string
10 | }
11 | 
12 | export async function isApiTokenRequest(req: Request, env: RequiredEnv) {
13 | 	// shortcircuit for dev
14 | 	if (env.DEV_CLOUDFLARE_API_TOKEN && env.DEV_DISABLE_OAUTH === 'true') {
15 | 		return true
16 | 	}
17 | 
18 | 	const authHeader = req.headers.get('Authorization')
19 | 	if (!authHeader) return false
20 | 
21 | 	const [type, token] = authHeader.split(' ')
22 | 	if (type !== 'Bearer') return false
23 | 
24 | 	// Return true only if the token was issued by the OAuthProvider.
25 | 	// A token provisioned by the OAuthProvider has 3 parts, split by colons.
26 | 	const codeParts = token.split(':')
27 | 	return codeParts.length !== 3
28 | }
29 | 
30 | export async function handleApiTokenMode<
31 | 	T extends typeof McpAgent<unknown, unknown, Record<string, unknown>>,
32 | >(agent: T, req: Request, env: RequiredEnv, ctx: ExecutionContext) {
33 | 	// Handle global API token case
34 | 	let opts, token
35 | 	// dev mode
36 | 	if (
37 | 		env.DEV_CLOUDFLARE_API_TOKEN &&
38 | 		env.DEV_CLOUDFLARE_EMAIL &&
39 | 		env.DEV_DISABLE_OAUTH === 'true'
40 | 	) {
41 | 		opts = {
42 | 			'X-Auth-Key': env.DEV_CLOUDFLARE_API_TOKEN,
43 | 			'X-Auth-Email': env.DEV_CLOUDFLARE_EMAIL,
44 | 		}
45 | 		token = env.DEV_CLOUDFLARE_API_TOKEN
46 | 		// header mode
47 | 	} else {
48 | 		const authHeader = req.headers.get('Authorization')
49 | 		if (!authHeader) {
50 | 			throw new Error('Authorization header is required')
51 | 		}
52 | 
53 | 		const [type, tokenStr] = authHeader.split(' ')
54 | 		if (type !== 'Bearer') {
55 | 			throw new Error('Invalid authorization type, must be Bearer')
56 | 		}
57 | 		token = tokenStr
58 | 	}
59 | 
60 | 	const { user, accounts } = await getUserAndAccounts(token, opts)
61 | 
62 | 	// If user is null, handle API token mode
63 | 	if (user === null) {
64 | 		ctx.props = {
65 | 			type: 'account_token',
66 | 			accessToken: token,
67 | 			// we always select the first account from the response,
68 | 			// this assumes that account owned tokens can only access one account
69 | 			account: accounts[0],
70 | 		} satisfies AuthProps
71 | 	} else {
72 | 		ctx.props = {
73 | 			type: 'user_token',
74 | 			accessToken: token,
75 | 			user,
76 | 			accounts,
77 | 		} satisfies AuthProps
78 | 	}
79 | 	return agent.mount('/sse').fetch(req, env, ctx)
80 | }
81 | 
```

--------------------------------------------------------------------------------
/apps/dex-analysis/CHANGELOG.md:
--------------------------------------------------------------------------------

```markdown
  1 | # dex-analysis
  2 | 
  3 | ## 0.2.2
  4 | 
  5 | ### Patch Changes
  6 | 
  7 | - 43f493d: Update agent + modelcontextprotocol deps
  8 | - Updated dependencies [43f493d]
  9 |   - @repo/[email protected]
 10 |   - @repo/[email protected]
 11 | 
 12 | ## 0.2.1
 13 | 
 14 | ### Patch Changes
 15 | 
 16 | - 24dd872: feat: Add MCP tool titles and hints to all Cloudflare tools
 17 | - Updated dependencies [24dd872]
 18 |   - @repo/[email protected]
 19 | 
 20 | ## 0.2.0
 21 | 
 22 | ### Minor Changes
 23 | 
 24 | - 9496e21: Add tool for analyzing WARP-diags for common issues via bonobo
 25 | 
 26 | ## 0.1.7
 27 | 
 28 | ### Patch Changes
 29 | 
 30 | - dffbd36: Improve DEX remote captures tools, separate by type for clarity
 31 | 
 32 | ## 0.1.6
 33 | 
 34 | ### Patch Changes
 35 | 
 36 | - d672471: dex-analysis: add WARP diag analysis tools and reader D.O.
 37 | 
 38 | ## 0.1.5
 39 | 
 40 | ### Patch Changes
 41 | 
 42 | - 016cb73: Add more DEX tools, including remote captures
 43 | 
 44 | ## 0.1.4
 45 | 
 46 | ### Patch Changes
 47 | 
 48 | - 7422e71: Update MCP sdk
 49 | - Updated dependencies [7422e71]
 50 |   - @repo/[email protected]
 51 |   - @repo/[email protected]
 52 | 
 53 | ## 0.1.3
 54 | 
 55 | ### Patch Changes
 56 | 
 57 | - cc6d41f: Update agents deps & modelcontextprotocol
 58 | - Updated dependencies [1833c6d]
 59 | - Updated dependencies [cc6d41f]
 60 |   - @repo/[email protected]
 61 |   - @repo/[email protected]
 62 | 
 63 | ## 0.1.2
 64 | 
 65 | ### Patch Changes
 66 | 
 67 | - Updated dependencies [f885d07]
 68 |   - @repo/[email protected]
 69 | 
 70 | ## 0.1.1
 71 | 
 72 | ### Patch Changes
 73 | 
 74 | - Updated dependencies [83e2d19]
 75 |   - @repo/[email protected]
 76 | 
 77 | ## 0.1.0
 78 | 
 79 | ### Minor Changes
 80 | 
 81 | - 6cf52a6: Support AOT tokens
 82 | 
 83 | ### Patch Changes
 84 | 
 85 | - 0fc4439: Update agents and modelcontext dependencies
 86 | - Updated dependencies [6cf52a6]
 87 | - Updated dependencies [0fc4439]
 88 |   - @repo/[email protected]
 89 |   - @repo/[email protected]
 90 | 
 91 | ## 0.0.4
 92 | 
 93 | ### Patch Changes
 94 | 
 95 | - 3677a18: Remove extraneous log
 96 | - Updated dependencies [3677a18]
 97 |   - @repo/[email protected]
 98 | 
 99 | ## 0.0.3
100 | 
101 | ### Patch Changes
102 | 
103 | - 86c2e4f: Add API token passthrough auth
104 | - Updated dependencies [86c2e4f]
105 |   - @repo/[email protected]
106 | 
107 | ## 0.0.2
108 | 
109 | ### Patch Changes
110 | 
111 | - cf3771b: chore: add suffixes to common files in apps and packages
112 | 
113 |   It can be confusing switching between 16 files named 'index.ts', or 3 files named workers.ts. This change renames common files to have suffixes such as .types.ts, .api.ts, etc. to make it easier to work across files in the monorepo.
114 | 
115 | - Updated dependencies [cf3771b]
116 |   - @repo/[email protected]
117 |   - @repo/[email protected]
118 | 
```

--------------------------------------------------------------------------------
/apps/sandbox-container/CHANGELOG.md:
--------------------------------------------------------------------------------

```markdown
  1 | # containers-mcp
  2 | 
  3 | ## 0.2.6
  4 | 
  5 | ### Patch Changes
  6 | 
  7 | - 43f493d: Update agent + modelcontextprotocol deps
  8 | - Updated dependencies [43f493d]
  9 |   - @repo/[email protected]
 10 |   - @repo/[email protected]
 11 |   - @repo/[email protected]
 12 | 
 13 | ## 0.2.5
 14 | 
 15 | ### Patch Changes
 16 | 
 17 | - 24dd872: feat: Add MCP tool titles and hints to all Cloudflare tools
 18 | - Updated dependencies [24dd872]
 19 |   - @repo/[email protected]
 20 |   - @repo/[email protected]
 21 | 
 22 | ## 0.2.4
 23 | 
 24 | ### Patch Changes
 25 | 
 26 | - dffbd36: Use proper wrangler deploy in all servers so we get the name and version
 27 | 
 28 | ## 0.2.3
 29 | 
 30 | ### Patch Changes
 31 | 
 32 | - 7422e71: Update MCP sdk
 33 | - Updated dependencies [7422e71]
 34 |   - @repo/[email protected]
 35 |   - @repo/[email protected]
 36 | 
 37 | ## 0.2.2
 38 | 
 39 | ### Patch Changes
 40 | 
 41 | - cc6d41f: Update agents deps & modelcontextprotocol
 42 | - Updated dependencies [1833c6d]
 43 | - Updated dependencies [cc6d41f]
 44 |   - @repo/[email protected]
 45 |   - @repo/[email protected]
 46 |   - @repo/[email protected]
 47 | 
 48 | ## 0.2.1
 49 | 
 50 | ### Patch Changes
 51 | 
 52 | - Updated dependencies [f885d07]
 53 |   - @repo/[email protected]
 54 | 
 55 | ## 0.2.0
 56 | 
 57 | ### Minor Changes
 58 | 
 59 | - 2621557: Use new workers:read scope instead of workers:write, as these mcp servers don't require workers write permissions
 60 | 
 61 | ### Patch Changes
 62 | 
 63 | - Updated dependencies [83e2d19]
 64 |   - @repo/[email protected]
 65 | 
 66 | ## 0.1.0
 67 | 
 68 | ### Minor Changes
 69 | 
 70 | - 6cf52a6: Support AOT tokens
 71 | 
 72 | ### Patch Changes
 73 | 
 74 | - 0fc4439: Update agents and modelcontext dependencies
 75 | - Updated dependencies [6cf52a6]
 76 | - Updated dependencies [0fc4439]
 77 |   - @repo/[email protected]
 78 |   - @repo/[email protected]
 79 |   - @repo/[email protected]
 80 | 
 81 | ## 0.0.4
 82 | 
 83 | ### Patch Changes
 84 | 
 85 | - 3677a18: Remove extraneous log
 86 | - Updated dependencies [3677a18]
 87 |   - @repo/[email protected]
 88 | 
 89 | ## 0.0.3
 90 | 
 91 | ### Patch Changes
 92 | 
 93 | - 86c2e4f: Add API token passthrough auth
 94 | - Updated dependencies [86c2e4f]
 95 |   - @repo/[email protected]
 96 | 
 97 | ## 0.0.2
 98 | 
 99 | ### Patch Changes
100 | 
101 | - cf3771b: chore: add suffixes to common files in apps and packages
102 | 
103 |   It can be confusing switching between 16 files named 'index.ts', or 3 files named workers.ts. This change renames common files to have suffixes such as .types.ts, .api.ts, etc. to make it easier to work across files in the monorepo.
104 | 
105 | - Updated dependencies [cf3771b]
106 |   - @repo/[email protected]
107 |   - @repo/[email protected]
108 |   - @repo/[email protected]
109 | 
```

--------------------------------------------------------------------------------
/apps/sandbox-container/container/fileUtils.spec.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import mime from 'mime'
 2 | import mock from 'mock-fs'
 3 | import { afterEach, describe, expect, it, vi } from 'vitest'
 4 | 
 5 | import { get_file_name_from_path, get_mime_type, list_files_in_directory } from './fileUtils'
 6 | 
 7 | vi.mock('mime', () => {
 8 | 	return {
 9 | 		default: {
10 | 			getType: vi.fn(),
11 | 		},
12 | 	}
13 | })
14 | 
15 | afterEach(async () => {
16 | 	mock.restore()
17 | 	vi.restoreAllMocks()
18 | })
19 | 
20 | describe('get_file_name_from_path', () => {
21 | 	it('strips files/contents', async () => {
22 | 		const path = await get_file_name_from_path('/files/contents/cats')
23 | 		expect(path).toBe('/cats')
24 | 	}),
25 | 		it('works if files/contents is not present', async () => {
26 | 			const path = await get_file_name_from_path('/dogs')
27 | 			expect(path).toBe('/dogs')
28 | 		}),
29 | 		it('strips a trailing slash', async () => {
30 | 			const path = await get_file_name_from_path('/files/contents/birds/')
31 | 			expect(path).toBe('/birds')
32 | 		})
33 | }),
34 | 	describe('list_files_in_directory', () => {
35 | 		it('lists the files in a directory', async () => {
36 | 			mock({
37 | 				testDir: {
38 | 					cats: 'aurora, luna',
39 | 					dogs: 'penny',
40 | 				},
41 | 			})
42 | 			const listFiles = await list_files_in_directory('testDir')
43 | 			expect(listFiles).toEqual(['file:///testDir/cats', 'file:///testDir/dogs'])
44 | 		}),
45 | 			it('throws an error if path is not a directory', async () => {
46 | 				mock({
47 | 					testDir: {
48 | 						cats: 'aurora, luna',
49 | 						dogs: 'penny',
50 | 					},
51 | 				})
52 | 				await expect(async () => await list_files_in_directory('testDir/cats')).rejects.toThrow(
53 | 					'Failed to read directory'
54 | 				)
55 | 			}),
56 | 			it('treats empty strings as cwd', async () => {
57 | 				mock({
58 | 					testDir: {
59 | 						cats: 'aurora, luna',
60 | 						dogs: 'penny',
61 | 					},
62 | 				})
63 | 
64 | 				const listFiles = await list_files_in_directory('')
65 | 				expect(listFiles).toEqual(['file:///../../../../../../testDir'])
66 | 			})
67 | 	}),
68 | 	describe('get_mime_type', async () => {
69 | 		it("provides the natural mime type when not 'inode/directory'", async () => {
70 | 			vi.mocked(mime.getType).mockReturnValueOnce('theType')
71 | 			const mimeType = await get_mime_type('someFile')
72 | 			expect(mimeType).toEqual('theType')
73 | 		})
74 | 		it("overrides mime type for 'inode/directory'", async () => {
75 | 			vi.mocked(mime.getType).mockReturnValueOnce('inode/directory')
76 | 			const mimeType = await get_mime_type('someDirectory')
77 | 			expect(mimeType).toEqual('text/directory')
78 | 		})
79 | 	})
80 | 
```

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

```typescript
 1 | import { DurableObject } from 'cloudflare:workers'
 2 | import JSZip from 'jszip'
 3 | 
 4 | import type { Env } from './dex-analysis.context'
 5 | 
 6 | // Helper for reading large WARP diag zip archives.
 7 | // Holds the contents in memory between requests from the agent for specific files
 8 | // instead of having the worker download the zip on every request.
 9 | //
10 | // Each DO represents one remote capture zip
11 | export class WarpDiagReader extends DurableObject<Env> {
12 | 	#cache?: { files: string[]; zip: JSZip }
13 | 
14 | 	// List the files in the zip for the agent
15 | 	async list(accessToken: string, url: string) {
16 | 		const { files } = await this.#getZip(accessToken, url)
17 | 		return files
18 | 	}
19 | 
20 | 	// Return the contents of a file by path
21 | 	async read(accessToken: string, url: string, filepath: string) {
22 | 		const { zip } = await this.#getZip(accessToken, url)
23 | 		const file = zip.file(filepath)
24 | 		const content = await file?.async('text')
25 | 		return content
26 | 	}
27 | 
28 | 	async #getZip(accessToken: string, url: string) {
29 | 		if (this.#cache) {
30 | 			return this.#cache
31 | 		}
32 | 
33 | 		console.log(`WarpDiagReader fetching `, url)
34 | 
35 | 		const res = await fetch(url, {
36 | 			headers: {
37 | 				Authorization: `Bearer ${accessToken}`,
38 | 			},
39 | 		})
40 | 
41 | 		if (res.status !== 200) {
42 | 			throw new Error(`failed to download zip, non-200 status code: ${res.status}`)
43 | 		}
44 | 
45 | 		const zip = await new JSZip().loadAsync(await res.arrayBuffer())
46 | 		const files: string[] = []
47 | 		for (const [relativePath, file] of Object.entries(zip.files)) {
48 | 			if (!file.dir) {
49 | 				files.push(relativePath)
50 | 			}
51 | 		}
52 | 
53 | 		const cache = { files, zip }
54 | 		this.#cache = cache
55 | 		return cache
56 | 	}
57 | }
58 | 
59 | async function hashToken(accessToken: string) {
60 | 	const hashArr = Array.from(
61 | 		new Uint8Array(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(accessToken)))
62 | 	)
63 | 	return hashArr.map((b) => b.toString(16).padStart(2, '0')).join('')
64 | }
65 | 
66 | // Create unique name based on accessToken hash and download url. In order to read cached zip from memory
67 | // you need to have the same access token that was used to fetch it.
68 | async function readerName(accessToken: string, url: string) {
69 | 	return (await hashToken(accessToken)) + url
70 | }
71 | 
72 | export async function getReader(env: Env, accessToken: string, download: string) {
73 | 	const name = await readerName(accessToken, download)
74 | 	const id = env.WARP_DIAG_READER.idFromName(name)
75 | 	return env.WARP_DIAG_READER.get(id)
76 | }
77 | 
```

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

```
1 | <svg width="531" height="240" viewBox="0 0 531 240" fill="none" xmlns="http://www.w3.org/2000/svg">
2 | <path d="M364.276 226.805L364.273 226.815L361.598 236.302C361.597 236.305 361.597 236.307 361.596 236.309C361.113 237.921 359.639 239 358.062 239H4.49414C3.17374 239 2.00552 237.981 1.76785 236.575C1.29745 232.723 1 228.886 1 225.057C1 181.893 35.2676 146.875 78.1587 145.596L79.4005 145.559L79.099 144.353C77.5739 138.256 77.146 131.677 77.7417 124.952C80.333 98.8682 101.26 77.9116 127.401 75.3232C142.053 73.966 155.679 78.2521 166.379 86.1874L167.481 87.0051L167.922 85.705C184.598 36.4752 231.241 1 286.199 1C345.788 1 395.645 42.6457 407.99 98.5134L407.993 98.5269L407.997 98.5403C408.24 99.5149 408.147 100.557 407.73 101.656C407.73 101.656 407.73 101.657 407.729 101.657L396.668 130.417L396.668 130.418C392.403 141.573 384.13 151.739 374.079 159.251C364.03 166.762 352.276 171.568 341.092 172.101L147.818 174.606L147.792 174.606L147.766 174.608C145.391 174.762 143.124 176.442 142.386 178.708L142.374 178.745L142.365 178.783C142.011 180.247 142.191 181.794 142.974 182.975C143.77 184.218 145.169 184.982 146.743 184.984C146.746 184.984 146.748 184.984 146.751 184.984L338.283 187.489C348.152 187.956 356.255 191.91 361.006 198.6L361.009 198.603C366.233 205.897 367.495 215.839 364.276 226.805Z" stroke="white" stroke-width="1"/>
3 | <path d="M420.981 105.214H421.032L421.083 105.209C422.708 105.042 424.513 105.041 426.253 105.041C483.434 105.041 530.001 151.584 530.001 208.558C530.001 218.224 528.631 227.635 526.148 236.579L526.145 236.591L526.142 236.604C525.832 237.842 524.712 238.654 523.395 238.654H377.422C376.423 238.654 375.85 237.79 376.193 236.908C376.193 236.907 376.193 236.907 376.193 236.907L379.304 228.962L379.307 228.955C383.572 217.821 391.803 207.666 401.832 200.159C411.862 192.651 423.623 187.843 434.899 187.315L434.911 187.314L477.389 184.809L477.395 184.809C479.77 184.654 482.037 182.974 482.775 180.709L482.782 180.689L482.787 180.669C483.162 179.333 482.96 177.655 482.193 176.45C481.402 175.209 480.011 174.443 478.443 174.432L437.679 171.929L437.672 171.929L437.665 171.928C427.789 171.464 419.679 167.51 414.925 160.817L414.923 160.813C409.698 153.519 408.436 143.577 411.656 132.611L411.657 132.606L419.119 106.698C419.497 105.775 420.265 105.214 420.981 105.214Z" stroke="white" stroke-width="1"/>
4 | </svg>
5 | 
```

--------------------------------------------------------------------------------
/apps/workers-observability/CHANGELOG.md:
--------------------------------------------------------------------------------

```markdown
  1 | # workers-observability
  2 | 
  3 | ## 0.4.1
  4 | 
  5 | ### Patch Changes
  6 | 
  7 | - 43f493d: Update agent + modelcontextprotocol deps
  8 | - Updated dependencies [43f493d]
  9 |   - @repo/[email protected]
 10 |   - @repo/[email protected]
 11 | 
 12 | ## 0.4.0
 13 | 
 14 | ### Minor Changes
 15 | 
 16 | - dee0a7b: Updated the model for docs search to embeddinggemma-300m
 17 | 
 18 | ## 0.3.4
 19 | 
 20 | ### Patch Changes
 21 | 
 22 | - 24dd872: feat: Add MCP tool titles and hints to all Cloudflare tools
 23 | - Updated dependencies [24dd872]
 24 |   - @repo/[email protected]
 25 | 
 26 | ## 0.3.3
 27 | 
 28 | ### Patch Changes
 29 | 
 30 | - dffbd36: Use proper wrangler deploy in all servers so we get the name and version
 31 | 
 32 | ## 0.3.2
 33 | 
 34 | ### Patch Changes
 35 | 
 36 | - 7422e71: Update MCP sdk
 37 | - Updated dependencies [7422e71]
 38 |   - @repo/[email protected]
 39 |   - @repo/[email protected]
 40 | 
 41 | ## 0.3.1
 42 | 
 43 | ### Patch Changes
 44 | 
 45 | - cc6d41f: Update agents deps & modelcontextprotocol
 46 | - Updated dependencies [1833c6d]
 47 | - Updated dependencies [cc6d41f]
 48 |   - @repo/[email protected]
 49 |   - @repo/[email protected]
 50 | 
 51 | ## 0.3.0
 52 | 
 53 | ### Minor Changes
 54 | 
 55 | - f885d07: Add search docs tool to bindings and obs servers
 56 | 
 57 | ### Patch Changes
 58 | 
 59 | - Updated dependencies [f885d07]
 60 |   - @repo/[email protected]
 61 | 
 62 | ## 0.2.0
 63 | 
 64 | ### Minor Changes
 65 | 
 66 | - 2621557: Use new workers:read scope instead of workers:write, as these mcp servers don't require workers write permissions
 67 | 
 68 | ### Patch Changes
 69 | 
 70 | - Updated dependencies [83e2d19]
 71 |   - @repo/[email protected]
 72 | 
 73 | ## 0.1.0
 74 | 
 75 | ### Minor Changes
 76 | 
 77 | - 6cf52a6: Support AOT tokens
 78 | 
 79 | ### Patch Changes
 80 | 
 81 | - 0fc4439: Update agents and modelcontext dependencies
 82 | - Updated dependencies [6cf52a6]
 83 | - Updated dependencies [0fc4439]
 84 |   - @repo/[email protected]
 85 |   - @repo/[email protected]
 86 | 
 87 | ## 0.0.4
 88 | 
 89 | ### Patch Changes
 90 | 
 91 | - 3677a18: Remove extraneous log
 92 | - Updated dependencies [3677a18]
 93 |   - @repo/[email protected]
 94 | 
 95 | ## 0.0.3
 96 | 
 97 | ### Patch Changes
 98 | 
 99 | - 86c2e4f: Add API token passthrough auth
100 | - Updated dependencies [86c2e4f]
101 |   - @repo/[email protected]
102 | 
103 | ## 0.0.2
104 | 
105 | ### Patch Changes
106 | 
107 | - b190e97: fix: set correct entrypoint in wrangler.jsonc
108 | - cf3771b: chore: add suffixes to common files in apps and packages
109 | 
110 |   It can be confusing switching between 16 files named 'index.ts', or 3 files named workers.ts. This change renames common files to have suffixes such as .types.ts, .api.ts, etc. to make it easier to work across files in the monorepo.
111 | 
112 | - Updated dependencies [cf3771b]
113 |   - @repo/[email protected]
114 |   - @repo/[email protected]
115 | 
```

--------------------------------------------------------------------------------
/packages/eval-tools/src/runTask.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { type MCPClientManager } from 'agents/mcp/client'
 2 | import { generateText, jsonSchema, tool } from 'ai'
 3 | import { z } from 'zod'
 4 | 
 5 | import type { GenerateTextResult, LanguageModelV1, ToolCallPart, ToolSet } from 'ai'
 6 | 
 7 | export async function runTask(
 8 | 	clientManager: MCPClientManager,
 9 | 	model: LanguageModelV1,
10 | 	input: string
11 | ): Promise<{
12 | 	promptOutput: string
13 | 	fullResult: GenerateTextResult<ToolSet, never>
14 | 	toolCalls: ToolCallPart[]
15 | }> {
16 | 	const tools = clientManager.listTools()
17 | 	const toolSet: ToolSet = tools.reduce((acc, v) => {
18 | 		if (!v.inputSchema.properties) {
19 | 			v.inputSchema.properties = {}
20 | 		}
21 | 
22 | 		acc[v.name] = tool({
23 | 			parameters: jsonSchema(v.inputSchema as any),
24 | 			description: v.description,
25 | 			execute: async (args: any, opts) => {
26 | 				try {
27 | 					const res = await clientManager.callTool(
28 | 						{
29 | 							...v,
30 | 							arguments: { ...args },
31 | 						},
32 | 						z.any() as any,
33 | 						{ signal: opts.abortSignal }
34 | 					)
35 | 					return res.content
36 | 				} catch (e) {
37 | 					console.log('Error calling tool')
38 | 					console.log(e)
39 | 					return e
40 | 				}
41 | 			},
42 | 		})
43 | 		return acc
44 | 	}, {} as ToolSet)
45 | 
46 | 	const res = await generateText({
47 | 		model,
48 | 		system:
49 | 			"You are an assistant responsible for evaluating the results of calling various tools. Given the user's query, use the tools available to you to answer the question.",
50 | 		tools: toolSet,
51 | 		prompt: input,
52 | 		maxRetries: 1,
53 | 		maxSteps: 10,
54 | 	})
55 | 
56 | 	// convert into an LLM readable result so our factuality checker can validate tool calls
57 | 	let messagesWithTools = ''
58 | 	const toolCalls: ToolCallPart[] = []
59 | 	const response = res.response
60 | 	const messages = response.messages
61 | 
62 | 	for (const message of messages) {
63 | 		for (const messagePart of message.content) {
64 | 			if (typeof messagePart === 'string') {
65 | 				messagesWithTools += `<message_content type="text">${messagePart}</message_content>`
66 | 			} else if (messagePart.type === 'tool-call') {
67 | 				messagesWithTools += `<message_content type=${messagePart.type}>
68 |     <tool_name>${messagePart.toolName}</tool_name>
69 |     <tool_arguments>${JSON.stringify(messagePart.args)}</tool_arguments>
70 | </message_content>`
71 | 				toolCalls.push(messagePart)
72 | 			} else if (messagePart.type === 'text') {
73 | 				messagesWithTools += `<message_content type=${messagePart.type}>${messagePart.text}</message_content>`
74 | 			}
75 | 		}
76 | 	}
77 | 
78 | 	return { promptOutput: messagesWithTools, fullResult: res, toolCalls }
79 | }
80 | 
```

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

```typescript
 1 | import { z } from 'zod'
 2 | 
 3 | import type {
 4 | 	Namespace,
 5 | 	NamespaceCreateParams,
 6 | 	NamespaceDeleteParams,
 7 | 	NamespaceGetParams,
 8 | 	NamespaceListParams,
 9 | 	NamespaceUpdateParams,
10 | } from 'cloudflare/resources/kv.mjs'
11 | 
12 | /**
13 |  * Zod schema for a KV namespace ID.
14 |  */
15 | export const KvNamespaceIdSchema: z.ZodType<Namespace['id']> = z
16 | 	.string()
17 | 	.describe('The ID of the KV namespace')
18 | 
19 | /**
20 |  * Zod schema for a KV namespace title.
21 |  */
22 | export const KvNamespaceTitleSchema: z.ZodType<Namespace['title']> = z
23 | 	.string()
24 | 	.describe('The human-readable name/title of the KV namespace')
25 | 
26 | /**
27 |  * Zod schema for the optional parameters when listing KV namespaces.
28 |  */
29 | export const KvNamespacesListParamsSchema: z.ZodType<Omit<NamespaceListParams, 'account_id'>> = z
30 | 	.object({
31 | 		direction: z
32 | 			.enum(['asc', 'desc'])
33 | 			.optional()
34 | 			.describe('Direction to order namespaces (asc/desc)'),
35 | 		order: z.enum(['id', 'title']).optional().describe('Field to order namespaces by (id/title)'),
36 | 		page: z.number().int().positive().optional().describe('Page number of results (starts at 1)'),
37 | 		per_page: z
38 | 			.number()
39 | 			.int()
40 | 			.min(1)
41 | 			.max(100)
42 | 			.optional()
43 | 			.describe('Number of namespaces per page (1-100)'),
44 | 	})
45 | 	.describe('Optional parameters for listing KV namespaces')
46 | 
47 | /**
48 |  * Zod schema for parameters needed to create a KV namespace.
49 |  */
50 | export const KvNamespaceCreateParamsSchema: z.ZodType<Omit<NamespaceCreateParams, 'account_id'>> = z
51 | 	.object({
52 | 		title: KvNamespaceTitleSchema,
53 | 	})
54 | 	.describe('Parameters for creating a KV namespace')
55 | 
56 | /**
57 |  * Zod schema for parameters needed to delete a KV namespace.
58 |  */
59 | export const KvNamespaceDeleteParamsSchema: z.ZodType<Omit<NamespaceDeleteParams, 'account_id'>> = z
60 | 	.object({
61 | 		namespace_id: KvNamespaceIdSchema,
62 | 	})
63 | 	.describe('Parameters for deleting a KV namespace')
64 | 
65 | /**
66 |  * Zod schema for parameters needed to get a KV namespace.
67 |  */
68 | export const KvNamespaceGetParamsSchema: z.ZodType<Omit<NamespaceGetParams, 'account_id'>> = z
69 | 	.object({
70 | 		namespace_id: KvNamespaceIdSchema,
71 | 	})
72 | 	.describe('Parameters for getting a KV namespace')
73 | 
74 | /**
75 |  * Zod schema for parameters needed to update a KV namespace.
76 |  */
77 | export const KvNamespaceUpdateParamsSchema: z.ZodType<Omit<NamespaceUpdateParams, 'account_id'>> = z
78 | 	.object({
79 | 		namespace_id: KvNamespaceIdSchema,
80 | 		title: KvNamespaceTitleSchema,
81 | 	})
82 | 	.describe('Parameters for updating a KV namespace')
83 | 
```

--------------------------------------------------------------------------------
/packages/tools/src/changesets.spec.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { program } from '@commander-js/extra-typings'
 2 | import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest'
 3 | 
 4 | import { getPublishedPackages } from './changesets'
 5 | 
 6 | describe('getPublishedPackages', () => {
 7 | 	const fixturesDir = path.join(__dirname, 'test/fixtures/changesets')
 8 | 	const fixture = (name: string) => path.join(fixturesDir, name)
 9 | 
10 | 	beforeAll(() => {
11 | 		// throw errors instead of calling process.exit(1)
12 | 		// within program.error() is called by cliError()
13 | 		program.exitOverride((e) => {
14 | 			throw e
15 | 		})
16 | 	})
17 | 	afterEach(() => {
18 | 		vi.unstubAllEnvs()
19 | 	})
20 | 
21 | 	it('should read and parse valid published packages', async () => {
22 | 		vi.stubEnv('RUNNER_TEMP', fixture('valid'))
23 | 
24 | 		const result = await getPublishedPackages()
25 | 
26 | 		expect(result).toStrictEqual([
27 | 			{ name: 'package-a', version: '1.0.0' },
28 | 			{ name: 'package-b', version: '2.1.3' },
29 | 		])
30 | 	})
31 | 
32 | 	it('should throw error when RUNNER_TEMP is not set', async () => {
33 | 		vi.stubEnv('RUNNER_TEMP', undefined)
34 | 
35 | 		await expect(getPublishedPackages()).rejects.toThrowErrorMatchingInlineSnapshot(
36 | 			`[CommanderError: error: ✖ $RUNNER_TEMP is not set]`
37 | 		)
38 | 	})
39 | 
40 | 	it('should throw error when RUNNER_TEMP is empty', async () => {
41 | 		vi.stubEnv('RUNNER_TEMP', '')
42 | 
43 | 		await expect(getPublishedPackages()).rejects.toThrowErrorMatchingInlineSnapshot(
44 | 			`[CommanderError: error: ✖ $RUNNER_TEMP is empty]`
45 | 		)
46 | 	})
47 | 
48 | 	it('should throw error when published packages file is not found', async () => {
49 | 		vi.stubEnv('RUNNER_TEMP', fixture('empty'))
50 | 
51 | 		await expect(getPublishedPackages()).rejects.toThrowErrorMatchingInlineSnapshot(
52 | 			`[CommanderError: error: No published packages file found at: ${fixture('empty/published-packages.json')}]`
53 | 		)
54 | 	})
55 | 
56 | 	it('should throw error when published packages JSON is invalid', async () => {
57 | 		vi.stubEnv('RUNNER_TEMP', fixture('invalid-json'))
58 | 
59 | 		await expect(getPublishedPackages()).rejects.toThrowErrorMatchingInlineSnapshot(
60 | 			`[Error: Failed to parse published packages: SyntaxError: Unexpected token 'h', "this is not"... is not valid JSON]`
61 | 		)
62 | 	})
63 | 
64 | 	it('should throw error when published packages schema is invalid', async () => {
65 | 		vi.stubEnv('RUNNER_TEMP', fixture('invalid-schema'))
66 | 
67 | 		await expect(getPublishedPackages()).rejects.toThrowErrorMatchingInlineSnapshot(`
68 | 			[Error: Failed to parse published packages: ✖ Invalid input: expected string, received number
69 | 			  → at [0].version]
70 | 		`)
71 | 	})
72 | })
73 | 
```

--------------------------------------------------------------------------------
/apps/workers-bindings/CHANGELOG.md:
--------------------------------------------------------------------------------

```markdown
  1 | # workers-bindings
  2 | 
  3 | ## 0.4.1
  4 | 
  5 | ### Patch Changes
  6 | 
  7 | - 43f493d: Update agent + modelcontextprotocol deps
  8 | - Updated dependencies [43f493d]
  9 |   - @repo/[email protected]
 10 |   - @repo/[email protected]
 11 |   - @repo/[email protected]
 12 | 
 13 | ## 0.4.0
 14 | 
 15 | ### Minor Changes
 16 | 
 17 | - dee0a7b: Updated the model for docs search to embeddinggemma-300m
 18 | 
 19 | ## 0.3.4
 20 | 
 21 | ### Patch Changes
 22 | 
 23 | - 24dd872: feat: Add MCP tool titles and hints to all Cloudflare tools
 24 | - Updated dependencies [24dd872]
 25 |   - @repo/[email protected]
 26 |   - @repo/[email protected]
 27 | 
 28 | ## 0.3.3
 29 | 
 30 | ### Patch Changes
 31 | 
 32 | - dffbd36: Use proper wrangler deploy in all servers so we get the name and version
 33 | 
 34 | ## 0.3.2
 35 | 
 36 | ### Patch Changes
 37 | 
 38 | - 7422e71: Update MCP sdk
 39 | - Updated dependencies [7422e71]
 40 |   - @repo/[email protected]
 41 |   - @repo/[email protected]
 42 | 
 43 | ## 0.3.1
 44 | 
 45 | ### Patch Changes
 46 | 
 47 | - cc6d41f: Update agents deps & modelcontextprotocol
 48 | - Updated dependencies [1833c6d]
 49 | - Updated dependencies [cc6d41f]
 50 |   - @repo/[email protected]
 51 |   - @repo/[email protected]
 52 |   - @repo/[email protected]
 53 | 
 54 | ## 0.3.0
 55 | 
 56 | ### Minor Changes
 57 | 
 58 | - f885d07: Add search docs tool to bindings and obs servers
 59 | 
 60 | ### Patch Changes
 61 | 
 62 | - Updated dependencies [f885d07]
 63 |   - @repo/[email protected]
 64 | 
 65 | ## 0.2.0
 66 | 
 67 | ### Minor Changes
 68 | 
 69 | - 2621557: Use new workers:read scope instead of workers:write, as these mcp servers don't require workers write permissions
 70 | 
 71 | ### Patch Changes
 72 | 
 73 | - Updated dependencies [83e2d19]
 74 |   - @repo/[email protected]
 75 | 
 76 | ## 0.1.0
 77 | 
 78 | ### Minor Changes
 79 | 
 80 | - 6cf52a6: Support AOT tokens
 81 | 
 82 | ### Patch Changes
 83 | 
 84 | - 0fc4439: Update agents and modelcontext dependencies
 85 | - Updated dependencies [6cf52a6]
 86 | - Updated dependencies [0fc4439]
 87 |   - @repo/[email protected]
 88 |   - @repo/[email protected]
 89 |   - @repo/[email protected]
 90 | 
 91 | ## 0.0.3
 92 | 
 93 | ### Patch Changes
 94 | 
 95 | - 3677a18: Remove extraneous log
 96 | - Updated dependencies [3677a18]
 97 |   - @repo/[email protected]
 98 | 
 99 | ## 0.0.2
100 | 
101 | ### Patch Changes
102 | 
103 | - 86c2e4f: Add API token passthrough auth
104 | - Updated dependencies [86c2e4f]
105 |   - @repo/[email protected]
106 | 
107 | ## 0.0.1
108 | 
109 | ### Patch Changes
110 | 
111 | - cf3771b: chore: add suffixes to common files in apps and packages
112 | 
113 |   It can be confusing switching between 16 files named 'index.ts', or 3 files named workers.ts. This change renames common files to have suffixes such as .types.ts, .api.ts, etc. to make it easier to work across files in the monorepo.
114 | 
115 | - Updated dependencies [cf3771b]
116 |   - @repo/[email protected]
117 |   - @repo/[email protected]
118 |   - @repo/[email protected]
119 | 
```

--------------------------------------------------------------------------------
/packages/mcp-common/src/api/workers-observability.api.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { env } from 'cloudflare:workers'
 2 | 
 3 | import { fetchCloudflareApi } from '../cloudflare-api'
 4 | import {
 5 | 	zKeysResponse,
 6 | 	zReturnedQueryRunResult,
 7 | 	zValuesResponse,
 8 | } from '../types/workers-logs.types'
 9 | import { V4Schema } from '../v4-api'
10 | 
11 | import type { z } from 'zod'
12 | import type { zKeysRequest, zQueryRunRequest, zValuesRequest } from '../types/workers-logs.types'
13 | 
14 | type QueryRunRequest = z.infer<typeof zQueryRunRequest>
15 | 
16 | function fixTimeframe(timeframe: QueryRunRequest['timeframe']) {
17 | 	return {
18 | 		from: new Date(timeframe.from).getTime(),
19 | 		to: new Date(timeframe.to).getTime(),
20 | 	}
21 | }
22 | 
23 | export async function queryWorkersObservability(
24 | 	apiToken: string,
25 | 	accountId: string,
26 | 	query: QueryRunRequest
27 | ): Promise<z.infer<typeof zReturnedQueryRunResult> | null> {
28 | 	// @ts-expect-error We don't have actual env in this package
29 | 	const environment = env.ENVIRONMENT
30 | 	const data = await fetchCloudflareApi({
31 | 		endpoint: '/workers/observability/telemetry/query',
32 | 		accountId,
33 | 		apiToken,
34 | 		responseSchema: V4Schema(zReturnedQueryRunResult),
35 | 		options: {
36 | 			method: 'POST',
37 | 			headers: {
38 | 				'Content-Type': 'application/json',
39 | 				'workers-observability-origin': `workers-observability-mcp-${environment}`,
40 | 			},
41 | 			body: JSON.stringify({ ...query, timeframe: fixTimeframe(query.timeframe) }),
42 | 		},
43 | 	})
44 | 
45 | 	return data.result
46 | }
47 | 
48 | type QueryKeysRequest = z.infer<typeof zKeysRequest>
49 | export async function handleWorkerLogsKeys(
50 | 	apiToken: string,
51 | 	accountId: string,
52 | 	keysQuery: QueryKeysRequest
53 | ): Promise<zKeysResponse> {
54 | 	const data = await fetchCloudflareApi({
55 | 		endpoint: '/workers/observability/telemetry/keys',
56 | 		accountId,
57 | 		apiToken,
58 | 		responseSchema: V4Schema(zKeysResponse),
59 | 		options: {
60 | 			method: 'POST',
61 | 			headers: {
62 | 				'Content-Type': 'application/json',
63 | 			},
64 | 			body: JSON.stringify({ ...keysQuery, timeframe: fixTimeframe(keysQuery.timeframe) }),
65 | 		},
66 | 	})
67 | 
68 | 	return data.result || []
69 | }
70 | 
71 | export async function handleWorkerLogsValues(
72 | 	apiToken: string,
73 | 	accountId: string,
74 | 	valuesQuery: z.infer<typeof zValuesRequest>
75 | ): Promise<z.infer<typeof zValuesResponse> | null> {
76 | 	const data = await fetchCloudflareApi({
77 | 		endpoint: '/workers/observability/telemetry/values',
78 | 		accountId,
79 | 		apiToken,
80 | 		responseSchema: V4Schema(zValuesResponse),
81 | 		options: {
82 | 			method: 'POST',
83 | 			headers: {
84 | 				'Content-Type': 'application/json',
85 | 			},
86 | 			body: JSON.stringify({ ...valuesQuery, timeframe: fixTimeframe(valuesQuery.timeframe) }),
87 | 		},
88 | 	})
89 | 
90 | 	return data.result
91 | }
92 | 
```

--------------------------------------------------------------------------------
/packages/eval-tools/src/scorers.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { generateObject } from 'ai'
 2 | import { z } from 'zod'
 3 | 
 4 | import { factualityModel } from './test-models'
 5 | 
 6 | import type { ScoreFn } from 'vitest-evals'
 7 | 
 8 | /**
 9 |  * Checks the factuality of a submission, using
10 |  * OpenAI's GPT-4o model.
11 |  */
12 | export const checkFactuality: ScoreFn = async ({ input, expected, output }) => {
13 | 	const { model } = factualityModel
14 | 	const { object } = await generateObject({
15 | 		model,
16 | 		/**
17 | 		 * Prompt taken from autoevals:
18 | 		 *
19 | 		 * {@link https://github.com/braintrustdata/autoevals/blob/5aa20a0a9eb8fc9e07e9e5722ebf71c68d082f32/templates/factuality.yaml}
20 | 		 */
21 | 		prompt: `
22 |         You are comparing a submitted answer to an expert's rubric on a given question. Here is the data:
23 |         [BEGIN DATA]
24 |         ************
25 |         [Question]: ${input}
26 |         ************
27 |         [Expert Rubric]: ${expected}
28 |         ************
29 |         [Submission]: ${output}
30 |         ************
31 |         [END DATA]
32 | 
33 | 		Submissions contain message metadata inside of the <message_content> XML tags. 
34 | 		The attribute \`type=text\` indicates text content. The attribute \`type=tool-call\` indicates a tool call. 
35 | 		Use this metadata to determine the accuracy of the response.
36 |   
37 |         Compare the factual content of the submitted answer with the expert's answer rubric. Ignore any differences in style, grammar, or punctuation.
38 |         The submitted answer may either be a subset or superset of the expert's expected answer, or it may conflict with it. Determine which case applies. Answer the question by selecting one of the following options:
39 |         (A) The submitted answer is a subset of the answer the expert's rubric describes and is fully consistent with it.
40 |         (B) The submitted answer is a superset of the answer the expert's rubric describes and is fully consistent with it.
41 |         (C) The submitted answer contains all the same details of the answer the expert's rubric describes.
42 |         (D) There is a disagreement between the submitted answer and the expert's rubric.
43 |         (E) The answers differ, but these differences don't matter from the perspective of factuality.
44 |       `,
45 | 		schema: z.object({
46 | 			answer: z.enum(['A', 'B', 'C', 'D', 'E']).describe('Your selection.'),
47 | 			rationale: z.string().describe('Why you chose this answer. Be very detailed.'),
48 | 		}),
49 | 	})
50 | 
51 | 	/**
52 | 	 * LLM's are well documented at being poor at generating
53 | 	 */
54 | 	const scores = {
55 | 		A: 0.4,
56 | 		B: 1,
57 | 		C: 1,
58 | 		D: 0,
59 | 		E: 1,
60 | 	}
61 | 
62 | 	return {
63 | 		score: scores[object.answer],
64 | 		metadata: {
65 | 			rationale: object.rationale,
66 | 		},
67 | 	}
68 | }
69 | 
```

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

```typescript
 1 | import { type EmbeddedResource } from '@modelcontextprotocol/sdk/types.js'
 2 | import mime from 'mime'
 3 | import { z } from 'zod'
 4 | 
 5 | import type { CloudflareDocumentationMCP } from '../docs-autorag.app'
 6 | 
 7 | /**
 8 |  * Registers the docs search tool with the MCP server
 9 |  * @param agent The MCP server instance
10 |  */
11 | export function registerDocsTools(agent: CloudflareDocumentationMCP) {
12 | 	// Register the worker logs analysis tool by worker name
13 | 	agent.server.tool(
14 | 		'search_cloudflare_documentation',
15 | 		`Search the Cloudflare documentation.
16 | 
17 | 		You should use this tool when:
18 | 		- A user asks questions about Cloudflare products (Workers, Developer Platform, Zero Trust, CDN, etc)
19 | 		- A user requests information about a Cloudflare feature
20 | 		- You are unsure of how to use some Cloudflare functionality
21 | 		- You are writing Cloudflare Workers code and need to look up Workers-specific documentation
22 | 
23 | 		This tool returns a number of results from a vector database. These are embedded as resources in the response and are plaintext documents in a variety of formats.
24 | 		`,
25 | 		{
26 | 			// partially pulled from autorag query optimization example
27 | 			query: z.string().describe(`Search query. The query should:
28 | 1. Identify the core concepts and intent
29 | 2. Add relevant synonyms and related terms
30 | 3. Remove irrelevant filler words
31 | 4. Structure the query to emphasize key terms
32 | 5. Include technical or domain-specific terminology if applicable`),
33 | 			scoreThreshold: z
34 | 				.number()
35 | 				.min(0)
36 | 				.max(1)
37 | 				.optional()
38 | 				.describe('A score threshold (0-1) for which matches should be included.'),
39 | 			maxNumResults: z
40 | 				.number()
41 | 				.default(10)
42 | 				.optional()
43 | 				.describe('The maximum number of results to return.'),
44 | 		},
45 | 		async (params) => {
46 | 			// we don't need "rewrite query" OR aiSearch because an LLM writes the query and formats the output for us.
47 | 			const result = await agent.env.AI.autorag(agent.env.AUTORAG_NAME).search({
48 | 				query: params.query,
49 | 				ranking_options: params.scoreThreshold
50 | 					? {
51 | 							score_threshold: params.scoreThreshold,
52 | 						}
53 | 					: undefined,
54 | 				max_num_results: params.maxNumResults,
55 | 			})
56 | 
57 | 			const resources: EmbeddedResource[] = result.data.map((result) => {
58 | 				const content = result.content.reduce((acc, contentPart) => {
59 | 					return acc + contentPart.text
60 | 				}, '')
61 | 				return {
62 | 					type: 'resource',
63 | 					resource: {
64 | 						uri: `docs://${result.filename}`,
65 | 						mimeType: mime.getType(result.filename) ?? 'text/plain',
66 | 						text: content,
67 | 					},
68 | 				}
69 | 			})
70 | 
71 | 			return {
72 | 				content: resources,
73 | 			}
74 | 		}
75 | 	)
76 | }
77 | 
```

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

```typescript
 1 | import { getCloudflareClient } from '@repo/mcp-common/src/cloudflare-api'
 2 | import { getProps } from '@repo/mcp-common/src/get-props'
 3 | import { pollUntilReady } from '@repo/mcp-common/src/poll'
 4 | 
 5 | import { CreateScanResult, UrlParam } from '../types/url-scanner'
 6 | 
 7 | import type { RadarMCP } from '../radar.app'
 8 | 
 9 | const MAX_WAIT_SECONDS = 30
10 | const INTERVAL_SECONDS = 2
11 | 
12 | export function registerUrlScannerTools(agent: RadarMCP) {
13 | 	agent.server.tool(
14 | 		'scan_url',
15 | 		'Submit a URL to scan',
16 | 		{
17 | 			url: UrlParam,
18 | 		},
19 | 		async ({ url }) => {
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 | 
32 | 			try {
33 | 				const props = getProps(agent)
34 | 				const client = getCloudflareClient(props.accessToken)
35 | 
36 | 				// Search if there are recent scans for the URL
37 | 				const scans = await client.urlScanner.scans.list({
38 | 					account_id: accountId,
39 | 					q: `page.url:"${url}"`,
40 | 				})
41 | 
42 | 				let scanId = scans.results.length > 0 ? scans.results[0]._id : null
43 | 
44 | 				if (!scanId) {
45 | 					// Submit scan
46 | 					// TODO theres an issue (reported) with this method in the cloudflare TS lib
47 | 					// const scan = await (client.urlScanner.scans.create({ account_id, url: "https://www.example.com" }, { headers })).withResponse()
48 | 
49 | 					const res = await fetch(
50 | 						`https://api.cloudflare.com/client/v4/accounts/${accountId}/urlscanner/v2/scan`,
51 | 						{
52 | 							method: 'POST',
53 | 							headers: {
54 | 								Authorization: `Bearer ${props.accessToken}`,
55 | 							},
56 | 							body: JSON.stringify({ url }),
57 | 						}
58 | 					)
59 | 
60 | 					if (!res.ok) {
61 | 						throw new Error('Failed to submit scan')
62 | 					}
63 | 
64 | 					const scan = CreateScanResult.parse(await res.json())
65 | 					scanId = scan?.uuid
66 | 				}
67 | 
68 | 				const r = await pollUntilReady({
69 | 					taskFn: () => client.urlScanner.scans.get(scanId, { account_id: accountId }),
70 | 					intervalSeconds: INTERVAL_SECONDS,
71 | 					maxWaitSeconds: MAX_WAIT_SECONDS,
72 | 				})
73 | 
74 | 				return {
75 | 					content: [
76 | 						{
77 | 							type: 'text',
78 | 							text: JSON.stringify({
79 | 								result: { verdicts: r.verdicts, stats: r.stats, page: r.page }, // TODO select what is more relevant, or add a param to allow the agent to select a set of metrics
80 | 							}),
81 | 						},
82 | 					],
83 | 				}
84 | 			} catch (error) {
85 | 				return {
86 | 					content: [
87 | 						{
88 | 							type: 'text',
89 | 							text: `Error scanning URL: ${error instanceof Error && error.message}`,
90 | 						},
91 | 					],
92 | 				}
93 | 			}
94 | 		}
95 | 	)
96 | }
97 | 
```

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

```
1 | <svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 651.29 94.76"><defs><style>.cls-1{fill:#ffffff;}.cls-2{fill:#ffffff;}</style></defs><path class="cls-1" d="M143.05,93.42l1.07-3.71c1.27-4.41.8-8.48-1.34-11.48-2-2.76-5.26-4.38-9.25-4.57L58,72.7a1.47,1.47,0,0,1-1.35-2,2,2,0,0,1,1.75-1.34l76.26-1c9-.41,18.84-7.75,22.27-16.71l4.34-11.36a2.68,2.68,0,0,0,.18-1,3.31,3.31,0,0,0-.06-.54,49.67,49.67,0,0,0-95.49-5.14,22.35,22.35,0,0,0-35,23.42A31.73,31.73,0,0,0,.34,93.45a1.47,1.47,0,0,0,1.45,1.27l139.49,0h0A1.83,1.83,0,0,0,143.05,93.42Z"/><path class="cls-2" d="M168.22,41.15q-1,0-2.1.06a.88.88,0,0,0-.32.07,1.17,1.17,0,0,0-.76.8l-3,10.26c-1.28,4.41-.81,8.48,1.34,11.48a11.65,11.65,0,0,0,9.24,4.57l16.11,1a1.44,1.44,0,0,1,1.14.62,1.5,1.5,0,0,1,.17,1.37,2,2,0,0,1-1.75,1.34l-16.73,1c-9.09.42-18.88,7.75-22.31,16.7l-1.21,3.16a.9.9,0,0,0,.79,1.22h57.63A1.55,1.55,0,0,0,208,93.63a41.34,41.34,0,0,0-39.76-52.48Z"/><polygon points="273.03 59.66 282.56 59.66 282.56 85.72 299.23 85.72 299.23 94.07 273.03 94.07 273.03 59.66"/><path d="M309.11,77v-.09c0-9.88,8-17.9,18.58-17.9s18.48,7.92,18.48,17.8v.1c0,9.88-8,17.89-18.58,17.89S309.11,86.85,309.11,77m27.33,0v-.09c0-5-3.59-9.29-8.85-9.29s-8.7,4.22-8.7,9.19v.1c0,5,3.59,9.29,8.8,9.29s8.75-4.23,8.75-9.2"/><path d="M357.84,79V59.66h9.69V78.78c0,5,2.5,7.33,6.34,7.33s6.34-2.26,6.34-7.08V59.66h9.68V78.73c0,11.11-6.34,16-16.12,16s-15.93-5-15.93-15.73"/><path d="M404.49,59.66h13.27c12.29,0,19.42,7.08,19.42,17v.1c0,9.93-7.23,17.3-19.61,17.3H404.49Zm13.42,26c5.7,0,9.49-3.15,9.49-8.71v-.09c0-5.51-3.79-8.71-9.49-8.71H414V85.62Z"/><polygon points="451.04 59.66 478.56 59.66 478.56 68.02 460.58 68.02 460.58 73.87 476.85 73.87 476.85 81.78 460.58 81.78 460.58 94.07 451.04 94.07 451.04 59.66"/><polygon points="491.84 59.66 501.37 59.66 501.37 85.72 518.04 85.72 518.04 94.07 491.84 94.07 491.84 59.66"/><path d="M543,59.42h9.19L566.8,94.07H556.58l-2.51-6.14H540.79l-2.45,6.14h-10Zm8.35,21.08-3.83-9.78L543.6,80.5Z"/><path d="M579.08,59.66h16.27c5.27,0,8.9,1.38,11.21,3.74a10.64,10.64,0,0,1,3.05,8v.1a10.88,10.88,0,0,1-7.08,10.57l8.21,12h-11L592.8,83.65h-4.18V94.07h-9.54Zm15.83,16.52c3.25,0,5.12-1.58,5.12-4.08V72c0-2.71-2-4.08-5.17-4.08h-6.24v8.26Z"/><polygon points="623.37 59.66 651.05 59.66 651.05 67.77 632.81 67.77 632.81 72.98 649.33 72.98 649.33 80.5 632.81 80.5 632.81 85.96 651.29 85.96 651.29 94.07 623.37 94.07 623.37 59.66"/><path d="M252.15,81a8.44,8.44,0,0,1-7.88,5.16c-5.22,0-8.8-4.33-8.8-9.29v-.1c0-5,3.49-9.2,8.7-9.2a8.64,8.64,0,0,1,8.18,5.71h10C260.79,65.09,253.6,59,244.27,59c-10.62,0-18.58,8-18.58,17.9V77c0,9.88,7.86,17.8,18.48,17.8,9.08,0,16.18-5.88,18.05-13.76Z"/></svg>
```

--------------------------------------------------------------------------------
/apps/sandbox-container/evals/files.eval.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { assert, expect } from 'vitest'
  2 | import { describeEval } from 'vitest-evals'
  3 | import { z } from 'zod'
  4 | 
  5 | import { runTask } from '@repo/eval-tools/src/runTask'
  6 | import { checkFactuality } from '@repo/eval-tools/src/scorers'
  7 | import { eachModel } from '@repo/eval-tools/src/test-models'
  8 | 
  9 | import { initializeClient } from './utils'
 10 | 
 11 | eachModel('$modelName', ({ model }) => {
 12 | 	describeEval('Runs container file write', {
 13 | 		data: async () => [
 14 | 			{
 15 | 				input: 'write a file named test.txt containing the text "asdf"',
 16 | 				expected: 'The container_file_write tool was called and the file\'s content is "asdf"',
 17 | 			},
 18 | 		],
 19 | 		task: async (input) => {
 20 | 			const client = await initializeClient()
 21 | 			const { promptOutput } = await runTask(client, model, input)
 22 | 			const fileRead = client.listTools().find((tool) => {
 23 | 				if (tool.name === 'container_file_read') {
 24 | 					return tool
 25 | 				}
 26 | 			})
 27 | 
 28 | 			assert(fileRead !== undefined)
 29 | 			const result = await client.callTool(
 30 | 				{
 31 | 					...fileRead,
 32 | 					arguments: {
 33 | 						args: { path: 'file://test.txt' },
 34 | 					},
 35 | 				},
 36 | 				z.any() as any,
 37 | 				{}
 38 | 			)
 39 | 
 40 | 			expect(result.content).toStrictEqual([
 41 | 				{
 42 | 					type: 'resource',
 43 | 					resource: {
 44 | 						uri: 'file://test.txt',
 45 | 						mimeType: 'text/plain',
 46 | 						text: 'asdf',
 47 | 					},
 48 | 				},
 49 | 			])
 50 | 
 51 | 			return promptOutput
 52 | 		},
 53 | 		scorers: [checkFactuality],
 54 | 		threshold: 1,
 55 | 		timeout: 60000,
 56 | 	})
 57 | 
 58 | 	describeEval('Runs container file delete', {
 59 | 		data: async () => [
 60 | 			{
 61 | 				input: 'write a file named test.txt, then delete it',
 62 | 				expected:
 63 | 					'The container_file_write tool was called and then the container_file_delete tool was called with the same parameters',
 64 | 			},
 65 | 		],
 66 | 		task: async (input) => {
 67 | 			const client = await initializeClient()
 68 | 			const { promptOutput, toolCalls } = await runTask(client, model, input)
 69 | 
 70 | 			const toolArgs = toolCalls.find((tool) => {
 71 | 				return tool.toolName === 'container_file_write' ? tool : undefined
 72 | 			})?.args as { args: { path: string } } | undefined
 73 | 
 74 | 			assert(toolArgs !== undefined)
 75 | 			expect(toolCalls).toEqual(
 76 | 				expect.arrayContaining([
 77 | 					expect.objectContaining({
 78 | 						type: 'tool-call',
 79 | 						toolName: 'container_file_write',
 80 | 						args: {
 81 | 							args: expect.objectContaining({
 82 | 								path: toolArgs.args.path,
 83 | 							}),
 84 | 						},
 85 | 					}),
 86 | 				])
 87 | 			)
 88 | 
 89 | 			expect(toolCalls).toEqual(
 90 | 				expect.arrayContaining([
 91 | 					expect.objectContaining({
 92 | 						type: 'tool-call',
 93 | 						toolName: 'container_file_delete',
 94 | 						args: {
 95 | 							args: expect.objectContaining({
 96 | 								path: toolArgs.args.path,
 97 | 							}),
 98 | 						},
 99 | 					}),
100 | 				])
101 | 			)
102 | 
103 | 			return promptOutput
104 | 		},
105 | 		scorers: [checkFactuality],
106 | 		threshold: 1,
107 | 		timeout: 60000,
108 | 	})
109 | })
110 | 
```

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

```typescript
  1 | import { z } from 'zod'
  2 | 
  3 | import { handleAccountsList } from '../api/account.api'
  4 | import { getCloudflareClient } from '../cloudflare-api'
  5 | import { getProps } from '../get-props'
  6 | 
  7 | import type { CloudflareMcpAgent } from '../types/cloudflare-mcp-agent.types'
  8 | 
  9 | export function registerAccountTools(agent: CloudflareMcpAgent) {
 10 | 	// Tool to list all accounts
 11 | 	agent.server.tool(
 12 | 		'accounts_list',
 13 | 		'List all accounts in your Cloudflare account',
 14 | 		{},
 15 | 		{
 16 | 			title: 'List accounts',
 17 | 			annotations: {
 18 | 				readOnlyHint: true,
 19 | 			},
 20 | 		},
 21 | 		async () => {
 22 | 			try {
 23 | 				const props = getProps(agent)
 24 | 				const results = await handleAccountsList({
 25 | 					client: getCloudflareClient(props.accessToken),
 26 | 				})
 27 | 				// Sort accounts by created_on date (newest first)
 28 | 				const accounts = results
 29 | 					// order by created_on desc ( newest first )
 30 | 					.sort((a, b) => {
 31 | 						if (!a.created_on) return 1
 32 | 						if (!b.created_on) return -1
 33 | 						return new Date(b.created_on).getTime() - new Date(a.created_on).getTime()
 34 | 					})
 35 | 					// Remove fields not needed by the LLM
 36 | 					.map((account) => {
 37 | 						return {
 38 | 							id: account.id,
 39 | 							name: account.name,
 40 | 							created_on: account.created_on,
 41 | 						}
 42 | 					})
 43 | 
 44 | 				return {
 45 | 					content: [
 46 | 						{
 47 | 							type: 'text',
 48 | 							text: JSON.stringify({
 49 | 								accounts,
 50 | 								count: accounts.length,
 51 | 							}),
 52 | 						},
 53 | 					],
 54 | 				}
 55 | 			} catch (e) {
 56 | 				agent.server.recordError(e)
 57 | 				return {
 58 | 					content: [
 59 | 						{
 60 | 							type: 'text',
 61 | 							text: `Error listing accounts: ${e instanceof Error && e.message}`,
 62 | 						},
 63 | 					],
 64 | 				}
 65 | 			}
 66 | 		}
 67 | 	)
 68 | 
 69 | 	// Only register set_active_account tool when user token is provided, as it doesn't make sense to expose
 70 | 	// this tool for account scoped tokens, given that they're scoped to a single account
 71 | 	if (getProps(agent).type === 'user_token') {
 72 | 		const activeAccountIdParam = z
 73 | 			.string()
 74 | 			.describe(
 75 | 				'The accountId present in the users Cloudflare account, that should be the active accountId.'
 76 | 			)
 77 | 		agent.server.tool(
 78 | 			'set_active_account',
 79 | 			'Set active account to be used for tool calls that require accountId',
 80 | 			{
 81 | 				activeAccountIdParam,
 82 | 			},
 83 | 			{
 84 | 				title: 'Set active account',
 85 | 				annotations: {
 86 | 					readOnlyHint: false,
 87 | 					destructiveHint: false,
 88 | 				},
 89 | 			},
 90 | 			async (params) => {
 91 | 				try {
 92 | 					const { activeAccountIdParam: activeAccountId } = params
 93 | 					await agent.setActiveAccountId(activeAccountId)
 94 | 					return {
 95 | 						content: [
 96 | 							{
 97 | 								type: 'text',
 98 | 								text: JSON.stringify({
 99 | 									activeAccountId,
100 | 								}),
101 | 							},
102 | 						],
103 | 					}
104 | 				} catch (e) {
105 | 					agent.server.recordError(e)
106 | 					return {
107 | 						content: [
108 | 							{
109 | 								type: 'text',
110 | 								text: `Error setting activeAccountID: ${e instanceof Error && e.message}`,
111 | 							},
112 | 						],
113 | 					}
114 | 				}
115 | 			}
116 | 		)
117 | 	}
118 | }
119 | 
```

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

```typescript
 1 | export const BASE_INSTRUCTIONS = /* markdown */ `
 2 | # Container MCP Agent
 3 | 
 4 | The Container MCP Agent provides access to a sandboxed container environment. This is an ephemeral container and has access to the internet.
 5 | 
 6 | The container is an Ubuntu 20.04 base image with the following packages installed:
 7 | - curl
 8 | - git
 9 | - net-tools
10 | - build-essential
11 | - nodejs
12 | - npm
13 | - python3
14 | - python3-pip
15 | 
16 | If necessary, you may install additional packages.
17 | 
18 | You are given a working directory in which you can create or delete files and execute commands as described below.
19 | 
20 | If you're using python, ALWAYS use \`python3\` instead of \`python\`. ALWAYS make sure to install dependencies, as they won't be installed ahead of time.
21 | 
22 | ## Resources
23 | 
24 | The primary resource in this image is the \`container_files\` resource. 
25 | This is a dynamic resource, which provides a list of files defined by \`file://{filepath}\`, where filepath is relative to the root working directory you are in. 
26 | 
27 | The \`container_files_list\` allows you to list all file resources in your working directory. Content is omitted from the response of this tool.
28 | 
29 | You can read files in the container using the \`container_file_read\` tool. The contents are returned as a text blob with their associated mime type if it is text, 
30 | or a base64 encoded blob for binary files.
31 | 
32 | Directories have the special mime type \`inode/directory\`. If \`container_file_read\` is called on a directory, it returns the contents of the directory as a list of resource URIs.
33 | 
34 | AVOID manually reading or writing files using the \`container_exec\` tool. You should prefer the dedicated file resources and tools to interact with the filesystem as it is less error prone. 
35 | 
36 | ## Tools
37 | 
38 | To manage container lifecycle, use the \`container_initialize\` tool. If you run into errors where you can't connect to the container, attempt to restart the container with the same \`container_initialize\` tool. If that doesn't work, the system is probably overloaded.
39 | 
40 | You can execute actions in the container using the \`container_exec\` tool. By default, stdout is returned back as a string.
41 | To write a file, use the \`container_file_write\` tool. To delete a file, use the \`container_file_delete\` tool.
42 | 
43 | The \`container_files_list\` allows you to list file resources. Content is omitted from the response of this tool and all mimeTypes are \`text/plain\` even if the file ending suggests otherwise.
44 | If you want to get the file contents of a file resource, use \`container_file_read\`, which will return the file contents.
45 | 
46 | If after calling a tool, you receive an error that a cloudchamber instance cannot be provided, just stop attempting to answer and request that the user attempt to try again later.
47 | 
48 | If you run into issues, do not attempt to retry after 3 tries unless the user prompts you to. Instead direct the user to report an issue at: https://github.com/cloudflare/mcp-server-cloudflare
49 | `
50 | 
```

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

```typescript
 1 | import { z } from 'zod'
 2 | 
 3 | export type BuildDetails = z.infer<typeof BuildDetails>
 4 | export const BuildDetails = z.object({
 5 | 	// TODO: Maybe remove fields we don't need to reduce surface area of things we need to update
 6 | 	build_uuid: z.string(),
 7 | 	status: z.string(),
 8 | 	build_outcome: z.string().nullable(),
 9 | 	created_on: z.coerce.date(),
10 | 	modified_on: z.coerce.date(),
11 | 	initializing_on: z.coerce.date().nullable(),
12 | 	running_on: z.coerce.date().nullable(),
13 | 	stopped_on: z.coerce.date().nullable(),
14 | 	trigger: z.object({
15 | 		trigger_uuid: z.string(),
16 | 		external_script_id: z.string(),
17 | 		trigger_name: z.string(),
18 | 		build_command: z.string(),
19 | 		deploy_command: z.string(),
20 | 		root_directory: z.string(),
21 | 		branch_includes: z.array(z.string()),
22 | 		branch_excludes: z.array(z.string()),
23 | 		path_includes: z.array(z.string()),
24 | 		path_excludes: z.array(z.string()),
25 | 		build_caching_enabled: z.boolean(),
26 | 		created_on: z.coerce.date(),
27 | 		modified_on: z.coerce.date(),
28 | 		deleted_on: z.coerce.date().nullable(),
29 | 		repo_connection: z.object({
30 | 			repo_connection_uuid: z.string(),
31 | 			repo_id: z.string(),
32 | 			repo_name: z.string(),
33 | 			provider_type: z.string(),
34 | 			provider_account_id: z.string(),
35 | 			provider_account_name: z.string(),
36 | 			created_on: z.coerce.date(),
37 | 			modified_on: z.coerce.date(),
38 | 			deleted_on: z.coerce.date().nullable(),
39 | 		}),
40 | 	}),
41 | 	build_trigger_metadata: z.object({
42 | 		build_trigger_source: z.string(),
43 | 		branch: z.string(),
44 | 		commit_hash: z.string(),
45 | 		commit_message: z.string(),
46 | 		author: z.string(),
47 | 		build_command: z.string(),
48 | 		deploy_command: z.string(),
49 | 		root_directory: z.string(),
50 | 		build_token_uuid: z.string(),
51 | 		environment_variables: z.record(
52 | 			z.string(),
53 | 			z.object({
54 | 				is_secret: z.boolean(),
55 | 				created_on: z.coerce.date(),
56 | 				value: z.string().nullable(),
57 | 			})
58 | 		),
59 | 		repo_name: z.string(),
60 | 		provider_account_name: z.string(),
61 | 		provider_type: z.string(),
62 | 	}),
63 | 	pull_request: z.unknown(),
64 | })
65 | 
66 | /**
67 |  * GET /builds/workers/:external_script_id/builds
68 |  */
69 | export type ListBuildsByScriptResult = z.infer<typeof ListBuildsByScriptResult>
70 | export const ListBuildsByScriptResult = z.array(BuildDetails)
71 | 
72 | export type ListBuildsByScriptResultInfo = z.infer<typeof ListBuildsByScriptResultInfo>
73 | export const ListBuildsByScriptResultInfo = z.object({
74 | 	next_page: z.boolean(),
75 | 	page: z.number(),
76 | 	per_page: z.number(),
77 | 	count: z.number(),
78 | 	total_count: z.number(),
79 | 	total_pages: z.number(),
80 | })
81 | 
82 | export type GetBuildResult = z.infer<typeof GetBuildResult>
83 | export const GetBuildResult = BuildDetails
84 | 
85 | export type LogLine = z.infer<typeof LogLine>
86 | export const LogLine = z.tuple([
87 | 	z.coerce.date().describe('line timestamp'),
88 | 	z.string().describe('line message'),
89 | ])
90 | 
91 | export type GetBuildLogsResult = z.infer<typeof GetBuildLogsResult>
92 | export const GetBuildLogsResult = z.object({
93 | 	cursor: z.string().optional().describe('pagination cursor'),
94 | 	truncated: z.boolean(),
95 | 	lines: z.array(LogLine),
96 | })
97 | 
```

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

```
 1 | <svg width="498" height="498" viewBox="0 0 498 498" fill="none" xmlns="http://www.w3.org/2000/svg">
 2 | <g filter="url(#filter0_d_1171_29)">
 3 | <rect x="5" y="1" width="488" height="488" rx="77" stroke="white" stroke-width="2" shape-rendering="crispEdges"/>
 4 | </g>
 5 | <g filter="url(#filter1_d_1171_29)">
 6 | <path d="M254.68 96.082C281.995 96.0821 308.964 100.336 336.251 111.109V187.169C311.044 173.904 279.661 166.483 254.68 166.482C245.797 166.482 238.504 167.735 233.402 170.75C228.213 173.817 225.379 178.659 225.379 185.499C225.379 190.498 227.517 194.558 231.143 197.993C234.74 201.401 239.824 204.219 245.808 206.81C251.8 209.404 258.78 211.804 266.211 214.338C273.655 216.876 281.566 219.553 289.477 222.711C305.304 229.03 321.028 237.241 332.817 249.986C344.395 262.503 352.235 279.455 352.556 303.463L352.565 304.611C352.565 334.175 340.793 357.4 320.487 373.259C300.152 389.14 271.174 397.695 236.7 397.695C208.383 397.695 177.442 392.12 146.805 379.016V301.951C174.624 316.941 209.332 327.962 236.7 327.962C246.077 327.962 254.276 326.71 260.162 323.414C263.121 321.757 265.51 319.575 267.153 316.771C268.797 313.966 269.663 310.595 269.663 306.61C269.663 296.03 261.196 289.259 249.324 283.608C243.345 280.763 236.384 278.146 228.973 275.449C221.547 272.748 213.664 269.965 205.778 266.772C190.001 260.386 174.342 252.41 162.628 240.368C150.947 228.36 143.143 212.261 143.143 189.502C143.143 160.251 154.263 136.938 173.701 120.919C193.162 104.881 221.05 96.082 254.68 96.082Z" stroke="white" stroke-width="2" shape-rendering="crispEdges"/>
 7 | </g>
 8 | <defs>
 9 | <filter id="filter0_d_1171_29" x="0" y="0" width="498" height="498" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
10 | <feFlood flood-opacity="0" result="BackgroundImageFix"/>
11 | <feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
12 | <feOffset dy="4"/>
13 | <feGaussianBlur stdDeviation="2"/>
14 | <feComposite in2="hardAlpha" operator="out"/>
15 | <feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
16 | <feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1171_29"/>
17 | <feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1171_29" result="shape"/>
18 | </filter>
19 | <filter id="filter1_d_1171_29" x="138.143" y="95.082" width="219.422" height="311.613" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
20 | <feFlood flood-opacity="0" result="BackgroundImageFix"/>
21 | <feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
22 | <feOffset dy="4"/>
23 | <feGaussianBlur stdDeviation="2"/>
24 | <feComposite in2="hardAlpha" operator="out"/>
25 | <feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
26 | <feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1171_29"/>
27 | <feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1171_29" result="shape"/>
28 | </filter>
29 | </defs>
30 | </svg>
31 | 
```

--------------------------------------------------------------------------------
/packages/eval-tools/src/test-models.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { createAnthropic } from '@ai-sdk/anthropic'
 2 | import { AnthropicMessagesModelId } from '@ai-sdk/anthropic/internal'
 3 | import { createGoogleGenerativeAI } from '@ai-sdk/google'
 4 | import { GoogleGenerativeAILanguageModel } from '@ai-sdk/google/internal'
 5 | import { createOpenAI } from '@ai-sdk/openai'
 6 | import { OpenAIChatModelId } from '@ai-sdk/openai/internal'
 7 | import { createAiGateway } from 'ai-gateway-provider'
 8 | import { env } from 'cloudflare:test'
 9 | import { describe } from 'vitest'
10 | import { createWorkersAI } from 'workers-ai-provider'
11 | 
12 | export const factualityModel = getOpenAiModel('gpt-4o')
13 | 
14 | type value2key<T, V> = {
15 | 	[K in keyof T]: T[K] extends V ? K : never
16 | }[keyof T]
17 | type AiTextGenerationModels = Exclude<
18 | 	value2key<AiModels, BaseAiTextGeneration>,
19 | 	value2key<AiModels, BaseAiTextToImage>
20 | >
21 | 
22 | function getOpenAiModel(modelName: OpenAIChatModelId) {
23 | 	if (!env.CLOUDFLARE_ACCOUNT_ID || !env.AI_GATEWAY_ID || !env.AI_GATEWAY_TOKEN) {
24 | 		throw new Error('No AI gateway credentials set!')
25 | 	}
26 | 
27 | 	const aigateway = createAiGateway({
28 | 		accountId: env.CLOUDFLARE_ACCOUNT_ID,
29 | 		gateway: env.AI_GATEWAY_ID,
30 | 		apiKey: env.AI_GATEWAY_TOKEN,
31 | 	})
32 | 
33 | 	const ai = createOpenAI({
34 | 		apiKey: '',
35 | 	})
36 | 
37 | 	const model = aigateway([ai(modelName)])
38 | 
39 | 	return { modelName, model, ai }
40 | }
41 | 
42 | function getAnthropicModel(modelName: AnthropicMessagesModelId) {
43 | 	const aigateway = createAiGateway({
44 | 		accountId: env.CLOUDFLARE_ACCOUNT_ID,
45 | 		gateway: env.AI_GATEWAY_ID,
46 | 		apiKey: env.AI_GATEWAY_TOKEN,
47 | 	})
48 | 
49 | 	const ai = createAnthropic({
50 | 		apiKey: '',
51 | 	})
52 | 
53 | 	const model = aigateway([ai(modelName)])
54 | 
55 | 	return { modelName, model, ai }
56 | }
57 | 
58 | function getGeminiModel(modelName: GoogleGenerativeAILanguageModel['modelId']) {
59 | 	if (!env.CLOUDFLARE_ACCOUNT_ID || !env.AI_GATEWAY_ID || !env.AI_GATEWAY_TOKEN) {
60 | 		throw new Error('No AI gateway credentials set!')
61 | 	}
62 | 
63 | 	const aigateway = createAiGateway({
64 | 		accountId: env.CLOUDFLARE_ACCOUNT_ID,
65 | 		gateway: env.AI_GATEWAY_ID,
66 | 		apiKey: env.AI_GATEWAY_TOKEN,
67 | 	})
68 | 
69 | 	const ai = createGoogleGenerativeAI({ apiKey: '' })
70 | 
71 | 	const model = aigateway([ai(modelName)])
72 | 
73 | 	return { modelName, model, ai }
74 | }
75 | 
76 | function getWorkersAiModel(modelName: AiTextGenerationModels) {
77 | 	if (!env.AI) {
78 | 		throw new Error('No AI binding provided!')
79 | 	}
80 | 
81 | 	const ai = createWorkersAI({ binding: env.AI })
82 | 
83 | 	const model = ai(modelName)
84 | 	return { modelName, model, ai }
85 | }
86 | 
87 | export const eachModel = describe.each([
88 | 	getOpenAiModel('gpt-4o'),
89 | 	getOpenAiModel('gpt-4o-mini'),
90 | 	// getAnthropicModel('claude-3-5-sonnet-20241022'), TODO: The evals pass with anthropic, but our rate limit is so low with AI wholesaling that we can't use it in CI because it's impossible to get a complete run with the current limits
91 | 	getGeminiModel('gemini-2.0-flash'),
92 | 	// llama 3 is somewhat inconsistent
93 | 	//getWorkersAiModel("@cf/meta/llama-3.3-70b-instruct-fp8-fast")
94 | 	// Currently llama 4 is having issues with tool calling
95 | 	//getWorkersAiModel("@cf/meta/llama-4-scout-17b-16e-instruct")
96 | ])
97 | 
```

--------------------------------------------------------------------------------
/packages/mcp-common/src/types/cf1-integrations.types.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { z } from 'zod'
  2 | 
  3 | const Vendor = z.object({
  4 | 	id: z.string(),
  5 | 	name: z.string(),
  6 | 	display_name: z.string(),
  7 | 	description: z.string().nullable(),
  8 | 	logo: z.string().nullable(),
  9 | 	static_logo: z.string().nullable(),
 10 | })
 11 | 
 12 | const Policy = z.object({
 13 | 	id: z.string(),
 14 | 	name: z.string(),
 15 | 	permissions: z.array(z.string()),
 16 | 	link: z.string().nullable(),
 17 | 	dlp_enabled: z.boolean(),
 18 | })
 19 | 
 20 | // Base Integration schema
 21 | export const Integration = z.object({
 22 | 	id: z.string(),
 23 | 	name: z.string(),
 24 | 	status: z.enum(['Healthy', 'Unhealthy', 'Initializing', 'Paused']),
 25 | 	upgradable: z.boolean(),
 26 | 	permissions: z.array(z.string()),
 27 | 
 28 | 	vendor: Vendor,
 29 | 	policy: Policy,
 30 | 
 31 | 	created: z.string(),
 32 | 	updated: z.string(),
 33 | 	credentials_expiry: z.string().nullable(),
 34 | 	last_hydrated: z.string().nullable(),
 35 | })
 36 | 
 37 | // Schema for output: a single integration
 38 | export const IntegrationResponse = Integration
 39 | export type zReturnedIntegrationResult = z.infer<typeof IntegrationResponse>
 40 | 
 41 | // Schema for output: multiple integrations
 42 | export const IntegrationsResponse = z.array(Integration)
 43 | export type zReturnedIntegrationsResult = z.infer<typeof IntegrationsResponse>
 44 | 
 45 | export const AssetCategory = z.object({
 46 | 	id: z.string().uuid(),
 47 | 	type: z.string(),
 48 | 	vendor: z.string(),
 49 | 	service: z.string().nullable(),
 50 | })
 51 | 
 52 | export const AssetDetail = z.object({
 53 | 	id: z.string().uuid(),
 54 | 	external_id: z.string(),
 55 | 	name: z.string(),
 56 | 	link: z.string().nullable(),
 57 | 	fields: z.array(
 58 | 		z.object({
 59 | 			link: z.string().nullable(),
 60 | 			name: z.string(),
 61 | 			value: z.any(),
 62 | 		})
 63 | 	),
 64 | 	category: AssetCategory,
 65 | 	integration: Integration,
 66 | })
 67 | 
 68 | export type zReturnedAssetResult = z.infer<typeof AssetDetail>
 69 | 
 70 | export const AssetsResponse = z.array(AssetDetail)
 71 | export type zReturnedAssetsResult = z.infer<typeof AssetsResponse>
 72 | 
 73 | export const AssetCategoriesResponse = z.array(AssetCategory)
 74 | export type zReturnedAssetCategoriesResult = z.infer<typeof AssetCategoriesResponse>
 75 | 
 76 | export const assetCategoryTypeParam = z
 77 | 	.enum([
 78 | 		'Account',
 79 | 		'Alert',
 80 | 		'App',
 81 | 		'Authentication Method',
 82 | 		'Bucket',
 83 | 		'Bucket Iam Permission',
 84 | 		'Bucket Permission',
 85 | 		'Calendar',
 86 | 		'Certificate',
 87 | 		'Channel',
 88 | 		'Commit',
 89 | 		'Content',
 90 | 		'Credential',
 91 | 		'Domain',
 92 | 		'Drive',
 93 | 		'Environment',
 94 | 		'Factor',
 95 | 		'File',
 96 | 		'File Permission',
 97 | 		'Folder',
 98 | 		'Group',
 99 | 		'Incident',
100 | 		'Instance',
101 | 		'Issue',
102 | 		'Label',
103 | 		'Meeting',
104 | 		'Message',
105 | 		'Message Rule',
106 | 		'Namespace',
107 | 		'Organization',
108 | 		'Package',
109 | 		'Pipeline',
110 | 		'Project',
111 | 		'Report',
112 | 		'Repository',
113 | 		'Risky User',
114 | 		'Role',
115 | 		'Server',
116 | 		'Site',
117 | 		'Space',
118 | 		'Submodule',
119 | 		'Third Party User',
120 | 		'User',
121 | 		'User No Mfa',
122 | 		'Variable',
123 | 		'Webhook',
124 | 		'Workspace',
125 | 	])
126 | 	.optional()
127 | 	.describe('Type of cloud resource or service category')
128 | 
129 | export const assetCategoryVendorParam = z
130 | 	.enum([
131 | 		'AWS',
132 | 		'Bitbucket',
133 | 		'Box',
134 | 		'Confluence',
135 | 		'Dropbox',
136 | 		'GitHub',
137 | 		'Google Cloud Platform',
138 | 		'Google Workspace',
139 | 		'Jira',
140 | 		'Microsoft',
141 | 		'Microsoft Azure',
142 | 		'Okta',
143 | 		'Salesforce',
144 | 		'ServiceNow',
145 | 		'Slack',
146 | 		'Workday',
147 | 		'Zoom',
148 | 	])
149 | 	.describe('Vendor of the cloud service or resource')
150 | 
```

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

```typescript
  1 | import { isPromise } from 'node:util/types'
  2 | import { type ServerOptions } from '@modelcontextprotocol/sdk/server/index.js'
  3 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
  4 | import { type ZodRawShape } from 'zod'
  5 | 
  6 | import { MetricsTracker, SessionStart, ToolCall } from '../../mcp-observability/src'
  7 | import { McpError } from './mcp-error'
  8 | 
  9 | import type { ToolCallback } from '@modelcontextprotocol/sdk/server/mcp.js'
 10 | import type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js'
 11 | import type { ServerNotification, ServerRequest } from '@modelcontextprotocol/sdk/types.js'
 12 | import type { SentryClient } from './sentry'
 13 | 
 14 | export class CloudflareMCPServer extends McpServer {
 15 | 	private metrics
 16 | 	private sentry?: SentryClient
 17 | 
 18 | 	constructor({
 19 | 		userId,
 20 | 		wae,
 21 | 		serverInfo,
 22 | 		options,
 23 | 		sentry,
 24 | 	}: {
 25 | 		userId?: string
 26 | 		wae: AnalyticsEngineDataset
 27 | 		serverInfo: {
 28 | 			[x: string]: unknown
 29 | 			name: string
 30 | 			version: string
 31 | 		}
 32 | 		options?: ServerOptions
 33 | 		sentry?: SentryClient
 34 | 	}) {
 35 | 		super(serverInfo, options)
 36 | 		this.metrics = new MetricsTracker(wae, serverInfo)
 37 | 		this.sentry = sentry
 38 | 
 39 | 		this.server.oninitialized = () => {
 40 | 			const clientInfo = this.server.getClientVersion()
 41 | 			const clientCapabilities = this.server.getClientCapabilities()
 42 | 			this.metrics.logEvent(
 43 | 				new SessionStart({
 44 | 					userId,
 45 | 					clientInfo,
 46 | 					clientCapabilities,
 47 | 				})
 48 | 			)
 49 | 		}
 50 | 
 51 | 		this.server.onerror = (e) => {
 52 | 			this.recordError(e)
 53 | 		}
 54 | 
 55 | 		const _tool = this.tool.bind(this)
 56 | 		this.tool = (name: string, ...rest: unknown[]): ReturnType<typeof this.tool> => {
 57 | 			const toolCb = rest[rest.length - 1] as ToolCallback<ZodRawShape | undefined>
 58 | 			const replacementToolCb: ToolCallback<ZodRawShape | undefined> = (arg1, arg2) => {
 59 | 				const toolCall = toolCb(
 60 | 					arg1 as { [x: string]: any } & RequestHandlerExtra<ServerRequest, ServerNotification>,
 61 | 					arg2
 62 | 				)
 63 | 				// There are 4 cases to track:
 64 | 				try {
 65 | 					if (isPromise(toolCall)) {
 66 | 						return toolCall
 67 | 							.then((r: any) => {
 68 | 								// promise succeeds
 69 | 								this.metrics.logEvent(
 70 | 									new ToolCall({
 71 | 										toolName: name,
 72 | 										userId,
 73 | 									})
 74 | 								)
 75 | 								return r
 76 | 							})
 77 | 							.catch((e: unknown) => {
 78 | 								// promise throws
 79 | 								this.trackToolCallError(e, name, userId)
 80 | 								throw e
 81 | 							})
 82 | 					} else {
 83 | 						// non-promise succeeds
 84 | 						this.metrics.logEvent(
 85 | 							new ToolCall({
 86 | 								toolName: name,
 87 | 								userId,
 88 | 							})
 89 | 						)
 90 | 						return toolCall
 91 | 					}
 92 | 				} catch (e: unknown) {
 93 | 					// non-promise throws
 94 | 					this.trackToolCallError(e, name, userId)
 95 | 					throw e
 96 | 				}
 97 | 			}
 98 | 			rest[rest.length - 1] = replacementToolCb
 99 | 
100 | 			// @ts-ignore
101 | 			return _tool(name, ...rest)
102 | 		}
103 | 	}
104 | 
105 | 	private trackToolCallError(e: unknown, toolName: string, userId?: string) {
106 | 		// placeholder error code
107 | 		let errorCode = -1
108 | 		if (e instanceof McpError) {
109 | 			errorCode = e.code
110 | 		}
111 | 		this.metrics.logEvent(
112 | 			new ToolCall({
113 | 				toolName,
114 | 				userId: userId,
115 | 				errorCode: errorCode,
116 | 			})
117 | 		)
118 | 	}
119 | 
120 | 	public recordError(e: unknown) {
121 | 		this.sentry?.recordError(e)
122 | 	}
123 | }
124 | 
```

--------------------------------------------------------------------------------
/packages/mcp-common/src/durable-kv-store.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import type { ZodSchema } from 'zod'
  2 | 
  3 | export type DurableKVStorageKeys = { [key: string]: ZodSchema }
  4 | 
  5 | /**
  6 |  * DurableKVStore is a type-safe key/value store backed by Durable Object storage.
  7 |  *
  8 |  * @example
  9 |  *
 10 |  * ```ts
 11 |  * export class MyDurableObject extends DurableObject<Bindings> {
 12 |  * 	readonly kv
 13 |  * 	constructor(
 14 |  * 		readonly state: DurableObjectState,
 15 |  * 		env: Bindings
 16 |  * 	) {
 17 |  * 		super(state, env)
 18 |  * 		this.kv = new DurableKVStore({
 19 |  * 			state,
 20 |  * 			prefix: 'meta',
 21 |  * 			keys: {
 22 |  * 				// Each key has a matching Zod schema enforcing what's stored
 23 |  * 				date_key: z.coerce.date(),
 24 |  * 				// While empty keys will always return null, adding
 25 |  * 				// `nullable()` allows us to explicitly set it to null
 26 |  * 				string_key: z.string().nullable(),
 27 |  * 				number_key: z.number(),
 28 |  * 			} as const satisfies StorageKeys,
 29 |  * 		})
 30 |  * 	}
 31 |  *
 32 |  * 	async example(): Promise<void> {
 33 |  * 		await this.kv.get('number_key') // -> null
 34 |  * 		this.kv.put('number_key', 5)
 35 |  * 		await this.kv.get('number_key') // -> 5
 36 |  * 	}
 37 |  * }
 38 |  *	```
 39 |  */
 40 | export class DurableKVStore<T extends DurableKVStorageKeys> {
 41 | 	private readonly prefix: string
 42 | 	private readonly keys: T
 43 | 	private readonly state: DurableObjectState
 44 | 
 45 | 	constructor({ state, prefix, keys }: { state: DurableObjectState; prefix: string; keys: T }) {
 46 | 		this.state = state
 47 | 		this.prefix = prefix
 48 | 		this.keys = keys
 49 | 	}
 50 | 
 51 | 	/** Add the prefix to a key (used for get/put operations) */
 52 | 	private addPrefix<K extends keyof T>(key: K): string {
 53 | 		if (this.prefix.length > 0) {
 54 | 			return `${this.prefix}/${key.toString()}`
 55 | 		}
 56 | 		return key.toString()
 57 | 	}
 58 | 
 59 | 	/**
 60 | 	 * Get a value from KV storage. Returns `null` if the value
 61 | 	 * is not set (or if it's explicitly set to `null`)
 62 | 	 */
 63 | 	async get<K extends keyof T>(key: K): Promise<T[K]['_output'] | null>
 64 | 	/**
 65 | 	 * Get a value from KV storage or return the provided
 66 | 	 * default if they value in storage is unset (undefined).
 67 | 	 * The default value must match the schema for the given key.
 68 | 	 *
 69 | 	 * If defaultValue is explicitly set to undefined, it will still return null (avoid this).
 70 | 	 *
 71 | 	 * If the value in storage is null then this will return null instead of the default.
 72 | 	 */
 73 | 	async get<K extends keyof T>(key: K, defaultValue: T[K]['_output']): Promise<T[K]['_output']>
 74 | 	async get<K extends keyof T>(
 75 | 		key: K,
 76 | 		defaultValue?: T[K]['_output']
 77 | 	): Promise<T[K]['_output'] | null> {
 78 | 		const schema = this.keys[key]
 79 | 		if (schema === undefined) {
 80 | 			throw new TypeError(`key ${key.toString()} has no matching schema`)
 81 | 		}
 82 | 
 83 | 		const res = await this.state.storage.get(this.addPrefix(key))
 84 | 		if (res === undefined) {
 85 | 			if (defaultValue !== undefined) {
 86 | 				return schema.parse(defaultValue)
 87 | 			}
 88 | 			return null
 89 | 		}
 90 | 
 91 | 		return schema.parse(res)
 92 | 	}
 93 | 
 94 | 	/** Write value to KV storage */
 95 | 	put<K extends keyof T>(key: K, value: T[K]['_input']): void {
 96 | 		const schema = this.keys[key]
 97 | 		if (schema === undefined) {
 98 | 			throw new TypeError(`key ${key.toString()} has no matching schema`)
 99 | 		}
100 | 		const parsedValue = schema.parse(value)
101 | 		void this.state.storage.put(this.addPrefix(key), parsedValue)
102 | 	}
103 | 
104 | 	/**
105 | 	 * Delete value in KV storage. **Does not need to be awaited**
106 | 	 *
107 | 	 * @returns `true` if a value was deleted, or `false` if it did not.
108 | 	 */
109 | 	async delete<K extends keyof T>(key: K): Promise<boolean> {
110 | 		return this.state.storage.delete(this.addPrefix(key))
111 | 	}
112 | }
113 | 
```

--------------------------------------------------------------------------------
/apps/dns-analytics/src/tools/dex-analytics.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 { AccountGetParams } from 'cloudflare/resources/accounts/accounts.mjs'
  7 | import type { ReportGetParams } from 'cloudflare/resources/dns/analytics.mjs'
  8 | import type { ZoneGetParams } from 'cloudflare/resources/dns/settings.mjs'
  9 | import type { DNSAnalyticsMCP } from '../dns-analytics.app'
 10 | 
 11 | function getStartDate(days: number) {
 12 | 	const today = new Date()
 13 | 	const start_date = new Date(today.setDate(today.getDate() - days))
 14 | 	return start_date.toISOString()
 15 | }
 16 | 
 17 | export function registerAnalyticTools(agent: DNSAnalyticsMCP) {
 18 | 	// Register DNS Report tool
 19 | 	agent.server.tool(
 20 | 		'dns_report',
 21 | 		'Fetch the DNS Report for a given zone since a date',
 22 | 		{
 23 | 			zone: z.string(),
 24 | 			days: z.number(),
 25 | 		},
 26 | 		async ({ zone, days }) => {
 27 | 			try {
 28 | 				const props = getProps(agent)
 29 | 				const client = getCloudflareClient(props.accessToken)
 30 | 				const start_date = getStartDate(days)
 31 | 				const params: ReportGetParams = {
 32 | 					zone_id: zone,
 33 | 					metrics: 'responseTimeAvg,queryCount,uncachedCount,staleCount',
 34 | 					dimensions: 'responseCode,responseCached',
 35 | 					since: start_date,
 36 | 				}
 37 | 				const result = await client.dns.analytics.reports.get(params)
 38 | 				return {
 39 | 					content: [
 40 | 						{
 41 | 							type: 'text',
 42 | 							text: JSON.stringify({
 43 | 								result,
 44 | 							}),
 45 | 						},
 46 | 					],
 47 | 				}
 48 | 			} catch (error) {
 49 | 				return {
 50 | 					content: [
 51 | 						{
 52 | 							type: 'text',
 53 | 							text: `Error fetching DNS report: ${error instanceof Error && error.message}`,
 54 | 						},
 55 | 					],
 56 | 				}
 57 | 			}
 58 | 		}
 59 | 	)
 60 | 	// Register Account DNS Settings display tool
 61 | 	agent.server.tool(
 62 | 		'show_account_dns_settings',
 63 | 		'Show DNS settings for current account',
 64 | 		async () => {
 65 | 			try {
 66 | 				const accountId = await agent.getActiveAccountId()
 67 | 				if (!accountId) {
 68 | 					return {
 69 | 						content: [
 70 | 							{
 71 | 								type: 'text',
 72 | 								text: 'No currently active accountId. Try listing your accounts (accounts_list) and then setting an active account (set_active_account)',
 73 | 							},
 74 | 						],
 75 | 					}
 76 | 				}
 77 | 				const props = getProps(agent)
 78 | 				const client = getCloudflareClient(props.accessToken)
 79 | 				const params: AccountGetParams = {
 80 | 					account_id: accountId,
 81 | 				}
 82 | 				const result = await client.dns.settings.account.get(params)
 83 | 				return {
 84 | 					content: [
 85 | 						{
 86 | 							type: 'text',
 87 | 							text: JSON.stringify({
 88 | 								result,
 89 | 							}),
 90 | 						},
 91 | 					],
 92 | 				}
 93 | 			} catch (error) {
 94 | 				return {
 95 | 					content: [
 96 | 						{
 97 | 							type: 'text',
 98 | 							text: `Error fetching DNS report: ${error instanceof Error && error.message}`,
 99 | 						},
100 | 					],
101 | 				}
102 | 			}
103 | 		}
104 | 	)
105 | 	// Register Zone DNS Settings display tool
106 | 	agent.server.tool(
107 | 		'show_zone_dns_settings',
108 | 		'Show DNS settings for a zone',
109 | 		{
110 | 			zone: z.string(),
111 | 		},
112 | 		async ({ zone }) => {
113 | 			try {
114 | 				const props = getProps(agent)
115 | 				const client = getCloudflareClient(props.accessToken)
116 | 				const params: ZoneGetParams = {
117 | 					zone_id: zone,
118 | 				}
119 | 				const result = await client.dns.settings.zone.get(params)
120 | 				return {
121 | 					content: [
122 | 						{
123 | 							type: 'text',
124 | 							text: JSON.stringify({
125 | 								result,
126 | 							}),
127 | 						},
128 | 					],
129 | 				}
130 | 			} catch (error) {
131 | 				return {
132 | 					content: [
133 | 						{
134 | 							type: 'text',
135 | 							text: `Error fetching DNS report: ${error instanceof Error && error.message}`,
136 | 						},
137 | 					],
138 | 				}
139 | 			}
140 | 		}
141 | 	)
142 | }
143 | 
```

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

```typescript
  1 | import { z } from 'zod'
  2 | 
  3 | import { handleZonesList } from '../api/zone.api'
  4 | import { getCloudflareClient } from '../cloudflare-api'
  5 | import { getProps } from '../get-props'
  6 | import { type CloudflareMcpAgent } from '../types/cloudflare-mcp-agent.types'
  7 | 
  8 | export function registerZoneTools(agent: CloudflareMcpAgent) {
  9 | 	// Tool to list all zones under an account
 10 | 	agent.server.tool(
 11 | 		'zones_list',
 12 | 		'List all zones under a Cloudflare account',
 13 | 		{
 14 | 			name: z.string().optional().describe('Filter zones by name'),
 15 | 			status: z
 16 | 				.string()
 17 | 				.optional()
 18 | 				.describe(
 19 | 					'Filter zones by status (active, pending, initializing, moved, deleted, deactivated, read only)'
 20 | 				),
 21 | 			page: z.number().min(1).default(1).describe('Page number for pagination'),
 22 | 			perPage: z.number().min(5).max(1000).default(50).describe('Number of zones per page'),
 23 | 			order: z
 24 | 				.string()
 25 | 				.default('name')
 26 | 				.describe('Field to order results by (name, status, account_name)'),
 27 | 			direction: z
 28 | 				.enum(['asc', 'desc'])
 29 | 				.default('desc')
 30 | 				.describe('Direction to order results (asc, desc)'),
 31 | 		},
 32 | 		{
 33 | 			title: 'List zones',
 34 | 			annotations: {
 35 | 				readOnlyHint: true,
 36 | 				destructiveHint: false,
 37 | 			},
 38 | 		},
 39 | 		async (params) => {
 40 | 			const accountId = await agent.getActiveAccountId()
 41 | 			if (!accountId) {
 42 | 				return {
 43 | 					content: [
 44 | 						{
 45 | 							type: 'text',
 46 | 							text: 'No currently active accountId. Try listing your accounts (accounts_list) and then setting an active account (set_active_account)',
 47 | 						},
 48 | 					],
 49 | 				}
 50 | 			}
 51 | 
 52 | 			try {
 53 | 				const props = getProps(agent)
 54 | 				const { page = 1, perPage = 50 } = params
 55 | 
 56 | 				const zones = await handleZonesList({
 57 | 					client: getCloudflareClient(props.accessToken),
 58 | 					accountId,
 59 | 					...params,
 60 | 				})
 61 | 
 62 | 				return {
 63 | 					content: [
 64 | 						{
 65 | 							type: 'text',
 66 | 							text: JSON.stringify({
 67 | 								zones,
 68 | 								count: zones.length,
 69 | 								page,
 70 | 								perPage,
 71 | 								accountId,
 72 | 							}),
 73 | 						},
 74 | 					],
 75 | 				}
 76 | 			} catch (error) {
 77 | 				return {
 78 | 					content: [
 79 | 						{
 80 | 							type: 'text',
 81 | 							text: `Error listing zones: ${error instanceof Error ? error.message : String(error)}`,
 82 | 						},
 83 | 					],
 84 | 				}
 85 | 			}
 86 | 		}
 87 | 	)
 88 | 
 89 | 	// Tool to get zone details by ID
 90 | 	agent.server.tool(
 91 | 		'zone_details',
 92 | 		'Get details for a specific Cloudflare zone',
 93 | 		{
 94 | 			zoneId: z.string().describe('The ID of the zone to get details for'),
 95 | 		},
 96 | 		{
 97 | 			title: 'Get zone details',
 98 | 			annotations: {
 99 | 				readOnlyHint: true,
100 | 				destructiveHint: false,
101 | 			},
102 | 		},
103 | 		async (params) => {
104 | 			const accountId = await agent.getActiveAccountId()
105 | 			if (!accountId) {
106 | 				return {
107 | 					content: [
108 | 						{
109 | 							type: 'text',
110 | 							text: 'No currently active accountId. Try listing your accounts (accounts_list) and then setting an active account (set_active_account)',
111 | 						},
112 | 					],
113 | 				}
114 | 			}
115 | 
116 | 			try {
117 | 				const props = getProps(agent)
118 | 				const { zoneId } = params
119 | 				const client = getCloudflareClient(props.accessToken)
120 | 
121 | 				// Use the zones.get method to fetch a specific zone
122 | 				const response = await client.zones.get({ zone_id: zoneId })
123 | 
124 | 				return {
125 | 					content: [
126 | 						{
127 | 							type: 'text',
128 | 							text: JSON.stringify({
129 | 								zone: response,
130 | 							}),
131 | 						},
132 | 					],
133 | 				}
134 | 			} catch (error) {
135 | 				return {
136 | 					content: [
137 | 						{
138 | 							type: 'text',
139 | 							text: `Error fetching zone details: ${error instanceof Error ? error.message : String(error)}`,
140 | 						},
141 | 					],
142 | 				}
143 | 			}
144 | 		}
145 | 	)
146 | }
147 | 
```

--------------------------------------------------------------------------------
/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 | 
```
Page 3/27FirstPrevNextLast