@agiflowai/one-mcp 0.2.7 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -128,6 +128,12 @@ mcpServers:
128
128
  command: node
129
129
  args: ["server.js"]
130
130
  disabled: true
131
+
132
+ # Custom timeout for slow servers
133
+ slow-server:
134
+ command: npx
135
+ args: ["-y", "@heavy/mcp-package"]
136
+ timeout: 60000 # 60 seconds (default: 30000)
131
137
  ```
132
138
 
133
139
  ### Environment Variables
@@ -414,6 +420,9 @@ npx @agiflowai/one-mcp mcp-serve --config ./mcp-config.yaml --type http --port 3
414
420
  # Initialize config file
415
421
  npx @agiflowai/one-mcp init --output mcp-config.yaml
416
422
 
423
+ # Pre-download packages for faster startup
424
+ npx @agiflowai/one-mcp prefetch --config ./mcp-config.yaml
425
+
417
426
  # List all tools from configured servers
418
427
  npx @agiflowai/one-mcp list-tools --config ./mcp-config.yaml
419
428
 
@@ -424,6 +433,31 @@ npx @agiflowai/one-mcp describe-tools --config ./mcp-config.yaml --tools read_fi
424
433
  npx @agiflowai/one-mcp use-tool --config ./mcp-config.yaml --tool-name read_file --args '{"path": "/tmp/test.txt"}'
425
434
  ```
426
435
 
436
+ ### Prefetch Command
437
+
438
+ Pre-download packages used by MCP servers (npx, pnpx, uvx, uv) to speed up initial connections:
439
+
440
+ ```bash
441
+ # Prefetch all packages
442
+ npx @agiflowai/one-mcp prefetch --config ./mcp-config.yaml
443
+
444
+ # Dry run - see what would be prefetched
445
+ npx @agiflowai/one-mcp prefetch --config ./mcp-config.yaml --dry-run
446
+
447
+ # Run prefetch in parallel (faster)
448
+ npx @agiflowai/one-mcp prefetch --config ./mcp-config.yaml --parallel
449
+
450
+ # Filter by package manager
451
+ npx @agiflowai/one-mcp prefetch --config ./mcp-config.yaml --filter npx
452
+ ```
453
+
454
+ | Option | Description |
455
+ |--------|-------------|
456
+ | `-c, --config` | Path to config file |
457
+ | `-p, --parallel` | Run prefetch commands in parallel |
458
+ | `-d, --dry-run` | Show what would be prefetched without executing |
459
+ | `-f, --filter` | Filter by package manager: `npx`, `pnpx`, `uvx`, or `uv` |
460
+
427
461
  ### Server Options
428
462
 
429
463
  | Option | Description | Default |
package/dist/cli.cjs CHANGED
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
- const require_http = require('./http-B4NAfsQl.cjs');
2
+ const require_http = require('./http-BYBRKvD4.cjs');
3
3
  let node_fs_promises = require("node:fs/promises");
4
4
  let node_path = require("node:path");
5
5
  let liquidjs = require("liquidjs");
6
6
  let commander = require("commander");
7
7
  let __agiflowai_aicode_utils = require("@agiflowai/aicode-utils");
8
+ let node_child_process = require("node:child_process");
8
9
 
9
10
  //#region src/types/index.ts
