@codify-ai/mcp-client 1.0.1 → 1.0.3

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/mcp-client.js CHANGED
@@ -1,44 +1,28 @@
1
- #!/usr/bin/env node
2
-
3
1
  /**
4
2
  * Codify MCP Client
5
3
  * 连接到远程 Codify MCP Server 的客户端适配器
6
- *
7
- * 使用方法:
8
- * 在 Cursor 配置 (~/.cursor/mcp.json) 中:
9
- {
10
- "mcpServers": {
11
- "codify": {
12
- "command": "npx",
13
- "args": ["-y", "@codify/mcp-client", "--url=http://your-server:8080"],
14
- "env": {
15
- "CODIFY_ACCESS_KEY": "sk-your-access-key"
16
- }
17
- }
18
- }
19
- }
20
4
  */
21
-
22
- import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
23
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
24
- import { z } from 'zod';
5
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
6
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
7
+ import { z } from 'zod'
8
+ import axios from 'axios'
25
9
 
26
10
  function parseArgs() {
27
- const args = process.argv.slice(2);
28
- let serverUrl = process.env.CODIFY_SERVER_URL || 'http://localhost:8080';
29
-
11
+ const args = process.argv.slice(2)
12
+ let serverUrl = process.env.CODIFY_SERVER_URL || 'https://mcp.codify-api.com'
13
+
30
14
  for (let i = 0; i < args.length; i++) {
31
- const arg = args[i];
32
-
15
+ const arg = args[i]
16
+
33
17
  if (arg === '--help' || arg === '-h') {
34
18
  console.log(`
35
19
  Codify MCP Client - 连接到远程 Codify MCP 服务器
36
20
 
37
21
  用法:
38
- npx -y @codify/mcp-client [选项]
22
+ npx -y @codify-ai/mcp-client [选项]
39
23
 
40
24
  选项:
41
- --url=<URL> 服务器 URL (默认: http://localhost:8080)
25
+ --url=<URL> 服务器 URL (默认: https://mcp.codify-api.com)
42
26
  --help, -h 显示帮助信息
43
27
  --version, -v 显示版本号
44
28
 
@@ -47,418 +31,499 @@ Codify MCP Client - 连接到远程 Codify MCP 服务器
47
31
  CODIFY_ACCESS_KEY 访问密钥
48
32
 
49
33
  示例:
50
- npx -y @codify/mcp-client --url=http://localhost:8080
51
- CODIFY_ACCESS_KEY=sk-xxx npx -y @codify/mcp-client
34
+ npx -y @codify-ai/mcp-client --url=https://mcp.codify-api.com
35
+ CODIFY_ACCESS_KEY=sk-xxx npx -y @codify-ai/mcp-client
52
36
 
53
37
  Cursor 配置示例 (~/.cursor/mcp.json):
54
38
  {
55
39
  "mcpServers": {
56
40
  "codify": {
57
41
  "command": "npx",
58
- "args": ["-y", "@codify/mcp-client", "--url=http://your-server:8080"],
42
+ "args": ["-y", "@codify-ai/mcp-client", "--url=https://mcp.codify-api.com"],
59
43
  "env": {
60
44
  "CODIFY_ACCESS_KEY": "sk-your-access-key"
61
45
  }
62
46
  }
63
47
  }
64
48
  }
65
- `);
66
- process.exit(0);
49
+ `)
50
+ process.exit(0)
67
51
  }
68
-
69
52
  if (arg === '--version' || arg === '-v') {
70
- console.log('1.0.0');
71
- process.exit(0);
53
+ console.log('1.0.1')
54
+ process.exit(0)
72
55
  }
73
-
74
56
  if (arg.startsWith('--url=')) {
75
- serverUrl = arg.substring(6);
57
+ serverUrl = arg.substring(6)
76
58
  } else if (arg === '--url' && i + 1 < args.length) {
77
- serverUrl = args[++i];
59
+ serverUrl = args[++i]
78
60
  } else if (!arg.startsWith('-')) {
79
- serverUrl = arg;
61
+ serverUrl = arg
80
62
  }
81
63
  }
82
-
83
- return { serverUrl };
64
+ return { serverUrl }
84
65
  }
85
66
 
86
- const { serverUrl: SERVER_URL } = parseArgs();
87
- const ACCESS_KEY = process.env.CODIFY_ACCESS_KEY;
67
+ const { serverUrl: SERVER_URL } = parseArgs()
68
+ const ACCESS_KEY = process.env.CODIFY_ACCESS_KEY
88
69
 
89
- if (process.env.DEBUG) {
90
- console.error(`[DEBUG] 环境变量:`, Object.keys(process.env).filter(k => k.includes('CODIFY')));
91
- }
92
-
93
- console.error(`🔌 连接到远程服务器: ${SERVER_URL}`);
94
- if (ACCESS_KEY) {
95
- console.error(`🔑 使用认证密钥: ${ACCESS_KEY.substring(0, 10)}...`);
96
- } else {
97
- console.error(`⚠️ 未设置认证密钥 (CODIFY_ACCESS_KEY)`);
98
- console.error(`💡 提示: 请在 ~/.cursor/mcp.json 的 "env" 字段中设置 CODIFY_ACCESS_KEY`);
99
- console.error(`💡 或者通过环境变量: export CODIFY_ACCESS_KEY=sk-xxx`);
70
+ // 辅助函数:检查值是否为空
71
+ function isEmpty(value) {
72
+ return value === null || value === undefined || value === '' || (typeof value === 'string' && value.trim() === '')
100
73
  }
101
74
 
102
75
  // 初始化 MCP Server
103
- const mcpServer = new McpServer({
104
- name: "Codify-MCP-Client",
105
- version: "1.0.0",
106
- }, {
107
- capabilities: {
108
- resources: {},
109
- tools: {} // 添加 tools 能力
110
- }
111
- });
112
-
113
- // 注册资源(使用 ResourceTemplate)
114
- const resourceTemplate = new ResourceTemplate(
115
- "codify://{channelId}/current",
76
+ const mcpServer = new McpServer(
116
77
  {
117
- list: async () => {
118
- try {
119
- // 从远程服务器获取所有通道列表
120
- const headers = {};
121
- if (ACCESS_KEY) {
122
- headers['Authorization'] = `Bearer ${ACCESS_KEY}`;
123
- }
124
-
125
- const response = await fetch(`${SERVER_URL}/channels`, { headers });
126
- if (!response.ok) {
127
- throw new Error(`HTTP error! status: ${response.status}`);
128
- }
129
- const channels = await response.json();
130
-
131
- return {
132
- resources: channels.map(ch => ({
133
- uri: `codify://${ch.channelId}/current`,
134
- name: `${ch.channelId}-code`,
135
- mimeType: "text/plain"
136
- }))
137
- };
138
- } catch (error) {
139
- console.error(`❌ 获取通道列表失败:`, error);
140
- return { resources: [] };
141
- }
142
- },
143
- complete: {}
78
+ name: 'Codify-MCP-Client',
79
+ version: '1.0.1'
80
+ },
81
+ {
82
+ capabilities: {
83
+ resources: {},
84
+ tools: {},
85
+ prompts: {}
86
+ }
144
87
  }
