diff --git a/examples/cloudflare-workers/.gitignore b/examples/cloudflare-workers/.gitignore new file mode 100644 index 000000000..8821deea8 --- /dev/null +++ b/examples/cloudflare-workers/.gitignore @@ -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 diff --git a/examples/cloudflare-workers/README.md b/examples/cloudflare-workers/README.md new file mode 100644 index 000000000..f4a3f9170 --- /dev/null +++ b/examples/cloudflare-workers/README.md @@ -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) diff --git a/examples/cloudflare-workers/package.json b/examples/cloudflare-workers/package.json new file mode 100644 index 000000000..650e9e48c --- /dev/null +++ b/examples/cloudflare-workers/package.json @@ -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" + } +} diff --git a/examples/cloudflare-workers/src/tools/get-weather.ts b/examples/cloudflare-workers/src/tools/get-weather.ts new file mode 100644 index 000000000..a9fdb435a --- /dev/null +++ b/examples/cloudflare-workers/src/tools/get-weather.ts @@ -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}%` + ); +} diff --git a/examples/cloudflare-workers/src/tools/hello.ts b/examples/cloudflare-workers/src/tools/hello.ts new file mode 100644 index 000000000..67c701153 --- /dev/null +++ b/examples/cloudflare-workers/src/tools/hello.ts @@ -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; +} diff --git a/examples/cloudflare-workers/tsconfig.json b/examples/cloudflare-workers/tsconfig.json new file mode 100644 index 000000000..82779d610 --- /dev/null +++ b/examples/cloudflare-workers/tsconfig.json @@ -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"] +} diff --git a/examples/cloudflare-workers/xmcp.config.ts b/examples/cloudflare-workers/xmcp.config.ts new file mode 100644 index 000000000..a43036f65 --- /dev/null +++ b/examples/cloudflare-workers/xmcp.config.ts @@ -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; diff --git a/examples/with-nestjs/tsconfig.json b/examples/with-nestjs/tsconfig.json index 5b42bb960..a06758043 100644 --- a/examples/with-nestjs/tsconfig.json +++ b/examples/with-nestjs/tsconfig.json @@ -19,8 +19,8 @@ "noFallthroughCasesInSwitch": false, "esModuleInterop": true, "paths": { - "@xmcp/*": ["./.xmcp/*"] - } + "@xmcp/*": ["./.xmcp/*"], + }, }, - "include": ["src/**/*", "xmcp-env.d.ts", ".xmcp/**/*"] + "include": ["src/**/*", "xmcp-env.d.ts", ".xmcp/**/*"], } diff --git a/packages/create-xmcp-app/src/helpers/cloudflare.ts b/packages/create-xmcp-app/src/helpers/cloudflare.ts new file mode 100644 index 000000000..e71b8825e --- /dev/null +++ b/packages/create-xmcp-app/src/helpers/cloudflare.ts @@ -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; + } + + 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); +} diff --git a/packages/create-xmcp-app/src/helpers/create.ts b/packages/create-xmcp-app/src/helpers/create.ts index 8f856a089..26299b23d 100644 --- a/packages/create-xmcp-app/src/helpers/create.ts +++ b/packages/create-xmcp-app/src/helpers/create.ts @@ -6,6 +6,7 @@ import { renameFiles } from "./rename.js"; import { updatePackageJson } from "./update-package.js"; import { install } from "./install.js"; import { generateConfig } from "./generate-config.js"; +import { applyCloudflareSettings } from "./cloudflare.js"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -20,6 +21,7 @@ interface ProjectOptions { paths?: string[]; template?: string; tailwind?: boolean; + cloudflare?: boolean; } /** @@ -45,6 +47,7 @@ export function createProject(options: ProjectOptions): void { paths = ["tools", "prompts", "resources"], template = "typescript", tailwind = false, + cloudflare = false, } = options; // Ensure the project directory exists @@ -76,6 +79,10 @@ export function createProject(options: ProjectOptions): void { // Update package.json with project configuration updatePackageJson(projectPath, projectName, transports); + if (cloudflare) { + applyCloudflareSettings(projectPath); + } + // Create necessary project directories createProjectDirectories(projectPath); diff --git a/packages/create-xmcp-app/src/index.ts b/packages/create-xmcp-app/src/index.ts index 563b8154f..3042d6930 100644 --- a/packages/create-xmcp-app/src/index.ts +++ b/packages/create-xmcp-app/src/index.ts @@ -11,6 +11,7 @@ import { checkNodeVersion } from "./utils/check-node.js"; import { createProject } from "./helpers/create.js"; import { isFolderEmpty } from "./utils/is-folder-empty.js"; import { downloadAndExtractExample, listExamples } from "./helpers/examples.js"; +import { applyCloudflareSettings } from "./helpers/cloudflare.js"; checkNodeVersion(); @@ -42,6 +43,7 @@ const program = new Command() .option("--skip-install", "Skip installing dependencies", false) .option("--http", "Enable HTTP transport", false) .option("--stdio", "Enable STDIO transport", false) + .option("--cloudflare, --cf", "Initialize for Cloudflare Workers", false) .option("--gpt", "Initialize with GPT App template", false) .option("--ui", "Initialize with MCP App template", false) .option( @@ -86,6 +88,10 @@ const program = new Command() await downloadAndExtractExample(targetPath, options.example); + if (options.cloudflare) { + applyCloudflareSettings(targetPath); + } + console.log(); console.log("Next steps:"); if (projectDir) { @@ -344,6 +350,7 @@ const program = new Command() paths: selectedPaths, template, tailwind, + cloudflare: options.cloudflare, }); spinner.succeed(chalk.green("Your xmcp app is ready")); diff --git a/packages/plugins/auth0/package.json b/packages/plugins/auth0/package.json index f2ed1e60c..26d473c84 100644 --- a/packages/plugins/auth0/package.json +++ b/packages/plugins/auth0/package.json @@ -1,7 +1,7 @@ { "name": "@xmcp-dev/auth0", "description": "Auth0 authentication integration for xmcp", - "version": "0.0.2", + "version": "0.1.0", "author": { "name": "xmcp", "email": "support@xmcp.dev", @@ -40,9 +40,9 @@ "@types/express": "^4.17.25", "@types/node": "^22.19.2", "typescript": "^5.9.3", - "xmcp": "^0.5.8" + "xmcp": "^0.6.2" }, "peerDependencies": { - "xmcp": "^0.5.8" + "xmcp": "^0.6.2" } } diff --git a/packages/xmcp/bundler/build-main.ts b/packages/xmcp/bundler/build-main.ts index a05a29084..a10790cf4 100644 --- a/packages/xmcp/bundler/build-main.ts +++ b/packages/xmcp/bundler/build-main.ts @@ -56,6 +56,7 @@ function getConfig() { name: "main", entry: { index: path.join(srcPath, "index.ts"), + cloudflare: path.join(srcPath, "cloudflare.ts"), cli: path.join(srcPath, "cli.ts"), "detached-flush": path.join( srcPath, diff --git a/packages/xmcp/bundler/build-runtime.ts b/packages/xmcp/bundler/build-runtime.ts index 1003d142c..6e8f97326 100644 --- a/packages/xmcp/bundler/build-runtime.ts +++ b/packages/xmcp/bundler/build-runtime.ts @@ -1,6 +1,6 @@ /** - * This script builds the compiler. It's not the compiler itself - * */ + * This script builds the runtime files. It's not the compiler itself. + */ import path from "path"; import { fileURLToPath } from "url"; @@ -19,6 +19,7 @@ interface RuntimeRoot { path: string; } +// Node.js runtime roots (express, nextjs adapters + transports) const runtimeRoots: RuntimeRoot[] = [ { name: "headers", path: "headers" }, { name: "stdio", path: "transports/stdio" }, @@ -29,13 +30,13 @@ const runtimeRoots: RuntimeRoot[] = [ ]; const entry: EntryObject = {}; - for (const root of runtimeRoots) { entry[root.name] = path.join(srcPath, "runtime", root.path); } +// Node.js config (express, nextjs, http, stdio) const config: RspackOptions = { - name: "runtime", + name: "runtime-node", entry, mode: "production", devtool: false, @@ -122,12 +123,13 @@ export function buildRuntime(onCompiled: (stats: any) => void) { const handleStats = (err: Error | null, stats: any) => { if (err) { - console.error(err); + console.error("Runtime build error:", err); return; } if (stats?.hasErrors()) { console.error( + "Runtime build errors:", stats.toString({ colors: true, chunks: false, @@ -143,8 +145,6 @@ export function buildRuntime(onCompiled: (stats: any) => void) { }) ); - console.log(chalk.bgGreen.bold("xmcp runtime compiled")); - if (process.env.GENERATE_STATS === "true" && stats) { const statsJson = stats.toJson({ all: false, @@ -159,9 +159,10 @@ export function buildRuntime(onCompiled: (stats: any) => void) { console.log(chalk.green(`Saved runtime stats to ${statsPath}`)); } - // Only call onCompiled once for the initial build if (!compileStarted) { compileStarted = true; + console.log(chalk.bgGreen.bold("xmcp runtime compiled")); + onCompiled(stats); } }; diff --git a/packages/xmcp/package.json b/packages/xmcp/package.json index 6688d9f44..3e241f724 100644 --- a/packages/xmcp/package.json +++ b/packages/xmcp/package.json @@ -37,8 +37,29 @@ "bin": { "xmcp": "dist/cli.js" }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "require": "./dist/index.js", + "default": "./dist/index.js" + }, + "./cloudflare": { + "types": "./dist/cloudflare.d.ts", + "require": "./dist/cloudflare.js", + "default": "./dist/cloudflare.js" + }, + "./package.json": "./package.json" + }, + "typesVersions": { + "*": { + "cloudflare": [ + "dist/cloudflare.d.ts" + ] + } + }, "files": [ - "dist" + "dist", + "src" ], "scripts": { "dev": "cross-env NODE_ENV=development tsx --watch --tsconfig ./bundler.tsconfig.json ./bundler/index.ts", @@ -49,6 +70,7 @@ "dependencies": { "@modelcontextprotocol/sdk": "^1.25.3", "@rspack/core": "^1.6.7", + "jose": "^6.1.3", "postcss-loader": "^8.2.0", "ts-checker-rspack-plugin": "^1.2.1", "typescript": "^5.9.3" diff --git a/packages/xmcp/src/cli.ts b/packages/xmcp/src/cli.ts index 50013e933..76bdb7241 100644 --- a/packages/xmcp/src/cli.ts +++ b/packages/xmcp/src/cli.ts @@ -2,6 +2,7 @@ import { Command } from "commander"; import { compile } from "./compiler"; import { buildVercelOutput } from "./platforms/build-vercel-output"; +import { buildCloudflareOutput } from "./platforms/build-cloudflare-output"; import chalk from "chalk"; import { xmcpLogo } from "./utils/cli-icons"; import { @@ -17,13 +18,16 @@ program.name("xmcp").description("The MCP framework CLI").version("0.0.1"); program .command("dev") .description("Start development mode") - .action(async () => { + .option("--cf", "Enable Cloudflare Workers output in development") + .action(async (options) => { console.log(`${xmcpLogo} Starting development mode...`); + const isCloudflareDev = options.cf || process.env.CF_PAGES === "1"; await compilerContextProvider( { mode: "development", - // Ignore platforms on dev mode - platforms: {}, + platforms: { + cloudflare: isCloudflareDev, + }, }, async () => { await compile(); @@ -35,15 +39,18 @@ program .command("build") .description("Build for production") .option("--vercel", "Build for Vercel deployment") + .option("--cf", "Build for Cloudflare Workers deployment") .action(async (options) => { console.log(`${xmcpLogo} Building for production...`); const isVercelBuild = options.vercel || process.env.VERCEL === "1"; + const isCloudflareBuild = options.cf || process.env.CF_PAGES === "1"; await compilerContextProvider( { mode: "production", platforms: { vercel: isVercelBuild, + cloudflare: isCloudflareBuild, }, }, async () => { @@ -63,6 +70,19 @@ program ); } } + if (isCloudflareBuild) { + console.log(`${xmcpLogo} Building for Cloudflare Workers...`); + try { + await buildCloudflareOutput(); + } catch (error) { + console.error( + chalk.red( + "❌ Failed to create Cloudflare output structure:" + ), + error + ); + } + } }, }); } diff --git a/packages/xmcp/src/cloudflare.ts b/packages/xmcp/src/cloudflare.ts new file mode 100644 index 000000000..53c388d6f --- /dev/null +++ b/packages/xmcp/src/cloudflare.ts @@ -0,0 +1,6 @@ +export { cloudflareApiKeyAuthMiddleware as apiKeyAuthMiddleware } from "./runtime/platforms/cloudflare/middlewares/api-key"; +export { + cloudflareJwtAuthMiddleware as jwtAuthMiddleware, + type CloudflareJWTAuthMiddlewareConfig as JWTAuthMiddlewareConfig, +} from "./runtime/platforms/cloudflare/middlewares/jwt"; +export type { WebMiddleware } from "./types/middleware"; diff --git a/packages/xmcp/src/compiler/compiler-context.ts b/packages/xmcp/src/compiler/compiler-context.ts index bb9d854c5..4aef399b5 100644 --- a/packages/xmcp/src/compiler/compiler-context.ts +++ b/packages/xmcp/src/compiler/compiler-context.ts @@ -11,6 +11,8 @@ interface CompilerContext { platforms: { /** Generates a .vercel folder to deploy on Vercel */ vercel?: boolean; + /** Builds a Cloudflare Workers bundle */ + cloudflare?: boolean; }; /** The paths to the tools. */ toolPaths: Set; diff --git a/packages/xmcp/src/compiler/config/__tests__/config.test.ts b/packages/xmcp/src/compiler/config/__tests__/config.test.ts index a62678b45..afe7ce4d7 100644 --- a/packages/xmcp/src/compiler/config/__tests__/config.test.ts +++ b/packages/xmcp/src/compiler/config/__tests__/config.test.ts @@ -51,7 +51,7 @@ describe("Config System - Zod Defaults", () => { assert.equal(resolved.name, "xmcp server"); assert.equal( resolved.description, - "This MCP server was bootstrapped with xmcp. Click the button below to connect to the endpoint." + "This MCP server was bootstrapped with xmcp." ); }); diff --git a/packages/xmcp/src/compiler/config/index.ts b/packages/xmcp/src/compiler/config/index.ts index 241a52746..13041cef7 100644 --- a/packages/xmcp/src/compiler/config/index.ts +++ b/packages/xmcp/src/compiler/config/index.ts @@ -8,7 +8,7 @@ import { typescriptConfigSchema, bundlerConfigSchema, } from "./schemas"; -import { RspackOptions } from "@rspack/core"; +import type { RspackOptions } from "@rspack/core"; /** * xmcp Config schema diff --git a/packages/xmcp/src/compiler/generate-import-code.ts b/packages/xmcp/src/compiler/generate-import-code.ts index 1e3e59bd5..f3abcfeef 100644 --- a/packages/xmcp/src/compiler/generate-import-code.ts +++ b/packages/xmcp/src/compiler/generate-import-code.ts @@ -1,5 +1,13 @@ import { compilerContext } from "./compiler-context"; +/** + * Generate a valid identifier from a file path for use in variable names. + * Converts paths like "src/tools/hello.ts" to "src_tools_hello_ts" + */ +function pathToIdentifier(path: string): string { + return path.replace(/[^a-zA-Z0-9]/g, "_"); +} + export function generateImportCode(): string { const { toolPaths, @@ -7,8 +15,120 @@ export function generateImportCode(): string { resourcePaths, hasMiddleware, clientBundles, + platforms, } = compilerContext.getContext(); + const isCloudflare = platforms?.cloudflare; + + // For Cloudflare, use static imports to avoid code splitting. + // For Node.js, use dynamic imports for lazy loading. + if (isCloudflare) { + return generateStaticImportCode( + toolPaths, + promptPaths, + resourcePaths, + hasMiddleware, + clientBundles + ); + } + + return generateDynamicImportCode( + toolPaths, + promptPaths, + resourcePaths, + hasMiddleware, + clientBundles + ); +} + +/** + * Generate static imports for Cloudflare Workers. + * This ensures all code is bundled into a single file without code splitting. + */ +function generateStaticImportCode( + toolPaths: Set, + promptPaths: Set, + resourcePaths: Set, + hasMiddleware: boolean, + clientBundles?: Map +): string { + // Generate static import statements at the top + const staticImports: string[] = []; + const toolsEntries: string[] = []; + const promptsEntries: string[] = []; + const resourcesEntries: string[] = []; + + Array.from(toolPaths).forEach((p) => { + const path = p.replace(/\\/g, "/"); + const relativePath = `../${path}`; + const identifier = pathToIdentifier(path); + staticImports.push(`import * as ${identifier} from "${relativePath}";`); + toolsEntries.push(`"${path}": () => Promise.resolve(${identifier}),`); + }); + + Array.from(promptPaths).forEach((p) => { + const path = p.replace(/\\/g, "/"); + const relativePath = `../${path}`; + const identifier = pathToIdentifier(path); + staticImports.push(`import * as ${identifier} from "${relativePath}";`); + promptsEntries.push(`"${path}": () => Promise.resolve(${identifier}),`); + }); + + Array.from(resourcePaths).forEach((p) => { + const path = p.replace(/\\/g, "/"); + const relativePath = `../${path}`; + const identifier = pathToIdentifier(path); + staticImports.push(`import * as ${identifier} from "${relativePath}";`); + resourcesEntries.push(`"${path}": () => Promise.resolve(${identifier}),`); + }); + + let middlewareCode = ""; + if (hasMiddleware) { + staticImports.push(`import * as _middleware from "../src/middleware.ts";`); + middlewareCode = `export const middleware = () => Promise.resolve(_middleware);`; + } + + // Generate client bundles mapping (empty object if none) + const clientBundlesEntries = + clientBundles && clientBundles.size > 0 + ? Array.from(clientBundles) + .map(([toolName, bundlePath]) => ` "${toolName}": "${bundlePath}",`) + .join("\n") + : ""; + + return `${staticImports.join("\n")} + +export const tools = { +${toolsEntries.join("\n")} +}; + +export const prompts = { +${promptsEntries.join("\n")} +}; + +export const resources = { +${resourcesEntries.join("\n")} +}; + +export const clientBundles = { +${clientBundlesEntries} +}; + +${middlewareCode} +`; +} + +/** + * Generate dynamic imports for Node.js environments. + * Uses lazy loading for better startup performance. + */ +function generateDynamicImportCode( + toolPaths: Set, + promptPaths: Set, + resourcePaths: Set, + hasMiddleware: boolean, + clientBundles?: Map +): string { const importToolsCode = Array.from(toolPaths) .map((p) => { const path = p.replace(/\\/g, "/"); @@ -34,7 +154,7 @@ export function generateImportCode(): string { .join("\n"); const importMiddlewareCode = hasMiddleware - ? `export const middleware = () => import("../src/middleware");` + ? `export const middleware = () => import("../src/middleware.ts");` : ""; // Generate client bundles mapping (empty object if none) diff --git a/packages/xmcp/src/compiler/get-bundler-config/get-entries.ts b/packages/xmcp/src/compiler/get-bundler-config/get-entries.ts index 3eaaf5ef5..0d5965b7c 100644 --- a/packages/xmcp/src/compiler/get-bundler-config/get-entries.ts +++ b/packages/xmcp/src/compiler/get-bundler-config/get-entries.ts @@ -1,11 +1,21 @@ -import { runtimeFolderPath } from "@/utils/constants"; +import { runtimeFolderPath, resolveXmcpSrcPath } from "@/utils/constants"; import { XmcpConfigOutputSchema } from "@/compiler/config"; import path from "path"; +import { compilerContext } from "@/compiler/compiler-context"; /** Get what packages are gonna be built by xmcp */ export function getEntries( xmcpConfig: XmcpConfigOutputSchema ): Record { + const { platforms } = compilerContext.getContext(); + + if (platforms.cloudflare) { + const xmcpSrcPath = resolveXmcpSrcPath(); + return { + worker: path.join(xmcpSrcPath, "runtime/platforms/cloudflare/worker.ts"), + }; + } + const entries: Record = {}; if (xmcpConfig.stdio) { entries.stdio = path.join(runtimeFolderPath, "stdio.js"); diff --git a/packages/xmcp/src/compiler/get-bundler-config/index.ts b/packages/xmcp/src/compiler/get-bundler-config/index.ts index f760c5c5c..68315e6a2 100644 --- a/packages/xmcp/src/compiler/get-bundler-config/index.ts +++ b/packages/xmcp/src/compiler/get-bundler-config/index.ts @@ -3,32 +3,116 @@ import { ProvidePlugin, DefinePlugin, BannerPlugin, + NormalModuleReplacementPlugin, + IgnorePlugin, + type ResolveAlias, } from "@rspack/core"; import path from "path"; -import { distOutputPath, adapterOutputPath } from "@/utils/constants"; +import { + distOutputPath, + adapterOutputPath, + cloudflareOutputPath, + resolveXmcpSrcPath, +} from "@/utils/constants"; import { compilerContext } from "@/compiler/compiler-context"; import { XmcpConfigOutputSchema } from "@/compiler/config"; import { getEntries } from "./get-entries"; import { getInjectedVariables } from "./get-injected-variables"; import { resolveTsconfigPathsToAlias } from "./resolve-tsconfig-paths"; -import { CreateTypeDefinitionPlugin, InjectRuntimePlugin } from "./plugins"; +import { + CreateTypeDefinitionPlugin, + InjectRuntimePlugin, + readClientBundlesFromDisk, +} from "./plugins"; import { getExternals } from "./get-externals"; import { TsCheckerRspackPlugin } from "ts-checker-rspack-plugin"; +import fs from "fs"; /** Creates the bundler configuration that xmcp will use to bundle the user's code */ export function getRspackConfig( xmcpConfig: XmcpConfigOutputSchema ): RspackOptions { const processFolder = process.cwd(); - const { mode } = compilerContext.getContext(); + const { mode, platforms } = compilerContext.getContext(); + + const isCloudflare = !!platforms.cloudflare; + const projectZodPath = path.join(processFolder, "node_modules", "zod"); + const zodAliases: ResolveAlias = fs.existsSync(projectZodPath) + ? { + zod: projectZodPath, + "zod/v3": path.join(projectZodPath, "v3"), + "zod/v4-mini": path.join(projectZodPath, "v4-mini"), + } + : {}; - const outputPath = xmcpConfig.experimental?.adapter - ? adapterOutputPath - : distOutputPath; + const outputPath = isCloudflare + ? cloudflareOutputPath + : xmcpConfig.experimental?.adapter + ? adapterOutputPath + : distOutputPath; - const outputFilename = xmcpConfig.experimental?.adapter - ? "index.js" - : "[name].js"; + const outputFilename = isCloudflare + ? "worker.js" + : xmcpConfig.experimental?.adapter + ? "index.js" + : "[name].js"; + + const xmcpSrcPath = isCloudflare ? resolveXmcpSrcPath() : undefined; + const nodeBuiltins = [ + "assert", + "buffer", + "child_process", + "cluster", + "console", + "constants", + "crypto", + "dgram", + "dns", + "domain", + "events", + "fs", + "http", + "https", + "module", + "net", + "os", + "path", + "perf_hooks", + "process", + "punycode", + "querystring", + "readline", + "repl", + "stream", + "string_decoder", + "sys", + "timers", + "tls", + "tty", + "url", + "util", + "vm", + "worker_threads", + "zlib", + ]; + const nodeBuiltinAliases = nodeBuiltins.reduce>( + (acc, builtin) => { + acc[`node:${builtin}`] = builtin; + return acc; + }, + {} + ); + const nodeBuiltinFallbacks = nodeBuiltins.reduce>( + (acc, builtin) => { + acc[builtin] = false; + acc[`node:${builtin}`] = false; + return acc; + }, + {} + ); + const nodeBuiltinsRegex = new RegExp( + `^(?:node:)?(${nodeBuiltins.join("|")})$` + ); const config: RspackOptions = { mode, @@ -37,24 +121,44 @@ export function getRspackConfig( output: { filename: outputFilename, path: outputPath, - libraryTarget: "commonjs2", + ...(isCloudflare + ? { + library: { type: "module" }, + chunkFormat: "module", + module: true, + } + : { + libraryTarget: "commonjs2", + }), clean: { - keep: xmcpConfig.experimental?.adapter - ? undefined - : path.join(outputPath, "client"), + keep: + xmcpConfig.experimental?.adapter || isCloudflare + ? undefined + : path.join(outputPath, "client"), }, }, - target: "node", - externals: getExternals(), + target: isCloudflare ? "webworker" : "node", + externals: isCloudflare ? { async_hooks: "async_hooks" } : getExternals(), + experiments: isCloudflare ? { outputModule: true } : undefined, resolve: { fallback: { process: false, + ...(isCloudflare ? nodeBuiltinFallbacks : {}), }, alias: { - "node:process": "process", + ...nodeBuiltinAliases, "xmcp/headers": path.resolve(processFolder, ".xmcp/headers.js"), "xmcp/utils": path.resolve(processFolder, ".xmcp/utils.js"), - "xmcp/plugins/x402": path.resolve(processFolder, ".xmcp/x402.js"), + "xmcp/plugins/x402": + isCloudflare && xmcpSrcPath + ? path.join(xmcpSrcPath, "plugins/x402/index.ts") + : path.resolve(processFolder, ".xmcp/x402.js"), + ...(isCloudflare && xmcpSrcPath + ? { + "@": xmcpSrcPath, + } + : {}), + ...zodAliases, ...resolveTsconfigPathsToAlias(), }, extensions: [".tsx", ".ts", ".jsx", ".js", ".json"], @@ -67,6 +171,12 @@ export function getRspackConfig( ], }, plugins: [ + isCloudflare ? new IgnorePlugin({ resourceRegExp: nodeBuiltinsRegex }) : null, + isCloudflare + ? new NormalModuleReplacementPlugin(/^node:/, (resource) => { + resource.request = resource.request.replace(/^node:/, ""); + }) + : null, new InjectRuntimePlugin(), new CreateTypeDefinitionPlugin(), xmcpConfig.typescript?.skipTypeCheck ? null : new TsCheckerRspackPlugin(), @@ -103,6 +213,7 @@ export function getRspackConfig( minimize: mode === "production", mergeDuplicateChunks: true, splitChunks: false, + ...(isCloudflare ? { runtimeChunk: false } : {}), }, }; @@ -140,8 +251,26 @@ export function getRspackConfig( config.plugins!.push(new ProvidePlugin(providedPackages)); // add defined variables to config - const definedVariables = getInjectedVariables(xmcpConfig); - config.plugins!.push(new DefinePlugin(definedVariables)); + const definedVariables: Record = + getInjectedVariables(xmcpConfig); + definedVariables["IS_CLOUDFLARE"] = JSON.stringify(isCloudflare); + + if (isCloudflare) { + const clientBundles = readClientBundlesFromDisk(); + definedVariables["INJECTED_CLIENT_BUNDLES"] = JSON.stringify(clientBundles); + } else { + definedVariables["INJECTED_CLIENT_BUNDLES"] = "undefined"; + } + + // Filter out undefined values for DefinePlugin (requires Record) + const filteredVariables: Record = {}; + for (const [key, value] of Object.entries(definedVariables)) { + if (value !== undefined) { + filteredVariables[key] = value; + } + } + + config.plugins!.push(new DefinePlugin(filteredVariables)); // add shebang to CLI output on stdio mode if (xmcpConfig.stdio) { diff --git a/packages/xmcp/src/compiler/get-bundler-config/plugins.ts b/packages/xmcp/src/compiler/get-bundler-config/plugins.ts deleted file mode 100644 index 717cf1ec1..000000000 --- a/packages/xmcp/src/compiler/get-bundler-config/plugins.ts +++ /dev/null @@ -1,218 +0,0 @@ -import { adapterOutputPath, runtimeFolderPath } from "@/utils/constants"; -import fs from "fs-extra"; -import path from "path"; -import { Compiler } from "@rspack/core"; -import { getXmcpConfig } from "../compiler-context"; -import { XmcpConfigOutputSchema } from "@/compiler/config"; - -// @ts-expect-error: injected by compiler -export const runtimeFiles = RUNTIME_FILES as Record; - -/** - * Determines which runtime files are needed based on user configuration. - */ -function getNeededRuntimeFiles(xmcpConfig: XmcpConfigOutputSchema): string[] { - const neededFiles: string[] = []; - - // headers included if http is configured - if (xmcpConfig.http) { - neededFiles.push("headers.js"); - } - - if (xmcpConfig.stdio) { - neededFiles.push("stdio.js"); - } - - if (xmcpConfig.http) { - if (xmcpConfig.experimental?.adapter === "express") { - neededFiles.push("adapter-express.js"); - } else if (xmcpConfig.experimental?.adapter === "nextjs") { - neededFiles.push("adapter-nextjs.js"); - } else if (xmcpConfig.experimental?.adapter === "nestjs") { - neededFiles.push("adapter-nestjs.js"); - } else { - neededFiles.push("http.js"); - } - } - - return neededFiles; -} - -export class InjectRuntimePlugin { - apply(compiler: Compiler) { - let hasRun = false; - compiler.hooks.beforeCompile.tap( - "InjectRuntimePlugin", - (_compilationParams) => { - if (hasRun) return; - hasRun = true; - - const xmcpConfig = getXmcpConfig(); - const neededFiles = getNeededRuntimeFiles(xmcpConfig); - - for (const [fileName, fileContent] of Object.entries(runtimeFiles)) { - if (neededFiles.includes(fileName)) { - fs.writeFileSync( - path.join(runtimeFolderPath, fileName), - fileContent - ); - } - } - } - ); - } -} - -const nextJsTypeDefinition = ` -export const xmcpHandler: (req: Request) => Promise; -export const withAuth: (handler: (req: Request) => Promise, authConfig: AuthConfig) => (req: Request) => Promise; -export const resourceMetadataHandler: ({authorizationServers}: {authorizationServers: string[]}) => (req: Request) => Response; -export const resourceMetadataOptions: (req: Request) => Response; -export const tools: () => Promise; -export const toolRegistry: () => Promise>; -export type VerifyToken = (req: Request, bearerToken?: string) => Promise; -export type AuthConfig = { - verifyToken: VerifyToken; - required?: boolean; - requiredScopes?: string[]; -}; -export type AuthInfo = { - token: string; - clientId: string; - scopes: string[]; - expiresAt?: number; - resource?: URL; - extra?: Record; -}; -export type OAuthProtectedResourceMetadata = { - resource: string; - authorization_servers: string[]; - bearer_methods_supported?: string[]; - resource_documentation?: string; - introspection_endpoint?: string; - revocation_endpoint?: string; - [key: string]: unknown; -}; -export type Tool = { - path: string; - name: string; - metadata: { - name: string; - description: string; - annotations?: { - title?: string; - [key: string]: any; - }; - [key: string]: any; - }; - schema: Record; - handler: (args: any) => Promise; -}; -export type ToolRegistryEntry = { - description: string; - inputSchema: any; // Zod schema object - execute: (args: any) => Promise; -}; - -`; - -const expressTypeDefinition = ` -export const xmcpHandler: (req: Request, res: Response) => Promise; -`; - -const nestJsTypeDefinition = ` -import { Request, Response } from "express"; -import { Type, CanActivate, DynamicModule } from "@nestjs/common"; - -// Auth types -export interface AuthInfo { - token: string; - clientId: string; - scopes: string[]; - expiresAt?: number; - extra?: Record; -} - -export interface McpAuthConfig { - verifyToken: (token: string) => Promise> | Omit; - required?: boolean; - requiredScopes?: string[]; -} - -export declare function createMcpAuthGuard(config: McpAuthConfig): Type; - -// OAuth types -export interface OAuthConfig { - authorizationServers: string[]; - scopesSupported?: string[]; - bearerMethodsSupported?: string[]; -} - -export interface OAuthProtectedResourceMetadata { - resource: string; - authorizationServers: string[]; - scopesSupported?: string[]; - bearerMethodsSupported?: string[]; -} - -export declare class OAuthModule { - static forRoot(config: OAuthConfig): DynamicModule; -} - -export declare class OAuthService { - getResourceMetadata(req: Request): OAuthProtectedResourceMetadata; -} - -export declare class OAuthController { - constructor(oauthService: OAuthService); - getResourceMetadata(req: Request): OAuthProtectedResourceMetadata; - handleOptions(res: Response): void; -} - -// Utility functions -export declare function buildBaseUrl(req: Request): string; -export declare function buildResourceMetadataUrl(req: Request): string; - -// MCP Service & Controller -export declare class XmcpService { - handleRequest(req: Request & { auth?: AuthInfo }, res: Response): Promise; -} - -export declare class XmcpController { - constructor(xmcpService: XmcpService); - handleMcp(req: Request, res: Response): Promise; - handleGet(res: Response): void; - handleOptions(res: Response): void; -} -`; - -export class CreateTypeDefinitionPlugin { - apply(compiler: Compiler) { - let hasRun = false; - compiler.hooks.afterEmit.tap( - "CreateTypeDefinitionPlugin", - (_compilationParams) => { - if (hasRun) return; - hasRun = true; - - const xmcpConfig = getXmcpConfig(); - - // Manually type the .xmcp/adapter/index.js file using a .xmcp/adapter/index.d.ts file - if (xmcpConfig.experimental?.adapter) { - let typeDefinitionContent = ""; - if (xmcpConfig.experimental?.adapter == "nextjs") { - typeDefinitionContent = nextJsTypeDefinition; - } else if (xmcpConfig.experimental?.adapter == "express") { - typeDefinitionContent = expressTypeDefinition; - } else if (xmcpConfig.experimental?.adapter == "nestjs") { - typeDefinitionContent = nestJsTypeDefinition; - } - fs.writeFileSync( - path.join(adapterOutputPath, "index.d.ts"), - typeDefinitionContent - ); - } - } - ); - } -} diff --git a/packages/xmcp/src/compiler/get-bundler-config/plugins/index.ts b/packages/xmcp/src/compiler/get-bundler-config/plugins/index.ts new file mode 100644 index 000000000..c2b76a837 --- /dev/null +++ b/packages/xmcp/src/compiler/get-bundler-config/plugins/index.ts @@ -0,0 +1,136 @@ +import { adapterOutputPath, runtimeFolderPath } from "@/utils/constants"; +import fs from "fs-extra"; +import path from "path"; +import { Compiler } from "@rspack/core"; +import { XmcpConfigOutputSchema } from "@/compiler/config"; +import { getXmcpConfig } from "@/compiler/compiler-context"; +import { + expressTypeDefinition, + nestJsTypeDefinition, + nextJsTypeDefinition, +} from "./types"; + +/** + * Read all client bundles from disk for Cloudflare injection. + * Returns a record mapping bundle names to their JS and CSS contents. + */ +export function readClientBundlesFromDisk(): Record< + string, + { js: string; css?: string } +> { + const bundles: Record = {}; + const clientDir = path.join(process.cwd(), "dist", "client"); + + if (!fs.existsSync(clientDir)) { + return bundles; + } + + const files = fs.readdirSync(clientDir); + const jsFiles = files.filter((f) => f.endsWith(".bundle.js")); + + for (const jsFile of jsFiles) { + const bundleName = jsFile.replace(".bundle.js", ""); + const jsPath = path.join(clientDir, jsFile); + const cssPath = path.join(clientDir, `${bundleName}.bundle.css`); + + const js = fs.readFileSync(jsPath, "utf-8"); + let css: string | undefined; + + if (fs.existsSync(cssPath)) { + css = fs.readFileSync(cssPath, "utf-8"); + } + + bundles[bundleName] = { js, css }; + } + + return bundles; +} + +// @ts-expect-error: injected by compiler +export const runtimeFiles = RUNTIME_FILES as Record; + +/** + * Determines which runtime files are needed based on user configuration. + */ +function getNeededRuntimeFiles(xmcpConfig: XmcpConfigOutputSchema): string[] { + const neededFiles: string[] = []; + + // headers included if http is configured + if (xmcpConfig.http) { + neededFiles.push("headers.js"); + } + + if (xmcpConfig.stdio) { + neededFiles.push("stdio.js"); + } + + if (xmcpConfig.http) { + if (xmcpConfig.experimental?.adapter === "express") { + neededFiles.push("adapter-express.js"); + } else if (xmcpConfig.experimental?.adapter === "nextjs") { + neededFiles.push("adapter-nextjs.js"); + } else if (xmcpConfig.experimental?.adapter === "nestjs") { + neededFiles.push("adapter-nestjs.js"); + } else { + neededFiles.push("http.js"); + } + } + + return neededFiles; +} + +export class InjectRuntimePlugin { + apply(compiler: Compiler) { + let hasRun = false; + compiler.hooks.beforeCompile.tap( + "InjectRuntimePlugin", + (_compilationParams) => { + if (hasRun) return; + hasRun = true; + + const xmcpConfig = getXmcpConfig(); + const neededFiles = getNeededRuntimeFiles(xmcpConfig); + + for (const [fileName, fileContent] of Object.entries(runtimeFiles)) { + if (neededFiles.includes(fileName)) { + fs.writeFileSync( + path.join(runtimeFolderPath, fileName), + fileContent + ); + } + } + } + ); + } +} + +export class CreateTypeDefinitionPlugin { + apply(compiler: Compiler) { + let hasRun = false; + compiler.hooks.afterEmit.tap( + "CreateTypeDefinitionPlugin", + (_compilationParams) => { + if (hasRun) return; + hasRun = true; + + const xmcpConfig = getXmcpConfig(); + + // Manually type the .xmcp/adapter/index.js file using a .xmcp/adapter/index.d.ts file + if (xmcpConfig.experimental?.adapter) { + let typeDefinitionContent = ""; + if (xmcpConfig.experimental?.adapter == "nextjs") { + typeDefinitionContent = nextJsTypeDefinition; + } else if (xmcpConfig.experimental?.adapter == "express") { + typeDefinitionContent = expressTypeDefinition; + } else if (xmcpConfig.experimental?.adapter == "nestjs") { + typeDefinitionContent = nestJsTypeDefinition; + } + fs.writeFileSync( + path.join(adapterOutputPath, "index.d.ts"), + typeDefinitionContent + ); + } + } + ); + } +} diff --git a/packages/xmcp/src/compiler/get-bundler-config/plugins/types/express.ts b/packages/xmcp/src/compiler/get-bundler-config/plugins/types/express.ts new file mode 100644 index 000000000..f871ca49c --- /dev/null +++ b/packages/xmcp/src/compiler/get-bundler-config/plugins/types/express.ts @@ -0,0 +1,3 @@ +export const expressTypeDefinition = ` +export const xmcpHandler: (req: Request, res: Response) => Promise; +`; diff --git a/packages/xmcp/src/compiler/get-bundler-config/plugins/types/index.ts b/packages/xmcp/src/compiler/get-bundler-config/plugins/types/index.ts new file mode 100644 index 000000000..faace54ea --- /dev/null +++ b/packages/xmcp/src/compiler/get-bundler-config/plugins/types/index.ts @@ -0,0 +1,4 @@ +//re exports +export { nextJsTypeDefinition } from "./next"; +export { expressTypeDefinition } from "./express"; +export { nestJsTypeDefinition } from "./nest"; diff --git a/packages/xmcp/src/compiler/get-bundler-config/plugins/types/nest.ts b/packages/xmcp/src/compiler/get-bundler-config/plugins/types/nest.ts new file mode 100644 index 000000000..c6acd305a --- /dev/null +++ b/packages/xmcp/src/compiler/get-bundler-config/plugins/types/nest.ts @@ -0,0 +1,65 @@ +export const nestJsTypeDefinition = ` +import { Request, Response } from "express"; +import { Type, CanActivate, DynamicModule } from "@nestjs/common"; + +// Auth types +export interface AuthInfo { + token: string; + clientId: string; + scopes: string[]; + expiresAt?: number; + extra?: Record; +} + +export interface McpAuthConfig { + verifyToken: (token: string) => Promise> | Omit; + required?: boolean; + requiredScopes?: string[]; +} + +export declare function createMcpAuthGuard(config: McpAuthConfig): Type; + +// OAuth types +export interface OAuthConfig { + authorizationServers: string[]; + scopesSupported?: string[]; + bearerMethodsSupported?: string[]; +} + +export interface OAuthProtectedResourceMetadata { + resource: string; + authorizationServers: string[]; + scopesSupported?: string[]; + bearerMethodsSupported?: string[]; +} + +export declare class OAuthModule { + static forRoot(config: OAuthConfig): DynamicModule; +} + +export declare class OAuthService { + getResourceMetadata(req: Request): OAuthProtectedResourceMetadata; +} + +export declare class OAuthController { + constructor(oauthService: OAuthService); + getResourceMetadata(req: Request): OAuthProtectedResourceMetadata; + handleOptions(res: Response): void; +} + +// Utility functions +export declare function buildBaseUrl(req: Request): string; +export declare function buildResourceMetadataUrl(req: Request): string; + +// MCP Service & Controller +export declare class XmcpService { + handleRequest(req: Request & { auth?: AuthInfo }, res: Response): Promise; +} + +export declare class XmcpController { + constructor(xmcpService: XmcpService); + handleMcp(req: Request, res: Response): Promise; + handleGet(res: Response): void; + handleOptions(res: Response): void; +} +`; diff --git a/packages/xmcp/src/compiler/get-bundler-config/plugins/types/next.ts b/packages/xmcp/src/compiler/get-bundler-config/plugins/types/next.ts new file mode 100644 index 000000000..abeaf531b --- /dev/null +++ b/packages/xmcp/src/compiler/get-bundler-config/plugins/types/next.ts @@ -0,0 +1,52 @@ +export const nextJsTypeDefinition = ` +export const xmcpHandler: (req: Request) => Promise; +export const withAuth: (handler: (req: Request) => Promise, authConfig: AuthConfig) => (req: Request) => Promise; +export const resourceMetadataHandler: ({authorizationServers}: {authorizationServers: string[]}) => (req: Request) => Response; +export const resourceMetadataOptions: (req: Request) => Response; +export const tools: () => Promise; +export const toolRegistry: () => Promise>; +export type VerifyToken = (req: Request, bearerToken?: string) => Promise; +export type AuthConfig = { + verifyToken: VerifyToken; + required?: boolean; + requiredScopes?: string[]; +}; +export type AuthInfo = { + token: string; + clientId: string; + scopes: string[]; + expiresAt?: number; + resource?: URL; + extra?: Record; +}; +export type OAuthProtectedResourceMetadata = { + resource: string; + authorization_servers: string[]; + bearer_methods_supported?: string[]; + resource_documentation?: string; + introspection_endpoint?: string; + revocation_endpoint?: string; + [key: string]: unknown; +}; +export type Tool = { + path: string; + name: string; + metadata: { + name: string; + description: string; + annotations?: { + title?: string; + [key: string]: any; + }; + [key: string]: any; + }; + schema: Record; + handler: (args: any) => Promise; +}; +export type ToolRegistryEntry = { + description: string; + inputSchema: any; // Zod schema object + execute: (args: any) => Promise; +}; + +`; diff --git a/packages/xmcp/src/compiler/index.ts b/packages/xmcp/src/compiler/index.ts index 1788a314d..c80abd5fa 100644 --- a/packages/xmcp/src/compiler/index.ts +++ b/packages/xmcp/src/compiler/index.ts @@ -33,6 +33,7 @@ import { isValidPath } from "@/utils/path-validation"; import { getResolvedPathsConfig } from "./config/utils"; import { pathToToolName } from "./utils/path-utils"; import { transpileClientComponent } from "./client/transpile"; +import { buildCloudflareOutput } from "../platforms/build-cloudflare-output"; const { version: XMCP_VERSION } = require("../../package.json"); dotenv.config(); @@ -43,7 +44,7 @@ export interface CompileOptions { } export async function compile({ onBuild }: CompileOptions = {}) { - const { mode, toolPaths, promptPaths, resourcePaths } = + const { mode, toolPaths, promptPaths, resourcePaths, platforms } = compilerContext.getContext(); const startTime = Date.now(); let compilerStarted = false; @@ -177,7 +178,7 @@ export async function compile({ onBuild }: CompileOptions = {}) { // Generate all code (including client bundles) BEFORE bundler runs await generateCode(); - rspack(bundlerConfig, (err, stats) => { + rspack(bundlerConfig, async (err, stats) => { // Track compilation time let compilationTime: number; if (stats?.endTime && stats?.startTime) { @@ -280,6 +281,17 @@ export async function compile({ onBuild }: CompileOptions = {}) { } } + if (mode === "development" && platforms.cloudflare) { + try { + await buildCloudflareOutput({ log: firstBuild }); + } catch (error) { + console.error( + chalk.red("❌ Failed to sync Cloudflare Workers output:"), + error + ); + } + } + if (mode === "production") { logBuildSuccess({ ...collectBaseTelemetryData(), diff --git a/packages/xmcp/src/compiler/parse-xmcp-config.ts b/packages/xmcp/src/compiler/parse-xmcp-config.ts index d3040ff48..de390f933 100644 --- a/packages/xmcp/src/compiler/parse-xmcp-config.ts +++ b/packages/xmcp/src/compiler/parse-xmcp-config.ts @@ -33,6 +33,14 @@ export async function getConfig(): Promise { // Remove stdio to deploy on vercel delete config.stdio; } + if (platforms.cloudflare) { + // Remove stdio for Cloudflare Workers + delete config.stdio; + // Ensure HTTP is enabled for Workers builds + if (!config.http) { + config.http = true; + } + } return config; } diff --git a/packages/xmcp/src/compiler/utils/path-utils.ts b/packages/xmcp/src/compiler/utils/path-utils.ts index a68d70bdc..ebdc998b5 100644 --- a/packages/xmcp/src/compiler/utils/path-utils.ts +++ b/packages/xmcp/src/compiler/utils/path-utils.ts @@ -1,20 +1,22 @@ -import assert from "node:assert"; -import { sep, normalize } from "node:path"; -import { createHash } from "node:crypto"; +import { compilerContext } from "../compiler-context"; +import { + pathToToolNameMd5, + pathToToolNameDjb2, +} from "@/runtime/utils/path-to-tool-name"; +/** + * Get the appropriate pathToToolName function based on target platform. + * - For Cloudflare: Use djb2 hash + * - For Node.js (express, nextjs, etc.): Use MD5 hash + */ export function pathToToolName(path: string): string { - const normalizedPath = normalize(path).split(sep).join("/"); - - assert(normalizedPath !== "", `Invalid tool path: path is empty`); - - const withoutExtension = normalizedPath.replace(/\.[^/.]+$/, ""); - - const baseName = withoutExtension.replace(/\//g, "_"); - - const hash = createHash("md5") - .update(normalizedPath) - .digest("hex") - .slice(0, 6); - - return `${baseName}_${hash}`; + try { + const { platforms } = compilerContext.getContext(); + if (platforms?.cloudflare) { + return pathToToolNameDjb2(path); + } + } catch { + // Context not available, use default (MD5) + } + return pathToToolNameMd5(path); } diff --git a/packages/xmcp/src/index.ts b/packages/xmcp/src/index.ts index e5bf5c5f8..4a6b6f983 100644 --- a/packages/xmcp/src/index.ts +++ b/packages/xmcp/src/index.ts @@ -1,5 +1,5 @@ import dotenv from "dotenv"; -export { type Middleware } from "./types/middleware"; +export type { Middleware, WebMiddleware, WebMiddlewareContext } from "./types/middleware"; dotenv.config(); export type { diff --git a/packages/xmcp/src/platforms/build-cloudflare-output.ts b/packages/xmcp/src/platforms/build-cloudflare-output.ts new file mode 100644 index 000000000..00ec8302a --- /dev/null +++ b/packages/xmcp/src/platforms/build-cloudflare-output.ts @@ -0,0 +1,116 @@ +import path from "path"; +import fs from "fs"; + +const rootDir = process.cwd(); + +interface BuildCloudflareOutputOptions { + log?: boolean; +} + +/** + * Build the Cloudflare Workers output structure. + * Writes Cloudflare Worker files to the project root: + * - worker.js + * - wrangler.jsonc (only if not already present) + */ +async function buildCloudflareOutput( + options: BuildCloudflareOutputOptions = {} +) { + const { log = true } = options; + const outputDir = rootDir; + const buildDir = path.join(rootDir, ".xmcp", "cloudflare"); + + // Check if Cloudflare worker build exists + const sourceFile = path.join(buildDir, "worker.js"); + if (!fs.existsSync(sourceFile)) { + throw new Error("Cloudflare build output not found. Run: xmcp build --cf"); + } + + // Copy the bundled worker + const targetFile = path.join(outputDir, "worker.js"); + fs.copyFileSync(sourceFile, targetFile); + + // Generate wrangler config (only if the user doesn't have one already) + const wranglerTomlPath = path.join(outputDir, "wrangler.toml"); + const wranglerJsoncPath = path.join(outputDir, "wrangler.jsonc"); + if (!fs.existsSync(wranglerTomlPath) && !fs.existsSync(wranglerJsoncPath)) { + const projectName = getProjectName(); + const wranglerConfig = generateWranglerConfig(projectName); + fs.writeFileSync(wranglerJsoncPath, wranglerConfig); + } + + if (log) { + console.log("Cloudflare Workers output structure created successfully"); + console.log(""); + console.log("Next steps:"); + console.log(" 1. npx wrangler dev # Test locally"); + console.log(" 2. npx wrangler deploy # Deploy to Cloudflare"); + } +} + +/** + * Get the project name from package.json or directory name. + */ +function getProjectName(): string { + try { + const packageJsonPath = path.join(rootDir, "package.json"); + if (fs.existsSync(packageJsonPath)) { + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")); + if (packageJson.name) { + // Sanitize the name for Cloudflare Workers + return packageJson.name + .replace(/^@[^/]+\//, "") // Remove scope + .replace(/[^a-zA-Z0-9-]/g, "-") // Replace invalid chars + .toLowerCase(); + } + } + } catch { + // Fallback to directory name + } + + return path + .basename(rootDir) + .replace(/[^a-zA-Z0-9-]/g, "-") + .toLowerCase(); +} + +/** + * Generate a wrangler.jsonc configuration file. + */ +function generateWranglerConfig(projectName: string): string { + const compatibilityDate = new Date().toISOString().split("T")[0]; + + // JSONC format: valid JSON with comments and trailing commas allowed + return `{ + "$schema": "node_modules/wrangler/config-schema.json", + // Wrangler config generated by: xmcp build --cf + // 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 + } + + // Uncomment to add environment variables: + // "vars": { + // "MY_VAR": "my-value" + // }, + + // Uncomment to add KV namespaces: + // "kv_namespaces": [ + // { "binding": "MY_KV", "id": "your-kv-namespace-id" } + // ], + + // Uncomment to add D1 databases: + // "d1_databases": [ + // { "binding": "MY_DB", "database_name": "my-database", "database_id": "your-database-id" } + // ] +} +`; +} + +export { buildCloudflareOutput }; diff --git a/packages/xmcp/src/runtime/contexts/http-request-context.ts b/packages/xmcp/src/runtime/contexts/http-request-context.ts index bc481e3d6..83fe88d1b 100644 --- a/packages/xmcp/src/runtime/contexts/http-request-context.ts +++ b/packages/xmcp/src/runtime/contexts/http-request-context.ts @@ -1,9 +1,11 @@ -import { IncomingHttpHeaders } from "http"; import { createContext } from "../../utils/context"; +// Headers type compatible with both Node.js IncomingHttpHeaders and Web API headers +export type HttpHeaders = Record; + export interface HttpRequestContext { id: string; - headers: IncomingHttpHeaders; + headers: HttpHeaders; } export const httpRequestContext = createContext({ diff --git a/packages/xmcp/src/runtime/platforms/cloudflare/cors.ts b/packages/xmcp/src/runtime/platforms/cloudflare/cors.ts new file mode 100644 index 000000000..66b621e0d --- /dev/null +++ b/packages/xmcp/src/runtime/platforms/cloudflare/cors.ts @@ -0,0 +1,45 @@ +/** + * CORS handling for Cloudflare Workers adapter. + */ + +import type { CorsConfig } from "@/compiler/config"; +import { buildCorsHeaders } from "@/runtime/transports/http/cors/headers"; + +// CORS config - injected by compiler as combined object +// @ts-expect-error: injected by compiler +const corsConfig = HTTP_CORS_CONFIG as CorsConfig; + +/** + * Add CORS headers to a Response + */ +export function addCorsHeaders( + response: Response, + requestOrigin: string | null +): Response { + const headers = new Headers(response.headers); + + const corsHeaders = buildCorsHeaders(corsConfig, requestOrigin); + for (const [key, value] of Object.entries(corsHeaders)) { + headers.set(key, value); + } + + return new Response(response.body, { + status: response.status, + statusText: response.statusText, + headers, + }); +} + +/** + * Handle CORS preflight requests + */ +export function handleCorsPreflightRequest( + requestOrigin: string | null +): Response { + const headers = new Headers(buildCorsHeaders(corsConfig, requestOrigin)); + + return new Response(null, { + status: 204, + headers, + }); +} diff --git a/packages/xmcp/src/runtime/platforms/cloudflare/middlewares/api-key.ts b/packages/xmcp/src/runtime/platforms/cloudflare/middlewares/api-key.ts new file mode 100644 index 000000000..17334774d --- /dev/null +++ b/packages/xmcp/src/runtime/platforms/cloudflare/middlewares/api-key.ts @@ -0,0 +1,102 @@ +import { z } from "zod/v3"; +import type { WebMiddleware } from "@/types/middleware"; + +const apiKeyAuthMiddlewareConfigSchema = z + .object({ + apiKey: z.string().optional(), + headerName: z.string().optional(), + validateApiKey: z + .function() + .args(z.string()) + .returns(z.promise(z.boolean())) + .optional(), + }) + .strict() + .refine( + (config) => + config.apiKey !== undefined || config.validateApiKey !== undefined, + { + message: "Either 'apiKey' or 'validateApiKey' must be provided", + } + ) + .refine( + (config) => + !(config.apiKey !== undefined && config.validateApiKey !== undefined), + { + message: + "'apiKey' and 'validateApiKey' are mutually exclusive - provide only one", + } + ); + +const errorMessage = "Unauthorized: Missing or invalid API key"; + +type StaticApiKeyConfig = { + /** The static API key to validate against */ + apiKey: string; + /** Optional header name to read the API key from. Defaults to 'x-api-key' */ + headerName?: string; +}; + +type CustomValidationConfig = { + /** Optional header name to read the API key from. Defaults to 'x-api-key' */ + headerName?: string; + /** Custom validation function that receives the API key and returns a Promise */ + validateApiKey: (key: string) => Promise; +}; + +export function cloudflareApiKeyAuthMiddleware( + config: StaticApiKeyConfig +): WebMiddleware; + +export function cloudflareApiKeyAuthMiddleware( + config: CustomValidationConfig +): WebMiddleware; + +export function cloudflareApiKeyAuthMiddleware( + config: StaticApiKeyConfig | CustomValidationConfig +): WebMiddleware { + const response = apiKeyAuthMiddlewareConfigSchema.safeParse(config); + if (!response.success) { + const hasApiKey = "apiKey" in config; + const hasValidateApiKey = "validateApiKey" in config; + + if (hasApiKey && hasValidateApiKey) { + throw new Error( + "'apiKey' and 'validateApiKey' are mutually exclusive - provide only one" + ); + } else if (!hasApiKey && !hasValidateApiKey) { + throw new Error("Either 'apiKey' or 'validateApiKey' must be provided"); + } else { + throw new Error(`Invalid configuration: ${response.error.message}`); + } + } + + const headerName = config.headerName ?? "x-api-key"; + const apiKey = "apiKey" in config ? config.apiKey : undefined; + const validateApiKey = + "validateApiKey" in config ? config.validateApiKey : undefined; + + return async (request) => { + const apiKeyHeader = request.headers.get(headerName); + if (!apiKeyHeader) { + return jsonUnauthorized(errorMessage); + } + if ("apiKey" in config && apiKeyHeader !== apiKey) { + return jsonUnauthorized(errorMessage); + } + if (validateApiKey) { + const isValid = await validateApiKey(apiKeyHeader); + if (!isValid) { + return jsonUnauthorized(errorMessage); + } + } + return; + }; +} + +function jsonUnauthorized(message: string): Response { + return new Response(JSON.stringify({ error: message }), { + status: 401, + headers: { "Content-Type": "application/json" }, + }); +} diff --git a/packages/xmcp/src/runtime/platforms/cloudflare/middlewares/jwt.ts b/packages/xmcp/src/runtime/platforms/cloudflare/middlewares/jwt.ts new file mode 100644 index 000000000..6461fedd5 --- /dev/null +++ b/packages/xmcp/src/runtime/platforms/cloudflare/middlewares/jwt.ts @@ -0,0 +1,127 @@ +import type { JWTPayload, JWTVerifyOptions } from "jose"; +import { decodeProtectedHeader, importJWK, importSPKI, importX509, jwtVerify } from "jose"; +import type { WebMiddleware } from "@/types/middleware"; + +export type CloudflareJWTAuthMiddlewareConfig = { + secret: string; + algorithms?: string[]; + audience?: string | string[]; + issuer?: string; + subject?: string; + clockTolerance?: number | string; + maxAge?: string | number; +}; + +export function cloudflareJwtAuthMiddleware( + config: CloudflareJWTAuthMiddlewareConfig +): WebMiddleware { + return async (request) => { + const authHeader = request.headers.get("authorization"); + const token = extractBearerToken(authHeader); + if (!token) { + return jsonUnauthorized( + "Unauthorized: Missing or malformed Authorization header" + ); + } + + try { + await verifyJwt(token, config); + return; + } catch { + return jsonUnauthorized("Unauthorized: Invalid or expired token"); + } + }; +} + +function extractBearerToken(authHeader: string | null | undefined): string | null { + if (!authHeader) return null; + const [scheme, token] = authHeader.split(" "); + if (!scheme || !token || scheme.toLowerCase() !== "bearer") { + return null; + } + return token.trim() || null; +} + +function jsonUnauthorized(message: string): Response { + return new Response(JSON.stringify({ error: message }), { + status: 401, + headers: { "Content-Type": "application/json" }, + }); +} + +async function verifyJwt( + token: string, + config: CloudflareJWTAuthMiddlewareConfig +): Promise { + const { secret, ...verifyOptions } = config; + const header = decodeProtectedHeader(token); + const alg = + verifyOptions.algorithms?.[0] ?? + (typeof header.alg === "string" ? header.alg : undefined); + + if (!alg) { + throw new Error("Missing JWT algorithm"); + } + + const key = await resolveJoseKey(secret, alg); + const joseOptions = mapJoseVerifyOptions(verifyOptions); + const { payload } = await jwtVerify(token, key as any, joseOptions); + return payload; +} + +async function resolveJoseKey( + secret: string, + alg: string +): Promise { + const trimmed = secret.trim(); + + if (trimmed.startsWith("{")) { + try { + const jwk = JSON.parse(trimmed); + return await importJWK(jwk, alg); + } catch { + return new TextEncoder().encode(secret); + } + } + + if (trimmed.includes("BEGIN CERTIFICATE")) { + return await importX509(trimmed, alg); + } + + if (trimmed.includes("BEGIN PUBLIC KEY")) { + return await importSPKI(trimmed, alg); + } + + return new TextEncoder().encode(secret); +} + +function mapJoseVerifyOptions( + verifyOptions: Omit +): JWTVerifyOptions { + const joseOptions: JWTVerifyOptions = {}; + + if (verifyOptions.algorithms) { + joseOptions.algorithms = verifyOptions.algorithms; + } + if (verifyOptions.audience) { + if (typeof verifyOptions.audience === "string") { + joseOptions.audience = verifyOptions.audience; + } else if (Array.isArray(verifyOptions.audience)) { + joseOptions.audience = verifyOptions.audience; + } + } + if (verifyOptions.issuer) { + joseOptions.issuer = verifyOptions.issuer; + } + if (verifyOptions.subject) { + joseOptions.subject = verifyOptions.subject; + } + if (verifyOptions.clockTolerance !== undefined) { + joseOptions.clockTolerance = verifyOptions.clockTolerance; + } + if (verifyOptions.maxAge !== undefined) { + joseOptions.maxTokenAge = verifyOptions.maxAge; + } + + return joseOptions; +} diff --git a/packages/xmcp/src/runtime/platforms/cloudflare/types.ts b/packages/xmcp/src/runtime/platforms/cloudflare/types.ts new file mode 100644 index 000000000..a3c598df6 --- /dev/null +++ b/packages/xmcp/src/runtime/platforms/cloudflare/types.ts @@ -0,0 +1,23 @@ +/** + * Shared types for Cloudflare Workers adapter. + * Placed here to avoid circular dependencies. + */ + +/** + * Cloudflare Workers environment bindings. + * Users can extend this with their own bindings. + */ +export interface Env { + /** + * Additional user-defined bindings (KV, D1, etc.) + */ + [key: string]: unknown; +} + +/** + * Cloudflare Workers ExecutionContext type + */ +export interface ExecutionContext { + waitUntil(promise: Promise): void; + passThroughOnException(): void; +} diff --git a/packages/xmcp/src/runtime/platforms/cloudflare/worker.ts b/packages/xmcp/src/runtime/platforms/cloudflare/worker.ts new file mode 100644 index 000000000..520a24e9d --- /dev/null +++ b/packages/xmcp/src/runtime/platforms/cloudflare/worker.ts @@ -0,0 +1,268 @@ +import { createServer } from "@/runtime/utils/server"; +import { WebStatelessHttpTransport } from "@/runtime/transports/http/web-stateless-http"; +import { httpRequestContextProvider } from "@/runtime/contexts/http-request-context"; +import homeTemplate from "../../templates/home"; + +import { addCorsHeaders, handleCorsPreflightRequest } from "./cors"; +import type { Env, ExecutionContext } from "./types"; +import type { WebMiddleware, WebMiddlewareContext } from "@/types/middleware"; +import type { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types"; + +// @ts-expect-error: injected by compiler +const httpConfig = HTTP_CONFIG as { + port: number; + host: string; + bodySizeLimit: string; + endpoint: string; + debug: boolean; +}; + +// @ts-expect-error: injected by compiler +const templateConfig = TEMPLATE_CONFIG as { + name?: string; + description?: string; + homePage?: string; +}; + +// @ts-expect-error: injected by compiler +const middleware = INJECTED_MIDDLEWARE as + | (() => Promise<{ default?: WebMiddleware | WebMiddleware[] }>) + | undefined; + +let resolvedMiddlewarePromise: Promise | null = null; + +async function resolveWebMiddleware(): Promise { + if (!middleware) { + return []; + } + + if (!resolvedMiddlewarePromise) { + resolvedMiddlewarePromise = (async () => { + try { + const module = await middleware(); + return normalizeWebMiddleware(module?.default); + } catch (error) { + console.error("[Cloudflare-MCP] Failed to load middleware:", error); + return []; + } + })(); + } + + return resolvedMiddlewarePromise; +} + +function normalizeWebMiddleware( + defaultExport: unknown +): WebMiddleware[] { + if (Array.isArray(defaultExport)) { + return defaultExport.filter(isWebMiddleware); + } + + if (isWebMiddleware(defaultExport)) { + return [defaultExport]; + } + + if ( + defaultExport && + typeof defaultExport === "object" && + "middleware" in defaultExport && + typeof (defaultExport as { middleware?: unknown }).middleware === "function" + ) { + return [ + (defaultExport as { middleware: WebMiddleware }).middleware, + ]; + } + + return []; +} + +function isWebMiddleware(value: unknown): value is WebMiddleware { + return typeof value === "function"; +} + +async function runWebMiddleware( + request: Request +): Promise<{ response?: Response; authInfo?: AuthInfo }> { + const webMiddleware = await resolveWebMiddleware(); + + if (webMiddleware.length === 0) { + return {}; + } + + const context: WebMiddlewareContext = { + auth: undefined, + setAuth: (auth) => { + context.auth = auth; + }, + }; + + for (const handler of webMiddleware) { + const result = await handler(request, context); + if (result instanceof Response) { + return { response: result, authInfo: context.auth }; + } + } + + return { authInfo: context.auth }; +} + +/** + * Log a message if debug mode is enabled + */ +function log(message: string, ...args: unknown[]): void { + if (httpConfig.debug) { + console.log(`[Cloudflare-MCP] ${message}`, ...args); + } +} + +/** + * Handle MCP requests + */ +async function handleMcpRequest( + request: Request, + requestOrigin: string | null, + ctx: ExecutionContext, + authInfo?: AuthInfo +): Promise { + const requestId = crypto.randomUUID(); + + // Use the http request context provider to maintain request isolation + return new Promise((resolve) => { + // Convert Web Request headers to a format compatible with httpRequestContext + const headers: Record = {}; + request.headers.forEach((value, key) => { + headers[key] = value; + }); + + httpRequestContextProvider({ id: requestId, headers }, async () => { + let server: Awaited> | null = null; + let transport: WebStatelessHttpTransport | null = null; + + try { + server = await createServer(); + transport = new WebStatelessHttpTransport(httpConfig.debug); + + await server.connect(transport); + const response = await transport.handleRequest(request, authInfo); + + resolve(addCorsHeaders(response, requestOrigin)); + } catch (error) { + console.error("[Cloudflare-MCP] Error handling MCP request:", error); + const errorResponse = new Response( + JSON.stringify({ + jsonrpc: "2.0", + error: { + code: -32603, + message: "Internal server error", + }, + id: null, + }), + { + status: 500, + headers: { "Content-Type": "application/json" }, + } + ); + resolve(addCorsHeaders(errorResponse, requestOrigin)); + } finally { + if (server && transport) { + ctx.waitUntil( + Promise.allSettled([transport.close(), server.close()]) + ); + } + } + }); + }); +} + +/** + * Cloudflare Workers fetch handler + */ +export default { + async fetch( + request: Request, + env: Env, + _ctx: ExecutionContext + ): Promise { + const url = new URL(request.url); + const pathname = url.pathname; + const requestOrigin = request.headers.get("origin"); + + log(`${request.method} ${pathname}`); + + // Handle CORS preflight + if (request.method === "OPTIONS") { + return handleCorsPreflightRequest(requestOrigin); + } + + // Normalize the MCP endpoint path + const mcpEndpoint = httpConfig.endpoint?.startsWith("/") + ? httpConfig.endpoint + : `/${httpConfig.endpoint || "mcp"}`; + + // Health check endpoint (no auth required) + if (pathname === "/health") { + const response = new Response( + JSON.stringify({ + status: "ok", + transport: "cloudflare-workers", + mode: "stateless", + auth: "none", + }), + { + status: 200, + headers: { "Content-Type": "application/json" }, + } + ); + return addCorsHeaders(response, requestOrigin); + } + + // Home page (no auth required) + if (pathname === "/" && request.method === "GET") { + const html = homeTemplate( + mcpEndpoint, + templateConfig.name, + templateConfig.description + ); + const response = new Response(html, { + status: 200, + headers: { "Content-Type": "text/html" }, + }); + return addCorsHeaders(response, requestOrigin); + } + + // MCP endpoint + if (pathname === mcpEndpoint) { + try { + const { response, authInfo } = await runWebMiddleware(request); + if (response) { + return addCorsHeaders(response, requestOrigin); + } + return handleMcpRequest(request, requestOrigin, _ctx, authInfo); + } catch (error) { + console.error("[Cloudflare-MCP] Middleware error:", error); + const response = new Response( + JSON.stringify({ + jsonrpc: "2.0", + error: { + code: -32603, + message: "Internal server error", + }, + id: null, + }), + { + status: 500, + headers: { "Content-Type": "application/json" }, + } + ); + return addCorsHeaders(response, requestOrigin); + } + } + + // 404 for unknown paths + const response = new Response(JSON.stringify({ error: "Not found" }), { + status: 404, + headers: { "Content-Type": "application/json" }, + }); + return addCorsHeaders(response, requestOrigin); + }, +}; diff --git a/packages/xmcp/src/runtime/transports/http/cors/headers.ts b/packages/xmcp/src/runtime/transports/http/cors/headers.ts new file mode 100644 index 000000000..5e2e10eb9 --- /dev/null +++ b/packages/xmcp/src/runtime/transports/http/cors/headers.ts @@ -0,0 +1,58 @@ +import type { CorsConfig } from "@/compiler/config"; + +export function buildCorsHeaders( + config: CorsConfig, + requestOrigin?: string | null +): Record { + const headers: Record = {}; + + if (config.origin !== undefined) { + if (config.origin === true) { + if (requestOrigin) { + headers["Access-Control-Allow-Origin"] = requestOrigin; + } + } else if (config.origin !== false) { + if (Array.isArray(config.origin)) { + const matchedOrigin = + requestOrigin && config.origin.includes(requestOrigin) + ? requestOrigin + : config.origin[0] || "*"; + headers["Access-Control-Allow-Origin"] = matchedOrigin; + } else { + headers["Access-Control-Allow-Origin"] = String(config.origin); + } + } + } + + if (config.methods !== undefined) { + headers["Access-Control-Allow-Methods"] = Array.isArray(config.methods) + ? config.methods.join(",") + : String(config.methods); + } + + if (config.allowedHeaders !== undefined) { + headers["Access-Control-Allow-Headers"] = Array.isArray( + config.allowedHeaders + ) + ? config.allowedHeaders.join(",") + : String(config.allowedHeaders); + } + + if (config.exposedHeaders !== undefined) { + headers["Access-Control-Expose-Headers"] = Array.isArray( + config.exposedHeaders + ) + ? config.exposedHeaders.join(",") + : String(config.exposedHeaders); + } + + if (typeof config.credentials === "boolean") { + headers["Access-Control-Allow-Credentials"] = String(config.credentials); + } + + if (typeof config.maxAge === "number") { + headers["Access-Control-Max-Age"] = String(config.maxAge); + } + + return headers; +} diff --git a/packages/xmcp/src/runtime/transports/http/cors/index.ts b/packages/xmcp/src/runtime/transports/http/cors/index.ts index f5cd517b5..10e28e768 100644 --- a/packages/xmcp/src/runtime/transports/http/cors/index.ts +++ b/packages/xmcp/src/runtime/transports/http/cors/index.ts @@ -1,5 +1,6 @@ import { CorsConfig } from "@/compiler/config"; import { Request, Response, NextFunction, RequestHandler } from "express"; +import { buildCorsHeaders } from "./headers"; /** * Sets CORS headers on the response based on the provided configuration @@ -9,57 +10,9 @@ export function setHeaders( config: CorsConfig, origin?: string ): void { - if (config.origin !== undefined) { - if (config.origin === true) { - if (origin) { - res.setHeader("Access-Control-Allow-Origin", origin); - } - } else if (config.origin !== false) { - res.setHeader( - "Access-Control-Allow-Origin", - Array.isArray(config.origin) - ? config.origin.join(",") - : String(config.origin) - ); - } - } - - if (config.methods !== undefined) { - res.setHeader( - "Access-Control-Allow-Methods", - Array.isArray(config.methods) - ? config.methods.join(",") - : String(config.methods) - ); - } - - if (config.allowedHeaders !== undefined) { - res.setHeader( - "Access-Control-Allow-Headers", - Array.isArray(config.allowedHeaders) - ? config.allowedHeaders.join(",") - : String(config.allowedHeaders) - ); - } - - if (config.exposedHeaders !== undefined) { - res.setHeader( - "Access-Control-Expose-Headers", - Array.isArray(config.exposedHeaders) - ? config.exposedHeaders.join(",") - : String(config.exposedHeaders) - ); - } - - if (typeof config.credentials === "boolean") { - res.setHeader( - "Access-Control-Allow-Credentials", - String(config.credentials) - ); - } - - if (typeof config.maxAge === "number") { - res.setHeader("Access-Control-Max-Age", String(config.maxAge)); + const headers = buildCorsHeaders(config, origin); + for (const [key, value] of Object.entries(headers)) { + res.setHeader(key, value); } } diff --git a/packages/xmcp/src/runtime/transports/http/web-stateless-http.ts b/packages/xmcp/src/runtime/transports/http/web-stateless-http.ts new file mode 100644 index 000000000..624d6616a --- /dev/null +++ b/packages/xmcp/src/runtime/transports/http/web-stateless-http.ts @@ -0,0 +1,288 @@ +import { Transport } from "@modelcontextprotocol/sdk/shared/transport"; +import { MessageExtraInfo } from "@modelcontextprotocol/sdk/types"; +import { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types"; + +export interface JsonRpcMessage { + jsonrpc: string; + method?: string; + params?: any; + id?: string | number | null; + result?: any; + error?: any; +} + +/** + * Web API-based stateless HTTP transport for Cloudflare Workers. + * Uses only Web APIs (no Node.js dependencies). + */ +export class WebStatelessHttpTransport implements Transport { + private debug: boolean; + private _started: boolean = false; + + // Promise-based response collection (replaces EventEmitter pattern) + private _responseResolvers: Map< + string, + { + requestIds: Set; + responses: JsonRpcMessage[]; + resolve: (response: Response) => void; + } + > = new Map(); + private _requestToCollectorMapping: Map = new Map(); + + // MCP SDK transport interface + onmessage?: (message: JsonRpcMessage, extra?: MessageExtraInfo) => void; + onerror?: (error: Error) => void; + onclose?: () => void; + + constructor(debug: boolean = false) { + this.debug = debug; + } + + private log(message: string, ...args: any[]): void { + if (this.debug) { + console.log(`[WebStatelessHTTP] ${message}`, ...args); + } + } + + async start(): Promise { + if (this._started) { + throw new Error("Transport already started"); + } + this._started = true; + } + + async close(): Promise { + // Resolve any pending requests with service unavailable + this._responseResolvers.forEach((collector) => { + collector.resolve( + new Response( + JSON.stringify({ + jsonrpc: "2.0", + error: { + code: -32000, + message: "Service unavailable: Server shutting down", + }, + id: null, + }), + { + status: 503, + headers: { "Content-Type": "application/json" }, + } + ) + ); + }); + this._responseResolvers.clear(); + this._requestToCollectorMapping.clear(); + } + + async send(message: JsonRpcMessage): Promise { + const requestId = message.id; + + if (requestId === undefined || requestId === null) { + // In stateless mode, we can't handle notifications without request IDs + if (this.debug) { + this.log("Dropping notification without request ID"); + } + return; + } + + const collectorId = this._requestToCollectorMapping.get(requestId); + if (collectorId) { + const collector = this._responseResolvers.get(collectorId); + if ( + collector && + (message.result !== undefined || message.error !== undefined) + ) { + collector.responses.push(message); + collector.requestIds.delete(requestId); + + // All responses collected, resolve the promise + if (collector.requestIds.size === 0) { + const responseBody = + collector.responses.length === 1 + ? collector.responses[0] + : collector.responses; + + collector.resolve( + new Response(JSON.stringify(responseBody), { + status: 200, + headers: { "Content-Type": "application/json" }, + }) + ); + + // Cleanup + this._responseResolvers.delete(collectorId); + for (const response of collector.responses) { + if (response.id !== undefined && response.id !== null) { + this._requestToCollectorMapping.delete(response.id); + } + } + } + } + } + } + + /** + * Handle an incoming Web Request and return a Web Response. + * This is the main entry point for Cloudflare Workers. + */ + async handleRequest( + request: Request, + authInfo?: AuthInfo + ): Promise { + // Only support POST in stateless mode + if (request.method !== "POST") { + return new Response( + JSON.stringify({ + jsonrpc: "2.0", + error: { + code: -32000, + message: "Method not allowed.", + }, + id: null, + }), + { + status: 405, + headers: { "Content-Type": "application/json" }, + } + ); + } + + return this.handlePOST(request, authInfo); + } + + private async handlePOST( + request: Request, + authInfo?: AuthInfo + ): Promise { + try { + const acceptHeader = request.headers.get("accept"); + const acceptsJson = acceptHeader?.includes("application/json"); + + if (!acceptsJson) { + return new Response( + JSON.stringify({ + jsonrpc: "2.0", + error: { + code: -32000, + message: "Not Acceptable: Client must accept application/json", + }, + id: null, + }), + { + status: 406, + headers: { "Content-Type": "application/json" }, + } + ); + } + + const contentType = request.headers.get("content-type"); + if (!contentType || !contentType.includes("application/json")) { + return new Response( + JSON.stringify({ + jsonrpc: "2.0", + error: { + code: -32000, + message: + "Unsupported Media Type: Content-Type must be application/json", + }, + id: null, + }), + { + status: 415, + headers: { "Content-Type": "application/json" }, + } + ); + } + + const rawMessage = await request.json(); + const incomingMessages: JsonRpcMessage[] = Array.isArray(rawMessage) + ? rawMessage + : [rawMessage]; + + const invalidResponses: JsonRpcMessage[] = []; + const messages = incomingMessages.filter((msg) => { + if (msg?.jsonrpc === "2.0") { + return true; + } + invalidResponses.push({ + jsonrpc: "2.0", + error: { + code: -32600, + message: "Invalid Request", + }, + id: msg && "id" in msg ? (msg as any).id ?? null : null, + } as JsonRpcMessage); + return false; + }); + + const hasRequests = messages.some( + (msg) => msg.method && msg.id !== undefined + ); + + if (!hasRequests) { + if (invalidResponses.length > 0) { + const payload = + invalidResponses.length === 1 + ? invalidResponses[0] + : invalidResponses; + return new Response(JSON.stringify(payload), { + status: 400, + headers: { "Content-Type": "application/json" }, + }); + } + return new Response(null, { status: 202 }); + } + + // Handle requests that expect responses + const requestIds = messages + .filter((msg) => msg.method && msg.id !== undefined) + .map((msg) => msg.id!); + + if (requestIds.length === 0) { + return new Response(null, { status: 202 }); + } + + // Create a promise that will resolve when all responses are collected + const responsePromise = new Promise((resolve) => { + const collectorId = crypto.randomUUID(); + this._responseResolvers.set(collectorId, { + requestIds: new Set(requestIds), + responses: invalidResponses, + resolve, + }); + + for (const requestId of requestIds) { + this._requestToCollectorMapping.set(requestId, collectorId); + } + }); + + // Process messages through MCP SDK transport interface + for (const message of messages) { + if (this.onmessage) { + this.onmessage(message, { authInfo }); + } + } + + // Wait for all responses + return responsePromise; + } catch (error) { + return new Response( + JSON.stringify({ + jsonrpc: "2.0", + error: { + code: -32700, + message: "Parse error", + data: String(error), + }, + id: null, + }), + { + status: 400, + headers: { "Content-Type": "application/json" }, + } + ); + } + } +} diff --git a/packages/xmcp/src/runtime/utils/path-to-tool-name.ts b/packages/xmcp/src/runtime/utils/path-to-tool-name.ts new file mode 100644 index 000000000..1452126f0 --- /dev/null +++ b/packages/xmcp/src/runtime/utils/path-to-tool-name.ts @@ -0,0 +1,69 @@ +/** + * Normalize a path and generate a base name for tool identification. + */ +function normalizeAndGetBaseName(path: string): { + normalizedPath: string; + baseName: string; +} { + // Normalize path (handle both / and \ separators) + const normalizedPath = path.replace(/\\/g, "/"); + + if (!normalizedPath) { + throw new Error("Invalid tool path: path is empty"); + } + + // Remove file extension + const withoutExtension = normalizedPath.replace(/\.[^/.]+$/, ""); + + // Replace / with _ + const baseName = withoutExtension.replace(/\//g, "_"); + + return { normalizedPath, baseName }; +} + +/** + * Web-compatible pathToToolName using djb2 hash. + * Used for Cloudflare Workers where node:crypto is not available. + */ +export function pathToToolNameDjb2(path: string): string { + const { normalizedPath, baseName } = normalizeAndGetBaseName(path); + + // djb2 hash algorithm - deterministic and works everywhere + let hash = 5381; + for (let i = 0; i < normalizedPath.length; i++) { + hash = (Math.imul(hash, 33) + normalizedPath.charCodeAt(i)) >>> 0; + } + const hashStr = hash.toString(16).slice(0, 6).padStart(6, "0"); + + return `${baseName}_${hashStr}`; +} + +/** + * Node.js pathToToolName using MD5 hash. + * Used for backwards compatibility with existing Node.js deployments. + * + * Note: For Cloudflare builds, crypto is resolved to an empty module + * via bundler fallback config. This is safe because pathToToolNameDjb2 + * is used instead in Cloudflare Workers. + */ +export function pathToToolNameMd5(path: string): string { + const { normalizedPath, baseName } = normalizeAndGetBaseName(path); + + // Use 'crypto' (not 'node:crypto') for bundler compatibility + // eslint-disable-next-line @typescript-eslint/no-require-imports + const crypto = require("crypto"); + const hash = crypto + .createHash("md5") + .update(normalizedPath) + .digest("hex") + .slice(0, 6); + + return `${baseName}_${hash}`; +} + +/** + * Default export for backwards compatibility. + * In Cloudflare Workers, use pathToToolNameDjb2 directly. + * In Node.js, this uses MD5 for backwards compatibility. + */ +export const pathToToolName = pathToToolNameMd5; diff --git a/packages/xmcp/src/runtime/utils/resources.ts b/packages/xmcp/src/runtime/utils/resources.ts index 7f22ed993..2265b570c 100644 --- a/packages/xmcp/src/runtime/utils/resources.ts +++ b/packages/xmcp/src/runtime/utils/resources.ts @@ -11,12 +11,80 @@ import { composeUriFromPath } from "./utils/resource-uri-composer"; import { ResourceMetadata } from "@/types/resource"; import { openAIResourceRegistry } from "./openai-resource-registry"; import { flattenMeta } from "./openai/flatten-meta"; -import fs from "fs"; -import path from "path"; import { generateOpenAIHTML, generateUIHTML } from "./react/generate-html"; -import { pathToToolName } from "@/compiler/utils/path-utils"; +import { pathToToolNameMd5, pathToToolNameDjb2 } from "./path-to-tool-name"; import { uIResourceRegistry } from "./ext-apps-registry"; +// Client bundles can be injected at compile time for Cloudflare Workers +// This variable is defined by DefinePlugin at compile time +declare const INJECTED_CLIENT_BUNDLES: + | Record + | undefined; + +declare const IS_CLOUDFLARE: boolean; + +/** + * Get the appropriate pathToToolName function based on runtime environment. + * - For Cloudflare (INJECTED_CLIENT_BUNDLES defined): Use djb2 hash + * - For Node.js: Use MD5 hash (backwards compatible) + */ +function pathToToolName(path: string): string { + // Cloudflare mode: use djb2 hash (bundles were named with djb2 at compile time) + if (IS_CLOUDFLARE) { + return pathToToolNameDjb2(path); + } + // Node.js mode: use MD5 hash (backwards compatible) + return pathToToolNameMd5(path); +} + +/** + * Get a client bundle by name. + * For Cloudflare Workers, bundles are injected at compile time. + * For Node.js, bundles are read from the filesystem at runtime. + */ +function getClientBundle( + bundleName: string +): { js: string; css?: string } | null { + // Cloudflare mode: use injected bundles + if (INJECTED_CLIENT_BUNDLES) { + return INJECTED_CLIENT_BUNDLES[bundleName] || null; + } + + // Node.js mode: read from filesystem + if (!IS_CLOUDFLARE) { + try { + // Dynamic import to avoid bundling fs in Cloudflare + const fs = require("fs"); + const path = require("path"); + + const searchRoots = [ + path.join(process.cwd(), "dist", "client"), + path.join(process.cwd(), "client"), + ]; + + for (const root of searchRoots) { + const jsCandidate = path.join(root, `${bundleName}.bundle.js`); + const cssCandidate = path.join(root, `${bundleName}.bundle.css`); + + if (fs.existsSync(jsCandidate)) { + const js = fs.readFileSync(jsCandidate, "utf-8"); + let css: string | undefined; + + if (fs.existsSync(cssCandidate)) { + css = fs.readFileSync(cssCandidate, "utf-8"); + } + + return { js, css }; + } + } + } catch { + // fs not available or file not found + } + } + + return null; +} + /** Loads resources and injects them into the server */ export function addResourcesToServer( server: McpServer, @@ -47,44 +115,20 @@ export function addResourcesToServer( ): Promise => { if (autoResource.mimeType && autoResource.toolPath) { try { - let clientCode: string | undefined; - let clientCss: string | undefined; - const bundleName = pathToToolName(autoResource.toolPath); + const bundle = getClientBundle(bundleName); - const searchRoots = [ - path.join(process.cwd(), "dist", "client"), - path.join(process.cwd(), "client"), - ]; - const attemptedPaths: string[] = []; - - for (const root of searchRoots) { - const candidate = path.join(root, `${bundleName}.bundle.js`); - const cssCandidate = path.join(root, `${bundleName}.bundle.css`); - attemptedPaths.push(candidate); - - if (fs.existsSync(candidate)) { - clientCode = fs.readFileSync(candidate, "utf-8"); - - if (fs.existsSync(cssCandidate)) { - clientCss = fs.readFileSync(cssCandidate, "utf-8"); - } - break; - } - } - - if (!clientCode) { - const formattedPaths = attemptedPaths - .map((p) => ` - ${p}`) - .join("\n"); + if (!bundle) { throw new Error( `React client bundle not found for "${autoResource.name}" (bundle: "${bundleName}").\n` + - `Expected to find it on filesystem at one of:\n${formattedPaths}\n` + `React tool bundles are generated automatically when you run "xmcp build" (or "xmcp dev").\n` + `Please re-run the build so the framework can regenerate the bundle.` ); } + const clientCode = bundle.js; + const clientCss = bundle.css; + const isMCPApp = autoResource.mimeType === "text/html;profile=mcp-app"; const fullHTML = isMCPApp diff --git a/packages/xmcp/src/runtime/utils/tools.ts b/packages/xmcp/src/runtime/utils/tools.ts index e81d0a255..0e71fc1bb 100644 --- a/packages/xmcp/src/runtime/utils/tools.ts +++ b/packages/xmcp/src/runtime/utils/tools.ts @@ -1,4 +1,5 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp"; +import { z } from "zod"; import { ZodRawShape } from "zod/v3"; import { ToolFile } from "./server"; import { ToolMetadata } from "@/types/tool"; @@ -176,7 +177,9 @@ export function addToolsToServer( const toolConfigFormatted = { title: toolConfig.annotations?.title, description: toolConfig.description, - inputSchema: toolSchema, + // Build the object schema using the project's Zod instance to avoid + // cross-instance v3 shape issues in tools/list JSON schema generation. + inputSchema: z.object(toolSchema), annotations: toolConfig.annotations, _meta: flattenedToolMeta, // Use flattened metadata for MCP protocol }; diff --git a/packages/xmcp/src/types/declarations.ts b/packages/xmcp/src/types/declarations.ts index 968ed8f6c..d15db26cc 100644 --- a/packages/xmcp/src/types/declarations.ts +++ b/packages/xmcp/src/types/declarations.ts @@ -9,6 +9,13 @@ declare module "xmcp/plugins/x402" { export type PaidHandler = import("../plugins/x402/index").PaidHandler; } +declare module "xmcp/cloudflare" { + export const apiKeyAuthMiddleware: typeof import("../runtime/platforms/cloudflare/middlewares/api-key").cloudflareApiKeyAuthMiddleware; + export const jwtAuthMiddleware: typeof import("../runtime/platforms/cloudflare/middlewares/jwt").cloudflareJwtAuthMiddleware; + export type JWTAuthMiddlewareConfig = import("../runtime/platforms/cloudflare/middlewares/jwt").CloudflareJWTAuthMiddlewareConfig; + export type WebMiddleware = import("./middleware").WebMiddleware; +} + declare module "*.module.css" { const classes: { readonly [key: string]: string }; export default classes; diff --git a/packages/xmcp/src/types/middleware.ts b/packages/xmcp/src/types/middleware.ts index 8ea7d2e8b..9ce1bbaf8 100644 --- a/packages/xmcp/src/types/middleware.ts +++ b/packages/xmcp/src/types/middleware.ts @@ -1,4 +1,5 @@ -import { type RequestHandler, type Router } from "express"; +import type { RequestHandler, Router } from "express"; +import type { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types"; export type RequestHandlerAndRouter = { middleware: RequestHandler; @@ -6,3 +7,13 @@ export type RequestHandlerAndRouter = { }; export type Middleware = RequestHandler | RequestHandlerAndRouter; + +export type WebMiddlewareContext = { + auth?: AuthInfo; + setAuth: (auth: AuthInfo) => void; +}; + +export type WebMiddleware = ( + request: Request, + context: WebMiddlewareContext +) => Promise | Response | void; diff --git a/packages/xmcp/src/utils/constants.ts b/packages/xmcp/src/utils/constants.ts index 3a1be651e..9bcf2460f 100644 --- a/packages/xmcp/src/utils/constants.ts +++ b/packages/xmcp/src/utils/constants.ts @@ -1,4 +1,5 @@ import path from "path"; +import { createRequire } from "module"; export const runtimeFolder = ".xmcp"; export const runtimeFolderPath = path.join(process.cwd(), runtimeFolder); @@ -7,3 +8,24 @@ export const rootFolder = path.join(process.cwd()); export const processFolder = process.cwd(); export const distOutputPath = path.join(processFolder, "dist"); export const adapterOutputPath = path.join(runtimeFolderPath, "adapter"); +export const cloudflareOutputPath = path.join(runtimeFolderPath, "cloudflare"); + +/** + * Resolve the absolute path to the `xmcp` package `src/` folder. + * + * Used when we need to bundle TypeScript source from xmcp itself + * (e.g. Cloudflare adapter builds). + */ +export function resolveXmcpSrcPath(): string { + try { + const req = createRequire(path.join(process.cwd(), "package.json")); + const pkgJsonPath = req.resolve("xmcp/package.json"); + return path.join(path.dirname(pkgJsonPath), "src"); + } catch (error) { + throw new Error( + `Could not resolve xmcp source path. ` + + `Make sure the \"xmcp\" package is resolvable from your project.\n` + + `Original error: ${error instanceof Error ? error.message : String(error)}` + ); + } +} diff --git a/packages/xmcp/xmcp.tsconfig.json b/packages/xmcp/xmcp.tsconfig.json index fde839bf9..c5b71ac36 100644 --- a/packages/xmcp/xmcp.tsconfig.json +++ b/packages/xmcp/xmcp.tsconfig.json @@ -22,6 +22,7 @@ "include": [ "src/types/declarations.d.ts", "src/types/adapter.d.ts", + "src/cloudflare.ts", "src/index.ts", "src/runtime/headers.ts", ], diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aad62cce8..f743175ba 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -199,7 +199,7 @@ importers: version: 5.0.0 xmcp: specifier: latest - version: 0.6.0(@swc/helpers@0.5.15)(hono@4.11.1)(postcss@8.5.6)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.104.1)(zod@4.1.13) + version: 0.6.2(@swc/helpers@0.5.15)(postcss@8.5.6)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.104.1)(zod@4.1.13) zod: specifier: ^4.1.13 version: 4.1.13 @@ -403,6 +403,28 @@ importers: specifier: ^4.0.10 version: 4.1.13 + examples/cloudflare-workers: + dependencies: + xmcp: + specifier: workspace:* + version: link:../../packages/xmcp + zod: + specifier: ^3.23.8 + version: 3.25.76 + devDependencies: + '@cloudflare/workers-types': + specifier: ^4.20241112.0 + version: 4.20260127.0 + concurrently: + specifier: ^9.2.0 + version: 9.2.1 + typescript: + specifier: ^5.7.2 + version: 5.9.3 + wrangler: + specifier: ^4.62.0 + version: 4.62.0(@cloudflare/workers-types@4.20260127.0) + examples/custom-bundler-config: dependencies: xmcp: @@ -866,7 +888,7 @@ importers: version: 5.1.0(esbuild@0.27.1) xmcp: specifier: latest - version: 0.6.0(@swc/helpers@0.5.15)(hono@4.11.1)(postcss@8.5.6)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.104.1(esbuild@0.27.1))(zod@4.1.13) + version: 0.6.2(@swc/helpers@0.5.15)(postcss@8.5.6)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.104.1(esbuild@0.27.1))(zod@4.1.13) zod: specifier: ^4.0.10 version: 4.1.13 @@ -1001,8 +1023,8 @@ importers: specifier: ^5.9.3 version: 5.9.3 xmcp: - specifier: ^0.5.8 - version: 0.5.8(@swc/helpers@0.5.15)(hono@4.11.1)(postcss@8.5.6)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.104.1)(zod@4.1.13) + specifier: ^0.6.2 + version: 0.6.2(@swc/helpers@0.5.15)(postcss@8.5.6)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.104.1)(zod@4.1.13) packages/plugins/better-auth: dependencies: @@ -1173,6 +1195,9 @@ importers: '@rspack/core': specifier: ^1.6.7 version: 1.6.7(@swc/helpers@0.5.15) + jose: + specifier: ^6.1.3 + version: 6.1.3 postcss-loader: specifier: ^8.2.0 version: 8.2.0(@rspack/core@1.6.7(@swc/helpers@0.5.15))(postcss@8.5.6)(typescript@5.9.3)(webpack@5.104.1) @@ -1540,10 +1565,60 @@ packages: resolution: {integrity: sha512-qlmgnAm/IeK02RKEKVN8/Glx07xw/Lcv67jBfikM8HXhHc5v7bfYLD8UiWTr6H2RGtvB09cIt9JezRRlsuVBew==} engines: {node: '>=18.17.0'} + '@cloudflare/kv-asset-handler@0.4.2': + resolution: {integrity: sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==} + engines: {node: '>=18.0.0'} + + '@cloudflare/unenv-preset@2.12.0': + resolution: {integrity: sha512-NK4vN+2Z/GbfGS4BamtbbVk1rcu5RmqaYGiyHJQrA09AoxdZPHDF3W/EhgI0YSK8p3vRo/VNCtbSJFPON7FWMQ==} + peerDependencies: + unenv: 2.0.0-rc.24 + workerd: ^1.20260115.0 + peerDependenciesMeta: + workerd: + optional: true + + '@cloudflare/workerd-darwin-64@1.20260131.0': + resolution: {integrity: sha512-+1X4qErc715NUhJZNhtlpuCxajhD5YNre7Cz50WPMmj+BMUrh9h7fntKEadtrUo5SM2YONY7CDzK7wdWbJJBVA==} + engines: {node: '>=16'} + cpu: [x64] + os: [darwin] + + '@cloudflare/workerd-darwin-arm64@1.20260131.0': + resolution: {integrity: sha512-M84mXR8WEMEBuX4/dL2IQ4wHV/ALwYjx9if5ePZR8rdbD7if/fkEEoMBq0bGS/1gMLRqqCZLstabxHV+g92NNg==} + engines: {node: '>=16'} + cpu: [arm64] + os: [darwin] + + '@cloudflare/workerd-linux-64@1.20260131.0': + resolution: {integrity: sha512-SWzr48bCL9y5wjkj23tXS6t/6us99EAH9T5TAscMV0hfJFZQt97RY/gaHKyRRjFv6jfJZvk7d4g+OmGeYBnwcg==} + engines: {node: '>=16'} + cpu: [x64] + os: [linux] + + '@cloudflare/workerd-linux-arm64@1.20260131.0': + resolution: {integrity: sha512-mL0kLPGIBJRPeHS3+erJ2t5dJT3ODhsKvR9aA4BcsY7M30/QhlgJIF6wsgwNisTJ23q8PbobZNHBUKIe8l/E9A==} + engines: {node: '>=16'} + cpu: [arm64] + os: [linux] + + '@cloudflare/workerd-windows-64@1.20260131.0': + resolution: {integrity: sha512-hoQqTFBpP1zntP2OQSpt5dEWbd9vSBliK+G7LmDXjKitPkmkRFo2PB4P9aBRE1edPAIO/fpdoJv928k2HaAn4A==} + engines: {node: '>=16'} + cpu: [x64] + os: [win32] + + '@cloudflare/workers-types@4.20260127.0': + resolution: {integrity: sha512-4M1HLcWViSdT/pAeDGEB5x5P3sqW7UIi34QrBRnxXbqjAY9if8vBU/lWRWnM+UqKzxWGB2LYjEVOzZrp0jZL+w==} + '@colors/colors@1.5.0': resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + '@dimforge/rapier3d-compat@0.12.0': resolution: {integrity: sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==} @@ -1568,6 +1643,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.27.0': + resolution: {integrity: sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/aix-ppc64@0.27.1': resolution: {integrity: sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA==} engines: {node: '>=18'} @@ -1586,6 +1667,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.27.0': + resolution: {integrity: sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.27.1': resolution: {integrity: sha512-45fuKmAJpxnQWixOGCrS+ro4Uvb4Re9+UTieUY2f8AEc+t7d4AaZ6eUJ3Hva7dtrxAAWHtlEFsXFMAgNnGU9uQ==} engines: {node: '>=18'} @@ -1604,6 +1691,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.27.0': + resolution: {integrity: sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-arm@0.27.1': resolution: {integrity: sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg==} engines: {node: '>=18'} @@ -1622,6 +1715,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.27.0': + resolution: {integrity: sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/android-x64@0.27.1': resolution: {integrity: sha512-LBEpOz0BsgMEeHgenf5aqmn/lLNTFXVfoWMUox8CtWWYK9X4jmQzWjoGoNb8lmAYml/tQ/Ysvm8q7szu7BoxRQ==} engines: {node: '>=18'} @@ -1640,6 +1739,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.27.0': + resolution: {integrity: sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.27.1': resolution: {integrity: sha512-veg7fL8eMSCVKL7IW4pxb54QERtedFDfY/ASrumK/SbFsXnRazxY4YykN/THYqFnFwJ0aVjiUrVG2PwcdAEqQQ==} engines: {node: '>=18'} @@ -1658,6 +1763,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.27.0': + resolution: {integrity: sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/darwin-x64@0.27.1': resolution: {integrity: sha512-+3ELd+nTzhfWb07Vol7EZ+5PTbJ/u74nC6iv4/lwIU99Ip5uuY6QoIf0Hn4m2HoV0qcnRivN3KSqc+FyCHjoVQ==} engines: {node: '>=18'} @@ -1676,6 +1787,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.27.0': + resolution: {integrity: sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.27.1': resolution: {integrity: sha512-/8Rfgns4XD9XOSXlzUDepG8PX+AVWHliYlUkFI3K3GB6tqbdjYqdhcb4BKRd7C0BhZSoaCxhv8kTcBrcZWP+xg==} engines: {node: '>=18'} @@ -1694,6 +1811,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.27.0': + resolution: {integrity: sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/freebsd-x64@0.27.1': resolution: {integrity: sha512-GITpD8dK9C+r+5yRT/UKVT36h/DQLOHdwGVwwoHidlnA168oD3uxA878XloXebK4Ul3gDBBIvEdL7go9gCUFzQ==} engines: {node: '>=18'} @@ -1712,6 +1835,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.27.0': + resolution: {integrity: sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.27.1': resolution: {integrity: sha512-W9//kCrh/6in9rWIBdKaMtuTTzNj6jSeG/haWBADqLLa9P8O5YSRDzgD5y9QBok4AYlzS6ARHifAb75V6G670Q==} engines: {node: '>=18'} @@ -1730,6 +1859,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.27.0': + resolution: {integrity: sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-arm@0.27.1': resolution: {integrity: sha512-ieMID0JRZY/ZeCrsFQ3Y3NlHNCqIhTprJfDgSB3/lv5jJZ8FX3hqPyXWhe+gvS5ARMBJ242PM+VNz/ctNj//eA==} engines: {node: '>=18'} @@ -1748,6 +1883,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.27.0': + resolution: {integrity: sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-ia32@0.27.1': resolution: {integrity: sha512-VIUV4z8GD8rtSVMfAj1aXFahsi/+tcoXXNYmXgzISL+KB381vbSTNdeZHHHIYqFyXcoEhu9n5cT+05tRv13rlw==} engines: {node: '>=18'} @@ -1766,6 +1907,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.27.0': + resolution: {integrity: sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.27.1': resolution: {integrity: sha512-l4rfiiJRN7sTNI//ff65zJ9z8U+k6zcCg0LALU5iEWzY+a1mVZ8iWC1k5EsNKThZ7XCQ6YWtsZ8EWYm7r1UEsg==} engines: {node: '>=18'} @@ -1784,6 +1931,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.27.0': + resolution: {integrity: sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-mips64el@0.27.1': resolution: {integrity: sha512-U0bEuAOLvO/DWFdygTHWY8C067FXz+UbzKgxYhXC0fDieFa0kDIra1FAhsAARRJbvEyso8aAqvPdNxzWuStBnA==} engines: {node: '>=18'} @@ -1802,6 +1955,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.27.0': + resolution: {integrity: sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-ppc64@0.27.1': resolution: {integrity: sha512-NzdQ/Xwu6vPSf/GkdmRNsOfIeSGnh7muundsWItmBsVpMoNPVpM61qNzAVY3pZ1glzzAxLR40UyYM23eaDDbYQ==} engines: {node: '>=18'} @@ -1820,6 +1979,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.27.0': + resolution: {integrity: sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-riscv64@0.27.1': resolution: {integrity: sha512-7zlw8p3IApcsN7mFw0O1Z1PyEk6PlKMu18roImfl3iQHTnr/yAfYv6s4hXPidbDoI2Q0pW+5xeoM4eTCC0UdrQ==} engines: {node: '>=18'} @@ -1838,6 +2003,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.27.0': + resolution: {integrity: sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-s390x@0.27.1': resolution: {integrity: sha512-cGj5wli+G+nkVQdZo3+7FDKC25Uh4ZVwOAK6A06Hsvgr8WqBBuOy/1s+PUEd/6Je+vjfm6stX0kmib5b/O2Ykw==} engines: {node: '>=18'} @@ -1856,6 +2027,12 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.27.0': + resolution: {integrity: sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/linux-x64@0.27.1': resolution: {integrity: sha512-z3H/HYI9MM0HTv3hQZ81f+AKb+yEoCRlUby1F80vbQ5XdzEMyY/9iNlAmhqiBKw4MJXwfgsh7ERGEOhrM1niMA==} engines: {node: '>=18'} @@ -1868,6 +2045,12 @@ packages: cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-arm64@0.27.0': + resolution: {integrity: sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-arm64@0.27.1': resolution: {integrity: sha512-wzC24DxAvk8Em01YmVXyjl96Mr+ecTPyOuADAvjGg+fyBpGmxmcr2E5ttf7Im8D0sXZihpxzO1isus8MdjMCXQ==} engines: {node: '>=18'} @@ -1886,6 +2069,12 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.27.0': + resolution: {integrity: sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/netbsd-x64@0.27.1': resolution: {integrity: sha512-1YQ8ybGi2yIXswu6eNzJsrYIGFpnlzEWRl6iR5gMgmsrR0FcNoV1m9k9sc3PuP5rUBLshOZylc9nqSgymI+TYg==} engines: {node: '>=18'} @@ -1898,6 +2087,12 @@ packages: cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-arm64@0.27.0': + resolution: {integrity: sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-arm64@0.27.1': resolution: {integrity: sha512-5Z+DzLCrq5wmU7RDaMDe2DVXMRm2tTDvX2KU14JJVBN2CT/qov7XVix85QoJqHltpvAOZUAc3ndU56HSMWrv8g==} engines: {node: '>=18'} @@ -1916,6 +2111,12 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.27.0': + resolution: {integrity: sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/openbsd-x64@0.27.1': resolution: {integrity: sha512-Q73ENzIdPF5jap4wqLtsfh8YbYSZ8Q0wnxplOlZUOyZy7B4ZKW8DXGWgTCZmF8VWD7Tciwv5F4NsRf6vYlZtqg==} engines: {node: '>=18'} @@ -1928,6 +2129,12 @@ packages: cpu: [arm64] os: [openharmony] + '@esbuild/openharmony-arm64@0.27.0': + resolution: {integrity: sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/openharmony-arm64@0.27.1': resolution: {integrity: sha512-ajbHrGM/XiK+sXM0JzEbJAen+0E+JMQZ2l4RR4VFwvV9JEERx+oxtgkpoKv1SevhjavK2z2ReHk32pjzktWbGg==} engines: {node: '>=18'} @@ -1946,6 +2153,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.27.0': + resolution: {integrity: sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/sunos-x64@0.27.1': resolution: {integrity: sha512-IPUW+y4VIjuDVn+OMzHc5FV4GubIwPnsz6ubkvN8cuhEqH81NovB53IUlrlBkPMEPxvNnf79MGBoz8rZ2iW8HA==} engines: {node: '>=18'} @@ -1964,6 +2177,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.27.0': + resolution: {integrity: sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.27.1': resolution: {integrity: sha512-RIVRWiljWA6CdVu8zkWcRmGP7iRRIIwvhDKem8UMBjPql2TXM5PkDVvvrzMtj1V+WFPB4K7zkIGM7VzRtFkjdg==} engines: {node: '>=18'} @@ -1982,6 +2201,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.27.0': + resolution: {integrity: sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-ia32@0.27.1': resolution: {integrity: sha512-2BR5M8CPbptC1AK5JbJT1fWrHLvejwZidKx3UMSF0ecHMa+smhi16drIrCEggkgviBwLYd5nwrFLSl5Kho96RQ==} engines: {node: '>=18'} @@ -2000,6 +2225,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.27.0': + resolution: {integrity: sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@esbuild/win32-x64@0.27.1': resolution: {integrity: sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw==} engines: {node: '>=18'} @@ -2405,6 +2636,9 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@jsonjoy.com/base64@1.1.2': resolution: {integrity: sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==} engines: {node: '>=10.0'} @@ -2774,6 +3008,15 @@ packages: engines: {node: '>=18'} hasBin: true + '@poppinss/colors@4.1.6': + resolution: {integrity: sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==} + + '@poppinss/dumper@0.6.5': + resolution: {integrity: sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==} + + '@poppinss/exception@1.2.3': + resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==} + '@radix-ui/colors@3.0.0': resolution: {integrity: sha512-FUOsGBkHrYJwCSEtWRCIfQbZG7q1e6DgxCIOe1SUQzDe/7rXXeA47s8yCn6fuTNQAj1Zq4oTFi9Yjp3wzElcxg==} @@ -3792,6 +4035,13 @@ packages: '@shikijs/vscode-textmate@9.3.1': resolution: {integrity: sha512-79QfK1393x9Ho60QFyLti+QfdJzRQCVLFb97kOIV7Eo9vQU/roINgk7m24uv0a7AUvN//RDH36FLjjK48v0s9g==} + '@sindresorhus/is@7.2.0': + resolution: {integrity: sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==} + engines: {node: '>=18'} + + '@speed-highlight/core@1.2.14': + resolution: {integrity: sha512-G4ewlBNhUtlLvrJTb88d2mdy2KRijzs4UhnlrOSRT4bmjh/IqNElZa3zkrZ+TC47TwtlDWzVLFADljF1Ijp5hA==} + '@stablelib/base64@1.0.1': resolution: {integrity: sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==} @@ -4755,6 +5005,9 @@ packages: bl@5.1.0: resolution: {integrity: sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==} + blake3-wasm@2.1.5: + resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} + body-parser@1.20.4: resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} @@ -4833,6 +5086,9 @@ packages: caniuse-lite@1.0.30001760: resolution: {integrity: sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==} + caniuse-lite@1.0.30001767: + resolution: {integrity: sha512-34+zUAMhSH+r+9eKmYG+k2Rpt8XttfE4yXAjoZvkAPs15xcYQhyBYdalJ65BzivAvGRMViEjy6oKr/S91loekQ==} + ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -4929,6 +5185,10 @@ packages: client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + clone@1.0.4: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} engines: {node: '>=0.8'} @@ -4989,6 +5249,11 @@ packages: resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} engines: {'0': node >= 6.0} + concurrently@9.2.1: + resolution: {integrity: sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==} + engines: {node: '>=18'} + hasBin: true + consola@3.4.2: resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} engines: {node: ^14.18.0 || >=16.10.0} @@ -5034,6 +5299,10 @@ packages: resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} engines: {node: '>= 0.10'} + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + cosmiconfig@8.3.6: resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} engines: {node: '>=14'} @@ -5268,6 +5537,9 @@ packages: error-ex@1.3.4: resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + error-stack-parser-es@1.0.5: + resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} + es-abstract@1.24.0: resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==} engines: {node: '>= 0.4'} @@ -5319,6 +5591,11 @@ packages: engines: {node: '>=18'} hasBin: true + esbuild@0.27.0: + resolution: {integrity: sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==} + engines: {node: '>=18'} + hasBin: true + esbuild@0.27.1: resolution: {integrity: sha512-yY35KZckJJuVVPXpvjgxiCuVEJT67F6zDeVTv4rizyPrfGBUpZQsvmxnN+C371c2esD/hNMjj4tpBhuueLN7aA==} engines: {node: '>=18'} @@ -5844,6 +6121,10 @@ packages: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + get-east-asian-width@1.4.0: resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} engines: {node: '>=18'} @@ -6489,6 +6770,10 @@ packages: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + kysely@0.28.8: resolution: {integrity: sha512-QUOgl5ZrS9IRuhq5FvOKFSsD/3+IA6MLE81/bOOTRA/YQpKDza2sFdN5g6JCB9BOpqMJDGefLCQ9F12hRS13TA==} engines: {node: '>=20.0.0'} @@ -6956,6 +7241,11 @@ packages: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} + miniflare@4.20260131.0: + resolution: {integrity: sha512-CtObRzlAzOUpCFH+MgImykxmDNKthrgIYtC+oLC3UGpve6bGLomKUW4u4EorTvzlQFHe66/9m/+AYbBbpzG0mQ==} + engines: {node: '>=18.0.0'} + hasBin: true + minimatch@10.1.1: resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} engines: {node: 20 || >=22} @@ -7273,6 +7563,9 @@ packages: path-to-regexp@0.1.12: resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} + path-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + path-to-regexp@8.3.0: resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} @@ -7280,6 +7573,9 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + pg-cloudflare@1.2.7: resolution: {integrity: sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==} @@ -7750,6 +8046,10 @@ packages: remark@15.0.1: resolution: {integrity: sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A==} + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} @@ -7942,6 +8242,10 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + shell-quote@1.8.3: + resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} + engines: {node: '>= 0.4'} + shiki@1.17.7: resolution: {integrity: sha512-Zf6hNtWhFyF4XP5OOsXkBTEx9JFPiN0TQx4wSe+Vqeuczewgk2vT4IZhF4gka55uelm052BD5BaHavNqUNZd+A==} @@ -8153,6 +8457,10 @@ packages: sugar-high@0.9.5: resolution: {integrity: sha512-eirwp9p7QcMg6EFCD6zrGh4H30uFx2YtfiMJUavagceP6/YUUjLeiQmis7QuwqKB3nXrWXlLaRumCqOd9AKpSA==} + supports-color@10.2.2: + resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} + engines: {node: '>=18'} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -8293,6 +8601,10 @@ packages: peerDependencies: tslib: '2' + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} @@ -8464,6 +8776,16 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici-types@6.23.0: + resolution: {integrity: sha512-HN7GeXgBUs1StmY/vf9hIH11LrNI5SfqmFVtxKyp9Dhuf1P1cDSRlS+H1NJDaGOWzlI08q+NmiHgu11Vx6QnhA==} + + undici@7.18.2: + resolution: {integrity: sha512-y+8YjDFzWdQlSE9N5nzKMT3g4a5UBX1HKowfdXh0uvAnTaqqwqB92Jt4UXBAeKekDs5IaDKyJFR4X1gYVCgXcw==} + engines: {node: '>=20.18.1'} + + unenv@2.0.0-rc.24: + resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==} + unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} @@ -8683,6 +9005,21 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + workerd@1.20260131.0: + resolution: {integrity: sha512-4zZxOdWeActbRfydQQlj7vZ2ay01AjjNC4K3stjmWC3xZHeXeN3EAROwsWE83SZHhtw4rn18srrhtXoQvQMw3Q==} + engines: {node: '>=16'} + hasBin: true + + wrangler@4.62.0: + resolution: {integrity: sha512-DogP9jifqw85g33BqwF6m21YBW5J7+Ep9IJLgr6oqHU0RkA79JMN5baeWXdmnIWZl+VZh6bmtNtR+5/Djd32tg==} + engines: {node: '>=20.0.0'} + hasBin: true + peerDependencies: + '@cloudflare/workers-types': ^4.20260131.0 + peerDependenciesMeta: + '@cloudflare/workers-types': + optional: true + wrap-ansi@6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} @@ -8702,6 +9039,18 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + xmcp@0.5.7: resolution: {integrity: sha512-NnBx+1P7v6Apah5lgtxZxojhp/lP1qLjTTS/0AVear60akULVYL5f54n9w7XjHvxm59JqNBppOVYdm8zemehYw==} hasBin: true @@ -8728,8 +9077,8 @@ packages: react-dom: optional: true - xmcp@0.6.0: - resolution: {integrity: sha512-2zuLx45cysYT/Xuk2ldPb75WKCyTCkRzumGb1j7kmM1x9XS0In5gSc/xmsYdrGlvzCQHnh5OJIviLmasaLSLlw==} + xmcp@0.6.2: + resolution: {integrity: sha512-xY88YcyD/CJab4PAK7b2CCPCian+DQv63wyVv40snVmgD3AFNWp8uZ1d41UqsJl4xN7gyc/XqV/GvhrgBoMbVg==} hasBin: true peerDependencies: react: '>=19.0.0' @@ -8749,6 +9098,10 @@ packages: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -8765,6 +9118,10 @@ packages: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -8773,11 +9130,22 @@ packages: resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} engines: {node: '>=18'} + youch-core@0.3.3: + resolution: {integrity: sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==} + + youch@4.1.0-beta.10: + resolution: {integrity: sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==} + zod-to-json-schema@3.25.0: resolution: {integrity: sha512-HvWtU2UG41LALjajJrML6uQejQhNJx+JBO9IflpSja4R03iNWfKXrj6W2h7ljuLyc1nKS+9yDyL/9tD1U/yBnQ==} peerDependencies: zod: ^3.25 || ^4 + zod-to-json-schema@3.25.1: + resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==} + peerDependencies: + zod: ^3.25 || ^4 + zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} @@ -9157,9 +9525,38 @@ snapshots: - react - react-dom + '@cloudflare/kv-asset-handler@0.4.2': {} + + '@cloudflare/unenv-preset@2.12.0(unenv@2.0.0-rc.24)(workerd@1.20260131.0)': + dependencies: + unenv: 2.0.0-rc.24 + optionalDependencies: + workerd: 1.20260131.0 + + '@cloudflare/workerd-darwin-64@1.20260131.0': + optional: true + + '@cloudflare/workerd-darwin-arm64@1.20260131.0': + optional: true + + '@cloudflare/workerd-linux-64@1.20260131.0': + optional: true + + '@cloudflare/workerd-linux-arm64@1.20260131.0': + optional: true + + '@cloudflare/workerd-windows-64@1.20260131.0': + optional: true + + '@cloudflare/workers-types@4.20260127.0': {} + '@colors/colors@1.5.0': optional: true + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + '@dimforge/rapier3d-compat@0.12.0': {} '@emnapi/core@1.7.1': @@ -9184,6 +9581,9 @@ snapshots: '@esbuild/aix-ppc64@0.25.12': optional: true + '@esbuild/aix-ppc64@0.27.0': + optional: true + '@esbuild/aix-ppc64@0.27.1': optional: true @@ -9193,6 +9593,9 @@ snapshots: '@esbuild/android-arm64@0.25.12': optional: true + '@esbuild/android-arm64@0.27.0': + optional: true + '@esbuild/android-arm64@0.27.1': optional: true @@ -9202,6 +9605,9 @@ snapshots: '@esbuild/android-arm@0.25.12': optional: true + '@esbuild/android-arm@0.27.0': + optional: true + '@esbuild/android-arm@0.27.1': optional: true @@ -9211,6 +9617,9 @@ snapshots: '@esbuild/android-x64@0.25.12': optional: true + '@esbuild/android-x64@0.27.0': + optional: true + '@esbuild/android-x64@0.27.1': optional: true @@ -9220,6 +9629,9 @@ snapshots: '@esbuild/darwin-arm64@0.25.12': optional: true + '@esbuild/darwin-arm64@0.27.0': + optional: true + '@esbuild/darwin-arm64@0.27.1': optional: true @@ -9229,6 +9641,9 @@ snapshots: '@esbuild/darwin-x64@0.25.12': optional: true + '@esbuild/darwin-x64@0.27.0': + optional: true + '@esbuild/darwin-x64@0.27.1': optional: true @@ -9238,6 +9653,9 @@ snapshots: '@esbuild/freebsd-arm64@0.25.12': optional: true + '@esbuild/freebsd-arm64@0.27.0': + optional: true + '@esbuild/freebsd-arm64@0.27.1': optional: true @@ -9247,6 +9665,9 @@ snapshots: '@esbuild/freebsd-x64@0.25.12': optional: true + '@esbuild/freebsd-x64@0.27.0': + optional: true + '@esbuild/freebsd-x64@0.27.1': optional: true @@ -9256,6 +9677,9 @@ snapshots: '@esbuild/linux-arm64@0.25.12': optional: true + '@esbuild/linux-arm64@0.27.0': + optional: true + '@esbuild/linux-arm64@0.27.1': optional: true @@ -9265,6 +9689,9 @@ snapshots: '@esbuild/linux-arm@0.25.12': optional: true + '@esbuild/linux-arm@0.27.0': + optional: true + '@esbuild/linux-arm@0.27.1': optional: true @@ -9274,6 +9701,9 @@ snapshots: '@esbuild/linux-ia32@0.25.12': optional: true + '@esbuild/linux-ia32@0.27.0': + optional: true + '@esbuild/linux-ia32@0.27.1': optional: true @@ -9283,6 +9713,9 @@ snapshots: '@esbuild/linux-loong64@0.25.12': optional: true + '@esbuild/linux-loong64@0.27.0': + optional: true + '@esbuild/linux-loong64@0.27.1': optional: true @@ -9292,6 +9725,9 @@ snapshots: '@esbuild/linux-mips64el@0.25.12': optional: true + '@esbuild/linux-mips64el@0.27.0': + optional: true + '@esbuild/linux-mips64el@0.27.1': optional: true @@ -9301,6 +9737,9 @@ snapshots: '@esbuild/linux-ppc64@0.25.12': optional: true + '@esbuild/linux-ppc64@0.27.0': + optional: true + '@esbuild/linux-ppc64@0.27.1': optional: true @@ -9310,6 +9749,9 @@ snapshots: '@esbuild/linux-riscv64@0.25.12': optional: true + '@esbuild/linux-riscv64@0.27.0': + optional: true + '@esbuild/linux-riscv64@0.27.1': optional: true @@ -9319,6 +9761,9 @@ snapshots: '@esbuild/linux-s390x@0.25.12': optional: true + '@esbuild/linux-s390x@0.27.0': + optional: true + '@esbuild/linux-s390x@0.27.1': optional: true @@ -9328,12 +9773,18 @@ snapshots: '@esbuild/linux-x64@0.25.12': optional: true + '@esbuild/linux-x64@0.27.0': + optional: true + '@esbuild/linux-x64@0.27.1': optional: true '@esbuild/netbsd-arm64@0.25.12': optional: true + '@esbuild/netbsd-arm64@0.27.0': + optional: true + '@esbuild/netbsd-arm64@0.27.1': optional: true @@ -9343,12 +9794,18 @@ snapshots: '@esbuild/netbsd-x64@0.25.12': optional: true + '@esbuild/netbsd-x64@0.27.0': + optional: true + '@esbuild/netbsd-x64@0.27.1': optional: true '@esbuild/openbsd-arm64@0.25.12': optional: true + '@esbuild/openbsd-arm64@0.27.0': + optional: true + '@esbuild/openbsd-arm64@0.27.1': optional: true @@ -9358,12 +9815,18 @@ snapshots: '@esbuild/openbsd-x64@0.25.12': optional: true + '@esbuild/openbsd-x64@0.27.0': + optional: true + '@esbuild/openbsd-x64@0.27.1': optional: true '@esbuild/openharmony-arm64@0.25.12': optional: true + '@esbuild/openharmony-arm64@0.27.0': + optional: true + '@esbuild/openharmony-arm64@0.27.1': optional: true @@ -9373,6 +9836,9 @@ snapshots: '@esbuild/sunos-x64@0.25.12': optional: true + '@esbuild/sunos-x64@0.27.0': + optional: true + '@esbuild/sunos-x64@0.27.1': optional: true @@ -9382,6 +9848,9 @@ snapshots: '@esbuild/win32-arm64@0.25.12': optional: true + '@esbuild/win32-arm64@0.27.0': + optional: true + '@esbuild/win32-arm64@0.27.1': optional: true @@ -9391,6 +9860,9 @@ snapshots: '@esbuild/win32-ia32@0.25.12': optional: true + '@esbuild/win32-ia32@0.27.0': + optional: true + '@esbuild/win32-ia32@0.27.1': optional: true @@ -9400,6 +9872,9 @@ snapshots: '@esbuild/win32-x64@0.25.12': optional: true + '@esbuild/win32-x64@0.27.0': + optional: true + '@esbuild/win32-x64@0.27.1': optional: true @@ -9489,8 +9964,7 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} - '@img/colour@1.0.0': - optional: true + '@img/colour@1.0.0': {} '@img/sharp-darwin-arm64@0.34.5': optionalDependencies: @@ -9776,6 +10250,11 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + '@jsonjoy.com/base64@1.1.2(tslib@2.8.1)': dependencies: tslib: 2.8.1 @@ -9920,7 +10399,7 @@ snapshots: ajv: 8.17.1 ajv-formats: 3.0.1(ajv@8.17.1) content-type: 1.0.5 - cors: 2.8.5 + cors: 2.8.6 cross-spawn: 7.0.6 eventsource: 3.0.7 eventsource-parser: 3.0.6 @@ -9931,7 +10410,7 @@ snapshots: pkce-challenge: 5.0.1 raw-body: 3.0.2 zod: 4.1.13 - zod-to-json-schema: 3.25.0(zod@4.1.13) + zod-to-json-schema: 3.25.1(zod@4.1.13) transitivePeerDependencies: - hono - supports-color @@ -10205,6 +10684,18 @@ snapshots: playwright: 1.58.0-alpha-2025-11-30 playwright-core: 1.58.0-alpha-2025-11-30 + '@poppinss/colors@4.1.6': + dependencies: + kleur: 4.1.5 + + '@poppinss/dumper@0.6.5': + dependencies: + '@poppinss/colors': 4.1.6 + '@sindresorhus/is': 7.2.0 + supports-color: 10.2.2 + + '@poppinss/exception@1.2.3': {} + '@radix-ui/colors@3.0.0': {} '@radix-ui/number@1.1.1': {} @@ -11261,6 +11752,10 @@ snapshots: '@shikijs/vscode-textmate@9.3.1': {} + '@sindresorhus/is@7.2.0': {} + + '@speed-highlight/core@1.2.14': {} + '@stablelib/base64@1.0.1': {} '@standard-schema/spec@1.0.0': {} @@ -12117,7 +12612,7 @@ snapshots: auth0@4.37.0: dependencies: jose: 4.15.9 - undici-types: 6.21.0 + undici-types: 6.23.0 uuid: 9.0.1 auth0@5.2.0: @@ -12271,6 +12766,8 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 + blake3-wasm@2.1.5: {} + body-parser@1.20.4: dependencies: bytes: 3.1.2 @@ -12375,6 +12872,9 @@ snapshots: caniuse-lite@1.0.30001760: {} + caniuse-lite@1.0.30001767: + optional: true + ccount@2.0.1: {} chalk@4.1.2: @@ -12472,6 +12972,12 @@ snapshots: client-only@0.0.1: {} + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + clone@1.0.4: {} clsx@2.1.1: {} @@ -12519,6 +13025,15 @@ snapshots: readable-stream: 3.6.2 typedarray: 0.0.6 + concurrently@9.2.1: + dependencies: + chalk: 4.1.2 + rxjs: 7.8.2 + shell-quote: 1.8.3 + supports-color: 8.1.1 + tree-kill: 1.2.2 + yargs: 17.7.2 + consola@3.4.2: {} content-disposition@0.5.4: @@ -12548,6 +13063,11 @@ snapshots: object-assign: 4.1.1 vary: 1.1.2 + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + cosmiconfig@8.3.6(typescript@5.9.3): dependencies: import-fresh: 3.3.1 @@ -12750,6 +13270,8 @@ snapshots: dependencies: is-arrayish: 0.2.1 + error-stack-parser-es@1.0.5: {} + es-abstract@1.24.0: dependencies: array-buffer-byte-length: 1.0.2 @@ -12922,6 +13444,35 @@ snapshots: '@esbuild/win32-ia32': 0.25.12 '@esbuild/win32-x64': 0.25.12 + esbuild@0.27.0: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.0 + '@esbuild/android-arm': 0.27.0 + '@esbuild/android-arm64': 0.27.0 + '@esbuild/android-x64': 0.27.0 + '@esbuild/darwin-arm64': 0.27.0 + '@esbuild/darwin-x64': 0.27.0 + '@esbuild/freebsd-arm64': 0.27.0 + '@esbuild/freebsd-x64': 0.27.0 + '@esbuild/linux-arm': 0.27.0 + '@esbuild/linux-arm64': 0.27.0 + '@esbuild/linux-ia32': 0.27.0 + '@esbuild/linux-loong64': 0.27.0 + '@esbuild/linux-mips64el': 0.27.0 + '@esbuild/linux-ppc64': 0.27.0 + '@esbuild/linux-riscv64': 0.27.0 + '@esbuild/linux-s390x': 0.27.0 + '@esbuild/linux-x64': 0.27.0 + '@esbuild/netbsd-arm64': 0.27.0 + '@esbuild/netbsd-x64': 0.27.0 + '@esbuild/openbsd-arm64': 0.27.0 + '@esbuild/openbsd-x64': 0.27.0 + '@esbuild/openharmony-arm64': 0.27.0 + '@esbuild/sunos-x64': 0.27.0 + '@esbuild/win32-arm64': 0.27.0 + '@esbuild/win32-ia32': 0.27.0 + '@esbuild/win32-x64': 0.27.0 + esbuild@0.27.1: optionalDependencies: '@esbuild/aix-ppc64': 0.27.1 @@ -12968,7 +13519,7 @@ snapshots: eslint: 9.39.1(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-react: 7.37.5(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-react-hooks: 5.2.0(eslint@9.39.1(jiti@2.6.1)) @@ -13043,7 +13594,7 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)) transitivePeerDependencies: - supports-color @@ -13098,7 +13649,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -13688,6 +14239,8 @@ snapshots: gensync@1.0.0-beta.2: {} + get-caller-file@2.0.5: {} + get-east-asian-width@1.4.0: {} get-intrinsic@1.3.0: @@ -14422,6 +14975,8 @@ snapshots: kind-of@6.0.3: {} + kleur@4.1.5: {} + kysely@0.28.8: {} language-subtag-registry@0.3.23: {} @@ -15121,6 +15676,18 @@ snapshots: mimic-function@5.0.1: {} + miniflare@4.20260131.0: + dependencies: + '@cspotcode/source-map-support': 0.8.1 + sharp: 0.34.5 + undici: 7.18.2 + workerd: 1.20260131.0 + ws: 8.18.0 + youch: 4.1.0-beta.10 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + minimatch@10.1.1: dependencies: '@isaacs/brace-expansion': 5.0.0 @@ -15224,7 +15791,7 @@ snapshots: dependencies: '@next/env': 15.5.9 '@swc/helpers': 0.5.15 - caniuse-lite: 1.0.30001760 + caniuse-lite: 1.0.30001767 postcss: 8.4.31 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -15249,7 +15816,7 @@ snapshots: dependencies: '@next/env': 15.5.9 '@swc/helpers': 0.5.15 - caniuse-lite: 1.0.30001760 + caniuse-lite: 1.0.30001767 postcss: 8.4.31 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) @@ -15486,10 +16053,14 @@ snapshots: path-to-regexp@0.1.12: {} + path-to-regexp@6.3.0: {} + path-to-regexp@8.3.0: {} path-type@4.0.0: {} + pathe@2.0.3: {} + pg-cloudflare@1.2.7: optional: true @@ -16058,6 +16629,8 @@ snapshots: transitivePeerDependencies: - supports-color + require-directory@2.1.1: {} + require-from-string@2.0.2: {} resolve-from@4.0.0: {} @@ -16358,7 +16931,6 @@ snapshots: '@img/sharp-win32-arm64': 0.34.5 '@img/sharp-win32-ia32': 0.34.5 '@img/sharp-win32-x64': 0.34.5 - optional: true shebang-command@2.0.0: dependencies: @@ -16366,6 +16938,8 @@ snapshots: shebang-regex@3.0.0: {} + shell-quote@1.8.3: {} + shiki@1.17.7: dependencies: '@shikijs/core': 1.17.7 @@ -16620,6 +17194,8 @@ snapshots: sugar-high@0.9.5: {} + supports-color@10.2.2: {} + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -16786,6 +17362,8 @@ snapshots: dependencies: tslib: 2.8.1 + tree-kill@1.2.2: {} + trim-lines@3.0.1: {} troika-three-text@0.52.4(three@0.178.0): @@ -16981,6 +17559,14 @@ snapshots: undici-types@6.21.0: {} + undici-types@6.23.0: {} + + undici@7.18.2: {} + + unenv@2.0.0-rc.24: + dependencies: + pathe: 2.0.3 + unified@11.0.5: dependencies: '@types/unist': 3.0.3 @@ -17290,6 +17876,31 @@ snapshots: word-wrap@1.2.5: {} + workerd@1.20260131.0: + optionalDependencies: + '@cloudflare/workerd-darwin-64': 1.20260131.0 + '@cloudflare/workerd-darwin-arm64': 1.20260131.0 + '@cloudflare/workerd-linux-64': 1.20260131.0 + '@cloudflare/workerd-linux-arm64': 1.20260131.0 + '@cloudflare/workerd-windows-64': 1.20260131.0 + + wrangler@4.62.0(@cloudflare/workers-types@4.20260127.0): + dependencies: + '@cloudflare/kv-asset-handler': 0.4.2 + '@cloudflare/unenv-preset': 2.12.0(unenv@2.0.0-rc.24)(workerd@1.20260131.0) + blake3-wasm: 2.1.5 + esbuild: 0.27.0 + miniflare: 4.20260131.0 + path-to-regexp: 6.3.0 + unenv: 2.0.0-rc.24 + workerd: 1.20260131.0 + optionalDependencies: + '@cloudflare/workers-types': 4.20260127.0 + fsevents: 2.3.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + wrap-ansi@6.2.0: dependencies: ansi-styles: 4.3.0 @@ -17316,6 +17927,8 @@ snapshots: wrappy@1.0.2: {} + ws@8.18.0: {} + xmcp@0.5.7(@swc/helpers@0.5.15)(hono@4.11.1)(postcss@8.5.6)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.104.1)(zod@4.1.13): dependencies: '@modelcontextprotocol/sdk': 1.25.2(hono@4.11.1)(zod@4.1.13) @@ -17354,28 +17967,9 @@ snapshots: - supports-color - webpack - xmcp@0.5.8(@swc/helpers@0.5.15)(hono@4.11.1)(postcss@8.5.6)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.104.1)(zod@4.1.13): - dependencies: - '@modelcontextprotocol/sdk': 1.25.2(hono@4.11.1)(zod@4.1.13) - '@rspack/core': 1.6.7(@swc/helpers@0.5.15) - postcss-loader: 8.2.0(@rspack/core@1.6.7(@swc/helpers@0.5.15))(postcss@8.5.6)(typescript@5.9.3)(webpack@5.104.1) - ts-checker-rspack-plugin: 1.2.1(@rspack/core@1.6.7(@swc/helpers@0.5.15))(typescript@5.9.3) - typescript: 5.9.3 - zod: 4.1.13 - optionalDependencies: - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) - transitivePeerDependencies: - - '@cfworker/json-schema' - - '@swc/helpers' - - hono - - postcss - - supports-color - - webpack - - xmcp@0.6.0(@swc/helpers@0.5.15)(hono@4.11.1)(postcss@8.5.6)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.104.1(esbuild@0.27.1))(zod@4.1.13): + xmcp@0.6.2(@swc/helpers@0.5.15)(postcss@8.5.6)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.104.1(esbuild@0.27.1))(zod@4.1.13): dependencies: - '@modelcontextprotocol/sdk': 1.25.2(hono@4.11.1)(zod@4.1.13) + '@modelcontextprotocol/sdk': 1.25.3(hono@4.11.1)(zod@4.1.13) '@rspack/core': 1.6.7(@swc/helpers@0.5.15) postcss-loader: 8.2.0(@rspack/core@1.6.7(@swc/helpers@0.5.15))(postcss@8.5.6)(typescript@5.9.3)(webpack@5.104.1(esbuild@0.27.1)) ts-checker-rspack-plugin: 1.2.1(@rspack/core@1.6.7(@swc/helpers@0.5.15))(typescript@5.9.3) @@ -17392,9 +17986,9 @@ snapshots: - supports-color - webpack - xmcp@0.6.0(@swc/helpers@0.5.15)(hono@4.11.1)(postcss@8.5.6)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.104.1)(zod@4.1.13): + xmcp@0.6.2(@swc/helpers@0.5.15)(postcss@8.5.6)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.104.1)(zod@4.1.13): dependencies: - '@modelcontextprotocol/sdk': 1.25.2(hono@4.11.1)(zod@4.1.13) + '@modelcontextprotocol/sdk': 1.25.3(hono@4.11.1)(zod@4.1.13) '@rspack/core': 1.6.7(@swc/helpers@0.5.15) postcss-loader: 8.2.0(@rspack/core@1.6.7(@swc/helpers@0.5.15))(postcss@8.5.6)(typescript@5.9.3)(webpack@5.104.1) ts-checker-rspack-plugin: 1.2.1(@rspack/core@1.6.7(@swc/helpers@0.5.15))(typescript@5.9.3) @@ -17415,6 +18009,8 @@ snapshots: xtend@4.0.2: {} + y18n@5.0.8: {} + yallist@3.1.1: {} yallist@5.0.0: {} @@ -17423,10 +18019,33 @@ snapshots: yargs-parser@21.1.1: {} + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + yocto-queue@0.1.0: {} yoctocolors-cjs@2.1.3: {} + youch-core@0.3.3: + dependencies: + '@poppinss/exception': 1.2.3 + error-stack-parser-es: 1.0.5 + + youch@4.1.0-beta.10: + dependencies: + '@poppinss/colors': 4.1.6 + '@poppinss/dumper': 0.6.5 + '@speed-highlight/core': 1.2.14 + cookie: 1.1.1 + youch-core: 0.3.3 + zod-to-json-schema@3.25.0(zod@3.25.76): dependencies: zod: 3.25.76 @@ -17435,6 +18054,10 @@ snapshots: dependencies: zod: 4.1.13 + zod-to-json-schema@3.25.1(zod@4.1.13): + dependencies: + zod: 4.1.13 + zod@3.25.76: {} zod@4.1.13: {}