im-connector 0.0.0-stage → 1.0.0
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/LICENSE +21 -0
- package/README.md +181 -2
- package/dist/cli/errors.d.ts +45 -0
- package/dist/cli/errors.d.ts.map +1 -0
- package/dist/cli/errors.js +75 -0
- package/dist/cli/errors.js.map +1 -0
- package/dist/cli/runner.d.ts +33 -0
- package/dist/cli/runner.d.ts.map +1 -0
- package/dist/cli/runner.js +136 -0
- package/dist/cli/runner.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/server.d.ts +14 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +108 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/providers/dingtalk.d.ts +4 -0
- package/dist/providers/dingtalk.d.ts.map +1 -0
- package/dist/providers/dingtalk.js +23 -0
- package/dist/providers/dingtalk.js.map +1 -0
- package/dist/providers/registry.d.ts +4 -0
- package/dist/providers/registry.d.ts.map +1 -0
- package/dist/providers/registry.js +11 -0
- package/dist/providers/registry.js.map +1 -0
- package/dist/providers/types.d.ts +23 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +2 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/providers/wecom.d.ts +4 -0
- package/dist/providers/wecom.d.ts.map +1 -0
- package/dist/providers/wecom.js +17 -0
- package/dist/providers/wecom.js.map +1 -0
- package/mcp-bundle/README.md +39 -0
- package/mcp-bundle/cordis.patch.yml +9 -0
- package/mcp-bundle/icon.svg +7 -0
- package/mcp-bundle/locale/en.json +6 -0
- package/mcp-bundle/locale/zh.json +6 -0
- package/mcp-bundle/package.json +9 -0
- package/package.json +64 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 im-connector contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,182 @@
|
|
|
1
|
-
#
|
|
1
|
+
# IM Connector(可扩展的多 IM 透传插件)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
一个可在 npm 上分发的统一 IM 连接器:通过**一个 MCP 工具 `mcp__im__run`**,按 `provider` 路由到各平台的命令行客户端,返回其 JSON 输出。当前接入**钉钉**(dws)与**企业微信**(wecom-cli),并预留了飞书、Discord 等平台的扩展位。
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
DSH Agent ── mcp__im__run { provider, args } ──▶ dist/mcp/server.js
|
|
7
|
+
├─ provider=dingtalk ──▶ dws ──▶ 钉钉
|
|
8
|
+
└─ provider=wecom ──▶ wecom-cli ──▶ 企业微信
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 定位:Agent 侧集成(让机器人在渠道里干活)
|
|
12
|
+
|
|
13
|
+
本连接器只负责**「Agent → 渠道」方向**:Agent 通过 MCP 工具调用 dws / wecom-cli,让机器人在钉钉/企微里完成实际工作——发消息、写日志/日报、建待办、查日程、处理审批等。
|
|
14
|
+
|
|
15
|
+
**渠道 → Agent 方向**(在群里/私聊里跟机器人对话)由独立的 `dsh-im` 插件负责,不在本连接器的范围内。
|
|
16
|
+
|
|
17
|
+
## 典型用法(让机器人在渠道里干活)
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
// 发消息到群 / 私聊
|
|
21
|
+
{ "provider": "dingtalk", "args": ["chat", "+messages-send", "--group", "<openConversationId>", "--markdown", "今天 18:00 前提交周报", "--yes"] }
|
|
22
|
+
{ "provider": "dingtalk", "args": ["chat", "+messages-send", "--user", "<userId>", "--markdown", "私聊通知", "--yes"] }
|
|
23
|
+
|
|
24
|
+
// 写/提交日志(日报周报)
|
|
25
|
+
{ "provider": "dingtalk", "args": ["report", "+create", "--template", "日报", "--content", "...", "--yes"] }
|
|
26
|
+
{ "provider": "dingtalk", "args": ["report", "+my", "--type", "日报"] }
|
|
27
|
+
|
|
28
|
+
// 待办
|
|
29
|
+
{ "provider": "dingtalk", "args": ["todo", "+create", "--title", "整理今日测试报告", "--due", "2026-10-23 18:00"] }
|
|
30
|
+
{ "provider": "dingtalk", "args": ["todo", "+get-my-tasks"] }
|
|
31
|
+
|
|
32
|
+
// 日程 / 审批 / 通讯录
|
|
33
|
+
{ "provider": "dingtalk", "args": ["calendar", "+today"] }
|
|
34
|
+
{ "provider": "dingtalk", "args": ["oa", "approval", "list-pending"] }
|
|
35
|
+
{ "provider": "dingtalk", "args": ["contact", "+me"] }
|
|
36
|
+
|
|
37
|
+
// 企业微信
|
|
38
|
+
{ "provider": "wecom", "args": ["contact", "users", "search", "--keywords", "张三"] }
|
|
39
|
+
{ "provider": "wecom", "args": ["message", "send", "--chat-id", "<id>", "--msg-type", "text", "--text", "{\"content\":\"通知内容\"}"] }
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
写操作需用户明确意图;不确定时先用 `--dry-run` 预览(两平台都支持)。
|
|
43
|
+
|
|
44
|
+
## 设计核心:Provider 注册表
|
|
45
|
+
|
|
46
|
+
每个 IM 平台只是 `src/providers/*.ts` 里的一条规格(`ImProviderSpec`),描述:
|
|
47
|
+
|
|
48
|
+
- 该平台的 CLI 命令名、npm 包名与 bin 脚本路径(用于自动解析 npm 全局安装、规避 Windows shim)
|
|
49
|
+
- 请求 JSON 输出的 flag(dws 用 `--format json`;wecom 默认 JSON 则留空)
|
|
50
|
+
- 覆盖 bin 的环境变量名、示例命令
|
|
51
|
+
|
|
52
|
+
**新增一个平台(飞书 / Discord / …)只需**:
|
|
53
|
+
1. 新建 `src/providers/feishu.ts`,导出一个 `ImProviderSpec`;
|
|
54
|
+
2. 在 `src/providers/registry.ts` 的 `providers` 数组里追加它。
|
|
55
|
+
|
|
56
|
+
MCP 工具的 `provider` 枚举与描述会**自动**从注册表生成,无需改其它代码。
|
|
57
|
+
|
|
58
|
+
## 快速开始(npm)
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npm i -g im-connector # 全局安装(提供 im-connector 命令)
|
|
62
|
+
# 或直接用 npx(无需安装):
|
|
63
|
+
npx -y im-connector
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`im-connector` 是一个 **stdio MCP server**:任何 MCP 客户端(DSH / Claude Desktop / 其它 Agent)都可以把它注册成 MCP 连接。配置示例:
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"mcpServers": {
|
|
71
|
+
"im": {
|
|
72
|
+
"command": "npx",
|
|
73
|
+
"args": ["-y", "im-connector"],
|
|
74
|
+
"env": {
|
|
75
|
+
"DINGTALK_DWS_BIN": "<npm root -g>/dingtalk-workspace-cli/bin/dws.js",
|
|
76
|
+
"WECOM_CLI_BIN": "<npm root -g>/@wecom/cli/bin/wecom.js"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
> `env` 可选:不填时连接器会自动通过 `npm root -g` 解析两个 CLI;填了更稳(规避 Windows shim)。
|
|
84
|
+
|
|
85
|
+
DSH 用户:本包**同时是 DSH 的 MCP 连接 bundle**(`package.json` 里声明了 `dsh.bundle.patch`),一条命令完成"装包 + 注册 MCP 连接":
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
plugin_manager install_bundle target: im-connector
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
安装后工具名为 `mcp__im__run`。细节与手工安装方式见 `mcp-bundle/README.md`。
|
|
92
|
+
|
|
93
|
+
## 前置条件
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# 钉钉
|
|
97
|
+
npm install -g dingtalk-workspace-cli
|
|
98
|
+
dws auth login --recommend --format json
|
|
99
|
+
|
|
100
|
+
# 企业微信
|
|
101
|
+
npm install -g @wecom/cli
|
|
102
|
+
wecom-cli auth show --status # 未授权时:wecom-cli auth init --noninteractive 扫码
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
连接器不保存任何凭据,认证复用各 CLI 的本地登录态。
|
|
106
|
+
|
|
107
|
+
## 构建与验证
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
pnpm install
|
|
111
|
+
pnpm verify # typecheck + 测试 + build + MCP 冒烟
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## 接入 DSH(官方推荐路径)
|
|
115
|
+
|
|
116
|
+
按 DSH 官方 `references/mcp-bundle.md` 的做法:**配置型 bundle**(只插入一条 `@deepseek-ai/dsh-mcp-client` 行,无 Host/Client 入口文件),display 元数据用 `locale/*.json` + `icon.svg`,patch 用官方 stdio 形式:
|
|
117
|
+
|
|
118
|
+
```yaml
|
|
119
|
+
- insert:
|
|
120
|
+
- id: im-connector-mcp
|
|
121
|
+
name: '@deepseek-ai/dsh-mcp-client'
|
|
122
|
+
config:
|
|
123
|
+
serverName: im
|
|
124
|
+
transport: stdio
|
|
125
|
+
command: npx
|
|
126
|
+
args: ['-y', 'im-connector']
|
|
127
|
+
failOnStartupError: true
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
- 推荐安装:`plugin_manager install_bundle target: im-connector`(包内已声明 `dsh.bundle.patch`,一步完成)
|
|
131
|
+
- 手工安装:把 `mcp-bundle/` 复制到任意目录后 `install_bundle` 指向该目录
|
|
132
|
+
- 不需要写死路径或凭据(`npx` 跨平台;凭据保持在两个 CLI 自己的登录态里,env 覆盖用 Loader `!!js`)
|
|
133
|
+
|
|
134
|
+
## 使用
|
|
135
|
+
|
|
136
|
+
调用 `mcp__im__run`,`provider` 选平台,`args` 是该平台 CLI 的命令数组:
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
// 钉钉(dws)
|
|
140
|
+
{ "provider": "dingtalk", "args": ["contact", "+me"] }
|
|
141
|
+
{ "provider": "dingtalk", "args": ["todo", "+get-my-tasks"] }
|
|
142
|
+
{ "provider": "dingtalk", "args": ["oa", "approval", "list-pending"] }
|
|
143
|
+
{ "provider": "dingtalk", "args": ["chat", "+chat-messages", "--group", "研发群", "--limit", "20"] }
|
|
144
|
+
{ "provider": "dingtalk", "args": ["chat", "+messages-send", "--group", "<cid>", "--markdown", "内容", "--title", "标题", "--yes"] }
|
|
145
|
+
|
|
146
|
+
// 企业微信(wecom-cli)
|
|
147
|
+
{ "provider": "wecom", "args": ["contact", "users", "search", "--keywords", "张三"] }
|
|
148
|
+
{ "provider": "wecom", "args": ["chat", "messages", "list", "--chat-id", "<id>", "--begin-time", "2026-10-01 00:00:00", "--end-time", "2026-10-21 00:00:00"] }
|
|
149
|
+
{ "provider": "wecom", "args": ["auth", "show", "--status"] }
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
写操作(发消息/提交/审批等)需用户明确意图,必要时加 `--yes`(钉钉)或先用 `--dry-run` 预览(两平台都支持 `--dry-run`)。
|
|
153
|
+
|
|
154
|
+
## 发布(维护者)
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
npm login # 首次发布前登录(已登录则跳过)
|
|
158
|
+
pnpm verify # typecheck + 测试 + build + MCP 冒烟(prepublishOnly 也会跑)
|
|
159
|
+
npm publish # 发布到 npm(publishConfig.access=public)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
发布内容(`files`):`dist/`(编译产物 + shebang 入口)、`mcp-bundle/`(DSH 连接 bundle)、`README.md`、`LICENSE`。
|
|
163
|
+
|
|
164
|
+
发布前可用 `npm pack --dry-run` 查看实际打包清单。
|
|
165
|
+
|
|
166
|
+
## 目录结构
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
src/
|
|
170
|
+
cli/runner.ts CliRunner:通用 CLI 透传(npm 全局解析、JSON 解析、错误信封映射)
|
|
171
|
+
cli/errors.ts CliError + stderr 错误信封解析
|
|
172
|
+
providers/ ImProviderSpec + registry(dingtalk / wecom,可扩展)
|
|
173
|
+
mcp/server.ts MCP stdio server,暴露单一 `run { provider, args }` 工具
|
|
174
|
+
index.ts 包入口
|
|
175
|
+
tests/ fake-cli.mjs + cli/mcp 测试
|
|
176
|
+
mcp-bundle/ DSH MCP 连接 bundle
|
|
177
|
+
scripts/ mcp-smoke.mjs / duty-reminder.mjs(钉钉值班提醒,独立任务)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## 值班提醒(独立任务)
|
|
181
|
+
|
|
182
|
+
`scripts/duty-reminder.mjs` 由 DSH 周提醒触发,用 `spawnSync` 直接调 `dws`,与本连接器解耦,不受影响。
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** CLI 错误信封(dws 风格;其它 CLI 若无结构化信封则回退到原始 stderr)。 */
|
|
2
|
+
export interface CliErrorEnvelope {
|
|
3
|
+
error?: {
|
|
4
|
+
code?: number | string;
|
|
5
|
+
category?: string;
|
|
6
|
+
message?: string;
|
|
7
|
+
reason?: string;
|
|
8
|
+
retryable?: boolean;
|
|
9
|
+
retry_after_seconds?: number;
|
|
10
|
+
hint?: string;
|
|
11
|
+
actions?: string[];
|
|
12
|
+
details?: unknown;
|
|
13
|
+
trace_id?: string;
|
|
14
|
+
server_error_code?: string;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/** 由底层 CLI 命令失败引发的统一错误。 */
|
|
18
|
+
export declare class CliError extends Error {
|
|
19
|
+
readonly code?: number | string;
|
|
20
|
+
readonly category?: string;
|
|
21
|
+
readonly reason?: string;
|
|
22
|
+
readonly retryable?: boolean;
|
|
23
|
+
readonly hint?: string;
|
|
24
|
+
readonly actions?: string[];
|
|
25
|
+
readonly command: string;
|
|
26
|
+
readonly stderr: string;
|
|
27
|
+
readonly exitCode: number | null;
|
|
28
|
+
constructor(init: {
|
|
29
|
+
message: string;
|
|
30
|
+
command: string;
|
|
31
|
+
stderr?: string;
|
|
32
|
+
exitCode?: number | null;
|
|
33
|
+
code?: number | string;
|
|
34
|
+
category?: string;
|
|
35
|
+
reason?: string;
|
|
36
|
+
retryable?: boolean;
|
|
37
|
+
hint?: string;
|
|
38
|
+
actions?: string[];
|
|
39
|
+
});
|
|
40
|
+
toJSON(): Record<string, unknown>;
|
|
41
|
+
}
|
|
42
|
+
export declare function isCliError(e: unknown): e is CliError;
|
|
43
|
+
/** 从 stderr 文本提取结构化错误信封;失败返回 undefined(调用方回退原始文本)。 */
|
|
44
|
+
export declare function parseErrorEnvelope(stderr: string): CliErrorEnvelope | undefined;
|
|
45
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/cli/errors.ts"],"names":[],"mappings":"AAAA,oDAAoD;AACpD,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE;QACN,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;QACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,mBAAmB,CAAC,EAAE,MAAM,CAAC;QAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;QACnB,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,iBAAiB,CAAC,EAAE,MAAM,CAAC;KAC5B,CAAC;CACH;AAED,2BAA2B;AAC3B,qBAAa,QAAS,SAAQ,KAAK;IACjC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAChC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;gBAErB,IAAI,EAAE;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACzB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;QACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;KACpB;IAcD,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAclC;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,IAAI,QAAQ,CAEpD;AAED,sDAAsD;AACtD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAgB/E"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/** 由底层 CLI 命令失败引发的统一错误。 */
|
|
2
|
+
export class CliError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
category;
|
|
5
|
+
reason;
|
|
6
|
+
retryable;
|
|
7
|
+
hint;
|
|
8
|
+
actions;
|
|
9
|
+
command;
|
|
10
|
+
stderr;
|
|
11
|
+
exitCode;
|
|
12
|
+
constructor(init) {
|
|
13
|
+
super(init.message);
|
|
14
|
+
this.name = "CliError";
|
|
15
|
+
this.code = init.code;
|
|
16
|
+
this.category = init.category;
|
|
17
|
+
this.reason = init.reason;
|
|
18
|
+
this.retryable = init.retryable;
|
|
19
|
+
this.hint = init.hint;
|
|
20
|
+
this.actions = init.actions;
|
|
21
|
+
this.command = init.command;
|
|
22
|
+
this.stderr = init.stderr ?? "";
|
|
23
|
+
this.exitCode = init.exitCode ?? null;
|
|
24
|
+
}
|
|
25
|
+
toJSON() {
|
|
26
|
+
return {
|
|
27
|
+
name: this.name,
|
|
28
|
+
message: this.message,
|
|
29
|
+
command: this.command,
|
|
30
|
+
code: this.code,
|
|
31
|
+
category: this.category,
|
|
32
|
+
reason: this.reason,
|
|
33
|
+
retryable: this.retryable,
|
|
34
|
+
hint: this.hint,
|
|
35
|
+
actions: this.actions,
|
|
36
|
+
exitCode: this.exitCode,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export function isCliError(e) {
|
|
41
|
+
return e instanceof CliError;
|
|
42
|
+
}
|
|
43
|
+
/** 从 stderr 文本提取结构化错误信封;失败返回 undefined(调用方回退原始文本)。 */
|
|
44
|
+
export function parseErrorEnvelope(stderr) {
|
|
45
|
+
const text = stderr.trim();
|
|
46
|
+
if (!text)
|
|
47
|
+
return undefined;
|
|
48
|
+
const direct = tryParse(text);
|
|
49
|
+
if (direct?.error)
|
|
50
|
+
return direct;
|
|
51
|
+
const start = text.indexOf("{");
|
|
52
|
+
const end = text.lastIndexOf("}");
|
|
53
|
+
if (start >= 0 && end > start) {
|
|
54
|
+
const parsed = tryParse(text.slice(start, end + 1));
|
|
55
|
+
if (parsed?.error)
|
|
56
|
+
return parsed;
|
|
57
|
+
}
|
|
58
|
+
for (const line of text.split(/\r?\n/)) {
|
|
59
|
+
const parsed = tryParse(line.trim());
|
|
60
|
+
if (parsed?.error)
|
|
61
|
+
return parsed;
|
|
62
|
+
}
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
function tryParse(text) {
|
|
66
|
+
if (!text)
|
|
67
|
+
return undefined;
|
|
68
|
+
try {
|
|
69
|
+
return JSON.parse(text);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/cli/errors.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ImProviderSpec } from "../providers/types.js";
|
|
2
|
+
export interface CliRunOptions {
|
|
3
|
+
env?: NodeJS.ProcessEnv;
|
|
4
|
+
cwd?: string;
|
|
5
|
+
signal?: AbortSignal;
|
|
6
|
+
/** 覆盖 bin(测试注入 fake CLI 用)。 */
|
|
7
|
+
bin?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface CliExecution {
|
|
10
|
+
/** 人类可读命令,如 `wecom-cli contact users search`。 */
|
|
11
|
+
command: string;
|
|
12
|
+
args: string[];
|
|
13
|
+
stdout: string;
|
|
14
|
+
stderr: string;
|
|
15
|
+
exitCode: number | null;
|
|
16
|
+
/** stdout 解析后的 JSON(无法解析时为原始文本)。 */
|
|
17
|
+
data: unknown;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* 通用 CLI 透传执行器:按 provider 规格定位 CLI(npm 全局 bin 脚本 → node 直跑,
|
|
21
|
+
* 规避 Windows shim),追加 provider 的 formatArgs,收集 stdout/stderr,解析 JSON,
|
|
22
|
+
* 非零退出时映射错误信封为 CliError。
|
|
23
|
+
*/
|
|
24
|
+
export declare class CliRunner {
|
|
25
|
+
private readonly spec;
|
|
26
|
+
constructor(spec: ImProviderSpec);
|
|
27
|
+
get providerId(): string;
|
|
28
|
+
/** 运行并返回解析后的 stdout JSON;失败抛 CliError。 */
|
|
29
|
+
runJson(args: string[], opts?: CliRunOptions): Promise<unknown>;
|
|
30
|
+
run(args: string[], opts?: CliRunOptions): Promise<CliExecution>;
|
|
31
|
+
private launch;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/cli/runner.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAG5D,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,+BAA+B;IAC/B,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,YAAY;IAC3B,iDAAiD;IACjD,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,oCAAoC;IACpC,IAAI,EAAE,OAAO,CAAC;CACf;AAQD;;;;GAIG;AACH,qBAAa,SAAS;IACR,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,cAAc;IAEjD,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED,0CAA0C;IACpC,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC;IAK/D,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC;YAmExD,MAAM;CAwBrB"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { CliError, parseErrorEnvelope } from "./errors.js";
|
|
5
|
+
/**
|
|
6
|
+
* 通用 CLI 透传执行器:按 provider 规格定位 CLI(npm 全局 bin 脚本 → node 直跑,
|
|
7
|
+
* 规避 Windows shim),追加 provider 的 formatArgs,收集 stdout/stderr,解析 JSON,
|
|
8
|
+
* 非零退出时映射错误信封为 CliError。
|
|
9
|
+
*/
|
|
10
|
+
export class CliRunner {
|
|
11
|
+
spec;
|
|
12
|
+
constructor(spec) {
|
|
13
|
+
this.spec = spec;
|
|
14
|
+
}
|
|
15
|
+
get providerId() {
|
|
16
|
+
return this.spec.id;
|
|
17
|
+
}
|
|
18
|
+
/** 运行并返回解析后的 stdout JSON;失败抛 CliError。 */
|
|
19
|
+
async runJson(args, opts) {
|
|
20
|
+
const exec = await this.run(args, opts);
|
|
21
|
+
return exec.data;
|
|
22
|
+
}
|
|
23
|
+
async run(args, opts) {
|
|
24
|
+
const spec = await this.launch(opts?.bin);
|
|
25
|
+
const finalArgs = [...spec.prefix, ...args, ...this.spec.formatArgs];
|
|
26
|
+
const command = `${this.spec.bin} ${args.join(" ")}`;
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
const child = spawn(spec.command, finalArgs, {
|
|
29
|
+
shell: spec.shell,
|
|
30
|
+
cwd: opts?.cwd,
|
|
31
|
+
env: { ...process.env, ...opts?.env },
|
|
32
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
33
|
+
windowsHide: true,
|
|
34
|
+
});
|
|
35
|
+
let stdout = "";
|
|
36
|
+
let stderr = "";
|
|
37
|
+
child.stdout.on("data", (d) => {
|
|
38
|
+
stdout += d.toString("utf8");
|
|
39
|
+
});
|
|
40
|
+
child.stderr.on("data", (d) => {
|
|
41
|
+
stderr += d.toString("utf8");
|
|
42
|
+
});
|
|
43
|
+
const onAbort = () => {
|
|
44
|
+
try {
|
|
45
|
+
child.kill("SIGTERM");
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
/* ignore */
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
if (opts?.signal) {
|
|
52
|
+
if (opts.signal.aborted)
|
|
53
|
+
onAbort();
|
|
54
|
+
else
|
|
55
|
+
opts.signal.addEventListener("abort", onAbort, { once: true });
|
|
56
|
+
}
|
|
57
|
+
child.on("error", (e) => {
|
|
58
|
+
reject(new CliError({
|
|
59
|
+
message: `Failed to launch ${this.spec.bin} (${spec.command}): ${e.message}`,
|
|
60
|
+
command,
|
|
61
|
+
retryable: true,
|
|
62
|
+
}));
|
|
63
|
+
});
|
|
64
|
+
child.on("exit", (code) => {
|
|
65
|
+
opts?.signal?.removeEventListener("abort", onAbort);
|
|
66
|
+
const parsed = parseStdoutJson(stdout);
|
|
67
|
+
if (code !== 0) {
|
|
68
|
+
const envelope = parseErrorEnvelope(stderr);
|
|
69
|
+
reject(new CliError({
|
|
70
|
+
message: envelope?.error?.message ?? stderr.trim() ?? `${this.spec.bin} exited with code ${code}`,
|
|
71
|
+
command,
|
|
72
|
+
stderr,
|
|
73
|
+
exitCode: code,
|
|
74
|
+
code: envelope?.error?.code,
|
|
75
|
+
category: envelope?.error?.category,
|
|
76
|
+
reason: envelope?.error?.reason,
|
|
77
|
+
retryable: envelope?.error?.retryable,
|
|
78
|
+
hint: envelope?.error?.hint,
|
|
79
|
+
actions: envelope?.error?.actions,
|
|
80
|
+
}));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
resolve({ command, args: finalArgs, stdout, stderr, exitCode: code, data: parsed });
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
async launch(binOverride) {
|
|
88
|
+
const env = process.env[this.spec.envBin ?? ""]?.trim();
|
|
89
|
+
const bin = binOverride?.trim() || env || this.spec.bin;
|
|
90
|
+
// 1) 显式 node 脚本
|
|
91
|
+
if (bin !== this.spec.bin && /\.(c?js|mjs)$/i.test(bin) && existsSync(bin)) {
|
|
92
|
+
return { command: process.execPath, prefix: [bin], shell: false };
|
|
93
|
+
}
|
|
94
|
+
// 2) 显式可执行文件
|
|
95
|
+
if (bin !== this.spec.bin && existsSync(bin)) {
|
|
96
|
+
return { command: bin, prefix: [], shell: false };
|
|
97
|
+
}
|
|
98
|
+
// 3) 默认 bin:解析 npm 全局包内脚本
|
|
99
|
+
if (bin === this.spec.bin) {
|
|
100
|
+
const globalRoot = await npmGlobalRoot();
|
|
101
|
+
if (globalRoot) {
|
|
102
|
+
const script = join(globalRoot, ...this.spec.binScript.split("/"));
|
|
103
|
+
if (existsSync(script)) {
|
|
104
|
+
return { command: process.execPath, prefix: [script], shell: false };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// 4) 回退:shell 执行(Windows shim)
|
|
109
|
+
return { command: bin, prefix: [], shell: process.platform === "win32" };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function parseStdoutJson(stdout) {
|
|
113
|
+
const text = stdout.trim();
|
|
114
|
+
if (!text)
|
|
115
|
+
return null;
|
|
116
|
+
try {
|
|
117
|
+
return JSON.parse(text);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return text;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
async function npmGlobalRoot() {
|
|
124
|
+
try {
|
|
125
|
+
const { execFile } = await import("node:child_process");
|
|
126
|
+
const { promisify } = await import("node:util");
|
|
127
|
+
const run = promisify(execFile);
|
|
128
|
+
const { stdout } = await run("npm", ["root", "-g"], { timeout: 10_000, windowsHide: true });
|
|
129
|
+
const root = String(stdout).trim();
|
|
130
|
+
return root || null;
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../../src/cli/runner.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { CliError, isCliError, parseErrorEnvelope, type CliErrorEnvelope } from "./cli/errors.js";
|
|
2
|
+
export { CliRunner, type CliExecution, type CliRunOptions } from "./cli/runner.js";
|
|
3
|
+
export type { ImProviderSpec } from "./providers/types.js";
|
|
4
|
+
export { dingtalk } from "./providers/dingtalk.js";
|
|
5
|
+
export { wecom } from "./providers/wecom.js";
|
|
6
|
+
export { getProvider, listProviders } from "./providers/registry.js";
|
|
7
|
+
export { buildMcpServer, type McpServerOptions } from "./mcp/server.js";
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,kBAAkB,EAAE,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAClG,OAAO,EAAE,SAAS,EAAE,KAAK,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACnF,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACnD,OAAO,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { CliError, isCliError, parseErrorEnvelope } from "./cli/errors.js";
|
|
2
|
+
export { CliRunner } from "./cli/runner.js";
|
|
3
|
+
export { dingtalk } from "./providers/dingtalk.js";
|
|
4
|
+
export { wecom } from "./providers/wecom.js";
|
|
5
|
+
export { getProvider, listProviders } from "./providers/registry.js";
|
|
6
|
+
export { buildMcpServer } from "./mcp/server.js";
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,kBAAkB,EAAyB,MAAM,iBAAiB,CAAC;AAClG,OAAO,EAAE,SAAS,EAAyC,MAAM,iBAAiB,CAAC;AAEnF,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACnD,OAAO,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACrE,OAAO,EAAE,cAAc,EAAyB,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
/**
|
|
4
|
+
* 构建统一 IM 透传 MCP server:暴露一个 `run { provider, args }` 工具,
|
|
5
|
+
* 按 provider 注册表路由到底层 CLI。新增平台只需在 registry 追加 spec。
|
|
6
|
+
*/
|
|
7
|
+
export interface McpServerOptions {
|
|
8
|
+
name?: string;
|
|
9
|
+
version?: string;
|
|
10
|
+
/** 测试/覆盖用:provider id → CLI 入口路径。 */
|
|
11
|
+
binOverrides?: Record<string, string>;
|
|
12
|
+
}
|
|
13
|
+
export declare function buildMcpServer(options?: McpServerOptions): Server;
|
|
14
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AA6CnE;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qCAAqC;IACrC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACvC;AAED,wBAAgB,cAAc,CAAC,OAAO,GAAE,gBAAqB,GAAG,MAAM,CA+DrE"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
6
|
+
import { getProvider, listProviders } from "../providers/registry.js";
|
|
7
|
+
import { CliRunner } from "../cli/runner.js";
|
|
8
|
+
import { isCliError } from "../cli/errors.js";
|
|
9
|
+
function buildRunTool() {
|
|
10
|
+
const providers = listProviders();
|
|
11
|
+
const lines = providers
|
|
12
|
+
.map((p) => {
|
|
13
|
+
const ex = p.examples.map((e) => ` ${e}`).join("\n");
|
|
14
|
+
return `- ${p.id}(${p.displayName},\`${p.bin}\`):\n${ex}`;
|
|
15
|
+
})
|
|
16
|
+
.join("\n");
|
|
17
|
+
return {
|
|
18
|
+
name: "run",
|
|
19
|
+
description: `统一 IM 透传:执行指定平台的 CLI 命令并返回其 JSON 输出。已接入平台:\n${lines}\n\n` +
|
|
20
|
+
`provider 选平台,args 是该平台 CLI 的命令字符串数组。写操作(发消息/提交/审批等)需用户明确意图,必要时加 --yes(钉钉)或先用 --dry-run 预览。`,
|
|
21
|
+
inputSchema: {
|
|
22
|
+
type: "object",
|
|
23
|
+
properties: {
|
|
24
|
+
provider: {
|
|
25
|
+
type: "string",
|
|
26
|
+
enum: providers.map((p) => p.id),
|
|
27
|
+
description: `IM 平台标识:${providers.map((p) => `${p.id}=${p.displayName}`).join(",")}`,
|
|
28
|
+
},
|
|
29
|
+
args: {
|
|
30
|
+
type: "array",
|
|
31
|
+
items: { type: "string" },
|
|
32
|
+
description: "对应平台 CLI 的命令及参数(字符串数组)。",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
required: ["provider", "args"],
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export function buildMcpServer(options = {}) {
|
|
40
|
+
const server = new Server({ name: options.name ?? "im", version: options.version ?? "0.1.0" }, { capabilities: { tools: {} } });
|
|
41
|
+
const runTool = buildRunTool();
|
|
42
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
43
|
+
tools: [runTool],
|
|
44
|
+
}));
|
|
45
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
46
|
+
if (request.params.name !== "run") {
|
|
47
|
+
return {
|
|
48
|
+
content: [{ type: "text", text: `Unknown tool: ${request.params.name}. Only "run" is available.` }],
|
|
49
|
+
isError: true,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const { provider, args } = request.params.arguments ?? {};
|
|
53
|
+
const spec = getProvider(provider);
|
|
54
|
+
if (!spec) {
|
|
55
|
+
return {
|
|
56
|
+
content: [
|
|
57
|
+
{
|
|
58
|
+
type: "text",
|
|
59
|
+
text: `未知平台 "${provider}"。可用:${listProviders().map((p) => p.id).join(",")}`,
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
isError: true,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (!Array.isArray(args) || args.length === 0 || !args.every((a) => typeof a === "string")) {
|
|
66
|
+
return {
|
|
67
|
+
content: [
|
|
68
|
+
{
|
|
69
|
+
type: "text",
|
|
70
|
+
text: '参数 args 必须是非空字符串数组,例如 {"provider":"dingtalk","args":["contact","+me"]}',
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
isError: true,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const runner = new CliRunner(spec);
|
|
78
|
+
const result = await runner.runJson(args, {
|
|
79
|
+
bin: options.binOverrides?.[spec.id],
|
|
80
|
+
});
|
|
81
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
82
|
+
}
|
|
83
|
+
catch (e) {
|
|
84
|
+
const message = isCliError(e)
|
|
85
|
+
? JSON.stringify(e.toJSON())
|
|
86
|
+
: e instanceof Error
|
|
87
|
+
? e.message
|
|
88
|
+
: String(e);
|
|
89
|
+
return {
|
|
90
|
+
content: [{ type: "text", text: `${spec.displayName} run failed: ${message}` }],
|
|
91
|
+
isError: true,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
return server;
|
|
96
|
+
}
|
|
97
|
+
async function main() {
|
|
98
|
+
const server = buildMcpServer();
|
|
99
|
+
await server.connect(new StdioServerTransport());
|
|
100
|
+
}
|
|
101
|
+
const invokedDirectly = process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
102
|
+
if (invokedDirectly) {
|
|
103
|
+
main().catch((e) => {
|
|
104
|
+
console.error(e);
|
|
105
|
+
process.exit(1);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dingtalk.d.ts","sourceRoot":"","sources":["../../src/providers/dingtalk.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,2CAA2C;AAC3C,eAAO,MAAM,QAAQ,EAAE,cAoBtB,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** 钉钉:通过 dws(dingtalk-workspace-cli)透传。 */
|
|
2
|
+
export const dingtalk = {
|
|
3
|
+
id: "dingtalk",
|
|
4
|
+
displayName: "钉钉",
|
|
5
|
+
bin: "dws",
|
|
6
|
+
npmPackage: "dingtalk-workspace-cli",
|
|
7
|
+
binScript: "dingtalk-workspace-cli/bin/dws.js",
|
|
8
|
+
formatArgs: ["--format", "json"],
|
|
9
|
+
envBin: "DINGTALK_DWS_BIN",
|
|
10
|
+
examples: [
|
|
11
|
+
'["contact","+me"]',
|
|
12
|
+
'["contact","+search-user","--query","张三"]',
|
|
13
|
+
'["calendar","+today"]',
|
|
14
|
+
'["todo","+get-my-tasks"]',
|
|
15
|
+
'["oa","approval","list-pending"] / ["oa","approval","detail","--instance-id","<id>"]',
|
|
16
|
+
'["attendance","+check-result","--users","u1,u2","--start","2026-10-01","--end","2026-10-10"]',
|
|
17
|
+
'["report","inbox","list"] / ["report","entry","get","--report-id","<id>"]',
|
|
18
|
+
'["doc","read","--node","<docId|url>"] / ["wiki","+space-list"]',
|
|
19
|
+
'["chat","+chat-messages","--group","<群名|id>","--limit","20"] / ["chat","+search-msg","--query","<kw>"]',
|
|
20
|
+
'写:["chat","+messages-send","--group","<cid>","--markdown","内容","--title","标题","--yes"]',
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=dingtalk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dingtalk.js","sourceRoot":"","sources":["../../src/providers/dingtalk.ts"],"names":[],"mappings":"AAEA,2CAA2C;AAC3C,MAAM,CAAC,MAAM,QAAQ,GAAmB;IACtC,EAAE,EAAE,UAAU;IACd,WAAW,EAAE,IAAI;IACjB,GAAG,EAAE,KAAK;IACV,UAAU,EAAE,wBAAwB;IACpC,SAAS,EAAE,mCAAmC;IAC9C,UAAU,EAAE,CAAC,UAAU,EAAE,MAAM,CAAC;IAChC,MAAM,EAAE,kBAAkB;IAC1B,QAAQ,EAAE;QACR,mBAAmB;QACnB,2CAA2C;QAC3C,uBAAuB;QACvB,0BAA0B;QAC1B,sFAAsF;QACtF,8FAA8F;QAC9F,2EAA2E;QAC3E,gEAAgE;QAChE,wGAAwG;QACxG,wFAAwF;KACzF;CACF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/providers/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAOjD,wBAAgB,aAAa,IAAI,cAAc,EAAE,CAEhD;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAElE"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { dingtalk } from "./dingtalk.js";
|
|
2
|
+
import { wecom } from "./wecom.js";
|
|
3
|
+
/** 已注册的 IM 平台。新增飞书 / Discord 时在此追加。 */
|
|
4
|
+
const providers = [dingtalk, wecom];
|
|
5
|
+
export function listProviders() {
|
|
6
|
+
return providers;
|
|
7
|
+
}
|
|
8
|
+
export function getProvider(id) {
|
|
9
|
+
return providers.find((p) => p.id === id);
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/providers/registry.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,uCAAuC;AACvC,MAAM,SAAS,GAAqB,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;AAEtD,MAAM,UAAU,aAAa;IAC3B,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,EAAU;IACpC,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AAC5C,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 一个 IM 平台(provider)接入规格:如何找到它的 CLI、如何请求 JSON 输出。
|
|
3
|
+
* 未来接入飞书 / Discord 等,只需新增一个 spec 并注册进 registry。
|
|
4
|
+
*/
|
|
5
|
+
export interface ImProviderSpec {
|
|
6
|
+
/** 稳定 id,用于 MCP 工具的 provider 参数,如 "dingtalk" / "wecom"。 */
|
|
7
|
+
id: string;
|
|
8
|
+
/** 人类可读名称。 */
|
|
9
|
+
displayName: string;
|
|
10
|
+
/** 默认命令名(PATH 上的 shim),如 "dws" / "wecom-cli"。 */
|
|
11
|
+
bin: string;
|
|
12
|
+
/** npm 全局包名,用于解析 bin 脚本(规避 Windows .cmd/.ps1 shim)。 */
|
|
13
|
+
npmPackage: string;
|
|
14
|
+
/** 包内 bin 脚本相对路径(含作用域,用 / 分隔),如 "@wecom/cli/bin/wecom.js"。 */
|
|
15
|
+
binScript: string;
|
|
16
|
+
/** 追加在业务参数之后、用于请求 JSON 输出的 flag;JSON 为默认输出的 CLI 用空数组。 */
|
|
17
|
+
formatArgs: string[];
|
|
18
|
+
/** 覆盖 bin 的环境变量名(可选),如 "DINGTALK_DWS_BIN"。 */
|
|
19
|
+
envBin?: string;
|
|
20
|
+
/** 展示在 MCP 工具描述里的示例命令(数组形式,便于模型理解)。 */
|
|
21
|
+
examples: string[];
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/providers/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,2DAA2D;IAC3D,EAAE,EAAE,MAAM,CAAC;IACX,cAAc;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,iDAAiD;IACjD,GAAG,EAAE,MAAM,CAAC;IACZ,uDAAuD;IACvD,UAAU,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,SAAS,EAAE,MAAM,CAAC;IAClB,yDAAyD;IACzD,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,8CAA8C;IAC9C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uCAAuC;IACvC,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/providers/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wecom.d.ts","sourceRoot":"","sources":["../../src/providers/wecom.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,8DAA8D;AAC9D,eAAO,MAAM,KAAK,EAAE,cAcnB,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** 企业微信:通过 wecom-cli(@wecom/cli)透传。JSON 为默认输出,无需 --format。 */
|
|
2
|
+
export const wecom = {
|
|
3
|
+
id: "wecom",
|
|
4
|
+
displayName: "企业微信",
|
|
5
|
+
bin: "wecom-cli",
|
|
6
|
+
npmPackage: "@wecom/cli",
|
|
7
|
+
binScript: "@wecom/cli/bin/wecom.js",
|
|
8
|
+
formatArgs: [],
|
|
9
|
+
envBin: "WECOM_CLI_BIN",
|
|
10
|
+
examples: [
|
|
11
|
+
'["contact","users","search","--keywords","张三"]',
|
|
12
|
+
'["chat","messages","list","--chat-id","<id>","--begin-time","2026-10-01 00:00:00","--end-time","2026-10-21 00:00:00"]',
|
|
13
|
+
'["auth","show","--status"]',
|
|
14
|
+
'通用形态:["<service>","<resource>","<operation>",...flags];--schema 查看方法 schema,--dry-run 预览不发送。',
|
|
15
|
+
],
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=wecom.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wecom.js","sourceRoot":"","sources":["../../src/providers/wecom.ts"],"names":[],"mappings":"AAEA,8DAA8D;AAC9D,MAAM,CAAC,MAAM,KAAK,GAAmB;IACnC,EAAE,EAAE,OAAO;IACX,WAAW,EAAE,MAAM;IACnB,GAAG,EAAE,WAAW;IAChB,UAAU,EAAE,YAAY;IACxB,SAAS,EAAE,yBAAyB;IACpC,UAAU,EAAE,EAAE;IACd,MAAM,EAAE,eAAe;IACvB,QAAQ,EAAE;QACR,gDAAgD;QAChD,uHAAuH;QACvH,4BAA4B;QAC5B,8FAA8F;KAC/F;CACF,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# mcp-bundle:DSH MCP 连接 bundle
|
|
2
|
+
|
|
3
|
+
按 DSH 官方推荐方式(`references/mcp-bundle.md`「Connect an MCP server」)做的**配置型 bundle**:只插入一条 `@deepseek-ai/dsh-mcp-client` 行,把 `im-connector` 注册成一个 MCP 连接,模型侧工具名为 `mcp__im__run`。
|
|
4
|
+
|
|
5
|
+
## 推荐安装(一步到位)
|
|
6
|
+
|
|
7
|
+
npm 包 `im-connector` 本身声明了 `dsh.bundle.patch`(指向本目录的 patch),所以装上包即完成注册:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
plugin_manager install_bundle target: im-connector
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
安装后 DSH 会:装包 → 应用 patch → 新增 MCP 连接(`serverName: im`),工具为 `mcp__im__run`。
|
|
14
|
+
|
|
15
|
+
## 手工安装(已有包或本地调试)
|
|
16
|
+
|
|
17
|
+
把本目录复制到任意位置,直接 `plugin_manager install_bundle`(target 指向该目录)。patch 用的是官方 stdio 形式:
|
|
18
|
+
|
|
19
|
+
```yaml
|
|
20
|
+
- insert:
|
|
21
|
+
- id: im-connector-mcp
|
|
22
|
+
name: '@deepseek-ai/dsh-mcp-client'
|
|
23
|
+
config:
|
|
24
|
+
serverName: im
|
|
25
|
+
transport: stdio
|
|
26
|
+
command: npx
|
|
27
|
+
args: ['-y', 'im-connector']
|
|
28
|
+
failOnStartupError: true
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
要点:
|
|
32
|
+
- `command: npx` 跨平台可用(MCP SDK 的 stdio 传输基于 cross-spawn,Windows 能解析 `npx.cmd`)。
|
|
33
|
+
- **不需要配 env**:`im-connector` 会通过 `npm root -g` 自动解析 `dws` / `wecom-cli` 的 bin 脚本。需要覆盖时再加 env,且**不要把凭据写进 patch**(可用 Loader 的 `!!js process.env.X` 引用环境变量)。
|
|
34
|
+
- `failOnStartupError: true`:连接失败时让该行激活失败,便于发现问题(官方模板同款)。
|
|
35
|
+
- 对照官方 `templates/mcp/`:manifest 只需 name/version/`dsh.bundle.patch`(配置型 bundle 无 Host/Client 入口文件),display 元数据走 `locale/*.json` + `icon.svg`。
|
|
36
|
+
|
|
37
|
+
## 验证
|
|
38
|
+
|
|
39
|
+
安装后在会话里调用 `mcp__im__run`(例如 `{ provider: "dingtalk", args: ["contact", "+me"] }`)确认连接可用。
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
|
2
|
+
<rect x="4" y="20" width="14" height="24" rx="5" fill="#0089ff"/>
|
|
3
|
+
<rect x="22" y="12" width="14" height="24" rx="5" fill="#0089ff"/>
|
|
4
|
+
<rect x="40" y="20" width="14" height="24" rx="5" fill="#0089ff"/>
|
|
5
|
+
<path d="M20 32h16" stroke="#0089ff" stroke-width="3" stroke-linecap="round"/>
|
|
6
|
+
<circle cx="32" cy="44" r="3" fill="#4aa3ff"/>
|
|
7
|
+
</svg>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@local/im-connector-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": { "./locale/*.json": "./locale/*.json", "./icon": "./icon.svg" },
|
|
7
|
+
"files": ["cordis.patch.yml", "locale/*.json", "icon.svg"],
|
|
8
|
+
"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }
|
|
9
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,66 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "im-connector",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server that lets agents drive DingTalk (dws) and WeCom (wecom-cli) CLIs: send messages, write reports, create todos, query calendars and approvals. Extensible provider registry for more IM platforms.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./dist/index.js",
|
|
10
|
+
"./locale/*.json": "./mcp-bundle/locale/*.json",
|
|
11
|
+
"./icon": "./mcp-bundle/icon.svg"
|
|
12
|
+
},
|
|
13
|
+
"bin": {
|
|
14
|
+
"im-connector": "dist/mcp/server.js"
|
|
15
|
+
},
|
|
16
|
+
"dsh": {
|
|
17
|
+
"bundle": {
|
|
18
|
+
"patch": "./mcp-bundle/cordis.patch.yml"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"mcp-bundle",
|
|
24
|
+
"README.md",
|
|
25
|
+
"LICENSE"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsc -p tsconfig.json",
|
|
29
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
30
|
+
"test": "vitest run",
|
|
31
|
+
"test:watch": "vitest",
|
|
32
|
+
"mcp": "node dist/mcp/server.js",
|
|
33
|
+
"smoke:mcp": "node scripts/mcp-smoke.mjs",
|
|
34
|
+
"verify": "pnpm typecheck && pnpm test && pnpm build && pnpm smoke:mcp",
|
|
35
|
+
"prepublishOnly": "npm run typecheck && npm run test && npm run build && npm run smoke:mcp"
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=18"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@modelcontextprotocol/sdk": "^1.12.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/node": "^22.10.0",
|
|
45
|
+
"typescript": "^5.7.0",
|
|
46
|
+
"vitest": "^3.0.0"
|
|
47
|
+
},
|
|
48
|
+
"keywords": [
|
|
49
|
+
"mcp",
|
|
50
|
+
"mcp-server",
|
|
51
|
+
"modelcontextprotocol",
|
|
52
|
+
"im",
|
|
53
|
+
"dingtalk",
|
|
54
|
+
"dws",
|
|
55
|
+
"wecom",
|
|
56
|
+
"wecom-cli",
|
|
57
|
+
"connector",
|
|
58
|
+
"agent",
|
|
59
|
+
"feishu",
|
|
60
|
+
"discord"
|
|
61
|
+
],
|
|
62
|
+
"publishConfig": {
|
|
63
|
+
"access": "public"
|
|
64
|
+
},
|
|
65
|
+
"license": "MIT"
|
|
66
|
+
}
|