@vitest-agent/mcp 2.0.13 → 2.1.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
@@ -12,7 +12,7 @@ import { parseSessionEnvExports, recoverSessionContextFromSessionEnv } from "./s
12
12
  *
13
13
  * @public
14
14
  */
15
- const CURRENT_MCP_VERSION = "2.0.13";
15
+ const CURRENT_MCP_VERSION = "2.1.0";
16
16
 
17
17
  //#endregion
18
18
  export { CURRENT_MCP_VERSION, McpLive, appRouter, buildMcpServer, createCallerFactory, createCurrentSessionIdRef, createSessionContextRef, parseSessionEnvExports, recoverSessionContextFromSessionEnv, startMcpServer };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitest-agent/mcp",
3
- "version": "2.0.13",
3
+ "version": "2.1.0",
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": [
@@ -38,12 +38,12 @@
38
38
  "vitest-agent-mcp": "bin/vitest-agent-mcp.js"
39
39
  },
40
40
  "dependencies": {
41
- "@effect/platform-node": "4.0.0-beta.101",
42
- "@effect/sql-sqlite-node": "4.0.0-beta.101",
41
+ "@effect/platform-node": "4.0.0-beta.107",
42
+ "@effect/sql-sqlite-node": "4.0.0-beta.107",
43
43
  "@modelcontextprotocol/sdk": "^1.30.0",
44
44
  "@trpc/server": "^11.18.0",
45
- "@vitest-agent/sdk": "2.0.13",
46
- "effect": "4.0.0-beta.101",
45
+ "@vitest-agent/sdk": "2.0.15",
46
+ "effect": "4.0.0-beta.107",
47
47
  "zod": "^4.4.3"
48
48
  },
