sloth-d2c-mcp 1.0.4-beta82 → 1.0.4-beta84

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.
@@ -0,0 +1,143 @@
1
+ import { Logger } from './logger.js';
2
+ // 缓存检测结果,避免重复检测
3
+ let cachedCapabilities = null;
4
+ /**
5
+ * 检测 MCP Client 的 API 支持程度
6
+ * @param mcpServer MCP 服务器实例
7
+ * @param forceRecheck 是否强制重新检测(忽略缓存)
8
+ * @returns ClientApiSupport 对象
9
+ */
10
+ export async function detectClientApiSupport(mcpServer, forceRecheck = false) {
11
+ // 使用缓存
12
+ if (cachedCapabilities && !forceRecheck) {
13
+ return cachedCapabilities;
14
+ }
15
+ const support = {
16
+ sampling: false,
17
+ roots: false,
18
+ };
19
+ try {
20
+ // 方法1: 通过 capabilities 协商机制检测
21
+ // MCP SDK 在连接时会进行能力协商
22
+ const serverInstance = mcpServer.server;
23
+ // 尝试获取客户端能力
24
+ if (typeof serverInstance.getClientCapabilities === 'function') {
25
+ const clientCapabilities = serverInstance.getClientCapabilities();
26
+ Logger.log('客户端能力:', JSON.stringify(clientCapabilities, null, 2));
27
+ if (clientCapabilities) {
28
+ // 检查 sampling 能力
29
+ support.sampling = clientCapabilities.sampling !== undefined;
30
+ // 检查 roots 能力
31
+ support.roots = clientCapabilities.roots !== undefined;
32
+ }
33
+ }
34
+ // 方法2: 如果 capabilities 检测不到,通过 try-catch 探测
35
+ // 检测 roots 支持
36
+ if (!support.roots) {
37
+ support.roots = await probeRootsSupport(mcpServer);
38
+ }
39
+ // 检测 sampling 支持(仅当 capabilities 未声明时才探测)
40
+ if (!support.sampling) {
41
+ support.sampling = await probeSamplingSupport(mcpServer);
42
+ }
43
+ }
44
+ catch (error) {
45
+ Logger.log('检测客户端能力时出错:', error);
46
+ }
47
+ // 缓存结果
48
+ cachedCapabilities = support;
49
+ Logger.log('MCP Client API 支持检测结果:', support);
50
+ return support;
51
+ }
52
+ /**
53
+ * 探测 listRoots API 支持
54
+ */
55
+ async function probeRootsSupport(mcpServer) {
56
+ try {
57
+ const result = await mcpServer.server.listRoots();
58
+ // 如果调用成功,说明支持
59
+ return result !== undefined;
60
+ }
61
+ catch (error) {
62
+ // 检查错误类型
63
+ const errorMessage = error?.message || String(error);
64
+ const errorCode = error?.code;
65
+ // Method not found 或明确不支持的错误
66
+ if (errorCode === -32601 ||
67
+ errorMessage.includes('not supported') ||
68
+ errorMessage.includes('not implemented') ||
69
+ errorMessage.includes('Method not found')) {
70
+ return false;
71
+ }
72
+ // 其他错误(如超时、网络问题)可能是暂时性的,保守认为支持
73
+ Logger.log('探测 listRoots 时出现非致命错误:', errorMessage);
74
+ return false;
75
+ }
76
+ }
77
+ /**
78
+ * 探测 createMessage (sampling) API 支持
79
+ * 注意:这个探测可能会产生实际的 API 调用,应谨慎使用
80
+ */
81
+ async function probeSamplingSupport(mcpServer) {
82
+ try {
83
+ // 使用最小化的测试请求
84
+ // 注意:某些客户端可能会实际执行这个请求,所以使用非常小的 maxTokens
85
+ await mcpServer.server.createMessage({
86
+ messages: [
87
+ {
88
+ role: 'user',
89
+ content: {
90
+ type: 'text',
91
+ text: 'ping', // 最小化测试消息
92
+ },
93
+ },
94
+ ],
95
+ maxTokens: 1, // 最小化 token 消耗
96
+ }, {
97
+ timeout: 10000, // 10秒超时
98
+ });
99
+ // 如果调用成功,说明支持
100
+ return true;
101
+ }
102
+ catch (error) {
103
+ const errorMessage = error?.message || String(error);
104
+ const errorCode = error?.code;
105
+ // Method not found 或明确不支持的错误
106
+ if (errorCode === -32601 ||
107
+ errorMessage.includes('not supported') ||
108
+ errorMessage.includes('not implemented') ||
109
+ errorMessage.includes('Method not found') ||
110
+ errorMessage.includes('Sampling not supported')) {
111
+ Logger.log('客户端不支持 sampling API');
112
+ return false;
113
+ }
114
+ // 超时或其他错误,可能是支持但调用失败
115
+ // 保守处理:认为不支持,让业务逻辑走降级路径
116
+ Logger.log('探测 sampling 时出现错误:', errorMessage);
117
+ return false;
118
+ }
119
+ }
120
+ /**
121
+ * 清除缓存的能力检测结果
122
+ * 在重新连接时应调用此方法
123
+ */
124
+ export function clearCapabilitiesCache() {
125
+ cachedCapabilities = null;
126
+ Logger.log('已清除客户端能力缓存');
127
+ }
128
+ /**
129
+ * 检查是否支持 sampling
130
+ * 便捷方法,直接返回 boolean
131
+ */
132
+ export async function isSamplingSupported(mcpServer) {
133
+ const support = await detectClientApiSupport(mcpServer);
134
+ return support.sampling;
135
+ }
136
+ /**
137
+ * 检查是否支持 roots
138
+ * 便捷方法,直接返回 boolean
139
+ */
140
+ export async function isRootsSupported(mcpServer) {
141
+ const support = await detectClientApiSupport(mcpServer);
142
+ return support.roots;
143
+ }
@@ -23,7 +23,7 @@ export class FileManager {
23
23
  Logger.log(`已设置工作目录根路径: ${rootPath}`);
24
24
  }
