@kwonye/mcpx 0.1.74 → 0.1.77
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/dist/adapters/claude-desktop.d.ts.map +1 -1
- package/dist/adapters/cline.d.ts.map +1 -1
- package/dist/adapters/cursor.d.ts.map +1 -1
- package/dist/adapters/kiro.d.ts +2 -1
- package/dist/adapters/kiro.d.ts.map +1 -1
- package/dist/adapters/qwen.d.ts +2 -1
- package/dist/adapters/qwen.d.ts.map +1 -1
- package/dist/adapters/utils/index.d.ts.map +1 -1
- package/dist/adapters/vscode.d.ts.map +1 -1
- package/dist/cli-7bsf6akp.js +202 -0
- package/dist/{cli-33pamqwn.js → cli-f16kphtq.js} +591 -456
- package/dist/cli-fsf33sgp.js +105 -0
- package/dist/{cli-ezvj44tr.js → cli-htm5cf82.js} +590 -455
- package/dist/{cli-q8de7zb0.js → cli-s4gspqdf.js} +4 -1
- package/dist/cli-s4py8ckf.js +187 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +506 -85
- package/dist/config-kayej21q.js +20 -0
- package/dist/core/auth-probe.d.ts.map +1 -1
- package/dist/core/config.d.ts +19 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/daemon.d.ts.map +1 -1
- package/dist/core/index.d.ts +5 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +44 -12
- package/dist/core/paths.d.ts +3 -0
- package/dist/core/paths.d.ts.map +1 -1
- package/dist/core/proxy.d.ts +2 -0
- package/dist/core/proxy.d.ts.map +1 -0
- package/dist/core/registry-client.d.ts +76 -0
- package/dist/core/registry-client.d.ts.map +1 -0
- package/dist/core/registry.d.ts +2 -0
- package/dist/core/registry.d.ts.map +1 -1
- package/dist/core/secrets.d.ts.map +1 -1
- package/dist/core/skills.d.ts +6 -0
- package/dist/core/skills.d.ts.map +1 -0
- package/dist/core/status.d.ts +6 -2
- package/dist/core/status.d.ts.map +1 -1
- package/dist/core/sync.d.ts.map +1 -1
- package/dist/gateway/server.d.ts +17 -0
- package/dist/gateway/server.d.ts.map +1 -1
- package/dist/registry-1s14wd58.js +24 -0
- package/dist/types.d.ts +17 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +2 -1
|
@@ -1,328 +1,98 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
} from "./cli-
|
|
2
|
+
loadMergedConfig,
|
|
3
|
+
saveConfig
|
|
4
|
+
} from "./cli-7bsf6akp.js";
|
|
5
|
+
import {
|
|
6
|
+
SecretsManager,
|
|
7
|
+
ensureGatewayToken,
|
|
8
|
+
getGatewayTokenSecretName
|
|
9
|
+
} from "./cli-s4py8ckf.js";
|
|
10
|
+
import {
|
|
11
|
+
ensureDir,
|
|
12
|
+
ensureParentDir,
|
|
13
|
+
getLogPath,
|
|
14
|
+
getManagedIndexPath,
|
|
15
|
+
getPidPath,
|
|
16
|
+
getSkillsDir,
|
|
17
|
+
getStagedVersionPath,
|
|
18
|
+
getUpdateLockPath,
|
|
19
|
+
getUpdatesDir,
|
|
20
|
+
homeDir,
|
|
21
|
+
readJsonFile,
|
|
22
|
+
sha256,
|
|
23
|
+
writeJsonAtomic
|
|
24
|
+
} from "./cli-fsf33sgp.js";
|
|
25
|
+
import {
|
|
26
|
+
isServerEnabled
|
|
27
|
+
} from "./cli-s4gspqdf.js";
|
|
5
28
|
|
|
6
|
-
// src/core/
|
|
7
|
-
import os from "node:os";
|
|
8
|
-
import path from "node:path";
|
|
29
|
+
// src/core/skills.ts
|
|
9
30
|
import fs from "node:fs";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
function getManagedIndexPath() {
|
|
32
|
-
return path.join(getDataRoot(), "mcpx", "managed-index.json");
|
|
33
|
-
}
|
|
34
|
-
function getSecretNamesPath() {
|
|
35
|
-
return path.join(getDataRoot(), "mcpx", "secret-names.json");
|
|
36
|
-
}
|
|
37
|
-
function getRuntimeDir() {
|
|
38
|
-
return path.join(getStateRoot(), "mcpx", "runtime");
|
|
39
|
-
}
|
|
40
|
-
function getLogDir() {
|
|
41
|
-
return path.join(getStateRoot(), "mcpx", "logs");
|
|
42
|
-
}
|
|
43
|
-
function getPidPath() {
|
|
44
|
-
return path.join(getRuntimeDir(), "daemon.pid");
|
|
45
|
-
}
|
|
46
|
-
function getLogPath() {
|
|
47
|
-
return path.join(getLogDir(), "daemon.log");
|
|
48
|
-
}
|
|
49
|
-
function getUpdatesDir() {
|
|
50
|
-
return path.join(getDataRoot(), "mcpx", "updates");
|
|
51
|
-
}
|
|
52
|
-
function getStagedVersionPath() {
|
|
53
|
-
return path.join(getUpdatesDir(), "staged-version.json");
|
|
54
|
-
}
|
|
55
|
-
function getUpdateLockPath() {
|
|
56
|
-
return path.join(getUpdatesDir(), ".update.lock");
|
|
57
|
-
}
|
|
58
|
-
function ensureDir(dirPath) {
|
|
59
|
-
fs.mkdirSync(dirPath, { recursive: true });
|
|
60
|
-
}
|
|
61
|
-
function ensureParentDir(filePath) {
|
|
62
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// src/core/config.ts
|
|
66
|
-
import { z } from "zod";
|
|
67
|
-
|
|
68
|
-
// src/util/fs.ts
|
|
69
|
-
import fs2 from "node:fs";
|
|
70
|
-
import path2 from "node:path";
|
|
71
|
-
import crypto from "node:crypto";
|
|
72
|
-
function readJsonFile(filePath, fallback) {
|
|
73
|
-
if (!fs2.existsSync(filePath)) {
|
|
74
|
-
return fallback;
|
|
75
|
-
}
|
|
76
|
-
try {
|
|
77
|
-
const raw = fs2.readFileSync(filePath, "utf8");
|
|
78
|
-
return JSON.parse(raw);
|
|
79
|
-
} catch {
|
|
80
|
-
return fallback;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
function writeJsonAtomic(filePath, data) {
|
|
84
|
-
fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
|
|
85
|
-
const tempPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
86
|
-
const content = `${JSON.stringify(data, null, 2)}
|
|
87
|
-
`;
|
|
88
|
-
fs2.writeFileSync(tempPath, content, { mode: 384 });
|
|
89
|
-
fs2.renameSync(tempPath, filePath);
|
|
90
|
-
}
|
|
91
|
-
function sha256(value) {
|
|
92
|
-
return crypto.createHash("sha256").update(value).digest("hex");
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// src/core/config.ts
|
|
96
|
-
var clientStateSchema = z.object({
|
|
97
|
-
status: z.enum(["SYNCED", "UNSUPPORTED_HTTP", "ERROR", "SKIPPED"]),
|
|
98
|
-
lastSyncAt: z.string().optional(),
|
|
99
|
-
message: z.string().optional(),
|
|
100
|
-
configPath: z.string().optional()
|
|
101
|
-
});
|
|
102
|
-
var httpServerSchema = z.object({
|
|
103
|
-
transport: z.literal("http"),
|
|
104
|
-
url: z.url(),
|
|
105
|
-
headers: z.record(z.string(), z.string()).optional(),
|
|
106
|
-
enabled: z.boolean().default(true)
|
|
107
|
-
});
|
|
108
|
-
var stdioServerSchema = z.object({
|
|
109
|
-
transport: z.literal("stdio"),
|
|
110
|
-
command: z.string().min(1),
|
|
111
|
-
args: z.array(z.string()).optional(),
|
|
112
|
-
env: z.record(z.string(), z.string()).optional(),
|
|
113
|
-
cwd: z.string().min(1).optional(),
|
|
114
|
-
enabled: z.boolean().default(true)
|
|
115
|
-
});
|
|
116
|
-
var serverSchema = z.discriminatedUnion("transport", [httpServerSchema, stdioServerSchema]);
|
|
117
|
-
var configSchema = z.object({
|
|
118
|
-
schemaVersion: z.literal(1).default(1),
|
|
119
|
-
gateway: z.object({
|
|
120
|
-
port: z.number().int().min(1).max(65535).default(37373),
|
|
121
|
-
tokenRef: z.string().default("secret://local_gateway_token"),
|
|
122
|
-
autoStart: z.boolean().default(true)
|
|
123
|
-
}).default({
|
|
124
|
-
port: 37373,
|
|
125
|
-
tokenRef: "secret://local_gateway_token",
|
|
126
|
-
autoStart: true
|
|
127
|
-
}),
|
|
128
|
-
servers: z.record(z.string(), serverSchema).default({}),
|
|
129
|
-
clients: z.record(z.string(), clientStateSchema).default({})
|
|
130
|
-
});
|
|
131
|
-
function defaultConfig() {
|
|
132
|
-
return {
|
|
133
|
-
schemaVersion: 1,
|
|
134
|
-
gateway: {
|
|
135
|
-
port: 37373,
|
|
136
|
-
tokenRef: "secret://local_gateway_token",
|
|
137
|
-
autoStart: true
|
|
138
|
-
},
|
|
139
|
-
servers: {},
|
|
140
|
-
clients: {}
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
function loadConfig(configPath = getConfigPath()) {
|
|
144
|
-
const raw = readJsonFile(configPath, defaultConfig());
|
|
145
|
-
const parsed = configSchema.safeParse(raw);
|
|
146
|
-
if (!parsed.success) {
|
|
147
|
-
return defaultConfig();
|
|
148
|
-
}
|
|
149
|
-
const clientEntries = {};
|
|
150
|
-
for (const [key, value] of Object.entries(parsed.data.clients)) {
|
|
151
|
-
if (["claude", "claude-desktop", "codex", "cursor", "cline", "opencode", "kiro", "vscode", "qwen"].includes(key)) {
|
|
152
|
-
clientEntries[key] = value;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
const servers = Object.fromEntries(Object.entries(parsed.data.servers).map(([name, spec]) => [name, normalizeServerSpecEnabled(spec)]));
|
|
156
|
-
return {
|
|
157
|
-
schemaVersion: 1,
|
|
158
|
-
gateway: parsed.data.gateway,
|
|
159
|
-
servers,
|
|
160
|
-
clients: clientEntries
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
function saveConfig(config, configPath = getConfigPath()) {
|
|
164
|
-
config.servers = Object.fromEntries(Object.entries(config.servers).map(([name, spec]) => [name, normalizeServerSpecEnabled(spec)]));
|
|
165
|
-
writeJsonAtomic(configPath, config);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// src/core/registry.ts
|
|
169
|
-
function validateServerName(name) {
|
|
170
|
-
if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,62}$/.test(name)) {
|
|
171
|
-
throw new Error("Invalid server name. Use 1-63 chars: letters, numbers, '.', '-', '_' and start with alphanumeric.");
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
function addServer(config, name, spec, force = false) {
|
|
175
|
-
validateServerName(name);
|
|
176
|
-
if (config.servers[name] && !force) {
|
|
177
|
-
throw new Error(`Server "${name}" already exists. Use --force to overwrite.`);
|
|
178
|
-
}
|
|
179
|
-
config.servers[name] = normalizeServerSpecEnabled(spec);
|
|
180
|
-
}
|
|
181
|
-
function removeServer(config, name, force = false) {
|
|
182
|
-
if (!config.servers[name] && !force) {
|
|
183
|
-
throw new Error(`Server "${name}" does not exist.`);
|
|
184
|
-
}
|
|
185
|
-
delete config.servers[name];
|
|
186
|
-
}
|
|
187
|
-
function updateServer(config, name, spec) {
|
|
188
|
-
validateServerName(name);
|
|
189
|
-
if (!config.servers[name]) {
|
|
190
|
-
throw new Error(`Server "${name}" does not exist.`);
|
|
191
|
-
}
|
|
192
|
-
config.servers[name] = normalizeServerSpecEnabled(spec);
|
|
193
|
-
}
|
|
194
|
-
function setServerEnabled(config, name, enabled) {
|
|
195
|
-
const existing = config.servers[name];
|
|
196
|
-
if (!existing) {
|
|
197
|
-
throw new Error(`Server "${name}" does not exist.`);
|
|
198
|
-
}
|
|
199
|
-
config.servers[name] = {
|
|
200
|
-
...existing,
|
|
201
|
-
enabled
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
function getGatewayTokenSecretName(config) {
|
|
205
|
-
if (!config.gateway.tokenRef.startsWith("secret://")) {
|
|
206
|
-
return "local_gateway_token";
|
|
207
|
-
}
|
|
208
|
-
return config.gateway.tokenRef.slice("secret://".length) || "local_gateway_token";
|
|
209
|
-
}
|
|
210
|
-
function ensureGatewayToken(config, secrets) {
|
|
211
|
-
const secretName = getGatewayTokenSecretName(config);
|
|
212
|
-
const existing = secrets.getSecret(secretName);
|
|
213
|
-
if (existing) {
|
|
214
|
-
return existing;
|
|
215
|
-
}
|
|
216
|
-
return secrets.rotateLocalToken(secretName);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// src/core/secrets.ts
|
|
220
|
-
import { execFileSync } from "node:child_process";
|
|
221
|
-
import crypto2 from "node:crypto";
|
|
222
|
-
import fs3 from "node:fs";
|
|
223
|
-
function parseSecretRef(ref) {
|
|
224
|
-
if (!ref.startsWith("secret://")) {
|
|
31
|
+
import path from "node:path";
|
|
32
|
+
function listSkills() {
|
|
33
|
+
const skillsDir = getSkillsDir();
|
|
34
|
+
if (!fs.existsSync(skillsDir)) {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
const files = fs.readdirSync(skillsDir);
|
|
38
|
+
const skills = [];
|
|
39
|
+
for (const file of files) {
|
|
40
|
+
if (file.endsWith(".md")) {
|
|
41
|
+
const id = path.basename(file, ".md");
|
|
42
|
+
const content = fs.readFileSync(path.join(skillsDir, file), "utf-8");
|
|
43
|
+
skills.push({ id, content });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return skills;
|
|
47
|
+
}
|
|
48
|
+
function getSkill(id) {
|
|
49
|
+
const skillsDir = getSkillsDir();
|
|
50
|
+
const filePath = path.join(skillsDir, `${id}.md`);
|
|
51
|
+
if (!fs.existsSync(filePath)) {
|
|
225
52
|
return null;
|
|
226
53
|
}
|
|
227
|
-
const
|
|
228
|
-
return
|
|
54
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
55
|
+
return { id, content };
|
|
229
56
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
this.service = service;
|
|
236
|
-
this.nameIndexPath = nameIndexPath;
|
|
237
|
-
}
|
|
238
|
-
loadNameIndex() {
|
|
239
|
-
return readJsonFile(this.nameIndexPath, { names: [] });
|
|
240
|
-
}
|
|
241
|
-
saveNameIndex(index) {
|
|
242
|
-
const deduped = Array.from(new Set(index.names)).sort();
|
|
243
|
-
writeJsonAtomic(this.nameIndexPath, { names: deduped });
|
|
244
|
-
}
|
|
245
|
-
trackSecretName(name) {
|
|
246
|
-
const index = this.loadNameIndex();
|
|
247
|
-
if (!index.names.includes(name)) {
|
|
248
|
-
index.names.push(name);
|
|
249
|
-
this.saveNameIndex(index);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
untrackSecretName(name) {
|
|
253
|
-
const index = this.loadNameIndex();
|
|
254
|
-
index.names = index.names.filter((entry) => entry !== name);
|
|
255
|
-
this.saveNameIndex(index);
|
|
256
|
-
}
|
|
257
|
-
listSecretNames() {
|
|
258
|
-
return this.loadNameIndex().names;
|
|
259
|
-
}
|
|
260
|
-
setSecret(name, value) {
|
|
261
|
-
if (process.platform !== "darwin") {
|
|
262
|
-
throw new Error("OS keychain integration is currently implemented for macOS only.");
|
|
263
|
-
}
|
|
264
|
-
execFileSync("security", ["add-generic-password", "-U", "-a", name, "-s", this.service, "-w", value], {
|
|
265
|
-
stdio: "ignore"
|
|
266
|
-
});
|
|
267
|
-
this.trackSecretName(name);
|
|
268
|
-
}
|
|
269
|
-
removeSecret(name) {
|
|
270
|
-
if (process.platform === "darwin") {
|
|
271
|
-
try {
|
|
272
|
-
execFileSync("security", ["delete-generic-password", "-a", name, "-s", this.service], {
|
|
273
|
-
stdio: "ignore"
|
|
274
|
-
});
|
|
275
|
-
} catch {}
|
|
276
|
-
}
|
|
277
|
-
this.untrackSecretName(name);
|
|
278
|
-
}
|
|
279
|
-
getSecret(name) {
|
|
280
|
-
const envOverride = process.env[`MCPX_SECRET_${name}`];
|
|
281
|
-
if (envOverride && envOverride.length > 0) {
|
|
282
|
-
return envOverride;
|
|
283
|
-
}
|
|
284
|
-
if (process.platform !== "darwin") {
|
|
285
|
-
return null;
|
|
286
|
-
}
|
|
287
|
-
try {
|
|
288
|
-
return execFileSync("security", ["find-generic-password", "-w", "-a", name, "-s", this.service], {
|
|
289
|
-
encoding: "utf8"
|
|
290
|
-
}).trim();
|
|
291
|
-
} catch {
|
|
292
|
-
return null;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
resolveMaybeSecret(value) {
|
|
296
|
-
const secretName = parseSecretRef(value);
|
|
297
|
-
if (!secretName) {
|
|
298
|
-
return value;
|
|
299
|
-
}
|
|
300
|
-
const secret = this.getSecret(secretName);
|
|
301
|
-
if (!secret) {
|
|
302
|
-
throw new Error(`Secret not found: ${secretName}`);
|
|
303
|
-
}
|
|
304
|
-
return secret;
|
|
305
|
-
}
|
|
306
|
-
rotateLocalToken(secretName = "local_gateway_token") {
|
|
307
|
-
const token = crypto2.randomBytes(32).toString("base64url");
|
|
308
|
-
this.setSecret(secretName, token);
|
|
309
|
-
return token;
|
|
310
|
-
}
|
|
57
|
+
function saveSkill(id, content) {
|
|
58
|
+
const skillsDir = getSkillsDir();
|
|
59
|
+
ensureDir(skillsDir);
|
|
60
|
+
const filePath = path.join(skillsDir, `${id}.md`);
|
|
61
|
+
fs.writeFileSync(filePath, content, "utf-8");
|
|
311
62
|
}
|
|
312
|
-
function
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
if (content.length === 0) {
|
|
318
|
-
throw new Error("Empty secret value provided.");
|
|
63
|
+
function deleteSkill(id) {
|
|
64
|
+
const skillsDir = getSkillsDir();
|
|
65
|
+
const filePath = path.join(skillsDir, `${id}.md`);
|
|
66
|
+
if (fs.existsSync(filePath)) {
|
|
67
|
+
fs.unlinkSync(filePath);
|
|
319
68
|
}
|
|
320
|
-
return content;
|
|
321
69
|
}
|
|
322
70
|
|
|
323
71
|
// src/core/auth-probe.ts
|
|
324
72
|
var JSON_RPC_VERSION = "2.0";
|
|
325
73
|
var DEFAULT_AUTH_PROBE_TIMEOUT_MS = 8000;
|
|
74
|
+
var AUTH_KEYWORDS = [
|
|
75
|
+
"unauthorized",
|
|
76
|
+
"unauthenticated",
|
|
77
|
+
"forbidden",
|
|
78
|
+
"authentication",
|
|
79
|
+
"authorization",
|
|
80
|
+
"api key",
|
|
81
|
+
"api_key",
|
|
82
|
+
"apikey",
|
|
83
|
+
"credentials",
|
|
84
|
+
"access denied",
|
|
85
|
+
"auth required",
|
|
86
|
+
"x-api-key",
|
|
87
|
+
"invalid key",
|
|
88
|
+
"missing header",
|
|
89
|
+
"not authenticated",
|
|
90
|
+
"auth token"
|
|
91
|
+
];
|
|
92
|
+
function isAuthRelatedMessage(message) {
|
|
93
|
+
const lower = message.toLowerCase();
|
|
94
|
+
return AUTH_KEYWORDS.some((kw) => lower.includes(kw));
|
|
95
|
+
}
|
|
326
96
|
function resolveHeaders(spec, secrets) {
|
|
327
97
|
const headers = {
|
|
328
98
|
"content-type": "application/json",
|
|
@@ -356,6 +126,22 @@ async function probeHttpAuthRequirement(spec, secrets, timeoutMs = DEFAULT_AUTH_
|
|
|
356
126
|
wwwAuthenticate: response.headers.get("www-authenticate") ?? undefined
|
|
357
127
|
};
|
|
358
128
|
}
|
|
129
|
+
if (response.status === 200) {
|
|
130
|
+
try {
|
|
131
|
+
const body = await response.json();
|
|
132
|
+
const err = body?.error;
|
|
133
|
+
if (err && typeof err === "object" && !("result" in body)) {
|
|
134
|
+
const message = typeof err.message === "string" ? err.message : "";
|
|
135
|
+
if (message && isAuthRelatedMessage(message)) {
|
|
136
|
+
return {
|
|
137
|
+
authRequired: true,
|
|
138
|
+
status: response.status,
|
|
139
|
+
error: `JSON-RPC error: ${message}`
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
} catch {}
|
|
144
|
+
}
|
|
359
145
|
return {
|
|
360
146
|
authRequired: false,
|
|
361
147
|
status: response.status
|
|
@@ -371,9 +157,132 @@ async function probeHttpAuthRequirement(spec, secrets, timeoutMs = DEFAULT_AUTH_
|
|
|
371
157
|
}
|
|
372
158
|
}
|
|
373
159
|
|
|
160
|
+
// src/core/registry-client.ts
|
|
161
|
+
var REGISTRY_BASE = "https://registry.modelcontextprotocol.io";
|
|
162
|
+
async function fetchRegistryServerDetail(name) {
|
|
163
|
+
const encoded = encodeURIComponent(name);
|
|
164
|
+
const response = await fetch(`${REGISTRY_BASE}/v0.1/servers/${encoded}/versions/latest`, {
|
|
165
|
+
headers: { accept: "application/json" }
|
|
166
|
+
});
|
|
167
|
+
if (!response.ok) {
|
|
168
|
+
throw new Error(`Registry API error: ${response.status}`);
|
|
169
|
+
}
|
|
170
|
+
const data = await response.json();
|
|
171
|
+
return data.server;
|
|
172
|
+
}
|
|
173
|
+
var PACKAGE_PRIORITY = {
|
|
174
|
+
npm: 1,
|
|
175
|
+
pypi: 2,
|
|
176
|
+
nuget: 3,
|
|
177
|
+
oci: 4,
|
|
178
|
+
mcpb: 5
|
|
179
|
+
};
|
|
180
|
+
var RUNTIME_HINT = {
|
|
181
|
+
npm: "npx",
|
|
182
|
+
pypi: "uvx",
|
|
183
|
+
nuget: "dnx"
|
|
184
|
+
};
|
|
185
|
+
function selectBestPackage(packages = [], remotes = []) {
|
|
186
|
+
const stdioPkgs = packages.filter((p) => p.transport.type === "stdio").sort((a, b) => (PACKAGE_PRIORITY[a.registryType] ?? 99) - (PACKAGE_PRIORITY[b.registryType] ?? 99));
|
|
187
|
+
if (stdioPkgs.length > 0) {
|
|
188
|
+
return { kind: "package", package: stdioPkgs[0] };
|
|
189
|
+
}
|
|
190
|
+
const httpRemotes = remotes.filter((r) => r.type === "streamable-http" || r.type === "sse");
|
|
191
|
+
if (httpRemotes.length > 0) {
|
|
192
|
+
return { kind: "remote", remote: httpRemotes[0] };
|
|
193
|
+
}
|
|
194
|
+
if (packages.length > 0) {
|
|
195
|
+
return { kind: "package", package: packages[0] };
|
|
196
|
+
}
|
|
197
|
+
if (remotes.length > 0) {
|
|
198
|
+
return { kind: "remote", remote: remotes[0] };
|
|
199
|
+
}
|
|
200
|
+
throw new Error("Server has no packages or remotes");
|
|
201
|
+
}
|
|
202
|
+
function extractRequiredInputs(option) {
|
|
203
|
+
const inputs = [];
|
|
204
|
+
if (option.kind === "package" && option.package?.environmentVariables) {
|
|
205
|
+
for (const env of option.package.environmentVariables) {
|
|
206
|
+
if (env.isRequired && !env.default) {
|
|
207
|
+
inputs.push({
|
|
208
|
+
name: env.name,
|
|
209
|
+
description: env.description,
|
|
210
|
+
isSecret: env.isSecret ?? false,
|
|
211
|
+
kind: "env"
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
for (const arg of option.package.packageArguments ?? []) {
|
|
216
|
+
if (arg.isRequired && !arg.value && !arg.default) {
|
|
217
|
+
inputs.push({
|
|
218
|
+
name: arg.name ?? arg.valueHint ?? "arg",
|
|
219
|
+
description: arg.description,
|
|
220
|
+
isSecret: false,
|
|
221
|
+
kind: "arg"
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (option.kind === "remote" && option.remote?.headers) {
|
|
227
|
+
for (const header of option.remote.headers) {
|
|
228
|
+
if (header.isRequired && !header.default) {
|
|
229
|
+
inputs.push({
|
|
230
|
+
name: header.name,
|
|
231
|
+
description: header.description,
|
|
232
|
+
isSecret: header.isSecret ?? false,
|
|
233
|
+
kind: "header"
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return inputs;
|
|
239
|
+
}
|
|
240
|
+
function mapRegistryToSpec(_name, option, resolvedValues) {
|
|
241
|
+
if (option.kind === "remote") {
|
|
242
|
+
const headers = {};
|
|
243
|
+
for (const header of option.remote?.headers ?? []) {
|
|
244
|
+
const value = resolvedValues[header.name] ?? header.default;
|
|
245
|
+
if (value)
|
|
246
|
+
headers[header.name] = value;
|
|
247
|
+
}
|
|
248
|
+
return {
|
|
249
|
+
transport: "http",
|
|
250
|
+
url: option.remote.url,
|
|
251
|
+
...Object.keys(headers).length > 0 ? { headers } : {}
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
const pkg = option.package;
|
|
255
|
+
const runtime = pkg.runtimeHint ?? RUNTIME_HINT[pkg.registryType] ?? "npx";
|
|
256
|
+
const args = [];
|
|
257
|
+
const identifier = pkg.version ? `${pkg.identifier}@${pkg.version}` : pkg.identifier;
|
|
258
|
+
args.push(identifier);
|
|
259
|
+
for (const arg of pkg.packageArguments ?? []) {
|
|
260
|
+
const value = arg.value ?? resolvedValues[arg.name ?? arg.valueHint ?? ""] ?? arg.default;
|
|
261
|
+
if (!value)
|
|
262
|
+
continue;
|
|
263
|
+
if (arg.type === "named" && arg.name) {
|
|
264
|
+
args.push(arg.name, value);
|
|
265
|
+
} else {
|
|
266
|
+
args.push(value);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
const env = {};
|
|
270
|
+
for (const envVar of pkg.environmentVariables ?? []) {
|
|
271
|
+
const value = resolvedValues[envVar.name] ?? envVar.default;
|
|
272
|
+
if (value)
|
|
273
|
+
env[envVar.name] = value;
|
|
274
|
+
}
|
|
275
|
+
return {
|
|
276
|
+
transport: "stdio",
|
|
277
|
+
command: runtime,
|
|
278
|
+
args,
|
|
279
|
+
...Object.keys(env).length > 0 ? { env } : {}
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
374
283
|
// src/adapters/claude.ts
|
|
375
|
-
import
|
|
376
|
-
import { z
|
|
284
|
+
import path2 from "node:path";
|
|
285
|
+
import { z } from "zod";
|
|
377
286
|
|
|
378
287
|
// src/adapters/utils/index.ts
|
|
379
288
|
function getManagedEntryNames(managedIndex, clientId) {
|
|
@@ -390,6 +299,11 @@ function pruneStaleManagedEntries(managedIndex, clientId, servers, keepEntryName
|
|
|
390
299
|
delete servers[name];
|
|
391
300
|
}
|
|
392
301
|
}
|
|
302
|
+
for (const name of Object.keys(servers)) {
|
|
303
|
+
if (!keep.has(name) && name.endsWith(" (mcpx)")) {
|
|
304
|
+
delete servers[name];
|
|
305
|
+
}
|
|
306
|
+
}
|
|
393
307
|
}
|
|
394
308
|
function ensureManagedEntryWritable(managedIndex, clientId, entryName, existingValue) {
|
|
395
309
|
if (existingValue === undefined || existingValue === null) {
|
|
@@ -496,22 +410,22 @@ function syncMcpExcludedArray(raw, managedNames, managedEntries) {
|
|
|
496
410
|
}
|
|
497
411
|
|
|
498
412
|
// src/adapters/claude.ts
|
|
499
|
-
var stringMapSchema =
|
|
500
|
-
var claudeEntrySchema =
|
|
501
|
-
type:
|
|
502
|
-
url:
|
|
413
|
+
var stringMapSchema = z.record(z.string(), z.string());
|
|
414
|
+
var claudeEntrySchema = z.object({
|
|
415
|
+
type: z.string().optional(),
|
|
416
|
+
url: z.string().min(1).optional(),
|
|
503
417
|
headers: stringMapSchema.optional(),
|
|
504
|
-
command:
|
|
505
|
-
args:
|
|
418
|
+
command: z.string().min(1).optional(),
|
|
419
|
+
args: z.array(z.string()).optional(),
|
|
506
420
|
env: stringMapSchema.optional(),
|
|
507
|
-
cwd:
|
|
508
|
-
disabled:
|
|
421
|
+
cwd: z.string().min(1).optional(),
|
|
422
|
+
disabled: z.boolean().optional()
|
|
509
423
|
}).passthrough();
|
|
510
424
|
|
|
511
425
|
class ClaudeAdapter {
|
|
512
426
|
id = "claude";
|
|
513
427
|
detectConfigPath() {
|
|
514
|
-
return
|
|
428
|
+
return path2.join(homeDir(), ".claude.json");
|
|
515
429
|
}
|
|
516
430
|
supportsHttp() {
|
|
517
431
|
return true;
|
|
@@ -623,24 +537,40 @@ class ClaudeAdapter {
|
|
|
623
537
|
}
|
|
624
538
|
|
|
625
539
|
// src/adapters/claude-desktop.ts
|
|
626
|
-
import
|
|
627
|
-
import
|
|
628
|
-
|
|
629
|
-
var
|
|
630
|
-
|
|
631
|
-
|
|
540
|
+
import { execFileSync } from "node:child_process";
|
|
541
|
+
import path3 from "node:path";
|
|
542
|
+
import { z as z2 } from "zod";
|
|
543
|
+
var stringMapSchema2 = z2.record(z2.string(), z2.string());
|
|
544
|
+
var claudeDesktopEntrySchema = z2.object({
|
|
545
|
+
type: z2.string().optional(),
|
|
546
|
+
url: z2.string().min(1).optional(),
|
|
632
547
|
headers: stringMapSchema2.optional(),
|
|
633
|
-
command:
|
|
634
|
-
args:
|
|
548
|
+
command: z2.string().min(1).optional(),
|
|
549
|
+
args: z2.array(z2.string()).optional(),
|
|
635
550
|
env: stringMapSchema2.optional(),
|
|
636
|
-
cwd:
|
|
637
|
-
disabled:
|
|
551
|
+
cwd: z2.string().min(1).optional(),
|
|
552
|
+
disabled: z2.boolean().optional()
|
|
638
553
|
}).passthrough();
|
|
554
|
+
function buildProxyEntry(entry) {
|
|
555
|
+
const upstreamName = entry.name.replace(/ \(mcpx\)$/, "");
|
|
556
|
+
try {
|
|
557
|
+
const mcpxPath = execFileSync("which", ["mcpx"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
558
|
+
if (mcpxPath)
|
|
559
|
+
return { command: mcpxPath, args: ["proxy", upstreamName] };
|
|
560
|
+
} catch {}
|
|
561
|
+
return { command: "npx", args: ["-y", "@kwonye/mcpx@latest", "proxy", upstreamName] };
|
|
562
|
+
}
|
|
639
563
|
|
|
640
564
|
class ClaudeDesktopAdapter {
|
|
641
565
|
id = "claude-desktop";
|
|
642
566
|
detectConfigPath() {
|
|
643
|
-
|
|
567
|
+
if (process.platform === "linux") {
|
|
568
|
+
return path3.join(homeDir(), ".config", "Claude", "claude_desktop_config.json");
|
|
569
|
+
}
|
|
570
|
+
if (process.platform === "win32") {
|
|
571
|
+
return path3.join(process.env.APPDATA ?? "", "Claude", "claude_desktop_config.json");
|
|
572
|
+
}
|
|
573
|
+
return path3.join(homeDir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
644
574
|
}
|
|
645
575
|
supportsHttp() {
|
|
646
576
|
return true;
|
|
@@ -663,7 +593,7 @@ class ClaudeDesktopAdapter {
|
|
|
663
593
|
continue;
|
|
664
594
|
}
|
|
665
595
|
const entry = parsed.data;
|
|
666
|
-
if ((entry.type === undefined || entry.type === "http") && entry.url && !entry.command) {
|
|
596
|
+
if ((entry.type === undefined || entry.type === "http" || entry.type === "streamable_http") && entry.url && !entry.command) {
|
|
667
597
|
result.candidates.push({
|
|
668
598
|
clientId: this.id,
|
|
669
599
|
configPath,
|
|
@@ -707,11 +637,10 @@ class ClaudeDesktopAdapter {
|
|
|
707
637
|
try {
|
|
708
638
|
const raw = readJsonFile(configPath, {});
|
|
709
639
|
const enabledEntries = options.managedEntries.filter((entry) => entry.enabled);
|
|
710
|
-
const serverEntries = Object.fromEntries(enabledEntries.map((entry) =>
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
}]));
|
|
640
|
+
const serverEntries = Object.fromEntries(enabledEntries.map((entry) => {
|
|
641
|
+
const { command, args } = buildProxyEntry(entry);
|
|
642
|
+
return [entry.name, { command, args }];
|
|
643
|
+
}));
|
|
715
644
|
const enabledManagedNames = enabledEntries.map((entry) => entry.name);
|
|
716
645
|
const topLevelServers = raw.mcpServers ?? {};
|
|
717
646
|
removeSourceEntries(topLevelServers, options.sourceEntriesToRemove);
|
|
@@ -727,10 +656,10 @@ class ClaudeDesktopAdapter {
|
|
|
727
656
|
}
|
|
728
657
|
raw.mcpServers = topLevelServers;
|
|
729
658
|
writeJsonAtomic(configPath, raw);
|
|
730
|
-
setManagedEntries(options.managedIndex, this.id, configPath, Object.fromEntries(options.managedEntries.map((entry) =>
|
|
731
|
-
entry
|
|
732
|
-
JSON.stringify({
|
|
733
|
-
|
|
659
|
+
setManagedEntries(options.managedIndex, this.id, configPath, Object.fromEntries(options.managedEntries.map((entry) => {
|
|
660
|
+
const { command, args } = buildProxyEntry(entry);
|
|
661
|
+
return [entry.name, JSON.stringify({ command, args, enabled: entry.enabled })];
|
|
662
|
+
})));
|
|
734
663
|
return okResult(this.id, configPath);
|
|
735
664
|
} catch (error) {
|
|
736
665
|
return errorResult(this.id, configPath, error.message);
|
|
@@ -739,31 +668,31 @@ class ClaudeDesktopAdapter {
|
|
|
739
668
|
}
|
|
740
669
|
|
|
741
670
|
// src/adapters/codex.ts
|
|
742
|
-
import
|
|
743
|
-
import
|
|
671
|
+
import path4 from "node:path";
|
|
672
|
+
import fs2 from "node:fs";
|
|
744
673
|
import { parse, stringify } from "@iarna/toml";
|
|
745
|
-
import { z as
|
|
746
|
-
var stringMapSchema3 =
|
|
674
|
+
import { z as z3 } from "zod";
|
|
675
|
+
var stringMapSchema3 = z3.preprocess((value) => {
|
|
747
676
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
748
677
|
return value;
|
|
749
678
|
}
|
|
750
679
|
return Object.fromEntries(Object.entries(value));
|
|
751
|
-
},
|
|
752
|
-
var codexEntrySchema =
|
|
753
|
-
enabled:
|
|
754
|
-
url:
|
|
680
|
+
}, z3.record(z3.string(), z3.string()));
|
|
681
|
+
var codexEntrySchema = z3.object({
|
|
682
|
+
enabled: z3.boolean().optional(),
|
|
683
|
+
url: z3.string().min(1).optional(),
|
|
755
684
|
headers: stringMapSchema3.optional(),
|
|
756
685
|
http_headers: stringMapSchema3.optional(),
|
|
757
|
-
command:
|
|
758
|
-
args:
|
|
686
|
+
command: z3.string().min(1).optional(),
|
|
687
|
+
args: z3.array(z3.string()).optional(),
|
|
759
688
|
env: stringMapSchema3.optional(),
|
|
760
|
-
cwd:
|
|
689
|
+
cwd: z3.string().min(1).optional()
|
|
761
690
|
}).passthrough();
|
|
762
691
|
|
|
763
692
|
class CodexAdapter {
|
|
764
693
|
id = "codex";
|
|
765
694
|
detectConfigPath() {
|
|
766
|
-
return
|
|
695
|
+
return path4.join(homeDir(), ".codex", "config.toml");
|
|
767
696
|
}
|
|
768
697
|
supportsHttp() {
|
|
769
698
|
return true;
|
|
@@ -771,10 +700,10 @@ class CodexAdapter {
|
|
|
771
700
|
scanForImports(_config, managedIndex) {
|
|
772
701
|
const configPath = this.detectConfigPath();
|
|
773
702
|
const result = emptyImportScan(this.id, configPath ?? undefined);
|
|
774
|
-
if (!configPath || !
|
|
703
|
+
if (!configPath || !fs2.existsSync(configPath)) {
|
|
775
704
|
return result;
|
|
776
705
|
}
|
|
777
|
-
const existing =
|
|
706
|
+
const existing = fs2.readFileSync(configPath, "utf8");
|
|
778
707
|
const doc = existing.trim().length > 0 ? parse(existing) : {};
|
|
779
708
|
const mcpServers = doc.mcp_servers ?? {};
|
|
780
709
|
for (const [name, rawEntry] of Object.entries(mcpServers)) {
|
|
@@ -829,7 +758,7 @@ class CodexAdapter {
|
|
|
829
758
|
return errorResult(this.id, undefined, "Unable to resolve Codex config.toml path.");
|
|
830
759
|
}
|
|
831
760
|
try {
|
|
832
|
-
const existing =
|
|
761
|
+
const existing = fs2.existsSync(configPath) ? fs2.readFileSync(configPath, "utf8") : "";
|
|
833
762
|
const doc = existing.trim().length > 0 ? parse(existing) : {};
|
|
834
763
|
const mcpServers = doc.mcp_servers ?? {};
|
|
835
764
|
removeSourceEntries(mcpServers, options.sourceEntriesToRemove);
|
|
@@ -850,10 +779,10 @@ class CodexAdapter {
|
|
|
850
779
|
mcpServers[name] = entry;
|
|
851
780
|
}
|
|
852
781
|
doc.mcp_servers = mcpServers;
|
|
853
|
-
|
|
782
|
+
fs2.mkdirSync(path4.dirname(configPath), { recursive: true });
|
|
854
783
|
const tmpPath = `${configPath}.tmp-${process.pid}-${Date.now()}`;
|
|
855
|
-
|
|
856
|
-
|
|
784
|
+
fs2.writeFileSync(tmpPath, stringify(doc), { mode: 384 });
|
|
785
|
+
fs2.renameSync(tmpPath, configPath);
|
|
857
786
|
setManagedEntries(options.managedIndex, this.id, configPath, Object.fromEntries(Object.entries(serverEntries).map(([name, entry]) => [name, JSON.stringify(entry)])));
|
|
858
787
|
return okResult(this.id, configPath);
|
|
859
788
|
} catch (error) {
|
|
@@ -863,24 +792,30 @@ class CodexAdapter {
|
|
|
863
792
|
}
|
|
864
793
|
|
|
865
794
|
// src/adapters/cursor.ts
|
|
866
|
-
import
|
|
867
|
-
import { z as
|
|
868
|
-
var stringMapSchema4 =
|
|
869
|
-
var cursorEntrySchema =
|
|
870
|
-
type:
|
|
871
|
-
url:
|
|
795
|
+
import path5 from "node:path";
|
|
796
|
+
import { z as z4 } from "zod";
|
|
797
|
+
var stringMapSchema4 = z4.record(z4.string(), z4.string());
|
|
798
|
+
var cursorEntrySchema = z4.object({
|
|
799
|
+
type: z4.string().optional(),
|
|
800
|
+
url: z4.string().min(1).optional(),
|
|
872
801
|
headers: stringMapSchema4.optional(),
|
|
873
|
-
command:
|
|
874
|
-
args:
|
|
802
|
+
command: z4.string().min(1).optional(),
|
|
803
|
+
args: z4.array(z4.string()).optional(),
|
|
875
804
|
env: stringMapSchema4.optional(),
|
|
876
|
-
cwd:
|
|
877
|
-
disabled:
|
|
805
|
+
cwd: z4.string().min(1).optional(),
|
|
806
|
+
disabled: z4.boolean().optional()
|
|
878
807
|
}).passthrough();
|
|
879
808
|
|
|
880
809
|
class CursorAdapter {
|
|
881
810
|
id = "cursor";
|
|
882
811
|
detectConfigPath() {
|
|
883
|
-
|
|
812
|
+
if (process.platform === "linux") {
|
|
813
|
+
return path5.join(homeDir(), ".config", "Cursor", "User", "mcp.json");
|
|
814
|
+
}
|
|
815
|
+
if (process.platform === "win32") {
|
|
816
|
+
return path5.join(process.env.APPDATA ?? "", "Cursor", "User", "mcp.json");
|
|
817
|
+
}
|
|
818
|
+
return path5.join(homeDir(), "Library", "Application Support", "Cursor", "User", "mcp.json");
|
|
884
819
|
}
|
|
885
820
|
supportsHttp() {
|
|
886
821
|
return true;
|
|
@@ -986,24 +921,36 @@ class CursorAdapter {
|
|
|
986
921
|
}
|
|
987
922
|
|
|
988
923
|
// src/adapters/cline.ts
|
|
989
|
-
import
|
|
990
|
-
import
|
|
991
|
-
import { z as
|
|
992
|
-
var stringMapSchema5 =
|
|
993
|
-
var clineEntrySchema =
|
|
994
|
-
transportType:
|
|
995
|
-
url:
|
|
924
|
+
import path6 from "node:path";
|
|
925
|
+
import fs3 from "node:fs";
|
|
926
|
+
import { z as z5 } from "zod";
|
|
927
|
+
var stringMapSchema5 = z5.record(z5.string(), z5.string());
|
|
928
|
+
var clineEntrySchema = z5.object({
|
|
929
|
+
transportType: z5.string().optional(),
|
|
930
|
+
url: z5.string().min(1).optional(),
|
|
996
931
|
headers: stringMapSchema5.optional(),
|
|
997
|
-
command:
|
|
998
|
-
args:
|
|
932
|
+
command: z5.string().min(1).optional(),
|
|
933
|
+
args: z5.array(z5.string()).optional(),
|
|
999
934
|
env: stringMapSchema5.optional(),
|
|
1000
|
-
cwd:
|
|
1001
|
-
disabled:
|
|
935
|
+
cwd: z5.string().min(1).optional(),
|
|
936
|
+
disabled: z5.boolean().optional()
|
|
1002
937
|
}).passthrough();
|
|
1003
938
|
function getCandidatePaths() {
|
|
939
|
+
if (process.platform === "linux") {
|
|
940
|
+
return [
|
|
941
|
+
path6.join(homeDir(), ".config", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json"),
|
|
942
|
+
path6.join(homeDir(), ".config", "Cursor", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json")
|
|
943
|
+
];
|
|
944
|
+
}
|
|
945
|
+
if (process.platform === "win32") {
|
|
946
|
+
return [
|
|
947
|
+
path6.join(process.env.APPDATA ?? "", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json"),
|
|
948
|
+
path6.join(process.env.APPDATA ?? "", "Cursor", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json")
|
|
949
|
+
];
|
|
950
|
+
}
|
|
1004
951
|
return [
|
|
1005
|
-
|
|
1006
|
-
|
|
952
|
+
path6.join(homeDir(), "Library", "Application Support", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json"),
|
|
953
|
+
path6.join(homeDir(), "Library", "Application Support", "Cursor", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json")
|
|
1007
954
|
];
|
|
1008
955
|
}
|
|
1009
956
|
|
|
@@ -1012,7 +959,7 @@ class ClineAdapter {
|
|
|
1012
959
|
detectConfigPath() {
|
|
1013
960
|
const candidates = getCandidatePaths();
|
|
1014
961
|
for (const candidate of candidates) {
|
|
1015
|
-
if (
|
|
962
|
+
if (fs3.existsSync(candidate)) {
|
|
1016
963
|
return candidate;
|
|
1017
964
|
}
|
|
1018
965
|
}
|
|
@@ -1024,7 +971,7 @@ class ClineAdapter {
|
|
|
1024
971
|
scanForImports(_config, managedIndex) {
|
|
1025
972
|
const configPath = this.detectConfigPath();
|
|
1026
973
|
const result = emptyImportScan(this.id, configPath ?? undefined);
|
|
1027
|
-
if (!configPath || !
|
|
974
|
+
if (!configPath || !fs3.existsSync(configPath)) {
|
|
1028
975
|
return result;
|
|
1029
976
|
}
|
|
1030
977
|
const raw = readJsonFile(configPath, {});
|
|
@@ -1119,23 +1066,24 @@ class ClineAdapter {
|
|
|
1119
1066
|
}
|
|
1120
1067
|
|
|
1121
1068
|
// src/adapters/kiro.ts
|
|
1122
|
-
import
|
|
1123
|
-
import
|
|
1124
|
-
|
|
1125
|
-
var
|
|
1126
|
-
|
|
1069
|
+
import fs4 from "node:fs";
|
|
1070
|
+
import path7 from "node:path";
|
|
1071
|
+
import { z as z6 } from "zod";
|
|
1072
|
+
var stringMapSchema6 = z6.record(z6.string(), z6.string());
|
|
1073
|
+
var kiroEntrySchema = z6.object({
|
|
1074
|
+
url: z6.string().min(1).optional(),
|
|
1127
1075
|
headers: stringMapSchema6.optional(),
|
|
1128
|
-
command:
|
|
1129
|
-
args:
|
|
1076
|
+
command: z6.string().min(1).optional(),
|
|
1077
|
+
args: z6.array(z6.string()).optional(),
|
|
1130
1078
|
env: stringMapSchema6.optional(),
|
|
1131
|
-
cwd:
|
|
1132
|
-
disabled:
|
|
1079
|
+
cwd: z6.string().min(1).optional(),
|
|
1080
|
+
disabled: z6.boolean().optional()
|
|
1133
1081
|
}).passthrough();
|
|
1134
1082
|
|
|
1135
1083
|
class KiroAdapter {
|
|
1136
1084
|
id = "kiro";
|
|
1137
1085
|
detectConfigPath() {
|
|
1138
|
-
return
|
|
1086
|
+
return path7.join(homeDir(), ".kiro", "settings", "mcp.json");
|
|
1139
1087
|
}
|
|
1140
1088
|
supportsHttp() {
|
|
1141
1089
|
return true;
|
|
@@ -1234,27 +1182,47 @@ class KiroAdapter {
|
|
|
1234
1182
|
return errorResult(this.id, configPath, error.message);
|
|
1235
1183
|
}
|
|
1236
1184
|
}
|
|
1185
|
+
syncSkills(skills) {
|
|
1186
|
+
const skillsDir = path7.join(homeDir(), ".kiro", "skills");
|
|
1187
|
+
if (!fs4.existsSync(skillsDir)) {
|
|
1188
|
+
fs4.mkdirSync(skillsDir, { recursive: true });
|
|
1189
|
+
}
|
|
1190
|
+
const existingFiles = fs4.readdirSync(skillsDir);
|
|
1191
|
+
const managedIds = new Set(skills.map((s) => s.id));
|
|
1192
|
+
for (const file of existingFiles) {
|
|
1193
|
+
if (file.endsWith(".md")) {
|
|
1194
|
+
const id = path7.basename(file, ".md");
|
|
1195
|
+
if (!managedIds.has(id)) {
|
|
1196
|
+
fs4.unlinkSync(path7.join(skillsDir, file));
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
for (const skill of skills) {
|
|
1201
|
+
const targetPath = path7.join(skillsDir, `${skill.id}.md`);
|
|
1202
|
+
fs4.writeFileSync(targetPath, skill.content, "utf-8");
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1237
1205
|
}
|
|
1238
1206
|
|
|
1239
1207
|
// src/adapters/opencode.ts
|
|
1240
|
-
import
|
|
1241
|
-
import { z as
|
|
1242
|
-
var stringMapSchema7 =
|
|
1243
|
-
var openCodeEntrySchema =
|
|
1244
|
-
type:
|
|
1245
|
-
url:
|
|
1208
|
+
import path8 from "node:path";
|
|
1209
|
+
import { z as z7 } from "zod";
|
|
1210
|
+
var stringMapSchema7 = z7.record(z7.string(), z7.string());
|
|
1211
|
+
var openCodeEntrySchema = z7.object({
|
|
1212
|
+
type: z7.string().optional(),
|
|
1213
|
+
url: z7.string().min(1).optional(),
|
|
1246
1214
|
headers: stringMapSchema7.optional(),
|
|
1247
|
-
command:
|
|
1248
|
-
args:
|
|
1215
|
+
command: z7.string().min(1).optional(),
|
|
1216
|
+
args: z7.array(z7.string()).optional(),
|
|
1249
1217
|
env: stringMapSchema7.optional(),
|
|
1250
|
-
cwd:
|
|
1251
|
-
enabled:
|
|
1218
|
+
cwd: z7.string().min(1).optional(),
|
|
1219
|
+
enabled: z7.boolean().optional()
|
|
1252
1220
|
}).passthrough();
|
|
1253
1221
|
|
|
1254
1222
|
class OpenCodeAdapter {
|
|
1255
1223
|
id = "opencode";
|
|
1256
1224
|
detectConfigPath() {
|
|
1257
|
-
return
|
|
1225
|
+
return path8.join(homeDir(), ".config", "opencode", "opencode.json");
|
|
1258
1226
|
}
|
|
1259
1227
|
supportsHttp() {
|
|
1260
1228
|
return true;
|
|
@@ -1357,24 +1325,30 @@ class OpenCodeAdapter {
|
|
|
1357
1325
|
}
|
|
1358
1326
|
|
|
1359
1327
|
// src/adapters/vscode.ts
|
|
1360
|
-
import
|
|
1361
|
-
import { z as
|
|
1362
|
-
var stringMapSchema8 =
|
|
1363
|
-
var vscodeEntrySchema =
|
|
1364
|
-
type:
|
|
1365
|
-
url:
|
|
1328
|
+
import path9 from "node:path";
|
|
1329
|
+
import { z as z8 } from "zod";
|
|
1330
|
+
var stringMapSchema8 = z8.record(z8.string(), z8.string());
|
|
1331
|
+
var vscodeEntrySchema = z8.object({
|
|
1332
|
+
type: z8.string().optional(),
|
|
1333
|
+
url: z8.string().min(1).optional(),
|
|
1366
1334
|
headers: stringMapSchema8.optional(),
|
|
1367
|
-
command:
|
|
1368
|
-
args:
|
|
1335
|
+
command: z8.string().min(1).optional(),
|
|
1336
|
+
args: z8.array(z8.string()).optional(),
|
|
1369
1337
|
env: stringMapSchema8.optional(),
|
|
1370
|
-
cwd:
|
|
1371
|
-
disabled:
|
|
1338
|
+
cwd: z8.string().min(1).optional(),
|
|
1339
|
+
disabled: z8.boolean().optional()
|
|
1372
1340
|
}).passthrough();
|
|
1373
1341
|
|
|
1374
1342
|
class VsCodeAdapter {
|
|
1375
1343
|
id = "vscode";
|
|
1376
1344
|
detectConfigPath() {
|
|
1377
|
-
|
|
1345
|
+
if (process.platform === "linux") {
|
|
1346
|
+
return path9.join(homeDir(), ".config", "Code", "User", "mcp.json");
|
|
1347
|
+
}
|
|
1348
|
+
if (process.platform === "win32") {
|
|
1349
|
+
return path9.join(process.env.APPDATA ?? "", "Code", "User", "mcp.json");
|
|
1350
|
+
}
|
|
1351
|
+
return path9.join(homeDir(), "Library", "Application Support", "Code", "User", "mcp.json");
|
|
1378
1352
|
}
|
|
1379
1353
|
supportsHttp() {
|
|
1380
1354
|
return true;
|
|
@@ -1480,23 +1454,24 @@ class VsCodeAdapter {
|
|
|
1480
1454
|
}
|
|
1481
1455
|
|
|
1482
1456
|
// src/adapters/qwen.ts
|
|
1483
|
-
import
|
|
1484
|
-
import
|
|
1485
|
-
|
|
1486
|
-
var
|
|
1487
|
-
|
|
1457
|
+
import fs5 from "node:fs";
|
|
1458
|
+
import path10 from "node:path";
|
|
1459
|
+
import { z as z9 } from "zod";
|
|
1460
|
+
var stringMapSchema9 = z9.record(z9.string(), z9.string());
|
|
1461
|
+
var qwenEntrySchema = z9.object({
|
|
1462
|
+
httpUrl: z9.string().min(1).optional(),
|
|
1488
1463
|
headers: stringMapSchema9.optional(),
|
|
1489
|
-
command:
|
|
1490
|
-
args:
|
|
1464
|
+
command: z9.string().min(1).optional(),
|
|
1465
|
+
args: z9.array(z9.string()).optional(),
|
|
1491
1466
|
env: stringMapSchema9.optional(),
|
|
1492
|
-
cwd:
|
|
1493
|
-
disabled:
|
|
1467
|
+
cwd: z9.string().min(1).optional(),
|
|
1468
|
+
disabled: z9.boolean().optional()
|
|
1494
1469
|
}).passthrough();
|
|
1495
1470
|
|
|
1496
1471
|
class QwenAdapter {
|
|
1497
1472
|
id = "qwen";
|
|
1498
1473
|
detectConfigPath() {
|
|
1499
|
-
return
|
|
1474
|
+
return path10.join(homeDir(), ".qwen", "settings.json");
|
|
1500
1475
|
}
|
|
1501
1476
|
supportsHttp() {
|
|
1502
1477
|
return true;
|
|
@@ -1592,6 +1567,26 @@ class QwenAdapter {
|
|
|
1592
1567
|
return errorResult(this.id, configPath, error.message);
|
|
1593
1568
|
}
|
|
1594
1569
|
}
|
|
1570
|
+
syncSkills(skills) {
|
|
1571
|
+
const skillsDir = path10.join(homeDir(), ".qwen", "skills");
|
|
1572
|
+
if (!fs5.existsSync(skillsDir)) {
|
|
1573
|
+
fs5.mkdirSync(skillsDir, { recursive: true });
|
|
1574
|
+
}
|
|
1575
|
+
const existingFiles = fs5.readdirSync(skillsDir);
|
|
1576
|
+
const managedIds = new Set(skills.map((s) => s.id));
|
|
1577
|
+
for (const file of existingFiles) {
|
|
1578
|
+
if (file.endsWith(".md")) {
|
|
1579
|
+
const id = path10.basename(file, ".md");
|
|
1580
|
+
if (!managedIds.has(id)) {
|
|
1581
|
+
fs5.unlinkSync(path10.join(skillsDir, file));
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
for (const skill of skills) {
|
|
1586
|
+
const targetPath = path10.join(skillsDir, `${skill.id}.md`);
|
|
1587
|
+
fs5.writeFileSync(targetPath, skill.content, "utf-8");
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1595
1590
|
}
|
|
1596
1591
|
|
|
1597
1592
|
// src/adapters/index.ts
|
|
@@ -1710,8 +1705,14 @@ function syncAllClients(config, secrets, targetClients) {
|
|
|
1710
1705
|
const localToken = ensureGatewayToken(config, secrets);
|
|
1711
1706
|
const gatewayUrl = getGatewayUrl(config);
|
|
1712
1707
|
const managedEntries = buildManagedEntries(config, gatewayUrl, localToken);
|
|
1708
|
+
const skills = listSkills();
|
|
1713
1709
|
const results = [];
|
|
1714
1710
|
for (const adapter of filteredAdapters) {
|
|
1711
|
+
if (adapter.syncSkills) {
|
|
1712
|
+
try {
|
|
1713
|
+
adapter.syncSkills(skills);
|
|
1714
|
+
} catch (error) {}
|
|
1715
|
+
}
|
|
1715
1716
|
if (!adapter.supportsHttp()) {
|
|
1716
1717
|
results.push({
|
|
1717
1718
|
clientId: adapter.id,
|
|
@@ -1857,11 +1858,11 @@ function maybePrefixBearer(target, value, raw = false) {
|
|
|
1857
1858
|
}
|
|
1858
1859
|
|
|
1859
1860
|
// src/version.ts
|
|
1860
|
-
var APP_VERSION = "0.1.
|
|
1861
|
+
var APP_VERSION = "0.1.77";
|
|
1861
1862
|
|
|
1862
1863
|
// src/core/update.ts
|
|
1863
1864
|
import fs6 from "node:fs";
|
|
1864
|
-
import
|
|
1865
|
+
import path11 from "node:path";
|
|
1865
1866
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
1866
1867
|
function compareVersions(a, b) {
|
|
1867
1868
|
const partsA = a.split(".").map(Number);
|
|
@@ -1909,7 +1910,7 @@ function getStagedUpdate() {
|
|
|
1909
1910
|
if (!info.version || !info.cliPath) {
|
|
1910
1911
|
return null;
|
|
1911
1912
|
}
|
|
1912
|
-
const absoluteCliPath =
|
|
1913
|
+
const absoluteCliPath = path11.isAbsolute(info.cliPath) ? info.cliPath : path11.join(getUpdatesDir(), info.cliPath);
|
|
1913
1914
|
if (!fs6.existsSync(absoluteCliPath)) {
|
|
1914
1915
|
return null;
|
|
1915
1916
|
}
|
|
@@ -1926,7 +1927,7 @@ function stageUpdate(version, cliPath) {
|
|
|
1926
1927
|
ensureDir(updatesDir);
|
|
1927
1928
|
const stagedInfo = {
|
|
1928
1929
|
version,
|
|
1929
|
-
cliPath:
|
|
1930
|
+
cliPath: path11.isAbsolute(cliPath) ? path11.relative(updatesDir, cliPath) : cliPath,
|
|
1930
1931
|
stagedAt: new Date().toISOString()
|
|
1931
1932
|
};
|
|
1932
1933
|
fs6.writeFileSync(getStagedVersionPath(), JSON.stringify(stagedInfo, null, 2), "utf8");
|
|
@@ -1952,7 +1953,7 @@ function removeOldVersions(keepCount = 2) {
|
|
|
1952
1953
|
return;
|
|
1953
1954
|
}
|
|
1954
1955
|
for (const oldVersion of versionDirs.slice(keepCount)) {
|
|
1955
|
-
const oldPath =
|
|
1956
|
+
const oldPath = path11.join(updatesDir, oldVersion);
|
|
1956
1957
|
try {
|
|
1957
1958
|
fs6.rmSync(oldPath, { recursive: true, force: true });
|
|
1958
1959
|
} catch {}
|
|
@@ -1961,12 +1962,12 @@ function removeOldVersions(keepCount = 2) {
|
|
|
1961
1962
|
|
|
1962
1963
|
// src/core/update-manager.ts
|
|
1963
1964
|
import fs7 from "node:fs";
|
|
1964
|
-
import
|
|
1965
|
+
import path12 from "node:path";
|
|
1965
1966
|
import { execSync, spawn } from "node:child_process";
|
|
1966
1967
|
function acquireLock() {
|
|
1967
1968
|
const lockPath = getUpdateLockPath();
|
|
1968
1969
|
try {
|
|
1969
|
-
ensureDir(
|
|
1970
|
+
ensureDir(path12.dirname(lockPath));
|
|
1970
1971
|
fs7.writeFileSync(lockPath, `${process.pid}
|
|
1971
1972
|
`, { flag: "wx" });
|
|
1972
1973
|
return true;
|
|
@@ -2006,7 +2007,7 @@ async function downloadAndStageUpdate() {
|
|
|
2006
2007
|
}
|
|
2007
2008
|
const targetVersion = updateStatus.latestVersion;
|
|
2008
2009
|
const updatesDir = getUpdatesDir();
|
|
2009
|
-
const versionDir =
|
|
2010
|
+
const versionDir = path12.join(updatesDir, `v${targetVersion}`);
|
|
2010
2011
|
if (fs7.existsSync(versionDir)) {
|
|
2011
2012
|
const existingStaged = getStagedUpdate();
|
|
2012
2013
|
if (existingStaged?.version === targetVersion) {
|
|
@@ -2019,7 +2020,7 @@ async function downloadAndStageUpdate() {
|
|
|
2019
2020
|
cwd: versionDir,
|
|
2020
2021
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2021
2022
|
});
|
|
2022
|
-
const tarballPath =
|
|
2023
|
+
const tarballPath = path12.join(versionDir, `kwonye-mcpx-${targetVersion}.tgz`);
|
|
2023
2024
|
if (!fs7.existsSync(tarballPath)) {
|
|
2024
2025
|
throw new Error("Downloaded tarball not found");
|
|
2025
2026
|
}
|
|
@@ -2031,7 +2032,7 @@ async function downloadAndStageUpdate() {
|
|
|
2031
2032
|
cwd: versionDir,
|
|
2032
2033
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2033
2034
|
});
|
|
2034
|
-
const cliPath =
|
|
2035
|
+
const cliPath = path12.join(versionDir, "dist", "cli.js");
|
|
2035
2036
|
if (!fs7.existsSync(cliPath)) {
|
|
2036
2037
|
throw new Error("CLI entry point not found in downloaded package");
|
|
2037
2038
|
}
|
|
@@ -2125,7 +2126,7 @@ import { spawn as spawn2 } from "node:child_process";
|
|
|
2125
2126
|
// src/gateway/server.ts
|
|
2126
2127
|
import http from "node:http";
|
|
2127
2128
|
import { URL } from "node:url";
|
|
2128
|
-
import
|
|
2129
|
+
import crypto from "node:crypto";
|
|
2129
2130
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2130
2131
|
import {
|
|
2131
2132
|
StdioClientTransport,
|
|
@@ -2304,6 +2305,61 @@ async function callUpstream(upstream, method, params, id, secrets, runtime, pass
|
|
|
2304
2305
|
}
|
|
2305
2306
|
return callHttpUpstream(upstream, method, params, id, secrets, passthroughAuthorizationHeader);
|
|
2306
2307
|
}
|
|
2308
|
+
async function getUpstreamTokenCounts(config, secrets, runtime) {
|
|
2309
|
+
if (!runtime.tokenCache) {
|
|
2310
|
+
runtime.tokenCache = new Map;
|
|
2311
|
+
}
|
|
2312
|
+
const results = {};
|
|
2313
|
+
const upstreams = listUpstreams(config);
|
|
2314
|
+
for (const upstream of upstreams) {
|
|
2315
|
+
if (runtime.tokenCache.has(upstream.name)) {
|
|
2316
|
+
results[upstream.name] = runtime.tokenCache.get(upstream.name);
|
|
2317
|
+
continue;
|
|
2318
|
+
}
|
|
2319
|
+
try {
|
|
2320
|
+
const prevTimeout = process.env.MCPX_UPSTREAM_TIMEOUT_MS;
|
|
2321
|
+
process.env.MCPX_UPSTREAM_TIMEOUT_MS = "3000";
|
|
2322
|
+
let toolsCount = 0;
|
|
2323
|
+
let resourcesCount = 0;
|
|
2324
|
+
let promptsCount = 0;
|
|
2325
|
+
try {
|
|
2326
|
+
const toolsResult = await callUpstream(upstream, "tools/list", {}, "token-tools", secrets, runtime);
|
|
2327
|
+
if (toolsResult && toolsResult.tools) {
|
|
2328
|
+
toolsCount = Math.ceil(JSON.stringify(toolsResult.tools).length / 4);
|
|
2329
|
+
}
|
|
2330
|
+
} catch (err) {}
|
|
2331
|
+
try {
|
|
2332
|
+
const resourcesResult = await callUpstream(upstream, "resources/list", {}, "token-resources", secrets, runtime);
|
|
2333
|
+
if (resourcesResult && resourcesResult.resources) {
|
|
2334
|
+
resourcesCount = Math.ceil(JSON.stringify(resourcesResult.resources).length / 4);
|
|
2335
|
+
}
|
|
2336
|
+
} catch (err) {}
|
|
2337
|
+
try {
|
|
2338
|
+
const promptsResult = await callUpstream(upstream, "prompts/list", {}, "token-prompts", secrets, runtime);
|
|
2339
|
+
if (promptsResult && promptsResult.prompts) {
|
|
2340
|
+
promptsCount = Math.ceil(JSON.stringify(promptsResult.prompts).length / 4);
|
|
2341
|
+
}
|
|
2342
|
+
} catch (err) {}
|
|
2343
|
+
if (prevTimeout !== undefined) {
|
|
2344
|
+
process.env.MCPX_UPSTREAM_TIMEOUT_MS = prevTimeout;
|
|
2345
|
+
} else {
|
|
2346
|
+
delete process.env.MCPX_UPSTREAM_TIMEOUT_MS;
|
|
2347
|
+
}
|
|
2348
|
+
const total = toolsCount + resourcesCount + promptsCount;
|
|
2349
|
+
const countObj = {
|
|
2350
|
+
tools: toolsCount,
|
|
2351
|
+
resources: resourcesCount,
|
|
2352
|
+
prompts: promptsCount,
|
|
2353
|
+
total
|
|
2354
|
+
};
|
|
2355
|
+
runtime.tokenCache.set(upstream.name, countObj);
|
|
2356
|
+
results[upstream.name] = countObj;
|
|
2357
|
+
} catch (error) {
|
|
2358
|
+
results[upstream.name] = { tools: 0, resources: 0, prompts: 0, total: 0 };
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
return results;
|
|
2362
|
+
}
|
|
2307
2363
|
function getConfiguredTimeoutMs() {
|
|
2308
2364
|
const configuredTimeout = Number(process.env.MCPX_UPSTREAM_TIMEOUT_MS ?? DEFAULT_UPSTREAM_TIMEOUT_MS);
|
|
2309
2365
|
return Number.isFinite(configuredTimeout) && configuredTimeout > 0 ? configuredTimeout : DEFAULT_UPSTREAM_TIMEOUT_MS;
|
|
@@ -2612,6 +2668,13 @@ async function handleListTools(config, secrets, runtime, upstreamFilter, clientA
|
|
|
2612
2668
|
for (const upstream of upstreams) {
|
|
2613
2669
|
try {
|
|
2614
2670
|
const result = await callUpstream(upstream, "tools/list", {}, `list-tools-${upstream.name}`, secrets, runtime, clientAuthorizationHeader);
|
|
2671
|
+
if (!runtime.tokenCache) {
|
|
2672
|
+
runtime.tokenCache = new Map;
|
|
2673
|
+
}
|
|
2674
|
+
const existing = runtime.tokenCache.get(upstream.name) ?? { tools: 0, resources: 0, prompts: 0, total: 0 };
|
|
2675
|
+
existing.tools = Math.ceil(JSON.stringify(result.tools ?? []).length / 4);
|
|
2676
|
+
existing.total = existing.tools + existing.resources + existing.prompts;
|
|
2677
|
+
runtime.tokenCache.set(upstream.name, existing);
|
|
2615
2678
|
for (const tool of result.tools ?? []) {
|
|
2616
2679
|
const name = typeof tool.name === "string" ? tool.name : "tool";
|
|
2617
2680
|
tools.push({
|
|
@@ -2635,6 +2698,13 @@ async function handleListResources(config, secrets, runtime, upstreamFilter, cli
|
|
|
2635
2698
|
for (const upstream of upstreams) {
|
|
2636
2699
|
try {
|
|
2637
2700
|
const result = await callUpstream(upstream, "resources/list", {}, `list-resources-${upstream.name}`, secrets, runtime, clientAuthorizationHeader);
|
|
2701
|
+
if (!runtime.tokenCache) {
|
|
2702
|
+
runtime.tokenCache = new Map;
|
|
2703
|
+
}
|
|
2704
|
+
const existing = runtime.tokenCache.get(upstream.name) ?? { tools: 0, resources: 0, prompts: 0, total: 0 };
|
|
2705
|
+
existing.resources = Math.ceil(JSON.stringify(result.resources ?? []).length / 4);
|
|
2706
|
+
existing.total = existing.tools + existing.resources + existing.prompts;
|
|
2707
|
+
runtime.tokenCache.set(upstream.name, existing);
|
|
2638
2708
|
for (const resource of result.resources ?? []) {
|
|
2639
2709
|
const originalUri = typeof resource.uri === "string" ? resource.uri : "";
|
|
2640
2710
|
const originalName = typeof resource.name === "string" ? resource.name : originalUri;
|
|
@@ -2660,6 +2730,13 @@ async function handleListPrompts(config, secrets, runtime, upstreamFilter, clien
|
|
|
2660
2730
|
for (const upstream of upstreams) {
|
|
2661
2731
|
try {
|
|
2662
2732
|
const result = await callUpstream(upstream, "prompts/list", {}, `list-prompts-${upstream.name}`, secrets, runtime, clientAuthorizationHeader);
|
|
2733
|
+
if (!runtime.tokenCache) {
|
|
2734
|
+
runtime.tokenCache = new Map;
|
|
2735
|
+
}
|
|
2736
|
+
const existing = runtime.tokenCache.get(upstream.name) ?? { tools: 0, resources: 0, prompts: 0, total: 0 };
|
|
2737
|
+
existing.prompts = Math.ceil(JSON.stringify(result.prompts ?? []).length / 4);
|
|
2738
|
+
existing.total = existing.tools + existing.resources + existing.prompts;
|
|
2739
|
+
runtime.tokenCache.set(upstream.name, existing);
|
|
2663
2740
|
for (const prompt of result.prompts ?? []) {
|
|
2664
2741
|
const name = typeof prompt.name === "string" ? prompt.name : "prompt";
|
|
2665
2742
|
prompts.push({
|
|
@@ -2801,11 +2878,15 @@ async function handleRequestObject(request, secrets, runtime, upstreamFilter, cl
|
|
|
2801
2878
|
if (request.method === "ping") {
|
|
2802
2879
|
return makeResult(id, { ok: true });
|
|
2803
2880
|
}
|
|
2804
|
-
const config =
|
|
2881
|
+
const config = loadMergedConfig();
|
|
2805
2882
|
if (upstreamFilter && listUpstreams(config, upstreamFilter).length === 0) {
|
|
2806
2883
|
return makeError(id, -32602, `Unknown upstream: ${upstreamFilter}`);
|
|
2807
2884
|
}
|
|
2808
2885
|
await reconcileStdioConnections(config, runtime);
|
|
2886
|
+
if (request.method === "custom/tokenCounts") {
|
|
2887
|
+
const result = await getUpstreamTokenCounts(config, secrets, runtime);
|
|
2888
|
+
return makeResult(id, result);
|
|
2889
|
+
}
|
|
2809
2890
|
if (request.method === "tools/list") {
|
|
2810
2891
|
const result = await handleListTools(config, secrets, runtime, upstreamFilter, clientAuthorizationHeader);
|
|
2811
2892
|
return makeResult(id, result);
|
|
@@ -2835,7 +2916,7 @@ async function maybeHandleWellKnownOAuthRequest(request, response, requestUrl, s
|
|
|
2835
2916
|
response.end(JSON.stringify({ error: "method_not_allowed" }));
|
|
2836
2917
|
return true;
|
|
2837
2918
|
}
|
|
2838
|
-
const config =
|
|
2919
|
+
const config = loadMergedConfig();
|
|
2839
2920
|
const requestedUpstream = getRequestedUpstream(requestUrl);
|
|
2840
2921
|
const upstream = getScopedHttpUpstream(config, requestedUpstream);
|
|
2841
2922
|
if (!upstream) {
|
|
@@ -3008,7 +3089,7 @@ function createGatewayServer(options) {
|
|
|
3008
3089
|
response.setHeader("content-type", "application/json");
|
|
3009
3090
|
}
|
|
3010
3091
|
if (hasInitialize) {
|
|
3011
|
-
const sessionId =
|
|
3092
|
+
const sessionId = crypto.randomUUID();
|
|
3012
3093
|
response.setHeader("mcp-session-id", sessionId);
|
|
3013
3094
|
response.setHeader("MCP-Session-Id", sessionId);
|
|
3014
3095
|
}
|
|
@@ -3136,12 +3217,18 @@ function getDaemonStatus(config) {
|
|
|
3136
3217
|
async function startDaemon(config, cliPath, secrets) {
|
|
3137
3218
|
const existingStatus = getDaemonStatus(config);
|
|
3138
3219
|
if (existingStatus.running && existingStatus.pid) {
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3220
|
+
const portAvailable = await isPortAvailable(config.gateway.port);
|
|
3221
|
+
if (!portAvailable) {
|
|
3222
|
+
return {
|
|
3223
|
+
started: false,
|
|
3224
|
+
pid: existingStatus.pid,
|
|
3225
|
+
port: config.gateway.port,
|
|
3226
|
+
message: "mcpx daemon already running."
|
|
3227
|
+
};
|
|
3228
|
+
}
|
|
3229
|
+
try {
|
|
3230
|
+
fs8.unlinkSync(getPidPath());
|
|
3231
|
+
} catch {}
|
|
3145
3232
|
}
|
|
3146
3233
|
const port = await resolveGatewayPort(config);
|
|
3147
3234
|
ensureGatewayToken(config, secrets);
|
|
@@ -3267,26 +3354,74 @@ function buildAuthBindings(spec) {
|
|
|
3267
3354
|
secretName: secretRefName(binding.value) ?? undefined
|
|
3268
3355
|
}));
|
|
3269
3356
|
}
|
|
3270
|
-
function
|
|
3357
|
+
async function fetchTokenCounts(gatewayUrl, token) {
|
|
3358
|
+
try {
|
|
3359
|
+
const res = await fetch(gatewayUrl, {
|
|
3360
|
+
method: "POST",
|
|
3361
|
+
headers: {
|
|
3362
|
+
"content-type": "application/json",
|
|
3363
|
+
authorization: `Bearer ${token}`
|
|
3364
|
+
},
|
|
3365
|
+
body: JSON.stringify({
|
|
3366
|
+
jsonrpc: "2.0",
|
|
3367
|
+
id: "status-tokens",
|
|
3368
|
+
method: "custom/tokenCounts",
|
|
3369
|
+
params: {}
|
|
3370
|
+
})
|
|
3371
|
+
});
|
|
3372
|
+
if (!res.ok) {
|
|
3373
|
+
return {};
|
|
3374
|
+
}
|
|
3375
|
+
const data = await res.json();
|
|
3376
|
+
return data?.result ?? {};
|
|
3377
|
+
} catch (error) {
|
|
3378
|
+
return {};
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
async function buildStatusReport(config, managedIndex, daemon = getDaemonStatus(config)) {
|
|
3382
|
+
const secrets = new SecretsManager;
|
|
3383
|
+
const token = ensureGatewayToken(config, secrets);
|
|
3384
|
+
const gatewayUrl = getGatewayUrl(config);
|
|
3385
|
+
let tokenCounts = {};
|
|
3386
|
+
if (daemon.running) {
|
|
3387
|
+
tokenCounts = await fetchTokenCounts(gatewayUrl, token);
|
|
3388
|
+
}
|
|
3389
|
+
const projectEntries = Object.values(config.projects ?? {});
|
|
3390
|
+
let totalGlobalTokens = 0;
|
|
3391
|
+
const totalProjectTokens = {};
|
|
3271
3392
|
const serverNames = Object.keys(config.servers).sort((left, right) => left.localeCompare(right));
|
|
3272
3393
|
const servers = serverNames.map((name) => {
|
|
3273
3394
|
const spec = config.servers[name];
|
|
3274
|
-
|
|
3395
|
+
const tokenCount = tokenCounts[name];
|
|
3396
|
+
const serverEntry = {
|
|
3275
3397
|
name,
|
|
3276
3398
|
enabled: isServerEnabled(spec),
|
|
3277
3399
|
transport: spec.transport,
|
|
3278
3400
|
target: describeServerTarget(spec),
|
|
3279
3401
|
authBindings: buildAuthBindings(spec),
|
|
3280
|
-
clients: buildClientMappings(config, managedIndex, name)
|
|
3402
|
+
clients: buildClientMappings(config, managedIndex, name),
|
|
3403
|
+
tokenCount
|
|
3281
3404
|
};
|
|
3405
|
+
if (serverEntry.enabled && tokenCount) {
|
|
3406
|
+
const matchingProject = projectEntries.find((p) => name.startsWith(`${p.name}.`));
|
|
3407
|
+
if (matchingProject) {
|
|
3408
|
+
totalProjectTokens[matchingProject.path] = (totalProjectTokens[matchingProject.path] ?? 0) + tokenCount.total;
|
|
3409
|
+
} else {
|
|
3410
|
+
totalGlobalTokens += tokenCount.total;
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3413
|
+
return serverEntry;
|
|
3282
3414
|
});
|
|
3283
3415
|
return {
|
|
3284
|
-
gatewayUrl
|
|
3416
|
+
gatewayUrl,
|
|
3285
3417
|
daemon,
|
|
3286
3418
|
upstreamCount: servers.length,
|
|
3287
3419
|
servers,
|
|
3288
|
-
clients: config.clients
|
|
3420
|
+
clients: config.clients,
|
|
3421
|
+
projects: config.projects,
|
|
3422
|
+
totalGlobalTokens,
|
|
3423
|
+
totalProjectTokens
|
|
3289
3424
|
};
|
|
3290
3425
|
}
|
|
3291
3426
|
|
|
3292
|
-
export {
|
|
3427
|
+
export { listSkills, getSkill, saveSkill, deleteSkill, probeHttpAuthRequirement, fetchRegistryServerDetail, selectBestPackage, extractRequiredInputs, mapRegistryToSpec, getAdapters, loadManagedIndex, getGatewayUrl, syncAllClients, toSecretRef, secretRefName, defaultAuthSecretName, resolveAuthTarget, applyAuthReference, removeAuthReference, listAuthBindings, maybePrefixBearer, APP_VERSION, compareVersions, checkForUpdates, getStagedUpdate, clearStagedUpdate, getStagedCliPath, startBackgroundUpdateCheck, runBackgroundUpdate, performUpdate, performRollback, getDaemonStatus, startDaemon, stopDaemon, restartDaemon, readDaemonLogs, runDaemonForeground, STATUS_CLIENTS, buildStatusReport };
|