@vitest-agent/mcp 4.3.1 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -2,7 +2,6 @@ import { RenderText } from "./annotations.js";
2
2
  import { withIdempotency } from "./idempotency.js";
3
3
  import { McpSession, createCurrentSessionIdRef, createSessionContextRef, sessionContextFromEnv } from "./session.js";
4
4
  import { PromptsLayer } from "./prompts/layer.js";
5
- import { registerStrictToolkit } from "./register-toolkit.js";
6
5
  import { AcceptanceMetricsResult } from "./tools/acceptance-metrics.js";
7
6
  import { CacheHealthResult } from "./tools/cache-health.js";
8
7
  import { CommitChangesInput, CommitChangesResult } from "./tools/commit-changes.js";
@@ -32,5 +31,6 @@ import { WrapupPromptInput, WrapupPromptResult } from "./tools/wrapup-prompt.js"
32
31
  import { Kit, ToolsLayer, toolHandlers } from "./toolkit.js";
33
32
  import { SERVER_INSTRUCTIONS, ServerLayer } from "./server.js";
34
33
  import { CURRENT_MCP_VERSION } from "./version.js";
34
+ import { ToolRefusal } from "@effected/mcp";
35
35
 
36
- export { AcceptanceMetricsResult, CURRENT_MCP_VERSION, CacheHealthResult, CommitChangesInput, CommitChangesResult, ConfigureInput, ConfigureResult, FailureSignatureGetInput, FailureSignatureGetResult, FileCoverageInput, FileCoverageResult, HelpResult, InventoryInput, InventoryResult, Kit, McpSession, NoteParams, NoteResult, PhaseTransitionInput, PhaseTransitionResult, PingResult, PromptsLayer, RegisterAgentInput, RegisterAgentResult, RenderText, RunTestsInput, RunTestsResult, SERVER_INSTRUCTIONS, ServerLayer, SettingsListResult, TddArtifactListInput, TddArtifactListResult, TddProgressPushInput, TddProgressPushResult, TestCoverageInput, TestCoverageResult, TestErrorsInput, TestErrorsResult, TestHistoryInput, TestHistoryResult, TestInput, TestOverviewInput, TestOverviewResult, TestResult, TestStatusInput, TestStatusResult, TestTrendsInput, TestTrendsResult, ToolsLayer, TriageBriefInput, TriageBriefResult, TurnSearchInput, TurnSearchResult, WrapupPromptInput, WrapupPromptResult, createCurrentSessionIdRef, createSessionContextRef, registerStrictToolkit, sessionContextFromEnv, toolHandlers, withIdempotency };
36
+ export { AcceptanceMetricsResult, CURRENT_MCP_VERSION, CacheHealthResult, CommitChangesInput, CommitChangesResult, ConfigureInput, ConfigureResult, FailureSignatureGetInput, FailureSignatureGetResult, FileCoverageInput, FileCoverageResult, HelpResult, InventoryInput, InventoryResult, Kit, McpSession, NoteParams, NoteResult, PhaseTransitionInput, PhaseTransitionResult, PingResult, PromptsLayer, RegisterAgentInput, RegisterAgentResult, RenderText, RunTestsInput, RunTestsResult, SERVER_INSTRUCTIONS, ServerLayer, SettingsListResult, TddArtifactListInput, TddArtifactListResult, TddProgressPushInput, TddProgressPushResult, TestCoverageInput, TestCoverageResult, TestErrorsInput, TestErrorsResult, TestHistoryInput, TestHistoryResult, TestInput, TestOverviewInput, TestOverviewResult, TestResult, TestStatusInput, TestStatusResult, TestTrendsInput, TestTrendsResult, ToolRefusal, ToolsLayer, TriageBriefInput, TriageBriefResult, TurnSearchInput, TurnSearchResult, WrapupPromptInput, WrapupPromptResult, createCurrentSessionIdRef, createSessionContextRef, sessionContextFromEnv, toolHandlers, withIdempotency };
package/main.d.ts CHANGED
@@ -1,31 +1,30 @@
1
+ import { Distribution } from "@effected/engine";
1
2
  //#region src/main.d.ts
