mirror of
https://github.com/morluto/rea.git
synced 2026-11-12 17:59:08 +00:00
* fix(cli)!: own MCP client registration boundaries * fix(cli): bind registration to native connection facts * test(cli): isolate registration failure and fixture controls * test(cli): distinguish native syntax and launcher alignment * fix(cli): preserve registration selection and cancellation * fix(cli): honor Goose and Zed native settings paths * fix(cli): honor registration output controls * fix(cli): reject disabled Junie global locations
197 lines
5.8 KiB
TypeScript
197 lines
5.8 KiB
TypeScript
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
||
import { join } from "node:path";
|
||
import { Ajv2020 } from "ajv/dist/2020.js";
|
||
import { expect } from "vitest";
|
||
import { z } from "zod";
|
||
|
||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||
import { cliTest } from "../../support/cli/cliFixture.js";
|
||
import { registrationEnvironment } from "../../support/cli/registrationEnvironment.js";
|
||
|
||
cliTest(
|
||
"packaged mcp add advertises named schemas without changing settings",
|
||
async ({ cli }) => {
|
||
const home = await createTestTempDirectory("rea-mcp-discovery-");
|
||
const path = join(home, ".config", "amp", "settings.json");
|
||
const environment = await registrationEnvironment(home);
|
||
const run = (arguments_: readonly string[]) =>
|
||
cli.run({
|
||
arguments: arguments_,
|
||
cwd: home,
|
||
environment,
|
||
timeoutMs: 30_000,
|
||
});
|
||
const schema = await run([
|
||
"mcp",
|
||
"add",
|
||
"--agent",
|
||
"amp",
|
||
"--schema",
|
||
"--json",
|
||
]);
|
||
expect(schema.exitCode, schema.stderr).toBe(0);
|
||
const named = z
|
||
.object({
|
||
inputSchema: z.record(z.string(), z.unknown()),
|
||
outputSchema: z.record(z.string(), z.unknown()),
|
||
})
|
||
.parse(schema.json);
|
||
expect(named.inputSchema.$schema).toBe(
|
||
"https://json-schema.org/draft/2020-12/schema",
|
||
);
|
||
expect(named.outputSchema.$schema).toBe(
|
||
"https://json-schema.org/draft/2020-12/schema",
|
||
);
|
||
const ajv = new Ajv2020({ strict: true, allErrors: true });
|
||
const input = ajv.compile(named.inputSchema);
|
||
const output = ajv.compile(named.outputSchema);
|
||
expect(input({ agent: ["amp"] }), JSON.stringify(input.errors)).toBe(true);
|
||
expect(input({ agent: "amp" })).toBe(false);
|
||
await expect(access(path)).rejects.toMatchObject({ code: "ENOENT" });
|
||
for (const format of ["csv", "tsv"]) {
|
||
const result = await run([
|
||
"mcp",
|
||
"add",
|
||
"--agent",
|
||
"amp",
|
||
"--schema",
|
||
"--format",
|
||
format,
|
||
"--filter-output",
|
||
"clients",
|
||
"--columns",
|
||
"client",
|
||
]);
|
||
expect(result.exitCode).toBe(1);
|
||
expect(result.json).toMatchObject({
|
||
error: { code: "UNSUPPORTED_OUTPUT_COMBINATION" },
|
||
});
|
||
await expect(access(path)).rejects.toMatchObject({ code: "ENOENT" });
|
||
}
|
||
const invalidFormat = await run([
|
||
"mcp",
|
||
"add",
|
||
"--agent",
|
||
"amp",
|
||
"--format",
|
||
"--schema",
|
||
]);
|
||
expect(invalidFormat.exitCode).toBe(1);
|
||
expect(invalidFormat.stdout).not.toContain("inputSchema");
|
||
await expect(access(path)).rejects.toMatchObject({ code: "ENOENT" });
|
||
await mkdir(join(home, ".config", "amp"), { recursive: true });
|
||
const original = '{"existing":true}\n';
|
||
await writeFile(path, original);
|
||
for (const flag of ["--schema", "--version", "--help"]) {
|
||
const result = await run(["mcp", "add", "--agent", "amp", flag]);
|
||
expect(result.exitCode, result.stderr).toBe(0);
|
||
expect(await readFile(path, "utf8")).toBe(original);
|
||
}
|
||
const registered = await run(["mcp", "add", "--agent", "amp", "--json"]);
|
||
expect(registered.exitCode, registered.stderr).toBe(0);
|
||
expect(output(registered.json), JSON.stringify(output.errors)).toBe(true);
|
||
expect(registered.json).toMatchObject({
|
||
ok: true,
|
||
clients: [{ client: "amp", status: "configured" }],
|
||
});
|
||
expect(
|
||
JSON.parse(await readFile(path, "utf8"))["amp.mcpServers"].rea,
|
||
).toBeDefined();
|
||
},
|
||
);
|
||
|
||
cliTest(
|
||
"mcp add applies output controls to its registration document",
|
||
async ({ cli }) => {
|
||
const home = await createTestTempDirectory("rea-mcp-output-");
|
||
const environment = await registrationEnvironment(home);
|
||
const run = (arguments_: readonly string[]) =>
|
||
cli.run({
|
||
arguments: arguments_,
|
||
cwd: home,
|
||
environment,
|
||
timeoutMs: 30_000,
|
||
});
|
||
const filtered = await run([
|
||
"mcp",
|
||
"add",
|
||
"--agent",
|
||
"amp",
|
||
"--json",
|
||
"--filter-output",
|
||
"ok",
|
||
]);
|
||
expect(filtered.exitCode, filtered.stderr).toBe(0);
|
||
expect(filtered.json).toBe(true);
|
||
const schema = await run([
|
||
"mcp",
|
||
"add",
|
||
"--schema",
|
||
"--json",
|
||
"--filter-output",
|
||
"outputSchema.type",
|
||
]);
|
||
expect(schema.exitCode, schema.stderr).toBe(0);
|
||
expect(schema.json).toEqual({ outputSchema: { type: "object" } });
|
||
const failure = await run([
|
||
"mcp",
|
||
"add",
|
||
"--agent",
|
||
"not-a-client",
|
||
"--json",
|
||
"--filter-output",
|
||
"message",
|
||
]);
|
||
expect(failure.exitCode).toBe(1);
|
||
expect(failure.json).toMatchObject({
|
||
code: "VALIDATION_ERROR",
|
||
message: expect.any(String),
|
||
fieldErrors: [{ path: "agent" }],
|
||
});
|
||
const path = join(home, ".config", "amp", "settings.json");
|
||
await writeFile(path, "{malformed");
|
||
const partial = await run([
|
||
"mcp",
|
||
"add",
|
||
"--agent",
|
||
"amp",
|
||
"--json",
|
||
"--filter-output",
|
||
"ok",
|
||
]);
|
||
expect(partial.exitCode).toBe(1);
|
||
expect(partial.json).toMatchObject({
|
||
ok: false,
|
||
clients: [
|
||
{
|
||
client: "amp",
|
||
status: "failed",
|
||
error: { reason: expect.any(String) },
|
||
},
|
||
],
|
||
});
|
||
await writeFile(path, "{}");
|
||
const counted = await run([
|
||
"mcp",
|
||
"add",
|
||
"--agent",
|
||
"amp",
|
||
"--token-count",
|
||
]);
|
||
expect(counted.exitCode, counted.stderr).toBe(0);
|
||
expect(counted.stdout.trim()).toMatch(/^\d+$/u);
|
||
const windowed = await run([
|
||
"mcp",
|
||
"add",
|
||
"--agent",
|
||
"amp",
|
||
"--token-limit",
|
||
"2",
|
||
]);
|
||
expect(windowed.exitCode, windowed.stderr).toBe(0);
|
||
expect(windowed.stdout).toMatch(
|
||
/\[truncated: showing tokens 0–2 of \d+\]/u,
|
||
);
|
||
},
|
||
);
|