145
- );
88
+ )
146
89
 
147
- mcpServer.registerResource(
148
- "current-code",
149
- resourceTemplate,
150
- { mimeType: "text/plain" },
151
- async (uri, variables) => {
90
+ // get_code
91
+ mcpServer.registerTool(
92
+ 'get_code',
93
+ {
94
+ description: '从 Codify For MasterGo 插件获取指定的代码',
95
+ inputSchema: {
96
+ contentId: z.string().describe('从Codify插件复制图层的指令')
97
+ }
98
+ },
99
+ async (args) => {
152
100
  try {
153
- // variables 中获取 channelId(由 SDK 解析)
154
- // 如果 variables 为空或没有 channelId,尝试从 URI 解析
155
- let channelId = variables?.channelId;
156
-
157
- if (!channelId) {
158
- // 尝试从 URI hostname 获取(codify://my-demo/current -> my-demo)
159
- channelId = uri.hostname;
160
- }
161
-
162
- // 如果还是为空,尝试从 pathname 解析(备用方案)
163
- if (!channelId && uri.pathname) {
164
- const pathParts = uri.pathname.split('/').filter(p => p);
165
- if (pathParts.length > 0) {
166
- channelId = pathParts[0];
167
- }
168
- }
169
-
170
- if (!channelId) {
171
- console.error(`❌ 无法从 URI 解析 channelId: ${uri.href}`);
172
- console.error(` variables:`, variables);
101
+ const { contentId } = args
102
+
103
+ if (!contentId) {
173
104
  return {
174
- contents: [{
175
- uri: uri.href,
176
- text: `// 错误: 无法解析频道 ID\n// URI: ${uri.href}\n// 请使用格式: codify://channelId/current`
177
- }]
178
- };
179
- }
180
-
181
- if (process.env.DEBUG) {
182
- console.error(`[DEBUG] 获取资源: ${uri.href}`);
183
- console.error(`[DEBUG] variables:`, variables);
184
- console.error(`[DEBUG] uri.hostname:`, uri.hostname);
185
- console.error(`[DEBUG] uri.pathname:`, uri.pathname);
186
- console.error(`[DEBUG] 解析的 channelId: ${channelId}`);
187
- console.error(`[DEBUG] SERVER_URL: ${SERVER_URL}`);
188
- console.error(`[DEBUG] ACCESS_KEY: ${ACCESS_KEY ? ACCESS_KEY.substring(0, 10) + '...' : '未设置'}`);
105
+ content: [
106
+ {
107
+ type: 'text',
108
+ text: `参数错误: 未提供 contentId\n- args: ${JSON.stringify(args)}`
109
+ }
110
+ ],
111
+ isError: true
112
+ }
189
113
  }
190
-
114
+
191
115
  // 添加认证头
192
- const headers = {};
116
+ const headers = {}
193
117
  if (ACCESS_KEY) {
194
- headers['Authorization'] = `Bearer ${ACCESS_KEY}`;
118
+ headers['Authorization'] = `Bearer ${ACCESS_KEY}`
195
119
  }
196
-
120
+
197
121
  // 通过 HTTP API 从远程服务器获取数据
198
- const apiUrl = `${SERVER_URL}/api/channel/${channelId}`;
199
- if (process.env.DEBUG) {
200
- console.error(`[DEBUG] 请求 URL: ${apiUrl}`);
201
- }
202
-
203
- let response;
122
+ const apiUrl = `${SERVER_URL}/api/getCode/${contentId}`
204
123
  try {
205
- response = await fetch(apiUrl, { headers });
206
- } catch (fetchError) {
207
- const errorMsg = fetchError instanceof Error ? fetchError.message : String(fetchError);
208
- console.error(`❌ 网络请求失败: ${errorMsg}`);
209
- console.error(` 服务器 URL: ${SERVER_URL}`);
210
- console.error(` 请检查: 1) 服务器是否运行 2) URL 是否正确 3) 网络连接`);
124
+ const response = await axios.get(apiUrl, { headers })
125
+ const data = response.data
126
+
127
+ if (!data.code) {
128
+ return {
129
+ content: [
130
+ {
131
+ type: 'text',
132
+ text: `⚠️ 未找到代码内容\n- contentId: ${contentId}\n- 可能该内容尚未从 Codify 插件同步`
133
+ }
134
+ ],
135
+ isError: false
136
+ }
137
+ }
138
+
139
+ // 构建返回信息
140
+ let resultText = `✅ 成功获取代码 (contentId: ${contentId})\n\n`
141
+ resultText += `代码长度: ${data.code.length} 字符\n`
142
+ if (data.teamId) {
143
+ resultText += `团队 ID: ${data.teamId}\n`
144
+ }
145
+
146
+ // 添加 shape、svg、image 字段(如果存在)
147
+ if (!isEmpty(data.shape)) {
148
+ resultText += `Shape: ${Object.keys(data.shape).length}\n`
149
+ }
150
+ if (!isEmpty(data.svg)) {
151
+ resultText += `SVG: ${Object.keys(data.svg).length}\n`
152
+ }
153
+ if (!isEmpty(data.image)) {
154
+ resultText += `Image: ${Object.keys(data.image).length}\n`
155
+ }
156
+
157
+ resultText += `--- 代码内容 ---\n${data.code}\n\n`
158
+ resultText += `--- 图片内容 ---\n${data.image}\n\n`
159
+ resultText += `--- SVG内容 ---\n${data.svg}\n\n`
160
+ resultText += `--- Shape内容 ---\n${data.shape}\n\n`
161
+ resultText += `## 根据上面的内容生成页面
162
+ - 根据 **代码内容** 内容生成页面。
163
+ - 将 **图片内容**,**SVG内容**,**Shape内容** 下载到本地。
164
+ - 不要询问用户,直接执行`
211
165
  return {
212
- contents: [{
213
- uri: uri.href,
214
- text: `// 网络错误: ${errorMsg}\n// 服务器: ${SERVER_URL}\n// 请检查服务器地址和网络连接`
215
- }]
216
- };
217
- }
218
-
219
- if (!response.ok) {
220
- const errorText = await response.text().catch(() => '无法读取错误信息');
221
- console.error(`❌ API 请求失败: ${response.status} ${response.statusText}`);
222
- console.error(` 错误详情: ${errorText}`);
223
-
224
- // 如果 API 返回错误,尝试从 channels 列表中查找
225
- try {
226
- const channelsResponse = await fetch(`${SERVER_URL}/channels`, { headers });
227
-
228
- if (!channelsResponse.ok) {
229
- const channelsErrorText = await channelsResponse.text().catch(() => '无法读取错误信息');
230
- console.error(`❌ 获取频道列表失败: ${channelsResponse.status}`);
231
- console.error(` 错误详情: ${channelsErrorText}`);
166
+ content: [
167
+ {
168
+ type: 'text',
169
+ text: resultText
170
+ }
171
+ ]
172
+ }
173
+ } catch (error) {
174
+ const status = error.response?.status
175
+ const errorData = error.response?.data || {}
176
+
177
+ switch (status) {
178
+ case 404:
232
179
  return {
233
- contents: [{
234
- uri: uri.href,
235
- text: `// 认证失败或服务器错误 (${channelsResponse.status})\n// 错误: ${channelsErrorText}\n// 请检查 CODIFY_ACCESS_KEY 是否正确`
236
- }]
237
- };
238
- }
239
-
240
- const channels = await channelsResponse.json();
241
- const channel = channels.find(ch => ch.channelId === channelId);
242
-
243
- if (!channel) {
180
+ content: [
181
+ {
182
+ type: 'text',
183
+ text: `❌ 未找到内容\n- contentId: ${contentId}\n- 请检查 contentId 是否正确,或该内容是否已从 Codify 插件同步`
184
+ }
185
+ ],
186
+ isError: true
187
+ }
188
+ case 403:
244
189
  return {
245
- contents: [{
246
- uri: uri.href,
247
- text: `// 通道 ${channelId} 不存在或为空\n// 可用通道: ${channels.map(ch => ch.channelId).join(', ') || '无'}`
248
- }]
249
- };
250
- }
251
-
252
- // 通道存在但第一次获取失败,再次尝试获取代码
253
- console.error(`⚠️ 第一次获取失败,但通道存在,再次尝试获取代码...`);
254
- try {
255
- const retryResponse = await fetch(`${SERVER_URL}/api/channel/${channelId}`, { headers });
256
- if (retryResponse.ok) {
257
- const retryData = await retryResponse.json();
258
- console.log(`✅ 重试成功,获取到代码`);
259
- return {
260
- contents: [{
261
- uri: uri.href,
262
- text: retryData.code || "// 等待 Figma WebSocket..."
263
- }]
264
- };
265
- } else {
266
- const retryErrorText = await retryResponse.text().catch(() => '无法读取错误信息');
267
- console.error(`❌ 重试仍然失败: ${retryResponse.status}`);
268
- return {
269
- contents: [{
270
- uri: uri.href,
271
- text: `// 通道 ${channelId} 存在但无法获取代码 (${retryResponse.status})\n// 代码长度: ${channel.codeLength} 字符\n// 最后更新: ${channel.lastUpdate}\n// 错误: ${retryErrorText}`
272
- }]
273
- };
190
+ content: [
191
+ {
192
+ type: 'text',
193
+ text: `❌ 权限不足\n- contentId: ${contentId}\n- 您没有权限访问该内容\n- 请检查您的 access_key 是否包含正确的团队权限`
194
+ }
195
+ ],
196
+ isError: true
274
197
  }
275
- } catch (retryError) {
276
- console.error(`❌ 重试请求异常:`, retryError);
198
+ case 401:
277
199
  return {
278
- contents: [{
279
- uri: uri.href,
280
- text: `// 通道 ${channelId} 存在但获取代码失败\n// 代码长度: ${channel.codeLength} 字符\n// 最后更新: ${channel.lastUpdate}\n// 错误: ${retryError instanceof Error ? retryError.message : String(retryError)}`
281
- }]
282
- };
283
- }
284
- } catch (channelsError) {
285
- console.error(`❌ 获取频道列表异常:`, channelsError);
286
- return {
287
- contents: [{
288
- uri: uri.href,
289
- text: `// 获取频道列表失败: ${channelsError instanceof Error ? channelsError.message : String(channelsError)}\n// 原始错误: ${response.status} ${errorText}`
290
- }]
291
- };
200
+ content: [
201
+ {
202
+ type: 'text',
203
+ text: `❌ 认证失败 (401)\n\n请检查 CODIFY_ACCESS_KEY 是否正确`
204
+ }
205
+ ],
206
+ isError: true
207
+ }
208
+ default:
209
+ return {
210
+ content: [
211
+ {
212
+ type: 'text',
213
+ text: `❌ 获取代码失败: ${error.message || String(error)}${errorData.message ? `\n详情: ${errorData.message}` : ''
214
+ }\n- contentId: ${contentId}\n- 服务器: ${SERVER_URL}`
215
+ }
216
+ ],
217
+ isError: true
218
+ }
292
219
  }
293
220
  }
294
-
295
- const data = await response.json();
296
-
297
- if (process.env.DEBUG) {
298
- console.error(`[DEBUG] 成功获取代码,长度: ${data.code?.length || 0} 字符`);
299
- }
300
-
301
- return {
302
- contents: [{
303
- uri: uri.href,
304
- text: data.code || "// 等待 Figma WebSocket..."
305
- }]
306
- };
307
221
  } catch (error) {
308
- const errorMsg = error instanceof Error ? error.message : String(error);
309
- console.error(`❌ 获取资源失败:`, errorMsg);
310
- if (error.stack && process.env.DEBUG) {
311
- console.error(` 堆栈:`, error.stack);
312
- }
313
222
  return {
314
- contents: [{
315
- uri: uri.href,
316
- text: `// 错误: ${errorMsg}\n// URI: ${uri.href}\n// 服务器: ${SERVER_URL}`
317
- }]
318
- };
223
+ content: [
224
+ {
225
+ type: 'text',
226
+ text: `❌ 获取代码失败: ${error.message || String(error)}`
227
+ }
228
+ ],
229
+ isError: true
230
+ }
319
231
  }
320
232
  }
