compendium-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +295 -0
- package/bin/run.js +201 -0
- package/npm/lib/platform.js +115 -0
- package/package.json +46 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Compendium contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
# Compendium
|
|
2
|
+
|
|
3
|
+
MCP server that **minimizes LLM token usage** by compressing, summarizing, filtering, and chunk-referencing large context before it reaches the model.
|
|
4
|
+
|
|
5
|
+
Built in Rust with the official [`rmcp`](https://crates.io/crates/rmcp) SDK.
|
|
6
|
+
|
|
7
|
+
## Install via npm / npx
|
|
8
|
+
|
|
9
|
+
Package name: **`compendium-mcp`** (the npm name `compendium` is taken). CLI bin: `compendium`.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx -y compendium-mcp --help
|
|
13
|
+
# or: npm install -g compendium-mcp
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
**Cursor / Claude Desktop**
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"mcpServers": {
|
|
21
|
+
"compendium": {
|
|
22
|
+
"command": "npx",
|
|
23
|
+
"args": ["-y", "compendium-mcp"]
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Distribution uses **optional platform packages** (`compendium-mcp-darwin-arm64`, …) with a **GitHub Releases download fallback**. See [npm/DISTRIBUTION.md](npm/DISTRIBUTION.md) for local testing, CI release, and publishing.
|
|
30
|
+
|
|
31
|
+
## Transports
|
|
32
|
+
|
|
33
|
+
| Mode | Command | Notes |
|
|
34
|
+
|------|---------|-------|
|
|
35
|
+
| **stdio** (default) | `compendium` / `compendium stdio` | Cursor / Claude Desktop |
|
|
36
|
+
| **Streamable HTTP/SSE** | `compendium http [BIND]` | Requires `--features http`. Endpoint: `http://{bind}/mcp` |
|
|
37
|
+
|
|
38
|
+
Default HTTP bind: `127.0.0.1:8788` (override with arg or `COMPENDIUM_HTTP_BIND`).
|
|
39
|
+
|
|
40
|
+
## Tools
|
|
41
|
+
|
|
42
|
+
Single MCP tool: **`compendium`**. Choose the operation with `action`:
|
|
43
|
+
|
|
44
|
+
| `action` | Purpose | Main fields |
|
|
45
|
+
|----------|---------|-------------|
|
|
46
|
+
| `filter` | Strip ANSI, boilerplate, whitespace; densify JSON; keep/drop regexes | `text`, `filter` |
|
|
47
|
+
| `compress` | Dense representation of text/code/logs | `text`, `compress` |
|
|
48
|
+
| `compress_output` | Domain-aware stdout/stderr scrub (git, cargo, npm, docker, …) | `text`, `output` |
|
|
49
|
+
| `summarize` | Hierarchical summary (conversation / file tree / outline) | `text`, `summarize` |
|
|
50
|
+
| `summarize_smart` | Local-SLM dense summary (heuristic fallback if unset/fails) | `text`, `smart?`, `summarize?` |
|
|
51
|
+
| `filter_relevant` | Query-aware keep of relevant lines (local SLM + heuristic fallback) | `text`, `query`, `smart?` |
|
|
52
|
+
| `prune_history` | Drop filler / compress older chat turns | `text` or `messages`, `prune` |
|
|
53
|
+
| `chunk` | Split into `cmp://` chunks (session-cached) | `text`, `chunk` |
|
|
54
|
+
| `resolve` | Fetch chunk content by id | `id` (+ optional `map` / `text`) |
|
|
55
|
+
| `count_tokens` | Measure tokens | `text` |
|
|
56
|
+
| `stats` | Session savings + latency/bypass/backend telemetry | `reset?` |
|
|
57
|
+
| `cache_store` | Park bulky payload outside the prompt | `text`, `cache` |
|
|
58
|
+
| `cache_get` | Retrieve by key | `key` |
|
|
59
|
+
| `cache_invalidate` | Drop one key or clear cache | `key?` |
|
|
60
|
+
| `sanitize` | Redact secrets + neutralize IPI phrases | `text`, `sanitize?` |
|
|
61
|
+
| `rerank` | BM25-rank candidates / chunks for a query | `query`, `items` or `text` or chunk `map`, `rerank?` |
|
|
62
|
+
|
|
63
|
+
Optional on most text actions: `sanitize_input: true` scrubs before processing. Soft payloads under `COMPENDIUM_SIGNAL_MIN_CHARS` (default 1000) bypass `compress` / `summarize` / `summarize_smart` unless `force: true`.
|
|
64
|
+
|
|
65
|
+
`filter` accepts optional `query` (top-level or `filter.query`) for BM25 line keep. `prune_history` supports `prune.strategy: "afm"` (Critical / Thematic / Distant tiers; distant blob cached for `cache_get`).
|
|
66
|
+
|
|
67
|
+
Example:
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"action": "filter",
|
|
72
|
+
"text": "…noisy log…",
|
|
73
|
+
"filter": { "strip_ansi": true, "keep_patterns": ["ERROR|WARN"] }
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Response envelope: `{ "ok": true, "action": "filter", "result_json": "{...}" }`. Parse `result_json` as JSON for the action-specific payload.
|
|
78
|
+
|
|
79
|
+
## Project layout
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
package.json / bin/run.js # npm wrapper for npx compendium-mcp
|
|
83
|
+
npm/ # platform packages + distribution docs
|
|
84
|
+
.github/workflows/ # release cross-compile + npm publish
|
|
85
|
+
src/
|
|
86
|
+
main.rs # CLI: stdio | http
|
|
87
|
+
lib.rs
|
|
88
|
+
config.rs # COMPENDIUM_* env config
|
|
89
|
+
server.rs # MCP tool handlers (rmcp macros)
|
|
90
|
+
http.rs # Streamable HTTP/SSE (feature = "http")
|
|
91
|
+
pipeline/
|
|
92
|
+
tokens.rs # heuristic or tiktoken BPE (feature = "real-tokens")
|
|
93
|
+
filter.rs
|
|
94
|
+
compress.rs
|
|
95
|
+
summarize.rs
|
|
96
|
+
smart.rs # summarize_smart + filter_relevant
|
|
97
|
+
local_llm.rs # OpenAI-compatible local SLM client
|
|
98
|
+
chunk.rs # chunk + resolve
|
|
99
|
+
cache.rs # session key/value cache
|
|
100
|
+
stats.rs # session savings counters
|
|
101
|
+
prune.rs # conversation history pruning
|
|
102
|
+
output.rs # domain-aware compress_output
|
|
103
|
+
tests/
|
|
104
|
+
integration.rs
|
|
105
|
+
e2e_smoke.rs # spawns binary, MCP handshake, all tools
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Build
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
# Default: heuristic tokens + stdio only
|
|
112
|
+
cargo build --release
|
|
113
|
+
|
|
114
|
+
# Exact BPE token counts (tiktoken-rs)
|
|
115
|
+
cargo build --release --features real-tokens
|
|
116
|
+
|
|
117
|
+
# Streamable HTTP transport
|
|
118
|
+
cargo build --release --features http
|
|
119
|
+
|
|
120
|
+
# Everything
|
|
121
|
+
cargo build --release --features real-tokens,http
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Binary: `target/release/compendium`
|
|
125
|
+
|
|
126
|
+
## Configure (Cursor / Claude Desktop)
|
|
127
|
+
|
|
128
|
+
### Cursor (`~/.cursor/mcp.json` or project `.cursor/mcp.json`)
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
{
|
|
132
|
+
"mcpServers": {
|
|
133
|
+
"compendium": {
|
|
134
|
+
"command": "npx",
|
|
135
|
+
"args": ["-y", "compendium-mcp"],
|
|
136
|
+
"env": {
|
|
137
|
+
"RUST_LOG": "compendium=info",
|
|
138
|
+
"COMPENDIUM_DEFAULT_MAX_TOKENS": "2048",
|
|
139
|
+
"COMPENDIUM_TOKENIZER": "cl100k_base"
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Or point at a local release binary:
|
|
147
|
+
|
|
148
|
+
```json
|
|
149
|
+
{
|
|
150
|
+
"mcpServers": {
|
|
151
|
+
"compendium": {
|
|
152
|
+
"command": "/absolute/path/to/Compendium/target/release/compendium",
|
|
153
|
+
"env": {
|
|
154
|
+
"RUST_LOG": "compendium=info",
|
|
155
|
+
"COMPENDIUM_DEFAULT_MAX_TOKENS": "2048",
|
|
156
|
+
"COMPENDIUM_CHARS_PER_TOKEN": "4.0",
|
|
157
|
+
"COMPENDIUM_TOKENIZER": "cl100k_base"
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Remote / sidecar (HTTP)
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
cargo run --features http -- http 127.0.0.1:8788
|
|
168
|
+
# MCP endpoint: http://127.0.0.1:8788/mcp
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Point an MCP streamable-HTTP client at that URL (e.g. `StreamableHttpClientTransport::from_uri`).
|
|
172
|
+
|
|
173
|
+
## Environment
|
|
174
|
+
|
|
175
|
+
| Variable | Default | Meaning |
|
|
176
|
+
|----------|---------|---------|
|
|
177
|
+
| `COMPENDIUM_CHARS_PER_TOKEN` | `4.0` | Heuristic chars÷tokens (ignored with `real-tokens`) |
|
|
178
|
+
| `COMPENDIUM_TOKENIZER` | `cl100k_base` | BPE encoding: `cl100k_base` or `o200k_base` (`real-tokens`) |
|
|
179
|
+
| `COMPENDIUM_DEFAULT_MAX_TOKENS` | `2048` | Soft cap for compress |
|
|
180
|
+
| `COMPENDIUM_MAX_BLANK_LINES` | `1` | Blank-line collapse limit |
|
|
181
|
+
| `COMPENDIUM_SIMILARITY_THRESHOLD` | `0.85` | Jaccard line-dedupe threshold |
|
|
182
|
+
| `COMPENDIUM_HTTP_BIND` | `127.0.0.1:8788` | Default HTTP listen address |
|
|
183
|
+
| `COMPENDIUM_LOCAL_LLM_URL` | _(unset)_ | OpenAI-compatible base URL (e.g. `http://127.0.0.1:11434/v1` or `http://127.0.0.1:13305/api/v1`). Enables smart actions. |
|
|
184
|
+
| `COMPENDIUM_LOCAL_LLM_MODEL` | `Qwen3-4B-GGUF` | Model id accepted by the local server |
|
|
185
|
+
| `COMPENDIUM_LOCAL_LLM_API_KEY` | _(unset)_ | Optional bearer token for locked loopback servers |
|
|
186
|
+
| `COMPENDIUM_LOCAL_LLM_TIMEOUT_SECS` | `120` | HTTP timeout (first model load can be slow) |
|
|
187
|
+
| `COMPENDIUM_SIGNAL_MIN_CHARS` | `1000` | Bypass compress/summarize below this length (`0` disables) |
|
|
188
|
+
| `RUST_LOG` | `compendium=info` | Logs on **stderr** only |
|
|
189
|
+
|
|
190
|
+
## Example tool calls
|
|
191
|
+
|
|
192
|
+
**Filter noisy terminal output**
|
|
193
|
+
|
|
194
|
+
```json
|
|
195
|
+
{
|
|
196
|
+
"name": "compendium_filter",
|
|
197
|
+
"arguments": {
|
|
198
|
+
"text": "\u001b[31mERROR\u001b[0m boom\n\n\nINFO ok",
|
|
199
|
+
"options": {
|
|
200
|
+
"strip_ansi": true,
|
|
201
|
+
"keep_patterns": ["ERROR|WARN"]
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**Compress a large log**
|
|
208
|
+
|
|
209
|
+
```json
|
|
210
|
+
{
|
|
211
|
+
"name": "compendium_compress",
|
|
212
|
+
"arguments": {
|
|
213
|
+
"text": "...",
|
|
214
|
+
"options": {
|
|
215
|
+
"content_type": "log",
|
|
216
|
+
"max_tokens": 512
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**Chunk a document into references**
|
|
223
|
+
|
|
224
|
+
```json
|
|
225
|
+
{
|
|
226
|
+
"name": "compendium_chunk",
|
|
227
|
+
"arguments": {
|
|
228
|
+
"text": "... huge file ...",
|
|
229
|
+
"options": {
|
|
230
|
+
"source": "file:///path/to/doc.md",
|
|
231
|
+
"chunk_tokens": 400,
|
|
232
|
+
"overlap_tokens": 40
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Prefer the returned `index_text` in the model context; pull individual chunk contents by id only when needed.
|
|
239
|
+
|
|
240
|
+
**Query-aware filter (local SLM or heuristic fallback)**
|
|
241
|
+
|
|
242
|
+
```json
|
|
243
|
+
{
|
|
244
|
+
"action": "filter_relevant",
|
|
245
|
+
"text": "... noisy cargo/test log ...",
|
|
246
|
+
"query": "why did the auth tests fail",
|
|
247
|
+
"smart": { "max_tokens": 512, "fallback": true }
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Without `COMPENDIUM_LOCAL_LLM_URL`, `summarize_smart` / `filter_relevant` automatically use heuristics and set `backend: "heuristic"` plus `fallback_reason` in the result.
|
|
252
|
+
|
|
253
|
+
## Local small language model
|
|
254
|
+
|
|
255
|
+
Smart actions call a **loopback-only** OpenAI-compatible chat endpoint — never a cloud API. `COMPENDIUM_LOCAL_LLM_URL` must be `127.0.0.1`, `::1`, or `localhost` (SSRF guard). Requests use `temperature=0` and `seed=0` for stable outputs. Point Compendium at Ollama, Lemonade, or llama.cpp:
|
|
256
|
+
|
|
257
|
+
```json
|
|
258
|
+
{
|
|
259
|
+
"mcpServers": {
|
|
260
|
+
"compendium": {
|
|
261
|
+
"command": "npx",
|
|
262
|
+
"args": ["-y", "compendium-mcp"],
|
|
263
|
+
"env": {
|
|
264
|
+
"COMPENDIUM_LOCAL_LLM_URL": "http://127.0.0.1:11434/v1",
|
|
265
|
+
"COMPENDIUM_LOCAL_LLM_MODEL": "qwen2.5:3b"
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Lemonade example: `COMPENDIUM_LOCAL_LLM_URL=http://127.0.0.1:13305/api/v1` and `COMPENDIUM_LOCAL_LLM_MODEL=Qwen3-4B-GGUF`.
|
|
273
|
+
|
|
274
|
+
## Develop / test
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
cargo test
|
|
278
|
+
cargo test --features real-tokens
|
|
279
|
+
cargo test --features http --test http_smoke
|
|
280
|
+
cargo test --test e2e_smoke
|
|
281
|
+
cargo run --features http -- http 127.0.0.1:8788
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
`e2e_smoke` spawns `CARGO_BIN_EXE_compendium`, completes the MCP initialize handshake over stdio, lists tools, then calls gateway actions. `http_smoke` (requires `--features http`) exercises streamable HTTP in-process.
|
|
285
|
+
|
|
286
|
+
## Design notes
|
|
287
|
+
|
|
288
|
+
- **Deterministic by default** — heuristic pipeline needs no network; smart actions only call a configured **local** OpenAI-compatible URL and fall back to heuristics when unset or failing.
|
|
289
|
+
- **Token backends** — fast heuristic by default; opt into exact BPE with `real-tokens`.
|
|
290
|
+
- **Zero stdout pollution** (stdio mode) — tracing goes to stderr so JSON-RPC framing stays clean.
|
|
291
|
+
- **Release profile** — LTO + stripped binary for low footprint.
|
|
292
|
+
|
|
293
|
+
## License
|
|
294
|
+
|
|
295
|
+
MIT
|
package/bin/run.js
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Compendium MCP dispatcher.
|
|
6
|
+
*
|
|
7
|
+
* Resolves the native binary and exec's it with inherited stdio so Cursor /
|
|
8
|
+
* Claude Desktop / `npx compendium-mcp` talk JSON-RPC over stdin/stdout.
|
|
9
|
+
*
|
|
10
|
+
* Resolution order:
|
|
11
|
+
* 1. COMPENDIUM_BINARY env
|
|
12
|
+
* 2. Dev build: <repo>/target/release|debug/compendium(.exe)
|
|
13
|
+
* 3. optionalDependency platform package (compendium-mcp-<platform>)
|
|
14
|
+
* 4. Cached GitHub Release download (~/.cache/compendium-mcp/<version>/)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const { spawn } = require('child_process');
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const https = require('https');
|
|
20
|
+
const http = require('http');
|
|
21
|
+
const path = require('path');
|
|
22
|
+
const { pipeline } = require('stream/promises');
|
|
23
|
+
const { createWriteStream } = require('fs');
|
|
24
|
+
|
|
25
|
+
const {
|
|
26
|
+
packageVersion,
|
|
27
|
+
githubRepo,
|
|
28
|
+
currentPlatform,
|
|
29
|
+
binaryName,
|
|
30
|
+
cacheDir,
|
|
31
|
+
platformKey,
|
|
32
|
+
} = require('../npm/lib/platform');
|
|
33
|
+
|
|
34
|
+
function existsExecutable(file) {
|
|
35
|
+
try {
|
|
36
|
+
fs.accessSync(file, fs.constants.F_OK);
|
|
37
|
+
// On Windows, X_OK is unreliable; presence is enough.
|
|
38
|
+
if (process.platform !== 'win32') {
|
|
39
|
+
fs.accessSync(file, fs.constants.X_OK);
|
|
40
|
+
}
|
|
41
|
+
return true;
|
|
42
|
+
} catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function tryRequirePlatformBinary() {
|
|
48
|
+
const spec = currentPlatform();
|
|
49
|
+
try {
|
|
50
|
+
// optionalDependency installs next to this package when available.
|
|
51
|
+
const pkgRoot = path.dirname(require.resolve(`${spec.pkg}/package.json`));
|
|
52
|
+
const candidate = path.join(pkgRoot, 'bin', binaryName(spec));
|
|
53
|
+
if (existsExecutable(candidate)) return candidate;
|
|
54
|
+
} catch {
|
|
55
|
+
// optional dep missing — fall through
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function tryDevBinary() {
|
|
61
|
+
const repoRoot = path.resolve(__dirname, '..');
|
|
62
|
+
const name = process.platform === 'win32' ? 'compendium.exe' : 'compendium';
|
|
63
|
+
const release = path.join(repoRoot, 'target', 'release', name);
|
|
64
|
+
const debug = path.join(repoRoot, 'target', 'debug', name);
|
|
65
|
+
if (existsExecutable(release)) return release;
|
|
66
|
+
if (existsExecutable(debug)) return debug;
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function download(url, dest) {
|
|
71
|
+
return new Promise((resolve, reject) => {
|
|
72
|
+
const client = url.startsWith('https:') ? https : http;
|
|
73
|
+
const req = client.get(
|
|
74
|
+
url,
|
|
75
|
+
{
|
|
76
|
+
headers: {
|
|
77
|
+
'User-Agent': `compendium-mcp/${packageVersion()}`,
|
|
78
|
+
Accept: 'application/octet-stream',
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
(res) => {
|
|
82
|
+
// Follow one redirect hop (GitHub release assets).
|
|
83
|
+
if (
|
|
84
|
+
res.statusCode >= 300 &&
|
|
85
|
+
res.statusCode < 400 &&
|
|
86
|
+
res.headers.location
|
|
87
|
+
) {
|
|
88
|
+
res.resume();
|
|
89
|
+
download(res.headers.location, dest).then(resolve, reject);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (res.statusCode !== 200) {
|
|
93
|
+
res.resume();
|
|
94
|
+
reject(
|
|
95
|
+
new Error(
|
|
96
|
+
`Download failed (${res.statusCode}) for ${url}. ` +
|
|
97
|
+
`Ensure release assets exist for ${platformKey()}.`
|
|
98
|
+
)
|
|
99
|
+
);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const tmp = `${dest}.partial`;
|
|
103
|
+
const out = createWriteStream(tmp);
|
|
104
|
+
pipeline(res, out)
|
|
105
|
+
.then(() => {
|
|
106
|
+
fs.renameSync(tmp, dest);
|
|
107
|
+
if (process.platform !== 'win32') {
|
|
108
|
+
fs.chmodSync(dest, 0o755);
|
|
109
|
+
}
|
|
110
|
+
resolve(dest);
|
|
111
|
+
})
|
|
112
|
+
.catch((err) => {
|
|
113
|
+
try {
|
|
114
|
+
fs.unlinkSync(tmp);
|
|
115
|
+
} catch {
|
|
116
|
+
/* ignore */
|
|
117
|
+
}
|
|
118
|
+
reject(err);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
);
|
|
122
|
+
req.on('error', reject);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function ensureDownloadedBinary() {
|
|
127
|
+
const spec = currentPlatform();
|
|
128
|
+
const version = packageVersion();
|
|
129
|
+
const dir = cacheDir(version);
|
|
130
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
131
|
+
const dest = path.join(dir, binaryName(spec));
|
|
132
|
+
if (existsExecutable(dest)) return dest;
|
|
133
|
+
|
|
134
|
+
const repo = githubRepo();
|
|
135
|
+
const tag = `v${version}`;
|
|
136
|
+
const url = `https://github.com/${repo}/releases/download/${tag}/${spec.asset}`;
|
|
137
|
+
|
|
138
|
+
process.stderr.write(
|
|
139
|
+
`[compendium-mcp] Downloading ${spec.asset} (${tag})…\n`
|
|
140
|
+
);
|
|
141
|
+
await download(url, dest);
|
|
142
|
+
process.stderr.write(`[compendium-mcp] Cached at ${dest}\n`);
|
|
143
|
+
return dest;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function resolveBinary() {
|
|
147
|
+
if (process.env.COMPENDIUM_BINARY) {
|
|
148
|
+
const forced = path.resolve(process.env.COMPENDIUM_BINARY);
|
|
149
|
+
if (!existsExecutable(forced)) {
|
|
150
|
+
throw new Error(`COMPENDIUM_BINARY not executable: ${forced}`);
|
|
151
|
+
}
|
|
152
|
+
return forced;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Prefer a local Cargo build when developing from this repo so `cargo build
|
|
156
|
+
// --release` is picked up immediately (optional platform packages can lag).
|
|
157
|
+
const fromDev = tryDevBinary();
|
|
158
|
+
if (fromDev) return fromDev;
|
|
159
|
+
|
|
160
|
+
const fromOptional = tryRequirePlatformBinary();
|
|
161
|
+
if (fromOptional) return fromOptional;
|
|
162
|
+
|
|
163
|
+
return ensureDownloadedBinary();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function main() {
|
|
167
|
+
let binary;
|
|
168
|
+
try {
|
|
169
|
+
binary = await resolveBinary();
|
|
170
|
+
} catch (err) {
|
|
171
|
+
process.stderr.write(`[compendium-mcp] ${err.message}\n`);
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
process.stderr.write(`[compendium-mcp] using ${binary}\n`);
|
|
176
|
+
|
|
177
|
+
const child = spawn(binary, process.argv.slice(2), {
|
|
178
|
+
stdio: 'inherit',
|
|
179
|
+
windowsHide: true,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
child.on('error', (err) => {
|
|
183
|
+
process.stderr.write(`[compendium-mcp] failed to spawn ${binary}: ${err.message}\n`);
|
|
184
|
+
process.exit(1);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const forward = (signal) => {
|
|
188
|
+
if (!child.killed) child.kill(signal);
|
|
189
|
+
};
|
|
190
|
+
process.on('SIGINT', () => forward('SIGINT'));
|
|
191
|
+
process.on('SIGTERM', () => forward('SIGTERM'));
|
|
192
|
+
|
|
193
|
+
child.on('exit', (code, signal) => {
|
|
194
|
+
if (signal) {
|
|
195
|
+
process.exit(signal === 'SIGINT' ? 130 : 1);
|
|
196
|
+
}
|
|
197
|
+
process.exit(code ?? 1);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
main();
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared platform → package / release-asset mapping for Compendium npm distribution.
|
|
5
|
+
*
|
|
6
|
+
* Distribution strategy (hybrid):
|
|
7
|
+
* 1. Prefer optionalDependency platform packages (esbuild-style) — offline, fast.
|
|
8
|
+
* 2. Fall back to downloading the matching GitHub Release asset into a local cache.
|
|
9
|
+
* 3. Dev override: COMPENDIUM_BINARY or ./target/release/compendium.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const os = require('os');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
/** @typedef {{ pkg: string, asset: string, rustTarget: string }} PlatformSpec */
|
|
16
|
+
|
|
17
|
+
/** @type {Record<string, PlatformSpec>} */
|
|
18
|
+
const PLATFORMS = {
|
|
19
|
+
'darwin-arm64': {
|
|
20
|
+
pkg: 'compendium-mcp-darwin-arm64',
|
|
21
|
+
asset: 'compendium-darwin-arm64',
|
|
22
|
+
rustTarget: 'aarch64-apple-darwin',
|
|
23
|
+
},
|
|
24
|
+
'darwin-x64': {
|
|
25
|
+
pkg: 'compendium-mcp-darwin-x64',
|
|
26
|
+
asset: 'compendium-darwin-x64',
|
|
27
|
+
rustTarget: 'x86_64-apple-darwin',
|
|
28
|
+
},
|
|
29
|
+
'linux-x64': {
|
|
30
|
+
pkg: 'compendium-mcp-linux-x64',
|
|
31
|
+
asset: 'compendium-linux-x64',
|
|
32
|
+
rustTarget: 'x86_64-unknown-linux-gnu',
|
|
33
|
+
},
|
|
34
|
+
'linux-arm64': {
|
|
35
|
+
pkg: 'compendium-mcp-linux-arm64',
|
|
36
|
+
asset: 'compendium-linux-arm64',
|
|
37
|
+
rustTarget: 'aarch64-unknown-linux-gnu',
|
|
38
|
+
},
|
|
39
|
+
'win32-x64': {
|
|
40
|
+
pkg: 'compendium-mcp-win32-x64',
|
|
41
|
+
asset: 'compendium-win32-x64.exe',
|
|
42
|
+
rustTarget: 'x86_64-pc-windows-msvc',
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
function packageVersion() {
|
|
47
|
+
// Keep in sync with root package.json / Cargo.toml at publish time.
|
|
48
|
+
return require('../../package.json').version;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function githubRepo() {
|
|
52
|
+
return (
|
|
53
|
+
process.env.COMPENDIUM_GITHUB_REPO ||
|
|
54
|
+
process.env.GITHUB_REPOSITORY ||
|
|
55
|
+
'hocestnonsatis/Compendium'
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Normalize Node's process.platform + process.arch into our key.
|
|
61
|
+
* @returns {string}
|
|
62
|
+
*/
|
|
63
|
+
function platformKey() {
|
|
64
|
+
const platform = process.platform;
|
|
65
|
+
let arch = process.arch;
|
|
66
|
+
// Rosetta / rare aliases
|
|
67
|
+
if (arch === 'ia32') arch = 'x64';
|
|
68
|
+
return `${platform}-${arch}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @returns {PlatformSpec}
|
|
73
|
+
*/
|
|
74
|
+
function currentPlatform() {
|
|
75
|
+
const key = platformKey();
|
|
76
|
+
const spec = PLATFORMS[key];
|
|
77
|
+
if (!spec) {
|
|
78
|
+
const supported = Object.keys(PLATFORMS).join(', ');
|
|
79
|
+
throw new Error(
|
|
80
|
+
`Unsupported platform "${key}". Supported: ${supported}.\n` +
|
|
81
|
+
`Set COMPENDIUM_BINARY to a local build, or open an issue for this target.`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
return spec;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Binary filename inside a platform package / cache.
|
|
89
|
+
* @param {PlatformSpec} [spec]
|
|
90
|
+
*/
|
|
91
|
+
function binaryName(spec = currentPlatform()) {
|
|
92
|
+
return process.platform === 'win32' ? 'compendium.exe' : 'compendium';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Cache directory for lazily downloaded binaries.
|
|
97
|
+
* @param {string} version
|
|
98
|
+
*/
|
|
99
|
+
function cacheDir(version = packageVersion()) {
|
|
100
|
+
const base =
|
|
101
|
+
process.env.COMPENDIUM_CACHE_DIR ||
|
|
102
|
+
process.env.XDG_CACHE_HOME ||
|
|
103
|
+
path.join(os.homedir(), '.cache');
|
|
104
|
+
return path.join(base, 'compendium-mcp', version);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
module.exports = {
|
|
108
|
+
PLATFORMS,
|
|
109
|
+
packageVersion,
|
|
110
|
+
githubRepo,
|
|
111
|
+
platformKey,
|
|
112
|
+
currentPlatform,
|
|
113
|
+
binaryName,
|
|
114
|
+
cacheDir,
|
|
115
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "compendium-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server that compresses, summarizes, and filters context to minimize LLM token usage",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"mcp",
|
|
8
|
+
"model-context-protocol",
|
|
9
|
+
"llm",
|
|
10
|
+
"tokens",
|
|
11
|
+
"compression",
|
|
12
|
+
"cursor",
|
|
13
|
+
"claude"
|
|
14
|
+
],
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/hocestnonsatis/Compendium.git"
|
|
18
|
+
},
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/hocestnonsatis/Compendium/issues"
|
|
21
|
+
},
|
|
22
|
+
"homepage": "https://github.com/hocestnonsatis/Compendium#readme",
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=18"
|
|
25
|
+
},
|
|
26
|
+
"bin": {
|
|
27
|
+
"compendium": "bin/run.js"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"bin/",
|
|
31
|
+
"npm/lib/",
|
|
32
|
+
"README.md",
|
|
33
|
+
"LICENSE"
|
|
34
|
+
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"prepack": "node npm/scripts/check-wrapper.js"
|
|
37
|
+
},
|
|
38
|
+
"optionalDependencies": {
|
|
39
|
+
"compendium-mcp-darwin-arm64": "0.1.0",
|
|
40
|
+
"compendium-mcp-darwin-x64": "0.1.0",
|
|
41
|
+
"compendium-mcp-linux-x64": "0.1.0",
|
|
42
|
+
"compendium-mcp-linux-arm64": "0.1.0",
|
|
43
|
+
"compendium-mcp-win32-x64": "0.1.0"
|
|
44
|
+
},
|
|
45
|
+
"preferUnplugged": true
|
|
46
|
+
}
|