@kya-os/mcp-i-core 1.3.12 → 1.3.14

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 (254) hide show
  1. package/dist/config/remote-config.js +9 -12
  2. package/dist/runtime/base.js +11 -0
  3. package/dist/services/access-control.service.js +5 -0
  4. package/dist/services/tool-protection.service.js +17 -8
  5. package/package.json +2 -2
  6. package/.turbo/turbo-build.log +0 -4
  7. package/.turbo/turbo-test$colon$coverage.log +0 -4586
  8. package/.turbo/turbo-test.log +0 -3169
  9. package/COMPLIANCE_IMPROVEMENT_REPORT.md +0 -483
  10. package/Composer 3.md +0 -615
  11. package/GPT-5.md +0 -1169
  12. package/OPUS-plan.md +0 -352
  13. package/PHASE_3_AND_4.1_SUMMARY.md +0 -585
  14. package/PHASE_3_SUMMARY.md +0 -317
  15. package/PHASE_4.1.3_SUMMARY.md +0 -428
  16. package/PHASE_4.1_COMPLETE.md +0 -525
  17. package/PHASE_4_USER_DID_IDENTITY_LINKING_PLAN.md +0 -1240
  18. package/SCHEMA_COMPLIANCE_REPORT.md +0 -275
  19. package/TEST_PLAN.md +0 -571
  20. package/coverage/coverage-final.json +0 -60
  21. package/dist/cache/oauth-config-cache.d.ts.map +0 -1
  22. package/dist/cache/oauth-config-cache.js.map +0 -1
  23. package/dist/cache/tool-protection-cache.d.ts.map +0 -1
  24. package/dist/cache/tool-protection-cache.js.map +0 -1
  25. package/dist/compliance/index.d.ts.map +0 -1
  26. package/dist/compliance/index.js.map +0 -1
  27. package/dist/compliance/schema-registry.d.ts.map +0 -1
  28. package/dist/compliance/schema-registry.js.map +0 -1
  29. package/dist/compliance/schema-verifier.d.ts.map +0 -1
  30. package/dist/compliance/schema-verifier.js.map +0 -1
  31. package/dist/config/remote-config.d.ts.map +0 -1
  32. package/dist/config/remote-config.js.map +0 -1
  33. package/dist/config.d.ts.map +0 -1
  34. package/dist/config.js.map +0 -1
  35. package/dist/delegation/audience-validator.d.ts.map +0 -1
  36. package/dist/delegation/audience-validator.js.map +0 -1
  37. package/dist/delegation/bitstring.d.ts.map +0 -1
  38. package/dist/delegation/bitstring.js.map +0 -1
  39. package/dist/delegation/cascading-revocation.d.ts.map +0 -1
  40. package/dist/delegation/cascading-revocation.js.map +0 -1
  41. package/dist/delegation/delegation-graph.d.ts.map +0 -1
  42. package/dist/delegation/delegation-graph.js.map +0 -1
  43. package/dist/delegation/did-key-resolver.d.ts.map +0 -1
  44. package/dist/delegation/did-key-resolver.js.map +0 -1
  45. package/dist/delegation/index.d.ts.map +0 -1
  46. package/dist/delegation/index.js.map +0 -1
  47. package/dist/delegation/statuslist-manager.d.ts.map +0 -1
  48. package/dist/delegation/statuslist-manager.js.map +0 -1
  49. package/dist/delegation/storage/index.d.ts.map +0 -1
  50. package/dist/delegation/storage/index.js.map +0 -1
  51. package/dist/delegation/storage/memory-graph-storage.d.ts.map +0 -1
  52. package/dist/delegation/storage/memory-graph-storage.js.map +0 -1
  53. package/dist/delegation/storage/memory-statuslist-storage.d.ts.map +0 -1
  54. package/dist/delegation/storage/memory-statuslist-storage.js.map +0 -1
  55. package/dist/delegation/utils.d.ts.map +0 -1
  56. package/dist/delegation/utils.js.map +0 -1
  57. package/dist/delegation/vc-issuer.d.ts.map +0 -1
  58. package/dist/delegation/vc-issuer.js.map +0 -1
  59. package/dist/delegation/vc-verifier.d.ts.map +0 -1
  60. package/dist/delegation/vc-verifier.js.map +0 -1
  61. package/dist/identity/idp-token-resolver.d.ts.map +0 -1
  62. package/dist/identity/idp-token-resolver.js.map +0 -1
  63. package/dist/identity/idp-token-storage.interface.d.ts.map +0 -1
  64. package/dist/identity/idp-token-storage.interface.js.map +0 -1
  65. package/dist/identity/user-did-manager.d.ts.map +0 -1
  66. package/dist/identity/user-did-manager.js.map +0 -1
  67. package/dist/index.d.ts.map +0 -1
  68. package/dist/index.js.map +0 -1
  69. package/dist/providers/base.d.ts.map +0 -1
  70. package/dist/providers/base.js.map +0 -1
  71. package/dist/providers/memory.d.ts.map +0 -1
  72. package/dist/providers/memory.js.map +0 -1
  73. package/dist/runtime/audit-logger.d.ts.map +0 -1
  74. package/dist/runtime/audit-logger.js.map +0 -1
  75. package/dist/runtime/base.d.ts.map +0 -1
  76. package/dist/runtime/base.js.map +0 -1
  77. package/dist/services/access-control.service.d.ts.map +0 -1
  78. package/dist/services/access-control.service.js.map +0 -1
  79. package/dist/services/authorization/authorization-registry.d.ts.map +0 -1
  80. package/dist/services/authorization/authorization-registry.js.map +0 -1
  81. package/dist/services/authorization/types.d.ts.map +0 -1
  82. package/dist/services/authorization/types.js.map +0 -1
  83. package/dist/services/batch-delegation.service.d.ts.map +0 -1
  84. package/dist/services/batch-delegation.service.js.map +0 -1
  85. package/dist/services/crypto.service.d.ts.map +0 -1
  86. package/dist/services/crypto.service.js.map +0 -1
  87. package/dist/services/errors.d.ts.map +0 -1
  88. package/dist/services/errors.js.map +0 -1
  89. package/dist/services/index.d.ts.map +0 -1
  90. package/dist/services/index.js.map +0 -1
  91. package/dist/services/oauth-config.service.d.ts.map +0 -1
  92. package/dist/services/oauth-config.service.js.map +0 -1
  93. package/dist/services/oauth-provider-registry.d.ts.map +0 -1
  94. package/dist/services/oauth-provider-registry.js.map +0 -1
  95. package/dist/services/oauth-service.d.ts.map +0 -1
  96. package/dist/services/oauth-service.js.map +0 -1
  97. package/dist/services/oauth-token-retrieval.service.d.ts.map +0 -1
  98. package/dist/services/oauth-token-retrieval.service.js.map +0 -1
  99. package/dist/services/proof-verifier.d.ts.map +0 -1
  100. package/dist/services/proof-verifier.js.map +0 -1
  101. package/dist/services/provider-resolver.d.ts.map +0 -1
  102. package/dist/services/provider-resolver.js.map +0 -1
  103. package/dist/services/provider-validator.d.ts.map +0 -1
  104. package/dist/services/provider-validator.js.map +0 -1
  105. package/dist/services/session-registration.service.d.ts.map +0 -1
  106. package/dist/services/session-registration.service.js.map +0 -1
  107. package/dist/services/storage.service.d.ts.map +0 -1
  108. package/dist/services/storage.service.js.map +0 -1
  109. package/dist/services/tool-context-builder.d.ts.map +0 -1
  110. package/dist/services/tool-context-builder.js.map +0 -1
  111. package/dist/services/tool-protection.service.d.ts.map +0 -1
  112. package/dist/services/tool-protection.service.js.map +0 -1
  113. package/dist/types/oauth-required-error.d.ts.map +0 -1
  114. package/dist/types/oauth-required-error.js.map +0 -1
  115. package/dist/types/tool-protection.d.ts.map +0 -1
  116. package/dist/types/tool-protection.js.map +0 -1
  117. package/dist/utils/base58.d.ts.map +0 -1
  118. package/dist/utils/base58.js.map +0 -1
  119. package/dist/utils/base64.d.ts.map +0 -1
  120. package/dist/utils/base64.js.map +0 -1
  121. package/dist/utils/cors.d.ts.map +0 -1
  122. package/dist/utils/cors.js.map +0 -1
  123. package/dist/utils/did-helpers.d.ts.map +0 -1
  124. package/dist/utils/did-helpers.js.map +0 -1
  125. package/dist/utils/index.d.ts.map +0 -1
  126. package/dist/utils/index.js.map +0 -1
  127. package/dist/utils/storage-keys.d.ts.map +0 -1
  128. package/dist/utils/storage-keys.js.map +0 -1
  129. package/docs/API_REFERENCE.md +0 -1362
  130. package/docs/COMPLIANCE_MATRIX.md +0 -691
  131. package/docs/STATUSLIST2021_GUIDE.md +0 -696
  132. package/docs/W3C_VC_DELEGATION_GUIDE.md +0 -710
  133. package/src/__tests__/cache/tool-protection-cache.test.ts +0 -640
  134. package/src/__tests__/config/provider-runtime-config.test.ts +0 -309
  135. package/src/__tests__/delegation-e2e.test.ts +0 -690
  136. package/src/__tests__/identity/user-did-manager.test.ts +0 -232
  137. package/src/__tests__/index.test.ts +0 -56
  138. package/src/__tests__/integration/full-flow.test.ts +0 -789
  139. package/src/__tests__/integration.test.ts +0 -281
  140. package/src/__tests__/providers/base.test.ts +0 -173
  141. package/src/__tests__/providers/memory.test.ts +0 -319
  142. package/src/__tests__/regression/phase2-regression.test.ts +0 -429
  143. package/src/__tests__/runtime/audit-logger.test.ts +0 -154
  144. package/src/__tests__/runtime/base-extensions.test.ts +0 -595
  145. package/src/__tests__/runtime/base.test.ts +0 -869
  146. package/src/__tests__/runtime/delegation-flow.test.ts +0 -164
  147. package/src/__tests__/runtime/proof-client-did.test.ts +0 -376
  148. package/src/__tests__/runtime/route-interception.test.ts +0 -686
  149. package/src/__tests__/runtime/tool-protection-enforcement.test.ts +0 -908
  150. package/src/__tests__/services/agentshield-integration.test.ts +0 -791
  151. package/src/__tests__/services/cache-busting.test.ts +0 -125
  152. package/src/__tests__/services/oauth-service-pkce.test.ts +0 -556
  153. package/src/__tests__/services/provider-resolver-edge-cases.test.ts +0 -591
  154. package/src/__tests__/services/tool-protection-merged-config.test.ts +0 -485
  155. package/src/__tests__/services/tool-protection-oauth-provider.test.ts +0 -480
  156. package/src/__tests__/services/tool-protection.service.test.ts +0 -1373
  157. package/src/__tests__/utils/mock-providers.ts +0 -340
  158. package/src/cache/oauth-config-cache.d.ts +0 -69
  159. package/src/cache/oauth-config-cache.d.ts.map +0 -1
  160. package/src/cache/oauth-config-cache.js.map +0 -1
  161. package/src/cache/oauth-config-cache.ts +0 -123
  162. package/src/cache/tool-protection-cache.ts +0 -171
  163. package/src/compliance/EXAMPLE.md +0 -412
  164. package/src/compliance/__tests__/schema-verifier.test.ts +0 -797
  165. package/src/compliance/index.ts +0 -8
  166. package/src/compliance/schema-registry.ts +0 -460
  167. package/src/compliance/schema-verifier.ts +0 -708
  168. package/src/config/__tests__/merged-config.spec.ts +0 -445
  169. package/src/config/__tests__/remote-config.spec.ts +0 -268
  170. package/src/config/remote-config.ts +0 -264
  171. package/src/config.ts +0 -312
  172. package/src/delegation/__tests__/audience-validator.test.ts +0 -112
  173. package/src/delegation/__tests__/bitstring.test.ts +0 -346
  174. package/src/delegation/__tests__/cascading-revocation.test.ts +0 -628
  175. package/src/delegation/__tests__/delegation-graph.test.ts +0 -584
  176. package/src/delegation/__tests__/did-key-resolver.test.ts +0 -265
  177. package/src/delegation/__tests__/utils.test.ts +0 -152
  178. package/src/delegation/__tests__/vc-issuer.test.ts +0 -442
  179. package/src/delegation/__tests__/vc-verifier.test.ts +0 -922
  180. package/src/delegation/audience-validator.ts +0 -52
  181. package/src/delegation/bitstring.ts +0 -278
  182. package/src/delegation/cascading-revocation.ts +0 -370
  183. package/src/delegation/delegation-graph.ts +0 -299
  184. package/src/delegation/did-key-resolver.ts +0 -179
  185. package/src/delegation/index.ts +0 -14
  186. package/src/delegation/statuslist-manager.ts +0 -353
  187. package/src/delegation/storage/__tests__/memory-graph-storage.test.ts +0 -366
  188. package/src/delegation/storage/__tests__/memory-statuslist-storage.test.ts +0 -228
  189. package/src/delegation/storage/index.ts +0 -9
  190. package/src/delegation/storage/memory-graph-storage.ts +0 -178
  191. package/src/delegation/storage/memory-statuslist-storage.ts +0 -77
  192. package/src/delegation/utils.ts +0 -221
  193. package/src/delegation/vc-issuer.ts +0 -232
  194. package/src/delegation/vc-verifier.ts +0 -568
  195. package/src/identity/idp-token-resolver.ts +0 -181
  196. package/src/identity/idp-token-storage.interface.ts +0 -94
  197. package/src/identity/user-did-manager.ts +0 -526
  198. package/src/index.ts +0 -310
  199. package/src/providers/base.d.ts +0 -91
  200. package/src/providers/base.d.ts.map +0 -1
  201. package/src/providers/base.js.map +0 -1
  202. package/src/providers/base.ts +0 -96
  203. package/src/providers/memory.ts +0 -142
  204. package/src/runtime/audit-logger.ts +0 -39
  205. package/src/runtime/base.ts +0 -1392
  206. package/src/services/__tests__/access-control.integration.test.ts +0 -443
  207. package/src/services/__tests__/access-control.proof-response-validation.test.ts +0 -578
  208. package/src/services/__tests__/access-control.service.test.ts +0 -970
  209. package/src/services/__tests__/batch-delegation.service.test.ts +0 -351
  210. package/src/services/__tests__/crypto.service.test.ts +0 -531
  211. package/src/services/__tests__/oauth-provider-registry.test.ts +0 -142
  212. package/src/services/__tests__/proof-verifier.integration.test.ts +0 -485
  213. package/src/services/__tests__/proof-verifier.test.ts +0 -489
  214. package/src/services/__tests__/provider-resolution.integration.test.ts +0 -202
  215. package/src/services/__tests__/provider-resolver.test.ts +0 -213
  216. package/src/services/__tests__/storage.service.test.ts +0 -358
  217. package/src/services/access-control.service.ts +0 -990
  218. package/src/services/authorization/authorization-registry.ts +0 -66
  219. package/src/services/authorization/types.ts +0 -71
  220. package/src/services/batch-delegation.service.ts +0 -137
  221. package/src/services/crypto.service.ts +0 -302
  222. package/src/services/errors.ts +0 -76
  223. package/src/services/index.ts +0 -18
  224. package/src/services/oauth-config.service.d.ts +0 -53
  225. package/src/services/oauth-config.service.d.ts.map +0 -1
  226. package/src/services/oauth-config.service.js.map +0 -1
  227. package/src/services/oauth-config.service.ts +0 -192
  228. package/src/services/oauth-provider-registry.d.ts +0 -57
  229. package/src/services/oauth-provider-registry.d.ts.map +0 -1
  230. package/src/services/oauth-provider-registry.js.map +0 -1
  231. package/src/services/oauth-provider-registry.ts +0 -141
  232. package/src/services/oauth-service.ts +0 -544
  233. package/src/services/oauth-token-retrieval.service.ts +0 -245
  234. package/src/services/proof-verifier.ts +0 -478
  235. package/src/services/provider-resolver.d.ts +0 -48
  236. package/src/services/provider-resolver.d.ts.map +0 -1
  237. package/src/services/provider-resolver.js.map +0 -1
  238. package/src/services/provider-resolver.ts +0 -146
  239. package/src/services/provider-validator.ts +0 -170
  240. package/src/services/session-registration.service.ts +0 -251
  241. package/src/services/storage.service.ts +0 -566
  242. package/src/services/tool-context-builder.ts +0 -237
  243. package/src/services/tool-protection.service.ts +0 -1070
  244. package/src/types/oauth-required-error.ts +0 -63
  245. package/src/types/tool-protection.ts +0 -155
  246. package/src/utils/__tests__/did-helpers.test.ts +0 -156
  247. package/src/utils/base58.ts +0 -109
  248. package/src/utils/base64.ts +0 -148
  249. package/src/utils/cors.ts +0 -83
  250. package/src/utils/did-helpers.ts +0 -210
  251. package/src/utils/index.ts +0 -8
  252. package/src/utils/storage-keys.ts +0 -278
  253. package/tsconfig.json +0 -21
  254. package/vitest.config.ts +0 -56
