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
« 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"""
6import argparse
7import sys
8from pathlib import Path
10from .factory import MCPFactory, create_mcp_from_module, MissingDependencyError, print_dependency_report
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
23 # Check dependencies before creating server
24 mcp-factory my_utils.py --check-deps
26 # Create with custom name
27 mcp-factory my_utils.py --name "utils-server"
29 # Include private functions
30 mcp-factory my_utils.py --include-private
32 # Ignore missing dependencies and try to load anyway
33 mcp-factory my_utils.py --ignore-missing-deps
35 # Export server info instead of running
36 mcp-factory my_utils.py --info
38 # Run on specific port (SSE mode)
39 mcp-factory my_utils.py --transport sse --port 8080
40 """
41 )
43 parser.add_argument(
44 "module",
45 help="Python module/file to convert to MCP server"
46 )
48 parser.add_argument(
49 "--name",
50 help="Server name (defaults to module name + '-mcp')"
51 )
53 parser.add_argument(
54 "--version",
55 default="1.0.0",
56 help="Server version (default: 1.0.0)"
57 )
59 parser.add_argument(
60 "--include-private",
61 action="store_true",
62 help="Include private functions (starting with _)"
63 )
65 parser.add_argument(
66 "--transport",
67 choices=["stdio", "sse"],
68 default="stdio",
69 help="Transport type (default: stdio)"
70 )
72 parser.add_argument(
73 "--port",
74 type=int,
75 default=8080,
76 help="Port for SSE transport (default: 8080)"
77 )
79 parser.add_argument(
80 "--info",
81 action="store_true",
82 help="Show server info instead of running"
83 )
85 parser.add_argument(
86 "--list-tools",
87 action="store_true",
88 help="List all tools that would be created"
89 )
91 parser.add_argument(
92 "--filter",
93 help="Filter functions by pattern (e.g., 'calc*')"
94 )
96 parser.add_argument(
97 "--exclude",
98 help="Exclude functions by pattern (e.g., 'test_*')"
99 )
101 parser.add_argument(
102 "--check-deps",
103 action="store_true",
104 help="Check dependencies before creating server"
105 )
107 parser.add_argument(
108 "--ignore-missing-deps",
109 action="store_true",
110 help="Ignore missing dependencies and try to load anyway"
111 )
113 args = parser.parse_args()
115 try:
116 # Check dependencies first if requested
117 if args.check_deps:
118 print_dependency_report(args.module)
119 return 0
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)
125 # Create server from module
126 server = factory.from_module(
127 args.module,
128 include_private=args.include_private
129 )
131 # Apply filters if specified
132 if args.filter or args.exclude:
133 import fnmatch
134 original_tools = server._tools.copy()
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]
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]
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())}")
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}")
167 return 0
169 # Run the server
170 print(f"Starting MCP server '{server.name}' with {len(server.list_tools())} tools")
171 print(f"Transport: {args.transport}")
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")
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
194if __name__ == "__main__":
195 sys.exit(main())