Agents, Workers - 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
- Announced
- Effective
- Tags
- Agents, Workers
- Severity
- info, set by
reef/rss-rule-v1 - Source
- https://developers.cloudflare.com/changelog/post/2026-10-01-workers-oauth-provider-1x/
<p><a href="https://github.com/cloudflare/workers-oauth-provider" target="_blank" rel="noopener"><code>@cloudflare/workers-oauth-provider</code><span class="external-link"> ↗︎</span></a> 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 <a href="https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/">Service Binding</a>, without crossing the public Internet.</p> <ul> <li>It supports the <a href="https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization" target="_blank" rel="noopener">MCP 2026-07-28 authorization specification<span class="external-link"> ↗︎</span></a>, including <a href="https://github.com/cloudflare/workers-oauth-provider/blob/main/docs/authorization-server.md#client-id-metadata-documents" target="_blank" rel="noopener">Client ID Metadata Documents<span class="external-link"> ↗︎</span></a> and <a href="https://github.com/cloudflare/workers-oauth-provider/blob/main/docs/authorization-server.md#authorization-response-issuer" target="_blank" rel="noopener">issuer identification<span class="external-link"> ↗︎</span></a>. It still works with older clients, including those using <a href="https://github.com/cloudflare/workers-oauth-provider/blob/main/docs/authorization-server.md#dynamic-client-registration" target="_blank" rel="noopener">Dynamic Client Registration<span class="external-link"> ↗︎</span></a>.</li> <li><code>insufficientScope()</code> gives you <a href="https://github.com/cloudflare/workers-oauth-provider/blob/main/docs/authorization-server.md#scopes-and-step-up-authorization" target="_blank" rel="noopener">step-up authorization<span class="external-link"> ↗︎</span></a> in one line.</li> <li>The authorization server and the resource server can run in <a href="https://github.com/cloudflare/workers-oauth-provider/blob/main/docs/resource-servers.md#separate-workers" target="_blank" rel="noopener">different Workers<span class="external-link"> ↗︎</span></a> with different <a href="https://developers.cloudflare.com/waf/">WAF</a> and <a href="https://developers.cloudflare.com/waf/rate-limiting-rules/">rate limiting</a> rules.</li> <li>One authorization server can issue tokens for <a href="https://github.com/cloudflare/workers-oauth-provider/blob/main/docs/authorization-server.md#resources-and-token-audiences" target="_blank" rel="noopener">many MCP servers<span class="external-link"> ↗︎</span></a>.</li> <li>A <a href="https://github.com/cloudflare/workers-oauth-provider/blob/main/skills/migrate-to-1.0/SKILL.md" target="_blank" rel="noopener">migration skill<span class="external-link"> ↗︎</span></a> ships in the npm package so a coding agent can perform the upgrade.</li> </ul> <div tabindex="-1" class="heading-wrapper level-h4"><h4 id="the-split-api">The split API</h4><a class="anchor-link" href="#the-split-api"><span aria-hidden="true" class="anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24"><path fill="currentcolor" d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 0 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 1 0-1.42-1.42Zm8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 0 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 1 0 1.42 1.42l3.88-3.89a4.49 4.49 0 0 0 0-6.33ZM8.83 15.17a1 1 0 0 0 1.1.22 1 1 0 0 0 .32-.22l4.92-4.92a1 1 0 0 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42Z"></path></svg></span></a></div> <div><div data-nb-tabs data-nb-sync-key="workersExamples" class><div class="relative flex border-b border-border" role="tablist" data-nb-tabs-list><span class="bg-primary pointer-events-none absolute -bottom-px h-0.5 rounded-t-sm transition-[left,width] duration-200 ease-out" data-nb-tabs-indicator aria-hidden="true"></span></div><div class="mt-3"><div role="tabpanel" data-nb-tabs-content data-nb-tab-label="JavaScript" class><figure class="nb-code-figure" data-nb-lang="js"><pre class="astro-code astro-code-themes github-light github-dark nb-shiki-c6xiwz" tabindex="0" data-language="js" data-nb-lang="js"><code><span class="line"><span class="nb-shiki-1itgoe">import</span><span class="nb-shiki-140thh"> {</span></span> <span class="line"><span class="nb-shiki-140thh"> OAuthAuthorizationServer,</span></span> <span class="line"><span class="nb-shiki-140thh"> OAuthResourceServer,</span></span> <span class="line"><span class="nb-shiki-140thh"> insufficientScope,</span></span> <span class="line"><span class="nb-shiki-140thh">} </span><span class="nb-shiki-1itgoe">from</span><span class="nb-shiki-mdbnqw"> "@cloudflare/workers-oauth-provider"</span><span class="nb-shiki-140thh">;</span></span> <span class="line"><span class="nb-shiki-1itgoe">import</span><span class="nb-shiki-140thh"> { WorkerEntrypoint } </span><span class="nb-shiki-1itgoe">from</span><span class="nb-shiki-mdbnqw"> "cloudflare:workers"</span><span class="nb-shiki-140thh">;</span></span> <span class="line"></span> <span class="line"><span class="nb-shiki-21nrsd">// auth-server Worker: signs users in and issues tokens for both MCP servers.</span></span> <span class="line"><span class="nb-shiki-1itgoe">const</span><span class="nb-shiki-dzsirb"> authorizationServer</span><span class="nb-shiki-1itgoe"> =</span><span class="nb-shiki-1itgoe"> new</span><span class="nb-shiki-1t8gfj"> OAuthAuthorizationServer</span><span class="nb-shiki-140thh">({</span></span> <span class="line"><span class="nb-shiki-140thh"> issuer: </span><span class="nb-shiki-mdbnqw">"https://auth.example.com"</span><span class="nb-shiki-140thh">,</span></span> <span class="line"><span class="nb-shiki-140thh"> resources: [</span></span> <span class="line"><span class="nb-shiki-mdbnqw"> "https://calendar.example.com/mcp"</span><span class="nb-shiki-140thh">,</span></span> <span class="line"><span class="nb-shiki-mdbnqw"> "https://drive.example.com/mcp"</span><span class="nb-shiki-140thh">,</span></span> <span class="line"><span class="nb-shiki-140thh"> ],</span></span> <span class="line"><span class="nb-shiki-140thh"> scopesSupported: [</span><span class="nb-shiki-mdbnqw">"calendar:read"</span><span class="nb-shiki-140thh">, </span><span class="nb-shiki-mdbnqw">"calendar:write"</span><span class="nb-shiki-140thh">, </span><span class="nb-shiki-mdbnqw">"offline_access"</span><span class="nb-shiki-140thh">],</span></span> <span class="line"><span class="nb-shiki-140thh"> clientIdMetadataDocumentEnabled: </span><span class="nb-shiki-dzsirb">true</span><span class="nb-shiki-140thh">,</span></span> <span class="line"><span class="nb-shiki-140thh">});</span></span> <span class="line"></span> <span class="line"><span class="nb-shiki-1itgoe">export</span><span class="nb-shiki-1itgoe"> class</span><span class="nb-shiki-1t8gfj"> AuthServer</span><span class="nb-shiki-1itgoe"> extends</span><span class="nb-shiki-1t8gfj"> WorkerEntrypoint</span><span class="nb-shiki-140thh"> {</span></span> <span class="line"><span class="nb-shiki-1t8gfj"> fetch</span><span class="nb-shiki-140thh">(</span><span class="nb-shiki-1jdh33">request</span><span class="nb-shiki-140thh">) {</span></span> <span class="line"><span class="nb-shiki-1itgoe"> if</span><span class="nb-shiki-140thh"> (</span><span class="nb-shiki-1itgoe">new</span><span class="nb-shiki-1t8gfj"> URL</span><span class="nb-shiki-140thh">(request.url).pathname </span><span class="nb-shiki-1itgoe">===</span><span class="nb-shiki-mdbnqw"> "/authorize"</span><span class="nb-shiki-140thh">) {</span></span> <span class="line"><span class="nb-shiki-1itgoe"> return</span><span class="nb-shiki-1t8gfj"> showConsent</span><span class="nb-shiki-140thh">(request, </span><span class="nb-shiki-dzsirb">this</span><span class="nb-shiki-140thh">.env);</span></span> <span class="line"><span class="nb-shiki-140thh"> }</span></span> <span class="line"><span class="nb-shiki-1itgoe"> return</span><span class="nb-shiki-140thh"> authorizationServer.</span><span class="nb-shiki-1t8gfj">fetch</span><span class="nb-shiki-140thh">(request, </span><span class="nb-shiki-dzsirb">this</span><span class="nb-shiki-140thh">.env, </span><span class="nb-shiki-dzsirb">this</span><span class="nb-shiki-140thh">.ctx);</span></span> <span class="line"><span class="nb-shiki-140thh"> }</span></span> <span class="line"></span> <span class="line"><span class="nb-shiki-1t8gfj"> validateToken</span><span class="nb-shiki-140thh">(</span><span class="nb-shiki-1jdh33">resource</span><span class="nb-shiki-140thh">, </span><span class="nb-shiki-1jdh33">token</span><span class="nb-shiki-140thh">) {</span></span> <span class="line"><span class="nb-shiki-1itgoe"> return</span><span class="nb-shiki-140thh"> authorizationServer.</span><span class="nb-shiki-1t8gfj">validateToken</span><span class="nb-shiki-140thh">(resource, token, </span><span class="nb-shiki-dzsirb">this</span><span class="nb-shiki-140thh">.env);</span></span> <span class="line"><span class="nb-shiki-140thh"> }</span></span> <span class="line"><span class="nb-shiki-140thh">}</span></span> <span class="line"></span> <span class="line"><span class="nb-shiki-21nrsd">// calendar MCP Worker: checks tokens with AuthServer over a Service Binding.</span></span> <span class="line"><span class="nb-shiki-1itgoe">export</span><span class="nb-shiki-1itgoe"> const</span><span class="nb-shiki-dzsirb"> calendar</span><span class="nb-shiki-1itgoe"> =</span><span class="nb-shiki-1itgoe"> new</span><span class="nb-shiki-1t8gfj"> OAuthResourceServer</span><span class="nb-shiki-140thh">({</span></span> <span class="line"><span class="nb-shiki-140thh"> resourceMetadata: {</span></span> <span class="line"><span class="nb-shiki-140thh"> resource: </span><span class="nb-shiki-mdbnqw">"https://calendar.example.com/mcp"</span><span class="nb-shiki-140thh">,</span></span> <span class="line"><span class="nb-shiki-140thh"> authorization_servers: [</span><span class="nb-shiki-mdbnqw">"https://auth.example.com"</span><span class="nb-shiki-140thh">],</span></span> <span class="line"><span class="nb-shiki-140thh"> },</span></span> <span class="line"><span class="nb-shiki-140thh"> requiredScopes: [</span><span class="nb-shiki-mdbnqw">"calendar:read"</span><span class="nb-shiki-140thh">],</span></span> <span class="line"><span class="nb-shiki-1t8gfj"> validateToken</span><span class="nb-shiki-140thh">: (</span><span class="nb-shiki-1jdh33">env</span><span class="nb-shiki-140thh">) </span><span class="nb-shiki-1itgoe">=></span><span class="nb-shiki-140thh"> env.</span><span class="nb-shiki-dzsirb">AUTH_SERVER</span><span class="nb-shiki-140thh">.validateToken,</span></span> <span class="line"><span class="nb-shiki-140thh"> handler: {</span></span> <span class="line"><span class="nb-shiki-1t8gfj"> fetch</span><span class="nb-shiki-140thh">(</span><span class="nb-shiki-1jdh33">request</span><span class="nb-shiki-140thh">, </span><span class="nb-shiki-1jdh33">env</span><span class="nb-shiki-140thh">, </span><span class="nb-shiki-1jdh33">ctx</span><span class="nb-shiki-140thh">) {</span></span> <span class="line"><span class="nb-shiki-1itgoe"> if</span><span class="nb-shiki-140thh"> (</span></span> <span class="line"><span class="nb-shiki-140thh"> request.method </span><span class="nb-shiki-1itgoe">===</span><span class="nb-shiki-mdbnqw"> "POST"</span><span class="nb-shiki-1itgoe"> &&</span></span> <span class="line"><span class="nb-shiki-1itgoe"> !</span><span class="nb-shiki-140thh">ctx.auth.scope.</span><span class="nb-shiki-1t8gfj">includes</span><span class="nb-shiki-140thh">(</span><span class="nb-shiki-mdbnqw">"calendar:write"</span><span class="nb-shiki-140thh">)</span></span> <span class="line"><span class="nb-shiki-140thh"> ) {</span></span> <span class="line"><span class="nb-shiki-1itgoe"> return</span><span class="nb-shiki-1t8gfj"> insufficientScope</span><span class="nb-shiki-140thh">(ctx.auth, [</span><span class="nb-shiki-mdbnqw">"calendar:read"</span><span class="nb-shiki-140thh">, </span><span class="nb-shiki-mdbnqw">"calendar:write"</span><span class="nb-shiki-140thh">]);</span></span> <span class="line"><span class="nb-shiki-140thh"> }</span></span> <span class="line"><span class="nb-shiki-1itgoe"> return</span><span class="nb-shiki-1t8gfj"> handleMcp</span><span class="nb-shiki-140thh">(request, ctx.props);</span></span> <span class="line"><span class="nb-shiki-140thh"> },</span></span> <span class="line"><span class="nb-shiki-140thh"> },</span></span> <span class="line"><span class="nb-shiki-140thh">});</span></span></code></pre></figure></div><div role="tabpanel" data-nb-tabs-content data-nb-tab-label="TypeScript" class><figure class="nb-code-figure" data-nb-lang="ts"><pre class="astro-code astro-code-themes github-light github-dark nb-shiki-c6xiwz" tabindex="0" data-language="ts" data-nb-lang="ts"><code><span class="line"><span class="nb-shiki-1itgoe">import</span><span class="nb-shiki-140thh"> {</span></span> <span class="line"><span class="nb-shiki-140thh"> OAuthAuthorizationServer,</span></span> <span class="line"><span class="nb-shiki-140thh"> OAuthResourceServer,</span></span> <span class="line"><span class="nb-shiki-140thh"> insufficientScope,</span></span> <span class="line"><span class="nb-shiki-140thh">} </span><span class="nb-shiki-1itgoe">from</span><span class="nb-shiki-mdbnqw"> "@cloudflare/workers-oauth-provider"</span><span class="nb-shiki-140thh">;</span></span> <span class="line"><span class="nb-shiki-1itgoe">import</span><span class="nb-shiki-140thh"> { WorkerEntrypoint } </span><span class="nb-shiki-1itgoe">from</span><span class="nb-shiki-mdbnqw"> "cloudflare:workers"</span><span class="nb-shiki-140thh">;</span></span> <span class="line"></span> <span class="line"><span class="nb-shiki-21nrsd">// auth-server Worker: signs users in and issues tokens for both MCP servers.</span></span> <span class="line"><span class="nb-shiki-1itgoe">const</span><span class="nb-shiki-dzsirb"> authorizationServer</span><span class="nb-shiki-1itgoe"> =</span><span class="nb-shiki-1itgoe"> new</span><span class="nb-shiki-1t8gfj"> OAuthAuthorizationServer</span><span class="nb-shiki-140thh"><</span><span class="nb-shiki-1t8gfj">Env</span><span class="nb-shiki-140thh">>({</span></span> <span class="line"><span class="nb-shiki-140thh"> issuer: </span><span class="nb-shiki-mdbnqw">"https://auth.example.com"</span><span class="nb-shiki-140thh">,</span></span> <span class="line"><span class="nb-shiki-140thh"> resources: [</span></span> <span class="line"><span class="nb-shiki-mdbnqw"> "https://calendar.example.com/mcp"</span><span class="nb-shiki-140thh">,</span></span> <span class="line"><span class="nb-shiki-mdbnqw"> "https://drive.example.com/mcp"</span><span class="nb-shiki-140thh">,</span></span> <span class="line"><span class="nb-shiki-140thh"> ],</span></span> <span class="line"><span class="nb-shiki-140thh"> scopesSupported: [</span><span class="nb-shiki-mdbnqw">"calendar:read"</span><span class="nb-shiki-140thh">, </span><span class="nb-shiki-mdbnqw">"calendar:write"</span><span class="nb-shiki-140thh">, </span><span class="nb-shiki-mdbnqw">"offline_access"</span><span class="nb-shiki-140thh">],</span></span> <span class="line"><span class="nb-shiki-140thh"> clientIdMetadataDocumentEnabled: </span><span class="nb-shiki-dzsirb">true</span><span class="nb-shiki-140thh">,</span></span> <span class="line"><span class="nb-shiki-140thh">});</span></span> <span class="line"></span> <span class="line"><span class="nb-shiki-1itgoe">export</span><span class="nb-shiki-1itgoe"> class</span><span class="nb-shiki-1t8gfj"> AuthServer</span><span class="nb-shiki-1itgoe"> extends</span><span class="nb-shiki-1t8gfj"> WorkerEntrypoint</span><span class="nb-shiki-140thh"><</span><span class="nb-shiki-1t8gfj">Env</span><span class="nb-shiki-140thh">> {</span></span> <span class="line"><span class="nb-shiki-1t8gfj"> fetch</span><span class="nb-shiki-140thh">(</span><span class="nb-shiki-1jdh33">request</span><span class="nb-shiki-1itgoe">:</span><span class="nb-shiki-1t8gfj"> Request</span><span class="nb-shiki-140thh">) {</span></span> <span class="line"><span class="nb-shiki-1itgoe"> if</span><span class="nb-shiki-140thh"> (</span><span class="nb-shiki-1itgoe">new</span><span class="nb-shiki-1t8gfj"> URL</span><span class="nb-shiki-140thh">(request.url).pathname </span><span class="nb-shiki-1itgoe">===</span><span class="nb-shiki-mdbnqw"> "/authorize"</span><span class="nb-shiki-140thh">) {</span></span> <span class="line"><span class="nb-shiki-1itgoe"> return</span><span class="nb-shiki-1t8gfj"> showConsent</span><span class="nb-shiki-140thh">(request, </span><span class="nb-shiki-dzsirb">this</span><span class="nb-shiki-140thh">.env);</span></span> <span class="line"><span class="nb-shiki-140thh"> }</span></span> <span class="line"><span class="nb-shiki-1itgoe"> return</span><span class="nb-shiki-140thh"> authorizationServer.</span><span class="nb-shiki-1t8gfj">fetch</span><span class="nb-shiki-140thh">(request, </span><span class="nb-shiki-dzsirb">this</span><span class="nb-shiki-140thh">.env, </span><span class="nb-shiki-dzsirb">this</span><span class="nb-shiki-140thh">.ctx);</span></span> <span class="line"><span class="nb-shiki-140thh"> }</span></span> <span class="line"></span> <span class="line"><span class="nb-shiki-1t8gfj"> validateToken</span><span class="nb-shiki-140thh">(</span><span class="nb-shiki-1jdh33">resource</span><span class="nb-shiki-1itgoe">:</span><span class="nb-shiki-dzsirb"> string</span><span class="nb-shiki-140thh">, </span><span class="nb-shiki-1jdh33">token</span><span class="nb-shiki-1itgoe">:</span><span class="nb-shiki-dzsirb"> string</span><span class="nb-shiki-140thh">) {</span></span> <span class="line"><span class="nb-shiki-1itgoe"> return</span><span class="nb-shiki-140thh"> authorizationServer.</span><span class="nb-shiki-1t8gfj">validateToken</span><span class="nb-shiki-140thh">(resource, token, </span><span class="nb-shiki-dzsirb">this</span><span class="nb-shiki-140thh">.env);</span></span> <span class="line"><span class="nb-shiki-140thh"> }</span></span> <span class="line"><span class="nb-shiki-140thh">}</span></span> <span class="line"></span> <span class="line"><span class="nb-shiki-21nrsd">// calendar MCP Worker: checks tokens with AuthServer over a Service Binding.</span></span> <span class="line"><span class="nb-shiki-1itgoe">export</span><span class="nb-shiki-1itgoe"> const</span><span class="nb-shiki-dzsirb"> calendar</span><span class="nb-shiki-1itgoe"> =</span><span class="nb-shiki-1itgoe"> new</span><span class="nb-shiki-1t8gfj"> OAuthResourceServer</span><span class="nb-shiki-140thh"><</span><span class="nb-shiki-1t8gfj">Env</span><span class="nb-shiki-140thh">, </span><span class="nb-shiki-1t8gfj">AuthProps</span><span class="nb-shiki-140thh">>({</span></span> <span class="line"><span class="nb-shiki-140thh"> resourceMetadata: {</span></span> <span class="line"><span class="nb-shiki-140thh"> resource: </span><span class="nb-shiki-mdbnqw">"https://calendar.example.com/mcp"</span><span class="nb-shiki-140thh">,</span></span> <span class="line"><span class="nb-shiki-140thh"> authorization_servers: [</span><span class="nb-shiki-mdbnqw">"https://auth.example.com"</span><span class="nb-shiki-140thh">],</span></span> <span class="line"><span class="nb-shiki-140thh"> },</span></span> <span class="line"><span class="nb-shiki-140thh"> requiredScopes: [</span><span class="nb-shiki-mdbnqw">"calendar:read"</span><span class="nb-shiki-140thh">],</span></span> <span class="line"><span class="nb-shiki-1t8gfj"> validateToken</span><span class="nb-shiki-140thh">: (</span><span class="nb-shiki-1jdh33">env</span><span class="nb-shiki-140thh">) </span><span class="nb-shiki-1itgoe">=></span><span class="nb-shiki-140thh"> env.</span><span class="nb-shiki-dzsirb">AUTH_SERVER</span><span class="nb-shiki-140thh">.validateToken,</span></span> <span class="line"><span class="nb-shiki-140thh"> handler: {</span></span> <span class="line"><span class="nb-shiki-1t8gfj"> fetch</span><span class="nb-shiki-140thh">(</span><span class="nb-shiki-1jdh33">request</span><span class="nb-shiki-140thh">, </span><span class="nb-shiki-1jdh33">env</span><span class="nb-shiki-140thh">, </span><span class="nb-shiki-1jdh33">ctx</span><span class="nb-shiki-140thh">) {</span></span> <span class="line"><span class="nb-shiki-1itgoe"> if</span><span class="nb-shiki-140thh"> (</span></span> <span class="line"><span class="nb-shiki-140thh"> request.method </span><span class="nb-shiki-1itgoe">===</span><span class="nb-shiki-mdbnqw"> "POST"</span><span class="nb-shiki-1itgoe"> &&</span></span> <span class="line"><span class="nb-shiki-1itgoe"> !</span><span class="nb-shiki-140thh">ctx.auth.scope.</span><span class="nb-shiki-1t8gfj">includes</span><span class="nb-shiki-140thh">(</span><span class="nb-shiki-mdbnqw">"calendar:write"</span><span class="nb-shiki-140thh">)</span></span> <span class="line"><span class="nb-shiki-140thh"> ) {</span></span> <span class="line"><span class="nb-shiki-1itgoe"> return</span><span class="nb-shiki-1t8gfj"> insufficientScope</span><span class="nb-shiki-140thh">(ctx.auth, [</span><span class="nb-shiki-mdbnqw">"calendar:read"</span><span class="nb-shiki-140thh">, </span><span class="nb-shiki-mdbnqw">"calendar:write"</span><span class="nb-shiki-140thh">]);</span></span> <span class="line"><span class="nb-shiki-140thh"> }</span></span> <span class="line"><span class="nb-shiki-1itgoe"> return</span><span class="nb-shiki-1t8gfj"> handleMcp</span><span class="nb-shiki-140thh">(request, ctx.props);</span></span> <span class="line"><span class="nb-shiki-140thh"> },</span></span> <span class="line"><span class="nb-shiki-140thh"> },</span></span> <span class="line"><span class="nb-shiki-140thh">});</span></span></code></pre></figure></div></div></div></div> <p>In the example, <code>env.AUTH_SERVER.validateToken</code> is that Service Binding call. The calendar Worker needs no KV namespace of its own.</p> <div data-nb-tabs data-nb-sync-key="wranglerConfig" class><div class="relative flex border-b border-border" role="tablist" data-nb-tabs-list><span class="bg-primary pointer-events-none absolute -bottom-px h-0.5 rounded-t-sm transition-[left,width] duration-200 ease-out" data-nb-tabs-indicator aria-hidden="true"></span></div><div class="mt-3"><div role="tabpanel" data-nb-tabs-content data-nb-tab-label="wrangler.jsonc" class><figure class="nb-code-figure" data-nb-lang="jsonc"><pre class="astro-code astro-code-themes github-light github-dark nb-shiki-c6xiwz" tabindex="0" data-language="jsonc" data-nb-lang="jsonc"><code><span class="line"><span class="nb-shiki-140thh">{</span></span> <span class="line"><span class="nb-shiki-dzsirb"> "name"</span><span class="nb-shiki-140thh">: </span><span class="nb-shiki-mdbnqw">"calendar-mcp"</span><span class="nb-shiki-140thh">,</span></span> <span class="line"><span class="nb-shiki-dzsirb"> "main"</span><span class="nb-shiki-140thh">: </span><span class="nb-shiki-mdbnqw">"src/index.ts"</span><span class="nb-shiki-140thh">,</span></span> <span class="line"><span class="nb-shiki-21nrsd"> // Set this to today's date</span></span> <span class="line"><span class="nb-shiki-dzsirb"> "compatibility_date"</span><span class="nb-shiki-140thh">: </span><span class="nb-shiki-mdbnqw">"2026-10-25"</span><span class="nb-shiki-140thh">,</span></span> <span class="line"><span class="nb-shiki-dzsirb"> "services"</span><span class="nb-shiki-140thh">: [</span></span> <span class="line"><span class="nb-shiki-140thh"> {</span></span> <span class="line"><span class="nb-shiki-dzsirb"> "binding"</span><span class="nb-shiki-140thh">: </span><span class="nb-shiki-mdbnqw">"AUTH_SERVER"</span><span class="nb-shiki-140thh">,</span></span> <span class="line"><span class="nb-shiki-dzsirb"> "service"</span><span class="nb-shiki-140thh">: </span><span class="nb-shiki-mdbnqw">"auth-server"</span><span class="nb-shiki-140thh">,</span></span> <span class="line"><span class="nb-shiki-dzsirb"> "entrypoint"</span><span class="nb-shiki-140thh">: </span><span class="nb-shiki-mdbnqw">"AuthServer"</span><span class="nb-shiki-140thh">,</span></span> <span class="line"><span class="nb-shiki-140thh"> },</span></span> <span class="line"><span class="nb-shiki-140thh"> ],</span></span> <span class="line"><span class="nb-shiki-140thh">}</span></span></code></pre></figure></div><div role="tabpanel" data-nb-tabs-content data-nb-tab-label="wrangler.toml" class><figure class="nb-code-figure" data-nb-lang="toml"><pre class="astro-code astro-code-themes github-light github-dark nb-shiki-c6xiwz" tabindex="0" data-language="toml" data-nb-lang="toml"><code><span class="line"><span class="nb-shiki-140thh">name = </span><span class="nb-shiki-mdbnqw">"calendar-mcp"</span></span> <span class="line"><span class="nb-shiki-140thh">main = </span><span class="nb-shiki-mdbnqw">"src/index.ts"</span></span> <span class="line"><span class="nb-shiki-21nrsd"># Set this to today's date</span></span> <span class="line"><span class="nb-shiki-140thh">compatibility_date = </span><span class="nb-shiki-mdbnqw">"2026-10-25"</span></span> <span class="line"></span> <span class="line"><span class="nb-shiki-140thh">[[</span><span class="nb-shiki-1t8gfj">services</span><span class="nb-shiki-140thh">]]</span></span> <span class="line"><span class="nb-shiki-140thh">binding = </span><span class="nb-shiki-mdbnqw">"AUTH_SERVER"</span></span> <span class="line"><span class="nb-shiki-140thh">service = </span><span class="nb-shiki-mdbnqw">"auth-server"</span></span> <span class="line"><span class="nb-shiki-140thh">entrypoint = </span><span class="nb-shiki-mdbnqw">"AuthServer"</span></span></code></pre></figure></div></div></div><script type="module" src="https://developers.cloudflare.com/home/runner/work/cloudflare-docs/cloudflare-docs/src/components/ui/tabs/Tabs.astro?astro&type=script&index=0&lang.ts"></script> <p><code>OAuthResourceServer</code> publishes the <a href="https://datatracker.ietf.org/doc/html/rfc9728" target="_blank" rel="noopener">RFC 9728<span class="external-link"> ↗︎</span></a> protected resource metadata that MCP clients use to find your authorization server. It answers requests without a token with a <code>401</code> challenge that points to that metadata. It also rejects tokens issued for any other resource.</p> <p>You can still use <code>OAuthProvider</code> as both the authorization server and the MCP server. For most 0.x deployments, the only required change is to add <code>resourceMetadata: { resource }</code>.</p> <div tabindex="-1" class="heading-wrapper level-h4"><h4 id="other-updates-and-helpers">Other updates and helpers</h4><a class="anchor-link" href="#other-updates-and-helpers"><span aria-hidden="true" class="anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24"><path fill="currentcolor" d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 0 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 1 0-1.42-1.42Zm8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 0 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 1 0 1.42 1.42l3.88-3.89a4.49 4.49 0 0 0 0-6.33ZM8.83 15.17a1 1 0 0 0 1.1.22 1 1 0 0 0 .32-.22l4.92-4.92a1 1 0 0 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42Z"></path></svg></span></a></div> <ul> <li><a href="https://github.com/cloudflare/workers-oauth-provider/blob/main/docs/consent-page.md" target="_blank" rel="noopener">Consent page<span class="external-link"> ↗︎</span></a> and <a href="https://github.com/cloudflare/workers-oauth-provider/blob/main/docs/upstream-sign-in.md" target="_blank" rel="noopener">upstream sign-in<span class="external-link"> ↗︎</span></a> helpers implement the MCP confused deputy protections.</li> <li><a href="https://github.com/cloudflare/workers-oauth-provider/blob/main/docs/advanced-configuration.md#sliding-expiry" target="_blank" rel="noopener">Sliding refresh token expiry<span class="external-link"> ↗︎</span></a> with <code>refreshTokenIdleTTL</code>.</li> <li><a href="https://github.com/cloudflare/workers-oauth-provider/blob/main/docs/advanced-configuration.md#kv-cleanup" target="_blank" rel="noopener">Resumable KV cleanup<span class="external-link"> ↗︎</span></a> with <code>purgeExpiredData()</code>.</li> <li>An <a href="https://github.com/cloudflare/workers-oauth-provider/blob/main/docs/advanced-configuration.md#the-internal-reason" target="_blank" rel="noopener">internal reason<span class="external-link"> ↗︎</span></a> on every error passed to <code>onError</code>.</li> </ul> <div tabindex="-1" class="heading-wrapper level-h4"><h4 id="upgrade-with-the-migration-skill">Upgrade with the migration skill</h4><a class="anchor-link" href="#upgrade-with-the-migration-skill"><span aria-hidden="true" class="anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24"><path fill="currentcolor" d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 0 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 1 0-1.42-1.42Zm8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 0 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 1 0 1.42 1.42l3.88-3.89a4.49 4.49 0 0 0 0-6.33ZM8.83 15.17a1 1 0 0 0 1.1.22 1 1 0 0 0 .32-.22l4.92-4.92a1 1 0 0 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42Z"></path></svg></span></a></div> <script> 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 < 1) return; tabs.forEach(function (t, i) { t.setAttribute("aria-selected", String(i === idx)); }); card.querySelectorAll("[data-nb-pm-panel]").forEach(function (p, i) { p.hidden = i !== idx; }); } }, ); } </script><div data-nb-pm class="w-full"><div class="flex w-full flex-col overflow-hidden rounded-lg text-sm ring ring-border bg-card"><div class="flex items-center gap-2 px-3 py-2 text-[0.8125rem] font-medium leading-5 text-muted-foreground bg-[var(--nb-surface-sunken)]" role="tablist" aria-label="Package manager"><button role="tab" type="button" aria-selected="true" aria-controls="pm-panel-pm-e266aae9-npm" id="pm-tab-pm-e266aae9-npm" data-nb-pm-tab class="text-muted-foreground hover:bg-accent hover:text-foreground aria-selected:bg-selected aria-selected:text-foreground focus-visible:outline-ring m-0 cursor-pointer rounded-md border-0 bg-transparent px-2 py-0.5 text-xs leading-5 font-medium transition-colors focus-visible:outline-2 focus-visible:outline-offset-2">npm</button><button role="tab" type="button" aria-selected="false" aria-controls="pm-panel-pm-e266aae9-yarn" id="pm-tab-pm-e266aae9-yarn" data-nb-pm-tab class="text-muted-foreground hover:bg-accent hover:text-foreground aria-selected:bg-selected aria-selected:text-foreground focus-visible:outline-ring m-0 cursor-pointer rounded-md border-0 bg-transparent px-2 py-0.5 text-xs leading-5 font-medium transition-colors focus-visible:outline-2 focus-visible:outline-offset-2">yarn</button><button role="tab" type="button" aria-selected="false" aria-controls="pm-panel-pm-e266aae9-pnpm" id="pm-tab-pm-e266aae9-pnpm" data-nb-pm-tab class="text-muted-foreground hover:bg-accent hover:text-foreground aria-selected:bg-selected aria-selected:text-foreground focus-visible:outline-ring m-0 cursor-pointer rounded-md border-0 bg-transparent px-2 py-0.5 text-xs leading-5 font-medium transition-colors focus-visible:outline-2 focus-visible:outline-offset-2">pnpm</button><button role="tab" type="button" aria-selected="false" aria-controls="pm-panel-pm-e266aae9-bun" id="pm-tab-pm-e266aae9-bun" data-nb-pm-tab class="text-muted-foreground hover:bg-accent hover:text-foreground aria-selected:bg-selected aria-selected:text-foreground focus-visible:outline-ring m-0 cursor-pointer rounded-md border-0 bg-transparent px-2 py-0.5 text-xs leading-5 font-medium transition-colors focus-visible:outline-2 focus-visible:outline-offset-2">bun</button></div><div role="tabpanel" id="pm-panel-pm-e266aae9-npm" aria-labelledby="pm-tab-pm-e266aae9-npm" data-nb-pm-panel class="bg-card ring-border relative overflow-hidden rounded-lg text-inherit ring"><div class="flex items-stretch"><pre class="text-foreground my-0 min-w-0 grow overflow-x-auto border-0 bg-transparent px-4 py-3 font-mono text-sm leading-relaxed whitespace-pre"><code data-nb-pm-code><span class="text-success">npm</span><span class="text-warning"> i @cloudflare/workers-oauth-provider@latest</span></code></pre><button type="button" data-nb-pm-copy data-nb-command="npm i @cloudflare/workers-oauth-provider@latest" aria-label="Copy to clipboard" class="border-border text-muted-foreground hover:text-foreground focus-visible:outline-ring m-0 flex shrink-0 cursor-pointer items-center justify-center border-0 border-l border-solid bg-transparent px-3 transition-colors focus-visible:outline-2 focus-visible:-outline-offset-2"><svg width="1em" height="1em" viewBox="0 0 256 256" class="h-[18px] w-[18px]" data-icon="ph:copy"><path fill="currentColor" d="M216 32H88a8 8 0 0 0-8 8v40H40a8 8 0 0 0-8 8v128a8 8 0 0 0 8 8h128a8 8 0 0 0 8-8v-40h40a8 8 0 0 0 8-8V40a8 8 0 0 0-8-8m-56 176H48V96h112Zm48-48h-32V88a8 8 0 0 0-8-8H96V48h112Z"/></svg><svg width="1em" height="1em" viewBox="0 0 256 256" class="hidden h-[18px] w-[18px]" data-icon="ph:check"><path fill="currentColor" d="m229.66 77.66l-128 128a8 8 0 0 1-11.32 0l-56-56a8 8 0 0 1 11.32-11.32L96 188.69L218.34 66.34a8 8 0 0 1 11.32 11.32"/></svg></button></div></div><div role="tabpanel" id="pm-panel-pm-e266aae9-yarn" aria-labelledby="pm-tab-pm-e266aae9-yarn" hidden data-nb-pm-panel class="bg-card ring-border relative overflow-hidden rounded-lg text-inherit ring"><div class="flex items-stretch"><pre class="text-foreground my-0 min-w-0 grow overflow-x-auto border-0 bg-transparent px-4 py-3 font-mono text-sm leading-relaxed whitespace-pre"><code data-nb-pm-code><span class="text-success">yarn</span><span class="text-warning"> add @cloudflare/workers-oauth-provider@latest</span></code></pre><button type="button" data-nb-pm-copy data-nb-command="yarn add @cloudflare/workers-oauth-provider@latest" aria-label="Copy to clipboard" class="border-border text-muted-foreground hover:text-foreground focus-visible:outline-ring m-0 flex shrink-0 cursor-pointer items-center justify-center border-0 border-l border-solid bg-transparent px-3 transition-colors focus-visible:outline-2 focus-visible:-outline-offset-2"><svg width="1em" height="1em" viewBox="0 0 256 256" class="h-[18px] w-[18px]" data-icon="ph:copy"><path fill="currentColor" d="M216 32H88a8 8 0 0 0-8 8v40H40a8 8 0 0 0-8 8v128a8 8 0 0 0 8 8h128a8 8 0 0 0 8-8v-40h40a8 8 0 0 0 8-8V40a8 8 0 0 0-8-8m-56 176H48V96h112Zm48-48h-32V88a8 8 0 0 0-8-8H96V48h112Z"/></svg><svg width="1em" height="1em" viewBox="0 0 256 256" class="hidden h-[18px] w-[18px]" data-icon="ph:check"><path fill="currentColor" d="m229.66 77.66l-128 128a8 8 0 0 1-11.32 0l-56-56a8 8 0 0 1 11.32-11.32L96 188.69L218.34 66.34a8 8 0 0 1 11.32 11.32"/></svg></button></div></div><div role="tabpanel" id="pm-panel-pm-e266aae9-pnpm" aria-labelledby="pm-tab-pm-e266aae9-pnpm" hidden data-nb-pm-panel class="bg-card ring-border relative overflow-hidden rounded-lg text-inherit ring"><div class="flex items-stretch"><pre class="text-foreground my-0 min-w-0 grow overflow-x-auto border-0 bg-transparent px-4 py-3 font-mono text-sm leading-relaxed whitespace-pre"><code data-nb-pm-code><span class="text-success">pnpm</span><span class="text-warning"> add @cloudflare/workers-oauth-provider@latest</span></code></pre><button type="button" data-nb-pm-copy data-nb-command="pnpm add @cloudflare/workers-oauth-provider@latest" aria-label="Copy to clipboard" class="border-border text-muted-foreground hover:text-foreground focus-visible:outline-ring m-0 flex shrink-0 cursor-pointer items-center justify-center border-0 border-l border-solid bg-transparent px-3 transition-colors focus-visible:outline-2 focus-visible:-outline-offset-2"><svg width="1em" height="1em" viewBox="0 0 256 256" class="h-[18px] w-[18px]" data-icon="ph:copy"><path fill="currentColor" d="M216 32H88a8 8 0 0 0-8 8v40H40a8 8 0 0 0-8 8v128a8 8 0 0 0 8 8h128a8 8 0 0 0 8-8v-40h40a8 8 0 0 0 8-8V40a8 8 0 0 0-8-8m-56 176H48V96h112Zm48-48h-32V88a8 8 0 0 0-8-8H96V48h112Z"/></svg><svg width="1em" height="1em" viewBox="0 0 256 256" class="hidden h-[18px] w-[18px]" data-icon="ph:check"><path fill="currentColor" d="m229.66 77.66l-128 128a8 8 0 0 1-11.32 0l-56-56a8 8 0 0 1 11.32-11.32L96 188.69L218.34 66.34a8 8 0 0 1 11.32 11.32"/></svg></button></div></div><div role="tabpanel" id="pm-panel-pm-e266aae9-bun" aria-labelledby="pm-tab-pm-e266aae9-bun" hidden data-nb-pm-panel class="bg-card ring-border relative overflow-hidden rounded-lg text-inherit ring"><div class="flex items-stretch"><pre class="text-foreground my-0 min-w-0 grow overflow-x-auto border-0 bg-transparent px-4 py-3 font-mono text-sm leading-relaxed whitespace-pre"><code data-nb-pm-code><span class="text-success">bun</span><span class="text-warning"> add @cloudflare/workers-oauth-provider@latest</span></code></pre><button type="button" data-nb-pm-copy data-nb-command="bun add @cloudflare/workers-oauth-provider@latest" aria-label="Copy to clipboard" class="border-border text-muted-foreground hover:text-foreground focus-visible:outline-ring m-0 flex shrink-0 cursor-pointer items-center justify-center border-0 border-l border-solid bg-transparent px-3 transition-colors focus-visible:outline-2 focus-visible:-outline-offset-2"><svg width="1em" height="1em" viewBox="0 0 256 256" class="h-[18px] w-[18px]" data-icon="ph:copy"><path fill="currentColor" d="M216 32H88a8 8 0 0 0-8 8v40H40a8 8 0 0 0-8 8v128a8 8 0 0 0 8 8h128a8 8 0 0 0 8-8v-40h40a8 8 0 0 0 8-8V40a8 8 0 0 0-8-8m-56 176H48V96h112Zm48-48h-32V88a8 8 0 0 0-8-8H96V48h112Z"/></svg><svg width="1em" height="1em" viewBox="0 0 256 256" class="hidden h-[18px] w-[18px]" data-icon="ph:check"><path fill="currentColor" d="m229.66 77.66l-128 128a8 8 0 0 1-11.32 0l-56-56a8 8 0 0 1 11.32-11.32L96 188.69L218.34 66.34a8 8 0 0 1 11.32 11.32"/></svg></button></div></div><nb-pm-restore style="display:contents"></nb-pm-restore></div></div><script type="module" src="https://developers.cloudflare.com/home/runner/work/cloudflare-docs/cloudflare-docs/src/components/ui/package-managers/PackageManagerTabs.astro?astro&type=script&index=0&lang.ts"></script> <p>Point your coding agent at <code>node_modules/@cloudflare/workers-oauth-provider/skills/migrate-to-1.0/SKILL.md</code>, or follow the <a href="https://github.com/cloudflare/workers-oauth-provider/blob/main/docs/migration-1.0.md" target="_blank" rel="noopener">migration guide<span class="external-link"> ↗︎</span></a>.</p> <p>For both Workers in full, refer to the <a href="https://github.com/cloudflare/workers-oauth-provider/tree/main/examples/split-workers" target="_blank" rel="noopener">split Workers example<span class="external-link"> ↗︎</span></a>.</p>
The text above is the source's. reef added the severity, the dates it could read from the text, and the endpoints and scopes named in code.