2
3
  /**
3
- * The assembled vitest-agent MCP server program over stdio. Owns the process.
4
+ * Options for {@link main}.
4
5
  *
5
- * This module deliberately carries NO static imports of the server graph:
6
- * the `unhandledRejection` and `uncaughtException` guards are registered
7
- * before `NodeRuntime`, the engine platform and `ServerLayer` are ever
8
- * evaluated, so a throw during module evaluation is still reported on stderr
9
- * rather than crashing silently. Adding a static import here would defeat
10
- * that. `bin.ts` is the published bin shim; this module is also published as
11
- * the `./main` subpath so the carrier (`@vitest-agent/plugin`) can ship its
12
- * own `vitest-agent-mcp` bin over it.
13
- *
14
- * Every `process` read lives here, not in the server layer or the tools.
15
- *
16
- * @packageDocumentation
6
+ * @public
17
7
  */
8
+ export interface MainOptions {
9
+ /**
10
+ * The package whose bin launched the server — the carrier
11
+ * (`@vitest-agent/plugin`) passes its own name and version. Omitted for a
12
+ * direct `@vitest-agent/mcp` install. Surfaced as `ping`'s `distribution`.
13
+ */
14
+ readonly distribution?: Distribution | undefined;
15
+ }
18
16
  /**
19
17
  * Run the vitest-agent MCP server over stdio. Owns the process: registers
20
- * the crash guards, resolves the project directory and `data.db` path,
18
+ * the crash guards through `McpGuard.run`, resolves the project directory and `data.db` path,
21
19
  * recovers the host session context, and launches the server layer under
22
- * `NodeRuntime.runMain`.
20
+ * `NodeRuntime.runMain` with `McpStdio.launch` / `McpStdio.teardown`.
23
21
  *
24
22
  * Not re-exported from `index.ts` — a library consumer's import graph must
25
23
  * not pull in the process-owning module.
26
24
  *
25
+ * @param options - the launching distribution, when a carrier shipped the bin
27
26
  * @public
28
27
  */
29
- export declare const main: () => Promise<void>;
28
+ export declare const main: (options?: MainOptions) => Promise<void>;
30
29
  //#endregion
31
30
  //# sourceMappingURL=main.d.ts.map
package/main.js CHANGED
@@ -1,105 +1,72 @@
1
- import { shouldExitOnUncaughtException } from "./utils/crash-guards.js";
1
+ import { McpGuard } from "@effected/mcp/guard";
2
2
 
3
3
  //#region src/main.ts
4
4
  /**
5
- * The assembled vitest-agent MCP server program over stdio. Owns the process.
6
- *
7
- * This module deliberately carries NO static imports of the server graph:
8
- * the `unhandledRejection` and `uncaughtException` guards are registered
9
- * before `NodeRuntime`, the engine platform and `ServerLayer` are ever
10
- * evaluated, so a throw during module evaluation is still reported on stderr
11
- * rather than crashing silently. Adding a static import here would defeat
12
- * that. `bin.ts` is the published bin shim; this module is also published as
13
- * the `./main` subpath so the carrier (`@vitest-agent/plugin`) can ship its
14
- * own `vitest-agent-mcp` bin over it.
15
- *
16
- * Every `process` read lives here, not in the server layer or the tools.
17
- *
18
- * @packageDocumentation
19
- */
20
- /**
21
- * Whether the stdio transport is live for this process — set once the
22
- * whole server layer graph (the stdio protocol included) has been built.
23
- * Read by the `uncaughtException` guard via `shouldExitOnUncaughtException`;
24
- * see that function's doc comment for the judgment call this flag backs
25
- * (issue #191).
26
- */
27
- let transportConnected = false;
28
- /**
29
- * Formatter for the crash guards. Starts as a dependency-free fallback so a
30
- * throw during module evaluation of the server graph is still described,
31
- * and is swapped for the SDK's `safeFormatFatalError` once that import
32
- * has resolved.
33
- */
34
- let formatFatal = (error) => {
35
- try {
36
- return error instanceof Error ? error.stack ?? error.message : String(error);
37
- } catch {
38
- return "<unformattable error value>";
39
- }
40
- };
41
- /**
42
- * Test-only crash injection, gated by an env var so it can never fire in a
43
- * normal install. Exists so `__test__/bin-crash-resilience.e2e.test.ts` can
44
- * exercise the guards against a *real* child process. Fires exactly once,
45
- * on the next event-loop turn after the transport is known to be connected,
46
- * so ordering relative to `transportConnected` is deterministic regardless
47
- * of client-side handshake timing.
48
- */
49
- const scheduleTestCrashInjection = () => {
50
- const kind = process.env.VITEST_AGENT_MCP_TEST_INJECT_CRASH;
51
- if (kind !== "unhandledRejection" && kind !== "uncaughtException") return;
52
- setImmediate(() => {
53
- if (kind === "unhandledRejection") Promise.reject(/* @__PURE__ */ new Error("[test-injected] unhandledRejection"));
54
- else throw new Error("[test-injected] uncaughtException");
55
- });
56
- };
57
- /**
58
5
  * Optional first positional argument: an initial Claude Code chat UUID (the
59
6
  * host's `chatId`) to seed the MCP server's session association. Claude Code
60
- * substitutes unknown `${...}` variables to literal text in some surfaces, so
61
- * a literal substitution is treated as absent rather than seeding garbage.
7
+ * passes unknown `${...}` variables through as literal text in some surfaces,
8
+ * so an unsubstituted placeholder is treated as absent rather than seeding
9
+ * garbage.
62
10
  *
63
11
  * @param argv - the raw `process.argv`
64
- * @returns the trimmed seed, or `null` when absent, empty, or a literal `${...}`
12
+ * @param isUnsubstituted - `LaunchContext.isUnsubstituted`, passed in because
13
+ * this module may not statically import the server graph
14
+ * @returns the trimmed seed, or `null` when absent, empty, or a placeholder
65
15
  */
