Skip to content

Commit 0ea0bc4

Browse files
authored
Oauth tests (#13)
* Moving the non-authed cfagents tests to /public * Added OAuth CF Agents tests --------- Co-authored-by: Glen Maddern <glen@glenmaddern.com>
1 parent fe65d50 commit 0ea0bc4

File tree

8 files changed

+127
-19
lines changed

8 files changed

+127
-19
lines changed

examples/servers/cf-agents/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717
},
1818
"devDependencies": {
1919
"@biomejs/biome": "^2.0.4",
20+
"@cloudflare/workers-oauth-provider": "^0.0.5",
2021
"@types/node": "^24.0.7",
2122
"agents": "^0.0.100",
23+
"hono": "^4.8.3",
2224
"typescript": "^5.8.3",
2325
"wrangler": "^4.22.0"
2426
}

examples/servers/cf-agents/pnpm-lock.yaml

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/servers/cf-agents/src/index.ts

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { McpAgent } from 'agents/mcp'
22
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
33
import { z } from 'zod'
4+
import OAuthProvider, { OAuthHelpers } from '@cloudflare/workers-oauth-provider'
5+
import { Hono } from 'hono'
46

57
// Define our MCP agent with tools
68
export class MyMCP extends McpAgent {
@@ -54,18 +56,59 @@ export class MyMCP extends McpAgent {
5456
}
5557
}
5658

