dingtalk-wiki 1.2.4 → 1.2.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 +95 -26
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -510,7 +510,7 @@ class DingTalkClient {
510
510
  const url = `${DINGTALK_API_V2}${pathName}`;
511
511
 
512
512
  try {
513
- const response = await axios({
513
+ const requestConfig = {
514
514
  method,
515
515
  url,
516
516
  headers: {
@@ -522,10 +522,16 @@ class DingTalkClient {
522
522
  ...extraParams
523
523
  },
524
524
  data
525
- });
525
+ };
526
+ if (pathName.includes('blocks')) {
527
+ console.error(`[DEBUG] docRequest: ${method} ${url} operatorId=${resolvedOperatorId} extraParams=${JSON.stringify(extraParams)}`);
528
+ }
529
+ const response = await axios(requestConfig);
526
530
  return response.data;
527
531
  } catch (error) {
528
532
  if (error.response) {
533
+ const fullErr = JSON.stringify(error.response.data);
534
+ console.error(`[DEBUG] docRequest error (${pathName}): status=${error.response.status} data=${fullErr}`);
529
535
  throw new Error(`${error.response.data?.message || error.message} (code: ${error.response.data?.code})`);
530
536
  }
531
537
  throw error;
@@ -850,7 +856,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
850
856
  properties: {
851
857
  doc_key: {
852
858
  type: 'string',
853
- description: '文档 docKey。wiki nodes 返回的 nodeId 本质是 dentryUuid,可直接用于此处'
859
+ description: '文档标识。支持 wiki nodes 的 nodeId(dentryUuid)、docKey,或文档 URL。重要:create_wiki_doc 返回的 nodeId 不能用于内容读写,请使用 docKey 或 dentryUuid。若传入 create 的 nodeId,需额外提供 workspace_id 以自动查找'
860
+ },
861
+ workspace_id: {
862
+ type: 'string',
863
+ description: '知识库 ID(可选)。当 doc_key 来自 create_wiki_doc 返回的 nodeId 时,提供此参数可自动查找正确的 dentryUuid'
854
864
  },
855
865
  operator_id: {
856
866
  type: 'string',
@@ -868,12 +878,16 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
868
878
  properties: {
869
879
  doc_key: {
870
880
  type: 'string',
871
- description: '文档 docKey。wiki nodes 返回的 nodeId 本质是 dentryUuid,可直接用于此处'
881
+ description: '文档标识。支持 wiki nodes 的 nodeId(dentryUuid)、docKey,或文档 URL。重要:create_wiki_doc 返回的 nodeId 不能用于内容读写,请使用 docKey 或 dentryUuid。若传入 create 的 nodeId,需额外提供 workspace_id 以自动查找'
872
882
  },
873
883
  content: {
874
884
  type: 'string',
875
885
  description: '要写入的 Markdown 内容'
876
886
  },
887
+ workspace_id: {
888
+ type: 'string',
889
+ description: '知识库 ID(可选)。当 doc_key 来自 create_wiki_doc 返回的 nodeId 时,提供此参数可自动查找正确的 dentryUuid'
890
+ },
877
891
  operator_id: {
878
892
  type: 'string',
879
893
  description: '操作者 unionid(不传则使用默认用户)'
@@ -1144,6 +1158,38 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1144
1158
  };
1145
1159
  });
1146
1160
 
1161
+ async function resolveDocKey(docKey, operatorId, dingtalkInstance) {
1162
+ const rawInput = docKey;
1163
+
1164
+ // 从 URL 中提取 ID
1165
+ const urlMatch = String(docKey).match(/\/i\/nodes\/([^\/\?#]+)/);
1166
+ docKey = urlMatch ? urlMatch[1] : docKey;
1167
+ console.error(`[DEBUG] resolveDocKey: rawInput=${rawInput}, extracted=${docKey}`);
1168
+
1169
+ try {
1170
+ const nodeInfo = await dingtalkInstance.docRequest('GET', `/v2.0/wiki/nodes/${docKey}`, { operatorId });
1171
+ const node = nodeInfo.node || nodeInfo;
1172
+ console.error(`[DEBUG] resolveDocKey: wiki/nodes response keys=${Object.keys(node).join(',')}`);
1173
+
1174
+ // wiki/nodes 的 nodeId = dentryUuid,直接可用
1175
+ // 但 node.document.docKey 可能是不同的值,优先使用
1176
+ if (node.document?.docKey) {
1177
+ console.error(`[DEBUG] resolveDocKey: found document.docKey=${node.document.docKey}`);
1178
+ return node.document.docKey;
1179
+ }
1180
+ // 部分响应直接把 docKey 放在顶层
1181
+ if (node.docKey) {
1182
+ console.error(`[DEBUG] resolveDocKey: found top-level docKey=${node.docKey}`);
1183
+ return node.docKey;
1184
+ }
1185
+ console.error(`[DEBUG] resolveDocKey: no docKey in response, returning raw docKey=${docKey}`);
1186
+ } catch (e) {
1187
+ console.error(`[DEBUG] resolveDocKey: wiki/nodes failed: ${e.message}`);
1188
+ }
1189
+
1190
+ return docKey;
1191
+ }
1192
+
1147
1193
  // 工具调用处理
1148
1194
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
1149
1195
  const { name, arguments: args } = request.params;
@@ -1297,11 +1343,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1297
1343
  '',
1298
1344
  `${typeIcon} ${name}`,
1299
1345
  `🗂️ 类型: ${doc_type}`,
1300
- `🆔 Node ID: ${doc.nodeId}`
1346
+ `🆔 Node ID: ${doc.nodeId}`,
1301
1347
  ];
1302
1348
 
1303
1349
  if (doc.docKey) {
1304
- lines.push(`🔑 DocKey: ${doc.docKey}`);
1350
+ lines.push(`🔑 DocKey(用于内容读写): ${doc.docKey}`);
1351
+ }
1352
+ if (doc.dentryUuid) {
1353
+ lines.push(`📄 dentryUuid(用于内容读写): ${doc.dentryUuid}`);
1305
1354
  }
1306
1355
  if (doc.url) {
1307
1356
  lines.push(`🔗 链接: ${doc.url}`);
@@ -1319,8 +1368,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1319
1368
  url: doc.url || '',
1320
1369
  type: doc_type,
1321
1370
  });
1322
- if (args.content && doc.nodeId) {
1323
- dingtalk.docRequest('POST', `/v1.0/doc/suites/documents/${doc.nodeId}/overwriteContent`, {
1371
+ const contentId = doc.docKey || doc.dentryUuid || doc.nodeId;
1372
+ if (args.content && contentId) {
1373
+ dingtalk.docRequest('POST', `/v1.0/doc/suites/documents/${contentId}/overwriteContent`, {
1374
+ operatorId: opId,
1324
1375
  data: { content: args.content, contentType: 'markdown' }
1325
1376
  }).catch(() => {});
1326
1377
  }
@@ -1557,20 +1608,31 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1557
1608
  }
1558
1609
 
1559
1610
  case 'get_wiki_doc_content': {
1560
- const { doc_key: docKey, operator_id } = args;
1611
+ const { doc_key: docKey, workspace_id: workspaceId, operator_id } = args;
1561
1612
  if (operator_id) {
1562
1613
  dingtalk.setOperatorId(operator_id);
1563
1614
  }
1564
1615
 
1565
- // nodeId 可能不等于 docKey,先尝试解析
1566
- let realDocKey = docKey;
1567
- try {
1568
- const nodeInfo = await dingtalk.docRequest('GET', `/v2.0/wiki/nodes/${docKey}`, { operatorId: operator_id || null });
1569
- if (nodeInfo.document?.docKey) {
1570
- realDocKey = nodeInfo.document.docKey;
1571
- }
1572
- } catch (e) { /* 非 nodeId,直接用原始值 */ }
1616
+ let realDocKey = await resolveDocKey(docKey, operator_id || null, dingtalk);
1617
+ console.error(`[DEBUG] get_wiki_doc_content: input=${docKey}, resolved=${realDocKey}`);
1618
+
1619
+ // 如果 resolve 后值没变(可能是 create 返回的 nodeId),尝试搜索 API 查找
1620
+ if (realDocKey === docKey && workspaceId) {
1621
+ try {
1622
+ const searchRes = await dingtalk.docRequest('GET', `/v1.0/doc/docs`, {
1623
+ operatorId: operator_id || null,
1624
+ extraParams: { workspaceId, keyword: '', maxResults: 50 }
1625
+ });
1626
+ const docs = searchRes.docs || [];
1627
+ const matched = docs.find(d => d.nodeBO?.nodeId === docKey || d.nodeBO?.nodeId === realDocKey) || docs[0];
1628
+ if (matched?.nodeBO?.nodeId) {
1629
+ realDocKey = matched.nodeBO.nodeId;
1630
+ console.error(`[DEBUG] get_wiki_doc_content: search fallback resolved to ${realDocKey}`);
1631
+ }
1632
+ } catch (e) { console.error(`[DEBUG] get_wiki_doc_content: search fallback failed: ${e.message}`); }
1633
+ }
1573
1634
 
1635
+ console.error(`[DEBUG] get_wiki_doc_content: calling blocks API with docKey=${realDocKey}`);
1574
1636
  const result = await dingtalk.docRequest('GET', `/v1.0/doc/suites/documents/${realDocKey}/blocks`, { operatorId: operator_id || null });
1575
1637
  const blocks = result.result?.data || [];
1576
1638
  let output = '';
@@ -1590,19 +1652,26 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1590
1652
  }
1591
1653
 
1592
1654
  case 'update_wiki_doc_content': {
1593
- const { doc_key: docKey, content, operator_id } = args;
1655
+ const { doc_key: docKey, content, workspace_id: workspaceId, operator_id } = args;
1594
1656
  if (operator_id) {
1595
1657
  dingtalk.setOperatorId(operator_id);
1596
1658
  }
1597
1659
 
1598
- // nodeId 可能不等于 docKey,先尝试解析
1599
- let realDocKey = docKey;
1600
- try {
1601
- const nodeInfo = await dingtalk.docRequest('GET', `/v2.0/wiki/nodes/${docKey}`, { operatorId: operator_id || null });
1602
- if (nodeInfo.document?.docKey) {
1603
- realDocKey = nodeInfo.document.docKey;
1604
- }
1605
- } catch (e) { /* 非 nodeId,直接用原始值 */ }
1660
+ let realDocKey = await resolveDocKey(docKey, operator_id || null, dingtalk);
1661
+
1662
+ if (realDocKey === docKey && workspaceId) {
1663
+ try {
1664
+ const searchRes = await dingtalk.docRequest('GET', `/v1.0/doc/docs`, {
1665
+ operatorId: operator_id || null,
1666
+ extraParams: { workspaceId, keyword: '', maxResults: 50 }
1667
+ });
1668
+ const docs = searchRes.docs || [];
1669
+ const matched = docs.find(d => d.nodeBO?.nodeId === docKey || d.nodeBO?.nodeId === realDocKey) || docs[0];
1670
+ if (matched?.nodeBO?.nodeId) {
1671
+ realDocKey = matched.nodeBO.nodeId;
1672
+ }
1673
+ } catch (e) { /* ignore */ }
1674
+ }
1606
1675
 
1607
1676
  await dingtalk.docRequest('POST', `/v1.0/doc/suites/documents/${realDocKey}/overwriteContent`, {
1608
1677
  operatorId: operator_id || null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dingtalk-wiki",
3
- "version": "1.2.4",
3
+ "version": "1.2.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": {