congmao-cli 1.0.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.
Files changed (180) hide show
  1. package/README.md +2 -0
  2. package/bin/index.js +20 -0
  3. package/package.json +42 -0
  4. package/src/commands/create.js +171 -0
  5. package/src/commands/mcp.js +157 -0
  6. package/src/mcp/CURSOR_SETUP.md +259 -0
  7. package/src/mcp/README.md +134 -0
  8. package/src/mcp/USAGE_EXAMPLES.md +181 -0
  9. package/src/mcp/get-cursor-config.js +58 -0
  10. package/src/mcp/package.json +23 -0
  11. package/src/mcp/pnpm-lock.yaml +2441 -0
  12. package/src/mcp/src/handlers.js +211 -0
  13. package/src/mcp/src/http-server.js +332 -0
  14. package/src/mcp/src/index.js +195 -0
  15. package/src/mcp/src/schemas.js +54 -0
  16. package/src/mcp/test-create-project.js +49 -0
  17. package/src/mcp/test-mcp.js +89 -0
  18. package/src/mcp/test-show-tree.js +66 -0
  19. package/src/template/Uni-app/README.md +19 -0
  20. package/src/template/Uni-app/babel.config.js +81 -0
  21. package/src/template/Uni-app/package.json +107 -0
  22. package/src/template/Uni-app/postcss.config.js +27 -0
  23. package/src/template/Uni-app/public/index.html +25 -0
  24. package/src/template/Uni-app/shims-uni.d.ts +11 -0
  25. package/src/template/Uni-app/shims-vue.d.ts +4 -0
  26. package/src/template/Uni-app/src/App.vue +17 -0
  27. package/src/template/Uni-app/src/main.js +12 -0
  28. package/src/template/Uni-app/src/manifest.json +75 -0
  29. package/src/template/Uni-app/src/pages/index/index.vue +49 -0
  30. package/src/template/Uni-app/src/pages.json +16 -0
  31. package/src/template/Uni-app/src/static/logo.png +0 -0
  32. package/src/template/Uni-app/src/uni.promisify.adaptor.js +13 -0
  33. package/src/template/Uni-app/src/uni.scss +76 -0
  34. package/src/template/Uni-app/yarn.lock +11466 -0
  35. package/src/template/Vue2/.editorconfig +14 -0
  36. package/src/template/Vue2/.env +2 -0
  37. package/src/template/Vue2/.env.development +2 -0
  38. package/src/template/Vue2/.env.site +2 -0
  39. package/src/template/Vue2/.prettierrc.js +39 -0
  40. package/src/template/Vue2/.stylelintignore +8 -0
  41. package/src/template/Vue2/README-zh_CN.md +115 -0
  42. package/src/template/Vue2/commitlint.config.js +1 -0
  43. package/src/template/Vue2/docs/docs-starter.png +0 -0
  44. package/src/template/Vue2/docs/docs-startup.png +0 -0
  45. package/src/template/Vue2/docs/docs-structure.png +0 -0
  46. package/src/template/Vue2/globals.d.ts +13 -0
  47. package/src/template/Vue2/index.html +27 -0
  48. package/src/template/Vue2/jsx.d.ts +13 -0
  49. package/src/template/Vue2/mock/index.ts +147 -0
  50. package/src/template/Vue2/package.json +91 -0
  51. package/src/template/Vue2/package.json.ejs +91 -0
  52. package/src/template/Vue2/public/favicon.ico +0 -0
  53. package/src/template/Vue2/shims-vue.d.ts +5 -0
  54. package/src/template/Vue2/src/App.vue +19 -0
  55. package/src/template/Vue2/src/assets/assets-login-bg-black.png +0 -0
  56. package/src/template/Vue2/src/assets/assets-login-bg-white.png +0 -0
  57. package/src/template/Vue2/src/assets/assets-logo-full.svg +39 -0
  58. package/src/template/Vue2/src/assets/assets-product-1.svg +5 -0
  59. package/src/template/Vue2/src/assets/assets-product-2.svg +5 -0
  60. package/src/template/Vue2/src/assets/assets-product-3.svg +5 -0
  61. package/src/template/Vue2/src/assets/assets-product-4.svg +5 -0
  62. package/src/template/Vue2/src/assets/assets-result-403.svg +32 -0
  63. package/src/template/Vue2/src/assets/assets-result-404.svg +36 -0
  64. package/src/template/Vue2/src/assets/assets-result-500.svg +32 -0
  65. package/src/template/Vue2/src/assets/assets-result-ie.svg +33 -0
  66. package/src/template/Vue2/src/assets/assets-result-maintenance.svg +49 -0
  67. package/src/template/Vue2/src/assets/assets-result-wifi.svg +23 -0
  68. package/src/template/Vue2/src/assets/assets-setting-auto.svg +13 -0
  69. package/src/template/Vue2/src/assets/assets-setting-dark.svg +5 -0
  70. package/src/template/Vue2/src/assets/assets-setting-light.svg +13 -0
  71. package/src/template/Vue2/src/assets/assets-t-logo.svg +41 -0
  72. package/src/template/Vue2/src/assets/assets-tencent-logo.png +0 -0
  73. package/src/template/Vue2/src/components/color/index.vue +35 -0
  74. package/src/template/Vue2/src/components/product-card/index.vue +121 -0
  75. package/src/template/Vue2/src/components/result/index.vue +118 -0
  76. package/src/template/Vue2/src/components/thumbnail/index.vue +49 -0
  77. package/src/template/Vue2/src/components/trend/index.vue +105 -0
  78. package/src/template/Vue2/src/config/color.ts +30 -0
  79. package/src/template/Vue2/src/config/global.ts +2 -0
  80. package/src/template/Vue2/src/config/host.ts +26 -0
  81. package/src/template/Vue2/src/config/style.ts +14 -0
  82. package/src/template/Vue2/src/constants/index.ts +46 -0
  83. package/src/template/Vue2/src/interface.ts +39 -0
  84. package/src/template/Vue2/src/layouts/blank.vue +12 -0
  85. package/src/template/Vue2/src/layouts/components/Breadcrumb.vue +39 -0
  86. package/src/template/Vue2/src/layouts/components/Content.vue +43 -0
  87. package/src/template/Vue2/src/layouts/components/Footer.vue +27 -0
  88. package/src/template/Vue2/src/layouts/components/Header.vue +321 -0
  89. package/src/template/Vue2/src/layouts/components/LayoutContent.vue +168 -0
  90. package/src/template/Vue2/src/layouts/components/LayoutHeader.vue +52 -0
  91. package/src/template/Vue2/src/layouts/components/LayoutSidebar.vue +51 -0
  92. package/src/template/Vue2/src/layouts/components/MenuContent.vue +108 -0
  93. package/src/template/Vue2/src/layouts/components/Notice.vue +221 -0
  94. package/src/template/Vue2/src/layouts/components/Search.vue +134 -0
  95. package/src/template/Vue2/src/layouts/components/SideNav.vue +150 -0
  96. package/src/template/Vue2/src/layouts/index.vue +100 -0
  97. package/src/template/Vue2/src/layouts/setting.vue +404 -0
  98. package/src/template/Vue2/src/main.js +9 -0
  99. package/src/template/Vue2/src/main.jsx +51 -0
  100. package/src/template/Vue2/src/pages/dashboard/base/components/MiddleChart.vue +158 -0
  101. package/src/template/Vue2/src/pages/dashboard/base/components/OutputOverview.vue +189 -0
  102. package/src/template/Vue2/src/pages/dashboard/base/components/RankList.vue +111 -0
  103. package/src/template/Vue2/src/pages/dashboard/base/components/TopPanel.vue +246 -0
  104. package/src/template/Vue2/src/pages/dashboard/base/index.ts +702 -0
  105. package/src/template/Vue2/src/pages/dashboard/base/index.vue +44 -0
  106. package/src/template/Vue2/src/pages/dashboard/detail/index.ts +267 -0
  107. package/src/template/Vue2/src/pages/dashboard/detail/index.vue +242 -0
  108. package/src/template/Vue2/src/pages/detail/advanced/components/Product.vue +167 -0
  109. package/src/template/Vue2/src/pages/detail/advanced/index.less +74 -0
  110. package/src/template/Vue2/src/pages/detail/advanced/index.vue +219 -0
  111. package/src/template/Vue2/src/pages/detail/base/index.less +105 -0
  112. package/src/template/Vue2/src/pages/detail/base/index.vue +46 -0
  113. package/src/template/Vue2/src/pages/detail/deploy/index.ts +204 -0
  114. package/src/template/Vue2/src/pages/detail/deploy/index.vue +224 -0
  115. package/src/template/Vue2/src/pages/detail/secondary/index.less +71 -0
  116. package/src/template/Vue2/src/pages/detail/secondary/index.vue +131 -0
  117. package/src/template/Vue2/src/pages/form/base/index.less +57 -0
  118. package/src/template/Vue2/src/pages/form/base/index.vue +254 -0
  119. package/src/template/Vue2/src/pages/form/step/index.less +37 -0
  120. package/src/template/Vue2/src/pages/form/step/index.vue +259 -0
  121. package/src/template/Vue2/src/pages/frame/doc/index.vue +86 -0
  122. package/src/template/Vue2/src/pages/frame/tdesign/index.vue +86 -0
  123. package/src/template/Vue2/src/pages/list/base/index.vue +267 -0
  124. package/src/template/Vue2/src/pages/list/card/index.vue +221 -0
  125. package/src/template/Vue2/src/pages/list/components/CommonTable.vue +313 -0
  126. package/src/template/Vue2/src/pages/list/filter/index.vue +15 -0
  127. package/src/template/Vue2/src/pages/list/tree/index.vue +174 -0
  128. package/src/template/Vue2/src/pages/login/components/components-header.vue +74 -0
  129. package/src/template/Vue2/src/pages/login/components/components-login.vue +154 -0
  130. package/src/template/Vue2/src/pages/login/components/components-register.vue +144 -0
  131. package/src/template/Vue2/src/pages/login/index.less +202 -0
  132. package/src/template/Vue2/src/pages/login/index.vue +53 -0
  133. package/src/template/Vue2/src/pages/nest-menu/Index.vue +10 -0
  134. package/src/template/Vue2/src/pages/result/403/index.vue +14 -0
  135. package/src/template/Vue2/src/pages/result/404/index.vue +14 -0
  136. package/src/template/Vue2/src/pages/result/500/index.vue +14 -0
  137. package/src/template/Vue2/src/pages/result/browser-incompatible/index.vue +77 -0
  138. package/src/template/Vue2/src/pages/result/fail/index.vue +57 -0
  139. package/src/template/Vue2/src/pages/result/maintenance/index.vue +14 -0
  140. package/src/template/Vue2/src/pages/result/network-error/index.vue +24 -0
  141. package/src/template/Vue2/src/pages/result/success/index.vue +59 -0
  142. package/src/template/Vue2/src/pages/user/index.less +148 -0
  143. package/src/template/Vue2/src/pages/user/index.ts +157 -0
  144. package/src/template/Vue2/src/pages/user/index.vue +204 -0
  145. package/src/template/Vue2/src/permission.js +56 -0
  146. package/src/template/Vue2/src/router/index.js +43 -0
  147. package/src/template/Vue2/src/router/modules/base.ts +29 -0
  148. package/src/template/Vue2/src/router/modules/components.ts +175 -0
  149. package/src/template/Vue2/src/router/modules/others.ts +55 -0
  150. package/src/template/Vue2/src/service/service-advance.ts +233 -0
  151. package/src/template/Vue2/src/service/service-base.ts +205 -0
  152. package/src/template/Vue2/src/service/service-detail-base.ts +84 -0
  153. package/src/template/Vue2/src/service/service-detail-deploy.ts +234 -0
  154. package/src/template/Vue2/src/service/service-detail.ts +57 -0
  155. package/src/template/Vue2/src/service/service-user.ts +64 -0
  156. package/src/template/Vue2/src/store/index.ts +22 -0
  157. package/src/template/Vue2/src/store/modules/notification.ts +90 -0
  158. package/src/template/Vue2/src/store/modules/permission.ts +66 -0
  159. package/src/template/Vue2/src/store/modules/setting.ts +122 -0
  160. package/src/template/Vue2/src/store/modules/tab-router.ts +83 -0
  161. package/src/template/Vue2/src/store/modules/user.ts +98 -0
  162. package/src/template/Vue2/src/style/font-family.less +6 -0
  163. package/src/template/Vue2/src/style/index.less +5 -0
  164. package/src/template/Vue2/src/style/layout.less +201 -0
  165. package/src/template/Vue2/src/style/reset.less +78 -0
  166. package/src/template/Vue2/src/style/variables.less +27 -0
  167. package/src/template/Vue2/src/utils/charts.ts +38 -0
  168. package/src/template/Vue2/src/utils/color.ts +118 -0
  169. package/src/template/Vue2/src/utils/date.ts +12 -0
  170. package/src/template/Vue2/src/utils/request.ts +60 -0
  171. package/src/template/Vue2/stylelint.config.js +5 -0
  172. package/src/template/Vue2/tsconfig.json +26 -0
  173. package/src/template/Vue2/vite.config.js +58 -0
  174. package/src/template/Vue3/package.json.ejs +8 -0
  175. package/src/template/Vue3/pages.json +10 -0
  176. package/src/template/Vue3/src/main.js +7 -0
  177. package/src/utils/copy.js +17 -0
  178. package/src/utils/eslint.js +205 -0
  179. package/src/utils/logo.js +18 -0
  180. package/src/utils/render.js +20 -0
