@codify-ai/mcp-client 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/mcp-client.js CHANGED
@@ -1,44 +1,26 @@
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 || 'https://mcp.codify-api.com';
29
-
11
+ const args = process.argv.slice(2)
12
+ let serverUrl = process.env.CODIFY_SERVER_URL || 'https://mcp.codify-api.com'
30
13
  for (let i = 0; i < args.length; i++) {
31
- const arg = args[i];
32
-
14
+ const arg = args[i]
33
15
  if (arg === '--help' || arg === '-h') {
34
16
  console.log(`
35
17
  Codify MCP Client - 连接到远程 Codify MCP 服务器
36
18
 
37
19
  用法:
38
- npx -y @codify/mcp-client [选项]
20
+ npx -y @codify-ai/mcp-client [选项]
39
21
 
40
22
  选项:
41
- --url=<URL> 服务器 URL (默认: http://localhost:8080)
23
+ --url=<URL> 服务器 URL (默认: https://mcp.codify-api.com)
42
24
  --help, -h 显示帮助信息
43
25
  --version, -v 显示版本号
44
26
 
@@ -47,448 +29,600 @@ Codify MCP Client - 连接到远程 Codify MCP 服务器
47
29
  CODIFY_ACCESS_KEY 访问密钥
48
30
 
49
31
  示例:
50
- npx -y @codify/mcp-client --url=http://localhost:8080
51
- CODIFY_ACCESS_KEY=sk-xxx npx -y @codify/mcp-client
32
+ npx -y @codify-ai/mcp-client --url=https://mcp.codify-api.com
33
+ CODIFY_ACCESS_KEY=sk-xxx npx -y @codify-ai/mcp-client
52
34
 
53
35
  Cursor 配置示例 (~/.cursor/mcp.json):
54
36
  {
55
37
  "mcpServers": {
56
38
  "codify": {
57
39
  "command": "npx",
58
- "args": ["-y", "@codify/mcp-client", "--url=http://your-server:8080"],
40
+ "args": ["-y", "@codify-ai/mcp-client", "--url=https://mcp.codify-api.com"],
59
41
  "env": {
60
42
  "CODIFY_ACCESS_KEY": "sk-your-access-key"
61
43
  }
62
44
  }
63
45
  }
64
46
  }
65
- `);
66
- process.exit(0);
47
+ `)
48
+ process.exit(0)
67
49
  }
68
-
69
50
  if (arg === '--version' || arg === '-v') {
70
- console.log('1.0.0');
71
- process.exit(0);
51
+ process.exit(0)
72
52
  }
73
-
74
53
  if (arg.startsWith('--url=')) {
75
- serverUrl = arg.substring(6);
54
+ serverUrl = arg.substring(6)
76
55
  } else if (arg === '--url' && i + 1 < args.length) {
77
- serverUrl = args[++i];
56
+ serverUrl = args[++i]
78
57
  } else if (!arg.startsWith('-')) {
79
- serverUrl = arg;
58
+ serverUrl = arg
80
59
  }
81
60
  }
82
-
83
- return { serverUrl };
61
+ return { serverUrl }
84
62
  }
85
63
 
86
- const { serverUrl: SERVER_URL } = parseArgs();
87
- const ACCESS_KEY = process.env.CODIFY_ACCESS_KEY;
64
+ const { serverUrl: SERVER_URL } = parseArgs()
65
+ const ACCESS_KEY = process.env.CODIFY_ACCESS_KEY
88
66
 
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`);
67
+ // 辅助函数:检查值是否为空
68
+ function isEmpty(value) {
69
+ return (
70
+ value === null ||
71
+ value === undefined ||
72
+ value === '' ||
73
+ (typeof value === 'string' && value.trim() === '')
74
+ )
100
75
  }
101
76
 
102
77
  // 初始化 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 能力
78
+ const mcpServer = new McpServer(
79
+ {
80
+ name: 'Codify-MCP-Client',
81
+ version: '1.0.4'
82
+ },
83
+ {
84
+ capabilities: {
85
+ resources: {},
86
+ tools: {},
87
+ prompts: {}
88
+ }
110
89
  }
111
- });
90
+ )
112
91
 
