dingtalk-wiki 1.1.5 → 1.1.6

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.
Files changed (2) hide show
  1. package/index.js +88 -34
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -19,7 +19,7 @@ const DINGTALK_API_V2 = 'https://api.dingtalk.com';
19
19
  const fs = require('fs');
20
20
  const path = require('path');
21
21
  const os = require('os');
22
- const CACHE_DIR = path.join(os.homedir(), '.cache', 'dingtalk-wiki-mcp');
22
+ const CACHE_DIR = path.join(os.homedir(), '.cache', 'dingtalk-wiki');
23
23
  const UNIONID_CACHE_PATH = path.join(CACHE_DIR, 'unionid-cache.json');
24
24
 
25
25
  if (!fs.existsSync(CACHE_DIR)) {
@@ -286,7 +286,7 @@ class DingTalkClient {
286
286
  return this.operatorId;
287
287
  }
288
288
 
289
- async docRequest(method, pathName, { operatorId = null, data = null } = {}) {
289
+ async docRequest(method, pathName, { operatorId = null, data = null, extraParams = {} } = {}) {
290
290
  const token = await this.getAccessToken();
291
291
  const resolvedOperatorId = await this.resolveOperatorId(operatorId);
292
292
  const url = `${DINGTALK_API_V2}${pathName}`;
@@ -300,7 +300,8 @@ class DingTalkClient {
300
300
  'Content-Type': 'application/json'
301
301
  },
302
302
  params: {
303
- operatorId: resolvedOperatorId
303
+ operatorId: resolvedOperatorId,
304
+ ...extraParams
304
305
  },
305
306
  data
306
307
  });
@@ -502,7 +503,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
502
503
  },
