@douyinfe/semi-mcp 1.0.2 → 1.0.4

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.
package/README.md CHANGED
@@ -1,38 +1,40 @@
1
+ [中文](README-zh_CN.md) | [English](README.md)
2
+
1
3
  # Semi MCP Server
2
4
 
3
- 基于 Model Context Protocol (MCP) SDK 实现的 MCP 服务器,提供 Semi Design 组件文档和组件列表查询功能。
5
+ An MCP (Model Context Protocol) server implementation based on the MCP SDK, providing Semi Design component documentation and component list query functionality.
4
6
 
5
- ## 简介
7
+ ## Introduction
6
8
 
7
- Semi MCP Server 是一个 MCP (Model Context Protocol) 服务器,通过 stdio 传输层与支持 MCP 协议的客户端通信。它提供了获取 Semi Design 组件文档、组件列表等功能。
9
+ Semi MCP Server is an MCP (Model Context Protocol) server that communicates with MCP-compatible clients through stdio transport. It provides functionality to fetch Semi Design component documentation, component lists, and more.
8
10
 
9
- ## 安装
11
+ ## Installation
10
12
 
11
- ### 全局安装
13
+ ### Global Installation
12
14
 
13
15
  ```bash
14
16
  npm install -g @douyinfe/semi-mcp
15
17
  ```
16
18
 
17
- ### 本地安装
19
+ ### Local Installation
18
20
 
19
21
  ```bash
20
22
  npm install @douyinfe/semi-mcp
21
23
  ```
22
24
 
23
- ## 使用方法
25
+ ## Usage
24
26
 
25
- ### 作为命令行工具
27
+ ### As a Command Line Tool
26
28
 
27
- 全局安装后,可以直接使用:
29
+ After global installation, you can use it directly:
28
30
 
29
31
  ```bash
30
32
  semi-mcp
31
33
  ```
32
34
 
33
- ### MCP 客户端中配置
35
+ ### Configuration in MCP Clients
34
36
 
35
- 在支持 MCP 的客户端(如 Claude Desktop)中配置:
37
+ Configure in MCP-compatible clients (such as Claude Desktop):
36
38
 
37
39
  ```json
38
40
  {
@@ -45,7 +47,7 @@ semi-mcp
45
47
  }
46
48
  ```
47
49
 
48
- 或者如果已全局安装:
50
+ Or if installed globally:
49
51
 
50
52
  ```json
51
53
  {
@@ -57,28 +59,29 @@ semi-mcp
57
59
  }
58
60
  ```
59
61
 
60
- ## 功能
62
+ ## Features
61
63
 
62
- ### 工具 (Tools)
64
+ ### Tools
63
65
 
64
66
  #### `get_semi_document`
65
67
 
66
- 获取 Semi Design 组件文档或组件列表。
68
+ Get Semi Design component documentation or component list.
67
69
 
68
- **参数:**
69
- - `componentName` (可选): 组件名称,例如 `Button`、`Input` 等。如果不提供,则返回组件列表
70
- - `version` (可选): 版本号,例如 `2.89.2-alpha.3`。如果不提供,默认使用 `latest`
70
+ **Parameters:**
71
+ - `componentName` (optional): Component name, e.g., `Button`, `Input`, etc. If not provided, returns the component list
72
+ - `version` (optional): Version number, e.g., `2.89.2-alpha.3`. If not provided, defaults to `latest`
73
+ - `get_path` (optional): If `true`, saves documents to the system temporary directory and returns the path instead of returning document content in the response. Defaults to `false`
71
74
 
72
- **示例:**
75
+ **Examples:**
73
76
 
74
- 获取组件列表:
77
+ Get component list:
75
78
  ```json
76
79
  {
77
80
  "name": "get_semi_document"
78
81
  }
79
82
  ```
80
83
 
81
- 获取指定组件文档:
84
+ Get specific component documentation:
82
85
  ```json
83
86
  {
84
87
  "name": "get_semi_document",
@@ -89,115 +92,137 @@ semi-mcp
89
92
  }
90
93
  ```
91
94
 
92
- **返回格式:**
95
+ **Response Format:**
93
96
 
94
- 获取组件列表时:
95
- ```json
96
- {
97
- "version": "2.89.2-alpha.3",
98
- "components": ["button", "input", "select", ...],
99
- "count": 70
100
- }
97
+ All responses are returned as plain text for AI-friendly consumption.
98
+
99
+ When getting component list:
101
100
  ```
101
+ Semi Design 组件列表 (版本 2.89.2-alpha.3),共 70 个组件:
102
102
 
