@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.
- package/.claude/settings.local.json +9 -0
- package/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-test$colon$coverage.log +3419 -3072
- package/.turbo/turbo-test.log +1805 -1680
- package/coverage/coverage-final.json +59 -56
- package/dist/config/remote-config.d.ts +51 -0
- package/dist/config/remote-config.d.ts.map +1 -1
- package/dist/config/remote-config.js +74 -0
- package/dist/config/remote-config.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +4 -1
- package/dist/config.js.map +1 -1
- package/dist/delegation/did-key-resolver.d.ts +64 -0
- package/dist/delegation/did-key-resolver.d.ts.map +1 -0
- package/dist/delegation/did-key-resolver.js +159 -0
- package/dist/delegation/did-key-resolver.js.map +1 -0
- package/dist/delegation/utils.d.ts +76 -0
- package/dist/delegation/utils.d.ts.map +1 -1
- package/dist/delegation/utils.js +117 -0
- package/dist/delegation/utils.js.map +1 -1
- package/dist/identity/idp-token-resolver.d.ts +17 -1
- package/dist/identity/idp-token-resolver.d.ts.map +1 -1
- package/dist/identity/idp-token-resolver.js +34 -6
- package/dist/identity/idp-token-resolver.js.map +1 -1
- package/dist/identity/idp-token-storage.interface.d.ts +38 -7
- package/dist/identity/idp-token-storage.interface.d.ts.map +1 -1
- package/dist/identity/idp-token-storage.interface.js +2 -0
- package/dist/identity/idp-token-storage.interface.js.map +1 -1
- package/dist/identity/user-did-manager.d.ts +95 -12
- package/dist/identity/user-did-manager.d.ts.map +1 -1
- package/dist/identity/user-did-manager.js +107 -25
- package/dist/identity/user-did-manager.js.map +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -2
- package/dist/index.js.map +1 -1
- package/dist/runtime/base.d.ts +25 -8
- package/dist/runtime/base.d.ts.map +1 -1
- package/dist/runtime/base.js +74 -21
- package/dist/runtime/base.js.map +1 -1
- package/dist/services/session-registration.service.d.ts.map +1 -1
- package/dist/services/session-registration.service.js +10 -90
- package/dist/services/session-registration.service.js.map +1 -1
- package/dist/services/tool-context-builder.d.ts +18 -1
- package/dist/services/tool-context-builder.d.ts.map +1 -1
- package/dist/services/tool-context-builder.js +63 -10
- package/dist/services/tool-context-builder.js.map +1 -1
- package/dist/services/tool-protection.service.d.ts +6 -3
- package/dist/services/tool-protection.service.d.ts.map +1 -1
- package/dist/services/tool-protection.service.js +89 -34
- package/dist/services/tool-protection.service.js.map +1 -1
- package/dist/utils/base58.d.ts +31 -0
- package/dist/utils/base58.d.ts.map +1 -0
- package/dist/utils/base58.js +103 -0
- package/dist/utils/base58.js.map +1 -0
- package/dist/utils/did-helpers.d.ts +33 -0
- package/dist/utils/did-helpers.d.ts.map +1 -1
- package/dist/utils/did-helpers.js +53 -0
- package/dist/utils/did-helpers.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/identity/user-did-manager.test.ts +64 -45
- package/src/__tests__/integration/full-flow.test.ts +23 -10
- package/src/__tests__/runtime/base-extensions.test.ts +23 -21
- package/src/__tests__/runtime/proof-client-did.test.ts +19 -18
- package/src/__tests__/services/agentshield-integration.test.ts +10 -3
- package/src/__tests__/services/tool-protection-merged-config.test.ts +485 -0
- package/src/__tests__/services/tool-protection.service.test.ts +18 -11
- package/src/config/__tests__/merged-config.spec.ts +445 -0
- package/src/config/remote-config.ts +90 -0
- package/src/config.ts +3 -0
- package/src/delegation/__tests__/did-key-resolver.test.ts +265 -0
- package/src/delegation/__tests__/vc-issuer.test.ts +1 -1
- package/src/delegation/did-key-resolver.ts +179 -0
- package/src/delegation/utils.ts +179 -0
- package/src/identity/idp-token-resolver.ts +41 -7
- package/src/identity/idp-token-storage.interface.ts +42 -7
- package/src/identity/user-did-manager.ts +185 -29
- package/src/index.ts +42 -3
- package/src/runtime/base.ts +84 -21
- package/src/services/session-registration.service.ts +26 -121
- package/src/services/tool-context-builder.ts +75 -10
- package/src/services/tool-protection.service.ts +176 -88
- package/src/utils/__tests__/did-helpers.test.ts +55 -0
- package/src/utils/base58.ts +109 -0
- package/src/utils/did-helpers.ts +60 -0
- package/dist/__tests__/utils/mock-providers.d.ts +0 -103
- package/dist/__tests__/utils/mock-providers.d.ts.map +0 -1
- package/dist/__tests__/utils/mock-providers.js +0 -293
- 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