Skip to content

Commit c957e2e

Browse files
committed
feat(mcp): add Cursor, Qoder, Qoder Work, and QwenWork native MCP targets
1 parent abfb69b commit c957e2e

13 files changed

Lines changed: 290 additions & 53 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ _Built for AI Agents. Every command works as a structured tool call._
2626
- **Model generation** — Full-modality generation across text, image, video, and speech, with editing and reference-based generation
2727
- **Asset understanding** — Parse and ask questions about images, documents, audio, and long videos
2828
- **App orchestration** — Call Managed Agents, agents, and workflows published on Aliyun Model Studio, wired to knowledge bases, memory, web search, and MCP tools
29-
- **Native MCP setup** — Register Bailian MCP servers in Codex, Claude Code, Qwen Code, or Gemini CLI with channel attribution preserved
29+
- **Native MCP setup** — Register Bailian MCP servers in Codex, Claude Code, Cursor, Qoder, Qoder Work, QwenWork (千问办公), Qwen Code, or Gemini CLI with channel attribution preserved
3030
- **Training & deployment** — Validate and upload datasets, fine-tune models, deploy dedicated models as endpoints
3131
- **Account operations** — Login, UI-based configuration, model marketplace, usage and quota, rate-limit increases, team seat management
3232
- **Plan onboarding** — Connect subscription plans such as Token Plan to the CLI and common coding agents in one step

README.zh.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ _专为 AI Agent 打造,每个命令均可作为结构化工具调用。_
2525
- **模型生成** — 文本、图像、视频、语音全模态生成,支持编辑与参考生成
2626
- **素材理解** — 图像、文档、音频、长视频的解析与问答
2727
- **应用编排** — 调用百炼已发布的 Managed Agent、智能体和工作流,接入知识库、记忆库、联网搜索与 MCP 工具
28-
- **原生 MCP 接入** — 将百炼 MCP 服务注册到 Codex、Claude Code、Qwen Code 或 Gemini CLI,并保留渠道归因
28+
- **原生 MCP 接入** — 将百炼 MCP 服务注册到 Codex、Claude Code、Cursor、Qoder、Qoder Work、千问办公(QwenWork)、Qwen Code 或 Gemini CLI,并保留渠道归因
2929
- **模型训推** — 数据集校验上传、模型精调、专属模型部署上线
3030
- **账号运维** — 授权登录、界面化配置、模型市场、用量与额度、限流提额、团队席位管理
3131
- **套餐接入** — 支持 Token Plan 等订阅计划一键接到 CLI 和常见 Coding Agent

packages/cli/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ _Built for AI Agents. Every command works as a structured tool call._
2626
- **Model generation** — Full-modality generation across text, image, video, and speech, with editing and reference-based generation
2727
- **Asset understanding** — Parse and ask questions about images, documents, audio, and long videos
2828
- **App orchestration** — Call Managed Agents, agents, and workflows published on Aliyun Model Studio, wired to knowledge bases, memory, web search, and MCP tools
29-
- **Native MCP setup** — Register Bailian MCP servers in Codex, Claude Code, Qwen Code, or Gemini CLI with channel attribution preserved
29+
- **Native MCP setup** — Register Bailian MCP servers in Codex, Claude Code, Cursor, Qoder, Qoder Work, QwenWork (千问办公), Qwen Code, or Gemini CLI with channel attribution preserved
3030
- **Training & deployment** — Validate and upload datasets, fine-tune models, deploy dedicated models as endpoints
3131
- **Account operations** — Login, UI-based configuration, model marketplace, usage and quota, rate-limit increases, team seat management
3232
- **Plan onboarding** — Connect subscription plans such as Token Plan to the CLI and common coding agents in one step

packages/cli/README.zh.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ _专为 AI Agent 打造,每个命令均可作为结构化工具调用。_
2525
- **模型生成** — 文本、图像、视频、语音全模态生成,支持编辑与参考生成
2626
- **素材理解** — 图像、文档、音频、长视频的解析与问答
2727
- **应用编排** — 调用百炼已发布的 Managed Agent、智能体和工作流,接入知识库、记忆库、联网搜索与 MCP 工具
28-
- **原生 MCP 接入** — 将百炼 MCP 服务注册到 Codex、Claude Code、Qwen Code 或 Gemini CLI,并保留渠道归因
28+
- **原生 MCP 接入** — 将百炼 MCP 服务注册到 Codex、Claude Code、Cursor、Qoder、Qoder Work、千问办公(QwenWork)、Qwen Code 或 Gemini CLI,并保留渠道归因
2929
- **模型训推** — 数据集校验上传、模型精调、专属模型部署上线
3030
- **账号运维** — 授权登录、界面化配置、模型市场、用量与额度、限流提额、团队席位管理
3131
- **套餐接入** — 支持 Token Plan 等订阅计划一键接到 CLI 和常见 Coding Agent

