@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/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,40 @@ 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);
20578
+ }
20579
+ function getCachedTools(serverId) {
20580
+ const db2 = getDb();
20581
+ const rows = db2.prepare("SELECT name, description, input_schema FROM tool_cache WHERE server_id = ? ORDER BY name").all(serverId);
20582
+ return rows.map((r) => ({
20583
+ name: r.name,
20584
+ description: r.description,
20585
+ input_schema: safeJsonParse(r.input_schema, {})
20586
+ }));
20176
20587
  }
20177
20588
 
20178
20589
  // src/lib/remote.ts
20590
+ init_config();
20179
20591
  function parseRegistryEntry(entry) {
20180
20592
  const s = entry.server;
20181
20593
  return {
@@ -20236,6 +20648,102 @@ async function installFromRegistry(id) {
20236
20648
  });
20237
20649
  }
20238
20650
 
20651
+ // src/lib/finder.ts
20652
+ init_sources();
20653
+ async function listAwesomeServers() {
20654
+ const { listSources: listSources2, searchSource: searchSource2 } = await Promise.resolve().then(() => (init_sources(), exports_sources));
20655
+ const source = listSources2().find((s) => s.type === "awesome-list" && s.enabled);
20656
+ if (!source)
20657
+ return [];
20658
+ return searchSource2(source, "");
20659
+ }
20660
+
20661
+ // src/mcp/index.ts
20662
+ init_sources();
20663
+
20664
+ // src/lib/install.ts
20665
+ import { execFileSync } from "child_process";
20666
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3 } from "fs";
20667
+ import { join as join3 } from "path";
20668
+ import { homedir as homedir2 } from "os";
20669
+ function installToClaude(entry) {
20670
+ try {
20671
+ const args = [
20672
+ "mcp",
20673
+ "add",
20674
+ "--transport",
20675
+ entry.transport,
20676
+ "--scope",
20677
+ "user"
20678
+ ];
20679
+ for (const [k, v] of Object.entries(entry.env)) {
20680
+ args.push("--env", `${k}=${v}`);
20681
+ }
20682
+ args.push(entry.id, "--", entry.command, ...entry.args);
20683
+ execFileSync("claude", args, { stdio: "pipe" });
20684
+ return { agent: "claude", success: true };
20685
+ } catch (err) {
20686
+ return { agent: "claude", success: false, error: err.message };
20687
+ }
20688
+ }
20689
+ function installToCodex(entry) {
20690
+ try {
20691
+ const configDir = join3(homedir2(), ".codex");
20692
+ const configPath = join3(configDir, "config.toml");
20693
+ if (!existsSync2(configDir)) {
20694
+ mkdirSync3(configDir, { recursive: true });
20695
+ }
20696
+ const block = `
20697
+ [mcp_servers.${entry.id}]
20698
+ ` + `command = ${JSON.stringify(entry.command)}
20699
+ ` + `args = [${entry.args.map((a) => JSON.stringify(a)).join(", ")}]
20700
+ `;
20701
+ const existing = existsSync2(configPath) ? readFileSync2(configPath, "utf-8") : "";
20702
+ if (existing.includes(`[mcp_servers.${entry.id}]`)) {
20703
+ return { agent: "codex", success: true };
20704
+ }
20705
+ writeFileSync2(configPath, existing + block, "utf-8");
20706
+ return { agent: "codex", success: true };
20707
+ } catch (err) {
20708
+ return { agent: "codex", success: false, error: err.message };
20709
+ }
20710
+ }
20711
+ function installToGemini(entry) {
20712
+ try {
20713
+ const configDir = join3(homedir2(), ".gemini");
20714
+ const configPath = join3(configDir, "settings.json");
20715
+ if (!existsSync2(configDir)) {
20716
+ mkdirSync3(configDir, { recursive: true });
20717
+ }
20718
+ let settings = {};
20719
+ if (existsSync2(configPath)) {
20720
+ settings = JSON.parse(readFileSync2(configPath, "utf-8"));
20721
+ }
20722
+ if (!settings.mcpServers)
20723
+ settings.mcpServers = {};
20724
+ settings.mcpServers[entry.id] = {
20725
+ command: entry.command,
20726
+ args: entry.args,
20727
+ ...Object.keys(entry.env).length > 0 ? { env: entry.env } : {}
20728
+ };
20729
+ writeFileSync2(configPath, JSON.stringify(settings, null, 2), "utf-8");
20730
+ return { agent: "gemini", success: true };
20731
+ } catch (err) {
20732
+ return { agent: "gemini", success: false, error: err.message };
20733
+ }
20734
+ }
20735
+ function installToAgents(entry, targets = ["claude", "codex", "gemini"]) {
20736
+ return targets.map((target) => {
20737
+ if (target === "claude")
20738
+ return installToClaude(entry);
20739
+ if (target === "codex")
20740
+ return installToCodex(entry);
20741
+ if (target === "gemini")
20742
+ return installToGemini(entry);
20743
+ return { agent: target, success: false, error: "Unknown target" };
20744
+ });
20745
+ }
20746
+
20239
20747
  // node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/client.js