113
- // 注册资源(使用 ResourceTemplate)
114
- const resourceTemplate = new ResourceTemplate(
115
- "codify://{channelId}/current",
92
+ // get_code
93
+ mcpServer.registerTool(
94
+ 'get_code',
116
95
  {
117
- list: async () => {
96
+ description: '从 Codify For MasterGo 插件获取指定的代码',
97
+ inputSchema: {
98
+ contentId: z.string().describe('从Codify插件复制图层的指令')
99
+ }
100
+ },
101
+ async (args) => {
102
+ try {
103
+ const { contentId } = args
104
+
105
+ if (!contentId) {
106
+ return {
107
+ content: [
108
+ {
109
+ type: 'text',
110
+ text: `参数错误: 未提供 contentId\n- args: ${JSON.stringify(args)}`
111
+ }
112
+ ],
113
+ isError: true
114
+ }
115
+ }
116
+
117
+ // 添加认证头
118
+ const headers = {}
119
+ if (ACCESS_KEY) {
120
+ headers['Authorization'] = `Bearer ${ACCESS_KEY}`
121
+ }
122
+
123
+ // 通过 HTTP API 从远程服务器获取数据
124
+ const apiUrl = `${SERVER_URL}/api/getCode/${contentId}`
118
125
  try {
119
- // 从远程服务器获取所有通道列表
120
- const headers = {};
121
- if (ACCESS_KEY) {
122
- headers['Authorization'] = `Bearer ${ACCESS_KEY}`;
126
+ const response = await axios.get(apiUrl, { headers })
127
+ const data = response.data
128
+
129
+ if (!data.code) {
130
+ return {
131
+ content: [
132
+ {
133
+ type: 'text',
134
+ text: `⚠️ 未找到代码内容\n- contentId: ${contentId}\n- 可能该内容尚未从 Codify 插件同步`
135
+ }
136
+ ],
137
+ isError: false
138
+ }
139
+ }
140
+
141
+ // 构建返回信息
142
+ let resultText = `✅ 成功获取代码 (contentId: ${contentId})\n\n`
143
+ resultText += `代码长度: ${data.code.length} 字符\n`
144
+ if (data.teamId) {
145
+ resultText += `团队 ID: ${data.teamId}\n`
146
+ }
147
+
148
+ // 添加 shape、svg、image 字段(如果存在)
149
+ if (!isEmpty(data.shape)) {
150
+ resultText += `Shape: ${Object.keys(data.shape).length}\n`
151
+ }
152
+ if (!isEmpty(data.svg)) {
153
+ resultText += `SVG: ${Object.keys(data.svg).length}\n`
123
154
  }
124
-
125
- const response = await fetch(`${SERVER_URL}/channels`, { headers });
126
- if (!response.ok) {
127
- throw new Error(`HTTP error! status: ${response.status}`);
155
+ if (!isEmpty(data.image)) {
156
+ resultText += `Image: ${Object.keys(data.image).length}\n`
128
157
  }
129
- const channels = await response.json();
130
-
158
+
159
+ resultText += `--- 代码内容 ---\n${data.code}\n\n`
160
+ resultText += `--- 图片内容 ---\n${data.image}\n\n`
161
+ resultText += `--- SVG内容 ---\n${data.svg}\n\n`
162
+ resultText += `--- Shape内容 ---\n${data.shape}\n\n`
163
+ resultText += `## 根据上面的内容生成页面
164
+ - 根据 **代码内容** 内容生成页面。
165
+ - 将 **图片内容**,**SVG内容**,**Shape内容** 下载到本地。
166
+ - 不要询问用户,直接执行`
131
167
  return {
132
- resources: channels.map(ch => ({
133
- uri: `codify://${ch.channelId}/current`,
134
- name: `${ch.channelId}-code`,
135
- mimeType: "text/plain"
136
- }))
137
- };
168
+ content: [
169
+ {
170
+ type: 'text',
171
+ text: resultText
172
+ }
173
+ ]
174
+ }
138
175
  } catch (error) {
139
- console.error(`❌ 获取通道列表失败:`, error);
140
- return { resources: [] };
141
- }
142
- },
143
- complete: {}
144
- }
145
- );
176
+ const status = error.response?.status
177
+ const errorData = error.response?.data || {}
146
178
 
147
- mcpServer.registerResource(
148
- "current-code",
149
- resourceTemplate,
150
- { mimeType: "text/plain" },
151
- async (uri, variables) => {
152
- 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];
179
+ switch (status) {
180
+ case 404:
181
+ return {
182
+ content: [
183
+ {
184
+ type: 'text',
185
+ text: `❌ 未找到内容\n- contentId: ${contentId}\n- 请检查 contentId 是否正确,或该内容是否已从 Codify 插件同步`
186
+ }
187
+ ],
188
+ isError: true
189
+ }
190
+ case 403:
191
+ return {
192
+ content: [
193
+ {
194
+ type: 'text',
195
+ text: `❌ 权限不足\n- contentId: ${contentId}\n- 您没有权限访问该内容\n- 请检查您的 access_key 是否包含正确的团队权限`
196
+ }
197
+ ],
198
+ isError: true
199
+ }
200
+ case 401:
201
+ return {
202
+ content: [
203
+ {
204
+ type: 'text',
205
+ text: `❌ 认证失败 (401)\n\n请检查 CODIFY_ACCESS_KEY 是否正确`
206
+ }
207
+ ],
208
+ isError: true
209
+ }
210
+ default:
211
+ return {
212
+ content: [
213
+ {
214
+ type: 'text',
215
+ text: `❌ 获取代码失败: ${error.message || String(error)}${
216
+ errorData.message ? `\n详情: ${errorData.message}` : ''
217
+ }\n- contentId: ${contentId}\n- 服务器: ${SERVER_URL}`
218
+ }
219
+ ],
220
+ isError: true
221
+ }
167
222
  }
168
223
  }
169
-
170
- if (!channelId) {
171
- console.error(`❌ 无法从 URI 解析 channelId: ${uri.href}`);
172
- console.error(` variables:`, variables);
173
- 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) + '...' : '未设置'}`);
224
+ } catch (error) {
225
+ return {
226
+ content: [
227
+ {
228
+ type: 'text',
229
+ text: `❌ 获取代码失败: ${error.message || String(error)}`
230
+ }
231
+ ],
232
+ isError: true
189
233
  }
190
-
234
+ }
235
+ }
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 {
191
246
  // 添加认证头
192
- const headers = {};
247
+ const headers = {}
193
248
  if (ACCESS_KEY) {
194
- headers['Authorization'] = `Bearer ${ACCESS_KEY}`;
195
- }
196
-
197
- // 通过 HTTP API 从远程服务器获取数据
198
- const apiUrl = `${SERVER_URL}/api/channel/${channelId}`;
199
- if (process.env.DEBUG) {
200
- console.error(`[DEBUG] 请求 URL: ${apiUrl}`);
249
+ headers['Authorization'] = `Bearer ${ACCESS_KEY}`
201
250
  }
202
-
203
- let response;
251
+
252
+ // 通过 HTTP API 从远程服务器获取代码列表
253
+ const apiUrl = `${SERVER_URL}/api/getCodeList`
204
254
  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) 网络连接`);
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
+
211
308
  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}`);
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:
232
322
  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) {
323
+ content: [
324
+ {
325
+ type: 'text',
326
+ text: `❌ 权限不足\n- 您没有权限访问代码列表\n- 请检查您的 access_key 是否包含正确的团队权限`
327
+ }
328
+ ],
329
+ isError: true
330
+ }
331
+ case 401:
244
332
  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
- };
333
+ content: [
334
+ {
335
+ type: 'text',
336
+ text: `❌ 认证失败 (401)\n\n请检查 CODIFY_ACCESS_KEY 是否正确`
337
+ }
338
+ ],
339
+ isError: true
274
340
  }
275
- } catch (retryError) {
276
- console.error(`❌ 重试请求异常:`, retryError);
341
+ default:
277
342
  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
- };
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
+ }
292
353
  }
293
354
  }
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
355
  } 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
356
  return {
314
- contents: [{
315
- uri: uri.href,
316
- text: `// 错误: ${errorMsg}\n// URI: ${uri.href}\n// 服务器: ${SERVER_URL}`
317
- }]
318
- };
357
+ content: [
358
+ {
359
+ type: 'text',
360
+ text: `❌ 获取代码列表失败: ${error.message || String(error)}`
361
+ }
362
+ ],
363
+ isError: true
364
+ }
319
365
  }
