Skip to content
This repository was archived by the owner on Feb 6, 2026. It is now read-only.
Merged
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
2 changes: 2 additions & 0 deletions examples/servers/cf-agents/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
},
"devDependencies": {
"@biomejs/biome": "^2.0.4",
"@cloudflare/workers-oauth-provider": "^0.0.5",
"@types/node": "^24.0.7",
"agents": "^0.0.100",
"hono": "^4.8.3",
"typescript": "^5.8.3",
"wrangler": "^4.22.0"
}
Expand Down
19 changes: 19 additions & 0 deletions examples/servers/cf-agents/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

53 changes: 48 additions & 5 deletions examples/servers/cf-agents/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { McpAgent } from 'agents/mcp'
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { z } from 'zod'
import OAuthProvider, { OAuthHelpers } from '@cloudflare/workers-oauth-provider'
import { Hono } from 'hono'

// Define our MCP agent with tools
export class MyMCP extends McpAgent {
Expand Down Expand Up @@ -54,18 +56,59 @@ export class MyMCP extends McpAgent {
}
}

export type Bindings = Env & {
OAUTH_PROVIDER: OAuthHelpers
}

const app = new Hono<{
Bindings: Bindings
}>()

app.get('/authorize', async (c) => {
const oauthReqInfo = await c.env.OAUTH_PROVIDER.parseAuthRequest(c.req.raw)
const email = 'example@dotcom.com'
const { redirectTo } = await c.env.OAUTH_PROVIDER.completeAuthorization({
request: oauthReqInfo,
userId: email,
metadata: {
label: 'Test User',
},
scope: oauthReqInfo.scope,
props: {
userEmail: email,
},
})
return Response.redirect(redirectTo)
})

export default {
fetch(request: Request, env: Env, ctx: ExecutionContext) {
const url = new URL(request.url)

if (url.pathname === '/sse' || url.pathname === '/sse/message') {
return MyMCP.serveSSE('/sse').fetch(request, env, ctx)
if (url.pathname === '/public/sse' || url.pathname === '/public/sse/message') {
return MyMCP.serveSSE('/public/sse').fetch(request, env, ctx)
}

if (url.pathname === '/mcp') {
return MyMCP.serve('/mcp').fetch(request, env, ctx)
if (url.pathname === '/public/mcp') {
return MyMCP.serve('/public/mcp').fetch(request, env, ctx)
}

return new Response('Not found', { status: 404 })
return new OAuthProvider({
apiRoute: ['/sse', '/mcp'],
apiHandler: {
// @ts-ignore
fetch: (request, env, ctx) => {
const { pathname } = new URL(request.url)
if (pathname.startsWith('/sse')) return MyMCP.serveSSE('/sse').fetch(request as any, env, ctx)
if (pathname === '/mcp') return MyMCP.serve('/mcp').fetch(request as any, env, ctx)
return new Response('Not found', { status: 404 })
},
},
// @ts-ignore
defaultHandler: app,
authorizeEndpoint: '/authorize',
tokenEndpoint: '/token',
clientRegistrationEndpoint: '/register',
}).fetch(request, env, ctx)
},
}
3 changes: 2 additions & 1 deletion examples/servers/cf-agents/worker-configuration.d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
/* eslint-disable */
// Generated by Wrangler by running `wrangler types` (hash: 15dd420dfccac8afafeb181790b9eddb)
// Generated by Wrangler by running `wrangler types` (hash: 71a1247d25eb5ef8b66311103d5f9180)
// Runtime types generated with workerd@1.20250617.0 2025-03-10 nodejs_compat
declare namespace Cloudflare {
interface Env {
OAUTH_KV: KVNamespace
MCP_OBJECT: DurableObjectNamespace<import('./src/index').MyMCP>
}
}
Expand Down
8 changes: 7 additions & 1 deletion examples/servers/cf-agents/wrangler.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,13 @@
},
"observability": {
"enabled": true
}
},
"kv_namespaces": [
{
"binding": "OAUTH_KV",
"id": "<Add-KV-ID>"
}
]
/**
* Smart Placement
* Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
"prettier:fix": "prettier --write .",
"fix:oranda": "sed -i 's/```tsx/```ts/g' README.md",
"build:site": "npx @axodotdev/oranda build",
"deploy:site": "npx wrangler deploy"
"deploy:site": "npx wrangler deploy",
"postinstall": "ln -sf ../../scripts/pre-commit .git/hooks/pre-commit"
},
"dependencies": {
"strict-url-sanitise": "^0.0.1"
Expand Down
24 changes: 24 additions & 0 deletions scripts/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/sh

# Pre-commit hook to run prettier on staged files
# This hook is called by "git commit" and formats only the files being committed.

echo "Running prettier on staged files..."

# Get list of staged files that prettier can handle
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(js|jsx|ts|tsx|json|css|scss|md)$' | tr '\n' ' ')

if [ -z "$STAGED_FILES" ]; then
echo "No staged files need formatting."
exit 0
fi

echo "Formatting files: $STAGED_FILES"

# Run prettier on staged files
pnpm prettier --write $STAGED_FILES

# Add the formatted files back to staging
git add $STAGED_FILES

echo "Prettier formatting completed."
34 changes: 23 additions & 11 deletions test/integration/mcp-connection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,21 @@ function getMCPServers() {
},
{
name: 'cf-agents',
url: `http://localhost:${state.cfAgentsPort}/mcp`,
url: `http://localhost:${state.cfAgentsPort}/public/mcp`,
expectedTools: 1, // Minimum expected tools count
},
{
name: 'cf-agents-sse',
url: `http://localhost:${state.cfAgentsPort}/public/sse`,
expectedTools: 1, // Minimum expected tools count
},
{
name: 'cf-agents-auth',
url: `http://localhost:${state.cfAgentsPort}/mcp`,
expectedTools: 1, // Minimum expected tools count
},
{
name: 'cf-agents-auth-sse',
url: `http://localhost:${state.cfAgentsPort}/sse`,
expectedTools: 1, // Minimum expected tools count
},
Expand Down Expand Up @@ -216,23 +226,25 @@ describe('MCP Connection Integration Tests', () => {
})

const testScenarios = [
// Working examples with auto transport (should pass)
// Hono examples (MCP only)
{ serverName: 'hono-mcp', transportType: 'auto' as const },
{ serverName: 'cf-agents', transportType: 'auto' as const },

// SSE endpoint with SSE transport (should pass)
{ serverName: 'cf-agents-sse', transportType: 'sse' as const },

// Additional test cases for HTTP transport
{ serverName: 'hono-mcp', transportType: 'http' as const },
{ serverName: 'cf-agents', transportType: 'http' as const },

// Failing case: SSE endpoint with auto transport (should fail)
// Agents, no auth
{ serverName: 'cf-agents', transportType: 'auto' as const },
{ serverName: 'cf-agents', transportType: 'http' as const },
{ serverName: 'cf-agents-sse', transportType: 'sse' as const },
{ serverName: 'cf-agents-sse', transportType: 'auto' as const },

// Agents, with auth
{ serverName: 'cf-agents-auth', transportType: 'auto' as const },
{ serverName: 'cf-agents-auth', transportType: 'http' as const },
{ serverName: 'cf-agents-auth-sse', transportType: 'sse' as const },
{ serverName: 'cf-agents-auth-sse', transportType: 'auto' as const },
]

test.each(testScenarios)(
'should connect to $serverName with $transportType transport (expect: $shouldPass)',
'should connect to $serverName with $transportType transport',
async ({ serverName, transportType }) => {
const servers = getMCPServers()
const server = servers.find((s) => s.name === serverName)
Expand Down
Loading