@hasna/mcps 0.0.2 → 0.0.4

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/dist/index.js CHANGED
@@ -25,8 +25,97 @@ var __export = (target, all) => {
25
25
  set: (newValue) => all[name] = () => newValue
26
26
  });
27
27
  };
28
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
28
29
  var __require = import.meta.require;
29
30
 
31
+ // src/lib/config.ts
32
+ import { join } from "path";
33
+ import { homedir } from "os";
34
+ var MCPS_DIR, DB_PATH, REGISTRY_API_URL = "https://registry.modelcontextprotocol.io/v0/servers", TOOL_PREFIX_SEPARATOR = "__";
35
+ var init_config = __esm(() => {
36
+ MCPS_DIR = join(homedir(), ".mcps");
37
+ DB_PATH = join(MCPS_DIR, "registry.db");
38
+ });
39
+
40
+ // src/lib/db.ts
41
+ import { Database } from "bun:sqlite";
42
+ import { mkdirSync } from "fs";
43
+ function getDb() {
44
+ if (db)
45
+ return db;
46
+ mkdirSync(MCPS_DIR, { recursive: true });
47
+ db = new Database(DB_PATH, { create: true });
48
+ db.exec("PRAGMA journal_mode = WAL");
49
+ db.exec("PRAGMA busy_timeout = 5000");
50
+ db.exec("PRAGMA foreign_keys = ON");
51
+ db.exec(`
52
+ CREATE TABLE IF NOT EXISTS servers (
53
+ id TEXT PRIMARY KEY,
54
+ name TEXT NOT NULL,
55
+ description TEXT,
56
+ command TEXT NOT NULL,
57
+ args TEXT NOT NULL DEFAULT '[]',
58
+ env TEXT NOT NULL DEFAULT '{}',
59
+ transport TEXT NOT NULL DEFAULT 'stdio',
60
+ url TEXT,
61
+ source TEXT NOT NULL DEFAULT 'local',
62
+ enabled INTEGER NOT NULL DEFAULT 1,
63
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
64
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
65
+ )
66
+ `);
67
+ db.exec(`
68
+ CREATE TABLE IF NOT EXISTS tool_cache (
69
+ server_id TEXT NOT NULL,
70
+ name TEXT NOT NULL,
71
+ description TEXT NOT NULL DEFAULT '',
72
+ input_schema TEXT NOT NULL DEFAULT '{}',
73
+ cached_at TEXT NOT NULL DEFAULT (datetime('now')),
74
+ PRIMARY KEY (server_id, name),
75
+ FOREIGN KEY (server_id) REFERENCES servers(id) ON DELETE CASCADE
76
+ )
77
+ `);
78
+ db.exec("CREATE INDEX IF NOT EXISTS idx_tool_cache_server ON tool_cache(server_id)");
79
+ try {
80
+ db.exec("ALTER TABLE servers ADD COLUMN last_connected_at TEXT");
81
+ } catch {}
82
+ try {
83
+ db.exec("ALTER TABLE servers ADD COLUMN last_error TEXT");
84
+ } catch {}
85
+ db.exec(`
86
+ CREATE TABLE IF NOT EXISTS sources (
87
+ id TEXT PRIMARY KEY,
88
+ name TEXT NOT NULL,
89
+ type TEXT NOT NULL,
90
+ url TEXT NOT NULL,
91
+ description TEXT,
92
+ enabled INTEGER NOT NULL DEFAULT 1,
93
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
94
+ )
95
+ `);
96
+ const count = db.query("SELECT COUNT(*) as c FROM sources").get().c;
97
+ if (count === 0) {
98
+ db.exec(`
99
+ INSERT OR IGNORE INTO sources (id, name, type, url, description) VALUES
100
+ ('official-registry', 'Official MCP Registry', 'mcp-registry', 'https://registry.modelcontextprotocol.io/v0/servers', 'The official Model Context Protocol server registry'),
101
+ ('awesome-mcp-servers', 'Awesome MCP Servers', 'awesome-list', 'https://raw.githubusercontent.com/punkpeye/awesome-mcp-servers/main/README.md', 'Curated list of MCP servers by punkpeye'),
102
+ ('npm-mcp', 'npm MCP Packages', 'npm-search', 'https://registry.npmjs.org/-/v1/search', 'Search npm packages for MCP servers'),
103
+ ('github-mcp-topic', 'GitHub MCP Topic', 'github-topic', 'https://api.github.com/search/repositories', 'GitHub repositories tagged with mcp-server topic')
104
+ `);
105
+ }
106
+ return db;
107
+ }
108
+ function closeDb() {
109
+ if (db) {
110
+ db.close();
111
+ db = null;
112
+ }
113
+ }
114
+ var db = null;
115
+ var init_db = __esm(() => {
116
+ init_config();
117
+ });
118
+
30
119
  // node_modules/ajv/dist/compile/codegen/code.js