10
11
  /**
@@ -546,9 +547,462 @@ const initCommand = new commander.Command("init").description("Initialize MCP co
546
547
  }
547
548
  });
548
549
 
550
+ //#endregion
551
+ //#region src/services/PrefetchService/constants.ts
552
+ /**
553
+ * PrefetchService Constants
554
+ *
555
+ * Constants for package manager commands and process configuration.
556
+ */
557
+ /** Transport type for stdio-based MCP servers */
558
+ const TRANSPORT_STDIO = "stdio";
559
+ /** npx command name */
560
+ const COMMAND_NPX = "npx";
561
+ /** npm command name */
562
+ const COMMAND_NPM = "npm";
563
+ /** pnpx command name (pnpm's npx equivalent) */
564
+ const COMMAND_PNPX = "pnpx";
565
+ /** pnpm command name */
566
+ const COMMAND_PNPM = "pnpm";
567
+ /** uvx command name */
568
+ const COMMAND_UVX = "uvx";
569
+ /** uv command name */
570
+ const COMMAND_UV = "uv";
571
+ /** Path suffix for npx command */
572
+ const COMMAND_NPX_SUFFIX = "/npx";
573
+ /** Path suffix for pnpx command */
574
+ const COMMAND_PNPX_SUFFIX = "/pnpx";
575
+ /** Path suffix for uvx command */
576
+ const COMMAND_UVX_SUFFIX = "/uvx";
577
+ /** Path suffix for uv command */
578
+ const COMMAND_UV_SUFFIX = "/uv";
579
+ /** Run subcommand for uv */
580
+ const ARG_RUN = "run";
581
+ /** Tool subcommand for uv */
582
+ const ARG_TOOL = "tool";
583
+ /** Install subcommand for uv tool and npm/pnpm */
584
+ const ARG_INSTALL = "install";
585
+ /** Add subcommand for pnpm */
586
+ const ARG_ADD = "add";
587
+ /** Global flag for npm/pnpm install */
588
+ const ARG_GLOBAL = "-g";
589
+ /** Flag prefix for command arguments */
590
+ const FLAG_PREFIX = "-";
591
+ /** npx --package flag (long form) */
592
+ const FLAG_PACKAGE_LONG = "--package";
593
+ /** npx -p flag (short form) */
594
+ const FLAG_PACKAGE_SHORT = "-p";
595
+ /** Equals delimiter used in flag=value patterns */
596
+ const EQUALS_DELIMITER = "=";
597
+ /**
598
+ * Regex pattern for valid package names (npm, pnpm, uvx, uv)
599
+ * Allows: @scope/package-name@version, package-name, package_name
600
+ * Prevents shell metacharacters that could enable command injection
601
+ * @example
602
+ * // Valid: '@scope/package@1.0.0', 'my-package', 'my_package', '@org/pkg'
603
+ * // Invalid: 'pkg; rm -rf /', 'pkg$(cmd)', 'pkg`whoami`', 'pkg|cat /etc/passwd'
604
+ */
605
+ const VALID_PACKAGE_NAME_PATTERN = /^(@[a-zA-Z0-9_-]+\/)?[a-zA-Z0-9._-]+(@[a-zA-Z0-9._-]+)?$/;
606
+ /** Windows platform identifier */
607
+ const PLATFORM_WIN32 = "win32";
608
+ /** Success exit code */
609
+ const EXIT_CODE_SUCCESS = 0;
610
+ /** Stdio option to ignore stream */
611
+ const STDIO_IGNORE = "ignore";
612
+ /** Stdio option to pipe stream */
613
+ const STDIO_PIPE = "pipe";
614
+
615
+ //#endregion
616
+ //#region src/services/PrefetchService/PrefetchService.ts
617
+ /**
618
+ * PrefetchService
619
+ *
620
+ * DESIGN PATTERNS:
621
+ * - Service pattern for business logic encapsulation
622
+ * - Single responsibility principle
623
+ *
624
+ * CODING STANDARDS:
625
+ * - Use async/await for asynchronous operations
626
+ * - Throw descriptive errors for error cases
627
+ * - Keep methods focused and well-named
628
+ * - Document complex logic with comments
629
+ *
630
+ * AVOID:
631
+ * - Mixing concerns (keep focused on single domain)
632
+ * - Direct tool implementation (services should be tool-agnostic)
633
+ */
634
+ /**
635
+ * Type guard to check if a config object is an McpStdioConfig
636
+ * @param config - Config object to check
637
+ * @returns True if config has required McpStdioConfig properties
638
+ */
639
+ function isMcpStdioConfig(config) {
640
+ return typeof config === "object" && config !== null && "command" in config;
641
+ }
642
+ /**
643
+ * PrefetchService handles pre-downloading packages used by MCP servers.
644
+ * Supports npx (Node.js), uvx (Python/uv), and uv run commands.
645
+ *
646
+ * @example
647
+ * ```typescript
648
+ * const service = new PrefetchService({
649
+ * mcpConfig: await configFetcher.fetchConfiguration(),
650
+ * parallel: true,
651
+ * });
652
+ * const packages = service.extractPackages();
653
+ * const summary = await service.prefetch();
654
+ * ```
655
+ */
656
+ var PrefetchService = class {
657
+ config;
658
+ /**
659
+ * Creates a new PrefetchService instance
660
+ * @param config - Service configuration options
661
+ */
662
+ constructor(config) {
663
+ this.config = config;
664
+ }
665
+ /**
666
+ * Extract all prefetchable packages from the MCP configuration
667
+ * @returns Array of package info objects
668
+ */
669
+ extractPackages() {
670
+ const packages = [];
671
+ const { mcpConfig, filter } = this.config;
672
+ for (const [serverName, serverConfig] of Object.entries(mcpConfig.mcpServers)) {
673
+ if (serverConfig.disabled) continue;
674
+ if (serverConfig.transport !== TRANSPORT_STDIO) continue;
675
+ if (!isMcpStdioConfig(serverConfig.config)) continue;
676
+ const packageInfo = this.extractPackageInfo(serverName, serverConfig.config);
677
+ if (packageInfo) {
678
+ if (filter && packageInfo.packageManager !== filter) continue;
679
+ packages.push(packageInfo);
680
+ }
681
+ }
682
+ return packages;
683
+ }
684
+ /**
685
+ * Prefetch all packages from the configuration
686
+ * @returns Summary of prefetch results
687
+ * @throws Error if prefetch operation fails unexpectedly
688
+ */
689
+ async prefetch() {
690
+ try {
691
+ const packages = this.extractPackages();
692
+ const results = [];
693
+ if (packages.length === 0) return {
694
+ totalPackages: 0,
695
+ successful: 0,
696
+ failed: 0,
697
+ results: []
698
+ };
699
+ if (this.config.parallel) {
700
+ const promises = packages.map(async (pkg) => this.prefetchPackage(pkg));
701
+ results.push(...await Promise.all(promises));
702
+ } else for (const pkg of packages) {
703
+ const result = await this.prefetchPackage(pkg);
704
+ results.push(result);
705
+ }
706
+ const successful = results.filter((r) => r.success).length;
707
+ const failed = results.filter((r) => !r.success).length;
708
+ return {
709
+ totalPackages: packages.length,
710
+ successful,
711
+ failed,
712
+ results
713
+ };
714
+ } catch (error) {
715
+ throw new Error(`Failed to prefetch packages: ${error instanceof Error ? error.message : String(error)}`);
716
+ }
717
+ }
718
+ /**
719
+ * Prefetch a single package
720
+ * @param pkg - Package info to prefetch
721
+ * @returns Result of the prefetch operation
722
+ */
723
+ async prefetchPackage(pkg) {
724
+ try {
725
+ const [command, ...args] = pkg.fullCommand;
726
+ const result = await this.runCommand(command, args);
727
+ return {
728
+ package: pkg,
729
+ success: result.success,
730
+ output: result.output
731
+ };
732
+ } catch (error) {
733
+ return {
734
+ package: pkg,
735
+ success: false,
736
+ output: error instanceof Error ? error.message : String(error)
737
+ };
738
+ }
739
+ }
740
+ /**
741
+ * Validate package name to prevent command injection
742
+ * @param packageName - Package name to validate
743
+ * @returns True if package name is safe, false otherwise
744
+ * @remarks Rejects package names containing shell metacharacters
745
+ * @example
746
+ * isValidPackageName('@scope/package') // true
747
+ * isValidPackageName('my-package@1.0.0') // true
748
+ * isValidPackageName('pkg; rm -rf /') // false (shell injection)
749
+ * isValidPackageName('pkg$(whoami)') // false (command substitution)
750
+ */
751
+ isValidPackageName(packageName) {
752
+ return VALID_PACKAGE_NAME_PATTERN.test(packageName);
753
+ }
754
+ /**
755
+ * Extract package info from a server's stdio config
756
+ * @param serverName - Name of the MCP server
757
+ * @param config - Stdio configuration for the server
758
+ * @returns Package info if extractable, null otherwise
759
+ */
760
+ extractPackageInfo(serverName, config) {
761
+ const command = config.command.toLowerCase();
762
+ const args = config.args || [];
763
+ if (command === COMMAND_NPX || command.endsWith(COMMAND_NPX_SUFFIX)) {
764
+ const packageName = this.extractNpxPackage(args);
765
+ if (packageName && this.isValidPackageName(packageName)) return {
766
+ serverName,
767
+ packageManager: COMMAND_NPX,
768
+ packageName,
769
+ fullCommand: [
770
+ COMMAND_NPM,
771
+ ARG_INSTALL,
772
+ ARG_GLOBAL,
773
+ packageName
774
+ ]
775
+ };
776
+ }
777
+ if (command === COMMAND_PNPX || command.endsWith(COMMAND_PNPX_SUFFIX)) {
778
+ const packageName = this.extractNpxPackage(args);
779
+ if (packageName && this.isValidPackageName(packageName)) return {
780
+ serverName,
781
+ packageManager: COMMAND_PNPX,
782
+ packageName,
783
+ fullCommand: [
784
+ COMMAND_PNPM,
785
+ ARG_ADD,
786
+ ARG_GLOBAL,
787
+ packageName
788
+ ]
789
+ };
790
+ }
791
+ if (command === COMMAND_UVX || command.endsWith(COMMAND_UVX_SUFFIX)) {
792
+ const packageName = this.extractUvxPackage(args);
793
+ if (packageName && this.isValidPackageName(packageName)) return {
794
+ serverName,
795
+ packageManager: COMMAND_UVX,
796
+ packageName,
797
+ fullCommand: [COMMAND_UVX, packageName]
798
+ };
799
+ }
800
+ if ((command === COMMAND_UV || command.endsWith(COMMAND_UV_SUFFIX)) && args.includes(ARG_RUN)) {
801
+ const packageName = this.extractUvRunPackage(args);
802
+ if (packageName && this.isValidPackageName(packageName)) return {
803
+ serverName,
804
+ packageManager: COMMAND_UV,
805
+ packageName,
806
+ fullCommand: [
807
+ COMMAND_UV,
808
+ ARG_TOOL,
809
+ ARG_INSTALL,
810
+ packageName
811
+ ]
812
+ };
813
+ }
814
+ return null;
815
+ }
816
+ /**
817
+ * Extract package name from npx command args
818
+ * @param args - Command arguments
819
+ * @returns Package name or null
820
+ * @remarks Handles --package=value, --package value, -p value patterns.
821
+ * Falls back to first non-flag argument if no --package/-p flag found.
822
+ * Returns null if flag has no value or is followed by another flag.
823
+ * When multiple --package flags exist, returns the first valid one.
824
+ * @example
825
+ * extractNpxPackage(['--package=@scope/pkg']) // returns '@scope/pkg'
826
+ * extractNpxPackage(['--package', 'pkg-name']) // returns 'pkg-name'
827
+ * extractNpxPackage(['-p', 'pkg']) // returns 'pkg'
828
+ * extractNpxPackage(['-y', 'pkg-name', '--flag']) // returns 'pkg-name' (fallback)
829
+ * extractNpxPackage(['--package=']) // returns null (empty value)
830
+ */
831
+ extractNpxPackage(args) {
832
+ for (let i = 0; i < args.length; i++) {
833
+ const arg = args[i];
834
+ if (arg.startsWith(FLAG_PACKAGE_LONG + EQUALS_DELIMITER)) return arg.slice(FLAG_PACKAGE_LONG.length + EQUALS_DELIMITER.length) || null;
835
+ if (arg === FLAG_PACKAGE_LONG && i + 1 < args.length) {
836
+ const nextArg = args[i + 1];
837
+ if (!nextArg.startsWith(FLAG_PREFIX)) return nextArg;
838
+ }
839
+ if (arg === FLAG_PACKAGE_SHORT && i + 1 < args.length) {
840
+ const nextArg = args[i + 1];
841
+ if (!nextArg.startsWith(FLAG_PREFIX)) return nextArg;
842
+ }
843
+ }
844
+ for (const arg of args) {
845
+ if (arg.startsWith(FLAG_PREFIX)) continue;
846
+ return arg;
847
+ }
848
+ return null;
849
+ }
850
+ /**
851
+ * Extract package name from uvx command args
852
+ * @param args - Command arguments
853
+ * @returns Package name or null
854
+ * @remarks Assumes the first non-flag argument is the package name.
855
+ * Handles both single (-) and double (--) dash flags.
856
+ * @example
857
+ * extractUvxPackage(['mcp-server-fetch']) // returns 'mcp-server-fetch'
858
+ * extractUvxPackage(['--quiet', 'pkg-name']) // returns 'pkg-name'
859
+ */
860
+ extractUvxPackage(args) {
861
+ for (const arg of args) {
862
+ if (arg.startsWith(FLAG_PREFIX)) continue;
863
+ return arg;
864
+ }
865
+ return null;
866
+ }
867
+ /**
868
+ * Extract package name from uv run command args
869
+ * @param args - Command arguments
870
+ * @returns Package name or null
871
+ * @remarks Looks for the first non-flag argument after the 'run' subcommand.
872
+ * Returns null if 'run' is not found in args.
873
+ * @example
874
+ * extractUvRunPackage(['run', 'mcp-server']) // returns 'mcp-server'
875
+ * extractUvRunPackage(['run', '--verbose', 'pkg']) // returns 'pkg'
876
+ * extractUvRunPackage(['install', 'pkg']) // returns null (no 'run')
877
+ */
878
+ extractUvRunPackage(args) {
879
+ const runIndex = args.indexOf(ARG_RUN);
880
+ if (runIndex === -1) return null;
881
+ for (let i = runIndex + 1; i < args.length; i++) {
882
+ const arg = args[i];
883
+ if (arg.startsWith(FLAG_PREFIX)) continue;
884
+ return arg;
885
+ }
886
+ return null;
887
+ }
888
+ /**
889
+ * Run a shell command and capture output
890
+ * @param command - Command to run
891
+ * @param args - Command arguments
892
+ * @returns Promise with success status and output
893
+ */
894
+ runCommand(command, args) {
895
+ return new Promise((resolve$1) => {
896
+ const proc = (0, node_child_process.spawn)(command, args, {
897
+ stdio: [
898
+ STDIO_IGNORE,
899
+ STDIO_PIPE,
900
+ STDIO_PIPE
901
+ ],
902
+ shell: process.platform === PLATFORM_WIN32
903
+ });
904
+ let stdout = "";
905
+ let stderr = "";
906
+ proc.stdout?.on("data", (data) => {
907
+ stdout += data.toString();
908
+ });
909
+ proc.stderr?.on("data", (data) => {
910
+ stderr += data.toString();
911
+ });
912
+ proc.on("close", (code) => {
913
+ resolve$1({
914
+ success: code === EXIT_CODE_SUCCESS,
915
+ output: stdout || stderr
916
+ });
917
+ });
918
+ proc.on("error", (error) => {
919
+ resolve$1({
920
+ success: false,
921
+ output: error.message
922
+ });
923
+ });
924
+ });
925
+ }
926
+ };
927
+
928
+ //#endregion
929
+ //#region src/commands/prefetch.ts
930
+ /**
931
+ * Prefetch Command
932
+ *
933
+ * DESIGN PATTERNS:
934
+ * - Command pattern with Commander for CLI argument parsing
935
+ * - Async/await pattern for asynchronous operations
936
+ * - Error handling pattern with try-catch and proper exit codes
937
+ *
938
+ * CODING STANDARDS:
939
+ * - Use async action handlers for asynchronous operations
940
+ * - Provide clear option descriptions and default values
941
+ * - Handle errors gracefully with process.exit()
942
+ * - Log progress and errors to console
943
+ * - Use Commander's .option() and .argument() for inputs
944
+ *
945
+ * AVOID:
946
+ * - Synchronous blocking operations in action handlers
947
+ * - Missing error handling (always use try-catch)
948
+ * - Hardcoded values (use options or environment variables)
949
+ * - Not exiting with appropriate exit codes on errors
950
+ */
951
+ /**
952
+ * Pre-download packages used by MCP servers (npx, pnpx, uvx, uv)
953
+ */
954
+ const prefetchCommand = new commander.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) => {
955
+ try {
956
+ const configFilePath = options.config || require_http.findConfigFile();
957
+ if (!configFilePath) {
958
+ __agiflowai_aicode_utils.print.error("No MCP configuration file found.");
959
+ __agiflowai_aicode_utils.print.info("Use --config <path> to specify a config file, or run \"one-mcp init\" to create one.");
960
+ process.exit(1);
961
+ }
962
+ __agiflowai_aicode_utils.print.info(`Loading configuration from: ${configFilePath}`);
963
+ const prefetchService = new PrefetchService({
964
+ mcpConfig: await new require_http.ConfigFetcherService({
965
+ configFilePath,
966
+ useCache: false
967
+ }).fetchConfiguration(true),
968
+ filter: options.filter,
969
+ parallel: options.parallel
970
+ });
971
+ const packages = prefetchService.extractPackages();
972
+ if (packages.length === 0) {
973
+ __agiflowai_aicode_utils.print.warning("No packages found to prefetch.");
974
+ __agiflowai_aicode_utils.print.info("Prefetch supports: npx, pnpx, uvx, and uv run commands");
975
+ return;
976
+ }
977
+ __agiflowai_aicode_utils.print.info(`Found ${packages.length} package(s) to prefetch:`);
978
+ for (const pkg of packages) __agiflowai_aicode_utils.print.item(`${pkg.serverName}: ${pkg.packageManager} ${pkg.packageName}`);
979
+ if (options.dryRun) {
980
+ __agiflowai_aicode_utils.print.newline();
981
+ __agiflowai_aicode_utils.print.header("Dry run mode - commands that would be executed:");
982
+ for (const pkg of packages) __agiflowai_aicode_utils.print.indent(pkg.fullCommand.join(" "));
983
+ return;
984
+ }
985
+ __agiflowai_aicode_utils.print.newline();
986
+ __agiflowai_aicode_utils.print.info("Prefetching packages...");
987
+ const summary = await prefetchService.prefetch();
988
+ __agiflowai_aicode_utils.print.newline();
989
+ if (summary.failed === 0) __agiflowai_aicode_utils.print.success(`Prefetch complete: ${summary.successful} succeeded, ${summary.failed} failed`);
990
+ else __agiflowai_aicode_utils.print.warning(`Prefetch complete: ${summary.successful} succeeded, ${summary.failed} failed`);
991
+ if (summary.failed > 0) {
992
+ __agiflowai_aicode_utils.print.newline();
993
+ __agiflowai_aicode_utils.print.error("Failed packages:");
994
+ for (const result of summary.results.filter((r) => !r.success)) __agiflowai_aicode_utils.print.item(`${result.package.serverName} (${result.package.packageName}): ${result.output.trim()}`);
995
+ process.exit(1);
996
+ }
997
+ } catch (error) {
998
+ __agiflowai_aicode_utils.print.error("Error executing prefetch:", error instanceof Error ? error.message : String(error));
999
+ process.exit(1);
1000
+ }
1001
+ });
1002
+
549
1003
  //#endregion
