diff --git a/src/assets/images/agents/mcp-demo-oauth-flow.png b/src/assets/images/agents/mcp-demo-oauth-flow.png new file mode 100644 index 000000000000000..beb9181b8e8dac4 Binary files /dev/null and b/src/assets/images/agents/mcp-demo-oauth-flow.png differ diff --git a/src/assets/images/agents/mcp-inspector-authenticated.png b/src/assets/images/agents/mcp-inspector-authenticated.png new file mode 100644 index 000000000000000..7029fc27bc42807 Binary files /dev/null and b/src/assets/images/agents/mcp-inspector-authenticated.png differ diff --git a/src/assets/images/agents/mcp-inspector-enter-url.png b/src/assets/images/agents/mcp-inspector-enter-url.png new file mode 100644 index 000000000000000..ed3596f4045bab9 Binary files /dev/null and b/src/assets/images/agents/mcp-inspector-enter-url.png differ diff --git a/src/content/docs/agents/model-context-protocol/index.mdx b/src/content/docs/agents/model-context-protocol/index.mdx new file mode 100644 index 000000000000000..44f1cc1f9de2d89 --- /dev/null +++ b/src/content/docs/agents/model-context-protocol/index.mdx @@ -0,0 +1,12 @@ +--- +title: Model Context Protocol (MCP) +pcx_content_type: navigation +sidebar: + order: 4 + group: + hideIndex: true +--- + +import { DirectoryListing } from "~/components"; + + diff --git a/src/content/docs/agents/model-context-protocol/mcp-client.mdx b/src/content/docs/agents/model-context-protocol/mcp-client.mdx new file mode 100644 index 000000000000000..4553d418ba1dcbf --- /dev/null +++ b/src/content/docs/agents/model-context-protocol/mcp-client.mdx @@ -0,0 +1,14 @@ +--- +pcx_content_type: concept +title: Agent as MCP Client +sidebar: + order: 4 +--- + +import { Render } from "~/components"; + +WIP + +- Explain use cases for Agent as an MCP Client, and the connection to this and using MCP Servers to provide tools +- Show example of using the Agents SDK to call an MCP Server +- Explain how this gets translated into the format of tools that people are used to from ai-sdk and from agents-sdk diff --git a/src/content/docs/agents/model-context-protocol/mcp-server.mdx b/src/content/docs/agents/model-context-protocol/mcp-server.mdx new file mode 100644 index 000000000000000..bc55d11ddc9710a --- /dev/null +++ b/src/content/docs/agents/model-context-protocol/mcp-server.mdx @@ -0,0 +1,8 @@ +--- +pcx_content_type: concept +title: MCP Servers +sidebar: + order: 1 +--- + +import { Render } from "~/components"; diff --git a/src/content/docs/agents/model-context-protocol/mcp-server/authentication/index.mdx b/src/content/docs/agents/model-context-protocol/mcp-server/authentication/index.mdx new file mode 100644 index 000000000000000..00a2243d5a174c4 --- /dev/null +++ b/src/content/docs/agents/model-context-protocol/mcp-server/authentication/index.mdx @@ -0,0 +1,17 @@ +--- +title: Authentication +sidebar: + order: 4 + group: + hideIndex: false +--- + +import { DirectoryListing } from "~/components"; + +WIP + +Explain / reiterate difference between authN and authz + +This section should have some examples of using other services / libraries for authentication. + +It should show you how to rip out the mock auth from the example project, and replace it with your own. diff --git a/src/content/docs/agents/model-context-protocol/mcp-server/authorization/external-oauth-provider.mdx b/src/content/docs/agents/model-context-protocol/mcp-server/authorization/external-oauth-provider.mdx new file mode 100644 index 000000000000000..c08d789f60f9ae0 --- /dev/null +++ b/src/content/docs/agents/model-context-protocol/mcp-server/authorization/external-oauth-provider.mdx @@ -0,0 +1,8 @@ +--- +pcx_content_type: concept +title: Use your own OAuth Provider +sidebar: + order: 4 +--- + +import { Render } from "~/components"; diff --git a/src/content/docs/agents/model-context-protocol/mcp-server/authorization/index.mdx b/src/content/docs/agents/model-context-protocol/mcp-server/authorization/index.mdx new file mode 100644 index 000000000000000..140104e0a7f205c --- /dev/null +++ b/src/content/docs/agents/model-context-protocol/mcp-server/authorization/index.mdx @@ -0,0 +1,146 @@ +--- +title: Authorization +sidebar: + order: 3 + group: + hideIndex: false +--- + +import { DirectoryListing } from "~/components"; + +When building a [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server, you need both a way to allow users to login (authentication) and allow them to grant the MCP client access to resources on their account (authorization). + + + + + +The Model Context Protocol uses [a subset of OAuth 2.1 for authorization](https://spec.modelcontextprotocol.io/specification/draft/basic/authorization/). OAuth allows your users to grant limited access to resources, without them having to share API keys or other credentials. + +Cloudflare provides an [OAuth Provider Library](https://github.com/geelen/mcp-remote-examples/tree/main/.vendor/workers-oauth-provider) that implements the provider side of the OAuth 2.1 protocol, allowing you to easily add authorization to your MCP server. + +You can use the OAuth Provider Library in three ways: + +1. **Your Worker handles authorization itself.** Your MCP server, running on Cloudflare, handles the complete OAuth flow. ([Example](/agents/model-context-protocol/mcp-server/getting-started/)) +2. **Integrate with a third-party OAuth provider**, such as GitHub or Google. ([Example](/agents/model-context-protocol/mcp-server/examples/third-party-oauth-provider/)) +3. **Integrate with your own OAuth provider**, including authorization-as-a-service providers such as Stytch and Auth0. ([Example](/agents/model-context-protocol/mcp-server/examples/external-oauth-provider/)) + +The following sections describe each of these options and link to runnable code examples for each. + +## Authorization options + +### (1) Your MCP Server handles authorization itself + +Your MCP Server, using the Cloudflare [MCP Server SDK](/agents/model-context-protocol/mcp-server/getting-started/) and [OAuth Provider Library](/agents/model-context-protocol/mcp-server/authorization/oauth-provider-api-reference/), can handle the complete OAuth authorization flow, without any third-party involvement. + +The [Workers OAuth Provider Library](/agents/model-context-protocol/mcp-server/authorization/oauth-provider-api-reference/) is a Cloudflare Worker that implements a [`fetch()` handler](/workers/runtime-apis/handlers/fetch/), and handles incoming requests to your MCP server. You provide your own handlers for your MCP Server's API, and autentication and authorization logic, and URI paths for the OAuth endpoints, and the SDK handles the rest. + +{/* TODO: Update link */} +The OAuth Provider Library comes with an [example handler implementation](https://github.com/geelen/mcp-remote-examples/tree/main/02-user-password/src/routes) for autentication and authorization, referenced below as `defaultHandler`: + +{/* TODO: GithubCodeComponent */} + +```ts +import OAuthProvider from "workers-oauth-provider"; + +// TODO: Bunch of naming decisions here +export default new OAuthProvider({ + apiRoute: "/mcp", + // Your MCP server: + apiHandler: MyMCPServer.Router, + // Your handler for authentication and authorization: + defaultHandler: OAuthProvider.defaultHandler, + // TODO: Should these have default values? + authorizeEndpoint: "/authorize", + tokenEndpoint: "/token", + clientRegistrationEndpoint: "/register", +}); +``` + +```mermaid +sequenceDiagram + participant B as User-Agent (Browser) + participant C as MCP Client + participant M as MCP Server (your Worker) + + C->>M: MCP Request + M->>C: HTTP 401 Unauthorized + Note over C: Generate code_verifier and code_challenge + C->>B: Open browser with authorization URL + code_challenge + B->>M: GET /authorize + Note over M: User logs in and authorizes + M->>B: Redirect to callback URL with auth code + B->>C: Callback with authorization code + C->>M: Token Request with code + code_verifier + M->>C: Access Token (+ Refresh Token) + C->>M: MCP Request with Access Token + Note over C,M: Begin standard MCP message exchange +``` + +Remember — [authentication is different from authorization](https://www.cloudflare.com/learning/access-management/authn-vs-authz/). Your MCP Server can handle authorization itself, while still relying on an external authentication service to first authenticate users. The [example](/agents/model-context-protocol/mcp-server/) in getting started provides a mock authentdcation flow. You will need to implement your own authentication handler — either handling authentication yourself, or using an external authentication service such as Clerk, Stytch, Auth0 or others. + +For a step-by-step example, refer to the [Worker as OAuth Provider](/agents/model-context-protocol/mcp-server/authorization/worker-as-oauth-provider/) section., and refer to the [API reference docs for the OAuth Provider SDK](/agents/model-context-protocol/mcp-server/authorization/oauth-sdk/). + +### (2) Third-party OAuth Provider + +The OAuth Provider Library can be configured to use a third-party OAuth provider, such as [GitHub](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app) or [Google](https://developers.google.com/identity/protocols/oauth2). + +When you use a third-party OAuth provider, you must provide a handler to the `OAuthProvider` that implements the OAuth flow for the third-party provider. + +```ts +import OAuthProvider from "workers-oauth-provider"; +import MyAuthHandler from "./auth-handler"; + +// TODO: Bunch of naming decisions here +export default new OAuthProvider({ + apiRoute: "/mcp", + // Your MCP server: + apiHandler: MyMCPServer.Router, + // Your handler for authentication and authorization with the third-party provider: + defaultHandler: MyAuthHandler, + // TODO: Should these have default values? + authorizeEndpoint: "/authorize", + tokenEndpoint: "/token", + clientRegistrationEndpoint: "/register", +}); +``` + +Note that as [defined in the Model Context Protocol specification](https://spec.modelcontextprotocol.io/specification/draft/basic/authorization/#292-flow-description) when you use a third-party OAuth provider, the MCP Server (your Worker) generates and issues its own token to the MCP client: + +```mermaid +sequenceDiagram + participant B as User-Agent (Browser) + participant C as MCP Client + participant M as MCP Server (your Worker) + participant T as Third-Party Auth Server + + C->>M: Initial OAuth Request + M->>B: Redirect to Third-Party /authorize + B->>T: Authorization Request + Note over T: User authorizes + T->>B: Redirect to MCP Server callback + B->>M: Authorization code + M->>T: Exchange code for token + T->>M: Third-party access token + Note over M: Generate bound MCP token + M->>B: Redirect to MCP Client callback + B->>C: MCP authorization code + C->>M: Exchange code for token + M->>C: MCP access token +``` + +Read the docs for the [Workers oAuth Provider Library](/agents/model-context-protocol/mcp-server/authorization/oauth-provider-api-reference/) for more details. + +### (3) Bring your own OAuth Provider + +If your application already implements an Oauth Provider itself, or you use Stytch, Auth0, or authorization-as-a-service provider, you can use this in the same way that you would use a third-party OAuth provider, described above in (2). + +The following examples show how to use the OAuth Provider Library with an external OAuth provider: + +- [Stytch](/agents/model-context-protocol/mcp-server/examples/stytch/) +- [Auth0](/agents/model-context-protocol/mcp-server/examples/auth0/) + +## Next steps + +- [Learn how to use the OAuth Provider SDK](/agents/model-context-protocol/mcp-server/authorization/oauth-provider-api-reference/) +- [Learn how to use a third-party OAuth provider](/agents/model-context-protocol/mcp-server/examples/third-party-oauth-provider/) +- [Learn how to bring your own OAuth provider](/agents/model-context-protocol/mcp-server/examples/external-oauth-provider/) diff --git a/src/content/docs/agents/model-context-protocol/mcp-server/authorization/oauth-provider-api-reference.mdx b/src/content/docs/agents/model-context-protocol/mcp-server/authorization/oauth-provider-api-reference.mdx new file mode 100644 index 000000000000000..3538e7e829ab986 --- /dev/null +++ b/src/content/docs/agents/model-context-protocol/mcp-server/authorization/oauth-provider-api-reference.mdx @@ -0,0 +1,11 @@ +--- +pcx_content_type: navigation +title: OAuth Provider Library +# TODO: Update link to published library +external_link: https://github.com/geelen/mcp-remote-examples/tree/main/.vendor/workers-oauth-provider +sidebar: + order: 5 +head: [] +--- + +{/* TODO: this page (or the linked docs) need to be really clear about what the defaultHandler must implement, and provide a really clear reference example */} diff --git a/src/content/docs/agents/model-context-protocol/mcp-server/authorization/third-party-oauth-provider.mdx b/src/content/docs/agents/model-context-protocol/mcp-server/authorization/third-party-oauth-provider.mdx new file mode 100644 index 000000000000000..72f765b04d11c0b --- /dev/null +++ b/src/content/docs/agents/model-context-protocol/mcp-server/authorization/third-party-oauth-provider.mdx @@ -0,0 +1,226 @@ +--- +pcx_content_type: concept +title: Third-party OAuth Provider +sidebar: + order: 3 +--- + +import { Render, GitHubCode } from "~/components"; + +{/* TODO: Why doesn't GitHub Code Component work? */} + +WIP + +Refer to the [GitHub OAuth Provider example](https://github.com/geelen/mcp-remote-examples/tree/main/04-oauth-pivot) for a complete example of how to use a third-party OAuth provider with the MCP Server SDK. + +```ts +import OAuthProvider, { + AuthRequest, + OAuthHelpers, +} from "workers-oauth-provider"; +import { MCPEntrypoint } from "./lib/MCPEntrypoint"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; +import { Hono } from "hono"; +import pick from "just-pick"; +import { Octokit } from "octokit"; + +// Context from the auth process, encrypted & stored in the auth token +// and provided to the MCP Server as this.props +type Props = { + login: string; + name: string; + email: string; + accessToken: string; +}; + +export class MyMCP extends MCPEntrypoint { + get server() { + const server = new McpServer({ + name: "Github OAuth Proxy Demo", + version: "1.0.0", + }); + + server.tool( + "add", + "Add two numbers the way only MCP can", + { a: z.number(), b: z.number() }, + async ({ a, b }) => ({ + content: [{ type: "text", text: String(a + b) }], + }), + ); + + server.tool( + "whoami", + "Tasty props from my OAuth provider", + {}, + async () => ({ + content: [ + { + type: "text", + text: JSON.stringify(pick(this.props, "login", "name", "email")), + }, + ], + }), + ); + + server.tool( + "userInfoHTTP", + "Get user info from GitHub, via HTTP", + {}, + async () => { + const res = await fetch("https://api.github.com/user", { + headers: { + Authorization: `Bearer ${this.props.accessToken}`, + "User-Agent": "04-auth-pivot", + }, + }); + return { content: [{ type: "text", text: await res.text() }] }; + }, + ); + + server.tool( + "userInfoOctokit", + "Get user info from GitHub, via Octokit", + {}, + async () => { + const octokit = new Octokit({ auth: this.props.accessToken }); + return { + content: [ + { + type: "text", + text: JSON.stringify(await octokit.rest.users.getAuthenticated()), + }, + ], + }; + }, + ); + return server; + } +} + +const app = new Hono<{ Bindings: Env & { OAUTH_PROVIDER: OAuthHelpers } }>(); + +/** + * OAuth Authorization Endpoint + * + * This route initiates the GitHub OAuth flow when a user wants to log in. + * It creates a random state parameter to prevent CSRF attacks and stores the + * original OAuth request information in KV storage for later retrieval. + * Then it redirects the user to GitHub's authorization page with the appropriate + * parameters so the user can authenticate and grant permissions. + */ +app.get("/authorize", async (c) => { + const oauthReqInfo = await c.env.OAUTH_PROVIDER.parseAuthRequest(c.req.raw); + // Store the request info in KV to catch ya up on the rebound + const randomString = crypto.randomUUID(); + await c.env.OAUTH_KV.put( + `login:${randomString}`, + JSON.stringify(oauthReqInfo), + { expirationTtl: 600 }, + ); + + const upstream = new URL(`https://github.com/login/oauth/authorize`); + upstream.searchParams.set("client_id", c.env.GITHUB_CLIENT_ID); + upstream.searchParams.set( + "redirect_uri", + new URL("/callback", c.req.url).href, + ); + upstream.searchParams.set("scope", "read:user"); + upstream.searchParams.set("state", randomString); + upstream.searchParams.set("response_type", "code"); + + return Response.redirect(upstream.href); +}); + +/** + * OAuth Callback Endpoint + * + * This route handles the callback from GitHub after user authentication. + * It exchanges the temporary code for an access token, then stores some + * user metadata & the auth token as part of the 'props' on the token passed + * down to the client. It ends by redirecting the client back to _its_ callback URL + */ +app.get("/callback", async (c) => { + const code = c.req.query("code") as string; + + // Get the oathReqInfo out of KV + const randomString = c.req.query("state"); + if (!randomString) { + return c.text("Missing state", 400); + } + const oauthReqInfo = await c.env.OAUTH_KV.get( + `login:${randomString}`, + { type: "json" }, + ); + if (!oauthReqInfo) { + return c.text("Invalid state", 400); + } + + // Exchange the code for an access token + const resp = await fetch(`https://github.com/login/oauth/access_token`, { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: new URLSearchParams({ + client_id: c.env.GITHUB_CLIENT_ID, + client_secret: c.env.GITHUB_CLIENT_SECRET, + code, + redirect_uri: new URL("/callback", c.req.url).href, + }).toString(), + }); + if (!resp.ok) { + console.log(await resp.text()); + return c.text("Failed to fetch access token", 500); + } + const body = await resp.formData(); + const accessToken = body.get("access_token"); + if (!accessToken) { + return c.text("Missing access token", 400); + } + + // Fetch the user info from GitHub + const apiRes = await fetch(`https://api.github.com/user`, { + headers: { + Authorization: `bearer ${accessToken}`, + "User-Agent": "04-auth-pivot", + }, + }); + if (!apiRes.ok) { + console.log(await apiRes.text()); + return c.text("Failed to fetch user", 500); + } + + const user = (await apiRes.json()) as Record; + const { login, name, email } = user; + + // Return back to the MCP client a new token + const { redirectTo } = await c.env.OAUTH_PROVIDER.completeAuthorization({ + request: oauthReqInfo, + userId: login, + metadata: { + label: name, + }, + scope: oauthReqInfo.scope, + // This will be available on this.props inside MyMCP + props: { + login, + name, + email, + accessToken, + } as Props, + }); + + return Response.redirect(redirectTo); +}); + +export default new OAuthProvider({ + apiRoute: "/sse", + apiHandler: MyMCP.Router, + defaultHandler: app, + authorizeEndpoint: "/authorize", + tokenEndpoint: "/token", + clientRegistrationEndpoint: "/register", +}); +``` diff --git a/src/content/docs/agents/model-context-protocol/mcp-server/authorization/worker-as-oauth-provider.mdx b/src/content/docs/agents/model-context-protocol/mcp-server/authorization/worker-as-oauth-provider.mdx new file mode 100644 index 000000000000000..06e8aba76d835cd --- /dev/null +++ b/src/content/docs/agents/model-context-protocol/mcp-server/authorization/worker-as-oauth-provider.mdx @@ -0,0 +1,14 @@ +--- +pcx_content_type: concept +title: Worker as OAuth Provider +sidebar: + order: 2 +--- + +import { Render } from "~/components"; + +WIP + +Take example from getting started + +And go deeper, showing someone how to replace the authentication layer with their own. diff --git a/src/content/docs/agents/model-context-protocol/mcp-server/examples/github.mdx b/src/content/docs/agents/model-context-protocol/mcp-server/examples/github.mdx new file mode 100644 index 000000000000000..d1e8d48b5dc7759 --- /dev/null +++ b/src/content/docs/agents/model-context-protocol/mcp-server/examples/github.mdx @@ -0,0 +1,8 @@ +--- +pcx_content_type: concept +title: GitHub as OAuth Provider +sidebar: + order: 3 +--- + +import { Render } from "~/components"; diff --git a/src/content/docs/agents/model-context-protocol/mcp-server/examples/index.mdx b/src/content/docs/agents/model-context-protocol/mcp-server/examples/index.mdx new file mode 100644 index 000000000000000..8486e2efcea9129 --- /dev/null +++ b/src/content/docs/agents/model-context-protocol/mcp-server/examples/index.mdx @@ -0,0 +1,12 @@ +--- +title: Examples +pcx_content_type: navigation +sidebar: + order: 6 + group: + hideIndex: true +--- + +import { DirectoryListing } from "~/components"; + + diff --git a/src/content/docs/agents/model-context-protocol/mcp-server/examples/stytch.mdx b/src/content/docs/agents/model-context-protocol/mcp-server/examples/stytch.mdx new file mode 100644 index 000000000000000..5a609619711ad92 --- /dev/null +++ b/src/content/docs/agents/model-context-protocol/mcp-server/examples/stytch.mdx @@ -0,0 +1,8 @@ +--- +pcx_content_type: concept +title: Stytch as OAuth Provider +sidebar: + order: 3 +--- + +import { Render } from "~/components"; diff --git a/src/content/docs/agents/model-context-protocol/mcp-server/getting-started.mdx b/src/content/docs/agents/model-context-protocol/mcp-server/getting-started.mdx new file mode 100644 index 000000000000000..44156d0086533fa --- /dev/null +++ b/src/content/docs/agents/model-context-protocol/mcp-server/getting-started.mdx @@ -0,0 +1,124 @@ +--- +pcx_content_type: concept +title: Getting Started +sidebar: + order: 2 +--- + +import { Details, Render, PackageManagers } from "~/components"; + +## Deploy your first MCP server + +This guide will walk you through how to deploy an [example MCP server](https://github.com/geelen/mcp-remote-examples/tree/main/02-user-password) to your Cloudflare account. You will then customize this example to suit your needs. + +{/* TODO: Update link to example */} +The link below will guide you through everything you need to do to deploy this [example MCP server](https://github.com/geelen/mcp-remote-examples/tree/main/02-user-password) to your Cloudflare account: + +{/* TODO: Update deploy to Workers button */} +[![Deploy to Workers](https://deploy.workers.cloudflare.com/button)](https://dash.cloudflare.com/?to=/:account/workers-and-pages/create/deploy-to-workers&repository=https://github.com/cloudflare/mcp-server-example) + +At the end of this process, you will have a new git repository on your GitHub or GitLab account for your MCP server, configured to automatically deploy Cloudflare each time you push a change or merge a pull request to the main branch of the repository. You can then clone this repository, [develop locally](/agents/model-context-protocol/mcp-server/getting-started/#local-development), and start writing code and building. + +Alternatively, you can use the command line as shown below to create a new MCP Server on your local machine. + +{/* TODO: Update path to example */} + + + +Now, you have the MCP server setup, with dependencies installed. Move into that project folder: + +```sh +cd my-mcp-server +``` + +### Local development + +In the directory of your new project, run the following command to start the development server: + +```sh +npm start +``` + +Your MCP server is now running on `http://localhost:8787/sse`. + +In a new terminal, run the [MCP inspector](https://github.com/modelcontextprotocol/mcp-inspector). The MCP inspector is an interactive MCP client that allows you to connect to your MCP server and invoke tools from a web browser. + +```sh +npx @modelcontextprotocol/inspector@latest +``` + +Open the MCP inspector in your web browser: + +```sh +open http://localhost:5173 +``` + +In the inspector, enter the URL of your MCP server, `http://localhost:8787/sse`, and click **Connect**: + +![MCP inspector — where to enter the URL of your MCP server](~/assets/images/agents/mcp-inspector-enter-url.png) + +You will be redirected to an example OAuth login page. Enter any username and password and click "Log in and approve" to continue. (you can add your own authentication and/or authorization provider to replace this. Refer to the [authorization](/agents/model-context-protocol/mcp-server/authorization/) section for details on how to do this.) + +![MCP OAuth Login Page](~/assets/images/agents/mcp-demo-oauth-flow.png) + +Once you have logged in, you will be redirected back to the inspector. You should see the "List Tools" button, which will list the tools that your MCP server exposes. + +![MCP inspector — authenticated](~/assets/images/agents/mcp-inspector-authenticated.png) + +### Deploy your MCP server + +You can deploy your MCP server to Cloudflare using the following [Wrangler CLI command](/workers/wrangler) within the example project: + +```sh +npx wrangler@latest deploy +``` + +If you have already [connected a git repository](/workers/ci-cd/builds/) to the Worker with your MCP server, you can deploy your MCP server by pushing a change or merging a pull request to the main branch of the repository. + +After deploying, take the URL of your deployed MCP server, and enter it in the MCP inspector running on `http://localhost:5173`. You now have a remote MCP server, deployed to Cloudflare, that MCP clients can connect to. + +## Add Authentication + +The example MCP server you just deployed above acts as an OAuth provider to MCP clients, handling authorization, but has a placeholder authentication flow. It lets you enter any username and password to log in, and doesn't actually authenticate you against any user database. + +In the next section, you will add a real authentication provider to your MCP server. Even if you already have an authentication provider in place, following these steps will show you more clearly how to integrate it with your MCP server. + +We'll use GitHub as the authentication provider here, but you can use any OAuth provider that supports the OAuth 2.0 specification, and we have examples for: + +- [Stytch](/agents/model-context-protocol/mcp-server/examples/stytch/) +- [Clerk](/agents/model-context-protocol/mcp-server/examples/clerk/) +- [Google](/agents/model-context-protocol/mcp-server/examples/google/) + +### Step 1 — Update the default handler + +In your example MCP server, open `src/index.ts`, and change the value of `defaultHandler` in to instead use the `GitHubHandler`: + +{/* TODO: GitHub Code Component */} + +```ts ins="OAuthProvider.GitHubHandler" +export default new OAuthProvider({ + apiRoute: "/sse", + apiHandler: MyMCP.Router, + defaultHandler: OAuthProvider.GitHubHandler, + authorizeEndpoint: "/authorize", + tokenEndpoint: "/token", + clientRegistrationEndpoint: "/register", +}); +``` + +{/* TODO: Update code link */} +This will ensure that your users are redirected to GitHub to authenticate. If you're curious to understand what the `GitHubHandler` does you can read the code [here](https://github.com/geelen/mcp-remote-examples/blob/main/04-oauth-pivot/src/index.ts#L48-L152). + +### Step 2 — Create a GitHub OAuth App + +{/* TODO: instructions */} + +## Next steps + +- Add [tools](/agents/model-context-protocol/mcp-server/tools/) to your MCP server. +- Customize your MCP Server's [authentication and authorization](/agents/model-context-protocol/mcp-server/authorization/). +- Try other [example MCP servers](/agents/model-context-protocol/mcp-server/examples/) diff --git a/src/content/docs/agents/model-context-protocol/mcp-server/index.mdx b/src/content/docs/agents/model-context-protocol/mcp-server/index.mdx new file mode 100644 index 000000000000000..1054df1d171086e --- /dev/null +++ b/src/content/docs/agents/model-context-protocol/mcp-server/index.mdx @@ -0,0 +1,41 @@ +--- +title: MCP Server SDK +pcx_content_type: navigation +sidebar: + order: 1 + group: + hideIndex: false +--- + +# Deploy an MCP Server to Cloudflare + +You can build and deploy MCP servers on Cloudflare, using the [`workers-mcp` package](https://github.com/cloudflare/workers-mcp), which provides an SDK for [authorization](/agents/model-context-protocol/mcp-server/authorization/), [transport](/agents/model-context-protocol/mcp-server/transport/), and [tool definition and discovery](/agents/model-context-protocol/mcp-server/tools/). + +This guide and the accompanying [examples](/agents/model-context-protocol/mcp-server/examples/) will help you get started. + +### What is the Model Context Protocol (MCP)? + +[Model Context Protocol (MCP)](https://modelcontextprotocol.io) is an open standard that connects AI systems with external applications. Think of MCP like a USB-C port for AI applications. Just as USB-C provides a standardized way to connect your devices to various accessories, MCP provides a standardized way to connect AI agents to different services. + +#### MCP Terminology + +- **MCP Hosts**: AI assistants (like [Claude](http://claude.ai) or [Cursor](http://cursor.com)), AI agents, or applications that need to access external capabilities. +- **MCP Clients**: Clients embedded within the MCP hosts that connect to MCP servers and invoke tools. Each MCP client instance has a single connection to an MCP server. +- **MCP Servers**: Applications that expose [tools](/agents/model-context-protocol/mcp-server/tools/), [prompts](https://modelcontextprotocol.io/docs/concepts/prompts), and [resources](https://modelcontextprotocol.io/docs/concepts/resources) that MCP clients can use. + +#### Remote vs. local MCP connections + +The MCP standard supports two modes of operation: + +- **Remote MCP connections**: MCP clients connect to MCP servers over the Internet, establishing a [long-lived connection using HTTP and Server-Sent Events (SSE)](/agents/model-context-protocol/mcp-server/transport/), and authorizing the MCP client access to resources on the user's account using [OAuth](/agents/model-context-protocol/mcp-server/authorization/). +- **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. + +Cloudflare's MCP Server SDK, is designed to support remote MCP connections. Remote MCP connections allow MCP clients that run in web browsers, mobile apps, and other environments outside of the end-user's machine to connect to your MCP server, such as [Claude.ai](https://www.anthropic.com/claude), and other AI agents. + +### Why deploy your MCP server to Cloudflare? + +- Define [tools](/agents/model-context-protocol/mcp-server/tools/) by just writing TypeScript methods, in the [same way you write RPC methods in Workers and Durable Objects](/workers/runtime-apis/rpc/). +- Authorization is [built-in](/agents/model-context-protocol/mcp-server/authorization/). The MCP Server SDK handles the hard parts of the OAuth flow for you. +- Transport over HTTP with Server-Sent Events (SSE) is [built-in](/agents/model-context-protocol/mcp-server/transport/), with automatic hibernation of idle connections to reduce costs +- You can create stateful MCP Servers with the [Agents SDK](/agents/) +- Your MCP server can be called by the [Agents SDK](/agents/), which is an MCP client diff --git a/src/content/docs/agents/model-context-protocol/mcp-server/tools.mdx b/src/content/docs/agents/model-context-protocol/mcp-server/tools.mdx new file mode 100644 index 000000000000000..1f9848ea61c6a98 --- /dev/null +++ b/src/content/docs/agents/model-context-protocol/mcp-server/tools.mdx @@ -0,0 +1,34 @@ +--- +pcx_content_type: concept +title: Tools +sidebar: + order: 2 +--- + +import { Render } from "~/components"; + +Model Context Protocol (MCP) tools are functions that a [MCP Server](/agents/model-context-protocol/mcp-server) provides and MCP clients can call. + +When you build MCP Servers with the `@cloudflare/model-context-protocol` package, you can define tools using the `@modelcontextprotocol/typescript-sdk` package. + +For example, the following code defines a simple MCP server that adds two numbers together: + +{/* TODO: Reference code in Github, link to a runnable example, use Deploy to Workers button */} + +```ts title="src/index.ts" +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp"; +import { DurableMCP } from "@cloudflare/model-context-protocol"; + +export class MyMCP extends DurableMCP { + server = new McpServer({ name: "Demo", version: "1.0.0" }); + async init() { + this.server.tool( + "add", + { a: z.number(), b: z.number() }, + async ({ a, b }) => ({ + content: [{ type: "text", text: String(a + b) }], + }), + ); + } +} +``` diff --git a/src/content/docs/agents/model-context-protocol/mcp-server/transport.mdx b/src/content/docs/agents/model-context-protocol/mcp-server/transport.mdx new file mode 100644 index 000000000000000..3b2d9ea9dadba2b --- /dev/null +++ b/src/content/docs/agents/model-context-protocol/mcp-server/transport.mdx @@ -0,0 +1,31 @@ +--- +pcx_content_type: concept +title: Transport +sidebar: + order: 5 +--- + +import { Render } from "~/components"; + +The Model Context Protocol (MCP) specification defines [two standard transport mechanisms](https://spec.modelcontextprotocol.io/specification/draft/basic/transport/): + +1. **stdio, communication over standard in and standard out** — designed for local MCP connections +2. **HTTP with Server-Sent Events (SSE)** — designed for remote MCP connections + +{/* TODO: Update link and make sure this gets merged into @modelcontextprotocol/typescript-sdk: https://github.com/modelcontextprotocol/typescript-sdk/pull/178 */} + +The MCP Server SDK supports remote MCP connections, using [HTTP with Server-Sent Events (SSE)](https://github.com/modelcontextprotocol/typescript-sdk/pull/178) as transport. SSE requires a persistent HTTP connection, which is supported by Cloudflare [Durable Objects](/durable-objects/). + +Transport is configured and handled automatically by the MCP Server SDK. You don't need to configure anything — it just works. + +## Automatic hibernation to reduce your costs + +SSE requires a persistent HTTP connection. The MCP Client opens a connection to the MCP Server and keeps it open while the application is running. For example, an MCP Client running in a browser will keep the connection open until the browser is closed. + +This means idle connections can be held open indefinately. On other serverless platforms, this results in high costs, because you are billed for the "wall clock" time while the connection is open, even if the connection is idle. + +When you build MCP Servers with the MCP Server SDK, the SDK automatically hibernates idle connections, waking them up only when new events are available. This means you are only billed for the time that the MCP Server is actively running, and not for the time that the connection is open but idle. + +This works by using the [WebSocket hibernation API](https://developers.cloudflare.com/durable-objects/best-practices/websockets/#websocket-hibernation-api) provided by Cloudflare Durable Objects. + +{/* TODO: Should we link to source code or explain more? */} diff --git a/src/content/docs/workers/runtime-apis/rpc/index.mdx b/src/content/docs/workers/runtime-apis/rpc/index.mdx index 67d00eab82f53a4..cb5f6b636ca98c0 100644 --- a/src/content/docs/workers/runtime-apis/rpc/index.mdx +++ b/src/content/docs/workers/runtime-apis/rpc/index.mdx @@ -4,10 +4,9 @@ title: Remote-procedure call (RPC) head: [] description: The built-in, JavaScript-native RPC system built into Workers and Durable Objects. - --- -import { DirectoryListing, Render, Stream, WranglerConfig } from "~/components" +import { DirectoryListing, Render, Stream, WranglerConfig } from "~/components"; :::note To use RPC, [define a compatibility date](/workers/configuration/compatibility-dates) of `2024-04-03` or higher, or include `rpc` in your [compatibility flags](/workers/configuration/compatibility-flags/#nodejs-compatibility-flag). @@ -15,8 +14,9 @@ To use RPC, [define a compatibility date](/workers/configuration/compatibility-d Workers provide a built-in, JavaScript-native [RPC (Remote Procedure Call)](https://en.wikipedia.org/wiki/Remote_procedure_call) system, allowing you to: -* Define public methods on your Worker that can be called by other Workers on the same Cloudflare account, via [Service Bindings](/workers/runtime-apis/bindings/service-bindings/rpc) -* Define public methods on [Durable Objects](/durable-objects) that can be called by other workers on the same Cloudflare account that declare a binding to it. +- Define public methods on your Worker that can be called by other Workers on the same Cloudflare account, via [Service Bindings](/workers/runtime-apis/bindings/service-bindings/rpc) +- Define public methods on [Durable Objects](/durable-objects) that can be called by other workers on the same Cloudflare account that declare a binding to it. +- Define public methods on [Model Context Protocol (MCP) Servers](/agents/model-context-protocol/mcp-server/) that are automatically translated to MCP [tools](/agents/model-context-protocol/mcp-server/tools/) that can be called by MCP clients. The RPC system is designed to feel as similar as possible to calling a JavaScript function in the same Worker. In most cases, you should be able to write code in the same way you would if everything was in a single Worker. @@ -42,11 +42,11 @@ As an exception to Structured Clone, application-defined classes (or objects wit The RPC system also supports a number of types that are not Structured Cloneable, including: -* Functions, which are replaced by stubs that call back to the sender. -* Application-defined classes that extend `RpcTarget`, which are similarly replaced by stubs. -* [ReadableStream](/workers/runtime-apis/streams/readablestream/) and [WriteableStream](/workers/runtime-apis/streams/writablestream/), with automatic streaming flow control. -* [Request](/workers/runtime-apis/request/) and [Response](/workers/runtime-apis/response/), for conveniently representing HTTP messages. -* RPC stubs themselves, even if the stub was received from a third Worker. +- Functions, which are replaced by stubs that call back to the sender. +- Application-defined classes that extend `RpcTarget`, which are similarly replaced by stubs. +- [ReadableStream](/workers/runtime-apis/streams/readablestream/) and [WriteableStream](/workers/runtime-apis/streams/writablestream/), with automatic streaming flow control. +- [Request](/workers/runtime-apis/request/) and [Response](/workers/runtime-apis/response/), for conveniently representing HTTP messages. +- RPC stubs themselves, even if the stub was received from a third Worker. ## Functions @@ -81,35 +81,33 @@ main = "./src/counter.js" import { WorkerEntrypoint, RpcTarget } from "cloudflare:workers"; class Counter extends RpcTarget { - #value = 0; + #value = 0; - increment(amount) { - this.#value += amount; - return this.#value; - } + increment(amount) { + this.#value += amount; + return this.#value; + } - get value() { - return this.#value; - } + get value() { + return this.#value; + } } export class CounterService extends WorkerEntrypoint { - async newCounter() { - return new Counter(); - } + async newCounter() { + return new Counter(); + } } export default { - fetch() { - return new Response("ok") - } -} + fetch() { + return new Response("ok"); + }, +}; ``` The method `increment` can be called directly by the client, as can the public property `value`: - - ```toml @@ -124,18 +122,18 @@ services = [ ```js export default { - async fetch(request, env) { - using counter = await env.COUNTER_SERVICE.newCounter(); + async fetch(request, env) { + using counter = await env.COUNTER_SERVICE.newCounter(); - await counter.increment(2); // returns 2 - await counter.increment(1); // returns 3 - await counter.increment(-5); // returns -2 + await counter.increment(2); // returns 2 + await counter.increment(1); // returns 3 + await counter.increment(-5); // returns -2 - const count = await counter.value; // returns -2 + const count = await counter.value; // returns -2 - return new Response(count); - } -} + return new Response(count); + }, +}; ``` :::note @@ -189,24 +187,24 @@ This works when calling properties of objects returned by RPC methods as well. F import { WorkerEntrypoint } from "cloudflare:workers"; export class MyService extends WorkerEntrypoint { - async foo() { - return { - bar: { - baz: () => "qux" - } - } - } + async foo() { + return { + bar: { + baz: () => "qux", + }, + }; + } } ``` ```js export default { - async fetch(request, env) { - using foo = env.MY_SERVICE.foo(); - let baz = await foo.bar.baz(); - return new Response(baz); - } -} + async fetch(request, env) { + using foo = env.MY_SERVICE.foo(); + let baz = await foo.bar.baz(); + return new Response(baz); + }, +}; ``` If the initial RPC ends up throwing an exception, then any pipelined calls will also fail with the same exception @@ -244,7 +242,11 @@ Currently, this proxying only lasts until the end of the Workers' execution cont In this video, we explore how Cloudflare Workers support Remote Procedure Calls (RPC) to simplify communication between Workers. Learn how to implement RPC in your JavaScript applications and build serverless solutions with ease. Whether you're managing microservices or optimizing web architecture, this tutorial will show you how to quickly set up and use Cloudflare Workers for RPC calls. By the end of this video, you'll understand how to call functions between Workers, pass functions as arguments, and implement user authentication with Cloudflare Workers. - + ## More Details @@ -252,5 +254,5 @@ In this video, we explore how Cloudflare Workers support Remote Procedure Calls ## Limitations -* [Smart Placement](/workers/configuration/smart-placement/) is currently ignored when making RPC calls. If Smart Placement is enabled for Worker A, and Worker B declares a [Service Binding](/workers/runtime-apis/bindings) to it, when Worker B calls Worker A via RPC, Worker A will run locally, on the same machine. -* The maximum serialized RPC limit is 1 MB. Consider using [`ReadableStream`](/workers/runtime-apis/streams/readablestream/) when returning more data. \ No newline at end of file +- [Smart Placement](/workers/configuration/smart-placement/) is currently ignored when making RPC calls. If Smart Placement is enabled for Worker A, and Worker B declares a [Service Binding](/workers/runtime-apis/bindings) to it, when Worker B calls Worker A via RPC, Worker A will run locally, on the same machine. +- The maximum serialized RPC limit is 1 MB. Consider using [`ReadableStream`](/workers/runtime-apis/streams/readablestream/) when returning more data. diff --git a/src/content/docs/workers/runtime-apis/rpc/mcp-tools.mdx b/src/content/docs/workers/runtime-apis/rpc/mcp-tools.mdx new file mode 100644 index 000000000000000..f0e43f57be0cd14 --- /dev/null +++ b/src/content/docs/workers/runtime-apis/rpc/mcp-tools.mdx @@ -0,0 +1,9 @@ +--- +pcx_content_type: navigation +title: Model Context Protocol (MCP) Tools +external_link: /agents/model-context-protocol/mcp-server/tools/ +sidebar: + order: 6 +head: [] +description: Write TypeScript methods using the JavaScript-native remote procedure call (RPC) system, and automatically translate these methods to Model Context Protocol (MCP) tools that can be called by MCP clients and AI agents. +---