@codify-ai/mcp-client 1.0.0 → 1.0.2
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 +185 -55
- package/package.json +12 -1
package/mcp-client.js
CHANGED
|
@@ -6,25 +6,26 @@
|
|
|
6
6
|
*
|
|
7
7
|
* 使用方法:
|
|
8
8
|
* 在 Cursor 配置 (~/.cursor/mcp.json) 中:
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
20
|
*/
|
|
21
21
|
|
|
22
22
|
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
23
23
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
24
|
+
import { z } from 'zod';
|
|
24
25
|
|
|
25
26
|
function parseArgs() {
|
|
26
27
|
const args = process.argv.slice(2);
|
|
27
|
-
let serverUrl = process.env.CODIFY_SERVER_URL || '
|
|
28
|
+
let serverUrl = process.env.CODIFY_SERVER_URL || 'https://mcp.codify-api.com';
|
|
28
29
|
|
|
29
30
|
for (let i = 0; i < args.length; i++) {
|
|
30
31
|
const arg = args[i];
|
|
@@ -150,7 +151,42 @@ mcpServer.registerResource(
|
|
|
150
151
|
async (uri, variables) => {
|
|
151
152
|
try {
|
|
152
153
|
// 从 variables 中获取 channelId(由 SDK 解析)
|
|
153
|
-
|
|
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);
|
|
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) + '...' : '未设置'}`);
|
|
189
|
+
}
|
|
154
190
|
|
|
155
191
|
// 添加认证头
|
|
156
192
|
const headers = {};
|
|
@@ -159,45 +195,109 @@ mcpServer.registerResource(
|
|
|
159
195
|
}
|
|
160
196
|
|
|
161
197
|
// 通过 HTTP API 从远程服务器获取数据
|
|
162
|
-
const
|
|
198
|
+
const apiUrl = `${SERVER_URL}/api/channel/${channelId}`;
|
|
199
|
+
if (process.env.DEBUG) {
|
|
200
|
+
console.error(`[DEBUG] 请求 URL: ${apiUrl}`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
let response;
|
|
204
|
+
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) 网络连接`);
|
|
211
|
+
return {
|
|
212
|
+
contents: [{
|
|
213
|
+
uri: uri.href,
|
|
214
|
+
text: `// 网络错误: ${errorMsg}\n// 服务器: ${SERVER_URL}\n// 请检查服务器地址和网络连接`
|
|
215
|
+
}]
|
|
216
|
+
};
|
|
217
|
+
}
|
|
163
218
|
|
|
164
219
|
if (!response.ok) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
if (!channelsResponse.ok) {
|
|
169
|
-
return {
|
|
170
|
-
contents: [{
|
|
171
|
-
uri: uri.href,
|
|
172
|
-
text: `// 认证失败或服务器错误 (${channelsResponse.status})`
|
|
173
|
-
}]
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const channels = await channelsResponse.json();
|
|
178
|
-
const channel = channels.find(ch => ch.channelId === channelId);
|
|
220
|
+
const errorText = await response.text().catch(() => '无法读取错误信息');
|
|
221
|
+
console.error(`❌ API 请求失败: ${response.status} ${response.statusText}`);
|
|
222
|
+
console.error(` 错误详情: ${errorText}`);
|
|
179
223
|
|
|
180
|
-
|
|
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}`);
|
|
232
|
+
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) {
|
|
244
|
+
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
|
+
};
|
|
274
|
+
}
|
|
275
|
+
} catch (retryError) {
|
|
276
|
+
console.error(`❌ 重试请求异常:`, retryError);
|
|
277
|
+
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);
|
|
181
286
|
return {
|
|
182
287
|
contents: [{
|
|
183
288
|
uri: uri.href,
|
|
184
|
-
text: `//
|
|
289
|
+
text: `// 获取频道列表失败: ${channelsError instanceof Error ? channelsError.message : String(channelsError)}\n// 原始错误: ${response.status} ${errorText}`
|
|
185
290
|
}]
|
|
186
291
|
};
|
|
187
292
|
}
|
|
188
|
-
|
|
189
|
-
// 需要从完整数据中获取代码
|
|
190
|
-
// 这里返回一个提示,因为我们没有直接的 API
|
|
191
|
-
return {
|
|
192
|
-
contents: [{
|
|
193
|
-
uri: uri.href,
|
|
194
|
-
text: `// 通道 ${channelId} 存在\n// 代码长度: ${channel.codeLength} 字符\n// 最后更新: ${channel.lastUpdate}`
|
|
195
|
-
}]
|
|
196
|
-
};
|
|
197
293
|
}
|
|
198
294
|
|
|
199
295
|
const data = await response.json();
|
|
200
296
|
|
|
297
|
+
if (process.env.DEBUG) {
|
|
298
|
+
console.error(`[DEBUG] 成功获取代码,长度: ${data.code?.length || 0} 字符`);
|
|
299
|
+
}
|
|
300
|
+
|
|
201
301
|
return {
|
|
202
302
|
contents: [{
|
|
203
303
|
uri: uri.href,
|
|
@@ -205,11 +305,15 @@ mcpServer.registerResource(
|
|
|
205
305
|
}]
|
|
206
306
|
};
|
|
207
307
|
} catch (error) {
|
|
208
|
-
|
|
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
|
+
}
|
|
209
313
|
return {
|
|
210
314
|
contents: [{
|
|
211
315
|
uri: uri.href,
|
|
212
|
-
text: `// 错误: ${
|
|
316
|
+
text: `// 错误: ${errorMsg}\n// URI: ${uri.href}\n// 服务器: ${SERVER_URL}`
|
|
213
317
|
}]
|
|
214
318
|
};
|
|
215
319
|
}
|
|
@@ -222,19 +326,36 @@ mcpServer.registerTool(
|
|
|
222
326
|
{
|
|
223
327
|
description: "从 Codify 服务器获取指定频道的代码",
|
|
224
328
|
inputSchema: {
|
|
225
|
-
|
|
226
|
-
properties: {
|
|
227
|
-
channelId: {
|
|
228
|
-
type: "string",
|
|
229
|
-
description: "频道 ID,例如: my-demo, test-channel"
|
|
230
|
-
}
|
|
231
|
-
},
|
|
232
|
-
required: ["channelId"]
|
|
329
|
+
channelId: z.string().describe("频道 ID,例如: my-demo, test-channel")
|
|
233
330
|
}
|
|
234
331
|
},
|
|
235
|
-
async (
|
|
332
|
+
async (args) => {
|
|
236
333
|
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
|
+
|
|
237
345
|
console.error(`[Tool] getCode 被调用: channelId=${channelId}`);
|
|
346
|
+
console.error(`[Tool] 原始 args:`, args);
|
|
347
|
+
|
|
348
|
+
if (!channelId) {
|
|
349
|
+
return {
|
|
350
|
+
content: [
|
|
351
|
+
{
|
|
352
|
+
type: "text",
|
|
353
|
+
text: `❌ 参数错误: 未提供 channelId\n\n💡 请使用格式: getCode({ channelId: "my-demo" })\n\n📋 调试信息:\n- args: ${JSON.stringify(args)}\n- args 类型: ${typeof args}`
|
|
354
|
+
}
|
|
355
|
+
],
|
|
356
|
+
isError: true
|
|
357
|
+
};
|
|
358
|
+
}
|
|
238
359
|
|
|
239
360
|
// 添加认证头
|
|
240
361
|
const headers = {};
|
|
@@ -298,7 +419,8 @@ mcpServer.registerTool(
|
|
|
298
419
|
mcpServer.registerTool(
|
|
299
420
|
"listChannels",
|
|
300
421
|
{
|
|
301
|
-
description: "列出 Codify 服务器上所有可用的频道"
|
|
422
|
+
description: "列出 Codify 服务器上所有可用的频道",
|
|
423
|
+
inputSchema: {}
|
|
302
424
|
},
|
|
303
425
|
async () => {
|
|
304
426
|
try {
|
|
@@ -358,7 +480,15 @@ mcpServer.registerTool(
|
|
|
358
480
|
);
|
|
359
481
|
|
|
360
482
|
// 连接 stdio 传输
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
console.error('✅ MCP 客户端已启动');
|
|
483
|
+
try {
|
|
484
|
+
const transport = new StdioServerTransport();
|
|
485
|
+
await mcpServer.connect(transport);
|
|
486
|
+
console.error('✅ MCP 客户端已启动');
|
|
487
|
+
} 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);
|
|
494
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codify-ai/mcp-client",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Codify MCP 客户端 - 连接到远程 Codify MCP 服务器,供 Cursor 等 IDE 使用",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -15,6 +15,17 @@
|
|
|
15
15
|
"node": ">=18.0.0",
|
|
16
16
|
"npm": ">=8.0.0"
|
|
17
17
|
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc",
|
|
20
|
+
"start": "node dist/index.js",
|
|
21
|
+
"dev": "tsx watch src/index.ts",
|
|
22
|
+
"clean": "rm -rf dist",
|
|
23
|
+
"typecheck": "tsc --noEmit",
|
|
24
|
+
"prepare": "npm run build",
|
|
25
|
+
"mcp-client": "node mcp-client.js",
|
|
26
|
+
"publish:client": "bash script/publish.sh",
|
|
27
|
+
"build:deploy": "bash script/build-deploy.sh"
|
|
28
|
+
},
|
|
18
29
|
"keywords": [
|
|
19
30
|
"mcp",
|
|
20
31
|
"model-context-protocol",
|