mcpman 0.6.0 → 0.7.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 +83 -0
- package/dist/chunk-YZNTMR6O.js +88 -0
- package/dist/index.cjs +1828 -419
- package/dist/index.js +1698 -410
- package/dist/lockfile-RBA7HB24.js +25 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -283,6 +283,83 @@ mcpman import backup.json --yes # skip confirmation
|
|
|
283
283
|
mcpman import backup.json --dry-run # preview without applying
|
|
284
284
|
```
|
|
285
285
|
|
|
286
|
+
### `create [name]`
|
|
287
|
+
|
|
288
|
+
Scaffold a new MCP server project with working boilerplate.
|
|
289
|
+
|
|
290
|
+
```sh
|
|
291
|
+
mcpman create my-server # interactive prompts
|
|
292
|
+
mcpman create my-server --yes # accept defaults
|
|
293
|
+
mcpman create my-server --runtime python # Python template
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Generates `package.json` (with `mcp` field), `src/index.ts`, and `tsconfig.json` for Node; or `pyproject.toml` and `main.py` for Python. Both templates implement the MCP protocol with a sample `hello` tool ready to run.
|
|
297
|
+
|
|
298
|
+
### `link [dir]`
|
|
299
|
+
|
|
300
|
+
Register a local MCP server directory with AI clients — like `npm link` but for MCP.
|
|
301
|
+
|
|
302
|
+
```sh
|
|
303
|
+
mcpman link . # link current directory
|
|
304
|
+
mcpman link ./path/to/server # link specific directory
|
|
305
|
+
mcpman link . --client cursor # link to specific client only
|
|
306
|
+
mcpman link . --name my-override # override detected server name
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Reads `package.json` or `pyproject.toml` to detect name, version, and entry point. Adds a lockfile entry with `source: "local"` and registers the absolute path in client configs. No file copying — edits are picked up immediately.
|
|
310
|
+
|
|
311
|
+
### `watch <server>`
|
|
312
|
+
|
|
313
|
+
Watch a local MCP server's source files and auto-restart on changes — like nodemon, built into mcpman.
|
|
314
|
+
|
|
315
|
+
```sh
|
|
316
|
+
mcpman watch my-server # watch with defaults
|
|
317
|
+
mcpman watch my-server --dir ./src # override watch directory
|
|
318
|
+
mcpman watch my-server --ext ts,js # watch specific extensions
|
|
319
|
+
mcpman watch my-server --delay 500 # set debounce delay (ms)
|
|
320
|
+
mcpman watch my-server --clear # clear terminal on restart
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
Uses Node.js built-in `fs.watch` (no chokidar). Debounces 300ms by default. Ignores `node_modules/`, `dist/`, `.git/`, `__pycache__/`. Vault secrets are injected same as `mcpman run`.
|
|
324
|
+
|
|
325
|
+
### `registry <list|add|remove|set-default>`
|
|
326
|
+
|
|
327
|
+
Manage custom registry URLs for MCP server resolution.
|
|
328
|
+
|
|
329
|
+
```sh
|
|
330
|
+
mcpman registry list # show all registries
|
|
331
|
+
mcpman registry add corp https://mcp.corp.com/api # add custom registry
|
|
332
|
+
mcpman registry remove corp # remove custom registry
|
|
333
|
+
mcpman registry set-default smithery # change default registry
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Built-in registries (npm, smithery) are always present and cannot be removed. Custom registries are stored in `~/.mcpman/config.json`.
|
|
337
|
+
|
|
338
|
+
### `completions <bash|zsh|fish|install>`
|
|
339
|
+
|
|
340
|
+
Generate shell completion scripts for tab-completion of commands and server names.
|
|
341
|
+
|
|
342
|
+
```sh
|
|
343
|
+
mcpman completions bash # output bash completion script
|
|
344
|
+
mcpman completions zsh # output zsh completion script
|
|
345
|
+
mcpman completions fish # output fish completion script
|
|
346
|
+
mcpman completions install # auto-detect shell and install
|
|
347
|
+
source <(mcpman completions bash) # enable completions in current session
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
Completes: subcommands, server names (from lockfile), client types (`--client`), and runtimes (`--runtime`). Server names are resolved dynamically at completion time so they stay fresh.
|
|
351
|
+
|
|
352
|
+
### `why <server>`
|
|
353
|
+
|
|
354
|
+
Show why a server is installed — source, clients, profiles, env vars.
|
|
355
|
+
|
|
356
|
+
```sh
|
|
357
|
+
mcpman why my-server # full provenance output
|
|
358
|
+
mcpman why my-server --json # JSON output for scripting
|
|
359
|
+
```
|
|
360
|
+
|
|
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
|
+
|
|
286
363
|
---
|
|
287
364
|
|
|
288
365
|
## Comparison
|
|
@@ -306,6 +383,12 @@ mcpman import backup.json --dry-run # preview without applying
|
|
|
306
383
|
| Self-upgrade | Built-in CLI updater | None | None |
|
|
307
384
|
| Interactive setup | Yes | Partial | No |
|
|
308
385
|
| Project-scoped | Yes (`init`) | No | No |
|
|
386
|
+
| Server scaffolding | `create` (Node + Python) | None | None |
|
|
387
|
+
| Local dev linking | `link` (like npm link) | None | None |
|
|
388
|
+
| File watching | `watch` (auto-restart) | None | None |
|
|
389
|
+
| Custom registries | `registry` CRUD | None | None |
|
|
390
|
+
| Shell completions | bash + zsh + fish | None | None |
|
|
391
|
+
| Provenance query | `why` (clients + profiles) | None | None |
|
|
309
392
|
|
|
310
393
|
---
|
|
311
394
|
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/core/lockfile.ts
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import os from "os";
|
|
6
|
+
import path from "path";
|
|
7
|
+
var LOCKFILE_NAME = "mcpman.lock";
|
|
8
|
+
function findLockfile() {
|
|
9
|
+
let dir = process.cwd();
|
|
10
|
+
while (true) {
|
|
11
|
+
const candidate = path.join(dir, LOCKFILE_NAME);
|
|
12
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
13
|
+
const parent = path.dirname(dir);
|
|
14
|
+
if (parent === dir) break;
|
|
15
|
+
dir = parent;
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
function getGlobalLockfilePath() {
|
|
20
|
+
return path.join(os.homedir(), ".mcpman", LOCKFILE_NAME);
|
|
21
|
+
}
|
|
22
|
+
function resolveLockfilePath() {
|
|
23
|
+
return findLockfile() ?? getGlobalLockfilePath();
|
|
24
|
+
}
|
|
25
|
+
function readLockfile(filePath) {
|
|
26
|
+
const target = filePath ?? resolveLockfilePath();
|
|
27
|
+
if (!fs.existsSync(target)) {
|
|
28
|
+
return { lockfileVersion: 1, servers: {} };
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const raw = fs.readFileSync(target, "utf-8");
|
|
32
|
+
return JSON.parse(raw);
|
|
33
|
+
} catch {
|
|
34
|
+
return { lockfileVersion: 1, servers: {} };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function serialize(data) {
|
|
38
|
+
const sorted = {
|
|
39
|
+
lockfileVersion: data.lockfileVersion,
|
|
40
|
+
servers: Object.fromEntries(
|
|
41
|
+
Object.entries(data.servers).sort(([a], [b]) => a.localeCompare(b))
|
|
42
|
+
)
|
|
43
|
+
};
|
|
44
|
+
return `${JSON.stringify(sorted, null, 2)}
|
|
45
|
+
`;
|
|
46
|
+
}
|
|
47
|
+
function writeLockfile(data, filePath) {
|
|
48
|
+
const target = filePath ?? resolveLockfilePath();
|
|
49
|
+
const dir = path.dirname(target);
|
|
50
|
+
if (!fs.existsSync(dir)) {
|
|
51
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
52
|
+
}
|
|
53
|
+
const tmp = `${target}.tmp`;
|
|
54
|
+
fs.writeFileSync(tmp, serialize(data), "utf-8");
|
|
55
|
+
fs.renameSync(tmp, target);
|
|
56
|
+
}
|
|
57
|
+
function addEntry(name, entry, filePath) {
|
|
58
|
+
const data = readLockfile(filePath);
|
|
59
|
+
data.servers[name] = entry;
|
|
60
|
+
writeLockfile(data, filePath);
|
|
61
|
+
}
|
|
62
|
+
function removeEntry(name, filePath) {
|
|
63
|
+
const data = readLockfile(filePath);
|
|
64
|
+
if (name in data.servers) {
|
|
65
|
+
delete data.servers[name];
|
|
66
|
+
writeLockfile(data, filePath);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function getLockedVersion(name, filePath) {
|
|
70
|
+
const data = readLockfile(filePath);
|
|
71
|
+
return data.servers[name]?.version;
|
|
72
|
+
}
|
|
73
|
+
function createEmptyLockfile(filePath) {
|
|
74
|
+
writeLockfile({ lockfileVersion: 1, servers: {} }, filePath);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export {
|
|
78
|
+
LOCKFILE_NAME,
|
|
79
|
+
findLockfile,
|
|
80
|
+
getGlobalLockfilePath,
|
|
81
|
+
resolveLockfilePath,
|
|
82
|
+
readLockfile,
|
|
83
|
+
writeLockfile,
|
|
84
|
+
addEntry,
|
|
85
|
+
removeEntry,
|
|
86
|
+
getLockedVersion,
|
|
87
|
+
createEmptyLockfile
|
|
88
|
+
};
|