@yawlabs/npmjs-mcp 0.12.0 → 0.12.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/dist/index.js +226 -107
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -31429,7 +31429,13 @@ var accessTools = [
|
|
|
31429
31429
|
registryGetAuth(`/-/package/${encPkg(input.name)}/access`),
|
|
31430
31430
|
registryGetAuth(`/-/package/${encPkg(input.name)}/collaborators`)
|
|
31431
31431
|
]);
|
|
31432
|
-
if (!accessRes.ok && !collabRes.ok)
|
|
31432
|
+
if (!accessRes.ok && !collabRes.ok) {
|
|
31433
|
+
return {
|
|
31434
|
+
ok: false,
|
|
31435
|
+
status: accessRes.status,
|
|
31436
|
+
error: `Both package_access requests failed. access endpoint: HTTP ${accessRes.status}${accessRes.error ? ` (${accessRes.error})` : ""}; collaborators endpoint: HTTP ${collabRes.status}${collabRes.error ? ` (${collabRes.error})` : ""}.`
|
|
31437
|
+
};
|
|
31438
|
+
}
|
|
31433
31439
|
const result = {
|
|
31434
31440
|
package: input.name,
|
|
31435
31441
|
isScoped: input.name.startsWith("@")
|
|
@@ -31468,6 +31474,10 @@ var analysisTools = [
|
|
|
31468
31474
|
packages: external_exports.array(external_exports.string()).min(2).max(5).describe("Package names to compare")
|
|
31469
31475
|
}),
|
|
31470
31476
|
handler: async (input) => {
|
|
31477
|
+
for (const pkg of input.packages) {
|
|
31478
|
+
const nameErr = validatePackageName(pkg);
|
|
31479
|
+
if (nameErr) return { ok: false, status: 400, error: nameErr };
|
|
31480
|
+
}
|
|
31471
31481
|
const partials = await Promise.all(
|
|
31472
31482
|
input.packages.map(async (name) => {
|
|
31473
31483
|
const [pkgRes, dlRes] = await Promise.all([
|
|
@@ -31514,9 +31524,11 @@ var analysisTools = [
|
|
|
31514
31524
|
// ok:true with no data (api.ts), and dlRes.data!.downloads would throw
|
|
31515
31525
|
// and crash the whole compare. Degrade to null instead.
|
|
31516
31526
|
weeklyDownloads: dlRes.ok && dlRes.data ? dlRes.data.downloads : null,
|
|
31527
|
+
// versionCount includes ALL published versions (stable + pre-releases).
|
|
31517
31528
|
versionCount: versionKeys.length,
|
|
31518
31529
|
created: pkg.time.created,
|
|
31519
31530
|
lastPublish: latest ? pkg.time[latest] : void 0,
|
|
31531
|
+
// `deprecated` is false when not deprecated, or the message string when deprecated.
|
|
31520
31532
|
deprecated: latestVersion?.deprecated ?? false,
|
|
31521
31533
|
hasReadme: !!(pkg.readme && pkg.readme.length > 0),
|
|
31522
31534
|
repository: pkg.repository,
|
|
@@ -31579,12 +31591,15 @@ var analysisTools = [
|
|
|
31579
31591
|
}
|
|
31580
31592
|
avgDaysBetweenReleases = Math.round(gaps.reduce((a, b) => a + b, 0) / gaps.length);
|
|
31581
31593
|
}
|
|
31594
|
+
const STALE_DAYS = 365;
|
|
31595
|
+
const ACTIVE_DAYS = 90;
|
|
31582
31596
|
const hasLicense = !!(pkg.license ?? latestVersion?.license);
|
|
31583
31597
|
const hasReadme = !!(pkg.readme && pkg.readme.length > 0);
|
|
31584
31598
|
const hasRepo = !!pkg.repository;
|
|
31585
31599
|
const hasHomepage = !!pkg.homepage;
|
|
31586
31600
|
const isDeprecated = !!latestVersion?.deprecated;
|
|
31587
|
-
const
|
|
31601
|
+
const deprecatedMessage = latestVersion?.deprecated ?? false;
|
|
31602
|
+
const isStale = daysSinceLastPublish !== null && daysSinceLastPublish > STALE_DAYS;
|
|
31588
31603
|
return {
|
|
31589
31604
|
ok: true,
|
|
31590
31605
|
status: 200,
|
|
@@ -31597,6 +31612,7 @@ var analysisTools = [
|
|
|
31597
31612
|
weeklyDownloads: dlWeekRes.ok && dlWeekRes.data ? dlWeekRes.data.downloads : null,
|
|
31598
31613
|
monthlyDownloads: dlMonthRes.ok && dlMonthRes.data ? dlMonthRes.data.downloads : null,
|
|
31599
31614
|
maintainerCount: pkg.maintainers?.length ?? 0,
|
|
31615
|
+
// versionCount includes ALL published versions (stable + pre-releases).
|
|
31600
31616
|
versionCount: versionKeys.length,
|
|
31601
31617
|
daysSinceLastPublish,
|
|
31602
31618
|
avgDaysBetweenReleases,
|
|
@@ -31606,7 +31622,8 @@ var analysisTools = [
|
|
|
31606
31622
|
hasReadme,
|
|
31607
31623
|
hasRepo,
|
|
31608
31624
|
hasHomepage,
|
|
31609
|
-
isDeprecated,
|
|
31625
|
+
// `isDeprecated` is false when not deprecated, or the message string when deprecated.
|
|
31626
|
+
isDeprecated: deprecatedMessage,
|
|
31610
31627
|
isStale
|
|
31611
31628
|
},
|
|
31612
31629
|
// Holistic single-string verdict layered priority-first: a deprecated
|
|
@@ -31616,7 +31633,7 @@ var analysisTools = [
|
|
|
31616
31633
|
// on the audit endpoint or a packument with no `latest` to audit) --
|
|
31617
31634
|
// better to flag the unknown than confidently report ACTIVE on
|
|
31618
31635
|
// unverified data. Then staleness, recency, and the catch-all.
|
|
31619
|
-
assessment: isDeprecated ? "DEPRECATED" : vulnerabilityCount !== null && vulnerabilityCount > 0 ? "VULNERABLE" : !auditReliable ? "AUDIT_UNKNOWN" : isStale ? "STALE" : daysSinceLastPublish !== null && daysSinceLastPublish <
|
|
31636
|
+
assessment: isDeprecated ? "DEPRECATED" : vulnerabilityCount !== null && vulnerabilityCount > 0 ? "VULNERABLE" : !auditReliable ? "AUDIT_UNKNOWN" : isStale ? "STALE" : daysSinceLastPublish !== null && daysSinceLastPublish < ACTIVE_DAYS ? "ACTIVE" : "MAINTENANCE"
|
|
31620
31637
|
}
|
|
31621
31638
|
};
|
|
31622
31639
|
}
|
|
@@ -31690,6 +31707,7 @@ var analysisTools = [
|
|
|
31690
31707
|
status: 200,
|
|
31691
31708
|
data: {
|
|
31692
31709
|
name: pkg.name,
|
|
31710
|
+
// totalVersions includes ALL published versions (stable + pre-releases).
|
|
31693
31711
|
totalVersions: Object.keys(pkg.versions).length,
|
|
31694
31712
|
analyzed: releases.length,
|
|
31695
31713
|
created: pkg.time.created,
|
|
@@ -31797,6 +31815,10 @@ var authTools = [
|
|
|
31797
31815
|
tokens: data.objects.map((t) => ({
|
|
31798
31816
|
key: t.key,
|
|
31799
31817
|
readonly: t.readonly,
|
|
31818
|
+
// Surface token class + automation flag so callers can tell automation
|
|
31819
|
+
// tokens (which bypass 2FA) from granular/legacy ones. Undefined drops out of JSON.
|
|
31820
|
+
type: t.type,
|
|
31821
|
+
automation: t.automation,
|
|
31800
31822
|
cidrWhitelist: t.cidr_whitelist,
|
|
31801
31823
|
created: t.created,
|
|
31802
31824
|
updated: t.updated
|
|
@@ -31941,7 +31963,7 @@ var dependencyTools = [
|
|
|
31941
31963
|
inputSchema: external_exports.object({
|
|
31942
31964
|
name: external_exports.string().describe("Package name"),
|
|
31943
31965
|
version: external_exports.string().optional().describe("Semver version or dist-tag (default: 'latest')"),
|
|
31944
|
-
depth: external_exports.number().min(1).max(5).optional().describe("Max tree depth (default 3, max 5)")
|
|
31966
|
+
depth: external_exports.number().min(1).max(5).optional().describe("Max tree depth where the root counts as level 1 (default 3 = root + 2 transitive levels, max 5)")
|
|
31945
31967
|
}),
|
|
31946
31968
|
handler: async (input) => {
|
|
31947
31969
|
const maxDepth = input.depth ?? 3;
|
|
@@ -31957,7 +31979,18 @@ var dependencyTools = [
|
|
|
31957
31979
|
resolved.add(hintKey);
|
|
31958
31980
|
let pending = packumentCache.get(name);
|
|
31959
31981
|
if (!pending) {
|
|
31960
|
-
|
|
31982
|
+
let encodedName;
|
|
31983
|
+
try {
|
|
31984
|
+
encodedName = encPkg(name);
|
|
31985
|
+
} catch {
|
|
31986
|
+
warnings.push(`Invalid package name "${name}": skipped`);
|
|
31987
|
+
if (!failedPackages.has(name)) {
|
|
31988
|
+
failedPackages.add(name);
|
|
31989
|
+
tree[hintKey] = { version: versionHint2, dependencies: {}, failed: true };
|
|
31990
|
+
}
|
|
31991
|
+
return;
|
|
31992
|
+
}
|
|
31993
|
+
pending = runLimited(() => registryGetAbbreviated(`/${encodedName}`)).then((res) => {
|
|
31961
31994
|
if (!res.ok) {
|
|
31962
31995
|
warnings.push(`Failed to fetch ${name}: ${res.error}`);
|
|
31963
31996
|
return null;
|
|
@@ -32095,7 +32128,7 @@ var downloadTools = [
|
|
|
32095
32128
|
},
|
|
32096
32129
|
inputSchema: external_exports.object({
|
|
32097
32130
|
name: external_exports.string().describe("Package name"),
|
|
32098
|
-
period: external_exports.string().optional().describe("Period: 'last-day', 'last-week', 'last-month', 'last-year', or 'YYYY-MM-DD:YYYY-MM-DD'")
|
|
32131
|
+
period: external_exports.string().optional().describe("Period: 'last-day', 'last-week', 'last-month', 'last-year', or 'YYYY-MM-DD:YYYY-MM-DD' (default: 'last-week')")
|
|
32099
32132
|
}),
|
|
32100
32133
|
handler: async (input) => {
|
|
32101
32134
|
const period = input.period ?? "last-week";
|
|
@@ -32115,7 +32148,7 @@ var downloadTools = [
|
|
|
32115
32148
|
},
|
|
32116
32149
|
inputSchema: external_exports.object({
|
|
32117
32150
|
name: external_exports.string().describe("Package name"),
|
|
32118
|
-
period: external_exports.string().optional().describe("Period: 'last-week', 'last-month', 'last-year', or 'YYYY-MM-DD:YYYY-MM-DD'")
|
|
32151
|
+
period: external_exports.string().optional().describe("Period: 'last-week', 'last-month', 'last-year', or 'YYYY-MM-DD:YYYY-MM-DD' (default: 'last-month')")
|
|
32119
32152
|
}),
|
|
32120
32153
|
handler: async (input) => {
|
|
32121
32154
|
const period = input.period ?? "last-month";
|
|
@@ -32139,6 +32172,10 @@ var downloadTools = [
|
|
|
32139
32172
|
}),
|
|
32140
32173
|
handler: async (input) => {
|
|
32141
32174
|
const period = input.period ?? "last-week";
|
|
32175
|
+
for (const pkg of input.packages) {
|
|
32176
|
+
const nameErr = validatePackageName(pkg);
|
|
32177
|
+
if (nameErr) return { ok: false, status: 400, error: nameErr };
|
|
32178
|
+
}
|
|
32142
32179
|
const scoped = input.packages.filter((p) => p.startsWith("@"));
|
|
32143
32180
|
if (scoped.length > 0) {
|
|
32144
32181
|
return {
|
|
@@ -32267,7 +32304,17 @@ var hookTools = [
|
|
|
32267
32304
|
const authErr = requireAuth();
|
|
32268
32305
|
if (authErr) return authErr;
|
|
32269
32306
|
const qs = new URLSearchParams();
|
|
32270
|
-
if (input.package)
|
|
32307
|
+
if (input.package) {
|
|
32308
|
+
const pkgErr = validatePackageName(input.package);
|
|
32309
|
+
if (pkgErr) {
|
|
32310
|
+
return {
|
|
32311
|
+
ok: false,
|
|
32312
|
+
status: 400,
|
|
32313
|
+
error: `Invalid package filter '${input.package}': ${pkgErr}`
|
|
32314
|
+
};
|
|
32315
|
+
}
|
|
32316
|
+
qs.set("package", input.package);
|
|
32317
|
+
}
|
|
32271
32318
|
if (input.limit !== void 0) qs.set("limit", String(input.limit));
|
|
32272
32319
|
if (input.offset !== void 0) qs.set("offset", String(input.offset));
|
|
32273
32320
|
const q = qs.toString();
|
|
@@ -32760,7 +32807,7 @@ var packageTools = [
|
|
|
32760
32807
|
var provenanceTools = [
|
|
32761
32808
|
{
|
|
32762
32809
|
name: "npm_provenance",
|
|
32763
|
-
description: "
|
|
32810
|
+
description: "Retrieve Sigstore attestations for a specific package version. Shows SLSA provenance (which CI built it, from which repo/commit) and publish attestations. NOTE: this tool RETRIEVES attestations from the registry -- it does NOT perform cryptographic signature, certificate-chain, or Rekor transparency-log verification. Use a dedicated Sigstore client to cryptographically verify the bundles.",
|
|
32764
32811
|
annotations: {
|
|
32765
32812
|
title: "Package provenance",
|
|
32766
32813
|
readOnlyHint: true,
|
|
@@ -32773,6 +32820,9 @@ var provenanceTools = [
|
|
|
32773
32820
|
version: external_exports.string().describe("Exact semver version (e.g. '1.0.0')")
|
|
32774
32821
|
}),
|
|
32775
32822
|
handler: async (input) => {
|
|
32823
|
+
if (!input.version || !input.version.trim()) {
|
|
32824
|
+
return { ok: false, status: 400, error: "version is required and must not be empty" };
|
|
32825
|
+
}
|
|
32776
32826
|
const res = await registryGet(
|
|
32777
32827
|
`/-/npm/v1/attestations/${encPkg(input.name)}@${encodeURIComponent(input.version)}`
|
|
32778
32828
|
);
|
|
@@ -32788,8 +32838,10 @@ var provenanceTools = [
|
|
|
32788
32838
|
package: input.name,
|
|
32789
32839
|
version: input.version,
|
|
32790
32840
|
attestationCount: attestations.length,
|
|
32791
|
-
hasProvenance: attestations.some((a) => a.predicateType.
|
|
32792
|
-
hasPublishAttestation: attestations.some(
|
|
32841
|
+
hasProvenance: attestations.some((a) => a.predicateType.startsWith("https://slsa.dev/provenance")),
|
|
32842
|
+
hasPublishAttestation: attestations.some(
|
|
32843
|
+
(a) => a.predicateType.startsWith("https://npmjs.com/attestation")
|
|
32844
|
+
),
|
|
32793
32845
|
attestations
|
|
32794
32846
|
}
|
|
32795
32847
|
};
|
|
@@ -32825,7 +32877,7 @@ var registryTools = [
|
|
|
32825
32877
|
title: "Recent registry changes",
|
|
32826
32878
|
readOnlyHint: true,
|
|
32827
32879
|
destructiveHint: false,
|
|
32828
|
-
idempotentHint:
|
|
32880
|
+
idempotentHint: false,
|
|
32829
32881
|
openWorldHint: true
|
|
32830
32882
|
},
|
|
32831
32883
|
inputSchema: external_exports.object({
|
|
@@ -32846,13 +32898,11 @@ var registryTools = [
|
|
|
32846
32898
|
ok: true,
|
|
32847
32899
|
status: 200,
|
|
32848
32900
|
data: {
|
|
32849
|
-
// `
|
|
32850
|
-
// (the entire npm registry, ~3M) -- NOT the
|
|
32851
|
-
// changes. `changes.length` is the per-call
|
|
32852
|
-
//
|
|
32853
|
-
|
|
32854
|
-
// only 50 packages exist on the registry.
|
|
32855
|
-
totalPackages: dbRes.data?.doc_count ?? null,
|
|
32901
|
+
// `registryPackageCount` is the registry-wide doc-count from
|
|
32902
|
+
// replicate.npmjs.com (the entire npm registry, ~3M) -- NOT the
|
|
32903
|
+
// count of returned changes. `changes.length` is the per-call
|
|
32904
|
+
// result size.
|
|
32905
|
+
registryPackageCount: dbRes.data?.doc_count ?? null,
|
|
32856
32906
|
changes
|
|
32857
32907
|
}
|
|
32858
32908
|
};
|
|
@@ -32885,7 +32935,7 @@ var registryTools = [
|
|
|
32885
32935
|
tool: "mcp_tool: npm_deprecate",
|
|
32886
32936
|
requiresNpmToken: true,
|
|
32887
32937
|
messageFormat: {
|
|
32888
|
-
preferred: "Renamed to @scope/pkg
|
|
32938
|
+
preferred: "Renamed to @scope/pkg -- install that instead",
|
|
32889
32939
|
avoid: "Renamed to @scope/pkg. Install that instead.",
|
|
32890
32940
|
note: "Period-capital form has triggered 422 on at least one scoped package; use em-dash."
|
|
32891
32941
|
}
|
|
@@ -33045,9 +33095,15 @@ var securityTools = [
|
|
|
33045
33095
|
dependencies: external_exports.record(external_exports.string(), external_exports.string()).describe('Dependencies to audit as { "package": "version" }, e.g. { "express": "4.17.1" }')
|
|
33046
33096
|
}),
|
|
33047
33097
|
handler: async (input) => {
|
|
33098
|
+
const nameErr = validatePackageName(input.name);
|
|
33099
|
+
if (nameErr) return { ok: false, status: 400, error: nameErr };
|
|
33100
|
+
const version3 = input.version ?? "1.0.0";
|
|
33101
|
+
if (!version3.trim()) {
|
|
33102
|
+
return { ok: false, status: 400, error: "version must not be empty" };
|
|
33103
|
+
}
|
|
33048
33104
|
const body = {
|
|
33049
33105
|
name: input.name,
|
|
33050
|
-
version:
|
|
33106
|
+
version: version3,
|
|
33051
33107
|
requires: input.dependencies,
|
|
33052
33108
|
dependencies: Object.fromEntries(
|
|
33053
33109
|
Object.entries(input.dependencies).map(([pkg, ver]) => [pkg, { version: ver }])
|
|
@@ -33103,12 +33159,20 @@ var trustTools = [
|
|
|
33103
33159
|
if (c.type === "github") {
|
|
33104
33160
|
result.repository = c.claims?.repository;
|
|
33105
33161
|
const workflowRef = c.claims?.workflow_ref;
|
|
33106
|
-
|
|
33162
|
+
if (typeof workflowRef === "string") {
|
|
33163
|
+
result.workflowFile = workflowRef;
|
|
33164
|
+
} else if (workflowRef && typeof workflowRef === "object") {
|
|
33165
|
+
result.workflowFile = workflowRef.file;
|
|
33166
|
+
}
|
|
33107
33167
|
result.environment = c.claims?.environment;
|
|
33108
33168
|
} else if (c.type === "gitlab") {
|
|
33109
33169
|
result.project = c.claims?.project_path;
|
|
33110
33170
|
const configRef = c.claims?.ci_config_ref_uri;
|
|
33111
|
-
|
|
33171
|
+
if (typeof configRef === "string") {
|
|
33172
|
+
result.configFile = configRef;
|
|
33173
|
+
} else if (configRef && typeof configRef === "object") {
|
|
33174
|
+
result.configFile = configRef.file;
|
|
33175
|
+
}
|
|
33112
33176
|
result.environment = c.claims?.environment;
|
|
33113
33177
|
} else if (c.type === "circleci") {
|
|
33114
33178
|
result.project = c.claims?.project_id;
|
|
@@ -33162,7 +33226,10 @@ var workflowTools = [
|
|
|
33162
33226
|
}
|
|
33163
33227
|
result.authenticated = true;
|
|
33164
33228
|
result.username = whoamiRes.data.username;
|
|
33165
|
-
const profileRes = await
|
|
33229
|
+
const [profileRes, tokensRes] = await Promise.all([
|
|
33230
|
+
registryGetAuth("/-/npm/v1/user"),
|
|
33231
|
+
registryGetAuth("/-/npm/v1/tokens")
|
|
33232
|
+
]);
|
|
33166
33233
|
if (profileRes.ok && profileRes.data) {
|
|
33167
33234
|
const tfa = profileRes.data.tfa;
|
|
33168
33235
|
if (tfa && !tfa.pending) {
|
|
@@ -33170,8 +33237,9 @@ var workflowTools = [
|
|
|
33170
33237
|
} else {
|
|
33171
33238
|
result.twoFactorAuth = "disabled";
|
|
33172
33239
|
}
|
|
33240
|
+
} else {
|
|
33241
|
+
result.twoFactorAuth = "fetch-failed";
|
|
33173
33242
|
}
|
|
33174
|
-
const tokensRes = await registryGetAuth("/-/npm/v1/tokens");
|
|
33175
33243
|
if (tokensRes.ok && tokensRes.data) {
|
|
33176
33244
|
const tokens = tokensRes.data.objects;
|
|
33177
33245
|
const hasReadWrite = tokens.some((t) => !t.readonly);
|
|
@@ -33182,6 +33250,10 @@ var workflowTools = [
|
|
|
33182
33250
|
result.canPublishHeadless = true;
|
|
33183
33251
|
result.tokenType = "any (2FA disabled)";
|
|
33184
33252
|
result.recommendation = "2FA is disabled \u2014 any valid token can publish. Consider enabling 2FA for security.";
|
|
33253
|
+
} else if (result.twoFactorAuth === "fetch-failed") {
|
|
33254
|
+
result.canPublishHeadless = null;
|
|
33255
|
+
result.tokenType = "unknown (profile fetch failed)";
|
|
33256
|
+
result.recommendation = "Could not determine 2FA status -- token may lack read permission on /-/npm/v1/user. Verify the token has at least read access and re-run npm_check_auth.";
|
|
33185
33257
|
} else {
|
|
33186
33258
|
result.tokenType = "unknown (tokens are redacted in API)";
|
|
33187
33259
|
result.canPublishHeadless = null;
|
|
@@ -33262,7 +33334,10 @@ var workflowTools = [
|
|
|
33262
33334
|
});
|
|
33263
33335
|
}
|
|
33264
33336
|
if (username) {
|
|
33265
|
-
const profileRes = await
|
|
33337
|
+
const [profileRes, tokensRes] = await Promise.all([
|
|
33338
|
+
registryGetAuth("/-/npm/v1/user"),
|
|
33339
|
+
registryGetAuth("/-/npm/v1/tokens")
|
|
33340
|
+
]);
|
|
33266
33341
|
if (profileRes.ok && profileRes.data) {
|
|
33267
33342
|
const tfa = profileRes.data.tfa;
|
|
33268
33343
|
if (tfa && !tfa.pending) {
|
|
@@ -33285,8 +33360,13 @@ var workflowTools = [
|
|
|
33285
33360
|
detail: "2FA is disabled. Any valid token can publish, but 2FA is strongly recommended for security."
|
|
33286
33361
|
});
|
|
33287
33362
|
}
|
|
33363
|
+
} else {
|
|
33364
|
+
checks.push({
|
|
33365
|
+
check: "2FA status",
|
|
33366
|
+
status: "warn",
|
|
33367
|
+
detail: "Could not determine 2FA status -- token may lack read permission on /-/npm/v1/user. Verify the token has at least read access and re-run npm_publish_preflight."
|
|
33368
|
+
});
|
|
33288
33369
|
}
|
|
33289
|
-
const tokensRes = await registryGetAuth("/-/npm/v1/tokens");
|
|
33290
33370
|
if (tokensRes.ok && tokensRes.data) {
|
|
33291
33371
|
const tokens = tokensRes.data.objects;
|
|
33292
33372
|
const totalTokens = tokensRes.data.total;
|
|
@@ -33433,15 +33513,8 @@ function parseTeamTarget(target) {
|
|
|
33433
33513
|
return { scope, team };
|
|
33434
33514
|
}
|
|
33435
33515
|
function highestVersion(versions) {
|
|
33436
|
-
const
|
|
33437
|
-
|
|
33438
|
-
if (v.includes("-")) continue;
|
|
33439
|
-
const m = v.match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
33440
|
-
if (m) parsed.push([Number(m[1]), Number(m[2]), Number(m[3]), v]);
|
|
33441
|
-
}
|
|
33442
|
-
if (parsed.length === 0) return null;
|
|
33443
|
-
parsed.sort((a, b) => a[0] - b[0] || a[1] - b[1] || a[2] - b[2]);
|
|
33444
|
-
return parsed[parsed.length - 1][3];
|
|
33516
|
+
const stable = versions.filter((v) => !v.includes("-"));
|
|
33517
|
+
return maxSatisfying(stable, "*");
|
|
33445
33518
|
}
|
|
33446
33519
|
var writeTools = [
|
|
33447
33520
|
// ───────────────────────────────────────────────────────
|
|
@@ -33459,7 +33532,7 @@ var writeTools = [
|
|
|
33459
33532
|
},
|
|
33460
33533
|
inputSchema: external_exports.object({
|
|
33461
33534
|
name: external_exports.string().describe("Package name (e.g. '@yawlabs/spend')"),
|
|
33462
|
-
message: external_exports.string().describe("Deprecation message.
|
|
33535
|
+
message: external_exports.string().describe("Deprecation message. Use npm_undeprecate to clear."),
|
|
33463
33536
|
versionRange: external_exports.string().optional().describe(
|
|
33464
33537
|
"Semver range. Omit to deprecate ALL versions. Example: '<1.0.0' or '0.3.x'. Standard semver applies \u2014 bare integers are x-ranges (e.g. '0' means '0.x.x', not exact version 0). For a single version use '=1.2.3'."
|
|
33465
33538
|
)
|
|
@@ -33469,46 +33542,50 @@ var writeTools = [
|
|
|
33469
33542
|
if (authErr) return authErr;
|
|
33470
33543
|
const problem = validateDeprecationMessage(input.message);
|
|
33471
33544
|
if (problem) return { ok: false, status: 400, error: problem };
|
|
33472
|
-
const pRes = await fetchPackument(input.name);
|
|
33473
|
-
if (!pRes.ok) return translateError(pRes, { pkg: input.name, op: "deprecate (fetch)" });
|
|
33474
|
-
const packument = pRes.data;
|
|
33475
|
-
const allVersions = Object.keys(packument.versions || {});
|
|
33476
33545
|
const range = input.versionRange ?? "*";
|
|
33477
|
-
|
|
33478
|
-
|
|
33479
|
-
return {
|
|
33480
|
-
|
|
33481
|
-
|
|
33482
|
-
|
|
33483
|
-
|
|
33484
|
-
|
|
33485
|
-
|
|
33486
|
-
|
|
33487
|
-
|
|
33488
|
-
|
|
33546
|
+
for (let attempt = 0; attempt <= 1; attempt++) {
|
|
33547
|
+
const pRes = await fetchPackument(input.name);
|
|
33548
|
+
if (!pRes.ok) return translateError(pRes, { pkg: input.name, op: "deprecate (fetch)" });
|
|
33549
|
+
const packument = pRes.data;
|
|
33550
|
+
const allVersions = Object.keys(packument.versions || {});
|
|
33551
|
+
const affected = versionsSatisfying(allVersions, range);
|
|
33552
|
+
if (affected.length === 0) {
|
|
33553
|
+
return {
|
|
33554
|
+
ok: false,
|
|
33555
|
+
status: 400,
|
|
33556
|
+
error: `No versions match range '${range}' for ${input.name}. Published versions: ${allVersions.join(", ") || "(none)"}.`
|
|
33557
|
+
};
|
|
33558
|
+
}
|
|
33559
|
+
if (!packument._rev) {
|
|
33560
|
+
return {
|
|
33561
|
+
ok: false,
|
|
33562
|
+
status: 500,
|
|
33563
|
+
error: `Packument for ${input.name} missing _rev -- cannot deprecate. Try again; this is usually transient.`
|
|
33564
|
+
};
|
|
33565
|
+
}
|
|
33566
|
+
for (const v of affected) {
|
|
33567
|
+
packument.versions[v].deprecated = input.message;
|
|
33568
|
+
}
|
|
33569
|
+
delete packument._revisions;
|
|
33570
|
+
delete packument._attachments;
|
|
33571
|
+
const putRes = await registryPutAuth(
|
|
33572
|
+
`/${encPkg(input.name)}/-rev/${encodeURIComponent(packument._rev)}`,
|
|
33573
|
+
packument
|
|
33574
|
+
);
|
|
33575
|
+
if (putRes.status === 409 && attempt === 0) continue;
|
|
33576
|
+
if (!putRes.ok) return translateError(putRes, { pkg: input.name, op: "deprecate (write)" });
|
|
33489
33577
|
return {
|
|
33490
|
-
ok:
|
|
33491
|
-
status:
|
|
33492
|
-
|
|
33578
|
+
ok: true,
|
|
33579
|
+
status: 200,
|
|
33580
|
+
data: {
|
|
33581
|
+
package: input.name,
|
|
33582
|
+
affectedVersions: affected,
|
|
33583
|
+
totalAffected: affected.length,
|
|
33584
|
+
message: input.message
|
|
33585
|
+
}
|
|
33493
33586
|
};
|
|
33494
33587
|
}
|
|
33495
|
-
|
|
33496
|
-
delete packument._attachments;
|
|
33497
|
-
const putRes = await registryPutAuth(
|
|
33498
|
-
`/${encPkg(input.name)}/-rev/${encodeURIComponent(packument._rev)}`,
|
|
33499
|
-
packument
|
|
33500
|
-
);
|
|
33501
|
-
if (!putRes.ok) return translateError(putRes, { pkg: input.name, op: "deprecate (write)" });
|
|
33502
|
-
return {
|
|
33503
|
-
ok: true,
|
|
33504
|
-
status: 200,
|
|
33505
|
-
data: {
|
|
33506
|
-
package: input.name,
|
|
33507
|
-
affectedVersions: affected,
|
|
33508
|
-
totalAffected: affected.length,
|
|
33509
|
-
message: input.message
|
|
33510
|
-
}
|
|
33511
|
-
};
|
|
33588
|
+
return { ok: false, status: 409, error: "Conflict after OCC retry -- try again." };
|
|
33512
33589
|
}
|
|
33513
33590
|
},
|
|
33514
33591
|
// ───────────────────────────────────────────────────────
|
|
@@ -33623,15 +33700,16 @@ var writeTools = [
|
|
|
33623
33700
|
return {
|
|
33624
33701
|
ok: false,
|
|
33625
33702
|
status: 500,
|
|
33626
|
-
error: `Packument for ${input.name} missing _rev
|
|
33703
|
+
error: `Packument for ${input.name} missing _rev -- cannot unpublish. Try again; this is usually transient.`
|
|
33627
33704
|
};
|
|
33628
33705
|
}
|
|
33629
33706
|
const tarballUrl = versionData.dist?.tarball;
|
|
33630
|
-
const
|
|
33707
|
+
const distTags = packument["dist-tags"] || {};
|
|
33708
|
+
const latestBefore = distTags.latest;
|
|
33631
33709
|
delete packument.versions[input.version];
|
|
33632
|
-
for (const tag of Object.keys(
|
|
33633
|
-
if (
|
|
33634
|
-
delete
|
|
33710
|
+
for (const tag of Object.keys(distTags)) {
|
|
33711
|
+
if (distTags[tag] === input.version) {
|
|
33712
|
+
delete distTags[tag];
|
|
33635
33713
|
}
|
|
33636
33714
|
}
|
|
33637
33715
|
if (latestBefore === input.version) {
|
|
@@ -33655,10 +33733,17 @@ var writeTools = [
|
|
|
33655
33733
|
const freshRev = freshRes.ok ? freshRes.data._rev : void 0;
|
|
33656
33734
|
if (freshRev) {
|
|
33657
33735
|
try {
|
|
33658
|
-
const
|
|
33659
|
-
const
|
|
33660
|
-
|
|
33661
|
-
|
|
33736
|
+
const tarballParsed = new URL(tarballUrl);
|
|
33737
|
+
const registryOrigin = new URL(
|
|
33738
|
+
(process.env.NPM_REGISTRY || "https://registry.npmjs.org").replace(/\/+$/, "")
|
|
33739
|
+
).origin;
|
|
33740
|
+
if (tarballParsed.origin !== registryOrigin) {
|
|
33741
|
+
tarballError = `tarball origin ${tarballParsed.origin} does not match registry origin ${registryOrigin} -- tarball DELETE skipped`;
|
|
33742
|
+
} else {
|
|
33743
|
+
const delRes = await registryDeleteAuth(`${tarballParsed.pathname}/-rev/${encodeURIComponent(freshRev)}`);
|
|
33744
|
+
tarballDeleted = delRes.ok;
|
|
33745
|
+
if (!delRes.ok) tarballError = delRes.error;
|
|
33746
|
+
}
|
|
33662
33747
|
} catch (err) {
|
|
33663
33748
|
tarballError = err instanceof Error ? err.message : String(err);
|
|
33664
33749
|
}
|
|
@@ -33715,7 +33800,7 @@ var writeTools = [
|
|
|
33715
33800
|
return {
|
|
33716
33801
|
ok: false,
|
|
33717
33802
|
status: 500,
|
|
33718
|
-
error: `Packument for ${input.name} missing _rev
|
|
33803
|
+
error: `Packument for ${input.name} missing _rev -- cannot unpublish.`
|
|
33719
33804
|
};
|
|
33720
33805
|
}
|
|
33721
33806
|
const delRes = await registryDeleteAuth(`/${encPkg(input.name)}/-rev/${encodeURIComponent(rev)}`);
|
|
@@ -33750,6 +33835,16 @@ var writeTools = [
|
|
|
33750
33835
|
if (authErr) return authErr;
|
|
33751
33836
|
const tagErr = validateTag(input.tag);
|
|
33752
33837
|
if (tagErr) return { ok: false, status: 400, error: tagErr };
|
|
33838
|
+
const pRes = await fetchPackument(input.name);
|
|
33839
|
+
if (!pRes.ok) return translateError(pRes, { pkg: input.name, op: `dist-tag set ${input.tag} (fetch)` });
|
|
33840
|
+
const packument = pRes.data;
|
|
33841
|
+
if (!packument.versions?.[input.version]) {
|
|
33842
|
+
return {
|
|
33843
|
+
ok: false,
|
|
33844
|
+
status: 404,
|
|
33845
|
+
error: `Version ${input.version} not found for ${input.name}. Published versions: ${Object.keys(packument.versions || {}).join(", ")}.`
|
|
33846
|
+
};
|
|
33847
|
+
}
|
|
33753
33848
|
const putRes = await registryPutAuth(
|
|
33754
33849
|
`/-/package/${encPkg(input.name)}/dist-tags/${encTag(input.tag)}`,
|
|
33755
33850
|
input.version
|
|
@@ -33855,7 +33950,7 @@ var writeTools = [
|
|
|
33855
33950
|
return {
|
|
33856
33951
|
ok: false,
|
|
33857
33952
|
status: 500,
|
|
33858
|
-
error: `Packument for ${input.name} missing _rev
|
|
33953
|
+
error: `Packument for ${input.name} missing _rev -- cannot update owners.`
|
|
33859
33954
|
};
|
|
33860
33955
|
}
|
|
33861
33956
|
const maintainers = [...owners, userRecord];
|
|
@@ -33902,14 +33997,14 @@ var writeTools = [
|
|
|
33902
33997
|
if (!pRes.ok) return translateError(pRes, { pkg: input.name, op: "owner_remove (fetch)" });
|
|
33903
33998
|
const packument = pRes.data;
|
|
33904
33999
|
const before = packument.maintainers || [];
|
|
33905
|
-
if (!before.some((m) => m.name === input.username)) {
|
|
34000
|
+
if (!before.some((m) => m.name.toLowerCase() === input.username.toLowerCase())) {
|
|
33906
34001
|
return {
|
|
33907
34002
|
ok: false,
|
|
33908
34003
|
status: 404,
|
|
33909
34004
|
error: `${input.username} is not a maintainer of ${input.name}. Current maintainers: ${before.map((m) => m.name).join(", ") || "(none)"}.`
|
|
33910
34005
|
};
|
|
33911
34006
|
}
|
|
33912
|
-
const after = before.filter((m) => m.name !== input.username);
|
|
34007
|
+
const after = before.filter((m) => m.name.toLowerCase() !== input.username.toLowerCase());
|
|
33913
34008
|
if (after.length === 0) {
|
|
33914
34009
|
return {
|
|
33915
34010
|
ok: false,
|
|
@@ -33921,7 +34016,7 @@ var writeTools = [
|
|
|
33921
34016
|
return {
|
|
33922
34017
|
ok: false,
|
|
33923
34018
|
status: 500,
|
|
33924
|
-
error: `Packument for ${input.name} missing _rev
|
|
34019
|
+
error: `Packument for ${input.name} missing _rev -- cannot update owners.`
|
|
33925
34020
|
};
|
|
33926
34021
|
}
|
|
33927
34022
|
const putRes = await registryPutAuth(`/${encPkg(input.name)}/-rev/${encodeURIComponent(packument._rev)}`, {
|
|
@@ -33946,7 +34041,7 @@ var writeTools = [
|
|
|
33946
34041
|
// ───────────────────────────────────────────────────────
|
|
33947
34042
|
{
|
|
33948
34043
|
name: "npm_access_set",
|
|
33949
|
-
description: "Set package access level: 'public'
|
|
34044
|
+
description: "Set package access level: 'public' or 'restricted' (private). 'private' is accepted as an alias for 'restricted' for ergonomics -- both map to the registry wire value 'restricted'. Unscoped packages are always public. Restricted access requires a paid npm account.",
|
|
33950
34045
|
annotations: {
|
|
33951
34046
|
title: "Set package access level",
|
|
33952
34047
|
readOnlyHint: false,
|
|
@@ -33956,17 +34051,18 @@ var writeTools = [
|
|
|
33956
34051
|
},
|
|
33957
34052
|
inputSchema: external_exports.object({
|
|
33958
34053
|
name: external_exports.string().describe("Package name"),
|
|
33959
|
-
access: external_exports.enum(["public", "private", "restricted"]).describe("Access level")
|
|
34054
|
+
access: external_exports.enum(["public", "private", "restricted"]).describe("Access level ('private' maps to 'restricted' on the wire)")
|
|
33960
34055
|
}),
|
|
33961
34056
|
handler: async (input) => {
|
|
33962
34057
|
const authErr = requireAuth();
|
|
33963
34058
|
if (authErr) return authErr;
|
|
33964
|
-
const
|
|
34059
|
+
const wireAccess = input.access === "private" ? "restricted" : input.access;
|
|
34060
|
+
const res = await registryPostAuth(`/-/package/${encPkg(input.name)}/access`, { access: wireAccess });
|
|
33965
34061
|
if (!res.ok) return translateError(res, { pkg: input.name, op: `access_set ${input.access}` });
|
|
33966
34062
|
return {
|
|
33967
34063
|
ok: true,
|
|
33968
34064
|
status: 200,
|
|
33969
|
-
data: { package: input.name, access:
|
|
34065
|
+
data: { package: input.name, access: wireAccess }
|
|
33970
34066
|
};
|
|
33971
34067
|
}
|
|
33972
34068
|
},
|
|
@@ -34022,12 +34118,16 @@ var writeTools = [
|
|
|
34022
34118
|
},
|
|
34023
34119
|
inputSchema: external_exports.object({
|
|
34024
34120
|
team: external_exports.string().describe("Team in the form '@scope:team' (e.g. '@yawlabs:devs')"),
|
|
34025
|
-
package: external_exports.string().describe(
|
|
34121
|
+
package: external_exports.string().describe(
|
|
34122
|
+
"Package name. Field is named 'package' to match the npm CLI (diverges from 'name' used elsewhere in this server)."
|
|
34123
|
+
),
|
|
34026
34124
|
permissions: external_exports.enum(["read-only", "read-write"]).describe("Permission level")
|
|
34027
34125
|
}),
|
|
34028
34126
|
handler: async (input) => {
|
|
34029
34127
|
const authErr = requireAuth();
|
|
34030
34128
|
if (authErr) return authErr;
|
|
34129
|
+
const pkgErr = validatePackageName(input.package);
|
|
34130
|
+
if (pkgErr) return { ok: false, status: 400, error: pkgErr };
|
|
34031
34131
|
const parsed = parseTeamTarget(input.team);
|
|
34032
34132
|
if ("error" in parsed) {
|
|
34033
34133
|
return { ok: false, status: 400, error: parsed.error };
|
|
@@ -34060,11 +34160,15 @@ var writeTools = [
|
|
|
34060
34160
|
},
|
|
34061
34161
|
inputSchema: external_exports.object({
|
|
34062
34162
|
team: external_exports.string().describe("Team in the form '@scope:team'"),
|
|
34063
|
-
package: external_exports.string().describe(
|
|
34163
|
+
package: external_exports.string().describe(
|
|
34164
|
+
"Package name. Field is named 'package' to match the npm CLI (diverges from 'name' used elsewhere in this server)."
|
|
34165
|
+
)
|
|
34064
34166
|
}),
|
|
34065
34167
|
handler: async (input) => {
|
|
34066
34168
|
const authErr = requireAuth();
|
|
34067
34169
|
if (authErr) return authErr;
|
|
34170
|
+
const pkgErr = validatePackageName(input.package);
|
|
34171
|
+
if (pkgErr) return { ok: false, status: 400, error: pkgErr };
|
|
34068
34172
|
const parsed = parseTeamTarget(input.team);
|
|
34069
34173
|
if ("error" in parsed) {
|
|
34070
34174
|
return { ok: false, status: 400, error: parsed.error };
|
|
@@ -34235,22 +34339,30 @@ var writeTools = [
|
|
|
34235
34339
|
inputSchema: external_exports.object({
|
|
34236
34340
|
org: external_exports.string().describe("Organization name (with or without leading @)"),
|
|
34237
34341
|
user: external_exports.string().describe("npm username"),
|
|
34238
|
-
role: external_exports.enum(["developer", "admin", "owner"]).optional().describe("Role to assign")
|
|
34342
|
+
role: external_exports.enum(["developer", "admin", "owner"]).optional().describe("Role to assign"),
|
|
34343
|
+
confirm: external_exports.literal(true).describe("Must be literally true. Guards against accidental org membership changes.")
|
|
34239
34344
|
}),
|
|
34240
34345
|
handler: async (input) => {
|
|
34241
34346
|
const authErr = requireAuth();
|
|
34242
34347
|
if (authErr) return authErr;
|
|
34348
|
+
if (input.confirm !== true) {
|
|
34349
|
+
return {
|
|
34350
|
+
ok: false,
|
|
34351
|
+
status: 400,
|
|
34352
|
+
error: "org_member_set requires confirm: true. This adds or changes a user's org role."
|
|
34353
|
+
};
|
|
34354
|
+
}
|
|
34243
34355
|
const orgErr = validateScope(input.org);
|
|
34244
34356
|
if (orgErr) return { ok: false, status: 400, error: orgErr };
|
|
34245
34357
|
const userErr = validateUsername(input.user);
|
|
34246
34358
|
if (userErr) return { ok: false, status: 400, error: userErr };
|
|
34247
|
-
const
|
|
34248
|
-
const
|
|
34249
|
-
const body = { user };
|
|
34359
|
+
const userBare = input.user.replace(/^@/, "");
|
|
34360
|
+
const orgBare = input.org.replace(/^@/, "");
|
|
34361
|
+
const body = { user: userBare };
|
|
34250
34362
|
if (input.role) body.role = input.role;
|
|
34251
|
-
const res = await registryPutAuth(`/-/org/${encScope(org)}/user`, body);
|
|
34252
|
-
if (!res.ok) return translateError(res, { op: `org_member_set ${
|
|
34253
|
-
const data = { org, user };
|
|
34363
|
+
const res = await registryPutAuth(`/-/org/${encScope(input.org)}/user`, body);
|
|
34364
|
+
if (!res.ok) return translateError(res, { op: `org_member_set ${orgBare}/${userBare}` });
|
|
34365
|
+
const data = { org: orgBare, user: userBare };
|
|
34254
34366
|
if (input.role) data.role = input.role;
|
|
34255
34367
|
return { ok: true, status: 200, data };
|
|
34256
34368
|
}
|
|
@@ -34287,11 +34399,11 @@ var writeTools = [
|
|
|
34287
34399
|
if (orgErr) return { ok: false, status: 400, error: orgErr };
|
|
34288
34400
|
const userErr = validateUsername(input.user);
|
|
34289
34401
|
if (userErr) return { ok: false, status: 400, error: userErr };
|
|
34290
|
-
const
|
|
34291
|
-
const
|
|
34292
|
-
const res = await registryDeleteAuth(`/-/org/${encScope(org)}/user`, { user });
|
|
34293
|
-
if (!res.ok) return translateError(res, { op: `org_member_remove ${
|
|
34294
|
-
return { ok: true, status: 200, data: { org, removedUser:
|
|
34402
|
+
const orgBare = input.org.replace(/^@/, "");
|
|
34403
|
+
const userBare = input.user.replace(/^@/, "");
|
|
34404
|
+
const res = await registryDeleteAuth(`/-/org/${encScope(input.org)}/user`, { user: userBare });
|
|
34405
|
+
if (!res.ok) return translateError(res, { op: `org_member_remove ${orgBare}/${userBare}` });
|
|
34406
|
+
return { ok: true, status: 200, data: { org: orgBare, removedUser: userBare } };
|
|
34295
34407
|
}
|
|
34296
34408
|
},
|
|
34297
34409
|
// ───────────────────────────────────────────────────────
|
|
@@ -34323,6 +34435,13 @@ var writeTools = [
|
|
|
34323
34435
|
error: "token_revoke requires confirm: true. Revoking the token in NPM_TOKEN will break the next call."
|
|
34324
34436
|
};
|
|
34325
34437
|
}
|
|
34438
|
+
if (!input.tokenKey || !/^[0-9a-f-]{8,}$/i.test(input.tokenKey)) {
|
|
34439
|
+
return {
|
|
34440
|
+
ok: false,
|
|
34441
|
+
status: 400,
|
|
34442
|
+
error: `Invalid token key '${input.tokenKey}'. Expected a UUID (hex characters and dashes, at least 8 characters). Use npm_tokens to list valid keys.`
|
|
34443
|
+
};
|
|
34444
|
+
}
|
|
34326
34445
|
const res = await registryDeleteAuth(`/-/npm/v1/tokens/token/${encodeURIComponent(input.tokenKey)}`);
|
|
34327
34446
|
if (!res.ok) return translateError(res, { op: "token_revoke" });
|
|
34328
34447
|
return { ok: true, status: 200, data: { tokenKey: input.tokenKey, revoked: true } };
|
|
@@ -34331,7 +34450,7 @@ var writeTools = [
|
|
|
34331
34450
|
];
|
|
34332
34451
|
|
|
34333
34452
|
// src/index.ts
|
|
34334
|
-
var version2 = true ? "0.12.
|
|
34453
|
+
var version2 = true ? "0.12.1" : (await null).createRequire(import.meta.url)("../package.json").version;
|
|
34335
34454
|
var subcommand = process.argv[2];
|
|
34336
34455
|
if (subcommand === "version" || subcommand === "--version" || subcommand === "-v" || subcommand === "-V") {
|
|
34337
34456
|
console.log(version2);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yawlabs/npmjs-mcp",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"mcpName": "io.github.YawLabs/npmjs-mcp",
|
|
5
5
|
"description": "npm registry MCP server — package intelligence, security audits, and dependency analysis for AI assistants",
|
|
6
6
|
"license": "MIT",
|