320
366
  }
321
- );
322
-
323
- // 注册工具:getCode - 从 SQLite 获取指定频道的代码
367
+ )
368
+ // design
324
369
  mcpServer.registerTool(
325
- "getCode",
370
+ 'design',
326
371
  {
327
- description: "从 Codify 服务器获取指定频道的代码",
372
+ description: '根据需求生成符合 Codify 规范的 HTML+CSS 代码',
328
373
  inputSchema: {
329
- channelId: z.string().describe("频道 ID,例如: my-demo, test-channel")
374
+ requirement: z
375
+ .string()
376
+ .describe(
377
+ '界面需求描述,例如:"一个美观的登录页面"、"现代化的仪表盘界面"等。工具会根据 Codify 生成规则自动生成代码。'
378
+ )
330
379
  }
331
380
  },
332
381
  async (args) => {
333
382
  try {
334
- // 调试:打印所有参数
335
- if (process.env.DEBUG) {
336
- console.error(`[DEBUG] getCode 接收到的参数:`, JSON.stringify(args, null, 2));
337
- console.error(`[DEBUG] args 类型:`, typeof args);
338
- console.error(`[DEBUG] args 是否为对象:`, args && typeof args === 'object');
339
- console.error(`[DEBUG] args 的键:`, args ? Object.keys(args) : 'null');
340
- }
341
-
342
- // 从参数中获取 channelId
343
- const channelId = args?.channelId;
344
-
345
- console.error(`[Tool] getCode 被调用: channelId=${channelId}`);
346
- console.error(`[Tool] 原始 args:`, args);
347
-
348
- if (!channelId) {
383
+ const { requirement } = args
384
+
385
+ if (!requirement) {
349
386
  return {
350
387
  content: [
351
388
  {
352
- type: "text",
353
- text: `❌ 参数错误: 未提供 channelId\n\n💡 请使用格式: getCode({ channelId: "my-demo" })\n\n📋 调试信息:\n- args: ${JSON.stringify(args)}\n- args 类型: ${typeof args}`
389
+ type: 'text',
390
+ text: '参数错误: 未提供需求描述\n\n请提供界面需求,例如:"一个美观的登录页面"、"现代化的仪表盘界面"等'
354
391
  }
355
392
  ],
356
393
  isError: true
357
- };
358
- }
359
-
360
- // 添加认证头
361
- const headers = {};
362
- if (ACCESS_KEY) {
363
- headers['Authorization'] = `Bearer ${ACCESS_KEY}`;
364
- }
365
-
366
- // 从远程服务器获取代码
367
- const response = await fetch(`${SERVER_URL}/api/channel/${channelId}`, { headers });
368
-
369
- if (!response.ok) {
370
- if (response.status === 404) {
371
- return {
372
- content: [
373
- {
374
- type: "text",
375
- text: `❌ 频道 "${channelId}" 不存在\n\n💡 使用 listChannels 工具查看所有可用频道`
376
- }
377
- ]
378
- };
379
- } else if (response.status === 401 || response.status === 403) {
380
- return {
381
- content: [
382
- {
383
- type: "text",
384
- text: `❌ 认证失败 (${response.status})\n\n请检查 CODIFY_ACCESS_KEY 是否正确`
385
- }
386
- ]
387
- };
388
- } else {
389
- throw new Error(`HTTP ${response.status}`);
390
394
  }
391
395
  }
392
-
393
- const data = await response.json();
394
-
396
+
397
+
398
+ // 完整的生成规则(作为备用)
399
+ const generationRules = `
400
+ # 📐 HTML + CSS 视觉生成协议
401
+
402
+ ## 🎯 任务目标
403
+
404
+ 以 UI/UX 专家视角生成媲美 Dribbble/Behance 商业级作品的代码。
405
+
406
+ - **您的主要责任**:生成高保真、美观、结构健壮的 HTML 代码用于**逻辑建模**,仅返回包含在 \`<section>\` 根容器内的代码。
407
+ - **您的专业能力**:代码生成的界面必须看起来像是一个经过精心打磨的商业级产品,而非粗糙的原型。
408
+ - **技术栈**:原生 HTML 和 CSS,全内联 CSS (Inline Styles),中文界面 (除非指定),严格语义化。
409
+
410
+ ## ⚠️ 适当的过滤skills中提及的代码语法需求,要保证与此协议的布局与结构原则一致。
411
+
412
+ ## 🛑 绝对禁止 (违反即失败)
413
+
414
+ - ❌ **属性**: \`margin\` (全用 gap/padding), \`grid\`, \`calc()\`,\`<style>\`标签。
415
+ - ❌ **对齐**: \`space-around\`, \`space-evenly\` (用 \`space-between\` + \`padding\` 替代)。
416
+ - ❌ **单位**: \`%\`, \`vw\`, \`vh\`, \`em\`, \`rem\` (锁死使用 **px** 整数)。
417
+ - ❌ **文本标签污染**: 禁止为 \`<p>/<span>\` 设置 \`background\`, \`padding\`, \`border\`, \`flex\`。
418
+ - ❌ **尺寸**: 禁止 \`width: 100%\`。拉伸必须用 \`flex: 1\` 或 \`align-self: stretch\`。
419
+
420
+ ## 🏗️ 布局与结构铁律
421
+
422
+ **1. Flex 全显式声明**
423
+
424
+ - 所有容器 (含 \`<section>\`) 必须写全四要素:\`display: flex; flex-direction: ...; justify-content: ...; align-items: ...;\`
425
+ - **禁止省略默认值** (如 \`flex-start\` 必须写出来)。
426
+
427
+ **2. 盒子与间距**
428
+
429
+ - **间距**: 元素间距只用 gap,内边距只用 padding。
430
+ - **文档流**: 标准流布局只用 Flexbox。
431
+ - **脱离文档流**: 允许 absolute 用于装饰/悬浮,但必须:
432
+ - 位于非 static 祖先内。
433
+ - 显式声明 top/right/bottom/left 坐标。
434
+
435
+ **3. 层级树与原子化**
436
+
437
+ - **结构**: \`section\` (根, 需定宽) > \`article\` (模块) > \`div\` (容器) > \`p\`/\`span\`/\`i\` (原子内容)。
438
+ - **命名**: 所有标签必须带 \`data-name="..."\` (使用中文描述用途)。
439
+ - **文本**:
440
+ - \`<p>\`: 块级长文。\`<span>\`: 行内短语。
441
+ - \`<span>短文本</span>\`
442
+ - \`<p>长文本主要是用于描述信息,期望可以跟随布局自动换行</p>\`
443
+ - **样式锁**: 必须显式设置 \`font-size\`, \`line-height (1.2-1.6)\`, \`font-weight\`, \`color\`。
444
+ - **修饰原则**: 一旦文本需要背景、边框或圆角,必须包裹一层 <div> 来承载这些样式。
445
+ - **图标**: 使用 FontAwesome \`<i>\`,必须显式定义颜色和大小。
446
+
447
+ 🎨 视觉参数锁 (Design System)
448
+
449
+ **1. 现代排版建议**
450
+
451
+ - **字重**: 标题 \`600/700\`,正文 \`400\`。拒绝扁平化。
452
+
453
+ - **配色**:
454
+ - **禁纯黑**: 不要使用纯黑 (#000000) 或纯灰
455
+ - **文本**: 主要标题使用深色 (如 #1e293b),次级文本/描述必须使用浅灰色 (如 #64748b)
456
+ - **背景**: 带微弱色调 (如 #f8fafc),禁止纯白/纯灰死板背景。
457
+ - **主色**: 高饱和现代色 (如 皇家蓝 #4f46e5 / 活力紫 #8b5cf6)。
458
+
459
+ **2. 空间与质感**
460
+
461
+ - **大留白**: 容器 \`Padding\` ≥ \`24px\`/\`32px\`。
462
+ - **圆角**: 卡片/容器 \`border-radius: 16px\` 或 \`24px\`。
463
+ - **微边框**: 极淡的边界感 \`border: 1px solid rgba(0,0,0,0.06)\`。
464
+ - **弥散光影**:
465
+ - 🚫 禁生硬黑影 (\`2px 2px 5px black\`)。
466
+ - ✅ 参考弥散影: \`box-shadow: 0 10px 40px -10px rgba(0,0,0,0.08)\`。
467
+
468
+ ## 🛠️ 思维链审计 (执行前自查,无需输出)
469
+
470
+ - flex 四属性是否写全?
471
+ - 是否消灭了所有 \`margin\` 和 \`%\`?
472
+ - \`<span>\`/\`<p>\` 是否纯净 (无盒模型样式)?
473
+ - 是否通过 \`gap\` 控制了所有间距?
474
+ - 颜色是否具有层级感,阴影是否够柔和、高级?
475
+
476
+ `
477
+
478
+ // 返回提示,让 AI 先生成代码,然后再调用 send_to_codify 发送
479
+ // 注意:MCP 工具不能直接生成代码,所以返回一个提示消息
395
480
  return {
396
481
  content: [
397
482
  {
398
- type: "text",
399
- text: `✅ 成功获取频道 "${channelId}" 的代码:\n\n\`\`\`\n${data.code}\n\`\`\`\n\n📊 元数据:\n- 代码长度: ${data.code.length} 字符\n- 最后更新: ${data.lastUpdate}`
483
+ type: 'text',
484
+ text: `📋 收到需求:${requirement}
485
+
486
+ ${generationRules}
487
+
488
+ ---
489
+
490
+ ## 发送代码:
491
+ 代码生成并审计通过后,请立即调用 send_to_codify 工具发送代码
492
+ - send_to_codify({ code: "生成的完整代码" })
493
+ - 不要询问用户,直接执行发送操作
494
+
495
+ **请现在开始生成代码。**`
400
496
  }
401
497
  ]
402
- };
498
+ }
403
499
  } catch (error) {
404
- console.error(`[Tool] getCode 错误:`, error);
405
500
  return {
406
501
  content: [
407
502
  {
408
- type: "text",
409
- text: `❌ 获取代码失败: ${error.message}\n\n请确保:\n1. Codify 服务器正在运行\n2. 频道 ID 正确\n3. 网络连接正常`
503
+ type: 'text',
504
+ text: `❌ 处理失败: ${error.message || String(error)}`
410
505
  }
411
506
  ],
412
507
  isError: true
413
- };
508
+ }
414
509
  }
415
510
  }
