@kya-os/mcp-i-core 1.3.10-canary.clientinfo.20251126124133 → 1.3.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.
Files changed (90) hide show
  1. package/.claude/settings.local.json +9 -0
  2. package/.turbo/turbo-build.log +1 -1
  3. package/.turbo/turbo-test$colon$coverage.log +3419 -3072
  4. package/.turbo/turbo-test.log +1805 -1680
  5. package/coverage/coverage-final.json +59 -56
  6. package/dist/config/remote-config.d.ts +51 -0
  7. package/dist/config/remote-config.d.ts.map +1 -1
  8. package/dist/config/remote-config.js +74 -0
  9. package/dist/config/remote-config.js.map +1 -1
  10. package/dist/config.d.ts +1 -1
  11. package/dist/config.d.ts.map +1 -1
  12. package/dist/config.js +4 -1
  13. package/dist/config.js.map +1 -1
  14. package/dist/delegation/did-key-resolver.d.ts +64 -0
  15. package/dist/delegation/did-key-resolver.d.ts.map +1 -0
  16. package/dist/delegation/did-key-resolver.js +159 -0
  17. package/dist/delegation/did-key-resolver.js.map +1 -0
  18. package/dist/delegation/utils.d.ts +76 -0
  19. package/dist/delegation/utils.d.ts.map +1 -1
  20. package/dist/delegation/utils.js +117 -0
  21. package/dist/delegation/utils.js.map +1 -1
  22. package/dist/identity/idp-token-resolver.d.ts +17 -1
  23. package/dist/identity/idp-token-resolver.d.ts.map +1 -1
  24. package/dist/identity/idp-token-resolver.js +34 -6
  25. package/dist/identity/idp-token-resolver.js.map +1 -1
  26. package/dist/identity/idp-token-storage.interface.d.ts +38 -7
  27. package/dist/identity/idp-token-storage.interface.d.ts.map +1 -1
  28. package/dist/identity/idp-token-storage.interface.js +2 -0
  29. package/dist/identity/idp-token-storage.interface.js.map +1 -1
  30. package/dist/identity/user-did-manager.d.ts +95 -12
  31. package/dist/identity/user-did-manager.d.ts.map +1 -1
  32. package/dist/identity/user-did-manager.js +107 -25
  33. package/dist/identity/user-did-manager.js.map +1 -1
  34. package/dist/index.d.ts +6 -3
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +24 -2
  37. package/dist/index.js.map +1 -1
  38. package/dist/runtime/base.d.ts +25 -8
  39. package/dist/runtime/base.d.ts.map +1 -1
  40. package/dist/runtime/base.js +74 -21
  41. package/dist/runtime/base.js.map +1 -1
  42. package/dist/services/session-registration.service.d.ts.map +1 -1
  43. package/dist/services/session-registration.service.js +10 -90
  44. package/dist/services/session-registration.service.js.map +1 -1
  45. package/dist/services/tool-context-builder.d.ts +18 -1
  46. package/dist/services/tool-context-builder.d.ts.map +1 -1
  47. package/dist/services/tool-context-builder.js +63 -10
  48. package/dist/services/tool-context-builder.js.map +1 -1
  49. package/dist/services/tool-protection.service.d.ts +6 -3
  50. package/dist/services/tool-protection.service.d.ts.map +1 -1
  51. package/dist/services/tool-protection.service.js +89 -34
  52. package/dist/services/tool-protection.service.js.map +1 -1
  53. package/dist/utils/base58.d.ts +31 -0
  54. package/dist/utils/base58.d.ts.map +1 -0
  55. package/dist/utils/base58.js +103 -0
  56. package/dist/utils/base58.js.map +1 -0
  57. package/dist/utils/did-helpers.d.ts +33 -0
  58. package/dist/utils/did-helpers.d.ts.map +1 -1
  59. package/dist/utils/did-helpers.js +53 -0
  60. package/dist/utils/did-helpers.js.map +1 -1
  61. package/package.json +3 -3
  62. package/src/__tests__/identity/user-did-manager.test.ts +64 -45
  63. package/src/__tests__/integration/full-flow.test.ts +23 -10
  64. package/src/__tests__/runtime/base-extensions.test.ts +23 -21
  65. package/src/__tests__/runtime/proof-client-did.test.ts +19 -18
  66. package/src/__tests__/services/agentshield-integration.test.ts +10 -3
  67. package/src/__tests__/services/tool-protection-merged-config.test.ts +485 -0
  68. package/src/__tests__/services/tool-protection.service.test.ts +18 -11
  69. package/src/config/__tests__/merged-config.spec.ts +445 -0
  70. package/src/config/remote-config.ts +90 -0
  71. package/src/config.ts +3 -0
  72. package/src/delegation/__tests__/did-key-resolver.test.ts +265 -0
  73. package/src/delegation/__tests__/vc-issuer.test.ts +1 -1
  74. package/src/delegation/did-key-resolver.ts +179 -0
  75. package/src/delegation/utils.ts +179 -0
  76. package/src/identity/idp-token-resolver.ts +41 -7
  77. package/src/identity/idp-token-storage.interface.ts +42 -7
  78. package/src/identity/user-did-manager.ts +185 -29
  79. package/src/index.ts +42 -3
  80. package/src/runtime/base.ts +84 -21
  81. package/src/services/session-registration.service.ts +26 -121
  82. package/src/services/tool-context-builder.ts +75 -10
  83. package/src/services/tool-protection.service.ts +176 -88
  84. package/src/utils/__tests__/did-helpers.test.ts +55 -0
  85. package/src/utils/base58.ts +109 -0
  86. package/src/utils/did-helpers.ts +60 -0
  87. package/dist/__tests__/utils/mock-providers.d.ts +0 -103
  88. package/dist/__tests__/utils/mock-providers.d.ts.map +0 -1
  89. package/dist/__tests__/utils/mock-providers.js +0 -293
  90. package/dist/__tests__/utils/mock-providers.js.map +0 -1