20240
20748
  class ExperimentalClientTasks {
20241
20749
  constructor(_client) {
@@ -22613,44 +23121,127 @@ class StreamableHTTPClientTransport {
22613
23121
  }
22614
23122
 
22615
23123
  // src/lib/proxy.ts
23124
+ init_config();
23125
+ init_db();
22616
23126
  var connections = new Map;
23127
+ var inflightConnections = new Map;
23128
+ var CONNECT_CONCURRENCY = 4;
23129
+ function buildEnv(extra) {
23130
+ const merged = {};
23131
+ for (const [key, value] of Object.entries(process.env)) {
23132
+ if (typeof value === "string")
23133
+ merged[key] = value;
23134
+ }
23135
+ for (const [key, value] of Object.entries(extra || {})) {
23136
+ if (value === undefined || value === null)
23137
+ continue;
23138
+ merged[key] = String(value);
23139
+ }
23140
+ return merged;
23141
+ }
23142
+ function requireUrl(entry) {
23143
+ if (!entry.url) {
23144
+ throw new Error(`Server "${entry.id}" is missing a URL for ${entry.transport} transport`);
23145
+ }
23146
+ try {
23147
+ return new URL(entry.url);
23148
+ } catch {
23149
+ throw new Error(`Server "${entry.id}" has an invalid URL: ${entry.url}`);
23150
+ }
23151
+ }
22617
23152
  async function connectToServer(entry) {
22618
23153
  if (connections.has(entry.id)) {
22619
23154
  return connections.get(entry.id);
22620
23155
  }
23156
+ const inflight = inflightConnections.get(entry.id);
23157
+ if (inflight) {
23158
+ return inflight;
23159
+ }
22621
23160
  const client = new Client({ name: "mcps-proxy", version: "0.0.1" });
22622
23161
  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);
23162
+ const connectPromise = (async () => {
23163
+ try {
23164
+ if (entry.transport === "stdio") {
23165
+ if (!entry.command?.trim()) {
23166
+ throw new Error(`Server "${entry.id}" is missing a command`);
23167
+ }
23168
+ transport = new StdioClientTransport({
23169
+ command: entry.command,
23170
+ args: entry.args,
23171
+ env: buildEnv(entry.env)
23172
+ });
23173
+ } else if (entry.transport === "sse") {
23174
+ transport = new SSEClientTransport(requireUrl(entry));
23175
+ } else {
23176
+ transport = new StreamableHTTPClientTransport(requireUrl(entry));
23177
+ }
23178
+ await client.connect(transport);
23179
+ const result = await client.listTools();
23180
+ const tools = (result.tools || []).map((t) => ({
23181
+ server_id: entry.id,
23182
+ name: t.name,
23183
+ description: t.description || "",
23184
+ input_schema: t.inputSchema || {}
23185
+ }));
23186
+ cacheTools(entry.id, tools.map((t) => ({ name: t.name, description: t.description, input_schema: t.input_schema })));
23187
+ const connected = {
23188
+ entry,
23189
+ tools,
23190
+ disconnect: async () => {
23191
+ try {
23192
+ await client.close();
23193
+ } finally {
23194
+ connections.delete(entry.id);
23195
+ }
23196
+ }
23197
+ };
23198
+ connected._client = client;
23199
+ connections.set(entry.id, connected);
23200
+ try {
23201
+ getDb().prepare("UPDATE servers SET last_connected_at = datetime('now'), last_error = NULL WHERE id = ?").run(entry.id);
23202
+ } catch {}
23203
+ return connected;
23204
+ } catch (err) {
23205
+ try {
23206
+ getDb().prepare("UPDATE servers SET last_error = ? WHERE id = ?").run(err.message, entry.id);
23207
+ } catch {}
23208
+ try {
23209
+ await client.close();
23210
+ } catch {}
23211
+ throw err;
22649
23212
  }
22650
- };
22651
- connected._client = client;
22652
- connections.set(entry.id, connected);
22653
- return connected;
23213
+ })();
23214
+ inflightConnections.set(entry.id, connectPromise);
23215
+ try {
23216
+ return await connectPromise;
23217
+ } finally {
23218
+ inflightConnections.delete(entry.id);
23219
+ }
23220
+ }
23221
+ async function disconnectServer(id) {
23222
+ const conn = connections.get(id);
23223
+ if (conn) {
23224
+ await conn.disconnect();
23225
+ return;
23226
+ }
23227
+ const inflight = inflightConnections.get(id);
23228
+ if (inflight) {
23229
+ try {
23230
+ const pending = await inflight;
23231
+ await pending.disconnect();
23232
+ } catch {}
23233
+ }
23234
+ }
23235
+ async function disconnectAll() {
23236
+ const ids = Array.from(connections.keys());
23237
+ await Promise.allSettled(ids.map((id) => disconnectServer(id)));
23238
+ const inflight = Array.from(inflightConnections.values());
23239
+ await Promise.allSettled(inflight.map(async (promise2) => {
23240
+ try {
23241
+ const conn = await promise2;
23242
+ await conn.disconnect();
23243
+ } catch {}
23244
+ }));
22654
23245
  }
