| @@ -1,6 +1,7 @@ |
| 1 | 1 | import { describe, expect, it } from "@effect/vitest"; |
| 2 | 2 | import { Client } from "@modelcontextprotocol/sdk/client/index.js"; |
| 3 | 3 | import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; |
| 4 | +import { Effect } from "effect"; |
| 4 | 5 | import { mkdtempSync } from "node:fs"; |
| 5 | 6 | import { tmpdir } from "node:os"; |
| 6 | 7 | import { join, resolve } from "node:path"; |
| @@ -10,35 +11,39 @@ const cliEntry = resolve(repoRoot, "apps/cli/src/main.ts"); · 3 unmodified lines |
| 10 | 11 | const testScope = resolve(repoRoot, "apps/local"); |
| 11 | 12 | |
| 12 | 13 | describe("MCP stdio integration", () => { |
| 13 | | − it("execute tool returns result over stdio transport", async () => { |
| 14 | | − // Fresh temp dir so the test doesn't migrate against the developer's |
| 15 | | − // real ~/.executor/data.db. |
| 16 | | − const dataDir = mkdtempSync(join(tmpdir(), "executor-mcp-test-")); |
| 17 | | − |
| 18 | | − const transport = new StdioClientTransport({ |
| 19 | | − command: "bun", |
| 20 | | − args: ["run", cliEntry, "mcp", "--scope", testScope], |
| 21 | | − env: { ...process.env, EXECUTOR_DATA_DIR: dataDir }, |
| 22 | | − }); |
| 14 | + it.effect("execute tool returns result over stdio transport", () => |
| 15 | + Effect.gen(function* () { |
| 16 | + // Fresh temp dir so the test doesn't migrate against the developer's |
| 17 | + // real ~/.executor/data.db. |
| 18 | + const dataDir = mkdtempSync(join(tmpdir(), "executor-mcp-test-")); |
| 19 | + |
| 20 | + const transport = new StdioClientTransport({ |
| 21 | + command: "bun", |
| 22 | + args: ["run", cliEntry, "mcp", "--scope", testScope], |
| 23 | + env: { ...process.env, EXECUTOR_DATA_DIR: dataDir }, |
| 24 | + }); |
| 23 | 25 | |
| 24 | | − const client = new Client({ name: "test-client", version: "1.0.0" }, { capabilities: {} }); |
| 26 | + const client = new Client({ name: "test-client", version: "1.0.0" }, { capabilities: {} }); |
| 25 | 27 | |
| 26 | | − await client.connect(transport); |
| 28 | + yield* Effect.acquireRelease( |
| 29 | + Effect.promise(() => client.connect(transport)), |
| 30 | + () => Effect.promise(() => transport.close()), |
| 31 | + ); |
| 27 | 32 | |
| 28 | | − try { |
| 29 | | − const { tools } = await client.listTools(); |
| 33 | + const { tools } = yield* Effect.promise(() => client.listTools()); |
| 30 | 34 | expect(tools.map((t) => t.name)).toContain("execute"); |
| 31 | 35 | |
| 32 | | − const result = await client.callTool({ |
| 33 | | − name: "execute", |
| 34 | | − arguments: { code: "return 2+2" }, |
| 35 | | − }); |
| 36 | + const result = yield* Effect.promise(() => |
| 37 | + client.callTool({ |
| 38 | + name: "execute", |
| 39 | + arguments: { code: "return 2+2" }, |
| 40 | + }), |
| 41 | + ); |
| 36 | 42 | |
| 37 | 43 | const text = (result.content as Array<{ type: string; text: string }>)[0]?.text; |
| 38 | 44 | expect(text).toContain("4"); |
| 39 | 45 | expect(result.isError).toBeFalsy(); |
| 40 | | − } finally { |
| 41 | | − await transport.close(); |
| 42 | | − } |
| 43 | | − }, 30_000); |
| 46 | + }).pipe(Effect.scoped), |
| 47 | + { timeout: 30_000 }, |
| 48 | + ); |
| 44 | 49 | }); |