@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.
- package/dist/cache/cloudflare-kv.d.ts +90 -0
- package/dist/cache/cloudflare-kv.js +98 -0
- package/dist/cache/index.d.ts +1 -1
- package/dist/cache/index.js +2 -2
- package/dist/compiler/get-webpack-config/get-entries.js +9 -5
- package/dist/compiler/get-webpack-config/get-externals.d.ts +5 -3
- package/dist/compiler/get-webpack-config/get-externals.js +14 -21
- package/dist/compiler/get-webpack-config/plugins.js +10 -12
- package/dist/runtime/adapter-express.js +1 -1
- package/dist/runtime/adapter-nextjs.js +1 -1
- package/dist/runtime/adapter-nextjs.js.LICENSE.txt +0 -6
- package/dist/runtime/http.js +1 -1
- package/dist/runtime/stdio.js +1 -1
- package/package.json +1 -1
|
@@ -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;
|
package/dist/cache/index.d.ts
CHANGED
|
@@ -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
|
|
15
|
+
export { CloudflareKVNonceCache, type KVNamespace, type CloudflareKVNonceCacheConfig } from "./cloudflare-kv";
|
|
16
16
|
export { createNonceCache, createNonceCacheWithConfig, detectCacheType, type NonceCacheOptions, } from "./nonce-cache-factory";
|
package/dist/cache/index.js
CHANGED
|
@@ -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
|
|
27
|
-
Object.defineProperty(exports, "CloudflareKVNonceCache", { enumerable: true, get: function () { return
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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
|
-
*
|
|
64
|
-
* so
|
|
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
|
|
67
|
-
// Bundle imports
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
66
|
-
|
|
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
|
-
//
|
|
106
|
-
//
|
|
107
|
-
if (xmcpConfig.experimental?.adapter) {
|
|
108
|
-
|
|
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
|
});
|