sloth-d2c-mcp 1.0.4-beta87 → 1.0.4-beta90

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,12 @@ 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';
36
+ import { listWorkspacePlugins } from './plugin/loader.js';
35
37
  // 保存 HTTP 服务器实例
36
- let httpServer = null;
38
+ export let httpServer = null;
37
39
  // 保存 Socket 服务器实例
38
- let socketServer = null;
40
+ export let socketServer = null;
39
41
  // 管理所有活跃的传输对象,按 sessionId 分类
40
42
  const transports = {
41
43
  streamable: {}, // 流式 HTTP 传输
@@ -196,6 +198,14 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
196
198
  next();
197
199
  });
198
200
  // app.use(express.json());
201
+ app.use((req, _res, next) => {
202
+ // 通过referer取url上面的uuid
203
+ const uuid = getParam(req.headers.referer || '', 'token') || '';
204
+ req.uuid = uuid;
205
+ req.fileManager = fileManager.withUUID(uuid);
206
+ req.workspaceRoot = req.fileManager.getWorkspaceRoot();
207
+ next();
208
+ });
199
209
  // 处理流式 HTTP 的 POST 请求,支持会话复用和初始化
200
210
  app.post('/mcp', async (req, res) => {
201
211
  Logger.log('Received StreamableHTTP request');
@@ -329,15 +339,16 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
329
339
  const fileKey = req.query.fileKey;
330
340
  const nodeId = req.query.nodeId;
331
341
  const framework = req.query.framework;
342
+ const plugins = await listWorkspacePlugins(req.workspaceRoot);
332
343
  if (fileKey) {
333
344
  // 如果提供了 fileKey,返回该 fileKey 的特定配置
334
345
  const globalConfig = await configManager.load();
335
346
  // const fileConfig = globalConfig.fileConfigs?.[fileKey] || {}
336
- const fileConfig = (await fileManager.loadConfigSetting(fileKey, nodeId)) || {};
347
+ const fileConfig = (await req.fileManager.loadConfigSetting(fileKey, nodeId)) || {};
337
348
  // 从 fileManager 按 nodeId 加载 groupsData 和 promptSetting
338
349
  // const fileManager = new FileManager('d2c-mcp')
339
- const groupsData = await fileManager.loadGroupsData(fileKey, nodeId);
340
- const savedPromptSetting = await fileManager.loadPromptSetting(fileKey, nodeId);
350
+ const groupsData = await req.fileManager.loadGroupsData(fileKey, nodeId);
351
+ const savedPromptSetting = await req.fileManager.loadPromptSetting(fileKey, nodeId);
341
352
  let curFramework = fileConfig?.convertSetting?.framework;
342
353
  // 获取框架列表
343
354
  const frameworks = await configManager.getFrameworks();
@@ -393,6 +404,7 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
393
404
  defaultFramework: globalConfig.defaultFramework || 'react',
394
405
  fileKey: fileKey,
395
406
  groupsData: groupsData,
407
+ plugins,
396
408
  },
397
409
  });
398
410
  }
@@ -433,6 +445,7 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
433
445
  imageSetting: configStorage.imageSetting || {},
434
446
  promptSetting: promptSetting,
435
447
  frameworks: frameworks,
448
+ plugins,
436
449
  },
437
450
  });
438
451
  }
@@ -681,11 +694,11 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
681
694
  // 使用 fileManager 按 nodeId 保存 groupsData 和 promptSetting
682
695
  // const fileManager = new FileManager('d2c-mcp')
683
696
  if (fileKey && value.groupsData && Array.isArray(value.groupsData)) {
684
- await fileManager.saveGroupsData(fileKey, nodeId, value.groupsData);
697
+ await req.fileManager.saveGroupsData(fileKey, nodeId, value.groupsData);
685
698
  Logger.log(`已保存 groupsData 到 fileKey "${fileKey}", nodeId "${nodeId}":`, value.groupsData.length, '个分组');
686
699
  }
687
700
  if (fileKey && value.promptSetting) {
688
- await fileManager.savePromptSetting(fileKey, nodeId, value.promptSetting);
701
+ await req.fileManager.savePromptSetting(fileKey, nodeId, value.promptSetting);
689
702
  Logger.log(`已保存 promptSetting 到 fileKey "${fileKey}", nodeId "${nodeId}"`);
690
703
  }
691
704
  // 如果有 MCP 配置,更新全局配置
@@ -766,9 +779,9 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
766
779
  /**
767
780
  * 获取项目根目录
768
781
  */
769
- function getProjectRoot() {
782
+ function getProjectRoot(fs) {
770
783
  try {
771
- const projectPath = fileManager.getWorkspaceRoot();
784
+ const projectPath = fs.getWorkspaceRoot();
772
785
  return projectPath || './';
773
786
  }
774
787
  catch (error) {
@@ -782,7 +795,7 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
782
795
  app.get('/getProjectFiles', async (req, res) => {
783
796
  try {
784
797
  const { directory = '' } = req.query;
785
- const projectPath = getProjectRoot();
798
+ const projectPath = getProjectRoot(req.fileManager);
786
799
  const targetPath = path.join(projectPath, directory);
787
800
  Logger.log(`获取项目文件树: ${targetPath}`);
788
801
  // 排除的文件扩展名(静态资源和配置文件)
@@ -912,12 +925,12 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
912
925
  return;
913
926
  }
914
927
  Logger.log(`批量分析 ${filePaths.length} 个项目文件`);
915
- const projectPath = getProjectRoot();
928
+ const projectPath = getProjectRoot(req.fileManager);
916
929
  // 尝试加载保存的提示词设置
917
930
  let savedPromptSetting = null;
918
931
  if (fileKey && nodeId) {
919
932
  try {
920
- savedPromptSetting = await fileManager.loadPromptSetting(fileKey, nodeId);
933
+ savedPromptSetting = await req.fileManager.loadPromptSetting(fileKey, nodeId);
921
934
  Logger.log(`已加载提示词设置: fileKey=${fileKey}, nodeId=${nodeId}`);
922
935
  }
923
936
  catch (error) {
@@ -1109,7 +1122,7 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
1109
1122
  }
1110
1123
  Logger.log(`准备保存 ${components.length} 个组件`);
1111
1124
  // 读取现有组件
1112
- const existingComponents = await fileManager.loadComponentsDatabase();
1125
+ const existingComponents = await req.fileManager.loadComponentsDatabase();
1113
1126
  const existingMap = new Map(existingComponents.map((c) => [c.id, c]));
1114
1127
  let addedCount = 0;
1115
1128
  let updatedCount = 0;
@@ -1128,7 +1141,7 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
1128
1141
  }
1129
1142
  const allComponents = Array.from(existingMap.values());
1130
1143
  // 保存到文件(带备份)
1131
- await fileManager.saveComponentsDatabase(allComponents);
1144
+ await req.fileManager.saveComponentsDatabase(allComponents);
1132
1145
  Logger.log(`✅ 成功保存:新增 ${addedCount} 个,更新 ${updatedCount} 个,共 ${allComponents.length} 个组件`);
1133
1146
  res.json({
1134
1147
  success: true,
@@ -1154,7 +1167,7 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
1154
1167
  app.get('/scanComponents', async (req, res) => {
1155
1168
  try {
1156
1169
  const { includePaths, excludePaths } = req.query;
1157
- const projectPath = getProjectRoot();
1170
+ const projectPath = getProjectRoot(req.fileManager);
1158
1171
  // 读取项目组件(从 .sloth/components.json)
1159
1172
  let projectComponents = [];
1160
1173
  const slothPath = path.join(projectPath, '.sloth', 'components.json');
@@ -1230,7 +1243,7 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
1230
1243
  const currentScreenshotPath = path.join(tempDir, `suggest_${Date.now()}.png`);
1231
1244
  await fs.promises.writeFile(currentScreenshotPath, currentScreenshotFile.buffer);
1232
1245
  // 从 components.json 加载所有组件
1233
- const components = await fileManager.loadComponentsDatabase();
1246
+ const components = await req.fileManager.loadComponentsDatabase();
1234
1247
  if (!components || components.length === 0) {
1235
1248
  Logger.log('未找到已保存的组件,无法生成建议');
1236
1249
  await fs.promises.unlink(currentScreenshotPath).catch(() => { });
@@ -1250,7 +1263,7 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
1250
1263
  continue;
1251
1264
  }
1252
1265
  // 根据 signature 搜索截图文件
1253
- const screenshotPath = await fileManager.findScreenshotByHash(component.signature);
1266
+ const screenshotPath = await req.fileManager.findScreenshotByHash(component.signature);
1254
1267
  if (!screenshotPath) {
1255
1268
  Logger.log(`组件 ${component.name} 的截图未找到 (hash: ${component.signature}),跳过`);
1256
1269
  continue;
@@ -1334,7 +1347,7 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
1334
1347
  }
1335
1348
  Logger.log(`接收到截图上传请求: fileKey=${fileKey}, nodeId=${nodeId}, hash=${hash}`);
1336
1349
  // 保存截图到 .sloth/{fileKey}/{nodeId}/screenshots/{hash}.png
1337
- await fileManager.saveScreenshot(fileKey, nodeId, hash, file.buffer);
1350
+ await req.fileManager.saveScreenshot(fileKey, nodeId, hash, file.buffer);
1338
1351
  Logger.log(`截图上传成功: ${hash}.png`);
1339
1352
  res.json({
1340
1353
  success: true,
@@ -1355,7 +1368,7 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
1355
1368
  app.get('/listDesignSnapshots', async (req, res) => {
1356
1369
  try {
1357
1370
  Logger.log('获取设计稿快照列表');
1358
- const workspaceRoot = getProjectRoot();
1371
+ const workspaceRoot = getProjectRoot(req.fileManager);
1359
1372
  Logger.log('workspaceRoot:', workspaceRoot);
1360
1373
  if (!workspaceRoot) {
1361
1374
  res.json({
@@ -1550,8 +1563,8 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
1550
1563
  return;
1551
1564
  }
1552
1565
  // 2. 加载新旧 groupsData
1553
- const oldGroupsData = (await fileManager.loadGroupsData(oldFileKey, oldNodeId)) || [];
1554
- const newGroupsData = (await fileManager.loadGroupsData(newFileKey, newNodeId)) || [];
1566
+ const oldGroupsData = (await req.fileManager.loadGroupsData(oldFileKey, oldNodeId)) || [];
1567
+ const newGroupsData = (await req.fileManager.loadGroupsData(newFileKey, newNodeId)) || [];
1555
1568
  // 3. 执行 HTML 差异分析
1556
1569
  // @ts-ignore
1557
1570
  const { diffLines } = await import('diff');
@@ -1770,8 +1783,9 @@ export async function getUserInput(payload) {
1770
1783
  // 连接到 Socket 服务器
1771
1784
  await socketClient.connect();
1772
1785
  Logger.log('Socket 客户端已连接');
1773
- // 注册 token 并等待响应
1774
- const responsePromise = socketClient.registerToken(token);
1786
+ const workspaceRoot = fileManager.getWorkspaceRoot();
1787
+ // 注册 token 并等待响应,同时传递 extra 数据给主进程
1788
+ const responsePromise = socketClient.registerToken(token, { workspaceRoot });
1775
1789
  // 打开浏览器
1776
1790
  await open(authUrl);
1777
1791
  // 等待认证响应
@@ -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,10 +1,10 @@
1
1
  {
2
- "buildTime": "2025-12-28T08:50:18.240Z",
2
+ "buildTime": "2026-01-07T02:01:53.150Z",
3
3
  "mode": "build",
4
4
  "pages": {
5
5
  "main": {
6
6
  "file": "index.html",
7
- "size": 1642308,
7
+ "size": 1643352,
8
8
  "sizeFormatted": "1.57 MB"
9
9
  },
10
10
  "detail": {
@@ -13,6 +13,6 @@
13
13
  "sizeFormatted": "275.19 KB"
14
14
  }
15
15
  },
16
- "totalSize": 1924107,
17
- "totalSizeFormatted": "1.83 MB"
16
+ "totalSize": 1925151,
17
+ "totalSizeFormatted": "1.84 MB"
18
18
  }