@yawlabs/npmjs-mcp 0.6.0 → 0.7.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 +706 -79
- 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
|
@@ -21081,11 +21081,14 @@ function registryPost(path, body) {
|
|
|
21081
21081
|
function registryGetAuth(path) {
|
|
21082
21082
|
return request(REGISTRY_URL, path, { headers: authHeaders() });
|
|
21083
21083
|
}
|
|
21084
|
+
function registryPostAuth(path, body) {
|
|
21085
|
+
return request(REGISTRY_URL, path, { method: "POST", body, headers: authHeaders() });
|
|
21086
|
+
}
|
|
21084
21087
|
function registryPutAuth(path, body) {
|
|
21085
21088
|
return request(REGISTRY_URL, path, { method: "PUT", body, headers: authHeaders() });
|
|
21086
21089
|
}
|
|
21087
|
-
function registryDeleteAuth(path) {
|
|
21088
|
-
return request(REGISTRY_URL, path, { method: "DELETE", headers: authHeaders() });
|
|
21090
|
+
function registryDeleteAuth(path, body) {
|
|
21091
|
+
return request(REGISTRY_URL, path, { method: "DELETE", body, headers: authHeaders() });
|
|
21089
21092
|
}
|
|
21090
21093
|
function downloadsGet(path) {
|
|
21091
21094
|
return request(DOWNLOADS_URL, path);
|
|
@@ -21948,6 +21951,195 @@ var downloadTools = [
|
|
|
21948
21951
|
}
|
|
21949
21952
|
];
|
|
21950
21953
|
|
|
21954
|
+
// src/errors.ts
|
|
21955
|
+
function translateError(res, context) {
|
|
21956
|
+
if (res.ok) return res;
|
|
21957
|
+
const pkgPart = context.pkg ? ` for ${context.pkg}` : "";
|
|
21958
|
+
const opPart = context.op ? ` during ${context.op}` : "";
|
|
21959
|
+
switch (res.status) {
|
|
21960
|
+
case 401:
|
|
21961
|
+
return {
|
|
21962
|
+
...res,
|
|
21963
|
+
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}`
|
|
21964
|
+
};
|
|
21965
|
+
case 403:
|
|
21966
|
+
return {
|
|
21967
|
+
...res,
|
|
21968
|
+
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}`
|
|
21969
|
+
};
|
|
21970
|
+
case 404:
|
|
21971
|
+
return {
|
|
21972
|
+
...res,
|
|
21973
|
+
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}`
|
|
21974
|
+
};
|
|
21975
|
+
case 422:
|
|
21976
|
+
return {
|
|
21977
|
+
...res,
|
|
21978
|
+
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}`
|
|
21979
|
+
};
|
|
21980
|
+
case 429:
|
|
21981
|
+
return {
|
|
21982
|
+
...res,
|
|
21983
|
+
error: `Rate limited${opPart}. Wait 60 seconds and retry. Raw: ${res.error}`
|
|
21984
|
+
};
|
|
21985
|
+
case 0:
|
|
21986
|
+
return {
|
|
21987
|
+
...res,
|
|
21988
|
+
error: `Network error${opPart}. Could not reach the registry. Raw: ${res.error}`
|
|
21989
|
+
};
|
|
21990
|
+
default:
|
|
21991
|
+
return res;
|
|
21992
|
+
}
|
|
21993
|
+
}
|
|
21994
|
+
function validateDeprecationMessage(msg) {
|
|
21995
|
+
if (msg.length > 1024) {
|
|
21996
|
+
return "Deprecation message exceeds 1024 characters (registry limit).";
|
|
21997
|
+
}
|
|
21998
|
+
if (msg.length === 0) return null;
|
|
21999
|
+
if (/\.\s+[A-Z]/.test(msg)) {
|
|
22000
|
+
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.`;
|
|
22001
|
+
}
|
|
22002
|
+
return null;
|
|
22003
|
+
}
|
|
22004
|
+
function versionsMatchingRange(versions, range, maxSatisfying2) {
|
|
22005
|
+
if (range === "*" || range === "") return [...versions];
|
|
22006
|
+
return versions.filter((v) => maxSatisfying2([v], range) === v);
|
|
22007
|
+
}
|
|
22008
|
+
|
|
22009
|
+
// src/tools/hooks.ts
|
|
22010
|
+
function classifyHookTarget(target) {
|
|
22011
|
+
if (target.startsWith("~")) return { type: "owner", name: target.slice(1) };
|
|
22012
|
+
if (/^@[^/]+$/.test(target)) return { type: "scope", name: target };
|
|
22013
|
+
return { type: "package", name: target };
|
|
22014
|
+
}
|
|
22015
|
+
var hookTools = [
|
|
22016
|
+
{
|
|
22017
|
+
name: "npm_hook_add",
|
|
22018
|
+
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.",
|
|
22019
|
+
annotations: {
|
|
22020
|
+
title: "Add webhook",
|
|
22021
|
+
readOnlyHint: false,
|
|
22022
|
+
destructiveHint: true,
|
|
22023
|
+
idempotentHint: false,
|
|
22024
|
+
openWorldHint: true
|
|
22025
|
+
},
|
|
22026
|
+
inputSchema: external_exports.object({
|
|
22027
|
+
target: external_exports.string().describe("Hook target: 'pkg', '@scope/pkg', '@scope', or '~user'"),
|
|
22028
|
+
endpoint: external_exports.string().url().describe("HTTPS URL that will receive POST events"),
|
|
22029
|
+
secret: external_exports.string().describe("Secret used to HMAC-sign webhook payloads")
|
|
22030
|
+
}),
|
|
22031
|
+
handler: async (input) => {
|
|
22032
|
+
const authErr = requireAuth();
|
|
22033
|
+
if (authErr) return authErr;
|
|
22034
|
+
const { type, name } = classifyHookTarget(input.target);
|
|
22035
|
+
const res = await registryPostAuth("/-/npm/v1/hooks/hook", {
|
|
22036
|
+
type,
|
|
22037
|
+
name,
|
|
22038
|
+
endpoint: input.endpoint,
|
|
22039
|
+
secret: input.secret
|
|
22040
|
+
});
|
|
22041
|
+
if (!res.ok) return translateError(res, { op: `hook_add ${input.target}` });
|
|
22042
|
+
return { ok: true, status: 200, data: res.data };
|
|
22043
|
+
}
|
|
22044
|
+
},
|
|
22045
|
+
{
|
|
22046
|
+
name: "npm_hook_list",
|
|
22047
|
+
description: "List webhooks. Optionally filter by package name.",
|
|
22048
|
+
annotations: {
|
|
22049
|
+
title: "List webhooks",
|
|
22050
|
+
readOnlyHint: true,
|
|
22051
|
+
destructiveHint: false,
|
|
22052
|
+
idempotentHint: true,
|
|
22053
|
+
openWorldHint: true
|
|
22054
|
+
},
|
|
22055
|
+
inputSchema: external_exports.object({
|
|
22056
|
+
package: external_exports.string().optional().describe("Filter by package name"),
|
|
22057
|
+
limit: external_exports.number().int().optional().describe("Max results"),
|
|
22058
|
+
offset: external_exports.number().int().optional().describe("Pagination offset")
|
|
22059
|
+
}),
|
|
22060
|
+
handler: async (input) => {
|
|
22061
|
+
const authErr = requireAuth();
|
|
22062
|
+
if (authErr) return authErr;
|
|
22063
|
+
const qs = new URLSearchParams();
|
|
22064
|
+
if (input.package) qs.set("package", input.package);
|
|
22065
|
+
if (input.limit !== void 0) qs.set("limit", String(input.limit));
|
|
22066
|
+
if (input.offset !== void 0) qs.set("offset", String(input.offset));
|
|
22067
|
+
const q = qs.toString();
|
|
22068
|
+
const res = await registryGetAuth(`/-/npm/v1/hooks${q ? `?${q}` : ""}`);
|
|
22069
|
+
if (!res.ok) return translateError(res, { op: "hook_list" });
|
|
22070
|
+
return { ok: true, status: 200, data: res.data };
|
|
22071
|
+
}
|
|
22072
|
+
},
|
|
22073
|
+
{
|
|
22074
|
+
name: "npm_hook_get",
|
|
22075
|
+
description: "Get a single webhook by its ID.",
|
|
22076
|
+
annotations: {
|
|
22077
|
+
title: "Get webhook",
|
|
22078
|
+
readOnlyHint: true,
|
|
22079
|
+
destructiveHint: false,
|
|
22080
|
+
idempotentHint: true,
|
|
22081
|
+
openWorldHint: true
|
|
22082
|
+
},
|
|
22083
|
+
inputSchema: external_exports.object({
|
|
22084
|
+
id: external_exports.string().describe("Hook ID (UUID from npm_hook_list)")
|
|
22085
|
+
}),
|
|
22086
|
+
handler: async (input) => {
|
|
22087
|
+
const authErr = requireAuth();
|
|
22088
|
+
if (authErr) return authErr;
|
|
22089
|
+
const res = await registryGetAuth(`/-/npm/v1/hooks/hook/${encodeURIComponent(input.id)}`);
|
|
22090
|
+
if (!res.ok) return translateError(res, { op: `hook_get ${input.id}` });
|
|
22091
|
+
return { ok: true, status: 200, data: res.data };
|
|
22092
|
+
}
|
|
22093
|
+
},
|
|
22094
|
+
{
|
|
22095
|
+
name: "npm_hook_update",
|
|
22096
|
+
description: "Update a webhook's endpoint and/or secret.",
|
|
22097
|
+
annotations: {
|
|
22098
|
+
title: "Update webhook",
|
|
22099
|
+
readOnlyHint: false,
|
|
22100
|
+
destructiveHint: true,
|
|
22101
|
+
idempotentHint: true,
|
|
22102
|
+
openWorldHint: true
|
|
22103
|
+
},
|
|
22104
|
+
inputSchema: external_exports.object({
|
|
22105
|
+
id: external_exports.string().describe("Hook ID"),
|
|
22106
|
+
endpoint: external_exports.string().url().describe("New HTTPS URL"),
|
|
22107
|
+
secret: external_exports.string().describe("New signing secret")
|
|
22108
|
+
}),
|
|
22109
|
+
handler: async (input) => {
|
|
22110
|
+
const authErr = requireAuth();
|
|
22111
|
+
if (authErr) return authErr;
|
|
22112
|
+
const res = await registryPutAuth(`/-/npm/v1/hooks/hook/${encodeURIComponent(input.id)}`, {
|
|
22113
|
+
endpoint: input.endpoint,
|
|
22114
|
+
secret: input.secret
|
|
22115
|
+
});
|
|
22116
|
+
if (!res.ok) return translateError(res, { op: `hook_update ${input.id}` });
|
|
22117
|
+
return { ok: true, status: 200, data: res.data };
|
|
22118
|
+
}
|
|
22119
|
+
},
|
|
22120
|
+
{
|
|
22121
|
+
name: "npm_hook_remove",
|
|
22122
|
+
description: "Delete a webhook by ID.",
|
|
22123
|
+
annotations: {
|
|
22124
|
+
title: "Remove webhook",
|
|
22125
|
+
readOnlyHint: false,
|
|
22126
|
+
destructiveHint: true,
|
|
22127
|
+
idempotentHint: true,
|
|
22128
|
+
openWorldHint: true
|
|
22129
|
+
},
|
|
22130
|
+
inputSchema: external_exports.object({
|
|
22131
|
+
id: external_exports.string().describe("Hook ID")
|
|
22132
|
+
}),
|
|
22133
|
+
handler: async (input) => {
|
|
22134
|
+
const authErr = requireAuth();
|
|
22135
|
+
if (authErr) return authErr;
|
|
22136
|
+
const res = await registryDeleteAuth(`/-/npm/v1/hooks/hook/${encodeURIComponent(input.id)}`);
|
|
22137
|
+
if (!res.ok) return translateError(res, { op: `hook_remove ${input.id}` });
|
|
22138
|
+
return { ok: true, status: 200, data: { id: input.id, removed: true } };
|
|
22139
|
+
}
|
|
22140
|
+
}
|
|
22141
|
+
];
|
|
22142
|
+
|
|
21951
22143
|
// src/tools/orgs.ts
|
|
21952
22144
|
var orgTools = [
|
|
21953
22145
|
{
|
|
@@ -22904,65 +23096,20 @@ var workflowTools = [
|
|
|
22904
23096
|
}
|
|
22905
23097
|
];
|
|
22906
23098
|
|
|
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
23099
|
// src/tools/writes.ts
|
|
22963
23100
|
async function fetchPackument(pkg) {
|
|
22964
23101
|
return registryGetAuth(`/${encPkg(pkg)}?write=true`);
|
|
22965
23102
|
}
|
|
23103
|
+
function highestVersion(versions) {
|
|
23104
|
+
const parsed = [];
|
|
23105
|
+
for (const v of versions) {
|
|
23106
|
+
const m = v.match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
23107
|
+
if (m) parsed.push([Number(m[1]), Number(m[2]), Number(m[3]), v]);
|
|
23108
|
+
}
|
|
23109
|
+
if (parsed.length === 0) return null;
|
|
23110
|
+
parsed.sort((a, b) => a[0] - b[0] || a[1] - b[1] || a[2] - b[2]);
|
|
23111
|
+
return parsed[parsed.length - 1][3];
|
|
23112
|
+
}
|
|
22966
23113
|
var writeTools = [
|
|
22967
23114
|
// ───────────────────────────────────────────────────────
|
|
22968
23115
|
// npm_deprecate
|
|
@@ -23072,9 +23219,17 @@ var writeTools = [
|
|
|
23072
23219
|
// ───────────────────────────────────────────────────────
|
|
23073
23220
|
// npm_unpublish_version
|
|
23074
23221
|
// ───────────────────────────────────────────────────────
|
|
23222
|
+
// Mirrors libnpmpublish/unpublish.js single-version flow:
|
|
23223
|
+
// 1. GET /{pkg}?write=true
|
|
23224
|
+
// 2. mutate: remove version, fix dist-tags, delete _revisions/_attachments
|
|
23225
|
+
// 3. PUT /{pkg}/-rev/{rev}
|
|
23226
|
+
// 4. GET /{pkg}?write=true (fresh rev)
|
|
23227
|
+
// 5. DELETE {tarball-pathname}/-rev/{newRev}
|
|
23228
|
+
// The tarball DELETE is best-effort — if it fails, the version is already unreachable
|
|
23229
|
+
// via the packument (step 3 succeeded), so we report success with a warning.
|
|
23075
23230
|
{
|
|
23076
23231
|
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.",
|
|
23232
|
+
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
23233
|
annotations: {
|
|
23079
23234
|
title: "Unpublish version",
|
|
23080
23235
|
readOnlyHint: false,
|
|
@@ -23100,33 +23255,118 @@ var writeTools = [
|
|
|
23100
23255
|
const pRes = await fetchPackument(input.name);
|
|
23101
23256
|
if (!pRes.ok) return translateError(pRes, { pkg: input.name, op: "unpublish (fetch)" });
|
|
23102
23257
|
const packument = pRes.data;
|
|
23103
|
-
|
|
23258
|
+
const versionData = packument.versions?.[input.version];
|
|
23259
|
+
if (!versionData) {
|
|
23104
23260
|
return {
|
|
23105
23261
|
ok: false,
|
|
23106
23262
|
status: 404,
|
|
23107
|
-
error: `Version ${input.version} not found for ${input.name}. Published versions: ${Object.keys(packument.versions).join(", ")}.`
|
|
23263
|
+
error: `Version ${input.version} not found for ${input.name}. Published versions: ${Object.keys(packument.versions || {}).join(", ")}.`
|
|
23108
23264
|
};
|
|
23109
23265
|
}
|
|
23266
|
+
if (!packument._rev) {
|
|
23267
|
+
return {
|
|
23268
|
+
ok: false,
|
|
23269
|
+
status: 500,
|
|
23270
|
+
error: `Packument for ${input.name} missing _rev \u2014 cannot unpublish. Try again; this is usually transient.`
|
|
23271
|
+
};
|
|
23272
|
+
}
|
|
23273
|
+
const tarballUrl = versionData.dist?.tarball;
|
|
23274
|
+
const latestBefore = packument["dist-tags"]?.latest;
|
|
23110
23275
|
delete packument.versions[input.version];
|
|
23111
|
-
for (const tag of Object.keys(packument["dist-tags"])) {
|
|
23276
|
+
for (const tag of Object.keys(packument["dist-tags"] || {})) {
|
|
23112
23277
|
if (packument["dist-tags"][tag] === input.version) {
|
|
23113
23278
|
delete packument["dist-tags"][tag];
|
|
23114
23279
|
}
|
|
23115
23280
|
}
|
|
23116
|
-
|
|
23117
|
-
|
|
23281
|
+
if (latestBefore === input.version) {
|
|
23282
|
+
const newLatest = highestVersion(Object.keys(packument.versions));
|
|
23283
|
+
if (newLatest) packument["dist-tags"].latest = newLatest;
|
|
23284
|
+
}
|
|
23285
|
+
delete packument._revisions;
|
|
23286
|
+
delete packument._attachments;
|
|
23287
|
+
const putRes = await registryPutAuth(
|
|
23288
|
+
`/${encPkg(input.name)}/-rev/${encodeURIComponent(packument._rev)}`,
|
|
23289
|
+
packument
|
|
23290
|
+
);
|
|
23291
|
+
if (!putRes.ok) return translateError(putRes, { pkg: input.name, op: "unpublish (packument PUT)" });
|
|
23292
|
+
let tarballDeleted = false;
|
|
23293
|
+
let tarballError;
|
|
23294
|
+
if (tarballUrl) {
|
|
23295
|
+
const freshRes = await fetchPackument(input.name);
|
|
23296
|
+
const freshRev = freshRes.ok ? freshRes.data._rev : void 0;
|
|
23297
|
+
if (freshRev) {
|
|
23298
|
+
try {
|
|
23299
|
+
const pathname = new URL(tarballUrl).pathname;
|
|
23300
|
+
const delRes = await registryDeleteAuth(`${pathname}/-rev/${encodeURIComponent(freshRev)}`);
|
|
23301
|
+
tarballDeleted = delRes.ok;
|
|
23302
|
+
if (!delRes.ok) tarballError = delRes.error;
|
|
23303
|
+
} catch (err) {
|
|
23304
|
+
tarballError = err instanceof Error ? err.message : String(err);
|
|
23305
|
+
}
|
|
23306
|
+
} else {
|
|
23307
|
+
tarballError = "could not re-fetch packument for tarball DELETE rev";
|
|
23308
|
+
}
|
|
23309
|
+
}
|
|
23118
23310
|
return {
|
|
23119
23311
|
ok: true,
|
|
23120
23312
|
status: 200,
|
|
23121
23313
|
data: {
|
|
23122
23314
|
package: input.name,
|
|
23123
23315
|
unpublishedVersion: input.version,
|
|
23124
|
-
remainingVersions: Object.keys(packument.versions)
|
|
23316
|
+
remainingVersions: Object.keys(packument.versions),
|
|
23317
|
+
tarballDeleted,
|
|
23318
|
+
...tarballError ? { tarballWarning: tarballError } : {}
|
|
23125
23319
|
}
|
|
23126
23320
|
};
|
|
23127
23321
|
}
|
|
23128
23322
|
},
|
|
23129
23323
|
// ───────────────────────────────────────────────────────
|
|
23324
|
+
// npm_unpublish_package
|
|
23325
|
+
// ───────────────────────────────────────────────────────
|
|
23326
|
+
{
|
|
23327
|
+
name: "npm_unpublish_package",
|
|
23328
|
+
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.",
|
|
23329
|
+
annotations: {
|
|
23330
|
+
title: "Unpublish entire package",
|
|
23331
|
+
readOnlyHint: false,
|
|
23332
|
+
destructiveHint: true,
|
|
23333
|
+
idempotentHint: false,
|
|
23334
|
+
openWorldHint: true
|
|
23335
|
+
},
|
|
23336
|
+
inputSchema: external_exports.object({
|
|
23337
|
+
name: external_exports.string().describe("Package name"),
|
|
23338
|
+
confirm: external_exports.literal(true).describe("Must be literally true. Guards against accidental full unpublish.")
|
|
23339
|
+
}),
|
|
23340
|
+
handler: async (input) => {
|
|
23341
|
+
const authErr = requireAuth();
|
|
23342
|
+
if (authErr) return authErr;
|
|
23343
|
+
if (input.confirm !== true) {
|
|
23344
|
+
return {
|
|
23345
|
+
ok: false,
|
|
23346
|
+
status: 400,
|
|
23347
|
+
error: "Full-package unpublish requires confirm: true. This blocks the name for 72 hours."
|
|
23348
|
+
};
|
|
23349
|
+
}
|
|
23350
|
+
const pRes = await fetchPackument(input.name);
|
|
23351
|
+
if (!pRes.ok) return translateError(pRes, { pkg: input.name, op: "unpublish_package (fetch)" });
|
|
23352
|
+
const rev = pRes.data._rev;
|
|
23353
|
+
if (!rev) {
|
|
23354
|
+
return {
|
|
23355
|
+
ok: false,
|
|
23356
|
+
status: 500,
|
|
23357
|
+
error: `Packument for ${input.name} missing _rev \u2014 cannot unpublish.`
|
|
23358
|
+
};
|
|
23359
|
+
}
|
|
23360
|
+
const delRes = await registryDeleteAuth(`/${encPkg(input.name)}/-rev/${encodeURIComponent(rev)}`);
|
|
23361
|
+
if (!delRes.ok) return translateError(delRes, { pkg: input.name, op: "unpublish_package (DELETE)" });
|
|
23362
|
+
return {
|
|
23363
|
+
ok: true,
|
|
23364
|
+
status: 200,
|
|
23365
|
+
data: { package: input.name, unpublished: true }
|
|
23366
|
+
};
|
|
23367
|
+
}
|
|
23368
|
+
},
|
|
23369
|
+
// ───────────────────────────────────────────────────────
|
|
23130
23370
|
// npm_dist_tag_set
|
|
23131
23371
|
// ───────────────────────────────────────────────────────
|
|
23132
23372
|
{
|
|
@@ -23204,9 +23444,13 @@ var writeTools = [
|
|
|
23204
23444
|
// ───────────────────────────────────────────────────────
|
|
23205
23445
|
// npm_owner_add
|
|
23206
23446
|
// ───────────────────────────────────────────────────────
|
|
23447
|
+
// Mirrors npm CLI owner.js:
|
|
23448
|
+
// 1. GET /-/user/org.couchdb.user:{user} → resolve {name, email}
|
|
23449
|
+
// 2. GET /{pkg}?write=true → packument with _rev
|
|
23450
|
+
// 3. PUT /{pkg}/-rev/{_rev} body {_id, _rev, maintainers}
|
|
23207
23451
|
{
|
|
23208
23452
|
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.",
|
|
23453
|
+
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
23454
|
annotations: {
|
|
23211
23455
|
title: "Add package owner",
|
|
23212
23456
|
readOnlyHint: false,
|
|
@@ -23216,38 +23460,53 @@ var writeTools = [
|
|
|
23216
23460
|
},
|
|
23217
23461
|
inputSchema: external_exports.object({
|
|
23218
23462
|
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)")
|
|
23463
|
+
username: external_exports.string().describe("npm username to add as maintainer")
|
|
23221
23464
|
}),
|
|
23222
23465
|
handler: async (input) => {
|
|
23223
23466
|
const authErr = requireAuth();
|
|
23224
23467
|
if (authErr) return authErr;
|
|
23468
|
+
const uRes = await registryGetAuth(
|
|
23469
|
+
`/-/user/org.couchdb.user:${encodeURIComponent(input.username)}`
|
|
23470
|
+
);
|
|
23471
|
+
if (!uRes.ok) return translateError(uRes, { pkg: input.name, op: `owner_add (resolve user ${input.username})` });
|
|
23472
|
+
const userRecord = { name: uRes.data.name, email: uRes.data.email ?? "" };
|
|
23225
23473
|
const pRes = await fetchPackument(input.name);
|
|
23226
23474
|
if (!pRes.ok) return translateError(pRes, { pkg: input.name, op: "owner_add (fetch)" });
|
|
23227
23475
|
const packument = pRes.data;
|
|
23228
|
-
|
|
23229
|
-
if (
|
|
23476
|
+
const owners = packument.maintainers || [];
|
|
23477
|
+
if (owners.some((m) => m.name === userRecord.name)) {
|
|
23230
23478
|
return {
|
|
23231
23479
|
ok: true,
|
|
23232
23480
|
status: 200,
|
|
23233
23481
|
data: {
|
|
23234
23482
|
package: input.name,
|
|
23235
|
-
username:
|
|
23483
|
+
username: userRecord.name,
|
|
23236
23484
|
alreadyOwner: true,
|
|
23237
|
-
maintainers:
|
|
23485
|
+
maintainers: owners.map((m) => m.name)
|
|
23238
23486
|
}
|
|
23239
23487
|
};
|
|
23240
23488
|
}
|
|
23241
|
-
packument.
|
|
23242
|
-
|
|
23489
|
+
if (!packument._rev) {
|
|
23490
|
+
return {
|
|
23491
|
+
ok: false,
|
|
23492
|
+
status: 500,
|
|
23493
|
+
error: `Packument for ${input.name} missing _rev \u2014 cannot update owners.`
|
|
23494
|
+
};
|
|
23495
|
+
}
|
|
23496
|
+
const maintainers = [...owners, userRecord];
|
|
23497
|
+
const putRes = await registryPutAuth(`/${encPkg(input.name)}/-rev/${encodeURIComponent(packument._rev)}`, {
|
|
23498
|
+
_id: packument._id,
|
|
23499
|
+
_rev: packument._rev,
|
|
23500
|
+
maintainers
|
|
23501
|
+
});
|
|
23243
23502
|
if (!putRes.ok) return translateError(putRes, { pkg: input.name, op: "owner_add (write)" });
|
|
23244
23503
|
return {
|
|
23245
23504
|
ok: true,
|
|
23246
23505
|
status: 200,
|
|
23247
23506
|
data: {
|
|
23248
23507
|
package: input.name,
|
|
23249
|
-
addedOwner:
|
|
23250
|
-
maintainers:
|
|
23508
|
+
addedOwner: userRecord.name,
|
|
23509
|
+
maintainers: maintainers.map((m) => m.name)
|
|
23251
23510
|
}
|
|
23252
23511
|
};
|
|
23253
23512
|
}
|
|
@@ -23291,8 +23550,18 @@ var writeTools = [
|
|
|
23291
23550
|
error: `Removing ${input.username} would leave ${input.name} with zero maintainers (lockout). Add another maintainer first with npm_owner_add.`
|
|
23292
23551
|
};
|
|
23293
23552
|
}
|
|
23294
|
-
packument.
|
|
23295
|
-
|
|
23553
|
+
if (!packument._rev) {
|
|
23554
|
+
return {
|
|
23555
|
+
ok: false,
|
|
23556
|
+
status: 500,
|
|
23557
|
+
error: `Packument for ${input.name} missing _rev \u2014 cannot update owners.`
|
|
23558
|
+
};
|
|
23559
|
+
}
|
|
23560
|
+
const putRes = await registryPutAuth(`/${encPkg(input.name)}/-rev/${encodeURIComponent(packument._rev)}`, {
|
|
23561
|
+
_id: packument._id,
|
|
23562
|
+
_rev: packument._rev,
|
|
23563
|
+
maintainers: after
|
|
23564
|
+
});
|
|
23296
23565
|
if (!putRes.ok) return translateError(putRes, { pkg: input.name, op: "owner_remove (write)" });
|
|
23297
23566
|
return {
|
|
23298
23567
|
ok: true,
|
|
@@ -23304,11 +23573,368 @@ var writeTools = [
|
|
|
23304
23573
|
}
|
|
23305
23574
|
};
|
|
23306
23575
|
}
|
|
23576
|
+
},
|
|
23577
|
+
// ───────────────────────────────────────────────────────
|
|
23578
|
+
// npm_access_set
|
|
23579
|
+
// ───────────────────────────────────────────────────────
|
|
23580
|
+
{
|
|
23581
|
+
name: "npm_access_set",
|
|
23582
|
+
description: "Set package access level: 'public', 'private', or 'restricted'. Unscoped packages are always public. Private access requires a paid npm account.",
|
|
23583
|
+
annotations: {
|
|
23584
|
+
title: "Set package access level",
|
|
23585
|
+
readOnlyHint: false,
|
|
23586
|
+
destructiveHint: true,
|
|
23587
|
+
idempotentHint: true,
|
|
23588
|
+
openWorldHint: true
|
|
23589
|
+
},
|
|
23590
|
+
inputSchema: external_exports.object({
|
|
23591
|
+
name: external_exports.string().describe("Package name"),
|
|
23592
|
+
access: external_exports.enum(["public", "private", "restricted"]).describe("Access level")
|
|
23593
|
+
}),
|
|
23594
|
+
handler: async (input) => {
|
|
23595
|
+
const authErr = requireAuth();
|
|
23596
|
+
if (authErr) return authErr;
|
|
23597
|
+
const res = await registryPostAuth(`/-/package/${encPkg(input.name)}/access`, { access: input.access });
|
|
23598
|
+
if (!res.ok) return translateError(res, { pkg: input.name, op: `access_set ${input.access}` });
|
|
23599
|
+
return {
|
|
23600
|
+
ok: true,
|
|
23601
|
+
status: 200,
|
|
23602
|
+
data: { package: input.name, access: input.access }
|
|
23603
|
+
};
|
|
23604
|
+
}
|
|
23605
|
+
},
|
|
23606
|
+
// ───────────────────────────────────────────────────────
|
|
23607
|
+
// npm_access_set_mfa
|
|
23608
|
+
// ───────────────────────────────────────────────────────
|
|
23609
|
+
{
|
|
23610
|
+
name: "npm_access_set_mfa",
|
|
23611
|
+
description: "Configure 2FA requirement for publishing: 'none' (off), 'publish' (2FA required), 'automation' (2FA required but automation tokens can bypass).",
|
|
23612
|
+
annotations: {
|
|
23613
|
+
title: "Set package 2FA publish policy",
|
|
23614
|
+
readOnlyHint: false,
|
|
23615
|
+
destructiveHint: true,
|
|
23616
|
+
idempotentHint: true,
|
|
23617
|
+
openWorldHint: true
|
|
23618
|
+
},
|
|
23619
|
+
inputSchema: external_exports.object({
|
|
23620
|
+
name: external_exports.string().describe("Package name"),
|
|
23621
|
+
level: external_exports.enum(["none", "publish", "automation"]).describe("MFA level for publish")
|
|
23622
|
+
}),
|
|
23623
|
+
handler: async (input) => {
|
|
23624
|
+
const authErr = requireAuth();
|
|
23625
|
+
if (authErr) return authErr;
|
|
23626
|
+
let body;
|
|
23627
|
+
if (input.level === "none") {
|
|
23628
|
+
body = { publish_requires_tfa: false };
|
|
23629
|
+
} else if (input.level === "publish") {
|
|
23630
|
+
body = { publish_requires_tfa: true, automation_token_overrides_tfa: false };
|
|
23631
|
+
} else {
|
|
23632
|
+
body = { publish_requires_tfa: true, automation_token_overrides_tfa: true };
|
|
23633
|
+
}
|
|
23634
|
+
const res = await registryPostAuth(`/-/package/${encPkg(input.name)}/access`, body);
|
|
23635
|
+
if (!res.ok) return translateError(res, { pkg: input.name, op: `access_set_mfa ${input.level}` });
|
|
23636
|
+
return {
|
|
23637
|
+
ok: true,
|
|
23638
|
+
status: 200,
|
|
23639
|
+
data: { package: input.name, mfaLevel: input.level }
|
|
23640
|
+
};
|
|
23641
|
+
}
|
|
23642
|
+
},
|
|
23643
|
+
// ───────────────────────────────────────────────────────
|
|
23644
|
+
// npm_team_grant
|
|
23645
|
+
// ───────────────────────────────────────────────────────
|
|
23646
|
+
{
|
|
23647
|
+
name: "npm_team_grant",
|
|
23648
|
+
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.",
|
|
23649
|
+
annotations: {
|
|
23650
|
+
title: "Grant team package permission",
|
|
23651
|
+
readOnlyHint: false,
|
|
23652
|
+
destructiveHint: true,
|
|
23653
|
+
idempotentHint: true,
|
|
23654
|
+
openWorldHint: true
|
|
23655
|
+
},
|
|
23656
|
+
inputSchema: external_exports.object({
|
|
23657
|
+
team: external_exports.string().describe("Team in the form '@scope:team' (e.g. '@yawlabs:devs')"),
|
|
23658
|
+
package: external_exports.string().describe("Package name"),
|
|
23659
|
+
permissions: external_exports.enum(["read-only", "read-write"]).describe("Permission level")
|
|
23660
|
+
}),
|
|
23661
|
+
handler: async (input) => {
|
|
23662
|
+
const authErr = requireAuth();
|
|
23663
|
+
if (authErr) return authErr;
|
|
23664
|
+
const m = input.team.match(/^@?([^:]+):(.+)$/);
|
|
23665
|
+
if (!m) {
|
|
23666
|
+
return {
|
|
23667
|
+
ok: false,
|
|
23668
|
+
status: 400,
|
|
23669
|
+
error: `Team must be in the form '@scope:team' (got '${input.team}').`
|
|
23670
|
+
};
|
|
23671
|
+
}
|
|
23672
|
+
const [, scope, team] = m;
|
|
23673
|
+
const res = await registryPutAuth(`/-/team/${encodeURIComponent(scope)}/${encodeURIComponent(team)}/package`, {
|
|
23674
|
+
package: input.package,
|
|
23675
|
+
permissions: input.permissions
|
|
23676
|
+
});
|
|
23677
|
+
if (!res.ok) return translateError(res, { pkg: input.package, op: `team_grant ${input.team}` });
|
|
23678
|
+
return {
|
|
23679
|
+
ok: true,
|
|
23680
|
+
status: 200,
|
|
23681
|
+
data: { team: `@${scope}:${team}`, package: input.package, permissions: input.permissions }
|
|
23682
|
+
};
|
|
23683
|
+
}
|
|
23684
|
+
},
|
|
23685
|
+
// ───────────────────────────────────────────────────────
|
|
23686
|
+
// npm_team_revoke
|
|
23687
|
+
// ───────────────────────────────────────────────────────
|
|
23688
|
+
{
|
|
23689
|
+
name: "npm_team_revoke",
|
|
23690
|
+
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.",
|
|
23691
|
+
annotations: {
|
|
23692
|
+
title: "Revoke team package permission",
|
|
23693
|
+
readOnlyHint: false,
|
|
23694
|
+
destructiveHint: true,
|
|
23695
|
+
idempotentHint: true,
|
|
23696
|
+
openWorldHint: true
|
|
23697
|
+
},
|
|
23698
|
+
inputSchema: external_exports.object({
|
|
23699
|
+
team: external_exports.string().describe("Team in the form '@scope:team'"),
|
|
23700
|
+
package: external_exports.string().describe("Package name")
|
|
23701
|
+
}),
|
|
23702
|
+
handler: async (input) => {
|
|
23703
|
+
const authErr = requireAuth();
|
|
23704
|
+
if (authErr) return authErr;
|
|
23705
|
+
const m = input.team.match(/^@?([^:]+):(.+)$/);
|
|
23706
|
+
if (!m) {
|
|
23707
|
+
return {
|
|
23708
|
+
ok: false,
|
|
23709
|
+
status: 400,
|
|
23710
|
+
error: `Team must be in the form '@scope:team' (got '${input.team}').`
|
|
23711
|
+
};
|
|
23712
|
+
}
|
|
23713
|
+
const [, scope, team] = m;
|
|
23714
|
+
const res = await registryDeleteAuth(`/-/team/${encodeURIComponent(scope)}/${encodeURIComponent(team)}/package`, {
|
|
23715
|
+
package: input.package
|
|
23716
|
+
});
|
|
23717
|
+
if (!res.ok) return translateError(res, { pkg: input.package, op: `team_revoke ${input.team}` });
|
|
23718
|
+
return {
|
|
23719
|
+
ok: true,
|
|
23720
|
+
status: 200,
|
|
23721
|
+
data: { team: `@${scope}:${team}`, package: input.package, revoked: true }
|
|
23722
|
+
};
|
|
23723
|
+
}
|
|
23724
|
+
},
|
|
23725
|
+
// ───────────────────────────────────────────────────────
|
|
23726
|
+
// npm_team_create
|
|
23727
|
+
// ───────────────────────────────────────────────────────
|
|
23728
|
+
{
|
|
23729
|
+
name: "npm_team_create",
|
|
23730
|
+
description: "Create a team inside an organization. Team is passed as '@scope:team'.",
|
|
23731
|
+
annotations: {
|
|
23732
|
+
title: "Create team",
|
|
23733
|
+
readOnlyHint: false,
|
|
23734
|
+
destructiveHint: true,
|
|
23735
|
+
idempotentHint: false,
|
|
23736
|
+
openWorldHint: true
|
|
23737
|
+
},
|
|
23738
|
+
inputSchema: external_exports.object({
|
|
23739
|
+
team: external_exports.string().describe("Team in the form '@scope:team'"),
|
|
23740
|
+
description: external_exports.string().optional().describe("Optional team description")
|
|
23741
|
+
}),
|
|
23742
|
+
handler: async (input) => {
|
|
23743
|
+
const authErr = requireAuth();
|
|
23744
|
+
if (authErr) return authErr;
|
|
23745
|
+
const m = input.team.match(/^@?([^:]+):(.+)$/);
|
|
23746
|
+
if (!m) {
|
|
23747
|
+
return { ok: false, status: 400, error: `Team must be in the form '@scope:team' (got '${input.team}').` };
|
|
23748
|
+
}
|
|
23749
|
+
const [, scope, team] = m;
|
|
23750
|
+
const res = await registryPutAuth(`/-/org/${encodeURIComponent(scope)}/team`, {
|
|
23751
|
+
name: team,
|
|
23752
|
+
description: input.description
|
|
23753
|
+
});
|
|
23754
|
+
if (!res.ok) return translateError(res, { op: `team_create ${input.team}` });
|
|
23755
|
+
return { ok: true, status: 200, data: { team: `@${scope}:${team}`, created: true } };
|
|
23756
|
+
}
|
|
23757
|
+
},
|
|
23758
|
+
// ───────────────────────────────────────────────────────
|
|
23759
|
+
// npm_team_delete
|
|
23760
|
+
// ───────────────────────────────────────────────────────
|
|
23761
|
+
{
|
|
23762
|
+
name: "npm_team_delete",
|
|
23763
|
+
description: "Delete a team. Team is passed as '@scope:team'. Revokes all package permissions that team held.",
|
|
23764
|
+
annotations: {
|
|
23765
|
+
title: "Delete team",
|
|
23766
|
+
readOnlyHint: false,
|
|
23767
|
+
destructiveHint: true,
|
|
23768
|
+
idempotentHint: true,
|
|
23769
|
+
openWorldHint: true
|
|
23770
|
+
},
|
|
23771
|
+
inputSchema: external_exports.object({
|
|
23772
|
+
team: external_exports.string().describe("Team in the form '@scope:team'")
|
|
23773
|
+
}),
|
|
23774
|
+
handler: async (input) => {
|
|
23775
|
+
const authErr = requireAuth();
|
|
23776
|
+
if (authErr) return authErr;
|
|
23777
|
+
const m = input.team.match(/^@?([^:]+):(.+)$/);
|
|
23778
|
+
if (!m) {
|
|
23779
|
+
return { ok: false, status: 400, error: `Team must be in the form '@scope:team' (got '${input.team}').` };
|
|
23780
|
+
}
|
|
23781
|
+
const [, scope, team] = m;
|
|
23782
|
+
const res = await registryDeleteAuth(`/-/team/${encodeURIComponent(scope)}/${encodeURIComponent(team)}`);
|
|
23783
|
+
if (!res.ok) return translateError(res, { op: `team_delete ${input.team}` });
|
|
23784
|
+
return { ok: true, status: 200, data: { team: `@${scope}:${team}`, deleted: true } };
|
|
23785
|
+
}
|
|
23786
|
+
},
|
|
23787
|
+
// ───────────────────────────────────────────────────────
|
|
23788
|
+
// npm_team_member_add
|
|
23789
|
+
// ───────────────────────────────────────────────────────
|
|
23790
|
+
{
|
|
23791
|
+
name: "npm_team_member_add",
|
|
23792
|
+
description: "Add a user to a team. Team is '@scope:team'. User must already be in the org.",
|
|
23793
|
+
annotations: {
|
|
23794
|
+
title: "Add team member",
|
|
23795
|
+
readOnlyHint: false,
|
|
23796
|
+
destructiveHint: true,
|
|
23797
|
+
idempotentHint: true,
|
|
23798
|
+
openWorldHint: true
|
|
23799
|
+
},
|
|
23800
|
+
inputSchema: external_exports.object({
|
|
23801
|
+
team: external_exports.string().describe("Team in the form '@scope:team'"),
|
|
23802
|
+
user: external_exports.string().describe("npm username")
|
|
23803
|
+
}),
|
|
23804
|
+
handler: async (input) => {
|
|
23805
|
+
const authErr = requireAuth();
|
|
23806
|
+
if (authErr) return authErr;
|
|
23807
|
+
const m = input.team.match(/^@?([^:]+):(.+)$/);
|
|
23808
|
+
if (!m) {
|
|
23809
|
+
return { ok: false, status: 400, error: `Team must be in the form '@scope:team' (got '${input.team}').` };
|
|
23810
|
+
}
|
|
23811
|
+
const [, scope, team] = m;
|
|
23812
|
+
const res = await registryPutAuth(`/-/team/${encodeURIComponent(scope)}/${encodeURIComponent(team)}/user`, {
|
|
23813
|
+
user: input.user
|
|
23814
|
+
});
|
|
23815
|
+
if (!res.ok) return translateError(res, { op: `team_member_add ${input.team}` });
|
|
23816
|
+
return { ok: true, status: 200, data: { team: `@${scope}:${team}`, addedUser: input.user } };
|
|
23817
|
+
}
|
|
23818
|
+
},
|
|
23819
|
+
// ───────────────────────────────────────────────────────
|
|
23820
|
+
// npm_team_member_remove
|
|
23821
|
+
// ───────────────────────────────────────────────────────
|
|
23822
|
+
{
|
|
23823
|
+
name: "npm_team_member_remove",
|
|
23824
|
+
description: "Remove a user from a team. Team is '@scope:team'. User remains in the org.",
|
|
23825
|
+
annotations: {
|
|
23826
|
+
title: "Remove team member",
|
|
23827
|
+
readOnlyHint: false,
|
|
23828
|
+
destructiveHint: true,
|
|
23829
|
+
idempotentHint: true,
|
|
23830
|
+
openWorldHint: true
|
|
23831
|
+
},
|
|
23832
|
+
inputSchema: external_exports.object({
|
|
23833
|
+
team: external_exports.string().describe("Team in the form '@scope:team'"),
|
|
23834
|
+
user: external_exports.string().describe("npm username")
|
|
23835
|
+
}),
|
|
23836
|
+
handler: async (input) => {
|
|
23837
|
+
const authErr = requireAuth();
|
|
23838
|
+
if (authErr) return authErr;
|
|
23839
|
+
const m = input.team.match(/^@?([^:]+):(.+)$/);
|
|
23840
|
+
if (!m) {
|
|
23841
|
+
return { ok: false, status: 400, error: `Team must be in the form '@scope:team' (got '${input.team}').` };
|
|
23842
|
+
}
|
|
23843
|
+
const [, scope, team] = m;
|
|
23844
|
+
const res = await registryDeleteAuth(`/-/team/${encodeURIComponent(scope)}/${encodeURIComponent(team)}/user`, {
|
|
23845
|
+
user: input.user
|
|
23846
|
+
});
|
|
23847
|
+
if (!res.ok) return translateError(res, { op: `team_member_remove ${input.team}` });
|
|
23848
|
+
return { ok: true, status: 200, data: { team: `@${scope}:${team}`, removedUser: input.user } };
|
|
23849
|
+
}
|
|
23850
|
+
},
|
|
23851
|
+
// ───────────────────────────────────────────────────────
|
|
23852
|
+
// npm_org_member_set
|
|
23853
|
+
// ───────────────────────────────────────────────────────
|
|
23854
|
+
{
|
|
23855
|
+
name: "npm_org_member_set",
|
|
23856
|
+
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.",
|
|
23857
|
+
annotations: {
|
|
23858
|
+
title: "Set org member",
|
|
23859
|
+
readOnlyHint: false,
|
|
23860
|
+
destructiveHint: true,
|
|
23861
|
+
idempotentHint: true,
|
|
23862
|
+
openWorldHint: true
|
|
23863
|
+
},
|
|
23864
|
+
inputSchema: external_exports.object({
|
|
23865
|
+
org: external_exports.string().describe("Organization name (with or without leading @)"),
|
|
23866
|
+
user: external_exports.string().describe("npm username"),
|
|
23867
|
+
role: external_exports.enum(["developer", "admin", "owner"]).optional().describe("Role to assign")
|
|
23868
|
+
}),
|
|
23869
|
+
handler: async (input) => {
|
|
23870
|
+
const authErr = requireAuth();
|
|
23871
|
+
if (authErr) return authErr;
|
|
23872
|
+
const org = input.org.replace(/^@/, "");
|
|
23873
|
+
const user = input.user.replace(/^@/, "");
|
|
23874
|
+
const body = { user };
|
|
23875
|
+
if (input.role) body.role = input.role;
|
|
23876
|
+
const res = await registryPutAuth(`/-/org/${encodeURIComponent(org)}/user`, body);
|
|
23877
|
+
if (!res.ok) return translateError(res, { op: `org_member_set ${org}/${user}` });
|
|
23878
|
+
return { ok: true, status: 200, data: { org, user, role: input.role } };
|
|
23879
|
+
}
|
|
23880
|
+
},
|
|
23881
|
+
// ───────────────────────────────────────────────────────
|
|
23882
|
+
// npm_org_member_remove
|
|
23883
|
+
// ───────────────────────────────────────────────────────
|
|
23884
|
+
{
|
|
23885
|
+
name: "npm_org_member_remove",
|
|
23886
|
+
description: "Remove a user from an org. Their team memberships in that org are also removed.",
|
|
23887
|
+
annotations: {
|
|
23888
|
+
title: "Remove org member",
|
|
23889
|
+
readOnlyHint: false,
|
|
23890
|
+
destructiveHint: true,
|
|
23891
|
+
idempotentHint: true,
|
|
23892
|
+
openWorldHint: true
|
|
23893
|
+
},
|
|
23894
|
+
inputSchema: external_exports.object({
|
|
23895
|
+
org: external_exports.string().describe("Organization name"),
|
|
23896
|
+
user: external_exports.string().describe("npm username")
|
|
23897
|
+
}),
|
|
23898
|
+
handler: async (input) => {
|
|
23899
|
+
const authErr = requireAuth();
|
|
23900
|
+
if (authErr) return authErr;
|
|
23901
|
+
const org = input.org.replace(/^@/, "");
|
|
23902
|
+
const user = input.user.replace(/^@/, "");
|
|
23903
|
+
const res = await registryDeleteAuth(`/-/org/${encodeURIComponent(org)}/user`, { user });
|
|
23904
|
+
if (!res.ok) return translateError(res, { op: `org_member_remove ${org}/${user}` });
|
|
23905
|
+
return { ok: true, status: 200, data: { org, removedUser: user } };
|
|
23906
|
+
}
|
|
23907
|
+
},
|
|
23908
|
+
// ───────────────────────────────────────────────────────
|
|
23909
|
+
// npm_token_revoke
|
|
23910
|
+
// ───────────────────────────────────────────────────────
|
|
23911
|
+
// Note: token CREATION requires a user password (not a token), so it cannot be
|
|
23912
|
+
// performed via NPM_TOKEN alone — we intentionally don't expose npm_token_create.
|
|
23913
|
+
{
|
|
23914
|
+
name: "npm_token_revoke",
|
|
23915
|
+
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.",
|
|
23916
|
+
annotations: {
|
|
23917
|
+
title: "Revoke access token",
|
|
23918
|
+
readOnlyHint: false,
|
|
23919
|
+
destructiveHint: true,
|
|
23920
|
+
idempotentHint: true,
|
|
23921
|
+
openWorldHint: true
|
|
23922
|
+
},
|
|
23923
|
+
inputSchema: external_exports.object({
|
|
23924
|
+
tokenKey: external_exports.string().describe("Token key (UUID shown by npm_tokens)")
|
|
23925
|
+
}),
|
|
23926
|
+
handler: async (input) => {
|
|
23927
|
+
const authErr = requireAuth();
|
|
23928
|
+
if (authErr) return authErr;
|
|
23929
|
+
const res = await registryDeleteAuth(`/-/npm/v1/tokens/token/${encodeURIComponent(input.tokenKey)}`);
|
|
23930
|
+
if (!res.ok) return translateError(res, { op: "token_revoke" });
|
|
23931
|
+
return { ok: true, status: 200, data: { tokenKey: input.tokenKey, revoked: true } };
|
|
23932
|
+
}
|
|
23307
23933
|
}
|
|
23308
23934
|
];
|
|
23309
23935
|
|
|
23310
23936
|
// src/index.ts
|
|
23311
|
-
var version2 = true ? "0.
|
|
23937
|
+
var version2 = true ? "0.7.0" : (await null).createRequire(import.meta.url)("../package.json").version;
|
|
23312
23938
|
var subcommand = process.argv[2];
|
|
23313
23939
|
if (subcommand === "version" || subcommand === "--version") {
|
|
23314
23940
|
console.log(version2);
|
|
@@ -23328,7 +23954,8 @@ var allTools = [
|
|
|
23328
23954
|
...provenanceTools,
|
|
23329
23955
|
...trustTools,
|
|
23330
23956
|
...workflowTools,
|
|
23331
|
-
...writeTools
|
|
23957
|
+
...writeTools,
|
|
23958
|
+
...hookTools
|
|
23332
23959
|
];
|
|
23333
23960
|
var server = new McpServer({
|
|
23334
23961
|
name: "@yawlabs/npmjs-mcp",
|
package/package.json
CHANGED