@douyinfe/semi-mcp 1.0.0-alpha.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.
package/README.md ADDED
@@ -0,0 +1,203 @@
1
+ # Semi MCP Server
2
+
3
+ 基于 Model Context Protocol (MCP) SDK 实现的 MCP 服务器,提供 Semi Design 组件文档和组件列表查询功能。
4
+
5
+ ## 简介
6
+
7
+ Semi MCP Server 是一个 MCP (Model Context Protocol) 服务器,通过 stdio 传输层与支持 MCP 协议的客户端通信。它提供了获取 Semi Design 组件文档、组件列表等功能。
8
+
9
+ ## 安装
10
+
11
+ ### 全局安装
12
+
13
+ ```bash
14
+ npm install -g @douyinfe/semi-mcp
15
+ ```
16
+
17
+ ### 本地安装
18
+
19
+ ```bash
20
+ npm install @douyinfe/semi-mcp
21
+ ```
22
+
23
+ ## 使用方法
24
+
25
+ ### 作为命令行工具
26
+
27
+ 全局安装后,可以直接使用:
28
+
29
+ ```bash
30
+ semi-mcp
31
+ ```
32
+
33
+ ### 在 MCP 客户端中配置
34
+
35
+ 在支持 MCP 的客户端(如 Claude Desktop)中配置:
36
+
37
+ ```json
38
+ {
39
+ "mcpServers": {
40
+ "semi-mcp": {
41
+ "command": "npx",
42
+ "args": ["-y", "@douyinfe/semi-mcp"]
43
+ }
44
+ }
45
+ }
46
+ ```
47
+
48
+ 或者如果已全局安装:
49
+
50
+ ```json
51
+ {
52
+ "mcpServers": {
53
+ "semi-mcp": {
54
+ "command": "semi-mcp"
55
+ }
56
+ }
57
+ }
58
+ ```
59
+
60
+ ## 功能
61
+
62
+ ### 工具 (Tools)
63
+
64
+ #### `get_semi_document`
65
+
66
+ 获取 Semi Design 组件文档或组件列表。
67
+
68
+ **参数:**
69
+ - `componentName` (可选): 组件名称,例如 `Button`、`Input` 等。如果不提供,则返回组件列表
70
+ - `version` (可选): 版本号,例如 `2.89.2-alpha.3`。如果不提供,默认使用 `latest`
71
+
72
+ **示例:**
73
+
74
+ 获取组件列表:
75
+ ```json
76
+ {
77
+ "name": "get_semi_document"
78
+ }
79
+ ```
80
+
81
+ 获取指定组件文档:
82
+ ```json
83
+ {
84
+ "name": "get_semi_document",
85
+ "arguments": {
86
+ "componentName": "Button",
87
+ "version": "2.89.2-alpha.3"
88
+ }
89
+ }
90
+ ```
91
+
92
+ **返回格式:**
93
+
94
+ 获取组件列表时:
95
+ ```json
96
+ {
97
+ "version": "2.89.2-alpha.3",
98
+ "components": ["button", "input", "select", ...],
99
+ "count": 70
100
+ }
101
+ ```
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
+ }
114
+ ```
115
+
116
+ ### 资源 (Resources)
117
+
118
+ #### `semi://components`
119
+
120
+ Semi Design 组件列表资源。
121
+
122
+ ## 开发
123
+
124
+ ### 环境要求
125
+
126
+ - Node.js >= 18.0.0
127
+ - npm 或 yarn
128
+
129
+ ### 安装依赖
130
+
131
+ ```bash
132
+ npm install
133
+ ```
134
+
135
+ ### 构建
136
+
137
+ 构建生产版本:
138
+
139
+ ```bash
140
+ npm run build
141
+ ```
142
+
143
+ 开发模式(监听文件变化并自动重建):
144
+
145
+ ```bash
146
+ npm run dev
147
+ ```
148
+
149
+ ### 测试
150
+
151
+ 运行测试:
152
+
153
+ ```bash
154
+ npm test
155
+ ```
156
+
157
+ ### 运行
158
+
159
+ 构建完成后运行服务器:
160
+
161
+ ```bash
162
+ npm start
163
+ ```
164
+
165
+ 或者直接运行构建后的文件:
166
+
167
+ ```bash
168
+ node dist/index.js
169
+ ```
170
+
171
+ ## 技术栈
172
+
173
+ - **TypeScript**: 类型安全的 JavaScript
174
+ - **Rslib**: 快速构建工具
175
+ - **@modelcontextprotocol/sdk**: MCP 官方 SDK
176
+
177
+ ## 项目结构
178
+
179
+ ```
180
+ semi-mcp/
181
+ ├── src/
182
+ │ ├── index.ts # 主入口文件
183
+ │ ├── tools/ # 工具定义
184
+ │ │ ├── index.ts
185
+ │ │ └── get-semi-document.ts
186
+ │ └── utils/ # 工具函数
187
+ │ ├── fetch-directory-list.ts
188
+ │ └── fetch-file-content.ts
189
+ ├── tests/ # 测试文件
190
+ │ └── get-semi-document.test.ts
191
+ ├── dist/ # 构建输出
192
+ ├── package.json
193
+ └── README.md
194
+ ```
195
+
196
+ ## 许可证
197
+
198
+ MIT
199
+
200
+ ## 相关链接
201
+
202
+ - [Semi Design 官网](https://semi.design)
203
+ - [Model Context Protocol 文档](https://modelcontextprotocol.io)
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,257 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";
5
+ const UNPKG_BASE_URL = 'https://unpkg.com';
6
+ const NPMMIRROR_BASE_URL = 'https://npmmirror.com';
7
+ async function fetchFromSource(baseUrl, packageName, version, path) {
8
+ const url = `${baseUrl}/${packageName}@${version}/${path}/?meta`;
9
+ const response = await fetch(url, {
10
+ headers: {
11
+ Accept: 'application/json'
12
+ }
13
+ });
14
+ if (!response.ok) throw new Error(`获取目录列表失败: ${response.status} ${response.statusText}`);
15
+ const contentType = response.headers.get('content-type') || '';
16
+ if (!contentType.includes('application/json')) throw new Error(`API 返回了非 JSON 格式: ${contentType}`);
17
+ const data = await response.json();
18
+ const normalizeType = (item)=>{
19
+ const path = item.path;
20
+ if (path.endsWith('/')) return {
21
+ path,
22
+ type: 'directory'
23
+ };
24
+ if (item.type && item.type.includes('/')) return {
25
+ path,
26
+ type: 'file'
27
+ };
28
+ if ('directory' === item.type) return {
29
+ path,
30
+ type: 'directory'
31
+ };
32
+ return {
33
+ path,
34
+ type: 'file'
35
+ };
36
+ };
37
+ if (Array.isArray(data)) return data.map(normalizeType);
38
+ if (data && 'object' == typeof data && 'files' in data && Array.isArray(data.files)) return data.files.map(normalizeType);
39
+ if (data && 'object' == typeof data && 'path' in data) return [
40
+ normalizeType(data)
41
+ ];
42
+ throw new Error('无法解析目录列表数据格式');
43
+ }
44
+ async function fetchDirectoryList(packageName, version, path) {
45
+ const unpkgPromise = fetchFromSource(UNPKG_BASE_URL, packageName, version, path);
46
+ const npmmirrorPromise = fetchFromSource(NPMMIRROR_BASE_URL, packageName, version, path);
47
+ const unpkgWithFallback = unpkgPromise.catch(()=>new Promise(()=>{}));
48
+ const npmmirrorWithFallback = npmmirrorPromise.catch(()=>new Promise(()=>{}));
49
+ const raceResult = await Promise.race([
50
+ unpkgWithFallback,
51
+ npmmirrorWithFallback
52
+ ]).catch(()=>null);
53
+ if (raceResult) return raceResult;
54
+ const results = await Promise.allSettled([
55
+ unpkgPromise,
56
+ npmmirrorPromise
57
+ ]);
58
+ const errors = [];
59
+ for (const result of results)if ('rejected' === result.status) errors.push(result.reason instanceof Error ? result.reason : new Error(String(result.reason)));
60
+ throw new Error(`所有数据源都失败了: ${errors.map((e)=>e.message).join('; ')}`);
61
+ }
62
+ const getSemiDocumentTool = {
63
+ name: 'get_semi_document',
64
+ description: '获取 Semi Design 组件文档或组件列表',
65
+ inputSchema: {
66
+ type: 'object',
67
+ properties: {
68
+ componentName: {
69
+ type: 'string',
70
+ description: '组件名称,例如 Button、Input 等。如果不提供,则返回组件列表'
71
+ },
72
+ version: {
73
+ type: 'string',
74
+ description: '版本号,例如 2.89.1。如果不提供,默认使用 latest'
75
+ }
76
+ },
77
+ required: []
78
+ }
79
+ };
80
+ async function getComponentList(version) {
81
+ const packageName = '@douyinfe/semi-ui';
82
+ const files = await fetchDirectoryList(packageName, version, 'lib');
83
+ if (!files || 0 === files.length) return [];
84
+ const components = files.filter((file)=>'directory' === file.type).map((file)=>{
85
+ const parts = file.path.split('/');
86
+ const componentName = parts[parts.length - 1] || parts[0];
87
+ return componentName.toLowerCase();
88
+ }).filter((name)=>name && 'lib' !== name);
89
+ return Array.from(new Set(components)).sort();
90
+ }
91
+ async function getComponentDocuments(componentName, version) {
92
+ const packageName = '@douyinfe/semi-ui';
93
+ const componentNameLower = componentName.toLowerCase();
94
+ const contentDirs = await fetchDirectoryList(packageName, version, 'content');
95
+ if (!contentDirs || 0 === contentDirs.length) return null;
96
+ const categories = contentDirs.filter((file)=>'directory' === file.type).map((file)=>{
97
+ const parts = file.path.split('/');
98
+ return parts[parts.length - 1] || parts[0];
99
+ }).filter((name)=>name && 'content' !== name);
100
+ for (const category of categories)try {
101
+ const categoryFiles = await fetchDirectoryList(packageName, version, `content/${category}`);
102
+ const componentDir = categoryFiles.find((file)=>'directory' === file.type && file.path.toLowerCase().endsWith(`/${componentNameLower}`));
103
+ if (componentDir) {
104
+ const componentFiles = await fetchDirectoryList(packageName, version, `content/${category}/${componentNameLower}`);
105
+ if (componentFiles && componentFiles.length > 0) {
106
+ const documents = componentFiles.filter((file)=>'file' === file.type).map((file)=>{
107
+ const parts = file.path.split('/');
108
+ return parts[parts.length - 1].toLowerCase();
109
+ }).filter((name)=>name);
110
+ return {
111
+ category,
112
+ documents: Array.from(new Set(documents)).sort()
113
+ };
114
+ }
115
+ }
116
+ } catch (error) {
117
+ continue;
118
+ }
119
+ return null;
120
+ }
121
+ async function handleGetSemiDocument(args) {
122
+ const componentName = args?.componentName;
123
+ const version = args?.version || 'latest';
124
+ try {
125
+ if (componentName) {
126
+ const result = await getComponentDocuments(componentName, version);
127
+ const allComponents = await getComponentList(version);
128
+ if (!result) return {
129
+ content: [
130
+ {
131
+ type: 'text',
132
+ text: JSON.stringify({
133
+ componentName: componentName.toLowerCase(),
134
+ version,
135
+ error: '未找到组件文档',
136
+ documents: [],
137
+ count: 0,
138
+ allComponents,
139
+ allComponentsCount: allComponents.length
140
+ }, null, 2)
141
+ }
142
+ ]
143
+ };
144
+ return {
145
+ content: [
146
+ {
147
+ type: 'text',
148
+ text: JSON.stringify({
149
+ componentName: componentName.toLowerCase(),
150
+ version,
151
+ category: result.category,
152
+ documents: result.documents,
153
+ count: result.documents.length,
154
+ allComponents,
155
+ allComponentsCount: allComponents.length
156
+ }, null, 2)
157
+ }
158
+ ]
159
+ };
160
+ }
161
+ {
162
+ const components = await getComponentList(version);
163
+ return {
164
+ content: [
165
+ {
166
+ type: 'text',
167
+ text: JSON.stringify({
168
+ version,
169
+ components,
170
+ count: components.length
171
+ }, null, 2)
172
+ }
173
+ ]
174
+ };
175
+ }
176
+ } catch (error) {
177
+ const errorMessage = error instanceof Error ? error.message : String(error);
178
+ return {
179
+ content: [
180
+ {
181
+ type: 'text',
182
+ text: JSON.stringify({
183
+ error: errorMessage,
184
+ componentName: componentName?.toLowerCase(),
185
+ version
186
+ }, null, 2)
187
+ }
188
+ ],
189
+ isError: true
190
+ };
191
+ }
192
+ }
193
+ const tools = [
194
+ getSemiDocumentTool
195
+ ];
196
+ const toolHandlers = {
197
+ [getSemiDocumentTool.name]: handleGetSemiDocument
198
+ };
199
+ async function main() {
200
+ const server = new Server({
201
+ name: 'semi-mcp',
202
+ version: '0.0.0'
203
+ }, {
204
+ capabilities: {
205
+ tools: {},
206
+ resources: {}
207
+ }
208
+ });
209
+ server.setRequestHandler(ListToolsRequestSchema, async ()=>({
210
+ tools: tools
211
+ }));
212
+ server.setRequestHandler(CallToolRequestSchema, async (request)=>{
213
+ const { name, arguments: args } = request.params;
214
+ const handler = toolHandlers[name];
215
+ if (!handler) throw new Error(`未知的工具: ${name}`);
216
+ return handler(args || {});
217
+ });
218
+ server.setRequestHandler(ListResourcesRequestSchema, async ()=>({
219
+ resources: [
220
+ {
221
+ uri: 'semi://components',
222
+ name: 'Semi Components',
223
+ description: 'Semi Design 组件列表',
224
+ mimeType: 'application/json'
225
+ }
226
+ ]
227
+ }));
228
+ server.setRequestHandler(ReadResourceRequestSchema, async (request)=>{
229
+ const { uri } = request.params;
230
+ if ('semi://components' === uri) return {
231
+ contents: [
232
+ {
233
+ uri,
234
+ mimeType: 'application/json',
235
+ text: JSON.stringify({
236
+ components: [
237
+ 'Button',
238
+ 'Input',
239
+ 'Select',
240
+ 'Table',
241
+ 'Form'
242
+ ],
243
+ description: 'Semi Design 组件列表'
244
+ }, null, 2)
245
+ }
246
+ ]
247
+ };
248
+ throw new Error(`未知的资源 URI: ${uri}`);
249
+ });
250
+ const transport = new StdioServerTransport();
251
+ await server.connect(transport);
252
+ console.error('Semi MCP Server 已启动,使用 stdio 传输');
253
+ }
254
+ main().catch((error)=>{
255
+ console.error('服务器启动失败:', error);
256
+ process.exit(1);
257
+ });
@@ -0,0 +1,10 @@
1
+ import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
2
+ import { Tool } from '@modelcontextprotocol/sdk/types.js';
3
+ /**
4
+ * 工具定义:获取 Semi Design 组件文档
5
+ */
6
+ export declare const getSemiDocumentTool: Tool;
7
+ /**
8
+ * 工具处理器:处理 get_semi_document 工具调用
9
+ */
10
+ export declare function handleGetSemiDocument(args: Record<string, unknown>): Promise<CallToolResult>;
@@ -0,0 +1,9 @@
1
+ import { Tool, CallToolResult } from '@modelcontextprotocol/sdk/types.js';
2
+ /**
3
+ * 所有工具的定义
4
+ */
5
+ export declare const tools: Tool[];
6
+ /**
7
+ * 工具名称到处理器的映射
8
+ */
9
+ export declare const toolHandlers: Record<string, (args: Record<string, unknown>) => Promise<CallToolResult>>;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * 从 unpkg 或 npmmirror 获取目录列表
3
+ * 同时向两个数据源发送请求,使用第一个成功返回的结果
4
+ */
5
+ /**
6
+ * 从 unpkg 或 npmmirror 获取目录列表
7
+ * 同时向两个数据源发送请求,使用第一个成功返回的结果
8
+ */
9
+ export declare function fetchDirectoryList(packageName: string, version: string, path: string): Promise<Array<{
10
+ path: string;
11
+ type: string;
12
+ }>>;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * 从 unpkg 或 npmmirror 获取具体文件内容
3
+ * 同时向两个数据源发送请求,使用第一个成功返回的结果
4
+ */
5
+ /**
6
+ * 从 unpkg 或 npmmirror 获取具体文件内容
7
+ * 同时向两个数据源发送请求,使用第一个成功返回的结果
8
+ */
9
+ export declare function fetchFileContent(packageName: string, version: string, filePath: string): Promise<string>;
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@douyinfe/semi-mcp",
3
+ "version": "1.0.0-alpha.0",
4
+ "description": "Semi Design MCP Server - Model Context Protocol server for Semi Design components and documentation",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "bin": {
8
+ "semi-mcp": "./dist/index.js"
9
+ },
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js"
14
+ }
15
+ },
16
+ "types": "./dist/index.d.ts",
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "engines": {
21
+ "node": ">=18.0.0"
22
+ },
23
+ "keywords": [
24
+ "semi",
25
+ "semi-design",
26
+ "mcp",
27
+ "model-context-protocol",
28
+ "design-system",
29
+ "component-library",
30
+ "documentation"
31
+ ],
32
+ "author": "",
33
+ "license": "MIT",
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "scripts": {
38
+ "build": "rslib build",
39
+ "dev": "rslib build --watch",
40
+ "test": "rstest",
41
+ "start": "node dist/index.js",
42
+ "prepublishOnly": "npm run build && npm test"
43
+ },
44
+ "dependencies": {
45
+ "@modelcontextprotocol/sdk": "^1.0.4"
46
+ },
47
+ "devDependencies": {
48
+ "@rslib/core": "^0.18.5",
49
+ "@rstest/core": "^0.7.2",
50
+ "@types/node": "^24.10.4",
51
+ "tsx": "^4.19.2",
52
+ "typescript": "^5.9.3"
53
+ }
54
+ }