@streamblur/mcp 0.1.0 → 1.0.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 +76 -24
- package/dist/src/index.js +251 -72
- package/dist/src/index.js.map +1 -1
- package/package.json +12 -4
- package/src/index.ts +260 -79
package/README.md
CHANGED
|
@@ -1,43 +1,95 @@
|
|
|
1
|
-
# streamblur
|
|
1
|
+
# @streamblur/mcp
|
|
2
2
|
|
|
3
|
-
StreamBlur MCP
|
|
3
|
+
**StreamBlur MCP Server** — Automatically redact API keys, tokens, passwords, and credentials from any text or file your AI assistant touches.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@streamblur/mcp)
|
|
6
|
+
[](https://www.npmjs.com/package/@streamblur/mcp)
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
- `scan_text`: Return detected secrets with `type`, `start`, and `end` positions.
|
|
11
|
-
- 50+ credential patterns including OpenAI, Anthropic, GitHub, AWS, Stripe, Twilio, Slack, SendGrid, database URLs, bearer tokens, private keys, and generic `.env` assignments.
|
|
8
|
+
## What it does
|
|
9
|
+
|
|
10
|
+
When your AI coding assistant reads config files, environment variables, or any text containing secrets — StreamBlur automatically redacts them before they appear in your AI context window. 50+ credential patterns detected including OpenAI, Anthropic, AWS, Stripe, GitHub, and more.
|
|
12
11
|
|
|
13
12
|
## Install
|
|
14
13
|
|
|
15
14
|
```bash
|
|
16
|
-
npm install
|
|
17
|
-
npm run build
|
|
15
|
+
npm install -g @streamblur/mcp
|
|
18
16
|
```
|
|
19
17
|
|
|
20
|
-
##
|
|
18
|
+
## Usage with Claude Desktop
|
|
21
19
|
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
Add to your `claude_desktop_config.json`:
|
|
21
|
+
|
|
22
|
+
```json
|
|
23
|
+
{
|
|
24
|
+
"mcpServers": {
|
|
25
|
+
"streamblur": {
|
|
26
|
+
"command": "npx",
|
|
27
|
+
"args": ["-y", "@streamblur/mcp"],
|
|
28
|
+
"env": {
|
|
29
|
+
"STREAMBLUR_LICENSE_KEY": "your-pro-email-or-license-key"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
24
34
|
```
|
|
25
35
|
|
|
26
|
-
|
|
36
|
+
## Usage with Cursor / Windsurf / other MCP clients
|
|
27
37
|
|
|
28
|
-
```
|
|
29
|
-
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"streamblur": {
|
|
41
|
+
"command": "npx",
|
|
42
|
+
"args": ["-y", "@streamblur/mcp"],
|
|
43
|
+
"env": {
|
|
44
|
+
"STREAMBLUR_LICENSE_KEY": "your-pro-email-or-license-key"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
30
48
|
```
|
|
31
49
|
|
|
32
|
-
##
|
|
50
|
+
## Tools
|
|
33
51
|
|
|
34
|
-
|
|
35
|
-
npm run build
|
|
36
|
-
node dist/test/redact.test.js
|
|
37
|
-
```
|
|
52
|
+
### Free
|
|
38
53
|
|
|
39
|
-
|
|
54
|
+
| Tool | Description |
|
|
55
|
+
|------|-------------|
|
|
56
|
+
| `redact_text` | Redact secrets from any string. Returns text with `[REDACTED:type]` placeholders. |
|
|
57
|
+
| `scan_text` | Scan text and return detected secrets with type and character position. |
|
|
58
|
+
|
|
59
|
+
### Pro — requires `STREAMBLUR_LICENSE_KEY`
|
|
60
|
+
|
|
61
|
+
| Tool | Description |
|
|
62
|
+
|------|-------------|
|
|
63
|
+
| `redact_file` | Read a file and return redacted content. Supports `.env`, configs, source code. File is never modified. |
|
|
64
|
+
| `scan_directory` | Recursively scan a directory for leaked secrets. Returns file paths, secret types, and line numbers. Skips `node_modules`, `.git`, `dist`, and build folders. |
|
|
65
|
+
|
|
66
|
+
## Detected Credential Types
|
|
67
|
+
|
|
68
|
+
OpenAI API keys · Anthropic API keys · AWS access keys · GitHub tokens · Stripe keys · Twilio auth tokens · Slack tokens · SendGrid keys · Database URLs · Bearer tokens · Private keys · `.env` assignments · and 40+ more patterns.
|
|
69
|
+
|
|
70
|
+
## Get Pro
|
|
71
|
+
|
|
72
|
+
StreamBlur Pro is **$2.99 one-time** — no subscription, no recurring charges.
|
|
73
|
+
|
|
74
|
+
[Get Pro at streamblur.com/pricing](https://streamblur.com/pricing)
|
|
75
|
+
|
|
76
|
+
Once you have Pro, set your email or license key as `STREAMBLUR_LICENSE_KEY` in your MCP config to unlock `redact_file` and `scan_directory`.
|
|
77
|
+
|
|
78
|
+
## Example
|
|
40
79
|
|
|
41
|
-
```bash
|
|
42
|
-
node -e "const r = require('./dist/redact'); const text = require('fs').readFileSync('./test/sample-secrets.txt','utf8'); console.log('BEFORE:\n' + text); console.log('\nAFTER:\n' + r.redactText(text));"
|
|
43
80
|
```
|
|
81
|
+
// AI asks to read .env file
|
|
82
|
+
// Without StreamBlur: OPENAI_API_KEY=sk-proj-abc123...
|
|
83
|
+
// With StreamBlur: OPENAI_API_KEY=[REDACTED:openai_project_key]
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Privacy
|
|
87
|
+
|
|
88
|
+
All pattern matching runs **100% locally**. Your files and text are never uploaded anywhere. The only network call is a one-time license key validation on startup (Pro only).
|
|
89
|
+
|
|
90
|
+
## Links
|
|
91
|
+
|
|
92
|
+
- [streamblur.com](https://streamblur.com)
|
|
93
|
+
- [Chrome Extension](https://chromewebstore.google.com/detail/streamblur/ikbeaahlgjhcpmnmoephpcoabconahim)
|
|
94
|
+
- [Pricing](https://streamblur.com/pricing)
|
|
95
|
+
- [Discord](https://discord.gg/628jYn5TwC)
|
package/dist/src/index.js
CHANGED
|
@@ -2,68 +2,205 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
5
6
|
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
6
7
|
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
7
8
|
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
8
9
|
const redact_1 = require("./redact");
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
// ─── Pro License Validation ────────────────────────────────────────────────
|
|
11
|
+
const LICENSE_KEY = process.env.STREAMBLUR_LICENSE_KEY ?? "";
|
|
12
|
+
let proValidated = null; // null = not yet checked
|
|
13
|
+
async function checkProLicense(email) {
|
|
14
|
+
try {
|
|
15
|
+
const res = await fetch("https://streamblur.com/.netlify/functions/check-pro", {
|
|
16
|
+
method: "POST",
|
|
17
|
+
headers: { "Content-Type": "application/json" },
|
|
18
|
+
body: JSON.stringify({ email }),
|
|
19
|
+
signal: AbortSignal.timeout(5000)
|
|
20
|
+
});
|
|
21
|
+
if (!res.ok)
|
|
22
|
+
return false;
|
|
23
|
+
const data = await res.json();
|
|
24
|
+
return data.isPro === true;
|
|
15
25
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async function isPro() {
|
|
31
|
+
if (proValidated !== null)
|
|
32
|
+
return proValidated;
|
|
33
|
+
if (!LICENSE_KEY) {
|
|
34
|
+
proValidated = false;
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
// LICENSE_KEY can be either an email (Pro account) or a license key string
|
|
38
|
+
// Try email format first, then fall back to key-based check
|
|
39
|
+
const isEmail = LICENSE_KEY.includes("@");
|
|
40
|
+
if (isEmail) {
|
|
41
|
+
proValidated = await checkProLicense(LICENSE_KEY);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
// License key format: check against validate-license endpoint
|
|
45
|
+
try {
|
|
46
|
+
const res = await fetch("https://streamblur.com/.netlify/functions/validate-license", {
|
|
47
|
+
method: "POST",
|
|
48
|
+
headers: { "Content-Type": "application/json" },
|
|
49
|
+
body: JSON.stringify({ licenseKey: LICENSE_KEY }),
|
|
50
|
+
signal: AbortSignal.timeout(5000)
|
|
51
|
+
});
|
|
52
|
+
if (!res.ok) {
|
|
53
|
+
proValidated = false;
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
const data = await res.json();
|
|
57
|
+
proValidated = data.valid === true || data.isPro === true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
proValidated = false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return proValidated;
|
|
65
|
+
}
|
|
66
|
+
// ─── Directory Scanner (Pro) ───────────────────────────────────────────────
|
|
67
|
+
const SCANNABLE_EXTENSIONS = new Set([
|
|
68
|
+
".env", ".txt", ".js", ".ts", ".jsx", ".tsx", ".json", ".yaml", ".yml",
|
|
69
|
+
".toml", ".ini", ".cfg", ".conf", ".sh", ".bash", ".zsh", ".py", ".rb",
|
|
70
|
+
".go", ".rs", ".java", ".kt", ".php", ".cs", ".cpp", ".c", ".h",
|
|
71
|
+
".tf", ".hcl", ".properties", ".xml", ".md", ".mdx"
|
|
72
|
+
]);
|
|
73
|
+
const IGNORED_DIRS = new Set([
|
|
74
|
+
"node_modules", ".git", ".next", "dist", "build", "out", ".cache",
|
|
75
|
+
"coverage", ".turbo", "vendor", "__pycache__", ".venv", "venv"
|
|
76
|
+
]);
|
|
77
|
+
function scanDirectory(dirPath, maxFiles = 500) {
|
|
78
|
+
const results = [];
|
|
79
|
+
let fileCount = 0;
|
|
80
|
+
function walk(current) {
|
|
81
|
+
if (fileCount >= maxFiles)
|
|
82
|
+
return;
|
|
83
|
+
let entries;
|
|
84
|
+
try {
|
|
85
|
+
entries = (0, node_fs_1.readdirSync)(current, { withFileTypes: true });
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
for (const entry of entries) {
|
|
91
|
+
if (fileCount >= maxFiles)
|
|
92
|
+
break;
|
|
93
|
+
const fullPath = (0, node_path_1.join)(current, entry.name);
|
|
94
|
+
if (entry.isDirectory()) {
|
|
95
|
+
if (!IGNORED_DIRS.has(entry.name))
|
|
96
|
+
walk(fullPath);
|
|
97
|
+
}
|
|
98
|
+
else if (entry.isFile()) {
|
|
99
|
+
const ext = (0, node_path_1.extname)(entry.name).toLowerCase();
|
|
100
|
+
const isEnvFile = entry.name.startsWith(".env");
|
|
101
|
+
if (!SCANNABLE_EXTENSIONS.has(ext) && !isEnvFile)
|
|
102
|
+
continue;
|
|
103
|
+
let size = 0;
|
|
104
|
+
try {
|
|
105
|
+
size = (0, node_fs_1.statSync)(fullPath).size;
|
|
33
106
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
name: "redact_file",
|
|
37
|
-
description: "Reads a file and returns redacted content without modifying the original file",
|
|
38
|
-
inputSchema: {
|
|
39
|
-
type: "object",
|
|
40
|
-
properties: {
|
|
41
|
-
path: {
|
|
42
|
-
type: "string",
|
|
43
|
-
description: "Path to file"
|
|
44
|
-
}
|
|
45
|
-
},
|
|
46
|
-
required: ["path"],
|
|
47
|
-
additionalProperties: false
|
|
107
|
+
catch {
|
|
108
|
+
continue;
|
|
48
109
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
110
|
+
if (size > 500000)
|
|
111
|
+
continue; // skip files > 500KB
|
|
112
|
+
fileCount++;
|
|
113
|
+
try {
|
|
114
|
+
const content = (0, node_fs_1.readFileSync)(fullPath, "utf8");
|
|
115
|
+
const detections = (0, redact_1.scanText)(content);
|
|
116
|
+
if (detections.length > 0) {
|
|
117
|
+
const lines = content.split("\n");
|
|
118
|
+
const mapped = detections.map(d => {
|
|
119
|
+
let chars = 0;
|
|
120
|
+
let lineNum = 1;
|
|
121
|
+
let col = d.start;
|
|
122
|
+
for (const line of lines) {
|
|
123
|
+
if (chars + line.length >= d.start) {
|
|
124
|
+
col = d.start - chars;
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
chars += line.length + 1;
|
|
128
|
+
lineNum++;
|
|
129
|
+
}
|
|
130
|
+
return { type: d.type, line: lineNum, column: col };
|
|
131
|
+
});
|
|
132
|
+
results.push({ file: fullPath, detections: mapped });
|
|
133
|
+
}
|
|
63
134
|
}
|
|
135
|
+
catch {
|
|
136
|
+
// skip unreadable files
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
walk(dirPath);
|
|
142
|
+
return results;
|
|
143
|
+
}
|
|
144
|
+
// ─── Server Setup ──────────────────────────────────────────────────────────
|
|
145
|
+
const server = new index_js_1.Server({ name: "streamblur-mcp", version: "1.0.0" }, { capabilities: { tools: {} } });
|
|
146
|
+
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
|
|
147
|
+
const proActive = await isPro();
|
|
148
|
+
const tools = [
|
|
149
|
+
{
|
|
150
|
+
name: "redact_text",
|
|
151
|
+
description: "Redacts API keys, tokens, passwords, and credentials from text. Replaces each match with [REDACTED:type].",
|
|
152
|
+
inputSchema: {
|
|
153
|
+
type: "object",
|
|
154
|
+
properties: {
|
|
155
|
+
text: { type: "string", description: "Text to redact" }
|
|
156
|
+
},
|
|
157
|
+
required: ["text"],
|
|
158
|
+
additionalProperties: false
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
name: "scan_text",
|
|
163
|
+
description: "Scans text and returns detected secrets with type and character position. Use this to audit content before sharing.",
|
|
164
|
+
inputSchema: {
|
|
165
|
+
type: "object",
|
|
166
|
+
properties: {
|
|
167
|
+
text: { type: "string", description: "Text to scan" }
|
|
168
|
+
},
|
|
169
|
+
required: ["text"],
|
|
170
|
+
additionalProperties: false
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: "redact_file",
|
|
175
|
+
description: proActive
|
|
176
|
+
? "Reads a file and returns redacted content. Supports .env, config files, source code, and more. File is not modified."
|
|
177
|
+
: "⚡ Pro feature — reads a file and returns redacted content. Set STREAMBLUR_LICENSE_KEY to your StreamBlur Pro email or license key to unlock. Get Pro at https://streamblur.com/pricing",
|
|
178
|
+
inputSchema: {
|
|
179
|
+
type: "object",
|
|
180
|
+
properties: {
|
|
181
|
+
path: { type: "string", description: "Absolute or relative path to file" }
|
|
182
|
+
},
|
|
183
|
+
required: ["path"],
|
|
184
|
+
additionalProperties: false
|
|
64
185
|
}
|
|
65
|
-
|
|
66
|
-
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
name: "scan_directory",
|
|
189
|
+
description: proActive
|
|
190
|
+
? "Recursively scans a directory for exposed secrets across all source files. Returns file paths, secret types, and line numbers. Skips node_modules, .git, dist, and build folders."
|
|
191
|
+
: "⚡ Pro feature — recursively scans a directory for leaked secrets. Set STREAMBLUR_LICENSE_KEY to your StreamBlur Pro email or license key to unlock. Get Pro at https://streamblur.com/pricing",
|
|
192
|
+
inputSchema: {
|
|
193
|
+
type: "object",
|
|
194
|
+
properties: {
|
|
195
|
+
path: { type: "string", description: "Directory path to scan" },
|
|
196
|
+
max_files: { type: "number", description: "Max files to scan (default 500)" }
|
|
197
|
+
},
|
|
198
|
+
required: ["path"],
|
|
199
|
+
additionalProperties: false
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
];
|
|
203
|
+
return { tools };
|
|
67
204
|
});
|
|
68
205
|
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
69
206
|
const args = request.params.arguments ?? {};
|
|
@@ -73,33 +210,75 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
73
210
|
if (typeof text !== "string") {
|
|
74
211
|
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, "'text' must be a string");
|
|
75
212
|
}
|
|
76
|
-
return {
|
|
77
|
-
|
|
78
|
-
|
|
213
|
+
return { content: [{ type: "text", text: (0, redact_1.redactText)(text) }] };
|
|
214
|
+
}
|
|
215
|
+
case "scan_text": {
|
|
216
|
+
const text = args.text;
|
|
217
|
+
if (typeof text !== "string") {
|
|
218
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, "'text' must be a string");
|
|
219
|
+
}
|
|
220
|
+
const detections = (0, redact_1.scanText)(text).map(d => ({
|
|
221
|
+
type: d.type,
|
|
222
|
+
start: d.start,
|
|
223
|
+
end: d.end
|
|
224
|
+
}));
|
|
225
|
+
return { content: [{ type: "text", text: JSON.stringify(detections, null, 2) }] };
|
|
79
226
|
}
|
|
80
227
|
case "redact_file": {
|
|
228
|
+
const proActive = await isPro();
|
|
229
|
+
if (!proActive) {
|
|
230
|
+
return {
|
|
231
|
+
content: [{
|
|
232
|
+
type: "text",
|
|
233
|
+
text: "⚡ redact_file is a StreamBlur Pro feature.\n\nSet the STREAMBLUR_LICENSE_KEY environment variable to your StreamBlur Pro email or license key.\n\nGet Pro at https://streamblur.com/pricing — $2.99 one-time."
|
|
234
|
+
}]
|
|
235
|
+
};
|
|
236
|
+
}
|
|
81
237
|
const path = args.path;
|
|
82
238
|
if (typeof path !== "string") {
|
|
83
239
|
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, "'path' must be a string");
|
|
84
240
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
content: [{ type: "text", text: (0, redact_1.redactText)(content) }]
|
|
88
|
-
}
|
|
241
|
+
try {
|
|
242
|
+
const content = (0, node_fs_1.readFileSync)(path, "utf8");
|
|
243
|
+
return { content: [{ type: "text", text: (0, redact_1.redactText)(content) }] };
|
|
244
|
+
}
|
|
245
|
+
catch (err) {
|
|
246
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
247
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Could not read file: ${msg}`);
|
|
248
|
+
}
|
|
89
249
|
}
|
|
90
|
-
case "
|
|
91
|
-
const
|
|
92
|
-
if (
|
|
93
|
-
|
|
250
|
+
case "scan_directory": {
|
|
251
|
+
const proActive = await isPro();
|
|
252
|
+
if (!proActive) {
|
|
253
|
+
return {
|
|
254
|
+
content: [{
|
|
255
|
+
type: "text",
|
|
256
|
+
text: "⚡ scan_directory is a StreamBlur Pro feature.\n\nSet the STREAMBLUR_LICENSE_KEY environment variable to your StreamBlur Pro email or license key.\n\nGet Pro at https://streamblur.com/pricing — $2.99 one-time."
|
|
257
|
+
}]
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
const dirPath = args.path;
|
|
261
|
+
if (typeof dirPath !== "string") {
|
|
262
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, "'path' must be a string");
|
|
263
|
+
}
|
|
264
|
+
const maxFiles = typeof args.max_files === "number" ? args.max_files : 500;
|
|
265
|
+
try {
|
|
266
|
+
const results = scanDirectory(dirPath, maxFiles);
|
|
267
|
+
if (results.length === 0) {
|
|
268
|
+
return { content: [{ type: "text", text: "✅ No secrets detected in directory." }] };
|
|
269
|
+
}
|
|
270
|
+
const summary = results.map(r => `${r.file}\n${r.detections.map(d => ` Line ${d.line}:${d.column} — ${d.type}`).join("\n")}`).join("\n\n");
|
|
271
|
+
return {
|
|
272
|
+
content: [{
|
|
273
|
+
type: "text",
|
|
274
|
+
text: `⚠️ Found secrets in ${results.length} file(s):\n\n${summary}`
|
|
275
|
+
}]
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
catch (err) {
|
|
279
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
280
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Could not scan directory: ${msg}`);
|
|
94
281
|
}
|
|
95
|
-
const detections = (0, redact_1.scanText)(text).map((detection) => ({
|
|
96
|
-
type: detection.type,
|
|
97
|
-
start: detection.start,
|
|
98
|
-
end: detection.end
|
|
99
|
-
}));
|
|
100
|
-
return {
|
|
101
|
-
content: [{ type: "text", text: JSON.stringify(detections, null, 2) }]
|
|
102
|
-
};
|
|
103
282
|
}
|
|
104
283
|
default:
|
|
105
284
|
throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`);
|
package/dist/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;AAEA,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@streamblur/mcp",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "StreamBlur MCP server - redact API keys, tokens, and secrets from text and files",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "StreamBlur MCP server - redact API keys, tokens, and secrets from text and files. Pro features: file & directory scanning.",
|
|
5
5
|
"main": "dist/src/index.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"streamblur-mcp": "
|
|
7
|
+
"streamblur-mcp": "dist/src/index.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"build": "tsc -p tsconfig.json",
|
|
11
11
|
"start": "node dist/src/index.js",
|
|
12
12
|
"test": "node dist/test/redact.test.js"
|
|
13
13
|
},
|
|
14
|
-
"keywords": [
|
|
14
|
+
"keywords": [
|
|
15
|
+
"mcp",
|
|
16
|
+
"security",
|
|
17
|
+
"redact",
|
|
18
|
+
"api-keys",
|
|
19
|
+
"secrets",
|
|
20
|
+
"privacy",
|
|
21
|
+
"streamblur"
|
|
22
|
+
],
|
|
15
23
|
"author": "StreamBlur",
|
|
16
24
|
"license": "MIT",
|
|
17
25
|
"dependencies": {
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { readFileSync } from "node:fs";
|
|
3
|
+
import { readFileSync, readdirSync, statSync } from "node:fs";
|
|
4
|
+
import { join, extname } from "node:path";
|
|
4
5
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
5
6
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
7
|
import {
|
|
@@ -11,112 +12,292 @@ import {
|
|
|
11
12
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
12
13
|
import { redactText, scanText } from "./redact";
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
// ─── Pro License Validation ────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
const LICENSE_KEY = process.env.STREAMBLUR_LICENSE_KEY ?? "";
|
|
18
|
+
let proValidated: boolean | null = null; // null = not yet checked
|
|
19
|
+
|
|
20
|
+
async function checkProLicense(email: string): Promise<boolean> {
|
|
21
|
+
try {
|
|
22
|
+
const res = await fetch("https://streamblur.com/.netlify/functions/check-pro", {
|
|
23
|
+
method: "POST",
|
|
24
|
+
headers: { "Content-Type": "application/json" },
|
|
25
|
+
body: JSON.stringify({ email }),
|
|
26
|
+
signal: AbortSignal.timeout(5000)
|
|
27
|
+
});
|
|
28
|
+
if (!res.ok) return false;
|
|
29
|
+
const data = await res.json() as { isPro?: boolean };
|
|
30
|
+
return data.isPro === true;
|
|
31
|
+
} catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function isPro(): Promise<boolean> {
|
|
37
|
+
if (proValidated !== null) return proValidated;
|
|
38
|
+
if (!LICENSE_KEY) {
|
|
39
|
+
proValidated = false;
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
// LICENSE_KEY can be either an email (Pro account) or a license key string
|
|
43
|
+
// Try email format first, then fall back to key-based check
|
|
44
|
+
const isEmail = LICENSE_KEY.includes("@");
|
|
45
|
+
if (isEmail) {
|
|
46
|
+
proValidated = await checkProLicense(LICENSE_KEY);
|
|
47
|
+
} else {
|
|
48
|
+
// License key format: check against validate-license endpoint
|
|
49
|
+
try {
|
|
50
|
+
const res = await fetch("https://streamblur.com/.netlify/functions/validate-license", {
|
|
51
|
+
method: "POST",
|
|
52
|
+
headers: { "Content-Type": "application/json" },
|
|
53
|
+
body: JSON.stringify({ licenseKey: LICENSE_KEY }),
|
|
54
|
+
signal: AbortSignal.timeout(5000)
|
|
55
|
+
});
|
|
56
|
+
if (!res.ok) {
|
|
57
|
+
proValidated = false;
|
|
58
|
+
} else {
|
|
59
|
+
const data = await res.json() as { valid?: boolean; isPro?: boolean };
|
|
60
|
+
proValidated = data.valid === true || data.isPro === true;
|
|
61
|
+
}
|
|
62
|
+
} catch {
|
|
63
|
+
proValidated = false;
|
|
22
64
|
}
|
|
23
65
|
}
|
|
66
|
+
return proValidated;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ─── Directory Scanner (Pro) ───────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
const SCANNABLE_EXTENSIONS = new Set([
|
|
72
|
+
".env", ".txt", ".js", ".ts", ".jsx", ".tsx", ".json", ".yaml", ".yml",
|
|
73
|
+
".toml", ".ini", ".cfg", ".conf", ".sh", ".bash", ".zsh", ".py", ".rb",
|
|
74
|
+
".go", ".rs", ".java", ".kt", ".php", ".cs", ".cpp", ".c", ".h",
|
|
75
|
+
".tf", ".hcl", ".properties", ".xml", ".md", ".mdx"
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
const IGNORED_DIRS = new Set([
|
|
79
|
+
"node_modules", ".git", ".next", "dist", "build", "out", ".cache",
|
|
80
|
+
"coverage", ".turbo", "vendor", "__pycache__", ".venv", "venv"
|
|
81
|
+
]);
|
|
82
|
+
|
|
83
|
+
interface DirectoryScanResult {
|
|
84
|
+
file: string;
|
|
85
|
+
detections: Array<{ type: string; line: number; column: number }>;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function scanDirectory(dirPath: string, maxFiles = 500): DirectoryScanResult[] {
|
|
89
|
+
const results: DirectoryScanResult[] = [];
|
|
90
|
+
let fileCount = 0;
|
|
91
|
+
|
|
92
|
+
function walk(current: string) {
|
|
93
|
+
if (fileCount >= maxFiles) return;
|
|
94
|
+
|
|
95
|
+
let entries;
|
|
96
|
+
try {
|
|
97
|
+
entries = readdirSync(current, { withFileTypes: true });
|
|
98
|
+
} catch {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
for (const entry of entries) {
|
|
103
|
+
if (fileCount >= maxFiles) break;
|
|
104
|
+
const fullPath = join(current, entry.name);
|
|
105
|
+
|
|
106
|
+
if (entry.isDirectory()) {
|
|
107
|
+
if (!IGNORED_DIRS.has(entry.name)) walk(fullPath);
|
|
108
|
+
} else if (entry.isFile()) {
|
|
109
|
+
const ext = extname(entry.name).toLowerCase();
|
|
110
|
+
const isEnvFile = entry.name.startsWith(".env");
|
|
111
|
+
if (!SCANNABLE_EXTENSIONS.has(ext) && !isEnvFile) continue;
|
|
112
|
+
|
|
113
|
+
let size = 0;
|
|
114
|
+
try { size = statSync(fullPath).size; } catch { continue; }
|
|
115
|
+
if (size > 500_000) continue; // skip files > 500KB
|
|
116
|
+
|
|
117
|
+
fileCount++;
|
|
118
|
+
try {
|
|
119
|
+
const content = readFileSync(fullPath, "utf8");
|
|
120
|
+
const detections = scanText(content);
|
|
121
|
+
if (detections.length > 0) {
|
|
122
|
+
const lines = content.split("\n");
|
|
123
|
+
const mapped = detections.map(d => {
|
|
124
|
+
let chars = 0;
|
|
125
|
+
let lineNum = 1;
|
|
126
|
+
let col = d.start;
|
|
127
|
+
for (const line of lines) {
|
|
128
|
+
if (chars + line.length >= d.start) {
|
|
129
|
+
col = d.start - chars;
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
chars += line.length + 1;
|
|
133
|
+
lineNum++;
|
|
134
|
+
}
|
|
135
|
+
return { type: d.type, line: lineNum, column: col };
|
|
136
|
+
});
|
|
137
|
+
results.push({ file: fullPath, detections: mapped });
|
|
138
|
+
}
|
|
139
|
+
} catch {
|
|
140
|
+
// skip unreadable files
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
walk(dirPath);
|
|
147
|
+
return results;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ─── Server Setup ──────────────────────────────────────────────────────────
|
|
151
|
+
|
|
152
|
+
const server = new Server(
|
|
153
|
+
{ name: "streamblur-mcp", version: "1.0.0" },
|
|
154
|
+
{ capabilities: { tools: {} } }
|
|
24
155
|
);
|
|
25
156
|
|
|
26
157
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
required: ["text"],
|
|
41
|
-
additionalProperties: false
|
|
42
|
-
}
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
name: "redact_file",
|
|
46
|
-
description: "Reads a file and returns redacted content without modifying the original file",
|
|
47
|
-
inputSchema: {
|
|
48
|
-
type: "object",
|
|
49
|
-
properties: {
|
|
50
|
-
path: {
|
|
51
|
-
type: "string",
|
|
52
|
-
description: "Path to file"
|
|
53
|
-
}
|
|
54
|
-
},
|
|
55
|
-
required: ["path"],
|
|
56
|
-
additionalProperties: false
|
|
57
|
-
}
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
name: "scan_text",
|
|
61
|
-
description: "Scans text and returns a list of detected secrets with type and position",
|
|
62
|
-
inputSchema: {
|
|
63
|
-
type: "object",
|
|
64
|
-
properties: {
|
|
65
|
-
text: {
|
|
66
|
-
type: "string",
|
|
67
|
-
description: "Text to scan"
|
|
68
|
-
}
|
|
69
|
-
},
|
|
70
|
-
required: ["text"],
|
|
71
|
-
additionalProperties: false
|
|
72
|
-
}
|
|
158
|
+
const proActive = await isPro();
|
|
159
|
+
|
|
160
|
+
const tools = [
|
|
161
|
+
{
|
|
162
|
+
name: "redact_text",
|
|
163
|
+
description: "Redacts API keys, tokens, passwords, and credentials from text. Replaces each match with [REDACTED:type].",
|
|
164
|
+
inputSchema: {
|
|
165
|
+
type: "object",
|
|
166
|
+
properties: {
|
|
167
|
+
text: { type: "string", description: "Text to redact" }
|
|
168
|
+
},
|
|
169
|
+
required: ["text"],
|
|
170
|
+
additionalProperties: false
|
|
73
171
|
}
|
|
74
|
-
|
|
75
|
-
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: "scan_text",
|
|
175
|
+
description: "Scans text and returns detected secrets with type and character position. Use this to audit content before sharing.",
|
|
176
|
+
inputSchema: {
|
|
177
|
+
type: "object",
|
|
178
|
+
properties: {
|
|
179
|
+
text: { type: "string", description: "Text to scan" }
|
|
180
|
+
},
|
|
181
|
+
required: ["text"],
|
|
182
|
+
additionalProperties: false
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: "redact_file",
|
|
187
|
+
description: proActive
|
|
188
|
+
? "Reads a file and returns redacted content. Supports .env, config files, source code, and more. File is not modified."
|
|
189
|
+
: "⚡ Pro feature — reads a file and returns redacted content. Set STREAMBLUR_LICENSE_KEY to your StreamBlur Pro email or license key to unlock. Get Pro at https://streamblur.com/pricing",
|
|
190
|
+
inputSchema: {
|
|
191
|
+
type: "object",
|
|
192
|
+
properties: {
|
|
193
|
+
path: { type: "string", description: "Absolute or relative path to file" }
|
|
194
|
+
},
|
|
195
|
+
required: ["path"],
|
|
196
|
+
additionalProperties: false
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
name: "scan_directory",
|
|
201
|
+
description: proActive
|
|
202
|
+
? "Recursively scans a directory for exposed secrets across all source files. Returns file paths, secret types, and line numbers. Skips node_modules, .git, dist, and build folders."
|
|
203
|
+
: "⚡ Pro feature — recursively scans a directory for leaked secrets. Set STREAMBLUR_LICENSE_KEY to your StreamBlur Pro email or license key to unlock. Get Pro at https://streamblur.com/pricing",
|
|
204
|
+
inputSchema: {
|
|
205
|
+
type: "object",
|
|
206
|
+
properties: {
|
|
207
|
+
path: { type: "string", description: "Directory path to scan" },
|
|
208
|
+
max_files: { type: "number", description: "Max files to scan (default 500)" }
|
|
209
|
+
},
|
|
210
|
+
required: ["path"],
|
|
211
|
+
additionalProperties: false
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
];
|
|
215
|
+
|
|
216
|
+
return { tools };
|
|
76
217
|
});
|
|
77
218
|
|
|
78
219
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
79
220
|
const args = request.params.arguments ?? {};
|
|
80
221
|
|
|
81
222
|
switch (request.params.name) {
|
|
223
|
+
|
|
82
224
|
case "redact_text": {
|
|
83
225
|
const text = args.text;
|
|
84
226
|
if (typeof text !== "string") {
|
|
85
227
|
throw new McpError(ErrorCode.InvalidParams, "'text' must be a string");
|
|
86
228
|
}
|
|
229
|
+
return { content: [{ type: "text", text: redactText(text) }] };
|
|
230
|
+
}
|
|
87
231
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
232
|
+
case "scan_text": {
|
|
233
|
+
const text = args.text;
|
|
234
|
+
if (typeof text !== "string") {
|
|
235
|
+
throw new McpError(ErrorCode.InvalidParams, "'text' must be a string");
|
|
236
|
+
}
|
|
237
|
+
const detections = scanText(text).map(d => ({
|
|
238
|
+
type: d.type,
|
|
239
|
+
start: d.start,
|
|
240
|
+
end: d.end
|
|
241
|
+
}));
|
|
242
|
+
return { content: [{ type: "text", text: JSON.stringify(detections, null, 2) }] };
|
|
91
243
|
}
|
|
92
244
|
|
|
93
245
|
case "redact_file": {
|
|
246
|
+
const proActive = await isPro();
|
|
247
|
+
if (!proActive) {
|
|
248
|
+
return {
|
|
249
|
+
content: [{
|
|
250
|
+
type: "text",
|
|
251
|
+
text: "⚡ redact_file is a StreamBlur Pro feature.\n\nSet the STREAMBLUR_LICENSE_KEY environment variable to your StreamBlur Pro email or license key.\n\nGet Pro at https://streamblur.com/pricing — $2.99 one-time."
|
|
252
|
+
}]
|
|
253
|
+
};
|
|
254
|
+
}
|
|
94
255
|
const path = args.path;
|
|
95
256
|
if (typeof path !== "string") {
|
|
96
257
|
throw new McpError(ErrorCode.InvalidParams, "'path' must be a string");
|
|
97
258
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
259
|
+
try {
|
|
260
|
+
const content = readFileSync(path, "utf8");
|
|
261
|
+
return { content: [{ type: "text", text: redactText(content) }] };
|
|
262
|
+
} catch (err: unknown) {
|
|
263
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
264
|
+
throw new McpError(ErrorCode.InternalError, `Could not read file: ${msg}`);
|
|
265
|
+
}
|
|
103
266
|
}
|
|
104
267
|
|
|
105
|
-
case "
|
|
106
|
-
const
|
|
107
|
-
if (
|
|
108
|
-
|
|
268
|
+
case "scan_directory": {
|
|
269
|
+
const proActive = await isPro();
|
|
270
|
+
if (!proActive) {
|
|
271
|
+
return {
|
|
272
|
+
content: [{
|
|
273
|
+
type: "text",
|
|
274
|
+
text: "⚡ scan_directory is a StreamBlur Pro feature.\n\nSet the STREAMBLUR_LICENSE_KEY environment variable to your StreamBlur Pro email or license key.\n\nGet Pro at https://streamblur.com/pricing — $2.99 one-time."
|
|
275
|
+
}]
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
const dirPath = args.path;
|
|
279
|
+
if (typeof dirPath !== "string") {
|
|
280
|
+
throw new McpError(ErrorCode.InvalidParams, "'path' must be a string");
|
|
281
|
+
}
|
|
282
|
+
const maxFiles = typeof args.max_files === "number" ? args.max_files : 500;
|
|
283
|
+
try {
|
|
284
|
+
const results = scanDirectory(dirPath, maxFiles);
|
|
285
|
+
if (results.length === 0) {
|
|
286
|
+
return { content: [{ type: "text", text: "✅ No secrets detected in directory." }] };
|
|
287
|
+
}
|
|
288
|
+
const summary = results.map(r =>
|
|
289
|
+
`${r.file}\n${r.detections.map(d => ` Line ${d.line}:${d.column} — ${d.type}`).join("\n")}`
|
|
290
|
+
).join("\n\n");
|
|
291
|
+
return {
|
|
292
|
+
content: [{
|
|
293
|
+
type: "text",
|
|
294
|
+
text: `⚠️ Found secrets in ${results.length} file(s):\n\n${summary}`
|
|
295
|
+
}]
|
|
296
|
+
};
|
|
297
|
+
} catch (err: unknown) {
|
|
298
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
299
|
+
throw new McpError(ErrorCode.InternalError, `Could not scan directory: ${msg}`);
|
|
109
300
|
}
|
|
110
|
-
|
|
111
|
-
const detections = scanText(text).map((detection) => ({
|
|
112
|
-
type: detection.type,
|
|
113
|
-
start: detection.start,
|
|
114
|
-
end: detection.end
|
|
115
|
-
}));
|
|
116
|
-
|
|
117
|
-
return {
|
|
118
|
-
content: [{ type: "text", text: JSON.stringify(detections, null, 2) }]
|
|
119
|
-
};
|
|
120
301
|
}
|
|
121
302
|
|
|
122
303
|
default:
|