@sysvv/ai-skill 1.2.0 → 1.4.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/dist/core/mcp.js +190 -0
- package/dist/index.js +57 -20
- package/mcps/azure-devops.mcp.json +16 -0
- package/package.json +6 -3
- package/schemas/mcp.schema.json +89 -0
- package/skills/ado-workflow/SKILL.md +163 -0
package/dist/core/mcp.js
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import * as TOML from "smol-toml";
|
|
5
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
// ── Targets por agent ───────────────────────────────────────────────────
|
|
7
|
+
const AGENT_MCP_TARGETS = {
|
|
8
|
+
// ── Claude ──────────────────────────────────────────────────────────
|
|
9
|
+
// .mcp.json → { mcpServers: { name: { command, args, env } } }
|
|
10
|
+
claude: {
|
|
11
|
+
format: "json",
|
|
12
|
+
file: ".mcp.json",
|
|
13
|
+
serversKey: "mcpServers",
|
|
14
|
+
buildServerConfig: (mcp) => {
|
|
15
|
+
if (mcp.transport === "stdio") {
|
|
16
|
+
const config = { command: mcp.command };
|
|
17
|
+
if (mcp.args?.length)
|
|
18
|
+
config.args = mcp.args;
|
|
19
|
+
if (mcp.env && Object.keys(mcp.env).length)
|
|
20
|
+
config.env = mcp.env;
|
|
21
|
+
return config;
|
|
22
|
+
}
|
|
23
|
+
const config = { url: mcp.url };
|
|
24
|
+
if (mcp.headers && Object.keys(mcp.headers).length)
|
|
25
|
+
config.headers = mcp.headers;
|
|
26
|
+
if (mcp.env && Object.keys(mcp.env).length)
|
|
27
|
+
config.env = mcp.env;
|
|
28
|
+
return config;
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
// ── Gemini ──────────────────────────────────────────────────────────
|
|
32
|
+
// .gemini/settings.json → { mcpServers: { name: { command, args, env, cwd, timeout } } }
|
|
33
|
+
// Gemini usa "httpUrl" para streamable-http
|
|
34
|
+
gemini: {
|
|
35
|
+
format: "json",
|
|
36
|
+
file: ".gemini/settings.json",
|
|
37
|
+
serversKey: "mcpServers",
|
|
38
|
+
buildServerConfig: (mcp) => {
|
|
39
|
+
const config = {};
|
|
40
|
+
if (mcp.transport === "stdio") {
|
|
41
|
+
config.command = mcp.command;
|
|
42
|
+
if (mcp.args?.length)
|
|
43
|
+
config.args = mcp.args;
|
|
44
|
+
if (mcp.cwd)
|
|
45
|
+
config.cwd = mcp.cwd;
|
|
46
|
+
}
|
|
47
|
+
else if (mcp.transport === "sse") {
|
|
48
|
+
config.url = mcp.url;
|
|
49
|
+
if (mcp.headers && Object.keys(mcp.headers).length)
|
|
50
|
+
config.headers = mcp.headers;
|
|
51
|
+
}
|
|
52
|
+
else if (mcp.transport === "streamable-http") {
|
|
53
|
+
config.httpUrl = mcp.url;
|
|
54
|
+
if (mcp.headers && Object.keys(mcp.headers).length)
|
|
55
|
+
config.headers = mcp.headers;
|
|
56
|
+
}
|
|
57
|
+
if (mcp.env && Object.keys(mcp.env).length)
|
|
58
|
+
config.env = mcp.env;
|
|
59
|
+
if (mcp.timeout)
|
|
60
|
+
config.timeout = mcp.timeout;
|
|
61
|
+
return config;
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
// ── Codex ───────────────────────────────────────────────────────────
|
|
65
|
+
// .codex/config.toml → [mcp_servers.<name>]
|
|
66
|
+
// stdio: command, args, env, cwd
|
|
67
|
+
// streamable-http: url, bearer_token_env_var, http_headers
|
|
68
|
+
// extras: startup_timeout_sec, tool_timeout_sec, enabled
|
|
69
|
+
codex: {
|
|
70
|
+
format: "toml",
|
|
71
|
+
file: ".codex/config.toml",
|
|
72
|
+
buildServerEntry: (mcp) => {
|
|
73
|
+
const entry = {};
|
|
74
|
+
if (mcp.transport === "stdio") {
|
|
75
|
+
entry.command = mcp.command;
|
|
76
|
+
if (mcp.args?.length)
|
|
77
|
+
entry.args = mcp.args;
|
|
78
|
+
if (mcp.cwd)
|
|
79
|
+
entry.cwd = mcp.cwd;
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
// sse e streamable-http → Codex usa "url" para ambos
|
|
83
|
+
entry.url = mcp.url;
|
|
84
|
+
if (mcp.headers && Object.keys(mcp.headers).length) {
|
|
85
|
+
entry.http_headers = mcp.headers;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (mcp.env && Object.keys(mcp.env).length)
|
|
89
|
+
entry.env = mcp.env;
|
|
90
|
+
if (mcp.timeout)
|
|
91
|
+
entry.tool_timeout_sec = Math.ceil(mcp.timeout / 1000);
|
|
92
|
+
return entry;
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
// ── Copilot ─────────────────────────────────────────────────────────
|
|
96
|
+
// .vscode/mcp.json → { inputs: [], servers: { name: { command, args, env } } }
|
|
97
|
+
copilot: {
|
|
98
|
+
format: "json",
|
|
99
|
+
file: ".vscode/mcp.json",
|
|
100
|
+
serversKey: "servers",
|
|
101
|
+
buildServerConfig: (mcp) => {
|
|
102
|
+
if (mcp.transport === "stdio") {
|
|
103
|
+
const config = { command: mcp.command };
|
|
104
|
+
if (mcp.args?.length)
|
|
105
|
+
config.args = mcp.args;
|
|
106
|
+
if (mcp.env && Object.keys(mcp.env).length)
|
|
107
|
+
config.env = mcp.env;
|
|
108
|
+
return config;
|
|
109
|
+
}
|
|
110
|
+
const config = { url: mcp.url };
|
|
111
|
+
if (mcp.headers && Object.keys(mcp.headers).length)
|
|
112
|
+
config.headers = mcp.headers;
|
|
113
|
+
if (mcp.env && Object.keys(mcp.env).length)
|
|
114
|
+
config.env = mcp.env;
|
|
115
|
+
return config;
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
// ── Writers ─────────────────────────────────────────────────────────────
|
|
120
|
+
async function writeJsonTarget(target, targetPath, mcp) {
|
|
121
|
+
let existing = {};
|
|
122
|
+
if (await fs.pathExists(targetPath)) {
|
|
123
|
+
existing = await fs.readJson(targetPath);
|
|
124
|
+
}
|
|
125
|
+
// Copilot: garante que "inputs" exista
|
|
126
|
+
if (target.serversKey === "servers" && !existing.inputs) {
|
|
127
|
+
existing.inputs = [];
|
|
128
|
+
}
|
|
129
|
+
const servers = existing[target.serversKey] ?? {};
|
|
130
|
+
servers[mcp.name] = target.buildServerConfig(mcp);
|
|
131
|
+
existing[target.serversKey] = servers;
|
|
132
|
+
await fs.ensureDir(path.dirname(targetPath));
|
|
133
|
+
await fs.writeJson(targetPath, existing, { spaces: 2 });
|
|
134
|
+
}
|
|
135
|
+
async function writeTomlTarget(target, targetPath, mcp) {
|
|
136
|
+
let existing = {};
|
|
137
|
+
if (await fs.pathExists(targetPath)) {
|
|
138
|
+
const content = await fs.readFile(targetPath, "utf8");
|
|
139
|
+
if (content.trim()) {
|
|
140
|
+
existing = TOML.parse(content);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const mcpServers = existing.mcp_servers ?? {};
|
|
144
|
+
mcpServers[mcp.name] = target.buildServerEntry(mcp);
|
|
145
|
+
existing.mcp_servers = mcpServers;
|
|
146
|
+
await fs.ensureDir(path.dirname(targetPath));
|
|
147
|
+
await fs.writeFile(targetPath, TOML.stringify(existing), "utf8");
|
|
148
|
+
}
|
|
149
|
+
// ── API pública ─────────────────────────────────────────────────────────
|
|
150
|
+
/** Carrega a definição de um MCP pelo nome */
|
|
151
|
+
export async function loadMcp(name) {
|
|
152
|
+
const mcpFile = path.resolve(__dirname, "../../mcps", `${name}.mcp.json`);
|
|
153
|
+
if (!(await fs.pathExists(mcpFile))) {
|
|
154
|
+
throw new Error(`MCP "${name}" não encontrado em mcps/${name}.mcp.json`);
|
|
155
|
+
}
|
|
156
|
+
return fs.readJson(mcpFile);
|
|
157
|
+
}
|
|
158
|
+
/** Escreve o MCP no arquivo alvo do agent, fazendo merge com configs existentes */
|
|
159
|
+
export async function writeMcp(agent, mcp) {
|
|
160
|
+
const target = AGENT_MCP_TARGETS[agent];
|
|
161
|
+
if (!target)
|
|
162
|
+
throw new Error(`Agent "${agent}" não tem target de MCP configurado.`);
|
|
163
|
+
const targetPath = path.resolve(target.file);
|
|
164
|
+
if (target.format === "json") {
|
|
165
|
+
await writeJsonTarget(target, targetPath, mcp);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
await writeTomlTarget(target, targetPath, mcp);
|
|
169
|
+
}
|
|
170
|
+
return targetPath;
|
|
171
|
+
}
|
|
172
|
+
/** Retorna o path do arquivo MCP de um agent */
|
|
173
|
+
export function getMcpTargetPath(agent) {
|
|
174
|
+
return AGENT_MCP_TARGETS[agent]?.file;
|
|
175
|
+
}
|
|
176
|
+
/** Lista todos os MCPs disponíveis */
|
|
177
|
+
export async function listMcps() {
|
|
178
|
+
const mcpsDir = path.resolve(__dirname, "../../mcps");
|
|
179
|
+
if (!(await fs.pathExists(mcpsDir)))
|
|
180
|
+
return [];
|
|
181
|
+
const files = await fs.readdir(mcpsDir);
|
|
182
|
+
const results = [];
|
|
183
|
+
for (const file of files) {
|
|
184
|
+
if (file.endsWith(".mcp.json")) {
|
|
185
|
+
const mcp = await fs.readJson(path.join(mcpsDir, file));
|
|
186
|
+
results.push(mcp);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return results;
|
|
190
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import { fileURLToPath } from "node:url";
|
|
|
7
7
|
import { findSkillFiles } from "./core/registry.js";
|
|
8
8
|
import { parseSkillFile } from "./core/frontmatter.js";
|
|
9
9
|
import { loadSkill } from "./core/skill.js";
|
|
10
|
+
import { loadMcp, writeMcp, getMcpTargetPath } from "./core/mcp.js";
|
|
10
11
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
12
|
// ── ANSI ────────────────────────────────────────────────────────────────
|
|
12
13
|
const ANSI = {
|
|
@@ -18,6 +19,7 @@ const PINK = '\x1b[38;2;238;54;141m';
|
|
|
18
19
|
const BLUE = '\x1b[38;2;14;93;171m';
|
|
19
20
|
const RED = '\x1b[38;2;255;70;70m';
|
|
20
21
|
const GREEN = '\x1b[38;2;80;200;120m';
|
|
22
|
+
const YELLOW = '\x1b[38;2;255;200;50m';
|
|
21
23
|
const WHITE = '\x1b[97m';
|
|
22
24
|
// ── Inquirer Theme ──────────────────────────────────────────────────────
|
|
23
25
|
const sysTheme = {
|
|
@@ -87,7 +89,7 @@ function showBanner() {
|
|
|
87
89
|
console.log(indent + ANSI.dim + centerPad('│ ' + WHITE + desc + ANSI.reset + ANSI.dim + ' │', maxWidth) + ANSI.reset);
|
|
88
90
|
console.log(indent + ANSI.dim + centerPad('└' + '─'.repeat(boxInner) + '┘', maxWidth) + ANSI.reset);
|
|
89
91
|
console.log('');
|
|
90
|
-
console.log(indent + ANSI.dim + centerPad('npx @sysvv/ai-skill --claude | --codex | --gemini | --clear', maxWidth) + ANSI.reset);
|
|
92
|
+
console.log(indent + ANSI.dim + centerPad('npx @sysvv/ai-skill --claude | --codex | --gemini | --copilot | --clear', maxWidth) + ANSI.reset);
|
|
91
93
|
console.log('');
|
|
92
94
|
console.log(separator);
|
|
93
95
|
console.log('');
|
|
@@ -97,6 +99,7 @@ const AGENT_DIRS = {
|
|
|
97
99
|
claude: ".claude/skills",
|
|
98
100
|
codex: ".codex/skills",
|
|
99
101
|
gemini: ".gemini/skills",
|
|
102
|
+
copilot: ".github/skills",
|
|
100
103
|
};
|
|
101
104
|
// ── Parse args ──────────────────────────────────────────────────────────
|
|
102
105
|
function parseArgs() {
|
|
@@ -109,7 +112,7 @@ function parseArgs() {
|
|
|
109
112
|
}
|
|
110
113
|
return {};
|
|
111
114
|
}
|
|
112
|
-
// ── Install skills for agent
|
|
115
|
+
// ── Install skills + MCPs for agent ─────────────────────────────────────
|
|
113
116
|
async function installSkills(agent) {
|
|
114
117
|
const skillFiles = await findSkillFiles();
|
|
115
118
|
const available = [];
|
|
@@ -120,7 +123,8 @@ async function installSkills(agent) {
|
|
|
120
123
|
file,
|
|
121
124
|
name: metadata.name,
|
|
122
125
|
title: metadata.title ?? metadata.name,
|
|
123
|
-
description: metadata.description
|
|
126
|
+
description: metadata.description,
|
|
127
|
+
mcps: metadata.mcps ?? []
|
|
124
128
|
});
|
|
125
129
|
}
|
|
126
130
|
if (available.length === 0) {
|
|
@@ -133,7 +137,7 @@ async function installSkills(agent) {
|
|
|
133
137
|
name: "selected",
|
|
134
138
|
message: "Escolha as skills:",
|
|
135
139
|
choices: available.map((s) => ({
|
|
136
|
-
name: `${s.title} ${ANSI.dim}— ${s.description}${ANSI.reset}`,
|
|
140
|
+
name: `${s.title} ${ANSI.dim}— ${s.description}${s.mcps.length > 0 ? ` ${YELLOW}[MCP]${ANSI.reset}${ANSI.dim}` : ''}${ANSI.reset}`,
|
|
137
141
|
value: s.file
|
|
138
142
|
})),
|
|
139
143
|
validate: (answer) => answer.length > 0 ? true : "Selecione pelo menos uma skill.",
|
|
@@ -141,6 +145,8 @@ async function installSkills(agent) {
|
|
|
141
145
|
}
|
|
142
146
|
]);
|
|
143
147
|
const destBase = path.resolve(AGENT_DIRS[agent]);
|
|
148
|
+
const mcpsToInstall = new Set();
|
|
149
|
+
// Instala skills
|
|
144
150
|
console.log('');
|
|
145
151
|
for (const file of selected) {
|
|
146
152
|
const skill = await loadSkill(file, agent);
|
|
@@ -148,32 +154,58 @@ async function installSkills(agent) {
|
|
|
148
154
|
await fs.ensureDir(skillDir);
|
|
149
155
|
await fs.writeFile(path.join(skillDir, "SKILL.md"), skill.renderedBody, "utf8");
|
|
150
156
|
console.log(` ${GREEN}✔${ANSI.reset} ${skill.metadata.name}`);
|
|
157
|
+
// Coleta MCPs necessários
|
|
158
|
+
for (const mcpName of skill.metadata.mcps ?? []) {
|
|
159
|
+
mcpsToInstall.add(mcpName);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// Instala MCPs automaticamente
|
|
163
|
+
if (mcpsToInstall.size > 0) {
|
|
164
|
+
console.log('');
|
|
165
|
+
let mcpTarget = '';
|
|
166
|
+
for (const mcpName of mcpsToInstall) {
|
|
167
|
+
try {
|
|
168
|
+
const mcp = await loadMcp(mcpName);
|
|
169
|
+
mcpTarget = await writeMcp(agent, mcp);
|
|
170
|
+
console.log(` ${GREEN}✔${ANSI.reset} MCP ${ANSI.bold}${mcpName}${ANSI.reset} ${ANSI.dim}→ ${mcpTarget}${ANSI.reset}`);
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
console.log(` ${RED}✖${ANSI.reset} MCP ${mcpName}: ${err.message}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
151
176
|
}
|
|
152
177
|
console.log(`\n Skills instaladas em ${ANSI.bold}${destBase}/${ANSI.reset}\n`);
|
|
153
178
|
}
|
|
154
|
-
// ── Clear all skills
|
|
179
|
+
// ── Clear all skills + MCPs ─────────────────────────────────────────────
|
|
155
180
|
async function clearSkills() {
|
|
156
|
-
const
|
|
181
|
+
const found = [];
|
|
157
182
|
for (const [agent, dir] of Object.entries(AGENT_DIRS)) {
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
183
|
+
const skillsFull = path.resolve(dir);
|
|
184
|
+
const mcpTarget = getMcpTargetPath(agent);
|
|
185
|
+
const mcpFull = mcpTarget ? path.resolve(mcpTarget) : "";
|
|
186
|
+
const hasSkills = await fs.pathExists(skillsFull);
|
|
187
|
+
const hasMcp = mcpFull ? await fs.pathExists(mcpFull) : false;
|
|
188
|
+
if (hasSkills || hasMcp) {
|
|
189
|
+
found.push({ agent, skillsDir: skillsFull, mcpFile: mcpFull, hasSkills, hasMcp });
|
|
161
190
|
}
|
|
162
191
|
}
|
|
163
|
-
if (
|
|
164
|
-
console.log(" Nenhuma pasta de skills encontrada.\n");
|
|
192
|
+
if (found.length === 0) {
|
|
193
|
+
console.log(" Nenhuma pasta de skills ou MCP encontrada.\n");
|
|
165
194
|
return;
|
|
166
195
|
}
|
|
167
|
-
console.log(` ${RED}⚠${ANSI.reset}
|
|
168
|
-
for (const
|
|
169
|
-
|
|
196
|
+
console.log(` ${RED}⚠${ANSI.reset} Encontrado:\n`);
|
|
197
|
+
for (const f of found) {
|
|
198
|
+
if (f.hasSkills)
|
|
199
|
+
console.log(` ${ANSI.dim}${f.skillsDir}${ANSI.reset}`);
|
|
200
|
+
if (f.hasMcp)
|
|
201
|
+
console.log(` ${ANSI.dim}${f.mcpFile}${ANSI.reset}`);
|
|
170
202
|
}
|
|
171
203
|
console.log('');
|
|
172
204
|
const { confirm } = await inquirer.prompt([
|
|
173
205
|
{
|
|
174
206
|
type: "confirm",
|
|
175
207
|
name: "confirm",
|
|
176
|
-
message: "Tem certeza que deseja apagar
|
|
208
|
+
message: "Tem certeza que deseja apagar TUDO (skills + MCPs)?",
|
|
177
209
|
default: false,
|
|
178
210
|
theme: sysTheme
|
|
179
211
|
}
|
|
@@ -182,12 +214,17 @@ async function clearSkills() {
|
|
|
182
214
|
console.log("\n Operação cancelada.\n");
|
|
183
215
|
return;
|
|
184
216
|
}
|
|
185
|
-
for (const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
217
|
+
for (const f of found) {
|
|
218
|
+
if (f.hasSkills) {
|
|
219
|
+
await fs.remove(f.skillsDir);
|
|
220
|
+
console.log(` ${RED}✖${ANSI.reset} ${f.skillsDir}`);
|
|
221
|
+
}
|
|
222
|
+
if (f.hasMcp) {
|
|
223
|
+
await fs.remove(f.mcpFile);
|
|
224
|
+
console.log(` ${RED}✖${ANSI.reset} ${f.mcpFile}`);
|
|
225
|
+
}
|
|
189
226
|
}
|
|
190
|
-
console.log("\n
|
|
227
|
+
console.log("\n Tudo removido.\n");
|
|
191
228
|
}
|
|
192
229
|
// ── Main ────────────────────────────────────────────────────────────────
|
|
193
230
|
async function main() {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../schemas/mcp.schema.json",
|
|
3
|
+
"name": "azure-devops",
|
|
4
|
+
"description": "Integração com Azure DevOps para work items, repos e pipelines.",
|
|
5
|
+
"transport": "stdio",
|
|
6
|
+
"command": "npx",
|
|
7
|
+
"args": [
|
|
8
|
+
"-y",
|
|
9
|
+
"@azure-devops/mcp",
|
|
10
|
+
"sysmanagerdevops",
|
|
11
|
+
"-d", "core", "work", "work-items"
|
|
12
|
+
],
|
|
13
|
+
"env": {
|
|
14
|
+
"AZURE_DEVOPS_PAT": "${AZURE_DEVOPS_PAT}"
|
|
15
|
+
}
|
|
16
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sysvv/ai-skill",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Instale skills de IA direto no seu projeto. Escolha o agent, escolha as skills.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"dist",
|
|
11
|
-
"skills"
|
|
11
|
+
"skills",
|
|
12
|
+
"mcps",
|
|
13
|
+
"schemas"
|
|
12
14
|
],
|
|
13
15
|
"scripts": {
|
|
14
16
|
"build": "tsc",
|
|
@@ -28,7 +30,8 @@
|
|
|
28
30
|
"dependencies": {
|
|
29
31
|
"fs-extra": "^11.3.0",
|
|
30
32
|
"gray-matter": "^4.0.3",
|
|
31
|
-
"inquirer": "^12.5.2"
|
|
33
|
+
"inquirer": "^12.5.2",
|
|
34
|
+
"smol-toml": "^1.6.0"
|
|
32
35
|
},
|
|
33
36
|
"devDependencies": {
|
|
34
37
|
"@types/fs-extra": "^11.0.4",
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://sysvv.dev/schemas/mcp.schema.json",
|
|
4
|
+
"title": "MCP Server Configuration",
|
|
5
|
+
"description": "Schema para configuração de servidores MCP (Model Context Protocol). Suporta os transportes stdio, sse e streamable-http. Campos opcionais como timeout e cwd são aplicados conforme o suporte de cada agent.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"$schema": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"description": "Referência ao JSON Schema para validação e autocomplete."
|
|
11
|
+
},
|
|
12
|
+
"name": {
|
|
13
|
+
"type": "string",
|
|
14
|
+
"pattern": "^[a-z0-9][a-z0-9-]*$",
|
|
15
|
+
"description": "Identificador único do MCP. Deve ser slug (lowercase, hífens). Ex: azure-devops"
|
|
16
|
+
},
|
|
17
|
+
"description": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"description": "Descrição curta do que o MCP faz."
|
|
20
|
+
},
|
|
21
|
+
"transport": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"enum": ["stdio", "sse", "streamable-http"],
|
|
24
|
+
"description": "Tipo de transporte do MCP."
|
|
25
|
+
},
|
|
26
|
+
"command": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"description": "[stdio] Comando para iniciar o servidor MCP."
|
|
29
|
+
},
|
|
30
|
+
"args": {
|
|
31
|
+
"type": "array",
|
|
32
|
+
"items": { "type": "string" },
|
|
33
|
+
"description": "[stdio] Argumentos passados ao comando."
|
|
34
|
+
},
|
|
35
|
+
"cwd": {
|
|
36
|
+
"type": "string",
|
|
37
|
+
"description": "[stdio] Diretório de trabalho para o processo do servidor."
|
|
38
|
+
},
|
|
39
|
+
"url": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"format": "uri",
|
|
42
|
+
"description": "[sse | streamable-http] URL do servidor MCP."
|
|
43
|
+
},
|
|
44
|
+
"headers": {
|
|
45
|
+
"type": "object",
|
|
46
|
+
"additionalProperties": { "type": "string" },
|
|
47
|
+
"description": "[sse | streamable-http] Headers HTTP enviados nas requisições."
|
|
48
|
+
},
|
|
49
|
+
"env": {
|
|
50
|
+
"type": "object",
|
|
51
|
+
"additionalProperties": { "type": "string" },
|
|
52
|
+
"description": "Variáveis de ambiente. Use ${VAR} para referências dinâmicas."
|
|
53
|
+
},
|
|
54
|
+
"timeout": {
|
|
55
|
+
"type": "number",
|
|
56
|
+
"description": "Timeout da requisição em milissegundos. Padrão varia por agent."
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"required": ["name", "description", "transport"],
|
|
60
|
+
"allOf": [
|
|
61
|
+
{
|
|
62
|
+
"if": {
|
|
63
|
+
"properties": { "transport": { "const": "stdio" } },
|
|
64
|
+
"required": ["transport"]
|
|
65
|
+
},
|
|
66
|
+
"then": {
|
|
67
|
+
"required": ["command"]
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"if": {
|
|
72
|
+
"properties": { "transport": { "const": "sse" } },
|
|
73
|
+
"required": ["transport"]
|
|
74
|
+
},
|
|
75
|
+
"then": {
|
|
76
|
+
"required": ["url"]
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"if": {
|
|
81
|
+
"properties": { "transport": { "const": "streamable-http" } },
|
|
82
|
+
"required": ["transport"]
|
|
83
|
+
},
|
|
84
|
+
"then": {
|
|
85
|
+
"required": ["url"]
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ado-workflow
|
|
3
|
+
title: ADO Workflow
|
|
4
|
+
description: gerenciar work items do Azure DevOps integrado com Git local. Sincroniza estados, vincula commits e mantém ADO atualizado.
|
|
5
|
+
version: 1.0.0
|
|
6
|
+
providers:
|
|
7
|
+
- claude
|
|
8
|
+
- codex
|
|
9
|
+
- gemini
|
|
10
|
+
- copilot
|
|
11
|
+
mcps:
|
|
12
|
+
- azure-devops
|
|
13
|
+
variables:
|
|
14
|
+
language: pt-BR
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# ADO Workflow Skill
|
|
18
|
+
|
|
19
|
+
Reusable skill that integrates Azure DevOps work items with local Git operations. Manages work item states, links commits to tasks, and keeps ADO in sync with development progress. Uses MCP tools as primary interface with CLI fallback.
|
|
20
|
+
|
|
21
|
+
## Setup
|
|
22
|
+
|
|
23
|
+
1. Verify MCP server "azure-devops" is available (check `mcp__azure-devops__*` tools)
|
|
24
|
+
2. If MCP is NOT available: fall back to `az` CLI commands for all operations
|
|
25
|
+
3. Verify `git` is installed and repository is initialized
|
|
26
|
+
4. **NEVER** print or display the value of `AZURE_DEVOPS_PAT`
|
|
27
|
+
|
|
28
|
+
## Rules
|
|
29
|
+
|
|
30
|
+
1. ALWAYS reference ADO work item IDs in commit messages (e.g., `feat: implement login #123`)
|
|
31
|
+
2. NEVER update work item state without verifying current state first
|
|
32
|
+
3. NEVER print or display PAT/token values
|
|
33
|
+
4. Make atomic commits — one logical change per commit
|
|
34
|
+
5. Use Conventional Commits format (`feat:`, `fix:`, `docs:`, `refactor:`, `chore:`, etc.)
|
|
35
|
+
6. Keep ADO work item state in sync with actual progress
|
|
36
|
+
7. ALWAYS confirm with the user before changing work item states
|
|
37
|
+
8. Organization, project, area paths, and states are project-specific — do not hardcode them
|
|
38
|
+
9. Work item states may use custom/localized names — always verify before updating
|
|
39
|
+
10. When creating Tasks: do NOT assign, ALWAYS ask Area Path, activity requires user selection
|
|
40
|
+
|
|
41
|
+
## Operations
|
|
42
|
+
|
|
43
|
+
### list
|
|
44
|
+
|
|
45
|
+
List work items assigned to the current user.
|
|
46
|
+
|
|
47
|
+
**MCP:** `mcp__azure-devops__wit_my_work_items` with `project: {project}`, `type: "assignedtome"`, `includeCompleted: false`
|
|
48
|
+
|
|
49
|
+
**Fallback:**
|
|
50
|
+
```bash
|
|
51
|
+
AZURE_DEVOPS_EXT_PAT=$AZURE_DEVOPS_PAT az boards query \
|
|
52
|
+
--wiql "SELECT [System.Id], [System.Title], [System.State], [System.WorkItemType] FROM WorkItems WHERE [System.AssignedTo] = @Me AND [System.State] <> 'Closed'" \
|
|
53
|
+
--project {project} --organization {organization} --output table
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Output:** Display ID, Title, State, Assigned To, Work Item Type as formatted table.
|
|
57
|
+
|
|
58
|
+
### show
|
|
59
|
+
|
|
60
|
+
Show full details of a work item by ID.
|
|
61
|
+
|
|
62
|
+
**Params:** `id` (required)
|
|
63
|
+
|
|
64
|
+
**MCP:** `mcp__azure-devops__wit_get_work_item` with `id: {id}`, `project: {project}`, `expand: "relations"`
|
|
65
|
+
|
|
66
|
+
**Fallback:**
|
|
67
|
+
```bash
|
|
68
|
+
AZURE_DEVOPS_EXT_PAT=$AZURE_DEVOPS_PAT az boards work-item show --id {id} --organization {organization} --output json
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Output:** Parse and display id, type, title, state, description, acceptance criteria, assignedTo, iterationPath, areaPath, priority, storyPoints, relations.
|
|
72
|
+
|
|
73
|
+
### start
|
|
74
|
+
|
|
75
|
+
Start working on a task — set state to active and begin implementation.
|
|
76
|
+
|
|
77
|
+
**Params:** `id` (required)
|
|
78
|
+
|
|
79
|
+
**Process:**
|
|
80
|
+
1. Fetch work item details using "show" operation
|
|
81
|
+
2. Display title, description, and acceptance criteria to user
|
|
82
|
+
3. Update state to the active state in ADO
|
|
83
|
+
4. Create implementation plan based on requirements
|
|
84
|
+
5. Ask user for confirmation before proceeding
|
|
85
|
+
|
|
86
|
+
**MCP:** `mcp__azure-devops__wit_update_work_item` with `id: {id}`, `updates: [{"path": "/fields/System.State", "value": "{active_state}"}]`
|
|
87
|
+
|
|
88
|
+
**Fallback:**
|
|
89
|
+
```bash
|
|
90
|
+
AZURE_DEVOPS_EXT_PAT=$AZURE_DEVOPS_PAT az boards work-item update --id {id} --state "{active_state}" --organization {organization} --output json
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Output:** Confirm state change. Show implementation plan. Wait for user approval.
|
|
94
|
+
|
|
95
|
+
### complete
|
|
96
|
+
|
|
97
|
+
Complete a task — verify criteria, final commit, set state to closed.
|
|
98
|
+
|
|
99
|
+
**Params:** `id` (required)
|
|
100
|
+
|
|
101
|
+
**Process:**
|
|
102
|
+
1. Fetch work item details — verify acceptance criteria are met
|
|
103
|
+
2. Run final tests if applicable
|
|
104
|
+
3. Make final commit referencing work item ID
|
|
105
|
+
4. Update state to "Closed" in ADO
|
|
106
|
+
5. Add completion comment with commit SHA
|
|
107
|
+
|
|
108
|
+
**MCP:** `mcp__azure-devops__wit_update_work_item` with `id: {id}`, `updates: [{"path": "/fields/System.State", "value": "Closed"}]`
|
|
109
|
+
|
|
110
|
+
**Fallback:**
|
|
111
|
+
```bash
|
|
112
|
+
AZURE_DEVOPS_EXT_PAT=$AZURE_DEVOPS_PAT az boards work-item update --id {id} --state "Closed" --organization {organization} --output json
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**Output:** Confirm completion. Show final commit SHA, updated state, summary.
|
|
116
|
+
|
|
117
|
+
### commit
|
|
118
|
+
|
|
119
|
+
Make an incremental commit linked to a work item.
|
|
120
|
+
|
|
121
|
+
**Params:** `message` (optional), `work-item-id` (optional)
|
|
122
|
+
|
|
123
|
+
**Process:**
|
|
124
|
+
1. Delegate to git-cli skill `/git-commit`
|
|
125
|
+
2. Append work item reference to commit message (e.g., `#123`)
|
|
126
|
+
|
|
127
|
+
**Output:** Commit hash, message, files changed. Tip: use `/git-push` to push.
|
|
128
|
+
|
|
129
|
+
**Notes:** This operation delegates to the git-cli skill for actual Git operations.
|
|
130
|
+
|
|
131
|
+
### create
|
|
132
|
+
|
|
133
|
+
Create a new work item in ADO.
|
|
134
|
+
|
|
135
|
+
**Params:** `type` (required), `title` (required), `description` (optional), `area-path` (optional — ALWAYS ask user), `activity` (optional — present types to user)
|
|
136
|
+
|
|
137
|
+
**MCP:** `mcp__azure-devops__wit_create_work_item` with `project: {project}`, `type: "{type}"`, `title: "{title}"`, `additionalFields: {"System.Description": "{description}", "System.AreaPath": "{area-path}", "Microsoft.VSTS.Common.Activity": "{activity}", "Microsoft.VSTS.Scheduling.OriginalEstimate": 0, "Microsoft.VSTS.Scheduling.RemainingWork": 0, "Microsoft.VSTS.Scheduling.CompletedWork": 0}`
|
|
138
|
+
|
|
139
|
+
**Fallback:**
|
|
140
|
+
```bash
|
|
141
|
+
AZURE_DEVOPS_EXT_PAT=$AZURE_DEVOPS_PAT az boards work-item create \
|
|
142
|
+
--type "{type}" --title "{title}" --project {project} \
|
|
143
|
+
--organization {organization} --area "{area-path}" \
|
|
144
|
+
--fields "Microsoft.VSTS.Common.Activity={activity}" \
|
|
145
|
+
--output json
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Output:** Parse response: extract id, title, state, url.
|
|
149
|
+
|
|
150
|
+
**Notes:**
|
|
151
|
+
- Omit additionalFields entries for params not provided
|
|
152
|
+
- Do NOT assign unless explicitly requested
|
|
153
|
+
- ALWAYS ask user for Area Path before executing
|
|
154
|
+
|
|
155
|
+
## Error Handling
|
|
156
|
+
|
|
157
|
+
| Error | Cause | Resolution |
|
|
158
|
+
|-------|-------|------------|
|
|
159
|
+
| MCP server not available | Not configured or not running | Check `.mcp.json`, verify PAT in `.env`, restart AI tool |
|
|
160
|
+
| 401 / 403 | Invalid or expired PAT | Check PAT scopes: Work Items (Read, Write, Manage), Project (Read) |
|
|
161
|
+
| Work item type not found | Process template mismatch | Agile = "User Story", Scrum = "Product Backlog Item" |
|
|
162
|
+
| State transition not allowed | Invalid state change | Verify allowed transitions for the work item type |
|
|
163
|
+
| MCP tool call fails | Various | Fall back to `az` CLI equivalent command |
|