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.
Files changed (38) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/LICENSE +21 -0
  3. package/README.md +842 -0
  4. package/dist/cli/commands.d.ts +25 -0
  5. package/dist/cli/commands.js +152 -0
  6. package/dist/cli/daemon.d.ts +4 -0
  7. package/dist/cli/daemon.js +336 -0
  8. package/dist/cli/index.d.ts +3 -0
  9. package/dist/cli/index.js +269 -0
  10. package/dist/cli/ipc-client.d.ts +11 -0
  11. package/dist/cli/ipc-client.js +81 -0
  12. package/dist/cli/payload-interceptor.d.ts +6 -0
  13. package/dist/cli/payload-interceptor.js +49 -0
  14. package/dist/config/loader.d.ts +53 -0
  15. package/dist/config/loader.js +332 -0
  16. package/dist/config/schema.d.ts +164 -0
  17. package/dist/config/schema.js +127 -0
  18. package/dist/index.d.ts +3 -0
  19. package/dist/index.js +821 -0
  20. package/dist/mcp/client-manager.d.ts +65 -0
  21. package/dist/mcp/client-manager.js +197 -0
  22. package/dist/services/compression-cache.d.ts +112 -0
  23. package/dist/services/compression-cache.js +238 -0
  24. package/dist/services/compression-persistence.d.ts +36 -0
  25. package/dist/services/compression-persistence.js +111 -0
  26. package/dist/services/compression-sampler.d.ts +89 -0
  27. package/dist/services/compression-sampler.js +171 -0
  28. package/dist/services/session-manager.d.ts +64 -0
  29. package/dist/services/session-manager.js +160 -0
  30. package/dist/services/stats-service.d.ts +101 -0
  31. package/dist/services/stats-service.js +246 -0
  32. package/dist/types/compression.d.ts +38 -0
  33. package/dist/types/compression.js +5 -0
  34. package/dist/types/index.d.ts +108 -0
  35. package/dist/types/index.js +2 -0
  36. package/dist/version.d.ts +11 -0
  37. package/dist/version.js +11 -0
  38. package/package.json +110 -0
