mcpman 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +168 -0
- package/dist/chunk-6GGMDJQE.js +181 -0
- package/dist/{chunk-NS6HV723.js → chunk-CC7ICP7U.js} +6 -62
- package/dist/chunk-DSCBWQ3W.js +96 -0
- package/dist/{client-detector-CY7WPF3K.js → client-detector-O2HN4MUB.js} +2 -1
- package/dist/index.cjs +2854 -767
- package/dist/index.js +2606 -665
- package/dist/{lockfile-RBA7HB24.js → lockfile-ITEBE7HU.js} +2 -1
- package/package.json +1 -1
- package/dist/chunk-YZNTMR6O.js +0 -88
package/README.md
CHANGED
|
@@ -360,6 +360,162 @@ mcpman why my-server --json # JSON output for scripting
|
|
|
360
360
|
|
|
361
361
|
Displays: source (npm/smithery/github/local), resolved URL, version, installed timestamp, which clients have it registered, which named profiles include it, and required env var names. Detects orphaned servers (in client config but not in lockfile) and suggests `mcpman sync --remove`.
|
|
362
362
|
|
|
363
|
+
### `env <set|get|list|del|clear>`
|
|
364
|
+
|
|
365
|
+
Manage per-server environment variables (non-sensitive defaults).
|
|
366
|
+
|
|
367
|
+
```sh
|
|
368
|
+
mcpman env set my-server API_URL=https://api.example.com
|
|
369
|
+
mcpman env get my-server API_URL
|
|
370
|
+
mcpman env list my-server
|
|
371
|
+
mcpman env del my-server API_URL
|
|
372
|
+
mcpman env clear my-server
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
Stored in `~/.mcpman/env/<server>.json`. For sensitive values, use `mcpman secrets` instead. At runtime, vault secrets take priority over env defaults.
|
|
376
|
+
|
|
377
|
+
### `bench <server>`
|
|
378
|
+
|
|
379
|
+
Benchmark MCP server latency with JSON-RPC initialize calls.
|
|
380
|
+
|
|
381
|
+
```sh
|
|
382
|
+
mcpman bench my-server # 5 runs (default)
|
|
383
|
+
mcpman bench my-server --runs 10 # custom run count
|
|
384
|
+
mcpman bench my-server --json # machine-readable output
|
|
385
|
+
mcpman bench my-server --timeout 5000 # exit 1 if p95 > 5s
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
Reports min, max, avg, p50, p95 response times in milliseconds.
|
|
389
|
+
|
|
390
|
+
### `diff <client-a> <client-b>`
|
|
391
|
+
|
|
392
|
+
Show visual diff of MCP server configs between two AI clients.
|
|
393
|
+
|
|
394
|
+
```sh
|
|
395
|
+
mcpman diff claude-desktop cursor # color-coded diff
|
|
396
|
+
mcpman diff vscode windsurf --json # JSON output
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
Displays added (green), removed (red), and changed (yellow) servers between two client configurations. Useful before running `mcpman sync`.
|
|
400
|
+
|
|
401
|
+
### `group <add|rm|list|delete|install|run>`
|
|
402
|
+
|
|
403
|
+
Organize servers into named groups for batch operations.
|
|
404
|
+
|
|
405
|
+
```sh
|
|
406
|
+
mcpman group add work server-a server-b # tag servers
|
|
407
|
+
mcpman group rm work server-b # untag server
|
|
408
|
+
mcpman group list # show all groups
|
|
409
|
+
mcpman group list work # show group members
|
|
410
|
+
mcpman group install work # install all in group
|
|
411
|
+
mcpman group run work # run all concurrently
|
|
412
|
+
mcpman group delete work # remove entire group
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
Groups are lightweight labels stored in `~/.mcpman/groups.json`. Unlike profiles (full snapshots), groups are just server name lists for convenience.
|
|
416
|
+
|
|
417
|
+
### `pin <server> [version]`
|
|
418
|
+
|
|
419
|
+
Pin a server to a specific version to prevent auto-updates.
|
|
420
|
+
|
|
421
|
+
```sh
|
|
422
|
+
mcpman pin my-server 1.2.3 # pin to exact version
|
|
423
|
+
mcpman pin my-server # pin to current version
|
|
424
|
+
mcpman pin --unpin my-server # remove pin
|
|
425
|
+
mcpman pin --list # show all pinned servers
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
Pinned servers are skipped by `mcpman update` and version check notifications. Pins stored in `~/.mcpman/pins.json`.
|
|
429
|
+
|
|
430
|
+
### `rollback [index]`
|
|
431
|
+
|
|
432
|
+
Restore a previous lockfile state from automatic snapshots.
|
|
433
|
+
|
|
434
|
+
```sh
|
|
435
|
+
mcpman rollback --list # show snapshot history
|
|
436
|
+
mcpman rollback 0 # restore most recent snapshot
|
|
437
|
+
mcpman rollback 2 # restore specific snapshot
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
Snapshots are created automatically before every lockfile write. Keeps the last 5 snapshots in `~/.mcpman/rollback/`. After rollback, run `mcpman sync` to apply to all clients.
|
|
441
|
+
|
|
442
|
+
### `validate [--client <name>]`
|
|
443
|
+
|
|
444
|
+
Validate lockfile schema and client config JSON for correctness.
|
|
445
|
+
|
|
446
|
+
```sh
|
|
447
|
+
mcpman validate # validate lockfile + all clients
|
|
448
|
+
mcpman validate --client cursor # validate specific client only
|
|
449
|
+
mcpman validate --json # machine-readable output
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
Checks: required fields (name, version, source, command, args), valid JSON structure, mcpServers entries have command+args. Exits 1 if any errors found. Distinct from `doctor` (which checks runtime health).
|
|
453
|
+
|
|
454
|
+
### `status [--server <name>]`
|
|
455
|
+
|
|
456
|
+
Show live process status of all installed MCP servers.
|
|
457
|
+
|
|
458
|
+
```sh
|
|
459
|
+
mcpman status # check all servers
|
|
460
|
+
mcpman status --server my-server # check specific server
|
|
461
|
+
mcpman status --json # JSON output
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
Probes each server with a JSON-RPC `initialize` call (3s timeout). Reports: alive/dead status, response time, and error details.
|
|
465
|
+
|
|
466
|
+
### `replay [index]`
|
|
467
|
+
|
|
468
|
+
Re-run previous CLI commands from history.
|
|
469
|
+
|
|
470
|
+
```sh
|
|
471
|
+
mcpman replay --list # show last 20 commands
|
|
472
|
+
mcpman replay 0 # re-run most recent command
|
|
473
|
+
mcpman replay 5 # re-run 5th command
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
Maintains a ring buffer of the last 50 commands at `~/.mcpman/history.json`. Each entry includes the command, arguments, and timestamp.
|
|
477
|
+
|
|
478
|
+
### `alias <add|remove|list>`
|
|
479
|
+
|
|
480
|
+
Create short aliases for frequently used commands.
|
|
481
|
+
|
|
482
|
+
```sh
|
|
483
|
+
mcpman alias add dev "group run dev-servers"
|
|
484
|
+
mcpman alias add fs "install @modelcontextprotocol/server-filesystem"
|
|
485
|
+
mcpman alias remove dev
|
|
486
|
+
mcpman alias list
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
Aliases stored in `~/.mcpman/aliases.json`. Unlike groups (server batches), aliases are command-line shorthands.
|
|
490
|
+
|
|
491
|
+
### `template <save|apply|list|delete>`
|
|
492
|
+
|
|
493
|
+
Save and share install templates for team onboarding.
|
|
494
|
+
|
|
495
|
+
```sh
|
|
496
|
+
mcpman template save myteam # snapshot current servers
|
|
497
|
+
mcpman template save myteam -d "Team setup"
|
|
498
|
+
mcpman template apply myteam # print install commands
|
|
499
|
+
mcpman template list # show all templates
|
|
500
|
+
mcpman template delete myteam # remove template
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
Templates stored in `~/.mcpman/templates/`. Unlike `export` (full migration bundle with vault+plugins), templates are lightweight server presets for sharing.
|
|
504
|
+
|
|
505
|
+
### `notify <add|remove|list|test>`
|
|
506
|
+
|
|
507
|
+
Configure webhook and shell hooks for server lifecycle events.
|
|
508
|
+
|
|
509
|
+
```sh
|
|
510
|
+
mcpman notify add --event install --webhook https://hooks.example.com/mcp
|
|
511
|
+
mcpman notify add --event health-fail --shell "echo alert | mail admin"
|
|
512
|
+
mcpman notify list # show all hooks
|
|
513
|
+
mcpman notify remove 0 # remove hook by index
|
|
514
|
+
mcpman notify test install # fire test event
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
Events: `install`, `remove`, `update`, `health-fail`. Webhooks use native `fetch()`, shell hooks use `execSync`. Hooks stored in `~/.mcpman/notify.json`.
|
|
518
|
+
|
|
363
519
|
---
|
|
364
520
|
|
|
365
521
|
## Comparison
|
|
@@ -389,6 +545,18 @@ Displays: source (npm/smithery/github/local), resolved URL, version, installed t
|
|
|
389
545
|
| Custom registries | `registry` CRUD | None | None |
|
|
390
546
|
| Shell completions | bash + zsh + fish | None | None |
|
|
391
547
|
| Provenance query | `why` (clients + profiles) | None | None |
|
|
548
|
+
| Env management | Per-server env var CRUD | None | None |
|
|
549
|
+
| Benchmarking | Latency p50/p95 stats | None | None |
|
|
550
|
+
| Config diff | Visual client diff | None | None |
|
|
551
|
+
| Server groups | Batch install/run tags | None | None |
|
|
552
|
+
| Version pinning | `pin`/`unpin` CLI | None | None |
|
|
553
|
+
| Rollback | Auto-snapshot + restore | None | None |
|
|
554
|
+
| Config validation | Schema + JSON checks | None | None |
|
|
555
|
+
| Live status | Process probe + response time | None | None |
|
|
556
|
+
| Command replay | History ring buffer | None | None |
|
|
557
|
+
| Command aliases | Shorthand definitions | None | None |
|
|
558
|
+
| Install templates | Sharable server presets | None | None |
|
|
559
|
+
| Event notifications | Webhook + shell hooks | None | None |
|
|
392
560
|
|
|
393
561
|
---
|
|
394
562
|
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
getRollbackDir
|
|
4
|
+
} from "./chunk-DSCBWQ3W.js";
|
|
5
|
+
|
|
6
|
+
// src/core/lockfile.ts
|
|
7
|
+
import fs2 from "fs";
|
|
8
|
+
import os from "os";
|
|
9
|
+
import path2 from "path";
|
|
10
|
+
|
|
11
|
+
// src/core/rollback-service.ts
|
|
12
|
+
import fs from "fs";
|
|
13
|
+
import path from "path";
|
|
14
|
+
var MAX_SNAPSHOTS = 5;
|
|
15
|
+
function ensureDir(dir) {
|
|
16
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
function listSnapshotFiles(dir) {
|
|
19
|
+
if (!fs.existsSync(dir)) return [];
|
|
20
|
+
return fs.readdirSync(dir).filter((f) => f.endsWith(".json")).sort();
|
|
21
|
+
}
|
|
22
|
+
function snapshotBeforeWrite(content, rollbackDir) {
|
|
23
|
+
const dir = rollbackDir ?? getRollbackDir();
|
|
24
|
+
ensureDir(dir);
|
|
25
|
+
const existing = listSnapshotFiles(dir);
|
|
26
|
+
if (existing.length > 0) {
|
|
27
|
+
const latest = existing[existing.length - 1];
|
|
28
|
+
try {
|
|
29
|
+
const prev = fs.readFileSync(path.join(dir, latest), "utf-8");
|
|
30
|
+
if (prev === content) return;
|
|
31
|
+
} catch {
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
35
|
+
const filename = `${ts}.json`;
|
|
36
|
+
fs.writeFileSync(path.join(dir, filename), content, "utf-8");
|
|
37
|
+
evictOldSnapshots(dir);
|
|
38
|
+
}
|
|
39
|
+
function evictOldSnapshots(rollbackDir) {
|
|
40
|
+
const dir = rollbackDir ?? getRollbackDir();
|
|
41
|
+
const files = listSnapshotFiles(dir);
|
|
42
|
+
const excess = files.length - MAX_SNAPSHOTS;
|
|
43
|
+
for (let i = 0; i < excess; i++) {
|
|
44
|
+
try {
|
|
45
|
+
fs.unlinkSync(path.join(dir, files[i]));
|
|
46
|
+
} catch {
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function listSnapshots(rollbackDir) {
|
|
51
|
+
const dir = rollbackDir ?? getRollbackDir();
|
|
52
|
+
const files = listSnapshotFiles(dir).reverse();
|
|
53
|
+
return files.map((filename, index) => {
|
|
54
|
+
const filepath = path.join(dir, filename);
|
|
55
|
+
let sizeBytes = 0;
|
|
56
|
+
let createdAt = "";
|
|
57
|
+
try {
|
|
58
|
+
const stat = fs.statSync(filepath);
|
|
59
|
+
sizeBytes = stat.size;
|
|
60
|
+
createdAt = stat.mtime.toISOString();
|
|
61
|
+
} catch {
|
|
62
|
+
}
|
|
63
|
+
return { index, filename, createdAt, sizeBytes };
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function readSnapshot(index, rollbackDir) {
|
|
67
|
+
const dir = rollbackDir ?? getRollbackDir();
|
|
68
|
+
const files = listSnapshotFiles(dir).reverse();
|
|
69
|
+
const filename = files[index];
|
|
70
|
+
if (!filename) return null;
|
|
71
|
+
try {
|
|
72
|
+
return fs.readFileSync(path.join(dir, filename), "utf-8");
|
|
73
|
+
} catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function restoreSnapshot(index, targetPath, rollbackDir) {
|
|
78
|
+
const content = readSnapshot(index, rollbackDir);
|
|
79
|
+
if (content === null) return null;
|
|
80
|
+
const dir = path.dirname(targetPath);
|
|
81
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
82
|
+
const tmp = `${targetPath}.tmp`;
|
|
83
|
+
fs.writeFileSync(tmp, content, "utf-8");
|
|
84
|
+
fs.renameSync(tmp, targetPath);
|
|
85
|
+
return content;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/core/lockfile.ts
|
|
89
|
+
var LOCKFILE_NAME = "mcpman.lock";
|
|
90
|
+
function findLockfile() {
|
|
91
|
+
let dir = process.cwd();
|
|
92
|
+
while (true) {
|
|
93
|
+
const candidate = path2.join(dir, LOCKFILE_NAME);
|
|
94
|
+
if (fs2.existsSync(candidate)) return candidate;
|
|
95
|
+
const parent = path2.dirname(dir);
|
|
96
|
+
if (parent === dir) break;
|
|
97
|
+
dir = parent;
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
function getGlobalLockfilePath() {
|
|
102
|
+
return path2.join(os.homedir(), ".mcpman", LOCKFILE_NAME);
|
|
103
|
+
}
|
|
104
|
+
function resolveLockfilePath() {
|
|
105
|
+
return findLockfile() ?? getGlobalLockfilePath();
|
|
106
|
+
}
|
|
107
|
+
function readLockfile(filePath) {
|
|
108
|
+
const target = filePath ?? resolveLockfilePath();
|
|
109
|
+
if (!fs2.existsSync(target)) {
|
|
110
|
+
return { lockfileVersion: 1, servers: {} };
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
const raw = fs2.readFileSync(target, "utf-8");
|
|
114
|
+
return JSON.parse(raw);
|
|
115
|
+
} catch {
|
|
116
|
+
return { lockfileVersion: 1, servers: {} };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function serialize(data) {
|
|
120
|
+
const sorted = {
|
|
121
|
+
lockfileVersion: data.lockfileVersion,
|
|
122
|
+
servers: Object.fromEntries(
|
|
123
|
+
Object.entries(data.servers).sort(([a], [b]) => a.localeCompare(b))
|
|
124
|
+
)
|
|
125
|
+
};
|
|
126
|
+
return `${JSON.stringify(sorted, null, 2)}
|
|
127
|
+
`;
|
|
128
|
+
}
|
|
129
|
+
function writeLockfile(data, filePath) {
|
|
130
|
+
const target = filePath ?? resolveLockfilePath();
|
|
131
|
+
const dir = path2.dirname(target);
|
|
132
|
+
if (!fs2.existsSync(dir)) {
|
|
133
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
134
|
+
}
|
|
135
|
+
const serialized = serialize(data);
|
|
136
|
+
if (fs2.existsSync(target)) {
|
|
137
|
+
try {
|
|
138
|
+
const current = fs2.readFileSync(target, "utf-8");
|
|
139
|
+
snapshotBeforeWrite(current);
|
|
140
|
+
} catch {
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const tmp = `${target}.tmp`;
|
|
144
|
+
fs2.writeFileSync(tmp, serialized, "utf-8");
|
|
145
|
+
fs2.renameSync(tmp, target);
|
|
146
|
+
}
|
|
147
|
+
function addEntry(name, entry, filePath) {
|
|
148
|
+
const data = readLockfile(filePath);
|
|
149
|
+
data.servers[name] = entry;
|
|
150
|
+
writeLockfile(data, filePath);
|
|
151
|
+
}
|
|
152
|
+
function removeEntry(name, filePath) {
|
|
153
|
+
const data = readLockfile(filePath);
|
|
154
|
+
if (name in data.servers) {
|
|
155
|
+
delete data.servers[name];
|
|
156
|
+
writeLockfile(data, filePath);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function getLockedVersion(name, filePath) {
|
|
160
|
+
const data = readLockfile(filePath);
|
|
161
|
+
return data.servers[name]?.version;
|
|
162
|
+
}
|
|
163
|
+
function createEmptyLockfile(filePath) {
|
|
164
|
+
writeLockfile({ lockfileVersion: 1, servers: {} }, filePath);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export {
|
|
168
|
+
listSnapshots,
|
|
169
|
+
readSnapshot,
|
|
170
|
+
restoreSnapshot,
|
|
171
|
+
LOCKFILE_NAME,
|
|
172
|
+
findLockfile,
|
|
173
|
+
getGlobalLockfilePath,
|
|
174
|
+
resolveLockfilePath,
|
|
175
|
+
readLockfile,
|
|
176
|
+
writeLockfile,
|
|
177
|
+
addEntry,
|
|
178
|
+
removeEntry,
|
|
179
|
+
getLockedVersion,
|
|
180
|
+
createEmptyLockfile
|
|
181
|
+
};
|
|
@@ -1,64 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import path from "path";
|
|
6
|
-
function getHomedir() {
|
|
7
|
-
return os.homedir();
|
|
8
|
-
}
|
|
9
|
-
function getMcpmanDir() {
|
|
10
|
-
return path.join(os.homedir(), ".mcpman");
|
|
11
|
-
}
|
|
12
|
-
function getConfigPath() {
|
|
13
|
-
return path.join(getMcpmanDir(), "config.json");
|
|
14
|
-
}
|
|
15
|
-
function getPluginDir() {
|
|
16
|
-
return path.join(getMcpmanDir(), "plugins");
|
|
17
|
-
}
|
|
18
|
-
function getProfilesDir() {
|
|
19
|
-
return path.join(getMcpmanDir(), "profiles");
|
|
20
|
-
}
|
|
21
|
-
function getAppDataDir() {
|
|
22
|
-
const home = getHomedir();
|
|
23
|
-
if (process.platform === "darwin") {
|
|
24
|
-
return path.join(home, "Library", "Application Support");
|
|
25
|
-
}
|
|
26
|
-
if (process.platform === "win32") {
|
|
27
|
-
return process.env.APPDATA ?? path.join(home, "AppData", "Roaming");
|
|
28
|
-
}
|
|
29
|
-
return process.env.XDG_CONFIG_HOME ?? path.join(home, ".config");
|
|
30
|
-
}
|
|
31
|
-
function resolveConfigPath(client) {
|
|
32
|
-
const appData = getAppDataDir();
|
|
33
|
-
const home = getHomedir();
|
|
34
|
-
switch (client) {
|
|
35
|
-
case "claude-desktop":
|
|
36
|
-
return path.join(appData, "Claude", "claude_desktop_config.json");
|
|
37
|
-
case "cursor":
|
|
38
|
-
return path.join(appData, "Cursor", "User", "globalStorage", "cursor.mcp", "mcp.json");
|
|
39
|
-
case "windsurf":
|
|
40
|
-
return path.join(
|
|
41
|
-
appData,
|
|
42
|
-
"Windsurf",
|
|
43
|
-
"User",
|
|
44
|
-
"globalStorage",
|
|
45
|
-
"windsurf.mcpConfigJson",
|
|
46
|
-
"mcp.json"
|
|
47
|
-
);
|
|
48
|
-
case "vscode":
|
|
49
|
-
if (process.platform === "darwin") {
|
|
50
|
-
return path.join(appData, "Code", "User", "settings.json");
|
|
51
|
-
}
|
|
52
|
-
if (process.platform === "win32") {
|
|
53
|
-
return path.join(appData, "Code", "User", "settings.json");
|
|
54
|
-
}
|
|
55
|
-
return path.join(home, ".config", "Code", "User", "settings.json");
|
|
56
|
-
}
|
|
57
|
-
}
|
|
2
|
+
import {
|
|
3
|
+
resolveConfigPath
|
|
4
|
+
} from "./chunk-DSCBWQ3W.js";
|
|
58
5
|
|
|
59
6
|
// src/clients/base-client-handler.ts
|
|
60
7
|
import fs from "fs";
|
|
61
|
-
import
|
|
8
|
+
import path from "path";
|
|
62
9
|
|
|
63
10
|
// src/clients/types.ts
|
|
64
11
|
var ConfigParseError = class extends Error {
|
|
@@ -80,7 +27,7 @@ var ConfigWriteError = class extends Error {
|
|
|
80
27
|
async function atomicWrite(filePath, content) {
|
|
81
28
|
const tmpPath = `${filePath}.tmp`;
|
|
82
29
|
try {
|
|
83
|
-
await fs.promises.mkdir(
|
|
30
|
+
await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
|
|
84
31
|
await fs.promises.writeFile(tmpPath, content, { encoding: "utf-8", mode: 384 });
|
|
85
32
|
await fs.promises.rename(tmpPath, filePath);
|
|
86
33
|
} catch (err) {
|
|
@@ -101,7 +48,7 @@ async function pathExists(p) {
|
|
|
101
48
|
}
|
|
102
49
|
var BaseClientHandler = class {
|
|
103
50
|
async isInstalled() {
|
|
104
|
-
const dir =
|
|
51
|
+
const dir = path.dirname(this.getConfigPath());
|
|
105
52
|
return pathExists(dir);
|
|
106
53
|
}
|
|
107
54
|
/** Read raw JSON from disk, return empty object if file missing */
|
|
@@ -228,9 +175,6 @@ async function getInstalledClients() {
|
|
|
228
175
|
}
|
|
229
176
|
|
|
230
177
|
export {
|
|
231
|
-
getConfigPath,
|
|
232
|
-
getPluginDir,
|
|
233
|
-
getProfilesDir,
|
|
234
178
|
getAllClientTypes,
|
|
235
179
|
getClient,
|
|
236
180
|
getInstalledClients
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/utils/paths.ts
|
|
4
|
+
import os from "os";
|
|
5
|
+
import path from "path";
|
|
6
|
+
function getHomedir() {
|
|
7
|
+
return os.homedir();
|
|
8
|
+
}
|
|
9
|
+
function getMcpmanDir() {
|
|
10
|
+
return path.join(os.homedir(), ".mcpman");
|
|
11
|
+
}
|
|
12
|
+
function getConfigPath() {
|
|
13
|
+
return path.join(getMcpmanDir(), "config.json");
|
|
14
|
+
}
|
|
15
|
+
function getPluginDir() {
|
|
16
|
+
return path.join(getMcpmanDir(), "plugins");
|
|
17
|
+
}
|
|
18
|
+
function getProfilesDir() {
|
|
19
|
+
return path.join(getMcpmanDir(), "profiles");
|
|
20
|
+
}
|
|
21
|
+
function getEnvDir() {
|
|
22
|
+
return path.join(getMcpmanDir(), "env");
|
|
23
|
+
}
|
|
24
|
+
function getGroupsFile() {
|
|
25
|
+
return path.join(getMcpmanDir(), "groups.json");
|
|
26
|
+
}
|
|
27
|
+
function getPinsFile() {
|
|
28
|
+
return path.join(getMcpmanDir(), "pins.json");
|
|
29
|
+
}
|
|
30
|
+
function getRollbackDir() {
|
|
31
|
+
return path.join(getMcpmanDir(), "rollback");
|
|
32
|
+
}
|
|
33
|
+
function getHistoryFile() {
|
|
34
|
+
return path.join(getMcpmanDir(), "history.json");
|
|
35
|
+
}
|
|
36
|
+
function getAliasesFile() {
|
|
37
|
+
return path.join(getMcpmanDir(), "aliases.json");
|
|
38
|
+
}
|
|
39
|
+
function getTemplatesDir() {
|
|
40
|
+
return path.join(getMcpmanDir(), "templates");
|
|
41
|
+
}
|
|
42
|
+
function getNotifyFile() {
|
|
43
|
+
return path.join(getMcpmanDir(), "notify.json");
|
|
44
|
+
}
|
|
45
|
+
function getAppDataDir() {
|
|
46
|
+
const home = getHomedir();
|
|
47
|
+
if (process.platform === "darwin") {
|
|
48
|
+
return path.join(home, "Library", "Application Support");
|
|
49
|
+
}
|
|
50
|
+
if (process.platform === "win32") {
|
|
51
|
+
return process.env.APPDATA ?? path.join(home, "AppData", "Roaming");
|
|
52
|
+
}
|
|
53
|
+
return process.env.XDG_CONFIG_HOME ?? path.join(home, ".config");
|
|
54
|
+
}
|
|
55
|
+
function resolveConfigPath(client) {
|
|
56
|
+
const appData = getAppDataDir();
|
|
57
|
+
const home = getHomedir();
|
|
58
|
+
switch (client) {
|
|
59
|
+
case "claude-desktop":
|
|
60
|
+
return path.join(appData, "Claude", "claude_desktop_config.json");
|
|
61
|
+
case "cursor":
|
|
62
|
+
return path.join(appData, "Cursor", "User", "globalStorage", "cursor.mcp", "mcp.json");
|
|
63
|
+
case "windsurf":
|
|
64
|
+
return path.join(
|
|
65
|
+
appData,
|
|
66
|
+
"Windsurf",
|
|
67
|
+
"User",
|
|
68
|
+
"globalStorage",
|
|
69
|
+
"windsurf.mcpConfigJson",
|
|
70
|
+
"mcp.json"
|
|
71
|
+
);
|
|
72
|
+
case "vscode":
|
|
73
|
+
if (process.platform === "darwin") {
|
|
74
|
+
return path.join(appData, "Code", "User", "settings.json");
|
|
75
|
+
}
|
|
76
|
+
if (process.platform === "win32") {
|
|
77
|
+
return path.join(appData, "Code", "User", "settings.json");
|
|
78
|
+
}
|
|
79
|
+
return path.join(home, ".config", "Code", "User", "settings.json");
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export {
|
|
84
|
+
getConfigPath,
|
|
85
|
+
getPluginDir,
|
|
86
|
+
getProfilesDir,
|
|
87
|
+
getEnvDir,
|
|
88
|
+
getGroupsFile,
|
|
89
|
+
getPinsFile,
|
|
90
|
+
getRollbackDir,
|
|
91
|
+
getHistoryFile,
|
|
92
|
+
getAliasesFile,
|
|
93
|
+
getTemplatesDir,
|
|
94
|
+
getNotifyFile,
|
|
95
|
+
resolveConfigPath
|
|
96
|
+
};
|