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/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [5.3.2] - 2026-10-17
|
|
11
|
+
|
|
12
|
+
### Highlights
|
|
13
|
+
|
|
14
|
+
- The GitHub preset in `/mcp-adapter setup` now signs in with your GitHub CLI login, so connecting to GitHub works.
|
|
15
|
+
- Saving direct tools in the MCP panel no longer brings back the "Allow project MCP server?" prompt for a server you already allowed.
|
|
16
|
+
- The MCP panel shows a connected server's tools and your saved direct tools, even for servers like GitHub that don't let their tool list be cached.
|
|
17
|
+
- MCP sampling uses your Pi thinking level, so servers that sample through a reasoning-only model stop failing.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- The GitHub preset in `/mcp-adapter setup` now signs in with your GitHub CLI login (`gh auth token`). Before, it used MCP OAuth, which GitHub doesn't offer to apps it hasn't registered, so `/mcp-auth github` always failed with "does not support dynamic client registration". Install `gh` and run `gh auth login` first. If you added the old preset, remove its `github` entry and add GitHub again from `/mcp-adapter setup`. Thanks to [@Milor123](https://github.com/Milor123) for [#841](https://github.com/nicobailon/pi-mcp-adapter/issues/841).
|
|
22
|
+
- When one config file gives a server a different `url` than another file that set its headers or token, the adapter still leaves those credentials out so they aren't sent to the new address, but it now tells you at startup which server, which settings and which two files are involved. Before, the credentials disappeared silently and the server failed with sign-in errors. Thanks to [@Milor123](https://github.com/Milor123) for [#841](https://github.com/nicobailon/pi-mcp-adapter/issues/841).
|
|
23
|
+
- The MCP panel now lists the tools of a server you already connected to in this session, with your saved direct tools checked, even when the server doesn't let its tool list be cached, as with GitHub. Before, such a server showed "(not cached)" with no tools every time you opened the panel, so direct tools you had saved looked lost. Thanks to [@Milor123](https://github.com/Milor123) for [#841](https://github.com/nicobailon/pi-mcp-adapter/issues/841).
|
|
24
|
+
- Saving direct tools in the MCP panel for a project server you already allowed no longer makes Pi ask "Allow project MCP server?" again on the next start or `/reload`. Saving used to change the server's config in a way that cancelled your approval. A server you didn't allow still asks, and so does any other change to the server's config. Thanks to [@Milor123](https://github.com/Milor123) for [#841](https://github.com/nicobailon/pi-mcp-adapter/issues/841).
|
|
25
|
+
- Approving a project MCP server now sticks when Pi is started from the same folder spelled with different capitalization, such as `C:\Users\Me` and `c:\users\me` on Windows. Before, each spelling counted as a separate project and asked "Allow project MCP server?" again. An approval saved under a differently capitalized spelling asks once more after this update. Thanks to [@Milor123](https://github.com/Milor123) for [#841](https://github.com/nicobailon/pi-mcp-adapter/issues/841).
|
|
26
|
+
- MCP sampling requests now use your current Pi thinking level, so a server that samples through a model that requires reasoning no longer fails with "Reasoning is mandatory for this endpoint and cannot be disabled". Before, sampling always ran with thinking off. On Pi 0.84 and 0.85 sampling still runs with thinking off. Thanks to [@bardic](https://github.com/bardic) for [#846](https://github.com/nicobailon/pi-mcp-adapter/issues/846).
|
|
27
|
+
|
|
28
|
+
## [5.3.1] - 2026-10-15
|
|
29
|
+
|
|
30
|
+
### Highlights
|
|
31
|
+
|
|
32
|
+
- On Windows, `!command` secrets now run with Git Bash when it is installed, so commands like `!$HOME/bin/get-token` work.
|
|
33
|
+
- `"openUi": false` really runs a server's tools without UI, instead of leaving them waiting on a window that never opens.
|
|
34
|
+
- `describe` no longer hides tool input details such as descriptions, limits and integer types behind a short summary.
|
|
35
|
+
- The MCP panel keeps your saved direct tools selected after a ctrl+r reconnect.
|
|
36
|
+
- OpenCode imports accept `env` for server variables and tell you about settings OpenCode ignores.
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
|
|
40
|
+
- `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).
|
|
41
|
+
- 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).
|
|
42
|
+
- `"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).
|
|
43
|
+
- 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).
|
|
44
|
+
- 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).
|
|
45
|
+
|
|
10
46
|
## [5.3.0] - 2026-10-12
|
|
11
47
|
|
|
12
48
|
### Highlights
|
|
@@ -118,6 +154,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
118
154
|
- `/mcp-adapter setup` adds Figma (desktop) to the global config instead of writing `.mcp.json` into whatever project you opened setup from.
|
|
119
155
|
- `/mcp-adapter setup` warns when a server you add won't be used, because another config file already defines or disables it, or the current config mode doesn't read the file it was written to.
|
|
120
156
|
- A bearer token or header value that isn't a valid HTTP header value, such as one containing a newline, no longer appears in connection errors. This covers `bearerToken` (including `!command` and `${VAR}` forms), `bearerTokenEnv`, and configured `headers`; the error now names the server and header instead.
|
|
157
|
+
|
|
121
158
|
## [4.0.0] - 2026-09-30
|
|
122
159
|
|
|
123
160
|
### 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/commands.ts
CHANGED
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
} from "./config.ts";
|
|
27
27
|
import { markKeepAliveAfterConnect, notifyToolMetadataUpdated, updateMetadataCache, updateStatusBar, getFailureAgeSeconds, getFailureMessage, clearFailure, recordFailure } from "./init.ts";
|
|
28
28
|
import { isServerInActiveFailureBackoff } from "./failure-backoff.ts";
|
|
29
|
-
import { loadMetadataCache, reconstructPromptMetadata } from "./metadata-cache.ts";
|
|
29
|
+
import { loadMetadataCache, reconstructPromptMetadata, withSessionMetadata } from "./metadata-cache.ts";
|
|
30
30
|
import { buildToolMetadata } from "./tool-metadata.ts";
|
|
31
31
|
import { supportsOAuth, authenticate, removeAuth, type McpOAuthRuntime } from "./mcp-auth-flow.ts";
|
|
32
32
|
import { getAuthStorageOptions, inspectAuthForUrl } from "./mcp-auth.ts";
|
|
@@ -36,7 +36,7 @@ import { findPiSignInImports, importPiSignIn } from "./pi-signin-import.ts";
|
|
|
36
36
|
import { formatTerminalError, openPath, providerSignInMessage, resolveServerUrl, sanitizeTerminalText } from "./utils.ts";
|
|
37
37
|
import { isAbortError } from "./runtime-owner.ts";
|
|
38
38
|
import { resolveJevCredential } from "./jev-key-store.ts";
|
|
39
|
-
import { describeProjectServerBlock } from "./project-server-trust.ts";
|
|
39
|
+
import { describeProjectServerBlock, keepProjectServerApprovals } from "./project-server-trust.ts";
|
|
40
40
|
|
|
41
41
|
function terminalHyperlink(label: string, url: string): string {
|
|
42
42
|
return `\u001B]8;;${sanitizeTerminalText(url)}\u001B\\${sanitizeTerminalText(label)}\u001B]8;;\u001B\\`;
|
|
@@ -792,7 +792,7 @@ export async function openMcpPanel(
|
|
|
792
792
|
}
|
|
793
793
|
|
|
794
794
|
const config = state.config;
|
|
795
|
-
const cache = loadMetadataCache();
|
|
795
|
+
const cache = withSessionMetadata(loadMetadataCache(), config, state.sessionMetadata);
|
|
796
796
|
const configPath = pi.getFlag("mcp-config") as string | undefined ?? configOverridePath;
|
|
797
797
|
const provenanceMap = getServerProvenance(configPath, ctx.cwd);
|
|
798
798
|
const { lines: noticeLines, fingerprint } = buildSharedConfigNoticeLines(configPath, ctx.cwd);
|
|
@@ -838,9 +838,11 @@ export async function openMcpPanel(
|
|
|
838
838
|
}
|
|
839
839
|
}
|
|
840
840
|
if (!result.cancelled && result.changes.size > 0) {
|
|
841
|
+
const before = loadMcpConfig(configPath, ctx.cwd);
|
|
841
842
|
writeDirectToolsConfig(result.changes, provenanceMap, config, () => {
|
|
842
843
|
directToolsFileWritten = true;
|
|
843
844
|
});
|
|
845
|
+
keepProjectServerApprovals(ctx, before, loadMcpConfig(configPath, ctx.cwd), result.changes.keys());
|
|
844
846
|
directToolsSaved = true;
|
|
845
847
|
await onDirectToolsConfigChanged?.(result.changes);
|
|
846
848
|
ctx.ui.notify("Direct tools updated for this session.", "info");
|
package/config.ts
CHANGED
|
@@ -79,8 +79,10 @@ export const KNOWN_SERVER_PRESETS: readonly KnownServerPreset[] = [
|
|
|
79
79
|
{
|
|
80
80
|
id: "github",
|
|
81
81
|
name: "GitHub",
|
|
82
|
-
summary: "Work with GitHub
|
|
83
|
-
|
|
82
|
+
summary: "Work with GitHub using your GitHub CLI sign-in. Needs `gh` installed and `gh auth login` done.",
|
|
83
|
+
// GitHub's OAuth server has no dynamic client registration, so MCP OAuth can't sign in;
|
|
84
|
+
// the GitHub CLI token works and is read at connect time, never written to config.
|
|
85
|
+
entry: { url: "https://api.githubcopilot.com/mcp/", auth: "bearer", bearerToken: "!gh auth token", protocolVersion: "auto" },
|
|
84
86
|
},
|
|
85
87
|
{
|
|
86
88
|
id: "chrome-devtools",
|
|
@@ -269,6 +271,91 @@ function legacyMcpConfigHasContent(filePath: string): boolean {
|
|
|
269
271
|
}
|
|
270
272
|
|
|
271
273
|
export function getLegacyMcpMigrationNotices(cwd = process.cwd(), overridePath?: string): string[] {
|
|
274
|
+
return [
|
|
275
|
+
...getPiMcpConfigNotices(cwd, overridePath),
|
|
276
|
+
...getOpenCodeImportNotices(cwd, overridePath),
|
|
277
|
+
...getDroppedCredentialNotices(cwd, overridePath),
|
|
278
|
+
];
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** server name -> credential field -> the file that supplied it. */
|
|
282
|
+
type CredentialFiles = Map<string, Map<string, string>>;
|
|
283
|
+
|
|
284
|
+
function credentialFiles(servers: Record<string, ServerEntry>, fileOf: (name: string, field: string) => string): CredentialFiles {
|
|
285
|
+
return new Map(Object.entries(servers).map(([name, definition]) => [
|
|
286
|
+
name,
|
|
287
|
+
new Map(CREDENTIAL_FIELDS.filter((field) => definition[field] !== undefined).map((field) => [field, fileOf(name, field)])),
|
|
288
|
+
]));
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Replays the server merges of loadMcpConfigWithSources with the file behind each credential field,
|
|
293
|
+
* and reports the fields mergeServerMaps drops because a later file points the server at another url.
|
|
294
|
+
*/
|
|
295
|
+
function getDroppedCredentialNotices(cwd: string, overridePath?: string): string[] {
|
|
296
|
+
const notices: string[] = [];
|
|
297
|
+
const merge = (
|
|
298
|
+
base: Record<string, ServerEntry>,
|
|
299
|
+
baseFiles: CredentialFiles,
|
|
300
|
+
next: Record<string, ServerEntry>,
|
|
301
|
+
nextFiles: CredentialFiles,
|
|
302
|
+
urlFile: (name: string) => string,
|
|
303
|
+
): [Record<string, ServerEntry>, CredentialFiles] => {
|
|
304
|
+
const files: CredentialFiles = new Map(baseFiles);
|
|
305
|
+
for (const [name, definition] of Object.entries(next)) {
|
|
306
|
+
const existing = base[name];
|
|
307
|
+
const fieldFiles = new Map(files.get(name));
|
|
308
|
+
const byFile = new Map<string, string[]>();
|
|
309
|
+
for (const field of existing ? droppedUrlBoundAuth(existing, definition) : []) {
|
|
310
|
+
const from = fieldFiles.get(field);
|
|
311
|
+
if (from !== undefined) byFile.set(from, [...byFile.get(from) ?? [], field]);
|
|
312
|
+
fieldFiles.delete(field);
|
|
313
|
+
}
|
|
314
|
+
for (const [from, fields] of byFile) {
|
|
315
|
+
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.`);
|
|
316
|
+
}
|
|
317
|
+
for (const [field, path] of nextFiles.get(name) ?? []) fieldFiles.set(field, path);
|
|
318
|
+
files.set(name, fieldFiles);
|
|
319
|
+
}
|
|
320
|
+
return [mergeServerMaps(base, next), files];
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
const host = !isExclusiveConfigMode() && getConfiguredHostConfigDiscovery(overridePath, cwd) === "on"
|
|
324
|
+
? loadDiscoveredHostConfigs(cwd)
|
|
325
|
+
: { config: { mcpServers: {} }, serverSources: new Map<string, ImportedServerSource>() };
|
|
326
|
+
const hostFile = (name: string) => host.serverSources.get(name)?.path ?? "a host config";
|
|
327
|
+
let servers = host.config.mcpServers;
|
|
328
|
+
let files = credentialFiles(servers, hostFile);
|
|
329
|
+
const sourceSpecs = getConfigSources(overridePath, cwd);
|
|
330
|
+
const piProjectSource = sourceSpecs.find((source) => source.id === "pi-mcp-project");
|
|
331
|
+
const piProjectNames = Object.keys((piProjectSource && readPiMcpConfig(piProjectSource.readPath))?.mcpServers ?? {});
|
|
332
|
+
for (const source of sourceSpecs) {
|
|
333
|
+
const loaded = readSourceConfig(source.id, source.readPath);
|
|
334
|
+
if (!loaded) continue;
|
|
335
|
+
if (source.id === "pi-mcp-global") {
|
|
336
|
+
for (const name of piProjectNames) delete loaded.mcpServers[name];
|
|
337
|
+
}
|
|
338
|
+
// The same two merges as expandImports and mergeConfigs: the file's own entries over its imports,
|
|
339
|
+
// then the file over the files before it.
|
|
340
|
+
const imported = expandImports({ ...loaded, mcpServers: {} }, cwd);
|
|
341
|
+
// An OpenCode project entry can inherit a credential from the global file, so name the file per key.
|
|
342
|
+
const importFile = (name: string, field: string) =>
|
|
343
|
+
imported.keySources?.get(name)?.get(field) ?? imported.serverSources.get(name)?.path ?? source.readPath;
|
|
344
|
+
const ownFile = () => source.readPath;
|
|
345
|
+
const [fileServers, fileFiles] = merge(
|
|
346
|
+
imported.config.mcpServers,
|
|
347
|
+
credentialFiles(imported.config.mcpServers, importFile),
|
|
348
|
+
loaded.mcpServers,
|
|
349
|
+
credentialFiles(loaded.mcpServers, ownFile),
|
|
350
|
+
ownFile,
|
|
351
|
+
);
|
|
352
|
+
const urlFile = (name: string) => typeof loaded.mcpServers[name]?.url === "string" ? source.readPath : importFile(name, "url");
|
|
353
|
+
[servers, files] = merge(servers, files, fileServers, fileFiles, urlFile);
|
|
354
|
+
}
|
|
355
|
+
return notices;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function getPiMcpConfigNotices(cwd: string, overridePath?: string): string[] {
|
|
272
359
|
if (piMcpConfigEnabled) {
|
|
273
360
|
return getConfigSources(overridePath, cwd).flatMap((source) => {
|
|
274
361
|
const file = isPiMcpSource(source.id) ? readPiMcpConfig(source.readPath) : null;
|
|
@@ -301,6 +388,31 @@ export function getLegacyMcpMigrationNotices(cwd = process.cwd(), overridePath?:
|
|
|
301
388
|
});
|
|
302
389
|
}
|
|
303
390
|
|
|
391
|
+
/** Settings OpenCode drops from an active `opencode` import, grouped by the file that defines each server. */
|
|
392
|
+
function getOpenCodeImportNotices(cwd: string, overridePath?: string): string[] {
|
|
393
|
+
const active = (!isExclusiveConfigMode() && getConfiguredHostConfigDiscovery(overridePath, cwd) === "on")
|
|
394
|
+
|| getConfigSources(overridePath, cwd).some((source) => readSourceConfig(source.id, source.readPath)?.imports?.includes("opencode"));
|
|
395
|
+
const imported = active ? loadImportedConfig("opencode", cwd, "Failed to inspect imported MCP config from opencode:") : null;
|
|
396
|
+
if (!imported) return [];
|
|
397
|
+
// Warn only where the OpenCode import is still the server's definition, matching /mcp-adapter.
|
|
398
|
+
const provenance = getServerProvenance(overridePath, cwd);
|
|
399
|
+
// Name the file each key came from: a project entry can inherit a stray key from the global file.
|
|
400
|
+
const byFile = new Map<string, Map<string, string[]>>();
|
|
401
|
+
for (const [name, keys] of getOpenCodeIgnoredSettings(imported.value)) {
|
|
402
|
+
if (provenance.get(name)?.ignoredSettingsSource !== "opencode") continue;
|
|
403
|
+
for (const key of keys) {
|
|
404
|
+
const path = imported.keySources?.get(name)?.get(key) ?? imported.path;
|
|
405
|
+
const servers = byFile.get(path) ?? new Map<string, string[]>();
|
|
406
|
+
servers.set(name, [...servers.get(name) ?? [], key]);
|
|
407
|
+
byFile.set(path, servers);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return [...byFile].map(([path, servers]) => {
|
|
411
|
+
const ignored = [...servers].map(([name, settings]) => `"${name}": ${settings.join(", ")}`);
|
|
412
|
+
return `${path}: OpenCode ignores these settings, so the import does too (details in /mcp-adapter): ${ignored.join("; ")}.`;
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
|
|
304
416
|
export function getSharedConfigPath(target: SharedConfigTarget, cwd = process.cwd()): string {
|
|
305
417
|
return target === "project" ? getProjectConfigPath(cwd) : getGenericGlobalConfigPath();
|
|
306
418
|
}
|
|
@@ -447,6 +559,8 @@ interface ImportedServerSource {
|
|
|
447
559
|
interface LoadedHostConfig {
|
|
448
560
|
config: McpConfig;
|
|
449
561
|
serverSources: Map<string, ImportedServerSource>;
|
|
562
|
+
/** OpenCode imports only: the file that last set each key of each server. */
|
|
563
|
+
keySources?: Map<string, Map<string, string>>;
|
|
450
564
|
}
|
|
451
565
|
|
|
452
566
|
export const MCP_CONFIG_SOURCE_METADATA = Symbol.for("pi-mcp-adapter/config-source-metadata");
|
|
@@ -910,6 +1024,18 @@ function mergeConfigs(base: McpConfig, next: McpConfig): McpConfig {
|
|
|
910
1024
|
// otherwise the original endpoint's credentials would be shipped to the new
|
|
911
1025
|
// url. See the SECURITY note in mergeServerMaps.
|
|
912
1026
|
const URL_BOUND_AUTH_FIELDS = ["headers", "bearerToken", "bearerTokenEnv", "bearerTokenStore", "requestHeadersCommand", "caFile"] as const;
|
|
1027
|
+
const CREDENTIAL_FIELDS = [...URL_BOUND_AUTH_FIELDS, "auth", "oauth"] as const;
|
|
1028
|
+
|
|
1029
|
+
/** The credential fields of `existing` that mergeServerMaps drops for `definition`'s different url. */
|
|
1030
|
+
function droppedUrlBoundAuth(existing: ServerEntry, definition: ServerEntry): (typeof CREDENTIAL_FIELDS)[number][] {
|
|
1031
|
+
if (typeof definition.url !== "string" || definition.url === existing.url) return [];
|
|
1032
|
+
return CREDENTIAL_FIELDS.filter((field) => {
|
|
1033
|
+
if (Object.hasOwn(definition, field)) return false;
|
|
1034
|
+
if (field === "auth") return typeof existing.auth === "object";
|
|
1035
|
+
if (field === "oauth") return existing.oauth !== undefined && existing.oauth !== false;
|
|
1036
|
+
return existing[field] !== undefined;
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
913
1039
|
|
|
914
1040
|
function mergeServerMaps(
|
|
915
1041
|
base: Record<string, ServerEntry>,
|
|
@@ -956,14 +1082,11 @@ function mergeServerMaps(
|
|
|
956
1082
|
}
|
|
957
1083
|
if (existing && typeof definition.url === "string" && definition.url !== existing.url) {
|
|
958
1084
|
if (baseEntry === existing) baseEntry = { ...existing };
|
|
959
|
-
|
|
1085
|
+
// Provider tokens (object `auth`) and OAuth config are bound to the url too; the startup
|
|
1086
|
+
// notice reports exactly these fields, so both read the one rule.
|
|
1087
|
+
for (const field of droppedUrlBoundAuth(existing, definition)) {
|
|
960
1088
|
delete baseEntry[field];
|
|
961
1089
|
}
|
|
962
|
-
// A provider token is bound to the url that asked for it, like the fields above.
|
|
963
|
-
if (typeof baseEntry.auth === "object") delete baseEntry.auth;
|
|
964
|
-
if (baseEntry.oauth !== false) {
|
|
965
|
-
delete baseEntry.oauth;
|
|
966
|
-
}
|
|
967
1090
|
}
|
|
968
1091
|
if (existing && Object.hasOwn(definition, "env") && isBuiltInAgentPlugin(existing, "env") && !Object.hasOwn(definition, "literalEnv")) {
|
|
969
1092
|
if (baseEntry === existing) baseEntry = { ...existing };
|
|
@@ -985,6 +1108,7 @@ function expandImports(config: McpConfig, cwd = process.cwd()): LoadedHostConfig
|
|
|
985
1108
|
|
|
986
1109
|
const importedServers: Record<string, ServerEntry> = {};
|
|
987
1110
|
const serverSources = new Map<string, ImportedServerSource>();
|
|
1111
|
+
const keySources = new Map<string, Map<string, string>>();
|
|
988
1112
|
for (const importKind of config.imports) {
|
|
989
1113
|
const imported = loadImportedConfig(importKind, cwd, `Failed to import MCP config from ${importKind}:`);
|
|
990
1114
|
if (!imported) continue;
|
|
@@ -994,6 +1118,8 @@ function expandImports(config: McpConfig, cwd = process.cwd()): LoadedHostConfig
|
|
|
994
1118
|
if (!importedServers[name]) {
|
|
995
1119
|
importedServers[name] = definition;
|
|
996
1120
|
serverSources.set(name, imported.serverSources.get(name) ?? imported.source);
|
|
1121
|
+
const keys = imported.keySources?.get(name);
|
|
1122
|
+
if (keys) keySources.set(name, keys);
|
|
997
1123
|
}
|
|
998
1124
|
}
|
|
999
1125
|
}
|
|
@@ -1006,6 +1132,7 @@ function expandImports(config: McpConfig, cwd = process.cwd()): LoadedHostConfig
|
|
|
1006
1132
|
mcpServers: mergeServerMaps(importedServers, config.mcpServers),
|
|
1007
1133
|
},
|
|
1008
1134
|
serverSources,
|
|
1135
|
+
keySources,
|
|
1009
1136
|
};
|
|
1010
1137
|
}
|
|
1011
1138
|
|
|
@@ -1047,11 +1174,19 @@ function loadImportedConfig(
|
|
|
1047
1174
|
importKind: ImportKind,
|
|
1048
1175
|
cwd: string,
|
|
1049
1176
|
warningPrefix: string,
|
|
1050
|
-
): {
|
|
1177
|
+
): {
|
|
1178
|
+
path: string;
|
|
1179
|
+
value: unknown;
|
|
1180
|
+
source: ImportedServerSource;
|
|
1181
|
+
serverSources: Map<string, ImportedServerSource>;
|
|
1182
|
+
/** OpenCode only: the file that last set each key of each server, in merge order. */
|
|
1183
|
+
keySources?: Map<string, Map<string, string>>;
|
|
1184
|
+
} | null {
|
|
1051
1185
|
if (importKind === "opencode") {
|
|
1052
1186
|
let merged: Record<string, unknown> = {};
|
|
1053
1187
|
let highestPrecedenceSource: ImportedServerSource | undefined;
|
|
1054
1188
|
const serverSources = new Map<string, ImportedServerSource>();
|
|
1189
|
+
const keySources = new Map<string, Map<string, string>>();
|
|
1055
1190
|
|
|
1056
1191
|
for (const source of resolveImportCandidates(importKind, cwd)) {
|
|
1057
1192
|
const { path } = source;
|
|
@@ -1067,8 +1202,13 @@ function loadImportedConfig(
|
|
|
1067
1202
|
const entries = isRecord(mcp.servers)
|
|
1068
1203
|
? { ...Object.fromEntries(Object.entries(mcp).filter(([name]) => name !== "servers" && name !== "timeout")), ...mcp.servers }
|
|
1069
1204
|
: mcp;
|
|
1070
|
-
const normalized = Object.fromEntries(Object.entries(entries).map(([name,
|
|
1071
|
-
if (!isRecord(
|
|
1205
|
+
const normalized = Object.fromEntries(Object.entries(entries).map(([name, raw]) => {
|
|
1206
|
+
if (!isRecord(raw)) return [name, raw];
|
|
1207
|
+
// OpenCode reads only `environment`; the import also accepts the common `env` spelling.
|
|
1208
|
+
// `environment` wins when both are set, since that is what OpenCode runs.
|
|
1209
|
+
const { env, ...rest } = raw;
|
|
1210
|
+
const entry = env !== undefined && rest.environment === undefined ? { ...rest, environment: env } : rest;
|
|
1211
|
+
if (!isRecord(entry.oauth)) return [name, entry];
|
|
1072
1212
|
const { client_id, client_secret, auth_server_metadata_url, ...oauth } = entry.oauth;
|
|
1073
1213
|
return [name, {
|
|
1074
1214
|
...entry,
|
|
@@ -1081,7 +1221,13 @@ function loadImportedConfig(
|
|
|
1081
1221
|
}];
|
|
1082
1222
|
}));
|
|
1083
1223
|
merged = mergeOpenCodeConfigs(merged, { ...imported, mcp: normalized });
|
|
1084
|
-
for (const name of Object.
|
|
1224
|
+
for (const [name, entry] of Object.entries(normalized)) {
|
|
1225
|
+
serverSources.set(name, source);
|
|
1226
|
+
if (!isRecord(entry)) continue;
|
|
1227
|
+
const keys = keySources.get(name) ?? new Map<string, string>();
|
|
1228
|
+
for (const key of Object.keys(entry)) keys.set(key, path);
|
|
1229
|
+
keySources.set(name, keys);
|
|
1230
|
+
}
|
|
1085
1231
|
highestPrecedenceSource = source;
|
|
1086
1232
|
}
|
|
1087
1233
|
} catch (error) {
|
|
@@ -1099,6 +1245,7 @@ function loadImportedConfig(
|
|
|
1099
1245
|
value: merged,
|
|
1100
1246
|
source: highestPrecedenceSource,
|
|
1101
1247
|
serverSources,
|
|
1248
|
+
keySources,
|
|
1102
1249
|
};
|
|
1103
1250
|
}
|
|
1104
1251
|
|
|
@@ -1385,6 +1532,24 @@ function isStringRecord(value: unknown): value is Record<string, string> {
|
|
|
1385
1532
|
return isRecord(value) && Object.values(value).every((entry) => typeof entry === "string");
|
|
1386
1533
|
}
|
|
1387
1534
|
|
|
1535
|
+
// OpenCode's schema allows only these keys on a server (V1 and V2 forms) and silently drops the rest.
|
|
1536
|
+
const OPENCODE_LOCAL_SERVER_KEYS = new Set(["type", "command", "cwd", "environment", "enabled", "disabled", "codemode", "timeout", "protocol"]);
|
|
1537
|
+
const OPENCODE_REMOTE_SERVER_KEYS = new Set(["type", "url", "headers", "oauth", "enabled", "disabled", "codemode", "timeout", "protocol"]);
|
|
1538
|
+
|
|
1539
|
+
/** Keys of imported OpenCode servers that OpenCode ignores, so the import ignores them too. */
|
|
1540
|
+
function getOpenCodeIgnoredSettings(merged: unknown): Map<string, string[]> {
|
|
1541
|
+
const ignored = new Map<string, string[]>();
|
|
1542
|
+
const entries = isRecord(merged) && isRecord(merged.mcp) ? merged.mcp : {};
|
|
1543
|
+
for (const name of Object.keys(extractServers(merged, "opencode"))) {
|
|
1544
|
+
const entry = entries[name];
|
|
1545
|
+
if (!isRecord(entry) || (entry.type !== "local" && entry.type !== "remote")) continue;
|
|
1546
|
+
const allowed = entry.type === "local" ? OPENCODE_LOCAL_SERVER_KEYS : OPENCODE_REMOTE_SERVER_KEYS;
|
|
1547
|
+
const keys = Object.keys(entry).filter((key) => !allowed.has(key));
|
|
1548
|
+
if (keys.length > 0) ignored.set(name, keys);
|
|
1549
|
+
}
|
|
1550
|
+
return ignored;
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1388
1553
|
function mergeOpenCodeConfigs(base: Record<string, unknown>, next: Record<string, unknown>): Record<string, unknown> {
|
|
1389
1554
|
const baseMcp = base.mcp;
|
|
1390
1555
|
const nextMcp = next.mcp;
|
|
@@ -1925,16 +2090,21 @@ export function writeSharedServerEntry(filePath: string, serverName: string, ent
|
|
|
1925
2090
|
export function getServerProvenance(overridePath?: string, cwd = process.cwd()): Map<string, ServerProvenance> {
|
|
1926
2091
|
const provenance = new Map<string, ServerProvenance>();
|
|
1927
2092
|
const userPath = getPiGlobalConfigPath(overridePath);
|
|
2093
|
+
const importProvenance = (importKind: ImportKind, value: unknown): Map<string, ServerProvenance> => {
|
|
2094
|
+
const ignored = importKind === "opencode" ? getOpenCodeIgnoredSettings(value) : new Map<string, string[]>();
|
|
2095
|
+
return new Map(Object.keys(extractServers(value, importKind)).map((name): [string, ServerProvenance] => {
|
|
2096
|
+
const ignoredSettings = ignored.get(name);
|
|
2097
|
+
// Keep writes inside adapter-owned storage even though the source is external.
|
|
2098
|
+
return [name, { path: userPath, kind: "import", importKind, ...(ignoredSettings ? { ignoredSettings, ignoredSettingsSource: "opencode" } : {}) }];
|
|
2099
|
+
}));
|
|
2100
|
+
};
|
|
1928
2101
|
|
|
1929
2102
|
if (getConfiguredHostConfigDiscovery(overridePath, cwd) === "on") {
|
|
1930
2103
|
for (const importKind of Object.keys(IMPORT_PATHS) as ImportKind[]) {
|
|
1931
2104
|
const imported = loadImportedConfig(importKind, cwd, `Failed to inspect imported MCP config from ${importKind}:`);
|
|
1932
2105
|
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
|
-
}
|
|
2106
|
+
// Later import kinds win in the same deterministic order as loadDiscoveredHostConfigs.
|
|
2107
|
+
for (const [name, prov] of importProvenance(importKind, imported.value)) provenance.set(name, prov);
|
|
1938
2108
|
}
|
|
1939
2109
|
}
|
|
1940
2110
|
|
|
@@ -1942,22 +2112,28 @@ export function getServerProvenance(overridePath?: string, cwd = process.cwd()):
|
|
|
1942
2112
|
const loaded = readSourceConfig(source.id, source.readPath);
|
|
1943
2113
|
if (!loaded) continue;
|
|
1944
2114
|
if (loaded.imports?.length) {
|
|
2115
|
+
// Same precedence as expandImports + mergeConfigs: the first import in a file wins, and a later file replaces earlier ones.
|
|
2116
|
+
const imported = new Set<string>();
|
|
1945
2117
|
for (const importKind of loaded.imports) {
|
|
1946
|
-
const
|
|
1947
|
-
if (!
|
|
2118
|
+
const importedConfig = loadImportedConfig(importKind, cwd, `Failed to inspect imported MCP config from ${importKind}:`);
|
|
2119
|
+
if (!importedConfig) continue;
|
|
1948
2120
|
|
|
1949
|
-
const
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
}
|
|
2121
|
+
for (const [name, prov] of importProvenance(importKind, importedConfig.value)) {
|
|
2122
|
+
if (imported.has(name)) continue;
|
|
2123
|
+
imported.add(name);
|
|
2124
|
+
provenance.set(name, prov);
|
|
1954
2125
|
}
|
|
1955
2126
|
}
|
|
1956
2127
|
}
|
|
1957
2128
|
|
|
1958
2129
|
for (const name of Object.keys(loaded.mcpServers)) {
|
|
1959
2130
|
// A Pi entry brings its own list; other sources keep the one from the Pi entry below them.
|
|
1960
|
-
|
|
2131
|
+
// A source that defines the server replaces an OpenCode import, so its OpenCode list no longer applies.
|
|
2132
|
+
// (`loaded.mcpServers` holds only the file's own servers, so a plain import keeps its list.)
|
|
2133
|
+
const below = provenance.get(name);
|
|
2134
|
+
const ignoredSettings = loaded.ignoredSettings
|
|
2135
|
+
? loaded.ignoredSettings.get(name)
|
|
2136
|
+
: below?.ignoredSettingsSource === "opencode" ? undefined : below?.ignoredSettings;
|
|
1961
2137
|
provenance.set(name, {
|
|
1962
2138
|
path: source.writePath,
|
|
1963
2139
|
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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"}
|