22655
23246
  function listAllTools() {
22656
23247
  const tools = [];
@@ -22665,12 +23256,19 @@ function listAllTools() {
22665
23256
  return tools;
22666
23257
  }
22667
23258
  async function callTool(prefixedName, args) {
22668
- const sepIdx = prefixedName.indexOf(TOOL_PREFIX_SEPARATOR);
23259
+ const normalized = prefixedName.trim();
23260
+ if (!normalized) {
23261
+ throw new Error("Tool name is required");
23262
+ }
23263
+ const sepIdx = normalized.indexOf(TOOL_PREFIX_SEPARATOR);
22669
23264
  if (sepIdx === -1) {
22670
23265
  throw new Error(`Invalid tool name "${prefixedName}" \u2014 expected format: server_id${TOOL_PREFIX_SEPARATOR}tool_name`);
22671
23266
  }
22672
- const serverId = prefixedName.slice(0, sepIdx);
22673
- const toolName = prefixedName.slice(sepIdx + TOOL_PREFIX_SEPARATOR.length);
23267
+ const serverId = normalized.slice(0, sepIdx).trim();
23268
+ const toolName = normalized.slice(sepIdx + TOOL_PREFIX_SEPARATOR.length).trim();
23269
+ if (!serverId || !toolName) {
23270
+ throw new Error(`Invalid tool name "${prefixedName}" \u2014 expected format: server_id${TOOL_PREFIX_SEPARATOR}tool_name`);
23271
+ }
22674
23272
  const conn = connections.get(serverId);
22675
23273
  if (!conn) {
22676
23274
  throw new Error(`Server "${serverId}" is not connected`);
@@ -22686,26 +23284,108 @@ async function callTool(prefixedName, args) {
22686
23284
  async function connectAllEnabled() {
22687
23285
  const servers = listServers().filter((s) => s.enabled);
22688
23286
  const results = [];
22689
- for (const server of servers) {
23287
+ let index = 0;
23288
+ const workerCount = Math.min(CONNECT_CONCURRENCY, servers.length);
23289
+ const workers = Array.from({ length: workerCount }, async () => {
23290
+ while (true) {
23291
+ const current = index++;
23292
+ if (current >= servers.length)
23293
+ return;
23294
+ const server = servers[current];
23295
+ try {
23296
+ const conn = await connectToServer(server);
23297
+ results.push(conn);
23298
+ } catch (err) {
23299
+ console.error(`Failed to connect to ${server.name}: ${err.message}`);
23300
+ }
23301
+ }
23302
+ });
23303
+ await Promise.all(workers);
23304
+ return results;
23305
+ }
23306
+
23307
+ // src/lib/doctor.ts
23308
+ import { execFileSync as execFileSync2 } from "child_process";
23309
+ async function diagnoseServer(server) {
23310
+ const checks4 = [];
23311
+ if (server.transport === "stdio") {
23312
+ try {
23313
+ const path = execFileSync2("which", [server.command], { stdio: "pipe" }).toString().trim();
23314
+ let version2 = "";
23315
+ try {
23316
+ version2 = execFileSync2(server.command, ["--version"], { stdio: "pipe" }).toString().trim().split(`
23317
+ `)[0];
23318
+ } catch {}
23319
+ checks4.push({ name: "command on PATH", pass: true, message: `${path}${version2 ? ` (${version2})` : ""}` });
23320
+ } catch {
23321
+ checks4.push({
23322
+ name: "command on PATH",
23323
+ pass: false,
23324
+ message: `${server.command} not found on PATH`,
23325
+ fixable: true,
23326
+ fixHint: server.args[0] || server.command
23327
+ });
23328
+ }
23329
+ }
23330
+ const missingEnv = Object.entries(server.env).filter(([, v]) => !v);
23331
+ if (Object.keys(server.env).length === 0) {
23332
+ checks4.push({ name: "env vars", pass: true, message: "no env vars required" });
23333
+ } else if (missingEnv.length > 0) {
23334
+ checks4.push({ name: "env vars", pass: false, message: `missing values for: ${missingEnv.map(([k]) => k).join(", ")}` });
23335
+ } else {
23336
+ checks4.push({ name: "env vars", pass: true, message: `${Object.keys(server.env).length} env var(s) set` });
23337
+ }
23338
+ if (server.transport !== "stdio" && server.url) {
22690
23339
  try {
22691
- const conn = await connectToServer(server);
22692
- results.push(conn);
23340
+ const res = await fetch(server.url, { signal: AbortSignal.timeout(5000) });
23341
+ checks4.push({ name: "URL reachable", pass: res.ok || res.status < 500, message: `HTTP ${res.status}` });
22693
23342
  } catch (err) {
22694
- console.error(`Failed to connect to ${server.name}: ${err.message}`);
23343
+ checks4.push({ name: "URL reachable", pass: false, message: `unreachable: ${err.message}` });
22695
23344
  }
22696
23345
  }
22697
- return results;
23346
+ if (server.enabled) {
23347
+ try {
23348
+ await Promise.race([
23349
+ connectToServer(server),
23350
+ new Promise((_, reject) => setTimeout(() => reject(new Error("timeout after 10s")), 1e4))
23351
+ ]);
23352
+ await disconnectServer(server.id);
23353
+ checks4.push({ name: "connect & list tools", pass: true, message: "connected successfully" });
23354
+ } catch (err) {
23355
+ checks4.push({ name: "connect & list tools", pass: false, message: err.message });
23356
+ }
23357
+ } else {
23358
+ checks4.push({ name: "connect & list tools", pass: true, message: "skipped (server disabled)" });
23359
+ }
23360
+ return {
23361
+ server,
23362
+ checks: checks4,
23363
+ healthy: checks4.every((c) => c.pass)
23364
+ };
22698
23365
  }
22699
23366
 
22700
23367
  // src/mcp/index.ts
23368
+ init_config();
23369
+ function redactServerEnv(server) {
23370
+ return { ...server, env: {} };
23371
+ }
23372
+ var VERSION = (() => {
23373
+ try {
23374
+ const pkgPath = join4(dirname2(fileURLToPath(import.meta.url)), "..", "..", "package.json");
23375
+ const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
23376
+ return pkg.version || "0.0.1";
23377
+ } catch {
23378
+ return "0.0.1";
23379
+ }
23380
+ })();
22701
23381
  var server = new McpServer({
22702
23382
  name: "mcps",
22703
- version: "0.0.1"
23383
+ version: VERSION
22704
23384
  });
22705
23385
  server.tool("list_servers", "List all registered MCP servers", {}, async () => {
22706
23386
  const servers = listServers();
22707
23387
  return {
22708
- content: [{ type: "text", text: JSON.stringify(servers, null, 2) }]
23388
+ content: [{ type: "text", text: JSON.stringify(servers.map(redactServerEnv), null, 2) }]
22709
23389
  };
22710
23390
  });
22711
23391
  server.tool("search_registry", "Search the official MCP registry for servers", { query: exports_external.string().describe("Search query") }, async ({ query }) => {
@@ -22756,17 +23436,84 @@ server.tool("remove_server", "Remove a registered MCP server", { id: exports_ext
22756
23436
  };
22757
23437
  });
22758
23438
  server.tool("enable_server", "Enable a registered MCP server", { id: exports_external.string().describe("Server ID to enable") }, async ({ id }) => {
23439
+ const existing = getServer(id);
23440
+ if (!existing) {
23441
+ return {
23442
+ content: [{ type: "text", text: `Server "${id}" not found.` }],
23443
+ isError: true
23444
+ };
23445
+ }
22759
23446
  const entry = enableServer(id);
22760
23447
  return {
22761
23448
  content: [{ type: "text", text: JSON.stringify(entry, null, 2) }]
22762
23449
  };
22763
23450
  });
22764
23451
  server.tool("disable_server", "Disable a registered MCP server", { id: exports_external.string().describe("Server ID to disable") }, async ({ id }) => {
23452
+ const existing = getServer(id);
23453
+ if (!existing) {
23454
+ return {
23455
+ content: [{ type: "text", text: `Server "${id}" not found.` }],
23456
+ isError: true
23457
+ };
23458
+ }
22765
23459
  const entry = disableServer(id);
22766
23460
  return {
22767
23461
  content: [{ type: "text", text: JSON.stringify(entry, null, 2) }]
22768
23462
  };
22769
23463
  });
23464
+ server.tool("update_server", "Update fields of a registered MCP server", {
23465
+ id: exports_external.string().describe("Server ID to update"),
23466
+ name: exports_external.string().optional().describe("New display name"),
23467
+ description: exports_external.string().optional().describe("New description"),
23468
+ command: exports_external.string().optional().describe("New command"),
23469
+ args: exports_external.array(exports_external.string()).optional().describe("New args list"),
23470
+ transport: exports_external.enum(["stdio", "sse", "streamable-http"]).optional().describe("New transport type"),
23471
+ url: exports_external.string().optional().describe("New URL for remote transports")
23472
+ }, async ({ id, name, description, command, args, transport, url: url2 }) => {
23473
+ const existing = getServer(id);
23474
+ if (!existing) {
23475
+ return {
23476
+ content: [{ type: "text", text: `Server "${id}" not found.` }],
23477
+ isError: true
23478
+ };
23479
+ }
23480
+ const fields = {};
23481
+ if (name !== undefined)
23482
+ fields.name = name;
23483
+ if (description !== undefined)
23484
+ fields.description = description;
23485
+ if (command !== undefined)
23486
+ fields.command = command;
23487
+ if (args !== undefined)
23488
+ fields.args = args;
23489
+ if (transport !== undefined)
23490
+ fields.transport = transport;
23491
+ if (url2 !== undefined)
23492
+ fields.url = url2;
23493
+ const updated = updateServer(id, fields);
23494
+ return {
23495
+ content: [{ type: "text", text: JSON.stringify(redactServerEnv(updated), null, 2) }]
23496
+ };
23497
+ });
23498
+ server.tool("list_tools", "List all cached tools across registered servers without connecting. Optionally filter by server_id.", { server_id: exports_external.string().optional().describe("Server ID to filter by (optional)") }, async ({ server_id }) => {
23499
+ if (server_id) {
23500
+ const tools = getCachedTools(server_id);
23501
+ return {
23502
+ content: [{ type: "text", text: JSON.stringify(tools.map((t) => ({ ...t, server_id })), null, 2) }]
23503
+ };
23504
+ }
23505
+ const servers = listServers();
23506
+ const allTools = [];
23507
+ for (const s of servers) {
23508
+ const tools = getCachedTools(s.id);
23509
+ for (const t of tools) {
23510
+ allTools.push({ server_id: s.id, ...t });
23511
+ }
23512
+ }
23513
+ return {
23514
+ content: [{ type: "text", text: JSON.stringify(allTools, null, 2) }]
23515
+ };
23516
+ });
22770
23517
  server.tool("get_server_info", "Get detailed information about a registered MCP server", { id: exports_external.string().describe("Server ID") }, async ({ id }) => {
22771
23518
  const entry = getServer(id);
22772
23519
  if (!entry) {
@@ -22776,12 +23523,108 @@ server.tool("get_server_info", "Get detailed information about a registered MCP
22776
23523
  };
22777
23524
  }
22778
23525
  return {
22779
- content: [{ type: "text", text: JSON.stringify(entry, null, 2) }]
23526
+ content: [{ type: "text", text: JSON.stringify(redactServerEnv(entry), null, 2) }]
23527
+ };
23528
+ });
23529
+ 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.", {
23530
+ query: exports_external.string().describe("Search query (e.g. 'filesystem', 'postgres', 'browser')"),
23531
+ sources: exports_external.array(exports_external.string()).optional().describe("Source IDs to search (default: all enabled). Use list_sources to get IDs."),
23532
+ limit: exports_external.number().optional().describe("Max results per source (default: 20)")
23533
+ }, async ({ query, sources, limit }) => {
23534
+ const results = await findServers(query, { sources, limit });
23535
+ return {
23536
+ content: [{ type: "text", text: JSON.stringify(results, null, 2) }]
23537
+ };
23538
+ });
23539
+ server.tool("list_sources", "List all configured search sources for finding MCP servers", {}, async () => {
23540
+ const sources = listSources();
23541
+ return {
23542
+ content: [{ type: "text", text: JSON.stringify(sources, null, 2) }]
23543
+ };
23544
+ });
23545
+ server.tool("add_source", "Add a new search source for finding MCP servers", {
23546
+ name: exports_external.string().describe("Source name"),
23547
+ type: exports_external.enum(["mcp-registry", "awesome-list", "npm-search", "github-topic"]).describe("Source type"),
23548
+ url: exports_external.string().describe("Source URL endpoint"),
23549
+ description: exports_external.string().optional().describe("Description")
23550
+ }, async ({ name, type, url: url2, description }) => {
23551
+ const source = addSource({ name, type, url: url2, description });
23552
+ return {
23553
+ content: [{ type: "text", text: JSON.stringify(source, null, 2) }]
23554
+ };
23555
+ });
23556
+ server.tool("remove_source", "Remove a search source by ID", { id: exports_external.string().describe("Source ID to remove") }, async ({ id }) => {
23557
+ const existing = getSource(id);
23558
+ if (!existing) {
23559
+ return {
23560
+ content: [{ type: "text", text: `Source "${id}" not found.` }],
23561
+ isError: true
23562
+ };
23563
+ }
23564
+ removeSource(id);
23565
+ return {
23566
+ content: [{ type: "text", text: `Removed source: ${existing.name} [${id}]` }]
23567
+ };
23568
+ });
23569
+ server.tool("enable_source_finder", "Enable a search source", { id: exports_external.string().describe("Source ID to enable") }, async ({ id }) => {
23570
+ const existing = getSource(id);
23571
+ if (!existing) {
23572
+ return {
23573
+ content: [{ type: "text", text: `Source "${id}" not found.` }],
23574
+ isError: true
23575
+ };
23576
+ }
23577
+ enableSource(id);
23578
+ return {
23579
+ content: [{ type: "text", text: `Enabled source: ${existing.name}` }]
23580
+ };
23581
+ });
23582
+ server.tool("disable_source_finder", "Disable a search source", { id: exports_external.string().describe("Source ID to disable") }, async ({ id }) => {
23583
+ const existing = getSource(id);
23584
+ if (!existing) {
23585
+ return {
23586
+ content: [{ type: "text", text: `Source "${id}" not found.` }],
23587
+ isError: true
23588
+ };
23589
+ }
23590
+ disableSource(id);
23591
+ return {
23592
+ content: [{ type: "text", text: `Disabled source: ${existing.name}` }]
23593
+ };
23594
+ });
23595
+ server.tool("install_to_agents", "Install a registered MCP server into Claude Code, Codex, and/or Gemini", {
23596
+ id: exports_external.string().describe("Server ID to install (from list_servers)"),
23597
+ targets: exports_external.array(exports_external.enum(["claude", "codex", "gemini"])).optional().describe("Target agents to install into (default: all)")
23598
+ }, async ({ id, targets }) => {
23599
+ const entry = getServer(id);
23600
+ if (!entry) {
23601
+ return {
23602
+ content: [{ type: "text", text: `Server "${id}" not found.` }],
23603
+ isError: true
23604
+ };
23605
+ }
23606
+ const agentTargets = targets ?? ["claude", "codex", "gemini"];
23607
+ const results = installToAgents(entry, agentTargets);
23608
+ return {
23609
+ content: [{ type: "text", text: JSON.stringify(results, null, 2) }]
23610
+ };
23611
+ });
23612
+ server.tool("list_awesome_servers", "List all MCP servers from the curated punkpeye/awesome-mcp-servers GitHub list", {}, async () => {
23613
+ const results = await listAwesomeServers();
23614
+ return {
23615
+ content: [{ type: "text", text: JSON.stringify(results, null, 2) }]
22780
23616
  };
22781
23617
  });
22782
23618
  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();
23619
+ let tools = [];
23620
+ try {
23621
+ await connectAllEnabled();
23622
+ tools = listAllTools();
23623
+ } finally {
23624
+ await disconnectAll().catch(() => {
23625
+ return;
23626
+ });
23627
+ }
22785
23628
  return {
22786
23629
  content: [{ type: "text", text: JSON.stringify(tools, null, 2) }]
22787
23630
  };
@@ -22791,6 +23634,28 @@ server.tool("call_upstream_tool", `Call a tool on a connected upstream MCP serve
22791
23634
  arguments: exports_external.record(exports_external.unknown()).optional().describe("Tool arguments as key-value pairs")
22792
23635
  }, async ({ tool_name, arguments: args }) => {
22793
23636
  try {
23637
+ const sepIdx = tool_name.indexOf(TOOL_PREFIX_SEPARATOR);
23638
+ if (sepIdx === -1) {
23639
+ return {
23640
+ content: [{ type: "text", text: `Error: Invalid tool name "${tool_name}"` }],
23641
+ isError: true
23642
+ };
23643
+ }
23644
+ const serverId = tool_name.slice(0, sepIdx);
23645
+ const entry = getServer(serverId);
23646
+ if (!entry) {
23647
+ return {
23648
+ content: [{ type: "text", text: `Error: Server "${serverId}" not found.` }],
23649
+ isError: true
23650
+ };
23651
+ }
23652
+ if (!entry.enabled) {
23653
+ return {
23654
+ content: [{ type: "text", text: `Error: Server "${serverId}" is disabled.` }],
23655
+ isError: true
23656
+ };
23657
+ }
23658
+ await connectToServer(entry);
22794
23659
  const result = await callTool(tool_name, args || {});
22795
23660
  return { content: result.content };
22796
23661
  } catch (err) {
@@ -22800,6 +23665,13 @@ server.tool("call_upstream_tool", `Call a tool on a connected upstream MCP serve
22800
23665
  };
22801
23666
  }
22802
23667
  });
23668
+ server.tool("diagnose_server", "Run health checks on a registered MCP server", { id: exports_external.string().describe("Server ID") }, async ({ id }) => {
23669
+ const entry = getServer(id);
23670
+ if (!entry)
23671
+ return { content: [{ type: "text", text: `Server "${id}" not found.` }], isError: true };
23672
+ const report = await diagnoseServer(entry);
23673
+ return { content: [{ type: "text", text: JSON.stringify(report, null, 2) }] };
23674
+ });
22803
23675
  async function startMcpServer() {
22804
23676
  const transport = new StdioServerTransport;
22805
23677
  await server.connect(transport);