dingtalk-wiki 1.2.10 → 1.2.12

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 +177 -7
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -872,7 +872,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
872
872
  },
873
873
  {
874
874
  name: 'get_wiki_doc_content',
875
- description: '读取文档正文内容(返回 Block 结构,含标题、段落、表格等)',
875
+ description: '读取文档正文内容(返回 Block 结构)。注意:仅对 create_wiki_doc 创建的文档有效;知识库中已有文档(非通过此工具创建的)不受支持——钉钉未提供公开 REST API 读取已有知识库文档内容。若需读取已有文档内容,可使用 DingTalk 官方 MCP 服务器的 get_document_content 工具。',
876
876
  inputSchema: {
877
877
  type: 'object',
878
878
  properties: {
@@ -894,7 +894,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
894
894
  },
895
895
  {
896
896
  name: 'update_wiki_doc_content',
897
- description: '覆写文档正文内容(⚠️ 全量覆盖,不可撤销)',
897
+ description: '覆写文档正文内容(⚠️ 全量覆盖,不可撤销)。注意:仅对 create_wiki_doc 创建的文档有效;知识库中已有文档(非通过此工具创建的)不受支持。',
898
898
  inputSchema: {
899
899
  type: 'object',
900
900
  properties: {
@@ -1522,6 +1522,159 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1522
1522
  }
1523
1523
  }
1524
1524
 
1525
+ // Step 9: 递归遍历目录树查找文档 + 存储API下载内容
1526
+ if (effectiveWsId) {
1527
+ try {
1528
+ // BFS 遍历目录树查找 dentry
1529
+ let foundDentry = null;
1530
+ let foundSpaceId = null;
1531
+ const queue = [''];
1532
+ const visited = new Set();
1533
+ while (queue.length > 0 && !foundDentry) {
1534
+ const parentId = queue.shift();
1535
+ if (visited.has(parentId)) continue;
1536
+ visited.add(parentId);
1537
+ try {
1538
+ const dirParams = { operatorId: opId, maxResults: 500 };
1539
+ if (parentId) dirParams.dentryId = parentId;
1540
+ const dirRes = await axios({
1541
+ method: 'GET',
1542
+ url: `${DINGTALK_API_V2}/v2.0/doc/spaces/${effectiveWsId}/directories`,
1543
+ headers: { 'x-acs-dingtalk-access-token': token },
1544
+ params: dirParams
1545
+ });
1546
+ const children = dirRes.data?.children || [];
1547
+ for (const child of children) {
1548
+ if (child.dentryUuid === input || child.dentryId === input) {
1549
+ foundDentry = child;
1550
+ foundSpaceId = child.spaceId;
1551
+ break;
1552
+ }
1553
+ if (child.hasChildren && child.dentryId && !visited.has(child.dentryId)) {
1554
+ queue.push(child.dentryId);
1555
+ }
1556
+ }
1557
+ } catch (e) { /* skip */ }
1558
+ }
1559
+
1560
+ if (foundDentry) {
1561
+ steps.push({
1562
+ step: 'BFS 目录遍历找到文档',
1563
+ result: `spaceId=${foundSpaceId}, dentryId=${foundDentry.dentryId}, name=${foundDentry.name}`,
1564
+ raw: foundDentry
1565
+ });
1566
+
1567
+ // 尝试存储 API 获取 dentry 详情(含 docKey)
1568
+ try {
1569
+ const dentryRes = await axios({
1570
+ method: 'GET',
1571
+ url: `${DINGTALK_API_V2}/v2.0/storage/spaces/${foundSpaceId}/dentries/${foundDentry.dentryId}`,
1572
+ headers: { 'x-acs-dingtalk-access-token': token },
1573
+ params: { operatorId: opId }
1574
+ });
1575
+ steps.push({
1576
+ step: '存储 API dentry 详情',
1577
+ result: '成功',
1578
+ raw: dentryRes.data
1579
+ });
1580
+ } catch (e) {
1581
+ steps.push({
1582
+ step: '存储 API dentry 详情',
1583
+ result: '失败',
1584
+ raw: { message: e.response?.data?.message || e.message, code: e.response?.data?.code || '(无)', status: e.response?.status || '(无)' }
1585
+ });
1586
+ }
1587
+
1588
+ // 尝试存储 API 下载
1589
+ try {
1590
+ const dlRes = await axios({
1591
+ method: 'POST',
1592
+ url: `${DINGTALK_API_V2}/v2.0/storage/spaces/${foundSpaceId}/dentries/${foundDentry.dentryId}/downloadInfos/query`,
1593
+ headers: { 'x-acs-dingtalk-access-token': token, 'Content-Type': 'application/json' },
1594
+ params: { operatorId: opId },
1595
+ data: {}
1596
+ });
1597
+ steps.push({
1598
+ step: '存储 API 获取下载链接',
1599
+ result: '成功',
1600
+ raw: dlRes.data
1601
+ });
1602
+ // 尝试从下载链接获取内容
1603
+ const dlInfo = dlRes.data;
1604
+ const downloadUrl = dlInfo.headerSignatureInfo?.resourceUrls?.[0] || dlInfo.downloadUrl || dlInfo.url;
1605
+ if (downloadUrl) {
1606
+ try {
1607
+ const contentRes = await axios({ method: 'GET', url: downloadUrl, responseType: 'text' });
1608
+ steps.push({
1609
+ step: '存储 API 下载内容',
1610
+ result: `成功 (${contentRes.data.length} 字符)`,
1611
+ raw: contentRes.data.slice(0, 500) + (contentRes.data.length > 500 ? '\n... (截断)' : '')
1612
+ });
1613
+ } catch (dlErr) {
1614
+ steps.push({
1615
+ step: '存储 API 下载内容',
1616
+ result: '失败',
1617
+ raw: dlErr.message
1618
+ });
1619
+ }
1620
+ }
1621
+ } catch (e) {
1622
+ steps.push({
1623
+ step: '存储 API 获取下载链接',
1624
+ result: '失败',
1625
+ raw: { message: e.response?.data?.message || e.message, code: e.response?.data?.code || '(无)', status: e.response?.status || '(无)' }
1626
+ });
1627
+ }
1628
+ } else {
1629
+ steps.push({ step: 'BFS 目录遍历', result: '未找到匹配(文档可能在深层目录)' });
1630
+ }
1631
+ } catch (e) {
1632
+ steps.push({ step: 'BFS 目录遍历', result: '失败', raw: e.message });
1633
+ }
1634
+ }
1635
+
1636
+ // Step 10: doc_2.0 API - DocContent (direct dentry content read, async returns taskId)
1637
+ try {
1638
+ const docContentRes = await axios({
1639
+ method: 'GET',
1640
+ url: `${DINGTALK_API_V2}/v2.0/doc/dentries/${input}/contents`,
1641
+ headers: { 'x-acs-dingtalk-access-token': token },
1642
+ params: { operatorId: opId }
1643
+ });
1644
+ steps.push({
1645
+ step: 'doc_2.0 DocContent GET /v2.0/doc/dentries/{uuid}/contents',
1646
+ result: '成功',
1647
+ raw: docContentRes.data
1648
+ });
1649
+ } catch (e) {
1650
+ steps.push({
1651
+ step: 'doc_2.0 DocContent GET /v2.0/doc/dentries/{uuid}/contents',
1652
+ result: '失败',
1653
+ raw: { message: e.response?.data?.message || e.message, code: e.response?.data?.code || '(无)', status: e.response?.status || '(无)' }
1654
+ });
1655
+ }
1656
+
1657
+ // Step 11: doc_2.0 API - GetDocContent (another way to get content)
1658
+ try {
1659
+ const getDocContentRes = await axios({
1660
+ method: 'GET',
1661
+ url: `${DINGTALK_API_V2}/v2.0/doc/me/query/${input}/contents`,
1662
+ headers: { 'x-acs-dingtalk-access-token': token },
1663
+ params: { targetFormat: 'markdown' }
1664
+ });
1665
+ steps.push({
1666
+ step: 'doc_2.0 GetDocContent GET /v2.0/doc/me/query/{uuid}/contents',
1667
+ result: '成功',
1668
+ raw: getDocContentRes.data
1669
+ });
1670
+ } catch (e) {
1671
+ steps.push({
1672
+ step: 'doc_2.0 GetDocContent GET /v2.0/doc/me/query/{uuid}/contents',
1673
+ result: '失败',
1674
+ raw: { message: e.response?.data?.message || e.message, code: e.response?.data?.code || '(无)', status: e.response?.status || '(无)' }
1675
+ });
1676
+ }
1677
+
1525
1678
  const lines = ['🔍 docKey 诊断报告', '', `输入: ${input}`, `operatorId: ${opId}`, ''];
1526
1679
  steps.forEach(s => {
1527
1680
  lines.push(`--- ${s.step} ---`);
@@ -1917,10 +2070,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1917
2070
  }]
1918
2071
  };
1919
2072
  } catch (blocksErr) {
2073
+ const isDocKeyIllegal = blocksErr.message.includes('doc key is illegal');
2074
+ let hint = '';
2075
+ if (isDocKeyIllegal) {
2076
+ hint = `\n\n原因: 此文档的 dentryUuid 在 Doc Suite API 中无关联 docKey。blocks/overwriteContent\n仅对通过 create_wiki_doc 创建的文档有效。对于知识库中已有文档,钉钉未提供公\n开 REST API 读取内容。可通过 DingTalk 官方 MCP 服务器读取(get_document_content)。`;
2077
+ }
1920
2078
  return {
1921
2079
  content: [{
1922
2080
  type: 'text',
1923
- text: `❌ blocks API 调用失败\n错误: ${blocksErr.message}\n\n调试信息:\n- 输入 doc_key: ${docKey}\n- 解析后 realDocKey: ${realDocKey}\n- workspaceId: ${workspaceId || '未提供'}\n- operator_id: ${operator_id || '未提供(将自动解析)'}`
2081
+ text: `❌ 读取文档内容失败\n错误: ${blocksErr.message}${hint}\n\n调试信息:\n- 输入 doc_key: ${docKey}\n- 解析后 realDocKey: ${realDocKey}\n- workspaceId: ${workspaceId || '未提供'}\n- operator_id: ${operator_id || '未提供(将自动解析)'}`
1924
2082
  }],
1925
2083
  isError: true
1926
2084
  };
@@ -1949,10 +2107,22 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1949
2107
  } catch (e) { /* ignore */ }
1950
2108
  }
