@kwonye/mcpx 0.1.75 → 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/vscode.d.ts.map +1 -1
- package/dist/cli-7bsf6akp.js +202 -0
- package/dist/{cli-fh6jh7fs.js → cli-f16kphtq.js} +539 -517
- package/dist/cli-fsf33sgp.js +105 -0
- package/dist/{cli-e9hdd44n.js → cli-htm5cf82.js} +538 -516
- 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 +359 -56
- 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/index.d.ts +4 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +36 -12
- package/dist/core/paths.d.ts +2 -0
- package/dist/core/paths.d.ts.map +1 -1
- 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/status.d.ts +6 -2
- package/dist/core/status.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 +12 -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,238 +1,45 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
} from "./cli-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
function getConfigPath() {
|
|
29
|
-
return path.join(getConfigRoot(), "mcpx", "config.json");
|
|
30
|
-
}
|
|
31
|
-
function getSkillsDir() {
|
|
32
|
-
return path.join(getConfigRoot(), "mcpx", "skills");
|
|
33
|
-
}
|
|
34
|
-
function getManagedIndexPath() {
|
|
35
|
-
return path.join(getDataRoot(), "mcpx", "managed-index.json");
|
|
36
|
-
}
|
|
37
|
-
function getSecretNamesPath() {
|
|
38
|
-
return path.join(getDataRoot(), "mcpx", "secret-names.json");
|
|
39
|
-
}
|
|
40
|
-
function getRuntimeDir() {
|
|
41
|
-
return path.join(getStateRoot(), "mcpx", "runtime");
|
|
42
|
-
}
|
|
43
|
-
function getLogDir() {
|
|
44
|
-
return path.join(getStateRoot(), "mcpx", "logs");
|
|
45
|
-
}
|
|
46
|
-
function getPidPath() {
|
|
47
|
-
return path.join(getRuntimeDir(), "daemon.pid");
|
|
48
|
-
}
|
|
49
|
-
function getLogPath() {
|
|
50
|
-
return path.join(getLogDir(), "daemon.log");
|
|
51
|
-
}
|
|
52
|
-
function getUpdatesDir() {
|
|
53
|
-
return path.join(getDataRoot(), "mcpx", "updates");
|
|
54
|
-
}
|
|
55
|
-
function getStagedVersionPath() {
|
|
56
|
-
return path.join(getUpdatesDir(), "staged-version.json");
|
|
57
|
-
}
|
|
58
|
-
function getUpdateLockPath() {
|
|
59
|
-
return path.join(getUpdatesDir(), ".update.lock");
|
|
60
|
-
}
|
|
61
|
-
function ensureDir(dirPath) {
|
|
62
|
-
fs.mkdirSync(dirPath, { recursive: true });
|
|
63
|
-
}
|
|
64
|
-
function ensureParentDir(filePath) {
|
|
65
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// src/core/config.ts
|
|
69
|
-
import { z } from "zod";
|
|
70
|
-
|
|
71
|
-
// src/util/fs.ts
|
|
72
|
-
import fs2 from "node:fs";
|
|
73
|
-
import path2 from "node:path";
|
|
74
|
-
import crypto from "node:crypto";
|
|
75
|
-
function readJsonFile(filePath, fallback) {
|
|
76
|
-
if (!fs2.existsSync(filePath)) {
|
|
77
|
-
return fallback;
|
|
78
|
-
}
|
|
79
|
-
try {
|
|
80
|
-
const raw = fs2.readFileSync(filePath, "utf8");
|
|
81
|
-
return JSON.parse(raw);
|
|
82
|
-
} catch {
|
|
83
|
-
return fallback;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
function writeJsonAtomic(filePath, data) {
|
|
87
|
-
fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
|
|
88
|
-
const tempPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
89
|
-
const content = `${JSON.stringify(data, null, 2)}
|
|
90
|
-
`;
|
|
91
|
-
fs2.writeFileSync(tempPath, content, { mode: 384 });
|
|
92
|
-
fs2.renameSync(tempPath, filePath);
|
|
93
|
-
}
|
|
94
|
-
function sha256(value) {
|
|
95
|
-
return crypto.createHash("sha256").update(value).digest("hex");
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// src/core/config.ts
|
|
99
|
-
var clientStateSchema = z.object({
|
|
100
|
-
status: z.enum(["SYNCED", "UNSUPPORTED_HTTP", "ERROR", "SKIPPED"]),
|
|
101
|
-
lastSyncAt: z.string().optional(),
|
|
102
|
-
message: z.string().optional(),
|
|
103
|
-
configPath: z.string().optional()
|
|
104
|
-
});
|
|
105
|
-
var httpServerSchema = z.object({
|
|
106
|
-
transport: z.literal("http"),
|
|
107
|
-
url: z.url(),
|
|
108
|
-
headers: z.record(z.string(), z.string()).optional(),
|
|
109
|
-
enabled: z.boolean().default(true)
|
|
110
|
-
});
|
|
111
|
-
var stdioServerSchema = z.object({
|
|
112
|
-
transport: z.literal("stdio"),
|
|
113
|
-
command: z.string().min(1),
|
|
114
|
-
args: z.array(z.string()).optional(),
|
|
115
|
-
env: z.record(z.string(), z.string()).optional(),
|
|
116
|
-
cwd: z.string().min(1).optional(),
|
|
117
|
-
enabled: z.boolean().default(true)
|
|
118
|
-
});
|
|
119
|
-
var serverSchema = z.discriminatedUnion("transport", [httpServerSchema, stdioServerSchema]);
|
|
120
|
-
var configSchema = z.object({
|
|
121
|
-
schemaVersion: z.literal(1).default(1),
|
|
122
|
-
gateway: z.object({
|
|
123
|
-
port: z.number().int().min(1).max(65535).default(37373),
|
|
124
|
-
tokenRef: z.string().default("secret://local_gateway_token"),
|
|
125
|
-
autoStart: z.boolean().default(true)
|
|
126
|
-
}).default({
|
|
127
|
-
port: 37373,
|
|
128
|
-
tokenRef: "secret://local_gateway_token",
|
|
129
|
-
autoStart: true
|
|
130
|
-
}),
|
|
131
|
-
servers: z.record(z.string(), serverSchema).default({}),
|
|
132
|
-
clients: z.record(z.string(), clientStateSchema).default({})
|
|
133
|
-
});
|
|
134
|
-
function defaultConfig() {
|
|
135
|
-
return {
|
|
136
|
-
schemaVersion: 1,
|
|
137
|
-
gateway: {
|
|
138
|
-
port: 37373,
|
|
139
|
-
tokenRef: "secret://local_gateway_token",
|
|
140
|
-
autoStart: true
|
|
141
|
-
},
|
|
142
|
-
servers: {},
|
|
143
|
-
clients: {}
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
function loadConfig(configPath = getConfigPath()) {
|
|
147
|
-
const raw = readJsonFile(configPath, defaultConfig());
|
|
148
|
-
const parsed = configSchema.safeParse(raw);
|
|
149
|
-
if (!parsed.success) {
|
|
150
|
-
return defaultConfig();
|
|
151
|
-
}
|
|
152
|
-
const clientEntries = {};
|
|
153
|
-
for (const [key, value] of Object.entries(parsed.data.clients)) {
|
|
154
|
-
if (["claude", "claude-desktop", "codex", "cursor", "cline", "opencode", "kiro", "vscode", "qwen"].includes(key)) {
|
|
155
|
-
clientEntries[key] = value;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
const servers = Object.fromEntries(Object.entries(parsed.data.servers).map(([name, spec]) => [name, normalizeServerSpecEnabled(spec)]));
|
|
159
|
-
return {
|
|
160
|
-
schemaVersion: 1,
|
|
161
|
-
gateway: parsed.data.gateway,
|
|
162
|
-
servers,
|
|
163
|
-
clients: clientEntries
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
function saveConfig(config, configPath = getConfigPath()) {
|
|
167
|
-
config.servers = Object.fromEntries(Object.entries(config.servers).map(([name, spec]) => [name, normalizeServerSpecEnabled(spec)]));
|
|
168
|
-
writeJsonAtomic(configPath, config);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// src/core/registry.ts
|
|
172
|
-
function validateServerName(name) {
|
|
173
|
-
if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,62}$/.test(name)) {
|
|
174
|
-
throw new Error("Invalid server name. Use 1-63 chars: letters, numbers, '.', '-', '_' and start with alphanumeric.");
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
function addServer(config, name, spec, force = false) {
|
|
178
|
-
validateServerName(name);
|
|
179
|
-
if (config.servers[name] && !force) {
|
|
180
|
-
throw new Error(`Server "${name}" already exists. Use --force to overwrite.`);
|
|
181
|
-
}
|
|
182
|
-
config.servers[name] = normalizeServerSpecEnabled(spec);
|
|
183
|
-
}
|
|
184
|
-
function removeServer(config, name, force = false) {
|
|
185
|
-
if (!config.servers[name] && !force) {
|
|
186
|
-
throw new Error(`Server "${name}" does not exist.`);
|
|
187
|
-
}
|
|
188
|
-
delete config.servers[name];
|
|
189
|
-
}
|
|
190
|
-
function updateServer(config, name, spec) {
|
|
191
|
-
validateServerName(name);
|
|
192
|
-
if (!config.servers[name]) {
|
|
193
|
-
throw new Error(`Server "${name}" does not exist.`);
|
|
194
|
-
}
|
|
195
|
-
config.servers[name] = normalizeServerSpecEnabled(spec);
|
|
196
|
-
}
|
|
197
|
-
function setServerEnabled(config, name, enabled) {
|
|
198
|
-
const existing = config.servers[name];
|
|
199
|
-
if (!existing) {
|
|
200
|
-
throw new Error(`Server "${name}" does not exist.`);
|
|
201
|
-
}
|
|
202
|
-
config.servers[name] = {
|
|
203
|
-
...existing,
|
|
204
|
-
enabled
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
function getGatewayTokenSecretName(config) {
|
|
208
|
-
if (!config.gateway.tokenRef.startsWith("secret://")) {
|
|
209
|
-
return "local_gateway_token";
|
|
210
|
-
}
|
|
211
|
-
return config.gateway.tokenRef.slice("secret://".length) || "local_gateway_token";
|
|
212
|
-
}
|
|
213
|
-
function ensureGatewayToken(config, secrets) {
|
|
214
|
-
const secretName = getGatewayTokenSecretName(config);
|
|
215
|
-
const existing = secrets.getSecret(secretName);
|
|
216
|
-
if (existing) {
|
|
217
|
-
return existing;
|
|
218
|
-
}
|
|
219
|
-
return secrets.rotateLocalToken(secretName);
|
|
220
|
-
}
|
|
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";
|
|
221
28
|
|
|
222
29
|
// src/core/skills.ts
|
|
223
|
-
import
|
|
224
|
-
import
|
|
30
|
+
import fs from "node:fs";
|
|
31
|
+
import path from "node:path";
|
|
225
32
|
function listSkills() {
|
|
226
33
|
const skillsDir = getSkillsDir();
|
|
227
|
-
if (!
|
|
34
|
+
if (!fs.existsSync(skillsDir)) {
|
|
228
35
|
return [];
|
|
229
36
|
}
|
|
230
|
-
const files =
|
|
37
|
+
const files = fs.readdirSync(skillsDir);
|
|
231
38
|
const skills = [];
|
|
232
39
|
for (const file of files) {
|
|
233
40
|
if (file.endsWith(".md")) {
|
|
234
|
-
const id =
|
|
235
|
-
const content =
|
|
41
|
+
const id = path.basename(file, ".md");
|
|
42
|
+
const content = fs.readFileSync(path.join(skillsDir, file), "utf-8");
|
|
236
43
|
skills.push({ id, content });
|
|
237
44
|
}
|
|
238
45
|
}
|
|
@@ -240,134 +47,52 @@ function listSkills() {
|
|
|
240
47
|
}
|
|
241
48
|
function getSkill(id) {
|
|
242
49
|
const skillsDir = getSkillsDir();
|
|
243
|
-
const filePath =
|
|
244
|
-
if (!
|
|
50
|
+
const filePath = path.join(skillsDir, `${id}.md`);
|
|
51
|
+
if (!fs.existsSync(filePath)) {
|
|
245
52
|
return null;
|
|
246
53
|
}
|
|
247
|
-
const content =
|
|
54
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
248
55
|
return { id, content };
|
|
249
56
|
}
|
|
250
57
|
function saveSkill(id, content) {
|
|
251
58
|
const skillsDir = getSkillsDir();
|
|
252
59
|
ensureDir(skillsDir);
|
|
253
|
-
const filePath =
|
|
254
|
-
|
|
60
|
+
const filePath = path.join(skillsDir, `${id}.md`);
|
|
61
|
+
fs.writeFileSync(filePath, content, "utf-8");
|
|
255
62
|
}
|
|
256
63
|
function deleteSkill(id) {
|
|
257
64
|
const skillsDir = getSkillsDir();
|
|
258
|
-
const filePath =
|
|
259
|
-
if (
|
|
260
|
-
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// src/core/secrets.ts
|
|
265
|
-
import { execFileSync } from "node:child_process";
|
|
266
|
-
import crypto2 from "node:crypto";
|
|
267
|
-
import fs4 from "node:fs";
|
|
268
|
-
function parseSecretRef(ref) {
|
|
269
|
-
if (!ref.startsWith("secret://")) {
|
|
270
|
-
return null;
|
|
65
|
+
const filePath = path.join(skillsDir, `${id}.md`);
|
|
66
|
+
if (fs.existsSync(filePath)) {
|
|
67
|
+
fs.unlinkSync(filePath);
|
|
271
68
|
}
|
|
272
|
-
const name = ref.slice("secret://".length);
|
|
273
|
-
return name.length > 0 ? name : null;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
class SecretsManager {
|
|
277
|
-
service;
|
|
278
|
-
nameIndexPath;
|
|
279
|
-
constructor(service = "mcpx", nameIndexPath = getSecretNamesPath()) {
|
|
280
|
-
this.service = service;
|
|
281
|
-
this.nameIndexPath = nameIndexPath;
|
|
282
|
-
}
|
|
283
|
-
loadNameIndex() {
|
|
284
|
-
return readJsonFile(this.nameIndexPath, { names: [] });
|
|
285
|
-
}
|
|
286
|
-
saveNameIndex(index) {
|
|
287
|
-
const deduped = Array.from(new Set(index.names)).sort();
|
|
288
|
-
writeJsonAtomic(this.nameIndexPath, { names: deduped });
|
|
289
|
-
}
|
|
290
|
-
trackSecretName(name) {
|
|
291
|
-
const index = this.loadNameIndex();
|
|
292
|
-
if (!index.names.includes(name)) {
|
|
293
|
-
index.names.push(name);
|
|
294
|
-
this.saveNameIndex(index);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
untrackSecretName(name) {
|
|
298
|
-
const index = this.loadNameIndex();
|
|
299
|
-
index.names = index.names.filter((entry) => entry !== name);
|
|
300
|
-
this.saveNameIndex(index);
|
|
301
|
-
}
|
|
302
|
-
listSecretNames() {
|
|
303
|
-
return this.loadNameIndex().names;
|
|
304
|
-
}
|
|
305
|
-
setSecret(name, value) {
|
|
306
|
-
if (process.platform !== "darwin") {
|
|
307
|
-
throw new Error("OS keychain integration is currently implemented for macOS only.");
|
|
308
|
-
}
|
|
309
|
-
execFileSync("security", ["add-generic-password", "-U", "-a", name, "-s", this.service, "-w", value], {
|
|
310
|
-
stdio: "ignore"
|
|
311
|
-
});
|
|
312
|
-
this.trackSecretName(name);
|
|
313
|
-
}
|
|
314
|
-
removeSecret(name) {
|
|
315
|
-
if (process.platform === "darwin") {
|
|
316
|
-
try {
|
|
317
|
-
execFileSync("security", ["delete-generic-password", "-a", name, "-s", this.service], {
|
|
318
|
-
stdio: "ignore"
|
|
319
|
-
});
|
|
320
|
-
} catch {}
|
|
321
|
-
}
|
|
322
|
-
this.untrackSecretName(name);
|
|
323
|
-
}
|
|
324
|
-
getSecret(name) {
|
|
325
|
-
const envOverride = process.env[`MCPX_SECRET_${name}`];
|
|
326
|
-
if (envOverride && envOverride.length > 0) {
|
|
327
|
-
return envOverride;
|
|
328
|
-
}
|
|
329
|
-
if (process.platform !== "darwin") {
|
|
330
|
-
return null;
|
|
331
|
-
}
|
|
332
|
-
try {
|
|
333
|
-
return execFileSync("security", ["find-generic-password", "-w", "-a", name, "-s", this.service], {
|
|
334
|
-
encoding: "utf8"
|
|
335
|
-
}).trim();
|
|
336
|
-
} catch {
|
|
337
|
-
return null;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
resolveMaybeSecret(value) {
|
|
341
|
-
const secretName = parseSecretRef(value);
|
|
342
|
-
if (!secretName) {
|
|
343
|
-
return value;
|
|
344
|
-
}
|
|
345
|
-
const secret = this.getSecret(secretName);
|
|
346
|
-
if (!secret) {
|
|
347
|
-
throw new Error(`Secret not found: ${secretName}`);
|
|
348
|
-
}
|
|
349
|
-
return secret;
|
|
350
|
-
}
|
|
351
|
-
rotateLocalToken(secretName = "local_gateway_token") {
|
|
352
|
-
const token = crypto2.randomBytes(32).toString("base64url");
|
|
353
|
-
this.setSecret(secretName, token);
|
|
354
|
-
return token;
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
function readSecretValueFromStdin() {
|
|
358
|
-
if (process.stdin.isTTY) {
|
|
359
|
-
throw new Error("No value provided. Use --value or pipe a value via stdin.");
|
|
360
|
-
}
|
|
361
|
-
const content = fs4.readFileSync(process.stdin.fd, "utf8").trim();
|
|
362
|
-
if (content.length === 0) {
|
|
363
|
-
throw new Error("Empty secret value provided.");
|
|
364
|
-
}
|
|
365
|
-
return content;
|
|
366
69
|
}
|
|
367
70
|
|
|
368
71
|
// src/core/auth-probe.ts
|
|
369
72
|
var JSON_RPC_VERSION = "2.0";
|
|
370
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
|
+
}
|
|
371
96
|
function resolveHeaders(spec, secrets) {
|
|
372
97
|
const headers = {
|
|
373
98
|
"content-type": "application/json",
|
|
@@ -401,6 +126,22 @@ async function probeHttpAuthRequirement(spec, secrets, timeoutMs = DEFAULT_AUTH_
|
|
|
401
126
|
wwwAuthenticate: response.headers.get("www-authenticate") ?? undefined
|
|
402
127
|
};
|
|
403
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
|
+
}
|
|
404
145
|
return {
|
|
405
146
|
authRequired: false,
|
|
406
147
|
status: response.status
|
|
@@ -416,9 +157,132 @@ async function probeHttpAuthRequirement(spec, secrets, timeoutMs = DEFAULT_AUTH_
|
|
|
416
157
|
}
|
|
417
158
|
}
|
|
418
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
|
+
|
|
419
283
|
// src/adapters/claude.ts
|
|
420
|
-
import
|
|
421
|
-
import { z
|
|
284
|
+
import path2 from "node:path";
|
|
285
|
+
import { z } from "zod";
|
|
422
286
|
|
|
423
287
|
// src/adapters/utils/index.ts
|
|
424
288
|
function getManagedEntryNames(managedIndex, clientId) {
|
|
@@ -546,22 +410,22 @@ function syncMcpExcludedArray(raw, managedNames, managedEntries) {
|
|
|
546
410
|
}
|
|
547
411
|
|
|
548
412
|
// src/adapters/claude.ts
|
|
549
|
-
var stringMapSchema =
|
|
550
|
-
var claudeEntrySchema =
|
|
551
|
-
type:
|
|
552
|
-
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(),
|
|
553
417
|
headers: stringMapSchema.optional(),
|
|
554
|
-
command:
|
|
555
|
-
args:
|
|
418
|
+
command: z.string().min(1).optional(),
|
|
419
|
+
args: z.array(z.string()).optional(),
|
|
556
420
|
env: stringMapSchema.optional(),
|
|
557
|
-
cwd:
|
|
558
|
-
disabled:
|
|
421
|
+
cwd: z.string().min(1).optional(),
|
|
422
|
+
disabled: z.boolean().optional()
|
|
559
423
|
}).passthrough();
|
|
560
424
|
|
|
561
425
|
class ClaudeAdapter {
|
|
562
426
|
id = "claude";
|
|
563
427
|
detectConfigPath() {
|
|
564
|
-
return
|
|
428
|
+
return path2.join(homeDir(), ".claude.json");
|
|
565
429
|
}
|
|
566
430
|
supportsHttp() {
|
|
567
431
|
return true;
|
|
@@ -673,24 +537,24 @@ class ClaudeAdapter {
|
|
|
673
537
|
}
|
|
674
538
|
|
|
675
539
|
// src/adapters/claude-desktop.ts
|
|
676
|
-
import { execFileSync
|
|
677
|
-
import
|
|
678
|
-
import { z as
|
|
679
|
-
var stringMapSchema2 =
|
|
680
|
-
var claudeDesktopEntrySchema =
|
|
681
|
-
type:
|
|
682
|
-
url:
|
|
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(),
|
|
683
547
|
headers: stringMapSchema2.optional(),
|
|
684
|
-
command:
|
|
685
|
-
args:
|
|
548
|
+
command: z2.string().min(1).optional(),
|
|
549
|
+
args: z2.array(z2.string()).optional(),
|
|
686
550
|
env: stringMapSchema2.optional(),
|
|
687
|
-
cwd:
|
|
688
|
-
disabled:
|
|
551
|
+
cwd: z2.string().min(1).optional(),
|
|
552
|
+
disabled: z2.boolean().optional()
|
|
689
553
|
}).passthrough();
|
|
690
554
|
function buildProxyEntry(entry) {
|
|
691
555
|
const upstreamName = entry.name.replace(/ \(mcpx\)$/, "");
|
|
692
556
|
try {
|
|
693
|
-
const mcpxPath =
|
|
557
|
+
const mcpxPath = execFileSync("which", ["mcpx"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
694
558
|
if (mcpxPath)
|
|
695
559
|
return { command: mcpxPath, args: ["proxy", upstreamName] };
|
|
696
560
|
} catch {}
|
|
@@ -700,7 +564,13 @@ function buildProxyEntry(entry) {
|
|
|
700
564
|
class ClaudeDesktopAdapter {
|
|
701
565
|
id = "claude-desktop";
|
|
702
566
|
detectConfigPath() {
|
|
703
|
-
|
|
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");
|
|
704
574
|
}
|
|
705
575
|
supportsHttp() {
|
|
706
576
|
return true;
|
|
@@ -798,31 +668,31 @@ class ClaudeDesktopAdapter {
|
|
|
798
668
|
}
|
|
799
669
|
|
|
800
670
|
// src/adapters/codex.ts
|
|
801
|
-
import
|
|
802
|
-
import
|
|
671
|
+
import path4 from "node:path";
|
|
672
|
+
import fs2 from "node:fs";
|
|
803
673
|
import { parse, stringify } from "@iarna/toml";
|
|
804
|
-
import { z as
|
|
805
|
-
var stringMapSchema3 =
|
|
674
|
+
import { z as z3 } from "zod";
|
|
675
|
+
var stringMapSchema3 = z3.preprocess((value) => {
|
|
806
676
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
807
677
|
return value;
|
|
808
678
|
}
|
|
809
679
|
return Object.fromEntries(Object.entries(value));
|
|
810
|
-
},
|
|
811
|
-
var codexEntrySchema =
|
|
812
|
-
enabled:
|
|
813
|
-
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(),
|
|
814
684
|
headers: stringMapSchema3.optional(),
|
|
815
685
|
http_headers: stringMapSchema3.optional(),
|
|
816
|
-
command:
|
|
817
|
-
args:
|
|
686
|
+
command: z3.string().min(1).optional(),
|
|
687
|
+
args: z3.array(z3.string()).optional(),
|
|
818
688
|
env: stringMapSchema3.optional(),
|
|
819
|
-
cwd:
|
|
689
|
+
cwd: z3.string().min(1).optional()
|
|
820
690
|
}).passthrough();
|
|
821
691
|
|
|
822
692
|
class CodexAdapter {
|
|
823
693
|
id = "codex";
|
|
824
694
|
detectConfigPath() {
|
|
825
|
-
return
|
|
695
|
+
return path4.join(homeDir(), ".codex", "config.toml");
|
|
826
696
|
}
|
|
827
697
|
supportsHttp() {
|
|
828
698
|
return true;
|
|
@@ -830,10 +700,10 @@ class CodexAdapter {
|
|
|
830
700
|
scanForImports(_config, managedIndex) {
|
|
831
701
|
const configPath = this.detectConfigPath();
|
|
832
702
|
const result = emptyImportScan(this.id, configPath ?? undefined);
|
|
833
|
-
if (!configPath || !
|
|
703
|
+
if (!configPath || !fs2.existsSync(configPath)) {
|
|
834
704
|
return result;
|
|
835
705
|
}
|
|
836
|
-
const existing =
|
|
706
|
+
const existing = fs2.readFileSync(configPath, "utf8");
|
|
837
707
|
const doc = existing.trim().length > 0 ? parse(existing) : {};
|
|
838
708
|
const mcpServers = doc.mcp_servers ?? {};
|
|
839
709
|
for (const [name, rawEntry] of Object.entries(mcpServers)) {
|
|
@@ -888,7 +758,7 @@ class CodexAdapter {
|
|
|
888
758
|
return errorResult(this.id, undefined, "Unable to resolve Codex config.toml path.");
|
|
889
759
|
}
|
|
890
760
|
try {
|
|
891
|
-
const existing =
|
|
761
|
+
const existing = fs2.existsSync(configPath) ? fs2.readFileSync(configPath, "utf8") : "";
|
|
892
762
|
const doc = existing.trim().length > 0 ? parse(existing) : {};
|
|
893
763
|
const mcpServers = doc.mcp_servers ?? {};
|
|
894
764
|
removeSourceEntries(mcpServers, options.sourceEntriesToRemove);
|
|
@@ -909,10 +779,10 @@ class CodexAdapter {
|
|
|
909
779
|
mcpServers[name] = entry;
|
|
910
780
|
}
|
|
911
781
|
doc.mcp_servers = mcpServers;
|
|
912
|
-
|
|
782
|
+
fs2.mkdirSync(path4.dirname(configPath), { recursive: true });
|
|
913
783
|
const tmpPath = `${configPath}.tmp-${process.pid}-${Date.now()}`;
|
|
914
|
-
|
|
915
|
-
|
|
784
|
+
fs2.writeFileSync(tmpPath, stringify(doc), { mode: 384 });
|
|
785
|
+
fs2.renameSync(tmpPath, configPath);
|
|
916
786
|
setManagedEntries(options.managedIndex, this.id, configPath, Object.fromEntries(Object.entries(serverEntries).map(([name, entry]) => [name, JSON.stringify(entry)])));
|
|
917
787
|
return okResult(this.id, configPath);
|
|
918
788
|
} catch (error) {
|
|
@@ -922,24 +792,30 @@ class CodexAdapter {
|
|
|
922
792
|
}
|
|
923
793
|
|
|
924
794
|
// src/adapters/cursor.ts
|
|
925
|
-
import
|
|
926
|
-
import { z as
|
|
927
|
-
var stringMapSchema4 =
|
|
928
|
-
var cursorEntrySchema =
|
|
929
|
-
type:
|
|
930
|
-
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(),
|
|
931
801
|
headers: stringMapSchema4.optional(),
|
|
932
|
-
command:
|
|
933
|
-
args:
|
|
802
|
+
command: z4.string().min(1).optional(),
|
|
803
|
+
args: z4.array(z4.string()).optional(),
|
|
934
804
|
env: stringMapSchema4.optional(),
|
|
935
|
-
cwd:
|
|
936
|
-
disabled:
|
|
805
|
+
cwd: z4.string().min(1).optional(),
|
|
806
|
+
disabled: z4.boolean().optional()
|
|
937
807
|
}).passthrough();
|
|
938
808
|
|
|
939
809
|
class CursorAdapter {
|
|
940
810
|
id = "cursor";
|
|
941
811
|
detectConfigPath() {
|
|
942
|
-
|
|
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");
|
|
943
819
|
}
|
|
944
820
|
supportsHttp() {
|
|
945
821
|
return true;
|
|
@@ -1045,24 +921,36 @@ class CursorAdapter {
|
|
|
1045
921
|
}
|
|
1046
922
|
|
|
1047
923
|
// src/adapters/cline.ts
|
|
1048
|
-
import
|
|
1049
|
-
import
|
|
1050
|
-
import { z as
|
|
1051
|
-
var stringMapSchema5 =
|
|
1052
|
-
var clineEntrySchema =
|
|
1053
|
-
transportType:
|
|
1054
|
-
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(),
|
|
1055
931
|
headers: stringMapSchema5.optional(),
|
|
1056
|
-
command:
|
|
1057
|
-
args:
|
|
932
|
+
command: z5.string().min(1).optional(),
|
|
933
|
+
args: z5.array(z5.string()).optional(),
|
|
1058
934
|
env: stringMapSchema5.optional(),
|
|
1059
|
-
cwd:
|
|
1060
|
-
disabled:
|
|
935
|
+
cwd: z5.string().min(1).optional(),
|
|
936
|
+
disabled: z5.boolean().optional()
|
|
1061
937
|
}).passthrough();
|
|
1062
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
|
+
}
|
|
1063
951
|
return [
|
|
1064
|
-
|
|
1065
|
-
|
|
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")
|
|
1066
954
|
];
|
|
1067
955
|
}
|
|
1068
956
|
|
|
@@ -1071,7 +959,7 @@ class ClineAdapter {
|
|
|
1071
959
|
detectConfigPath() {
|
|
1072
960
|
const candidates = getCandidatePaths();
|
|
1073
961
|
for (const candidate of candidates) {
|
|
1074
|
-
if (
|
|
962
|
+
if (fs3.existsSync(candidate)) {
|
|
1075
963
|
return candidate;
|
|
1076
964
|
}
|
|
1077
965
|
}
|
|
@@ -1083,7 +971,7 @@ class ClineAdapter {
|
|
|
1083
971
|
scanForImports(_config, managedIndex) {
|
|
1084
972
|
const configPath = this.detectConfigPath();
|
|
1085
973
|
const result = emptyImportScan(this.id, configPath ?? undefined);
|
|
1086
|
-
if (!configPath || !
|
|
974
|
+
if (!configPath || !fs3.existsSync(configPath)) {
|
|
1087
975
|
return result;
|
|
1088
976
|
}
|
|
1089
977
|
const raw = readJsonFile(configPath, {});
|
|
@@ -1178,24 +1066,24 @@ class ClineAdapter {
|
|
|
1178
1066
|
}
|
|
1179
1067
|
|
|
1180
1068
|
// src/adapters/kiro.ts
|
|
1181
|
-
import
|
|
1182
|
-
import
|
|
1183
|
-
import { z as
|
|
1184
|
-
var stringMapSchema6 =
|
|
1185
|
-
var kiroEntrySchema =
|
|
1186
|
-
url:
|
|
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(),
|
|
1187
1075
|
headers: stringMapSchema6.optional(),
|
|
1188
|
-
command:
|
|
1189
|
-
args:
|
|
1076
|
+
command: z6.string().min(1).optional(),
|
|
1077
|
+
args: z6.array(z6.string()).optional(),
|
|
1190
1078
|
env: stringMapSchema6.optional(),
|
|
1191
|
-
cwd:
|
|
1192
|
-
disabled:
|
|
1079
|
+
cwd: z6.string().min(1).optional(),
|
|
1080
|
+
disabled: z6.boolean().optional()
|
|
1193
1081
|
}).passthrough();
|
|
1194
1082
|
|
|
1195
1083
|
class KiroAdapter {
|
|
1196
1084
|
id = "kiro";
|
|
1197
1085
|
detectConfigPath() {
|
|
1198
|
-
return
|
|
1086
|
+
return path7.join(homeDir(), ".kiro", "settings", "mcp.json");
|
|
1199
1087
|
}
|
|
1200
1088
|
supportsHttp() {
|
|
1201
1089
|
return true;
|
|
@@ -1295,46 +1183,46 @@ class KiroAdapter {
|
|
|
1295
1183
|
}
|
|
1296
1184
|
}
|
|
1297
1185
|
syncSkills(skills) {
|
|
1298
|
-
const skillsDir =
|
|
1299
|
-
if (!
|
|
1300
|
-
|
|
1186
|
+
const skillsDir = path7.join(homeDir(), ".kiro", "skills");
|
|
1187
|
+
if (!fs4.existsSync(skillsDir)) {
|
|
1188
|
+
fs4.mkdirSync(skillsDir, { recursive: true });
|
|
1301
1189
|
}
|
|
1302
|
-
const existingFiles =
|
|
1190
|
+
const existingFiles = fs4.readdirSync(skillsDir);
|
|
1303
1191
|
const managedIds = new Set(skills.map((s) => s.id));
|
|
1304
1192
|
for (const file of existingFiles) {
|
|
1305
1193
|
if (file.endsWith(".md")) {
|
|
1306
|
-
const id =
|
|
1194
|
+
const id = path7.basename(file, ".md");
|
|
1307
1195
|
if (!managedIds.has(id)) {
|
|
1308
|
-
|
|
1196
|
+
fs4.unlinkSync(path7.join(skillsDir, file));
|
|
1309
1197
|
}
|
|
1310
1198
|
}
|
|
1311
1199
|
}
|
|
1312
1200
|
for (const skill of skills) {
|
|
1313
|
-
const targetPath =
|
|
1314
|
-
|
|
1201
|
+
const targetPath = path7.join(skillsDir, `${skill.id}.md`);
|
|
1202
|
+
fs4.writeFileSync(targetPath, skill.content, "utf-8");
|
|
1315
1203
|
}
|
|
1316
1204
|
}
|
|
1317
1205
|
}
|
|
1318
1206
|
|
|
1319
1207
|
// src/adapters/opencode.ts
|
|
1320
|
-
import
|
|
1321
|
-
import { z as
|
|
1322
|
-
var stringMapSchema7 =
|
|
1323
|
-
var openCodeEntrySchema =
|
|
1324
|
-
type:
|
|
1325
|
-
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(),
|
|
1326
1214
|
headers: stringMapSchema7.optional(),
|
|
1327
|
-
command:
|
|
1328
|
-
args:
|
|
1215
|
+
command: z7.string().min(1).optional(),
|
|
1216
|
+
args: z7.array(z7.string()).optional(),
|
|
1329
1217
|
env: stringMapSchema7.optional(),
|
|
1330
|
-
cwd:
|
|
1331
|
-
enabled:
|
|
1218
|
+
cwd: z7.string().min(1).optional(),
|
|
1219
|
+
enabled: z7.boolean().optional()
|
|
1332
1220
|
}).passthrough();
|
|
1333
1221
|
|
|
1334
1222
|
class OpenCodeAdapter {
|
|
1335
1223
|
id = "opencode";
|
|
1336
1224
|
detectConfigPath() {
|
|
1337
|
-
return
|
|
1225
|
+
return path8.join(homeDir(), ".config", "opencode", "opencode.json");
|
|
1338
1226
|
}
|
|
1339
1227
|
supportsHttp() {
|
|
1340
1228
|
return true;
|
|
@@ -1437,24 +1325,30 @@ class OpenCodeAdapter {
|
|
|
1437
1325
|
}
|
|
1438
1326
|
|
|
1439
1327
|
// src/adapters/vscode.ts
|
|
1440
|
-
import
|
|
1441
|
-
import { z as
|
|
1442
|
-
var stringMapSchema8 =
|
|
1443
|
-
var vscodeEntrySchema =
|
|
1444
|
-
type:
|
|
1445
|
-
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(),
|
|
1446
1334
|
headers: stringMapSchema8.optional(),
|
|
1447
|
-
command:
|
|
1448
|
-
args:
|
|
1335
|
+
command: z8.string().min(1).optional(),
|
|
1336
|
+
args: z8.array(z8.string()).optional(),
|
|
1449
1337
|
env: stringMapSchema8.optional(),
|
|
1450
|
-
cwd:
|
|
1451
|
-
disabled:
|
|
1338
|
+
cwd: z8.string().min(1).optional(),
|
|
1339
|
+
disabled: z8.boolean().optional()
|
|
1452
1340
|
}).passthrough();
|
|
1453
1341
|
|
|
1454
1342
|
class VsCodeAdapter {
|
|
1455
1343
|
id = "vscode";
|
|
1456
1344
|
detectConfigPath() {
|
|
1457
|
-
|
|
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");
|
|
1458
1352
|
}
|
|
1459
1353
|
supportsHttp() {
|
|
1460
1354
|
return true;
|
|
@@ -1560,24 +1454,24 @@ class VsCodeAdapter {
|
|
|
1560
1454
|
}
|
|
1561
1455
|
|
|
1562
1456
|
// src/adapters/qwen.ts
|
|
1563
|
-
import
|
|
1564
|
-
import
|
|
1565
|
-
import { z as
|
|
1566
|
-
var stringMapSchema9 =
|
|
1567
|
-
var qwenEntrySchema =
|
|
1568
|
-
httpUrl:
|
|
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(),
|
|
1569
1463
|
headers: stringMapSchema9.optional(),
|
|
1570
|
-
command:
|
|
1571
|
-
args:
|
|
1464
|
+
command: z9.string().min(1).optional(),
|
|
1465
|
+
args: z9.array(z9.string()).optional(),
|
|
1572
1466
|
env: stringMapSchema9.optional(),
|
|
1573
|
-
cwd:
|
|
1574
|
-
disabled:
|
|
1467
|
+
cwd: z9.string().min(1).optional(),
|
|
1468
|
+
disabled: z9.boolean().optional()
|
|
1575
1469
|
}).passthrough();
|
|
1576
1470
|
|
|
1577
1471
|
class QwenAdapter {
|
|
1578
1472
|
id = "qwen";
|
|
1579
1473
|
detectConfigPath() {
|
|
1580
|
-
return
|
|
1474
|
+
return path10.join(homeDir(), ".qwen", "settings.json");
|
|
1581
1475
|
}
|
|
1582
1476
|
supportsHttp() {
|
|
1583
1477
|
return true;
|
|
@@ -1674,23 +1568,23 @@ class QwenAdapter {
|
|
|
1674
1568
|
}
|
|
1675
1569
|
}
|
|
1676
1570
|
syncSkills(skills) {
|
|
1677
|
-
const skillsDir =
|
|
1678
|
-
if (!
|
|
1679
|
-
|
|
1571
|
+
const skillsDir = path10.join(homeDir(), ".qwen", "skills");
|
|
1572
|
+
if (!fs5.existsSync(skillsDir)) {
|
|
1573
|
+
fs5.mkdirSync(skillsDir, { recursive: true });
|
|
1680
1574
|
}
|
|
1681
|
-
const existingFiles =
|
|
1575
|
+
const existingFiles = fs5.readdirSync(skillsDir);
|
|
1682
1576
|
const managedIds = new Set(skills.map((s) => s.id));
|
|
1683
1577
|
for (const file of existingFiles) {
|
|
1684
1578
|
if (file.endsWith(".md")) {
|
|
1685
|
-
const id =
|
|
1579
|
+
const id = path10.basename(file, ".md");
|
|
1686
1580
|
if (!managedIds.has(id)) {
|
|
1687
|
-
|
|
1581
|
+
fs5.unlinkSync(path10.join(skillsDir, file));
|
|
1688
1582
|
}
|
|
1689
1583
|
}
|
|
1690
1584
|
}
|
|
1691
1585
|
for (const skill of skills) {
|
|
1692
|
-
const targetPath =
|
|
1693
|
-
|
|
1586
|
+
const targetPath = path10.join(skillsDir, `${skill.id}.md`);
|
|
1587
|
+
fs5.writeFileSync(targetPath, skill.content, "utf-8");
|
|
1694
1588
|
}
|
|
1695
1589
|
}
|
|
1696
1590
|
}
|
|
@@ -1967,9 +1861,9 @@ function maybePrefixBearer(target, value, raw = false) {
|
|
|
1967
1861
|
var APP_VERSION = "0.1.3";
|
|
1968
1862
|
|
|
1969
1863
|
// src/core/update.ts
|
|
1970
|
-
import
|
|
1971
|
-
import
|
|
1972
|
-
import { execFileSync as
|
|
1864
|
+
import fs6 from "node:fs";
|
|
1865
|
+
import path11 from "node:path";
|
|
1866
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
1973
1867
|
function compareVersions(a, b) {
|
|
1974
1868
|
const partsA = a.split(".").map(Number);
|
|
1975
1869
|
const partsB = b.split(".").map(Number);
|
|
@@ -1985,7 +1879,7 @@ function compareVersions(a, b) {
|
|
|
1985
1879
|
}
|
|
1986
1880
|
async function checkForUpdates() {
|
|
1987
1881
|
try {
|
|
1988
|
-
const output =
|
|
1882
|
+
const output = execFileSync2("npm", ["view", "@kwonye/mcpx", "version"], {
|
|
1989
1883
|
encoding: "utf8",
|
|
1990
1884
|
stdio: ["pipe", "pipe", "pipe"]
|
|
1991
1885
|
}).trim();
|
|
@@ -2007,17 +1901,17 @@ async function checkForUpdates() {
|
|
|
2007
1901
|
}
|
|
2008
1902
|
function getStagedUpdate() {
|
|
2009
1903
|
const stagedPath = getStagedVersionPath();
|
|
2010
|
-
if (!
|
|
1904
|
+
if (!fs6.existsSync(stagedPath)) {
|
|
2011
1905
|
return null;
|
|
2012
1906
|
}
|
|
2013
1907
|
try {
|
|
2014
|
-
const raw =
|
|
1908
|
+
const raw = fs6.readFileSync(stagedPath, "utf8");
|
|
2015
1909
|
const info = JSON.parse(raw);
|
|
2016
1910
|
if (!info.version || !info.cliPath) {
|
|
2017
1911
|
return null;
|
|
2018
1912
|
}
|
|
2019
|
-
const absoluteCliPath =
|
|
2020
|
-
if (!
|
|
1913
|
+
const absoluteCliPath = path11.isAbsolute(info.cliPath) ? info.cliPath : path11.join(getUpdatesDir(), info.cliPath);
|
|
1914
|
+
if (!fs6.existsSync(absoluteCliPath)) {
|
|
2021
1915
|
return null;
|
|
2022
1916
|
}
|
|
2023
1917
|
return {
|
|
@@ -2033,15 +1927,15 @@ function stageUpdate(version, cliPath) {
|
|
|
2033
1927
|
ensureDir(updatesDir);
|
|
2034
1928
|
const stagedInfo = {
|
|
2035
1929
|
version,
|
|
2036
|
-
cliPath:
|
|
1930
|
+
cliPath: path11.isAbsolute(cliPath) ? path11.relative(updatesDir, cliPath) : cliPath,
|
|
2037
1931
|
stagedAt: new Date().toISOString()
|
|
2038
1932
|
};
|
|
2039
|
-
|
|
1933
|
+
fs6.writeFileSync(getStagedVersionPath(), JSON.stringify(stagedInfo, null, 2), "utf8");
|
|
2040
1934
|
}
|
|
2041
1935
|
function clearStagedUpdate() {
|
|
2042
1936
|
const stagedPath = getStagedVersionPath();
|
|
2043
|
-
if (
|
|
2044
|
-
|
|
1937
|
+
if (fs6.existsSync(stagedPath)) {
|
|
1938
|
+
fs6.unlinkSync(stagedPath);
|
|
2045
1939
|
}
|
|
2046
1940
|
}
|
|
2047
1941
|
function getStagedCliPath() {
|
|
@@ -2050,31 +1944,31 @@ function getStagedCliPath() {
|
|
|
2050
1944
|
}
|
|
2051
1945
|
function removeOldVersions(keepCount = 2) {
|
|
2052
1946
|
const updatesDir = getUpdatesDir();
|
|
2053
|
-
if (!
|
|
1947
|
+
if (!fs6.existsSync(updatesDir)) {
|
|
2054
1948
|
return;
|
|
2055
1949
|
}
|
|
2056
|
-
const entries =
|
|
1950
|
+
const entries = fs6.readdirSync(updatesDir, { withFileTypes: true });
|
|
2057
1951
|
const versionDirs = entries.filter((entry) => entry.isDirectory() && entry.name.startsWith("v")).map((entry) => entry.name).sort((a, b) => compareVersions(b.slice(1), a.slice(1)));
|
|
2058
1952
|
if (versionDirs.length <= keepCount) {
|
|
2059
1953
|
return;
|
|
2060
1954
|
}
|
|
2061
1955
|
for (const oldVersion of versionDirs.slice(keepCount)) {
|
|
2062
|
-
const oldPath =
|
|
1956
|
+
const oldPath = path11.join(updatesDir, oldVersion);
|
|
2063
1957
|
try {
|
|
2064
|
-
|
|
1958
|
+
fs6.rmSync(oldPath, { recursive: true, force: true });
|
|
2065
1959
|
} catch {}
|
|
2066
1960
|
}
|
|
2067
1961
|
}
|
|
2068
1962
|
|
|
2069
1963
|
// src/core/update-manager.ts
|
|
2070
|
-
import
|
|
2071
|
-
import
|
|
1964
|
+
import fs7 from "node:fs";
|
|
1965
|
+
import path12 from "node:path";
|
|
2072
1966
|
import { execSync, spawn } from "node:child_process";
|
|
2073
1967
|
function acquireLock() {
|
|
2074
1968
|
const lockPath = getUpdateLockPath();
|
|
2075
1969
|
try {
|
|
2076
|
-
ensureDir(
|
|
2077
|
-
|
|
1970
|
+
ensureDir(path12.dirname(lockPath));
|
|
1971
|
+
fs7.writeFileSync(lockPath, `${process.pid}
|
|
2078
1972
|
`, { flag: "wx" });
|
|
2079
1973
|
return true;
|
|
2080
1974
|
} catch {
|
|
@@ -2084,25 +1978,25 @@ function acquireLock() {
|
|
|
2084
1978
|
function releaseLock() {
|
|
2085
1979
|
const lockPath = getUpdateLockPath();
|
|
2086
1980
|
try {
|
|
2087
|
-
|
|
1981
|
+
fs7.unlinkSync(lockPath);
|
|
2088
1982
|
} catch {}
|
|
2089
1983
|
}
|
|
2090
1984
|
function isUpdateInProgress() {
|
|
2091
1985
|
const lockPath = getUpdateLockPath();
|
|
2092
|
-
if (!
|
|
1986
|
+
if (!fs7.existsSync(lockPath)) {
|
|
2093
1987
|
return false;
|
|
2094
1988
|
}
|
|
2095
1989
|
try {
|
|
2096
|
-
const raw =
|
|
1990
|
+
const raw = fs7.readFileSync(lockPath, "utf8").trim();
|
|
2097
1991
|
const pid = Number(raw);
|
|
2098
1992
|
if (!Number.isFinite(pid) || pid <= 0) {
|
|
2099
|
-
|
|
1993
|
+
fs7.unlinkSync(lockPath);
|
|
2100
1994
|
return false;
|
|
2101
1995
|
}
|
|
2102
1996
|
process.kill(pid, 0);
|
|
2103
1997
|
return true;
|
|
2104
1998
|
} catch {
|
|
2105
|
-
|
|
1999
|
+
fs7.unlinkSync(lockPath);
|
|
2106
2000
|
return false;
|
|
2107
2001
|
}
|
|
2108
2002
|
}
|
|
@@ -2113,8 +2007,8 @@ async function downloadAndStageUpdate() {
|
|
|
2113
2007
|
}
|
|
2114
2008
|
const targetVersion = updateStatus.latestVersion;
|
|
2115
2009
|
const updatesDir = getUpdatesDir();
|
|
2116
|
-
const versionDir =
|
|
2117
|
-
if (
|
|
2010
|
+
const versionDir = path12.join(updatesDir, `v${targetVersion}`);
|
|
2011
|
+
if (fs7.existsSync(versionDir)) {
|
|
2118
2012
|
const existingStaged = getStagedUpdate();
|
|
2119
2013
|
if (existingStaged?.version === targetVersion) {
|
|
2120
2014
|
return { success: false, error: `Version ${targetVersion} already staged` };
|
|
@@ -2126,8 +2020,8 @@ async function downloadAndStageUpdate() {
|
|
|
2126
2020
|
cwd: versionDir,
|
|
2127
2021
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2128
2022
|
});
|
|
2129
|
-
const tarballPath =
|
|
2130
|
-
if (!
|
|
2023
|
+
const tarballPath = path12.join(versionDir, `kwonye-mcpx-${targetVersion}.tgz`);
|
|
2024
|
+
if (!fs7.existsSync(tarballPath)) {
|
|
2131
2025
|
throw new Error("Downloaded tarball not found");
|
|
2132
2026
|
}
|
|
2133
2027
|
execSync(`tar -xzf ${tarballPath} --strip-components=1`, {
|
|
@@ -2138,8 +2032,8 @@ async function downloadAndStageUpdate() {
|
|
|
2138
2032
|
cwd: versionDir,
|
|
2139
2033
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2140
2034
|
});
|
|
2141
|
-
const cliPath =
|
|
2142
|
-
if (!
|
|
2035
|
+
const cliPath = path12.join(versionDir, "dist", "cli.js");
|
|
2036
|
+
if (!fs7.existsSync(cliPath)) {
|
|
2143
2037
|
throw new Error("CLI entry point not found in downloaded package");
|
|
2144
2038
|
}
|
|
2145
2039
|
stageUpdate(targetVersion, cliPath);
|
|
@@ -2147,7 +2041,7 @@ async function downloadAndStageUpdate() {
|
|
|
2147
2041
|
return { success: true, version: targetVersion };
|
|
2148
2042
|
} catch (error) {
|
|
2149
2043
|
try {
|
|
2150
|
-
|
|
2044
|
+
fs7.rmSync(versionDir, { recursive: true, force: true });
|
|
2151
2045
|
} catch {}
|
|
2152
2046
|
return {
|
|
2153
2047
|
success: false,
|
|
@@ -2225,14 +2119,14 @@ function performRollback() {
|
|
|
2225
2119
|
}
|
|
2226
2120
|
|
|
2227
2121
|
// src/core/daemon.ts
|
|
2228
|
-
import
|
|
2122
|
+
import fs8 from "node:fs";
|
|
2229
2123
|
import net from "node:net";
|
|
2230
2124
|
import { spawn as spawn2 } from "node:child_process";
|
|
2231
2125
|
|
|
2232
2126
|
// src/gateway/server.ts
|
|
2233
2127
|
import http from "node:http";
|
|
2234
2128
|
import { URL } from "node:url";
|
|
2235
|
-
import
|
|
2129
|
+
import crypto from "node:crypto";
|
|
2236
2130
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2237
2131
|
import {
|
|
2238
2132
|
StdioClientTransport,
|
|
@@ -2411,6 +2305,61 @@ async function callUpstream(upstream, method, params, id, secrets, runtime, pass
|
|
|
2411
2305
|
}
|
|
2412
2306
|
return callHttpUpstream(upstream, method, params, id, secrets, passthroughAuthorizationHeader);
|
|
2413
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
|
+
}
|
|
2414
2363
|
function getConfiguredTimeoutMs() {
|
|
2415
2364
|
const configuredTimeout = Number(process.env.MCPX_UPSTREAM_TIMEOUT_MS ?? DEFAULT_UPSTREAM_TIMEOUT_MS);
|
|
2416
2365
|
return Number.isFinite(configuredTimeout) && configuredTimeout > 0 ? configuredTimeout : DEFAULT_UPSTREAM_TIMEOUT_MS;
|
|
@@ -2719,6 +2668,13 @@ async function handleListTools(config, secrets, runtime, upstreamFilter, clientA
|
|
|
2719
2668
|
for (const upstream of upstreams) {
|
|
2720
2669
|
try {
|
|
2721
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);
|
|
2722
2678
|
for (const tool of result.tools ?? []) {
|
|
2723
2679
|
const name = typeof tool.name === "string" ? tool.name : "tool";
|
|
2724
2680
|
tools.push({
|
|
@@ -2742,6 +2698,13 @@ async function handleListResources(config, secrets, runtime, upstreamFilter, cli
|
|
|
2742
2698
|
for (const upstream of upstreams) {
|
|
2743
2699
|
try {
|
|
2744
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);
|
|
2745
2708
|
for (const resource of result.resources ?? []) {
|
|
2746
2709
|
const originalUri = typeof resource.uri === "string" ? resource.uri : "";
|
|
2747
2710
|
const originalName = typeof resource.name === "string" ? resource.name : originalUri;
|
|
@@ -2767,6 +2730,13 @@ async function handleListPrompts(config, secrets, runtime, upstreamFilter, clien
|
|
|
2767
2730
|
for (const upstream of upstreams) {
|
|
2768
2731
|
try {
|
|
2769
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);
|
|
2770
2740
|
for (const prompt of result.prompts ?? []) {
|
|
2771
2741
|
const name = typeof prompt.name === "string" ? prompt.name : "prompt";
|
|
2772
2742
|
prompts.push({
|
|
@@ -2908,11 +2878,15 @@ async function handleRequestObject(request, secrets, runtime, upstreamFilter, cl
|
|
|
2908
2878
|
if (request.method === "ping") {
|
|
2909
2879
|
return makeResult(id, { ok: true });
|
|
2910
2880
|
}
|
|
2911
|
-
const config =
|
|
2881
|
+
const config = loadMergedConfig();
|
|
2912
2882
|
if (upstreamFilter && listUpstreams(config, upstreamFilter).length === 0) {
|
|
2913
2883
|
return makeError(id, -32602, `Unknown upstream: ${upstreamFilter}`);
|
|
2914
2884
|
}
|
|
2915
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
|
+
}
|
|
2916
2890
|
if (request.method === "tools/list") {
|
|
2917
2891
|
const result = await handleListTools(config, secrets, runtime, upstreamFilter, clientAuthorizationHeader);
|
|
2918
2892
|
return makeResult(id, result);
|
|
@@ -2942,7 +2916,7 @@ async function maybeHandleWellKnownOAuthRequest(request, response, requestUrl, s
|
|
|
2942
2916
|
response.end(JSON.stringify({ error: "method_not_allowed" }));
|
|
2943
2917
|
return true;
|
|
2944
2918
|
}
|
|
2945
|
-
const config =
|
|
2919
|
+
const config = loadMergedConfig();
|
|
2946
2920
|
const requestedUpstream = getRequestedUpstream(requestUrl);
|
|
2947
2921
|
const upstream = getScopedHttpUpstream(config, requestedUpstream);
|
|
2948
2922
|
if (!upstream) {
|
|
@@ -3115,7 +3089,7 @@ function createGatewayServer(options) {
|
|
|
3115
3089
|
response.setHeader("content-type", "application/json");
|
|
3116
3090
|
}
|
|
3117
3091
|
if (hasInitialize) {
|
|
3118
|
-
const sessionId =
|
|
3092
|
+
const sessionId = crypto.randomUUID();
|
|
3119
3093
|
response.setHeader("mcp-session-id", sessionId);
|
|
3120
3094
|
response.setHeader("MCP-Session-Id", sessionId);
|
|
3121
3095
|
}
|
|
@@ -3171,10 +3145,10 @@ function createGatewayServer(options) {
|
|
|
3171
3145
|
|
|
3172
3146
|
// src/core/daemon.ts
|
|
3173
3147
|
function readPidFromFile(pidPath = getPidPath()) {
|
|
3174
|
-
if (!
|
|
3148
|
+
if (!fs8.existsSync(pidPath)) {
|
|
3175
3149
|
return null;
|
|
3176
3150
|
}
|
|
3177
|
-
const raw =
|
|
3151
|
+
const raw = fs8.readFileSync(pidPath, "utf8").trim();
|
|
3178
3152
|
if (!raw) {
|
|
3179
3153
|
return null;
|
|
3180
3154
|
}
|
|
@@ -3253,7 +3227,7 @@ async function startDaemon(config, cliPath, secrets) {
|
|
|
3253
3227
|
};
|
|
3254
3228
|
}
|
|
3255
3229
|
try {
|
|
3256
|
-
|
|
3230
|
+
fs8.unlinkSync(getPidPath());
|
|
3257
3231
|
} catch {}
|
|
3258
3232
|
}
|
|
3259
3233
|
const port = await resolveGatewayPort(config);
|
|
@@ -3262,7 +3236,7 @@ async function startDaemon(config, cliPath, secrets) {
|
|
|
3262
3236
|
const logPath = getLogPath();
|
|
3263
3237
|
ensureParentDir(pidPath);
|
|
3264
3238
|
ensureParentDir(logPath);
|
|
3265
|
-
const logFd =
|
|
3239
|
+
const logFd = fs8.openSync(logPath, "a", 384);
|
|
3266
3240
|
const child = spawn2(process.execPath, [cliPath, "daemon", "run", "--port", String(port)], {
|
|
3267
3241
|
detached: true,
|
|
3268
3242
|
stdio: ["ignore", logFd, logFd],
|
|
@@ -3272,7 +3246,7 @@ async function startDaemon(config, cliPath, secrets) {
|
|
|
3272
3246
|
}
|
|
3273
3247
|
});
|
|
3274
3248
|
child.unref();
|
|
3275
|
-
|
|
3249
|
+
fs8.writeFileSync(pidPath, `${child.pid}
|
|
3276
3250
|
`, { mode: 384 });
|
|
3277
3251
|
startBackgroundUpdateCheck();
|
|
3278
3252
|
return {
|
|
@@ -3295,7 +3269,7 @@ function stopDaemon() {
|
|
|
3295
3269
|
process.kill(pid, "SIGTERM");
|
|
3296
3270
|
} catch {}
|
|
3297
3271
|
try {
|
|
3298
|
-
|
|
3272
|
+
fs8.unlinkSync(pidPath);
|
|
3299
3273
|
} catch {}
|
|
3300
3274
|
return {
|
|
3301
3275
|
stopped: true,
|
|
@@ -3308,10 +3282,10 @@ async function restartDaemon(config, cliPath, secrets) {
|
|
|
3308
3282
|
}
|
|
3309
3283
|
function readDaemonLogs(maxLines = 200) {
|
|
3310
3284
|
const logPath = getLogPath();
|
|
3311
|
-
if (!
|
|
3285
|
+
if (!fs8.existsSync(logPath)) {
|
|
3312
3286
|
return "";
|
|
3313
3287
|
}
|
|
3314
|
-
const lines =
|
|
3288
|
+
const lines = fs8.readFileSync(logPath, "utf8").split(`
|
|
3315
3289
|
`);
|
|
3316
3290
|
return lines.slice(Math.max(lines.length - maxLines, 0)).join(`
|
|
3317
3291
|
`);
|
|
@@ -3380,26 +3354,74 @@ function buildAuthBindings(spec) {
|
|
|
3380
3354
|
secretName: secretRefName(binding.value) ?? undefined
|
|
3381
3355
|
}));
|
|
3382
3356
|
}
|
|
3383
|
-
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 = {};
|
|
3384
3392
|
const serverNames = Object.keys(config.servers).sort((left, right) => left.localeCompare(right));
|
|
3385
3393
|
const servers = serverNames.map((name) => {
|
|
3386
3394
|
const spec = config.servers[name];
|
|
3387
|
-
|
|
3395
|
+
const tokenCount = tokenCounts[name];
|
|
3396
|
+
const serverEntry = {
|
|
3388
3397
|
name,
|
|
3389
3398
|
enabled: isServerEnabled(spec),
|
|
3390
3399
|
transport: spec.transport,
|
|
3391
3400
|
target: describeServerTarget(spec),
|
|
3392
3401
|
authBindings: buildAuthBindings(spec),
|
|
3393
|
-
clients: buildClientMappings(config, managedIndex, name)
|
|
3402
|
+
clients: buildClientMappings(config, managedIndex, name),
|
|
3403
|
+
tokenCount
|
|
3394
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;
|
|
3395
3414
|
});
|
|
3396
3415
|
return {
|
|
3397
|
-
gatewayUrl
|
|
3416
|
+
gatewayUrl,
|
|
3398
3417
|
daemon,
|
|
3399
3418
|
upstreamCount: servers.length,
|
|
3400
3419
|
servers,
|
|
3401
|
-
clients: config.clients
|
|
3420
|
+
clients: config.clients,
|
|
3421
|
+
projects: config.projects,
|
|
3422
|
+
totalGlobalTokens,
|
|
3423
|
+
totalProjectTokens
|
|
3402
3424
|
};
|
|
3403
3425
|
}
|
|
3404
3426
|
|
|
3405
|
-
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 };
|