mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
fix(coding-agent): accept mcp.json files with a UTF-8 BOM
closes #11191
This commit is contained in:
parent
22dc6c26f3
commit
bdd9f0399d
3 changed files with 27 additions and 2 deletions
|
|
@ -21,6 +21,7 @@
|
|||
- Fixed pasting in the shell hanging after copying text in pi and suspending it with Ctrl+Z on Linux: the background `wl-copy`/`xclip`/`xsel` process that keeps serving the clipboard was suspended along with pi ([#11179](https://github.com/earendil-works/pi/issues/11179))
|
||||
- Fixed codemode scripts failing immediately when both codemode workers are busy, for example with parallel codemode calls or in-process subagents. Scripts now wait up to 60 seconds for a free worker, and `PI_CODEMODE_MAX_WORKERS` raises the limit of two workers ([#11184](https://github.com/earendil-works/pi/issues/11184))
|
||||
- Fixed extensions failing to load when importing `@earendil-works/pi-ai/models` (e.g. for `clampThinkingLevel`) by exposing the subpath through the extension loader aliases and virtual modules ([#11156](https://github.com/earendil-works/pi/issues/11156))
|
||||
- Fixed `mcp.json` files that start with a UTF-8 BOM failing to parse, which skipped all their MCP servers and made `pi mcp add`/`pi mcp remove` fail ([#11191](https://github.com/earendil-works/pi/issues/11191))
|
||||
|
||||
## [1.3.0] - 2026-10-15
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import {
|
|||
mcpNamespace,
|
||||
validateMcpServerConfig,
|
||||
} from "../../core/mcp-servers.ts";
|
||||
import { stripBom } from "../../utils/text.ts";
|
||||
|
||||
export type {
|
||||
McpExposure,
|
||||
|
|
@ -93,7 +94,7 @@ function readConfigFile(path: string, scope: "global" | "project", state: McpCon
|
|||
if (!existsSync(path)) return;
|
||||
let parsed: unknown;
|
||||
try {
|
||||
parsed = JSON.parse(readFileSync(path, "utf8"));
|
||||
parsed = JSON.parse(stripBom(readFileSync(path, "utf8")));
|
||||
} catch (error) {
|
||||
errors.push(`${path}: ${error instanceof Error ? error.message : String(error)}`);
|
||||
return;
|
||||
|
|
@ -229,7 +230,7 @@ function editMcpServers(
|
|||
path: string,
|
||||
edit: (servers: Record<string, unknown> | undefined, parsed: Record<string, unknown>) => boolean,
|
||||
): void {
|
||||
const text = existsSync(path) ? readFileSync(path, "utf8") : undefined;
|
||||
const text = existsSync(path) ? stripBom(readFileSync(path, "utf8")) : undefined;
|
||||
const parsed: unknown = text === undefined ? {} : JSON.parse(text);
|
||||
if (!isRecord(parsed) || (parsed.mcpServers !== undefined && !isRecord(parsed.mcpServers))) {
|
||||
throw new Error(`${path}: expected an object with an "mcpServers" object`);
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import { afterEach, describe, expect, it } from "vitest";
|
|||
import { InMemoryAuthStorageBackend } from "../src/core/auth-storage.ts";
|
||||
import { truncateMiddle } from "../src/core/tools/truncate.ts";
|
||||
import {
|
||||
addMcpServerConfig,
|
||||
getMcpToolExposure,
|
||||
loadMcpConfig,
|
||||
type McpServerEntry,
|
||||
|
|
@ -115,6 +116,28 @@ describe("MCP config", () => {
|
|||
expect(JSON.parse(readFileSync(project, "utf8")).mcpServers.tools).toEqual({ enabled: true });
|
||||
});
|
||||
|
||||
// Regression: #11191.
|
||||
it("reads and edits mcp.json files that start with a UTF-8 BOM", () => {
|
||||
const paths = setup({}, {});
|
||||
const global = join(paths.agentDir, "mcp.json");
|
||||
const project = join(paths.cwd, ".pi", "mcp.json");
|
||||
writeFileSync(global, '\uFEFF{"mcpServers":{"demo":{"command":"echo","enabled":false}}}\n');
|
||||
writeFileSync(project, '\uFEFF{"mcpServers":{"local":{"command":"local-cmd"}}}\n');
|
||||
|
||||
const loaded = loadMcpConfig({ ...paths, projectTrusted: true });
|
||||
expect(loaded.errors).toEqual([]);
|
||||
expect(loaded.servers.map((server) => [server.name, server.scope])).toEqual([
|
||||
["demo", "global"],
|
||||
["local", "project"],
|
||||
]);
|
||||
|
||||
expect(addMcpServerConfig(global, "other", { command: "other-cmd" })).toBe(false);
|
||||
expect(JSON.parse(readFileSync(global, "utf8")).mcpServers).toEqual({
|
||||
demo: { command: "echo", enabled: false },
|
||||
other: { command: "other-cmd" },
|
||||
});
|
||||
});
|
||||
|
||||
// Regression: #10239.
|
||||
it("rejects server names that differ only in - and _", () => {
|
||||
const paths = setup({ mcpServers: { "work-files": { command: "a" }, work_files: { command: "b" } } }, {});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue