@hasna/mcps 0.0.2 → 0.0.3

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/bin/mcp.js CHANGED
@@ -25,6 +25,7 @@ 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
 
30
31
  // node_modules/ajv/dist/compile/codegen/code.js
@@ -6286,7 +6287,7 @@ var require_formats = __commonJS((exports) => {
6286
6287
  }
6287
6288
  var TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i;
6288
6289
  function getTime(strictTimeZone) {
6289
- return function time3(str) {
6290
+ return function time(str) {
6290
6291
  const matches = TIME.exec(str);
6291
6292
  if (!matches)
6292
6293
  return false;
@@ -6499,6 +6500,383 @@ var require_dist = __commonJS((exports, module) => {
6499
6500
  exports.default = formatsPlugin;
6500
6501
  });
6501
6502
 
6503
+ // src/lib/config.ts
6504
+ import { join } from "path";
6505
+ import { homedir } from "os";
6506
+ var MCPS_DIR, DB_PATH, REGISTRY_API_URL = "https://registry.modelcontextprotocol.io/v0/servers", TOOL_PREFIX_SEPARATOR = "__";
6507
+ var init_config = __esm(() => {
6508
+ MCPS_DIR = join(homedir(), ".mcps");
6509
+ DB_PATH = join(MCPS_DIR, "registry.db");
6510
+ });
6511
+
6512
+ // src/lib/db.ts
6513
+ import { Database } from "bun:sqlite";
6514
+ import { mkdirSync } from "fs";
6515
+ function getDb() {
6516
+ if (db)
6517
+ return db;
6518
+ mkdirSync(MCPS_DIR, { recursive: true });
6519
+ db = new Database(DB_PATH, { create: true });
6520
+ db.exec("PRAGMA journal_mode = WAL");
6521
+ db.exec("PRAGMA busy_timeout = 5000");
6522
+ db.exec("PRAGMA foreign_keys = ON");
6523
+ db.exec(`
6524
+ CREATE TABLE IF NOT EXISTS servers (
6525
+ id TEXT PRIMARY KEY,
6526
+ name TEXT NOT NULL,
6527
+ description TEXT,
6528
+ command TEXT NOT NULL,
6529
+ args TEXT NOT NULL DEFAULT '[]',
6530
+ env TEXT NOT NULL DEFAULT '{}',
6531
+ transport TEXT NOT NULL DEFAULT 'stdio',
6532
+ url TEXT,
6533
+ source TEXT NOT NULL DEFAULT 'local',
6534
+ enabled INTEGER NOT NULL DEFAULT 1,
6535
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
6536
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
6537
+ )
6538
+ `);
6539
+ db.exec(`
6540
+ CREATE TABLE IF NOT EXISTS tool_cache (
6541
+ server_id TEXT NOT NULL,
6542
+ name TEXT NOT NULL,
6543
+ description TEXT NOT NULL DEFAULT '',
6544
+ input_schema TEXT NOT NULL DEFAULT '{}',
6545
+ cached_at TEXT NOT NULL DEFAULT (datetime('now')),
6546
+ PRIMARY KEY (server_id, name),
6547
+ FOREIGN KEY (server_id) REFERENCES servers(id) ON DELETE CASCADE
6548
+ )
6549
+ `);
6550
+ db.exec("CREATE INDEX IF NOT EXISTS idx_tool_cache_server ON tool_cache(server_id)");
6551
+ try {
6552
+ db.exec("ALTER TABLE servers ADD COLUMN last_connected_at TEXT");
6553
+ } catch {}
6554
+ try {
6555
+ db.exec("ALTER TABLE servers ADD COLUMN last_error TEXT");
6556
+ } catch {}
6557
+ db.exec(`
6558
+ CREATE TABLE IF NOT EXISTS sources (
6559
+ id TEXT PRIMARY KEY,
6560
+ name TEXT NOT NULL,
6561
+ type TEXT NOT NULL,
6562
+ url TEXT NOT NULL,
6563
+ description TEXT,
6564
+ enabled INTEGER NOT NULL DEFAULT 1,
6565
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
6566
+ )
6567
+ `);
6568
+ const count = db.query("SELECT COUNT(*) as c FROM sources").get().c;
6569
+ if (count === 0) {
6570
+ db.exec(`
6571
+ INSERT OR IGNORE INTO sources (id, name, type, url, description) VALUES
6572
+ ('official-registry', 'Official MCP Registry', 'mcp-registry', 'https://registry.modelcontextprotocol.io/v0/servers', 'The official Model Context Protocol server registry'),
6573
+ ('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'),
6574
+ ('npm-mcp', 'npm MCP Packages', 'npm-search', 'https://registry.npmjs.org/-/v1/search', 'Search npm packages for MCP servers'),
6575
+ ('github-mcp-topic', 'GitHub MCP Topic', 'github-topic', 'https://api.github.com/search/repositories', 'GitHub repositories tagged with mcp-server topic')
6576
+ `);
6577
+ }
6578
+ return db;
6579
+ }
6580
+ var db = null;
6581
+ var init_db = __esm(() => {
6582
+ init_config();
6583
+ });
6584
+
6585
+ // src/lib/sources.ts
6586
+ var exports_sources = {};
6587
+ __export(exports_sources, {
6588
+ searchSource: () => searchSource,
6589
+ removeSource: () => removeSource,
6590
+ listSources: () => listSources,
6591
+ getSource: () => getSource,
6592
+ findServers: () => findServers,
6593
+ enableSource: () => enableSource,
6594
+ disableSource: () => disableSource,
6595
+ clearCache: () => clearCache,
6596
+ addSource: () => addSource
6597
+ });
6598
+ import { mkdirSync as mkdirSync2, existsSync, readFileSync, writeFileSync, readdirSync, unlinkSync } from "fs";
6599
+ import { join as join2 } from "path";
6600
+ function getCacheFile(sourceId) {
6601
+ return join2(CACHE_DIR, `${sourceId}.json`);
6602
+ }
6603
+ function readCache(sourceId) {
6604
+ try {
6605
+ const file = getCacheFile(sourceId);
6606
+ if (!existsSync(file))
6607
+ return null;
6608
+ const data = JSON.parse(readFileSync(file, "utf-8"));
6609
+ return data;
6610
+ } catch {
6611
+ return null;
6612
+ }
6613
+ }
6614
+ function writeCache(sourceId, results) {
6615
+ try {
6616
+ mkdirSync2(CACHE_DIR, { recursive: true });
6617
+ writeFileSync(getCacheFile(sourceId), JSON.stringify({ results, cachedAt: Date.now() }), "utf-8");
6618
+ } catch {}
6619
+ }
6620
+ function clearCache(sourceId) {
6621
+ try {
6622
+ if (!existsSync(CACHE_DIR))
6623
+ return;
6624
+ const files = readdirSync(CACHE_DIR);
6625
+ for (const file of files) {
6626
+ if (!file.endsWith(".json"))
6627
+ continue;
6628
+ if (!sourceId || file.startsWith(`${sourceId}.`)) {
6629
+ try {
6630
+ unlinkSync(join2(CACHE_DIR, file));
6631
+ } catch {}
6632
+ }
6633
+ }
6634
+ } catch {}
6635
+ }
6636
+ function generateId2(name) {
6637
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
6638
+ }
6639
+ function listSources() {
6640
+ const db2 = getDb();
6641
+ const rows = db2.query("SELECT * FROM sources ORDER BY created_at ASC").all();
6642
+ return rows.map((r) => ({ ...r, enabled: r.enabled === 1 }));
6643
+ }
6644
+ function getSource(id) {
6645
+ const db2 = getDb();
6646
+ const row = db2.query("SELECT * FROM sources WHERE id = ?").get(id);
6647
+ if (!row)
6648
+ return null;
6649
+ return { ...row, enabled: row.enabled === 1 };
6650
+ }
6651
+ function addSource(opts) {
6652
+ const db2 = getDb();
6653
+ const id = generateId2(opts.name);
6654
+ db2.run("INSERT INTO sources (id, name, type, url, description) VALUES (?, ?, ?, ?, ?)", [id, opts.name, opts.type, opts.url, opts.description ?? null]);
6655
+ return getSource(id);
6656
+ }
6657
+ function removeSource(id) {
6658
+ const db2 = getDb();
6659
+ db2.run("DELETE FROM sources WHERE id = ?", [id]);
6660
+ }
6661
+ function enableSource(id) {
6662
+ const db2 = getDb();
6663
+ db2.run("UPDATE sources SET enabled = 1 WHERE id = ?", [id]);
6664
+ }
6665
+ function disableSource(id) {
6666
+ const db2 = getDb();
6667
+ db2.run("UPDATE sources SET enabled = 0 WHERE id = ?", [id]);
6668
+ }
6669
+ async function searchMcpRegistry(source, query) {
6670
+ try {
6671
+ const res = await fetch(source.url);
6672
+ if (!res.ok)
6673
+ return [];
6674
+ const data = await res.json();
6675
+ const q = query.toLowerCase();
6676
+ return (data.servers || []).map((e) => e.server).filter((s) => q === "" || s.name.toLowerCase().includes(q) || (s.description || "").toLowerCase().includes(q)).map((s) => ({
6677
+ name: s.name,
6678
+ description: s.description || "",
6679
+ source: "registry",
6680
+ sourceId: source.id,
6681
+ url: s.repository?.url,
6682
+ githubRepo: s.repository?.url,
6683
+ installCmd: s.packages?.[0] ? s.packages[0].registryType === "npm" ? `npx -y ${s.packages[0].identifier}` : s.packages[0].identifier : undefined,
6684
+ npmPackage: s.packages?.find((p) => p.registryType === "npm")?.identifier
6685
+ }));
6686
+ } catch {
6687
+ return [];
6688
+ }
6689
+ }
6690
+ async function searchAwesomeList(source, query) {
6691
+ try {
6692
+ const res = await fetch(source.url);
6693
+ if (!res.ok)
6694
+ return [];
6695
+ const text = await res.text();
6696
+ const results = [];
6697
+ const linkPattern = /\[([^\]]+)\]\((https?:\/\/[^)]+)\)(?:\s*[-\u2013]\s*([^\n]+))?/g;
6698
+ const q = query.toLowerCase();
6699
+ let match;
6700
+ while ((match = linkPattern.exec(text)) !== null) {
6701
+ const name = match[1].trim();
6702
+ const url2 = match[2].trim();
6703
+ const description = match[3]?.trim() || "";
6704
+ if ((url2.includes("github.com") || url2.includes("npmjs.com")) && name.length > 2) {
6705
+ if (q === "" || `${name} ${description}`.toLowerCase().includes(q)) {
6706
+ results.push({
6707
+ name,
6708
+ description,
6709
+ source: "awesome",
6710
+ sourceId: source.id,
6711
+ url: url2,
6712
+ githubRepo: url2.includes("github.com") ? url2 : undefined,
6713
+ installCmd: url2.includes("npmjs.com") ? `npx -y ${url2.split("/").pop()}` : undefined
6714
+ });
6715
+ }
6716
+ }
6717
+ }
6718
+ return results;
6719
+ } catch {
6720
+ return [];
6721
+ }
6722
+ }
6723
+ function scoreNpmPackage(pkg) {
6724
+ const name = pkg.name.toLowerCase();
6725
+ const keywords = (pkg.keywords || []).map((k) => k.toLowerCase());
6726
+ let score = 0;
6727
+ if (name.includes("mcp-server"))
6728
+ score += 3;
6729
+ else if (name.startsWith("mcp-") || name.endsWith("-mcp"))
6730
+ score += 2;
6731
+ else if (name.includes("mcp"))
6732
+ score += 1;
6733
+ if (keywords.includes("mcp"))
6734
+ score += 1;
6735
+ if (keywords.includes("mcp-server"))
6736
+ score += 2;
6737
+ if (keywords.some((k) => k.includes("modelcontextprotocol")))
6738
+ score += 2;
6739
+ return score;
6740
+ }
6741
+ async function searchNpm(source, query) {
6742
+ try {
6743
+ const url2 = new URL(source.url);
6744
+ url2.searchParams.set("text", `mcp-server ${query}`);
6745
+ url2.searchParams.set("size", "50");
6746
+ const res = await fetch(url2.toString());
6747
+ if (!res.ok)
6748
+ return [];
6749
+ const data = await res.json();
6750
+ const q = query.toLowerCase();
6751
+ const scored = (data.objects || []).map((o) => ({ pkg: o.package, score: scoreNpmPackage(o.package) })).filter(({ pkg, score }) => {
6752
+ if (score < 1)
6753
+ return false;
6754
+ const text = `${pkg.name} ${pkg.description || ""} ${(pkg.keywords || []).join(" ")}`.toLowerCase();
6755
+ return q === "" || text.includes(q);
6756
+ });
6757
+ scored.sort((a, b) => b.score - a.score);
6758
+ return scored.map(({ pkg }) => ({
6759
+ name: pkg.name,
6760
+ description: pkg.description || "",
6761
+ source: "npm",
6762
+ sourceId: source.id,
6763
+ url: pkg.links?.repository || pkg.links?.npm,
6764
+ npmPackage: pkg.name,
6765
+ installCmd: `npx -y ${pkg.name}`
6766
+ }));
6767
+ } catch {
6768
+ return [];
6769
+ }
6770
+ }
6771
+ async function searchGitHubTopic(source, query) {
6772
+ const token = process.env.GITHUB_TOKEN;
6773
+ try {
6774
+ const url2 = new URL(source.url);
6775
+ const q = query ? `${query} topic:mcp-server` : "topic:mcp-server";
6776
+ url2.searchParams.set("q", q);
6777
+ url2.searchParams.set("sort", "stars");
6778
+ url2.searchParams.set("per_page", "30");
6779
+ const headers = {
6780
+ Accept: "application/vnd.github.v3+json"
6781
+ };
6782
+ if (token)
6783
+ headers["Authorization"] = `Bearer ${token}`;
6784
+ const res = await fetch(url2.toString(), { headers });
6785
+ if (!res.ok)
6786
+ return [];
6787
+ const data = await res.json();
6788
+ return (data.items || []).map((repo) => ({
6789
+ name: repo.full_name,
6790
+ description: repo.description || "",
6791
+ source: "github",
6792
+ sourceId: source.id,
6793
+ url: repo.html_url,
6794
+ githubRepo: repo.html_url,
6795
+ stars: repo.stargazers_count
6796
+ }));
6797
+ } catch {
6798
+ return [];
6799
+ }
6800
+ }
6801
+ async function fetchSource(source) {
6802
+ switch (source.type) {
6803
+ case "mcp-registry":
6804
+ return searchMcpRegistry(source, "");
6805
+ case "awesome-list":
6806
+ return searchAwesomeList(source, "");
6807
+ case "npm-search":
6808
+ return searchNpm(source, "");
6809
+ case "github-topic":
6810
+ return searchGitHubTopic(source, "");
6811
+ default:
6812
+ return [];
6813
+ }
6814
+ }
6815
+ function filterResults(results, query) {
6816
+ if (!query)
6817
+ return results;
6818
+ const q = query.toLowerCase();
6819
+ return results.filter((r) => {
6820
+ const text = `${r.name} ${r.description || ""}`.toLowerCase();
6821
+ return text.includes(q);
6822
+ });
6823
+ }
6824
+ async function searchSource(source, query, noCache = false) {
6825
+ if (!noCache) {
6826
+ const cached2 = readCache(source.id);
6827
+ if (cached2 && Date.now() - cached2.cachedAt < DEFAULT_TTL_MS) {
6828
+ return filterResults(cached2.results, query);
6829
+ }
6830
+ }
6831
+ const results = await fetchSource(source);
6832
+ writeCache(source.id, results);
6833
+ return filterResults(results, query);
6834
+ }
6835
+ function deduplicate(results) {
6836
+ const seen = new Set;
6837
+ return results.filter((r) => {
6838
+ const key = r.npmPackage || r.githubRepo || r.name.toLowerCase();
6839
+ if (seen.has(key))
6840
+ return false;
6841
+ seen.add(key);
6842
+ return true;
6843
+ });
6844
+ }
6845
+ async function findServers(query, opts = {}) {
6846
+ let sources = listSources().filter((s) => s.enabled);
6847
+ if (opts.sources && opts.sources.length > 0) {
6848
+ sources = sources.filter((s) => opts.sources.includes(s.id));
6849
+ }
6850
+ const limit = opts.limit ?? 20;
6851
+ const all = await Promise.all(sources.map((s) => searchSource(s, query, opts.noCache)));
6852
+ const flat = all.flat();
6853
+ const deduped = deduplicate(flat);
6854
+ const typeOrder = {
6855
+ "mcp-registry": 0,
6856
+ "awesome-list": 1,
6857
+ "npm-search": 2,
6858
+ "github-topic": 3
6859
+ };
6860
+ const sourceTypeMap = new Map(sources.map((s) => [s.id, s.type]));
6861
+ deduped.sort((a, b) => {
6862
+ const aType = a.sourceId ? sourceTypeMap.get(a.sourceId) ?? "" : a.source;
6863
+ const bType = b.sourceId ? sourceTypeMap.get(b.sourceId) ?? "" : b.source;
6864
+ const ao = typeOrder[aType] ?? 99;
6865
+ const bo = typeOrder[bType] ?? 99;
6866
+ if (ao !== bo)
6867
+ return ao - bo;
6868
+ return (b.stars ?? 0) - (a.stars ?? 0);
6869
+ });
6870
+ return deduped.slice(0, limit * Math.max(sources.length, 1));
6871
+ }
6872
+ var CACHE_DIR, DEFAULT_TTL_MS;
6873
+ var init_sources = __esm(() => {
6874
+ init_db();
6875
+ init_config();
6876
+ CACHE_DIR = join2(MCPS_DIR, "cache");
6877
+ DEFAULT_TTL_MS = 10 * 60 * 1000;
6878
+ });
6879
+
6502
6880
  // node_modules/isexe/windows.js
6503
6881
  var require_windows = __commonJS((exports, module) => {
6504
6882
  module.exports = isexe;
@@ -20023,86 +20401,91 @@ class StdioServerTransport {
20023
20401
  }
20024
20402
  }
20025
20403
 
20026
- // src/lib/db.ts
20027
- import { Database } from "bun:sqlite";
20028
- import { mkdirSync } from "fs";
20029
-
20030
- // src/lib/config.ts
20031
- import { join } from "path";
20032
- import { homedir } from "os";
20033
- var MCPS_DIR = join(homedir(), ".mcps");
20034
- var DB_PATH = join(MCPS_DIR, "registry.db");
20035
- var REGISTRY_API_URL = "https://registry.modelcontextprotocol.io/v0/servers";
20036
- var TOOL_PREFIX_SEPARATOR = "__";
20037
-
20038
- // src/lib/db.ts
20039
- var db = null;
20040
- function getDb() {
20041
- if (db)
20042
- return db;
20043
- mkdirSync(MCPS_DIR, { recursive: true });
20044
- db = new Database(DB_PATH, { create: true });
20045
- db.exec("PRAGMA journal_mode = WAL");
20046
- db.exec("PRAGMA busy_timeout = 5000");
20047
- db.exec("PRAGMA foreign_keys = ON");
20048
- db.exec(`
20049
- CREATE TABLE IF NOT EXISTS servers (
20050
- id TEXT PRIMARY KEY,
20051
- name TEXT NOT NULL,
20052
- description TEXT,
20053
- command TEXT NOT NULL,
20054
- args TEXT NOT NULL DEFAULT '[]',
20055
- env TEXT NOT NULL DEFAULT '{}',
20056
- transport TEXT NOT NULL DEFAULT 'stdio',
20057
- url TEXT,
20058
- source TEXT NOT NULL DEFAULT 'local',
20059
- enabled INTEGER NOT NULL DEFAULT 1,
20060
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
20061
- updated_at TEXT NOT NULL DEFAULT (datetime('now'))
20062
- )
20063
- `);
20064
- db.exec(`
20065
- CREATE TABLE IF NOT EXISTS tool_cache (
20066
- server_id TEXT NOT NULL,
20067
- name TEXT NOT NULL,
20068
- description TEXT NOT NULL DEFAULT '',
20069
- input_schema TEXT NOT NULL DEFAULT '{}',
20070
- cached_at TEXT NOT NULL DEFAULT (datetime('now')),
20071
- PRIMARY KEY (server_id, name),
20072
- FOREIGN KEY (server_id) REFERENCES servers(id) ON DELETE CASCADE
20073
- )
20074
- `);
20075
- db.exec("CREATE INDEX IF NOT EXISTS idx_tool_cache_server ON tool_cache(server_id)");
20076
- return db;
20077
- }
20404
+ // src/mcp/index.ts
20405
+ import { readFileSync as readFileSync3 } from "fs";
20406
+ import { join as join4, dirname as dirname2 } from "path";
20407
+ import { fileURLToPath } from "url";
20078
20408
 
20079
20409
  // src/lib/registry.ts
20410
+ init_db();
20080
20411
  function parseRow(row) {
20081
20412
  return {
20082
20413
  id: row.id,
20083
20414
  name: row.name,
20084
20415
  description: row.description || null,
20085
20416
  command: row.command,
20086
- args: JSON.parse(row.args),
20087
- env: JSON.parse(row.env),
20417
+ args: safeJsonParse(row.args, []),
20418
+ env: safeJsonParse(row.env, {}),
20088
20419
  transport: row.transport,
20089
20420
  url: row.url || null,
20090
20421
  source: row.source,
20091
20422
  enabled: row.enabled === 1,
20092
20423
  created_at: row.created_at,
20093
- updated_at: row.updated_at
20424
+ updated_at: row.updated_at,
20425
+ last_connected_at: row.last_connected_at ?? null,
20426
+ last_error: row.last_error ?? null
20094
20427
  };
20095
20428
  }
20429
+ function safeJsonParse(value, fallback) {
20430
+ if (typeof value !== "string")
20431
+ return fallback;
20432
+ try {
20433
+ return JSON.parse(value);
20434
+ } catch {
20435
+ return fallback;
20436
+ }
20437
+ }
20096
20438
  function generateId(name) {
20097
20439
  return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
20098
20440
  }
20441
+ function normalizeCandidate(value) {
20442
+ const trimmed = value?.trim();
20443
+ return trimmed ? trimmed : undefined;
20444
+ }
20445
+ function pickNameFromArgs(args) {
20446
+ if (!args || args.length === 0)
20447
+ return;
20448
+ const ddIndex = args.indexOf("--");
20449
+ if (ddIndex >= 0 && ddIndex < args.length - 1) {
20450
+ const after = normalizeCandidate(args[ddIndex + 1]);
20451
+ if (after)
20452
+ return after;
20453
+ }
20454
+ for (const arg of args) {
20455
+ const candidate = normalizeCandidate(arg);
20456
+ if (!candidate)
20457
+ continue;
20458
+ if (candidate.startsWith("-"))
20459
+ continue;
20460
+ return candidate;
20461
+ }
20462
+ return;
20463
+ }
20464
+ function pickId(candidates) {
20465
+ for (const candidate of candidates) {
20466
+ if (!candidate)
20467
+ continue;
20468
+ const id = generateId(candidate);
20469
+ if (id)
20470
+ return id;
20471
+ }
20472
+ return null;
20473
+ }
20099
20474
  function addServer(opts) {
20100
20475
  const db2 = getDb();
20101
- const name = opts.name || opts.args?.[0] || opts.command;
20102
- const id = generateId(name);
20476
+ const command = normalizeCandidate(opts.command);
20477
+ if (!command) {
20478
+ throw new Error("Command is required");
20479
+ }
20480
+ const argName = pickNameFromArgs(opts.args);
20481
+ const name = normalizeCandidate(opts.name) || argName || command;
20482
+ const id = pickId([normalizeCandidate(opts.name), argName, command]) || null;
20483
+ if (!id) {
20484
+ throw new Error("Unable to generate a valid server ID");
20485
+ }
20103
20486
  const row = db2.prepare(`INSERT INTO servers (id, name, description, command, args, env, transport, url, source)
20104
20487
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
20105
- 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");
20488
+ 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");
20106
20489
  return parseRow(row);
20107
20490
  }
20108
20491
  function removeServer(id) {
@@ -20158,6 +20541,9 @@ function updateServer(id, updates) {
20158
20541
  sets.push("updated_at = datetime('now')");
20159
20542
  values.push(id);
20160
20543
  const row = db2.prepare(`UPDATE servers SET ${sets.join(", ")} WHERE id = ? RETURNING *`).get(...values);
20544
+ if (!row) {
20545
+ throw new Error(`Server "${id}" not found`);
20546
+ }
20161
20547
  return parseRow(row);
20162
20548
  }
20163
20549
  function enableServer(id) {
@@ -20168,14 +20554,31 @@ function disableServer(id) {
20168
20554
  }
20169
20555
  function cacheTools(serverId, tools) {
20170
20556
  const db2 = getDb();
20171
- db2.prepare("DELETE FROM tool_cache WHERE server_id = ?").run(serverId);
20172
20557
  const insert = db2.prepare("INSERT INTO tool_cache (server_id, name, description, input_schema) VALUES (?, ?, ?, ?)");
20558
+ const uniqueTools = [];
20559
+ const seen = new Set;
20173
20560
  for (const tool of tools) {
20174
- insert.run(serverId, tool.name, tool.description, JSON.stringify(tool.input_schema));
20561
+ const name = tool.name?.trim();
20562
+ if (!name || seen.has(name))
20563
+ continue;
20564
+ seen.add(name);
20565
+ uniqueTools.push({
20566
+ name,
20567
+ description: tool.description || "",
20568
+ input_schema: tool.input_schema || {}
20569
+ });
20175
20570
  }
20571
+ const run = db2.transaction((rows) => {
20572
+ db2.prepare("DELETE FROM tool_cache WHERE server_id = ?").run(serverId);
20573
+ for (const tool of rows) {
20574
+ insert.run(serverId, tool.name, tool.description, JSON.stringify(tool.input_schema));
20575
+ }
20576
+ });
20577
+ run(uniqueTools);
20176
20578
  }
20177
20579
 
20178
20580
  // src/lib/remote.ts
20581
+ init_config();
20179
20582
  function parseRegistryEntry(entry) {
20180
20583
  const s = entry.server;
20181
20584
  return {
@@ -20236,6 +20639,102 @@ async function installFromRegistry(id) {
20236
20639
  });
20237
20640
  }
20238
20641
 
20642
+ // src/lib/finder.ts
20643
+ init_sources();
20644
+ async function listAwesomeServers() {
20645
+ const { listSources: listSources2, searchSource: searchSource2 } = await Promise.resolve().then(() => (init_sources(), exports_sources));
20646
+ const source = listSources2().find((s) => s.type === "awesome-list" && s.enabled);
20647
+ if (!source)
20648
+ return [];
20649
+ return searchSource2(source, "");
20650
+ }
20651
+
20652
+ // src/mcp/index.ts
20653
+ init_sources();
20654
+
20655
+ // src/lib/install.ts
20656
+ import { execFileSync } from "child_process";
20657
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3 } from "fs";
20658
+ import { join as join3 } from "path";
20659
+ import { homedir as homedir2 } from "os";
20660
+ function installToClaude(entry) {
20661
+ try {
20662
+ const args = [
20663
+ "mcp",
20664
+ "add",
20665
+ "--transport",
20666
+ entry.transport,
20667
+ "--scope",
20668
+ "user"
20669
+ ];
20670
+ for (const [k, v] of Object.entries(entry.env)) {
20671
+ args.push("--env", `${k}=${v}`);
20672
+ }
20673
+ args.push(entry.id, "--", entry.command, ...entry.args);
20674
+ execFileSync("claude", args, { stdio: "pipe" });
20675
+ return { agent: "claude", success: true };
20676
+ } catch (err) {
20677
+ return { agent: "claude", success: false, error: err.message };
20678
+ }
20679
+ }
20680
+ function installToCodex(entry) {
20681
+ try {
20682
+ const configDir = join3(homedir2(), ".codex");
20683
+ const configPath = join3(configDir, "config.toml");
20684
+ if (!existsSync2(configDir)) {
20685
+ mkdirSync3(configDir, { recursive: true });
20686
+ }
20687
+ const block = `
20688
+ [mcp_servers.${entry.id}]
20689
+ ` + `command = ${JSON.stringify(entry.command)}
20690
+ ` + `args = [${entry.args.map((a) => JSON.stringify(a)).join(", ")}]
20691
+ `;
20692
+ const existing = existsSync2(configPath) ? readFileSync2(configPath, "utf-8") : "";
20693
+ if (existing.includes(`[mcp_servers.${entry.id}]`)) {
20694
+ return { agent: "codex", success: true };
20695
+ }
20696
+ writeFileSync2(configPath, existing + block, "utf-8");
20697
+ return { agent: "codex", success: true };
20698
+ } catch (err) {
20699
+ return { agent: "codex", success: false, error: err.message };
20700
+ }
20701
+ }
20702
+ function installToGemini(entry) {
20703
+ try {
20704
+ const configDir = join3(homedir2(), ".gemini");
20705
+ const configPath = join3(configDir, "settings.json");
20706
+ if (!existsSync2(configDir)) {
20707
+ mkdirSync3(configDir, { recursive: true });
20708
+ }
20709
+ let settings = {};
20710
+ if (existsSync2(configPath)) {
20711
+ settings = JSON.parse(readFileSync2(configPath, "utf-8"));
20712
+ }
20713
+ if (!settings.mcpServers)
20714
+ settings.mcpServers = {};
20715
+ settings.mcpServers[entry.id] = {
20716
+ command: entry.command,
20717
+ args: entry.args,
20718
+ ...Object.keys(entry.env).length > 0 ? { env: entry.env } : {}
20719
+ };
20720
+ writeFileSync2(configPath, JSON.stringify(settings, null, 2), "utf-8");
20721
+ return { agent: "gemini", success: true };
20722
+ } catch (err) {
20723
+ return { agent: "gemini", success: false, error: err.message };
20724
+ }
20725
+ }
20726
+ function installToAgents(entry, targets = ["claude", "codex", "gemini"]) {
20727
+ return targets.map((target) => {
20728
+ if (target === "claude")
20729
+ return installToClaude(entry);
20730
+ if (target === "codex")
20731
+ return installToCodex(entry);
20732
+ if (target === "gemini")
20733
+ return installToGemini(entry);
20734
+ return { agent: target, success: false, error: "Unknown target" };
20735
+ });
20736
+ }
20737
+
20239
20738
  // node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/client.js
20240
20739
  class ExperimentalClientTasks {
20241
20740
  constructor(_client) {
@@ -22613,44 +23112,127 @@ class StreamableHTTPClientTransport {
22613
23112
  }
22614
23113
 
22615
23114
  // src/lib/proxy.ts
23115
+ init_config();
23116
+ init_db();
22616
23117
  var connections = new Map;
23118
+ var inflightConnections = new Map;
23119
+ var CONNECT_CONCURRENCY = 4;
23120
+ function buildEnv(extra) {
23121
+ const merged = {};
23122
+ for (const [key, value] of Object.entries(process.env)) {
23123
+ if (typeof value === "string")
23124
+ merged[key] = value;
23125
+ }
23126
+ for (const [key, value] of Object.entries(extra || {})) {
23127
+ if (value === undefined || value === null)
23128
+ continue;
23129
+ merged[key] = String(value);
23130
+ }
23131
+ return merged;
23132
+ }
23133
+ function requireUrl(entry) {
23134
+ if (!entry.url) {
23135
+ throw new Error(`Server "${entry.id}" is missing a URL for ${entry.transport} transport`);
23136
+ }
23137
+ try {
23138
+ return new URL(entry.url);
23139
+ } catch {
23140
+ throw new Error(`Server "${entry.id}" has an invalid URL: ${entry.url}`);
23141
+ }
23142
+ }
22617
23143
  async function connectToServer(entry) {
22618
23144
  if (connections.has(entry.id)) {
22619
23145
  return connections.get(entry.id);
22620
23146
  }
23147
+ const inflight = inflightConnections.get(entry.id);
23148
+ if (inflight) {
23149
+ return inflight;
23150
+ }
22621
23151
  const client = new Client({ name: "mcps-proxy", version: "0.0.1" });
22622
23152
  let transport;
22623
- if (entry.transport === "stdio") {
22624
- transport = new StdioClientTransport({
22625
- command: entry.command,
22626
- args: entry.args,
22627
- env: { ...process.env, ...entry.env }
22628
- });
22629
- } else if (entry.transport === "sse") {
22630
- transport = new SSEClientTransport(new URL(entry.url));
22631
- } else {
22632
- transport = new StreamableHTTPClientTransport(new URL(entry.url));
22633
- }
22634
- await client.connect(transport);
22635
- const result = await client.listTools();
22636
- const tools = (result.tools || []).map((t) => ({
22637
- server_id: entry.id,
22638
- name: t.name,
22639
- description: t.description || "",
22640
- input_schema: t.inputSchema || {}
22641
- }));
22642
- cacheTools(entry.id, tools.map((t) => ({ name: t.name, description: t.description, input_schema: t.input_schema })));
22643
- const connected = {
22644
- entry,
22645
- tools,
22646
- disconnect: async () => {
22647
- await client.close();
22648
- connections.delete(entry.id);
23153
+ const connectPromise = (async () => {
23154
+ try {
23155
+ if (entry.transport === "stdio") {
23156
+ if (!entry.command?.trim()) {
23157
+ throw new Error(`Server "${entry.id}" is missing a command`);
23158
+ }
23159
+ transport = new StdioClientTransport({
23160
+ command: entry.command,
23161
+ args: entry.args,
23162
+ env: buildEnv(entry.env)
23163
+ });
23164
+ } else if (entry.transport === "sse") {
23165
+ transport = new SSEClientTransport(requireUrl(entry));
23166
+ } else {
23167
+ transport = new StreamableHTTPClientTransport(requireUrl(entry));
23168
+ }
23169
+ await client.connect(transport);
23170
+ const result = await client.listTools();
23171
+ const tools = (result.tools || []).map((t) => ({
23172
+ server_id: entry.id,
23173
+ name: t.name,
23174
+ description: t.description || "",
23175
+ input_schema: t.inputSchema || {}
23176
+ }));
23177
+ cacheTools(entry.id, tools.map((t) => ({ name: t.name, description: t.description, input_schema: t.input_schema })));
23178
+ const connected = {
23179
+ entry,
23180
+ tools,
23181
+ disconnect: async () => {
23182
+ try {
23183
+ await client.close();
23184
+ } finally {
23185
+ connections.delete(entry.id);
23186
+ }
23187
+ }
23188
+ };
23189
+ connected._client = client;
23190
+ connections.set(entry.id, connected);
23191
+ try {
23192
+ getDb().prepare("UPDATE servers SET last_connected_at = datetime('now'), last_error = NULL WHERE id = ?").run(entry.id);
23193
+ } catch {}
23194
+ return connected;
23195
+ } catch (err) {
23196
+ try {
23197
+ getDb().prepare("UPDATE servers SET last_error = ? WHERE id = ?").run(err.message, entry.id);
23198
+ } catch {}
23199
+ try {
23200
+ await client.close();
23201
+ } catch {}
23202
+ throw err;
22649
23203
  }
22650
- };
22651
- connected._client = client;
22652
- connections.set(entry.id, connected);
22653
- return connected;
23204
+ })();
23205
+ inflightConnections.set(entry.id, connectPromise);
23206
+ try {
23207
+ return await connectPromise;
23208
+ } finally {
23209
+ inflightConnections.delete(entry.id);
23210
+ }
23211
+ }
23212
+ async function disconnectServer(id) {
23213
+ const conn = connections.get(id);
23214
+ if (conn) {
23215
+ await conn.disconnect();
23216
+ return;
23217
+ }
23218
+ const inflight = inflightConnections.get(id);
23219
+ if (inflight) {
23220
+ try {
23221
+ const pending = await inflight;
23222
+ await pending.disconnect();
23223
+ } catch {}
23224
+ }
23225
+ }
23226
+ async function disconnectAll() {
23227
+ const ids = Array.from(connections.keys());
23228
+ await Promise.allSettled(ids.map((id) => disconnectServer(id)));
23229
+ const inflight = Array.from(inflightConnections.values());
23230
+ await Promise.allSettled(inflight.map(async (promise2) => {
23231
+ try {
23232
+ const conn = await promise2;
23233
+ await conn.disconnect();
23234
+ } catch {}
23235
+ }));
22654
23236
  }
22655
23237
  function listAllTools() {
22656
23238
  const tools = [];
@@ -22665,12 +23247,19 @@ function listAllTools() {
22665
23247
  return tools;
22666
23248
  }
22667
23249
  async function callTool(prefixedName, args) {
22668
- const sepIdx = prefixedName.indexOf(TOOL_PREFIX_SEPARATOR);
23250
+ const normalized = prefixedName.trim();
23251
+ if (!normalized) {
23252
+ throw new Error("Tool name is required");
23253
+ }
23254
+ const sepIdx = normalized.indexOf(TOOL_PREFIX_SEPARATOR);
22669
23255
  if (sepIdx === -1) {
22670
23256
  throw new Error(`Invalid tool name "${prefixedName}" \u2014 expected format: server_id${TOOL_PREFIX_SEPARATOR}tool_name`);
22671
23257
  }
22672
- const serverId = prefixedName.slice(0, sepIdx);
22673
- const toolName = prefixedName.slice(sepIdx + TOOL_PREFIX_SEPARATOR.length);
23258
+ const serverId = normalized.slice(0, sepIdx).trim();
23259
+ const toolName = normalized.slice(sepIdx + TOOL_PREFIX_SEPARATOR.length).trim();
23260
+ if (!serverId || !toolName) {
23261
+ throw new Error(`Invalid tool name "${prefixedName}" \u2014 expected format: server_id${TOOL_PREFIX_SEPARATOR}tool_name`);
23262
+ }
22674
23263
  const conn = connections.get(serverId);
22675
23264
  if (!conn) {
22676
23265
  throw new Error(`Server "${serverId}" is not connected`);
@@ -22686,26 +23275,97 @@ async function callTool(prefixedName, args) {
22686
23275
  async function connectAllEnabled() {
22687
23276
  const servers = listServers().filter((s) => s.enabled);
22688
23277
  const results = [];
22689
- for (const server of servers) {
23278
+ let index = 0;
23279
+ const workerCount = Math.min(CONNECT_CONCURRENCY, servers.length);
23280
+ const workers = Array.from({ length: workerCount }, async () => {
23281
+ while (true) {
23282
+ const current = index++;
23283
+ if (current >= servers.length)
23284
+ return;
23285
+ const server = servers[current];
23286
+ try {
23287
+ const conn = await connectToServer(server);
23288
+ results.push(conn);
23289
+ } catch (err) {
23290
+ console.error(`Failed to connect to ${server.name}: ${err.message}`);
23291
+ }
23292
+ }
23293
+ });
23294
+ await Promise.all(workers);
23295
+ return results;
23296
+ }
23297
+
23298
+ // src/lib/doctor.ts
23299
+ import { execFileSync as execFileSync2 } from "child_process";
23300
+ async function diagnoseServer(server) {
23301
+ const checks4 = [];
23302
+ if (server.transport === "stdio") {
22690
23303
  try {
22691
- const conn = await connectToServer(server);
22692
- results.push(conn);
23304
+ execFileSync2("which", [server.command], { stdio: "pipe" });
23305
+ checks4.push({ name: "command on PATH", pass: true, message: `${server.command} found` });
23306
+ } catch {
23307
+ checks4.push({ name: "command on PATH", pass: false, message: `${server.command} not found on PATH` });
23308
+ }
23309
+ }
23310
+ const missingEnv = Object.entries(server.env).filter(([, v]) => !v);
23311
+ if (Object.keys(server.env).length === 0) {
23312
+ checks4.push({ name: "env vars", pass: true, message: "no env vars required" });
23313
+ } else if (missingEnv.length > 0) {
23314
+ checks4.push({ name: "env vars", pass: false, message: `missing values for: ${missingEnv.map(([k]) => k).join(", ")}` });
23315
+ } else {
23316
+ checks4.push({ name: "env vars", pass: true, message: `${Object.keys(server.env).length} env var(s) set` });
23317
+ }
23318
+ if (server.transport !== "stdio" && server.url) {
23319
+ try {
23320
+ const res = await fetch(server.url, { signal: AbortSignal.timeout(5000) });
23321
+ checks4.push({ name: "URL reachable", pass: res.ok || res.status < 500, message: `HTTP ${res.status}` });
22693
23322
  } catch (err) {
22694
- console.error(`Failed to connect to ${server.name}: ${err.message}`);
23323
+ checks4.push({ name: "URL reachable", pass: false, message: `unreachable: ${err.message}` });
22695
23324
  }
22696
23325
  }
22697
- return results;
23326
+ if (server.enabled) {
23327
+ try {
23328
+ await Promise.race([
23329
+ connectToServer(server),
23330
+ new Promise((_, reject) => setTimeout(() => reject(new Error("timeout after 10s")), 1e4))
23331
+ ]);
23332
+ await disconnectServer(server.id);
23333
+ checks4.push({ name: "connect & list tools", pass: true, message: "connected successfully" });
23334
+ } catch (err) {
23335
+ checks4.push({ name: "connect & list tools", pass: false, message: err.message });
23336
+ }
23337
+ } else {
23338
+ checks4.push({ name: "connect & list tools", pass: true, message: "skipped (server disabled)" });
23339
+ }
23340
+ return {
23341
+ server,
23342
+ checks: checks4,
23343
+ healthy: checks4.every((c) => c.pass)
23344
+ };
22698
23345
  }
22699
23346
 
22700
23347
  // src/mcp/index.ts
23348
+ init_config();
23349
+ function redactServerEnv(server) {
23350
+ return { ...server, env: {} };
23351
+ }
23352
+ var VERSION = (() => {
23353
+ try {
23354
+ const pkgPath = join4(dirname2(fileURLToPath(import.meta.url)), "..", "..", "package.json");
23355
+ const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
23356
+ return pkg.version || "0.0.1";
23357
+ } catch {
23358
+ return "0.0.1";
23359
+ }
23360
+ })();
22701
23361
  var server = new McpServer({
22702
23362
  name: "mcps",
22703
- version: "0.0.1"
23363
+ version: VERSION
22704
23364
  });
22705
23365
  server.tool("list_servers", "List all registered MCP servers", {}, async () => {
22706
23366
  const servers = listServers();
22707
23367
  return {
22708
- content: [{ type: "text", text: JSON.stringify(servers, null, 2) }]
23368
+ content: [{ type: "text", text: JSON.stringify(servers.map(redactServerEnv), null, 2) }]
22709
23369
  };
22710
23370
  });
22711
23371
  server.tool("search_registry", "Search the official MCP registry for servers", { query: exports_external.string().describe("Search query") }, async ({ query }) => {
@@ -22756,12 +23416,26 @@ server.tool("remove_server", "Remove a registered MCP server", { id: exports_ext
22756
23416
  };
22757
23417
  });
22758
23418
  server.tool("enable_server", "Enable a registered MCP server", { id: exports_external.string().describe("Server ID to enable") }, async ({ id }) => {
23419
+ const existing = getServer(id);
23420
+ if (!existing) {
23421
+ return {
23422
+ content: [{ type: "text", text: `Server "${id}" not found.` }],
23423
+ isError: true
23424
+ };
23425
+ }
22759
23426
  const entry = enableServer(id);
22760
23427
  return {
22761
23428
  content: [{ type: "text", text: JSON.stringify(entry, null, 2) }]
22762
23429
  };
22763
23430
  });
22764
23431
  server.tool("disable_server", "Disable a registered MCP server", { id: exports_external.string().describe("Server ID to disable") }, async ({ id }) => {
23432
+ const existing = getServer(id);
23433
+ if (!existing) {
23434
+ return {
23435
+ content: [{ type: "text", text: `Server "${id}" not found.` }],
23436
+ isError: true
23437
+ };
23438
+ }
22765
23439
  const entry = disableServer(id);
22766
23440
  return {
22767
23441
  content: [{ type: "text", text: JSON.stringify(entry, null, 2) }]
@@ -22776,12 +23450,108 @@ server.tool("get_server_info", "Get detailed information about a registered MCP
22776
23450
  };
22777
23451
  }
22778
23452
  return {
22779
- content: [{ type: "text", text: JSON.stringify(entry, null, 2) }]
23453
+ content: [{ type: "text", text: JSON.stringify(redactServerEnv(entry), null, 2) }]
23454
+ };
23455
+ });
23456
+ server.tool("find_mcp_servers", "Search for MCP servers across configured sources (official registry, npm, GitHub topics, awesome lists). Use list_sources to see available source IDs.", {
23457
+ query: exports_external.string().describe("Search query (e.g. 'filesystem', 'postgres', 'browser')"),
23458
+ sources: exports_external.array(exports_external.string()).optional().describe("Source IDs to search (default: all enabled). Use list_sources to get IDs."),
23459
+ limit: exports_external.number().optional().describe("Max results per source (default: 20)")
23460
+ }, async ({ query, sources, limit }) => {
23461
+ const results = await findServers(query, { sources, limit });
23462
+ return {
23463
+ content: [{ type: "text", text: JSON.stringify(results, null, 2) }]
23464
+ };
23465
+ });
23466
+ server.tool("list_sources", "List all configured search sources for finding MCP servers", {}, async () => {
23467
+ const sources = listSources();
23468
+ return {
23469
+ content: [{ type: "text", text: JSON.stringify(sources, null, 2) }]
23470
+ };
23471
+ });
23472
+ server.tool("add_source", "Add a new search source for finding MCP servers", {
23473
+ name: exports_external.string().describe("Source name"),
23474
+ type: exports_external.enum(["mcp-registry", "awesome-list", "npm-search", "github-topic"]).describe("Source type"),
23475
+ url: exports_external.string().describe("Source URL endpoint"),
23476
+ description: exports_external.string().optional().describe("Description")
23477
+ }, async ({ name, type, url: url2, description }) => {
23478
+ const source = addSource({ name, type, url: url2, description });
23479
+ return {
23480
+ content: [{ type: "text", text: JSON.stringify(source, null, 2) }]
23481
+ };
23482
+ });
23483
+ server.tool("remove_source", "Remove a search source by ID", { id: exports_external.string().describe("Source ID to remove") }, async ({ id }) => {
23484
+ const existing = getSource(id);
23485
+ if (!existing) {
23486
+ return {
23487
+ content: [{ type: "text", text: `Source "${id}" not found.` }],
23488
+ isError: true
23489
+ };
23490
+ }
23491
+ removeSource(id);
23492
+ return {
23493
+ content: [{ type: "text", text: `Removed source: ${existing.name} [${id}]` }]
23494
+ };
23495
+ });
23496
+ server.tool("enable_source_finder", "Enable a search source", { id: exports_external.string().describe("Source ID to enable") }, async ({ id }) => {
23497
+ const existing = getSource(id);
23498
+ if (!existing) {
23499
+ return {
23500
+ content: [{ type: "text", text: `Source "${id}" not found.` }],
23501
+ isError: true
23502
+ };
23503
+ }
23504
+ enableSource(id);
23505
+ return {
23506
+ content: [{ type: "text", text: `Enabled source: ${existing.name}` }]
23507
+ };
23508
+ });
23509
+ server.tool("disable_source_finder", "Disable a search source", { id: exports_external.string().describe("Source ID to disable") }, async ({ id }) => {
23510
+ const existing = getSource(id);
23511
+ if (!existing) {
23512
+ return {
23513
+ content: [{ type: "text", text: `Source "${id}" not found.` }],
23514
+ isError: true
23515
+ };
23516
+ }
23517
+ disableSource(id);
23518
+ return {
23519
+ content: [{ type: "text", text: `Disabled source: ${existing.name}` }]
23520
+ };
23521
+ });
23522
+ server.tool("install_to_agents", "Install a registered MCP server into Claude Code, Codex, and/or Gemini", {
23523
+ id: exports_external.string().describe("Server ID to install (from list_servers)"),
23524
+ targets: exports_external.array(exports_external.enum(["claude", "codex", "gemini"])).optional().describe("Target agents to install into (default: all)")
23525
+ }, async ({ id, targets }) => {
23526
+ const entry = getServer(id);
23527
+ if (!entry) {
23528
+ return {
23529
+ content: [{ type: "text", text: `Server "${id}" not found.` }],
23530
+ isError: true
23531
+ };
23532
+ }
23533
+ const agentTargets = targets ?? ["claude", "codex", "gemini"];
23534
+ const results = installToAgents(entry, agentTargets);
23535
+ return {
23536
+ content: [{ type: "text", text: JSON.stringify(results, null, 2) }]
23537
+ };
23538
+ });
23539
+ server.tool("list_awesome_servers", "List all MCP servers from the curated punkpeye/awesome-mcp-servers GitHub list", {}, async () => {
23540
+ const results = await listAwesomeServers();
23541
+ return {
23542
+ content: [{ type: "text", text: JSON.stringify(results, null, 2) }]
22780
23543
  };
22781
23544
  });
22782
23545
  server.tool("connect_and_list_tools", "Connect to all enabled MCP servers and list their available tools", {}, async () => {
22783
- await connectAllEnabled();
22784
- const tools = listAllTools();
23546
+ let tools = [];
23547
+ try {
23548
+ await connectAllEnabled();
23549
+ tools = listAllTools();
23550
+ } finally {
23551
+ await disconnectAll().catch(() => {
23552
+ return;
23553
+ });
23554
+ }
22785
23555
  return {
22786
23556
  content: [{ type: "text", text: JSON.stringify(tools, null, 2) }]
22787
23557
  };
@@ -22791,6 +23561,28 @@ server.tool("call_upstream_tool", `Call a tool on a connected upstream MCP serve
22791
23561
  arguments: exports_external.record(exports_external.unknown()).optional().describe("Tool arguments as key-value pairs")
22792
23562
  }, async ({ tool_name, arguments: args }) => {
22793
23563
  try {
23564
+ const sepIdx = tool_name.indexOf(TOOL_PREFIX_SEPARATOR);
23565
+ if (sepIdx === -1) {
23566
+ return {
23567
+ content: [{ type: "text", text: `Error: Invalid tool name "${tool_name}"` }],
23568
+ isError: true
23569
+ };
23570
+ }
23571
+ const serverId = tool_name.slice(0, sepIdx);
23572
+ const entry = getServer(serverId);
23573
+ if (!entry) {
23574
+ return {
23575
+ content: [{ type: "text", text: `Error: Server "${serverId}" not found.` }],
23576
+ isError: true
23577
+ };
23578
+ }
23579
+ if (!entry.enabled) {
23580
+ return {
23581
+ content: [{ type: "text", text: `Error: Server "${serverId}" is disabled.` }],
23582
+ isError: true
23583
+ };
23584
+ }
23585
+ await connectToServer(entry);
22794
23586
  const result = await callTool(tool_name, args || {});
22795
23587
  return { content: result.content };
22796
23588
  } catch (err) {
@@ -22800,6 +23592,13 @@ server.tool("call_upstream_tool", `Call a tool on a connected upstream MCP serve
22800
23592
  };
22801
23593
  }
22802
23594
  });
23595
+ server.tool("diagnose_server", "Run health checks on a registered MCP server", { id: exports_external.string().describe("Server ID") }, async ({ id }) => {
23596
+ const entry = getServer(id);
23597
+ if (!entry)
23598
+ return { content: [{ type: "text", text: `Server "${id}" not found.` }], isError: true };
23599
+ const report = await diagnoseServer(entry);
23600
+ return { content: [{ type: "text", text: JSON.stringify(report, null, 2) }] };
23601
+ });
22803
23602
  async function startMcpServer() {
22804
23603
  const transport = new StdioServerTransport;
22805
23604
  await server.connect(transport);