mcp-stdio.test.ts 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
  1. import { existsSync } from "node:fs";
  2. import { mkdtemp, rm } from "node:fs/promises";
  3. import { tmpdir } from "node:os";
  4. import { join, resolve } from "node:path";
  5. import { describe, expect, it } from "vitest";
  6. import { Client } from "@modelcontextprotocol/sdk/client/index.js";
  7. import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
  8. import { MCP_SERVER_NAME } from "../src/mcp-server.js";
  9. import { newProject } from "../src/template.js";
  10. const entry = resolve(process.cwd(), "dist/main/mcp-stdio.js");
  11. function textOf(result: { content: { type: string; text?: string }[] }): string {
  12. return result.content.find((block) => block.type === "text")?.text ?? "";
  13. }
  14. describe("editor --mcp-stdio headless mode (spawned entry over stdio)", () => {
  15. it("spawns a drop-in stdio MCP server for --dir: handshake + full surface + a tool call", async () => {
  16. expect(existsSync(entry)).toBe(true);
  17. const dir = await mkdtemp(join(tmpdir(), "editor-stdio-"));
  18. try {
  19. const project = await newProject("Stdio Farm", dir);
  20. const client = new Client({ name: "editor-stdio-test", version: "0.0.1" });
  21. const transport = new StdioClientTransport({ command: "node", args: [entry, "--dir", project.root] });
  22. await client.connect(transport);
  23. try {
  24. expect(client.getServerVersion()).toMatchObject({ name: MCP_SERVER_NAME, version: "0.1.0" });
  25. const tools = await client.listTools();
  26. expect(tools.tools).toHaveLength(124);
  27. const info = JSON.parse(textOf(await client.callTool({ name: "server.info", arguments: {} }))) as {
  28. engine: string;
  29. content: { map: string[]; time: string[]; item: string[] };
  30. };
  31. expect(info.engine).toBe("nengine");
  32. expect(info.content.map).toEqual(["stdio-farm"]);
  33. expect(info.content.item.sort()).toEqual(["hoe", "stdio-farm", "stdio-farm-seed"]);
  34. } finally {
  35. await client.close();
  36. }
  37. } finally {
  38. await rm(dir, { recursive: true, force: true });
  39. }
  40. });
  41. it("item.create over stdio writes content into the project's content dir", async () => {
  42. expect(existsSync(entry)).toBe(true);
  43. const dir = await mkdtemp(join(tmpdir(), "editor-stdio-"));
  44. try {
  45. const project = await newProject("Stdio Farm", dir);
  46. const client = new Client({ name: "editor-stdio-test", version: "0.0.1" });
  47. const transport = new StdioClientTransport({ command: "node", args: [entry, "--dir", project.root] });
  48. await client.connect(transport);
  49. try {
  50. const created = await client.callTool({
  51. name: "item.create",
  52. arguments: { def: { id: "sickle", name: "Sickle", kind: "tool", stackable: false } },
  53. });
  54. expect(created.isError).not.toBe(true);
  55. const file = resolve(project.root, "content/items/sickle.yaml");
  56. expect(existsSync(file)).toBe(true);
  57. } finally {
  58. await client.close();
  59. }
  60. } finally {
  61. await rm(dir, { recursive: true, force: true });
  62. }
  63. });
  64. });