mcp-compression-proxy 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/CHANGELOG.md +97 -0
- package/LICENSE +21 -0
- package/README.md +842 -0
- package/dist/cli/commands.d.ts +25 -0
- package/dist/cli/commands.js +152 -0
- package/dist/cli/daemon.d.ts +4 -0
- package/dist/cli/daemon.js +336 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +269 -0
- package/dist/cli/ipc-client.d.ts +11 -0
- package/dist/cli/ipc-client.js +81 -0
- package/dist/cli/payload-interceptor.d.ts +6 -0
- package/dist/cli/payload-interceptor.js +49 -0
- package/dist/config/loader.d.ts +53 -0
- package/dist/config/loader.js +332 -0
- package/dist/config/schema.d.ts +164 -0
- package/dist/config/schema.js +127 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +821 -0
- package/dist/mcp/client-manager.d.ts +65 -0
- package/dist/mcp/client-manager.js +197 -0
- package/dist/services/compression-cache.d.ts +112 -0
- package/dist/services/compression-cache.js +238 -0
- package/dist/services/compression-persistence.d.ts +36 -0
- package/dist/services/compression-persistence.js +111 -0
- package/dist/services/compression-sampler.d.ts +89 -0
- package/dist/services/compression-sampler.js +171 -0
- package/dist/services/session-manager.d.ts +64 -0
- package/dist/services/session-manager.js +160 -0
- package/dist/services/stats-service.d.ts +101 -0
- package/dist/services/stats-service.js +246 -0
- package/dist/types/compression.d.ts +38 -0
- package/dist/types/compression.js +5 -0
- package/dist/types/index.d.ts +108 -0
- package/dist/types/index.js +2 -0
- package/dist/version.d.ts +11 -0
- package/dist/version.js +11 -0
- package/package.json +110 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from 'child_process';
|
|
3
|
+
import { existsSync, readFileSync, unlinkSync } from 'fs';
|
|
4
|
+
import { join, dirname } from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { homedir } from 'os';
|
|
7
|
+
import { isDaemonRunning } from './ipc-client.js';
|
|
8
|
+
import { handleTools, handleSearch, handleInfo, handleCall, handleStats, handleDaemonStatus, } from './commands.js';
|
|
9
|
+
const BASE_DIR = join(homedir(), '.mcp-compression-proxy');
|
|
10
|
+
const SOCKET_PATH = join(BASE_DIR, 'daemon.sock');
|
|
11
|
+
const PID_FILE = join(BASE_DIR, 'daemon.pid');
|
|
12
|
+
const READY_FILE = join(BASE_DIR, 'daemon.ready');
|
|
13
|
+
const USAGE = `
|
|
14
|
+
mcp-cli — Progressive MCP tool discovery for LLMs
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
mcp-cli tools List all tools (compressed)
|
|
18
|
+
mcp-cli search <query> Search tools by name/description
|
|
19
|
+
mcp-cli info <server>/<tool> Get full schema for a tool
|
|
20
|
+
mcp-cli call <server>/<tool> <json> Execute a tool
|
|
21
|
+
mcp-cli stats Show compression statistics
|
|
22
|
+
mcp-cli daemon start Start the background daemon
|
|
23
|
+
mcp-cli daemon stop Stop the daemon
|
|
24
|
+
mcp-cli daemon status Show daemon status
|
|
25
|
+
mcp-cli help Show this help
|
|
26
|
+
|
|
27
|
+
Options:
|
|
28
|
+
--no-auto-start Don't auto-start daemon
|
|
29
|
+
`.trim();
|
|
30
|
+
/**
|
|
31
|
+
* Start the daemon as a background process.
|
|
32
|
+
* Forks the daemon.ts module with detached: true.
|
|
33
|
+
*/
|
|
34
|
+
async function startDaemon() {
|
|
35
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
36
|
+
const __dirname = dirname(__filename);
|
|
37
|
+
const daemonScript = join(__dirname, 'daemon.js');
|
|
38
|
+
if (!existsSync(daemonScript)) {
|
|
39
|
+
console.error(`Error: Daemon script not found at ${daemonScript}`);
|
|
40
|
+
console.error('Run "npm run build" first.');
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
// Clean up stale ready file
|
|
44
|
+
if (existsSync(READY_FILE)) {
|
|
45
|
+
unlinkSync(READY_FILE);
|
|
46
|
+
}
|
|
47
|
+
// spawn, not fork: fork opens an IPC channel to the child, and that channel
|
|
48
|
+
// keeps this process's event loop alive even after child.unref(), so
|
|
49
|
+
// `mcp-cli daemon start` printed its success message and then hung forever.
|
|
50
|
+
// The daemon never uses process.send(), so the channel was pure overhead.
|
|
51
|
+
const child = spawn(process.execPath, [daemonScript], {
|
|
52
|
+
detached: true,
|
|
53
|
+
stdio: 'ignore',
|
|
54
|
+
});
|
|
55
|
+
child.unref();
|
|
56
|
+
// Wait for the daemon to be ready (socket available)
|
|
57
|
+
const maxWaitMs = 15000;
|
|
58
|
+
const pollIntervalMs = 200;
|
|
59
|
+
const startTime = Date.now();
|
|
60
|
+
while (Date.now() - startTime < maxWaitMs) {
|
|
61
|
+
if (existsSync(READY_FILE)) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
await new Promise(resolve => setTimeout(resolve, pollIntervalMs));
|
|
65
|
+
}
|
|
66
|
+
// Fallback: check if socket is reachable
|
|
67
|
+
return isDaemonRunning(SOCKET_PATH);
|
|
68
|
+
}
|
|
69
|
+
/** Remove the socket, PID and ready markers left behind by a dead daemon. */
|
|
70
|
+
function cleanupDaemonFiles() {
|
|
71
|
+
for (const file of [PID_FILE, SOCKET_PATH, READY_FILE]) {
|
|
72
|
+
try {
|
|
73
|
+
unlinkSync(file);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
/* ignore */
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Stop the daemon by sending SIGTERM and waiting for it to actually exit.
|
|
82
|
+
*/
|
|
83
|
+
async function stopDaemon() {
|
|
84
|
+
if (!existsSync(PID_FILE)) {
|
|
85
|
+
console.log('Daemon is not running (no PID file).');
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
const pid = Number.parseInt(readFileSync(PID_FILE, 'utf-8').trim(), 10);
|
|
89
|
+
// A truncated or corrupt PID file yields NaN, and process.kill(NaN) throws
|
|
90
|
+
// ERR_INVALID_ARG_TYPE rather than ESRCH - which would escape the handler
|
|
91
|
+
// below and surface as a stack trace instead of a stale-file message.
|
|
92
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
93
|
+
cleanupDaemonFiles();
|
|
94
|
+
console.log('Daemon is not running (corrupt PID file cleaned up).');
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
// A PID alone is not proof this is our daemon: if it died without cleaning
|
|
98
|
+
// up, the OS may have recycled the number for an unrelated process, and
|
|
99
|
+
// signalling that would kill something we do not own. Only trust the PID
|
|
100
|
+
// when the daemon also answers on its socket.
|
|
101
|
+
if (!(await isDaemonRunning(SOCKET_PATH))) {
|
|
102
|
+
cleanupDaemonFiles();
|
|
103
|
+
console.log('Daemon was not running (stale PID file cleaned up).');
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
process.kill(pid, 'SIGTERM');
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
if (error.code === 'ESRCH') {
|
|
111
|
+
cleanupDaemonFiles();
|
|
112
|
+
console.log('Daemon was not running (stale PID file cleaned up).');
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
// Shutdown disconnects every backend server first, so the process does not
|
|
118
|
+
// exit immediately. Reporting success too early lets a follow-up `daemon
|
|
119
|
+
// start` race the old daemon, which still owns the socket.
|
|
120
|
+
const deadline = Date.now() + 10000;
|
|
121
|
+
while (Date.now() < deadline) {
|
|
122
|
+
try {
|
|
123
|
+
process.kill(pid, 0);
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
cleanupDaemonFiles();
|
|
127
|
+
console.log(`Daemon stopped (PID ${pid}).`);
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
131
|
+
}
|
|
132
|
+
console.error(`Daemon (PID ${pid}) did not exit within 10s; it may still be shutting down.`);
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Ensure daemon is running, auto-starting if needed.
|
|
137
|
+
*/
|
|
138
|
+
async function ensureDaemon(noAutoStart) {
|
|
139
|
+
const running = await isDaemonRunning(SOCKET_PATH);
|
|
140
|
+
if (running)
|
|
141
|
+
return;
|
|
142
|
+
if (noAutoStart) {
|
|
143
|
+
console.error('Error: Daemon is not running. Start it with: mcp-cli daemon start');
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
console.error('Daemon not running. Starting...');
|
|
147
|
+
const started = await startDaemon();
|
|
148
|
+
if (!started) {
|
|
149
|
+
console.error('Error: Failed to start daemon. Try manually: mcp-cli daemon start');
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
console.error('Daemon started.');
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Read JSON payload from stdin if available.
|
|
156
|
+
*/
|
|
157
|
+
async function readStdin() {
|
|
158
|
+
if (process.stdin.isTTY)
|
|
159
|
+
return null;
|
|
160
|
+
return new Promise((resolve) => {
|
|
161
|
+
let data = '';
|
|
162
|
+
process.stdin.setEncoding('utf-8');
|
|
163
|
+
// Give up after 1s rather than hanging on a pipe that never closes. The
|
|
164
|
+
// timer is unref'd and cleared so it cannot keep the CLI alive after
|
|
165
|
+
// stdin has already ended.
|
|
166
|
+
const timer = setTimeout(() => resolve(data.trim() || null), 1000);
|
|
167
|
+
timer.unref?.();
|
|
168
|
+
process.stdin.on('data', (chunk) => { data += chunk; });
|
|
169
|
+
process.stdin.on('end', () => {
|
|
170
|
+
clearTimeout(timer);
|
|
171
|
+
resolve(data.trim() || null);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
async function main() {
|
|
176
|
+
const args = process.argv.slice(2);
|
|
177
|
+
const noAutoStart = args.includes('--no-auto-start');
|
|
178
|
+
const filteredArgs = args.filter(a => a !== '--no-auto-start');
|
|
179
|
+
const command = filteredArgs[0];
|
|
180
|
+
if (!command || command === 'help' || command === '--help' || command === '-h') {
|
|
181
|
+
console.log(USAGE);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
// Daemon management commands don't need a running daemon
|
|
185
|
+
if (command === 'daemon') {
|
|
186
|
+
const action = filteredArgs[1];
|
|
187
|
+
switch (action) {
|
|
188
|
+
case 'start': {
|
|
189
|
+
const running = await isDaemonRunning(SOCKET_PATH);
|
|
190
|
+
if (running) {
|
|
191
|
+
console.log('Daemon is already running.');
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
const started = await startDaemon();
|
|
195
|
+
if (started) {
|
|
196
|
+
// Get connection info
|
|
197
|
+
const response = await (await import('./ipc-client.js')).sendRequest(SOCKET_PATH, 'daemon-status');
|
|
198
|
+
if (!response.error) {
|
|
199
|
+
const status = response.result;
|
|
200
|
+
console.log(`Daemon started (PID ${status.pid}). Connected to ${status.connectedServers}/${status.totalServers} servers.`);
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
console.log('Daemon started.');
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
console.error('Failed to start daemon.');
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
case 'stop':
|
|
213
|
+
await stopDaemon();
|
|
214
|
+
return;
|
|
215
|
+
case 'status':
|
|
216
|
+
await handleDaemonStatus(SOCKET_PATH);
|
|
217
|
+
return;
|
|
218
|
+
default:
|
|
219
|
+
console.error('Usage: mcp-cli daemon <start|stop|status>');
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// All other commands require a running daemon
|
|
224
|
+
await ensureDaemon(noAutoStart);
|
|
225
|
+
switch (command) {
|
|
226
|
+
case 'tools':
|
|
227
|
+
await handleTools(SOCKET_PATH);
|
|
228
|
+
break;
|
|
229
|
+
case 'search':
|
|
230
|
+
await handleSearch(SOCKET_PATH, filteredArgs.slice(1).join(' '));
|
|
231
|
+
break;
|
|
232
|
+
case 'info':
|
|
233
|
+
if (!filteredArgs[1]) {
|
|
234
|
+
console.error('Usage: mcp-cli info <server>/<tool>');
|
|
235
|
+
process.exit(1);
|
|
236
|
+
}
|
|
237
|
+
await handleInfo(SOCKET_PATH, filteredArgs[1]);
|
|
238
|
+
break;
|
|
239
|
+
case 'call': {
|
|
240
|
+
if (!filteredArgs[1]) {
|
|
241
|
+
console.error('Usage: mcp-cli call <server>/<tool> \'<json_payload>\'');
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
// Payload from args or stdin
|
|
245
|
+
let payload = filteredArgs[2] || '';
|
|
246
|
+
if (!payload) {
|
|
247
|
+
const stdinData = await readStdin();
|
|
248
|
+
if (stdinData)
|
|
249
|
+
payload = stdinData;
|
|
250
|
+
}
|
|
251
|
+
if (!payload)
|
|
252
|
+
payload = '{}';
|
|
253
|
+
await handleCall(SOCKET_PATH, filteredArgs[1], payload);
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
case 'stats':
|
|
257
|
+
await handleStats(SOCKET_PATH);
|
|
258
|
+
break;
|
|
259
|
+
default:
|
|
260
|
+
console.error(`Unknown command: ${command}\n`);
|
|
261
|
+
console.log(USAGE);
|
|
262
|
+
process.exit(1);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
main().catch((error) => {
|
|
266
|
+
console.error(`Error: ${error.message || error}`);
|
|
267
|
+
process.exit(1);
|
|
268
|
+
});
|
|
269
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { IPCResponse, IPCMethod } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Sends a request to the daemon via Unix domain socket
|
|
4
|
+
* and waits for the response.
|
|
5
|
+
*/
|
|
6
|
+
export declare function sendRequest(socketPath: string, method: IPCMethod, params?: Record<string, unknown>, timeoutMs?: number): Promise<IPCResponse>;
|
|
7
|
+
/**
|
|
8
|
+
* Check if the daemon is reachable by sending a status ping.
|
|
9
|
+
*/
|
|
10
|
+
export declare function isDaemonRunning(socketPath: string): Promise<boolean>;
|
|
11
|
+
//# sourceMappingURL=ipc-client.d.ts.map
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import net from 'net';
|
|
2
|
+
import { randomUUID } from 'crypto';
|
|
3
|
+
const DEFAULT_TIMEOUT_MS = 30000;
|
|
4
|
+
/**
|
|
5
|
+
* Sends a request to the daemon via Unix domain socket
|
|
6
|
+
* and waits for the response.
|
|
7
|
+
*/
|
|
8
|
+
export async function sendRequest(socketPath, method, params, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
9
|
+
const request = {
|
|
10
|
+
id: randomUUID(),
|
|
11
|
+
method,
|
|
12
|
+
params,
|
|
13
|
+
};
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
const socket = net.createConnection({ path: socketPath });
|
|
16
|
+
let buffer = '';
|
|
17
|
+
let settled = false;
|
|
18
|
+
const timer = setTimeout(() => {
|
|
19
|
+
if (!settled) {
|
|
20
|
+
settled = true;
|
|
21
|
+
socket.destroy();
|
|
22
|
+
reject(new Error(`Request timed out after ${timeoutMs}ms`));
|
|
23
|
+
}
|
|
24
|
+
}, timeoutMs);
|
|
25
|
+
socket.on('connect', () => {
|
|
26
|
+
socket.write(JSON.stringify(request) + '\n');
|
|
27
|
+
});
|
|
28
|
+
socket.on('data', (data) => {
|
|
29
|
+
buffer += data.toString();
|
|
30
|
+
const newlineIndex = buffer.indexOf('\n');
|
|
31
|
+
if (newlineIndex !== -1) {
|
|
32
|
+
const line = buffer.slice(0, newlineIndex);
|
|
33
|
+
if (!settled) {
|
|
34
|
+
settled = true;
|
|
35
|
+
clearTimeout(timer);
|
|
36
|
+
socket.destroy();
|
|
37
|
+
try {
|
|
38
|
+
const response = JSON.parse(line);
|
|
39
|
+
resolve(response);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
reject(new Error('Invalid response from daemon'));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
socket.on('error', (error) => {
|
|
48
|
+
if (!settled) {
|
|
49
|
+
settled = true;
|
|
50
|
+
clearTimeout(timer);
|
|
51
|
+
if (error.code === 'ECONNREFUSED' ||
|
|
52
|
+
error.code === 'ENOENT') {
|
|
53
|
+
reject(new Error('Daemon is not running'));
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
reject(error);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
socket.on('close', () => {
|
|
61
|
+
if (!settled) {
|
|
62
|
+
settled = true;
|
|
63
|
+
clearTimeout(timer);
|
|
64
|
+
reject(new Error('Connection closed before response'));
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Check if the daemon is reachable by sending a status ping.
|
|
71
|
+
*/
|
|
72
|
+
export async function isDaemonRunning(socketPath) {
|
|
73
|
+
try {
|
|
74
|
+
const response = await sendRequest(socketPath, 'daemon-status', undefined, 3000);
|
|
75
|
+
return !response.error;
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=ipc-client.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Intercepts large tool call outputs, saves them to a temp file,
|
|
3
|
+
* and returns a reference instead of the full payload.
|
|
4
|
+
*/
|
|
5
|
+
export declare function interceptPayload(output: string, threshold?: number): string;
|
|
6
|
+
//# sourceMappingURL=payload-interceptor.d.ts.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { createHash } from 'crypto';
|
|
2
|
+
import { mkdtempSync, writeFileSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { tmpdir } from 'os';
|
|
5
|
+
const DEFAULT_THRESHOLD = 500;
|
|
6
|
+
/**
|
|
7
|
+
* Private directory for intercepted payloads, created on first use.
|
|
8
|
+
*
|
|
9
|
+
* Writing `mcp_output_<hash>.txt` straight into the shared temp directory
|
|
10
|
+
* made the path predictable from the content alone: another local user could
|
|
11
|
+
* pre-plant a symlink there and turn our write into an arbitrary-file
|
|
12
|
+
* overwrite, or simply read whatever a backend MCP server had just returned.
|
|
13
|
+
* mkdtemp gives us a 0700 directory with an unpredictable name, so neither is
|
|
14
|
+
* reachable. The name is stable for the lifetime of the process, which keeps
|
|
15
|
+
* identical content mapping to a single file.
|
|
16
|
+
*/
|
|
17
|
+
let payloadDir;
|
|
18
|
+
function getPayloadDir() {
|
|
19
|
+
if (payloadDir === undefined) {
|
|
20
|
+
payloadDir = mkdtempSync(join(tmpdir(), 'mcp-output-'));
|
|
21
|
+
}
|
|
22
|
+
return payloadDir;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Intercepts large tool call outputs, saves them to a temp file,
|
|
26
|
+
* and returns a reference instead of the full payload.
|
|
27
|
+
*/
|
|
28
|
+
export function interceptPayload(output, threshold) {
|
|
29
|
+
const limit = threshold ?? DEFAULT_THRESHOLD;
|
|
30
|
+
if (output.length <= limit) {
|
|
31
|
+
return output;
|
|
32
|
+
}
|
|
33
|
+
const hash = createHash('sha256').update(output).digest('hex').slice(0, 12);
|
|
34
|
+
const filename = `mcp_output_${hash}.txt`;
|
|
35
|
+
const filepath = join(getPayloadDir(), filename);
|
|
36
|
+
try {
|
|
37
|
+
// 'wx' fails rather than following anything already at the path.
|
|
38
|
+
writeFileSync(filepath, output, { encoding: 'utf-8', mode: 0o600, flag: 'wx' });
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
// Same content already written by this process - the existing file is the
|
|
42
|
+
// file we were about to write, so reuse it. Anything else is a real error.
|
|
43
|
+
if (error.code !== 'EEXIST') {
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return `Output saved to ${filepath} (${output.length} chars). Read file for full content.`;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=payload-interceptor.js.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { MCPServerConfig } from '../types/index.js';
|
|
2
|
+
import { type InheritEnv, type CompressionFallbackBehavior } from './schema.js';
|
|
3
|
+
/**
|
|
4
|
+
* Check if tool name matches any ignore pattern
|
|
5
|
+
*/
|
|
6
|
+
export declare function matchesIgnorePattern(toolName: string, patterns: string[]): boolean;
|
|
7
|
+
export type ConfigResult = {
|
|
8
|
+
servers: MCPServerConfig[];
|
|
9
|
+
excludePatterns: string[];
|
|
10
|
+
noCompressPatterns: string[];
|
|
11
|
+
defaultTimeout?: number;
|
|
12
|
+
cli?: {
|
|
13
|
+
payloadThreshold?: number;
|
|
14
|
+
autoStartDaemon?: boolean;
|
|
15
|
+
daemonLogLevel?: string;
|
|
16
|
+
};
|
|
17
|
+
inheritEnv?: InheritEnv;
|
|
18
|
+
compressionFallbackBehavior?: CompressionFallbackBehavior;
|
|
19
|
+
} | null;
|
|
20
|
+
/**
|
|
21
|
+
* Load and aggregate server configuration from JSON files
|
|
22
|
+
* 1. Load user-level config and collect patterns
|
|
23
|
+
* 2. Load project-level config and append servers
|
|
24
|
+
* 3. Aggregate exclude and noCompress patterns from both configs
|
|
25
|
+
*/
|
|
26
|
+
export declare function loadJSONServers(): ConfigResult;
|
|
27
|
+
/**
|
|
28
|
+
* Cached wrapper around {@link loadJSONServers}.
|
|
29
|
+
*
|
|
30
|
+
* `tools/list` runs on every client refresh, and re-reading, re-validating and
|
|
31
|
+
* re-logging both config files each time is pure overhead. The cache is keyed
|
|
32
|
+
* on file mtime/size, so edits are still picked up without a restart.
|
|
33
|
+
*/
|
|
34
|
+
export declare function loadJSONServersCached(): ConfigResult;
|
|
35
|
+
/**
|
|
36
|
+
* Drop the cached config. Primarily for tests that swap config files
|
|
37
|
+
* within a single process.
|
|
38
|
+
*/
|
|
39
|
+
export declare function clearConfigCache(): void;
|
|
40
|
+
/**
|
|
41
|
+
* Get the path that would be used for config
|
|
42
|
+
* (for migration script purposes)
|
|
43
|
+
*/
|
|
44
|
+
export declare function getConfigPath(): string;
|
|
45
|
+
/**
|
|
46
|
+
* Get the daemon Unix socket path
|
|
47
|
+
*/
|
|
48
|
+
export declare function getSocketPath(): string;
|
|
49
|
+
/**
|
|
50
|
+
* Get the daemon PID file path
|
|
51
|
+
*/
|
|
52
|
+
export declare function getPidFilePath(): string;
|
|
53
|
+
//# sourceMappingURL=loader.d.ts.map
|