66
- const resolveInitialSessionId = (argv) => {
67
- const first = argv[2];
68
- if (first === void 0) return null;
69
- const trimmed = first.trim();
70
- if (trimmed.length === 0) return null;
71
- if (trimmed.startsWith("${") && trimmed.endsWith("}")) return null;
16
+ const resolveInitialSessionId = (argv, isUnsubstituted) => {
17
+ const trimmed = argv[2]?.trim();
18
+ if (trimmed === void 0 || trimmed.length === 0 || isUnsubstituted(trimmed)) return null;
72
19
  return trimmed;
73
20
  };
21
+ const INJECT_AT = ["load", "connected"];
22
+ const INJECT_KIND = ["uncaughtException", "unhandledRejection"];
23
+ /**
24
+ * Parse the test-only `VITEST_AGENT_MCP_TEST_INJECT_CRASH` value into the
25
+ * guard's `injectCrash`. Accepts `<at>:<kind>` (`at` is `load` or
26
+ * `connected`, `kind` is `uncaughtException` or `unhandledRejection`), and a
27
+ * bare `<kind>` as the legacy spelling of `connected:<kind>`. Anything else,
28
+ * or no value, is `undefined`: no injection.
29
+ */
30
+ const parseInjectCrash = (value) => {
31
+ if (value === void 0) return void 0;
32
+ const [first, second, ...rest] = value.split(":");
33
+ if (rest.length > 0) return void 0;
34
+ const at = second === void 0 ? "connected" : first;
35
+ const kind = second === void 0 ? first : second;
36
+ const validAt = INJECT_AT.find((candidate) => candidate === at);
37
+ const validKind = INJECT_KIND.find((candidate) => candidate === kind);
38
+ return validAt === void 0 || validKind === void 0 ? void 0 : {
39
+ at: validAt,
40
+ kind: validKind
41
+ };
42
+ };
74
43
  /**
75
44
  * Run the vitest-agent MCP server over stdio. Owns the process: registers
76
- * the crash guards, resolves the project directory and `data.db` path,
45
+ * the crash guards through `McpGuard.run`, resolves the project directory and `data.db` path,
77
46
  * recovers the host session context, and launches the server layer under
78
- * `NodeRuntime.runMain`.
47
+ * `NodeRuntime.runMain` with `McpStdio.launch` / `McpStdio.teardown`.
79
48
  *
80
49
  * Not re-exported from `index.ts` — a library consumer's import graph must
81
50
  * not pull in the process-owning module.
82
51
  *
52
+ * @param options - the launching distribution, when a carrier shipped the bin
83
53
  * @public
84
54
  */
85
- const main = async () => {
86
- process.on("unhandledRejection", (reason) => {
87
- process.stderr.write(`vitest-agent-mcp: unhandledRejection: ${formatFatal(reason)}\n`);
88
- });
89
- process.on("uncaughtException", (err, origin) => {
90
- process.stderr.write(`vitest-agent-mcp: uncaughtException (${origin}): ${formatFatal(err)}\n`);
91
- if (shouldExitOnUncaughtException(transportConnected)) {
92
- process.exitCode = 1;
93
- process.exit(1);
94
- }
95
- });
96
- try {
55
+ const main = (options = {}) => McpGuard.run({
56
+ label: "vitest-agent-mcp",
57
+ host: process,
58
+ policy: {
59
+ onUncaught: "exitBeforeConnect",
60
+ onRejection: "log"
61
+ },
62
+ injectCrash: parseInjectCrash(process.env.VITEST_AGENT_MCP_TEST_INJECT_CRASH),
63
+ load: async () => {
97
64
  const { safeFormatFatalError } = await import("./utils/safe-format-fatal-error.js");
98
- formatFatal = safeFormatFatalError;
99
65
  const NodeRuntime = await import("@effect/platform-node/NodeRuntime");
100
66
  const NodeServices = await import("@effect/platform-node/NodeServices");
101
67
  const NodeStdio = await import("@effect/platform-node/NodeStdio");
102
- const { Cause, Effect, Exit, Layer, Logger, Runtime } = await import("effect");
68
+ const { Effect, Layer, Option } = await import("effect");
69
+ const { CurrentDistribution, LaunchContext } = await import("@effected/engine");
103
70
  const { PathResolutionLive, PlatformLive, recoverSessionContextFromSessionEnv, resolveDataPath, resolveLogFile, resolveLogLevel, resolveProjectDir } = await import("@vitest-agent/engine");
104
71
  const { McpSession, sessionContextFromEnv } = await import("./session.js");
105
72
  const { ServerLayer } = await import("./server.js");
@@ -109,7 +76,7 @@ const main = async () => {
109
76
  env,
110
77
  cwd: process.cwd()
111
78
  });
112
- const initialSessionId = resolveInitialSessionId(process.argv);
79
+ const initialSessionId = resolveInitialSessionId(process.argv, LaunchContext.isUnsubstituted);
113
80
  const dbPath = await Effect.runPromise(resolveDataPath(projectDir).pipe(Effect.provide(PathResolutionLive(projectDir)), Effect.provide(NodeServices.layer)));
114
81
  const recovered = sessionContextFromEnv(env);
115
82
  const homeDir = env.HOME ?? env.USERPROFILE ?? "";
@@ -122,23 +89,18 @@ const main = async () => {
122
89
  homeDir
123
90
  })
124
91
  });
125
- const Main = ServerLayer({ version: CURRENT_MCP_VERSION }).pipe(Layer.provide(Session), Layer.provide(PlatformLive({
126
- dbPath,
127
- env,
128
- logLevel: resolveLogLevel(env),
129
- logFile: resolveLogFile(env)
130
- })), Layer.provide(NodeStdio.layer), Layer.provide(Layer.succeed(Logger.LogToStderr, true)));
131
- const Connected = Layer.effectDiscard(Effect.sync(() => {
132
- transportConnected = true;
133
- scheduleTestCrashInjection();
134
- })).pipe(Layer.provide(Main));
135
- const program = Layer.launch(Connected).pipe(Effect.provideService(Logger.LogToStderr, true));
136
- NodeRuntime.runMain(program, { teardown: (exit, onExit) => Exit.isSuccess(exit) || Cause.hasInterruptsOnly(exit.cause) ? onExit(0) : Runtime.defaultTeardown(exit, onExit) });
137
- } catch (err) {
138
- process.stderr.write(`vitest-agent-mcp: startup failed: ${formatFatal(err)}\n`);
139
- process.exit(1);
92
+ return {
93
+ layer: ServerLayer({ version: CURRENT_MCP_VERSION }).pipe(Layer.provide(Session), Layer.provide(PlatformLive({
94
+ dbPath,
95
+ env,
96
+ logLevel: resolveLogLevel(env),
97
+ logFile: resolveLogFile(env)
98
+ })), Layer.provide(Layer.succeed(CurrentDistribution, Option.fromNullishOr(options.distribution))), Layer.provide(NodeStdio.layer)),
99
+ runMain: NodeRuntime.runMain,
100
+ format: safeFormatFatalError
101
+ };
140
102
  }
141
- };
103
+ });
142
104
 