1951
2109
 
1952
- await dingtalk.docRequest('POST', `/v1.0/doc/suites/documents/${realDocKey}/overwriteContent`, {
1953
- operatorId: operator_id || null,
1954
- data: { content, contentType: 'markdown' }
1955
- });
2110
+ try {
2111
+ await dingtalk.docRequest('POST', `/v1.0/doc/suites/documents/${realDocKey}/overwriteContent`, {
2112
+ operatorId: operator_id || null,
2113
+ data: { content, contentType: 'markdown' }
2114
+ });
2115
+ } catch (e) {
2116
+ const isDocKeyIllegal = e.message.includes('doc key is illegal');
2117
+ let hint = '';
2118
+ if (isDocKeyIllegal) {
2119
+ hint = `\n\n原因: 此文档在 Doc Suite API 中无关联 docKey。overwriteContent 仅对通过 create_wiki_doc 创建的文档有效。`;
2120
+ }
2121
+ return {
2122
+ content: [{ type: 'text', text: `❌ 写入文档内容失败\n${e.message}${hint}` }],
2123
+ isError: true
2124
+ };
2125
+ }
1956
2126
  setImmediate(() => { wikiIndex.update(docKey, { content }); });
1957
2127
  return {
1958
2128
  content: [{ type: 'text', text: '✅ 文档内容已更新' }]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dingtalk-wiki",
3
- "version": "1.2.10",
3
+ "version": "1.2.12",
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": {