|
1 | | -import app from "./app"; |
2 | | -import { McpAgent } from "agents/mcp"; |
3 | | -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; |
4 | | -import { z } from "zod"; |
5 | | -import OAuthProvider from "@cloudflare/workers-oauth-provider"; |
6 | | -import { handleKVNamespacesList } from "@repo/mcp-common/src/api/kv"; |
7 | | -import { getCloudflareClient } from "@repo/mcp-common/src/cloudflare-api"; |
| 1 | +import OAuthProvider from '@cloudflare/workers-oauth-provider' |
| 2 | +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js' |
| 3 | +import { McpAgent } from 'agents/mcp' |
| 4 | + |
8 | 5 | import { |
9 | 6 | CloudflareAuthHandler, |
10 | 7 | handleTokenExchangeCallback, |
11 | | - type AccountSchema, |
12 | | - type UserSchema, |
13 | | -} from "@repo/mcp-common/src/cloudflare-oauth-handler"; |
| 8 | +} from '@repo/mcp-common/src/cloudflare-oauth-handler' |
| 9 | +import { registerAccountTools } from '@repo/mcp-common/src/tools/account' |
| 10 | +import { registerKVTools } from '@repo/mcp-common/src/tools/kv_namespace' |
| 11 | +import { registerWorkersTools } from '@repo/mcp-common/src/tools/worker' |
| 12 | + |
| 13 | +import type { AccountSchema, UserSchema } from '@repo/mcp-common/src/cloudflare-oauth-handler' |
14 | 14 |
|
15 | | -export type WorkersBindingsMCPState = { activeAccountId: string | null }; |
| 15 | +export type WorkersBindingsMCPState = { activeAccountId: string | null } |
16 | 16 |
|
17 | 17 | // Context from the auth process, encrypted & stored in the auth token |
18 | 18 | // and provided to the DurableMCP as this.props |
19 | 19 | export type Props = { |
20 | | - accessToken: string; |
21 | | - user: UserSchema["result"]; |
22 | | - accounts: AccountSchema["result"]; |
23 | | -}; |
| 20 | + accessToken: string |
| 21 | + user: UserSchema['result'] |
| 22 | + accounts: AccountSchema['result'] |
| 23 | +} |
24 | 24 |
|
25 | 25 | export class WorkersBindingsMCP extends McpAgent<Env, WorkersBindingsMCPState, Props> { |
26 | 26 | server = new McpServer({ |
27 | | - name: "Demo", |
28 | | - version: "1.0.0", |
29 | | - }); |
| 27 | + name: 'Demo', |
| 28 | + version: '1.0.0', |
| 29 | + }) |
30 | 30 |
|
31 | 31 | initialState: WorkersBindingsMCPState = { |
32 | 32 | activeAccountId: null, |
33 | | - }; |
| 33 | + } |
34 | 34 |
|
35 | 35 | async init() { |
36 | | - this.server.tool("add", { a: z.number(), b: z.number() }, async ({ a, b }) => ({ |
37 | | - content: [{ type: "text", text: String(a + b) }], |
38 | | - })); |
39 | | - this.server.tool('kv_namespaces_list', "List all of the kv namespaces in your Cloudflare account", {}, async () => { |
40 | | - const account_id = this.getActiveAccountId(); |
41 | | - if (!account_id) { |
42 | | - return { |
43 | | - content: [ |
44 | | - { |
45 | | - type: "text", |
46 | | - text: "No currently active accountId. Try listing your accounts (accounts_list) and then setting an active account (set_active_account)", |
47 | | - }, |
48 | | - ], |
49 | | - }; |
50 | | - } |
51 | | - try { |
52 | | - const namespaces = await handleKVNamespacesList({ |
53 | | - client: getCloudflareClient(this.props.accessToken), |
54 | | - account_id |
55 | | - }); |
56 | | - |
57 | | - return { |
58 | | - content: [ |
59 | | - { |
60 | | - type: "text", |
61 | | - text: JSON.stringify({ |
62 | | - namespaces, |
63 | | - count: namespaces.length |
64 | | - }) |
65 | | - } |
66 | | - ] |
67 | | - } |
68 | | - } catch(error) { |
69 | | - return { |
70 | | - content: [ |
71 | | - { |
72 | | - type: "text", |
73 | | - text: `Error listing KV namespaces: ${error instanceof Error && error.message}`, |
74 | | - }, |
75 | | - ], |
76 | | - }; |
77 | | - } |
78 | | - }) |
| 36 | + registerAccountTools(this) |
| 37 | + registerKVTools(this) |
| 38 | + registerWorkersTools(this) |
79 | 39 | } |
80 | 40 | getActiveAccountId() { |
81 | 41 | // TODO: Figure out why this fail sometimes, and why we need to wrap this in a try catch |
82 | 42 | try { |
83 | | - return this.state.activeAccountId ?? null; |
| 43 | + return this.state.activeAccountId ?? null |
| 44 | + } catch (e) { |
| 45 | + return null |
| 46 | + } |
| 47 | + } |
| 48 | + |
| 49 | + setActiveAccountId(accountId: string) { |
| 50 | + // TODO: Figure out why this fail sometimes, and why we need to wrap this in a try catch |
| 51 | + try { |
| 52 | + this.setState({ |
| 53 | + ...this.state, |
| 54 | + activeAccountId: accountId, |
| 55 | + }) |
84 | 56 | } catch (e) { |
85 | | - return null; |
| 57 | + return null |
86 | 58 | } |
87 | 59 | } |
88 | 60 | } |
89 | 61 |
|
90 | 62 | // Export the OAuth handler as the default |
91 | 63 | export default new OAuthProvider({ |
92 | | - apiRoute: "/workers/bindings/sse", |
93 | | - // TODO: fix these types |
| 64 | + apiRoute: '/workers/bindings/sse', |
94 | 65 | // @ts-ignore |
95 | | - apiHandler: WorkersBindingsMCP.mount("/workers/bindings/sse"), |
| 66 | + apiHandler: WorkersBindingsMCP.mount('/workers/bindings/sse'), |
96 | 67 | // @ts-ignore |
97 | 68 | defaultHandler: CloudflareAuthHandler, |
98 | | - authorizeEndpoint: "/oauth/authorize", |
99 | | - tokenEndpoint: "/token", |
| 69 | + authorizeEndpoint: '/oauth/authorize', |
| 70 | + tokenEndpoint: '/token', |
100 | 71 | tokenExchangeCallback: handleTokenExchangeCallback, |
| 72 | + // Cloudflare access token TTL |
101 | 73 | accessTokenTTL: 3600, |
102 | | - clientRegistrationEndpoint: "/register", |
103 | | -}); |
| 74 | + clientRegistrationEndpoint: '/register', |
| 75 | +}) |
0 commit comments