src / mcp / mcp.test.ts
src / mcp / mcp.test.ts
import assert from "node:assert/strict";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import test from "node:test";
import { Client } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
test("the built MCP server exposes generate_image and upscale over stdio", async () => {
const baseDirectory = await fs.mkdtemp(path.join(os.tmpdir(), "generate-image-mcp-"));
const client = new Client({ name: "generate-image-mcp-test", version: "0.1.0" });
try {
// Spawn via start-mcp.mjs, not dist-mcp/mcp/index.js directly: this keeps
// process.argv[1] at the project root, matching how Bionic invokes it
// (see start-mcp.mjs's own comment for why this matters).
const entryPoint = new URL("../../start-mcp.mjs", import.meta.url).pathname;
await client.connect(new StdioClientTransport({
command: process.execPath,
args: [entryPoint],
env: { CHAT_WORKING_DIRECTORIES: baseDirectory },
stderr: "pipe",
}));
const { tools } = await client.listTools();
assert.deepEqual(tools.map((tool) => tool.name).sort(), ["generate_image", "upscale"]);
const generateImage = tools.find((tool) => tool.name === "generate_image");
assert.deepEqual(generateImage?.inputSchema.required, ["scratchpadFolder"]);
assert.equal(generateImage?.inputSchema.additionalProperties, false);
assert.equal("quality" in ((generateImage?.inputSchema.properties as Record<string, unknown>) ?? {}), false);
const upscale = tools.find((tool) => tool.name === "upscale");
assert.deepEqual(upscale?.inputSchema.required, ["scratchpadFolder"]);
assert.equal("quality" in ((upscale?.inputSchema.properties as Record<string, unknown>) ?? {}), false);
// A missing scratchpad folder must fail fast, before any renderer call, with a clear cause.
const missingScratchpad = await client.callTool({
name: "generate_image",
arguments: { scratchpadFolder: "missing-session", prompt: "a cat" },
});
assert.equal(missingScratchpad.isError, true);
// aN must be rejected before the renderer is ever invoked.
await fs.mkdir(path.join(baseDirectory, "8x"));
const attachmentNotation = await client.callTool({
name: "generate_image",
arguments: { scratchpadFolder: "8x", prompt: "a cat", canvas: "a1" },
});
assert.equal(attachmentNotation.isError, true);
} finally {
await client.close();
await fs.rm(baseDirectory, { recursive: true, force: true });
}
});
import assert from "node:assert/strict";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import test from "node:test";
import { Client } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
test("the built MCP server exposes generate_image and upscale over stdio", async () => {
const baseDirectory = await fs.mkdtemp(path.join(os.tmpdir(), "generate-image-mcp-"));
const client = new Client({ name: "generate-image-mcp-test", version: "0.1.0" });
try {
// Spawn via start-mcp.mjs, not dist-mcp/mcp/index.js directly: this keeps
// process.argv[1] at the project root, matching how Bionic invokes it
// (see start-mcp.mjs's own comment for why this matters).
const entryPoint = new URL("../../start-mcp.mjs", import.meta.url).pathname;
await client.connect(new StdioClientTransport({
command: process.execPath,
args: [entryPoint],
env: { CHAT_WORKING_DIRECTORIES: baseDirectory },
stderr: "pipe",
}));
const { tools } = await client.listTools();
assert.deepEqual(tools.map((tool) => tool.name).sort(), ["generate_image", "upscale"]);
const generateImage = tools.find((tool) => tool.name === "generate_image");
assert.deepEqual(generateImage?.inputSchema.required, ["scratchpadFolder"]);
assert.equal(generateImage?.inputSchema.additionalProperties, false);
assert.equal("quality" in ((generateImage?.inputSchema.properties as Record<string, unknown>) ?? {}), false);
const upscale = tools.find((tool) => tool.name === "upscale");
assert.deepEqual(upscale?.inputSchema.required, ["scratchpadFolder"]);
assert.equal("quality" in ((upscale?.inputSchema.properties as Record<string, unknown>) ?? {}), false);
// A missing scratchpad folder must fail fast, before any renderer call, with a clear cause.
const missingScratchpad = await client.callTool({
name: "generate_image",
arguments: { scratchpadFolder: "missing-session", prompt: "a cat" },
});
assert.equal(missingScratchpad.isError, true);
// aN must be rejected before the renderer is ever invoked.
await fs.mkdir(path.join(baseDirectory, "8x"));
const attachmentNotation = await client.callTool({
name: "generate_image",
arguments: { scratchpadFolder: "8x", prompt: "a cat", canvas: "a1" },
});
assert.equal(attachmentNotation.isError, true);
} finally {
await client.close();
await fs.rm(baseDirectory, { recursive: true, force: true });
}
});