@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 CHANGED
@@ -1,43 +1,95 @@
1
- # streamblur-mcp
1
+ # @streamblur/mcp
2
2
 
3
- StreamBlur MCP server that redacts API keys, tokens, credentials, and sensitive secrets from text and files.
3
+ **StreamBlur MCP Server** Automatically redact API keys, tokens, passwords, and credentials from any text or file your AI assistant touches.
4
4
 
5
- ## Features
5
+ [![npm version](https://img.shields.io/npm/v/@streamblur/mcp.svg)](https://www.npmjs.com/package/@streamblur/mcp)
6
+ [![npm downloads](https://img.shields.io/npm/dw/@streamblur/mcp.svg)](https://www.npmjs.com/package/@streamblur/mcp)
6
7
 
7
- - 3 MCP tools:
8
- - `redact_text`: Redact secrets in a string.
9
- - `redact_file`: Read a file and return redacted content without writing changes.
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
- ## Run
18
+ ## Usage with Claude Desktop
21
19
 
22
- ```bash
23
- streamblur-mcp
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
- or
36
+ ## Usage with Cursor / Windsurf / other MCP clients
27
37
 
28
- ```bash
29
- node dist/index.js
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
- ## Development
50
+ ## Tools
33
51
 
34
- ```bash
35
- npm run build
36
- node dist/test/redact.test.js
37
- ```
52
+ ### Free
38
53
 
39
- ## Quick manual redaction check
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
- const server = new index_js_1.Server({
10
- name: "streamblur-mcp",
11
- version: "0.1.0"
12
- }, {
13
- capabilities: {
14
- tools: {}
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
- server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
18
- return {
19
- tools: [
20
- {
21
- name: "redact_text",
22
- description: "Redacts secrets from input text and replaces them with [REDACTED:type]",
23
- inputSchema: {
24
- type: "object",
25
- properties: {
26
- text: {
27
- type: "string",
28
- description: "Text to redact"
29
- }
30
- },
31
- required: ["text"],
32
- additionalProperties: false
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
- name: "scan_text",
52
- description: "Scans text and returns a list of detected secrets with type and position",
53
- inputSchema: {
54
- type: "object",
55
- properties: {
56
- text: {
57
- type: "string",
58
- description: "Text to scan"
59
- }
60
- },
61
- required: ["text"],
62
- additionalProperties: false
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
- content: [{ type: "text", text: (0, redact_1.redactText)(text) }]
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
- const content = (0, node_fs_1.readFileSync)(path, "utf8");
86
- return {
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 "scan_text": {
91
- const text = args.text;
92
- if (typeof text !== "string") {
93
- throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, "'text' must be a string");
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}`);
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;AAEA,qCAAuC;AACvC,wEAAmE;AACnE,wEAAiF;AACjF,iEAK4C;AAC5C,qCAAgD;AAEhD,MAAM,MAAM,GAAG,IAAI,iBAAM,CACvB;IACE,IAAI,EAAE,gBAAgB;IACtB,OAAO,EAAE,OAAO;CACjB,EACD;IACE,YAAY,EAAE;QACZ,KAAK,EAAE,EAAE;KACV;CACF,CACF,CAAC;AAEF,MAAM,CAAC,iBAAiB,CAAC,iCAAsB,EAAE,KAAK,IAAI,EAAE;IAC1D,OAAO;QACL,KAAK,EAAE;YACL;gBACE,IAAI,EAAE,aAAa;gBACnB,WAAW,EAAE,wEAAwE;gBACrF,WAAW,EAAE;oBACX,IAAI,EAAE,QAAQ;oBACd,UAAU,EAAE;wBACV,IAAI,EAAE;4BACJ,IAAI,EAAE,QAAQ;4BACd,WAAW,EAAE,gBAAgB;yBAC9B;qBACF;oBACD,QAAQ,EAAE,CAAC,MAAM,CAAC;oBAClB,oBAAoB,EAAE,KAAK;iBAC5B;aACF;YACD;gBACE,IAAI,EAAE,aAAa;gBACnB,WAAW,EAAE,+EAA+E;gBAC5F,WAAW,EAAE;oBACX,IAAI,EAAE,QAAQ;oBACd,UAAU,EAAE;wBACV,IAAI,EAAE;4BACJ,IAAI,EAAE,QAAQ;4BACd,WAAW,EAAE,cAAc;yBAC5B;qBACF;oBACD,QAAQ,EAAE,CAAC,MAAM,CAAC;oBAClB,oBAAoB,EAAE,KAAK;iBAC5B;aACF;YACD;gBACE,IAAI,EAAE,WAAW;gBACjB,WAAW,EAAE,0EAA0E;gBACvF,WAAW,EAAE;oBACX,IAAI,EAAE,QAAQ;oBACd,UAAU,EAAE;wBACV,IAAI,EAAE;4BACJ,IAAI,EAAE,QAAQ;4BACd,WAAW,EAAE,cAAc;yBAC5B;qBACF;oBACD,QAAQ,EAAE,CAAC,MAAM,CAAC;oBAClB,oBAAoB,EAAE,KAAK;iBAC5B;aACF;SACF;KACF,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,MAAM,CAAC,iBAAiB,CAAC,gCAAqB,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;IAChE,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,CAAC;IAE5C,QAAQ,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QAC5B,KAAK,aAAa,CAAC,CAAC,CAAC;YACnB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;YACvB,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC7B,MAAM,IAAI,mBAAQ,CAAC,oBAAS,CAAC,aAAa,EAAE,yBAAyB,CAAC,CAAC;YACzE,CAAC;YAED,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAA,mBAAU,EAAC,IAAI,CAAC,EAAE,CAAC;aACpD,CAAC;QACJ,CAAC;QAED,KAAK,aAAa,CAAC,CAAC,CAAC;YACnB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;YACvB,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC7B,MAAM,IAAI,mBAAQ,CAAC,oBAAS,CAAC,aAAa,EAAE,yBAAyB,CAAC,CAAC;YACzE,CAAC;YAED,MAAM,OAAO,GAAG,IAAA,sBAAY,EAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YAC3C,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAA,mBAAU,EAAC,OAAO,CAAC,EAAE,CAAC;aACvD,CAAC;QACJ,CAAC;QAED,KAAK,WAAW,CAAC,CAAC,CAAC;YACjB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;YACvB,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC7B,MAAM,IAAI,mBAAQ,CAAC,oBAAS,CAAC,aAAa,EAAE,yBAAyB,CAAC,CAAC;YACzE,CAAC;YAED,MAAM,UAAU,GAAG,IAAA,iBAAQ,EAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;gBACpD,IAAI,EAAE,SAAS,CAAC,IAAI;gBACpB,KAAK,EAAE,SAAS,CAAC,KAAK;gBACtB,GAAG,EAAE,SAAS,CAAC,GAAG;aACnB,CAAC,CAAC,CAAC;YAEJ,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;aACvE,CAAC;QACJ,CAAC;QAED;YACE,MAAM,IAAI,mBAAQ,CAAC,oBAAS,CAAC,cAAc,EAAE,iBAAiB,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IACzF,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,IAAI,+BAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,gCAAgC,EAAE,KAAK,CAAC,CAAC;IACvD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
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": "0.1.0",
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": "./dist/src/index.js"
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": ["mcp", "security", "redact", "api-keys", "secrets", "privacy", "streamblur"],
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
- const server = new Server(
15
- {
16
- name: "streamblur-mcp",
17
- version: "0.1.0"
18
- },
19
- {
20
- capabilities: {
21
- tools: {}
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
- return {
28
- tools: [
29
- {
30
- name: "redact_text",
31
- description: "Redacts secrets from input text and replaces them with [REDACTED:type]",
32
- inputSchema: {
33
- type: "object",
34
- properties: {
35
- text: {
36
- type: "string",
37
- description: "Text to redact"
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
- return {
89
- content: [{ type: "text", text: redactText(text) }]
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
- const content = readFileSync(path, "utf8");
100
- return {
101
- content: [{ type: "text", text: redactText(content) }]
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 "scan_text": {
106
- const text = args.text;
107
- if (typeof text !== "string") {
108
- throw new McpError(ErrorCode.InvalidParams, "'text' must be a string");
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: