dingtalk-wiki 1.1.4 → 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 (3) hide show
  1. package/index.js +154 -47
  2. package/package.json +1 -1
  3. package/skill/SKILL.md +7 -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
  });
@@ -417,6 +418,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
417
418
  workspace_id: {
418
419
  type: 'string',
419
420
  description: '知识库工作空间 ID'
421
+ },
422
+ operator_id: {
423
+ type: 'string',
424
+ description: '操作者 unionid(不传则使用默认用户)'
420
425
  }
421
426
  },
422
427
  required: ['workspace_id']
@@ -434,7 +439,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
434
439
  },
435
440
  parent_node_id: {
436
441
  type: 'string',
437
- description: '父节点 ID(不传则获取根目录)'
442
+ description: '父节点 ID(不传则列出根目录节点)'
443
+ },
444
+ operator_id: {
445
+ type: 'string',
446
+ description: '操作者 unionid(不传则使用默认用户)'
438
447
  }
439
448
  },
440
449
  required: ['workspace_id']
@@ -456,9 +465,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
456
465
  },
457
466
  doc_type: {
458
467
  type: 'string',
459
- description: '文档类型: DOC(文字), WORKBOOK(表格), MIND(脑图), FOLDER(文件夹)',
460
- enum: ['DOC', 'WORKBOOK', 'MIND', 'FOLDER'],
461
- default: 'DOC'
468
+ description: '文档类型,可选: DOC / WORKBOOK / MIND / FOLDER(默认 DOC)'
469
+ },
470
+ operator_id: {
471
+ type: 'string',
472
+ description: '操作者 unionid(不传则使用默认用户)'
462
473
  },
463
474
  parent_node_id: {
464
475
  type: 'string',
@@ -481,6 +492,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
481
492
  node_id: {
482
493
  type: 'string',
483
494
  description: '节点 ID'
495
+ },
496
+ operator_id: {
497
+ type: 'string',
498
+ description: '操作者 unionid(不传则使用默认用户)'
484
499
  }
485
500
  },
486
501
  required: ['node_id']
@@ -488,7 +503,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
488
503
  },
489
504
  {
490
505
  name: 'search_wiki',
491
- description: '搜索知识库内容',
506
+ description: '搜索知识库中的文档和文件夹(遍历目录树按名称匹配)',
492
507
  inputSchema: {
493
508
  type: 'object',
494
509
  properties: {
@@ -498,7 +513,15 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
498
513
  },
499
514
  workspace_id: {
500
515
  type: 'string',
501
- description: '指定知识库 ID(可选)'
516
+ description: '指定知识库 ID(可选,不传则搜索所有知识库)'
517
+ },
518
+ max_results: {
519
+ type: 'number',
520
+ description: '返回条数上限(默认 20,最大 50)'
521
+ },
522
+ operator_id: {
523
+ type: 'string',
524
+ description: '操作者 unionid(不传则使用默认用户)'
502
525
  }
503
526
  },
504
527
  required: ['keyword']
@@ -894,8 +917,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
894
917
  }
895
918
 
896
919
  case 'get_wiki_workspace': {
897
- const { workspace_id } = args;
898
- // 通过列表获取详情
920
+ const { workspace_id, operator_id } = args;
921
+ if (operator_id) {
922
+ dingtalk.setOperatorId(operator_id);
923
+ }
899
924
  const result = await dingtalk.wikiRequest('workspaces');
900
925
  const workspaces = result.workspaces || [];
901
926
  const workspace = workspaces.find(ws => ws.workspaceId === workspace_id);
@@ -1066,24 +1091,122 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1066
1091
  }
1067
1092
 
1068
1093
  case 'get_wiki_node': {
1069
- const { node_id } = args;
1070
- // 通过搜索或其他方式获取节点详情
1094
+ const { node_id, operator_id } = args;
1095
+ if (operator_id) {
1096
+ dingtalk.setOperatorId(operator_id);
1097
+ }
1098
+ const result = await dingtalk.docRequest('GET', `/v2.0/wiki/nodes/${node_id}`, {
1099
+ operatorId: operator_id || null
1100
+ });
1101
+ const node = result;
1102
+ let output = `📄 节点详情\n\n`;
1103
+ output += `名称: ${node.name || '-'}\n`;
1104
+ output += `ID: ${node.id || node.nodeId || '-'}\n`;
1105
+ output += `类型: ${node.type || '-'}\n`;
1106
+ output += `知识库 ID: ${node.workspaceId || '-'}\n`;
1107
+ output += `父节点: ${node.parentNodeId || '-'}\n`;
1108
+ output += `创建时间: ${node.createdTime || '-'}\n`;
1109
+ output += `修改时间: ${node.modifiedTime || '-'}\n`;
1110
+ output += `URL: ${node.url || '-'}\n`;
1111
+ if (node.document) {
1112
+ output += `文档信息: ${JSON.stringify(node.document)}\n`;
1113
+ }
1071
1114
  return {
1072
- content: [{
1073
- type: 'text',
1074
- text: `📄 节点 ID: ${node_id}\n\n请使用 list_wiki_nodes 获取节点列表,然后通过节点链接访问详情。`
1075
- }]
1115
+ content: [{ type: 'text', text: output }]
1076
1116
  };
1077
1117
  }
1078
1118
 