@@ -0,0 +1,65 @@
1
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
2
+ import type { MCPServerConfig, ServerStatus } from '../types/index.js';
3
+ import type { Logger } from 'pino';
4
+ /**
5
+ * Manages connections to multiple MCP servers
6
+ */
7
+ export declare class MCPClientManager {
8
+ private connections;
9
+ private logger;
10
+ private readonly DEFAULT_TIMEOUT_MS;
11
+ constructor(logger: Logger);
12
+ /**
13
+ * Wraps a promise with a timeout.
14
+ *
15
+ * The timer is always cleared - leaving it pending keeps the Node event loop
16
+ * alive for the full duration even after a fast connection succeeds.
17
+ */
18
+ private withTimeout;
19
+ /**
20
+ * Build the environment handed to a spawned server.
21
+ *
22
+ * The stdio transport only inherits a small allowlist of "safe" variables
23
+ * (PATH, HOME, ...), so anything else the user exported - API tokens, base
24
+ * URLs - never reaches the child unless it is passed explicitly. By default
25
+ * we forward the proxy's full environment, matching what users expect from a
26
+ * process they launched themselves. `inheritEnv` narrows that when a server
27
+ * should not see unrelated secrets.
28
+ */
29
+ private buildEnv;
30
+ /**
31
+ * Initialize and connect to all configured MCP servers
32
+ * @param servers - Server configurations to initialize
33
+ * @param defaultTimeout - Optional default timeout in seconds (overrides class default)
34
+ * @param defaultInheritEnv - Optional default env inheritance policy (overridden per-server)
35
+ */
36
+ initializeServers(servers: MCPServerConfig[], defaultTimeout?: number, defaultInheritEnv?: boolean | string[]): Promise<void>;
37
+ /**
38
+ * Connect to a single MCP server with timeout
39
+ */
40
+ private connectToServer;
41
+ /**
42
+ * Get a connected client by server name
43
+ */
44
+ getClient(serverName: string): Client | undefined;
45
+ /**
46
+ * Get all connected clients
47
+ */
48
+ getConnectedClients(): Array<{
49
+ name: string;
50
+ client: Client;
51
+ }>;
52
+ /**
53
+ * Get status of all servers
54
+ */
55
+ getServerStatuses(): ServerStatus[];
56
+ /**
57
+ * Check if at least one server is connected
58
+ */
59
+ hasConnectedServers(): boolean;
60
+ /**
61
+ * Disconnect from all servers
62
+ */
63
+ disconnectAll(): Promise<void>;
64
+ }
65
+ //# sourceMappingURL=client-manager.d.ts.map
@@ -0,0 +1,197 @@
1
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
2
+ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
3
+ import { SERVER_NAME, VERSION } from '../version.js';
4
+ /**
5
+ * Manages connections to multiple MCP servers
6
+ */
7
+ export class MCPClientManager {
8
+ connections = new Map();
9
+ logger;
10
+ DEFAULT_TIMEOUT_MS = 30000; // 30 seconds default timeout
11
+ constructor(logger) {
12
+ this.logger = logger;
13
+ }
14
+ /**
15
+ * Wraps a promise with a timeout.
16
+ *
17
+ * The timer is always cleared - leaving it pending keeps the Node event loop
18
+ * alive for the full duration even after a fast connection succeeds.
19
+ */
20
+ async withTimeout(promise, timeoutMs) {
21
+ let timer;
22
+ try {
23
+ return await Promise.race([
24
+ promise,
25
+ new Promise((_, reject) => {
26
+ timer = setTimeout(() => {
27
+ reject(new Error(`Connection timeout after ${timeoutMs}ms`));
28
+ }, timeoutMs);
29
+ }),
30
+ ]);
31
+ }
32
+ finally {
33
+ if (timer !== undefined) {
34
+ clearTimeout(timer);
35
+ }
36
+ }
37
+ }
38
+ /**
39
+ * Build the environment handed to a spawned server.
40
+ *
41
+ * The stdio transport only inherits a small allowlist of "safe" variables
42
+ * (PATH, HOME, ...), so anything else the user exported - API tokens, base
43
+ * URLs - never reaches the child unless it is passed explicitly. By default
44
+ * we forward the proxy's full environment, matching what users expect from a
45
+ * process they launched themselves. `inheritEnv` narrows that when a server
46
+ * should not see unrelated secrets.
47
+ */
48
+ buildEnv(config) {
49
+ const inherit = config.inheritEnv ?? true;
50
+ // `false` defers entirely to the transport's safe defaults.
51
+ if (inherit === false) {
52
+ return config.env;
53
+ }
54
+ const names = Array.isArray(inherit) ? inherit : Object.keys(process.env);
55
+ const inherited = {};
56
+ for (const name of names) {
57
+ const value = process.env[name];
58
+ if (value === undefined)
59
+ continue;
60
+ // Skip exported shell functions, which are a known injection vector.
61
+ if (value.startsWith('()'))
62
+ continue;
63
+ inherited[name] = value;
64
+ }
65
+ // Explicit `env` entries always win over inherited ones.
66
+ return { ...inherited, ...config.env };
67
+ }
68
+ /**
69
+ * Initialize and connect to all configured MCP servers
70
+ * @param servers - Server configurations to initialize
71
+ * @param defaultTimeout - Optional default timeout in seconds (overrides class default)
72
+ * @param defaultInheritEnv - Optional default env inheritance policy (overridden per-server)
73
+ */
74
+ async initializeServers(servers, defaultTimeout, defaultInheritEnv) {
75
+ this.logger.info({ count: servers.length }, 'Initializing MCP servers');
76
+ // Apply defaults to servers that don't specify their own
77
+ const serversWithTimeout = servers.map(server => ({
78
+ ...server,
79
+ timeout: server.timeout ?? defaultTimeout,
80
+ inheritEnv: server.inheritEnv ?? defaultInheritEnv,
81
+ }));
82
+ const connectionPromises = serversWithTimeout.map(async (config) => {
83
+ try {
84
+ await this.connectToServer(config);
85
+ }
86
+ catch (error) {
87
+ this.logger.error({ server: config.name, error }, 'Failed to connect to MCP server');
88
+ }
89
+ });
90
+ await Promise.allSettled(connectionPromises);
91
+ const connectedCount = Array.from(this.connections.values()).filter((c) => c.connected).length;
92
+ this.logger.info({ connected: connectedCount, total: servers.length }, 'MCP servers initialization complete');
93
+ }
94
+ /**
95
+ * Connect to a single MCP server with timeout
96
+ */
97
+ async connectToServer(config) {
98
+ // Use server-specific timeout or default (convert seconds to milliseconds)
99
+ const timeoutMs = config.timeout
100
+ ? config.timeout * 1000
101
+ : this.DEFAULT_TIMEOUT_MS;
102
+ this.logger.info({ server: config.name, timeoutMs }, 'Connecting to MCP server');
103
+ const transport = new StdioClientTransport({
104
+ command: config.command,
105
+ args: config.args,
106
+ env: this.buildEnv(config),
107
+ });
108
+ const client = new Client({
109
+ name: SERVER_NAME,
110
+ version: VERSION,
111
+ }, {
112
+ capabilities: {},
113
+ });
114
+ try {
115
+ const connectPromise = client.connect(transport);
116
+ // If the timeout wins the race below, this promise may still reject on its
117
+ // own later; swallow it so it doesn't surface as an unhandled rejection.
118
+ connectPromise.catch(() => { });
119
+ await this.withTimeout(connectPromise, timeoutMs);
120
+ this.connections.set(config.name, {
121
+ name: config.name,
122
+ client,
123
+ transport,
124
+ connected: true,
125
+ });
126
+ this.logger.info({ server: config.name }, 'Successfully connected to MCP server');
127
+ }
128
+ catch (error) {
129
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
130
+ // A timed-out connect leaves the spawned process running. Tear the
131
+ // transport down so we don't orphan a child for the proxy's lifetime.
132
+ try {
133
+ await transport.close();
134
+ }
135
+ catch (closeError) {
136
+ this.logger.debug({ server: config.name, error: closeError }, 'Failed to close transport for unsuccessful connection');
137
+ }
138
+ this.connections.set(config.name, {
139
+ name: config.name,
140
+ client,
141
+ transport,
142
+ connected: false,
143
+ lastError: errorMessage,
144
+ });
145
+ throw error;
146
+ }
147
+ }
148
+ /**
149
+ * Get a connected client by server name
150
+ */
151
+ getClient(serverName) {
152
+ const connection = this.connections.get(serverName);
153
+ return connection?.connected ? connection.client : undefined;
154
+ }
155
+ /**
156
+ * Get all connected clients
157
+ */
158
+ getConnectedClients() {
159
+ return Array.from(this.connections.values())
160
+ .filter((conn) => conn.connected)
161
+ .map((conn) => ({ name: conn.name, client: conn.client }));
162
+ }
163
+ /**
164
+ * Get status of all servers
165
+ */
166
+ getServerStatuses() {
167
+ return Array.from(this.connections.values()).map((conn) => ({
168
+ name: conn.name,
169
+ connected: conn.connected,
170
+ lastError: conn.lastError,
171
+ }));
172
+ }
173
+ /**
174
+ * Check if at least one server is connected
175
+ */
176
+ hasConnectedServers() {
177
+ return Array.from(this.connections.values()).some((conn) => conn.connected);
178
+ }
179
+ /**
180
+ * Disconnect from all servers
181
+ */
182
+ async disconnectAll() {
183
+ this.logger.info('Disconnecting from all MCP servers');
184
+ const disconnectPromises = Array.from(this.connections.values()).map(async (conn) => {
185
+ try {
186
+ await conn.client.close();
187
+ }
188
+ catch (error) {
189
+ this.logger.error({ server: conn.name, error }, 'Error disconnecting from server');
190
+ }
191
+ });
192
+ await Promise.allSettled(disconnectPromises);
193
+ this.connections.clear();
194
+ this.logger.info('All MCP servers disconnected');
195
+ }
196
+ }
197
+ //# sourceMappingURL=client-manager.js.map
@@ -0,0 +1,112 @@
1
+ import type { CompressionStats } from '../types/compression.js';
2
+ import type { CacheMetrics } from '../types/compression.js';
3
+ import type { Logger } from 'pino';
4
+ import { CompressionPersistence } from './compression-persistence.js';
5
+ import type { CompressionFallbackBehavior } from '../config/schema.js';
6
+ /**
7
+ * In-memory cache for compressed tool descriptions
8
+ * Key format: "serverName:toolName"
9
+ */
10
+ export declare class CompressionCache {
11
+ private cache;
12
+ private logger;
13
+ private persistence;
14
+ private noCompressPatterns;
15
+ private fallbackBehavior;
16
+ constructor(logger: Logger, persistence?: CompressionPersistence);
17
+ /**
18
+ * Set what to show for tools that have no compressed description yet.
19
+ * 'original' (default) keeps the server's description; 'blank' hides it so
20
+ * uncompressed tools cost no context until they have been compressed.
21
+ */
22
+ setFallbackBehavior(behavior: CompressionFallbackBehavior): void;
23
+ /**
24
+ * Current fallback behavior for uncompressed tools.
25
+ */
26
+ getFallbackBehavior(): CompressionFallbackBehavior;
27
+ /**
28
+ * Set patterns for tools that should display original descriptions
29
+ * (tools are still compressed and cached, but show original when listing)
30
+ */
31
+ setNoCompressPatterns(patterns: string[]): void;
32
+ /**
33
+ * Get all cached entries (for reporting/stats)
34
+ */
35
+ getCacheEntries(): Array<{
36
+ serverName: string;
37
+ toolName: string;
38
+ original?: string;
39
+ compressed: string;
40
+ compressedAt: string;
41
+ }>;
42
+ /**
43
+ * Check if a tool should bypass compression display
44
+ * (for showing original descriptions while still caching compressed versions)
45
+ */
46
+ private shouldBypassCompression;
47
+ /**
48
+ * Generate cache key from server and tool name
49
+ */
50
+ private getKey;
51
+ /**
52
+ * Save compressed description for a tool
53
+ * Always saves compression to cache regardless of noCompress patterns
54
+ */
55
+ saveCompressed(serverName: string, toolName: string, compressedDescription: string, originalDescription?: string): void;
56
+ /**
57
+ * Get description for a tool (session-aware)
58
+ * - If tool matches noCompress pattern: always use original (display-only bypass)
59
+ * - If tool is expanded in session: use original
60
+ * - If compressed exists: use compressed
61
+ * - Otherwise: apply the configured fallback behavior
62
+ */
63
+ getDescription(serverName: string, toolName: string, originalDescription?: string, isExpandedInSession?: boolean): string | undefined;
64
+ /**
65
+ * Check if a tool has compressed description
66
+ */
67
+ hasCompressed(serverName: string, toolName: string): boolean;
68
+ /**
69
+ * Get original description if cached
70
+ */
71
+ getOriginalDescription(serverName: string, toolName: string): string | undefined;
72
+ /**
73
+ * Get compressed description if cached
74
+ */
75
+ getCompressedDescription(serverName: string, toolName: string): string | undefined;
76
+ /**
77
+ * Get compression statistics
78
+ */
79
+ getStats(): CompressionStats;
80
+ /**
81
+ * Detailed cache metrics for reporting
82
+ */
83
+ getCacheMetrics(): CacheMetrics;
84
+ /**
85
+ * Clear all compressed descriptions
86
+ */
87
+ clear(): void;
88
+ /**
89
+ * Get all cached tools
90
+ */
91
+ getAllCached(): Array<{
92
+ serverName: string;
93
+ toolName: string;
94
+ }>;
95
+ /**
96
+ * Load cache from disk
97
+ */
98
+ loadFromDisk(): Promise<void>;
99
+ /**
100
+ * Save cache to disk
101
+ */
102
+ saveToDisk(): Promise<void>;
103
+ /**
104
+ * Clear cache from both memory and disk
105
+ */
106
+ clearAll(): Promise<void>;
107
+ /**
108
+ * Get on-disk cache file path if available
109
+ */
110
+ getCacheFilePath(): string;
111
+ }
112
+ //# sourceMappingURL=compression-cache.d.ts.map
@@ -0,0 +1,238 @@
1
+ import { CompressionPersistence } from './compression-persistence.js';
2
+ import { matchesIgnorePattern } from '../config/loader.js';
3
+ /**
4
+ * In-memory cache for compressed tool descriptions
5
+ * Key format: "serverName:toolName"
6
+ */
7
+ export class CompressionCache {
8
+ cache = {};
9
+ logger;
10
+ persistence;
11
+ noCompressPatterns = [];
12
+ fallbackBehavior = 'original';
13
+ constructor(logger, persistence) {
14
+ this.logger = logger;
15
+ this.persistence = persistence || new CompressionPersistence(logger);
16
+ }
17
+ /**
18
+ * Set what to show for tools that have no compressed description yet.
19
+ * 'original' (default) keeps the server's description; 'blank' hides it so
20
+ * uncompressed tools cost no context until they have been compressed.
21
+ */
22
+ setFallbackBehavior(behavior) {
23
+ this.fallbackBehavior = behavior;
24
+ this.logger.debug({ behavior }, 'Set compression fallback behavior');
25
+ }
26
+ /**
27
+ * Current fallback behavior for uncompressed tools.
28
+ */
29
+ getFallbackBehavior() {
30
+ return this.fallbackBehavior;
31
+ }
32
+ /**
33
+ * Set patterns for tools that should display original descriptions
34
+ * (tools are still compressed and cached, but show original when listing)
35
+ */
36
+ setNoCompressPatterns(patterns) {
37
+ this.noCompressPatterns = patterns;
38
+ this.logger.debug({ patterns }, 'Set noCompress patterns (display-only bypass)');
39
+ }
40
+ /**
41
+ * Get all cached entries (for reporting/stats)
42
+ */
43
+ getCacheEntries() {
44
+ return Object.entries(this.cache).map(([key, value]) => {
45
+ const [serverName, toolName] = key.split(':');
46
+ return {
47
+ serverName,
48
+ toolName,
49
+ original: value.original,
50
+ compressed: value.compressed,
51
+ compressedAt: value.compressedAt,
52
+ };
53
+ });
54
+ }
55
+ /**
56
+ * Check if a tool should bypass compression display
57
+ * (for showing original descriptions while still caching compressed versions)
58
+ */
59
+ shouldBypassCompression(toolName) {
60
+ return matchesIgnorePattern(toolName, this.noCompressPatterns);
61
+ }
62
+ /**
63
+ * Generate cache key from server and tool name
64
+ */
65
+ getKey(serverName, toolName) {
66
+ return `${serverName}:${toolName}`;
67
+ }
68
+ /**
69
+ * Save compressed description for a tool
70
+ * Always saves compression to cache regardless of noCompress patterns
71
+ */
72
+ saveCompressed(serverName, toolName, compressedDescription, originalDescription) {
73
+ const key = this.getKey(serverName, toolName);
74
+ this.cache[key] = {
75
+ original: originalDescription,
76
+ compressed: compressedDescription,
77
+ compressedAt: new Date().toISOString(),
78
+ };
79
+ this.logger.debug({ serverName, toolName }, 'Saved compressed description');
80
+ }
81
+ /**
82
+ * Get description for a tool (session-aware)
83
+ * - If tool matches noCompress pattern: always use original (display-only bypass)
84
+ * - If tool is expanded in session: use original
85
+ * - If compressed exists: use compressed
86
+ * - Otherwise: apply the configured fallback behavior
87
+ */
88
+ getDescription(serverName, toolName, originalDescription, isExpandedInSession) {
89
+ const fullToolName = `${serverName}__${toolName}`;
90
+ const key = this.getKey(serverName, toolName);
91
+ // Always bypass compression for noCompress patterns
92
+ if (this.shouldBypassCompression(fullToolName)) {
93
+ return originalDescription;
94
+ }
95
+ // If tool is expanded in session, use original description
96
+ if (isExpandedInSession) {
97
+ return this.cache[key]?.original || originalDescription;
98
+ }
99
+ const compressed = this.cache[key]?.compressed;
100
+ if (compressed) {
101
+ return compressed;
102
+ }
103
+ // Nothing compressed yet - 'blank' keeps the tool callable while costing
104
+ // no context; 'original' (default) shows the server's own description.
105
+ return this.fallbackBehavior === 'blank' ? '' : originalDescription;
106
+ }
107
+ /**
108
+ * Check if a tool has compressed description
109
+ */
110
+ hasCompressed(serverName, toolName) {
111
+ const key = this.getKey(serverName, toolName);
112
+ return !!this.cache[key]?.compressed;
113
+ }
114
+ /**
115
+ * Get original description if cached
116
+ */
117
+ getOriginalDescription(serverName, toolName) {
118
+ const key = this.getKey(serverName, toolName);
119
+ return this.cache[key]?.original;
120
+ }
121
+ /**
122
+ * Get compressed description if cached
123
+ */
124
+ getCompressedDescription(serverName, toolName) {
125
+ const key = this.getKey(serverName, toolName);
126
+ return this.cache[key]?.compressed;
127
+ }
128
+ /**
129
+ * Get compression statistics
130
+ */
131
+ getStats() {
132
+ return {
133
+ totalTools: Object.keys(this.cache).length,
134
+ compressedTools: Object.keys(this.cache).length,
135
+ expandedTools: [],
136
+ cacheSize: JSON.stringify(this.cache).length,
137
+ };
138
+ }
139
+ /**
140
+ * Detailed cache metrics for reporting
141
+ */
142
+ getCacheMetrics() {
143
+ const entries = this.getCacheEntries();
144
+ const perServer = {};
145
+ let totalOriginalChars = 0;
146
+ let totalCompressedChars = 0;
147
+ let missingOriginals = 0;
148
+ let latestCompressedAt;
149
+ for (const entry of entries) {
150
+ const originalLength = entry.original?.length ?? 0;
151
+ const compressedLength = entry.compressed.length;
152
+ if (!perServer[entry.serverName]) {
153
+ perServer[entry.serverName] = {
154
+ cached: 0,
155
+ totalOriginalChars: 0,
156
+ totalCompressedChars: 0,
157
+ missingOriginals: 0,
158
+ latestCompressedAt: undefined,
159
+ };
160
+ }
161
+ perServer[entry.serverName].cached += 1;
162
+ perServer[entry.serverName].totalOriginalChars += originalLength;
163
+ perServer[entry.serverName].totalCompressedChars += compressedLength;
164
+ if (!entry.original) {
165
+ perServer[entry.serverName].missingOriginals += 1;
166
+ }
167
+ const serverLatest = perServer[entry.serverName].latestCompressedAt;
168
+ if (entry.compressedAt && (!serverLatest || serverLatest < entry.compressedAt)) {
169
+ perServer[entry.serverName].latestCompressedAt = entry.compressedAt;
170
+ }
171
+ totalOriginalChars += originalLength;
172
+ totalCompressedChars += compressedLength;
173
+ if (!entry.original)
174
+ missingOriginals += 1;
175
+ if (!latestCompressedAt || latestCompressedAt < entry.compressedAt) {
176
+ latestCompressedAt = entry.compressedAt;
177
+ }
178
+ }
179
+ return {
180
+ totalCached: entries.length,
181
+ totalOriginalChars,
182
+ totalCompressedChars,
183
+ missingOriginals,
184
+ latestCompressedAt,
185
+ cacheSizeBytes: Buffer.byteLength(JSON.stringify(this.cache)),
186
+ perServer,
187
+ };
188
+ }
189
+ /**
190
+ * Clear all compressed descriptions
191
+ */
192
+ clear() {
193
+ this.cache = {};
194
+ this.logger.info('Cleared compression cache');
195
+ }
196
+ /**
197
+ * Get all cached tools
198
+ */
199
+ getAllCached() {
200
+ return Object.keys(this.cache).map((key) => {
201
+ const [serverName, toolName] = key.split(':');
202
+ return { serverName, toolName };
203
+ });
204
+ }
205
+ /**
206
+ * Load cache from disk
207
+ */
208
+ async loadFromDisk() {
209
+ const loadedCache = await this.persistence.load();
210
+ // Convert Map to cache object
211
+ for (const [key, value] of loadedCache.entries()) {
212
+ this.cache[key] = value;
213
+ }
214
+ this.logger.info({ count: Object.keys(this.cache).length }, 'Loaded compression cache into memory');
215
+ }
216
+ /**
217
+ * Save cache to disk
218
+ */
219
+ async saveToDisk() {
220
+ // Convert cache object to Map
221
+ const cacheMap = new Map(Object.entries(this.cache));
222
+ await this.persistence.save(cacheMap);
223
+ }
224
+ /**
225
+ * Clear cache from both memory and disk
226
+ */
227
+ async clearAll() {
228
+ this.clear();
229
+ await this.persistence.clear();
230
+ }
231
+ /**
232
+ * Get on-disk cache file path if available
233
+ */
234
+ getCacheFilePath() {
235
+ return this.persistence.getCacheFilePath();
236
+ }
237
+ }
238
+ //# sourceMappingURL=compression-cache.js.map
@@ -0,0 +1,36 @@
1
+ import type { Logger } from 'pino';
2
+ /**
3
+ * Service for persisting compressed tool descriptions to disk
4
+ */
5
+ export declare class CompressionPersistence {
6
+ private logger;
7
+ private cacheDir;
8
+ private cacheFile;
9
+ private readonly VERSION;
10
+ constructor(logger: Logger, cacheDir?: string);
11
+ /**
12
+ * Load cached compressions from disk
13
+ */
14
+ load(): Promise<Map<string, {
15
+ original?: string;
16
+ compressed: string;
17
+ compressedAt: string;
18
+ }>>;
19
+ /**
20
+ * Save compressions to disk
21
+ */
22
+ save(cache: Map<string, {
23
+ original?: string;
24
+ compressed: string;
25
+ compressedAt: string;
26
+ }>): Promise<void>;
27
+ /**
28
+ * Clear the cache file from disk
29
+ */
30
+ clear(): Promise<void>;
31
+ /**
32
+ * Get the cache file path (useful for debugging)
33
+ */
34
+ getCacheFilePath(): string;
35
+ }
36
+ //# sourceMappingURL=compression-persistence.d.ts.map