@@ -0,0 +1,445 @@
1
+ /**
2
+ * Tests for Merged Configuration Handling
3
+ *
4
+ * TDD tests for the merged config API response format where tool protections
5
+ * are embedded in the config at `toolProtection.tools`.
6
+ *
7
+ * These tests document the expected behavior BEFORE implementation.
8
+ *
9
+ * @since 1.6.0
10
+ */
11
+
12
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
13
+ import {
14
+ fetchRemoteConfig,
15
+ type RemoteConfigOptions,
16
+ type RemoteConfigCache
17
+ } from '../remote-config.js';
18
+ import type { MergedMCPIServerConfig } from '@kya-os/contracts/dashboard-config';
19
+
20
+ describe('fetchRemoteConfig - Merged Config Format', () => {
21
+ let mockFetch: ReturnType<typeof vi.fn>;
22
+ let mockCache: RemoteConfigCache;
23
+
24
+ beforeEach(() => {
25
+ mockFetch = vi.fn();
26
+ mockCache = {
27
+ get: vi.fn(),
28
+ set: vi.fn()
29
+ };
30
+ });
31
+
32
+ describe('Embedded tool protections', () => {
33
+ it('should handle merged config with toolProtection.tools field', async () => {
34
+ vi.mocked(mockCache.get).mockResolvedValue(null);
35
+
36
+ const mergedConfig: MergedMCPIServerConfig = {
37
+ identity: {
38
+ serverDid: 'did:key:test',
39
+ environment: 'production',
40
+ storageLocation: 'cloudflare-kv'
41
+ },
42
+ proofing: {
43
+ enabled: true,
44
+ destinations: [],
45
+ batchQueue: { maxBatchSize: 10, flushIntervalMs: 5000, maxRetries: 3 }
46
+ },
47
+ delegation: {
48
+ enabled: true,
49
+ enforceStrictly: true,
50
+ verifier: { type: 'agentshield' },
51
+ authorization: {}
52
+ },
53
+ toolProtection: {
54
+ source: 'agentshield',
55
+ tools: {
56
+ greet: {
57
+ requiresDelegation: true,
58
+ requiredScopes: ['greeting:write'],
59
+ riskLevel: 'low'
60
+ },
61
+ checkout: {
62
+ requiresDelegation: true,
63
+ requiredScopes: ['checkout:execute', 'payment:write'],
64
+ riskLevel: 'high'
65
+ }
66
+ }
67
+ },
68
+ audit: {
69
+ enabled: true,
70
+ includeProofHashes: true,
71
+ includePayloads: false
72
+ },
73
+ session: {
74
+ timestampSkewSeconds: 120,
75
+ ttlMinutes: 30
76
+ },
77
+ platform: {
78
+ type: 'cloudflare'
79
+ },
80
+ metadata: {
81
+ version: '1.6.0',
82
+ lastUpdated: new Date().toISOString(),
83
+ source: 'dashboard'
84
+ }
85
+ };
86
+
87
+ mockFetch.mockResolvedValue({
88
+ ok: true,
89
+ json: async () => ({
90
+ success: true,
91
+ data: { config: mergedConfig }
92
+ })
93
+ } as Response);
94
+
95
+ const options: RemoteConfigOptions = {
96
+ apiUrl: 'https://kya.vouched.id',
97
+ apiKey: 'test-key',
98
+ projectId: 'test-project',
99
+ fetchProvider: mockFetch
100
+ };
101
+
102
+ const result = await fetchRemoteConfig(options, mockCache);
103
+
104
+ expect(result).toBeDefined();
105
+ // The toolProtection.tools should be accessible
106
+ expect(result?.toolProtection?.tools).toBeDefined();
107
+ expect(result?.toolProtection?.tools?.greet).toEqual({
108
+ requiresDelegation: true,
109
+ requiredScopes: ['greeting:write'],
110
+ riskLevel: 'low'
111
+ });
112
+ expect(result?.toolProtection?.tools?.checkout).toEqual({
113
+ requiresDelegation: true,
114
+ requiredScopes: ['checkout:execute', 'payment:write'],
115
+ riskLevel: 'high'
116
+ });
117
+ });
118
+
119
+ it('should handle empty tools object when no tools discovered', async () => {
120
+ vi.mocked(mockCache.get).mockResolvedValue(null);
121
+
122
+ const mergedConfig = {
123
+ identity: {
124
+ serverDid: 'did:key:test',
125
+ environment: 'production',
126
+ storageLocation: 'cloudflare-kv'
127
+ },
128
+ proofing: {
129
+ enabled: false,
130
+ destinations: [],
131
+ batchQueue: { maxBatchSize: 10, flushIntervalMs: 5000, maxRetries: 3 }
132
+ },
133
+ delegation: {
134
+ enabled: false,
135
+ enforceStrictly: false,
136
+ verifier: { type: 'memory' },
137
+ authorization: {}
138
+ },
139
+ toolProtection: {
140
+ source: 'agentshield',
141
+ tools: {} // Empty - no tools discovered yet
142
+ },
143
+ audit: {
144
+ enabled: false,
145
+ includeProofHashes: false,
146
+ includePayloads: false
147
+ },
148
+ session: {
149
+ timestampSkewSeconds: 120,
150
+ ttlMinutes: 30
151
+ },
152
+ platform: {
153
+ type: 'node'
154
+ },
155
+ metadata: {
156
+ version: '1.6.0',
157
+ lastUpdated: new Date().toISOString(),
158
+ source: 'dashboard'
159
+ }
160
+ };
161
+
162
+ mockFetch.mockResolvedValue({
163
+ ok: true,
164
+ json: async () => ({
165
+ success: true,
166
+ data: { config: mergedConfig }
167
+ })
168
+ } as Response);
169
+
170
+ const options: RemoteConfigOptions = {
171
+ apiUrl: 'https://kya.vouched.id',
172
+ apiKey: 'test-key',
173
+ projectId: 'test-project',
174
+ fetchProvider: mockFetch
175
+ };
176
+
177
+ const result = await fetchRemoteConfig(options, mockCache);
178
+
179
+ expect(result).toBeDefined();
180
+ expect(result?.toolProtection?.tools).toEqual({});
181
+ });
182
+
183
+ it('should gracefully handle missing tools field for backward compatibility', async () => {
184
+ vi.mocked(mockCache.get).mockResolvedValue(null);
185
+
186
+ // Old API format without tools field
187
+ const oldFormatConfig = {
188
+ identity: {
189
+ serverDid: 'did:key:test',
190
+ environment: 'production',
191
+ storageLocation: 'cloudflare-kv'
192
+ },
193
+ proofing: {
194
+ enabled: false,
195
+ destinations: [],
196
+ batchQueue: { maxBatchSize: 10, flushIntervalMs: 5000, maxRetries: 3 }
197
+ },
198
+ delegation: {
199
+ enabled: false,
200
+ enforceStrictly: false,
201
+ verifier: { type: 'memory' },
202
+ authorization: {}
203
+ },
204
+ toolProtection: {
205
+ source: 'agentshield'
206
+ // No tools field - old format
207
+ },
208
+ audit: {
209
+ enabled: false,
210
+ includeProofHashes: false,
211
+ includePayloads: false
212
+ },
213
+ session: {
214
+ timestampSkewSeconds: 120,
215
+ ttlMinutes: 30
216
+ },
217
+ platform: {
218
+ type: 'node'
219
+ },
220
+ metadata: {
221
+ version: '1.5.0',
222
+ lastUpdated: new Date().toISOString(),
223
+ source: 'dashboard'
224
+ }
225
+ };
226
+
227
+ mockFetch.mockResolvedValue({
228
+ ok: true,
229
+ json: async () => ({
230
+ success: true,
231
+ data: { config: oldFormatConfig }
232
+ })
233
+ } as Response);
234
+
235
+ const options: RemoteConfigOptions = {
236
+ apiUrl: 'https://kya.vouched.id',
237
+ apiKey: 'test-key',
238
+ projectId: 'test-project',
239
+ fetchProvider: mockFetch
240
+ };
241
+
242
+ const result = await fetchRemoteConfig(options, mockCache);
243
+
244
+ // Should not throw and should return config (implementation will add empty tools)
245
+ expect(result).toBeDefined();
246
+ // Current implementation doesn't handle this - test documents expected behavior
247
+ // After implementation, toolProtection.tools should default to {}
248
+ });
249
+ });
250
+
251
+ describe('Response format with embedded tools', () => {
252
+ it('should handle response with deprecated top-level toolProtections field', async () => {
253
+ vi.mocked(mockCache.get).mockResolvedValue(null);
254
+
255
+ const mergedConfig = {
256
+ identity: {
257
+ serverDid: 'did:key:test',
258
+ environment: 'production',
259
+ storageLocation: 'cloudflare-kv'
260
+ },
261
+ proofing: {
262
+ enabled: false,
263
+ destinations: [],
264
+ batchQueue: { maxBatchSize: 10, flushIntervalMs: 5000, maxRetries: 3 }
265
+ },
266
+ delegation: {
267
+ enabled: false,
268
+ enforceStrictly: false,
269
+ verifier: { type: 'memory' },
270
+ authorization: {}
271
+ },
272
+ toolProtection: {
273
+ source: 'agentshield',
274
+ tools: {
275
+ greet: { requiresDelegation: true, requiredScopes: ['greeting:write'] }
276
+ }
277
+ },
278
+ audit: {
279
+ enabled: false,
280
+ includeProofHashes: false,
281
+ includePayloads: false
282
+ },
283
+ session: {
284
+ timestampSkewSeconds: 120,
285
+ ttlMinutes: 30
286
+ },
287
+ platform: {
288
+ type: 'node'
289
+ },
290
+ metadata: {
291
+ version: '1.6.0',
292
+ lastUpdated: new Date().toISOString(),
293
+ source: 'dashboard'
294
+ }
295
+ };
296
+
297
+ // Response includes both embedded tools and deprecated top-level field
298
+ mockFetch.mockResolvedValue({
299
+ ok: true,
300
+ json: async () => ({
301
+ success: true,
302
+ data: {
303
+ config: mergedConfig,
304
+ // Deprecated: This field exists for backward compatibility
305
+ toolProtections: {
306
+ greet: { requiresDelegation: true, requiredScopes: ['greeting:write'] }
307
+ }
308
+ },
309
+ metadata: {
310
+ timestamp: new Date().toISOString(),
311
+ cachedUntil: new Date(Date.now() + 60000).toISOString()
312
+ }
313
+ })
314
+ } as Response);
315
+
316
+ const options: RemoteConfigOptions = {
317
+ apiUrl: 'https://kya.vouched.id',
318
+ apiKey: 'test-key',
319
+ projectId: 'test-project',
320
+ fetchProvider: mockFetch
321
+ };
322
+
323
+ const result = await fetchRemoteConfig(options, mockCache);
324
+
325
+ expect(result).toBeDefined();
326
+ // Should use config.toolProtection.tools (embedded), not top-level toolProtections
327
+ expect(result?.toolProtection?.tools?.greet).toEqual({
328
+ requiresDelegation: true,
329
+ requiredScopes: ['greeting:write']
330
+ });
331
+ });
332
+ });
333
+
334
+ describe('Caching with merged config', () => {
335
+ it('should cache merged config correctly', async () => {
336
+ vi.mocked(mockCache.get).mockResolvedValue(null);
337
+
338
+ const mergedConfig = {
339
+ identity: {
340
+ serverDid: 'did:key:test',
341
+ environment: 'production',
342
+ storageLocation: 'cloudflare-kv'
343
+ },
344
+ proofing: {
345
+ enabled: false,
346
+ destinations: [],
347
+ batchQueue: { maxBatchSize: 10, flushIntervalMs: 5000, maxRetries: 3 }
348
+ },
349
+ delegation: {
350
+ enabled: false,
351
+ enforceStrictly: false,
352
+ verifier: { type: 'memory' },
353
+ authorization: {}
354
+ },
355
+ toolProtection: {
356
+ source: 'agentshield',
357
+ tools: {
358
+ greet: { requiresDelegation: true, requiredScopes: ['greeting:write'] }
359
+ }
360
+ },
361
+ audit: {
362
+ enabled: false,
363
+ includeProofHashes: false,
364
+ includePayloads: false
365
+ },
366
+ session: {
367
+ timestampSkewSeconds: 120,
368
+ ttlMinutes: 30
369
+ },
370
+ platform: {
371
+ type: 'node'
372
+ },
373
+ metadata: {
374
+ version: '1.6.0',
375
+ lastUpdated: new Date().toISOString(),
376
+ source: 'dashboard'
377
+ }
378
+ };
379
+
380
+ mockFetch.mockResolvedValue({
381
+ ok: true,
382
+ json: async () => ({
383
+ success: true,
384
+ data: { config: mergedConfig }
385
+ })
386
+ } as Response);
387
+
388
+ const options: RemoteConfigOptions = {
389
+ apiUrl: 'https://kya.vouched.id',
390
+ apiKey: 'test-key',
391
+ projectId: 'test-project',
392
+ fetchProvider: mockFetch,
393
+ cacheTtl: 300000
394
+ };
395
+
396
+ await fetchRemoteConfig(options, mockCache);
397
+
398
+ expect(mockCache.set).toHaveBeenCalledWith(
399
+ 'config:project:test-project',
400
+ expect.stringContaining('toolProtection'),
401
+ 300000
402
+ );
403
+ });
404
+
405
+ it('should return cached merged config with embedded tools', async () => {
406
+ const cachedConfig = {
407
+ identity: {
408
+ serverDid: 'did:key:test',
409
+ environment: 'production',
410
+ storageLocation: 'cloudflare-kv'
411
+ },
412
+ toolProtection: {
413
+ source: 'agentshield',
414
+ tools: {
415
+ greet: { requiresDelegation: true, requiredScopes: ['greeting:write'] }
416
+ }
417
+ },
418
+ // ... other fields
419
+ };
420
+
421
+ vi.mocked(mockCache.get).mockResolvedValue(
422
+ JSON.stringify({
423
+ config: cachedConfig,
424
+ expiresAt: Date.now() + 60000
425
+ })
426
+ );
427
+
428
+ const options: RemoteConfigOptions = {
429
+ apiUrl: 'https://kya.vouched.id',
430
+ apiKey: 'test-key',
431
+ projectId: 'test-project',
432
+ fetchProvider: mockFetch
433
+ };
434
+
435
+ const result = await fetchRemoteConfig(options, mockCache);
436
+
437
+ expect(result?.toolProtection?.tools?.greet).toEqual({
438
+ requiresDelegation: true,
439
+ requiredScopes: ['greeting:write']
440
+ });
441
+ expect(mockFetch).not.toHaveBeenCalled();
442
+ });
443
+ });
444
+ });
445
+
@@ -8,6 +8,8 @@
8
8
  */
