stillship
One feed. Every update.

See what shipped.

Follow the apps you care about. Stillship turns scattered changelogs into one calm, searchable feed.

447 apps tracked

Latest ships

Updated daily
Babelv8.0.7 ↗2026-10-07
Vitev8.3.3 ↗2026-10-06
Vitev6.4.4 ↗2026-10-06
CloudflareStandardize provider credential error responses in AI Gateway AI Gateway AI Gateway's REST API now returns consistent responses when an AI provider rejects credentials. The change applies to POST /ai/run ↗︎ . Scenario Previous AI Gateway response New AI Gateway response ElevenLabs Provider-specific UserCredentialsError with HTTP 403 HTTP 401 with error code 2009 Google Vertex HTTP 500 for rejected credentials, with upstream retries HTTP 401 with error code 2009 ; the request fails without retrying the provider All other providers HTTP 402 or another provider-specific status for rejected credentials HTTP 401 with error code 2009 When using Unified Billing , the provider rejects the credentials Provider-specific authentication error HTTP 503 Update applications that handle AI Gateway REST API errors to treat HTTP 401 as an invalid or rejected provider credential. For details about providing provider credentials, refer to Bring your own provider keys . Oct 6, 2026 Warnings when approaching your DNS records quota DNS The DNS records page in the Cloudflare dashboard now shows a warning once you have used 85% of your DNS records quota . The warning reflects the quota that applies to you. If your zone has its own quota, the warning shows that zone's usage. If your account uses an account-level DNS records quota , the warning shows your usage across all zones in the account. Oct 6, 2026 WAF Release - 2026-10-06 WAF This release introduces a new detection to mitigate a heap-based buffer overflow vulnerability in F5 BIG-IP, and enhances existing command injection protections by incorporating tested beta logic into the baseline rule. Key Findings CVE-2026-94127: A heap-based buffer overflow vulnerability in F5 BIG-IP. Attackers can exploit this flaw to execute arbitrary code on the affected system. Ruleset Rule ID Legacy Rule ID Description Previous Action New Action Comments Cloudflare Managed Ruleset ...a056caff N/A Command Injection - Generic 8 - uri - Beta Log Block This rule is merged into the original rule "Command Injection - Generic 8 - uri" (ID: ...ee159e2e ). Cloudflare Managed Ruleset ...7206c737 N/A F5 BIG-IP - UnAuth Heap-Overflow - CVE:CVE-2026-94127 Log Block This is a new detection. Cloudflare Managed Ruleset ...549f7356 N/A Next.js - Cache Poisoning - CVE:CVE-2026-94543 Block Block Rule metadata description refined. Detection unchanged. Oct 6, 2026 WAF Release - Scheduled changes for 2026-10-12 WAF Announcement Date Release Date Release Behavior Legacy Rule ID Rule ID Description Comments 2026-10-06 2026-10-12 Log N/A ...02751ef3 Generic Rules - Template Injection - 2 - Beta This rule will be merged into the original rule "Generic Rules - Template Injection - 2" (ID: ...d3ed0123 ). Oct 5, 2026 BGP over IPsec and GRE tunnels generally available Cloudflare WAN Magic Transit Cloudflare One BGP peering over IPsec and GRE tunnels is generally available for Cloudflare WAN and Magic Transit. You can use it for production workloads. BGP peering exchanges routes dynamically between your devices and your Cloudflare virtual network routing table. You no longer need to update static routes manually as your network changes. BGP over IPsec and GRE tunnels is available to all accounts that use Unified Routing . No enablement is required. BGP over CNI remains in closed beta. For configuration details, refer to: Configure BGP routes for Cloudflare WAN Configure BGP routes for Magic Transit Oct 2, 2026 Introducing Web Search API AI Gateway Web Search API Web Search API is now available in beta. Web Search API lets your AI agents and applications search the Internet and ground their responses in live information, instead of guessing URLs or relying on a model's training cutoff. At launch, you can choose between three search providers: Ceramic.ai, Exa, and Linkup . All three support Zero Data Retention for requests made through Cloudflare, and all have committed to Cloudflare's verified bot crawling standards. Web Search API runs through AI Gateway , so search requests appear in your gateway logs and are billed to your AI Gateway credits at each provider's list API price, with no additional markup. You can also bring your own provider API key. Call Web Search API with the REST API: curl https://api.cloudflare.com/client/v4/accounts/ $CLOUDFLARE_ACCOUNT_ID /ai/websearch/ \ --request POST \ --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN " \ --header "Content-Type: application/json" \ --data '{ "query": "What are some fun things to do in Salt Lake City as fall approaches?", "provider": "ceramic", "limit": 5, "options": { "gateway": { "id": "default" } } }' Or from a Worker with the AI binding: const response = await env. AI . websearch ({ gatewayId: "default" , query: "What are some fun things to do in Salt Lake City as fall approaches?" , provider: "exa" , limit: 5 , }); const results = await response. json (); To get started, refer to How to use Web Search API . Oct 2, 2026 New strict service token authentication setting for Access Access Cloudflare One The strict service token authentication setting applies consistent behavior to requests made with service tokens. When the setting is on for a Zero Trust organization, Access handles requests with service token headers as follows: If authentication or authorization fails, Access always returns 401 or 403 instead of redirecting the client to the login page with 302 . Only Service Auth policies can authorize the request. Access ignores Allow policies and any CF_Authorization cookie sent with the request. Access does not return a CF_Authorization cookie to the client after successful authentication. Subsequent requests should continue to use service token headers. Failed requests for recognized service tokens appear in Access authentication logs . Zero Trust organizations created on or after October 5, 2026 have strict service token authentication turned on by default and cannot turn it off. Cloudflare recommends that existing organizations turn it on as well. Organizations created before October 5, 2026 can configure the setting in the dashboard or through the API. In the Cloudflare dashboard ↗︎ , go to Zero Trust > Access controls > Access settings . Go to Access settings  ↗ Under Manage service tokens , turn on Strict service token authentication . In the confirmation dialog, select Enable . To turn off strict service token authentication, turn off the setting and select Disable . curl "https://api.cloudflare.com/client/v4/accounts/%7Baccount_id%7D/access/organizations" \ --request PATCH \ --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN " \ --json '{ "strict_service_token_auth": true }' To turn off strict service token authentication, set strict_service_token_auth to false . For behavior and configuration details, refer to Strict service token authentication . Oct 2, 2026 Run the Pi Durable harness on Cloudflare with the Agents SDK Agents Workers The Agents SDK now provides first-class support for building agents using the Pi harness. You can build long-running agents using the combination of Pi 1.0 ↗︎ , Pi Durable ↗︎ , and the new PiHarness class that the Cloudflare Agents SDK provides, ensuring your agent's work is durably persisted, even if interrupted mid-turn. Built with Earendil ↗︎ , this integration is our first step toward first-class support for third-party agent harnesses on Cloudflare. Copy prompt Prompt copied! function e(e,t){let n=new URL(t.pathname,t.origin).href;return e.replaceAll(`{{pageUrl}}`,n)}function t(){document.querySelectorAll(`.copy-prompt-btn`).forEach(t=>{if(t.dataset.bound===`true`)return;t.dataset.bound=`true`;let n=t.dataset.tooltip;n&&t.setAttribute(`title`,n),t.addEventListener(`click`,async()=>{try{await navigator.clipboard.writeText(e(t.dataset.prompt??``,new URL(window.location.href)))}catch{return}t.dataset.copied=`true`,setTimeout(()=>delete t.dataset.copied,1500)})})}t(),document.addEventListener(`astro:page-load`,t); Beta PiHarness is in beta. Pi Durable ↗︎ is a new, experimental package, and the PiHarness API will likely change as Pi Durable matures. PiHarness is a new "Lifecycle capability" provided by the Cloudflare Agents SDK. Pi Durable provides the agent harness and the Lifecycle is responsible for keeping the agent running in the Durable Object. The Lifecycle is a core concept in the Agents SDK ensuring that long-running work can run in a Durable Object, surviving restarts, crashes, and network issues. We will share more on Lifecycle capabilities in the near future. Install if (!customElements.get("nb-pm-restore")) { customElements.define( "nb-pm-restore", class extends HTMLElement { connectedCallback() { const card = this.closest("[data-nb-pm]"); if (!card) return; let saved; try { saved = sessionStorage.getItem("ui-pm-tab"); } catch { return; } if (!saved) return; const tabs = card.querySelectorAll("[data-nb-pm-tab]"); let idx = -1; tabs.forEach(function (t, i) { if (t.textContent.trim() === saved) idx = i; }); if (idx npm yarn pnpm bun npm i agents@latest @earendil-works/pi-durable @earendil-works/pi-ai yarn add agents@latest @earendil-works/pi-durable @earendil-works/pi-ai pnpm add agents@latest @earendil-works/pi-durable @earendil-works/pi-ai bun add agents@latest @earendil-works/pi-durable @earendil-works/pi-ai Both Pi packages are optional peer dependencies of agents , so you only install them if you use the harness. Use it in an Agent Creating a Pi agent requires configuring the Pi Harness with a model, skills, and tools, then registering the PiHarness with the Agent class. import { Agent } from "agents" ; import { createModels } from "@earendil-works/pi-ai/models" ; import { createRegistry, Harness } from "@earendil-works/pi-durable" ; import { PiHarness } from "agents/harness/pi" ; import { createAI } from "agents/models/pi-ai" ; export class Assistant extends Agent { ai = createAI ({ binding: this .env. AI }); registry = createRegistry (); harness = new PiHarness ({ harness : ({ storage , context }) => { const models = createModels (); models. setProvider ( this .ai.provider); return Harness. open ( storage, { models, registry: this .registry }, context, ); }, defaults: { model: this . ai ( "@cf/moonshotai/kimi-k2.7-code" ) }, }); constructor ( ctx , env ) { super (ctx, env); this .lifecycle. use ( this .harness); } async ask ( prompt ) { const { text } = await this .harness. prompt (prompt); return text; } } import { Agent } from "agents" ; import { createModels } from "@earendil-works/pi-ai/models" ; import { createRegistry, Harness } from "@earendil-works/pi-durable" ; import { PiHarness } from "agents/harness/pi" ; import { createAI } from "agents/models/pi-ai" ; export class Assistant extends Agent < Env > { ai = createAI ({ binding: this .env. AI }); registry = createRegistry (); harness = new PiHarness ({ harness : ({ storage , context }) => { const models = createModels (); models. setProvider ( this .ai.provider); return Harness. open ( storage, { models, registry: this .registry }, context, ); }, defaults: { model: this . ai ( "@cf/moonshotai/kimi-k2.7-code" ) }, }); constructor ( ctx : DurableObjectState , env : Env ) { super (ctx, env); this .lifecycle. use ( this .harness); } async ask ( prompt : string ) { const { text } = await this .harness. prompt (prompt); return text; } } The agents/models/pi-ai entry point supports AI Gateway and Workers AI models, so you can get started with Cloudflare models right away or use your existing Pi AI provider. Add tools with extensions Both tools and system prompt sections are provided to the Pi Harness via extensions. import { Type } from "@earendil-works/pi-ai" ; import { skills } from "agents/harness/pi" ; const WordCount = Type. Object ({ text: Type. String () }); const wordCount = { name: "word_count" , description: "Count the words in a text." , parameters: WordCount, replay: "safe" , async execute ({ text }) { const words = text. split ( / \s + / ). filter (Boolean). length ; return { content: [{ type: "text" , text: String (words) }] }; }, }; // In the harness factory, before Harness.open(): registry. install ({ name: "editor" , sections: [ { key: "preamble" , render : () => "You are an editor." , tag: false }, ], tools: [wordCount], }); registry. install ( await skills (sources)); import { Type } from "@earendil-works/pi-ai" ; import type { ToolRegistration } from "@earendil-works/pi-durable" ; import { skills } from "agents/harness/pi" ; const WordCount = Type. Object ({ text: Type. String () }); const wordCount : ToolRegistration < typeof WordCount> = { name: "word_count" , description: "Count the words in a text." , parameters: WordCount, replay: "safe" , async execute ({ text }) { const words = text. split ( / \s + / ). filter (Boolean). length ; return { content: [{ type: "text" , text: String (words) }] }; }, }; // In the harness factory, before Harness.open(): registry. install ({ name: "editor" , sections: [ { key: "preamble" , render : () => "You are an editor." , tag: false }, ], tools: [wordCount], }); registry. install ( await skills (sources)); For more information on creating and configuring extensions, refer to Extensions . Learn more Pi harness documentation Pi harness extensions pi-ai model provider Pi harness example ↗︎ , with WebSockets, a browser UI, and a @cloudflare/computer Workspace for the model's tools Lifecycle ↗︎ Pi Durable announcement ↗︎ from Earendil Oct 2, 2026 30 days of analytics data on every plan Analytics Every plan now gets at least 30 days of analytics data. Adaptive analytics datasets, such as HTTP requests, security events, and DNS analytics, retain at least 31 days of data for Free and Pro domains, and you can query up to 30 days in a single request. Previously, Free and Pro domains could see between 24 hours and 8 days of history depending on the dataset. A full month of history lets you investigate an issue after it happens, compare today with the same day in previous weeks, and tell a one-time spike from a longer trend. The change applies in the Cloudflare dashboard, in Custom Dashboards , and through the GraphQL Analytics API . Domain analytics also now live in one place. In the Cloudflare dashboard, select a domain and go to Analytics to see Traffic, Performance, Security, Cache, Origin, DNS, and Visitors as tabs that share one time range and one set of filters. Account-level analytics are under Observability > Analytics . This change does not alter which datasets or fields your plan can access. Aggregated datasets, such as httpRequests1hGroups , keep their existing per-plan limits. To check the exact retention and query window for a zone or account, query the settings for each dataset. For plan-specific limits, refer to Security Analytics , Security Events , and GraphQL Analytics API limits . Oct 2, 2026 Workers Observability logs and traces in Custom Dashboards Analytics You can now build Custom Dashboards charts from Workers Observability data. Two new datasets, Workers Observability — Logs and Workers Observability — Traces (OTel) , let you chart Worker invocations, log levels, errors, CPU and wall time, span counts, and durations next to HTTP traffic, security events, and other analytics datasets. This gives you one dashboard for an application that spans Cloudflare's network and your Workers. For example, you can put request volume, WAF blocks, and Worker error rates on the same view, filter all three by time range, and spot whether a spike in errors lines up with a change in traffic. The datasets are available for every Worker in your account that has Workers Logs or Workers Traces turned on. Custom Dashboards also now allow up to 100 dashboards for every account. To get started, refer to Workers Observability data in Custom Dashboards . Oct 2, 2026 United States jurisdiction D1 You can create D1 databases with the us jurisdiction. These databases run and persist data within the United States. Use this option for regional data residency requirements. To create a database with the us jurisdiction, run: npx wrangler@latest d1 create db-with-us-jurisdiction --jurisdiction=us For more information, refer to D1 data location . Oct 2, 2026 Organizations support increased account and zone limits Cloudflare Fundamentals Organizations Cloudflare Organizations now support up to 20,000 accounts and 200,000 zones . For MSSP/Distributors using sub-organizations, these limits are applied at the root Organization. If you require a higher limit, reach out to your account team. The new limits apply to enterprise and MSSP/Distributor Organizations. Legacy reseller partner and brand partner tenants retain their existing quota behavior. For more information, refer to Account and zone limits . Oct 2, 2026 Workers KV namespace jurisdictions are now generally available KV Jurisdictions for Workers KV namespaces are now generally available. When you create a namespace, you can set a jurisdiction to make sure the namespace's data is only durably stored within that region. Jurisdictions can help you comply with data localization regulations such as GDPR or FedRAMP. Supported jurisdictions are eu , us , and fedramp . A jurisdiction can only be set when a namespace is created, using the Cloudflare dashboard, Wrangler, the cf CLI, or the REST API, and cannot be added or changed afterwards. npx wrangler@latest kv namespace create < NAMESPACE_NAM E > --jurisdiction=eu cf kv namespaces create --title < NAMESPACE_NAM E > --jurisdiction eu curl "https://api.cloudflare.com/client/v4/accounts/ $ACCOUNT_ID /storage/kv/namespaces" \ --request POST \ --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN " \ --header "Content-Type: application/json" \ --data '{ "title": "<NAMESPACE_NAME>", "jurisdiction": "eu" }' Workers can still access a namespace restricted to a jurisdiction from anywhere in the world, and KV data can be cached outside the jurisdiction on Cloudflare's network. The jurisdiction only controls where the namespace's data is durably stored. To learn more, refer to Data location . Oct 2, 2026 hash_in_range() is globally available for HTTP products Rules hash_in_range() is globally available for HTTP products on all plans. It hashes fields into an integer within a specified range. Use this result to select a portion of requests. Use cf.random_seed to select approximately 10% of requests at random: hash_in_range(0, 100, cf.random_seed) < 10 With Cloudflare for SaaS, use custom metadata to control rollout progression. Define rollout_pct as a custom key for each hostname. Set its value to an integer from 0 to 100. The expression selects approximately that percentage of requests: hash_in_range(0, 100, cf.random_seed) < coalesce(lookup_json_integer(cf.hostname.metadata, "rollout_pct"), 0) If rollout_pct is missing, coalesce() supplies 0 . The rule then matches no requests. For details, refer to the hash_in_range() function reference . Oct 2, 2026 Protect Quick Tunnels with email authentication Cloudflare Tunnel You can now restrict who can access a Quick Tunnel . Use the new --allowed-mail flag in cloudflared to require visitors to authenticate with a one-time PIN sent to their email before they reach your local service. cloudflared tunnel --url http://localhost:8080 --allowed-mail alice@example.com Previously, anyone with a trycloudflare.com URL could access the service behind it. Protected Quick Tunnels let you share a local development server, webhook receiver, or demo with specific people without creating a Cloudflare account or configuring a domain. You can allow: A single email address: --allowed-mail alice@example.com Multiple email addresses, by repeating the flag or using a comma-separated list: --allowed-mail 'alice@example.com,bob@example.com' Every address on a domain: --allowed-mail '*@example.com' Visitors do not need a Cloudflare account. Access ends for everyone when you stop the cloudflared process. To get started, update cloudflared to the latest version and refer to Restrict access by email . Oct 1, 2026 Introducing Clef: Cloudflare's first open-source decision models, now on Workers AI Workers AI Meet @cf/cloudflare/clef and @cf/cloudflare/clef-flash , the first models trained by the Cloudflare Workers AI team, available on Workers AI today. Clef is a decision model, in the same family as Typesafe's Jev ↗︎ . Instead of generating text, it reads an input state and a set of typed questions, then returns a probability for every allowed answer. Your agent gets a structured decision it can act on immediately, for example: route the ticket, block the request, or escalate to a human. There is no free-form output to parse and no reasoning tokens to wait for. Both models are hosted on Workers AI as Clef and Clef-flash . We are also open-sourcing the weights under the Apache 2.0 license on Hugging Face: Clef ↗︎ and Clef-flash ↗︎ . Read the launch blog post ↗︎ for the full story, including how we trained them. We are also launching a reinforcement learning (RL) fine-tuning service to help you tune Clef for your own workloads. Sign up to work with us as a design partner ↗︎ . Built for the hot path Clef is designed to be fast so decisions come back in milliseconds. Across our 43 benchmark runs, we achieved speeds where Clef is 2.5x faster than Jev at the median, and Clef-flash 13x faster. Latency Clef Clef-flash Jev Median 209.3 ms 38.8 ms 524.1 ms p95 238.6 ms 122.4 ms 536.0 ms Hosting on Workers AI adds to that speed. Requests run on GPUs across Cloudflare's network, running close to your users, so the network round trip stays short. You can put Clef directly in the request path of your agent, then hand off to an LLM on Workers AI to take action. Leading the benchmarks Across 10 decision benchmarks, a Clef model scores highest on 7, ahead of Jev and other open decision models. A few highlights: Benchmark Clef Clef-flash Jev BFCL (case exact) 98.47 98.76 95.75 BANKING77 (macro-F1) 94.20 90.93 79.74 CLINC150+OOS (macro-F1) 97.43 66.77 89.27 Home appliances (case exact) 82.95 97.73 52.27 On Typesafe's own workflow evals, Clef beats Jev in 3 of 4 areas: invoice processing, customer service, and security incidents. The full results are on the Hugging Face model card ↗︎ . Drop-in compatible with Jev Model Size Best for Context window @cf/cloudflare/clef 27B Highest-precision decisions 64K tokens @cf/cloudflare/clef-flash 9B Latency-critical, hot-path decisions 64K tokens Clef follows the System One API, so you can switch an existing Jev integration to Clef by changing the endpoint and model. Ask up to 64 questions per request, in three types: noul : A yes/no question. Returns the probability that the answer is yes. choice : Pick one option from a set you define. Returns the chosen option, a probability per option, and a confidence value. score : Rate against an ordered rubric. Returns a probability-weighted score and a probability per level. const response = await env. AI . run ( "@cf/cloudflare/clef" , { model: "clef" , state: "Checkout has been failing for every customer for the last hour." , questions: { urgent: { type: "noul" , instructions: "Is this support request urgent?" , }, team: { type: "choice" , instructions: "Which team should handle this request?" , criteria: { billing: "Payments, invoices, and refunds" , technical: "Outages, errors, and configuration" , sales: "Plans and upgrades" , }, }, }, }); // response.answers.urgent.noul -> probability the request is urgent // response.answers.team.choice -> highest-probability team const response = await env. AI . run ( "@cf/cloudflare/clef" , { model: "clef" , state: "Checkout has been failing for every customer for the last hour." , questions: { urgent: { type: "noul" , instructions: "Is this support request urgent?" , }, team: { type: "choice" , instructions: "Which team should handle this request?" , criteria: { billing: "Payments, invoices, and refunds" , technical: "Outages, errors, and configuration" , sales: "Plans and upgrades" , }, }, }, }); // response.answers.urgent.noul -> probability the request is urgent // response.answers.team.choice -> highest-probability team What you can build with decision models Support triage : Decide whether a ticket is urgent and which team owns it, then route it without a human in the loop. Threat intelligence : Classify a website by category. Paired with Browser Run , Clef fetched, rendered, and classified a domain in 2.2 seconds, compared to 4.7 seconds for gpt-oss-120b in the same workflow. Trust and safety : Score user submissions against your own policy rubric and act on the probability. Agent guardrails : Let an agent check "should I take this action?" in tens of milliseconds before calling a tool. Visual classification : Pass up to four images alongside the state. Unlike text-only decision models, Clef has a vision encoder. Get started Use Clef through the Workers AI binding ( env.AI.run() ) or the REST API at /ai/run . You can also use AI Gateway with these endpoints. For more information, refer to the Clef model page , the Clef-flash model page , and pricing . Oct 1, 2026 The best way to do MCP auth just got better: Workers OAuth Provider goes v1, with a new split API and full support for MCP 2026-07-28 Agents Workers @cloudflare/workers-oauth-provider ↗︎ is now v1, with a new split API. One Worker acts as the authorization server: it signs users in and issues tokens. Your MCP server acts as the resource server, and can run in another Worker. It validates each token with the authorization server over a Service Binding , without crossing the public Internet. It supports the MCP 2026-07-28 authorization specification ↗︎ , including Client ID Metadata Documents ↗︎ and issuer identification ↗︎ . It still works with older clients, including those using Dynamic Client Registration ↗︎ . insufficientScope() gives you step-up authorization ↗︎ in one line. The authorization server and the resource server can run in different Workers ↗︎ with different WAF and rate limiting rules. One authorization server can issue tokens for many MCP servers ↗︎ . A migration skill ↗︎ ships in the npm package so a coding agent can perform the upgrade. The split API import { OAuthAuthorizationServer, OAuthResourceServer, insufficientScope, } from "@cloudflare/workers-oauth-provider" ; import { WorkerEntrypoint } from "cloudflare:workers" ; // auth-server Worker: signs users in and issues tokens for both MCP servers. const authorizationServer = new OAuthAuthorizationServer ({ issuer: "https://auth.example.com" , resources: [ "https://calendar.example.com/mcp" , "https://drive.example.com/mcp" , ], scopesSupported: [ "calendar:read" , "calendar:write" , "offline_access" ], clientIdMetadataDocumentEnabled: true , }); export class AuthServer extends WorkerEntrypoint { fetch ( request ) { if ( new URL (request.url).pathname === "/authorize" ) { return showConsent (request, this .env); } return authorizationServer. fetch (request, this .env, this .ctx); } validateToken ( resource , token ) { return authorizationServer. validateToken (resource, token, this .env); } } // calendar MCP Worker: checks tokens with AuthServer over a Service Binding. export const calendar = new OAuthResourceServer ({ resourceMetadata: { resource: "https://calendar.example.com/mcp" , authorization_servers: [ "https://auth.example.com" ], }, requiredScopes: [ "calendar:read" ], validateToken : ( env ) => env. AUTH_SERVER .validateToken, handler: { fetch ( request , env , ctx ) { if ( request.method === "POST" && ! ctx.auth.scope. includes ( "calendar:write" ) ) { return insufficientScope (ctx.auth, [ "calendar:read" , "calendar:write" ]); } return handleMcp (request, ctx.props); }, }, }); import { OAuthAuthorizationServer, OAuthResourceServer, insufficientScope, } from "@cloudflare/workers-oauth-provider" ; import { WorkerEntrypoint } from "cloudflare:workers" ; // auth-server Worker: signs users in and issues tokens for both MCP servers. const authorizationServer = new OAuthAuthorizationServer < Env >({ issuer: "https://auth.example.com" , resources: [ "https://calendar.example.com/mcp" , "https://drive.example.com/mcp" , ], scopesSupported: [ "calendar:read" , "calendar:write" , "offline_access" ], clientIdMetadataDocumentEnabled: true , }); export class AuthServer extends WorkerEntrypoint < Env > { fetch ( request : Request ) { if ( new URL (request.url).pathname === "/authorize" ) { return showConsent (request, this .env); } return authorizationServer. fetch (request, this .env, this .ctx); } validateToken ( resource : string , token : string ) { return authorizationServer. validateToken (resource, token, this .env); } } // calendar MCP Worker: checks tokens with AuthServer over a Service Binding. export const calendar = new OAuthResourceServer < Env , AuthProps >({ resourceMetadata: { resource: "https://calendar.example.com/mcp" , authorization_servers: [ "https://auth.example.com" ], }, requiredScopes: [ "calendar:read" ], validateToken : ( env ) => env. AUTH_SERVER .validateToken, handler: { fetch ( request , env , ctx ) { if ( request.method === "POST" && ! ctx.auth.scope. includes ( "calendar:write" ) ) { return insufficientScope (ctx.auth, [ "calendar:read" , "calendar:write" ]); } return handleMcp (request, ctx.props); }, }, }); In the example, env.AUTH_SERVER.validateToken is that Service Binding call. The calendar Worker needs no KV namespace of its own. { "name" : "calendar-mcp" , "main" : "src/index.ts" , // Set this to today's date "compatibility_date" : "2026-10-07" , "services" : [ { "binding" : "AUTH_SERVER" , "service" : "auth-server" , "entrypoint" : "AuthServer" , }, ], } name = "calendar-mcp" main = "src/index.ts" # Set this to today's date compatibility_date = "2026-10-07" [[ services ]] binding = "AUTH_SERVER" service = "auth-server" entrypoint = "AuthServer" OAuthResourceServer publishes the RFC 9728 ↗︎ protected resource metadata that MCP clients use to find your authorization server. It answers requests without a token with a 401 challenge that points to that metadata. It also rejects tokens issued for any other resource. You can still use OAuthProvider as both the authorization server and the MCP server. For most 0.x deployments, the only required change is to add resourceMetadata: { resource } . Other updates and helpers Consent page ↗︎ and upstream sign-in ↗︎ helpers implement the MCP confused deputy protections. Sliding refresh token expiry ↗︎ with refreshTokenIdleTTL . Resumable KV cleanup ↗︎ with purgeExpiredData() . An internal reason ↗︎ on every error passed to onError . Upgrade with the migration skill if (!customElements.get("nb-pm-restore")) { customElements.define( "nb-pm-restore", class extends HTMLElement { connectedCallback() { const card = this.closest("[data-nb-pm]"); if (!card) return; let saved; try { saved = sessionStorage.getItem("ui-pm-tab"); } catch { return; } if (!saved) return; const tabs = card.querySelectorAll("[data-nb-pm-tab]"); let idx = -1; tabs.forEach(function (t, i) { if (t.textContent.trim() === saved) idx = i; }); if (idx npm yarn pnpm bun npm i @cloudflare/workers-oauth-provider@latest yarn add @cloudflare/workers-oauth-provider@latest pnpm add @cloudflare/workers-oauth-provider@latest bun add @cloudflare/workers-oauth-provider@latest Point your coding agent at node_modules/@cloudflare/workers-oauth-provider/skills/migrate-to-1.0/SKILL.md , or follow the migration guide ↗︎ . For both Workers in full, refer to the split Workers example ↗︎ . Oct 1, 2026 AI Search is generally available ↗2026-10-05

← Newer · Older →