143
105
  //#endregion
144
106
  export { main };
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@vitest-agent/mcp",
3
- "version": "4.3.1",
3
+ "version": "5.0.0",
4
4
  "private": false,
5
- "description": "Model Context Protocol server for vitest-agent. Exposes 53 tools for agent access to test data, TDD lifecycle, and session management.",
5
+ "description": "Model Context Protocol server for vitest-agent. Exposes 30 tools for agent access to test data, TDD lifecycle, and session management.",
6
6
  "keywords": [
7
7
  "vitest",
8
8
  "agent",
@@ -45,7 +45,9 @@
45
45
  "dependencies": {
46
46
  "@effect/platform-node": "4.0.0-rc.117",
47
47
  "@effect/sql-sqlite-node": "4.0.0-rc.117",
48
- "@vitest-agent/engine": "0.2.5",
48
+ "@effected/engine": "^0.1.0",
49
+ "@effected/mcp": "^0.2.0",
50
+ "@vitest-agent/engine": "0.2.7",
49
51
  "@vitest-agent/sdk": "5.1.2",
50
52
  "effect": "4.0.0-rc.117"
51
53
  },
package/server.js CHANGED
@@ -1,8 +1,7 @@
1
1
  import { PromptsLayer } from "./prompts/layer.js";
2
- import { registerStrictToolkit } from "./register-toolkit.js";
3
2
  import { Kit, ToolsLayer } from "./toolkit.js";
4
- import { Layer, Logger } from "effect";
5
- import { McpProtocol, McpServer } from "effect/unstable/ai";
3
+ import { McpStdio, McpToolkit } from "@effected/mcp";
4
+ import { Layer } from "effect";
6
5
 
7
6
  //#region src/server.ts
8
7
  /** One-line human summary agents see on `serverInfo.description`. */
@@ -20,42 +19,31 @@ const SERVER_INSTRUCTIONS = [
20
19
  "vitest-agent serves the test landscape of one Vitest project to LLM coding agents: run results, failure detail and history, coverage, flakiness, and the TDD lifecycle (tasks, goals, behaviors, phase transitions, hypotheses, notes).",
21
20
  "Call the help tool first for the full tool reference; triage_brief orients on the current landscape; run_tests executes Vitest and persists the run so every query tool reads it back.",
22
21
  "Every input is strict at every object level: an unknown key is rejected with a message naming the unrecognized key(s) and the accepted params, so fix the call rather than retrying it.",
23
- "Every successful result carries the typed object in structuredContent, and content[0].text holds the same object as JSON; read structuredContent when you need fields.",
22
+ "Every successful result carries the typed object in structuredContent; read structuredContent for every field.",
24
23
  "An expected domain error is NOT an isError result: it is a normal result whose structuredContent has ok: false and an error object. tdd_goal and tdd_behavior put _tag, the failing ids and a remediation naming the tool to call next in that object; register_agent puts a code and a message in it, plus existingAgentId or expectedPrefix when the code calls for one.",
25
- "An isError result means the call itself failed: invalid params (bare text in content[0].text, no structuredContent) or an unexpected crash inside the tool (structuredContent is an UnexpectedToolError envelope with ok: false and a remediation). Read the message and fix the call."
24
+ "An isError result means the call itself failed and carries no structuredContent. Its text either names what to fix (invalid params, or a refused call such as an unknown id, often ending 'Try <tool>.' with the tool to call next) or is a generic internal-error message (the server logged the detail). Fix the call, or report the internal failure rather than retrying it unchanged."
26
25
  ].join("\n");
27
26
  /**
28
- * The server layer: every tool registered under the strict contract, plus
29
- * the six framing prompts, over `McpServer.layerStdio`.
27
+ * The server layer: every tool registered through `McpToolkit.layer` (strict
28
+ * by default, every unknown key named in one `InvalidParams`) plus the six
29
+ * framing prompts, over `McpStdio.layer`.
30
30
  *
31
- * `protocols` order is load-bearing (rc.116 `unstable/ai/internal/mcpRuntime.ts`):
32
- * a request carrying `_meta["io.modelcontextprotocol/protocolVersion"]`
33
- * routes to that adapter, an `initialize` matches the STATEFUL adapters
34
- * only, and anything else with no session falls to `protocols[0]`. The
35
- * stateless `2026-07-28` adapter (SEP-2575: no handshake, no session,
36
- * `server/discover` instead of `initialize`) is listed first, then the two
37
- * newest stateful ones — every shipping client (Claude Code's default stdio
38
- * session, Copilot, Cursor, the Inspector) still opens with `initialize`,
39
- * which a server offering ONLY `2026-07-28` answers with
40
- * `METHOD_NOT_FOUND`. At most one stateless adapter is allowed; a second
41
- * fails the layer with `Cause.IllegalArgumentError`, which is why the
42
- * error channel is `orDie`d: `protocols` is a static literal, so a failure
43
- * there is an implementer-time defect.
31
+ * `McpStdio.layer` serves `McpStdio.protocols` — the stateless `2026-07-28`
32
+ * adapter first, then `2025-11-25` and `2025-06-18` — provides
33
+ * `LogToStderr` (stdout is the JSON-RPC wire) to everything it provides, and
34
+ * answers a malformed stdin line itself instead of wedging core's decoder.
35
+ * It is provided, not merged, so the output stays `never`; a test registers
36
+ * extra tools beside it through the shared module-level `McpServer` registry.
44
37
  *
45
38
  * @param options - the advertised server version
46
39
  * @public
47
40
  */
48
- const ServerLayer = (options) => Layer.mergeAll(registerStrictToolkit(Kit).pipe(Layer.provide(ToolsLayer)), PromptsLayer).pipe(Layer.provide(McpServer.layerStdio({
41
+ const ServerLayer = (options) => Layer.mergeAll(McpToolkit.layer(Kit).pipe(Layer.provide(ToolsLayer)), PromptsLayer).pipe(Layer.provide(McpStdio.layer({
49
42
  name: "vitest-agent",
50
43
  version: options.version,
51
44
  description: DESCRIPTION,
52
- instructions: SERVER_INSTRUCTIONS,
53
- protocols: [
54
- McpProtocol.v2026_07_28,
55
- McpProtocol.v2025_11_25,
56
- McpProtocol.v2025_06_18
57
- ]
58
- })), Layer.provide(Layer.succeed(Logger.LogToStderr, true)), Layer.orDie);
45
+ instructions: SERVER_INSTRUCTIONS
46
+ })));
59
47
 
60
48
  //#endregion
61
49
  export { SERVER_INSTRUCTIONS, ServerLayer };
package/toolkit.js CHANGED
@@ -29,6 +29,7 @@ import { handleTestTrends, testTrendsTool } from "./tools/trends.js";
29
29
  import { handleTriageBrief, triageBriefTool } from "./tools/triage-brief.js";
30
30
  import { handleTurnSearch, turnSearchTool } from "./tools/turn-search.js";
31
31
  import { handleWrapupPrompt, wrapupPromptTool } from "./tools/wrapup-prompt.js";
32
+ import { McpToolkit } from "@effected/mcp";
32
33
  import { Toolkit } from "effect/unstable/ai";
33
34
 
34
35
  //#region src/toolkit.ts
@@ -39,7 +40,10 @@ import { Toolkit } from "effect/unstable/ai";
39
40
  */
40
41
  const Kit = Toolkit.make(pingTool, helpTool, testStatusTool, testOverviewTool, testCoverageTool, testHistoryTool, testTrendsTool, testErrorsTool, fileCoverageTool, settingsListTool, cacheHealthTool, configureTool, commitChangesTool, turnSearchTool, failureSignatureGetTool, acceptanceMetricsTool, triageBriefTool, wrapupPromptTool, inventoryTool, testTool, registerAgentTool, noteTool, hypothesisTool, tddTaskTool, tddPhaseTransitionRequestTool, tddGoalTool, tddBehaviorTool, tddArtifactListTool, tddProgressPushTool, runTestsTool);
41
42
  /**
42
- * The handler for each tool in {@link Kit}, keyed by tool name.
43
+ * The handler for each tool in {@link Kit}, keyed by tool name. The seven
44
+ * action-keyed tools are `Tool.dynamic` (a union root cannot be a `Tool.make`
45
+ * parameters schema), so their handlers take the raw payload and decode it
46
+ * through `McpToolkit.unionHandler`.
43
47
  *
44
48
  * @public
45
49
  */
@@ -62,15 +66,15 @@ const toolHandlers = {
62
66
  acceptance_metrics: handleAcceptanceMetrics,
63
67
  triage_brief: handleTriageBrief,
64
68
  wrapup_prompt: handleWrapupPrompt,
65
- inventory: handleInventory,
66
- test: handleTest,
69
+ inventory: McpToolkit.unionHandler(inventoryTool, handleInventory),
70
+ test: McpToolkit.unionHandler(testTool, handleTest),
67
71
  register_agent: handleRegisterAgent,
68
- note: handleNote,
69
- hypothesis: withIdempotency("hypothesis", handleHypothesis),
70
- tdd_task: withIdempotency("tdd_task", handleTddTask),
72
+ note: McpToolkit.unionHandler(noteTool, handleNote),
73
+ hypothesis: McpToolkit.unionHandler(hypothesisTool, withIdempotency("hypothesis", handleHypothesis)),
74
+ tdd_task: McpToolkit.unionHandler(tddTaskTool, withIdempotency("tdd_task", handleTddTask)),
71
75
  tdd_phase_transition_request: handlePhaseTransitionRequest,
72
- tdd_goal: withIdempotency("tdd_goal", handleTddGoal),
73
- tdd_behavior: withIdempotency("tdd_behavior", handleTddBehavior),
76
+ tdd_goal: McpToolkit.unionHandler(tddGoalTool, withIdempotency("tdd_goal", handleTddGoal)),
77
+ tdd_behavior: McpToolkit.unionHandler(tddBehaviorTool, withIdempotency("tdd_behavior", handleTddBehavior)),
74
78
  tdd_artifact_list: handleTddArtifactList,
75
79
  tdd_progress_push: handleTddProgressPush,
76
80
  run_tests: handleRunTests
@@ -2,15 +2,6 @@ import { Effect } from "effect";
2
2
  import { BehaviorNotFoundError, GoalNotFoundError, IllegalStatusTransitionError, TddTaskAlreadyEndedError, TddTaskNotFoundError } from "@vitest-agent/sdk";
3
3
 
4
4
  //#region src/tools/_tdd-error-envelope.ts
5
- /**
6
- * Shared error-to-success-shape envelope conversion for goal / behavior CRUD
7
- * tools. Maps each tagged TDD error to an ok:false response matching the
8
- * tdd_phase_transition_request accept/deny envelope shape, so the agent
9
- * sees a normal tool response instead of a transport error.
10
- *
11
- * `Effect.catchTags` returns `Effect.succeed(envelope)` so the success
12
- * channel carries the union `Success | ErrorEnvelope`.
13
- */
14
5
  const goalNotFound = (e) => ({
15
6
  ok: false,
16
7
  error: {
@@ -20,7 +11,7 @@ const goalNotFound = (e) => ({
20
11
  remediation: {
21
12
  suggestedTool: "tdd_goal",
22
13
  suggestedArgs: { action: "list" },
23
- humanHint: `No tdd_session_goals row with id=${e.id}. Call tdd_goal({ action: "list", tddTaskId }) to find the correct goal id.`
14
+ hint: `No tdd_session_goals row with id=${e.id}. Call tdd_goal({ action: "list", tddTaskId }) to find the correct goal id.`
24
15
  }
25
16
  }
26
17
  });
@@ -33,7 +24,7 @@ const behaviorNotFound = (e) => ({
33
24
  remediation: {
34
25
  suggestedTool: "tdd_behavior",
35
26
  suggestedArgs: { action: "list_by_goal" },
36
- humanHint: `No tdd_session_behaviors row with id=${e.id}. Call tdd_behavior({ action: "list_by_goal", goalId }) or tdd_behavior({ action: "list_by_tdd_task", tddTaskId }) to find the correct behavior id.`
27
+ hint: `No tdd_session_behaviors row with id=${e.id}. Call tdd_behavior({ action: "list_by_goal", goalId }) or tdd_behavior({ action: "list_by_tdd_task", tddTaskId }) to find the correct behavior id.`
37
28
  }
38
29
  }
39
30
  });
@@ -46,7 +37,7 @@ const tddTaskNotFound = (e) => ({
46
37
  remediation: {
47
38
  suggestedTool: "tdd_task",
48
39
  suggestedArgs: { action: "start" },
49
- humanHint: `No tdd_tasks row with id=${e.id}. Call tdd_task({ action: "start" }) to open a TDD task before creating goals or behaviors.`
40
+ hint: `No tdd_tasks row with id=${e.id}. Call tdd_task({ action: "start" }) to open a TDD task before creating goals or behaviors.`
50
41
  }
51
42
  }
52
43
  });
@@ -60,7 +51,7 @@ const tddTaskAlreadyEnded = (e) => ({
60
51
  remediation: {
61
52
  suggestedTool: "tdd_task",
62
53
  suggestedArgs: { action: "start" },
63
- humanHint: `tdd_tasks row id=${e.id} is already ended (outcome=${e.outcome}). Open a new TDD task if you need to add more goals or behaviors.`
54
+ hint: `tdd_tasks row id=${e.id} is already ended (outcome=${e.outcome}). Open a new TDD task if you need to add more goals or behaviors.`
64
55
  }
65
56
  }
66
57
  });
@@ -80,7 +71,7 @@ const illegalStatusTransition = (e) => ({
80
71
  id: e.id,
81
72
  status: "abandoned"
82
73
  },
83
- humanHint: `Cannot transition ${e.entity} id=${e.id} from ${e.from} to ${e.to}. Use status:'abandoned' to drop work; do not delete unless the entity was created by mistake.`
74
+ hint: `Cannot transition ${e.entity} id=${e.id} from ${e.from} to ${e.to}. Use status:'abandoned' to drop work; do not delete unless the entity was created by mistake.`
84
75
  }
85
76
  }
