postar-pipe-mcp 0.0.2 → 1.0.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/server.js +368 -31
- package/dist/skills/cd/SKILL.md +9 -13
- package/dist/skills/ci/SKILL.md +152 -28
- package/dist/skills/ci/template.yaml +13 -0
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { ZodOptional as ZodOptional$2, z } from "zod";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import require$$0, { existsSync, readdirSync, mkdirSync, copyFileSync, readFileSync, writeFileSync, rmSync } from "fs";
|
|
5
|
+
import { tmpdir } from "os";
|
|
6
|
+
import require$$0$1, { resolve as resolve$3, dirname, join } from "path";
|
|
4
7
|
import require$$0$2 from "child_process";
|
|
5
|
-
import require$$0$1, { dirname, resolve as resolve$3 } from "path";
|
|
6
|
-
import require$$0, { existsSync, readdirSync, readFileSync } from "fs";
|
|
7
8
|
import process$1 from "node:process";
|
|
8
9
|
import { PassThrough } from "node:stream";
|
|
9
10
|
import { fileURLToPath } from "url";
|
|
@@ -21197,15 +21198,75 @@ async function handleProxiedTool(name, args) {
|
|
|
21197
21198
|
const skillCache = /* @__PURE__ */ new Map();
|
|
21198
21199
|
const skillMetadataCache = /* @__PURE__ */ new Map();
|
|
21199
21200
|
let remoteSkillsList = null;
|
|
21201
|
+
function parseSkillSource(url, index) {
|
|
21202
|
+
const rawUrl = url.replace("/tree/", "/raw/").replace(/\/$/, "");
|
|
21203
|
+
const match = url.match(/\/([^\/]+)\/[^\/]+\/tree\//);
|
|
21204
|
+
const name = match ? match[1] : `source-${index}`;
|
|
21205
|
+
return {
|
|
21206
|
+
name,
|
|
21207
|
+
baseUrl: rawUrl,
|
|
21208
|
+
originalUrl: url
|
|
21209
|
+
};
|
|
21210
|
+
}
|
|
21211
|
+
function parseCommandLineArgs() {
|
|
21212
|
+
const result = {};
|
|
21213
|
+
const skillUrls = [];
|
|
21214
|
+
for (let i = 0; i < process.argv.length; i++) {
|
|
21215
|
+
const arg = process.argv[i];
|
|
21216
|
+
if (arg === "--skill-url" && i + 1 < process.argv.length) {
|
|
21217
|
+
skillUrls.push(process.argv[i + 1]);
|
|
21218
|
+
} else if (arg.startsWith("--skill-url=")) {
|
|
21219
|
+
skillUrls.push(arg.split("=")[1]);
|
|
21220
|
+
}
|
|
21221
|
+
if (arg.startsWith("--skills-urls=")) {
|
|
21222
|
+
result.skillsUrls = arg.split("=")[1];
|
|
21223
|
+
} else if (arg === "--skills-urls" && i + 1 < process.argv.length) {
|
|
21224
|
+
result.skillsUrls = process.argv[i + 1];
|
|
21225
|
+
}
|
|
21226
|
+
if (arg.startsWith("--skills-url=")) {
|
|
21227
|
+
result.skillsUrl = arg.split("=")[1];
|
|
21228
|
+
} else if (arg === "--skills-url" && i + 1 < process.argv.length) {
|
|
21229
|
+
result.skillsUrl = process.argv[i + 1];
|
|
21230
|
+
}
|
|
21231
|
+
}
|
|
21232
|
+
if (skillUrls.length > 0) {
|
|
21233
|
+
result.skillUrls = skillUrls;
|
|
21234
|
+
}
|
|
21235
|
+
return result;
|
|
21236
|
+
}
|
|
21200
21237
|
function getSkillsConfig() {
|
|
21201
|
-
const
|
|
21238
|
+
const cliArgs = parseCommandLineArgs();
|
|
21239
|
+
if (cliArgs.skillUrls && cliArgs.skillUrls.length > 0) {
|
|
21240
|
+
const sources = cliArgs.skillUrls.map((url, index) => parseSkillSource(url.trim(), index)).filter((source) => source.originalUrl.length > 0);
|
|
21241
|
+
if (sources.length > 0) {
|
|
21242
|
+
return {
|
|
21243
|
+
source: "remote",
|
|
21244
|
+
sources,
|
|
21245
|
+
baseUrl: sources[0].baseUrl,
|
|
21246
|
+
originalUrl: sources[0].originalUrl
|
|
21247
|
+
};
|
|
21248
|
+
}
|
|
21249
|
+
}
|
|
21250
|
+
const skillsUrls = cliArgs.skillsUrls || process.env.SKILLS_URLS;
|
|
21251
|
+
if (skillsUrls) {
|
|
21252
|
+
const sources = skillsUrls.split(",").map((url, index) => parseSkillSource(url.trim(), index)).filter((source) => source.originalUrl.length > 0);
|
|
21253
|
+
if (sources.length > 0) {
|
|
21254
|
+
return {
|
|
21255
|
+
source: "remote",
|
|
21256
|
+
sources,
|
|
21257
|
+
baseUrl: sources[0].baseUrl,
|
|
21258
|
+
originalUrl: sources[0].originalUrl
|
|
21259
|
+
};
|
|
21260
|
+
}
|
|
21261
|
+
}
|
|
21262
|
+
const skillsUrl = cliArgs.skillsUrl || process.env.SKILLS_URL;
|
|
21202
21263
|
if (skillsUrl) {
|
|
21203
|
-
const
|
|
21264
|
+
const source = parseSkillSource(skillsUrl, 0);
|
|
21204
21265
|
return {
|
|
21205
21266
|
source: "remote",
|
|
21206
|
-
|
|
21207
|
-
|
|
21208
|
-
|
|
21267
|
+
sources: [source],
|
|
21268
|
+
baseUrl: source.baseUrl,
|
|
21269
|
+
originalUrl: source.originalUrl
|
|
21209
21270
|
};
|
|
21210
21271
|
}
|
|
21211
21272
|
return {
|
|
@@ -21283,21 +21344,52 @@ async function fetchRemoteSkillsFromDirectory(config2) {
|
|
|
21283
21344
|
return [];
|
|
21284
21345
|
}
|
|
21285
21346
|
}
|
|
21347
|
+
const skillSourceMap = /* @__PURE__ */ new Map();
|
|
21286
21348
|
async function fetchRemoteSkillsList(config2) {
|
|
21287
21349
|
if (remoteSkillsList) {
|
|
21288
21350
|
return remoteSkillsList;
|
|
21289
21351
|
}
|
|
21290
21352
|
const skillsListEnv = process.env.SKILLS_LIST;
|
|
21291
21353
|
if (skillsListEnv) {
|
|
21292
|
-
const
|
|
21293
|
-
remoteSkillsList =
|
|
21294
|
-
console.error(`[SKILL-LOADER] 从环境变量加载 skills: ${
|
|
21295
|
-
return
|
|
21354
|
+
const skills = skillsListEnv.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
21355
|
+
remoteSkillsList = skills;
|
|
21356
|
+
console.error(`[SKILL-LOADER] 从环境变量加载 skills: ${skills.join(", ")}`);
|
|
21357
|
+
return skills;
|
|
21358
|
+
}
|
|
21359
|
+
console.error(`[SKILL-LOADER] 未配置 SKILLS_LIST,自动遍历所有远程目录...`);
|
|
21360
|
+
const allSkills = [];
|
|
21361
|
+
skillSourceMap.clear();
|
|
21362
|
+
if (config2.sources && config2.sources.length > 0) {
|
|
21363
|
+
for (const source of config2.sources) {
|
|
21364
|
+
console.error(`[SKILL-LOADER] 扫描源: ${source.name} (${source.originalUrl})`);
|
|
21365
|
+
const sourceConfig = {
|
|
21366
|
+
source: "remote",
|
|
21367
|
+
baseUrl: source.baseUrl,
|
|
21368
|
+
originalUrl: source.originalUrl
|
|
21369
|
+
};
|
|
21370
|
+
const skills = await fetchRemoteSkillsFromDirectory(sourceConfig);
|
|
21371
|
+
console.error(`[SKILL-LOADER] 源 ${source.name} 发现 skills: ${skills.join(", ")}`);
|
|
21372
|
+
for (const skillName of skills) {
|
|
21373
|
+
if (!allSkills.includes(skillName)) {
|
|
21374
|
+
allSkills.push(skillName);
|
|
21375
|
+
}
|
|
21376
|
+
skillSourceMap.set(skillName, source);
|
|
21377
|
+
}
|
|
21378
|
+
}
|
|
21379
|
+
} else if (config2.baseUrl && config2.originalUrl) {
|
|
21380
|
+
const skills = await fetchRemoteSkillsFromDirectory(config2);
|
|
21381
|
+
for (const skillName of skills) {
|
|
21382
|
+
allSkills.push(skillName);
|
|
21383
|
+
skillSourceMap.set(skillName, {
|
|
21384
|
+
name: "default",
|
|
21385
|
+
baseUrl: config2.baseUrl,
|
|
21386
|
+
originalUrl: config2.originalUrl
|
|
21387
|
+
});
|
|
21388
|
+
}
|
|
21296
21389
|
}
|
|
21297
|
-
console.error(`[SKILL-LOADER]
|
|
21298
|
-
|
|
21299
|
-
|
|
21300
|
-
return skills;
|
|
21390
|
+
console.error(`[SKILL-LOADER] 汇总所有 skills: ${allSkills.join(", ")}`);
|
|
21391
|
+
remoteSkillsList = allSkills;
|
|
21392
|
+
return allSkills;
|
|
21301
21393
|
}
|
|
21302
21394
|
async function loadRemoteSkillContent(skillName, baseUrl) {
|
|
21303
21395
|
const skillUrl = `${baseUrl}/${skillName}/SKILL.md`;
|
|
@@ -21357,8 +21449,20 @@ async function getAvailableSkills() {
|
|
|
21357
21449
|
}
|
|
21358
21450
|
async function loadSkillContent(skillName) {
|
|
21359
21451
|
const config2 = getSkillsConfig();
|
|
21360
|
-
if (config2.source === "remote"
|
|
21361
|
-
|
|
21452
|
+
if (config2.source === "remote") {
|
|
21453
|
+
const source = skillSourceMap.get(skillName);
|
|
21454
|
+
if (source) {
|
|
21455
|
+
console.error(`[SKILL-LOADER] 从源 ${source.name} 加载 skill: ${skillName}`);
|
|
21456
|
+
return loadRemoteSkillContent(skillName, source.baseUrl);
|
|
21457
|
+
}
|
|
21458
|
+
if (config2.sources && config2.sources.length > 0) {
|
|
21459
|
+
const firstSource = config2.sources[0];
|
|
21460
|
+
console.error(`[SKILL-LOADER] 未找到来源映射,使用默认源 ${firstSource.name} 加载: ${skillName}`);
|
|
21461
|
+
return loadRemoteSkillContent(skillName, firstSource.baseUrl);
|
|
21462
|
+
}
|
|
21463
|
+
if (config2.baseUrl) {
|
|
21464
|
+
return loadRemoteSkillContent(skillName, config2.baseUrl);
|
|
21465
|
+
}
|
|
21362
21466
|
}
|
|
21363
21467
|
const skillPath = resolve$3(getSkillsDir(), skillName, "SKILL.md");
|
|
21364
21468
|
try {
|
|
@@ -21376,7 +21480,7 @@ async function getSkillContent(skillName) {
|
|
|
21376
21480
|
}
|
|
21377
21481
|
return skillCache.get(skillName);
|
|
21378
21482
|
}
|
|
21379
|
-
function parseFrontmatter(content) {
|
|
21483
|
+
async function parseFrontmatter(content) {
|
|
21380
21484
|
const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
|
|
21381
21485
|
const match = content.match(frontmatterRegex);
|
|
21382
21486
|
if (!match) {
|
|
@@ -21384,30 +21488,37 @@ function parseFrontmatter(content) {
|
|
|
21384
21488
|
}
|
|
21385
21489
|
const frontmatterText = match[1];
|
|
21386
21490
|
const body = match[2];
|
|
21387
|
-
|
|
21388
|
-
|
|
21389
|
-
|
|
21390
|
-
|
|
21391
|
-
|
|
21392
|
-
|
|
21393
|
-
|
|
21394
|
-
|
|
21395
|
-
|
|
21491
|
+
try {
|
|
21492
|
+
const yaml = await import("js-yaml");
|
|
21493
|
+
const metadata2 = yaml.load(frontmatterText) || {};
|
|
21494
|
+
return { metadata: metadata2, body };
|
|
21495
|
+
} catch {
|
|
21496
|
+
const metadata2 = {};
|
|
21497
|
+
const lines = frontmatterText.split("\n");
|
|
21498
|
+
for (const line of lines) {
|
|
21499
|
+
const colonIndex = line.indexOf(":");
|
|
21500
|
+
if (colonIndex > 0 && !line.startsWith("-") && !line.startsWith(" ")) {
|
|
21501
|
+
const key = line.slice(0, colonIndex).trim();
|
|
21502
|
+
let value = line.slice(colonIndex + 1).trim();
|
|
21503
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
21504
|
+
value = value.slice(1, -1);
|
|
21505
|
+
}
|
|
21506
|
+
metadata2[key] = value;
|
|
21396
21507
|
}
|
|
21397
|
-
metadata2[key] = value;
|
|
21398
21508
|
}
|
|
21509
|
+
return { metadata: metadata2, body };
|
|
21399
21510
|
}
|
|
21400
|
-
return { metadata: metadata2, body };
|
|
21401
21511
|
}
|
|
21402
21512
|
async function getSkillMetadata(skillName) {
|
|
21403
21513
|
if (!skillMetadataCache.has(skillName)) {
|
|
21404
21514
|
const content = await getSkillContent(skillName);
|
|
21405
|
-
const { metadata: metadata2 } = parseFrontmatter(content);
|
|
21515
|
+
const { metadata: metadata2 } = await parseFrontmatter(content);
|
|
21406
21516
|
const metadataObj = {
|
|
21407
21517
|
name: metadata2.name || skillName,
|
|
21408
21518
|
description: metadata2.description || `${skillName} skill`,
|
|
21409
21519
|
constraint: metadata2.constraint,
|
|
21410
21520
|
version: metadata2.version,
|
|
21521
|
+
tools: metadata2.tools || [],
|
|
21411
21522
|
...metadata2
|
|
21412
21523
|
// 包含其他自定义字段
|
|
21413
21524
|
};
|
|
@@ -21419,6 +21530,184 @@ async function getAllSkillsMetadata() {
|
|
|
21419
21530
|
const skills = await getAvailableSkills();
|
|
21420
21531
|
return Promise.all(skills.map((skillName) => getSkillMetadata(skillName)));
|
|
21421
21532
|
}
|
|
21533
|
+
let globalTempSkillDir = null;
|
|
21534
|
+
function setTempSkillDir(tempDir) {
|
|
21535
|
+
globalTempSkillDir = tempDir;
|
|
21536
|
+
}
|
|
21537
|
+
function getTempSkillDir() {
|
|
21538
|
+
return globalTempSkillDir;
|
|
21539
|
+
}
|
|
21540
|
+
function getSkillResourceFiles(skillName) {
|
|
21541
|
+
if (!globalTempSkillDir) {
|
|
21542
|
+
return [];
|
|
21543
|
+
}
|
|
21544
|
+
const skillDir = resolve$3(globalTempSkillDir, skillName);
|
|
21545
|
+
if (!existsSync(skillDir)) {
|
|
21546
|
+
return [];
|
|
21547
|
+
}
|
|
21548
|
+
try {
|
|
21549
|
+
return readdirSync(skillDir).filter((name) => name !== "SKILL.md");
|
|
21550
|
+
} catch {
|
|
21551
|
+
return [];
|
|
21552
|
+
}
|
|
21553
|
+
}
|
|
21554
|
+
function getWorkspaceSkillDir(skillName) {
|
|
21555
|
+
return resolve$3(globalTempSkillDir, skillName);
|
|
21556
|
+
}
|
|
21557
|
+
function copyDirectorySync(src, dest) {
|
|
21558
|
+
mkdirSync(dest, { recursive: true });
|
|
21559
|
+
const entries = readdirSync(src, { withFileTypes: true });
|
|
21560
|
+
for (const entry of entries) {
|
|
21561
|
+
const srcPath = resolve$3(src, entry.name);
|
|
21562
|
+
const destPath = resolve$3(dest, entry.name);
|
|
21563
|
+
if (entry.isDirectory()) {
|
|
21564
|
+
copyDirectorySync(srcPath, destPath);
|
|
21565
|
+
} else {
|
|
21566
|
+
copyFileSync(srcPath, destPath);
|
|
21567
|
+
}
|
|
21568
|
+
}
|
|
21569
|
+
}
|
|
21570
|
+
async function downloadRemoteFile(url, targetPath) {
|
|
21571
|
+
try {
|
|
21572
|
+
const headers = {};
|
|
21573
|
+
const gitlabToken = process.env.GITLAB_TOKEN;
|
|
21574
|
+
if (gitlabToken) {
|
|
21575
|
+
headers["PRIVATE-TOKEN"] = gitlabToken;
|
|
21576
|
+
}
|
|
21577
|
+
const response = await fetch(url, { headers });
|
|
21578
|
+
if (!response.ok) {
|
|
21579
|
+
console.error(`[SKILL-LOADER] 下载远程文件失败: ${url} - ${response.status}`);
|
|
21580
|
+
return false;
|
|
21581
|
+
}
|
|
21582
|
+
const content = await response.text();
|
|
21583
|
+
if (content.trim().startsWith("<!DOCTYPE") || content.trim().startsWith("<html")) {
|
|
21584
|
+
console.error(`[SKILL-LOADER] 下载远程文件返回了 HTML 页面: ${url}`);
|
|
21585
|
+
return false;
|
|
21586
|
+
}
|
|
21587
|
+
mkdirSync(dirname(targetPath), { recursive: true });
|
|
21588
|
+
writeFileSync(targetPath, content, "utf-8");
|
|
21589
|
+
return true;
|
|
21590
|
+
} catch (error2) {
|
|
21591
|
+
console.error(`[SKILL-LOADER] 下载远程文件异常: ${url}`, error2);
|
|
21592
|
+
return false;
|
|
21593
|
+
}
|
|
21594
|
+
}
|
|
21595
|
+
async function listRemoteSkillFiles(skillName, source) {
|
|
21596
|
+
try {
|
|
21597
|
+
const parsed = await parseGitLabUrl(source.originalUrl);
|
|
21598
|
+
if (!parsed) {
|
|
21599
|
+
console.error(`[SKILL-LOADER] 无法解析远程 URL: ${source.originalUrl}`);
|
|
21600
|
+
return [];
|
|
21601
|
+
}
|
|
21602
|
+
const { host, projectPath, ref: ref2, path: basePath } = parsed;
|
|
21603
|
+
const skillPath = basePath ? `${basePath}/${skillName}` : skillName;
|
|
21604
|
+
const encodedProjectPath = encodeURIComponent(projectPath);
|
|
21605
|
+
const apiUrl = `${host}/api/v4/projects/${encodedProjectPath}/repository/tree?ref=${encodeURIComponent(ref2)}&path=${encodeURIComponent(skillPath)}`;
|
|
21606
|
+
const headers = {};
|
|
21607
|
+
const gitlabToken = process.env.GITLAB_TOKEN;
|
|
21608
|
+
if (gitlabToken) {
|
|
21609
|
+
headers["PRIVATE-TOKEN"] = gitlabToken;
|
|
21610
|
+
}
|
|
21611
|
+
const response = await fetch(apiUrl, { headers });
|
|
21612
|
+
if (!response.ok) {
|
|
21613
|
+
console.error(`[SKILL-LOADER] 获取远程文件列表失败: ${response.status}`);
|
|
21614
|
+
return [];
|
|
21615
|
+
}
|
|
21616
|
+
const items2 = await response.json();
|
|
21617
|
+
return items2.filter((item) => item.type === "blob" && item.name !== "SKILL.md").map((item) => item.name);
|
|
21618
|
+
} catch (error2) {
|
|
21619
|
+
console.error(`[SKILL-LOADER] 获取远程文件列表异常:`, error2);
|
|
21620
|
+
return [];
|
|
21621
|
+
}
|
|
21622
|
+
}
|
|
21623
|
+
async function extractRemoteSkillResourcesToTempDir(skillName, source) {
|
|
21624
|
+
const extractedPaths = [];
|
|
21625
|
+
const files = await listRemoteSkillFiles(skillName, source);
|
|
21626
|
+
if (files.length === 0) {
|
|
21627
|
+
console.error(`[SKILL-LOADER] 远程 Skill ${skillName} 没有资源文件`);
|
|
21628
|
+
return [];
|
|
21629
|
+
}
|
|
21630
|
+
console.error(`[SKILL-LOADER] 发现远程 Skill ${skillName} 的资源文件: ${files.join(", ")}`);
|
|
21631
|
+
for (const fileName of files) {
|
|
21632
|
+
const fileUrl = `${source.baseUrl}/${skillName}/${fileName}`;
|
|
21633
|
+
const targetDir = getWorkspaceSkillDir(skillName);
|
|
21634
|
+
const targetPath = resolve$3(targetDir, fileName);
|
|
21635
|
+
const success = await downloadRemoteFile(fileUrl, targetPath);
|
|
21636
|
+
if (success) {
|
|
21637
|
+
extractedPaths.push(targetPath);
|
|
21638
|
+
console.error(`[SKILL-LOADER] 已下载远程资源: ${skillName}/${fileName} -> ${targetPath}`);
|
|
21639
|
+
} else {
|
|
21640
|
+
console.error(`[SKILL-LOADER] 下载远程资源失败: ${skillName}/${fileName}`);
|
|
21641
|
+
}
|
|
21642
|
+
}
|
|
21643
|
+
return extractedPaths;
|
|
21644
|
+
}
|
|
21645
|
+
async function extractSkillResourcesToTempDir(skillName) {
|
|
21646
|
+
const config2 = getSkillsConfig();
|
|
21647
|
+
if (config2.source === "remote") {
|
|
21648
|
+
const source = skillSourceMap.get(skillName);
|
|
21649
|
+
if (source) {
|
|
21650
|
+
console.error(`[SKILL-LOADER] 从远程源 ${source.name} 提取 Skill ${skillName} 的资源`);
|
|
21651
|
+
return extractRemoteSkillResourcesToTempDir(skillName, source);
|
|
21652
|
+
}
|
|
21653
|
+
if (config2.sources && config2.sources.length > 0) {
|
|
21654
|
+
console.error(`[SKILL-LOADER] 使用默认远程源提取 Skill ${skillName} 的资源`);
|
|
21655
|
+
return extractRemoteSkillResourcesToTempDir(skillName, config2.sources[0]);
|
|
21656
|
+
}
|
|
21657
|
+
console.error(`[SKILL-LOADER] 远程模式下找不到 Skill ${skillName} 的源配置`);
|
|
21658
|
+
return [];
|
|
21659
|
+
}
|
|
21660
|
+
const skillDir = getSkillsDir();
|
|
21661
|
+
const sourceDir = resolve$3(skillDir, skillName);
|
|
21662
|
+
if (!existsSync(sourceDir)) {
|
|
21663
|
+
console.error(`[SKILL-LOADER] Skill 目录不存在: ${sourceDir}`);
|
|
21664
|
+
return [];
|
|
21665
|
+
}
|
|
21666
|
+
const extractedPaths = [];
|
|
21667
|
+
try {
|
|
21668
|
+
const entries = readdirSync(sourceDir, { withFileTypes: true });
|
|
21669
|
+
for (const entry of entries) {
|
|
21670
|
+
if (entry.name === "SKILL.md") {
|
|
21671
|
+
continue;
|
|
21672
|
+
}
|
|
21673
|
+
const sourcePath = resolve$3(sourceDir, entry.name);
|
|
21674
|
+
const targetDir = getWorkspaceSkillDir(skillName);
|
|
21675
|
+
const targetPath = resolve$3(targetDir, entry.name);
|
|
21676
|
+
try {
|
|
21677
|
+
mkdirSync(dirname(targetPath), { recursive: true });
|
|
21678
|
+
if (entry.isDirectory()) {
|
|
21679
|
+
copyDirectorySync(sourcePath, targetPath);
|
|
21680
|
+
} else {
|
|
21681
|
+
copyFileSync(sourcePath, targetPath);
|
|
21682
|
+
}
|
|
21683
|
+
extractedPaths.push(targetPath);
|
|
21684
|
+
console.error(`[SKILL-LOADER] 已提取资源: ${skillName}/${entry.name} -> ${targetPath}`);
|
|
21685
|
+
} catch (error2) {
|
|
21686
|
+
console.error(`[SKILL-LOADER] 提取资源失败: ${skillName}/${entry.name}`, error2);
|
|
21687
|
+
}
|
|
21688
|
+
}
|
|
21689
|
+
} catch (error2) {
|
|
21690
|
+
console.error(`[SKILL-LOADER] 读取 Skill 目录失败: ${sourceDir}`, error2);
|
|
21691
|
+
}
|
|
21692
|
+
return extractedPaths;
|
|
21693
|
+
}
|
|
21694
|
+
async function extractAllSkillResourcesToTempDir(tempDir) {
|
|
21695
|
+
setTempSkillDir(tempDir);
|
|
21696
|
+
const skills = await getAvailableSkills();
|
|
21697
|
+
const allExtracted = {};
|
|
21698
|
+
console.error(`[SKILL-LOADER] 开始提取 ${skills.length} 个 Skill 的资源到临时目录: ${tempDir}`);
|
|
21699
|
+
for (const skillName of skills) {
|
|
21700
|
+
const paths = await extractSkillResourcesToTempDir(skillName);
|
|
21701
|
+
if (paths.length > 0) {
|
|
21702
|
+
allExtracted[skillName] = paths;
|
|
21703
|
+
}
|
|
21704
|
+
}
|
|
21705
|
+
const totalCount = Object.values(allExtracted).flat().length;
|
|
21706
|
+
console.error(`[SKILL-LOADER] 资源提取完成,共 ${totalCount} 个文件`);
|
|
21707
|
+
return allExtracted;
|
|
21708
|
+
}
|
|
21709
|
+
const FIXED_SKILL_DIR = join(tmpdir(), "mcp-pipe-skills");
|
|
21710
|
+
let skillResourcesDir = FIXED_SKILL_DIR;
|
|
21422
21711
|
async function createServer() {
|
|
21423
21712
|
const server = new McpServer({
|
|
21424
21713
|
name: "mcp-pipe",
|
|
@@ -21440,6 +21729,27 @@ async function createServer() {
|
|
|
21440
21729
|
const skillLoaded = skills.includes(metadata2.name);
|
|
21441
21730
|
const skillMetadata = await getSkillMetadata(metadata2.name);
|
|
21442
21731
|
const skillContent = await getSkillContent(metadata2.name);
|
|
21732
|
+
let injectedContent = skillContent;
|
|
21733
|
+
const currentTempDir = getTempSkillDir();
|
|
21734
|
+
if (currentTempDir) {
|
|
21735
|
+
const skillResourceDir = join(currentTempDir, metadata2.name);
|
|
21736
|
+
const resourceFiles = getSkillResourceFiles(metadata2.name);
|
|
21737
|
+
let resourceNotice = `> 📁 **本 Skill 的资源文件**:
|
|
21738
|
+
`;
|
|
21739
|
+
if (resourceFiles.length > 0) {
|
|
21740
|
+
for (const file of resourceFiles) {
|
|
21741
|
+
resourceNotice += `> - \`${join(skillResourceDir, file)}\`
|
|
21742
|
+
`;
|
|
21743
|
+
}
|
|
21744
|
+
} else {
|
|
21745
|
+
resourceNotice += "> (无额外资源文件)\n";
|
|
21746
|
+
}
|
|
21747
|
+
resourceNotice += `>
|
|
21748
|
+
> 💡 **提示**: 当 Skill 文档中提到读取配置文件或模板文件时,请使用上述完整路径
|
|
21749
|
+
|
|
21750
|
+
`;
|
|
21751
|
+
injectedContent = resourceNotice + skillContent;
|
|
21752
|
+
}
|
|
21443
21753
|
return {
|
|
21444
21754
|
content: [{
|
|
21445
21755
|
type: "text",
|
|
@@ -21447,7 +21757,7 @@ async function createServer() {
|
|
|
21447
21757
|
|
|
21448
21758
|
📖 ${skillMetadata.description}
|
|
21449
21759
|
|
|
21450
|
-
${
|
|
21760
|
+
${injectedContent}
|
|
21451
21761
|
|
|
21452
21762
|
---
|
|
21453
21763
|
${skillLoaded ? "✅" : "❌"} Skills 加载状态: ${skillLoaded ? `${metadata2.name} skill 已加载` : `${metadata2.name} skill 未加载`}
|
|
@@ -21511,8 +21821,34 @@ async function preloadSkills() {
|
|
|
21511
21821
|
}
|
|
21512
21822
|
}
|
|
21513
21823
|
}
|
|
21824
|
+
function cleanupSkillDir() {
|
|
21825
|
+
if (existsSync(skillResourcesDir)) {
|
|
21826
|
+
try {
|
|
21827
|
+
rmSync(skillResourcesDir, { recursive: true, force: true });
|
|
21828
|
+
console.error(`[MCP-PIPE] Skill 资源目录已清理: ${skillResourcesDir}`);
|
|
21829
|
+
} catch (error2) {
|
|
21830
|
+
console.error(`[MCP-PIPE] 清理 Skill 资源目录失败: ${skillResourcesDir}`, error2);
|
|
21831
|
+
}
|
|
21832
|
+
}
|
|
21833
|
+
}
|
|
21514
21834
|
async function main() {
|
|
21835
|
+
process.on("exit", cleanupSkillDir);
|
|
21836
|
+
process.on("SIGINT", () => {
|
|
21837
|
+
console.error("[MCP-PIPE] 收到 SIGINT,正在清理...");
|
|
21838
|
+
cleanupSkillDir();
|
|
21839
|
+
process.exit(0);
|
|
21840
|
+
});
|
|
21841
|
+
process.on("SIGTERM", () => {
|
|
21842
|
+
console.error("[MCP-PIPE] 收到 SIGTERM,正在清理...");
|
|
21843
|
+
cleanupSkillDir();
|
|
21844
|
+
process.exit(0);
|
|
21845
|
+
});
|
|
21846
|
+
if (!existsSync(skillResourcesDir)) {
|
|
21847
|
+
mkdirSync(skillResourcesDir, { recursive: true });
|
|
21848
|
+
}
|
|
21849
|
+
console.error(`[MCP-PIPE] Skill 资源目录: ${skillResourcesDir}`);
|
|
21515
21850
|
await preloadSkills();
|
|
21851
|
+
await extractAllSkillResourcesToTempDir(skillResourcesDir);
|
|
21516
21852
|
initMCPClients();
|
|
21517
21853
|
const server = await createServer();
|
|
21518
21854
|
const transport = new StdioServerTransport();
|
|
@@ -21521,5 +21857,6 @@ async function main() {
|
|
|
21521
21857
|
}
|
|
21522
21858
|
main().catch((error2) => {
|
|
21523
21859
|
console.error("[MCP-PIPE] 致命错误:", error2);
|
|
21860
|
+
cleanupSkillDir();
|
|
21524
21861
|
process.exit(1);
|
|
21525
21862
|
});
|
package/dist/skills/cd/SKILL.md
CHANGED
|
@@ -20,7 +20,7 @@ version: 1.0.0
|
|
|
20
20
|
|
|
21
21
|
## 说明
|
|
22
22
|
|
|
23
|
-
本指令用于直接触发 Jenkins 构建,执行服务部署。与 `/ci` 命令不同,`/cd`
|
|
23
|
+
本指令用于直接触发 Jenkins 构建,执行服务部署。与 `/ci` 命令不同,`/cd` 只负责部署。适用于仅需重新部署或部署特定分支的场景。
|
|
24
24
|
|
|
25
25
|
> **⚠️ 重要约束**:
|
|
26
26
|
> - 当 `config.yaml` 中设置 `jenkins.is_current_branch: false` 时,**严禁执行任何本地git命令**获取分支信息
|
|
@@ -83,7 +83,7 @@ version: 1.0.0
|
|
|
83
83
|
|
|
84
84
|
> **⚠️ 配置读取优先级**:按以下顺序读取配置,找到即停止:
|
|
85
85
|
> 1. 项目根目录的 `config.yaml`
|
|
86
|
-
> 2.
|
|
86
|
+
> 2. 默认模版配置 `./template.yaml`
|
|
87
87
|
>
|
|
88
88
|
> **必须每次重新读取**:禁止使用上下文缓存,每次执行时都要重新读取配置。
|
|
89
89
|
>
|
|
@@ -104,10 +104,14 @@ version: 1.0.0
|
|
|
104
104
|
- 若用户传入了 `branch:xxx`,提取指定分支名,**跳过分支获取**
|
|
105
105
|
- 若未指定分支,继续下一步
|
|
106
106
|
|
|
107
|
-
3.
|
|
108
|
-
-
|
|
109
|
-
|
|
107
|
+
3. **查找 Jenkins Job(优先执行)**:
|
|
108
|
+
- 对每个环境,按以下顺序查找配置:
|
|
109
|
+
1. 首先在 `config.yaml` 中查找对应配置
|
|
110
|
+
2. 若 `config.yaml` 中不存在,在 `template.yaml` 中查找对应配置
|
|
111
|
+
- **若配置存在(config.yaml 或 template.yaml)**:使用配置中的 `fullname` 和 MCP 名称,直接进入参数处理流程(步骤 2),**无需用户确认**
|
|
110
112
|
- **若配置不存在(智能发现模式)**:
|
|
113
|
+
- **智能发现定义**:只有 AI 主动去 Jenkins 上查找 Job 才算智能发现
|
|
114
|
+
- template.yaml 中的配置属于**预配置**,不算智能发现
|
|
111
115
|
1. **扫描所有 Jenkins MCP**:
|
|
112
116
|
- 读取 `mcp.json` 配置,识别所有可用的 Jenkins MCP(名称包含 `jenkins` 的 MCP)
|
|
113
117
|
- 按 MCP 名称排序,优先尝试 `jenkins`,然后是 `jenkins-prod`、`jenkins-test` 等其他 Jenkins MCP
|
|
@@ -472,11 +476,3 @@ params:
|
|
|
472
476
|
/cd dev-api,dev-admin branch:release/v1.0
|
|
473
477
|
```
|
|
474
478
|
|
|
475
|
-
## 与 /ci 命令的区别
|
|
476
|
-
|
|
477
|
-
| 命令 | 功能 | 适用场景 |
|
|
478
|
-
|------|------|----------|
|
|
479
|
-
| `/ci` | MR 合并 + 可选 Jenkins 构建 | 需要代码合并并部署 |
|
|
480
|
-
| `/cd` | 直接 Jenkins 构建 | 仅需部署,无需合并 |
|
|
481
|
-
|
|
482
|
-
> **建议**:日常开发使用 `/ci` 完成合并+部署;若只想重新部署或部署特定分支,使用 `/cd`。
|
package/dist/skills/ci/SKILL.md
CHANGED
|
@@ -55,7 +55,11 @@ version: 1.0.0
|
|
|
55
55
|
### 0. 分支存在性检查逻辑
|
|
56
56
|
|
|
57
57
|
创建 MR 前执行:
|
|
58
|
-
- 调用 `mcp_gitlab_browse_refs` (action=get_branch, branch={目标分支})
|
|
58
|
+
- 调用 `mcp_gitlab_browse_refs` (action=get_branch, project_id={project_id}, branch={目标分支})
|
|
59
|
+
- **必需参数**:
|
|
60
|
+
- `action`: "get_branch"
|
|
61
|
+
- `project_id`: 项目 ID 或 URL 编码路径(如 "zhangkr/ai-test")
|
|
62
|
+
- `branch`: 分支名称
|
|
59
63
|
- 如果返回 404 或分支不存在:**立即停止执行**,输出:
|
|
60
64
|
```
|
|
61
65
|
❌ 目标分支不存在:{目标分支}
|
|
@@ -65,11 +69,12 @@ version: 1.0.0
|
|
|
65
69
|
### 1. MR 状态检查逻辑
|
|
66
70
|
|
|
67
71
|
创建 MR 后执行:
|
|
72
|
+
- **查询 MR 状态**:使用【查询 MR 工具】
|
|
68
73
|
- 检查 `merge_status` 字段
|
|
69
74
|
- 如果为 `cannot_be_merged`:
|
|
70
75
|
1. **检查是否为无文件变更的情况**:比较 `diff_refs.base_sha` 和 `diff_refs.head_sha`
|
|
71
76
|
- 如果两者相同(无实际文件变更):
|
|
72
|
-
-
|
|
77
|
+
- 执行【关闭 MR 逻辑】
|
|
73
78
|
- 输出提示信息:
|
|
74
79
|
```
|
|
75
80
|
ℹ️ MR 无文件变更(已合并过),自动关闭
|
|
@@ -93,8 +98,7 @@ version: 1.0.0
|
|
|
93
98
|
|
|
94
99
|
### 3. MR 合并逻辑
|
|
95
100
|
|
|
96
|
-
|
|
97
|
-
- `merge_when_pipeline_succeeds: true`
|
|
101
|
+
执行【合并 MR 工具】
|
|
98
102
|
- 如果失败(如 405 错误):**立即停止执行**,输出:
|
|
99
103
|
```
|
|
100
104
|
❌ MR 合并失败:{error_message}
|
|
@@ -102,40 +106,38 @@ version: 1.0.0
|
|
|
102
106
|
请检查 MR 状态后手动处理
|
|
103
107
|
```
|
|
104
108
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
### 前置检查: GitLab MCP 可用性验证
|
|
109
|
+
### 4. 关闭 MR 逻辑
|
|
108
110
|
|
|
109
|
-
|
|
111
|
+
执行 `mcp_gitlab_manage_merge_request` (action=update, project_id={project_id}, merge_request_iid={iid}, source_branch={source_branch}, target_branch={target_branch}, state_event=close)
|
|
112
|
+
- **必需参数**:
|
|
113
|
+
- `action`: "update"
|
|
114
|
+
- `project_id`: 项目 ID 或 URL 编码路径
|
|
115
|
+
- `merge_request_iid`: MR 的内部 ID
|
|
116
|
+
- `source_branch`: 源分支名称
|
|
117
|
+
- `target_branch`: 目标分支名称
|
|
118
|
+
- `state_event`: "close"
|
|
110
119
|
|
|
111
|
-
|
|
112
|
-
2. **判断结果**:
|
|
113
|
-
- 如果返回 `tool not found` 或 `not found MCPHost` 或 `50001` 错误码:**立即停止,禁止执行后续任何步骤**,输出:
|
|
114
|
-
```
|
|
115
|
-
❌ GitLab MCP 不可用,无法执行自动化 CI 流程
|
|
116
|
-
请在 MCP 配置中启用 GitLab MCP 后重试:
|
|
117
|
-
编辑器设置 → MCP → 启用 GitLab
|
|
118
|
-
```
|
|
119
|
-
- 如果返回其他任何结果(含 404、401、数据等):视为 MCP 可用,继续执行
|
|
120
|
+
## 执行流程
|
|
120
121
|
|
|
121
122
|
### 步骤 1: 获取分支信息
|
|
122
123
|
|
|
123
124
|
> **⚠️ 配置读取优先级**:按以下顺序读取配置,找到即停止:
|
|
124
125
|
> 1. 项目根目录的 `config.yaml`
|
|
125
|
-
> 2.
|
|
126
|
+
> 2. 默认模版配置(Skill 目录下的 `template.yaml`)
|
|
126
127
|
>
|
|
127
128
|
> **必须每次重新读取**:禁止使用上下文缓存,每次执行 `/ci` 时都要重新读取配置。
|
|
128
129
|
|
|
129
130
|
**配置读取逻辑**:
|
|
130
|
-
1. 首先尝试读取项目根目录的 `config.yaml`
|
|
131
|
-
2. 若 `config.yaml`
|
|
131
|
+
1. 首先尝试读取项目根目录的 `config.yaml`(使用 `read_file` 工具)
|
|
132
|
+
2. 若 `config.yaml` 不存在或读取失败,则读取 Skill 目录下的默认配置 `template.yaml`
|
|
133
|
+
- 使用 `read_file` 工具读取 Skill 目录下的 `template.yaml` 文件
|
|
132
134
|
3. 合并配置:以读取到的配置为基础,缺失的值使用 `template.yaml` 中的默认值补充
|
|
133
135
|
|
|
134
136
|
1. **解析 SOURCE_BRANCH(源分支)和项目路径**:
|
|
135
137
|
- 若用户传入了 `branch:分支名`,直接使用该分支作为 SOURCE_BRANCH
|
|
136
138
|
- 若 `config.yaml` 中配置了 `projects.url`,从 URL 中解析项目路径
|
|
137
139
|
- **若以上两者都未满足**,合并执行以下命令(一次终端调用获取全部信息):
|
|
138
|
-
执行 `git branch --show-current && git remote get-url origin
|
|
140
|
+
执行 `git branch --show-current && git remote get-url origin`,解析项目路径
|
|
139
141
|
- 第一行输出作为 SOURCE_BRANCH
|
|
140
142
|
- 第二行输出解析为项目路径
|
|
141
143
|
|
|
@@ -153,12 +155,13 @@ version: 1.0.0
|
|
|
153
155
|
|
|
154
156
|
1. **执行【分支存在性检查逻辑】**(目标分支:{PERSONAL_BRANCH})
|
|
155
157
|
|
|
156
|
-
2. **创建 MR
|
|
158
|
+
2. **创建 MR**:执行【创建 MR 工具】
|
|
157
159
|
- source_branch: {SOURCE_BRANCH}
|
|
158
160
|
- target_branch: {PERSONAL_BRANCH}
|
|
159
|
-
-
|
|
161
|
+
- 记录返回的 MR IID(`iid` 字段),用于后续查询
|
|
160
162
|
|
|
161
|
-
3. **执行【MR
|
|
163
|
+
3. **执行【MR 状态检查】**
|
|
164
|
+
- 使用步骤 2 中获取的 MR IID 查询状态
|
|
162
165
|
|
|
163
166
|
4. **执行【Review 检查逻辑】**(如果启用 Review 模式)
|
|
164
167
|
|
|
@@ -172,12 +175,13 @@ version: 1.0.0
|
|
|
172
175
|
|
|
173
176
|
1. **执行【分支存在性检查逻辑】**(目标分支:{TARGET_BRANCH})
|
|
174
177
|
|
|
175
|
-
2. **创建 MR
|
|
176
|
-
- source_branch:
|
|
178
|
+
2. **创建 MR**:执行【创建 MR 工具】
|
|
179
|
+
- source_branch: 根据规则确定({PERSONAL_BRANCH} 或 {SOURCE_BRANCH})
|
|
177
180
|
- target_branch: {TARGET_BRANCH}
|
|
178
|
-
-
|
|
181
|
+
- 记录返回的 MR IID(`iid` 字段),用于后续查询
|
|
179
182
|
|
|
180
183
|
3. **执行【MR 状态检查逻辑】**
|
|
184
|
+
- 使用步骤 2 中获取的 MR IID 查询状态
|
|
181
185
|
|
|
182
186
|
4. **执行【Review 检查逻辑】**(如果启用 Review 模式)
|
|
183
187
|
|
|
@@ -227,8 +231,13 @@ version: 1.0.0
|
|
|
227
231
|
2. **按照 CD skill 的规范执行部署流程**:
|
|
228
232
|
- 执行 Jenkins MCP 可用性验证
|
|
229
233
|
- 读取 `config.yaml` 中的 Jenkins 配置
|
|
230
|
-
- 使用 `mcp_jenkins_build_item`
|
|
234
|
+
- 使用 `mcp_{jenkins_name}_build_item` 触发构建(如 `mcp_jenkins_build_item` 或 `mcp_jenkins_prod_build_item`)
|
|
231
235
|
- **⚠️ 关键:`build_type` 必须为 `buildWithParameters`**
|
|
236
|
+
- **必需参数**:
|
|
237
|
+
- `fullname`: Jenkins Job 全名
|
|
238
|
+
- `build_type`: "buildWithParameters"
|
|
239
|
+
- `params`: 构建参数对象(如 `{BUILD_NAME: "build:dev:api"}`)
|
|
240
|
+
- **响应说明**:返回队列 ID(如 107941)
|
|
232
241
|
|
|
233
242
|
3. **多环境部署**:按环境顺序逐个触发,记录每个环境的队列 ID
|
|
234
243
|
|
|
@@ -332,3 +341,118 @@ version: 1.0.0
|
|
|
332
341
|
# 方式2:一体化执行
|
|
333
342
|
/ci dev 发布 # 合并 + 部署一步完成
|
|
334
343
|
```
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
## 附录:工具参数参考
|
|
348
|
+
|
|
349
|
+
### 公共工具定义
|
|
350
|
+
|
|
351
|
+
以下工具在多个步骤中被复用,统一在此定义:
|
|
352
|
+
|
|
353
|
+
#### 【查询 MR 工具】
|
|
354
|
+
`mcp_gitlab_browse_merge_requests` - 查询 MR 状态
|
|
355
|
+
- **必需参数**:
|
|
356
|
+
- `action`: "get"
|
|
357
|
+
- `project_id`: 项目 ID 或 URL 编码路径
|
|
358
|
+
- `merge_request_iid`: MR 内部 ID
|
|
359
|
+
- `per_page`: 每页数量(如 20)
|
|
360
|
+
- **关键响应字段**:
|
|
361
|
+
- `merge_status`: 合并状态("can_be_merged")
|
|
362
|
+
- `state`: MR 状态("opened"/"merged"/"closed")
|
|
363
|
+
- `diff_refs.base_sha`: 基础提交 SHA
|
|
364
|
+
- `diff_refs.head_sha`: 头部提交 SHA
|
|
365
|
+
- `web_url`: MR 页面链接
|
|
366
|
+
|
|
367
|
+
#### 【创建 MR 工具】
|
|
368
|
+
`mcp_gitlab_manage_merge_request` - 创建新的合并请求
|
|
369
|
+
- **必需参数**:
|
|
370
|
+
- `action`: "create"
|
|
371
|
+
- `project_id`: 项目 ID 或 URL 编码路径
|
|
372
|
+
- `source_branch`: 源分支名称
|
|
373
|
+
- `target_branch`: 目标分支名称
|
|
374
|
+
- `title`: MR 标题(支持模板变量)
|
|
375
|
+
- `description`: MR 描述(可选)
|
|
376
|
+
- **关键响应字段**:
|
|
377
|
+
- `iid`: MR 内部 ID(必需记录)
|
|
378
|
+
- `web_url`: MR 页面链接
|
|
379
|
+
- `merge_status`: 合并状态
|
|
380
|
+
|
|
381
|
+
#### 【合并 MR 工具】
|
|
382
|
+
`mcp_gitlab_manage_merge_request` - 合并 MR
|
|
383
|
+
- **必需参数**:
|
|
384
|
+
- `action`: "merge"
|
|
385
|
+
- `project_id`: 项目 ID 或 URL 编码路径
|
|
386
|
+
- `merge_request_iid`: MR 内部 ID
|
|
387
|
+
- `source_branch`: 源分支名称
|
|
388
|
+
- `target_branch`: 目标分支名称
|
|
389
|
+
- `merge_when_pipeline_succeeds`: true
|
|
390
|
+
|
|
391
|
+
---
|
|
392
|
+
|
|
393
|
+
### GitLab 工具详情
|
|
394
|
+
|
|
395
|
+
#### 1. mcp_gitlab_browse_refs
|
|
396
|
+
查询分支信息。
|
|
397
|
+
|
|
398
|
+
**参数**:
|
|
399
|
+
| 参数名 | 类型 | 必需 | 说明 |
|
|
400
|
+
|--------|------|------|------|
|
|
401
|
+
| `action` | string | ✅ | 固定值 "get_branch" |
|
|
402
|
+
| `project_id` | string | ✅ | 项目 ID 或 URL 编码路径(如 "zhangkr/ai-test") |
|
|
403
|
+
| `branch` | string | ✅ | 分支名称 |
|
|
404
|
+
|
|
405
|
+
**响应字段**:
|
|
406
|
+
| 字段名 | 说明 |
|
|
407
|
+
|--------|------|
|
|
408
|
+
| `name` | 分支名称 |
|
|
409
|
+
| `commit.id` | 最新提交 SHA |
|
|
410
|
+
| `commit.title` | 最新提交标题 |
|
|
411
|
+
| `merged` | 是否已合并 |
|
|
412
|
+
| `protected` | 是否受保护 |
|
|
413
|
+
|
|
414
|
+
#### 2. mcp_gitlab_browse_merge_requests
|
|
415
|
+
查询 MR 信息。详见【查询 MR 工具】定义。
|
|
416
|
+
|
|
417
|
+
**额外说明**:
|
|
418
|
+
- `action=list` 时不需要 `merge_request_iid`
|
|
419
|
+
- 支持分页参数:`page`, `per_page`
|
|
420
|
+
|
|
421
|
+
#### 3. mcp_gitlab_manage_merge_request
|
|
422
|
+
管理 MR(创建、更新、合并)。详见【创建 MR 工具】、【合并 MR 工具】、【关闭 MR 逻辑】定义。
|
|
423
|
+
|
|
424
|
+
**update 专属参数**(关闭/重新打开 MR):
|
|
425
|
+
| 参数名 | 类型 | 必需 | 说明 |
|
|
426
|
+
|--------|------|------|------|
|
|
427
|
+
| `merge_request_iid` | number | ✅ | MR 内部 ID |
|
|
428
|
+
| `source_branch` | string | ✅ | 源分支名称 |
|
|
429
|
+
| `target_branch` | string | ✅ | 目标分支名称 |
|
|
430
|
+
| `state_event` | string | ✅ | "close" 或 "reopen" |
|
|
431
|
+
|
|
432
|
+
### Jenkins 工具
|
|
433
|
+
|
|
434
|
+
#### mcp_{jenkins_name}_build_item
|
|
435
|
+
触发 Jenkins 构建。
|
|
436
|
+
|
|
437
|
+
**参数**:
|
|
438
|
+
| 参数名 | 类型 | 必需 | 说明 |
|
|
439
|
+
|--------|------|------|------|
|
|
440
|
+
| `fullname` | string | ✅ | Jenkins Job 全名 |
|
|
441
|
+
| `build_type` | string | ✅ | 固定值 "buildWithParameters" |
|
|
442
|
+
| `params` | object | ✅ | 构建参数对象(如 `{BUILD_NAME: "build:dev:api"}`) |
|
|
443
|
+
|
|
444
|
+
**响应**:
|
|
445
|
+
- 成功:返回队列 ID(number,如 107941)
|
|
446
|
+
- 失败:返回错误信息
|
|
447
|
+
|
|
448
|
+
#### mcp_{jenkins_name}_get_all_items
|
|
449
|
+
获取所有 Jenkins Jobs(用于可用性验证)。
|
|
450
|
+
|
|
451
|
+
**参数**:
|
|
452
|
+
| 参数名 | 类型 | 必需 | 说明 |
|
|
453
|
+
|--------|------|------|------|
|
|
454
|
+
| `random_string` | string | ✅ | 任意字符串(如 "check") |
|
|
455
|
+
|
|
456
|
+
**响应**:
|
|
457
|
+
- 成功:返回 Job 列表数组
|
|
458
|
+
- 失败:返回错误信息
|
|
@@ -24,6 +24,19 @@ templates:
|
|
|
24
24
|
- 目标分支: {target_branch}
|
|
25
25
|
- 触发环境: {env}
|
|
26
26
|
- 触发时间: {timestamp}
|
|
27
|
+
jenkins:
|
|
28
|
+
is_current_branch: false
|
|
29
|
+
'广告主开发':
|
|
30
|
+
fullname: "dev-platforms-advertiser-140.227"
|
|
31
|
+
url: "http://10.169.140.235:30866/view/双统前台/job/dev-platforms-advertiser-140.227/"
|
|
32
|
+
'广告主测试':
|
|
33
|
+
fullname: "test-platforms-advertiser-140.227"
|
|
34
|
+
url: "http://10.169.140.235:30866/view/双统前台/job/test-platforms-advertiser-140.227/"
|
|
35
|
+
jenkins-prod:
|
|
36
|
+
is_current_branch: false
|
|
37
|
+
'广告主准生产':
|
|
38
|
+
fullname: "uat-gtqt-platforms-advertiser-150.27"
|
|
39
|
+
url: "http://10.168.171.71:28080/view/uat-双统前台/job/uat-platforms-advertiser-150.27/"
|
|
27
40
|
|
|
28
41
|
defaults:
|
|
29
42
|
env: "dev" # 默认环境
|