cloudcc-cli 2.2.6 → 2.2.8

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.
Files changed (122) hide show
  1. package/.cloudcc-cache.json +24 -20
  2. package/README.md +24 -0
  3. package/bin/cc.js +7 -0
  4. package/cloudcc-cli-dev/BACKEND_CODE.md +114 -0
  5. package/cloudcc-cli-dev/CLI_CHEATSHEET.md +90 -0
  6. package/cloudcc-cli-dev/INSTALL_AND_BOOTSTRAP.md +59 -0
  7. package/cloudcc-cli-dev/OBJECTS_AND_FIELDS.md +120 -0
  8. package/cloudcc-cli-dev/REQUIREMENTS_BREAKDOWN.md +98 -0
  9. package/cloudcc-cli-dev/SKILL.md +39 -0
  10. package/cloudcc-cli-dev/VUE_CUSTOM_COMPONENT.md +50 -0
  11. package/java/com/cloudcc/core/BaseException.java +100 -0
  12. package/java/com/cloudcc/core/BusiException.java +43 -0
  13. package/java/com/cloudcc/core/CCService.java +3 -1
  14. package/java/com/cloudcc/core/StringUtils.java +7 -0
  15. package/java/com/cloudcc/core/TimeUtil.java +33 -0
  16. package/java/com/cloudcc/core/UserInfo.java +9 -0
  17. package/package.json +7 -1
  18. package/pom.xml +1 -1
  19. package/src/api/backend-sdk-java.md +427 -0
  20. package/src/api/ccdk-sdk.md +1039 -0
  21. package/src/classes/doc.js +486 -0
  22. package/src/classes/index.js +1 -0
  23. package/src/mcp/cliRunner.js +61 -0
  24. package/src/mcp/index.js +41 -3
  25. package/src/mcp/tools/Application Creator/handler.js +7 -9
  26. package/src/mcp/tools/Approval/handler.js +34 -151
  27. package/src/mcp/tools/Class Creator/handler.js +18 -15
  28. package/src/mcp/tools/Class Detail Retriever/handler.js +8 -9
  29. package/src/mcp/tools/Class Editor Guide/handler.js +5 -19
  30. package/src/mcp/tools/Class List Retriever/handler.js +8 -3
  31. package/src/mcp/tools/Class Publisher/handler.js +7 -9
  32. package/src/mcp/tools/Class Puller/handler.js +6 -65
  33. package/src/mcp/tools/Client Script Detail Retriever/handler.js +12 -18
  34. package/src/mcp/tools/Client Script Editor Guide/handler.js +9 -605
  35. package/src/mcp/tools/Client Script List Retriever/handler.js +30 -33
  36. package/src/mcp/tools/Client Script Publisher/handler.js +12 -11
  37. package/src/mcp/tools/Client Script Puller/handler.js +23 -30
  38. package/src/mcp/tools/CloudCC Development Overview/handler.js +11 -5
  39. package/src/mcp/tools/Component Creator/handler.js +12 -11
  40. package/src/mcp/tools/Component Detail Retriever/handler.js +12 -9
  41. package/src/mcp/tools/Component Editor Guide/handler.js +5 -22
  42. package/src/mcp/tools/Component List Retriever/handler.js +21 -18
  43. package/src/mcp/tools/Component Publisher/handler.js +25 -3
  44. package/src/mcp/tools/Component Puller/handler.js +13 -16
  45. package/src/mcp/tools/Dev Environment Creator/handler.js +5 -72
  46. package/src/mcp/tools/Dev Environment Validator/handler.js +5 -66
  47. package/src/mcp/tools/Developer Key Setup Guide/handler.js +11 -20
  48. package/src/mcp/tools/JSP Migrator/handler.js +842 -0
  49. package/src/mcp/tools/Menu Creator/handler.js +7 -30
  50. package/src/mcp/tools/Object Creator/handler.js +14 -6
  51. package/src/mcp/tools/Object Fields Creator/handler.js +9 -10
  52. package/src/mcp/tools/Object Fields Retriever/handler.js +6 -3
  53. package/src/mcp/tools/Object List Retriever/handler.js +10 -7
  54. package/src/mcp/tools/Scheduled Class Creator/handler.js +12 -16
  55. package/src/mcp/tools/Scheduled Class Detail Retriever/handler.js +7 -9
  56. package/src/mcp/tools/Scheduled Class List Retriever/handler.js +21 -23
  57. package/src/mcp/tools/Scheduled Class Publisher/handler.js +7 -9
  58. package/src/mcp/tools/Scheduled Class Puller/handler.js +6 -70
  59. package/src/mcp/tools/Trigger Creator/handler.js +12 -20
  60. package/src/mcp/tools/Trigger Detail Retriever/handler.js +7 -9
  61. package/src/mcp/tools/Trigger Editor Guide/handler.js +10 -35
  62. package/src/mcp/tools/Trigger List Retriever/handler.js +12 -4
  63. package/src/mcp/tools/Trigger Publisher/handler.js +8 -11
  64. package/src/mcp/tools/Trigger Puller/handler.js +12 -17
  65. package/src/plugin/doc.js +801 -0
  66. package/src/plugin/get.js +0 -1
  67. package/src/plugin/index.js +1 -0
  68. package/src/plugin/publish1.js +34 -20
  69. package/src/plugin/pull.js +69 -31
  70. package/src/project/doc.js +378 -0
  71. package/src/project/index.js +1 -0
  72. package/src/script/doc.js +259 -0
  73. package/src/script/index.js +1 -0
  74. package/src/timer/index.js +1 -0
  75. package/src/triggers/doc.js +342 -0
  76. package/src/triggers/index.js +5 -0
  77. package/target/ccopenapi-0.0.4-classes.jar +0 -0
  78. package/target/ccopenapi-0.0.4.jar +0 -0
  79. package/target/classes/com/cloudcc/core/BaseException.class +0 -0
  80. package/target/classes/com/cloudcc/core/BusiException.class +0 -0
  81. package/target/classes/com/cloudcc/core/CCService.class +0 -0
  82. package/target/classes/com/cloudcc/core/StringUtils.class +0 -0
  83. package/target/classes/com/cloudcc/core/TimeUtil.class +0 -0
  84. package/target/classes/com/cloudcc/core/UserInfo.class +0 -0
  85. package/target/maven-archiver/pom.properties +1 -1
  86. package/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +3 -1
  87. package/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +3 -3
  88. package/template/lib/ccopenapi-0.0.4.jar +0 -0
  89. package/test/application.cli.test.js +30 -0
  90. package/test/classes.cli.test.js +121 -0
  91. package/test/fields.cli.test.js +69 -0
  92. package/test/mcp.cli.test.js +21 -0
  93. package/test/menu.cli.test.js +41 -0
  94. package/test/object.cli.test.js +64 -0
  95. package/test/plugin.cli.test.js +109 -0
  96. package/test/script.cli.test.js +101 -0
  97. package/test/timer.cli.test.js +107 -0
  98. package/test/trigger.cli.test.js +146 -0
  99. package/.vscode/settings.json +0 -3
  100. package/bin/mcp-svc.js +0 -13
  101. package/src/mcp/MCP/345/234/272/346/231/257/346/250/241/346/213/237.md +0 -8
  102. package/src/mcp/index-sse-svc.js +0 -126
  103. package/src/mcp/index-streamable-svc.js +0 -180
  104. package/src/mcp/tools/Class Detail Retriever/prompt.js +0 -37
  105. package/src/mcp/tools/Class Editor Guide/prompt.js +0 -468
  106. package/src/mcp/tools/Class Publisher/prompt.js +0 -40
  107. package/src/mcp/tools/Class Puller/prompt.js +0 -49
  108. package/src/mcp/tools/Client Script Creator/handler.js +0 -179
  109. package/src/mcp/tools/CloudCC Development Overview/prompt.js +0 -871
  110. package/src/mcp/tools/Component Editor Guide/prompt.js +0 -519
  111. package/src/mcp/tools/Component Publisher/prompt.js +0 -659
  112. package/src/mcp/tools/Dev Environment Creator/prompt.js +0 -273
  113. package/src/mcp/tools/Dev Environment Validator/prompt.js +0 -193
  114. package/src/mcp/tools/Developer Key Setup Guide/prompt.js +0 -71
  115. package/src/mcp/tools/Object Fields Retriever/prompt.js +0 -10
  116. package/src/mcp/tools/Object List Retriever/prompt.js +0 -10
  117. package/src/mcp/tools/ccdk/fetcher.js +0 -18
  118. package/src/mcp/tools/ccdk/handler.js +0 -98
  119. package/src/mcp/tools/ccdk/prompt.js +0 -453
  120. package/target/ccopenapi-0.0.3-classes.jar +0 -0
  121. package/target/ccopenapi-0.0.3.jar +0 -0
  122. package/template/lib/ccopenapi-0.0.3.jar +0 -0
