Coverage for src/quickmcp/cli_factory.py: 0%

74 statements  

« prev     ^ index     » next       coverage.py v7.10.4, created at 2025-08-20 20:56 +0200

1#!/usr/bin/env python 

2""" 

3QuickMCP Factory CLI - Generate MCP servers from Python code 

4""" 

5 

6import argparse 

7import sys 

8from pathlib import Path 

9 

10from .factory import MCPFactory, create_mcp_from_module, MissingDependencyError, print_dependency_report 

11 

12 

13def main(): 

14 """Main entry point for mcp-factory CLI.""" 

15 parser = argparse.ArgumentParser( 

16 description="Generate MCP servers from Python code", 

17 formatter_class=argparse.RawDescriptionHelpFormatter, 

18 epilog=""" 

19Examples: 

20 # Create MCP server from a Python file 

21 mcp-factory my_utils.py 

22  

23 # Check dependencies before creating server 

24 mcp-factory my_utils.py --check-deps 

25  

26 # Create with custom name 

27 mcp-factory my_utils.py --name "utils-server" 

28  

29 # Include private functions 

30 mcp-factory my_utils.py --include-private 

31  

32 # Ignore missing dependencies and try to load anyway 

33 mcp-factory my_utils.py --ignore-missing-deps 

34  

35 # Export server info instead of running 

36 mcp-factory my_utils.py --info 

37  

38 # Run on specific port (SSE mode) 

39 mcp-factory my_utils.py --transport sse --port 8080 

40 """ 

41 ) 

42 

43 parser.add_argument( 

44 "module", 

45 help="Python module/file to convert to MCP server" 

46 ) 

47 

48 parser.add_argument( 

49 "--name", 

50 help="Server name (defaults to module name + '-mcp')" 

51 ) 

52 

53 parser.add_argument( 

54 "--version", 

55 default="1.0.0", 

56 help="Server version (default: 1.0.0)" 

57 ) 

58 

59 parser.add_argument( 

60 "--include-private", 

61 action="store_true", 

62 help="Include private functions (starting with _)" 

63 ) 

64 

65 parser.add_argument( 

66 "--transport", 

67 choices=["stdio", "sse"], 

68 default="stdio", 

69 help="Transport type (default: stdio)" 

70 ) 

71 

72 parser.add_argument( 

73 "--port", 

74 type=int, 

75 default=8080, 

76 help="Port for SSE transport (default: 8080)" 

77 ) 

78 

79 parser.add_argument( 

80 "--info", 

81 action="store_true", 

82 help="Show server info instead of running" 

83 ) 

84 

85 parser.add_argument( 

86 "--list-tools", 

87 action="store_true", 

88 help="List all tools that would be created" 

89 ) 

90 

91 parser.add_argument( 

92 "--filter", 

93 help="Filter functions by pattern (e.g., 'calc*')" 

94 ) 

95 

96 parser.add_argument( 

97 "--exclude", 

98 help="Exclude functions by pattern (e.g., 'test_*')" 

99 ) 

100 

101 parser.add_argument( 

102 "--check-deps", 

103 action="store_true", 

104 help="Check dependencies before creating server" 

105 ) 

106 

107 parser.add_argument( 

108 "--ignore-missing-deps", 

109 action="store_true", 

110 help="Ignore missing dependencies and try to load anyway" 

111 ) 

112 

113 args = parser.parse_args() 

114 

115 try: 

116 # Check dependencies first if requested 

117 if args.check_deps: 

118 print_dependency_report(args.module) 

119 return 0 

120 

121 # Create the factory 

122 check_dependencies = not args.ignore_missing_deps 

123 factory = MCPFactory(name=args.name, version=args.version, check_dependencies=check_dependencies) 

124 

125 # Create server from module 

126 server = factory.from_module( 

127 args.module, 

128 include_private=args.include_private 

129 ) 

130 

131 # Apply filters if specified 

132 if args.filter or args.exclude: 

133 import fnmatch 

134 original_tools = server._tools.copy() 

135 

136 for tool_name in list(server._tools.keys()): 

137 # Apply include filter 

138 if args.filter and not fnmatch.fnmatch(tool_name, args.filter): 

139 del server._tools[tool_name] 

140 if tool_name in server._tool_metadata: 

141 del server._tool_metadata[tool_name] 

142 

143 # Apply exclude filter 

144 if args.exclude and fnmatch.fnmatch(tool_name, args.exclude): 

145 if tool_name in server._tools: 

146 del server._tools[tool_name] 

147 if tool_name in server._tool_metadata: 

148 del server._tool_metadata[tool_name] 

149 

150 # Handle info/list modes 

151 if args.info or args.list_tools: 

152 print(f"Server: {server.name}") 

153 print(f"Version: {server.version}") 

154 print(f"Description: {server.description}") 

155 print(f"Tools: {len(server.list_tools())}") 

156 

157 if args.list_tools: 

158 print("\nAvailable tools:") 

159 for tool_name in sorted(server.list_tools()): 

160 tool_func = server._tools.get(tool_name) 

161 if tool_func and tool_func.__doc__: 

162 desc = tool_func.__doc__.strip().split('\n')[0] 

163 print(f" - {tool_name}: {desc}") 

164 else: 

165 print(f" - {tool_name}") 

166 

167 return 0 

168 

169 # Run the server 

170 print(f"Starting MCP server '{server.name}' with {len(server.list_tools())} tools") 

171 print(f"Transport: {args.transport}") 

172 

173 if args.transport == "sse": 

174 print(f"URL: http://localhost:{args.port}/sse") 

175 server.run(transport="sse", port=args.port) 

176 else: 

177 print("Running in stdio mode...") 

178 server.run(transport="stdio") 

179 

180 except KeyboardInterrupt: 

181 print("\nShutdown requested") 

182 return 0 

183 except MissingDependencyError as e: 

184 print("❌ Missing Dependencies Detected:", file=sys.stderr) 

185 print(e.format_error_message(), file=sys.stderr) 

186 print("\n💡 Tip: Use --ignore-missing-deps to try loading anyway", file=sys.stderr) 

187 print("💡 Tip: Use --check-deps to analyze dependencies without loading", file=sys.stderr) 

188 return 1 

189 except Exception as e: 

190 print(f"Error: {e}", file=sys.stderr) 

191 return 1 

192 

193 

194if __name__ == "__main__": 

195 sys.exit(main())