postar-pipe-mcp 1.0.0 → 1.0.2
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 +158 -68
- package/dist/skills/cd/SKILL.md +91 -238
- package/dist/skills/cd/steps.yaml +448 -0
- package/dist/skills/cd/template.yaml +67 -4
- package/dist/skills/ci/SKILL.md +170 -166
- package/dist/skills/ci/steps.yaml +504 -0
- package/dist/skills/ci/template.yaml +60 -3
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -21015,7 +21015,7 @@ function registerGitLab(manager2) {
|
|
|
21015
21015
|
manager2.registerConfig("gitlab", {
|
|
21016
21016
|
name: "gitlab",
|
|
21017
21017
|
command: "npx",
|
|
21018
|
-
args: ["-y", "
|
|
21018
|
+
args: ["-y", "gitlab-core-mcp"],
|
|
21019
21019
|
env: {
|
|
21020
21020
|
GITLAB_API_URL: gitlabUrl,
|
|
21021
21021
|
GITLAB_TOKEN: gitlabToken
|
|
@@ -21057,11 +21057,21 @@ function initMCPClients() {
|
|
|
21057
21057
|
}
|
|
21058
21058
|
const GITLAB_PREFIX = "mcp_gitlab_";
|
|
21059
21059
|
function getRegisteredServers() {
|
|
21060
|
-
const servers = [
|
|
21060
|
+
const servers = [];
|
|
21061
|
+
if (process.env.GITLAB_TOKEN) {
|
|
21062
|
+
servers.push("gitlab");
|
|
21063
|
+
console.error("[MCP-PIPE][DEBUG] 检测到 GITLAB_TOKEN,添加 gitlab 服务器");
|
|
21064
|
+
} else {
|
|
21065
|
+
console.error("[MCP-PIPE][DEBUG] 未检测到 GITLAB_TOKEN");
|
|
21066
|
+
}
|
|
21061
21067
|
if (process.env.JENKINS_URL && process.env.JENKINS_USER && process.env.JENKINS_TOKEN) {
|
|
21062
21068
|
servers.push("jenkins");
|
|
21069
|
+
console.error("[MCP-PIPE][DEBUG] 检测到默认 Jenkins 配置");
|
|
21070
|
+
} else {
|
|
21071
|
+
console.error("[MCP-PIPE][DEBUG] 未检测到默认 Jenkins 配置 (JENKINS_URL/JENKINS_USER/JENKINS_TOKEN)");
|
|
21063
21072
|
}
|
|
21064
21073
|
const urlPattern = /^JENKINS_([A-Z0-9_]+)_URL$/;
|
|
21074
|
+
console.error("[MCP-PIPE][DEBUG] 开始检测命名 Jenkins 实例...");
|
|
21065
21075
|
for (const key of Object.keys(process.env)) {
|
|
21066
21076
|
const match = key.match(urlPattern);
|
|
21067
21077
|
if (!match) continue;
|
|
@@ -21070,11 +21080,14 @@ function getRegisteredServers() {
|
|
|
21070
21080
|
const tokenKey = `JENKINS_${suffix}_TOKEN`;
|
|
21071
21081
|
const user = process.env[userKey];
|
|
21072
21082
|
const token = process.env[tokenKey];
|
|
21083
|
+
console.error(`[MCP-PIPE][DEBUG] 检查 ${key} -> suffix=${suffix}, user=${!!user}, token=${!!token}`);
|
|
21073
21084
|
if (user && token) {
|
|
21074
21085
|
const instanceName = `jenkins-${suffix.toLowerCase().replace(/_/g, "-")}`;
|
|
21075
21086
|
servers.push(instanceName);
|
|
21087
|
+
console.error(`[MCP-PIPE][DEBUG] ✅ 添加命名 Jenkins 实例: ${instanceName}`);
|
|
21076
21088
|
}
|
|
21077
21089
|
}
|
|
21090
|
+
console.error(`[MCP-PIPE][DEBUG] 最终注册的服务器列表: ${servers.join(", ") || "(空)"}`);
|
|
21078
21091
|
return servers;
|
|
21079
21092
|
}
|
|
21080
21093
|
function getToolPrefix(serverName) {
|
|
@@ -21097,49 +21110,59 @@ async function getProxiedTools() {
|
|
|
21097
21110
|
const jenkinsServers = servers.filter((s) => s.startsWith("jenkins") && s !== "jenkins");
|
|
21098
21111
|
const mainJenkins = servers.includes("jenkins") ? "jenkins" : null;
|
|
21099
21112
|
const otherServers = servers.filter((s) => !s.startsWith("jenkins"));
|
|
21100
|
-
|
|
21101
|
-
|
|
21102
|
-
|
|
21103
|
-
|
|
21104
|
-
|
|
21113
|
+
const allJenkinsServers = mainJenkins ? [mainJenkins, ...jenkinsServers] : jenkinsServers;
|
|
21114
|
+
let isFirstJenkins = true;
|
|
21115
|
+
for (const serverName of allJenkinsServers) {
|
|
21116
|
+
const prefix = getToolPrefix(serverName);
|
|
21117
|
+
if (isFirstJenkins) {
|
|
21118
|
+
try {
|
|
21119
|
+
const tools = await manager2.listTools(serverName);
|
|
21120
|
+
for (const tool of tools.tools || []) {
|
|
21121
|
+
const proxiedToolName = `${prefix}${tool.name}`;
|
|
21122
|
+
const proxiedTool = {
|
|
21123
|
+
...tool,
|
|
21124
|
+
name: proxiedToolName,
|
|
21125
|
+
description: `[${serverName}] ${tool.description || ""}`
|
|
21126
|
+
};
|
|
21127
|
+
allTools.push(proxiedTool);
|
|
21128
|
+
toolSchemaCache.set(proxiedToolName, tool.inputSchema);
|
|
21129
|
+
}
|
|
21130
|
+
console.error(`[MCP-PIPE] 已从 ${serverName} 获取 ${tools.tools?.length || 0} 个工具`);
|
|
21131
|
+
isFirstJenkins = false;
|
|
21132
|
+
} catch (error2) {
|
|
21133
|
+
console.error(`[MCP-PIPE] 无法从 ${serverName} 获取工具列表:`, error2);
|
|
21134
|
+
continue;
|
|
21135
|
+
}
|
|
21136
|
+
} else {
|
|
21137
|
+
const sourcePrefix = mainJenkins ? getToolPrefix(mainJenkins) : getToolPrefix(allJenkinsServers[0]);
|
|
21138
|
+
const jenkinsTools = allTools.filter((t) => t.name.startsWith(sourcePrefix));
|
|
21139
|
+
for (const tool of jenkinsTools) {
|
|
21140
|
+
const originalToolName = tool.name.slice(sourcePrefix.length);
|
|
21141
|
+
const proxiedToolName = `${prefix}${originalToolName}`;
|
|
21105
21142
|
const proxiedTool = {
|
|
21106
21143
|
...tool,
|
|
21107
|
-
name:
|
|
21108
|
-
description: `[${
|
|
21144
|
+
name: proxiedToolName,
|
|
21145
|
+
description: `[${serverName}] ${tool.description || ""}`
|
|
21109
21146
|
};
|
|
21110
21147
|
allTools.push(proxiedTool);
|
|
21148
|
+
toolSchemaCache.set(proxiedToolName, tool.inputSchema);
|
|
21111
21149
|
}
|
|
21112
|
-
console.error(`[MCP-PIPE]
|
|
21113
|
-
} catch (error2) {
|
|
21114
|
-
console.error(`[MCP-PIPE] 无法获取 ${mainJenkins} 的工具列表:`, error2);
|
|
21115
|
-
}
|
|
21116
|
-
}
|
|
21117
|
-
for (const serverName of jenkinsServers) {
|
|
21118
|
-
const prefix = getToolPrefix(serverName);
|
|
21119
|
-
const sourcePrefix = mainJenkins ? getToolPrefix(mainJenkins) : prefix;
|
|
21120
|
-
const jenkinsTools = allTools.filter((t) => t.name.startsWith(sourcePrefix));
|
|
21121
|
-
for (const tool of jenkinsTools) {
|
|
21122
|
-
const originalToolName = tool.name.slice(sourcePrefix.length);
|
|
21123
|
-
const proxiedTool = {
|
|
21124
|
-
...tool,
|
|
21125
|
-
name: `${prefix}${originalToolName}`,
|
|
21126
|
-
description: `[${serverName}] ${tool.description || ""}`
|
|
21127
|
-
};
|
|
21128
|
-
allTools.push(proxiedTool);
|
|
21150
|
+
console.error(`[MCP-PIPE] 已为 ${serverName} 生成 ${jenkinsTools.length} 个工具别名`);
|
|
21129
21151
|
}
|
|
21130
|
-
console.error(`[MCP-PIPE] 已为 ${serverName} 生成 ${jenkinsTools.length} 个工具别名`);
|
|
21131
21152
|
}
|
|
21132
21153
|
for (const serverName of otherServers) {
|
|
21133
21154
|
const prefix = getToolPrefix(serverName);
|
|
21134
21155
|
try {
|
|
21135
21156
|
const tools = await manager2.listTools(serverName);
|
|
21136
21157
|
for (const tool of tools.tools || []) {
|
|
21158
|
+
const proxiedToolName = `${prefix}${tool.name}`;
|
|
21137
21159
|
const proxiedTool = {
|
|
21138
21160
|
...tool,
|
|
21139
|
-
name:
|
|
21161
|
+
name: proxiedToolName,
|
|
21140
21162
|
description: `[${serverName}] ${tool.description || ""}`
|
|
21141
21163
|
};
|
|
21142
21164
|
allTools.push(proxiedTool);
|
|
21165
|
+
toolSchemaCache.set(proxiedToolName, tool.inputSchema);
|
|
21143
21166
|
}
|
|
21144
21167
|
console.error(`[MCP-PIPE] 已代理 ${serverName} 的 ${tools.tools?.length || 0} 个工具`);
|
|
21145
21168
|
} catch (error2) {
|
|
@@ -21148,9 +21171,40 @@ async function getProxiedTools() {
|
|
|
21148
21171
|
}
|
|
21149
21172
|
return allTools;
|
|
21150
21173
|
}
|
|
21174
|
+
function fixArgumentTypes(args, toolSchema) {
|
|
21175
|
+
if (!args || typeof args !== "object") return args;
|
|
21176
|
+
const fixedArgs = { ...args };
|
|
21177
|
+
if (!toolSchema?.properties) {
|
|
21178
|
+
console.warn(`[MCP-PIPE] 警告: 工具缺少 schema,跳过类型修复`);
|
|
21179
|
+
return fixedArgs;
|
|
21180
|
+
}
|
|
21181
|
+
for (const [key, schema] of Object.entries(toolSchema.properties)) {
|
|
21182
|
+
if (key in fixedArgs && typeof fixedArgs[key] === "string") {
|
|
21183
|
+
const propSchema = schema;
|
|
21184
|
+
if (propSchema.type === "number" || propSchema.type === "integer") {
|
|
21185
|
+
const num = Number(fixedArgs[key]);
|
|
21186
|
+
if (!isNaN(num)) {
|
|
21187
|
+
fixedArgs[key] = num;
|
|
21188
|
+
console.debug(`[MCP-PIPE] 类型转换: ${key} "${fixedArgs[key]}" → ${num} (number)`);
|
|
21189
|
+
}
|
|
21190
|
+
}
|
|
21191
|
+
if (propSchema.type === "boolean") {
|
|
21192
|
+
const str = fixedArgs[key].toLowerCase();
|
|
21193
|
+
if (str === "true" || str === "false") {
|
|
21194
|
+
fixedArgs[key] = str === "true";
|
|
21195
|
+
console.debug(`[MCP-PIPE] 类型转换: ${key} "${str}" → ${fixedArgs[key]} (boolean)`);
|
|
21196
|
+
}
|
|
21197
|
+
}
|
|
21198
|
+
}
|
|
21199
|
+
}
|
|
21200
|
+
return fixedArgs;
|
|
21201
|
+
}
|
|
21202
|
+
const toolSchemaCache = /* @__PURE__ */ new Map();
|
|
21151
21203
|
async function handleProxiedTool(name, args) {
|
|
21152
21204
|
const manager2 = getMCPClientManager();
|
|
21153
21205
|
const servers = getRegisteredServers();
|
|
21206
|
+
const toolSchema = toolSchemaCache.get(name);
|
|
21207
|
+
const fixedArgs = fixArgumentTypes(args, toolSchema);
|
|
21154
21208
|
if (name.startsWith("mcp_jenkins_")) {
|
|
21155
21209
|
let targetMCP = null;
|
|
21156
21210
|
const otherJenkinsServers = servers.filter((s) => s.startsWith("jenkins-"));
|
|
@@ -21168,7 +21222,7 @@ async function handleProxiedTool(name, args) {
|
|
|
21168
21222
|
const prefix = getToolPrefix(targetMCP);
|
|
21169
21223
|
const originalToolName = name.slice(prefix.length);
|
|
21170
21224
|
try {
|
|
21171
|
-
const result = await manager2.callTool(targetMCP, originalToolName,
|
|
21225
|
+
const result = await manager2.callTool(targetMCP, originalToolName, fixedArgs);
|
|
21172
21226
|
return result;
|
|
21173
21227
|
} catch (error2) {
|
|
21174
21228
|
return {
|
|
@@ -21183,7 +21237,7 @@ async function handleProxiedTool(name, args) {
|
|
|
21183
21237
|
if (name.startsWith(prefix)) {
|
|
21184
21238
|
const originalToolName = name.slice(prefix.length);
|
|
21185
21239
|
try {
|
|
21186
|
-
const result = await manager2.callTool(serverName, originalToolName,
|
|
21240
|
+
const result = await manager2.callTool(serverName, originalToolName, fixedArgs);
|
|
21187
21241
|
return result;
|
|
21188
21242
|
} catch (error2) {
|
|
21189
21243
|
return {
|
|
@@ -21199,13 +21253,26 @@ const skillCache = /* @__PURE__ */ new Map();
|
|
|
21199
21253
|
const skillMetadataCache = /* @__PURE__ */ new Map();
|
|
21200
21254
|
let remoteSkillsList = null;
|
|
21201
21255
|
function parseSkillSource(url, index) {
|
|
21202
|
-
const
|
|
21203
|
-
|
|
21256
|
+
const questionMarkIndex = url.indexOf("?");
|
|
21257
|
+
let cleanUrl = url;
|
|
21258
|
+
let skillsList;
|
|
21259
|
+
if (questionMarkIndex !== -1) {
|
|
21260
|
+
cleanUrl = url.substring(0, questionMarkIndex);
|
|
21261
|
+
const queryString = url.substring(questionMarkIndex + 1);
|
|
21262
|
+
const params = new URLSearchParams(queryString);
|
|
21263
|
+
const skillsParam = params.get("skills");
|
|
21264
|
+
if (skillsParam) {
|
|
21265
|
+
skillsList = skillsParam.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
21266
|
+
}
|
|
21267
|
+
}
|
|
21268
|
+
const rawUrl = cleanUrl.replace("/tree/", "/raw/").replace(/\/$/, "");
|
|
21269
|
+
const match = cleanUrl.match(/\/([^\/]+)\/[^\/]+\/tree\//);
|
|
21204
21270
|
const name = match ? match[1] : `source-${index}`;
|
|
21205
21271
|
return {
|
|
21206
21272
|
name,
|
|
21207
21273
|
baseUrl: rawUrl,
|
|
21208
|
-
originalUrl:
|
|
21274
|
+
originalUrl: cleanUrl,
|
|
21275
|
+
skillsList
|
|
21209
21276
|
};
|
|
21210
21277
|
}
|
|
21211
21278
|
function parseCommandLineArgs() {
|
|
@@ -21345,35 +21412,52 @@ async function fetchRemoteSkillsFromDirectory(config2) {
|
|
|
21345
21412
|
}
|
|
21346
21413
|
}
|
|
21347
21414
|
const skillSourceMap = /* @__PURE__ */ new Map();
|
|
21415
|
+
const CACHE_TTL_MINUTES = process.env.SKILLS_CACHE_TTL ? parseInt(process.env.SKILLS_CACHE_TTL) : 30;
|
|
21416
|
+
const CACHE_TTL = CACHE_TTL_MINUTES * 60 * 1e3;
|
|
21417
|
+
function isCacheExpired(timestamp) {
|
|
21418
|
+
return Date.now() - timestamp > CACHE_TTL;
|
|
21419
|
+
}
|
|
21348
21420
|
async function fetchRemoteSkillsList(config2) {
|
|
21349
|
-
if (remoteSkillsList) {
|
|
21350
|
-
return remoteSkillsList;
|
|
21421
|
+
if (remoteSkillsList && !isCacheExpired(remoteSkillsList.timestamp)) {
|
|
21422
|
+
return remoteSkillsList.data;
|
|
21351
21423
|
}
|
|
21424
|
+
console.error(`[SKILL-LOADER] Skills 列表缓存过期或不存在,重新加载...`);
|
|
21352
21425
|
const skillsListEnv = process.env.SKILLS_LIST;
|
|
21353
21426
|
if (skillsListEnv) {
|
|
21354
21427
|
const skills = skillsListEnv.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
21355
|
-
remoteSkillsList = skills;
|
|
21428
|
+
remoteSkillsList = { data: skills, timestamp: Date.now() };
|
|
21356
21429
|
console.error(`[SKILL-LOADER] 从环境变量加载 skills: ${skills.join(", ")}`);
|
|
21357
21430
|
return skills;
|
|
21358
21431
|
}
|
|
21359
|
-
console.error(`[SKILL-LOADER] 未配置 SKILLS_LIST
|
|
21432
|
+
console.error(`[SKILL-LOADER] 未配置 SKILLS_LIST,检查各源独立配置...`);
|
|
21360
21433
|
const allSkills = [];
|
|
21361
21434
|
skillSourceMap.clear();
|
|
21362
21435
|
if (config2.sources && config2.sources.length > 0) {
|
|
21363
21436
|
for (const source of config2.sources) {
|
|
21364
21437
|
console.error(`[SKILL-LOADER] 扫描源: ${source.name} (${source.originalUrl})`);
|
|
21365
|
-
|
|
21366
|
-
source
|
|
21367
|
-
|
|
21368
|
-
|
|
21369
|
-
|
|
21370
|
-
|
|
21371
|
-
|
|
21372
|
-
|
|
21373
|
-
|
|
21374
|
-
|
|
21438
|
+
if (source.skillsList && source.skillsList.length > 0) {
|
|
21439
|
+
console.error(`[SKILL-LOADER] 源 ${source.name} 使用独立 skills 列表: ${source.skillsList.join(", ")}`);
|
|
21440
|
+
for (const skillName of source.skillsList) {
|
|
21441
|
+
if (!allSkills.includes(skillName)) {
|
|
21442
|
+
allSkills.push(skillName);
|
|
21443
|
+
}
|
|
21444
|
+
skillSourceMap.set(skillName, source);
|
|
21445
|
+
}
|
|
21446
|
+
} else {
|
|
21447
|
+
console.error(`[SKILL-LOADER] 源 ${source.name} 未配置独立 skills,自动遍历目录...`);
|
|
21448
|
+
const sourceConfig = {
|
|
21449
|
+
source: "remote",
|
|
21450
|
+
baseUrl: source.baseUrl,
|
|
21451
|
+
originalUrl: source.originalUrl
|
|
21452
|
+
};
|
|
21453
|
+
const skills = await fetchRemoteSkillsFromDirectory(sourceConfig);
|
|
21454
|
+
console.error(`[SKILL-LOADER] 源 ${source.name} 发现 skills: ${skills.join(", ")}`);
|
|
21455
|
+
for (const skillName of skills) {
|
|
21456
|
+
if (!allSkills.includes(skillName)) {
|
|
21457
|
+
allSkills.push(skillName);
|
|
21458
|
+
}
|
|
21459
|
+
skillSourceMap.set(skillName, source);
|
|
21375
21460
|
}
|
|
21376
|
-
skillSourceMap.set(skillName, source);
|
|
21377
21461
|
}
|
|
21378
21462
|
}
|
|
21379
21463
|
} else if (config2.baseUrl && config2.originalUrl) {
|
|
@@ -21388,7 +21472,7 @@ async function fetchRemoteSkillsList(config2) {
|
|
|
21388
21472
|
}
|
|
21389
21473
|
}
|
|
21390
21474
|
console.error(`[SKILL-LOADER] 汇总所有 skills: ${allSkills.join(", ")}`);
|
|
21391
|
-
remoteSkillsList = allSkills;
|
|
21475
|
+
remoteSkillsList = { data: allSkills, timestamp: Date.now() };
|
|
21392
21476
|
return allSkills;
|
|
21393
21477
|
}
|
|
21394
21478
|
async function loadRemoteSkillContent(skillName, baseUrl) {
|
|
@@ -21474,11 +21558,14 @@ Skill 文件加载失败,请检查安装。`;
|
|
|
21474
21558
|
}
|
|
21475
21559
|
}
|
|
21476
21560
|
async function getSkillContent(skillName) {
|
|
21477
|
-
|
|
21478
|
-
|
|
21479
|
-
|
|
21561
|
+
const cached2 = skillCache.get(skillName);
|
|
21562
|
+
if (cached2 && !isCacheExpired(cached2.timestamp)) {
|
|
21563
|
+
return cached2.data;
|
|
21480
21564
|
}
|
|
21481
|
-
|
|
21565
|
+
console.error(`[SKILL-LOADER] Skill 缓存过期或不存在,重新加载: ${skillName}`);
|
|
21566
|
+
const content = await loadSkillContent(skillName);
|
|
21567
|
+
skillCache.set(skillName, { data: content, timestamp: Date.now() });
|
|
21568
|
+
return content;
|
|
21482
21569
|
}
|
|
21483
21570
|
async function parseFrontmatter(content) {
|
|
21484
21571
|
const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
|
|
@@ -21510,21 +21597,24 @@ async function parseFrontmatter(content) {
|
|
|
21510
21597
|
}
|
|
21511
21598
|
}
|
|
21512
21599
|
async function getSkillMetadata(skillName) {
|
|
21513
|
-
|
|
21514
|
-
|
|
21515
|
-
|
|
21516
|
-
|
|
21517
|
-
|
|
21518
|
-
|
|
21519
|
-
|
|
21520
|
-
|
|
21521
|
-
|
|
21522
|
-
|
|
21523
|
-
|
|
21524
|
-
|
|
21525
|
-
|
|
21526
|
-
|
|
21527
|
-
|
|
21600
|
+
const cached2 = skillMetadataCache.get(skillName);
|
|
21601
|
+
if (cached2 && !isCacheExpired(cached2.timestamp)) {
|
|
21602
|
+
return cached2.data;
|
|
21603
|
+
}
|
|
21604
|
+
console.error(`[SKILL-LOADER] Skill 元数据缓存过期或不存在,重新加载: ${skillName}`);
|
|
21605
|
+
const content = await getSkillContent(skillName);
|
|
21606
|
+
const { metadata: metadata2 } = await parseFrontmatter(content);
|
|
21607
|
+
const metadataObj = {
|
|
21608
|
+
name: metadata2.name || skillName,
|
|
21609
|
+
description: metadata2.description || `${skillName} skill`,
|
|
21610
|
+
constraint: metadata2.constraint,
|
|
21611
|
+
version: metadata2.version,
|
|
21612
|
+
tools: metadata2.tools || [],
|
|
21613
|
+
...metadata2
|
|
21614
|
+
// 包含其他自定义字段
|
|
21615
|
+
};
|
|
21616
|
+
skillMetadataCache.set(skillName, { data: metadataObj, timestamp: Date.now() });
|
|
21617
|
+
return metadataObj;
|
|
21528
21618
|
}
|
|
21529
21619
|
async function getAllSkillsMetadata() {
|
|
21530
21620
|
const skills = await getAvailableSkills();
|