@yawlabs/npmjs-mcp 0.4.0 → 0.6.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 +33 -5
- package/dist/index.js +622 -76
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -65,7 +65,7 @@ Add to `claude_desktop_config.json`:
|
|
|
65
65
|
}
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
## Tools (
|
|
68
|
+
## Tools (46)
|
|
69
69
|
|
|
70
70
|
### Search
|
|
71
71
|
- `npm_search` — Search the npm registry with qualifiers (keywords, author, scope)
|
|
@@ -76,6 +76,7 @@ Add to `claude_desktop_config.json`:
|
|
|
76
76
|
- `npm_versions` — List all published versions with dates
|
|
77
77
|
- `npm_readme` — Get README content
|
|
78
78
|
- `npm_dist_tags` — Get dist-tags (latest, next, beta, etc)
|
|
79
|
+
- `npm_types` — Check TypeScript type support (built-in types or @types/*)
|
|
79
80
|
|
|
80
81
|
### Dependencies
|
|
81
82
|
- `npm_dependencies` — Get dependency lists (prod, dev, peer, optional)
|
|
@@ -102,14 +103,20 @@ Add to `claude_desktop_config.json`:
|
|
|
102
103
|
### Registry
|
|
103
104
|
- `npm_registry_stats` — Total npm-wide download counts
|
|
104
105
|
- `npm_recent_changes` — Recent package publishes from the CouchDB changes feed
|
|
106
|
+
- `npm_ops_playbook` — Canonical recipes for npm operations (call this FIRST when unsure which tool to use)
|
|
105
107
|
|
|
106
108
|
### Provenance
|
|
107
109
|
- `npm_provenance` — Get Sigstore provenance attestations (SLSA, publish)
|
|
108
110
|
|
|
111
|
+
### Trusted Publishers (requires NPM_TOKEN)
|
|
112
|
+
- `npm_trusted_publishers` — List OIDC trust relationships with CI/CD providers
|
|
113
|
+
|
|
109
114
|
### Auth (requires NPM_TOKEN)
|
|
110
115
|
- `npm_whoami` — Check authenticated user
|
|
111
116
|
- `npm_profile` — Get profile, email, 2FA status
|
|
112
117
|
- `npm_tokens` — List access tokens
|
|
118
|
+
- `npm_verify_token` — One-call capability check (call this FIRST when debugging write failures)
|
|
119
|
+
- `npm_user_packages` — List packages published by a user
|
|
113
120
|
|
|
114
121
|
### Access (requires NPM_TOKEN)
|
|
115
122
|
- `npm_collaborators` — List package collaborators and permissions
|
|
@@ -121,16 +128,37 @@ Add to `claude_desktop_config.json`:
|
|
|
121
128
|
- `npm_org_teams` — List org teams
|
|
122
129
|
- `npm_team_packages` — List team package permissions
|
|
123
130
|
|
|
124
|
-
### Hooks (requires NPM_TOKEN)
|
|
125
|
-
- `npm_hooks` — List npm webhooks
|
|
126
|
-
|
|
127
131
|
### Workflows
|
|
128
132
|
- `npm_check_auth` — Auth health check with headless publish feasibility
|
|
129
133
|
- `npm_publish_preflight` — Pre-publish validation checklist
|
|
130
134
|
|
|
135
|
+
### Write Operations (requires NPM_TOKEN with write scope)
|
|
136
|
+
|
|
137
|
+
These bypass the CLI/2FA friction that causes `npm deprecate` and similar commands to 422 locally. All use the HTTP API with your `NPM_TOKEN`.
|
|
138
|
+
|
|
139
|
+
- `npm_deprecate` — Deprecate a package or specific versions (validates message format)
|
|
140
|
+
- `npm_undeprecate` — Clear deprecation
|
|
141
|
+
- `npm_unpublish_version` — Unpublish a specific version (requires `confirm: true`)
|
|
142
|
+
- `npm_dist_tag_set` — Point a dist-tag at a version
|
|
143
|
+
- `npm_dist_tag_remove` — Remove a dist-tag (except `latest`)
|
|
144
|
+
- `npm_owner_add` — Add a maintainer
|
|
145
|
+
- `npm_owner_remove` — Remove a maintainer (prevents lockout)
|
|
146
|
+
|
|
147
|
+
### Operation Decision Matrix
|
|
148
|
+
|
|
149
|
+
| Operation | Preferred path | Why |
|
|
150
|
+
|---|---|---|
|
|
151
|
+
| Read (search/view/stats) | These MCP tools, no auth required | Fast, zero friction |
|
|
152
|
+
| Deprecate / dist-tag / owner | `npm_deprecate`, `npm_dist_tag_*`, `npm_owner_*` | HTTP API, no CLI auth issues |
|
|
153
|
+
| Publish | CI tag-push workflow | Version discipline, provenance, org token |
|
|
154
|
+
| Unpublish | `npm_unpublish_version` (with `confirm: true`) | Safer than CLI; irreversible within 72h |
|
|
155
|
+
| CLI fallback (only if MCP returns 422) | `npm login --auth-type=web` then `npm <op>` | End-user interactive path |
|
|
156
|
+
|
|
157
|
+
Call `npm_ops_playbook` at the start of any session for the up-to-date matrix.
|
|
158
|
+
|
|
131
159
|
## Features
|
|
132
160
|
|
|
133
|
-
- **
|
|
161
|
+
- **37 tools** covering search, packages, deps, downloads, security, analysis, auth, orgs, provenance, trust, and publish workflows
|
|
134
162
|
- **No API key required** for read-only tools — authenticated tools opt-in via NPM_TOKEN
|
|
135
163
|
- **Zero runtime dependencies** — Single bundled file for instant `npx` startup
|
|
136
164
|
- **Agent-aware publish tools** — Detects non-interactive context, provides human hand-off actions instead of unworkable retries
|
package/dist/index.js
CHANGED
|
@@ -21081,12 +21081,35 @@ function registryPost(path, body) {
|
|
|
21081
21081
|
function registryGetAuth(path) {
|
|
21082
21082
|
return request(REGISTRY_URL, path, { headers: authHeaders() });
|
|
21083
21083
|
}
|
|
21084
|
+
function registryPutAuth(path, body) {
|
|
21085
|
+
return request(REGISTRY_URL, path, { method: "PUT", body, headers: authHeaders() });
|
|
21086
|
+
}
|
|
21087
|
+
function registryDeleteAuth(path) {
|
|
21088
|
+
return request(REGISTRY_URL, path, { method: "DELETE", headers: authHeaders() });
|
|
21089
|
+
}
|
|
21084
21090
|
function downloadsGet(path) {
|
|
21085
21091
|
return request(DOWNLOADS_URL, path);
|
|
21086
21092
|
}
|
|
21087
21093
|
function replicateGet(path) {
|
|
21088
21094
|
return request(REPLICATE_URL, path);
|
|
21089
21095
|
}
|
|
21096
|
+
function createLimiter(max) {
|
|
21097
|
+
let active = 0;
|
|
21098
|
+
const queue = [];
|
|
21099
|
+
return function runLimited(fn) {
|
|
21100
|
+
return new Promise((resolve, reject) => {
|
|
21101
|
+
const run = () => {
|
|
21102
|
+
active++;
|
|
21103
|
+
fn().then(resolve, reject).finally(() => {
|
|
21104
|
+
active--;
|
|
21105
|
+
if (queue.length > 0) queue.shift()();
|
|
21106
|
+
});
|
|
21107
|
+
};
|
|
21108
|
+
if (active < max) run();
|
|
21109
|
+
else queue.push(run);
|
|
21110
|
+
});
|
|
21111
|
+
};
|
|
21112
|
+
}
|
|
21090
21113
|
function parseSemver(v) {
|
|
21091
21114
|
const m = v.match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
21092
21115
|
return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
|
|
@@ -21098,63 +21121,109 @@ function cmpSemver(a, b) {
|
|
|
21098
21121
|
}
|
|
21099
21122
|
return 0;
|
|
21100
21123
|
}
|
|
21101
|
-
function
|
|
21102
|
-
|
|
21103
|
-
|
|
21104
|
-
|
|
21105
|
-
let maxExclusive = null;
|
|
21124
|
+
function parseSingleConstraint(r) {
|
|
21125
|
+
if (r === "*" || r === "") {
|
|
21126
|
+
return { min: null, max: null };
|
|
21127
|
+
}
|
|
21106
21128
|
if (r.startsWith("^")) {
|
|
21107
21129
|
const base = parseSemver(r.slice(1));
|
|
21108
21130
|
if (!base) return null;
|
|
21109
|
-
|
|
21110
|
-
if (base[0] > 0)
|
|
21111
|
-
else if (base[1] > 0)
|
|
21112
|
-
else
|
|
21113
|
-
|
|
21131
|
+
let max;
|
|
21132
|
+
if (base[0] > 0) max = [base[0] + 1, 0, 0];
|
|
21133
|
+
else if (base[1] > 0) max = [0, base[1] + 1, 0];
|
|
21134
|
+
else max = [0, 0, base[2] + 1];
|
|
21135
|
+
return { min: base, max };
|
|
21136
|
+
}
|
|
21137
|
+
if (r.startsWith("~")) {
|
|
21114
21138
|
const base = parseSemver(r.slice(1));
|
|
21115
21139
|
if (!base) return null;
|
|
21116
|
-
|
|
21117
|
-
|
|
21118
|
-
|
|
21140
|
+
return { min: base, max: [base[0], base[1] + 1, 0] };
|
|
21141
|
+
}
|
|
21142
|
+
if (r.startsWith(">=")) {
|
|
21119
21143
|
const base = parseSemver(r.slice(2));
|
|
21120
21144
|
if (!base) return null;
|
|
21121
|
-
|
|
21122
|
-
}
|
|
21145
|
+
return { min: base, max: null };
|
|
21146
|
+
}
|
|
21147
|
+
if (r.startsWith(">")) {
|
|
21148
|
+
const base = parseSemver(r.slice(1));
|
|
21149
|
+
if (!base) return null;
|
|
21150
|
+
return { min: [base[0], base[1], base[2] + 1], max: null };
|
|
21151
|
+
}
|
|
21152
|
+
if (r.startsWith("<=")) {
|
|
21123
21153
|
const base = parseSemver(r.slice(2));
|
|
21124
21154
|
if (!base) return null;
|
|
21125
|
-
|
|
21126
|
-
}
|
|
21127
|
-
|
|
21128
|
-
|
|
21129
|
-
|
|
21130
|
-
|
|
21131
|
-
|
|
21132
|
-
|
|
21133
|
-
|
|
21134
|
-
|
|
21135
|
-
|
|
21136
|
-
|
|
21137
|
-
|
|
21138
|
-
|
|
21139
|
-
|
|
21140
|
-
|
|
21141
|
-
|
|
21155
|
+
return { min: null, max: [base[0], base[1], base[2] + 1] };
|
|
21156
|
+
}
|
|
21157
|
+
if (r.startsWith("<")) {
|
|
21158
|
+
const base = parseSemver(r.slice(1));
|
|
21159
|
+
if (!base) return null;
|
|
21160
|
+
return { min: null, max: base };
|
|
21161
|
+
}
|
|
21162
|
+
if (r.startsWith("=")) {
|
|
21163
|
+
const base = parseSemver(r.slice(1));
|
|
21164
|
+
if (!base) return null;
|
|
21165
|
+
return { min: base, max: [base[0], base[1], base[2] + 1] };
|
|
21166
|
+
}
|
|
21167
|
+
const xm = r.match(/^(\d+)(?:\.(\d+|x|\*)(?:\.(\d+|x|\*))?)?$/);
|
|
21168
|
+
if (xm) {
|
|
21169
|
+
const major = Number(xm[1]);
|
|
21170
|
+
const minor = xm[2] !== void 0 && xm[2] !== "x" && xm[2] !== "*" ? Number(xm[2]) : null;
|
|
21171
|
+
if (minor === null) {
|
|
21172
|
+
return { min: [major, 0, 0], max: [major + 1, 0, 0] };
|
|
21173
|
+
}
|
|
21174
|
+
const patch = xm[3] !== void 0 && xm[3] !== "x" && xm[3] !== "*" ? Number(xm[3]) : null;
|
|
21175
|
+
if (patch === null) {
|
|
21176
|
+
return { min: [major, minor, 0], max: [major, minor + 1, 0] };
|
|
21177
|
+
}
|
|
21178
|
+
return null;
|
|
21179
|
+
}
|
|
21180
|
+
return null;
|
|
21181
|
+
}
|
|
21182
|
+
function parseRange(r) {
|
|
21183
|
+
const hyphenMatch = r.match(/^(\d+\.\d+\.\d+)\s+-\s+(\d+\.\d+\.\d+)$/);
|
|
21184
|
+
if (hyphenMatch) {
|
|
21185
|
+
const low = parseSemver(hyphenMatch[1]);
|
|
21186
|
+
const high = parseSemver(hyphenMatch[2]);
|
|
21187
|
+
if (low && high) return { min: low, max: [high[0], high[1], high[2] + 1] };
|
|
21188
|
+
return null;
|
|
21189
|
+
}
|
|
21190
|
+
const parts = r.trim().split(/\s+/);
|
|
21191
|
+
if (parts.length > 1) {
|
|
21192
|
+
let min = null;
|
|
21193
|
+
let max = null;
|
|
21194
|
+
for (const part of parts) {
|
|
21195
|
+
const constraint = parseSingleConstraint(part);
|
|
21196
|
+
if (!constraint) return null;
|
|
21197
|
+
if (constraint.min) {
|
|
21198
|
+
if (!min || cmpSemver(constraint.min, min) > 0) min = constraint.min;
|
|
21199
|
+
}
|
|
21200
|
+
if (constraint.max) {
|
|
21201
|
+
if (!max || cmpSemver(constraint.max, max) < 0) max = constraint.max;
|
|
21142
21202
|
}
|
|
21143
|
-
} else {
|
|
21144
|
-
return null;
|
|
21145
21203
|
}
|
|
21204
|
+
return { min, max };
|
|
21146
21205
|
}
|
|
21206
|
+
return parseSingleConstraint(r.trim());
|
|
21207
|
+
}
|
|
21208
|
+
function maxSatisfying(versions, range) {
|
|
21209
|
+
const r = range.trim().replace(/^v/, "");
|
|
21210
|
+
if (versions.includes(r)) return r;
|
|
21211
|
+
const subRanges = r.split("||").map((s) => s.trim());
|
|
21147
21212
|
let best = null;
|
|
21148
21213
|
let bestParsed = null;
|
|
21149
|
-
for (const
|
|
21150
|
-
|
|
21151
|
-
const parsed = parseSemver(v);
|
|
21214
|
+
for (const sub of subRanges) {
|
|
21215
|
+
const parsed = parseRange(sub);
|
|
21152
21216
|
if (!parsed) continue;
|
|
21153
|
-
|
|
21154
|
-
|
|
21155
|
-
|
|
21156
|
-
|
|
21157
|
-
|
|
21217
|
+
for (const v of versions) {
|
|
21218
|
+
if (v.includes("-") && !sub.includes("-")) continue;
|
|
21219
|
+
const vp = parseSemver(v);
|
|
21220
|
+
if (!vp) continue;
|
|
21221
|
+
if (parsed.min && cmpSemver(vp, parsed.min) < 0) continue;
|
|
21222
|
+
if (parsed.max && cmpSemver(vp, parsed.max) >= 0) continue;
|
|
21223
|
+
if (!bestParsed || cmpSemver(vp, bestParsed) > 0) {
|
|
21224
|
+
best = v;
|
|
21225
|
+
bestParsed = vp;
|
|
21226
|
+
}
|
|
21158
21227
|
}
|
|
21159
21228
|
}
|
|
21160
21229
|
return best;
|
|
@@ -21553,6 +21622,43 @@ var authTools = [
|
|
|
21553
21622
|
};
|
|
21554
21623
|
}
|
|
21555
21624
|
},
|
|
21625
|
+
{
|
|
21626
|
+
name: "npm_verify_token",
|
|
21627
|
+
description: "Verify the NPM_TOKEN and surface its capabilities \u2014 username, 2FA status, and whether writes are likely to succeed. Call this FIRST when debugging any write failure to rule out auth issues before trying other fixes. Faster than running writes and interpreting 401/403 errors.",
|
|
21628
|
+
annotations: {
|
|
21629
|
+
title: "Verify token capabilities",
|
|
21630
|
+
readOnlyHint: true,
|
|
21631
|
+
destructiveHint: false,
|
|
21632
|
+
idempotentHint: true,
|
|
21633
|
+
openWorldHint: true
|
|
21634
|
+
},
|
|
21635
|
+
inputSchema: external_exports.object({}),
|
|
21636
|
+
handler: async () => {
|
|
21637
|
+
const authErr = requireAuth();
|
|
21638
|
+
if (authErr) return authErr;
|
|
21639
|
+
const [whoami, profile] = await Promise.all([
|
|
21640
|
+
registryGetAuth("/-/whoami"),
|
|
21641
|
+
registryGetAuth("/-/npm/v1/user")
|
|
21642
|
+
]);
|
|
21643
|
+
if (!whoami.ok) {
|
|
21644
|
+
return {
|
|
21645
|
+
...whoami,
|
|
21646
|
+
error: `Token failed /-/whoami check. Token is invalid, expired, or revoked. Create a new one at https://www.npmjs.com/settings/~/tokens. Raw: ${whoami.error}`
|
|
21647
|
+
};
|
|
21648
|
+
}
|
|
21649
|
+
const tfa = profile.ok && profile.data?.tfa ? { enabled: true, mode: profile.data.tfa.mode } : { enabled: false };
|
|
21650
|
+
return {
|
|
21651
|
+
ok: true,
|
|
21652
|
+
status: 200,
|
|
21653
|
+
data: {
|
|
21654
|
+
username: whoami.data?.username,
|
|
21655
|
+
tokenValid: true,
|
|
21656
|
+
tfa,
|
|
21657
|
+
hint: "For write ops, token must have 'Read and write' scope on the target package. Granular Access Tokens require 2FA for writes; Classic Automation tokens bypass 2FA. Check your token's scope at https://www.npmjs.com/settings/~/tokens if writes return 401 or 403."
|
|
21658
|
+
}
|
|
21659
|
+
};
|
|
21660
|
+
}
|
|
21661
|
+
},
|
|
21556
21662
|
{
|
|
21557
21663
|
name: "npm_user_packages",
|
|
21558
21664
|
description: "List all packages published by a specific npm user. Shows package names and the user's access level for each. Requires authentication.",
|
|
@@ -21641,26 +21747,11 @@ var dependencyTools = [
|
|
|
21641
21747
|
}),
|
|
21642
21748
|
handler: async (input) => {
|
|
21643
21749
|
const maxDepth = input.depth ?? 3;
|
|
21644
|
-
const
|
|
21750
|
+
const runLimited = createLimiter(10);
|
|
21645
21751
|
const packumentCache = /* @__PURE__ */ new Map();
|
|
21646
21752
|
const resolved = /* @__PURE__ */ new Set();
|
|
21647
21753
|
const tree = {};
|
|
21648
21754
|
const warnings = [];
|
|
21649
|
-
let active = 0;
|
|
21650
|
-
const queue = [];
|
|
21651
|
-
function runLimited(fn) {
|
|
21652
|
-
return new Promise((resolve2, reject) => {
|
|
21653
|
-
const run = () => {
|
|
21654
|
-
active++;
|
|
21655
|
-
fn().then(resolve2, reject).finally(() => {
|
|
21656
|
-
active--;
|
|
21657
|
-
if (queue.length > 0) queue.shift()();
|
|
21658
|
-
});
|
|
21659
|
-
};
|
|
21660
|
-
if (active < MAX_CONCURRENT) run();
|
|
21661
|
-
else queue.push(run);
|
|
21662
|
-
});
|
|
21663
|
-
}
|
|
21664
21755
|
async function resolve(name, versionHint2, currentDepth) {
|
|
21665
21756
|
const hintKey = `${name}@${versionHint2}`;
|
|
21666
21757
|
if (resolved.has(hintKey) || currentDepth > maxDepth) return;
|
|
@@ -21739,22 +21830,7 @@ var dependencyTools = [
|
|
|
21739
21830
|
if (!res.ok) return res;
|
|
21740
21831
|
const pkg = res.data;
|
|
21741
21832
|
const depEntries = Object.entries(pkg.dependencies ?? {});
|
|
21742
|
-
const
|
|
21743
|
-
let active = 0;
|
|
21744
|
-
const queue = [];
|
|
21745
|
-
function runLimited(fn) {
|
|
21746
|
-
return new Promise((resolve, reject) => {
|
|
21747
|
-
const run = () => {
|
|
21748
|
-
active++;
|
|
21749
|
-
fn().then(resolve, reject).finally(() => {
|
|
21750
|
-
active--;
|
|
21751
|
-
if (queue.length > 0) queue.shift()();
|
|
21752
|
-
});
|
|
21753
|
-
};
|
|
21754
|
-
if (active < MAX_CONCURRENT) run();
|
|
21755
|
-
else queue.push(run);
|
|
21756
|
-
});
|
|
21757
|
-
}
|
|
21833
|
+
const runLimited = createLimiter(10);
|
|
21758
21834
|
const depLicenses = await Promise.all(
|
|
21759
21835
|
depEntries.map(async ([depName, depRange]) => {
|
|
21760
21836
|
const abbrevRes = await runLimited(() => registryGetAbbreviated(`/${encPkg(depName)}`));
|
|
@@ -22324,6 +22400,72 @@ var registryTools = [
|
|
|
22324
22400
|
}
|
|
22325
22401
|
};
|
|
22326
22402
|
}
|
|
22403
|
+
},
|
|
22404
|
+
{
|
|
22405
|
+
name: "npm_ops_playbook",
|
|
22406
|
+
description: "Return canonical recipes for common npm operations \u2014 which MCP tool to call for which op, CLI fallbacks when the MCP server can't handle something, and message format guidance. Call this FIRST when you're not sure how to do an npm operation. Prevents reinventing approaches that don't work.",
|
|
22407
|
+
annotations: {
|
|
22408
|
+
title: "npm operations playbook",
|
|
22409
|
+
readOnlyHint: true,
|
|
22410
|
+
destructiveHint: false,
|
|
22411
|
+
idempotentHint: true,
|
|
22412
|
+
openWorldHint: false
|
|
22413
|
+
},
|
|
22414
|
+
inputSchema: external_exports.object({}),
|
|
22415
|
+
handler: async () => ({
|
|
22416
|
+
ok: true,
|
|
22417
|
+
status: 200,
|
|
22418
|
+
data: {
|
|
22419
|
+
read: {
|
|
22420
|
+
search: "mcp_tool: npm_search",
|
|
22421
|
+
view: "mcp_tool: npm_package",
|
|
22422
|
+
downloads: "mcp_tool: npm_downloads",
|
|
22423
|
+
securityAudit: "mcp_tool: npm_audit",
|
|
22424
|
+
auth: "none required"
|
|
22425
|
+
},
|
|
22426
|
+
write: {
|
|
22427
|
+
deprecate: {
|
|
22428
|
+
tool: "mcp_tool: npm_deprecate",
|
|
22429
|
+
requiresNpmToken: true,
|
|
22430
|
+
messageFormat: {
|
|
22431
|
+
preferred: "Renamed to @scope/pkg \u2014 install that instead",
|
|
22432
|
+
avoid: "Renamed to @scope/pkg. Install that instead.",
|
|
22433
|
+
note: "Period-capital form has triggered 422 on at least one scoped package; use em-dash."
|
|
22434
|
+
}
|
|
22435
|
+
},
|
|
22436
|
+
undeprecate: "mcp_tool: npm_undeprecate",
|
|
22437
|
+
unpublishVersion: "mcp_tool: npm_unpublish_version (requires confirm: true)",
|
|
22438
|
+
distTag: "mcp_tool: npm_dist_tag_set / npm_dist_tag_remove",
|
|
22439
|
+
owner: "mcp_tool: npm_owner_add / npm_owner_remove"
|
|
22440
|
+
},
|
|
22441
|
+
publish: {
|
|
22442
|
+
method: "CI tag-push",
|
|
22443
|
+
neverRunLocally: true,
|
|
22444
|
+
steps: [
|
|
22445
|
+
"Bump version in package.json",
|
|
22446
|
+
"git add package.json && git commit -m 'vX.Y.Z'",
|
|
22447
|
+
"git tag vX.Y.Z",
|
|
22448
|
+
"git push origin main --follow-tags",
|
|
22449
|
+
"gh run list --limit 1 to confirm CI published"
|
|
22450
|
+
],
|
|
22451
|
+
why: "Local npm publish bypasses version discipline and often fails on 2FA. CI uses repo-level NPM_TOKEN."
|
|
22452
|
+
},
|
|
22453
|
+
auth: {
|
|
22454
|
+
verifyToken: "mcp_tool: npm_verify_token (first step when debugging write failures)",
|
|
22455
|
+
envVar: "NPM_TOKEN",
|
|
22456
|
+
tokenTypes: {
|
|
22457
|
+
granularAccess: "Requires 2FA for writes. Most common.",
|
|
22458
|
+
classicAutomation: "Bypasses 2FA. Ideal for CI.",
|
|
22459
|
+
classicPublish: "Requires 2FA for writes."
|
|
22460
|
+
}
|
|
22461
|
+
},
|
|
22462
|
+
cliFallback: {
|
|
22463
|
+
when: "When an MCP write op returns 422 despite valid token (rare, account-level 2FA policy)",
|
|
22464
|
+
sequence: ["npm login --auth-type=web", "npm <deprecate|unpublish|dist-tag> <args>"],
|
|
22465
|
+
who: "End user runs in their terminal \u2014 MCP server cannot initiate browser auth."
|
|
22466
|
+
}
|
|
22467
|
+
}
|
|
22468
|
+
})
|
|
22327
22469
|
}
|
|
22328
22470
|
];
|
|
22329
22471
|
|
|
@@ -22762,8 +22904,411 @@ var workflowTools = [
|
|
|
22762
22904
|
}
|
|
22763
22905
|
];
|
|
22764
22906
|
|
|
22907
|
+
// src/errors.ts
|
|
22908
|
+
function translateError(res, context) {
|
|
22909
|
+
if (res.ok) return res;
|
|
22910
|
+
const pkgPart = context.pkg ? ` for ${context.pkg}` : "";
|
|
22911
|
+
const opPart = context.op ? ` during ${context.op}` : "";
|
|
22912
|
+
switch (res.status) {
|
|
22913
|
+
case 401:
|
|
22914
|
+
return {
|
|
22915
|
+
...res,
|
|
22916
|
+
error: `Authentication failed${pkgPart}${opPart}. Your NPM_TOKEN may be invalid, expired, or lack write scope. Create a Granular Access Token with 'Read and write' permission at https://www.npmjs.com/settings/~/tokens, or use a classic Automation token (which bypasses 2FA). Raw: ${res.error}`
|
|
22917
|
+
};
|
|
22918
|
+
case 403:
|
|
22919
|
+
return {
|
|
22920
|
+
...res,
|
|
22921
|
+
error: `Not authorized${pkgPart}${opPart}. You may not be a maintainer of this package, or the token's scope doesn't include it. Check current maintainers with npm_collaborators or npm_package_access. Raw: ${res.error}`
|
|
22922
|
+
};
|
|
22923
|
+
case 404:
|
|
22924
|
+
return {
|
|
22925
|
+
...res,
|
|
22926
|
+
error: `Not found${pkgPart}${opPart}. Check the exact package name (scoped packages require the @scope/ prefix). If the version is specified, verify it exists with npm_package. Raw: ${res.error}`
|
|
22927
|
+
};
|
|
22928
|
+
case 422:
|
|
22929
|
+
return {
|
|
22930
|
+
...res,
|
|
22931
|
+
error: `Registry rejected the request payload${pkgPart}${opPart} (422 Unprocessable Entity). Most common causes: (1) invalid semver range \u2014 validate with npm_package versions first; (2) deprecation message format \u2014 em-dash form works, period-capital form sometimes 422s; (3) account-level 2FA policy requires interactive CLI session. If #3, CLI fallback: \`npm login --auth-type=web\` followed by the npm CLI command. Raw: ${res.error}`
|
|
22932
|
+
};
|
|
22933
|
+
case 429:
|
|
22934
|
+
return {
|
|
22935
|
+
...res,
|
|
22936
|
+
error: `Rate limited${opPart}. Wait 60 seconds and retry. Raw: ${res.error}`
|
|
22937
|
+
};
|
|
22938
|
+
case 0:
|
|
22939
|
+
return {
|
|
22940
|
+
...res,
|
|
22941
|
+
error: `Network error${opPart}. Could not reach the registry. Raw: ${res.error}`
|
|
22942
|
+
};
|
|
22943
|
+
default:
|
|
22944
|
+
return res;
|
|
22945
|
+
}
|
|
22946
|
+
}
|
|
22947
|
+
function validateDeprecationMessage(msg) {
|
|
22948
|
+
if (msg.length > 1024) {
|
|
22949
|
+
return "Deprecation message exceeds 1024 characters (registry limit).";
|
|
22950
|
+
}
|
|
22951
|
+
if (msg.length === 0) return null;
|
|
22952
|
+
if (/\.\s+[A-Z]/.test(msg)) {
|
|
22953
|
+
return `Deprecation message contains the "period + space + capital letter" pattern that has triggered 422 Unprocessable Entity on at least one scoped package. The working form uses em-dash and lowercase continuation: e.g. "Renamed to @yawlabs/spend \u2014 install that instead". Pass force: true to bypass this validation.`;
|
|
22954
|
+
}
|
|
22955
|
+
return null;
|
|
22956
|
+
}
|
|
22957
|
+
function versionsMatchingRange(versions, range, maxSatisfying2) {
|
|
22958
|
+
if (range === "*" || range === "") return [...versions];
|
|
22959
|
+
return versions.filter((v) => maxSatisfying2([v], range) === v);
|
|
22960
|
+
}
|
|
22961
|
+
|
|
22962
|
+
// src/tools/writes.ts
|
|
22963
|
+
async function fetchPackument(pkg) {
|
|
22964
|
+
return registryGetAuth(`/${encPkg(pkg)}?write=true`);
|
|
22965
|
+
}
|
|
22966
|
+
var writeTools = [
|
|
22967
|
+
// ───────────────────────────────────────────────────────
|
|
22968
|
+
// npm_deprecate
|
|
22969
|
+
// ───────────────────────────────────────────────────────
|
|
22970
|
+
{
|
|
22971
|
+
name: "npm_deprecate",
|
|
22972
|
+
description: "Deprecate a package or specific versions. Shows a warning message on install. Uses the HTTP API with NPM_TOKEN, bypassing the CLI auth friction that causes 422 errors on accounts with 2FA. Message format: prefer em-dash form ('Renamed to @scope/pkg \u2014 install that instead'); period-capital form sometimes triggers 422.",
|
|
22973
|
+
annotations: {
|
|
22974
|
+
title: "Deprecate package",
|
|
22975
|
+
readOnlyHint: false,
|
|
22976
|
+
destructiveHint: true,
|
|
22977
|
+
idempotentHint: true,
|
|
22978
|
+
openWorldHint: true
|
|
22979
|
+
},
|
|
22980
|
+
inputSchema: external_exports.object({
|
|
22981
|
+
name: external_exports.string().describe("Package name (e.g. '@yawlabs/tokenmeter-mcp')"),
|
|
22982
|
+
message: external_exports.string().describe("Deprecation message. Empty string to clear deprecation (use npm_undeprecate instead)."),
|
|
22983
|
+
versionRange: external_exports.string().optional().describe("Semver range. Omit to deprecate ALL versions. Example: '<1.0.0' or '0.3.x'."),
|
|
22984
|
+
force: external_exports.boolean().optional().describe("Bypass message format validation (default: false).")
|
|
22985
|
+
}),
|
|
22986
|
+
handler: async (input) => {
|
|
22987
|
+
const authErr = requireAuth();
|
|
22988
|
+
if (authErr) return authErr;
|
|
22989
|
+
if (!input.force) {
|
|
22990
|
+
const problem = validateDeprecationMessage(input.message);
|
|
22991
|
+
if (problem) return { ok: false, status: 400, error: problem };
|
|
22992
|
+
}
|
|
22993
|
+
const pRes = await fetchPackument(input.name);
|
|
22994
|
+
if (!pRes.ok) return translateError(pRes, { pkg: input.name, op: "deprecate (fetch)" });
|
|
22995
|
+
const packument = pRes.data;
|
|
22996
|
+
const allVersions = Object.keys(packument.versions || {});
|
|
22997
|
+
const range = input.versionRange ?? "*";
|
|
22998
|
+
const affected = versionsMatchingRange(allVersions, range, maxSatisfying);
|
|
22999
|
+
if (affected.length === 0) {
|
|
23000
|
+
return {
|
|
23001
|
+
ok: false,
|
|
23002
|
+
status: 400,
|
|
23003
|
+
error: `No versions match range '${range}' for ${input.name}. Published versions: ${allVersions.join(", ") || "(none)"}.`
|
|
23004
|
+
};
|
|
23005
|
+
}
|
|
23006
|
+
for (const v of affected) {
|
|
23007
|
+
packument.versions[v].deprecated = input.message;
|
|
23008
|
+
}
|
|
23009
|
+
const putRes = await registryPutAuth(`/${encPkg(input.name)}`, packument);
|
|
23010
|
+
if (!putRes.ok) return translateError(putRes, { pkg: input.name, op: "deprecate (write)" });
|
|
23011
|
+
return {
|
|
23012
|
+
ok: true,
|
|
23013
|
+
status: 200,
|
|
23014
|
+
data: {
|
|
23015
|
+
package: input.name,
|
|
23016
|
+
affectedVersions: affected,
|
|
23017
|
+
totalAffected: affected.length,
|
|
23018
|
+
message: input.message
|
|
23019
|
+
}
|
|
23020
|
+
};
|
|
23021
|
+
}
|
|
23022
|
+
},
|
|
23023
|
+
// ───────────────────────────────────────────────────────
|
|
23024
|
+
// npm_undeprecate
|
|
23025
|
+
// ───────────────────────────────────────────────────────
|
|
23026
|
+
{
|
|
23027
|
+
name: "npm_undeprecate",
|
|
23028
|
+
description: "Clear the deprecation message from a package or specific versions. Equivalent to npm_deprecate with an empty message but more explicit about intent.",
|
|
23029
|
+
annotations: {
|
|
23030
|
+
title: "Undeprecate package",
|
|
23031
|
+
readOnlyHint: false,
|
|
23032
|
+
destructiveHint: true,
|
|
23033
|
+
idempotentHint: true,
|
|
23034
|
+
openWorldHint: true
|
|
23035
|
+
},
|
|
23036
|
+
inputSchema: external_exports.object({
|
|
23037
|
+
name: external_exports.string().describe("Package name"),
|
|
23038
|
+
versionRange: external_exports.string().optional().describe("Semver range. Omit to undeprecate ALL versions.")
|
|
23039
|
+
}),
|
|
23040
|
+
handler: async (input) => {
|
|
23041
|
+
const authErr = requireAuth();
|
|
23042
|
+
if (authErr) return authErr;
|
|
23043
|
+
const pRes = await fetchPackument(input.name);
|
|
23044
|
+
if (!pRes.ok) return translateError(pRes, { pkg: input.name, op: "undeprecate (fetch)" });
|
|
23045
|
+
const packument = pRes.data;
|
|
23046
|
+
const allVersions = Object.keys(packument.versions || {});
|
|
23047
|
+
const range = input.versionRange ?? "*";
|
|
23048
|
+
const affected = versionsMatchingRange(allVersions, range, maxSatisfying);
|
|
23049
|
+
if (affected.length === 0) {
|
|
23050
|
+
return {
|
|
23051
|
+
ok: false,
|
|
23052
|
+
status: 400,
|
|
23053
|
+
error: `No versions match range '${range}' for ${input.name}.`
|
|
23054
|
+
};
|
|
23055
|
+
}
|
|
23056
|
+
for (const v of affected) {
|
|
23057
|
+
packument.versions[v].deprecated = "";
|
|
23058
|
+
}
|
|
23059
|
+
const putRes = await registryPutAuth(`/${encPkg(input.name)}`, packument);
|
|
23060
|
+
if (!putRes.ok) return translateError(putRes, { pkg: input.name, op: "undeprecate (write)" });
|
|
23061
|
+
return {
|
|
23062
|
+
ok: true,
|
|
23063
|
+
status: 200,
|
|
23064
|
+
data: {
|
|
23065
|
+
package: input.name,
|
|
23066
|
+
affectedVersions: affected,
|
|
23067
|
+
totalAffected: affected.length
|
|
23068
|
+
}
|
|
23069
|
+
};
|
|
23070
|
+
}
|
|
23071
|
+
},
|
|
23072
|
+
// ───────────────────────────────────────────────────────
|
|
23073
|
+
// npm_unpublish_version
|
|
23074
|
+
// ───────────────────────────────────────────────────────
|
|
23075
|
+
{
|
|
23076
|
+
name: "npm_unpublish_version",
|
|
23077
|
+
description: "Unpublish a specific version of a package. IRREVERSIBLE: once unpublished, the version cannot be re-published and will be blocked for 72 hours. Only works within 72 hours of the original publish for most packages. Requires explicit confirm: true to prevent accidents.",
|
|
23078
|
+
annotations: {
|
|
23079
|
+
title: "Unpublish version",
|
|
23080
|
+
readOnlyHint: false,
|
|
23081
|
+
destructiveHint: true,
|
|
23082
|
+
idempotentHint: false,
|
|
23083
|
+
openWorldHint: true
|
|
23084
|
+
},
|
|
23085
|
+
inputSchema: external_exports.object({
|
|
23086
|
+
name: external_exports.string().describe("Package name"),
|
|
23087
|
+
version: external_exports.string().describe("Specific version to unpublish (e.g. '1.2.3')"),
|
|
23088
|
+
confirm: external_exports.literal(true).describe("Must be literally true. Guards against accidental unpublish.")
|
|
23089
|
+
}),
|
|
23090
|
+
handler: async (input) => {
|
|
23091
|
+
const authErr = requireAuth();
|
|
23092
|
+
if (authErr) return authErr;
|
|
23093
|
+
if (input.confirm !== true) {
|
|
23094
|
+
return {
|
|
23095
|
+
ok: false,
|
|
23096
|
+
status: 400,
|
|
23097
|
+
error: "Unpublish requires confirm: true. This op is irreversible within 72 hours."
|
|
23098
|
+
};
|
|
23099
|
+
}
|
|
23100
|
+
const pRes = await fetchPackument(input.name);
|
|
23101
|
+
if (!pRes.ok) return translateError(pRes, { pkg: input.name, op: "unpublish (fetch)" });
|
|
23102
|
+
const packument = pRes.data;
|
|
23103
|
+
if (!packument.versions[input.version]) {
|
|
23104
|
+
return {
|
|
23105
|
+
ok: false,
|
|
23106
|
+
status: 404,
|
|
23107
|
+
error: `Version ${input.version} not found for ${input.name}. Published versions: ${Object.keys(packument.versions).join(", ")}.`
|
|
23108
|
+
};
|
|
23109
|
+
}
|
|
23110
|
+
delete packument.versions[input.version];
|
|
23111
|
+
for (const tag of Object.keys(packument["dist-tags"])) {
|
|
23112
|
+
if (packument["dist-tags"][tag] === input.version) {
|
|
23113
|
+
delete packument["dist-tags"][tag];
|
|
23114
|
+
}
|
|
23115
|
+
}
|
|
23116
|
+
const putRes = await registryPutAuth(`/${encPkg(input.name)}`, packument);
|
|
23117
|
+
if (!putRes.ok) return translateError(putRes, { pkg: input.name, op: "unpublish (write)" });
|
|
23118
|
+
return {
|
|
23119
|
+
ok: true,
|
|
23120
|
+
status: 200,
|
|
23121
|
+
data: {
|
|
23122
|
+
package: input.name,
|
|
23123
|
+
unpublishedVersion: input.version,
|
|
23124
|
+
remainingVersions: Object.keys(packument.versions)
|
|
23125
|
+
}
|
|
23126
|
+
};
|
|
23127
|
+
}
|
|
23128
|
+
},
|
|
23129
|
+
// ───────────────────────────────────────────────────────
|
|
23130
|
+
// npm_dist_tag_set
|
|
23131
|
+
// ───────────────────────────────────────────────────────
|
|
23132
|
+
{
|
|
23133
|
+
name: "npm_dist_tag_set",
|
|
23134
|
+
description: "Point a dist-tag (e.g. 'latest', 'beta', 'next') at a specific version. Common uses: promote a beta to latest, roll back latest to a prior version, maintain separate channels.",
|
|
23135
|
+
annotations: {
|
|
23136
|
+
title: "Set dist-tag",
|
|
23137
|
+
readOnlyHint: false,
|
|
23138
|
+
destructiveHint: true,
|
|
23139
|
+
idempotentHint: true,
|
|
23140
|
+
openWorldHint: true
|
|
23141
|
+
},
|
|
23142
|
+
inputSchema: external_exports.object({
|
|
23143
|
+
name: external_exports.string().describe("Package name"),
|
|
23144
|
+
tag: external_exports.string().describe("Dist-tag name (e.g. 'latest', 'beta', 'next')"),
|
|
23145
|
+
version: external_exports.string().describe("Version the tag should point to")
|
|
23146
|
+
}),
|
|
23147
|
+
handler: async (input) => {
|
|
23148
|
+
const authErr = requireAuth();
|
|
23149
|
+
if (authErr) return authErr;
|
|
23150
|
+
const putRes = await registryPutAuth(
|
|
23151
|
+
`/-/package/${encPkg(input.name)}/dist-tags/${encodeURIComponent(input.tag)}`,
|
|
23152
|
+
input.version
|
|
23153
|
+
);
|
|
23154
|
+
if (!putRes.ok) return translateError(putRes, { pkg: input.name, op: `dist-tag set ${input.tag}` });
|
|
23155
|
+
return {
|
|
23156
|
+
ok: true,
|
|
23157
|
+
status: 200,
|
|
23158
|
+
data: {
|
|
23159
|
+
package: input.name,
|
|
23160
|
+
tag: input.tag,
|
|
23161
|
+
version: input.version
|
|
23162
|
+
}
|
|
23163
|
+
};
|
|
23164
|
+
}
|
|
23165
|
+
},
|
|
23166
|
+
// ───────────────────────────────────────────────────────
|
|
23167
|
+
// npm_dist_tag_remove
|
|
23168
|
+
// ───────────────────────────────────────────────────────
|
|
23169
|
+
{
|
|
23170
|
+
name: "npm_dist_tag_remove",
|
|
23171
|
+
description: "Remove a dist-tag from a package. The 'latest' tag cannot be removed, only reassigned.",
|
|
23172
|
+
annotations: {
|
|
23173
|
+
title: "Remove dist-tag",
|
|
23174
|
+
readOnlyHint: false,
|
|
23175
|
+
destructiveHint: true,
|
|
23176
|
+
idempotentHint: true,
|
|
23177
|
+
openWorldHint: true
|
|
23178
|
+
},
|
|
23179
|
+
inputSchema: external_exports.object({
|
|
23180
|
+
name: external_exports.string().describe("Package name"),
|
|
23181
|
+
tag: external_exports.string().describe("Dist-tag name to remove")
|
|
23182
|
+
}),
|
|
23183
|
+
handler: async (input) => {
|
|
23184
|
+
const authErr = requireAuth();
|
|
23185
|
+
if (authErr) return authErr;
|
|
23186
|
+
if (input.tag === "latest") {
|
|
23187
|
+
return {
|
|
23188
|
+
ok: false,
|
|
23189
|
+
status: 400,
|
|
23190
|
+
error: "The 'latest' tag cannot be removed. Use npm_dist_tag_set to reassign it to a different version."
|
|
23191
|
+
};
|
|
23192
|
+
}
|
|
23193
|
+
const delRes = await registryDeleteAuth(
|
|
23194
|
+
`/-/package/${encPkg(input.name)}/dist-tags/${encodeURIComponent(input.tag)}`
|
|
23195
|
+
);
|
|
23196
|
+
if (!delRes.ok) return translateError(delRes, { pkg: input.name, op: `dist-tag remove ${input.tag}` });
|
|
23197
|
+
return {
|
|
23198
|
+
ok: true,
|
|
23199
|
+
status: 200,
|
|
23200
|
+
data: { package: input.name, removedTag: input.tag }
|
|
23201
|
+
};
|
|
23202
|
+
}
|
|
23203
|
+
},
|
|
23204
|
+
// ───────────────────────────────────────────────────────
|
|
23205
|
+
// npm_owner_add
|
|
23206
|
+
// ───────────────────────────────────────────────────────
|
|
23207
|
+
{
|
|
23208
|
+
name: "npm_owner_add",
|
|
23209
|
+
description: "Add a user as a maintainer of a package. They will have publish and write permissions. Use npm_collaborators to verify before adding.",
|
|
23210
|
+
annotations: {
|
|
23211
|
+
title: "Add package owner",
|
|
23212
|
+
readOnlyHint: false,
|
|
23213
|
+
destructiveHint: true,
|
|
23214
|
+
idempotentHint: true,
|
|
23215
|
+
openWorldHint: true
|
|
23216
|
+
},
|
|
23217
|
+
inputSchema: external_exports.object({
|
|
23218
|
+
name: external_exports.string().describe("Package name"),
|
|
23219
|
+
username: external_exports.string().describe("npm username to add as maintainer"),
|
|
23220
|
+
email: external_exports.string().optional().describe("Optional email for the maintainer record (defaults to empty)")
|
|
23221
|
+
}),
|
|
23222
|
+
handler: async (input) => {
|
|
23223
|
+
const authErr = requireAuth();
|
|
23224
|
+
if (authErr) return authErr;
|
|
23225
|
+
const pRes = await fetchPackument(input.name);
|
|
23226
|
+
if (!pRes.ok) return translateError(pRes, { pkg: input.name, op: "owner_add (fetch)" });
|
|
23227
|
+
const packument = pRes.data;
|
|
23228
|
+
packument.maintainers = packument.maintainers || [];
|
|
23229
|
+
if (packument.maintainers.some((m) => m.name === input.username)) {
|
|
23230
|
+
return {
|
|
23231
|
+
ok: true,
|
|
23232
|
+
status: 200,
|
|
23233
|
+
data: {
|
|
23234
|
+
package: input.name,
|
|
23235
|
+
username: input.username,
|
|
23236
|
+
alreadyOwner: true,
|
|
23237
|
+
maintainers: packument.maintainers.map((m) => m.name)
|
|
23238
|
+
}
|
|
23239
|
+
};
|
|
23240
|
+
}
|
|
23241
|
+
packument.maintainers.push({ name: input.username, email: input.email ?? "" });
|
|
23242
|
+
const putRes = await registryPutAuth(`/${encPkg(input.name)}`, packument);
|
|
23243
|
+
if (!putRes.ok) return translateError(putRes, { pkg: input.name, op: "owner_add (write)" });
|
|
23244
|
+
return {
|
|
23245
|
+
ok: true,
|
|
23246
|
+
status: 200,
|
|
23247
|
+
data: {
|
|
23248
|
+
package: input.name,
|
|
23249
|
+
addedOwner: input.username,
|
|
23250
|
+
maintainers: packument.maintainers.map((m) => m.name)
|
|
23251
|
+
}
|
|
23252
|
+
};
|
|
23253
|
+
}
|
|
23254
|
+
},
|
|
23255
|
+
// ───────────────────────────────────────────────────────
|
|
23256
|
+
// npm_owner_remove
|
|
23257
|
+
// ───────────────────────────────────────────────────────
|
|
23258
|
+
{
|
|
23259
|
+
name: "npm_owner_remove",
|
|
23260
|
+
description: "Remove a user from a package's maintainer list. Refuses if it would leave the package with zero maintainers (lockout prevention).",
|
|
23261
|
+
annotations: {
|
|
23262
|
+
title: "Remove package owner",
|
|
23263
|
+
readOnlyHint: false,
|
|
23264
|
+
destructiveHint: true,
|
|
23265
|
+
idempotentHint: true,
|
|
23266
|
+
openWorldHint: true
|
|
23267
|
+
},
|
|
23268
|
+
inputSchema: external_exports.object({
|
|
23269
|
+
name: external_exports.string().describe("Package name"),
|
|
23270
|
+
username: external_exports.string().describe("npm username to remove")
|
|
23271
|
+
}),
|
|
23272
|
+
handler: async (input) => {
|
|
23273
|
+
const authErr = requireAuth();
|
|
23274
|
+
if (authErr) return authErr;
|
|
23275
|
+
const pRes = await fetchPackument(input.name);
|
|
23276
|
+
if (!pRes.ok) return translateError(pRes, { pkg: input.name, op: "owner_remove (fetch)" });
|
|
23277
|
+
const packument = pRes.data;
|
|
23278
|
+
const before = packument.maintainers || [];
|
|
23279
|
+
if (!before.some((m) => m.name === input.username)) {
|
|
23280
|
+
return {
|
|
23281
|
+
ok: false,
|
|
23282
|
+
status: 404,
|
|
23283
|
+
error: `${input.username} is not a maintainer of ${input.name}. Current maintainers: ${before.map((m) => m.name).join(", ") || "(none)"}.`
|
|
23284
|
+
};
|
|
23285
|
+
}
|
|
23286
|
+
const after = before.filter((m) => m.name !== input.username);
|
|
23287
|
+
if (after.length === 0) {
|
|
23288
|
+
return {
|
|
23289
|
+
ok: false,
|
|
23290
|
+
status: 400,
|
|
23291
|
+
error: `Removing ${input.username} would leave ${input.name} with zero maintainers (lockout). Add another maintainer first with npm_owner_add.`
|
|
23292
|
+
};
|
|
23293
|
+
}
|
|
23294
|
+
packument.maintainers = after;
|
|
23295
|
+
const putRes = await registryPutAuth(`/${encPkg(input.name)}`, packument);
|
|
23296
|
+
if (!putRes.ok) return translateError(putRes, { pkg: input.name, op: "owner_remove (write)" });
|
|
23297
|
+
return {
|
|
23298
|
+
ok: true,
|
|
23299
|
+
status: 200,
|
|
23300
|
+
data: {
|
|
23301
|
+
package: input.name,
|
|
23302
|
+
removedOwner: input.username,
|
|
23303
|
+
remainingMaintainers: after.map((m) => m.name)
|
|
23304
|
+
}
|
|
23305
|
+
};
|
|
23306
|
+
}
|
|
23307
|
+
}
|
|
23308
|
+
];
|
|
23309
|
+
|
|
22765
23310
|
// src/index.ts
|
|
22766
|
-
var version2 = true ? "0.
|
|
23311
|
+
var version2 = true ? "0.6.0" : (await null).createRequire(import.meta.url)("../package.json").version;
|
|
22767
23312
|
var subcommand = process.argv[2];
|
|
22768
23313
|
if (subcommand === "version" || subcommand === "--version") {
|
|
22769
23314
|
console.log(version2);
|
|
@@ -22782,7 +23327,8 @@ var allTools = [
|
|
|
22782
23327
|
...accessTools,
|
|
22783
23328
|
...provenanceTools,
|
|
22784
23329
|
...trustTools,
|
|
22785
|
-
...workflowTools
|
|
23330
|
+
...workflowTools,
|
|
23331
|
+
...writeTools
|
|
22786
23332
|
];
|
|
22787
23333
|
var server = new McpServer({
|
|
22788
23334
|
name: "@yawlabs/npmjs-mcp",
|
package/package.json
CHANGED