@agiflowai/one-mcp 0.2.7 → 0.2.8

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/dist/cli.mjs CHANGED
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import { a as SkillService, c as ConfigFetcherService, i as createServer, n as SseTransportHandler, o as findConfigFile, r as StdioTransportHandler, s as McpClientManagerService, t as HttpTransportHandler } from "./http-DSkkpGJU.mjs";
2
+ import { a as SkillService, c as ConfigFetcherService, i as createServer, n as SseTransportHandler, o as findConfigFile, r as StdioTransportHandler, s as McpClientManagerService, t as HttpTransportHandler } from "./http-DeUYygKb.mjs";
3
3
  import { writeFile } from "node:fs/promises";
4
4
  import { resolve } from "node:path";
5
5
  import { Liquid } from "liquidjs";
6
6
  import { Command } from "commander";
7
- import { log } from "@agiflowai/aicode-utils";
7
+ import { log, print } from "@agiflowai/aicode-utils";
8
+ import { spawn } from "node:child_process";
8
9
 
9
10
  //#region src/types/index.ts
10
11
  /**
@@ -68,7 +69,7 @@ async function startServer(handler) {
68
69
  /**
69
70
  * MCP Serve command
70
71
  */
71
- const mcpServeCommand = new Command("mcp-serve").description("Start MCP server with specified transport").option("-t, --type <type>", "Transport type: stdio, http, or sse", "stdio").option("-p, --port <port>", "Port to listen on (http/sse only)", (val) => parseInt(val, 10), 3e3).option("--host <host>", "Host to bind to (http/sse only)", "localhost").option("-c, --config <path>", "Path to MCP server configuration file").option("--no-cache", "Disable configuration caching, always reload from config file").action(async (options) => {
72
+ const mcpServeCommand = new Command("mcp-serve").description("Start MCP server with specified transport").option("-t, --type <type>", "Transport type: stdio, http, or sse", "stdio").option("-p, --port <port>", "Port to listen on (http/sse only)", (val) => parseInt(val, 10), 3e3).option("--host <host>", "Host to bind to (http/sse only)", "localhost").option("-c, --config <path>", "Path to MCP server configuration file").option("--no-cache", "Disable configuration caching, always reload from config file").option("--id <id>", "Unique server identifier (overrides config file id, auto-generated if not provided)").action(async (options) => {
72
73
  const transportType = options.type.toLowerCase();
73
74
  if (!isValidTransportType(transportType)) {
74
75
  console.error(`Unknown transport type: '${transportType}'. Valid options: stdio, http, sse`);
@@ -77,7 +78,8 @@ const mcpServeCommand = new Command("mcp-serve").description("Start MCP server w
77
78
  try {
78
79
  const serverOptions = {
79
80
  configFilePath: options.config || findConfigFile() || void 0,
80
- noCache: options.cache === false
81
+ noCache: options.cache === false,
82
+ serverId: options.id
81
83
  };
82
84
  if (transportType === "stdio") await startServer(new StdioTransportHandler(await createServer(serverOptions)));
83
85
  else if (transportType === "http") await startServer(new HttpTransportHandler(await createServer(serverOptions), {
@@ -146,14 +148,27 @@ const listToolsCommand = new Command("list-tools").description("List all availab
146
148
  process.exit(1);
147
149
  }
148
150
  const toolsByServer = {};
149
- for (const client of clients) try {
150
- const tools = await client.listTools();
151
- const blacklist = new Set(client.toolBlacklist || []);
152
- const filteredTools = tools.filter((t) => !blacklist.has(t.name));
153
- toolsByServer[client.serverName] = filteredTools;
154
- } catch (error) {
155
- if (!options.json) console.error(`Failed to list tools from ${client.serverName}:`, error);
156
- toolsByServer[client.serverName] = [];
151
+ const toolResults = await Promise.all(clients.map(async (client) => {
152
+ try {
153
+ const tools = await client.listTools();
154
+ const blacklist = new Set(client.toolBlacklist || []);
155
+ const filteredTools = tools.filter((t) => !blacklist.has(t.name));
156
+ return {
157
+ serverName: client.serverName,
158
+ tools: filteredTools,
159
+ error: null
160
+ };
161
+ } catch (error) {
162
+ return {
163
+ serverName: client.serverName,
164
+ tools: [],
165
+ error
166
+ };
167
+ }
168
+ }));
169
+ for (const { serverName, tools, error } of toolResults) {
170
+ if (error && !options.json) console.error(`Failed to list tools from ${serverName}:`, error);
171
+ toolsByServer[serverName] = tools;
157
172
  }
158
173
  if (options.json) console.log(JSON.stringify(toolsByServer, null, 2));
159
174
  else for (const [serverName, tools] of Object.entries(toolsByServer)) {
@@ -227,43 +242,60 @@ const describeToolsCommand = new Command("describe-tools").description("Describe
227
242
  const foundTools = [];
228
243
  const foundSkills = [];
229
244
  const notFoundTools = [...toolNames];
230
- for (const client of clients) {
231
- if (options.server && client.serverName !== options.server) continue;
245
+ const filteredClients = clients.filter((client) => !options.server || client.serverName === options.server);
246
+ const toolResults = await Promise.all(filteredClients.map(async (client) => {
232
247
  try {
233
- const tools = await client.listTools();
234
- for (const toolName of toolNames) {
235
- const tool = tools.find((t) => t.name === toolName);
236
- if (tool) {
237
- foundTools.push({
238
- server: client.serverName,
239
- name: tool.name,
240
- description: tool.description,
241
- inputSchema: tool.inputSchema
242
- });
243
- const idx = notFoundTools.indexOf(toolName);
244
- if (idx > -1) notFoundTools.splice(idx, 1);
245
- }
246
- }
248
+ return {
249
+ client,
250
+ tools: await client.listTools(),
251
+ error: null
252
+ };
247
253
  } catch (error) {
254
+ return {
255
+ client,
256
+ tools: [],
257
+ error
258
+ };
259
+ }
260
+ }));
261
+ for (const { client, tools, error } of toolResults) {
262
+ if (error) {
248
263
  if (!options.json) console.error(`Failed to list tools from ${client.serverName}:`, error);
264
+ continue;
249
265
  }
250
- }
251
- if (skillService && notFoundTools.length > 0) {
252
- const skillsToCheck = [...notFoundTools];
253
- for (const toolName of skillsToCheck) {
254
- const skillName = toolName.startsWith("skill__") ? toolName.slice(7) : toolName;
255
- const skill = await skillService.getSkill(skillName);
256
- if (skill) {
257
- foundSkills.push({
258
- name: skill.name,
259
- location: skill.basePath,
260
- instructions: skill.content
266
+ for (const toolName of toolNames) {
267
+ const tool = tools.find((t) => t.name === toolName);
268
+ if (tool) {
269
+ foundTools.push({
270
+ server: client.serverName,
271
+ name: tool.name,
272
+ description: tool.description,
273
+ inputSchema: tool.inputSchema
261
274
  });
262
275
  const idx = notFoundTools.indexOf(toolName);
263
276
  if (idx > -1) notFoundTools.splice(idx, 1);
264
277
  }
265
278
  }
266
279
  }
280
+ if (skillService && notFoundTools.length > 0) {
281
+ const skillsToCheck = [...notFoundTools];
282
+ const skillResults = await Promise.all(skillsToCheck.map(async (toolName) => {
283
+ const skillName = toolName.startsWith("skill__") ? toolName.slice(7) : toolName;
284
+ return {
285
+ toolName,
286
+ skill: await skillService.getSkill(skillName)
287
+ };
288
+ }));
289
+ for (const { toolName, skill } of skillResults) if (skill) {
290
+ foundSkills.push({
291
+ name: skill.name,
292
+ location: skill.basePath,
293
+ instructions: skill.content
294
+ });
295
+ const idx = notFoundTools.indexOf(toolName);
296
+ if (idx > -1) notFoundTools.splice(idx, 1);
297
+ }
298
+ }
267
299
  const nextSteps = [];
268
300
  if (foundTools.length > 0) nextSteps.push("For MCP tools: Use the use_tool function with toolName and toolArgs based on the inputSchema above.");
269
301
  if (foundSkills.length > 0) nextSteps.push(`For skill, just follow skill's description to continue.`);
@@ -396,11 +428,29 @@ const useToolCommand = new Command("use-tool").description("Execute an MCP tool
396
428
  process.exit(1);
397
429
  }
398
430
  }
431
+ const searchResults = await Promise.all(clients.map(async (client$1) => {
432
+ try {
433
+ const hasTool = (await client$1.listTools()).some((t) => t.name === toolName);
434
+ return {
435
+ serverName: client$1.serverName,
436
+ hasTool,
437
+ error: null
438
+ };
439
+ } catch (error) {
440
+ return {
441
+ serverName: client$1.serverName,
442
+ hasTool: false,
443
+ error
444
+ };
445
+ }
446
+ }));
399
447
  const matchingServers = [];
400
- for (const client$1 of clients) try {
401
- if ((await client$1.listTools()).some((t) => t.name === toolName)) matchingServers.push(client$1.serverName);
402
- } catch (error) {
403
- if (!options.json) console.error(`Failed to list tools from ${client$1.serverName}:`, error);
448
+ for (const { serverName, hasTool, error } of searchResults) {
449
+ if (error) {
450
+ if (!options.json) console.error(`Failed to list tools from ${serverName}:`, error);
451
+ continue;
452
+ }
453
+ if (hasTool) matchingServers.push(serverName);
404
454
  }
405
455
  if (matchingServers.length === 0) {
406
456
  const cwd = process.env.PROJECT_PATH || process.cwd();
@@ -546,9 +596,462 @@ const initCommand = new Command("init").description("Initialize MCP configuratio
546
596
  }
547
597
  });
548
598
 
599
+ //#endregion
600
+ //#region src/services/PrefetchService/constants.ts
601
+ /**
602
+ * PrefetchService Constants
603
+ *
604
+ * Constants for package manager commands and process configuration.
605
+ */
606
+ /** Transport type for stdio-based MCP servers */
607
+ const TRANSPORT_STDIO = "stdio";
608
+ /** npx command name */
609
+ const COMMAND_NPX = "npx";
610
+ /** npm command name */
611
+ const COMMAND_NPM = "npm";
612
+ /** pnpx command name (pnpm's npx equivalent) */
613
+ const COMMAND_PNPX = "pnpx";
614
+ /** pnpm command name */
615
+ const COMMAND_PNPM = "pnpm";
616
+ /** uvx command name */
617
+ const COMMAND_UVX = "uvx";
618
+ /** uv command name */
619
+ const COMMAND_UV = "uv";
620
+ /** Path suffix for npx command */
621
+ const COMMAND_NPX_SUFFIX = "/npx";
622
+ /** Path suffix for pnpx command */
623
+ const COMMAND_PNPX_SUFFIX = "/pnpx";
624
+ /** Path suffix for uvx command */
625
+ const COMMAND_UVX_SUFFIX = "/uvx";
626
+ /** Path suffix for uv command */
627
+ const COMMAND_UV_SUFFIX = "/uv";
628
+ /** Run subcommand for uv */
629
+ const ARG_RUN = "run";
630
+ /** Tool subcommand for uv */
631
+ const ARG_TOOL = "tool";
632
+ /** Install subcommand for uv tool and npm/pnpm */
633
+ const ARG_INSTALL = "install";
634
+ /** Add subcommand for pnpm */
635
+ const ARG_ADD = "add";
636
+ /** Global flag for npm/pnpm install */
637
+ const ARG_GLOBAL = "-g";
638
+ /** Flag prefix for command arguments */
639
+ const FLAG_PREFIX = "-";
640
+ /** npx --package flag (long form) */
641
+ const FLAG_PACKAGE_LONG = "--package";
642
+ /** npx -p flag (short form) */
643
+ const FLAG_PACKAGE_SHORT = "-p";
644
+ /** Equals delimiter used in flag=value patterns */
645
+ const EQUALS_DELIMITER = "=";
646
+ /**
647
+ * Regex pattern for valid package names (npm, pnpm, uvx, uv)
648
+ * Allows: @scope/package-name@version, package-name, package_name
649
+ * Prevents shell metacharacters that could enable command injection
650
+ * @example
651
+ * // Valid: '@scope/package@1.0.0', 'my-package', 'my_package', '@org/pkg'
652
+ * // Invalid: 'pkg; rm -rf /', 'pkg$(cmd)', 'pkg`whoami`', 'pkg|cat /etc/passwd'
653
+ */
654
+ const VALID_PACKAGE_NAME_PATTERN = /^(@[a-zA-Z0-9_-]+\/)?[a-zA-Z0-9._-]+(@[a-zA-Z0-9._-]+)?$/;
655
+ /** Windows platform identifier */
656
+ const PLATFORM_WIN32 = "win32";
657
+ /** Success exit code */
658
+ const EXIT_CODE_SUCCESS = 0;
659
+ /** Stdio option to ignore stream */
660
+ const STDIO_IGNORE = "ignore";
661
+ /** Stdio option to pipe stream */
662
+ const STDIO_PIPE = "pipe";
663
+
664
+ //#endregion
665
+ //#region src/services/PrefetchService/PrefetchService.ts
666
+ /**
667
+ * PrefetchService
668
+ *
669
+ * DESIGN PATTERNS:
670
+ * - Service pattern for business logic encapsulation
671
+ * - Single responsibility principle
672
+ *
673
+ * CODING STANDARDS:
674
+ * - Use async/await for asynchronous operations
675
+ * - Throw descriptive errors for error cases
676
+ * - Keep methods focused and well-named
677
+ * - Document complex logic with comments
678
+ *
679
+ * AVOID:
680
+ * - Mixing concerns (keep focused on single domain)
681
+ * - Direct tool implementation (services should be tool-agnostic)
682
+ */
683
+ /**
684
+ * Type guard to check if a config object is an McpStdioConfig
685
+ * @param config - Config object to check
686
+ * @returns True if config has required McpStdioConfig properties
687
+ */
688
+ function isMcpStdioConfig(config) {
689
+ return typeof config === "object" && config !== null && "command" in config;
690
+ }
691
+ /**
692
+ * PrefetchService handles pre-downloading packages used by MCP servers.
693
+ * Supports npx (Node.js), uvx (Python/uv), and uv run commands.
694
+ *
695
+ * @example
696
+ * ```typescript
697
+ * const service = new PrefetchService({
698
+ * mcpConfig: await configFetcher.fetchConfiguration(),
699
+ * parallel: true,
700
+ * });
701
+ * const packages = service.extractPackages();
702
+ * const summary = await service.prefetch();
703
+ * ```
704
+ */
705
+ var PrefetchService = class {
706
+ config;
707
+ /**
708
+ * Creates a new PrefetchService instance
709
+ * @param config - Service configuration options
710
+ */
711
+ constructor(config) {
712
+ this.config = config;
713
+ }
714
+ /**
715
+ * Extract all prefetchable packages from the MCP configuration
716
+ * @returns Array of package info objects
717
+ */
718
+ extractPackages() {
719
+ const packages = [];
720
+ const { mcpConfig, filter } = this.config;
721
+ for (const [serverName, serverConfig] of Object.entries(mcpConfig.mcpServers)) {
722
+ if (serverConfig.disabled) continue;
723
+ if (serverConfig.transport !== TRANSPORT_STDIO) continue;
724
+ if (!isMcpStdioConfig(serverConfig.config)) continue;
725
+ const packageInfo = this.extractPackageInfo(serverName, serverConfig.config);
726
+ if (packageInfo) {
727
+ if (filter && packageInfo.packageManager !== filter) continue;
728
+ packages.push(packageInfo);
729
+ }
730
+ }
731
+ return packages;
732
+ }
733
+ /**
734
+ * Prefetch all packages from the configuration
735
+ * @returns Summary of prefetch results
736
+ * @throws Error if prefetch operation fails unexpectedly
737
+ */
738
+ async prefetch() {
739
+ try {
740
+ const packages = this.extractPackages();
741
+ const results = [];
742
+ if (packages.length === 0) return {
743
+ totalPackages: 0,
744
+ successful: 0,
745
+ failed: 0,
746
+ results: []
747
+ };
748
+ if (this.config.parallel) {
749
+ const promises = packages.map(async (pkg) => this.prefetchPackage(pkg));
750
+ results.push(...await Promise.all(promises));
751
+ } else for (const pkg of packages) {
752
+ const result = await this.prefetchPackage(pkg);
753
+ results.push(result);
754
+ }
755
+ const successful = results.filter((r) => r.success).length;
756
+ const failed = results.filter((r) => !r.success).length;
757
+ return {
758
+ totalPackages: packages.length,
759
+ successful,
760
+ failed,
761
+ results
762
+ };
763
+ } catch (error) {
764
+ throw new Error(`Failed to prefetch packages: ${error instanceof Error ? error.message : String(error)}`);
765
+ }
766
+ }
767
+ /**
768
+ * Prefetch a single package
769
+ * @param pkg - Package info to prefetch
770
+ * @returns Result of the prefetch operation
771
+ */
772
+ async prefetchPackage(pkg) {
773
+ try {
774
+ const [command, ...args] = pkg.fullCommand;
775
+ const result = await this.runCommand(command, args);
776
+ return {
777
+ package: pkg,
778
+ success: result.success,
779
+ output: result.output
780
+ };
781
+ } catch (error) {
782
+ return {
783
+ package: pkg,
784
+ success: false,
785
+ output: error instanceof Error ? error.message : String(error)
786
+ };
787
+ }
788
+ }
789
+ /**
790
+ * Validate package name to prevent command injection
791
+ * @param packageName - Package name to validate
792
+ * @returns True if package name is safe, false otherwise
793
+ * @remarks Rejects package names containing shell metacharacters
794
+ * @example
795
+ * isValidPackageName('@scope/package') // true
796
+ * isValidPackageName('my-package@1.0.0') // true
797
+ * isValidPackageName('pkg; rm -rf /') // false (shell injection)
798
+ * isValidPackageName('pkg$(whoami)') // false (command substitution)
799
+ */
800
+ isValidPackageName(packageName) {
801
+ return VALID_PACKAGE_NAME_PATTERN.test(packageName);
802
+ }
803
+ /**
804
+ * Extract package info from a server's stdio config
805
+ * @param serverName - Name of the MCP server
806
+ * @param config - Stdio configuration for the server
807
+ * @returns Package info if extractable, null otherwise
808
+ */
809
+ extractPackageInfo(serverName, config) {
810
+ const command = config.command.toLowerCase();
811
+ const args = config.args || [];
812
+ if (command === COMMAND_NPX || command.endsWith(COMMAND_NPX_SUFFIX)) {
813
+ const packageName = this.extractNpxPackage(args);
814
+ if (packageName && this.isValidPackageName(packageName)) return {
815
+ serverName,
816
+ packageManager: COMMAND_NPX,
817
+ packageName,
818
+ fullCommand: [
819
+ COMMAND_NPM,
820
+ ARG_INSTALL,
821
+ ARG_GLOBAL,
822
+ packageName
823
+ ]
824
+ };
825
+ }
826
+ if (command === COMMAND_PNPX || command.endsWith(COMMAND_PNPX_SUFFIX)) {
827
+ const packageName = this.extractNpxPackage(args);
828
+ if (packageName && this.isValidPackageName(packageName)) return {
829
+ serverName,
830
+ packageManager: COMMAND_PNPX,
831
+ packageName,
832
+ fullCommand: [
833
+ COMMAND_PNPM,
834
+ ARG_ADD,
835
+ ARG_GLOBAL,
836
+ packageName
837
+ ]
838
+ };
839
+ }
840
+ if (command === COMMAND_UVX || command.endsWith(COMMAND_UVX_SUFFIX)) {
841
+ const packageName = this.extractUvxPackage(args);
842
+ if (packageName && this.isValidPackageName(packageName)) return {
843
+ serverName,
844
+ packageManager: COMMAND_UVX,
845
+ packageName,
846
+ fullCommand: [COMMAND_UVX, packageName]
847
+ };
848
+ }
849
+ if ((command === COMMAND_UV || command.endsWith(COMMAND_UV_SUFFIX)) && args.includes(ARG_RUN)) {
850
+ const packageName = this.extractUvRunPackage(args);
851
+ if (packageName && this.isValidPackageName(packageName)) return {
852
+ serverName,
853
+ packageManager: COMMAND_UV,
854
+ packageName,
855
+ fullCommand: [
856
+ COMMAND_UV,
857
+ ARG_TOOL,
858
+ ARG_INSTALL,
859
+ packageName
860
+ ]
861
+ };
862
+ }
863
+ return null;
864
+ }
865
+ /**
866
+ * Extract package name from npx command args
867
+ * @param args - Command arguments
868
+ * @returns Package name or null
869
+ * @remarks Handles --package=value, --package value, -p value patterns.
870
+ * Falls back to first non-flag argument if no --package/-p flag found.
871
+ * Returns null if flag has no value or is followed by another flag.
872
+ * When multiple --package flags exist, returns the first valid one.
873
+ * @example
874
+ * extractNpxPackage(['--package=@scope/pkg']) // returns '@scope/pkg'
875
+ * extractNpxPackage(['--package', 'pkg-name']) // returns 'pkg-name'
876
+ * extractNpxPackage(['-p', 'pkg']) // returns 'pkg'
877
+ * extractNpxPackage(['-y', 'pkg-name', '--flag']) // returns 'pkg-name' (fallback)
878
+ * extractNpxPackage(['--package=']) // returns null (empty value)
879
+ */
880
+ extractNpxPackage(args) {
881
+ for (let i = 0; i < args.length; i++) {
882
+ const arg = args[i];
883
+ if (arg.startsWith(FLAG_PACKAGE_LONG + EQUALS_DELIMITER)) return arg.slice(FLAG_PACKAGE_LONG.length + EQUALS_DELIMITER.length) || null;
884
+ if (arg === FLAG_PACKAGE_LONG && i + 1 < args.length) {
885
+ const nextArg = args[i + 1];
886
+ if (!nextArg.startsWith(FLAG_PREFIX)) return nextArg;
887
+ }
888
+ if (arg === FLAG_PACKAGE_SHORT && i + 1 < args.length) {
889
+ const nextArg = args[i + 1];
890
+ if (!nextArg.startsWith(FLAG_PREFIX)) return nextArg;
891
+ }
892
+ }
893
+ for (const arg of args) {
894
+ if (arg.startsWith(FLAG_PREFIX)) continue;
895
+ return arg;
896
+ }
897
+ return null;
898
+ }
899
+ /**
900
+ * Extract package name from uvx command args
901
+ * @param args - Command arguments
902
+ * @returns Package name or null
903
+ * @remarks Assumes the first non-flag argument is the package name.
904
+ * Handles both single (-) and double (--) dash flags.
905
+ * @example
906
+ * extractUvxPackage(['mcp-server-fetch']) // returns 'mcp-server-fetch'
907
+ * extractUvxPackage(['--quiet', 'pkg-name']) // returns 'pkg-name'
908
+ */
909
+ extractUvxPackage(args) {
910
+ for (const arg of args) {
911
+ if (arg.startsWith(FLAG_PREFIX)) continue;
912
+ return arg;
913
+ }
914
+ return null;
915
+ }
916
+ /**
917
+ * Extract package name from uv run command args
918
+ * @param args - Command arguments
919
+ * @returns Package name or null
920
+ * @remarks Looks for the first non-flag argument after the 'run' subcommand.
921
+ * Returns null if 'run' is not found in args.
922
+ * @example
923
+ * extractUvRunPackage(['run', 'mcp-server']) // returns 'mcp-server'
924
+ * extractUvRunPackage(['run', '--verbose', 'pkg']) // returns 'pkg'
925
+ * extractUvRunPackage(['install', 'pkg']) // returns null (no 'run')
926
+ */
927
+ extractUvRunPackage(args) {
928
+ const runIndex = args.indexOf(ARG_RUN);
929
+ if (runIndex === -1) return null;
930
+ for (let i = runIndex + 1; i < args.length; i++) {
931
+ const arg = args[i];
932
+ if (arg.startsWith(FLAG_PREFIX)) continue;
933
+ return arg;
934
+ }
935
+ return null;
936
+ }
937
+ /**
938
+ * Run a shell command and capture output
939
+ * @param command - Command to run
940
+ * @param args - Command arguments
941
+ * @returns Promise with success status and output
942
+ */
943
+ runCommand(command, args) {
944
+ return new Promise((resolve$1) => {
945
+ const proc = spawn(command, args, {
946
+ stdio: [
947
+ STDIO_IGNORE,
948
+ STDIO_PIPE,
949
+ STDIO_PIPE
950
+ ],
951
+ shell: process.platform === PLATFORM_WIN32
952
+ });
953
+ let stdout = "";
954
+ let stderr = "";
955
+ proc.stdout?.on("data", (data) => {
956
+ stdout += data.toString();
957
+ });
958
+ proc.stderr?.on("data", (data) => {
959
+ stderr += data.toString();
960
+ });
961
+ proc.on("close", (code) => {
962
+ resolve$1({
963
+ success: code === EXIT_CODE_SUCCESS,
964
+ output: stdout || stderr
965
+ });
966
+ });
967
+ proc.on("error", (error) => {
968
+ resolve$1({
969
+ success: false,
970
+ output: error.message
971
+ });
972
+ });
973
+ });
974
+ }
975
+ };
976
+
977
+ //#endregion
978
+ //#region src/commands/prefetch.ts
979
+ /**
980
+ * Prefetch Command
981
+ *
982
+ * DESIGN PATTERNS:
983
+ * - Command pattern with Commander for CLI argument parsing
984
+ * - Async/await pattern for asynchronous operations
985
+ * - Error handling pattern with try-catch and proper exit codes
986
+ *
987
+ * CODING STANDARDS:
988
+ * - Use async action handlers for asynchronous operations
989
+ * - Provide clear option descriptions and default values
990
+ * - Handle errors gracefully with process.exit()
991
+ * - Log progress and errors to console
992
+ * - Use Commander's .option() and .argument() for inputs
993
+ *
994
+ * AVOID:
995
+ * - Synchronous blocking operations in action handlers
996
+ * - Missing error handling (always use try-catch)
997
+ * - Hardcoded values (use options or environment variables)
998
+ * - Not exiting with appropriate exit codes on errors
999
+ */
1000
+ /**
1001
+ * Pre-download packages used by MCP servers (npx, pnpx, uvx, uv)
1002
+ */
1003
+ const prefetchCommand = new Command("prefetch").description("Pre-download packages used by MCP servers (npx, pnpx, uvx, uv)").option("-c, --config <path>", "Path to MCP server configuration file").option("-p, --parallel", "Run prefetch commands in parallel", false).option("-d, --dry-run", "Show what would be prefetched without executing", false).option("-f, --filter <type>", "Filter by package manager type: npx, pnpx, uvx, or uv").action(async (options) => {
1004
+ try {
1005
+ const configFilePath = options.config || findConfigFile();
1006
+ if (!configFilePath) {
1007
+ print.error("No MCP configuration file found.");
1008
+ print.info("Use --config <path> to specify a config file, or run \"one-mcp init\" to create one.");
1009
+ process.exit(1);
1010
+ }
1011
+ print.info(`Loading configuration from: ${configFilePath}`);
1012
+ const prefetchService = new PrefetchService({
1013
+ mcpConfig: await new ConfigFetcherService({
1014
+ configFilePath,
1015
+ useCache: false
1016
+ }).fetchConfiguration(true),
1017
+ filter: options.filter,
1018
+ parallel: options.parallel
1019
+ });
1020
+ const packages = prefetchService.extractPackages();
1021
+ if (packages.length === 0) {
1022
+ print.warning("No packages found to prefetch.");
1023
+ print.info("Prefetch supports: npx, pnpx, uvx, and uv run commands");
1024
+ return;
1025
+ }
1026
+ print.info(`Found ${packages.length} package(s) to prefetch:`);
1027
+ for (const pkg of packages) print.item(`${pkg.serverName}: ${pkg.packageManager} ${pkg.packageName}`);
1028
+ if (options.dryRun) {
1029
+ print.newline();
1030
+ print.header("Dry run mode - commands that would be executed:");
1031
+ for (const pkg of packages) print.indent(pkg.fullCommand.join(" "));
1032
+ return;
1033
+ }
1034
+ print.newline();
1035
+ print.info("Prefetching packages...");
1036
+ const summary = await prefetchService.prefetch();
1037
+ print.newline();
1038
+ if (summary.failed === 0) print.success(`Prefetch complete: ${summary.successful} succeeded, ${summary.failed} failed`);
1039
+ else print.warning(`Prefetch complete: ${summary.successful} succeeded, ${summary.failed} failed`);
1040
+ if (summary.failed > 0) {
1041
+ print.newline();
1042
+ print.error("Failed packages:");
1043
+ for (const result of summary.results.filter((r) => !r.success)) print.item(`${result.package.serverName} (${result.package.packageName}): ${result.output.trim()}`);
1044
+ process.exit(1);
1045
+ }
1046
+ } catch (error) {
1047
+ print.error("Error executing prefetch:", error instanceof Error ? error.message : String(error));
1048
+ process.exit(1);
1049
+ }
1050
+ });
1051
+
549
1052
  //#endregion
550
1053
  //#region package.json
551
- var version = "0.2.6";
1054
+ var version = "0.3.2";
552
1055
 
553
1056
  //#endregion
554
1057
  //#region src/cli.ts
@@ -574,16 +1077,25 @@ var version = "0.2.6";
574
1077
  * Main entry point
575
1078
  */
576
1079
  async function main() {
577
- const program = new Command();
578
- program.name("one-mcp").description("One MCP server package").version(version);
579
- program.addCommand(initCommand);
580
- program.addCommand(mcpServeCommand);
581
- program.addCommand(listToolsCommand);
582
- program.addCommand(describeToolsCommand);
583
- program.addCommand(useToolCommand);
584
- await program.parseAsync(process.argv);
1080
+ try {
1081
+ const program = new Command();
1082
+ program.name("one-mcp").description("One MCP server package").version(version);
1083
+ program.addCommand(initCommand);
1084
+ program.addCommand(mcpServeCommand);
1085
+ program.addCommand(listToolsCommand);
1086
+ program.addCommand(describeToolsCommand);
1087
+ program.addCommand(useToolCommand);
1088
+ program.addCommand(prefetchCommand);
1089
+ await program.parseAsync(process.argv);
1090
+ } catch (error) {
1091
+ console.error(`CLI execution failed: ${error instanceof Error ? error.message : error}`);
1092
+ process.exit(1);
1093
+ }
585
1094
  }
586
- main();
1095
+ main().catch((error) => {
1096
+ console.error(`Fatal error: ${error instanceof Error ? error.message : error}`);
1097
+ process.exit(1);
1098
+ });
587
1099
 
588
1100
  //#endregion
589
1101
  export { };