@securitychecks/mcp 0.1.1-rc.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/LICENSE +87 -0
- package/README.md +151 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +607 -0
- package/dist/index.js.map +1 -0
- package/package.json +55 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
SecurityChecks Proprietary License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-2026 SecurityChecks. All rights reserved.
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS
|
|
6
|
+
|
|
7
|
+
1. DEFINITIONS
|
|
8
|
+
|
|
9
|
+
"Software" means the SecurityChecks CLI, collector, MCP server, and all
|
|
10
|
+
associated documentation, source code, and compiled binaries.
|
|
11
|
+
|
|
12
|
+
"License Key" means a valid subscription key obtained from SecurityChecks.
|
|
13
|
+
|
|
14
|
+
"Free Tier" means usage of the Software without a License Key, subject to
|
|
15
|
+
the limitations described in Section 3.
|
|
16
|
+
|
|
17
|
+
2. GRANT OF LICENSE
|
|
18
|
+
|
|
19
|
+
Subject to the terms of this License, SecurityChecks grants you a limited,
|
|
20
|
+
non-exclusive, non-transferable license to:
|
|
21
|
+
|
|
22
|
+
a) Install and use the Software for your internal business purposes
|
|
23
|
+
b) Make copies of the Software for backup purposes only
|
|
24
|
+
c) Use the Software in continuous integration/continuous deployment pipelines
|
|
25
|
+
|
|
26
|
+
3. FREE TIER LIMITATIONS
|
|
27
|
+
|
|
28
|
+
Without a valid License Key, you may use the Software subject to:
|
|
29
|
+
|
|
30
|
+
a) Maximum of 10 scans per calendar month
|
|
31
|
+
b) Basic finding output only (no SARIF export)
|
|
32
|
+
c) No access to calibration API features
|
|
33
|
+
d) No access to Pro patterns
|
|
34
|
+
e) Community support only
|
|
35
|
+
|
|
36
|
+
4. RESTRICTIONS
|
|
37
|
+
|
|
38
|
+
You may NOT:
|
|
39
|
+
|
|
40
|
+
a) Distribute, sublicense, lease, rent, or lend the Software to third parties
|
|
41
|
+
b) Modify, adapt, translate, reverse engineer, decompile, or disassemble the Software
|
|
42
|
+
c) Remove or alter any proprietary notices, labels, or marks on the Software
|
|
43
|
+
d) Use the Software to create a competing product or service
|
|
44
|
+
e) Share or publish License Keys
|
|
45
|
+
f) Circumvent any license enforcement mechanisms
|
|
46
|
+
g) Use the Software for illegal purposes
|
|
47
|
+
|
|
48
|
+
5. INTELLECTUAL PROPERTY
|
|
49
|
+
|
|
50
|
+
The Software and all copies thereof are proprietary to SecurityChecks and
|
|
51
|
+
title thereto remains exclusively with SecurityChecks. All rights in the
|
|
52
|
+
Software not specifically granted in this License are reserved to SecurityChecks.
|
|
53
|
+
|
|
54
|
+
6. DISCLAIMER OF WARRANTY
|
|
55
|
+
|
|
56
|
+
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
57
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
58
|
+
FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT.
|
|
59
|
+
|
|
60
|
+
7. LIMITATION OF LIABILITY
|
|
61
|
+
|
|
62
|
+
IN NO EVENT SHALL SECURITYCHECKS BE LIABLE FOR ANY INDIRECT, INCIDENTAL,
|
|
63
|
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION
|
|
64
|
+
WITH THIS LICENSE OR THE USE OF THE SOFTWARE.
|
|
65
|
+
|
|
66
|
+
8. TERMINATION
|
|
67
|
+
|
|
68
|
+
This License is effective until terminated. SecurityChecks may terminate
|
|
69
|
+
this License immediately if you breach any term. Upon termination, you must
|
|
70
|
+
destroy all copies of the Software in your possession.
|
|
71
|
+
|
|
72
|
+
9. GOVERNING LAW
|
|
73
|
+
|
|
74
|
+
This License shall be governed by and construed in accordance with the laws
|
|
75
|
+
of the State of Delaware, United States, without regard to its conflict of
|
|
76
|
+
law provisions.
|
|
77
|
+
|
|
78
|
+
10. ENTIRE AGREEMENT
|
|
79
|
+
|
|
80
|
+
This License constitutes the entire agreement between you and SecurityChecks
|
|
81
|
+
regarding the Software and supersedes all prior agreements and understandings.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
For licensing inquiries: licensing@securitychecks.ai
|
|
86
|
+
For support: support@securitychecks.ai
|
|
87
|
+
Website: https://securitychecks.ai
|
package/README.md
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# @securitychecks/mcp
|
|
2
|
+
|
|
3
|
+
> **Review AI-generated code with AI** — MCP server for production-ready code review.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@securitychecks/mcp)
|
|
6
|
+
|
|
7
|
+
MCP server that lets Claude catch what Copilot misses — webhook idempotency, service-layer auth, transaction safety.
|
|
8
|
+
|
|
9
|
+
## What is this?
|
|
10
|
+
|
|
11
|
+
Your AI assistant writes code. This gives it the ability to review that code for production-readiness.
|
|
12
|
+
|
|
13
|
+
**The loop:** Copilot/Cursor writes → Claude reviews via MCP → Ship with confidence.
|
|
14
|
+
|
|
15
|
+
Claude can now check for the patterns that cause production incidents, based on what staff engineers actually catch in review.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install -g @securitychecks/mcp
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage with Claude Code
|
|
24
|
+
|
|
25
|
+
Add to your Claude Code MCP configuration:
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"mcpServers": {
|
|
30
|
+
"scheck": {
|
|
31
|
+
"command": "scheck-mcp",
|
|
32
|
+
"args": [],
|
|
33
|
+
"env": {
|
|
34
|
+
"SCHECK_MCP_ALLOWED_ROOTS": "."
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Allowed roots (required)
|
|
42
|
+
For safety, `scheck-mcp` will only run inside the allowed roots. If you don’t set `SCHECK_MCP_ALLOWED_ROOTS` and the server is not started inside a git repository, it will refuse to scan.
|
|
43
|
+
|
|
44
|
+
## Available Tools
|
|
45
|
+
|
|
46
|
+
### `scheck_run`
|
|
47
|
+
Run scheck on the codebase.
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
Arguments:
|
|
51
|
+
- path (optional): Target path to audit
|
|
52
|
+
- include_context (optional): Include code context snippets in results
|
|
53
|
+
- max_findings (optional): Limit number of findings returned (default: 200)
|
|
54
|
+
- only (optional): Only run specific invariant checks by ID
|
|
55
|
+
- skip (optional): Skip specific invariant checks by ID
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### `scheck_list_findings`
|
|
59
|
+
List current findings from the last run.
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
Arguments:
|
|
63
|
+
- severity (optional): Filter by severity (P0, P1, P2)
|
|
64
|
+
- include_context (optional): Include code context snippets in results
|
|
65
|
+
- max_findings (optional): Limit number of findings returned (default: 200)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### `scheck_explain`
|
|
69
|
+
Explain an invariant - what a staff engineer would say about it.
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
Arguments:
|
|
73
|
+
- invariant_id: The invariant to explain (e.g., "AUTHZ.SERVICE_LAYER.ENFORCED")
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### `scheck_list_invariants`
|
|
77
|
+
List all patterns a staff engineer checks for.
|
|
78
|
+
|
|
79
|
+
### `scheck_generate_test`
|
|
80
|
+
Generate a test skeleton to prove an invariant is satisfied.
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
Arguments:
|
|
84
|
+
- invariant_id: The invariant to generate a test for
|
|
85
|
+
- framework (optional): Test framework (jest, vitest, playwright)
|
|
86
|
+
- context (optional): Extra context to generate a more targeted test
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### `scheck_feedback`
|
|
90
|
+
Report whether a finding was a true positive or false positive.
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
Arguments:
|
|
94
|
+
- invariant_id: Invariant ID (e.g., AUTHZ.SERVICE_LAYER.ENFORCED)
|
|
95
|
+
- verdict: true_positive or false_positive
|
|
96
|
+
- reason (optional): not_applicable, acceptable_risk, wrong_location, outdated_pattern, missing_context, other
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Example Session
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
User: Check my code for issues a senior engineer would catch
|
|
103
|
+
|
|
104
|
+
Claude: [calls scheck_run]
|
|
105
|
+
|
|
106
|
+
Found 2 issues a staff engineer would flag:
|
|
107
|
+
|
|
108
|
+
1. **AUTHZ.SERVICE_LAYER.ENFORCED** (P0)
|
|
109
|
+
Service "MembershipService" has exports without auth checks
|
|
110
|
+
Location: src/services/membership.ts:12
|
|
111
|
+
|
|
112
|
+
A staff engineer would ask: "What happens when a background
|
|
113
|
+
job calls removeMember() directly, bypassing the route?"
|
|
114
|
+
|
|
115
|
+
2. **WEBHOOK.IDEMPOTENT** (P0)
|
|
116
|
+
Webhook handler missing idempotency check
|
|
117
|
+
Location: src/api/webhooks/stripe.ts:45
|
|
118
|
+
|
|
119
|
+
A staff engineer would ask: "What happens when Stripe
|
|
120
|
+
retries this webhook?"
|
|
121
|
+
|
|
122
|
+
User: Explain the webhook issue
|
|
123
|
+
|
|
124
|
+
Claude: [calls scheck_explain with invariant_id="WEBHOOK.IDEMPOTENT"]
|
|
125
|
+
|
|
126
|
+
Webhooks can be delivered multiple times. Without idempotency,
|
|
127
|
+
you might double-charge customers, send duplicate emails, or
|
|
128
|
+
corrupt data...
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Why MCP?
|
|
132
|
+
|
|
133
|
+
AI writes code fast but doesn't reason about production scenarios:
|
|
134
|
+
|
|
135
|
+
- Webhook retries → double-charges
|
|
136
|
+
- Internal service calls → auth bypass
|
|
137
|
+
- Transaction rollbacks → phantom emails
|
|
138
|
+
|
|
139
|
+
This MCP server gives Claude the ability to catch these patterns — the things AI-generated code routinely misses.
|
|
140
|
+
|
|
141
|
+
## Enterprise
|
|
142
|
+
|
|
143
|
+
For teams with compliance requirements:
|
|
144
|
+
|
|
145
|
+
- **Audit trails:** Every AI-assisted review is logged
|
|
146
|
+
- **Local analysis:** SOC2 compliant — no source code transmission
|
|
147
|
+
- **Consistent patterns:** Same staff check for all developers
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
Proprietary. See [LICENSE](../../LICENSE) for details.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
5
|
+
import { getInvariantById, ALL_INVARIANTS } from '@securitychecks/collector';
|
|
6
|
+
import { generateTestSkeleton, getStaffQuestion, audit } from '@securitychecks/cli';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import fs from 'fs';
|
|
9
|
+
import { execSync } from 'child_process';
|
|
10
|
+
|
|
11
|
+
function parseAllowedRootsFromEnv(env, cwd) {
|
|
12
|
+
const raw = env["SCHECK_MCP_ALLOWED_ROOTS"] ?? env["MCP_ALLOWED_ROOTS"] ?? env["SCHECK_ALLOWED_ROOTS"];
|
|
13
|
+
const roots = (raw ?? "").split(",").map((s) => s.trim()).filter(Boolean).map((p) => path.resolve(cwd, p));
|
|
14
|
+
if (roots.length > 0) return roots;
|
|
15
|
+
const gitRoot = getGitRoot(cwd);
|
|
16
|
+
if (gitRoot) return [gitRoot];
|
|
17
|
+
throw new Error(
|
|
18
|
+
"Refusing to scan because no allowed roots are configured and no git repository was detected. Run scheck-mcp from inside a git repo, or set SCHECK_MCP_ALLOWED_ROOTS (or MCP_ALLOWED_ROOTS)."
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
function getGitRoot(cwd) {
|
|
22
|
+
try {
|
|
23
|
+
const out = execSync("git rev-parse --show-toplevel", {
|
|
24
|
+
cwd,
|
|
25
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
26
|
+
encoding: "utf-8"
|
|
27
|
+
}).trim();
|
|
28
|
+
return out.length > 0 ? out : null;
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function isWithinRoot(candidatePath, root) {
|
|
34
|
+
const relative = path.relative(root, candidatePath);
|
|
35
|
+
return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
36
|
+
}
|
|
37
|
+
function resolveAndValidateTargetPath(requestedPath, options) {
|
|
38
|
+
const { cwd, allowedRoots } = options;
|
|
39
|
+
const input = requestedPath && requestedPath.trim().length > 0 ? requestedPath : cwd;
|
|
40
|
+
const resolved = path.resolve(cwd, input);
|
|
41
|
+
if (!fs.existsSync(resolved)) {
|
|
42
|
+
throw new Error(`Target path does not exist: ${resolved}`);
|
|
43
|
+
}
|
|
44
|
+
const realResolved = fs.realpathSync(resolved);
|
|
45
|
+
const realAllowedRoots = allowedRoots.map((r) => fs.realpathSync(path.resolve(cwd, r)));
|
|
46
|
+
const allowed = realAllowedRoots.some((root) => isWithinRoot(realResolved, root));
|
|
47
|
+
if (!allowed) {
|
|
48
|
+
const rootsList = realAllowedRoots.join(", ");
|
|
49
|
+
throw new Error(
|
|
50
|
+
`Refusing to scan outside allowed roots. Requested: ${realResolved}. Allowed roots: ${rootsList}. Set SCHECK_MCP_ALLOWED_ROOTS (or MCP_ALLOWED_ROOTS) to override.`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
return realResolved;
|
|
54
|
+
}
|
|
55
|
+
function formatEvidenceForMcp(evidence, includeContext) {
|
|
56
|
+
if (includeContext) return evidence;
|
|
57
|
+
return evidence.map(({ file, line }) => ({ file, line }));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/feedback.ts
|
|
61
|
+
async function handleFeedbackTool(args, options) {
|
|
62
|
+
const invariantId = args?.invariant_id;
|
|
63
|
+
const verdict = args?.verdict;
|
|
64
|
+
const reason = args?.reason;
|
|
65
|
+
if (!invariantId || !verdict) {
|
|
66
|
+
return {
|
|
67
|
+
content: [
|
|
68
|
+
{
|
|
69
|
+
type: "text",
|
|
70
|
+
text: "Error: invariant_id and verdict are required."
|
|
71
|
+
}
|
|
72
|
+
],
|
|
73
|
+
isError: true
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
if (!["true_positive", "false_positive"].includes(verdict)) {
|
|
77
|
+
return {
|
|
78
|
+
content: [
|
|
79
|
+
{
|
|
80
|
+
type: "text",
|
|
81
|
+
text: `Error: verdict must be "true_positive" or "false_positive", got "${verdict}"`
|
|
82
|
+
}
|
|
83
|
+
],
|
|
84
|
+
isError: true
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
const { writeFileSync, readFileSync, existsSync, mkdirSync, lstatSync } = await import('fs');
|
|
89
|
+
const { join } = await import('path');
|
|
90
|
+
const cwd = options?.cwd ?? process.cwd();
|
|
91
|
+
const feedbackDir = join(cwd, ".scheck");
|
|
92
|
+
const feedbackFile = join(feedbackDir, "feedback.json");
|
|
93
|
+
const now = options?.now ?? (() => /* @__PURE__ */ new Date());
|
|
94
|
+
if (!existsSync(feedbackDir)) {
|
|
95
|
+
mkdirSync(feedbackDir, { recursive: true });
|
|
96
|
+
} else {
|
|
97
|
+
const st = lstatSync(feedbackDir);
|
|
98
|
+
if (st.isSymbolicLink()) {
|
|
99
|
+
throw new Error("Refusing to write feedback: .scheck is a symlink");
|
|
100
|
+
}
|
|
101
|
+
if (!st.isDirectory()) {
|
|
102
|
+
throw new Error("Refusing to write feedback: .scheck is not a directory");
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (existsSync(feedbackFile)) {
|
|
106
|
+
const st = lstatSync(feedbackFile);
|
|
107
|
+
if (st.isSymbolicLink()) {
|
|
108
|
+
throw new Error("Refusing to write feedback: feedback.json is a symlink");
|
|
109
|
+
}
|
|
110
|
+
if (!st.isFile()) {
|
|
111
|
+
throw new Error("Refusing to write feedback: feedback.json is not a file");
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
let feedbackData = [];
|
|
115
|
+
if (existsSync(feedbackFile)) {
|
|
116
|
+
try {
|
|
117
|
+
feedbackData = JSON.parse(readFileSync(feedbackFile, "utf-8"));
|
|
118
|
+
} catch {
|
|
119
|
+
feedbackData = [];
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
feedbackData.push({
|
|
123
|
+
invariantId,
|
|
124
|
+
verdict,
|
|
125
|
+
reason,
|
|
126
|
+
timestamp: now().toISOString()
|
|
127
|
+
});
|
|
128
|
+
writeFileSync(feedbackFile, JSON.stringify(feedbackData, null, 2));
|
|
129
|
+
try {
|
|
130
|
+
const clientVersion = "0.1.1-rc.1";
|
|
131
|
+
const endpoint = "https://api.securitychecks.ai/v1/feedback";
|
|
132
|
+
const controller = new AbortController();
|
|
133
|
+
const timeoutId = setTimeout(() => controller.abort(), 3e3);
|
|
134
|
+
const fetchFn = options?.fetchFn ?? (typeof fetch === "function" ? fetch : void 0);
|
|
135
|
+
if (fetchFn) {
|
|
136
|
+
fetchFn(endpoint, {
|
|
137
|
+
method: "POST",
|
|
138
|
+
headers: { "Content-Type": "application/json" },
|
|
139
|
+
body: JSON.stringify({ invariantId, verdict, reason, clientVersion }),
|
|
140
|
+
signal: controller.signal
|
|
141
|
+
}).catch(() => {
|
|
142
|
+
}).finally(() => clearTimeout(timeoutId));
|
|
143
|
+
} else {
|
|
144
|
+
clearTimeout(timeoutId);
|
|
145
|
+
}
|
|
146
|
+
} catch {
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
content: [
|
|
150
|
+
{
|
|
151
|
+
type: "text",
|
|
152
|
+
text: JSON.stringify(
|
|
153
|
+
{
|
|
154
|
+
recorded: true,
|
|
155
|
+
invariantId,
|
|
156
|
+
verdict,
|
|
157
|
+
reason: reason ?? null,
|
|
158
|
+
storedLocally: true
|
|
159
|
+
},
|
|
160
|
+
null,
|
|
161
|
+
2
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
]
|
|
165
|
+
};
|
|
166
|
+
} catch (error) {
|
|
167
|
+
return {
|
|
168
|
+
content: [
|
|
169
|
+
{
|
|
170
|
+
type: "text",
|
|
171
|
+
text: `Error recording feedback: ${error instanceof Error ? error.message : String(error)}`
|
|
172
|
+
}
|
|
173
|
+
],
|
|
174
|
+
isError: true
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// src/index.ts
|
|
180
|
+
var version = "0.1.1-rc.1";
|
|
181
|
+
var server = new Server(
|
|
182
|
+
{
|
|
183
|
+
name: "scheck",
|
|
184
|
+
version
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
capabilities: {
|
|
188
|
+
tools: {}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
);
|
|
192
|
+
var TOOL_PREFIXES = ["scheck"];
|
|
193
|
+
var TOOL_DEFS = [
|
|
194
|
+
{
|
|
195
|
+
suffix: "run",
|
|
196
|
+
description: "Run scheck \u2014 the patterns a senior engineer would flag in review. Catches webhook idempotency, auth at service layer, transaction safety, and more.",
|
|
197
|
+
inputSchema: {
|
|
198
|
+
type: "object",
|
|
199
|
+
properties: {
|
|
200
|
+
path: {
|
|
201
|
+
type: "string",
|
|
202
|
+
description: "Target path to audit (default: current directory)"
|
|
203
|
+
},
|
|
204
|
+
include_context: {
|
|
205
|
+
type: "boolean",
|
|
206
|
+
description: "Include code context snippets in results (may expose source code to the assistant)."
|
|
207
|
+
},
|
|
208
|
+
max_findings: {
|
|
209
|
+
type: "integer",
|
|
210
|
+
minimum: 1,
|
|
211
|
+
maximum: 500,
|
|
212
|
+
description: "Limit number of findings returned (default: 200)"
|
|
213
|
+
},
|
|
214
|
+
only: {
|
|
215
|
+
type: "array",
|
|
216
|
+
items: { type: "string" },
|
|
217
|
+
description: "Only run specific invariant checks by ID"
|
|
218
|
+
},
|
|
219
|
+
skip: {
|
|
220
|
+
type: "array",
|
|
221
|
+
items: { type: "string" },
|
|
222
|
+
description: "Skip specific invariant checks by ID"
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
suffix: "list_findings",
|
|
229
|
+
description: "List issues a staff engineer would flag \u2014 current findings from the last run, by severity.",
|
|
230
|
+
inputSchema: {
|
|
231
|
+
type: "object",
|
|
232
|
+
properties: {
|
|
233
|
+
severity: {
|
|
234
|
+
type: "string",
|
|
235
|
+
enum: ["P0", "P1", "P2"],
|
|
236
|
+
description: "Filter findings by severity"
|
|
237
|
+
},
|
|
238
|
+
include_context: {
|
|
239
|
+
type: "boolean",
|
|
240
|
+
description: "Include code context snippets in results (may expose source code to the assistant)."
|
|
241
|
+
},
|
|
242
|
+
max_findings: {
|
|
243
|
+
type: "integer",
|
|
244
|
+
minimum: 1,
|
|
245
|
+
maximum: 500,
|
|
246
|
+
description: "Limit number of findings returned (default: 200)"
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
suffix: "explain",
|
|
253
|
+
description: "What a staff engineer checks: explain why a pattern matters, real incidents it prevents, and proof needed.",
|
|
254
|
+
inputSchema: {
|
|
255
|
+
type: "object",
|
|
256
|
+
properties: {
|
|
257
|
+
invariant_id: {
|
|
258
|
+
type: "string",
|
|
259
|
+
description: "The invariant ID to explain (e.g., AUTHZ.SERVICE_LAYER.ENFORCED)"
|
|
260
|
+
}
|
|
261
|
+
},
|
|
262
|
+
required: ["invariant_id"]
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
suffix: "list_invariants",
|
|
267
|
+
description: "List all patterns a staff engineer checks for \u2014 the patterns that prevent production incidents.",
|
|
268
|
+
inputSchema: {
|
|
269
|
+
type: "object",
|
|
270
|
+
properties: {
|
|
271
|
+
category: {
|
|
272
|
+
type: "string",
|
|
273
|
+
description: "Filter by category (authz, revocation, webhooks, transactions, etc.)"
|
|
274
|
+
},
|
|
275
|
+
severity: {
|
|
276
|
+
type: "string",
|
|
277
|
+
enum: ["P0", "P1", "P2"],
|
|
278
|
+
description: "Filter by severity"
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
suffix: "generate_test",
|
|
285
|
+
description: "Generate test code that proves a pattern is enforced \u2014 the proof a staff engineer would ask for.",
|
|
286
|
+
inputSchema: {
|
|
287
|
+
type: "object",
|
|
288
|
+
properties: {
|
|
289
|
+
invariant_id: {
|
|
290
|
+
type: "string",
|
|
291
|
+
description: "The invariant ID to generate a test for"
|
|
292
|
+
},
|
|
293
|
+
framework: {
|
|
294
|
+
type: "string",
|
|
295
|
+
enum: ["jest", "vitest", "playwright"],
|
|
296
|
+
description: "Test framework to generate code for (default: vitest)"
|
|
297
|
+
},
|
|
298
|
+
context: {
|
|
299
|
+
type: "string",
|
|
300
|
+
description: "Additional context about the specific violation to generate a more targeted test"
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
required: ["invariant_id"]
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
suffix: "feedback",
|
|
308
|
+
description: "Report whether a finding was a true positive or false positive to improve accuracy.",
|
|
309
|
+
inputSchema: {
|
|
310
|
+
type: "object",
|
|
311
|
+
properties: {
|
|
312
|
+
invariant_id: {
|
|
313
|
+
type: "string",
|
|
314
|
+
description: "Invariant ID (e.g., AUTHZ.SERVICE_LAYER.ENFORCED)"
|
|
315
|
+
},
|
|
316
|
+
verdict: {
|
|
317
|
+
type: "string",
|
|
318
|
+
enum: ["true_positive", "false_positive"],
|
|
319
|
+
description: "Whether the finding was a true positive or false positive"
|
|
320
|
+
},
|
|
321
|
+
reason: {
|
|
322
|
+
type: "string",
|
|
323
|
+
enum: [
|
|
324
|
+
"not_applicable",
|
|
325
|
+
"acceptable_risk",
|
|
326
|
+
"wrong_location",
|
|
327
|
+
"outdated_pattern",
|
|
328
|
+
"missing_context"
|
|
329
|
+
],
|
|
330
|
+
description: "Reason for the verdict"
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
required: ["invariant_id", "verdict"]
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
];
|
|
337
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
338
|
+
return {
|
|
339
|
+
tools: TOOL_PREFIXES.flatMap(
|
|
340
|
+
(prefix) => TOOL_DEFS.map((def) => ({
|
|
341
|
+
name: `${prefix}_${def.suffix}`,
|
|
342
|
+
description: def.description,
|
|
343
|
+
inputSchema: def.inputSchema
|
|
344
|
+
}))
|
|
345
|
+
)
|
|
346
|
+
};
|
|
347
|
+
});
|
|
348
|
+
var lastAuditResult = null;
|
|
349
|
+
server.setRequestHandler(
|
|
350
|
+
CallToolRequestSchema,
|
|
351
|
+
async (request) => {
|
|
352
|
+
const { name, arguments: args } = request.params;
|
|
353
|
+
const tool = normalizeToolName(name);
|
|
354
|
+
if (!tool) {
|
|
355
|
+
return {
|
|
356
|
+
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
357
|
+
isError: true
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
switch (tool) {
|
|
361
|
+
case "run": {
|
|
362
|
+
const path2 = args?.["path"] || process.cwd();
|
|
363
|
+
const only = args?.["only"];
|
|
364
|
+
const skip = args?.["skip"];
|
|
365
|
+
const includeContext = args?.["include_context"] === true;
|
|
366
|
+
const maxFindings = typeof args?.["max_findings"] === "number" ? Math.max(1, Math.min(500, args["max_findings"])) : 200;
|
|
367
|
+
try {
|
|
368
|
+
const allowedRoots = parseAllowedRootsFromEnv(process.env, process.cwd());
|
|
369
|
+
const targetPath = resolveAndValidateTargetPath(path2, {
|
|
370
|
+
cwd: process.cwd(),
|
|
371
|
+
allowedRoots
|
|
372
|
+
});
|
|
373
|
+
const result = await audit({
|
|
374
|
+
targetPath,
|
|
375
|
+
only,
|
|
376
|
+
skip
|
|
377
|
+
});
|
|
378
|
+
lastAuditResult = result;
|
|
379
|
+
const findings = result.results.flatMap((r) => r.findings);
|
|
380
|
+
const truncated = findings.length > maxFindings;
|
|
381
|
+
const findingsToReturn = truncated ? findings.slice(0, maxFindings) : findings;
|
|
382
|
+
const summary = {
|
|
383
|
+
total_checks: result.summary.total,
|
|
384
|
+
passed: result.summary.passed,
|
|
385
|
+
failed: result.summary.failed,
|
|
386
|
+
findings_count: findings.length,
|
|
387
|
+
by_severity: result.summary.byPriority,
|
|
388
|
+
truncated,
|
|
389
|
+
max_findings: maxFindings
|
|
390
|
+
};
|
|
391
|
+
const formattedFindings = findingsToReturn.map((f) => ({
|
|
392
|
+
invariant_id: f.invariantId,
|
|
393
|
+
severity: f.severity,
|
|
394
|
+
message: f.message,
|
|
395
|
+
evidence: formatEvidenceForMcp(f.evidence, includeContext),
|
|
396
|
+
required_proof: f.requiredProof,
|
|
397
|
+
suggested_test: f.suggestedTest,
|
|
398
|
+
staff_engineer_asks: getStaffQuestion(f.invariantId)
|
|
399
|
+
}));
|
|
400
|
+
return {
|
|
401
|
+
content: [
|
|
402
|
+
{
|
|
403
|
+
type: "text",
|
|
404
|
+
text: JSON.stringify(
|
|
405
|
+
{
|
|
406
|
+
summary,
|
|
407
|
+
findings: formattedFindings
|
|
408
|
+
},
|
|
409
|
+
null,
|
|
410
|
+
2
|
|
411
|
+
)
|
|
412
|
+
}
|
|
413
|
+
]
|
|
414
|
+
};
|
|
415
|
+
} catch (error) {
|
|
416
|
+
return {
|
|
417
|
+
content: [
|
|
418
|
+
{
|
|
419
|
+
type: "text",
|
|
420
|
+
text: formatMcpRunError(error)
|
|
421
|
+
}
|
|
422
|
+
],
|
|
423
|
+
isError: true
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
case "list_findings": {
|
|
428
|
+
if (!lastAuditResult) {
|
|
429
|
+
return {
|
|
430
|
+
content: [
|
|
431
|
+
{
|
|
432
|
+
type: "text",
|
|
433
|
+
text: "No scan has been run yet. Use scheck_run first."
|
|
434
|
+
}
|
|
435
|
+
]
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
const severity = args?.["severity"];
|
|
439
|
+
const includeContext = args?.["include_context"] === true;
|
|
440
|
+
const maxFindings = typeof args?.["max_findings"] === "number" ? Math.max(1, Math.min(500, args["max_findings"])) : 200;
|
|
441
|
+
let findings = lastAuditResult.results.flatMap((r) => r.findings);
|
|
442
|
+
if (severity) {
|
|
443
|
+
findings = findings.filter((f) => f.severity === severity);
|
|
444
|
+
}
|
|
445
|
+
const truncated = findings.length > maxFindings;
|
|
446
|
+
const findingsToReturn = truncated ? findings.slice(0, maxFindings) : findings;
|
|
447
|
+
return {
|
|
448
|
+
content: [
|
|
449
|
+
{
|
|
450
|
+
type: "text",
|
|
451
|
+
text: JSON.stringify(
|
|
452
|
+
{
|
|
453
|
+
findings: findingsToReturn.map((f) => ({
|
|
454
|
+
invariant_id: f.invariantId,
|
|
455
|
+
severity: f.severity,
|
|
456
|
+
message: f.message,
|
|
457
|
+
evidence: formatEvidenceForMcp(f.evidence, includeContext)
|
|
458
|
+
})),
|
|
459
|
+
meta: {
|
|
460
|
+
total: findings.length,
|
|
461
|
+
truncated,
|
|
462
|
+
max_findings: maxFindings
|
|
463
|
+
}
|
|
464
|
+
},
|
|
465
|
+
null,
|
|
466
|
+
2
|
|
467
|
+
)
|
|
468
|
+
}
|
|
469
|
+
]
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
case "explain": {
|
|
473
|
+
const invariantId = args?.["invariant_id"];
|
|
474
|
+
const invariant = getInvariantById(invariantId);
|
|
475
|
+
if (!invariant) {
|
|
476
|
+
return {
|
|
477
|
+
content: [
|
|
478
|
+
{
|
|
479
|
+
type: "text",
|
|
480
|
+
text: `Unknown pattern: ${invariantId}. Use scheck_list_invariants to see available patterns.`
|
|
481
|
+
}
|
|
482
|
+
]
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
return {
|
|
486
|
+
content: [
|
|
487
|
+
{
|
|
488
|
+
type: "text",
|
|
489
|
+
text: JSON.stringify(
|
|
490
|
+
{
|
|
491
|
+
id: invariant.id,
|
|
492
|
+
name: invariant.name,
|
|
493
|
+
severity: invariant.severity,
|
|
494
|
+
category: invariant.category,
|
|
495
|
+
description: invariant.description,
|
|
496
|
+
required_proof: invariant.requiredProof,
|
|
497
|
+
staff_engineer_asks: getStaffQuestion(invariant.id)
|
|
498
|
+
},
|
|
499
|
+
null,
|
|
500
|
+
2
|
|
501
|
+
)
|
|
502
|
+
}
|
|
503
|
+
]
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
case "list_invariants": {
|
|
507
|
+
const category = args?.["category"];
|
|
508
|
+
const severity = args?.["severity"];
|
|
509
|
+
let invariants = ALL_INVARIANTS;
|
|
510
|
+
if (category) {
|
|
511
|
+
invariants = invariants.filter((i) => i.category === category);
|
|
512
|
+
}
|
|
513
|
+
if (severity) {
|
|
514
|
+
invariants = invariants.filter((i) => i.severity === severity);
|
|
515
|
+
}
|
|
516
|
+
return {
|
|
517
|
+
content: [
|
|
518
|
+
{
|
|
519
|
+
type: "text",
|
|
520
|
+
text: JSON.stringify(
|
|
521
|
+
invariants.map((i) => ({
|
|
522
|
+
id: i.id,
|
|
523
|
+
name: i.name,
|
|
524
|
+
severity: i.severity,
|
|
525
|
+
category: i.category
|
|
526
|
+
})),
|
|
527
|
+
null,
|
|
528
|
+
2
|
|
529
|
+
)
|
|
530
|
+
}
|
|
531
|
+
]
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
case "generate_test": {
|
|
535
|
+
const invariantId = args?.["invariant_id"];
|
|
536
|
+
const framework = args?.["framework"] || "vitest";
|
|
537
|
+
const context = args?.["context"];
|
|
538
|
+
const invariant = getInvariantById(invariantId);
|
|
539
|
+
if (!invariant) {
|
|
540
|
+
return {
|
|
541
|
+
content: [
|
|
542
|
+
{
|
|
543
|
+
type: "text",
|
|
544
|
+
text: `Unknown invariant: ${invariantId}`
|
|
545
|
+
}
|
|
546
|
+
]
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
const test = generateTestSkeleton(invariant, framework, context);
|
|
550
|
+
return {
|
|
551
|
+
content: [
|
|
552
|
+
{
|
|
553
|
+
type: "text",
|
|
554
|
+
text: test
|
|
555
|
+
}
|
|
556
|
+
]
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
case "feedback": {
|
|
560
|
+
return handleFeedbackTool(args);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
);
|
|
565
|
+
function normalizeToolName(name) {
|
|
566
|
+
const suffix = name.replace(/^scheck_/, "");
|
|
567
|
+
const allowed = /* @__PURE__ */ new Set([
|
|
568
|
+
"run",
|
|
569
|
+
"list_findings",
|
|
570
|
+
"explain",
|
|
571
|
+
"list_invariants",
|
|
572
|
+
"generate_test",
|
|
573
|
+
"feedback"
|
|
574
|
+
]);
|
|
575
|
+
return allowed.has(suffix) ? suffix : null;
|
|
576
|
+
}
|
|
577
|
+
function formatMcpRunError(error) {
|
|
578
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
579
|
+
if (/no allowed roots are configured and no git repository was detected/i.test(message)) {
|
|
580
|
+
return [
|
|
581
|
+
"Refusing to scan: no git repository detected and no allowed roots configured.",
|
|
582
|
+
"",
|
|
583
|
+
"Fix:",
|
|
584
|
+
"- Start the MCP server from inside the repo you want to scan, or",
|
|
585
|
+
"- Set SCHECK_MCP_ALLOWED_ROOTS (or MCP_ALLOWED_ROOTS) in your MCP server config.",
|
|
586
|
+
"",
|
|
587
|
+
"Example (Claude Code):",
|
|
588
|
+
"{",
|
|
589
|
+
' "mcpServers": {',
|
|
590
|
+
' "scheck": {',
|
|
591
|
+
' "command": "scheck-mcp",',
|
|
592
|
+
' "env": { "SCHECK_MCP_ALLOWED_ROOTS": "." }',
|
|
593
|
+
" }",
|
|
594
|
+
" }",
|
|
595
|
+
"}"
|
|
596
|
+
].join("\n");
|
|
597
|
+
}
|
|
598
|
+
return `Error running audit: ${message}`;
|
|
599
|
+
}
|
|
600
|
+
async function main() {
|
|
601
|
+
const transport = new StdioServerTransport();
|
|
602
|
+
await server.connect(transport);
|
|
603
|
+
console.error("SecurityChecks MCP server (scheck) running on stdio");
|
|
604
|
+
}
|
|
605
|
+
main().catch(console.error);
|
|
606
|
+
//# sourceMappingURL=index.js.map
|
|
607
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/safety.ts","../src/feedback.ts","../src/index.ts"],"names":["path"],"mappings":"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","file":"index.js","sourcesContent":["import path from 'node:path';\nimport fs from 'node:fs';\nimport { execSync } from 'node:child_process';\n\nexport type McpSafetyOptions = {\n cwd: string;\n allowedRoots: string[];\n};\n\nexport function parseAllowedRootsFromEnv(\n env: Record<string, string | undefined>,\n cwd: string\n): string[] {\n const raw =\n env['SCHECK_MCP_ALLOWED_ROOTS'] ??\n env['MCP_ALLOWED_ROOTS'] ??\n env['SCHECK_ALLOWED_ROOTS'];\n\n const roots = (raw ?? '')\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean)\n .map((p) => path.resolve(cwd, p));\n\n if (roots.length > 0) return roots;\n\n const gitRoot = getGitRoot(cwd);\n if (gitRoot) return [gitRoot];\n\n throw new Error(\n 'Refusing to scan because no allowed roots are configured and no git repository was detected. ' +\n 'Run scheck-mcp from inside a git repo, or set SCHECK_MCP_ALLOWED_ROOTS (or MCP_ALLOWED_ROOTS).'\n );\n}\n\nfunction getGitRoot(cwd: string): string | null {\n try {\n const out = execSync('git rev-parse --show-toplevel', {\n cwd,\n stdio: ['ignore', 'pipe', 'ignore'],\n encoding: 'utf-8',\n }).trim();\n return out.length > 0 ? out : null;\n } catch {\n return null;\n }\n}\n\nfunction isWithinRoot(candidatePath: string, root: string): boolean {\n const relative = path.relative(root, candidatePath);\n // `path.relative()` returns '' when equal. Equality should be allowed.\n return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));\n}\n\nexport function resolveAndValidateTargetPath(\n requestedPath: string | undefined,\n options: McpSafetyOptions\n): string {\n const { cwd, allowedRoots } = options;\n const input = (requestedPath && requestedPath.trim().length > 0)\n ? requestedPath\n : cwd;\n\n const resolved = path.resolve(cwd, input);\n\n if (!fs.existsSync(resolved)) {\n throw new Error(`Target path does not exist: ${resolved}`);\n }\n\n // Fail closed on symlink escapes: validate based on real paths, not just `path.resolve`.\n const realResolved = fs.realpathSync(resolved);\n const realAllowedRoots = allowedRoots.map((r) => fs.realpathSync(path.resolve(cwd, r)));\n\n const allowed = realAllowedRoots.some((root) => isWithinRoot(realResolved, root));\n\n if (!allowed) {\n const rootsList = realAllowedRoots.join(', ');\n throw new Error(\n `Refusing to scan outside allowed roots. Requested: ${realResolved}. Allowed roots: ${rootsList}. ` +\n `Set SCHECK_MCP_ALLOWED_ROOTS (or MCP_ALLOWED_ROOTS) to override.`\n );\n }\n\n return realResolved;\n}\n\nexport type EvidenceForMcp = { file: string; line: number; context?: string };\n\nexport function formatEvidenceForMcp(\n evidence: EvidenceForMcp[],\n includeContext: boolean\n): EvidenceForMcp[] {\n if (includeContext) return evidence;\n return evidence.map(({ file, line }) => ({ file, line }));\n}\n","export type FeedbackVerdict = 'true_positive' | 'false_positive';\n\nexport interface FeedbackToolArgs {\n invariant_id?: string;\n verdict?: string;\n reason?: string;\n}\n\nexport interface FeedbackToolResponse {\n content: Array<{ type: 'text'; text: string }>;\n isError?: boolean;\n}\n\n// Keep tool responses aligned with the MCP SDK result types.\n// (Our response object is a valid CallToolResult / CompatibilityCallToolResult.)\nexport type McpToolResult = import('@modelcontextprotocol/sdk/types.js').CompatibilityCallToolResult;\n\nexport async function handleFeedbackTool(\n args: FeedbackToolArgs | undefined,\n options?: {\n cwd?: string;\n now?: () => Date;\n fetchFn?: typeof fetch;\n }\n): Promise<McpToolResult> {\n const invariantId = args?.invariant_id;\n const verdict = args?.verdict as FeedbackVerdict | undefined;\n const reason = args?.reason;\n\n if (!invariantId || !verdict) {\n return {\n content: [\n {\n type: 'text',\n text: 'Error: invariant_id and verdict are required.',\n },\n ],\n isError: true,\n };\n }\n\n if (!['true_positive', 'false_positive'].includes(verdict)) {\n return {\n content: [\n {\n type: 'text',\n text: `Error: verdict must be \"true_positive\" or \"false_positive\", got \"${verdict}\"`,\n },\n ],\n isError: true,\n };\n }\n\n try {\n const { writeFileSync, readFileSync, existsSync, mkdirSync, lstatSync } = await import('fs');\n const { join } = await import('path');\n const cwd = options?.cwd ?? process.cwd();\n const feedbackDir = join(cwd, '.scheck');\n const feedbackFile = join(feedbackDir, 'feedback.json');\n const now = options?.now ?? (() => new Date());\n\n if (!existsSync(feedbackDir)) {\n mkdirSync(feedbackDir, { recursive: true });\n } else {\n const st = lstatSync(feedbackDir);\n // Avoid writing through malicious symlinks (e.g., repo-controlled `.scheck` -> /etc).\n if (st.isSymbolicLink()) {\n throw new Error('Refusing to write feedback: .scheck is a symlink');\n }\n if (!st.isDirectory()) {\n throw new Error('Refusing to write feedback: .scheck is not a directory');\n }\n }\n\n if (existsSync(feedbackFile)) {\n const st = lstatSync(feedbackFile);\n if (st.isSymbolicLink()) {\n throw new Error('Refusing to write feedback: feedback.json is a symlink');\n }\n if (!st.isFile()) {\n throw new Error('Refusing to write feedback: feedback.json is not a file');\n }\n }\n\n let feedbackData: Array<{\n invariantId: string;\n verdict: string;\n reason?: string;\n timestamp: string;\n }> = [];\n if (existsSync(feedbackFile)) {\n try {\n feedbackData = JSON.parse(readFileSync(feedbackFile, 'utf-8'));\n } catch {\n feedbackData = [];\n }\n }\n\n feedbackData.push({\n invariantId,\n verdict,\n reason,\n timestamp: now().toISOString(),\n });\n\n writeFileSync(feedbackFile, JSON.stringify(feedbackData, null, 2));\n\n try {\n const clientVersion = process.env['MCP_VERSION'] ?? '0.0.0-dev';\n const endpoint = 'https://api.securitychecks.ai/v1/feedback';\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 3000);\n const fetchFn = options?.fetchFn ?? (typeof fetch === 'function' ? fetch : undefined);\n if (fetchFn) {\n fetchFn(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ invariantId, verdict, reason, clientVersion }),\n signal: controller.signal,\n })\n .catch(() => {})\n .finally(() => clearTimeout(timeoutId));\n } else {\n clearTimeout(timeoutId);\n }\n } catch {\n // Silent failure for API reporting\n }\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n {\n recorded: true,\n invariantId,\n verdict,\n reason: reason ?? null,\n storedLocally: true,\n },\n null,\n 2\n ),\n },\n ],\n };\n } catch (error) {\n return {\n content: [\n {\n type: 'text',\n text: `Error recording feedback: ${error instanceof Error ? error.message : String(error)}`,\n },\n ],\n isError: true,\n };\n }\n}\n","#!/usr/bin/env node\n\n/**\n * SecurityChecks MCP Server (scheck)\n *\n * Catch what Copilot misses — production-ready code review.\n * MCP server that exposes scheck tools for LLM integration.\n */\n\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport {\n CallToolRequestSchema,\n ListToolsRequestSchema,\n} from '@modelcontextprotocol/sdk/types.js';\nimport type { CompatibilityCallToolResult, CreateTaskResult } from '@modelcontextprotocol/sdk/types.js';\nimport {\n getInvariantById,\n ALL_INVARIANTS,\n type AuditResult,\n type Finding,\n} from '@securitychecks/collector';\nimport { audit } from '@securitychecks/cli';\nimport {\n formatEvidenceForMcp,\n parseAllowedRootsFromEnv,\n resolveAndValidateTargetPath,\n type EvidenceForMcp,\n} from './safety.js';\nimport { generateTestSkeleton, getStaffQuestion } from '@securitychecks/cli';\nimport { handleFeedbackTool } from './feedback.js';\n\n// Version injected at build time via tsup define\nconst version = process.env['MCP_VERSION'] ?? '0.0.0-dev';\n\nconst server = new Server(\n {\n name: 'scheck',\n version: version,\n },\n {\n capabilities: {\n tools: {},\n },\n }\n);\n\nconst TOOL_PREFIXES = ['scheck'] as const;\ntype ToolSuffix = 'run' | 'list_findings' | 'explain' | 'list_invariants' | 'generate_test' | 'feedback';\ntype ToolDef = {\n suffix: ToolSuffix;\n description: string;\n inputSchema: unknown;\n};\n\nconst TOOL_DEFS: ToolDef[] = [\n {\n suffix: 'run',\n description:\n 'Run scheck — the patterns a senior engineer would flag in review. ' +\n 'Catches webhook idempotency, auth at service layer, transaction safety, and more.',\n inputSchema: {\n type: 'object',\n properties: {\n path: {\n type: 'string',\n description: 'Target path to audit (default: current directory)',\n },\n include_context: {\n type: 'boolean',\n description:\n 'Include code context snippets in results (may expose source code to the assistant).',\n },\n max_findings: {\n type: 'integer',\n minimum: 1,\n maximum: 500,\n description: 'Limit number of findings returned (default: 200)',\n },\n only: {\n type: 'array',\n items: { type: 'string' },\n description: 'Only run specific invariant checks by ID',\n },\n skip: {\n type: 'array',\n items: { type: 'string' },\n description: 'Skip specific invariant checks by ID',\n },\n },\n },\n },\n {\n suffix: 'list_findings',\n description:\n 'List issues a staff engineer would flag — current findings from the last run, by severity.',\n inputSchema: {\n type: 'object',\n properties: {\n severity: {\n type: 'string',\n enum: ['P0', 'P1', 'P2'],\n description: 'Filter findings by severity',\n },\n include_context: {\n type: 'boolean',\n description:\n 'Include code context snippets in results (may expose source code to the assistant).',\n },\n max_findings: {\n type: 'integer',\n minimum: 1,\n maximum: 500,\n description: 'Limit number of findings returned (default: 200)',\n },\n },\n },\n },\n {\n suffix: 'explain',\n description:\n 'What a staff engineer checks: explain why a pattern matters, real incidents it prevents, and proof needed.',\n inputSchema: {\n type: 'object',\n properties: {\n invariant_id: {\n type: 'string',\n description: 'The invariant ID to explain (e.g., AUTHZ.SERVICE_LAYER.ENFORCED)',\n },\n },\n required: ['invariant_id'],\n },\n },\n {\n suffix: 'list_invariants',\n description:\n 'List all patterns a staff engineer checks for — the patterns that prevent production incidents.',\n inputSchema: {\n type: 'object',\n properties: {\n category: {\n type: 'string',\n description: 'Filter by category (authz, revocation, webhooks, transactions, etc.)',\n },\n severity: {\n type: 'string',\n enum: ['P0', 'P1', 'P2'],\n description: 'Filter by severity',\n },\n },\n },\n },\n {\n suffix: 'generate_test',\n description:\n 'Generate test code that proves a pattern is enforced — the proof a staff engineer would ask for.',\n inputSchema: {\n type: 'object',\n properties: {\n invariant_id: {\n type: 'string',\n description: 'The invariant ID to generate a test for',\n },\n framework: {\n type: 'string',\n enum: ['jest', 'vitest', 'playwright'],\n description: 'Test framework to generate code for (default: vitest)',\n },\n context: {\n type: 'string',\n description:\n 'Additional context about the specific violation to generate a more targeted test',\n },\n },\n required: ['invariant_id'],\n },\n },\n {\n suffix: 'feedback',\n description:\n 'Report whether a finding was a true positive or false positive to improve accuracy.',\n inputSchema: {\n type: 'object',\n properties: {\n invariant_id: {\n type: 'string',\n description: 'Invariant ID (e.g., AUTHZ.SERVICE_LAYER.ENFORCED)',\n },\n verdict: {\n type: 'string',\n enum: ['true_positive', 'false_positive'],\n description: 'Whether the finding was a true positive or false positive',\n },\n reason: {\n type: 'string',\n enum: [\n 'not_applicable',\n 'acceptable_risk',\n 'wrong_location',\n 'outdated_pattern',\n 'missing_context',\n ],\n description: 'Reason for the verdict',\n },\n },\n required: ['invariant_id', 'verdict'],\n },\n },\n];\n\n// List available tools\nserver.setRequestHandler(ListToolsRequestSchema, async () => {\n return {\n tools: TOOL_PREFIXES.flatMap((prefix) =>\n TOOL_DEFS.map((def) => ({\n name: `${prefix}_${def.suffix}`,\n description: def.description,\n inputSchema: def.inputSchema,\n }))\n ),\n };\n});\n\n// Track last audit result for list_findings\nlet lastAuditResult: AuditResult | null = null;\n\n// Handle tool calls\nserver.setRequestHandler(\n CallToolRequestSchema,\n async (request): Promise<CompatibilityCallToolResult | CreateTaskResult> => {\n const { name, arguments: args } = request.params;\n const tool = normalizeToolName(name);\n if (!tool) {\n return {\n content: [{ type: 'text', text: `Unknown tool: ${name}` }],\n isError: true,\n };\n }\n\n switch (tool) {\n case 'run': {\n const path = (args?.['path'] as string) || process.cwd();\n const only = args?.['only'] as string[] | undefined;\n const skip = args?.['skip'] as string[] | undefined;\n const includeContext = args?.['include_context'] === true;\n const maxFindings =\n typeof args?.['max_findings'] === 'number'\n ? Math.max(1, Math.min(500, args['max_findings'] as number))\n : 200;\n\n try {\n const allowedRoots = parseAllowedRootsFromEnv(process.env, process.cwd());\n const targetPath = resolveAndValidateTargetPath(path, {\n cwd: process.cwd(),\n allowedRoots,\n });\n\n const result = await audit({\n targetPath,\n only,\n skip,\n });\n\n lastAuditResult = result;\n\n // Format findings for LLM consumption\n const findings: Finding[] = result.results.flatMap((r) => r.findings);\n const truncated = findings.length > maxFindings;\n const findingsToReturn = truncated ? findings.slice(0, maxFindings) : findings;\n const summary = {\n total_checks: result.summary.total,\n passed: result.summary.passed,\n failed: result.summary.failed,\n findings_count: findings.length,\n by_severity: result.summary.byPriority,\n truncated,\n max_findings: maxFindings,\n };\n\n const formattedFindings = findingsToReturn.map((f: Finding) => ({\n invariant_id: f.invariantId,\n severity: f.severity,\n message: f.message,\n evidence: formatEvidenceForMcp(f.evidence as EvidenceForMcp[], includeContext),\n required_proof: f.requiredProof,\n suggested_test: f.suggestedTest,\n staff_engineer_asks: getStaffQuestion(f.invariantId),\n }));\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n {\n summary,\n findings: formattedFindings,\n },\n null,\n 2\n ),\n },\n ],\n };\n } catch (error) {\n return {\n content: [\n {\n type: 'text',\n text: formatMcpRunError(error),\n },\n ],\n isError: true,\n };\n }\n }\n\n case 'list_findings': {\n if (!lastAuditResult) {\n return {\n content: [\n {\n type: 'text',\n text: 'No scan has been run yet. Use scheck_run first.',\n },\n ],\n };\n }\n\n const severity = args?.['severity'] as string | undefined;\n const includeContext = args?.['include_context'] === true;\n const maxFindings =\n typeof args?.['max_findings'] === 'number'\n ? Math.max(1, Math.min(500, args['max_findings'] as number))\n : 200;\n let findings: Finding[] = lastAuditResult.results.flatMap((r) => r.findings);\n\n if (severity) {\n findings = findings.filter((f: Finding) => f.severity === severity);\n }\n\n const truncated = findings.length > maxFindings;\n const findingsToReturn = truncated ? findings.slice(0, maxFindings) : findings;\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n {\n findings: findingsToReturn.map((f: Finding) => ({\n invariant_id: f.invariantId,\n severity: f.severity,\n message: f.message,\n evidence: formatEvidenceForMcp(f.evidence as EvidenceForMcp[], includeContext),\n })),\n meta: {\n total: findings.length,\n truncated,\n max_findings: maxFindings,\n },\n },\n null,\n 2\n ),\n },\n ],\n };\n }\n\n case 'explain': {\n const invariantId = args?.['invariant_id'] as string;\n const invariant = getInvariantById(invariantId);\n\n if (!invariant) {\n return {\n content: [\n {\n type: 'text',\n text: `Unknown pattern: ${invariantId}. Use scheck_list_invariants to see available patterns.`,\n },\n ],\n };\n }\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n {\n id: invariant.id,\n name: invariant.name,\n severity: invariant.severity,\n category: invariant.category,\n description: invariant.description,\n required_proof: invariant.requiredProof,\n staff_engineer_asks: getStaffQuestion(invariant.id),\n },\n null,\n 2\n ),\n },\n ],\n };\n }\n\n case 'list_invariants': {\n const category = args?.['category'] as string | undefined;\n const severity = args?.['severity'] as string | undefined;\n\n let invariants = ALL_INVARIANTS;\n\n if (category) {\n invariants = invariants.filter((i) => i.category === category);\n }\n if (severity) {\n invariants = invariants.filter((i) => i.severity === severity);\n }\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n invariants.map((i) => ({\n id: i.id,\n name: i.name,\n severity: i.severity,\n category: i.category,\n })),\n null,\n 2\n ),\n },\n ],\n };\n }\n\n case 'generate_test': {\n const invariantId = args?.['invariant_id'] as string;\n const framework = (args?.['framework'] as string) || 'vitest';\n const context = args?.['context'] as string | undefined;\n\n const invariant = getInvariantById(invariantId);\n if (!invariant) {\n return {\n content: [\n {\n type: 'text',\n text: `Unknown invariant: ${invariantId}`,\n },\n ],\n };\n }\n\n const test = generateTestSkeleton(invariant, framework, context);\n\n return {\n content: [\n {\n type: 'text',\n text: test,\n },\n ],\n };\n }\n\n case 'feedback': {\n return handleFeedbackTool(args as Record<string, unknown> | undefined);\n }\n }\n }\n);\n\nfunction normalizeToolName(name: string): ToolSuffix | null {\n const suffix = name.replace(/^scheck_/, '') as ToolSuffix;\n const allowed = new Set<ToolSuffix>([\n 'run',\n 'list_findings',\n 'explain',\n 'list_invariants',\n 'generate_test',\n 'feedback',\n ]);\n return allowed.has(suffix) ? suffix : null;\n}\n\nfunction formatMcpRunError(error: unknown): string {\n const message = error instanceof Error ? error.message : String(error);\n\n if (/no allowed roots are configured and no git repository was detected/i.test(message)) {\n return [\n 'Refusing to scan: no git repository detected and no allowed roots configured.',\n '',\n 'Fix:',\n '- Start the MCP server from inside the repo you want to scan, or',\n '- Set SCHECK_MCP_ALLOWED_ROOTS (or MCP_ALLOWED_ROOTS) in your MCP server config.',\n '',\n 'Example (Claude Code):',\n '{',\n ' \"mcpServers\": {',\n ' \"scheck\": {',\n ' \"command\": \"scheck-mcp\",',\n ' \"env\": { \"SCHECK_MCP_ALLOWED_ROOTS\": \".\" }',\n ' }',\n ' }',\n '}',\n ].join('\\n');\n }\n\n return `Error running audit: ${message}`;\n}\n\n// Start the server\nasync function main() {\n const transport = new StdioServerTransport();\n await server.connect(transport);\n console.error('SecurityChecks MCP server (scheck) running on stdio');\n}\n\nmain().catch(console.error);\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@securitychecks/mcp",
|
|
3
|
+
"version": "0.1.1-rc.1",
|
|
4
|
+
"description": "MCP server for SecurityChecks - expose scheck tools to AI assistants",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
7
|
+
"author": "SecurityChecks <hello@securitychecks.ai>",
|
|
8
|
+
"homepage": "https://securitychecks.ai",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/securitychecks/securitychecks.ai"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"scheck",
|
|
15
|
+
"securitychecks",
|
|
16
|
+
"mcp",
|
|
17
|
+
"model-context-protocol",
|
|
18
|
+
"llm",
|
|
19
|
+
"claude",
|
|
20
|
+
"ai-assistant",
|
|
21
|
+
"code-review",
|
|
22
|
+
"cursor",
|
|
23
|
+
"anthropic"
|
|
24
|
+
],
|
|
25
|
+
"bin": {
|
|
26
|
+
"scheck-mcp": "./dist/index.js"
|
|
27
|
+
},
|
|
28
|
+
"main": "./dist/index.js",
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"files": [
|
|
31
|
+
"dist"
|
|
32
|
+
],
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
35
|
+
"@securitychecks/cli": "0.1.1-rc.1",
|
|
36
|
+
"@securitychecks/collector": "0.1.1-rc.1"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^25.1.0",
|
|
40
|
+
"tsup": "^8.5.1",
|
|
41
|
+
"typescript": "^5.9.3"
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=18.0.0"
|
|
45
|
+
},
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public"
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "tsup",
|
|
51
|
+
"dev": "tsup --watch",
|
|
52
|
+
"typecheck": "tsc --noEmit",
|
|
53
|
+
"clean": "rm -rf dist"
|
|
54
|
+
}
|
|
55
|
+
}
|