semantic-code-mcp 2.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.
@@ -0,0 +1,504 @@
1
+ // Using native global fetch (Node.js 18+)
2
+
3
+ /**
4
+ * Version checker for package registries across multiple ecosystems.
5
+ * Supports caching and automatic retry for transient failures.
6
+ */
7
+ export class VersionChecker {
8
+ /**
9
+ * Creates a new VersionChecker instance.
10
+ * @param {Object} config - Configuration object
11
+ * @param {number} [config.versionCheckTimeout=10000] - Timeout for API requests in ms
12
+ * @param {number} [config.versionCacheTTL=300000] - Cache TTL in ms (default: 5 minutes)
13
+ * @param {number} [config.retryAttempts=1] - Number of retry attempts for failed requests
14
+ * @param {number} [config.retryDelay=500] - Delay between retries in ms
15
+ */
16
+ constructor(config) {
17
+ this.config = config;
18
+ this.timeout = config.versionCheckTimeout || 10000; // 10 seconds for slow APIs
19
+ this.cacheTTL = config.versionCacheTTL || 300000; // 5 minutes
20
+ this.retryAttempts = config.retryAttempts ?? 1;
21
+ this.retryDelay = config.retryDelay || 500;
22
+
23
+ // Simple in-memory cache: Map<cacheKey, { data, timestamp }>
24
+ this.cache = new Map();
25
+ }
26
+
27
+ /**
28
+ * Checks the latest version of a package from its registry.
29
+ * @param {string} packageName - Package name (may include ecosystem prefix like "npm:")
30
+ * @param {string} [ecosystem] - Ecosystem identifier (auto-detected if not provided)
31
+ * @returns {Promise<Object>} Result object with package, ecosystem, version, found, source, or error
32
+ */
33
+ async checkVersion(packageName, ecosystem) {
34
+ try {
35
+ // 1. Sanitization
36
+ if (!packageName || typeof packageName !== 'string') {
37
+ throw new Error("Invalid package name");
38
+ }
39
+ packageName = packageName.trim();
40
+
41
+ // 2. Auto-detect
42
+ if (!ecosystem) {
43
+ ecosystem = this._detectEcosystem(packageName);
44
+ } else {
45
+ ecosystem = ecosystem.toLowerCase().trim();
46
+ }
47
+
48
+ // 3. Check cache
49
+ const cacheKey = `${ecosystem || 'unknown'}:${packageName}`;
50
+ const cached = this._getFromCache(cacheKey);
51
+ if (cached) {
52
+ return { ...cached, fromCache: true };
53
+ }
54
+
55
+ // 4. Fetch
56
+ const result = await this._fetchVersion(packageName, ecosystem);
57
+ const response = {
58
+ package: packageName,
59
+ ecosystem: ecosystem || "unknown",
60
+ ...result
61
+ };
62
+
63
+ // 5. Cache successful results
64
+ if (result.found) {
65
+ this._setCache(cacheKey, response);
66
+ }
67
+
68
+ return response;
69
+ } catch (error) {
70
+ return {
71
+ package: packageName,
72
+ ecosystem: ecosystem || "unknown",
73
+ error: error.message,
74
+ found: false,
75
+ message: `Failed to check version: ${error.message}`
76
+ };
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Gets a value from cache if it exists and hasn't expired.
82
+ * @param {string} key - Cache key
83
+ * @returns {Object|null} Cached data or null
84
+ * @private
85
+ */
86
+ _getFromCache(key) {
87
+ const entry = this.cache.get(key);
88
+ if (!entry) return null;
89
+
90
+ if (Date.now() - entry.timestamp > this.cacheTTL) {
91
+ this.cache.delete(key);
92
+ return null;
93
+ }
94
+ return entry.data;
95
+ }
96
+
97
+ /**
98
+ * Sets a value in the cache.
99
+ * @param {string} key - Cache key
100
+ * @param {Object} data - Data to cache
101
+ * @private
102
+ */
103
+ _setCache(key, data) {
104
+ this.cache.set(key, { data, timestamp: Date.now() });
105
+ }
106
+
107
+ /**
108
+ * Clears the version cache.
109
+ */
110
+ clearCache() {
111
+ this.cache.clear();
112
+ }
113
+
114
+ /**
115
+ * Detects the ecosystem from package name prefixes.
116
+ * @param {string} packageName - Package name with optional prefix
117
+ * @returns {string|null} Detected ecosystem or null
118
+ * @private
119
+ */
120
+ _detectEcosystem(packageName) {
121
+ // Check specific prefixes first (order matters - more specific before generic)
122
+ if (packageName.startsWith("npm:")) return "npm";
123
+ if (packageName.startsWith("pip:")) return "pypi";
124
+ if (packageName.startsWith("go:")) return "go";
125
+ if (packageName.startsWith("cargo:")) return "crates";
126
+ if (packageName.startsWith("gem:")) return "rubygems";
127
+ if (packageName.startsWith("composer:")) return "packagist";
128
+ if (packageName.startsWith("nuget:")) return "nuget";
129
+ if (packageName.startsWith("pod:")) return "cocoapods";
130
+ if (packageName.startsWith("hex:")) return "hex";
131
+
132
+ // R ecosystem
133
+ if (packageName.startsWith("cran:") || packageName.startsWith("R:")) return "cran";
134
+ // Perl ecosystem
135
+ if (packageName.startsWith("cpan:") || packageName.startsWith("perl:")) return "cpan";
136
+ // Dart ecosystem
137
+ if (packageName.startsWith("dart:") || packageName.startsWith("pub:")) return "pub";
138
+
139
+ // Homebrew
140
+ if (packageName.startsWith("brew:")) return "homebrew";
141
+ // Conda
142
+ if (packageName.startsWith("conda:")) return "conda";
143
+ // Clojars (Clojure)
144
+ if (packageName.startsWith("clojars:") || packageName.startsWith("clj:")) return "clojars";
145
+ // Hackage (Haskell)
146
+ if (packageName.startsWith("hackage:") || packageName.startsWith("haskell:")) return "hackage";
147
+ // Julia
148
+ if (packageName.startsWith("julia:") || packageName.startsWith("jl:")) return "julia";
149
+ // Swift Package Manager
150
+ if (packageName.startsWith("swift:") || packageName.startsWith("spm:")) return "swift";
151
+ // Chocolatey (Windows)
152
+ if (packageName.startsWith("choco:")) return "chocolatey";
153
+
154
+ // Check for Maven patterns last (generic colon for group:artifact or explicit mvn:)
155
+ if (packageName.startsWith("mvn:") || packageName.includes(":")) return "maven";
156
+
157
+ // Default to npm for unprefixed package names (most common use case)
158
+ return "npm";
159
+ }
160
+
161
+ /**
162
+ * Fetches with automatic retry for transient failures.
163
+ * @param {string} url - URL to fetch
164
+ * @param {Object} options - Fetch options
165
+ * @param {number} [retriesLeft] - Number of retries remaining
166
+ * @returns {Promise<Response>} Fetch response
167
+ * @private
168
+ */
169
+ async _fetchWithRetry(url, options, retriesLeft = this.retryAttempts) {
170
+ try {
171
+ return await fetch(url, options);
172
+ } catch (err) {
173
+ if (retriesLeft > 0) {
174
+ await new Promise(resolve => setTimeout(resolve, this.retryDelay));
175
+ return this._fetchWithRetry(url, options, retriesLeft - 1);
176
+ }
177
+ throw err;
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Fetches the latest version from the appropriate registry.
183
+ * @param {string} pkgInput - Package name (with or without prefix)
184
+ * @param {string} ecosystem - Target ecosystem
185
+ * @returns {Promise<Object>} Result with version info or error
186
+ * @private
187
+ */
188
+ async _fetchVersion(pkgInput, ecosystem) {
189
+ const signal = AbortSignal.timeout(this.timeout);
190
+ let url, version;
191
+
192
+ // Helper to handle simple JSON fetch with specific 404 message
193
+ const fetchJson = async (u, errorPrefix) => {
194
+ const res = await this._fetchWithRetry(u, { signal });
195
+ if (res.status === 404) throw new Error(`${errorPrefix} not found (404)`);
196
+ if (!res.ok) throw new Error(`${errorPrefix} registry error: ${res.status} ${res.statusText}`);
197
+ return res.json();
198
+ };
199
+
200
+ switch (ecosystem) {
201
+ case "npm":
202
+ let npmPkg = pkgInput.replace("npm:", "");
203
+ if (npmPkg.startsWith("@") && npmPkg.includes("/")) {
204
+ npmPkg = npmPkg.replace("/", "%2f");
205
+ }
206
+ url = `https://registry.npmjs.org/${npmPkg}`;
207
+ const npmData = await fetchJson(url, "npm package");
208
+ version = npmData["dist-tags"]?.latest;
209
+ break;
210
+
211
+ case "pypi":
212
+ url = `https://pypi.org/pypi/${pkgInput.replace("pip:", "")}/json`;
213
+ const pypiData = await fetchJson(url, "PyPI package");
214
+ version = pypiData.info?.version;
215
+ break;
216
+
217
+ case "packagist":
218
+ const packPkg = pkgInput.replace("composer:", "");
219
+ const parts = packPkg.split("/");
220
+ if (parts.length !== 2) throw new Error("Packagist package must be in 'vendor/package' format");
221
+ url = `https://repo.packagist.org/p2/${packPkg}.json`;
222
+ const packData = await fetchJson(url, "Packagist package");
223
+ const versions = packData.packages[packPkg];
224
+ if (!versions || versions.length === 0) throw new Error("No versions found in registry response");
225
+ version = versions[0].version;
226
+ break;
227
+
228
+ case "crates":
229
+ url = `https://crates.io/api/v1/crates/${pkgInput.replace("cargo:", "")}`;
230
+ const crateRes = await this._fetchWithRetry(url, { headers: { "User-Agent": "Smart-Coding-MCP" }, signal });
231
+ if (crateRes.status === 404) throw new Error("Crate not found (404)");
232
+ if (!crateRes.ok) throw new Error(`Crates.io error: ${crateRes.status}`);
233
+ const crateData = await crateRes.json();
234
+ version = crateData.crate?.max_stable_version || crateData.crate?.newest_version;
235
+ break;
236
+
237
+ case "maven":
238
+ const mvnPkg = pkgInput.replace("mvn:", "");
239
+ const [g, a] = mvnPkg.split(":");
240
+ if (!g || !a) throw new Error("Maven artifact must be in 'group:artifact' format");
241
+ url = `https://search.maven.org/solrsearch/select?q=g:"${g}"+AND+a:"${a}"&wt=json`;
242
+ const mvnRes = await this._fetchWithRetry(url, { signal });
243
+ if (!mvnRes.ok) throw new Error(`Maven Central error: ${mvnRes.status}`);
244
+ const mvnData = await mvnRes.json();
245
+ if (mvnData.response?.docs?.length > 0) {
246
+ version = mvnData.response.docs[0].latestVersion;
247
+ } else {
248
+ throw new Error("Maven artifact not found");
249
+ }
250
+ break;
251
+
252
+ case "go":
253
+ url = `https://proxy.golang.org/${pkgInput.replace("go:", "")}/@latest`;
254
+ const goData = await fetchJson(url, "Go module");
255
+ version = goData.Version;
256
+ break;
257
+
258
+ case "rubygems":
259
+ url = `https://rubygems.org/api/v1/versions/${pkgInput.replace("gem:", "")}/latest.json`;
260
+ const gemData = await fetchJson(url, "Gem");
261
+ version = gemData.version;
262
+ break;
263
+
264
+ case "nuget":
265
+ const nugetPkg = pkgInput.replace("nuget:", "").toLowerCase();
266
+ url = `https://api.nuget.org/v3-flatcontainer/${nugetPkg}/index.json`;
267
+ const nugetData = await fetchJson(url, "NuGet package");
268
+ if (nugetData.versions && nugetData.versions.length > 0) {
269
+ version = nugetData.versions[nugetData.versions.length - 1];
270
+ } else {
271
+ throw new Error("No versions found");
272
+ }
273
+ break;
274
+
275
+ case "cocoapods":
276
+ // CocoaPods trunk API requires special client authentication.
277
+ // The API returns pod metadata but version info requires parsing specs repo.
278
+ // We intentionally fall back to suggesting a web search for reliable results.
279
+ url = `https://trunk.cocoapods.org/api/v1/pods/${pkgInput.replace("pod:", "")}`;
280
+ const podRes = await this._fetchWithRetry(url, { signal });
281
+ if (podRes.ok) {
282
+ throw new Error("CocoaPods API requires client; please perform a web search.");
283
+ }
284
+ throw new Error("CocoaPods lookup not supported directly; please perform a web search.");
285
+
286
+ case "hex":
287
+ url = `https://hex.pm/api/packages/${pkgInput.replace("hex:", "")}`;
288
+ const hexData = await fetchJson(url, "Hex package");
289
+ if (hexData.releases && hexData.releases.length > 0) {
290
+ version = hexData.releases[0].version;
291
+ } else {
292
+ throw new Error("No releases found");
293
+ }
294
+ break;
295
+
296
+ case "cran":
297
+ // R packages from CRAN
298
+ const cranPkg = pkgInput.replace(/^R:|^cran:/i, "");
299
+ url = `https://crandb.r-pkg.org/${cranPkg}`;
300
+ const cranData = await fetchJson(url, "CRAN package");
301
+ version = cranData.Version;
302
+ break;
303
+
304
+ case "cpan":
305
+ // Perl modules from CPAN/MetaCPAN
306
+ const cpanPkg = pkgInput.replace(/^perl:|^cpan:/i, "");
307
+ url = `https://fastapi.metacpan.org/v1/module/${cpanPkg}`;
308
+ const cpanData = await fetchJson(url, "CPAN module");
309
+ version = cpanData.version;
310
+ break;
311
+
312
+ case "pub":
313
+ // Dart packages from pub.dev
314
+ const pubPkg = pkgInput.replace(/^dart:|^pub:/i, "");
315
+ url = `https://pub.dev/api/packages/${pubPkg}`;
316
+ const pubData = await fetchJson(url, "pub.dev package");
317
+ version = pubData.latest?.version;
318
+ break;
319
+
320
+ case "homebrew":
321
+ // Homebrew formulae (macOS/Linux)
322
+ const brewPkg = pkgInput.replace(/^brew:/i, "");
323
+ url = `https://formulae.brew.sh/api/formula/${brewPkg}.json`;
324
+ const brewData = await fetchJson(url, "Homebrew formula");
325
+ version = brewData.versions?.stable;
326
+ break;
327
+
328
+ case "conda":
329
+ // Conda packages (defaults channel via anaconda.org)
330
+ const condaPkg = pkgInput.replace(/^conda:/i, "");
331
+ // Try conda-forge first, then defaults
332
+ url = `https://api.anaconda.org/package/conda-forge/${condaPkg}`;
333
+ try {
334
+ const condaData = await fetchJson(url, "Conda package");
335
+ version = condaData.latest_version;
336
+ } catch {
337
+ // Fallback to main channel
338
+ url = `https://api.anaconda.org/package/anaconda/${condaPkg}`;
339
+ const condaData2 = await fetchJson(url, "Conda package");
340
+ version = condaData2.latest_version;
341
+ }
342
+ break;
343
+
344
+ case "clojars":
345
+ // Clojure packages from Clojars
346
+ const cljPkg = pkgInput.replace(/^clojars:|^clj:/i, "");
347
+ // Format: group/artifact or just artifact (implies same group)
348
+ const cljParts = cljPkg.includes("/") ? cljPkg.split("/") : [cljPkg, cljPkg];
349
+ url = `https://clojars.org/api/artifacts/${cljParts[0]}/${cljParts[1]}`;
350
+ const cljData = await fetchJson(url, "Clojars artifact");
351
+ version = cljData.latest_version;
352
+ break;
353
+
354
+ case "hackage":
355
+ // Haskell packages from Hackage
356
+ const hackPkg = pkgInput.replace(/^hackage:|^haskell:/i, "");
357
+ url = `https://hackage.haskell.org/package/${hackPkg}/preferred.json`;
358
+ const hackData = await fetchJson(url, "Hackage package");
359
+ // preferred.json returns array of version ranges, get the latest normal version
360
+ if (hackData["normal-version"] && hackData["normal-version"].length > 0) {
361
+ version = hackData["normal-version"][0];
362
+ } else {
363
+ throw new Error("No versions found");
364
+ }
365
+ break;
366
+
367
+ case "julia":
368
+ // Julia packages from JuliaHub/General registry
369
+ const juliaPkg = pkgInput.replace(/^julia:|^jl:/i, "");
370
+ url = `https://juliahub.com/ui/Packages/General/${juliaPkg}`;
371
+ // JuliaHub doesn't have a simple JSON API, use package registry
372
+ const juliaUrl = `https://raw.githubusercontent.com/JuliaRegistries/General/master/${juliaPkg[0].toUpperCase()}/${juliaPkg}/Versions.toml`;
373
+ const juliaRes = await this._fetchWithRetry(juliaUrl, { signal });
374
+ if (!juliaRes.ok) throw new Error("Julia package not found");
375
+ const juliaText = await juliaRes.text();
376
+ // Parse TOML to find latest version (versions are in ["x.y.z"] sections)
377
+ const juliaVersions = juliaText.match(/\["([\d.]+)"\]/g);
378
+ if (juliaVersions && juliaVersions.length > 0) {
379
+ version = juliaVersions[juliaVersions.length - 1].replace(/[\["\]]/g, "");
380
+ url = juliaUrl;
381
+ } else {
382
+ throw new Error("No versions found in Julia registry");
383
+ }
384
+ break;
385
+
386
+ case "swift":
387
+ // Swift Package Index
388
+ const swiftPkg = pkgInput.replace(/^swift:|^spm:/i, "");
389
+ // Format: owner/repo
390
+ if (!swiftPkg.includes("/")) {
391
+ throw new Error("Swift package must be in 'owner/repo' format");
392
+ }
393
+ url = `https://swiftpackageindex.com/api/packages/${swiftPkg}`;
394
+ const swiftRes = await this._fetchWithRetry(url, {
395
+ headers: { "Accept": "application/json" },
396
+ signal
397
+ });
398
+ if (swiftRes.status === 404) throw new Error("Swift package not found (404)");
399
+ if (!swiftRes.ok) throw new Error(`Swift Package Index error: ${swiftRes.status}`);
400
+ const swiftData = await swiftRes.json();
401
+ // Get latest stable release
402
+ if (swiftData.releases && swiftData.releases.length > 0) {
403
+ const stable = swiftData.releases.find(r => !r.preRelease) || swiftData.releases[0];
404
+ version = stable.tagName?.replace(/^v/, "");
405
+ } else {
406
+ throw new Error("No releases found");
407
+ }
408
+ break;
409
+
410
+ case "chocolatey":
411
+ // Chocolatey (Windows) packages
412
+ const chocoPkg = pkgInput.replace(/^choco:/i, "").toLowerCase();
413
+ url = `https://community.chocolatey.org/api/v2/package-versions/${chocoPkg}`;
414
+ const chocoRes = await this._fetchWithRetry(url, { signal });
415
+ if (chocoRes.status === 404) throw new Error("Chocolatey package not found (404)");
416
+ if (!chocoRes.ok) throw new Error(`Chocolatey error: ${chocoRes.status}`);
417
+ const chocoVersions = await chocoRes.json();
418
+ if (chocoVersions && chocoVersions.length > 0) {
419
+ version = chocoVersions[chocoVersions.length - 1];
420
+ } else {
421
+ throw new Error("No versions found");
422
+ }
423
+ break;
424
+
425
+ default:
426
+ return {
427
+ found: false,
428
+ message: `Ecosystem '${ecosystem || "unknown"}' not directly supported. Please perform a web search for '${pkgInput} latest version'.`
429
+ };
430
+ }
431
+
432
+ if (version) {
433
+ return {
434
+ found: true,
435
+ version: version,
436
+ source: url
437
+ };
438
+ }
439
+
440
+ throw new Error("Version not found in response (parsing failed)");
441
+ }
442
+ }
443
+
444
+ /**
445
+ * Returns the MCP tool definition for version checking.
446
+ * @returns {Object} Tool definition object
447
+ */
448
+ export function getToolDefinition() {
449
+ return {
450
+ name: "d_check_last_version",
451
+ description: "Get the latest version of a library/package from its official registry. Supported ecosystems: npm (JS/TS), PyPI (Python), Packagist (PHP), Crates.io (Rust), Maven (Java/Kotlin), Go, RubyGems, NuGet (.NET), Hex (Elixir), CRAN (R), CPAN (Perl), pub.dev (Dart), Homebrew (macOS), Conda (Python/R), Clojars (Clojure), Hackage (Haskell), Julia, Swift PM, Chocolatey (Windows). Returns the version string to help you avoid using outdated dependencies.",
452
+ inputSchema: {
453
+ type: "object",
454
+ properties: {
455
+ package: {
456
+ type: "string",
457
+ description: "Package name (e.g., 'express', 'requests', 'flutter', 'brew:wget', 'conda:numpy', 'swift:apple/swift-nio'). Use prefixes for explicit ecosystem detection."
458
+ },
459
+ ecosystem: {
460
+ type: "string",
461
+ enum: ["npm", "pypi", "packagist", "crates", "maven", "go", "rubygems", "nuget", "cocoapods", "hex", "cran", "cpan", "pub", "homebrew", "conda", "clojars", "hackage", "julia", "swift", "chocolatey"],
462
+ description: "Package ecosystem (optional - auto-detected from prefix)"
463
+ }
464
+ },
465
+ required: ["package"]
466
+ },
467
+ annotations: {
468
+ title: "Check Latest Package Version",
469
+ readOnlyHint: true,
470
+ idempotentHint: true
471
+ }
472
+ };
473
+ }
474
+
475
+ /**
476
+ * Handles an MCP tool call for version checking.
477
+ * @param {Object} request - MCP request object
478
+ * @param {VersionChecker} checker - VersionChecker instance
479
+ * @returns {Promise<Object>} MCP response object
480
+ */
481
+ export async function handleToolCall(request, checker) {
482
+ const pkg = request.params.arguments.package;
483
+ const ecosystem = request.params.arguments.ecosystem;
484
+
485
+ const result = await checker.checkVersion(pkg, ecosystem);
486
+
487
+ if (result.found) {
488
+ const cacheNote = result.fromCache ? " (cached)" : "";
489
+ return {
490
+ content: [{
491
+ type: "text",
492
+ text: `Latest version of \`${result.package}\` (${result.ecosystem}): **${result.version}**${cacheNote}\n\nSource: ${result.source}`
493
+ }]
494
+ };
495
+ } else {
496
+ return {
497
+ content: [{
498
+ type: "text",
499
+ text: result.message || `Could not find version for \`${pkg}\`. Error: ${result.error}`
500
+ }],
501
+ isError: true
502
+ };
503
+ }
504
+ }
@@ -0,0 +1,75 @@
1
+ export class CacheClearer {
2
+ constructor(embedder, cache, config, indexer) {
3
+ this.cache = cache;
4
+ this.config = config;
5
+ this.indexer = indexer;
6
+ this.isClearing = false;
7
+ }
8
+
9
+ async execute() {
10
+ // Check if indexing is in progress
11
+ if (this.indexer && this.indexer.isIndexing) {
12
+ throw new Error("Cannot clear cache while indexing is in progress. Please wait for indexing to complete.");
13
+ }
14
+
15
+ // Check if cache is currently being saved (race condition prevention)
16
+ if (this.cache.isSaving) {
17
+ throw new Error("Cannot clear cache while cache is being saved. Please try again in a moment.");
18
+ }
19
+
20
+ // Check if a clear operation is already in progress (prevent concurrent clears)
21
+ if (this.isClearing) {
22
+ throw new Error("Cache clear operation already in progress. Please wait for it to complete.");
23
+ }
24
+
25
+ this.isClearing = true;
26
+
27
+ try {
28
+ await this.cache.clear();
29
+ return {
30
+ success: true,
31
+ message: `Cache cleared successfully. Next indexing will be a full rebuild.`,
32
+ cacheDirectory: this.config.cacheDirectory
33
+ };
34
+ } finally {
35
+ this.isClearing = false;
36
+ }
37
+ }
38
+ }
39
+
40
+ export function getToolDefinition() {
41
+ return {
42
+ name: "c_clear_cache",
43
+ description: "Clears the embeddings cache, forcing a complete reindex on next search or manual index operation. Useful when encountering cache corruption or after major codebase changes.",
44
+ inputSchema: {
45
+ type: "object",
46
+ properties: {}
47
+ },
48
+ annotations: {
49
+ title: "Clear Embeddings Cache",
50
+ readOnlyHint: false,
51
+ destructiveHint: true,
52
+ idempotentHint: true,
53
+ openWorldHint: false
54
+ }
55
+ };
56
+ }
57
+
58
+ export async function handleToolCall(request, cacheClearer) {
59
+ try {
60
+ const result = await cacheClearer.execute();
61
+ return {
62
+ content: [{
63
+ type: "text",
64
+ text: `${result.message}\n\nCache directory: ${result.cacheDirectory}`
65
+ }]
66
+ };
67
+ } catch (error) {
68
+ return {
69
+ content: [{
70
+ type: "text",
71
+ text: `Failed to clear cache: ${error.message}`
72
+ }]
73
+ };
74
+ }
75
+ }