86
77
  });
@@ -1,3 +1,4 @@
1
+ import { ToolOutputSchema } from "@effected/mcp";
1
2
  import { Effect, Option, Schema } from "effect";
2
3
  import { DataReader } from "@vitest-agent/engine";
3
4
  import { Tool } from "effect/unstable/ai";
@@ -22,7 +23,7 @@ const ManifestAbsent = Schema.Struct({ manifestPresent: Schema.Literal(false).an
22
23
  *
23
24
  * @public
24
25
  */
25
- const CacheHealthResult = Schema.Union([ManifestPresent, ManifestAbsent]).annotate({
26
+ const CacheHealthResult = ToolOutputSchema.objectRooted(Schema.Union([ManifestPresent, ManifestAbsent])).annotate({
26
27
  identifier: "CacheHealthResult",
27
28
  title: "cache_health result",
28
29
  description: "Cache health snapshot. Discriminate on `manifestPresent` to see whether the manifest exists."
@@ -1,3 +1,4 @@
1
+ import { ToolOutputSchema } from "@effected/mcp";
1
2
  import { Effect, Option, Schema } from "effect";
2
3
  import { DataReader } from "@vitest-agent/engine";
3
4
  import { Tool } from "effect/unstable/ai";
@@ -35,7 +36,7 @@ const SettingsAbsent = Schema.Struct({
35
36
  *
36
37
  * @public
37
38
  */
38
- const ConfigureResult = Schema.Union([SettingsFound, SettingsAbsent]).annotate({
39
+ const ConfigureResult = ToolOutputSchema.objectRooted(Schema.Union([SettingsFound, SettingsAbsent])).annotate({
39
40
  identifier: "ConfigureResult",
40
41
  title: "configure result",
41
42
  description: "Captured Vitest settings for a run, or an absence record when the lookup found nothing."
package/tools/coverage.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { ToolOutputSchema } from "@effected/mcp";
1
2
  import { Effect, Option, Schema } from "effect";
2
3
  import { DataReader } from "@vitest-agent/engine";
3
4
  import { Tool } from "effect/unstable/ai";
@@ -18,7 +19,7 @@ const CoverageAbsent = Schema.Struct({
18
19
  *
19
20
  * @public
20
21
  */
21
- const TestCoverageResult = Schema.Union([CoverageAvailable, CoverageAbsent]).annotate({
22
+ const TestCoverageResult = ToolOutputSchema.objectRooted(Schema.Union([CoverageAvailable, CoverageAbsent])).annotate({
22
23
  identifier: "TestCoverageResult",
23
24
  title: "test_coverage result",
24
25
  description: "Per-project coverage report. Discriminate on `dataAvailable` for cold-start handling."
@@ -1,3 +1,4 @@
1
+ import { ToolOutputSchema } from "@effected/mcp";
1
2
  import { Effect, Option, Schema } from "effect";
2
3
  import { DataReader } from "@vitest-agent/engine";
3
4
  import { Tool } from "effect/unstable/ai";
@@ -29,7 +30,7 @@ const SignatureMissing = Schema.Struct({
29
30
  *
30
31
  * @public
31
32
  */
32
- const FailureSignatureGetResult = Schema.Union([SignatureFound, SignatureMissing]).annotate({
33
+ const FailureSignatureGetResult = ToolOutputSchema.objectRooted(Schema.Union([SignatureFound, SignatureMissing])).annotate({
33
34
  identifier: "FailureSignatureGetResult",
34
35
  title: "failure_signature_get result",
35
36
  description: "Discriminate on `found`. Found rows carry first/last-seen timestamps and recent occurrences."
@@ -1,3 +1,4 @@
1
+ import { ToolOutputSchema } from "@effected/mcp";
1
2
  import { Effect, Option, Schema } from "effect";
2
3
  import { DataReader } from "@vitest-agent/engine";
3
4
  import { Tool } from "effect/unstable/ai";
@@ -34,11 +35,11 @@ const FileCoverageAbsent = Schema.Struct({
34
35
  *
35
36
  * @public
36
37
  */
37
- const FileCoverageResult = Schema.Union([
38
+ const FileCoverageResult = ToolOutputSchema.objectRooted(Schema.Union([
38
39
  FileCoverageMatched,
39
40
  FileCoverageNoMatch,
40
41
  FileCoverageAbsent
41
- ]).annotate({
42
+ ])).annotate({
42
43
  identifier: "FileCoverageResult",
43
44
  title: "file_coverage result",
44
45
  description: "Per-file coverage with related tests. Discriminate on `dataAvailable` then on `matched`."