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