@codify-ai/mcp-client 1.0.3 → 1.0.5
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 +4 -31
- package/mcp-client.js +201 -96
- package/mcp-client.min.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -77,7 +77,7 @@ Figma Plugin (WebSocket)
|
|
|
77
77
|
在代码或对话中引用:
|
|
78
78
|
|
|
79
79
|
```
|
|
80
|
-
codify://getCode/code_id
|
|
80
|
+
@codify://getCode/code_id
|
|
81
81
|
```
|
|
82
82
|
|
|
83
83
|
## 配置说明
|
|
@@ -86,7 +86,7 @@ codify://getCode/code_id
|
|
|
86
86
|
|
|
87
87
|
| 参数 | 说明 | 默认值 |
|
|
88
88
|
| ----------------- | ----------------- | --------------------------- |
|
|
89
|
-
| `--url=<URL>` | Codify 服务器地址 | `
|
|
89
|
+
| `--url=<URL>` | Codify 服务器地址 | `https://mcp.codify-api.com` |
|
|
90
90
|
| `--help`, `-h` | 显示帮助信息 | - |
|
|
91
91
|
| `--version`, `-v` | 显示版本号 | - |
|
|
92
92
|
|
|
@@ -150,7 +150,7 @@ codify://getCode/code_id
|
|
|
150
150
|
AI,请使用 getCode 工具获取 demo-channel 的代码
|
|
151
151
|
```
|
|
152
152
|
|
|
153
|
-
###
|
|
153
|
+
### getCodeList
|
|
154
154
|
|
|
155
155
|
列出所有可用频道。
|
|
156
156
|
|
|
@@ -159,7 +159,7 @@ AI,请使用 getCode 工具获取 demo-channel 的代码
|
|
|
159
159
|
**示例:**
|
|
160
160
|
|
|
161
161
|
```
|
|
162
|
-
AI,请使用
|
|
162
|
+
AI,请使用 getCodeList 工具查看所有频道
|
|
163
163
|
```
|
|
164
164
|
|
|
165
165
|
## 可用资源
|
|
@@ -177,33 +177,6 @@ codify://getCode/code_123
|
|
|
177
177
|
codify://getCode/code_456
|
|
178
178
|
```
|
|
179
179
|
|
|
180
|
-
## 故障排查
|
|
181
|
-
|
|
182
|
-
### 问题 1:连接失败
|
|
183
|
-
|
|
184
|
-
**错误信息:** `❌ 获取通道列表失败`
|
|
185
|
-
|
|
186
|
-
**解决方案:**
|
|
187
|
-
|
|
188
|
-
1. 确保 Codify 服务器正在运行
|
|
189
|
-
2. 检查服务器 URL 是否正确
|
|
190
|
-
3. 检查网络连接是否正常
|
|
191
|
-
4. 查看服务器日志
|
|
192
|
-
|
|
193
|
-
### 问题 2:认证失败
|
|
194
|
-
|
|
195
|
-
**错误信息:** `❌ 认证失败 (401)`
|
|
196
|
-
|
|
197
|
-
**解决方案:**
|
|
198
|
-
|
|
199
|
-
1. 确认 `CODIFY_ACCESS_KEY` 已设置
|
|
200
|
-
2. 检查 access_key 是否正确,并且你的帐号拥有订阅权限。
|
|
201
|
-
|
|
202
|
-
## 相关项目
|
|
203
|
-
|
|
204
|
-
- **Codify MCP Server** - 服务端实现
|
|
205
|
-
- **Codify AI Plugin** - Codify 插件
|
|
206
|
-
|
|
207
180
|
## 许可证
|
|
208
181
|
|
|
209
182
|
MIT
|
package/mcp-client.js
CHANGED
|
@@ -10,10 +10,8 @@ import axios from 'axios'
|
|
|
10
10
|
function parseArgs() {
|
|
11
11
|
const args = process.argv.slice(2)
|
|
12
12
|
let serverUrl = process.env.CODIFY_SERVER_URL || 'https://mcp.codify-api.com'
|
|
13
|
-
|
|
14
13
|
for (let i = 0; i < args.length; i++) {
|
|
15
14
|
const arg = args[i]
|
|
16
|
-
|
|
17
15
|
if (arg === '--help' || arg === '-h') {
|
|
18
16
|
console.log(`
|
|
19
17
|
Codify MCP Client - 连接到远程 Codify MCP 服务器
|
|
@@ -50,7 +48,6 @@ Cursor 配置示例 (~/.cursor/mcp.json):
|
|
|
50
48
|
process.exit(0)
|
|
51
49
|
}
|
|
52
50
|
if (arg === '--version' || arg === '-v') {
|
|
53
|
-
console.log('1.0.1')
|
|
54
51
|
process.exit(0)
|
|
55
52
|
}
|
|
56
53
|
if (arg.startsWith('--url=')) {
|
|
@@ -69,14 +66,19 @@ const ACCESS_KEY = process.env.CODIFY_ACCESS_KEY
|
|
|
69
66
|
|
|
70
67
|
// 辅助函数:检查值是否为空
|
|
71
68
|
function isEmpty(value) {
|
|
72
|
-
return
|
|
69
|
+
return (
|
|
70
|
+
value === null ||
|
|
71
|
+
value === undefined ||
|
|
72
|
+
value === '' ||
|
|
73
|
+
(typeof value === 'string' && value.trim() === '')
|
|
74
|
+
)
|
|
73
75
|
}
|
|
74
76
|
|
|
75
77
|
// 初始化 MCP Server
|
|
76
78
|
const mcpServer = new McpServer(
|
|
77
79
|
{
|
|
78
80
|
name: 'Codify-MCP-Client',
|
|
79
|
-
version: '1.0.
|
|
81
|
+
version: '1.0.5'
|
|
80
82
|
},
|
|
81
83
|
{
|
|
82
84
|
capabilities: {
|
|
@@ -153,7 +155,7 @@ mcpServer.registerTool(
|
|
|
153
155
|
if (!isEmpty(data.image)) {
|
|
154
156
|
resultText += `Image: ${Object.keys(data.image).length}\n`
|
|
155
157
|
}
|
|
156
|
-
|
|
158
|
+
|
|
157
159
|
resultText += `--- 代码内容 ---\n${data.code}\n\n`
|
|
158
160
|
resultText += `--- 图片内容 ---\n${data.image}\n\n`
|
|
159
161
|
resultText += `--- SVG内容 ---\n${data.svg}\n\n`
|
|
@@ -210,8 +212,9 @@ mcpServer.registerTool(
|
|
|
210
212
|
content: [
|
|
211
213
|
{
|
|
212
214
|
type: 'text',
|
|
213
|
-
text: `❌ 获取代码失败: ${error.message || String(error)}${
|
|
214
|
-
|
|
215
|
+
text: `❌ 获取代码失败: ${error.message || String(error)}${
|
|
216
|
+
errorData.message ? `\n详情: ${errorData.message}` : ''
|
|
217
|
+
}\n- contentId: ${contentId}\n- 服务器: ${SERVER_URL}`
|
|
215
218
|
}
|
|
216
219
|
],
|
|
217
220
|
isError: true
|
|
@@ -231,14 +234,148 @@ mcpServer.registerTool(
|
|
|
231
234
|
}
|
|
232
235
|
}
|
|
233
236
|
)
|
|
237
|
+
// get_code_List
|
|
238
|
+
mcpServer.registerTool(
|
|
239
|
+
'get_code_list',
|
|
240
|
+
{
|
|
241
|
+
description: '获取所有可用的代码列表',
|
|
242
|
+
inputSchema: z.object({}).describe('无需参数,直接调用即可获取所有代码列表')
|
|
243
|
+
},
|
|
244
|
+
async (args) => {
|
|
245
|
+
try {
|
|
246
|
+
// 添加认证头
|
|
247
|
+
const headers = {}
|
|
248
|
+
if (ACCESS_KEY) {
|
|
249
|
+
headers['Authorization'] = `Bearer ${ACCESS_KEY}`
|
|
250
|
+
}
|
|
234
251
|
|
|
252
|
+
// 通过 HTTP API 从远程服务器获取代码列表
|
|
253
|
+
const apiUrl = `${SERVER_URL}/api/getCodeList`
|
|
254
|
+
try {
|
|
255
|
+
const response = await axios.get(apiUrl, { headers })
|
|
256
|
+
const data = response.data
|
|
257
|
+
|
|
258
|
+
if (!Array.isArray(data)) {
|
|
259
|
+
return {
|
|
260
|
+
content: [
|
|
261
|
+
{
|
|
262
|
+
type: 'text',
|
|
263
|
+
text: `⚠️ 服务器返回的数据格式不正确\n- 期望: 数组\n- 实际: ${typeof data}`
|
|
264
|
+
}
|
|
265
|
+
],
|
|
266
|
+
isError: true
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (data.length === 0) {
|
|
271
|
+
return {
|
|
272
|
+
content: [
|
|
273
|
+
{
|
|
274
|
+
type: 'text',
|
|
275
|
+
text: `📋 代码列表为空\n\n当前没有可用的代码内容。\n请先从 Codify 插件同步代码。`
|
|
276
|
+
}
|
|
277
|
+
],
|
|
278
|
+
isError: false
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// 构建返回信息
|
|
283
|
+
let resultText = `✅ 成功获取代码列表 (共 ${data.length} 项)\n\n`
|
|
284
|
+
resultText += `--- 代码列表 ---\n\n`
|
|
285
|
+
|
|
286
|
+
data.forEach((item, index) => {
|
|
287
|
+
resultText += `${index + 1}. **${item.contentId}**\n`
|
|
288
|
+
resultText += ` - 代码长度: ${item.codeLength} 字符\n`
|
|
289
|
+
if (item.teamId) {
|
|
290
|
+
resultText += ` - 团队 ID: ${item.teamId}\n`
|
|
291
|
+
}
|
|
292
|
+
if (item.fileInfo) {
|
|
293
|
+
resultText += ` - 文件信息: ${JSON.stringify(item.fileInfo)}\n`
|
|
294
|
+
}
|
|
295
|
+
if (item.createdAt) {
|
|
296
|
+
resultText += ` - 创建时间: ${item.createdAt}\n`
|
|
297
|
+
}
|
|
298
|
+
if (item.updatedAt) {
|
|
299
|
+
resultText += ` - 更新时间: ${item.updatedAt}\n`
|
|
300
|
+
}
|
|
301
|
+
resultText += `\n`
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
resultText += `\n--- 使用说明 ---\n`
|
|
305
|
+
resultText += `使用 get_code 工具获取具体代码内容:\n`
|
|
306
|
+
resultText += `- get_code({ contentId: "your-content-id" })`
|
|
307
|
+
|
|
308
|
+
return {
|
|
309
|
+
content: [
|
|
310
|
+
{
|
|
311
|
+
type: 'text',
|
|
312
|
+
text: resultText
|
|
313
|
+
}
|
|
314
|
+
]
|
|
315
|
+
}
|
|
316
|
+
} catch (error) {
|
|
317
|
+
const status = error.response?.status
|
|
318
|
+
const errorData = error.response?.data || {}
|
|
319
|
+
|
|
320
|
+
switch (status) {
|
|
321
|
+
case 403:
|
|
322
|
+
return {
|
|
323
|
+
content: [
|
|
324
|
+
{
|
|
325
|
+
type: 'text',
|
|
326
|
+
text: `❌ 权限不足\n- 您没有权限访问代码列表\n- 请检查您的 access_key 是否包含正确的团队权限`
|
|
327
|
+
}
|
|
328
|
+
],
|
|
329
|
+
isError: true
|
|
330
|
+
}
|
|
331
|
+
case 401:
|
|
332
|
+
return {
|
|
333
|
+
content: [
|
|
334
|
+
{
|
|
335
|
+
type: 'text',
|
|
336
|
+
text: `❌ 认证失败 (401)\n\n请检查 CODIFY_ACCESS_KEY 是否正确`
|
|
337
|
+
}
|
|
338
|
+
],
|
|
339
|
+
isError: true
|
|
340
|
+
}
|
|
341
|
+
default:
|
|
342
|
+
return {
|
|
343
|
+
content: [
|
|
344
|
+
{
|
|
345
|
+
type: 'text',
|
|
346
|
+
text: `❌ 获取代码列表失败: ${error.message || String(error)}${
|
|
347
|
+
errorData.message ? `\n详情: ${errorData.message}` : ''
|
|
348
|
+
}\n- 服务器: ${SERVER_URL}`
|
|
349
|
+
}
|
|
350
|
+
],
|
|
351
|
+
isError: true
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
} catch (error) {
|
|
356
|
+
return {
|
|
357
|
+
content: [
|
|
358
|
+
{
|
|
359
|
+
type: 'text',
|
|
360
|
+
text: `❌ 获取代码列表失败: ${error.message || String(error)}`
|
|
361
|
+
}
|
|
362
|
+
],
|
|
363
|
+
isError: true
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
)
|
|
235
368
|
// design
|
|
236
369
|
mcpServer.registerTool(
|
|
237
370
|
'design',
|
|
238
371
|
{
|
|
239
372
|
description: '根据需求生成符合 Codify 规范的 HTML+CSS 代码',
|
|
240
373
|
inputSchema: {
|
|
241
|
-
requirement: z
|
|
374
|
+
requirement: z
|
|
375
|
+
.string()
|
|
376
|
+
.describe(
|
|
377
|
+
'界面需求描述,例如:"一个美观的登录页面"、"现代化的仪表盘界面"等。工具会根据 Codify 生成规则自动生成代码。'
|
|
378
|
+
)
|
|
242
379
|
}
|
|
243
380
|
},
|
|
244
381
|
async (args) => {
|
|
@@ -257,124 +394,92 @@ mcpServer.registerTool(
|
|
|
257
394
|
}
|
|
258
395
|
}
|
|
259
396
|
|
|
260
|
-
// 读取生成规则
|
|
261
|
-
const fs = await import('fs')
|
|
262
|
-
const path = await import('path')
|
|
263
|
-
const { fileURLToPath } = await import('url')
|
|
264
|
-
const { dirname } = await import('path')
|
|
265
|
-
|
|
266
|
-
const __filename = fileURLToPath(import.meta.url)
|
|
267
|
-
const __dirname = dirname(__filename)
|
|
268
|
-
const rulesPath = path.join(__dirname, '..', '生成规则.md')
|
|
269
397
|
|
|
270
398
|
// 完整的生成规则(作为备用)
|
|
271
|
-
const
|
|
399
|
+
const generationRules = `
|
|
400
|
+
# 📐 HTML + CSS 视觉生成协议
|
|
272
401
|
|
|
273
402
|
## 🎯 任务目标
|
|
274
403
|
|
|
275
|
-
|
|
404
|
+
以 UI/UX 专家视角生成媲美 Dribbble/Behance 商业级作品的代码。
|
|
276
405
|
|
|
277
|
-
**您的主要责任**:生成高保真、美观、结构健壮的 HTML
|
|
406
|
+
- **您的主要责任**:生成高保真、美观、结构健壮的 HTML 代码用于**逻辑建模**,仅返回包含在 \`<main>\` 根容器内的代码。
|
|
407
|
+
- **您的专业能力**:代码生成的界面必须看起来像是一个经过精心打磨的商业级产品,而非粗糙的原型。
|
|
408
|
+
- **技术栈**:原生 HTML 和 CSS,全内联 CSS (Inline Styles),中文界面 (除非指定),严格语义化。
|
|
409
|
+
- **工作流**:代码生成后,立即调用 \`send_to_codify\` 工具** 将代码发送到 Codify 插件进行设计模型转换
|
|
278
410
|
|
|
279
|
-
|
|
411
|
+
## 🛑 绝对禁止 (违反即失败)
|
|
280
412
|
|
|
281
|
-
|
|
413
|
+
- ❌ **属性**: \`margin\` (全用 gap/padding), \`grid\`, \`calc()\`,\`<style>\`标签。
|
|
414
|
+
- ❌ **对齐**: \`space-around\`, \`space-evenly\` (用 \`space-between\` + \`padding\` 替代)。
|
|
415
|
+
- ❌ **单位**: \`%\`, \`vw\`, \`vh\`, \`em\`, \`rem\`, \`auto\` (锁死使用 **px** 整数)。
|
|
416
|
+
- ❌ **文本标签污染**: 禁止为 \`<p>/<span>\` 设置 \`background\`, \`padding\`, \`border\`, \`flex\`。
|
|
417
|
+
- ❌ **尺寸**: 禁止 \`width: 100%\`。拉伸必须用 \`flex: 1\` 或 \`align-self: stretch\`。
|
|
282
418
|
|
|
283
|
-
|
|
419
|
+
## 🏗️ 布局与结构铁律
|
|
284
420
|
|
|
285
|
-
|
|
421
|
+
**1. Flex 全显式声明**
|
|
286
422
|
|
|
287
|
-
-
|
|
288
|
-
|
|
289
|
-
|
|
423
|
+
- 所有容器 (含 \`<main>\`) 必须写全四要素:
|
|
424
|
+
\`display: flex; flex-direction: ...; justify-content: ...; align-items: ...;\`
|
|
425
|
+
- **禁止省略默认值** (如 \`flex-start\` 必须写出来)。
|
|
290
426
|
|
|
291
|
-
|
|
292
|
-
- 禁止使用:\`<style>\` 标签、\`margin\` 属性、\`grid\` 布局、\`calc()\` 函数。
|
|
293
|
-
- 禁止\`space-around\` / \`space-evenly\` (使用 \`space-between\` + padding 代替)。
|
|
294
|
-
- 禁止单位:严禁使用 \`%\`, \`vw\`, \`vh\`, \`em\`, \`rem\`。**所有数值必须为 px 整数**。
|
|
295
|
-
- 禁止为 \`<span>\` 添加\`flex\`布局属性,或\`background\`,\`padding\`,\`border\`等样式。
|
|
296
|
-
- 禁止在\`flex\`布局中添加宽高尺寸,除非有需要固定尺寸的元素。
|
|
297
|
-
- ❌ **严禁使用** "margin" 属性。
|
|
427
|
+
**2. 盒子与间距**
|
|
298
428
|
|
|
299
|
-
-
|
|
300
|
-
|
|
429
|
+
- **间距**: 元素间距只用 gap,内边距只用 padding。
|
|
430
|
+
- **文档流**: 标准流布局只用 Flexbox。
|
|
431
|
+
- **脱离文档流**: 允许 absolute 用于装饰/悬浮,但必须:
|
|
432
|
+
- 位于非 static 祖先内。
|
|
433
|
+
- 显式声明 top/right/bottom/left 坐标。
|
|
301
434
|
|
|
302
|
-
|
|
435
|
+
**3. 层级树与原子化**
|
|
303
436
|
|
|
304
|
-
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
-
|
|
313
|
-
-
|
|
314
|
-
- **严禁**在 \`<p>\` 内嵌套 \`<span>\` 或其他标签。
|
|
315
|
-
- 所有\`<p>\`和\`<span>\` 标签必须显式设置:\`font-size\`, \`line-height\` (1.2-1.5倍), \`font-weight\`, \`color\` (#Hex)。
|
|
316
|
-
- **关键**:如果你需要为\`<span>\`增加\`background\`,\`padding\`,\`border\`等样式,请立即改为\`<div>\`标签。
|
|
317
|
-
|
|
318
|
-
- **图标**:
|
|
319
|
-
- 使用 FontAwesome \`<i>\` 标签 (class="fas fa-...").
|
|
320
|
-
|
|
321
|
-
## 🏗️ 布局与拉伸
|
|
322
|
-
|
|
323
|
-
- **拉伸行为必须由子元素主动声明**:父容器仅负责容器边界、对齐方式和成员间距。仅允许使用 \`flex: 1\` 或 \`align-self: stretch\` 来拉伸元素。
|
|
324
|
-
- **防挤压声明**:所有固定宽高的元素(如图标、头像、按钮容器)必须显式添加 \`flex: none\`。
|
|
325
|
-
|
|
326
|
-
## 📐 视觉与间距规范
|
|
327
|
-
|
|
328
|
-
- **边距控制**:仅允许使用 \`padding\` 控制内边距。
|
|
329
|
-
- **间距控制**:仅允许使用 \`gap\` 控制子元素间距。
|
|
330
|
-
- **视觉修饰**:边框使用 \`border: 1px solid #XXXXXX\`,圆角使用 \`border-radius: Xpx\`。
|
|
437
|
+
- **结构**: \`main\` (根, 需定宽) > \`section\` (模块) > \`div\` (容器) > \`span\`(文本) / \`p\`(长文本) / \`i\` (图标)。
|
|
438
|
+
- **命名**: 所有标签必须带 \`data-name="..."\` (描述用途)。
|
|
439
|
+
- **文本**: \`<p>\`: 块级长文。\`<span>\`: 行内短语。
|
|
440
|
+
- \`<span>短文本</span>\`
|
|
441
|
+
- \`<p>长文本主要是用于描述信息,期望可以跟随布局自动换行</p>\`
|
|
442
|
+
- **样式锁**: 必须显式设置 \`font-size\`, \`line-height (1.2-1.6)\`, \`font-weight\`, \`color\`。
|
|
443
|
+
- **修饰原则**: 一旦文本需要背景、边框或圆角,必须包裹一层 <div> 来承载这些样式。
|
|
444
|
+
- **图标**: 使用 FontAwesome \`<i>\` 标签,
|
|
445
|
+
- 仅允许使用 \`fas\`、\`far\`、\`fab\`库 如:\`class="fas fa-..."\` / \`class="far fa-..."\`
|
|
446
|
+
- 必须为 \`<i>\` 显示声明 \`color\`、\`font-size\` 属性
|
|
331
447
|
|
|
332
448
|
## 🎨 设计系统严格规范
|
|
333
449
|
|
|
334
450
|
在生成任何代码时,必须严格强制执行以下视觉规则:
|
|
335
451
|
|
|
336
|
-
1.
|
|
452
|
+
1. **视觉层级:**
|
|
337
453
|
- 拒绝扁平的单一字重。标题必须使用加粗 (font-weight: 600/700),正文使用常规 (400)。
|
|
338
454
|
- 使用有层次的文字颜色:主要标题使用深色 (如 #1e293b),次级文本/描述必须使用浅灰色 (如 #64748b),不仅是为了美观,也是为了信息降噪。
|
|
339
455
|
|
|
340
|
-
2.
|
|
456
|
+
2. **现代盒模型与空间感:**
|
|
341
457
|
- **大留白:** 拒绝拥挤。卡片和容器内部必须有宽裕的 padding (至少 24px 或 32px)。
|
|
342
458
|
- **大圆角:** 容器和卡片应使用明显的圆角 (border-radius: 16px 或 24px) 以呈现亲和力,避免尖锐的直角。
|
|
343
459
|
- **微边框:** 避免使用粗黑边框。使用极其微妙的边框来界定边界 (例如: \`border: 1px solid rgba(0,0,0,0.06)\`)。
|
|
344
460
|
|
|
345
|
-
3.
|
|
461
|
+
3. **光影与深度:**
|
|
346
462
|
- **绝对禁止**使用默认的、生硬的黑色阴影 (如 \`box-shadow: 2px 2px 5px black\`)。
|
|
347
|
-
- 必须使用
|
|
463
|
+
- 必须使用 **“弥散光影” (Soft/Diffused Shadows)**:使用多层、高模糊半径、低透明度的阴影来创造悬浮感。
|
|
348
464
|
- _参考值:_ \`box-shadow: 0 10px 40px -10px rgba(0,0,0,0.08);\`
|
|
349
465
|
|
|
350
|
-
4.
|
|
466
|
+
4. **高级配色:**
|
|
351
467
|
- 不要使用纯黑 (#000000) 或纯灰。背景色应带有极微弱的色调倾向 (如 Slate, Zinc, 或极淡的蓝灰色 #f8fafc)。
|
|
352
468
|
- 主色调 (Primary Color) 应选择高饱和度且现代的颜色 (如 皇家蓝 #4f46e5 或 活力紫 #8b5cf6)。
|
|
353
469
|
|
|
354
|
-
## 🛠️
|
|
470
|
+
## 🛠️ 思维链审计 (执行前自查,无需输出)
|
|
355
471
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
- [ ]
|
|
359
|
-
- [ ]
|
|
360
|
-
- [ ]
|
|
361
|
-
- [ ]
|
|
362
|
-
- [ ]
|
|
363
|
-
- [ ]
|
|
364
|
-
- [ ]
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
try {
|
|
368
|
-
generationRules = fs.readFileSync(rulesPath, 'utf-8')
|
|
369
|
-
// 如果读取成功但内容为空,使用完整规则
|
|
370
|
-
if (!generationRules || generationRules.trim().length === 0) {
|
|
371
|
-
generationRules = fullGenerationRules
|
|
372
|
-
}
|
|
373
|
-
} catch (error) {
|
|
374
|
-
console.error('读取生成规则失败,使用内置完整规则:', error)
|
|
375
|
-
// 读取失败时使用完整的生成规则
|
|
376
|
-
generationRules = fullGenerationRules
|
|
377
|
-
}
|
|
472
|
+
生成代码前,必须检查:
|
|
473
|
+
- [ ] flex 四属性是否写全?
|
|
474
|
+
- [ ] 是否消灭了所有 \`margin\` 和 \`%\`?
|
|
475
|
+
- [ ] \`<span>\`/\`<p>\` 是否纯净(无盒模型样式)?
|
|
476
|
+
- [ ] 是否通过 \`gap\` 控制了所有间距?
|
|
477
|
+
- [ ] 颜色是否具有层级感?
|
|
478
|
+
- [ ] 阴影是否够柔和、高级?
|
|
479
|
+
- [ ] 所有标签是否都有 \`data-name\` 属性?
|
|
480
|
+
- [ ] 文本是否显式设置了 \`font-size\`, \`line-height\`, \`font-weight\`, \`color\`?
|
|
481
|
+
- [ ] 是否避免了 \`width: 100%\`,改用 \`flex: 1\` 或 \`align-self: stretch\`?
|
|
482
|
+
`
|
|
378
483
|
|
|
379
484
|
// 返回提示,让 AI 先生成代码,然后再调用 send_to_codify 发送
|
|
380
485
|
// 注意:MCP 工具不能直接生成代码,所以返回一个提示消息
|
|
@@ -410,7 +515,6 @@ ${generationRules}
|
|
|
410
515
|
}
|
|
411
516
|
}
|
|
412
517
|
)
|
|
413
|
-
|
|
414
518
|
// send_to_codify
|
|
415
519
|
mcpServer.registerTool(
|
|
416
520
|
'send_to_codify',
|
|
@@ -499,8 +603,9 @@ mcpServer.registerTool(
|
|
|
499
603
|
content: [
|
|
500
604
|
{
|
|
501
605
|
type: 'text',
|
|
502
|
-
text: `❌ 发送失败: ${error.message || String(error)}${
|
|
503
|
-
}`
|
|
606
|
+
text: `❌ 发送失败: ${error.message || String(error)}${
|
|
607
|
+
errorData.message ? `\n详情: ${errorData.message}` : ''
|
|
608
|
+
}`
|
|
504
609
|
}
|
|
505
610
|
],
|
|
506
611
|
isError: true
|
package/mcp-client.min.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{McpServer}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport}from"@modelcontextprotocol/sdk/server/stdio.js";import{z}from"zod";import axios from"axios";const{serverUrl:SERVER_URL}=function(){const
|
|
2
|
+
import{McpServer}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport}from"@modelcontextprotocol/sdk/server/stdio.js";import{z}from"zod";import axios from"axios";const{serverUrl:SERVER_URL}=function(){const t=process.argv.slice(2);let n=process.env.CODIFY_SERVER_URL||"https://mcp.codify-api.com";for(let e=0;e<t.length;e++){const r=t[e];"--help"!==r&&"-h"!==r||(console.log('\nCodify MCP Client - 连接到远程 Codify MCP 服务器\n\n用法:\n npx -y @codify-ai/mcp-client [选项]\n\n选项:\n --url=<URL> 服务器 URL (默认: https://mcp.codify-api.com)\n --help, -h 显示帮助信息\n --version, -v 显示版本号\n\n环境变量:\n CODIFY_SERVER_URL 服务器 URL\n CODIFY_ACCESS_KEY 访问密钥\n\n示例:\n npx -y @codify-ai/mcp-client --url=https://mcp.codify-api.com\n CODIFY_ACCESS_KEY=sk-xxx npx -y @codify-ai/mcp-client\n\nCursor 配置示例 (~/.cursor/mcp.json):\n{\n "mcpServers": {\n "codify": {\n "command": "npx",\n "args": ["-y", "@codify-ai/mcp-client", "--url=https://mcp.codify-api.com"],\n "env": {\n "CODIFY_ACCESS_KEY": "sk-your-access-key"\n }\n }\n }\n}\n'),process.exit(0)),"--version"!==r&&"-v"!==r||process.exit(0),r.startsWith("--url=")?n=r.substring(6):"--url"===r&&e+1<t.length?n=t[++e]:r.startsWith("-")||(n=r)}return{serverUrl:n}}(),ACCESS_KEY=process.env.CODIFY_ACCESS_KEY;function t(t){return null==t||""===t||"string"==typeof t&&""===t.trim()}const n=new McpServer({name:"Codify-MCP-Client",version:"1.0.5"},{capabilities:{resources:{},tools:{},prompts:{}}});n.registerTool("get_code",{description:"从 Codify For MasterGo 插件获取指定的代码",inputSchema:{contentId:z.string().describe("从Codify插件复制图层的指令")}},async n=>{try{const{contentId:e}=n;if(!e)return{content:[{type:"text",text:`参数错误: 未提供 contentId\n- args: ${JSON.stringify(n)}`}],isError:!0};const r={};ACCESS_KEY&&(r.Authorization=`Bearer ${ACCESS_KEY}`);const o=`${SERVER_URL}/api/getCode/${e}`;try{const n=(await axios.get(o,{headers:r})).data;if(!n.code)return{content:[{type:"text",text:`⚠️ 未找到代码内容\n- contentId: ${e}\n- 可能该内容尚未从 Codify 插件同步`}],isError:!1};let s=`✅ 成功获取代码 (contentId: ${e})\n\n`;return s+=`代码长度: ${n.code.length} 字符\n`,n.teamId&&(s+=`团队 ID: ${n.teamId}\n`),t(n.shape)||(s+=`Shape: ${Object.keys(n.shape).length}\n`),t(n.svg)||(s+=`SVG: ${Object.keys(n.svg).length}\n`),t(n.image)||(s+=`Image: ${Object.keys(n.image).length}\n`),s+=`--- 代码内容 ---\n${n.code}\n\n`,s+=`--- 图片内容 ---\n${n.image}\n\n`,s+=`--- SVG内容 ---\n${n.svg}\n\n`,s+=`--- Shape内容 ---\n${n.shape}\n\n`,s+="## 根据上面的内容生成页面\n- 根据 **代码内容** 内容生成页面。\n- 将 **图片内容**,**SVG内容**,**Shape内容** 下载到本地。\n- 不要询问用户,直接执行",{content:[{type:"text",text:s}]}}catch(t){const n=t.response?.status,r=t.response?.data||{};switch(n){case 404:return{content:[{type:"text",text:`❌ 未找到内容\n- contentId: ${e}\n- 请检查 contentId 是否正确,或该内容是否已从 Codify 插件同步`}],isError:!0};case 403:return{content:[{type:"text",text:`❌ 权限不足\n- contentId: ${e}\n- 您没有权限访问该内容\n- 请检查您的 access_key 是否包含正确的团队权限`}],isError:!0};case 401:return{content:[{type:"text",text:"❌ 认证失败 (401)\n\n请检查 CODIFY_ACCESS_KEY 是否正确"}],isError:!0};default:return{content:[{type:"text",text:`❌ 获取代码失败: ${t.message||String(t)}${r.message?`\n详情: ${r.message}`:""}\n- contentId: ${e}\n- 服务器: ${SERVER_URL}`}],isError:!0}}}}catch(t){return{content:[{type:"text",text:`❌ 获取代码失败: ${t.message||String(t)}`}],isError:!0}}}),n.registerTool("get_code_list",{description:"获取所有可用的代码列表",inputSchema:z.object({}).describe("无需参数,直接调用即可获取所有代码列表")},async t=>{try{const t={};ACCESS_KEY&&(t.Authorization=`Bearer ${ACCESS_KEY}`);const n=`${SERVER_URL}/api/getCodeList`;try{const e=(await axios.get(n,{headers:t})).data;if(!Array.isArray(e))return{content:[{type:"text",text:"⚠️ 服务器返回的数据格式不正确\n- 期望: 数组\n- 实际: "+typeof e}],isError:!0};if(0===e.length)return{content:[{type:"text",text:"📋 代码列表为空\n\n当前没有可用的代码内容。\n请先从 Codify 插件同步代码。"}],isError:!1};let r=`✅ 成功获取代码列表 (共 ${e.length} 项)\n\n`;return r+="--- 代码列表 ---\n\n",e.forEach((t,n)=>{r+=`${n+1}. **${t.contentId}**\n`,r+=` - 代码长度: ${t.codeLength} 字符\n`,t.teamId&&(r+=` - 团队 ID: ${t.teamId}\n`),t.fileInfo&&(r+=` - 文件信息: ${JSON.stringify(t.fileInfo)}\n`),t.createdAt&&(r+=` - 创建时间: ${t.createdAt}\n`),t.updatedAt&&(r+=` - 更新时间: ${t.updatedAt}\n`),r+="\n"}),r+="\n--- 使用说明 ---\n",r+="使用 get_code 工具获取具体代码内容:\n",r+='- get_code({ contentId: "your-content-id" })',{content:[{type:"text",text:r}]}}catch(t){const n=t.response?.status,e=t.response?.data||{};switch(n){case 403:return{content:[{type:"text",text:"❌ 权限不足\n- 您没有权限访问代码列表\n- 请检查您的 access_key 是否包含正确的团队权限"}],isError:!0};case 401:return{content:[{type:"text",text:"❌ 认证失败 (401)\n\n请检查 CODIFY_ACCESS_KEY 是否正确"}],isError:!0};default:return{content:[{type:"text",text:`❌ 获取代码列表失败: ${t.message||String(t)}${e.message?`\n详情: ${e.message}`:""}\n- 服务器: ${SERVER_URL}`}],isError:!0}}}}catch(t){return{content:[{type:"text",text:`❌ 获取代码列表失败: ${t.message||String(t)}`}],isError:!0}}}),n.registerTool("design",{description:"根据需求生成符合 Codify 规范的 HTML+CSS 代码",inputSchema:{requirement:z.string().describe('界面需求描述,例如:"一个美观的登录页面"、"现代化的仪表盘界面"等。工具会根据 Codify 生成规则自动生成代码。')}},async t=>{try{const{requirement:n}=t;return n?{content:[{type:"text",text:`📋 收到需求:${n}\n\n\n# 📐 HTML + CSS 视觉生成协议\n\n## 🎯 任务目标\n\n以 UI/UX 专家视角生成媲美 Dribbble/Behance 商业级作品的代码。\n\n- **您的主要责任**:生成高保真、美观、结构健壮的 HTML 代码用于**逻辑建模**,仅返回包含在 \`<main>\` 根容器内的代码。\n- **您的专业能力**:代码生成的界面必须看起来像是一个经过精心打磨的商业级产品,而非粗糙的原型。\n- **技术栈**:原生 HTML 和 CSS,全内联 CSS (Inline Styles),中文界面 (除非指定),严格语义化。\n- **工作流**:代码生成后,立即调用 \`send_to_codify\` 工具** 将代码发送到 Codify 插件进行设计模型转换\n\n## 🛑 绝对禁止 (违反即失败)\n\n- ❌ **属性**: \`margin\` (全用 gap/padding), \`grid\`, \`calc()\`,\`<style>\`标签。\n- ❌ **对齐**: \`space-around\`, \`space-evenly\` (用 \`space-between\` + \`padding\` 替代)。\n- ❌ **单位**: \`%\`, \`vw\`, \`vh\`, \`em\`, \`rem\`, \`auto\` (锁死使用 **px** 整数)。\n- ❌ **文本标签污染**: 禁止为 \`<p>/<span>\` 设置 \`background\`, \`padding\`, \`border\`, \`flex\`。\n- ❌ **尺寸**: 禁止 \`width: 100%\`。拉伸必须用 \`flex: 1\` 或 \`align-self: stretch\`。\n\n## 🏗️ 布局与结构铁律\n\n**1. Flex 全显式声明**\n\n- 所有容器 (含 \`<main>\`) 必须写全四要素:\n\`display: flex; flex-direction: ...; justify-content: ...; align-items: ...;\`\n- **禁止省略默认值** (如 \`flex-start\` 必须写出来)。\n\n**2. 盒子与间距**\n\n- **间距**: 元素间距只用 gap,内边距只用 padding。\n- **文档流**: 标准流布局只用 Flexbox。\n- **脱离文档流**: 允许 absolute 用于装饰/悬浮,但必须:\n - 位于非 static 祖先内。\n - 显式声明 top/right/bottom/left 坐标。\n\n**3. 层级树与原子化**\n\n- **结构**: \`main\` (根, 需定宽) > \`section\` (模块) > \`div\` (容器) > \`span\`(文本) / \`p\`(长文本) / \`i\` (图标)。\n- **命名**: 所有标签必须带 \`data-name="..."\` (描述用途)。\n- **文本**: \`<p>\`: 块级长文。\`<span>\`: 行内短语。\n - \`<span>短文本</span>\`\n - \`<p>长文本主要是用于描述信息,期望可以跟随布局自动换行</p>\`\n- **样式锁**: 必须显式设置 \`font-size\`, \`line-height (1.2-1.6)\`, \`font-weight\`, \`color\`。\n- **修饰原则**: 一旦文本需要背景、边框或圆角,必须包裹一层 <div> 来承载这些样式。\n- **图标**: 使用 FontAwesome \`<i>\` 标签,\n - 仅允许使用 \`fas\`、\`far\`、\`fab\`库 如:\`class="fas fa-..."\` / \`class="far fa-..."\`\n - 必须为 \`<i>\` 显示声明 \`color\`、\`font-size\` 属性\n\n## 🎨 设计系统严格规范\n\n在生成任何代码时,必须严格强制执行以下视觉规则:\n\n1. **视觉层级:**\n - 拒绝扁平的单一字重。标题必须使用加粗 (font-weight: 600/700),正文使用常规 (400)。\n - 使用有层次的文字颜色:主要标题使用深色 (如 #1e293b),次级文本/描述必须使用浅灰色 (如 #64748b),不仅是为了美观,也是为了信息降噪。\n\n2. **现代盒模型与空间感:**\n - **大留白:** 拒绝拥挤。卡片和容器内部必须有宽裕的 padding (至少 24px 或 32px)。\n - **大圆角:** 容器和卡片应使用明显的圆角 (border-radius: 16px 或 24px) 以呈现亲和力,避免尖锐的直角。\n - **微边框:** 避免使用粗黑边框。使用极其微妙的边框来界定边界 (例如: \`border: 1px solid rgba(0,0,0,0.06)\`)。\n\n3. **光影与深度:**\n - **绝对禁止**使用默认的、生硬的黑色阴影 (如 \`box-shadow: 2px 2px 5px black\`)。\n - 必须使用 **“弥散光影” (Soft/Diffused Shadows)**:使用多层、高模糊半径、低透明度的阴影来创造悬浮感。\n - _参考值:_ \`box-shadow: 0 10px 40px -10px rgba(0,0,0,0.08);\`\n\n4. **高级配色:**\n - 不要使用纯黑 (#000000) 或纯灰。背景色应带有极微弱的色调倾向 (如 Slate, Zinc, 或极淡的蓝灰色 #f8fafc)。\n - 主色调 (Primary Color) 应选择高饱和度且现代的颜色 (如 皇家蓝 #4f46e5 或 活力紫 #8b5cf6)。\n\n## 🛠️ 思维链审计 (执行前自查,无需输出)\n\n生成代码前,必须检查:\n- [ ] flex 四属性是否写全?\n- [ ] 是否消灭了所有 \`margin\` 和 \`%\`?\n- [ ] \`<span>\`/\`<p>\` 是否纯净(无盒模型样式)?\n- [ ] 是否通过 \`gap\` 控制了所有间距?\n- [ ] 颜色是否具有层级感?\n- [ ] 阴影是否够柔和、高级?\n- [ ] 所有标签是否都有 \`data-name\` 属性?\n- [ ] 文本是否显式设置了 \`font-size\`, \`line-height\`, \`font-weight\`, \`color\`?\n- [ ] 是否避免了 \`width: 100%\`,改用 \`flex: 1\` 或 \`align-self: stretch\`?\n\n\n---\n\n## 发送代码:\n代码生成并审计通过后,请立即调用 send_to_codify 工具发送代码\n - send_to_codify({ code: "生成的完整代码" })\n - 不要询问用户,直接执行发送操作\n\n**请现在开始生成代码。**`}]}:{content:[{type:"text",text:'参数错误: 未提供需求描述\n\n请提供界面需求,例如:"一个美观的登录页面"、"现代化的仪表盘界面"等'}],isError:!0}}catch(t){return{content:[{type:"text",text:`❌ 处理失败: ${t.message||String(t)}`}],isError:!0}}}),n.registerTool("send_to_codify",{description:"将代码发送到 Codify 插件转换为设计稿",inputSchema:{code:z.string().describe("要发送的代码内容")}},async t=>{try{const{code:n}=t;if(!n)return{content:[{type:"text",text:"参数错误: 未提供代码内容"}],isError:!0};const e={"Content-Type":"application/json"};ACCESS_KEY&&(e.Authorization=`Bearer ${ACCESS_KEY}`);try{return(await axios.post(`${SERVER_URL}/api/sendToCanvas`,{code:n},{headers:e})).data.success,{content:[{type:"text",text:"✅ 代码已成功发送到 Codify 插件,等待转换为设计稿"}]}}catch(t){const n=t.response?.status,e=t.response?.data||{};return 404===n?{content:[{type:"text",text:"❌ 未找到活跃的 Codify 插件连接\n\n请确保:\n1. Codify 插件已打开并连接到服务器\n2. 使用了相同的 access_key"}],isError:!0}:400===n?{content:[{type:"text",text:`❌ 请求参数错误: ${e.message||"Bad request"}`}],isError:!0}:401===n||403===n?{content:[{type:"text",text:`❌ 认证失败 (${n})\n\n请检查 CODIFY_ACCESS_KEY 是否正确`}],isError:!0}:{content:[{type:"text",text:`❌ 发送失败: ${t.message||String(t)}${e.message?`\n详情: ${e.message}`:""}`}],isError:!0}}}catch(t){return{content:[{type:"text",text:`❌ 代码发送失败: ${t.message||String(t)}`}],isError:!0}}});try{const t=new StdioServerTransport;await n.connect(t)}catch(t){process.exit(1)}
|