@vitest-agent/mcp 4.1.1 → 4.2.1

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.d.ts CHANGED
@@ -166,7 +166,7 @@ export declare const PromptsLayer: Layer.Layer<never, never, McpSession>;
166
166
  *
167
167
  * @public
168
168
  */
169
- export declare const registerStrictToolkit: <Tools extends Record<string, Tool.Any>>(toolkit: Toolkit.Toolkit<Tools>) => Layer.Layer<never, never, Tool.HandlersFor<Tools> | Exclude<Tool.HandlerServices<Tools>, McpSchema.McpServerClient>>;
169
+ export declare const registerStrictToolkit: <Tools extends Record<string, Tool.Any>>(toolkit: Toolkit.Toolkit<Tools>) => Layer.Layer<never, never, Tool.HandlersFor<Tools> | Exclude<Tool.HandlerServices<Tools>, McpSchema.McpRequestContext>>;
170
170
  //#endregion
171
171
  //#region src/server.d.ts
172
172
  /**
@@ -184,10 +184,33 @@ interface ServerLayerOptions {
184
184
  /** The `serverInfo.version` advertised on initialize (the package version in production). */
185
185
  readonly version: string;
186
186
  }
187
+ /**
188
+ * The agent-facing orientation every client receives: in the `initialize`
189
+ * result on the stateful protocols and in the `server/discover` result on
190
+ * `2026-07-28`. Says what the server is for, which tool to reach for first,
191
+ * and how to read a result; the one-line human summary stays on
192
+ * `serverInfo.description`.
193
+ *
194
+ * @public
195
+ */
196
+ export declare const SERVER_INSTRUCTIONS: string;
187
197
  /**
188
198
  * The server layer: every tool registered under the strict contract, plus
189
- * the six framing prompts, over `McpServer.layerStdio`. `protocols` is newest-first because the registry
190
- * falls back to `protocols[0]` for a client offering an unknown version.
199
+ * the six framing prompts, over `McpServer.layerStdio`.
200
+ *
201
+ * `protocols` order is load-bearing (rc.116 `unstable/ai/internal/mcpRuntime.ts`):
202
+ * a request carrying `_meta["io.modelcontextprotocol/protocolVersion"]`
203
+ * routes to that adapter, an `initialize` matches the STATEFUL adapters
204
+ * only, and anything else with no session falls to `protocols[0]`. The
205
+ * stateless `2026-07-28` adapter (SEP-2575: no handshake, no session,
206
+ * `server/discover` instead of `initialize`) is listed first, then the two
207
+ * newest stateful ones — every shipping client (Claude Code's default stdio
208
+ * session, Copilot, Cursor, the Inspector) still opens with `initialize`,
209
+ * which a server offering ONLY `2026-07-28` answers with
210
+ * `METHOD_NOT_FOUND`. At most one stateless adapter is allowed; a second
211
+ * fails the layer with `Cause.IllegalArgumentError`, which is why the
212
+ * error channel is `orDie`d: `protocols` is a static literal, so a failure
213
+ * there is an implementer-time defect.
191
214
  *
192
215
  * @param options - the advertised server version
193
216
  * @public
package/index.js CHANGED
@@ -30,7 +30,7 @@ import { TriageBriefInput, TriageBriefResult } from "./tools/triage-brief.js";
30
30
  import { TurnSearchInput, TurnSearchResult } from "./tools/turn-search.js";
31
31
  import { WrapupPromptInput, WrapupPromptResult } from "./tools/wrapup-prompt.js";
32
32
  import { Kit, ToolsLayer, toolHandlers } from "./toolkit.js";
33
- import { ServerLayer } from "./server.js";
33
+ import { SERVER_INSTRUCTIONS, ServerLayer } from "./server.js";
34
34
  import { CURRENT_MCP_VERSION } from "./version.js";
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, 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, ToolsLayer, TriageBriefInput, TriageBriefResult, TurnSearchInput, TurnSearchResult, WrapupPromptInput, WrapupPromptResult, createCurrentSessionIdRef, createSessionContextRef, registerStrictToolkit, sessionContextFromEnv, toolHandlers, withIdempotency };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitest-agent/mcp",
3
- "version": "4.1.1",
3
+ "version": "4.2.1",
4
4
  "private": false,
5
5
  "description": "Model Context Protocol server for vitest-agent. Exposes 53 tools for agent access to test data, TDD lifecycle, and session management.",
6
6
  "keywords": [
@@ -43,11 +43,11 @@
43
43
  "vitest-agent-mcp": "bin/vitest-agent-mcp.js"
44
44
  },
45
45
  "dependencies": {
46
- "@effect/platform-node": "4.0.0-rc.115",
47
- "@effect/sql-sqlite-node": "4.0.0-rc.115",
48
- "@vitest-agent/engine": "0.2.1",
49
- "@vitest-agent/sdk": "5.1.0",
50
- "effect": "4.0.0-rc.115"
46
+ "@effect/platform-node": "4.0.0-rc.116",
47
+ "@effect/sql-sqlite-node": "4.0.0-rc.116",
48
+ "@vitest-agent/engine": "0.2.3",
49
+ "@vitest-agent/sdk": "5.1.1",
50
+ "effect": "4.0.0-rc.116"
51
51
  },
52
52
  "peerDependencies": {
53
53
  "vitest": "^5.0.0"
@@ -1,7 +1,8 @@
1
1
  import { RenderText } from "./annotations.js";
2
2
  import { buildUnexpectedToolErrorEnvelope } from "./utils/tool-error-envelope.js";
3
- import { Cause, Context, Effect, Layer, Option, Result, Schema, Sink, Stream } from "effect";
4
- import { AiError, McpSchema, McpServer, Tool } from "effect/unstable/ai";
3
+ import { Cause, Context, Effect, ErrorReporter, Layer, Option, References, Result, Schema, Stream } from "effect";
4
+ import { AiError, McpSchema, McpServer, Tool, Toolkit } from "effect/unstable/ai";
5
+ import { HttpServerRequest } from "effect/unstable/http";
5
6
 
6
7
  //#region src/register-toolkit.ts
7
8
  const isPlainObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
@@ -214,13 +215,32 @@ const formatUnknownKeys = (levels) => levels.map((level) => {
214
215
  * primitive encoded result is omitted rather than sent through as-is.
215
216
  */