@@ -0,0 +1,107 @@
1
+ const test = require("node:test");
2
+ const assert = require("node:assert/strict");
3
+ const fs = require("node:fs");
4
+ const path = require("node:path");
5
+ const { exec } = require("node:child_process");
6
+ const { promisify } = require("node:util");
7
+
8
+ const execAsync = promisify(exec);
9
+ const repoRoot = path.resolve(__dirname, "..");
10
+ const scheduleRoot = path.join(repoRoot, "schedule");
11
+
12
+ function quoteArg(value) {
13
+ return `'${String(value).replace(/'/g, `'\\''`)}'`;
14
+ }
15
+
16
+ async function runCc(args) {
17
+ const cmd = `npx --prefix "${repoRoot}" cc ${args.map(quoteArg).join(" ")}`;
18
+ return execAsync(cmd, { cwd: repoRoot });
19
+ }
20
+
21
+ test("定时任务管理流程:创建→发布→拉取→线上详情→列表→批量拉取前2个→清空 schedule 文件夹", async (t) => {
22
+ const timerName = `CCTimer${Date.now()}`;
23
+ const timerDir = path.join(scheduleRoot, timerName);
24
+ const configPath = path.join(timerDir, "config.json");
25
+ const javaPath = path.join(timerDir, `${timerName}.java`);
26
+ let publishedId = null;
27
+ let topIds = [];
28
+
29
+ t.test("1) 创建定时任务", async () => {
30
+ await runCc(["create", "timer", timerName]);
31
+
32
+ assert.equal(fs.existsSync(timerDir), true);
33
+ assert.equal(fs.existsSync(configPath), true);
34
+ assert.equal(fs.existsSync(javaPath), true);
35
+
36
+ const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
37
+ assert.equal(config.name, timerName);
38
+ });
39
+
40
+ t.test("2) 发布定时任务(publish,写回 config.id)", async () => {
41
+ await runCc(["publish", "timer", timerName]);
42
+ const configAfterPublish = JSON.parse(fs.readFileSync(configPath, "utf8"));
43
+ publishedId = configAfterPublish.id || null;
44
+ assert.ok(publishedId, "publish 后应写入 config.id");
45
+ });
46
+
47
+ t.test("3) 拉取定时任务(pull:覆盖 SOURCE 区域)", async () => {
48
+ const localJava = fs.readFileSync(javaPath, "utf8");
49
+ const modifiedJava = localJava.replace(
50
+ /\/\/ @SOURCE_CONTENT_START[\s\S]*?\/\/ @SOURCE_CONTENT_END/,
51
+ "// @SOURCE_CONTENT_START\nSystem.out.print(\"LOCAL_ONLY\");\n// @SOURCE_CONTENT_END"
52
+ );
53
+ fs.writeFileSync(javaPath, modifiedJava, "utf8");
54
+
55
+ await runCc(["pull", "timer", timerName]);
56
+ const javaAfterPull = fs.readFileSync(javaPath, "utf8");
57
+ assert.ok(!javaAfterPull.includes("LOCAL_ONLY"), "pull 后应使用远端源码替换本地标记内容");
58
+ });
59
+
60
+ t.test("4) 查询线上定时任务详情(detail:仅使用 id)", async () => {
61
+ assert.ok(publishedId, "发布后应有 publishedId");
62
+ await assert.doesNotReject(() => runCc(["detail", "timer", "", publishedId]));
63
+ });
64
+
65
+ t.test("5) get 获取线上定时任务列表(取前2个 id)", async () => {
66
+ const listQuery = encodeURI(
67
+ JSON.stringify({
68
+ shownum: "2000",
69
+ showpage: "1",
70
+ fid: "",
71
+ sname: "",
72
+ rptcond: "",
73
+ rptorder: "",
74
+ })
75
+ );
76
+ const { stdout } = await runCc(["get", "timer", listQuery, repoRoot]);
77
+ const list = JSON.parse(stdout.trim());
78
+ assert.ok(Array.isArray(list), "get timer 应返回数组");
79
+ topIds = list.slice(0, 2).map((x) => x.id).filter(Boolean);
80
+ assert.ok(topIds.length >= 1, "线上定时任务列表至少应包含 1 个 id");
81
+ });
82
+
83
+ t.test("6) 批量拉取前2个定时任务(pullList)", async () => {
84
+ assert.ok(topIds.length >= 1, "pullList 需要至少 1 个线上定时任务 id");
85
+ const beforeCount = fs.existsSync(scheduleRoot)
86
+ ? fs.readdirSync(scheduleRoot).filter((n) => fs.statSync(path.join(scheduleRoot, n)).isDirectory()).length
87
+ : 0;
88
+
89
+ for (const id of topIds) {
90
+ await runCc(["pullList", "timer", id, repoRoot]);
91
+ }
92
+
93
+ const afterCount = fs.readdirSync(scheduleRoot).filter((n) =>
94
+ fs.statSync(path.join(scheduleRoot, n)).isDirectory()
95
+ ).length;
96
+ assert.ok(afterCount >= beforeCount, "pullList 之后 schedule 目录应存在/可用");
97
+ });
98
+
99
+ t.test("7) 清空 schedule 文件夹", async () => {
100
+ if (fs.existsSync(scheduleRoot)) {
101
+ fs.rmSync(scheduleRoot, { recursive: true, force: true });
102
+ }
103
+ fs.mkdirSync(scheduleRoot, { recursive: true });
104
+ const remain = fs.readdirSync(scheduleRoot);
105
+ assert.equal(remain.length, 0);
106
+ });
107
+ });
@@ -0,0 +1,146 @@
1
+ const test = require("node:test");
2
+ const assert = require("node:assert/strict");
3
+ const fs = require("node:fs");
4
+ const path = require("node:path");
5
+ const { exec } = require("node:child_process");
6
+ const { promisify } = require("node:util");
7
+
8
+ const execAsync = promisify(exec);
9
+ const repoRoot = path.resolve(__dirname, "..");
10
+ const triggersRoot = path.join(repoRoot, "triggers");
11
+
12
+ function quoteArg(value) {
13
+ return `'${String(value).replace(/'/g, `'\\''`)}'`;
14
+ }
15
+
16
+ async function runCc(args) {
17
+ const cmd = `npx --prefix "${repoRoot}" cc ${args.map(quoteArg).join(" ")}`;
18
+ return execAsync(cmd, { cwd: repoRoot });
19
+ }
20
+
21
+ test("触发器管理流程:获取对象→创建→doc→发布→拉取→线上详情→列表→批量拉取前2个→清空 triggers 文件夹", async (t) => {
22
+ let schemetableName = "Account";
23
+ let targetObjectId = null;
24
+ let apiname = null;
25
+ const triggerTime = "beforeInsert";
26
+ const triggerName = `TestTrigger${Date.now()}`;
27
+ const namePath = `${schemetableName.toLowerCase()}/${triggerName}`;
28
+ const triggerDir = path.join(triggersRoot, schemetableName.toLowerCase(), triggerName);
29
+ const configPath = path.join(triggerDir, "config.json");
30
+ const javaPath = path.join(triggerDir, `${triggerName}.java`);
31
+ let topIds = [];
32
+
33
+ t.test("1) 获取自定义对象列表(取 schemetableName 用于创建触发器)", async () => {
34
+ const { stdout } = await runCc(["get", "object", repoRoot, "custom"]);
35
+ const list = JSON.parse(stdout.trim());
36
+ assert.ok(Array.isArray(list) && list.length > 0, "get object custom 应返回非空数组");
37
+ const first = list[0];
38
+ schemetableName = first.schemetableName || first.label || "Account";
39
+ targetObjectId = first.id || null;
40
+ apiname = `NTrigger${Date.now()}`;
41
+ assert.ok(targetObjectId, "应能拿到自定义对象 id 作为 targetObjectId");
42
+ });
43
+
44
+ t.test("2) 创建触发器", async () => {
45
+ const body = encodeURI(
46
+ JSON.stringify({
47
+ schemetableName,
48
+ targetObjectId,
49
+ triggerTime,
50
+ name: triggerName,
51
+ apiname,
52
+ })
53
+ );
54
+ await runCc(["create", "triggers", body]);
55
+
56
+ const expectedDir = path.join(triggersRoot, schemetableName.toLowerCase(), triggerName);
57
+ assert.equal(fs.existsSync(expectedDir), true);
58
+ assert.equal(fs.existsSync(path.join(expectedDir, "config.json")), true);
59
+ assert.equal(fs.existsSync(path.join(expectedDir, `${triggerName}.java`)), true);
60
+
61
+ const config = JSON.parse(fs.readFileSync(path.join(expectedDir, "config.json"), "utf8"));
62
+ assert.equal(config.name, triggerName);
63
+ assert.equal(config.apiname, apiname);
64
+ assert.equal(config.targetObjectId, targetObjectId);
65
+ assert.equal(config.triggerTime, triggerTime);
66
+ });
67
+
68
+ t.test("3) 获取 doc(triggers overview)", async () => {
69
+ const { stdout } = await runCc(["doc", "triggers", "overview"]);
70
+ assert.match(stdout, /触发器|CCTrigger|编辑指南/i);
71
+ });
72
+
73
+ t.test("4) 发布触发器(publish,写回 config.id)", async () => {
74
+ const namePathForPublish = `${schemetableName.toLowerCase()}/${triggerName}`;
75
+ await runCc(["publish", "triggers", namePathForPublish]);
76
+ const configAfterPublish = JSON.parse(
77
+ fs.readFileSync(path.join(triggersRoot, schemetableName.toLowerCase(), triggerName, "config.json"), "utf8")
78
+ );
79
+ assert.ok(configAfterPublish.id, "publish 后应写入 config.id");
80
+ });
81
+
82
+ t.test("5) 拉取触发器(pull:覆盖 SOURCE 区域)", async () => {
83
+ const triggerPath = path.join(triggersRoot, schemetableName.toLowerCase(), triggerName);
84
+ const javaPathLocal = path.join(triggerPath, `${triggerName}.java`);
85
+ const localJava = fs.readFileSync(javaPathLocal, "utf8");
86
+ const modifiedJava = localJava.replace(
87
+ /\/\/ @SOURCE_CONTENT_START[\s\S]*?\/\/ @SOURCE_CONTENT_END/,
88
+ "// @SOURCE_CONTENT_START\nSystem.out.print(\"LOCAL_ONLY\");\n// @SOURCE_CONTENT_END"
89
+ );
90
+ fs.writeFileSync(javaPathLocal, modifiedJava, "utf8");
91
+
92
+ await runCc(["pull", "triggers", `${schemetableName.toLowerCase()}/${triggerName}`]);
93
+ const javaAfterPull = fs.readFileSync(javaPathLocal, "utf8");
94
+ assert.ok(!javaAfterPull.includes("LOCAL_ONLY"), "pull 后应使用远端源码替换本地标记内容");
95
+ });
96
+
97
+ t.test("6) 查询线上触发器详情(detail:仅使用 id)", async () => {
98
+ const configPathLocal = path.join(triggersRoot, schemetableName.toLowerCase(), triggerName, "config.json");
99
+ const publishedId = JSON.parse(fs.readFileSync(configPathLocal, "utf8")).id;
100
+ assert.ok(publishedId, "发布后应有 id");
101
+ await assert.doesNotReject(() => runCc(["detail", "triggers", "", publishedId]));
102
+ });
103
+
104
+ t.test("7) get 获取线上触发器列表(取前2个 id)", async () => {
105
+ const listQuery = encodeURI(
106
+ JSON.stringify({
107
+ shownum: "2000",
108
+ showpage: "1",
109
+ fid: "",
110
+ sname: "",
111
+ rptcond: "",
112
+ rptorder: "",
113
+ })
114
+ );
115
+ const { stdout } = await runCc(["get", "triggers", listQuery, repoRoot]);
116
+ const list = JSON.parse(stdout.trim());
117
+ assert.ok(Array.isArray(list), "get triggers 应返回数组");
118
+ topIds = list.slice(0, 2).map((x) => x.id).filter(Boolean);
119
+ assert.ok(topIds.length >= 1, "线上触发器列表至少应包含 1 个 id");
120
+ });
121
+
122
+ t.test("8) 批量拉取前2个触发器(pullList)", async () => {
123
+ assert.ok(topIds.length >= 1, "pullList 需要至少 1 个线上触发器 id");
124
+ const beforeCount = fs.existsSync(triggersRoot)
125
+ ? fs.readdirSync(triggersRoot).filter((n) => fs.statSync(path.join(triggersRoot, n)).isDirectory()).length
126
+ : 0;
127
+
128
+ for (const id of topIds) {
129
+ await runCc(["pullList", "triggers", id, repoRoot]);
130
+ }
131
+
132
+ const afterCount = fs.existsSync(triggersRoot)
133
+ ? fs.readdirSync(triggersRoot).filter((n) => fs.statSync(path.join(triggersRoot, n)).isDirectory()).length
134
+ : 0;
135
+ assert.ok(afterCount >= beforeCount, "pullList 之后 triggers 目录应存在/可用");
136
+ });
137
+
138
+ t.test("9) 清空 triggers 文件夹", async () => {
139
+ if (fs.existsSync(triggersRoot)) {
140
+ fs.rmSync(triggersRoot, { recursive: true, force: true });
141
+ }
142
+ fs.mkdirSync(triggersRoot, { recursive: true });
143
+ const remain = fs.readdirSync(triggersRoot);
144
+ assert.equal(remain.length, 0);
145
+ });
146
+ });
@@ -1,3 +0,0 @@
1
- {
2
- "java.configuration.updateBuildConfiguration": "automatic"
3
- }
package/bin/mcp-svc.js DELETED
@@ -1,13 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * MCP (Model Context Protocol) Streamable HTTP 服务启动脚本
4
- * 用于部署到服务器,通过 HTTP 与客户端通信
5
- *
6
- * 使用方法:
7
- * node bin/mcp-svc.js
8
- * 或
9
- * MCP_PORT=3000 node bin/mcp-svc.js
10
- */
11
-
12
- require('../src/mcp/index-svc.js');
13
-
@@ -1,8 +0,0 @@
1
- ## 审批
2
- *
3
- * 登录公司系统——》打开小云——〉点击语音输入
4
- * 1:帮我查看今天需要我审批的记录
5
- * 2:帮我预算类型的审批记录全部审批通过
6
- * 3:R017的报备记录文档位置不对,审批拒绝,添加交互确认
7
- * 4:帮我查看001预算的拆分情况
8
-
@@ -1,126 +0,0 @@
1
- /**
2
- * SSE 版本的 MCP 服务器实现
3
- * 基于 MCP 协议版本 2024-11-05 (已弃用,主要用于向后兼容测试)
4
- *
5
- * 服务器暴露两个端点:
6
- * - GET /mcp: 用于建立 SSE 流
7
- * - POST /messages: 用于接收客户端消息
8
- */
9
-
10
- const { McpServer } = require("@modelcontextprotocol/sdk/server/mcp.js");
11
- const { SSEServerTransport } = require("@modelcontextprotocol/sdk/server/sse.js");
12
- const { createMcpExpressApp } = require("@modelcontextprotocol/sdk/server/express.js");
13
- const { registerAllTools } = require('./tools/index.js');
14
- const MCP_PORT = process.env.MCP_PORT ? parseInt(process.env.MCP_PORT, 10) : 8018;
15
-
16
- // 获取服务器实例(单例模式)
17
- let mcpServerInstance = null;
18
- function getServer() {
19
- if (!mcpServerInstance) {
20
- mcpServerInstance = new McpServer({
21
- name: 'cctool',
22
- version: '1.0.0'
23
- });
24
-
25
- // 注册所有工具
26
- registerAllTools(mcpServerInstance);
27
- }
28
- return mcpServerInstance;
29
- }
30
-
31
- const app = createMcpExpressApp();
32
-
33
- // 按 session ID 存储 transports
34
- const transports = {};
35
-
36
- // SSE 端点:用于建立 SSE 流
37
- app.get('/mcp', async (req, res) => {
38
- console.log('Received GET request to /mcp (establishing SSE stream)');
39
-
40
- try {
41
- // 为客户端创建新的 SSE transport
42
- // POST 消息的端点是 '/messages'
43
- const transport = new SSEServerTransport('/messages', res);
44
-
45
- // 按 session ID 存储 transport
46
- const sessionId = transport.sessionId;
47
- transports[sessionId] = transport;
48
-
49
- // 设置 onclose 处理器,在关闭时清理 transport
50
- transport.onclose = () => {
51
- console.log(`SSE transport closed for session ${sessionId}`);
52
- delete transports[sessionId];
53
- };
54
-
55
- // 将 transport 连接到 MCP 服务器
56
- const server = getServer();
57
- await server.connect(transport);
58
-
59
- console.log(`Established SSE stream with session ID: ${sessionId}`);
60
- } catch (error) {
61
- console.error('Error establishing SSE stream:', error);
62
- if (!res.headersSent) {
63
- res.status(500).send('Error establishing SSE stream');
64
- }
65
- }
66
- });
67
-
68
- // 消息端点:用于接收客户端 JSON-RPC 请求
69
- app.post('/messages', async (req, res) => {
70
- console.log('Received POST request to /messages');
71
-
72
- // 从 URL query 参数中提取 session ID
73
- // 在 SSE 协议中,这是客户端根据端点事件添加的
74
- const sessionId = req.query.sessionId;
75
-
76
- if (!sessionId) {
77
- console.error('No session ID provided in request URL');
78
- res.status(400).send('Missing sessionId parameter');
79
- return;
80
- }
81
-
82
- const transport = transports[sessionId];
83
- if (!transport) {
84
- console.error(`No active transport found for session ID: ${sessionId}`);
85
- res.status(404).send('Session not found');
86
- return;
87
- }
88
-
89
- try {
90
- // 使用 transport 处理 POST 消息
91
- await transport.handlePostMessage(req, res, req.body);
92
- } catch (error) {
93
- console.error('Error handling request:', error);
94
- if (!res.headersSent) {
95
- res.status(500).send('Error handling request');
96
- }
97
- }
98
- });
99
-
100
- // 启动服务器
101
- app.listen(MCP_PORT, error => {
102
- if (error) {
103
- console.error('Failed to start server:', error);
104
- process.exit(1);
105
- }
106
- console.log(`Simple SSE Server (deprecated protocol version 2024-11-05) listening on port ${MCP_PORT}`);
107
- });
108
-
109
- // Handle server shutdown
110
- process.on('SIGINT', async () => {
111
- console.log('Shutting down server...');
112
-
113
- // Close all active transports to properly clean up resources
114
- for (const sessionId in transports) {
115
- try {
116
- console.log(`Closing transport for session ${sessionId}`);
117
- await transports[sessionId].close();
118
- delete transports[sessionId];
119
- } catch (error) {
120
- console.error(`Error closing transport for session ${sessionId}:`, error);
121
- }
122
- }
123
- console.log('Server shutdown complete');
124
- process.exit(0);
125
- });
126
-
@@ -1,180 +0,0 @@
1
- const { randomUUID } = require('node:crypto');
2
- const { McpServer } = require("@modelcontextprotocol/sdk/server/mcp.js");
3
- const { StreamableHTTPServerTransport } = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
4
- const { createMcpExpressApp } = require("@modelcontextprotocol/sdk/server/express.js");
5
- const { isInitializeRequest } = require("@modelcontextprotocol/sdk/types.js");
6
- const { InMemoryEventStore } = require("@modelcontextprotocol/sdk/examples/shared/inMemoryEventStore.js");
7
- const { registerAllTools } = require('./tools/index.js');
8
- const MCP_PORT = process.env.MCP_PORT ? parseInt(process.env.MCP_PORT, 10) : 8018;
9
-
10
- // 获取服务器实例(单例模式)
11
-
12
- let mcpServerInstance = null;
13
- function getServer() {
14
- if (!mcpServerInstance) {
15
- mcpServerInstance = new McpServer({
16
- name: 'cctool',
17
- version: '1.0.0'
18
- });
19
-
20
- // 注册所有工具
21
- registerAllTools(mcpServerInstance);
22
- }
23
- return mcpServerInstance;
24
- }
25
-
26
- const app = createMcpExpressApp();
27
-
28
- // Map to store transports by session ID
29
- const transports = {};
30
-
31
- // MCP POST endpoint
32
- const mcpPostHandler = async (req, res) => {
33
- const sessionId = req.headers['mcp-session-id'];
34
- if (sessionId) {
35
- console.log(`Received MCP request for session: ${sessionId}`);
36
- } else {
37
- console.log('Request body:', req.body);
38
- }
39
- try {
40
- let transport;
41
- if (sessionId && transports[sessionId]) {
42
- // Reuse existing transport
43
- transport = transports[sessionId];
44
- } else if (!sessionId && isInitializeRequest(req.body)) {
45
- // New initialization request
46
- const eventStore = new InMemoryEventStore();
47
- transport = new StreamableHTTPServerTransport({
48
- sessionIdGenerator: () => randomUUID(),
49
- eventStore, // Enable resumability
50
- onsessioninitialized: sessionId => {
51
- // Store the transport by session ID when session is initialized
52
- // This avoids race conditions where requests might come in before the session is stored
53
- console.log(`Session initialized with ID: ${sessionId}`);
54
- transports[sessionId] = transport;
55
- }
56
- });
57
-
58
- // Set up onclose handler to clean up transport when closed
59
- transport.onclose = () => {
60
- const sid = transport.sessionId;
61
- if (sid && transports[sid]) {
62
- console.log(`Transport closed for session ${sid}, removing from transports map`);
63
- delete transports[sid];
64
- }
65
- };
66
-
67
- // Connect the transport to the MCP server BEFORE handling the request
68
- // so responses can flow back through the same transport
69
- const server = getServer();
70
- await server.connect(transport);
71
-
72
- await transport.handleRequest(req, res, req.body);
73
- return; // Already handled
74
- } else {
75
- // Invalid request - no session ID or not initialization request
76
- res.status(400).json({
77
- jsonrpc: '2.0',
78
- error: {
79
- code: -32000,
80
- message: 'Bad Request: No valid session ID provided'
81
- },
82
- id: null
83
- });
84
- return;
85
- }
86
-
87
- // Handle the request with existing transport - no need to reconnect
88
- // The existing transport is already connected to the server
89
- await transport.handleRequest(req, res, req.body);
90
- } catch (error) {
91
- console.error('Error handling MCP request:', error);
92
- if (!res.headersSent) {
93
- res.status(500).json({
94
- jsonrpc: '2.0',
95
- error: {
96
- code: -32603,
97
- message: 'Internal server error'
98
- },
99
- id: null
100
- });
101
- }
102
- }
103
- };
104
-
105
- // Set up POST route
106
- app.post('/mcp', mcpPostHandler);
107
-
108
- // Handle GET requests for SSE streams (using built-in support from StreamableHTTP)
109
- const mcpGetHandler = async (req, res) => {
110
- const sessionId = req.headers['mcp-session-id'];
111
- if (!sessionId || !transports[sessionId]) {
112
- res.status(400).send('Invalid or missing session ID');
113
- return;
114
- }
115
-
116
- // Check for Last-Event-ID header for resumability
117
- const lastEventId = req.headers['last-event-id'];
118
- if (lastEventId) {
119
- console.log(`Client reconnecting with Last-Event-ID: ${lastEventId}`);
120
- } else {
121
- console.log(`Establishing new SSE stream for session ${sessionId}`);
122
- }
123
-
124
- const transport = transports[sessionId];
125
- await transport.handleRequest(req, res);
126
- };
127
-
128
- // Set up GET route
129
- app.get('/mcp', mcpGetHandler);
130
-
131
- // Handle DELETE requests for session termination (according to MCP spec)
132
- const mcpDeleteHandler = async (req, res) => {
133
- const sessionId = req.headers['mcp-session-id'];
134
- if (!sessionId || !transports[sessionId]) {
135
- res.status(400).send('Invalid or missing session ID');
136
- return;
137
- }
138
-
139
- console.log(`Received session termination request for session ${sessionId}`);
140
-
141
- try {
142
- const transport = transports[sessionId];
143
- await transport.handleRequest(req, res);
144
- } catch (error) {
145
- console.error('Error handling session termination:', error);
146
- if (!res.headersSent) {
147
- res.status(500).send('Error processing session termination');
148
- }
149
- }
150
- };
151
-
152
- // Set up DELETE route
153
- app.delete('/mcp', mcpDeleteHandler);
154
-
155
- app.listen(MCP_PORT, error => {
156
- if (error) {
157
- console.error('Failed to start server:', error);
158
- process.exit(1);
159
- }
160
- console.log(`MCP Streamable HTTP Server listening on port ${MCP_PORT}`);
161
- });
162
-
163
- // Handle server shutdown
164
- process.on('SIGINT', async () => {
165
- console.log('Shutting down server...');
166
-
167
- // Close all active transports to properly clean up resources
168
- for (const sessionId in transports) {
169
- try {
170
- console.log(`Closing transport for session ${sessionId}`);
171
- await transports[sessionId].close();
172
- delete transports[sessionId];
173
- } catch (error) {
174
- console.error(`Error closing transport for session ${sessionId}:`, error);
175
- }
176
- }
177
- console.log('Server shutdown complete');
178
- process.exit(0);
179
- });
180
-
@@ -1,37 +0,0 @@
1
- /**
2
- * Class Detail Getter 工具知识库
3
- * 提供类详细信息查看指南
4
- */
5
-
6
- module.exports = {
7
- description: '查看自定义类的详细信息',
8
- parameters: {
9
- className: {
10
- type: 'string',
11
- required: false,
12
- description: '类名(可选,优先使用本地查询)'
13
- },
14
- classId: {
15
- type: 'string',
16
- required: false,
17
- description: '类ID(可选,当没有className时使用服务器查询)'
18
- },
19
- projectPath: {
20
- type: 'string',
21
- required: false,
22
- description: '项目路径,默认为当前工作目录'
23
- }
24
- },
25
- notes: [
26
- '至少需要提供 className 或 classId 其中之一',
27
- '如果提供了 className,优先从本地文件获取(更快)',
28
- '如果只提供 classId,从服务器查询',
29
- '返回信息包括:基本配置、发布状态、服务器信息(如有)和源码预览'
30
- ],
31
- displayedInfo: [
32
- '基本信息:包括类名、版本号、服务器ID、数据来源',
33
- '发布状态:是否已发布到服务器',
34
- '服务器信息:API名称、状态、创建/更新时间(如有)',
35
- '源码预览:显示前50行源码内容'
36
- ]
37
- };