pi-mcp-adapter 5.3.0 → 5.3.1
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 +18 -0
- package/bearer-command-resolver.ts +8 -4
- package/cli.js +7 -2
- package/config.ts +95 -17
- package/dist/bearer-command-resolver.js +7 -3
- package/dist/bearer-command-resolver.js.map +1 -1
- package/dist/config.js +91 -16
- package/dist/config.js.map +1 -1
- package/dist/metadata-cache.js +2 -0
- package/dist/metadata-cache.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 +1 -1
- package/docs/prompts-and-ui.md +1 -1
- package/docs/servers.md +1 -1
- package/docs/tools.md +3 -1
- package/index.ts +3 -1
- package/mcp-panel.ts +25 -23
- package/metadata-cache.ts +2 -0
- package/package.json +1 -1
- package/proxy-modes.ts +17 -2
- package/server-manager.ts +7 -8
- package/tool-metadata.ts +19 -0
- package/types.ts +11 -2
- package/utils.ts +42 -4
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [5.3.1] - 2026-10-15
|
|
11
|
+
|
|
12
|
+
### Highlights
|
|
13
|
+
|
|
14
|
+
- On Windows, `!command` secrets now run with Git Bash when it is installed, so commands like `!$HOME/bin/get-token` work.
|
|
15
|
+
- `"openUi": false` really runs a server's tools without UI, instead of leaving them waiting on a window that never opens.
|
|
16
|
+
- `describe` no longer hides tool input details such as descriptions, limits and integer types behind a short summary.
|
|
17
|
+
- The MCP panel keeps your saved direct tools selected after a ctrl+r reconnect.
|
|
18
|
+
- OpenCode imports accept `env` for server variables and tell you about settings OpenCode ignores.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- `describe` in the `mcp` tool now shows a tool's full input schema whenever the short summary would leave something out, such as field descriptions, integer types, minimum and maximum values, or shared definitions. Simple schemas still get the short summary. When search shortens a parameter's description, it points you to `describe` for the full text, and very long descriptions are saved to a file instead of being cut off. Thanks to [@chpock](https://github.com/chpock) for [#833](https://github.com/nicobailon/pi-mcp-adapter/pull/833) and [#834](https://github.com/nicobailon/pi-mcp-adapter/pull/834).
|
|
23
|
+
- After you reconnect a server in the MCP panel with ctrl+r, the tools you had saved as direct tools now show as selected, so toggling one tool and saving no longer drops the rest of your selection. Thanks to [@Milor123](https://github.com/Milor123) for [#829](https://github.com/nicobailon/pi-mcp-adapter/issues/829).
|
|
24
|
+
- `"openUi": false` now runs a server's tools without UI instead of only hiding the window. The adapter no longer tells that server it supports MCP Apps, so a server that checks for MCP Apps support runs its tools and returns the result rather than waiting on a form that never opens. Turning it on or off makes the adapter fetch that server's tool list again. Thanks to [@nicolasgarciac](https://github.com/nicolasgarciac) for [#827](https://github.com/nicobailon/pi-mcp-adapter/issues/827).
|
|
25
|
+
- The OpenCode import now accepts `env` as another name for `environment` on a server, so servers configured with `env` get their variables in Pi instead of failing with "Connection closed". When both are set, `environment` wins. OpenCode itself still reads only `environment`. Other settings outside OpenCode's schema are reported at startup, in `/mcp-adapter` and in `pi-mcp-adapter doctor`. Thanks to [@cbaudino](https://github.com/cbaudino) and [@syf2211](https://github.com/syf2211) for [#828](https://github.com/nicobailon/pi-mcp-adapter/issues/828) and [#830](https://github.com/nicobailon/pi-mcp-adapter/pull/830).
|
|
26
|
+
- On Windows, `!command` values for `headers`, `bearerToken`, `oauth.clientSecret` and stdio `env` now run with Git Bash when it is installed, like Pi does, so commands written with POSIX syntax such as `$HOME` work instead of failing in cmd.exe. Without Git Bash they still run in cmd.exe. If Git Bash is installed and a command uses cmd.exe syntax such as `%MY_SECRET%`, rewrite it in POSIX syntax (`$MY_SECRET`). Thanks to [@estrizhok](https://github.com/estrizhok) for [#840](https://github.com/nicobailon/pi-mcp-adapter/issues/840).
|
|
27
|
+
|
|
10
28
|
## [5.3.0] - 2026-10-12
|
|
11
29
|
|
|
12
30
|
### Highlights
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
1
|
+
import { spawn, type SpawnOptionsWithStdioTuple } from "node:child_process";
|
|
2
|
+
import { getWindowsCommandBash } from "./utils.ts";
|
|
2
3
|
|
|
3
4
|
const DEFAULT_TTL_MS = 5 * 60_000;
|
|
4
5
|
const ENV_TTL_MS = "PI_MCP_ADAPTER_BEARER_COMMAND_TTL_MS";
|
|
@@ -20,12 +21,15 @@ function runCommand(command: string, context: string, signal: AbortSignal): Prom
|
|
|
20
21
|
let output = Buffer.alloc(0);
|
|
21
22
|
let settled = false;
|
|
22
23
|
let terminating = false;
|
|
23
|
-
const
|
|
24
|
-
shell: true,
|
|
24
|
+
const options: SpawnOptionsWithStdioTuple<"ignore", "pipe", "ignore"> = {
|
|
25
25
|
stdio: ["ignore", "pipe", "ignore"],
|
|
26
26
|
windowsHide: true,
|
|
27
27
|
detached: USE_PROCESS_GROUP,
|
|
28
|
-
}
|
|
28
|
+
};
|
|
29
|
+
const bash = getWindowsCommandBash();
|
|
30
|
+
const child = bash === undefined
|
|
31
|
+
? spawn(command.slice(1), { ...options, shell: true })
|
|
32
|
+
: spawn(bash, ["-c", command.slice(1)], { ...options, shell: false });
|
|
29
33
|
|
|
30
34
|
const kill = async (): Promise<void> => {
|
|
31
35
|
if (child.pid === undefined) return;
|
package/cli.js
CHANGED
|
@@ -530,9 +530,9 @@ async function runDoctor(argv, log, error) {
|
|
|
530
530
|
error("Usage: pi-mcp-adapter doctor [--json]");
|
|
531
531
|
return 1;
|
|
532
532
|
}
|
|
533
|
-
let config, trust, manager, auth, authFlow, utils, agentDir;
|
|
533
|
+
let config, trust, manager, auth, authFlow, utils, agentDir, types;
|
|
534
534
|
try {
|
|
535
|
-
[config, trust, manager, auth, authFlow, utils, agentDir] = await Promise.all([
|
|
535
|
+
[config, trust, manager, auth, authFlow, utils, agentDir, types] = await Promise.all([
|
|
536
536
|
import("./dist/config.js"),
|
|
537
537
|
import("./dist/project-server-trust.js"),
|
|
538
538
|
import("./dist/server-manager.js"),
|
|
@@ -540,6 +540,7 @@ async function runDoctor(argv, log, error) {
|
|
|
540
540
|
import("./dist/mcp-auth-flow.js"),
|
|
541
541
|
import("./dist/utils.js"),
|
|
542
542
|
import("./dist/agent-dir.js"),
|
|
543
|
+
import("./dist/types.js"),
|
|
543
544
|
]);
|
|
544
545
|
} catch (err) {
|
|
545
546
|
error("Unable to load doctor command modules.");
|
|
@@ -557,6 +558,7 @@ async function runDoctor(argv, log, error) {
|
|
|
557
558
|
error("Pi 0.99 or later wasn't found on PATH, so Pi's own mcp.json files were not checked.");
|
|
558
559
|
}
|
|
559
560
|
const loaded = config.loadMcpConfigWithSources(undefined, cwd);
|
|
561
|
+
const provenance = config.getServerProvenance(undefined, cwd);
|
|
560
562
|
let projectTrusted = false;
|
|
561
563
|
if (loaded.projectServers.size > 0) {
|
|
562
564
|
try {
|
|
@@ -636,11 +638,13 @@ async function runDoctor(argv, log, error) {
|
|
|
636
638
|
timedOut,
|
|
637
639
|
]);
|
|
638
640
|
clearTimeout(timer);
|
|
641
|
+
const ignoredSettings = types.describeIgnoredSettings(provenance.get(name));
|
|
639
642
|
return {
|
|
640
643
|
name,
|
|
641
644
|
state: result.state,
|
|
642
645
|
tools: result.tools ?? null,
|
|
643
646
|
message: result.message ? utils.sanitizeTerminalText(redactDoctorMessage(result.message, definition, utils)) : null,
|
|
647
|
+
...(ignoredSettings ? { ignoredSettings: utils.sanitizeTerminalText(ignoredSettings) } : {}),
|
|
644
648
|
};
|
|
645
649
|
}));
|
|
646
650
|
|
|
@@ -654,6 +658,7 @@ async function runDoctor(argv, log, error) {
|
|
|
654
658
|
for (const result of results) {
|
|
655
659
|
const tools = result.tools === null ? "" : `, ${result.tools} tool${result.tools === 1 ? "" : "s"}`;
|
|
656
660
|
log(`${utils.sanitizeTerminalText(result.name)}: ${result.state}${tools}${result.message ? ` — ${result.message}` : ""}`);
|
|
661
|
+
if (result.ignoredSettings) log(` ${result.ignoredSettings}`);
|
|
657
662
|
}
|
|
658
663
|
}
|
|
659
664
|
} finally {
|
package/config.ts
CHANGED
|
@@ -269,6 +269,10 @@ function legacyMcpConfigHasContent(filePath: string): boolean {
|
|
|
269
269
|
}
|
|
270
270
|
|
|
271
271
|
export function getLegacyMcpMigrationNotices(cwd = process.cwd(), overridePath?: string): string[] {
|
|
272
|
+
return [...getPiMcpConfigNotices(cwd, overridePath), ...getOpenCodeImportNotices(cwd, overridePath)];
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function getPiMcpConfigNotices(cwd: string, overridePath?: string): string[] {
|
|
272
276
|
if (piMcpConfigEnabled) {
|
|
273
277
|
return getConfigSources(overridePath, cwd).flatMap((source) => {
|
|
274
278
|
const file = isPiMcpSource(source.id) ? readPiMcpConfig(source.readPath) : null;
|
|
@@ -301,6 +305,31 @@ export function getLegacyMcpMigrationNotices(cwd = process.cwd(), overridePath?:
|
|
|
301
305
|
});
|
|
302
306
|
}
|
|
303
307
|
|
|
308
|
+
/** Settings OpenCode drops from an active `opencode` import, grouped by the file that defines each server. */
|
|
309
|
+
function getOpenCodeImportNotices(cwd: string, overridePath?: string): string[] {
|
|
310
|
+
const active = (!isExclusiveConfigMode() && getConfiguredHostConfigDiscovery(overridePath, cwd) === "on")
|
|
311
|
+
|| getConfigSources(overridePath, cwd).some((source) => readSourceConfig(source.id, source.readPath)?.imports?.includes("opencode"));
|
|
312
|
+
const imported = active ? loadImportedConfig("opencode", cwd, "Failed to inspect imported MCP config from opencode:") : null;
|
|
313
|
+
if (!imported) return [];
|
|
314
|
+
// Warn only where the OpenCode import is still the server's definition, matching /mcp-adapter.
|
|
315
|
+
const provenance = getServerProvenance(overridePath, cwd);
|
|
316
|
+
// Name the file each key came from: a project entry can inherit a stray key from the global file.
|
|
317
|
+
const byFile = new Map<string, Map<string, string[]>>();
|
|
318
|
+
for (const [name, keys] of getOpenCodeIgnoredSettings(imported.value)) {
|
|
319
|
+
if (provenance.get(name)?.ignoredSettingsSource !== "opencode") continue;
|
|
320
|
+
for (const key of keys) {
|
|
321
|
+
const path = imported.keySources?.get(name)?.get(key) ?? imported.path;
|
|
322
|
+
const servers = byFile.get(path) ?? new Map<string, string[]>();
|
|
323
|
+
servers.set(name, [...servers.get(name) ?? [], key]);
|
|
324
|
+
byFile.set(path, servers);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return [...byFile].map(([path, servers]) => {
|
|
328
|
+
const ignored = [...servers].map(([name, settings]) => `"${name}": ${settings.join(", ")}`);
|
|
329
|
+
return `${path}: OpenCode ignores these settings, so the import does too (details in /mcp-adapter): ${ignored.join("; ")}.`;
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
304
333
|
export function getSharedConfigPath(target: SharedConfigTarget, cwd = process.cwd()): string {
|
|
305
334
|
return target === "project" ? getProjectConfigPath(cwd) : getGenericGlobalConfigPath();
|
|
306
335
|
}
|
|
@@ -1047,11 +1076,19 @@ function loadImportedConfig(
|
|
|
1047
1076
|
importKind: ImportKind,
|
|
1048
1077
|
cwd: string,
|
|
1049
1078
|
warningPrefix: string,
|
|
1050
|
-
): {
|
|
1079
|
+
): {
|
|
1080
|
+
path: string;
|
|
1081
|
+
value: unknown;
|
|
1082
|
+
source: ImportedServerSource;
|
|
1083
|
+
serverSources: Map<string, ImportedServerSource>;
|
|
1084
|
+
/** OpenCode only: the file that last set each key of each server, in merge order. */
|
|
1085
|
+
keySources?: Map<string, Map<string, string>>;
|
|
1086
|
+
} | null {
|
|
1051
1087
|
if (importKind === "opencode") {
|
|
1052
1088
|
let merged: Record<string, unknown> = {};
|
|
1053
1089
|
let highestPrecedenceSource: ImportedServerSource | undefined;
|
|
1054
1090
|
const serverSources = new Map<string, ImportedServerSource>();
|
|
1091
|
+
const keySources = new Map<string, Map<string, string>>();
|
|
1055
1092
|
|
|
1056
1093
|
for (const source of resolveImportCandidates(importKind, cwd)) {
|
|
1057
1094
|
const { path } = source;
|
|
@@ -1067,8 +1104,13 @@ function loadImportedConfig(
|
|
|
1067
1104
|
const entries = isRecord(mcp.servers)
|
|
1068
1105
|
? { ...Object.fromEntries(Object.entries(mcp).filter(([name]) => name !== "servers" && name !== "timeout")), ...mcp.servers }
|
|
1069
1106
|
: mcp;
|
|
1070
|
-
const normalized = Object.fromEntries(Object.entries(entries).map(([name,
|
|
1071
|
-
if (!isRecord(
|
|
1107
|
+
const normalized = Object.fromEntries(Object.entries(entries).map(([name, raw]) => {
|
|
1108
|
+
if (!isRecord(raw)) return [name, raw];
|
|
1109
|
+
// OpenCode reads only `environment`; the import also accepts the common `env` spelling.
|
|
1110
|
+
// `environment` wins when both are set, since that is what OpenCode runs.
|
|
1111
|
+
const { env, ...rest } = raw;
|
|
1112
|
+
const entry = env !== undefined && rest.environment === undefined ? { ...rest, environment: env } : rest;
|
|
1113
|
+
if (!isRecord(entry.oauth)) return [name, entry];
|
|
1072
1114
|
const { client_id, client_secret, auth_server_metadata_url, ...oauth } = entry.oauth;
|
|
1073
1115
|
return [name, {
|
|
1074
1116
|
...entry,
|
|
@@ -1081,7 +1123,13 @@ function loadImportedConfig(
|
|
|
1081
1123
|
}];
|
|
1082
1124
|
}));
|
|
1083
1125
|
merged = mergeOpenCodeConfigs(merged, { ...imported, mcp: normalized });
|
|
1084
|
-
for (const name of Object.
|
|
1126
|
+
for (const [name, entry] of Object.entries(normalized)) {
|
|
1127
|
+
serverSources.set(name, source);
|
|
1128
|
+
if (!isRecord(entry)) continue;
|
|
1129
|
+
const keys = keySources.get(name) ?? new Map<string, string>();
|
|
1130
|
+
for (const key of Object.keys(entry)) keys.set(key, path);
|
|
1131
|
+
keySources.set(name, keys);
|
|
1132
|
+
}
|
|
1085
1133
|
highestPrecedenceSource = source;
|
|
1086
1134
|
}
|
|
1087
1135
|
} catch (error) {
|
|
@@ -1099,6 +1147,7 @@ function loadImportedConfig(
|
|
|
1099
1147
|
value: merged,
|
|
1100
1148
|
source: highestPrecedenceSource,
|
|
1101
1149
|
serverSources,
|
|
1150
|
+
keySources,
|
|
1102
1151
|
};
|
|
1103
1152
|
}
|
|
1104
1153
|
|
|
@@ -1385,6 +1434,24 @@ function isStringRecord(value: unknown): value is Record<string, string> {
|
|
|
1385
1434
|
return isRecord(value) && Object.values(value).every((entry) => typeof entry === "string");
|
|
1386
1435
|
}
|
|
1387
1436
|
|
|
1437
|
+
// OpenCode's schema allows only these keys on a server (V1 and V2 forms) and silently drops the rest.
|
|
1438
|
+
const OPENCODE_LOCAL_SERVER_KEYS = new Set(["type", "command", "cwd", "environment", "enabled", "disabled", "codemode", "timeout", "protocol"]);
|
|
1439
|
+
const OPENCODE_REMOTE_SERVER_KEYS = new Set(["type", "url", "headers", "oauth", "enabled", "disabled", "codemode", "timeout", "protocol"]);
|
|
1440
|
+
|
|
1441
|
+
/** Keys of imported OpenCode servers that OpenCode ignores, so the import ignores them too. */
|
|
1442
|
+
function getOpenCodeIgnoredSettings(merged: unknown): Map<string, string[]> {
|
|
1443
|
+
const ignored = new Map<string, string[]>();
|
|
1444
|
+
const entries = isRecord(merged) && isRecord(merged.mcp) ? merged.mcp : {};
|
|
1445
|
+
for (const name of Object.keys(extractServers(merged, "opencode"))) {
|
|
1446
|
+
const entry = entries[name];
|
|
1447
|
+
if (!isRecord(entry) || (entry.type !== "local" && entry.type !== "remote")) continue;
|
|
1448
|
+
const allowed = entry.type === "local" ? OPENCODE_LOCAL_SERVER_KEYS : OPENCODE_REMOTE_SERVER_KEYS;
|
|
1449
|
+
const keys = Object.keys(entry).filter((key) => !allowed.has(key));
|
|
1450
|
+
if (keys.length > 0) ignored.set(name, keys);
|
|
1451
|
+
}
|
|
1452
|
+
return ignored;
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1388
1455
|
function mergeOpenCodeConfigs(base: Record<string, unknown>, next: Record<string, unknown>): Record<string, unknown> {
|
|
1389
1456
|
const baseMcp = base.mcp;
|
|
1390
1457
|
const nextMcp = next.mcp;
|
|
@@ -1925,16 +1992,21 @@ export function writeSharedServerEntry(filePath: string, serverName: string, ent
|
|
|
1925
1992
|
export function getServerProvenance(overridePath?: string, cwd = process.cwd()): Map<string, ServerProvenance> {
|
|
1926
1993
|
const provenance = new Map<string, ServerProvenance>();
|
|
1927
1994
|
const userPath = getPiGlobalConfigPath(overridePath);
|
|
1995
|
+
const importProvenance = (importKind: ImportKind, value: unknown): Map<string, ServerProvenance> => {
|
|
1996
|
+
const ignored = importKind === "opencode" ? getOpenCodeIgnoredSettings(value) : new Map<string, string[]>();
|
|
1997
|
+
return new Map(Object.keys(extractServers(value, importKind)).map((name): [string, ServerProvenance] => {
|
|
1998
|
+
const ignoredSettings = ignored.get(name);
|
|
1999
|
+
// Keep writes inside adapter-owned storage even though the source is external.
|
|
2000
|
+
return [name, { path: userPath, kind: "import", importKind, ...(ignoredSettings ? { ignoredSettings, ignoredSettingsSource: "opencode" } : {}) }];
|
|
2001
|
+
}));
|
|
2002
|
+
};
|
|
1928
2003
|
|
|
1929
2004
|
if (getConfiguredHostConfigDiscovery(overridePath, cwd) === "on") {
|
|
1930
2005
|
for (const importKind of Object.keys(IMPORT_PATHS) as ImportKind[]) {
|
|
1931
2006
|
const imported = loadImportedConfig(importKind, cwd, `Failed to inspect imported MCP config from ${importKind}:`);
|
|
1932
2007
|
if (!imported) continue;
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
// Later import kinds win in the same deterministic order as loadDiscoveredHostConfigs.
|
|
1936
|
-
provenance.set(name, { path: userPath, kind: "import", importKind });
|
|
1937
|
-
}
|
|
2008
|
+
// Later import kinds win in the same deterministic order as loadDiscoveredHostConfigs.
|
|
2009
|
+
for (const [name, prov] of importProvenance(importKind, imported.value)) provenance.set(name, prov);
|
|
1938
2010
|
}
|
|
1939
2011
|
}
|
|
1940
2012
|
|
|
@@ -1942,22 +2014,28 @@ export function getServerProvenance(overridePath?: string, cwd = process.cwd()):
|
|
|
1942
2014
|
const loaded = readSourceConfig(source.id, source.readPath);
|
|
1943
2015
|
if (!loaded) continue;
|
|
1944
2016
|
if (loaded.imports?.length) {
|
|
2017
|
+
// Same precedence as expandImports + mergeConfigs: the first import in a file wins, and a later file replaces earlier ones.
|
|
2018
|
+
const imported = new Set<string>();
|
|
1945
2019
|
for (const importKind of loaded.imports) {
|
|
1946
|
-
const
|
|
1947
|
-
if (!
|
|
2020
|
+
const importedConfig = loadImportedConfig(importKind, cwd, `Failed to inspect imported MCP config from ${importKind}:`);
|
|
2021
|
+
if (!importedConfig) continue;
|
|
1948
2022
|
|
|
1949
|
-
const
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
}
|
|
2023
|
+
for (const [name, prov] of importProvenance(importKind, importedConfig.value)) {
|
|
2024
|
+
if (imported.has(name)) continue;
|
|
2025
|
+
imported.add(name);
|
|
2026
|
+
provenance.set(name, prov);
|
|
1954
2027
|
}
|
|
1955
2028
|
}
|
|
1956
2029
|
}
|
|
1957
2030
|
|
|
1958
2031
|
for (const name of Object.keys(loaded.mcpServers)) {
|
|
1959
2032
|
// A Pi entry brings its own list; other sources keep the one from the Pi entry below them.
|
|
1960
|
-
|
|
2033
|
+
// A source that defines the server replaces an OpenCode import, so its OpenCode list no longer applies.
|
|
2034
|
+
// (`loaded.mcpServers` holds only the file's own servers, so a plain import keeps its list.)
|
|
2035
|
+
const below = provenance.get(name);
|
|
2036
|
+
const ignoredSettings = loaded.ignoredSettings
|
|
2037
|
+
? loaded.ignoredSettings.get(name)
|
|
2038
|
+
: below?.ignoredSettingsSource === "opencode" ? undefined : below?.ignoredSettings;
|
|
1961
2039
|
provenance.set(name, {
|
|
1962
2040
|
path: source.writePath,
|
|
1963
2041
|
kind: source.kind,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { getWindowsCommandBash } from "./utils.js";
|
|
2
3
|
const DEFAULT_TTL_MS = 5 * 60_000;
|
|
3
4
|
const ENV_TTL_MS = "PI_MCP_ADAPTER_BEARER_COMMAND_TTL_MS";
|
|
4
5
|
const COMMAND_TIMEOUT_MS = 10_000;
|
|
@@ -16,12 +17,15 @@ function runCommand(command, context, signal) {
|
|
|
16
17
|
let output = Buffer.alloc(0);
|
|
17
18
|
let settled = false;
|
|
18
19
|
let terminating = false;
|
|
19
|
-
const
|
|
20
|
-
shell: true,
|
|
20
|
+
const options = {
|
|
21
21
|
stdio: ["ignore", "pipe", "ignore"],
|
|
22
22
|
windowsHide: true,
|
|
23
23
|
detached: USE_PROCESS_GROUP,
|
|
24
|
-
}
|
|
24
|
+
};
|
|
25
|
+
const bash = getWindowsCommandBash();
|
|
26
|
+
const child = bash === undefined
|
|
27
|
+
? spawn(command.slice(1), { ...options, shell: true })
|
|
28
|
+
: spawn(bash, ["-c", command.slice(1)], { ...options, shell: false });
|
|
25
29
|
const kill = async () => {
|
|
26
30
|
if (child.pid === undefined)
|
|
27
31
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bearer-command-resolver.js","sourceRoot":"","sources":["../bearer-command-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,
|
|
1
|
+
{"version":3,"file":"bearer-command-resolver.js","sourceRoot":"","sources":["../bearer-command-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAmC,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAEnD,MAAM,cAAc,GAAG,CAAC,GAAG,MAAM,CAAC;AAClC,MAAM,UAAU,GAAG,sCAAsC,CAAC;AAC1D,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAClC,MAAM,wBAAwB,GAAG,IAAI,GAAG,IAAI,CAAC;AAC7C,MAAM,iBAAiB,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC;AAEvD,SAAS,mBAAmB;IAC1B,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,cAAc,CAAC,CAAC;IACjE,OAAO,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC;AAC9E,CAAC;AAED,SAAS,WAAW,CAAC,MAAmB;IACtC,OAAO,MAAM,CAAC,MAAM,IAAI,IAAI,YAAY,CAAC,2BAA2B,EAAE,YAAY,CAAC,CAAC;AACtF,CAAC;AAED,SAAS,UAAU,CAAC,OAAe,EAAE,OAAe,EAAE,MAAmB;IACvE,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,IAAI,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC7B,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,IAAI,WAAW,GAAG,KAAK,CAAC;QACxB,MAAM,OAAO,GAA2D;YACtE,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;YACnC,WAAW,EAAE,IAAI;YACjB,QAAQ,EAAE,iBAAiB;SAC5B,CAAC;QACF,MAAM,IAAI,GAAG,qBAAqB,EAAE,CAAC;QACrC,MAAM,KAAK,GAAG,IAAI,KAAK,SAAS;YAC9B,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;YACtD,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;QAExE,MAAM,IAAI,GAAG,KAAK,IAAmB,EAAE;YACrC,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS;gBAAE,OAAO;YACpC,IAAI,CAAC,iBAAiB,EAAE,CAAC;gBACvB,MAAM,IAAI,OAAO,CAAO,CAAC,WAAW,EAAE,UAAU,EAAE,EAAE;oBAClD,MAAM,MAAM,GAAG,KAAK,CAAC,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE;wBACxE,KAAK,EAAE,QAAQ;wBACf,WAAW,EAAE,IAAI;qBAClB,CAAC,CAAC;oBACH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC,CAAC,CAAC;oBACvF,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE;wBACxB,IAAI,IAAI,KAAK,CAAC,IAAI,IAAI,KAAK,GAAG;4BAAE,WAAW,EAAE,CAAC;;4BACzC,UAAU,CAAC,IAAI,KAAK,CAAC,kEAAkE,IAAI,IAAI,SAAS,EAAE,CAAC,CAAC,CAAC;oBACpH,CAAC,CAAC,CAAC;gBACL,CAAC,CAAC,CAAC;gBACH,OAAO;YACT,CAAC;YACD,IAAI,CAAC;gBACH,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;YACtC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAK,KAA+B,CAAC,IAAI,KAAK,OAAO;oBAAE,MAAM,KAAK,CAAC;YACrE,CAAC;QACH,CAAC,CAAC;QACF,MAAM,MAAM,GAAG,CAAC,KAAc,EAAE,KAAc,EAAE,EAAE;YAChD,IAAI,OAAO;gBAAE,OAAO;YACpB,OAAO,GAAG,IAAI,CAAC;YACf,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC7C,IAAI,KAAK,KAAK,SAAS;gBAAE,MAAM,CAAC,KAAK,CAAC,CAAC;;gBAClC,OAAO,CAAC,KAAM,CAAC,CAAC;QACvB,CAAC,CAAC;QACF,MAAM,SAAS,GAAG,KAAK,EAAE,KAAc,EAAE,EAAE;YACzC,IAAI,OAAO,IAAI,WAAW;gBAAE,OAAO;YACnC,WAAW,GAAG,IAAI,CAAC;YACnB,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC7C,IAAI,CAAC;gBACH,MAAM,IAAI,EAAE,CAAC;gBACb,MAAM,CAAC,KAAK,CAAC,CAAC;YAChB,CAAC;YAAC,OAAO,YAAY,EAAE,CAAC;gBACtB,MAAM,CAAC,YAAY,CAAC,CAAC;YACvB,CAAC;QACH,CAAC,CAAC;QACF,MAAM,OAAO,GAAG,GAAG,EAAE;YACnB,KAAK,SAAS,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC;QACtC,CAAC,CAAC;QACF,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,KAAK,SAAS,CAAC,IAAI,KAAK,CAAC,qBAAqB,OAAO,6BAA6B,kBAAkB,IAAI,CAAC,CAAC,CAAC;QAC7G,CAAC,EAAE,kBAAkB,CAAC,CAAC;QAEvB,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1D,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,OAAO,EAAE,CAAC;YACV,OAAO;QACT,CAAC;QACD,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YACrB,IAAI,CAAC,WAAW;gBAAE,MAAM,CAAC,IAAI,KAAK,CAAC,qBAAqB,OAAO,2BAA2B,CAAC,CAAC,CAAC;QAC/F,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAsB,EAAE,EAAE;YACjD,IAAI,OAAO,IAAI,WAAW;gBAAE,OAAO;YACnC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YACrD,IAAI,MAAM,CAAC,UAAU,GAAG,wBAAwB,EAAE,CAAC;gBACjD,KAAK,SAAS,CAAC,IAAI,KAAK,CAAC,qBAAqB,OAAO,iCAAiC,CAAC,CAAC,CAAC;YAC3F,CAAC;QACH,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE;YACvB,IAAI,OAAO,IAAI,WAAW;gBAAE,OAAO;YACnC,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;gBACf,MAAM,CAAC,IAAI,KAAK,CAAC,qBAAqB,OAAO,8BAA8B,IAAI,IAAI,SAAS,EAAE,CAAC,CAAC,CAAC;gBACjG,OAAO;YACT,CAAC;YACD,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;YAC7C,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,MAAM,CAAC,IAAI,KAAK,CAAC,qBAAqB,OAAO,iCAAiC,CAAC,CAAC,CAAC;gBACjF,OAAO;YACT,CAAC;YACD,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QAC3B,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAQD,wEAAwE;AACxE,MAAM,OAAO,qBAAqB;IACvB,QAAQ,CAAS;IACjB,QAAQ,CAAS;IACjB,MAAM,CAAS;IACxB,OAAO,CAAmD;IAC1D,QAAQ,CAAkD;IAC1D,SAAS,CAAuB;IAEhC,YAAY,OAAe,EAAE,OAAe,EAAE,QAAgB,mBAAmB,EAAE;QACjF,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;QACxB,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;QACxB,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;IACtB,CAAC;IAED,OAAO,CAAC,MAAoB;QAC1B,IAAI,MAAM,EAAE,OAAO;YAAE,OAAO,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC;QAChE,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,IAAI,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC;YAC/D,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC7C,CAAC;QACD,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS,IAAI,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;YAC/D,OAAO,IAAI,CAAC,OAAO,KAAK,SAAS;gBAC/B,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC;gBACrC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC1C,CAAC;QAED,IAAI,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC;QAC9B,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;YACzC,IAAI,OAAiB,CAAC;YACtB,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,MAAM,CAAC;iBACxE,IAAI,CAAC,KAAK,CAAC,EAAE;gBACZ,IAAI,CAAC,OAAO,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;gBAC9D,IAAI,CAAC,QAAQ,GAAG,SAAS,CAAC;gBAC1B,OAAO,KAAK,CAAC;YACf,CAAC,CAAC;iBACD,KAAK,CAAC,KAAK,CAAC,EAAE;gBACb,8DAA8D;gBAC9D,mEAAmE;gBACnE,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;oBAC/B,IAAI,CAAC,QAAQ,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;gBAC/D,CAAC;gBACD,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS;oBAAE,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC;gBAC1D,MAAM,KAAK,CAAC;YACd,CAAC,CAAC;iBACD,OAAO,CAAC,GAAG,EAAE;gBACZ,IAAI,IAAI,CAAC,SAAS,KAAK,OAAO;oBAAE,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;YAC7D,CAAC,CAAC,CAAC;YACL,OAAO,GAAG,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;YAC9C,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC;YACzB,QAAQ,GAAG,OAAO,CAAC;QACrB,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACtC,CAAC;IAED,KAAK,CAAC,QAAkB,EAAE,MAAoB;QAC5C,QAAQ,CAAC,OAAO,EAAE,CAAC;QACnB,IAAI,OAAiC,CAAC;QACtC,MAAM,MAAM,GAAG,MAAM,KAAK,SAAS;YACjC,CAAC,CAAC,QAAQ,CAAC,OAAO;YAClB,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC;gBACX,QAAQ,CAAC,OAAO;gBAChB,IAAI,OAAO,CAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE;oBACvC,OAAO,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC;oBAC5C,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;gBAC5D,CAAC,CAAC;aACH,CAAC,CAAC;QACP,OAAO,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE;YACzB,IAAI,OAAO;gBAAE,MAAO,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC3D,QAAQ,CAAC,OAAO,EAAE,CAAC;YACnB,IAAI,IAAI,CAAC,SAAS,KAAK,QAAQ,IAAI,QAAQ,CAAC,OAAO,KAAK,CAAC,EAAE,CAAC;gBAC1D,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;gBAC3B,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YAC5C,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}
|
package/dist/config.js
CHANGED
|
@@ -162,6 +162,9 @@ function legacyMcpConfigHasContent(filePath) {
|
|
|
162
162
|
}
|
|
163
163
|
}
|
|
164
164
|
export function getLegacyMcpMigrationNotices(cwd = process.cwd(), overridePath) {
|
|
165
|
+
return [...getPiMcpConfigNotices(cwd, overridePath), ...getOpenCodeImportNotices(cwd, overridePath)];
|
|
166
|
+
}
|
|
167
|
+
function getPiMcpConfigNotices(cwd, overridePath) {
|
|
165
168
|
if (piMcpConfigEnabled) {
|
|
166
169
|
return getConfigSources(overridePath, cwd).flatMap((source) => {
|
|
167
170
|
const file = isPiMcpSource(source.id) ? readPiMcpConfig(source.readPath) : null;
|
|
@@ -196,6 +199,32 @@ export function getLegacyMcpMigrationNotices(cwd = process.cwd(), overridePath)
|
|
|
196
199
|
return [`pi-mcp-adapter no longer reads ${source}. ${fix}`];
|
|
197
200
|
});
|
|
198
201
|
}
|
|
202
|
+
/** Settings OpenCode drops from an active `opencode` import, grouped by the file that defines each server. */
|
|
203
|
+
function getOpenCodeImportNotices(cwd, overridePath) {
|
|
204
|
+
const active = (!isExclusiveConfigMode() && getConfiguredHostConfigDiscovery(overridePath, cwd) === "on")
|
|
205
|
+
|| getConfigSources(overridePath, cwd).some((source) => readSourceConfig(source.id, source.readPath)?.imports?.includes("opencode"));
|
|
206
|
+
const imported = active ? loadImportedConfig("opencode", cwd, "Failed to inspect imported MCP config from opencode:") : null;
|
|
207
|
+
if (!imported)
|
|
208
|
+
return [];
|
|
209
|
+
// Warn only where the OpenCode import is still the server's definition, matching /mcp-adapter.
|
|
210
|
+
const provenance = getServerProvenance(overridePath, cwd);
|
|
211
|
+
// Name the file each key came from: a project entry can inherit a stray key from the global file.
|
|
212
|
+
const byFile = new Map();
|
|
213
|
+
for (const [name, keys] of getOpenCodeIgnoredSettings(imported.value)) {
|
|
214
|
+
if (provenance.get(name)?.ignoredSettingsSource !== "opencode")
|
|
215
|
+
continue;
|
|
216
|
+
for (const key of keys) {
|
|
217
|
+
const path = imported.keySources?.get(name)?.get(key) ?? imported.path;
|
|
218
|
+
const servers = byFile.get(path) ?? new Map();
|
|
219
|
+
servers.set(name, [...servers.get(name) ?? [], key]);
|
|
220
|
+
byFile.set(path, servers);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return [...byFile].map(([path, servers]) => {
|
|
224
|
+
const ignored = [...servers].map(([name, settings]) => `"${name}": ${settings.join(", ")}`);
|
|
225
|
+
return `${path}: OpenCode ignores these settings, so the import does too (details in /mcp-adapter): ${ignored.join("; ")}.`;
|
|
226
|
+
});
|
|
227
|
+
}
|
|
199
228
|
export function getSharedConfigPath(target, cwd = process.cwd()) {
|
|
200
229
|
return target === "project" ? getProjectConfigPath(cwd) : getGenericGlobalConfigPath();
|
|
201
230
|
}
|
|
@@ -886,6 +915,7 @@ function loadImportedConfig(importKind, cwd, warningPrefix) {
|
|
|
886
915
|
let merged = {};
|
|
887
916
|
let highestPrecedenceSource;
|
|
888
917
|
const serverSources = new Map();
|
|
918
|
+
const keySources = new Map();
|
|
889
919
|
for (const source of resolveImportCandidates(importKind, cwd)) {
|
|
890
920
|
const { path } = source;
|
|
891
921
|
if (!existsSync(path))
|
|
@@ -900,8 +930,14 @@ function loadImportedConfig(importKind, cwd, warningPrefix) {
|
|
|
900
930
|
const entries = isRecord(mcp.servers)
|
|
901
931
|
? { ...Object.fromEntries(Object.entries(mcp).filter(([name]) => name !== "servers" && name !== "timeout")), ...mcp.servers }
|
|
902
932
|
: mcp;
|
|
903
|
-
const normalized = Object.fromEntries(Object.entries(entries).map(([name,
|
|
904
|
-
if (!isRecord(
|
|
933
|
+
const normalized = Object.fromEntries(Object.entries(entries).map(([name, raw]) => {
|
|
934
|
+
if (!isRecord(raw))
|
|
935
|
+
return [name, raw];
|
|
936
|
+
// OpenCode reads only `environment`; the import also accepts the common `env` spelling.
|
|
937
|
+
// `environment` wins when both are set, since that is what OpenCode runs.
|
|
938
|
+
const { env, ...rest } = raw;
|
|
939
|
+
const entry = env !== undefined && rest.environment === undefined ? { ...rest, environment: env } : rest;
|
|
940
|
+
if (!isRecord(entry.oauth))
|
|
905
941
|
return [name, entry];
|
|
906
942
|
const { client_id, client_secret, auth_server_metadata_url, ...oauth } = entry.oauth;
|
|
907
943
|
return [name, {
|
|
@@ -915,8 +951,15 @@ function loadImportedConfig(importKind, cwd, warningPrefix) {
|
|
|
915
951
|
}];
|
|
916
952
|
}));
|
|
917
953
|
merged = mergeOpenCodeConfigs(merged, { ...imported, mcp: normalized });
|
|
918
|
-
for (const name of Object.
|
|
954
|
+
for (const [name, entry] of Object.entries(normalized)) {
|
|
919
955
|
serverSources.set(name, source);
|
|
956
|
+
if (!isRecord(entry))
|
|
957
|
+
continue;
|
|
958
|
+
const keys = keySources.get(name) ?? new Map();
|
|
959
|
+
for (const key of Object.keys(entry))
|
|
960
|
+
keys.set(key, path);
|
|
961
|
+
keySources.set(name, keys);
|
|
962
|
+
}
|
|
920
963
|
highestPrecedenceSource = source;
|
|
921
964
|
}
|
|
922
965
|
}
|
|
@@ -936,6 +979,7 @@ function loadImportedConfig(importKind, cwd, warningPrefix) {
|
|
|
936
979
|
value: merged,
|
|
937
980
|
source: highestPrecedenceSource,
|
|
938
981
|
serverSources,
|
|
982
|
+
keySources,
|
|
939
983
|
};
|
|
940
984
|
}
|
|
941
985
|
for (const source of resolveImportCandidates(importKind, cwd)) {
|
|
@@ -1231,6 +1275,24 @@ function isRecord(value) {
|
|
|
1231
1275
|
function isStringRecord(value) {
|
|
1232
1276
|
return isRecord(value) && Object.values(value).every((entry) => typeof entry === "string");
|
|
1233
1277
|
}
|
|
1278
|
+
// OpenCode's schema allows only these keys on a server (V1 and V2 forms) and silently drops the rest.
|
|
1279
|
+
const OPENCODE_LOCAL_SERVER_KEYS = new Set(["type", "command", "cwd", "environment", "enabled", "disabled", "codemode", "timeout", "protocol"]);
|
|
1280
|
+
const OPENCODE_REMOTE_SERVER_KEYS = new Set(["type", "url", "headers", "oauth", "enabled", "disabled", "codemode", "timeout", "protocol"]);
|
|
1281
|
+
/** Keys of imported OpenCode servers that OpenCode ignores, so the import ignores them too. */
|
|
1282
|
+
function getOpenCodeIgnoredSettings(merged) {
|
|
1283
|
+
const ignored = new Map();
|
|
1284
|
+
const entries = isRecord(merged) && isRecord(merged.mcp) ? merged.mcp : {};
|
|
1285
|
+
for (const name of Object.keys(extractServers(merged, "opencode"))) {
|
|
1286
|
+
const entry = entries[name];
|
|
1287
|
+
if (!isRecord(entry) || (entry.type !== "local" && entry.type !== "remote"))
|
|
1288
|
+
continue;
|
|
1289
|
+
const allowed = entry.type === "local" ? OPENCODE_LOCAL_SERVER_KEYS : OPENCODE_REMOTE_SERVER_KEYS;
|
|
1290
|
+
const keys = Object.keys(entry).filter((key) => !allowed.has(key));
|
|
1291
|
+
if (keys.length > 0)
|
|
1292
|
+
ignored.set(name, keys);
|
|
1293
|
+
}
|
|
1294
|
+
return ignored;
|
|
1295
|
+
}
|
|
1234
1296
|
function mergeOpenCodeConfigs(base, next) {
|
|
1235
1297
|
const baseMcp = base.mcp;
|
|
1236
1298
|
const nextMcp = next.mcp;
|
|
@@ -1728,16 +1790,22 @@ export function writeSharedServerEntry(filePath, serverName, entry) {
|
|
|
1728
1790
|
export function getServerProvenance(overridePath, cwd = process.cwd()) {
|
|
1729
1791
|
const provenance = new Map();
|
|
1730
1792
|
const userPath = getPiGlobalConfigPath(overridePath);
|
|
1793
|
+
const importProvenance = (importKind, value) => {
|
|
1794
|
+
const ignored = importKind === "opencode" ? getOpenCodeIgnoredSettings(value) : new Map();
|
|
1795
|
+
return new Map(Object.keys(extractServers(value, importKind)).map((name) => {
|
|
1796
|
+
const ignoredSettings = ignored.get(name);
|
|
1797
|
+
// Keep writes inside adapter-owned storage even though the source is external.
|
|
1798
|
+
return [name, { path: userPath, kind: "import", importKind, ...(ignoredSettings ? { ignoredSettings, ignoredSettingsSource: "opencode" } : {}) }];
|
|
1799
|
+
}));
|
|
1800
|
+
};
|
|
1731
1801
|
if (getConfiguredHostConfigDiscovery(overridePath, cwd) === "on") {
|
|
1732
1802
|
for (const importKind of Object.keys(IMPORT_PATHS)) {
|
|
1733
1803
|
const imported = loadImportedConfig(importKind, cwd, `Failed to inspect imported MCP config from ${importKind}:`);
|
|
1734
1804
|
if (!imported)
|
|
1735
1805
|
continue;
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
provenance.set(name, { path: userPath, kind: "import", importKind });
|
|
1740
|
-
}
|
|
1806
|
+
// Later import kinds win in the same deterministic order as loadDiscoveredHostConfigs.
|
|
1807
|
+
for (const [name, prov] of importProvenance(importKind, imported.value))
|
|
1808
|
+
provenance.set(name, prov);
|
|
1741
1809
|
}
|
|
1742
1810
|
}
|
|
1743
1811
|
for (const source of getConfigSources(overridePath, cwd)) {
|
|
@@ -1745,21 +1813,28 @@ export function getServerProvenance(overridePath, cwd = process.cwd()) {
|
|
|
1745
1813
|
if (!loaded)
|
|
1746
1814
|
continue;
|
|
1747
1815
|
if (loaded.imports?.length) {
|
|
1816
|
+
// Same precedence as expandImports + mergeConfigs: the first import in a file wins, and a later file replaces earlier ones.
|
|
1817
|
+
const imported = new Set();
|
|
1748
1818
|
for (const importKind of loaded.imports) {
|
|
1749
|
-
const
|
|
1750
|
-
if (!
|
|
1819
|
+
const importedConfig = loadImportedConfig(importKind, cwd, `Failed to inspect imported MCP config from ${importKind}:`);
|
|
1820
|
+
if (!importedConfig)
|
|
1751
1821
|
continue;
|
|
1752
|
-
const
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1822
|
+
for (const [name, prov] of importProvenance(importKind, importedConfig.value)) {
|
|
1823
|
+
if (imported.has(name))
|
|
1824
|
+
continue;
|
|
1825
|
+
imported.add(name);
|
|
1826
|
+
provenance.set(name, prov);
|
|
1757
1827
|
}
|
|
1758
1828
|
}
|
|
1759
1829
|
}
|
|
1760
1830
|
for (const name of Object.keys(loaded.mcpServers)) {
|
|
1761
1831
|
// A Pi entry brings its own list; other sources keep the one from the Pi entry below them.
|
|
1762
|
-
|
|
1832
|
+
// A source that defines the server replaces an OpenCode import, so its OpenCode list no longer applies.
|
|
1833
|
+
// (`loaded.mcpServers` holds only the file's own servers, so a plain import keeps its list.)
|
|
1834
|
+
const below = provenance.get(name);
|
|
1835
|
+
const ignoredSettings = loaded.ignoredSettings
|
|
1836
|
+
? loaded.ignoredSettings.get(name)
|
|
1837
|
+
: below?.ignoredSettingsSource === "opencode" ? undefined : below?.ignoredSettings;
|
|
1763
1838
|
provenance.set(name, {
|
|
1764
1839
|
path: source.writePath,
|
|
1765
1840
|
kind: source.kind,
|