packages/commands/src/commands/config/inventory.ts

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
import { inflateRawSync } from "node:zlib";
2020
import yaml from "yaml";
2121
import { parse as parseToml } from "smol-toml";
22+
import { qwenworkMcpPath } from "../mcp/agent-config.ts";
2223

2324
/**
2425
* Where an item comes from. Everything discovered on disk today is `local`;
@@ -453,20 +454,26 @@ export function listMcpServers(home: string = homedir()): McpServerInfo[] {
453454
const opencode = readJsonSafe(join(home, ".config", "opencode", "opencode.json"));
454455
if (opencode) collectMcpMap(opencode.mcp, "opencode", "global", out, true);
455456

456-
// Cursor, Windsurf, Gemini, QoderWork, OpenClaw, Claude Desktop: all JSON with
457-
// a top-level `mcpServers` map (Claude/Cursor convention).
457+
// Cursor, Windsurf, Gemini, Qoder, Qoder Work, QwenWork (千问办公), OpenClaw,
458+
// Claude Desktop: JSON with a top-level `mcpServers` map.
458459
const cursor = readJsonSafe(join(home, ".cursor", "mcp.json"));
459460
if (cursor) collectMcpMap(cursor.mcpServers, "cursor", "global", out, true);
460461

462+
const qoder = readJsonSafe(join(home, ".qoder", "mcp.json"));
463+
if (qoder) collectMcpMap(qoder.mcpServers, "qoder", "global", out, true);
464+
465+
const qoderwork = readJsonSafe(join(home, ".qoderwork", "mcp.json"));
466+
if (qoderwork) collectMcpMap(qoderwork.mcpServers, "qoderwork", "global", out, true);
467+
468+
const qwenwork = readJsonSafe(qwenworkMcpPath(home));
469+
if (qwenwork) collectMcpMap(qwenwork.mcpServers, "qwenwork", "global", out, true);
470+
461471
const windsurf = readJsonSafe(join(home, ".codeium", "windsurf", "mcp_config.json"));
462472
if (windsurf) collectMcpMap(windsurf.mcpServers, "windsurf", "global", out, true);
463473

464474
const gemini = readJsonSafe(join(home, ".gemini", "settings.json"));
465475
if (gemini) collectMcpMap(gemini.mcpServers, "gemini", "global", out, true);
466476

467-
const qoder = readJsonSafe(join(home, ".qoderwork", "mcp.json"));
468-
if (qoder) collectMcpMap(qoder.mcpServers, "qoderwork", "global", out, true);
469-
470477
const openclaw = readJsonSafe(join(home, ".openclaw", "openclaw.json"));
471478
if (openclaw) collectMcpMap(openclaw.mcpServers, "openclaw", "global", out, true);
472479

@@ -553,12 +560,24 @@ function mcpWriteTarget(source: string, scope: string, home: string): McpWriteTa
553560
mapKey: "mcpServers",
554561
projectScoped: false,
555562
};
563+
if (source === "qoder")
564+
return {
565+
file: join(home, ".qoder", "mcp.json"),
566+
mapKey: "mcpServers",
567+
projectScoped: false,
568+
};
556569
if (source === "qoderwork")
557570
return {
558571
file: join(home, ".qoderwork", "mcp.json"),
559572
mapKey: "mcpServers",
560573
projectScoped: false,
561574
};
575+
if (source === "qwenwork")
576+
return {
577+
file: qwenworkMcpPath(home),
578+
mapKey: "mcpServers",
579+
projectScoped: false,
580+
};
562581
if (source === "openclaw")
563582
return {
564583
file: join(home, ".openclaw", "openclaw.json"),

packages/commands/src/commands/mcp/agent-config.ts

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,16 @@ import {
1010
writeTextAtomic,
1111
} from "../config/agent/writers/utils.ts";
1212

13-
export const MCP_AGENT_IDS = ["codex", "claude-code", "qwen-code", "gemini"] as const;
13+
export const MCP_AGENT_IDS = [
14+
"codex",
15+
"claude-code",
16+
"cursor",
17+
"qoder",
18+
"qoderwork",
19+
"qwenwork",
20+
"qwen-code",
21+
"gemini",
22+
] as const;
1423

1524
export type NativeMcpAgent = (typeof MCP_AGENT_IDS)[number];
1625
export type McpTransport = "streamable-http" | "sse";
@@ -121,6 +130,54 @@ function serverMap(config: Record<string, unknown>, key: string): Record<string,
121130
return current;
122131
}
123132

133+
function qwenworkUserDataDirs(home: string): string[] {
134+
if (process.platform === "darwin") {
135+
const support = join(home, "Library", "Application Support");
136+
return [join(support, "QwenWorkCN"), join(support, "QwenWork"), join(support, "QwenWork CN")];
137+
}
138+
if (process.platform === "win32") {
139+
const appData = join(home, "AppData", "Roaming");
140+
return [join(appData, "QwenWorkCN"), join(appData, "QwenWork")];
141+
}
142+
return [join(home, ".config", "QwenWorkCN"), join(home, ".config", "QwenWork")];
143+
}
144+
145+
/** QwenWork / 千问办公 stores MCP config in Electron userData (`mcp.json`). */
146+
export function qwenworkMcpPath(home: string): string {
147+
const dirs = qwenworkUserDataDirs(home);
148+
for (const dir of dirs) {
149+
const file = join(dir, "mcp.json");
150+
if (existsSync(file)) return file;
151+
}
152+
for (const dir of dirs) {
153+
if (existsSync(dir)) return join(dir, "mcp.json");
154+
}
155+
return join(dirs[0], "mcp.json");
156+
}
157+
158+
function jsonMcpAdapter(options: {
159+
path: (home: string) => string;
160+
installed: (home: string) => boolean;
161+
typed?: boolean;
162+
}): AgentAdapter {
163+
return {
164+
path: options.path,
165+
installed: options.installed,
166+
supports: () => true,
167+
parse: parseJson,
168+
serialize: (config) => `${JSON.stringify(config, null, 2)}\n`,
169+
getServers: (config) => serverMap(config, "mcpServers"),
170+
buildEntry: (spec) =>
171+
options.typed
172+
? {
173+
type: spec.transport === "sse" ? "sse" : "http",
174+
url: spec.endpoint,
175+
headers: spec.headers,
176+
}
177+
: { url: spec.endpoint, headers: spec.headers },
178+
};
179+
}
180+
124181
const adapters: Record<NativeMcpAgent, AgentAdapter> = {
125182
codex: {
126183
path: (home) => join(process.env.CODEX_HOME || join(home, ".codex"), "config.toml"),
@@ -147,6 +204,26 @@ const adapters: Record<NativeMcpAgent, AgentAdapter> = {
147204
headers: spec.headers,
148205
}),
149206
},
207+
cursor: jsonMcpAdapter({
208+
path: (home) => join(home, ".cursor", "mcp.json"),
209+
installed: (home) =>
210+
existsSync(join(home, ".cursor")) || existsSync(join(home, ".cursor", "mcp.json")),
211+
}),
212+
qoder: jsonMcpAdapter({
213+
path: (home) => join(home, ".qoder", "mcp.json"),
214+
installed: (home) =>
215+
existsSync(join(home, ".qoder")) || existsSync(join(home, ".qoder", "mcp.json")),
216+
}),
217+
qoderwork: jsonMcpAdapter({
218+
path: (home) => join(home, ".qoderwork", "mcp.json"),
219+
installed: (home) =>
220+
existsSync(join(home, ".qoderwork")) || existsSync(join(home, ".qoderwork", "mcp.json")),
221+
}),
222+
qwenwork: jsonMcpAdapter({
223+
path: qwenworkMcpPath,
224+
installed: (home) => qwenworkUserDataDirs(home).some((dir) => existsSync(dir)),
225+
typed: true,
226+
}),
150227
"qwen-code": {
151228
path: (home) => join(home, ".qwen", "settings.json"),
152229
installed: (home) => existsSync(join(home, ".qwen")),

packages/commands/src/commands/mcp/connect.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,15 @@ export default defineCommand({
7474
"The resolved API key is written to the selected Agent's private local configuration. Existing unmanaged entries are never overwritten.",
7575
"zh-CN": "解析出的 API Key 会写入所选 Agent 的本地私有配置;CLI 不会覆盖非其管理的同名条目。",
7676
},
77+
{
78+
"en-US":
79+
"qoder, qoderwork, and qwenwork are independent products. qwenwork is QwenWork (千问办公); qoderwork is Qoder Work.",
80+
"zh-CN":
81+
"qoder、qoderwork、qwenwork 是彼此独立的产品。qwenwork 是千问办公(QwenWork);qoderwork 是 Qoder Work。",
82+
},
7783
],
7884
exampleArgs: [
79-
"--server TextGenerateImage --transport streamable-http --agent codex",
85+
"--server TextGenerateImage --transport streamable-http --agent cursor",
8086
"--server VideoGenerate --transport sse --agent claude-code",
8187
"--server TextGenerateImage --transport streamable-http --agent all",
8288
],

packages/commands/src/commands/mcp/disconnect.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export default defineCommand({
5454
},
5555
],
5656
exampleArgs: [
57-
"--server TextGenerateImage --agent codex",
57+
"--server TextGenerateImage --agent cursor",
5858
"--server TextGenerateImage --agent all",
5959
],
6060
async run(ctx) {

packages/commands/tests/e2e/mcp.e2e.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ describe("e2e: mcp", () => {
4343
const connect = await runCommandHelp(MCP_ROUTES, ["mcp", "connect", "--help"]);
4444
expect(connect.exitCode, connect.stderr).toBe(0);
4545
expect(connect.stderr).toMatch(/--server|--transport|streamable-http|--agent/i);
46+
expect(connect.stderr).toMatch(/cursor/);
47+
expect(connect.stderr).toMatch(/qoder/);
48+
expect(connect.stderr).toMatch(/qoderwork/);
49+
expect(connect.stderr).toMatch(/qwenwork/);
4650

4751
const disconnect = await runCommandHelp(MCP_ROUTES, ["mcp", "disconnect", "--help"]);
4852
expect(disconnect.exitCode, disconnect.stderr).toBe(0);

packages/commands/tests/inventory.test.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, symlinkSync } from "node:fs";
22
import { tmpdir } from "node:os";
3-
import { join } from "node:path";
3+
import { join, relative } from "node:path";
44
import { expect, test } from "vite-plus/test";
55
import {
66
listSkills,
77
listMcpServers,
88
listAgents,
99
getSkillDetail,
1010
} from "../src/commands/config/inventory.ts";
11+
import { qwenworkMcpPath } from "../src/commands/mcp/agent-config.ts";
1112

1213
/** Build an isolated fake $HOME and clean it up afterwards. */
1314
function withHome(fn: (home: string) => void): void {
@@ -145,6 +146,32 @@ test("listMcpServers 无配置时返回空数组", () => {
145146
});
146147
});
147148

149+
test("listMcpServers 区分 Qoder、Qoder Work 与千问办公 QwenWork", () => {
150+
withHome((home) => {
151+
write(
152+
home,
153+
".qoder/mcp.json",
154+
JSON.stringify({ mcpServers: { qoder: { url: "https://qoder" } } }),
155+
);
156+
write(
157+
home,
158+
".qoderwork/mcp.json",
159+
JSON.stringify({ mcpServers: { qoderwork: { url: "https://qoderwork" } } }),
160+
);
161+
write(
162+
home,
163+
relative(home, qwenworkMcpPath(home)),
164+
JSON.stringify({ mcpServers: { qwenwork: { type: "http", url: "https://qwenwork" } } }),
165+
);
166+
167+
const servers = listMcpServers(home);
168+
const byName = Object.fromEntries(servers.map((s) => [s.name, s]));
169+
expect(byName.qoder).toMatchObject({ source: "qoder" });
170+
expect(byName.qoderwork).toMatchObject({ source: "qoderwork" });
171+
expect(byName.qwenwork).toMatchObject({ source: "qwenwork" });
172+
});
173+
});
174+
148175
test("listAgents 报告安装与已连接 bailian-cli 的状态", () => {
149176
withHome((home) => {
150177
// Claude Code: installed + configured (base url present).

0 commit comments

Comments
 (0)