550
1004
  //#region package.json
551
- var version = "0.2.6";
1005
+ var version = "0.2.2";
552
1006
 
553
1007
  //#endregion
554
1008
  //#region src/cli.ts
@@ -574,15 +1028,24 @@ var version = "0.2.6";
574
1028
  * Main entry point
575
1029
  */
576
1030
  async function main() {
577
- const program = new commander.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);
1031
+ try {
1032
+ const program = new commander.Command();
1033
+ program.name("one-mcp").description("One MCP server package").version(version);
1034
+ program.addCommand(initCommand);
1035
+ program.addCommand(mcpServeCommand);
1036
+ program.addCommand(listToolsCommand);
1037
+ program.addCommand(describeToolsCommand);
1038
+ program.addCommand(useToolCommand);
1039
+ program.addCommand(prefetchCommand);
1040
+ await program.parseAsync(process.argv);
1041
+ } catch (error) {
1042
+ console.error(`CLI execution failed: ${error instanceof Error ? error.message : error}`);
1043
+ process.exit(1);
1044
+ }
585
1045
  }
586
- main();
1046
+ main().catch((error) => {
1047
+ console.error(`Fatal error: ${error instanceof Error ? error.message : error}`);
1048
+ process.exit(1);
1049
+ });
587
1050
 
