tca-mcp-server 1.0.2 → 1.2.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/README.md CHANGED
@@ -1,22 +1,78 @@
1
- ### tools 接口
1
+ # Tencent Cloud Code Analysis (TCA) MCP Server​
2
+ > Official website (https://tca.tencent.com) MCP Server supporting MCP protocol for quickly starting code analysis and obtaining code analysis reports.
2
3
 
3
- 配置需求:
4
- - token
5
- - username
6
- - url (可选,不对用户开放)
4
+ Tencent Cloud Code Analysis (TCA), which started in 2012 (internal code name: CodeDog), is a cloud-native, distributed, and high-performance comprehensive code analysis and tracking management platform integrating numerous code analysis tools. Its main functions are to continuously track and analyze code, observe project code quality, and support teams in inheriting code culture. For more information about Tencent Cloud Code Assistant, please visit the official website usage guide: https://tca.tencent.com/document/zh/guide/.
7
5
 
8
- 1,启动分析
9
- 参数:
10
- - 仓库类型(git, svn)
11
- - 仓库地址
12
- - 仓库分支
13
- - 方案id
14
- - 本地分析/远程分析
15
- - 是否等待分析完成
6
+ ## TCA MCP Server Usage Steps​
16
7
 
17
- 2,获取分析的进度
8
+ ### 1,Create relevant resources on the TCA official website
18
9
 
19
- 3,获取当前仓库的方案列表
10
+ Official website: https://tca.tencent.com/​
20
11
 
21
- 4,接入仓库接口
12
+ - step1: [Create a team] Visit the TCA official website, log in, select to create a team, fill in relevant information, and wait for the application to be approved:
13
+ ![create_team](https://cnb.cool/tca/plugins/tca-mcp-server/-/git/raw/master/docs/images/org.png)
22
14
 
15
+ - step2: [Create a project team] After creating the team, click to select the team, and create a project team after entering:
16
+ ![create a project team](https://cnb.cool/tca/plugins/tca-mcp-server/-/git/raw/master/docs/images/project_team.png)
17
+
18
+ - step3: [Access the code repository] After creating the project team, click to select the project team, and select to access the code repository that needs to be analyzed after entering:
19
+ ![repo](https://cnb.cool/tca/plugins/tca-mcp-server/-/git/raw/master/docs/images/repo.png)
20
+
21
+ - step4: [Create an analysis project] After successfully accessing the code repository, create an analysis project (it is recommended to first use the official experience plan in the figure for usage experience):
22
+ ![create a project](https://cnb.cool/tca/plugins/tca-mcp-server/-/git/raw/master/docs/images/project.png)
23
+
24
+ ### 2, Create a `tca-mcp.ini` configuration file in the code repository
25
+
26
+ Create a tca-mcp.ini configuration file in the code repository that needs code analysis. The configuration file is stored in the root directory of the code repository, and the content of the configuration file is as follows:
27
+
28
+ ```ini
29
+ [config]
30
+ project_id=<project_id>
31
+ repo_id=<repo_id>
32
+ org_sid=<org_sid>
33
+ team_name=<team_name>
34
+ ```
35
+
36
+ Relevant parameters can be obtained from the route of the corresponding page, as shown in the following figure:
37
+
38
+ ![tca-mcp-ini参数](https://cnb.cool/tca/plugins/tca-mcp-server/-/git/raw/master/docs/images/tca-mcp-ini.png)
39
+
40
+ Where `4iYVpci9nAX` corresponds to `org_sid`; `19485` corresponds to `repo_id`; `234521` corresponds to `project_id`; `first` corresponds to `team_name`. Fill in according to the actual situation.
41
+
42
+
43
+ ### 3, Configure TCA MCP Server
44
+ ```json
45
+ {
46
+ "mcpServers": {
47
+ "tca-mcp-server": {
48
+ "command": "npx",
49
+ "args": ["-y", "-p", "tca-mcp-server@latest", "tca-mcp-stdio"],
50
+ "env": {
51
+ "TCA_TOKEN": "<TCA_TOKEN>",
52
+ "TCA_USER_NAME": "<TCA_USER_NAME>"
53
+ }
54
+ }
55
+ }
56
+ }
57
+ ```
58
+ The corresponding TCA_TOKEN and TCA_USER_NAME are obtained from the TCA official website, [Personal Center] -> [Personal Token], and can be accessed at https://tca.tencent.com/user/token.
59
+
60
+ ## TCA MCP Server Development Steps​
61
+ > Requirements: nodejs >= 22.0.0
62
+
63
+ 1,npm run build
64
+ 2, Manually add test configuration:
65
+ ```bash
66
+ {
67
+ "mcpServers": {
68
+ "tca-mcp-server-test": {
69
+ "command": "node",
70
+ "args": ["/path/to/tca-mcp-server/dist/stdio.js"],
71
+ "env": {
72
+ "TCA_TOKEN": "<TCA_TOKEN>",
73
+ "TCA_USER_NAME": "<TCA_USER_NAME>",
74
+ }
75
+ }
76
+ }
77
+ }
78
+ ```
package/README_zh.md ADDED
@@ -0,0 +1,80 @@
1
+ # 腾讯云代码分析(TCA)MCP Server
2
+ > 官网地址(https://tca.tencent.com) 支持 MCP 协议的 MCP Server,用于快速启动分析代码,获取代码分析报告。
3
+
4
+ 腾讯云代码分析(Tencent Code Analysis, TCA)起步于 2012 年(内部代号CodeDog),是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,支撑团队传承代码文化。了解腾讯云代码助手更多信息,请访问官网使用指南: https://tca.tencent.com/document/zh/guide/
5
+
6
+
7
+ ## TCA MCP Server 使用步骤
8
+
9
+ ### 1,在TCA官网上创建相关资源
10
+
11
+ 官网地址: https://tca.tencent.com/
12
+
13
+ - step1:【创建团队】 访问TCA官网,登录后选择创建一个团队,填写相关信息,等待申请通过:
14
+ ![创建团队](https://cnb.cool/tca/plugins/tca-mcp-server/-/git/raw/master/docs/images/org.png)
15
+
16
+ - step2:【创建项目组】 创建团队后,点击选择团队,进入后创建一个项目组:
17
+ ![创建项目组](https://cnb.cool/tca/plugins/tca-mcp-server/-/git/raw/master/docs/images/project_team.png)
18
+
19
+ - step3:【接入代码库】 创建项目组后,点击选择项目组,进入后选择接入需要进行分析的代码库:
20
+ ![接入代码库](https://cnb.cool/tca/plugins/tca-mcp-server/-/git/raw/master/docs/images/repo.png)
21
+
22
+ - step4:【创建分析项目】 成功接入代码库后,创建一个分析项目(推荐先使用图中的官方体验方案进行使用体验):
23
+ ![创建分析项目](https://cnb.cool/tca/plugins/tca-mcp-server/-/git/raw/master/docs/images/project.png)
24
+
25
+ ### 2,在代码库中创建 `tca-mcp.ini` 配置文件
26
+
27
+ 在需要进行代码分析的代码库中创建 **tca-mcp.ini** 配置文件,配置文件存放在代码库**根目录**下,配置文件内容如下:
28
+
29
+ ```ini
30
+ [config]
31
+ project_id=<project_id>
32
+ repo_id=<repo_id>
33
+ org_sid=<org_sid>
34
+ team_name=<team_name>
35
+ ```
36
+
37
+ 相关参数可以从对应页面的路由中获取,如下图所示:
38
+
39
+ ![tca-mcp-ini参数](https://cnb.cool/tca/plugins/tca-mcp-server/-/git/raw/master/docs/images/tca-mcp-ini.png)
40
+ 其中`4iYVpci9nAX` 对应 `org_sid`; `19485` 对应 `repo_id`; `234521` 对应 `project_id`; `first` 对应 `team_name`。按照实际情况填写即可。
41
+
42
+
43
+ ### 3, 配置TCA MCP Server
44
+ ```json
45
+ {
46
+ "mcpServers": {
47
+ "tca-mcp-server": {
48
+ "command": "npx",
49
+ "args": ["-y", "-p", "tca-mcp-server@latest", "tca-mcp-stdio"],
50
+ "env": {
51
+ "TCA_TOKEN": "<TCA_TOKEN>",
52
+ "TCA_USER_NAME": "<TCA_USER_NAME>"
53
+ }
54
+ }
55
+ }
56
+ }
57
+ ```
58
+ 对应的 `TCA_TOKEN` 和 `TCA_USER_NAME` 从TCA官网, 【个人中心】 -> 【个人令牌
59
+ 】 中获取,可访问 https://tca.tencent.com/user/token 获取。
60
+
61
+
62
+ ## TCA MCP Server 开发步骤
63
+ > 要求: nodejs >= 22.0.0
64
+
65
+ 1,npm run build
66
+ 2, 手动添加测试配置:
67
+ ```bash
68
+ {
69
+ "mcpServers": {
70
+ "tca-mcp-server-test": {
71
+ "command": "node",
72
+ "args": ["/path/to/tca-mcp-server/dist/stdio.js"],
73
+ "env": {
74
+ "TCA_TOKEN": "<TCA_TOKEN>",
75
+ "TCA_USER_NAME": "<TCA_USER_NAME>",
76
+ }
77
+ }
78
+ }
79
+ }
80
+ ```
@@ -1,17 +1,26 @@
1
1
  import { createHash } from 'crypto';
2
+ import * as ini from 'ini';
3
+ import * as fs from 'fs';
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/";
2
7
  export default class TcaClient {
3
8
  static instance = null;
9
+ static commonParams = null;
10
+ static commonOAParams = null;
4
11
  token;
5
12
  userName;
6
13
  baseUrl;
14
+ env;
7
15
  constructor(options) {
8
16
  this.token = options.token;
9
17
  this.userName = options.userName;
10
- this.baseUrl = options.baseUrl || "https://tca.tencent.com/";
18
+ this.baseUrl = options.baseUrl;
19
+ this.env = options.env;
11
20
  }
12
21
  static initTcaClient(options) {
13
22
  if (!TcaClient.instance) {
14
- const client = new TcaClient(options);
23
+ TcaClient.instance = new TcaClient(options);
15
24
  }
16
25
  }
17
26
  static getInstance() {
@@ -22,6 +31,42 @@ export default class TcaClient {
22
31
  throw new Error("TcaClient is not initialized");
23
32
  }
24
33
  }
34
+ static async initConfig(configPath) {
35
+ // 解析 ini 配置文件
36
+ const data = await fs.promises.readFile(configPath, 'utf-8');
37
+ const config = ini.parse(data);
38
+ const configSection = config.config || {};
39
+ if (TcaClient.instance?.runInOA()) {
40
+ TcaClient.commonOAParams = {
41
+ projectId: configSection.project_id,
42
+ repoId: configSection.repo_id
43
+ };
44
+ }
45
+ else {
46
+ TcaClient.commonParams = {
47
+ orgSid: configSection.org_sid,
48
+ teamName: configSection.team_name,
49
+ repoId: configSection.repo_id,
50
+ projectId: configSection.project_id,
51
+ };
52
+ }
53
+ return;
54
+ }
55
+ static getcommonParams() {
56
+ if (!TcaClient.commonParams) {
57
+ throw new Error("common params is not initialized");
58
+ }
59
+ return TcaClient.commonParams;
60
+ }
61
+ static getcommonOAParams() {
62
+ if (!TcaClient.commonOAParams) {
63
+ throw new Error("common OA params is not initialized");
64
+ }
65
+ return TcaClient.commonOAParams;
66
+ }
67
+ runInOA() {
68
+ return this.env === "oa";
69
+ }
25
70
  getHeader() {
26
71
  const timestamp = Math.floor(new Date().getTime() / 1000);
27
72
  const tokenSig = `${timestamp}${this.userName}#${this.token}#${this.userName}${timestamp}`;
@@ -32,27 +77,69 @@ export default class TcaClient {
32
77
  "TCA-TIMESTAMP": timestamp.toString(),
33
78
  };
34
79
  }
35
- async request(path, method, data) {
36
- const norBaseUrl = this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`;
80
+ buildUrlWithNestedParams(url, params) {
81
+ const paramsObj = new URLSearchParams();
82
+ // 递归处理嵌套对象
83
+ function addParams(prefix, value) {
84
+ if (value === undefined || value === null) {
85
+ return;
86
+ }
87
+ if (typeof value === 'object' && !Array.isArray(value)) {
88
+ Object.entries(value).forEach(([key, val]) => {
89
+ addParams(`${prefix}[${key}]`, val);
90
+ });
91
+ }
92
+ else if (Array.isArray(value)) {
93
+ value.forEach((item, index) => {
94
+ addParams(`${prefix}[${index}]`, item);
95
+ });
96
+ }
97
+ else {
98
+ paramsObj.append(prefix, String(value));
99
+ }
100
+ }
101
+ Object.entries(params).forEach(([key, value]) => {
102
+ addParams(key, value);
103
+ });
104
+ return paramsObj.toString() ? `${url}?${paramsObj}` : url;
105
+ }
106
+ async request(path, method, data, params, baseUrl) {
107
+ if (!baseUrl) {
108
+ baseUrl = this.baseUrl;
109
+ }
110
+ const norBaseUrl = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
37
111
  const norPath = path.startsWith("/") ? path.slice(1) : path;
38
112
  const url = new URL(norPath, norBaseUrl);
39
- const header = this.getHeader();
40
- const options = {
41
- method,
42
- headers: {
113
+ var headers = {};
114
+ if (TcaClient.getInstance().runInOA()) {
115
+ headers = {
116
+ "Content-Type": "application/json",
117
+ "Authorization": `Token ${this.token}`,
118
+ };
119
+ }
120
+ else {
121
+ let header = this.getHeader();
122
+ headers = {
43
123
  "Content-Type": "application/json",
44
124
  "TCA-USERID": header['TCA-USERID'],
45
125
  "TCA-TICKET": header['TCA-TICKET'],
46
126
  "TCA-TIMESTAMP": header['TCA-TIMESTAMP']
47
- },
127
+ };
128
+ }
129
+ const options = {
130
+ method,
131
+ headers: headers,
48
132
  };
49
133
  if (data) {
50
134
  options.body = JSON.stringify(data);
51
135
  }
136
+ if (params) {
137
+ url.search = this.buildUrlWithNestedParams(url.search, params);
138
+ }
52
139
  try {
53
140
  const response = await fetch(url, options);
54
141
  if (!response.ok) {
55
- throw new Error(`Request failed with status ${response.status}`);
142
+ throw new Error(`Request failed with status ${response.status}, ${options.body}, ${options.headers}, ${options.method}, ${url}`);
56
143
  }
57
144
  const json = await response.json();
58
145
  return json;
@@ -0,0 +1,27 @@
1
+ import TcaClient, { analysisUrlOA } from "./client.js";
2
+ export async function getIssueList(state, severity, offset, limit) {
3
+ const method = "GET";
4
+ const clientInst = TcaClient.getInstance();
5
+ if (clientInst.runInOA()) {
6
+ const pathParamsOA = TcaClient.getcommonOAParams();
7
+ const path = `/projects/${pathParamsOA.projectId}/codelint/issues/`;
8
+ const params = {
9
+ state: state,
10
+ severity: severity,
11
+ offset: offset,
12
+ limit: limit
13
+ };
14
+ return clientInst.request(path, method, null, params, analysisUrlOA);
15
+ }
16
+ else {
17
+ const pathParams = TcaClient.getcommonParams();
18
+ const path = `/server/analysis/api/orgs/${pathParams.orgSid}/teams/${pathParams.teamName}/repos/${pathParams.repoId}/projects/${pathParams.projectId}/codelint/issues/`;
19
+ const params = {
20
+ state: state,
21
+ severity: severity,
22
+ offset: offset,
23
+ limit: limit
24
+ };
25
+ return clientInst.request(path, method, null, params);
26
+ }
27
+ }
@@ -0,0 +1,39 @@
1
+ import TcaClient, { mainUrlOA } from "./client.js";
2
+ export async function getJobList(offset, limit, state) {
3
+ const method = "GET";
4
+ const clientInst = TcaClient.getInstance();
5
+ if (clientInst.runInOA()) {
6
+ const pathParamsOA = TcaClient.getcommonOAParams();
7
+ const path = `/projects/${pathParamsOA.projectId}/jobs/`;
8
+ const params = {
9
+ "offset": offset,
10
+ "limit": limit,
11
+ "state": state,
12
+ };
13
+ return clientInst.request(path, method, null, params, mainUrlOA);
14
+ }
15
+ else {
16
+ const pathParams = TcaClient.getcommonParams();
17
+ const path = `/server/main/api/orgs/${pathParams.orgSid}/teams/${pathParams.teamName}/repos/${pathParams.repoId}/projects/${pathParams.projectId}/jobs/`;
18
+ const params = {
19
+ "offset": offset,
20
+ "limit": limit,
21
+ "state": state,
22
+ };
23
+ return clientInst.request(path, method, null, params);
24
+ }
25
+ }
26
+ export async function getJobDetail(jobId) {
27
+ const method = "GET";
28
+ const clientInst = TcaClient.getInstance();
29
+ if (clientInst.runInOA()) {
30
+ const pathParamsOA = TcaClient.getcommonOAParams();
31
+ const path = `/projects/${pathParamsOA.projectId}/jobs/${jobId}/detail/`;
32
+ return clientInst.request(path, method, null, null, mainUrlOA);
33
+ }
34
+ else {
35
+ const pathParams = TcaClient.getcommonParams();
36
+ const path = `/server/main/api/orgs/${pathParams.orgSid}/teams/${pathParams.teamName}/repos/${pathParams.repoId}/projects/${pathParams.projectId}/jobs/${jobId}/detail/`;
37
+ return clientInst.request(path, method, null, null);
38
+ }
39
+ }
@@ -0,0 +1,25 @@
1
+ import TcaClient, { mainUrlOA } from "./client.js";
2
+ export async function startScan(incr_scan, force_create) {
3
+ const method = "POST";
4
+ const clientInst = TcaClient.getInstance();
5
+ if (clientInst.runInOA()) {
6
+ const pathParamsOA = TcaClient.getcommonOAParams();
7
+ const path = `/projects/${pathParamsOA.projectId}/scans/create/`;
8
+ const data = {
9
+ "incr_scan": incr_scan,
10
+ "created_from": "tca-mcp-server",
11
+ "force_create": force_create,
12
+ };
13
+ return clientInst.request(path, method, data, null, mainUrlOA);
14
+ }
15
+ else {
16
+ const pathParams = TcaClient.getcommonParams();
17
+ const path = `/server/main/api/orgs/${pathParams.orgSid}/teams/${pathParams.teamName}/repos/${pathParams.repoId}/projects/${pathParams.projectId}/scans/create/`;
18
+ const data = {
19
+ "incr_scan": incr_scan,
20
+ "created_from": "tca-mcp-server",
21
+ "force_create": force_create,
22
+ };
23
+ return clientInst.request(path, method, data);
24
+ }
25
+ }
package/dist/stdio.js CHANGED
@@ -1,24 +1,16 @@
1
1
  #!/usr/bin/env node
2
- import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
- import { z } from "zod";
4
+ import { registerTools } from "./tools/index.js";
5
5
  // Create an MCP server
6
6
  const server = new McpServer({
7
7
  name: "腾讯云代码分析(TCA)",
8
8
  version: "1.0.0"
9
9
  });
10
- // Add an addition tool
11
- server.tool("add", { a: z.number(), b: z.number() }, async ({ a, b }) => ({
12
- content: [{ type: "text", text: String(a + b) }]
13
- }));
14
- // Add a dynamic greeting resource
15
- server.resource("greeting", new ResourceTemplate("greeting://{name}", { list: undefined }), async (uri, { name }) => ({
16
- contents: [{
17
- uri: uri.href,
18
- text: `Hello, ${name}!`
19
- }]
20
- }));
21
- // Start receiving messages on stdin and sending messages on stdout
10
+ // 从命令行获取参数,如果 --oa参数存在,则表示运行在OA环境,否则运行在SaaS环境
11
+ const env = process.argv.includes("--oa") ? "oa" : "saas";
12
+ process.env.TCA_ENV = env;
13
+ registerTools(server);
22
14
  const transport = new StdioServerTransport();
23
15
  await server.connect(transport);
24
16
  console.log("Server started");
@@ -0,0 +1,15 @@
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
+ export function registerTools(server) {
6
+ TcaClient.initTcaClient({
7
+ "token": process.env.TCA_TOKEN || "",
8
+ "userName": process.env.TCA_USER_NAME || "",
9
+ "baseUrl": process.env.TCA_BASE_URL || "https://tca.tencent.com/",
10
+ "env": process.env.TCA_ENV || "saas"
11
+ });
12
+ registerStartScan(server);
13
+ registerJob(server);
14
+ registerIssueReport(server);
15
+ }
@@ -1 +1,48 @@
1
- export {};
1
+ import { z } from "zod";
2
+ import TcaClient, { webUrlOA } from "../api/client.js";
3
+ import { formatTextToolResult, formatToolError } from "../utils/resp.js";
4
+ import { getIssueList } from "../api/issue.js";
5
+ export default function registerIssueReport(server) {
6
+ server.tool('tca_issue_list', `获取当前项目未解决的问题列表, 参数如下:
7
+ -mcpConfigFile: mcp配置文件路径,当前项目根目录查找 tca-mcp.ini(绝对路径)
8
+ -state: 查询任务状态, (0为等待中,1为执行中,2为关闭,3为入库中,4正在初始化, 5初始化完成 可多选,格式为1,2,3)
9
+ -severity: 问题严重程度, (0为低,1为中,2为高)
10
+ -offset: 偏移量
11
+ -limit: 限制数量
12
+ `, {
13
+ mcpConfigFile: z.string(),
14
+ state: z.string().default("0,1,2,3,4,5"),
15
+ severity: z.string().default("0,1,2"),
16
+ offset: z.number().default(0),
17
+ limit: z.number().default(10),
18
+ }, async ({ mcpConfigFile, state, severity, offset, limit }) => {
19
+ await TcaClient.initConfig(mcpConfigFile);
20
+ try {
21
+ const res = await getIssueList(state, severity, offset, limit);
22
+ return formatTextToolResult(JSON.stringify(res, null, 2), 'get issue list');
23
+ }
24
+ catch (error) {
25
+ return formatToolError(error, 'get issue list error');
26
+ }
27
+ });
28
+ server.tool('tca_issue_report', `tca报告链接, 参数如下:
29
+ -mcpConfigFile: mcp配置文件路径,当前项目根目录查找 tca-mcp.ini(绝对路径)
30
+ -jobId: 任务id, 启动分析任务后,从返回结果中获取`, {
31
+ mcpConfigFile: z.string(),
32
+ jobId: z.string().describe("jobId"),
33
+ }, async ({ mcpConfigFile, jobId }) => {
34
+ await TcaClient.initConfig(mcpConfigFile);
35
+ const clientInst = TcaClient.getInstance();
36
+ if (clientInst.runInOA()) {
37
+ const paramsOA = TcaClient.getcommonOAParams();
38
+ const path = `code-analysis/repos/${paramsOA.repoId}/projects/${paramsOA.projectId}/scan-history/${jobId}/result`;
39
+ return formatTextToolResult(webUrlOA + path, 'tca issue report link');
40
+ }
41
+ else {
42
+ const baseUrl = TcaClient.getInstance().baseUrl;
43
+ const params = TcaClient.getcommonParams();
44
+ const path = `t/${params.orgSid}/p/${params.teamName}/repos/${params.repoId}/projects/${params.projectId}/jobs/${jobId}/result`;
45
+ return formatTextToolResult(baseUrl + path, 'tca issue report link');
46
+ }
47
+ });
48
+ }
@@ -0,0 +1,40 @@
1
+ import { z } from "zod";
2
+ import TcaClient from "../api/client.js";
3
+ import { formatTextToolResult, formatToolError } from "../utils/resp.js";
4
+ import { getJobDetail, getJobList } from "../api/job.js";
5
+ export default function registerJob(server) {
6
+ server.tool('job_detail', `获取任务详情, 参数如下:
7
+ -mcpConfigFile: mcp配置文件路径,当前项目根目录查找 tca-mcp.ini(绝对路径)
8
+ -jobId: 任务id`, {
9
+ mcpConfigFile: z.string(),
10
+ jobId: z.string().describe("任务id")
11
+ }, async ({ mcpConfigFile, jobId }) => {
12
+ await TcaClient.initConfig(mcpConfigFile);
13
+ try {
14
+ const res = await getJobDetail(jobId);
15
+ return formatTextToolResult(JSON.stringify(res, null, 2), 'get job detail');
16
+ }
17
+ catch (error) {
18
+ return formatToolError(error, 'get job detail error!');
19
+ }
20
+ });
21
+ server.tool('job_list', `获取当前任务列表, 参数如下:
22
+ -mcpConfigFile: mcp配置文件路径,当前项目根目录查找 tca-mcp.ini(绝对路径)
23
+ -offset: 偏移量
24
+ -limit: 限制数量
25
+ -state: 查询任务状态, (0为等待中,1为执行中,2为关闭,3为入库中,4正在初始化, 5初始化完成 可多选,格式为1,2,3)`, {
26
+ mcpConfigFile: z.string(),
27
+ offset: z.number().default(0).describe("偏移量"),
28
+ limit: z.number().default(10).describe("限制数量"),
29
+ state: z.string().default("0,1,2,3,4,5").describe("查询任务状态, (0为等待中,1为执行中,2为关闭,3为入库中,4正在初始化, 5初始化完成 可多选,格式为1,2,3)")
30
+ }, async ({ mcpConfigFile, offset, limit, state }) => {
31
+ await TcaClient.initConfig(mcpConfigFile);
32
+ try {
33
+ const res = await getJobList(offset, limit, state);
34
+ return formatTextToolResult(JSON.stringify(res, null, 2), 'get job list');
35
+ }
36
+ catch (error) {
37
+ return formatToolError(error, 'get job list error!');
38
+ }
39
+ });
40
+ }
@@ -1 +1,23 @@
1
- export {};
1
+ import { z } from "zod";
2
+ import { startScan } from "../api/scan.js";
3
+ import { formatTextToolResult, formatToolError } from "../utils/resp.js";
4
+ import TcaClient from "../api/client.js";
5
+ export default function registerStartScan(server) {
6
+ server.tool('start_scan', `启动tca代码分析 参数如下:
7
+ -mcpConfigFile: mcp配置文件路径,当前项目根目录查找 tca-mcp.ini(绝对路径)
8
+ -incrScan: 是否增量扫描
9
+ -forceCreate: 如果已经存在扫描结果,是否强制启动新扫描`, {
10
+ mcpConfigFile: z.string(),
11
+ incrScan: z.boolean().default(false).describe("是否增量扫描"),
12
+ forceCreate: z.boolean().default(false).describe("如果已经存在扫描结果,是否强制启动新扫描"),
13
+ }, async ({ mcpConfigFile, incrScan, forceCreate }) => {
14
+ await TcaClient.initConfig(mcpConfigFile);
15
+ try {
16
+ const res = await startScan(incrScan, forceCreate);
17
+ return formatTextToolResult(JSON.stringify(res, null, 2), 'start tca scan');
18
+ }
19
+ catch (error) {
20
+ return formatToolError(error, 'start tca scan error!');
21
+ }
22
+ });
23
+ }
@@ -0,0 +1,21 @@
1
+ export function formatTextToolResult(text, toolName) {
2
+ return {
3
+ content: [
4
+ {
5
+ type: 'text',
6
+ text
7
+ }
8
+ ]
9
+ };
10
+ }
11
+ export function formatToolError(error, toolName) {
12
+ return {
13
+ content: [
14
+ {
15
+ type: 'text',
16
+ text: `Error ${toolName}:\n${error instanceof Error ? error.message : String(error)}`
17
+ }
18
+ ],
19
+ isError: true
20
+ };
21
+ }
Binary file
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tca-mcp-server",
3
- "version": "1.0.2",
3
+ "version": "1.2.0",
4
4
  "description": "",
5
5
  "main": "./dist/stdio.js",
6
6
  "scripts": {
@@ -15,11 +15,13 @@
15
15
  "author": "",
16
16
  "license": "ISC",
17
17
  "devDependencies": {
18
+ "@types/ini": "^4.1.1",
18
19
  "@types/node": "^24.0.1",
19
20
  "ts-node": "^10.9.2",
20
21
  "typescript": "^5.8.3"
21
22
  },
22
23
  "dependencies": {
23
- "@modelcontextprotocol/sdk": "^1.12.1"
24
+ "@modelcontextprotocol/sdk": "^1.12.1",
25
+ "ini": "^5.0.0"
24
26
  }
25
27
  }
package/src/api/client.ts CHANGED
@@ -1,11 +1,23 @@
1
1
  import { createHash } from 'crypto';
2
+ import * as ini from 'ini';
3
+ import * as fs from 'fs';
4
+
5
+ export const mainUrlOA = "http://server.codedog.woa.com/main/api/"
6
+ export const analysisUrlOA = "http://server.codedog.woa.com/analysis/api/"
7
+ export const webUrlOA = "https://codedog.woa.com/"
8
+
9
+ export interface CommonParams {
10
+ orgSid: string;
11
+ teamName: string;
12
+ repoId?: string;
13
+ projectId?: string;
14
+ }
2
15
 
3
16
  export interface TcaClientOptions {
4
17
  token: string;
5
18
  userName: string;
6
- orgSid: string;
7
- projectName: string;
8
- baseUrl?: string;
19
+ baseUrl: string;
20
+ env: string;
9
21
  }
10
22
 
11
23
  export interface TcaClientHeaders {
@@ -20,21 +32,31 @@ type RequestOptions = {
20
32
  body?: any;
21
33
  }
22
34
 
35
+ export interface OACommonParams {
36
+ projectId?: string;
37
+ repoId?: string;
38
+ }
39
+
23
40
  export default class TcaClient {
24
41
  private static instance: TcaClient | null = null;
42
+ private static commonParams: CommonParams | null = null;
43
+ private static commonOAParams: OACommonParams | null = null;
25
44
 
26
45
  private token: string;
27
46
  private userName: string;
28
- private baseUrl: string;
47
+ public baseUrl: string;
48
+ public env: string;
29
49
 
30
50
  constructor(options: TcaClientOptions) {
31
51
  this.token = options.token;
32
52
  this.userName = options.userName;
33
- this.baseUrl = options.baseUrl || "https://tca.tencent.com/";
53
+ this.baseUrl = options.baseUrl;
54
+ this.env = options.env;
34
55
  }
56
+
35
57
  static initTcaClient(options: TcaClientOptions) {
36
58
  if (!TcaClient.instance) {
37
- const client = new TcaClient(options);
59
+ TcaClient.instance = new TcaClient(options);
38
60
  }
39
61
  }
40
62
  static getInstance(): TcaClient {
@@ -45,6 +67,45 @@ export default class TcaClient {
45
67
  }
46
68
  }
47
69
 
70
+ static async initConfig(configPath: string) {
71
+ // 解析 ini 配置文件
72
+ const data = await fs.promises.readFile(configPath, 'utf-8');
73
+ const config = ini.parse(data);
74
+ const configSection = config.config || {};
75
+
76
+ if (TcaClient.instance?.runInOA()) {
77
+ TcaClient.commonOAParams = {
78
+ projectId: configSection.project_id,
79
+ repoId: configSection.repo_id
80
+ }
81
+ } else {
82
+ TcaClient.commonParams = {
83
+ orgSid: configSection.org_sid,
84
+ teamName: configSection.team_name,
85
+ repoId: configSection.repo_id,
86
+ projectId: configSection.project_id,
87
+ }
88
+ }
89
+ return
90
+ }
91
+ static getcommonParams(): CommonParams{
92
+ if (!TcaClient.commonParams) {
93
+ throw new Error("common params is not initialized");
94
+ }
95
+ return TcaClient.commonParams;
96
+ }
97
+
98
+ static getcommonOAParams(): OACommonParams {
99
+ if (!TcaClient.commonOAParams) {
100
+ throw new Error("common OA params is not initialized");
101
+ }
102
+ return TcaClient.commonOAParams;
103
+ }
104
+
105
+ runInOA(): boolean {
106
+ return this.env === "oa";
107
+ }
108
+
48
109
  getHeader(): TcaClientHeaders {
49
110
  const timestamp = Math.floor(new Date().getTime() / 1000);
50
111
  const tokenSig = `${timestamp}${this.userName}#${this.token}#${this.userName}${timestamp}`;
@@ -55,28 +116,73 @@ export default class TcaClient {
55
116
  "TCA-TIMESTAMP": timestamp.toString(),
56
117
  }
57
118
  }
119
+ buildUrlWithNestedParams(url: string, params: any): string {
120
+ const paramsObj = new URLSearchParams();
121
+
122
+ // 递归处理嵌套对象
123
+ function addParams(prefix: string, value: any) {
124
+ if (value === undefined || value === null) {
125
+ return;
126
+ }
127
+
128
+ if (typeof value === 'object' && !Array.isArray(value)) {
129
+ Object.entries(value).forEach(([key, val]) => {
130
+ addParams(`${prefix}[${key}]`, val);
131
+ });
132
+ } else if (Array.isArray(value)) {
133
+ value.forEach((item, index) => {
134
+ addParams(`${prefix}[${index}]`, item);
135
+ });
136
+ } else {
137
+ paramsObj.append(prefix, String(value));
138
+ }
139
+ }
140
+
141
+ Object.entries(params).forEach(([key, value]) => {
142
+ addParams(key, value);
143
+ });
144
+
145
+ return paramsObj.toString() ? `${url}?${paramsObj}` : url;
146
+ }
147
+
58
148
 
59
- async request<T>(path: string, method: string, data?: any): Promise<T> {
60
- const norBaseUrl = this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`;
149
+ async request<T>(path: string, method: string, data?: any, params?: any, baseUrl?: any): Promise<T> {
150
+ if (!baseUrl) {
151
+ baseUrl = this.baseUrl;
152
+ }
153
+ const norBaseUrl = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
61
154
  const norPath = path.startsWith("/") ? path.slice(1) : path;
62
155
  const url = new URL(norPath, norBaseUrl);
63
- const header = this.getHeader();
64
- const options: RequestOptions = {
65
- method,
66
- headers: {
156
+
157
+ var headers = {};
158
+ if (TcaClient.getInstance().runInOA()) {
159
+ headers = {
160
+ "Content-Type": "application/json",
161
+ "Authorization": `Token ${this.token}`,
162
+ }
163
+ } else {
164
+ let header = this.getHeader();
165
+ headers = {
67
166
  "Content-Type": "application/json",
68
167
  "TCA-USERID": header['TCA-USERID'],
69
168
  "TCA-TICKET": header['TCA-TICKET'],
70
169
  "TCA-TIMESTAMP": header['TCA-TIMESTAMP']
71
- },
170
+ }
171
+ }
172
+ const options: RequestOptions = {
173
+ method,
174
+ headers: headers,
72
175
  };
73
176
  if (data) {
74
177
  options.body = JSON.stringify(data);
75
178
  }
179
+ if (params) {
180
+ url.search = this.buildUrlWithNestedParams(url.search, params);
181
+ }
76
182
  try {
77
183
  const response = await fetch(url, options)
78
184
  if (!response.ok) {
79
- throw new Error(`Request failed with status ${response.status}`);
185
+ throw new Error(`Request failed with status ${response.status}, ${options.body}, ${options.headers}, ${options.method}, ${url}`);
80
186
  }
81
187
  const json = await response.json();
82
188
  return json as T;
@@ -0,0 +1,29 @@
1
+ import TcaClient, { analysisUrlOA } from "./client.js"
2
+
3
+
4
+ export async function getIssueList(state: string, severity: string, offset: number, limit: number) {
5
+ const method = "GET"
6
+ const clientInst = TcaClient.getInstance()
7
+
8
+ if (clientInst.runInOA()) {
9
+ const pathParamsOA = TcaClient.getcommonOAParams()
10
+ const path = `/projects/${pathParamsOA.projectId}/codelint/issues/`
11
+ const params = {
12
+ state: state,
13
+ severity: severity,
14
+ offset: offset,
15
+ limit: limit
16
+ }
17
+ return clientInst.request(path, method, null, params, analysisUrlOA)
18
+ } else {
19
+ const pathParams = TcaClient.getcommonParams()
20
+ const path = `/server/analysis/api/orgs/${pathParams.orgSid}/teams/${pathParams.teamName}/repos/${pathParams.repoId}/projects/${pathParams.projectId}/codelint/issues/`
21
+ const params = {
22
+ state: state,
23
+ severity: severity,
24
+ offset: offset,
25
+ limit: limit
26
+ }
27
+ return clientInst.request(path, method, null, params)
28
+ }
29
+ }
package/src/api/job.ts ADDED
@@ -0,0 +1,39 @@
1
+ import TcaClient, { mainUrlOA } from "./client.js"
2
+
3
+ export async function getJobList(offset: number, limit: number, state: string) {
4
+ const method = "GET"
5
+ const clientInst = TcaClient.getInstance()
6
+ if (clientInst.runInOA()) {
7
+ const pathParamsOA = TcaClient.getcommonOAParams()
8
+ const path = `/projects/${pathParamsOA.projectId}/jobs/`
9
+ const params = {
10
+ "offset": offset,
11
+ "limit": limit,
12
+ "state": state,
13
+ }
14
+ return clientInst.request(path, method, null, params, mainUrlOA)
15
+ } else {
16
+ const pathParams = TcaClient.getcommonParams()
17
+ const path = `/server/main/api/orgs/${pathParams.orgSid}/teams/${pathParams.teamName}/repos/${pathParams.repoId}/projects/${pathParams.projectId}/jobs/`
18
+ const params = {
19
+ "offset": offset,
20
+ "limit": limit,
21
+ "state": state,
22
+ }
23
+ return clientInst.request(path, method, null, params)
24
+ }
25
+ }
26
+
27
+ export async function getJobDetail(jobId: string) {
28
+ const method = "GET"
29
+ const clientInst = TcaClient.getInstance()
30
+ if (clientInst.runInOA()) {
31
+ const pathParamsOA = TcaClient.getcommonOAParams()
32
+ const path = `/projects/${pathParamsOA.projectId}/jobs/${jobId}/detail/`
33
+ return clientInst.request(path, method, null, null, mainUrlOA)
34
+ } else {
35
+ const pathParams = TcaClient.getcommonParams()
36
+ const path = `/server/main/api/orgs/${pathParams.orgSid}/teams/${pathParams.teamName}/repos/${pathParams.repoId}/projects/${pathParams.projectId}/jobs/${jobId}/detail/`
37
+ return clientInst.request(path, method, null, null)
38
+ }
39
+ }
@@ -0,0 +1,39 @@
1
+ import TcaClient, { CommonParams, mainUrlOA, analysisUrlOA } from "./client.js"
2
+
3
+ export interface startScanParams {
4
+ "token": string,
5
+ "userName": string,
6
+ "orgSid": string,
7
+ "projectName": string,
8
+ "scanName": string,
9
+ }
10
+
11
+ export interface startScanData {
12
+ "incr_scan": boolean,
13
+ "created_from": string,
14
+ "force_create": boolean,
15
+ }
16
+
17
+ export async function startScan(incr_scan: boolean, force_create: boolean) {
18
+ const method = "POST"
19
+ const clientInst = TcaClient.getInstance()
20
+ if (clientInst.runInOA()) {
21
+ const pathParamsOA = TcaClient.getcommonOAParams()
22
+ const path = `/projects/${pathParamsOA.projectId}/scans/create/`
23
+ const data = {
24
+ "incr_scan": incr_scan,
25
+ "created_from": "tca-mcp-server",
26
+ "force_create": force_create,
27
+ } as startScanData
28
+ return clientInst.request(path, method, data, null, mainUrlOA)
29
+ } else {
30
+ const pathParams = TcaClient.getcommonParams()
31
+ const path = `/server/main/api/orgs/${pathParams.orgSid}/teams/${pathParams.teamName}/repos/${pathParams.repoId}/projects/${pathParams.projectId}/scans/create/`
32
+ const data = {
33
+ "incr_scan": incr_scan,
34
+ "created_from": "tca-mcp-server",
35
+ "force_create": force_create,
36
+ } as startScanData
37
+ return clientInst.request(path, method, data)
38
+ }
39
+ }
package/src/stdio.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
4
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
- import { z } from "zod";
5
+ import { registerTools } from "./tools/index.js";
6
6
 
7
7
  // Create an MCP server
8
8
  const server = new McpServer({
@@ -10,27 +10,11 @@ const server = new McpServer({
10
10
  version: "1.0.0"
11
11
  });
12
12
 
13
- // Add an addition tool
14
- server.tool("add",
15
- { a: z.number(), b: z.number() },
16
- async ({ a, b }) => ({
17
- content: [{ type: "text", text: String(a + b) }]
18
- })
19
- );
13
+ // 从命令行获取参数,如果 --oa参数存在,则表示运行在OA环境,否则运行在SaaS环境
14
+ const env = process.argv.includes("--oa") ? "oa" : "saas";
15
+ process.env.TCA_ENV = env;
20
16
 
21
- // Add a dynamic greeting resource
22
- server.resource(
23
- "greeting",
24
- new ResourceTemplate("greeting://{name}", { list: undefined }),
25
- async (uri, { name }) => ({
26
- contents: [{
27
- uri: uri.href,
28
- text: `Hello, ${name}!`
29
- }]
30
- })
31
- );
32
-
33
- // Start receiving messages on stdin and sending messages on stdout
17
+ registerTools(server);
34
18
  const transport = new StdioServerTransport();
35
19
  await server.connect(transport);
36
- console.log("Server started");
20
+ console.log("Server started");
@@ -0,0 +1,17 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import TcaClient from "../api/client.js";
3
+ import registerStartScan from "./scan.js";
4
+ import registerJob from "./job.js";
5
+ import registerIssueReport from "./issue.js";
6
+
7
+ export function registerTools(server: McpServer) {
8
+ TcaClient.initTcaClient({
9
+ "token": process.env.TCA_TOKEN || "",
10
+ "userName": process.env.TCA_USER_NAME || "",
11
+ "baseUrl": process.env.TCA_BASE_URL || "https://tca.tencent.com/",
12
+ "env": process.env.TCA_ENV || "saas"
13
+ })
14
+ registerStartScan(server);
15
+ registerJob(server);
16
+ registerIssueReport(server);
17
+ }
@@ -0,0 +1,60 @@
1
+ import { z } from "zod";
2
+ import TcaClient, { webUrlOA } from "../api/client.js";
3
+ import { format } from "path";
4
+ import { formatTextToolResult, formatToolError } from "../utils/resp.js";
5
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
+ import { getIssueList } from "../api/issue.js";
7
+
8
+ export default function registerIssueReport(server: McpServer) {
9
+ server.tool(
10
+ 'tca_issue_list',
11
+ `获取当前项目未解决的问题列表, 参数如下:
12
+ -mcpConfigFile: mcp配置文件路径,当前项目根目录查找 tca-mcp.ini(绝对路径)
13
+ -state: 查询任务状态, (0为等待中,1为执行中,2为关闭,3为入库中,4正在初始化, 5初始化完成 可多选,格式为1,2,3)
14
+ -severity: 问题严重程度, (0为低,1为中,2为高)
15
+ -offset: 偏移量
16
+ -limit: 限制数量
17
+ `,
18
+ {
19
+ mcpConfigFile: z.string(),
20
+ state: z.string().default("0,1,2,3,4,5"),
21
+ severity: z.string().default("0,1,2"),
22
+ offset: z.number().default(0),
23
+ limit: z.number().default(10),
24
+ },
25
+ async ({ mcpConfigFile, state, severity, offset, limit }) => {
26
+ await TcaClient.initConfig(mcpConfigFile)
27
+ try {
28
+ const res = await getIssueList(state, severity, offset, limit)
29
+ return formatTextToolResult(JSON.stringify(res, null, 2), 'get issue list');
30
+ } catch (error) {
31
+ return formatToolError(error, 'get issue list error')
32
+ }
33
+ }
34
+ )
35
+
36
+ server.tool(
37
+ 'tca_issue_report',
38
+ `tca报告链接, 参数如下:
39
+ -mcpConfigFile: mcp配置文件路径,当前项目根目录查找 tca-mcp.ini(绝对路径)
40
+ -jobId: 任务id, 启动分析任务后,从返回结果中获取`,
41
+ {
42
+ mcpConfigFile: z.string(),
43
+ jobId: z.string().describe("jobId"),
44
+ },
45
+ async ({ mcpConfigFile, jobId }) => {
46
+ await TcaClient.initConfig(mcpConfigFile)
47
+ const clientInst = TcaClient.getInstance()
48
+ if (clientInst.runInOA()) {
49
+ const paramsOA = TcaClient.getcommonOAParams()
50
+ const path = `code-analysis/repos/${paramsOA.repoId}/projects/${paramsOA.projectId}/scan-history/${jobId}/result`
51
+ return formatTextToolResult(webUrlOA + path, 'tca issue report link')
52
+ } else {
53
+ const baseUrl = TcaClient.getInstance().baseUrl
54
+ const params = TcaClient.getcommonParams()
55
+ const path = `t/${params.orgSid}/p/${params.teamName}/repos/${params.repoId}/projects/${params.projectId}/jobs/${jobId}/result`
56
+ return formatTextToolResult(baseUrl + path, 'tca issue report link');
57
+ }
58
+ }
59
+ );
60
+ }
@@ -0,0 +1,51 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import TcaClient from "../api/client.js";
4
+ import { formatTextToolResult, formatToolError } from "../utils/resp.js";
5
+ import { getJobDetail, getJobList } from "../api/job.js";
6
+
7
+ export default function registerJob(server: McpServer) {
8
+ server.tool(
9
+ 'job_detail',
10
+ `获取任务详情, 参数如下:
11
+ -mcpConfigFile: mcp配置文件路径,当前项目根目录查找 tca-mcp.ini(绝对路径)
12
+ -jobId: 任务id`,
13
+ {
14
+ mcpConfigFile: z.string(),
15
+ jobId: z.string().describe("任务id")
16
+ },
17
+ async ({ mcpConfigFile, jobId }) => {
18
+ await TcaClient.initConfig(mcpConfigFile)
19
+ try {
20
+ const res = await getJobDetail(jobId)
21
+ return formatTextToolResult(JSON.stringify(res, null, 2), 'get job detail');
22
+ } catch (error) {
23
+ return formatToolError(error, 'get job detail error!')
24
+ }
25
+ }
26
+ )
27
+
28
+ server.tool(
29
+ 'job_list',
30
+ `获取当前任务列表, 参数如下:
31
+ -mcpConfigFile: mcp配置文件路径,当前项目根目录查找 tca-mcp.ini(绝对路径)
32
+ -offset: 偏移量
33
+ -limit: 限制数量
34
+ -state: 查询任务状态, (0为等待中,1为执行中,2为关闭,3为入库中,4正在初始化, 5初始化完成 可多选,格式为1,2,3)`,
35
+ {
36
+ mcpConfigFile: z.string(),
37
+ offset: z.number().default(0).describe("偏移量"),
38
+ limit: z.number().default(10).describe("限制数量"),
39
+ state: z.string().default("0,1,2,3,4,5").describe("查询任务状态, (0为等待中,1为执行中,2为关闭,3为入库中,4正在初始化, 5初始化完成 可多选,格式为1,2,3)")
40
+ },
41
+ async ({ mcpConfigFile, offset, limit, state }) => {
42
+ await TcaClient.initConfig(mcpConfigFile)
43
+ try {
44
+ const res = await getJobList(offset, limit, state)
45
+ return formatTextToolResult(JSON.stringify(res, null, 2), 'get job list');
46
+ } catch (error) {
47
+ return formatToolError(error, 'get job list error!')
48
+ }
49
+ }
50
+ )
51
+ }
package/src/tools/scan.ts CHANGED
@@ -0,0 +1,30 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { startScan } from "../api/scan.js";
4
+ import { formatTextToolResult, formatToolError } from "../utils/resp.js";
5
+ import TcaClient from "../api/client.js";
6
+
7
+
8
+ export default function registerStartScan(server: McpServer) {
9
+ server.tool(
10
+ 'start_scan',
11
+ `启动tca代码分析 参数如下:
12
+ -mcpConfigFile: mcp配置文件路径,当前项目根目录查找 tca-mcp.ini(绝对路径)
13
+ -incrScan: 是否增量扫描
14
+ -forceCreate: 如果已经存在扫描结果,是否强制启动新扫描`,
15
+ {
16
+ mcpConfigFile: z.string(),
17
+ incrScan: z.boolean().default(false).describe("是否增量扫描"),
18
+ forceCreate: z.boolean().default(false).describe("如果已经存在扫描结果,是否强制启动新扫描"),
19
+ },
20
+ async ({ mcpConfigFile, incrScan, forceCreate }) => {
21
+ await TcaClient.initConfig(mcpConfigFile)
22
+ try {
23
+ const res = await startScan(incrScan, forceCreate)
24
+ return formatTextToolResult(JSON.stringify(res, null, 2), 'start tca scan');
25
+ } catch (error) {
26
+ return formatToolError(error, 'start tca scan error!')
27
+ }
28
+ }
29
+ );
30
+ }
@@ -0,0 +1,24 @@
1
+ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
2
+
3
+ export function formatTextToolResult(text: string, toolName: string): CallToolResult {
4
+ return {
5
+ content: [
6
+ {
7
+ type: 'text',
8
+ text
9
+ }
10
+ ]
11
+ };
12
+ }
13
+
14
+ export function formatToolError(error: unknown, toolName: string): CallToolResult {
15
+ return {
16
+ content: [
17
+ {
18
+ type: 'text',
19
+ text: `Error ${toolName}:\n${error instanceof Error ? error.message : String(error)}`
20
+ }
21
+ ],
22
+ isError: true
23
+ };
24
+ }
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1,2 +0,0 @@
1
- import TcaClient from "./client.js";
2
-
File without changes
package/src/tools/repo.ts DELETED
File without changes
Binary file