@kya-os/mcp-i 1.2.10 → 1.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Cloudflare KV-based nonce cache implementation for Workers
3
+ *
4
+ * This cache prevents replay attacks by tracking used nonces in Cloudflare KV.
5
+ * Each nonce is stored with a TTL matching the session timeout.
6
+ */
7
+ import type { NonceCache } from "@kya-os/contracts/handshake";
8
+ /**
9
+ * Cloudflare KV namespace interface
10
+ */
11
+ export interface KVNamespace {
12
+ get(key: string, options?: {
13
+ type?: "text" | "json" | "arrayBuffer" | "stream";
14
+ }): Promise<any>;
15
+ put(key: string, value: string | ArrayBuffer | ReadableStream, options?: {
16
+ expiration?: number;
17
+ expirationTtl?: number;
18
+ }): Promise<void>;
19
+ delete(key: string): Promise<void>;
20
+ }
21
+ /**
22
+ * Configuration for Cloudflare KV nonce cache
23
+ */
24
+ export interface CloudflareKVNonceCacheConfig {
25
+ /**
26
+ * KV namespace binding from Worker environment
27
+ */
28
+ namespace: KVNamespace;
29
+ /**
30
+ * TTL for nonce entries in seconds
31
+ * Should match or exceed session timeout
32
+ * @default 1800 (30 minutes)
33
+ */
34
+ ttl?: number;
35
+ /**
36
+ * Key prefix for nonce entries
37
+ * @default "nonce:"
38
+ */
39
+ keyPrefix?: string;
40
+ }
41
+ /**
42
+ * Cloudflare KV nonce cache for Workers
43
+ *
44
+ * Usage in Worker:
45
+ * ```typescript
46
+ * export interface Env {
47
+ * NONCE_CACHE: KVNamespace;
48
+ * }
49
+ *
50
+ * export default {
51
+ * async fetch(request: Request, env: Env): Promise<Response> {
52
+ * const nonceCache = new CloudflareKVNonceCache({
53
+ * namespace: env.NONCE_CACHE,
54
+ * ttl: 1800, // 30 minutes
55
+ * });
56
+ *
57
+ * // Use with verifier or MCP-I runtime
58
+ * const runtime = new MCPIRuntime({
59
+ * nonce: { cache: nonceCache }
60
+ * });
61
+ * }
62
+ * }
63
+ * ```
64
+ */
65
+ export declare class CloudflareKVNonceCache implements NonceCache {
66
+ private kv;
67
+ private ttl;
68
+ private keyPrefix;
69
+ constructor(config: CloudflareKVNonceCacheConfig);
70
+ /**
71
+ * Check if a nonce exists in the cache
72
+ *
73
+ * @param nonce - The nonce to check
74
+ * @returns Promise<boolean> - true if exists, false if not
75
+ */
76
+ has(nonce: string): Promise<boolean>;
77
+ /**
78
+ * Add a nonce to the cache with TTL
79
+ * Implements atomic add-if-absent semantics for replay prevention
80
+ *
81
+ * @param nonce - The nonce to add
82
+ * @param ttl - Time-to-live in seconds
83
+ */
84
+ add(nonce: string, ttl: number): Promise<void>;
85
+ /**
86
+ * Cleanup expired nonces
87
+ * Note: Cloudflare KV handles expiration automatically via TTL
88
+ */
89
+ cleanup(): Promise<void>;
90
+ }
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ /**
3
+ * Cloudflare KV-based nonce cache implementation for Workers
4
+ *
5
+ * This cache prevents replay attacks by tracking used nonces in Cloudflare KV.
6
+ * Each nonce is stored with a TTL matching the session timeout.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.CloudflareKVNonceCache = void 0;
10
+ /**
11
+ * Cloudflare KV nonce cache for Workers
12
+ *
13
+ * Usage in Worker:
14
+ * ```typescript
15
+ * export interface Env {
16
+ * NONCE_CACHE: KVNamespace;
17
+ * }
18
+ *
19
+ * export default {
20
+ * async fetch(request: Request, env: Env): Promise<Response> {
21
+ * const nonceCache = new CloudflareKVNonceCache({
22
+ * namespace: env.NONCE_CACHE,
23
+ * ttl: 1800, // 30 minutes
24
+ * });
25
+ *
26
+ * // Use with verifier or MCP-I runtime
27
+ * const runtime = new MCPIRuntime({
28
+ * nonce: { cache: nonceCache }
29
+ * });
30
+ * }
31
+ * }
32
+ * ```
33
+ */
34
+ class CloudflareKVNonceCache {
35
+ kv;
36
+ ttl;
37
+ keyPrefix;
38
+ constructor(config) {
39
+ this.kv = config.namespace;
40
+ this.ttl = config.ttl ?? 1800; // 30 minutes default
41
+ this.keyPrefix = config.keyPrefix ?? "nonce:";
42
+ }
43
+ /**
44
+ * Check if a nonce exists in the cache
45
+ *
46
+ * @param nonce - The nonce to check
47
+ * @returns Promise<boolean> - true if exists, false if not
48
+ */
49
+ async has(nonce) {
50
+ const key = this.keyPrefix + nonce;
51
+ try {
52
+ const existing = await this.kv.get(key, { type: "text" });
53
+ return existing !== null;
54
+ }
55
+ catch (error) {
56
+ console.error("CloudflareKVNonceCache.has error:", error);
57
+ // On error, assume nonce exists (fail closed for security)
58
+ return true;
59
+ }
60
+ }
61
+ /**
62
+ * Add a nonce to the cache with TTL
63
+ * Implements atomic add-if-absent semantics for replay prevention
64
+ *
65
+ * @param nonce - The nonce to add
66
+ * @param ttl - Time-to-live in seconds
67
+ */
68
+ async add(nonce, ttl) {
69
+ const key = this.keyPrefix + nonce;
70
+ try {
71
+ // Check if nonce already exists
72
+ const existing = await this.kv.get(key, { type: "text" });
73
+ if (existing !== null) {
74
+ throw new Error(`Nonce ${nonce} already exists (replay attack detected)`);
75
+ }
76
+ // Add nonce with TTL
77
+ await this.kv.put(key, new Date().toISOString(), {
78
+ expirationTtl: ttl,
79
+ });
80
+ }
81
+ catch (error) {
82
+ if (error instanceof Error && error.message.includes("already exists")) {
83
+ throw error;
84
+ }
85
+ console.error("CloudflareKVNonceCache.add error:", error);
86
+ throw new Error("Failed to add nonce to cache");
87
+ }
88
+ }
89
+ /**
90
+ * Cleanup expired nonces
91
+ * Note: Cloudflare KV handles expiration automatically via TTL
92
+ */
93
+ async cleanup() {
94
+ // KV automatically expires entries based on TTL
95
+ // No manual cleanup needed
96
+ }
97
+ }
98
+ exports.CloudflareKVNonceCache = CloudflareKVNonceCache;
@@ -12,5 +12,5 @@ export { SessionContextSchema, HandshakeRequestSchema, NonceCacheEntrySchema, No
12
12
  export { MemoryNonceCache } from "./memory-nonce-cache";
13
13
  export { RedisNonceCache } from "./redis-nonce-cache";
14
14
  export { DynamoNonceCache } from "./dynamodb-nonce-cache";
15
- export { CloudflareKVNonceCache } from "./cloudflare-kv-nonce-cache";
15
+ export { CloudflareKVNonceCache, type KVNamespace, type CloudflareKVNonceCacheConfig } from "./cloudflare-kv";
16
16
  export { createNonceCache, createNonceCacheWithConfig, detectCacheType, type NonceCacheOptions, } from "./nonce-cache-factory";
@@ -23,8 +23,8 @@ var redis_nonce_cache_1 = require("./redis-nonce-cache");
23
23
  Object.defineProperty(exports, "RedisNonceCache", { enumerable: true, get: function () { return redis_nonce_cache_1.RedisNonceCache; } });
24
24
  var dynamodb_nonce_cache_1 = require("./dynamodb-nonce-cache");
25
25
  Object.defineProperty(exports, "DynamoNonceCache", { enumerable: true, get: function () { return dynamodb_nonce_cache_1.DynamoNonceCache; } });
26
- var cloudflare_kv_nonce_cache_1 = require("./cloudflare-kv-nonce-cache");
27
- Object.defineProperty(exports, "CloudflareKVNonceCache", { enumerable: true, get: function () { return cloudflare_kv_nonce_cache_1.CloudflareKVNonceCache; } });
26
+ var cloudflare_kv_1 = require("./cloudflare-kv");
27
+ Object.defineProperty(exports, "CloudflareKVNonceCache", { enumerable: true, get: function () { return cloudflare_kv_1.CloudflareKVNonceCache; } });
28
28
  // Factory and auto-detection
29
29
  var nonce_cache_factory_1 = require("./nonce-cache-factory");
30
30
  Object.defineProperty(exports, "createNonceCache", { enumerable: true, get: function () { return nonce_cache_factory_1.createNonceCache; } });
@@ -4,25 +4,29 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.getEntries = getEntries;
7
- const constants_1 = require("../../utils/constants");
8
7
  const path_1 = __importDefault(require("path"));
9
8
  /** Get what packages are gonna be built by xmcp */
10
9
  function getEntries(xmcpConfig) {
11
10
  const entries = {};
11
+ // Point to SOURCE files in the mcp-i package, not pre-built runtime files
12
+ // This allows webpack to compile them directly without double-wrapping
13
+ // Cache bust: Updated adapter paths to correct locations (2025-10-09)
14
+ const mcpiPackageRoot = path_1.default.resolve(__dirname, "../../..");
15
+ const runtimeSourcePath = path_1.default.join(mcpiPackageRoot, "src/runtime");
12
16
  if (xmcpConfig.stdio) {
13
- entries.stdio = path_1.default.join(constants_1.runtimeFolderPath, "stdio.js");
17
+ entries.stdio = path_1.default.join(runtimeSourcePath, "transports/stdio/index.ts");
14
18
  }
15
19
  if (xmcpConfig["http"]) {
16
20
  // non adapter mode
17
21
  if (!xmcpConfig.experimental?.adapter) {
18
- entries["http"] = path_1.default.join(constants_1.runtimeFolderPath, "http.js");
22
+ entries["http"] = path_1.default.join(runtimeSourcePath, "transports/http/index.ts");
19
23
  }
20
24
  // adapter mode enabled
21
25
  if (xmcpConfig.experimental?.adapter === "express") {
22
- entries["adapter"] = path_1.default.join(constants_1.runtimeFolderPath, "adapter-express.js");
26
+ entries["adapter"] = path_1.default.join(runtimeSourcePath, "adapters/express/index.ts");
23
27
  }
24
28
  if (xmcpConfig.experimental?.adapter === "nextjs") {
25
- entries["adapter"] = path_1.default.join(constants_1.runtimeFolderPath, "adapter-nextjs.js");
29
+ entries["adapter"] = path_1.default.join(runtimeSourcePath, "adapters/nextjs/index.ts");
26
30
  }
27
31
  }
28
32
  return entries;
@@ -1,7 +1,9 @@
1
1
  import { Configuration } from "webpack";
2
2
  /**
3
- * This function will decide is a file is bundled by xmcp compiler or not.
4
- * We want to avoid building node modules.
5
- * When using Next.js, we want to avoid building tools/*, since the nexjs compiler will handle that code.
3
+ * This function will decide if a file is bundled by xmcp compiler or not.
4
+ * - Built-in Node modules: externalized
5
+ * - npm packages: externalized
6
+ * - User code (tools, etc): bundled
7
+ * For adapters, tool files must be bundled so they're available at runtime.
6
8
  */
7
9
  export declare function getExternals(): Configuration["externals"];
@@ -5,13 +5,14 @@ const compiler_context_1 = require("../compiler-context");
5
5
  const module_1 = require("module");
6
6
  const plugins_1 = require("./plugins");
7
7
  /**
8
- * This function will decide is a file is bundled by xmcp compiler or not.
9
- * We want to avoid building node modules.
10
- * When using Next.js, we want to avoid building tools/*, since the nexjs compiler will handle that code.
8
+ * This function will decide if a file is bundled by xmcp compiler or not.
9
+ * - Built-in Node modules: externalized
10
+ * - npm packages: externalized
11
+ * - User code (tools, etc): bundled
12
+ * For adapters, tool files must be bundled so they're available at runtime.
11
13
  */
12
14
  function getExternals() {
13
15
  const xmcpConfig = (0, compiler_context_1.getXmcpConfig)();
14
- const replacedImports = new Set();
15
16
  return [
16
17
  function (data, callback) {
17
18
  const { request } = data;
@@ -60,27 +61,19 @@ function getExternals() {
60
61
  }
61
62
  }
62
63
  /**
63
- * When using Next.js, we want them to bundle the code for the tool file,
64
- * so just keep the reference for the import as external
64
+ * For adapters (Next.js, Express), bundle tool files into the adapter
65
+ * so they're available when the adapter runs at runtime.
66
+ * Only externalize npm packages - let webpack bundle user code.
65
67
  */
66
- if (xmcpConfig.experimental?.adapter === "nextjs") {
67
- // Bundle imports from the same folder
68
- if (request.startsWith("./")) {
68
+ if (xmcpConfig.experimental?.adapter) {
69
+ // Bundle local/relative imports (tool files, etc)
70
+ if (request.startsWith("./") || request.startsWith("../")) {
69
71
  return callback();
70
72
  }
71
- let pathRequest = request;
72
- /**
73
- * Paths are relative to the .mcpi/nextjs-adapter folder,
74
- * but we are building in .mcpi/adapter/index.js, so we need to go up 2 levels
75
- */
76
- if (request.startsWith("../")) {
77
- // Only replace the import if it hasn't been replaced yet
78
- if (!replacedImports.has(request)) {
79
- pathRequest = pathRequest.replace("../", "../../");
80
- replacedImports.add(pathRequest);
81
- }
73
+ // Externalize npm packages only
74
+ if (!request.startsWith(".") && !request.startsWith("/")) {
75
+ return callback(null, `commonjs ${request}`);
82
76
  }
83
- return callback(null, `commonjs ${pathRequest}`);
84
77
  }
85
78
  callback();
86
79
  },
@@ -61,9 +61,13 @@ class InjectRuntimePlugin {
61
61
  if (hasRun)
62
62
  return;
63
63
  hasRun = true;
64
+ // Only copy headers.js - transport files are now compiled from source
65
+ // to avoid double-wrapping issues with webpack
64
66
  for (const [fileName, fileContent] of Object.entries(exports.runtimeFiles)) {
65
- const targetPath = path_1.default.join(constants_1.runtimeFolderPath, fileName);
66
- fs_extra_1.default.writeFileSync(targetPath, fileContent);
67
+ if (fileName === "headers.js") {
68
+ const targetPath = path_1.default.join(constants_1.runtimeFolderPath, fileName);
69
+ fs_extra_1.default.writeFileSync(targetPath, fileContent);
70
+ }
67
71
  }
68
72
  });
69
73
  }
@@ -102,16 +106,10 @@ class CreateTypeDefinitionPlugin {
102
106
  return;
103
107
  hasRun = true;
104
108
  const xmcpConfig = (0, compiler_context_1.getXmcpConfig)();
105
- // Manually type the .mcpi/adapter/index.js file using a .mcpi/adapter/index.d.ts file
106
- // TO DO add withAuth to the type definition & AuthConfig
107
- if (xmcpConfig.experimental?.adapter) {
108
- let typeDefinitionContent = "";
109
- if (xmcpConfig.experimental?.adapter == "nextjs") {
110
- typeDefinitionContent = nextJsTypeDefinition;
111
- }
112
- else if (xmcpConfig.experimental?.adapter == "express") {
113
- typeDefinitionContent = expressTypeDefinition;
114
- }
109
+ // For Express adapters, create type definitions
110
+ // Next.js doesn't need .d.ts files as it causes webpack conflicts
111
+ if (xmcpConfig.experimental?.adapter === "express") {
112
+ const typeDefinitionContent = expressTypeDefinition;
115
113
  fs_extra_1.default.writeFileSync(path_1.default.join(constants_1.adapterOutputPath, "index.d.ts"), typeDefinitionContent);
116
114
  }
117
115
  });