dingtalk-wiki 1.2.7 → 1.2.8

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 +219 -0
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -726,6 +726,28 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
726
726
  required: ['node_id']
727
727
  }
728
728
  },
729
+ {
730
+ name: 'debug_resolve_doc_key',
731
+ description: '[调试] 诊断 docKey 解析问题,测试所有可能的 API 路径并返回原始响应',
732
+ inputSchema: {
733
+ type: 'object',
734
+ properties: {
735
+ doc_key: {
736
+ type: 'string',
737
+ description: '文档标识(dentryUuid / docKey / nodeId / URL)'
738
+ },
739
+ workspace_id: {
740
+ type: 'string',
741
+ description: '知识库 ID(可选)'
742
+ },
743
+ operator_id: {
744
+ type: 'string',
745
+ description: '操作者 unionid(不传则使用默认用户)'
746
+ }
747
+ },
748
+ required: ['doc_key']
749
+ }
750
+ },
729
751
  {
730
752
  name: 'search_wiki',
731
753
  description: '按名称搜索知识库中的文档和文件夹(遍历目录树,无需索引即可使用)',
@@ -1286,6 +1308,203 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1286
1308
  };
1287
1309
  }
1288
1310
 
1311
+ case 'debug_resolve_doc_key': {
1312
+ const { doc_key: docKey, workspace_id: workspaceId, operator_id } = args;
1313
+ if (operator_id) {
1314
+ dingtalk.setOperatorId(operator_id);
1315
+ }
1316
+
1317
+ const token = await dingtalk.getAccessToken();
1318
+ const opId = await dingtalk.resolveOperatorId(operator_id || null);
1319
+ const steps = [];
1320
+ const input = String(docKey);
1321
+
1322
+ steps.push({ step: '输入', result: input });
1323
+
1324
+ // Step 1: URL extraction
1325
+ const urlMatch = input.match(/\/i\/nodes\/([^\/\?#]+)/);
1326
+ const urlExtracted = urlMatch ? urlMatch[1] : null;
1327
+ steps.push({ step: 'URL 提取', result: urlExtracted || '无匹配', raw: urlMatch ? urlMatch[0] : null });
1328
+
1329
+ // Step 2: wiki/nodes API
1330
+ try {
1331
+ const nodeRes = await axios({
1332
+ method: 'GET',
1333
+ url: `${DINGTALK_API_V2}/v2.0/wiki/nodes/${input}`,
1334
+ headers: { 'x-acs-dingtalk-access-token': token },
1335
+ params: { operatorId: opId }
1336
+ });
1337
+ const node = nodeRes.data;
1338
+ steps.push({
1339
+ step: 'wiki/nodes API',
1340
+ result: '成功',
1341
+ raw: {
1342
+ nodeId: node.nodeId || node.id || node.node?.nodeId || '(无)',
1343
+ name: node.name || node.node?.name || '(无)',
1344
+ docKey: node.document?.docKey || node.docKey || node.node?.document?.docKey || '(无)',
1345
+ dentryUuid: node.dentryUuid || node.node?.dentryUuid || '(无)',
1346
+ hasChildren: node.hasChildren || node.node?.hasChildren || '(无)',
1347
+ type: node.type || node.node?.type || '(无)',
1348
+ workspaceId: node.workspaceId || node.node?.workspaceId || '(无)',
1349
+ }
1350
+ });
1351
+ } catch (e) {
1352
+ steps.push({
1353
+ step: 'wiki/nodes API',
1354
+ result: '失败',
1355
+ raw: { message: e.response?.data?.message || e.message, code: e.response?.data?.code || '(无)', status: e.response?.status || '(无)' }
1356
+ });
1357
+ }
1358
+
1359
+ // Step 3: doc metadata API (GET /v1.0/doc/suites/documents/{docKey})
1360
+ try {
1361
+ const metaRes = await axios({
1362
+ method: 'GET',
1363
+ url: `${DINGTALK_API_V2}/v1.0/doc/suites/documents/${input}`,
1364
+ headers: { 'x-acs-dingtalk-access-token': token },
1365
+ params: { operatorId: opId }
1366
+ });
1367
+ steps.push({
1368
+ step: 'doc metadata API (GET /documents/{docKey})',
1369
+ result: '成功',
1370
+ raw: metaRes.data
1371
+ });
1372
+ } catch (e) {
1373
+ steps.push({
1374
+ step: 'doc metadata API (GET /documents/{docKey})',
1375
+ result: '失败',
1376
+ raw: { message: e.response?.data?.message || e.message, code: e.response?.data?.code || '(无)', status: e.response?.status || '(无)' }
1377
+ });
1378
+ }
1379
+
1380
+ // Step 4: blocks API
1381
+ try {
1382
+ const blocksRes = await axios({
1383
+ method: 'GET',
1384
+ url: `${DINGTALK_API_V2}/v1.0/doc/suites/documents/${input}/blocks`,
1385
+ headers: { 'x-acs-dingtalk-access-token': token },
1386
+ params: { operatorId: opId }
1387
+ });
1388
+ const blockCount = blocksRes.data?.result?.data?.length || 0;
1389
+ steps.push({
1390
+ step: 'blocks API',
1391
+ result: `成功 (${blockCount} 个 block)`,
1392
+ raw: blocksRes.data
1393
+ });
1394
+ } catch (e) {
1395
+ steps.push({
1396
+ step: 'blocks API',
1397
+ result: '失败',
1398
+ raw: { message: e.response?.data?.message || e.message, code: e.response?.data?.code || '(无)', status: e.response?.status || '(无)' }
1399
+ });
1400
+ }
1401
+
1402
+ // Step 5: overwriteContent API (test with empty content — don't actually write)
1403
+ try {
1404
+ const overwriteRes = await axios({
1405
+ method: 'POST',
1406
+ url: `${DINGTALK_API_V2}/v1.0/doc/suites/documents/${input}/overwriteContent`,
1407
+ headers: { 'x-acs-dingtalk-access-token': token, 'Content-Type': 'application/json' },
1408
+ params: { operatorId: opId },
1409
+ data: { content: 'test', contentType: 'markdown' }
1410
+ });
1411
+ steps.push({
1412
+ step: 'overwriteContent API',
1413
+ result: '成功(可写入)',
1414
+ raw: overwriteRes.data
1415
+ });
1416
+ } catch (e) {
1417
+ steps.push({
1418
+ step: 'overwriteContent API',
1419
+ result: '失败',
1420
+ raw: { message: e.response?.data?.message || e.message, code: e.response?.data?.code || '(无)', status: e.response?.status || '(无)' }
1421
+ });
1422
+ }
1423
+
1424
+ // Step 6: search docs API (requires workspaceId)
1425
+ if (workspaceId) {
1426
+ try {
1427
+ const searchRes = await axios({
1428
+ method: 'GET',
1429
+ url: `${DINGTALK_API_V2}/v1.0/doc/docs`,
1430
+ headers: { 'x-acs-dingtalk-access-token': token },
1431
+ params: { operatorId: opId, workspaceId, maxResults: 10 }
1432
+ });
1433
+ const docs = searchRes.data?.docs || [];
1434
+ const matched = docs.find(d => d.nodeBO?.nodeId === input || d.nodeBO?.dentryUuid === input || d.docKey === input);
1435
+ steps.push({
1436
+ step: 'doc search API (v1.0/doc/docs)',
1437
+ result: `成功 (${docs.length} 篇文档)` + (matched ? ', 找到匹配' : ', 未找到匹配'),
1438
+ raw: matched || `${docs.length} results, first doc docKey=${docs[0]?.docKey || '(none)'}`
1439
+ });
1440
+ } catch (e) {
1441
+ steps.push({
1442
+ step: 'doc search API (v1.0/doc/docs)',
1443
+ result: '失败',
1444
+ raw: { message: e.response?.data?.message || e.message, code: e.response?.data?.code || '(无)', status: e.response?.status || '(无)' }
1445
+ });
1446
+ }
1447
+ } else {
1448
+ steps.push({ step: 'doc search API (v1.0/doc/docs)', result: '跳过(需 workspace_id)' });
1449
+ }
1450
+
1451
+ // Step 7: wiki workspaces + directories search (try all workspaces)
1452
+ if (!workspaceId) {
1453
+ try {
1454
+ const wsRes = await axios({
1455
+ method: 'GET',
1456
+ url: `${DINGTALK_API_V2}/v2.0/wiki/workspaces`,
1457
+ headers: { 'x-acs-dingtalk-access-token': token },
1458
+ params: { operatorId: opId }
1459
+ });
1460
+ const workspaces = wsRes.data?.workspaces || [];
1461
+ let found = null;
1462
+ for (const ws of workspaces) {
1463
+ if (found) break;
1464
+ try {
1465
+ const dirRes = await axios({
1466
+ method: 'GET',
1467
+ url: `${DINGTALK_API_V2}/v2.0/doc/spaces/${ws.workspaceId}/directories`,
1468
+ headers: { 'x-acs-dingtalk-access-token': token },
1469
+ params: { operatorId: opId, maxResults: 500 }
1470
+ });
1471
+ const children = dirRes.data?.children || [];
1472
+ const match = children.find(c => c.dentryUuid === input || c.dentryId === input || c.id === input);
1473
+ if (match) {
1474
+ found = { workspaceId: ws.workspaceId, workspaceName: ws.name, node: match };
1475
+ }
1476
+ } catch (dirErr) { /* skip workspace */ }
1477
+ }
1478
+ steps.push({
1479
+ step: '遍历知识库目录匹配',
1480
+ result: found ? `在知识库 ${found.workspaceName}(${found.workspaceId}) 中找到` : '在所有知识库中未找到匹配',
1481
+ raw: found ? { workspaceId: found.workspaceId, name: found.node?.name, dentryUuid: found.node?.dentryUuid, dentryId: found.node?.dentryId, hasChildren: found.node?.hasChildren, contentType: found.node?.contentType, docKey: found.node?.docKey || '(无)' } : null
1482
+ });
1483
+ } catch (e) {
1484
+ steps.push({ step: '遍历知识库目录匹配', result: '失败', raw: e.message });
1485
+ }
1486
+ }
1487
+
1488
+ const lines = ['🔍 docKey 诊断报告', '', `输入: ${input}`, `operatorId: ${opId}`, ''];
1489
+ steps.forEach(s => {
1490
+ lines.push(`--- ${s.step} ---`);
1491
+ lines.push(`结果: ${s.result}`);
1492
+ if (s.raw) {
1493
+ lines.push(`详情: ${JSON.stringify(s.raw, null, 2)}`);
1494
+ }
1495
+ lines.push('');
1496
+ });
1497
+ lines.push('💡 提示: 如果 wiki/nodes API 成功并返回了 docKey,请使用该 docKey 调用 get_wiki_doc_content');
1498
+ lines.push(' 如果 doc metadata API 成功,说明该 docKey 在 doc suite 中有效');
1499
+ lines.push(' 如果 blocks API 失败但 overwriteContent 成功,问题在 blocks API 端');
1500
+ lines.push(' 如果所有 API 都失败,说明该 docKey 在 doc suite 中不存在');
1501
+ if (!workspaceId) {
1502
+ lines.push(' 建议提供 workspace_id 参数以启用搜索回退路径');
1503
+ }
1504
+
1505
+ return { content: [{ type: 'text', text: lines.join('\n') }] };
1506
+ }
1507
+
1289
1508
  case 'create_wiki_doc': {
1290
1509
  const { workspace_id, parent_node_id, name, doc_type = 'DOC', operator_id } = args;
1291
1510
  if (operator_id) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dingtalk-wiki",
3
- "version": "1.2.7",
3
+ "version": "1.2.8",
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": {