@cnbcool/mcp-server 0.1.2 → 0.3.0-beta.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/api/client.js +42 -0
- package/dist/api/issue.js +25 -0
- package/dist/api/repository.js +28 -0
- package/dist/api/workspace.js +20 -0
- package/dist/index.js +2 -7
- package/dist/tools/index.js +13 -0
- package/dist/tools/issueTools.js +93 -0
- package/dist/tools/repoTools.js +83 -0
- package/dist/tools/workspaceTools.js +93 -0
- package/package.json +1 -1
- package/dist/CnbApiClient.js +0 -76
- package/dist/tools.js +0 -171
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export default class CnbApiClient {
|
|
2
|
+
static instance = null;
|
|
3
|
+
_baseUrl;
|
|
4
|
+
_token;
|
|
5
|
+
constructor(options) {
|
|
6
|
+
this._baseUrl = options.baseUrl;
|
|
7
|
+
this._token = options.token;
|
|
8
|
+
}
|
|
9
|
+
static initialize(options) {
|
|
10
|
+
if (!CnbApiClient.instance) {
|
|
11
|
+
CnbApiClient.instance = new CnbApiClient(options);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
static getInstance() {
|
|
15
|
+
if (!CnbApiClient.instance) {
|
|
16
|
+
throw new Error('CnbApiClient not initialized. Call CnbApiClient.initialize(baseUrl, token) first.');
|
|
17
|
+
}
|
|
18
|
+
return CnbApiClient.instance;
|
|
19
|
+
}
|
|
20
|
+
get baseUrl() {
|
|
21
|
+
return this._baseUrl;
|
|
22
|
+
}
|
|
23
|
+
async request(method, path, body, config) {
|
|
24
|
+
const url = `${this._baseUrl}${path}`;
|
|
25
|
+
const headers = {
|
|
26
|
+
'Authorization': `Bearer ${this._token}`,
|
|
27
|
+
'Accept': 'application/vnd.cnb.api+json',
|
|
28
|
+
...(config?.header || {})
|
|
29
|
+
};
|
|
30
|
+
const options = {
|
|
31
|
+
method,
|
|
32
|
+
headers,
|
|
33
|
+
body: body ? JSON.stringify(body) : undefined
|
|
34
|
+
};
|
|
35
|
+
const response = await fetch(url, options);
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
const errorText = await response.text();
|
|
38
|
+
throw new Error(`API request failed: ${response.status} ${errorText}`);
|
|
39
|
+
}
|
|
40
|
+
return response.json();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import CnbApiClient from "./client.js";
|
|
2
|
+
export async function listIssues(repo, params) {
|
|
3
|
+
const cnbInst = CnbApiClient.getInstance();
|
|
4
|
+
const url = new URL(`/${repo}/-/issues`, cnbInst.baseUrl);
|
|
5
|
+
if (params) {
|
|
6
|
+
for (const [key, value] of Object.entries(params)) {
|
|
7
|
+
if (value === undefined)
|
|
8
|
+
continue;
|
|
9
|
+
url.searchParams.set(key, value.toString());
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return cnbInst.request('GET', `${url.pathname}${url.search}`);
|
|
13
|
+
}
|
|
14
|
+
export async function getIssue(repo, issueId) {
|
|
15
|
+
return CnbApiClient.getInstance().request('GET', `/${repo}/-/issues/${issueId}`);
|
|
16
|
+
}
|
|
17
|
+
export async function createIssue(repo, params) {
|
|
18
|
+
const newParams = Object.entries(params).reduce((acc, [key, value]) => {
|
|
19
|
+
if (value === undefined)
|
|
20
|
+
return acc;
|
|
21
|
+
Object.assign(acc, { key: value });
|
|
22
|
+
return acc;
|
|
23
|
+
}, {});
|
|
24
|
+
return CnbApiClient.getInstance().request('POST', `/${repo}/-/issues`, newParams, { header: { 'Content-Type': 'application/json' } });
|
|
25
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import CnbApiClient from "./client.js";
|
|
2
|
+
export async function listRepositories(params) {
|
|
3
|
+
const cnbInst = CnbApiClient.getInstance();
|
|
4
|
+
const url = new URL('/user/repos', cnbInst.baseUrl);
|
|
5
|
+
if (params) {
|
|
6
|
+
for (const [key, value] of Object.entries(params)) {
|
|
7
|
+
if (value === undefined)
|
|
8
|
+
continue;
|
|
9
|
+
url.searchParams.set(key, value.toString());
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return cnbInst.request('GET', `${url.pathname}${url.search}`);
|
|
13
|
+
}
|
|
14
|
+
export async function listGroupRepositories(group, params) {
|
|
15
|
+
const cnbInst = CnbApiClient.getInstance();
|
|
16
|
+
const url = new URL(`/${group}/-/repos`, cnbInst.baseUrl);
|
|
17
|
+
if (params) {
|
|
18
|
+
for (const [key, value] of Object.entries(params)) {
|
|
19
|
+
if (value === undefined)
|
|
20
|
+
continue;
|
|
21
|
+
url.searchParams.set(key, value.toString());
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return cnbInst.request('GET', `${url.pathname}${url.search}`);
|
|
25
|
+
}
|
|
26
|
+
export async function getRepository(repo) {
|
|
27
|
+
return CnbApiClient.getInstance().request('GET', `/${repo}`);
|
|
28
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import CnbApiClient from "./client.js";
|
|
2
|
+
export async function listWorkspace(params) {
|
|
3
|
+
const cnbInst = CnbApiClient.getInstance();
|
|
4
|
+
const url = new URL("/workspace/list", cnbInst.baseUrl);
|
|
5
|
+
if (params) {
|
|
6
|
+
for (const [key, value] of Object.entries(params)) {
|
|
7
|
+
if (value === undefined)
|
|
8
|
+
continue;
|
|
9
|
+
url.searchParams.set(key, value.toString());
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return cnbInst.request("GET", `${url.pathname}${url.search}`);
|
|
13
|
+
}
|
|
14
|
+
export async function deleteWorkspace(params) {
|
|
15
|
+
const cnbInst = CnbApiClient.getInstance();
|
|
16
|
+
const url = new URL("/workspace/delete", cnbInst.baseUrl);
|
|
17
|
+
return cnbInst.request("POST", `${url.pathname}`, params, {
|
|
18
|
+
header: { "Content-Type": "application/json" },
|
|
19
|
+
});
|
|
20
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -2,20 +2,15 @@
|
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import dotenv from "dotenv";
|
|
5
|
-
import {
|
|
6
|
-
import { registerTools } from "./tools.js";
|
|
5
|
+
import { registerTools } from "./tools/index.js";
|
|
7
6
|
// https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-3.html#import-attributes
|
|
8
7
|
import packageJSON from "../package.json" with { type: 'json' };
|
|
9
8
|
dotenv.config();
|
|
10
|
-
const cnbApiClient = new CnbApiClient({
|
|
11
|
-
baseUrl: process.env.API_BASE_URL || "https://api.cnb.cool",
|
|
12
|
-
token: process.env.API_TOKEN || ""
|
|
13
|
-
});
|
|
14
9
|
const server = new McpServer({
|
|
15
10
|
name: "cnb-mcp-server",
|
|
16
11
|
version: packageJSON.version
|
|
17
12
|
});
|
|
18
|
-
registerTools(server
|
|
13
|
+
registerTools(server);
|
|
19
14
|
async function main() {
|
|
20
15
|
console.error("server starting...");
|
|
21
16
|
const transport = new StdioServerTransport();
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import CnbApiClient from "../api/client.js";
|
|
2
|
+
import registerRepoTools from "./repoTools.js";
|
|
3
|
+
import registerIssueTools from "./issueTools.js";
|
|
4
|
+
import registerWorkspaceTools from "./workspaceTools.js";
|
|
5
|
+
export function registerTools(server) {
|
|
6
|
+
CnbApiClient.initialize({
|
|
7
|
+
baseUrl: process.env.API_BASE_URL || "https://api.cnb.cool",
|
|
8
|
+
token: process.env.API_TOKEN || ""
|
|
9
|
+
});
|
|
10
|
+
registerRepoTools(server);
|
|
11
|
+
registerIssueTools(server);
|
|
12
|
+
registerWorkspaceTools(server);
|
|
13
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createIssue, getIssue, listIssues } from "../api/issue.js";
|
|
3
|
+
export default function registerIssueTools(server) {
|
|
4
|
+
server.tool("list-issues", "查询仓库的 Issues", {
|
|
5
|
+
repoId: z.string().describe("仓库 ID"),
|
|
6
|
+
page: z.number().optional().describe("第几页,从1开始"),
|
|
7
|
+
page_size: z.number().optional().describe("每页多少条数据"),
|
|
8
|
+
state: z.enum(["open", "closed"]).optional().describe("Issue 状态"),
|
|
9
|
+
keyword: z.string().optional().describe("Issue 关键字"),
|
|
10
|
+
priority: z.string().optional().describe("Issue 优先级"),
|
|
11
|
+
labels: z.string().optional().describe("Issue 标签"),
|
|
12
|
+
authors: z.string().optional().describe("Issue 作者的名字"),
|
|
13
|
+
assignees: z.string().optional().describe("Issue 处理人"),
|
|
14
|
+
updated_time_begin: z.string().optional().describe("Issue 更新时间的范围,开始时间点"),
|
|
15
|
+
updated_time_end: z.string().optional().describe("Issue 更新时间的范围,结束时间点"),
|
|
16
|
+
order_by: z.string().optional().describe("Issue 排序顺序"),
|
|
17
|
+
}, async ({ repoId, page, page_size, state, keyword, priority, labels, authors, assignees, updated_time_begin, updated_time_end, order_by }) => {
|
|
18
|
+
try {
|
|
19
|
+
const issues = await listIssues(repoId, { page, page_size, state, keyword, priority, labels, authors, assignees, updated_time_begin, updated_time_end, order_by });
|
|
20
|
+
return {
|
|
21
|
+
content: [{
|
|
22
|
+
type: "text",
|
|
23
|
+
text: JSON.stringify(issues, null, 2)
|
|
24
|
+
}]
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
return {
|
|
29
|
+
content: [{
|
|
30
|
+
type: "text",
|
|
31
|
+
text: `Error listing issues: ${error instanceof Error ? error.message : String(error)}`
|
|
32
|
+
}],
|
|
33
|
+
isError: true
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
server.tool("get-issue", "获取指定 Issue 信息", {
|
|
38
|
+
repoId: z.string().describe("仓库 ID"),
|
|
39
|
+
issueId: z.number().describe("Issue ID")
|
|
40
|
+
}, async ({ repoId, issueId }) => {
|
|
41
|
+
try {
|
|
42
|
+
const issues = await getIssue(repoId, issueId);
|
|
43
|
+
return {
|
|
44
|
+
content: [{
|
|
45
|
+
type: "text",
|
|
46
|
+
text: JSON.stringify(issues, null, 2)
|
|
47
|
+
}]
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
return {
|
|
52
|
+
content: [{
|
|
53
|
+
type: "text",
|
|
54
|
+
text: `Error listing issues: ${error instanceof Error ? error.message : String(error)}`
|
|
55
|
+
}],
|
|
56
|
+
isError: true
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
server.tool("create-issue", "创建一个 Issue", {
|
|
61
|
+
repoId: z.string().describe("仓库 ID"),
|
|
62
|
+
title: z.string().describe("Issue 标题"),
|
|
63
|
+
body: z.string().optional().describe("Issue 描述"),
|
|
64
|
+
assignees: z.array(z.string()).optional().describe("一个或多个 Issue 处理人的用户名"),
|
|
65
|
+
labels: z.array(z.string()).optional().describe("一个或多个 Issue 标签"),
|
|
66
|
+
priority: z.string().optional().describe("Issue 优先级")
|
|
67
|
+
}, async ({ repoId, title, body, assignees, labels, priority }) => {
|
|
68
|
+
try {
|
|
69
|
+
const issue = await createIssue(repoId, {
|
|
70
|
+
title,
|
|
71
|
+
body,
|
|
72
|
+
assignees,
|
|
73
|
+
labels,
|
|
74
|
+
priority
|
|
75
|
+
});
|
|
76
|
+
return {
|
|
77
|
+
content: [{
|
|
78
|
+
type: "text",
|
|
79
|
+
text: `Issue created successfully:\n${JSON.stringify(issue, null, 2)}`
|
|
80
|
+
}]
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
return {
|
|
85
|
+
content: [{
|
|
86
|
+
type: "text",
|
|
87
|
+
text: `Error creating issue: ${error instanceof Error ? error.message : String(error)}`
|
|
88
|
+
}],
|
|
89
|
+
isError: true
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getRepository, listGroupRepositories, listRepositories } from "../api/repository.js";
|
|
3
|
+
export default function registerRepoTools(server) {
|
|
4
|
+
server.tool("list-repositories", "获取当前用户拥有指定权限及其以上权限的仓库", {
|
|
5
|
+
page: z.number().default(1).describe("第几页,从1开始"),
|
|
6
|
+
page_size: z.number().default(20).describe("每页多少条数据"),
|
|
7
|
+
search: z.string().optional().describe("仓库关键字"),
|
|
8
|
+
filter_type: z.enum(["private", "public", "encrypted"]).optional().describe("仓库类型"),
|
|
9
|
+
role: z.enum(["Reporter", "Developer", "Master", "Owner"]).optional().describe("最小仓库权限"),
|
|
10
|
+
order_by: z.enum(["created_at", "last_updated_at", "stars"]).optional().describe("排序类型"),
|
|
11
|
+
desc: z.boolean().optional().describe("排序顺序")
|
|
12
|
+
}, async ({ page, page_size, search, filter_type, role, order_by, desc }) => {
|
|
13
|
+
try {
|
|
14
|
+
const repos = await listRepositories({ page, page_size, search, filter_type, role, order_by, desc });
|
|
15
|
+
return {
|
|
16
|
+
content: [{
|
|
17
|
+
type: "text",
|
|
18
|
+
text: JSON.stringify(repos, null, 2)
|
|
19
|
+
}]
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
return {
|
|
24
|
+
content: [{
|
|
25
|
+
type: "text",
|
|
26
|
+
text: `Error listing repositories: ${error instanceof Error ? error.message : String(error)}`
|
|
27
|
+
}],
|
|
28
|
+
isError: true
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
server.tool("list-group-repositories", "获取分组里当前用户有权限的仓库", {
|
|
33
|
+
group: z.string().describe("组织名称"),
|
|
34
|
+
page: z.number().default(1).describe("第几页,从1开始"),
|
|
35
|
+
page_size: z.number().default(20).describe("每页多少条数据"),
|
|
36
|
+
search: z.string().optional().describe("仓库关键字"),
|
|
37
|
+
filter_type: z.enum(["private", "public", "encrypted"]).optional().describe("仓库类型"),
|
|
38
|
+
descendant: z.enum(["all", "sub", "grand"]).optional().describe("查全部、直接属于当前组织的仓库、子组织的仓库"),
|
|
39
|
+
order_by: z.enum(["created_at", "last_updated_at", "stars", "slug_path"]).optional().describe("排序类型"),
|
|
40
|
+
desc: z.boolean().optional().describe("排序顺序")
|
|
41
|
+
}, async ({ group, page, page_size, search, filter_type, descendant, order_by, desc }) => {
|
|
42
|
+
try {
|
|
43
|
+
const repos = await listGroupRepositories(group, { page, page_size, search, filter_type, descendant, order_by, desc });
|
|
44
|
+
return {
|
|
45
|
+
content: [{
|
|
46
|
+
type: "text",
|
|
47
|
+
text: JSON.stringify(repos, null, 2)
|
|
48
|
+
}]
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
return {
|
|
53
|
+
content: [{
|
|
54
|
+
type: "text",
|
|
55
|
+
text: `Error listing repositories: ${error instanceof Error ? error.message : String(error)}`
|
|
56
|
+
}],
|
|
57
|
+
isError: true
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
server.tool("get-repository", "获取指定仓库信息", {
|
|
62
|
+
repoId: z.string().describe("仓库 ID")
|
|
63
|
+
}, async ({ repoId }) => {
|
|
64
|
+
try {
|
|
65
|
+
const repo = await getRepository(repoId);
|
|
66
|
+
return {
|
|
67
|
+
content: [{
|
|
68
|
+
type: "text",
|
|
69
|
+
text: JSON.stringify(repo, null, 2)
|
|
70
|
+
}]
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
return {
|
|
75
|
+
content: [{
|
|
76
|
+
type: "text",
|
|
77
|
+
text: `Error getting repository: ${error instanceof Error ? error.message : String(error)}`
|
|
78
|
+
}],
|
|
79
|
+
isError: true
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { deleteWorkspace, listWorkspace } from "../api/workspace.js";
|
|
3
|
+
export default function registerWorkspaceTools(server) {
|
|
4
|
+
server.tool("list-workspace", "获取我的云原生开发环境列表", {
|
|
5
|
+
branch: z.string().optional().describe("分支名,例如:main"),
|
|
6
|
+
start: z
|
|
7
|
+
.string()
|
|
8
|
+
.optional()
|
|
9
|
+
.describe("查询结束时间,格式:YYYY-MM-DD HH:mm:ssZZ,例如:2024-12-01 00:00:00+0800"),
|
|
10
|
+
end: z
|
|
11
|
+
.string()
|
|
12
|
+
.optional()
|
|
13
|
+
.describe("查询开始时间,格式:YYYY-MM-DD HH:mm:ssZZ,例如:2024-12-01 00:00:00+0800"),
|
|
14
|
+
page: z.number().optional().describe("分页页码,从 1 开始,默认为 1"),
|
|
15
|
+
pageSize: z.number().optional().describe("每页条数,默认为 20,最高 100"),
|
|
16
|
+
slug: z
|
|
17
|
+
.string()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe("仓库路径,例如:groupname/reponame"),
|
|
20
|
+
status: z
|
|
21
|
+
.enum(["running", "closed"])
|
|
22
|
+
.optional()
|
|
23
|
+
.describe("开发环境状态,running: 开发环境已启动,closed:开发环境已关闭,默认为所有状态"),
|
|
24
|
+
}, async ({ branch, page, pageSize, start, end, slug, status }) => {
|
|
25
|
+
console.log("list-workspace", {
|
|
26
|
+
branch,
|
|
27
|
+
page,
|
|
28
|
+
pageSize,
|
|
29
|
+
start,
|
|
30
|
+
end,
|
|
31
|
+
slug,
|
|
32
|
+
status,
|
|
33
|
+
});
|
|
34
|
+
try {
|
|
35
|
+
const workspaces = await listWorkspace({
|
|
36
|
+
branch,
|
|
37
|
+
page,
|
|
38
|
+
pageSize,
|
|
39
|
+
start,
|
|
40
|
+
end,
|
|
41
|
+
slug,
|
|
42
|
+
status,
|
|
43
|
+
});
|
|
44
|
+
return {
|
|
45
|
+
content: [
|
|
46
|
+
{
|
|
47
|
+
type: "text",
|
|
48
|
+
text: JSON.stringify(workspaces, null, 2),
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
return {
|
|
55
|
+
content: [
|
|
56
|
+
{
|
|
57
|
+
type: "text",
|
|
58
|
+
text: `Error listing workspace: ${error instanceof Error ? error.message : String(error)}`,
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
isError: true,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
server.tool("delete-workspace", "删除我的云原生开发环境", {
|
|
66
|
+
pipelineId: z.string().describe("开发环境 ID"),
|
|
67
|
+
}, async ({ pipelineId }) => {
|
|
68
|
+
try {
|
|
69
|
+
const result = await deleteWorkspace({
|
|
70
|
+
pipelineId,
|
|
71
|
+
});
|
|
72
|
+
return {
|
|
73
|
+
content: [
|
|
74
|
+
{
|
|
75
|
+
type: "text",
|
|
76
|
+
text: JSON.stringify(result, null, 2),
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
return {
|
|
83
|
+
content: [
|
|
84
|
+
{
|
|
85
|
+
type: "text",
|
|
86
|
+
text: `error delete workspace: ${error instanceof Error ? error.message : String(error)}`,
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
isError: true,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
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.
|
|
4
|
+
"version": "0.3.0-beta.0",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"cnb-mcp-server": "dist/index.js"
|
package/dist/CnbApiClient.js
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
export class CnbApiClient {
|
|
2
|
-
baseUrl;
|
|
3
|
-
token;
|
|
4
|
-
constructor(options) {
|
|
5
|
-
this.baseUrl = options.baseUrl;
|
|
6
|
-
this.token = options.token;
|
|
7
|
-
}
|
|
8
|
-
async request(method, path, body, config) {
|
|
9
|
-
const url = `${this.baseUrl}${path}`;
|
|
10
|
-
const headers = {
|
|
11
|
-
'Authorization': `Bearer ${this.token}`,
|
|
12
|
-
'Accept': 'application/vnd.cnb.api+json',
|
|
13
|
-
...(config?.header || {})
|
|
14
|
-
};
|
|
15
|
-
const options = {
|
|
16
|
-
method,
|
|
17
|
-
headers,
|
|
18
|
-
body: body ? JSON.stringify(body) : undefined
|
|
19
|
-
};
|
|
20
|
-
const response = await fetch(url, options);
|
|
21
|
-
if (!response.ok) {
|
|
22
|
-
const errorText = await response.text();
|
|
23
|
-
throw new Error(`API request failed: ${response.status} ${errorText}`);
|
|
24
|
-
}
|
|
25
|
-
return response.json();
|
|
26
|
-
}
|
|
27
|
-
async listRepositories(params) {
|
|
28
|
-
const url = new URL('/user/repos', this.baseUrl);
|
|
29
|
-
if (params) {
|
|
30
|
-
for (const [key, value] of Object.entries(params)) {
|
|
31
|
-
if (value === undefined)
|
|
32
|
-
continue;
|
|
33
|
-
url.searchParams.set(key, value.toString());
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
return this.request('GET', `${url.pathname}${url.search}`);
|
|
37
|
-
}
|
|
38
|
-
async listGroupRepositories(group, params) {
|
|
39
|
-
const url = new URL(`/${group}/-/repos`, this.baseUrl);
|
|
40
|
-
if (params) {
|
|
41
|
-
for (const [key, value] of Object.entries(params)) {
|
|
42
|
-
if (value === undefined)
|
|
43
|
-
continue;
|
|
44
|
-
url.searchParams.set(key, value.toString());
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
return this.request('GET', `${url.pathname}${url.search}`);
|
|
48
|
-
}
|
|
49
|
-
async getRepository(repo) {
|
|
50
|
-
return this.request('GET', `/${repo}`);
|
|
51
|
-
}
|
|
52
|
-
async listIssues(repo, params) {
|
|
53
|
-
const url = new URL(`/${repo}/-/issues`, this.baseUrl);
|
|
54
|
-
if (params) {
|
|
55
|
-
for (const [key, value] of Object.entries(params)) {
|
|
56
|
-
if (value === undefined)
|
|
57
|
-
continue;
|
|
58
|
-
url.searchParams.set(key, value.toString());
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return this.request('GET', `${url.pathname}${url.search}`);
|
|
62
|
-
}
|
|
63
|
-
async getIssue(repo, issueId) {
|
|
64
|
-
return this.request('GET', `/${repo}/-/issues/${issueId}`);
|
|
65
|
-
}
|
|
66
|
-
async createIssue(repo, params) {
|
|
67
|
-
const newParams = Object.entries(params).reduce((acc, [key, value]) => {
|
|
68
|
-
if (value === undefined)
|
|
69
|
-
return acc;
|
|
70
|
-
// @ts-ignore
|
|
71
|
-
acc[key] = value;
|
|
72
|
-
return acc;
|
|
73
|
-
}, {});
|
|
74
|
-
return this.request('POST', `/${repo}/-/issues`, newParams, { header: { 'Content-Type': 'application/json' } });
|
|
75
|
-
}
|
|
76
|
-
}
|
package/dist/tools.js
DELETED
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
export function registerTools(server, cnbApi) {
|
|
3
|
-
server.tool("list-repositories", "获取当前用户拥有指定权限及其以上权限的仓库", {
|
|
4
|
-
page: z.number().default(1).describe("第几页,从1开始"),
|
|
5
|
-
page_size: z.number().default(20).describe("每页多少条数据"),
|
|
6
|
-
search: z.string().optional().describe("仓库关键字"),
|
|
7
|
-
filter_type: z.enum(["private", "public", "encrypted"]).optional().describe("仓库类型"),
|
|
8
|
-
role: z.enum(["Reporter", "Developer", "Master", "Owner"]).optional().describe("最小仓库权限"),
|
|
9
|
-
order_by: z.enum(["created_at", "last_updated_at", "stars"]).optional().describe("排序类型"),
|
|
10
|
-
desc: z.boolean().optional().describe("排序顺序")
|
|
11
|
-
}, async ({ page, page_size, search, filter_type, role, order_by, desc }) => {
|
|
12
|
-
try {
|
|
13
|
-
const repos = await cnbApi.listRepositories({ page, page_size, search, filter_type, role, order_by, desc });
|
|
14
|
-
return {
|
|
15
|
-
content: [{
|
|
16
|
-
type: "text",
|
|
17
|
-
text: JSON.stringify(repos, null, 2)
|
|
18
|
-
}]
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
catch (error) {
|
|
22
|
-
return {
|
|
23
|
-
content: [{
|
|
24
|
-
type: "text",
|
|
25
|
-
text: `Error listing repositories: ${error instanceof Error ? error.message : String(error)}`
|
|
26
|
-
}],
|
|
27
|
-
isError: true
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
server.tool("list-group-repositories", "获取分组里当前用户有权限的仓库", {
|
|
32
|
-
group: z.string().describe("组织名称"),
|
|
33
|
-
page: z.number().default(1).describe("第几页,从1开始"),
|
|
34
|
-
page_size: z.number().default(20).describe("每页多少条数据"),
|
|
35
|
-
search: z.string().optional().describe("仓库关键字"),
|
|
36
|
-
filter_type: z.enum(["private", "public", "encrypted"]).optional().describe("仓库类型"),
|
|
37
|
-
descendant: z.enum(["all", "sub", "grand"]).optional().describe("查全部、直接属于当前组织的仓库、子组织的仓库"),
|
|
38
|
-
order_by: z.enum(["created_at", "last_updated_at", "stars", "slug_path"]).optional().describe("排序类型"),
|
|
39
|
-
desc: z.boolean().optional().describe("排序顺序")
|
|
40
|
-
}, async ({ group, page, page_size, search, filter_type, descendant, order_by, desc }) => {
|
|
41
|
-
try {
|
|
42
|
-
const repos = await cnbApi.listGroupRepositories(group, { page, page_size, search, filter_type, descendant, order_by, desc });
|
|
43
|
-
return {
|
|
44
|
-
content: [{
|
|
45
|
-
type: "text",
|
|
46
|
-
text: JSON.stringify(repos, null, 2)
|
|
47
|
-
}]
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
catch (error) {
|
|
51
|
-
return {
|
|
52
|
-
content: [{
|
|
53
|
-
type: "text",
|
|
54
|
-
text: `Error listing repositories: ${error instanceof Error ? error.message : String(error)}`
|
|
55
|
-
}],
|
|
56
|
-
isError: true
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
server.tool("get-repository", "获取指定仓库信息", {
|
|
61
|
-
repoId: z.string().describe("仓库 ID")
|
|
62
|
-
}, async ({ repoId }) => {
|
|
63
|
-
try {
|
|
64
|
-
const repo = await cnbApi.getRepository(repoId);
|
|
65
|
-
return {
|
|
66
|
-
content: [{
|
|
67
|
-
type: "text",
|
|
68
|
-
text: JSON.stringify(repo, null, 2)
|
|
69
|
-
}]
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
catch (error) {
|
|
73
|
-
return {
|
|
74
|
-
content: [{
|
|
75
|
-
type: "text",
|
|
76
|
-
text: `Error getting repository: ${error instanceof Error ? error.message : String(error)}`
|
|
77
|
-
}],
|
|
78
|
-
isError: true
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
server.tool("list-issues", "查询仓库的 Issues", {
|
|
83
|
-
repoId: z.string().describe("仓库 ID"),
|
|
84
|
-
page: z.number().optional().describe("第几页,从1开始"),
|
|
85
|
-
page_size: z.number().optional().describe("每页多少条数据"),
|
|
86
|
-
state: z.enum(["open", "closed"]).optional().describe("Issue 状态"),
|
|
87
|
-
keyword: z.string().optional().describe("Issue 关键字"),
|
|
88
|
-
priority: z.string().optional().describe("Issue 优先级"),
|
|
89
|
-
labels: z.string().optional().describe("Issue 标签"),
|
|
90
|
-
authors: z.string().optional().describe("Issue 作者的名字"),
|
|
91
|
-
assignees: z.string().optional().describe("Issue 处理人"),
|
|
92
|
-
updated_time_begin: z.string().optional().describe("Issue 更新时间的范围,开始时间点"),
|
|
93
|
-
updated_time_end: z.string().optional().describe("Issue 更新时间的范围,结束时间点"),
|
|
94
|
-
order_by: z.string().optional().describe("Issue 排序顺序"),
|
|
95
|
-
}, async ({ repoId, page, page_size, state, keyword, priority, labels, authors, assignees, updated_time_begin, updated_time_end, order_by }) => {
|
|
96
|
-
try {
|
|
97
|
-
const issues = await cnbApi.listIssues(repoId, { page, page_size, state, keyword, priority, labels, authors, assignees, updated_time_begin, updated_time_end, order_by });
|
|
98
|
-
return {
|
|
99
|
-
content: [{
|
|
100
|
-
type: "text",
|
|
101
|
-
text: JSON.stringify(issues, null, 2)
|
|
102
|
-
}]
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
catch (error) {
|
|
106
|
-
return {
|
|
107
|
-
content: [{
|
|
108
|
-
type: "text",
|
|
109
|
-
text: `Error listing issues: ${error instanceof Error ? error.message : String(error)}`
|
|
110
|
-
}],
|
|
111
|
-
isError: true
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
server.tool("get-issue", "获取指定 Issue 信息", {
|
|
116
|
-
repoId: z.string().describe("仓库 ID"),
|
|
117
|
-
issueId: z.number().describe("Issue ID")
|
|
118
|
-
}, async ({ repoId, issueId }) => {
|
|
119
|
-
try {
|
|
120
|
-
const issues = await cnbApi.getIssue(repoId, issueId);
|
|
121
|
-
return {
|
|
122
|
-
content: [{
|
|
123
|
-
type: "text",
|
|
124
|
-
text: JSON.stringify(issues, null, 2)
|
|
125
|
-
}]
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
catch (error) {
|
|
129
|
-
return {
|
|
130
|
-
content: [{
|
|
131
|
-
type: "text",
|
|
132
|
-
text: `Error listing issues: ${error instanceof Error ? error.message : String(error)}`
|
|
133
|
-
}],
|
|
134
|
-
isError: true
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
});
|
|
138
|
-
server.tool("create-issue", "创建一个 Issue", {
|
|
139
|
-
repoId: z.string().describe("仓库 ID"),
|
|
140
|
-
title: z.string().describe("Issue 标题"),
|
|
141
|
-
body: z.string().optional().describe("Issue 描述"),
|
|
142
|
-
assignees: z.array(z.string()).optional().describe("一个或多个 Issue 处理人的用户名"),
|
|
143
|
-
labels: z.array(z.string()).optional().describe("一个或多个 Issue 标签"),
|
|
144
|
-
priority: z.string().optional().describe("Issue 优先级")
|
|
145
|
-
}, async ({ repoId, title, body, assignees, labels, priority }) => {
|
|
146
|
-
try {
|
|
147
|
-
const issue = await cnbApi.createIssue(repoId, {
|
|
148
|
-
title,
|
|
149
|
-
body,
|
|
150
|
-
assignees,
|
|
151
|
-
labels,
|
|
152
|
-
priority
|
|
153
|
-
});
|
|
154
|
-
return {
|
|
155
|
-
content: [{
|
|
156
|
-
type: "text",
|
|
157
|
-
text: `Issue created successfully:\n${JSON.stringify(issue, null, 2)}`
|
|
158
|
-
}]
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
catch (error) {
|
|
162
|
-
return {
|
|
163
|
-
content: [{
|
|
164
|
-
type: "text",
|
|
165
|
-
text: `Error creating issue: ${error instanceof Error ? error.message : String(error)}`
|
|
166
|
-
}],
|
|
167
|
-
isError: true
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
|
-
}
|