@kya-os/mcp-i-core 1.3.2 → 1.3.3
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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-test.log +1635 -1635
- package/dist/services/tool-protection.service.d.ts +16 -12
- package/dist/services/tool-protection.service.d.ts.map +1 -1
- package/dist/services/tool-protection.service.js +134 -24
- package/dist/services/tool-protection.service.js.map +1 -1
- package/package.json +1 -1
- package/src/services/tool-protection.service.ts +160 -25
- package/src/__tests__/services/cache-no-warming.test.ts +0 -177
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cache Invalidation: No Warming Test
|
|
3
|
-
*
|
|
4
|
-
* CRITICAL TEST: Proves that clearAndRefresh does NOT fetch immediately after clearing.
|
|
5
|
-
*
|
|
6
|
-
* Background:
|
|
7
|
-
* - In mcp-i-cloudflare@1.6.4+, cache warming was added as an "optimization"
|
|
8
|
-
* - The idea was to fetch fresh data immediately after clearing
|
|
9
|
-
* - BUT this backfired when upstream APIs (AgentShield) have CDN caching
|
|
10
|
-
* - The immediate fetch would get stale CDN data and store it in our cache
|
|
11
|
-
* - This defeated the purpose of cache invalidation
|
|
12
|
-
*
|
|
13
|
-
* Fix:
|
|
14
|
-
* - clearAndRefresh now just DELETES the cache entry
|
|
15
|
-
* - It does NOT fetch or warm the cache
|
|
16
|
-
* - The next actual tool call will fetch fresh data (with natural delay)
|
|
17
|
-
*
|
|
18
|
-
* This was the behavior in mcp-i-cloudflare@1.6.3 that worked perfectly.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
22
|
-
import { ToolProtectionService } from "../../services/tool-protection.service.js";
|
|
23
|
-
import { InMemoryToolProtectionCache } from "../../cache/tool-protection-cache.js";
|
|
24
|
-
|
|
25
|
-
describe("Cache Invalidation - No Warming", () => {
|
|
26
|
-
let cache: InMemoryToolProtectionCache;
|
|
27
|
-
let service: ToolProtectionService;
|
|
28
|
-
let fetchSpy: ReturnType<typeof vi.fn>;
|
|
29
|
-
|
|
30
|
-
beforeEach(() => {
|
|
31
|
-
cache = new InMemoryToolProtectionCache();
|
|
32
|
-
fetchSpy = vi.fn();
|
|
33
|
-
|
|
34
|
-
service = new ToolProtectionService(
|
|
35
|
-
{
|
|
36
|
-
apiUrl: "https://test.agentshield.com",
|
|
37
|
-
apiKey: "test-key",
|
|
38
|
-
projectId: "test-project",
|
|
39
|
-
cacheTtl: 300000,
|
|
40
|
-
debug: true,
|
|
41
|
-
},
|
|
42
|
-
cache
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
// Spy on the internal fetch method
|
|
46
|
-
(service as any).fetchFromApi = fetchSpy;
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it("clearAndRefresh should DELETE cache without fetching", async () => {
|
|
50
|
-
// Seed the cache with old data
|
|
51
|
-
const oldConfig = {
|
|
52
|
-
toolProtections: {
|
|
53
|
-
greet: { requiresDelegation: false, requiredScopes: ["greet:execute"] },
|
|
54
|
-
},
|
|
55
|
-
};
|
|
56
|
-
await cache.set("config:tool-protections:test-project", oldConfig, 300000);
|
|
57
|
-
|
|
58
|
-
// Clear the cache
|
|
59
|
-
const result = await service.clearAndRefresh("did:key:test-agent");
|
|
60
|
-
|
|
61
|
-
// CRITICAL: fetchFromApi should NOT have been called
|
|
62
|
-
expect(fetchSpy).not.toHaveBeenCalled();
|
|
63
|
-
|
|
64
|
-
// Result should indicate cache was cleared, not refreshed
|
|
65
|
-
expect(result.source).toBe("cleared");
|
|
66
|
-
expect(result.cacheKey).toBe("config:tool-protections:test-project");
|
|
67
|
-
expect(result.config.toolProtections).toEqual({});
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it("cache should be empty after clearAndRefresh (no warming)", async () => {
|
|
71
|
-
// Seed the cache
|
|
72
|
-
const oldConfig = {
|
|
73
|
-
toolProtections: {
|
|
74
|
-
greet: { requiresDelegation: true, requiredScopes: ["greet:execute"] },
|
|
75
|
-
},
|
|
76
|
-
};
|
|
77
|
-
await cache.set("config:tool-protections:test-project", oldConfig, 300000);
|
|
78
|
-
|
|
79
|
-
// Verify it's cached
|
|
80
|
-
const beforeClear = await cache.get("config:tool-protections:test-project");
|
|
81
|
-
expect(beforeClear).not.toBeNull();
|
|
82
|
-
expect(beforeClear?.toolProtections.greet.requiresDelegation).toBe(true);
|
|
83
|
-
|
|
84
|
-
// Clear the cache
|
|
85
|
-
await service.clearAndRefresh("did:key:test-agent");
|
|
86
|
-
|
|
87
|
-
// Cache should be EMPTY (not warmed with new data)
|
|
88
|
-
const afterClear = await cache.get("config:tool-protections:test-project");
|
|
89
|
-
expect(afterClear).toBeNull();
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it("next getToolProtectionConfig call should fetch fresh from API", async () => {
|
|
93
|
-
// Setup: Mock API to return DIFFERENT data than what was cached
|
|
94
|
-
const staleConfig = {
|
|
95
|
-
toolProtections: {
|
|
96
|
-
greet: { requiresDelegation: false, requiredScopes: [] },
|
|
97
|
-
},
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
// Seed cache with stale data
|
|
101
|
-
await cache.set(
|
|
102
|
-
"config:tool-protections:test-project",
|
|
103
|
-
staleConfig,
|
|
104
|
-
300000
|
|
105
|
-
);
|
|
106
|
-
|
|
107
|
-
// Mock API to return fresh data (requiresDelegation: TRUE)
|
|
108
|
-
fetchSpy.mockResolvedValueOnce({
|
|
109
|
-
data: {
|
|
110
|
-
toolProtections: {
|
|
111
|
-
greet: {
|
|
112
|
-
requiresDelegation: true,
|
|
113
|
-
requiredScopes: ["greet:execute"],
|
|
114
|
-
},
|
|
115
|
-
},
|
|
116
|
-
},
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
// Clear cache (should NOT fetch)
|
|
120
|
-
await service.clearAndRefresh("did:key:test-agent");
|
|
121
|
-
expect(fetchSpy).not.toHaveBeenCalled();
|
|
122
|
-
|
|
123
|
-
// Now get config - this SHOULD fetch from API
|
|
124
|
-
const result = await service.getToolProtectionConfig("did:key:test-agent");
|
|
125
|
-
|
|
126
|
-
// API should have been called now (cache was cleared, so it fetched)
|
|
127
|
-
expect(fetchSpy).toHaveBeenCalledTimes(1);
|
|
128
|
-
|
|
129
|
-
// Result should have fresh data from API (not stale cached data)
|
|
130
|
-
expect(result.toolProtections.greet.requiresDelegation).toBe(true);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it("proves stale CDN data issue is avoided", async () => {
|
|
134
|
-
/**
|
|
135
|
-
* This test proves why NO warming is better than warming.
|
|
136
|
-
*
|
|
137
|
-
* Scenario:
|
|
138
|
-
* 1. User toggles delegation ON in dashboard
|
|
139
|
-
* 2. AgentShield DB updates, but CDN still has old data (5 min TTL)
|
|
140
|
-
* 3. User triggers cache clear
|
|
141
|
-
*
|
|
142
|
-
* WITH warming (broken):
|
|
143
|
-
* - clearAndRefresh immediately fetches from API
|
|
144
|
-
* - API returns stale CDN data (requiresDelegation: false)
|
|
145
|
-
* - We store stale data in our cache
|
|
146
|
-
* - Tool call uses stale data = BUG
|
|
147
|
-
*
|
|
148
|
-
* WITHOUT warming (fixed):
|
|
149
|
-
* - clearAndRefresh just deletes cache
|
|
150
|
-
* - Next tool call fetches from API (some time later)
|
|
151
|
-
* - Better chance CDN has refreshed
|
|
152
|
-
* - Even if CDN still stale, user can retry after CDN TTL
|
|
153
|
-
*/
|
|
154
|
-
|
|
155
|
-
// Simulate stale CDN response (old config before user toggled delegation)
|
|
156
|
-
const staleCdnResponse = {
|
|
157
|
-
data: {
|
|
158
|
-
toolProtections: {
|
|
159
|
-
greet: { requiresDelegation: false, requiredScopes: [] },
|
|
160
|
-
},
|
|
161
|
-
},
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
fetchSpy.mockResolvedValue(staleCdnResponse);
|
|
165
|
-
|
|
166
|
-
// Clear cache
|
|
167
|
-
await service.clearAndRefresh("did:key:test-agent");
|
|
168
|
-
|
|
169
|
-
// CRITICAL: We did NOT store the stale CDN data
|
|
170
|
-
// fetchSpy was NOT called during clearAndRefresh
|
|
171
|
-
expect(fetchSpy).not.toHaveBeenCalled();
|
|
172
|
-
|
|
173
|
-
// Cache is empty, not poisoned with stale data
|
|
174
|
-
const cached = await cache.get("config:tool-protections:test-project");
|
|
175
|
-
expect(cached).toBeNull();
|
|
176
|
-
});
|
|
177
|
-
});
|