@mumulinya167/cc-web 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -0
- package/bin/ccm.js +680 -0
- package/bin/server.js +4428 -0
- package/bin/setup.js +148 -0
- package/configs/config-template.toml +54 -0
- package/mcp-feishu/.env.example +2 -0
- package/mcp-feishu/package-lock.json +1194 -0
- package/mcp-feishu/package.json +23 -0
- package/mcp-feishu/src/cli.ts +239 -0
- package/mcp-feishu/src/feishu-client.ts +209 -0
- package/mcp-feishu/src/index.ts +55 -0
- package/mcp-feishu/src/tools.ts +222 -0
- package/mcp-feishu/tsconfig.json +18 -0
- package/package.json +27 -0
- package/public/index.html +6604 -0
- package/templates/CLAUDE-backend.md +105 -0
- package/templates/CLAUDE-frontend.md +61 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import {
|
|
4
|
+
FeishuClient,
|
|
5
|
+
parseMessageContent,
|
|
6
|
+
formatTimestamp,
|
|
7
|
+
} from "./feishu-client.js";
|
|
8
|
+
|
|
9
|
+
export function registerTools(server: McpServer, client: FeishuClient) {
|
|
10
|
+
// Tool 1: 列出群聊
|
|
11
|
+
server.tool(
|
|
12
|
+
"list_chats",
|
|
13
|
+
"列出机器人所在的飞书群聊。返回群聊名称、chat_id、成员数量等信息。",
|
|
14
|
+
{
|
|
15
|
+
page_size: z
|
|
16
|
+
.number()
|
|
17
|
+
.optional()
|
|
18
|
+
.describe("每页返回数量,默认20,最大100"),
|
|
19
|
+
page_token: z.string().optional().describe("分页标记,首次请求不填"),
|
|
20
|
+
},
|
|
21
|
+
async (params) => {
|
|
22
|
+
const data = await client.listChats(
|
|
23
|
+
params.page_size || 20,
|
|
24
|
+
params.page_token
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const items = data.items || [];
|
|
28
|
+
const lines = items.map(
|
|
29
|
+
(chat: any, i: number) =>
|
|
30
|
+
`${i + 1}. ${chat.name || "未命名群聊"} (chat_id: ${chat.chat_id}, 成员数: ${chat.user_count || "?"}, 描述: ${chat.description || "无"})`
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const result = [
|
|
34
|
+
`找到 ${items.length} 个群聊(共 ${data.page_token ? "更多" : items.length} 个):`,
|
|
35
|
+
"",
|
|
36
|
+
...lines,
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
if (data.page_token) {
|
|
40
|
+
result.push("", `下一页标记: ${data.page_token}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return { content: [{ type: "text" as const, text: result.join("\n") }] };
|
|
44
|
+
}
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// Tool 2: 获取群聊历史消息
|
|
48
|
+
server.tool(
|
|
49
|
+
"get_chat_history",
|
|
50
|
+
"获取指定飞书群聊的历史消息。需要提供chat_id(通过list_chats获取)。返回消息列表,包含发送者、时间、内容。注意:飞书的start_time和end_time是Unix时间戳(秒级字符串),日期转换示例:2025-05-28 00:00:00 UTC+8 → '1748361600'",
|
|
51
|
+
{
|
|
52
|
+
chat_id: z.string().describe("群聊ID,通过 list_chats 获取"),
|
|
53
|
+
start_time: z
|
|
54
|
+
.string()
|
|
55
|
+
.optional()
|
|
56
|
+
.describe(
|
|
57
|
+
"起始时间,Unix时间戳(秒级字符串),如 '1748361600' 表示 2025-05-28"
|
|
58
|
+
),
|
|
59
|
+
end_time: z
|
|
60
|
+
.string()
|
|
61
|
+
.optional()
|
|
62
|
+
.describe("结束时间,Unix时间戳(秒级字符串)"),
|
|
63
|
+
page_size: z
|
|
64
|
+
.number()
|
|
65
|
+
.optional()
|
|
66
|
+
.describe("返回消息数量,默认20,最大50"),
|
|
67
|
+
page_token: z.string().optional().describe("分页标记"),
|
|
68
|
+
},
|
|
69
|
+
async (params) => {
|
|
70
|
+
const [history, chatInfo] = await Promise.all([
|
|
71
|
+
client.getChatHistory(
|
|
72
|
+
params.chat_id,
|
|
73
|
+
params.start_time,
|
|
74
|
+
params.end_time,
|
|
75
|
+
params.page_size || 20,
|
|
76
|
+
params.page_token
|
|
77
|
+
),
|
|
78
|
+
client.getChatInfo(params.chat_id).catch(() => null),
|
|
79
|
+
]);
|
|
80
|
+
|
|
81
|
+
const chatName = chatInfo?.name || params.chat_id;
|
|
82
|
+
const items = history.items || [];
|
|
83
|
+
|
|
84
|
+
const lines = items.map((msg: any) => {
|
|
85
|
+
const sender = msg.sender?.id || "未知";
|
|
86
|
+
const time = formatTimestamp(msg.create_time);
|
|
87
|
+
const content = parseMessageContent(msg.msg_type, msg.body?.content || "{}");
|
|
88
|
+
return `[${time}] ${sender}: ${content}`;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const result = [
|
|
92
|
+
`群聊「${chatName}」的消息记录(共 ${items.length} 条):`,
|
|
93
|
+
"",
|
|
94
|
+
...lines,
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
if (history.page_token) {
|
|
98
|
+
result.push("", `下一页标记: ${history.page_token}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return { content: [{ type: "text" as const, text: result.join("\n") }] };
|
|
102
|
+
}
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
// Tool 3: 搜索消息(本地过滤)
|
|
106
|
+
server.tool(
|
|
107
|
+
"search_messages",
|
|
108
|
+
"在飞书群聊中搜索包含关键词的消息。此工具通过拉取最近消息并在本地过滤实现,搜索范围限制在最近50条消息内。建议先用list_chats获取chat_id,再指定chat_id搜索以提高效率。",
|
|
109
|
+
{
|
|
110
|
+
query: z.string().describe("搜索关键词"),
|
|
111
|
+
chat_id: z
|
|
112
|
+
.string()
|
|
113
|
+
.optional()
|
|
114
|
+
.describe("限定在某个群聊中搜索,不填则搜索所有群聊"),
|
|
115
|
+
start_time: z.string().optional().describe("起始时间,Unix时间戳(秒级字符串)"),
|
|
116
|
+
end_time: z.string().optional().describe("结束时间,Unix时间戳(秒级字符串)"),
|
|
117
|
+
},
|
|
118
|
+
async (params) => {
|
|
119
|
+
const chatIds: string[] = [];
|
|
120
|
+
|
|
121
|
+
if (params.chat_id) {
|
|
122
|
+
chatIds.push(params.chat_id);
|
|
123
|
+
} else {
|
|
124
|
+
// 搜索所有群聊
|
|
125
|
+
let pageToken: string | undefined;
|
|
126
|
+
do {
|
|
127
|
+
const chats = await client.listChats(100, pageToken);
|
|
128
|
+
for (const chat of chats.items || []) {
|
|
129
|
+
chatIds.push(chat.chat_id);
|
|
130
|
+
}
|
|
131
|
+
pageToken = chats.page_token;
|
|
132
|
+
} while (pageToken);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const matches: string[] = [];
|
|
136
|
+
const query = params.query.toLowerCase();
|
|
137
|
+
|
|
138
|
+
for (const chatId of chatIds) {
|
|
139
|
+
try {
|
|
140
|
+
const history = await client.getChatHistory(
|
|
141
|
+
chatId,
|
|
142
|
+
params.start_time,
|
|
143
|
+
params.end_time,
|
|
144
|
+
50
|
|
145
|
+
);
|
|
146
|
+
const chatInfo = await client
|
|
147
|
+
.getChatInfo(chatId)
|
|
148
|
+
.catch(() => null);
|
|
149
|
+
const chatName = chatInfo?.name || chatId;
|
|
150
|
+
|
|
151
|
+
for (const msg of history.items || []) {
|
|
152
|
+
const content = parseMessageContent(
|
|
153
|
+
msg.msg_type,
|
|
154
|
+
msg.body?.content || "{}"
|
|
155
|
+
);
|
|
156
|
+
if (content.toLowerCase().includes(query)) {
|
|
157
|
+
const time = formatTimestamp(msg.create_time);
|
|
158
|
+
const sender = msg.sender?.id || "未知";
|
|
159
|
+
matches.push(
|
|
160
|
+
`[群聊: ${chatName}] [${time}] ${sender}: ${content}`
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
} catch (err) {
|
|
165
|
+
// 跳过无权限的群聊
|
|
166
|
+
matches.push(`[跳过群聊 ${chatId}: 无权限或获取失败]`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (matches.length === 0) {
|
|
171
|
+
return {
|
|
172
|
+
content: [
|
|
173
|
+
{
|
|
174
|
+
type: "text" as const,
|
|
175
|
+
text: `搜索「${params.query}」未找到匹配消息。尝试扩大搜索范围或使用 get_chat_history 获取更多消息。`,
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const result = [
|
|
182
|
+
`搜索「${params.query}」找到 ${matches.length} 条匹配消息:`,
|
|
183
|
+
"",
|
|
184
|
+
...matches,
|
|
185
|
+
];
|
|
186
|
+
|
|
187
|
+
return { content: [{ type: "text" as const, text: result.join("\n") }] };
|
|
188
|
+
}
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
// Tool 4: 获取消息详情
|
|
192
|
+
server.tool(
|
|
193
|
+
"get_message_detail",
|
|
194
|
+
"获取飞书消息的完整详情,包括完整的富文本内容、附件信息等。message_id格式如 om_xxxxx。",
|
|
195
|
+
{
|
|
196
|
+
message_id: z.string().describe("消息ID,格式如 om_xxxxx"),
|
|
197
|
+
},
|
|
198
|
+
async (params) => {
|
|
199
|
+
const data = await client.getMessageDetail(params.message_id);
|
|
200
|
+
const msg = data.items?.[0] || data;
|
|
201
|
+
|
|
202
|
+
const content = parseMessageContent(
|
|
203
|
+
msg.msg_type,
|
|
204
|
+
msg.body?.content || "{}"
|
|
205
|
+
);
|
|
206
|
+
const time = formatTimestamp(msg.create_time);
|
|
207
|
+
const sender = msg.sender?.id || "未知";
|
|
208
|
+
|
|
209
|
+
const result = [
|
|
210
|
+
`消息详情:`,
|
|
211
|
+
` ID: ${msg.message_id}`,
|
|
212
|
+
` 类型: ${msg.msg_type}`,
|
|
213
|
+
` 发送者: ${sender}`,
|
|
214
|
+
` 时间: ${time}`,
|
|
215
|
+
` 内容:`,
|
|
216
|
+
content,
|
|
217
|
+
];
|
|
218
|
+
|
|
219
|
+
return { content: [{ type: "text" as const, text: result.join("\n") }] };
|
|
220
|
+
}
|
|
221
|
+
);
|
|
222
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "Node16",
|
|
5
|
+
"moduleResolution": "Node16",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"types": ["node"],
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"declaration": true
|
|
15
|
+
},
|
|
16
|
+
"include": ["src/**/*"],
|
|
17
|
+
"exclude": ["node_modules", "dist"]
|
|
18
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mumulinya167/cc-web",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "cc-web - cc-connect 的 Web 管理界面,支持多 Agent 协作、任务派发、代码变更查看等",
|
|
5
|
+
"main": "bin/server.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"cc-web": "bin/ccm.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node bin/server.js",
|
|
11
|
+
"dev": "node bin/server.js"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"cc-connect",
|
|
15
|
+
"claude-code",
|
|
16
|
+
"ai-agent",
|
|
17
|
+
"feishu",
|
|
18
|
+
"multi-agent",
|
|
19
|
+
"web-console"
|
|
20
|
+
],
|
|
21
|
+
"author": "",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"dependencies": {},
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=18.0.0"
|
|
26
|
+
}
|
|
27
|
+
}
|