dingding-local-api-mcp 1.3.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.
package/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # dingding-local-api-mcp
2
+
3
+ MCP stdio server for DingDing Browser Local API.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g dingding-local-api-mcp
9
+ ```
10
+
11
+ ## Codex
12
+
13
+ ```bash
14
+ codex mcp add --env DDB_PORT="19876" --env DDB_API_KEY="your_api_key" dingding-local-api -- npx -y dingding-local-api-mcp
15
+ ```
16
+
17
+ ## Claude Code
18
+
19
+ ```bash
20
+ claude mcp add dingding-local-api -e DDB_PORT="19876" -e DDB_API_KEY="your_api_key" -- npx -y dingding-local-api-mcp
21
+ ```
22
+
23
+ Tools include:
24
+
25
+ - `check_status`
26
+ - `list_profiles`
27
+ - `create_profile`
28
+ - `open_browser`
29
+ - `close_browser`
30
+ - `get_runtime_session`
31
+ - `list_automation_scripts`
32
+ - `run_automation_script`
33
+ - `list_groups`
34
+ - `list_proxies`
35
+ - `list_tags`
36
+ - `list_cores`
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ 'use strict'
3
+
4
+ const { runServer } = require('../src/server.cjs')
5
+
6
+ runServer().catch((err) => {
7
+ process.stderr.write(`[dingding-local-api-mcp] ${err && err.stack ? err.stack : err}\n`)
8
+ process.exitCode = 1
9
+ })
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "dingding-local-api-mcp",
3
+ "version": "1.3.8",
4
+ "description": "MCP stdio server for DingDing Browser Local API.",
5
+ "license": "MIT",
6
+ "main": "src/server.cjs",
7
+ "bin": {
8
+ "dingding-local-api-mcp": "bin/dingding-local-api-mcp.cjs"
9
+ },
10
+ "files": [
11
+ "bin",
12
+ "src",
13
+ "README.md"
14
+ ],
15
+ "dependencies": {
16
+ "dingding-local-api-core": "1.3.8"
17
+ },
18
+ "engines": {
19
+ "node": ">=18"
20
+ },
21
+ "scripts": {
22
+ "test": "node --test test/*.test.cjs",
23
+ "check": "node --check bin/dingding-local-api-mcp.cjs && node --check src/server.cjs"
24
+ }
25
+ }
package/src/server.cjs ADDED
@@ -0,0 +1,287 @@
1
+ 'use strict'
2
+
3
+ const { createClient } = loadCore()
4
+
5
+ const VERSION = '1.3.8'
6
+
7
+ const TOOL_DEFINITIONS = [
8
+ tool('check_status', 'Check DingDing Browser Local API availability.', {}),
9
+ tool('list_profiles', 'List browser profiles.', {}),
10
+ tool('get_profile', 'Get one browser profile by profileId.', { profileId: stringProp('Profile ID') }, ['profileId']),
11
+ tool('create_profile', 'Create a browser profile. Body matches POST /api/profiles.', { profile: objectProp('Profile input'), launchCode: stringProp('Launch code'), autoLaunch: boolProp('Auto launch after create'), start: objectProp('One-time start options') }, ['profile']),
12
+ tool('update_profile', 'Update a browser profile. Body matches PUT /api/profiles/{profileId}.', { profileId: stringProp('Profile ID'), profile: objectProp('Profile input'), launchCode: stringProp('Launch code'), autoLaunch: boolProp('Auto launch after update'), start: objectProp('One-time start options') }, ['profileId', 'profile']),
13
+ tool('delete_profile', 'Delete a stopped browser profile.', { profileId: stringProp('Profile ID') }, ['profileId']),
14
+ tool('open_browser', 'Open or launch a browser profile by selector.', selectorProps()),
15
+ tool('get_runtime_active', 'Get the active runtime target.', {}),
16
+ tool('get_runtime_session', 'Open and wait for a debuggable runtime session.', { ...selectorProps(), timeoutMs: intProp('Wait timeout in milliseconds') }),
17
+ tool('get_runtime_status', 'Get runtime status by selector without launching.', selectorProps()),
18
+ tool('close_browser', 'Stop a browser profile by selector.', selectorProps()),
19
+ tool('list_launch_logs', 'List recent Launch API call logs.', { limit: intProp('Max records') }),
20
+ tool('list_automation_scripts', 'List automation scripts.', {}),
21
+ tool('get_automation_script', 'Get automation script metadata by scriptId.', { scriptId: stringProp('Script ID') }, ['scriptId']),
22
+ tool('run_automation_script', 'Run an automation script.', { scriptId: stringProp('Script ID'), selector: objectProp('Target selector'), params: objectProp('Script params'), useScriptSelector: boolProp('Use default selector'), useScriptParams: boolProp('Use default params'), timeoutMs: intProp('Timeout in milliseconds') }, ['scriptId']),
23
+ tool('list_automation_runs', 'List recent automation script runs.', { limit: intProp('Max records') }),
24
+ tool('list_groups', 'List profile groups.', {}),
25
+ tool('create_group', 'Create a profile group.', { groupName: stringProp('Group name'), parentId: stringProp('Parent group ID'), sortOrder: intProp('Sort order') }, ['groupName']),
26
+ tool('update_group', 'Update a profile group.', { groupId: stringProp('Group ID'), groupName: stringProp('Group name'), parentId: stringProp('Parent group ID'), sortOrder: intProp('Sort order') }, ['groupId']),
27
+ tool('delete_group', 'Delete a profile group.', { groupId: stringProp('Group ID') }, ['groupId']),
28
+ tool('move_profiles_to_group', 'Move profiles to a group.', { groupId: stringProp('Group ID'), profileIds: arrayProp('Profile IDs') }, ['groupId', 'profileIds']),
29
+ tool('list_proxies', 'List proxy pool nodes.', { groupName: stringProp('Filter by proxy group name') }),
30
+ tool('list_proxy_groups', 'List proxy group names.', {}),
31
+ tool('get_proxy', 'Get one proxy pool node.', { proxyId: stringProp('Proxy ID') }, ['proxyId']),
32
+ tool('create_proxy', 'Append one proxy or a proxy list to the proxy pool.', { proxy: objectProp('Single proxy object'), proxies: arrayProp('Proxy list') }),
33
+ tool('update_proxy', 'Update one proxy pool node.', { proxyId: stringProp('Proxy ID'), proxy: objectProp('Proxy object') }, ['proxyId', 'proxy']),
34
+ tool('delete_proxy', 'Delete one proxy pool node.', { proxyId: stringProp('Proxy ID') }, ['proxyId']),
35
+ tool('list_tags', 'List all tags used by profiles.', {}),
36
+ tool('rename_tag', 'Rename a tag across profiles.', { oldName: stringProp('Old tag name'), newName: stringProp('New tag name') }, ['oldName', 'newName']),
37
+ tool('set_tags', 'Batch set or append tags for profiles.', { profileIds: arrayProp('Profile IDs'), tags: arrayProp('Tags'), replace: boolProp('Replace existing tags') }, ['profileIds', 'tags']),
38
+ tool('remove_tags', 'Batch remove tags from profiles.', { profileIds: arrayProp('Profile IDs'), tags: arrayProp('Tags') }, ['profileIds', 'tags']),
39
+ tool('list_cores', 'List installed browser cores.', {}),
40
+ ]
41
+
42
+ async function callTool(name, args = {}, client = createClient()) {
43
+ switch (name) {
44
+ case 'check_status':
45
+ return client.health()
46
+ case 'list_profiles':
47
+ return client.listProfiles()
48
+ case 'get_profile':
49
+ return client.getProfile(args.profileId)
50
+ case 'create_profile':
51
+ return client.createProfile(pick(args, ['profile', 'launchCode', 'autoLaunch', 'start']))
52
+ case 'update_profile':
53
+ return client.updateProfile(args.profileId, pick(args, ['profile', 'launchCode', 'autoLaunch', 'start']))
54
+ case 'delete_profile':
55
+ return client.deleteProfile(args.profileId)
56
+ case 'open_browser':
57
+ return client.launch(args)
58
+ case 'get_runtime_active':
59
+ return client.runtimeActive()
60
+ case 'get_runtime_session':
61
+ return client.runtimeSession(args)
62
+ case 'get_runtime_status':
63
+ return client.runtimeStatus(args)
64
+ case 'close_browser':
65
+ return client.runtimeStop(args)
66
+ case 'list_launch_logs':
67
+ return client.launchLogs(args.limit)
68
+ case 'list_automation_scripts':
69
+ return client.listAutomationScripts()
70
+ case 'get_automation_script':
71
+ return client.getAutomationScript(args.scriptId)
72
+ case 'run_automation_script':
73
+ return client.runAutomationScript(pick(args, ['scriptId', 'selector', 'params', 'useScriptSelector', 'useScriptParams', 'timeoutMs']))
74
+ case 'list_automation_runs':
75
+ return client.listAutomationRuns(args.limit)
76
+ case 'list_groups':
77
+ return client.listGroups()
78
+ case 'create_group':
79
+ return client.createGroup(pick(args, ['groupName', 'parentId', 'sortOrder']))
80
+ case 'update_group':
81
+ return client.updateGroup(args.groupId, pick(args, ['groupName', 'parentId', 'sortOrder']))
82
+ case 'delete_group':
83
+ return client.deleteGroup(args.groupId)
84
+ case 'move_profiles_to_group':
85
+ return client.moveProfilesToGroup(args.groupId, args.profileIds)
86
+ case 'list_proxies':
87
+ return client.listProxies(args.groupName)
88
+ case 'list_proxy_groups':
89
+ return client.listProxyGroups()
90
+ case 'get_proxy':
91
+ return client.getProxy(args.proxyId)
92
+ case 'create_proxy':
93
+ return client.createProxy(args.proxies || args.proxy)
94
+ case 'update_proxy':
95
+ return client.updateProxy(args.proxyId, args.proxy)
96
+ case 'delete_proxy':
97
+ return client.deleteProxy(args.proxyId)
98
+ case 'list_tags':
99
+ return client.listTags()
100
+ case 'rename_tag':
101
+ return client.renameTag(args.oldName, args.newName)
102
+ case 'set_tags':
103
+ return client.setTags(args.profileIds, args.tags, args.replace)
104
+ case 'remove_tags':
105
+ return client.removeTags(args.profileIds, args.tags)
106
+ case 'list_cores':
107
+ return client.listCores()
108
+ default:
109
+ throw new Error(`unknown tool: ${name}`)
110
+ }
111
+ }
112
+
113
+ function runServer(input = process.stdin, output = process.stdout) {
114
+ const client = createClient()
115
+ let buffer = Buffer.alloc(0)
116
+
117
+ input.on('data', (chunk) => {
118
+ buffer = Buffer.concat([buffer, chunk])
119
+ while (true) {
120
+ const parsed = readMessage(buffer)
121
+ if (!parsed) return
122
+ buffer = parsed.rest
123
+ handleMessage(parsed.message, client)
124
+ .then((response) => {
125
+ if (response) writeMessage(output, response)
126
+ })
127
+ .catch((err) => {
128
+ if (parsed.message && parsed.message.id !== undefined) {
129
+ writeMessage(output, errorResponse(parsed.message.id, -32603, err.message || String(err)))
130
+ }
131
+ })
132
+ }
133
+ })
134
+
135
+ return Promise.resolve()
136
+ }
137
+
138
+ async function handleMessage(message, client) {
139
+ if (!message || message.id === undefined) return null
140
+ switch (message.method) {
141
+ case 'initialize':
142
+ return resultResponse(message.id, {
143
+ protocolVersion: (message.params && message.params.protocolVersion) || '2024-11-05',
144
+ capabilities: { tools: {} },
145
+ serverInfo: { name: 'dingding-local-api-mcp', version: VERSION },
146
+ })
147
+ case 'ping':
148
+ return resultResponse(message.id, {})
149
+ case 'tools/list':
150
+ return resultResponse(message.id, { tools: TOOL_DEFINITIONS })
151
+ case 'tools/call':
152
+ return handleToolCall(message, client)
153
+ case 'shutdown':
154
+ return resultResponse(message.id, null)
155
+ default:
156
+ return errorResponse(message.id, -32601, `method not found: ${message.method}`)
157
+ }
158
+ }
159
+
160
+ async function handleToolCall(message, client) {
161
+ const params = message.params || {}
162
+ try {
163
+ const data = await callTool(params.name, params.arguments || {}, client)
164
+ return resultResponse(message.id, {
165
+ content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
166
+ isError: false,
167
+ })
168
+ } catch (err) {
169
+ const payload = { ok: false, error: err.message || String(err) }
170
+ if (err.status) payload.status = err.status
171
+ if (err.payload !== undefined) payload.payload = err.payload
172
+ return resultResponse(message.id, {
173
+ content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }],
174
+ isError: true,
175
+ })
176
+ }
177
+ }
178
+
179
+ function readMessage(buffer) {
180
+ const sep = buffer.indexOf('\r\n\r\n')
181
+ if (sep < 0) return null
182
+ const header = buffer.subarray(0, sep).toString('utf8')
183
+ const match = /content-length:\s*(\d+)/i.exec(header)
184
+ if (!match) throw new Error('missing Content-Length header')
185
+ const length = Number(match[1])
186
+ const start = sep + 4
187
+ const end = start + length
188
+ if (buffer.length < end) return null
189
+ const raw = buffer.subarray(start, end).toString('utf8')
190
+ return {
191
+ message: JSON.parse(raw),
192
+ rest: buffer.subarray(end),
193
+ }
194
+ }
195
+
196
+ function writeMessage(output, message) {
197
+ const body = Buffer.from(JSON.stringify(message), 'utf8')
198
+ output.write(`Content-Length: ${body.length}\r\n\r\n`)
199
+ output.write(body)
200
+ }
201
+
202
+ function resultResponse(id, result) {
203
+ return { jsonrpc: '2.0', id, result }
204
+ }
205
+
206
+ function errorResponse(id, code, message) {
207
+ return { jsonrpc: '2.0', id, error: { code, message } }
208
+ }
209
+
210
+ function tool(name, description, properties, required = []) {
211
+ return {
212
+ name,
213
+ description,
214
+ inputSchema: {
215
+ type: 'object',
216
+ properties,
217
+ required,
218
+ additionalProperties: false,
219
+ },
220
+ }
221
+ }
222
+
223
+ function selectorProps() {
224
+ return {
225
+ selector: objectProp('Nested selector object'),
226
+ code: stringProp('Launch code'),
227
+ key: stringProp('Alias for launch code'),
228
+ profileId: stringProp('Profile ID'),
229
+ profileName: stringProp('Profile name'),
230
+ keyword: stringProp('Keyword'),
231
+ keywords: arrayProp('Keywords'),
232
+ tag: stringProp('Tag'),
233
+ tags: arrayProp('Tags'),
234
+ groupId: stringProp('Group ID'),
235
+ matchMode: stringProp('unique, first, or all depending on endpoint'),
236
+ startUrls: arrayProp('One-time start URLs'),
237
+ skipDefaultStartUrls: boolProp('Skip configured start URLs'),
238
+ launchArgs: arrayProp('One-time launch args'),
239
+ proxyId: stringProp('One-time proxy ID'),
240
+ proxyConfig: stringProp('One-time proxy config'),
241
+ }
242
+ }
243
+
244
+ function stringProp(description) {
245
+ return { type: 'string', description }
246
+ }
247
+
248
+ function boolProp(description) {
249
+ return { type: 'boolean', description }
250
+ }
251
+
252
+ function intProp(description) {
253
+ return { type: 'integer', description }
254
+ }
255
+
256
+ function objectProp(description) {
257
+ return { type: 'object', description, additionalProperties: true }
258
+ }
259
+
260
+ function arrayProp(description) {
261
+ return { type: 'array', description, items: {} }
262
+ }
263
+
264
+ function pick(source, keys) {
265
+ const out = {}
266
+ for (const key of keys) {
267
+ if (source[key] !== undefined) out[key] = source[key]
268
+ }
269
+ return out
270
+ }
271
+
272
+ function loadCore() {
273
+ try {
274
+ return require('dingding-local-api-core')
275
+ } catch {
276
+ return require('../../dingding-local-api-core/src/index.cjs')
277
+ }
278
+ }
279
+
280
+ module.exports = {
281
+ TOOL_DEFINITIONS,
282
+ callTool,
283
+ handleMessage,
284
+ readMessage,
285
+ runServer,
286
+ writeMessage,
287
+ }