@cnbcool/mcp-server 0.1.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/CHANGELOG.md ADDED
@@ -0,0 +1,9 @@
1
+ ## 0.1.0 (March 17, 2025)
2
+
3
+ ### New Features
4
+
5
+ - Supports list user repositories with various filters
6
+ - Supports get repository details
7
+ - Supports list repository issues with various filters
8
+ - Supports get issue details
9
+ - Supports create repository issue
package/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # CNB.COOL MCP Server
2
+
3
+ https://cnb.cool toolkits for LLMs supporting the MCP protocol
4
+
5
+ ## How to develop
6
+
7
+ 1. `npm install`
8
+ 2. Rename `.env.example` to `.env` and fill in the values
9
+ 3. `npm build`
10
+ 4. `npx @modelcontextprotocol/inspector node dist/index.js`
11
+
12
+ ## How to configure
13
+
14
+ ### Local
15
+
16
+ ```json
17
+ {
18
+ "mcpServers": {
19
+ "cnb": {
20
+ "command": "node",
21
+ "args": ["/path/to/cnbcool/mcp-server/dist/index.js"],
22
+ "env": {
23
+ "API_BASE_URL": "<BASE_URL>",
24
+ "API_TOKEN": "<YOUR_TOKEN>"
25
+ }
26
+ }
27
+ }
28
+ }
29
+ ```
30
+
31
+ ### Production
32
+
33
+ ```json
34
+ {
35
+ "mcpServers": {
36
+ "cnb": {
37
+ "command": "npx",
38
+ "args": ["-y", "@cnbcool/mcp-server"],
39
+ "env": {
40
+ "API_BASE_URL": "<BASE_URL>",
41
+ "API_TOKEN": "<YOUR_TOKEN>"
42
+ }
43
+ }
44
+ }
45
+ }
46
+ ```
@@ -0,0 +1,65 @@
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 getRepository(repo) {
39
+ return this.request('GET', `/${repo}`);
40
+ }
41
+ async listIssues(repo, params) {
42
+ const url = new URL(`/${repo}/-/issues`, this.baseUrl);
43
+ if (params) {
44
+ for (const [key, value] of Object.entries(params)) {
45
+ if (value === undefined)
46
+ continue;
47
+ url.searchParams.set(key, value.toString());
48
+ }
49
+ }
50
+ return this.request('GET', `${url.pathname}${url.search}`);
51
+ }
52
+ async getIssue(repo, issueId) {
53
+ return this.request('GET', `/${repo}/-/issues/${issueId}`);
54
+ }
55
+ async createIssue(repo, params) {
56
+ const newParams = Object.entries(params).reduce((acc, [key, value]) => {
57
+ if (value === undefined)
58
+ return acc;
59
+ // @ts-ignore
60
+ acc[key] = value;
61
+ return acc;
62
+ }, {});
63
+ return this.request('POST', `/${repo}/-/issues`, newParams, { header: { 'Content-Type': 'application/json' } });
64
+ }
65
+ }
package/dist/index.js ADDED
@@ -0,0 +1,25 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import dotenv from "dotenv";
4
+ import { CnbApiClient } from "./CnbApiClient.js";
5
+ import { registerTools } from "./tools.js";
6
+ dotenv.config();
7
+ const cnbApiClient = new CnbApiClient({
8
+ baseUrl: process.env.API_BASE_URL || "https://api.cnb.cool",
9
+ token: process.env.API_TOKEN || ""
10
+ });
11
+ const server = new McpServer({
12
+ name: "cnb-cool",
13
+ version: "0.0.1"
14
+ });
15
+ registerTools(server, cnbApiClient);
16
+ async function main() {
17
+ console.error("server starting...");
18
+ const transport = new StdioServerTransport();
19
+ await server.connect(transport);
20
+ console.error("server connected");
21
+ }
22
+ main().catch(error => {
23
+ console.error("Fatal error:", error);
24
+ process.exit(1);
25
+ });
package/dist/tools.js ADDED
@@ -0,0 +1,119 @@
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("get-repository", "获取指定仓库信息", {
32
+ repoId: z.string().describe("仓库 ID")
33
+ }, async ({ repoId }) => {
34
+ try {
35
+ const repo = await cnbApi.getRepository(repoId);
36
+ return {
37
+ content: [{
38
+ type: "text",
39
+ text: JSON.stringify(repo, null, 2)
40
+ }]
41
+ };
42
+ }
43
+ catch (error) {
44
+ return {
45
+ content: [{
46
+ type: "text",
47
+ text: `Error getting repository: ${error instanceof Error ? error.message : String(error)}`
48
+ }],
49
+ isError: true
50
+ };
51
+ }
52
+ });
53
+ server.tool("list-issues", "查询仓库的 Issues", {
54
+ repoId: z.string().describe("仓库 ID"),
55
+ page: z.number().optional().describe("第几页,从1开始"),
56
+ page_size: z.number().optional().describe("每页多少条数据"),
57
+ state: z.enum(["open", "closed"]).optional().describe("Issue 状态"),
58
+ keyword: z.string().optional().describe("Issue 关键字"),
59
+ priority: z.string().optional().describe("Issue 优先级"),
60
+ labels: z.string().optional().describe("Issue 标签"),
61
+ authors: z.string().optional().describe("Issue 作者的名字"),
62
+ assignees: z.string().optional().describe("Issue 处理人"),
63
+ updated_time_begin: z.string().optional().describe("Issue 更新时间的范围,开始时间点"),
64
+ updated_time_end: z.string().optional().describe("Issue 更新时间的范围,结束时间点"),
65
+ order_by: z.string().optional().describe("Issue 排序顺序"),
66
+ }, async ({ repoId, page, page_size, state, keyword, priority, labels, authors, assignees, updated_time_begin, updated_time_end, order_by }) => {
67
+ try {
68
+ const issues = await cnbApi.listIssues(repoId, { page, page_size, state, keyword, priority, labels, authors, assignees, updated_time_begin, updated_time_end, order_by });
69
+ return {
70
+ content: [{
71
+ type: "text",
72
+ text: JSON.stringify(issues, null, 2)
73
+ }]
74
+ };
75
+ }
76
+ catch (error) {
77
+ return {
78
+ content: [{
79
+ type: "text",
80
+ text: `Error listing issues: ${error instanceof Error ? error.message : String(error)}`
81
+ }],
82
+ isError: true
83
+ };
84
+ }
85
+ });
86
+ server.tool("create-issue", "创建一个 Issue", {
87
+ repoId: z.string().describe("仓库 ID"),
88
+ title: z.string().describe("Issue 标题"),
89
+ body: z.string().optional().describe("Issue 描述"),
90
+ assignees: z.array(z.string()).optional().describe("一个或多个 Issue 处理人的用户名"),
91
+ labels: z.array(z.string()).optional().describe("一个或多个 Issue 标签"),
92
+ priority: z.string().optional().describe("Issue 优先级")
93
+ }, async ({ repoId, title, body, assignees, labels, priority }) => {
94
+ try {
95
+ const issue = await cnbApi.createIssue(repoId, {
96
+ title,
97
+ body,
98
+ assignees,
99
+ labels,
100
+ priority
101
+ });
102
+ return {
103
+ content: [{
104
+ type: "text",
105
+ text: `Issue created successfully:\n${JSON.stringify(issue, null, 2)}`
106
+ }]
107
+ };
108
+ }
109
+ catch (error) {
110
+ return {
111
+ content: [{
112
+ type: "text",
113
+ text: `Error creating issue: ${error instanceof Error ? error.message : String(error)}`
114
+ }],
115
+ isError: true
116
+ };
117
+ }
118
+ });
119
+ }
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@cnbcool/mcp-server",
3
+ "description": "CMB MCP server",
4
+ "version": "0.1.0-beta.0",
5
+ "main": "./dist/index.js",
6
+ "type": "module",
7
+ "scripts": {
8
+ "postinstall": "openapi-typescript https://api.cnb.cool/swagger.json -o src/schema.d.ts",
9
+ "build": "tsc",
10
+ "start": "node dist/index.js"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://cnb.cool/cnb/tools/mcp-server.git"
15
+ },
16
+ "keywords": [
17
+ "cnb",
18
+ "cloud native build",
19
+ "model context protocol",
20
+ "mcp server"
21
+ ],
22
+ "license": "MIT",
23
+ "files": [
24
+ "dist/",
25
+ "package.json",
26
+ "CHANGELOG.md",
27
+ "README.md"
28
+ ],
29
+ "engines": {
30
+ "node": ">= 20",
31
+ "typescript": ">=5.5"
32
+ },
33
+ "dependencies": {
34
+ "@modelcontextprotocol/sdk": "^1.6.1",
35
+ "dotenv": "^16.4.7",
36
+ "zod": "^3.24.2"
37
+ },
38
+ "devDependencies": {
39
+ "@types/node": "^22.13.9",
40
+ "openapi-typescript": "^5.4.2",
41
+ "typescript": "^5.8.2"
42
+ }
43
+ }