31
120
  var require_code = __commonJS((exports) => {
32
121
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -6286,7 +6375,7 @@ var require_formats = __commonJS((exports) => {
6286
6375
  }
6287
6376
  var TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i;
6288
6377
  function getTime(strictTimeZone) {
6289
- return function time3(str) {
6378
+ return function time(str) {
6290
6379
  const matches = TIME.exec(str);
6291
6380
  if (!matches)
6292
6381
  return false;
@@ -6955,92 +7044,381 @@ var require_cross_spawn = __commonJS((exports, module) => {
6955
7044
  module.exports._enoent = enoent;
6956
7045
  });
6957
7046
 
6958
- // src/lib/db.ts
6959
- import { Database } from "bun:sqlite";
6960
- import { mkdirSync } from "fs";
6961
-
6962
- // src/lib/config.ts
6963
- import { join } from "path";
6964
- import { homedir } from "os";
6965
- var MCPS_DIR = join(homedir(), ".mcps");
6966
- var DB_PATH = join(MCPS_DIR, "registry.db");
6967
- var REGISTRY_API_URL = "https://registry.modelcontextprotocol.io/v0/servers";
6968
- var TOOL_PREFIX_SEPARATOR = "__";
6969
-
6970
- // src/lib/db.ts
6971
- var db = null;
6972
- function getDb() {
6973
- if (db)
6974
- return db;
6975
- mkdirSync(MCPS_DIR, { recursive: true });
6976
- db = new Database(DB_PATH, { create: true });
6977
- db.exec("PRAGMA journal_mode = WAL");
6978
- db.exec("PRAGMA busy_timeout = 5000");
6979
- db.exec("PRAGMA foreign_keys = ON");
6980
- db.exec(`
6981
- CREATE TABLE IF NOT EXISTS servers (
6982
- id TEXT PRIMARY KEY,
6983
- name TEXT NOT NULL,
6984
- description TEXT,
6985
- command TEXT NOT NULL,
6986
- args TEXT NOT NULL DEFAULT '[]',
6987
- env TEXT NOT NULL DEFAULT '{}',
6988
- transport TEXT NOT NULL DEFAULT 'stdio',
6989
- url TEXT,
6990
- source TEXT NOT NULL DEFAULT 'local',
6991
- enabled INTEGER NOT NULL DEFAULT 1,
6992
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
6993
- updated_at TEXT NOT NULL DEFAULT (datetime('now'))
6994
- )
6995
- `);
6996
- db.exec(`
6997
- CREATE TABLE IF NOT EXISTS tool_cache (
6998
- server_id TEXT NOT NULL,
6999
- name TEXT NOT NULL,
7000
- description TEXT NOT NULL DEFAULT '',
7001
- input_schema TEXT NOT NULL DEFAULT '{}',
7002
- cached_at TEXT NOT NULL DEFAULT (datetime('now')),
7003
- PRIMARY KEY (server_id, name),
7004
- FOREIGN KEY (server_id) REFERENCES servers(id) ON DELETE CASCADE
7005
- )
7006
- `);
7007
- db.exec("CREATE INDEX IF NOT EXISTS idx_tool_cache_server ON tool_cache(server_id)");
7008
- return db;
7047
+ // src/lib/sources.ts
7048
+ var exports_sources = {};
7049
+ __export(exports_sources, {
7050
+ searchSource: () => searchSource,
7051
+ removeSource: () => removeSource,
7052
+ listSources: () => listSources,
7053
+ getSource: () => getSource,
7054
+ findServers: () => findServers,
7055
+ enableSource: () => enableSource,
7056
+ disableSource: () => disableSource,
7057
+ clearCache: () => clearCache,
7058
+ addSource: () => addSource
7059
+ });
7060
+ import { mkdirSync as mkdirSync2, existsSync, readFileSync, writeFileSync, readdirSync, unlinkSync } from "fs";
7061
+ import { join as join2 } from "path";
7062
+ function getCacheFile(sourceId) {
7063
+ return join2(CACHE_DIR, `${sourceId}.json`);
7064
+ }
7065
+ function readCache(sourceId) {
7066
+ try {
7067
+ const file = getCacheFile(sourceId);
7068
+ if (!existsSync(file))
7069
+ return null;
7070
+ const data = JSON.parse(readFileSync(file, "utf-8"));
7071
+ return data;
7072
+ } catch {
7073
+ return null;
7074
+ }
7009
7075
  }
7010
- function closeDb() {
7011
- if (db) {
7012
- db.close();
7013
- db = null;
7076
+ function writeCache(sourceId, results) {
7077
+ try {
7078
+ mkdirSync2(CACHE_DIR, { recursive: true });
7079
+ writeFileSync(getCacheFile(sourceId), JSON.stringify({ results, cachedAt: Date.now() }), "utf-8");
7080
+ } catch {}
7081
+ }
7082
+ function clearCache(sourceId) {
7083
+ try {
7084
+ if (!existsSync(CACHE_DIR))
7085
+ return;
7086
+ const files = readdirSync(CACHE_DIR);
7087
+ for (const file of files) {
7088
+ if (!file.endsWith(".json"))
7089
+ continue;
7090
+ if (!sourceId || file.startsWith(`${sourceId}.`)) {
7091
+ try {
7092
+ unlinkSync(join2(CACHE_DIR, file));
7093
+ } catch {}
7094
+ }
7095
+ }
7096
+ } catch {}
7097
+ }
7098
+ function generateId2(name) {
7099
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
7100
+ }
7101
+ function listSources() {
7102
+ const db2 = getDb();
7103
+ const rows = db2.query("SELECT * FROM sources ORDER BY created_at ASC").all();
7104
+ return rows.map((r) => ({ ...r, enabled: r.enabled === 1 }));
7105
+ }
7106
+ function getSource(id) {
7107
+ const db2 = getDb();
7108
+ const row = db2.query("SELECT * FROM sources WHERE id = ?").get(id);
7109
+ if (!row)
7110
+ return null;
7111
+ return { ...row, enabled: row.enabled === 1 };
7112
+ }
7113
+ function addSource(opts) {
7114
+ const db2 = getDb();
7115
+ const id = generateId2(opts.name);
7116
+ db2.run("INSERT INTO sources (id, name, type, url, description) VALUES (?, ?, ?, ?, ?)", [id, opts.name, opts.type, opts.url, opts.description ?? null]);
7117
+ return getSource(id);
7118
+ }
7119
+ function removeSource(id) {
7120
+ const db2 = getDb();
7121
+ db2.run("DELETE FROM sources WHERE id = ?", [id]);
7122
+ }
7123
+ function enableSource(id) {
7124
+ const db2 = getDb();
7125
+ db2.run("UPDATE sources SET enabled = 1 WHERE id = ?", [id]);
7126
+ }
7127
+ function disableSource(id) {
7128
+ const db2 = getDb();
7129
+ db2.run("UPDATE sources SET enabled = 0 WHERE id = ?", [id]);
7130
+ }
7131
+ async function searchMcpRegistry(source, query) {
7132
+ try {
7133
+ const res = await fetch(source.url);
7134
+ if (!res.ok)
7135
+ return [];
7136
+ const data = await res.json();
7137
+ const q = query.toLowerCase();
7138
+ return (data.servers || []).map((e) => e.server).filter((s) => q === "" || s.name.toLowerCase().includes(q) || (s.description || "").toLowerCase().includes(q)).map((s) => ({
7139
+ name: s.name,
7140
+ description: s.description || "",
7141
+ source: "registry",
7142
+ sourceId: source.id,
7143
+ url: s.repository?.url,
7144
+ githubRepo: s.repository?.url,
7145
+ installCmd: s.packages?.[0] ? s.packages[0].registryType === "npm" ? `npx -y ${s.packages[0].identifier}` : s.packages[0].identifier : undefined,
7146
+ npmPackage: s.packages?.find((p) => p.registryType === "npm")?.identifier
7147
+ }));
7148
+ } catch {
7149
+ return [];
7014
7150
  }
7015
7151
  }
7152
+ async function searchAwesomeList(source, query) {
7153
+ try {
7154
+ const res = await fetch(source.url);
7155
+ if (!res.ok)
7156
+ return [];
7157
+ const text = await res.text();
7158
+ const results = [];
7159
+ const linkPattern = /\[([^\]]+)\]\((https?:\/\/[^)]+)\)(?:\s*[-\u2013]\s*([^\n]+))?/g;
7160
+ const q = query.toLowerCase();
7161
+ let match;
7162
+ while ((match = linkPattern.exec(text)) !== null) {
7163
+ const name = match[1].trim();
7164
+ const url2 = match[2].trim();
7165
+ const description = match[3]?.trim() || "";
7166
+ if ((url2.includes("github.com") || url2.includes("npmjs.com")) && name.length > 2) {
7167
+ if (q === "" || `${name} ${description}`.toLowerCase().includes(q)) {
7168
+ results.push({
7169
+ name,
7170
+ description,
7171
+ source: "awesome",
7172
+ sourceId: source.id,
7173
+ url: url2,
7174
+ githubRepo: url2.includes("github.com") ? url2 : undefined,
7175
+ installCmd: url2.includes("npmjs.com") ? `npx -y ${url2.split("/").pop()}` : undefined
7176
+ });
7177
+ }
7178
+ }
7179
+ }
7180
+ return results;
7181
+ } catch {
7182
+ return [];
7183
+ }
7184
+ }
7185
+ function scoreNpmPackage(pkg) {
7186
+ const name = pkg.name.toLowerCase();
7187
+ const keywords = (pkg.keywords || []).map((k) => k.toLowerCase());
7188
+ let score = 0;
7189
+ if (name.includes("mcp-server"))
7190
+ score += 3;
7191
+ else if (name.startsWith("mcp-") || name.endsWith("-mcp"))
7192
+ score += 2;
7193
+ else if (name.includes("mcp"))
7194
+ score += 1;
7195
+ if (keywords.includes("mcp"))
7196
+ score += 1;
7197
+ if (keywords.includes("mcp-server"))
7198
+ score += 2;
7199
+ if (keywords.some((k) => k.includes("modelcontextprotocol")))
7200
+ score += 2;
7201
+ return score;
7202
+ }
7203
+ async function searchNpm(source, query) {
7204
+ try {
7205
+ const url2 = new URL(source.url);
7206
+ url2.searchParams.set("text", `mcp-server ${query}`);
7207
+ url2.searchParams.set("size", "50");
7208
+ const res = await fetch(url2.toString());
7209
+ if (!res.ok)
7210
+ return [];
7211
+ const data = await res.json();
7212
+ const q = query.toLowerCase();
7213
+ const scored = (data.objects || []).map((o) => ({ pkg: o.package, score: scoreNpmPackage(o.package) })).filter(({ pkg, score }) => {
7214
+ if (score < 1)
7215
+ return false;
7216
+ const text = `${pkg.name} ${pkg.description || ""} ${(pkg.keywords || []).join(" ")}`.toLowerCase();
7217
+ return q === "" || text.includes(q);
7218
+ });
7219
+ scored.sort((a, b) => b.score - a.score);
7220
+ return scored.map(({ pkg }) => ({
7221
+ name: pkg.name,
7222
+ description: pkg.description || "",
7223
+ source: "npm",
7224
+ sourceId: source.id,
7225
+ url: pkg.links?.repository || pkg.links?.npm,
7226
+ npmPackage: pkg.name,
7227
+ installCmd: `npx -y ${pkg.name}`
7228
+ }));
7229
+ } catch {
7230
+ return [];
7231
+ }
7232
+ }
7233
+ async function searchGitHubTopic(source, query) {
7234
+ const token = process.env.GITHUB_TOKEN;
7235
+ try {
7236
+ const url2 = new URL(source.url);
7237
+ const q = query ? `${query} topic:mcp-server` : "topic:mcp-server";
7238
+ url2.searchParams.set("q", q);
7239
+ url2.searchParams.set("sort", "stars");
7240
+ url2.searchParams.set("per_page", "30");
7241
+ const headers = {
7242
+ Accept: "application/vnd.github.v3+json"
7243
+ };
7244
+ if (token)
7245
+ headers["Authorization"] = `Bearer ${token}`;
7246
+ const res = await fetch(url2.toString(), { headers });
7247
+ if (!res.ok)
7248
+ return [];
7249
+ const data = await res.json();
7250
+ return (data.items || []).map((repo) => ({
7251
+ name: repo.full_name,
7252
+ description: repo.description || "",
7253
+ source: "github",
7254
+ sourceId: source.id,
7255
+ url: repo.html_url,
7256
+ githubRepo: repo.html_url,
7257
+ stars: repo.stargazers_count
7258
+ }));
7259
+ } catch {
7260
+ return [];
7261
+ }
7262
+ }
7263
+ async function fetchSource(source) {
7264
+ switch (source.type) {
7265
+ case "mcp-registry":
7266
+ return searchMcpRegistry(source, "");
7267
+ case "awesome-list":
7268
+ return searchAwesomeList(source, "");
7269
+ case "npm-search":
7270
+ return searchNpm(source, "");
7271
+ case "github-topic":
7272
+ return searchGitHubTopic(source, "");
7273
+ default:
7274
+ return [];
7275
+ }
7276
+ }
7277
+ function filterResults(results, query) {
7278
+ if (!query)
7279
+ return results;
7280
+ const q = query.toLowerCase();
7281
+ return results.filter((r) => {
7282
+ const text = `${r.name} ${r.description || ""}`.toLowerCase();
7283
+ return text.includes(q);
7284
+ });
7285
+ }
7286
+ async function searchSource(source, query, noCache = false) {
7287
+ if (!noCache) {
7288
+ const cached2 = readCache(source.id);
7289
+ if (cached2 && Date.now() - cached2.cachedAt < DEFAULT_TTL_MS) {
7290
+ return filterResults(cached2.results, query);
7291
+ }
7292
+ }
7293
+ const results = await fetchSource(source);
7294
+ writeCache(source.id, results);
7295
+ return filterResults(results, query);
7296
+ }
7297
+ function deduplicate(results) {
7298
+ const seen = new Set;
7299
+ return results.filter((r) => {
7300
+ const key = r.npmPackage || r.githubRepo || r.name.toLowerCase();
7301
+ if (seen.has(key))
7302
+ return false;
7303
+ seen.add(key);
7304
+ return true;
7305
+ });
7306
+ }
7307
+ async function findServers(query, opts = {}) {
7308
+ let sources = listSources().filter((s) => s.enabled);
7309
+ if (opts.sources && opts.sources.length > 0) {
7310
+ sources = sources.filter((s) => opts.sources.includes(s.id));
7311
+ }
7312
+ const limit = opts.limit ?? 20;
7313
+ const all = await Promise.all(sources.map((s) => searchSource(s, query, opts.noCache)));
7314
+ const flat = all.flat();
7315
+ const deduped = deduplicate(flat);
7316
+ const typeOrder = {
7317
+ "mcp-registry": 0,
7318
+ "awesome-list": 1,
7319
+ "npm-search": 2,
7320
+ "github-topic": 3
7321
+ };
7322
+ const sourceTypeMap = new Map(sources.map((s) => [s.id, s.type]));
7323
+ deduped.sort((a, b) => {
7324
+ const aType = a.sourceId ? sourceTypeMap.get(a.sourceId) ?? "" : a.source;
7325
+ const bType = b.sourceId ? sourceTypeMap.get(b.sourceId) ?? "" : b.source;
7326
+ const ao = typeOrder[aType] ?? 99;
7327
+ const bo = typeOrder[bType] ?? 99;
7328
+ if (ao !== bo)
7329
+ return ao - bo;
7330
+ return (b.stars ?? 0) - (a.stars ?? 0);
7331
+ });
7332
+ return deduped.slice(0, limit * Math.max(sources.length, 1));
7333
+ }
7334
+ var CACHE_DIR, DEFAULT_TTL_MS;
7335
+ var init_sources = __esm(() => {
7336
+ init_db();
7337
+ init_config();
7338
+ CACHE_DIR = join2(MCPS_DIR, "cache");
7339
+ DEFAULT_TTL_MS = 10 * 60 * 1000;
7340
+ });
7016
7341
 
7017
7342
  // src/lib/registry.ts
7343
+ init_db();
7018
7344
  function parseRow(row) {
7019
7345
  return {
7020
7346
  id: row.id,
7021
7347
  name: row.name,
7022
7348
  description: row.description || null,
7023
7349
  command: row.command,
7024
- args: JSON.parse(row.args),
7025
- env: JSON.parse(row.env),
7350
+ args: safeJsonParse(row.args, []),
7351
+ env: safeJsonParse(row.env, {}),
7026
7352
  transport: row.transport,
7027
7353
  url: row.url || null,
7028
7354
  source: row.source,
7029
7355
  enabled: row.enabled === 1,
7030
7356
  created_at: row.created_at,
7031
- updated_at: row.updated_at
7357
+ updated_at: row.updated_at,
7358
+ last_connected_at: row.last_connected_at ?? null,
7359
+ last_error: row.last_error ?? null
7032
7360
  };
7033
7361
  }
7362
+ function safeJsonParse(value, fallback) {
7363
+ if (typeof value !== "string")
7364
+ return fallback;
7365
+ try {
7366
+ return JSON.parse(value);
7367
+ } catch {
7368
+ return fallback;
7369
+ }
7370
+ }
7034
7371
  function generateId(name) {
7035
7372
  return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
7036
7373
  }
7374
+ function normalizeCandidate(value) {
7375
+ const trimmed = value?.trim();
7376
+ return trimmed ? trimmed : undefined;
7377
+ }
7378
+ function pickNameFromArgs(args) {
7379
+ if (!args || args.length === 0)
7380
+ return;
7381
+ const ddIndex = args.indexOf("--");
7382
+ if (ddIndex >= 0 && ddIndex < args.length - 1) {
7383
+ const after = normalizeCandidate(args[ddIndex + 1]);
7384
+ if (after)
7385
+ return after;
7386
+ }
7387
+ for (const arg of args) {
7388
+ const candidate = normalizeCandidate(arg);
7389
+ if (!candidate)
7390
+ continue;
7391
+ if (candidate.startsWith("-"))
7392
+ continue;
7393
+ return candidate;
7394
+ }
7395
+ return;
7396
+ }
7397
+ function pickId(candidates) {
7398
+ for (const candidate of candidates) {
7399
+ if (!candidate)
7400
+ continue;
7401
+ const id = generateId(candidate);
7402
+ if (id)
7403
+ return id;
7404
+ }
7405
+ return null;
7406
+ }
7037
7407
  function addServer(opts) {
7038
7408
  const db2 = getDb();
7039
- const name = opts.name || opts.args?.[0] || opts.command;
7040
- const id = generateId(name);
7409
+ const command = normalizeCandidate(opts.command);
7410
+ if (!command) {
7411
+ throw new Error("Command is required");
7412
+ }
7413
+ const argName = pickNameFromArgs(opts.args);
7414
+ const name = normalizeCandidate(opts.name) || argName || command;
7415
+ const id = pickId([normalizeCandidate(opts.name), argName, command]) || null;
7416
+ if (!id) {
7417
+ throw new Error("Unable to generate a valid server ID");
7418
+ }
7041
7419
  const row = db2.prepare(`INSERT INTO servers (id, name, description, command, args, env, transport, url, source)
7042
7420
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
7043
- RETURNING *`).get(id, name, opts.description || null, opts.command, JSON.stringify(opts.args || []), JSON.stringify(opts.env || {}), opts.transport || "stdio", opts.url || null, opts.source || "local");
7421
+ RETURNING *`).get(id, name, opts.description || null, command, JSON.stringify(opts.args || []), JSON.stringify(opts.env || {}), opts.transport || "stdio", opts.url || null, opts.source || "local");
7044
7422
  return parseRow(row);
7045
7423
  }
7046
7424
  function removeServer(id) {
@@ -7096,6 +7474,9 @@ function updateServer(id, updates) {
7096
7474
  sets.push("updated_at = datetime('now')");
7097
7475
  values.push(id);
7098
7476
  const row = db2.prepare(`UPDATE servers SET ${sets.join(", ")} WHERE id = ? RETURNING *`).get(...values);
7477
+ if (!row) {
7478
+ throw new Error(`Server "${id}" not found`);
7479
+ }
7099
7480
  return parseRow(row);
7100
7481
  }
7101
7482
  function enableServer(id) {
@@ -7104,74 +7485,79 @@ function enableServer(id) {
7104
7485
  function disableServer(id) {
7105
7486
  return updateServer(id, { enabled: false });
7106
7487
  }
7488
+ function setServerEnv(id, key, value) {
7489
+ const db2 = getDb();
7490
+ const server = getServer(id);
7491
+ if (!server)
7492
+ throw new Error(`Server "${id}" not found`);
7493
+ const env = { ...server.env, [key]: value };
7494
+ db2.prepare("UPDATE servers SET env = ?, updated_at = datetime('now') WHERE id = ?").run(JSON.stringify(env), id);
7495
+ }
7496
+ function unsetServerEnv(id, key) {
7497
+ const db2 = getDb();
7498
+ const server = getServer(id);
7499
+ if (!server)
7500
+ throw new Error(`Server "${id}" not found`);
7501
+ const env = { ...server.env };
7502
+ delete env[key];
7503
+ db2.prepare("UPDATE servers SET env = ?, updated_at = datetime('now') WHERE id = ?").run(JSON.stringify(env), id);
7504
+ }
7107
7505
  function cacheTools(serverId, tools) {
7108
7506
  const db2 = getDb();
7109
- db2.prepare("DELETE FROM tool_cache WHERE server_id = ?").run(serverId);
7110
7507
  const insert = db2.prepare("INSERT INTO tool_cache (server_id, name, description, input_schema) VALUES (?, ?, ?, ?)");
7508
+ const uniqueTools = [];
7509
+ const seen = new Set;
7111
7510
  for (const tool of tools) {
7112
- insert.run(serverId, tool.name, tool.description, JSON.stringify(tool.input_schema));
7113
- }
7114
- }
7115
- // src/lib/remote.ts
7116
- function parseRegistryEntry(entry) {
7117
- const s = entry.server;
7118
- return {
7119
- id: s.name,
7120
- name: s.name,
7121
- description: s.description || "",
7122
- repository: s.repository,
7123
- packages: s.packages || []
7124
- };
7125
- }
7126
- async function searchRegistry(query) {
7127
- const res = await fetch(REGISTRY_API_URL);
7128
- if (!res.ok) {
7129
- throw new Error(`Registry API error: ${res.status} ${res.statusText}`);
7511
+ const name = tool.name?.trim();
7512
+ if (!name || seen.has(name))
7513
+ continue;
7514
+ seen.add(name);
7515
+ uniqueTools.push({
7516
+ name,
7517
+ description: tool.description || "",
7518
+ input_schema: tool.input_schema || {}
7519
+ });
7130
7520
  }
7131
- const data = await res.json();
7132
- const entries = data.servers || [];
7133
- const q = query.toLowerCase();
7134
- return entries.map(parseRegistryEntry).filter((s) => s.name.toLowerCase().includes(q) || s.description?.toLowerCase().includes(q) || s.id.toLowerCase().includes(q));
7521
+ const run = db2.transaction((rows) => {
7522
+ db2.prepare("DELETE FROM tool_cache WHERE server_id = ?").run(serverId);
7523
+ for (const tool of rows) {
7524
+ insert.run(serverId, tool.name, tool.description, JSON.stringify(tool.input_schema));
7525
+ }
7526
+ });
7527
+ run(uniqueTools);
7135
7528
  }
7136
- async function getRegistryServer(id) {
7137
- const res = await fetch(REGISTRY_API_URL);
7138
- if (!res.ok) {
7139
- throw new Error(`Registry API error: ${res.status} ${res.statusText}`);
7140
- }
7141
- const data = await res.json();
7142
- const entries = data.servers || [];
7143
- const all = entries.map(parseRegistryEntry);
7144
- return all.find((s) => s.id === id) || null;
7529
+ function getToolCounts() {
7530
+ const db2 = getDb();
7531
+ const rows = db2.prepare("SELECT server_id, COUNT(*) as count FROM tool_cache GROUP BY server_id").all();
7532
+ return new Map(rows.map((row) => [row.server_id, Number(row.count)]));
7145
7533
  }
7146
- async function installFromRegistry(id) {
7147
- const server = await getRegistryServer(id);
7148
- if (!server) {
7149
- throw new Error(`Server "${id}" not found in registry`);
7150
- }
7151
- const pkg = server.packages?.[0];
7152
- let command = "npx";
7153
- let args = [];
7154
- let transport = "stdio";
7155
- if (pkg) {
7156
- if (pkg.registryType === "npm") {
7157
- command = "npx";
7158
- args = ["-y", pkg.identifier];
7159
- } else {
7160
- command = pkg.identifier;
7161
- }
7162
- if (pkg.transport?.type) {
7163
- transport = pkg.transport.type;
7164
- }
7165
- }
7534
+ function cloneServer(id, newName) {
7535
+ const server = getServer(id);
7536
+ if (!server)
7537
+ throw new Error(`Server "${id}" not found`);
7166
7538
  return addServer({
7167
- name: server.name,
7168
- description: server.description,
7169
- command,
7170
- args,
7171
- transport,
7172
- source: "registry"
7539
+ name: newName,
7540
+ description: server.description ?? undefined,
7541
+ command: server.command,
7542
+ args: server.args,
7543
+ env: server.env,
7544
+ transport: server.transport,
7545
+ url: server.url ?? undefined,
7546
+ source: server.source
7173
7547
  });
7174
7548
  }
7549
+ function getCachedTools(serverId) {
7550
+ const db2 = getDb();
7551
+ const rows = db2.prepare("SELECT name, description, input_schema FROM tool_cache WHERE server_id = ? ORDER BY name").all(serverId);
7552
+ return rows.map((r) => ({
7553
+ name: r.name,
7554
+ description: r.description,
7555
+ input_schema: safeJsonParse(r.input_schema, {})
7556
+ }));
7557
+ }
7558
+ // src/lib/doctor.ts
7559
+ import { execFileSync } from "child_process";
7560
+
7175
7561
  // node_modules/zod/v4/core/core.js
7176
7562
  var NEVER = Object.freeze({
7177
7563
  status: "aborted"
@@ -15284,54 +15670,126 @@ class StreamableHTTPClientTransport {
15284
15670
  }
15285
15671
 
15286
15672
  // src/lib/proxy.ts
15673
+ init_config();
15674
+ init_db();
15287
15675
  var connections = new Map;
15676
+ var inflightConnections = new Map;
15677
+ function buildEnv(extra) {
15678
+ const merged = {};
15679
+ for (const [key, value] of Object.entries(process.env)) {
15680
+ if (typeof value === "string")
15681
+ merged[key] = value;
15682
+ }
15683
+ for (const [key, value] of Object.entries(extra || {})) {
15684
+ if (value === undefined || value === null)
15685
+ continue;
15686
+ merged[key] = String(value);
15687
+ }
15688
+ return merged;
15689
+ }
15690
+ function requireUrl(entry) {
15691
+ if (!entry.url) {
15692
+ throw new Error(`Server "${entry.id}" is missing a URL for ${entry.transport} transport`);
15693
+ }
15694
+ try {
15695
+ return new URL(entry.url);
15696
+ } catch {
15697
+ throw new Error(`Server "${entry.id}" has an invalid URL: ${entry.url}`);
15698
+ }
15699
+ }
15288
15700
  async function connectToServer(entry) {
15289
15701
  if (connections.has(entry.id)) {
15290
15702
  return connections.get(entry.id);
15291
15703
  }
15704
+ const inflight = inflightConnections.get(entry.id);
15705
+ if (inflight) {
15706
+ return inflight;
15707
+ }
15292
15708
  const client = new Client({ name: "mcps-proxy", version: "0.0.1" });
15293
15709
  let transport;
15294
- if (entry.transport === "stdio") {
15295
- transport = new StdioClientTransport({
15296
- command: entry.command,
15297
- args: entry.args,
15298
- env: { ...process.env, ...entry.env }
15299
- });
15300
- } else if (entry.transport === "sse") {
15301
- transport = new SSEClientTransport(new URL(entry.url));
15302
- } else {
15303
- transport = new StreamableHTTPClientTransport(new URL(entry.url));
15304
- }
15305
- await client.connect(transport);
15306
- const result = await client.listTools();
15307
- const tools = (result.tools || []).map((t) => ({
15308
- server_id: entry.id,
15309
- name: t.name,
15310
- description: t.description || "",
15311
- input_schema: t.inputSchema || {}
15312
- }));
15313
- cacheTools(entry.id, tools.map((t) => ({ name: t.name, description: t.description, input_schema: t.input_schema })));
15314
- const connected = {
15315
- entry,
15316
- tools,
15317
- disconnect: async () => {
15318
- await client.close();
15319
- connections.delete(entry.id);
15710
+ const connectPromise = (async () => {
15711
+ try {
15712
+ if (entry.transport === "stdio") {
15713
+ if (!entry.command?.trim()) {
15714
+ throw new Error(`Server "${entry.id}" is missing a command`);
15715
+ }
15716
+ transport = new StdioClientTransport({
15717
+ command: entry.command,
15718
+ args: entry.args,
15719
+ env: buildEnv(entry.env)
15720
+ });
15721
+ } else if (entry.transport === "sse") {
15722
+ transport = new SSEClientTransport(requireUrl(entry));
15723
+ } else {
15724
+ transport = new StreamableHTTPClientTransport(requireUrl(entry));
15725
+ }
15726
+ await client.connect(transport);
15727
+ const result = await client.listTools();
15728
+ const tools = (result.tools || []).map((t) => ({
15729
+ server_id: entry.id,
15730
+ name: t.name,
15731
+ description: t.description || "",
15732
+ input_schema: t.inputSchema || {}
15733
+ }));
15734
+ cacheTools(entry.id, tools.map((t) => ({ name: t.name, description: t.description, input_schema: t.input_schema })));
15735
+ const connected = {
15736
+ entry,
15737
+ tools,
15738
+ disconnect: async () => {
15739
+ try {
15740
+ await client.close();
15741
+ } finally {
15742
+ connections.delete(entry.id);
15743
+ }
15744
+ }
15745
+ };
15746
+ connected._client = client;
15747
+ connections.set(entry.id, connected);
15748
+ try {
15749
+ getDb().prepare("UPDATE servers SET last_connected_at = datetime('now'), last_error = NULL WHERE id = ?").run(entry.id);
15750
+ } catch {}
15751
+ return connected;
15752
+ } catch (err) {
15753
+ try {
15754
+ getDb().prepare("UPDATE servers SET last_error = ? WHERE id = ?").run(err.message, entry.id);
15755
+ } catch {}
15756
+ try {
15757
+ await client.close();
15758
+ } catch {}
15759
+ throw err;
15320
15760
  }
15321
- };
15322
- connected._client = client;
15323
- connections.set(entry.id, connected);
15324
- return connected;
15761
+ })();
15762
+ inflightConnections.set(entry.id, connectPromise);
15763
+ try {
15764
+ return await connectPromise;
15765
+ } finally {
15766
+ inflightConnections.delete(entry.id);
15767
+ }
15325
15768
  }
15326
15769
  async function disconnectServer(id) {
15327
15770
  const conn = connections.get(id);
15328
15771
  if (conn) {
15329
15772
  await conn.disconnect();
15773
+ return;
15774
+ }
15775
+ const inflight = inflightConnections.get(id);
15776
+ if (inflight) {
15777
+ try {
15778
+ const pending = await inflight;
15779
+ await pending.disconnect();
15780
+ } catch {}
15330
15781
  }
15331
15782
  }
15332
15783
  async function disconnectAll() {
15333
15784
  const ids = Array.from(connections.keys());
15334
- await Promise.all(ids.map((id) => disconnectServer(id)));
15785
+ await Promise.allSettled(ids.map((id) => disconnectServer(id)));
15786
+ const inflight = Array.from(inflightConnections.values());
15787
+ await Promise.allSettled(inflight.map(async (promise2) => {
15788
+ try {
15789
+ const conn = await promise2;
15790
+ await conn.disconnect();
15791
+ } catch {}
15792
+ }));
15335
15793
  }
15336
15794
  function listAllTools() {
15337
15795
  const tools = [];
@@ -15346,12 +15804,19 @@ function listAllTools() {
15346
15804
  return tools;
15347
15805
  }
15348
15806
  async function callTool(prefixedName, args) {
15349
- const sepIdx = prefixedName.indexOf(TOOL_PREFIX_SEPARATOR);
15807
+ const normalized = prefixedName.trim();
15808
+ if (!normalized) {
15809
+ throw new Error("Tool name is required");
15810
+ }
15811
+ const sepIdx = normalized.indexOf(TOOL_PREFIX_SEPARATOR);
15350
15812
  if (sepIdx === -1) {
15351
15813
  throw new Error(`Invalid tool name "${prefixedName}" \u2014 expected format: server_id${TOOL_PREFIX_SEPARATOR}tool_name`);
15352
15814
  }
15353
- const serverId = prefixedName.slice(0, sepIdx);
15354
- const toolName = prefixedName.slice(sepIdx + TOOL_PREFIX_SEPARATOR.length);
15815
+ const serverId = normalized.slice(0, sepIdx).trim();
15816
+ const toolName = normalized.slice(sepIdx + TOOL_PREFIX_SEPARATOR.length).trim();
15817
+ if (!serverId || !toolName) {
15818
+ throw new Error(`Invalid tool name "${prefixedName}" \u2014 expected format: server_id${TOOL_PREFIX_SEPARATOR}tool_name`);
15819
+ }
15355
15820
  const conn = connections.get(serverId);
15356
15821
  if (!conn) {
15357
15822
  throw new Error(`Server "${serverId}" is not connected`);
@@ -15381,23 +15846,256 @@ async function refreshTools(id) {
15381
15846
  cacheTools(id, tools.map((t) => ({ name: t.name, description: t.description, input_schema: t.input_schema })));
15382
15847
  return tools;
15383
15848
  }
15849
+
15850
+ // src/lib/doctor.ts
15851
+ async function diagnoseServer(server) {
15852
+ const checks3 = [];
15853
+ if (server.transport === "stdio") {
15854
+ try {
15855
+ const path = execFileSync("which", [server.command], { stdio: "pipe" }).toString().trim();
15856
+ let version2 = "";
15857
+ try {
15858
+ version2 = execFileSync(server.command, ["--version"], { stdio: "pipe" }).toString().trim().split(`
15859
+ `)[0];
15860
+ } catch {}
15861
+ checks3.push({ name: "command on PATH", pass: true, message: `${path}${version2 ? ` (${version2})` : ""}` });
15862
+ } catch {
15863
+ checks3.push({
15864
+ name: "command on PATH",
15865
+ pass: false,
15866
+ message: `${server.command} not found on PATH`,
15867
+ fixable: true,
15868
+ fixHint: server.args[0] || server.command
15869
+ });
15870
+ }
15871
+ }
15872
+ const missingEnv = Object.entries(server.env).filter(([, v]) => !v);
15873
+ if (Object.keys(server.env).length === 0) {
15874
+ checks3.push({ name: "env vars", pass: true, message: "no env vars required" });
15875
+ } else if (missingEnv.length > 0) {
15876
+ checks3.push({ name: "env vars", pass: false, message: `missing values for: ${missingEnv.map(([k]) => k).join(", ")}` });
15877
+ } else {
15878
+ checks3.push({ name: "env vars", pass: true, message: `${Object.keys(server.env).length} env var(s) set` });
15879
+ }
15880
+ if (server.transport !== "stdio" && server.url) {
15881
+ try {
15882
+ const res = await fetch(server.url, { signal: AbortSignal.timeout(5000) });
15883
+ checks3.push({ name: "URL reachable", pass: res.ok || res.status < 500, message: `HTTP ${res.status}` });
15884
+ } catch (err) {
15885
+ checks3.push({ name: "URL reachable", pass: false, message: `unreachable: ${err.message}` });
15886
+ }
15887
+ }
15888
+ if (server.enabled) {
15889
+ try {
15890
+ await Promise.race([
15891
+ connectToServer(server),
15892
+ new Promise((_, reject) => setTimeout(() => reject(new Error("timeout after 10s")), 1e4))
15893
+ ]);
15894
+ await disconnectServer(server.id);
15895
+ checks3.push({ name: "connect & list tools", pass: true, message: "connected successfully" });
15896
+ } catch (err) {
15897
+ checks3.push({ name: "connect & list tools", pass: false, message: err.message });
15898
+ }
15899
+ } else {
15900
+ checks3.push({ name: "connect & list tools", pass: true, message: "skipped (server disabled)" });
15901
+ }
15902
+ return {
15903
+ server,
15904
+ checks: checks3,
15905
+ healthy: checks3.every((c) => c.pass)
15906
+ };
15907
+ }
15908
+ // src/lib/remote.ts
15909
+ init_config();
15910
+ function parseRegistryEntry(entry) {
15911
+ const s = entry.server;
15912
+ return {
15913
+ id: s.name,
15914
+ name: s.name,
15915
+ description: s.description || "",
15916
+ repository: s.repository,
15917
+ packages: s.packages || []
15918
+ };
15919
+ }
15920
+ async function searchRegistry(query) {
15921
+ const res = await fetch(REGISTRY_API_URL);
15922
+ if (!res.ok) {
15923
+ throw new Error(`Registry API error: ${res.status} ${res.statusText}`);
15924
+ }
15925
+ const data = await res.json();
15926
+ const entries = data.servers || [];
15927
+ const q = query.toLowerCase();
15928
+ return entries.map(parseRegistryEntry).filter((s) => s.name.toLowerCase().includes(q) || s.description?.toLowerCase().includes(q) || s.id.toLowerCase().includes(q));
15929
+ }
15930
+ async function getRegistryServer(id) {
15931
+ const res = await fetch(REGISTRY_API_URL);
15932
+ if (!res.ok) {
15933
+ throw new Error(`Registry API error: ${res.status} ${res.statusText}`);
15934
+ }
15935
+ const data = await res.json();
15936
+ const entries = data.servers || [];
15937
+ const all = entries.map(parseRegistryEntry);
15938
+ return all.find((s) => s.id === id) || null;
15939
+ }
15940
+ async function installFromRegistry(id) {
15941
+ const server = await getRegistryServer(id);
15942
+ if (!server) {
15943
+ throw new Error(`Server "${id}" not found in registry`);
15944
+ }
15945
+ const pkg = server.packages?.[0];
15946
+ let command = "npx";
15947
+ let args = [];
15948
+ let transport = "stdio";
15949
+ if (pkg) {
15950
+ if (pkg.registryType === "npm") {
15951
+ command = "npx";
15952
+ args = ["-y", pkg.identifier];
15953
+ } else {
15954
+ command = pkg.identifier;
15955
+ }
15956
+ if (pkg.transport?.type) {
15957
+ transport = pkg.transport.type;
15958
+ }
15959
+ }
15960
+ return addServer({
15961
+ name: server.name,
15962
+ description: server.description,
15963
+ command,
15964
+ args,
15965
+ transport,
15966
+ source: "registry"
15967
+ });
15968
+ }
15969
+ // src/lib/finder.ts
15970
+ init_sources();
15971
+ async function listAwesomeServers() {
15972
+ const { listSources: listSources2, searchSource: searchSource2 } = await Promise.resolve().then(() => (init_sources(), exports_sources));
15973
+ const source = listSources2().find((s) => s.type === "awesome-list" && s.enabled);
15974
+ if (!source)
15975
+ return [];
15976
+ return searchSource2(source, "");
15977
+ }
15978
+
15979
+ // src/index.ts
15980
+ init_sources();
15981
+
15982
+ // src/lib/install.ts
15983
+ import { execFileSync as execFileSync2 } from "child_process";
15984
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3 } from "fs";
15985
+ import { join as join3 } from "path";
15986
+ import { homedir as homedir2 } from "os";
15987
+ function installToClaude(entry) {
15988
+ try {
15989
+ const args = [
15990
+ "mcp",
15991
+ "add",
15992
+ "--transport",
15993
+ entry.transport,
15994
+ "--scope",
15995
+ "user"
15996
+ ];
15997
+ for (const [k, v] of Object.entries(entry.env)) {
15998
+ args.push("--env", `${k}=${v}`);
15999
+ }
16000
+ args.push(entry.id, "--", entry.command, ...entry.args);
16001
+ execFileSync2("claude", args, { stdio: "pipe" });
16002
+ return { agent: "claude", success: true };
16003
+ } catch (err) {
16004
+ return { agent: "claude", success: false, error: err.message };
16005
+ }
16006
+ }
16007
+ function installToCodex(entry) {
16008
+ try {
16009
+ const configDir = join3(homedir2(), ".codex");
16010
+ const configPath = join3(configDir, "config.toml");
16011
+ if (!existsSync2(configDir)) {
16012
+ mkdirSync3(configDir, { recursive: true });
16013
+ }
16014
+ const block = `
16015
+ [mcp_servers.${entry.id}]
16016
+ ` + `command = ${JSON.stringify(entry.command)}
16017
+ ` + `args = [${entry.args.map((a) => JSON.stringify(a)).join(", ")}]
16018
+ `;
16019
+ const existing = existsSync2(configPath) ? readFileSync2(configPath, "utf-8") : "";
16020
+ if (existing.includes(`[mcp_servers.${entry.id}]`)) {
16021
+ return { agent: "codex", success: true };
16022
+ }
16023
+ writeFileSync2(configPath, existing + block, "utf-8");
16024
+ return { agent: "codex", success: true };
16025
+ } catch (err) {
16026
+ return { agent: "codex", success: false, error: err.message };
16027
+ }
16028
+ }
16029
+ function installToGemini(entry) {
16030
+ try {
16031
+ const configDir = join3(homedir2(), ".gemini");
16032
+ const configPath = join3(configDir, "settings.json");
16033
+ if (!existsSync2(configDir)) {
16034
+ mkdirSync3(configDir, { recursive: true });
16035
+ }
16036
+ let settings = {};
16037
+ if (existsSync2(configPath)) {
16038
+ settings = JSON.parse(readFileSync2(configPath, "utf-8"));
16039
+ }
16040
+ if (!settings.mcpServers)
16041
+ settings.mcpServers = {};
16042
+ settings.mcpServers[entry.id] = {
16043
+ command: entry.command,
16044
+ args: entry.args,
16045
+ ...Object.keys(entry.env).length > 0 ? { env: entry.env } : {}
16046
+ };
16047
+ writeFileSync2(configPath, JSON.stringify(settings, null, 2), "utf-8");
16048
+ return { agent: "gemini", success: true };
16049
+ } catch (err) {
16050
+ return { agent: "gemini", success: false, error: err.message };
16051
+ }
16052
+ }
16053
+ function installToAgents(entry, targets = ["claude", "codex", "gemini"]) {
16054
+ return targets.map((target) => {
16055
+ if (target === "claude")
16056
+ return installToClaude(entry);
16057
+ if (target === "codex")
16058
+ return installToCodex(entry);
16059
+ if (target === "gemini")
16060
+ return installToGemini(entry);
16061
+ return { agent: target, success: false, error: "Unknown target" };
16062
+ });
16063
+ }
16064
+
16065
+ // src/index.ts
16066
+ init_db();
15384
16067
  export {
15385
16068
  updateServer,
16069
+ unsetServerEnv,
16070
+ setServerEnv,
15386
16071
  searchRegistry,
16072
+ removeSource,
15387
16073
  removeServer,
15388
16074
  refreshTools,
16075
+ listSources,
15389
16076
  listServers,
16077
+ listAwesomeServers,
15390
16078
  listAllTools,
16079
+ installToAgents,
15391
16080
  installFromRegistry,
16081
+ getToolCounts,
16082
+ getSource,
15392
16083
  getServer,
15393
16084
  getRegistryServer,
15394
16085
  getDb,
16086
+ getCachedTools,
16087
+ findServers,
16088
+ enableSource,
15395
16089
  enableServer,
15396
16090
  disconnectServer,
15397
16091
  disconnectAll,
16092
+ disableSource,
15398
16093
  disableServer,
16094
+ diagnoseServer,
15399
16095
  connectToServer,
15400
16096
  closeDb,
16097
+ cloneServer,
15401
16098
  callTool,
16099
+ addSource,
15402
16100
  addServer
15403
16101
  };