@@ -1,908 +0,0 @@
1
- /**
2
- * Tool Protection Enforcement Tests
3
- *
4
- * Tests for tool protection enforcement in processToolCall.
5
- */
6
-
7
- import { describe, it, expect, beforeEach, vi } from "vitest";
8
- import { MCPIRuntimeBase } from "../../runtime/base";
9
- import { ProviderRuntimeConfig } from "../../config";
10
- import {
11
- createMockProviders,
12
- MockClockProvider,
13
- } from "../utils/mock-providers";
14
- import { ToolProtectionService } from "../../services/tool-protection.service";
15
- import { DelegationRequiredError } from "../../types/tool-protection";
16
- import { AccessControlApiService } from "../../services/access-control.service";
17
- import type { VerifyDelegationAPIResponse } from "@kya-os/contracts/agentshield-api";
18
-
19
- describe("MCPIRuntimeBase - Tool Protection Enforcement", () => {
20
- let runtime: MCPIRuntimeBase;
21
- let config: ProviderRuntimeConfig;
22
- let mockProviders: ReturnType<typeof createMockProviders>;
23
- let mockToolProtectionService: {
24
- checkToolProtection: ReturnType<typeof vi.fn>;
25
- };
26
- let mockAccessControlService: {
27
- verifyDelegation: ReturnType<typeof vi.fn>;
28
- };
29
-
30
- beforeEach(async () => {
31
- vi.clearAllMocks();
32
- mockProviders = createMockProviders();
33
-
34
- // Create mock tool protection service
35
- mockToolProtectionService = {
36
- checkToolProtection: vi.fn(),
37
- };
38
-
39
- // Create mock access control service
40
- mockAccessControlService = {
41
- verifyDelegation: vi.fn(),
42
- };
43
-
44
- config = {
45
- ...mockProviders,
46
- environment: "development",
47
- session: {
48
- timestampSkewSeconds: 120,
49
- ttlMinutes: 30,
50
- },
51
- audit: {
52
- enabled: true,
53
- logFunction: vi.fn(),
54
- includePayloads: true,
55
- },
56
- toolProtectionService: mockToolProtectionService as any,
57
- };
58
- runtime = new MCPIRuntimeBase(config);
59
- // Inject mock access control service
60
- (runtime as any).accessControlService = mockAccessControlService as any;
61
- await runtime.initialize();
62
- });
63
-
64
- describe("processToolCall with tool protection", () => {
65
- const mockHandler = vi.fn().mockResolvedValue({ result: "success" });
66
- const session = {
67
- id: "session123",
68
- audience: "https://client.example.com",
69
- nonce: "test-nonce",
70
- };
71
-
72
- it("should allow tool execution when no protection required", async () => {
73
- mockToolProtectionService.checkToolProtection.mockResolvedValue(null);
74
-
75
- const result = await runtime.processToolCall(
76
- "unprotectedTool",
77
- { arg: "value" },
78
- mockHandler,
79
- session
80
- );
81
-
82
- expect(mockHandler).toHaveBeenCalledWith({ arg: "value" });
83
- expect(result).toEqual({ result: "success" });
84
- expect(
85
- mockToolProtectionService.checkToolProtection
86
- ).toHaveBeenCalledWith("unprotectedTool", "did:key:zmock123");
87
- });
88
-
89
- it("should block tool execution when protection required and no delegation", async () => {
90
- mockToolProtectionService.checkToolProtection.mockResolvedValue({
91
- requiresDelegation: true,
92
- requiredScopes: ["files:write"],
93
- });
94
-
95
- await expect(
96
- runtime.processToolCall(
97
- "protectedTool",
98
- { arg: "value" },
99
- mockHandler,
100
- session
101
- )
102
- ).rejects.toThrow(DelegationRequiredError);
103
-
104
- expect(mockHandler).not.toHaveBeenCalled();
105
- });
106
-
107
- it("should allow tool execution when protection required and delegation provided", async () => {
108
- mockToolProtectionService.checkToolProtection.mockResolvedValue({
109
- requiresDelegation: true,
110
- requiredScopes: ["files:write"],
111
- });
112
-
113
- // Mock successful delegation verification
114
- mockAccessControlService.verifyDelegation.mockResolvedValue({
115
- success: true,
116
- data: {
117
- valid: true,
118
- delegation_id: "test-delegation-id",
119
- credential: {
120
- agent_did: "did:key:zmock123",
121
- scopes: ["files:write"],
122
- issued_at: Date.now(),
123
- created_at: Date.now(),
124
- },
125
- },
126
- } as VerifyDelegationAPIResponse);
127
-
128
- const sessionWithDelegation = {
129
- ...session,
130
- delegationToken: "valid-delegation-token",
131
- };
132
-
133
- const result = await runtime.processToolCall(
134
- "protectedTool",
135
- { arg: "value" },
136
- mockHandler,
137
- sessionWithDelegation
138
- );
139
-
140
- // Verify delegation was checked
141
- expect(mockAccessControlService.verifyDelegation).toHaveBeenCalledWith(
142
- expect.objectContaining({
143
- agent_did: "did:key:zmock123",
144
- delegation_token: "valid-delegation-token",
145
- scopes: ["files:write"],
146
- }),
147
- expect.objectContaining({
148
- delegationToken: "valid-delegation-token",
149
- })
150
- );
151
-
152
- expect(mockHandler).toHaveBeenCalledWith({ arg: "value" });
153
- expect(result).toEqual({ result: "success" });
154
- });
155
-
156
- it("should allow tool execution when protection required and consentProof provided", async () => {
157
- mockToolProtectionService.checkToolProtection.mockResolvedValue({
158
- requiresDelegation: true,
159
- requiredScopes: ["files:write"],
160
- });
161
-
162
- // Mock successful delegation verification using consent proof as credential_jwt
163
- mockAccessControlService.verifyDelegation.mockResolvedValue({
164
- success: true,
165
- data: {
166
- valid: true,
167
- delegation_id: "test-delegation-id",
168
- credential: {
169
- agent_did: "did:key:zmock123",
170
- scopes: ["files:write"],
171
- issued_at: Date.now(),
172
- created_at: Date.now(),
173
- },
174
- },
175
- } as VerifyDelegationAPIResponse);
176
-
177
- const sessionWithConsent = {
178
- ...session,
179
- consentProof: "valid-consent-proof",
180
- };
181
-
182
- const result = await runtime.processToolCall(
183
- "protectedTool",
184
- { arg: "value" },
185
- mockHandler,
186
- sessionWithConsent
187
- );
188
-
189
- // Verify delegation was checked using consent proof
190
- expect(mockAccessControlService.verifyDelegation).toHaveBeenCalledWith(
191
- expect.objectContaining({
192
- agent_did: "did:key:zmock123",
193
- credential_jwt: "valid-consent-proof",
194
- scopes: ["files:write"],
195
- }),
196
- expect.objectContaining({
197
- credentialJwt: "valid-consent-proof",
198
- })
199
- );
200
-
201
- expect(mockHandler).toHaveBeenCalledWith({ arg: "value" });
202
- expect(result).toEqual({ result: "success" });
203
- });
204
-
205
- it("should block tool execution when delegation verification fails", async () => {
206
- mockToolProtectionService.checkToolProtection.mockResolvedValue({
207
- requiresDelegation: true,
208
- requiredScopes: ["files:write"],
209
- });
210
-
211
- // Mock failed delegation verification
212
- mockAccessControlService.verifyDelegation.mockResolvedValue({
213
- success: true,
214
- data: {
215
- valid: false,
216
- reason: "Delegation token expired",
217
- },
218
- } as VerifyDelegationAPIResponse);
219
-
220
- const sessionWithDelegation = {
221
- ...session,
222
- delegationToken: "expired-delegation-token",
223
- };
224
-
225
- await expect(
226
- runtime.processToolCall(
227
- "protectedTool",
228
- { arg: "value" },
229
- mockHandler,
230
- sessionWithDelegation
231
- )
232
- ).rejects.toThrow(DelegationRequiredError);
233
-
234
- expect(mockAccessControlService.verifyDelegation).toHaveBeenCalled();
235
- expect(mockHandler).not.toHaveBeenCalled();
236
- });
237
-
238
- it("should block tool execution when delegation has wrong scopes", async () => {
239
- mockToolProtectionService.checkToolProtection.mockResolvedValue({
240
- requiresDelegation: true,
241
- requiredScopes: ["files:write"],
242
- });
243
-
244
- // Mock delegation with insufficient scopes
245
- mockAccessControlService.verifyDelegation.mockResolvedValue({
246
- success: true,
247
- data: {
248
- valid: false,
249
- reason: "Insufficient scopes",
250
- credential: {
251
- agent_did: "did:key:zmock123",
252
- scopes: ["files:read"], // Only read, not write
253
- issued_at: Date.now(),
254
- created_at: Date.now(),
255
- },
256
- },
257
- } as VerifyDelegationAPIResponse);
258
-
259
- const sessionWithDelegation = {
260
- ...session,
261
- delegationToken: "insufficient-scope-token",
262
- };
263
-
264
- await expect(
265
- runtime.processToolCall(
266
- "protectedTool",
267
- { arg: "value" },
268
- mockHandler,
269
- sessionWithDelegation
270
- )
271
- ).rejects.toThrow(DelegationRequiredError);
272
-
273
- expect(mockAccessControlService.verifyDelegation).toHaveBeenCalledWith(
274
- expect.objectContaining({
275
- scopes: ["files:write"],
276
- }),
277
- expect.any(Object)
278
- );
279
- expect(mockHandler).not.toHaveBeenCalled();
280
- });
281
-
282
- it("should handle API errors during verification gracefully", async () => {
283
- mockToolProtectionService.checkToolProtection.mockResolvedValue({
284
- requiresDelegation: true,
285
- requiredScopes: ["files:write"],
286
- });
287
-
288
- // Mock API error
289
- const { AgentShieldAPIError } = await import(
290
- "@kya-os/contracts/agentshield-api"
291
- );
292
- mockAccessControlService.verifyDelegation.mockRejectedValue(
293
- new AgentShieldAPIError("network_error", "API unavailable", {})
294
- );
295
-
296
- const sessionWithDelegation = {
297
- ...session,
298
- delegationToken: "valid-token",
299
- };
300
-
301
- // Should fail securely by requiring delegation
302
- await expect(
303
- runtime.processToolCall(
304
- "protectedTool",
305
- { arg: "value" },
306
- mockHandler,
307
- sessionWithDelegation
308
- )
309
- ).rejects.toThrow(DelegationRequiredError);
310
-
311
- expect(mockHandler).not.toHaveBeenCalled();
312
- });
313
-
314
- it("should allow tool execution when access control service not configured (graceful degradation)", async () => {
315
- mockToolProtectionService.checkToolProtection.mockResolvedValue({
316
- requiresDelegation: true,
317
- requiredScopes: ["files:write"],
318
- });
319
-
320
- // Remove access control service
321
- (runtime as any).accessControlService = undefined;
322
-
323
- const sessionWithDelegation = {
324
- ...session,
325
- delegationToken: "valid-delegation-token",
326
- };
327
-
328
- // Should allow execution with warning (graceful degradation)
329
- const result = await runtime.processToolCall(
330
- "protectedTool",
331
- { arg: "value" },
332
- mockHandler,
333
- sessionWithDelegation
334
- );
335
-
336
- expect(mockHandler).toHaveBeenCalledWith({ arg: "value" });
337
- expect(result).toEqual({ result: "success" });
338
- });
339
-
340
- describe("user_identifier validation", () => {
341
- const userDidA = "did:key:zUserA123456789";
342
- const userDidB = "did:key:zUserB987654321";
343
-
344
- it("should reject delegation when user_identifier does not match session userDid", async () => {
345
- mockToolProtectionService.checkToolProtection.mockResolvedValue({
346
- requiresDelegation: true,
347
- requiredScopes: ["files:write"],
348
- });
349
-
350
- // Mock verification response with User B's user_identifier
351
- mockAccessControlService.verifyDelegation.mockResolvedValue({
352
- success: true,
353
- data: {
354
- valid: true,
355
- delegation_id: "test-delegation-id",
356
- credential: {
357
- agent_did: "did:key:zmock123",
358
- user_identifier: userDidB, // ❌ User B's identifier, not User A's
359
- scopes: ["files:write"],
360
- issued_at: Date.now(),
361
- created_at: Date.now(),
362
- },
363
- },
364
- } as VerifyDelegationAPIResponse);
365
-
366
- // Session with User A's DID
367
- const sessionWithUserA = {
368
- ...session,
369
- userDid: userDidA, // User A's DID
370
- delegationToken: "delegation-token-for-user-b",
371
- };
372
-
373
- // Should reject delegation due to user_identifier mismatch
374
- await expect(
375
- runtime.processToolCall(
376
- "protectedTool",
377
- { arg: "value" },
378
- mockHandler,
379
- sessionWithUserA
380
- )
381
- ).rejects.toThrow(DelegationRequiredError);
382
-
383
- expect(mockAccessControlService.verifyDelegation).toHaveBeenCalled();
384
- expect(mockHandler).not.toHaveBeenCalled();
385
- });
386
-
387
- it("should accept delegation when user_identifier matches session userDid", async () => {
388
- mockToolProtectionService.checkToolProtection.mockResolvedValue({
389
- requiresDelegation: true,
390
- requiredScopes: ["files:write"],
391
- });
392
-
393
- // Mock verification response with User A's user_identifier
394
- mockAccessControlService.verifyDelegation.mockResolvedValue({
395
- success: true,
396
- data: {
397
- valid: true,
398
- delegation_id: "test-delegation-id",
399
- credential: {
400
- agent_did: "did:key:zmock123",
401
- user_identifier: userDidA, // ✅ User A's identifier matches session
402
- scopes: ["files:write"],
403
- issued_at: Date.now(),
404
- created_at: Date.now(),
405
- },
406
- },
407
- } as VerifyDelegationAPIResponse);
408
-
409
- // Session with User A's DID
410
- const sessionWithUserA = {
411
- ...session,
412
- userDid: userDidA, // User A's DID
413
- delegationToken: "delegation-token-for-user-a",
414
- };
415
-
416
- // Should accept delegation when user_identifier matches
417
- const result = await runtime.processToolCall(
418
- "protectedTool",
419
- { arg: "value" },
420
- mockHandler,
421
- sessionWithUserA
422
- );
423
-
424
- expect(mockAccessControlService.verifyDelegation).toHaveBeenCalled();
425
- expect(mockHandler).toHaveBeenCalledWith({ arg: "value" });
426
- expect(result).toEqual({ result: "success" });
427
- });
428
-
429
- it("should handle missing user_identifier gracefully (backward compatibility)", async () => {
430
- mockToolProtectionService.checkToolProtection.mockResolvedValue({
431
- requiresDelegation: true,
432
- requiredScopes: ["files:write"],
433
- });
434
-
435
- // Mock verification response without user_identifier (legacy format)
436
- mockAccessControlService.verifyDelegation.mockResolvedValue({
437
- success: true,
438
- data: {
439
- valid: true,
440
- delegation_id: "test-delegation-id",
441
- credential: {
442
- agent_did: "did:key:zmock123",
443
- // No user_identifier field
444
- scopes: ["files:write"],
445
- issued_at: Date.now(),
446
- created_at: Date.now(),
447
- },
448
- },
449
- } as VerifyDelegationAPIResponse);
450
-
451
- const sessionWithUserA = {
452
- ...session,
453
- userDid: userDidA,
454
- delegationToken: "legacy-delegation-token",
455
- };
456
-
457
- // Should allow execution when user_identifier is missing (backward compatibility)
458
- const result = await runtime.processToolCall(
459
- "protectedTool",
460
- { arg: "value" },
461
- mockHandler,
462
- sessionWithUserA
463
- );
464
-
465
- expect(mockHandler).toHaveBeenCalledWith({ arg: "value" });
466
- expect(result).toEqual({ result: "success" });
467
- });
468
-
469
- it("should handle missing session userDid gracefully", async () => {
470
- mockToolProtectionService.checkToolProtection.mockResolvedValue({
471
- requiresDelegation: true,
472
- requiredScopes: ["files:write"],
473
- });
474
-
475
- // Mock verification response with user_identifier
476
- mockAccessControlService.verifyDelegation.mockResolvedValue({
477
- success: true,
478
- data: {
479
- valid: true,
480
- delegation_id: "test-delegation-id",
481
- credential: {
482
- agent_did: "did:key:zmock123",
483
- user_identifier: userDidA,
484
- scopes: ["files:write"],
485
- issued_at: Date.now(),
486
- created_at: Date.now(),
487
- },
488
- },
489
- } as VerifyDelegationAPIResponse);
490
-
491
- // Session without userDid (legacy format)
492
- const sessionWithoutUserDid = {
493
- ...session,
494
- // No userDid field
495
- delegationToken: "delegation-token",
496
- };
497
-
498
- // Should allow execution when session userDid is missing (backward compatibility)
499
- const result = await runtime.processToolCall(
500
- "protectedTool",
501
- { arg: "value" },
502
- mockHandler,
503
- sessionWithoutUserDid
504
- );
505
-
506
- expect(mockHandler).toHaveBeenCalledWith({ arg: "value" });
507
- expect(result).toEqual({ result: "success" });
508
- });
509
- });
510
-
511
- it("should create proof after successful tool execution", async () => {
512
- mockToolProtectionService.checkToolProtection.mockResolvedValue(null);
513
-
514
- await runtime.processToolCall(
515
- "unprotectedTool",
516
- { arg: "value" },
517
- mockHandler,
518
- session
519
- );
520
-
521
- const proof = runtime.getLastProof();
522
- expect(proof).toBeDefined();
523
- expect(proof.did).toBe("did:key:zmock123");
524
- expect(proof.sessionId).toBe("session123");
525
- });
526
-
527
- it("should not create proof when tool execution is blocked", async () => {
528
- mockToolProtectionService.checkToolProtection.mockResolvedValue({
529
- requiresDelegation: true,
530
- requiredScopes: ["files:write"],
531
- });
532
-
533
- const initialProof = runtime.getLastProof();
534
-
535
- await expect(
536
- runtime.processToolCall(
537
- "protectedTool",
538
- { arg: "value" },
539
- mockHandler,
540
- session
541
- )
542
- ).rejects.toThrow(DelegationRequiredError);
543
-
544
- // Proof should not be created when tool is blocked
545
- const proofAfterBlock = runtime.getLastProof();
546
- expect(proofAfterBlock).toBe(initialProof);
547
- });
548
- });
549
-
550
- describe("DelegationRequiredError details", () => {
551
- const mockHandler = vi.fn().mockResolvedValue({ result: "success" });
552
- const session = {
553
- id: "session123",
554
- audience: "https://client.example.com",
555
- nonce: "test-nonce",
556
- };
557
-
558
- it("should include tool name in error", async () => {
559
- mockToolProtectionService.checkToolProtection.mockResolvedValue({
560
- requiresDelegation: true,
561
- requiredScopes: ["files:write"],
562
- });
563
-
564
- try {
565
- await runtime.processToolCall(
566
- "protectedTool",
567
- { arg: "value" },
568
- mockHandler,
569
- session
570
- );
571
- expect.fail("Should have thrown DelegationRequiredError");
572
- } catch (error: any) {
573
- expect(error).toBeInstanceOf(DelegationRequiredError);
574
- expect(error.toolName).toBe("protectedTool");
575
- expect(error.message).toContain("protectedTool");
576
- }
577
- });
578
-
579
- it("should include required scopes in error", async () => {
580
- mockToolProtectionService.checkToolProtection.mockResolvedValue({
581
- requiresDelegation: true,
582
- requiredScopes: ["files:write", "files:read"],
583
- });
584
-
585
- try {
586
- await runtime.processToolCall(
587
- "protectedTool",
588
- { arg: "value" },
589
- mockHandler,
590
- session
591
- );
592
- expect.fail("Should have thrown DelegationRequiredError");
593
- } catch (error: any) {
594
- expect(error).toBeInstanceOf(DelegationRequiredError);
595
- expect(error.requiredScopes).toEqual(["files:write", "files:read"]);
596
- }
597
- });
598
-
599
- it("should include consent URL in error", async () => {
600
- mockToolProtectionService.checkToolProtection.mockResolvedValue({
601
- requiresDelegation: true,
602
- requiredScopes: ["files:write"],
603
- });
604
-
605
- try {
606
- await runtime.processToolCall(
607
- "protectedTool",
608
- { arg: "value" },
609
- mockHandler,
610
- session
611
- );
612
- expect.fail("Should have thrown DelegationRequiredError");
613
- } catch (error: any) {
614
- expect(error).toBeInstanceOf(DelegationRequiredError);
615
- expect(error.consentUrl).toBeDefined();
616
- expect(error.consentUrl).toContain("kya.vouched.id/bouncer/consent");
617
- expect(error.consentUrl).toContain("tool=protectedTool");
618
- expect(error.consentUrl).toContain("scopes=files%3Awrite");
619
- }
620
- });
621
-
622
- it("should include resume token in error", async () => {
623
- mockToolProtectionService.checkToolProtection.mockResolvedValue({
624
- requiresDelegation: true,
625
- requiredScopes: ["files:write"],
626
- });
627
-
628
- try {
629
- await runtime.processToolCall(
630
- "protectedTool",
631
- { arg: "value" },
632
- mockHandler,
633
- session
634
- );
635
- expect.fail("Should have thrown DelegationRequiredError");
636
- } catch (error: any) {
637
- expect(error).toBeInstanceOf(DelegationRequiredError);
638
- expect(error.resumeToken).toBeDefined();
639
- expect(error.resumeToken).toMatch(/^resume_/);
640
- }
641
- });
642
-
643
- it("should include intercepted call context in error", async () => {
644
- mockToolProtectionService.checkToolProtection.mockResolvedValue({
645
- requiresDelegation: true,
646
- requiredScopes: ["files:write"],
647
- });
648
-
649
- try {
650
- await runtime.processToolCall(
651
- "protectedTool",
652
- { arg: "value", nested: { data: "test" } },
653
- mockHandler,
654
- session
655
- );
656
- expect.fail("Should have thrown DelegationRequiredError");
657
- } catch (error: any) {
658
- expect(error).toBeInstanceOf(DelegationRequiredError);
659
- expect(error.interceptedCall).toBeDefined();
660
- expect(error.interceptedCall.toolName).toBe("protectedTool");
661
- expect(error.interceptedCall.args).toEqual({
662
- arg: "value",
663
- nested: { data: "test" },
664
- });
665
- expect(error.interceptedCall.sessionId).toBe("session123");
666
- }
667
- });
668
- });
669
-
670
- describe("audit logging", () => {
671
- const mockHandler = vi.fn().mockResolvedValue({ result: "success" });
672
- const session = {
673
- id: "session123",
674
- audience: "https://client.example.com",
675
- nonce: "test-nonce",
676
- };
677
-
678
- it("should log tool protection check when audit enabled", async () => {
679
- mockToolProtectionService.checkToolProtection.mockResolvedValue(null);
680
-
681
- await runtime.processToolCall(
682
- "testTool",
683
- { arg: "value" },
684
- mockHandler,
685
- session
686
- );
687
-
688
- expect(config.audit!.logFunction).toHaveBeenCalled();
689
- const logCalls = (config.audit!.logFunction as any).mock.calls;
690
- const protectionLogCall = logCalls.find((call: any[]) => {
691
- try {
692
- const log = JSON.parse(call[0]);
693
- return log.event === "tool_executed";
694
- } catch {
695
- return false;
696
- }
697
- });
698
- expect(protectionLogCall).toBeDefined();
699
- });
700
-
701
- it("should log blocked tool call when audit enabled", async () => {
702
- mockToolProtectionService.checkToolProtection.mockResolvedValue({
703
- requiresDelegation: true,
704
- requiredScopes: ["files:write"],
705
- });
706
-
707
- const consoleWarnSpy = vi
708
- .spyOn(console, "warn")
709
- .mockImplementation(() => {});
710
-
711
- try {
712
- await runtime.processToolCall(
713
- "protectedTool",
714
- { arg: "value" },
715
- mockHandler,
716
- session
717
- );
718
- } catch (error) {
719
- // Expected
720
- }
721
-
722
- expect(consoleWarnSpy).toHaveBeenCalledWith(
723
- expect.stringContaining("[MCP-I] BLOCKED"),
724
- expect.any(Object)
725
- );
726
-
727
- consoleWarnSpy.mockRestore();
728
- });
729
-
730
- it("should not log when audit disabled", async () => {
731
- const auditLogFn = vi.fn();
732
- const configNoAudit = {
733
- ...config,
734
- audit: {
735
- enabled: false,
736
- logFunction: auditLogFn,
737
- },
738
- };
739
- const runtimeNoAudit = new MCPIRuntimeBase(configNoAudit);
740
- await runtimeNoAudit.initialize();
741
-
742
- mockToolProtectionService.checkToolProtection.mockResolvedValue(null);
743
-
744
- await runtimeNoAudit.processToolCall(
745
- "testTool",
746
- { arg: "value" },
747
- mockHandler,
748
- session
749
- );
750
-
751
- // Should not call log function for tool execution (initialization may still log)
752
- const toolExecutionLogs = auditLogFn.mock.calls.filter((call: any[]) => {
753
- try {
754
- const log = JSON.parse(call[0]);
755
- return log.event === "tool_executed";
756
- } catch {
757
- return false;
758
- }
759
- });
760
- expect(toolExecutionLogs.length).toBe(0);
761
- });
762
- });
763
-
764
- describe("tool protection service integration", () => {
765
- const mockHandler = vi.fn().mockResolvedValue({ result: "success" });
766
- const session = {
767
- id: "session123",
768
- audience: "https://client.example.com",
769
- nonce: "test-nonce",
770
- };
771
-
772
- it("should use agent DID from identity for protection check", async () => {
773
- mockToolProtectionService.checkToolProtection.mockResolvedValue(null);
774
-
775
- await runtime.processToolCall(
776
- "testTool",
777
- { arg: "value" },
778
- mockHandler,
779
- session
780
- );
781
-
782
- expect(
783
- mockToolProtectionService.checkToolProtection
784
- ).toHaveBeenCalledWith("testTool", "did:key:zmock123");
785
- });
786
-
787
- it("should handle tool protection service errors gracefully", async () => {
788
- mockToolProtectionService.checkToolProtection.mockRejectedValue(
789
- new Error("Service unavailable")
790
- );
791
-
792
- // If the service throws, the error propagates and tool execution is blocked
793
- // This is the current behavior - service errors prevent tool execution
794
- await expect(
795
- runtime.processToolCall(
796
- "testTool",
797
- { arg: "value" },
798
- mockHandler,
799
- session
800
- )
801
- ).rejects.toThrow("Service unavailable");
802
- });
803
-
804
- it("should work without tool protection service", async () => {
805
- const configNoService = {
806
- ...config,
807
- toolProtectionService: undefined,
808
- };
809
- const runtimeNoService = new MCPIRuntimeBase(configNoService);
810
- await runtimeNoService.initialize();
811
-
812
- const result = await runtimeNoService.processToolCall(
813
- "testTool",
814
- { arg: "value" },
815
- mockHandler,
816
- session
817
- );
818
-
819
- expect(mockHandler).toHaveBeenCalled();
820
- expect(result).toEqual({ result: "success" });
821
- });
822
- });
823
-
824
- describe("edge cases", () => {
825
- const mockHandler = vi.fn().mockResolvedValue({ result: "success" });
826
- const session = {
827
- id: "session123",
828
- audience: "https://client.example.com",
829
- nonce: "test-nonce",
830
- };
831
-
832
- it("should handle empty required scopes array", async () => {
833
- mockToolProtectionService.checkToolProtection.mockResolvedValue({
834
- requiresDelegation: true,
835
- requiredScopes: [],
836
- });
837
-
838
- await expect(
839
- runtime.processToolCall(
840
- "protectedTool",
841
- { arg: "value" },
842
- mockHandler,
843
- session
844
- )
845
- ).rejects.toThrow(DelegationRequiredError);
846
- });
847
-
848
- it("should handle multiple required scopes", async () => {
849
- mockToolProtectionService.checkToolProtection.mockResolvedValue({
850
- requiresDelegation: true,
851
- requiredScopes: ["scope1", "scope2", "scope3"],
852
- });
853
-
854
- try {
855
- await runtime.processToolCall(
856
- "protectedTool",
857
- { arg: "value" },
858
- mockHandler,
859
- session
860
- );
861
- expect.fail("Should have thrown DelegationRequiredError");
862
- } catch (error: any) {
863
- expect(error.requiredScopes).toEqual(["scope1", "scope2", "scope3"]);
864
- expect(error.consentUrl).toContain("scopes=scope1%2Cscope2%2Cscope3");
865
- }
866
- });
867
-
868
- it("should handle session without id", async () => {
869
- mockToolProtectionService.checkToolProtection.mockResolvedValue({
870
- requiresDelegation: true,
871
- requiredScopes: ["files:write"],
872
- });
873
-
874
- const sessionWithoutId = {
875
- audience: "https://client.example.com",
876
- nonce: "test-nonce",
877
- };
878
-
879
- try {
880
- await runtime.processToolCall(
881
- "protectedTool",
882
- { arg: "value" },
883
- mockHandler,
884
- sessionWithoutId
885
- );
886
- expect.fail("Should have thrown DelegationRequiredError");
887
- } catch (error: any) {
888
- expect(error.interceptedCall.sessionId).toBe("unknown");
889
- }
890
- });
891
-
892
- it("should handle handler errors independently of protection", async () => {
893
- mockToolProtectionService.checkToolProtection.mockResolvedValue(null);
894
- const errorHandler = vi
895
- .fn()
896
- .mockRejectedValue(new Error("Handler failed"));
897
-
898
- await expect(
899
- runtime.processToolCall(
900
- "errorTool",
901
- { arg: "value" },
902
- errorHandler,
903
- session
904
- )
905
- ).rejects.toThrow("Handler failed");
906
- });
907
- });
908
- });