103
- 获取组件文档时:
104
- ```json
105
- {
106
- "componentName": "button",
107
- "version": "2.89.2-alpha.3",
108
- "category": "basic",
109
- "documents": ["index.md", "index-en-us.md"],
110
- "count": 2,
111
- "allComponents": ["button", "input", "select", ...],
112
- "allComponentsCount": 70
113
- }
103
+ button, input, select, table, ...
104
+ ```
105
+
106
+ When getting small component documentation (< 888 lines):
107
+ ```
108
+ ===== index.md =====
109
+
110
+ ---
111
+ title: Button
112
+ ...
113
+ ---
114
+
115
+ ## Usage
116
+ ...
117
+
118
+ ===== index-en-US.md =====
119
+
120
+ ---
121
+ title: Button
122
+ ...
123
+ ---
124
+
125
+ ## Usage
126
+ ...
127
+ ```
128
+
129
+ When getting large component documentation (> 888 lines), the tool automatically saves to temp directory:
130
+ ```
131
+ 组件 Table (版本 2.89.2-alpha.3) 文档较大,已保存到临时目录。
132
+
133
+ 文档文件列表:
134
+ - /tmp/semi-docs-table-2.89.2-alpha.3-1234567890/index.md (6,055 行)
135
+ - /tmp/semi-docs-table-2.89.2-alpha.3-1234567890/index-en-US.md (5,660 行)
136
+
137
+ 请使用文件读取工具查看文档内容。
114
138
  ```
115
139
 
116
- ### 资源 (Resources)
140
+ ### Resources
117
141
 
118
142
  #### `semi://components`
119
143
 
120
- Semi Design 组件列表资源。
144
+ Semi Design component list resource.
121
145
 
122
- ## 开发
146
+ ## Development
123
147
 
124
- ### 环境要求
148
+ ### Requirements
125
149
 
126
150
  - Node.js >= 18.0.0
127
- - npm yarn
151
+ - npm or yarn
128
152
 
129
- ### 安装依赖
153
+ ### Install Dependencies
130
154
 
131
155
  ```bash
132
156
  npm install
133
157
  ```
134
158
 
135
- ### 构建
159
+ ### Build
136
160
 
137
- 构建生产版本:
161
+ Build production version:
138
162
 
139
163
  ```bash
140
164
  npm run build
141
165
  ```
142
166
 
143
- 开发模式(监听文件变化并自动重建):
167
+ Development mode (watch for file changes and auto-rebuild):
144
168
 
145
169
  ```bash
146
170
  npm run dev
147
171
  ```
148
172
 
149
- ### 测试
173
+ ### Test
150
174
 
151
- 运行测试:
175
+ Run tests:
152
176
 
153
177
  ```bash
154
178
  npm test
155
179
  ```
156
180
 
157
- ### 运行
181
+ ### Run
158
182
 
159
- 构建完成后运行服务器:
183
+ Run the server after building:
160
184
 
161
185
  ```bash
162
186
  npm start
163
187
  ```
164
188
 
165
- 或者直接运行构建后的文件:
189
+ Or run the built file directly:
166
190
 
167
191
  ```bash
168
192
  node dist/index.js
169
193
  ```
170
194
 
171
- ## 技术栈
195
+ ## Tech Stack
172
196
 
173
- - **TypeScript**: 类型安全的 JavaScript
174
- - **Rslib**: 快速构建工具
175
- - **@modelcontextprotocol/sdk**: MCP 官方 SDK
197
+ - **TypeScript**: Type-safe JavaScript
198
+ - **Rslib**: Fast build tool
199
+ - **@modelcontextprotocol/sdk**: Official MCP SDK
176
200
 
177
- ## 项目结构
201
+ ## Project Structure
178
202
 
179
203
  ```
180
204
  semi-mcp/
181
205
  ├── src/
182
- │ ├── index.ts # 主入口文件
183
- │ ├── tools/ # 工具定义
206
+ │ ├── index.ts # Main entry file
207
+ │ ├── tools/ # Tool definitions
184
208
  │ │ ├── index.ts
185
209
  │ │ └── get-semi-document.ts
186
- │ └── utils/ # 工具函数
210
+ │ └── utils/ # Utility functions
187
211
  │ ├── fetch-directory-list.ts
188
- └── fetch-file-content.ts
189
- ├── tests/ # 测试文件
212
+ ├── fetch-file-content.ts
213
+ │ └── get-component-list.ts
214
+ ├── tests/ # Test files
190
215
  │ └── get-semi-document.test.ts
191
- ├── dist/ # 构建输出
216
+ ├── dist/ # Build output
192
217
  ├── package.json
193
218
  └── README.md
194
219
  ```
