tca-mcp-server 1.3.2 → 2.0.1
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 +34 -20
- package/dist/api/project.js +207 -0
- package/dist/api/setup.js +149 -0
- package/dist/http.js +157 -0
- package/dist/stdio.js +2 -1
- package/dist/tools/index.js +10 -8
- package/dist/tools/issue.js +52 -8
- package/dist/tools/job.js +52 -8
- package/dist/tools/project.js +268 -0
- package/dist/tools/scan.js +26 -4
- package/dist/tools/setup.js +248 -0
- package/package.json +1 -1
- package/skill/install.sh +55 -0
- package/skill/pack.sh +32 -0
- package/skill/tca/README.md +70 -0
- package/skill/tca/SKILL.md +251 -0
- package/skill/tca/references/api_references.md +642 -0
- package/skill/tca/references/tca-code-scan.md +132 -0
- package/skill/tca/references/tca-issue-fix.md +150 -0
- package/skill/tca/references/tca-job-monitor.md +145 -0
- package/skill/tca/references/tca-setup.md +131 -0
- package/skill/tca/setup.sh +154 -0
- package/src/api/client.ts +79 -56
- package/src/api/setup.ts +177 -0
- package/src/stdio.ts +2 -1
- package/src/tools/index.ts +12 -10
- package/src/tools/issue.ts +68 -8
- package/src/tools/job.ts +58 -8
- package/src/tools/scan.ts +29 -4
- package/src/tools/setup.ts +348 -0
package/dist/api/client.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { createHash } from 'crypto';
|
|
2
2
|
import * as ini from 'ini';
|
|
3
3
|
import * as fs from 'fs';
|
|
4
|
-
export const mainUrlOA =
|
|
5
|
-
export const analysisUrlOA =
|
|
6
|
-
export const webUrlOA =
|
|
4
|
+
export const mainUrlOA = 'http://server.codedog.woa.com/main/api/';
|
|
5
|
+
export const analysisUrlOA = 'http://server.codedog.woa.com/analysis/api/';
|
|
6
|
+
export const webUrlOA = 'https://codedog.woa.com/';
|
|
7
7
|
export default class TcaClient {
|
|
8
8
|
static instance = null;
|
|
9
9
|
static commonParams = null;
|
|
@@ -28,7 +28,7 @@ export default class TcaClient {
|
|
|
28
28
|
return TcaClient.instance;
|
|
29
29
|
}
|
|
30
30
|
else {
|
|
31
|
-
throw new Error(
|
|
31
|
+
throw new Error('TcaClient is not initialized');
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
static async initConfig(configPath) {
|
|
@@ -39,7 +39,7 @@ export default class TcaClient {
|
|
|
39
39
|
if (TcaClient.instance?.runInOA()) {
|
|
40
40
|
TcaClient.commonOAParams = {
|
|
41
41
|
projectId: configSection.project_id,
|
|
42
|
-
repoId: configSection.repo_id
|
|
42
|
+
repoId: configSection.repo_id,
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
45
|
else {
|
|
@@ -52,29 +52,43 @@ export default class TcaClient {
|
|
|
52
52
|
}
|
|
53
53
|
return;
|
|
54
54
|
}
|
|
55
|
+
static setCommonParams(orgSid, teamName, repoId, projectId) {
|
|
56
|
+
TcaClient.commonParams = {
|
|
57
|
+
orgSid,
|
|
58
|
+
teamName,
|
|
59
|
+
repoId,
|
|
60
|
+
projectId,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
static setCommonOAParams(repoId, projectId) {
|
|
64
|
+
TcaClient.commonOAParams = {
|
|
65
|
+
repoId,
|
|
66
|
+
projectId,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
55
69
|
static getcommonParams() {
|
|
56
70
|
if (!TcaClient.commonParams) {
|
|
57
|
-
throw new Error(
|
|
71
|
+
throw new Error('common params is not initialized');
|
|
58
72
|
}
|
|
59
73
|
return TcaClient.commonParams;
|
|
60
74
|
}
|
|
61
75
|
static getcommonOAParams() {
|
|
62
76
|
if (!TcaClient.commonOAParams) {
|
|
63
|
-
throw new Error(
|
|
77
|
+
throw new Error('common OA params is not initialized');
|
|
64
78
|
}
|
|
65
79
|
return TcaClient.commonOAParams;
|
|
66
80
|
}
|
|
67
81
|
runInOA() {
|
|
68
|
-
return this.env ===
|
|
82
|
+
return this.env === 'oa';
|
|
69
83
|
}
|
|
70
84
|
getHeader() {
|
|
71
85
|
const timestamp = Math.floor(new Date().getTime() / 1000);
|
|
72
86
|
const tokenSig = `${timestamp}${this.userName}#${this.token}#${this.userName}${timestamp}`;
|
|
73
|
-
const ticket = createHash(
|
|
87
|
+
const ticket = createHash('sha256').update(tokenSig).digest('hex').toUpperCase();
|
|
74
88
|
return {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
89
|
+
'TCA-USERID': this.userName,
|
|
90
|
+
'TCA-TICKET': ticket,
|
|
91
|
+
'TCA-TIMESTAMP': timestamp.toString(),
|
|
78
92
|
};
|
|
79
93
|
}
|
|
80
94
|
buildUrlWithNestedParams(url, params) {
|
|
@@ -107,23 +121,23 @@ export default class TcaClient {
|
|
|
107
121
|
if (!baseUrl) {
|
|
108
122
|
baseUrl = this.baseUrl;
|
|
109
123
|
}
|
|
110
|
-
const norBaseUrl = baseUrl.endsWith(
|
|
111
|
-
const norPath = path.startsWith(
|
|
124
|
+
const norBaseUrl = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;
|
|
125
|
+
const norPath = path.startsWith('/') ? path.slice(1) : path;
|
|
112
126
|
const url = new URL(norPath, norBaseUrl);
|
|
113
127
|
var headers = {};
|
|
114
128
|
if (TcaClient.getInstance().runInOA()) {
|
|
115
129
|
headers = {
|
|
116
|
-
|
|
117
|
-
|
|
130
|
+
'Content-Type': 'application/json',
|
|
131
|
+
Authorization: `Token ${this.token}`,
|
|
118
132
|
};
|
|
119
133
|
}
|
|
120
134
|
else {
|
|
121
135
|
let header = this.getHeader();
|
|
122
136
|
headers = {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
137
|
+
'Content-Type': 'application/json',
|
|
138
|
+
'TCA-USERID': header['TCA-USERID'],
|
|
139
|
+
'TCA-TICKET': header['TCA-TICKET'],
|
|
140
|
+
'TCA-TIMESTAMP': header['TCA-TIMESTAMP'],
|
|
127
141
|
};
|
|
128
142
|
}
|
|
129
143
|
const options = {
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import TcaClient, { mainUrlOA } from "./client.js";
|
|
2
|
+
/**
|
|
3
|
+
* 列出用户所有团队
|
|
4
|
+
*/
|
|
5
|
+
export async function listOrgs() {
|
|
6
|
+
const clientInst = TcaClient.getInstance();
|
|
7
|
+
if (clientInst.runInInternal()) {
|
|
8
|
+
const path = `/orgs/`;
|
|
9
|
+
return clientInst.request(path, "GET", null, null, mainUrlOA);
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
const path = `/server/main/api/orgs/`;
|
|
13
|
+
return clientInst.request(path, "GET");
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 列出团队下的所有项目组
|
|
18
|
+
*/
|
|
19
|
+
export async function listTeams(orgSid) {
|
|
20
|
+
const clientInst = TcaClient.getInstance();
|
|
21
|
+
if (clientInst.runInInternal()) {
|
|
22
|
+
const path = `/orgs/${orgSid}/teams/`;
|
|
23
|
+
return clientInst.request(path, "GET", null, null, mainUrlOA);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
const path = `/server/main/api/orgs/${orgSid}/teams/`;
|
|
27
|
+
return clientInst.request(path, "GET");
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 列出项目组下的所有代码仓库
|
|
32
|
+
*/
|
|
33
|
+
export async function listRepos(orgSid, teamName, offset = 0, limit = 50) {
|
|
34
|
+
const clientInst = TcaClient.getInstance();
|
|
35
|
+
const params = { offset, limit };
|
|
36
|
+
if (clientInst.runInInternal()) {
|
|
37
|
+
const path = `/orgs/${orgSid}/teams/${teamName}/repos/`;
|
|
38
|
+
return clientInst.request(path, "GET", null, params, mainUrlOA);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
const path = `/server/main/api/orgs/${orgSid}/teams/${teamName}/repos/`;
|
|
42
|
+
return clientInst.request(path, "GET", null, params);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* 列出仓库下的所有分析项目
|
|
47
|
+
*/
|
|
48
|
+
export async function listProjects(orgSid, teamName, repoId, offset = 0, limit = 50) {
|
|
49
|
+
const clientInst = TcaClient.getInstance();
|
|
50
|
+
const params = { offset, limit };
|
|
51
|
+
if (clientInst.runInInternal()) {
|
|
52
|
+
const path = `/orgs/${orgSid}/teams/${teamName}/repos/${repoId}/projects/`;
|
|
53
|
+
return clientInst.request(path, "GET", null, params, mainUrlOA);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
const path = `/server/main/api/orgs/${orgSid}/teams/${teamName}/repos/${repoId}/projects/`;
|
|
57
|
+
return clientInst.request(path, "GET", null, params);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* 创建项目组
|
|
62
|
+
*/
|
|
63
|
+
export async function createTeam(orgSid, teamName, displayName) {
|
|
64
|
+
const clientInst = TcaClient.getInstance();
|
|
65
|
+
const data = {
|
|
66
|
+
name: teamName,
|
|
67
|
+
display_name: displayName,
|
|
68
|
+
};
|
|
69
|
+
if (clientInst.runInInternal()) {
|
|
70
|
+
const path = `/orgs/${orgSid}/teams/`;
|
|
71
|
+
return clientInst.request(path, "POST", data, null, mainUrlOA);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
const path = `/server/main/api/orgs/${orgSid}/teams/`;
|
|
75
|
+
return clientInst.request(path, "POST", data);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 列出用户的 SCM 凭证列表(包含用户名密码、SSH Key、OAuth、Private Token 等所有类型)
|
|
80
|
+
*/
|
|
81
|
+
export async function listScmAuths(orgSid) {
|
|
82
|
+
const clientInst = TcaClient.getInstance();
|
|
83
|
+
if (clientInst.runInInternal()) {
|
|
84
|
+
const path = `/authen/scmallaccounts/`;
|
|
85
|
+
return clientInst.request(path, "GET", null, null, mainUrlOA);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
const path = `/server/main/api/authen/scmallaccounts/`;
|
|
89
|
+
return clientInst.request(path, "GET");
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* 创建用户名密码类型的 SCM 凭证
|
|
94
|
+
*/
|
|
95
|
+
export async function createScmAccount(scmUsername, scmPassword) {
|
|
96
|
+
const clientInst = TcaClient.getInstance();
|
|
97
|
+
const data = {
|
|
98
|
+
scm_username: scmUsername,
|
|
99
|
+
scm_password: scmPassword,
|
|
100
|
+
};
|
|
101
|
+
if (clientInst.runInInternal()) {
|
|
102
|
+
const path = `/authen/scmaccounts/`;
|
|
103
|
+
return clientInst.request(path, "POST", data, null, mainUrlOA);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
const path = `/server/main/api/authen/scmaccounts/`;
|
|
107
|
+
return clientInst.request(path, "POST", data);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* 创建 SSH 类型的 SCM 凭证
|
|
112
|
+
*/
|
|
113
|
+
export async function createScmSSH(name, sshPrivateKey, password) {
|
|
114
|
+
const clientInst = TcaClient.getInstance();
|
|
115
|
+
const data = {
|
|
116
|
+
name,
|
|
117
|
+
ssh_private_key: sshPrivateKey,
|
|
118
|
+
};
|
|
119
|
+
if (password) {
|
|
120
|
+
data.password = password;
|
|
121
|
+
}
|
|
122
|
+
if (clientInst.runInInternal()) {
|
|
123
|
+
const path = `/authen/scmsshinfos/`;
|
|
124
|
+
return clientInst.request(path, "POST", data, null, mainUrlOA);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
const path = `/server/main/api/authen/scmsshinfos/`;
|
|
128
|
+
return clientInst.request(path, "POST", data);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* 接入代码仓库到项目组
|
|
133
|
+
*/
|
|
134
|
+
export async function createRepo(orgSid, teamName, scmUrl, scmType = "git", options) {
|
|
135
|
+
const clientInst = TcaClient.getInstance();
|
|
136
|
+
const data = {
|
|
137
|
+
scm_url: scmUrl,
|
|
138
|
+
scm_type: scmType,
|
|
139
|
+
};
|
|
140
|
+
if (options?.name) {
|
|
141
|
+
data.name = options.name;
|
|
142
|
+
}
|
|
143
|
+
if (options?.scm_auth) {
|
|
144
|
+
data.scm_auth = options.scm_auth;
|
|
145
|
+
}
|
|
146
|
+
if (options?.created_from) {
|
|
147
|
+
data.created_from = options.created_from;
|
|
148
|
+
}
|
|
149
|
+
if (options?.symbol) {
|
|
150
|
+
data.symbol = options.symbol;
|
|
151
|
+
}
|
|
152
|
+
if (clientInst.runInInternal()) {
|
|
153
|
+
const path = `/orgs/${orgSid}/teams/${teamName}/repos/`;
|
|
154
|
+
return clientInst.request(path, "POST", data, null, mainUrlOA);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
const path = `/server/main/api/orgs/${orgSid}/teams/${teamName}/repos/`;
|
|
158
|
+
return clientInst.request(path, "POST", data);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* 创建分析项目(使用指定的分析方案或官方体验方案)
|
|
163
|
+
*/
|
|
164
|
+
export async function createProject(orgSid, teamName, repoId, scanSchemeName, branch = "master") {
|
|
165
|
+
const clientInst = TcaClient.getInstance();
|
|
166
|
+
const data = {
|
|
167
|
+
scan_scheme_name: scanSchemeName,
|
|
168
|
+
branch: branch,
|
|
169
|
+
};
|
|
170
|
+
if (clientInst.runInInternal()) {
|
|
171
|
+
const path = `/orgs/${orgSid}/teams/${teamName}/repos/${repoId}/projects/`;
|
|
172
|
+
return clientInst.request(path, "POST", data, null, mainUrlOA);
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
const path = `/server/main/api/orgs/${orgSid}/teams/${teamName}/repos/${repoId}/projects/`;
|
|
176
|
+
return clientInst.request(path, "POST", data);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* 获取仓库详情
|
|
181
|
+
*/
|
|
182
|
+
export async function getRepoDetail(orgSid, teamName, repoId) {
|
|
183
|
+
const clientInst = TcaClient.getInstance();
|
|
184
|
+
if (clientInst.runInInternal()) {
|
|
185
|
+
const path = `/orgs/${orgSid}/teams/${teamName}/repos/${repoId}/`;
|
|
186
|
+
return clientInst.request(path, "GET", null, null, mainUrlOA);
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
const path = `/server/main/api/orgs/${orgSid}/teams/${teamName}/repos/${repoId}/`;
|
|
190
|
+
return clientInst.request(path, "GET");
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* 获取分析方案列表
|
|
195
|
+
*/
|
|
196
|
+
export async function listSchemes(orgSid, teamName, repoId, offset = 0, limit = 50) {
|
|
197
|
+
const clientInst = TcaClient.getInstance();
|
|
198
|
+
const params = { offset, limit };
|
|
199
|
+
if (clientInst.runInInternal()) {
|
|
200
|
+
const path = `/orgs/${orgSid}/teams/${teamName}/repos/${repoId}/schemes/`;
|
|
201
|
+
return clientInst.request(path, "GET", null, params, mainUrlOA);
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
const path = `/server/main/api/orgs/${orgSid}/teams/${teamName}/repos/${repoId}/schemes/`;
|
|
205
|
+
return clientInst.request(path, "GET", null, params);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import TcaClient from './client.js';
|
|
2
|
+
import * as ini from 'ini';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
/**
|
|
5
|
+
* 获取用户所属的团队列表
|
|
6
|
+
*/
|
|
7
|
+
export async function listOrgs() {
|
|
8
|
+
const clientInst = TcaClient.getInstance();
|
|
9
|
+
const path = `/server/main/api/orgs/`;
|
|
10
|
+
return clientInst.request(path, 'GET');
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* 获取指定团队下的项目组列表
|
|
14
|
+
*/
|
|
15
|
+
export async function listTeams(orgSid) {
|
|
16
|
+
const clientInst = TcaClient.getInstance();
|
|
17
|
+
const path = `/server/main/api/orgs/${orgSid}/teams/`;
|
|
18
|
+
return clientInst.request(path, 'GET');
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 获取指定项目组下的代码仓库列表
|
|
22
|
+
*/
|
|
23
|
+
export async function listRepos(orgSid, teamName) {
|
|
24
|
+
const clientInst = TcaClient.getInstance();
|
|
25
|
+
const path = `/server/main/api/orgs/${orgSid}/teams/${teamName}/repos/`;
|
|
26
|
+
return clientInst.request(path, 'GET');
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 获取指定仓库下的分析项目列表
|
|
30
|
+
*/
|
|
31
|
+
export async function listProjects(orgSid, teamName, repoId) {
|
|
32
|
+
const clientInst = TcaClient.getInstance();
|
|
33
|
+
const path = `/server/main/api/orgs/${orgSid}/teams/${teamName}/repos/${repoId}/projects/`;
|
|
34
|
+
return clientInst.request(path, 'GET');
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* 获取指定仓库下可用的分析方案列表
|
|
38
|
+
*/
|
|
39
|
+
export async function listSchemes(orgSid, teamName, repoId) {
|
|
40
|
+
const clientInst = TcaClient.getInstance();
|
|
41
|
+
const path = `/server/main/api/orgs/${orgSid}/teams/${teamName}/repos/${repoId}/schemes/`;
|
|
42
|
+
return clientInst.request(path, 'GET');
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* 在指定仓库下创建分析项目
|
|
46
|
+
*/
|
|
47
|
+
export async function createProject(orgSid, teamName, repoId, scanSchemeName, branch) {
|
|
48
|
+
const clientInst = TcaClient.getInstance();
|
|
49
|
+
const path = `/server/main/api/orgs/${orgSid}/teams/${teamName}/repos/${repoId}/projects/`;
|
|
50
|
+
const data = {
|
|
51
|
+
scan_scheme_name: scanSchemeName,
|
|
52
|
+
branch: branch,
|
|
53
|
+
};
|
|
54
|
+
return clientInst.request(path, 'POST', data);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* 在指定团队下创建项目组
|
|
58
|
+
*/
|
|
59
|
+
export async function createTeam(orgSid, teamName, displayName) {
|
|
60
|
+
const clientInst = TcaClient.getInstance();
|
|
61
|
+
const path = `/server/main/api/orgs/${orgSid}/teams/`;
|
|
62
|
+
const data = {
|
|
63
|
+
name: teamName,
|
|
64
|
+
display_name: displayName,
|
|
65
|
+
};
|
|
66
|
+
return clientInst.request(path, 'POST', data);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* 在指定项目组下接入代码仓库
|
|
70
|
+
*/
|
|
71
|
+
export async function createRepo(orgSid, teamName, scmUrl, scmType = 'git', name, scmAuthType, scmAccountId, scmSshId, scmOauthId, scmPrivateTokenId, createdFrom, symbol) {
|
|
72
|
+
const clientInst = TcaClient.getInstance();
|
|
73
|
+
const path = `/server/main/api/orgs/${orgSid}/teams/${teamName}/repos/`;
|
|
74
|
+
const data = {
|
|
75
|
+
scm_url: scmUrl,
|
|
76
|
+
scm_type: scmType,
|
|
77
|
+
};
|
|
78
|
+
if (name)
|
|
79
|
+
data.name = name;
|
|
80
|
+
if (scmAuthType)
|
|
81
|
+
data.scm_auth_type = scmAuthType;
|
|
82
|
+
if (scmAccountId)
|
|
83
|
+
data.scm_account = scmAccountId;
|
|
84
|
+
if (scmSshId)
|
|
85
|
+
data.scm_ssh = scmSshId;
|
|
86
|
+
if (scmOauthId)
|
|
87
|
+
data.scm_oauth = scmOauthId;
|
|
88
|
+
if (scmPrivateTokenId)
|
|
89
|
+
data.scm_private_token = scmPrivateTokenId;
|
|
90
|
+
if (createdFrom)
|
|
91
|
+
data.created_from = createdFrom;
|
|
92
|
+
if (symbol)
|
|
93
|
+
data.symbol = symbol;
|
|
94
|
+
return clientInst.request(path, 'POST', data);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* 获取用户在 TCA 上已配置的凭证列表
|
|
98
|
+
*/
|
|
99
|
+
export async function listScmAuths(orgSid) {
|
|
100
|
+
const clientInst = TcaClient.getInstance();
|
|
101
|
+
const path = `/server/main/api/v3/authen/scmauthinfos/`;
|
|
102
|
+
return clientInst.request(path, 'GET');
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* 创建用户名密码类型的凭证
|
|
106
|
+
*/
|
|
107
|
+
export async function createScmAccount(scmUsername, scmPassword) {
|
|
108
|
+
const clientInst = TcaClient.getInstance();
|
|
109
|
+
const path = `/server/main/api/v3/authen/scmaccounts/`;
|
|
110
|
+
const data = {
|
|
111
|
+
scm_username: scmUsername,
|
|
112
|
+
scm_password: scmPassword,
|
|
113
|
+
};
|
|
114
|
+
return clientInst.request(path, 'POST', data);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* 创建 SSH 类型的凭证
|
|
118
|
+
*/
|
|
119
|
+
export async function createScmSsh(name, sshPrivateKey, password) {
|
|
120
|
+
const clientInst = TcaClient.getInstance();
|
|
121
|
+
const path = `/server/main/api/v3/authen/scmsshinfos/`;
|
|
122
|
+
const data = {
|
|
123
|
+
name: name,
|
|
124
|
+
ssh_private_key: sshPrivateKey,
|
|
125
|
+
};
|
|
126
|
+
if (password)
|
|
127
|
+
data.password = password;
|
|
128
|
+
return clientInst.request(path, 'POST', data);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* 生成 tca-mcp.ini 配置文件
|
|
132
|
+
*/
|
|
133
|
+
export async function setupConfig(configPath, orgSid, teamName, repoId, projectId) {
|
|
134
|
+
const config = {
|
|
135
|
+
config: {
|
|
136
|
+
org_sid: orgSid,
|
|
137
|
+
team_name: teamName,
|
|
138
|
+
repo_id: repoId,
|
|
139
|
+
project_id: projectId,
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
const content = ini.stringify(config);
|
|
143
|
+
await fs.promises.writeFile(configPath, content, 'utf-8');
|
|
144
|
+
return {
|
|
145
|
+
message: '配置文件已成功生成',
|
|
146
|
+
path: configPath,
|
|
147
|
+
config: config.config,
|
|
148
|
+
};
|
|
149
|
+
}
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
4
|
+
import { createServer } from 'node:http';
|
|
5
|
+
import { randomUUID } from 'node:crypto';
|
|
6
|
+
import { registerTools } from './tools/index.js';
|
|
7
|
+
// 从命令行获取参数,判断运行环境
|
|
8
|
+
function getEnv() {
|
|
9
|
+
if (process.argv.includes('--tai'))
|
|
10
|
+
return 'tai';
|
|
11
|
+
if (process.argv.includes('--oa'))
|
|
12
|
+
return 'oa';
|
|
13
|
+
return 'saas';
|
|
14
|
+
}
|
|
15
|
+
// 从命令行获取端口号,默认 3000
|
|
16
|
+
function getPort() {
|
|
17
|
+
const portIndex = process.argv.indexOf('--port');
|
|
18
|
+
if (portIndex !== -1 && process.argv[portIndex + 1]) {
|
|
19
|
+
const port = parseInt(process.argv[portIndex + 1], 10);
|
|
20
|
+
if (!isNaN(port))
|
|
21
|
+
return port;
|
|
22
|
+
}
|
|
23
|
+
return parseInt(process.env.PORT || '3000', 10);
|
|
24
|
+
}
|
|
25
|
+
const env = getEnv();
|
|
26
|
+
process.env.TCA_ENV = env;
|
|
27
|
+
const port = getPort();
|
|
28
|
+
// 存储活跃的 transport 实例(按 sessionId 索引)
|
|
29
|
+
const transports = new Map();
|
|
30
|
+
/**
|
|
31
|
+
* 创建新的 MCP Server + Transport 对
|
|
32
|
+
*/
|
|
33
|
+
function createMcpSession() {
|
|
34
|
+
const server = new McpServer({
|
|
35
|
+
name: '腾讯云代码分析(TCA)',
|
|
36
|
+
version: '1.3.2',
|
|
37
|
+
});
|
|
38
|
+
registerTools(server);
|
|
39
|
+
const transport = new StreamableHTTPServerTransport({
|
|
40
|
+
sessionIdGenerator: () => randomUUID(),
|
|
41
|
+
onsessioninitialized: (sessionId) => {
|
|
42
|
+
transports.set(sessionId, transport);
|
|
43
|
+
console.log(`[会话创建] sessionId=${sessionId}`);
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
transport.onclose = () => {
|
|
47
|
+
if (transport.sessionId) {
|
|
48
|
+
transports.delete(transport.sessionId);
|
|
49
|
+
console.log(`[会话关闭] sessionId=${transport.sessionId}`);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
// 连接 server 和 transport
|
|
53
|
+
server.connect(transport);
|
|
54
|
+
return transport;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* 从请求头中获取 sessionId
|
|
58
|
+
*/
|
|
59
|
+
function getSessionId(req) {
|
|
60
|
+
const header = req.headers['mcp-session-id'];
|
|
61
|
+
return Array.isArray(header) ? header[0] : header;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* 处理 HTTP 请求
|
|
65
|
+
*/
|
|
66
|
+
async function handleRequest(req, res) {
|
|
67
|
+
const url = req.url || '/';
|
|
68
|
+
// 健康检查端点
|
|
69
|
+
if (url === '/health' && req.method === 'GET') {
|
|
70
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
71
|
+
res.end(JSON.stringify({ status: 'ok', env, sessions: transports.size }));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
// MCP 端点 —— 只处理根路径 /mcp 或 /
|
|
75
|
+
if (url !== '/mcp' && url !== '/') {
|
|
76
|
+
res.writeHead(404);
|
|
77
|
+
res.end('Not Found');
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
// 如果是太湖环境,将 x-tai-identity 写入环境变量,供 TcaClient 使用
|
|
81
|
+
if (env === 'tai') {
|
|
82
|
+
const taiIdentity = req.headers['x-tai-identity'];
|
|
83
|
+
if (taiIdentity) {
|
|
84
|
+
// 将太湖身份信息存入环境变量,TcaClient 初始化时会读取
|
|
85
|
+
process.env.TAI_IDENTITY = Array.isArray(taiIdentity) ? taiIdentity[0] : taiIdentity;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const sessionId = getSessionId(req);
|
|
89
|
+
if (req.method === 'POST') {
|
|
90
|
+
// 读取请求体
|
|
91
|
+
const body = await new Promise((resolve, reject) => {
|
|
92
|
+
let data = '';
|
|
93
|
+
req.on('data', (chunk) => { data += chunk.toString(); });
|
|
94
|
+
req.on('end', () => resolve(data));
|
|
95
|
+
req.on('error', reject);
|
|
96
|
+
});
|
|
97
|
+
let parsedBody;
|
|
98
|
+
try {
|
|
99
|
+
parsedBody = JSON.parse(body);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
103
|
+
res.end(JSON.stringify({ error: 'Invalid JSON' }));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
// 如果有 sessionId,使用已有的 transport
|
|
107
|
+
if (sessionId && transports.has(sessionId)) {
|
|
108
|
+
const transport = transports.get(sessionId);
|
|
109
|
+
await transport.handleRequest(req, res, parsedBody);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
// 没有 sessionId 或 sessionId 无效 —— 可能是初始化请求,创建新会话
|
|
113
|
+
if (!sessionId) {
|
|
114
|
+
const transport = createMcpSession();
|
|
115
|
+
await transport.handleRequest(req, res, parsedBody);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
// sessionId 无效(会话已过期或不存在)
|
|
119
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
120
|
+
res.end(JSON.stringify({ error: 'Session not found', code: -32000 }));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (req.method === 'GET') {
|
|
124
|
+
// SSE 连接
|
|
125
|
+
if (sessionId && transports.has(sessionId)) {
|
|
126
|
+
const transport = transports.get(sessionId);
|
|
127
|
+
await transport.handleRequest(req, res);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
131
|
+
res.end(JSON.stringify({ error: 'Missing or invalid session ID for GET request' }));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (req.method === 'DELETE') {
|
|
135
|
+
// 关闭会话
|
|
136
|
+
if (sessionId && transports.has(sessionId)) {
|
|
137
|
+
const transport = transports.get(sessionId);
|
|
138
|
+
await transport.handleRequest(req, res);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
142
|
+
res.end(JSON.stringify({ error: 'Session not found' }));
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
// 不支持的方法
|
|
146
|
+
res.writeHead(405);
|
|
147
|
+
res.end('Method Not Allowed');
|
|
148
|
+
}
|
|
149
|
+
// 创建 HTTP 服务器
|
|
150
|
+
const httpServer = createServer(handleRequest);
|
|
151
|
+
httpServer.listen(port, () => {
|
|
152
|
+
console.log(`TCA MCP HTTP Server 已启动`);
|
|
153
|
+
console.log(` 环境: ${env}`);
|
|
154
|
+
console.log(` 端口: ${port}`);
|
|
155
|
+
console.log(` MCP 端点: http://localhost:${port}/mcp`);
|
|
156
|
+
console.log(` 健康检查: http://localhost:${port}/health`);
|
|
157
|
+
});
|
package/dist/stdio.js
CHANGED
|
@@ -13,4 +13,5 @@ process.env.TCA_ENV = env;
|
|
|
13
13
|
registerTools(server);
|
|
14
14
|
const transport = new StdioServerTransport();
|
|
15
15
|
await server.connect(transport);
|
|
16
|
-
|
|
16
|
+
// 注意:stdio 模式下必须使用 stderr 输出日志,stdout 专用于 MCP 协议通信
|
|
17
|
+
console.error('TCA MCP Server started');
|
package/dist/tools/index.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import TcaClient from
|
|
2
|
-
import registerStartScan from
|
|
3
|
-
import registerJob from
|
|
4
|
-
import registerIssueReport from
|
|
1
|
+
import TcaClient from '../api/client.js';
|
|
2
|
+
import registerStartScan from './scan.js';
|
|
3
|
+
import registerJob from './job.js';
|
|
4
|
+
import registerIssueReport from './issue.js';
|
|
5
|
+
import registerSetup from './setup.js';
|
|
5
6
|
export function registerTools(server) {
|
|
6
7
|
TcaClient.initTcaClient({
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
token: process.env.TCA_TOKEN || '',
|
|
9
|
+
userName: process.env.TCA_USER_NAME || '',
|
|
10
|
+
baseUrl: process.env.TCA_BASE_URL || 'https://tca.tencent.com/',
|
|
11
|
+
env: process.env.TCA_ENV || 'saas',
|
|
11
12
|
});
|
|
12
13
|
registerStartScan(server);
|
|
13
14
|
registerJob(server);
|
|
14
15
|
registerIssueReport(server);
|
|
16
|
+
registerSetup(server);
|
|
15
17
|
}
|