sloth-d2c-mcp 1.0.4-beta87 → 1.0.4-beta89

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.
@@ -32,10 +32,11 @@ const upload = multer({
32
32
  });
33
33
  // 导入默认提示词
34
34
  import { chunkOptimizeCodePrompt, aggregationOptimizeCodePrompt, finalOptimizeCodePrompt, chunkOptimizeCodePromptVue, aggregationOptimizeCodePromptVue, finalOptimizeCodePromptVue, noSamplingAggregationPrompt, noSamplingAggregationPromptVue, } from 'sloth-d2c-node/convert';
35
+ import { getParam } from './utils/utils.js';
35
36
  // 保存 HTTP 服务器实例
36
- let httpServer = null;
37
+ export let httpServer = null;
37
38
  // 保存 Socket 服务器实例
38
- let socketServer = null;
39
+ export let socketServer = null;
39
40
  // 管理所有活跃的传输对象,按 sessionId 分类
40
41
  const transports = {
41
42
  streamable: {}, // 流式 HTTP 传输
@@ -196,6 +197,13 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
196
197
  next();
197
198
  });
198
199
  // app.use(express.json());
200
+ app.use((req, _res, next) => {
201
+ // 通过referer取url上面的uuid
202
+ const uuid = getParam(req.headers.referer || '', 'token') || '';
203
+ req.uuid = uuid;
204
+ req.fileManager = fileManager.withUUID(uuid);
205
+ next();
206
+ });
199
207
  // 处理流式 HTTP 的 POST 请求,支持会话复用和初始化