195
220
 
196
- ## 许可证
221
+ ## License
197
222
 
198
223
  MIT
199
224
 
200
- ## 相关链接
225
+ ## Related Links
201
226
 
202
- - [Semi Design 官网](https://semi.design)
203
- - [Model Context Protocol 文档](https://modelcontextprotocol.io)
227
+ - [Semi Design Official Website](https://semi.design)
228
+ - [Model Context Protocol Documentation](https://modelcontextprotocol.io)
package/dist/index.js CHANGED
@@ -16,8 +16,65 @@ function flattenDirectoryStructure(item, result = []) {
16
16
  if (item.files && Array.isArray(item.files)) for (const file of item.files)flattenDirectoryStructure(file, result);
17
17
  return result;
18
18
  }
19
+ async function fetchNpmMirrorDirectoryRecursive(baseUrl, packageName, version, path, maxDepth = 10) {
20
+ if (maxDepth <= 0) return [];
21
+ const url = `${baseUrl}/${packageName}/${version}/files/${path}/?meta`;
22
+ const response = await fetch(url, {
23
+ headers: {
24
+ Accept: 'application/json'
25
+ }
26
+ });
27
+ if (!response.ok) throw new Error(`获取目录列表失败: ${response.status} ${response.statusText}`);
28
+ const contentType = response.headers.get('content-type') || '';
29
+ if (!contentType.includes('application/json')) throw new Error(`API 返回了非 JSON 格式: ${contentType}`);
30
+ const data = await response.json();
31
+ const normalizeType = (item)=>{
32
+ const path = item.path;
33
+ if (path.endsWith('/')) return {
34
+ path,
35
+ type: 'directory'
36
+ };
37
+ if (item.type && item.type.includes('/')) return {
38
+ path,
39
+ type: 'file'
40
+ };
41
+ if ('directory' === item.type) return {
42
+ path,
43
+ type: 'directory'
44
+ };
45
+ return {
46
+ path,
47
+ type: 'file'
48
+ };
49
+ };
50
+ const result = [];
51
+ if (data && 'object' == typeof data && 'files' in data && Array.isArray(data.files)) {
52
+ const promises = [];
53
+ for (const item of data.files){
54
+ const normalized = normalizeType(item);
55
+ result.push(normalized);
56
+ if ('directory' !== normalized.type || item.files && 0 !== item.files.length) {
57
+ if (item.files && Array.isArray(item.files) && item.files.length > 0) {
58
+ const flattened = [];
59
+ flattenDirectoryStructure(item, flattened);
60
+ const subFiles = flattened.filter((f)=>f.path !== normalized.path).map(normalizeType);
61
+ result.push(...subFiles);
62
+ }
63
+ } else {
64
+ const subPath = normalized.path.startsWith('/') ? normalized.path.slice(1) : normalized.path;
65
+ promises.push(fetchNpmMirrorDirectoryRecursive(baseUrl, packageName, version, subPath, maxDepth - 1).then((subFiles)=>subFiles.filter((f)=>f.path !== normalized.path)).catch(()=>[]));
66
+ }
67
+ }
68
+ if (promises.length > 0) {
69
+ const subResults = await Promise.all(promises);
70
+ for (const subFiles of subResults)result.push(...subFiles);
71
+ }
72
+ }
73
+ return result;
74
+ }
19
75
  async function fetchDirectoryListFromSource(baseUrl, packageName, version, path, isNpmMirror = false) {
20
- const url = isNpmMirror ? `${baseUrl}/${packageName}/${version}/files/${path}/?meta` : `${baseUrl}/${packageName}@${version}/${path}/?meta`;
76
+ if (isNpmMirror) return fetchNpmMirrorDirectoryRecursive(baseUrl, packageName, version, path);
77
+ const url = `${baseUrl}/${packageName}@${version}/${path}/?meta`;
21
78
  const response = await fetch(url, {
22
79
  headers: {
23
80
  Accept: 'application/json'
@@ -48,12 +105,8 @@ async function fetchDirectoryListFromSource(baseUrl, packageName, version, path,
48
105
  };
49
106
  if (Array.isArray(data)) return data.map(normalizeType);
50
107
  if (data && 'object' == typeof data && 'files' in data) {
51
- if (Array.isArray(data.files)) {
52
- const flattened = [];
53
- for (const item of data.files)flattenDirectoryStructure(item, flattened);
54
- return flattened.map(normalizeType);
55
- }
56
- return [];
108
+ const filesData = data;
109
+ if (Array.isArray(filesData.files)) return filesData.files.map(normalizeType);
57
110
  }
58
111
  if (data && 'object' == typeof data && 'path' in data) {
59
112
  const singleItem = data;
@@ -71,20 +124,28 @@ async function fetchDirectoryListFromSource(baseUrl, packageName, version, path,
71
124
  async function fetchDirectoryList(packageName, version, path) {
72
125
  const unpkgPromise = fetchDirectoryListFromSource(UNPKG_BASE_URL, packageName, version, path, false);
73
126
  const npmmirrorPromise = fetchDirectoryListFromSource(NPMMIRROR_BASE_URL, packageName, version, path, true);
74
- const unpkgWithFallback = unpkgPromise.catch(()=>new Promise(()=>{}));
75
- const npmmirrorWithFallback = npmmirrorPromise.catch(()=>new Promise(()=>{}));
76
- const raceResult = await Promise.race([
77
- unpkgWithFallback,
78
- npmmirrorWithFallback
79
- ]).catch(()=>null);
80
- if (raceResult) return raceResult;
81
127
  const results = await Promise.allSettled([
82
128
  unpkgPromise,
83
129
  npmmirrorPromise
84
130
  ]);
131
+ const successfulResults = [];
85
132
  const errors = [];
86
- for (const result of results)if ('rejected' === result.status) errors.push(result.reason instanceof Error ? result.reason : new Error(String(result.reason)));
87
- throw new Error(`所有数据源都失败了: ${errors.map((e)=>e.message).join('; ')}`);
133
+ if ('fulfilled' === results[0].status) successfulResults.push({
134
+ source: 'unpkg',
135
+ files: results[0].value
136
+ });
137
+ else errors.push(results[0].reason instanceof Error ? results[0].reason : new Error(String(results[0].reason)));
138
+ if ('fulfilled' === results[1].status) successfulResults.push({
139
+ source: 'npmmirror',
140
+ files: results[1].value
141
+ });
142
+ else errors.push(results[1].reason instanceof Error ? results[1].reason : new Error(String(results[1].reason)));
143
+ if (0 === successfulResults.length) throw new Error(`所有数据源都失败了: ${errors.map((e)=>e.message).join('; ')}`);
144
+ successfulResults.sort((a, b)=>{
145
+ if (b.files.length !== a.files.length) return b.files.length - a.files.length;
146
+ return 'unpkg' === a.source ? -1 : 1;
147
+ });
148
+ return successfulResults[0].files;
88
149
  }
89
150
  const fetch_file_content_UNPKG_BASE_URL = 'https://unpkg.com';
90
151
  const fetch_file_content_NPMMIRROR_BASE_URL = 'https://registry.npmmirror.com';
@@ -219,15 +280,7 @@ async function handleGetSemiDocument(args) {
219
280
  content: [
220
281
  {
221
282
  type: 'text',
222
- text: JSON.stringify({
223
- componentName: componentName.toLowerCase(),
224
- version,
225
- error: '未找到组件文档',
226
- documents: [],
227
- count: 0,
228
- allComponents,
229
- allComponentsCount: allComponents.length
230
- }, null, 2)
283
+ text: `未找到组件 "${componentName}" 的文档 (版本 ${version})。\n\n可用组件列表:${allComponents.join(', ')}`
231
284
  }
232
285
  ]
233
286
  };
@@ -245,68 +298,32 @@ async function handleGetSemiDocument(args) {
245
298
  await mkdir(tempDir, {
246
299
  recursive: true
247
300
  });
248
- const filePaths = [];
249
301
  for (const doc of result.documents){
250
302
  const filePath = join(tempDir, doc.name);
251
303
  await writeFile(filePath, doc.content, 'utf-8');
252
- filePaths.push(filePath);
253
- }
254
- const largeDocs = documentsWithLines.filter((doc)=>doc.lines > 888);
255
- let message = `文档已保存到临时目录: ${tempDir}\n请使用文件读取工具查看文档内容。`;
256
- if (hasLargeDocument && !userExplicitlySetGetPath) {
257
- const largeDocNames = largeDocs.map((doc)=>`${doc.name} (${doc.lines.toLocaleString()} 行)`).join(', ');
258
- message = `文档已保存到临时目录: ${tempDir}\n注意:以下文档文件较大,已自动保存到临时目录:${largeDocNames}\n请使用文件读取工具查看文档内容。`;
259
- } else if (hasLargeDocument) {
260
- const largeDocNames = largeDocs.map((doc)=>`${doc.name} (${doc.lines.toLocaleString()} 行)`).join(', ');
261
- message = `文档已保存到临时目录: ${tempDir}\n注意:以下文档文件较大:${largeDocNames}\n请使用文件读取工具查看文档内容。`;
262
304
  }
305
+ const fileList = documentsWithLines.map((doc)=>` - ${join(tempDir, doc.name)} (${doc.lines.toLocaleString()} 行)`).join('\n');
306
+ const message = `组件 ${componentName} (版本 ${version}) 文档较大,已保存到临时目录。
307
+
308
+ 文档文件列表:
309
+ ${fileList}
310
+
311
+ 请使用文件读取工具查看文档内容。`;
263
312
  return {
264
313
  content: [
265
314
  {
266
315
  type: 'text',
267
- text: JSON.stringify({
268
- componentName: componentName.toLowerCase(),
269
- version,
270
- category: result.category,
271
- tempDirectory: tempDir,
272
- files: documentsWithLines.map((doc)=>({
273
- name: doc.name,
274
- path: join(tempDir, doc.name),
275
- contentLength: doc.content.length,
276
- lines: doc.lines
277
- })),
278
- count: result.documents.length,
279
- message,
280
- autoGetPath: hasLargeDocument && !userExplicitlySetGetPath,
281
- allComponents,
282
- allComponentsCount: allComponents.length
283
- }, null, 2)
316
+ text: message
284
317
  }
285
318
  ]
286
319
  };
287
320
  }
321
+ const docContents = result.documents.map((doc)=>`===== ${doc.name} =====\n\n${doc.content}`).join('\n\n');
288
322
  return {
289
323
  content: [
290
324
  {
291
325
  type: 'text',
292
- text: JSON.stringify({
293
- componentName: componentName.toLowerCase(),
294
- version,
295
- category: result.category,
296
- documents: result.documents.map((doc)=>({
297
- name: doc.name,
298
- path: doc.path,
299
- contentLength: doc.content.length
300
- })),
301
- contents: result.documents.map((doc)=>({
302
- name: doc.name,
303
- path: doc.path,
304
- content: doc.content
305
- })),
306
- count: result.documents.length,
307
- allComponents,
308
- allComponentsCount: allComponents.length
309
- }, null, 2)
326
+ text: docContents
310
327
  }
311
328
  ]
312
329
  };
@@ -317,12 +334,7 @@ async function handleGetSemiDocument(args) {
317
334
  content: [
318
335
  {
319
336
  type: 'text',
320
- text: JSON.stringify({
321
- version,
322
- error: `未找到组件列表,请检查版本号 ${version} 是否正确`,
323
- components: [],
324
- count: 0
325
- }, null, 2)
337
+ text: `未找到组件列表,请检查版本号 ${version} 是否正确`
326
338
  }
327
339
  ],
328
340
  isError: true
@@ -331,11 +343,7 @@ async function handleGetSemiDocument(args) {
331
343
  content: [
332
344
  {
333
345
  type: 'text',
334
- text: JSON.stringify({
335
- version,
336
- components,
337
- count: components.length
338
- }, null, 2)
346
+ text: `Semi Design 组件列表 (版本 ${version}),共 ${components.length} 个组件:\n\n${components.join(', ')}`
339
347
  }
340
348
  ]
341
349
  };
@@ -346,11 +354,7 @@ async function handleGetSemiDocument(args) {
346
354
  content: [
347
355
  {
348
356
  type: 'text',
349
- text: JSON.stringify({
350
- error: errorMessage,
351
- componentName: componentName?.toLowerCase(),
352
- version
353
- }, null, 2)
357
+ text: `获取文档失败: ${errorMessage}`
354
358
  }
355
359
  ],
356
360
  isError: true
@@ -14,7 +14,7 @@ export declare function fetchDirectoryListFromSource(baseUrl: string, packageNam
14
14
  }>>;
15
15
  /**
16
16
  * 从 unpkg 或 npmmirror 获取目录列表
17
- * 同时向两个数据源发送请求,使用第一个成功返回的结果
17
+ * 同时向两个数据源发送请求,优先使用返回更多文件的结果
18
18
  */
19
19
  export declare function fetchDirectoryList(packageName: string, version: string, path: string): Promise<Array<{
20
20
  path: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@douyinfe/semi-mcp",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Semi Design MCP Server - Model Context Protocol server for Semi Design components and documentation",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",