pi-mcp-adapter 5.3.0 → 5.3.2
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/CHANGELOG.md +37 -0
- package/bearer-command-resolver.ts +8 -4
- package/cli.js +7 -2
- package/commands.ts +5 -3
- package/config.ts +201 -25
- package/dist/bearer-command-resolver.js +7 -3
- package/dist/bearer-command-resolver.js.map +1 -1
- package/dist/config.js +184 -25
- package/dist/config.js.map +1 -1
- package/dist/metadata-cache.d.ts +5 -0
- package/dist/metadata-cache.js +21 -0
- package/dist/metadata-cache.js.map +1 -1
- package/dist/project-server-trust.d.ts +8 -0
- package/dist/project-server-trust.js +45 -12
- package/dist/project-server-trust.js.map +1 -1
- package/dist/sampling-handler.d.ts +3 -2
- package/dist/sampling-handler.js +12 -3
- package/dist/sampling-handler.js.map +1 -1
- package/dist/server-manager.js +7 -8
- package/dist/server-manager.js.map +1 -1
- package/dist/types.d.ts +6 -2
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -1
- package/dist/utils.d.ts +7 -0
- package/dist/utils.js +42 -3
- package/dist/utils.js.map +1 -1
- package/docs/auth.md +1 -1
- package/docs/configuration.md +4 -2
- package/docs/prompts-and-ui.md +1 -1
- package/docs/servers.md +1 -1
- package/docs/tools.md +3 -1
- package/index.ts +5 -18
- package/init.ts +1 -0
- package/mcp-panel.ts +25 -23
- package/metadata-cache.ts +23 -0
- package/package.json +1 -1
- package/project-server-trust.ts +49 -12
- package/proxy-modes.ts +17 -2
- package/sampling-handler.ts +21 -15
- package/server-manager.ts +7 -8
- package/tool-metadata.ts +19 -0
- package/types.ts +11 -2
- package/utils.ts +42 -4
package/dist/config.js
CHANGED
|
@@ -68,8 +68,10 @@ export const KNOWN_SERVER_PRESETS = [
|
|
|
68
68
|
{
|
|
69
69
|
id: "github",
|
|
70
70
|
name: "GitHub",
|
|
71
|
-
summary: "Work with GitHub
|
|
72
|
-
|
|
71
|
+
summary: "Work with GitHub using your GitHub CLI sign-in. Needs `gh` installed and `gh auth login` done.",
|
|
72
|
+
// GitHub's OAuth server has no dynamic client registration, so MCP OAuth can't sign in;
|
|
73
|
+
// the GitHub CLI token works and is read at connect time, never written to config.
|
|
74
|
+
entry: { url: "https://api.githubcopilot.com/mcp/", auth: "bearer", bearerToken: "!gh auth token", protocolVersion: "auto" },
|
|
73
75
|
},
|
|
74
76
|
{
|
|
75
77
|
id: "chrome-devtools",
|
|
@@ -162,6 +164,75 @@ function legacyMcpConfigHasContent(filePath) {
|
|
|
162
164
|
}
|
|
163
165
|
}
|
|
164
166
|
export function getLegacyMcpMigrationNotices(cwd = process.cwd(), overridePath) {
|
|
167
|
+
return [
|
|
168
|
+
...getPiMcpConfigNotices(cwd, overridePath),
|
|
169
|
+
...getOpenCodeImportNotices(cwd, overridePath),
|
|
170
|
+
...getDroppedCredentialNotices(cwd, overridePath),
|
|
171
|
+
];
|
|
172
|
+
}
|
|
173
|
+
function credentialFiles(servers, fileOf) {
|
|
174
|
+
return new Map(Object.entries(servers).map(([name, definition]) => [
|
|
175
|
+
name,
|
|
176
|
+
new Map(CREDENTIAL_FIELDS.filter((field) => definition[field] !== undefined).map((field) => [field, fileOf(name, field)])),
|
|
177
|
+
]));
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Replays the server merges of loadMcpConfigWithSources with the file behind each credential field,
|
|
181
|
+
* and reports the fields mergeServerMaps drops because a later file points the server at another url.
|
|
182
|
+
*/
|
|
183
|
+
function getDroppedCredentialNotices(cwd, overridePath) {
|
|
184
|
+
const notices = [];
|
|
185
|
+
const merge = (base, baseFiles, next, nextFiles, urlFile) => {
|
|
186
|
+
const files = new Map(baseFiles);
|
|
187
|
+
for (const [name, definition] of Object.entries(next)) {
|
|
188
|
+
const existing = base[name];
|
|
189
|
+
const fieldFiles = new Map(files.get(name));
|
|
190
|
+
const byFile = new Map();
|
|
191
|
+
for (const field of existing ? droppedUrlBoundAuth(existing, definition) : []) {
|
|
192
|
+
const from = fieldFiles.get(field);
|
|
193
|
+
if (from !== undefined)
|
|
194
|
+
byFile.set(from, [...byFile.get(from) ?? [], field]);
|
|
195
|
+
fieldFiles.delete(field);
|
|
196
|
+
}
|
|
197
|
+
for (const [from, fields] of byFile) {
|
|
198
|
+
notices.push(`"${name}": ignoring ${fields.join(", ")} from ${from}, because ${urlFile(name)} sets a different url for this server. Use one url, or set ${fields.length === 1 ? "it" : "them"} next to that url.`);
|
|
199
|
+
}
|
|
200
|
+
for (const [field, path] of nextFiles.get(name) ?? [])
|
|
201
|
+
fieldFiles.set(field, path);
|
|
202
|
+
files.set(name, fieldFiles);
|
|
203
|
+
}
|
|
204
|
+
return [mergeServerMaps(base, next), files];
|
|
205
|
+
};
|
|
206
|
+
const host = !isExclusiveConfigMode() && getConfiguredHostConfigDiscovery(overridePath, cwd) === "on"
|
|
207
|
+
? loadDiscoveredHostConfigs(cwd)
|
|
208
|
+
: { config: { mcpServers: {} }, serverSources: new Map() };
|
|
209
|
+
const hostFile = (name) => host.serverSources.get(name)?.path ?? "a host config";
|
|
210
|
+
let servers = host.config.mcpServers;
|
|
211
|
+
let files = credentialFiles(servers, hostFile);
|
|
212
|
+
const sourceSpecs = getConfigSources(overridePath, cwd);
|
|
213
|
+
const piProjectSource = sourceSpecs.find((source) => source.id === "pi-mcp-project");
|
|
214
|
+
const piProjectNames = Object.keys((piProjectSource && readPiMcpConfig(piProjectSource.readPath))?.mcpServers ?? {});
|
|
215
|
+
for (const source of sourceSpecs) {
|
|
216
|
+
const loaded = readSourceConfig(source.id, source.readPath);
|
|
217
|
+
if (!loaded)
|
|
218
|
+
continue;
|
|
219
|
+
if (source.id === "pi-mcp-global") {
|
|
220
|
+
for (const name of piProjectNames)
|
|
221
|
+
delete loaded.mcpServers[name];
|
|
222
|
+
}
|
|
223
|
+
// The same two merges as expandImports and mergeConfigs: the file's own entries over its imports,
|
|
224
|
+
// then the file over the files before it.
|
|
225
|
+
const imported = expandImports({ ...loaded, mcpServers: {} }, cwd);
|
|
226
|
+
// An OpenCode project entry can inherit a credential from the global file, so name the file per key.
|
|
227
|
+
const importFile = (name, field) => imported.keySources?.get(name)?.get(field) ?? imported.serverSources.get(name)?.path ?? source.readPath;
|
|
228
|
+
const ownFile = () => source.readPath;
|
|
229
|
+
const [fileServers, fileFiles] = merge(imported.config.mcpServers, credentialFiles(imported.config.mcpServers, importFile), loaded.mcpServers, credentialFiles(loaded.mcpServers, ownFile), ownFile);
|
|
230
|
+
const urlFile = (name) => typeof loaded.mcpServers[name]?.url === "string" ? source.readPath : importFile(name, "url");
|
|
231
|
+
[servers, files] = merge(servers, files, fileServers, fileFiles, urlFile);
|
|
232
|
+
}
|
|
233
|
+
return notices;
|
|
234
|
+
}
|
|
235
|
+
function getPiMcpConfigNotices(cwd, overridePath) {
|
|
165
236
|
if (piMcpConfigEnabled) {
|
|
166
237
|
return getConfigSources(overridePath, cwd).flatMap((source) => {
|
|
167
238
|
const file = isPiMcpSource(source.id) ? readPiMcpConfig(source.readPath) : null;
|
|
@@ -196,6 +267,32 @@ export function getLegacyMcpMigrationNotices(cwd = process.cwd(), overridePath)
|
|
|
196
267
|
return [`pi-mcp-adapter no longer reads ${source}. ${fix}`];
|
|
197
268
|
});
|
|
198
269
|
}
|
|
270
|
+
/** Settings OpenCode drops from an active `opencode` import, grouped by the file that defines each server. */
|
|
271
|
+
function getOpenCodeImportNotices(cwd, overridePath) {
|
|
272
|
+
const active = (!isExclusiveConfigMode() && getConfiguredHostConfigDiscovery(overridePath, cwd) === "on")
|
|
273
|
+
|| getConfigSources(overridePath, cwd).some((source) => readSourceConfig(source.id, source.readPath)?.imports?.includes("opencode"));
|
|
274
|
+
const imported = active ? loadImportedConfig("opencode", cwd, "Failed to inspect imported MCP config from opencode:") : null;
|
|
275
|
+
if (!imported)
|
|
276
|
+
return [];
|
|
277
|
+
// Warn only where the OpenCode import is still the server's definition, matching /mcp-adapter.
|
|
278
|
+
const provenance = getServerProvenance(overridePath, cwd);
|
|
279
|
+
// Name the file each key came from: a project entry can inherit a stray key from the global file.
|
|
280
|
+
const byFile = new Map();
|
|
281
|
+
for (const [name, keys] of getOpenCodeIgnoredSettings(imported.value)) {
|
|
282
|
+
if (provenance.get(name)?.ignoredSettingsSource !== "opencode")
|
|
283
|
+
continue;
|
|
284
|
+
for (const key of keys) {
|
|
285
|
+
const path = imported.keySources?.get(name)?.get(key) ?? imported.path;
|
|
286
|
+
const servers = byFile.get(path) ?? new Map();
|
|
287
|
+
servers.set(name, [...servers.get(name) ?? [], key]);
|
|
288
|
+
byFile.set(path, servers);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return [...byFile].map(([path, servers]) => {
|
|
292
|
+
const ignored = [...servers].map(([name, settings]) => `"${name}": ${settings.join(", ")}`);
|
|
293
|
+
return `${path}: OpenCode ignores these settings, so the import does too (details in /mcp-adapter): ${ignored.join("; ")}.`;
|
|
294
|
+
});
|
|
295
|
+
}
|
|
199
296
|
export function getSharedConfigPath(target, cwd = process.cwd()) {
|
|
200
297
|
return target === "project" ? getProjectConfigPath(cwd) : getGenericGlobalConfigPath();
|
|
201
298
|
}
|
|
@@ -750,6 +847,21 @@ function mergeConfigs(base, next) {
|
|
|
750
847
|
// otherwise the original endpoint's credentials would be shipped to the new
|
|
751
848
|
// url. See the SECURITY note in mergeServerMaps.
|
|
752
849
|
const URL_BOUND_AUTH_FIELDS = ["headers", "bearerToken", "bearerTokenEnv", "bearerTokenStore", "requestHeadersCommand", "caFile"];
|
|
850
|
+
const CREDENTIAL_FIELDS = [...URL_BOUND_AUTH_FIELDS, "auth", "oauth"];
|
|
851
|
+
/** The credential fields of `existing` that mergeServerMaps drops for `definition`'s different url. */
|
|
852
|
+
function droppedUrlBoundAuth(existing, definition) {
|
|
853
|
+
if (typeof definition.url !== "string" || definition.url === existing.url)
|
|
854
|
+
return [];
|
|
855
|
+
return CREDENTIAL_FIELDS.filter((field) => {
|
|
856
|
+
if (Object.hasOwn(definition, field))
|
|
857
|
+
return false;
|
|
858
|
+
if (field === "auth")
|
|
859
|
+
return typeof existing.auth === "object";
|
|
860
|
+
if (field === "oauth")
|
|
861
|
+
return existing.oauth !== undefined && existing.oauth !== false;
|
|
862
|
+
return existing[field] !== undefined;
|
|
863
|
+
});
|
|
864
|
+
}
|
|
753
865
|
function mergeServerMaps(base, next) {
|
|
754
866
|
const merged = { ...base };
|
|
755
867
|
for (const [name, definition] of Object.entries(next)) {
|
|
@@ -795,15 +907,11 @@ function mergeServerMaps(base, next) {
|
|
|
795
907
|
if (existing && typeof definition.url === "string" && definition.url !== existing.url) {
|
|
796
908
|
if (baseEntry === existing)
|
|
797
909
|
baseEntry = { ...existing };
|
|
798
|
-
|
|
910
|
+
// Provider tokens (object `auth`) and OAuth config are bound to the url too; the startup
|
|
911
|
+
// notice reports exactly these fields, so both read the one rule.
|
|
912
|
+
for (const field of droppedUrlBoundAuth(existing, definition)) {
|
|
799
913
|
delete baseEntry[field];
|
|
800
914
|
}
|
|
801
|
-
// A provider token is bound to the url that asked for it, like the fields above.
|
|
802
|
-
if (typeof baseEntry.auth === "object")
|
|
803
|
-
delete baseEntry.auth;
|
|
804
|
-
if (baseEntry.oauth !== false) {
|
|
805
|
-
delete baseEntry.oauth;
|
|
806
|
-
}
|
|
807
915
|
}
|
|
808
916
|
if (existing && Object.hasOwn(definition, "env") && isBuiltInAgentPlugin(existing, "env") && !Object.hasOwn(definition, "literalEnv")) {
|
|
809
917
|
if (baseEntry === existing)
|
|
@@ -825,6 +933,7 @@ function expandImports(config, cwd = process.cwd()) {
|
|
|
825
933
|
return { config, serverSources: new Map() };
|
|
826
934
|
const importedServers = {};
|
|
827
935
|
const serverSources = new Map();
|
|
936
|
+
const keySources = new Map();
|
|
828
937
|
for (const importKind of config.imports) {
|
|
829
938
|
const imported = loadImportedConfig(importKind, cwd, `Failed to import MCP config from ${importKind}:`);
|
|
830
939
|
if (!imported)
|
|
@@ -834,6 +943,9 @@ function expandImports(config, cwd = process.cwd()) {
|
|
|
834
943
|
if (!importedServers[name]) {
|
|
835
944
|
importedServers[name] = definition;
|
|
836
945
|
serverSources.set(name, imported.serverSources.get(name) ?? imported.source);
|
|
946
|
+
const keys = imported.keySources?.get(name);
|
|
947
|
+
if (keys)
|
|
948
|
+
keySources.set(name, keys);
|
|
837
949
|
}
|
|
838
950
|
}
|
|
839
951
|
}
|
|
@@ -845,6 +957,7 @@ function expandImports(config, cwd = process.cwd()) {
|
|
|
845
957
|
mcpServers: mergeServerMaps(importedServers, config.mcpServers),
|
|
846
958
|
},
|
|
847
959
|
serverSources,
|
|
960
|
+
keySources,
|
|
848
961
|
};
|
|
849
962
|
}
|
|
850
963
|
function resolveImportCandidates(importKind, cwd) {
|
|
@@ -886,6 +999,7 @@ function loadImportedConfig(importKind, cwd, warningPrefix) {
|
|
|
886
999
|
let merged = {};
|
|
887
1000
|
let highestPrecedenceSource;
|
|
888
1001
|
const serverSources = new Map();
|
|
1002
|
+
const keySources = new Map();
|
|
889
1003
|
for (const source of resolveImportCandidates(importKind, cwd)) {
|
|
890
1004
|
const { path } = source;
|
|
891
1005
|
if (!existsSync(path))
|
|
@@ -900,8 +1014,14 @@ function loadImportedConfig(importKind, cwd, warningPrefix) {
|
|
|
900
1014
|
const entries = isRecord(mcp.servers)
|
|
901
1015
|
? { ...Object.fromEntries(Object.entries(mcp).filter(([name]) => name !== "servers" && name !== "timeout")), ...mcp.servers }
|
|
902
1016
|
: mcp;
|
|
903
|
-
const normalized = Object.fromEntries(Object.entries(entries).map(([name,
|
|
904
|
-
if (!isRecord(
|
|
1017
|
+
const normalized = Object.fromEntries(Object.entries(entries).map(([name, raw]) => {
|
|
1018
|
+
if (!isRecord(raw))
|
|
1019
|
+
return [name, raw];
|
|
1020
|
+
// OpenCode reads only `environment`; the import also accepts the common `env` spelling.
|
|
1021
|
+
// `environment` wins when both are set, since that is what OpenCode runs.
|
|
1022
|
+
const { env, ...rest } = raw;
|
|
1023
|
+
const entry = env !== undefined && rest.environment === undefined ? { ...rest, environment: env } : rest;
|
|
1024
|
+
if (!isRecord(entry.oauth))
|
|
905
1025
|
return [name, entry];
|
|
906
1026
|
const { client_id, client_secret, auth_server_metadata_url, ...oauth } = entry.oauth;
|
|
907
1027
|
return [name, {
|
|
@@ -915,8 +1035,15 @@ function loadImportedConfig(importKind, cwd, warningPrefix) {
|
|
|
915
1035
|
}];
|
|
916
1036
|
}));
|
|
917
1037
|
merged = mergeOpenCodeConfigs(merged, { ...imported, mcp: normalized });
|
|
918
|
-
for (const name of Object.
|
|
1038
|
+
for (const [name, entry] of Object.entries(normalized)) {
|
|
919
1039
|
serverSources.set(name, source);
|
|
1040
|
+
if (!isRecord(entry))
|
|
1041
|
+
continue;
|
|
1042
|
+
const keys = keySources.get(name) ?? new Map();
|
|
1043
|
+
for (const key of Object.keys(entry))
|
|
1044
|
+
keys.set(key, path);
|
|
1045
|
+
keySources.set(name, keys);
|
|
1046
|
+
}
|
|
920
1047
|
highestPrecedenceSource = source;
|
|
921
1048
|
}
|
|
922
1049
|
}
|
|
@@ -936,6 +1063,7 @@ function loadImportedConfig(importKind, cwd, warningPrefix) {
|
|
|
936
1063
|
value: merged,
|
|
937
1064
|
source: highestPrecedenceSource,
|
|
938
1065
|
serverSources,
|
|
1066
|
+
keySources,
|
|
939
1067
|
};
|
|
940
1068
|
}
|
|
941
1069
|
for (const source of resolveImportCandidates(importKind, cwd)) {
|
|
@@ -1231,6 +1359,24 @@ function isRecord(value) {
|
|
|
1231
1359
|
function isStringRecord(value) {
|
|
1232
1360
|
return isRecord(value) && Object.values(value).every((entry) => typeof entry === "string");
|
|
1233
1361
|
}
|
|
1362
|
+
// OpenCode's schema allows only these keys on a server (V1 and V2 forms) and silently drops the rest.
|
|
1363
|
+
const OPENCODE_LOCAL_SERVER_KEYS = new Set(["type", "command", "cwd", "environment", "enabled", "disabled", "codemode", "timeout", "protocol"]);
|
|
1364
|
+
const OPENCODE_REMOTE_SERVER_KEYS = new Set(["type", "url", "headers", "oauth", "enabled", "disabled", "codemode", "timeout", "protocol"]);
|
|
1365
|
+
/** Keys of imported OpenCode servers that OpenCode ignores, so the import ignores them too. */
|
|
1366
|
+
function getOpenCodeIgnoredSettings(merged) {
|
|
1367
|
+
const ignored = new Map();
|
|
1368
|
+
const entries = isRecord(merged) && isRecord(merged.mcp) ? merged.mcp : {};
|
|
1369
|
+
for (const name of Object.keys(extractServers(merged, "opencode"))) {
|
|
1370
|
+
const entry = entries[name];
|
|
1371
|
+
if (!isRecord(entry) || (entry.type !== "local" && entry.type !== "remote"))
|
|
1372
|
+
continue;
|
|
1373
|
+
const allowed = entry.type === "local" ? OPENCODE_LOCAL_SERVER_KEYS : OPENCODE_REMOTE_SERVER_KEYS;
|
|
1374
|
+
const keys = Object.keys(entry).filter((key) => !allowed.has(key));
|
|
1375
|
+
if (keys.length > 0)
|
|
1376
|
+
ignored.set(name, keys);
|
|
1377
|
+
}
|
|
1378
|
+
return ignored;
|
|
1379
|
+
}
|
|
1234
1380
|
function mergeOpenCodeConfigs(base, next) {
|
|
1235
1381
|
const baseMcp = base.mcp;
|
|
1236
1382
|
const nextMcp = next.mcp;
|
|
@@ -1728,16 +1874,22 @@ export function writeSharedServerEntry(filePath, serverName, entry) {
|
|
|
1728
1874
|
export function getServerProvenance(overridePath, cwd = process.cwd()) {
|
|
1729
1875
|
const provenance = new Map();
|
|
1730
1876
|
const userPath = getPiGlobalConfigPath(overridePath);
|
|
1877
|
+
const importProvenance = (importKind, value) => {
|
|
1878
|
+
const ignored = importKind === "opencode" ? getOpenCodeIgnoredSettings(value) : new Map();
|
|
1879
|
+
return new Map(Object.keys(extractServers(value, importKind)).map((name) => {
|
|
1880
|
+
const ignoredSettings = ignored.get(name);
|
|
1881
|
+
// Keep writes inside adapter-owned storage even though the source is external.
|
|
1882
|
+
return [name, { path: userPath, kind: "import", importKind, ...(ignoredSettings ? { ignoredSettings, ignoredSettingsSource: "opencode" } : {}) }];
|
|
1883
|
+
}));
|
|
1884
|
+
};
|
|
1731
1885
|
if (getConfiguredHostConfigDiscovery(overridePath, cwd) === "on") {
|
|
1732
1886
|
for (const importKind of Object.keys(IMPORT_PATHS)) {
|
|
1733
1887
|
const imported = loadImportedConfig(importKind, cwd, `Failed to inspect imported MCP config from ${importKind}:`);
|
|
1734
1888
|
if (!imported)
|
|
1735
1889
|
continue;
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
provenance.set(name, { path: userPath, kind: "import", importKind });
|
|
1740
|
-
}
|
|
1890
|
+
// Later import kinds win in the same deterministic order as loadDiscoveredHostConfigs.
|
|
1891
|
+
for (const [name, prov] of importProvenance(importKind, imported.value))
|
|
1892
|
+
provenance.set(name, prov);
|
|
1741
1893
|
}
|
|
1742
1894
|
}
|
|
1743
1895
|
for (const source of getConfigSources(overridePath, cwd)) {
|
|
@@ -1745,21 +1897,28 @@ export function getServerProvenance(overridePath, cwd = process.cwd()) {
|
|
|
1745
1897
|
if (!loaded)
|
|
1746
1898
|
continue;
|
|
1747
1899
|
if (loaded.imports?.length) {
|
|
1900
|
+
// Same precedence as expandImports + mergeConfigs: the first import in a file wins, and a later file replaces earlier ones.
|
|
1901
|
+
const imported = new Set();
|
|
1748
1902
|
for (const importKind of loaded.imports) {
|
|
1749
|
-
const
|
|
1750
|
-
if (!
|
|
1903
|
+
const importedConfig = loadImportedConfig(importKind, cwd, `Failed to inspect imported MCP config from ${importKind}:`);
|
|
1904
|
+
if (!importedConfig)
|
|
1751
1905
|
continue;
|
|
1752
|
-
const
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1906
|
+
for (const [name, prov] of importProvenance(importKind, importedConfig.value)) {
|
|
1907
|
+
if (imported.has(name))
|
|
1908
|
+
continue;
|
|
1909
|
+
imported.add(name);
|
|
1910
|
+
provenance.set(name, prov);
|
|
1757
1911
|
}
|
|
1758
1912
|
}
|
|
1759
1913
|
}
|
|
1760
1914
|
for (const name of Object.keys(loaded.mcpServers)) {
|
|
1761
1915
|
// A Pi entry brings its own list; other sources keep the one from the Pi entry below them.
|
|
1762
|
-
|
|
1916
|
+
// A source that defines the server replaces an OpenCode import, so its OpenCode list no longer applies.
|
|
1917
|
+
// (`loaded.mcpServers` holds only the file's own servers, so a plain import keeps its list.)
|
|
1918
|
+
const below = provenance.get(name);
|
|
1919
|
+
const ignoredSettings = loaded.ignoredSettings
|
|
1920
|
+
? loaded.ignoredSettings.get(name)
|
|
1921
|
+
: below?.ignoredSettingsSource === "opencode" ? undefined : below?.ignoredSettings;
|
|
1763
1922
|
provenance.set(name, {
|
|
1764
1923
|
path: source.writePath,
|
|
1765
1924
|
kind: source.kind,
|