pi-mcp-adapter 5.2.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 +38 -0
- package/bearer-command-resolver.ts +8 -4
- package/cli.js +7 -2
- package/config.ts +96 -17
- package/dist/bearer-command-resolver.js +7 -3
- package/dist/bearer-command-resolver.js.map +1 -1
- package/dist/config.js +92 -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/project-server-trust.js +4 -2
- package/dist/project-server-trust.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 +3 -1
- package/docs/prompts-and-ui.md +1 -1
- package/docs/servers.md +1 -1
- package/docs/tools.md +3 -1
- package/index.ts +15 -1
- package/mcp-panel.ts +25 -23
- package/metadata-cache.ts +2 -0
- package/package.json +1 -1
- package/panel-keys.ts +23 -0
- package/pi-signin-import.ts +3 -2
- package/project-server-trust.ts +4 -2
- 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,44 @@ 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
|
+
|
|
28
|
+
## [5.3.0] - 2026-10-12
|
|
29
|
+
|
|
30
|
+
### Highlights
|
|
31
|
+
|
|
32
|
+
- Open the MCP panel with a key of your choice, without losing the prompt you are typing.
|
|
33
|
+
- Pi in RPC mode, as used by hosts such as Paseo, no longer hangs at startup in a project with MCP servers you have not approved yet.
|
|
34
|
+
- The Parallel Search preset now tells Parallel that requests come from pi-mcp-adapter, with no user or install details.
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
|
|
38
|
+
- You can open the MCP panel from a key you choose, so turning a server on or off no longer means clearing the prompt you are typing. Bind `mcp.panel.open` in Pi's `keybindings.json`, for example `{ "mcp.panel.open": "alt+m" }`. There is no default key. Thanks to [@Milor123](https://github.com/Milor123) for [#822](https://github.com/nicobailon/pi-mcp-adapter/issues/822).
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
|
|
42
|
+
- The Parallel Search preset in `/mcp setup` now sends `User-Agent: pi-mcp-adapter`, so Parallel can see traffic from this project. It is a fixed project name with no user or install details. Existing `mcp.json` entries are unchanged. Thanks to [@georgeatparallel](https://github.com/georgeatparallel) for [#821](https://github.com/nicobailon/pi-mcp-adapter/pull/821).
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
|
|
46
|
+
- Pi in RPC mode no longer hangs at startup when the project has an MCP server you have not approved, or when there is a Pi sign-in the adapter has not imported yet. The adapter asked in a dialog, but Pi can't answer dialogs until startup finishes, so hosts such as Paseo saw Pi never respond. RPC sessions now skip unapproved project servers, as the docs describe, and admit them when your user-level `"projectServers"` setting is `"allow"`. Those two dialogs now appear only in the interactive terminal UI. Thanks to [@itssimon](https://github.com/itssimon) for [#825](https://github.com/nicobailon/pi-mcp-adapter/pull/825).
|
|
47
|
+
|
|
10
48
|
## [5.2.0] - 2026-10-09
|
|
11
49
|
|
|
12
50
|
### 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
|
@@ -54,6 +54,7 @@ export const KNOWN_SERVER_PRESETS: readonly KnownServerPreset[] = [
|
|
|
54
54
|
summary: "Search the web and fetch pages without an API key.",
|
|
55
55
|
entry: {
|
|
56
56
|
url: "https://search.parallel.ai/mcp",
|
|
57
|
+
headers: { "User-Agent": "pi-mcp-adapter" },
|
|
57
58
|
protocolVersion: "auto",
|
|
58
59
|
directTools: true,
|
|
59
60
|
},
|
|
@@ -268,6 +269,10 @@ function legacyMcpConfigHasContent(filePath: string): boolean {
|
|
|
268
269
|
}
|
|
269
270
|
|
|
270
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[] {
|
|
271
276
|
if (piMcpConfigEnabled) {
|
|
272
277
|
return getConfigSources(overridePath, cwd).flatMap((source) => {
|
|
273
278
|
const file = isPiMcpSource(source.id) ? readPiMcpConfig(source.readPath) : null;
|
|
@@ -300,6 +305,31 @@ export function getLegacyMcpMigrationNotices(cwd = process.cwd(), overridePath?:
|
|
|
300
305
|
});
|
|
301
306
|
}
|
|
302
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
|
+
|
|
303
333
|
export function getSharedConfigPath(target: SharedConfigTarget, cwd = process.cwd()): string {
|
|
304
334
|
return target === "project" ? getProjectConfigPath(cwd) : getGenericGlobalConfigPath();
|
|
305
335
|
}
|
|
@@ -1046,11 +1076,19 @@ function loadImportedConfig(
|
|
|
1046
1076
|
importKind: ImportKind,
|
|
1047
1077
|
cwd: string,
|
|
1048
1078
|
warningPrefix: string,
|
|
1049
|
-
): {
|
|
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 {
|
|
1050
1087
|
if (importKind === "opencode") {
|
|
1051
1088
|
let merged: Record<string, unknown> = {};
|
|
1052
1089
|
let highestPrecedenceSource: ImportedServerSource | undefined;
|
|
1053
1090
|
const serverSources = new Map<string, ImportedServerSource>();
|
|
1091
|
+
const keySources = new Map<string, Map<string, string>>();
|
|
1054
1092
|
|
|
1055
1093
|
for (const source of resolveImportCandidates(importKind, cwd)) {
|
|
1056
1094
|
const { path } = source;
|
|
@@ -1066,8 +1104,13 @@ function loadImportedConfig(
|
|
|
1066
1104
|
const entries = isRecord(mcp.servers)
|
|
1067
1105
|
? { ...Object.fromEntries(Object.entries(mcp).filter(([name]) => name !== "servers" && name !== "timeout")), ...mcp.servers }
|
|
1068
1106
|
: mcp;
|
|
1069
|
-
const normalized = Object.fromEntries(Object.entries(entries).map(([name,
|
|
1070
|
-
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];
|
|
1071
1114
|
const { client_id, client_secret, auth_server_metadata_url, ...oauth } = entry.oauth;
|
|
1072
1115
|
return [name, {
|
|
1073
1116
|
...entry,
|
|
@@ -1080,7 +1123,13 @@ function loadImportedConfig(
|
|
|
1080
1123
|
}];
|
|
1081
1124
|
}));
|
|
1082
1125
|
merged = mergeOpenCodeConfigs(merged, { ...imported, mcp: normalized });
|
|
1083
|
-
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
|
+
}
|
|
1084
1133
|
highestPrecedenceSource = source;
|
|
1085
1134
|
}
|
|
1086
1135
|
} catch (error) {
|
|
@@ -1098,6 +1147,7 @@ function loadImportedConfig(
|
|
|
1098
1147
|
value: merged,
|
|
1099
1148
|
source: highestPrecedenceSource,
|
|
1100
1149
|
serverSources,
|
|
1150
|
+
keySources,
|
|
1101
1151
|
};
|
|
1102
1152
|
}
|
|
1103
1153
|
|
|
@@ -1384,6 +1434,24 @@ function isStringRecord(value: unknown): value is Record<string, string> {
|
|
|
1384
1434
|
return isRecord(value) && Object.values(value).every((entry) => typeof entry === "string");
|
|
1385
1435
|
}
|
|
1386
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
|
+
|
|
1387
1455
|
function mergeOpenCodeConfigs(base: Record<string, unknown>, next: Record<string, unknown>): Record<string, unknown> {
|
|
1388
1456
|
const baseMcp = base.mcp;
|
|
1389
1457
|
const nextMcp = next.mcp;
|
|
@@ -1924,16 +1992,21 @@ export function writeSharedServerEntry(filePath: string, serverName: string, ent
|
|
|
1924
1992
|
export function getServerProvenance(overridePath?: string, cwd = process.cwd()): Map<string, ServerProvenance> {
|
|
1925
1993
|
const provenance = new Map<string, ServerProvenance>();
|
|
1926
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
|
+
};
|
|
1927
2003
|
|
|
1928
2004
|
if (getConfiguredHostConfigDiscovery(overridePath, cwd) === "on") {
|
|
1929
2005
|
for (const importKind of Object.keys(IMPORT_PATHS) as ImportKind[]) {
|
|
1930
2006
|
const imported = loadImportedConfig(importKind, cwd, `Failed to inspect imported MCP config from ${importKind}:`);
|
|
1931
2007
|
if (!imported) continue;
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
// Later import kinds win in the same deterministic order as loadDiscoveredHostConfigs.
|
|
1935
|
-
provenance.set(name, { path: userPath, kind: "import", importKind });
|
|
1936
|
-
}
|
|
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);
|
|
1937
2010
|
}
|
|
1938
2011
|
}
|
|
1939
2012
|
|
|
@@ -1941,22 +2014,28 @@ export function getServerProvenance(overridePath?: string, cwd = process.cwd()):
|
|
|
1941
2014
|
const loaded = readSourceConfig(source.id, source.readPath);
|
|
1942
2015
|
if (!loaded) continue;
|
|
1943
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>();
|
|
1944
2019
|
for (const importKind of loaded.imports) {
|
|
1945
|
-
const
|
|
1946
|
-
if (!
|
|
2020
|
+
const importedConfig = loadImportedConfig(importKind, cwd, `Failed to inspect imported MCP config from ${importKind}:`);
|
|
2021
|
+
if (!importedConfig) continue;
|
|
1947
2022
|
|
|
1948
|
-
const
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
}
|
|
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);
|
|
1953
2027
|
}
|
|
1954
2028
|
}
|
|
1955
2029
|
}
|
|
1956
2030
|
|
|
1957
2031
|
for (const name of Object.keys(loaded.mcpServers)) {
|
|
1958
2032
|
// A Pi entry brings its own list; other sources keep the one from the Pi entry below them.
|
|
1959
|
-
|
|
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;
|
|
1960
2039
|
provenance.set(name, {
|
|
1961
2040
|
path: source.writePath,
|
|
1962
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":"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"}
|
package/dist/config.js
CHANGED
|
@@ -43,6 +43,7 @@ export const KNOWN_SERVER_PRESETS = [
|
|
|
43
43
|
summary: "Search the web and fetch pages without an API key.",
|
|
44
44
|
entry: {
|
|
45
45
|
url: "https://search.parallel.ai/mcp",
|
|
46
|
+
headers: { "User-Agent": "pi-mcp-adapter" },
|
|
46
47
|
protocolVersion: "auto",
|
|
47
48
|
directTools: true,
|
|
48
49
|
},
|
|
@@ -161,6 +162,9 @@ function legacyMcpConfigHasContent(filePath) {
|
|
|
161
162
|
}
|
|
162
163
|
}
|
|
163
164
|
export function getLegacyMcpMigrationNotices(cwd = process.cwd(), overridePath) {
|
|
165
|
+
return [...getPiMcpConfigNotices(cwd, overridePath), ...getOpenCodeImportNotices(cwd, overridePath)];
|
|
166
|
+
}
|
|
167
|
+
function getPiMcpConfigNotices(cwd, overridePath) {
|
|
164
168
|
if (piMcpConfigEnabled) {
|
|
165
169
|
return getConfigSources(overridePath, cwd).flatMap((source) => {
|
|
166
170
|
const file = isPiMcpSource(source.id) ? readPiMcpConfig(source.readPath) : null;
|
|
@@ -195,6 +199,32 @@ export function getLegacyMcpMigrationNotices(cwd = process.cwd(), overridePath)
|
|
|
195
199
|
return [`pi-mcp-adapter no longer reads ${source}. ${fix}`];
|
|
196
200
|
});
|
|
197
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
|
+
}
|
|
198
228
|
export function getSharedConfigPath(target, cwd = process.cwd()) {
|
|
199
229
|
return target === "project" ? getProjectConfigPath(cwd) : getGenericGlobalConfigPath();
|
|
200
230
|
}
|
|
@@ -885,6 +915,7 @@ function loadImportedConfig(importKind, cwd, warningPrefix) {
|
|
|
885
915
|
let merged = {};
|
|
886
916
|
let highestPrecedenceSource;
|
|
887
917
|
const serverSources = new Map();
|
|
918
|
+
const keySources = new Map();
|
|
888
919
|
for (const source of resolveImportCandidates(importKind, cwd)) {
|
|
889
920
|
const { path } = source;
|
|
890
921
|
if (!existsSync(path))
|
|
@@ -899,8 +930,14 @@ function loadImportedConfig(importKind, cwd, warningPrefix) {
|
|
|
899
930
|
const entries = isRecord(mcp.servers)
|
|
900
931
|
? { ...Object.fromEntries(Object.entries(mcp).filter(([name]) => name !== "servers" && name !== "timeout")), ...mcp.servers }
|
|
901
932
|
: mcp;
|
|
902
|
-
const normalized = Object.fromEntries(Object.entries(entries).map(([name,
|
|
903
|
-
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))
|
|
904
941
|
return [name, entry];
|
|
905
942
|
const { client_id, client_secret, auth_server_metadata_url, ...oauth } = entry.oauth;
|
|
906
943
|
return [name, {
|
|
@@ -914,8 +951,15 @@ function loadImportedConfig(importKind, cwd, warningPrefix) {
|
|
|
914
951
|
}];
|
|
915
952
|
}));
|
|
916
953
|
merged = mergeOpenCodeConfigs(merged, { ...imported, mcp: normalized });
|
|
917
|
-
for (const name of Object.
|
|
954
|
+
for (const [name, entry] of Object.entries(normalized)) {
|
|
918
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
|
+
}
|
|
919
963
|
highestPrecedenceSource = source;
|
|
920
964
|
}
|
|
921
965
|
}
|
|
@@ -935,6 +979,7 @@ function loadImportedConfig(importKind, cwd, warningPrefix) {
|
|
|
935
979
|
value: merged,
|
|
936
980
|
source: highestPrecedenceSource,
|
|
937
981
|
serverSources,
|
|
982
|
+
keySources,
|
|
938
983
|
};
|
|
939
984
|
}
|
|
940
985
|
for (const source of resolveImportCandidates(importKind, cwd)) {
|
|
@@ -1230,6 +1275,24 @@ function isRecord(value) {
|
|
|
1230
1275
|
function isStringRecord(value) {
|
|
1231
1276
|
return isRecord(value) && Object.values(value).every((entry) => typeof entry === "string");
|
|
1232
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
|
+
}
|
|
1233
1296
|
function mergeOpenCodeConfigs(base, next) {
|
|
1234
1297
|
const baseMcp = base.mcp;
|
|
1235
1298
|
const nextMcp = next.mcp;
|
|
@@ -1727,16 +1790,22 @@ export function writeSharedServerEntry(filePath, serverName, entry) {
|
|
|
1727
1790
|
export function getServerProvenance(overridePath, cwd = process.cwd()) {
|
|
1728
1791
|
const provenance = new Map();
|
|
1729
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
|
+
};
|
|
1730
1801
|
if (getConfiguredHostConfigDiscovery(overridePath, cwd) === "on") {
|
|
1731
1802
|
for (const importKind of Object.keys(IMPORT_PATHS)) {
|
|
1732
1803
|
const imported = loadImportedConfig(importKind, cwd, `Failed to inspect imported MCP config from ${importKind}:`);
|
|
1733
1804
|
if (!imported)
|
|
1734
1805
|
continue;
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
provenance.set(name, { path: userPath, kind: "import", importKind });
|
|
1739
|
-
}
|
|
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);
|
|
1740
1809
|
}
|
|
1741
1810
|
}
|
|
1742
1811
|
for (const source of getConfigSources(overridePath, cwd)) {
|
|
@@ -1744,21 +1813,28 @@ export function getServerProvenance(overridePath, cwd = process.cwd()) {
|
|
|
1744
1813
|
if (!loaded)
|
|
1745
1814
|
continue;
|
|
1746
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();
|
|
1747
1818
|
for (const importKind of loaded.imports) {
|
|
1748
|
-
const
|
|
1749
|
-
if (!
|
|
1819
|
+
const importedConfig = loadImportedConfig(importKind, cwd, `Failed to inspect imported MCP config from ${importKind}:`);
|
|
1820
|
+
if (!importedConfig)
|
|
1750
1821
|
continue;
|
|
1751
|
-
const
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
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);
|
|
1756
1827
|
}
|
|
1757
1828
|
}
|
|
1758
1829
|
}
|
|
1759
1830
|
for (const name of Object.keys(loaded.mcpServers)) {
|
|
1760
1831
|
// A Pi entry brings its own list; other sources keep the one from the Pi entry below them.
|
|
1761
|
-
|
|
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;
|
|
1762
1838
|
provenance.set(name, {
|
|
1763
1839
|
path: source.writePath,
|
|
1764
1840
|
kind: source.kind,
|