-
Notifications
You must be signed in to change notification settings - Fork 70
0.6.3 #488
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
0.6.3 #488
Changes from all commits
13e6ebd
c66090f
420becd
6d25cd9
ece0347
b12416a
bfecf93
0100a6a
e7d293c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| # Dependencies | ||
| node_modules/ | ||
|
|
||
| # Build output | ||
| dist/ | ||
| .xmcp/ | ||
| worker.js | ||
| wrangler.jsonc | ||
|
|
||
| # Environment variables (contains secrets) | ||
| .dev.vars | ||
|
|
||
| # IDE | ||
| .idea/ | ||
| .vscode/ | ||
| *.swp | ||
| *.swo | ||
|
|
||
| # OS | ||
| .DS_Store | ||
| Thumbs.db |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| # Cloudflare Workers Example | ||
|
|
||
| This example demonstrates how to deploy an xmcp MCP server to Cloudflare Workers. | ||
|
|
||
| ## Getting Started | ||
|
|
||
| 1. Install dependencies: | ||
|
|
||
| ```bash | ||
| pnpm install | ||
| ``` | ||
|
|
||
| 2. Build for Cloudflare Workers: | ||
|
|
||
| ```bash | ||
| pnpm build | ||
| ``` | ||
|
|
||
| This writes these files into the project root: | ||
|
|
||
| - `worker.js` - The bundled Cloudflare Worker | ||
| - `wrangler.jsonc` - Wrangler configuration template (only if you don't already have `wrangler.toml/jsonc`) | ||
|
|
||
| 3. Start development mode (watch + rebuild worker output) and Wrangler: | ||
|
|
||
| ```bash | ||
| pnpm dev | ||
| ``` | ||
|
|
||
| This runs `xmcp dev --cf` (rebuilds `worker.js`) and `wrangler dev` together. | ||
|
|
||
| 4. Test with curl: | ||
|
|
||
| ```bash | ||
| pnpm preview | ||
| # or | ||
| npx wrangler dev | ||
| ``` | ||
|
|
||
| ```bash | ||
| # Health check | ||
| curl http://localhost:8787/health | ||
|
|
||
| # List tools | ||
| curl -X POST http://localhost:8787/mcp \ | ||
| -H "Content-Type: application/json" \ | ||
| -H "Accept: application/json" \ | ||
| -d '{"jsonrpc":"2.0","method":"tools/list","id":1}' | ||
|
|
||
| # Call a tool | ||
| curl -X POST http://localhost:8787/mcp \ | ||
| -H "Content-Type: application/json" \ | ||
| -H "Accept: application/json" \ | ||
| -d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"hello","arguments":{"name":"World"}},"id":2}' | ||
| ``` | ||
|
|
||
| 5. Deploy to Cloudflare: | ||
|
|
||
| ```bash | ||
| pnpm deploy | ||
| # or | ||
| npx wrangler deploy | ||
| ``` | ||
|
|
||
| ## Notes | ||
|
|
||
| - The `--cf` flag builds a Cloudflare Workers-native bundle | ||
| - All Node.js APIs are bundled into the worker (no external dependencies) | ||
| - React component bundles are inlined at compile time | ||
| - The worker uses Web APIs only (no Node.js runtime dependencies) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| { | ||
| "name": "cloudflare-workers-example", | ||
| "version": "0.0.1", | ||
| "description": "Example xmcp server deployed to Cloudflare Workers", | ||
| "private": true, | ||
| "type": "module", | ||
| "scripts": { | ||
| "build": "xmcp build --cf", | ||
| "dev": "concurrently \"xmcp dev --cf\" \"npx wrangler dev\"", | ||
| "deploy": "npx wrangler deploy", | ||
| "preview": "npx wrangler dev" | ||
| }, | ||
| "dependencies": { | ||
| "xmcp": "workspace:*", | ||
| "zod": "^3.23.8" | ||
| }, | ||
| "devDependencies": { | ||
| "@cloudflare/workers-types": "^4.20241112.0", | ||
| "concurrently": "^9.2.0", | ||
| "typescript": "^5.7.2", | ||
| "wrangler": "^4.62.0" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| import { z } from "zod"; | ||
|
|
||
| export const metadata = { | ||
| name: "get-weather", | ||
| description: "Get the current weather for a location (mock data)", | ||
| }; | ||
|
|
||
| export const schema = { | ||
| location: z.string().describe("The city or location to get weather for"), | ||
| }; | ||
|
|
||
| export default async function getWeather({ location }: { location: string }) { | ||
| // This is mock data - in a real app you would call a weather API | ||
| const mockWeather = { | ||
| location, | ||
| temperature: Math.round(15 + Math.random() * 20), | ||
| conditions: ["sunny", "cloudy", "rainy", "partly cloudy"][ | ||
| Math.floor(Math.random() * 4) | ||
| ], | ||
| humidity: Math.round(40 + Math.random() * 40), | ||
| }; | ||
|
|
||
| return ( | ||
| `Weather in ${mockWeather.location}:\n` + | ||
| `Temperature: ${mockWeather.temperature}°C\n` + | ||
| `Conditions: ${mockWeather.conditions}\n` + | ||
| `Humidity: ${mockWeather.humidity}%` | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| import { z } from "zod"; | ||
| import type { ToolExtraArguments } from "xmcp"; | ||
|
|
||
| export const metadata = { | ||
| name: "hello", | ||
| description: "Says hello to a user", | ||
| }; | ||
|
|
||
| export const schema = { | ||
| name: z.string().describe("The name to greet"), | ||
| }; | ||
|
|
||
| export default async function hello( | ||
| { name }: { name: string }, | ||
| extra: ToolExtraArguments | ||
| ) { | ||
| const greeting = `Hello, ${name}! From Cloudflare Workers.`; | ||
|
|
||
| if (extra.authInfo) { | ||
| return `${greeting}\n(Authenticated as: ${extra.authInfo.clientId})`; | ||
| } | ||
|
|
||
| return greeting; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| { | ||
| "compilerOptions": { | ||
| "target": "ES2022", | ||
| "module": "ESNext", | ||
| "moduleResolution": "bundler", | ||
| "lib": ["ES2022"], | ||
| "types": ["@cloudflare/workers-types"], | ||
| "strict": true, | ||
| "esModuleInterop": true, | ||
| "skipLibCheck": true, | ||
| "forceConsistentCasingInFileNames": true, | ||
| "outDir": "dist", | ||
| "declaration": true, | ||
| "declarationMap": true, | ||
| "sourceMap": true | ||
| }, | ||
| "include": ["src/**/*", "xmcp-env.d.ts"], | ||
| "exclude": ["node_modules", "dist"] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| import { XmcpConfig } from "xmcp"; | ||
|
|
||
| const config: XmcpConfig = { | ||
| http: { | ||
| debug: true, | ||
| }, | ||
| paths: { | ||
| tools: "./src/tools", | ||
| prompts: false, | ||
| resources: false, | ||
| }, | ||
| }; | ||
|
|
||
| export default config; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,117 @@ | ||||||||||||||||||||
| import path from "path"; | ||||||||||||||||||||
| import fs from "fs-extra"; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| const DEFAULT_WRANGLER_VERSION = "^4.61.1"; | ||||||||||||||||||||
| const DEFAULT_WORKERS_TYPES_VERSION = "^4.20241112.0"; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| function sanitizeWorkerName(name: string): string { | ||||||||||||||||||||
| return name | ||||||||||||||||||||
| .replace(/^@[^/]+\//, "") | ||||||||||||||||||||
| .replace(/[^a-zA-Z0-9-]/g, "-") | ||||||||||||||||||||
| .toLowerCase(); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| function getProjectName(projectPath: string): string { | ||||||||||||||||||||
| try { | ||||||||||||||||||||
| const packageJsonPath = path.join(projectPath, "package.json"); | ||||||||||||||||||||
| if (fs.existsSync(packageJsonPath)) { | ||||||||||||||||||||
| const packageJson = fs.readJsonSync(packageJsonPath); | ||||||||||||||||||||
| if (packageJson.name) { | ||||||||||||||||||||
| return sanitizeWorkerName(packageJson.name); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } catch { | ||||||||||||||||||||
| // fall back to directory name | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| return sanitizeWorkerName(path.basename(projectPath)); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| function ensureWranglerConfig(projectPath: string): void { | ||||||||||||||||||||
| const wranglerTomlPath = path.join(projectPath, "wrangler.toml"); | ||||||||||||||||||||
| const wranglerJsoncPath = path.join(projectPath, "wrangler.jsonc"); | ||||||||||||||||||||
| if (fs.existsSync(wranglerTomlPath) || fs.existsSync(wranglerJsoncPath)) { | ||||||||||||||||||||
| return; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| const projectName = getProjectName(projectPath); | ||||||||||||||||||||
| const compatibilityDate = new Date().toISOString().split("T")[0]; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| const wranglerConfig = `{ | ||||||||||||||||||||
| "$schema": "node_modules/wrangler/config-schema.json", | ||||||||||||||||||||
| // Wrangler config generated by: create-xmcp-app --cloudflare | ||||||||||||||||||||
| // Docs: https://developers.cloudflare.com/workers/wrangler/configuration/ | ||||||||||||||||||||
| "name": ${JSON.stringify(projectName)}, | ||||||||||||||||||||
| "main": "worker.js", | ||||||||||||||||||||
| "compatibility_date": ${JSON.stringify(compatibilityDate)}, | ||||||||||||||||||||
| "compatibility_flags": ["nodejs_compat"], | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Observability (Workers Logs) | ||||||||||||||||||||
| "observability": { | ||||||||||||||||||||
| "enabled": true | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
| `; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| fs.writeFileSync(wranglerJsoncPath, wranglerConfig); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| function ensureTsConfig(projectPath: string): void { | ||||||||||||||||||||
| const tsconfigPath = path.join(projectPath, "tsconfig.json"); | ||||||||||||||||||||
| if (!fs.existsSync(tsconfigPath)) { | ||||||||||||||||||||
| return; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| const tsconfig = fs.readJsonSync(tsconfigPath); | ||||||||||||||||||||
| tsconfig.compilerOptions = tsconfig.compilerOptions ?? {}; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| if (!Array.isArray(tsconfig.compilerOptions.types)) { | ||||||||||||||||||||
| return; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
Comment on lines
+68
to
+70
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The current logic in The logic should be adjusted to handle cases where
Suggested change
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| const types: string[] = tsconfig.compilerOptions.types; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| if (!types.includes("@cloudflare/workers-types")) { | ||||||||||||||||||||
| types.push("@cloudflare/workers-types"); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| tsconfig.compilerOptions.types = types; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| fs.writeJsonSync(tsconfigPath, tsconfig, { spaces: 2 }); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| export function applyCloudflareSettings(projectPath: string): void { | ||||||||||||||||||||
| const packageJsonPath = path.join(projectPath, "package.json"); | ||||||||||||||||||||
| if (fs.existsSync(packageJsonPath)) { | ||||||||||||||||||||
| const packageJson = fs.readJsonSync(packageJsonPath); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| packageJson.scripts = packageJson.scripts ?? {}; | ||||||||||||||||||||
| packageJson.scripts.build = "xmcp build --cf"; | ||||||||||||||||||||
| if (!packageJson.scripts.dev || packageJson.scripts.dev === "xmcp dev") { | ||||||||||||||||||||
| packageJson.scripts.dev = | ||||||||||||||||||||
| "concurrently \"xmcp dev --cf\" \"npx wrangler dev\""; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| packageJson.scripts.deploy = | ||||||||||||||||||||
| packageJson.scripts.deploy ?? "npx wrangler deploy"; | ||||||||||||||||||||
| packageJson.scripts.preview = | ||||||||||||||||||||
| packageJson.scripts.preview ?? "npx wrangler dev"; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| if (!packageJson.type) { | ||||||||||||||||||||
| packageJson.type = "module"; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| packageJson.devDependencies = packageJson.devDependencies ?? {}; | ||||||||||||||||||||
| packageJson.devDependencies.concurrently = | ||||||||||||||||||||
| packageJson.devDependencies.concurrently ?? "^9.2.0"; | ||||||||||||||||||||
| packageJson.devDependencies.wrangler = | ||||||||||||||||||||
| packageJson.devDependencies.wrangler ?? DEFAULT_WRANGLER_VERSION; | ||||||||||||||||||||
| packageJson.devDependencies["@cloudflare/workers-types"] = | ||||||||||||||||||||
| packageJson.devDependencies["@cloudflare/workers-types"] ?? | ||||||||||||||||||||
| DEFAULT_WORKERS_TYPES_VERSION; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| fs.writeJsonSync(packageJsonPath, packageJson, { spaces: 2 }); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| ensureWranglerConfig(projectPath); | ||||||||||||||||||||
| ensureTsConfig(projectPath); | ||||||||||||||||||||
| } | ||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For improved readability and maintainability, consider using an array of strings joined by a newline character instead of string concatenation with
+. This pattern is often cleaner when building multi-line strings.