@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/index.js +2269 -232
- package/bin/mcp.js +976 -104
- package/dashboard/dist/assets/index-Df11SKJo.css +1 -0
- package/dashboard/dist/assets/index-a3768emB.js +264 -0
- package/dashboard/dist/index.html +2 -2
- package/dist/index.d.ts +10 -2
- package/dist/index.js +852 -154
- package/dist/lib/doctor.d.ts +14 -0
- package/dist/lib/finder.d.ts +8 -0
- package/dist/lib/install.d.ts +8 -0
- package/dist/lib/registry.d.ts +4 -0
- package/dist/lib/sources.d.ts +18 -0
- package/dist/server/serve.d.ts +1 -0
- package/dist/types.d.ts +38 -1
- package/package.json +1 -1
- package/dashboard/dist/assets/index-Bl3d9_Ks.css +0 -1
- package/dashboard/dist/assets/index-KmgzJKQR.js +0 -224
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
|
|
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/
|
|
20027
|
-
import {
|
|
20028
|
-
import {
|
|
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:
|
|
20087
|
-
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
|
|
20102
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
22624
|
-
|
|
22625
|
-
|
|
22626
|
-
|
|
22627
|
-
|
|
22628
|
-
|
|
22629
|
-
|
|
22630
|
-
|
|
22631
|
-
|
|
22632
|
-
|
|
22633
|
-
|
|
22634
|
-
|
|
22635
|
-
|
|
22636
|
-
|
|
22637
|
-
|
|
22638
|
-
|
|
22639
|
-
|
|
22640
|
-
|
|
22641
|
-
|
|
22642
|
-
|
|
22643
|
-
|
|
22644
|
-
|
|
22645
|
-
|
|
22646
|
-
|
|
22647
|
-
|
|
22648
|
-
|
|
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
|
-
|
|
22652
|
-
|
|
22653
|
-
|
|
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
|
|
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 =
|
|
22673
|
-
const toolName =
|
|
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
|
-
|
|
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
|
|
22692
|
-
|
|
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
|
-
|
|
23343
|
+
checks4.push({ name: "URL reachable", pass: false, message: `unreachable: ${err.message}` });
|
|
22695
23344
|
}
|
|
22696
23345
|
}
|
|
22697
|
-
|
|
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:
|
|
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
|
-
|
|
22784
|
-
|
|
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);
|