dingtalk-wiki 1.2.7 → 1.2.9

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 +235 -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,219 @@ 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
+ let wikiNodeResponse = null;
1331
+ let wikiWsId = null;
1332
+ try {
1333
+ const nodeRes = await axios({
1334
+ method: 'GET',
1335
+ url: `${DINGTALK_API_V2}/v2.0/wiki/nodes/${input}`,
1336
+ headers: { 'x-acs-dingtalk-access-token': token },
1337
+ params: { operatorId: opId }
1338
+ });
1339
+ wikiNodeResponse = nodeRes.data;
1340
+ wikiWsId = wikiNodeResponse.workspaceId || wikiNodeResponse.node?.workspaceId || null;
1341
+ const node = wikiNodeResponse;
1342
+ steps.push({
1343
+ step: 'wiki/nodes API',
1344
+ result: '成功',
1345
+ raw: node
1346
+ });
1347
+ } catch (e) {
1348
+ steps.push({
1349
+ step: 'wiki/nodes API',
1350
+ result: '失败',
1351
+ raw: { message: e.response?.data?.message || e.message, code: e.response?.data?.code || '(无)', status: e.response?.status || '(无)' }
1352
+ });
1353
+ }
1354
+
1355
+ const effectiveWsId = workspaceId || wikiWsId;
1356
+
1357
+ // Step 3: doc metadata API (GET /v1.0/doc/suites/documents/{docKey})
1358
+ try {
1359
+ const metaRes = await axios({
1360
+ method: 'GET',
1361
+ url: `${DINGTALK_API_V2}/v1.0/doc/suites/documents/${input}`,
1362
+ headers: { 'x-acs-dingtalk-access-token': token },
1363
+ params: { operatorId: opId }
1364
+ });
1365
+ steps.push({
1366
+ step: 'doc metadata API (GET /documents/{docKey})',
1367
+ result: '成功',
1368
+ raw: metaRes.data
1369
+ });
1370
+ } catch (e) {
1371
+ steps.push({
1372
+ step: 'doc metadata API (GET /documents/{docKey})',
1373
+ result: '失败',
1374
+ raw: { message: e.response?.data?.message || e.message, code: e.response?.data?.code || '(无)', status: e.response?.status || '(无)' }
1375
+ });
1376
+ }
1377
+
1378
+ // Step 4: blocks API
1379
+ try {
1380
+ const blocksRes = await axios({
1381
+ method: 'GET',
1382
+ url: `${DINGTALK_API_V2}/v1.0/doc/suites/documents/${input}/blocks`,
1383
+ headers: { 'x-acs-dingtalk-access-token': token },
1384
+ params: { operatorId: opId }
1385
+ });
1386
+ const blockCount = blocksRes.data?.result?.data?.length || 0;
1387
+ steps.push({
1388
+ step: 'blocks API',
1389
+ result: `成功 (${blockCount} 个 block)`,
1390
+ raw: blocksRes.data
1391
+ });
1392
+ } catch (e) {
1393
+ steps.push({
1394
+ step: 'blocks API',
1395
+ result: '失败',
1396
+ raw: { message: e.response?.data?.message || e.message, code: e.response?.data?.code || '(无)', status: e.response?.status || '(无)' }
1397
+ });
1398
+ }
1399
+
1400
+ // Step 5: overwriteContent API (test with empty content — don't actually write)
1401
+ try {
1402
+ const overwriteRes = await axios({
1403
+ method: 'POST',
1404
+ url: `${DINGTALK_API_V2}/v1.0/doc/suites/documents/${input}/overwriteContent`,
1405
+ headers: { 'x-acs-dingtalk-access-token': token, 'Content-Type': 'application/json' },
1406
+ params: { operatorId: opId },
1407
+ data: { content: 'test', contentType: 'markdown' }
1408
+ });
1409
+ steps.push({
1410
+ step: 'overwriteContent API',
1411
+ result: '成功(可写入)',
1412
+ raw: overwriteRes.data
1413
+ });
1414
+ } catch (e) {
1415
+ steps.push({
1416
+ step: 'overwriteContent API',
1417
+ result: '失败',
1418
+ raw: { message: e.response?.data?.message || e.message, code: e.response?.data?.code || '(无)', status: e.response?.status || '(无)' }
1419
+ });
1420
+ }
1421
+
1422
+ // Step 6: search docs API (v1.0/doc/docs) — 需要 workspaceId
1423
+ if (effectiveWsId) {
1424
+ try {
1425
+ const searchRes = await axios({
1426
+ method: 'GET',
1427
+ url: `${DINGTALK_API_V2}/v1.0/doc/docs`,
1428
+ headers: { 'x-acs-dingtalk-access-token': token },
1429
+ params: { operatorId: opId, workspaceId: effectiveWsId, maxResults: 50 }
1430
+ });
1431
+ const docs = searchRes.data?.docs || [];
1432
+ const matched = docs.find(d => d.nodeBO?.nodeId === input || d.nodeBO?.dentryUuid === input || d.docKey === input || d.nodeBO?.dentryId === input);
1433
+ steps.push({
1434
+ step: `doc search API (workspace=${effectiveWsId})`,
1435
+ result: `成功 (${docs.length} 篇文档)` + (matched ? ', 找到匹配!' : ', 未找到匹配'),
1436
+ raw: matched || `共 ${docs.length} 篇,第一篇 docKey=${docs[0]?.docKey || '(无)'}`
1437
+ });
1438
+ } catch (e) {
1439
+ steps.push({
1440
+ step: `doc search API (workspace=${effectiveWsId})`,
1441
+ result: '失败',
1442
+ raw: { message: e.response?.data?.message || e.message, code: e.response?.data?.code || '(无)', status: e.response?.status || '(无)' }
1443
+ });
1444
+ }
1445
+ } else {
1446
+ steps.push({ step: 'doc search API', result: '跳过(无法获取 workspace_id)' });
1447
+ }
1448
+
1449
+ // Step 7: 在知识库目录中查找此节点 + 查看是否有 docKey 字段
1450
+ if (effectiveWsId) {
1451
+ try {
1452
+ const dirRes = await axios({
1453
+ method: 'GET',
1454
+ url: `${DINGTALK_API_V2}/v2.0/doc/spaces/${effectiveWsId}/directories`,
1455
+ headers: { 'x-acs-dingtalk-access-token': token },
1456
+ params: { operatorId: opId, maxResults: 500 }
1457
+ });
1458
+ const children = dirRes.data?.children || [];
1459
+ const match = children.find(c => c.dentryUuid === input || c.dentryId === input || c.id === input);
1460
+ // Show first few children's available fields so user can see what fields exist
1461
+ const sampleKeys = children.length > 0 ? Object.keys(children[0]) : [];
1462
+ steps.push({
1463
+ step: `知识库目录 (workspace=${effectiveWsId})`,
1464
+ result: `${children.length} 个子节点` + (match ? ', 找到匹配!' : ', 未找到匹配'),
1465
+ raw: match ? match : { sampleFields: sampleKeys, firstChildSample: children[0] || null }
1466
+ });
1467
+ } catch (e) {
1468
+ steps.push({
1469
+ step: `知识库目录 (workspace=${effectiveWsId})`,
1470
+ result: '失败',
1471
+ raw: { message: e.response?.data?.message || e.message, code: e.response?.data?.code || '(无)', status: e.response?.status || '(无)' }
1472
+ });
1473
+ }
1474
+ }
1475
+
1476
+ // Step 8: 用 workspaceId 再次尝试 overwriteContent(确认文档套件是否完全不可用)
1477
+ if (effectiveWsId) {
1478
+ try {
1479
+ const overwriteRes = await axios({
1480
+ method: 'POST',
1481
+ url: `${DINGTALK_API_V2}/v1.0/doc/suites/documents/${input}/overwriteContent`,
1482
+ headers: { 'x-acs-dingtalk-access-token': token, 'Content-Type': 'application/json' },
1483
+ params: { operatorId: opId },
1484
+ data: { content: 'test', contentType: 'markdown' }
1485
+ });
1486
+ steps.push({
1487
+ step: 'overwriteContent(带 workspaceId)',
1488
+ result: '成功',
1489
+ raw: overwriteRes.data
1490
+ });
1491
+ } catch (e) {
1492
+ steps.push({
1493
+ step: 'overwriteContent(带 workspaceId)',
1494
+ result: '失败',
1495
+ raw: { message: e.response?.data?.message || e.message, code: e.response?.data?.code || '(无)', status: e.response?.status || '(无)' }
1496
+ });
1497
+ }
1498
+ }
1499
+
1500
+ const lines = ['🔍 docKey 诊断报告', '', `输入: ${input}`, `operatorId: ${opId}`, ''];
1501
+ steps.forEach(s => {
1502
+ lines.push(`--- ${s.step} ---`);
1503
+ lines.push(`结果: ${s.result}`);
1504
+ if (s.raw) {
1505
+ lines.push(`详情: ${JSON.stringify(s.raw, null, 2)}`);
1506
+ }
1507
+ lines.push('');
1508
+ });
1509
+ lines.push('💡 提示:');
1510
+ lines.push(' - wiki/nodes API 成功但无 docKey → 此文档在 doc suite 中没有关联的 docKey');
1511
+ lines.push(' - docKey 是 create_wiki_doc 返回的独立 ID,与 nodeId/dentryUuid 不同');
1512
+ lines.push(' - 对于已有文档(非新建),doc suite API 可能完全不支持');
1513
+ lines.push(' - overwriteContent 也失败 → 不是 blocks 特有的问题');
1514
+ if (effectiveWsId) {
1515
+ lines.push(` - 自动检测到 workspaceId: ${effectiveWsId}`);
1516
+ }
1517
+ if (workspaceId) {
1518
+ lines.push(' - 已提供 workspaceId: ' + workspaceId);
1519
+ }
1520
+
1521
+ return { content: [{ type: 'text', text: lines.join('\n') }] };
1522
+ }
1523
+
1289
1524
  case 'create_wiki_doc': {
1290
1525
  const { workspace_id, parent_node_id, name, doc_type = 'DOC', operator_id } = args;
1291
1526
  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.9",
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": {