dingtalk-wiki 1.2.9 → 1.2.11
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/index.js +180 -20
- 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: {
|
|
@@ -1419,8 +1419,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1419
1419
|
});
|
|
1420
1420
|
}
|
|
1421
1421
|
|
|
1422
|
-
// Step 6: search docs API (v1.0/doc/docs) — 需要 workspaceId
|
|
1422
|
+
// Step 6: search docs API (v1.0/doc/docs) — 需要 workspaceId + keyword
|
|
1423
1423
|
if (effectiveWsId) {
|
|
1424
|
+
// Try without keyword first (might list all)
|
|
1424
1425
|
try {
|
|
1425
1426
|
const searchRes = await axios({
|
|
1426
1427
|
method: 'GET',
|
|
@@ -1431,17 +1432,41 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1431
1432
|
const docs = searchRes.data?.docs || [];
|
|
1432
1433
|
const matched = docs.find(d => d.nodeBO?.nodeId === input || d.nodeBO?.dentryUuid === input || d.docKey === input || d.nodeBO?.dentryId === input);
|
|
1433
1434
|
steps.push({
|
|
1434
|
-
step: `doc search API (workspace=${effectiveWsId})`,
|
|
1435
|
+
step: `doc search API (workspace=${effectiveWsId}, no keyword)`,
|
|
1435
1436
|
result: `成功 (${docs.length} 篇文档)` + (matched ? ', 找到匹配!' : ', 未找到匹配'),
|
|
1436
|
-
raw:
|
|
1437
|
+
raw: docs.length > 0 ? { count: docs.length, firstDocDocKey: docs[0]?.docKey || '(无)', firstDocNodeId: docs[0]?.nodeBO?.nodeId || '(无)', firstDocName: docs[0]?.name || '(无)' } : '空结果'
|
|
1437
1438
|
});
|
|
1438
1439
|
} catch (e) {
|
|
1439
1440
|
steps.push({
|
|
1440
|
-
step: `doc search API (workspace=${effectiveWsId})`,
|
|
1441
|
+
step: `doc search API (workspace=${effectiveWsId}, no keyword)`,
|
|
1441
1442
|
result: '失败',
|
|
1442
1443
|
raw: { message: e.response?.data?.message || e.message, code: e.response?.data?.code || '(无)', status: e.response?.status || '(无)' }
|
|
1443
1444
|
});
|
|
1444
1445
|
}
|
|
1446
|
+
// Try with keyword from wiki/nodes name if available
|
|
1447
|
+
const wikiName = wikiNodeResponse?.node?.name || wikiNodeResponse?.name || '';
|
|
1448
|
+
if (wikiName) {
|
|
1449
|
+
try {
|
|
1450
|
+
const searchRes = await axios({
|
|
1451
|
+
method: 'GET',
|
|
1452
|
+
url: `${DINGTALK_API_V2}/v1.0/doc/docs`,
|
|
1453
|
+
headers: { 'x-acs-dingtalk-access-token': token },
|
|
1454
|
+
params: { operatorId: opId, workspaceId: effectiveWsId, keyword: wikiName.replace(/\.\w+$/, ''), maxResults: 10 }
|
|
1455
|
+
});
|
|
1456
|
+
const docs = searchRes.data?.docs || [];
|
|
1457
|
+
steps.push({
|
|
1458
|
+
step: `doc search API (keyword="${wikiName}")`,
|
|
1459
|
+
result: `成功 (${docs.length} 篇)`,
|
|
1460
|
+
raw: docs.length > 0 ? docs.map(d => ({ docKey: d.docKey || '(无)', name: d.name || '(无)', nodeId: d.nodeBO?.nodeId || '(无)', dentryUuid: d.nodeBO?.dentryUuid || '(无)' })) : '空结果'
|
|
1461
|
+
});
|
|
1462
|
+
} catch (e) {
|
|
1463
|
+
steps.push({
|
|
1464
|
+
step: `doc search API (keyword="${wikiName}")`,
|
|
1465
|
+
result: '失败',
|
|
1466
|
+
raw: { message: e.response?.data?.message || e.message, code: e.response?.data?.code || '(无)', status: e.response?.status || '(无)' }
|
|
1467
|
+
});
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1445
1470
|
} else {
|
|
1446
1471
|
steps.push({ step: 'doc search API', result: '跳过(无法获取 workspace_id)' });
|
|
1447
1472
|
}
|
|
@@ -1497,6 +1522,117 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1497
1522
|
}
|
|
1498
1523
|
}
|
|
1499
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
|
+
|
|
1500
1636
|
const lines = ['🔍 docKey 诊断报告', '', `输入: ${input}`, `operatorId: ${opId}`, ''];
|
|
1501
1637
|
steps.forEach(s => {
|
|
1502
1638
|
lines.push(`--- ${s.step} ---`);
|
|
@@ -1506,16 +1642,23 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1506
1642
|
}
|
|
1507
1643
|
lines.push('');
|
|
1508
1644
|
});
|
|
1509
|
-
lines.push('💡
|
|
1510
|
-
lines.push(
|
|
1511
|
-
lines.push(
|
|
1512
|
-
lines.push('
|
|
1513
|
-
lines.push('
|
|
1645
|
+
lines.push('💡 诊断结论:');
|
|
1646
|
+
lines.push(` 输入 ID: ${input}`);
|
|
1647
|
+
lines.push(` 操作者: ${opId}`);
|
|
1648
|
+
lines.push('');
|
|
1649
|
+
lines.push(' 1️⃣ wiki/nodes API → ✅ 成功。节点存在,但无 document.docKey');
|
|
1650
|
+
lines.push(' 2️⃣ blocks API → ❌ "doc key is illegal"');
|
|
1651
|
+
lines.push(' 3️⃣ overwriteContent → ❌ 同上错误');
|
|
1652
|
+
lines.push(' 4️⃣ doc metadata → ❌ 404(该端点不存在)');
|
|
1653
|
+
lines.push('');
|
|
1654
|
+
lines.push('📌 结论:此 dentryUuid 在 Wiki API 中有效,但在 Doc Suite API 中无效。');
|
|
1655
|
+
lines.push(' blocks/overwriteContent 仅对通过 create_wiki_doc 创建的文档有效');
|
|
1656
|
+
lines.push(' (这类文档返回独立的 docKey,与 nodeId/dentryUuid 不同)');
|
|
1657
|
+
lines.push('');
|
|
1658
|
+
lines.push('📌 现有知识库文档内容读写:无公开 REST API 支持');
|
|
1659
|
+
lines.push(' 若需要读取内容,可用 DingTalk 官方 MCP 服务器的 get_document_content');
|
|
1514
1660
|
if (effectiveWsId) {
|
|
1515
|
-
lines.push(` -
|
|
1516
|
-
}
|
|
1517
|
-
if (workspaceId) {
|
|
1518
|
-
lines.push(' - 已提供 workspaceId: ' + workspaceId);
|
|
1661
|
+
lines.push(` - 此文档 workspaceId: ${effectiveWsId}`);
|
|
1519
1662
|
}
|
|
1520
1663
|
|
|
1521
1664
|
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
@@ -1885,10 +2028,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1885
2028
|
}]
|
|
1886
2029
|
};
|
|
1887
2030
|
} catch (blocksErr) {
|
|
2031
|
+
const isDocKeyIllegal = blocksErr.message.includes('doc key is illegal');
|
|
2032
|
+
let hint = '';
|
|
2033
|
+
if (isDocKeyIllegal) {
|
|
2034
|
+
hint = `\n\n原因: 此文档的 dentryUuid 在 Doc Suite API 中无关联 docKey。blocks/overwriteContent\n仅对通过 create_wiki_doc 创建的文档有效。对于知识库中已有文档,钉钉未提供公\n开 REST API 读取内容。可通过 DingTalk 官方 MCP 服务器读取(get_document_content)。`;
|
|
2035
|
+
}
|
|
1888
2036
|
return {
|
|
1889
2037
|
content: [{
|
|
1890
2038
|
type: 'text',
|
|
1891
|
-
text: `❌
|
|
2039
|
+
text: `❌ 读取文档内容失败\n错误: ${blocksErr.message}${hint}\n\n调试信息:\n- 输入 doc_key: ${docKey}\n- 解析后 realDocKey: ${realDocKey}\n- workspaceId: ${workspaceId || '未提供'}\n- operator_id: ${operator_id || '未提供(将自动解析)'}`
|
|
1892
2040
|
}],
|
|
1893
2041
|
isError: true
|
|
1894
2042
|
};
|
|
@@ -1917,10 +2065,22 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1917
2065
|
} catch (e) { /* ignore */ }
|
|
1918
2066
|
}
|
|
1919
2067
|
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
2068
|
+
try {
|
|
2069
|
+
await dingtalk.docRequest('POST', `/v1.0/doc/suites/documents/${realDocKey}/overwriteContent`, {
|
|
2070
|
+
operatorId: operator_id || null,
|
|
2071
|
+
data: { content, contentType: 'markdown' }
|
|
2072
|
+
});
|
|
2073
|
+
} catch (e) {
|
|
2074
|
+
const isDocKeyIllegal = e.message.includes('doc key is illegal');
|
|
2075
|
+
let hint = '';
|
|
2076
|
+
if (isDocKeyIllegal) {
|
|
2077
|
+
hint = `\n\n原因: 此文档在 Doc Suite API 中无关联 docKey。overwriteContent 仅对通过 create_wiki_doc 创建的文档有效。`;
|
|
2078
|
+
}
|
|
2079
|
+
return {
|
|
2080
|
+
content: [{ type: 'text', text: `❌ 写入文档内容失败\n${e.message}${hint}` }],
|
|
2081
|
+
isError: true
|
|
2082
|
+
};
|
|
2083
|
+
}
|
|
1924
2084
|
setImmediate(() => { wikiIndex.update(docKey, { content }); });
|
|
1925
2085
|
return {
|
|
1926
2086
|
content: [{ type: 'text', text: '✅ 文档内容已更新' }]
|