588
1051
  //#endregion
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-Bi2N9PUM.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
  /**
@@ -546,9 +547,462 @@ const initCommand = new Command("init").description("Initialize MCP configuratio
546
547
  }
547
548
  });
548
549
 
550
+ //#endregion
551
+ //#region src/services/PrefetchService/constants.ts
552
+ /**
553
+ * PrefetchService Constants
554
+ *
555
+ * Constants for package manager commands and process configuration.
556
+ */
557
+ /** Transport type for stdio-based MCP servers */
558
+ const TRANSPORT_STDIO = "stdio";
559
+ /** npx command name */
560
+ const COMMAND_NPX = "npx";
561
+ /** npm command name */
562
+ const COMMAND_NPM = "npm";
563
+ /** pnpx command name (pnpm's npx equivalent) */
564
+ const COMMAND_PNPX = "pnpx";
565
+ /** pnpm command name */
566
+ const COMMAND_PNPM = "pnpm";
567
+ /** uvx command name */
568
+ const COMMAND_UVX = "uvx";
569
+ /** uv command name */
570
+ const COMMAND_UV = "uv";
571
+ /** Path suffix for npx command */
572
+ const COMMAND_NPX_SUFFIX = "/npx";
573
+ /** Path suffix for pnpx command */
574
+ const COMMAND_PNPX_SUFFIX = "/pnpx";
575
+ /** Path suffix for uvx command */
576
+ const COMMAND_UVX_SUFFIX = "/uvx";
577
+ /** Path suffix for uv command */
578
+ const COMMAND_UV_SUFFIX = "/uv";
579
+ /** Run subcommand for uv */
580
+ const ARG_RUN = "run";
581
+ /** Tool subcommand for uv */
582
+ const ARG_TOOL = "tool";
583
+ /** Install subcommand for uv tool and npm/pnpm */
584
+ const ARG_INSTALL = "install";
585
+ /** Add subcommand for pnpm */
586
+ const ARG_ADD = "add";
587
+ /** Global flag for npm/pnpm install */
588
+ const ARG_GLOBAL = "-g";
589
+ /** Flag prefix for command arguments */
590
+ const FLAG_PREFIX = "-";
591
+ /** npx --package flag (long form) */
592
+ const FLAG_PACKAGE_LONG = "--package";
593
+ /** npx -p flag (short form) */
594
+ const FLAG_PACKAGE_SHORT = "-p";
595
+ /** Equals delimiter used in flag=value patterns */
596
+ const EQUALS_DELIMITER = "=";
597
+ /**
598
+ * Regex pattern for valid package names (npm, pnpm, uvx, uv)
599
+ * Allows: @scope/package-name@version, package-name, package_name
600
+ * Prevents shell metacharacters that could enable command injection
601
+ * @example
602
+ * // Valid: '@scope/package@1.0.0', 'my-package', 'my_package', '@org/pkg'
603
+ * // Invalid: 'pkg; rm -rf /', 'pkg$(cmd)', 'pkg`whoami`', 'pkg|cat /etc/passwd'
604
+ */
605
+ const VALID_PACKAGE_NAME_PATTERN = /^(@[a-zA-Z0-9_-]+\/)?[a-zA-Z0-9._-]+(@[a-zA-Z0-9._-]+)?$/;
606
+ /** Windows platform identifier */
607
+ const PLATFORM_WIN32 = "win32";
608
+ /** Success exit code */
609
+ const EXIT_CODE_SUCCESS = 0;
610
+ /** Stdio option to ignore stream */
611
+ const STDIO_IGNORE = "ignore";
612
+ /** Stdio option to pipe stream */
613
+ const STDIO_PIPE = "pipe";
614
+
615
+ //#endregion
616
+ //#region src/services/PrefetchService/PrefetchService.ts
617
+ /**
618
+ * PrefetchService
619
+ *
620
+ * DESIGN PATTERNS:
621
+ * - Service pattern for business logic encapsulation
622
+ * - Single responsibility principle
623
+ *
624
+ * CODING STANDARDS:
625
+ * - Use async/await for asynchronous operations
626
+ * - Throw descriptive errors for error cases
627
+ * - Keep methods focused and well-named
628
+ * - Document complex logic with comments
629
+ *
630
+ * AVOID:
631
+ * - Mixing concerns (keep focused on single domain)
632
+ * - Direct tool implementation (services should be tool-agnostic)
633
+ */
634
+ /**
635
+ * Type guard to check if a config object is an McpStdioConfig
636
+ * @param config - Config object to check
637
+ * @returns True if config has required McpStdioConfig properties
638
+ */
639
+ function isMcpStdioConfig(config) {
640
+ return typeof config === "object" && config !== null && "command" in config;
641
+ }
642
+ /**
643
+ * PrefetchService handles pre-downloading packages used by MCP servers.
644
+ * Supports npx (Node.js), uvx (Python/uv), and uv run commands.
645
+ *
646
+ * @example
647
+ * ```typescript
648
+ * const service = new PrefetchService({
649
+ * mcpConfig: await configFetcher.fetchConfiguration(),
650
+ * parallel: true,
651
+ * });
652
+ * const packages = service.extractPackages();
653
+ * const summary = await service.prefetch();
654
+ * ```
655
+ */
656
+ var PrefetchService = class {
657
+ config;
658
+ /**
659
+ * Creates a new PrefetchService instance
660
+ * @param config - Service configuration options
661
+ */
662
+ constructor(config) {
663
+ this.config = config;
664
+ }
665
+ /**
666
+ * Extract all prefetchable packages from the MCP configuration
667
+ * @returns Array of package info objects
668
+ */
669
+ extractPackages() {
670
+ const packages = [];
671
+ const { mcpConfig, filter } = this.config;
672
+ for (const [serverName, serverConfig] of Object.entries(mcpConfig.mcpServers)) {
673
+ if (serverConfig.disabled) continue;
674
+ if (serverConfig.transport !== TRANSPORT_STDIO) continue;
675
+ if (!isMcpStdioConfig(serverConfig.config)) continue;
676
+ const packageInfo = this.extractPackageInfo(serverName, serverConfig.config);
677
+ if (packageInfo) {
678
+ if (filter && packageInfo.packageManager !== filter) continue;
679
+ packages.push(packageInfo);
680
+ }
681
+ }
682
+ return packages;
683
+ }
684
+ /**
685
+ * Prefetch all packages from the configuration
686
+ * @returns Summary of prefetch results
687
+ * @throws Error if prefetch operation fails unexpectedly
688
+ */
689
+ async prefetch() {
690
+ try {
691
+ const packages = this.extractPackages();
692
+ const results = [];
693
+ if (packages.length === 0) return {
694
+ totalPackages: 0,
695
+ successful: 0,
696
+ failed: 0,
697
+ results: []
698
+ };
699
+ if (this.config.parallel) {
700
+ const promises = packages.map(async (pkg) => this.prefetchPackage(pkg));
701
+ results.push(...await Promise.all(promises));
702
+ } else for (const pkg of packages) {
703
+ const result = await this.prefetchPackage(pkg);
704
+ results.push(result);
705
+ }
706
+ const successful = results.filter((r) => r.success).length;
707
+ const failed = results.filter((r) => !r.success).length;
708
+ return {
709
+ totalPackages: packages.length,
710
+ successful,
711
+ failed,
712
+ results
713
+ };
714
+ } catch (error) {
715
+ throw new Error(`Failed to prefetch packages: ${error instanceof Error ? error.message : String(error)}`);
716
+ }
717
+ }
718
+ /**
719
+ * Prefetch a single package
720
+ * @param pkg - Package info to prefetch
721
+ * @returns Result of the prefetch operation
722
+ */
723
+ async prefetchPackage(pkg) {
724
+ try {
725
+ const [command, ...args] = pkg.fullCommand;
726
+ const result = await this.runCommand(command, args);
727
+ return {
728
+ package: pkg,
729
+ success: result.success,
730
+ output: result.output
731
+ };
732
+ } catch (error) {
733
+ return {
734
+ package: pkg,
735
+ success: false,
736
+ output: error instanceof Error ? error.message : String(error)
737
+ };
738
+ }
739
+ }
740
+ /**
741
+ * Validate package name to prevent command injection
742
+ * @param packageName - Package name to validate
743
+ * @returns True if package name is safe, false otherwise
744
+ * @remarks Rejects package names containing shell metacharacters
745
+ * @example
746
+ * isValidPackageName('@scope/package') // true
747
+ * isValidPackageName('my-package@1.0.0') // true
748
+ * isValidPackageName('pkg; rm -rf /') // false (shell injection)
749
+ * isValidPackageName('pkg$(whoami)') // false (command substitution)
750
+ */
751
+ isValidPackageName(packageName) {
752
+ return VALID_PACKAGE_NAME_PATTERN.test(packageName);
753
+ }
754
+ /**
755
+ * Extract package info from a server's stdio config
756
+ * @param serverName - Name of the MCP server
757
+ * @param config - Stdio configuration for the server
758
+ * @returns Package info if extractable, null otherwise
759
+ */
760
+ extractPackageInfo(serverName, config) {
761
+ const command = config.command.toLowerCase();
762
+ const args = config.args || [];
763
+ if (command === COMMAND_NPX || command.endsWith(COMMAND_NPX_SUFFIX)) {
764
+ const packageName = this.extractNpxPackage(args);
765
+ if (packageName && this.isValidPackageName(packageName)) return {
766
+ serverName,
767
+ packageManager: COMMAND_NPX,
768
+ packageName,
769
+ fullCommand: [
770
+ COMMAND_NPM,
771
+ ARG_INSTALL,
772
+ ARG_GLOBAL,
773
+ packageName
774
+ ]
775
+ };
776
+ }
777
+ if (command === COMMAND_PNPX || command.endsWith(COMMAND_PNPX_SUFFIX)) {
778
+ const packageName = this.extractNpxPackage(args);
779
+ if (packageName && this.isValidPackageName(packageName)) return {
780
+ serverName,
781
+ packageManager: COMMAND_PNPX,
782
+ packageName,
783
+ fullCommand: [
784
+ COMMAND_PNPM,
785
+ ARG_ADD,
786
+ ARG_GLOBAL,
787
+ packageName
788
+ ]
789
+ };
790
+ }
791
+ if (command === COMMAND_UVX || command.endsWith(COMMAND_UVX_SUFFIX)) {
792
+ const packageName = this.extractUvxPackage(args);
793
+ if (packageName && this.isValidPackageName(packageName)) return {
794
+ serverName,
795
+ packageManager: COMMAND_UVX,
796
+ packageName,
797
+ fullCommand: [COMMAND_UVX, packageName]
798
+ };
799
+ }
800
+ if ((command === COMMAND_UV || command.endsWith(COMMAND_UV_SUFFIX)) && args.includes(ARG_RUN)) {
801
+ const packageName = this.extractUvRunPackage(args);
802
+ if (packageName && this.isValidPackageName(packageName)) return {
803
+ serverName,
804
+ packageManager: COMMAND_UV,
805
+ packageName,
806
+ fullCommand: [
807
+ COMMAND_UV,
808
+ ARG_TOOL,
809
+ ARG_INSTALL,
810
+ packageName
811
+ ]
812
+ };
813
+ }
814
+ return null;
815
+ }
816
+ /**
817
+ * Extract package name from npx command args
818
+ * @param args - Command arguments
819
+ * @returns Package name or null
820
+ * @remarks Handles --package=value, --package value, -p value patterns.
821
+ * Falls back to first non-flag argument if no --package/-p flag found.
822
+ * Returns null if flag has no value or is followed by another flag.
823
+ * When multiple --package flags exist, returns the first valid one.
824
+ * @example
825
+ * extractNpxPackage(['--package=@scope/pkg']) // returns '@scope/pkg'
826
+ * extractNpxPackage(['--package', 'pkg-name']) // returns 'pkg-name'
827
+ * extractNpxPackage(['-p', 'pkg']) // returns 'pkg'
828
+ * extractNpxPackage(['-y', 'pkg-name', '--flag']) // returns 'pkg-name' (fallback)
829
+ * extractNpxPackage(['--package=']) // returns null (empty value)
830
+ */
831
+ extractNpxPackage(args) {
832
+ for (let i = 0; i < args.length; i++) {
833
+ const arg = args[i];
834
+ if (arg.startsWith(FLAG_PACKAGE_LONG + EQUALS_DELIMITER)) return arg.slice(FLAG_PACKAGE_LONG.length + EQUALS_DELIMITER.length) || null;
835
+ if (arg === FLAG_PACKAGE_LONG && i + 1 < args.length) {
836
+ const nextArg = args[i + 1];
837
+ if (!nextArg.startsWith(FLAG_PREFIX)) return nextArg;
838
+ }
839
+ if (arg === FLAG_PACKAGE_SHORT && i + 1 < args.length) {
840
+ const nextArg = args[i + 1];
841
+ if (!nextArg.startsWith(FLAG_PREFIX)) return nextArg;
842
+ }
843
+ }
844
+ for (const arg of args) {
845
+ if (arg.startsWith(FLAG_PREFIX)) continue;
846
+ return arg;
847
+ }
848
+ return null;
849
+ }
850
+ /**
851
+ * Extract package name from uvx command args
852
+ * @param args - Command arguments
853
+ * @returns Package name or null
854
+ * @remarks Assumes the first non-flag argument is the package name.
855
+ * Handles both single (-) and double (--) dash flags.
856
+ * @example
857
+ * extractUvxPackage(['mcp-server-fetch']) // returns 'mcp-server-fetch'
858
+ * extractUvxPackage(['--quiet', 'pkg-name']) // returns 'pkg-name'
859
+ */
860
+ extractUvxPackage(args) {
861
+ for (const arg of args) {
862
+ if (arg.startsWith(FLAG_PREFIX)) continue;
863
+ return arg;
864
+ }
865
+ return null;
866
+ }
867
+ /**
868
+ * Extract package name from uv run command args
869
+ * @param args - Command arguments
870
+ * @returns Package name or null
871
+ * @remarks Looks for the first non-flag argument after the 'run' subcommand.
872
+ * Returns null if 'run' is not found in args.
873
+ * @example
874
+ * extractUvRunPackage(['run', 'mcp-server']) // returns 'mcp-server'
875
+ * extractUvRunPackage(['run', '--verbose', 'pkg']) // returns 'pkg'
876
+ * extractUvRunPackage(['install', 'pkg']) // returns null (no 'run')
877
+ */
878
+ extractUvRunPackage(args) {
879
+ const runIndex = args.indexOf(ARG_RUN);
880
+ if (runIndex === -1) return null;
881
+ for (let i = runIndex + 1; i < args.length; i++) {
882
+ const arg = args[i];
883
+ if (arg.startsWith(FLAG_PREFIX)) continue;
884
+ return arg;
885
+ }
886
+ return null;
887
+ }
888
+ /**
889
+ * Run a shell command and capture output
890
+ * @param command - Command to run
891
+ * @param args - Command arguments
892
+ * @returns Promise with success status and output
893
+ */
894
+ runCommand(command, args) {
895
+ return new Promise((resolve$1) => {
896
+ const proc = spawn(command, args, {
897
+ stdio: [
898
+ STDIO_IGNORE,
899
+ STDIO_PIPE,
900
+ STDIO_PIPE
901
+ ],
902
+ shell: process.platform === PLATFORM_WIN32
903
+ });
904
+ let stdout = "";
905
+ let stderr = "";
906
+ proc.stdout?.on("data", (data) => {
907
+ stdout += data.toString();
908
+ });
909
+ proc.stderr?.on("data", (data) => {
910
+ stderr += data.toString();
911
+ });
912
+ proc.on("close", (code) => {
913
+ resolve$1({
914
+ success: code === EXIT_CODE_SUCCESS,
915
+ output: stdout || stderr
916
+ });
917
+ });
918
+ proc.on("error", (error) => {
919
+ resolve$1({
920
+ success: false,
921
+ output: error.message
922
+ });
923
+ });
924
+ });
925
+ }
926
+ };
927
+
928
+ //#endregion
929
+ //#region src/commands/prefetch.ts
930
+ /**
931
+ * Prefetch Command
932
+ *
933
+ * DESIGN PATTERNS:
934
+ * - Command pattern with Commander for CLI argument parsing
935
+ * - Async/await pattern for asynchronous operations
936
+ * - Error handling pattern with try-catch and proper exit codes
937
+ *
938
+ * CODING STANDARDS:
939
+ * - Use async action handlers for asynchronous operations
940
+ * - Provide clear option descriptions and default values
941
+ * - Handle errors gracefully with process.exit()
942
+ * - Log progress and errors to console
943
+ * - Use Commander's .option() and .argument() for inputs
944
+ *
945
+ * AVOID:
946
+ * - Synchronous blocking operations in action handlers
947
+ * - Missing error handling (always use try-catch)
948
+ * - Hardcoded values (use options or environment variables)
949
+ * - Not exiting with appropriate exit codes on errors
950
+ */
951
+ /**
952
+ * Pre-download packages used by MCP servers (npx, pnpx, uvx, uv)
953
+ */
954
+ 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) => {
955
+ try {
956
+ const configFilePath = options.config || findConfigFile();
957
+ if (!configFilePath) {
958
+ print.error("No MCP configuration file found.");
959
+ print.info("Use --config <path> to specify a config file, or run \"one-mcp init\" to create one.");
960
+ process.exit(1);
961
+ }
962
+ print.info(`Loading configuration from: ${configFilePath}`);
963
+ const prefetchService = new PrefetchService({
964
+ mcpConfig: await new ConfigFetcherService({
965
+ configFilePath,
966
+ useCache: false
967
+ }).fetchConfiguration(true),
968
+ filter: options.filter,
969
+ parallel: options.parallel
970
+ });
971
+ const packages = prefetchService.extractPackages();
972
+ if (packages.length === 0) {
973
+ print.warning("No packages found to prefetch.");
974
+ print.info("Prefetch supports: npx, pnpx, uvx, and uv run commands");
975
+ return;
976
+ }
977
+ print.info(`Found ${packages.length} package(s) to prefetch:`);
978
+ for (const pkg of packages) print.item(`${pkg.serverName}: ${pkg.packageManager} ${pkg.packageName}`);
979
+ if (options.dryRun) {
980
+ print.newline();
981
+ print.header("Dry run mode - commands that would be executed:");
982
+ for (const pkg of packages) print.indent(pkg.fullCommand.join(" "));
983
+ return;
984
+ }
985
+ print.newline();
986
+ print.info("Prefetching packages...");
987
+ const summary = await prefetchService.prefetch();
988
+ print.newline();
989
+ if (summary.failed === 0) print.success(`Prefetch complete: ${summary.successful} succeeded, ${summary.failed} failed`);
990
+ else print.warning(`Prefetch complete: ${summary.successful} succeeded, ${summary.failed} failed`);
991
+ if (summary.failed > 0) {
992
+ print.newline();
993
+ print.error("Failed packages:");
994
+ for (const result of summary.results.filter((r) => !r.success)) print.item(`${result.package.serverName} (${result.package.packageName}): ${result.output.trim()}`);
995
+ process.exit(1);
996
+ }
997
+ } catch (error) {
998
+ print.error("Error executing prefetch:", error instanceof Error ? error.message : String(error));
999
+ process.exit(1);
1000
+ }
1001
+ });
1002
+
549
1003
  //#endregion
