agent-skills-standard 2.1.2 → 2.2.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 +21 -2
- package/dist/index.js +810 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ const path = require("path");
|
|
|
9
9
|
const yaml = require("js-yaml");
|
|
10
10
|
const zod = require("zod");
|
|
11
11
|
const fetch$1 = require("cross-fetch");
|
|
12
|
+
const os = require("os");
|
|
12
13
|
const child_process = require("child_process");
|
|
13
14
|
class FeedbackService {
|
|
14
15
|
/**
|
|
@@ -838,6 +839,13 @@ const SUPPORTED_AGENTS = Object.values(Agent).map(getAgentDefinition);
|
|
|
838
839
|
const SUPPORTED_FRAMEWORKS = Object.values(
|
|
839
840
|
Framework
|
|
840
841
|
).map(getFrameworkDefinition);
|
|
842
|
+
const McpScopeSchema = zod.z.enum(["project", "user", "snippets-only", "disabled"]);
|
|
843
|
+
const McpConfigSchema = zod.z.object({
|
|
844
|
+
enabled: zod.z.boolean(),
|
|
845
|
+
scope: McpScopeSchema,
|
|
846
|
+
prompted: zod.z.boolean(),
|
|
847
|
+
version: zod.z.string().optional()
|
|
848
|
+
});
|
|
841
849
|
const SkillConfigSchema = zod.z.object({
|
|
842
850
|
registry: zod.z.string().url(),
|
|
843
851
|
agents: zod.z.array(zod.z.nativeEnum(Agent)).optional(),
|
|
@@ -851,7 +859,8 @@ const SkillConfigSchema = zod.z.object({
|
|
|
851
859
|
})
|
|
852
860
|
),
|
|
853
861
|
custom_overrides: zod.z.array(zod.z.string()).optional(),
|
|
854
|
-
workflows: zod.z.union([zod.z.boolean(), zod.z.array(zod.z.string())]).optional()
|
|
862
|
+
workflows: zod.z.union([zod.z.boolean(), zod.z.array(zod.z.string())]).optional(),
|
|
863
|
+
mcp: McpConfigSchema.optional()
|
|
855
864
|
});
|
|
856
865
|
class ConfigService {
|
|
857
866
|
/**
|
|
@@ -1369,6 +1378,13 @@ class InitService {
|
|
|
1369
1378
|
const projectDeps = await this.detectionService.getProjectDeps();
|
|
1370
1379
|
this.configService.applyDependencyExclusions(config, projectDeps);
|
|
1371
1380
|
this.configService.reconcileDependencies(config, projectDeps);
|
|
1381
|
+
if (answers.mcpScope !== void 0) {
|
|
1382
|
+
config.mcp = {
|
|
1383
|
+
enabled: answers.mcpEnabled === true,
|
|
1384
|
+
scope: answers.mcpEnabled === false ? "disabled" : answers.mcpScope,
|
|
1385
|
+
prompted: true
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1372
1388
|
const commentHeader = `# Auto-detected configuration generated by agent-skills-standard init
|
|
1373
1389
|
#
|
|
1374
1390
|
# Presence in 'skills' list = Active. To disable a skill, remove it from the list.
|
|
@@ -1669,15 +1685,70 @@ class InitCommand {
|
|
|
1669
1685
|
default: DEFAULT_REGISTER
|
|
1670
1686
|
}
|
|
1671
1687
|
]);
|
|
1688
|
+
this.printMcpValueProp();
|
|
1689
|
+
const mcpDecision = await inquirer.prompt([
|
|
1690
|
+
{
|
|
1691
|
+
type: "confirm",
|
|
1692
|
+
name: "mcpEnabled",
|
|
1693
|
+
message: "Enable MCP server integration?",
|
|
1694
|
+
default: true
|
|
1695
|
+
},
|
|
1696
|
+
{
|
|
1697
|
+
type: "list",
|
|
1698
|
+
name: "mcpScope",
|
|
1699
|
+
message: "Where should sync write MCP configs?",
|
|
1700
|
+
when: (a) => a.mcpEnabled === true,
|
|
1701
|
+
choices: [
|
|
1702
|
+
{
|
|
1703
|
+
name: "project (recommended) — only files inside this project (.mcp.json, .cursor/mcp.json, etc.)",
|
|
1704
|
+
value: "project"
|
|
1705
|
+
},
|
|
1706
|
+
{
|
|
1707
|
+
name: "user — also $HOME files (~/.cursor/mcp.json, ~/.gemini/settings.json) — sync prompts each",
|
|
1708
|
+
value: "user"
|
|
1709
|
+
},
|
|
1710
|
+
{
|
|
1711
|
+
name: "snippets-only — never edits any runtime config; just generates ./mcp-config-snippets/*.json",
|
|
1712
|
+
value: "snippets-only"
|
|
1713
|
+
}
|
|
1714
|
+
],
|
|
1715
|
+
default: "project"
|
|
1716
|
+
}
|
|
1717
|
+
]);
|
|
1718
|
+
answers.mcpEnabled = mcpDecision.mcpEnabled;
|
|
1719
|
+
answers.mcpScope = mcpDecision.mcpEnabled ? mcpDecision.mcpScope : "disabled";
|
|
1672
1720
|
await this.initService.buildAndSaveConfig(answers, metadata);
|
|
1673
1721
|
console.log(pc.green("\n✅ Initialized .skillsrc with your preferences!"));
|
|
1674
1722
|
console.log(pc.gray(` Selected framework: ${answers.framework}`));
|
|
1723
|
+
if (answers.mcpEnabled) {
|
|
1724
|
+
console.log(pc.gray(` MCP integration: enabled (scope: ${answers.mcpScope})`));
|
|
1725
|
+
} else {
|
|
1726
|
+
console.log(pc.gray(` MCP integration: disabled (change later via \`ags mcp enable\`)`));
|
|
1727
|
+
}
|
|
1675
1728
|
console.log(
|
|
1676
1729
|
pc.cyan(
|
|
1677
|
-
"\nNext step: Run `agent-skills-standard sync` to
|
|
1730
|
+
"\nNext step: Run `agent-skills-standard sync` to install skills and wire MCP."
|
|
1678
1731
|
)
|
|
1679
1732
|
);
|
|
1680
1733
|
}
|
|
1734
|
+
printMcpValueProp() {
|
|
1735
|
+
console.log(pc.bold("\n🔌 MCP server setup\n"));
|
|
1736
|
+
console.log(
|
|
1737
|
+
"agent-skills-standard ships an optional MCP server that serves your skills"
|
|
1738
|
+
);
|
|
1739
|
+
console.log("to AI agents at runtime. Quick comparison:\n");
|
|
1740
|
+
console.log(pc.bold(" WITHOUT MCP") + " (skills as static files only)");
|
|
1741
|
+
console.log(pc.green(" ✓") + " Zero setup overhead");
|
|
1742
|
+
console.log(pc.red(" ✗") + " Sub-agents (Claude Code tdd-implementer, architecture-guard, etc.)");
|
|
1743
|
+
console.log(" do NOT inherit AGENTS.md — they often skip skill loading entirely");
|
|
1744
|
+
console.log(pc.red(" ✗") + " No audit trail of which skills were consulted");
|
|
1745
|
+
console.log(pc.red(" ✗") + " False findings in code review (agent uses pre-training defaults)\n");
|
|
1746
|
+
console.log(pc.bold(" WITH MCP") + " (runtime tool calls)");
|
|
1747
|
+
console.log(pc.green(" ✓") + " Sub-agents in EVERY runtime can call load_skills_for_files");
|
|
1748
|
+
console.log(pc.green(" ✓") + " Returns matched SKILL.md as a tool response — can't be silently skipped");
|
|
1749
|
+
console.log(pc.green(" ✓") + " audit_session_compliance gives you a receipt of what was loaded");
|
|
1750
|
+
console.log(pc.gray(" -") + " Adds one more process to your AI runtime config\n");
|
|
1751
|
+
}
|
|
1681
1752
|
}
|
|
1682
1753
|
class SkillService {
|
|
1683
1754
|
registryService = new RegistryService();
|
|
@@ -1793,9 +1864,588 @@ Available skills for ${framework}:`));
|
|
|
1793
1864
|
);
|
|
1794
1865
|
}
|
|
1795
1866
|
}
|
|
1867
|
+
const SERVER_NAME = "agent-skills-standard";
|
|
1868
|
+
const PACKAGE = "agent-skills-standard-mcp";
|
|
1869
|
+
const getTargets = (home = os.homedir()) => {
|
|
1870
|
+
const HOME = home;
|
|
1871
|
+
return {
|
|
1872
|
+
[Agent.Claude]: {
|
|
1873
|
+
agent: Agent.Claude,
|
|
1874
|
+
projectFile: ".mcp.json",
|
|
1875
|
+
userFile: path.join(HOME, ".claude", ".mcp.json"),
|
|
1876
|
+
key: "mcpServers",
|
|
1877
|
+
shape: "map"
|
|
1878
|
+
},
|
|
1879
|
+
[Agent.Cursor]: {
|
|
1880
|
+
agent: Agent.Cursor,
|
|
1881
|
+
projectFile: ".cursor/mcp.json",
|
|
1882
|
+
userFile: path.join(HOME, ".cursor", "mcp.json"),
|
|
1883
|
+
key: "mcpServers",
|
|
1884
|
+
shape: "map"
|
|
1885
|
+
},
|
|
1886
|
+
[Agent.Antigravity]: {
|
|
1887
|
+
agent: Agent.Antigravity,
|
|
1888
|
+
projectFile: ".antigravity/mcp.json",
|
|
1889
|
+
userFile: null,
|
|
1890
|
+
key: "mcpServers",
|
|
1891
|
+
shape: "map"
|
|
1892
|
+
},
|
|
1893
|
+
[Agent.Kiro]: {
|
|
1894
|
+
agent: Agent.Kiro,
|
|
1895
|
+
projectFile: ".kiro/settings/mcp.json",
|
|
1896
|
+
userFile: null,
|
|
1897
|
+
key: "mcpServers",
|
|
1898
|
+
shape: "map"
|
|
1899
|
+
},
|
|
1900
|
+
[Agent.Windsurf]: {
|
|
1901
|
+
agent: Agent.Windsurf,
|
|
1902
|
+
projectFile: ".codeium/windsurf/mcp_config.json",
|
|
1903
|
+
userFile: path.join(HOME, ".codeium", "windsurf", "mcp_config.json"),
|
|
1904
|
+
key: "mcpServers",
|
|
1905
|
+
shape: "map"
|
|
1906
|
+
},
|
|
1907
|
+
[Agent.Trae]: {
|
|
1908
|
+
agent: Agent.Trae,
|
|
1909
|
+
projectFile: ".trae/mcp.json",
|
|
1910
|
+
userFile: null,
|
|
1911
|
+
key: "mcpServers",
|
|
1912
|
+
shape: "map"
|
|
1913
|
+
},
|
|
1914
|
+
[Agent.Roo]: {
|
|
1915
|
+
agent: Agent.Roo,
|
|
1916
|
+
projectFile: ".roo/mcp.json",
|
|
1917
|
+
userFile: null,
|
|
1918
|
+
key: "mcpServers",
|
|
1919
|
+
shape: "map"
|
|
1920
|
+
},
|
|
1921
|
+
[Agent.Gemini]: {
|
|
1922
|
+
agent: Agent.Gemini,
|
|
1923
|
+
projectFile: ".gemini/settings.json",
|
|
1924
|
+
userFile: path.join(HOME, ".gemini", "settings.json"),
|
|
1925
|
+
key: "mcpServers",
|
|
1926
|
+
shape: "map"
|
|
1927
|
+
}
|
|
1928
|
+
};
|
|
1929
|
+
};
|
|
1930
|
+
class McpConfigService {
|
|
1931
|
+
/** Returns the standard MCP server entry our CLI proposes. */
|
|
1932
|
+
buildEntry(version2) {
|
|
1933
|
+
const spec = version2 ? `${PACKAGE}@${version2}` : PACKAGE;
|
|
1934
|
+
return { command: "npx", args: ["-y", spec] };
|
|
1935
|
+
}
|
|
1936
|
+
/** For testing: allows overriding the home directory. */
|
|
1937
|
+
testHome = null;
|
|
1938
|
+
setHomeForTesting(home) {
|
|
1939
|
+
this.testHome = home;
|
|
1940
|
+
}
|
|
1941
|
+
getTargets() {
|
|
1942
|
+
return getTargets(this.testHome ?? void 0);
|
|
1943
|
+
}
|
|
1944
|
+
/**
|
|
1945
|
+
* Write MCP configs for the given agents based on the configured scope.
|
|
1946
|
+
* Always safe-merge (never overwrite other servers, never replace whole files).
|
|
1947
|
+
*/
|
|
1948
|
+
async install(opts) {
|
|
1949
|
+
const report = {
|
|
1950
|
+
projectWrites: [],
|
|
1951
|
+
userWrites: [],
|
|
1952
|
+
snippets: [],
|
|
1953
|
+
declined: [],
|
|
1954
|
+
unsupported: []
|
|
1955
|
+
};
|
|
1956
|
+
const { rootDir, agents, mcp } = opts;
|
|
1957
|
+
if (mcp.scope === "disabled" || !mcp.enabled) {
|
|
1958
|
+
return report;
|
|
1959
|
+
}
|
|
1960
|
+
const TARGETS = this.getTargets();
|
|
1961
|
+
const entry = this.buildEntry(mcp.version);
|
|
1962
|
+
await this.generateSnippets(rootDir, agents, entry, report);
|
|
1963
|
+
if (mcp.scope === "snippets-only") {
|
|
1964
|
+
return report;
|
|
1965
|
+
}
|
|
1966
|
+
for (const agent of agents) {
|
|
1967
|
+
const target = TARGETS[agent];
|
|
1968
|
+
if (!target) {
|
|
1969
|
+
report.unsupported.push(agent);
|
|
1970
|
+
continue;
|
|
1971
|
+
}
|
|
1972
|
+
if (target.projectFile) {
|
|
1973
|
+
const abs = path.join(rootDir, target.projectFile);
|
|
1974
|
+
const action = await this.mergeFile(abs, target, entry);
|
|
1975
|
+
report.projectWrites.push({ agent, file: target.projectFile, action });
|
|
1976
|
+
}
|
|
1977
|
+
if (mcp.scope === "user" && target.userFile) {
|
|
1978
|
+
const ok = opts.userScopePrompt ? await opts.userScopePrompt(agent, target.userFile) : false;
|
|
1979
|
+
if (!ok) {
|
|
1980
|
+
report.declined.push({ agent, file: target.userFile });
|
|
1981
|
+
continue;
|
|
1982
|
+
}
|
|
1983
|
+
const action = await this.mergeFile(target.userFile, target, entry);
|
|
1984
|
+
report.userWrites.push({ agent, file: target.userFile, action });
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
return report;
|
|
1988
|
+
}
|
|
1989
|
+
/**
|
|
1990
|
+
* Remove our MCP entry from configs we may have added it to. Other entries
|
|
1991
|
+
* in the same file are untouched.
|
|
1992
|
+
*/
|
|
1993
|
+
async uninstall(opts) {
|
|
1994
|
+
const removed = [];
|
|
1995
|
+
const TARGETS = this.getTargets();
|
|
1996
|
+
for (const agent of opts.agents) {
|
|
1997
|
+
const target = TARGETS[agent];
|
|
1998
|
+
if (!target) continue;
|
|
1999
|
+
const candidates = [];
|
|
2000
|
+
if (target.projectFile && (opts.from === "project" || opts.from === "all")) {
|
|
2001
|
+
candidates.push({
|
|
2002
|
+
abs: path.join(opts.rootDir, target.projectFile),
|
|
2003
|
+
rel: target.projectFile
|
|
2004
|
+
});
|
|
2005
|
+
}
|
|
2006
|
+
if (target.userFile && (opts.from === "user" || opts.from === "all")) {
|
|
2007
|
+
candidates.push({ abs: target.userFile, rel: target.userFile });
|
|
2008
|
+
}
|
|
2009
|
+
for (const { abs, rel } of candidates) {
|
|
2010
|
+
if (!await fs.pathExists(abs)) continue;
|
|
2011
|
+
const removedHere = await this.removeFromFile(abs, target);
|
|
2012
|
+
if (removedHere) removed.push({ agent, file: rel });
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
return { removed };
|
|
2016
|
+
}
|
|
2017
|
+
/** Snapshot what's currently installed. Read-only. */
|
|
2018
|
+
async status(opts) {
|
|
2019
|
+
const out = [];
|
|
2020
|
+
const TARGETS = this.getTargets();
|
|
2021
|
+
for (const agent of opts.agents) {
|
|
2022
|
+
const target = TARGETS[agent];
|
|
2023
|
+
if (!target) continue;
|
|
2024
|
+
const row = {
|
|
2025
|
+
agent
|
|
2026
|
+
};
|
|
2027
|
+
if (target.projectFile) {
|
|
2028
|
+
row.project = await this.hasOurEntry(
|
|
2029
|
+
path.join(opts.rootDir, target.projectFile),
|
|
2030
|
+
target
|
|
2031
|
+
);
|
|
2032
|
+
}
|
|
2033
|
+
if (target.userFile) {
|
|
2034
|
+
row.user = await this.hasOurEntry(target.userFile, target);
|
|
2035
|
+
}
|
|
2036
|
+
out.push(row);
|
|
2037
|
+
}
|
|
2038
|
+
return out;
|
|
2039
|
+
}
|
|
2040
|
+
// ---- private helpers ----
|
|
2041
|
+
async generateSnippets(rootDir, agents, entry, report) {
|
|
2042
|
+
const dir = path.join(rootDir, "mcp-config-snippets");
|
|
2043
|
+
await fs.ensureDir(dir);
|
|
2044
|
+
const TARGETS = this.getTargets();
|
|
2045
|
+
for (const agent of agents) {
|
|
2046
|
+
const target = TARGETS[agent];
|
|
2047
|
+
if (!target) continue;
|
|
2048
|
+
const file = path.join(dir, `${agent}.json`);
|
|
2049
|
+
const snippet = this.buildFreshDoc(target, entry);
|
|
2050
|
+
await fs.writeJson(file, snippet, { spaces: 2 });
|
|
2051
|
+
report.snippets.push({ agent, file: path.relative(rootDir, file) });
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
/**
|
|
2055
|
+
* Read-modify-write a config file. Only the SERVER_NAME key is touched.
|
|
2056
|
+
* Never replaces or removes other entries.
|
|
2057
|
+
*/
|
|
2058
|
+
async mergeFile(abs, target, entry) {
|
|
2059
|
+
const existing = await fs.pathExists(abs) ? await fs.readJson(abs).catch(() => ({})) : {};
|
|
2060
|
+
const container = this.ensurePathContainer(existing, target);
|
|
2061
|
+
if (target.shape === "map") {
|
|
2062
|
+
const map = container;
|
|
2063
|
+
const previous = map[SERVER_NAME];
|
|
2064
|
+
const next = entry;
|
|
2065
|
+
if (previous && deepEqual(previous, next)) {
|
|
2066
|
+
return "skipped-existing";
|
|
2067
|
+
}
|
|
2068
|
+
map[SERVER_NAME] = next;
|
|
2069
|
+
await this.writeAtomic(abs, existing);
|
|
2070
|
+
return previous ? "updated" : "added";
|
|
2071
|
+
} else {
|
|
2072
|
+
const list = container;
|
|
2073
|
+
const idx = list.findIndex((item) => item.name === SERVER_NAME);
|
|
2074
|
+
const next = {
|
|
2075
|
+
name: SERVER_NAME,
|
|
2076
|
+
transport: { type: "stdio", ...entry }
|
|
2077
|
+
};
|
|
2078
|
+
if (idx >= 0) {
|
|
2079
|
+
if (deepEqual(list[idx], next)) return "skipped-existing";
|
|
2080
|
+
list[idx] = next;
|
|
2081
|
+
await this.writeAtomic(abs, existing);
|
|
2082
|
+
return "updated";
|
|
2083
|
+
}
|
|
2084
|
+
list.push(next);
|
|
2085
|
+
await this.writeAtomic(abs, existing);
|
|
2086
|
+
return "added";
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
async removeFromFile(abs, target) {
|
|
2090
|
+
const data = await fs.readJson(abs).catch(() => null);
|
|
2091
|
+
if (!data) return false;
|
|
2092
|
+
const container = this.ensurePathContainer(data, target);
|
|
2093
|
+
if (target.shape === "map") {
|
|
2094
|
+
const map = container;
|
|
2095
|
+
if (!(SERVER_NAME in map)) return false;
|
|
2096
|
+
delete map[SERVER_NAME];
|
|
2097
|
+
await this.writeAtomic(abs, data);
|
|
2098
|
+
return true;
|
|
2099
|
+
} else {
|
|
2100
|
+
const list = container;
|
|
2101
|
+
const before = list.length;
|
|
2102
|
+
const filtered = list.filter((item) => item.name !== SERVER_NAME);
|
|
2103
|
+
if (filtered.length === before) return false;
|
|
2104
|
+
this.setNestedValue(data, target.key, filtered);
|
|
2105
|
+
await this.writeAtomic(abs, data);
|
|
2106
|
+
return true;
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
async hasOurEntry(abs, target) {
|
|
2110
|
+
if (!await fs.pathExists(abs)) return false;
|
|
2111
|
+
const data = await fs.readJson(abs).catch(() => null);
|
|
2112
|
+
if (!data) return false;
|
|
2113
|
+
const container = this.getNestedValue(data, target.key);
|
|
2114
|
+
if (!container) return false;
|
|
2115
|
+
if (target.shape === "map") {
|
|
2116
|
+
return Boolean(container[SERVER_NAME]);
|
|
2117
|
+
}
|
|
2118
|
+
return container.some(
|
|
2119
|
+
(item) => item.name === SERVER_NAME
|
|
2120
|
+
);
|
|
2121
|
+
}
|
|
2122
|
+
buildFreshDoc(target, entry) {
|
|
2123
|
+
const doc = {};
|
|
2124
|
+
const container = target.shape === "map" ? { [SERVER_NAME]: entry } : [{ name: SERVER_NAME, transport: { type: "stdio", ...entry } }];
|
|
2125
|
+
this.setNestedValue(doc, target.key, container);
|
|
2126
|
+
return doc;
|
|
2127
|
+
}
|
|
2128
|
+
ensurePathContainer(data, target) {
|
|
2129
|
+
const existing = this.getNestedValue(data, target.key);
|
|
2130
|
+
if (existing) {
|
|
2131
|
+
if (target.shape === "map") {
|
|
2132
|
+
if (typeof existing === "object" && existing !== null && !Array.isArray(existing)) {
|
|
2133
|
+
return existing;
|
|
2134
|
+
}
|
|
2135
|
+
} else if (target.shape === "list") {
|
|
2136
|
+
if (Array.isArray(existing)) {
|
|
2137
|
+
return existing;
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
const fresh = target.shape === "map" ? {} : [];
|
|
2142
|
+
this.setNestedValue(data, target.key, fresh);
|
|
2143
|
+
return fresh;
|
|
2144
|
+
}
|
|
2145
|
+
getNestedValue(data, dotted) {
|
|
2146
|
+
const parts = dotted.split(".");
|
|
2147
|
+
let cur = data;
|
|
2148
|
+
for (const p of parts) {
|
|
2149
|
+
if (p === "__proto__" || p === "constructor" || p === "prototype") {
|
|
2150
|
+
throw new Error(
|
|
2151
|
+
`Prototype pollution attempt detected in key: ${dotted}`
|
|
2152
|
+
);
|
|
2153
|
+
}
|
|
2154
|
+
if (typeof cur !== "object" || cur === null) return void 0;
|
|
2155
|
+
cur = cur[p];
|
|
2156
|
+
}
|
|
2157
|
+
return cur;
|
|
2158
|
+
}
|
|
2159
|
+
setNestedValue(data, dotted, value) {
|
|
2160
|
+
const parts = dotted.split(".");
|
|
2161
|
+
let cur = data;
|
|
2162
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
2163
|
+
const p = parts[i];
|
|
2164
|
+
if (p === "__proto__" || p === "constructor" || p === "prototype") {
|
|
2165
|
+
throw new Error(
|
|
2166
|
+
`Prototype pollution attempt detected in key: ${dotted}`
|
|
2167
|
+
);
|
|
2168
|
+
}
|
|
2169
|
+
if (typeof cur[p] !== "object" || cur[p] === null) {
|
|
2170
|
+
cur[p] = {};
|
|
2171
|
+
}
|
|
2172
|
+
cur = cur[p];
|
|
2173
|
+
}
|
|
2174
|
+
const last = parts[parts.length - 1];
|
|
2175
|
+
if (last === "__proto__" || last === "constructor" || last === "prototype") {
|
|
2176
|
+
throw new Error(`Prototype pollution attempt detected in key: ${dotted}`);
|
|
2177
|
+
}
|
|
2178
|
+
cur[last] = value;
|
|
2179
|
+
}
|
|
2180
|
+
async writeAtomic(abs, data) {
|
|
2181
|
+
await fs.ensureDir(path.dirname(abs));
|
|
2182
|
+
const tmp = `${abs}.tmp`;
|
|
2183
|
+
await fs.writeJson(tmp, data, { spaces: 2 });
|
|
2184
|
+
await fs.move(tmp, abs, { overwrite: true });
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
function defaultMcpConfig() {
|
|
2188
|
+
return { enabled: false, scope: "snippets-only", prompted: false };
|
|
2189
|
+
}
|
|
2190
|
+
function deepEqual(a, b) {
|
|
2191
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
2192
|
+
}
|
|
2193
|
+
const VALID_SCOPES = ["project", "user", "snippets-only", "disabled"];
|
|
2194
|
+
class McpCommand {
|
|
2195
|
+
configService;
|
|
2196
|
+
mcpService;
|
|
2197
|
+
constructor(configService, mcpService) {
|
|
2198
|
+
this.configService = configService ?? new ConfigService();
|
|
2199
|
+
this.mcpService = mcpService ?? new McpConfigService();
|
|
2200
|
+
}
|
|
2201
|
+
async run(action, options = {}) {
|
|
2202
|
+
const config = await this.configService.loadConfig();
|
|
2203
|
+
if (!config) {
|
|
2204
|
+
console.log(
|
|
2205
|
+
pc.red(
|
|
2206
|
+
"❌ .skillsrc not found. Run `agent-skills-standard init` first."
|
|
2207
|
+
)
|
|
2208
|
+
);
|
|
2209
|
+
return;
|
|
2210
|
+
}
|
|
2211
|
+
const cwd = process.cwd();
|
|
2212
|
+
const agents = config.agents ?? [];
|
|
2213
|
+
switch (action) {
|
|
2214
|
+
case "status":
|
|
2215
|
+
return this.actionStatus(config, cwd);
|
|
2216
|
+
case "enable":
|
|
2217
|
+
return this.actionEnable(config);
|
|
2218
|
+
case "disable":
|
|
2219
|
+
return this.actionDisable(config);
|
|
2220
|
+
case "scope":
|
|
2221
|
+
return this.actionScope(config, options);
|
|
2222
|
+
case "install":
|
|
2223
|
+
return this.actionInstall(config, agents, cwd, options);
|
|
2224
|
+
case "uninstall":
|
|
2225
|
+
return this.actionUninstall(config, agents, cwd, options);
|
|
2226
|
+
case "snippets":
|
|
2227
|
+
return this.actionSnippets(config, agents, cwd);
|
|
2228
|
+
default:
|
|
2229
|
+
console.log(
|
|
2230
|
+
pc.red(`Unknown action: ${action}`),
|
|
2231
|
+
"\nValid actions: status, enable, disable, scope, install, uninstall, snippets"
|
|
2232
|
+
);
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
// ---- actions ----
|
|
2236
|
+
async actionStatus(config, cwd) {
|
|
2237
|
+
const mcp = config.mcp ?? defaultMcpConfig();
|
|
2238
|
+
const agents = config.agents ?? [];
|
|
2239
|
+
const rows = await this.mcpService.status({ rootDir: cwd, agents });
|
|
2240
|
+
console.log(pc.bold("\n🔌 MCP integration status\n"));
|
|
2241
|
+
console.log(` enabled : ${mcp.enabled ? pc.green("yes") : pc.gray("no")}`);
|
|
2242
|
+
console.log(` scope : ${pc.cyan(mcp.scope)}`);
|
|
2243
|
+
console.log(` prompted: ${mcp.prompted ? "yes" : "no"}`);
|
|
2244
|
+
if (mcp.version) console.log(` version : ${mcp.version}`);
|
|
2245
|
+
if (rows.length === 0) {
|
|
2246
|
+
console.log(pc.gray("\n (no agents configured)"));
|
|
2247
|
+
return;
|
|
2248
|
+
}
|
|
2249
|
+
console.log("\n Per-agent installation state:");
|
|
2250
|
+
let installedAnywhere = false;
|
|
2251
|
+
for (const row of rows) {
|
|
2252
|
+
const project = row.project === void 0 ? "—" : row.project ? pc.green("✓") : pc.gray("✗");
|
|
2253
|
+
const user = row.user === void 0 ? "—" : row.user ? pc.green("✓") : pc.gray("✗");
|
|
2254
|
+
if (row.project === true || row.user === true) installedAnywhere = true;
|
|
2255
|
+
console.log(` ${row.agent.padEnd(12)} project: ${project} user: ${user}`);
|
|
2256
|
+
}
|
|
2257
|
+
if (!mcp.enabled && installedAnywhere) {
|
|
2258
|
+
console.log(
|
|
2259
|
+
pc.yellow(
|
|
2260
|
+
"\n⚠️ Mismatch detected: .skillsrc says mcp.enabled=false, but the MCP entry IS present in at least one runtime config."
|
|
2261
|
+
)
|
|
2262
|
+
);
|
|
2263
|
+
console.log(
|
|
2264
|
+
pc.gray(
|
|
2265
|
+
" The MCP will work, but `sync` will not keep its config in step. Consider one of:"
|
|
2266
|
+
)
|
|
2267
|
+
);
|
|
2268
|
+
console.log(
|
|
2269
|
+
pc.gray(
|
|
2270
|
+
" • `ags mcp enable` — opt the project in (CLI keeps configs in step)"
|
|
2271
|
+
)
|
|
2272
|
+
);
|
|
2273
|
+
console.log(
|
|
2274
|
+
pc.gray(
|
|
2275
|
+
" • `ags mcp uninstall --from project` — remove the manual entry if it was unintended"
|
|
2276
|
+
)
|
|
2277
|
+
);
|
|
2278
|
+
} else if (mcp.enabled && !installedAnywhere) {
|
|
2279
|
+
console.log(
|
|
2280
|
+
pc.yellow(
|
|
2281
|
+
"\n⚠️ Mismatch detected: .skillsrc says mcp.enabled=true, but no runtime config has the MCP registered."
|
|
2282
|
+
)
|
|
2283
|
+
);
|
|
2284
|
+
console.log(
|
|
2285
|
+
pc.gray(
|
|
2286
|
+
" Run `ags mcp install` to write the entries, or `ags mcp disable` to clear the consent flag."
|
|
2287
|
+
)
|
|
2288
|
+
);
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
async actionEnable(config) {
|
|
2292
|
+
const mcp = config.mcp ?? defaultMcpConfig();
|
|
2293
|
+
if (mcp.scope === "disabled") {
|
|
2294
|
+
console.log(
|
|
2295
|
+
pc.yellow(
|
|
2296
|
+
'⚠️ scope is currently "disabled". Set a real scope first: `ags mcp scope project`'
|
|
2297
|
+
)
|
|
2298
|
+
);
|
|
2299
|
+
return;
|
|
2300
|
+
}
|
|
2301
|
+
config.mcp = { ...mcp, enabled: true, prompted: true };
|
|
2302
|
+
await this.configService.saveConfig(config);
|
|
2303
|
+
console.log(pc.green("✅ MCP enabled."));
|
|
2304
|
+
console.log(pc.gray(" Run `ags mcp install` to write configs now, or it happens on next `sync`."));
|
|
2305
|
+
}
|
|
2306
|
+
async actionDisable(config) {
|
|
2307
|
+
const mcp = config.mcp ?? defaultMcpConfig();
|
|
2308
|
+
config.mcp = { ...mcp, enabled: false, prompted: true };
|
|
2309
|
+
await this.configService.saveConfig(config);
|
|
2310
|
+
console.log(pc.yellow("🔕 MCP disabled."));
|
|
2311
|
+
console.log(pc.gray(" Existing config entries were NOT removed. Use `ags mcp uninstall` to clean them."));
|
|
2312
|
+
}
|
|
2313
|
+
async actionScope(config, options) {
|
|
2314
|
+
const scope = options.scope ?? options._?.[0];
|
|
2315
|
+
if (!scope || !VALID_SCOPES.includes(scope)) {
|
|
2316
|
+
console.log(
|
|
2317
|
+
pc.red(`Invalid scope. Valid: ${VALID_SCOPES.join(" | ")}`)
|
|
2318
|
+
);
|
|
2319
|
+
return;
|
|
2320
|
+
}
|
|
2321
|
+
const mcp = config.mcp ?? defaultMcpConfig();
|
|
2322
|
+
config.mcp = { ...mcp, scope, prompted: true };
|
|
2323
|
+
await this.configService.saveConfig(config);
|
|
2324
|
+
console.log(pc.green(`✅ MCP scope set to "${scope}".`));
|
|
2325
|
+
if (scope === "user") {
|
|
2326
|
+
console.log(
|
|
2327
|
+
pc.yellow(
|
|
2328
|
+
"⚠️ user-scope writes will be prompted per file during `sync` or `install`."
|
|
2329
|
+
)
|
|
2330
|
+
);
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
async actionInstall(config, agents, cwd, options) {
|
|
2334
|
+
const mcp = config.mcp ?? defaultMcpConfig();
|
|
2335
|
+
const scopeOverride = options.scope;
|
|
2336
|
+
const effectiveScope = scopeOverride ?? mcp.scope;
|
|
2337
|
+
if (scopeOverride && !VALID_SCOPES.includes(scopeOverride)) {
|
|
2338
|
+
console.log(
|
|
2339
|
+
pc.red(`Invalid --scope value. Valid: ${VALID_SCOPES.join(" | ")}`)
|
|
2340
|
+
);
|
|
2341
|
+
return;
|
|
2342
|
+
}
|
|
2343
|
+
if (effectiveScope === "disabled") {
|
|
2344
|
+
console.log(pc.yellow('Scope is "disabled" — nothing to install.'));
|
|
2345
|
+
return;
|
|
2346
|
+
}
|
|
2347
|
+
const userPrompt = async (agent, file) => {
|
|
2348
|
+
const { ok } = await inquirer.prompt([
|
|
2349
|
+
{
|
|
2350
|
+
type: "confirm",
|
|
2351
|
+
name: "ok",
|
|
2352
|
+
message: `Write user-scope MCP config for ${pc.cyan(agent)} at ${pc.gray(file)}?`,
|
|
2353
|
+
default: false
|
|
2354
|
+
}
|
|
2355
|
+
]);
|
|
2356
|
+
return ok;
|
|
2357
|
+
};
|
|
2358
|
+
const report = await this.mcpService.install({
|
|
2359
|
+
rootDir: cwd,
|
|
2360
|
+
agents,
|
|
2361
|
+
mcp: { ...mcp, enabled: true, scope: effectiveScope },
|
|
2362
|
+
userScopePrompt: userPrompt
|
|
2363
|
+
});
|
|
2364
|
+
this.printReport(report, cwd);
|
|
2365
|
+
}
|
|
2366
|
+
async actionUninstall(config, agents, cwd, options) {
|
|
2367
|
+
const from = options.from ?? "project";
|
|
2368
|
+
if (!["project", "user", "all"].includes(from)) {
|
|
2369
|
+
console.log(pc.red("--from must be: project | user | all"));
|
|
2370
|
+
return;
|
|
2371
|
+
}
|
|
2372
|
+
const { removed } = await this.mcpService.uninstall({
|
|
2373
|
+
rootDir: cwd,
|
|
2374
|
+
agents,
|
|
2375
|
+
from
|
|
2376
|
+
});
|
|
2377
|
+
if (removed.length === 0) {
|
|
2378
|
+
console.log(pc.gray(`Nothing to remove at scope "${from}".`));
|
|
2379
|
+
return;
|
|
2380
|
+
}
|
|
2381
|
+
console.log(pc.green(`✅ Removed agent-skills MCP entry from:`));
|
|
2382
|
+
for (const r of removed) {
|
|
2383
|
+
console.log(` - ${pc.cyan(r.agent)} → ${pc.gray(r.file)}`);
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
async actionSnippets(config, agents, cwd) {
|
|
2387
|
+
const mcp = config.mcp ?? defaultMcpConfig();
|
|
2388
|
+
const report = await this.mcpService.install({
|
|
2389
|
+
rootDir: cwd,
|
|
2390
|
+
agents,
|
|
2391
|
+
mcp: { ...mcp, enabled: true, scope: "snippets-only" }
|
|
2392
|
+
});
|
|
2393
|
+
if (report.snippets.length === 0) {
|
|
2394
|
+
console.log(pc.gray("No snippets generated (no supported agents in .skillsrc)."));
|
|
2395
|
+
return;
|
|
2396
|
+
}
|
|
2397
|
+
console.log(pc.green(`✅ Generated ${report.snippets.length} snippet(s):`));
|
|
2398
|
+
for (const s of report.snippets) {
|
|
2399
|
+
console.log(` - ${pc.cyan(s.agent)} → ${pc.gray(s.file)}`);
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
// ---- output ----
|
|
2403
|
+
printReport(report, cwd) {
|
|
2404
|
+
if (report.projectWrites.length > 0) {
|
|
2405
|
+
console.log(pc.bold("\n📁 Project-scope writes:"));
|
|
2406
|
+
for (const w of report.projectWrites) {
|
|
2407
|
+
const tag = w.action === "added" ? pc.green("+ added ") : w.action === "updated" ? pc.cyan("~ updated ") : pc.gray("= up-to-date");
|
|
2408
|
+
console.log(` ${tag} ${w.agent.padEnd(12)} ${pc.gray(w.file)}`);
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
if (report.userWrites.length > 0) {
|
|
2412
|
+
console.log(pc.bold("\n🏠 User-scope writes:"));
|
|
2413
|
+
for (const w of report.userWrites) {
|
|
2414
|
+
console.log(` ${pc.green("+ wrote ")} ${w.agent.padEnd(12)} ${pc.gray(path.relative(cwd, w.file))}`);
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
if (report.declined.length > 0) {
|
|
2418
|
+
console.log(pc.bold("\n⏭ User-scope declined:"));
|
|
2419
|
+
for (const d of report.declined) {
|
|
2420
|
+
console.log(` ${pc.gray("-")} ${d.agent.padEnd(12)} ${pc.gray(d.file)}`);
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
if (report.snippets.length > 0) {
|
|
2424
|
+
console.log(pc.bold("\n📄 Snippet files:"));
|
|
2425
|
+
for (const s of report.snippets) {
|
|
2426
|
+
console.log(` ${pc.gray(s.file)}`);
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
2429
|
+
if (report.unsupported.length > 0) {
|
|
2430
|
+
console.log(
|
|
2431
|
+
pc.gray(
|
|
2432
|
+
`
|
|
2433
|
+
(skipped — no MCP support yet: ${report.unsupported.join(", ")})`
|
|
2434
|
+
)
|
|
2435
|
+
);
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
1796
2439
|
class AgentBridgeService {
|
|
1797
2440
|
/**
|
|
1798
2441
|
* Bridges native agent rule files to AGENTS.md by creating discovery instructions.
|
|
2442
|
+
*
|
|
2443
|
+
* The MCP runtime-enforcement block lives in `AGENTS.md` (single source of
|
|
2444
|
+
* truth, generated by `IndexGeneratorService.assembleRouterIndex`). Rule
|
|
2445
|
+
* files generated here just instruct the agent to read AGENTS.md first —
|
|
2446
|
+
* they do NOT duplicate the MCP block. This keeps the message in one place
|
|
2447
|
+
* so it can't drift between surfaces.
|
|
2448
|
+
*
|
|
1799
2449
|
* @param rootDir Project root directory
|
|
1800
2450
|
* @param agents List of agents to generate rules for
|
|
1801
2451
|
*/
|
|
@@ -1885,7 +2535,7 @@ applyTo: '**/*'
|
|
|
1885
2535
|
"",
|
|
1886
2536
|
"## Agent Protocol",
|
|
1887
2537
|
"",
|
|
1888
|
-
"See `AGENTS.md` for the Zero-Trust skill loading protocol (applies to all AI agents).",
|
|
2538
|
+
"See `AGENTS.md` for the Zero-Trust skill loading protocol (applies to all AI agents) and the MCP runtime-enforcement section (when enabled).",
|
|
1889
2539
|
"",
|
|
1890
2540
|
"## Self-Learning Protocol",
|
|
1891
2541
|
"",
|
|
@@ -2354,7 +3004,7 @@ class IndexGeneratorServiceImpl extends IndexGeneratorService {
|
|
|
2354
3004
|
);
|
|
2355
3005
|
return lines.join("\n");
|
|
2356
3006
|
}
|
|
2357
|
-
async assembleRouterIndex(baseDir, allowedCategories) {
|
|
3007
|
+
async assembleRouterIndex(baseDir, allowedCategories, mcpEnabled = false) {
|
|
2358
3008
|
const fileRouting = await this.reader.loadFileRouting(baseDir);
|
|
2359
3009
|
let availableCategories = [];
|
|
2360
3010
|
if (await fs.pathExists(baseDir)) {
|
|
@@ -2403,12 +3053,35 @@ class IndexGeneratorServiceImpl extends IndexGeneratorService {
|
|
|
2403
3053
|
"| QE workflow | `<SKILLS>/quality-engineering/_INDEX.md` |"
|
|
2404
3054
|
);
|
|
2405
3055
|
}
|
|
3056
|
+
const mcpStatusNote = mcpEnabled ? "> [!TIP] **This project has the MCP server enabled in `.skillsrc`** — `sync` keeps your runtime configs in step. Run `ags mcp status` to verify per-agent installation." : "> [!NOTE] To enable MCP-managed installs in this project, run `ags mcp enable` (or edit `.skillsrc`). The MCP works fine if you registered it manually too.";
|
|
3057
|
+
const mcpBlock = [
|
|
3058
|
+
"## 🔌 Runtime Enforcement via MCP",
|
|
3059
|
+
"",
|
|
3060
|
+
"If the `agent-skills-standard` MCP server is registered in your runtime (check your tool list — look for `load_skills_for_files`), **prefer those tools over manually walking the router below**. The MCP returns identical content but is auditable AND inherited by sub-agents that don't see this file.",
|
|
3061
|
+
"",
|
|
3062
|
+
"| Tool | When to call it |",
|
|
3063
|
+
"| --- | --- |",
|
|
3064
|
+
"| `load_skills_for_files(files=[...])` | Before editing/reviewing any source file |",
|
|
3065
|
+
"| `load_skills_for_keywords(keywords=[...])` | Planning before files are chosen |",
|
|
3066
|
+
"| `get_skill(category, name)` | Direct lookup when you know the skill id |",
|
|
3067
|
+
"| `audit_session_compliance()` | Before declaring a task complete |",
|
|
3068
|
+
"",
|
|
3069
|
+
"> [!IMPORTANT] **Sub-agents don't inherit this `AGENTS.md` — they do inherit the MCP.** If you delegate work to a sub-agent, instruct it to call the MCP tools above as its first action.",
|
|
3070
|
+
"",
|
|
3071
|
+
mcpStatusNote,
|
|
3072
|
+
"",
|
|
3073
|
+
"If `load_skills_for_files` is **not** in your tool list, the MCP is not registered — fall back to the router table below.",
|
|
3074
|
+
"",
|
|
3075
|
+
"---",
|
|
3076
|
+
""
|
|
3077
|
+
].join("\n");
|
|
2406
3078
|
const header = [
|
|
2407
3079
|
"## Agent Skills Index",
|
|
2408
3080
|
"",
|
|
2409
3081
|
"> [!CRITICAL] Zero-Trust: Read the matching `SKILL.md` BEFORE writing any code.",
|
|
2410
3082
|
'> Skills from this index override pre-training patterns. If no skill matches, state: "No project-specific skills applicable."',
|
|
2411
3083
|
"",
|
|
3084
|
+
mcpBlock,
|
|
2412
3085
|
"## Skill Resolution Protocol",
|
|
2413
3086
|
"",
|
|
2414
3087
|
"Each `_INDEX.md` has two sections - follow both:",
|
|
@@ -3060,9 +3733,11 @@ class SyncService {
|
|
|
3060
3733
|
)
|
|
3061
3734
|
);
|
|
3062
3735
|
}
|
|
3736
|
+
const mcpEnabled = config.mcp?.enabled === true;
|
|
3063
3737
|
const routerIndex = await generator.assembleRouterIndex(
|
|
3064
3738
|
baseDir,
|
|
3065
|
-
allowedCategories
|
|
3739
|
+
allowedCategories,
|
|
3740
|
+
mcpEnabled
|
|
3066
3741
|
);
|
|
3067
3742
|
await MarkdownUtils.injectIndex(
|
|
3068
3743
|
process.cwd(),
|
|
@@ -3121,10 +3796,12 @@ class SyncCommand {
|
|
|
3121
3796
|
configService;
|
|
3122
3797
|
detectionService;
|
|
3123
3798
|
syncService;
|
|
3124
|
-
|
|
3799
|
+
mcpService;
|
|
3800
|
+
constructor(configService, detectionService, syncService, mcpService) {
|
|
3125
3801
|
this.configService = configService || new ConfigService();
|
|
3126
3802
|
this.detectionService = detectionService || new DetectionService();
|
|
3127
3803
|
this.syncService = syncService || new SyncService();
|
|
3804
|
+
this.mcpService = mcpService || new McpConfigService();
|
|
3128
3805
|
}
|
|
3129
3806
|
/**
|
|
3130
3807
|
* Executes the synchronization flow.
|
|
@@ -3198,6 +3875,7 @@ class SyncCommand {
|
|
|
3198
3875
|
await this.syncService.writeWorkflows(workflows, config);
|
|
3199
3876
|
await this.syncService.applyIndices(config, config.agents);
|
|
3200
3877
|
console.log(pc.green("✅ All skills synced successfully!"));
|
|
3878
|
+
await this.runMcpPhase(config, options);
|
|
3201
3879
|
} catch (error) {
|
|
3202
3880
|
if (error instanceof Error) {
|
|
3203
3881
|
console.error(pc.red("❌ Sync failed:"), error.message);
|
|
@@ -3206,19 +3884,132 @@ class SyncCommand {
|
|
|
3206
3884
|
}
|
|
3207
3885
|
}
|
|
3208
3886
|
}
|
|
3887
|
+
/**
|
|
3888
|
+
* Phase 7 — MCP integration. Three paths:
|
|
3889
|
+
* A. Never prompted yet: ask once now (with full value-prop), persist decision.
|
|
3890
|
+
* B. Already prompted, mcp.enabled=true: dispatch install at the configured scope.
|
|
3891
|
+
* C. Already prompted, mcp.enabled=false: skip silently.
|
|
3892
|
+
*
|
|
3893
|
+
* In non-interactive mode (no TTY) and never-prompted: skip with a hint, do
|
|
3894
|
+
* not assume consent. Users can opt in later via `ags mcp enable`.
|
|
3895
|
+
*/
|
|
3896
|
+
async runMcpPhase(config, options) {
|
|
3897
|
+
const mcp = config.mcp ?? defaultMcpConfig();
|
|
3898
|
+
const agents = config.agents ?? [];
|
|
3899
|
+
if (!mcp.prompted) {
|
|
3900
|
+
if (!process.stdin.isTTY && !options.yes) {
|
|
3901
|
+
console.log(
|
|
3902
|
+
pc.gray(
|
|
3903
|
+
"\nℹ️ MCP integration not yet configured. Run `ags mcp enable` (or `ags mcp scope project`) when ready."
|
|
3904
|
+
)
|
|
3905
|
+
);
|
|
3906
|
+
return;
|
|
3907
|
+
}
|
|
3908
|
+
const decided = await this.promptMcpConsent(options);
|
|
3909
|
+
config.mcp = {
|
|
3910
|
+
enabled: decided.enabled,
|
|
3911
|
+
scope: decided.enabled ? decided.scope : "disabled",
|
|
3912
|
+
prompted: true
|
|
3913
|
+
};
|
|
3914
|
+
await this.configService.saveConfig(config);
|
|
3915
|
+
}
|
|
3916
|
+
const finalMcp = config.mcp ?? defaultMcpConfig();
|
|
3917
|
+
if (!finalMcp.enabled || finalMcp.scope === "disabled") {
|
|
3918
|
+
return;
|
|
3919
|
+
}
|
|
3920
|
+
const userScopePrompt = options.yes ? async () => true : async (agent, file) => {
|
|
3921
|
+
const { ok } = await inquirer.prompt([
|
|
3922
|
+
{
|
|
3923
|
+
type: "confirm",
|
|
3924
|
+
name: "ok",
|
|
3925
|
+
message: `Write user-scope MCP config for ${pc.cyan(agent)} at ${pc.gray(file)}?`,
|
|
3926
|
+
default: false
|
|
3927
|
+
}
|
|
3928
|
+
]);
|
|
3929
|
+
return ok;
|
|
3930
|
+
};
|
|
3931
|
+
console.log(pc.cyan("\n🔌 Wiring MCP server..."));
|
|
3932
|
+
const report = await this.mcpService.install({
|
|
3933
|
+
rootDir: process.cwd(),
|
|
3934
|
+
agents,
|
|
3935
|
+
mcp: finalMcp,
|
|
3936
|
+
userScopePrompt
|
|
3937
|
+
});
|
|
3938
|
+
for (const w of report.projectWrites) {
|
|
3939
|
+
const tag = w.action === "added" ? pc.green(" + added ") : w.action === "updated" ? pc.cyan(" ~ updated ") : pc.gray(" = up-to-date");
|
|
3940
|
+
console.log(`${tag} ${w.agent.padEnd(12)} ${pc.gray(w.file)}`);
|
|
3941
|
+
}
|
|
3942
|
+
for (const w of report.userWrites) {
|
|
3943
|
+
console.log(` ${pc.green("+ wrote ")} ${w.agent.padEnd(12)} ${pc.gray(w.file)}`);
|
|
3944
|
+
}
|
|
3945
|
+
for (const d of report.declined) {
|
|
3946
|
+
console.log(` ${pc.gray("- ")} ${d.agent.padEnd(12)} ${pc.gray(d.file)} (declined)`);
|
|
3947
|
+
}
|
|
3948
|
+
if (report.snippets.length > 0) {
|
|
3949
|
+
console.log(
|
|
3950
|
+
pc.gray(` ${report.snippets.length} snippet(s) written to ./mcp-config-snippets/`)
|
|
3951
|
+
);
|
|
3952
|
+
}
|
|
3953
|
+
if (report.unsupported.length > 0) {
|
|
3954
|
+
console.log(
|
|
3955
|
+
pc.gray(` (skipped — no MCP support yet: ${report.unsupported.join(", ")})`)
|
|
3956
|
+
);
|
|
3957
|
+
}
|
|
3958
|
+
}
|
|
3959
|
+
async promptMcpConsent(options) {
|
|
3960
|
+
if (options.yes) {
|
|
3961
|
+
return { enabled: true, scope: "project" };
|
|
3962
|
+
}
|
|
3963
|
+
console.log(pc.bold("\n🔌 MCP server setup\n"));
|
|
3964
|
+
console.log("agent-skills-standard ships an optional MCP server that serves your skills");
|
|
3965
|
+
console.log("to AI agents at runtime. Quick comparison:\n");
|
|
3966
|
+
console.log(pc.bold(" WITHOUT MCP:") + " sub-agents skip skill loading; no audit trail");
|
|
3967
|
+
console.log(pc.bold(" WITH MCP: ") + " every sub-agent can call load_skills_for_files; auditable\n");
|
|
3968
|
+
const decision = await inquirer.prompt([
|
|
3969
|
+
{
|
|
3970
|
+
type: "confirm",
|
|
3971
|
+
name: "enabled",
|
|
3972
|
+
message: "Enable MCP server integration?",
|
|
3973
|
+
default: true
|
|
3974
|
+
},
|
|
3975
|
+
{
|
|
3976
|
+
type: "list",
|
|
3977
|
+
name: "scope",
|
|
3978
|
+
message: "Where should sync write MCP configs?",
|
|
3979
|
+
when: (a) => a.enabled === true,
|
|
3980
|
+
choices: [
|
|
3981
|
+
{
|
|
3982
|
+
name: "project (recommended) — only files inside this project",
|
|
3983
|
+
value: "project"
|
|
3984
|
+
},
|
|
3985
|
+
{
|
|
3986
|
+
name: "user — also $HOME files (sync prompts each)",
|
|
3987
|
+
value: "user"
|
|
3988
|
+
},
|
|
3989
|
+
{
|
|
3990
|
+
name: "snippets-only — never edits any runtime config",
|
|
3991
|
+
value: "snippets-only"
|
|
3992
|
+
}
|
|
3993
|
+
],
|
|
3994
|
+
default: "project"
|
|
3995
|
+
}
|
|
3996
|
+
]);
|
|
3997
|
+
return decision;
|
|
3998
|
+
}
|
|
3209
3999
|
}
|
|
3210
|
-
const version = "2.
|
|
4000
|
+
const version = "2.2.0";
|
|
3211
4001
|
const pkg = {
|
|
3212
4002
|
version
|
|
3213
4003
|
};
|
|
3214
4004
|
class UpgradeCommand {
|
|
4005
|
+
currentVersion = pkg.version;
|
|
3215
4006
|
/**
|
|
3216
4007
|
* Executes the upgrade flow.
|
|
3217
4008
|
* @param options Command options, including `dryRun` to check for updates without installing.
|
|
3218
4009
|
*/
|
|
3219
4010
|
async run(options) {
|
|
3220
4011
|
console.log(pc.cyan("🔍 Checking for updates..."));
|
|
3221
|
-
const currentVersion =
|
|
4012
|
+
const currentVersion = this.currentVersion;
|
|
3222
4013
|
let latestVersion = null;
|
|
3223
4014
|
try {
|
|
3224
4015
|
latestVersion = child_process.execSync("npm view agent-skills-standard version", {
|
|
@@ -3737,7 +4528,7 @@ dotenv.config();
|
|
|
3737
4528
|
const program = new commander.Command();
|
|
3738
4529
|
program.name("agent-skills-standard").description(
|
|
3739
4530
|
"A CLI to manage and sync AI agent skills for Cursor, Claude, Copilot, Windsurf, and more."
|
|
3740
|
-
).version("2.
|
|
4531
|
+
).version("2.2.0");
|
|
3741
4532
|
program.command("init").description("Initialize a .skillsrc configuration file interactively").action(async () => {
|
|
3742
4533
|
const init = new InitCommand();
|
|
3743
4534
|
await init.run();
|
|
@@ -3786,5 +4577,15 @@ program.command("upgrade").description("Upgrade the CLI to the latest version").
|
|
|
3786
4577
|
const cmd = new UpgradeCommand();
|
|
3787
4578
|
await cmd.run(options);
|
|
3788
4579
|
});
|
|
4580
|
+
program.command("mcp <action> [scope]").description(
|
|
4581
|
+
"Manage the optional MCP server integration. Actions: status | enable | disable | scope <project|user|snippets-only|disabled> | install | uninstall | snippets"
|
|
4582
|
+
).option("--scope <scope>", "Override scope for install (project|user|snippets-only|disabled)").option("--from <from>", "For uninstall: project | user | all (default: project)").action(
|
|
4583
|
+
async (action, positional, opts) => {
|
|
4584
|
+
const merged = { ...opts };
|
|
4585
|
+
if (positional) merged.scope = opts.scope ?? positional;
|
|
4586
|
+
const cmd = new McpCommand();
|
|
4587
|
+
await cmd.run(action, merged);
|
|
4588
|
+
}
|
|
4589
|
+
);
|
|
3789
4590
|
program.parse();
|
|
3790
4591
|
//# sourceMappingURL=index.js.map
|