49
49
  "peerDependencies": {
@@ -17,33 +17,33 @@ const totalAnnotation = { description: "Sample size — number of observations t
17
17
  const ratioAnnotation = { description: "Compliance ratio in [0, 1]. Multiply by 100 for the percentage form rendered in the markdown view." };
18
18
  const AcceptanceMetricsResult = Schema.Struct({
19
19
  phaseEvidenceIntegrity: Schema.Struct({
20
- total: Schema.Number.annotate(totalAnnotation),
21
- compliant: Schema.Number.annotate({ description: "Phase transitions that cited a valid artifact and passed binding-rule validation." }),
22
- ratio: Schema.Number.annotate(ratioAnnotation)
20
+ total: Schema.Finite.annotate(totalAnnotation),
21
+ compliant: Schema.Finite.annotate({ description: "Phase transitions that cited a valid artifact and passed binding-rule validation." }),
22
+ ratio: Schema.Finite.annotate(ratioAnnotation)
23
23
  }).annotate({
24
24
  title: "Phase-evidence integrity",
25
25
  description: "Fraction of accepted TDD phase transitions whose cited artifact satisfied the D2 binding rules. Spec target ≥80%."
26
26
  }),
27
27
  complianceHookResponsiveness: Schema.Struct({
28
- total: Schema.Number.annotate(totalAnnotation),
29
- withFollowup: Schema.Number.annotate({ description: "PreToolUse denials / `additionalContext` reminders the orchestrator acknowledged in the next turn." }),
30
- ratio: Schema.Number.annotate(ratioAnnotation)
28
+ total: Schema.Finite.annotate(totalAnnotation),
29
+ withFollowup: Schema.Finite.annotate({ description: "PreToolUse denials / `additionalContext` reminders the orchestrator acknowledged in the next turn." }),
30
+ ratio: Schema.Finite.annotate(ratioAnnotation)
31
31
  }).annotate({
32
32
  title: "Compliance-hook responsiveness",
33
33
  description: "Fraction of compliance signals from PreToolUse hooks the orchestrator acted on. Spec target ≥40%."
34
34
  }),
35
35
  orientationUsefulness: Schema.Struct({
36
- total: Schema.Number.annotate(totalAnnotation),
37
- referencedCount: Schema.Number.annotate({ description: "Sessions where `triage_brief` / `wrapup_prompt` content was referenced in subsequent decisions." }),
38
- ratio: Schema.Number.annotate(ratioAnnotation)
36
+ total: Schema.Finite.annotate(totalAnnotation),
37
+ referencedCount: Schema.Finite.annotate({ description: "Sessions where `triage_brief` / `wrapup_prompt` content was referenced in subsequent decisions." }),
38
+ ratio: Schema.Finite.annotate(ratioAnnotation)
39
39
  }).annotate({
40
40
  title: "Orientation usefulness",
41
41
  description: "Fraction of sessions where orientation prompts measurably steered orchestrator behaviour. Spec target ≥50%."
42
42
  }),
43
43
  antiPatternDetectionRate: Schema.Struct({
44
- total: Schema.Number.annotate(totalAnnotation),
45
- cleanSessions: Schema.Number.annotate({ description: "Sessions that produced no `tdd_artifacts(kind='test_weakened')` rows or DATABASE_BYPASS notes." }),
46
- ratio: Schema.Number.annotate(ratioAnnotation)
44
+ total: Schema.Finite.annotate(totalAnnotation),
45
+ cleanSessions: Schema.Finite.annotate({ description: "Sessions that produced no `tdd_artifacts(kind='test_weakened')` rows or DATABASE_BYPASS notes." }),
46
+ ratio: Schema.Finite.annotate(ratioAnnotation)
47
47
  }).annotate({
48
48
  title: "Anti-pattern detection rate",
49
49
  description: "Fraction of sessions free of weakening edits or sqlite3 bypass attempts. Spec target ≥95%."
@@ -18,7 +18,7 @@ import { Effect, Option, Schema, SchemaGetter } from "effect";
18
18
  const ManifestPresent = Schema.Struct({
19
19
  manifestPresent: Schema.Literal(true).annotate({ description: "Discriminant — `true` when a cache manifest exists." }),
20
20
  manifest: CacheManifest.annotate({ description: "Full cache manifest content as written by the reporter." }),
21
- ageMs: Schema.Number.annotate({ description: "Milliseconds since the manifest was last updated. Computed at query time, not stored." }),
21
+ ageMs: Schema.Finite.annotate({ description: "Milliseconds since the manifest was last updated. Computed at query time, not stored." }),
22
22
  stale: Schema.Boolean.annotate({ description: "Convenience flag — `true` when `ageMs` exceeds 24 hours, otherwise `false`." })
23
23
  }).annotate({
24
24
  identifier: "CacheHealthPresent",
@@ -29,7 +29,7 @@ const CommitRow = Schema.Struct({
29
29
  }).annotate({ identifier: "CommitRow" });
30
30
  const CommitChangesResult = Schema.Struct({
31
31
  filterSha: Schema.optional(Schema.String).annotate({ description: "Echo of the optional `sha` filter the caller passed; absent when no filter was applied (recent commits returned)." }),
32
- count: Schema.Number.annotate({ description: "Number of commit rows returned." }),
32
+ count: Schema.Finite.annotate({ description: "Number of commit rows returned." }),
33
33
  commits: Schema.Array(CommitRow).annotate({ description: "Matching commits, newest first when `sha` was omitted; up to 20 rows." })
34
34
  }).annotate({
35
35
  identifier: "CommitChangesResult",
package/tools/errors.js CHANGED
@@ -22,7 +22,7 @@ import { Effect, Schema, SchemaGetter } from "effect";
22
22
  */
23
23
  /** One row in the structured `errors[]` array. */
24
24
  const TestErrorRow = Schema.Struct({
25
- id: Schema.Number.annotate({
25
+ id: Schema.Finite.annotate({
26
26
  title: "test_errors.id",
27
27
  description: "Numeric primary key of this error row. Pass as `citedTestErrorId` when calling `hypothesis (action: record)`."
28
28
  }),
@@ -57,7 +57,7 @@ const TestErrorsResult = Schema.Struct({
57
57
  examples: ["playground", "@org/pkg"]
58
58
  }),
59
59
  errorName: Schema.optional(Schema.String).annotate({ description: "Echo of the optional `errorName` filter the caller passed; absent when no filter was applied." }),
60
- count: Schema.Number.annotate({ description: "Total error rows in `errors`." }),
60
+ count: Schema.Finite.annotate({ description: "Total error rows in `errors`." }),
61
61
  errors: Schema.Array(TestErrorRow).annotate({ description: "Errors from the most recent test run for this project, optionally filtered by `errorName`. Empty when no errors matched." })
62
62
  }).annotate({
63
63
  identifier: "TestErrorsResult",
@@ -22,7 +22,7 @@ const SignatureFound = Schema.Struct({
22
22
  firstSeenRunId: Schema.NullOr(Schema.Number),
23
23
  firstSeenAt: Schema.String,
24
24
  lastSeenAt: Schema.NullOr(Schema.String),
25
- occurrenceCount: Schema.Number.annotate({ description: "Total times this signature has been observed." }),
25
+ occurrenceCount: Schema.Finite.annotate({ description: "Total times this signature has been observed." }),
26
26
  recentErrors: Schema.Array(RecentError)
27
27
  });
28
28
  const SignatureMissing = Schema.Struct({
package/tools/history.js CHANGED
@@ -16,8 +16,8 @@ const FlakyTestRow = Schema.Struct({
16
16
  fullName: Schema.String.annotate({ description: "Full hierarchical test name (`describe > it`)." }),
17
17
  modulePath: Schema.String.annotate({ description: "Project-relative test module path -- disambiguates same-named tests across files." }),
18
18
  project: Schema.String,
19
- passCount: Schema.Number.annotate({ description: "Number of passing runs in the recent window." }),
20
- failCount: Schema.Number.annotate({ description: "Number of failing runs in the recent window." }),
19
+ passCount: Schema.Finite.annotate({ description: "Number of passing runs in the recent window." }),
20
+ failCount: Schema.Finite.annotate({ description: "Number of failing runs in the recent window." }),
21
21
  lastState: Schema.Literals(["passed", "failed"]).annotate({ description: "State of the most recent run." }),
22
22
  lastTimestamp: Schema.String.annotate({ description: "ISO-8601 timestamp of the most recent run." })
23
23
  }).annotate({
@@ -28,7 +28,7 @@ const PersistentFailureRow = Schema.Struct({
28
28
  fullName: Schema.String,
29
29
  modulePath: Schema.String.annotate({ description: "Project-relative test module path -- disambiguates same-named tests across files." }),
30
30
  project: Schema.String,
31
- consecutiveFailures: Schema.Number.annotate({ description: "Length of the current uninterrupted failure streak." }),
31
+ consecutiveFailures: Schema.Finite.annotate({ description: "Length of the current uninterrupted failure streak." }),
32
32
  firstFailedAt: Schema.String.annotate({ description: "ISO-8601 timestamp of the first failure in this streak." }),
33
33
  lastFailedAt: Schema.String.annotate({ description: "ISO-8601 timestamp of the most recent failure." }),
34
34
  lastErrorMessage: Schema.NullOr(Schema.String).annotate({ description: "Last error message reported by the failing test, when captured." })
@@ -28,7 +28,7 @@ const HypothesisRowSchema = Schema.Struct({
28
28
  }).annotate({ identifier: "HypothesisRow" });
29
29
  const HypothesisRecordOk = Schema.Struct({
30
30
  action: Schema.Literal("record"),
31
- id: Schema.Number.annotate({ description: "Newly inserted hypothesis row primary key." })
31
+ id: Schema.Finite.annotate({ description: "Newly inserted hypothesis row primary key." })
32
32
  });
33
33
  const HypothesisValidateOk = Schema.Struct({ action: Schema.Literal("validate") });
34
34
  const HypothesisListOk = Schema.Struct({
package/tools/note.js CHANGED
@@ -12,7 +12,7 @@ const NoteScope = Schema.Literals([
12
12
  "note"
13
13
  ]);
14
14
  const NoteRowSchema = Schema.Struct({
15
- id: Schema.Number.annotate({ description: "Note primary key." }),
15
+ id: Schema.Finite.annotate({ description: "Note primary key." }),
16
16
  title: Schema.String,
17
17
  content: Schema.String,
18
18
  scope: NoteScope.annotate({ description: "`global` (project-agnostic), `project`, `module`, `suite`, `test` (scoped), or `note` (child note attached via `parentNoteId`)." }),
@@ -28,7 +28,7 @@ const NoteRowSchema = Schema.Struct({
28
28
  }).annotate({ identifier: "NoteRow" });
29
29
  const NoteCreateOk = Schema.Struct({
30
30
  action: Schema.Literal("create"),
31
- id: Schema.Number.annotate({ description: "Primary key of the newly inserted note." })
31
+ id: Schema.Finite.annotate({ description: "Primary key of the newly inserted note." })
32
32
  });
33
33
  const NoteListOk = Schema.Struct({
34
34
  action: Schema.Literal("list"),
@@ -24,7 +24,7 @@ const ArtifactKindSchema = Schema.Literals([
24
24
  "test_weakened"
25
25
  ]);
26
26
  const TddArtifactRow = Schema.Struct({
27
- id: Schema.Number.annotate({
27
+ id: Schema.Finite.annotate({
28
28
  title: "tdd_artifacts.id",
29
29
  description: "Pass as `citedArtifactId` to `tdd_phase_transition_request`."
30
30
  }),
@@ -46,7 +46,7 @@ const RemediationSchema = Schema.Struct({
46
46
  const PhaseTransitionAccepted = Schema.Struct({
47
47
  accepted: Schema.Literal(true).annotate({ description: "Discriminant — `true` when the transition was granted." }),
48
48
  phase: phaseLiteral.annotate({ description: "Phase the session is now in (echo of `requestedPhase`)." }),
49
- newPhaseId: Schema.Number.annotate({ description: "`tdd_phases.id` of the freshly opened row." }),
49
+ newPhaseId: Schema.Finite.annotate({ description: "`tdd_phases.id` of the freshly opened row." }),
50
50
  previousPhaseId: Schema.NullOr(Schema.Number).annotate({ description: "`tdd_phases.id` of the phase that was closed (`null` for the first transition)." }),
51
51
  citedArtifactId: Schema.optional(Schema.Number).annotate({ description: "Resolved `tdd_artifacts.id` actually used. Absent only for transitions that need no artifact (e.g. spike→red)." }),
52
52
  citedArtifactSource: Schema.optional(Schema.Literals([
@@ -9,9 +9,9 @@ import { Effect, Schema, SchemaGetter } from "effect";
9
9
  * @packageDocumentation
10
10
  */
11
11
  const TurnRow = Schema.Struct({
12
- id: Schema.Number.annotate({ description: "Numeric primary key of this turn row." }),
13
- sessionId: Schema.Number.annotate({ description: "Owning `sessions.id` (integer FK)." }),
14
- turnNo: Schema.Number.annotate({ description: "Turn ordinal within the session (1-based)." }),
12
+ id: Schema.Finite.annotate({ description: "Numeric primary key of this turn row." }),
13
+ sessionId: Schema.Finite.annotate({ description: "Owning `sessions.id` (integer FK)." }),
14
+ turnNo: Schema.Finite.annotate({ description: "Turn ordinal within the session (1-based)." }),
15
15
  type: Schema.String.annotate({ description: "Turn category (`user_prompt`, `tool_call`, `tool_result`, `file_edit`, `hook_fire`, `note`, `hypothesis`)." }),
16
16
  payload: Schema.String.annotate({ description: "Type-specific payload as a JSON-encoded string. Decode shape depends on `type`." }),
17
17
  occurredAt: Schema.String.annotate({ description: "ISO-8601 timestamp the turn was recorded at." })
@@ -20,7 +20,7 @@ const TurnRow = Schema.Struct({
20
20
  description: "One row from the turns log."
21
21
  });
22
22
  const TurnSearchResult = Schema.Struct({
23
- count: Schema.Number.annotate({ description: "Number of matching turn rows returned." }),
23
+ count: Schema.Finite.annotate({ description: "Number of matching turn rows returned." }),
24
24
  turns: Schema.Array(TurnRow).annotate({ description: "Matching turns ordered by `occurredAt` ascending." })
25
25
  }).annotate({
26
26
  identifier: "TurnSearchResult",
@@ -38,6 +38,17 @@ import { z } from "zod";
38
38
  * substitution, then drop `$defs`). The schemas don't use
39
39
  * `Schema.suspend`, so the substitution is acyclic.
40
40
  *
41
+ * Annotation lifting: Effect lowers a checked numeric schema
42
+ * (`Schema.Finite`, `Schema.Int`) to `{ type, allOf: [{ description }] }`
43
+ * rather than putting the annotation on the node itself, and
44
+ * `z.fromJSONSchema` preserves that nesting verbatim. A tool's
45
+ * `outputSchema` would then advertise no description for the field. The
46
+ * bridge therefore lifts annotation-only `allOf` members onto their
47
+ * parent before handing the document to zod. Only the JSON Schema
48
+ * annotation vocabulary is lifted -- a member carrying any constraint
49
+ * keyword (`pattern`, `minimum`, ...) stays put so validation semantics
50
+ * are never changed.
51
+ *
41
52
  * MCP SDK constraint: `outputSchema` must normalise to a Zod object
42
53
  * schema (`normalizeObjectSchema` returns `undefined` for unions, then
43
54
  * `safeParseAsync(undefined, ...)` crashes with "Cannot read properties
@@ -67,6 +78,57 @@ const isObjectLike = (schema) => {
67
78
  };
68
79
  const REF_PREFIX = "#/$defs/";
69
80
  /**
81
+ * The JSON Schema annotation vocabulary -- keywords that describe a
82
+ * schema without constraining what it accepts. Only these are safe to
83
+ * hoist out of an `allOf` member onto the parent node.
84
+ */
85
+ const ANNOTATION_KEYWORDS = /* @__PURE__ */ new Set([
86
+ "title",
87
+ "description",
88
+ "examples",
89
+ "default",
90
+ "deprecated",
91
+ "readOnly",
92
+ "writeOnly",
93
+ "contentEncoding",
94
+ "contentMediaType"
95
+ ]);
96
+ const isPlainObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
97
+ /**
98
+ * Hoist the annotation keywords out of every `allOf` member onto
99
+ * `node`, leaving each member's constraint keywords exactly where they
100
+ * were. A member reduced to nothing is dropped, and `allOf` disappears
101
+ * once every member has been consumed.
102
+ *
103
+ * Keys already present on the node win -- a nested annotation never
104
+ * overwrites a more specific one -- and the first member to supply a
105
+ * given key wins among siblings.
106
+ */
107
+ const liftAnnotations = (node) => {
108
+ const members = node.allOf;
109
+ if (!Array.isArray(members)) return node;
110
+ const kept = [];
111
+ const lifted = {};
112
+ for (const member of members) {
113
+ if (!isPlainObject(member)) {
114
+ kept.push(member);
115
+ continue;
116
+ }
117
+ const constraints = {};
118
+ for (const [key, value] of Object.entries(member)) if (!ANNOTATION_KEYWORDS.has(key)) constraints[key] = value;
119
+ else if (!(key in node) && !(key in lifted)) lifted[key] = value;
120
+ if (Object.keys(constraints).length > 0) kept.push(constraints);
121
+ }
122
+ if (Object.keys(lifted).length === 0) return node;
123
+ const out = {
124
+ ...node,
125
+ ...lifted
126
+ };
127
+ if (kept.length > 0) out.allOf = kept;
128
+ else delete out.allOf;
129
+ return out;
130
+ };
131
+ /**
70
132
  * Walk a JSON Schema tree and replace every `$ref: "#/$defs/X"` node
71
133
  * with the contents of `$defs.X`, recursively. The `$defs` table is
72
134
  * dropped from the returned root.
@@ -87,7 +149,7 @@ const inlineAllRefs = (root) => {
87
149
  if (k === "$defs") continue;
88
150
  out[k] = visit(v);
89
151
  }
90
- return out;
152
+ return liftAnnotations(out);
91
153
  };
92
154
  return visit(root);
93
155
  };