@yawlabs/mcp 0.63.1 → 0.64.1
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/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@yawlabs/mcp` (formerly `@yawlabs/mcph`) are documented here. This project uses [semantic versioning](https://semver.org) and a script-gated release flow: `./release.sh <version>` runs lint + tests + build, bumps, tags, publishes to npm, and creates the GitHub release.
|
|
4
4
|
|
|
5
|
+
## 0.63.2 -- release pipeline: publish npm from CI
|
|
6
|
+
|
|
7
|
+
No changes to the package runtime or CLI -- this release exists to exercise the
|
|
8
|
+
new CI-on-tag-push publish flow end to end. The published artifact is identical
|
|
9
|
+
to 0.63.1 aside from the version bump.
|
|
10
|
+
|
|
11
|
+
- **npm is now published from CI, not the workstation.** A new `publish-npm` job
|
|
12
|
+
in `release.yml` publishes `@yawlabs/mcp` on every `v*` tag using the org
|
|
13
|
+
`NPM_TOKEN` + `--provenance` (the repo and package are public), gated on the
|
|
14
|
+
binary build so npm and the GitHub Release stay in lockstep. It is idempotent:
|
|
15
|
+
a version already live is a clean skip, and an `EPUBLISHCONFLICT` from
|
|
16
|
+
registry read-replica lag is treated as success. `publish-registry` now
|
|
17
|
+
`needs: publish-npm`, so the MCP-registry verify can no longer race ahead of
|
|
18
|
+
the npm publish. `release.sh`'s hand-off detection was tightened to key on a
|
|
19
|
+
real `npm publish` / `NODE_AUTH_TOKEN` signal instead of the registry job's
|
|
20
|
+
`id-token: write` (the false positive that wedged the 0.63.0/0.63.1 runs).
|
|
21
|
+
- **Registry job hardening.** `mcp-publisher` is pinned to a tagged release and
|
|
22
|
+
verified against its published sha256 before execution (was an unpinned
|
|
23
|
+
`curl .../latest | tar`), and the job pins its Node toolchain via `setup-node`.
|
|
24
|
+
|
|
5
25
|
## 0.63.1 -- CLI follow-ups: wire dead --dry-run/--stdin flags, fix completion drift, dedup probes
|
|
6
26
|
|
|
7
27
|
Patch-level follow-ups on the 0.63.0 CLI hardening pass. All fixes; no behavior changes for callers who weren't already hitting the dead-flag bugs.
|
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
// src/team-sync.ts
|
|
4
4
|
import { existsSync } from "fs";
|
|
5
|
-
import { chmod, readFile, unlink as unlink2 } from "fs/promises";
|
|
5
|
+
import { chmod as chmod2, readFile, unlink as unlink2 } from "fs/promises";
|
|
6
6
|
import { homedir as homedir2 } from "os";
|
|
7
7
|
import { join } from "path";
|
|
8
8
|
|
|
9
9
|
// src/atomic-write.ts
|
|
10
|
-
import { mkdir, rename, unlink, writeFile } from "fs/promises";
|
|
10
|
+
import { chmod, mkdir, rename, stat, unlink, writeFile } from "fs/promises";
|
|
11
11
|
import path from "path";
|
|
12
|
-
async function atomicWriteFile(filePath, contents, encoding = "utf8", mode) {
|
|
12
|
+
async function atomicWriteFile(filePath, contents, encoding = "utf8", mode, dirMode) {
|
|
13
13
|
const dir = path.dirname(filePath);
|
|
14
14
|
const tmp = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
15
|
-
await
|
|
15
|
+
await mkdirpWithMode(dir, dirMode);
|
|
16
16
|
try {
|
|
17
17
|
await writeFile(tmp, contents, mode === void 0 ? { encoding } : { encoding, mode });
|
|
18
18
|
await rename(tmp, filePath);
|
|
@@ -21,6 +21,36 @@ async function atomicWriteFile(filePath, contents, encoding = "utf8", mode) {
|
|
|
21
21
|
throw err;
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
+
async function mkdirpWithMode(dir, dirMode) {
|
|
25
|
+
if (dirMode === void 0 || process.platform === "win32") {
|
|
26
|
+
await mkdir(dir, { recursive: true });
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const resolved = path.resolve(dir);
|
|
30
|
+
const toCreate = [];
|
|
31
|
+
let cursor = resolved;
|
|
32
|
+
while (true) {
|
|
33
|
+
let exists = true;
|
|
34
|
+
try {
|
|
35
|
+
await stat(cursor);
|
|
36
|
+
} catch {
|
|
37
|
+
exists = false;
|
|
38
|
+
}
|
|
39
|
+
if (exists) break;
|
|
40
|
+
toCreate.unshift(cursor);
|
|
41
|
+
const parent = path.dirname(cursor);
|
|
42
|
+
if (parent === cursor) break;
|
|
43
|
+
cursor = parent;
|
|
44
|
+
}
|
|
45
|
+
if (toCreate.length === 0) return;
|
|
46
|
+
await mkdir(resolved, { recursive: true });
|
|
47
|
+
for (const created of toCreate) {
|
|
48
|
+
try {
|
|
49
|
+
await chmod(created, dirMode);
|
|
50
|
+
} catch {
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
24
54
|
|
|
25
55
|
// src/logger.ts
|
|
26
56
|
var LOG_LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
@@ -52,12 +82,24 @@ var CONFIG_DIRNAME = ".yaw-mcp";
|
|
|
52
82
|
function userConfigDir(home = homedir()) {
|
|
53
83
|
return path2.join(home, CONFIG_DIRNAME);
|
|
54
84
|
}
|
|
85
|
+
function normalizeForCompare(p) {
|
|
86
|
+
return process.platform === "win32" ? p.toLowerCase() : p;
|
|
87
|
+
}
|
|
88
|
+
function isUnderHome(dir, homeResolved) {
|
|
89
|
+
const dirKey = normalizeForCompare(dir);
|
|
90
|
+
const homeKey = normalizeForCompare(homeResolved);
|
|
91
|
+
if (dirKey === homeKey) return false;
|
|
92
|
+
const rel = path2.relative(homeResolved, dir);
|
|
93
|
+
const relNorm = normalizeForCompare(rel);
|
|
94
|
+
return relNorm !== "" && !relNorm.startsWith("..") && !path2.isAbsolute(rel);
|
|
95
|
+
}
|
|
55
96
|
async function findProjectConfigDir(start, home = homedir()) {
|
|
56
|
-
const
|
|
97
|
+
const homeFallback = home && home.length > 0 ? home : process.env.USERPROFILE || homedir();
|
|
98
|
+
const homeResolved = path2.resolve(homeFallback);
|
|
57
99
|
let dir = path2.resolve(start);
|
|
58
100
|
let prev = "";
|
|
59
101
|
while (dir !== prev) {
|
|
60
|
-
if (dir
|
|
102
|
+
if (!isUnderHome(dir, homeResolved)) return null;
|
|
61
103
|
const candidate = path2.join(dir, CONFIG_DIRNAME);
|
|
62
104
|
try {
|
|
63
105
|
await access(candidate);
|
|
@@ -108,7 +150,8 @@ function resolveBaseUrl(env = process.env) {
|
|
|
108
150
|
}
|
|
109
151
|
function expMs(session) {
|
|
110
152
|
const e = session.exp;
|
|
111
|
-
|
|
153
|
+
if (typeof e !== "number" || !Number.isFinite(e) || e <= 0) return 0;
|
|
154
|
+
return e < 1e12 ? e * 1e3 : e;
|
|
112
155
|
}
|
|
113
156
|
var cachedState = null;
|
|
114
157
|
function invalidateState() {
|
|
@@ -117,7 +160,7 @@ function invalidateState() {
|
|
|
117
160
|
async function loadStoredState(filePath) {
|
|
118
161
|
if (cachedState && cachedState.filePath === filePath) {
|
|
119
162
|
const s = cachedState.state;
|
|
120
|
-
if (s && expMs(s.session)
|
|
163
|
+
if (s && expMs(s.session) <= Date.now()) {
|
|
121
164
|
cachedState = { filePath, state: null };
|
|
122
165
|
return null;
|
|
123
166
|
}
|
|
@@ -130,11 +173,12 @@ async function loadStoredState(filePath) {
|
|
|
130
173
|
if (!obj || typeof obj !== "object") parsed = null;
|
|
131
174
|
else if (typeof obj.cookie !== "string" || !obj.cookie) parsed = null;
|
|
132
175
|
else if (!obj.session || typeof obj.session !== "object") parsed = null;
|
|
176
|
+
else if (typeof obj.session.exp !== "number" || !Number.isFinite(obj.session.exp)) parsed = null;
|
|
133
177
|
else parsed = obj;
|
|
134
178
|
} catch {
|
|
135
179
|
parsed = null;
|
|
136
180
|
}
|
|
137
|
-
if (parsed && expMs(parsed.session)
|
|
181
|
+
if (parsed && expMs(parsed.session) <= Date.now()) {
|
|
138
182
|
cachedState = { filePath, state: null };
|
|
139
183
|
return null;
|
|
140
184
|
}
|
|
@@ -144,10 +188,10 @@ async function loadStoredState(filePath) {
|
|
|
144
188
|
async function saveStoredState(filePath, state) {
|
|
145
189
|
cachedState = { filePath, state };
|
|
146
190
|
try {
|
|
147
|
-
await atomicWriteFile(filePath, JSON.stringify(state, null, 2), "utf8", 384);
|
|
191
|
+
await atomicWriteFile(filePath, JSON.stringify(state, null, 2), "utf8", 384, 448);
|
|
148
192
|
if (process.platform !== "win32") {
|
|
149
193
|
try {
|
|
150
|
-
await
|
|
194
|
+
await chmod2(filePath, 384);
|
|
151
195
|
} catch {
|
|
152
196
|
}
|
|
153
197
|
}
|
|
@@ -208,12 +252,18 @@ async function signIn(key, opts = {}) {
|
|
|
208
252
|
const trimmed = key.trim();
|
|
209
253
|
if (!trimmed) throw new Error("License key is required.");
|
|
210
254
|
const baseUrl = opts.baseUrl;
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
255
|
+
let post;
|
|
256
|
+
try {
|
|
257
|
+
post = await httpJson({
|
|
258
|
+
method: "POST",
|
|
259
|
+
path: "/api/team/session",
|
|
260
|
+
body: { key: trimmed },
|
|
261
|
+
baseUrl
|
|
262
|
+
});
|
|
263
|
+
} catch (err) {
|
|
264
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
265
|
+
throw new TeamSyncAuthError(`Sign in request failed: ${msg.replace(trimmed, "[redacted]")}`);
|
|
266
|
+
}
|
|
217
267
|
if (post.status !== 200 || !post.cookie || !post.body.email || !post.body.role || !post.body.order_id) {
|
|
218
268
|
if (post.body.error) log("warn", "team sign-in failed", { status: post.status, error: post.body.error });
|
|
219
269
|
throw new TeamSyncAuthError("Sign in failed. Check your license key and try again.");
|