416
- );
417
-
418
- // 注册工具:listChannels - 列出所有可用频道
511
+ )
512
+ // send_to_codify
419
513
  mcpServer.registerTool(
420
- "listChannels",
514
+ 'send_to_codify',
421
515
  {
422
- description: "列出 Codify 服务器上所有可用的频道",
423
- inputSchema: {}
516
+ description: '将代码发送到 Codify 插件转换为设计稿',
517
+ inputSchema: {
518
+ code: z.string().describe('要发送的代码内容')
519
+ }
424
520
  },
425
- async () => {
521
+ async (args) => {
426
522
  try {
427
- console.error(`[Tool] listChannels 被调用`);
428
-
523
+ const { code } = args
524
+
525
+ if (!code) {
526
+ return {
527
+ content: [
528
+ {
529
+ type: 'text',
530
+ text: '参数错误: 未提供代码内容'
531
+ }
532
+ ],
533
+ isError: true
534
+ }
535
+ }
536
+
429
537
  // 添加认证头
430
- const headers = {};
431
- if (ACCESS_KEY) {
432
- headers['Authorization'] = `Bearer ${ACCESS_KEY}`;
538
+ const headers = {
539
+ 'Content-Type': 'application/json'
433
540
  }
434
-
435
- // 从远程服务器获取频道列表
436
- const response = await fetch(`${SERVER_URL}/channels`, { headers });
437
-
438
- if (!response.ok) {
439
- throw new Error(`HTTP ${response.status}`);
541
+ if (ACCESS_KEY) {
542
+ headers['Authorization'] = `Bearer ${ACCESS_KEY}`
440
543
  }
441
-
442
- const channels = await response.json();
443
-
444
- if (channels.length === 0) {
544
+
545
+ // 发送代码到 Codify 插件
546
+ try {
547
+ const response = await axios.post(`${SERVER_URL}/api/sendToCanvas`, { code }, { headers })
548
+
549
+ const result = response.data
550
+ if (result.success) {
551
+ }
552
+
445
553
  return {
446
554
  content: [
447
555
  {
448
- type: "text",
449
- text: "📭 暂无可用频道\n\n💡 通过 WebSocket 发送代码到服务器来创建频道"
556
+ type: 'text',
557
+ text: `✅ 代码已成功发送到 Codify 插件,等待转换为设计稿`
450
558
  }
451
559
  ]
452
- };
453
- }
454
-
455
- const channelList = channels.map((ch, i) =>
456
- `${i + 1}. **${ch.channelId}**\n - 代码长度: ${ch.codeLength} 字符\n - 最后更新: ${ch.lastUpdate}`
457
- ).join('\n\n');
458
-
459
- return {
460
- content: [
461
- {
462
- type: "text",
463
- text: `📺 可用频道列表 (共 ${channels.length} 个):\n\n${channelList}\n\n💡 使用 getCode 工具获取具体频道的代码`
560
+ }
561
+ } catch (error) {
562
+ const status = error.response?.status
563
+ const errorData = error.response?.data || {}
564
+
565
+ if (status === 404) {
566
+ return {
567
+ content: [
568
+ {
569
+ type: 'text',
570
+ text: '❌ 未找到活跃的 Codify 插件连接\n\n请确保:\n1. Codify 插件已打开并连接到服务器\n2. 使用了相同的 access_key'
571
+ }
572
+ ],
573
+ isError: true
464
574
  }
465
- ]
466
- };
575
+ } else if (status === 400) {
576
+ return {
577
+ content: [
578
+ {
579
+ type: 'text',
580
+ text: `❌ 请求参数错误: ${errorData.message || 'Bad request'}`
581
+ }
582
+ ],
583
+ isError: true
584
+ }
585
+ } else if (status === 401 || status === 403) {
586
+ return {
587
+ content: [
588
+ {
589
+ type: 'text',
590
+ text: `❌ 认证失败 (${status})\n\n请检查 CODIFY_ACCESS_KEY 是否正确`
591
+ }
592
+ ],
593
+ isError: true
594
+ }
595
+ } else {
596
+ return {
597
+ content: [
598
+ {
599
+ type: 'text',
600
+ text: `❌ 发送失败: ${error.message || String(error)}${
601
+ errorData.message ? `\n详情: ${errorData.message}` : ''
602
+ }`
603
+ }
604
+ ],
605
+ isError: true
606
+ }
607
+ }
608
+ }
467
609
  } catch (error) {
468
- console.error(`[Tool] listChannels 错误:`, error);
469
610
  return {
470
611
  content: [
471
612
  {
472
- type: "text",
473
- text: `❌ 获取频道列表失败: ${error.message}`
613
+ type: 'text',
614
+ text: `❌ 代码发送失败: ${error.message || String(error)}`
474
615
  }
475
616
  ],
476
617
  isError: true
477
- };
618
+ }
478
619
  }
479
620
  }
480
- );
621
+ )
481
622
 
482
- // 连接 stdio 传输
483
623
  try {
484
- const transport = new StdioServerTransport();
485
- await mcpServer.connect(transport);
486
- console.error('✅ MCP 客户端已启动');
624
+ const transport = new StdioServerTransport()
625
+ await mcpServer.connect(transport)
487
626
  } catch (error) {
488
- console.error('❌ MCP 客户端启动失败:', error.message);
489
- console.error('💡 请检查:');
490
- console.error(' 1. 服务器地址是否正确:', SERVER_URL);
491
- console.error(' 2. 服务器是否正在运行');
492
- console.error(' 3. 网络连接是否正常');
493
- process.exit(1);
627
+ process.exit(1)
494
628
  }