puppet-mcp 4.5.0 → 4.5.2
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 +21 -4
- package/bin/puppet-mcp.js +82 -4
- package/engine-manifest.json +6 -6
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -7,9 +7,13 @@ manifest — it contains no server bytes.
|
|
|
7
7
|
## Run
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
|
-
npx puppet-mcp@
|
|
10
|
+
npx puppet-mcp@4.5.2
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
Global installs are refused by design (`npm i -g` exits non-zero): a
|
|
14
|
+
global copy would shadow the pinned `npx` invocation and run a stale
|
|
15
|
+
engine. Always invoke the pinned version above.
|
|
16
|
+
|
|
13
17
|
Or pin it in an MCP client as a stdio server with command and args:
|
|
14
18
|
|
|
15
19
|
```json
|
|
@@ -26,11 +30,13 @@ stdout belongs to the MCP channel.
|
|
|
26
30
|
## Diagnostics (no network)
|
|
27
31
|
|
|
28
32
|
```sh
|
|
29
|
-
npx puppet-mcp@
|
|
33
|
+
npx puppet-mcp@4.5.2 --doctor
|
|
30
34
|
```
|
|
31
35
|
|
|
32
|
-
Prints the
|
|
33
|
-
|
|
36
|
+
Prints the launcher version, the pinned manifest, the resolved engine
|
|
37
|
+
path, the engine version parsed from the cache directory name, and
|
|
38
|
+
whether they match; exits 0, performs no fetch. `--print-manifest` is
|
|
39
|
+
an alias. `npx puppet-mcp@4.5.2 --version` prints `puppet-mcp 4.5.2`.
|
|
34
40
|
|
|
35
41
|
## Requirements
|
|
36
42
|
|
|
@@ -39,6 +45,17 @@ no fetch. `--print-manifest` is an alias.
|
|
|
39
45
|
- Node.js >= 22.14.0 (global `fetch` plus `crypto`/`zlib` builtins only;
|
|
40
46
|
zero dependencies).
|
|
41
47
|
|
|
48
|
+
## Overrides
|
|
49
|
+
|
|
50
|
+
Use `npx puppet-mcp@latest`; a pinned version is only installable while its
|
|
51
|
+
GitHub release exists; the one-release policy deletes older releases.
|
|
52
|
+
|
|
53
|
+
Redirect the cache with `PUPPET_ENGINE_CACHE`, or inspect the pin with no
|
|
54
|
+
network access (`--print-manifest` is an alias for `--doctor` above).
|
|
55
|
+
There is no fully offline fresh install: the first serve run still downloads
|
|
56
|
+
the pinned binary, so treat these as pins and overrides, not as a no-network
|
|
57
|
+
setup.
|
|
58
|
+
|
|
42
59
|
## License
|
|
43
60
|
|
|
44
61
|
MIT — see LICENSE.
|
package/bin/puppet-mcp.js
CHANGED
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
//
|
|
6
6
|
// Published package model (see docs/PUBLISHING.md): this tarball holds ONLY
|
|
7
7
|
// this launcher plus the pinned server manifest (engine-manifest.json).
|
|
8
|
-
// It holds NO server bytes
|
|
8
|
+
// It holds NO server bytes and NO `os` field. Its only `scripts` are the
|
|
9
|
+
// global-install guards (preinstall/postinstall refuse `npm i -g` so a
|
|
10
|
+
// global copy can never shadow a pinned `npx` run); no download happens
|
|
11
|
+
// at install time.
|
|
9
12
|
// npm 12 blocks dependency install scripts by default (measured
|
|
10
13
|
// `allow-scripts=false`), and npx has no project package.json for
|
|
11
14
|
// `allowScripts` to consult, so a postinstall download would silently never
|
|
@@ -42,6 +45,7 @@ const { spawn } = require('node:child_process');
|
|
|
42
45
|
|
|
43
46
|
const PKG_DIR = path.dirname(path.dirname(__filename));
|
|
44
47
|
const MANIFEST_PATH = path.join(PKG_DIR, 'engine-manifest.json');
|
|
48
|
+
const PKG_PATH = path.join(PKG_DIR, 'package.json');
|
|
45
49
|
const PLATFORM_OVERRIDE_ENV = 'PUPPET_MCP_ALLOW_NON_WINDOWS';
|
|
46
50
|
const RUNTIME_CACHE_OVERRIDE_ENV = 'PUPPET_ENGINE_CACHE';
|
|
47
51
|
const DOWNLOAD_TIMEOUT_MS = 15 * 60 * 1000;
|
|
@@ -74,6 +78,57 @@ function loadManifest() {
|
|
|
74
78
|
}
|
|
75
79
|
}
|
|
76
80
|
|
|
81
|
+
function loadPackageVersion() {
|
|
82
|
+
let raw;
|
|
83
|
+
try {
|
|
84
|
+
raw = fs.readFileSync(PKG_PATH, 'utf8');
|
|
85
|
+
} catch (err) {
|
|
86
|
+
fail(`cannot read package.json at ${PKG_PATH}: ${err.message}`);
|
|
87
|
+
}
|
|
88
|
+
let pkg;
|
|
89
|
+
try {
|
|
90
|
+
pkg = JSON.parse(raw);
|
|
91
|
+
} catch (err) {
|
|
92
|
+
fail(`package.json is not valid JSON: ${err.message}`);
|
|
93
|
+
}
|
|
94
|
+
if (pkg.name !== 'puppet-mcp' || typeof pkg.version !== 'string' || pkg.version.length === 0) {
|
|
95
|
+
fail('package.json does not name a versioned puppet-mcp package.');
|
|
96
|
+
}
|
|
97
|
+
return pkg.version;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Engine cache leaf dirs are `<semver>-<bundleSha8>` (or `<semver>-unpublished`
|
|
101
|
+
// before the release workflow fills the manifest hashes).
|
|
102
|
+
function isEngineLeafName(base) {
|
|
103
|
+
return /^\d+\.\d+\.\d+-(?:[0-9a-fA-F]{8}|unpublished)$/.test(base);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// The semver embedded in an engine cache path, or null when the path names
|
|
107
|
+
// no engine leaf.
|
|
108
|
+
function parseEngineVersionFromDir(dir) {
|
|
109
|
+
const m = /^(\d+\.\d+\.\d+)-(?:[0-9a-fA-F]{8}|unpublished)$/.exec(path.basename(dir));
|
|
110
|
+
return m ? m[1] : null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Fail closed on version skew: never spawn an engine older (or newer) than
|
|
114
|
+
// this launcher. Prints one actionable line and exits non-zero.
|
|
115
|
+
function assertVersionMatch(pkgVersion, manifest, cacheDir) {
|
|
116
|
+
const reprovision = `npx -y puppet-mcp@${pkgVersion}`;
|
|
117
|
+
if (manifest.serverVersion !== pkgVersion) {
|
|
118
|
+
fail(
|
|
119
|
+
`version mismatch: launcher ${pkgVersion} but manifest pins server ${manifest.serverVersion}; ` +
|
|
120
|
+
`re-provision with \`${reprovision}\` from a clean cache.`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
const engineVersion = parseEngineVersionFromDir(cacheDir);
|
|
124
|
+
if (engineVersion !== null && engineVersion !== pkgVersion) {
|
|
125
|
+
fail(
|
|
126
|
+
`version mismatch: launcher ${pkgVersion} but engine ${engineVersion} at ${cacheDir}; ` +
|
|
127
|
+
`re-provision with \`${reprovision}\` (unset ${RUNTIME_CACHE_OVERRIDE_ENV} if it names the stale engine).`
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
77
132
|
// A sha256 slot the release workflow has not filled yet. The committed
|
|
78
133
|
// manifest carries the sentinel for every hash computed at release time;
|
|
79
134
|
// the workflow replaces each with the real 64-hex digest. Anything else
|
|
@@ -91,6 +146,12 @@ function shortHash(sha) {
|
|
|
91
146
|
function resolveRuntimeDir(manifest) {
|
|
92
147
|
const notes = [];
|
|
93
148
|
let root = process.env[RUNTIME_CACHE_OVERRIDE_ENV];
|
|
149
|
+
// An override that already names an engine leaf dir (<semver>-<sha8>)
|
|
150
|
+
// is used as-is, so a stale pin resolves to the stale engine and the
|
|
151
|
+
// version gate below can refuse it instead of silently provisioning past.
|
|
152
|
+
if (root && isEngineLeafName(path.basename(path.resolve(root)))) {
|
|
153
|
+
return { dir: path.resolve(root), overridden: true, notes };
|
|
154
|
+
}
|
|
94
155
|
let overridden = Boolean(root);
|
|
95
156
|
if (!root) {
|
|
96
157
|
root = process.env.LOCALAPPDATA;
|
|
@@ -101,11 +162,16 @@ function resolveRuntimeDir(manifest) {
|
|
|
101
162
|
return { dir: path.join(root, 'puppet', 'engine', leaf), overridden, notes };
|
|
102
163
|
}
|
|
103
164
|
|
|
104
|
-
function printDoctor(manifest) {
|
|
165
|
+
function printDoctor(manifest, pkgVersion) {
|
|
105
166
|
const runtime = resolveRuntimeDir(manifest);
|
|
167
|
+
const engineVersion = parseEngineVersionFromDir(runtime.dir);
|
|
106
168
|
const report = {
|
|
107
169
|
package: manifest.package,
|
|
170
|
+
launcherVersion: pkgVersion,
|
|
108
171
|
serverVersion: manifest.serverVersion,
|
|
172
|
+
engineVersion,
|
|
173
|
+
enginePath: path.join(runtime.dir, manifest.runtime.server.name),
|
|
174
|
+
versionsMatch: engineVersion === pkgVersion && manifest.serverVersion === pkgVersion,
|
|
109
175
|
platform: process.platform,
|
|
110
176
|
node: process.version,
|
|
111
177
|
runtimeCacheDir: runtime.dir,
|
|
@@ -120,9 +186,10 @@ function printDoctor(manifest) {
|
|
|
120
186
|
|
|
121
187
|
function usage(exitCode, stream) {
|
|
122
188
|
const text = [
|
|
123
|
-
'usage: puppet-mcp [--doctor | --print-manifest | --help]',
|
|
189
|
+
'usage: puppet-mcp [--version | --doctor | --print-manifest | --help]',
|
|
124
190
|
'',
|
|
125
191
|
' (no args) provision the pinned server on first run, then serve MCP over stdio',
|
|
192
|
+
' --version print `puppet-mcp <launcherVersion>`, exit 0',
|
|
126
193
|
' --doctor, print the pinned manifest and the resolved cache',
|
|
127
194
|
' --print-manifest path, perform no network access, exit 0',
|
|
128
195
|
' --help, -h print this usage',
|
|
@@ -385,12 +452,20 @@ async function main() {
|
|
|
385
452
|
const args = process.argv.slice(2);
|
|
386
453
|
const manifest = loadManifest();
|
|
387
454
|
if (manifest.package !== 'puppet-mcp') fail('engine manifest names the wrong package.');
|
|
455
|
+
const pkgVersion = loadPackageVersion();
|
|
388
456
|
if (args.includes('--help') || args.includes('-h')) usage(0, process.stdout);
|
|
457
|
+
if (args.includes('--version')) {
|
|
458
|
+
const known = new Set(['--version']);
|
|
459
|
+
const unknown = args.filter((a) => !known.has(a));
|
|
460
|
+
if (unknown.length > 0) usage(2, process.stderr);
|
|
461
|
+
process.stdout.write(`puppet-mcp ${pkgVersion}\n`);
|
|
462
|
+
process.exit(0);
|
|
463
|
+
}
|
|
389
464
|
if (args.includes('--doctor') || args.includes('--print-manifest')) {
|
|
390
465
|
const known = new Set(['--doctor', '--print-manifest']);
|
|
391
466
|
const unknown = args.filter((a) => !known.has(a));
|
|
392
467
|
if (unknown.length > 0) usage(2, process.stderr);
|
|
393
|
-
printDoctor(manifest); // exits 0; no network on this path by construction
|
|
468
|
+
printDoctor(manifest, pkgVersion); // exits 0; no network on this path by construction
|
|
394
469
|
}
|
|
395
470
|
// Every known flag returns above; anything left is unknown.
|
|
396
471
|
if (args.length > 0) usage(2, process.stderr);
|
|
@@ -406,6 +481,9 @@ async function main() {
|
|
|
406
481
|
if (runtime.dir.startsWith('<unresolvable>')) {
|
|
407
482
|
fail(`cannot resolve runtime cache dir: ${runtime.notes.join('; ')}. Set ${RUNTIME_CACHE_OVERRIDE_ENV} to an absolute directory.`);
|
|
408
483
|
}
|
|
484
|
+
// Fail closed BEFORE any download or spawn: a stale cache path (e.g. via
|
|
485
|
+
// override) or a mispublished manifest refuses here, never runs.
|
|
486
|
+
assertVersionMatch(pkgVersion, manifest, runtime.dir);
|
|
409
487
|
const readyRuntime = await ensureRuntime(manifest, runtime.dir);
|
|
410
488
|
runServer(readyRuntime, manifest);
|
|
411
489
|
}
|
package/engine-manifest.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_comment": "Pinned server manifest for the puppet-mcp launcher. The published package holds ONLY this launcher plus this manifest (no server bytes). On first run the launcher downloads ONLY runtime.archive from the tagged GitHub Release, verifying its sha256 before and after extraction. The release workflow fills the runtime.* hashes it computes from the real build artifacts; the committed manifest carries placeholders the launcher refuses to run with.",
|
|
3
3
|
"package": "puppet-mcp",
|
|
4
|
-
"serverVersion": "4.5.
|
|
4
|
+
"serverVersion": "4.5.2",
|
|
5
5
|
"runtime": {
|
|
6
|
-
"source": "https://github.com/nathwn12/puppet/releases/download/v4.5.
|
|
6
|
+
"source": "https://github.com/nathwn12/puppet/releases/download/v4.5.2",
|
|
7
7
|
"archive": {
|
|
8
|
-
"name": "puppet-v4.5.
|
|
9
|
-
"sha256": "
|
|
10
|
-
"size":
|
|
8
|
+
"name": "puppet-v4.5.2-windows-x86_64.zip",
|
|
9
|
+
"sha256": "4b0ebbf25194530fd426d6dcc1fbb54270cc4a1b8148d1ffedd5fc2f94fc83ea",
|
|
10
|
+
"size": 687786
|
|
11
11
|
},
|
|
12
12
|
"server": {
|
|
13
13
|
"name": "puppet.exe",
|
|
14
|
-
"sha256": "
|
|
14
|
+
"sha256": "c0fb32a61583084eb0e54a6c9f5be1c2d4c85eac3761256ae547b5ae850a2e09",
|
|
15
15
|
"size": 1810944
|
|
16
16
|
}
|
|
17
17
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "puppet-mcp",
|
|
3
|
-
"version": "4.5.
|
|
3
|
+
"version": "4.5.2",
|
|
4
4
|
"description": "Windows desktop-control MCP server (npx launcher): provisions the pinned server binary from the GitHub Release on first run, then serves MCP over stdio",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
"bin": {
|
|
15
15
|
"puppet-mcp": "./bin/puppet-mcp.js"
|
|
16
16
|
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"preinstall": "node -e \"if(process.env.npm_config_global==='true'){console.error('puppet-mcp: global install refused — use npx puppet-mcp@4.5.2');process.exit(1)}\"",
|
|
19
|
+
"postinstall": "node -e \"if(process.env.npm_config_global==='true'){console.error('puppet-mcp: global install refused — use npx puppet-mcp@4.5.2');process.exit(1)}\""
|
|
20
|
+
},
|
|
17
21
|
"files": [
|
|
18
22
|
"bin/",
|
|
19
23
|
"engine-manifest.json",
|