503
504
  {
504
505
  name: 'search_wiki',
505
- description: '搜索知识库(POST /v2.0/doc/search)',
506
+ description: '搜索知识库中的文档和文件夹(遍历目录树按名称匹配)',
506
507
  inputSchema: {
507
508
  type: 'object',
508
509
  properties: {
@@ -512,15 +513,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
512
513
  },
513
514
  workspace_id: {
514
515
  type: 'string',
515
- description: '指定知识库 ID(可选)'
516
+ description: '指定知识库 ID(可选,不传则搜索所有知识库)'
516
517
  },
517
518
  max_results: {
518
519
  type: 'number',
519
- description: '返回条数上限(默认 10,最大 20)'
520
- },
521
- next_token: {
522
- type: 'string',
523
- description: '分页游标(上次返回的 nextToken)'
520
+ description: '返回条数上限(默认 20,最大 50)'
524
521
  },
525
522
  operator_id: {
526
523
  type: 'string',
@@ -1120,37 +1117,94 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1120
1117
  }
1121
1118
 
1122
1119
  case 'search_wiki': {
1123
- const { keyword, workspace_id, max_results = 10, next_token, operator_id } = args;
1120
+ const { keyword, workspace_id, max_results = 20, operator_id } = args;
1121
+ if (!keyword) {
1122
+ return {
1123
+ content: [{ type: 'text', text: '⚠️ 请提供搜索关键词 keyword' }],
1124
+ isError: true
1125
+ };
1126
+ }
1127
+
1128
+ const MAX_RESULTS = Math.min(max_results, 50);
1124
1129
  if (operator_id) {
1125
1130
  dingtalk.setOperatorId(operator_id);
1126
1131
  }
1127
- const body = {
1128
- keyword,
1129
- maxResults: Math.min(max_results, 20)
1130
- };
1131
- if (next_token) {
1132
- body.nextToken = next_token;
1133
- }
1132
+
1133
+ const wsResult = await dingtalk.wikiRequest('workspaces');
1134
+ let workspaces = wsResult.workspaces || [];
1134
1135
  if (workspace_id) {
1135
- body.option = { workspaceIds: [workspace_id] };
1136
+ workspaces = workspaces.filter(ws => ws.workspaceId === workspace_id);
1136
1137
  }
1137
- const result = await dingtalk.docRequest('POST', '/v2.0/doc/search', {
1138
- operatorId: operator_id || null,
1139
- data: body
1140
- });
1141
- const items = result.items || [];
1142
- let output = `🔍 搜索 "${keyword}" (${items.length}条)\n\n`;
1143
- items.forEach((item, i) => {
1144
- output += `${i + 1}. ${item.name}\n`;
1145
- output += ` 知识库: ${item.workspaceId}\n`;
1146
- output += ` 链接: ${item.url}\n\n`;
1147
- });
1148
- if (!items.length) {
1149
- output += '没有找到匹配的知识库。\n';
1138
+
1139
+ if (workspaces.length === 0) {
1140
+ return {
1141
+ content: [{ type: 'text', text: '⚠️ 未找到可搜索的知识库' }],
1142
+ isError: true
1143
+ };
1150
1144
  }
1151
- if (result.nextToken) {
1152
- output += `--- 更多结果, nextToken: ${result.nextToken} ---\n`;
1145
+
1146
+ const matchedNodes = [];
1147
+
1148
+ for (const ws of workspaces) {
1149
+ if (matchedNodes.length >= MAX_RESULTS) break;
1150
+
1151
+ const queue = [ws.rootNodeId];
1152
+ const visited = new Set();
1153
+
1154
+ while (queue.length > 0 && matchedNodes.length < MAX_RESULTS) {
1155
+ const dentryId = queue.shift();
1156
+ if (!dentryId || visited.has(dentryId)) continue;
1157
+ visited.add(dentryId);
1158
+
1159
+ try {
1160
+ const result = await dingtalk.docRequest('GET', `/v2.0/doc/spaces/${ws.workspaceId}/directories`, {
1161
+ operatorId: operator_id || null,
1162
+ extraParams: dentryId !== ws.rootNodeId ? { dentryId, maxResults: 500 } : { maxResults: 500 }
1163
+ });
1164
+
1165
+ const children = result.children || [];
1166
+ for (const child of children) {
1167
+ const name = child.name || '';
1168
+ if (name.includes(keyword)) {
1169
+ matchedNodes.push({
1170
+ name,
1171
+ nodeId: child.dentryId || child.nodeId || child.id,
1172
+ workspaceId: ws.workspaceId,
1173
+ workspaceName: ws.name,
1174
+ type: child.contentType === 'folder' ? '文件夹' : '文档',
1175
+ url: child.url || '',
1176
+ hasChildren: child.hasChildren
1177
+ });
1178
+ }
1179
+ if (child.hasChildren && matchedNodes.length < MAX_RESULTS) {
1180
+ const childId = child.dentryId || child.nodeId || child.id;
1181
+ if (childId && !visited.has(childId)) {
1182
+ queue.push(childId);
1183
+ }
1184
+ }
1185
+ }
1186
+ } catch (e) {
1187
+ // 单个节点遍历失败跳过,不中断整体搜索
1188
+ }
1189
+ }
1153
1190
  }
1191
+
1192
+ let output = `🔍 搜索 "${keyword}" (${matchedNodes.length}条)\n\n`;
1193
+ matchedNodes.slice(0, MAX_RESULTS).forEach((item, i) => {
1194
+ const icon = item.type === '文件夹' ? '📁' : '📄';
1195
+ output += `${i + 1}. ${icon} ${item.name}\n`;
1196
+ output += ` 知识库: ${item.workspaceName} (${item.workspaceId})\n`;
1197
+ if (item.url) output += ` 链接: ${item.url}\n`;
1198
+ output += '\n';
1199
+ });
1200
+
1201
+ if (matchedNodes.length === 0) {
1202
+ output += '没有找到匹配的文档或文件夹。\n';
1203
+ output += `\n💡 此方式通过遍历目录树按名称匹配,非全文搜索。`;
1204
+ output += `如需全文搜索,请在钉钉客户端中操作:\n`;
1205
+ output += `https://alidocs.dingtalk.com/i/search?keyword=${encodeURIComponent(keyword)}`;
1206
+ }
1207
+
1154
1208
  return {
1155
1209
  content: [{ type: 'text', text: output }]
1156
1210
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dingtalk-wiki",
3
- "version": "1.1.5",
3
+ "version": "1.1.6",
4
4
  "description": "DingTalk Wiki / Docs read-write MCP server that fills the gap left by DingTalk official MCP.",
5
5
  "main": "index.js",
6
6
  "bin": {