550
1004
  //#region package.json
551
- var version = "0.2.6";
1005
+ var version = "0.2.2";
552
1006
 
553
1007
  //#endregion
554
1008
  //#region src/cli.ts
@@ -574,16 +1028,25 @@ var version = "0.2.6";
574
1028
  * Main entry point
575
1029
  */
576
1030
  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);
1031
+ try {
1032
+ const program = new Command();
1033
+ program.name("one-mcp").description("One MCP server package").version(version);
1034
+ program.addCommand(initCommand);
1035
+ program.addCommand(mcpServeCommand);
1036
+ program.addCommand(listToolsCommand);
1037
+ program.addCommand(describeToolsCommand);
1038
+ program.addCommand(useToolCommand);
1039
+ program.addCommand(prefetchCommand);
1040
+ await program.parseAsync(process.argv);
1041
+ } catch (error) {
1042
+ console.error(`CLI execution failed: ${error instanceof Error ? error.message : error}`);
1043
+ process.exit(1);
1044
+ }
585
1045
  }
586
- main();
1046
+ main().catch((error) => {
1047
+ console.error(`Fatal error: ${error instanceof Error ? error.message : error}`);
1048
+ process.exit(1);
1049
+ });
587
1050
 
588
1051
  //#endregion
589
1052
  export { };
