Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 10 additions & 12 deletions demos/remote-mcp-cf-access/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ You now have a remote MCP server deployed!

This MCP server uses Access for authentication. All authenticated Access users can access basic tools like "add".

The "generateImage" tool is restricted to specific Access users listed in the `ALLOWED_USERNAMES` configuration:
The "generateImage" tool is restricted to specific Access users listed in the `ALLOWED_EMAILS` configuration:

```typescript
// Add user emails for image generation access
Expand Down Expand Up @@ -119,7 +119,7 @@ When using Claude to connect to your remote MCP server, you may see some error m

To connect Cursor with your MCP server, choose `Type`: "Command" and in the `Command` field, combine the command and args fields into one (e.g. `npx mcp-remote https://<your-worker-name>.<your-subdomain>.workers.dev/mcp`).

Note that while Cursor supports HTTP+SSE servers, it doesn't support authentication, so you still need to use `mcp-remote` (and to use a STDIO server, not an HTTP one).
If your MCP client cannot complete OAuth for a remote server directly, use `mcp-remote` as a local STDIO adapter.

You can connect your MCP server to other MCP clients like Windsurf by opening the client's configuration file, adding the same JSON that was used for the Claude setup, and restarting the MCP client.

Expand All @@ -133,20 +133,18 @@ The OAuth Provider library serves as a complete OAuth 2.1 server implementation
- Managing the connection to Access's OAuth services
- Securely storing tokens and authentication state in KV storage

#### Durable MCP
#### Stateless MCP

Durable MCP extends the base MCP functionality with Cloudflare's Durable Objects, providing:
The MCP SDK v2 server uses a stateless Streamable HTTP handler, providing:

- Persistent state management for your MCP server
- Secure storage of authentication context between requests
- Access to authenticated user information via `this.props`
- A fresh MCP server instance for each request
- Access to authenticated user information via `getMcpAuthContext()`
- Support for conditional tool availability based on user identity

#### MCP Remote

The MCP Remote library enables your server to expose tools that can be invoked by MCP clients like the Inspector. It:
For clients that do not support remote MCP servers or OAuth directly, `mcp-remote` runs as a local STDIO adapter. It:

- Defines the protocol for communication between clients and your server
- Provides a structured way to define tools
- Handles serialization and deserialization of requests and responses
- Maintains the Server-Sent Events (SSE) connection between clients and your server
- Bridges local STDIO clients to the remote Streamable HTTP endpoint
- Opens the browser-based OAuth flow
- Forwards MCP requests and responses between the client and server
5 changes: 3 additions & 2 deletions demos/remote-mcp-cf-access/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
"type-check": "tsc --noEmit"
},
"dependencies": {
"@cloudflare/workers-oauth-provider": "^0.8.1",
"agents": "^0.17.1",
"@cloudflare/workers-oauth-provider": "^0.8.3",
"@modelcontextprotocol/server": "2.0.0",
"agents": "^0.20.1",
"just-pick": "^4.2.0",
"octokit": "^5.0.5",
"zod": "^4.4.3"
Expand Down
2 changes: 1 addition & 1 deletion demos/remote-mcp-cf-access/src/access-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ export async function handleAccessRequest(
metadata: {
label: user.name,
},
// This will be available on this.props inside MyMCP
// This will be available through getMcpAuthContext() in MCP handlers.
props: {
accessToken,
email: user.email,
Expand Down
81 changes: 47 additions & 34 deletions demos/remote-mcp-cf-access/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,39 @@
import { env } from "cloudflare:workers";
import OAuthProvider from "@cloudflare/workers-oauth-provider";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { McpAgent } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/server";
import { createMcpHandler, getMcpAuthContext } from "agents/mcp/server";
import { z } from "zod";
import { handleAccessRequest } from "./access-handler";
import type { Props } from "./workers-oauth-utils";

const ALLOWED_EMAILS = new Set(["<INSERT EMAIL>"]);

export class MyMCP extends McpAgent<Env, Record<string, never>, Props> {
server = new McpServer({
function createServer() {
const server = new McpServer({
name: "Access OAuth Proxy Demo",
version: "1.0.0",
});
const props = getMcpAuthContext()?.props as Props | undefined;

async init() {
// Hello, world!
this.server.tool(
"add",
"Add two numbers the way only MCP can",
{ a: z.number(), b: z.number() },
async ({ a, b }) => ({
content: [{ text: String(a + b), type: "text" }],
}),
);
server.registerTool(
"add",
{
description: "Add two numbers the way only MCP can",
inputSchema: z.object({ a: z.number(), b: z.number() }),
},
async ({ a, b }) => ({
content: [{ text: String(a + b), type: "text" }],
}),
);

// Dynamically add tools based on the user's login. In this case, I want to limit
// access to my Image Generation tool to just me
if (ALLOWED_EMAILS.has(this.props!.email)) {
this.server.tool(
"generateImage",
"Generate an image using the `flux-1-schnell` model. Works best with 8 steps.",
{
// Dynamically add tools based on the authenticated user's email.
if (props && ALLOWED_EMAILS.has(props.email)) {
server.registerTool(
"generateImage",
{
description:
"Generate an image using the `flux-1-schnell` model. Works best with 8 steps.",
inputSchema: z.object({
prompt: z
.string()
.describe("A text description of the image you want to generate."),
Expand All @@ -42,24 +45,34 @@ export class MyMCP extends McpAgent<Env, Record<string, never>, Props> {
.describe(
"The number of diffusion steps; higher values can improve quality but take longer. Must be between 4 and 8, inclusive.",
),
},
async ({ prompt, steps }) => {
const response = await this.env.AI.run("@cf/black-forest-labs/flux-1-schnell", {
prompt,
steps,
});
}),
},
async ({ prompt, steps }) => {
const response = await env.AI.run("@cf/black-forest-labs/flux-1-schnell", {
prompt,
steps,
});

return {
content: [{ data: response.image!, mimeType: "image/jpeg", type: "image" }],
};
},
);
}
return {
content: [{ data: response.image!, mimeType: "image/jpeg", type: "image" }],
};
},
);
}

return server;
}

const mcpHandler = createMcpHandler(createServer);
const apiHandler = {
fetch(request: Request, bindings: Env, ctx: ExecutionContext) {
return mcpHandler(request, bindings, ctx);
},
} satisfies ExportedHandler<Env>;

export default new OAuthProvider({
apiHandler: MyMCP.serve("/mcp"),
allowPlainPKCE: false,
apiHandler,
apiRoute: "/mcp",
authorizeEndpoint: "/authorize",
clientRegistrationEndpoint: "/register",
Expand Down
Loading
Loading