@myspec/mcp-server 0.4.0-next.92 → 0.4.0-next.93
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/index.js +56 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -255,6 +255,7 @@ async function exchangeCode(userAuthUrl, code, fetchImpl) {
|
|
|
255
255
|
}
|
|
256
256
|
|
|
257
257
|
// src/auth/credentials-store.ts
|
|
258
|
+
import { randomUUID } from "crypto";
|
|
258
259
|
import { promises as fs } from "fs";
|
|
259
260
|
import os from "os";
|
|
260
261
|
import path from "path";
|
|
@@ -365,9 +366,16 @@ async function writeJsonFile(target, data, opts) {
|
|
|
365
366
|
if (process.platform !== "win32") {
|
|
366
367
|
await fs.chmod(dir, 448);
|
|
367
368
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
await fs.
|
|
369
|
+
const tmp = `${target}.${randomUUID()}.tmp`;
|
|
370
|
+
try {
|
|
371
|
+
await fs.writeFile(tmp, JSON.stringify(data, null, 2), { mode: 384 });
|
|
372
|
+
if (opts.secret && process.platform !== "win32") {
|
|
373
|
+
await fs.chmod(tmp, 384);
|
|
374
|
+
}
|
|
375
|
+
await fs.rename(tmp, target);
|
|
376
|
+
} catch (err) {
|
|
377
|
+
await unlinkIfExists(tmp);
|
|
378
|
+
throw err;
|
|
371
379
|
}
|
|
372
380
|
}
|
|
373
381
|
async function unlinkIfExists(target) {
|
|
@@ -584,6 +592,37 @@ var TokenManager = class {
|
|
|
584
592
|
now;
|
|
585
593
|
cached = null;
|
|
586
594
|
refreshInFlight = null;
|
|
595
|
+
/**
|
|
596
|
+
* The in-flight disk read, shared by every concurrent caller.
|
|
597
|
+
*
|
|
598
|
+
* `refresh` below has always deduped; this did not, so N concurrent platform
|
|
599
|
+
* calls each issued their own `store.load()` before any of them populated
|
|
600
|
+
* `cached`. That is N redundant reads on every cold call, and it is what made
|
|
601
|
+
* a torn read reachable at all: one caller could be refreshing (and so
|
|
602
|
+
* rewriting the credential files) while the others were still reading them.
|
|
603
|
+
*
|
|
604
|
+
* The atomic write in credentials-store.ts is the real guarantee. This is the
|
|
605
|
+
* other half — it removes the redundant reads rather than merely surviving
|
|
606
|
+
* them.
|
|
607
|
+
*/
|
|
608
|
+
loadInFlight = null;
|
|
609
|
+
/**
|
|
610
|
+
* Bumped by clear(), so a read that started before it cannot publish its
|
|
611
|
+
* result to `cached` afterwards.
|
|
612
|
+
*
|
|
613
|
+
* Scope, precisely: the caller already awaiting that read still receives the
|
|
614
|
+
* pre-clear credentials as its own return value and proceeds with them —
|
|
615
|
+
* nothing can retract a value from a call already in flight. What the guard
|
|
616
|
+
* buys is that the value stops there instead of being cached, so no
|
|
617
|
+
* SUBSEQUENT caller is handed credentials the user asked us to forget.
|
|
618
|
+
*
|
|
619
|
+
* Dropping `loadInFlight` alone would not achieve even that: the awaiting
|
|
620
|
+
* caller resumes after the clear and would assign `cached` itself.
|
|
621
|
+
* Pre-existing — the old code assigned `cached` straight after an un-deduped
|
|
622
|
+
* `await` — but the fix belongs with the dedupe rather than left as a claim
|
|
623
|
+
* the comment cannot back up.
|
|
624
|
+
*/
|
|
625
|
+
loadGeneration = 0;
|
|
587
626
|
/**
|
|
588
627
|
* Set once the exchange endpoint rejects the API token outright.
|
|
589
628
|
*
|
|
@@ -616,17 +655,25 @@ var TokenManager = class {
|
|
|
616
655
|
}
|
|
617
656
|
async clear() {
|
|
618
657
|
this.cached = null;
|
|
658
|
+
this.loadInFlight = null;
|
|
659
|
+
this.loadGeneration += 1;
|
|
619
660
|
await this.store.clear();
|
|
620
661
|
}
|
|
621
662
|
async ensureLoaded() {
|
|
622
663
|
if (this.cached) {
|
|
623
664
|
return this.cached;
|
|
624
665
|
}
|
|
625
|
-
const
|
|
666
|
+
const generation = this.loadGeneration;
|
|
667
|
+
this.loadInFlight ??= this.store.load().finally(() => {
|
|
668
|
+
this.loadInFlight = null;
|
|
669
|
+
});
|
|
670
|
+
const loaded = await this.loadInFlight;
|
|
626
671
|
if (!loaded) {
|
|
627
672
|
throw new NeedsLoginError();
|
|
628
673
|
}
|
|
629
|
-
this.
|
|
674
|
+
if (generation === this.loadGeneration) {
|
|
675
|
+
this.cached = loaded;
|
|
676
|
+
}
|
|
630
677
|
return loaded;
|
|
631
678
|
}
|
|
632
679
|
async refresh(creds) {
|
|
@@ -1729,6 +1776,9 @@ function transformSpecFileResponse(raw) {
|
|
|
1729
1776
|
deletedAt: raw.deleted_at ? new Date(raw.deleted_at) : void 0
|
|
1730
1777
|
};
|
|
1731
1778
|
}
|
|
1779
|
+
function toRevisionAuthorKind(raw) {
|
|
1780
|
+
return raw === "ai" ? "ai" : "human";
|
|
1781
|
+
}
|
|
1732
1782
|
function transformFileRevisionResponse(raw) {
|
|
1733
1783
|
return {
|
|
1734
1784
|
id: raw.id,
|
|
@@ -1739,6 +1789,7 @@ function transformFileRevisionResponse(raw) {
|
|
|
1739
1789
|
fileSizeBytes: raw.file_size_bytes,
|
|
1740
1790
|
checksum: raw.checksum,
|
|
1741
1791
|
changedBy: raw.changed_by,
|
|
1792
|
+
changedByKind: toRevisionAuthorKind(raw.changed_by_kind),
|
|
1742
1793
|
createdAt: new Date(raw.created_at)
|
|
1743
1794
|
};
|
|
1744
1795
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myspec/mcp-server",
|
|
3
|
-
"version": "0.4.0-next.
|
|
3
|
+
"version": "0.4.0-next.93",
|
|
4
4
|
"description": "MySpec MCP server — exposes MySpec platform projects, files and attachments to MCP-aware clients via OAuth-authenticated access tokens.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|