@@ -0,0 +1,211 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import copyDir from "../../utils/copy.js";
5
+ import renderTemplates from "../../utils/render.js";
6
+ import setupESLint from "../../utils/eslint.js";
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+
11
+ /**
12
+ * 根据模板类型获取启动命令
13
+ */
14
+ function getStartCommand(templateName, needESLint) {
15
+ const commands = {
16
+ 'Uni-app': {
17
+ install: 'pnpm install',
18
+ lint: needESLint ? 'pnpm run lint' : null,
19
+ dev: 'pnpm run dev:h5'
20
+ },
21
+ 'Vue2': {
22
+ install: 'pnpm install',
23
+ lint: needESLint ? 'pnpm run lint' : null,
24
+ dev: 'pnpm run dev'
25
+ },
26
+ 'Vue3': {
27
+ install: 'pnpm install',
28
+ lint: needESLint ? 'pnpm run lint' : null,
29
+ dev: 'pnpm run dev'
30
+ }
31
+ };
32
+
33
+ return commands[templateName] || commands['Vue2'];
34
+ }
35
+
36
+ /**
37
+ * 快速创建项目
38
+ */
39
+ export async function createProject(input) {
40
+ try {
41
+ const {
42
+ projectName,
43
+ projectType,
44
+ vueVersion,
45
+ needESLint = true,
46
+ targetPath = process.cwd(),
47
+ } = input;
48
+
49
+ // 验证项目名称
50
+ if (!/^[a-z0-9-]+$/.test(projectName)) {
51
+ return {
52
+ success: false,
53
+ error: "项目名称只能包含小写字母、数字和连字符",
54
+ };
55
+ }
56
+
57
+ // 确定模板名称
58
+ let templateName;
59
+ if (projectType === 'mobile' || projectType === 'miniprogram') {
60
+ templateName = 'Uni-app';
61
+ } else if (projectType === 'admin') {
62
+ if (!vueVersion) {
63
+ return {
64
+ success: false,
65
+ error: "后台管理系统需要指定Vue版本",
66
+ };
67
+ }
68
+ templateName = vueVersion;
69
+ } else {
70
+ return {
71
+ success: false,
72
+ error: `不支持的项目类型: ${projectType}`,
73
+ };
74
+ }
75
+
76
+ // 检查目标目录
77
+ const targetDir = path.resolve(targetPath, projectName);
78
+ if (fs.existsSync(targetDir)) {
79
+ return {
80
+ success: false,
81
+ error: `目录已存在: ${targetDir}`,
82
+ };
83
+ }
84
+
85
+ // 获取模板目录
86
+ const templateDir = path.resolve(
87
+ __dirname,
88
+ `../../template/${templateName}`
89
+ );
90
+
91
+ if (!fs.existsSync(templateDir)) {
92
+ return {
93
+ success: false,
94
+ error: `模板不存在: ${templateDir}`,
95
+ };
96
+ }
97
+
98
+ // 复制模板
99
+ copyDir(templateDir, targetDir);
100
+
101
+ // 渲染模板
102
+ renderTemplates(targetDir, { projectName, needESLint });
103
+
104
+ // 配置 ESLint
105
+ if (needESLint) {
106
+ await setupESLint(targetDir, templateName);
107
+ }
108
+
109
+ // 获取启动命令
110
+ const startCmd = getStartCommand(templateName, needESLint);
111
+ let installCmd = `cd ${projectName}\n${startCmd.install}`;
112
+
113
+ if (startCmd.lint) {
114
+ installCmd += `\n${startCmd.lint}`;
115
+ }
116
+
117
+ installCmd += `\n${startCmd.dev}`;
118
+
119
+ return {
120
+ success: true,
121
+ message: `项目创建成功!\n\n${installCmd}`,
122
+ projectPath: targetDir,
123
+ templateName,
124
+ startCommands: installCmd,
125
+ };
126
+ } catch (error) {
127
+ return {
128
+ success: false,
129
+ error: `创建项目失败: ${error.message}`,
130
+ };
131
+ }
132
+ }
133
+
134
+ /**
135
+ * 生成项目结构树
136
+ */
137
+ function generateTree(dir, prefix = "", maxDepth = 3, currentDepth = 0, ignorePatterns = []) {
138
+ if (currentDepth >= maxDepth) {
139
+ return "";
140
+ }
141
+
142
+ const items = fs.readdirSync(dir).filter(item => {
143
+ // 忽略隐藏文件和指定模式
144
+ if (item.startsWith('.')) return false;
145
+ return !ignorePatterns.some(pattern => item.includes(pattern));
146
+ });
147
+
148
+ let tree = "";
149
+ items.forEach((item, index) => {
150
+ const itemPath = path.join(dir, item);
151
+ const isLast = index === items.length - 1;
152
+ const stat = fs.statSync(itemPath);
153
+
154
+ const connector = isLast ? "└── " : "├── ";
155
+ tree += prefix + connector + item + "\n";
156
+
157
+ if (stat.isDirectory() && currentDepth < maxDepth - 1) {
158
+ const nextPrefix = prefix + (isLast ? " " : "│ ");
159
+ tree += generateTree(itemPath, nextPrefix, maxDepth, currentDepth + 1, ignorePatterns);
160
+ }
161
+ });
162
+
163
+ return tree;
164
+ }
165
+
166
+ /**
167
+ * 显示项目结构树
168
+ */
169
+ export async function showProjectTree(input) {
170
+ try {
171
+ const {
172
+ projectPath,
173
+ maxDepth = 3,
174
+ ignorePatterns = ["node_modules", ".git", "dist", "build"],
175
+ } = input;
176
+
177
+ const resolvedPath = path.isAbsolute(projectPath)
178
+ ? projectPath
179
+ : path.resolve(process.cwd(), projectPath);
180
+
181
+ if (!fs.existsSync(resolvedPath)) {
182
+ return {
183
+ success: false,
184
+ error: `路径不存在: ${resolvedPath}`,
185
+ };
186
+ }
187
+
188
+ if (!fs.statSync(resolvedPath).isDirectory()) {
189
+ return {
190
+ success: false,
191
+ error: `路径不是目录: ${resolvedPath}`,
192
+ };
193
+ }
194
+
195
+ const tree = generateTree(resolvedPath, "", maxDepth, 0, ignorePatterns);
196
+ const projectName = path.basename(resolvedPath);
197
+
198
+ return {
199
+ success: true,
200
+ message: `${projectName}\n${tree}`,
201
+ tree,
202
+ projectPath: resolvedPath,
203
+ };
204
+ } catch (error) {
205
+ return {
206
+ success: false,
207
+ error: `生成项目结构树失败: ${error.message}`,
208
+ };
209
+ }
210
+ }
211
+
@@ -0,0 +1,332 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * HTTP 版本的 MCP 服务器
5
+ * 通过 HTTP 端口提供服务
6
+ */
7
+
8
+ import http from "node:http";
9
+ import packageJson from "../package.json" assert { type: "json" };
10
+ import { createProject, showProjectTree } from "./handlers.js";
11
+ import { validateCreateProject, validateShowProjectTree } from "./schemas.js";
12
+
13
+ /**
14
+ * 获取可用工具列表
15
+ */
16
+ function getToolsList() {
17
+ return {
18
+ tools: [
19
+ {
20
+ name: "create-project",
21
+ description:
22
+ "快速创建项目。支持创建移动端(Uni-app)、小程序(Uni-app)、后台管理系统(Vue2/Vue3)项目。可以自动配置ESLint。",
23
+ inputSchema: {
24
+ type: "object",
25
+ properties: {
26
+ projectName: {
27
+ type: "string",
28
+ description: "项目名称,只能包含小写字母、数字和连字符",
29
+ },
30
+ projectType: {
31
+ type: "string",
32
+ enum: ["mobile", "miniprogram", "admin"],
33
+ description: "项目类型:mobile-移动端, miniprogram-小程序, admin-后台管理系统",
34
+ },
35
+ vueVersion: {
36
+ type: "string",
37
+ enum: ["Vue2", "Vue3"],
38
+ description: "Vue版本(仅当projectType为admin时需要)",
39
+ },
40
+ needESLint: {
41
+ type: "boolean",
42
+ description: "是否配置ESLint,默认为true",
43
+ default: true,
44
+ },
45
+ targetPath: {
46
+ type: "string",
47
+ description: "目标路径,默认为当前工作目录",
48
+ },
49
+ },
50
+ required: ["projectName", "projectType"],
51
+ },
52
+ },
53
+ {
54
+ name: "show-project-tree",
55
+ description:
56
+ "查看项目结构树。可以显示指定项目的目录结构,支持自定义深度和忽略模式。",
57
+ inputSchema: {
58
+ type: "object",
59
+ properties: {
60
+ projectPath: {
61
+ type: "string",
62
+ description: "项目路径,可以是相对路径或绝对路径",
63
+ },
64
+ maxDepth: {
65
+ type: "number",
66
+ description: "最大深度,默认为3",
67
+ default: 3,
68
+ },
69
+ ignorePatterns: {
70
+ type: "array",
71
+ items: {
72
+ type: "string",
73
+ },
74
+ description: "忽略的目录模式,默认为['node_modules', '.git', 'dist', 'build']",
75
+ default: ["node_modules", ".git", "dist", "build"],
76
+ },
77
+ },
78
+ required: ["projectPath"],
79
+ },
80
+ },
81
+ ],
82
+ };
83
+ }
84
+
85
+ /**
86
+ * 处理工具调用
87
+ */
88
+ async function handleToolCall(name, args) {
89
+ try {
90
+ switch (name) {
91
+ case "create-project": {
92
+ const validatedInput = validateCreateProject(args);
93
+ const result = await createProject(validatedInput);
94
+
95
+ if (result.success) {
96
+ return {
97
+ content: [
98
+ {
99
+ type: "text",
100
+ text: result.message || "项目创建成功!",
101
+ },
102
+ ],
103
+ };
104
+ } else {
105
+ return {
106
+ content: [
107
+ {
108
+ type: "text",
109
+ text: `错误: ${result.error}`,
110
+ },
111
+ ],
112
+ isError: true,
113
+ };
114
+ }
115
+ }
116
+
117
+ case "show-project-tree": {
118
+ const validatedInput = validateShowProjectTree(args);
119
+ const result = await showProjectTree(validatedInput);
120
+
121
+ if (result.success) {
122
+ return {
123
+ content: [
124
+ {
125
+ type: "text",
126
+ text: result.message || result.tree || "项目结构树",
127
+ },
128
+ ],
129
+ };
130
+ } else {
131
+ return {
132
+ content: [
133
+ {
134
+ type: "text",
135
+ text: `错误: ${result.error}`,
136
+ },
137
+ ],
138
+ isError: true,
139
+ };
140
+ }
141
+ }
142
+
143
+ default:
144
+ return {
145
+ content: [
146
+ {
147
+ type: "text",
148
+ text: `未知的工具: ${name}`,
149
+ },
150
+ ],
151
+ isError: true,
152
+ };
153
+ }
154
+ } catch (error) {
155
+ return {
156
+ content: [
157
+ {
158
+ type: "text",
159
+ text: `工具执行失败: ${error.message}`,
160
+ },
161
+ ],
162
+ isError: true,
163
+ };
164
+ }
165
+ }
166
+
167
+ /**
168
+ * 查找可用端口
169
+ */
170
+ export async function findAvailablePort(startPort = 3000, maxPort = 3100) {
171
+ const net = await import("node:net");
172
+
173
+ for (let port = startPort; port <= maxPort; port++) {
174
+ const isAvailable = await new Promise((resolve) => {
175
+ const server = net.createServer();
176
+ server.listen(port, () => {
177
+ server.once("close", () => resolve(true));
178
+ server.close();
179
+ });
180
+ server.on("error", () => resolve(false));
181
+ });
182
+
183
+ if (isAvailable) {
184
+ return port;
185
+ }
186
+ }
187
+
188
+ // 如果指定范围内没有可用端口,让系统自动分配(端口0)
189
+ return 0;
190
+ }
191
+
192
+ /**
193
+ * 创建 HTTP 服务器
194
+ */
195
+ export async function createHTTPServer(port = 0) {
196
+ return new Promise((resolve, reject) => {
197
+ const httpServer = http.createServer(async (req, res) => {
198
+ // 设置 CORS 头
199
+ res.setHeader("Access-Control-Allow-Origin", "*");
200
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
201
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
202
+
203
+ if (req.method === "OPTIONS") {
204
+ res.writeHead(200);
205
+ res.end();
206
+ return;
207
+ }
208
+
209
+ if (req.method === "GET" && req.url === "/health") {
210
+ res.writeHead(200, { "Content-Type": "application/json" });
211
+ res.end(JSON.stringify({ status: "ok", service: "cm-cli-mcp" }));
212
+ return;
213
+ }
214
+
215
+ if (req.method === "POST" && req.url === "/mcp") {
216
+ try {
217
+ let body = "";
218
+ req.on("data", (chunk) => {
219
+ body += chunk.toString();
220
+ });
221
+
222
+ req.on("end", async () => {
223
+ try {
224
+ const request = JSON.parse(body);
225
+ let response;
226
+
227
+ // 处理 MCP 协议请求
228
+ if (request.method === "tools/list") {
229
+ // 列出可用工具
230
+ const result = getToolsList();
231
+ response = {
232
+ jsonrpc: "2.0",
233
+ id: request.id,
234
+ result: result,
235
+ };
236
+ } else if (request.method === "tools/call") {
237
+ // 调用工具
238
+ const { name, arguments: args } = request.params || {};
239
+ const result = await handleToolCall(name, args);
240
+ response = {
241
+ jsonrpc: "2.0",
242
+ id: request.id,
243
+ result: result,
244
+ };
245
+ } else {
246
+ response = {
247
+ jsonrpc: "2.0",
248
+ id: request.id,
249
+ error: {
250
+ code: -32601,
251
+ message: `Method not found: ${request.method}`,
252
+ },
253
+ };
254
+ }
255
+
256
+ res.writeHead(200, { "Content-Type": "application/json" });
257
+ res.end(JSON.stringify(response));
258
+ } catch (error) {
259
+ res.writeHead(400, { "Content-Type": "application/json" });
260
+ res.end(JSON.stringify({
261
+ jsonrpc: "2.0",
262
+ id: request?.id || null,
263
+ error: {
264
+ code: -32603,
265
+ message: error.message
266
+ }
267
+ }));
268
+ }
269
+ });
270
+ } catch (error) {
271
+ res.writeHead(500, { "Content-Type": "application/json" });
272
+ res.end(JSON.stringify({ error: error.message }));
273
+ }
274
+ return;
275
+ }
276
+
277
+ res.writeHead(404, { "Content-Type": "application/json" });
278
+ res.end(JSON.stringify({ error: "Not Found" }));
279
+ });
280
+
281
+ httpServer.listen(port, "0.0.0.0", () => {
282
+ resolve(httpServer);
283
+ });
284
+
285
+ httpServer.on("error", (error) => {
286
+ reject(error);
287
+ });
288
+ });
289
+ }
290
+
291
+ /**
292
+ * 启动 HTTP 服务器
293
+ */
294
+ export async function startHTTPServer(requestedPort = 0) {
295
+ try {
296
+ let port = requestedPort;
297
+
298
+ // 如果端口为0或未指定,自动查找可用端口
299
+ if (port === 0 || !port) {
300
+ port = await findAvailablePort(3000, 3100);
301
+ // 如果找不到可用端口,使用系统自动分配(端口0)
302
+ if (port === 0) {
303
+ port = 0; // 让系统自动分配
304
+ }
305
+ }
306
+
307
+ const httpServer = await createHTTPServer(port);
308
+ const address = httpServer.address();
309
+ const actualPort = address.port;
310
+ const url = `http://localhost:${actualPort}`;
311
+
312
+ console.error(`CM CLI MCP HTTP 服务器已启动`);
313
+ console.error(`监听地址: ${url}`);
314
+ console.error(`健康检查: ${url}/health`);
315
+ console.error(`MCP 端点: ${url}/mcp`);
316
+
317
+ return { httpServer, url, port: actualPort };
318
+ } catch (error) {
319
+ if (error.code === "EADDRINUSE") {
320
+ // 如果指定端口被占用,尝试自动查找可用端口
321
+ console.error(`端口 ${requestedPort} 已被占用,正在查找可用端口...`);
322
+ const availablePort = await findAvailablePort(3000, 3100);
323
+ if (availablePort === 0) {
324
+ throw new Error(`无法找到可用端口(3000-3100 范围)`);
325
+ }
326
+ console.error(`找到可用端口: ${availablePort}`);
327
+ return startHTTPServer(availablePort);
328
+ }
329
+ throw error;
330
+ }
331
+ }
332
+