@@ -231,6 +231,7 @@ const ClaudeCodeStdioServerSchema = zod.z.object({
231
231
  env: zod.z.record(zod.z.string(), zod.z.string()).optional(),
232
232
  disabled: zod.z.boolean().optional(),
233
233
  instruction: zod.z.string().optional(),
234
+ timeout: zod.z.number().positive().optional(),
234
235
  config: AdditionalConfigSchema
235
236
  });
236
237
  const ClaudeCodeHttpServerSchema = zod.z.object({
@@ -239,6 +240,7 @@ const ClaudeCodeHttpServerSchema = zod.z.object({
239
240
  type: zod.z.enum(["http", "sse"]).optional(),
240
241
  disabled: zod.z.boolean().optional(),
241
242
  instruction: zod.z.string().optional(),
243
+ timeout: zod.z.number().positive().optional(),
242
244
  config: AdditionalConfigSchema
243
245
  });
244
246
  const ClaudeCodeServerConfigSchema = zod.z.union([ClaudeCodeStdioServerSchema, ClaudeCodeHttpServerSchema]);
@@ -309,6 +311,7 @@ const McpServerConfigSchema = zod.z.discriminatedUnion("transport", [
309
311
  toolBlacklist: zod.z.array(zod.z.string()).optional(),
310
312
  omitToolDescription: zod.z.boolean().optional(),
311
313
  prompts: zod.z.record(zod.z.string(), InternalPromptConfigSchema).optional(),
314
+ timeout: zod.z.number().positive().optional(),
312
315
  transport: zod.z.literal("stdio"),
313
316
  config: McpStdioConfigSchema
314
317
  }),
@@ -318,6 +321,7 @@ const McpServerConfigSchema = zod.z.discriminatedUnion("transport", [
318
321
  toolBlacklist: zod.z.array(zod.z.string()).optional(),
319
322
  omitToolDescription: zod.z.boolean().optional(),
320
323
  prompts: zod.z.record(zod.z.string(), InternalPromptConfigSchema).optional(),
324
+ timeout: zod.z.number().positive().optional(),
321
325
  transport: zod.z.literal("http"),
322
326
  config: McpHttpConfigSchema
323
327
  }),
@@ -327,6 +331,7 @@ const McpServerConfigSchema = zod.z.discriminatedUnion("transport", [
327
331
  toolBlacklist: zod.z.array(zod.z.string()).optional(),
328
332
  omitToolDescription: zod.z.boolean().optional(),
329
333
  prompts: zod.z.record(zod.z.string(), InternalPromptConfigSchema).optional(),
334
+ timeout: zod.z.number().positive().optional(),
330
335
  transport: zod.z.literal("sse"),
331
336
  config: McpSseConfigSchema
332
337
  })
@@ -360,6 +365,7 @@ function transformClaudeCodeConfig(claudeConfig) {
360
365
  toolBlacklist: stdioConfig.config?.toolBlacklist,
361
366
  omitToolDescription: stdioConfig.config?.omitToolDescription,
362
367
  prompts: stdioConfig.config?.prompts,
368
+ timeout: stdioConfig.timeout,
363
369
  transport: "stdio",
364
370
  config: {
365
371
  command: interpolatedCommand,
@@ -378,6 +384,7 @@ function transformClaudeCodeConfig(claudeConfig) {
378
384
  toolBlacklist: httpConfig.config?.toolBlacklist,
379
385
  omitToolDescription: httpConfig.config?.omitToolDescription,
380
386
  prompts: httpConfig.config?.prompts,
387
+ timeout: httpConfig.timeout,
381
388
  transport,
382
389
  config: {
383
390
  url: interpolatedUrl,
@@ -836,6 +843,8 @@ var ConfigFetcherService = class {
836
843
 
837
844
  //#endregion
838
845
  //#region src/services/McpClientManagerService.ts
846
+ /** Default connection timeout in milliseconds (30 seconds) */
847
+ const DEFAULT_CONNECTION_TIMEOUT_MS = 3e4;
839
848
  /**
840
849
  * MCP Client wrapper for managing individual server connections
841
850
  * This is an internal class used by McpClientManagerService
@@ -941,8 +950,10 @@ var McpClientManagerService = class {
941
950
  }
942
951
  /**
943
952
  * Connect to an MCP server based on its configuration with timeout
953
+ * Uses the timeout from server config, falling back to default (30s)
944
954
  */
945
- async connectToServer(serverName, config, timeoutMs = 1e4) {
955
+ async connectToServer(serverName, config) {
956
+ const timeoutMs = config.timeout ?? DEFAULT_CONNECTION_TIMEOUT_MS;
946
957
  if (this.clients.has(serverName)) throw new Error(`Client for ${serverName} is already connected`);
947
958
  const client = new __modelcontextprotocol_sdk_client_index_js.Client({
948
959
  name: `@agiflowai/one-mcp-client`,
@@ -202,6 +202,7 @@ const ClaudeCodeStdioServerSchema = z.object({
202
202
  env: z.record(z.string(), z.string()).optional(),
203
203
  disabled: z.boolean().optional(),
204
204
  instruction: z.string().optional(),
205
+ timeout: z.number().positive().optional(),
205
206
  config: AdditionalConfigSchema
206
207
  });
207
208
  const ClaudeCodeHttpServerSchema = z.object({
@@ -210,6 +211,7 @@ const ClaudeCodeHttpServerSchema = z.object({
210
211
  type: z.enum(["http", "sse"]).optional(),
211
212
  disabled: z.boolean().optional(),
212
213
  instruction: z.string().optional(),
214
+ timeout: z.number().positive().optional(),
213
215
  config: AdditionalConfigSchema
214
216
  });
215
217
  const ClaudeCodeServerConfigSchema = z.union([ClaudeCodeStdioServerSchema, ClaudeCodeHttpServerSchema]);
@@ -280,6 +282,7 @@ const McpServerConfigSchema = z.discriminatedUnion("transport", [
280
282
  toolBlacklist: z.array(z.string()).optional(),
281
283
  omitToolDescription: z.boolean().optional(),
282
284
  prompts: z.record(z.string(), InternalPromptConfigSchema).optional(),
285
+ timeout: z.number().positive().optional(),
283
286
  transport: z.literal("stdio"),
284
287
  config: McpStdioConfigSchema
285
288
  }),
@@ -289,6 +292,7 @@ const McpServerConfigSchema = z.discriminatedUnion("transport", [
289
292
  toolBlacklist: z.array(z.string()).optional(),
290
293
  omitToolDescription: z.boolean().optional(),
291
294
  prompts: z.record(z.string(), InternalPromptConfigSchema).optional(),
295
+ timeout: z.number().positive().optional(),
292
296
  transport: z.literal("http"),
293
297
  config: McpHttpConfigSchema
294
298
  }),
@@ -298,6 +302,7 @@ const McpServerConfigSchema = z.discriminatedUnion("transport", [
298
302
  toolBlacklist: z.array(z.string()).optional(),
299
303
  omitToolDescription: z.boolean().optional(),
300
304
  prompts: z.record(z.string(), InternalPromptConfigSchema).optional(),
305
+ timeout: z.number().positive().optional(),
301
306
  transport: z.literal("sse"),
302
307
  config: McpSseConfigSchema
303
308
  })
@@ -331,6 +336,7 @@ function transformClaudeCodeConfig(claudeConfig) {
331
336
  toolBlacklist: stdioConfig.config?.toolBlacklist,
332
337
  omitToolDescription: stdioConfig.config?.omitToolDescription,
333
338
  prompts: stdioConfig.config?.prompts,
339
+ timeout: stdioConfig.timeout,
334
340
  transport: "stdio",
335
341
  config: {
336
342
  command: interpolatedCommand,
@@ -349,6 +355,7 @@ function transformClaudeCodeConfig(claudeConfig) {
349
355
  toolBlacklist: httpConfig.config?.toolBlacklist,
350
356
  omitToolDescription: httpConfig.config?.omitToolDescription,
351
357
  prompts: httpConfig.config?.prompts,
358
+ timeout: httpConfig.timeout,
352
359
  transport,
353
360
  config: {
354
361
  url: interpolatedUrl,
@@ -807,6 +814,8 @@ var ConfigFetcherService = class {
807
814
 
808
815
  //#endregion
809
816
  //#region src/services/McpClientManagerService.ts
817
+ /** Default connection timeout in milliseconds (30 seconds) */
818
+ const DEFAULT_CONNECTION_TIMEOUT_MS = 3e4;
810
819
  /**
811
820
  * MCP Client wrapper for managing individual server connections
812
821
  * This is an internal class used by McpClientManagerService
@@ -912,8 +921,10 @@ var McpClientManagerService = class {
912
921
  }
913
922
  /**
914
923
  * Connect to an MCP server based on its configuration with timeout
924
+ * Uses the timeout from server config, falling back to default (30s)
915
925
  */
916
- async connectToServer(serverName, config, timeoutMs = 1e4) {
926
+ async connectToServer(serverName, config) {
927
+ const timeoutMs = config.timeout ?? DEFAULT_CONNECTION_TIMEOUT_MS;
917
928
  if (this.clients.has(serverName)) throw new Error(`Client for ${serverName} is already connected`);
918
929
  const client = new Client({
919
930
  name: `@agiflowai/one-mcp-client`,
package/dist/index.cjs CHANGED
@@ -1,4 +1,4 @@
1
- const require_http = require('./http-B4NAfsQl.cjs');
1
+ const require_http = require('./http-BYBRKvD4.cjs');
2
2
 
3
3
  exports.HttpTransportHandler = require_http.HttpTransportHandler;
4
4
  exports.SseTransportHandler = require_http.SseTransportHandler;
package/dist/index.d.cts CHANGED
@@ -133,6 +133,8 @@ type McpServerTransportConfig = McpStdioConfig | McpHttpConfig | McpSseConfig;
133
133
  * @property prompts - Optional prompts configuration for skill conversion
134
134
  * @property transport - The transport type (stdio, http, or sse)
135
135
  * @property config - Transport-specific configuration options
136
+ * @property timeout - Optional connection timeout in milliseconds (default: 30000)
137
+ * @property disabled - Whether this server is disabled and should not be started
136
138
  */
137
139
  interface McpServerConfig {
138
140
  name: string;
@@ -142,6 +144,8 @@ interface McpServerConfig {
142
144
  prompts?: Record<string, PromptConfig>;
143
145
  transport: McpServerTransportType;
144
146
  config: McpServerTransportConfig;
147
+ timeout?: number;
148
+ disabled?: boolean;
145
149
  }
146
150
  /**
147
151
  * Skills configuration
package/dist/index.d.mts CHANGED
@@ -133,6 +133,8 @@ type McpServerTransportConfig = McpStdioConfig | McpHttpConfig | McpSseConfig;
133
133
  * @property prompts - Optional prompts configuration for skill conversion
134
134
  * @property transport - The transport type (stdio, http, or sse)
135
135
  * @property config - Transport-specific configuration options
136
+ * @property timeout - Optional connection timeout in milliseconds (default: 30000)
137
+ * @property disabled - Whether this server is disabled and should not be started
136
138
  */
137
139
  interface McpServerConfig {
138
140
  name: string;
@@ -142,6 +144,8 @@ interface McpServerConfig {
142
144
  prompts?: Record<string, PromptConfig>;
143
145
  transport: McpServerTransportType;
144
146
  config: McpServerTransportConfig;
147
+ timeout?: number;
148
+ disabled?: boolean;
145
149
  }
146
150
  /**
147
151
  * Skills configuration
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { i as createServer, n as SseTransportHandler, r as StdioTransportHandler, t as HttpTransportHandler } from "./http-DSkkpGJU.mjs";
1
+ import { i as createServer, n as SseTransportHandler, r as StdioTransportHandler, t as HttpTransportHandler } from "./http-Bi2N9PUM.mjs";
2
2
 
3
3
  export { HttpTransportHandler, SseTransportHandler, StdioTransportHandler, createServer };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@agiflowai/one-mcp",
3
3
  "description": "One MCP server package",
4
- "version": "0.2.7",
4
+ "version": "0.3.0",
5
5
  "license": "AGPL-3.0",
6
6
  "keywords": [
7
7
  "mcp",