9
9
 
10
10
  import type { MCPIConfig } from '@kya-os/contracts/config';
11
+ import type { MergedMCPIServerConfig } from '@kya-os/contracts/dashboard-config';
12
+ import type { ToolProtection, ToolProtectionMap } from '@kya-os/contracts/tool-protection';
11
13
  import { AGENTSHIELD_ENDPOINTS } from '@kya-os/contracts/agentshield-api';
12
14
 
13
15
  /**
@@ -172,3 +174,91 @@ export async function fetchRemoteConfig(
172
174
  }
173
175
  }
174
176
 
177
+ /**
178
+ * Get tool protection for a specific tool from a merged config
179
+ *
180
+ * This helper function extracts tool protection from a merged config response.
181
+ * It handles both the new format (toolProtection.tools) and returns null
182
+ * for unprotected or unknown tools.
183
+ *
184
+ * @param config - Merged config object (must have toolProtection.tools)
185
+ * @param toolName - Name of the tool to look up
186
+ * @returns Tool protection or null if tool not protected or not found
187
+ *
188
+ * @since 1.6.0
189
+ */
190
+ export function getToolProtection(
191
+ config: { toolProtection?: { tools?: ToolProtectionMap } },
192
+ toolName: string
193
+ ): ToolProtection | null {
194
+ const tools = config?.toolProtection?.tools;
195
+
196
+ if (!tools) {
197
+ return null;
198
+ }
199
+
200
+ // Check for specific tool protection first
201
+ let protection = tools[toolName];
202
+
203
+ // Fall back to wildcard protection if specific tool not found
204
+ if (!protection && tools['*']) {
205
+ protection = tools['*'];
206
+ }
207
+
208
+ // Return null for unprotected tools (requiresDelegation: false) or unknown tools
209
+ if (!protection || !protection.requiresDelegation) {
210
+ return null;
211
+ }
212
+
213
+ return protection;
214
+ }
215
+
216
+ /**
217
+ * Extract tool protections map from merged config
218
+ *
219
+ * This helper function extracts the tool protections map from a merged config.
220
+ * Returns an empty object if no tools are found.
221
+ *
222
+ * @param config - Config object that may contain toolProtection.tools
223
+ * @returns Tool protection map or empty object
224
+ *
225
+ * @since 1.6.0
226
+ */
227
+ export function extractToolProtections(
228
+ config: { toolProtection?: { tools?: ToolProtectionMap } } | null | undefined
229
+ ): ToolProtectionMap {
230
+ if (!config?.toolProtection?.tools) {
231
+ return {};
232
+ }
233
+ return config.toolProtection.tools;
234
+ }
235
+
236
+ /**
237
+ * Check if config has embedded tool protections
238
+ *
239
+ * Utility to check if a config response is in the new merged format
240
+ * with embedded tool protections.
241
+ *
242
+ * @param config - Config object to check
243
+ * @returns True if config has embedded tools, false otherwise
244
+ *
245
+ * @since 1.6.0
246
+ */
247
+ export function hasMergedToolProtections(
248
+ config: unknown
249
+ ): config is { toolProtection: { tools: ToolProtectionMap } } {
250
+ if (!config || typeof config !== 'object') {
251
+ return false;
252
+ }
253
+
254
+ const c = config as { toolProtection?: { tools?: unknown } };
255
+ return (
256
+ c.toolProtection !== undefined &&
257
+ typeof c.toolProtection === 'object' &&
258
+ c.toolProtection !== null &&
259
+ 'tools' in c.toolProtection &&
260
+ typeof c.toolProtection.tools === 'object' &&
261
+ c.toolProtection.tools !== null // typeof null === 'object' in JS
262
+ );
263
+ }
264
+
package/src/config.ts CHANGED
@@ -274,6 +274,9 @@ export type {
274
274
  */
275
275
  export {
276
276
  fetchRemoteConfig,
277
+ getToolProtection,
278
+ extractToolProtections,
279
+ hasMergedToolProtections,
277
280
  type RemoteConfigOptions,
278
281
  type RemoteConfigCache
279
282
  } from './config/remote-config';