context-compress 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/LICENSE +21 -0
- package/README.md +275 -0
- package/dist/cli/doctor.d.ts +2 -0
- package/dist/cli/doctor.d.ts.map +1 -0
- package/dist/cli/doctor.js +131 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/index.d.ts +12 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +31 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/setup.d.ts +2 -0
- package/dist/cli/setup.d.ts.map +1 -0
- package/dist/cli/setup.js +43 -0
- package/dist/cli/setup.js.map +1 -0
- package/dist/cli/uninstall.d.ts +2 -0
- package/dist/cli/uninstall.d.ts.map +1 -0
- package/dist/cli/uninstall.js +102 -0
- package/dist/cli/uninstall.js.map +1 -0
- package/dist/config.d.ts +37 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +79 -0
- package/dist/config.js.map +1 -0
- package/dist/executor.d.ts +21 -0
- package/dist/executor.d.ts.map +1 -0
- package/dist/executor.js +301 -0
- package/dist/executor.js.map +1 -0
- package/dist/hooks/pretooluse.d.ts +10 -0
- package/dist/hooks/pretooluse.d.ts.map +1 -0
- package/dist/hooks/pretooluse.js +117 -0
- package/dist/hooks/pretooluse.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +4 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +13 -0
- package/dist/logger.js.map +1 -0
- package/dist/runtime/index.d.ts +22 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +81 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/languages/elixir.d.ts +3 -0
- package/dist/runtime/languages/elixir.d.ts.map +1 -0
- package/dist/runtime/languages/elixir.js +15 -0
- package/dist/runtime/languages/elixir.js.map +1 -0
- package/dist/runtime/languages/go.d.ts +3 -0
- package/dist/runtime/languages/go.d.ts.map +1 -0
- package/dist/runtime/languages/go.js +29 -0
- package/dist/runtime/languages/go.js.map +1 -0
- package/dist/runtime/languages/javascript.d.ts +3 -0
- package/dist/runtime/languages/javascript.d.ts.map +1 -0
- package/dist/runtime/languages/javascript.js +13 -0
- package/dist/runtime/languages/javascript.js.map +1 -0
- package/dist/runtime/languages/perl.d.ts +3 -0
- package/dist/runtime/languages/perl.d.ts.map +1 -0
- package/dist/runtime/languages/perl.js +13 -0
- package/dist/runtime/languages/perl.js.map +1 -0
- package/dist/runtime/languages/php.d.ts +3 -0
- package/dist/runtime/languages/php.d.ts.map +1 -0
- package/dist/runtime/languages/php.js +24 -0
- package/dist/runtime/languages/php.js.map +1 -0
- package/dist/runtime/languages/python.d.ts +3 -0
- package/dist/runtime/languages/python.d.ts.map +1 -0
- package/dist/runtime/languages/python.js +13 -0
- package/dist/runtime/languages/python.js.map +1 -0
- package/dist/runtime/languages/r.d.ts +3 -0
- package/dist/runtime/languages/r.d.ts.map +1 -0
- package/dist/runtime/languages/r.js +13 -0
- package/dist/runtime/languages/r.js.map +1 -0
- package/dist/runtime/languages/ruby.d.ts +3 -0
- package/dist/runtime/languages/ruby.d.ts.map +1 -0
- package/dist/runtime/languages/ruby.js +13 -0
- package/dist/runtime/languages/ruby.js.map +1 -0
- package/dist/runtime/languages/rust.d.ts +3 -0
- package/dist/runtime/languages/rust.d.ts.map +1 -0
- package/dist/runtime/languages/rust.js +28 -0
- package/dist/runtime/languages/rust.js.map +1 -0
- package/dist/runtime/languages/shell.d.ts +3 -0
- package/dist/runtime/languages/shell.d.ts.map +1 -0
- package/dist/runtime/languages/shell.js +14 -0
- package/dist/runtime/languages/shell.js.map +1 -0
- package/dist/runtime/languages/typescript.d.ts +3 -0
- package/dist/runtime/languages/typescript.d.ts.map +1 -0
- package/dist/runtime/languages/typescript.js +15 -0
- package/dist/runtime/languages/typescript.js.map +1 -0
- package/dist/runtime/plugin.d.ts +38 -0
- package/dist/runtime/plugin.d.ts.map +1 -0
- package/dist/runtime/plugin.js +2 -0
- package/dist/runtime/plugin.js.map +1 -0
- package/dist/server.bundle.mjs +22769 -0
- package/dist/server.bundle.mjs.map +7 -0
- package/dist/server.d.ts +5 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +405 -0
- package/dist/server.js.map +1 -0
- package/dist/snippet.d.ts +15 -0
- package/dist/snippet.d.ts.map +1 -0
- package/dist/snippet.js +92 -0
- package/dist/snippet.js.map +1 -0
- package/dist/stats.d.ts +10 -0
- package/dist/stats.d.ts.map +1 -0
- package/dist/stats.js +65 -0
- package/dist/stats.js.map +1 -0
- package/dist/store.d.ts +44 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +590 -0
- package/dist/store.js.map +1 -0
- package/dist/types.d.ts +75 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +14 -0
- package/dist/types.js.map +1 -0
- package/hooks/hooks.json +52 -0
- package/hooks/pretooluse.mjs +94 -0
- package/package.json +55 -0
- package/skills/context-compress/SKILL.md +118 -0
- package/skills/doctor/SKILL.md +15 -0
- package/skills/stats/SKILL.md +11 -0
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAiC1C,wBAAsB,YAAY,CAAC,MAAM,EAAE,MAAM;;GA6ahD"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { SubprocessExecutor } from "./executor.js";
|
|
8
|
+
import { debug, error as logError } from "./logger.js";
|
|
9
|
+
import { detectRuntimes, hasBun } from "./runtime/index.js";
|
|
10
|
+
import { SessionTracker } from "./stats.js";
|
|
11
|
+
import { ContentStore, cleanupStaleDbs } from "./store.js";
|
|
12
|
+
const LANGUAGES = [
|
|
13
|
+
"javascript",
|
|
14
|
+
"typescript",
|
|
15
|
+
"python",
|
|
16
|
+
"shell",
|
|
17
|
+
"ruby",
|
|
18
|
+
"go",
|
|
19
|
+
"rust",
|
|
20
|
+
"php",
|
|
21
|
+
"perl",
|
|
22
|
+
"r",
|
|
23
|
+
"elixir",
|
|
24
|
+
];
|
|
25
|
+
function getVersion() {
|
|
26
|
+
try {
|
|
27
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
28
|
+
const pkgPath = join(__dirname, "..", "package.json");
|
|
29
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
30
|
+
return pkg.version ?? "1.0.0";
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return "1.0.0";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export async function createServer(config) {
|
|
37
|
+
const version = getVersion();
|
|
38
|
+
debug("Version:", version);
|
|
39
|
+
// Cleanup stale databases from previous sessions
|
|
40
|
+
cleanupStaleDbs();
|
|
41
|
+
// Detect runtimes in parallel
|
|
42
|
+
const runtimes = await detectRuntimes();
|
|
43
|
+
const bunDetected = hasBun(runtimes);
|
|
44
|
+
debug("Runtimes detected:", runtimes.size);
|
|
45
|
+
const executor = new SubprocessExecutor(runtimes, config);
|
|
46
|
+
const store = new ContentStore();
|
|
47
|
+
const tracker = new SessionTracker();
|
|
48
|
+
// Search throttling state
|
|
49
|
+
const searchCalls = [];
|
|
50
|
+
const server = new McpServer({
|
|
51
|
+
name: "context-compress",
|
|
52
|
+
version,
|
|
53
|
+
});
|
|
54
|
+
// ─── Tool: execute ──────────────────────────────────────
|
|
55
|
+
server.tool("execute", `Execute code in a sandboxed subprocess. Only stdout enters context — raw data stays in the subprocess. Use instead of bash/cat when output would exceed 20 lines. ${bunDetected ? "(Bun detected — JS/TS runs 3-5x faster) " : ""}Available: ${LANGUAGES.join(", ")}.
|
|
56
|
+
|
|
57
|
+
PREFER THIS OVER BASH for: API calls (gh, curl, aws), test runners (npm test, pytest), git queries (git log, git diff), data processing, and ANY CLI command that may produce large output. Bash should only be used for file mutations, git writes, and navigation.`, {
|
|
58
|
+
language: z.enum(LANGUAGES).describe("Runtime language"),
|
|
59
|
+
code: z
|
|
60
|
+
.string()
|
|
61
|
+
.describe("Source code to execute. Use console.log (JS/TS), print (Python/Ruby/Perl/R), echo (Shell), echo (PHP), fmt.Println (Go), or IO.puts (Elixir) to output a summary to context."),
|
|
62
|
+
intent: z
|
|
63
|
+
.string()
|
|
64
|
+
.optional()
|
|
65
|
+
.describe("What you're looking for in the output. When provided and output is large (>5KB), indexes output into knowledge base and returns section titles + previews — not full content. Use search(queries: [...]) to retrieve specific sections."),
|
|
66
|
+
timeout: z.number().default(30000).describe("Max execution time in ms"),
|
|
67
|
+
}, async ({ language, code, intent, timeout }) => {
|
|
68
|
+
const result = await executor.execute({ language, code, timeout });
|
|
69
|
+
if (result.networkBytes) {
|
|
70
|
+
tracker.trackSandboxed(result.networkBytes);
|
|
71
|
+
}
|
|
72
|
+
let output = result.stdout;
|
|
73
|
+
if (result.stderr && result.exitCode !== 0) {
|
|
74
|
+
output += `\n\nSTDERR:\n${result.stderr}`;
|
|
75
|
+
}
|
|
76
|
+
// Intent-driven filtering for large outputs
|
|
77
|
+
if (intent && Buffer.byteLength(output) > config.intentSearchThreshold) {
|
|
78
|
+
const indexed = store.index(output, `execute:${language}`);
|
|
79
|
+
tracker.trackIndexed(Buffer.byteLength(output));
|
|
80
|
+
const searchResults = store.search(intent, { limit: 3 });
|
|
81
|
+
const terms = store.getDistinctiveTerms(indexed.sourceId);
|
|
82
|
+
let filtered = `Indexed ${indexed.totalChunks} sections from execute output.\n`;
|
|
83
|
+
filtered += `${searchResults.results.length} sections matched "${intent}":\n\n`;
|
|
84
|
+
for (const hit of searchResults.results) {
|
|
85
|
+
filtered += ` - **${hit.title}**: ${hit.snippet.slice(0, 200)}\n`;
|
|
86
|
+
}
|
|
87
|
+
if (terms.length > 0) {
|
|
88
|
+
filtered += `\nSearchable terms: ${terms.join(", ")}\n`;
|
|
89
|
+
}
|
|
90
|
+
filtered += "\nUse search(queries: [...]) to retrieve full content of any section.";
|
|
91
|
+
output = filtered;
|
|
92
|
+
}
|
|
93
|
+
const responseBytes = Buffer.byteLength(output);
|
|
94
|
+
tracker.trackCall("execute", responseBytes);
|
|
95
|
+
return { content: [{ type: "text", text: output }] };
|
|
96
|
+
});
|
|
97
|
+
// ─── Tool: execute_file ─────────────────────────────────
|
|
98
|
+
server.tool("execute_file", "Read a file and process it without loading contents into context. The file is read into a FILE_CONTENT variable inside the sandbox. Only your printed summary enters context.\n\nPREFER THIS OVER Read/cat for: log files, data files (CSV, JSON, XML), large source files for analysis, and any file where you need to extract specific information rather than read the entire content.", {
|
|
99
|
+
path: z.string().describe("Absolute file path or relative to project root"),
|
|
100
|
+
language: z.enum(LANGUAGES).describe("Runtime language"),
|
|
101
|
+
code: z
|
|
102
|
+
.string()
|
|
103
|
+
.describe("Code to process FILE_CONTENT. Print summary via console.log/print/echo/IO.puts."),
|
|
104
|
+
intent: z.string().optional().describe("What you're looking for in the output."),
|
|
105
|
+
timeout: z.number().default(30000).describe("Max execution time in ms"),
|
|
106
|
+
}, async ({ path: filePath, language, code, intent, timeout }) => {
|
|
107
|
+
const absPath = resolve(process.env.CLAUDE_PROJECT_DIR ?? process.cwd(), filePath);
|
|
108
|
+
const result = await executor.executeFile({
|
|
109
|
+
language,
|
|
110
|
+
code,
|
|
111
|
+
filePath: absPath,
|
|
112
|
+
timeout,
|
|
113
|
+
});
|
|
114
|
+
let output = result.stdout;
|
|
115
|
+
if (result.stderr && result.exitCode !== 0) {
|
|
116
|
+
output += `\n\nSTDERR:\n${result.stderr}`;
|
|
117
|
+
}
|
|
118
|
+
// Intent-driven filtering
|
|
119
|
+
if (intent && Buffer.byteLength(output) > config.intentSearchThreshold) {
|
|
120
|
+
const indexed = store.index(output, `file:${filePath}`);
|
|
121
|
+
tracker.trackIndexed(Buffer.byteLength(output));
|
|
122
|
+
const searchResults = store.search(intent, { limit: 3 });
|
|
123
|
+
const terms = store.getDistinctiveTerms(indexed.sourceId);
|
|
124
|
+
let filtered = `Indexed ${indexed.totalChunks} sections from "${filePath}" into knowledge base.\n`;
|
|
125
|
+
filtered += `${searchResults.results.length} sections matched "${intent}":\n\n`;
|
|
126
|
+
for (const hit of searchResults.results) {
|
|
127
|
+
filtered += ` - **${hit.title}**: ${hit.snippet.slice(0, 200)}\n`;
|
|
128
|
+
}
|
|
129
|
+
if (terms.length > 0) {
|
|
130
|
+
filtered += `\nSearchable terms: ${terms.join(", ")}\n`;
|
|
131
|
+
}
|
|
132
|
+
filtered += "\nUse search(queries: [...]) to retrieve full content of any section.";
|
|
133
|
+
output = filtered;
|
|
134
|
+
}
|
|
135
|
+
const responseBytes = Buffer.byteLength(output);
|
|
136
|
+
tracker.trackCall("execute_file", responseBytes);
|
|
137
|
+
return { content: [{ type: "text", text: output }] };
|
|
138
|
+
});
|
|
139
|
+
// ─── Tool: index ────────────────────────────────────────
|
|
140
|
+
server.tool("index", "Index documentation or knowledge content into a searchable BM25 knowledge base. Chunks markdown by headings (keeping code blocks intact) and stores in ephemeral FTS5 database. The full content does NOT stay in context — only a brief summary is returned.\n\nWHEN TO USE:\n- Documentation (API docs, framework guides, code examples)\n- README files, migration guides, changelog entries\n- Any content with code examples you may need to reference precisely\n\nAfter indexing, use 'search' to retrieve specific sections on-demand.", {
|
|
141
|
+
content: z
|
|
142
|
+
.string()
|
|
143
|
+
.optional()
|
|
144
|
+
.describe("Raw text/markdown to index. Provide this OR path, not both."),
|
|
145
|
+
path: z
|
|
146
|
+
.string()
|
|
147
|
+
.optional()
|
|
148
|
+
.describe("File path to read and index (content never enters context)."),
|
|
149
|
+
source: z.string().optional().describe("Label for the indexed content"),
|
|
150
|
+
}, async ({ content, path: filePath, source }) => {
|
|
151
|
+
let text;
|
|
152
|
+
let label = source ?? "indexed content";
|
|
153
|
+
if (filePath) {
|
|
154
|
+
const absPath = resolve(process.env.CLAUDE_PROJECT_DIR ?? process.cwd(), filePath);
|
|
155
|
+
text = readFileSync(absPath, "utf-8");
|
|
156
|
+
label = source ?? filePath;
|
|
157
|
+
}
|
|
158
|
+
else if (content) {
|
|
159
|
+
text = content;
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
return {
|
|
163
|
+
content: [{ type: "text", text: "Error: provide either 'content' or 'path'" }],
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
const result = store.index(text, label);
|
|
167
|
+
tracker.trackIndexed(Buffer.byteLength(text));
|
|
168
|
+
const summary = `Indexed "${label}": ${result.totalChunks} chunks (${result.codeChunks} with code). Use search(queries: [...]) to retrieve sections.`;
|
|
169
|
+
tracker.trackCall("index", Buffer.byteLength(summary));
|
|
170
|
+
return { content: [{ type: "text", text: summary }] };
|
|
171
|
+
});
|
|
172
|
+
// ─── Tool: search ───────────────────────────────────────
|
|
173
|
+
server.tool("search", "Search indexed content. Pass ALL search questions as queries array in ONE call.\n\nTIPS: 2-4 specific terms per query. Use 'source' to scope results.", {
|
|
174
|
+
queries: z
|
|
175
|
+
.array(z.string())
|
|
176
|
+
.describe("Array of search queries. Batch ALL questions in one call."),
|
|
177
|
+
source: z
|
|
178
|
+
.string()
|
|
179
|
+
.optional()
|
|
180
|
+
.describe("Filter to a specific indexed source (partial match)."),
|
|
181
|
+
limit: z.number().default(3).describe("Results per query (default: 3)"),
|
|
182
|
+
}, async ({ queries, source, limit }) => {
|
|
183
|
+
// Progressive throttling
|
|
184
|
+
const now = Date.now();
|
|
185
|
+
searchCalls.push(now);
|
|
186
|
+
// Clean old entries outside window
|
|
187
|
+
while (searchCalls.length > 0 && searchCalls[0] < now - config.searchWindowMs) {
|
|
188
|
+
searchCalls.shift();
|
|
189
|
+
}
|
|
190
|
+
const callCount = searchCalls.length;
|
|
191
|
+
if (callCount > config.searchBlockAfter) {
|
|
192
|
+
const msg = "Too many search calls in quick succession. Use batch_execute instead to run commands and search in one call.";
|
|
193
|
+
tracker.trackCall("search", Buffer.byteLength(msg));
|
|
194
|
+
return { content: [{ type: "text", text: msg }] };
|
|
195
|
+
}
|
|
196
|
+
const effectiveLimit = callCount > config.searchReduceAfter ? 1 : Math.min(limit, config.searchLimit);
|
|
197
|
+
const allResults = [];
|
|
198
|
+
let totalBytes = 0;
|
|
199
|
+
for (const query of queries) {
|
|
200
|
+
if (totalBytes > config.searchMaxBytes)
|
|
201
|
+
break;
|
|
202
|
+
const result = store.search(query, { source, limit: effectiveLimit });
|
|
203
|
+
let block = `## ${query}\n`;
|
|
204
|
+
if (result.corrected) {
|
|
205
|
+
block += `(corrected to: "${result.corrected}")\n`;
|
|
206
|
+
}
|
|
207
|
+
if (result.results.length === 0) {
|
|
208
|
+
block += "No results found.\n";
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
for (const hit of result.results) {
|
|
212
|
+
block += `\n--- [${hit.source}] ---\n### ${hit.title}\n\n${hit.snippet}\n`;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
allResults.push(block);
|
|
216
|
+
totalBytes += Buffer.byteLength(block);
|
|
217
|
+
}
|
|
218
|
+
if (callCount > config.searchReduceAfter) {
|
|
219
|
+
allResults.push(`\n⚠ Search rate limited (${callCount} calls in ${config.searchWindowMs / 1000}s). Results reduced to 1 per query.`);
|
|
220
|
+
}
|
|
221
|
+
const output = allResults.join("\n---\n\n");
|
|
222
|
+
tracker.trackCall("search", Buffer.byteLength(output));
|
|
223
|
+
return { content: [{ type: "text", text: output }] };
|
|
224
|
+
});
|
|
225
|
+
// ─── Tool: fetch_and_index ──────────────────────────────
|
|
226
|
+
server.tool("fetch_and_index", "Fetches URL content, converts HTML to markdown, indexes into searchable knowledge base, and returns a ~3KB preview. Full content stays in sandbox — use search() for deeper lookups.\n\nBetter than WebFetch: preview is immediate, full content is searchable, raw HTML never enters context.", {
|
|
227
|
+
url: z.string().describe("The URL to fetch and index"),
|
|
228
|
+
source: z.string().optional().describe("Label for the indexed content"),
|
|
229
|
+
}, async ({ url, source }) => {
|
|
230
|
+
const label = source ?? url;
|
|
231
|
+
// Use executor to fetch and convert HTML to markdown in subprocess
|
|
232
|
+
const fetchCode = buildFetchCode(url);
|
|
233
|
+
const result = await executor.execute({
|
|
234
|
+
language: "javascript",
|
|
235
|
+
code: fetchCode,
|
|
236
|
+
timeout: 30_000,
|
|
237
|
+
});
|
|
238
|
+
if (result.exitCode !== 0 || !result.stdout.trim()) {
|
|
239
|
+
const errMsg = `Failed to fetch ${url}: ${result.stderr || "empty response"}`;
|
|
240
|
+
tracker.trackCall("fetch_and_index", Buffer.byteLength(errMsg));
|
|
241
|
+
return { content: [{ type: "text", text: errMsg }] };
|
|
242
|
+
}
|
|
243
|
+
const markdown = result.stdout;
|
|
244
|
+
tracker.trackSandboxed(result.networkBytes ?? 0);
|
|
245
|
+
const indexed = store.index(markdown, label);
|
|
246
|
+
tracker.trackIndexed(Buffer.byteLength(markdown));
|
|
247
|
+
// Return ~3KB preview
|
|
248
|
+
const preview = markdown.slice(0, 3072);
|
|
249
|
+
const terms = store.getDistinctiveTerms(indexed.sourceId);
|
|
250
|
+
let output = `Indexed "${label}": ${indexed.totalChunks} chunks.\n\n`;
|
|
251
|
+
output += `**Preview:**\n${preview}`;
|
|
252
|
+
if (markdown.length > 3072)
|
|
253
|
+
output += "\n…(truncated)";
|
|
254
|
+
if (terms.length > 0) {
|
|
255
|
+
output += `\n\nSearchable terms: ${terms.join(", ")}`;
|
|
256
|
+
}
|
|
257
|
+
output += "\n\nUse search(queries: [...]) to retrieve full content of any section.";
|
|
258
|
+
tracker.trackCall("fetch_and_index", Buffer.byteLength(output));
|
|
259
|
+
return { content: [{ type: "text", text: output }] };
|
|
260
|
+
});
|
|
261
|
+
// ─── Tool: batch_execute ────────────────────────────────
|
|
262
|
+
server.tool("batch_execute", "Execute multiple commands in ONE call, auto-index all output, and search with multiple queries. Returns search results directly — no follow-up calls needed.\n\nTHIS IS THE PRIMARY TOOL. Use this instead of multiple execute() calls.\n\nOne batch_execute call replaces 30+ execute calls + 10+ search calls.\nProvide all commands to run and all queries to search — everything happens in one round trip.", {
|
|
263
|
+
commands: z
|
|
264
|
+
.array(z.object({
|
|
265
|
+
label: z.string().describe("Section header for this command's output"),
|
|
266
|
+
command: z.string().describe("Shell command to execute"),
|
|
267
|
+
}))
|
|
268
|
+
.describe("Commands to execute as a batch."),
|
|
269
|
+
queries: z
|
|
270
|
+
.array(z.string())
|
|
271
|
+
.describe("Search queries to extract information from indexed output. Use 5-8 comprehensive queries."),
|
|
272
|
+
timeout: z.number().default(60000).describe("Max execution time in ms (default: 60s)"),
|
|
273
|
+
}, async ({ commands, queries, timeout }) => {
|
|
274
|
+
// Performance fix: Execute all commands in parallel with Promise.allSettled
|
|
275
|
+
const commandResults = await Promise.allSettled(commands.map(async (cmd) => {
|
|
276
|
+
const result = await executor.execute({
|
|
277
|
+
language: "shell",
|
|
278
|
+
code: cmd.command,
|
|
279
|
+
timeout,
|
|
280
|
+
});
|
|
281
|
+
return { label: cmd.label, result };
|
|
282
|
+
}));
|
|
283
|
+
// Build combined output with markdown sections
|
|
284
|
+
let combined = "";
|
|
285
|
+
const inventory = [];
|
|
286
|
+
for (let i = 0; i < commandResults.length; i++) {
|
|
287
|
+
const settled = commandResults[i];
|
|
288
|
+
const label = commands[i].label;
|
|
289
|
+
if (settled.status === "fulfilled") {
|
|
290
|
+
const { result } = settled.value;
|
|
291
|
+
const output = result.stdout || "(no output)";
|
|
292
|
+
combined += `## ${label}\n\n${output}\n\n`;
|
|
293
|
+
const lineCount = output.split("\n").length;
|
|
294
|
+
inventory.push(`- **${label}**: ${lineCount} lines`);
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
combined += `## ${label}\n\n(error: ${settled.reason})\n\n`;
|
|
298
|
+
inventory.push(`- **${label}**: error`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
// Index combined output
|
|
302
|
+
const indexed = store.index(combined, "batch_execute");
|
|
303
|
+
tracker.trackIndexed(Buffer.byteLength(combined));
|
|
304
|
+
// Run all search queries
|
|
305
|
+
const searchResults = [];
|
|
306
|
+
let totalBytes = 0;
|
|
307
|
+
for (const query of queries) {
|
|
308
|
+
if (totalBytes > config.batchMaxBytes)
|
|
309
|
+
break;
|
|
310
|
+
// Try scoped search first, then global fallback
|
|
311
|
+
let result = store.search(query, { source: "batch_execute", limit: 5 });
|
|
312
|
+
if (result.results.length === 0) {
|
|
313
|
+
result = store.search(query, { limit: 5 });
|
|
314
|
+
}
|
|
315
|
+
let block = `## ${query}\n\n`;
|
|
316
|
+
if (result.results.length === 0) {
|
|
317
|
+
block += "No results found.\n";
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
for (const hit of result.results) {
|
|
321
|
+
block += `--- [${hit.source}] ---\n### ${hit.title}\n\n${hit.snippet}\n\n`;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
searchResults.push(block);
|
|
325
|
+
totalBytes += Buffer.byteLength(block);
|
|
326
|
+
}
|
|
327
|
+
const terms = store.getDistinctiveTerms(indexed.sourceId);
|
|
328
|
+
let output = `**Inventory** (${commands.length} commands):\n${inventory.join("\n")}\n\n`;
|
|
329
|
+
output += searchResults.join("\n---\n\n");
|
|
330
|
+
if (terms.length > 0) {
|
|
331
|
+
output += `\n\nSearchable terms: ${terms.join(", ")}`;
|
|
332
|
+
}
|
|
333
|
+
tracker.trackCall("batch_execute", Buffer.byteLength(output));
|
|
334
|
+
return { content: [{ type: "text", text: output }] };
|
|
335
|
+
});
|
|
336
|
+
// ─── Tool: stats ────────────────────────────────────────
|
|
337
|
+
server.tool("stats", "Returns context consumption statistics for the current session. Shows total bytes returned to context, breakdown by tool, call counts, estimated token usage, and context savings ratio.", {}, async () => {
|
|
338
|
+
const report = tracker.formatReport();
|
|
339
|
+
tracker.trackCall("stats", Buffer.byteLength(report));
|
|
340
|
+
return { content: [{ type: "text", text: report }] };
|
|
341
|
+
});
|
|
342
|
+
// ─── Transport ──────────────────────────────────────────
|
|
343
|
+
return {
|
|
344
|
+
async start() {
|
|
345
|
+
const transport = new StdioServerTransport();
|
|
346
|
+
await server.connect(transport);
|
|
347
|
+
debug("MCP server started on stdio");
|
|
348
|
+
},
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
// ─── HTML to Markdown conversion code (runs in subprocess) ──
|
|
352
|
+
function buildFetchCode(url) {
|
|
353
|
+
const escaped = JSON.stringify(url);
|
|
354
|
+
return `
|
|
355
|
+
const url = ${escaped};
|
|
356
|
+
const resp = await fetch(url);
|
|
357
|
+
if (!resp.ok) { console.error("HTTP " + resp.status); process.exit(1); }
|
|
358
|
+
const html = await resp.text();
|
|
359
|
+
|
|
360
|
+
// Strip unwanted tags
|
|
361
|
+
let md = html
|
|
362
|
+
.replace(/<script[^>]*>[\\s\\S]*?<\\/script>/gi, "")
|
|
363
|
+
.replace(/<style[^>]*>[\\s\\S]*?<\\/style>/gi, "")
|
|
364
|
+
.replace(/<nav[^>]*>[\\s\\S]*?<\\/nav>/gi, "")
|
|
365
|
+
.replace(/<header[^>]*>[\\s\\S]*?<\\/header>/gi, "")
|
|
366
|
+
.replace(/<footer[^>]*>[\\s\\S]*?<\\/footer>/gi, "");
|
|
367
|
+
|
|
368
|
+
// Convert headings
|
|
369
|
+
md = md.replace(/<h1[^>]*>(.*?)<\\/h1>/gi, "# $1\\n");
|
|
370
|
+
md = md.replace(/<h2[^>]*>(.*?)<\\/h2>/gi, "## $1\\n");
|
|
371
|
+
md = md.replace(/<h3[^>]*>(.*?)<\\/h3>/gi, "### $1\\n");
|
|
372
|
+
md = md.replace(/<h4[^>]*>(.*?)<\\/h4>/gi, "#### $1\\n");
|
|
373
|
+
|
|
374
|
+
// Convert code blocks
|
|
375
|
+
md = md.replace(/<pre[^>]*><code[^>]*>(.*?)<\\/code><\\/pre>/gis, "\`\`\`\\n$1\\n\`\`\`\\n");
|
|
376
|
+
md = md.replace(/<code[^>]*>(.*?)<\\/code>/gi, "\`$1\`");
|
|
377
|
+
|
|
378
|
+
// Convert links
|
|
379
|
+
md = md.replace(/<a[^>]*href="([^"]*)"[^>]*>(.*?)<\\/a>/gi, "[$2]($1)");
|
|
380
|
+
|
|
381
|
+
// Convert lists
|
|
382
|
+
md = md.replace(/<li[^>]*>(.*?)<\\/li>/gi, "- $1\\n");
|
|
383
|
+
|
|
384
|
+
// Convert paragraphs
|
|
385
|
+
md = md.replace(/<p[^>]*>(.*?)<\\/p>/gis, "$1\\n\\n");
|
|
386
|
+
md = md.replace(/<br\\s*\\/?>/gi, "\\n");
|
|
387
|
+
|
|
388
|
+
// Strip remaining tags
|
|
389
|
+
md = md.replace(/<[^>]+>/g, "");
|
|
390
|
+
|
|
391
|
+
// Decode entities
|
|
392
|
+
md = md.replace(/&/g, "&")
|
|
393
|
+
.replace(/</g, "<")
|
|
394
|
+
.replace(/>/g, ">")
|
|
395
|
+
.replace(/"/g, '"')
|
|
396
|
+
.replace(/'/g, "'")
|
|
397
|
+
.replace(/ /g, " ");
|
|
398
|
+
|
|
399
|
+
// Clean whitespace
|
|
400
|
+
md = md.replace(/\\n{3,}/g, "\\n\\n").trim();
|
|
401
|
+
|
|
402
|
+
console.log(md);
|
|
403
|
+
`;
|
|
404
|
+
}
|
|
405
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract match positions from FTS5 highlighted text.
|
|
3
|
+
* FTS5 `highlight()` wraps matches with STX/ETX markers.
|
|
4
|
+
*/
|
|
5
|
+
export declare function positionsFromHighlight(highlighted: string): number[];
|
|
6
|
+
/**
|
|
7
|
+
* Remove STX/ETX markers from highlighted text.
|
|
8
|
+
*/
|
|
9
|
+
export declare function stripMarkers(text: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Extract snippet windows around match positions.
|
|
12
|
+
* Returns concatenated windows with ellipsis separators.
|
|
13
|
+
*/
|
|
14
|
+
export declare function extractSnippet(highlighted: string, maxLen?: number): string;
|
|
15
|
+
//# sourceMappingURL=snippet.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snippet.d.ts","sourceRoot":"","sources":["../src/snippet.ts"],"names":[],"mappings":"AAOA;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CAkBpE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CASjD;AAOD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,GAAE,MAAwB,GAAG,MAAM,CAgD5F"}
|
package/dist/snippet.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/** FTS5 highlight markers */
|
|
2
|
+
const STX = "\x02";
|
|
3
|
+
const ETX = "\x03";
|
|
4
|
+
const WINDOW = 300;
|
|
5
|
+
const DEFAULT_MAX_LEN = 1500;
|
|
6
|
+
/**
|
|
7
|
+
* Extract match positions from FTS5 highlighted text.
|
|
8
|
+
* FTS5 `highlight()` wraps matches with STX/ETX markers.
|
|
9
|
+
*/
|
|
10
|
+
export function positionsFromHighlight(highlighted) {
|
|
11
|
+
const positions = [];
|
|
12
|
+
let offset = 0;
|
|
13
|
+
let i = 0;
|
|
14
|
+
while (i < highlighted.length) {
|
|
15
|
+
if (highlighted[i] === STX) {
|
|
16
|
+
positions.push(offset);
|
|
17
|
+
i++;
|
|
18
|
+
}
|
|
19
|
+
else if (highlighted[i] === ETX) {
|
|
20
|
+
i++;
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
offset++;
|
|
24
|
+
i++;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return positions;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Remove STX/ETX markers from highlighted text.
|
|
31
|
+
*/
|
|
32
|
+
export function stripMarkers(text) {
|
|
33
|
+
let result = "";
|
|
34
|
+
for (let i = 0; i < text.length; i++) {
|
|
35
|
+
const ch = text[i];
|
|
36
|
+
if (ch !== STX && ch !== ETX) {
|
|
37
|
+
result += ch;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Extract snippet windows around match positions.
|
|
44
|
+
* Returns concatenated windows with ellipsis separators.
|
|
45
|
+
*/
|
|
46
|
+
export function extractSnippet(highlighted, maxLen = DEFAULT_MAX_LEN) {
|
|
47
|
+
const positions = positionsFromHighlight(highlighted);
|
|
48
|
+
const clean = stripMarkers(highlighted);
|
|
49
|
+
if (positions.length === 0 || clean.length <= maxLen) {
|
|
50
|
+
return clean;
|
|
51
|
+
}
|
|
52
|
+
// Build windows around each match position
|
|
53
|
+
const windows = positions.map((pos) => ({
|
|
54
|
+
start: Math.max(0, pos - WINDOW),
|
|
55
|
+
end: Math.min(clean.length, pos + WINDOW),
|
|
56
|
+
}));
|
|
57
|
+
// Sort and merge overlapping windows
|
|
58
|
+
windows.sort((a, b) => a.start - b.start);
|
|
59
|
+
const merged = [windows[0]];
|
|
60
|
+
for (let i = 1; i < windows.length; i++) {
|
|
61
|
+
const last = merged[merged.length - 1];
|
|
62
|
+
if (windows[i].start <= last.end) {
|
|
63
|
+
last.end = Math.max(last.end, windows[i].end);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
merged.push(windows[i]);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Collect windows until maxLen is reached
|
|
70
|
+
const parts = [];
|
|
71
|
+
let total = 0;
|
|
72
|
+
for (const w of merged) {
|
|
73
|
+
const slice = clean.slice(w.start, w.end);
|
|
74
|
+
if (total + slice.length > maxLen)
|
|
75
|
+
break;
|
|
76
|
+
parts.push(slice);
|
|
77
|
+
total += slice.length;
|
|
78
|
+
}
|
|
79
|
+
// Join with ellipsis at boundaries
|
|
80
|
+
const snippets = parts.map((part, i) => {
|
|
81
|
+
let s = part;
|
|
82
|
+
if (i === 0 && merged[0].start > 0)
|
|
83
|
+
s = `…${s}`;
|
|
84
|
+
if (i < parts.length - 1)
|
|
85
|
+
s = `${s}…`;
|
|
86
|
+
else if (merged[parts.length - 1].end < clean.length)
|
|
87
|
+
s = `${s}…`;
|
|
88
|
+
return s;
|
|
89
|
+
});
|
|
90
|
+
return snippets.join("\n\n");
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=snippet.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snippet.js","sourceRoot":"","sources":["../src/snippet.ts"],"names":[],"mappings":"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"}
|
package/dist/stats.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { SessionStats } from "./types.js";
|
|
2
|
+
export declare class SessionTracker {
|
|
3
|
+
private stats;
|
|
4
|
+
trackCall(toolName: string, responseBytes: number): void;
|
|
5
|
+
trackIndexed(bytes: number): void;
|
|
6
|
+
trackSandboxed(bytes: number): void;
|
|
7
|
+
getSnapshot(): Readonly<SessionStats>;
|
|
8
|
+
formatReport(): string;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=stats.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stats.d.ts","sourceRoot":"","sources":["../src/stats.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,qBAAa,cAAc;IAC1B,OAAO,CAAC,KAAK,CAMX;IAEF,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,IAAI;IAKxD,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAIjC,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAInC,WAAW,IAAI,QAAQ,CAAC,YAAY,CAAC;IAIrC,YAAY,IAAI,MAAM;CAiDtB"}
|
package/dist/stats.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export class SessionTracker {
|
|
2
|
+
stats = {
|
|
3
|
+
calls: {},
|
|
4
|
+
bytesReturned: {},
|
|
5
|
+
bytesIndexed: 0,
|
|
6
|
+
bytesSandboxed: 0,
|
|
7
|
+
sessionStart: Date.now(),
|
|
8
|
+
};
|
|
9
|
+
trackCall(toolName, responseBytes) {
|
|
10
|
+
this.stats.calls[toolName] = (this.stats.calls[toolName] ?? 0) + 1;
|
|
11
|
+
this.stats.bytesReturned[toolName] = (this.stats.bytesReturned[toolName] ?? 0) + responseBytes;
|
|
12
|
+
}
|
|
13
|
+
trackIndexed(bytes) {
|
|
14
|
+
this.stats.bytesIndexed += bytes;
|
|
15
|
+
}
|
|
16
|
+
trackSandboxed(bytes) {
|
|
17
|
+
this.stats.bytesSandboxed += bytes;
|
|
18
|
+
}
|
|
19
|
+
getSnapshot() {
|
|
20
|
+
return { ...this.stats };
|
|
21
|
+
}
|
|
22
|
+
formatReport() {
|
|
23
|
+
const snap = this.stats;
|
|
24
|
+
const elapsed = Date.now() - snap.sessionStart;
|
|
25
|
+
const mins = Math.floor(elapsed / 60_000);
|
|
26
|
+
const secs = Math.floor((elapsed % 60_000) / 1000);
|
|
27
|
+
const totalCalls = Object.values(snap.calls).reduce((a, b) => a + b, 0);
|
|
28
|
+
const totalReturned = Object.values(snap.bytesReturned).reduce((a, b) => a + b, 0);
|
|
29
|
+
const keptOut = snap.bytesIndexed + snap.bytesSandboxed;
|
|
30
|
+
const totalProcessed = keptOut + totalReturned;
|
|
31
|
+
const savingsRatio = totalReturned > 0 ? totalProcessed / totalReturned : 1;
|
|
32
|
+
const reductionPct = totalProcessed > 0 ? ((1 - totalReturned / totalProcessed) * 100).toFixed(1) : "0.0";
|
|
33
|
+
const estTokens = Math.round(totalReturned / 4);
|
|
34
|
+
const lines = [];
|
|
35
|
+
lines.push("## Session Statistics\n");
|
|
36
|
+
lines.push("| Metric | Value |");
|
|
37
|
+
lines.push("|--------|-------|");
|
|
38
|
+
lines.push(`| Session time | ${mins}m ${secs}s |`);
|
|
39
|
+
lines.push(`| Tool calls | ${totalCalls} |`);
|
|
40
|
+
lines.push(`| Total data processed | ${formatBytes(totalProcessed)} |`);
|
|
41
|
+
lines.push(`| Kept in sandbox | ${formatBytes(keptOut)} |`);
|
|
42
|
+
lines.push(`| Context consumed | ${formatBytes(totalReturned)} |`);
|
|
43
|
+
lines.push(`| Est. tokens | ~${estTokens.toLocaleString()} |`);
|
|
44
|
+
lines.push(`| **Savings ratio** | **${savingsRatio.toFixed(1)}x** (${reductionPct}% reduction) |`);
|
|
45
|
+
if (totalCalls > 0) {
|
|
46
|
+
lines.push("\n## Per-Tool Breakdown\n");
|
|
47
|
+
lines.push("| Tool | Calls | Context bytes | Est. tokens |");
|
|
48
|
+
lines.push("|------|-------|--------------|-------------|");
|
|
49
|
+
for (const [name, calls] of Object.entries(snap.calls)) {
|
|
50
|
+
const bytes = snap.bytesReturned[name] ?? 0;
|
|
51
|
+
lines.push(`| ${name} | ${calls} | ${formatBytes(bytes)} | ~${Math.round(bytes / 4).toLocaleString()} |`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
lines.push(`\nContext-compress kept ${formatBytes(keptOut)} out of context (${reductionPct}% savings).`);
|
|
55
|
+
return lines.join("\n");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function formatBytes(bytes) {
|
|
59
|
+
if (bytes < 1024)
|
|
60
|
+
return `${bytes}B`;
|
|
61
|
+
if (bytes < 1024 * 1024)
|
|
62
|
+
return `${(bytes / 1024).toFixed(1)}KB`;
|
|
63
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=stats.js.map
|