brevix-shrink 0.4.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 +48 -0
- package/compress.js +99 -0
- package/index.js +98 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# brevix-shrink
|
|
2
|
+
|
|
3
|
+
> MCP middleware proxy that compresses tool/prompt/resource descriptions to save tokens.
|
|
4
|
+
|
|
5
|
+
`brevix-shrink` sits between an MCP client (Claude Desktop, Claude Code, Cursor, etc.) and an upstream MCP server. It intercepts `tools/list`, `prompts/list`, `resources/list`, and `resources/templates/list` responses, then runs Brevix's compression rules on description-style fields. Code, URLs, identifiers, and error quotes are preserved.
|
|
6
|
+
|
|
7
|
+
Why: MCP servers ship verbose descriptions for every tool. Those descriptions are loaded into the model's input on every session. Compressing them once at the proxy saves input tokens for the whole session lifetime.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g brevix-shrink
|
|
13
|
+
# or run on demand
|
|
14
|
+
npx brevix-shrink <upstream> [args...]
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
Wrap any MCP server command:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
brevix-shrink npx -y @modelcontextprotocol/server-filesystem /tmp
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Register in Claude Desktop / Claude Code config:
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"mcpServers": {
|
|
30
|
+
"fs-shrunk": {
|
|
31
|
+
"command": "npx",
|
|
32
|
+
"args": ["-y", "brevix-shrink", "npx", "-y",
|
|
33
|
+
"@modelcontextprotocol/server-filesystem", "/tmp"]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Configuration
|
|
40
|
+
|
|
41
|
+
| Env var | Default | Effect |
|
|
42
|
+
|---------|---------|--------|
|
|
43
|
+
| `BREVIX_SHRINK_FIELDS` | `description,prompt,instructions` | Comma-separated field names to compress |
|
|
44
|
+
| `BREVIX_SHRINK_DEBUG` | unset | Log proxy events to stderr |
|
|
45
|
+
|
|
46
|
+
## License
|
|
47
|
+
|
|
48
|
+
MIT
|
package/compress.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// Minimal output-prose compressor for MCP description fields.
|
|
2
|
+
// Mirrors Brevix's full-mode rule set, kept tiny for proxy hot-path use.
|
|
3
|
+
// Does NOT touch code, URLs, error quotes, or technical identifiers.
|
|
4
|
+
|
|
5
|
+
const CODE_FENCE = /```[\s\S]*?```/g;
|
|
6
|
+
const INLINE_CODE = /`[^`\n]+`/g;
|
|
7
|
+
const URL_RE = /(https?:\/\/\S+|www\.\S+)/g;
|
|
8
|
+
const ERROR_QUOTE = /"[^"\n]*(?:Error|Exception|Warning|Failed|Traceback)[^"\n]*"/gi;
|
|
9
|
+
|
|
10
|
+
const PLEASANTRIES = [
|
|
11
|
+
/\b(?:i(?:'d| would| am| ?'m)?\s+(?:be\s+)?)?(?:happy|glad|pleased|delighted)\s+to\s+(?:help|assist|explain|show|walk|guide|take a look)\b[^.!?]*[.!?]/gi,
|
|
12
|
+
/\b(?:sure|certainly|of course|absolutely|gladly)\b[,!.\s]+/gi,
|
|
13
|
+
/\b(?:great|good|excellent)\s+(?:question|point|catch|observation)\b[!.\s]*/gi,
|
|
14
|
+
/\bhope (?:this|that) helps\b[!.\s]*/gi,
|
|
15
|
+
/\bplease (?:note|be aware|keep in mind)\s+(?:that\s+)?/gi,
|
|
16
|
+
];
|
|
17
|
+
const HEDGES = [
|
|
18
|
+
/\bit\s+(?:seems|appears|looks|might be|could be)\s+(?:that|like)?\s*/gi,
|
|
19
|
+
/\bi\s+(?:think|believe|suppose|guess|assume)\s+(?:that\s+)?/gi,
|
|
20
|
+
/\bin my opinion\b,?\s*/gi,
|
|
21
|
+
/\bgenerally speaking\b,?\s*/gi,
|
|
22
|
+
];
|
|
23
|
+
const VERBOSE = [
|
|
24
|
+
[/\bin order to\b/gi, "to"],
|
|
25
|
+
[/\bdue to the fact that\b/gi, "because"],
|
|
26
|
+
[/\bat this point in time\b/gi, "now"],
|
|
27
|
+
[/\bin the event that\b/gi, "if"],
|
|
28
|
+
[/\bfor the purpose of\b/gi, "to"],
|
|
29
|
+
[/\bmake use of\b/gi, "use"],
|
|
30
|
+
[/\bin spite of the fact that\b/gi, "though"],
|
|
31
|
+
[/\ba large number of\b/gi, "many"],
|
|
32
|
+
[/\bthe majority of\b/gi, "most"],
|
|
33
|
+
[/\bprior to\b/gi, "before"],
|
|
34
|
+
[/\bsubsequent to\b/gi, "after"],
|
|
35
|
+
];
|
|
36
|
+
const FILLER = /\b(?:just|really|basically|actually|simply|very|quite|perhaps|maybe|essentially|literally|obviously|clearly|definitely|absolutely)\b/gi;
|
|
37
|
+
const ARTICLES = /\b(?:a|an|the)\b/gi;
|
|
38
|
+
|
|
39
|
+
export function compressProse(text) {
|
|
40
|
+
if (!text || typeof text !== "string") return text;
|
|
41
|
+
const stash = [];
|
|
42
|
+
const stashFn = (m) => {
|
|
43
|
+
const k = `__BRVX_${stash.length}__`;
|
|
44
|
+
stash.push(m);
|
|
45
|
+
return k;
|
|
46
|
+
};
|
|
47
|
+
let t = text
|
|
48
|
+
.replace(CODE_FENCE, stashFn)
|
|
49
|
+
.replace(INLINE_CODE, stashFn)
|
|
50
|
+
.replace(URL_RE, stashFn)
|
|
51
|
+
.replace(ERROR_QUOTE, stashFn);
|
|
52
|
+
|
|
53
|
+
for (const re of PLEASANTRIES) t = t.replace(re, "");
|
|
54
|
+
for (const re of HEDGES) t = t.replace(re, "");
|
|
55
|
+
for (const [re, rep] of VERBOSE) t = t.replace(re, rep);
|
|
56
|
+
t = t.replace(FILLER, "");
|
|
57
|
+
t = t.replace(ARTICLES, "");
|
|
58
|
+
t = t.replace(/ {2,}/g, " ").replace(/ +([,.;:!?])/g, "$1").replace(/^[,;:\s]+/gm, "").trim();
|
|
59
|
+
|
|
60
|
+
for (let i = stash.length - 1; i >= 0; i--) {
|
|
61
|
+
t = t.replace(`__BRVX_${i}__`, stash[i]);
|
|
62
|
+
}
|
|
63
|
+
return t;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const SHRINK_FIELDS = (process.env.BREVIX_SHRINK_FIELDS || "description,prompt,instructions")
|
|
67
|
+
.split(",")
|
|
68
|
+
.map((s) => s.trim())
|
|
69
|
+
.filter(Boolean);
|
|
70
|
+
|
|
71
|
+
export function shrinkRecord(rec) {
|
|
72
|
+
if (!rec || typeof rec !== "object") return rec;
|
|
73
|
+
for (const key of Object.keys(rec)) {
|
|
74
|
+
const val = rec[key];
|
|
75
|
+
if (SHRINK_FIELDS.includes(key) && typeof val === "string") {
|
|
76
|
+
rec[key] = compressProse(val);
|
|
77
|
+
} else if (Array.isArray(val)) {
|
|
78
|
+
rec[key] = val.map(shrinkRecord);
|
|
79
|
+
} else if (val && typeof val === "object") {
|
|
80
|
+
rec[key] = shrinkRecord(val);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return rec;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const SHRINK_RESULTS_FOR = new Set([
|
|
87
|
+
"tools/list",
|
|
88
|
+
"prompts/list",
|
|
89
|
+
"resources/list",
|
|
90
|
+
"resources/templates/list",
|
|
91
|
+
]);
|
|
92
|
+
|
|
93
|
+
export function maybeShrinkResponse(req, response) {
|
|
94
|
+
if (!response || !req || !req.method) return response;
|
|
95
|
+
if (!SHRINK_RESULTS_FOR.has(req.method)) return response;
|
|
96
|
+
if (!response.result) return response;
|
|
97
|
+
shrinkRecord(response.result);
|
|
98
|
+
return response;
|
|
99
|
+
}
|
package/index.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// brevix-shrink — stdio MCP proxy that compresses tools/list, prompts/list,
|
|
3
|
+
// and resources/list response descriptions to save tokens.
|
|
4
|
+
//
|
|
5
|
+
// Usage:
|
|
6
|
+
// npx brevix-shrink <upstream-command> [args...]
|
|
7
|
+
// e.g. npx brevix-shrink npx -y @modelcontextprotocol/server-filesystem /tmp
|
|
8
|
+
//
|
|
9
|
+
// Env:
|
|
10
|
+
// BREVIX_SHRINK_FIELDS=description,prompt override fields to compress
|
|
11
|
+
// BREVIX_SHRINK_DEBUG=1 log to stderr
|
|
12
|
+
|
|
13
|
+
import { spawn } from "node:child_process";
|
|
14
|
+
import { maybeShrinkResponse } from "./compress.js";
|
|
15
|
+
|
|
16
|
+
const DEBUG = process.env.BREVIX_SHRINK_DEBUG === "1";
|
|
17
|
+
const log = (...a) => DEBUG && process.stderr.write(`[brevix-shrink] ${a.join(" ")}\n`);
|
|
18
|
+
|
|
19
|
+
const args = process.argv.slice(2);
|
|
20
|
+
if (args.length === 0) {
|
|
21
|
+
process.stderr.write(
|
|
22
|
+
"brevix-shrink: missing upstream command.\n" +
|
|
23
|
+
"Usage: brevix-shrink <command> [args...]\n"
|
|
24
|
+
);
|
|
25
|
+
process.exit(2);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const [cmd, ...rest] = args;
|
|
29
|
+
const child = spawn(cmd, rest, { stdio: ["pipe", "pipe", "inherit"] });
|
|
30
|
+
|
|
31
|
+
child.on("exit", (code) => process.exit(code ?? 0));
|
|
32
|
+
child.on("error", (err) => {
|
|
33
|
+
process.stderr.write(`brevix-shrink: failed to start upstream: ${err.message}\n`);
|
|
34
|
+
process.exit(127);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Track in-flight requests so we know which response method we're shrinking.
|
|
38
|
+
const pending = new Map();
|
|
39
|
+
|
|
40
|
+
const lineSplit = (chunk, buf) => {
|
|
41
|
+
buf.value += chunk.toString("utf8");
|
|
42
|
+
const out = [];
|
|
43
|
+
let idx;
|
|
44
|
+
while ((idx = buf.value.indexOf("\n")) !== -1) {
|
|
45
|
+
out.push(buf.value.slice(0, idx));
|
|
46
|
+
buf.value = buf.value.slice(idx + 1);
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Client → child: forward verbatim, but remember each request's method.
|
|
52
|
+
const reqBuf = { value: "" };
|
|
53
|
+
process.stdin.on("data", (chunk) => {
|
|
54
|
+
for (const line of lineSplit(chunk, reqBuf)) {
|
|
55
|
+
const trimmed = line.trim();
|
|
56
|
+
if (!trimmed) {
|
|
57
|
+
child.stdin.write("\n");
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const msg = JSON.parse(trimmed);
|
|
62
|
+
if (msg && msg.id !== undefined && msg.method) {
|
|
63
|
+
pending.set(msg.id, msg);
|
|
64
|
+
log("→", msg.method, msg.id);
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
/* not json — passthrough */
|
|
68
|
+
}
|
|
69
|
+
child.stdin.write(line + "\n");
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
process.stdin.on("end", () => child.stdin.end());
|
|
73
|
+
|
|
74
|
+
// Child → client: compress tools/list etc. responses.
|
|
75
|
+
const respBuf = { value: "" };
|
|
76
|
+
child.stdout.on("data", (chunk) => {
|
|
77
|
+
for (const line of lineSplit(chunk, respBuf)) {
|
|
78
|
+
const trimmed = line.trim();
|
|
79
|
+
if (!trimmed) {
|
|
80
|
+
process.stdout.write("\n");
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
let msg;
|
|
84
|
+
try {
|
|
85
|
+
msg = JSON.parse(trimmed);
|
|
86
|
+
} catch {
|
|
87
|
+
process.stdout.write(line + "\n");
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (msg && msg.id !== undefined && pending.has(msg.id)) {
|
|
91
|
+
const req = pending.get(msg.id);
|
|
92
|
+
pending.delete(msg.id);
|
|
93
|
+
maybeShrinkResponse(req, msg);
|
|
94
|
+
log("←", req.method, msg.id, "(shrunk)");
|
|
95
|
+
}
|
|
96
|
+
process.stdout.write(JSON.stringify(msg) + "\n");
|
|
97
|
+
}
|
|
98
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "brevix-shrink",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "MCP middleware proxy that compresses tools/list, prompts/list, and resources/list responses to save tokens. Sister project to Brevix.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"brevix-shrink": "./index.js"
|
|
7
|
+
},
|
|
8
|
+
"main": "index.js",
|
|
9
|
+
"type": "module",
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=18"
|
|
12
|
+
},
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"author": "Yash Koladiya <yashkoladiya123@gmail.com>",
|
|
15
|
+
"homepage": "https://github.com/Yash-Koladiya30/brevix",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/Yash-Koladiya30/brevix.git",
|
|
19
|
+
"directory": "mcp-servers/brevix-shrink"
|
|
20
|
+
},
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/Yash-Koladiya30/brevix/issues"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"mcp",
|
|
26
|
+
"claude",
|
|
27
|
+
"compression",
|
|
28
|
+
"tokens",
|
|
29
|
+
"brevix",
|
|
30
|
+
"proxy",
|
|
31
|
+
"model-context-protocol"
|
|
32
|
+
],
|
|
33
|
+
"files": [
|
|
34
|
+
"index.js",
|
|
35
|
+
"compress.js",
|
|
36
|
+
"README.md"
|
|
37
|
+
],
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
}
|
|
41
|
+
}
|