25
25
  getWorkspaceRoot() {
26
- return this.workspaceRoot;
26
+ return this.workspaceRoot || './';
27
27
  }
28
28
  /**
29
29
  * 生成文件路径
@@ -588,9 +588,6 @@ export class FileManager {
588
588
  */
589
589
  async saveComponentsDatabase(components) {
590
590
  const workspaceRoot = this.getWorkspaceRoot();
591
- if (!workspaceRoot) {
592
- throw new Error('工作目录根路径未设置,无法保存组件数据库');
593
- }
594
591
  const slothDir = path.join(workspaceRoot, '.sloth');
595
592
  const componentsPath = path.join(slothDir, 'components.json');
596
593
  // 确保目录存在
@@ -1,5 +1,22 @@
1
1
  import { Logger } from './logger.js';
2
2
  import { getResourceMapAdvanced } from 'sloth-d2c-node/convert';
3
+ import { replacePathVariables } from './webpack-substitutions.js';
4
+ import { getSystemSupportFileName } from './utils.js';
5
+ import { sha1 } from 'js-sha1';
6
+ /**
7
+ * 移除 base64 字符串的前缀(如 data:image/png;base64,),只保留纯 base64 数据
8
+ * @param base64String 带前缀的 base64 字符串
9
+ * @returns 纯 base64 数据
10
+ */
11
+ export function removeBase64Prefix(base64String) {
12
+ if (!base64String)
13
+ return '';
14
+ const commaIndex = base64String.indexOf(',');
15
+ if (commaIndex !== -1) {
16
+ return base64String.slice(commaIndex + 1);
17
+ }
18
+ return base64String;
19
+ }
3
20
  /**
4
21
  * 检查imageSetting配置是否有变化,如果有变化则重新生成imageMap
5
22
  * @param currentImageMap 当前的imageMap
@@ -8,7 +25,36 @@ import { getResourceMapAdvanced } from 'sloth-d2c-node/convert';
8
25
  * @param newConfig 新的配置
9
26
  * @returns Promise<{ imageMap: any, updated: boolean }> - 返回新的imageMap和是否更新的标志
10
27
  */
11
- export async function updateImageMapIfNeeded(currentImageMap, imageNodeList, oldConfig, newConfig) {
28
+ export async function updateImageMapIfNeeded(currentImageMap, imageNodeList, oldConfig, newConfig, local) {
29
+ if (local) {
30
+ Logger.log('本地模式,直接更新imageMap');
31
+ for (let node of imageNodeList) {
32
+ const type = node.srcType === 'PNG' ? 'png' : 'svg';
33
+ const base64 = removeBase64Prefix(currentImageMap[node.id]?.base64);
34
+ const { imageStorageType, imageStorageNamingRule, imageStoragePath } = newConfig?.imageSetting;
35
+ if (imageStorageType === 'local') {
36
+ let webpackPathRule = imageStorageNamingRule;
37
+ if (!webpackPathRule.endsWith('.[ext]')) {
38
+ webpackPathRule += '.[ext]';
39
+ }
40
+ const name = replacePathVariables(webpackPathRule, {
41
+ filename: [getSystemSupportFileName(node.name), type].join('.'),
42
+ chunk: {
43
+ id: node.id,
44
+ name: getSystemSupportFileName(node.name),
45
+ contentHash: {
46
+ [type]: sha1(base64).substring(0, 8),
47
+ },
48
+ },
49
+ contentHashType: type,
50
+ }, undefined);
51
+ const path = [imageStoragePath, name].join('/');
52
+ node.src = path;
53
+ currentImageMap[node.id].path = path;
54
+ }
55
+ }
56
+ return { imageMap: currentImageMap, updated: true };
57
+ }
12
58
  // 提取相关的imageSetting配置项
13
59
  const oldImageSetting = oldConfig?.imageSetting || {};
14
60
  const newImageSetting = newConfig?.imageSetting || {};
@@ -29,10 +75,10 @@ export async function updateImageMapIfNeeded(currentImageMap, imageNodeList, old
29
75
  'imageStorageApiFileField',
30
76
  'imageStorageApiUrlField',
31
77
  'imageStorageApiCustomHeader',
32
- 'imageStorageApiCustomBody'
78
+ 'imageStorageApiCustomBody',
33
79
  ];
34
80
  // 检查是否有相关配置发生变化
35
- const hasImageSettingChanged = relevantKeys.some(key => {
81
+ const hasImageSettingChanged = relevantKeys.some((key) => {
36
82
  return oldImageSetting[key] !== newImageSetting[key];
37
83
  });
38
84
  if (!hasImageSettingChanged) {
@@ -40,7 +86,7 @@ export async function updateImageMapIfNeeded(currentImageMap, imageNodeList, old
40
86
  return { imageMap: currentImageMap, updated: false };
41
87
  }
42
88
  Logger.log('检测到imageSetting配置变化,重新生成imageMap');
43
- Logger.log('变化的配置项:', relevantKeys.filter(key => oldImageSetting[key] !== newImageSetting[key]));
89
+ Logger.log('变化的配置项:', relevantKeys.filter((key) => oldImageSetting[key] !== newImageSetting[key]));
44
90
  try {
45
91
  // 重新生成imageMap
46
92
  const newImageMap = await getResourceMapAdvanced(imageNodeList, newConfig);
@@ -1,18 +1,18 @@
1
1
  {
2
- "buildTime": "2025-12-23T05:35:46.297Z",
2
+ "buildTime": "2025-12-25T13:17:35.078Z",
3
3
  "mode": "build",
4
4
  "pages": {
5
5
  "main": {
6
6
  "file": "index.html",
7
- "size": 1628662,
8
- "sizeFormatted": "1.55 MB"
7
+ "size": 1642308,
8
+ "sizeFormatted": "1.57 MB"
9
9
  },
10
10
  "detail": {
11
11
  "file": "detail.html",
12
- "size": 281671,
13
- "sizeFormatted": "275.07 KB"
12
+ "size": 281799,
13
+ "sizeFormatted": "275.19 KB"
14
14
  }
15
15
  },
16
- "totalSize": 1910333,
17
- "totalSizeFormatted": "1.82 MB"
16
+ "totalSize": 1924107,
17
+ "totalSizeFormatted": "1.83 MB"
18
18
  }