@cnbcool/mcp-server 0.6.4 → 0.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README_zh.md +1 -0
- package/dist/api/workspace.js +6 -0
- package/dist/constants/toolDescriptions.js +1 -0
- package/dist/constants/toolNames.js +1 -0
- package/dist/helpers/convertLink.js +101 -0
- package/dist/tools/issueTools.js +19 -3
- package/dist/tools/workspaceTools.js +20 -0
- package/package.json +1 -1
package/README_zh.md
CHANGED
|
@@ -42,6 +42,7 @@ CNB(https://cnb.cool) 支持 MCP 协议的 MCP Server
|
|
|
42
42
|
| cnb_startBuild | 开始一个构建 |
|
|
43
43
|
| cnb_stopBuild | 停止一个构建 |
|
|
44
44
|
| cnb_list_workspaces | 获取当前用户在CNB平台的云原生开发环境列表 |
|
|
45
|
+
| cnb_start_workspace | 在CNB平台启动指定仓库的云原生开发环境 |
|
|
45
46
|
| cnb_delete_workspace | 在CNB平台删除指定的云原生开发环境 |
|
|
46
47
|
| cnb_getKnowledgeBaseInfo | 获取知识库信息 |
|
|
47
48
|
| cnb_queryKnowledgeBase | 查询知识库 |
|
package/dist/api/workspace.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.listWorkspace = listWorkspace;
|
|
4
4
|
exports.deleteWorkspace = deleteWorkspace;
|
|
5
|
+
exports.startWorkspace = startWorkspace;
|
|
5
6
|
async function listWorkspace(client, params) {
|
|
6
7
|
const url = new URL('/workspace/list', client.baseUrl);
|
|
7
8
|
if (params) {
|
|
@@ -19,3 +20,8 @@ async function deleteWorkspace(client, params) {
|
|
|
19
20
|
header: { 'Content-Type': 'application/json' }
|
|
20
21
|
});
|
|
21
22
|
}
|
|
23
|
+
async function startWorkspace(client, repo, params) {
|
|
24
|
+
return client.request('POST', `/${repo}/-/workspace/start`, params, {
|
|
25
|
+
header: { 'Content-Type': 'application/json' }
|
|
26
|
+
});
|
|
27
|
+
}
|
|
@@ -46,6 +46,7 @@ exports.toolDescriptions = {
|
|
|
46
46
|
// 云原生开发工具
|
|
47
47
|
[toolNames_1.ToolNames.LIST_WORKSPACES]: '获取当前用户在CNB平台的云原生开发环境列表',
|
|
48
48
|
[toolNames_1.ToolNames.DELETE_WORKSPACE]: '在CNB平台删除指定的云原生开发环境',
|
|
49
|
+
[toolNames_1.ToolNames.START_WORKSPACE]: '在CNB平台启动指定仓库的云原生开发环境',
|
|
49
50
|
// 知识库工具
|
|
50
51
|
[toolNames_1.ToolNames.GET_KNOWLEDGE_BASE_INFO]: '获取知识库信息',
|
|
51
52
|
[toolNames_1.ToolNames.QUERY_KNOWLEDGE_BASE]: '查询知识库'
|
|
@@ -46,6 +46,7 @@ var ToolNames;
|
|
|
46
46
|
// 云原生开发工具
|
|
47
47
|
ToolNames["LIST_WORKSPACES"] = "cnb_list_workspaces";
|
|
48
48
|
ToolNames["DELETE_WORKSPACE"] = "cnb_delete_workspace";
|
|
49
|
+
ToolNames["START_WORKSPACE"] = "cnb_start_workspace";
|
|
49
50
|
// 知识库工具
|
|
50
51
|
ToolNames["GET_KNOWLEDGE_BASE_INFO"] = "cnb_getKnowledgeBaseInfo";
|
|
51
52
|
ToolNames["QUERY_KNOWLEDGE_BASE"] = "cnb_queryKnowledgeBase";
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = convertLink;
|
|
4
|
+
function trimTrailingSlash(val) {
|
|
5
|
+
return val.replace(/\/+$/, '');
|
|
6
|
+
}
|
|
7
|
+
function normaliseLink(link, repoSlug, branchOrSha) {
|
|
8
|
+
const endpoint = trimTrailingSlash(process.env.CNB_WEB_ENDPOINT || 'https://cnb.cool');
|
|
9
|
+
const baseURL = `${endpoint}/${repoSlug}`;
|
|
10
|
+
if (!link)
|
|
11
|
+
return link;
|
|
12
|
+
if (/^([a-z][a-z0-9+.-]*:)?\/\//i.test(link) || /^[a-z][a-z0-9+.-]*:/i.test(link) || link.startsWith('#')) {
|
|
13
|
+
return link;
|
|
14
|
+
}
|
|
15
|
+
if (link.startsWith('/-/')) {
|
|
16
|
+
return `${baseURL}${link}`;
|
|
17
|
+
}
|
|
18
|
+
if (!branchOrSha) {
|
|
19
|
+
return link;
|
|
20
|
+
}
|
|
21
|
+
if (link.startsWith('../')) {
|
|
22
|
+
return `${baseURL}/-/git/raw/${branchOrSha}/${link}`;
|
|
23
|
+
}
|
|
24
|
+
if (link.startsWith('./')) {
|
|
25
|
+
return `${baseURL}/-/git/raw/${branchOrSha}${link.substring(1)}`;
|
|
26
|
+
}
|
|
27
|
+
if (link.startsWith('/') && !link.startsWith('//')) {
|
|
28
|
+
return `${baseURL}/-/git/raw/${branchOrSha}${link}`;
|
|
29
|
+
}
|
|
30
|
+
return link;
|
|
31
|
+
}
|
|
32
|
+
function convertLink(text, repoSlug, branchOrSha) {
|
|
33
|
+
if (!text || !repoSlug)
|
|
34
|
+
return text;
|
|
35
|
+
let codeBlockPositions = [];
|
|
36
|
+
let index = 0;
|
|
37
|
+
while (index !== -1) {
|
|
38
|
+
const nextIndex = text.indexOf('```', index);
|
|
39
|
+
if (nextIndex === -1)
|
|
40
|
+
break;
|
|
41
|
+
codeBlockPositions.push(nextIndex);
|
|
42
|
+
index = nextIndex + 3;
|
|
43
|
+
}
|
|
44
|
+
if (codeBlockPositions.length % 2 !== 0) {
|
|
45
|
+
codeBlockPositions = codeBlockPositions.slice(0, codeBlockPositions.length - 1);
|
|
46
|
+
}
|
|
47
|
+
let inlineCodeBlockPositions = [];
|
|
48
|
+
index = 0;
|
|
49
|
+
while (index < text.length) {
|
|
50
|
+
const nextIndex = text.indexOf('`', index);
|
|
51
|
+
if (nextIndex === -1)
|
|
52
|
+
break;
|
|
53
|
+
if ((nextIndex === 0 || text.charAt(nextIndex - 1) !== '`') &&
|
|
54
|
+
(nextIndex === text.length - 1 || text.charAt(nextIndex + 1) !== '`')) {
|
|
55
|
+
inlineCodeBlockPositions.push(nextIndex);
|
|
56
|
+
}
|
|
57
|
+
index = nextIndex + 1;
|
|
58
|
+
}
|
|
59
|
+
if (inlineCodeBlockPositions.length % 2 !== 0) {
|
|
60
|
+
inlineCodeBlockPositions = inlineCodeBlockPositions.slice(0, inlineCodeBlockPositions.length - 1);
|
|
61
|
+
}
|
|
62
|
+
const excludedRanges = [...codeBlockPositions, ...inlineCodeBlockPositions].reduce((result, item, idx) => {
|
|
63
|
+
if (idx % 2 === 0) {
|
|
64
|
+
return result.concat([[item]]);
|
|
65
|
+
}
|
|
66
|
+
result[result.length - 1].push(item);
|
|
67
|
+
return result;
|
|
68
|
+
}, []);
|
|
69
|
+
const isInExcludedRange = (pos) => excludedRanges.some(([start, end]) => pos >= start && pos < end);
|
|
70
|
+
const replacements = [];
|
|
71
|
+
let match;
|
|
72
|
+
const markdownLinkRegex = /!?\[([^\]]+)\]\(([^)]+)\)/g;
|
|
73
|
+
while ((match = markdownLinkRegex.exec(text)) !== null) {
|
|
74
|
+
if (isInExcludedRange(match.index))
|
|
75
|
+
continue;
|
|
76
|
+
const prefixLength = (match[0].startsWith('!') ? 1 : 0) + 1 + match[1].length + 2;
|
|
77
|
+
const urlStart = match.index + prefixLength;
|
|
78
|
+
const urlEnd = urlStart + match[2].length;
|
|
79
|
+
const newUrl = normaliseLink(match[2], repoSlug, branchOrSha);
|
|
80
|
+
if (newUrl !== match[2]) {
|
|
81
|
+
replacements.push({ start: urlStart, end: urlEnd, newUrl });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const htmlTagRegex = /\s(src|href)=["']([^"']+)["']/gi;
|
|
85
|
+
while ((match = htmlTagRegex.exec(text)) !== null) {
|
|
86
|
+
if (isInExcludedRange(match.index))
|
|
87
|
+
continue;
|
|
88
|
+
const urlStart = match.index + match[0].indexOf(match[2]);
|
|
89
|
+
const urlEnd = urlStart + match[2].length;
|
|
90
|
+
const newUrl = normaliseLink(match[2], repoSlug, branchOrSha);
|
|
91
|
+
if (newUrl !== match[2]) {
|
|
92
|
+
replacements.push({ start: urlStart, end: urlEnd, newUrl });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
replacements.sort((a, b) => b.start - a.start);
|
|
96
|
+
let result = text;
|
|
97
|
+
for (const { start, end, newUrl } of replacements) {
|
|
98
|
+
result = result.substring(0, start) + newUrl + result.substring(end);
|
|
99
|
+
}
|
|
100
|
+
return result;
|
|
101
|
+
}
|
package/dist/tools/issueTools.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.default = registerIssueTools;
|
|
4
7
|
const zod_1 = require("zod");
|
|
5
8
|
const toolNames_js_1 = require("../constants/toolNames.js");
|
|
6
9
|
const toolDescriptions_js_1 = require("../constants/toolDescriptions.js");
|
|
7
10
|
const issue_js_1 = require("../api/issue.js");
|
|
11
|
+
const convertLink_js_1 = __importDefault(require("../helpers/convertLink.js"));
|
|
8
12
|
const formatToolResult_js_1 = require("../helpers/formatToolResult.js");
|
|
9
13
|
function registerIssueTools(server, client) {
|
|
10
14
|
server.tool(toolNames_js_1.ToolNames.LIST_ISSUES, toolDescriptions_js_1.toolDescriptions[toolNames_js_1.ToolNames.LIST_ISSUES], {
|
|
@@ -60,8 +64,11 @@ function registerIssueTools(server, client) {
|
|
|
60
64
|
issueId: zod_1.z.number().describe('Issue ID')
|
|
61
65
|
}, async ({ repo, issueId }) => {
|
|
62
66
|
try {
|
|
63
|
-
const
|
|
64
|
-
|
|
67
|
+
const issue = await (0, issue_js_1.getIssue)(client, repo, issueId);
|
|
68
|
+
if (typeof issue.body === 'string') {
|
|
69
|
+
issue.body = (0, convertLink_js_1.default)(issue.body, repo);
|
|
70
|
+
}
|
|
71
|
+
return (0, formatToolResult_js_1.formatTextToolResult)(JSON.stringify(issue, null, 2), toolNames_js_1.ToolNames.GET_ISSUE);
|
|
65
72
|
}
|
|
66
73
|
catch (error) {
|
|
67
74
|
return (0, formatToolResult_js_1.formatToolError)(error, toolNames_js_1.ToolNames.GET_ISSUE);
|
|
@@ -142,7 +149,16 @@ function registerIssueTools(server, client) {
|
|
|
142
149
|
}, async ({ repo, issueId, page, page_size }) => {
|
|
143
150
|
try {
|
|
144
151
|
const comments = await (0, issue_js_1.listIssueComments)(client, repo, issueId, { page, page_size });
|
|
145
|
-
|
|
152
|
+
const formattedComments = comments.map((comment) => {
|
|
153
|
+
if (typeof comment.body === 'string') {
|
|
154
|
+
return {
|
|
155
|
+
...comment,
|
|
156
|
+
body: (0, convertLink_js_1.default)(comment.body, repo)
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
return comment;
|
|
160
|
+
});
|
|
161
|
+
return (0, formatToolResult_js_1.formatTextToolResult)(JSON.stringify(formattedComments, null, 2), toolNames_js_1.ToolNames.LIST_ISSUE_COMMENTS);
|
|
146
162
|
}
|
|
147
163
|
catch (error) {
|
|
148
164
|
return (0, formatToolResult_js_1.formatToolError)(error, toolNames_js_1.ToolNames.LIST_ISSUE_COMMENTS);
|
|
@@ -59,4 +59,24 @@ function registerWorkspaceTools(server, client) {
|
|
|
59
59
|
return (0, formatToolResult_js_1.formatToolError)(error, toolNames_js_1.ToolNames.DELETE_WORKSPACE);
|
|
60
60
|
}
|
|
61
61
|
});
|
|
62
|
+
server.tool(toolNames_js_1.ToolNames.START_WORKSPACE, toolDescriptions_js_1.toolDescriptions[toolNames_js_1.ToolNames.START_WORKSPACE], {
|
|
63
|
+
repo: zod_1.z.string().describe('仓库路径,格式为 {group}/{repo}'),
|
|
64
|
+
branch: zod_1.z
|
|
65
|
+
.preprocess((val) => (val === null ? undefined : val), zod_1.z.string().optional())
|
|
66
|
+
.describe('分支名或 tag 名,例如:main 或 v1.0.0'),
|
|
67
|
+
ref: zod_1.z
|
|
68
|
+
.preprocess((val) => (val === null ? undefined : val), zod_1.z.string().optional())
|
|
69
|
+
.describe('Git ref,例如,refs/heads/main 或 refs/tags/v1.0.0。不传 ref 时默认基于分支启动')
|
|
70
|
+
}, async ({ repo, branch, ref }) => {
|
|
71
|
+
try {
|
|
72
|
+
const result = await (0, workspace_js_1.startWorkspace)(client, repo, {
|
|
73
|
+
branch,
|
|
74
|
+
ref
|
|
75
|
+
});
|
|
76
|
+
return (0, formatToolResult_js_1.formatTextToolResult)(JSON.stringify(result, null, 2), toolNames_js_1.ToolNames.START_WORKSPACE);
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
return (0, formatToolResult_js_1.formatToolError)(error, toolNames_js_1.ToolNames.START_WORKSPACE);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
62
82
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cnbcool/mcp-server",
|
|
3
3
|
"description": "CNB MCP Server. A comprehensive MCP server that provides seamless integration to the CNB's API(https://cnb.cool), offering a wide range of tools for repository management, pipelines operations and collaboration features",
|
|
4
|
-
"version": "0.6.
|
|
4
|
+
"version": "0.6.6",
|
|
5
5
|
"main": "./dist/stdio.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"cnb-mcp-stdio": "dist/stdio.js",
|