@yawlabs/npmjs-mcp 0.6.0 → 0.8.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 +18 -3
- package/dist/index.js +795 -115
- 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 (63)
|
|
69
69
|
|
|
70
70
|
### Search
|
|
71
71
|
- `npm_search` — Search the npm registry with qualifiers (keywords, author, scope)
|
|
@@ -139,10 +139,25 @@ These bypass the CLI/2FA friction that causes `npm deprecate` and similar comman
|
|
|
139
139
|
- `npm_deprecate` — Deprecate a package or specific versions (validates message format)
|
|
140
140
|
- `npm_undeprecate` — Clear deprecation
|
|
141
141
|
- `npm_unpublish_version` — Unpublish a specific version (requires `confirm: true`)
|
|
142
|
+
- `npm_unpublish_package` — Unpublish an entire package (requires `confirm: true`)
|
|
142
143
|
- `npm_dist_tag_set` — Point a dist-tag at a version
|
|
143
144
|
- `npm_dist_tag_remove` — Remove a dist-tag (except `latest`)
|
|
144
|
-
- `npm_owner_add` — Add a maintainer
|
|
145
|
+
- `npm_owner_add` — Add a maintainer (resolves user via `/-/user/`)
|
|
145
146
|
- `npm_owner_remove` — Remove a maintainer (prevents lockout)
|
|
147
|
+
- `npm_access_set` — Set public/private/restricted access
|
|
148
|
+
- `npm_access_set_mfa` — Configure 2FA requirement for publish (none/publish/automation)
|
|
149
|
+
- `npm_team_grant` / `npm_team_revoke` — Grant/revoke team permissions on a package
|
|
150
|
+
- `npm_team_create` / `npm_team_delete` — Create/delete a team in an org
|
|
151
|
+
- `npm_team_member_add` / `npm_team_member_remove` — Manage team members
|
|
152
|
+
- `npm_org_member_set` / `npm_org_member_remove` — Add/remove org members, set roles
|
|
153
|
+
- `npm_token_revoke` — Revoke an access token by key (creation requires a password and isn't exposed)
|
|
154
|
+
|
|
155
|
+
### Webhooks (requires NPM_TOKEN)
|
|
156
|
+
- `npm_hook_add` — Register a webhook on a package, scope, or user
|
|
157
|
+
- `npm_hook_list` — List webhooks (optional package filter)
|
|
158
|
+
- `npm_hook_get` — Fetch a single webhook
|
|
159
|
+
- `npm_hook_update` — Update endpoint/secret of a webhook
|
|
160
|
+
- `npm_hook_remove` — Delete a webhook
|
|
146
161
|
|
|
147
162
|
### Operation Decision Matrix
|
|
148
163
|
|
|
@@ -158,7 +173,7 @@ Call `npm_ops_playbook` at the start of any session for the up-to-date matrix.
|
|
|
158
173
|
|
|
159
174
|
## Features
|
|
160
175
|
|
|
161
|
-
- **
|
|
176
|
+
- **63 tools** covering search, packages, deps, downloads, security, analysis, auth, orgs, access, provenance, trust, publish workflows, write operations, and registry webhooks
|
|
162
177
|
- **No API key required** for read-only tools — authenticated tools opt-in via NPM_TOKEN
|
|
163
178
|
- **Zero runtime dependencies** — Single bundled file for instant `npx` startup
|
|
164
179
|
- **Agent-aware publish tools** — Detects non-interactive context, provides human hand-off actions instead of unworkable retries
|
package/dist/index.js
CHANGED
|
@@ -21015,8 +21015,21 @@ var REGISTRY_URL = "https://registry.npmjs.org";
|
|
|
21015
21015
|
var DOWNLOADS_URL = "https://api.npmjs.org";
|
|
21016
21016
|
var REPLICATE_URL = "https://replicate.npmjs.com";
|
|
21017
21017
|
var REQUEST_TIMEOUT_MS = 3e4;
|
|
21018
|
+
var PACKAGE_NAME_MAX_LENGTH = 214;
|
|
21019
|
+
var PACKAGE_NAME_PATTERN = /^(?:@[a-zA-Z0-9][a-zA-Z0-9\-_.]*\/)?[a-zA-Z0-9][a-zA-Z0-9\-_.]*$/;
|
|
21020
|
+
function validatePackageName(name) {
|
|
21021
|
+
if (typeof name !== "string" || name.length === 0) return "Package name is empty";
|
|
21022
|
+
if (name.length > PACKAGE_NAME_MAX_LENGTH) {
|
|
21023
|
+
return `Package name exceeds ${PACKAGE_NAME_MAX_LENGTH} characters (got ${name.length}).`;
|
|
21024
|
+
}
|
|
21025
|
+
if (!PACKAGE_NAME_PATTERN.test(name)) {
|
|
21026
|
+
return `Invalid package name '${name}'. Names must start with an alphanumeric character and contain only [a-zA-Z0-9-_.], optionally prefixed with '@scope/' for scoped packages.`;
|
|
21027
|
+
}
|
|
21028
|
+
return null;
|
|
21029
|
+
}
|
|
21018
21030
|
function encPkg(name) {
|
|
21019
|
-
|
|
21031
|
+
const err = validatePackageName(name);
|
|
21032
|
+
if (err) throw new Error(err);
|
|
21020
21033
|
return name.startsWith("@") ? `@${encodeURIComponent(name.slice(1))}` : encodeURIComponent(name);
|
|
21021
21034
|
}
|
|
21022
21035
|
function isAuthenticated() {
|
|
@@ -21081,11 +21094,14 @@ function registryPost(path, body) {
|
|
|
21081
21094
|
function registryGetAuth(path) {
|
|
21082
21095
|
return request(REGISTRY_URL, path, { headers: authHeaders() });
|
|
21083
21096
|
}
|
|
21097
|
+
function registryPostAuth(path, body) {
|
|
21098
|
+
return request(REGISTRY_URL, path, { method: "POST", body, headers: authHeaders() });
|
|
21099
|
+
}
|
|
21084
21100
|
function registryPutAuth(path, body) {
|
|
21085
21101
|
return request(REGISTRY_URL, path, { method: "PUT", body, headers: authHeaders() });
|
|
21086
21102
|
}
|
|
21087
|
-
function registryDeleteAuth(path) {
|
|
21088
|
-
return request(REGISTRY_URL, path, { method: "DELETE", headers: authHeaders() });
|
|
21103
|
+
function registryDeleteAuth(path, body) {
|
|
21104
|
+
return request(REGISTRY_URL, path, { method: "DELETE", body, headers: authHeaders() });
|
|
21089
21105
|
}
|
|
21090
21106
|
function downloadsGet(path) {
|
|
21091
21107
|
return request(DOWNLOADS_URL, path);
|
|
@@ -21229,6 +21245,61 @@ function maxSatisfying(versions, range) {
|
|
|
21229
21245
|
return best;
|
|
21230
21246
|
}
|
|
21231
21247
|
|
|
21248
|
+
// src/errors.ts
|
|
21249
|
+
function translateError(res, context) {
|
|
21250
|
+
if (res.ok) return res;
|
|
21251
|
+
const pkgPart = context.pkg ? ` for ${context.pkg}` : "";
|
|
21252
|
+
const opPart = context.op ? ` during ${context.op}` : "";
|
|
21253
|
+
switch (res.status) {
|
|
21254
|
+
case 401:
|
|
21255
|
+
return {
|
|
21256
|
+
...res,
|
|
21257
|
+
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}`
|
|
21258
|
+
};
|
|
21259
|
+
case 403:
|
|
21260
|
+
return {
|
|
21261
|
+
...res,
|
|
21262
|
+
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}`
|
|
21263
|
+
};
|
|
21264
|
+
case 404:
|
|
21265
|
+
return {
|
|
21266
|
+
...res,
|
|
21267
|
+
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}`
|
|
21268
|
+
};
|
|
21269
|
+
case 422:
|
|
21270
|
+
return {
|
|
21271
|
+
...res,
|
|
21272
|
+
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}`
|
|
21273
|
+
};
|
|
21274
|
+
case 429:
|
|
21275
|
+
return {
|
|
21276
|
+
...res,
|
|
21277
|
+
error: `Rate limited${opPart}. Wait 60 seconds and retry. Raw: ${res.error}`
|
|
21278
|
+
};
|
|
21279
|
+
case 0:
|
|
21280
|
+
return {
|
|
21281
|
+
...res,
|
|
21282
|
+
error: `Network error${opPart}. Could not reach the registry. Raw: ${res.error}`
|
|
21283
|
+
};
|
|
21284
|
+
default:
|
|
21285
|
+
return res;
|
|
21286
|
+
}
|
|
21287
|
+
}
|
|
21288
|
+
function validateDeprecationMessage(msg) {
|
|
21289
|
+
if (msg.length > 1024) {
|
|
21290
|
+
return "Deprecation message exceeds 1024 characters (registry limit).";
|
|
21291
|
+
}
|
|
21292
|
+
if (msg.length === 0) return null;
|
|
21293
|
+
if (/\.\s+[A-Z]/.test(msg)) {
|
|
21294
|
+
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.`;
|
|
21295
|
+
}
|
|
21296
|
+
return null;
|
|
21297
|
+
}
|
|
21298
|
+
function versionsMatchingRange(versions, range, maxSatisfying2) {
|
|
21299
|
+
if (range === "*" || range === "") return [...versions];
|
|
21300
|
+
return versions.filter((v) => maxSatisfying2([v], range) === v);
|
|
21301
|
+
}
|
|
21302
|
+
|
|
21232
21303
|
// src/tools/access.ts
|
|
21233
21304
|
var accessTools = [
|
|
21234
21305
|
{
|
|
@@ -21248,7 +21319,7 @@ var accessTools = [
|
|
|
21248
21319
|
const authErr = requireAuth();
|
|
21249
21320
|
if (authErr) return authErr;
|
|
21250
21321
|
const res = await registryGetAuth(`/-/package/${encPkg(input.name)}/collaborators`);
|
|
21251
|
-
if (!res.ok) return res;
|
|
21322
|
+
if (!res.ok) return translateError(res, { pkg: input.name, op: "collaborators" });
|
|
21252
21323
|
const collaborators = Object.entries(res.data).map(([username, permissions]) => ({
|
|
21253
21324
|
username,
|
|
21254
21325
|
permissions
|
|
@@ -21284,7 +21355,7 @@ var accessTools = [
|
|
|
21284
21355
|
registryGetAuth(`/-/package/${encPkg(input.name)}/access`),
|
|
21285
21356
|
registryGetAuth(`/-/package/${encPkg(input.name)}/collaborators`)
|
|
21286
21357
|
]);
|
|
21287
|
-
if (!accessRes.ok && !collabRes.ok) return collabRes;
|
|
21358
|
+
if (!accessRes.ok && !collabRes.ok) return translateError(collabRes, { pkg: input.name, op: "package_access" });
|
|
21288
21359
|
const result = {
|
|
21289
21360
|
package: input.name,
|
|
21290
21361
|
isScoped: input.name.startsWith("@")
|
|
@@ -21385,7 +21456,7 @@ var analysisTools = [
|
|
|
21385
21456
|
downloadsGet(`/downloads/point/last-week/${encPkg(input.name)}`),
|
|
21386
21457
|
downloadsGet(`/downloads/point/last-month/${encPkg(input.name)}`)
|
|
21387
21458
|
]);
|
|
21388
|
-
if (!pkgRes.ok) return pkgRes;
|
|
21459
|
+
if (!pkgRes.ok) return translateError(pkgRes, { pkg: input.name, op: "health" });
|
|
21389
21460
|
const pkg = pkgRes.data;
|
|
21390
21461
|
const latest = pkg["dist-tags"]?.latest;
|
|
21391
21462
|
const latestVersion = latest ? pkg.versions[latest] : void 0;
|
|
@@ -21459,7 +21530,7 @@ var analysisTools = [
|
|
|
21459
21530
|
}),
|
|
21460
21531
|
handler: async (input) => {
|
|
21461
21532
|
const res = await registryGet(`/${encPkg(input.name)}`);
|
|
21462
|
-
if (!res.ok) return res;
|
|
21533
|
+
if (!res.ok) return translateError(res, { pkg: input.name, op: "maintainers" });
|
|
21463
21534
|
const pkg = res.data;
|
|
21464
21535
|
const publishCounts = {};
|
|
21465
21536
|
for (const ver of Object.values(pkg.versions)) {
|
|
@@ -21495,7 +21566,7 @@ var analysisTools = [
|
|
|
21495
21566
|
}),
|
|
21496
21567
|
handler: async (input) => {
|
|
21497
21568
|
const res = await registryGet(`/${encPkg(input.name)}`);
|
|
21498
|
-
if (!res.ok) return res;
|
|
21569
|
+
if (!res.ok) return translateError(res, { pkg: input.name, op: "release_frequency" });
|
|
21499
21570
|
const pkg = res.data;
|
|
21500
21571
|
const limit = input.limit ?? 20;
|
|
21501
21572
|
const releases = Object.keys(pkg.versions).map((v) => ({ version: v, date: pkg.time[v] })).filter((r) => r.date).sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()).slice(0, limit);
|
|
@@ -21542,7 +21613,8 @@ var authTools = [
|
|
|
21542
21613
|
handler: async () => {
|
|
21543
21614
|
const authErr = requireAuth();
|
|
21544
21615
|
if (authErr) return authErr;
|
|
21545
|
-
|
|
21616
|
+
const res = await registryGetAuth("/-/whoami");
|
|
21617
|
+
return res.ok ? res : translateError(res, { op: "whoami" });
|
|
21546
21618
|
}
|
|
21547
21619
|
},
|
|
21548
21620
|
{
|
|
@@ -21560,7 +21632,7 @@ var authTools = [
|
|
|
21560
21632
|
const authErr = requireAuth();
|
|
21561
21633
|
if (authErr) return authErr;
|
|
21562
21634
|
const res = await registryGetAuth("/-/npm/v1/user");
|
|
21563
|
-
if (!res.ok) return res;
|
|
21635
|
+
if (!res.ok) return translateError(res, { op: "profile" });
|
|
21564
21636
|
const p = res.data;
|
|
21565
21637
|
return {
|
|
21566
21638
|
ok: true,
|
|
@@ -21604,7 +21676,7 @@ var authTools = [
|
|
|
21604
21676
|
const qs = params.toString();
|
|
21605
21677
|
const path = `/-/npm/v1/tokens${qs ? `?${qs}` : ""}`;
|
|
21606
21678
|
const res = await registryGetAuth(path);
|
|
21607
|
-
if (!res.ok) return res;
|
|
21679
|
+
if (!res.ok) return translateError(res, { op: "tokens" });
|
|
21608
21680
|
const data = res.data;
|
|
21609
21681
|
return {
|
|
21610
21682
|
ok: true,
|
|
@@ -21678,7 +21750,7 @@ var authTools = [
|
|
|
21678
21750
|
const res = await registryGetAuth(
|
|
21679
21751
|
`/-/user/org.couchdb.user:${encodeURIComponent(input.username)}/package`
|
|
21680
21752
|
);
|
|
21681
|
-
if (!res.ok) return res;
|
|
21753
|
+
if (!res.ok) return translateError(res, { op: `user_packages ${input.username}` });
|
|
21682
21754
|
const packages = Object.entries(res.data).map(([name, access]) => ({ name, access }));
|
|
21683
21755
|
return {
|
|
21684
21756
|
ok: true,
|
|
@@ -21712,7 +21784,7 @@ var dependencyTools = [
|
|
|
21712
21784
|
handler: async (input) => {
|
|
21713
21785
|
const ver = input.version ?? "latest";
|
|
21714
21786
|
const res = await registryGet(`/${encPkg(input.name)}/${ver}`);
|
|
21715
|
-
if (!res.ok) return res;
|
|
21787
|
+
if (!res.ok) return translateError(res, { pkg: input.name, op: `dependencies ${ver}` });
|
|
21716
21788
|
const v = res.data;
|
|
21717
21789
|
return {
|
|
21718
21790
|
ok: true,
|
|
@@ -21827,7 +21899,7 @@ var dependencyTools = [
|
|
|
21827
21899
|
handler: async (input) => {
|
|
21828
21900
|
const ver = input.version ?? "latest";
|
|
21829
21901
|
const res = await registryGet(`/${encPkg(input.name)}/${ver}`);
|
|
21830
|
-
if (!res.ok) return res;
|
|
21902
|
+
if (!res.ok) return translateError(res, { pkg: input.name, op: `license_check ${ver}` });
|
|
21831
21903
|
const pkg = res.data;
|
|
21832
21904
|
const depEntries = Object.entries(pkg.dependencies ?? {});
|
|
21833
21905
|
const runLimited = createLimiter(10);
|
|
@@ -21885,7 +21957,8 @@ var downloadTools = [
|
|
|
21885
21957
|
}),
|
|
21886
21958
|
handler: async (input) => {
|
|
21887
21959
|
const period = input.period ?? "last-week";
|
|
21888
|
-
|
|
21960
|
+
const res = await downloadsGet(`/downloads/point/${period}/${encPkg(input.name)}`);
|
|
21961
|
+
return res.ok ? res : translateError(res, { pkg: input.name, op: `downloads ${period}` });
|
|
21889
21962
|
}
|
|
21890
21963
|
},
|
|
21891
21964
|
{
|
|
@@ -21904,7 +21977,8 @@ var downloadTools = [
|
|
|
21904
21977
|
}),
|
|
21905
21978
|
handler: async (input) => {
|
|
21906
21979
|
const period = input.period ?? "last-month";
|
|
21907
|
-
|
|
21980
|
+
const res = await downloadsGet(`/downloads/range/${period}/${encPkg(input.name)}`);
|
|
21981
|
+
return res.ok ? res : translateError(res, { pkg: input.name, op: `downloads_range ${period}` });
|
|
21908
21982
|
}
|
|
21909
21983
|
},
|
|
21910
21984
|
{
|
|
@@ -21924,7 +21998,8 @@ var downloadTools = [
|
|
|
21924
21998
|
handler: async (input) => {
|
|
21925
21999
|
const period = input.period ?? "last-week";
|
|
21926
22000
|
const names = input.packages.map((p) => encPkg(p)).join(",");
|
|
21927
|
-
|
|
22001
|
+
const res = await downloadsGet(`/downloads/point/${period}/${names}`);
|
|
22002
|
+
return res.ok ? res : translateError(res, { op: `downloads_bulk ${period}` });
|
|
21928
22003
|
}
|
|
21929
22004
|
},
|
|
21930
22005
|
{
|
|
@@ -21943,7 +22018,142 @@ var downloadTools = [
|
|
|
21943
22018
|
}),
|
|
21944
22019
|
handler: async (input) => {
|
|
21945
22020
|
const period = input.period ?? "last-week";
|
|
21946
|
-
|
|
22021
|
+
const res = await downloadsGet(`/versions/${encPkg(input.name)}/${period}`);
|
|
22022
|
+
return res.ok ? res : translateError(res, { pkg: input.name, op: `version_downloads ${period}` });
|
|
22023
|
+
}
|
|
22024
|
+
}
|
|
22025
|
+
];
|
|
22026
|
+
|
|
22027
|
+
// src/tools/hooks.ts
|
|
22028
|
+
function classifyHookTarget(target) {
|
|
22029
|
+
if (target.startsWith("~")) return { type: "owner", name: target.slice(1) };
|
|
22030
|
+
if (/^@[^/]+$/.test(target)) return { type: "scope", name: target };
|
|
22031
|
+
return { type: "package", name: target };
|
|
22032
|
+
}
|
|
22033
|
+
var hookTools = [
|
|
22034
|
+
{
|
|
22035
|
+
name: "npm_hook_add",
|
|
22036
|
+
description: "Create a registry webhook. Target is 'pkg' or '@scope/pkg' for a package, '@scope' for a scope, or '~user' for a user's packages. Endpoint is the HTTPS URL to POST events to; secret is used to HMAC-sign payloads.",
|
|
22037
|
+
annotations: {
|
|
22038
|
+
title: "Add webhook",
|
|
22039
|
+
readOnlyHint: false,
|
|
22040
|
+
destructiveHint: true,
|
|
22041
|
+
idempotentHint: false,
|
|
22042
|
+
openWorldHint: true
|
|
22043
|
+
},
|
|
22044
|
+
inputSchema: external_exports.object({
|
|
22045
|
+
target: external_exports.string().describe("Hook target: 'pkg', '@scope/pkg', '@scope', or '~user'"),
|
|
22046
|
+
endpoint: external_exports.string().url().describe("HTTPS URL that will receive POST events"),
|
|
22047
|
+
secret: external_exports.string().describe("Secret used to HMAC-sign webhook payloads")
|
|
22048
|
+
}),
|
|
22049
|
+
handler: async (input) => {
|
|
22050
|
+
const authErr = requireAuth();
|
|
22051
|
+
if (authErr) return authErr;
|
|
22052
|
+
const { type, name } = classifyHookTarget(input.target);
|
|
22053
|
+
const res = await registryPostAuth("/-/npm/v1/hooks/hook", {
|
|
22054
|
+
type,
|
|
22055
|
+
name,
|
|
22056
|
+
endpoint: input.endpoint,
|
|
22057
|
+
secret: input.secret
|
|
22058
|
+
});
|
|
22059
|
+
if (!res.ok) return translateError(res, { op: `hook_add ${input.target}` });
|
|
22060
|
+
return { ok: true, status: 200, data: res.data };
|
|
22061
|
+
}
|
|
22062
|
+
},
|
|
22063
|
+
{
|
|
22064
|
+
name: "npm_hook_list",
|
|
22065
|
+
description: "List webhooks. Optionally filter by package name.",
|
|
22066
|
+
annotations: {
|
|
22067
|
+
title: "List webhooks",
|
|
22068
|
+
readOnlyHint: true,
|
|
22069
|
+
destructiveHint: false,
|
|
22070
|
+
idempotentHint: true,
|
|
22071
|
+
openWorldHint: true
|
|
22072
|
+
},
|
|
22073
|
+
inputSchema: external_exports.object({
|
|
22074
|
+
package: external_exports.string().optional().describe("Filter by package name"),
|
|
22075
|
+
limit: external_exports.number().int().optional().describe("Max results"),
|
|
22076
|
+
offset: external_exports.number().int().optional().describe("Pagination offset")
|
|
22077
|
+
}),
|
|
22078
|
+
handler: async (input) => {
|
|
22079
|
+
const authErr = requireAuth();
|
|
22080
|
+
if (authErr) return authErr;
|
|
22081
|
+
const qs = new URLSearchParams();
|
|
22082
|
+
if (input.package) qs.set("package", input.package);
|
|
22083
|
+
if (input.limit !== void 0) qs.set("limit", String(input.limit));
|
|
22084
|
+
if (input.offset !== void 0) qs.set("offset", String(input.offset));
|
|
22085
|
+
const q = qs.toString();
|
|
22086
|
+
const res = await registryGetAuth(`/-/npm/v1/hooks${q ? `?${q}` : ""}`);
|
|
22087
|
+
if (!res.ok) return translateError(res, { op: "hook_list" });
|
|
22088
|
+
return { ok: true, status: 200, data: res.data };
|
|
22089
|
+
}
|
|
22090
|
+
},
|
|
22091
|
+
{
|
|
22092
|
+
name: "npm_hook_get",
|
|
22093
|
+
description: "Get a single webhook by its ID.",
|
|
22094
|
+
annotations: {
|
|
22095
|
+
title: "Get webhook",
|
|
22096
|
+
readOnlyHint: true,
|
|
22097
|
+
destructiveHint: false,
|
|
22098
|
+
idempotentHint: true,
|
|
22099
|
+
openWorldHint: true
|
|
22100
|
+
},
|
|
22101
|
+
inputSchema: external_exports.object({
|
|
22102
|
+
id: external_exports.string().describe("Hook ID (UUID from npm_hook_list)")
|
|
22103
|
+
}),
|
|
22104
|
+
handler: async (input) => {
|
|
22105
|
+
const authErr = requireAuth();
|
|
22106
|
+
if (authErr) return authErr;
|
|
22107
|
+
const res = await registryGetAuth(`/-/npm/v1/hooks/hook/${encodeURIComponent(input.id)}`);
|
|
22108
|
+
if (!res.ok) return translateError(res, { op: `hook_get ${input.id}` });
|
|
22109
|
+
return { ok: true, status: 200, data: res.data };
|
|
22110
|
+
}
|
|
22111
|
+
},
|
|
22112
|
+
{
|
|
22113
|
+
name: "npm_hook_update",
|
|
22114
|
+
description: "Update a webhook's endpoint and/or secret.",
|
|
22115
|
+
annotations: {
|
|
22116
|
+
title: "Update webhook",
|
|
22117
|
+
readOnlyHint: false,
|
|
22118
|
+
destructiveHint: true,
|
|
22119
|
+
idempotentHint: true,
|
|
22120
|
+
openWorldHint: true
|
|
22121
|
+
},
|
|
22122
|
+
inputSchema: external_exports.object({
|
|
22123
|
+
id: external_exports.string().describe("Hook ID"),
|
|
22124
|
+
endpoint: external_exports.string().url().describe("New HTTPS URL"),
|
|
22125
|
+
secret: external_exports.string().describe("New signing secret")
|
|
22126
|
+
}),
|
|
22127
|
+
handler: async (input) => {
|
|
22128
|
+
const authErr = requireAuth();
|
|
22129
|
+
if (authErr) return authErr;
|
|
22130
|
+
const res = await registryPutAuth(`/-/npm/v1/hooks/hook/${encodeURIComponent(input.id)}`, {
|
|
22131
|
+
endpoint: input.endpoint,
|
|
22132
|
+
secret: input.secret
|
|
22133
|
+
});
|
|
22134
|
+
if (!res.ok) return translateError(res, { op: `hook_update ${input.id}` });
|
|
22135
|
+
return { ok: true, status: 200, data: res.data };
|
|
22136
|
+
}
|
|
22137
|
+
},
|
|
22138
|
+
{
|
|
22139
|
+
name: "npm_hook_remove",
|
|
22140
|
+
description: "Delete a webhook by ID.",
|
|
22141
|
+
annotations: {
|
|
22142
|
+
title: "Remove webhook",
|
|
22143
|
+
readOnlyHint: false,
|
|
22144
|
+
destructiveHint: true,
|
|
22145
|
+
idempotentHint: true,
|
|
22146
|
+
openWorldHint: true
|
|
22147
|
+
},
|
|
22148
|
+
inputSchema: external_exports.object({
|
|
22149
|
+
id: external_exports.string().describe("Hook ID")
|
|
22150
|
+
}),
|
|
22151
|
+
handler: async (input) => {
|
|
22152
|
+
const authErr = requireAuth();
|
|
22153
|
+
if (authErr) return authErr;
|
|
22154
|
+
const res = await registryDeleteAuth(`/-/npm/v1/hooks/hook/${encodeURIComponent(input.id)}`);
|
|
22155
|
+
if (!res.ok) return translateError(res, { op: `hook_remove ${input.id}` });
|
|
22156
|
+
return { ok: true, status: 200, data: { id: input.id, removed: true } };
|
|
21947
22157
|
}
|
|
21948
22158
|
}
|
|
21949
22159
|
];
|
|
@@ -21967,7 +22177,7 @@ var orgTools = [
|
|
|
21967
22177
|
const authErr = requireAuth();
|
|
21968
22178
|
if (authErr) return authErr;
|
|
21969
22179
|
const res = await registryGetAuth(`/-/org/${encodeURIComponent(input.org)}/user`);
|
|
21970
|
-
if (!res.ok) return res;
|
|
22180
|
+
if (!res.ok) return translateError(res, { op: `org_members ${input.org}` });
|
|
21971
22181
|
const members = Object.entries(res.data).map(([username, role]) => ({ username, role }));
|
|
21972
22182
|
return {
|
|
21973
22183
|
ok: true,
|
|
@@ -21997,7 +22207,7 @@ var orgTools = [
|
|
|
21997
22207
|
const authErr = requireAuth();
|
|
21998
22208
|
if (authErr) return authErr;
|
|
21999
22209
|
const res = await registryGetAuth(`/-/org/${encodeURIComponent(input.org)}/package`);
|
|
22000
|
-
if (!res.ok) return res;
|
|
22210
|
+
if (!res.ok) return translateError(res, { op: `org_packages ${input.org}` });
|
|
22001
22211
|
const packages = Object.entries(res.data).map(([name, access]) => ({ name, access }));
|
|
22002
22212
|
return {
|
|
22003
22213
|
ok: true,
|
|
@@ -22027,7 +22237,7 @@ var orgTools = [
|
|
|
22027
22237
|
const authErr = requireAuth();
|
|
22028
22238
|
if (authErr) return authErr;
|
|
22029
22239
|
const res = await registryGetAuth(`/-/org/${encodeURIComponent(input.org)}/team`);
|
|
22030
|
-
if (!res.ok) return res;
|
|
22240
|
+
if (!res.ok) return translateError(res, { op: `org_teams ${input.org}` });
|
|
22031
22241
|
return {
|
|
22032
22242
|
ok: true,
|
|
22033
22243
|
status: 200,
|
|
@@ -22059,7 +22269,7 @@ var orgTools = [
|
|
|
22059
22269
|
const res = await registryGetAuth(
|
|
22060
22270
|
`/-/team/${encodeURIComponent(input.org)}/${encodeURIComponent(input.team)}/package`
|
|
22061
22271
|
);
|
|
22062
|
-
if (!res.ok) return res;
|
|
22272
|
+
if (!res.ok) return translateError(res, { op: `team_packages ${input.org}:${input.team}` });
|
|
22063
22273
|
const packages = Object.entries(res.data).map(([name, permissions]) => ({ name, permissions }));
|
|
22064
22274
|
return {
|
|
22065
22275
|
ok: true,
|
|
@@ -22092,7 +22302,7 @@ var packageTools = [
|
|
|
22092
22302
|
}),
|
|
22093
22303
|
handler: async (input) => {
|
|
22094
22304
|
const res = await registryGet(`/${encPkg(input.name)}`);
|
|
22095
|
-
if (!res.ok) return res;
|
|
22305
|
+
if (!res.ok) return translateError(res, { pkg: input.name, op: "package" });
|
|
22096
22306
|
const pkg = res.data;
|
|
22097
22307
|
const latest = pkg["dist-tags"]?.latest;
|
|
22098
22308
|
const latestVersion = latest ? pkg.versions[latest] : void 0;
|
|
@@ -22136,7 +22346,7 @@ var packageTools = [
|
|
|
22136
22346
|
handler: async (input) => {
|
|
22137
22347
|
const ver = input.version ?? "latest";
|
|
22138
22348
|
const res = await registryGet(`/${encPkg(input.name)}/${ver}`);
|
|
22139
|
-
if (!res.ok) return res;
|
|
22349
|
+
if (!res.ok) return translateError(res, { pkg: input.name, op: `version ${ver}` });
|
|
22140
22350
|
const v = res.data;
|
|
22141
22351
|
return {
|
|
22142
22352
|
ok: true,
|
|
@@ -22186,7 +22396,7 @@ var packageTools = [
|
|
|
22186
22396
|
}),
|
|
22187
22397
|
handler: async (input) => {
|
|
22188
22398
|
const res = await registryGet(`/${encPkg(input.name)}`);
|
|
22189
|
-
if (!res.ok) return res;
|
|
22399
|
+
if (!res.ok) return translateError(res, { pkg: input.name, op: "versions" });
|
|
22190
22400
|
const pkg = res.data;
|
|
22191
22401
|
const limit = input.limit ?? 50;
|
|
22192
22402
|
const allVersions = Object.keys(pkg.versions).map((v) => ({
|
|
@@ -22224,7 +22434,7 @@ var packageTools = [
|
|
|
22224
22434
|
}),
|
|
22225
22435
|
handler: async (input) => {
|
|
22226
22436
|
const res = await registryGet(`/${encPkg(input.name)}`);
|
|
22227
|
-
if (!res.ok) return res;
|
|
22437
|
+
if (!res.ok) return translateError(res, { pkg: input.name, op: "readme" });
|
|
22228
22438
|
const readme = res.data.readme;
|
|
22229
22439
|
if (!readme) {
|
|
22230
22440
|
return { ok: true, status: 200, data: { name: input.name, readme: "(no readme available)" } };
|
|
@@ -22246,7 +22456,8 @@ var packageTools = [
|
|
|
22246
22456
|
name: external_exports.string().describe("Package name")
|
|
22247
22457
|
}),
|
|
22248
22458
|
handler: async (input) => {
|
|
22249
|
-
|
|
22459
|
+
const res = await registryGet(`/-/package/${encPkg(input.name)}/dist-tags`);
|
|
22460
|
+
return res.ok ? res : translateError(res, { pkg: input.name, op: "dist_tags" });
|
|
22250
22461
|
}
|
|
22251
22462
|
},
|
|
22252
22463
|
{
|
|
@@ -22276,7 +22487,7 @@ var packageTools = [
|
|
|
22276
22487
|
registryGet(`/${encPkg(input.name)}/${ver}`),
|
|
22277
22488
|
registryGet(`/${encPkg(typesPackage)}`)
|
|
22278
22489
|
]);
|
|
22279
|
-
if (!versionRes.ok) return versionRes;
|
|
22490
|
+
if (!versionRes.ok) return translateError(versionRes, { pkg: input.name, op: `types ${ver}` });
|
|
22280
22491
|
const v = versionRes.data;
|
|
22281
22492
|
const hasBuiltinTypes = !!(v.types || v.typings);
|
|
22282
22493
|
const typesEntry = hasBuiltinTypes ? v.types ?? v.typings : void 0;
|
|
@@ -22324,9 +22535,9 @@ var provenanceTools = [
|
|
|
22324
22535
|
}),
|
|
22325
22536
|
handler: async (input) => {
|
|
22326
22537
|
const res = await registryGet(
|
|
22327
|
-
`/-/npm/v1/attestations/${encPkg(input.name)}@${input.version}`
|
|
22538
|
+
`/-/npm/v1/attestations/${encPkg(input.name)}@${encodeURIComponent(input.version)}`
|
|
22328
22539
|
);
|
|
22329
|
-
if (!res.ok) return res;
|
|
22540
|
+
if (!res.ok) return translateError(res, { pkg: input.name, op: `provenance ${input.version}` });
|
|
22330
22541
|
const attestations = (res.data.attestations ?? []).map((a) => ({
|
|
22331
22542
|
predicateType: a.predicateType,
|
|
22332
22543
|
bundle: a.bundle
|
|
@@ -22364,7 +22575,8 @@ var registryTools = [
|
|
|
22364
22575
|
}),
|
|
22365
22576
|
handler: async (input) => {
|
|
22366
22577
|
const period = input.period ?? "last-week";
|
|
22367
|
-
|
|
22578
|
+
const res = await downloadsGet(`/downloads/point/${period}`);
|
|
22579
|
+
return res.ok ? res : translateError(res, { op: `registry_stats ${period}` });
|
|
22368
22580
|
}
|
|
22369
22581
|
},
|
|
22370
22582
|
{
|
|
@@ -22386,7 +22598,7 @@ var registryTools = [
|
|
|
22386
22598
|
replicateGet("/"),
|
|
22387
22599
|
replicateGet(`/_changes?limit=${limit}&descending=true`)
|
|
22388
22600
|
]);
|
|
22389
|
-
if (!changesRes.ok) return changesRes;
|
|
22601
|
+
if (!changesRes.ok) return translateError(changesRes, { op: "recent_changes" });
|
|
22390
22602
|
const changes = changesRes.data.results.map((r) => ({
|
|
22391
22603
|
package: r.id,
|
|
22392
22604
|
rev: r.changes[0]?.rev
|
|
@@ -22497,7 +22709,7 @@ var searchTools = [
|
|
|
22497
22709
|
if (input.popularity !== void 0) params.set("popularity", String(input.popularity));
|
|
22498
22710
|
if (input.maintenance !== void 0) params.set("maintenance", String(input.maintenance));
|
|
22499
22711
|
const res = await registryGet(`/-/v1/search?${params}`);
|
|
22500
|
-
if (!res.ok) return res;
|
|
22712
|
+
if (!res.ok) return translateError(res, { op: `search "${input.query}"` });
|
|
22501
22713
|
const results = res.data.objects.map((obj) => ({
|
|
22502
22714
|
name: obj.package.name,
|
|
22503
22715
|
version: obj.package.version,
|
|
@@ -22530,7 +22742,34 @@ var securityTools = [
|
|
|
22530
22742
|
packages: external_exports.record(external_exports.array(external_exports.string())).describe('Object mapping package names to arrays of version strings, e.g. {"lodash": ["4.17.20"]}')
|
|
22531
22743
|
}),
|
|
22532
22744
|
handler: async (input) => {
|
|
22533
|
-
|
|
22745
|
+
const res = await registryPost("/-/npm/v1/security/advisories/bulk", input.packages);
|
|
22746
|
+
if (!res.ok) return translateError(res, { op: "audit" });
|
|
22747
|
+
const advisoriesByPackage = res.data ?? {};
|
|
22748
|
+
const summary = Object.entries(advisoriesByPackage).map(([name, advisories]) => {
|
|
22749
|
+
const list = Array.isArray(advisories) ? advisories : [];
|
|
22750
|
+
const severityCounts = {};
|
|
22751
|
+
for (const adv of list) {
|
|
22752
|
+
const severity = adv?.severity ?? "unknown";
|
|
22753
|
+
severityCounts[severity] = (severityCounts[severity] ?? 0) + 1;
|
|
22754
|
+
}
|
|
22755
|
+
return { name, advisoryCount: list.length, severityCounts };
|
|
22756
|
+
});
|
|
22757
|
+
const queried = Object.keys(input.packages);
|
|
22758
|
+
const vulnerable = summary.filter((s) => s.advisoryCount > 0).map((s) => s.name);
|
|
22759
|
+
const clean = queried.filter((n) => !vulnerable.includes(n));
|
|
22760
|
+
return {
|
|
22761
|
+
ok: true,
|
|
22762
|
+
status: 200,
|
|
22763
|
+
data: {
|
|
22764
|
+
queriedCount: queried.length,
|
|
22765
|
+
vulnerableCount: vulnerable.length,
|
|
22766
|
+
cleanCount: clean.length,
|
|
22767
|
+
vulnerable,
|
|
22768
|
+
clean,
|
|
22769
|
+
summary,
|
|
22770
|
+
advisories: advisoriesByPackage
|
|
22771
|
+
}
|
|
22772
|
+
};
|
|
22534
22773
|
}
|
|
22535
22774
|
},
|
|
22536
22775
|
{
|
|
@@ -22557,7 +22796,8 @@ var securityTools = [
|
|
|
22557
22796
|
Object.entries(input.dependencies).map(([pkg, ver]) => [pkg, { version: ver }])
|
|
22558
22797
|
)
|
|
22559
22798
|
};
|
|
22560
|
-
|
|
22799
|
+
const res = await registryPost("/-/npm/v1/security/audits", body);
|
|
22800
|
+
return res.ok ? res : translateError(res, { pkg: input.name, op: "audit_deep" });
|
|
22561
22801
|
}
|
|
22562
22802
|
},
|
|
22563
22803
|
{
|
|
@@ -22572,7 +22812,8 @@ var securityTools = [
|
|
|
22572
22812
|
},
|
|
22573
22813
|
inputSchema: external_exports.object({}),
|
|
22574
22814
|
handler: async () => {
|
|
22575
|
-
|
|
22815
|
+
const res = await registryGet("/-/npm/v1/keys");
|
|
22816
|
+
return res.ok ? res : translateError(res, { op: "signing_keys" });
|
|
22576
22817
|
}
|
|
22577
22818
|
}
|
|
22578
22819
|
];
|
|
@@ -22596,7 +22837,7 @@ var trustTools = [
|
|
|
22596
22837
|
const authErr = requireAuth();
|
|
22597
22838
|
if (authErr) return authErr;
|
|
22598
22839
|
const res = await registryGetAuth(`/-/package/${encPkg(input.name)}/trust`);
|
|
22599
|
-
if (!res.ok) return res;
|
|
22840
|
+
if (!res.ok) return translateError(res, { pkg: input.name, op: "trusted_publishers" });
|
|
22600
22841
|
const configs = (res.data ?? []).map((c) => {
|
|
22601
22842
|
const result = {
|
|
22602
22843
|
id: c.id,
|
|
@@ -22904,72 +23145,31 @@ var workflowTools = [
|
|
|
22904
23145
|
}
|
|
22905
23146
|
];
|
|
22906
23147
|
|
|
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
23148
|
// src/tools/writes.ts
|
|
22963
23149
|
async function fetchPackument(pkg) {
|
|
22964
23150
|
return registryGetAuth(`/${encPkg(pkg)}?write=true`);
|
|
22965
23151
|
}
|
|
23152
|
+
function parseTeamTarget(target) {
|
|
23153
|
+
const m = target.match(/^@?([^:]+):(.+)$/);
|
|
23154
|
+
return m ? { scope: m[1], team: m[2] } : null;
|
|
23155
|
+
}
|
|
23156
|
+
function highestVersion(versions) {
|
|
23157
|
+
const parsed = [];
|
|
23158
|
+
for (const v of versions) {
|
|
23159
|
+
const m = v.match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
23160
|
+
if (m) parsed.push([Number(m[1]), Number(m[2]), Number(m[3]), v]);
|
|
23161
|
+
}
|
|
23162
|
+
if (parsed.length === 0) return null;
|
|
23163
|
+
parsed.sort((a, b) => a[0] - b[0] || a[1] - b[1] || a[2] - b[2]);
|
|
23164
|
+
return parsed[parsed.length - 1][3];
|
|
23165
|
+
}
|
|
22966
23166
|
var writeTools = [
|
|
22967
23167
|
// ───────────────────────────────────────────────────────
|
|
22968
23168
|
// npm_deprecate
|
|
22969
23169
|
// ───────────────────────────────────────────────────────
|
|
22970
23170
|
{
|
|
22971
23171
|
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:
|
|
23172
|
+
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 tip: the period-then-capital pattern ('... install that instead. Thanks.') has 422'd on at least one scoped package; em-dash form is the known-good workaround. Pass force: true to skip the preflight check if you want the exact message as-is.",
|
|
22973
23173
|
annotations: {
|
|
22974
23174
|
title: "Deprecate package",
|
|
22975
23175
|
readOnlyHint: false,
|
|
@@ -23072,9 +23272,17 @@ var writeTools = [
|
|
|
23072
23272
|
// ───────────────────────────────────────────────────────
|
|
23073
23273
|
// npm_unpublish_version
|
|
23074
23274
|
// ───────────────────────────────────────────────────────
|
|
23275
|
+
// Mirrors libnpmpublish/unpublish.js single-version flow:
|
|
23276
|
+
// 1. GET /{pkg}?write=true
|
|
23277
|
+
// 2. mutate: remove version, fix dist-tags, delete _revisions/_attachments
|
|
23278
|
+
// 3. PUT /{pkg}/-rev/{rev}
|
|
23279
|
+
// 4. GET /{pkg}?write=true (fresh rev)
|
|
23280
|
+
// 5. DELETE {tarball-pathname}/-rev/{newRev}
|
|
23281
|
+
// The tarball DELETE is best-effort — if it fails, the version is already unreachable
|
|
23282
|
+
// via the packument (step 3 succeeded), so we report success with a warning.
|
|
23075
23283
|
{
|
|
23076
23284
|
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.",
|
|
23285
|
+
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. Follows the npm CLI flow (mutate packument + delete tarball). For full-package unpublish use npm_unpublish_package.",
|
|
23078
23286
|
annotations: {
|
|
23079
23287
|
title: "Unpublish version",
|
|
23080
23288
|
readOnlyHint: false,
|
|
@@ -23100,33 +23308,118 @@ var writeTools = [
|
|
|
23100
23308
|
const pRes = await fetchPackument(input.name);
|
|
23101
23309
|
if (!pRes.ok) return translateError(pRes, { pkg: input.name, op: "unpublish (fetch)" });
|
|
23102
23310
|
const packument = pRes.data;
|
|
23103
|
-
|
|
23311
|
+
const versionData = packument.versions?.[input.version];
|
|
23312
|
+
if (!versionData) {
|
|
23104
23313
|
return {
|
|
23105
23314
|
ok: false,
|
|
23106
23315
|
status: 404,
|
|
23107
|
-
error: `Version ${input.version} not found for ${input.name}. Published versions: ${Object.keys(packument.versions).join(", ")}.`
|
|
23316
|
+
error: `Version ${input.version} not found for ${input.name}. Published versions: ${Object.keys(packument.versions || {}).join(", ")}.`
|
|
23108
23317
|
};
|
|
23109
23318
|
}
|
|
23319
|
+
if (!packument._rev) {
|
|
23320
|
+
return {
|
|
23321
|
+
ok: false,
|
|
23322
|
+
status: 500,
|
|
23323
|
+
error: `Packument for ${input.name} missing _rev \u2014 cannot unpublish. Try again; this is usually transient.`
|
|
23324
|
+
};
|
|
23325
|
+
}
|
|
23326
|
+
const tarballUrl = versionData.dist?.tarball;
|
|
23327
|
+
const latestBefore = packument["dist-tags"]?.latest;
|
|
23110
23328
|
delete packument.versions[input.version];
|
|
23111
|
-
for (const tag of Object.keys(packument["dist-tags"])) {
|
|
23329
|
+
for (const tag of Object.keys(packument["dist-tags"] || {})) {
|
|
23112
23330
|
if (packument["dist-tags"][tag] === input.version) {
|
|
23113
23331
|
delete packument["dist-tags"][tag];
|
|
23114
23332
|
}
|
|
23115
23333
|
}
|
|
23116
|
-
|
|
23117
|
-
|
|
23334
|
+
if (latestBefore === input.version) {
|
|
23335
|
+
const newLatest = highestVersion(Object.keys(packument.versions));
|
|
23336
|
+
if (newLatest) packument["dist-tags"].latest = newLatest;
|
|
23337
|
+
}
|
|
23338
|
+
delete packument._revisions;
|
|
23339
|
+
delete packument._attachments;
|
|
23340
|
+
const putRes = await registryPutAuth(
|
|
23341
|
+
`/${encPkg(input.name)}/-rev/${encodeURIComponent(packument._rev)}`,
|
|
23342
|
+
packument
|
|
23343
|
+
);
|
|
23344
|
+
if (!putRes.ok) return translateError(putRes, { pkg: input.name, op: "unpublish (packument PUT)" });
|
|
23345
|
+
let tarballDeleted = false;
|
|
23346
|
+
let tarballError;
|
|
23347
|
+
if (tarballUrl) {
|
|
23348
|
+
const freshRes = await fetchPackument(input.name);
|
|
23349
|
+
const freshRev = freshRes.ok ? freshRes.data._rev : void 0;
|
|
23350
|
+
if (freshRev) {
|
|
23351
|
+
try {
|
|
23352
|
+
const pathname = new URL(tarballUrl).pathname;
|
|
23353
|
+
const delRes = await registryDeleteAuth(`${pathname}/-rev/${encodeURIComponent(freshRev)}`);
|
|
23354
|
+
tarballDeleted = delRes.ok;
|
|
23355
|
+
if (!delRes.ok) tarballError = delRes.error;
|
|
23356
|
+
} catch (err) {
|
|
23357
|
+
tarballError = err instanceof Error ? err.message : String(err);
|
|
23358
|
+
}
|
|
23359
|
+
} else {
|
|
23360
|
+
tarballError = "could not re-fetch packument for tarball DELETE rev";
|
|
23361
|
+
}
|
|
23362
|
+
}
|
|
23118
23363
|
return {
|
|
23119
23364
|
ok: true,
|
|
23120
23365
|
status: 200,
|
|
23121
23366
|
data: {
|
|
23122
23367
|
package: input.name,
|
|
23123
23368
|
unpublishedVersion: input.version,
|
|
23124
|
-
remainingVersions: Object.keys(packument.versions)
|
|
23369
|
+
remainingVersions: Object.keys(packument.versions),
|
|
23370
|
+
tarballDeleted,
|
|
23371
|
+
...tarballError ? { tarballWarning: tarballError } : {}
|
|
23125
23372
|
}
|
|
23126
23373
|
};
|
|
23127
23374
|
}
|
|
23128
23375
|
},
|
|
23129
23376
|
// ───────────────────────────────────────────────────────
|
|
23377
|
+
// npm_unpublish_package
|
|
23378
|
+
// ───────────────────────────────────────────────────────
|
|
23379
|
+
{
|
|
23380
|
+
name: "npm_unpublish_package",
|
|
23381
|
+
description: "Unpublish an ENTIRE package (all versions). DELETE /{pkg}/-rev/{rev}. IRREVERSIBLE: the name is blocked for 72 hours and cannot be re-published. For single-version unpublish prefer npm_unpublish_version. Requires confirm: true.",
|
|
23382
|
+
annotations: {
|
|
23383
|
+
title: "Unpublish entire package",
|
|
23384
|
+
readOnlyHint: false,
|
|
23385
|
+
destructiveHint: true,
|
|
23386
|
+
idempotentHint: false,
|
|
23387
|
+
openWorldHint: true
|
|
23388
|
+
},
|
|
23389
|
+
inputSchema: external_exports.object({
|
|
23390
|
+
name: external_exports.string().describe("Package name"),
|
|
23391
|
+
confirm: external_exports.literal(true).describe("Must be literally true. Guards against accidental full unpublish.")
|
|
23392
|
+
}),
|
|
23393
|
+
handler: async (input) => {
|
|
23394
|
+
const authErr = requireAuth();
|
|
23395
|
+
if (authErr) return authErr;
|
|
23396
|
+
if (input.confirm !== true) {
|
|
23397
|
+
return {
|
|
23398
|
+
ok: false,
|
|
23399
|
+
status: 400,
|
|
23400
|
+
error: "Full-package unpublish requires confirm: true. This blocks the name for 72 hours."
|
|
23401
|
+
};
|
|
23402
|
+
}
|
|
23403
|
+
const pRes = await fetchPackument(input.name);
|
|
23404
|
+
if (!pRes.ok) return translateError(pRes, { pkg: input.name, op: "unpublish_package (fetch)" });
|
|
23405
|
+
const rev = pRes.data._rev;
|
|
23406
|
+
if (!rev) {
|
|
23407
|
+
return {
|
|
23408
|
+
ok: false,
|
|
23409
|
+
status: 500,
|
|
23410
|
+
error: `Packument for ${input.name} missing _rev \u2014 cannot unpublish.`
|
|
23411
|
+
};
|
|
23412
|
+
}
|
|
23413
|
+
const delRes = await registryDeleteAuth(`/${encPkg(input.name)}/-rev/${encodeURIComponent(rev)}`);
|
|
23414
|
+
if (!delRes.ok) return translateError(delRes, { pkg: input.name, op: "unpublish_package (DELETE)" });
|
|
23415
|
+
return {
|
|
23416
|
+
ok: true,
|
|
23417
|
+
status: 200,
|
|
23418
|
+
data: { package: input.name, unpublished: true }
|
|
23419
|
+
};
|
|
23420
|
+
}
|
|
23421
|
+
},
|
|
23422
|
+
// ───────────────────────────────────────────────────────
|
|
23130
23423
|
// npm_dist_tag_set
|
|
23131
23424
|
// ───────────────────────────────────────────────────────
|
|
23132
23425
|
{
|
|
@@ -23204,9 +23497,13 @@ var writeTools = [
|
|
|
23204
23497
|
// ───────────────────────────────────────────────────────
|
|
23205
23498
|
// npm_owner_add
|
|
23206
23499
|
// ───────────────────────────────────────────────────────
|
|
23500
|
+
// Mirrors npm CLI owner.js:
|
|
23501
|
+
// 1. GET /-/user/org.couchdb.user:{user} → resolve {name, email}
|
|
23502
|
+
// 2. GET /{pkg}?write=true → packument with _rev
|
|
23503
|
+
// 3. PUT /{pkg}/-rev/{_rev} body {_id, _rev, maintainers}
|
|
23207
23504
|
{
|
|
23208
23505
|
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.",
|
|
23506
|
+
description: "Add a user as a maintainer of a package. They will have publish and write permissions. Resolves the user's email via /-/user/ (no need to supply it). Use npm_collaborators to verify before adding.",
|
|
23210
23507
|
annotations: {
|
|
23211
23508
|
title: "Add package owner",
|
|
23212
23509
|
readOnlyHint: false,
|
|
@@ -23216,38 +23513,53 @@ var writeTools = [
|
|
|
23216
23513
|
},
|
|
23217
23514
|
inputSchema: external_exports.object({
|
|
23218
23515
|
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)")
|
|
23516
|
+
username: external_exports.string().describe("npm username to add as maintainer")
|
|
23221
23517
|
}),
|
|
23222
23518
|
handler: async (input) => {
|
|
23223
23519
|
const authErr = requireAuth();
|
|
23224
23520
|
if (authErr) return authErr;
|
|
23521
|
+
const uRes = await registryGetAuth(
|
|
23522
|
+
`/-/user/org.couchdb.user:${encodeURIComponent(input.username)}`
|
|
23523
|
+
);
|
|
23524
|
+
if (!uRes.ok) return translateError(uRes, { pkg: input.name, op: `owner_add (resolve user ${input.username})` });
|
|
23525
|
+
const userRecord = { name: uRes.data.name, email: uRes.data.email ?? "" };
|
|
23225
23526
|
const pRes = await fetchPackument(input.name);
|
|
23226
23527
|
if (!pRes.ok) return translateError(pRes, { pkg: input.name, op: "owner_add (fetch)" });
|
|
23227
23528
|
const packument = pRes.data;
|
|
23228
|
-
|
|
23229
|
-
if (
|
|
23529
|
+
const owners = packument.maintainers || [];
|
|
23530
|
+
if (owners.some((m) => m.name === userRecord.name)) {
|
|
23230
23531
|
return {
|
|
23231
23532
|
ok: true,
|
|
23232
23533
|
status: 200,
|
|
23233
23534
|
data: {
|
|
23234
23535
|
package: input.name,
|
|
23235
|
-
username:
|
|
23536
|
+
username: userRecord.name,
|
|
23236
23537
|
alreadyOwner: true,
|
|
23237
|
-
maintainers:
|
|
23538
|
+
maintainers: owners.map((m) => m.name)
|
|
23238
23539
|
}
|
|
23239
23540
|
};
|
|
23240
23541
|
}
|
|
23241
|
-
packument.
|
|
23242
|
-
|
|
23542
|
+
if (!packument._rev) {
|
|
23543
|
+
return {
|
|
23544
|
+
ok: false,
|
|
23545
|
+
status: 500,
|
|
23546
|
+
error: `Packument for ${input.name} missing _rev \u2014 cannot update owners.`
|
|
23547
|
+
};
|
|
23548
|
+
}
|
|
23549
|
+
const maintainers = [...owners, userRecord];
|
|
23550
|
+
const putRes = await registryPutAuth(`/${encPkg(input.name)}/-rev/${encodeURIComponent(packument._rev)}`, {
|
|
23551
|
+
_id: packument._id,
|
|
23552
|
+
_rev: packument._rev,
|
|
23553
|
+
maintainers
|
|
23554
|
+
});
|
|
23243
23555
|
if (!putRes.ok) return translateError(putRes, { pkg: input.name, op: "owner_add (write)" });
|
|
23244
23556
|
return {
|
|
23245
23557
|
ok: true,
|
|
23246
23558
|
status: 200,
|
|
23247
23559
|
data: {
|
|
23248
23560
|
package: input.name,
|
|
23249
|
-
addedOwner:
|
|
23250
|
-
maintainers:
|
|
23561
|
+
addedOwner: userRecord.name,
|
|
23562
|
+
maintainers: maintainers.map((m) => m.name)
|
|
23251
23563
|
}
|
|
23252
23564
|
};
|
|
23253
23565
|
}
|
|
@@ -23291,8 +23603,18 @@ var writeTools = [
|
|
|
23291
23603
|
error: `Removing ${input.username} would leave ${input.name} with zero maintainers (lockout). Add another maintainer first with npm_owner_add.`
|
|
23292
23604
|
};
|
|
23293
23605
|
}
|
|
23294
|
-
packument.
|
|
23295
|
-
|
|
23606
|
+
if (!packument._rev) {
|
|
23607
|
+
return {
|
|
23608
|
+
ok: false,
|
|
23609
|
+
status: 500,
|
|
23610
|
+
error: `Packument for ${input.name} missing _rev \u2014 cannot update owners.`
|
|
23611
|
+
};
|
|
23612
|
+
}
|
|
23613
|
+
const putRes = await registryPutAuth(`/${encPkg(input.name)}/-rev/${encodeURIComponent(packument._rev)}`, {
|
|
23614
|
+
_id: packument._id,
|
|
23615
|
+
_rev: packument._rev,
|
|
23616
|
+
maintainers: after
|
|
23617
|
+
});
|
|
23296
23618
|
if (!putRes.ok) return translateError(putRes, { pkg: input.name, op: "owner_remove (write)" });
|
|
23297
23619
|
return {
|
|
23298
23620
|
ok: true,
|
|
@@ -23304,11 +23626,368 @@ var writeTools = [
|
|
|
23304
23626
|
}
|
|
23305
23627
|
};
|
|
23306
23628
|
}
|
|
23629
|
+
},
|
|
23630
|
+
// ───────────────────────────────────────────────────────
|
|
23631
|
+
// npm_access_set
|
|
23632
|
+
// ───────────────────────────────────────────────────────
|
|
23633
|
+
{
|
|
23634
|
+
name: "npm_access_set",
|
|
23635
|
+
description: "Set package access level: 'public', 'private', or 'restricted'. Unscoped packages are always public. Private access requires a paid npm account.",
|
|
23636
|
+
annotations: {
|
|
23637
|
+
title: "Set package access level",
|
|
23638
|
+
readOnlyHint: false,
|
|
23639
|
+
destructiveHint: true,
|
|
23640
|
+
idempotentHint: true,
|
|
23641
|
+
openWorldHint: true
|
|
23642
|
+
},
|
|
23643
|
+
inputSchema: external_exports.object({
|
|
23644
|
+
name: external_exports.string().describe("Package name"),
|
|
23645
|
+
access: external_exports.enum(["public", "private", "restricted"]).describe("Access level")
|
|
23646
|
+
}),
|
|
23647
|
+
handler: async (input) => {
|
|
23648
|
+
const authErr = requireAuth();
|
|
23649
|
+
if (authErr) return authErr;
|
|
23650
|
+
const res = await registryPostAuth(`/-/package/${encPkg(input.name)}/access`, { access: input.access });
|
|
23651
|
+
if (!res.ok) return translateError(res, { pkg: input.name, op: `access_set ${input.access}` });
|
|
23652
|
+
return {
|
|
23653
|
+
ok: true,
|
|
23654
|
+
status: 200,
|
|
23655
|
+
data: { package: input.name, access: input.access }
|
|
23656
|
+
};
|
|
23657
|
+
}
|
|
23658
|
+
},
|
|
23659
|
+
// ───────────────────────────────────────────────────────
|
|
23660
|
+
// npm_access_set_mfa
|
|
23661
|
+
// ───────────────────────────────────────────────────────
|
|
23662
|
+
{
|
|
23663
|
+
name: "npm_access_set_mfa",
|
|
23664
|
+
description: "Configure 2FA requirement for publishing: 'none' (off), 'publish' (2FA required), 'automation' (2FA required but automation tokens can bypass).",
|
|
23665
|
+
annotations: {
|
|
23666
|
+
title: "Set package 2FA publish policy",
|
|
23667
|
+
readOnlyHint: false,
|
|
23668
|
+
destructiveHint: true,
|
|
23669
|
+
idempotentHint: true,
|
|
23670
|
+
openWorldHint: true
|
|
23671
|
+
},
|
|
23672
|
+
inputSchema: external_exports.object({
|
|
23673
|
+
name: external_exports.string().describe("Package name"),
|
|
23674
|
+
level: external_exports.enum(["none", "publish", "automation"]).describe("MFA level for publish")
|
|
23675
|
+
}),
|
|
23676
|
+
handler: async (input) => {
|
|
23677
|
+
const authErr = requireAuth();
|
|
23678
|
+
if (authErr) return authErr;
|
|
23679
|
+
let body;
|
|
23680
|
+
if (input.level === "none") {
|
|
23681
|
+
body = { publish_requires_tfa: false };
|
|
23682
|
+
} else if (input.level === "publish") {
|
|
23683
|
+
body = { publish_requires_tfa: true, automation_token_overrides_tfa: false };
|
|
23684
|
+
} else {
|
|
23685
|
+
body = { publish_requires_tfa: true, automation_token_overrides_tfa: true };
|
|
23686
|
+
}
|
|
23687
|
+
const res = await registryPostAuth(`/-/package/${encPkg(input.name)}/access`, body);
|
|
23688
|
+
if (!res.ok) return translateError(res, { pkg: input.name, op: `access_set_mfa ${input.level}` });
|
|
23689
|
+
return {
|
|
23690
|
+
ok: true,
|
|
23691
|
+
status: 200,
|
|
23692
|
+
data: { package: input.name, mfaLevel: input.level }
|
|
23693
|
+
};
|
|
23694
|
+
}
|
|
23695
|
+
},
|
|
23696
|
+
// ───────────────────────────────────────────────────────
|
|
23697
|
+
// npm_team_grant
|
|
23698
|
+
// ───────────────────────────────────────────────────────
|
|
23699
|
+
{
|
|
23700
|
+
name: "npm_team_grant",
|
|
23701
|
+
description: "Grant a team read-only or read-write permission on a package. Scope and team are passed as @scope:team (e.g. '@yawlabs:devs'). Requires org admin or team admin.",
|
|
23702
|
+
annotations: {
|
|
23703
|
+
title: "Grant team package permission",
|
|
23704
|
+
readOnlyHint: false,
|
|
23705
|
+
destructiveHint: true,
|
|
23706
|
+
idempotentHint: true,
|
|
23707
|
+
openWorldHint: true
|
|
23708
|
+
},
|
|
23709
|
+
inputSchema: external_exports.object({
|
|
23710
|
+
team: external_exports.string().describe("Team in the form '@scope:team' (e.g. '@yawlabs:devs')"),
|
|
23711
|
+
package: external_exports.string().describe("Package name"),
|
|
23712
|
+
permissions: external_exports.enum(["read-only", "read-write"]).describe("Permission level")
|
|
23713
|
+
}),
|
|
23714
|
+
handler: async (input) => {
|
|
23715
|
+
const authErr = requireAuth();
|
|
23716
|
+
if (authErr) return authErr;
|
|
23717
|
+
const parsed = parseTeamTarget(input.team);
|
|
23718
|
+
if (!parsed) {
|
|
23719
|
+
return {
|
|
23720
|
+
ok: false,
|
|
23721
|
+
status: 400,
|
|
23722
|
+
error: `Team must be in the form '@scope:team' (got '${input.team}').`
|
|
23723
|
+
};
|
|
23724
|
+
}
|
|
23725
|
+
const { scope, team } = parsed;
|
|
23726
|
+
const res = await registryPutAuth(`/-/team/${encodeURIComponent(scope)}/${encodeURIComponent(team)}/package`, {
|
|
23727
|
+
package: input.package,
|
|
23728
|
+
permissions: input.permissions
|
|
23729
|
+
});
|
|
23730
|
+
if (!res.ok) return translateError(res, { pkg: input.package, op: `team_grant ${input.team}` });
|
|
23731
|
+
return {
|
|
23732
|
+
ok: true,
|
|
23733
|
+
status: 200,
|
|
23734
|
+
data: { team: `@${scope}:${team}`, package: input.package, permissions: input.permissions }
|
|
23735
|
+
};
|
|
23736
|
+
}
|
|
23737
|
+
},
|
|
23738
|
+
// ───────────────────────────────────────────────────────
|
|
23739
|
+
// npm_team_revoke
|
|
23740
|
+
// ───────────────────────────────────────────────────────
|
|
23741
|
+
{
|
|
23742
|
+
name: "npm_team_revoke",
|
|
23743
|
+
description: "Revoke a team's access to a package. Team is passed as '@scope:team'. Does not delete the team itself \u2014 use npm_team_delete for that.",
|
|
23744
|
+
annotations: {
|
|
23745
|
+
title: "Revoke team package permission",
|
|
23746
|
+
readOnlyHint: false,
|
|
23747
|
+
destructiveHint: true,
|
|
23748
|
+
idempotentHint: true,
|
|
23749
|
+
openWorldHint: true
|
|
23750
|
+
},
|
|
23751
|
+
inputSchema: external_exports.object({
|
|
23752
|
+
team: external_exports.string().describe("Team in the form '@scope:team'"),
|
|
23753
|
+
package: external_exports.string().describe("Package name")
|
|
23754
|
+
}),
|
|
23755
|
+
handler: async (input) => {
|
|
23756
|
+
const authErr = requireAuth();
|
|
23757
|
+
if (authErr) return authErr;
|
|
23758
|
+
const parsed = parseTeamTarget(input.team);
|
|
23759
|
+
if (!parsed) {
|
|
23760
|
+
return {
|
|
23761
|
+
ok: false,
|
|
23762
|
+
status: 400,
|
|
23763
|
+
error: `Team must be in the form '@scope:team' (got '${input.team}').`
|
|
23764
|
+
};
|
|
23765
|
+
}
|
|
23766
|
+
const { scope, team } = parsed;
|
|
23767
|
+
const res = await registryDeleteAuth(`/-/team/${encodeURIComponent(scope)}/${encodeURIComponent(team)}/package`, {
|
|
23768
|
+
package: input.package
|
|
23769
|
+
});
|
|
23770
|
+
if (!res.ok) return translateError(res, { pkg: input.package, op: `team_revoke ${input.team}` });
|
|
23771
|
+
return {
|
|
23772
|
+
ok: true,
|
|
23773
|
+
status: 200,
|
|
23774
|
+
data: { team: `@${scope}:${team}`, package: input.package, revoked: true }
|
|
23775
|
+
};
|
|
23776
|
+
}
|
|
23777
|
+
},
|
|
23778
|
+
// ───────────────────────────────────────────────────────
|
|
23779
|
+
// npm_team_create
|
|
23780
|
+
// ───────────────────────────────────────────────────────
|
|
23781
|
+
{
|
|
23782
|
+
name: "npm_team_create",
|
|
23783
|
+
description: "Create a team inside an organization. Team is passed as '@scope:team'.",
|
|
23784
|
+
annotations: {
|
|
23785
|
+
title: "Create team",
|
|
23786
|
+
readOnlyHint: false,
|
|
23787
|
+
destructiveHint: true,
|
|
23788
|
+
idempotentHint: false,
|
|
23789
|
+
openWorldHint: true
|
|
23790
|
+
},
|
|
23791
|
+
inputSchema: external_exports.object({
|
|
23792
|
+
team: external_exports.string().describe("Team in the form '@scope:team'"),
|
|
23793
|
+
description: external_exports.string().optional().describe("Optional team description")
|
|
23794
|
+
}),
|
|
23795
|
+
handler: async (input) => {
|
|
23796
|
+
const authErr = requireAuth();
|
|
23797
|
+
if (authErr) return authErr;
|
|
23798
|
+
const parsed = parseTeamTarget(input.team);
|
|
23799
|
+
if (!parsed) {
|
|
23800
|
+
return { ok: false, status: 400, error: `Team must be in the form '@scope:team' (got '${input.team}').` };
|
|
23801
|
+
}
|
|
23802
|
+
const { scope, team } = parsed;
|
|
23803
|
+
const res = await registryPutAuth(`/-/org/${encodeURIComponent(scope)}/team`, {
|
|
23804
|
+
name: team,
|
|
23805
|
+
description: input.description
|
|
23806
|
+
});
|
|
23807
|
+
if (!res.ok) return translateError(res, { op: `team_create ${input.team}` });
|
|
23808
|
+
return { ok: true, status: 200, data: { team: `@${scope}:${team}`, created: true } };
|
|
23809
|
+
}
|
|
23810
|
+
},
|
|
23811
|
+
// ───────────────────────────────────────────────────────
|
|
23812
|
+
// npm_team_delete
|
|
23813
|
+
// ───────────────────────────────────────────────────────
|
|
23814
|
+
{
|
|
23815
|
+
name: "npm_team_delete",
|
|
23816
|
+
description: "Delete a team. Team is passed as '@scope:team'. Revokes all package permissions that team held.",
|
|
23817
|
+
annotations: {
|
|
23818
|
+
title: "Delete team",
|
|
23819
|
+
readOnlyHint: false,
|
|
23820
|
+
destructiveHint: true,
|
|
23821
|
+
idempotentHint: true,
|
|
23822
|
+
openWorldHint: true
|
|
23823
|
+
},
|
|
23824
|
+
inputSchema: external_exports.object({
|
|
23825
|
+
team: external_exports.string().describe("Team in the form '@scope:team'")
|
|
23826
|
+
}),
|
|
23827
|
+
handler: async (input) => {
|
|
23828
|
+
const authErr = requireAuth();
|
|
23829
|
+
if (authErr) return authErr;
|
|
23830
|
+
const parsed = parseTeamTarget(input.team);
|
|
23831
|
+
if (!parsed) {
|
|
23832
|
+
return { ok: false, status: 400, error: `Team must be in the form '@scope:team' (got '${input.team}').` };
|
|
23833
|
+
}
|
|
23834
|
+
const { scope, team } = parsed;
|
|
23835
|
+
const res = await registryDeleteAuth(`/-/team/${encodeURIComponent(scope)}/${encodeURIComponent(team)}`);
|
|
23836
|
+
if (!res.ok) return translateError(res, { op: `team_delete ${input.team}` });
|
|
23837
|
+
return { ok: true, status: 200, data: { team: `@${scope}:${team}`, deleted: true } };
|
|
23838
|
+
}
|
|
23839
|
+
},
|
|
23840
|
+
// ───────────────────────────────────────────────────────
|
|
23841
|
+
// npm_team_member_add
|
|
23842
|
+
// ───────────────────────────────────────────────────────
|
|
23843
|
+
{
|
|
23844
|
+
name: "npm_team_member_add",
|
|
23845
|
+
description: "Add a user to a team. Team is '@scope:team'. User must already be in the org.",
|
|
23846
|
+
annotations: {
|
|
23847
|
+
title: "Add team member",
|
|
23848
|
+
readOnlyHint: false,
|
|
23849
|
+
destructiveHint: true,
|
|
23850
|
+
idempotentHint: true,
|
|
23851
|
+
openWorldHint: true
|
|
23852
|
+
},
|
|
23853
|
+
inputSchema: external_exports.object({
|
|
23854
|
+
team: external_exports.string().describe("Team in the form '@scope:team'"),
|
|
23855
|
+
user: external_exports.string().describe("npm username")
|
|
23856
|
+
}),
|
|
23857
|
+
handler: async (input) => {
|
|
23858
|
+
const authErr = requireAuth();
|
|
23859
|
+
if (authErr) return authErr;
|
|
23860
|
+
const parsed = parseTeamTarget(input.team);
|
|
23861
|
+
if (!parsed) {
|
|
23862
|
+
return { ok: false, status: 400, error: `Team must be in the form '@scope:team' (got '${input.team}').` };
|
|
23863
|
+
}
|
|
23864
|
+
const { scope, team } = parsed;
|
|
23865
|
+
const res = await registryPutAuth(`/-/team/${encodeURIComponent(scope)}/${encodeURIComponent(team)}/user`, {
|
|
23866
|
+
user: input.user
|
|
23867
|
+
});
|
|
23868
|
+
if (!res.ok) return translateError(res, { op: `team_member_add ${input.team}` });
|
|
23869
|
+
return { ok: true, status: 200, data: { team: `@${scope}:${team}`, addedUser: input.user } };
|
|
23870
|
+
}
|
|
23871
|
+
},
|
|
23872
|
+
// ───────────────────────────────────────────────────────
|
|
23873
|
+
// npm_team_member_remove
|
|
23874
|
+
// ───────────────────────────────────────────────────────
|
|
23875
|
+
{
|
|
23876
|
+
name: "npm_team_member_remove",
|
|
23877
|
+
description: "Remove a user from a team. Team is '@scope:team'. User remains in the org.",
|
|
23878
|
+
annotations: {
|
|
23879
|
+
title: "Remove team member",
|
|
23880
|
+
readOnlyHint: false,
|
|
23881
|
+
destructiveHint: true,
|
|
23882
|
+
idempotentHint: true,
|
|
23883
|
+
openWorldHint: true
|
|
23884
|
+
},
|
|
23885
|
+
inputSchema: external_exports.object({
|
|
23886
|
+
team: external_exports.string().describe("Team in the form '@scope:team'"),
|
|
23887
|
+
user: external_exports.string().describe("npm username")
|
|
23888
|
+
}),
|
|
23889
|
+
handler: async (input) => {
|
|
23890
|
+
const authErr = requireAuth();
|
|
23891
|
+
if (authErr) return authErr;
|
|
23892
|
+
const parsed = parseTeamTarget(input.team);
|
|
23893
|
+
if (!parsed) {
|
|
23894
|
+
return { ok: false, status: 400, error: `Team must be in the form '@scope:team' (got '${input.team}').` };
|
|
23895
|
+
}
|
|
23896
|
+
const { scope, team } = parsed;
|
|
23897
|
+
const res = await registryDeleteAuth(`/-/team/${encodeURIComponent(scope)}/${encodeURIComponent(team)}/user`, {
|
|
23898
|
+
user: input.user
|
|
23899
|
+
});
|
|
23900
|
+
if (!res.ok) return translateError(res, { op: `team_member_remove ${input.team}` });
|
|
23901
|
+
return { ok: true, status: 200, data: { team: `@${scope}:${team}`, removedUser: input.user } };
|
|
23902
|
+
}
|
|
23903
|
+
},
|
|
23904
|
+
// ───────────────────────────────────────────────────────
|
|
23905
|
+
// npm_org_member_set
|
|
23906
|
+
// ───────────────────────────────────────────────────────
|
|
23907
|
+
{
|
|
23908
|
+
name: "npm_org_member_set",
|
|
23909
|
+
description: "Add a user to an org or change their role. Roles: 'developer', 'admin', 'owner'. If user is already in the org, updates the role. Omit role to keep existing role.",
|
|
23910
|
+
annotations: {
|
|
23911
|
+
title: "Set org member",
|
|
23912
|
+
readOnlyHint: false,
|
|
23913
|
+
destructiveHint: true,
|
|
23914
|
+
idempotentHint: true,
|
|
23915
|
+
openWorldHint: true
|
|
23916
|
+
},
|
|
23917
|
+
inputSchema: external_exports.object({
|
|
23918
|
+
org: external_exports.string().describe("Organization name (with or without leading @)"),
|
|
23919
|
+
user: external_exports.string().describe("npm username"),
|
|
23920
|
+
role: external_exports.enum(["developer", "admin", "owner"]).optional().describe("Role to assign")
|
|
23921
|
+
}),
|
|
23922
|
+
handler: async (input) => {
|
|
23923
|
+
const authErr = requireAuth();
|
|
23924
|
+
if (authErr) return authErr;
|
|
23925
|
+
const org = input.org.replace(/^@/, "");
|
|
23926
|
+
const user = input.user.replace(/^@/, "");
|
|
23927
|
+
const body = { user };
|
|
23928
|
+
if (input.role) body.role = input.role;
|
|
23929
|
+
const res = await registryPutAuth(`/-/org/${encodeURIComponent(org)}/user`, body);
|
|
23930
|
+
if (!res.ok) return translateError(res, { op: `org_member_set ${org}/${user}` });
|
|
23931
|
+
return { ok: true, status: 200, data: { org, user, role: input.role } };
|
|
23932
|
+
}
|
|
23933
|
+
},
|
|
23934
|
+
// ───────────────────────────────────────────────────────
|
|
23935
|
+
// npm_org_member_remove
|
|
23936
|
+
// ───────────────────────────────────────────────────────
|
|
23937
|
+
{
|
|
23938
|
+
name: "npm_org_member_remove",
|
|
23939
|
+
description: "Remove a user from an org. Their team memberships in that org are also removed.",
|
|
23940
|
+
annotations: {
|
|
23941
|
+
title: "Remove org member",
|
|
23942
|
+
readOnlyHint: false,
|
|
23943
|
+
destructiveHint: true,
|
|
23944
|
+
idempotentHint: true,
|
|
23945
|
+
openWorldHint: true
|
|
23946
|
+
},
|
|
23947
|
+
inputSchema: external_exports.object({
|
|
23948
|
+
org: external_exports.string().describe("Organization name"),
|
|
23949
|
+
user: external_exports.string().describe("npm username")
|
|
23950
|
+
}),
|
|
23951
|
+
handler: async (input) => {
|
|
23952
|
+
const authErr = requireAuth();
|
|
23953
|
+
if (authErr) return authErr;
|
|
23954
|
+
const org = input.org.replace(/^@/, "");
|
|
23955
|
+
const user = input.user.replace(/^@/, "");
|
|
23956
|
+
const res = await registryDeleteAuth(`/-/org/${encodeURIComponent(org)}/user`, { user });
|
|
23957
|
+
if (!res.ok) return translateError(res, { op: `org_member_remove ${org}/${user}` });
|
|
23958
|
+
return { ok: true, status: 200, data: { org, removedUser: user } };
|
|
23959
|
+
}
|
|
23960
|
+
},
|
|
23961
|
+
// ───────────────────────────────────────────────────────
|
|
23962
|
+
// npm_token_revoke
|
|
23963
|
+
// ───────────────────────────────────────────────────────
|
|
23964
|
+
// Note: token CREATION requires a user password (not a token), so it cannot be
|
|
23965
|
+
// performed via NPM_TOKEN alone — we intentionally don't expose npm_token_create.
|
|
23966
|
+
{
|
|
23967
|
+
name: "npm_token_revoke",
|
|
23968
|
+
description: "Revoke an access token by its key (UUID from npm_tokens). Creating tokens is NOT exposed because the endpoint requires the user password \u2014 create via https://www.npmjs.com/settings/~/tokens instead.",
|
|
23969
|
+
annotations: {
|
|
23970
|
+
title: "Revoke access token",
|
|
23971
|
+
readOnlyHint: false,
|
|
23972
|
+
destructiveHint: true,
|
|
23973
|
+
idempotentHint: true,
|
|
23974
|
+
openWorldHint: true
|
|
23975
|
+
},
|
|
23976
|
+
inputSchema: external_exports.object({
|
|
23977
|
+
tokenKey: external_exports.string().describe("Token key (UUID shown by npm_tokens)")
|
|
23978
|
+
}),
|
|
23979
|
+
handler: async (input) => {
|
|
23980
|
+
const authErr = requireAuth();
|
|
23981
|
+
if (authErr) return authErr;
|
|
23982
|
+
const res = await registryDeleteAuth(`/-/npm/v1/tokens/token/${encodeURIComponent(input.tokenKey)}`);
|
|
23983
|
+
if (!res.ok) return translateError(res, { op: "token_revoke" });
|
|
23984
|
+
return { ok: true, status: 200, data: { tokenKey: input.tokenKey, revoked: true } };
|
|
23985
|
+
}
|
|
23307
23986
|
}
|
|
23308
23987
|
];
|
|
23309
23988
|
|
|
23310
23989
|
// src/index.ts
|
|
23311
|
-
var version2 = true ? "0.
|
|
23990
|
+
var version2 = true ? "0.8.0" : (await null).createRequire(import.meta.url)("../package.json").version;
|
|
23312
23991
|
var subcommand = process.argv[2];
|
|
23313
23992
|
if (subcommand === "version" || subcommand === "--version") {
|
|
23314
23993
|
console.log(version2);
|
|
@@ -23328,7 +24007,8 @@ var allTools = [
|
|
|
23328
24007
|
...provenanceTools,
|
|
23329
24008
|
...trustTools,
|
|
23330
24009
|
...workflowTools,
|
|
23331
|
-
...writeTools
|
|
24010
|
+
...writeTools,
|
|
24011
|
+
...hookTools
|
|
23332
24012
|
];
|
|
23333
24013
|
var server = new McpServer({
|
|
23334
24014
|
name: "@yawlabs/npmjs-mcp",
|
package/package.json
CHANGED