@vucinatim/agentic-devtools 0.1.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/LICENSE +21 -0
- package/README.md +202 -0
- package/SECURITY.md +47 -0
- package/adapters/claude/namecheap/README.md +13 -0
- package/adapters/claude/npm/README.md +11 -0
- package/adapters/claude/railway/README.md +11 -0
- package/adapters/codex/namecheap/.codex-plugin/plugin.json +40 -0
- package/adapters/codex/namecheap/.mcp.json +21 -0
- package/adapters/codex/namecheap/SKILL.md +40 -0
- package/adapters/codex/npm/.codex-plugin/plugin.json +41 -0
- package/adapters/codex/npm/.mcp.json +18 -0
- package/adapters/codex/npm/SKILL.md +54 -0
- package/adapters/codex/railway/.codex-plugin/plugin.json +39 -0
- package/adapters/codex/railway/.mcp.json +20 -0
- package/adapters/codex/railway/SKILL.md +44 -0
- package/docs/README.md +14 -0
- package/docs/architecture.md +208 -0
- package/docs/auth-and-setup-guidelines.md +261 -0
- package/docs/migration-plan.md +55 -0
- package/docs/open-source-readiness.md +119 -0
- package/docs/publishing.md +211 -0
- package/docs/testing.md +61 -0
- package/docs/usage.md +144 -0
- package/package.json +78 -0
- package/src/cli.mjs +158 -0
- package/src/core/config-store.mjs +106 -0
- package/src/core/result.mjs +13 -0
- package/src/core/tool-registry.mjs +29 -0
- package/src/index.mjs +47 -0
- package/src/tools/namecheap/auth.mjs +429 -0
- package/src/tools/namecheap/client.mjs +655 -0
- package/src/tools/namecheap/mcp.mjs +298 -0
- package/src/tools/npm/auth.mjs +367 -0
- package/src/tools/npm/client.mjs +317 -0
- package/src/tools/npm/mcp.mjs +343 -0
- package/src/tools/railway/auth.mjs +402 -0
- package/src/tools/railway/client.mjs +388 -0
- package/src/tools/railway/mcp.mjs +282 -0
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { chmod, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { DEFAULT_NPM_REGISTRY, getNpmAuthStatus, resolveNpmAuthConfig } from "./auth.mjs";
|
|
6
|
+
|
|
7
|
+
export class NpmRegistryError extends Error {
|
|
8
|
+
constructor(message, details = {}) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "NpmRegistryError";
|
|
11
|
+
this.details = details;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const encodePackageName = (packageName) =>
|
|
16
|
+
encodeURIComponent(String(packageName ?? "").trim());
|
|
17
|
+
|
|
18
|
+
const normalizeRegistry = (registry = DEFAULT_NPM_REGISTRY) =>
|
|
19
|
+
String(registry || DEFAULT_NPM_REGISTRY).replace(/\/+$/, "");
|
|
20
|
+
|
|
21
|
+
const isNotFound = (error) =>
|
|
22
|
+
error instanceof NpmRegistryError && error.details.status === 404;
|
|
23
|
+
|
|
24
|
+
export const createNpmClient = ({
|
|
25
|
+
env = process.env,
|
|
26
|
+
fetchImpl = globalThis.fetch,
|
|
27
|
+
} = {}) => {
|
|
28
|
+
const auth = resolveNpmAuthConfig(env);
|
|
29
|
+
const registry = normalizeRegistry(auth.registry);
|
|
30
|
+
|
|
31
|
+
if (typeof fetchImpl !== "function") {
|
|
32
|
+
throw new Error("npm client requires a fetch implementation.");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const request = async (pathname, { method = "GET", body, authRequired = false, headers = {} } = {}) => {
|
|
36
|
+
if (authRequired && !auth.token) {
|
|
37
|
+
throw new NpmRegistryError(
|
|
38
|
+
"Missing npm token. Run `agentic-devtools connect npm`, set NPM_TOKEN/NODE_AUTH_TOKEN, or configure ~/.npmrc.",
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const response = await fetchImpl(`${registry}${pathname}`, {
|
|
43
|
+
method,
|
|
44
|
+
headers: {
|
|
45
|
+
accept: "application/json",
|
|
46
|
+
...(body ? { "content-type": "application/json" } : {}),
|
|
47
|
+
...(authRequired ? { Authorization: `Bearer ${auth.token}` } : {}),
|
|
48
|
+
...headers,
|
|
49
|
+
},
|
|
50
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const text = await response.text();
|
|
54
|
+
let payload = null;
|
|
55
|
+
if (text) {
|
|
56
|
+
try {
|
|
57
|
+
payload = JSON.parse(text);
|
|
58
|
+
} catch {
|
|
59
|
+
payload = { error: text };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!response.ok) {
|
|
64
|
+
throw new NpmRegistryError(formatNpmErrorMessage(payload, response.status), {
|
|
65
|
+
status: response.status,
|
|
66
|
+
payload,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return payload;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const getCurrentUser = async () => request("/-/whoami", { authRequired: true });
|
|
74
|
+
|
|
75
|
+
const getPackageInfo = async (packageName) =>
|
|
76
|
+
request(`/${encodePackageName(packageName)}`, { authRequired: false });
|
|
77
|
+
|
|
78
|
+
const checkPackageNameAvailability = async (packageName) => {
|
|
79
|
+
try {
|
|
80
|
+
const info = await getPackageInfo(packageName);
|
|
81
|
+
return {
|
|
82
|
+
packageName,
|
|
83
|
+
available: false,
|
|
84
|
+
version: info["dist-tags"]?.latest ?? null,
|
|
85
|
+
description: info.description ?? null,
|
|
86
|
+
};
|
|
87
|
+
} catch (error) {
|
|
88
|
+
if (isNotFound(error)) {
|
|
89
|
+
return {
|
|
90
|
+
packageName,
|
|
91
|
+
available: true,
|
|
92
|
+
version: null,
|
|
93
|
+
description: null,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const getPackageVersions = async (packageName) => {
|
|
101
|
+
const info = await getPackageInfo(packageName);
|
|
102
|
+
return {
|
|
103
|
+
packageName: info.name,
|
|
104
|
+
latest: info["dist-tags"]?.latest ?? null,
|
|
105
|
+
versions: Object.keys(info.versions ?? {}),
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const getPackageDistTags = async (packageName) => {
|
|
110
|
+
const info = await getPackageInfo(packageName);
|
|
111
|
+
return {
|
|
112
|
+
packageName: info.name,
|
|
113
|
+
distTags: info["dist-tags"] ?? {},
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const getPackageVisibility = async (packageName) =>
|
|
118
|
+
request(`/-/package/${encodePackageName(packageName)}/visibility`, {
|
|
119
|
+
authRequired: true,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const setPackageAccess = async ({
|
|
123
|
+
packageName,
|
|
124
|
+
access = "public",
|
|
125
|
+
publishRequiresTfa,
|
|
126
|
+
automationTokenOverridesTfa,
|
|
127
|
+
}) =>
|
|
128
|
+
request(`/-/package/${encodePackageName(packageName)}/access`, {
|
|
129
|
+
method: "POST",
|
|
130
|
+
authRequired: true,
|
|
131
|
+
body: {
|
|
132
|
+
access,
|
|
133
|
+
...(publishRequiresTfa == null
|
|
134
|
+
? {}
|
|
135
|
+
: { publish_requires_tfa: Boolean(publishRequiresTfa) }),
|
|
136
|
+
...(automationTokenOverridesTfa == null
|
|
137
|
+
? {}
|
|
138
|
+
: { automation_token_overrides_tfa: Boolean(automationTokenOverridesTfa) }),
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const listTokens = async ({ page = 0, perPage = 10 } = {}) =>
|
|
143
|
+
request(`/-/npm/v1/tokens?page=${page}&perPage=${perPage}`, {
|
|
144
|
+
authRequired: true,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const deleteToken = async ({ token, otp } = {}) =>
|
|
148
|
+
request(`/-/npm/v1/tokens/token/${encodeURIComponent(token)}`, {
|
|
149
|
+
method: "DELETE",
|
|
150
|
+
authRequired: true,
|
|
151
|
+
headers: otp ? { "npm-otp": otp } : {},
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const getTrustedPublishers = async ({ packageName, otp } = {}) =>
|
|
155
|
+
request(`/-/package/${encodePackageName(packageName)}/trust`, {
|
|
156
|
+
authRequired: true,
|
|
157
|
+
headers: otp ? { "npm-otp": otp } : {},
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const addGitHubTrustedPublisher = async ({
|
|
161
|
+
packageName,
|
|
162
|
+
repository,
|
|
163
|
+
workflowFile = "publish.yml",
|
|
164
|
+
environment,
|
|
165
|
+
otp,
|
|
166
|
+
} = {}) =>
|
|
167
|
+
request(`/-/package/${encodePackageName(packageName)}/trust`, {
|
|
168
|
+
method: "POST",
|
|
169
|
+
authRequired: true,
|
|
170
|
+
headers: otp ? { "npm-otp": otp } : {},
|
|
171
|
+
body: [
|
|
172
|
+
{
|
|
173
|
+
type: "github",
|
|
174
|
+
claims: {
|
|
175
|
+
repository,
|
|
176
|
+
workflow_ref: {
|
|
177
|
+
file: workflowFile,
|
|
178
|
+
},
|
|
179
|
+
...(environment ? { environment } : {}),
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const deleteTrustedPublisher = async ({ packageName, configId, otp } = {}) =>
|
|
186
|
+
request(
|
|
187
|
+
`/-/package/${encodePackageName(packageName)}/trust/${encodeURIComponent(configId)}`,
|
|
188
|
+
{
|
|
189
|
+
method: "DELETE",
|
|
190
|
+
authRequired: true,
|
|
191
|
+
headers: otp ? { "npm-otp": otp } : {},
|
|
192
|
+
},
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
const exchangeOidcToken = async ({ packageName, oidcToken } = {}) => {
|
|
196
|
+
if (!oidcToken) {
|
|
197
|
+
throw new NpmRegistryError("Missing OIDC id_token for npm token exchange.");
|
|
198
|
+
}
|
|
199
|
+
return request(`/-/npm/v1/oidc/token/exchange/package/${encodePackageName(packageName)}`, {
|
|
200
|
+
method: "POST",
|
|
201
|
+
authRequired: false,
|
|
202
|
+
headers: { Authorization: `Bearer ${oidcToken}` },
|
|
203
|
+
});
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const publishPackageDirectory = async ({
|
|
207
|
+
cwd = process.cwd(),
|
|
208
|
+
tag = "latest",
|
|
209
|
+
access = "public",
|
|
210
|
+
dryRun = true,
|
|
211
|
+
confirm = "",
|
|
212
|
+
} = {}) => {
|
|
213
|
+
const packageJson = JSON.parse(
|
|
214
|
+
await readFile(path.join(cwd, "package.json"), "utf8"),
|
|
215
|
+
);
|
|
216
|
+
if (!dryRun && confirm !== `publish ${packageJson.name}@${packageJson.version}`) {
|
|
217
|
+
throw new NpmRegistryError(
|
|
218
|
+
`Publishing requires confirm="publish ${packageJson.name}@${packageJson.version}".`,
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const args = ["publish", "--access", access, "--tag", tag];
|
|
223
|
+
if (dryRun) {
|
|
224
|
+
args.push("--dry-run");
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const tempDir = await mkdtemp(path.join(os.tmpdir(), "agentic-npm-"));
|
|
228
|
+
const userconfigPath = path.join(tempDir, ".npmrc");
|
|
229
|
+
if (auth.token) {
|
|
230
|
+
const registryUrl = new URL(registry);
|
|
231
|
+
await writeFile(
|
|
232
|
+
userconfigPath,
|
|
233
|
+
`registry=${registry}/\n//${registryUrl.host}/:_authToken=${auth.token}\n`,
|
|
234
|
+
);
|
|
235
|
+
await chmod(userconfigPath, 0o600).catch(() => {});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const envForPublish = {
|
|
239
|
+
...process.env,
|
|
240
|
+
...env,
|
|
241
|
+
NPM_CONFIG_REGISTRY: registry,
|
|
242
|
+
...(auth.token ? { NPM_CONFIG_USERCONFIG: userconfigPath } : {}),
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
try {
|
|
246
|
+
const result = await execNpm(args, { cwd, env: envForPublish });
|
|
247
|
+
return {
|
|
248
|
+
ok: result.status === 0,
|
|
249
|
+
dryRun,
|
|
250
|
+
packageName: packageJson.name,
|
|
251
|
+
version: packageJson.version,
|
|
252
|
+
tag,
|
|
253
|
+
access,
|
|
254
|
+
tokenSource: auth.source,
|
|
255
|
+
stdout: result.stdout,
|
|
256
|
+
stderr: result.stderr,
|
|
257
|
+
};
|
|
258
|
+
} finally {
|
|
259
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
auth,
|
|
265
|
+
registry,
|
|
266
|
+
getAuthStatus: () => getNpmAuthStatus(env),
|
|
267
|
+
getCurrentUser,
|
|
268
|
+
getPackageInfo,
|
|
269
|
+
checkPackageNameAvailability,
|
|
270
|
+
getPackageVersions,
|
|
271
|
+
getPackageDistTags,
|
|
272
|
+
getPackageVisibility,
|
|
273
|
+
setPackageAccess,
|
|
274
|
+
listTokens,
|
|
275
|
+
deleteToken,
|
|
276
|
+
getTrustedPublishers,
|
|
277
|
+
addGitHubTrustedPublisher,
|
|
278
|
+
deleteTrustedPublisher,
|
|
279
|
+
exchangeOidcToken,
|
|
280
|
+
publishPackageDirectory,
|
|
281
|
+
};
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
const execNpm = (args, { cwd, env }) =>
|
|
285
|
+
new Promise((resolve, reject) => {
|
|
286
|
+
execFile("npm", args, { cwd, env }, (error, stdout, stderr) => {
|
|
287
|
+
const result = {
|
|
288
|
+
status: error?.code ?? 0,
|
|
289
|
+
stdout,
|
|
290
|
+
stderr,
|
|
291
|
+
};
|
|
292
|
+
if (error && error.code == null) {
|
|
293
|
+
reject(error);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
resolve(result);
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
const formatNpmErrorMessage = (payload, status) => {
|
|
301
|
+
if (payload && typeof payload === "object") {
|
|
302
|
+
const message = payload.error || payload.message;
|
|
303
|
+
if (typeof message === "string" && message.trim()) {
|
|
304
|
+
const trimmed = message.trim();
|
|
305
|
+
try {
|
|
306
|
+
const nested = JSON.parse(trimmed);
|
|
307
|
+
if (typeof nested?.error === "string" && nested.error.trim()) {
|
|
308
|
+
return nested.error.trim();
|
|
309
|
+
}
|
|
310
|
+
} catch {
|
|
311
|
+
// Keep the original npm error string when it is not nested JSON.
|
|
312
|
+
}
|
|
313
|
+
return trimmed;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return `npm registry request failed with HTTP ${status}`;
|
|
317
|
+
};
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import {
|
|
7
|
+
disconnectNpm,
|
|
8
|
+
getNpmAuthStatus,
|
|
9
|
+
NPM_AUTH_CONFIG_PATH,
|
|
10
|
+
runNpmBrowserAuthFlow,
|
|
11
|
+
} from "./auth.mjs";
|
|
12
|
+
import { createNpmClient } from "./client.mjs";
|
|
13
|
+
|
|
14
|
+
const HELP_TEXT = `Usage: agentic-devtools mcp npm
|
|
15
|
+
|
|
16
|
+
npm MCP server
|
|
17
|
+
|
|
18
|
+
Optional environment variables:
|
|
19
|
+
NPM_TOKEN
|
|
20
|
+
NODE_AUTH_TOKEN
|
|
21
|
+
NPM_CONFIG_REGISTRY
|
|
22
|
+
AGENTIC_DEVTOOLS_NPM_AUTH_CONFIG_PATH
|
|
23
|
+
|
|
24
|
+
Prefer GitHub Actions Trusted Publishing for real package publishing. Local publishing is supported but explicit confirmation is required.
|
|
25
|
+
`;
|
|
26
|
+
|
|
27
|
+
const createToolResult = (value) => ({
|
|
28
|
+
content: [
|
|
29
|
+
{
|
|
30
|
+
type: "text",
|
|
31
|
+
text: JSON.stringify(value, null, 2),
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
structuredContent: value,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const packageNameSchema = z.string().min(1);
|
|
38
|
+
|
|
39
|
+
const createServer = () => {
|
|
40
|
+
const server = new McpServer(
|
|
41
|
+
{
|
|
42
|
+
name: "npm",
|
|
43
|
+
version: "0.1.0",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
instructions:
|
|
47
|
+
"Use these tools for npm registry inspection, token status checks, trusted publishing verification, and explicitly confirmed publishing. Prefer GitHub Actions Trusted Publishing over local write tokens.",
|
|
48
|
+
},
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const withClient = async (callback) => {
|
|
52
|
+
const client = createNpmClient();
|
|
53
|
+
return callback(client);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
server.registerTool(
|
|
57
|
+
"getNpmAuthStatus",
|
|
58
|
+
{
|
|
59
|
+
description:
|
|
60
|
+
"Show whether npm credentials are configured without exposing token values.",
|
|
61
|
+
},
|
|
62
|
+
async () => createToolResult(getNpmAuthStatus()),
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
server.registerTool(
|
|
66
|
+
"connectNpm",
|
|
67
|
+
{
|
|
68
|
+
description:
|
|
69
|
+
"Open a browser-based guided setup flow for an npm granular token.",
|
|
70
|
+
},
|
|
71
|
+
async () =>
|
|
72
|
+
createToolResult({
|
|
73
|
+
...(await runNpmBrowserAuthFlow()),
|
|
74
|
+
configPath: NPM_AUTH_CONFIG_PATH,
|
|
75
|
+
}),
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
server.registerTool(
|
|
79
|
+
"disconnectNpm",
|
|
80
|
+
{
|
|
81
|
+
description: "Remove the locally stored npm token from Agentic Devtools.",
|
|
82
|
+
},
|
|
83
|
+
async () => createToolResult(await disconnectNpm()),
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
server.registerTool(
|
|
87
|
+
"testNpmConnection",
|
|
88
|
+
{
|
|
89
|
+
description:
|
|
90
|
+
"Verify that the configured npm token can call the npm registry identity endpoint.",
|
|
91
|
+
},
|
|
92
|
+
async () =>
|
|
93
|
+
createToolResult(
|
|
94
|
+
await withClient(async (client) => ({
|
|
95
|
+
ok: true,
|
|
96
|
+
tokenSource: client.auth.source,
|
|
97
|
+
registry: client.registry,
|
|
98
|
+
user: await client.getCurrentUser(),
|
|
99
|
+
})),
|
|
100
|
+
),
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
server.registerTool(
|
|
104
|
+
"getNpmPackageInfo",
|
|
105
|
+
{
|
|
106
|
+
description: "Fetch public package metadata from the npm registry.",
|
|
107
|
+
inputSchema: {
|
|
108
|
+
packageName: packageNameSchema,
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
async ({ packageName }) =>
|
|
112
|
+
createToolResult(
|
|
113
|
+
await withClient((client) => client.getPackageInfo(packageName)),
|
|
114
|
+
),
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
server.registerTool(
|
|
118
|
+
"checkNpmPackageNameAvailability",
|
|
119
|
+
{
|
|
120
|
+
description: "Check if a package name is available on npm.",
|
|
121
|
+
inputSchema: {
|
|
122
|
+
packageName: packageNameSchema,
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
async ({ packageName }) =>
|
|
126
|
+
createToolResult(
|
|
127
|
+
await withClient((client) =>
|
|
128
|
+
client.checkPackageNameAvailability(packageName),
|
|
129
|
+
),
|
|
130
|
+
),
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
server.registerTool(
|
|
134
|
+
"getNpmPackageVersions",
|
|
135
|
+
{
|
|
136
|
+
description: "List published versions and latest tag for an npm package.",
|
|
137
|
+
inputSchema: {
|
|
138
|
+
packageName: packageNameSchema,
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
async ({ packageName }) =>
|
|
142
|
+
createToolResult(
|
|
143
|
+
await withClient((client) => client.getPackageVersions(packageName)),
|
|
144
|
+
),
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
server.registerTool(
|
|
148
|
+
"getNpmPackageDistTags",
|
|
149
|
+
{
|
|
150
|
+
description: "List npm dist-tags for a package.",
|
|
151
|
+
inputSchema: {
|
|
152
|
+
packageName: packageNameSchema,
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
async ({ packageName }) =>
|
|
156
|
+
createToolResult(
|
|
157
|
+
await withClient((client) => client.getPackageDistTags(packageName)),
|
|
158
|
+
),
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
server.registerTool(
|
|
162
|
+
"getNpmPackageVisibility",
|
|
163
|
+
{
|
|
164
|
+
description:
|
|
165
|
+
"Get npm package visibility. Requires an npm token with access to the package.",
|
|
166
|
+
inputSchema: {
|
|
167
|
+
packageName: packageNameSchema,
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
async ({ packageName }) =>
|
|
171
|
+
createToolResult(
|
|
172
|
+
await withClient((client) => client.getPackageVisibility(packageName)),
|
|
173
|
+
),
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
server.registerTool(
|
|
177
|
+
"setNpmPackageAccess",
|
|
178
|
+
{
|
|
179
|
+
description:
|
|
180
|
+
"Set npm package access and optional 2FA publishing policy. Requires explicit inputs and a token with package admin rights.",
|
|
181
|
+
inputSchema: {
|
|
182
|
+
packageName: packageNameSchema,
|
|
183
|
+
access: z.enum(["public", "private"]).default("public"),
|
|
184
|
+
publishRequiresTfa: z.boolean().optional(),
|
|
185
|
+
automationTokenOverridesTfa: z.boolean().optional(),
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
async (args) =>
|
|
189
|
+
createToolResult(
|
|
190
|
+
await withClient((client) => client.setPackageAccess(args)),
|
|
191
|
+
),
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
server.registerTool(
|
|
195
|
+
"listNpmTokens",
|
|
196
|
+
{
|
|
197
|
+
description:
|
|
198
|
+
"List npm access tokens for the configured user. Token values are redacted by npm.",
|
|
199
|
+
inputSchema: {
|
|
200
|
+
page: z.number().int().min(0).optional(),
|
|
201
|
+
perPage: z.number().int().min(1).max(100).optional(),
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
async (args) =>
|
|
205
|
+
createToolResult(
|
|
206
|
+
await withClient((client) => client.listTokens(args ?? {})),
|
|
207
|
+
),
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
server.registerTool(
|
|
211
|
+
"exchangeNpmOidcToken",
|
|
212
|
+
{
|
|
213
|
+
description:
|
|
214
|
+
"Exchange a supported CI OIDC id_token for a short-lived npm registry token for one package. Usually npm CLI handles this inside GitHub Actions.",
|
|
215
|
+
inputSchema: {
|
|
216
|
+
packageName: packageNameSchema,
|
|
217
|
+
oidcToken: z.string().min(1),
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
async (args) =>
|
|
221
|
+
createToolResult(
|
|
222
|
+
await withClient((client) => client.exchangeOidcToken(args)),
|
|
223
|
+
),
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
server.registerTool(
|
|
227
|
+
"getNpmTrustedPublishers",
|
|
228
|
+
{
|
|
229
|
+
description:
|
|
230
|
+
"Get npm Trusted Publisher configurations for a package. Requires package write permission and usually npm 2FA OTP.",
|
|
231
|
+
inputSchema: {
|
|
232
|
+
packageName: packageNameSchema,
|
|
233
|
+
otp: z.string().optional(),
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
async (args) =>
|
|
237
|
+
createToolResult(
|
|
238
|
+
await withClient((client) => client.getTrustedPublishers(args)),
|
|
239
|
+
),
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
server.registerTool(
|
|
243
|
+
"addNpmGitHubTrustedPublisher",
|
|
244
|
+
{
|
|
245
|
+
description:
|
|
246
|
+
"Add a GitHub Actions Trusted Publisher for an npm package. Requires package write permission and usually npm 2FA OTP.",
|
|
247
|
+
inputSchema: {
|
|
248
|
+
packageName: packageNameSchema,
|
|
249
|
+
repository: z.string().min(1),
|
|
250
|
+
workflowFile: z.string().min(1).default("publish.yml"),
|
|
251
|
+
environment: z.string().optional(),
|
|
252
|
+
otp: z.string().optional(),
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
async (args) =>
|
|
256
|
+
createToolResult(
|
|
257
|
+
await withClient((client) => client.addGitHubTrustedPublisher(args)),
|
|
258
|
+
),
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
server.registerTool(
|
|
262
|
+
"deleteNpmTrustedPublisher",
|
|
263
|
+
{
|
|
264
|
+
description:
|
|
265
|
+
"Delete a Trusted Publisher configuration by UUID. Requires package write permission and usually npm 2FA OTP.",
|
|
266
|
+
inputSchema: {
|
|
267
|
+
packageName: packageNameSchema,
|
|
268
|
+
configId: z.string().min(1),
|
|
269
|
+
otp: z.string().optional(),
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
async (args) =>
|
|
273
|
+
createToolResult(
|
|
274
|
+
await withClient((client) => client.deleteTrustedPublisher(args)),
|
|
275
|
+
),
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
server.registerTool(
|
|
279
|
+
"publishNpmPackageDirectory",
|
|
280
|
+
{
|
|
281
|
+
description:
|
|
282
|
+
"Run npm publish for a local package directory. Defaults to dry-run. Real publishing requires confirm='publish <name>@<version>'. Prefer GitHub Actions Trusted Publishing.",
|
|
283
|
+
inputSchema: {
|
|
284
|
+
cwd: z.string().min(1).optional(),
|
|
285
|
+
tag: z.string().min(1).default("latest"),
|
|
286
|
+
access: z.enum(["public", "restricted"]).default("public"),
|
|
287
|
+
dryRun: z.boolean().default(true),
|
|
288
|
+
confirm: z.string().optional(),
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
async (args) =>
|
|
292
|
+
createToolResult(
|
|
293
|
+
await withClient((client) => client.publishPackageDirectory(args)),
|
|
294
|
+
),
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
return server;
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
const argv = process.argv.slice(2);
|
|
301
|
+
|
|
302
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
303
|
+
process.stdout.write(HELP_TEXT);
|
|
304
|
+
process.exit(0);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (argv.includes("--auth-status")) {
|
|
308
|
+
process.stdout.write(`${JSON.stringify(getNpmAuthStatus(), null, 2)}\n`);
|
|
309
|
+
process.exit(0);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (argv.includes("--connect")) {
|
|
313
|
+
process.stdout.write("Opening npm browser setup flow...\n");
|
|
314
|
+
const result = await runNpmBrowserAuthFlow({
|
|
315
|
+
onReady: ({ url }) => {
|
|
316
|
+
process.stdout.write(`npm setup URL: ${url}\n`);
|
|
317
|
+
},
|
|
318
|
+
});
|
|
319
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
320
|
+
process.exit(0);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (argv.includes("--test-connection")) {
|
|
324
|
+
const client = createNpmClient();
|
|
325
|
+
process.stdout.write(
|
|
326
|
+
`${JSON.stringify(
|
|
327
|
+
{
|
|
328
|
+
ok: true,
|
|
329
|
+
tokenSource: client.auth.source,
|
|
330
|
+
registry: client.registry,
|
|
331
|
+
user: await client.getCurrentUser(),
|
|
332
|
+
},
|
|
333
|
+
null,
|
|
334
|
+
2,
|
|
335
|
+
)}\n`,
|
|
336
|
+
);
|
|
337
|
+
process.exit(0);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const server = createServer();
|
|
341
|
+
const transport = new StdioServerTransport();
|
|
342
|
+
|
|
343
|
+
await server.connect(transport);
|