@vitest-agent/mcp 4.3.2 → 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/README.md +5 -4
- package/idempotency.js +1 -1
- package/index.d.ts +2235 -2128
- package/index.js +2 -2
- package/main.d.ts +15 -16
- package/main.js +58 -96
- package/package.json +5 -3
- package/server.js +16 -28
- package/toolkit.js +12 -8
- package/tools/_tdd-error-envelope.js +5 -14
- package/tools/cache-health.js +2 -1
- package/tools/configure.js +2 -1
- package/tools/coverage.js +2 -1
- package/tools/failure-signature-get.js +2 -1
- package/tools/file-coverage.js +3 -2
- package/tools/hypothesis.js +33 -23
- package/tools/inventory.js +6 -5
- package/tools/note.js +6 -5
- package/tools/overview.js +2 -1
- package/tools/ping.js +15 -5
- package/tools/register-agent.js +2 -1
- package/tools/run-tests.js +18 -8
- package/tools/status.js +2 -1
- package/tools/tdd-behavior.js +8 -10
- package/tools/tdd-goal.js +8 -10
- package/tools/tdd-phase-transition-request.js +21 -14
- package/tools/tdd-task.js +25 -13
- package/tools/test.js +6 -5
- package/tools/trends.js +2 -1
- package/tsdoc-metadata.json +1 -1
- package/version.js +1 -1
- package/register-toolkit.js +0 -328
- package/utils/crash-guards.js +0 -13
- package/utils/tool-error-envelope.js +0 -61
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,
|
|
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
|
-
*
|
|
4
|
+
* Options for {@link main}.
|
|
4
5
|
*
|
|
5
|
-
*
|
|
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
|
|
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 {
|
|
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
|
-
*
|
|
61
|
-
*
|
|
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
|
-
* @
|
|
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
|
|
68
|
-
if (
|
|
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
|
|
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 =
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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 {
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
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": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"private": false,
|
|
5
|
-
"description": "Model Context Protocol server for vitest-agent. Exposes
|
|
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
|
-
"@
|
|
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 {
|
|
5
|
-
import {
|
|
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
|
|
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
|
|
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
|
|
29
|
-
*
|
|
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`
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
});
|
package/tools/cache-health.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";
|
|
@@ -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."
|
package/tools/configure.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";
|
|
@@ -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."
|
package/tools/file-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";
|
|
@@ -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`."
|