216
217
  const toStructuredContent = (value) => isPlainObject(value) ? value : void 0;
217
- const declaredFailureResult = (message) => new McpSchema.CallToolResult({
218
+ const toolErrorResult = (message) => new McpSchema.CallToolResult({
218
219
  isError: true,
219
220
  content: [{
220
221
  type: "text",
221
222
  text: message
222
223
  }]
223
224
  });
225
+ const toolResultContent = (encoded) => encoded === void 0 ? [] : [{
226
+ type: "text",
227
+ text: JSON.stringify(encoded)
228
+ }];
229
+ const omitRequestServices = Context.omit(McpSchema.McpRequestContext, McpSchema.McpServerClient, HttpServerRequest.HttpServerRequest, References.CurrentLogLevel);
230
+ const isParameterValidationError = (error) => AiError.isAiError(error) && error.reason._tag === "ToolParameterValidationError";
231
+ /**
232
+ * The input JSON Schema document as rc.116's `toolInputJsonSchema` builds it
233
+ * (`Schema.toJsonSchemaDocument` with `onExcessProperty` following the
234
+ * tool's strict mode, definitions attached as `$defs`); the top-level `$ref`
235
+ * hoist happens in `inlineRootRefs` via `strictifyJsonSchema`.
236
+ */
237
+ const toolInputJsonSchema = (schema, strict) => {
238
+ const document = Schema.toJsonSchemaDocument(schema, { onExcessProperty: strict ? "error" : "ignore" });
239
+ return Object.keys(document.definitions).length === 0 ? document.schema : {
240
+ ...document.schema,
241
+ $defs: document.definitions
242
+ };
243
+ };
224
244
  const envelopeResult = (toolName, err) => {
225
245
  const envelope = buildUnexpectedToolErrorEnvelope(toolName, err);
226
246
  return new McpSchema.CallToolResult({
@@ -234,26 +254,64 @@ const envelopeResult = (toolName, err) => {
234
254
  };
235
255
  /**
236
256
  * Register every tool of `toolkit` with the ambient `McpServer` under the
237
- * strict contract described in the module docs.
257
+ * strict contract described in the module docs. A port of rc.116's
258
+ * `McpServer.registerToolkit`; the deviations are enumerated in the module
259
+ * header.
238
260
  *
239
261
  * @internal
240
262
  */
241
263
  const registerStrictToolkitEffect = Effect.fnUntraced(function* (toolkit) {
242
264
  const registry = yield* McpServer.McpServer;
243
- const built = yield* toolkit;
244
- const services = yield* Effect.context();
265
+ const built = yield* toolkit.pipe(Effect.updateContext((context) => {
266
+ const services = new Map(context.mapUnsafe);
267
+ for (const tool of Object.values(toolkit.tools)) {
268
+ const handler = services.get(tool.id);
269
+ if (handler !== void 0) services.set(tool.id, {
270
+ ...handler,
271
+ context: omitRequestServices(handler.context)
272
+ });
273
+ }
274
+ return Context.makeUnsafe(services);
275
+ }));
276
+ const services = omitRequestServices(yield* Effect.context());
277
+ const reportCause = (cause) => Effect.provideContext(ErrorReporter.report(cause), services);
278
+ const registrations = [];
245
279
  for (const tool of Object.values(built.tools)) {
280
+ const internalToolError = (cause) => {
281
+ const failure = Cause.findFail(cause);
282
+ return Result.isFailure(failure) && !Cause.hasDies(cause) ? Effect.failCause(failure.failure) : Effect.logError(`tool ${tool.name} failed`, cause).pipe(Effect.andThen(reportCause(cause)), Effect.as(envelopeResult(tool.name, Result.isSuccess(failure) ? failure.success.error : Cause.squash(cause))));
283
+ };
284
+ const strict = Tool.getStrictMode(tool) === true;
285
+ const rawJsonSchema = Tool.isDynamic(tool) ? tool.jsonSchema : void 0;
286
+ if (strict && rawJsonSchema !== void 0) return yield* Effect.die(`McpServer cannot strictly validate the raw JSON Schema for tool '${tool.name}'; use an Effect Schema instead`);
287
+ const decodeOptions = strict ? { onExcessProperty: "error" } : void 0;
246
288
  const annotations = tool.annotations;
247
289
  const render = Context.get(annotations, RenderText);
248
- const isDeclaredFailure = Schema.is(tool.failureSchema);
249
290
  const toolMeta = Context.getOrUndefined(annotations, Tool.Meta);
291
+ const isDeclaredFailure = Schema.is(tool.failureSchema);
292
+ const encodeFailure = Schema.encodeUnknownEffect(tool.failureSchema);
293
+ const declaredFailureResult = (error) => error instanceof Error ? Effect.succeed(toolErrorResult(error.message)) : Effect.map(encodeFailure(error), (encoded) => new McpSchema.CallToolResult({
294
+ isError: true,
295
+ content: toolResultContent(encoded)
296
+ }));
297
+ const handleCause = (cause) => {
298
+ const failure = Cause.findFail(cause);
299
+ if (Result.isSuccess(failure)) {
300
+ const error = failure.success.error;
301
+ const origin = Context.get(Cause.reasonAnnotations(failure.success), Toolkit.FailureOrigin);
302
+ if (origin === "parameters" && isParameterValidationError(error)) return Effect.fail(new McpSchema.InvalidParams({ message: error.reason.message }));
303
+ if (origin === "handler" && isDeclaredFailure(error)) return Effect.catchCause(declaredFailureResult(error), internalToolError);
304
+ }
305
+ return internalToolError(cause);
306
+ };
250
307
  const outputJsonSchema = inlineRootRefs(Tool.getJsonSchemaFromSchema(tool.successSchema));
251
- const outputSchema = outputJsonSchema.type === "object" ? yield* Schema.decodeUnknownEffect(McpSchema.ToolJsonSchema)(outputJsonSchema).pipe(Effect.orDie) : void 0;
252
- const servedInput = strictifyJsonSchema(Tool.getJsonSchema(tool));
253
- const inputSchema = yield* Schema.decodeUnknownEffect(McpSchema.ToolJsonSchema)(servedInput).pipe(Effect.orDie);
308
+ const outputSchema = outputJsonSchema.type === "object" ? yield* Schema.decodeUnknownEffect(McpSchema.ToolOutputJson)(outputJsonSchema).pipe(Effect.orDie) : void 0;
309
+ const servedInput = strictifyJsonSchema(rawJsonSchema ?? toolInputJsonSchema(tool.parametersSchema, strict));
310
+ const inputSchema = yield* Schema.decodeUnknownEffect(McpSchema.ToolJson)(servedInput).pipe(Effect.orDie);
311
+ const description = Tool.getDescription(tool);
254
312
  const mcpTool = new McpSchema.Tool({
255
313
  name: tool.name,
256
- description: Tool.getDescription(tool),
314
+ ...description === void 0 ? {} : { description },
257
315
  inputSchema,
258
316
  ...outputSchema === void 0 ? {} : { outputSchema },
259
317
  annotations: {
@@ -263,38 +321,27 @@ const registerStrictToolkitEffect = Effect.fnUntraced(function* (toolkit) {
263
321
  idempotentHint: Context.get(annotations, Tool.Idempotent),
264
322
  openWorldHint: Context.get(annotations, Tool.OpenWorld)
265
323
  },
266
- _meta: toolMeta
324
+ ...toolMeta === void 0 ? {} : { _meta: toolMeta }
267
325
  });
268
- yield* registry.addTool({
326
+ registrations.push({
269
327
  tool: mcpTool,
270
328
  annotations,
271
329
  handle(payload) {
272
330
  const raw = payload ?? {};
273
331
  const unknownKeys = collectUnknownKeys(raw, servedInput);
274
332
  if (unknownKeys.length > 0) return Effect.fail(new McpSchema.InvalidParams({ message: formatUnknownKeys(unknownKeys) }));
275
- return built.handle(tool.name, raw).pipe(Stream.unwrap, Stream.run(Sink.last()), Effect.flatMap(Effect.fromOption), Effect.map((result) => {
276
- const encoded = result.encodedResult;
277
- const text = render?.(encoded) ?? JSON.stringify(encoded);
278
- return new McpSchema.CallToolResult({
279
- isError: false,
280
- structuredContent: toStructuredContent(encoded),
281
- content: encoded === void 0 ? [] : [{
282
- type: "text",
283
- text
284
- }]
285
- });
286
- }), Effect.provideContext(services), Effect.catchCause((cause) => {
287
- const failure = Cause.findError(cause);
288
- if (Result.isFailure(failure) && !Cause.hasDies(cause)) return Effect.failCause(failure.failure);
289
- const err = Result.isSuccess(failure) ? failure.success : Cause.squash(cause);
290
- if (AiError.isAiError(err) && err.reason._tag === "ToolParameterValidationError") return Effect.fail(new McpSchema.InvalidParams({ message: err.reason.message }));
291
- const logged = Effect.logError(`tool ${tool.name} failed`, cause);
292
- if (isDeclaredFailure(err) && err instanceof Error) return Effect.as(logged, declaredFailureResult(err.message));
293
- return Effect.as(logged, envelopeResult(tool.name, err));
294
- }));
333
+ return built.handle(tool.name, raw, void 0, decodeOptions).pipe(Stream.unwrap, Stream.runLast, Effect.flatMap(Effect.fromOption), Effect.flatMap((result) => result.isFailure && result.failureOrigin !== "handler" ? Effect.failCause(Cause.annotate(Cause.fail(result.result), Context.make(Toolkit.FailureOrigin, result.failureOrigin ?? "result"))) : Effect.succeed(new McpSchema.CallToolResult({
334
+ isError: result.isFailure,
335
+ structuredContent: result.isFailure ? void 0 : toStructuredContent(result.encodedResult),
336
+ content: result.isFailure || result.encodedResult === void 0 ? toolResultContent(result.encodedResult) : [{
337
+ type: "text",
338
+ text: render?.(result.encodedResult) ?? JSON.stringify(result.encodedResult)
339
+ }]
340
+ }))), Effect.catchCause(handleCause), Effect.provideContext(services));
295
341
  }
296
342
  });
297
343
  }
344
+ for (const registration of registrations) yield* registry.addTool(registration);
298
345
  });
299
346
  /**
300
347
  * A layer that registers `toolkit` with the strict contract. Same shape
package/server.js CHANGED
@@ -5,16 +5,42 @@ import { Layer, Logger } from "effect";
5
5
  import { McpProtocol, McpServer } from "effect/unstable/ai";
6
6
 
7
7
  //#region src/server.ts
8
+ /** One-line human summary agents see on `serverInfo.description`. */
9
+ const DESCRIPTION = "vitest-agent MCP server: test results, coverage, history and TDD lifecycle for LLM coding agents.";
8
10
  /**
9
- * One-line pointer agents see on `serverInfo.description`. `instructions`
10
- * cannot be set through `McpServer.layerStdio` at rc.115, so this is the
11
- * at-initialize orientation hook.
11
+ * The agent-facing orientation every client receives: in the `initialize`
12
+ * result on the stateful protocols and in the `server/discover` result on
13
+ * `2026-07-28`. Says what the server is for, which tool to reach for first,
14
+ * and how to read a result; the one-line human summary stays on
15
+ * `serverInfo.description`.
16
+ *
17
+ * @public
12
18
  */
13
- const DESCRIPTION = "vitest-agent MCP server: test results, coverage, history and TDD lifecycle for LLM coding agents. Call the `help` tool first for the full tool reference.";
19
+ const SERVER_INSTRUCTIONS = [
20
+ "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
+ "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
+ "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 a text rendering (markdown where the tool renders one, otherwise the same JSON) in content[0].text; read structuredContent when you need fields.",
24
+ "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."
26
+ ].join("\n");
14
27
  /**
15
28
  * The server layer: every tool registered under the strict contract, plus
16
- * the six framing prompts, over `McpServer.layerStdio`. `protocols` is newest-first because the registry
17
- * falls back to `protocols[0]` for a client offering an unknown version.
29
+ * the six framing prompts, over `McpServer.layerStdio`.
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.
18
44
  *
19
45
  * @param options - the advertised server version
20
46
  * @public
@@ -23,12 +49,13 @@ const ServerLayer = (options) => Layer.mergeAll(registerStrictToolkit(Kit).pipe(
23
49
  name: "vitest-agent",
24
50
  version: options.version,
25
51
  description: DESCRIPTION,
52
+ instructions: SERVER_INSTRUCTIONS,
26
53
  protocols: [
54
+ McpProtocol.v2026_07_28,
27
55
  McpProtocol.v2025_11_25,
28
- McpProtocol.v2025_06_18,
29
- McpProtocol.v2025_03_26
56
+ McpProtocol.v2025_06_18
30
57
  ]
31
58
  })), Layer.provide(Layer.succeed(Logger.LogToStderr, true)), Layer.orDie);
32
59
 
33
60
  //#endregion
34
- export { ServerLayer };
61
+ export { SERVER_INSTRUCTIONS, ServerLayer };
package/version.js CHANGED
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * @public
9
9
  */
10
- const CURRENT_MCP_VERSION = "4.1.1";
10
+ const CURRENT_MCP_VERSION = "4.2.1";
11
11
 
12
12
  //#endregion
13
13
  export { CURRENT_MCP_VERSION };