Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/mcp/test/common/mcpSamplingLog.test.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import * as assert from "assert";
7
import * as sinon from "sinon";
8
import { ensureNoDisposablesAreLeakedInTestSuite } from "../../../../../base/test/common/utils.js";
9
import {
10
StorageScope
11
} from "../../../../../platform/storage/common/storage.js";
12
import { TestStorageService } from "../../../../test/common/workbenchTestServices.js";
13
import { McpSamplingLog } from "../../common/mcpSamplingLog.js";
14
import { IMcpServer } from "../../common/mcpTypes.js";
15
16
suite("MCP - Sampling Log", () => {
17
const ds = ensureNoDisposablesAreLeakedInTestSuite();
18
const fakeServer: IMcpServer = {
19
definition: { id: "testServer" },
20
readDefinitions: () => ({
21
get: () => ({ collection: { scope: StorageScope.APPLICATION } }),
22
}),
23
} as any;
24
25
let log: McpSamplingLog;
26
let storage: TestStorageService;
27
let clock: sinon.SinonFakeTimers;
28
29
setup(() => {
30
storage = ds.add(new TestStorageService());
31
log = ds.add(new McpSamplingLog(storage));
32
clock = sinon.useFakeTimers();
33
clock.setSystemTime(new Date("2023-10-01T00:00:00Z").getTime());
34
});
35
36
teardown(() => {
37
clock.restore();
38
});
39
40
test("logs a single request", async () => {
41
log.add(
42
fakeServer,
43
[{ role: "user", content: { type: "text", text: "test request" } }],
44
"test response here",
45
"foobar9000",
46
);
47
48
// storage.testEmitWillSaveState(WillSaveStateReason.NONE);
49
await storage.flush();
50
assert.deepStrictEqual(
51
(storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as any),
52
[
53
[
54
"testServer",
55
{
56
head: 19631,
57
bins: [1, 0, 0, 0, 0, 0, 0],
58
lastReqs: [
59
{
60
request: [{ role: "user", content: { type: "text", text: "test request" } }],
61
response: "test response here",
62
at: 1696118400000,
63
model: "foobar9000",
64
},
65
],
66
},
67
],
68
],
69
);
70
});
71
72
test("logs multiple requests on the same day", async () => {
73
// First request
74
log.add(
75
fakeServer,
76
[{ role: "user", content: { type: "text", text: "first request" } }],
77
"first response",
78
"foobar9000",
79
);
80
81
// Advance time by a few hours but stay on the same day
82
clock.tick(5 * 60 * 60 * 1000); // 5 hours
83
84
// Second request
85
log.add(
86
fakeServer,
87
[{ role: "user", content: { type: "text", text: "second request" } }],
88
"second response",
89
"foobar9000",
90
);
91
92
await storage.flush();
93
const data = (storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as any)[0][1];
94
95
// Verify the bin for the current day has 2 requests
96
assert.strictEqual(data.bins[0], 2);
97
98
// Verify both requests are in the lastReqs array, with the most recent first
99
assert.strictEqual(data.lastReqs.length, 2);
100
assert.strictEqual(data.lastReqs[0].request[0].content.text, "second request");
101
assert.strictEqual(data.lastReqs[1].request[0].content.text, "first request");
102
});
103
104
test("shifts bins when adding requests on different days", async () => {
105
// First request on day 1
106
log.add(
107
fakeServer,
108
[{ role: "user", content: { type: "text", text: "day 1 request" } }],
109
"day 1 response",
110
"foobar9000",
111
);
112
113
// Advance time to the next day
114
clock.tick(24 * 60 * 60 * 1000);
115
116
// Second request on day 2
117
log.add(
118
fakeServer,
119
[{ role: "user", content: { type: "text", text: "day 2 request" } }],
120
"day 2 response",
121
"foobar9000",
122
);
123
124
await storage.flush();
125
const data = (storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as any)[0][1];
126
127
// Verify the bins: day 2 should have 1 request, day 1 should have 1 request
128
assert.strictEqual(data.bins[0], 1); // day 2
129
assert.strictEqual(data.bins[1], 1); // day 1
130
131
// Advance time by 5 more days
132
clock.tick(5 * 24 * 60 * 60 * 1000);
133
134
// Request on day 7
135
log.add(
136
fakeServer,
137
[{ role: "user", content: { type: "text", text: "day 7 request" } }],
138
"day 7 response",
139
"foobar9000",
140
);
141
142
await storage.flush();
143
const updatedData = (storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as any)[0][1];
144
145
// Verify the bins have shifted correctly
146
assert.strictEqual(updatedData.bins[0], 1); // day 7
147
assert.strictEqual(updatedData.bins[5], 1); // day 2
148
assert.strictEqual(updatedData.bins[6], 1); // day 1
149
});
150
151
test("limits the number of stored requests", async () => {
152
// Add more than the maximum number of requests (Constants.SamplingLastNMessage = 30)
153
for (let i = 0; i < 35; i++) {
154
log.add(
155
fakeServer,
156
[{ role: "user", content: { type: "text", text: `request ${i}` } }],
157
`response ${i}`,
158
"foobar9000",
159
);
160
}
161
162
await storage.flush();
163
const data = (storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as any)[0][1];
164
165
// Verify only the last 30 requests are kept
166
assert.strictEqual(data.lastReqs.length, 30);
167
assert.strictEqual(data.lastReqs[0].request[0].content.text, "request 34");
168
assert.strictEqual(data.lastReqs[29].request[0].content.text, "request 5");
169
});
170
171
test("handles different content types", async () => {
172
// Add a request with text content
173
log.add(
174
fakeServer,
175
[{ role: "user", content: { type: "text", text: "text request" } }],
176
"text response",
177
"foobar9000",
178
);
179
180
// Add a request with image content
181
log.add(
182
fakeServer,
183
[{
184
role: "user",
185
content: {
186
type: "image",
187
data: "base64data",
188
mimeType: "image/png"
189
}
190
}],
191
"image response",
192
"foobar9000",
193
);
194
195
// Add a request with mixed content
196
log.add(
197
fakeServer,
198
[
199
{ role: "user", content: { type: "text", text: "text and image" } },
200
{
201
role: "assistant",
202
content: {
203
type: "image",
204
data: "base64data",
205
mimeType: "image/jpeg"
206
}
207
}
208
],
209
"mixed response",
210
"foobar9000",
211
);
212
213
await storage.flush();
214
const data = (storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as any)[0][1];
215
216
// Verify all requests are stored correctly
217
assert.strictEqual(data.lastReqs.length, 3);
218
assert.strictEqual(data.lastReqs[0].request.length, 2); // Mixed content request has 2 messages
219
assert.strictEqual(data.lastReqs[1].request[0].content.type, "image");
220
assert.strictEqual(data.lastReqs[2].request[0].content.type, "text");
221
});
222
223
test("handles multiple servers", async () => {
224
const fakeServer2: IMcpServer = {
225
definition: { id: "testServer2" },
226
readDefinitions: () => ({
227
get: () => ({ collection: { scope: StorageScope.APPLICATION } }),
228
}),
229
} as any;
230
231
log.add(
232
fakeServer,
233
[{ role: "user", content: { type: "text", text: "server1 request" } }],
234
"server1 response",
235
"foobar9000",
236
);
237
238
log.add(
239
fakeServer2,
240
[{ role: "user", content: { type: "text", text: "server2 request" } }],
241
"server2 response",
242
"foobar9000",
243
);
244
245
await storage.flush();
246
const storageData = (storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as any);
247
248
// Verify both servers have their data stored
249
assert.strictEqual(storageData.length, 2);
250
assert.strictEqual(storageData[0][0], "testServer");
251
assert.strictEqual(storageData[1][0], "testServer2");
252
});
253
});
254
255