@kya-os/mcp-i 1.3.2 → 1.4.0

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.
@@ -52,16 +52,11 @@ class CloudflareKVNonceCache {
52
52
  const existingWithMetadata = await this.kv.getWithMetadata(key);
53
53
  if (existingWithMetadata && existingWithMetadata.value !== null) {
54
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
55
+ const existingData = JSON.parse(existingWithMetadata.value);
56
+ if (Date.now() <= existingData.expiresAt) {
57
+ throw new Error(`Nonce ${nonce} already exists - potential replay attack`);
64
58
  }
59
+ // If expired, we can proceed to overwrite
65
60
  }
66
61
  // Store with KV TTL as backup (convert to seconds)
67
62
  await this.kv.put(key, JSON.stringify(data), {
@@ -69,6 +64,10 @@ class CloudflareKVNonceCache {
69
64
  });
70
65
  }
71
66
  catch (error) {
67
+ // If this is a replay attack error, always rethrow it
68
+ if (error.message?.includes("potential replay attack")) {
69
+ throw error;
70
+ }
72
71
  // If getWithMetadata is not available, fall back to basic approach
73
72
  if (error.message?.includes("getWithMetadata")) {
74
73
  // Check if already exists first (less atomic but still functional)
@@ -15,6 +15,8 @@ class MemoryNonceCache {
15
15
  cleanupInterval;
16
16
  constructor(cleanupIntervalMs = 60000) {
17
17
  // Default: 1 minute cleanup
18
+ console.warn("⚠️ MemoryNonceCache is not suitable for multi-instance deployments. " +
19
+ "For production use with multiple instances, configure Redis, DynamoDB, or Cloudflare KV.");
18
20
  // Start periodic cleanup
19
21
  this.cleanupInterval = setInterval(() => {
20
22
  this.cleanup().catch(console.error);
@@ -43,7 +45,7 @@ class MemoryNonceCache {
43
45
  // Check if nonce already exists (atomic check-and-set)
44
46
  const existing = this.cache.get(nonce);
45
47
  if (existing && Date.now() <= existing.expiresAt) {
46
- throw new Error(`Nonce ${nonce} already exists`);
48
+ throw new Error(`Nonce ${nonce} already exists - potential replay attack`);
47
49
  }
48
50
  // Handle zero or negative TTL - set expiration to past time
49
51
  const expiresAt = ttlSeconds <= 0 ? Date.now() - 1 : Date.now() + ttlSeconds * 1000;
@@ -6,7 +6,9 @@ export declare function detectCacheType(): "memory" | "redis" | "dynamodb" | "cl
6
6
  /**
7
7
  * Create a nonce cache instance based on configuration or environment detection
8
8
  */
9
- export declare function createNonceCache(config?: NonceCacheConfig): Promise<NonceCache>;
9
+ export declare function createNonceCache(config?: NonceCacheConfig, options?: {
10
+ throwOnError?: boolean;
11
+ }): Promise<NonceCache>;
10
12
  /**
11
13
  * Configuration override options for nonce cache
12
14
  */
@@ -70,7 +70,7 @@ function detectCacheType() {
70
70
  /**
71
71
  * Create a nonce cache instance based on configuration or environment detection
72
72
  */
73
- async function createNonceCache(config) {
73
+ async function createNonceCache(config, options) {
74
74
  const cacheType = config?.type || detectCacheType();
75
75
  switch (cacheType) {
76
76
  case "redis": {
@@ -78,6 +78,10 @@ async function createNonceCache(config) {
78
78
  process.env.REDIS_URL ||
79
79
  getEnvWithFallback("MCPI_REDIS_URL", "XMCPI_REDIS_URL");
80
80
  if (!redisUrl) {
81
+ const error = new Error("Redis URL not found");
82
+ if (options?.throwOnError) {
83
+ throw error;
84
+ }
81
85
  console.warn("Redis URL not found, falling back to memory cache");
82
86
  return new memory_nonce_cache_1.MemoryNonceCache();
83
87
  }
@@ -97,6 +101,9 @@ async function createNonceCache(config) {
97
101
  return new redis_nonce_cache_1.RedisNonceCache(redis, config?.redis?.keyPrefix);
98
102
  }
99
103
  catch (error) {
104
+ if (options?.throwOnError) {
105
+ throw error;
106
+ }
100
107
  const errorMessage = error instanceof Error ? error.message : String(error);
101
108
  console.warn("Failed to connect to Redis, falling back to memory cache:", errorMessage);
102
109
  return new memory_nonce_cache_1.MemoryNonceCache();
@@ -106,6 +113,10 @@ async function createNonceCache(config) {
106
113
  const tableName = config?.dynamodb?.tableName ||
107
114
  getEnvWithFallback("MCPI_DYNAMODB_TABLE", "XMCPI_DYNAMODB_TABLE");
108
115
  if (!tableName) {
116
+ const error = new Error("DynamoDB table name not found");
117
+ if (options?.throwOnError) {
118
+ throw error;
119
+ }
109
120
  console.warn("DynamoDB table name not found, falling back to memory cache");
110
121
  return new memory_nonce_cache_1.MemoryNonceCache();
111
122
  }
@@ -139,6 +150,9 @@ async function createNonceCache(config) {
139
150
  return new dynamodb_nonce_cache_1.DynamoNonceCache(dynamodb, tableName, config?.dynamodb?.keyAttribute, config?.dynamodb?.ttlAttribute);
140
151
  }
141
152
  catch (error) {
153
+ if (options?.throwOnError) {
154
+ throw error;
155
+ }
142
156
  const errorMessage = error instanceof Error ? error.message : String(error);
143
157
  console.warn("Failed to initialize DynamoDB, falling back to memory cache:", errorMessage);
144
158
  return new memory_nonce_cache_1.MemoryNonceCache();
@@ -148,6 +162,10 @@ async function createNonceCache(config) {
148
162
  const namespace = config?.cloudflareKv?.namespace ||
149
163
  getEnvWithFallback("MCPI_KV_NAMESPACE", "XMCPI_KV_NAMESPACE");
150
164
  if (!namespace) {
165
+ const error = new Error("Cloudflare KV namespace not found");
166
+ if (options?.throwOnError) {
167
+ throw error;
168
+ }
151
169
  console.warn("Cloudflare KV namespace not found, falling back to memory cache");
152
170
  return new memory_nonce_cache_1.MemoryNonceCache();
153
171
  }
@@ -181,6 +199,9 @@ async function createNonceCache(config) {
181
199
  return new cloudflare_kv_nonce_cache_1.CloudflareKVNonceCache(kv, config?.cloudflareKv?.keyPrefix);
182
200
  }
183
201
  catch (error) {
202
+ if (options?.throwOnError) {
203
+ throw error;
204
+ }
184
205
  const errorMessage = error instanceof Error ? error.message : String(error);
185
206
  console.warn("Failed to initialize Cloudflare KV, falling back to memory cache:", errorMessage);
186
207
  return new memory_nonce_cache_1.MemoryNonceCache();
@@ -196,7 +217,9 @@ async function createNonceCache(config) {
196
217
  */
197
218
  async function createNonceCacheWithConfig(options = {}) {
198
219
  try {
199
- return await createNonceCache(options.config);
220
+ return await createNonceCache(options.config, {
221
+ throwOnError: options.fallbackToMemory === false,
222
+ });
200
223
  }
201
224
  catch (error) {
202
225
  if (options.fallbackToMemory !== false) {
@@ -23,7 +23,13 @@ async function enableMCPIdentityCLI(options = {}) {
23
23
  // Helper to emit progress events
24
24
  const emitProgress = async (stage, message, data) => {
25
25
  if (onProgress) {
26
- await onProgress({ stage, message, data });
26
+ try {
27
+ await onProgress({ stage, message, data });
28
+ }
29
+ catch (error) {
30
+ // Continue even if progress callback throws
31
+ console.warn("Warning: Progress callback threw an error:", error);
32
+ }
27
33
  }
28
34
  };
29
35
  // Step 1: Check for existing identity
@@ -57,11 +57,12 @@ async function registerWithKTA(options) {
57
57
  // Extract agent ID from DID
58
58
  const agentId = did.split(":").pop() || `agent-${Date.now()}`;
59
59
  // Build result structure
60
+ // Handle both camelCase (agentUrl, claimUrl) and uppercase (agentURL, claimURL) from API
60
61
  const result = {
61
62
  agentDID: did,
62
- agentURL: data.agentURL || `${endpoint}/agents/by-did/${encodeURIComponent(did)}`,
63
+ agentURL: data.agentURL || data.agentUrl || `${endpoint}/agents/by-did/${encodeURIComponent(did)}`,
63
64
  agentId: data.agentId || agentId,
64
- claimURL: data.claimURL || `${endpoint}/claim/${agentId}`,
65
+ claimURL: data.claimURL || data.claimUrl || `${endpoint}/claim/${agentId}`,
65
66
  verificationEndpoint,
66
67
  conformanceCapabilities: MCP_I_CAPABILITIES,
67
68
  mirrorStatus: data.mirrorStatus || "pending",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kya-os/mcp-i",
3
- "version": "1.3.2",
3
+ "version": "1.4.0",
4
4
  "description": "The TypeScript MCP framework with identity features built-in",
5
5
  "type": "commonjs",
6
6
  "main": "dist/index.js",
@@ -25,6 +25,11 @@
25
25
  "require": "./dist/runtime/headers.js",
26
26
  "import": "./dist/runtime/headers.js",
27
27
  "types": "./dist/runtime/headers.d.ts"
28
+ },
29
+ "./cli-internal": {
30
+ "require": "./dist/cli-adapter/index.js",
31
+ "import": "./dist/cli-adapter/index.js",
32
+ "types": "./dist/cli-adapter/index.d.ts"
28
33
  }
29
34
  },
30
35
  "files": [
@@ -34,7 +39,8 @@
34
39
  "scripts": {
35
40
  "dev": "cross-env NODE_ENV=development tsx --watch --tsconfig ./bundler.tsconfig.json ./bundler/index.ts",
36
41
  "build": "cross-env NODE_ENV=production tsx --tsconfig ./bundler.tsconfig.json ./bundler/index.ts && node scripts/fix-esm-imports.js",
37
- "test": "vitest",
42
+ "test": "vitest --run",
43
+ "test:coverage": "vitest --run --coverage",
38
44
  "lint": "eslint src/**/*.ts",
39
45
  "type-check": "tsc --noEmit",
40
46
  "prepublishOnly": "npm run build && node ../create-mcpi-app/scripts/validate-no-workspace.js"
@@ -47,8 +53,8 @@
47
53
  "model-context-protocol"
48
54
  ],
49
55
  "dependencies": {
50
- "@kya-os/contracts": "^1.2.0",
51
- "@kya-os/mcp-i-core": "^1.0.0",
56
+ "@kya-os/contracts": "^1.3.0",
57
+ "@kya-os/mcp-i-core": "^1.1.0",
52
58
  "@modelcontextprotocol/sdk": "^1.11.4",
53
59
  "@swc/core": "^1.11.24",
54
60
  "@types/express": "^5.0.1",
@@ -93,13 +99,14 @@
93
99
  "@types/node": "^20.0.0",
94
100
  "@typescript-eslint/eslint-plugin": "^6.0.0",
95
101
  "@typescript-eslint/parser": "^6.0.0",
102
+ "@vitest/coverage-v8": "^3.2.4",
96
103
  "copy-webpack-plugin": "^12.0.2",
97
104
  "eslint": "^8.0.0",
98
105
  "node-loader": "^1.0.1",
99
106
  "redis": "^4.0.0",
100
107
  "ts-loader": "^9.4.2",
101
108
  "typescript": "^5.3.0",
102
- "vitest": "^1.0.0",
109
+ "vitest": "^3.2.4",
103
110
  "zod": "^3.22.4"
104
111
  },
105
112
  "engines": {