security-mcp 1.0.4 → 1.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/README.md +77 -21
- package/defaults/checklists/ai.json +25 -0
- package/defaults/checklists/api.json +27 -0
- package/defaults/checklists/infra.json +27 -0
- package/defaults/checklists/mobile.json +25 -0
- package/defaults/checklists/payments.json +25 -0
- package/defaults/checklists/web.json +30 -0
- package/defaults/control-catalog.json +549 -0
- package/defaults/evidence-map.json +194 -0
- package/defaults/security-exceptions.json +4 -0
- package/defaults/security-policy.json +41 -2
- package/defaults/security-tools.json +41 -0
- package/dist/ci/pr-gate.js +2 -3
- package/dist/cli/index.js +63 -23
- package/dist/cli/install.js +47 -15
- package/dist/cli/onboarding.js +590 -0
- package/dist/cli/update.js +124 -0
- package/dist/gate/baseline.js +115 -0
- package/dist/gate/catalog.js +55 -0
- package/dist/gate/checks/ai-redteam.js +374 -0
- package/dist/gate/checks/ai.js +45 -14
- package/dist/gate/checks/api.js +93 -0
- package/dist/gate/checks/crypto.js +153 -0
- package/dist/gate/checks/database.js +144 -0
- package/dist/gate/checks/dependencies.js +130 -0
- package/dist/gate/checks/dlp.js +153 -0
- package/dist/gate/checks/graphql.js +122 -0
- package/dist/gate/checks/infra.js +126 -12
- package/dist/gate/checks/k8s.js +190 -0
- package/dist/gate/checks/playbook.js +160 -0
- package/dist/gate/checks/runtime.js +263 -0
- package/dist/gate/checks/sbom.js +199 -0
- package/dist/gate/checks/scanners.js +450 -0
- package/dist/gate/checks/secrets.js +119 -27
- package/dist/gate/diff.js +2 -2
- package/dist/gate/evidence.js +116 -0
- package/dist/gate/exceptions.js +85 -0
- package/dist/gate/policy.js +189 -17
- package/dist/gate/threat-intel.js +157 -0
- package/dist/mcp/server.js +938 -9
- package/dist/repo/fs.js +10 -5
- package/dist/repo/search.js +13 -1
- package/dist/review/store.js +208 -0
- package/dist/tests/run.js +103 -0
- package/package.json +13 -3
- package/prompts/SECURITY_PROMPT.md +455 -1
- package/skills/senior-security-engineer/SKILL.md +81 -4
package/dist/repo/fs.js
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
function getWorkspaceRoot() {
|
|
4
|
+
return process.cwd();
|
|
5
|
+
}
|
|
6
|
+
function getWorkspacePrefix(root) {
|
|
7
|
+
return root.endsWith(path.sep) ? root : root + path.sep;
|
|
8
|
+
}
|
|
6
9
|
export async function readFileSafe(relPath) {
|
|
7
|
-
const
|
|
10
|
+
const root = getWorkspaceRoot();
|
|
11
|
+
const rootPrefix = getWorkspacePrefix(root);
|
|
12
|
+
const p = path.resolve(root, relPath);
|
|
8
13
|
// Allow exact match to ROOT itself or any path strictly under it.
|
|
9
14
|
// Using ROOT_PREFIX prevents the classic prefix-collision bypass
|
|
10
15
|
// (e.g. /app-sibling matching /app as a prefix). CWE-22.
|
|
11
|
-
if (p !==
|
|
16
|
+
if (p !== root && !p.startsWith(rootPrefix)) {
|
|
12
17
|
throw new Error("Path traversal blocked");
|
|
13
18
|
}
|
|
14
19
|
return await readFile(p, "utf8");
|
package/dist/repo/search.js
CHANGED
|
@@ -42,7 +42,19 @@ function scanLines(file, lines, opts, re, matches) {
|
|
|
42
42
|
export async function searchRepo(opts) {
|
|
43
43
|
const files = await fg(["**/*.*"], {
|
|
44
44
|
dot: true,
|
|
45
|
-
ignore: [
|
|
45
|
+
ignore: [
|
|
46
|
+
"**/node_modules/**",
|
|
47
|
+
"**/.git/**",
|
|
48
|
+
"**/dist/**",
|
|
49
|
+
"**/.claude/**",
|
|
50
|
+
// Exclude tool-internal files — they contain detection patterns and remediation
|
|
51
|
+
// examples that would trigger their own scanners (false positives in self-scan).
|
|
52
|
+
// When deployed as a package, these live in node_modules and are ignored naturally.
|
|
53
|
+
"src/gate/**",
|
|
54
|
+
"src/mcp/**",
|
|
55
|
+
"src/cli/**",
|
|
56
|
+
"prompts/**"
|
|
57
|
+
]
|
|
46
58
|
});
|
|
47
59
|
const re = opts.isRegex ? compileUserRegex(opts.query) : null;
|
|
48
60
|
const matches = [];
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { createHash, createHmac, randomUUID } from "node:crypto";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
const REVIEW_DIR = path.join(".mcp", "reviews");
|
|
5
|
+
const REPORT_DIR = path.join(".mcp", "reports");
|
|
6
|
+
const CHECKLIST_DEFAULTS_DIR = path.join(path.dirname(path.dirname(path.dirname(new URL(import.meta.url).pathname))), "defaults", "checklists");
|
|
7
|
+
async function ensureDir(dirPath) {
|
|
8
|
+
await mkdir(dirPath, { recursive: true });
|
|
9
|
+
}
|
|
10
|
+
function reviewPath(runId) {
|
|
11
|
+
return path.join(process.cwd(), REVIEW_DIR, `${runId}.json`);
|
|
12
|
+
}
|
|
13
|
+
function reportPath(runId) {
|
|
14
|
+
return path.join(process.cwd(), REPORT_DIR, `${runId}.attestation.json`);
|
|
15
|
+
}
|
|
16
|
+
async function writeJson(filePath, value) {
|
|
17
|
+
await ensureDir(path.dirname(filePath));
|
|
18
|
+
await writeFile(filePath, JSON.stringify(value, null, 2) + "\n", "utf-8");
|
|
19
|
+
}
|
|
20
|
+
function checklistPath(runId) {
|
|
21
|
+
return path.join(process.cwd(), REVIEW_DIR, `${runId}-checklist.json`);
|
|
22
|
+
}
|
|
23
|
+
async function readChecklistRaw(runId) {
|
|
24
|
+
try {
|
|
25
|
+
const raw = await readFile(checklistPath(runId), "utf-8");
|
|
26
|
+
return JSON.parse(raw);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function computeAllCriticalComplete(items) {
|
|
33
|
+
return items
|
|
34
|
+
.filter((i) => i.critical)
|
|
35
|
+
.every((i) => i.status === "completed" || i.status === "na");
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Initialize a checklist for a run from the surface template.
|
|
39
|
+
*/
|
|
40
|
+
export async function initChecklist(runId, surface) {
|
|
41
|
+
// Load template from defaults/checklists/{surface}.json
|
|
42
|
+
let template;
|
|
43
|
+
try {
|
|
44
|
+
const raw = await readFile(path.join(CHECKLIST_DEFAULTS_DIR, `${surface}.json`), "utf-8");
|
|
45
|
+
template = JSON.parse(raw);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// Fallback to empty template
|
|
49
|
+
template = { surface, items: [] };
|
|
50
|
+
}
|
|
51
|
+
const items = template.items.map((item) => ({
|
|
52
|
+
id: item.id,
|
|
53
|
+
surface,
|
|
54
|
+
description: item.description,
|
|
55
|
+
critical: item.critical,
|
|
56
|
+
status: "pending",
|
|
57
|
+
runId
|
|
58
|
+
}));
|
|
59
|
+
const state = {
|
|
60
|
+
runId,
|
|
61
|
+
surface,
|
|
62
|
+
items,
|
|
63
|
+
allCriticalComplete: false
|
|
64
|
+
};
|
|
65
|
+
await writeJson(checklistPath(runId), state);
|
|
66
|
+
return state;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Mark a checklist item as completed.
|
|
70
|
+
*/
|
|
71
|
+
export async function completeChecklistItem(runId, itemId, completedBy, evidence) {
|
|
72
|
+
const state = await readChecklistRaw(runId);
|
|
73
|
+
if (!state)
|
|
74
|
+
throw new Error(`No checklist found for runId: ${runId}`);
|
|
75
|
+
const item = state.items.find((i) => i.id === itemId);
|
|
76
|
+
if (!item)
|
|
77
|
+
throw new Error(`Checklist item not found: ${itemId}`);
|
|
78
|
+
item.status = "completed";
|
|
79
|
+
item.completedBy = completedBy;
|
|
80
|
+
item.completedAt = new Date().toISOString();
|
|
81
|
+
if (evidence)
|
|
82
|
+
item.evidence = evidence;
|
|
83
|
+
state.allCriticalComplete = computeAllCriticalComplete(state.items);
|
|
84
|
+
await writeJson(checklistPath(runId), state);
|
|
85
|
+
return state;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Mark a checklist item as not applicable.
|
|
89
|
+
*/
|
|
90
|
+
export async function markChecklistItemNA(runId, itemId, completedBy, reason) {
|
|
91
|
+
const state = await readChecklistRaw(runId);
|
|
92
|
+
if (!state)
|
|
93
|
+
throw new Error(`No checklist found for runId: ${runId}`);
|
|
94
|
+
const item = state.items.find((i) => i.id === itemId);
|
|
95
|
+
if (!item)
|
|
96
|
+
throw new Error(`Checklist item not found: ${itemId}`);
|
|
97
|
+
item.status = "na";
|
|
98
|
+
item.completedBy = completedBy;
|
|
99
|
+
item.completedAt = new Date().toISOString();
|
|
100
|
+
item.evidence = reason;
|
|
101
|
+
state.allCriticalComplete = computeAllCriticalComplete(state.items);
|
|
102
|
+
await writeJson(checklistPath(runId), state);
|
|
103
|
+
return state;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Mark a checklist item as failed.
|
|
107
|
+
*/
|
|
108
|
+
export async function failChecklistItem(runId, itemId, completedBy, reason) {
|
|
109
|
+
const state = await readChecklistRaw(runId);
|
|
110
|
+
if (!state)
|
|
111
|
+
throw new Error(`No checklist found for runId: ${runId}`);
|
|
112
|
+
const item = state.items.find((i) => i.id === itemId);
|
|
113
|
+
if (!item)
|
|
114
|
+
throw new Error(`Checklist item not found: ${itemId}`);
|
|
115
|
+
item.status = "failed";
|
|
116
|
+
item.completedBy = completedBy;
|
|
117
|
+
item.completedAt = new Date().toISOString();
|
|
118
|
+
item.evidence = reason;
|
|
119
|
+
state.allCriticalComplete = computeAllCriticalComplete(state.items);
|
|
120
|
+
await writeJson(checklistPath(runId), state);
|
|
121
|
+
return state;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Sign off on a checklist. Requires all non-NA critical items to be completed.
|
|
125
|
+
*/
|
|
126
|
+
export async function signOffChecklist(runId, signedOffBy) {
|
|
127
|
+
const state = await readChecklistRaw(runId);
|
|
128
|
+
if (!state)
|
|
129
|
+
throw new Error(`No checklist found for runId: ${runId}`);
|
|
130
|
+
const blockers = state.items.filter((i) => i.critical && (i.status === "pending" || i.status === "failed"));
|
|
131
|
+
if (blockers.length > 0) {
|
|
132
|
+
const list = blockers.map((b) => `${b.id}: ${b.description} (${b.status})`).join("; ");
|
|
133
|
+
throw new Error(`Cannot sign off: ${blockers.length} critical item(s) are not completed: ${list}`);
|
|
134
|
+
}
|
|
135
|
+
state.signedOffBy = signedOffBy;
|
|
136
|
+
state.signedOffAt = new Date().toISOString();
|
|
137
|
+
state.allCriticalComplete = true;
|
|
138
|
+
await writeJson(checklistPath(runId), state);
|
|
139
|
+
return state;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Read checklist state for a run.
|
|
143
|
+
*/
|
|
144
|
+
export async function readChecklist(runId) {
|
|
145
|
+
return readChecklistRaw(runId);
|
|
146
|
+
}
|
|
147
|
+
export async function createReviewRun(opts) {
|
|
148
|
+
const now = new Date().toISOString();
|
|
149
|
+
const cleanTargets = (opts.targets ?? []).map((target) => target.trim()).filter(Boolean);
|
|
150
|
+
const run = {
|
|
151
|
+
id: randomUUID(),
|
|
152
|
+
createdAt: now,
|
|
153
|
+
updatedAt: now,
|
|
154
|
+
mode: opts.mode,
|
|
155
|
+
targets: cleanTargets,
|
|
156
|
+
baseRef: opts.baseRef,
|
|
157
|
+
headRef: opts.headRef,
|
|
158
|
+
requiredSteps: ["scan_strategy", "threat_model", "checklist", "run_pr_gate"],
|
|
159
|
+
steps: {
|
|
160
|
+
start_review: {
|
|
161
|
+
status: "completed",
|
|
162
|
+
updatedAt: now,
|
|
163
|
+
details: {
|
|
164
|
+
mode: opts.mode,
|
|
165
|
+
targets: cleanTargets,
|
|
166
|
+
baseRef: opts.baseRef,
|
|
167
|
+
headRef: opts.headRef
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
await writeJson(reviewPath(run.id), run);
|
|
173
|
+
return run;
|
|
174
|
+
}
|
|
175
|
+
export async function readReviewRun(runId) {
|
|
176
|
+
const raw = await readFile(reviewPath(runId), "utf-8");
|
|
177
|
+
return JSON.parse(raw);
|
|
178
|
+
}
|
|
179
|
+
export async function updateReviewStep(runId, step, status, details) {
|
|
180
|
+
const run = await readReviewRun(runId);
|
|
181
|
+
run.steps[step] = {
|
|
182
|
+
status,
|
|
183
|
+
updatedAt: new Date().toISOString(),
|
|
184
|
+
details
|
|
185
|
+
};
|
|
186
|
+
run.updatedAt = new Date().toISOString();
|
|
187
|
+
await writeJson(reviewPath(run.id), run);
|
|
188
|
+
return run;
|
|
189
|
+
}
|
|
190
|
+
export async function createReviewAttestation(runId, payload, signatureKey) {
|
|
191
|
+
const digestInput = JSON.stringify(payload);
|
|
192
|
+
const sha256 = createHash("sha256").update(digestInput).digest("hex");
|
|
193
|
+
const hmacSha256 = signatureKey
|
|
194
|
+
? createHmac("sha256", signatureKey).update(digestInput).digest("hex")
|
|
195
|
+
: undefined;
|
|
196
|
+
await writeJson(reportPath(runId), {
|
|
197
|
+
...payload,
|
|
198
|
+
integrity: {
|
|
199
|
+
sha256,
|
|
200
|
+
...(hmacSha256 ? { hmacSha256 } : {})
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
return {
|
|
204
|
+
path: reportPath(runId),
|
|
205
|
+
sha256,
|
|
206
|
+
hmacSha256
|
|
207
|
+
};
|
|
208
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { existsSync, readFileSync, rmSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { runPrGate } from "../gate/policy.js";
|
|
5
|
+
import { createReviewAttestation, createReviewRun, readReviewRun, updateReviewStep } from "../review/store.js";
|
|
6
|
+
function repoPath(...parts) {
|
|
7
|
+
return path.join(process.cwd(), ...parts);
|
|
8
|
+
}
|
|
9
|
+
function cleanupFixtureReviewArtifacts(fixtureName) {
|
|
10
|
+
const fixtureRoot = repoPath("fixtures", fixtureName, ".mcp");
|
|
11
|
+
rmSync(path.join(fixtureRoot, "reports"), { recursive: true, force: true });
|
|
12
|
+
rmSync(path.join(fixtureRoot, "reviews"), { recursive: true, force: true });
|
|
13
|
+
}
|
|
14
|
+
async function withFixture(fixtureName, fn) {
|
|
15
|
+
const previous = process.cwd();
|
|
16
|
+
process.chdir(repoPath("fixtures", fixtureName));
|
|
17
|
+
try {
|
|
18
|
+
return await fn();
|
|
19
|
+
}
|
|
20
|
+
finally {
|
|
21
|
+
process.chdir(previous);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async function runPromptConformanceTests() {
|
|
25
|
+
const prompt = readFileSync(repoPath("prompts", "SECURITY_PROMPT.md"), "utf-8");
|
|
26
|
+
const skill = readFileSync(repoPath("skills", "senior-security-engineer", "SKILL.md"), "utf-8");
|
|
27
|
+
const readme = readFileSync(repoPath("README.md"), "utf-8");
|
|
28
|
+
const serverSource = readFileSync(repoPath("src", "mcp", "server.ts"), "utf-8");
|
|
29
|
+
assert.match(prompt, /security\.start_review/);
|
|
30
|
+
assert.match(prompt, /security\.attest_review/);
|
|
31
|
+
assert.match(prompt, /Human approval is mandatory/i);
|
|
32
|
+
assert.match(skill, /90% fixing/);
|
|
33
|
+
assert.match(skill, /security\.self_heal_loop/);
|
|
34
|
+
assert.match(readme, /security\.start_review/);
|
|
35
|
+
assert.match(readme, /security\.attest_review/);
|
|
36
|
+
assert.match(serverSource, /"security\.start_review"/);
|
|
37
|
+
assert.match(serverSource, /"security\.attest_review"/);
|
|
38
|
+
}
|
|
39
|
+
async function runFixtureGateTests() {
|
|
40
|
+
await withFixture("web-insecure", async () => {
|
|
41
|
+
const result = await runPrGate({
|
|
42
|
+
mode: "folder_by_folder",
|
|
43
|
+
targets: ["src"],
|
|
44
|
+
policyPath: ".mcp/policies/security-policy.json"
|
|
45
|
+
});
|
|
46
|
+
const ids = result.findings.map((finding) => finding.id);
|
|
47
|
+
assert.ok(ids.includes("WEB_HEADERS_MISSING"));
|
|
48
|
+
assert.ok(ids.includes("DANGEROUSLY_SET_INNER_HTML"));
|
|
49
|
+
assert.ok(ids.includes("SSRF_GUARD_REQUIRED"));
|
|
50
|
+
assert.ok(result.confidence);
|
|
51
|
+
});
|
|
52
|
+
await withFixture("infra-insecure", async () => {
|
|
53
|
+
const result = await runPrGate({
|
|
54
|
+
mode: "folder_by_folder",
|
|
55
|
+
targets: ["terraform"],
|
|
56
|
+
policyPath: ".mcp/policies/security-policy.json"
|
|
57
|
+
});
|
|
58
|
+
const ids = result.findings.map((finding) => finding.id);
|
|
59
|
+
assert.ok(ids.includes("PUBLIC_EXPOSURE_RISK"));
|
|
60
|
+
assert.ok(ids.includes("CONTROL_EVIDENCE_MISSING"));
|
|
61
|
+
});
|
|
62
|
+
await withFixture("ai-insecure", async () => {
|
|
63
|
+
const result = await runPrGate({
|
|
64
|
+
mode: "folder_by_folder",
|
|
65
|
+
targets: ["ai"],
|
|
66
|
+
policyPath: ".mcp/policies/security-policy.json"
|
|
67
|
+
});
|
|
68
|
+
const ids = result.findings.map((finding) => finding.id);
|
|
69
|
+
assert.ok(ids.includes("AI_OUTPUT_BOUNDS_MISSING"));
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
async function runReviewWorkflowTests() {
|
|
73
|
+
cleanupFixtureReviewArtifacts("web-insecure");
|
|
74
|
+
await withFixture("web-insecure", async () => {
|
|
75
|
+
const run = await createReviewRun({
|
|
76
|
+
mode: "folder_by_folder",
|
|
77
|
+
targets: ["src"]
|
|
78
|
+
});
|
|
79
|
+
await updateReviewStep(run.id, "scan_strategy", "completed", { mode: "folder_by_folder", targets: ["src"] });
|
|
80
|
+
await updateReviewStep(run.id, "threat_model", "completed", { feature: "fixture web flow" });
|
|
81
|
+
await updateReviewStep(run.id, "checklist", "completed", { surface: "web" });
|
|
82
|
+
await updateReviewStep(run.id, "run_pr_gate", "completed", { status: "FAIL", confidence: { score: 20 } });
|
|
83
|
+
const saved = await readReviewRun(run.id);
|
|
84
|
+
assert.equal(saved.steps["run_pr_gate"]?.status, "completed");
|
|
85
|
+
const attestation = await createReviewAttestation(run.id, {
|
|
86
|
+
runId: run.id,
|
|
87
|
+
steps: saved.steps
|
|
88
|
+
});
|
|
89
|
+
assert.ok(existsSync(attestation.path));
|
|
90
|
+
assert.match(attestation.sha256, /^[a-f0-9]{64}$/);
|
|
91
|
+
});
|
|
92
|
+
cleanupFixtureReviewArtifacts("web-insecure");
|
|
93
|
+
}
|
|
94
|
+
async function main() {
|
|
95
|
+
await runPromptConformanceTests();
|
|
96
|
+
await runFixtureGateTests();
|
|
97
|
+
await runReviewWorkflowTests();
|
|
98
|
+
console.log("security-mcp tests passed");
|
|
99
|
+
}
|
|
100
|
+
main().catch((error) => {
|
|
101
|
+
console.error(error);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "security-mcp",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "AI security MCP server and enforcement gate for Claude Code, Cursor, GitHub Copilot, Codex, Replit, and any MCP-compatible editor. Applies OWASP, MITRE ATT&CK, NIST, Zero Trust, PCI DSS, SOC 2, and ISO 27001.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -53,21 +53,31 @@
|
|
|
53
53
|
],
|
|
54
54
|
"scripts": {
|
|
55
55
|
"build": "tsc -p tsconfig.json",
|
|
56
|
+
"lint": "eslint . --max-warnings=0",
|
|
56
57
|
"prepublishOnly": "npm run build",
|
|
57
58
|
"start": "node dist/cli/index.js serve",
|
|
58
59
|
"mcp:server": "node dist/mcp/server.js",
|
|
59
|
-
"ci:pr-gate": "node dist/ci/pr-gate.js"
|
|
60
|
+
"ci:pr-gate": "node dist/ci/pr-gate.js",
|
|
61
|
+
"test": "npm run build && node dist/tests/run.js"
|
|
60
62
|
},
|
|
61
63
|
"dependencies": {
|
|
62
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
64
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
63
65
|
"execa": "^9.5.2",
|
|
64
66
|
"fast-glob": "^3.3.3",
|
|
65
67
|
"picomatch": "^3.0.1",
|
|
66
68
|
"zod": "^3.24.1"
|
|
67
69
|
},
|
|
70
|
+
"overrides": {
|
|
71
|
+
"express-rate-limit": "^8.2.2",
|
|
72
|
+
"hono": "^4.12.7"
|
|
73
|
+
},
|
|
68
74
|
"devDependencies": {
|
|
75
|
+
"@eslint/js": "^9.22.0",
|
|
69
76
|
"@types/node": "^22.13.5",
|
|
70
77
|
"@types/picomatch": "^2.3.4",
|
|
78
|
+
"eslint": "^9.22.0",
|
|
79
|
+
"globals": "^16.0.0",
|
|
80
|
+
"typescript-eslint": "^8.26.0",
|
|
71
81
|
"typescript": "^5.7.3"
|
|
72
82
|
},
|
|
73
83
|
"engines": {
|