@bigbrain-work/mcp-connect 1.2.2 → 1.3.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 +33 -59
- package/bin/mcp-connect.js +4 -4
- package/bin/shiliu.js +8 -0
- package/package.json +9 -5
- package/src/arguments.js +75 -19
- package/src/authorization.js +49 -0
- package/src/cli.js +424 -73
- package/src/configurators.js +118 -137
- package/src/constants.js +12 -6
- package/src/credentials.js +166 -148
- package/src/detection.js +16 -21
- package/src/device-auth-client.js +130 -0
- package/src/login-flow.js +39 -0
- package/src/proxy.js +59 -58
- package/src/remote-client.js +27 -10
- package/src/security.js +63 -0
- package/src/status.js +117 -58
- package/src/token-store.js +116 -0
- package/src/updater.js +59 -0
package/src/configurators.js
CHANGED
|
@@ -1,205 +1,186 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { PACKAGE_NAME, SERVER_NAME } from "./constants.js";
|
|
7
7
|
|
|
8
|
-
export function
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
8
|
+
export function stdioServerDefinition(platform = process.platform) {
|
|
9
|
+
if (platform === "win32") {
|
|
10
|
+
return {
|
|
11
|
+
command: "cmd",
|
|
12
|
+
args: ["/d", "/s", "/c", "npx", "-y", PACKAGE_NAME, "mcp"],
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
command: "npx",
|
|
17
|
+
args: ["-y", PACKAGE_NAME, "mcp"],
|
|
18
|
+
};
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
export function
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
url: MCP_URL,
|
|
24
|
-
headers: {
|
|
25
|
-
Authorization: `Bearer \${${API_KEY_ENV}}`,
|
|
26
|
-
},
|
|
27
|
-
}
|
|
21
|
+
export function codexArguments(platform = process.platform) {
|
|
22
|
+
const proxy = stdioServerDefinition(platform);
|
|
23
|
+
return ["mcp", "add", SERVER_NAME, "--", proxy.command, ...proxy.args];
|
|
28
24
|
}
|
|
29
25
|
|
|
30
|
-
export function
|
|
26
|
+
export function claudeServerDefinition(platform = process.platform) {
|
|
31
27
|
return {
|
|
32
|
-
type:
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
Authorization: `Bearer \${env:${API_KEY_ENV}}`,
|
|
36
|
-
},
|
|
37
|
-
}
|
|
28
|
+
type: "stdio",
|
|
29
|
+
...stdioServerDefinition(platform),
|
|
30
|
+
};
|
|
38
31
|
}
|
|
39
32
|
|
|
33
|
+
export const cursorServerDefinition = stdioServerDefinition;
|
|
34
|
+
|
|
40
35
|
function commandText(command, args) {
|
|
41
|
-
return [command, ...args]
|
|
42
|
-
|
|
43
|
-
|
|
36
|
+
return [command, ...args]
|
|
37
|
+
.map((value) => (/\s|"/u.test(value) ? JSON.stringify(value) : value))
|
|
38
|
+
.join(" ");
|
|
44
39
|
}
|
|
45
40
|
|
|
46
|
-
export function commandInvocation(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
|
|
41
|
+
export function commandInvocation(
|
|
42
|
+
command,
|
|
43
|
+
args,
|
|
44
|
+
{
|
|
45
|
+
platform = process.platform,
|
|
46
|
+
env = process.env,
|
|
47
|
+
fileExists = existsSync,
|
|
48
|
+
nodeExecutable = process.execPath,
|
|
49
|
+
} = {},
|
|
50
|
+
) {
|
|
51
|
+
if (platform !== "win32") return { command, args };
|
|
52
|
+
|
|
53
|
+
const searchPath = env.PATH || env.Path || "";
|
|
57
54
|
const npmDirectory = searchPath
|
|
58
|
-
.split(
|
|
59
|
-
.map((entry) => entry.trim().replace(/^"|"$/gu,
|
|
55
|
+
.split(";")
|
|
56
|
+
.map((entry) => entry.trim().replace(/^"|"$/gu, ""))
|
|
60
57
|
.filter(Boolean)
|
|
61
|
-
.find((entry) => fileExists(path.join(entry, `${command}.cmd`)))
|
|
58
|
+
.find((entry) => fileExists(path.join(entry, `${command}.cmd`)));
|
|
62
59
|
|
|
63
|
-
if (!npmDirectory) {
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (command === 'claude') {
|
|
60
|
+
if (!npmDirectory) return { command, args };
|
|
61
|
+
if (command === "claude") {
|
|
68
62
|
const executable = path.join(
|
|
69
63
|
npmDirectory,
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
)
|
|
76
|
-
if (fileExists(executable)) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
if (command === 'codex') {
|
|
64
|
+
"node_modules",
|
|
65
|
+
"@anthropic-ai",
|
|
66
|
+
"claude-code",
|
|
67
|
+
"bin",
|
|
68
|
+
"claude.exe",
|
|
69
|
+
);
|
|
70
|
+
if (fileExists(executable)) return { command: executable, args };
|
|
71
|
+
}
|
|
72
|
+
if (command === "codex") {
|
|
81
73
|
const script = path.join(
|
|
82
74
|
npmDirectory,
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
)
|
|
89
|
-
if (fileExists(script))
|
|
90
|
-
return { command: nodeExecutable, args: [script, ...args] }
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return { command, args }
|
|
75
|
+
"node_modules",
|
|
76
|
+
"@openai",
|
|
77
|
+
"codex",
|
|
78
|
+
"bin",
|
|
79
|
+
"codex.js",
|
|
80
|
+
);
|
|
81
|
+
if (fileExists(script))
|
|
82
|
+
return { command: nodeExecutable, args: [script, ...args] };
|
|
83
|
+
}
|
|
84
|
+
return { command, args };
|
|
95
85
|
}
|
|
96
86
|
|
|
97
87
|
function execute(command, args, { dryRun = false } = {}) {
|
|
98
88
|
if (dryRun) {
|
|
99
|
-
console.log(`[dry-run] ${commandText(command, args)}`)
|
|
100
|
-
return
|
|
89
|
+
console.log(`[dry-run] ${commandText(command, args)}`);
|
|
90
|
+
return;
|
|
101
91
|
}
|
|
102
92
|
|
|
103
|
-
const invocation = commandInvocation(command, args)
|
|
93
|
+
const invocation = commandInvocation(command, args);
|
|
104
94
|
const result = spawnSync(invocation.command, invocation.args, {
|
|
105
|
-
encoding:
|
|
95
|
+
encoding: "utf8",
|
|
106
96
|
windowsHide: true,
|
|
107
97
|
shell: false,
|
|
108
|
-
})
|
|
98
|
+
});
|
|
109
99
|
if (result.error) {
|
|
110
|
-
if (result.error.code ===
|
|
111
|
-
throw new Error(`未找到 ${command},请先安装对应客户端`)
|
|
112
|
-
|
|
113
|
-
throw result.error
|
|
100
|
+
if (result.error.code === "ENOENT")
|
|
101
|
+
throw new Error(`未找到 ${command},请先安装对应客户端`);
|
|
102
|
+
throw result.error;
|
|
114
103
|
}
|
|
115
104
|
if (result.status !== 0) {
|
|
116
|
-
const detail = `${result.stderr ||
|
|
117
|
-
const error = new Error(detail || `${command} 返回退出码 ${result.status}`)
|
|
118
|
-
error.commandOutput = detail
|
|
119
|
-
throw error
|
|
105
|
+
const detail = `${result.stderr || ""}\n${result.stdout || ""}`.trim();
|
|
106
|
+
const error = new Error(detail || `${command} 返回退出码 ${result.status}`);
|
|
107
|
+
error.commandOutput = detail;
|
|
108
|
+
throw error;
|
|
120
109
|
}
|
|
121
110
|
}
|
|
122
111
|
|
|
123
112
|
function isDuplicateError(error) {
|
|
124
|
-
return /already|exist|duplicate|已存在/iu.test(
|
|
113
|
+
return /already|exist|duplicate|已存在/iu.test(
|
|
114
|
+
error.commandOutput || error.message,
|
|
115
|
+
);
|
|
125
116
|
}
|
|
126
117
|
|
|
127
118
|
export function configureCodex(options = {}) {
|
|
128
119
|
try {
|
|
129
|
-
execute(
|
|
120
|
+
execute("codex", codexArguments(), options);
|
|
130
121
|
} catch (error) {
|
|
131
|
-
if (!isDuplicateError(error))
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
execute('codex', ['mcp', 'remove', SERVER_NAME], options)
|
|
135
|
-
execute('codex', codexArguments(), options)
|
|
122
|
+
if (!isDuplicateError(error)) throw error;
|
|
123
|
+
execute("codex", ["mcp", "remove", SERVER_NAME], options);
|
|
124
|
+
execute("codex", codexArguments(), options);
|
|
136
125
|
}
|
|
137
126
|
}
|
|
138
127
|
|
|
139
128
|
export function configureClaude(options = {}) {
|
|
140
129
|
const addArgs = [
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
130
|
+
"mcp",
|
|
131
|
+
"add-json",
|
|
132
|
+
"--scope",
|
|
133
|
+
"user",
|
|
145
134
|
SERVER_NAME,
|
|
146
135
|
JSON.stringify(claudeServerDefinition()),
|
|
147
|
-
]
|
|
136
|
+
];
|
|
148
137
|
try {
|
|
149
|
-
execute(
|
|
138
|
+
execute("claude", addArgs, options);
|
|
150
139
|
} catch (error) {
|
|
151
|
-
if (!isDuplicateError(error))
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
140
|
+
if (!isDuplicateError(error)) throw error;
|
|
141
|
+
execute(
|
|
142
|
+
"claude",
|
|
143
|
+
["mcp", "remove", "--scope", "user", SERVER_NAME],
|
|
144
|
+
options,
|
|
145
|
+
);
|
|
146
|
+
execute("claude", addArgs, options);
|
|
156
147
|
}
|
|
157
148
|
}
|
|
158
149
|
|
|
159
150
|
export async function configureCursor({ home, dryRun = false } = {}) {
|
|
160
|
-
const configPath = path.join(home,
|
|
151
|
+
const configPath = path.join(home, ".cursor", "mcp.json");
|
|
161
152
|
if (dryRun) {
|
|
162
|
-
console.log(
|
|
163
|
-
|
|
153
|
+
console.log(
|
|
154
|
+
`[dry-run] 更新 ${configPath} 中的 ${SERVER_NAME},使用本地 stdio 桥接`,
|
|
155
|
+
);
|
|
156
|
+
return configPath;
|
|
164
157
|
}
|
|
165
158
|
|
|
166
|
-
let config = {}
|
|
159
|
+
let config = {};
|
|
167
160
|
try {
|
|
168
|
-
|
|
169
|
-
config = JSON.parse(source)
|
|
161
|
+
config = JSON.parse(await readFile(configPath, "utf8"));
|
|
170
162
|
} catch (error) {
|
|
171
|
-
if (error.code !==
|
|
172
|
-
if (error instanceof SyntaxError)
|
|
173
|
-
throw new Error(`${configPath} 不是有效 JSON,未做任何修改`)
|
|
174
|
-
|
|
175
|
-
throw error
|
|
163
|
+
if (error.code !== "ENOENT") {
|
|
164
|
+
if (error instanceof SyntaxError)
|
|
165
|
+
throw new Error(`${configPath} 不是有效 JSON,未做任何修改`);
|
|
166
|
+
throw error;
|
|
176
167
|
}
|
|
177
168
|
}
|
|
178
169
|
|
|
179
170
|
config.mcpServers = {
|
|
180
171
|
...(config.mcpServers || {}),
|
|
181
172
|
[SERVER_NAME]: cursorServerDefinition(),
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
await
|
|
187
|
-
|
|
188
|
-
return configPath
|
|
173
|
+
};
|
|
174
|
+
await mkdir(path.dirname(configPath), { recursive: true });
|
|
175
|
+
const tempPath = `${configPath}.${process.pid}.tmp`;
|
|
176
|
+
await writeFile(tempPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
177
|
+
await rename(tempPath, configPath);
|
|
178
|
+
return configPath;
|
|
189
179
|
}
|
|
190
180
|
|
|
191
181
|
export async function configureAgent(agent, options) {
|
|
192
|
-
if (agent ===
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
196
|
-
if (agent === 'claude') {
|
|
197
|
-
configureClaude(options)
|
|
198
|
-
return
|
|
199
|
-
}
|
|
200
|
-
if (agent === 'cursor') {
|
|
201
|
-
await configureCursor(options)
|
|
202
|
-
return
|
|
203
|
-
}
|
|
204
|
-
throw new Error(`不支持的客户端:${agent}`)
|
|
182
|
+
if (agent === "codex") return configureCodex(options);
|
|
183
|
+
if (agent === "claude") return configureClaude(options);
|
|
184
|
+
if (agent === "cursor") return configureCursor(options);
|
|
185
|
+
throw new Error(`不支持的客户端:${agent}`);
|
|
205
186
|
}
|
package/src/constants.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
export const PACKAGE_NAME =
|
|
2
|
-
export const PACKAGE_VERSION =
|
|
3
|
-
export const SERVER_NAME =
|
|
4
|
-
export const MCP_URL =
|
|
5
|
-
export const
|
|
6
|
-
export const
|
|
1
|
+
export const PACKAGE_NAME = "@bigbrain-work/mcp-connect";
|
|
2
|
+
export const PACKAGE_VERSION = "1.3.0";
|
|
3
|
+
export const SERVER_NAME = "shiliu_mcp";
|
|
4
|
+
export const MCP_URL = "https://api.bigbrain.work/shiliu/mcp";
|
|
5
|
+
export const AUTH_URL = "https://api.bigbrain.work/shiliu/auth/device";
|
|
6
|
+
export const API_KEY_ENV = "SHILIU_AI_API_KEY";
|
|
7
|
+
export const KEYRING_SERVICE = "work.bigbrain.shiliu-ai";
|
|
8
|
+
export const KEYRING_ACCOUNT = "default";
|
|
9
|
+
export const PENDING_KEYRING_ACCOUNT = "pending-device-login";
|
|
10
|
+
export const SUPPORTED_AGENTS = ["codex", "claude", "cursor"];
|
|
11
|
+
export const REGISTRY_LATEST_URL =
|
|
12
|
+
"https://registry.npmjs.org/@bigbrain-work%2Fmcp-connect/latest";
|