diff --git a/packages/hosts/mcp/src/stdio-integration.test.ts b/packages/hosts/mcp/src/stdio-integration.test.ts index db3b461b5..6d0aea108 100644 --- a/packages/hosts/mcp/src/stdio-integration.test.ts +++ b/packages/hosts/mcp/src/stdio-integration.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from "@effect/vitest"; import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; +import { Effect } from "effect"; import { mkdtempSync } from "node:fs"; import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; @@ -10,35 +11,39 @@ const cliEntry = resolve(repoRoot, "apps/cli/src/main.ts"); const testScope = resolve(repoRoot, "apps/local"); describe("MCP stdio integration", () => { - it("execute tool returns result over stdio transport", async () => { - // Fresh temp dir so the test doesn't migrate against the developer's - // real ~/.executor/data.db. - const dataDir = mkdtempSync(join(tmpdir(), "executor-mcp-test-")); - - const transport = new StdioClientTransport({ - command: "bun", - args: ["run", cliEntry, "mcp", "--scope", testScope], - env: { ...process.env, EXECUTOR_DATA_DIR: dataDir }, - }); + it.effect("execute tool returns result over stdio transport", () => + Effect.gen(function* () { + // Fresh temp dir so the test doesn't migrate against the developer's + // real ~/.executor/data.db. + const dataDir = mkdtempSync(join(tmpdir(), "executor-mcp-test-")); + + const transport = new StdioClientTransport({ + command: "bun", + args: ["run", cliEntry, "mcp", "--scope", testScope], + env: { ...process.env, EXECUTOR_DATA_DIR: dataDir }, + }); - const client = new Client({ name: "test-client", version: "1.0.0" }, { capabilities: {} }); + const client = new Client({ name: "test-client", version: "1.0.0" }, { capabilities: {} }); - await client.connect(transport); + yield* Effect.acquireRelease( + Effect.promise(() => client.connect(transport)), + () => Effect.promise(() => transport.close()), + ); - try { - const { tools } = await client.listTools(); + const { tools } = yield* Effect.promise(() => client.listTools()); expect(tools.map((t) => t.name)).toContain("execute"); - const result = await client.callTool({ - name: "execute", - arguments: { code: "return 2+2" }, - }); + const result = yield* Effect.promise(() => + client.callTool({ + name: "execute", + arguments: { code: "return 2+2" }, + }), + ); const text = (result.content as Array<{ type: string; text: string }>)[0]?.text; expect(text).toContain("4"); expect(result.isError).toBeFalsy(); - } finally { - await transport.close(); - } - }, 30_000); + }).pipe(Effect.scoped), + { timeout: 30_000 }, + ); }); diff --git a/tests/presets-reachable.test.ts b/tests/presets-reachable.test.ts index 120e7603c..86c051a7e 100644 --- a/tests/presets-reachable.test.ts +++ b/tests/presets-reachable.test.ts @@ -62,10 +62,11 @@ describe("graphql presets are reachable endpoints", () => { const result = yield* introspect(preset.url).pipe( Effect.provide(FetchHttpClient.layer), Effect.map((r) => ({ ok: true as const, schema: r })), - Effect.catch((err) => + Effect.catchTag("GraphqlIntrospectionError", (err) => Effect.succeed({ ok: false as const, - message: String(err), + // oxlint-disable-next-line executor/no-unknown-error-message -- boundary: catchTag narrows to GraphqlIntrospectionError whose public contract includes message + message: err.message, }), ), ); diff --git a/tests/tools-cli.test.ts b/tests/tools-cli.test.ts index d3931beb5..66a5b6940 100644 --- a/tests/tools-cli.test.ts +++ b/tests/tools-cli.test.ts @@ -34,6 +34,7 @@ describe("CLI tooling helpers", () => { it.effect("rejects non-object JSON input", () => Effect.gen(function* () { const error = yield* parseJsonObjectInput('[1,2,3]').pipe(Effect.flip); + // oxlint-disable-next-line executor/no-unknown-error-message -- boundary: helper contract returns a native Error for CLI input parsing expect(error.message).toContain("must decode to a JSON object"); }), );