@kya-os/mcp-i 0.1.0 → 1.2.1
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/README.md +406 -71
- package/dist/149.js +1 -0
- package/dist/189.js +1 -0
- package/dist/261.js +1 -0
- package/dist/28.js +1 -0
- package/dist/295.js +1 -0
- package/dist/460.js +1 -0
- package/dist/570.js +1 -0
- package/dist/634.js +1 -0
- package/dist/647.js +1 -0
- package/dist/67.js +1 -0
- package/dist/739.js +1 -0
- package/dist/742.js +1 -0
- package/dist/904.js +1 -0
- package/dist/938.js +1 -0
- package/dist/auth/api-key.d.ts +16 -0
- package/dist/auth/api-key.js +82 -0
- package/dist/auth/jwt.d.ts +43 -0
- package/dist/auth/jwt.js +51 -0
- package/dist/auth/oauth/factory.d.ts +12 -0
- package/dist/auth/oauth/factory.js +36 -0
- package/dist/auth/oauth/index.d.ts +5 -0
- package/dist/auth/oauth/index.js +27 -0
- package/dist/auth/oauth/providers/proxy-provider.d.ts +13 -0
- package/dist/auth/oauth/providers/proxy-provider.js +159 -0
- package/dist/auth/oauth/router.d.ts +4 -0
- package/dist/auth/oauth/router.js +294 -0
- package/dist/auth/oauth/storage/memory-storage.d.ts +12 -0
- package/dist/auth/oauth/storage/memory-storage.js +40 -0
- package/dist/auth/oauth/types.d.ts +112 -0
- package/dist/auth/oauth/types.js +2 -0
- package/dist/cache/__tests__/cloudflare-kv-nonce-cache.test.d.ts +4 -0
- package/dist/cache/__tests__/cloudflare-kv-nonce-cache.test.js +176 -0
- package/dist/cache/__tests__/concurrency.test.d.ts +5 -0
- package/dist/cache/__tests__/concurrency.test.js +300 -0
- package/dist/cache/__tests__/dynamodb-nonce-cache.test.d.ts +4 -0
- package/dist/cache/__tests__/dynamodb-nonce-cache.test.js +176 -0
- package/dist/cache/__tests__/memory-nonce-cache.test.d.ts +4 -0
- package/dist/cache/__tests__/memory-nonce-cache.test.js +132 -0
- package/dist/cache/__tests__/nonce-cache-factory-simple.test.d.ts +4 -0
- package/dist/cache/__tests__/nonce-cache-factory-simple.test.js +133 -0
- package/dist/cache/__tests__/nonce-cache-factory.test.d.ts +4 -0
- package/dist/cache/__tests__/nonce-cache-factory.test.js +252 -0
- package/dist/cache/__tests__/redis-nonce-cache.test.d.ts +4 -0
- package/dist/cache/__tests__/redis-nonce-cache.test.js +95 -0
- package/dist/cache/cloudflare-kv-nonce-cache.d.ts +14 -0
- package/dist/cache/cloudflare-kv-nonce-cache.js +93 -0
- package/dist/cache/dynamodb-nonce-cache.d.ts +15 -0
- package/dist/cache/dynamodb-nonce-cache.js +92 -0
- package/dist/cache/index.d.ts +16 -0
- package/dist/cache/index.js +32 -0
- package/dist/cache/memory-nonce-cache.d.ts +44 -0
- package/dist/cache/memory-nonce-cache.js +105 -0
- package/dist/cache/nonce-cache-factory.d.ts +20 -0
- package/dist/cache/nonce-cache-factory.js +208 -0
- package/dist/cache/redis-nonce-cache.d.ts +14 -0
- package/dist/cache/redis-nonce-cache.js +53 -0
- package/dist/compiler/compiler-context.d.ts +23 -0
- package/dist/compiler/compiler-context.js +24 -0
- package/dist/compiler/config/constants.d.ts +41 -0
- package/dist/compiler/config/constants.js +45 -0
- package/dist/compiler/config/index.d.ts +252 -0
- package/dist/compiler/config/index.js +15 -0
- package/dist/compiler/config/injection.d.ts +26 -0
- package/dist/compiler/config/injection.js +58 -0
- package/dist/compiler/config/schemas/experimental/index.d.ts +91 -0
- package/dist/compiler/config/schemas/experimental/index.js +16 -0
- package/dist/compiler/config/schemas/experimental/oauth.d.ts +74 -0
- package/dist/compiler/config/schemas/experimental/oauth.js +25 -0
- package/dist/compiler/config/schemas/index.d.ts +6 -0
- package/dist/compiler/config/schemas/index.js +17 -0
- package/dist/compiler/config/schemas/paths.d.ts +9 -0
- package/dist/compiler/config/schemas/paths.js +12 -0
- package/dist/compiler/config/schemas/transport/http.d.ts +82 -0
- package/dist/compiler/config/schemas/transport/http.js +33 -0
- package/dist/compiler/config/schemas/transport/stdio.d.ts +9 -0
- package/dist/compiler/config/schemas/transport/stdio.js +15 -0
- package/dist/compiler/config/schemas/webpack.d.ts +3 -0
- package/dist/compiler/config/schemas/webpack.js +15 -0
- package/dist/compiler/config/types.d.ts +1 -0
- package/dist/compiler/config/types.js +2 -0
- package/dist/compiler/config/utils.d.ts +20 -0
- package/dist/compiler/config/utils.js +36 -0
- package/dist/compiler/generate-env-code.d.ts +1 -0
- package/dist/compiler/generate-env-code.js +8 -0
- package/dist/compiler/generate-import-code.d.ts +1 -0
- package/dist/compiler/generate-import-code.js +24 -0
- package/dist/compiler/get-webpack-config/get-entries.d.ts +3 -0
- package/dist/compiler/get-webpack-config/get-entries.js +29 -0
- package/dist/compiler/get-webpack-config/get-externals.d.ts +7 -0
- package/dist/compiler/get-webpack-config/get-externals.js +88 -0
- package/dist/compiler/get-webpack-config/get-injected-variables.d.ts +8 -0
- package/dist/compiler/get-webpack-config/get-injected-variables.js +25 -0
- package/dist/compiler/get-webpack-config/index.d.ts +4 -0
- package/dist/compiler/get-webpack-config/index.js +101 -0
- package/dist/compiler/get-webpack-config/plugins.d.ts +8 -0
- package/dist/compiler/get-webpack-config/plugins.js +132 -0
- package/dist/compiler/get-webpack-config/resolve-tsconfig-paths.d.ts +9 -0
- package/dist/compiler/get-webpack-config/resolve-tsconfig-paths.js +40 -0
- package/dist/compiler/index.d.ts +6 -0
- package/dist/compiler/index.js +194 -0
- package/dist/compiler/on-first-build.d.ts +3 -0
- package/dist/compiler/on-first-build.js +58 -0
- package/dist/compiler/parse-xmcp-config.d.ts +9 -0
- package/dist/compiler/parse-xmcp-config.js +155 -0
- package/dist/compiler/start-http-server.d.ts +1 -0
- package/dist/compiler/start-http-server.js +34 -0
- package/dist/index.d.ts +12 -54
- package/dist/index.js +22 -190
- package/dist/index.js.LICENSE.txt +49 -0
- package/dist/runtime/__tests__/audit.test.d.ts +4 -0
- package/dist/runtime/__tests__/audit.test.js +328 -0
- package/dist/runtime/__tests__/identity.test.d.ts +4 -0
- package/dist/runtime/__tests__/identity.test.js +164 -0
- package/dist/runtime/__tests__/mcpi-runtime.test.d.ts +4 -0
- package/dist/runtime/__tests__/mcpi-runtime.test.js +372 -0
- package/dist/runtime/__tests__/proof.test.d.ts +4 -0
- package/dist/runtime/__tests__/proof.test.js +302 -0
- package/dist/runtime/__tests__/session.test.d.ts +4 -0
- package/dist/runtime/__tests__/session.test.js +254 -0
- package/dist/runtime/__tests__/well-known.test.d.ts +4 -0
- package/dist/runtime/__tests__/well-known.test.js +312 -0
- package/dist/runtime/adapter-express.js +2 -0
- package/dist/runtime/adapter-express.js.LICENSE.txt +252 -0
- package/dist/runtime/adapter-nextjs.js +2 -0
- package/dist/runtime/adapter-nextjs.js.LICENSE.txt +53 -0
- package/dist/runtime/adapters/express/index.d.ts +2 -0
- package/dist/runtime/adapters/express/index.js +48 -0
- package/dist/runtime/adapters/nextjs/index.d.ts +8 -0
- package/dist/runtime/adapters/nextjs/index.js +18 -0
- package/dist/runtime/audit.d.ts +93 -0
- package/dist/runtime/audit.js +212 -0
- package/dist/runtime/debug.d.ts +118 -0
- package/dist/runtime/debug.js +612 -0
- package/dist/runtime/delegation-hooks.d.ts +85 -0
- package/dist/runtime/delegation-hooks.js +116 -0
- package/dist/runtime/demo.d.ts +71 -0
- package/dist/runtime/demo.js +135 -0
- package/dist/runtime/headers.d.ts +1 -0
- package/dist/runtime/headers.js +9 -0
- package/dist/runtime/http.js +2 -0
- package/dist/runtime/http.js.LICENSE.txt +252 -0
- package/dist/runtime/identity.d.ts +105 -0
- package/dist/runtime/identity.js +232 -0
- package/dist/runtime/index.d.ts +16 -0
- package/dist/runtime/index.js +56 -0
- package/dist/runtime/mcpi-runtime.d.ts +164 -0
- package/dist/runtime/mcpi-runtime.js +352 -0
- package/dist/runtime/proof.d.ts +87 -0
- package/dist/runtime/proof.js +223 -0
- package/dist/runtime/session.d.ts +88 -0
- package/dist/runtime/session.js +216 -0
- package/dist/runtime/stdio.js +2 -0
- package/dist/runtime/stdio.js.LICENSE.txt +1 -0
- package/dist/runtime/templates/home.d.ts +2 -0
- package/dist/runtime/templates/home.js +50 -0
- package/dist/runtime/transports/http/base-streamable-http.d.ts +25 -0
- package/dist/runtime/transports/http/base-streamable-http.js +16 -0
- package/dist/runtime/transports/http/http-context.d.ts +9 -0
- package/dist/runtime/transports/http/http-context.js +8 -0
- package/dist/runtime/transports/http/index.d.ts +1 -0
- package/dist/runtime/transports/http/index.js +55 -0
- package/dist/runtime/transports/http/setup-cors.d.ts +4 -0
- package/dist/runtime/transports/http/setup-cors.js +24 -0
- package/dist/runtime/transports/http/stateless-streamable-http.d.ts +39 -0
- package/dist/runtime/transports/http/stateless-streamable-http.js +331 -0
- package/dist/runtime/transports/stdio/index.d.ts +1 -0
- package/dist/runtime/transports/stdio/index.js +51 -0
- package/dist/runtime/utils/server.d.ts +42 -0
- package/dist/runtime/utils/server.js +39 -0
- package/dist/runtime/utils/tools.d.ts +8 -0
- package/dist/runtime/utils/tools.js +115 -0
- package/dist/runtime/verifier-middleware.d.ts +76 -0
- package/dist/runtime/verifier-middleware.js +322 -0
- package/dist/runtime/well-known.d.ts +151 -0
- package/dist/runtime/well-known.js +258 -0
- package/dist/storage/config.d.ts +28 -0
- package/dist/storage/config.js +79 -0
- package/dist/storage/delegation.d.ts +59 -0
- package/dist/storage/delegation.js +130 -0
- package/dist/storage/merkle-verifier.d.ts +84 -0
- package/dist/storage/merkle-verifier.js +261 -0
- package/dist/test/__tests__/nonce-cache-integration.test.d.ts +1 -0
- package/dist/test/__tests__/nonce-cache-integration.test.js +116 -0
- package/dist/test/__tests__/nonce-cache.test.d.ts +1 -0
- package/dist/test/__tests__/nonce-cache.test.js +122 -0
- package/dist/test/__tests__/runtime-integration.test.d.ts +4 -0
- package/dist/test/__tests__/runtime-integration.test.js +192 -0
- package/dist/test/__tests__/test-infrastructure.test.d.ts +4 -0
- package/dist/test/__tests__/test-infrastructure.test.js +178 -0
- package/dist/test/deterministic-keys.d.ts +31 -0
- package/dist/test/deterministic-keys.js +108 -0
- package/dist/test/examples/test-usage-example.d.ts +140 -0
- package/dist/test/examples/test-usage-example.js +175 -0
- package/dist/test/index.d.ts +11 -0
- package/dist/test/index.js +27 -0
- package/dist/test/local-verification.d.ts +28 -0
- package/dist/test/local-verification.js +342 -0
- package/dist/test/mock-identity-provider.d.ts +96 -0
- package/dist/test/mock-identity-provider.js +243 -0
- package/dist/test/runtime-integration.d.ts +63 -0
- package/dist/test/runtime-integration.js +140 -0
- package/dist/test/test-environment.d.ts +26 -0
- package/dist/test/test-environment.js +50 -0
- package/dist/types/declarations.d.ts +1 -0
- package/dist/types/declarations.js +6 -0
- package/dist/types/middleware.d.ts +2 -0
- package/dist/types/middleware.js +2 -0
- package/dist/types/tool.d.ts +80 -0
- package/dist/types/tool.js +2 -0
- package/dist/utils/cli-icons.d.ts +3 -0
- package/dist/utils/cli-icons.js +7 -0
- package/dist/utils/constants.d.ts +6 -0
- package/dist/utils/constants.js +13 -0
- package/dist/utils/context.d.ts +33 -0
- package/dist/utils/context.js +58 -0
- package/dist/utils/file-watcher.d.ts +19 -0
- package/dist/utils/file-watcher.js +49 -0
- package/dist/utils/fs-utils.d.ts +2 -0
- package/dist/utils/fs-utils.js +22 -0
- package/dist/utils/path-validation.d.ts +3 -0
- package/dist/utils/path-validation.js +56 -0
- package/dist/utils/spawn-process.d.ts +9 -0
- package/dist/utils/spawn-process.js +50 -0
- package/dist/utils/subscribable.d.ts +12 -0
- package/dist/utils/subscribable.js +44 -0
- package/package.json +99 -21
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CloudflareKVNonceCache = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Cloudflare KV-based nonce cache implementation
|
|
6
|
+
* Suitable for Cloudflare Workers deployments
|
|
7
|
+
*/
|
|
8
|
+
class CloudflareKVNonceCache {
|
|
9
|
+
kv; // KV namespace - type depends on Cloudflare Workers runtime
|
|
10
|
+
keyPrefix;
|
|
11
|
+
constructor(kv, keyPrefix = "nonce:") {
|
|
12
|
+
this.kv = kv;
|
|
13
|
+
this.keyPrefix = keyPrefix;
|
|
14
|
+
}
|
|
15
|
+
getKey(nonce) {
|
|
16
|
+
return `${this.keyPrefix}${nonce}`;
|
|
17
|
+
}
|
|
18
|
+
async has(nonce) {
|
|
19
|
+
const key = this.getKey(nonce);
|
|
20
|
+
const value = await this.kv.get(key);
|
|
21
|
+
if (!value) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
// Parse the stored data to check expiry
|
|
25
|
+
try {
|
|
26
|
+
const data = JSON.parse(value);
|
|
27
|
+
if (Date.now() > data.expiresAt) {
|
|
28
|
+
// Clean up expired entry
|
|
29
|
+
await this.kv.delete(key);
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// If we can't parse, assume expired and clean up
|
|
36
|
+
await this.kv.delete(key);
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async add(nonce, ttl) {
|
|
41
|
+
const key = this.getKey(nonce);
|
|
42
|
+
const expiresAt = Date.now() + ttl * 1000;
|
|
43
|
+
const data = {
|
|
44
|
+
nonce,
|
|
45
|
+
expiresAt,
|
|
46
|
+
createdAt: Date.now(),
|
|
47
|
+
};
|
|
48
|
+
// Cloudflare KV doesn't have native atomic add-if-absent
|
|
49
|
+
// We implement a best-effort approach with metadata checking
|
|
50
|
+
try {
|
|
51
|
+
// First, try to get the existing value with metadata
|
|
52
|
+
const existingWithMetadata = await this.kv.getWithMetadata(key);
|
|
53
|
+
if (existingWithMetadata && existingWithMetadata.value !== null) {
|
|
54
|
+
// Key exists, check if it's still valid
|
|
55
|
+
try {
|
|
56
|
+
const existingData = JSON.parse(existingWithMetadata.value);
|
|
57
|
+
if (Date.now() <= existingData.expiresAt) {
|
|
58
|
+
throw new Error(`Nonce ${nonce} already exists - potential replay attack`);
|
|
59
|
+
}
|
|
60
|
+
// If expired, we can proceed to overwrite
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// If we can't parse existing data, assume it's corrupted and overwrite
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Store with KV TTL as backup (convert to seconds)
|
|
67
|
+
await this.kv.put(key, JSON.stringify(data), {
|
|
68
|
+
expirationTtl: ttl,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
// If getWithMetadata is not available, fall back to basic approach
|
|
73
|
+
if (error.message?.includes("getWithMetadata")) {
|
|
74
|
+
// Check if already exists first (less atomic but still functional)
|
|
75
|
+
if (await this.has(nonce)) {
|
|
76
|
+
throw new Error(`Nonce ${nonce} already exists - potential replay attack`);
|
|
77
|
+
}
|
|
78
|
+
// Store with KV TTL as backup (convert to seconds)
|
|
79
|
+
await this.kv.put(key, JSON.stringify(data), {
|
|
80
|
+
expirationTtl: ttl,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async cleanup() {
|
|
89
|
+
// Cloudflare KV handles expiry automatically via TTL, so this is a no-op
|
|
90
|
+
// This method exists to satisfy the interface
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
exports.CloudflareKVNonceCache = CloudflareKVNonceCache;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { NonceCache } from "@kya-os/contracts/handshake";
|
|
2
|
+
/**
|
|
3
|
+
* DynamoDB-based nonce cache implementation
|
|
4
|
+
* Suitable for AWS Lambda deployments
|
|
5
|
+
*/
|
|
6
|
+
export declare class DynamoNonceCache implements NonceCache {
|
|
7
|
+
private dynamodb;
|
|
8
|
+
private tableName;
|
|
9
|
+
private keyAttribute;
|
|
10
|
+
private ttlAttribute;
|
|
11
|
+
constructor(dynamodb: any, tableName: string, keyAttribute?: string, ttlAttribute?: string);
|
|
12
|
+
has(nonce: string): Promise<boolean>;
|
|
13
|
+
add(nonce: string, ttl: number): Promise<void>;
|
|
14
|
+
cleanup(): Promise<void>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DynamoNonceCache = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* DynamoDB-based nonce cache implementation
|
|
6
|
+
* Suitable for AWS Lambda deployments
|
|
7
|
+
*/
|
|
8
|
+
class DynamoNonceCache {
|
|
9
|
+
dynamodb; // DynamoDB client - type depends on AWS SDK version
|
|
10
|
+
tableName;
|
|
11
|
+
keyAttribute;
|
|
12
|
+
ttlAttribute;
|
|
13
|
+
constructor(dynamodb, tableName, keyAttribute = "nonce", ttlAttribute = "expiresAt") {
|
|
14
|
+
this.dynamodb = dynamodb;
|
|
15
|
+
this.tableName = tableName;
|
|
16
|
+
this.keyAttribute = keyAttribute;
|
|
17
|
+
this.ttlAttribute = ttlAttribute;
|
|
18
|
+
}
|
|
19
|
+
async has(nonce) {
|
|
20
|
+
try {
|
|
21
|
+
const result = await this.dynamodb
|
|
22
|
+
.getItem({
|
|
23
|
+
TableName: this.tableName,
|
|
24
|
+
Key: {
|
|
25
|
+
[this.keyAttribute]: { S: nonce },
|
|
26
|
+
},
|
|
27
|
+
ConsistentRead: true, // Ensure we get the latest data
|
|
28
|
+
})
|
|
29
|
+
.promise();
|
|
30
|
+
if (!result.Item) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
// Check if expired (DynamoDB TTL might not have cleaned up yet)
|
|
34
|
+
const expiresAt = parseInt(result.Item[this.ttlAttribute].N);
|
|
35
|
+
if (Date.now() / 1000 > expiresAt) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
// Log error but don't expose internal details
|
|
42
|
+
console.error("DynamoDB has() operation failed:", error);
|
|
43
|
+
// For certain errors, we can safely return false
|
|
44
|
+
if (error.code === "ResourceNotFoundException" ||
|
|
45
|
+
error.code === "ItemCollectionSizeLimitExceededException") {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
// For other errors, we should throw to avoid security issues
|
|
49
|
+
throw new Error(`Failed to check nonce existence: ${error.code || error.message}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async add(nonce, ttl) {
|
|
53
|
+
const expiresAt = Math.floor(Date.now() / 1000) + ttl;
|
|
54
|
+
try {
|
|
55
|
+
// Use conditional write to ensure atomic add-if-absent
|
|
56
|
+
// This is truly atomic in DynamoDB
|
|
57
|
+
await this.dynamodb
|
|
58
|
+
.putItem({
|
|
59
|
+
TableName: this.tableName,
|
|
60
|
+
Item: {
|
|
61
|
+
[this.keyAttribute]: { S: nonce },
|
|
62
|
+
[this.ttlAttribute]: { N: expiresAt.toString() },
|
|
63
|
+
createdAt: { N: Math.floor(Date.now() / 1000).toString() },
|
|
64
|
+
},
|
|
65
|
+
ConditionExpression: `attribute_not_exists(${this.keyAttribute})`,
|
|
66
|
+
})
|
|
67
|
+
.promise();
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
if (error.code === "ConditionalCheckFailedException") {
|
|
71
|
+
throw new Error(`Nonce ${nonce} already exists - potential replay attack`);
|
|
72
|
+
}
|
|
73
|
+
console.error("DynamoDB add() operation failed:", error);
|
|
74
|
+
// Provide more specific error messages for common issues
|
|
75
|
+
if (error.code === "ResourceNotFoundException") {
|
|
76
|
+
throw new Error(`DynamoDB table ${this.tableName} not found`);
|
|
77
|
+
}
|
|
78
|
+
else if (error.code === "ValidationException") {
|
|
79
|
+
throw new Error(`Invalid DynamoDB operation: ${error.message}`);
|
|
80
|
+
}
|
|
81
|
+
else if (error.code === "ProvisionedThroughputExceededException") {
|
|
82
|
+
throw new Error("DynamoDB throughput exceeded - consider increasing capacity");
|
|
83
|
+
}
|
|
84
|
+
throw new Error(`Failed to add nonce to cache: ${error.code || error.message}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async cleanup() {
|
|
88
|
+
// DynamoDB handles TTL cleanup automatically, so this is a no-op
|
|
89
|
+
// This method exists to satisfy the interface
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
exports.DynamoNonceCache = DynamoNonceCache;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pluggable nonce cache architecture for XMCP-I
|
|
3
|
+
*
|
|
4
|
+
* Provides replay prevention through various cache backends:
|
|
5
|
+
* - Memory (development, single instance)
|
|
6
|
+
* - Redis (production, multi-instance)
|
|
7
|
+
* - DynamoDB (AWS Lambda)
|
|
8
|
+
* - Cloudflare KV (Cloudflare Workers)
|
|
9
|
+
*/
|
|
10
|
+
export type { SessionContext, HandshakeRequest, NonceCacheEntry, NonceCache, NonceCacheConfig, } from "@kya-os/contracts/handshake";
|
|
11
|
+
export { SessionContextSchema, HandshakeRequestSchema, NonceCacheEntrySchema, NonceCacheConfigSchema, } from "@kya-os/contracts/handshake";
|
|
12
|
+
export { MemoryNonceCache } from "./memory-nonce-cache";
|
|
13
|
+
export { RedisNonceCache } from "./redis-nonce-cache";
|
|
14
|
+
export { DynamoNonceCache } from "./dynamodb-nonce-cache";
|
|
15
|
+
export { CloudflareKVNonceCache } from "./cloudflare-kv-nonce-cache";
|
|
16
|
+
export { createNonceCache, createNonceCacheWithConfig, detectCacheType, type NonceCacheOptions, } from "./nonce-cache-factory";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Pluggable nonce cache architecture for XMCP-I
|
|
4
|
+
*
|
|
5
|
+
* Provides replay prevention through various cache backends:
|
|
6
|
+
* - Memory (development, single instance)
|
|
7
|
+
* - Redis (production, multi-instance)
|
|
8
|
+
* - DynamoDB (AWS Lambda)
|
|
9
|
+
* - Cloudflare KV (Cloudflare Workers)
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.detectCacheType = exports.createNonceCacheWithConfig = exports.createNonceCache = exports.CloudflareKVNonceCache = exports.DynamoNonceCache = exports.RedisNonceCache = exports.MemoryNonceCache = exports.NonceCacheConfigSchema = exports.NonceCacheEntrySchema = exports.HandshakeRequestSchema = exports.SessionContextSchema = void 0;
|
|
13
|
+
// Re-export schemas
|
|
14
|
+
var handshake_1 = require("@kya-os/contracts/handshake");
|
|
15
|
+
Object.defineProperty(exports, "SessionContextSchema", { enumerable: true, get: function () { return handshake_1.SessionContextSchema; } });
|
|
16
|
+
Object.defineProperty(exports, "HandshakeRequestSchema", { enumerable: true, get: function () { return handshake_1.HandshakeRequestSchema; } });
|
|
17
|
+
Object.defineProperty(exports, "NonceCacheEntrySchema", { enumerable: true, get: function () { return handshake_1.NonceCacheEntrySchema; } });
|
|
18
|
+
Object.defineProperty(exports, "NonceCacheConfigSchema", { enumerable: true, get: function () { return handshake_1.NonceCacheConfigSchema; } });
|
|
19
|
+
// Cache implementations
|
|
20
|
+
var memory_nonce_cache_1 = require("./memory-nonce-cache");
|
|
21
|
+
Object.defineProperty(exports, "MemoryNonceCache", { enumerable: true, get: function () { return memory_nonce_cache_1.MemoryNonceCache; } });
|
|
22
|
+
var redis_nonce_cache_1 = require("./redis-nonce-cache");
|
|
23
|
+
Object.defineProperty(exports, "RedisNonceCache", { enumerable: true, get: function () { return redis_nonce_cache_1.RedisNonceCache; } });
|
|
24
|
+
var dynamodb_nonce_cache_1 = require("./dynamodb-nonce-cache");
|
|
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; } });
|
|
28
|
+
// Factory and auto-detection
|
|
29
|
+
var nonce_cache_factory_1 = require("./nonce-cache-factory");
|
|
30
|
+
Object.defineProperty(exports, "createNonceCache", { enumerable: true, get: function () { return nonce_cache_factory_1.createNonceCache; } });
|
|
31
|
+
Object.defineProperty(exports, "createNonceCacheWithConfig", { enumerable: true, get: function () { return nonce_cache_factory_1.createNonceCacheWithConfig; } });
|
|
32
|
+
Object.defineProperty(exports, "detectCacheType", { enumerable: true, get: function () { return nonce_cache_factory_1.detectCacheType; } });
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory nonce cache implementation
|
|
3
|
+
*
|
|
4
|
+
* WARNING: This implementation is only suitable for single-instance deployments.
|
|
5
|
+
* For multi-instance deployments, use Redis, DynamoDB, or Cloudflare KV implementations.
|
|
6
|
+
*/
|
|
7
|
+
import { NonceCache } from "@kya-os/contracts/handshake";
|
|
8
|
+
/**
|
|
9
|
+
* In-memory nonce cache with TTL support
|
|
10
|
+
*/
|
|
11
|
+
export declare class MemoryNonceCache implements NonceCache {
|
|
12
|
+
private cache;
|
|
13
|
+
private cleanupInterval?;
|
|
14
|
+
constructor(cleanupIntervalMs?: number);
|
|
15
|
+
/**
|
|
16
|
+
* Check if a nonce exists in the cache
|
|
17
|
+
*/
|
|
18
|
+
has(nonce: string): Promise<boolean>;
|
|
19
|
+
/**
|
|
20
|
+
* Add a nonce to the cache with TTL
|
|
21
|
+
* MUST ensure atomic add-if-absent semantics for replay prevention
|
|
22
|
+
*/
|
|
23
|
+
add(nonce: string, ttlSeconds: number): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Clean up expired entries
|
|
26
|
+
* Safe to call frequently and should be no-op for backends that auto-expire
|
|
27
|
+
*/
|
|
28
|
+
cleanup(): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Get cache statistics
|
|
31
|
+
*/
|
|
32
|
+
getStats(): {
|
|
33
|
+
size: number;
|
|
34
|
+
expired: number;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Clear all entries (useful for testing)
|
|
38
|
+
*/
|
|
39
|
+
clear(): void;
|
|
40
|
+
/**
|
|
41
|
+
* Destroy the cache and stop cleanup interval
|
|
42
|
+
*/
|
|
43
|
+
destroy(): void;
|
|
44
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* In-memory nonce cache implementation
|
|
4
|
+
*
|
|
5
|
+
* WARNING: This implementation is only suitable for single-instance deployments.
|
|
6
|
+
* For multi-instance deployments, use Redis, DynamoDB, or Cloudflare KV implementations.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.MemoryNonceCache = void 0;
|
|
10
|
+
/**
|
|
11
|
+
* In-memory nonce cache with TTL support
|
|
12
|
+
*/
|
|
13
|
+
class MemoryNonceCache {
|
|
14
|
+
cache = new Map();
|
|
15
|
+
cleanupInterval;
|
|
16
|
+
constructor(cleanupIntervalMs = 60000) {
|
|
17
|
+
// Default: 1 minute cleanup
|
|
18
|
+
// Start periodic cleanup
|
|
19
|
+
this.cleanupInterval = setInterval(() => {
|
|
20
|
+
this.cleanup().catch(console.error);
|
|
21
|
+
}, cleanupIntervalMs);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Check if a nonce exists in the cache
|
|
25
|
+
*/
|
|
26
|
+
async has(nonce) {
|
|
27
|
+
const entry = this.cache.get(nonce);
|
|
28
|
+
if (!entry) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
// Check if expired
|
|
32
|
+
if (Date.now() > entry.expiresAt) {
|
|
33
|
+
this.cache.delete(nonce);
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Add a nonce to the cache with TTL
|
|
40
|
+
* MUST ensure atomic add-if-absent semantics for replay prevention
|
|
41
|
+
*/
|
|
42
|
+
async add(nonce, ttlSeconds) {
|
|
43
|
+
// Check if nonce already exists (atomic check-and-set)
|
|
44
|
+
const existing = this.cache.get(nonce);
|
|
45
|
+
if (existing && Date.now() <= existing.expiresAt) {
|
|
46
|
+
throw new Error(`Nonce ${nonce} already exists`);
|
|
47
|
+
}
|
|
48
|
+
// Handle zero or negative TTL - set expiration to past time
|
|
49
|
+
const expiresAt = ttlSeconds <= 0 ? Date.now() - 1 : Date.now() + ttlSeconds * 1000;
|
|
50
|
+
const entry = {
|
|
51
|
+
sessionId: `mem_${Date.now()}_${Math.random().toString(36).substring(2)}`,
|
|
52
|
+
expiresAt,
|
|
53
|
+
};
|
|
54
|
+
this.cache.set(nonce, entry);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Clean up expired entries
|
|
58
|
+
* Safe to call frequently and should be no-op for backends that auto-expire
|
|
59
|
+
*/
|
|
60
|
+
async cleanup() {
|
|
61
|
+
const now = Date.now();
|
|
62
|
+
const expiredKeys = [];
|
|
63
|
+
for (const [nonce, entry] of this.cache.entries()) {
|
|
64
|
+
if (now > entry.expiresAt) {
|
|
65
|
+
expiredKeys.push(nonce);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
for (const key of expiredKeys) {
|
|
69
|
+
this.cache.delete(key);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Get cache statistics
|
|
74
|
+
*/
|
|
75
|
+
getStats() {
|
|
76
|
+
const now = Date.now();
|
|
77
|
+
let expired = 0;
|
|
78
|
+
for (const entry of this.cache.values()) {
|
|
79
|
+
if (now > entry.expiresAt) {
|
|
80
|
+
expired++;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
size: this.cache.size,
|
|
85
|
+
expired,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Clear all entries (useful for testing)
|
|
90
|
+
*/
|
|
91
|
+
clear() {
|
|
92
|
+
this.cache.clear();
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Destroy the cache and stop cleanup interval
|
|
96
|
+
*/
|
|
97
|
+
destroy() {
|
|
98
|
+
if (this.cleanupInterval) {
|
|
99
|
+
clearInterval(this.cleanupInterval);
|
|
100
|
+
this.cleanupInterval = undefined;
|
|
101
|
+
}
|
|
102
|
+
this.cache.clear();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
exports.MemoryNonceCache = MemoryNonceCache;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { NonceCache, NonceCacheConfig } from "@kya-os/contracts/handshake";
|
|
2
|
+
/**
|
|
3
|
+
* Environment-based cache type detection
|
|
4
|
+
*/
|
|
5
|
+
export declare function detectCacheType(): "memory" | "redis" | "dynamodb" | "cloudflare-kv";
|
|
6
|
+
/**
|
|
7
|
+
* Create a nonce cache instance based on configuration or environment detection
|
|
8
|
+
*/
|
|
9
|
+
export declare function createNonceCache(config?: NonceCacheConfig): Promise<NonceCache>;
|
|
10
|
+
/**
|
|
11
|
+
* Configuration override options for nonce cache
|
|
12
|
+
*/
|
|
13
|
+
export interface NonceCacheOptions {
|
|
14
|
+
config?: NonceCacheConfig;
|
|
15
|
+
fallbackToMemory?: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Create nonce cache with explicit configuration override
|
|
19
|
+
*/
|
|
20
|
+
export declare function createNonceCacheWithConfig(options?: NonceCacheOptions): Promise<NonceCache>;
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.detectCacheType = detectCacheType;
|
|
4
|
+
exports.createNonceCache = createNonceCache;
|
|
5
|
+
exports.createNonceCacheWithConfig = createNonceCacheWithConfig;
|
|
6
|
+
const memory_nonce_cache_1 = require("./memory-nonce-cache");
|
|
7
|
+
const redis_nonce_cache_1 = require("./redis-nonce-cache");
|
|
8
|
+
const dynamodb_nonce_cache_1 = require("./dynamodb-nonce-cache");
|
|
9
|
+
const cloudflare_kv_nonce_cache_1 = require("./cloudflare-kv-nonce-cache");
|
|
10
|
+
/**
|
|
11
|
+
* Get environment variable with fallback support for old naming
|
|
12
|
+
* Supports migration from XMCPI_ to MCPI_ prefixes
|
|
13
|
+
*/
|
|
14
|
+
function getEnvWithFallback(newKey, oldKey) {
|
|
15
|
+
const newValue = process.env[newKey];
|
|
16
|
+
if (newValue !== undefined) {
|
|
17
|
+
return newValue;
|
|
18
|
+
}
|
|
19
|
+
if (oldKey) {
|
|
20
|
+
const oldValue = process.env[oldKey];
|
|
21
|
+
if (oldValue !== undefined) {
|
|
22
|
+
console.warn(`⚠️ Environment variable ${oldKey} is deprecated. Please use ${newKey} instead.`);
|
|
23
|
+
return oldValue;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Environment-based cache type detection
|
|
30
|
+
*/
|
|
31
|
+
function detectCacheType() {
|
|
32
|
+
// Check for explicit configuration (with fallback support)
|
|
33
|
+
const explicitType = getEnvWithFallback("MCPI_NONCE_CACHE_TYPE", "XMCPI_NONCE_CACHE_TYPE");
|
|
34
|
+
if (explicitType &&
|
|
35
|
+
["memory", "redis", "dynamodb", "cloudflare-kv"].includes(explicitType)) {
|
|
36
|
+
return explicitType;
|
|
37
|
+
}
|
|
38
|
+
// Auto-detect based on environment (most specific first)
|
|
39
|
+
// Redis detection
|
|
40
|
+
if (process.env.REDIS_URL ||
|
|
41
|
+
getEnvWithFallback("MCPI_REDIS_URL", "XMCPI_REDIS_URL")) {
|
|
42
|
+
return "redis";
|
|
43
|
+
}
|
|
44
|
+
// DynamoDB detection (AWS Lambda environment)
|
|
45
|
+
if ((process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION) &&
|
|
46
|
+
getEnvWithFallback("MCPI_DYNAMODB_TABLE", "XMCPI_DYNAMODB_TABLE")) {
|
|
47
|
+
return "dynamodb";
|
|
48
|
+
}
|
|
49
|
+
// Cloudflare Workers detection (more robust)
|
|
50
|
+
if (typeof globalThis !== "undefined") {
|
|
51
|
+
// Check for Cloudflare Workers specific globals
|
|
52
|
+
const globalScope = globalThis;
|
|
53
|
+
if ("caches" in globalThis &&
|
|
54
|
+
typeof globalScope.fetch === "function" &&
|
|
55
|
+
(typeof globalScope.addEventListener === "function" ||
|
|
56
|
+
typeof globalScope.Response === "function")) {
|
|
57
|
+
return "cloudflare-kv";
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Warn about memory cache in production
|
|
61
|
+
if (process.env.NODE_ENV === "production" ||
|
|
62
|
+
getEnvWithFallback("MCPI_ENV", "XMCPI_ENV") === "production") {
|
|
63
|
+
console.warn("mcpi: Using memory cache in production environment. " +
|
|
64
|
+
"For multi-instance deployments, configure Redis (REDIS_URL), " +
|
|
65
|
+
"DynamoDB (AWS_REGION + MCPI_DYNAMODB_TABLE), or " +
|
|
66
|
+
"Cloudflare KV (MCPI_KV_NAMESPACE).");
|
|
67
|
+
}
|
|
68
|
+
return "memory";
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Create a nonce cache instance based on configuration or environment detection
|
|
72
|
+
*/
|
|
73
|
+
async function createNonceCache(config) {
|
|
74
|
+
const cacheType = config?.type || detectCacheType();
|
|
75
|
+
switch (cacheType) {
|
|
76
|
+
case "redis": {
|
|
77
|
+
const redisUrl = config?.redis?.url ||
|
|
78
|
+
process.env.REDIS_URL ||
|
|
79
|
+
getEnvWithFallback("MCPI_REDIS_URL", "XMCPI_REDIS_URL");
|
|
80
|
+
if (!redisUrl) {
|
|
81
|
+
console.warn("Redis URL not found, falling back to memory cache");
|
|
82
|
+
return new memory_nonce_cache_1.MemoryNonceCache();
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
// Dynamic import to avoid bundling Redis in environments that don't need it
|
|
86
|
+
const { createClient } = await import("redis");
|
|
87
|
+
const redis = createClient({
|
|
88
|
+
url: redisUrl,
|
|
89
|
+
socket: {
|
|
90
|
+
connectTimeout: 5000, // 5 second timeout
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
// Test connection
|
|
94
|
+
await redis.connect();
|
|
95
|
+
// Verify basic operations work
|
|
96
|
+
await redis.ping();
|
|
97
|
+
return new redis_nonce_cache_1.RedisNonceCache(redis, config?.redis?.keyPrefix);
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
101
|
+
console.warn("Failed to connect to Redis, falling back to memory cache:", errorMessage);
|
|
102
|
+
return new memory_nonce_cache_1.MemoryNonceCache();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
case "dynamodb": {
|
|
106
|
+
const tableName = config?.dynamodb?.tableName ||
|
|
107
|
+
getEnvWithFallback("MCPI_DYNAMODB_TABLE", "XMCPI_DYNAMODB_TABLE");
|
|
108
|
+
if (!tableName) {
|
|
109
|
+
console.warn("DynamoDB table name not found, falling back to memory cache");
|
|
110
|
+
return new memory_nonce_cache_1.MemoryNonceCache();
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
// Dynamic import to avoid bundling AWS SDK in environments that don't need it
|
|
114
|
+
const awsSdk = await import("@aws-sdk/client-dynamodb");
|
|
115
|
+
// Extract types from dynamic import - handle both ESM and CJS exports
|
|
116
|
+
const DynamoDBClient = awsSdk.DynamoDBClient ||
|
|
117
|
+
awsSdk.default?.DynamoDBClient;
|
|
118
|
+
const DescribeTableCommand = awsSdk.DescribeTableCommand ||
|
|
119
|
+
awsSdk.default?.DescribeTableCommand;
|
|
120
|
+
const region = config?.dynamodb?.region ||
|
|
121
|
+
process.env.AWS_REGION ||
|
|
122
|
+
process.env.AWS_DEFAULT_REGION ||
|
|
123
|
+
"us-east-1";
|
|
124
|
+
const dynamodb = new DynamoDBClient({
|
|
125
|
+
region,
|
|
126
|
+
maxAttempts: 3,
|
|
127
|
+
});
|
|
128
|
+
// Verify table exists and is accessible
|
|
129
|
+
try {
|
|
130
|
+
await dynamodb.send(new DescribeTableCommand({ TableName: tableName }));
|
|
131
|
+
}
|
|
132
|
+
catch (tableError) {
|
|
133
|
+
const error = tableError;
|
|
134
|
+
if (error.name === "ResourceNotFoundException") {
|
|
135
|
+
throw new Error(`DynamoDB table '${tableName}' not found in region '${region}'`);
|
|
136
|
+
}
|
|
137
|
+
throw tableError;
|
|
138
|
+
}
|
|
139
|
+
return new dynamodb_nonce_cache_1.DynamoNonceCache(dynamodb, tableName, config?.dynamodb?.keyAttribute, config?.dynamodb?.ttlAttribute);
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
143
|
+
console.warn("Failed to initialize DynamoDB, falling back to memory cache:", errorMessage);
|
|
144
|
+
return new memory_nonce_cache_1.MemoryNonceCache();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
case "cloudflare-kv": {
|
|
148
|
+
const namespace = config?.cloudflareKv?.namespace ||
|
|
149
|
+
getEnvWithFallback("MCPI_KV_NAMESPACE", "XMCPI_KV_NAMESPACE");
|
|
150
|
+
if (!namespace) {
|
|
151
|
+
console.warn("Cloudflare KV namespace not found, falling back to memory cache");
|
|
152
|
+
return new memory_nonce_cache_1.MemoryNonceCache();
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
// In Cloudflare Workers, KV namespaces are available as globals
|
|
156
|
+
const globalScope = globalThis;
|
|
157
|
+
const kv = globalScope[namespace];
|
|
158
|
+
if (!kv) {
|
|
159
|
+
throw new Error(`KV namespace '${namespace}' not found in global scope`);
|
|
160
|
+
}
|
|
161
|
+
// Verify KV namespace has required methods
|
|
162
|
+
const kvInterface = kv;
|
|
163
|
+
if (typeof kvInterface.get !== "function" ||
|
|
164
|
+
typeof kvInterface.put !== "function") {
|
|
165
|
+
throw new Error(`KV namespace '${namespace}' does not have required methods`);
|
|
166
|
+
}
|
|
167
|
+
// Test basic KV operation (this will be very fast in Workers)
|
|
168
|
+
try {
|
|
169
|
+
const kvInterface = kv;
|
|
170
|
+
await kvInterface.get("__mcpi_test_key__");
|
|
171
|
+
}
|
|
172
|
+
catch (kvError) {
|
|
173
|
+
const error = kvError;
|
|
174
|
+
// Some KV errors are expected (like key not found), but connection errors are not
|
|
175
|
+
if (error.message?.includes("network") ||
|
|
176
|
+
error.message?.includes("timeout")) {
|
|
177
|
+
throw kvError;
|
|
178
|
+
}
|
|
179
|
+
// Other errors (like key not found) are fine
|
|
180
|
+
}
|
|
181
|
+
return new cloudflare_kv_nonce_cache_1.CloudflareKVNonceCache(kv, config?.cloudflareKv?.keyPrefix);
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
185
|
+
console.warn("Failed to initialize Cloudflare KV, falling back to memory cache:", errorMessage);
|
|
186
|
+
return new memory_nonce_cache_1.MemoryNonceCache();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
case "memory":
|
|
190
|
+
default:
|
|
191
|
+
return new memory_nonce_cache_1.MemoryNonceCache();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Create nonce cache with explicit configuration override
|
|
196
|
+
*/
|
|
197
|
+
async function createNonceCacheWithConfig(options = {}) {
|
|
198
|
+
try {
|
|
199
|
+
return await createNonceCache(options.config);
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
if (options.fallbackToMemory !== false) {
|
|
203
|
+
console.warn("Failed to create configured nonce cache, falling back to memory:", error);
|
|
204
|
+
return new memory_nonce_cache_1.MemoryNonceCache();
|
|
205
|
+
}
|
|
206
|
+
throw error;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { NonceCache } from "@kya-os/contracts/handshake";
|
|
2
|
+
/**
|
|
3
|
+
* Redis-based nonce cache implementation
|
|
4
|
+
* Suitable for multi-instance deployments
|
|
5
|
+
*/
|
|
6
|
+
export declare class RedisNonceCache implements NonceCache {
|
|
7
|
+
private redis;
|
|
8
|
+
private keyPrefix;
|
|
9
|
+
constructor(redis: any, keyPrefix?: string);
|
|
10
|
+
private getKey;
|
|
11
|
+
has(nonce: string): Promise<boolean>;
|
|
12
|
+
add(nonce: string, ttl: number): Promise<void>;
|
|
13
|
+
cleanup(): Promise<void>;
|
|
14
|
+
}
|