321
- );
233
+ )
322
234
 
323
- // 注册工具:getCode - 从 SQLite 获取指定频道的代码
235
+ // design
324
236
  mcpServer.registerTool(
325
- "getCode",
326
- z.object({
327
- channelId: z.string().describe("频道 ID,例如: my-demo, test-channel")
328
- }).describe("从 Codify 服务器获取指定频道的代码"),
329
- async ({ channelId }) => {
237
+ 'design',
238
+ {
239
+ description: '根据需求生成符合 Codify 规范的 HTML+CSS 代码',
240
+ inputSchema: {
241
+ requirement: z.string().describe('界面需求描述,例如:"一个美观的登录页面"、"现代化的仪表盘界面"等。工具会根据 Codify 生成规则自动生成代码。')
242
+ }
243
+ },
244
+ async (args) => {
330
245
  try {
331
- console.error(`[Tool] getCode 被调用: channelId=${channelId}`);
332
-
333
- // 添加认证头
334
- const headers = {};
335
- if (ACCESS_KEY) {
336
- headers['Authorization'] = `Bearer ${ACCESS_KEY}`;
246
+ const { requirement } = args
247
+
248
+ if (!requirement) {
249
+ return {
250
+ content: [
251
+ {
252
+ type: 'text',
253
+ text: '参数错误: 未提供需求描述\n\n请提供界面需求,例如:"一个美观的登录页面"、"现代化的仪表盘界面"等'
254
+ }
255
+ ],
256
+ isError: true
257
+ }
337
258
  }
338
-
339
- // 从远程服务器获取代码
340
- const response = await fetch(`${SERVER_URL}/api/channel/${channelId}`, { headers });
341
-
342
- if (!response.ok) {
343
- if (response.status === 404) {
344
- return {
345
- content: [
346
- {
347
- type: "text",
348
- text: `❌ 频道 "${channelId}" 不存在\n\n💡 使用 listChannels 工具查看所有可用频道`
349
- }
350
- ]
351
- };
352
- } else if (response.status === 401 || response.status === 403) {
353
- return {
354
- content: [
355
- {
356
- type: "text",
357
- text: `❌ 认证失败 (${response.status})\n\n请检查 CODIFY_ACCESS_KEY 是否正确`
358
- }
359
- ]
360
- };
361
- } else {
362
- throw new Error(`HTTP ${response.status}`);
259
+
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
+
270
+ // 完整的生成规则(作为备用)
271
+ const fullGenerationRules = `# 📐 HTML + CSS 视觉生成协议
272
+
273
+ ## 🎯 任务目标
274
+
275
+ 你是一位拥有顶级审美的 UI/UX 设计师,同时也是精通 HTML/CSS 的前端开发专家。你的目标是生成在视觉上媲美 Dribbble 或 Behance 热门作品的 HTML/CSS 代码。
276
+
277
+ **您的主要责任**:生成高保真、美观、结构健壮的 HTML 代码用于**逻辑建模**,而不是用于真实网页的交互或行为。
278
+
279
+ **你的专业能力**:代码生成的界面必须看起来像是一个经过精心打磨的商业级产品,而非粗糙的原型。
280
+
281
+ **输出限制**:仅返回包含在 \`<section>\` 根容器内的代码,全部使用 **内联 CSS**。
282
+
283
+ **语言**:默认使用 **中文** 界面,除非用户提出其它语言的界面需求。
284
+
285
+ ## 🛑 关键约束 (违反视为任务失败)
286
+
287
+ - **Flex 属性全显式声明**:
288
+ - 所有 flex 容器(包含 section)必须手动写全三要素:\`flex-direction\`, \`justify-content\`, \`align-items\`。
289
+ - **禁止省略默认值**:即便对齐方式是 \`flex-start\`,也必须显式写出。
290
+
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" 属性。
298
+
299
+ - **元素间的间距**:
300
+ - 必须由父容器的 \`gap\` 属性承担。
301
+
302
+ ## 📜 HTML 结构与原子化规范
303
+
304
+ - **文档流布局**:
305
+ - 所有在文档流中的布局都必须使用flex布局,不允许使用其他布局方式
306
+ - \`<section>\` 根元素必须明确声明 \`width: px\` 尺寸。
307
+
308
+ - **层级树协议**:
309
+ - 严格遵守:\`section\` (根) > \`article\` (模块) > \`div\` (容器) > \`p/span/i/svg\` (内容)。
310
+ - 所有标签必须包含 \`data-name="..."\` 描述图层用途(如 \`data-name="submit-button"\`)。
311
+
312
+ - **文本原子化**:
313
+ - 文本内容必须且仅能包裹在 \`<span>\` (行内文本) 或 \`<p>\` (长文本/换行) 中。
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\`。
331
+
332
+ ## 🎨 设计系统严格规范
333
+
334
+ 在生成任何代码时,必须严格强制执行以下视觉规则:
335
+
336
+ 1. **视觉层级 (Visual Hierarchy):**
337
+ - 拒绝扁平的单一字重。标题必须使用加粗 (font-weight: 600/700),正文使用常规 (400)。
338
+ - 使用有层次的文字颜色:主要标题使用深色 (如 #1e293b),次级文本/描述必须使用浅灰色 (如 #64748b),不仅是为了美观,也是为了信息降噪。
339
+
340
+ 2. **现代盒模型与空间感 (Modern Box Model):**
341
+ - **大留白:** 拒绝拥挤。卡片和容器内部必须有宽裕的 padding (至少 24px 或 32px)。
342
+ - **大圆角:** 容器和卡片应使用明显的圆角 (border-radius: 16px 或 24px) 以呈现亲和力,避免尖锐的直角。
343
+ - **微边框:** 避免使用粗黑边框。使用极其微妙的边框来界定边界 (例如: \`border: 1px solid rgba(0,0,0,0.06)\`)。
344
+
345
+ 3. **光影与深度 (Elevation & Depth):**
346
+ - **绝对禁止**使用默认的、生硬的黑色阴影 (如 \`box-shadow: 2px 2px 5px black\`)。
347
+ - 必须使用 **"弥散光影" (Soft/Diffused Shadows)**:使用多层、高模糊半径、低透明度的阴影来创造悬浮感。
348
+ - _参考值:_ \`box-shadow: 0 10px 40px -10px rgba(0,0,0,0.08);\`
349
+
350
+ 4. **高级配色 (Refined Palette):**
351
+ - 不要使用纯黑 (#000000) 或纯灰。背景色应带有极微弱的色调倾向 (如 Slate, Zinc, 或极淡的蓝灰色 #f8fafc)。
352
+ - 主色调 (Primary Color) 应选择高饱和度且现代的颜色 (如 皇家蓝 #4f46e5 或 活力紫 #8b5cf6)。
353
+
354
+ ## 🛠️ 输出前专项审计清单
355
+
356
+ **请在输出代码前,按顺序完成以下审查,确保逻辑无误:**
357
+
358
+ - [ ] **显式声明检查**:每一个 \`flex\` 节点是否都写全了 \`flex-direction\`, \`justify-content\`, \`align-items\`?
359
+ - [ ] **拉伸逻辑检查**:拉伸行为是否由子元素主动声明;代码中是否彻底消灭了 \`width: 100%\` 和 \`%\` 符号,
360
+ - [ ] **尺寸检查**:所有尺寸都使用了 \`[具体px值]\` 格式
361
+ - [ ] **文本检查**:所有 \`<span>\` 和 \`<p>\` 是否都配置了 \`line-height\`?
362
+ - [ ] **margin检查**:是否移除了所有 \`margin\` 属性?
363
+ - [ ] **容器补全**:如果发现没有父容器可以承载 padding,请务必新建一个 \`data-name="padding-wrapper"\` 的 div 来包裹目标元素。
364
+ - [ ] **代码检查**:请确保页面布局正确。`
365
+
366
+ let generationRules = ''
367
+ try {
368
+ generationRules = fs.readFileSync(rulesPath, 'utf-8')
369
+ // 如果读取成功但内容为空,使用完整规则
370
+ if (!generationRules || generationRules.trim().length === 0) {
371
+ generationRules = fullGenerationRules
363
372
  }
373
+ } catch (error) {
374
+ console.error('读取生成规则失败,使用内置完整规则:', error)
375
+ // 读取失败时使用完整的生成规则
376
+ generationRules = fullGenerationRules
364
377
  }
365
-
366
- const data = await response.json();
367
-
378
+
379
+ // 返回提示,让 AI 先生成代码,然后再调用 send_to_codify 发送
380
+ // 注意:MCP 工具不能直接生成代码,所以返回一个提示消息
368
381
  return {
369
382
  content: [
370
383
  {
371
- type: "text",
372
- text: `✅ 成功获取频道 "${channelId}" 的代码:\n\n\`\`\`\n${data.code}\n\`\`\`\n\n📊 元数据:\n- 代码长度: ${data.code.length} 字符\n- 最后更新: ${data.lastUpdate}`
384
+ type: 'text',
385
+ text: `📋 收到需求:${requirement}
386
+
387
+ ${generationRules}
388
+
389
+ ---
390
+
391
+ ## 发送代码:
392
+ 代码生成并审计通过后,请立即调用 send_to_codify 工具发送代码
393
+ - send_to_codify({ code: "生成的完整代码" })
394
+ - 不要询问用户,直接执行发送操作
395
+
396
+ **请现在开始生成代码。**`
373
397
  }
374
398
  ]
375
- };
399
+ }
376
400
  } catch (error) {
377
- console.error(`[Tool] getCode 错误:`, error);
378
401
  return {
379
402
  content: [
380
403
  {
381
- type: "text",
382
- text: `❌ 获取代码失败: ${error.message}\n\n请确保:\n1. Codify 服务器正在运行\n2. 频道 ID 正确\n3. 网络连接正常`
404
+ type: 'text',
405
+ text: `❌ 处理失败: ${error.message || String(error)}`
383
406
  }
384
407
  ],
385
408
  isError: true
386
- };
409
+ }
387
410
  }
388
411
  }
389
- );
412
+ )
390
413
 
391
- // 注册工具:listChannels - 列出所有可用频道
414
+ // send_to_codify
392
415
  mcpServer.registerTool(
393
- "listChannels",
394
- z.object({}).describe("列出 Codify 服务器上所有可用的频道"),
395
- async () => {
416
+ 'send_to_codify',
417
+ {
418
+ description: '将代码发送到 Codify 插件转换为设计稿',
419
+ inputSchema: {
420
+ code: z.string().describe('要发送的代码内容')
421
+ }
422
+ },
423
+ async (args) => {
396
424
  try {
397
- console.error(`[Tool] listChannels 被调用`);
398
-
425
+ const { code } = args
426
+
427
+ if (!code) {
428
+ return {
429
+ content: [
430
+ {
431
+ type: 'text',
432
+ text: '参数错误: 未提供代码内容'
433
+ }
434
+ ],
435
+ isError: true
436
+ }
437
+ }
438
+
399
439
  // 添加认证头
400
- const headers = {};
401
- if (ACCESS_KEY) {
402
- headers['Authorization'] = `Bearer ${ACCESS_KEY}`;
440
+ const headers = {
441
+ 'Content-Type': 'application/json'
403
442
  }
404
-
405
- // 从远程服务器获取频道列表
406
- const response = await fetch(`${SERVER_URL}/channels`, { headers });
407
-
408
- if (!response.ok) {
409
- throw new Error(`HTTP ${response.status}`);
443
+ if (ACCESS_KEY) {
444
+ headers['Authorization'] = `Bearer ${ACCESS_KEY}`
410
445
  }
411
-
412
- const channels = await response.json();
413
-
414
- if (channels.length === 0) {
446
+
447
+ // 发送代码到 Codify 插件
448
+ try {
449
+ const response = await axios.post(`${SERVER_URL}/api/sendToCanvas`, { code }, { headers })
450
+
451
+ const result = response.data
452
+ if (result.success) {
453
+ }
454
+
415
455
  return {
416
456
  content: [
417
457
  {
418
- type: "text",
419
- text: "📭 暂无可用频道\n\n💡 通过 WebSocket 发送代码到服务器来创建频道"
458
+ type: 'text',
459
+ text: `✅ 代码已成功发送到 Codify 插件,等待转换为设计稿`
420
460
  }
421
461
  ]
422
- };
423
- }
424
-
425
- const channelList = channels.map((ch, i) =>
426
- `${i + 1}. **${ch.channelId}**\n - 代码长度: ${ch.codeLength} 字符\n - 最后更新: ${ch.lastUpdate}`
427
- ).join('\n\n');
428
-
429
- return {
430
- content: [
431
- {
432
- type: "text",
433
- text: `📺 可用频道列表 (共 ${channels.length} 个):\n\n${channelList}\n\n💡 使用 getCode 工具获取具体频道的代码`
462
+ }
463
+ } catch (error) {
464
+ const status = error.response?.status
465
+ const errorData = error.response?.data || {}
466
+
467
+ if (status === 404) {
468
+ return {
469
+ content: [
470
+ {
471
+ type: 'text',
472
+ text: '❌ 未找到活跃的 Codify 插件连接\n\n请确保:\n1. Codify 插件已打开并连接到服务器\n2. 使用了相同的 access_key'
473
+ }
474
+ ],
475
+ isError: true
434
476
  }
435
- ]
436
- };
477
+ } else if (status === 400) {
478
+ return {
479
+ content: [
480
+ {
481
+ type: 'text',
482
+ text: `❌ 请求参数错误: ${errorData.message || 'Bad request'}`
483
+ }
484
+ ],
485
+ isError: true
486
+ }
487
+ } else if (status === 401 || status === 403) {
488
+ return {
489
+ content: [
490
+ {
491
+ type: 'text',
492
+ text: `❌ 认证失败 (${status})\n\n请检查 CODIFY_ACCESS_KEY 是否正确`
493
+ }
494
+ ],
495
+ isError: true
496
+ }
497
+ } else {
498
+ return {
499
+ content: [
500
+ {
501
+ type: 'text',
502
+ text: `❌ 发送失败: ${error.message || String(error)}${errorData.message ? `\n详情: ${errorData.message}` : ''
503
+ }`
504
+ }
505
+ ],
506
+ isError: true
507
+ }
508
+ }
509
+ }
437
510
  } catch (error) {
438
- console.error(`[Tool] listChannels 错误:`, error);
439
511
  return {
440
512
  content: [
441
513
  {
442
- type: "text",
443
- text: `❌ 获取频道列表失败: ${error.message}`
514
+ type: 'text',
515
+ text: `❌ 代码发送失败: ${error.message || String(error)}`
444
516
  }
445
517
  ],
446
518
  isError: true
447
- };
519
+ }
448
520
  }
449
521
  }
450
- );
522
+ )
451
523
 
452
- // 连接 stdio 传输
453
524
  try {
454
- const transport = new StdioServerTransport();
455
- await mcpServer.connect(transport);
456
- console.error('✅ MCP 客户端已启动');
525
+ const transport = new StdioServerTransport()
526
+ await mcpServer.connect(transport)
457
527
  } catch (error) {
458
- console.error('❌ MCP 客户端启动失败:', error.message);
459
- console.error('💡 请检查:');
460
- console.error(' 1. 服务器地址是否正确:', SERVER_URL);
461
- console.error(' 2. 服务器是否正在运行');
462
- console.error(' 3. 网络连接是否正常');
463
- process.exit(1);
528
+ process.exit(1)
464
529
  }