-
Notifications
You must be signed in to change notification settings - Fork 187
Expand file tree
/
Copy pathconfig.ts
More file actions
220 lines (191 loc) · 6.03 KB
/
config.ts
File metadata and controls
220 lines (191 loc) · 6.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
import type {
Logger,
McpbManifestAny,
McpbUserConfigValues,
McpServerConfig,
} from "../types.js";
/**
* This file contains utility functions for handling MCPB configuration,
* including variable replacement and MCP server configuration generation.
*/
/**
* Recursively replaces variables in any value. Handles strings, arrays, and objects.
*
* @param value The value to process
* @param variables Object containing variable replacements
* @returns The processed value with all variables replaced
*/
export function replaceVariables(
value: unknown,
variables: Record<string, string | string[]>,
): unknown {
if (typeof value === "string") {
let result = value;
// Replace all variables in the string
for (const [key, replacement] of Object.entries(variables)) {
const pattern = new RegExp(`\\$\\{${key}\\}`, "g");
// Check if this pattern actually exists in the string
if (result.match(pattern)) {
if (Array.isArray(replacement)) {
console.warn(
`Cannot replace ${key} with array value in string context: "${value}"`,
{ key, replacement },
);
} else {
result = result.replace(pattern, replacement);
}
}
}
return result;
} else if (Array.isArray(value)) {
// For arrays, we need to handle special case of array expansion
const result: unknown[] = [];
for (const item of value) {
if (
typeof item === "string" &&
item.match(/^\$\{user_config\.[^}]+\}$/)
) {
// This is a user config variable that might expand to multiple values
const varName = item.match(/^\$\{([^}]+)\}$/)?.[1];
if (varName && variables[varName]) {
const replacement = variables[varName];
if (Array.isArray(replacement)) {
// Expand array inline
result.push(...replacement);
} else {
result.push(replacement);
}
} else {
// Variable not found, keep original
result.push(item);
}
} else {
// Recursively process non-variable items
result.push(replaceVariables(item, variables));
}
}
return result;
} else if (value && typeof value === "object") {
const result: Record<string, unknown> = {};
for (const [key, val] of Object.entries(value)) {
result[key] = replaceVariables(val, variables);
}
return result;
}
return value;
}
interface GetMcpConfigForManifestOptions {
manifest: McpbManifestAny;
extensionPath: string;
systemDirs: Record<string, string>;
userConfig: McpbUserConfigValues;
pathSeparator: string;
logger?: Logger;
}
export async function getMcpConfigForManifest(
options: GetMcpConfigForManifestOptions,
): Promise<McpServerConfig | undefined> {
const {
manifest,
extensionPath,
systemDirs,
userConfig,
pathSeparator,
logger,
} = options;
const baseConfig = manifest.server?.mcp_config;
if (!baseConfig) {
return undefined;
}
let result: McpServerConfig = {
...baseConfig,
};
if (baseConfig.platform_overrides) {
if (process.platform in baseConfig.platform_overrides) {
const platformConfig = baseConfig.platform_overrides[process.platform];
result.command = platformConfig.command || result.command;
result.args = platformConfig.args || result.args;
result.env = platformConfig.env || result.env;
}
}
// Check if required configuration is missing
if (hasRequiredConfigMissing({ manifest, userConfig })) {
logger?.warn(
`Extension ${manifest.name} has missing required configuration, skipping MCP config`,
);
return undefined;
}
const variables: Record<string, string | string[]> = {
__dirname: extensionPath,
pathSeparator,
"/": pathSeparator,
...systemDirs,
};
// Build merged configuration from defaults and user settings
const mergedConfig: Record<string, unknown> = {};
// First, add defaults from manifest
if (manifest.user_config) {
for (const [key, configOption] of Object.entries(manifest.user_config)) {
if (configOption.default !== undefined) {
mergedConfig[key] = configOption.default;
}
}
}
// Then, override with user settings
if (userConfig) {
Object.assign(mergedConfig, userConfig);
}
// Add merged configuration variables for substitution
for (const [key, value] of Object.entries(mergedConfig)) {
// Convert user config to the format expected by variable substitution
const userConfigKey = `user_config.${key}`;
if (Array.isArray(value)) {
// Keep arrays as arrays for proper expansion
variables[userConfigKey] = value.map(String);
} else if (typeof value === "boolean") {
// Convert booleans to "true"/"false" strings as per spec
variables[userConfigKey] = value ? "true" : "false";
} else {
// Convert other types to strings
variables[userConfigKey] = String(value);
}
}
// Replace all variables in the config
result = replaceVariables(result, variables) as McpServerConfig;
return result;
}
interface HasRequiredConfigMissingOptions {
manifest: McpbManifestAny;
userConfig?: McpbUserConfigValues;
}
function isInvalidSingleValue(value: unknown): boolean {
return value === undefined || value === null || value === "";
}
/**
* Check if an extension has missing required configuration
* @param manifest The extension manifest
* @param userConfig The user configuration
* @returns true if required configuration is missing
*/
export function hasRequiredConfigMissing({
manifest,
userConfig,
}: HasRequiredConfigMissingOptions): boolean {
if (!manifest.user_config) {
return false;
}
const config = userConfig || {};
for (const [key, configOption] of Object.entries(manifest.user_config)) {
if (configOption.required) {
const value = config[key];
if (
isInvalidSingleValue(value) ||
(Array.isArray(value) &&
(value.length === 0 || value.some(isInvalidSingleValue)))
) {
return true;
}
}
}
return false;
}