This is page 5 of 25. Use http://codebase.md/cloudflare/mcp-server-cloudflare?lines=false&page={x} to view the full context. # Directory Structure ``` ├── .changeset │ ├── config.json │ └── README.md ├── .dockerignore ├── .editorconfig ├── .eslintrc.cjs ├── .github │ ├── actions │ │ └── setup │ │ └── action.yml │ ├── ISSUE_TEMPLATE │ │ └── bug_report.md │ └── workflows │ ├── branches.yml │ ├── main.yml │ └── release.yml ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc.cjs ├── .syncpackrc.cjs ├── .vscode │ ├── extensions.json │ ├── launch.json │ ├── settings.json │ └── tasks.json ├── apps │ ├── ai-gateway │ │ ├── .dev.vars.example │ │ ├── .eslintrc.cjs │ │ ├── CHANGELOG.md │ │ ├── CONTRIBUTING.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── ai-gateway.app.ts │ │ │ ├── ai-gateway.context.ts │ │ │ ├── tools │ │ │ │ └── ai-gateway.tools.ts │ │ │ └── types.ts │ │ ├── tsconfig.json │ │ ├── types.d.ts │ │ ├── vitest.config.ts │ │ ├── worker-configuration.d.ts │ │ └── wrangler.jsonc │ ├── auditlogs │ │ ├── .dev.vars.example │ │ ├── .eslintrc.cjs │ │ ├── CHANGELOG.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── auditlogs.app.ts │ │ │ ├── auditlogs.context.ts │ │ │ └── tools │ │ │ └── auditlogs.tools.ts │ │ ├── tsconfig.json │ │ ├── types.d.ts │ │ ├── vitest.config.ts │ │ ├── worker-configuration.d.ts │ │ └── wrangler.jsonc │ ├── autorag │ │ ├── .dev.vars.example │ │ ├── .eslintrc.cjs │ │ ├── CHANGELOG.md │ │ ├── CONTRIBUTING.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── autorag.app.ts │ │ │ ├── autorag.context.ts │ │ │ ├── tools │ │ │ │ └── autorag.tools.ts │ │ │ └── types.ts │ │ ├── tsconfig.json │ │ ├── types.d.ts │ │ ├── vitest.config.ts │ │ ├── worker-configuration.d.ts │ │ └── wrangler.jsonc │ ├── browser-rendering │ │ ├── .dev.vars.example │ │ ├── .eslintrc.cjs │ │ ├── CHANGELOG.md │ │ ├── CONTRIBUTING.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── browser.app.ts │ │ │ ├── browser.context.ts │ │ │ └── tools │ │ │ └── browser.tools.ts │ │ ├── tsconfig.json │ │ ├── types.d.ts │ │ ├── vitest.config.ts │ │ ├── worker-configuration.d.ts │ │ └── wrangler.jsonc │ ├── cloudflare-one-casb │ │ ├── .dev.vars.example │ │ ├── .eslintrc.cjs │ │ ├── CHANGELOG.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── cf1-casb.app.ts │ │ │ ├── cf1-casb.context.ts │ │ │ └── tools │ │ │ └── integrations.tools.ts │ │ ├── tsconfig.json │ │ ├── types.d.ts │ │ ├── vitest.config.ts │ │ ├── worker-configuration.d.ts │ │ └── wrangler.jsonc │ ├── demo-day │ │ ├── .eslintrc.cjs │ │ ├── CHANGELOG.md │ │ ├── frontend │ │ │ ├── index.html │ │ │ ├── public │ │ │ │ ├── anthropic.svg │ │ │ │ ├── asana.svg │ │ │ │ ├── atlassian.svg │ │ │ │ ├── canva.svg │ │ │ │ ├── cloudflare_logo.svg │ │ │ │ ├── cloudflare.svg │ │ │ │ ├── dina.jpg │ │ │ │ ├── favicon-16x16.png │ │ │ │ ├── favicon-32x32.png │ │ │ │ ├── favicon.ico │ │ │ │ ├── favicon.png │ │ │ │ ├── intercom.svg │ │ │ │ ├── linear.svg │ │ │ │ ├── matt.jpg │ │ │ │ ├── mcp_demo_day.svg │ │ │ │ ├── mcpog.png │ │ │ │ ├── more.svg │ │ │ │ ├── paypal.svg │ │ │ │ ├── pete.jpeg │ │ │ │ ├── sentry.svg │ │ │ │ ├── special_guest.png │ │ │ │ ├── square.svg │ │ │ │ ├── stripe.svg │ │ │ │ ├── sunil.jpg │ │ │ │ └── webflow.svg │ │ │ ├── script.js │ │ │ └── styles.css │ │ ├── package.json │ │ ├── src │ │ │ └── demo-day.app.ts │ │ ├── tsconfig.json │ │ ├── worker-configuration.d.ts │ │ └── wrangler.json │ ├── dex-analysis │ │ ├── .dev.vars.example │ │ ├── .eslintrc.cjs │ │ ├── CHANGELOG.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── dex-analysis.app.ts │ │ │ ├── dex-analysis.context.ts │ │ │ ├── tools │ │ │ │ └── dex-analysis.tools.ts │ │ │ └── warp_diag_reader.ts │ │ ├── tsconfig.json │ │ ├── types.d.ts │ │ ├── vitest.config.ts │ │ ├── worker-configuration.d.ts │ │ └── wrangler.jsonc │ ├── dns-analytics │ │ ├── .dev.vars.example │ │ ├── .eslintrc.cjs │ │ ├── CHANGELOG.md │ │ ├── CONTRIBUTING.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── dns-analytics.app.ts │ │ │ ├── dns-analytics.context.ts │ │ │ └── tools │ │ │ └── dex-analytics.tools.ts │ │ ├── tsconfig.json │ │ ├── types.d.ts │ │ ├── vitest.config.ts │ │ ├── worker-configuration.d.ts │ │ └── wrangler.jsonc │ ├── docs-autorag │ │ ├── .eslintrc.cjs │ │ ├── CHANGELOG.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── docs-autorag.app.ts │ │ │ ├── docs-autorag.context.ts │ │ │ └── tools │ │ │ └── docs-autorag.tools.ts │ │ ├── tsconfig.json │ │ ├── vitest.config.ts │ │ ├── worker-configuration.d.ts │ │ └── wrangler.jsonc │ ├── docs-vectorize │ │ ├── .eslintrc.cjs │ │ ├── CHANGELOG.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── docs-vectorize.app.ts │ │ │ └── docs-vectorize.context.ts │ │ ├── tsconfig.json │ │ ├── vitest.config.ts │ │ ├── worker-configuration.d.ts │ │ └── wrangler.jsonc │ ├── graphql │ │ ├── .eslintrc.cjs │ │ ├── CHANGELOG.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── graphql.app.ts │ │ │ ├── graphql.context.ts │ │ │ └── tools │ │ │ └── graphql.tools.ts │ │ ├── tsconfig.json │ │ ├── types.d.ts │ │ ├── worker-configuration.d.ts │ │ └── wrangler.jsonc │ ├── logpush │ │ ├── .dev.vars.example │ │ ├── .eslintrc.cjs │ │ ├── CHANGELOG.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── logpush.app.ts │ │ │ ├── logpush.context.ts │ │ │ └── tools │ │ │ └── logpush.tools.ts │ │ ├── tsconfig.json │ │ ├── types.d.ts │ │ ├── vitest.config.ts │ │ ├── worker-configuration.d.ts │ │ └── wrangler.jsonc │ ├── radar │ │ ├── .dev.vars.example │ │ ├── .eslintrc.cjs │ │ ├── CHANGELOG.md │ │ ├── CONTRIBUTING.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── radar.app.ts │ │ │ ├── radar.context.ts │ │ │ ├── tools │ │ │ │ ├── radar.tools.ts │ │ │ │ └── url-scanner.tools.ts │ │ │ ├── types │ │ │ │ ├── radar.ts │ │ │ │ └── url-scanner.ts │ │ │ └── utils.ts │ │ ├── tsconfig.json │ │ ├── types.d.ts │ │ ├── vitest.config.ts │ │ ├── worker-configuration.d.ts │ │ └── wrangler.jsonc │ ├── sandbox-container │ │ ├── .dev.vars.example │ │ ├── .eslintrc.cjs │ │ ├── CHANGELOG.md │ │ ├── container │ │ │ ├── fileUtils.spec.ts │ │ │ ├── fileUtils.ts │ │ │ ├── sandbox.container.app.ts │ │ │ └── tsconfig.json │ │ ├── CONTRIBUTING.md │ │ ├── Dockerfile │ │ ├── evals │ │ │ ├── exec.eval.ts │ │ │ ├── files.eval.ts │ │ │ ├── initialize.eval.ts │ │ │ └── utils.ts │ │ ├── package.json │ │ ├── README.md │ │ ├── server │ │ │ ├── containerHelpers.ts │ │ │ ├── containerManager.ts │ │ │ ├── containerMcp.ts │ │ │ ├── metrics.ts │ │ │ ├── prompts.ts │ │ │ ├── sandbox.server.app.ts │ │ │ ├── sandbox.server.context.ts │ │ │ ├── userContainer.ts │ │ │ ├── utils.spec.ts │ │ │ └── utils.ts │ │ ├── shared │ │ │ ├── consts.ts │ │ │ └── schema.ts │ │ ├── tsconfig.json │ │ ├── types.d.ts │ │ ├── vitest.config.evals.ts │ │ ├── worker-configuration.d.ts │ │ └── wrangler.jsonc │ ├── workers-bindings │ │ ├── .dev.vars.example │ │ ├── .eslintrc.cjs │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── CONTRIBUTING.md │ │ ├── evals │ │ │ ├── accounts.eval.ts │ │ │ ├── hyperdrive.eval.ts │ │ │ ├── kv_namespaces.eval.ts │ │ │ ├── types.d.ts │ │ │ └── utils.ts │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── bindings.app.ts │ │ │ └── bindings.context.ts │ │ ├── tsconfig.json │ │ ├── vitest.config.evals.ts │ │ ├── vitest.config.ts │ │ ├── worker-configuration.d.ts │ │ └── wrangler.jsonc │ ├── workers-builds │ │ ├── .dev.vars.example │ │ ├── .eslintrc.cjs │ │ ├── CHANGELOG.md │ │ ├── CONTRIBUTING.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── tools │ │ │ │ └── workers-builds.tools.ts │ │ │ ├── workers-builds.app.ts │ │ │ └── workers-builds.context.ts │ │ ├── tsconfig.json │ │ ├── types.d.ts │ │ ├── vite.config.mts │ │ ├── vitest.config.ts │ │ ├── worker-configuration.d.ts │ │ └── wrangler.jsonc │ └── workers-observability │ ├── .dev.vars.example │ ├── .eslintrc.cjs │ ├── CHANGELOG.md │ ├── CONTRIBUTING.md │ ├── package.json │ ├── README.md │ ├── src │ │ ├── tools │ │ │ └── workers-observability.tools.ts │ │ ├── workers-observability.app.ts │ │ └── workers-observability.context.ts │ ├── tsconfig.json │ ├── types.d.ts │ ├── vitest.config.ts │ ├── worker-configuration.d.ts │ └── wrangler.jsonc ├── CONTRIBUTING.md ├── implementation-guides │ ├── evals.md │ ├── tools.md │ └── type-validators.md ├── LICENSE ├── package.json ├── packages │ ├── eslint-config │ │ ├── CHANGELOG.md │ │ ├── default.cjs │ │ ├── package.json │ │ └── README.md │ ├── eval-tools │ │ ├── CHANGELOG.md │ │ ├── package.json │ │ ├── src │ │ │ ├── runTask.ts │ │ │ ├── scorers.ts │ │ │ └── test-models.ts │ │ ├── tsconfig.json │ │ ├── worker-configuration.d.ts │ │ └── wrangler.json │ ├── mcp-common │ │ ├── .eslintrc.cjs │ │ ├── CHANGELOG.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── api │ │ │ │ ├── account.api.ts │ │ │ │ ├── cf1-integration.api.ts │ │ │ │ ├── workers-builds.api.ts │ │ │ │ ├── workers-observability.api.ts │ │ │ │ ├── workers.api.ts │ │ │ │ └── zone.api.ts │ │ │ ├── api-handler.ts │ │ │ ├── api-token-mode.ts │ │ │ ├── cloudflare-api.ts │ │ │ ├── cloudflare-auth.ts │ │ │ ├── cloudflare-oauth-handler.ts │ │ │ ├── config.ts │ │ │ ├── constants.ts │ │ │ ├── durable-kv-store.ts │ │ │ ├── durable-objects │ │ │ │ └── user_details.do.ts │ │ │ ├── env.ts │ │ │ ├── format.spec.ts │ │ │ ├── format.ts │ │ │ ├── get-props.ts │ │ │ ├── mcp-error.ts │ │ │ ├── poll.ts │ │ │ ├── prompts │ │ │ │ └── docs-vectorize.prompts.ts │ │ │ ├── scopes.ts │ │ │ ├── sentry.ts │ │ │ ├── server.ts │ │ │ ├── tools │ │ │ │ ├── account.tools.ts │ │ │ │ ├── d1.tools.ts │ │ │ │ ├── docs-vectorize.tools.ts │ │ │ │ ├── hyperdrive.tools.ts │ │ │ │ ├── kv_namespace.tools.ts │ │ │ │ ├── r2_bucket.tools.ts │ │ │ │ ├── worker.tools.ts │ │ │ │ └── zone.tools.ts │ │ │ ├── types │ │ │ │ ├── cf1-integrations.types.ts │ │ │ │ ├── cloudflare-mcp-agent.types.ts │ │ │ │ ├── d1.types.ts │ │ │ │ ├── hyperdrive.types.ts │ │ │ │ ├── kv_namespace.types.ts │ │ │ │ ├── r2_bucket.types.ts │ │ │ │ ├── shared.types.ts │ │ │ │ ├── tools.types.ts │ │ │ │ ├── workers-builds.types.ts │ │ │ │ ├── workers-logs.types.ts │ │ │ │ └── workers.types.ts │ │ │ ├── utils.spec.ts │ │ │ ├── utils.ts │ │ │ └── v4-api.ts │ │ ├── tests │ │ │ └── utils │ │ │ └── cloudflare-mock.ts │ │ ├── tsconfig.json │ │ ├── types.d.ts │ │ ├── vitest.config.ts │ │ └── worker-configuration.d.ts │ ├── mcp-observability │ │ ├── CHANGELOG.md │ │ ├── package.json │ │ ├── src │ │ │ ├── analytics-engine.ts │ │ │ ├── index.ts │ │ │ └── metrics.ts │ │ ├── tsconfig.json │ │ └── worker-configuration.d.ts │ ├── tools │ │ ├── .eslintrc.cjs │ │ ├── bin │ │ │ ├── run-changeset-new │ │ │ ├── run-eslint-workers │ │ │ ├── run-fix-deps │ │ │ ├── run-tsc │ │ │ ├── run-turbo │ │ │ ├── run-vitest │ │ │ ├── run-vitest-ci │ │ │ ├── run-wrangler-deploy │ │ │ ├── run-wrangler-types │ │ │ └── runx │ │ ├── CHANGELOG.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── bin │ │ │ │ └── runx.ts │ │ │ ├── changesets.spec.ts │ │ │ ├── changesets.ts │ │ │ ├── cmd │ │ │ │ └── deploy-published-packages.ts │ │ │ ├── proc.ts │ │ │ ├── test │ │ │ │ ├── fixtures │ │ │ │ │ └── changesets │ │ │ │ │ ├── empty │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ ├── invalid-json │ │ │ │ │ │ └── published-packages.json │ │ │ │ │ ├── invalid-schema │ │ │ │ │ │ └── published-packages.json │ │ │ │ │ └── valid │ │ │ │ │ └── published-packages.json │ │ │ │ └── setup.ts │ │ │ └── tsconfig.ts │ │ ├── tsconfig.json │ │ └── vitest.config.ts │ └── typescript-config │ ├── CHANGELOG.md │ ├── package.json │ ├── tools.json │ ├── workers-lib.json │ └── workers.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── README.md ├── server.json ├── tsconfig.json ├── turbo.json └── vitest.workspace.ts ``` # Files -------------------------------------------------------------------------------- /apps/demo-day/frontend/script.js: -------------------------------------------------------------------------------- ```javascript document.addEventListener('DOMContentLoaded', () => { const container = document.querySelector('.page-wrapper') const starfield = document.createElement('div') starfield.className = 'starfield' container.appendChild(starfield) // Create initial stars const numberOfStars = 300 const stars = [] function createStar() { const star = document.createElement('div') star.className = 'star' // Random position const x = Math.random() * window.innerWidth const y = Math.random() * window.innerHeight // Random size (more variation in sizes) const size = Math.random() * 2 + (Math.random() > 0.95 ? 1.5 : 0) // More subtle initial opacity const opacity = Math.random() * 0.15 + 0.05 star.style.cssText = ` left: ${x}px; top: ${y}px; width: ${size}px; height: ${size}px; opacity: ${opacity}; ` return star } // Initialize stars for (let i = 0; i < numberOfStars; i++) { const star = createStar() starfield.appendChild(star) stars.push(star) } // Animate stars function twinkle() { stars.forEach((star) => { // Random chance to twinkle if (Math.random() > 0.98) { const currentOpacity = parseFloat(star.style.opacity) const targetOpacity = currentOpacity < 0.1 ? Math.random() * 0.15 + 0.05 // Brighter : Math.random() * 0.05 + 0.02 // Dimmer // Slower transition for more subtle effect star.style.transition = 'opacity 1.5s ease-in-out' star.style.opacity = targetOpacity } }) requestAnimationFrame(twinkle) } // Start animation twinkle() // Form handling const emailForm = document.querySelector('.input-group') //const //emailInput = null //emailForm.querySelector('input[type="email"]') const honeypotInput = emailForm.querySelector('input[name="contact_me_by_fax"]') //const notifyButton = emailForm.querySelector('.notify-btn') // Check if user has already signed up /*if (localStorage.getItem('mcp_demo_signup')) { const savedEmail = localStorage.getItem('mcp_demo_signup') showSuccessState(savedEmail) }*/ /*function showSuccessState(email) { const inputGroup = //emailInput.closest('.input-group') inputGroup.classList.add('success') //emailInput.value = email //emailInput.disabled = true notifyButton.disabled = true // Update button notifyButton.innerHTML = ` <svg width="16" height="16" viewBox="0 0 16 16" fill="none"> <path d="M13.5 4.5L6 12L2.5 8.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> </svg> ` notifyButton.classList.add('success') createConfetti(notifyButton) }*/ // Rate limiting configuration const RATE_LIMIT_DURATION = 60000 // 1 minute const MAX_ATTEMPTS = 5 let attemptCount = 0 let lastAttemptTime = 0 function checkRateLimit() { const now = Date.now() if (now - lastAttemptTime > RATE_LIMIT_DURATION) { attemptCount = 0 } if (attemptCount >= MAX_ATTEMPTS) { return false } attemptCount++ lastAttemptTime = now return true } // Enhanced email validation function isValidEmail(email) { if (email.length > 254) return false const re = /^(?=[a-zA-Z0-9@._%+-]{6,254}$)[a-zA-Z0-9._%+-]{1,64}@(?:[a-zA-Z0-9-]{1,63}\.){1,8}[a-zA-Z]{2,63}$/ return re.test(email) } // XSS Prevention function sanitizeInput(str) { const div = document.createElement('div') div.textContent = str return div.innerHTML } // Debounced email validation const debounce = (fn, delay) => { let timeoutId return (...args) => { clearTimeout(timeoutId) timeoutId = setTimeout(() => fn(...args), delay) } } // Retry logic for API calls async function retryFetch(url, options, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { const response = await fetch(url, options) const data = await response.json() return { response, data } } catch (error) { if (i === maxRetries - 1) throw error await new Promise((resolve) => setTimeout(resolve, 1000 * Math.pow(2, i))) } } } // Enhanced toast function with accessibility function showToast(message, duration = 3000) { const existingToast = document.querySelector('.toast') if (existingToast) { existingToast.remove() } const toast = document.createElement('div') toast.className = 'toast' toast.setAttribute('role', 'alert') toast.setAttribute('aria-live', 'polite') toast.textContent = sanitizeInput(message) document.body.appendChild(toast) toast.offsetHeight toast.classList.add('show') setTimeout(() => { toast.classList.remove('show') setTimeout(() => toast.remove(), 300) }, duration) } // Setup accessibility attributes /*function setupAccessibility() { //emailInput.setAttribute('aria-label', 'Email address for notification') notifyButton.setAttribute('aria-label', 'Sign up for notification') document.querySelector('.success-message')?.setAttribute('role', 'status') }*/ // Debounced email validation on input const validateEmailDebounced = debounce((email) => { const isValid = isValidEmail(email) //emailInput.style.border = isValid ? '' : '1px solid red' }, 300) //emailInput.addEventListener('input', (e) => validateEmailDebounced(e.target.value)) // Enhanced click handler with all improvements /*notifyButton.addEventListener('click', async (e) => { e.preventDefault() //const email = //emailInput.value.trim() // Rate limit check if (!checkRateLimit()) { showToast('Please wait a minute before trying again.') return } // Basic validation if (!email) { //emailInput.style.border = '1px solid red' return } if (!isValidEmail(email)) { //emailInput.style.border = '1px solid red' showToast('Please enter a valid email address.') return } // Check if already signed up /*if (localStorage.getItem('mcp_demo_signup')) { return }*/ // Honeypot check /*if (honeypotInput.value) { console.log('Bot detected') return } try { const { response, data } = await retryFetch( 'https://starbasedb-3285.outerbase.workers.dev/query', { method: 'POST', headers: { 'X-Starbase-Source': 'internal', Authorization: 'Bearer 8gmjguywgvsy2hvxnqpqzapwjq896ke3', 'Content-Type': 'application/json', }, body: JSON.stringify({ sql: 'INSERT INTO signups (email, status, created_at) VALUES (?, ?, CURRENT_TIMESTAMP)', params: [sanitizeInput(email), 'pending'], }), } ) if (!response.ok) { throw new Error(data.error || 'Signup failed') } localStorage.setItem('mcp_demo_signup', email) showSuccessState(email) setupCalendarActions() setupAccessibility() } catch (error) { console.error('Error:', error) //emailInput.style.border = '1px solid red' if (error.message.includes('UNIQUE constraint failed')) { showToast('This email is already registered for the demo. Check your inbox for details.') localStorage.setItem('mcp_demo_signup', email) showSuccessState(email) setupCalendarActions() setupAccessibility() } else { showToast('Something went wrong. Please try again.') } } })*/ // Initialize accessibility //() function setupCalendarActions() { const calendarActions = document.querySelectorAll( '.success-message .calendar-action, .calendar-option' ) const eventDetails = "Get a preview of the future of agentic software. See how the world's most innovative platforms have connected agents to their services with MCP to build a new class of product experiences.\n\nJoin Live: https://cloudflare.tv/mcp-demo-day" const location = 'https://cloudflare.tv/mcp-demo-day' calendarActions.forEach((option) => { option.addEventListener('click', () => { const calendarType = option.dataset.calendarType switch (calendarType) { case 'google': window.open( 'https://calendar.google.com/calendar/render?action=TEMPLATE&text=MCP+Demo+Day&details=Get+a+preview+of+the+future+of+agentic+software.+See+how+the+world%27s+most+innovative+platforms+have+connected+agents+to+their+services+with+MCP+to+build+a+new+class+of+product+experiences.%0A%0AJoin+Live%3A+https%3A%2F%2Fcloudflare.tv%2Fmcp-demo-day&location=https%3A%2F%2Fcloudflare.tv%2Fmcp-demo-day&dates=20250501T170000Z%2F20250501T183000Z', '_blank' ) break case 'outlook': window.open( 'https://outlook.live.com/calendar/0/deeplink/compose?subject=MCP+Demo+Day&body=Get+a+preview+of+the+future+of+agentic+software.+See+how+the+world%27s+most+innovative+platforms+have+connected+agents+to+their+services+with+MCP+to+build+a+new+class+of+product+experiences.%0A%0AJoin+Live%3A+https%3A%2F%2Fcloudflare.tv%2Fmcp-demo-day&startdt=2025-05-01T17%3A00%3A00Z&enddt=2025-05-01T18%3A30%3A00Z&location=https%3A%2F%2Fcloudflare.tv%2Fmcp-demo-day', '_blank' ) break case 'apple': case 'ics': const icsContent = `BEGIN:VCALENDAR VERSION:2.0 PRODID:-//MCP Demo Day//EN CALSCALE:GREGORIAN BEGIN:VEVENT SUMMARY:MCP Demo Day DESCRIPTION:${eventDetails.replace(/\n/g, '\\n')} LOCATION:${location} DTSTART:20250501T170000Z DTEND:20250501T183000Z STATUS:CONFIRMED SEQUENCE:0 END:VEVENT END:VCALENDAR` if (calendarType === 'apple') { const dataUri = 'data:text/calendar;charset=utf-8,' + encodeURIComponent(icsContent) window.open(dataUri) } else { const blob = new Blob([icsContent], { type: 'text/calendar;charset=utf-8' }) const link = document.createElement('a') link.href = window.URL.createObjectURL(blob) link.download = 'mcp_demo_day.ics' document.body.appendChild(link) link.click() document.body.removeChild(link) } break } // Close the dialog if it's open const dialog = document.getElementById('calendarDialog') if (dialog) { dialog.close() } }) }) } // Call setupCalendarActions immediately if needed setupCalendarActions() // Remove red border on input focus //emailInput.addEventListener('focus', () => { //emailInput.style.border = 'none' //}) // Company list hover effect const companies = document.querySelectorAll('.demo-companies li') companies.forEach((company) => { company.addEventListener('mouseenter', () => { companies.forEach((c) => { if (c !== company) { c.style.opacity = '0.5' } }) }) company.addEventListener('mouseleave', () => { companies.forEach((c) => { c.style.opacity = '1' }) }) }) // Setup company backgrounds const companyNames = [ 'asana', 'atlassian', 'cloudflare', 'intercom', 'linear', 'paypal', 'sentry', 'square', 'stripe', 'webflow', 'more', ] const demoList = document.querySelector('.demo-companies') const companyItems = demoList.querySelectorAll('li') // Add data attributes to company items companyItems.forEach((item, index) => { const company = companyNames[index] if (company) { item.setAttribute('data-company', company) } console.log('add attrib') }) // Create background containers for each company companyNames.forEach((company) => { console.log('add container') const background = document.createElement('div') background.className = `company-background ${company}` // Load SVG from file fetch(`/public/${company}.svg`) .then((response) => response.text()) .then((svgContent) => { // Clean up the SVG to remove any fill paths const parser = new DOMParser() const doc = parser.parseFromString(svgContent, 'image/svg+xml') const svg = doc.querySelector('svg') // Remove any fill attributes and set stroke svg.querySelectorAll('path, circle, rect').forEach((path) => { path.setAttribute('fill', 'none') path.setAttribute('stroke', 'currentColor') path.setAttribute('stroke-width', '.5') }) background.innerHTML = svg.outerHTML }) .catch((error) => console.error(`Error loading ${company}.svg:`, error)) demoList.appendChild(background) }) // Add cycling animation for company backgrounds let currentBackgroundIndex = 0 let isHovering = false let hoverCompany = null const backgrounds = document.querySelectorAll('.company-background') function cycleBackgrounds() { // Remove active class from all backgrounds and company names backgrounds.forEach((bg) => bg.classList.remove('active')) companyItems.forEach((item) => item.classList.remove('active')) // If hovering, show the hovered company's background and highlight its name if (isHovering && hoverCompany) { const hoverBackground = Array.from(backgrounds).find((bg) => bg.classList.contains(hoverCompany) ) const hoverItem = Array.from(companyItems).find( (item) => item.getAttribute('data-company') === hoverCompany ) if (hoverBackground) { hoverBackground.classList.add('active') if (hoverItem) hoverItem.classList.add('active') return } } // Otherwise, show the next background in the cycle and highlight its name backgrounds[currentBackgroundIndex].classList.add('active') companyItems[currentBackgroundIndex].classList.add('active') currentBackgroundIndex = (currentBackgroundIndex + 1) % backgrounds.length console.log('cucle') } // Start cycling every 3 seconds setInterval(cycleBackgrounds, 3000) // Show first background immediately cycleBackgrounds() // Handle hover states companyItems.forEach((item) => { item.addEventListener('mouseenter', () => { isHovering = true hoverCompany = item.getAttribute('data-company') cycleBackgrounds() // Show the hovered company immediately }) item.addEventListener('mouseleave', () => { isHovering = false hoverCompany = null cycleBackgrounds() // Resume normal cycling }) }) function parseDateTime() { const dateText = document.querySelector('.date-time h2').textContent.trim() // e.g., "APRIL 30TH, 2025" const timeText = document.querySelector('.date-time h3').textContent.trim() // e.g., "ONLINE, 1:00 PM PT" // Remove ordinal indicators (st, nd, rd, th) and parse date const cleanDateText = dateText.replace(/(ST|ND|RD|TH),/i, ',') // Create a more precise regex to match the date format const dateTimeParts = cleanDateText.match(/^([A-Za-z]+)\s+(\d{1,2}),\s*(\d{4})$/i) // Handle "ONLINE, " prefix in time const timeParts = timeText .replace(/^ONLINE,\s*/i, '') .match(/^(\d{1,2}):(\d{2})\s*(AM|PM)\s*(PST|PDT|EST|EDT|CST|CDT|MST|MDT|PT)?$/i) if (!dateTimeParts || !timeParts) { throw new Error('Invalid date/time format') } const [, month, day, year] = dateTimeParts const [, hours, minutes, ampm, timezone] = timeParts let hour24 = parseInt(hours) // Convert to 24-hour format if (ampm.toUpperCase() === 'PM' && hour24 < 12) hour24 += 12 if (ampm.toUpperCase() === 'AM' && hour24 === 12) hour24 = 0 // Create date in UTC const date = new Date( Date.UTC(parseInt(year), getMonthIndex(month), parseInt(day), hour24, parseInt(minutes), 0) ) // Since the time is specified in PT (Pacific Time), adjust for PT (-7 hours from UTC during PDT) date.setUTCHours(date.getUTCHours() - 7) if (isNaN(date.getTime())) { throw new Error('Invalid date/time format') } return date } // Helper function to get month index (0-11) from month name function getMonthIndex(month) { const months = { JANUARY: 0, JAN: 0, FEBRUARY: 1, FEB: 1, MARCH: 2, MAR: 2, APRIL: 3, APR: 3, MAY: 4, JUNE: 5, JUN: 5, JULY: 6, JUL: 6, AUGUST: 7, AUG: 7, SEPTEMBER: 8, SEP: 8, OCTOBER: 9, OCT: 9, NOVEMBER: 10, NOV: 10, DECEMBER: 11, DEC: 11, } return months[month.toUpperCase()] } }) // Helper functions function isValidEmail(email) { const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ return re.test(email.toLowerCase()) } // Particle System function createParticle() { const particle = document.createElement('div') particle.className = 'particle' // Random size between 3-6px const size = Math.random() * 3 + 3 particle.style.width = `${size}px` particle.style.height = `${size}px` // Random starting position, but keep particles within viewport bounds const startX = Math.random() * (window.innerWidth - size) const startY = window.innerHeight + size particle.style.left = `${startX}px` particle.style.top = `${startY}px` // Random animation duration between 6-10 seconds const duration = Math.random() * 4000 + 6000 particle.style.animation = `floatUp ${duration}ms cubic-bezier(0.4, 0, 0.2, 1) forwards` document.body.appendChild(particle) // Cleanup after animation setTimeout(() => { if (particle && particle.parentNode) { particle.parentNode.removeChild(particle) } }, duration) } // Particle manager let particleInterval const startParticles = () => { // Create initial batch of particles for (let i = 0; i < 5; i++) { setTimeout(() => createParticle(), i * 200) } // Create a new particle every 400ms particleInterval = setInterval(() => { // Limit to 15 particles at a time for better performance if (document.querySelectorAll('.particle').length < 15) { createParticle() } }, 400) } // Cleanup function const cleanupParticles = () => { if (particleInterval) { clearInterval(particleInterval) particleInterval = null } document.querySelectorAll('.particle').forEach((particle) => { if (particle.parentNode) { particle.parentNode.removeChild(particle) } }) } // Start particles when page loads startParticles() // Cleanup on page unload window.addEventListener('unload', cleanupParticles) // Pause particles when page is not visible document.addEventListener('visibilitychange', () => { if (document.hidden) { cleanupParticles() } else { startParticles() } }) // Restart particles on window resize let resizeTimeout window.addEventListener('resize', () => { if (resizeTimeout) { clearTimeout(resizeTimeout) } resizeTimeout = setTimeout(() => { cleanupParticles() startParticles() }, 200) }) function createConfetti(button) { const colors = ['#FF6633', '#FF8533', '#FF9966', '#FFAA80'] const confettiCount = 20 for (let i = 0; i < confettiCount; i++) { const confetti = document.createElement('div') confetti.className = 'confetti' // Random size between 4-8px const size = Math.random() * 4 + 4 confetti.style.width = `${size}px` confetti.style.height = `${size}px` // Random color from our palette confetti.style.backgroundColor = colors[Math.floor(Math.random() * colors.length)] // Random position behind the button const startX = Math.random() * button.offsetWidth confetti.style.left = `${startX}px` confetti.style.top = '50%' // Random animation duration and delay const duration = Math.random() * 400 + 600 const delay = Math.random() * 200 confetti.style.animation = `confettiFall ${duration}ms cubic-bezier(0.4, 0, 0.2, 1) ${delay}ms forwards` button.appendChild(confetti) // Cleanup setTimeout(() => { if (confetti.parentNode === button) { button.removeChild(confetti) } }, duration + delay) } } ``` -------------------------------------------------------------------------------- /apps/demo-day/frontend/public/intercom.svg: -------------------------------------------------------------------------------- ``` <svg width="491" height="490" viewBox="0 0 491 490" fill="none" xmlns="http://www.w3.org/2000/svg"> <mask id="path-1-inside-1_1164_3" fill="white"> <path d="M425.137 269.635C425.137 274.005 423.416 278.193 420.353 281.282C417.29 284.37 413.138 286.106 408.806 286.106C404.476 286.106 400.321 284.37 397.258 281.282C394.195 278.193 392.475 274.005 392.475 269.635V122.5C392.475 118.133 394.195 113.944 397.258 110.856C400.321 107.765 404.476 106.031 408.806 106.031C413.138 106.031 417.29 107.765 420.353 110.856C423.416 113.944 425.137 118.133 425.137 122.5V269.635ZM419.468 371.584C417.31 373.761 356.576 424.803 245.229 424.803C133.885 424.803 73.5572 374.034 70.9925 371.855C69.3751 370.481 68.0455 368.799 67.0746 366.905C66.1059 365.01 65.5152 362.942 65.3391 360.817C65.1651 358.693 65.4057 356.555 66.0522 354.524C66.6987 352.495 67.7341 350.613 69.1023 348.988C71.9205 345.7 75.9157 343.669 80.2138 343.336C84.5119 343.005 88.7648 344.402 92.0469 347.219C92.992 347.9 147.248 392.271 245.096 392.271C342.945 392.271 397.604 347.627 398.143 347.219C401.462 344.425 405.73 343.048 410.039 343.379C414.35 343.71 418.362 345.721 421.223 348.988C423.977 352.229 425.352 356.435 425.047 360.69C424.744 364.945 422.787 368.91 419.604 371.719L419.468 371.584ZM65.1887 122.5C65.4336 118.12 67.3861 114.013 70.6231 111.079C73.8601 108.146 78.1173 106.624 82.4648 106.847C86.4923 107.077 90.2942 108.805 93.136 111.696C95.9777 114.587 97.6553 118.436 97.8507 122.5V269.365C97.8507 273.732 96.1302 277.92 93.0672 281.009C90.0042 284.099 85.85 285.833 81.5197 285.833C77.1873 285.833 73.0353 284.099 69.9723 281.009C66.9092 277.92 65.1887 273.732 65.1887 269.365V122.5ZM147.112 89.8328C147.355 85.4529 149.31 81.3459 152.547 78.4124C155.784 75.4788 160.041 73.9594 164.386 74.1807C168.416 74.4128 172.218 76.1386 175.059 79.0292C177.899 81.9197 179.579 85.7688 179.772 89.8328V307.611C179.772 311.98 178.052 316.168 174.991 319.257C171.928 322.345 167.774 324.079 163.443 324.079C159.111 324.079 154.957 322.345 151.896 319.257C148.833 316.168 147.112 311.98 147.112 307.611V89.8328ZM229.44 81.6661C229.44 77.2991 231.16 73.1105 234.223 70.0222C237.286 66.9339 241.438 65.1974 245.771 65.1974C250.101 65.1974 254.255 66.9339 257.318 70.0222C260.381 73.1105 262.102 77.2991 262.102 81.6661V318.5C262.102 322.867 260.381 327.056 257.318 330.146C254.255 333.235 250.101 334.969 245.771 334.969C241.438 334.969 237.286 333.235 234.223 330.146C231.16 327.056 229.44 322.867 229.44 318.5V81.6661ZM310.418 89.8328C310.418 85.4658 312.139 81.2772 315.202 78.1889C318.262 75.1006 322.417 73.3641 326.749 73.3641C331.079 73.3641 335.234 75.1006 338.297 78.1889C341.357 81.2772 343.078 85.4658 343.078 89.8328V307.611C343.078 311.98 341.357 316.168 338.297 319.257C335.234 322.345 331.079 324.079 326.749 324.079C322.417 324.079 318.262 322.345 315.202 319.257C312.139 316.168 310.418 311.98 310.418 307.611V89.8328ZM429.592 0.00135809H61.4104C53.3878 -0.0523702 45.4338 1.48856 38.0019 4.54032C30.572 7.58994 23.8102 12.0902 18.1074 17.779C12.4024 23.4677 7.86801 30.2375 4.7599 37.6971C1.65394 45.1546 0.0365155 53.1601 0 61.2516V428.751C0.0365155 436.84 1.65394 444.845 4.7599 452.305C7.86801 459.763 12.4024 466.532 18.1074 472.221C23.8102 477.91 30.572 482.41 38.0019 485.46C45.4338 488.511 53.3878 490.052 61.4104 489.999H429.592C437.602 490.052 445.545 488.516 452.966 485.473C460.389 482.432 467.143 477.944 472.845 472.268C478.548 466.595 483.087 459.842 486.201 452.398C489.316 444.955 490.946 436.965 491 428.886V61.2516C490.963 53.1708 489.35 45.1782 486.253 37.7272C483.153 30.2762 478.63 23.515 472.94 17.8262C467.252 12.1396 460.505 7.63722 453.091 4.579C445.676 1.52079 437.737 -0.0351771 429.725 0.00135809"/> </mask> <path d="M397.258 110.856L397.968 111.56L397.969 111.56L397.258 110.856ZM420.353 110.856L419.643 111.56L419.643 111.56L420.353 110.856ZM419.468 371.584L420.176 370.877L419.466 370.166L418.758 370.88L419.468 371.584ZM70.9925 371.855L71.6401 371.093L71.6398 371.092L70.9925 371.855ZM67.0746 366.905L66.1842 367.36L66.1848 367.361L67.0746 366.905ZM65.3391 360.817L64.3424 360.898L64.3425 360.899L65.3391 360.817ZM66.0522 354.524L65.0994 354.22L65.0993 354.221L66.0522 354.524ZM69.1023 348.988L68.343 348.337L68.3374 348.344L69.1023 348.988ZM80.2138 343.336L80.137 342.339L80.1365 342.339L80.2138 343.336ZM92.0469 347.219L91.3956 347.978L91.4278 348.005L91.4622 348.03L92.0469 347.219ZM398.143 347.219L398.747 348.016L398.768 348.001L398.787 347.984L398.143 347.219ZM410.039 343.379L409.962 344.376L409.962 344.376L410.039 343.379ZM421.223 348.988L421.985 348.34L421.98 348.335L421.976 348.329L421.223 348.988ZM425.047 360.69L424.049 360.618L424.049 360.619L425.047 360.69ZM419.604 371.719L418.896 372.426L419.561 373.091L420.265 372.469L419.604 371.719ZM65.1887 122.5L64.1903 122.444L64.1887 122.472V122.5H65.1887ZM82.4648 106.847L82.5218 105.849L82.5162 105.849L82.4648 106.847ZM97.8507 122.5H98.8507V122.476L98.8496 122.452L97.8507 122.5ZM93.0672 281.009L92.3572 280.305L92.357 280.305L93.0672 281.009ZM69.9723 281.009L70.6825 280.305L70.6823 280.305L69.9723 281.009ZM147.112 89.8328L146.114 89.7775L146.112 89.8052V89.8328H147.112ZM164.386 74.1807L164.444 73.1824L164.437 73.182L164.386 74.1807ZM175.059 79.0292L175.773 78.3284L175.773 78.3281L175.059 79.0292ZM179.772 89.8328H180.772V89.8091L180.771 89.7853L179.772 89.8328ZM174.991 319.257L175.701 319.961L175.701 319.961L174.991 319.257ZM151.896 319.257L152.606 318.553L152.606 318.553L151.896 319.257ZM257.318 70.0222L256.608 70.7264L257.318 70.0222ZM257.318 330.146L258.028 330.851L258.028 330.85L257.318 330.146ZM234.223 330.146L233.513 330.85L233.513 330.851L234.223 330.146ZM315.202 78.1889L315.912 78.893L315.912 78.8928L315.202 78.1889ZM338.297 78.1889L339.007 77.4849L339.007 77.4847L338.297 78.1889ZM338.297 319.257L339.007 319.961L339.007 319.961L338.297 319.257ZM315.202 319.257L315.912 318.553L315.912 318.553L315.202 319.257ZM61.4104 0.00135809L61.4037 1.00136H61.4104V0.00135809ZM38.0019 4.54032L38.3816 5.46542L38.3817 5.46537L38.0019 4.54032ZM18.1074 17.779L18.8135 18.4871L18.8136 18.4869L18.1074 17.779ZM4.7599 37.6971L3.83682 37.3125L3.83677 37.3126L4.7599 37.6971ZM0 61.2516L-1 61.247V61.2516H0ZM0 428.751H-1.00001L-0.99999 428.755L0 428.751ZM4.7599 452.305L3.83673 452.689L3.83686 452.69L4.7599 452.305ZM18.1074 472.221L18.8136 471.513L18.8135 471.513L18.1074 472.221ZM38.0019 485.46L38.3817 484.535L38.3816 484.535L38.0019 485.46ZM61.4104 489.999V488.999L61.4037 488.999L61.4104 489.999ZM429.592 489.999L429.598 488.999H429.592V489.999ZM452.966 485.473L452.587 484.547L452.587 484.547L452.966 485.473ZM472.845 472.268L472.14 471.559L472.14 471.56L472.845 472.268ZM486.201 452.398L485.279 452.011L485.279 452.012L486.201 452.398ZM491 428.886L492 428.893V428.886H491ZM491 61.2516L492 61.2516L492 61.247L491 61.2516ZM486.253 37.7272L487.176 37.3433L487.176 37.3431L486.253 37.7272ZM472.94 17.8262L473.647 17.1191L473.647 17.1191L472.94 17.8262ZM425.137 269.635H424.137C424.137 273.742 422.52 277.677 419.643 280.577L420.353 281.282L421.063 281.986C424.313 278.709 426.137 274.267 426.137 269.635H425.137ZM420.353 281.282L419.643 280.577C416.767 283.478 412.87 285.106 408.806 285.106V286.106V287.106C413.407 287.106 417.814 285.262 421.063 281.986L420.353 281.282ZM408.806 286.106V285.106C404.744 285.106 400.845 283.478 397.968 280.577L397.258 281.282L396.548 281.986C399.798 285.262 404.207 287.106 408.806 287.106V286.106ZM397.258 281.282L397.968 280.577C395.092 277.677 393.475 273.742 393.475 269.635H392.475H391.475C391.475 274.267 393.299 278.709 396.548 281.986L397.258 281.282ZM392.475 269.635H393.475V122.5H392.475H391.475V269.635H392.475ZM392.475 122.5H393.475C393.475 118.395 395.092 114.46 397.968 111.56L397.258 110.856L396.548 110.151C393.299 113.428 391.475 117.87 391.475 122.5H392.475ZM397.258 110.856L397.969 111.56C400.845 108.658 404.743 107.031 408.806 107.031V106.031V105.031C404.208 105.031 399.798 106.873 396.548 110.152L397.258 110.856ZM408.806 106.031V107.031C412.87 107.031 416.767 108.658 419.643 111.56L420.353 110.856L421.064 110.152C417.814 106.873 413.406 105.031 408.806 105.031V106.031ZM420.353 110.856L419.643 111.56C422.52 114.46 424.137 118.395 424.137 122.5H425.137H426.137C426.137 117.87 424.313 113.428 421.063 110.151L420.353 110.856ZM425.137 122.5H424.137V269.635H425.137H426.137V122.5H425.137ZM419.468 371.584L418.758 370.88C416.764 372.891 356.277 423.803 245.229 423.803V424.803V425.803C356.875 425.803 417.856 374.63 420.178 372.288L419.468 371.584ZM245.229 424.803V423.803C134.205 423.803 74.0994 373.182 71.6401 371.093L70.9925 371.855L70.345 372.617C73.015 374.885 133.565 425.803 245.229 425.803V424.803ZM70.9925 371.855L71.6398 371.092C70.1235 369.805 68.876 368.226 67.9645 366.449L67.0746 366.905L66.1848 367.361C67.2151 369.371 68.6267 371.158 70.3453 372.617L70.9925 371.855ZM67.0746 366.905L67.9651 366.45C67.0555 364.67 66.501 362.729 66.3357 360.734L65.3391 360.817L64.3425 360.899C64.5294 363.155 65.1563 365.349 66.1842 367.36L67.0746 366.905ZM65.3391 360.817L66.3357 360.735C66.1723 358.741 66.3983 356.733 67.0051 354.827L66.0522 354.524L65.0993 354.221C64.413 356.377 64.1579 358.646 64.3424 360.898L65.3391 360.817ZM66.0522 354.524L67.005 354.828C67.6121 352.922 68.584 351.156 69.8672 349.632L69.1023 348.988L68.3374 348.344C66.8841 350.069 65.7854 352.068 65.0994 354.22L66.0522 354.524ZM69.1023 348.988L69.8616 349.639C72.5079 346.551 76.2582 344.645 80.291 344.333L80.2138 343.336L80.1365 342.339C75.5731 342.692 71.333 344.848 68.343 348.337L69.1023 348.988ZM80.2138 343.336L80.2905 344.333C84.3226 344.022 88.3139 345.332 91.3956 347.978L92.0469 347.219L92.6983 346.46C89.2157 343.471 84.7011 341.987 80.137 342.339L80.2138 343.336ZM92.0469 347.219L91.4622 348.03C91.4594 348.028 91.4717 348.037 91.517 348.073C91.5568 348.103 91.6087 348.144 91.6767 348.197C91.8112 348.302 91.9988 348.448 92.2385 348.631C92.7179 348.998 93.4041 349.515 94.2947 350.16C96.0758 351.45 98.6726 353.252 102.066 355.392C108.853 359.672 118.827 365.307 131.837 370.919C157.857 382.143 196.017 393.271 245.096 393.271V392.271V391.271C147.571 391.271 93.5301 347.056 92.6317 346.408L92.0469 347.219ZM245.096 392.271V393.271C294.176 393.271 332.438 382.074 358.507 370.816C371.541 365.188 381.528 359.544 388.296 355.277C391.68 353.143 394.259 351.353 396.013 350.082C396.89 349.446 397.56 348.94 398.021 348.585C398.251 348.407 398.429 348.268 398.554 348.169C398.616 348.12 398.665 348.081 398.7 348.053C398.743 348.02 398.751 348.014 398.747 348.016L398.143 347.219L397.54 346.422C397.069 346.778 342.624 391.271 245.096 391.271V392.271ZM398.143 347.219L398.787 347.984C401.906 345.358 405.916 344.065 409.962 344.376L410.039 343.379L410.115 342.382C405.544 342.03 401.018 343.492 397.499 346.454L398.143 347.219ZM410.039 343.379L409.962 344.376C414.011 344.687 417.781 346.576 420.471 349.647L421.223 348.988L421.976 348.329C418.943 344.866 414.688 342.733 410.115 342.382L410.039 343.379ZM421.223 348.988L420.461 349.635C423.045 352.676 424.336 356.623 424.049 360.618L425.047 360.69L426.044 360.761C426.368 356.246 424.909 351.781 421.985 348.34L421.223 348.988ZM425.047 360.69L424.049 360.619C423.765 364.614 421.928 368.335 418.942 370.969L419.604 371.719L420.265 372.469C423.646 369.486 425.723 365.276 426.044 360.761L425.047 360.69ZM419.604 371.719L420.311 371.012L420.176 370.877L419.468 371.584L418.761 372.291L418.896 372.426L419.604 371.719ZM65.1887 122.5L66.1872 122.555C66.4174 118.436 68.2536 114.576 71.2946 111.82L70.6231 111.079L69.9516 110.338C66.5186 113.449 64.4497 117.803 64.1903 122.444L65.1887 122.5ZM70.6231 111.079L71.2946 111.82C74.3353 109.064 78.3328 107.636 82.4135 107.846L82.4648 106.847L82.5162 105.849C77.9019 105.612 73.3848 107.227 69.9516 110.338L70.6231 111.079ZM82.4648 106.847L82.4078 107.846C86.1858 108.062 89.7542 109.683 92.4228 112.397L93.136 111.696L93.8491 110.995C90.8341 107.928 86.7988 106.093 82.5218 105.849L82.4648 106.847ZM93.136 111.696L92.4228 112.397C95.0913 115.111 96.6682 118.727 96.8519 122.548L97.8507 122.5L98.8496 122.452C98.6424 118.144 96.8641 114.062 93.8491 110.995L93.136 111.696ZM97.8507 122.5H96.8507V269.365H97.8507H98.8507V122.5H97.8507ZM97.8507 269.365H96.8507C96.8507 273.469 95.2336 277.404 92.3572 280.305L93.0672 281.009L93.7772 281.713C97.0269 278.436 98.8507 273.994 98.8507 269.365H97.8507ZM93.0672 281.009L92.357 280.305C89.481 283.207 85.5822 284.833 81.5197 284.833V285.833V286.833C86.1179 286.833 90.5275 284.992 93.7775 281.713L93.0672 281.009ZM81.5197 285.833V284.833C77.4552 284.833 73.5586 283.207 70.6825 280.305L69.9723 281.009L69.262 281.713C72.512 284.992 76.9193 286.833 81.5197 286.833V285.833ZM69.9723 281.009L70.6823 280.305C67.8059 277.404 66.1887 273.469 66.1887 269.365H65.1887H64.1887C64.1887 273.994 66.0126 278.436 69.2622 281.713L69.9723 281.009ZM65.1887 269.365H66.1887V122.5H65.1887H64.1887V269.365H65.1887ZM147.112 89.8328L148.111 89.8882C148.339 85.7698 150.177 81.9095 153.218 79.1534L152.547 78.4124L151.875 77.6714C148.442 80.7824 146.371 85.1361 146.114 89.7775L147.112 89.8328ZM152.547 78.4124L153.218 79.1534C156.259 76.3979 160.256 74.9716 164.335 75.1794L164.386 74.1807L164.437 73.182C159.826 72.9471 155.309 74.5598 151.875 77.6714L152.547 78.4124ZM164.386 74.1807L164.329 75.1791C168.11 75.3968 171.678 77.0161 174.346 79.7302L175.059 79.0292L175.773 78.3281C172.757 75.2611 168.722 73.4288 164.444 73.1824L164.386 74.1807ZM175.059 79.0292L174.346 79.73C177.013 82.4446 178.592 86.0609 178.773 89.8804L179.772 89.8328L180.771 89.7853C180.566 85.4768 178.785 81.3949 175.773 78.3284L175.059 79.0292ZM179.772 89.8328H178.772V307.611H179.772H180.772V89.8328H179.772ZM179.772 307.611H178.772C178.772 311.717 177.155 315.653 174.28 318.553L174.991 319.257L175.701 319.961C178.948 316.684 180.772 312.242 180.772 307.611H179.772ZM174.991 319.257L174.281 318.553C171.405 321.452 167.506 323.079 163.443 323.079V324.079V325.079C168.041 325.079 172.451 323.238 175.701 319.961L174.991 319.257ZM163.443 324.079V323.079C159.379 323.079 155.48 321.452 152.606 318.553L151.896 319.257L151.186 319.961C154.434 323.238 158.843 325.079 163.443 325.079V324.079ZM151.896 319.257L152.606 318.553C149.729 315.652 148.112 311.717 148.112 307.611H147.112H146.112C146.112 312.242 147.936 316.684 151.186 319.961L151.896 319.257ZM147.112 307.611H148.112V89.8328H147.112H146.112V307.611H147.112ZM229.44 81.6661H230.44C230.44 77.5617 232.057 73.6265 234.933 70.7264L234.223 70.0222L233.513 69.318C230.264 72.5944 228.44 77.0366 228.44 81.6661H229.44ZM234.223 70.0222L234.933 70.7264C237.81 67.8262 241.706 66.1974 245.771 66.1974V65.1974V64.1974C241.17 64.1974 236.763 66.0415 233.513 69.318L234.223 70.0222ZM245.771 65.1974V66.1974C249.833 66.1974 253.732 67.8263 256.608 70.7264L257.318 70.0222L258.028 69.318C254.778 66.0414 250.369 64.1974 245.771 64.1974V65.1974ZM257.318 70.0222L256.608 70.7264C259.484 73.6265 261.102 77.5617 261.102 81.6661H262.102H263.102C263.102 77.0366 261.278 72.5944 258.028 69.318L257.318 70.0222ZM262.102 81.6661H261.102V318.5H262.102H263.102V81.6661H262.102ZM262.102 318.5H261.102C261.102 322.605 259.485 326.54 256.608 329.442L257.318 330.146L258.028 330.85C261.278 327.572 263.102 323.13 263.102 318.5H262.102ZM257.318 330.146L256.608 329.442C253.732 332.342 249.833 333.969 245.771 333.969V334.969V335.969C250.369 335.969 254.778 334.127 258.028 330.851L257.318 330.146ZM245.771 334.969V333.969C241.706 333.969 237.809 332.342 234.933 329.442L234.223 330.146L233.513 330.851C236.763 334.127 241.17 335.969 245.771 335.969V334.969ZM234.223 330.146L234.933 329.442C232.057 326.54 230.44 322.605 230.44 318.5H229.44H228.44C228.44 323.13 230.264 327.572 233.513 330.85L234.223 330.146ZM229.44 318.5H230.44V81.6661H229.44H228.44V318.5H229.44ZM310.418 89.8328H311.418C311.418 85.7284 313.035 81.7932 315.912 78.893L315.202 78.1889L314.492 77.4847C311.242 80.7611 309.418 85.2033 309.418 89.8328H310.418ZM315.202 78.1889L315.912 78.8928C318.786 75.9931 322.685 74.3641 326.749 74.3641V73.3641V72.3641C322.149 72.3641 317.739 74.2081 314.491 77.4849L315.202 78.1889ZM326.749 73.3641V74.3641C330.811 74.3641 334.71 75.993 337.587 78.893L338.297 78.1889L339.007 77.4847C335.757 74.2081 331.348 72.3641 326.749 72.3641V73.3641ZM338.297 78.1889L337.586 78.8928C340.461 81.793 342.078 85.7283 342.078 89.8328H343.078H344.078C344.078 85.2033 342.254 80.7613 339.007 77.4849L338.297 78.1889ZM343.078 89.8328H342.078V307.611H343.078H344.078V89.8328H343.078ZM343.078 307.611H342.078C342.078 311.717 340.461 315.653 337.586 318.553L338.297 319.257L339.007 319.961C342.254 316.684 344.078 312.242 344.078 307.611H343.078ZM338.297 319.257L337.587 318.553C334.711 321.452 330.812 323.079 326.749 323.079V324.079V325.079C331.347 325.079 335.757 323.238 339.007 319.961L338.297 319.257ZM326.749 324.079V323.079C322.684 323.079 318.786 321.452 315.912 318.553L315.202 319.257L314.491 319.961C317.739 323.238 322.149 325.079 326.749 325.079V324.079ZM315.202 319.257L315.912 318.553C313.035 315.652 311.418 311.717 311.418 307.611H310.418H309.418C309.418 312.242 311.242 316.684 314.492 319.961L315.202 319.257ZM310.418 307.611H311.418V89.8328H310.418H309.418V307.611H310.418ZM429.592 0.00135809V-0.998642H61.4104V0.00135809V1.00136H429.592V0.00135809ZM61.4104 0.00135809L61.4171 -0.998619C53.2619 -1.05324 45.1766 0.513168 37.622 3.61527L38.0019 4.54032L38.3817 5.46537C45.6911 2.46394 53.5136 0.948495 61.4037 1.00134L61.4104 0.00135809ZM38.0019 4.54032L37.6222 3.61522C30.0698 6.71513 23.1971 11.2894 17.4012 17.071L18.1074 17.779L18.8136 18.4869C24.4234 12.8911 31.0743 8.46474 38.3816 5.46542L38.0019 4.54032ZM18.1074 17.779L17.4013 17.0708C11.603 22.8526 6.99511 29.7324 3.83682 37.3125L4.7599 37.6971L5.68298 38.0817C8.74092 30.7425 13.2018 24.0828 18.8135 18.4871L18.1074 17.779ZM4.7599 37.6971L3.83677 37.3126C0.680544 44.8908 -0.962887 53.0254 -0.99999 61.247L0 61.2516L0.99999 61.2561C1.03592 53.2948 2.62733 45.4184 5.68304 38.0816L4.7599 37.6971ZM0 61.2516H-1V428.751H0H1V61.2516H0ZM0 428.751L-0.99999 428.755C-0.962886 436.975 0.680557 445.109 3.83673 452.689L4.7599 452.305L5.68307 451.921C2.62732 444.582 1.03592 436.705 0.99999 428.746L0 428.751ZM4.7599 452.305L3.83686 452.69C6.99514 460.268 11.603 467.147 17.4013 472.929L18.1074 472.221L18.8135 471.513C13.2018 465.917 8.74089 459.258 5.68294 451.92L4.7599 452.305ZM18.1074 472.221L17.4012 472.929C23.1971 478.711 30.0698 483.285 37.6222 486.385L38.0019 485.46L38.3816 484.535C31.0743 481.535 24.4234 477.109 18.8136 471.513L18.1074 472.221ZM38.0019 485.46L37.622 486.385C45.1766 489.487 53.2619 491.053 61.4171 490.999L61.4104 489.999L61.4037 488.999C53.5136 489.052 45.6911 487.536 38.3817 484.535L38.0019 485.46ZM61.4104 489.999V490.999H429.592V489.999V488.999H61.4104V489.999ZM429.592 489.999L429.585 490.999C437.727 491.053 445.802 489.491 453.345 486.398L452.966 485.473L452.587 484.547C445.288 487.54 437.476 489.052 429.598 488.999L429.592 489.999ZM452.966 485.473L453.345 486.398C460.891 483.307 467.755 478.746 473.551 472.977L472.845 472.268L472.14 471.56C466.53 477.143 459.888 481.556 452.587 484.547L452.966 485.473ZM472.845 472.268L473.551 472.977C479.347 467.211 483.959 460.349 487.124 452.783L486.201 452.398L485.279 452.012C482.215 459.336 477.75 465.978 472.14 471.559L472.845 472.268ZM486.201 452.398L487.124 452.784C490.289 445.221 491.945 437.101 492 428.893L491 428.886L490 428.879C489.947 436.828 488.343 444.689 485.279 452.011L486.201 452.398ZM491 428.886H492V61.2516H491H490V428.886H491ZM491 61.2516L492 61.247C491.963 53.0362 490.324 44.9148 487.176 37.3433L486.253 37.7272L485.33 38.111C488.377 45.4417 489.964 53.3054 490 61.2561L491 61.2516ZM486.253 37.7272L487.176 37.3431C484.027 29.7716 479.43 22.9006 473.647 17.1191L472.94 17.8262L472.233 18.5334C477.83 24.1294 482.28 30.7807 485.33 38.1113L486.253 37.7272ZM472.94 17.8262L473.647 17.1191C467.866 11.3395 461.009 6.76314 453.472 3.65455L453.091 4.579L452.709 5.50346C460.002 8.51129 466.638 12.9398 472.233 18.5334L472.94 17.8262ZM453.091 4.579L453.472 3.65455C445.935 0.545961 437.865 -1.03577 429.72 -0.998632L429.725 0.00135809L429.729 1.00135C437.609 0.965417 445.417 2.49563 452.709 5.50346L453.091 4.579Z" fill="white" mask="url(#path-1-inside-1_1164_3)"/> </svg> ``` -------------------------------------------------------------------------------- /apps/demo-day/frontend/styles.css: -------------------------------------------------------------------------------- ```css :root { --primary-color: rgb(255, 102, 51); --text-color: #ffffff; --background-color: #0a0a0a; --input-background: rgba(255, 255, 255, 0.05); --button-hover: #ff8533; } * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Inter', sans-serif; background-color: var(--background-color); color: var(--text-color); line-height: 1.5; -webkit-font-smoothing: antialiased; min-height: 100vh; position: relative; overflow-x: hidden; overflow-y: auto; width: 100%; } body::before { content: ''; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: radial-gradient( circle at 30% 50%, rgba(255, 255, 255, 0.02) 0%, rgba(255, 255, 255, 0.01) 30%, transparent 60% ), linear-gradient( 45deg, rgba(10, 10, 10, 0.98) 0%, rgba(15, 15, 15, 0.98) 50%, rgba(10, 10, 10, 0.98) 100% ); z-index: 0; animation: gradientFlow 30s ease infinite; } @keyframes gradientFlow { 0% { background-position: 0% 50%, 0% 50%; } 50% { background-position: 100% 50%, 100% 50%; } 100% { background-position: 0% 50%, 0% 50%; } } .page-wrapper { position: relative; min-height: 100vh; display: flex; flex-direction: column; z-index: 2; min-width: 0; width: 100%; overflow-x: hidden; overflow-y: hidden; } .container { display: grid; grid-template-columns: 1fr 1fr; flex: 1; min-width: 0; width: 100%; overflow: hidden; } /* Left Panel Styles */ .left-panel { padding: 3rem 4rem; display: flex; flex-direction: column; } .header { display: flex; justify-content: space-between; align-items: flex-start; } .logo { display: flex; align-items: flex-start; } .cloud-logo { width: 140px; height: auto; } .logo-text { display: flex; flex-direction: column; font-weight: 700; font-size: 1.75rem; line-height: 1; letter-spacing: -0.02em; } .content { margin-top: 4rem; } .date-time { display: inline-flex; align-items: center; gap: 1rem; border-radius: 14px; cursor: pointer; transition: all 0.2s ease; } .date-time-text { display: flex; flex-direction: column; gap: 0.25rem; order: 2; } .date-time h2 { font-size: 0.75rem; font-weight: 600; letter-spacing: 0.02em; line-height: 1.2; color: var(--text-color); } .date-time h3 { font-size: 0.75rem; color: rgba(255, 255, 255, 0.6); font-weight: 500; letter-spacing: 0.02em; line-height: 1.2; } .calendar-trigger { display: flex; align-items: center; justify-content: center; background: rgba(255, 255, 255, 0.03); border: 1px solid rgba(255, 255, 255, 0.1); padding: 0.5rem; color: rgba(255, 255, 255, 0.5); transition: all 0.2s ease; border-radius: 10px; order: 1; } .date-time:hover .calendar-trigger { color: var(--text-color); transform: scale(1.05); background: rgba(255, 255, 255, 0.06); border-color: rgba(255, 255, 255, 0.2); } h1 { font-size: 4rem; line-height: 1; margin-bottom: 2rem; font-weight: 700; letter-spacing: -0.03em; } .description { font-size: 1.25rem; color: rgba(255, 255, 255, 0.7); max-width: 85%; margin-bottom: 2.5rem; line-height: 1.4; } .input-group { display: flex; gap: 1rem; margin-bottom: 2rem; max-width: 560px; position: relative; flex-wrap: nowrap; } .input-group.success { animation: successSlide 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards; } @keyframes successSlide { 0% { transform: translateY(0); } 100% { transform: translateY(-20px); } } .success-message { position: absolute; left: 0; top: calc(100% + 24px); width: 100%; opacity: 0; transform: translateY(10px); transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); display: flex; flex-direction: column; gap: 1rem; pointer-events: none; } .input-group.success .success-message { opacity: 1; transform: translateY(0); pointer-events: all; } .success-text { display: flex; align-items: center; gap: 0.75rem; color: var(--text-color); font-size: 0.875rem; font-weight: 500; } .success-text svg { width: 18px; height: 18px; color: var(--primary-color); } .calendar-actions { display: flex; gap: 0.75rem; align-items: center; flex-wrap: wrap; } .calendar-action { display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem 0.75rem; background: rgba(255, 255, 255, 0.03); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 6px; color: rgba(255, 255, 255, 0.8); font-size: 0.875rem; font-weight: 500; cursor: pointer; transition: all 0.2s ease; } .calendar-action:hover { background: rgba(255, 255, 255, 0.06); border-color: rgba(255, 255, 255, 0.2); transform: translateY(-1px); } .calendar-action svg { width: 16px; height: 16px; opacity: 0.7; transition: all 0.2s ease; } .calendar-action:hover svg { opacity: 1; } input[type='email'] { flex: 1; background: rgba(255, 255, 255, 0.03); border: 1px solid rgba(255, 255, 255, 0.2); min-height: 38px; padding: 0 16px; color: var(--text-color); font-size: 14px; border-radius: 6px; font-family: 'Inter', sans-serif; } input[type='email']::placeholder { color: rgba(255, 255, 255, 0.5); } input[type='email']:focus { outline: none; border-color: rgba(255, 255, 255, 0.4); } /* Add keyframes for glow animation */ @keyframes glowPulse { 0% { box-shadow: 0 0 0 0 rgba(255, 107, 0, 0.4); } 70% { box-shadow: 0 0 0 10px rgba(255, 107, 0, 0); } 100% { box-shadow: 0 0 0 0 rgba(255, 107, 0, 0); } } .notify-btn { text-decoration: none; background: transparent; border: 1px solid var(--primary-color); color: var(--primary-color); height: 38px; padding: 8px 16px; border-radius: 6px; display: flex; align-items: center; gap: 8px; cursor: pointer; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); font-size: 14px; font-weight: 500; position: relative; overflow: visible; } .notify-btn.success { background: var(--primary-color); color: var(--text-color); border-color: var(--primary-color); pointer-events: none; } .notify-btn.success svg { animation: checkmark 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards; } @keyframes checkmark { 0% { transform: scale(0); opacity: 0; } 50% { transform: scale(1.2); } 100% { transform: scale(1); opacity: 1; } } .notify-btn:hover { background: rgba(255, 107, 0, 0.1); border-color: var(--primary-color); animation: glowPulse 2s infinite; } .notify-btn svg { transition: all 0.3s ease; } .notify-btn:hover svg { filter: drop-shadow(0 0 8px rgba(255, 107, 0, 0.6)); transform: scale(1.02); opacity: 1; } .calendar-links { display: flex; align-items: center; gap: 1rem; color: rgba(255, 255, 255, 0.6); font-size: 0.875rem; } .calendar-icons { display: flex; gap: 0.75rem; } .calendar-icon { color: rgba(255, 255, 255, 0.6); transition: all 0.2s ease; display: flex; } .calendar-icon:hover { color: var(--text-color); } .calendar-icon svg { transition: all 0.3s ease; } .calendar-icon:hover svg { filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.4)); transform: scale(1.02); } /* Right Panel Styles */ .right-panel { padding: 3rem 4rem; position: relative; border-left: 1px solid rgba(255, 255, 255, 0.1); display: flex; flex-direction: column; overflow: hidden; } .company-background { position: absolute; bottom: -120%; right: -80%; width: 200%; height: 200%; opacity: 0; transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1); pointer-events: none; z-index: 1; transform: scale(0.95); } .company-background.cloudflare { right: -100%; width: 180%; height: 180%; } .company-background.active { opacity: 1; transform: scale(1); } .company-background svg { width: 100%; height: 100%; object-fit: contain; opacity: 0.15; fill: none; stroke: #ffffff; filter: drop-shadow(0 0 20px rgba(255, 255, 255, 0.2)) drop-shadow(0 0 40px rgba(255, 255, 255, 0.1)); transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1); } .company-background svg path, .company-background svg circle, .company-background svg rect { stroke: #ffffff; stroke-width: 1; transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1); } .company-background.active svg path, .company-background.active svg circle, .company-background.active svg rect { stroke: #ffffff; stroke-width: 1.5; filter: drop-shadow(0 0 5px rgba(255, 255, 255, 0.3)); } /* Add a subtle pulse animation for active backgrounds */ @keyframes pulseGlow { 0% { filter: drop-shadow(0 0 20px rgba(255, 255, 255, 0.2)) drop-shadow(0 0 40px rgba(255, 255, 255, 0.1)); } 50% { filter: drop-shadow(0 0 30px rgba(255, 255, 255, 0.3)) drop-shadow(0 0 60px rgba(255, 255, 255, 0.15)); } 100% { filter: drop-shadow(0 0 20px rgba(255, 255, 255, 0.2)) drop-shadow(0 0 40px rgba(255, 255, 255, 0.1)); } } .company-background.active svg { animation: pulseGlow 3s infinite ease-in-out; } .demos-section { position: relative; z-index: 3; } .demos-section h4 { color: rgba(255, 255, 255, 0.5); font-size: 0.875rem; margin-bottom: 2.5rem; font-weight: 500; letter-spacing: 0.05em; } .demo-companies { list-style: none; position: relative; z-index: 2; } .demo-companies li { font-size: 2rem; font-weight: 600; margin-bottom: 0rem; transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); letter-spacing: -0.02em; line-height: 1.1; cursor: pointer; color: rgba(255, 255, 255, 0.3); text-shadow: 0 0 8px rgba(255, 255, 255, 0.1); } .demo-companies li.active { color: var(--primary-color); text-shadow: 0 0 15px rgba(255, 107, 0, 0.5), 0 0 30px rgba(255, 107, 0, 0.2); transform: scale(1.02); } .demo-companies li:hover { color: var(--primary-color); text-shadow: 0 0 15px rgba(255, 107, 0, 0.5), 0 0 30px rgba(255, 107, 0, 0.2); transform: scale(1.02); } /* Company-specific background visibility */ .demo-companies li[data-company='atlassian']:hover ~ .company-background.atlassian, .demo-companies li[data-company='canva']:hover ~ .company-background.canva, .demo-companies li[data-company='linear']:hover ~ .company-background.linear, .demo-companies li[data-company='paypal']:hover ~ .company-background.paypal, .demo-companies li[data-company='sentry']:hover ~ .company-background.sentry, .demo-companies li[data-company='cloudflare']:hover ~ .company-background.cloudflare, .demo-companies li[data-company='webflow']:hover ~ .company-background.webflow { opacity: 1; } /* Footer Styles */ footer { padding: 2rem 4rem; border-top: 1px solid rgba(255, 255, 255, 0.1); display: flex; justify-content: space-between; align-items: center; position: relative; } .footer-left { display: flex; align-items: center; gap: 3rem; } .cloudflare-logo { height: 20px; width: auto; filter: brightness(0) invert(1); opacity: 1; } .footer-links { position: absolute; left: 50%; transform: translateX(-50%); display: flex; gap: 2rem; } .footer-links a { color: rgba(255, 255, 255, 0.6); text-decoration: none; transition: color 0.2s ease; font-size: 0.875rem; } .footer-links a:hover { color: var(--text-color); } .build-btn { background: transparent; border: 1px solid var(--primary-color); color: var(--primary-color); height: 38px; padding: 8px 16px; border-radius: 6px; display: flex; align-items: center; gap: 8px; cursor: pointer; text-decoration: none; transition: all 0.2s ease; font-size: 14px; font-weight: 500; position: relative; overflow: visible; } .build-btn:hover { background: rgba(255, 107, 0, 0.1); border-color: var(--primary-color); animation: glowPulse 2s infinite; } .build-btn img { width: 24px; height: 24px; opacity: 0.7; transition: all 0.3s ease; } .build-btn:hover img { filter: drop-shadow(0 0 8px rgba(255, 107, 0, 0.6)); transform: scale(1.02); opacity: 1; } /* Responsive Design */ @media (max-width: 1400px) { h1 { font-size: 4rem; } .demo-companies li { font-size: 2.75rem; } } @media (max-width: 1200px) { h1 { font-size: 3.5rem; } .demo-companies li { font-size: 2.5rem; } .description { max-width: 100%; } .input-group { max-width: 560px; } } @media (max-width: 968px) { body { height: auto; overflow: auto; } .container { grid-template-columns: 1fr; } .right-panel { padding: 2rem; border-left: none; border-top: 1px solid rgba(255, 255, 255, 0.1); } .left-panel { padding: 2rem; } h1 { font-size: 3rem; } .input-group { max-width: 100%; flex-wrap: nowrap; gap: 0.75rem; } input[type='email'] { min-width: 0; flex: 1; } .notify-btn { width: fit-content; min-height: 38px; } footer { padding: 2rem; flex-direction: column; gap: 2rem; } .footer-links { position: static; transform: none; margin: 0 auto; } } @media (max-width: 480px) { .left-panel, .right-panel { padding: 1rem; min-width: 0; width: 100%; } .container { min-width: 0; width: 100%; overflow: hidden; } .page-wrapper { min-width: 0; width: 100%; overflow-x: hidden; } .date-time { padding: 0.5rem; } /*.date-time-text { display: none; }*/ .calendar-trigger { order: 1; margin: 0; } .input-group { flex-direction: column; gap: 0.75rem; width: 100%; } .input-group.success { margin-bottom: 125px; } input[type='email'] { width: 100%; min-width: 0; height: 38px; } .notify-btn { width: 100%; justify-content: center; height: 38px; } h1 { font-size: 2.5rem; max-width: 100%; } .description { font-size: 1rem; max-width: 100%; } .demo-companies li { font-size: 2rem; } .footer-left { flex-direction: column; gap: 1.5rem; } .footer-links { flex-direction: column; align-items: center; gap: 1rem; } } /* Add a subtle light effect */ .left-panel::before { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: linear-gradient( 90deg, transparent 0%, rgba(255, 255, 255, 0.01) 30%, rgba(255, 255, 255, 0.005) 50%, transparent 100% ); z-index: -1; pointer-events: none; animation: lightBeam 15s ease-in-out infinite; transform-origin: left; filter: blur(40px); } @keyframes lightBeam { 0%, 100% { opacity: 0.3; transform: scaleX(0.9) translateX(-5%); } 50% { opacity: 0.4; transform: scaleX(1.1) translateX(5%); } } /* Add a subtle depth effect */ .left-panel::after { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: radial-gradient(circle at 30% 50%, rgba(255, 255, 255, 0.01) 0%, transparent 60%); z-index: -2; pointer-events: none; animation: depthPulse 20s ease-in-out infinite; filter: blur(50px); } @keyframes depthPulse { 0%, 100% { opacity: 0.2; transform: scale(1); } 50% { opacity: 0.3; transform: scale(1.05); } } /* Remove the old animations */ .starfield, .floating-particle, body::after { display: none; } .calendar-popover { background: rgb(23, 23, 23); border-radius: 12px; padding: 1.5rem; width: 320px; box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.1), 0 8px 32px rgba(0, 0, 0, 0.4), 0 2px 8px rgba(0, 0, 0, 0.2); position: relative; animation: modalAppear 0.2s cubic-bezier(0.21, 1.02, 0.73, 1); transform-origin: center center; } @keyframes modalAppear { from { opacity: 0; transform: scale(0.98); } to { opacity: 1; transform: scale(1); } } .calendar-popover h4 { font-size: 1.125rem; font-weight: 600; margin-bottom: 1rem; color: var(--text-color); padding-right: 2rem; } .calendar-options { display: flex; flex-direction: column; gap: 0.5rem; } .calendar-option { display: flex; align-items: center; gap: 0.75rem; padding: 0.75rem; border-radius: 8px; background: transparent; border: none; cursor: pointer; transition: all 0.15s ease; color: rgba(255, 255, 255, 0.8); width: 100%; text-align: left; } .calendar-option:hover { background: rgba(255, 255, 255, 0.06); color: var(--text-color); } .calendar-option svg { width: 20px; height: 20px; opacity: 0.7; transition: all 0.2s ease; } .calendar-option:hover svg { opacity: 1; } .calendar-option span { font-size: 0.875rem; font-weight: 500; } .close-button { position: absolute; top: 1rem; right: 1rem; background: transparent; border: none; color: rgba(255, 255, 255, 0.5); padding: 0.375rem; cursor: pointer; transition: all 0.2s ease; border-radius: 6px; display: flex; align-items: center; justify-content: center; } .close-button:hover { background: rgba(255, 255, 255, 0.06); color: var(--text-color); } .close-button svg { width: 16px; height: 16px; } dialog { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%) !important; margin: 0; background: transparent; border: none; padding: 0; width: fit-content; height: fit-content; } dialog::backdrop { background: rgba(0, 0, 0, 0.5); backdrop-filter: blur(4px); animation: backdropFade 0.2s ease; } @keyframes backdropFade { from { opacity: 0; } to { opacity: 1; } } .attendees { display: flex; align-items: center; gap: 0.75rem; margin-top: 1rem; font-size: 0.875rem; color: rgba(255, 255, 255, 0.7); opacity: 1; transform: translateY(0); transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); } .input-group.success ~ .attendees { opacity: 0; transform: translateY(-10px); pointer-events: none; } .attendee-avatars { display: flex; align-items: center; } .attendee-avatar { width: 32px; height: 32px; border-radius: 50%; overflow: visible; position: relative; border: 2px solid rgba(255, 255, 255, 0.1); transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); margin-right: -8px; background: var(--background-color); } .attendee-avatar:hover { transform: translateY(-2px); border-color: rgba(255, 255, 255, 0.3); z-index: 2; margin-right: 4px; } .attendee-avatar img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; } .attendee-avatar[data-tooltip] { cursor: pointer; } .attendee-avatar[data-tooltip]::before { content: attr(data-tooltip); position: absolute; bottom: calc(100% + 8px); left: 50%; transform: translateX(-50%); padding: 0.5rem 0.75rem; background: rgba(23, 23, 23, 0.95); color: white; font-size: 0.875rem; border-radius: 6px; white-space: nowrap; opacity: 0; visibility: hidden; transition: all 0.2s ease; backdrop-filter: blur(8px); border: 1px solid rgba(255, 255, 255, 0.1); z-index: 1000; pointer-events: none; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); width: max-content; } .attendee-avatar[data-tooltip]::after { content: ''; position: absolute; bottom: calc(100% + 4px); left: 50%; transform: translateX(-50%); border: 6px solid transparent; border-top-color: rgba(23, 23, 23, 0.95); opacity: 0; visibility: hidden; transition: all 0.2s ease; z-index: 1000; pointer-events: none; } .attendee-avatar[data-tooltip]:hover::before, .attendee-avatar[data-tooltip]:hover::after { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(-4px); pointer-events: none; } .attendee-count { font-weight: 500; margin-left: 4px; } .attendee-count strong { color: var(--text-color); font-weight: 600; } .particle { position: fixed; pointer-events: none; border-radius: 50%; background: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0.05)); box-shadow: 0 0 4px rgba(255, 255, 255, 0.1); opacity: 0; z-index: 10; will-change: transform, opacity; } @keyframes floatUp { 0% { opacity: 0; transform: translateY(0) scale(1); } 15% { opacity: 0.3; } 50% { opacity: 0.2; } 85% { opacity: 0.1; } 100% { opacity: 0; transform: translateY(-100vh) scale(0.8); } } /* Confetti piece */ .confetti { position: absolute; pointer-events: none; transform-origin: center; mix-blend-mode: screen; will-change: transform, opacity; } @keyframes confettiFall { 0% { transform: translateY(0) rotate(0deg) scale(0); opacity: 1; } 100% { transform: translateY(20px) rotate(360deg) scale(1); opacity: 0; } } @keyframes successPop { 0% { transform: scale(1); } 50% { transform: scale(1.1); } 100% { transform: scale(1); } } .notify-btn.success { animation: successPop 0.4s cubic-bezier(0.4, 0, 0.2, 1); } /* Honeypot field - hide it from users but keep it visible to bots */ .contact-field { display: none !important; position: absolute !important; left: -9999px !important; } .toast { position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%); background: rgba(17, 17, 17, 0.95); color: #fff; padding: 12px 24px; border-radius: 8px; font-size: 14px; z-index: 1000; opacity: 0; transition: opacity 0.3s ease; backdrop-filter: blur(8px); border: 1px solid rgba(255, 255, 255, 0.1); box-shadow: 0 4px 24px rgba(0, 0, 0, 0.2); pointer-events: none; } .toast.show { opacity: 1; } ``` -------------------------------------------------------------------------------- /apps/dex-analysis/src/tools/dex-analysis.tools.ts: -------------------------------------------------------------------------------- ```typescript import { z } from 'zod' import { fetchCloudflareApi } from '@repo/mcp-common/src/cloudflare-api' import { getEnv } from '@repo/mcp-common/src/env' import { getProps } from '@repo/mcp-common/src/get-props' import { getReader } from '../warp_diag_reader' import type { ToolCallback } from '@modelcontextprotocol/sdk/server/mcp.js' import type { ToolAnnotations } from '@modelcontextprotocol/sdk/types.js' import type { ZodRawShape, ZodTypeAny } from 'zod' import type { CloudflareDEXMCP } from '../dex-analysis.app' import type { Env } from '../dex-analysis.context' const env = getEnv<Env>() export function registerDEXTools(agent: CloudflareDEXMCP) { registerTool({ name: 'dex_test_statistics', description: 'Analyze Cloudflare DEX Test Results by quartile given a Test ID', schema: { testId: z.string().describe('The DEX Test ID to analyze details of.'), from: timeStartParam, to: timeEndParam, }, llmContext: "The quartiles are sorted by 'resource fetch time' from LEAST performant in quartile 1 to MOST performant in quartile 4. For each quartile-based entry, it provides extensive information about the up-to-20 specific test results that are within that quartile of performance.", agent, callback: async ({ accountId, accessToken, ...params }) => { return await fetchCloudflareApi({ endpoint: `/dex/test-results/by-quartile?${new URLSearchParams({ ...(params as Record<string, string>) })}`, accountId, apiToken: accessToken, options: { method: 'GET', headers: { 'Content-Type': 'application/json', }, }, }) }, }) registerTool({ name: 'dex_list_tests', description: 'Retrieve a list of all Cloudflare DEX Tests configured.', agent, schema: { page: pageParam }, callback: async ({ accountId, accessToken, page }) => { return await fetchCloudflareApi({ endpoint: `/dex/tests/overview?page=${page}&per_page=50`, accountId, apiToken: accessToken, options: { method: 'GET', headers: { 'Content-Type': 'application/json', }, }, }) }, }) registerTool({ name: 'dex_http_test_details', description: 'Retrieve detailed time series results for an HTTP DEX test by id.', schema: { testId: z.string().describe('The HTTP DEX Test ID to get details for.'), deviceId: z .string() .optional() .describe( "Optionally limit results to specific device(s). Can't be used in conjunction with the colo parameter." ), colo: z .string() .optional() .describe('Optionally limit results to a specific Cloudflare colo.'), from: timeStartParam, to: timeEndParam, interval: aggregationIntervalParam, }, agent, callback: async ({ testId, accountId, accessToken, ...params }) => { return await fetchCloudflareApi({ endpoint: `/dex/http-tests/${testId}?${new URLSearchParams({ ...(params as Record<string, string>) })}`, accountId, apiToken: accessToken, options: { method: 'GET', headers: { 'Content-Type': 'application/json', }, }, }) }, }) registerTool({ name: 'dex_traceroute_test_details', description: 'Retrieve detailed time series results for a Traceroute DEX test by id.', schema: { testId: z.string().describe('The traceroute DEX Test ID to get details for.'), deviceId: z .string() .optional() .describe( "Optionally limit results to specific device(s). Can't be used in conjunction with the colo parameter." ), colo: z .string() .optional() .describe('Optionally limit results to a specific Cloudflare colo.'), timeStart: timeStartParam, timeEnd: timeEndParam, interval: aggregationIntervalParam, }, agent, callback: async ({ testId, accountId, accessToken, ...params }) => { return await fetchCloudflareApi({ endpoint: `/dex/traceroute-tests/${testId}?${new URLSearchParams({ ...(params as Record<string, string>) })}`, accountId, apiToken: accessToken, options: { method: 'GET', headers: { 'Content-Type': 'application/json', }, }, }) }, }) registerTool({ name: 'dex_traceroute_test_network_path', description: 'Retrieve aggregate network path data for a Traceroute DEX test by id. Use the dex_traceroute_test_result_network_path tool to further explore individual test runs hop-by-hop.', schema: { testId: z.string().describe('The traceroute DEX Test ID to get network path details for.'), deviceId: z.string().describe('The ID of the device to get network path details for.'), from: timeStartParam, to: timeEndParam, interval: aggregationIntervalParam, }, agent, callback: async ({ testId, accountId, accessToken, ...params }) => { return await fetchCloudflareApi({ endpoint: `/dex/traceroute-tests/${testId}/network-path?${new URLSearchParams({ ...(params as unknown as Record<string, string>) })}`, accountId, apiToken: accessToken, options: { method: 'GET', headers: { 'Content-Type': 'application/json', }, }, }) }, }) registerTool({ name: 'dex_traceroute_test_result_network_path', description: 'Retrieve the hop-by-hop network path for a specific Traceroute DEX test result by id.', schema: { testResultId: z .string() .describe('The traceroute DEX Test Result ID to get network path details for.'), }, agent, callback: async ({ testResultId, accountId, accessToken }) => { return await fetchCloudflareApi({ endpoint: `/dex/traceroute-test-results/${testResultId}/network-path`, accountId, apiToken: accessToken, options: { method: 'GET', headers: { 'Content-Type': 'application/json', }, }, }) }, }) registerTool({ name: 'dex_list_remote_capture_eligible_devices', description: "Retrieve a list of devices eligible for remote captures. You'll need the device_id and user_email from this " + 'response in order to create a remote capture for a specific device. It can also be used as a generic source to find ' + 'devices registered to the account, filtering by user email if necessary.', schema: { page: pageParam, search: z.string().optional().describe('Filter devices by name or email.'), }, agent, callback: async ({ accountId, accessToken, ...params }) => { return await fetchCloudflareApi({ endpoint: `/dex/commands/devices?${new URLSearchParams({ ...(params as unknown as Record<string, string>) })}&per_page=50`, accountId, apiToken: accessToken, options: { method: 'GET', headers: { 'Content-Type': 'application/json', }, }, }) }, }) registerTool({ name: 'dex_create_remote_pcap', description: 'Create a remote packet capture (PCAP) for a device. This is a resource intensive and privacy-sensitive operation on a real user device.' + 'Always ask for confirmation from the user that the targeted email and device are correct before executing a capture', schema: { device_id: z.string().describe('The device ID to target.'), user_email: z.string().describe('The email of the user associated with the device.'), 'max-file-size-mb': z .number() .min(1) .default(5) .optional() .describe( 'Maximum file size in MB for the capture file. Specifies the maximum file size of the warp-daig zip artifact that can be uploaded. ' + 'If the zip artifact exceeds the specified max file size it will NOT be uploaded.' ), 'packet-size-bytes': z .number() .min(1) .default(160) .optional() .describe('Maximum number of bytes to save for each packet.'), 'time-limit-min': z .number() .min(1) .default(5) .describe('Limit on capture duration in minutes'), }, agent, llmContext: 'If the request was successful, the capture has been initiated. You can poll the dex_list_remote_commands tool periodically to check on the completion status.', callback: async ({ accountId, accessToken, device_id, user_email, ...command_args }) => { return await fetchCloudflareApi({ endpoint: `/dex/commands`, accountId, apiToken: accessToken, options: { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ commands: [ { type: 'pcap', device_id, user_email, args: command_args, version: 1, }, ], }), }, }) }, }) registerTool({ name: 'dex_create_remote_warp_diag', description: 'Create a remote Warp Diagnostic (WARP-diag) for a device. This is a resource intensive and privacy-sensitive operation on a real user device.' + 'Always ask for confirmation from the user that the targeted email and device are correct before executing a capture', schema: { device_id: z.string().describe('The device ID to target.'), user_email: z.string().describe('The email of the user associated with the device.'), 'test-all-routes': z .boolean() .default(true) .describe( 'Test an IP address from all included or excluded ranges. Tests an IP address from all included or excluded ranges.' + "Essentially the same as running 'route get '' and collecting the results. This option may increase the time taken to collect the warp-diag" ), }, agent, llmContext: 'If the request was successful, the diagnostic has been initiated. You can poll the dex_list_remote_commands tool periodically to check on the completion status.' + 'See https://developers.cloudflare.com/cloudflare-one/connections/connect-devices/warp/troubleshooting/warp-logs/ for more info on warp-diags', callback: async ({ accountId, accessToken, device_id, user_email, ...command_args }) => { return await fetchCloudflareApi({ endpoint: `/dex/commands`, accountId, apiToken: accessToken, options: { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ commands: [ { type: 'warp-diag', device_id, user_email, args: command_args, version: 1, }, ], }), }, }) }, }) registerTool({ name: 'dex_list_remote_captures', description: 'Retrieve a list of remote captures for device debugging, like PCAPs or WARP Diags.', schema: { page: pageParam }, agent, callback: async ({ accountId, accessToken, page }) => { return await fetchCloudflareApi({ endpoint: `/dex/commands?page=${page}&per_page=50`, accountId, apiToken: accessToken, options: { method: 'GET', headers: { 'Content-Type': 'application/json', }, }, }) }, }) registerTool({ name: 'dex_fleet_status_live', description: 'Retrieve details about the real-time status of the fleet of devices broken down by dimension (mode, status, colo, platform, version)', schema: { since_minutes: z .number() .min(1) .max(60) .default(10) .describe( 'Number of minutes before current time to use as cutoff for device states to include.' ), colo: z .string() .optional() .describe('Optionally filter results to a specific Cloudflare colo.'), }, agent, callback: async ({ accountId, accessToken, ...params }) => { return await fetchCloudflareApi({ endpoint: `/dex/fleet-status/live?${new URLSearchParams({ ...(params as unknown as Record<string, string>) })}`, accountId, apiToken: accessToken, options: { method: 'GET', headers: { 'Content-Type': 'application/json', }, }, }) }, }) registerTool({ name: 'dex_fleet_status_over_time', description: 'Retrieve aggregate time series details about the status of the fleet of devices, or performance metrics for a specific device, over the specified time period.', schema: { from: timeStartParam, to: timeEndParam, interval: aggregationIntervalParam, colo: z .string() .optional() .describe('Filter results to WARP devices connected to a specific colo.'), device_id: z.string().optional().describe('Filter results to a specific device.'), }, agent, callback: async ({ accountId, accessToken, ...params }) => { return await fetchCloudflareApi({ endpoint: `/dex/fleet-status/over-time?${new URLSearchParams({ ...(params as Record<string, string>) })}`, accountId, apiToken: accessToken, options: { method: 'GET', headers: { 'Content-Type': 'application/json', }, }, }) }, }) registerTool({ name: 'dex_fleet_status_logs', description: 'Retrieve raw fleet status device logs with a variety of levels of granularity and filtering. Use `source=last_seen` to view logs showing the last known ' + 'state per device within the specified time period. Use `source=hourly` to view logs showing an hourly rollup per device where values are the average value of all' + 'events within the time period. Use `source=raw` to view all logs for the specified period.', schema: { page: pageParam, from: timeStartParam, to: timeEndParam, source: z .enum(['last_seen', 'hourly', 'raw']) .describe('Specifies the granularity of results.'), colo: z .string() .optional() .describe('Filter results to WARP devices connected to a specific colo.'), device_id: z.string().optional().describe('Filter results to a specific device.'), mode: z.string().optional().describe('Filter results to devices with a specific WARP mode.'), platform: z .string() .optional() .describe('Filter results to devices on a specific operating system.'), status: z .string() .optional() .describe('Filter results to devices with a specific WARP connection status.'), version: z .string() .optional() .describe('Filter results to devices with a specific WARP client version.'), }, agent, callback: async ({ accountId, accessToken, ...params }) => { return await fetchCloudflareApi({ endpoint: `/dex/fleet-status/devices?${new URLSearchParams({ ...(params as unknown as Record<string, string>) })}&per_page=50`, accountId, apiToken: accessToken, options: { method: 'GET', headers: { 'Content-Type': 'application/json', }, }, }) }, }) registerTool({ name: 'dex_list_warp_change_events', description: 'View logs of events when users toggle WARP on or off, or change configurations.', schema: { from: timeStartParam, to: timeEndParam, page: pageParam, account_name: z.string().optional().describe('Optionally filter events by account name.'), config_name: z .string() .optional() .describe( 'Optionally filter events by WARP configuration name changed from or to. Applicable to `type=config` events only.' ), sort_order: z .enum(['ASC', 'DESC']) .optional() .default('ASC') .describe('Set timestamp sort order.'), toggle: z .enum(['on', 'off']) .optional() .describe( 'Optionally filter events by toggle value. Applicable to `type=toggle` events only.' ), type: z.enum(['config', 'toggle']).optional().describe('Optionally filter events by type.'), }, agent, callback: async ({ accountId, accessToken, ...params }) => { return await fetchCloudflareApi({ endpoint: `/dex/warp-change-events?${new URLSearchParams({ ...(params as unknown as Record<string, string>) })}&per_page=50`, accountId, apiToken: accessToken, options: { method: 'GET', headers: { 'Content-Type': 'application/json', }, }, }) }, }) registerTool({ name: 'dex_list_colos', description: 'View a list of Cloudflare colos sorted alphabetically or by frequency encountered in fleet status or DEX test data.', schema: { from: timeStartParam, to: timeEndParam, sortBy: z .enum(['fleet-status-usage', 'application-tests-usage']) .optional() .describe( 'Use `fleet-status-usage` to sort by frequency seen in device state checkins.' + 'Use `application-tests-usage` to sort by frequency seen in DEX test results. Omit to sort alphabetically.' ), }, agent, callback: async ({ accountId, accessToken, ...params }) => { return await fetchCloudflareApi({ endpoint: `/dex/colos?${new URLSearchParams({ ...(params as unknown as Record<string, string>) })}`, accountId, apiToken: accessToken, options: { method: 'GET', headers: { 'Content-Type': 'application/json', }, }, }) }, }) registerTool({ name: 'dex_list_remote_warp_diag_contents', description: 'Given a WARP diag remote capture download url, returns a list of the files contained in the archive.', schema: { download: z .string() .describe( 'The `filename` url from the dex_list_remote_captures response for successful WARP diag captures.' ), }, llmContext: 'Use the dex_explore_remote_warp_diag_output tool for specific file paths to explore the file contents for analysis. ' + 'Hint: you can call dex_explore_remote_warp_diag_output multiple times in parallel if necessary to take advantage of in-memory caching for best performance.' + 'See https://developers.cloudflare.com/cloudflare-one/connections/connect-devices/warp/troubleshooting/warp-logs/ for more info on warp-diags', agent, callback: async ({ accessToken, download }) => { const reader = await getReader(env, accessToken, download) return await reader.list(accessToken, download) }, }) registerTool({ name: 'dex_explore_remote_warp_diag_output', description: 'Explore the contents of remote capture WARP diag archive filepaths returned by the dex_list_remote_warp_diag_contents tool for analysis.', schema: { download: z .string() .describe( 'The `filename` url from the dex_list_remote_captures response for successful WARP diag captures.' ), filepath: z.string().describe('The file path from the archive to retrieve contents for.'), }, llmContext: 'To avoid hitting conversation and memory limits, avoid outputting the whole contents of these files to the user unless specifically asked to. Instead prefer to show relevant snippets only.', agent, callback: async ({ accessToken, download, filepath }) => { const reader = await getReader(env, accessToken, download) return await reader.read(accessToken, download, filepath) }, }) registerTool({ name: 'dex_analyze_warp_diag', description: 'Analyze successful WARP-diag remote captures for common issues. This should be the first place you start when trying to narrow down device-level issues with WARP.', schema: { command_id: z .string() .describe('The command_id of the successful WARP-diag remote capture to analyze.'), }, llmContext: 'Detections with 0 occurences can be ruled out. Focus on detections with the highest severity.', agent, callback: async ({ accessToken, accountId, command_id }) => { return await fetchCloudflareApi({ endpoint: `/dex/commands/${command_id}/analysis`, accountId, apiToken: accessToken, options: { method: 'GET', headers: { 'Content-Type': 'application/json', }, }, }) }, }) } // Helper to simplify tool registration by reducing boilerplate for accountId and accessToken const registerTool = <T extends ZodRawShape, U = unknown>({ name, description, agent, callback, schema = {}, llmContext = '', }: { name: string description: string schema?: T | ToolAnnotations llmContext?: string agent: CloudflareDEXMCP callback: ( p: { extra: unknown; accountId: string; accessToken: string } & z.objectOutputType< T, ZodTypeAny > ) => Promise<U> }) => { agent.server.tool<T>(name, description, schema, (async (params, extra) => { const accountId = await agent.getActiveAccountId() if (!accountId) { return { content: [ { type: 'text', text: 'No currently active accountId. Try listing your accounts (accounts_list) and then setting an active account (set_active_account)', }, ], } } try { const props = getProps(agent) const accessToken = props.accessToken const res = await callback({ ...(params as T), extra, accountId, accessToken }) return { content: [ { type: 'text', text: JSON.stringify({ data: res, llmContext, }), }, ], } } catch (error) { return { content: [ { type: 'text', text: JSON.stringify({ error: `Error with tool ${name}: ${error instanceof Error && error.message}`, }), }, ], } } }) as ToolCallback<T>) } // Shared parameter schemas const timeStartParam = z .string() .describe( 'The datetime of the beginning point of time range for results. Must be in ISO 8601 datetime string in the extended format with UTC time (e.g, 2025-04-21T18:00:00Z).' ) const timeEndParam = z .string() .describe( 'The datetime of the ending point of time range for results. Must be in ISO 8601 datetime string in the extended format with UTC time (e.g, 2025-04-22T00:00:00Z).' ) const aggregationIntervalParam = z .enum(['minute', 'hour']) .describe('The time interval to group results by.') const pageParam = z.number().min(1).describe('The page of results to retrieve.') ``` -------------------------------------------------------------------------------- /packages/mcp-common/src/tools/r2_bucket.tools.ts: -------------------------------------------------------------------------------- ```typescript import { getCloudflareClient } from '../cloudflare-api' import { MISSING_ACCOUNT_ID_RESPONSE } from '../constants' import { getProps } from '../get-props' import { type CloudflareMcpAgent } from '../types/cloudflare-mcp-agent.types' import { BucketListCursorParam, BucketListDirectionParam, BucketListNameContainsParam, BucketListStartAfterParam, BucketNameSchema, } from '../types/r2_bucket.types' import { PaginationPerPageParam } from '../types/shared.types' export function registerR2BucketTools(agent: CloudflareMcpAgent) { agent.server.tool( 'r2_buckets_list', 'List r2 buckets in your Cloudflare account', { cursor: BucketListCursorParam, direction: BucketListDirectionParam, name_contains: BucketListNameContainsParam, per_page: PaginationPerPageParam, start_after: BucketListStartAfterParam, }, { title: 'List R2 buckets', annotations: { readOnlyHint: true, }, }, async ({ cursor, direction, name_contains, per_page, start_after }) => { const account_id = await agent.getActiveAccountId() if (!account_id) { return MISSING_ACCOUNT_ID_RESPONSE } try { const props = getProps(agent) const client = getCloudflareClient(props.accessToken) const listResponse = await client.r2.buckets.list({ account_id, cursor: cursor ?? undefined, direction: direction ?? undefined, name_contains: name_contains ?? undefined, per_page: per_page ?? undefined, start_after: start_after ?? undefined, }) return { content: [ { type: 'text', text: JSON.stringify({ buckets: listResponse.buckets, count: listResponse.buckets?.length ?? 0, }), }, ], } } catch (error) { return { content: [ { type: 'text', text: `Error listing R2 buckets: ${error instanceof Error && error.message}`, }, ], } } } ) agent.server.tool( 'r2_bucket_create', 'Create a new r2 bucket in your Cloudflare account', { name: BucketNameSchema }, { title: 'Create R2 bucket', annotations: { readOnlyHint: false, destructiveHint: false, }, }, async ({ name }) => { const account_id = await agent.getActiveAccountId() if (!account_id) { return MISSING_ACCOUNT_ID_RESPONSE } try { const props = getProps(agent) const client = getCloudflareClient(props.accessToken) const bucket = await client.r2.buckets.create({ account_id, name, }) return { content: [ { type: 'text', text: JSON.stringify(bucket), }, ], } } catch (error) { return { content: [ { type: 'text', text: `Error creating KV namespace: ${error instanceof Error && error.message}`, }, ], } } } ) agent.server.tool( 'r2_bucket_get', 'Get details about a specific R2 bucket', { name: BucketNameSchema }, { title: 'Get R2 bucket', annotations: { readOnlyHint: true, }, }, async ({ name }) => { const account_id = await agent.getActiveAccountId() if (!account_id) { return MISSING_ACCOUNT_ID_RESPONSE } try { const props = getProps(agent) const client = getCloudflareClient(props.accessToken) const bucket = await client.r2.buckets.get(name, { account_id }) return { content: [ { type: 'text', text: JSON.stringify(bucket), }, ], } } catch (error) { return { content: [ { type: 'text', text: `Error getting R2 bucket: ${error instanceof Error && error.message}`, }, ], } } } ) agent.server.tool( 'r2_bucket_delete', 'Delete an R2 bucket', { name: BucketNameSchema }, { title: 'Delete R2 bucket', annotations: { readOnlyHint: false, destructiveHint: true, }, }, async ({ name }) => { const account_id = await agent.getActiveAccountId() if (!account_id) { return MISSING_ACCOUNT_ID_RESPONSE } try { const props = getProps(agent) const client = getCloudflareClient(props.accessToken) const result = await client.r2.buckets.delete(name, { account_id }) return { content: [ { type: 'text', text: JSON.stringify(result), }, ], } } catch (error) { return { content: [ { type: 'text', text: `Error deleting R2 bucket: ${error instanceof Error && error.message}`, }, ], } } } ) // Commenting out non-CRUD tools for now to keep the bindings MCP surface small // agent.server.tool( // 'r2_bucket_cors_get', // 'Get CORS configuration for an R2 bucket', // { // name: BucketNameSchema, // params: CorsGetParamsSchema.optional(), // }, // async ({ name, params }) => { // const account_id = await agent.getActiveAccountId() // if (!account_id) { // return MISSING_ACCOUNT_ID_RESPONSE // } // try { // const client = getCloudflareClient(props.accessToken) // const cors = await client.r2.buckets.cors.get(name, { // account_id, // ...params, // }) // return { // content: [ // { // type: 'text', // text: JSON.stringify(cors), // }, // ], // } // } catch (error) { // return { // content: [ // { // type: 'text', // text: `Error getting R2 bucket CORS configuration: ${error instanceof Error && error.message}`, // }, // ], // } // } // } // ) // agent.server.tool( // 'r2_bucket_cors_update', // 'Update CORS configuration for an R2 bucket', // { // name: BucketNameSchema, // cors_config: CorsRulesSchema, // }, // async ({ name, cors_config }) => { // const account_id = await agent.getActiveAccountId() // if (!account_id) { // return MISSING_ACCOUNT_ID_RESPONSE // } // try { // const client = getCloudflareClient(props.accessToken) // const result = await client.r2.buckets.cors.update(name, { // account_id, // ...cors_config, // }) // return { // content: [ // { // type: 'text', // text: JSON.stringify(result), // }, // ], // } // } catch (error) { // return { // content: [ // { // type: 'text', // text: `Error updating R2 bucket CORS configuration: ${error instanceof Error && error.message}`, // }, // ], // } // } // } // ) // agent.server.tool( // 'r2_bucket_cors_delete', // 'Delete CORS configuration for an R2 bucket', // { // name: BucketNameSchema, // params: CorsDeleteParamsSchema.optional(), // }, // async ({ name, params }) => { // const account_id = await agent.getActiveAccountId() // if (!account_id) { // return MISSING_ACCOUNT_ID_RESPONSE // } // try { // const client = getCloudflareClient(props.accessToken) // const result = await client.r2.buckets.cors.delete(name, { // account_id, // ...params, // }) // return { // content: [ // { // type: 'text', // text: JSON.stringify(result), // }, // ], // } // } catch (error) { // return { // content: [ // { // type: 'text', // text: `Error deleting R2 bucket CORS configuration: ${error instanceof Error && error.message}`, // }, // ], // } // } // } // ) // agent.server.tool( // 'r2_bucket_domains_list', // 'List all of the domains for an R2 bucket', // { name: BucketNameSchema, params: CustomDomainListParamsSchema.optional() }, // async ({ name, params }) => { // const account_id = await agent.getActiveAccountId() // if (!account_id) { // return MISSING_ACCOUNT_ID_RESPONSE // } // try { // const client = getCloudflareClient(props.accessToken) // const domains = await client.r2.buckets.domains.custom.list(name, { account_id, ...params }) // return { // content: [ // { // type: 'text', // text: JSON.stringify(domains), // }, // ], // } // } catch (error) { // return { // content: [ // { // type: 'text', // text: `Error listing R2 bucket domains: ${error instanceof Error && error.message}`, // }, // ], // } // } // } // ) // agent.server.tool( // 'r2_bucket_domains_get', // 'Get details about a specific domain for an R2 bucket', // { // name: BucketNameSchema, // domain: CustomDomainNameSchema, // params: CustomDomainGetParamsSchema.optional(), // }, // async ({ name, domain, params }) => { // const account_id = await agent.getActiveAccountId() // if (!account_id) { // return MISSING_ACCOUNT_ID_RESPONSE // } // try { // const client = getCloudflareClient(props.accessToken) // const result = await client.r2.buckets.domains.custom.get(name, domain, { // account_id, // ...params, // }) // return { // content: [ // { // type: 'text', // text: JSON.stringify(result), // }, // ], // } // } catch (error) { // return { // content: [ // { // type: 'text', // text: `Error getting R2 bucket domain: ${error instanceof Error && error.message}`, // }, // ], // } // } // } // ) // agent.server.tool( // 'r2_bucket_domains_create', // 'Create a new domain for an R2 bucket', // { name: BucketNameSchema, params: CustomDomainCreateParamsSchema }, // async ({ name, params }) => { // const account_id = await agent.getActiveAccountId() // if (!account_id) { // return MISSING_ACCOUNT_ID_RESPONSE // } // try { // const client = getCloudflareClient(props.accessToken) // const result = await client.r2.buckets.domains.custom.create(name, { // account_id, // ...params, // }) // return { // content: [ // { // type: 'text', // text: JSON.stringify(result), // }, // ], // } // } catch (error) { // return { // content: [ // { // type: 'text', // text: `Error creating R2 bucket domain: ${error instanceof Error && error.message}`, // }, // ], // } // } // } // ) // agent.server.tool( // 'r2_bucket_domains_delete', // 'Delete a domain for an R2 bucket', // { // name: BucketNameSchema, // domain: CustomDomainNameSchema, // params: CustomDomainDeleteParamsSchema.optional(), // }, // async ({ name, domain, params }) => { // const account_id = await agent.getActiveAccountId() // if (!account_id) { // return MISSING_ACCOUNT_ID_RESPONSE // } // try { // const client = getCloudflareClient(props.accessToken) // const result = await client.r2.buckets.domains.custom.delete(name, domain, { // account_id, // ...params, // }) // return { // content: [ // { // type: 'text', // text: JSON.stringify(result), // }, // ], // } // } catch (error) { // return { // content: [ // { // type: 'text', // text: `Error deleting R2 bucket domain: ${error instanceof Error && error.message}`, // }, // ], // } // } // } // ) // agent.server.tool( // 'r2_bucket_domains_update', // 'Update a domain for an R2 bucket', // { // name: BucketNameSchema, // domain: CustomDomainNameSchema, // params: CustomDomainUpdateParamsSchema, // }, // async ({ name, domain, params }) => { // const account_id = await agent.getActiveAccountId() // if (!account_id) { // return MISSING_ACCOUNT_ID_RESPONSE // } // try { // const client = getCloudflareClient(props.accessToken) // const result = await client.r2.buckets.domains.custom.update(name, domain, { // account_id, // ...params, // }) // return { // content: [ // { // type: 'text', // text: JSON.stringify(result), // }, // ], // } // } catch (error) { // return { // content: [ // { // type: 'text', // text: `Error updating R2 bucket domain: ${error instanceof Error && error.message}`, // }, // ], // } // } // } // ) // agent.server.tool( // 'r2_bucket_event_notifications_get', // 'Get event notifications for an R2 bucket', // { name: BucketNameSchema, params: EventNotificationGetParamsSchema.optional() }, // async ({ name, params }) => { // const account_id = await agent.getActiveAccountId() // if (!account_id) { // return MISSING_ACCOUNT_ID_RESPONSE // } // try { // const client = getCloudflareClient(props.accessToken) // const result = await client.r2.buckets.eventNotifications.get(name, { // account_id, // ...params, // }) // return { // content: [ // { // type: 'text', // text: JSON.stringify(result), // }, // ], // } // } catch (error) { // return { // content: [ // { // type: 'text', // text: `Error getting R2 bucket event notifications: ${error instanceof Error && error.message}`, // }, // ], // } // } // } // ) // agent.server.tool( // 'r2_bucket_event_notifications_update', // 'Update event notifications for an R2 bucket', // { // name: BucketNameSchema, // queueId: QueueIdSchema, // params: EventNotificationUpdateParamsSchema.optional(), // }, // async ({ name, queueId, params }) => { // const account_id = await agent.getActiveAccountId() // if (!account_id) { // return MISSING_ACCOUNT_ID_RESPONSE // } // try { // const client = getCloudflareClient(props.accessToken) // const result = await client.r2.buckets.eventNotifications.update(name, queueId, { // account_id, // ...params, // }) // return { // content: [ // { // type: 'text', // text: JSON.stringify(result), // }, // ], // } // } catch (error) { // return { // content: [ // { // type: 'text', // text: `Error updating R2 bucket event notifications: ${error instanceof Error && error.message}`, // }, // ], // } // } // } // ) // agent.server.tool( // 'r2_bucket_event_notifications_delete', // 'Delete event notifications for an R2 bucket', // { // name: BucketNameSchema, // queueId: QueueIdSchema, // params: EventNotificationDeleteParamsSchema.optional(), // }, // async ({ name, queueId, params }) => { // const account_id = await agent.getActiveAccountId() // if (!account_id) { // return MISSING_ACCOUNT_ID_RESPONSE // } // try { // const client = getCloudflareClient(props.accessToken) // const result = await client.r2.buckets.eventNotifications.delete(name, queueId, { // account_id, // ...params, // }) // return { // content: [ // { // type: 'text', // text: JSON.stringify(result), // }, // ], // } // } catch (error) { // return { // content: [ // { // type: 'text', // text: `Error deleting R2 bucket event notifications: ${error instanceof Error && error.message}`, // }, // ], // } // } // } // ) // agent.server.tool( // 'r2_bucket_locks_get', // 'Get locks for an R2 bucket', // { name: BucketNameSchema, params: LockGetParamsSchema.optional() }, // async ({ name, params }) => { // const account_id = await agent.getActiveAccountId() // if (!account_id) { // return MISSING_ACCOUNT_ID_RESPONSE // } // try { // const client = getCloudflareClient(props.accessToken) // const result = await client.r2.buckets.locks.get(name, { account_id, ...params }) // return { // content: [ // { // type: 'text', // text: JSON.stringify(result), // }, // ], // } // } catch (error) { // return { // content: [ // { // type: 'text', // text: `Error getting R2 bucket locks: ${error instanceof Error && error.message}`, // }, // ], // } // } // } // ) // agent.server.tool( // 'r2_bucket_locks_update', // 'Update locks for an R2 bucket', // { name: BucketNameSchema, params: LockUpdateParamsSchema }, // async ({ name, params }) => { // const account_id = await agent.getActiveAccountId() // if (!account_id) { // return MISSING_ACCOUNT_ID_RESPONSE // } // try { // const client = getCloudflareClient(props.accessToken) // const result = await client.r2.buckets.locks.update(name, { account_id, ...params }) // return { // content: [ // { // type: 'text', // text: JSON.stringify(result), // }, // ], // } // } catch (error) { // return { // content: [ // { // type: 'text', // text: `Error updating R2 bucket locks: ${error instanceof Error && error.message}`, // }, // ], // } // } // } // ) // agent.server.tool( // 'r2_bucket_temporary_credentials_create', // 'Create temporary credentials for an R2 bucket', // { params: TemporaryCredentialsCreateParamsSchema }, // async ({ params }) => { // const account_id = await agent.getActiveAccountId() // if (!account_id) { // return MISSING_ACCOUNT_ID_RESPONSE // } // try { // const client = getCloudflareClient(props.accessToken) // const result = await client.r2.temporaryCredentials.create({ // account_id, // ...params, // }) // return { // content: [ // { // type: 'text', // text: JSON.stringify(result), // }, // ], // } // } catch (error) { // return { // content: [ // { // type: 'text', // text: `Error creating temporary credentials for R2 bucket: ${error instanceof Error && error.message}`, // }, // ], // } // } // } // ) // agent.server.tool('r2_metrics_list', 'List metrics for an R2 bucket', async () => { // const account_id = await agent.getActiveAccountId() // if (!account_id) { // return MISSING_ACCOUNT_ID_RESPONSE // } // try { // const client = getCloudflareClient(props.accessToken) // const result = await client.r2.buckets.metrics.list({ account_id }) // return { // content: [ // { // type: 'text', // text: JSON.stringify(result), // }, // ], // } // } catch (error) { // return { // content: [ // { // type: 'text', // text: `Error listing R2 bucket metrics: ${error instanceof Error && error.message}`, // }, // ], // } // } // }) // agent.server.tool( // 'r2_sippy_get', // 'Get configuration for sippy for an R2 bucket', // { bucketName: BucketNameSchema, params: SippyGetParamsSchema.optional() }, // async ({ bucketName, params }) => { // const account_id = await agent.getActiveAccountId() // if (!account_id) { // return MISSING_ACCOUNT_ID_RESPONSE // } // try { // const client = getCloudflareClient(props.accessToken) // const result = await client.r2.buckets.sippy.get(bucketName, { account_id, ...params }) // console.log('sippy get result', result) // return { // content: [ // { // type: 'text', // text: JSON.stringify(result ?? null), // }, // ], // } // } catch (error) { // return { // content: [ // { // type: 'text', // text: `Error getting R2 bucket sippy: ${error instanceof Error && error.message}`, // }, // ], // } // } // } // ) // agent.server.tool( // 'r2_sippy_update', // 'Update configuration for sippy for an R2 bucket', // { bucketName: BucketNameSchema, params: SippyUpdateParamsSchema }, // async ({ bucketName, params }) => { // const account_id = await agent.getActiveAccountId() // if (!account_id) { // return MISSING_ACCOUNT_ID_RESPONSE // } // try { // const client = getCloudflareClient(props.accessToken) // const result = await client.r2.buckets.sippy.update(bucketName, { account_id, ...params }) // return { // content: [ // { // type: 'text', // text: JSON.stringify(result), // }, // ], // } // } catch (error) { // return { // content: [ // { // type: 'text', // text: `Error updating R2 bucket sippy: ${error instanceof Error && error.message}`, // }, // ], // } // } // } // ) // agent.server.tool( // 'r2_sippy_delete', // 'Delete sippy for an R2 bucket', // { bucketName: BucketNameSchema }, // async ({ bucketName }) => { // const account_id = await agent.getActiveAccountId() // if (!account_id) { // return MISSING_ACCOUNT_ID_RESPONSE // } // try { // const client = getCloudflareClient(props.accessToken) // const result = await client.r2.buckets.sippy.delete(bucketName, { account_id }) // return { // content: [ // { // type: 'text', // text: JSON.stringify(result), // }, // ], // } // } catch (error) { // return { // content: [ // { // type: 'text', // text: `Error deleting R2 bucket sippy: ${error instanceof Error && error.message}`, // }, // ], // } // } // } // ) } ``` -------------------------------------------------------------------------------- /apps/graphql/src/tools/graphql.tools.ts: -------------------------------------------------------------------------------- ```typescript import * as LZString from 'lz-string' import { z } from 'zod' import { getProps } from '@repo/mcp-common/src/get-props' import type { GraphQLMCP } from '../graphql.app' // GraphQL API endpoint const CLOUDFLARE_GRAPHQL_ENDPOINT = 'https://api.cloudflare.com/client/v4/graphql' // Type definitions for GraphQL schema responses interface GraphQLTypeRef { kind: string name: string | null ofType?: GraphQLTypeRef | null } interface GraphQLField { name: string description: string | null args: Array<{ name: string description: string | null type: GraphQLTypeRef }> type: GraphQLTypeRef } interface GraphQLType { name: string kind: string description: string | null fields?: GraphQLField[] | null inputFields?: Array<{ name: string description: string | null type: GraphQLTypeRef }> | null interfaces?: Array<{ name: string }> | null enumValues?: Array<{ name: string description: string | null }> | null possibleTypes?: Array<{ name: string }> | null } interface SchemaOverviewResponse { data: { __schema: { queryType: { name: string } | null mutationType: { name: string } | null subscriptionType: { name: string } | null types: Array<{ name: string kind: string description: string | null }> } } } interface TypeDetailsResponse { data: { __type: GraphQLType } } // Define the structure of a single error const graphQLErrorSchema = z.object({ message: z.string(), path: z.array(z.union([z.string(), z.number()])), extensions: z.object({ code: z.string(), timestamp: z.string(), ray_id: z.string(), }), }) // Define the overall GraphQL response schema const graphQLResponseSchema = z.object({ data: z.union([z.record(z.unknown()), z.null()]), errors: z.union([z.array(graphQLErrorSchema), z.null()]), }) /** * Fetches the high-level overview of the GraphQL schema * @param apiToken Cloudflare API token * @returns Basic schema structure */ async function fetchSchemaOverview(apiToken: string): Promise<SchemaOverviewResponse> { const overviewQuery = ` query SchemaOverview { __schema { queryType { name } mutationType { name } subscriptionType { name } types { name kind description } } } ` const response = await executeGraphQLRequest<SchemaOverviewResponse>(overviewQuery, apiToken) return response } /** * Fetches detailed information about a specific GraphQL type * @param typeName The name of the type to fetch details for * @param apiToken Cloudflare API token * @returns Detailed type information */ async function fetchTypeDetails(typeName: string, apiToken: string): Promise<TypeDetailsResponse> { const typeDetailsQuery = ` query TypeDetails { __type(name: "${typeName}") { name kind description fields(includeDeprecated: false) { name description args { name description type { kind name ofType { kind name } } } type { kind name ofType { kind name ofType { kind name } } } } inputFields { name description type { kind name ofType { kind name } } } interfaces { name } enumValues(includeDeprecated: false) { name description } possibleTypes { name } } } ` const response = await executeGraphQLRequest<TypeDetailsResponse>(typeDetailsQuery, apiToken) return response } /** * Helper function to execute GraphQL requests * @param query GraphQL query to execute * @param apiToken Cloudflare API token * @returns Response data */ async function executeGraphQLRequest<T>(query: string, apiToken: string): Promise<T> { const response = await fetch(CLOUDFLARE_GRAPHQL_ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiToken}`, }, body: JSON.stringify({ query }), }) if (!response.ok) { throw new Error(`Failed to execute GraphQL request: ${response.statusText}`) } const data = graphQLResponseSchema.parse(await response.json()) // Check for GraphQL errors in the response if (data && data.errors && Array.isArray(data.errors) && data.errors.length > 0) { const errorMessages = data.errors.map((e: { message: string }) => e.message).join(', ') console.warn(`GraphQL errors: ${errorMessages}`) // If the error is about mutations not being supported, we can handle it gracefully if (errorMessages.includes('Mutations are not supported')) { console.info('Mutations are not supported by the Cloudflare GraphQL API') } } return data as T } /** * Executes a GraphQL query against Cloudflare's API * @param query The GraphQL query to execute * @param variables Variables for the query * @param apiToken Cloudflare API token * @returns The query results */ async function executeGraphQLQuery(query: string, variables: any, apiToken: string) { // Clone the variables to avoid modifying the original const queryVariables = { ...variables } const response = await fetch(CLOUDFLARE_GRAPHQL_ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiToken}`, }, body: JSON.stringify({ query, variables: queryVariables, }), }) if (!response.ok) { throw new Error(`Failed to execute GraphQL query: ${response.statusText}`) } const result = graphQLResponseSchema.parse(await response.json()) // Check for GraphQL errors in the response if (result && result.errors && Array.isArray(result.errors) && result.errors.length > 0) { const errorMessages = result.errors.map((e: { message: string }) => e.message).join(', ') console.warn(`GraphQL query errors: ${errorMessages}`) } return result } /** * Searches for matching types and fields in a GraphQL schema * @param schema The GraphQL schema to search * @param keyword The keyword to search for * @param typeDetails Optional map of type details for deeper searching * @returns Matching types and fields */ async function searchGraphQLSchema( schema: SchemaOverviewResponse, keyword: string, accountId: string, apiToken: string, maxDetailsToFetch: number = 10, onlyObjectTypes: boolean = true ) { const normalizedKeyword = keyword.toLowerCase() const results = { types: [] as Array<{ name: string kind: string description: string | null matchReason: string }>, fields: [] as Array<{ typeName: string fieldName: string description: string | null matchReason: string }>, enumValues: [] as Array<{ typeName: string enumValue: string description: string | null matchReason: string }>, args: [] as Array<{ typeName: string fieldName: string argName: string description: string | null matchReason: string }>, } // First pass: Search through type names and descriptions const matchingTypeNames: string[] = [] for (const type of schema.data.__schema.types || []) { // Skip internal types (those starting with __) if (type.name?.startsWith('__')) continue // Check if type name or description matches if (type.name?.toLowerCase().includes(normalizedKeyword)) { results.types.push({ ...type, matchReason: `Type name contains "${keyword}"`, }) matchingTypeNames.push(type.name) } else if (type.description?.toLowerCase().includes(normalizedKeyword)) { results.types.push({ ...type, matchReason: `Type description contains "${keyword}"`, }) matchingTypeNames.push(type.name) } } // Second pass: For potentially relevant types, fetch details and search deeper // Start with matching types, then add important schema types if we have capacity let typesToExamine = [...matchingTypeNames] // Add root operation types if they're not already included const rootTypes = [ schema.data.__schema.queryType?.name, schema.data.__schema.mutationType?.name, schema.data.__schema.subscriptionType?.name, ].filter(Boolean) as string[] for (const rootType of rootTypes) { if (!typesToExamine.includes(rootType)) { typesToExamine.push(rootType) } } // Add object types that might contain relevant fields const objectTypes = schema.data.__schema.types .filter((t) => { // If onlyObjectTypes is true, only include OBJECT types if (onlyObjectTypes) { return t.kind === 'OBJECT' && !t.name.startsWith('__') } // Otherwise include both OBJECT and INTERFACE types return (t.kind === 'OBJECT' || t.kind === 'INTERFACE') && !t.name.startsWith('__') }) .map((t) => t.name) // Combine all potential types to examine, but limit to a reasonable number typesToExamine = [...new Set([...typesToExamine, ...objectTypes])].slice(0, maxDetailsToFetch) // Fetch details for these types and search through their fields for (const typeName of typesToExamine) { try { const typeDetails = await fetchTypeDetails(typeName, apiToken) const type = typeDetails.data.__type if (!type) continue // Search through fields if (type.fields) { for (const field of type.fields) { // Check if field name or description matches if (field.name.toLowerCase().includes(normalizedKeyword)) { results.fields.push({ typeName: type.name, fieldName: field.name, description: field.description, matchReason: `Field name contains "${keyword}"`, }) } else if (field.description?.toLowerCase().includes(normalizedKeyword)) { results.fields.push({ typeName: type.name, fieldName: field.name, description: field.description, matchReason: `Field description contains "${keyword}"`, }) } // Search through field arguments if (field.args) { for (const arg of field.args) { if (arg.name.toLowerCase().includes(normalizedKeyword)) { results.args.push({ typeName: type.name, fieldName: field.name, argName: arg.name, description: arg.description, matchReason: `Argument name contains "${keyword}"`, }) } else if (arg.description?.toLowerCase().includes(normalizedKeyword)) { results.args.push({ typeName: type.name, fieldName: field.name, argName: arg.name, description: arg.description, matchReason: `Argument description contains "${keyword}"`, }) } } } } } // Search through enum values if (type.enumValues) { for (const enumValue of type.enumValues) { if (enumValue.name.toLowerCase().includes(normalizedKeyword)) { results.enumValues.push({ typeName: type.name, enumValue: enumValue.name, description: enumValue.description, matchReason: `Enum value contains "${keyword}"`, }) } else if (enumValue.description?.toLowerCase().includes(normalizedKeyword)) { results.enumValues.push({ typeName: type.name, enumValue: enumValue.name, description: enumValue.description, matchReason: `Enum value description contains "${keyword}"`, }) } } } } catch (error) { console.error(`Error fetching details for type ${typeName}:`, error) } } return results } /** * Registers GraphQL tools with the MCP server * @param agent The MCP agent instance */ export function registerGraphQLTools(agent: GraphQLMCP) { // Tool to search the GraphQL schema for types, fields, and enum values matching a keyword agent.server.tool( 'graphql_schema_search', `Search the Cloudflare GraphQL API schema for types, fields, and enum values matching a keyword Use this tool when: - You are unsure which dataset to use for your query. - A user is looking for specific types, fields, or enum values in the Cloudflare GraphQL API schema. IMPORTANT GUIDELINES: - DO NOT query for dimensions unless the user explicitly asked to group by or show dimensions. - Only include fields that the user specifically requested in their query. - Keep queries as simple as possible while fulfilling the user's request. Workflow: 1. Use this tool to search for dataset types by keyword. 2. When a relevant dataset type is found, immediately use graphql_schema_details to get the complete structure of that dataset. 3. After understanding the schema structure, proceed directly to constructing and executing queries using the graphql_query tool. 4. Do not use graphql_schema_overview or graphql_complete_schema after finding the relevant dataset - these are redundant steps. This tool searches the Cloudflare GraphQL API schema for any schema elements (such as object types, field names, or enum options) that match a given keyword. It returns schema fragments and definitions to assist in constructing valid and precise GraphQL queries. `, { keyword: z.string().describe('The keyword to search for in the schema'), maxDetailsToFetch: z .number() .min(1) .max(50) .default(10) .describe('Maximum number of types to fetch details for'), includeInternalTypes: z .boolean() .default(false) .describe( 'Whether to include internal types (those starting with __) in the search results' ), onlyObjectTypes: z .boolean() .default(true) .describe( 'Whether to only include OBJECT kind types in the search results with descriptions' ), }, async (params) => { const { keyword, maxDetailsToFetch = 10, includeInternalTypes = false, onlyObjectTypes = true, } = params const accountId = await agent.getActiveAccountId() if (!accountId) { return { content: [ { type: 'text', text: 'No currently active accountId. Try listing your accounts (accounts_list) and then setting an active account (set_active_account)', }, ], } } try { const props = getProps(agent) // First fetch the schema overview const schemaOverview = await fetchSchemaOverview(props.accessToken) // Search the schema for the keyword const searchResults = await searchGraphQLSchema( schemaOverview, keyword, accountId, props.accessToken, maxDetailsToFetch, onlyObjectTypes ) // Filter out internal types if requested if (!includeInternalTypes) { searchResults.types = searchResults.types.filter((t) => !t.name.startsWith('__')) searchResults.fields = searchResults.fields.filter((f) => !f.typeName.startsWith('__')) searchResults.enumValues = searchResults.enumValues.filter( (e) => !e.typeName.startsWith('__') ) searchResults.args = searchResults.args.filter((a) => !a.typeName.startsWith('__')) } // Filter out items without descriptions when onlyObjectTypes is true if (onlyObjectTypes) { searchResults.types = searchResults.types.filter((t) => { return t.description && t.description.trim() !== '' }) searchResults.fields = searchResults.fields.filter((f) => { return f.description && f.description.trim() !== '' }) searchResults.enumValues = searchResults.enumValues.filter((e) => { return e.description && e.description.trim() !== '' }) searchResults.args = searchResults.args.filter((a) => { return a.description && a.description.trim() !== '' }) } // Add summary information const results = { keyword, summary: { totalMatches: searchResults.types.length + searchResults.fields.length + searchResults.enumValues.length + searchResults.args.length, typeMatches: searchResults.types.length, fieldMatches: searchResults.fields.length, enumValueMatches: searchResults.enumValues.length, argumentMatches: searchResults.args.length, }, results: searchResults, } return { content: [ { type: 'text', text: JSON.stringify(results), }, ], } } catch (error) { return { content: [ { type: 'text', text: JSON.stringify({ error: `Error searching GraphQL schema: ${error instanceof Error ? error.message : String(error)}`, }), }, ], } } } ) // Tool to fetch the GraphQL schema overview (high-level structure) agent.server.tool( 'graphql_schema_overview', `Fetch the high-level overview of the Cloudflare GraphQL API schema Use this tool when: - A user requests insights into the structure or capabilities of Cloudflare’s GraphQL API. - You need to explore available types, queries, mutations, or schema relationships exposed by Cloudflare’s GraphQL interface. - You're generating or validating GraphQL queries against Cloudflare’s schema. - You are troubleshooting or developing integrations with Cloudflare’s API and require up-to-date schema information. This tool returns a high-level summary of the Cloudflare GraphQL API schema. It provides a structured outline of API entry points, data models, and relationships to help guide query construction or system integration. `, { pageSize: z .number() .min(10) .max(1000) .default(100) .describe('Number of types to return per page'), page: z.number().min(1).default(1).describe('Page number to fetch'), }, async (params) => { const { pageSize = 100, page = 1 } = params const accountId = await agent.getActiveAccountId() if (!accountId) { return { content: [ { type: 'text', text: 'No currently active accountId. Try listing your accounts (accounts_list) and then setting an active account (set_active_account)', }, ], } } try { const props = getProps(agent) const schemaOverview = await fetchSchemaOverview(props.accessToken) // Apply pagination to the types array const allTypes = schemaOverview.data.__schema.types || [] const totalTypes = allTypes.length const totalPages = Math.ceil(totalTypes / pageSize) // Calculate start and end indices for the current page const startIndex = (page - 1) * pageSize const endIndex = Math.min(startIndex + pageSize, totalTypes) // Create a paginated version of the schema const paginatedSchema = { data: { __schema: { queryType: schemaOverview.data.__schema.queryType, mutationType: schemaOverview.data.__schema.mutationType, subscriptionType: schemaOverview.data.__schema.subscriptionType, types: allTypes.slice(startIndex, endIndex), }, }, pagination: { page, pageSize, totalTypes, totalPages, hasNextPage: page < totalPages, hasPreviousPage: page > 1, }, } return { content: [ { type: 'text', text: JSON.stringify(paginatedSchema), }, ], } } catch (error) { return { content: [ { type: 'text', text: JSON.stringify({ error: `Error fetching GraphQL schema overview: ${error instanceof Error ? error.message : String(error)}`, }), }, ], } } } ) // Tool to fetch detailed information about a specific GraphQL type agent.server.tool( 'graphql_type_details', `Fetch detailed information about a specific GraphQL type (dataset) IMPORTANT: After exploring the schema, DO NOT generate overly complicated GraphQL queries that the user didn't explicitly ask for. Only include fields that were specifically requested. Use this tool when: - You need to explore the fields by the type name (dataset) for detailed information - You're building or debugging GraphQL queries and want to ensure the correct usage of schema components - You need contextual information about how a certain concept or object is represented in Cloudflare's GraphQL API. Guidelines for query construction: - Keep queries as simple as possible while fulfilling the user's request - Only include fields that the user specifically asked for - Do not add dimensions or additional fields unless explicitly requested - When in doubt, ask the user for clarification rather than creating a complex query `, { typeName: z .string() .describe('The type name (dataset) of the GraphQL type to fetch details for'), fieldsPageSize: z .number() .min(5) .max(500) .default(50) .describe('Number of fields to return per page'), fieldsPage: z.number().min(1).default(1).describe('Page number for fields to fetch'), enumValuesPageSize: z .number() .min(5) .max(500) .default(50) .describe('Number of enum values to return per page'), enumValuesPage: z.number().min(1).default(1).describe('Page number for enum values to fetch'), }, async (params) => { const { typeName, fieldsPageSize = 50, fieldsPage = 1, enumValuesPageSize = 50, enumValuesPage = 1, } = params const accountId = await agent.getActiveAccountId() if (!accountId) { return { content: [ { type: 'text', text: 'No currently active accountId. Try listing your accounts (accounts_list) and then setting an active account (set_active_account)', }, ], } } try { const props = getProps(agent) const typeDetails = await fetchTypeDetails(typeName, props.accessToken) // Apply pagination to fields if they exist const allFields = typeDetails.data.__type.fields || [] const totalFields = allFields.length const totalFieldsPages = Math.ceil(totalFields / fieldsPageSize) // Calculate start and end indices for the fields page const fieldsStartIndex = (fieldsPage - 1) * fieldsPageSize const fieldsEndIndex = Math.min(fieldsStartIndex + fieldsPageSize, totalFields) // Apply pagination to enum values if they exist const allEnumValues = typeDetails.data.__type.enumValues || [] const totalEnumValues = allEnumValues.length const totalEnumValuesPages = Math.ceil(totalEnumValues / enumValuesPageSize) // Calculate start and end indices for the enum values page const enumValuesStartIndex = (enumValuesPage - 1) * enumValuesPageSize const enumValuesEndIndex = Math.min( enumValuesStartIndex + enumValuesPageSize, totalEnumValues ) // Create a paginated version of the type details const paginatedTypeDetails = { data: { __type: { ...typeDetails.data.__type, fields: allFields.slice(fieldsStartIndex, fieldsEndIndex), enumValues: allEnumValues.slice(enumValuesStartIndex, enumValuesEndIndex), }, }, pagination: { fields: { page: fieldsPage, pageSize: fieldsPageSize, totalFields, totalPages: totalFieldsPages, hasNextPage: fieldsPage < totalFieldsPages, hasPreviousPage: fieldsPage > 1, }, enumValues: { page: enumValuesPage, pageSize: enumValuesPageSize, totalEnumValues, totalPages: totalEnumValuesPages, hasNextPage: enumValuesPage < totalEnumValuesPages, hasPreviousPage: enumValuesPage > 1, }, }, } return { content: [ { type: 'text', text: JSON.stringify(paginatedTypeDetails), }, ], } } catch (error) { return { content: [ { type: 'text', text: JSON.stringify({ error: `Error fetching type details: ${error instanceof Error ? error.message : String(error)}`, }), }, ], } } } ) // Tool to fetch the complete GraphQL schema (combines overview and important type details) agent.server.tool( 'graphql_complete_schema', 'Fetch the complete Cloudflare GraphQL API schema (combines overview and important type details)', { typesPageSize: z .number() .min(10) .max(500) .default(100) .describe('Number of types to return per page'), typesPage: z.number().min(1).default(1).describe('Page number for types to fetch'), includeRootTypeDetails: z .boolean() .default(true) .describe('Whether to include detailed information about root types'), maxTypeDetailsToFetch: z .number() .min(0) .max(10) .default(3) .describe('Maximum number of important types to fetch details for'), }, async (params) => { const { typesPageSize = 100, typesPage = 1, includeRootTypeDetails = true, maxTypeDetailsToFetch = 3, } = params const accountId = await agent.getActiveAccountId() if (!accountId) { return { content: [ { type: 'text', text: 'No currently active accountId. Try listing your accounts (accounts_list) and then setting an active account (set_active_account)', }, ], } } try { const props = getProps(agent) // First fetch the schema overview const schemaOverview = await fetchSchemaOverview(props.accessToken) // Apply pagination to the types array const allTypes = schemaOverview.data.__schema.types || [] const totalTypes = allTypes.length const totalPages = Math.ceil(totalTypes / typesPageSize) // Calculate start and end indices for the current page const startIndex = (typesPage - 1) * typesPageSize const endIndex = Math.min(startIndex + typesPageSize, totalTypes) // Get the paginated types const paginatedTypes = allTypes.slice(startIndex, endIndex) // Create the base schema with paginated types const schema: { data: { __schema: { queryType: { name: string } | null mutationType: { name: string } | null subscriptionType: { name: string } | null types: Array<{ name: string kind: string description: string | null }> } } typeDetails: Record<string, GraphQLType> pagination: { types: { page: number pageSize: number totalTypes: number totalPages: number hasNextPage: boolean hasPreviousPage: boolean } } } = { data: { __schema: { queryType: schemaOverview.data.__schema.queryType, mutationType: schemaOverview.data.__schema.mutationType, subscriptionType: schemaOverview.data.__schema.subscriptionType, types: paginatedTypes, }, }, typeDetails: {} as Record<string, GraphQLType>, pagination: { types: { page: typesPage, pageSize: typesPageSize, totalTypes, totalPages, hasNextPage: typesPage < totalPages, hasPreviousPage: typesPage > 1, }, }, } // If requested, fetch details for root types if (includeRootTypeDetails) { // Identify important root types const rootTypes = [ schemaOverview.data.__schema.queryType?.name, ...(schemaOverview.data.__schema.mutationType?.name ? [schemaOverview.data.__schema.mutationType.name] : []), ].filter(Boolean) as string[] // Limit the number of types to fetch details for const typesToFetch = rootTypes.slice(0, maxTypeDetailsToFetch) // Fetch details for each type for (const typeName of typesToFetch) { try { const typeDetails = await fetchTypeDetails(typeName, props.accessToken) if (typeDetails.data.__type) { schema.typeDetails[typeName] = typeDetails.data.__type } } catch (error) { console.error(`Error fetching details for type ${typeName}:`, error) } } } return { content: [ { type: 'text', text: JSON.stringify(schema), }, ], } } catch (error) { return { content: [ { type: 'text', text: JSON.stringify({ error: `Error fetching GraphQL schema: ${error instanceof Error ? error.message : String(error)}`, }), }, ], } } } ) // Tool to execute a GraphQL query agent.server.tool( 'graphql_query', `Execute a GraphQL query against the Cloudflare API IMPORTANT: ONLY execute the EXACT GraphQL query provided by the user. DO NOT generate complicated queries that the user didn't explicitly ask for. CRITICAL: When querying, make sure to set a LIMIT (e.g., first: 10, limit: 20) otherwise the response may be too large for the MCP server to process. Use this tool when: - A user provides a GraphQL query and expects real-time data from Cloudflare's API. - You need to retrieve live information from Cloudflare, such as analytics, logs, account data, or configuration details. - You want to validate the behavior of a GraphQL query or inspect its runtime results. This tool sends a user-defined GraphQL query to the Cloudflare API and returns the raw response exactly as received. When filtering or querying by time, use ISO 8601 datetime format (e.g., "2020-08-03T02:07:05Z"). For each query execution, a clickable GraphQL API Explorer link will be provided in the response. Users can click this link to open the query in Cloudflare's GraphQL Explorer interface where they can further modify and experiment with the query. Guidelines: - Only use the exact query provided by the user. Do not modify or expand it unless explicitly requested. - Always suggest including limits in queries (e.g., first: 10, limit: 20) to prevent response size issues. - If a query fails due to size limits, advise the user to add or reduce limits in their query. `, { query: z.string().describe('The GraphQL query to execute'), variables: z.record(z.any()).optional().describe('Variables for the query'), }, async (params) => { const accountId = await agent.getActiveAccountId() if (!accountId) { return { content: [ { type: 'text', text: 'No currently active accountId. Try listing your accounts (accounts_list) and then setting an active account (set_active_account)', }, ], } } try { const props = getProps(agent) const { query, variables = {} } = params // Execute the GraphQL query and get the raw result const result = await executeGraphQLQuery(query, variables, props.accessToken) // Generate GraphQL API Explorer link for this query const compressedQuery = LZString.compressToEncodedURIComponent(query) const compressedVariables = LZString.compressToEncodedURIComponent( JSON.stringify(variables) ) const explorerUrl = `https://graphql.cloudflare.com/explorer?query=${compressedQuery}&variables=${compressedVariables}` // Check if the response is too large (MCP server will fail if > 1MB) const resultString = JSON.stringify(result) const SIZE_LIMIT = 800000 // Set a safer limit (800KB) to ensure we stay under 1MB if (resultString.length > SIZE_LIMIT) { return { content: [ { type: 'text', text: `ERROR: Query result exceeds size limit (${Math.round(resultString.length / 1024)}KB). MCP server will fail with results larger than 1MB. Please use a lower LIMIT in your GraphQL query to reduce the number of returned items. For example: - Add 'first: 10' or 'limit: 10' parameters to your query - Reduce the number of requested fields - Add more specific filters to narrow down results`, }, ], } } return { content: [ { type: 'text', text: `${resultString}\n\n**[Open in GraphQL Explorer](${explorerUrl})**\nClick the link above to view and modify this query in the Cloudflare GraphQL API Explorer.`, }, ], } } catch (error) { return { content: [ { type: 'text', text: JSON.stringify({ error: `Error executing GraphQL query: ${error instanceof Error ? error.message : String(error)}`, }), }, ], } } } ) // Tool to generate a GraphQL API Explorer link agent.server.tool( 'graphql_api_explorer', `Generate a Cloudflare GraphQL API Explorer link Use this tool when: - A user asks for any GraphQL queries and wants to explore them in the Cloudflare GraphQL API Explorer. - You want to provide a shareable link to a specific GraphQL query for the user to explore and modify. - You need to help the user visualize or interact with GraphQL queries in a user-friendly interface. This tool generates a direct link to the Cloudflare GraphQL API Explorer with a pre-populated query and variables. The response includes a clickable Markdown link that users can click to open the query in Cloudflare's interactive GraphQL playground. The original query and variables are also displayed for reference. `, { query: z.string().describe('The GraphQL query to include in the explorer link'), variables: z.record(z.any()).optional().describe('Variables for the query in JSON format'), }, async (params) => { try { const { query, variables = {} } = params // Compress the query and variables using lz-string const compressedQuery = LZString.compressToEncodedURIComponent(query) const compressedVariables = LZString.compressToEncodedURIComponent( JSON.stringify(variables) ) // Generate the GraphQL API Explorer URL const explorerUrl = `https://graphql.cloudflare.com/explorer?query=${compressedQuery}&variables=${compressedVariables}` return { content: [ { type: 'text', text: `**[Open in GraphQL Explorer](${explorerUrl})**\n\nYou can click the link above to open the Cloudflare GraphQL API Explorer with your query pre-populated.\n\n**Query:**\n\`\`\`graphql\n${query}\n\`\`\`\n\n${Object.keys(variables).length > 0 ? `**Variables:**\n\`\`\`json\n${JSON.stringify(variables, null, 2)}\n\`\`\`\n` : ''}`, }, ], } } catch (error) { return { content: [ { type: 'text', text: JSON.stringify({ error: `Error generating GraphQL API Explorer link: ${error instanceof Error ? error.message : String(error)}`, }), }, ], } } } ) } ```