fix(setup): stop writing the default compact input profile for Kimi and LM Studio (#10559)

* fix(setup): stop writing the default compact input profile for Kimi and LM Studio

The server already advertises compact input schemas when
REA_MCP_INPUT_SCHEMA_PROFILE is unset, so setup no longer adds that value to
Kimi Code and LM Studio registrations, and doctor/status no longer report a
registration without it as stale. Explicit stored or setup-environment
profiles are still preserved.

Refs #10514

* fix(setup): validate LM Studio registration environment

* test(hopper): retry cleanup after confirmed application exit
This commit is contained in:
morluto authored and GitHub committed 2026-11-14 19:01:15 -05:00
1 parent a0d96e520e
commit 6c062c9c2a
7 files changed
+57 -63

No files matched your search

+9 -16
View File
@@ -427,18 +427,12 @@ client environment overrides remain intact unless setup supplies the same key.
You can edit `startupTimeoutMs` and `toolTimeoutMs` there; doctor keeps positive
caller-selected values aligned and reports a non-blocking advisory when a tool
timeout is shorter than the known provider allowance.
Setup selects the compact input-schema advertisement for new or refreshed Kimi
registrations unless their stored configuration or setup environment explicitly
selects `REA_MCP_INPUT_SCHEMA_PROFILE=full` or `compact`. This keeps REA's
canonical tool validation unchanged while avoiding a Moonshot Kimi Code 2.2.0
failure observed with the full catalog: its request was rejected because a
single tool schema exceeded the provider's size limit. The limit is
provider-specific and not an MCP protocol limit. `rea doctor --client kimi`
reports an older registration with no explicit profile as stale. To restore the
compact profile, run `rea setup --client kimi` or add
`REA_MCP_INPUT_SCHEMA_PROFILE=compact` to the registration's `env`, then restart
Kimi Code. An explicit `full` profile remains respected and can still exceed
Moonshot's limit.
The server advertises compact input schemas by default, which avoids a Moonshot
Kimi Code 2.2.0 failure observed with the full catalog: its request was rejected
because a single tool schema exceeded the provider's size limit. The limit is
provider-specific and not an MCP protocol limit. Setup preserves an explicit
`REA_MCP_INPUT_SCHEMA_PROFILE` from the stored registration or the setup
environment; an explicit `full` profile can still exceed Moonshot's limit.
Kimi Code also discovers personal skills in `~/.agents/skills` and project-local
`.agents/skills`, so no separate skill root is required; setup still installs
and verifies the matching skill files through the normal shared-skill flow.
@@ -453,10 +447,9 @@ LM Studio does: the path stored in `~/.lmstudio-home-pointer`, otherwise
the pointer file. LM Studio parses `mcp.json` as strict JSON, so setup rejects
a file with comments, trailing commas, or a byte order mark without a backup
or a write. The entry carries `command`, `args`, and `env`, and setup keeps
every other server. Local models often have small context windows, so setup
selects `REA_MCP_INPUT_SCHEMA_PROFILE=compact` unless the stored entry or the
setup environment selects a profile explicitly. Doctor reports an entry with no
explicit profile as stale, as for Kimi Code. LM Studio 0.4.26 on macOS loaded
every other server. Local models often have small context windows; the default
compact input-schema profile applies unless the stored entry or the setup
environment selects `REA_MCP_INPUT_SCHEMA_PROFILE` explicitly. LM Studio 0.4.26 on macOS loaded
this registration, and a local model called an REA tool through it. Linux and
Windows remain unverified.
+6 -16
View File
@@ -11,7 +11,6 @@ import { lstat, stat } from "node:fs/promises";
import { err, ok, type Result } from "../domain/result.js";
import { PRODUCT_IDENTITY } from "../identity.js";
import { snapshotEnvironment } from "../process/snapshotEnvironment.js";
import { CURRENT_ENTRYPOINT_PATH } from "../process/CurrentEntrypointPath.js";
import {
MCP_STARTUP_POLICY,
@@ -28,7 +27,6 @@ import {
import {
clientEvidencePaths,
clientOwnershipEvidencePaths,
compactInputSchemaByDefault,
manualRegistrationRemediation,
supportedClients,
} from "./SupportedClients.js";
@@ -337,15 +335,17 @@ export const readClientRegistrationInspections = async (
continue;
}
const registrationEnvironment =
compactInputSchemaByDefault(client) || client.name === "codex"
client.name === "kimi" ||
client.name === "codex" ||
client.name === "lm_studio"
? await clientRegistrationEnvironmentDetailsWithBudget(
parsed,
client,
{},
"effective",
client.name === "kimi" || client.name === "codex"
? options.resolveProviderRequestBudget
: undefined,
client.name === "lm_studio"
? undefined
: options.resolveProviderRequestBudget,
options.platform,
)
: undefined;
@@ -375,12 +375,6 @@ export const readClientRegistrationInspections = async (
registration,
client: effectiveClient,
commandFacts,
inputSchemaProfileSelected:
registrationEnvironment?.ok === true &&
snapshotEnvironment(
registrationEnvironment.value.values,
options.platform,
).REA_MCP_INPUT_SCHEMA_PROFILE !== undefined,
});
record(
configuredStatus(
@@ -510,12 +504,10 @@ const registrationAligned = ({
registration,
client,
commandFacts,
inputSchemaProfileSelected,
}: {
readonly registration: ClientRegistration;
readonly client: SetupClient;
readonly commandFacts: ClientRegistrationCommandFacts;
readonly inputSchemaProfileSelected: boolean;
}): "aligned" | "stale" | "unknown" => {
const { identity } = commandFacts;
if (identity === "unknown") return "unknown";
@@ -537,8 +529,6 @@ const registrationAligned = ({
registration.toolTimeoutMs === undefined)
)
return "stale";
if (compactInputSchemaByDefault(client) && !inputSchemaProfileSelected)
return "stale";
// Native transport is admitted separately; the Harness route must name REA.
if (client.format === "dsh" && registration.serverName !== "rea")
return "stale";
+1 -12
View File
@@ -47,7 +47,6 @@ import {
} from "./ClientRegistration.js";
import { PRODUCT_IDENTITY } from "../identity.js";
import { snapshotEnvironment } from "../process/snapshotEnvironment.js";
import { currentClientRegistrationCommand } from "./ClientRegistrationIdentity.js";
import { resolveClientConfigTransactionPath } from "./ClientConfigPath.js";
import { packageReadTargetChanged } from "./MsixPackageStorage.js";
@@ -67,7 +66,6 @@ import type {
SetupProviderEnvironment,
} from "./SetupTypes.js";
import {
compactInputSchemaByDefault,
registrationWriteBlocker,
type SetupClient,
} from "./SupportedClients.js";
@@ -940,20 +938,11 @@ const clientConfigurationDesired = async (
"fragment",
);
if (!environment.ok) throw environment.error;
const registrationEnvironment =
compactInputSchemaByDefault(client) &&
snapshotEnvironment(environment.value).REA_MCP_INPUT_SCHEMA_PROFILE ===
undefined
? {
...environment.value,
REA_MCP_INPUT_SCHEMA_PROFILE: "compact",
}
: environment.value;
const registration = refreshClientRegistration(
client,
format,
launcher,
registrationEnvironment,
environment.value,
parsed === undefined
? undefined
: effectiveClientServer(parsed, PRODUCT_IDENTITY.mcpServerKey),
-6
View File
@@ -190,12 +190,6 @@ const codexDirectory = ({ home, env }: ClientPathContext): string =>
const kimiDirectory = ({ home, env }: ClientPathContext): string =>
env.KIMI_CODE_HOME || join(home, ".kimi-code");
/** Clients registered with the compact input-schema profile unless one is selected. */
export const compactInputSchemaByDefault = ({
name,
}: Pick<SetupClient, "name">): boolean =>
name === "kimi" || name === "lm_studio";
/**
* LM Studio's findLMStudioHome, read-only: a home pointer file wins, then an
* existing ~/.cache/lm-studio, then ~/.lmstudio. Setup never writes the pointer.
@@ -136,6 +136,30 @@ describe("client registration status", () => {
});
describe("client registration environment validation", () => {
it("reports invalid LM Studio runtime settings without requiring an explicit profile", async () => {
const home = await createTestTempDirectory("rea-lm-studio-env-");
await mkdir(join(home, ".lmstudio"));
await writeFile(
join(home, ".lmstudio/mcp.json"),
JSON.stringify({
mcpServers: {
rea: {
command: "npx",
args: ["-y", PRODUCT_IDENTITY.registrationPackageSpecifier, "mcp"],
env: { REA_MCP_INPUT_SCHEMA_PROFILE: "invalid" },
},
},
}),
);
const statuses = await readClientRegistrationStatuses(home, "/current/rea");
expect(statuses).toContainEqual(
expect.objectContaining({
client: "lm_studio",
state: "invalid",
remediation: expect.stringContaining("REA_MCP_INPUT_SCHEMA_PROFILE"),
}),
);
});
it("reports an otherwise aligned registration invalid for a bad tool-family scope", async () => {
const home = await createTestTempDirectory("rea-registration-env-");
await mkdir(join(home, ".cursor"));
@@ -38,12 +38,14 @@ const exists = async (path: string): Promise<boolean> => {
const expectedRegistration = (
launcher: readonly string[],
profile = "compact",
profile?: "full" | "compact",
toolTimeoutMs = 450_000,
) => ({
command: launcher[0],
args: launcher.slice(1),
env: { REA_MCP_INPUT_SCHEMA_PROFILE: profile },
...(profile === undefined
? {}
: { env: { REA_MCP_INPUT_SCHEMA_PROFILE: profile } }),
startupTimeoutMs: MCP_STARTUP_POLICY.kimiStartupTimeoutMs,
toolTimeoutMs,
});
@@ -318,7 +320,7 @@ describe("Kimi Code input-schema profile selection", () => {
},
);
it("reports an omitted-profile registration stale until setup adds compact", async () => {
it("reports an omitted-profile registration aligned because compact is the server default", async () => {
const { home, client } = await kimiClient();
await writeFile(
client.configPath,
@@ -340,13 +342,13 @@ describe("Kimi Code input-schema profile selection", () => {
environment: {},
})
).find(({ client: name }) => name === "kimi"),
).toMatchObject({ state: "stale", command });
expect(
(await configureClientConfiguration(client, {}, command)).status,
).toBe("configured");
).toMatchObject({ state: "aligned", command });
expect(await configureClientConfiguration(client, {}, command)).toEqual({
status: "unchanged",
});
expect(
JSON.parse(await readFile(client.configPath, "utf8")).mcpServers.rea,
).toEqual(expectedRegistration(command, "compact", 120_000));
).toEqual(expectedRegistration(command, undefined, 120_000));
});
});
@@ -30,10 +30,12 @@ const exists = async (path: string): Promise<boolean> => {
}
};
const expectedRegistration = (profile = "compact") => ({
const expectedRegistration = (profile?: "full" | "compact") => ({
command: command[0],
args: command.slice(1),
env: { REA_MCP_INPUT_SCHEMA_PROFILE: profile },
...(profile === undefined
? {}
: { env: { REA_MCP_INPUT_SCHEMA_PROFILE: profile } }),
});
const doctorState = async (home: string) =>
@@ -89,7 +91,7 @@ describe("LM Studio home resolution", () => {
describe("LM Studio registration lifecycle", () => {
it.each(["default", "pointer", "cache"] as const)(
"registers rea with the compact profile in the %s home",
"registers rea with the default input profile in the %s home",
async (layout) => {
const { home, directory, client } = await installedHome(layout);
const pointer = join(home, ".lmstudio-home-pointer");
@@ -155,7 +157,7 @@ describe("LM Studio registration lifecycle", () => {
});
});
it("keeps an explicit full profile and reports an omitted profile stale", async () => {
it("keeps an explicit full profile and reports an omitted profile aligned", async () => {
const { home, client } = await installedHome("default");
await writeFile(
client.configPath,
@@ -163,7 +165,7 @@ describe("LM Studio registration lifecycle", () => {
mcpServers: { rea: { command: command[0], args: command.slice(1) } },
}),
);
expect(await doctorState(home)).toMatchObject({ state: "stale" });
expect(await doctorState(home)).toMatchObject({ state: "aligned" });
expect(
(