@kya-os/create-mcpi-app 1.7.19 → 1.7.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/.turbo/turbo-test$colon$coverage.log +315 -0
  3. package/.turbo/turbo-test.log +95 -0
  4. package/CHANGELOG.md +372 -0
  5. package/IMPLEMENTATION_SUMMARY.md +108 -0
  6. package/REMEDIATION_PLAN.md +99 -0
  7. package/coverage/base.css +224 -0
  8. package/coverage/block-navigation.js +87 -0
  9. package/coverage/clover.xml +252 -0
  10. package/coverage/config-builder.ts.html +580 -0
  11. package/coverage/coverage-final.json +7 -0
  12. package/coverage/favicon.png +0 -0
  13. package/coverage/fetch-cloudflare-mcpi-template.ts.html +7006 -0
  14. package/coverage/generate-config.ts.html +436 -0
  15. package/coverage/generate-identity.ts.html +574 -0
  16. package/coverage/index.html +191 -0
  17. package/coverage/install.ts.html +322 -0
  18. package/coverage/prettify.css +1 -0
  19. package/coverage/prettify.js +2 -0
  20. package/coverage/sort-arrow-sprite.png +0 -0
  21. package/coverage/sorter.js +210 -0
  22. package/coverage/validate-project-structure.ts.html +466 -0
  23. package/package.json +14 -7
  24. package/scripts/prepare-pack.js +47 -0
  25. package/scripts/validate-no-workspace.js +79 -0
  26. package/src/__tests__/cloudflare-template.test.ts +488 -0
  27. package/src/__tests__/helpers/fetch-cloudflare-mcpi-template.test.ts +337 -0
  28. package/src/__tests__/helpers/generate-config.test.ts +312 -0
  29. package/src/__tests__/helpers/generate-identity.test.ts +271 -0
  30. package/src/__tests__/helpers/install.test.ts +362 -0
  31. package/src/__tests__/helpers/validate-project-structure.test.ts +467 -0
  32. package/src/__tests__.bak/regression.test.ts +434 -0
  33. package/src/effects/index.ts +80 -0
  34. package/src/helpers/__tests__/config-builder.spec.ts +231 -0
  35. package/src/helpers/apply-identity-preset.ts +209 -0
  36. package/src/helpers/config-builder.ts +165 -0
  37. package/src/helpers/copy-template.ts +11 -0
  38. package/src/helpers/create.ts +239 -0
  39. package/src/helpers/fetch-cloudflare-mcpi-template.ts +2311 -0
  40. package/src/helpers/fetch-cloudflare-template.ts +361 -0
  41. package/src/helpers/fetch-mcpi-template.ts +236 -0
  42. package/src/helpers/fetch-xmcp-template.ts +153 -0
  43. package/src/helpers/generate-config.ts +117 -0
  44. package/src/helpers/generate-identity.ts +163 -0
  45. package/src/helpers/identity-manager.ts +186 -0
  46. package/src/helpers/install.ts +79 -0
  47. package/src/helpers/rename.ts +17 -0
  48. package/src/helpers/validate-project-structure.ts +127 -0
  49. package/src/index.ts +480 -0
  50. package/src/utils/check-node.ts +17 -0
  51. package/src/utils/is-folder-empty.ts +60 -0
  52. package/src/utils/validate-project-name.ts +132 -0
  53. package/test-cloudflare/README.md +164 -0
  54. package/test-cloudflare/package.json +28 -0
  55. package/test-cloudflare/src/index.ts +340 -0
  56. package/test-cloudflare/src/tools/greet.ts +19 -0
  57. package/test-cloudflare/tests/cache-invalidation.test.ts +410 -0
  58. package/test-cloudflare/tests/cors-security.test.ts +349 -0
  59. package/test-cloudflare/tests/delegation.test.ts +335 -0
  60. package/test-cloudflare/tests/do-routing.test.ts +314 -0
  61. package/test-cloudflare/tests/integration.test.ts +205 -0
  62. package/test-cloudflare/tests/session-management.test.ts +359 -0
  63. package/test-cloudflare/tsconfig.json +22 -0
  64. package/test-cloudflare/vitest.config.ts +9 -0
  65. package/test-cloudflare/wrangler.toml +37 -0
  66. package/test-node/README.md +44 -0
  67. package/test-node/package.json +23 -0
  68. package/test-node/src/tools/greet.ts +25 -0
  69. package/test-node/xmcp.config.ts +20 -0
  70. package/tsconfig.json +26 -0
  71. package/vitest.config.ts +14 -0
@@ -0,0 +1,132 @@
1
+ import fs from "fs-extra";
2
+ import path from "path";
3
+
4
+ /**
5
+ * Validate project name meets requirements
6
+ */
7
+ export function validateProjectName(name: string): {
8
+ valid: boolean;
9
+ issues: string[];
10
+ } {
11
+ const issues: string[] = [];
12
+
13
+ // Check if name is empty
14
+ if (!name || name.trim().length === 0) {
15
+ issues.push("Project name cannot be empty");
16
+ return { valid: false, issues };
17
+ }
18
+
19
+ const trimmedName = name.trim();
20
+
21
+ // Check length
22
+ if (trimmedName.length < 1) {
23
+ issues.push("Project name must be at least 1 character long");
24
+ }
25
+
26
+ if (trimmedName.length > 214) {
27
+ issues.push("Project name must be less than 214 characters");
28
+ }
29
+
30
+ // Check for invalid characters (npm package name rules)
31
+ if (!/^[a-z0-9@._/-]+$/.test(trimmedName)) {
32
+ issues.push(
33
+ "Project name can only contain lowercase letters, numbers, and the characters @._/-"
34
+ );
35
+ }
36
+
37
+ // Check if it starts with . or _
38
+ if (trimmedName.startsWith(".") || trimmedName.startsWith("_")) {
39
+ issues.push("Project name cannot start with . or _");
40
+ }
41
+
42
+ // Check for reserved names
43
+ const reservedNames = [
44
+ "node_modules",
45
+ "favicon.ico",
46
+ "package.json",
47
+ "package-lock.json",
48
+ "yarn.lock",
49
+ "pnpm-lock.yaml",
50
+ ".git",
51
+ ".gitignore",
52
+ ".env",
53
+ "readme",
54
+ "readme.md",
55
+ "readme.txt",
56
+ "license",
57
+ "license.md",
58
+ "license.txt",
59
+ "changelog",
60
+ "changelog.md",
61
+ "changelog.txt",
62
+ ];
63
+
64
+ if (reservedNames.includes(trimmedName.toLowerCase())) {
65
+ issues.push(
66
+ `"${trimmedName}" is a reserved name and cannot be used as a project name`
67
+ );
68
+ }
69
+
70
+ // Check for scoped package format
71
+ if (trimmedName.startsWith("@")) {
72
+ const parts = trimmedName.split("/");
73
+ if (parts.length !== 2) {
74
+ issues.push("Scoped package names must be in the format @scope/name");
75
+ } else if (parts[1].length === 0) {
76
+ issues.push("Scoped package name cannot have empty package name");
77
+ }
78
+ }
79
+
80
+ return {
81
+ valid: issues.length === 0,
82
+ issues,
83
+ };
84
+ }
85
+
86
+ /**
87
+ * Check if directory is available for project creation
88
+ */
89
+ export function validateDirectoryAvailability(projectPath: string): {
90
+ valid: boolean;
91
+ issues: string[];
92
+ } {
93
+ const issues: string[] = [];
94
+
95
+ try {
96
+ // Check if path exists
97
+ if (fs.existsSync(projectPath)) {
98
+ const stats = fs.statSync(projectPath);
99
+
100
+ if (!stats.isDirectory()) {
101
+ issues.push(`Path exists but is not a directory: ${projectPath}`);
102
+ } else {
103
+ // Check if directory is empty
104
+ const files = fs.readdirSync(projectPath);
105
+ const nonHiddenFiles = files.filter(
106
+ (file: string) => !file.startsWith(".")
107
+ );
108
+
109
+ if (nonHiddenFiles.length > 0) {
110
+ issues.push(`Directory is not empty: ${projectPath}`);
111
+ }
112
+ }
113
+ }
114
+
115
+ // Check if parent directory is writable
116
+ const parentDir = path.dirname(projectPath);
117
+ try {
118
+ fs.accessSync(parentDir, fs.constants.W_OK);
119
+ } catch {
120
+ issues.push(`Parent directory is not writable: ${parentDir}`);
121
+ }
122
+ } catch (error) {
123
+ issues.push(
124
+ `Error checking directory: ${error instanceof Error ? error.message : String(error)}`
125
+ );
126
+ }
127
+
128
+ return {
129
+ valid: issues.length === 0,
130
+ issues,
131
+ };
132
+ }
@@ -0,0 +1,164 @@
1
+ # test-cloudflare
2
+
3
+ MCP-I server running on Cloudflare Workers with identity verification.
4
+
5
+ ## Quick Start
6
+
7
+ ### 1. Install Dependencies
8
+
9
+ ```bash
10
+ npm install
11
+ ```
12
+
13
+ ### 2. Create KV Namespace
14
+
15
+ ```bash
16
+ npm run kv:create
17
+ ```
18
+
19
+ This runs: `wrangler kv namespace create NONCE_CACHE`
20
+
21
+ Copy the `id` from the output and update `wrangler.toml`:
22
+
23
+ ```toml
24
+ [[kv_namespaces]]
25
+ binding = "NONCE_CACHE"
26
+ id = "your-actual-kv-id-here" # ← Update this
27
+ ```
28
+
29
+ ### 3. Test Locally
30
+
31
+ ```bash
32
+ npm run dev
33
+ ```
34
+
35
+ **Endpoints:**
36
+ - MCP: http://localhost:8787/mcp
37
+ - Verify: http://localhost:8787/verify
38
+ - Health: http://localhost:8787/health
39
+
40
+ ### 4. Deploy to Cloudflare
41
+
42
+ ```bash
43
+ # Login to Cloudflare (first time only)
44
+ npx wrangler login
45
+
46
+ # Deploy to development
47
+ npm run deploy
48
+
49
+ # Deploy to production
50
+ npm run deploy --env production
51
+ ```
52
+
53
+ ## Connect with Claude Desktop
54
+
55
+ Add to your `claude_desktop_config.json`:
56
+
57
+ ```json
58
+ {
59
+ "mcpServers": {
60
+ "test-cloudflare": {
61
+ "command": "npx",
62
+ "args": [
63
+ "mcp-remote",
64
+ "https://your-worker.workers.dev/mcp"
65
+ ]
66
+ }
67
+ }
68
+ }
69
+ ```
70
+
71
+ ## Adding Tools
72
+
73
+ Create new tools in `src/tools/`:
74
+
75
+ ```typescript
76
+ // src/tools/example.ts
77
+ import { z } from "zod";
78
+ import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/types.js";
79
+
80
+ export const exampleTool = {
81
+ name: "example",
82
+ description: "Example tool description",
83
+ inputSchema: z.object({
84
+ input: z.string().describe("Input parameter"),
85
+ }),
86
+ handler: async (args: { [key: string]: any }, extra: RequestHandlerExtra<any, any>) => {
87
+ const { input } = args as { input: string };
88
+ return {
89
+ content: [
90
+ {
91
+ type: "text" as const,
92
+ text: `Result: ${input}`
93
+ }
94
+ ],
95
+ };
96
+ },
97
+ };
98
+ ```
99
+
100
+ Register in `src/index.ts`:
101
+
102
+ ```typescript
103
+ import { exampleTool } from "./tools/example";
104
+
105
+ async init() {
106
+ this.server.tool(
107
+ exampleTool.name,
108
+ exampleTool.description,
109
+ exampleTool.inputSchema.shape,
110
+ exampleTool.handler
111
+ );
112
+ }
113
+ ```
114
+
115
+ ## API Endpoints
116
+
117
+ ### `GET /health`
118
+ Health check endpoint.
119
+
120
+ **Response:**
121
+ ```json
122
+ {
123
+ "status": "healthy",
124
+ "timestamp": "2024-10-10T12:00:00.000Z"
125
+ }
126
+ ```
127
+
128
+ ### `POST /mcp`
129
+ MCP protocol endpoint for tool calls.
130
+
131
+ ### `POST /verify`
132
+ Verify MCP-I cryptographic proofs.
133
+
134
+ **Request:**
135
+ ```json
136
+ {
137
+ "proof": {
138
+ "agentDid": "did:web:example.com",
139
+ "timestamp": 1234567890,
140
+ "nonce": "abc123",
141
+ "signature": "...",
142
+ "publicKey": "..."
143
+ }
144
+ }
145
+ ```
146
+
147
+ **Response:**
148
+ ```json
149
+ {
150
+ "verified": true,
151
+ "agent": {
152
+ "did": "did:web:example.com",
153
+ "keyId": "key-123",
154
+ "scopes": ["read", "write"],
155
+ "session": "session-456"
156
+ }
157
+ }
158
+ ```
159
+
160
+ ## Learn More
161
+
162
+ - [MCP-I Documentation](https://github.com/kya-os/xmcp-i)
163
+ - [Cloudflare Workers](https://developers.cloudflare.com/workers/)
164
+ - [agents/mcp](https://www.npmjs.com/package/agents)
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "test-cloudflare",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "deploy": "wrangler deploy",
7
+ "dev": "wrangler dev",
8
+ "start": "wrangler dev",
9
+ "test": "vitest run",
10
+ "test:watch": "vitest",
11
+ "kv:create": "wrangler kv namespace create NONCE_CACHE",
12
+ "kv:list": "wrangler kv namespace list",
13
+ "type-check": "tsc --noEmit"
14
+ },
15
+ "dependencies": {
16
+ "@kya-os/mcp-i-cloudflare": "^1.0.0",
17
+ "@modelcontextprotocol/sdk": "^1.19.1",
18
+ "agents": "^0.2.8",
19
+ "hono": "^4.9.10",
20
+ "zod": "^3.25.76"
21
+ },
22
+ "devDependencies": {
23
+ "@cloudflare/workers-types": "^4.20240925.0",
24
+ "typescript": "^5.6.2",
25
+ "vitest": "^2.1.8",
26
+ "wrangler": "^4.42.2"
27
+ }
28
+ }
@@ -0,0 +1,340 @@
1
+ import { McpAgent } from "agents/mcp";
2
+ import { Hono } from "hono";
3
+ import { cors } from "hono/cors";
4
+ import { createCloudflareRuntime, KVProofArchive } from "@kya-os/mcp-i-cloudflare";
5
+ import type { MCPIRuntimeBase } from "@kya-os/mcp-i-core";
6
+ import { WELL_KNOWN_CORS_HEADERS } from "@kya-os/mcp-i-core";
7
+ import { greetTool } from "./tools/greet";
8
+
9
+ interface Env {
10
+ MCP_OBJECT: DurableObjectNamespace;
11
+ NONCE_CACHE: KVNamespace;
12
+ PROOF_ARCHIVE?: KVNamespace;
13
+ XMCP_I_TS_SKEW_SEC?: string;
14
+ XMCP_I_SESSION_TTL?: string;
15
+ ADMIN_PROOFS_ENABLED?: string;
16
+ MCP_IDENTITY_PRIVATE_KEY?: string;
17
+ MCP_IDENTITY_PUBLIC_KEY?: string;
18
+ MCP_IDENTITY_AGENT_DID?: string;
19
+ }
20
+
21
+ export class CloudflareMCP extends McpAgent {
22
+ private mcpiRuntime?: MCPIRuntimeBase;
23
+ private proofArchive?: KVProofArchive;
24
+ private sessionId?: string;
25
+
26
+ constructor(state: DurableObjectState, env: Env) {
27
+ super(state, env);
28
+
29
+ this.mcpiRuntime = createCloudflareRuntime({
30
+ env: env as any,
31
+ environment: 'development',
32
+ timestampSkewSeconds: parseInt(env.XMCP_I_TS_SKEW_SEC || '120'),
33
+ sessionTtlMinutes: parseInt(env.XMCP_I_SESSION_TTL || '1800') / 60,
34
+ audit: {
35
+ enabled: true
36
+ }
37
+ });
38
+
39
+ if (env.PROOF_ARCHIVE) {
40
+ this.proofArchive = new KVProofArchive(env.PROOF_ARCHIVE, {
41
+ ttl: 86400 * 30
42
+ });
43
+ }
44
+ }
45
+
46
+ async init() {
47
+ await this.mcpiRuntime?.initialize();
48
+
49
+ this.server.tool(
50
+ greetTool.name,
51
+ greetTool.description,
52
+ greetTool.inputSchema.shape,
53
+ async (args: any) => {
54
+ const identity = await this.mcpiRuntime!.getIdentity();
55
+ const sessionId = await this.ensureSessionId();
56
+ const establishedAt = await this.state.storage.get<number>("establishedAt") ?? Date.now();
57
+ const nonce = await this.mcpiRuntime!.issueNonce(sessionId);
58
+
59
+ const sessionContext = {
60
+ sessionId,
61
+ agentDid: identity.did,
62
+ kid: identity.kid,
63
+ establishedAt,
64
+ lastActivityAt: Date.now(),
65
+ nonce,
66
+ };
67
+
68
+ const result = await this.mcpiRuntime!.processToolCall(
69
+ greetTool.name,
70
+ args,
71
+ greetTool.handler,
72
+ sessionContext
73
+ );
74
+
75
+ if (this.proofArchive) {
76
+ const proof = this.mcpiRuntime!.getLastProof();
77
+ if (proof) {
78
+ await this.proofArchive.store(proof, { toolName: greetTool.name, sessionId });
79
+ }
80
+ }
81
+
82
+ return result;
83
+ }
84
+ );
85
+ }
86
+
87
+ private async ensureSessionId(): Promise<string> {
88
+ if (!this.sessionId) {
89
+ this.sessionId = await this.state.storage.get<string>("sessionId");
90
+ if (!this.sessionId) {
91
+ this.sessionId = crypto.randomUUID();
92
+ await this.state.storage.put("sessionId", this.sessionId);
93
+ await this.state.storage.put("establishedAt", Date.now());
94
+ }
95
+ }
96
+ return this.sessionId;
97
+ }
98
+
99
+ async fetch(request: Request): Promise<Response> {
100
+ const url = new URL(request.url);
101
+
102
+ if (url.pathname === '/_internal/identity') {
103
+ const identity = await this.mcpiRuntime!.getIdentity();
104
+ return new Response(JSON.stringify({
105
+ did: identity.did,
106
+ publicKey: identity.publicKey,
107
+ kid: identity.kid
108
+ }), {
109
+ headers: { 'Content-Type': 'application/json' }
110
+ });
111
+ }
112
+
113
+ if (url.pathname.startsWith('/_internal/well-known/')) {
114
+ const handler = this.mcpiRuntime!.createWellKnownHandler({
115
+ serviceName: 'test-cloudflare',
116
+ serviceEndpoint: request.headers.get('X-Service-Origin') || url.origin
117
+ });
118
+ const path = url.pathname.replace('/_internal/well-known/', '/.well-known/');
119
+ const result = await handler(path);
120
+
121
+ if (result?.status && result?.headers && result?.body) {
122
+ return new Response(result.body, {
123
+ status: result.status,
124
+ headers: result.headers
125
+ });
126
+ }
127
+
128
+ return new Response(JSON.stringify(result), {
129
+ headers: { 'Content-Type': 'application/json' }
130
+ });
131
+ }
132
+
133
+ if (url.pathname === '/_internal/verify') {
134
+ const body = await request.json() as { data: any; proof: any };
135
+ const isValid = await this.mcpiRuntime!.verifyProof(body.data, body.proof);
136
+ return new Response(JSON.stringify({ valid: isValid }), {
137
+ headers: { 'Content-Type': 'application/json' }
138
+ });
139
+ }
140
+
141
+ if (url.pathname === '/_internal/proofs') {
142
+ if (!this.proofArchive) {
143
+ return new Response(JSON.stringify({ error: 'Proof archive not initialized' }), {
144
+ status: 500,
145
+ headers: { 'Content-Type': 'application/json' }
146
+ });
147
+ }
148
+
149
+ const limit = parseInt(url.searchParams.get('limit') || '10');
150
+ const sessionId = url.searchParams.get('sessionId');
151
+
152
+ const proofs = sessionId
153
+ ? await this.proofArchive.getBySession(sessionId, limit)
154
+ : await this.proofArchive.getRecent(limit);
155
+
156
+ return new Response(JSON.stringify({ proofs }), {
157
+ headers: { 'Content-Type': 'application/json' }
158
+ });
159
+ }
160
+
161
+ if (url.pathname === '/_internal/proofs/tail') {
162
+ if (!this.proofArchive) {
163
+ return new Response(JSON.stringify({ error: 'Proof archive not initialized' }), {
164
+ status: 500,
165
+ headers: { 'Content-Type': 'application/json' }
166
+ });
167
+ }
168
+
169
+ const sessionId = url.searchParams.get('session') ?? '';
170
+ const limit = parseInt(url.searchParams.get('limit') || '10');
171
+ let iv: ReturnType<typeof setInterval> | undefined;
172
+
173
+ const stream = new ReadableStream({
174
+ start: async (controller) => {
175
+ const enc = new TextEncoder();
176
+ let lastSeenId: string | null = null;
177
+
178
+ const send = (p: any) => {
179
+ const data = `event: mcpi-proof\ndata: ${JSON.stringify(p)}\n\n`;
180
+ controller.enqueue(enc.encode(data));
181
+ };
182
+
183
+ const poll = async () => {
184
+ try {
185
+ const list = sessionId
186
+ ? await this.proofArchive!.getBySession(sessionId, limit)
187
+ : await this.proofArchive!.getRecent(limit);
188
+
189
+ for (const p of list) {
190
+ if (!lastSeenId || p.id > lastSeenId) {
191
+ send(p);
192
+ lastSeenId = p.id;
193
+ }
194
+ }
195
+ } catch (error) {
196
+ console.error('Error polling proofs:', error);
197
+ }
198
+ };
199
+
200
+ await poll();
201
+ iv = setInterval(poll, 500);
202
+ },
203
+ cancel() {
204
+ if (iv) clearInterval(iv);
205
+ }
206
+ });
207
+
208
+ return new Response(stream, {
209
+ headers: {
210
+ 'Content-Type': 'text/event-stream',
211
+ 'Cache-Control': 'no-cache',
212
+ 'Connection': 'keep-alive'
213
+ }
214
+ });
215
+ }
216
+
217
+ return super.fetch(request);
218
+ }
219
+ }
220
+
221
+ const app = new Hono<{ Bindings: Env }>();
222
+
223
+ app.use("/*", cors({
224
+ origin: "*",
225
+ allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
226
+ allowHeaders: ["Content-Type", "Authorization", "mcp-session-id", "mcp-protocol-version"],
227
+ exposeHeaders: ["mcp-session-id"],
228
+ }));
229
+
230
+ app.get("/health", (c) => c.json({
231
+ status: 'healthy',
232
+ timestamp: new Date().toISOString(),
233
+ transport: { sse: '/sse', streamableHttp: '/mcp' }
234
+ }));
235
+
236
+ app.get("/.well-known/did.json", async (c) => {
237
+ const stub = c.env.MCP_OBJECT.get(c.env.MCP_OBJECT.idFromName("singleton"));
238
+ const origin = new URL(c.req.url).origin;
239
+ const response = await stub.fetch("https://do/_internal/well-known/did.json", {
240
+ headers: { 'X-Service-Origin': origin }
241
+ });
242
+
243
+ return new Response(response.body, {
244
+ status: response.status,
245
+ headers: {
246
+ ...Object.fromEntries(response.headers),
247
+ ...WELL_KNOWN_CORS_HEADERS
248
+ }
249
+ });
250
+ });
251
+
252
+ app.get("/.well-known/agent.json", async (c) => {
253
+ const stub = c.env.MCP_OBJECT.get(c.env.MCP_OBJECT.idFromName("singleton"));
254
+ const origin = new URL(c.req.url).origin;
255
+ const response = await stub.fetch("https://do/_internal/well-known/agent.json", {
256
+ headers: { 'X-Service-Origin': origin }
257
+ });
258
+
259
+ return new Response(response.body, {
260
+ status: response.status,
261
+ headers: {
262
+ ...Object.fromEntries(response.headers),
263
+ ...WELL_KNOWN_CORS_HEADERS
264
+ }
265
+ });
266
+ });
267
+
268
+ app.get("/.well-known/mcp-identity", async (c) => {
269
+ const stub = c.env.MCP_OBJECT.get(c.env.MCP_OBJECT.idFromName("singleton"));
270
+ const origin = new URL(c.req.url).origin;
271
+ const response = await stub.fetch("https://do/_internal/well-known/mcp-identity", {
272
+ headers: { 'X-Service-Origin': origin }
273
+ });
274
+
275
+ return new Response(response.body, {
276
+ status: response.status,
277
+ headers: {
278
+ ...Object.fromEntries(response.headers),
279
+ ...WELL_KNOWN_CORS_HEADERS
280
+ }
281
+ });
282
+ });
283
+
284
+ app.post("/verify", async (c) => {
285
+ const stub = c.env.MCP_OBJECT.get(c.env.MCP_OBJECT.idFromName("singleton"));
286
+ const body = await c.req.json();
287
+
288
+ const response = await stub.fetch("https://do/_internal/verify", {
289
+ method: 'POST',
290
+ headers: { 'Content-Type': 'application/json' },
291
+ body: JSON.stringify(body)
292
+ });
293
+
294
+ return c.json(await response.json());
295
+ });
296
+
297
+ app.get("/admin/proofs", async (c) => {
298
+ if (c.env.ADMIN_PROOFS_ENABLED !== 'true') {
299
+ return c.json({ error: 'Admin endpoint disabled' }, 403);
300
+ }
301
+
302
+ const stub = c.env.MCP_OBJECT.get(c.env.MCP_OBJECT.idFromName("singleton"));
303
+ const limit = c.req.query('limit') || '10';
304
+ const sessionId = c.req.query('sessionId');
305
+
306
+ const url = new URL("https://do/_internal/proofs");
307
+ url.searchParams.set('limit', limit);
308
+ if (sessionId) {
309
+ url.searchParams.set('sessionId', sessionId);
310
+ }
311
+
312
+ const response = await stub.fetch(url.toString());
313
+ return c.json(await response.json());
314
+ });
315
+
316
+ app.get("/proofs", async (c) => {
317
+ const stub = c.env.MCP_OBJECT.get(c.env.MCP_OBJECT.idFromName("singleton"));
318
+ const sessionId = c.req.query('session') ?? '';
319
+ const limit = c.req.query('limit') || '10';
320
+
321
+ const url = new URL("https://do/_internal/proofs/tail");
322
+ url.searchParams.set('session', sessionId);
323
+ url.searchParams.set('limit', limit);
324
+
325
+ const response = await stub.fetch(url.toString());
326
+
327
+ return new Response(response.body, {
328
+ headers: {
329
+ 'Content-Type': 'text/event-stream',
330
+ 'Cache-Control': 'no-cache',
331
+ 'Connection': 'keep-alive',
332
+ 'Access-Control-Allow-Origin': '*'
333
+ }
334
+ });
335
+ });
336
+
337
+ app.mount("/sse", CloudflareMCP.serveSSE("/sse").fetch, { replaceRequest: false });
338
+ app.mount("/mcp", CloudflareMCP.serve("/mcp").fetch, { replaceRequest: false });
339
+
340
+ export default app;
@@ -0,0 +1,19 @@
1
+ import { z } from "zod";
2
+
3
+ export const greetTool = {
4
+ name: "greet",
5
+ description: "Greet a user by name",
6
+ inputSchema: z.object({
7
+ name: z.string().describe("The name of the user to greet")
8
+ }),
9
+ handler: async ({ name }: { name: string }) => {
10
+ return {
11
+ content: [
12
+ {
13
+ type: "text" as const,
14
+ text: `Hello, ${name}! Welcome to your Cloudflare MCP server.`
15
+ }
16
+ ]
17
+ };
18
+ }
19
+ };