@yawlabs/mcp 0.58.4 → 0.59.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -4
- package/dist/index.js +800 -299
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -302,7 +302,7 @@ function unionBlocked(files) {
|
|
|
302
302
|
}
|
|
303
303
|
return touched ? [...set] : void 0;
|
|
304
304
|
}
|
|
305
|
-
async function
|
|
305
|
+
async function loadYawMcpConfig(opts = {}) {
|
|
306
306
|
const cwd = resolve(opts.cwd ?? process.cwd());
|
|
307
307
|
const home = resolve(opts.home ?? homedir());
|
|
308
308
|
const env = opts.env ?? process.env;
|
|
@@ -390,7 +390,7 @@ function toProfile(config) {
|
|
|
390
390
|
return result;
|
|
391
391
|
}
|
|
392
392
|
async function loadEffectiveProfile(cwd, home) {
|
|
393
|
-
const config = await
|
|
393
|
+
const config = await loadYawMcpConfig({ cwd, home });
|
|
394
394
|
return toProfile(config);
|
|
395
395
|
}
|
|
396
396
|
function isAllowed(rules, namespace) {
|
|
@@ -542,7 +542,7 @@ async function runBundlesCommand(opts = {}) {
|
|
|
542
542
|
}
|
|
543
543
|
return { exitCode: 0, lines };
|
|
544
544
|
}
|
|
545
|
-
const config = await
|
|
545
|
+
const config = await loadYawMcpConfig({
|
|
546
546
|
cwd: opts.cwd,
|
|
547
547
|
home: opts.home,
|
|
548
548
|
env: opts.env
|
|
@@ -636,13 +636,15 @@ var COMPLETION_USAGE = `Usage: yaw-mcp completion <bash|zsh|fish|powershell>
|
|
|
636
636
|
location for your shell:
|
|
637
637
|
|
|
638
638
|
bash yaw-mcp completion bash > ~/.local/share/bash-completion/completions/yaw-mcp
|
|
639
|
-
zsh yaw-mcp completion zsh > "\${fpath[1]}/
|
|
639
|
+
zsh yaw-mcp completion zsh > "\${fpath[1]}/_yaw-mcp" (must be on $fpath)
|
|
640
640
|
fish yaw-mcp completion fish > ~/.config/fish/completions/yaw-mcp.fish
|
|
641
641
|
powershell yaw-mcp completion powershell >> $PROFILE`;
|
|
642
642
|
var INSTALL_CLIENTS = ["claude-code", "claude-desktop", "cursor", "vscode"];
|
|
643
643
|
var SUBCOMMAND_SPEC = [
|
|
644
|
+
// Setup -- connect a client to yaw-mcp.
|
|
644
645
|
{
|
|
645
646
|
name: "install",
|
|
647
|
+
description: "Connect an MCP client to yaw-mcp",
|
|
646
648
|
positional: [...INSTALL_CLIENTS],
|
|
647
649
|
flags: [
|
|
648
650
|
"--scope",
|
|
@@ -657,14 +659,59 @@ var SUBCOMMAND_SPEC = [
|
|
|
657
659
|
"--all"
|
|
658
660
|
]
|
|
659
661
|
},
|
|
660
|
-
|
|
661
|
-
{
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
{ name: "
|
|
662
|
+
// Local servers -- manage ~/.yaw-mcp/bundles.json (no account).
|
|
663
|
+
{
|
|
664
|
+
name: "add",
|
|
665
|
+
description: "Add a catalog server to bundles.json",
|
|
666
|
+
positional: ["<slug>"],
|
|
667
|
+
flags: ["--env", "--dry-run", "--json", "--catalog", "--help"]
|
|
668
|
+
},
|
|
669
|
+
{ name: "remove", description: "Remove a local server", positional: ["<slug-or-namespace>"], flags: ["--help"] },
|
|
670
|
+
{ name: "list", description: "List the servers yaw-mcp loads locally", flags: ["--json", "--help"] },
|
|
671
|
+
{
|
|
672
|
+
name: "try",
|
|
673
|
+
description: "Wire a one-off trial of a catalog server",
|
|
674
|
+
positional: ["<slug>"],
|
|
675
|
+
flags: ["--client", "--ttl", "--env", "--dry-run", "--base", "--help"]
|
|
676
|
+
},
|
|
677
|
+
{ name: "try-cleanup", description: "Remove a wired trial", positional: ["<slug>"], flags: ["--base", "--help"] },
|
|
678
|
+
// Inspection.
|
|
679
|
+
{ name: "doctor", description: "Print diagnostic of yaw-mcp setup", flags: ["--json", "--help"] },
|
|
680
|
+
{ name: "servers", description: "List servers in your yaw.sh/mcp dashboard", flags: ["--json", "--help"] },
|
|
681
|
+
{
|
|
682
|
+
name: "bundles",
|
|
683
|
+
description: "Browse curated multi-server bundles",
|
|
684
|
+
positional: ["list", "match"],
|
|
685
|
+
flags: ["--json", "--help"]
|
|
686
|
+
},
|
|
687
|
+
// Maintenance.
|
|
688
|
+
{ name: "upgrade", description: "Upgrade @yawlabs/mcp to the latest version", flags: ["--run", "--json", "--help"] },
|
|
689
|
+
{ name: "reset-learning", description: "Clear cross-session learning history", flags: ["--help"] },
|
|
690
|
+
{
|
|
691
|
+
name: "completion",
|
|
692
|
+
description: "Print a shell completion script",
|
|
693
|
+
positional: ["bash", "zsh", "fish", "powershell"],
|
|
694
|
+
flags: ["--help"]
|
|
695
|
+
},
|
|
696
|
+
// Account / sync (Pro + Team).
|
|
697
|
+
{ name: "login", description: "Authenticate with a Yaw MCP account", flags: ["--key", "--json", "--help"] },
|
|
698
|
+
{ name: "logout", description: "Sign out of your account", flags: ["--json", "--help"] },
|
|
699
|
+
{
|
|
700
|
+
name: "sync",
|
|
701
|
+
description: "Sync bundles across machines",
|
|
702
|
+
positional: ["push", "pull", "status"],
|
|
703
|
+
flags: ["--key", "--json", "--help"]
|
|
704
|
+
},
|
|
705
|
+
{ name: "stats", description: "Show usage statistics", flags: ["--key", "--limit", "--days", "--json", "--help"] },
|
|
706
|
+
{
|
|
707
|
+
name: "secrets",
|
|
708
|
+
description: "Manage stored secrets",
|
|
709
|
+
positional: ["set", "get", "list", "remove", "lock", "push", "pull"],
|
|
710
|
+
flags: ["--key", "--value", "--stdin", "--json", "--help"]
|
|
711
|
+
},
|
|
712
|
+
// Other.
|
|
713
|
+
{ name: "compliance", description: "Run the compliance suite against a server", flags: ["--publish", "--help"] },
|
|
714
|
+
{ name: "help", description: "Show usage", flags: [] }
|
|
668
715
|
];
|
|
669
716
|
function parseCompletionArgs(argv) {
|
|
670
717
|
if (argv.includes("--help") || argv.includes("-h")) {
|
|
@@ -737,7 +784,7 @@ ${posClause}
|
|
|
737
784
|
return `# bash completion for yaw-mcp \u2014 generated by \`yaw-mcp completion bash\`
|
|
738
785
|
# Install: save this to ~/.local/share/bash-completion/completions/yaw-mcp
|
|
739
786
|
# or source it from your .bashrc.
|
|
740
|
-
|
|
787
|
+
_yaw-mcp() {
|
|
741
788
|
local cur prev words cword
|
|
742
789
|
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
743
790
|
cword=$COMP_CWORD
|
|
@@ -751,24 +798,11 @@ _mcph() {
|
|
|
751
798
|
${cases}
|
|
752
799
|
esac
|
|
753
800
|
}
|
|
754
|
-
complete -F
|
|
801
|
+
complete -F _yaw-mcp yaw-mcp
|
|
755
802
|
`;
|
|
756
803
|
}
|
|
757
804
|
function renderZsh() {
|
|
758
|
-
const
|
|
759
|
-
install: "Auto-edit an MCP client's config",
|
|
760
|
-
doctor: "Print diagnostic of yaw-mcp setup",
|
|
761
|
-
servers: "List servers in your yaw.sh/mcp dashboard",
|
|
762
|
-
bundles: "Browse curated multi-server bundles",
|
|
763
|
-
compliance: "Run the compliance suite against a server",
|
|
764
|
-
"reset-learning": "Clear cross-session learning history",
|
|
765
|
-
completion: "Print a shell completion script",
|
|
766
|
-
upgrade: "Upgrade @yawlabs/mcp to the latest version",
|
|
767
|
-
help: "Show usage"
|
|
768
|
-
};
|
|
769
|
-
const subcommandList = SUBCOMMAND_SPEC.map((s) => ` '${s.name}:${subcommandDescriptions[s.name] ?? ""}'`).join(
|
|
770
|
-
"\n"
|
|
771
|
-
);
|
|
805
|
+
const subcommandList = SUBCOMMAND_SPEC.map((s) => ` '${s.name}:${s.description}'`).join("\n");
|
|
772
806
|
const argsCases = SUBCOMMAND_SPEC.map((spec) => {
|
|
773
807
|
const lines = [` ${spec.name})`];
|
|
774
808
|
if (spec.positional) {
|
|
@@ -781,10 +815,10 @@ function renderZsh() {
|
|
|
781
815
|
}).join("\n");
|
|
782
816
|
return `#compdef yaw-mcp
|
|
783
817
|
# zsh completion for yaw-mcp \u2014 generated by \`yaw-mcp completion zsh\`
|
|
784
|
-
# Install: save this to a file on your $fpath named
|
|
785
|
-
# (e.g., ~/.zsh/completions/
|
|
818
|
+
# Install: save this to a file on your $fpath named _yaw-mcp
|
|
819
|
+
# (e.g., ~/.zsh/completions/_yaw-mcp), then rebuild completions:
|
|
786
820
|
# autoload -U compinit && compinit
|
|
787
|
-
|
|
821
|
+
_yaw-mcp() {
|
|
788
822
|
local context state line
|
|
789
823
|
_arguments -C \\
|
|
790
824
|
'1: :->cmd' \\
|
|
@@ -802,7 +836,7 @@ ${argsCases}
|
|
|
802
836
|
;;
|
|
803
837
|
esac
|
|
804
838
|
}
|
|
805
|
-
|
|
839
|
+
_yaw-mcp "$@"
|
|
806
840
|
`;
|
|
807
841
|
}
|
|
808
842
|
function renderFish() {
|
|
@@ -821,7 +855,8 @@ complete -c yaw-mcp -f`;
|
|
|
821
855
|
}
|
|
822
856
|
}
|
|
823
857
|
for (const f of spec.flags) {
|
|
824
|
-
|
|
858
|
+
if (!f.startsWith("--")) continue;
|
|
859
|
+
const long = f.slice(2);
|
|
825
860
|
flagLines.push(`complete -c yaw-mcp -n "__fish_seen_subcommand_from ${spec.name}" -l ${long}`);
|
|
826
861
|
}
|
|
827
862
|
}
|
|
@@ -1457,9 +1492,12 @@ function buildLaunchEntry(opts) {
|
|
|
1457
1492
|
if (opts.token) entry.env = { YAW_MCP_TOKEN: opts.token };
|
|
1458
1493
|
return entry;
|
|
1459
1494
|
}
|
|
1460
|
-
var ENTRY_NAME = "
|
|
1461
|
-
var
|
|
1462
|
-
|
|
1495
|
+
var ENTRY_NAME = "mcp";
|
|
1496
|
+
var LEGACY_ENTRY_NAMES = ["mcp.hosting", "mcph", "yaw-mcp"];
|
|
1497
|
+
function findLegacyEntry(container) {
|
|
1498
|
+
return LEGACY_ENTRY_NAMES.find((n) => n in container) ?? null;
|
|
1499
|
+
}
|
|
1500
|
+
var CLAUDE_CODE_ALLOW_PATTERN = "mcp__mcp__*";
|
|
1463
1501
|
function resolveClaudeCodeSettingsPath(scope, opts) {
|
|
1464
1502
|
const { home, projectDir, claudeConfigDir } = opts;
|
|
1465
1503
|
const cfgDir = claudeConfigDir && claudeConfigDir.length > 0 ? claudeConfigDir : null;
|
|
@@ -1599,6 +1637,101 @@ import { homedir as homedir4, hostname, userInfo } from "os";
|
|
|
1599
1637
|
import { join as join5, resolve as resolve3 } from "path";
|
|
1600
1638
|
import { request as request5 } from "undici";
|
|
1601
1639
|
|
|
1640
|
+
// src/catalog.ts
|
|
1641
|
+
var DEFAULT_CATALOG_URL = "https://yaw.sh/data/mcp-catalog.json";
|
|
1642
|
+
var FETCH_TIMEOUT_MS = 1e4;
|
|
1643
|
+
var ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
1644
|
+
function tokenizeCommand(cmd) {
|
|
1645
|
+
const out = [];
|
|
1646
|
+
let cur = "";
|
|
1647
|
+
let has = false;
|
|
1648
|
+
let quote = null;
|
|
1649
|
+
for (const ch of cmd) {
|
|
1650
|
+
if (quote) {
|
|
1651
|
+
if (ch === quote) quote = null;
|
|
1652
|
+
else cur += ch;
|
|
1653
|
+
has = true;
|
|
1654
|
+
} else if (ch === '"' || ch === "'") {
|
|
1655
|
+
quote = ch;
|
|
1656
|
+
has = true;
|
|
1657
|
+
} else if (ch === " " || ch === " " || ch === "\n" || ch === "\r") {
|
|
1658
|
+
if (has) {
|
|
1659
|
+
out.push(cur);
|
|
1660
|
+
cur = "";
|
|
1661
|
+
has = false;
|
|
1662
|
+
}
|
|
1663
|
+
} else {
|
|
1664
|
+
cur += ch;
|
|
1665
|
+
has = true;
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
if (has) out.push(cur);
|
|
1669
|
+
return out;
|
|
1670
|
+
}
|
|
1671
|
+
async function defaultFetchCatalog(url = DEFAULT_CATALOG_URL) {
|
|
1672
|
+
const ac = new AbortController();
|
|
1673
|
+
const timer = setTimeout(() => ac.abort(), FETCH_TIMEOUT_MS);
|
|
1674
|
+
let body;
|
|
1675
|
+
try {
|
|
1676
|
+
const res = await fetch(url, { signal: ac.signal, headers: { accept: "application/json" } });
|
|
1677
|
+
if (!res.ok) {
|
|
1678
|
+
throw new Error(`the Yaw MCP catalog at ${url} returned HTTP ${res.status}.`);
|
|
1679
|
+
}
|
|
1680
|
+
body = await res.json();
|
|
1681
|
+
} catch (err) {
|
|
1682
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
1683
|
+
throw new Error(`timed out fetching the Yaw MCP catalog at ${url}.`);
|
|
1684
|
+
}
|
|
1685
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
1686
|
+
} finally {
|
|
1687
|
+
clearTimeout(timer);
|
|
1688
|
+
}
|
|
1689
|
+
const servers = body?.servers;
|
|
1690
|
+
if (!Array.isArray(servers)) {
|
|
1691
|
+
throw new Error(`the Yaw MCP catalog at ${url} was not in the expected shape.`);
|
|
1692
|
+
}
|
|
1693
|
+
return servers.filter(
|
|
1694
|
+
(s) => typeof s === "object" && s !== null && typeof s.slug === "string"
|
|
1695
|
+
);
|
|
1696
|
+
}
|
|
1697
|
+
async function resolveCatalogSlug(slug, opts = {}) {
|
|
1698
|
+
const url = opts.catalogUrl ?? DEFAULT_CATALOG_URL;
|
|
1699
|
+
const fetchCatalog = opts.fetchCatalog ?? defaultFetchCatalog;
|
|
1700
|
+
const servers = await fetchCatalog(url);
|
|
1701
|
+
const entry = servers.find((s) => s.slug === slug);
|
|
1702
|
+
if (!entry) {
|
|
1703
|
+
throw new Error(
|
|
1704
|
+
`no server with slug "${slug}" in the Yaw MCP catalog. Browse https://yaw.sh/mcp/catalog/ for the list.`
|
|
1705
|
+
);
|
|
1706
|
+
}
|
|
1707
|
+
const install = entry.install ?? {};
|
|
1708
|
+
const runtime = typeof install.runtime === "string" ? install.runtime.toLowerCase() : "";
|
|
1709
|
+
if (install.url || install.type === "remote" || /^(remote|https?|sse|url)$/.test(runtime)) {
|
|
1710
|
+
throw new Error(`"${slug}" is a remote server -- add it from the Yaw MCP dashboard, not the local CLI.`);
|
|
1711
|
+
}
|
|
1712
|
+
const cmdStr = typeof install.command === "string" ? install.command.trim() : "";
|
|
1713
|
+
if (!cmdStr) {
|
|
1714
|
+
throw new Error(`catalog entry "${slug}" has no install command.`);
|
|
1715
|
+
}
|
|
1716
|
+
const tokens = tokenizeCommand(cmdStr);
|
|
1717
|
+
if (tokens.length === 0) {
|
|
1718
|
+
throw new Error(`catalog entry "${slug}" install command was empty.`);
|
|
1719
|
+
}
|
|
1720
|
+
const [command, ...args] = tokens;
|
|
1721
|
+
const requiredEnvKeys = Array.isArray(entry.requiredEnv) ? entry.requiredEnv.map((e) => e && typeof e === "object" ? e.key : void 0).filter((k) => typeof k === "string" && ENV_KEY_RE.test(k)) : [];
|
|
1722
|
+
const source = typeof entry.repo === "string" ? entry.repo : typeof entry.homepage === "string" ? entry.homepage : void 0;
|
|
1723
|
+
return {
|
|
1724
|
+
slug,
|
|
1725
|
+
name: typeof entry.name === "string" && entry.name.trim() ? entry.name.trim() : slug,
|
|
1726
|
+
command,
|
|
1727
|
+
args,
|
|
1728
|
+
requiredEnvKeys,
|
|
1729
|
+
description: typeof entry.description === "string" ? entry.description : void 0,
|
|
1730
|
+
source,
|
|
1731
|
+
docUrl: source
|
|
1732
|
+
};
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1602
1735
|
// src/install-cmd.ts
|
|
1603
1736
|
import { existsSync } from "fs";
|
|
1604
1737
|
import { chmod, readFile as readFile3 } from "fs/promises";
|
|
@@ -1675,7 +1808,7 @@ ${USAGE}`);
|
|
|
1675
1808
|
log2(`File: ${resolved.absolute}`);
|
|
1676
1809
|
let token5 = opts.token ?? null;
|
|
1677
1810
|
if (!token5) {
|
|
1678
|
-
const cfg = await
|
|
1811
|
+
const cfg = await loadYawMcpConfig({ home: opts.home, cwd: process.cwd(), env: {} });
|
|
1679
1812
|
token5 = cfg.token;
|
|
1680
1813
|
}
|
|
1681
1814
|
if (!token5) {
|
|
@@ -1687,7 +1820,7 @@ ${USAGE}`);
|
|
|
1687
1820
|
const containerPath = resolved.containerPath;
|
|
1688
1821
|
let existing = {};
|
|
1689
1822
|
let existingHasEntry = false;
|
|
1690
|
-
let
|
|
1823
|
+
let legacyEntry = null;
|
|
1691
1824
|
if (existsSync(resolved.absolute)) {
|
|
1692
1825
|
let raw;
|
|
1693
1826
|
try {
|
|
@@ -1717,7 +1850,7 @@ ${USAGE}`);
|
|
|
1717
1850
|
if (typeof container === "object" && container !== null && !Array.isArray(container)) {
|
|
1718
1851
|
const c = container;
|
|
1719
1852
|
existingHasEntry = ENTRY_NAME in c;
|
|
1720
|
-
|
|
1853
|
+
legacyEntry = findLegacyEntry(c);
|
|
1721
1854
|
}
|
|
1722
1855
|
}
|
|
1723
1856
|
if (existingHasEntry) {
|
|
@@ -1747,16 +1880,16 @@ ${USAGE}`);
|
|
|
1747
1880
|
const merged = mergeClientConfig(existing, containerPath, newEntry);
|
|
1748
1881
|
const clientJson = `${JSON.stringify(merged, null, 2)}
|
|
1749
1882
|
`;
|
|
1750
|
-
const
|
|
1883
|
+
const writeYawMcpConfig = !opts.skipYawMcpConfig && token5 !== null;
|
|
1751
1884
|
const home = opts.home ?? homedir3();
|
|
1752
|
-
const
|
|
1753
|
-
const
|
|
1754
|
-
if ("backupPath" in
|
|
1885
|
+
const yawMcpConfigPath = join4(home, CONFIG_DIRNAME, CONFIG_FILENAME);
|
|
1886
|
+
const yawMcpConfigComposed = writeYawMcpConfig ? await composeYawMcpConfig(yawMcpConfigPath, token5) : { json: "" };
|
|
1887
|
+
if ("backupPath" in yawMcpConfigComposed && yawMcpConfigComposed.backupPath) {
|
|
1755
1888
|
log2(
|
|
1756
|
-
`yaw-mcp install: existing ${
|
|
1889
|
+
`yaw-mcp install: existing ${yawMcpConfigPath} was malformed; original bytes backed up to ${yawMcpConfigComposed.backupPath} before overwriting.`
|
|
1757
1890
|
);
|
|
1758
1891
|
}
|
|
1759
|
-
const
|
|
1892
|
+
const yawMcpConfigJson = yawMcpConfigComposed.json;
|
|
1760
1893
|
const settingsPatch = opts.clientId === "claude-code" ? await prepareClaudeCodeSettingsPatch({
|
|
1761
1894
|
scope,
|
|
1762
1895
|
home,
|
|
@@ -1766,40 +1899,40 @@ ${USAGE}`);
|
|
|
1766
1899
|
}) : null;
|
|
1767
1900
|
if (opts.dryRun) {
|
|
1768
1901
|
log2("\n--- dry run: would write the following ---");
|
|
1769
|
-
if (
|
|
1770
|
-
${
|
|
1902
|
+
if (writeYawMcpConfig) log2(`# ${yawMcpConfigPath}
|
|
1903
|
+
${yawMcpConfigJson}`);
|
|
1771
1904
|
log2(`
|
|
1772
1905
|
# ${resolved.absolute}
|
|
1773
1906
|
${clientJson}`);
|
|
1774
1907
|
if (settingsPatch?.changed) log2(`# ${settingsPatch.path}
|
|
1775
1908
|
${settingsPatch.nextJson}`);
|
|
1776
|
-
if (
|
|
1909
|
+
if (legacyEntry) {
|
|
1777
1910
|
log2(
|
|
1778
|
-
`Note: legacy "${
|
|
1911
|
+
`Note: legacy "${legacyEntry}" entry at ${resolved.absolute} would remain \u2014 remove it to avoid running yaw-mcp twice.`
|
|
1779
1912
|
);
|
|
1780
1913
|
}
|
|
1781
1914
|
const wouldWrite = [];
|
|
1782
|
-
if (
|
|
1915
|
+
if (writeYawMcpConfig) wouldWrite.push(yawMcpConfigPath);
|
|
1783
1916
|
wouldWrite.push(resolved.absolute);
|
|
1784
1917
|
if (settingsPatch?.changed) wouldWrite.push(settingsPatch.path);
|
|
1785
1918
|
return { written: [], wouldWrite, messages, exitCode: 0 };
|
|
1786
1919
|
}
|
|
1787
1920
|
const written = [];
|
|
1788
|
-
if (
|
|
1921
|
+
if (writeYawMcpConfig) {
|
|
1789
1922
|
try {
|
|
1790
|
-
await atomicWriteFile(
|
|
1923
|
+
await atomicWriteFile(yawMcpConfigPath, yawMcpConfigJson);
|
|
1791
1924
|
if (process.platform !== "win32") {
|
|
1792
1925
|
try {
|
|
1793
|
-
await chmod(
|
|
1926
|
+
await chmod(yawMcpConfigPath, 384);
|
|
1794
1927
|
} catch {
|
|
1795
1928
|
}
|
|
1796
1929
|
}
|
|
1797
1930
|
} catch (e) {
|
|
1798
|
-
err(`yaw-mcp install: failed to write ${
|
|
1931
|
+
err(`yaw-mcp install: failed to write ${yawMcpConfigPath}: ${e.message}`);
|
|
1799
1932
|
return { written: [], wouldWrite: [], messages, exitCode: 1 };
|
|
1800
1933
|
}
|
|
1801
|
-
log2(`Wrote ${
|
|
1802
|
-
written.push(
|
|
1934
|
+
log2(`Wrote ${yawMcpConfigPath}`);
|
|
1935
|
+
written.push(yawMcpConfigPath);
|
|
1803
1936
|
}
|
|
1804
1937
|
try {
|
|
1805
1938
|
await atomicWriteFile(resolved.absolute, clientJson);
|
|
@@ -1821,9 +1954,9 @@ ${settingsPatch.nextJson}`);
|
|
|
1821
1954
|
}
|
|
1822
1955
|
}
|
|
1823
1956
|
if (target.notes) log2(`Note: ${target.notes}`);
|
|
1824
|
-
if (
|
|
1957
|
+
if (legacyEntry) {
|
|
1825
1958
|
log2(
|
|
1826
|
-
`Note: legacy "${
|
|
1959
|
+
`Note: legacy "${legacyEntry}" entry remains at ${resolved.absolute}. Remove it to avoid running yaw-mcp twice.`
|
|
1827
1960
|
);
|
|
1828
1961
|
}
|
|
1829
1962
|
log2(`
|
|
@@ -1861,14 +1994,14 @@ async function prepareClaudeCodeSettingsPatch(opts) {
|
|
|
1861
1994
|
return { path: path3, nextJson: `${JSON.stringify(merged, null, 2)}
|
|
1862
1995
|
`, changed: true };
|
|
1863
1996
|
}
|
|
1864
|
-
var
|
|
1997
|
+
var LEGACY_CLAUDE_CODE_ALLOW_PATTERNS = ["mcp__mcp_hosting__*", "mcp__yaw_mcp__*"];
|
|
1865
1998
|
function mergePermissionsAllow(existing, patterns) {
|
|
1866
1999
|
const out = { ...existing };
|
|
1867
2000
|
const prev = out.permissions;
|
|
1868
2001
|
const perms = typeof prev === "object" && prev !== null && !Array.isArray(prev) ? { ...prev } : {};
|
|
1869
2002
|
const prevAllow = perms.allow;
|
|
1870
2003
|
const allow = Array.isArray(prevAllow) ? prevAllow.filter(
|
|
1871
|
-
(x) => typeof x === "string" && x
|
|
2004
|
+
(x) => typeof x === "string" && !LEGACY_CLAUDE_CODE_ALLOW_PATTERNS.includes(x)
|
|
1872
2005
|
) : [];
|
|
1873
2006
|
for (const p of patterns) {
|
|
1874
2007
|
if (!allow.includes(p)) allow.push(p);
|
|
@@ -1943,7 +2076,7 @@ function removeFromClientConfig(existing, containerPath, entryName) {
|
|
|
1943
2076
|
parent[leafKey] = container;
|
|
1944
2077
|
return out;
|
|
1945
2078
|
}
|
|
1946
|
-
async function
|
|
2079
|
+
async function composeYawMcpConfig(path3, token5) {
|
|
1947
2080
|
let existing = {};
|
|
1948
2081
|
let backupPath;
|
|
1949
2082
|
if (existsSync(path3)) {
|
|
@@ -2019,7 +2152,7 @@ function parseInstallArgs(argv) {
|
|
|
2019
2152
|
opts.dryRun = true;
|
|
2020
2153
|
break;
|
|
2021
2154
|
case "--no-yaw-mcp-config":
|
|
2022
|
-
opts.
|
|
2155
|
+
opts.skipYawMcpConfig = true;
|
|
2023
2156
|
break;
|
|
2024
2157
|
case "--list":
|
|
2025
2158
|
opts.listOnly = true;
|
|
@@ -2070,7 +2203,7 @@ async function runInstallList(opts, log2) {
|
|
|
2070
2203
|
path: displayPath(p.path, home),
|
|
2071
2204
|
status: statusFor(p)
|
|
2072
2205
|
}));
|
|
2073
|
-
const installed = probes.filter((p) => p.
|
|
2206
|
+
const installed = probes.filter((p) => p.hasMcpEntry).length;
|
|
2074
2207
|
const available = probes.filter((p) => !p.unavailable).length;
|
|
2075
2208
|
log2(`${installed}/${available} client scopes have yaw-mcp configured on ${os}.`);
|
|
2076
2209
|
log2("");
|
|
@@ -2095,7 +2228,7 @@ async function runInstallList(opts, log2) {
|
|
|
2095
2228
|
function statusFor(p) {
|
|
2096
2229
|
if (p.unavailable) return "unavailable";
|
|
2097
2230
|
if (p.malformed) return "malformed";
|
|
2098
|
-
if (p.
|
|
2231
|
+
if (p.hasMcpEntry) return "installed";
|
|
2099
2232
|
if (p.exists) return "other-entries";
|
|
2100
2233
|
return "not installed";
|
|
2101
2234
|
}
|
|
@@ -2197,8 +2330,9 @@ var TRY_USAGE = `Usage: yaw-mcp try <slug> [flags]
|
|
|
2197
2330
|
Required env vars not supplied here AND not in your
|
|
2198
2331
|
shell's env block the trial with an explainer.
|
|
2199
2332
|
--dry-run Print what would happen without writing anything.
|
|
2200
|
-
--base <url>
|
|
2201
|
-
$YAW_MCP_BASE_URL or https://yaw.sh/mcp)
|
|
2333
|
+
--base <url> Base URL for the signup/telemetry links (default:
|
|
2334
|
+
$YAW_MCP_BASE_URL or https://yaw.sh/mcp). The catalog
|
|
2335
|
+
itself is set via $YAW_MCP_CATALOG_URL.`;
|
|
2202
2336
|
var TRY_CLEANUP_USAGE = `Usage: yaw-mcp try-cleanup <slug>
|
|
2203
2337
|
|
|
2204
2338
|
Remove a previously-wired trial: peels the yaw-mcp-try-<slug> entry out of
|
|
@@ -2348,44 +2482,16 @@ async function loadOrCreateAnonId(home = homedir4()) {
|
|
|
2348
2482
|
}
|
|
2349
2483
|
return id;
|
|
2350
2484
|
}
|
|
2351
|
-
async function defaultFetchExplore(
|
|
2352
|
-
const
|
|
2353
|
-
const ac = new AbortController();
|
|
2354
|
-
const timer = setTimeout(() => ac.abort(), 1e4);
|
|
2355
|
-
try {
|
|
2356
|
-
const res = await fetch(url, { signal: ac.signal, headers: { accept: "application/json" } });
|
|
2357
|
-
if (res.status === 404) {
|
|
2358
|
-
throw new Error(`yaw-mcp try: no server with slug "${slug}" \u2014 check ${baseUrl}/explore for the catalog.`);
|
|
2359
|
-
}
|
|
2360
|
-
if (!res.ok) {
|
|
2361
|
-
throw new Error(`yaw-mcp try: ${url} returned HTTP ${res.status}`);
|
|
2362
|
-
}
|
|
2363
|
-
const body = await res.json();
|
|
2364
|
-
return validateExploreResponse(body, slug);
|
|
2365
|
-
} finally {
|
|
2366
|
-
clearTimeout(timer);
|
|
2367
|
-
}
|
|
2368
|
-
}
|
|
2369
|
-
function validateExploreResponse(body, slug) {
|
|
2370
|
-
if (!body || typeof body !== "object") {
|
|
2371
|
-
throw new Error(`yaw-mcp try: /api/explore/${slug} returned a non-object response.`);
|
|
2372
|
-
}
|
|
2373
|
-
const b = body;
|
|
2374
|
-
if (typeof b.slug !== "string" || typeof b.name !== "string" || typeof b.command !== "string") {
|
|
2375
|
-
throw new Error(`yaw-mcp try: /api/explore/${slug} missing required string fields (slug/name/command).`);
|
|
2376
|
-
}
|
|
2377
|
-
if (!Array.isArray(b.args) || !b.args.every((x) => typeof x === "string")) {
|
|
2378
|
-
throw new Error(`yaw-mcp try: /api/explore/${slug} has invalid args (expected string[]).`);
|
|
2379
|
-
}
|
|
2380
|
-
const req = Array.isArray(b.requiredEnvVars) ? b.requiredEnvVars.filter((x) => typeof x === "string") : [];
|
|
2485
|
+
async function defaultFetchExplore(_baseUrl, slug) {
|
|
2486
|
+
const resolved = await resolveCatalogSlug(slug, { catalogUrl: process.env.YAW_MCP_CATALOG_URL });
|
|
2381
2487
|
const out = {
|
|
2382
|
-
slug:
|
|
2383
|
-
name:
|
|
2384
|
-
command:
|
|
2385
|
-
args:
|
|
2386
|
-
requiredEnvVars:
|
|
2488
|
+
slug: resolved.slug,
|
|
2489
|
+
name: resolved.name,
|
|
2490
|
+
command: resolved.command,
|
|
2491
|
+
args: resolved.args,
|
|
2492
|
+
requiredEnvVars: resolved.requiredEnvKeys
|
|
2387
2493
|
};
|
|
2388
|
-
if (
|
|
2494
|
+
if (resolved.docUrl) out.docUrl = resolved.docUrl;
|
|
2389
2495
|
return out;
|
|
2390
2496
|
}
|
|
2391
2497
|
async function defaultPostEvent(baseUrl, body) {
|
|
@@ -2771,7 +2877,7 @@ function selectFlakyNamespaces(entries, limit) {
|
|
|
2771
2877
|
}
|
|
2772
2878
|
|
|
2773
2879
|
// src/doctor-cmd.ts
|
|
2774
|
-
var VERSION = true ? "0.
|
|
2880
|
+
var VERSION = true ? "0.59.0" : "dev";
|
|
2775
2881
|
async function runDoctor(opts = {}) {
|
|
2776
2882
|
if (opts.json) return runDoctorJson(opts);
|
|
2777
2883
|
const lines = [];
|
|
@@ -2789,7 +2895,7 @@ async function runDoctor(opts = {}) {
|
|
|
2789
2895
|
print(`yaw-mcp version: ${VERSION}`);
|
|
2790
2896
|
print(`platform: ${os}`);
|
|
2791
2897
|
print("");
|
|
2792
|
-
const config = await
|
|
2898
|
+
const config = await loadYawMcpConfig({ cwd, home, env });
|
|
2793
2899
|
print("CONFIG FILES");
|
|
2794
2900
|
if (config.loadedFiles.length === 0) {
|
|
2795
2901
|
print(" (none \u2014 using defaults + env)");
|
|
@@ -2874,7 +2980,7 @@ async function runDoctorJson(opts) {
|
|
|
2874
2980
|
const os = opts.os ?? CURRENT_OS;
|
|
2875
2981
|
const env = opts.env ?? process.env;
|
|
2876
2982
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
2877
|
-
const config = await
|
|
2983
|
+
const config = await loadYawMcpConfig({ cwd, home, env });
|
|
2878
2984
|
const claudeConfigDir = env.CLAUDE_CONFIG_DIR && env.CLAUDE_CONFIG_DIR.length > 0 ? env.CLAUDE_CONFIG_DIR : void 0;
|
|
2879
2985
|
const clients = probeClients({ home, os, cwd, claudeConfigDir });
|
|
2880
2986
|
const envVarNames = [
|
|
@@ -3114,12 +3220,12 @@ function schemaSuffix(f) {
|
|
|
3114
3220
|
function renderClientStatus(c, installCmd) {
|
|
3115
3221
|
if (c.unavailable) return "unavailable on this OS";
|
|
3116
3222
|
if (c.malformed) return "exists but JSON is malformed \u2014 fix or rerun `yaw-mcp install`";
|
|
3117
|
-
if (c.
|
|
3118
|
-
return `OK \u2014 has "${ENTRY_NAME}" entry; legacy "${
|
|
3223
|
+
if (c.hasMcpEntry && c.hasLegacyEntry) {
|
|
3224
|
+
return `OK \u2014 has "${ENTRY_NAME}" entry; legacy "${c.legacyEntryName}" entry also present \u2014 remove it to avoid running yaw-mcp twice`;
|
|
3119
3225
|
}
|
|
3120
|
-
if (c.
|
|
3226
|
+
if (c.hasMcpEntry) return `OK \u2014 has "${ENTRY_NAME}" entry`;
|
|
3121
3227
|
if (c.hasLegacyEntry) {
|
|
3122
|
-
return `legacy "${
|
|
3228
|
+
return `legacy "${c.legacyEntryName}" entry present \u2014 run \`${installCmd}\` to migrate, then remove the legacy entry by hand`;
|
|
3123
3229
|
}
|
|
3124
3230
|
if (c.exists) return `present, no "${ENTRY_NAME}" entry \u2014 run \`${installCmd}\``;
|
|
3125
3231
|
return `not configured \u2014 run \`${installCmd}\``;
|
|
@@ -3134,8 +3240,9 @@ function probeClients(opts) {
|
|
|
3134
3240
|
scope: target.scopes[0].scope,
|
|
3135
3241
|
path: "(n/a)",
|
|
3136
3242
|
exists: false,
|
|
3137
|
-
|
|
3243
|
+
hasMcpEntry: false,
|
|
3138
3244
|
hasLegacyEntry: false,
|
|
3245
|
+
legacyEntryName: null,
|
|
3139
3246
|
malformed: false,
|
|
3140
3247
|
unavailable: true
|
|
3141
3248
|
});
|
|
@@ -3156,8 +3263,9 @@ function probeClients(opts) {
|
|
|
3156
3263
|
continue;
|
|
3157
3264
|
}
|
|
3158
3265
|
const exists3 = existsSync3(resolved.absolute);
|
|
3159
|
-
let
|
|
3266
|
+
let hasMcpEntry = false;
|
|
3160
3267
|
let hasLegacyEntry = false;
|
|
3268
|
+
let legacyEntryName = null;
|
|
3161
3269
|
let malformed = false;
|
|
3162
3270
|
if (exists3) {
|
|
3163
3271
|
try {
|
|
@@ -3168,8 +3276,9 @@ function probeClients(opts) {
|
|
|
3168
3276
|
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
3169
3277
|
const container = walkContainer(parsed, resolved.containerPath);
|
|
3170
3278
|
if (container) {
|
|
3171
|
-
|
|
3172
|
-
|
|
3279
|
+
hasMcpEntry = ENTRY_NAME in container;
|
|
3280
|
+
legacyEntryName = findLegacyEntry(container);
|
|
3281
|
+
hasLegacyEntry = legacyEntryName !== null;
|
|
3173
3282
|
}
|
|
3174
3283
|
} else {
|
|
3175
3284
|
malformed = true;
|
|
@@ -3184,8 +3293,9 @@ function probeClients(opts) {
|
|
|
3184
3293
|
scope: scope.scope,
|
|
3185
3294
|
path: resolved.absolute,
|
|
3186
3295
|
exists: exists3,
|
|
3187
|
-
|
|
3296
|
+
hasMcpEntry,
|
|
3188
3297
|
hasLegacyEntry,
|
|
3298
|
+
legacyEntryName,
|
|
3189
3299
|
malformed,
|
|
3190
3300
|
unavailable: false
|
|
3191
3301
|
});
|
|
@@ -3212,8 +3322,9 @@ async function probeClientsAsync(opts) {
|
|
|
3212
3322
|
scope: target.scopes[0].scope,
|
|
3213
3323
|
path: "(n/a)",
|
|
3214
3324
|
exists: false,
|
|
3215
|
-
|
|
3325
|
+
hasMcpEntry: false,
|
|
3216
3326
|
hasLegacyEntry: false,
|
|
3327
|
+
legacyEntryName: null,
|
|
3217
3328
|
malformed: false,
|
|
3218
3329
|
unavailable: true
|
|
3219
3330
|
});
|
|
@@ -3229,8 +3340,9 @@ async function probeClientsAsync(opts) {
|
|
|
3229
3340
|
claudeConfigDir: opts.claudeConfigDir
|
|
3230
3341
|
});
|
|
3231
3342
|
const exists3 = existsSync3(resolved.absolute);
|
|
3232
|
-
let
|
|
3343
|
+
let hasMcpEntry = false;
|
|
3233
3344
|
let hasLegacyEntry = false;
|
|
3345
|
+
let legacyEntryName = null;
|
|
3234
3346
|
let malformed = false;
|
|
3235
3347
|
if (exists3) {
|
|
3236
3348
|
try {
|
|
@@ -3240,8 +3352,9 @@ async function probeClientsAsync(opts) {
|
|
|
3240
3352
|
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
3241
3353
|
const container = walkContainer(parsed, resolved.containerPath);
|
|
3242
3354
|
if (container) {
|
|
3243
|
-
|
|
3244
|
-
|
|
3355
|
+
hasMcpEntry = ENTRY_NAME in container;
|
|
3356
|
+
legacyEntryName = findLegacyEntry(container);
|
|
3357
|
+
hasLegacyEntry = legacyEntryName !== null;
|
|
3245
3358
|
}
|
|
3246
3359
|
} else {
|
|
3247
3360
|
malformed = true;
|
|
@@ -3256,8 +3369,9 @@ async function probeClientsAsync(opts) {
|
|
|
3256
3369
|
scope: scope.scope,
|
|
3257
3370
|
path: resolved.absolute,
|
|
3258
3371
|
exists: exists3,
|
|
3259
|
-
|
|
3372
|
+
hasMcpEntry,
|
|
3260
3373
|
hasLegacyEntry,
|
|
3374
|
+
legacyEntryName,
|
|
3261
3375
|
malformed,
|
|
3262
3376
|
unavailable: false
|
|
3263
3377
|
});
|
|
@@ -3439,6 +3553,466 @@ function closestNames(query, candidates, limit) {
|
|
|
3439
3553
|
return scored.slice(0, limit).map((s) => s.name);
|
|
3440
3554
|
}
|
|
3441
3555
|
|
|
3556
|
+
// src/local-add-cmd.ts
|
|
3557
|
+
import { homedir as homedir7 } from "os";
|
|
3558
|
+
|
|
3559
|
+
// src/local-bundles.ts
|
|
3560
|
+
import { createHash as createHash2 } from "crypto";
|
|
3561
|
+
import { existsSync as existsSync4 } from "fs";
|
|
3562
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
3563
|
+
import { homedir as homedir6 } from "os";
|
|
3564
|
+
import { join as join7 } from "path";
|
|
3565
|
+
var BUNDLES_FILENAME = "bundles.json";
|
|
3566
|
+
var CURRENT_BUNDLES_SCHEMA_VERSION = 1;
|
|
3567
|
+
function localBundlesPath(configDir) {
|
|
3568
|
+
return join7(configDir, BUNDLES_FILENAME);
|
|
3569
|
+
}
|
|
3570
|
+
var NAMESPACE_RE = /^[a-z][a-z0-9_]{0,29}$/;
|
|
3571
|
+
function validateEntry(entry, warnings) {
|
|
3572
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
3573
|
+
warnings.push("bundles.json: skipping non-object server entry");
|
|
3574
|
+
return null;
|
|
3575
|
+
}
|
|
3576
|
+
const e = entry;
|
|
3577
|
+
const namespace = typeof e.namespace === "string" ? e.namespace : "";
|
|
3578
|
+
if (!namespace || !NAMESPACE_RE.test(namespace)) {
|
|
3579
|
+
warnings.push(`bundles.json: skipping server with invalid namespace ${JSON.stringify(namespace)}`);
|
|
3580
|
+
return null;
|
|
3581
|
+
}
|
|
3582
|
+
const name = typeof e.name === "string" && e.name.length > 0 ? e.name : namespace;
|
|
3583
|
+
const type = e.type === "remote" ? "remote" : "local";
|
|
3584
|
+
const transport = e.transport === "streamable-http" || e.transport === "sse" || e.transport === "stdio" ? e.transport : void 0;
|
|
3585
|
+
const command = typeof e.command === "string" ? e.command : void 0;
|
|
3586
|
+
const args = Array.isArray(e.args) ? e.args.filter((a) => typeof a === "string") : void 0;
|
|
3587
|
+
const env = e.env && typeof e.env === "object" && !Array.isArray(e.env) ? Object.fromEntries(
|
|
3588
|
+
Object.entries(e.env).filter(([, v]) => typeof v === "string")
|
|
3589
|
+
) : void 0;
|
|
3590
|
+
const url = typeof e.url === "string" ? e.url : void 0;
|
|
3591
|
+
const description = typeof e.description === "string" ? e.description : void 0;
|
|
3592
|
+
const isActive = e.isActive !== false;
|
|
3593
|
+
const id = typeof e.id === "string" && e.id.length > 0 ? e.id : `local-${namespace}`;
|
|
3594
|
+
return {
|
|
3595
|
+
id,
|
|
3596
|
+
name,
|
|
3597
|
+
namespace,
|
|
3598
|
+
type,
|
|
3599
|
+
transport,
|
|
3600
|
+
command,
|
|
3601
|
+
args,
|
|
3602
|
+
env,
|
|
3603
|
+
url,
|
|
3604
|
+
isActive,
|
|
3605
|
+
description
|
|
3606
|
+
};
|
|
3607
|
+
}
|
|
3608
|
+
async function readBundlesAt(path3, warnings) {
|
|
3609
|
+
let raw;
|
|
3610
|
+
try {
|
|
3611
|
+
raw = await readFile6(path3, "utf8");
|
|
3612
|
+
} catch {
|
|
3613
|
+
return { exists: false, file: null };
|
|
3614
|
+
}
|
|
3615
|
+
let parsed;
|
|
3616
|
+
try {
|
|
3617
|
+
parsed = parseJsonc(raw);
|
|
3618
|
+
} catch (err) {
|
|
3619
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3620
|
+
warnings.push(`${path3}: invalid JSON (${msg}) -- file ignored`);
|
|
3621
|
+
log("warn", "bundles.json is not valid JSON; ignoring", { path: path3, error: msg });
|
|
3622
|
+
return { exists: true, file: null };
|
|
3623
|
+
}
|
|
3624
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
3625
|
+
warnings.push(`${path3}: root must be a JSON object -- file ignored`);
|
|
3626
|
+
return { exists: true, file: null };
|
|
3627
|
+
}
|
|
3628
|
+
const obj = parsed;
|
|
3629
|
+
const version = typeof obj.version === "number" ? obj.version : void 0;
|
|
3630
|
+
if (version !== void 0 && version > CURRENT_BUNDLES_SCHEMA_VERSION) {
|
|
3631
|
+
warnings.push(
|
|
3632
|
+
`${path3}: schema version ${version} is newer than this yaw-mcp (${CURRENT_BUNDLES_SCHEMA_VERSION}); upgrade with \`npm i -g @yawlabs/mcp@latest\`. Loading best-effort.`
|
|
3633
|
+
);
|
|
3634
|
+
}
|
|
3635
|
+
const rawServers = obj.servers;
|
|
3636
|
+
if (!Array.isArray(rawServers)) {
|
|
3637
|
+
warnings.push(`${path3}: 'servers' must be an array -- file ignored`);
|
|
3638
|
+
return { exists: true, file: null };
|
|
3639
|
+
}
|
|
3640
|
+
return {
|
|
3641
|
+
exists: true,
|
|
3642
|
+
file: { version, servers: rawServers }
|
|
3643
|
+
};
|
|
3644
|
+
}
|
|
3645
|
+
function hashContent(servers) {
|
|
3646
|
+
const h = createHash2("sha256");
|
|
3647
|
+
h.update(JSON.stringify(servers));
|
|
3648
|
+
return `local-${h.digest("hex").slice(0, 16)}`;
|
|
3649
|
+
}
|
|
3650
|
+
async function loadLocalBundles(opts = {}) {
|
|
3651
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
3652
|
+
const home = opts.home ?? homedir6();
|
|
3653
|
+
const warnings = [];
|
|
3654
|
+
const projectDir = await findProjectConfigDir(cwd, home).catch(() => null);
|
|
3655
|
+
const projectPath = projectDir ? localBundlesPath(projectDir) : null;
|
|
3656
|
+
const globalPath = localBundlesPath(join7(home, CONFIG_DIRNAME));
|
|
3657
|
+
const projectResult = projectPath ? await readBundlesAt(projectPath, warnings) : { exists: false, file: null };
|
|
3658
|
+
let file;
|
|
3659
|
+
let sourcePath;
|
|
3660
|
+
if (projectResult.exists) {
|
|
3661
|
+
file = projectResult.file;
|
|
3662
|
+
sourcePath = projectPath;
|
|
3663
|
+
} else {
|
|
3664
|
+
const globalResult = await readBundlesAt(globalPath, warnings);
|
|
3665
|
+
file = globalResult.file;
|
|
3666
|
+
sourcePath = globalResult.exists ? globalPath : null;
|
|
3667
|
+
}
|
|
3668
|
+
if (!file) {
|
|
3669
|
+
return { config: null, path: sourcePath, warnings };
|
|
3670
|
+
}
|
|
3671
|
+
const servers = [];
|
|
3672
|
+
for (const raw of file.servers) {
|
|
3673
|
+
const validated = validateEntry(raw, warnings);
|
|
3674
|
+
if (validated) servers.push(validated);
|
|
3675
|
+
}
|
|
3676
|
+
return {
|
|
3677
|
+
config: {
|
|
3678
|
+
servers,
|
|
3679
|
+
configVersion: hashContent(servers)
|
|
3680
|
+
},
|
|
3681
|
+
path: sourcePath,
|
|
3682
|
+
warnings
|
|
3683
|
+
};
|
|
3684
|
+
}
|
|
3685
|
+
function deriveNamespace(name) {
|
|
3686
|
+
let ns = name.toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
3687
|
+
if (ns.length === 0) return "server";
|
|
3688
|
+
if (!/^[a-z]/.test(ns)) ns = `s${ns}`;
|
|
3689
|
+
if (ns.length > 30) ns = ns.slice(0, 30);
|
|
3690
|
+
return ns;
|
|
3691
|
+
}
|
|
3692
|
+
async function readRawUserBundles(home) {
|
|
3693
|
+
const path3 = localBundlesPath(userConfigDir(home));
|
|
3694
|
+
if (!existsSync4(path3)) {
|
|
3695
|
+
return { version: CURRENT_BUNDLES_SCHEMA_VERSION, servers: [] };
|
|
3696
|
+
}
|
|
3697
|
+
const warnings = [];
|
|
3698
|
+
const r = await readBundlesAt(path3, warnings);
|
|
3699
|
+
if (!r.file) {
|
|
3700
|
+
const detail = warnings.length > 0 ? ` (${warnings.join("; ")})` : "";
|
|
3701
|
+
throw new Error(`${path3} is malformed${detail}; fix it by hand before adding servers.`);
|
|
3702
|
+
}
|
|
3703
|
+
return { version: r.file.version ?? CURRENT_BUNDLES_SCHEMA_VERSION, servers: r.file.servers };
|
|
3704
|
+
}
|
|
3705
|
+
async function upsertUserBundle(entry, opts = {}) {
|
|
3706
|
+
const home = opts.home ?? homedir6();
|
|
3707
|
+
const path3 = localBundlesPath(userConfigDir(home));
|
|
3708
|
+
const file = await readRawUserBundles(home);
|
|
3709
|
+
const idx = file.servers.findIndex(
|
|
3710
|
+
(s) => s?.namespace === entry.namespace || entry.name != null && s?.name === entry.name
|
|
3711
|
+
);
|
|
3712
|
+
const replaced = idx >= 0;
|
|
3713
|
+
if (replaced) file.servers[idx] = entry;
|
|
3714
|
+
else file.servers.push(entry);
|
|
3715
|
+
file.version = file.version ?? CURRENT_BUNDLES_SCHEMA_VERSION;
|
|
3716
|
+
await atomicWriteFile(path3, `${JSON.stringify(file, null, 2)}
|
|
3717
|
+
`);
|
|
3718
|
+
return { path: path3, replaced };
|
|
3719
|
+
}
|
|
3720
|
+
async function removeUserBundle(namespace, opts = {}) {
|
|
3721
|
+
const home = opts.home ?? homedir6();
|
|
3722
|
+
const path3 = localBundlesPath(userConfigDir(home));
|
|
3723
|
+
if (!existsSync4(path3)) return { path: path3, removed: false };
|
|
3724
|
+
const file = await readRawUserBundles(home);
|
|
3725
|
+
const before = file.servers.length;
|
|
3726
|
+
file.servers = file.servers.filter((s) => s?.namespace !== namespace);
|
|
3727
|
+
if (file.servers.length === before) return { path: path3, removed: false };
|
|
3728
|
+
file.version = file.version ?? CURRENT_BUNDLES_SCHEMA_VERSION;
|
|
3729
|
+
await atomicWriteFile(path3, `${JSON.stringify(file, null, 2)}
|
|
3730
|
+
`);
|
|
3731
|
+
return { path: path3, removed: true };
|
|
3732
|
+
}
|
|
3733
|
+
async function findShadowingProjectBundles(cwd, home = homedir6()) {
|
|
3734
|
+
const projectDir = await findProjectConfigDir(cwd, home).catch(() => null);
|
|
3735
|
+
if (!projectDir) return null;
|
|
3736
|
+
const projectPath = localBundlesPath(projectDir);
|
|
3737
|
+
return existsSync4(projectPath) ? projectPath : null;
|
|
3738
|
+
}
|
|
3739
|
+
|
|
3740
|
+
// src/local-add-cmd.ts
|
|
3741
|
+
var SLUG_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
3742
|
+
var ADD_USAGE = `Usage: yaw-mcp add <slug> [flags]
|
|
3743
|
+
|
|
3744
|
+
Resolve <slug> from the yaw.sh/mcp catalog and add it to your local
|
|
3745
|
+
~/.yaw-mcp/bundles.json so yaw-mcp loads it (no account needed).
|
|
3746
|
+
|
|
3747
|
+
This is NOT the same as \`yaw-mcp install\` -- install wires the yaw-mcp
|
|
3748
|
+
aggregator into an AI client; add adds an MCP server to yaw-mcp itself.
|
|
3749
|
+
|
|
3750
|
+
--env KEY=value Provide a required env var's value. Repeatable. Required
|
|
3751
|
+
vars not given here AND not in your shell block the add.
|
|
3752
|
+
--dry-run Print what would be written without writing.
|
|
3753
|
+
--json Emit the written entry as JSON (implies success on stdout).
|
|
3754
|
+
--catalog <url> Override the catalog URL (default the public catalog).`;
|
|
3755
|
+
function parseEnvFlag(v, bag) {
|
|
3756
|
+
if (!v || !v.includes("=")) return "--env requires KEY=value";
|
|
3757
|
+
const eq = v.indexOf("=");
|
|
3758
|
+
const key = v.slice(0, eq);
|
|
3759
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) return `--env: invalid KEY "${key}"`;
|
|
3760
|
+
bag[key] = v.slice(eq + 1);
|
|
3761
|
+
return null;
|
|
3762
|
+
}
|
|
3763
|
+
function parseAddArgs(argv) {
|
|
3764
|
+
if (argv.length === 0) return { ok: false, error: ADD_USAGE };
|
|
3765
|
+
const positional = [];
|
|
3766
|
+
const opts = {};
|
|
3767
|
+
const env = {};
|
|
3768
|
+
for (let i = 0; i < argv.length; i++) {
|
|
3769
|
+
const a = argv[i];
|
|
3770
|
+
const next = () => argv[++i];
|
|
3771
|
+
switch (a) {
|
|
3772
|
+
case "--env": {
|
|
3773
|
+
const e = parseEnvFlag(next(), env);
|
|
3774
|
+
if (e) return { ok: false, error: e };
|
|
3775
|
+
break;
|
|
3776
|
+
}
|
|
3777
|
+
case "--dry-run":
|
|
3778
|
+
opts.dryRun = true;
|
|
3779
|
+
break;
|
|
3780
|
+
case "--json":
|
|
3781
|
+
opts.json = true;
|
|
3782
|
+
break;
|
|
3783
|
+
case "--catalog": {
|
|
3784
|
+
const v = next();
|
|
3785
|
+
if (!v) return { ok: false, error: "--catalog requires a URL" };
|
|
3786
|
+
opts.catalogUrl = v;
|
|
3787
|
+
break;
|
|
3788
|
+
}
|
|
3789
|
+
case "-h":
|
|
3790
|
+
case "--help":
|
|
3791
|
+
return { ok: false, error: ADD_USAGE };
|
|
3792
|
+
default:
|
|
3793
|
+
if (a.startsWith("--")) return { ok: false, error: `Unknown flag: ${a}
|
|
3794
|
+
${ADD_USAGE}` };
|
|
3795
|
+
positional.push(a);
|
|
3796
|
+
}
|
|
3797
|
+
}
|
|
3798
|
+
if (positional.length !== 1) {
|
|
3799
|
+
return { ok: false, error: `Expected exactly one server slug, got ${positional.length}.
|
|
3800
|
+
${ADD_USAGE}` };
|
|
3801
|
+
}
|
|
3802
|
+
opts.slug = positional[0];
|
|
3803
|
+
if (Object.keys(env).length > 0) opts.envOverrides = env;
|
|
3804
|
+
return { ok: true, options: opts };
|
|
3805
|
+
}
|
|
3806
|
+
async function runAdd(opts) {
|
|
3807
|
+
const out = opts.out ?? ((s) => process.stdout.write(s));
|
|
3808
|
+
const err = opts.err ?? ((s) => process.stderr.write(s));
|
|
3809
|
+
const print = (s = "") => out(`${s}
|
|
3810
|
+
`);
|
|
3811
|
+
const printErr = (s) => err(`${s}
|
|
3812
|
+
`);
|
|
3813
|
+
if (!opts.slug) {
|
|
3814
|
+
printErr(ADD_USAGE);
|
|
3815
|
+
return { exitCode: 2, written: [] };
|
|
3816
|
+
}
|
|
3817
|
+
const slug = opts.slug;
|
|
3818
|
+
if (!SLUG_RE.test(slug)) {
|
|
3819
|
+
printErr(`yaw-mcp add: invalid slug "${slug}" (lowercase letters, digits, and dashes only).`);
|
|
3820
|
+
return { exitCode: 2, written: [] };
|
|
3821
|
+
}
|
|
3822
|
+
const env = opts.env ?? process.env;
|
|
3823
|
+
const home = opts.home ?? homedir7();
|
|
3824
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
3825
|
+
let server;
|
|
3826
|
+
try {
|
|
3827
|
+
server = await resolveCatalogSlug(slug, {
|
|
3828
|
+
catalogUrl: opts.catalogUrl ?? env.YAW_MCP_CATALOG_URL,
|
|
3829
|
+
fetchCatalog: opts.fetchCatalog
|
|
3830
|
+
});
|
|
3831
|
+
} catch (e) {
|
|
3832
|
+
printErr(`yaw-mcp add: ${e.message}`);
|
|
3833
|
+
return { exitCode: 1, written: [] };
|
|
3834
|
+
}
|
|
3835
|
+
const namespace = deriveNamespace(server.name);
|
|
3836
|
+
const supplied = { ...env, ...opts.envOverrides ?? {} };
|
|
3837
|
+
const missing = server.requiredEnvKeys.filter((k) => !supplied[k] || supplied[k] === "");
|
|
3838
|
+
if (missing.length > 0) {
|
|
3839
|
+
printErr(`yaw-mcp add: ${server.name} needs the following env var(s) before it can run:`);
|
|
3840
|
+
for (const k of missing) printErr(` - ${k}`);
|
|
3841
|
+
printErr("");
|
|
3842
|
+
printErr("Provide them with --env KEY=value (repeatable) or your shell, then re-run:");
|
|
3843
|
+
printErr(` yaw-mcp add ${slug} ${missing.map((k) => `--env ${k}=...`).join(" ")}`);
|
|
3844
|
+
if (server.docUrl) printErr(`Docs: ${server.docUrl}`);
|
|
3845
|
+
return { exitCode: 1, written: [] };
|
|
3846
|
+
}
|
|
3847
|
+
const entryEnv = {};
|
|
3848
|
+
for (const k of server.requiredEnvKeys) entryEnv[k] = "";
|
|
3849
|
+
for (const [k, v] of Object.entries(opts.envOverrides ?? {})) entryEnv[k] = v;
|
|
3850
|
+
const entry = {
|
|
3851
|
+
id: `local-${namespace}`,
|
|
3852
|
+
name: server.name,
|
|
3853
|
+
namespace,
|
|
3854
|
+
type: "local",
|
|
3855
|
+
transport: "stdio",
|
|
3856
|
+
command: server.command,
|
|
3857
|
+
args: server.args,
|
|
3858
|
+
env: Object.keys(entryEnv).length > 0 ? entryEnv : void 0,
|
|
3859
|
+
isActive: true,
|
|
3860
|
+
description: server.description
|
|
3861
|
+
};
|
|
3862
|
+
if (opts.dryRun) {
|
|
3863
|
+
if (opts.json) {
|
|
3864
|
+
print(JSON.stringify({ ok: true, dryRun: true, namespace, entry }, null, 2));
|
|
3865
|
+
} else {
|
|
3866
|
+
print(`yaw-mcp add (dry-run): would write ${server.name} as namespace "${namespace}"`);
|
|
3867
|
+
print(` command: ${entry.command} ${(entry.args ?? []).join(" ")}`);
|
|
3868
|
+
if (entry.env) print(` env keys: ${Object.keys(entry.env).join(", ")}`);
|
|
3869
|
+
}
|
|
3870
|
+
return { exitCode: 0, written: [] };
|
|
3871
|
+
}
|
|
3872
|
+
let res;
|
|
3873
|
+
try {
|
|
3874
|
+
res = await upsertUserBundle(entry, { home });
|
|
3875
|
+
} catch (e) {
|
|
3876
|
+
printErr(`yaw-mcp add: ${e.message}`);
|
|
3877
|
+
return { exitCode: 1, written: [] };
|
|
3878
|
+
}
|
|
3879
|
+
if (opts.json) {
|
|
3880
|
+
print(JSON.stringify({ ok: true, namespace, path: res.path, replaced: res.replaced, entry }, null, 2));
|
|
3881
|
+
} else {
|
|
3882
|
+
print(`${res.replaced ? "Updated" : "Added"} ${server.name} (namespace "${namespace}") in ${res.path}`);
|
|
3883
|
+
print("Restart your MCP client (or yaw-mcp) to pick it up.");
|
|
3884
|
+
}
|
|
3885
|
+
const shadow = await findShadowingProjectBundles(cwd, home).catch(() => null);
|
|
3886
|
+
if (shadow) {
|
|
3887
|
+
printErr(
|
|
3888
|
+
`Note: ${shadow} overrides your user-global bundles.json, so this entry won't load until you add it there or remove that file.`
|
|
3889
|
+
);
|
|
3890
|
+
}
|
|
3891
|
+
return { exitCode: 0, written: [res.path] };
|
|
3892
|
+
}
|
|
3893
|
+
var REMOVE_USAGE = `Usage: yaw-mcp remove <slug-or-namespace>
|
|
3894
|
+
|
|
3895
|
+
Remove a server from your local ~/.yaw-mcp/bundles.json. Accepts either the
|
|
3896
|
+
catalog slug it was added with (e.g. "brave-search") or its namespace as
|
|
3897
|
+
shown by \`yaw-mcp list\` (e.g. "bravesearch"). No-op if it isn't present.`;
|
|
3898
|
+
var REMOVE_TARGET_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/i;
|
|
3899
|
+
function parseRemoveArgs(argv) {
|
|
3900
|
+
if (argv.length === 0) return { ok: false, error: REMOVE_USAGE };
|
|
3901
|
+
const positional = [];
|
|
3902
|
+
for (const a of argv) {
|
|
3903
|
+
if (a === "-h" || a === "--help") return { ok: false, error: REMOVE_USAGE };
|
|
3904
|
+
if (a.startsWith("--")) return { ok: false, error: `Unknown flag: ${a}
|
|
3905
|
+
${REMOVE_USAGE}` };
|
|
3906
|
+
positional.push(a);
|
|
3907
|
+
}
|
|
3908
|
+
if (positional.length !== 1) {
|
|
3909
|
+
return { ok: false, error: `Expected exactly one slug or namespace.
|
|
3910
|
+
${REMOVE_USAGE}` };
|
|
3911
|
+
}
|
|
3912
|
+
return { ok: true, options: { target: positional[0] } };
|
|
3913
|
+
}
|
|
3914
|
+
async function runRemove(opts) {
|
|
3915
|
+
const out = opts.out ?? ((s) => process.stdout.write(s));
|
|
3916
|
+
const err = opts.err ?? ((s) => process.stderr.write(s));
|
|
3917
|
+
const print = (s = "") => out(`${s}
|
|
3918
|
+
`);
|
|
3919
|
+
const printErr = (s) => err(`${s}
|
|
3920
|
+
`);
|
|
3921
|
+
if (!opts.target) {
|
|
3922
|
+
printErr(REMOVE_USAGE);
|
|
3923
|
+
return { exitCode: 2, written: [] };
|
|
3924
|
+
}
|
|
3925
|
+
if (!REMOVE_TARGET_RE.test(opts.target)) {
|
|
3926
|
+
printErr(`yaw-mcp remove: "${opts.target}" isn't a valid slug or namespace.`);
|
|
3927
|
+
return { exitCode: 2, written: [] };
|
|
3928
|
+
}
|
|
3929
|
+
const home = opts.home ?? homedir7();
|
|
3930
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
3931
|
+
const derived = deriveNamespace(opts.target);
|
|
3932
|
+
const candidates = derived === opts.target ? [opts.target] : [opts.target, derived];
|
|
3933
|
+
let res = null;
|
|
3934
|
+
let matched = "";
|
|
3935
|
+
try {
|
|
3936
|
+
for (const ns of candidates) {
|
|
3937
|
+
res = await removeUserBundle(ns, { home });
|
|
3938
|
+
if (res.removed) {
|
|
3939
|
+
matched = ns;
|
|
3940
|
+
break;
|
|
3941
|
+
}
|
|
3942
|
+
}
|
|
3943
|
+
} catch (e) {
|
|
3944
|
+
printErr(`yaw-mcp remove: ${e.message}`);
|
|
3945
|
+
return { exitCode: 1, written: [] };
|
|
3946
|
+
}
|
|
3947
|
+
if (!res || !res.removed) {
|
|
3948
|
+
print(`yaw-mcp remove: no server matching "${opts.target}" in ${res?.path ?? "bundles.json"} (nothing to do).`);
|
|
3949
|
+
const shadow2 = await findShadowingProjectBundles(cwd, home).catch(() => null);
|
|
3950
|
+
if (shadow2) {
|
|
3951
|
+
printErr(
|
|
3952
|
+
`Note: a project-local ${shadow2} is in effect; \`remove\` only manages your user-global bundles.json, so a server defined there must be removed from that file directly.`
|
|
3953
|
+
);
|
|
3954
|
+
}
|
|
3955
|
+
return { exitCode: 0, written: [] };
|
|
3956
|
+
}
|
|
3957
|
+
print(`Removed "${matched}" from ${res.path}. Restart your MCP client to apply.`);
|
|
3958
|
+
const shadow = await findShadowingProjectBundles(cwd, home).catch(() => null);
|
|
3959
|
+
if (shadow) {
|
|
3960
|
+
printErr(
|
|
3961
|
+
`Note: ${shadow} shadows your user-global bundles.json; a server defined there is unaffected by this removal.`
|
|
3962
|
+
);
|
|
3963
|
+
}
|
|
3964
|
+
return { exitCode: 0, written: [res.path] };
|
|
3965
|
+
}
|
|
3966
|
+
var LIST_USAGE = `Usage: yaw-mcp list [--json]
|
|
3967
|
+
|
|
3968
|
+
List the MCP servers yaw-mcp loads locally from bundles.json (the
|
|
3969
|
+
project-local file wins over user-global). --json for machine output.`;
|
|
3970
|
+
function parseListArgs(argv) {
|
|
3971
|
+
const opts = {};
|
|
3972
|
+
for (const a of argv) {
|
|
3973
|
+
if (a === "-h" || a === "--help") return { ok: false, error: LIST_USAGE };
|
|
3974
|
+
if (a === "--json") {
|
|
3975
|
+
opts.json = true;
|
|
3976
|
+
continue;
|
|
3977
|
+
}
|
|
3978
|
+
return { ok: false, error: `Unknown argument: ${a}
|
|
3979
|
+
${LIST_USAGE}` };
|
|
3980
|
+
}
|
|
3981
|
+
return { ok: true, options: opts };
|
|
3982
|
+
}
|
|
3983
|
+
async function runList(opts) {
|
|
3984
|
+
const out = opts.out ?? ((s) => process.stdout.write(s));
|
|
3985
|
+
const print = (s = "") => out(`${s}
|
|
3986
|
+
`);
|
|
3987
|
+
const home = opts.home ?? homedir7();
|
|
3988
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
3989
|
+
const loaded = await loadLocalBundles({ home, cwd });
|
|
3990
|
+
const servers = loaded.config?.servers ?? [];
|
|
3991
|
+
if (opts.json) {
|
|
3992
|
+
print(JSON.stringify({ path: loaded.path, servers }, null, 2));
|
|
3993
|
+
return { exitCode: 0, written: [] };
|
|
3994
|
+
}
|
|
3995
|
+
if (servers.length === 0) {
|
|
3996
|
+
print("No local servers configured. Add one with `yaw-mcp add <slug>`");
|
|
3997
|
+
print("(browse the catalog at https://yaw.sh/mcp/catalog/).");
|
|
3998
|
+
return { exitCode: 0, written: [] };
|
|
3999
|
+
}
|
|
4000
|
+
const rows = [...servers].sort((a, b) => a.namespace.localeCompare(b.namespace));
|
|
4001
|
+
const cols = [
|
|
4002
|
+
["NAMESPACE", (s) => s.namespace],
|
|
4003
|
+
["NAME", (s) => s.name],
|
|
4004
|
+
["STATUS", (s) => s.isActive ? "active" : "disabled"],
|
|
4005
|
+
["LAUNCH", (s) => [s.command, ...s.args ?? []].filter(Boolean).join(" ") || s.url || ""]
|
|
4006
|
+
];
|
|
4007
|
+
const widths = cols.map(([h, get]) => Math.max(h.length, ...rows.map((r) => get(r).length)));
|
|
4008
|
+
const fmt = (cells) => cells.map((c, i) => c.padEnd(widths[i])).join(" ").trimEnd();
|
|
4009
|
+
print(fmt(cols.map(([h]) => h)));
|
|
4010
|
+
for (const r of rows) print(fmt(cols.map(([, get]) => get(r))));
|
|
4011
|
+
if (loaded.path) print(`
|
|
4012
|
+
${servers.length} server${servers.length === 1 ? "" : "s"} in ${loaded.path}`);
|
|
4013
|
+
return { exitCode: 0, written: [] };
|
|
4014
|
+
}
|
|
4015
|
+
|
|
3442
4016
|
// src/login-cmd.ts
|
|
3443
4017
|
var LOGIN_USAGE = `Usage: yaw-mcp login --key <license-key>
|
|
3444
4018
|
|
|
@@ -3557,15 +4131,18 @@ async function runLogout(opts = {}, io = {
|
|
|
3557
4131
|
}
|
|
3558
4132
|
|
|
3559
4133
|
// src/nag.ts
|
|
3560
|
-
import { readFile as
|
|
3561
|
-
import { homedir as
|
|
3562
|
-
import { join as
|
|
4134
|
+
import { readFile as readFile7 } from "fs/promises";
|
|
4135
|
+
import { homedir as homedir8 } from "os";
|
|
4136
|
+
import { join as join8 } from "path";
|
|
3563
4137
|
var NAG_STATE_FILENAME = "nag-state.json";
|
|
3564
4138
|
var MIN_THRESHOLD = 2;
|
|
3565
4139
|
var MAX_THRESHOLD = 4;
|
|
3566
4140
|
var FLOOR_MS = 36 * 60 * 60 * 1e3;
|
|
3567
4141
|
var NAG_ELIGIBLE_SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
3568
4142
|
"install",
|
|
4143
|
+
"add",
|
|
4144
|
+
"remove",
|
|
4145
|
+
"list",
|
|
3569
4146
|
"doctor",
|
|
3570
4147
|
"servers",
|
|
3571
4148
|
"bundles",
|
|
@@ -3583,15 +4160,15 @@ var NAG_ELIGIBLE_SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
|
3583
4160
|
function emptyNagState() {
|
|
3584
4161
|
return { touchPoints: 0, nextThreshold: MIN_THRESHOLD, lastShownAt: 0 };
|
|
3585
4162
|
}
|
|
3586
|
-
function nagStatePath(home =
|
|
3587
|
-
return
|
|
4163
|
+
function nagStatePath(home = homedir8()) {
|
|
4164
|
+
return join8(home, CONFIG_DIRNAME, NAG_STATE_FILENAME);
|
|
3588
4165
|
}
|
|
3589
4166
|
function isFileNotFound2(err) {
|
|
3590
4167
|
return !!err && typeof err === "object" && "code" in err && err.code === "ENOENT";
|
|
3591
4168
|
}
|
|
3592
4169
|
async function loadNagState(filePath = nagStatePath()) {
|
|
3593
4170
|
try {
|
|
3594
|
-
const raw = await
|
|
4171
|
+
const raw = await readFile7(filePath, "utf8");
|
|
3595
4172
|
const parsed = JSON.parse(raw);
|
|
3596
4173
|
if (!parsed || typeof parsed !== "object") return emptyNagState();
|
|
3597
4174
|
const p = parsed;
|
|
@@ -3691,8 +4268,8 @@ async function showNagInterstitial(opts = {}) {
|
|
|
3691
4268
|
|
|
3692
4269
|
// src/reset-learning-cmd.ts
|
|
3693
4270
|
import { unlink as unlink2 } from "fs/promises";
|
|
3694
|
-
import { homedir as
|
|
3695
|
-
import { join as
|
|
4271
|
+
import { homedir as homedir9 } from "os";
|
|
4272
|
+
import { join as join9 } from "path";
|
|
3696
4273
|
var RESET_LEARNING_USAGE = `Usage: yaw-mcp reset-learning
|
|
3697
4274
|
|
|
3698
4275
|
Delete ~/.yaw-mcp/state.json so cross-session learning starts fresh.
|
|
@@ -3714,7 +4291,7 @@ ${RESET_LEARNING_USAGE}`
|
|
|
3714
4291
|
return { kind: "ok", options: {} };
|
|
3715
4292
|
}
|
|
3716
4293
|
async function runResetLearning(opts = {}) {
|
|
3717
|
-
const home = opts.home ??
|
|
4294
|
+
const home = opts.home ?? homedir9();
|
|
3718
4295
|
const env = opts.env ?? process.env;
|
|
3719
4296
|
const write = opts.out ?? ((s) => process.stdout.write(s));
|
|
3720
4297
|
const writeErr = opts.err ?? ((s) => process.stderr.write(s));
|
|
@@ -3729,7 +4306,7 @@ async function runResetLearning(opts = {}) {
|
|
|
3729
4306
|
writeErr(`${s}
|
|
3730
4307
|
`);
|
|
3731
4308
|
};
|
|
3732
|
-
const filePath =
|
|
4309
|
+
const filePath = join9(userConfigDir(home), STATE_FILENAME);
|
|
3733
4310
|
const raw = env.YAW_MCP_DISABLE_PERSISTENCE;
|
|
3734
4311
|
const disabled = raw !== void 0 && raw !== "" && (raw === "1" || raw.toLowerCase() === "true");
|
|
3735
4312
|
if (disabled) {
|
|
@@ -3762,16 +4339,16 @@ function isFileNotFound3(err) {
|
|
|
3762
4339
|
}
|
|
3763
4340
|
|
|
3764
4341
|
// src/secrets-cmd.ts
|
|
3765
|
-
import { existsSync as
|
|
4342
|
+
import { existsSync as existsSync6 } from "fs";
|
|
3766
4343
|
import { mkdir as mkdir3 } from "fs/promises";
|
|
3767
|
-
import { homedir as
|
|
4344
|
+
import { homedir as homedir11 } from "os";
|
|
3768
4345
|
import { dirname as dirname3 } from "path";
|
|
3769
4346
|
|
|
3770
4347
|
// src/secrets-vault.ts
|
|
3771
|
-
import { existsSync as
|
|
3772
|
-
import { chmod as chmod3, readFile as
|
|
3773
|
-
import { homedir as
|
|
3774
|
-
import { dirname as dirname2, join as
|
|
4348
|
+
import { existsSync as existsSync5 } from "fs";
|
|
4349
|
+
import { chmod as chmod3, readFile as readFile8 } from "fs/promises";
|
|
4350
|
+
import { homedir as homedir10 } from "os";
|
|
4351
|
+
import { dirname as dirname2, join as join10 } from "path";
|
|
3775
4352
|
|
|
3776
4353
|
// src/secrets-crypto.ts
|
|
3777
4354
|
import { createCipheriv, createDecipheriv, randomBytes, scrypt as scryptCb } from "crypto";
|
|
@@ -3829,8 +4406,8 @@ function decryptEntry(entry, key) {
|
|
|
3829
4406
|
// src/secrets-vault.ts
|
|
3830
4407
|
var SECRETS_FILENAME = "secrets.json";
|
|
3831
4408
|
var SECRETS_SCHEMA_VERSION = 1;
|
|
3832
|
-
function vaultPath(home =
|
|
3833
|
-
return
|
|
4409
|
+
function vaultPath(home = homedir10()) {
|
|
4410
|
+
return join10(home, CONFIG_DIRNAME, SECRETS_FILENAME);
|
|
3834
4411
|
}
|
|
3835
4412
|
function emptyVault() {
|
|
3836
4413
|
return {
|
|
@@ -3840,10 +4417,10 @@ function emptyVault() {
|
|
|
3840
4417
|
};
|
|
3841
4418
|
}
|
|
3842
4419
|
async function loadVault(path3) {
|
|
3843
|
-
if (!
|
|
4420
|
+
if (!existsSync5(path3)) return null;
|
|
3844
4421
|
let raw;
|
|
3845
4422
|
try {
|
|
3846
|
-
raw = await
|
|
4423
|
+
raw = await readFile8(path3, "utf8");
|
|
3847
4424
|
} catch (err) {
|
|
3848
4425
|
log("warn", "Failed to read vault", { path: path3, error: err instanceof Error ? err.message : String(err) });
|
|
3849
4426
|
return null;
|
|
@@ -4097,13 +4674,13 @@ async function readStdinValue(io) {
|
|
|
4097
4674
|
}
|
|
4098
4675
|
async function ensureVaultDir(path3) {
|
|
4099
4676
|
const dir = dirname3(path3);
|
|
4100
|
-
if (!
|
|
4677
|
+
if (!existsSync6(dir)) await mkdir3(dir, { recursive: true });
|
|
4101
4678
|
}
|
|
4102
4679
|
async function runSecrets(opts, io = {
|
|
4103
4680
|
out: (s) => process.stdout.write(s),
|
|
4104
4681
|
err: (s) => process.stderr.write(s)
|
|
4105
4682
|
}) {
|
|
4106
|
-
const home = opts.home ??
|
|
4683
|
+
const home = opts.home ?? homedir11();
|
|
4107
4684
|
const path3 = vaultPath(home);
|
|
4108
4685
|
if (opts.action === "lock") {
|
|
4109
4686
|
lock();
|
|
@@ -4121,7 +4698,7 @@ async function runSecrets(opts, io = {
|
|
|
4121
4698
|
if (opts.action === "list") {
|
|
4122
4699
|
const vault2 = await loadVault(path3);
|
|
4123
4700
|
const keys = vault2 ? listKeys(vault2) : [];
|
|
4124
|
-
if (opts.json) io.out(`${JSON.stringify({ ok: true, vault:
|
|
4701
|
+
if (opts.json) io.out(`${JSON.stringify({ ok: true, vault: existsSync6(path3), keys }, null, 2)}
|
|
4125
4702
|
`);
|
|
4126
4703
|
else if (!vault2) io.out(`No vault at ${path3}. Run \`yaw-mcp secrets set <name>\` to create one.
|
|
4127
4704
|
`);
|
|
@@ -4136,7 +4713,7 @@ async function runSecrets(opts, io = {
|
|
|
4136
4713
|
return { exitCode: 0 };
|
|
4137
4714
|
}
|
|
4138
4715
|
let vault = await loadVault(path3) ?? newVault();
|
|
4139
|
-
const isFresh = !
|
|
4716
|
+
const isFresh = !existsSync6(path3);
|
|
4140
4717
|
const passphrase = await resolvePassphrase(opts);
|
|
4141
4718
|
if (passphrase === null) {
|
|
4142
4719
|
const msg = "Passphrase required. Set YAW_MCP_VAULT_PASSPHRASE or run from a TTY so we can prompt.";
|
|
@@ -4231,7 +4808,7 @@ async function runSecrets(opts, io = {
|
|
|
4231
4808
|
}
|
|
4232
4809
|
var MCP_SECRETS_RESOURCE = "mcp_secrets";
|
|
4233
4810
|
async function runSecretsPush(opts, io) {
|
|
4234
|
-
const home = opts.home ??
|
|
4811
|
+
const home = opts.home ?? homedir11();
|
|
4235
4812
|
const path3 = vaultPath(home);
|
|
4236
4813
|
const session = await getSession({ home, baseUrl: opts.baseUrl });
|
|
4237
4814
|
if (!session) {
|
|
@@ -4294,7 +4871,7 @@ async function runSecretsPush(opts, io) {
|
|
|
4294
4871
|
}
|
|
4295
4872
|
}
|
|
4296
4873
|
async function runSecretsPull(opts, io) {
|
|
4297
|
-
const home = opts.home ??
|
|
4874
|
+
const home = opts.home ?? homedir11();
|
|
4298
4875
|
const path3 = vaultPath(home);
|
|
4299
4876
|
const session = await getSession({ home, baseUrl: opts.baseUrl });
|
|
4300
4877
|
if (!session) {
|
|
@@ -4350,7 +4927,7 @@ async function runSecretsPull(opts, io) {
|
|
|
4350
4927
|
|
|
4351
4928
|
// src/server.ts
|
|
4352
4929
|
import { readFile as readFile10 } from "fs/promises";
|
|
4353
|
-
import { homedir as
|
|
4930
|
+
import { homedir as homedir12 } from "os";
|
|
4354
4931
|
import { isAbsolute, relative, resolve as resolve4 } from "path";
|
|
4355
4932
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
4356
4933
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -4550,7 +5127,7 @@ Or re-run with --run to upgrade in place.`);
|
|
|
4550
5127
|
return { exitCode: 3, lines };
|
|
4551
5128
|
}
|
|
4552
5129
|
function readCurrentVersion() {
|
|
4553
|
-
return true ? "0.
|
|
5130
|
+
return true ? "0.59.0" : "dev";
|
|
4554
5131
|
}
|
|
4555
5132
|
|
|
4556
5133
|
// src/auto-upgrade.ts
|
|
@@ -4598,7 +5175,7 @@ function defaultSpawn2(cmd, args) {
|
|
|
4598
5175
|
async function maybeAutoUpgrade(deps = {}) {
|
|
4599
5176
|
const optOut = process.env.YAW_MCP_AUTO_UPGRADE;
|
|
4600
5177
|
if (optOut === "0" || optOut?.toLowerCase() === "false") return;
|
|
4601
|
-
const current = deps.currentVersion ?? (true ? "0.
|
|
5178
|
+
const current = deps.currentVersion ?? (true ? "0.59.0" : "dev");
|
|
4602
5179
|
if (current === "dev") return;
|
|
4603
5180
|
const method = detectInstallMethod(deps.argvPath ?? process.argv[1]);
|
|
4604
5181
|
const latest = await (deps.fetchLatestImpl ?? fetchLatestVersion2)();
|
|
@@ -4969,13 +5546,13 @@ function stepBindingKey(step, index) {
|
|
|
4969
5546
|
}
|
|
4970
5547
|
|
|
4971
5548
|
// src/guide.ts
|
|
4972
|
-
import { readFile as
|
|
5549
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
4973
5550
|
var GUIDE_READ_TIMEOUT_MS = 1e3;
|
|
4974
5551
|
async function readGuide(path3, scope) {
|
|
4975
5552
|
let raw;
|
|
4976
5553
|
try {
|
|
4977
5554
|
raw = await Promise.race([
|
|
4978
|
-
|
|
5555
|
+
readFile9(path3, "utf8"),
|
|
4979
5556
|
new Promise(
|
|
4980
5557
|
(_, reject) => setTimeout(() => reject(new Error("guide read timeout")), GUIDE_READ_TIMEOUT_MS)
|
|
4981
5558
|
)
|
|
@@ -5257,132 +5834,6 @@ var LearningStore = class {
|
|
|
5257
5834
|
}
|
|
5258
5835
|
};
|
|
5259
5836
|
|
|
5260
|
-
// src/local-bundles.ts
|
|
5261
|
-
import { createHash as createHash2 } from "crypto";
|
|
5262
|
-
import { readFile as readFile9 } from "fs/promises";
|
|
5263
|
-
import { homedir as homedir10 } from "os";
|
|
5264
|
-
import { join as join10 } from "path";
|
|
5265
|
-
var BUNDLES_FILENAME = "bundles.json";
|
|
5266
|
-
var CURRENT_BUNDLES_SCHEMA_VERSION = 1;
|
|
5267
|
-
function localBundlesPath(configDir) {
|
|
5268
|
-
return join10(configDir, BUNDLES_FILENAME);
|
|
5269
|
-
}
|
|
5270
|
-
var NAMESPACE_RE = /^[a-z][a-z0-9_]{0,29}$/;
|
|
5271
|
-
function validateEntry(entry, warnings) {
|
|
5272
|
-
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
5273
|
-
warnings.push("bundles.json: skipping non-object server entry");
|
|
5274
|
-
return null;
|
|
5275
|
-
}
|
|
5276
|
-
const e = entry;
|
|
5277
|
-
const namespace = typeof e.namespace === "string" ? e.namespace : "";
|
|
5278
|
-
if (!namespace || !NAMESPACE_RE.test(namespace)) {
|
|
5279
|
-
warnings.push(`bundles.json: skipping server with invalid namespace ${JSON.stringify(namespace)}`);
|
|
5280
|
-
return null;
|
|
5281
|
-
}
|
|
5282
|
-
const name = typeof e.name === "string" && e.name.length > 0 ? e.name : namespace;
|
|
5283
|
-
const type = e.type === "remote" ? "remote" : "local";
|
|
5284
|
-
const transport = e.transport === "streamable-http" || e.transport === "sse" || e.transport === "stdio" ? e.transport : void 0;
|
|
5285
|
-
const command = typeof e.command === "string" ? e.command : void 0;
|
|
5286
|
-
const args = Array.isArray(e.args) ? e.args.filter((a) => typeof a === "string") : void 0;
|
|
5287
|
-
const env = e.env && typeof e.env === "object" && !Array.isArray(e.env) ? Object.fromEntries(
|
|
5288
|
-
Object.entries(e.env).filter(([, v]) => typeof v === "string")
|
|
5289
|
-
) : void 0;
|
|
5290
|
-
const url = typeof e.url === "string" ? e.url : void 0;
|
|
5291
|
-
const description = typeof e.description === "string" ? e.description : void 0;
|
|
5292
|
-
const isActive = e.isActive !== false;
|
|
5293
|
-
const id = typeof e.id === "string" && e.id.length > 0 ? e.id : `local-${namespace}`;
|
|
5294
|
-
return {
|
|
5295
|
-
id,
|
|
5296
|
-
name,
|
|
5297
|
-
namespace,
|
|
5298
|
-
type,
|
|
5299
|
-
transport,
|
|
5300
|
-
command,
|
|
5301
|
-
args,
|
|
5302
|
-
env,
|
|
5303
|
-
url,
|
|
5304
|
-
isActive,
|
|
5305
|
-
description
|
|
5306
|
-
};
|
|
5307
|
-
}
|
|
5308
|
-
async function readBundlesAt(path3, warnings) {
|
|
5309
|
-
let raw;
|
|
5310
|
-
try {
|
|
5311
|
-
raw = await readFile9(path3, "utf8");
|
|
5312
|
-
} catch {
|
|
5313
|
-
return { exists: false, file: null };
|
|
5314
|
-
}
|
|
5315
|
-
let parsed;
|
|
5316
|
-
try {
|
|
5317
|
-
parsed = parseJsonc(raw);
|
|
5318
|
-
} catch (err) {
|
|
5319
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
5320
|
-
warnings.push(`${path3}: invalid JSON (${msg}) -- file ignored`);
|
|
5321
|
-
log("warn", "bundles.json is not valid JSON; ignoring", { path: path3, error: msg });
|
|
5322
|
-
return { exists: true, file: null };
|
|
5323
|
-
}
|
|
5324
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
5325
|
-
warnings.push(`${path3}: root must be a JSON object -- file ignored`);
|
|
5326
|
-
return { exists: true, file: null };
|
|
5327
|
-
}
|
|
5328
|
-
const obj = parsed;
|
|
5329
|
-
const version = typeof obj.version === "number" ? obj.version : void 0;
|
|
5330
|
-
if (version !== void 0 && version > CURRENT_BUNDLES_SCHEMA_VERSION) {
|
|
5331
|
-
warnings.push(
|
|
5332
|
-
`${path3}: schema version ${version} is newer than this yaw-mcp (${CURRENT_BUNDLES_SCHEMA_VERSION}); upgrade with \`npm i -g @yawlabs/mcp@latest\`. Loading best-effort.`
|
|
5333
|
-
);
|
|
5334
|
-
}
|
|
5335
|
-
const rawServers = obj.servers;
|
|
5336
|
-
if (!Array.isArray(rawServers)) {
|
|
5337
|
-
warnings.push(`${path3}: 'servers' must be an array -- file ignored`);
|
|
5338
|
-
return { exists: true, file: null };
|
|
5339
|
-
}
|
|
5340
|
-
return {
|
|
5341
|
-
exists: true,
|
|
5342
|
-
file: { version, servers: rawServers }
|
|
5343
|
-
};
|
|
5344
|
-
}
|
|
5345
|
-
function hashContent(servers) {
|
|
5346
|
-
const h = createHash2("sha256");
|
|
5347
|
-
h.update(JSON.stringify(servers));
|
|
5348
|
-
return `local-${h.digest("hex").slice(0, 16)}`;
|
|
5349
|
-
}
|
|
5350
|
-
async function loadLocalBundles(opts = {}) {
|
|
5351
|
-
const cwd = opts.cwd ?? process.cwd();
|
|
5352
|
-
const home = opts.home ?? homedir10();
|
|
5353
|
-
const warnings = [];
|
|
5354
|
-
const projectDir = await findProjectConfigDir(cwd, home).catch(() => null);
|
|
5355
|
-
const projectPath = projectDir ? localBundlesPath(projectDir) : null;
|
|
5356
|
-
const globalPath = localBundlesPath(join10(home, CONFIG_DIRNAME));
|
|
5357
|
-
const projectResult = projectPath ? await readBundlesAt(projectPath, warnings) : { exists: false, file: null };
|
|
5358
|
-
let file;
|
|
5359
|
-
let sourcePath;
|
|
5360
|
-
if (projectResult.exists) {
|
|
5361
|
-
file = projectResult.file;
|
|
5362
|
-
sourcePath = projectPath;
|
|
5363
|
-
} else {
|
|
5364
|
-
const globalResult = await readBundlesAt(globalPath, warnings);
|
|
5365
|
-
file = globalResult.file;
|
|
5366
|
-
sourcePath = globalResult.exists ? globalPath : null;
|
|
5367
|
-
}
|
|
5368
|
-
if (!file) {
|
|
5369
|
-
return { config: null, path: sourcePath, warnings };
|
|
5370
|
-
}
|
|
5371
|
-
const servers = [];
|
|
5372
|
-
for (const raw of file.servers) {
|
|
5373
|
-
const validated = validateEntry(raw, warnings);
|
|
5374
|
-
if (validated) servers.push(validated);
|
|
5375
|
-
}
|
|
5376
|
-
return {
|
|
5377
|
-
config: {
|
|
5378
|
-
servers,
|
|
5379
|
-
configVersion: hashContent(servers)
|
|
5380
|
-
},
|
|
5381
|
-
path: sourcePath,
|
|
5382
|
-
warnings
|
|
5383
|
-
};
|
|
5384
|
-
}
|
|
5385
|
-
|
|
5386
5837
|
// src/meta-tools.ts
|
|
5387
5838
|
var META_TOOLS = {
|
|
5388
5839
|
discover: {
|
|
@@ -6993,7 +7444,7 @@ function categorizeSpawnError(err) {
|
|
|
6993
7444
|
}
|
|
6994
7445
|
async function connectToUpstream(config, onDisconnect, onListChanged) {
|
|
6995
7446
|
const client = new Client(
|
|
6996
|
-
{ name: "yaw-mcp", version: true ? "0.
|
|
7447
|
+
{ name: "yaw-mcp", version: true ? "0.59.0" : "dev" },
|
|
6997
7448
|
{ capabilities: {} }
|
|
6998
7449
|
);
|
|
6999
7450
|
let transport;
|
|
@@ -7302,7 +7753,7 @@ var ConnectServer = class _ConnectServer {
|
|
|
7302
7753
|
this.apiUrl = apiUrl5;
|
|
7303
7754
|
this.token = token5;
|
|
7304
7755
|
this.server = new Server(
|
|
7305
|
-
{ name: "yaw-mcp", version: true ? "0.
|
|
7756
|
+
{ name: "yaw-mcp", version: true ? "0.59.0" : "dev" },
|
|
7306
7757
|
{
|
|
7307
7758
|
capabilities: {
|
|
7308
7759
|
tools: { listChanged: true },
|
|
@@ -8833,7 +9284,7 @@ ${activeCount} loaded in this session, ${totalTools} tools in context${tokenSumm
|
|
|
8833
9284
|
}
|
|
8834
9285
|
const ALLOWED_FILENAMES = ["claude_desktop_config.json", "mcp.json", "settings.json", "mcp_config.json"];
|
|
8835
9286
|
try {
|
|
8836
|
-
const resolved = filepath.startsWith("~/") || filepath.startsWith("~\\") ? resolve4(
|
|
9287
|
+
const resolved = filepath.startsWith("~/") || filepath.startsWith("~\\") ? resolve4(homedir12(), filepath.slice(2)) : resolve4(filepath);
|
|
8837
9288
|
const resolvedBasename = resolved.split(/[/\\]/).pop() || "";
|
|
8838
9289
|
if (!ALLOWED_FILENAMES.includes(resolvedBasename)) {
|
|
8839
9290
|
return {
|
|
@@ -8850,7 +9301,7 @@ ${activeCount} loaded in this session, ${totalTools} tools in context${tokenSumm
|
|
|
8850
9301
|
const rel = relative(base, p);
|
|
8851
9302
|
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
8852
9303
|
};
|
|
8853
|
-
if (!isUnder(
|
|
9304
|
+
if (!isUnder(homedir12(), resolved) && !isUnder(process.cwd(), resolved)) {
|
|
8854
9305
|
return {
|
|
8855
9306
|
content: [
|
|
8856
9307
|
{ type: "text", text: "Import path must be under your home directory or the current working directory." }
|
|
@@ -9504,7 +9955,7 @@ async function runServersCommand(opts = {}) {
|
|
|
9504
9955
|
writeErr(`${s}
|
|
9505
9956
|
`);
|
|
9506
9957
|
};
|
|
9507
|
-
const config = await
|
|
9958
|
+
const config = await loadYawMcpConfig({
|
|
9508
9959
|
cwd: opts.cwd,
|
|
9509
9960
|
home: opts.home,
|
|
9510
9961
|
env: opts.env
|
|
@@ -9584,7 +10035,7 @@ function truncateVersion(v) {
|
|
|
9584
10035
|
}
|
|
9585
10036
|
|
|
9586
10037
|
// src/stats-cmd.ts
|
|
9587
|
-
import { homedir as
|
|
10038
|
+
import { homedir as homedir13 } from "os";
|
|
9588
10039
|
var STATS_USAGE = `Usage: yaw-mcp stats [--json] [--limit N] [--days N]
|
|
9589
10040
|
|
|
9590
10041
|
Print a digest of recent AI tool calls recorded against your Yaw
|
|
@@ -9699,7 +10150,7 @@ async function runStats(opts, io = {
|
|
|
9699
10150
|
out: (s) => process.stdout.write(s),
|
|
9700
10151
|
err: (s) => process.stderr.write(s)
|
|
9701
10152
|
}) {
|
|
9702
|
-
const home = opts.home ??
|
|
10153
|
+
const home = opts.home ?? homedir13();
|
|
9703
10154
|
const session = await getSession({ home, baseUrl: opts.baseUrl });
|
|
9704
10155
|
if (!session) {
|
|
9705
10156
|
const msg = "Not signed in. Yaw MCP analytics requires a Yaw Team account.\n - Yaw Team: $15/seat/mo or $150/seat/yr -- https://yaw.sh/mcp\nSign in with: yaw-mcp login --key <license-key>";
|
|
@@ -9756,9 +10207,9 @@ async function runStats(opts, io = {
|
|
|
9756
10207
|
}
|
|
9757
10208
|
|
|
9758
10209
|
// src/sync-cmd.ts
|
|
9759
|
-
import { existsSync as
|
|
10210
|
+
import { existsSync as existsSync7 } from "fs";
|
|
9760
10211
|
import { mkdir as mkdir4, readFile as readFile11 } from "fs/promises";
|
|
9761
|
-
import { homedir as
|
|
10212
|
+
import { homedir as homedir14 } from "os";
|
|
9762
10213
|
import { dirname as dirname4, join as join11 } from "path";
|
|
9763
10214
|
var SYNC_USAGE = `Usage: yaw-mcp sync <push|pull|status> [--json]
|
|
9764
10215
|
|
|
@@ -9806,7 +10257,7 @@ function bundlesPath(home) {
|
|
|
9806
10257
|
}
|
|
9807
10258
|
async function readLocalBundles(home) {
|
|
9808
10259
|
const path3 = bundlesPath(home);
|
|
9809
|
-
if (!
|
|
10260
|
+
if (!existsSync7(path3)) return { version: 1, servers: [] };
|
|
9810
10261
|
const raw = await readFile11(path3, "utf8");
|
|
9811
10262
|
const parsed = JSON.parse(raw);
|
|
9812
10263
|
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.servers)) {
|
|
@@ -9844,7 +10295,7 @@ async function runSync(opts, io = {
|
|
|
9844
10295
|
out: (s) => process.stdout.write(s),
|
|
9845
10296
|
err: (s) => process.stderr.write(s)
|
|
9846
10297
|
}) {
|
|
9847
|
-
const home = opts.home ??
|
|
10298
|
+
const home = opts.home ?? homedir14();
|
|
9848
10299
|
const session = await getSession({ home, baseUrl: opts.baseUrl });
|
|
9849
10300
|
if (!session) {
|
|
9850
10301
|
const msg = "Not signed in. Run `yaw-mcp login --key <license-key>` first.";
|
|
@@ -9987,6 +10438,9 @@ function handleSyncError(err, opts, io) {
|
|
|
9987
10438
|
var KNOWN_SUBCOMMANDS = [
|
|
9988
10439
|
"compliance",
|
|
9989
10440
|
"install",
|
|
10441
|
+
"add",
|
|
10442
|
+
"remove",
|
|
10443
|
+
"list",
|
|
9990
10444
|
"doctor",
|
|
9991
10445
|
"reset-learning",
|
|
9992
10446
|
"servers",
|
|
@@ -10012,7 +10466,7 @@ if (subcommand && NAG_ELIGIBLE_SUBCOMMANDS.has(subcommand) && process.env.YAW_MC
|
|
|
10012
10466
|
let inAccountMode = envHasToken;
|
|
10013
10467
|
if (!inAccountMode) {
|
|
10014
10468
|
try {
|
|
10015
|
-
const cfg = await
|
|
10469
|
+
const cfg = await loadYawMcpConfig();
|
|
10016
10470
|
inAccountMode = Boolean(cfg.token);
|
|
10017
10471
|
} catch {
|
|
10018
10472
|
inAccountMode = false;
|
|
@@ -10121,6 +10575,30 @@ if (subcommand === "compliance") {
|
|
|
10121
10575
|
process.exit(2);
|
|
10122
10576
|
}
|
|
10123
10577
|
runTryCleanup(parsed.options).then((r) => process.exit(r.exitCode));
|
|
10578
|
+
} else if (subcommand === "add") {
|
|
10579
|
+
const parsed = parseAddArgs(process.argv.slice(3));
|
|
10580
|
+
if (!parsed.ok) {
|
|
10581
|
+
process.stderr.write(`${parsed.error}
|
|
10582
|
+
`);
|
|
10583
|
+
process.exit(2);
|
|
10584
|
+
}
|
|
10585
|
+
runAdd(parsed.options).then((r) => process.exit(r.exitCode));
|
|
10586
|
+
} else if (subcommand === "remove") {
|
|
10587
|
+
const parsed = parseRemoveArgs(process.argv.slice(3));
|
|
10588
|
+
if (!parsed.ok) {
|
|
10589
|
+
process.stderr.write(`${parsed.error}
|
|
10590
|
+
`);
|
|
10591
|
+
process.exit(2);
|
|
10592
|
+
}
|
|
10593
|
+
runRemove(parsed.options).then((r) => process.exit(r.exitCode));
|
|
10594
|
+
} else if (subcommand === "list") {
|
|
10595
|
+
const parsed = parseListArgs(process.argv.slice(3));
|
|
10596
|
+
if (!parsed.ok) {
|
|
10597
|
+
process.stderr.write(`${parsed.error}
|
|
10598
|
+
`);
|
|
10599
|
+
process.exit(2);
|
|
10600
|
+
}
|
|
10601
|
+
runList(parsed.options).then((r) => process.exit(r.exitCode));
|
|
10124
10602
|
} else if (subcommand === "login") {
|
|
10125
10603
|
const parsed = parseLoginArgs(process.argv.slice(3));
|
|
10126
10604
|
if (!parsed.ok) {
|
|
@@ -10170,16 +10648,26 @@ if (subcommand === "compliance") {
|
|
|
10170
10648
|
2. Install yaw-mcp yaw-mcp install claude-code --token mcp_pat_...
|
|
10171
10649
|
3. Verify setup yaw-mcp doctor
|
|
10172
10650
|
|
|
10173
|
-
Setup:
|
|
10174
|
-
install <client>
|
|
10175
|
-
|
|
10176
|
-
|
|
10651
|
+
Setup (connect a client to yaw-mcp):
|
|
10652
|
+
install <client> Connect one MCP client to yaw-mcp. This wires the
|
|
10653
|
+
aggregator into the client; it does NOT add a
|
|
10654
|
+
server (for that, see \`add\` below). <client> is
|
|
10655
|
+
one of: claude-code, claude-desktop, cursor, vscode.
|
|
10177
10656
|
install --list List which MCP clients are installed on this
|
|
10178
10657
|
machine (read-only; no writes).
|
|
10179
10658
|
install --all Configure every installed MCP client in one go.
|
|
10180
|
-
|
|
10181
|
-
|
|
10182
|
-
|
|
10659
|
+
|
|
10660
|
+
Local servers (no account):
|
|
10661
|
+
add <slug> Add an MCP server from the yaw.sh/mcp catalog to
|
|
10662
|
+
your local ~/.yaw-mcp/bundles.json so yaw-mcp loads
|
|
10663
|
+
it. Pass required env with --env KEY=value.
|
|
10664
|
+
remove <slug> Remove a server (by slug or namespace) from
|
|
10665
|
+
bundles.json.
|
|
10666
|
+
list List the servers yaw-mcp loads locally.
|
|
10667
|
+
try <slug> Wire a one-off trial of a catalog MCP server
|
|
10668
|
+
directly into your AI client (bypassing yaw-mcp).
|
|
10669
|
+
No account needed; expires after --ttl (default
|
|
10670
|
+
1h). Doctor GCs it after.
|
|
10183
10671
|
try-cleanup <slug> Remove a wired trial early.
|
|
10184
10672
|
|
|
10185
10673
|
Inspection:
|
|
@@ -10200,6 +10688,17 @@ if (subcommand === "compliance") {
|
|
|
10200
10688
|
fish, or powershell. Redirect to your
|
|
10201
10689
|
completions directory to install.
|
|
10202
10690
|
|
|
10691
|
+
Account / sync (Pro + Team):
|
|
10692
|
+
login Authenticate this machine with a Yaw MCP account
|
|
10693
|
+
(Pro/Team). --key <license> to pass the key inline.
|
|
10694
|
+
logout Sign this machine out of the account.
|
|
10695
|
+
sync <push|pull|status> Replicate your local bundles.json to/from the
|
|
10696
|
+
account store (env values stripped on push).
|
|
10697
|
+
secrets <action> Manage synced secret VALUES: set, get, list,
|
|
10698
|
+
remove, lock, push, pull.
|
|
10699
|
+
stats Show your account usage statistics
|
|
10700
|
+
(--limit, --days, --json).
|
|
10701
|
+
|
|
10203
10702
|
Other:
|
|
10204
10703
|
compliance <target> Run the 88-test compliance suite against an MCP
|
|
10205
10704
|
server. --publish posts the report to
|
|
@@ -10230,8 +10729,10 @@ if (subcommand === "compliance") {
|
|
|
10230
10729
|
upgraded in the background).
|
|
10231
10730
|
YAW_MCP_PRUNE_RESPONSES Set to \`0\` to disable response pruning.
|
|
10232
10731
|
YAW_MCP_DISABLE_PERSISTENCE Disable cross-session learning state.
|
|
10233
|
-
|
|
10234
|
-
|
|
10732
|
+
YAW_MCP_CATALOG_URL Override the catalog \`add\`/\`try\` resolve slugs
|
|
10733
|
+
against (default https://yaw.sh/data/mcp-catalog.json).
|
|
10734
|
+
YAW_MCP_BASE_URL Base URL for \`yaw-mcp try\` signup/telemetry
|
|
10735
|
+
links (default https://yaw.sh/mcp).
|
|
10235
10736
|
|
|
10236
10737
|
Config resolution (highest precedence first):
|
|
10237
10738
|
1. YAW_MCP_TOKEN / YAW_MCP_URL env vars
|
|
@@ -10249,7 +10750,7 @@ if (subcommand === "compliance") {
|
|
|
10249
10750
|
`);
|
|
10250
10751
|
process.exit(0);
|
|
10251
10752
|
} else if (subcommand === "--version" || subcommand === "-V") {
|
|
10252
|
-
process.stdout.write(`yaw-mcp ${true ? "0.
|
|
10753
|
+
process.stdout.write(`yaw-mcp ${true ? "0.59.0" : "dev"}
|
|
10253
10754
|
`);
|
|
10254
10755
|
process.exit(0);
|
|
10255
10756
|
} else if (subcommand && !subcommand.startsWith("-")) {
|
|
@@ -10263,7 +10764,7 @@ if (subcommand === "compliance") {
|
|
|
10263
10764
|
runServer();
|
|
10264
10765
|
}
|
|
10265
10766
|
async function runServer() {
|
|
10266
|
-
const config = await
|
|
10767
|
+
const config = await loadYawMcpConfig();
|
|
10267
10768
|
for (const w of config.warnings) {
|
|
10268
10769
|
log("warn", "Config warning", { warning: w });
|
|
10269
10770
|
}
|