dingtalk-mcp 1.1.9 → 1.1.10

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/README.md CHANGED
@@ -1,7 +1,6 @@
1
1
  # 钉钉MCP Server
2
2
 
3
3
 
4
-
5
4
  ## 🚀 功能特性
6
5
  - 钉钉通讯录
7
6
  - 钉钉部门管理
@@ -13,8 +12,6 @@
13
12
  - 钉钉工作通知
14
13
  - 钉钉应用管理
15
14
  - 钉钉服务窗
16
- - 钉钉项目管理
17
- - 钉钉日志
18
15
 
19
16
  ## 如何使用
20
17
  ```json
@@ -40,20 +37,18 @@
40
37
  2. DINGTALK_Client_Secret
41
38
  3. ACTIVE_PROFILES,激活哪些钉钉MCP服务,逗号风格,如果是ALL则激活全部。可选集合
42
39
 
43
- | ProfileId | Description | Permission |
44
- |-----------------------------|--------------------|--------------------------------------------------|
45
- | dingtalk-contacts | 钉钉通讯录,默认激活 | qyapi_addresslist_search qyapi_get_member
46
- | dingtalk-department | 钉钉部门管理 |qyapi_get_department_list qyapi_get_department_member
47
- | dingtalk-robot-send-message | 钉钉机器人发消息/DING,默认激活 | 需要企业内机器人发送消息权限 <br/>Premium.Ding.Write |
48
- | dingtalk-honor | 钉钉企业文化荣誉 |OrgCulture.Honor.Read OrgCulture.Honor.Read
49
- | dingtalk-tasks | 钉钉待办 | Todo.Todo.Write<br>Todo.Todo.Read |
50
- | dingtalk-calendar | 钉钉日程 |Calendar.Event.Write Calendar.Event.Read Calendar.EventSchedule.Read
51
- | dingtalk-checkin | 钉钉签到 |qyapi_checkin_read
52
- | dingtalk-notice | 钉钉工作通知 |
53
- | dingtalk-app-manage | 钉钉应用管理 | qyapi_microapp_manage<br>qyapi_get_microapp_list |
54
- | dingtalk-service-window | 钉钉服务窗 | OfficialAccount.Message.Send OfficialAccount.Contact.Read OfficialAccount.Account.Read |
55
- | dingtalk-teambition | 钉钉项目管理 | Project.Project.Write.All Project.Project.Read.All Project.Task.Write.All Project.Task.Read.All |
56
- | dingtalk-report | 钉钉日志 | qyapi_report_statistics qyapi_report_manage qyapi_report_query|
40
+ | ProfileId | Description | Permission |
41
+ |--------------------------|--------------------|-------------------------------------------------|
42
+ | dingtalk-contacts | 钉钉通讯录,默认激活 |
43
+ | dingtalk-department | 钉钉部门管理 |
44
+ | dingtalk-robot-send-message | 钉钉机器人发消息/DING,默认激活 | 需要企业内机器人发送消息权限 |
45
+ | dingtalk-honor | 钉钉企业文化荣誉 |
46
+ | dingtalk-tasks | 钉钉待办 | Todo.Todo.WriteTodo.Todo.Read |
47
+ | dingtalk-calendar | 钉钉日程 |
48
+ | dingtalk-checkin | 钉钉签到 |
49
+ | dingtalk-notice | 钉钉工作通知 |
50
+ | dingtalk-app-manage | 钉钉应用管理 | qyapi_microapp_manageqyapi_get_microapp_list |
51
+ | dingtalk-service-window | 钉钉服务窗 | |
57
52
 
58
53
  4. ROBOT_CODE,用于发消息/DING的机器人Code
59
54
  5. ROBOT_ACCESS_TOKEN,群自定义机器人ACCESS_TOKEN,用于自定义机器人发消息
@@ -69,7 +64,6 @@
69
64
 
70
65
  ## 📞 支持
71
66
 
72
- - 帮助文档: https://open.dingtalk.com/document/ai-dev/dingtalk-server-api-mcp-overview
73
67
  - 钉钉开放平台: https://open.dingtalk.com
74
68
  - MCP协议: https://modelcontextprotocol.io
75
69
  - 欢迎加入钉钉MCP交流群
@@ -8,6 +8,9 @@ server:
8
8
  in: header
9
9
  name: x-acs-dingtalk-access-token
10
10
  tools:
11
+ - name: currentDateTime
12
+ description: 获取当前日期和事件
13
+
11
14
  - name: searchUser
12
15
  description: 根据姓名搜索钉钉用户的userId。
13
16
  args:
@@ -0,0 +1,404 @@
1
+ server:
2
+ name: dingtalk-notable
3
+ description: 钉钉AI表格/多维表
4
+ tools:
5
+ - name: notableSupportedFieldInfo
6
+ description: AI表格/多维表支持的字段类型和额外属性
7
+ requestTemplate:
8
+ type: file
9
+ url: /resources/notable-field-property.md
10
+
11
+ - name: notableRecordValuesFormat
12
+ description: AI表格/多维表记录值格式
13
+ requestTemplate:
14
+ type: file
15
+ url: /resources/notable-record-values.md
16
+
17
+
18
+ - name: queryNotables
19
+ description: 根据名称查询AI表格/多维表
20
+ requestTemplate:
21
+ method: POST
22
+ url: https://api.dingtalk.com/v2.0/storage/dentries/search?operatorId=String
23
+ args:
24
+ - name: operatorId
25
+ description: 操作人的unionId
26
+ type: string
27
+ required: true
28
+ position: query
29
+ - name: keyword
30
+ description: AI表格/多维表名称
31
+ type: string
32
+ required: true
33
+ position: body
34
+ - name: option
35
+ description: 查询参数
36
+ type: object
37
+ required: true
38
+ position: body
39
+ items:
40
+ type: object
41
+ required:
42
+ - dentryCategories
43
+ - creatorIds
44
+ properties:
45
+ dentryCategories:
46
+ type: array
47
+ enum: [ "alidoc" ]
48
+ description: 文件类别
49
+ items:
50
+ type: string
51
+ creatorIds:
52
+ type: array
53
+ description: 创建者的userId
54
+ items:
55
+ type: string
56
+ nextToken:
57
+ type: string
58
+ description: 分页游标,第一次查询不需要此值,第二次查询需要,从本接口的返回值nextToken获取,如果返回值为空则说明没有更多数据
59
+ maxResults:
60
+ type: string
61
+ description: 分页大小,默认值10
62
+ # 数据表
63
+ - name: getNotableSheet
64
+ description: 获取AI表格/多维表的单个数据表
65
+ requestTemplate:
66
+ method: GET
67
+ url: https://api.dingtalk.com/v1.0/notable/bases/{baseId}/sheets/{sheetIdOrName}?operatorId=String
68
+ args:
69
+ - name: operatorId
70
+ description: 操作人的unionId
71
+ type: string
72
+ required: true
73
+ position: query
74
+ - name: baseId
75
+ description: AI表格/多维表ID,从「根据名称查询AI表格/多维表」Tool中的结果字段「dentryUuid」获取。
76
+ type: string
77
+ required: true
78
+ position: query
79
+ - name: sheetIdOrName
80
+ description: 数据表ID或数据表名称。数据表ID可以通过调用「getNotableAllSheets」获取
81
+ type: string
82
+ required: true
83
+ position: path
84
+
85
+ - name: getNotableAllSheets
86
+ description: 获取AI表格/多维表的所有数据表
87
+ requestTemplate:
88
+ method: GET
89
+ url: https://api.dingtalk.com/v1.0/notable/bases/{baseId}/sheets?operatorId=String
90
+ args:
91
+ - name: operatorId
92
+ description: 操作人的unionId
93
+ type: string
94
+ required: true
95
+ position: query
96
+ - name: baseId
97
+ description: AI表格/多维表ID,从「根据名称查询AI表格/多维表」Tool中的结果字段「dentryUuid」获取。
98
+ type: string
99
+ required: true
100
+ position: query
101
+
102
+ - name: updateNotableSheetName
103
+ description: 更新AI表格/多维表的单个数据表的名称
104
+ requestTemplate:
105
+ method: PUT
106
+ url: https://api.dingtalk.com/v1.0/notable/bases/{baseId}/sheets/{sheetIdOrName}?operatorId=String
107
+ args:
108
+ - name: operatorId
109
+ description: 操作人的unionId
110
+ type: string
111
+ required: true
112
+ position: query
113
+ - name: baseId
114
+ description: AI表格/多维表ID,从「根据名称查询AI表格/多维表」Tool中的结果字段「dentryUuid」获取。
115
+ type: string
116
+ required: true
117
+ position: query
118
+ - name: sheetIdOrName
119
+ description: 数据表ID或数据表名称。数据表ID可以通过调用「getNotableAllSheets」获取
120
+ type: string
121
+ required: true
122
+ position: path
123
+ - name: name
124
+ description: 数据表名称
125
+ type: string
126
+ required: true
127
+ position: body
128
+
129
+ - name: createNotableSheet
130
+ description: 创建AI表格/多维表的数据表
131
+ requestTemplate:
132
+ method: POST
133
+ url: https://api.dingtalk.com/v1.0/notable/bases/{baseId}/sheets?operatorId=String
134
+ args:
135
+ - name: operatorId
136
+ description: 操作人的unionId
137
+ type: string
138
+ required: true
139
+ position: query
140
+ - name: baseId
141
+ description: AI表格/多维表ID,从「根据名称查询AI表格/多维表」Tool中的结果字段「dentryUuid」获取。
142
+ type: string
143
+ required: true
144
+ position: query
145
+ - name: name
146
+ description: 数据表ID或数据表名称。
147
+ type: string
148
+ required: true
149
+ position: body
150
+ - name: fields
151
+ type: array
152
+ required: true
153
+ position: body
154
+ items:
155
+ type: object
156
+ properties:
157
+ name:
158
+ type: string
159
+ description: 字段名称
160
+ type:
161
+ type: string
162
+ description: 字段类型,参考「notableSupportedFieldInfo」Tool返回结果中的[类型]。
163
+ property:
164
+ type: Map<String, Any>
165
+ description: 字段属性,具体格式请参考「notableSupportedFieldInfo」Tool返回结果中的[属性]。
166
+
167
+
168
+ - name: deleteNotableSheet
169
+ description: 删除AI表格/多维表的单个数据表
170
+ requestTemplate:
171
+ method: DELETE
172
+ url: https://api.dingtalk.com/v1.0/notable/bases/{baseId}/sheets/{sheetIdOrName}?operatorId=String
173
+ args:
174
+ - name: operatorId
175
+ description: 操作人的unionId
176
+ type: string
177
+ required: true
178
+ position: query
179
+ - name: baseId
180
+ description: AI表格/多维表ID,从「根据名称查询AI表格/多维表」Tool中的结果字段「dentryUuid」获取。
181
+ type: string
182
+ required: true
183
+ position: query
184
+ - name: sheetIdOrName
185
+ description: 数据表ID或数据表名称。数据表ID可以通过调用「getNotableAllSheets」获取
186
+ type: string
187
+ required: true
188
+ position: path
189
+
190
+ # 记录
191
+ - name: listNotableRecords
192
+ description: 获取AI表格/多维表里指定数据表的多行记录
193
+ requestTemplate:
194
+ method: POST
195
+ url: https://api.dingtalk.com/v1.0/notable/bases/{baseId}/sheets/{sheetIdOrName}/records/list?operatorId=String
196
+ args:
197
+ - name: operatorId
198
+ description: 操作人的unionId
199
+ type: string
200
+ required: true
201
+ position: query
202
+ - name: baseId
203
+ description: AI表格/多维表ID,从「根据名称查询AI表格/多维表」Tool中的结果字段「dentryUuid」获取。
204
+ type: string
205
+ required: true
206
+ position: path
207
+ - name: sheetIdOrName
208
+ description: 数据表ID或数据表名称。数据表ID可以通过调用「getNotableAllSheets」获取
209
+ type: string
210
+ required: true
211
+ position: path
212
+ - name: maxResults
213
+ description: 每页记录数,最大100,默认100
214
+ type: integer
215
+ required: false
216
+ position: body
217
+ - name: nextToken
218
+ description: 分页游标
219
+ type: string
220
+ required: false
221
+ position: body
222
+ - name: filter
223
+ description: 过滤条件
224
+ type: object
225
+ required: true
226
+ position: body
227
+ items:
228
+ type: object
229
+ properties:
230
+ combination:
231
+ type: string
232
+ description: 条件组合方式,可选值:
233
+ || and:同时满足所有条件
234
+ || or:满足任一条件
235
+ conditions:
236
+ type: array
237
+ items:
238
+ type: object
239
+ properties:
240
+ field:
241
+ type: string
242
+ description: 字段ID或字段名。
243
+ operator:
244
+ type: string
245
+ description: 条件类型:equal | notEqual | greater | greaterEqual | less | lessEqual | contain | notContain | empty | notEmpty。
246
+ value:
247
+ type: string
248
+ description: 字段值
249
+ required:
250
+ - field
251
+ - operator
252
+ - value
253
+
254
+ - name: getNotableRecord
255
+ description: 获取AI表格/多维表里指定数据表的单行记录
256
+ requestTemplate:
257
+ method: GET
258
+ url: https://api.dingtalk.com/v1.0/notable/bases/{baseId}/sheets/{sheetIdOrName}/records/{recordId}?operatorId=String
259
+ args:
260
+ - name: operatorId
261
+ description: 操作人的unionId
262
+ type: string
263
+ required: true
264
+ position: query
265
+ - name: baseId
266
+ description: AI表格/多维表ID,从「根据名称查询AI表格/多维表」Tool中的结果字段「dentryUuid」获取。
267
+ type: string
268
+ required: true
269
+ position: path
270
+ - name: sheetIdOrName
271
+ description: 数据表ID或数据表名称。数据表ID可以通过调用「getNotableAllSheets」获取
272
+ type: string
273
+ required: true
274
+ position: path
275
+ - name: recordId
276
+ description: 记录ID
277
+ type: string
278
+ required: true
279
+ position: path
280
+
281
+ - name: deleteNotableRecords
282
+ description: 删除AI表格/多维表里指定数据表的多行记录
283
+ requestTemplate:
284
+ method: POST
285
+ url: https://api.dingtalk.com/v1.0/notable/bases/{baseId}/sheets/{sheetIdOrName}/records/delete?operatorId=String
286
+ args:
287
+ - name: operatorId
288
+ description: 操作人的unionId
289
+ type: string
290
+ required: true
291
+ position: query
292
+ - name: baseId
293
+ description: AI表格/多维表ID,从「根据名称查询AI表格/多维表」Tool中的结果字段「dentryUuid」获取。
294
+ type: string
295
+ required: true
296
+ position: path
297
+ - name: sheetIdOrName
298
+ description: 数据表ID或数据表名称。数据表ID可以通过调用「getNotableAllSheets」获取
299
+ type: string
300
+ required: true
301
+ position: path
302
+ - name: recordIds
303
+ description: 记录ID数组
304
+ type: array
305
+ required: true
306
+ position: body
307
+ items:
308
+ type: string
309
+
310
+ - name: insertNotableRecords
311
+ description: 在AI表格/多维表里指定数据表中新增行记录
312
+ requestTemplate:
313
+ method: POST
314
+ url: https://api.dingtalk.com/v1.0/notable/bases/{baseId}/sheets/{sheetIdOrName}/records?operatorId=String
315
+ args:
316
+ - name: operatorId
317
+ description: 操作人的unionId
318
+ type: string
319
+ required: true
320
+ position: query
321
+ - name: baseId
322
+ description: AI表格/多维表ID,从「根据名称查询AI表格/多维表」Tool中的结果字段「dentryUuid」获取。
323
+ type: string
324
+ required: true
325
+ position: path
326
+ - name: sheetIdOrName
327
+ description: 数据表ID或数据表名称。数据表ID可以通过调用「getNotableAllSheets」获取
328
+ type: string
329
+ required: true
330
+ position: path
331
+ - name: records
332
+ description: 记录数组
333
+ type: array
334
+ required: true
335
+ position: body
336
+ items:
337
+ type: object
338
+ description: 该行记录中各字段的值,具体格式请参考「notableRecordValuesFormat」Tool的结果。
339
+ properties:
340
+ fields:
341
+ type: object
342
+ description: 字段键值对,key为字段ID,value为字段设置值,格式需参考「notableRecordValuesFormat」Tool的结果。
343
+ additionalProperties:
344
+ type: [ string, number, boolean, object, array] # 值可以是这些类型中的任意一种
345
+
346
+ # 字段
347
+ - name: getNotableAllFields
348
+ description: 获取AI表格/多维表里指定数据表的所有字段
349
+ requestTemplate:
350
+ method: GET
351
+ url: https://api.dingtalk.com/v1.0/notable/bases/{baseId}/sheets/{sheetIdOrName}/fields?operatorId=String
352
+ args:
353
+ - name: operatorId
354
+ description: 操作人的unionId
355
+ type: string
356
+ required: true
357
+ position: query
358
+ - name: baseId
359
+ description: AI表格/多维表ID,从「根据名称查询AI表格/多维表」Tool中的结果字段「dentryUuid」获取。
360
+ type: string
361
+ required: true
362
+ position: path
363
+ - name: sheetIdOrName
364
+ description: 数据表ID或数据表名称。数据表ID可以通过调用「getNotableAllSheets」获取
365
+ type: string
366
+ required: true
367
+ position: path
368
+
369
+ - name: createNotableField
370
+ description: 在AI表格/多维表里指定数据表中创建字段
371
+ requestTemplate:
372
+ method: POST
373
+ url: https://api.dingtalk.com/v1.0/notable/bases/{baseId}/sheets/{sheetIdOrName}/fields?operatorId=String
374
+ args:
375
+ - name: operatorId
376
+ description: 操作人的unionId
377
+ type: string
378
+ required: true
379
+ position: query
380
+ - name: baseId
381
+ description: AI表格/多维表ID,从「根据名称查询AI表格/多维表」Tool中的结果字段「dentryUuid」获取。
382
+ type: string
383
+ required: true
384
+ position: path
385
+ - name: sheetIdOrName
386
+ description: 数据表ID或数据表名称。数据表ID可以通过调用「getNotableAllSheets」获取
387
+ type: string
388
+ required: true
389
+ position: path
390
+ - name: name
391
+ description: 字段名称
392
+ type: string
393
+ required: true
394
+ position: body
395
+ - name: type
396
+ description: 字段类型,需要参考「notableSupportedFieldInfo」Tool的内容
397
+ type: string
398
+ required: true
399
+ position: body
400
+ - name: property
401
+ description: 字段属性,,需要参考「notableSupportedFieldInfo」Tool的对不同类型字段的属性描述。
402
+ type: object
403
+ required: false
404
+ position: body
@@ -154,7 +154,7 @@ tools:
154
154
  保存日志内容供后续编辑和发送(草稿功能)。
155
155
 
156
156
  此接口用于保存日志内容但不立即发送,可以后续在钉钉中编辑和发送。
157
- contents参数格式与createLog相同,必须包含完整的字段结构。
157
+ contents参数格式与「createReport」相同,必须包含完整的字段结构。
158
158
 
159
159
  【使用场景】
160
160
  - 需要保存日志但暂不发送
@@ -1,17 +1,22 @@
1
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
- import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
4
- import { ServiceWindowMessageBuilder } from './utils/messageBuilder.js'
5
- import { LogMessageBuilder } from './utils/logBuilder.js'
1
+ import {Server} from '@modelcontextprotocol/sdk/server/index.js';
2
+ import {StdioServerTransport} from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import {CallToolRequestSchema, ListToolsRequestSchema} from '@modelcontextprotocol/sdk/types.js';
4
+ import {ServiceWindowMessageBuilder} from './utils/messageBuilder.js'
5
+ import {LogMessageBuilder} from './utils/logBuilder.js'
6
+ import {LocalTools} from './utils/localTools.js'
6
7
  import axios from 'axios';
7
8
  import * as yaml from 'js-yaml';
8
9
  import fs from 'fs';
9
10
  import path from 'path';
10
- import { fileURLToPath } from 'url';
11
+ import {fileURLToPath} from 'url';
11
12
  import {ifError} from "node:assert";
12
13
  import {resolveObjectURL} from "node:buffer";
14
+
13
15
  const __filename = fileURLToPath(import.meta.url);
14
16
  const __dirname = path.dirname(__filename);
17
+
18
+
19
+
15
20
  export class DingTalkMCPServer {
16
21
  constructor() {
17
22
  this.accessToken = null;
@@ -26,7 +31,7 @@ export class DingTalkMCPServer {
26
31
  this.tokenCacheFile = path.join(__dirname, '..', '.dingtalk_token_cache.json');
27
32
  this.loadConfig(path.join(__dirname, '..'));
28
33
  // 测试状态
29
- if (process.env.STAGING){
34
+ if (process.env.STAGING) {
30
35
  this.loadConfig(path.join(__dirname, '../staging'));
31
36
  }
32
37
  this.server = new Server({
@@ -40,12 +45,13 @@ export class DingTalkMCPServer {
40
45
  this.loadTokenCache();
41
46
  this.setupHandlers();
42
47
  }
48
+
43
49
  loadConfig(dirname) {
44
50
  try {
45
51
  //激活的profile
46
52
  const profiles = process.env.ACTIVE_PROFILES;
47
53
  let profiles_list = [];
48
- if (profiles){
54
+ if (profiles) {
49
55
  profiles_list = profiles.split(',');
50
56
  }
51
57
 
@@ -64,7 +70,7 @@ export class DingTalkMCPServer {
64
70
 
65
71
  (config.tools || []).forEach(tool => {
66
72
  if (this.tools.includes(tool.name)) {
67
- throw new Error('Dulipict tool name: '+ tool.name);
73
+ throw new Error('Dulipict tool name: ' + tool.name);
68
74
  }
69
75
  this.tools.push(tool);
70
76
  });
@@ -75,18 +81,19 @@ export class DingTalkMCPServer {
75
81
 
76
82
  }
77
83
  );
84
+
78
85
  if (this.tools && this.tools.length > 0) {
79
86
  console.error(`Loaded ${this.tools.length} tools from config`);
80
- console.error(`Tools:\r\n${this.tools.map(t => t.name + ', '+ t.requestTemplate.method+' ' + t.requestTemplate.url + ', ' + t.description).join('\r\n')}`);
87
+ console.error(`Tools:\r\n${this.tools.map(t => t.name + ', ' + t.description).join('\r\n')}`);
81
88
  }
82
- }
83
- catch (error) {
89
+ } catch (error) {
84
90
  const err = error;
85
91
  console.error('Failed to load config:', err.message);
86
92
  this.tools = [];
87
93
  throw error;
88
94
  }
89
95
  }
96
+
90
97
  /**
91
98
  * 加载本地缓存的access_token
92
99
  */
@@ -101,22 +108,20 @@ export class DingTalkMCPServer {
101
108
  this.accessToken = cacheData.access_token;
102
109
  console.error('Loaded valid access token from cache');
103
110
  console.error(`Token expires at: ${new Date(cacheData.expires_at).toISOString()}`);
104
- }
105
- else {
111
+ } else {
106
112
  console.error('Cached token expired, will refresh when needed');
107
113
  this.clearTokenCache();
108
114
  }
109
- }
110
- else {
115
+ } else {
111
116
  console.error('No token cache found');
112
117
  }
113
- }
114
- catch (error) {
118
+ } catch (error) {
115
119
  const err = error;
116
120
  console.error('Failed to load token cache:', err.message);
117
121
  this.clearTokenCache();
118
122
  }
119
123
  }
124
+
120
125
  /**
121
126
  * 检查token缓存是否有效(未过期)
122
127
  */
@@ -129,6 +134,7 @@ export class DingTalkMCPServer {
129
134
  const now = Date.now();
130
135
  return now < (cacheData.expires_at - bufferTime);
131
136
  }
137
+
132
138
  /**
133
139
  * 保存access_token到本地缓存
134
140
  */
@@ -146,12 +152,12 @@ export class DingTalkMCPServer {
146
152
  fs.writeFileSync(this.tokenCacheFile, JSON.stringify(this.tokenCacheData, null, 2));
147
153
  console.error('Access token saved to cache');
148
154
  console.error(`Token expires at: ${new Date(expiresAt).toISOString()}`);
149
- }
150
- catch (error) {
155
+ } catch (error) {
151
156
  const err = error;
152
157
  console.error('Failed to save token cache:', err.message);
153
158
  }
154
159
  }
160
+
155
161
  /**
156
162
  * 清除token缓存
157
163
  */
@@ -162,12 +168,12 @@ export class DingTalkMCPServer {
162
168
  console.error('Token cache cleared');
163
169
  }
164
170
  this.tokenCacheData = null;
165
- }
166
- catch (error) {
171
+ } catch (error) {
167
172
  const err = error;
168
173
  console.error('Failed to clear token cache:', err.message);
169
174
  }
170
175
  }
176
+
171
177
  /**
172
178
  * 获取有效的access_token(优先使用缓存)
173
179
  */
@@ -185,6 +191,7 @@ export class DingTalkMCPServer {
185
191
  console.error('Token cache invalid or missing, refreshing...');
186
192
  return await this.refreshAccessToken();
187
193
  }
194
+
188
195
  async refreshAccessToken() {
189
196
  if (!this.appId || !this.appSecret) {
190
197
  throw new Error('DINGTALK_APP_ID and DINGTALK_APP_SECRET are required');
@@ -205,26 +212,24 @@ export class DingTalkMCPServer {
205
212
  this.saveTokenCache(this.accessToken, expiresIn);
206
213
  console.error('Access token refreshed successfully');
207
214
  return this.accessToken;
208
- }
209
- else {
215
+ } else {
210
216
  throw new Error(`Token refresh failed: ${response.data.errmsg} (errcode: ${response.data.errcode})`);
211
217
  }
212
- }
213
- catch (error) {
218
+ } catch (error) {
214
219
  // 刷新失败时清除缓存
215
220
  this.clearTokenCache();
216
221
  if (axios.isAxiosError(error) && error.response) {
217
222
  throw new Error(`Failed to refresh access token: HTTP ${error.response.status} - ${JSON.stringify(error.response.data)}`);
218
- }
219
- else {
223
+ } else {
220
224
  const err = error;
221
225
  throw new Error(`Failed to refresh access token: ${err.message}`);
222
226
  }
223
227
  }
224
228
  }
229
+
225
230
  setupHandlers() {
226
231
  // 列出可用工具
227
- if (this.debug){
232
+ if (this.debug) {
228
233
  const tools = this.tools.map(tool => ({
229
234
  name: tool.name,
230
235
  description: tool.description,
@@ -248,19 +253,22 @@ export class DingTalkMCPServer {
248
253
  }
249
254
  }));
250
255
 
251
- return { tools };
256
+ return {tools};
252
257
  });
253
258
  // 执行工具调用
254
259
  this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
255
- const { name, arguments: args } = request.params;
260
+ const {name, arguments: args} = request.params;
261
+
256
262
  const result = await this.executeTool(name, args || {});
257
263
  // 转换为 MCP SDK 期望的格式
258
264
  return {
259
265
  content: result.content,
260
266
  isError: result.isError
261
267
  };
268
+
262
269
  });
263
270
  }
271
+
264
272
  generateSchema(args) {
265
273
  const schema = {};
266
274
  args.forEach(arg => {
@@ -276,12 +284,13 @@ export class DingTalkMCPServer {
276
284
  type: arg.type,
277
285
  description: arg.description
278
286
  };
279
- if ((arg.type === 'array' || arg.type === 'object' ) && arg.items) {
287
+ if ((arg.type === 'array' || arg.type === 'object') && arg.items) {
280
288
  schema[arg.name].items = arg.items;
281
289
  }
282
290
  });
283
291
  return schema;
284
292
  }
293
+
285
294
  async executeTool(toolName, args) {
286
295
  const tool = this.tools.find(t => t.name === toolName);
287
296
  if (!tool) {
@@ -294,6 +303,15 @@ export class DingTalkMCPServer {
294
303
  };
295
304
  }
296
305
  try {
306
+ if (LocalTools.isLocalTool(toolName)){
307
+ return {
308
+ content: [{
309
+ type: 'text',
310
+ text: JSON.stringify(LocalTools.callTool(tool), null, 2)
311
+ }]
312
+ };
313
+ }
314
+
297
315
  // 获取有效的访问令牌(使用缓存机制)
298
316
  const accessToken = await this.getValidAccessToken();
299
317
  if (!accessToken) {
@@ -317,18 +335,18 @@ export class DingTalkMCPServer {
317
335
  data: body,
318
336
  timeout: 30000
319
337
  });
320
- if (this.debug){
338
+ if (this.debug) {
321
339
  console.log(response)
322
340
  }
341
+
323
342
  return {
324
343
  content: [{
325
344
  type: 'text',
326
345
  text: JSON.stringify(response.data, null, 2)
327
346
  }]
328
347
  };
329
- }
330
- catch (error) {
331
- if (this.debug){
348
+ } catch (error) {
349
+ if (this.debug) {
332
350
  console.log(error)
333
351
  }
334
352
  let errorMessage = error.message;
@@ -352,6 +370,7 @@ export class DingTalkMCPServer {
352
370
  };
353
371
  }
354
372
  }
373
+
355
374
  buildUrl(template, args) {
356
375
  let url = template;
357
376
  // 替换路径参数
@@ -373,12 +392,11 @@ export class DingTalkMCPServer {
373
392
  if (args[key] !== undefined) {
374
393
  queryParams.set(key, String(args[key]));
375
394
  }
376
- }
377
- else {
395
+ } else {
378
396
  // 处理系统参数,如自动化机器人access_token
379
- if (process.env[value]){
397
+ if (process.env[value]) {
380
398
  queryParams.set(key, process.env[value]);
381
- }else {
399
+ } else {
382
400
  queryParams.set(key, value);
383
401
  }
384
402
  }
@@ -393,12 +411,17 @@ export class DingTalkMCPServer {
393
411
  }
394
412
  return url;
395
413
  }
414
+
396
415
  buildHeaders(tool) {
416
+ if (tool.requestTemplate.url && !tool.requestTemplate.url.includes('dingtalk.com')) {
417
+ return;
418
+ }
419
+
397
420
  const headers = {
398
421
  'Content-Type': 'application/json'
399
422
  };
400
423
  // 只有对新版API (api.dingtalk.com) 才添加token到header
401
- if (tool.requestTemplate.url && !tool.requestTemplate.url.includes('oapi.dingtalk.com') && this.accessToken) {
424
+ if (tool.requestTemplate.url && tool.requestTemplate.url.includes('https://api.dingtalk.com') && this.accessToken) {
402
425
  headers['x-acs-dingtalk-access-token'] = this.accessToken;
403
426
  }
404
427
  if (tool.requestTemplate.headers) {
@@ -409,20 +432,20 @@ export class DingTalkMCPServer {
409
432
  return headers;
410
433
  }
411
434
 
412
- processMultiParam(body, name, value){
435
+ processMultiParam(body, name, value) {
413
436
  let objParmas = name.split('.');
414
437
  if (objParmas && objParmas.length > 1) {
415
- if (objParmas.length == 2){
416
- if (!body[objParmas[0]]){
438
+ if (objParmas.length == 2) {
439
+ if (!body[objParmas[0]]) {
417
440
  body[objParmas[0]] = {};
418
441
  }
419
442
  body[objParmas[0]][objParmas[1]] = value;
420
443
  }
421
- if (objParmas.length == 3){
422
- if (!body[objParmas[0]]){
444
+ if (objParmas.length == 3) {
445
+ if (!body[objParmas[0]]) {
423
446
  body[objParmas[0]] = {};
424
447
  }
425
- if (!body[objParmas[0]][objParmas[1]]){
448
+ if (!body[objParmas[0]][objParmas[1]]) {
426
449
  body[objParmas[0]][objParmas[1]] = {};
427
450
  }
428
451
  body[objParmas[0]][objParmas[1]][objParmas[2]] = value;
@@ -439,11 +462,11 @@ export class DingTalkMCPServer {
439
462
  return undefined;
440
463
  }
441
464
 
442
- if (tool.name === 'sendServiceWindowMessage'){
465
+ if (tool.name === 'sendServiceWindowMessage') {
443
466
  return ServiceWindowMessageBuilder.buildSendServiceWindowMarkdownBody(args);
444
- }else if (tool.name === 'batchSendServiceWindowMessage'){
467
+ } else if (tool.name === 'batchSendServiceWindowMessage') {
445
468
  return ServiceWindowMessageBuilder.buildBatchSendServiceWindowMarkdownBody(args);
446
- }else if (tool.name === 'createLog' || tool.name === 'saveLogDraft'){
469
+ } else if (tool.name === 'createReport' || tool.name === 'saveReportDraft') {
447
470
  return LogMessageBuilder.buildBody(args);
448
471
  }
449
472
 
@@ -453,24 +476,24 @@ export class DingTalkMCPServer {
453
476
  if (arg.position === 'body') {
454
477
  // 系统参数,从环境变量读取
455
478
  if (arg.system) {
456
- if (!process.env[arg.system]){
479
+ if (!process.env[arg.system]) {
457
480
  throw new Error(`System parameter ${arg.system} is required, please check your environment variables`);
458
481
  }
459
482
  body[arg.name] = process.env[arg.system];
460
483
  return;
461
484
  }
462
485
  // 不需要模型处理,直接取默认值赋值
463
- if (arg.not_need_model_transform){
486
+ if (arg.not_need_model_transform) {
464
487
  this.processMultiParam(body, arg.name, arg.default);
465
488
  return;
466
489
  }
467
490
 
468
491
  // 如果模型没有对应提参,则不赋值给API对应的入参
469
- if (!args[arg.name]) {
492
+ if (args[arg.name] == null) {
470
493
  return;
471
494
  }
472
495
 
473
- if (arg.extendType === 'json'){
496
+ if (arg.extendType === 'json') {
474
497
  body[arg.name] = JSON.stringify(args[arg.name]);
475
498
  return;
476
499
  }
@@ -496,7 +519,7 @@ export class DingTalkMCPServer {
496
519
  // 为searchUser/searchDepartment添加默认的分页参数
497
520
 
498
521
  // 设置默认的offset为0
499
- if (tool.name === 'searchDepartment' || tool.name === 'searchUser'){
522
+ if (tool.name === 'searchDepartment' || tool.name === 'searchUser') {
500
523
  if (body.offset === undefined) {
501
524
  body.offset = 0;
502
525
  }
@@ -516,4 +539,5 @@ export class DingTalkMCPServer {
516
539
  console.error('DingTalk MCP server running on stdio');
517
540
  }
518
541
  }
542
+
519
543
  //# sourceMappingURL=DingTalkMCPServer.js.map
@@ -0,0 +1,17 @@
1
+ # 字段属性
2
+
3
+ 该文档介绍了AI表格中字段属性的相关配置。
4
+
5
+ | 字段名 | 类型 (type) | 属性 (property) |
6
+ | --- | --- | --- |
7
+ | 文本 | text | 无 |
8
+ | 数字 | number | ``` { formatter: "INT" // 整数 \| "FLOAT_1" // 保留1位小数 \| "FLOAT_2" // 保留2位小数 \| "FLOAT_3" // 保留3位小数 \| "FLOAT_4" // 保留4位小数 \| "THOUSAND" // 千分位 \| "THOUSAND_FLOAT" // 千分位(小数点) \| "PRESENT" // 百分比 \| "PRESENT_FLOAT" // 百分比(小数点) \| "CNY" // 人民币 \| "CNY_FLOAT" // 人民币(小数点) \| "HKD" // 港元 \| "HKD_FLOAT" // 港元(小数点) \| "USD" // 美元 \| "USD_FLOAT" // 美元(小数点) \| "EUR" // 欧元 \| "EUR_FLOAT" // 欧元(小数点) \| "JPY" // 日元 \| "JPY_FLOAT"; // 日元(小数点) } ``` |
9
+ | 单选 | singleSelect | ``` { choices: [{ name: "optionName1" // 配置选项名 }, { name: "optionName2" }]; } ``` |
10
+ | 多选 | multipleSelect | 同「单选」 |
11
+ | 日期 | date | ``` { formatter: "YYYY-MM-DD" // 显示格式: 2023-12-31 \| "YYYY-MM-DD HH:mm" // 显示格式: 2023-12-31 09:00 \| "YYYY/MM/DD" // 显示格式: 2023/12/31 \| "YYYY/MM/DD HH:mm"; // 显示格式: 2023/12/31 09:00 } ``` |
12
+ | 人员 | user | ``` { multiple: boolean; // 支持多选,默认为true } ``` |
13
+ | 部门 | department | ``` { multiple: boolean; // 支持多选,默认为true } ``` |
14
+ | 附件 | attachment | 无 |
15
+ | 单向关联 | unidirectionalLink | ``` { multiple: boolean; // 支持多选,默认为true linkedSheetId: "xxx" // 关联的数据表ID } ``` |
16
+ | 双向关联 | bidirectionalLink | ``` { multiple: boolean; // 支持多选,默认为true linkedSheetId: "xxx", // 关联的数据表ID linkedFieldId: "yyy" // 关联的数据表上的字段ID,创建字段时不传 } ``` |
17
+ | 链接 | url | 无 |
@@ -0,0 +1,18 @@
1
+ # 记录值格式
2
+ 该文档介绍AI表格中记录值的读写格式。
3
+
4
+ 不同字段类型所使用的格式请参考下表:
5
+
6
+ | 字段名 | 类型 (type) | 设置值(新增/更新记录时使用的格式) | 返回值(返回记录值时返回的格式) |
7
+ | --- | --- | --- | --- |
8
+ | 文本 | text | `"TextString" // 字符串` | `"TextString" // 字符串` |
9
+ | 数字 | number | `123 // 支持整数/浮点数/字符串` | `"123" // 数字值,以字符串形式返回` |
10
+ | 单选 | singleSelect | `"optionName1" // 单选选项名` | `{ "id": "id", // 选项ID "name": "optionName1" // 选项名 }` |
11
+ | 多选 | multipleSelect | `["optionName1", "optionName2"] // 多选选项名` | `[ { "id": "id1", // 选项ID "name": "optionName1" // 选项名 }, { "id": "id2", // 选项ID "name": "optionName2" // 选项名 } ]` |
12
+ | 日期 | date | `1688601600000 // 时间戳 "2023-12-20 03:00" // 或者 ISO 8601字符串` | `1688601600000 // 时间戳` |
13
+ | 人员 | user | `[ { unionId: "xxx" } ]` | `[ { unionId: "xxx" } ]` |
14
+ | 部门 | department | `[ { deptId: "xxx" } ]` | `[ { deptId: "xxx" } ]` |
15
+ | 附件 | attachment | 具体请参考[上传附件](https://open.dingtalk.com/document/orgapp/notable-upload-attachment) 。 | `[ { "filename": "image.xlsx", "size": 92250, "type": "xls", "url": "xxx" } ]`<br> <br><br>**说明**<br><br>url是附件访问链接。<br><br>* 当附件是在线文档时,其是在线文档链接,该链接没有访问时效。<br> <br>* 当附件是其它文件时,是一个有**访问时效** 的下载链接,一段时间后该链接将无法访问。 |
16
+ | 单向关联 | unidirectionalLink | `{ "linkedRecordIds": [ "xxx", "yyy" ] }` | `{ "linkedRecordIds": [ "xxx", "yyy" ] }`<br> <br><br>**说明**<br><br>field property中包含关联的sheetId,配合这里返回的recordId,可以通过调用[获取记录](/document/orgapp/api-getrecord#) 接口去获取关联记录的值。 |
17
+ | 双向关联 | bidirectionalLink | `{ "linkedRecordIds": [ "xxx", "yyy" ] }` | `{ "linkedRecordIds": [ "xxx", "yyy" ] }` |
18
+ | 链接 | url | `{ "text": "Dingtalk", "link": "https://dingtalk.com" }` | `{ "text": "Dingtalk", "link": "https://dingtalk.com" }` |
@@ -0,0 +1,29 @@
1
+
2
+ /**
3
+ * 日期和时间处理的工具类 (适用于 Node.js 环境)
4
+ */
5
+ export class DateUtils {
6
+
7
+ /**
8
+ * 异步获取当前时间,并根据运行环境的区域和时区设置进行格式化。
9
+ * @returns {Promise<string>} 一个 Promise,解析为格式化后的本地时间字符串, e.g., "2023-10-27 16:45:30"
10
+ */
11
+ static getFormattedLocalNow() {
12
+ // 1. 异步、安全地获取操作系统的区域设置,提供一个后备值
13
+ const locale = Intl.DateTimeFormat().resolvedOptions().locale || 'zh-CN';
14
+
15
+ // 2. Intl.DateTimeFormat 默认就会使用环境时区
16
+ const formatter = new Intl.DateTimeFormat(locale, {
17
+ year: 'numeric',
18
+ month: '2-digit',
19
+ day: '2-digit',
20
+ hour: '2-digit',
21
+ minute: '2-digit',
22
+ second: '2-digit',
23
+ hour12: false // 使用24小时制
24
+ });
25
+
26
+ // 3. 格式化并统一分隔符
27
+ return formatter.format(new Date()).replace(/\//g, '-');
28
+ }
29
+ }
@@ -0,0 +1,14 @@
1
+ import path, {normalize} from 'path';
2
+ import fs from "fs";
3
+ import {fileURLToPath} from 'url';
4
+
5
+ export class FileUtils {
6
+
7
+ static readLocalFileContent(tool){
8
+ if (tool.requestTemplate.type === "file"){
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+ return fs.readFileSync(path.join(path.join(__dirname, '..'), tool.requestTemplate.url), 'utf8');
12
+ }
13
+ }
14
+ }
@@ -0,0 +1,20 @@
1
+ import {DateUtils} from './DateUtils.js'
2
+ import {FileUtils} from './file.js'
3
+
4
+ const localTools = ["currentDateTime", "notableSupportedFieldInfo", "notableRecordValuesFormat"]
5
+
6
+ export class LocalTools{
7
+
8
+ static isLocalTool(toolName){
9
+ return localTools.includes(toolName)
10
+ }
11
+
12
+ static callTool(tool){
13
+ if (tool.name === "currentDateTime"){
14
+ return DateUtils.getFormattedLocalNow();
15
+ }else if (tool.name === "notableSupportedFieldInfo" || tool.name === "notableRecordValuesFormat"){
16
+ return FileUtils.readLocalFileContent(tool);
17
+ }
18
+ }
19
+
20
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dingtalk-mcp",
3
- "version": "1.1.9",
3
+ "version": "1.1.10",
4
4
  "description": "DingTalk MCP Server - A TypeScript-based MCP server for DingTalk integration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",