Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/mcp/test/common/mcpIcons.test.ts
4780 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 { URI } from '../../../../../base/common/uri.js';
8
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
9
import { NullLogger } from '../../../../../platform/log/common/log.js';
10
import { McpIcons, parseAndValidateMcpIcon } from '../../common/mcpIcons.js';
11
import { McpServerTransportHTTP, McpServerTransportStdio, McpServerTransportType } from '../../common/mcpTypes.js';
12
13
const createHttpLaunch = (url: string): McpServerTransportHTTP => ({
14
type: McpServerTransportType.HTTP,
15
uri: URI.parse(url),
16
headers: []
17
});
18
19
const createStdioLaunch = (): McpServerTransportStdio => ({
20
type: McpServerTransportType.Stdio,
21
cwd: undefined,
22
command: 'cmd',
23
args: [],
24
env: {},
25
envFile: undefined
26
});
27
28
suite('MCP Icons', () => {
29
suite('parseAndValidateMcpIcon', () => {
30
ensureNoDisposablesAreLeakedInTestSuite();
31
32
test('includes supported icons and sorts sizes ascending', () => {
33
const logger = new NullLogger();
34
const launch = createHttpLaunch('https://example.com');
35
36
const result = parseAndValidateMcpIcon({
37
icons: [
38
{ src: 'ftp://example.com/ignored.png', mimeType: 'image/png' },
39
{ src: 'data:image/png;base64,AAA', mimeType: 'image/png', sizes: ['64x64', '16x16'] },
40
{ src: 'https://example.com/icon.png', mimeType: 'image/png', sizes: ['128x128'] }
41
]
42
}, launch, logger);
43
44
assert.strictEqual(result.length, 2);
45
assert.strictEqual((result[0].src as URI).toString(true), 'data:image/png;base64,AAA');
46
assert.deepStrictEqual(result[0].sizes.map(s => s.width), [16, 64]);
47
assert.strictEqual(result[1].src.toString(), 'https://example.com/icon.png');
48
assert.deepStrictEqual(result[1].sizes, [{ width: 128, height: 128 }]);
49
});
50
51
test('requires http transport with matching authority for remote icons', () => {
52
const logger = new NullLogger();
53
const httpLaunch = createHttpLaunch('https://example.com');
54
const stdioLaunch = createStdioLaunch();
55
56
const icons = {
57
icons: [
58
{ src: 'https://example.com/icon.png', mimeType: 'image/png', sizes: ['64x64'] },
59
{ src: 'https://other.com/icon.png', mimeType: 'image/png', sizes: ['64x64'] }
60
]
61
};
62
63
const httpResult = parseAndValidateMcpIcon(icons, httpLaunch, logger);
64
assert.deepStrictEqual(httpResult.map(icon => icon.src.toString()), ['https://example.com/icon.png']);
65
66
const stdioResult = parseAndValidateMcpIcon(icons, stdioLaunch, logger);
67
assert.strictEqual(stdioResult.length, 0);
68
});
69
70
test('accepts file icons only for stdio transport', () => {
71
const logger = new NullLogger();
72
const stdioLaunch = createStdioLaunch();
73
const httpLaunch = createHttpLaunch('https://example.com');
74
75
const icons = {
76
icons: [
77
{ src: 'file:///tmp/icon.png', mimeType: 'image/png', sizes: ['32x32'] }
78
]
79
};
80
81
const stdioResult = parseAndValidateMcpIcon(icons, stdioLaunch, logger);
82
assert.strictEqual(stdioResult.length, 1);
83
assert.strictEqual(stdioResult[0].src.scheme, 'file');
84
85
const httpResult = parseAndValidateMcpIcon(icons, httpLaunch, logger);
86
assert.strictEqual(httpResult.length, 0);
87
});
88
});
89
90
suite('McpIcons', () => {
91
ensureNoDisposablesAreLeakedInTestSuite();
92
93
test('getUrl returns undefined when no icons are available', () => {
94
const icons = McpIcons.fromParsed(undefined);
95
assert.strictEqual(icons.getUrl(16), undefined);
96
});
97
98
test('getUrl prefers theme-specific icons and keeps light fallback', () => {
99
const logger = new NullLogger();
100
const launch = createHttpLaunch('https://example.com');
101
const parsed = parseAndValidateMcpIcon({
102
icons: [
103
{ src: 'https://example.com/dark.png', mimeType: 'image/png', sizes: ['16x16', '48x48'], theme: 'dark' },
104
{ src: 'https://example.com/any.png', mimeType: 'image/png', sizes: ['24x24'] },
105
{ src: 'https://example.com/light.png', mimeType: 'image/png', sizes: ['64x64'], theme: 'light' }
106
]
107
}, launch, logger);
108
const icons = McpIcons.fromParsed(parsed);
109
const result = icons.getUrl(32);
110
111
assert.ok(result);
112
assert.strictEqual(result!.dark.toString(), 'https://example.com/dark.png');
113
assert.strictEqual(result!.light?.toString(), 'https://example.com/light.png');
114
});
115
116
test('getUrl falls back to any-theme icons when no exact size exists', () => {
117
const logger = new NullLogger();
118
const launch = createHttpLaunch('https://example.com');
119
const parsed = parseAndValidateMcpIcon({
120
icons: [
121
{ src: 'https://example.com/dark.png', mimeType: 'image/png', sizes: ['16x16'], theme: 'dark' },
122
{ src: 'https://example.com/any.png', mimeType: 'image/png', sizes: ['64x64'] }
123
]
124
}, launch, logger);
125
const icons = McpIcons.fromParsed(parsed);
126
const result = icons.getUrl(60);
127
128
assert.ok(result);
129
assert.strictEqual(result!.dark.toString(), 'https://example.com/any.png');
130
assert.strictEqual(result!.light, undefined);
131
});
132
133
test('getUrl reuses light icons when dark theme assets are missing', () => {
134
const logger = new NullLogger();
135
const launch = createHttpLaunch('https://example.com');
136
const parsed = parseAndValidateMcpIcon({
137
icons: [
138
{ src: 'https://example.com/light.png', mimeType: 'image/png', sizes: ['32x32'], theme: 'light' }
139
]
140
}, launch, logger);
141
const icons = McpIcons.fromParsed(parsed);
142
const result = icons.getUrl(16);
143
144
assert.ok(result);
145
assert.strictEqual(result!.dark.toString(), 'https://example.com/light.png');
146
assert.strictEqual(result!.light?.toString(), 'https://example.com/light.png');
147
});
148
});
149
});
150
151