create-academic-research 0.1.13 → 0.1.15
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 +71 -11
- package/dist/bin/academic-research.js +0 -0
- package/dist/bin/create-academic-research.js +0 -0
- package/dist/src/capabilities.d.ts +132 -3
- package/dist/src/capabilities.js +993 -48
- package/dist/src/cli.js +448 -33
- package/dist/src/mcp-env.d.ts +4 -0
- package/dist/src/mcp-env.js +2 -2
- package/dist/src/mcp-probe.d.ts +3 -2
- package/dist/src/mcp-probe.js +87 -30
- package/dist/src/project.d.ts +18 -0
- package/dist/src/project.js +654 -22
- package/dist/src/stack.d.ts +38 -0
- package/dist/src/stack.js +260 -14
- package/package.json +2 -2
- package/template/README.md +37 -4
- package/template/_gitignore +1 -0
- package/template/docs/agent/mcp-client-setup.md +43 -3
- package/template/docs/agent/mcp-setup.md +60 -0
- package/template/docs/getting-started.md +17 -5
- package/template/package.json +7 -1
package/dist/src/cli.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { basename, delimiter, dirname, join, resolve } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { assertKnownMcpServers, disableMcpServers, doctorMcpServers, enableMcpServers, DEFAULT_AGENT, installMcpTools, installSkillIds, installSkills, formatMcpDotenv, listInstalledSkills, listMcpEnvironmentEntries, mergeMcpEnvironment, mcpToolCommandTexts, probeMcpServers, readCapabilities, readMcpEnvironmentFile, removeSkills, uninstallMcpTools, updateSkills } from "./capabilities.js";
|
|
5
|
-
import { createProject, doctorProject, renameProject } from "./project.js";
|
|
4
|
+
import { assertKnownMcpServers, clientAddMcpServer, clientRemoveMcpServer, disableMcpServers, doctorMcpServers, enableMcpServers, DEFAULT_AGENT, getMcpLifecycleStatus, installMcpTools, installSkillIds, installSkills, formatMcpDotenv, listInstalledSkills, listMcpEnvironmentEntries, mergeMcpEnvironment, mcpToolCommandTexts, probeMcpServers, readCapabilities, readMcpEnvironmentFile, removeSkills, resolveMcpServerForState, setupMcpServer, uninstallMcpTools, updateSkills } from "./capabilities.js";
|
|
5
|
+
import { createProject, doctorProject, initProject, renameProject, updateProject } from "./project.js";
|
|
6
6
|
import { askCreateOptions } from "./prompts.js";
|
|
7
|
-
import { AGENT_STACK, presetMcpServers } from "./stack.js";
|
|
7
|
+
import { AGENT_STACK, mcpModeLabel, mcpRecommendedMode, mcpServerModeKeys, mcpSupportedModeLabels, presetMcpServers, resolveMcpServer } from "./stack.js";
|
|
8
8
|
import { formatAgentAliasLines, formatAgentTargetList, formatSupportedAgentTargetLines } from "./agents.js";
|
|
9
9
|
import { packageify, slugify, titleFromSlug } from "./names.js";
|
|
10
10
|
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
|
|
@@ -19,9 +19,11 @@ const CREATE_FLAGS = flagSchema([
|
|
|
19
19
|
"no-install-mcp-tools"
|
|
20
20
|
], ["title", "slug", "package", "preset", "profile", "agent"]);
|
|
21
21
|
const ROOT_FLAGS = flagSchema(["help"], ["root"]);
|
|
22
|
+
const UPDATE_FLAGS = flagSchema(["help", "dry-run", "apply"], ["root"]);
|
|
23
|
+
const INIT_FLAGS = flagSchema(["help", "install-skills"], ["root", "title", "slug", "package", "preset", "profile", "agent"]);
|
|
22
24
|
const RENAME_FLAGS = flagSchema(["help"], ["root", "title", "slug", "package"]);
|
|
23
25
|
const SKILLS_FLAGS = flagSchema(["help"], ["root", "preset", "agent"]);
|
|
24
|
-
const MCP_FLAGS = flagSchema(["help", "all", "dotenv", "required", "recommended"], ["root", "agent", "env-file", "write", "timeout-ms"]);
|
|
26
|
+
const MCP_FLAGS = flagSchema(["help", "all", "dotenv", "required", "recommended", "dry-run", "verbose"], ["root", "agent", "env-file", "write", "timeout-ms", "mode", "url", "url-env", "bearer-token-env-var"]);
|
|
25
27
|
export async function main(argv = process.argv.slice(2), mode = "create") {
|
|
26
28
|
try {
|
|
27
29
|
if (mode === "create")
|
|
@@ -113,6 +115,10 @@ async function lifecycleMain(argv) {
|
|
|
113
115
|
}
|
|
114
116
|
if (command === "doctor")
|
|
115
117
|
return doctorCommand(argv.slice(1));
|
|
118
|
+
if (command === "update")
|
|
119
|
+
return updateCommand(argv.slice(1));
|
|
120
|
+
if (command === "init")
|
|
121
|
+
return initCommand(argv.slice(1));
|
|
116
122
|
if (command === "setup")
|
|
117
123
|
return setupCommand(argv.slice(1));
|
|
118
124
|
if (command === "rename")
|
|
@@ -136,10 +142,61 @@ async function doctorCommand(argv) {
|
|
|
136
142
|
const result = await doctorProject(root);
|
|
137
143
|
for (const error of result.errors)
|
|
138
144
|
console.error(`ERROR: ${error}`);
|
|
145
|
+
for (const warning of result.warnings)
|
|
146
|
+
console.warn(`WARN: ${warning}`);
|
|
139
147
|
if (result.ok)
|
|
140
148
|
console.log(`OK: ${root}`);
|
|
141
149
|
return result.ok ? 0 : 1;
|
|
142
150
|
}
|
|
151
|
+
async function updateCommand(argv) {
|
|
152
|
+
const parsed = parseFlags(argv, UPDATE_FLAGS);
|
|
153
|
+
if (flagBool(parsed.flags, "help")) {
|
|
154
|
+
printUpdateHelp();
|
|
155
|
+
return 0;
|
|
156
|
+
}
|
|
157
|
+
assertNoArguments(parsed.positionals, "update");
|
|
158
|
+
if (flagBool(parsed.flags, "dry-run") && flagBool(parsed.flags, "apply")) {
|
|
159
|
+
throw new Error("update cannot use --dry-run and --apply together");
|
|
160
|
+
}
|
|
161
|
+
const root = resolve(flagString(parsed.flags, "root") ?? ".");
|
|
162
|
+
const apply = flagBool(parsed.flags, "apply");
|
|
163
|
+
const result = await updateProject(root, { apply });
|
|
164
|
+
console.log(`${apply ? "UPDATED" : "DRY-RUN"}: ${root}`);
|
|
165
|
+
if (result.changes.length === 0) {
|
|
166
|
+
console.log("No managed file changes.");
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
for (const change of result.changes) {
|
|
170
|
+
console.log(`${change.action}\t${change.path}${change.reason ? `\t${change.reason}` : ""}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (!apply && result.changes.length > 0) {
|
|
174
|
+
console.log("Run `npm run update -- --apply` from a generated project to write these managed changes.");
|
|
175
|
+
}
|
|
176
|
+
return 0;
|
|
177
|
+
}
|
|
178
|
+
async function initCommand(argv) {
|
|
179
|
+
const parsed = parseFlags(argv, INIT_FLAGS);
|
|
180
|
+
if (flagBool(parsed.flags, "help")) {
|
|
181
|
+
printInitHelp();
|
|
182
|
+
return 0;
|
|
183
|
+
}
|
|
184
|
+
assertNoArguments(parsed.positionals, "init");
|
|
185
|
+
const root = resolve(flagString(parsed.flags, "root") ?? ".");
|
|
186
|
+
const result = await initProject({
|
|
187
|
+
target: root,
|
|
188
|
+
title: flagString(parsed.flags, "title"),
|
|
189
|
+
slug: flagString(parsed.flags, "slug"),
|
|
190
|
+
packageName: flagString(parsed.flags, "package"),
|
|
191
|
+
profile: flagString(parsed.flags, "profile") ?? "academic-general",
|
|
192
|
+
preset: flagString(parsed.flags, "preset") ?? "default",
|
|
193
|
+
agent: flagString(parsed.flags, "agent") ?? DEFAULT_AGENT,
|
|
194
|
+
installSkills: flagBool(parsed.flags, "install-skills")
|
|
195
|
+
});
|
|
196
|
+
console.log(`Initialized ${result.slug} at ${result.root}`);
|
|
197
|
+
console.log("Next: run `npm run doctor`.");
|
|
198
|
+
return 0;
|
|
199
|
+
}
|
|
143
200
|
async function setupCommand(argv) {
|
|
144
201
|
const parsed = parseFlags(argv, ROOT_FLAGS);
|
|
145
202
|
if (flagBool(parsed.flags, "help")) {
|
|
@@ -161,19 +218,28 @@ async function setupCommand(argv) {
|
|
|
161
218
|
console.log(`installed_skill_ids\t${skillIds.size}`);
|
|
162
219
|
console.log(`installed_skill_copies\t${skills.length}`);
|
|
163
220
|
console.log(`mcp_enabled\t${state.mcp_servers.length > 0 ? state.mcp_servers.join(",") : "none"}`);
|
|
221
|
+
console.log(`mcp_selected\t${state.mcp_servers.length > 0 ? state.mcp_servers.join(",") : "none"}`);
|
|
164
222
|
if (!project.ok) {
|
|
165
223
|
for (const error of project.errors)
|
|
166
224
|
console.error(`ERROR: ${error}`);
|
|
167
225
|
}
|
|
226
|
+
for (const warning of project.warnings)
|
|
227
|
+
console.warn(`WARN: ${warning}`);
|
|
228
|
+
const lifecycle = await getMcpLifecycleStatus(root);
|
|
168
229
|
console.log("");
|
|
169
230
|
console.log("Next Commands");
|
|
170
231
|
console.log(`npm run skills:install -- --preset ${state.preset}`);
|
|
171
232
|
console.log("npm run skills:status");
|
|
172
233
|
console.log("npm run mcp:list");
|
|
234
|
+
console.log("npm run mcp:status");
|
|
173
235
|
console.log("npm run mcp:env");
|
|
174
236
|
console.log("npm run mcp:dotenv");
|
|
175
237
|
console.log("npm run mcp:smoke");
|
|
176
|
-
|
|
238
|
+
for (const item of lifecycle.servers.filter((server) => server.selected)) {
|
|
239
|
+
for (const command of setupNextCommands(item)) {
|
|
240
|
+
console.log(command);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
177
243
|
console.log("npm run doctor");
|
|
178
244
|
return project.ok ? 0 : 1;
|
|
179
245
|
}
|
|
@@ -313,6 +379,41 @@ async function mcpCommand(argv) {
|
|
|
313
379
|
}
|
|
314
380
|
return 0;
|
|
315
381
|
}
|
|
382
|
+
if (subcommand === "modes") {
|
|
383
|
+
assertOnlyOptions(parsed.flags, "mcp modes", ["root"]);
|
|
384
|
+
const root = resolve(flagString(parsed.flags, "root") ?? ".");
|
|
385
|
+
const state = await readCapabilities(root);
|
|
386
|
+
if (parsed.positionals.length > 1) {
|
|
387
|
+
throw new Error(`mcp modes accepts at most one server: ${parsed.positionals.join(" ")}`);
|
|
388
|
+
}
|
|
389
|
+
if (parsed.positionals.length === 1) {
|
|
390
|
+
assertKnownMcpServers(parsed.positionals);
|
|
391
|
+
printMcpModeDetail(parsed.positionals[0], state);
|
|
392
|
+
return 0;
|
|
393
|
+
}
|
|
394
|
+
printMcpModesTable(state);
|
|
395
|
+
return 0;
|
|
396
|
+
}
|
|
397
|
+
if (subcommand === "status") {
|
|
398
|
+
assertOnlyOptions(parsed.flags, "mcp status", ["root", "env-file", "verbose"]);
|
|
399
|
+
const root = resolve(flagString(parsed.flags, "root") ?? ".");
|
|
400
|
+
assertNoArguments(parsed.positionals, "mcp status");
|
|
401
|
+
const env = await mcpCommandEnvironment(root, parsed.flags);
|
|
402
|
+
const status = await getMcpLifecycleStatus(root, { env });
|
|
403
|
+
if (flagBool(parsed.flags, "verbose")) {
|
|
404
|
+
console.log("id\tselected\tmode\tconnection_mode\tenv\tinstall\tsnippet\tclient\tprobe\tnext");
|
|
405
|
+
for (const item of status.servers) {
|
|
406
|
+
console.log(`${item.id}\t${item.selected ? "yes" : "no"}\t${item.mode}\t${item.connection_mode}\t${item.env}\t${item.install}\t${item.snippet}\t${item.client}\t${item.probe}\t${item.next}`);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
console.log("id\tselected\tmode\tstate\tnext");
|
|
411
|
+
for (const item of status.servers) {
|
|
412
|
+
console.log(`${item.id}\t${item.selected ? "yes" : "no"}\t${item.mode}\t${item.state}\t${friendlyNext(item.next)}`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
return 0;
|
|
416
|
+
}
|
|
316
417
|
if (subcommand === "enabled") {
|
|
317
418
|
assertOnlyOptions(parsed.flags, "mcp enabled", ["root"]);
|
|
318
419
|
const root = resolve(flagString(parsed.flags, "root") ?? ".");
|
|
@@ -334,37 +435,45 @@ async function mcpCommand(argv) {
|
|
|
334
435
|
if (subcommand === "commands") {
|
|
335
436
|
assertOnlyOptions(parsed.flags, "mcp commands", ["root"]);
|
|
336
437
|
const root = resolve(flagString(parsed.flags, "root") ?? ".");
|
|
337
|
-
const
|
|
338
|
-
const
|
|
438
|
+
const state = await readCapabilities(root);
|
|
439
|
+
const selected = parsed.positionals.length > 0 ? parsed.positionals : state.mcp_servers;
|
|
440
|
+
const modes = Object.fromEntries(selected.map((server) => [server, state.mcp_server_modes[server]]));
|
|
441
|
+
const commands = mcpToolCommandTexts(selected, "install_command", modes);
|
|
339
442
|
for (const command of commands)
|
|
340
443
|
console.log(command);
|
|
341
444
|
return 0;
|
|
342
445
|
}
|
|
343
446
|
if (subcommand === "env") {
|
|
344
|
-
assertOnlyOptions(parsed.flags, "mcp env", ["root", "all", "dotenv", "required", "recommended", "write"]);
|
|
447
|
+
assertOnlyOptions(parsed.flags, "mcp env", ["root", "all", "dotenv", "required", "recommended", "write", "mode"]);
|
|
345
448
|
const root = resolve(flagString(parsed.flags, "root") ?? ".");
|
|
346
449
|
if (flagBool(parsed.flags, "required") && flagBool(parsed.flags, "recommended")) {
|
|
347
450
|
throw new Error("mcp env cannot use --required and --recommended together");
|
|
348
451
|
}
|
|
452
|
+
const state = await readCapabilities(root);
|
|
349
453
|
const selected = flagBool(parsed.flags, "all")
|
|
350
454
|
? Object.keys(AGENT_STACK.mcp_servers)
|
|
351
455
|
: parsed.positionals.length > 0
|
|
352
456
|
? parsed.positionals
|
|
353
|
-
:
|
|
457
|
+
: state.mcp_servers;
|
|
354
458
|
assertKnownMcpServers(selected);
|
|
459
|
+
const mode = flagString(parsed.flags, "mode");
|
|
460
|
+
const modes = mode ? undefined : Object.fromEntries(selected.map((server) => [server, state.mcp_server_modes[server]]));
|
|
355
461
|
const filters = {
|
|
356
462
|
requiredOnly: flagBool(parsed.flags, "required"),
|
|
357
|
-
recommendedOnly: flagBool(parsed.flags, "recommended")
|
|
463
|
+
recommendedOnly: flagBool(parsed.flags, "recommended"),
|
|
464
|
+
mode,
|
|
465
|
+
modes,
|
|
466
|
+
remote: state.mcp_server_remote
|
|
358
467
|
};
|
|
359
468
|
const writePath = flagString(parsed.flags, "write");
|
|
360
469
|
if (writePath) {
|
|
361
470
|
const outputPath = resolve(root, writePath);
|
|
362
|
-
writeFileSync(outputPath,
|
|
471
|
+
writeFileSync(outputPath, formatMcpDotenvWithRemote(selected, filters), "utf8");
|
|
363
472
|
console.log(`Wrote MCP dotenv environment reference: ${outputPath}`);
|
|
364
473
|
return 0;
|
|
365
474
|
}
|
|
366
475
|
if (flagBool(parsed.flags, "dotenv")) {
|
|
367
|
-
process.stdout.write(
|
|
476
|
+
process.stdout.write(formatMcpDotenvWithRemote(selected, filters));
|
|
368
477
|
return 0;
|
|
369
478
|
}
|
|
370
479
|
console.log("id\ttype\tvalue");
|
|
@@ -372,11 +481,15 @@ async function mcpCommand(argv) {
|
|
|
372
481
|
return 0;
|
|
373
482
|
}
|
|
374
483
|
if (subcommand === "enable") {
|
|
375
|
-
assertOnlyOptions(parsed.flags, "mcp enable", ["root", "agent"]);
|
|
484
|
+
assertOnlyOptions(parsed.flags, "mcp enable", ["root", "agent", "mode", "url", "url-env", "bearer-token-env-var"]);
|
|
376
485
|
const root = resolve(flagString(parsed.flags, "root") ?? ".");
|
|
377
486
|
assertSomeArguments(parsed.positionals, "mcp enable");
|
|
378
487
|
const agent = flagString(parsed.flags, "agent");
|
|
379
|
-
await enableMcpServers(root, parsed.positionals,
|
|
488
|
+
await enableMcpServers(root, parsed.positionals, {
|
|
489
|
+
...(agent ? { agent } : {}),
|
|
490
|
+
mode: flagString(parsed.flags, "mode"),
|
|
491
|
+
remote: mcpRemoteOptions(parsed.flags)
|
|
492
|
+
});
|
|
380
493
|
return 0;
|
|
381
494
|
}
|
|
382
495
|
if (subcommand === "disable") {
|
|
@@ -392,6 +505,9 @@ async function mcpCommand(argv) {
|
|
|
392
505
|
const root = resolve(flagString(parsed.flags, "root") ?? ".");
|
|
393
506
|
const result = await installMcpTools(root, parsed.positionals);
|
|
394
507
|
console.log(`Ran ${result.count ?? 0} MCP install command(s).`);
|
|
508
|
+
for (const skipped of result.skipped ?? []) {
|
|
509
|
+
console.log(`Skipped ${skipped.server}: ${skipped.reason}; ${skipped.next ?? "no install action needed"}.`);
|
|
510
|
+
}
|
|
395
511
|
return 0;
|
|
396
512
|
}
|
|
397
513
|
if (subcommand === "uninstall") {
|
|
@@ -399,17 +515,41 @@ async function mcpCommand(argv) {
|
|
|
399
515
|
const root = resolve(flagString(parsed.flags, "root") ?? ".");
|
|
400
516
|
const result = await uninstallMcpTools(root, parsed.positionals);
|
|
401
517
|
console.log(`Ran ${result.count ?? 0} MCP uninstall command(s).`);
|
|
518
|
+
for (const skipped of result.skipped ?? []) {
|
|
519
|
+
console.log(`Skipped ${skipped.server}: ${skipped.reason}; ${skipped.next ?? "no uninstall action needed"}.`);
|
|
520
|
+
}
|
|
402
521
|
return 0;
|
|
403
522
|
}
|
|
523
|
+
if (subcommand === "setup") {
|
|
524
|
+
assertOnlyOptions(parsed.flags, "mcp setup", ["root", "mode", "env-file", "dry-run"]);
|
|
525
|
+
const root = resolve(flagString(parsed.flags, "root") ?? ".");
|
|
526
|
+
assertSomeArguments(parsed.positionals, "mcp setup");
|
|
527
|
+
if (parsed.positionals.length > 1)
|
|
528
|
+
throw new Error(`mcp setup accepts one server at a time: ${parsed.positionals.join(" ")}`);
|
|
529
|
+
const env = await mcpCommandEnvironment(root, parsed.flags);
|
|
530
|
+
const result = await setupMcpServer(root, parsed.positionals[0], {
|
|
531
|
+
mode: flagString(parsed.flags, "mode"),
|
|
532
|
+
envFile: flagString(parsed.flags, "env-file"),
|
|
533
|
+
env,
|
|
534
|
+
dryRun: flagBool(parsed.flags, "dry-run")
|
|
535
|
+
});
|
|
536
|
+
printMcpSetupResult(result);
|
|
537
|
+
return result.ok ? 0 : 1;
|
|
538
|
+
}
|
|
539
|
+
if (subcommand === "client") {
|
|
540
|
+
return mcpClientCommand(parsed);
|
|
541
|
+
}
|
|
404
542
|
if (subcommand === "smoke") {
|
|
405
|
-
assertOnlyOptions(parsed.flags, "mcp smoke", ["root", "env-file"]);
|
|
543
|
+
assertOnlyOptions(parsed.flags, "mcp smoke", ["root", "env-file", "mode"]);
|
|
406
544
|
const root = resolve(flagString(parsed.flags, "root") ?? ".");
|
|
407
545
|
const env = await mcpCommandEnvironment(root, parsed.flags);
|
|
408
546
|
const state = await readCapabilities(root);
|
|
409
547
|
const explicitSelection = parsed.positionals.length > 0;
|
|
410
548
|
const selected = explicitSelection ? parsed.positionals : state.mcp_servers;
|
|
411
549
|
assertKnownMcpServers(selected);
|
|
412
|
-
const
|
|
550
|
+
const mode = flagString(parsed.flags, "mode");
|
|
551
|
+
const modes = Object.fromEntries(selected.map((server) => [server, mode ?? state.mcp_server_modes[server]]));
|
|
552
|
+
const failed = printMcpSmokeDiagnostics(root, selected, env, modes, state);
|
|
413
553
|
if (!explicitSelection) {
|
|
414
554
|
const result = await doctorMcpServers(root, { env });
|
|
415
555
|
for (const error of result.errors)
|
|
@@ -435,7 +575,7 @@ async function mcpCommand(argv) {
|
|
|
435
575
|
return result.ok ? 0 : 1;
|
|
436
576
|
}
|
|
437
577
|
if (subcommand === "probe") {
|
|
438
|
-
assertOnlyOptions(parsed.flags, "mcp probe", ["root", "all", "env-file", "timeout-ms"]);
|
|
578
|
+
assertOnlyOptions(parsed.flags, "mcp probe", ["root", "all", "env-file", "timeout-ms", "mode"]);
|
|
439
579
|
const root = resolve(flagString(parsed.flags, "root") ?? ".");
|
|
440
580
|
const selected = flagBool(parsed.flags, "all")
|
|
441
581
|
? Object.keys(AGENT_STACK.mcp_servers)
|
|
@@ -445,7 +585,12 @@ async function mcpCommand(argv) {
|
|
|
445
585
|
assertKnownMcpServers(selected);
|
|
446
586
|
const timeoutMs = parseTimeoutMs(flagString(parsed.flags, "timeout-ms"));
|
|
447
587
|
const env = await mcpCommandEnvironment(root, parsed.flags);
|
|
448
|
-
const result = await probeMcpServers(root, selected, {
|
|
588
|
+
const result = await probeMcpServers(root, selected, {
|
|
589
|
+
env,
|
|
590
|
+
timeoutMs,
|
|
591
|
+
clientVersion: packageVersion,
|
|
592
|
+
mode: flagString(parsed.flags, "mode")
|
|
593
|
+
});
|
|
449
594
|
console.log("id\tstatus\tdetail");
|
|
450
595
|
for (const item of result.results)
|
|
451
596
|
console.log(`${item.server}\t${item.status}\t${item.detail}`);
|
|
@@ -453,6 +598,138 @@ async function mcpCommand(argv) {
|
|
|
453
598
|
}
|
|
454
599
|
throw new Error(`unknown mcp command: ${subcommand}`);
|
|
455
600
|
}
|
|
601
|
+
async function mcpClientCommand(parsed) {
|
|
602
|
+
const action = parsed.positionals[0];
|
|
603
|
+
const server = parsed.positionals[1];
|
|
604
|
+
if (!action || action === "help" || action === "--help" || action === "-h") {
|
|
605
|
+
printMcpHelp();
|
|
606
|
+
return 0;
|
|
607
|
+
}
|
|
608
|
+
if (action !== "add" && action !== "remove")
|
|
609
|
+
throw new Error(`unknown mcp client command: ${action}`);
|
|
610
|
+
if (!server)
|
|
611
|
+
throw new Error(`mcp client ${action} requires a server`);
|
|
612
|
+
if (parsed.positionals.length > 2) {
|
|
613
|
+
throw new Error(`mcp client ${action} accepts one server: ${parsed.positionals.slice(1).join(" ")}`);
|
|
614
|
+
}
|
|
615
|
+
assertOnlyOptions(parsed.flags, `mcp client ${action}`, ["root", "agent", "mode", "dry-run"]);
|
|
616
|
+
const root = resolve(flagString(parsed.flags, "root") ?? ".");
|
|
617
|
+
const options = {
|
|
618
|
+
agent: flagString(parsed.flags, "agent"),
|
|
619
|
+
mode: flagString(parsed.flags, "mode"),
|
|
620
|
+
dryRun: flagBool(parsed.flags, "dry-run")
|
|
621
|
+
};
|
|
622
|
+
const result = action === "add"
|
|
623
|
+
? await clientAddMcpServer(root, server, options)
|
|
624
|
+
: await clientRemoveMcpServer(root, server, options);
|
|
625
|
+
if (result.command.length > 0) {
|
|
626
|
+
console.log(result.command.join(" "));
|
|
627
|
+
}
|
|
628
|
+
for (const instruction of result.instructions)
|
|
629
|
+
console.log(instruction);
|
|
630
|
+
return result.ok || options.dryRun ? 0 : 1;
|
|
631
|
+
}
|
|
632
|
+
function setupNextCommands(item) {
|
|
633
|
+
const commands = [];
|
|
634
|
+
if (item.next === "ready")
|
|
635
|
+
return commands;
|
|
636
|
+
if (item.connection_mode === "manual-local") {
|
|
637
|
+
if (item.env === "missing-required")
|
|
638
|
+
commands.push("fill OVERLEAF_TOKEN, PROJECT_ID in .env.local");
|
|
639
|
+
if (item.install !== "ready") {
|
|
640
|
+
commands.push("npm run mcp:setup -- overleaf --mode local --env-file .env.local");
|
|
641
|
+
return dedupeStrings(commands);
|
|
642
|
+
}
|
|
643
|
+
if (item.client.endsWith(":not-added"))
|
|
644
|
+
commands.push("npm run mcp:client:add -- overleaf --agent codex");
|
|
645
|
+
if (item.probe === "unknown")
|
|
646
|
+
commands.push("npm run mcp:probe -- overleaf --env-file .env.local");
|
|
647
|
+
return dedupeStrings(commands);
|
|
648
|
+
}
|
|
649
|
+
commands.push(item.next.replace(/^run /, ""));
|
|
650
|
+
return dedupeStrings(commands);
|
|
651
|
+
}
|
|
652
|
+
function dedupeStrings(values) {
|
|
653
|
+
return [...new Set(values.filter(Boolean))];
|
|
654
|
+
}
|
|
655
|
+
function printMcpModesTable(state) {
|
|
656
|
+
const selected = new Set(state.mcp_servers ?? []);
|
|
657
|
+
console.log("id\tselected\trecommended\tsupported\tenv\tnext");
|
|
658
|
+
for (const name of Object.keys(AGENT_STACK.mcp_servers)) {
|
|
659
|
+
const required = modeEnvSummary(name);
|
|
660
|
+
const recommended = modeKeyDisplay(mcpRecommendedMode(name));
|
|
661
|
+
const supported = orderedModeLabels(name).join(", ");
|
|
662
|
+
const next = selected.has(name) ? "ready" : `enable ${name}`;
|
|
663
|
+
console.log(`${name}\t${selected.has(name) ? "yes" : "no"}\t${recommended}\t${supported}\t${required}\t${next}`);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
function printMcpModeDetail(serverName, state) {
|
|
667
|
+
const selected = new Set(state.mcp_servers ?? []);
|
|
668
|
+
const uniqueLabels = orderedModeLabels(serverName);
|
|
669
|
+
console.log(`${serverName} supports ${formatHumanList(uniqueLabels)}.`);
|
|
670
|
+
console.log(`Selected: ${selected.has(serverName) ? "yes" : "no"}`);
|
|
671
|
+
console.log(`Recommended: ${mcpModeLabel(serverName, mcpRecommendedMode(serverName))}`);
|
|
672
|
+
console.log(`Env: ${modeEnvSummary(serverName)}`);
|
|
673
|
+
console.log(`Next: ${selected.has(serverName) ? "npm run mcp:status" : `npm run mcp:enable -- ${serverName} --mode ${mcpRecommendedMode(serverName)}`}`);
|
|
674
|
+
for (const mode of mcpServerModeKeys(serverName)) {
|
|
675
|
+
const resolved = resolveMcpServer(serverName, mode);
|
|
676
|
+
const details = [
|
|
677
|
+
`mode ${mode}: ${mcpModeLabel(serverName, mode)}`,
|
|
678
|
+
resolved.hosted_url ? `endpoint ${resolved.hosted_url}` : "",
|
|
679
|
+
resolved.command ? `runtime ${[resolved.command, ...resolved.args].join(" ")}` : "",
|
|
680
|
+
resolved.local_service ? `requires ${resolved.local_service}` : ""
|
|
681
|
+
].filter(Boolean);
|
|
682
|
+
console.log(details.join("; "));
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
function modeEnvSummary(serverName) {
|
|
686
|
+
const names = new Set();
|
|
687
|
+
for (const mode of mcpServerModeKeys(serverName)) {
|
|
688
|
+
const server = resolveMcpServer(serverName, mode);
|
|
689
|
+
for (const name of server.required_env)
|
|
690
|
+
names.add(name);
|
|
691
|
+
for (const name of server.recommended_env)
|
|
692
|
+
names.add(name);
|
|
693
|
+
}
|
|
694
|
+
return names.size > 0 ? [...names].join(", ") : "none";
|
|
695
|
+
}
|
|
696
|
+
function orderedModeLabels(serverName) {
|
|
697
|
+
const recommended = mcpModeLabel(serverName, mcpRecommendedMode(serverName));
|
|
698
|
+
const labels = mcpSupportedModeLabels(serverName);
|
|
699
|
+
return [recommended, ...labels.filter((label) => label !== recommended)];
|
|
700
|
+
}
|
|
701
|
+
function modeKeyDisplay(mode) {
|
|
702
|
+
if (mode === "remote-custom")
|
|
703
|
+
return "custom remote";
|
|
704
|
+
if (mode === "remote")
|
|
705
|
+
return "remote";
|
|
706
|
+
if (mode === "manual")
|
|
707
|
+
return "manual setup";
|
|
708
|
+
return "local";
|
|
709
|
+
}
|
|
710
|
+
function formatHumanList(values) {
|
|
711
|
+
if (values.length <= 1)
|
|
712
|
+
return values[0] ?? "";
|
|
713
|
+
if (values.length === 2)
|
|
714
|
+
return `${values[0]} and ${values[1]}`;
|
|
715
|
+
return `${values.slice(0, -1).join(", ")}, and ${values.at(-1)}`;
|
|
716
|
+
}
|
|
717
|
+
function friendlyNext(next) {
|
|
718
|
+
return next.replace(/^run /, "");
|
|
719
|
+
}
|
|
720
|
+
function mcpRemoteOptions(flags) {
|
|
721
|
+
const url = flagString(flags, "url");
|
|
722
|
+
const urlEnv = flagString(flags, "url-env");
|
|
723
|
+
const bearerTokenEnvVar = flagString(flags, "bearer-token-env-var");
|
|
724
|
+
if (!url && !urlEnv && !bearerTokenEnvVar)
|
|
725
|
+
return undefined;
|
|
726
|
+
return {
|
|
727
|
+
...(url ? { url } : {}),
|
|
728
|
+
...(urlEnv ? { url_env: urlEnv } : {}),
|
|
729
|
+
transport: "streamable-http",
|
|
730
|
+
...(bearerTokenEnvVar ? { bearer_token_env_var: bearerTokenEnvVar } : {})
|
|
731
|
+
};
|
|
732
|
+
}
|
|
456
733
|
function parseFlags(argv, schema) {
|
|
457
734
|
const flags = {};
|
|
458
735
|
const positionals = [];
|
|
@@ -580,6 +857,9 @@ export function formatInteractiveCreateGuide() {
|
|
|
580
857
|
" MCP installers are optional and run only finite installer commands.",
|
|
581
858
|
" MCP execution modes are explicit: uvx-runtime, npx-runtime, local-service, manual, or fallback.",
|
|
582
859
|
" Use `npm run mcp:env -- <server>` to inspect env vars and local prerequisites.",
|
|
860
|
+
" Use `npm run mcp:status` to see selected mode, setup, client, probe, and next action.",
|
|
861
|
+
" Use `npm run mcp:enable -- <server> --mode remote` for hosted endpoints where supported.",
|
|
862
|
+
" Use `npm run mcp:setup -- overleaf --mode local --env-file .env.local` for manual-local setup.",
|
|
583
863
|
" Use `npm run mcp:env -- --dotenv --all` to print a committed env example.",
|
|
584
864
|
" Use `npm run mcp:dotenv` to regenerate a committed env example.",
|
|
585
865
|
" Use `npm run mcp:doctor -- --env-file .env.local` to check explicit local secrets.",
|
|
@@ -619,7 +899,7 @@ function printMissingTargetHelp() {
|
|
|
619
899
|
}
|
|
620
900
|
function printLifecycleHelp() {
|
|
621
901
|
console.log([
|
|
622
|
-
"Usage: academic-research <doctor|setup|rename|agents|skills|mcp>",
|
|
902
|
+
"Usage: academic-research <doctor|update|init|setup|rename|agents|skills|mcp>",
|
|
623
903
|
"",
|
|
624
904
|
"Manage a generated academic research repository after creation.",
|
|
625
905
|
"",
|
|
@@ -628,6 +908,37 @@ function printLifecycleHelp() {
|
|
|
628
908
|
" -v, --version Show package version."
|
|
629
909
|
].join("\n"));
|
|
630
910
|
}
|
|
911
|
+
function printUpdateHelp() {
|
|
912
|
+
console.log([
|
|
913
|
+
"Usage: academic-research update [options]",
|
|
914
|
+
"",
|
|
915
|
+
"Preview or apply non-destructive updates to managed project files.",
|
|
916
|
+
"",
|
|
917
|
+
"Options:",
|
|
918
|
+
" --root <path> Project root. Default: current directory.",
|
|
919
|
+
" --dry-run Preview managed changes without writing. Default.",
|
|
920
|
+
" --apply Write managed changes.",
|
|
921
|
+
" -h, --help Show this help."
|
|
922
|
+
].join("\n"));
|
|
923
|
+
}
|
|
924
|
+
function printInitHelp() {
|
|
925
|
+
console.log([
|
|
926
|
+
"Usage: academic-research init [options]",
|
|
927
|
+
"",
|
|
928
|
+
"Initialize an existing repository without overwriting existing files.",
|
|
929
|
+
"",
|
|
930
|
+
"Options:",
|
|
931
|
+
" --root <path> Project root. Default: current directory.",
|
|
932
|
+
" --title <name> Project title. Default: title-cased directory name.",
|
|
933
|
+
" --slug <name> Repository/package slug. Default: normalized directory name.",
|
|
934
|
+
" --package <name> Python package name. Default: normalized directory name.",
|
|
935
|
+
" --preset <name> Capability preset: minimal, default, enhanced, literature, writing, full.",
|
|
936
|
+
" --profile <name> Project profile metadata. Default: academic-general.",
|
|
937
|
+
" --agent <id> Agent target: universal, auto, or a supported skills.sh id.",
|
|
938
|
+
" --install-skills Install project-local skills after initialization.",
|
|
939
|
+
" -h, --help Show this help."
|
|
940
|
+
].join("\n"));
|
|
941
|
+
}
|
|
631
942
|
function printSetupHelp() {
|
|
632
943
|
console.log([
|
|
633
944
|
"Usage: academic-research setup [options]",
|
|
@@ -673,12 +984,20 @@ function printSkillsHelp() {
|
|
|
673
984
|
}
|
|
674
985
|
function printMcpHelp() {
|
|
675
986
|
console.log([
|
|
676
|
-
"Usage: academic-research mcp <list|enabled|available|commands|env|enable|disable|install|uninstall|smoke|doctor|probe> [servers...]",
|
|
987
|
+
"Usage: academic-research mcp <list|modes|status|enabled|available|commands|env|enable|disable|setup|client|install|uninstall|smoke|doctor|probe> [servers...]",
|
|
677
988
|
"",
|
|
678
989
|
"Manage MCP records, readiness checks, and finite external MCP tool installs.",
|
|
679
990
|
"",
|
|
680
991
|
"Examples:",
|
|
992
|
+
" academic-research mcp modes",
|
|
993
|
+
" academic-research mcp modes openalex",
|
|
681
994
|
" academic-research mcp env openalex semantic-scholar",
|
|
995
|
+
" academic-research mcp enable openalex --mode remote",
|
|
996
|
+
" academic-research mcp enable openalex --mode remote-custom --url https://example.com/mcp",
|
|
997
|
+
" academic-research mcp status",
|
|
998
|
+
" academic-research mcp status --verbose",
|
|
999
|
+
" academic-research mcp setup overleaf --mode local --env-file .env.local",
|
|
1000
|
+
" academic-research mcp client add overleaf --agent codex",
|
|
682
1001
|
" academic-research mcp env --dotenv --all > .env.example",
|
|
683
1002
|
" academic-research mcp env --write .env.example --all",
|
|
684
1003
|
" academic-research mcp doctor --env-file .env.local",
|
|
@@ -687,31 +1006,45 @@ function printMcpHelp() {
|
|
|
687
1006
|
"",
|
|
688
1007
|
"Options:",
|
|
689
1008
|
" --root <path> Project root for project-state commands.",
|
|
690
|
-
" --agent <id> Agent for enable/disable
|
|
1009
|
+
" --agent <id> Agent for enable/disable snippets or client registration.",
|
|
1010
|
+
" --mode <mode> Connection mode: local, remote, remote-custom, or manual where supported.",
|
|
1011
|
+
" --url <url> Custom remote MCP endpoint URL for --mode remote-custom.",
|
|
1012
|
+
" --url-env <name> Env var that contains a custom remote MCP endpoint URL.",
|
|
1013
|
+
" --bearer-token-env-var <name>",
|
|
1014
|
+
" Env var that contains a custom remote bearer token; value is not stored.",
|
|
691
1015
|
" --all Select all catalog MCP servers for mcp env.",
|
|
1016
|
+
" --verbose Show technical MCP lifecycle fields for mcp status.",
|
|
692
1017
|
" --dotenv Print mcp env as dotenv content.",
|
|
693
1018
|
" --write <path> Write mcp env dotenv content to a file.",
|
|
694
|
-
" --env-file <path> Read local env values for mcp smoke, doctor, and probe.",
|
|
1019
|
+
" --env-file <path> Read local env values for mcp setup, smoke, doctor, and probe.",
|
|
695
1020
|
" --timeout-ms <ms> Per-server probe timeout. Default: 5000.",
|
|
696
1021
|
" --required Print only required env vars for mcp env.",
|
|
697
1022
|
" --recommended Print only recommended/default env vars for mcp env.",
|
|
1023
|
+
" --dry-run Print setup or client registration actions without changing external state.",
|
|
698
1024
|
" -h, --help Show this help."
|
|
699
1025
|
].join("\n"));
|
|
700
1026
|
}
|
|
701
|
-
function printMcpSmokeDiagnostics(servers, env = process.env) {
|
|
1027
|
+
function printMcpSmokeDiagnostics(root, servers, env = process.env, modes = {}, state) {
|
|
702
1028
|
let failed = false;
|
|
703
1029
|
console.log("id\tstatus\truntime\tcheck");
|
|
704
1030
|
for (const name of servers) {
|
|
705
|
-
const server =
|
|
706
|
-
const missingRequired = server.required_env.filter((envName) => !env
|
|
1031
|
+
const server = state ? resolveMcpServerForState(state, name, modes[name]) : resolveMcpServer(name, modes[name]);
|
|
1032
|
+
const missingRequired = server.required_env.filter((envName) => !envHasValue(env, envName));
|
|
707
1033
|
if (missingRequired.length > 0)
|
|
708
1034
|
failed = true;
|
|
709
|
-
const runtime =
|
|
1035
|
+
const runtime = mcpSmokeRuntime(name, server, state);
|
|
710
1036
|
let status = "manual";
|
|
711
1037
|
if (missingRequired.length > 0) {
|
|
712
1038
|
status = `missing-required-env:${missingRequired.join(",")}`;
|
|
713
1039
|
}
|
|
714
|
-
else if (server.
|
|
1040
|
+
else if (server.connection_mode === "remote-custom" && !server.remote_configured) {
|
|
1041
|
+
failed = true;
|
|
1042
|
+
status = "missing-remote-url";
|
|
1043
|
+
}
|
|
1044
|
+
else if (server.connection_mode === "remote-curated" || server.connection_mode === "remote-custom") {
|
|
1045
|
+
status = "remote-endpoint";
|
|
1046
|
+
}
|
|
1047
|
+
else if (server.command && commandExists(commandForRuntime(root, server.command), env)) {
|
|
715
1048
|
status = "runtime-found";
|
|
716
1049
|
}
|
|
717
1050
|
else if (server.command) {
|
|
@@ -724,6 +1057,51 @@ function printMcpSmokeDiagnostics(servers, env = process.env) {
|
|
|
724
1057
|
}
|
|
725
1058
|
return failed;
|
|
726
1059
|
}
|
|
1060
|
+
function mcpSmokeRuntime(name, server, state) {
|
|
1061
|
+
if (server.connection_mode === "remote-custom") {
|
|
1062
|
+
const urlEnv = state?.mcp_server_remote?.[name]?.url_env;
|
|
1063
|
+
if (!server.remote_configured)
|
|
1064
|
+
return "custom remote endpoint not configured";
|
|
1065
|
+
return urlEnv ? `custom remote endpoint from ${urlEnv}` : "custom remote endpoint";
|
|
1066
|
+
}
|
|
1067
|
+
if (server.hosted_url && !server.command)
|
|
1068
|
+
return server.hosted_url;
|
|
1069
|
+
if (server.command)
|
|
1070
|
+
return [server.command, ...server.args].join(" ");
|
|
1071
|
+
return "manual setup";
|
|
1072
|
+
}
|
|
1073
|
+
function envHasValue(env, name) {
|
|
1074
|
+
return typeof env[name] === "string" && env[name] !== "";
|
|
1075
|
+
}
|
|
1076
|
+
function printMcpSetupResult(result) {
|
|
1077
|
+
const title = result.server === "overleaf" ? "Overleaf setup plan" : `MCP setup plan: ${result.server}`;
|
|
1078
|
+
console.log(title);
|
|
1079
|
+
console.log(`server\t${result.server}`);
|
|
1080
|
+
console.log(`mode\t${result.mode}`);
|
|
1081
|
+
console.log(`status\t${result.ok ? "ok" : "blocked"}`);
|
|
1082
|
+
for (const error of result.errors)
|
|
1083
|
+
console.error(`ERROR: ${error}`);
|
|
1084
|
+
for (const warning of result.warnings)
|
|
1085
|
+
console.warn(`WARN: ${warning}`);
|
|
1086
|
+
if (result.commands.length > 0) {
|
|
1087
|
+
console.log("");
|
|
1088
|
+
console.log("Commands");
|
|
1089
|
+
for (const command of result.commands)
|
|
1090
|
+
console.log(command);
|
|
1091
|
+
}
|
|
1092
|
+
if (result.created.length > 0) {
|
|
1093
|
+
console.log("");
|
|
1094
|
+
console.log("Created");
|
|
1095
|
+
for (const path of result.created)
|
|
1096
|
+
console.log(path);
|
|
1097
|
+
}
|
|
1098
|
+
if (result.next.length > 0) {
|
|
1099
|
+
console.log("");
|
|
1100
|
+
console.log("Next");
|
|
1101
|
+
for (const command of result.next)
|
|
1102
|
+
console.log(command);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
727
1105
|
function printMcpEnvironment(servers, options = {}) {
|
|
728
1106
|
const grouped = new Map();
|
|
729
1107
|
for (const entry of listMcpEnvironmentEntries(servers, options)) {
|
|
@@ -732,23 +1110,36 @@ function printMcpEnvironment(servers, options = {}) {
|
|
|
732
1110
|
grouped.set(entry.server, entries);
|
|
733
1111
|
}
|
|
734
1112
|
for (const name of servers) {
|
|
735
|
-
const
|
|
1113
|
+
const modeServer = flagModeServer(name, options.mode ?? options.modes?.[name]);
|
|
736
1114
|
const entries = grouped.get(name) ?? [];
|
|
737
1115
|
let wroteLine = false;
|
|
738
1116
|
for (const entry of entries) {
|
|
739
1117
|
console.log(`${name}\t${entry.kind}\t${entry.name}${entry.value ? `=${entry.value}` : ""}`);
|
|
740
1118
|
wroteLine = true;
|
|
741
1119
|
}
|
|
742
|
-
if (!options.requiredOnly && !options.recommendedOnly &&
|
|
743
|
-
console.log(`${name}\thosted-endpoint\t${
|
|
1120
|
+
if (!options.requiredOnly && !options.recommendedOnly && modeServer.hosted_url) {
|
|
1121
|
+
console.log(`${name}\thosted-endpoint\t${modeServer.hosted_url}`);
|
|
1122
|
+
wroteLine = true;
|
|
1123
|
+
}
|
|
1124
|
+
const remote = options.remote?.[name];
|
|
1125
|
+
if (!options.requiredOnly && !options.recommendedOnly && remote?.url) {
|
|
1126
|
+
console.log(`${name}\tcustom-remote-url\t${remote.url}`);
|
|
1127
|
+
wroteLine = true;
|
|
1128
|
+
}
|
|
1129
|
+
if (!options.recommendedOnly && remote?.url_env) {
|
|
1130
|
+
console.log(`${name}\trequired\t${remote.url_env}`);
|
|
744
1131
|
wroteLine = true;
|
|
745
1132
|
}
|
|
746
|
-
if (!options.requiredOnly &&
|
|
747
|
-
console.log(`${name}\
|
|
1133
|
+
if (!options.requiredOnly && remote?.bearer_token_env_var) {
|
|
1134
|
+
console.log(`${name}\trecommended\t${remote.bearer_token_env_var}`);
|
|
1135
|
+
wroteLine = true;
|
|
1136
|
+
}
|
|
1137
|
+
if (!options.requiredOnly && !options.recommendedOnly && modeServer.local_service) {
|
|
1138
|
+
console.log(`${name}\tlocal-service\t${modeServer.local_service}`);
|
|
748
1139
|
wroteLine = true;
|
|
749
1140
|
}
|
|
750
1141
|
if (!options.requiredOnly && !options.recommendedOnly) {
|
|
751
|
-
for (const command of
|
|
1142
|
+
for (const command of modeServer.setup_commands) {
|
|
752
1143
|
console.log(`${name}\tsetup-command\t${command}`);
|
|
753
1144
|
wroteLine = true;
|
|
754
1145
|
}
|
|
@@ -757,6 +1148,22 @@ function printMcpEnvironment(servers, options = {}) {
|
|
|
757
1148
|
console.log(`${name}\tnone\t-`);
|
|
758
1149
|
}
|
|
759
1150
|
}
|
|
1151
|
+
function formatMcpDotenvWithRemote(servers, options = {}) {
|
|
1152
|
+
const base = formatMcpDotenv(servers, options);
|
|
1153
|
+
const lines = [];
|
|
1154
|
+
for (const name of servers) {
|
|
1155
|
+
const remote = options.remote?.[name];
|
|
1156
|
+
if (!remote)
|
|
1157
|
+
continue;
|
|
1158
|
+
if (!options.recommendedOnly && remote.url_env)
|
|
1159
|
+
lines.push(`${remote.url_env}=`);
|
|
1160
|
+
if (!options.requiredOnly && remote.bearer_token_env_var)
|
|
1161
|
+
lines.push(`${remote.bearer_token_env_var}=`);
|
|
1162
|
+
}
|
|
1163
|
+
if (lines.length === 0)
|
|
1164
|
+
return base;
|
|
1165
|
+
return `${base.trimEnd()}\n\n# Custom remote MCP endpoint environment\n${lines.join("\n")}\n`;
|
|
1166
|
+
}
|
|
760
1167
|
function commandExists(command, env = process.env) {
|
|
761
1168
|
if (!command)
|
|
762
1169
|
return false;
|
|
@@ -776,6 +1183,14 @@ function commandExists(command, env = process.env) {
|
|
|
776
1183
|
}
|
|
777
1184
|
return false;
|
|
778
1185
|
}
|
|
1186
|
+
function commandForRuntime(root, command) {
|
|
1187
|
+
if (!command.includes("/") && !command.includes("\\"))
|
|
1188
|
+
return command;
|
|
1189
|
+
return resolve(root, command);
|
|
1190
|
+
}
|
|
1191
|
+
function flagModeServer(name, mode) {
|
|
1192
|
+
return resolveMcpServer(name, mode);
|
|
1193
|
+
}
|
|
779
1194
|
function readPackageVersion() {
|
|
780
1195
|
try {
|
|
781
1196
|
const packageJson = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8"));
|