@kya-os/mcp-i-core 1.2.3-canary.6 → 1.3.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.
- package/.claude/settings.local.json +9 -0
- package/.turbo/turbo-build.log +4 -0
- package/.turbo/turbo-test$colon$coverage.log +4514 -0
- package/.turbo/turbo-test.log +2973 -0
- package/COMPLIANCE_IMPROVEMENT_REPORT.md +483 -0
- package/Composer 3.md +615 -0
- package/GPT-5.md +1169 -0
- package/OPUS-plan.md +352 -0
- package/PHASE_3_AND_4.1_SUMMARY.md +585 -0
- package/PHASE_3_SUMMARY.md +317 -0
- package/PHASE_4.1.3_SUMMARY.md +428 -0
- package/PHASE_4.1_COMPLETE.md +525 -0
- package/PHASE_4_USER_DID_IDENTITY_LINKING_PLAN.md +1240 -0
- package/SCHEMA_COMPLIANCE_REPORT.md +275 -0
- package/TEST_PLAN.md +571 -0
- package/coverage/coverage-final.json +57 -0
- package/dist/__tests__/utils/mock-providers.d.ts +1 -2
- package/dist/__tests__/utils/mock-providers.d.ts.map +1 -1
- package/dist/__tests__/utils/mock-providers.js.map +1 -1
- package/dist/cache/oauth-config-cache.d.ts +69 -0
- package/dist/cache/oauth-config-cache.d.ts.map +1 -0
- package/dist/cache/oauth-config-cache.js +76 -0
- package/dist/cache/oauth-config-cache.js.map +1 -0
- package/dist/identity/idp-token-resolver.d.ts +53 -0
- package/dist/identity/idp-token-resolver.d.ts.map +1 -0
- package/dist/identity/idp-token-resolver.js +108 -0
- package/dist/identity/idp-token-resolver.js.map +1 -0
- package/dist/identity/idp-token-storage.interface.d.ts +42 -0
- package/dist/identity/idp-token-storage.interface.d.ts.map +1 -0
- package/dist/identity/idp-token-storage.interface.js +12 -0
- package/dist/identity/idp-token-storage.interface.js.map +1 -0
- package/dist/identity/user-did-manager.d.ts +39 -1
- package/dist/identity/user-did-manager.d.ts.map +1 -1
- package/dist/identity/user-did-manager.js +69 -3
- package/dist/identity/user-did-manager.js.map +1 -1
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +39 -1
- package/dist/index.js.map +1 -1
- package/dist/runtime/audit-logger.d.ts +37 -0
- package/dist/runtime/audit-logger.d.ts.map +1 -0
- package/dist/runtime/audit-logger.js +9 -0
- package/dist/runtime/audit-logger.js.map +1 -0
- package/dist/runtime/base.d.ts +58 -2
- package/dist/runtime/base.d.ts.map +1 -1
- package/dist/runtime/base.js +266 -11
- package/dist/runtime/base.js.map +1 -1
- package/dist/services/access-control.service.d.ts.map +1 -1
- package/dist/services/access-control.service.js +200 -35
- package/dist/services/access-control.service.js.map +1 -1
- package/dist/services/authorization/authorization-registry.d.ts +29 -0
- package/dist/services/authorization/authorization-registry.d.ts.map +1 -0
- package/dist/services/authorization/authorization-registry.js +57 -0
- package/dist/services/authorization/authorization-registry.js.map +1 -0
- package/dist/services/authorization/types.d.ts +53 -0
- package/dist/services/authorization/types.d.ts.map +1 -0
- package/dist/services/authorization/types.js +10 -0
- package/dist/services/authorization/types.js.map +1 -0
- package/dist/services/batch-delegation.service.d.ts +53 -0
- package/dist/services/batch-delegation.service.d.ts.map +1 -0
- package/dist/services/batch-delegation.service.js +95 -0
- package/dist/services/batch-delegation.service.js.map +1 -0
- package/dist/services/oauth-config.service.d.ts +53 -0
- package/dist/services/oauth-config.service.d.ts.map +1 -0
- package/dist/services/oauth-config.service.js +117 -0
- package/dist/services/oauth-config.service.js.map +1 -0
- package/dist/services/oauth-provider-registry.d.ts +77 -0
- package/dist/services/oauth-provider-registry.d.ts.map +1 -0
- package/dist/services/oauth-provider-registry.js +112 -0
- package/dist/services/oauth-provider-registry.js.map +1 -0
- package/dist/services/oauth-service.d.ts +77 -0
- package/dist/services/oauth-service.d.ts.map +1 -0
- package/dist/services/oauth-service.js +348 -0
- package/dist/services/oauth-service.js.map +1 -0
- package/dist/services/oauth-token-retrieval.service.d.ts +49 -0
- package/dist/services/oauth-token-retrieval.service.d.ts.map +1 -0
- package/dist/services/oauth-token-retrieval.service.js +150 -0
- package/dist/services/oauth-token-retrieval.service.js.map +1 -0
- package/dist/services/provider-resolver.d.ts +48 -0
- package/dist/services/provider-resolver.d.ts.map +1 -0
- package/dist/services/provider-resolver.js +120 -0
- package/dist/services/provider-resolver.js.map +1 -0
- package/dist/services/provider-validator.d.ts +55 -0
- package/dist/services/provider-validator.d.ts.map +1 -0
- package/dist/services/provider-validator.js +135 -0
- package/dist/services/provider-validator.js.map +1 -0
- package/dist/services/tool-context-builder.d.ts +57 -0
- package/dist/services/tool-context-builder.d.ts.map +1 -0
- package/dist/services/tool-context-builder.js +125 -0
- package/dist/services/tool-context-builder.js.map +1 -0
- package/dist/services/tool-protection.service.d.ts +87 -10
- package/dist/services/tool-protection.service.d.ts.map +1 -1
- package/dist/services/tool-protection.service.js +282 -112
- package/dist/services/tool-protection.service.js.map +1 -1
- package/dist/types/oauth-required-error.d.ts +40 -0
- package/dist/types/oauth-required-error.d.ts.map +1 -0
- package/dist/types/oauth-required-error.js +40 -0
- package/dist/types/oauth-required-error.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 +40 -0
- package/dist/utils/did-helpers.js.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/docs/API_REFERENCE.md +1362 -0
- package/docs/COMPLIANCE_MATRIX.md +691 -0
- package/docs/STATUSLIST2021_GUIDE.md +696 -0
- package/docs/W3C_VC_DELEGATION_GUIDE.md +710 -0
- package/package.json +24 -50
- package/scripts/audit-compliance.ts +724 -0
- package/src/__tests__/cache/tool-protection-cache.test.ts +640 -0
- package/src/__tests__/config/provider-runtime-config.test.ts +309 -0
- package/src/__tests__/delegation-e2e.test.ts +690 -0
- package/src/__tests__/identity/user-did-manager.test.ts +213 -0
- package/src/__tests__/index.test.ts +56 -0
- package/src/__tests__/integration/full-flow.test.ts +776 -0
- package/src/__tests__/integration.test.ts +281 -0
- package/src/__tests__/providers/base.test.ts +173 -0
- package/src/__tests__/providers/memory.test.ts +319 -0
- package/src/__tests__/regression/phase2-regression.test.ts +427 -0
- package/src/__tests__/runtime/audit-logger.test.ts +154 -0
- package/src/__tests__/runtime/base-extensions.test.ts +593 -0
- package/src/__tests__/runtime/base.test.ts +869 -0
- package/src/__tests__/runtime/delegation-flow.test.ts +164 -0
- package/src/__tests__/runtime/proof-client-did.test.ts +375 -0
- package/src/__tests__/runtime/route-interception.test.ts +686 -0
- package/src/__tests__/runtime/tool-protection-enforcement.test.ts +908 -0
- package/src/__tests__/services/agentshield-integration.test.ts +784 -0
- package/src/__tests__/services/provider-resolver-edge-cases.test.ts +487 -0
- package/src/__tests__/services/tool-protection-oauth-provider.test.ts +480 -0
- package/src/__tests__/services/tool-protection.service.test.ts +1366 -0
- package/src/__tests__/utils/mock-providers.ts +340 -0
- package/src/cache/oauth-config-cache.d.ts +69 -0
- package/src/cache/oauth-config-cache.d.ts.map +1 -0
- package/src/cache/oauth-config-cache.js +71 -0
- package/src/cache/oauth-config-cache.js.map +1 -0
- package/src/cache/oauth-config-cache.ts +123 -0
- package/src/cache/tool-protection-cache.ts +171 -0
- package/src/compliance/EXAMPLE.md +412 -0
- package/src/compliance/__tests__/schema-verifier.test.ts +797 -0
- package/src/compliance/index.ts +8 -0
- package/src/compliance/schema-registry.ts +460 -0
- package/src/compliance/schema-verifier.ts +708 -0
- package/src/config/__tests__/remote-config.spec.ts +268 -0
- package/src/config/remote-config.ts +174 -0
- package/src/config.ts +309 -0
- package/src/delegation/__tests__/audience-validator.test.ts +112 -0
- package/src/delegation/__tests__/bitstring.test.ts +346 -0
- package/src/delegation/__tests__/cascading-revocation.test.ts +628 -0
- package/src/delegation/__tests__/delegation-graph.test.ts +584 -0
- package/src/delegation/__tests__/utils.test.ts +152 -0
- package/src/delegation/__tests__/vc-issuer.test.ts +442 -0
- package/src/delegation/__tests__/vc-verifier.test.ts +922 -0
- package/src/delegation/audience-validator.ts +52 -0
- package/src/delegation/bitstring.ts +278 -0
- package/src/delegation/cascading-revocation.ts +370 -0
- package/src/delegation/delegation-graph.ts +299 -0
- package/src/delegation/index.ts +14 -0
- package/src/delegation/statuslist-manager.ts +353 -0
- package/src/delegation/storage/__tests__/memory-graph-storage.test.ts +366 -0
- package/src/delegation/storage/__tests__/memory-statuslist-storage.test.ts +228 -0
- package/src/delegation/storage/index.ts +9 -0
- package/src/delegation/storage/memory-graph-storage.ts +178 -0
- package/src/delegation/storage/memory-statuslist-storage.ts +77 -0
- package/src/delegation/utils.ts +42 -0
- package/src/delegation/vc-issuer.ts +232 -0
- package/src/delegation/vc-verifier.ts +568 -0
- package/src/identity/idp-token-resolver.ts +147 -0
- package/src/identity/idp-token-storage.interface.ts +59 -0
- package/src/identity/user-did-manager.ts +370 -0
- package/src/index.ts +260 -0
- package/src/providers/base.d.ts +91 -0
- package/src/providers/base.d.ts.map +1 -0
- package/src/providers/base.js +38 -0
- package/src/providers/base.js.map +1 -0
- package/src/providers/base.ts +96 -0
- package/src/providers/memory.ts +142 -0
- package/src/runtime/audit-logger.ts +39 -0
- package/src/runtime/base.ts +1329 -0
- package/src/services/__tests__/access-control.integration.test.ts +443 -0
- package/src/services/__tests__/access-control.proof-response-validation.test.ts +578 -0
- package/src/services/__tests__/access-control.service.test.ts +970 -0
- package/src/services/__tests__/batch-delegation.service.test.ts +351 -0
- package/src/services/__tests__/crypto.service.test.ts +531 -0
- package/src/services/__tests__/oauth-provider-registry.test.ts +142 -0
- package/src/services/__tests__/proof-verifier.integration.test.ts +485 -0
- package/src/services/__tests__/proof-verifier.test.ts +489 -0
- package/src/services/__tests__/provider-resolution.integration.test.ts +198 -0
- package/src/services/__tests__/provider-resolver.test.ts +217 -0
- package/src/services/__tests__/storage.service.test.ts +358 -0
- package/src/services/access-control.service.ts +990 -0
- package/src/services/authorization/authorization-registry.ts +66 -0
- package/src/services/authorization/types.ts +71 -0
- package/src/services/batch-delegation.service.ts +137 -0
- package/src/services/crypto.service.ts +302 -0
- package/src/services/errors.ts +76 -0
- package/src/services/index.ts +9 -0
- package/src/services/oauth-config.service.d.ts +53 -0
- package/src/services/oauth-config.service.d.ts.map +1 -0
- package/src/services/oauth-config.service.js +113 -0
- package/src/services/oauth-config.service.js.map +1 -0
- package/src/services/oauth-config.service.ts +166 -0
- package/src/services/oauth-provider-registry.d.ts +57 -0
- package/src/services/oauth-provider-registry.d.ts.map +1 -0
- package/src/services/oauth-provider-registry.js +73 -0
- package/src/services/oauth-provider-registry.js.map +1 -0
- package/src/services/oauth-provider-registry.ts +123 -0
- package/src/services/oauth-service.ts +510 -0
- package/src/services/oauth-token-retrieval.service.ts +245 -0
- package/src/services/proof-verifier.ts +478 -0
- package/src/services/provider-resolver.d.ts +48 -0
- package/src/services/provider-resolver.d.ts.map +1 -0
- package/src/services/provider-resolver.js +106 -0
- package/src/services/provider-resolver.js.map +1 -0
- package/src/services/provider-resolver.ts +144 -0
- package/src/services/provider-validator.ts +170 -0
- package/src/services/storage.service.ts +566 -0
- package/src/services/tool-context-builder.ts +172 -0
- package/src/services/tool-protection.service.ts +958 -0
- package/src/types/oauth-required-error.ts +63 -0
- package/src/types/tool-protection.ts +155 -0
- package/src/utils/__tests__/did-helpers.test.ts +101 -0
- package/src/utils/base64.ts +148 -0
- package/src/utils/cors.ts +83 -0
- package/src/utils/did-helpers.ts +150 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/storage-keys.ts +278 -0
- package/tsconfig.json +21 -0
- package/vitest.config.ts +56 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Remote Configuration Fetching
|
|
3
|
+
*
|
|
4
|
+
* Validates that remote config fetching works correctly with caching
|
|
5
|
+
* and fallback behavior.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
9
|
+
import {
|
|
10
|
+
fetchRemoteConfig,
|
|
11
|
+
type RemoteConfigOptions,
|
|
12
|
+
type RemoteConfigCache
|
|
13
|
+
} from '../remote-config.js';
|
|
14
|
+
import type { MCPIConfig } from '@kya-os/contracts/config';
|
|
15
|
+
|
|
16
|
+
describe('fetchRemoteConfig', () => {
|
|
17
|
+
let mockFetch: ReturnType<typeof vi.fn>;
|
|
18
|
+
let mockCache: RemoteConfigCache;
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
mockFetch = vi.fn();
|
|
22
|
+
mockCache = {
|
|
23
|
+
get: vi.fn(),
|
|
24
|
+
set: vi.fn()
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('Cache hit scenario', () => {
|
|
29
|
+
it('should return cached config if available and not expired', async () => {
|
|
30
|
+
const cachedConfig: MCPIConfig = {
|
|
31
|
+
environment: 'production',
|
|
32
|
+
identity: { enabled: true, environment: 'production' }
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
vi.mocked(mockCache.get).mockResolvedValue(
|
|
36
|
+
JSON.stringify({
|
|
37
|
+
config: cachedConfig,
|
|
38
|
+
expiresAt: Date.now() + 60000 // 1 minute in future
|
|
39
|
+
})
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const options: RemoteConfigOptions = {
|
|
43
|
+
apiUrl: 'https://kya.vouched.id',
|
|
44
|
+
apiKey: 'test-key',
|
|
45
|
+
projectId: 'test-project',
|
|
46
|
+
fetchProvider: mockFetch
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const result = await fetchRemoteConfig(options, mockCache);
|
|
50
|
+
|
|
51
|
+
expect(result).toEqual(cachedConfig);
|
|
52
|
+
expect(mockCache.get).toHaveBeenCalledWith('config:project:test-project');
|
|
53
|
+
expect(mockFetch).not.toHaveBeenCalled();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should fetch fresh config if cache expired', async () => {
|
|
57
|
+
const cachedConfig: MCPIConfig = {
|
|
58
|
+
environment: 'production'
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
vi.mocked(mockCache.get).mockResolvedValue(
|
|
62
|
+
JSON.stringify({
|
|
63
|
+
config: cachedConfig,
|
|
64
|
+
expiresAt: Date.now() - 1000 // Expired
|
|
65
|
+
})
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const freshConfig: MCPIConfig = {
|
|
69
|
+
environment: 'production',
|
|
70
|
+
identity: { enabled: true, environment: 'production' }
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
mockFetch.mockResolvedValue({
|
|
74
|
+
ok: true,
|
|
75
|
+
json: async () => ({ success: true, data: freshConfig })
|
|
76
|
+
} as Response);
|
|
77
|
+
|
|
78
|
+
const options: RemoteConfigOptions = {
|
|
79
|
+
apiUrl: 'https://kya.vouched.id',
|
|
80
|
+
apiKey: 'test-key',
|
|
81
|
+
projectId: 'test-project',
|
|
82
|
+
fetchProvider: mockFetch
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const result = await fetchRemoteConfig(options, mockCache);
|
|
86
|
+
|
|
87
|
+
expect(result).toEqual(freshConfig);
|
|
88
|
+
expect(mockFetch).toHaveBeenCalled();
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('Cache miss scenario', () => {
|
|
93
|
+
it('should fetch from API when cache is empty', async () => {
|
|
94
|
+
vi.mocked(mockCache.get).mockResolvedValue(null);
|
|
95
|
+
|
|
96
|
+
const config: MCPIConfig = {
|
|
97
|
+
environment: 'production',
|
|
98
|
+
identity: { enabled: true, environment: 'production' }
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
mockFetch.mockResolvedValue({
|
|
102
|
+
ok: true,
|
|
103
|
+
json: async () => ({ success: true, data: config })
|
|
104
|
+
} as Response);
|
|
105
|
+
|
|
106
|
+
const options: RemoteConfigOptions = {
|
|
107
|
+
apiUrl: 'https://kya.vouched.id',
|
|
108
|
+
apiKey: 'test-key',
|
|
109
|
+
projectId: 'test-project',
|
|
110
|
+
fetchProvider: mockFetch
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const result = await fetchRemoteConfig(options, mockCache);
|
|
114
|
+
|
|
115
|
+
expect(result).toEqual(config);
|
|
116
|
+
expect(mockCache.set).toHaveBeenCalled();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should use agentDid when projectId not available', async () => {
|
|
120
|
+
vi.mocked(mockCache.get).mockResolvedValue(null);
|
|
121
|
+
|
|
122
|
+
const config: MCPIConfig = {
|
|
123
|
+
environment: 'production'
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
mockFetch.mockResolvedValue({
|
|
127
|
+
ok: true,
|
|
128
|
+
json: async () => ({ success: true, data: config })
|
|
129
|
+
} as Response);
|
|
130
|
+
|
|
131
|
+
const options: RemoteConfigOptions = {
|
|
132
|
+
apiUrl: 'https://kya.vouched.id',
|
|
133
|
+
apiKey: 'test-key',
|
|
134
|
+
agentDid: 'did:key:test',
|
|
135
|
+
fetchProvider: mockFetch
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const result = await fetchRemoteConfig(options, mockCache);
|
|
139
|
+
|
|
140
|
+
expect(result).toEqual(config);
|
|
141
|
+
expect(mockCache.get).toHaveBeenCalledWith('config:agent:did:key:test');
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe('Error handling', () => {
|
|
146
|
+
it('should return null if API request fails', async () => {
|
|
147
|
+
vi.mocked(mockCache.get).mockResolvedValue(null);
|
|
148
|
+
|
|
149
|
+
mockFetch.mockResolvedValue({
|
|
150
|
+
ok: false,
|
|
151
|
+
status: 404,
|
|
152
|
+
statusText: 'Not Found'
|
|
153
|
+
} as Response);
|
|
154
|
+
|
|
155
|
+
const options: RemoteConfigOptions = {
|
|
156
|
+
apiUrl: 'https://kya.vouched.id',
|
|
157
|
+
apiKey: 'test-key',
|
|
158
|
+
projectId: 'test-project',
|
|
159
|
+
fetchProvider: mockFetch
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const result = await fetchRemoteConfig(options, mockCache);
|
|
163
|
+
|
|
164
|
+
expect(result).toBeNull();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should return null if API throws error', async () => {
|
|
168
|
+
vi.mocked(mockCache.get).mockResolvedValue(null);
|
|
169
|
+
|
|
170
|
+
mockFetch.mockRejectedValue(new Error('Network error'));
|
|
171
|
+
|
|
172
|
+
const options: RemoteConfigOptions = {
|
|
173
|
+
apiUrl: 'https://kya.vouched.id',
|
|
174
|
+
apiKey: 'test-key',
|
|
175
|
+
projectId: 'test-project',
|
|
176
|
+
fetchProvider: mockFetch
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const result = await fetchRemoteConfig(options, mockCache);
|
|
180
|
+
|
|
181
|
+
expect(result).toBeNull();
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should return null if neither projectId nor agentDid provided', async () => {
|
|
185
|
+
const options: RemoteConfigOptions = {
|
|
186
|
+
apiUrl: 'https://kya.vouched.id',
|
|
187
|
+
apiKey: 'test-key',
|
|
188
|
+
fetchProvider: mockFetch
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const result = await fetchRemoteConfig(options, mockCache);
|
|
192
|
+
|
|
193
|
+
expect(result).toBeNull();
|
|
194
|
+
expect(mockFetch).not.toHaveBeenCalled();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('should handle cache read errors gracefully', async () => {
|
|
198
|
+
vi.mocked(mockCache.get).mockRejectedValue(new Error('Cache error'));
|
|
199
|
+
|
|
200
|
+
const config: MCPIConfig = {
|
|
201
|
+
environment: 'production'
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
mockFetch.mockResolvedValue({
|
|
205
|
+
ok: true,
|
|
206
|
+
json: async () => ({ success: true, data: config })
|
|
207
|
+
} as Response);
|
|
208
|
+
|
|
209
|
+
const options: RemoteConfigOptions = {
|
|
210
|
+
apiUrl: 'https://kya.vouched.id',
|
|
211
|
+
apiKey: 'test-key',
|
|
212
|
+
projectId: 'test-project',
|
|
213
|
+
fetchProvider: mockFetch
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const result = await fetchRemoteConfig(options, mockCache);
|
|
217
|
+
|
|
218
|
+
expect(result).toEqual(config);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe('Response format handling', () => {
|
|
223
|
+
it('should handle different API response formats', async () => {
|
|
224
|
+
vi.mocked(mockCache.get).mockResolvedValue(null);
|
|
225
|
+
|
|
226
|
+
const config: MCPIConfig = {
|
|
227
|
+
environment: 'production'
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
// Format 1: { config: {...} }
|
|
231
|
+
mockFetch.mockResolvedValueOnce({
|
|
232
|
+
ok: true,
|
|
233
|
+
json: async () => ({ config })
|
|
234
|
+
} as Response);
|
|
235
|
+
|
|
236
|
+
const options: RemoteConfigOptions = {
|
|
237
|
+
apiUrl: 'https://kya.vouched.id',
|
|
238
|
+
apiKey: 'test-key',
|
|
239
|
+
projectId: 'test-project',
|
|
240
|
+
fetchProvider: mockFetch
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
const result1 = await fetchRemoteConfig(options, mockCache);
|
|
244
|
+
expect(result1).toEqual(config);
|
|
245
|
+
|
|
246
|
+
// Format 2: { data: { config: {...} } }
|
|
247
|
+
mockCache.get.mockResolvedValue(null);
|
|
248
|
+
mockFetch.mockResolvedValueOnce({
|
|
249
|
+
ok: true,
|
|
250
|
+
json: async () => ({ data: { config } })
|
|
251
|
+
} as Response);
|
|
252
|
+
|
|
253
|
+
const result2 = await fetchRemoteConfig(options, mockCache);
|
|
254
|
+
expect(result2).toEqual(config);
|
|
255
|
+
|
|
256
|
+
// Format 3: { success: true, data: {...} }
|
|
257
|
+
mockCache.get.mockResolvedValue(null);
|
|
258
|
+
mockFetch.mockResolvedValueOnce({
|
|
259
|
+
ok: true,
|
|
260
|
+
json: async () => ({ success: true, data: config })
|
|
261
|
+
} as Response);
|
|
262
|
+
|
|
263
|
+
const result3 = await fetchRemoteConfig(options, mockCache);
|
|
264
|
+
expect(result3).toEqual(config);
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote Configuration Fetching
|
|
3
|
+
*
|
|
4
|
+
* Service for fetching configuration from remote APIs (AgentShield dashboard)
|
|
5
|
+
* with caching support for performance optimization.
|
|
6
|
+
*
|
|
7
|
+
* @module @kya-os/mcp-i-core/config/remote-config
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { MCPIConfig } from '@kya-os/contracts/config';
|
|
11
|
+
import { AGENTSHIELD_ENDPOINTS } from '@kya-os/contracts/agentshield-api';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Options for fetching remote configuration
|
|
15
|
+
*/
|
|
16
|
+
export interface RemoteConfigOptions {
|
|
17
|
+
/**
|
|
18
|
+
* API base URL
|
|
19
|
+
* @example 'https://kya.vouched.id'
|
|
20
|
+
*/
|
|
21
|
+
apiUrl: string;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* API key for authentication
|
|
25
|
+
*/
|
|
26
|
+
apiKey: string;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Project ID (optional, preferred over agentDid)
|
|
30
|
+
* Used for project-scoped configuration
|
|
31
|
+
*/
|
|
32
|
+
projectId?: string;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Agent DID (optional, used when projectId not available)
|
|
36
|
+
* Used for agent-scoped configuration
|
|
37
|
+
*/
|
|
38
|
+
agentDid?: string;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Cache TTL in milliseconds
|
|
42
|
+
* @default 300000 (5 minutes)
|
|
43
|
+
*/
|
|
44
|
+
cacheTtl?: number;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Fetch provider function
|
|
48
|
+
* Platform-agnostic fetch implementation
|
|
49
|
+
*/
|
|
50
|
+
fetchProvider: (url: string, options: RequestInit) => Promise<Response>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Cache interface for remote configuration
|
|
55
|
+
* Abstracts platform-specific caching (KV, Redis, Memory, etc.)
|
|
56
|
+
*/
|
|
57
|
+
export interface RemoteConfigCache {
|
|
58
|
+
/**
|
|
59
|
+
* Get a cached value
|
|
60
|
+
*/
|
|
61
|
+
get(key: string): Promise<string | null>;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Set a cached value with TTL
|
|
65
|
+
*/
|
|
66
|
+
set(key: string, value: string, ttl: number): Promise<void>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Fetch configuration from remote API (AgentShield dashboard)
|
|
71
|
+
*
|
|
72
|
+
* Attempts to fetch configuration from the AgentShield API with caching support.
|
|
73
|
+
* Falls back gracefully if remote fetch fails.
|
|
74
|
+
*
|
|
75
|
+
* @param options - Remote config options
|
|
76
|
+
* @param cache - Optional cache implementation
|
|
77
|
+
* @returns Configuration object or null if fetch fails
|
|
78
|
+
*/
|
|
79
|
+
export async function fetchRemoteConfig(
|
|
80
|
+
options: RemoteConfigOptions,
|
|
81
|
+
cache?: RemoteConfigCache
|
|
82
|
+
): Promise<MCPIConfig | null> {
|
|
83
|
+
const { apiUrl, apiKey, projectId, agentDid, cacheTtl = 300000, fetchProvider } = options;
|
|
84
|
+
|
|
85
|
+
// Generate cache key
|
|
86
|
+
const cacheKey = projectId
|
|
87
|
+
? `config:project:${projectId}`
|
|
88
|
+
: agentDid
|
|
89
|
+
? `config:agent:${agentDid}`
|
|
90
|
+
: null;
|
|
91
|
+
|
|
92
|
+
// Try cache first
|
|
93
|
+
if (cache && cacheKey) {
|
|
94
|
+
try {
|
|
95
|
+
const cached = await cache.get(cacheKey);
|
|
96
|
+
if (cached) {
|
|
97
|
+
try {
|
|
98
|
+
const parsed = JSON.parse(cached) as { config: MCPIConfig; expiresAt: number };
|
|
99
|
+
if (parsed.expiresAt > Date.now()) {
|
|
100
|
+
return parsed.config;
|
|
101
|
+
}
|
|
102
|
+
} catch {
|
|
103
|
+
// Invalid cache entry, continue to fetch
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
} catch (error) {
|
|
107
|
+
// Cache read failed, continue to fetch
|
|
108
|
+
console.warn('[RemoteConfig] Cache read failed:', error);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Fetch from API
|
|
113
|
+
try {
|
|
114
|
+
// Build API URL
|
|
115
|
+
let url: string;
|
|
116
|
+
if (projectId) {
|
|
117
|
+
// Use project-scoped endpoint (preferred)
|
|
118
|
+
url = `${apiUrl}${AGENTSHIELD_ENDPOINTS.CONFIG(projectId)}`;
|
|
119
|
+
} else if (agentDid) {
|
|
120
|
+
// Use agent-scoped endpoint
|
|
121
|
+
url = `${apiUrl}/api/v1/bouncer/config?agent_did=${encodeURIComponent(agentDid)}`;
|
|
122
|
+
} else {
|
|
123
|
+
console.warn('[RemoteConfig] Neither projectId nor agentDid provided');
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const response = await fetchProvider(url, {
|
|
128
|
+
headers: {
|
|
129
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
130
|
+
'Content-Type': 'application/json'
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
if (!response.ok) {
|
|
135
|
+
console.warn(`[RemoteConfig] API returned ${response.status}: ${response.statusText}`);
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const data = await response.json();
|
|
140
|
+
|
|
141
|
+
// Extract config from API response
|
|
142
|
+
// API response format: { success: boolean, data: { config: MCPIConfig } }
|
|
143
|
+
const responseData = data as { config?: MCPIConfig; data?: { config?: MCPIConfig }; success?: boolean };
|
|
144
|
+
const config = responseData.config || responseData.data?.config || (responseData.success ? responseData.data as MCPIConfig | null : null) as MCPIConfig | null;
|
|
145
|
+
|
|
146
|
+
if (!config) {
|
|
147
|
+
console.warn('[RemoteConfig] No config found in API response');
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Cache the result
|
|
152
|
+
if (cache && cacheKey) {
|
|
153
|
+
try {
|
|
154
|
+
await cache.set(
|
|
155
|
+
cacheKey,
|
|
156
|
+
JSON.stringify({
|
|
157
|
+
config,
|
|
158
|
+
expiresAt: Date.now() + cacheTtl
|
|
159
|
+
}),
|
|
160
|
+
cacheTtl
|
|
161
|
+
);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
// Cache write failed, but we got the config so continue
|
|
164
|
+
console.warn('[RemoteConfig] Cache write failed:', error);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return config as MCPIConfig;
|
|
169
|
+
} catch (error) {
|
|
170
|
+
console.warn('[RemoteConfig] Failed to fetch config:', error);
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|