200
208
  app.post('/mcp', async (req, res) => {
201
209
  Logger.log('Received StreamableHTTP request');
@@ -333,11 +341,11 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
333
341
  // 如果提供了 fileKey,返回该 fileKey 的特定配置
334
342
  const globalConfig = await configManager.load();
335
343
  // const fileConfig = globalConfig.fileConfigs?.[fileKey] || {}
336
- const fileConfig = (await fileManager.loadConfigSetting(fileKey, nodeId)) || {};
344
+ const fileConfig = (await req.fileManager.loadConfigSetting(fileKey, nodeId)) || {};
337
345
  // 从 fileManager 按 nodeId 加载 groupsData 和 promptSetting
338
346
  // const fileManager = new FileManager('d2c-mcp')
339
- const groupsData = await fileManager.loadGroupsData(fileKey, nodeId);
340
- const savedPromptSetting = await fileManager.loadPromptSetting(fileKey, nodeId);
347
+ const groupsData = await req.fileManager.loadGroupsData(fileKey, nodeId);
348
+ const savedPromptSetting = await req.fileManager.loadPromptSetting(fileKey, nodeId);
341
349
  let curFramework = fileConfig?.convertSetting?.framework;
342
350
  // 获取框架列表
343
351
  const frameworks = await configManager.getFrameworks();
@@ -681,11 +689,11 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
681
689
  // 使用 fileManager 按 nodeId 保存 groupsData 和 promptSetting
682
690
  // const fileManager = new FileManager('d2c-mcp')
683
691
  if (fileKey && value.groupsData && Array.isArray(value.groupsData)) {
684
- await fileManager.saveGroupsData(fileKey, nodeId, value.groupsData);
692
+ await req.fileManager.saveGroupsData(fileKey, nodeId, value.groupsData);
685
693
  Logger.log(`已保存 groupsData 到 fileKey "${fileKey}", nodeId "${nodeId}":`, value.groupsData.length, '个分组');
686
694
  }
687
695
  if (fileKey && value.promptSetting) {
688
- await fileManager.savePromptSetting(fileKey, nodeId, value.promptSetting);
696
+ await req.fileManager.savePromptSetting(fileKey, nodeId, value.promptSetting);
689
697
  Logger.log(`已保存 promptSetting 到 fileKey "${fileKey}", nodeId "${nodeId}"`);
690
698
  }
691
699
  // 如果有 MCP 配置,更新全局配置
@@ -766,9 +774,9 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
766
774
  /**
767
775
  * 获取项目根目录
768
776
  */
769
- function getProjectRoot() {
777
+ function getProjectRoot(fs) {
770
778
  try {
771
- const projectPath = fileManager.getWorkspaceRoot();
779
+ const projectPath = fs.getWorkspaceRoot();
772
780
  return projectPath || './';
773
781
  }
774
782
  catch (error) {
@@ -782,7 +790,7 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
782
790
  app.get('/getProjectFiles', async (req, res) => {
783
791
  try {
784
792
  const { directory = '' } = req.query;
785
- const projectPath = getProjectRoot();
793
+ const projectPath = getProjectRoot(req.fileManager);
786
794
  const targetPath = path.join(projectPath, directory);
787
795
  Logger.log(`获取项目文件树: ${targetPath}`);
788
796
  // 排除的文件扩展名(静态资源和配置文件)
@@ -912,12 +920,12 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
912
920
  return;
913
921
  }
914
922
  Logger.log(`批量分析 ${filePaths.length} 个项目文件`);
915
- const projectPath = getProjectRoot();
923
+ const projectPath = getProjectRoot(req.fileManager);
916
924
  // 尝试加载保存的提示词设置
917
925
  let savedPromptSetting = null;
918
926
  if (fileKey && nodeId) {
919
927
  try {
920
- savedPromptSetting = await fileManager.loadPromptSetting(fileKey, nodeId);
928
+ savedPromptSetting = await req.fileManager.loadPromptSetting(fileKey, nodeId);
921
929
  Logger.log(`已加载提示词设置: fileKey=${fileKey}, nodeId=${nodeId}`);
922
930
  }
923
931
  catch (error) {
@@ -1109,7 +1117,7 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
1109
1117
  }
1110
1118
  Logger.log(`准备保存 ${components.length} 个组件`);
1111
1119
  // 读取现有组件
1112
- const existingComponents = await fileManager.loadComponentsDatabase();
1120
+ const existingComponents = await req.fileManager.loadComponentsDatabase();
1113
1121
  const existingMap = new Map(existingComponents.map((c) => [c.id, c]));
1114
1122
  let addedCount = 0;
1115
1123
  let updatedCount = 0;
@@ -1128,7 +1136,7 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
1128
1136
  }
1129
1137
  const allComponents = Array.from(existingMap.values());
1130
1138
  // 保存到文件(带备份)
1131
- await fileManager.saveComponentsDatabase(allComponents);
1139
+ await req.fileManager.saveComponentsDatabase(allComponents);
1132
1140
  Logger.log(`✅ 成功保存:新增 ${addedCount} 个,更新 ${updatedCount} 个,共 ${allComponents.length} 个组件`);
1133
1141
  res.json({
1134
1142
  success: true,
@@ -1154,7 +1162,7 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
1154
1162
  app.get('/scanComponents', async (req, res) => {
1155
1163
  try {
1156
1164
  const { includePaths, excludePaths } = req.query;
1157
- const projectPath = getProjectRoot();
1165
+ const projectPath = getProjectRoot(req.fileManager);
1158
1166
  // 读取项目组件(从 .sloth/components.json)
1159
1167
  let projectComponents = [];
1160
1168
  const slothPath = path.join(projectPath, '.sloth', 'components.json');
@@ -1230,7 +1238,7 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
1230
1238
  const currentScreenshotPath = path.join(tempDir, `suggest_${Date.now()}.png`);
1231
1239
  await fs.promises.writeFile(currentScreenshotPath, currentScreenshotFile.buffer);
1232
1240
  // 从 components.json 加载所有组件
1233
- const components = await fileManager.loadComponentsDatabase();
1241
+ const components = await req.fileManager.loadComponentsDatabase();
1234
1242
  if (!components || components.length === 0) {
1235
1243
  Logger.log('未找到已保存的组件,无法生成建议');
1236
1244
  await fs.promises.unlink(currentScreenshotPath).catch(() => { });
@@ -1250,7 +1258,7 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
1250
1258
  continue;
1251
1259
  }
1252
1260
  // 根据 signature 搜索截图文件
1253
- const screenshotPath = await fileManager.findScreenshotByHash(component.signature);
1261
+ const screenshotPath = await req.fileManager.findScreenshotByHash(component.signature);
1254
1262
  if (!screenshotPath) {
1255
1263
  Logger.log(`组件 ${component.name} 的截图未找到 (hash: ${component.signature}),跳过`);
1256
1264
  continue;
@@ -1334,7 +1342,7 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
1334
1342
  }
1335
1343
  Logger.log(`接收到截图上传请求: fileKey=${fileKey}, nodeId=${nodeId}, hash=${hash}`);
1336
1344
  // 保存截图到 .sloth/{fileKey}/{nodeId}/screenshots/{hash}.png
1337
- await fileManager.saveScreenshot(fileKey, nodeId, hash, file.buffer);
1345
+ await req.fileManager.saveScreenshot(fileKey, nodeId, hash, file.buffer);
1338
1346
  Logger.log(`截图上传成功: ${hash}.png`);
1339
1347
  res.json({
1340
1348
  success: true,
@@ -1355,7 +1363,7 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
1355
1363
  app.get('/listDesignSnapshots', async (req, res) => {
1356
1364
  try {
1357
1365
  Logger.log('获取设计稿快照列表');
1358
- const workspaceRoot = getProjectRoot();
1366
+ const workspaceRoot = getProjectRoot(req.fileManager);
1359
1367
  Logger.log('workspaceRoot:', workspaceRoot);
1360
1368
  if (!workspaceRoot) {
1361
1369
  res.json({
@@ -1550,8 +1558,8 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
1550
1558
  return;
1551
1559
  }
1552
1560
  // 2. 加载新旧 groupsData
1553
- const oldGroupsData = (await fileManager.loadGroupsData(oldFileKey, oldNodeId)) || [];
1554
- const newGroupsData = (await fileManager.loadGroupsData(newFileKey, newNodeId)) || [];
1561
+ const oldGroupsData = (await req.fileManager.loadGroupsData(oldFileKey, oldNodeId)) || [];
1562
+ const newGroupsData = (await req.fileManager.loadGroupsData(newFileKey, newNodeId)) || [];
1555
1563
  // 3. 执行 HTML 差异分析
1556
1564
  // @ts-ignore
1557
1565
  const { diffLines } = await import('diff');
@@ -1770,8 +1778,9 @@ export async function getUserInput(payload) {
1770
1778
  // 连接到 Socket 服务器
1771
1779
  await socketClient.connect();
1772
1780
  Logger.log('Socket 客户端已连接');
1773
- // 注册 token 并等待响应
1774
- const responsePromise = socketClient.registerToken(token);
1781
+ const workspaceRoot = fileManager.getWorkspaceRoot();
1782
+ // 注册 token 并等待响应,同时传递 extra 数据给主进程
1783
+ const responsePromise = socketClient.registerToken(token, { workspaceRoot });
1775
1784
  // 打开浏览器
1776
1785
  await open(authUrl);
1777
1786
  // 等待认证响应
@@ -115,8 +115,10 @@ export class SocketClient {
115
115
  }
116
116
  /**
117
117
  * 注册 token,等待认证响应
118
+ * @param token 认证 token
119
+ * @param extra 额外数据(可选),如 { workspaceRoot: string, ... }
118
120
  */
119
- registerToken(token) {
121
+ registerToken(token, extra) {
120
122
  return new Promise((resolve, reject) => {
121
123
  // 设置超时(可选,根据需求调整)
122
124
  const timeout = setTimeout(() => {
@@ -132,9 +134,10 @@ export class SocketClient {
132
134
  this.send({
133
135
  type: 'register-token',
134
136
  token,
137
+ extra,
135
138
  timestamp: Date.now(),
136
139
  });
137
- Logger.log(`已注册 token: ${token},等待认证响应...`);
140
+ Logger.log(`已注册 token: ${token},extra: ${JSON.stringify(extra)},等待认证响应...`);
138
141
  });
139
142
  }
140
143
  /**
@@ -8,6 +8,7 @@ export class SocketServer {
8
8
  server = null;
9
9
  connections = new Set();
10
10
  tokenSockets = new Map(); // token -> socket 映射
11
+ tokenExtras = new Map(); // token -> extra 数据映射
11
12
  messageBuffers = new Map(); // socket -> 消息缓冲区
12
13
  port = 0;
13
14
  constructor() {
@@ -42,6 +43,7 @@ export class SocketServer {
42
43
  for (const [token, sock] of this.tokenSockets.entries()) {
43
44
  if (sock === socket) {
44
45
  this.tokenSockets.delete(token);
46
+ this.tokenExtras.delete(token);
45
47
  Logger.log(`清理 token: ${token}`);
46
48
  }
47
49
  }
@@ -95,6 +97,11 @@ export class SocketServer {
95
97
  // 注册 token,建立 token -> socket 映射
96
98
  if (message.token) {
97
99
  this.tokenSockets.set(message.token, socket);
100
+ // 保存 extra 数据(如果提供)
101
+ if (message.extra && typeof message.extra === 'object') {
102
+ this.tokenExtras.set(message.token, message.extra);
103
+ Logger.log(`已保存 extra 数据: ${message.token} -> ${JSON.stringify(message.extra)}`);
104
+ }
98
105
  Logger.log(`已注册 token: ${message.token} -> ${clientId}`);
99
106
  this.sendMessage(socket, {
100
107
  type: 'token-registered',
@@ -204,6 +211,7 @@ export class SocketServer {
204
211
  }
205
212
  this.connections.clear();
206
213
  this.tokenSockets.clear();
214
+ this.tokenExtras.clear();
207
215
  this.messageBuffers.clear();
208
216
  // 关闭服务器
209
217
  if (this.server) {
@@ -230,4 +238,23 @@ export class SocketServer {
230
238
  getPort() {
231
239
  return this.port;
232
240
  }
241
+ /**
242
+ * 根据 token 获取对应的 extra 数据
243
+ */
244
+ getTokenExtra(token) {
245
+ return this.tokenExtras.get(token);
246
+ }
247
+ /**
248
+ * 根据 token 获取 extra 中的特定字段
249
+ */
250
+ getTokenExtraField(token, field) {
251
+ const extra = this.tokenExtras.get(token);
252
+ return extra?.[field];
253
+ }
254
+ /**
255
+ * 获取所有已注册的 token extra 映射
256
+ */
257
+ getAllTokenExtras() {
258
+ return new Map(this.tokenExtras);
259
+ }
233
260
  }
@@ -2,6 +2,7 @@ import { promises as fs } from 'fs';
2
2
  import * as path from 'path';
3
3
  import envPaths from 'env-paths';
4
4
  import { Logger } from '../utils/logger.js';
5
+ import { httpServer, socketServer } from '../server.js';
5
6
  /**
6
7
  * 通用文件管理器
7
8
  * 可以保存任何类型的文件,使用fileKey和nodeId组织目录结构
@@ -9,11 +10,23 @@ import { Logger } from '../utils/logger.js';
9
10
  export class FileManager {
10
11
  paths; // 应用路径配置
11
12
  baseDir; // 基础存储目录
12
- workspaceRoot = null; // MCP 工作目录根路径
13
+ _workspaceRoot = null; // MCP 工作目录根路径
14
+ _uuid = '';
13
15
  constructor(appName) {
14
16
  this.paths = envPaths(appName);
15
17
  this.baseDir = path.join(this.paths.data, 'files');
16
18
  }
19
+ get workspaceRoot() {
20
+ const isMainProcess = httpServer !== null;
21
+ if (isMainProcess && this._uuid) {
22
+ const workspaceRoot = socketServer?.getTokenExtraField(this._uuid, 'workspaceRoot');
23
+ return workspaceRoot || this._workspaceRoot;
24
+ }
25
+ return this._workspaceRoot;
26
+ }
27
+ set workspaceRoot(rootPath) {
28
+ this._workspaceRoot = rootPath;
29
+ }
17
30
  /**
18
31
  * 设置工作目录根路径(用于 MCP 工作项目)
19
32
  * @param rootPath - 工作目录根路径
@@ -786,5 +799,19 @@ export class FileManager {
786
799
  return [];
787
800
  }
788
801
  }
802
+ /**
803
+ * 创建一个临时的 FileManager 代理,仅在本次链式调用中使用指定的 uuid
804
+ * 不会影响原始实例的 _uuid 值
805
+ * @param uuid - 临时使用的 uuid
806
+ * @returns 带有临时 uuid 的 FileManager 代理对象
807
+ */
808
+ withUUID(uuid) {
809
+ if (!uuid) {
810
+ return this;
811
+ }
812
+ const proxy = Object.create(this);
813
+ proxy._uuid = uuid;
814
+ return proxy;
815
+ }
789
816
  }
790
817
  export default FileManager;
@@ -176,3 +176,9 @@ export const formatListRoots = (rootRes) => {
176
176
  }
177
177
  return root;
178
178
  };
179
+ export const getParam = (url, key) => {
180
+ if (!url)
181
+ return '';
182
+ const urlObj = new URL(url);
183
+ return urlObj.searchParams.get(key);
184
+ };
@@ -1,5 +1,5 @@
1
1
  {
2
- "buildTime": "2025-12-28T08:50:18.240Z",
2
+ "buildTime": "2025-12-31T04:53:16.839Z",
3
3
  "mode": "build",
4
4
  "pages": {
5
5
  "main": {