1079
1119
  case 'search_wiki': {
1080
- const { keyword, workspace_id } = args;
1081
- // Wiki 搜索 API 需要额外权限
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);
1129
+ if (operator_id) {
1130
+ dingtalk.setOperatorId(operator_id);
1131
+ }
1132
+
1133
+ const wsResult = await dingtalk.wikiRequest('workspaces');
1134
+ let workspaces = wsResult.workspaces || [];
1135
+ if (workspace_id) {
1136
+ workspaces = workspaces.filter(ws => ws.workspaceId === workspace_id);
1137
+ }
1138
+
1139
+ if (workspaces.length === 0) {
1140
+ return {
1141
+ content: [{ type: 'text', text: '⚠️ 未找到可搜索的知识库' }],
1142
+ isError: true
1143
+ };
1144
+ }
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
+ }
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
+
1082
1208
  return {
1083
- content: [{
1084
- type: 'text',
1085
- text: `🔍 搜索知识库: ${keyword}\n\n搜索功能需要 Wiki.Search 权限。\n\n请直接访问知识库网页版进行搜索:\nhttps://alidocs.dingtalk.com/i/spaces/${workspace_id || ''}/search?keyword=${encodeURIComponent(keyword)}`
1086
- }]
1209
+ content: [{ type: 'text', text: output }]
1087
1210
  };
1088
1211
  }
1089
1212
 
@@ -1165,20 +1288,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1165
1288
  if (operator_id) {
1166
1289
  dingtalk.setOperatorId(operator_id);
1167
1290
  }
1168
- const opId = await dingtalk.resolveOperatorId(operator_id || null);
1169
1291
  await dingtalk.docRequest('POST', `/v1.0/doc/suites/documents/${docKey}/overwriteContent`, {
1170
- operatorId: opId,
1171
- data: {
1172
- operatorId: opId,
1173
- content,
1174
- contentType: 'markdown'
1175
- }
1292
+ operatorId: operator_id || null,
1293
+ data: { content, contentType: 'markdown' }
1176
1294
  });
1177
1295
  return {
1178
- content: [{
1179
- type: 'text',
1180
- text: '✅ 文档内容已更新'
1181
- }]
1296
+ content: [{ type: 'text', text: '✅ 文档内容已更新' }]
1182
1297
  };
1183
1298
  }
1184
1299
 
@@ -1187,19 +1302,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1187
1302
  if (operator_id) {
1188
1303
  dingtalk.setOperatorId(operator_id);
1189
1304
  }
1190
- const opId = await dingtalk.resolveOperatorId(operator_id || null);
1191
1305
  await dingtalk.docRequest('PATCH', `/v1.0/doc/workspaces/${workspace_id}/docs/${node_id}`, {
1192
- operatorId: opId,
1193
- data: {
1194
- name,
1195
- operatorId: opId
1196
- }
1306
+ operatorId: operator_id || null,
1307
+ data: { name }
1197
1308
  });
1198
1309
  return {
1199
- content: [{
1200
- type: 'text',
1201
- text: `✅ 文档已重命名为: ${name}`
1202
- }]
1310
+ content: [{ type: 'text', text: `✅ 文档已重命名为: ${name}` }]
1203
1311
  };
1204
1312
  }
1205
1313
 
@@ -1208,9 +1316,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1208
1316
  if (operator_id) {
1209
1317
  dingtalk.setOperatorId(operator_id);
1210
1318
  }
1211
- const opId = await dingtalk.resolveOperatorId(operator_id || null);
1212
1319
  await dingtalk.docRequest('DELETE', `/v1.0/doc/workspaces/${workspace_id}/docs/${node_id}`, {
1213
- operatorId: opId
1320
+ operatorId: operator_id || null
1214
1321
  });
1215
1322
  return {
1216
1323
  content: [{
@@ -1362,7 +1469,7 @@ async function main() {
1362
1469
  const transport = new StdioServerTransport();
1363
1470
  await server.connect(transport);
1364
1471
  console.error('钉钉 Wiki MCP Server 已启动 v2.0');
1365
- console.error(`Config path: ${CONFIG_PATH}`);
1472
+ console.error(`Config via DINGTALK_WIKI_CONFIG env var`);
1366
1473
  }
1367
1474
 
1368
1475
  main().catch(console.error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dingtalk-wiki",
3
- "version": "1.1.4",
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": {
package/skill/SKILL.md CHANGED
@@ -100,11 +100,17 @@ mcporter call dingtalk-wiki.delete_wiki_doc \
100
100
  workspace_id="your_workspace_id" \
101
101
  node_id="your_node_id"
102
102
 
103
- # 搜索知识库内容
103
+ # 搜索知识库
104
104
  mcporter call dingtalk-wiki.search_wiki keyword="项目规划"
105
105
 
106
106
  # 在指定知识库内搜索
107
107
  mcporter call dingtalk-wiki.search_wiki keyword="项目规划" workspace_id="your_workspace_id"
108
+
109
+ # 自定义返回条数
110
+ mcporter call dingtalk-wiki.search_wiki keyword="项目规划" max_results=5
111
+
112
+ # 分页搜索
113
+ mcporter call dingtalk-wiki.search_wiki keyword="项目规划" next_token="your_next_token"
108
114
  ```
109
115
 
110
116
  ### AI 表格(Notable)