@savvy-web/mcp 1.6.7 → 1.7.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 +1 -0
- package/package.json +2 -2
- package/server.js +6 -1
- package/tools/biome-check.js +38 -12
- package/tools/changeset-deps-detect.js +3 -1
- package/tools/changeset-deps-regen.js +3 -1
package/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Changesets, SilkWorkspaceAnalyzer, Turbo } from "@savvy-web/silk-effects";
|
|
2
2
|
import { Layer, ManagedRuntime } from "effect";
|
|
3
3
|
import { WorkspaceDiscoveryError, WorkspaceRoot } from "workspaces-effect";
|
|
4
|
+
import "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
5
|
//#region src/context.d.ts
|
|
5
6
|
/** The long-lived runtime and the project working directory. */
|
|
6
7
|
interface McpContext {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@savvy-web/mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "The savvy MCP server — Silk Suite tooling and library knowledge for coding agents",
|
|
6
6
|
"homepage": "https://github.com/savvy-web/systems/tree/main/packages/mcp",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"@effect/sql": "^0.51.1",
|
|
39
39
|
"@effect/workflow": "^0.18.2",
|
|
40
40
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
41
|
-
"@savvy-web/silk-effects": "3.0
|
|
41
|
+
"@savvy-web/silk-effects": "3.2.0",
|
|
42
42
|
"effect": "^3.21.4",
|
|
43
43
|
"workspaces-effect": "^2.0.2",
|
|
44
44
|
"zod": "^4.4.3"
|
package/server.js
CHANGED
|
@@ -100,6 +100,8 @@ function buildServer(ctx) {
|
|
|
100
100
|
inputSchema: {
|
|
101
101
|
base: z.optional(z.string()).describe("Override the base branch used to compute the merge-base."),
|
|
102
102
|
package: z.optional(z.string()).describe("Restrict output to a single workspace package."),
|
|
103
|
+
packages: z.optional(z.array(z.string())).describe("Restrict output to these workspace packages (unioned with package)."),
|
|
104
|
+
exclude: z.optional(z.array(z.string())).describe("Drop these packages from the output entirely."),
|
|
103
105
|
cwd: z.optional(z.string()).describe("Directory to resolve the workspace root from.")
|
|
104
106
|
},
|
|
105
107
|
outputSchema: effectToZodSchema(ChangesetDepsDetectResult),
|
|
@@ -124,6 +126,8 @@ function buildServer(ctx) {
|
|
|
124
126
|
inputSchema: {
|
|
125
127
|
base: z.optional(z.string()).describe("Override the base branch used to compute the merge-base."),
|
|
126
128
|
package: z.optional(z.string()).describe("Restrict regeneration to a single workspace package."),
|
|
129
|
+
packages: z.optional(z.array(z.string())).describe("Restrict regeneration to these workspace packages (unioned with package)."),
|
|
130
|
+
exclude: z.optional(z.array(z.string())).describe("Skip these packages entirely: nothing written, existing changesets untouched."),
|
|
127
131
|
dryRun: z.optional(z.boolean()).describe("Compute the plan without writing or deleting any file."),
|
|
128
132
|
cwd: z.optional(z.string()).describe("Directory to resolve the workspace root from.")
|
|
129
133
|
},
|
|
@@ -138,12 +142,13 @@ function buildServer(ctx) {
|
|
|
138
142
|
return structuredResult(text, data);
|
|
139
143
|
});
|
|
140
144
|
server.registerTool("biome_check", {
|
|
141
|
-
description: "Run Biome over a path and get structured diagnostics back. mode=check (default; lint + format + organize-imports) or mode=lint. Set write=true to apply safe fixes (--write), unsafe=true for unsafe fixes (--write --unsafe). Prefer this over shelling out to biome; the LSP already covers files you've edited. Returns markdown in content[] and a typed object in structuredContent. NOTE: with write/unsafe this tool MUTATES files (git-reversible).",
|
|
145
|
+
description: "Run Biome over a path and get structured diagnostics back. mode=check (default; lint + format + organize-imports) or mode=lint. Set write=true to apply safe fixes (--write), unsafe=true for unsafe fixes (--write --unsafe). Severities match the project's Biome config (what `biome check` reports); set strict=true to surface project warnings as errors, each marked with its originalSeverity. Prefer this over shelling out to biome; the LSP already covers files you've edited. Returns markdown in content[] and a typed object in structuredContent. NOTE: with write/unsafe this tool MUTATES files (git-reversible).",
|
|
142
146
|
inputSchema: {
|
|
143
147
|
paths: z.optional(z.array(z.string())).describe("Paths to check. Defaults to the whole workspace."),
|
|
144
148
|
mode: z.optional(z.enum(["check", "lint"])).describe("check = lint+format+imports (default); lint = lint only."),
|
|
145
149
|
write: z.optional(z.boolean()).describe("Apply safe fixes (--write)."),
|
|
146
150
|
unsafe: z.optional(z.boolean()).describe("Apply unsafe fixes (--write --unsafe); implies write."),
|
|
151
|
+
strict: z.optional(z.boolean()).describe("Report project warnings as errors (marked with originalSeverity). Default: honor project config."),
|
|
147
152
|
cwd: z.optional(z.string()).describe("Directory to resolve the workspace root from.")
|
|
148
153
|
},
|
|
149
154
|
outputSchema: effectToZodSchema(BiomeCheckResult)
|
package/tools/biome-check.js
CHANGED
|
@@ -21,13 +21,17 @@ const BiomeDiagnostic = Schema.Struct({
|
|
|
21
21
|
line: Schema.Number,
|
|
22
22
|
severity: BiomeSeverity,
|
|
23
23
|
rule: Schema.String,
|
|
24
|
-
message: Schema.String
|
|
24
|
+
message: Schema.String,
|
|
25
|
+
/** Present only when `strict` upgraded this diagnostic; holds the project-configured severity. */
|
|
26
|
+
originalSeverity: Schema.optional(BiomeSeverity)
|
|
25
27
|
}).annotations({ identifier: "BiomeDiagnostic" });
|
|
26
28
|
/** The `biome_check` tool result. */
|
|
27
29
|
const BiomeCheckResult = Schema.Struct({
|
|
28
30
|
summary: Schema.Struct({
|
|
29
31
|
errors: Schema.Number,
|
|
30
|
-
warnings: Schema.Number
|
|
32
|
+
warnings: Schema.Number,
|
|
33
|
+
/** Count of project-level warnings reported as errors because `strict` was set. */
|
|
34
|
+
upgradedWarnings: Schema.optional(Schema.Number)
|
|
31
35
|
}),
|
|
32
36
|
diagnostics: Schema.Array(BiomeDiagnostic),
|
|
33
37
|
wrote: Schema.Boolean,
|
|
@@ -38,7 +42,11 @@ const BiomeCheckResult = Schema.Struct({
|
|
|
38
42
|
description: "Structured Biome diagnostics, with a flag for whether a --write pass ran."
|
|
39
43
|
});
|
|
40
44
|
/** Guardrail shown to the agent so it fixes code rather than silencing rules. */
|
|
41
|
-
const
|
|
45
|
+
const GUIDANCE_ERRORS = "Fix the actual code. Do NOT disable rules or add config overrides to silence these.";
|
|
46
|
+
/** Guidance when only project-tolerated warnings remain (default, non-strict run). */
|
|
47
|
+
const GUIDANCE_WARNINGS = "These are warnings under the project's Biome config — the project's own lint passes and they do not block commits or CI. Fix them when they sit in code you are already changing; do not churn unrelated files to silence them, and do NOT disable rules.";
|
|
48
|
+
/** Suffix appended when strict mode upgraded project warnings to errors. */
|
|
49
|
+
const GUIDANCE_STRICT_NOTE = "Diagnostics marked with originalSeverity are project-level warnings surfaced strictly by this run — they are not CI blockers.";
|
|
42
50
|
/** Shape of a single diagnostic in Biome's `--reporter=gitlab` output. */
|
|
43
51
|
const GitlabDiagnostic = Schema.Struct({
|
|
44
52
|
description: Schema.String,
|
|
@@ -82,22 +90,40 @@ const parseBiomeGitlab = (stdout) => {
|
|
|
82
90
|
};
|
|
83
91
|
/** Assemble the structured result from normalized diagnostics + the write flag. */
|
|
84
92
|
const buildBiomeResult = (params) => {
|
|
93
|
+
const diagnostics = params.strict ? params.diagnostics.map((d) => d.severity === "warning" ? {
|
|
94
|
+
...d,
|
|
95
|
+
severity: "error",
|
|
96
|
+
originalSeverity: "warning"
|
|
97
|
+
} : d) : [...params.diagnostics];
|
|
98
|
+
const upgradedWarnings = diagnostics.filter((d) => d.originalSeverity === "warning").length;
|
|
99
|
+
const errors = diagnostics.filter((d) => d.severity === "error").length;
|
|
100
|
+
const warnings = diagnostics.filter((d) => d.severity === "warning").length;
|
|
101
|
+
const guidance = errors - upgradedWarnings > 0 ? params.strict && upgradedWarnings > 0 ? `${GUIDANCE_ERRORS} ${GUIDANCE_STRICT_NOTE}` : GUIDANCE_ERRORS : upgradedWarnings > 0 ? `${GUIDANCE_WARNINGS} ${GUIDANCE_STRICT_NOTE}` : GUIDANCE_WARNINGS;
|
|
85
102
|
return {
|
|
86
|
-
summary: {
|
|
87
|
-
errors
|
|
88
|
-
warnings
|
|
103
|
+
summary: params.strict ? {
|
|
104
|
+
errors,
|
|
105
|
+
warnings,
|
|
106
|
+
upgradedWarnings
|
|
107
|
+
} : {
|
|
108
|
+
errors,
|
|
109
|
+
warnings
|
|
89
110
|
},
|
|
90
|
-
diagnostics
|
|
111
|
+
diagnostics,
|
|
91
112
|
wrote: params.wrote,
|
|
92
|
-
guidance:
|
|
113
|
+
guidance: diagnostics.length === 0 ? GUIDANCE_ERRORS : guidance
|
|
93
114
|
};
|
|
94
115
|
};
|
|
95
116
|
/** Render the structured result as markdown. */
|
|
96
117
|
const renderMarkdown = (data) => {
|
|
97
118
|
if (data.diagnostics.length === 0) return `# biome — clean\n\n✅ No remaining diagnostics.${data.wrote ? " A --write pass ran; check `git diff` for what changed." : ""}`;
|
|
98
|
-
const
|
|
119
|
+
const upgraded = data.summary.upgradedWarnings ?? 0;
|
|
120
|
+
const upgradedNote = upgraded > 0 ? ` (${upgraded} strict-upgraded from project warnings)` : "";
|
|
121
|
+
const lines = [`# biome — ${data.summary.errors} error(s)${upgradedNote}, ${data.summary.warnings} warning(s)`, ``];
|
|
99
122
|
if (data.wrote) lines.push(`A --write pass ran; the diagnostics below remain unfixed.`, ``);
|
|
100
|
-
for (const d of data.diagnostics)
|
|
123
|
+
for (const d of data.diagnostics) {
|
|
124
|
+
const severity = d.originalSeverity ? `${d.severity} (project ${d.originalSeverity}, strict)` : d.severity;
|
|
125
|
+
lines.push(`- \`${d.file}:${d.line}\` **${severity}** ${d.rule} — ${d.message}`);
|
|
126
|
+
}
|
|
101
127
|
lines.push(``, `---`, data.guidance);
|
|
102
128
|
return lines.join("\n");
|
|
103
129
|
};
|
|
@@ -166,7 +192,6 @@ const runBiomeCheck = async (args, fallbackCwd) => {
|
|
|
166
192
|
...prefix,
|
|
167
193
|
mode,
|
|
168
194
|
"--reporter=gitlab",
|
|
169
|
-
"--error-on-warnings",
|
|
170
195
|
"--no-errors-on-unmatched",
|
|
171
196
|
...paths
|
|
172
197
|
], {
|
|
@@ -180,7 +205,8 @@ const runBiomeCheck = async (args, fallbackCwd) => {
|
|
|
180
205
|
if ((read.status ?? 0) > 1) throw new Error(`Biome failed (exit ${read.status}): ${(read.stderr ?? "").trim() || "unknown error"}`);
|
|
181
206
|
return buildBiomeResult({
|
|
182
207
|
diagnostics: parseBiomeGitlab(read.stdout ?? ""),
|
|
183
|
-
wrote
|
|
208
|
+
wrote,
|
|
209
|
+
...args.strict !== void 0 ? { strict: args.strict } : {}
|
|
184
210
|
});
|
|
185
211
|
};
|
|
186
212
|
|
|
@@ -75,7 +75,9 @@ const changesetDepsDetect = (args, fallbackCwd) => Effect.gen(function* () {
|
|
|
75
75
|
cwd: root,
|
|
76
76
|
includeDevDeps: true,
|
|
77
77
|
...args.base ? { base: args.base } : {},
|
|
78
|
-
...args.package ? { package: args.package } : {}
|
|
78
|
+
...args.package ? { package: args.package } : {},
|
|
79
|
+
...args.packages && args.packages.length > 0 ? { packages: args.packages } : {},
|
|
80
|
+
...args.exclude && args.exclude.length > 0 ? { exclude: args.exclude } : {}
|
|
79
81
|
})).toWrite.map((entry) => ({
|
|
80
82
|
package: entry.diff.package,
|
|
81
83
|
relativePath: entry.diff.relativePath,
|
|
@@ -77,7 +77,9 @@ const changesetDepsRegen = (args, fallbackCwd) => Effect.gen(function* () {
|
|
|
77
77
|
const plan = yield* service.plan({
|
|
78
78
|
cwd: root,
|
|
79
79
|
...args.base ? { base: args.base } : {},
|
|
80
|
-
...args.package ? { package: args.package } : {}
|
|
80
|
+
...args.package ? { package: args.package } : {},
|
|
81
|
+
...args.packages && args.packages.length > 0 ? { packages: args.packages } : {},
|
|
82
|
+
...args.exclude && args.exclude.length > 0 ? { exclude: args.exclude } : {}
|
|
81
83
|
});
|
|
82
84
|
if (args.dryRun === true) return {
|
|
83
85
|
root,
|