.workers.dev/sse`.
+
+You can now test your deployed server using the MCP inspector by entering your Worker's URL.
+
+## Adding Authentication to your MCP Server
+
+OAuth-based user authentication allows you to control access and identify users. It uses the [Cloudflare Workers OAuth Provider](https://github.com/cloudflare/workers-oauth-provider) to handle the OAuth flow. Get started with the Authenticated MCP Worker template from the [cloudflare/agents repository](https://github.com/cloudflare/agents/tree/main/examples/mcp-worker-authenticated).
+
+[](https://deploy.workers.cloudflare.com/?url=https://github.com/cloudflare/agents/tree/main/examples/mcp-worker-authenticated)
+
+Alternatively, you follow the steps below to add authentication to your stateless MCP server.
+
+### Create your project
+
+Create a new directory and initialize it:
+
+```bash
+mkdir my-auth-mcp-server
+cd my-auth-mcp-server
+```
+
+
+
+Install the required dependencies:
+
+
+
+### Set up KV namespace
+
+The OAuth provider requires a KV namespace to store OAuth tokens and client registrations:
+
+```bash
+npx wrangler kv namespace create OAUTH_KV
+```
+
+This will output a namespace ID. Add it to your `wrangler.jsonc`:
+
+
+```jsonc
+{
+ "compatibility_date": "2025-10-08",
+ "compatibility_flags": ["nodejs_compat"],
+ "main": "src/index.ts",
+ "name": "my-auth-mcp-server",
+ "kv_namespaces": [
+ {
+ "binding": "OAUTH_KV",
+ "id": ""
+ }
+ ],
+ "observability": {
+ "logs": {
+ "enabled": true
+ }
+ }
+}
+```
+
+
+### Create an authorization handler
+
+Create a `src/auth-handler.ts` file to handle the OAuth flow:
+
+```typescript
+import type {
+ AuthRequest,
+ OAuthHelpers,
+} from "@cloudflare/workers-oauth-provider";
+import { Hono } from "hono";
+
+interface Env {
+ OAUTH_PROVIDER: OAuthHelpers;
+}
+
+const app = new Hono<{ Bindings: Env }>();
+
+app.get("/authorize", async (c) => {
+ const oauthReqInfo: AuthRequest = await c.env.OAUTH_PROVIDER.parseAuthRequest(
+ c.req.raw,
+ );
+ const clientInfo = await c.env.OAUTH_PROVIDER.lookupClient(
+ oauthReqInfo.clientId,
+ );
+
+ if (!clientInfo) {
+ return c.text("Invalid client_id", 400);
+ }
+
+ // Show approval page
+ const approvalPage = `
+
+
+
+ Authorize ${clientInfo.clientName || "MCP Client"}
+
+
+ Authorization Request
+ ${clientInfo.clientName || "An MCP Client"} is requesting access.
+
+
+
+ `;
+
+ return c.html(approvalPage);
+});
+
+app.post("/authorize", async (c) => {
+ const formData = await c.req.formData();
+ const state = formData.get("state");
+
+ if (!state || typeof state !== "string") {
+ return c.text("Missing state parameter", 400);
+ }
+
+ const oauthReqInfo: AuthRequest = JSON.parse(atob(state));
+
+ // Create a user profile
+ const userProfile = {
+ userId: "demo-user",
+ username: "Demo User",
+ email: "demo@example.com",
+ };
+
+ // Complete authorization
+ const { redirectTo } = await c.env.OAUTH_PROVIDER.completeAuthorization({
+ request: oauthReqInfo,
+ userId: userProfile.userId,
+ metadata: {
+ label: "MCP Server Access",
+ clientName:
+ (await c.env.OAUTH_PROVIDER.lookupClient(oauthReqInfo.clientId))
+ ?.clientName || "Unknown Client",
+ },
+ scope: oauthReqInfo.scope,
+ props: userProfile,
+ });
+
+ return c.redirect(redirectTo, 302);
+});
+
+export { app as AuthHandler };
+```
+
+### Implement your authenticated MCP server
+
+Create a `src/index.ts` file:
+
+```typescript
+import {
+ experimental_createMcpHandler as createMcpHandler,
+ getMcpAuthContext,
+} from "agents/mcp";
+import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
+import { z } from "zod";
+import { OAuthProvider } from "@cloudflare/workers-oauth-provider";
+import { AuthHandler } from "./auth-handler";
+
+const server = new McpServer({
+ name: "Authenticated MCP Server",
+ version: "1.0.0",
+});
+
+server.tool(
+ "hello",
+ "Returns a greeting message",
+ { name: z.string().optional() },
+ async ({ name }) => {
+ const auth = getMcpAuthContext();
+ const username = auth?.props?.username as string | undefined;
+
+ return {
+ content: [
+ {
+ text: `Hello, ${name ?? username ?? "World"}!`,
+ type: "text",
+ },
+ ],
+ };
+ },
+);
+
+// API Handler - handles authenticated MCP requests
+const apiHandler = {
+ async fetch(request: Request, env: unknown, ctx: ExecutionContext) {
+ return createMcpHandler(server)(request, env, ctx);
+ },
+};
+
+export default new OAuthProvider({
+ authorizeEndpoint: "/authorize",
+ tokenEndpoint: "/oauth/token",
+ clientRegistrationEndpoint: "/oauth/register",
+ apiRoute: "/mcp",
+ apiHandler: apiHandler,
+ //@ts-expect-error
+ defaultHandler: AuthHandler,
+});
+```
+
+### Access authentication context in tools
+
+Inside your MCP tools, you can access the authenticated user's information using `getMcpAuthContext()`:
+
+```typescript
+server.tool(
+ "my-tool",
+ "A tool that uses authentication",
+ {
+ /* ... */
+ },
+ async (params) => {
+ const auth = getMcpAuthContext();
+
+ if (!auth) {
+ // Handle unauthenticated request
+ return { content: [{ text: "Authentication required", type: "text" }] };
+ }
+
+ // Access user information set during oauth flow
+ const userId = auth.props?.userId;
+ const username = auth.props?.username;
+ const email = auth.props?.email;
+
+ // ...
+ },
+);
+```
+
+To test your authenticated server locally, run `npx wrangler dev` and connect using the MCP inspector. You will need to complete the OAuth flow to connect to your MCP server.
+
+Deploy with `npx wrangler deploy`. Your authenticated MCP server will be available at `https://my-auth-mcp-server..workers.dev/mcp`.
+
+## How stateless MCP servers work
+
+Stateless MCP servers use `experimental_createMcpHandler` to wrap a standard MCP Server instance and expose it as a Cloudflare Worker. The handler:
+
+1. Accepts HTTP requests on the `/mcp` endpoint (or a custom route)
+2. Handles the streamable-http transport for MCP
+3. Routes tool calls to your MCP server implementation
+4. Returns responses back to the MCP client
+
+For authenticated servers, the `OAuthProvider` wrapper:
+
+1. Handles OAuth endpoints (`/authorize`, `/token`, `/register`)
+2. Validates access tokens for incoming requests
+3. Injects user context into `getMcpAuthContext()`
+4. Routes authenticated requests to your MCP handler
+
+## Next steps
+
+- Add [tools](/agents/model-context-protocol/tools/) to your MCP server
+- Customize your MCP server's [authentication and authorization](/agents/model-context-protocol/authorization/)
+- Learn about [testing remote MCP servers](/agents/guides/test-remote-mcp-server/)
+- Explore the [Model Context Protocol specification](https://modelcontextprotocol.io/docs/getting-started/intro)
diff --git a/src/content/docs/agents/model-context-protocol/index.mdx b/src/content/docs/agents/model-context-protocol/index.mdx
index 1326b2f97a64a5..71faf095413bde 100644
--- a/src/content/docs/agents/model-context-protocol/index.mdx
+++ b/src/content/docs/agents/model-context-protocol/index.mdx
@@ -29,6 +29,7 @@ The MCP standard supports two modes of operation:
- **Local MCP connections**: MCP clients connect to MCP servers on the same machine, using [stdio](https://spec.modelcontextprotocol.io/specification/draft/basic/transports/#stdio) as a local transport method.
### Best Practices
+
- **Tool design**: Do not treat your MCP server as a wrapper around your full API schema. Instead, build tools that are optimized for specific user goals and reliable outcomes. Fewer, well-designed tools often outperform many granular ones, especially for agents with small context windows or tight latency budgets.
- **Scoped permissions**: Deploying several focused MCP servers, each with narrowly scoped permissions, reduces the risk of over-privileged access and makes it easier to manage and audit what each server is allowed to do.
- **Tool descriptions**: Detailed parameter descriptions help agents understand how to use your tools correctly — including what values are expected, how they affect behavior, and any important constraints. This reduces errors and improves reliability.
diff --git a/src/content/docs/agents/model-context-protocol/state.mdx b/src/content/docs/agents/model-context-protocol/state.mdx
new file mode 100644
index 00000000000000..4302dbdf663d49
--- /dev/null
+++ b/src/content/docs/agents/model-context-protocol/state.mdx
@@ -0,0 +1,23 @@
+---
+pcx_content_type: concept
+title: Stateful vs. Stateless MCP Servers
+tags:
+ - MCP
+sidebar:
+ order: 100
+---
+
+MCP was originally designed as a stateful protocol. The servers would maintain a long lived connection to a client and could maintain state between requests.
+
+However, since many MCP servers have been used as simply remote tools, prompts and resources, which for the most part do not require state, there was a push to make an optionally stateless version of the protocol.
+
+The stateless version of the protocol is the easiest to deploy and covers the majority of use cases. Users can host MCP servers on any platform that supports HTTP and Server-Sent Events (SSE), [including Cloudflare Workers](/agents/guides/build-stateless-mcp-server/), and take advantage of this stateless option.
+
+If you wish to maintain state inside the MCP Server you can migrate to the [McpAgent](/agents/guides/remote-mcp-server/) class which is backed by a Durable Object.
+
+| Feature | Stateless MCP Servers | Stateful MCP Servers |
+| ------------------ | ----------------------------------------------------------------------------- | ------------------------------------------------ |
+| **Transport** | `streamable-http` | `stdio`, `streamable-http` or `sse` (deprecated) |
+| **Connection** | Request-response per invocation | Long-lived connection |
+| **Use cases** | Remote tools, prompts, and resources | Elicitations, MCP Agents |
+| **Implementation** | [`experimental_createMcpHandler`](/agents/guides/build-stateless-mcp-server/) | [`McpAgent`](/agents/guides/remote-mcp-server/) |