59+
export type Bindings = Env & {
60+
OAUTH_PROVIDER: OAuthHelpers
61+
}
62+
63+
const app = new Hono<{
64+
Bindings: Bindings
65+
}>()
66+
67+
app.get('/authorize', async (c) => {
68+
const oauthReqInfo = await c.env.OAUTH_PROVIDER.parseAuthRequest(c.req.raw)
69+
const email = 'example@dotcom.com'
70+
const { redirectTo } = await c.env.OAUTH_PROVIDER.completeAuthorization({
71+
request: oauthReqInfo,
72+
userId: email,
73+
metadata: {
74+
label: 'Test User',
75+
},
76+
scope: oauthReqInfo.scope,
77+
props: {
78+
userEmail: email,
79+
},
80+
})
81+
return Response.redirect(redirectTo)
82+
})
83+
5784
export default {
5885
fetch(request: Request, env: Env, ctx: ExecutionContext) {
5986
const url = new URL(request.url)
6087

61-
if (url.pathname === '/sse' || url.pathname === '/sse/message') {
62-
return MyMCP.serveSSE('/sse').fetch(request, env, ctx)
88+
if (url.pathname === '/public/sse' || url.pathname === '/public/sse/message') {
89+
return MyMCP.serveSSE('/public/sse').fetch(request, env, ctx)
6390
}
6491

65-
if (url.pathname === '/mcp') {
66-
return MyMCP.serve('/mcp').fetch(request, env, ctx)
92+
if (url.pathname === '/public/mcp') {
93+
return MyMCP.serve('/public/mcp').fetch(request, env, ctx)
6794
}
6895

69-
return new Response('Not found', { status: 404 })
96+
return new OAuthProvider({
97+
apiRoute: ['/sse', '/mcp'],
98+
apiHandler: {
99+
// @ts-ignore
100+
fetch: (request, env, ctx) => {
101+
const { pathname } = new URL(request.url)
102+
if (pathname.startsWith('/sse')) return MyMCP.serveSSE('/sse').fetch(request as any, env, ctx)
103+
if (pathname === '/mcp') return MyMCP.serve('/mcp').fetch(request as any, env, ctx)
104+
return new Response('Not found', { status: 404 })
105+
},
106+
},
107+
// @ts-ignore
108+
defaultHandler: app,
109+
authorizeEndpoint: '/authorize',
110+
tokenEndpoint: '/token',
111+
clientRegistrationEndpoint: '/register',
112+
}).fetch(request, env, ctx)
70113
},
71114
}

examples/servers/cf-agents/worker-configuration.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
/* eslint-disable */
2-
// Generated by Wrangler by running `wrangler types` (hash: 15dd420dfccac8afafeb181790b9eddb)
2+
// Generated by Wrangler by running `wrangler types` (hash: 71a1247d25eb5ef8b66311103d5f9180)
33
// Runtime types generated with workerd@1.20250617.0 2025-03-10 nodejs_compat
44
declare namespace Cloudflare {
55
interface Env {
6+
OAUTH_KV: KVNamespace
67
MCP_OBJECT: DurableObjectNamespace<import('./src/index').MyMCP>
78
}
89
}

examples/servers/cf-agents/wrangler.jsonc

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,13 @@
2424
},
2525
"observability": {
2626
"enabled": true
27-
}
27+
},
28+
"kv_namespaces": [
29+
{
30+
"binding": "OAUTH_KV",
31+
"id": "<Add-KV-ID>"
32+
}
33+
]
2834
/**
2935
* Smart Placement
3036
* Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727
"prettier:fix": "prettier --write .",
2828
"fix:oranda": "sed -i 's/```tsx/```ts/g' README.md",
2929
"build:site": "npx @axodotdev/oranda build",
30-
"deploy:site": "npx wrangler deploy"
30+
"deploy:site": "npx wrangler deploy",
31+
"postinstall": "ln -sf ../../scripts/pre-commit .git/hooks/pre-commit"
3132
},
3233
"dependencies": {
3334
"strict-url-sanitise": "^0.0.1"

scripts/pre-commit

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/bin/sh
2+
3+
# Pre-commit hook to run prettier on staged files
4+
# This hook is called by "git commit" and formats only the files being committed.
5+
6+
echo "Running prettier on staged files..."
7+
8+
# Get list of staged files that prettier can handle
9+
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(js|jsx|ts|tsx|json|css|scss|md)$' | tr '\n' ' ')
10+
11+
if [ -z "$STAGED_FILES" ]; then
12+
echo "No staged files need formatting."
13+
exit 0
14+
fi
15+
16+
echo "Formatting files: $STAGED_FILES"
17+
18+
# Run prettier on staged files
19+
pnpm prettier --write $STAGED_FILES
20+
21+
# Add the formatted files back to staging
22+
git add $STAGED_FILES
23+
24+
echo "Prettier formatting completed."

test/integration/mcp-connection.test.ts

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,21 @@ function getMCPServers() {
3030
},
3131
{
3232
name: 'cf-agents',
33-
url: `http://localhost:${state.cfAgentsPort}/mcp`,
33+
url: `http://localhost:${state.cfAgentsPort}/public/mcp`,
3434
expectedTools: 1, // Minimum expected tools count
3535
},
3636
{
3737
name: 'cf-agents-sse',
38+
url: `http://localhost:${state.cfAgentsPort}/public/sse`,
39+
expectedTools: 1, // Minimum expected tools count
40+
},
41+
{
42+
name: 'cf-agents-auth',
43+
url: `http://localhost:${state.cfAgentsPort}/mcp`,
44+
expectedTools: 1, // Minimum expected tools count
45+
},
46+
{
47+
name: 'cf-agents-auth-sse',
3848
url: `http://localhost:${state.cfAgentsPort}/sse`,
3949
expectedTools: 1, // Minimum expected tools count
4050
},
@@ -216,23 +226,25 @@ describe('MCP Connection Integration Tests', () => {
216226
})
217227

218228
const testScenarios = [
219-
// Working examples with auto transport (should pass)
229+
// Hono examples (MCP only)
220230
{ serverName: 'hono-mcp', transportType: 'auto' as const },
221-
{ serverName: 'cf-agents', transportType: 'auto' as const },
222-
223-
// SSE endpoint with SSE transport (should pass)
224-
{ serverName: 'cf-agents-sse', transportType: 'sse' as const },
225-
226-
// Additional test cases for HTTP transport
227231
{ serverName: 'hono-mcp', transportType: 'http' as const },
228-
{ serverName: 'cf-agents', transportType: 'http' as const },
229232

230-
// Failing case: SSE endpoint with auto transport (should fail)
233+
// Agents, no auth
234+
{ serverName: 'cf-agents', transportType: 'auto' as const },
235+
{ serverName: 'cf-agents', transportType: 'http' as const },
236+
{ serverName: 'cf-agents-sse', transportType: 'sse' as const },
231237
{ serverName: 'cf-agents-sse', transportType: 'auto' as const },
238+
239+
// Agents, with auth
240+
{ serverName: 'cf-agents-auth', transportType: 'auto' as const },
241+
{ serverName: 'cf-agents-auth', transportType: 'http' as const },
242+
{ serverName: 'cf-agents-auth-sse', transportType: 'sse' as const },
243+
{ serverName: 'cf-agents-auth-sse', transportType: 'auto' as const },
232244
]
233245

234246
test.each(testScenarios)(
235-
'should connect to $serverName with $transportType transport (expect: $shouldPass)',
247+
'should connect to $serverName with $transportType transport',
236248
async ({ serverName, transportType }) => {
237249
const servers = getMCPServers()
238250
const server = servers.find((s) => s.name === serverName)

0 commit comments

Comments
 (0)