@webiny/mcp 6.3.0 → 6.4.0-beta.1
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/agents/claude.js +23 -34
- package/agents/claude.js.map +1 -1
- package/agents/cline.js +17 -30
- package/agents/cline.js.map +1 -1
- package/agents/copilot.js +49 -63
- package/agents/copilot.js.map +1 -1
- package/agents/cursor.js +23 -34
- package/agents/cursor.js.map +1 -1
- package/agents/discover.js +17 -20
- package/agents/discover.js.map +1 -1
- package/agents/instructions.js +81 -20
- package/agents/instructions.js.map +1 -1
- package/agents/kiro.js +23 -34
- package/agents/kiro.js.map +1 -1
- package/agents/opencode.js +53 -76
- package/agents/opencode.js.map +1 -1
- package/agents/shared.js +62 -113
- package/agents/shared.js.map +1 -1
- package/agents/types.js +0 -3
- package/agents/windsurf.js +23 -34
- package/agents/windsurf.js.map +1 -1
- package/bin.js +0 -2
- package/cli/ConfigureMcp.js +31 -38
- package/cli/ConfigureMcp.js.map +1 -1
- package/cli/McpServer.js +145 -184
- package/cli/McpServer.js.map +1 -1
- package/cli.js +51 -50
- package/cli.js.map +1 -1
- package/index.js +0 -7
- package/package.json +4 -4
- package/skills/generated/admin/website-builder/SKILL.md +6 -1
- package/skills/generated/api/SKILL.md +6 -1
- package/skills/generated/api/cms/SKILL.md +49 -1
- package/skills/generated/api/webhooks/SKILL.md +106 -0
- package/ui.js +20 -28
- package/ui.js.map +1 -1
- package/agents/types.js.map +0 -1
- package/bin.js.map +0 -1
- package/index.js.map +0 -1
package/cli/ConfigureMcp.js
CHANGED
|
@@ -1,44 +1,37 @@
|
|
|
1
1
|
import { ConsoleUi } from "../ui.js";
|
|
2
2
|
import { discoverAgents } from "../agents/discover.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
await printInstructions();
|
|
11
|
-
return;
|
|
12
|
-
}
|
|
13
|
-
const agents = await discoverAgents();
|
|
14
|
-
const supported = Array.from(agents.keys());
|
|
15
|
-
const target = params.agent;
|
|
16
|
-
if (!target) {
|
|
17
|
-
ui.text("Available agents:");
|
|
18
|
-
ui.emptyLine();
|
|
19
|
-
for (const [slug, {
|
|
20
|
-
preset
|
|
21
|
-
}] of agents) {
|
|
22
|
-
ui.text(` ${slug.padEnd(12)} ${preset.displayName}`);
|
|
3
|
+
async function configureMcp(params = {}) {
|
|
4
|
+
const ui = params.ui ?? new ConsoleUi();
|
|
5
|
+
const cwd = params.cwd ?? process.cwd();
|
|
6
|
+
if (params.instructions) {
|
|
7
|
+
const { printInstructions } = await import("../agents/instructions.js");
|
|
8
|
+
await printInstructions();
|
|
9
|
+
return;
|
|
23
10
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
11
|
+
const agents = await discoverAgents();
|
|
12
|
+
const supported = Array.from(agents.keys());
|
|
13
|
+
const target = params.agent;
|
|
14
|
+
if (!target) {
|
|
15
|
+
ui.text("Available agents:");
|
|
16
|
+
ui.emptyLine();
|
|
17
|
+
for (const [slug, { preset }] of agents)ui.text(` ${slug.padEnd(12)} ${preset.displayName}`);
|
|
18
|
+
ui.emptyLine();
|
|
19
|
+
ui.text("Usage: npx webiny-mcp configure <agent>");
|
|
20
|
+
ui.text("For other agents, run: npx webiny-mcp configure --instructions");
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (!agents.has(target)) {
|
|
24
|
+
ui.error(`Unknown agent "${target}".`);
|
|
25
|
+
ui.text(`Supported: ${supported.join(", ")}`);
|
|
26
|
+
ui.text("For other agents, run: npx webiny-mcp configure --instructions");
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
const { init } = agents.get(target);
|
|
30
|
+
await init({
|
|
31
|
+
ui,
|
|
32
|
+
cwd
|
|
33
|
+
});
|
|
42
34
|
}
|
|
35
|
+
export { configureMcp };
|
|
43
36
|
|
|
44
37
|
//# sourceMappingURL=ConfigureMcp.js.map
|
package/cli/ConfigureMcp.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"file":"cli/ConfigureMcp.js","sources":["../../src/cli/ConfigureMcp.ts"],"sourcesContent":["import type { IUi } from \"../ui.js\";\nimport { ConsoleUi } from \"../ui.js\";\nimport { discoverAgents } from \"../agents/discover.js\";\n\nexport interface IConfigureMcpParams {\n agent?: string;\n instructions?: boolean;\n ui?: IUi;\n cwd?: string;\n}\n\nexport async function configureMcp(params: IConfigureMcpParams = {}): Promise<void> {\n const ui = params.ui ?? new ConsoleUi();\n const cwd = params.cwd ?? process.cwd();\n\n if (params.instructions) {\n const { printInstructions } = await import(\"../agents/instructions.js\");\n await printInstructions();\n return;\n }\n\n const agents = await discoverAgents();\n const supported = Array.from(agents.keys());\n const target = params.agent;\n\n if (!target) {\n ui.text(\"Available agents:\");\n ui.emptyLine();\n for (const [slug, { preset }] of agents) {\n ui.text(` ${slug.padEnd(12)} ${preset.displayName}`);\n }\n ui.emptyLine();\n ui.text(\"Usage: npx webiny-mcp configure <agent>\");\n ui.text(\"For other agents, run: npx webiny-mcp configure --instructions\");\n return;\n }\n\n if (!agents.has(target)) {\n ui.error(`Unknown agent \"${target}\".`);\n ui.text(`Supported: ${supported.join(\", \")}`);\n ui.text(\"For other agents, run: npx webiny-mcp configure --instructions\");\n process.exit(1);\n }\n\n const { init } = agents.get(target)!;\n await init({ ui, cwd });\n}\n"],"names":["configureMcp","params","ui","ConsoleUi","cwd","process","printInstructions","agents","discoverAgents","supported","Array","target","slug","preset","init"],"mappings":";;AAWO,eAAeA,aAAaC,SAA8B,CAAC,CAAC;IAC/D,MAAMC,KAAKD,OAAO,EAAE,IAAI,IAAIE;IAC5B,MAAMC,MAAMH,OAAO,GAAG,IAAII,QAAQ,GAAG;IAErC,IAAIJ,OAAO,YAAY,EAAE;QACrB,MAAM,EAAEK,iBAAiB,EAAE,GAAG,MAAM,MAAM,CAAC;QAC3C,MAAMA;QACN;IACJ;IAEA,MAAMC,SAAS,MAAMC;IACrB,MAAMC,YAAYC,MAAM,IAAI,CAACH,OAAO,IAAI;IACxC,MAAMI,SAASV,OAAO,KAAK;IAE3B,IAAI,CAACU,QAAQ;QACTT,GAAG,IAAI,CAAC;QACRA,GAAG,SAAS;QACZ,KAAK,MAAM,CAACU,MAAM,EAAEC,MAAM,EAAE,CAAC,IAAIN,OAC7BL,GAAG,IAAI,CAAC,CAAC,EAAE,EAAEU,KAAK,MAAM,CAAC,IAAI,CAAC,EAAEC,OAAO,WAAW,EAAE;QAExDX,GAAG,SAAS;QACZA,GAAG,IAAI,CAAC;QACRA,GAAG,IAAI,CAAC;QACR;IACJ;IAEA,IAAI,CAACK,OAAO,GAAG,CAACI,SAAS;QACrBT,GAAG,KAAK,CAAC,CAAC,eAAe,EAAES,OAAO,EAAE,CAAC;QACrCT,GAAG,IAAI,CAAC,CAAC,WAAW,EAAEO,UAAU,IAAI,CAAC,OAAO;QAC5CP,GAAG,IAAI,CAAC;QACRG,QAAQ,IAAI,CAAC;IACjB;IAEA,MAAM,EAAES,IAAI,EAAE,GAAGP,OAAO,GAAG,CAACI;IAC5B,MAAMG,KAAK;QAAEZ;QAAIE;IAAI;AACzB"}
|
package/cli/McpServer.js
CHANGED
|
@@ -1,213 +1,174 @@
|
|
|
1
|
-
import { readFileSync, readdirSync
|
|
2
|
-
import { join, resolve
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from "fs";
|
|
2
|
+
import { dirname, join, resolve } from "path";
|
|
3
3
|
import { fileURLToPath } from "url";
|
|
4
4
|
import { createRequire } from "module";
|
|
5
|
-
import
|
|
5
|
+
import front_matter from "front-matter";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
8
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
// ---------------------------------------------------------------------------
|
|
12
|
-
// Skill discovery
|
|
13
|
-
// ---------------------------------------------------------------------------
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Recursively find all SKILL.md files under `dir`.
|
|
17
|
-
*/
|
|
9
|
+
const McpServer_dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
10
|
function findSkillFiles(dir) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (entry.isDirectory()) {
|
|
28
|
-
results.push(...findSkillFiles(fullPath));
|
|
29
|
-
} else if (entry.isFile() && entry.name === "SKILL.md") {
|
|
30
|
-
results.push(fullPath);
|
|
11
|
+
if (!existsSync(dir)) return [];
|
|
12
|
+
const results = [];
|
|
13
|
+
for (const entry of readdirSync(dir, {
|
|
14
|
+
withFileTypes: true
|
|
15
|
+
})){
|
|
16
|
+
const fullPath = join(dir, entry.name);
|
|
17
|
+
if (entry.isDirectory()) results.push(...findSkillFiles(fullPath));
|
|
18
|
+
else if (entry.isFile() && "SKILL.md" === entry.name) results.push(fullPath);
|
|
31
19
|
}
|
|
32
|
-
|
|
33
|
-
return results;
|
|
20
|
+
return results;
|
|
34
21
|
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Discover skills from multiple directories. First match wins (higher-priority dirs first).
|
|
38
|
-
*/
|
|
39
22
|
function discoverSkills(skillsDirs) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
for (const filePath of findSkillFiles(dir)) {
|
|
43
|
-
try {
|
|
23
|
+
const skills = new Map();
|
|
24
|
+
for (const dir of skillsDirs)for (const filePath of findSkillFiles(dir))try {
|
|
44
25
|
const raw = readFileSync(filePath, "utf8");
|
|
45
|
-
const parsed =
|
|
46
|
-
const {
|
|
47
|
-
name,
|
|
48
|
-
description
|
|
49
|
-
} = parsed.attributes;
|
|
26
|
+
const parsed = front_matter(raw);
|
|
27
|
+
const { name, description } = parsed.attributes;
|
|
50
28
|
if (!name || !description) {
|
|
51
|
-
|
|
52
|
-
|
|
29
|
+
console.error(`[webiny-mcp] skipping ${filePath}: missing name or description`);
|
|
30
|
+
continue;
|
|
53
31
|
}
|
|
54
32
|
if (!skills.has(name)) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
33
|
+
const context = parsed.attributes.context || "webiny-extensions";
|
|
34
|
+
skills.set(name, {
|
|
35
|
+
name,
|
|
36
|
+
description,
|
|
37
|
+
context,
|
|
38
|
+
filePath
|
|
39
|
+
});
|
|
62
40
|
}
|
|
63
|
-
|
|
41
|
+
} catch (err) {
|
|
64
42
|
console.error(`[webiny-mcp] error reading ${filePath}:`, err);
|
|
65
|
-
}
|
|
66
43
|
}
|
|
67
|
-
|
|
68
|
-
return skills;
|
|
44
|
+
return skills;
|
|
69
45
|
}
|
|
70
|
-
|
|
71
|
-
// ---------------------------------------------------------------------------
|
|
72
|
-
// Helpers
|
|
73
|
-
// ---------------------------------------------------------------------------
|
|
74
|
-
|
|
75
46
|
function getVersion() {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
47
|
+
try {
|
|
48
|
+
return createRequire(import.meta.url)("../../package.json").version;
|
|
49
|
+
} catch {
|
|
50
|
+
return "0.0.0";
|
|
51
|
+
}
|
|
81
52
|
}
|
|
82
53
|
function buildCatalog(skills) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
for (const [context, contextSkills] of [...groups.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
101
|
-
lines.push(`## ${context}`, "");
|
|
102
|
-
const desc = contextDescriptions[context];
|
|
103
|
-
if (desc) {
|
|
104
|
-
lines.push(desc, "");
|
|
54
|
+
const lines = [
|
|
55
|
+
`# Webiny Skills (v${getVersion()})`,
|
|
56
|
+
""
|
|
57
|
+
];
|
|
58
|
+
if (0 === skills.size) {
|
|
59
|
+
lines.push("_(No skills found. Add SKILL.md files with front-matter to a skills directory.)_");
|
|
60
|
+
return lines.join("\n");
|
|
61
|
+
}
|
|
62
|
+
const contextDescriptions = {
|
|
63
|
+
"webiny-extensions": "Use these skills when writing Webiny extensions (usually in `extensions/`) or making changes to `webiny.config.tsx` (user project development).",
|
|
64
|
+
"webiny-packages": "Use these skills when writing code in `packages/` (core Webiny framework development)."
|
|
65
|
+
};
|
|
66
|
+
const groups = new Map();
|
|
67
|
+
for (const skill of skills.values()){
|
|
68
|
+
const list = groups.get(skill.context) || [];
|
|
69
|
+
list.push(skill);
|
|
70
|
+
groups.set(skill.context, list);
|
|
105
71
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
72
|
+
for (const [context, contextSkills] of [
|
|
73
|
+
...groups.entries()
|
|
74
|
+
].sort((a, b)=>a[0].localeCompare(b[0]))){
|
|
75
|
+
lines.push(`## ${context}`, "");
|
|
76
|
+
const desc = contextDescriptions[context];
|
|
77
|
+
if (desc) lines.push(desc, "");
|
|
78
|
+
lines.push("| Skill | Description |");
|
|
79
|
+
lines.push("|---|---|");
|
|
80
|
+
for (const skill of contextSkills.sort((a, b)=>a.name.localeCompare(b.name))){
|
|
81
|
+
const skillDesc = skill.description.replace(/\n/g, " ").trim();
|
|
82
|
+
lines.push(`| \`${skill.name}\` | ${skillDesc} |`);
|
|
83
|
+
}
|
|
84
|
+
lines.push("");
|
|
111
85
|
}
|
|
112
|
-
lines.
|
|
113
|
-
}
|
|
114
|
-
return lines.join("\n");
|
|
86
|
+
return lines.join("\n");
|
|
115
87
|
}
|
|
116
88
|
function readSkillContent(skill) {
|
|
117
|
-
|
|
118
|
-
|
|
89
|
+
const raw = readFileSync(skill.filePath, "utf8");
|
|
90
|
+
return front_matter(raw).body;
|
|
119
91
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
// In-memory cache: populated on first list, reused by get.
|
|
142
|
-
let skillsCache = null;
|
|
143
|
-
function getSkills() {
|
|
144
|
-
if (!skillsCache) {
|
|
145
|
-
skillsCache = discoverSkills(skillsDirs);
|
|
146
|
-
console.error(`[webiny-mcp] discovered ${skillsCache.size} skill(s)`);
|
|
147
|
-
}
|
|
148
|
-
return skillsCache;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// ---------------------------------------------------------------
|
|
152
|
-
// MCP server
|
|
153
|
-
// ---------------------------------------------------------------
|
|
154
|
-
|
|
155
|
-
const server = new McpServer({
|
|
156
|
-
name: "webiny",
|
|
157
|
-
version: getVersion()
|
|
158
|
-
});
|
|
159
|
-
server.registerTool("list_webiny_skills", {
|
|
160
|
-
title: "List Webiny Skills",
|
|
161
|
-
description: "Returns a catalog of all available Webiny skills with names and descriptions. " + "Always call this first when working on anything Webiny-related, then call " + "get_webiny_skill to load the specific skill you need.",
|
|
162
|
-
inputSchema: {},
|
|
163
|
-
annotations: {
|
|
164
|
-
readOnlyHint: true
|
|
165
|
-
}
|
|
166
|
-
}, async () => ({
|
|
167
|
-
content: [{
|
|
168
|
-
type: "text",
|
|
169
|
-
text: buildCatalog(getSkills())
|
|
170
|
-
}]
|
|
171
|
-
}));
|
|
172
|
-
server.registerTool("get_webiny_skill", {
|
|
173
|
-
title: "Get Webiny Skill",
|
|
174
|
-
description: "Loads the full Webiny documentation for a specific skill. " + "Call list_webiny_skills first to see available skill names.",
|
|
175
|
-
inputSchema: {
|
|
176
|
-
topic: z.string().describe("Skill name — use exact names from list_webiny_skills")
|
|
177
|
-
},
|
|
178
|
-
annotations: {
|
|
179
|
-
readOnlyHint: true
|
|
180
|
-
}
|
|
181
|
-
}, async ({
|
|
182
|
-
topic
|
|
183
|
-
}) => {
|
|
184
|
-
const skills = getSkills();
|
|
185
|
-
const skill = skills.get(topic);
|
|
186
|
-
if (!skill) {
|
|
187
|
-
const available = [...skills.keys()].sort();
|
|
188
|
-
return {
|
|
189
|
-
content: [{
|
|
190
|
-
type: "text",
|
|
191
|
-
text: `Skill not found: "${topic}".\n\n` + `Available skills: ${available.join(", ") || "(none)"}.`
|
|
192
|
-
}],
|
|
193
|
-
isError: true
|
|
194
|
-
};
|
|
92
|
+
async function startMcpServer(params = {}) {
|
|
93
|
+
const cwd = process.cwd();
|
|
94
|
+
const builtInSkillsDir = join(McpServer_dirname, "..", "skills");
|
|
95
|
+
const skillsOverride = params.skills;
|
|
96
|
+
const additionalSkillsDirs = params.additionalSkills || [];
|
|
97
|
+
const baseDir = skillsOverride ? resolve(cwd, skillsOverride) : builtInSkillsDir;
|
|
98
|
+
const skillsDirs = [
|
|
99
|
+
...[
|
|
100
|
+
...additionalSkillsDirs
|
|
101
|
+
].map((p)=>resolve(cwd, p)).reverse(),
|
|
102
|
+
baseDir
|
|
103
|
+
];
|
|
104
|
+
if (skillsOverride) console.error(`[webiny-mcp] skills override: ${baseDir}`);
|
|
105
|
+
for (const d of additionalSkillsDirs)console.error(`[webiny-mcp] additional skills: ${resolve(cwd, d)}`);
|
|
106
|
+
let skillsCache = null;
|
|
107
|
+
function getSkills() {
|
|
108
|
+
if (!skillsCache) {
|
|
109
|
+
skillsCache = discoverSkills(skillsDirs);
|
|
110
|
+
console.error(`[webiny-mcp] discovered ${skillsCache.size} skill(s)`);
|
|
111
|
+
}
|
|
112
|
+
return skillsCache;
|
|
195
113
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
114
|
+
const server = new McpServer({
|
|
115
|
+
name: "webiny",
|
|
116
|
+
version: getVersion()
|
|
117
|
+
});
|
|
118
|
+
server.registerTool("list_webiny_skills", {
|
|
119
|
+
title: "List Webiny Skills",
|
|
120
|
+
description: "Returns a catalog of all available Webiny skills with names and descriptions. Always call this first when working on anything Webiny-related, then call get_webiny_skill to load the specific skill you need.",
|
|
121
|
+
inputSchema: {},
|
|
122
|
+
annotations: {
|
|
123
|
+
readOnlyHint: true
|
|
124
|
+
}
|
|
125
|
+
}, async ()=>({
|
|
126
|
+
content: [
|
|
127
|
+
{
|
|
128
|
+
type: "text",
|
|
129
|
+
text: buildCatalog(getSkills())
|
|
130
|
+
}
|
|
131
|
+
]
|
|
132
|
+
}));
|
|
133
|
+
server.registerTool("get_webiny_skill", {
|
|
134
|
+
title: "Get Webiny Skill",
|
|
135
|
+
description: "Loads the full Webiny documentation for a specific skill. Call list_webiny_skills first to see available skill names.",
|
|
136
|
+
inputSchema: {
|
|
137
|
+
topic: z.string().describe("Skill name — use exact names from list_webiny_skills")
|
|
138
|
+
},
|
|
139
|
+
annotations: {
|
|
140
|
+
readOnlyHint: true
|
|
141
|
+
}
|
|
142
|
+
}, async ({ topic })=>{
|
|
143
|
+
const skills = getSkills();
|
|
144
|
+
const skill = skills.get(topic);
|
|
145
|
+
if (!skill) {
|
|
146
|
+
const available = [
|
|
147
|
+
...skills.keys()
|
|
148
|
+
].sort();
|
|
149
|
+
return {
|
|
150
|
+
content: [
|
|
151
|
+
{
|
|
152
|
+
type: "text",
|
|
153
|
+
text: `Skill not found: "${topic}".\n\nAvailable skills: ${available.join(", ") || "(none)"}.`
|
|
154
|
+
}
|
|
155
|
+
],
|
|
156
|
+
isError: true
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
content: [
|
|
161
|
+
{
|
|
162
|
+
type: "text",
|
|
163
|
+
text: readSkillContent(skill)
|
|
164
|
+
}
|
|
165
|
+
]
|
|
166
|
+
};
|
|
167
|
+
});
|
|
168
|
+
const transport = new StdioServerTransport();
|
|
169
|
+
await server.connect(transport);
|
|
170
|
+
console.error("[webiny-mcp] server ready");
|
|
211
171
|
}
|
|
172
|
+
export { startMcpServer };
|
|
212
173
|
|
|
213
174
|
//# sourceMappingURL=McpServer.js.map
|
package/cli/McpServer.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["readFileSync","readdirSync","existsSync","join","resolve","dirname","fileURLToPath","createRequire","fm","z","McpServer","StdioServerTransport","__dirname","import","meta","url","findSkillFiles","dir","results","entry","withFileTypes","fullPath","name","isDirectory","push","isFile","discoverSkills","skillsDirs","skills","Map","filePath","raw","parsed","description","attributes","console","error","has","context","set","err","getVersion","version","buildCatalog","lines","size","contextDescriptions","groups","skill","values","list","get","contextSkills","entries","sort","a","b","localeCompare","desc","skillDesc","replace","trim","readSkillContent","body","startMcpServer","params","cwd","process","builtInSkillsDir","skillsOverride","additionalSkillsDirs","additionalSkills","baseDir","map","p","reverse","d","skillsCache","getSkills","server","registerTool","title","inputSchema","annotations","readOnlyHint","content","type","text","topic","string","describe","available","keys","isError","transport","connect"],"sources":["McpServer.ts"],"sourcesContent":["import { readFileSync, readdirSync, existsSync } from \"fs\";\nimport { join, resolve, dirname } from \"path\";\nimport { fileURLToPath } from \"url\";\nimport { createRequire } from \"module\";\nimport fm from \"front-matter\";\nimport { z } from \"zod\";\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nexport interface IMcpServerParams {\n skills?: string;\n additionalSkills?: string[];\n}\n\n// ---------------------------------------------------------------------------\n// Skill discovery\n// ---------------------------------------------------------------------------\n\ninterface SkillAttributes {\n name: string;\n description: string;\n context?: string;\n}\n\ninterface Skill {\n name: string;\n description: string;\n context: string;\n filePath: string;\n}\n\n/**\n * Recursively find all SKILL.md files under `dir`.\n */\nfunction findSkillFiles(dir: string): string[] {\n if (!existsSync(dir)) {\n return [];\n }\n const results: string[] = [];\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n const fullPath = join(dir, entry.name);\n if (entry.isDirectory()) {\n results.push(...findSkillFiles(fullPath));\n } else if (entry.isFile() && entry.name === \"SKILL.md\") {\n results.push(fullPath);\n }\n }\n return results;\n}\n\n/**\n * Discover skills from multiple directories. First match wins (higher-priority dirs first).\n */\nfunction discoverSkills(skillsDirs: string[]): Map<string, Skill> {\n const skills = new Map<string, Skill>();\n\n for (const dir of skillsDirs) {\n for (const filePath of findSkillFiles(dir)) {\n try {\n const raw = readFileSync(filePath, \"utf8\");\n const parsed = fm<SkillAttributes>(raw);\n const { name, description } = parsed.attributes;\n\n if (!name || !description) {\n console.error(`[webiny-mcp] skipping ${filePath}: missing name or description`);\n continue;\n }\n\n if (!skills.has(name)) {\n const context = parsed.attributes.context || \"webiny-extensions\";\n skills.set(name, { name, description, context, filePath });\n }\n } catch (err) {\n console.error(`[webiny-mcp] error reading ${filePath}:`, err);\n }\n }\n }\n\n return skills;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction getVersion(): string {\n try {\n return createRequire(import.meta.url)(\"../../package.json\").version;\n } catch {\n return \"0.0.0\";\n }\n}\n\nfunction buildCatalog(skills: Map<string, Skill>): string {\n const lines: string[] = [`# Webiny Skills (v${getVersion()})`, \"\"];\n\n if (skills.size === 0) {\n lines.push(\n \"_(No skills found. Add SKILL.md files with front-matter to a skills directory.)_\"\n );\n return lines.join(\"\\n\");\n }\n\n // Group skills by context.\n const contextDescriptions: Record<string, string> = {\n \"webiny-extensions\":\n \"Use these skills when writing Webiny extensions (usually in `extensions/`) or making changes to `webiny.config.tsx` (user project development).\",\n \"webiny-packages\":\n \"Use these skills when writing code in `packages/` (core Webiny framework development).\"\n };\n\n const groups = new Map<string, Skill[]>();\n for (const skill of skills.values()) {\n const list = groups.get(skill.context) || [];\n list.push(skill);\n groups.set(skill.context, list);\n }\n\n for (const [context, contextSkills] of [...groups.entries()].sort((a, b) =>\n a[0].localeCompare(b[0])\n )) {\n lines.push(`## ${context}`, \"\");\n const desc = contextDescriptions[context];\n if (desc) {\n lines.push(desc, \"\");\n }\n lines.push(\"| Skill | Description |\");\n lines.push(\"|---|---|\");\n for (const skill of contextSkills.sort((a, b) => a.name.localeCompare(b.name))) {\n const skillDesc = skill.description.replace(/\\n/g, \" \").trim();\n lines.push(`| \\`${skill.name}\\` | ${skillDesc} |`);\n }\n lines.push(\"\");\n }\n\n return lines.join(\"\\n\");\n}\n\nfunction readSkillContent(skill: Skill): string {\n const raw = readFileSync(skill.filePath, \"utf8\");\n return fm(raw).body;\n}\n\n// ---------------------------------------------------------------------------\n// Standalone entry point\n// ---------------------------------------------------------------------------\n\nexport async function startMcpServer(params: IMcpServerParams = {}): Promise<void> {\n const cwd = process.cwd();\n const builtInSkillsDir = join(__dirname, \"..\", \"skills\");\n const skillsOverride = params.skills;\n const additionalSkillsDirs = params.additionalSkills || [];\n\n const baseDir = skillsOverride ? resolve(cwd, skillsOverride) : builtInSkillsDir;\n\n // skillsDirs[0] = highest priority, skillsDirs[last] = lowest priority\n const skillsDirs = [...[...additionalSkillsDirs].map(p => resolve(cwd, p)).reverse(), baseDir];\n\n if (skillsOverride) {\n console.error(`[webiny-mcp] skills override: ${baseDir}`);\n }\n for (const d of additionalSkillsDirs) {\n console.error(`[webiny-mcp] additional skills: ${resolve(cwd, d)}`);\n }\n\n // In-memory cache: populated on first list, reused by get.\n let skillsCache: Map<string, Skill> | null = null;\n\n function getSkills(): Map<string, Skill> {\n if (!skillsCache) {\n skillsCache = discoverSkills(skillsDirs);\n console.error(`[webiny-mcp] discovered ${skillsCache.size} skill(s)`);\n }\n return skillsCache;\n }\n\n // ---------------------------------------------------------------\n // MCP server\n // ---------------------------------------------------------------\n\n const server = new McpServer({ name: \"webiny\", version: getVersion() });\n\n server.registerTool(\n \"list_webiny_skills\",\n {\n title: \"List Webiny Skills\",\n description:\n \"Returns a catalog of all available Webiny skills with names and descriptions. \" +\n \"Always call this first when working on anything Webiny-related, then call \" +\n \"get_webiny_skill to load the specific skill you need.\",\n inputSchema: {},\n annotations: { readOnlyHint: true }\n },\n async () => ({\n content: [{ type: \"text\", text: buildCatalog(getSkills()) }]\n })\n );\n\n server.registerTool(\n \"get_webiny_skill\",\n {\n title: \"Get Webiny Skill\",\n description:\n \"Loads the full Webiny documentation for a specific skill. \" +\n \"Call list_webiny_skills first to see available skill names.\",\n inputSchema: {\n topic: z.string().describe(\"Skill name — use exact names from list_webiny_skills\")\n },\n annotations: { readOnlyHint: true }\n },\n async ({ topic }) => {\n const skills = getSkills();\n const skill = skills.get(topic);\n if (!skill) {\n const available = [...skills.keys()].sort();\n return {\n content: [\n {\n type: \"text\",\n text:\n `Skill not found: \"${topic}\".\\n\\n` +\n `Available skills: ${available.join(\", \") || \"(none)\"}.`\n }\n ],\n isError: true\n };\n }\n return {\n content: [{ type: \"text\", text: readSkillContent(skill) }]\n };\n }\n );\n\n // ---------------------------------------------------------------\n // Start\n // ---------------------------------------------------------------\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n console.error(\"[webiny-mcp] server ready\");\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"cli/McpServer.js","sources":["../../src/cli/McpServer.ts"],"sourcesContent":["import { readFileSync, readdirSync, existsSync } from \"fs\";\nimport { join, resolve, dirname } from \"path\";\nimport { fileURLToPath } from \"url\";\nimport { createRequire } from \"module\";\nimport fm from \"front-matter\";\nimport { z } from \"zod\";\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nexport interface IMcpServerParams {\n skills?: string;\n additionalSkills?: string[];\n}\n\n// ---------------------------------------------------------------------------\n// Skill discovery\n// ---------------------------------------------------------------------------\n\ninterface SkillAttributes {\n name: string;\n description: string;\n context?: string;\n}\n\ninterface Skill {\n name: string;\n description: string;\n context: string;\n filePath: string;\n}\n\n/**\n * Recursively find all SKILL.md files under `dir`.\n */\nfunction findSkillFiles(dir: string): string[] {\n if (!existsSync(dir)) {\n return [];\n }\n const results: string[] = [];\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n const fullPath = join(dir, entry.name);\n if (entry.isDirectory()) {\n results.push(...findSkillFiles(fullPath));\n } else if (entry.isFile() && entry.name === \"SKILL.md\") {\n results.push(fullPath);\n }\n }\n return results;\n}\n\n/**\n * Discover skills from multiple directories. First match wins (higher-priority dirs first).\n */\nfunction discoverSkills(skillsDirs: string[]): Map<string, Skill> {\n const skills = new Map<string, Skill>();\n\n for (const dir of skillsDirs) {\n for (const filePath of findSkillFiles(dir)) {\n try {\n const raw = readFileSync(filePath, \"utf8\");\n const parsed = fm<SkillAttributes>(raw);\n const { name, description } = parsed.attributes;\n\n if (!name || !description) {\n console.error(`[webiny-mcp] skipping ${filePath}: missing name or description`);\n continue;\n }\n\n if (!skills.has(name)) {\n const context = parsed.attributes.context || \"webiny-extensions\";\n skills.set(name, { name, description, context, filePath });\n }\n } catch (err) {\n console.error(`[webiny-mcp] error reading ${filePath}:`, err);\n }\n }\n }\n\n return skills;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction getVersion(): string {\n try {\n return createRequire(import.meta.url)(\"../../package.json\").version;\n } catch {\n return \"0.0.0\";\n }\n}\n\nfunction buildCatalog(skills: Map<string, Skill>): string {\n const lines: string[] = [`# Webiny Skills (v${getVersion()})`, \"\"];\n\n if (skills.size === 0) {\n lines.push(\n \"_(No skills found. Add SKILL.md files with front-matter to a skills directory.)_\"\n );\n return lines.join(\"\\n\");\n }\n\n // Group skills by context.\n const contextDescriptions: Record<string, string> = {\n \"webiny-extensions\":\n \"Use these skills when writing Webiny extensions (usually in `extensions/`) or making changes to `webiny.config.tsx` (user project development).\",\n \"webiny-packages\":\n \"Use these skills when writing code in `packages/` (core Webiny framework development).\"\n };\n\n const groups = new Map<string, Skill[]>();\n for (const skill of skills.values()) {\n const list = groups.get(skill.context) || [];\n list.push(skill);\n groups.set(skill.context, list);\n }\n\n for (const [context, contextSkills] of [...groups.entries()].sort((a, b) =>\n a[0].localeCompare(b[0])\n )) {\n lines.push(`## ${context}`, \"\");\n const desc = contextDescriptions[context];\n if (desc) {\n lines.push(desc, \"\");\n }\n lines.push(\"| Skill | Description |\");\n lines.push(\"|---|---|\");\n for (const skill of contextSkills.sort((a, b) => a.name.localeCompare(b.name))) {\n const skillDesc = skill.description.replace(/\\n/g, \" \").trim();\n lines.push(`| \\`${skill.name}\\` | ${skillDesc} |`);\n }\n lines.push(\"\");\n }\n\n return lines.join(\"\\n\");\n}\n\nfunction readSkillContent(skill: Skill): string {\n const raw = readFileSync(skill.filePath, \"utf8\");\n return fm(raw).body;\n}\n\n// ---------------------------------------------------------------------------\n// Standalone entry point\n// ---------------------------------------------------------------------------\n\nexport async function startMcpServer(params: IMcpServerParams = {}): Promise<void> {\n const cwd = process.cwd();\n const builtInSkillsDir = join(__dirname, \"..\", \"skills\");\n const skillsOverride = params.skills;\n const additionalSkillsDirs = params.additionalSkills || [];\n\n const baseDir = skillsOverride ? resolve(cwd, skillsOverride) : builtInSkillsDir;\n\n // skillsDirs[0] = highest priority, skillsDirs[last] = lowest priority\n const skillsDirs = [...[...additionalSkillsDirs].map(p => resolve(cwd, p)).reverse(), baseDir];\n\n if (skillsOverride) {\n console.error(`[webiny-mcp] skills override: ${baseDir}`);\n }\n for (const d of additionalSkillsDirs) {\n console.error(`[webiny-mcp] additional skills: ${resolve(cwd, d)}`);\n }\n\n // In-memory cache: populated on first list, reused by get.\n let skillsCache: Map<string, Skill> | null = null;\n\n function getSkills(): Map<string, Skill> {\n if (!skillsCache) {\n skillsCache = discoverSkills(skillsDirs);\n console.error(`[webiny-mcp] discovered ${skillsCache.size} skill(s)`);\n }\n return skillsCache;\n }\n\n // ---------------------------------------------------------------\n // MCP server\n // ---------------------------------------------------------------\n\n const server = new McpServer({ name: \"webiny\", version: getVersion() });\n\n server.registerTool(\n \"list_webiny_skills\",\n {\n title: \"List Webiny Skills\",\n description:\n \"Returns a catalog of all available Webiny skills with names and descriptions. \" +\n \"Always call this first when working on anything Webiny-related, then call \" +\n \"get_webiny_skill to load the specific skill you need.\",\n inputSchema: {},\n annotations: { readOnlyHint: true }\n },\n async () => ({\n content: [{ type: \"text\", text: buildCatalog(getSkills()) }]\n })\n );\n\n server.registerTool(\n \"get_webiny_skill\",\n {\n title: \"Get Webiny Skill\",\n description:\n \"Loads the full Webiny documentation for a specific skill. \" +\n \"Call list_webiny_skills first to see available skill names.\",\n inputSchema: {\n topic: z.string().describe(\"Skill name — use exact names from list_webiny_skills\")\n },\n annotations: { readOnlyHint: true }\n },\n async ({ topic }) => {\n const skills = getSkills();\n const skill = skills.get(topic);\n if (!skill) {\n const available = [...skills.keys()].sort();\n return {\n content: [\n {\n type: \"text\",\n text:\n `Skill not found: \"${topic}\".\\n\\n` +\n `Available skills: ${available.join(\", \") || \"(none)\"}.`\n }\n ],\n isError: true\n };\n }\n return {\n content: [{ type: \"text\", text: readSkillContent(skill) }]\n };\n }\n );\n\n // ---------------------------------------------------------------\n // Start\n // ---------------------------------------------------------------\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n console.error(\"[webiny-mcp] server ready\");\n}\n"],"names":["__dirname","dirname","fileURLToPath","findSkillFiles","dir","existsSync","results","entry","readdirSync","fullPath","join","discoverSkills","skillsDirs","skills","Map","filePath","raw","readFileSync","parsed","fm","name","description","console","context","err","getVersion","createRequire","buildCatalog","lines","contextDescriptions","groups","skill","list","contextSkills","a","b","desc","skillDesc","readSkillContent","startMcpServer","params","cwd","process","builtInSkillsDir","skillsOverride","additionalSkillsDirs","baseDir","resolve","p","d","skillsCache","getSkills","server","McpServer","z","topic","available","transport","StdioServerTransport"],"mappings":"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"}
|