@webspire/mcp 0.9.0 → 0.10.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.
@@ -7,9 +7,15 @@ export interface RegistryOptions {
7
7
  }
8
8
  /**
9
9
  * Load the registry with fallback chain:
10
- * 1. Explicit --registry-file path
11
- * 2. Bundled data/registry.json (shipped with npm package)
12
- * 3. Remote fetch from --registry-url or default URL
10
+ * 1. Explicit --registry-file path (dev / CI override)
11
+ * 2. In-memory cache (5-min TTL, cleared when MCP process restarts)
12
+ * 3. Remote fetch from webspire.de if newer than disk cache, writes to disk
13
+ * 4. Disk cache (~/.cache/webspire/registry.json) — persists across restarts,
14
+ * always holds the last successfully fetched remote version
15
+ * 5. Bundled data/registry.json — frozen at npm publish time, last resort
16
+ *
17
+ * The date comparison (registry.generated) ensures the disk cache is only
18
+ * overwritten when the remote actually has newer content.
13
19
  */
14
20
  export declare function loadRegistry(options?: RegistryOptions): Promise<Registry>;
15
21
  /**
package/dist/registry.js CHANGED
@@ -1,19 +1,16 @@
1
1
  import { existsSync } from 'node:fs';
2
- import { readFile } from 'node:fs/promises';
2
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
3
+ import { homedir } from 'node:os';
3
4
  import { dirname, resolve } from 'node:path';
4
5
  import { fileURLToPath } from 'node:url';
5
6
  import { searchSnippets as searchEntries } from './search.js';
6
7
  const DEFAULT_REGISTRY_URL = 'https://webspire.de/registry.json';
7
8
  const CACHE_TTL_MS = 5 * 60 * 1000;
8
- let cache = null;
9
- /**
10
- * Resolve the path to the bundled registry.json shipped with this package.
11
- * Works both from src/ (development) and dist/ (published).
12
- */
9
+ const DISK_CACHE_PATH = resolve(homedir(), '.cache', 'webspire', 'registry.json');
10
+ let memCache = null;
13
11
  function getBundledRegistryPath() {
14
12
  try {
15
13
  const thisDir = dirname(fileURLToPath(import.meta.url));
16
- // From dist/ → ../data/registry.json, from src/ → ../data/registry.json
17
14
  const candidate = resolve(thisDir, '..', 'data', 'registry.json');
18
15
  return existsSync(candidate) ? candidate : undefined;
19
16
  }
@@ -21,11 +18,39 @@ function getBundledRegistryPath() {
21
18
  return undefined;
22
19
  }
23
20
  }
21
+ async function readDiskCache() {
22
+ try {
23
+ const raw = await readFile(DISK_CACHE_PATH, 'utf-8');
24
+ return JSON.parse(raw);
25
+ }
26
+ catch {
27
+ return null;
28
+ }
29
+ }
30
+ async function writeDiskCache(registry) {
31
+ try {
32
+ await mkdir(dirname(DISK_CACHE_PATH), { recursive: true });
33
+ await writeFile(DISK_CACHE_PATH, JSON.stringify(registry), 'utf-8');
34
+ }
35
+ catch {
36
+ // Best-effort — disk write failures are non-fatal
37
+ }
38
+ }
39
+ /** Returns true if registry a was generated after registry b. */
40
+ function isNewer(a, b) {
41
+ return new Date(a.generated ?? 0).getTime() > new Date(b.generated ?? 0).getTime();
42
+ }
24
43
  /**
25
44
  * Load the registry with fallback chain:
26
- * 1. Explicit --registry-file path
27
- * 2. Bundled data/registry.json (shipped with npm package)
28
- * 3. Remote fetch from --registry-url or default URL
45
+ * 1. Explicit --registry-file path (dev / CI override)
46
+ * 2. In-memory cache (5-min TTL, cleared when MCP process restarts)
47
+ * 3. Remote fetch from webspire.de if newer than disk cache, writes to disk
48
+ * 4. Disk cache (~/.cache/webspire/registry.json) — persists across restarts,
49
+ * always holds the last successfully fetched remote version
50
+ * 5. Bundled data/registry.json — frozen at npm publish time, last resort
51
+ *
52
+ * The date comparison (registry.generated) ensures the disk cache is only
53
+ * overwritten when the remote actually has newer content.
29
54
  */
30
55
  export async function loadRegistry(options) {
31
56
  // 1. Explicit file path
@@ -33,24 +58,42 @@ export async function loadRegistry(options) {
33
58
  const raw = await readFile(options.filePath, 'utf-8');
34
59
  return JSON.parse(raw);
35
60
  }
36
- // 2. Bundled registry (no cache needed file is local and fast)
61
+ // 2. In-memory cache (avoids re-fetching within the same session)
62
+ if (memCache && Date.now() - memCache.loadedAt < CACHE_TTL_MS) {
63
+ return memCache.registry;
64
+ }
65
+ // 3. Remote fetch — compare with disk cache, write if newer
66
+ try {
67
+ const url = options?.url ?? DEFAULT_REGISTRY_URL;
68
+ const response = await fetch(url);
69
+ if (response.ok) {
70
+ const remote = (await response.json());
71
+ memCache = { registry: remote, loadedAt: Date.now() };
72
+ // Write to disk only when remote is actually newer than what's cached
73
+ const disk = await readDiskCache();
74
+ if (!disk || isNewer(remote, disk)) {
75
+ await writeDiskCache(remote);
76
+ }
77
+ return remote;
78
+ }
79
+ }
80
+ catch {
81
+ // Network unavailable — fall through to disk cache
82
+ }
83
+ // 4. Disk cache — last successfully fetched version, survives process restarts
84
+ const disk = await readDiskCache();
85
+ if (disk) {
86
+ memCache = { registry: disk, loadedAt: Date.now() };
87
+ return disk;
88
+ }
89
+ // 5. Bundled registry — frozen at npm publish time
37
90
  const bundledPath = getBundledRegistryPath();
38
- if (bundledPath && !options?.url) {
91
+ if (bundledPath) {
39
92
  const raw = await readFile(bundledPath, 'utf-8');
40
93
  return JSON.parse(raw);
41
94
  }
42
- // 3. Remote fetch with TTL cache
43
- if (cache && Date.now() - cache.loadedAt < CACHE_TTL_MS) {
44
- return cache.registry;
45
- }
46
- const url = options?.url ?? DEFAULT_REGISTRY_URL;
47
- const response = await fetch(url);
48
- if (!response.ok) {
49
- throw new Error(`Failed to fetch registry from ${url}: ${response.statusText}`);
50
- }
51
- const registry = (await response.json());
52
- cache = { registry, loadedAt: Date.now() };
53
- return registry;
95
+ throw new Error('Registry unavailable: network unreachable and no local cache found. ' +
96
+ 'Try reinstalling @webspire/mcp or check your internet connection.');
54
97
  }
55
98
  /**
56
99
  * Load the bundled fonts.json shipped with this package.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webspire/mcp",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "MCP server for Webspire — AI-native discovery of CSS snippets, UI patterns, canvas effects, page templates, and font recommendations",
5
5
  "type": "module",
6
6
  "exports": {