@aiwerk/mcp-bridge 2.8.44 → 2.9.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.
@@ -1,45 +1,121 @@
1
1
  /**
2
- * CatalogClient — REST client for the AIWerk MCP Catalog API.
2
+ * CatalogClient — REST client for the AIWerk MCP catalog at bridge.aiwerk.ch.
3
3
  *
4
- * Default endpoint: https://catalog.aiwerk.ch
5
- * Supports local file caching with offline fallback.
4
+ * Restored 2026-05-03 after maintenance mode ended. Endpoint moved from the
5
+ * historical catalog.aiwerk.ch to bridge.aiwerk.ch/api/recipes/<name>/download.
6
+ * Every fetched recipe is Ed25519-verified against the bundled AIWerk public
7
+ * key before it is cached or returned. Unsigned or tampered recipes are
8
+ * refused.
6
9
  */
7
10
  import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, statSync } from "node:fs";
8
11
  import { join } from "node:path";
9
12
  import { homedir } from "node:os";
10
- // ── Error ────────────────────────────────────────────────────────────────────
13
+ import { createPublicKey, verify } from "node:crypto";
14
+ // ── Public key (Ed25519, AIWerk catalog signer) ──────────────────────────────
15
+ //
16
+ // The hosted bridge signs every recipe with the matching private key kept in
17
+ // pass under aiwerk/mcp-catalog-private-key. The public key is baked into
18
+ // the standalone bundle so a fresh install does not need to fetch it.
19
+ const AIWERK_CATALOG_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
20
+ MCowBQYDK2VwAyEAkHESasC8Mbf2+pGe+bhKRQkgOBSPcqGj0ZWGop4TS6k=
21
+ -----END PUBLIC KEY-----
22
+ `;
23
+ const SIGNED_FIELDS = [
24
+ "id",
25
+ "name",
26
+ "description",
27
+ "transports",
28
+ "auth",
29
+ "install",
30
+ "metadata",
31
+ "skill",
32
+ "localOnly",
33
+ ];
34
+ // ── Errors ───────────────────────────────────────────────────────────────────
11
35
  export class CatalogError extends Error {
12
36
  constructor(message) {
13
37
  super(message);
14
38
  this.name = "CatalogError";
15
39
  }
16
40
  }
41
+ export class CatalogSignatureError extends CatalogError {
42
+ constructor(message) {
43
+ super(message);
44
+ this.name = "CatalogSignatureError";
45
+ }
46
+ }
17
47
  // ── Helpers ──────────────────────────────────────────────────────────────────
18
48
  const TIMEOUT_MS = 5_000;
49
+ const DEFAULT_BASE_URL = "https://bridge.aiwerk.ch";
19
50
  const noop = {
20
51
  info: () => { },
21
52
  warn: () => { },
22
53
  error: () => { },
23
54
  debug: () => { },
24
55
  };
25
- // ── CatalogClient ────────────────────────────────────────────────────────────
56
+ function stableStringify(value) {
57
+ if (value === null || value === undefined)
58
+ return JSON.stringify(value);
59
+ if (typeof value !== "object")
60
+ return JSON.stringify(value);
61
+ if (Array.isArray(value)) {
62
+ return "[" + value.map((v) => stableStringify(v)).join(",") + "]";
63
+ }
64
+ const sorted = Object.keys(value).sort();
65
+ const entries = sorted.map((k) => JSON.stringify(k) + ":" + stableStringify(value[k]));
66
+ return "{" + entries.join(",") + "}";
67
+ }
68
+ function canonicalSignedPayload(recipe) {
69
+ const subset = {};
70
+ for (const field of SIGNED_FIELDS) {
71
+ if (field in recipe) {
72
+ subset[field] = recipe[field];
73
+ }
74
+ }
75
+ return stableStringify(subset);
76
+ }
26
77
  /**
27
- * CatalogClient REST client for the AIWerk MCP Catalog API.
28
- *
29
- * NOTE: File I/O operations (readCache, writeCache, etc.) are intentionally
30
- * synchronous. This is acceptable for CLI tools and bridge startup, but
31
- * should be converted to async if used in hot paths (e.g., per-request).
78
+ * Verify the Ed25519 signature on a recipe against the bundled AIWerk public
79
+ * key. Throws CatalogSignatureError on any failure (missing signature, wrong
80
+ * algorithm, tampered payload, key mismatch).
81
+ */
82
+ export function verifyRecipeSignature(recipe) {
83
+ const sig = recipe.signature;
84
+ if (!sig) {
85
+ throw new CatalogSignatureError("recipe has no signature");
86
+ }
87
+ if (sig.algorithm !== "ed25519") {
88
+ throw new CatalogSignatureError(`unsupported signature algorithm: ${sig.algorithm}`);
89
+ }
90
+ if (typeof sig.value !== "string" || sig.value.length === 0) {
91
+ throw new CatalogSignatureError("recipe signature value is empty");
92
+ }
93
+ const publicKey = createPublicKey(AIWERK_CATALOG_PUBLIC_KEY_PEM);
94
+ const payload = Buffer.from(canonicalSignedPayload(recipe), "utf-8");
95
+ const signatureBytes = Buffer.from(sig.value, "base64");
96
+ const ok = verify(null, payload, publicKey, signatureBytes);
97
+ if (!ok) {
98
+ throw new CatalogSignatureError("recipe signature does not match");
99
+ }
100
+ }
101
+ /**
102
+ * REST client for the AIWerk MCP catalog. File I/O is intentionally
103
+ * synchronous — fine for CLI tools and bridge startup. Signature verification
104
+ * runs on every fetch (including cache reads) so a tampered cache cannot
105
+ * silently slip through.
32
106
  */
33
107
  export class CatalogClient {
34
108
  baseUrl;
35
109
  cacheDir;
36
110
  logger;
37
111
  staleMs;
112
+ skipSignatureVerify;
38
113
  constructor(opts) {
39
- this.baseUrl = (opts?.baseUrl ?? "https://catalog.aiwerk.ch").replace(/\/+$/, "");
114
+ this.baseUrl = (opts?.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
40
115
  this.cacheDir = opts?.cacheDir ?? join(homedir(), ".mcp-bridge", "recipes");
41
116
  this.logger = opts?.logger ?? noop;
42
117
  this.staleMs = (opts?.staleDays ?? 7) * 24 * 60 * 60 * 1000;
118
+ this.skipSignatureVerify = opts?.skipSignatureVerify ?? false;
43
119
  }
44
120
  // ── Private helpers ──────────────────────────────────────────────────────
45
121
  async fetchJson(path) {
@@ -95,11 +171,23 @@ export class CatalogClient {
95
171
  return true;
96
172
  }
97
173
  }
174
+ verifyOrThrow(recipe, name) {
175
+ if (this.skipSignatureVerify)
176
+ return;
177
+ try {
178
+ verifyRecipeSignature(recipe);
179
+ }
180
+ catch (err) {
181
+ const reason = err instanceof Error ? err.message : String(err);
182
+ throw new CatalogSignatureError(`Recipe "${name}" failed signature verification: ${reason}`);
183
+ }
184
+ }
98
185
  // ── Public API ───────────────────────────────────────────────────────────
99
186
  /** Search for recipes by keyword. */
100
187
  async search(query) {
101
188
  const encoded = encodeURIComponent(query);
102
- return this.fetchJson(`/api/search?q=${encoded}`);
189
+ const result = await this.fetchJson(`/api/search?q=${encoded}`);
190
+ return Array.isArray(result) ? result : (result.results ?? []);
103
191
  }
104
192
  /** List recipes with optional filtering. */
105
193
  async list(opts) {
@@ -110,75 +198,54 @@ export class CatalogClient {
110
198
  params.set("category", opts.category);
111
199
  if (opts?.sort)
112
200
  params.set("sort", opts.sort);
113
- if (opts?.hostedSafe)
114
- params.set("hostedSafe", "true");
115
201
  const qs = params.toString();
116
202
  return this.fetchJson(`/api/recipes${qs ? `?${qs}` : ""}`);
117
203
  }
118
- /** Download a recipe from the catalog and cache it locally. */
204
+ /**
205
+ * Download a recipe from the catalog, verify its signature, and cache it
206
+ * locally. Throws CatalogSignatureError if the recipe is unsigned or
207
+ * tampered — nothing is written to the cache in that case.
208
+ */
119
209
  async download(name) {
120
210
  const recipe = await this.fetchJson(`/api/recipes/${encodeURIComponent(name)}/download`);
211
+ this.verifyOrThrow(recipe, name);
121
212
  this.writeCache(name, recipe);
122
213
  return recipe;
123
214
  }
124
215
  /**
125
- * Resolve a recipe returns cached if available, otherwise fetches from catalog.
126
- * Falls back to cache when the catalog is unreachable (offline mode).
216
+ * Resolve a recipe by name. Returns the cached copy if fresh; otherwise
217
+ * fetches from the catalog. Falls back to a stale cache if the network is
218
+ * unreachable. Signature is verified on both fresh and cached paths so a
219
+ * tampered cache cannot slip through.
127
220
  */
128
221
  async resolve(name) {
129
222
  const cached = this.readCache(name);
130
- if (cached && !this.isCacheStale(name))
223
+ if (cached && !this.isCacheStale(name)) {
224
+ this.verifyOrThrow(cached, name);
131
225
  return cached;
226
+ }
132
227
  try {
133
228
  const recipe = await this.fetchJson(`/api/recipes/${encodeURIComponent(name)}/download`);
229
+ this.verifyOrThrow(recipe, name);
134
230
  this.writeCache(name, recipe);
135
231
  return recipe;
136
232
  }
137
233
  catch (err) {
234
+ if (err instanceof CatalogSignatureError)
235
+ throw err;
138
236
  if (err instanceof CatalogError && err.message.startsWith("Recipe not found:")) {
139
237
  throw err;
140
238
  }
141
239
  if (cached) {
240
+ // Stale cache fallback: still verify the signature so an offline user
241
+ // does not run a tampered local copy.
242
+ this.verifyOrThrow(cached, name);
142
243
  this.logger.warn(`Catalog unreachable for "${name}", using cached version`);
143
244
  return cached;
144
245
  }
145
246
  throw new CatalogError(`Cannot resolve recipe "${name}": catalog unreachable and no local cache`);
146
247
  }
147
248
  }
148
- /**
149
- * Bootstrap by downloading the top N most popular recipes.
150
- * Skips already-cached recipes unless they are stale.
151
- */
152
- async bootstrap(limit = 15, hostedSafe = false) {
153
- const { results } = await this.list({ limit, sort: "popular", hostedSafe });
154
- const names = [];
155
- const toDownload = [];
156
- for (const entry of results) {
157
- const name = entry.name;
158
- if (!this.isCacheStale(name)) {
159
- names.push(name);
160
- continue;
161
- }
162
- toDownload.push(name);
163
- }
164
- const BATCH_SIZE = 5;
165
- for (let i = 0; i < toDownload.length; i += BATCH_SIZE) {
166
- const batch = toDownload.slice(i, i + BATCH_SIZE);
167
- const results = await Promise.allSettled(batch.map(async (name) => {
168
- await this.download(name);
169
- return name;
170
- }));
171
- for (const r of results) {
172
- if (r.status === "fulfilled") {
173
- names.push(r.value);
174
- }
175
- else {
176
- this.logger.warn(`Failed to download recipe: ${r.reason}`);
177
- }
178
- }
179
- }
180
- return names;
181
- }
182
249
  /** Synchronously read a recipe from local cache. Returns null if not cached. */
183
250
  getCached(name) {
184
251
  return this.readCache(name);
@@ -1,5 +1,4 @@
1
1
  import { BridgeConfig, Logger, McpServerConfig } from "./types.js";
2
- import type { CatalogRecipe } from "./catalog-client.js";
3
2
  /**
4
3
  * Load ~/.openclaw/.env as a fallback env source.
5
4
  *
@@ -25,43 +24,11 @@ export interface LoadConfigOptions {
25
24
  * 4. Validate required fields
26
25
  */
27
26
  export declare function loadConfig(options?: LoadConfigOptions): BridgeConfig;
28
- /**
29
- * Warn about deprecated bundled recipes.
30
- * In v2.8.0, bundled servers/ recipes are deprecated in favor of catalog.
31
- * They will be removed in v3.0.0.
32
- */
33
- export declare function warnDeprecatedBundledRecipes(logger: Logger): void;
34
27
  /** Get the default config directory path. */
35
28
  export declare function getConfigDir(configPath?: string): string;
36
29
  /** Initialize the config directory with template files. */
37
30
  export declare function initConfigDir(logger: Logger): void;
38
- /**
39
- * Bootstrap the local recipe cache from the catalog.
40
- * Downloads top N popular recipes if cache is empty or force=true.
41
- * Returns array of recipe names now cached. Never throws on network errors.
42
- */
43
- export declare function bootstrapCatalog(options?: {
44
- logger?: Logger;
45
- cacheDir?: string;
46
- catalogUrl?: string;
47
- limit?: number;
48
- force?: boolean;
49
- requireCleanAudit?: boolean;
50
- catalog?: boolean;
51
- }): Promise<string[]>;
52
- /**
53
- * Merge cached catalog recipes into a BridgeConfig.
54
- * Only adds recipes whose required env vars are all present in process.env.
55
- * Never overwrites manually configured servers.
56
- *
57
- * IMPORTANT: Must be called AFTER loadConfig() / dotenv, since env var
58
- * checks rely on process.env being fully populated.
59
- */
60
- export declare function mergeRecipesIntoConfig(config: BridgeConfig, options?: {
61
- cacheDir?: string;
62
- logger?: Logger;
63
- }): BridgeConfig;
64
- /** Convert a catalog recipe JSON to McpServerConfig, or null if unsupported. */
31
+ import type { CatalogRecipe } from "./catalog-client.js";
65
32
  export declare function recipeToServerConfig(recipe: CatalogRecipe): McpServerConfig | null;
66
- /** Collect all env var names required by a recipe. */
33
+ /** Collect all env var names required by a recipe (auth.envVars + ${VAR} refs). */
67
34
  export declare function collectRequiredEnvVars(recipe: CatalogRecipe): string[];
@@ -3,7 +3,6 @@ import { join, extname } from "path";
3
3
  import { homedir } from "os";
4
4
  import { resolveEnvVars } from "./transport-base.js";
5
5
  import { randomBytes } from "crypto";
6
- import { CatalogClient } from "./catalog-client.js";
7
6
  const DEFAULT_CONFIG_DIR = join(homedir(), ".mcp-bridge");
8
7
  const DEFAULT_CONFIG_FILE = "config.json";
9
8
  const DEFAULT_ENV_FILE = ".env";
@@ -154,18 +153,6 @@ export function loadConfig(options = {}) {
154
153
  }
155
154
  return config;
156
155
  }
157
- /**
158
- * Warn about deprecated bundled recipes.
159
- * In v2.8.0, bundled servers/ recipes are deprecated in favor of catalog.
160
- * They will be removed in v3.0.0.
161
- */
162
- export function warnDeprecatedBundledRecipes(logger) {
163
- const catalogClient = new CatalogClient({ logger });
164
- const cached = catalogClient.listCached();
165
- if (cached.length === 0) {
166
- logger.info('[mcp-bridge] Tip: Run bootstrapCatalog() to fetch recipes from catalog.aiwerk.ch (replaces bundled servers/)');
167
- }
168
- }
169
156
  /** Get the default config directory path. */
170
157
  export function getConfigDir(configPath) {
171
158
  if (!configPath)
@@ -218,120 +205,7 @@ export function initConfigDir(logger) {
218
205
  }
219
206
  logger.info(`Config directory ready: ${dir}`);
220
207
  }
221
- // ── Catalog bootstrap ─────────────────────────────────────────────────────────
222
- const noopLogger = {
223
- info: () => { },
224
- warn: () => { },
225
- error: () => { },
226
- debug: () => { },
227
- };
228
- /**
229
- * Extract the depAudit value from a catalog recipe's metadata.verification field.
230
- * Returns null if not present.
231
- */
232
- function getDepAudit(recipe) {
233
- const meta = recipe.metadata;
234
- const verification = meta?.verification;
235
- const depAudit = verification?.depAudit;
236
- return typeof depAudit === "string" ? depAudit : null;
237
- }
238
- /**
239
- * Bootstrap the local recipe cache from the catalog.
240
- * Downloads top N popular recipes if cache is empty or force=true.
241
- * Returns array of recipe names now cached. Never throws on network errors.
242
- */
243
- export async function bootstrapCatalog(options) {
244
- // If catalog is explicitly disabled, skip fetching
245
- if (options?.catalog === false) {
246
- const logger = options?.logger ?? noopLogger;
247
- logger.info("[mcp-bridge] Catalog discovery disabled (catalog: false), skipping bootstrap");
248
- return [];
249
- }
250
- const logger = options?.logger ?? noopLogger;
251
- const cacheDir = options?.cacheDir ?? join(homedir(), ".mcp-bridge", "recipes");
252
- const requireCleanAudit = options?.requireCleanAudit ?? false;
253
- const client = new CatalogClient({
254
- baseUrl: options?.catalogUrl,
255
- cacheDir,
256
- logger,
257
- });
258
- // Check if cache already has recipes
259
- if (!options?.force) {
260
- const cached = client.listCached();
261
- if (cached.length > 0) {
262
- logger.debug(`Recipe cache already has ${cached.length} recipes, skipping bootstrap`);
263
- return cached;
264
- }
265
- }
266
- try {
267
- return await client.bootstrap(options?.limit ?? 15, requireCleanAudit);
268
- }
269
- catch (err) {
270
- logger.warn(`Catalog unreachable during bootstrap: ${err instanceof Error ? err.message : err}`);
271
- return [];
272
- }
273
- }
274
- /**
275
- * Merge cached catalog recipes into a BridgeConfig.
276
- * Only adds recipes whose required env vars are all present in process.env.
277
- * Never overwrites manually configured servers.
278
- *
279
- * IMPORTANT: Must be called AFTER loadConfig() / dotenv, since env var
280
- * checks rely on process.env being fully populated.
281
- */
282
- export function mergeRecipesIntoConfig(config, options) {
283
- const logger = options?.logger ?? noopLogger;
284
- // autoMerge defaults to false (opt-in) — only merge when explicitly enabled
285
- if (config.autoMerge !== true) {
286
- logger.debug("[mcp-bridge] Auto-merge disabled (autoMerge is not true), skipping recipe merge");
287
- return config;
288
- }
289
- const cacheDir = options?.cacheDir ?? join(homedir(), ".mcp-bridge", "recipes");
290
- const client = new CatalogClient({ cacheDir, logger });
291
- const names = client.listCached();
292
- if (names.length === 0)
293
- return config;
294
- const servers = { ...config.servers };
295
- const requireCleanAudit = config.security?.requireCleanAudit ?? false;
296
- for (const name of names) {
297
- // Never overwrite manually configured servers
298
- if (servers[name])
299
- continue;
300
- const recipe = client.getCached(name);
301
- if (!recipe)
302
- continue;
303
- // Check depAudit security policy
304
- const depAudit = getDepAudit(recipe);
305
- const auditOk = depAudit === null || depAudit === "clean" || depAudit === "not-applicable";
306
- if (!auditOk) {
307
- if (requireCleanAudit) {
308
- logger.warn(`⚠️ Skipping server "${name}": has known security advisories (depAudit: ${depAudit}). Set security.requireCleanAudit=false to allow.`);
309
- continue;
310
- }
311
- else {
312
- logger.info(`ℹ️ Server "${name}" has known advisories (depAudit: ${depAudit}). Set security.requireCleanAudit=true to block.`);
313
- }
314
- }
315
- const converted = recipeToServerConfig(recipe);
316
- if (!converted) {
317
- logger.debug(`Skipping recipe "${name}": unsupported format`);
318
- continue;
319
- }
320
- // Check that all required env vars are available
321
- const requiredVars = collectRequiredEnvVars(recipe);
322
- const missing = requiredVars.filter((v) => !process.env[v]);
323
- if (missing.length > 0) {
324
- logger.debug(`Skipping recipe "${name}": missing env vars: ${missing.join(", ")}`);
325
- continue;
326
- }
327
- servers[name] = converted;
328
- logger.debug(`Added catalog recipe "${name}" to config`);
329
- }
330
- return { ...config, servers };
331
- }
332
- /** Convert a catalog recipe JSON to McpServerConfig, or null if unsupported. */
333
208
  export function recipeToServerConfig(recipe) {
334
- // v2 recipe: has transports array
335
209
  if (Array.isArray(recipe.transports) && recipe.transports.length > 0) {
336
210
  const t = recipe.transports[0];
337
211
  if (t.type === "stdio") {
@@ -353,7 +227,6 @@ export function recipeToServerConfig(recipe) {
353
227
  }
354
228
  return null;
355
229
  }
356
- // v1 recipe: has transport string
357
230
  if (recipe.transport === "stdio") {
358
231
  return {
359
232
  transport: "stdio",
@@ -373,15 +246,13 @@ export function recipeToServerConfig(recipe) {
373
246
  }
374
247
  return null;
375
248
  }
376
- /** Collect all env var names required by a recipe. */
249
+ /** Collect all env var names required by a recipe (auth.envVars + ${VAR} refs). */
377
250
  export function collectRequiredEnvVars(recipe) {
378
251
  const vars = new Set();
379
- // From auth.envVars
380
252
  if (Array.isArray(recipe.auth?.envVars)) {
381
253
  for (const v of recipe.auth.envVars)
382
254
  vars.add(v);
383
255
  }
384
- // From env object: extract ${VAR} references
385
256
  const envObj = Array.isArray(recipe.transports)
386
257
  ? recipe.transports[0]?.env
387
258
  : recipe.env;
@@ -394,10 +265,8 @@ export function collectRequiredEnvVars(recipe) {
394
265
  }
395
266
  }
396
267
  }
397
- // If auth is explicitly required but no env vars were found,
398
- // return a placeholder to prevent auto-registration without credentials
399
268
  if (recipe.auth?.required === true && vars.size === 0) {
400
269
  vars.add("__AUTH_REQUIRED__");
401
270
  }
402
- return [...vars];
271
+ return Array.from(vars);
403
272
  }
@@ -18,7 +18,7 @@ export { ToolResolver } from "./tool-resolution.js";
18
18
  export type { ToolResolutionResult, ToolResolutionCandidate } from "./tool-resolution.js";
19
19
  export { convertJsonSchemaToTypeBox, createToolParameters, setTypeBoxLoader, setSchemaLogger } from "./schema-convert.js";
20
20
  export { initializeProtocol, fetchToolsList, PACKAGE_VERSION } from "./protocol.js";
21
- export { loadConfig, parseEnvFile, initConfigDir, getConfigDir, bootstrapCatalog, mergeRecipesIntoConfig, warnDeprecatedBundledRecipes } from "./config.js";
21
+ export { loadConfig, parseEnvFile, initConfigDir, getConfigDir } from "./config.js";
22
22
  export type { Logger, McpServerConfig, McpClientConfig, HttpAuthConfig, RetryConfig, McpTool, McpRequest, McpCallRequest, McpResponse, JsonRpcMessage, McpTransport, McpServerConnection, BridgeConfig, RequestIdState, RequestIdGenerator, } from "./types.js";
23
23
  export { nextRequestId } from "./types.js";
24
24
  export { pickRegisteredToolName } from "./tool-naming.js";
@@ -26,7 +26,3 @@ export { StandaloneServer } from "./standalone-server.js";
26
26
  export { checkForUpdate, checkPluginUpdate, getUpdateNotice, runUpdate, resetNoticeFlag } from "./update-checker.js";
27
27
  export type { UpdateInfo, PluginUpdateInfo } from "./update-checker.js";
28
28
  export { filterServers, buildFilteredDescription } from "./smart-filter.js";
29
- export { CatalogClient, CatalogError } from "./catalog-client.js";
30
- export type { CatalogRecipe, CatalogSearchResult } from "./catalog-client.js";
31
- export { RecipeCache } from "./recipe-cache.js";
32
- export type { CachedRecipe } from "./recipe-cache.js";
package/dist/src/index.js CHANGED
@@ -20,7 +20,7 @@ export { convertJsonSchemaToTypeBox, createToolParameters, setTypeBoxLoader, set
20
20
  // Protocol helpers
21
21
  export { initializeProtocol, fetchToolsList, PACKAGE_VERSION } from "./protocol.js";
22
22
  // Config
23
- export { loadConfig, parseEnvFile, initConfigDir, getConfigDir, bootstrapCatalog, mergeRecipesIntoConfig, warnDeprecatedBundledRecipes } from "./config.js";
23
+ export { loadConfig, parseEnvFile, initConfigDir, getConfigDir } from "./config.js";
24
24
  export { nextRequestId } from "./types.js";
25
25
  // Tool naming
26
26
  export { pickRegisteredToolName } from "./tool-naming.js";
@@ -30,7 +30,3 @@ export { StandaloneServer } from "./standalone-server.js";
30
30
  export { checkForUpdate, checkPluginUpdate, getUpdateNotice, runUpdate, resetNoticeFlag } from "./update-checker.js";
31
31
  // Smart filter
32
32
  export { filterServers, buildFilteredDescription } from "./smart-filter.js";
33
- // Catalog client
34
- export { CatalogClient, CatalogError } from "./catalog-client.js";
35
- // Recipe cache
36
- export { RecipeCache } from "./recipe-cache.js";
@@ -75,42 +75,6 @@ export type RouterDispatchResponse = {
75
75
  callCount: number;
76
76
  lastCall: string;
77
77
  }>;
78
- } | {
79
- action: "search";
80
- query: string;
81
- results: Array<{
82
- id: string;
83
- name: string;
84
- description: string;
85
- category?: string;
86
- auth?: string;
87
- origin?: string;
88
- maturity?: string;
89
- sideEffects?: string;
90
- pricing?: string;
91
- signed?: boolean;
92
- }>;
93
- } | {
94
- action: "catalog";
95
- recipes: Array<{
96
- id: string;
97
- name: string;
98
- description: string;
99
- category?: string;
100
- auth?: string;
101
- origin?: string;
102
- maturity?: string;
103
- sideEffects?: string;
104
- pricing?: string;
105
- signed?: boolean;
106
- }>;
107
- } | {
108
- action: "install";
109
- server: string;
110
- installed: boolean;
111
- message: string;
112
- missingEnvVars?: string[];
113
- credentialsUrl?: string;
114
78
  } | {
115
79
  action: "remove";
116
80
  server: string;
@@ -173,7 +137,6 @@ export declare class McpRouter {
173
137
  private readonly tokenManager;
174
138
  private readonly rateLimiter;
175
139
  private readonly requestIdState;
176
- private readonly catalogClient;
177
140
  private intentRouter;
178
141
  private promotion;
179
142
  constructor(servers: Record<string, McpServerConfig>, clientConfig: McpClientConfig, logger: Logger, transportRefs?: Partial<RouterTransportRefs>);