mfcli 0.2.1__py3-none-any.whl
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.
- mfcli/.env.example +72 -0
- mfcli/__init__.py +0 -0
- mfcli/agents/__init__.py +0 -0
- mfcli/agents/controller/__init__.py +0 -0
- mfcli/agents/controller/agent.py +19 -0
- mfcli/agents/controller/config.yaml +27 -0
- mfcli/agents/controller/tools.py +42 -0
- mfcli/agents/tools/general.py +118 -0
- mfcli/alembic/env.py +61 -0
- mfcli/alembic/script.py.mako +28 -0
- mfcli/alembic/versions/6ccc0c7c397c_added_fields_to_pdf_parts_model.py +39 -0
- mfcli/alembic/versions/769019ef4870_added_gemini_file_path_to_pdf_part_model.py +33 -0
- mfcli/alembic/versions/7a2e3a779fdc_added_functional_block_and_component_.py +54 -0
- mfcli/alembic/versions/7d5adb2a47a7_added_pdf_parts_model.py +41 -0
- mfcli/alembic/versions/7fcb7d6a5836_init.py +167 -0
- mfcli/alembic/versions/e0f2b5765c72_added_cascade_delete_for_models_that_.py +32 -0
- mfcli/alembic.ini +147 -0
- mfcli/cli/__init__.py +0 -0
- mfcli/cli/dependencies.py +59 -0
- mfcli/cli/main.py +200 -0
- mfcli/client/__init__.py +0 -0
- mfcli/client/chroma_db.py +184 -0
- mfcli/client/docling.py +44 -0
- mfcli/client/gemini.py +252 -0
- mfcli/client/llama_parse.py +38 -0
- mfcli/client/vector_db.py +93 -0
- mfcli/constants/__init__.py +0 -0
- mfcli/constants/base_enum.py +18 -0
- mfcli/constants/directory_names.py +1 -0
- mfcli/constants/file_types.py +189 -0
- mfcli/constants/gemini.py +1 -0
- mfcli/constants/openai.py +6 -0
- mfcli/constants/pipeline_run_status.py +3 -0
- mfcli/crud/__init__.py +0 -0
- mfcli/crud/file.py +42 -0
- mfcli/crud/functional_blocks.py +26 -0
- mfcli/crud/netlist.py +18 -0
- mfcli/crud/pipeline_run.py +17 -0
- mfcli/crud/project.py +144 -0
- mfcli/digikey/__init__.py +0 -0
- mfcli/digikey/digikey.py +105 -0
- mfcli/main.py +5 -0
- mfcli/mcp/__init__.py +0 -0
- mfcli/mcp/configs/cline_mcp_settings.json +11 -0
- mfcli/mcp/configs/mfcli.mcp.json +7 -0
- mfcli/mcp/mcp_instance.py +6 -0
- mfcli/mcp/server.py +37 -0
- mfcli/mcp/state_manager.py +51 -0
- mfcli/mcp/tools/__init__.py +0 -0
- mfcli/mcp/tools/query_knowledgebase.py +108 -0
- mfcli/models/__init__.py +10 -0
- mfcli/models/base.py +10 -0
- mfcli/models/bom.py +71 -0
- mfcli/models/datasheet.py +10 -0
- mfcli/models/debug_setup.py +64 -0
- mfcli/models/file.py +43 -0
- mfcli/models/file_docket.py +94 -0
- mfcli/models/file_metadata.py +19 -0
- mfcli/models/functional_blocks.py +94 -0
- mfcli/models/llm_response.py +5 -0
- mfcli/models/mcu.py +97 -0
- mfcli/models/mcu_errata.py +26 -0
- mfcli/models/netlist.py +59 -0
- mfcli/models/pdf_parts.py +25 -0
- mfcli/models/pipeline_run.py +34 -0
- mfcli/models/project.py +27 -0
- mfcli/models/project_metadata.py +15 -0
- mfcli/pipeline/__init__.py +0 -0
- mfcli/pipeline/analysis/__init__.py +0 -0
- mfcli/pipeline/analysis/bom_netlist_mapper.py +28 -0
- mfcli/pipeline/analysis/generators/__init__.py +0 -0
- mfcli/pipeline/analysis/generators/bom/__init__.py +0 -0
- mfcli/pipeline/analysis/generators/bom/bom.py +74 -0
- mfcli/pipeline/analysis/generators/debug_setup/__init__.py +0 -0
- mfcli/pipeline/analysis/generators/debug_setup/debug_setup.py +71 -0
- mfcli/pipeline/analysis/generators/debug_setup/instructions.py +150 -0
- mfcli/pipeline/analysis/generators/functional_blocks/__init__.py +0 -0
- mfcli/pipeline/analysis/generators/functional_blocks/functional_blocks.py +93 -0
- mfcli/pipeline/analysis/generators/functional_blocks/instructions.py +34 -0
- mfcli/pipeline/analysis/generators/functional_blocks/validator.py +94 -0
- mfcli/pipeline/analysis/generators/generator.py +258 -0
- mfcli/pipeline/analysis/generators/generator_base.py +18 -0
- mfcli/pipeline/analysis/generators/mcu/__init__.py +0 -0
- mfcli/pipeline/analysis/generators/mcu/instructions.py +156 -0
- mfcli/pipeline/analysis/generators/mcu/mcu.py +84 -0
- mfcli/pipeline/analysis/generators/mcu_errata/__init__.py +1 -0
- mfcli/pipeline/analysis/generators/mcu_errata/instructions.py +77 -0
- mfcli/pipeline/analysis/generators/mcu_errata/mcu_errata.py +95 -0
- mfcli/pipeline/analysis/generators/summary/__init__.py +0 -0
- mfcli/pipeline/analysis/generators/summary/summary.py +47 -0
- mfcli/pipeline/classifier.py +93 -0
- mfcli/pipeline/data_enricher.py +15 -0
- mfcli/pipeline/extractor.py +34 -0
- mfcli/pipeline/extractors/__init__.py +0 -0
- mfcli/pipeline/extractors/pdf.py +12 -0
- mfcli/pipeline/parser.py +120 -0
- mfcli/pipeline/parsers/__init__.py +0 -0
- mfcli/pipeline/parsers/netlist/__init__.py +0 -0
- mfcli/pipeline/parsers/netlist/edif.py +93 -0
- mfcli/pipeline/parsers/netlist/kicad_legacy_net.py +326 -0
- mfcli/pipeline/parsers/netlist/kicad_spice.py +135 -0
- mfcli/pipeline/parsers/netlist/pads.py +185 -0
- mfcli/pipeline/parsers/netlist/protel.py +166 -0
- mfcli/pipeline/parsers/netlist/protel_detector.py +29 -0
- mfcli/pipeline/pipeline.py +470 -0
- mfcli/pipeline/preprocessors/__init__.py +0 -0
- mfcli/pipeline/preprocessors/user_guide.py +127 -0
- mfcli/pipeline/run_context.py +32 -0
- mfcli/pipeline/schema_mapper.py +89 -0
- mfcli/pipeline/sub_classifier.py +115 -0
- mfcli/utils/__init__.py +0 -0
- mfcli/utils/cline_rules.py +256 -0
- mfcli/utils/config.py +33 -0
- mfcli/utils/configurator.py +324 -0
- mfcli/utils/data_cleaner.py +114 -0
- mfcli/utils/datasheet_vectorizer.py +283 -0
- mfcli/utils/directory_manager.py +116 -0
- mfcli/utils/file_upload.py +298 -0
- mfcli/utils/files.py +16 -0
- mfcli/utils/http_requests.py +54 -0
- mfcli/utils/kb_lister.py +89 -0
- mfcli/utils/kb_remover.py +173 -0
- mfcli/utils/logger.py +28 -0
- mfcli/utils/mcp_configurator.py +394 -0
- mfcli/utils/migrations.py +18 -0
- mfcli/utils/orm.py +43 -0
- mfcli/utils/pdf_splitter.py +63 -0
- mfcli/utils/pre_uninstall.py +167 -0
- mfcli/utils/query_service.py +22 -0
- mfcli/utils/system_check.py +306 -0
- mfcli/utils/tools.py +98 -0
- mfcli/utils/vectorizer.py +28 -0
- mfcli-0.2.1.dist-info/METADATA +956 -0
- mfcli-0.2.1.dist-info/RECORD +138 -0
- mfcli-0.2.1.dist-info/WHEEL +5 -0
- mfcli-0.2.1.dist-info/entry_points.txt +4 -0
- mfcli-0.2.1.dist-info/licenses/LICENSE +21 -0
- mfcli-0.2.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
"""MCP server auto-configuration for Cline and Claude Code."""
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import platform
|
|
5
|
+
import shutil
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import List, Tuple, Optional
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_mcp_config_paths() -> List[Tuple[str, Path]]:
|
|
12
|
+
"""Get potential MCP configuration file paths for different editors."""
|
|
13
|
+
paths = []
|
|
14
|
+
system = platform.system()
|
|
15
|
+
|
|
16
|
+
if system == "Windows":
|
|
17
|
+
appdata = Path(os.environ.get("APPDATA", ""))
|
|
18
|
+
localappdata = Path(os.environ.get("LOCALAPPDATA", ""))
|
|
19
|
+
|
|
20
|
+
# Cline (VS Code extension)
|
|
21
|
+
cline_vscode = appdata / "Code" / "User" / "globalStorage" / "saoudrizwan.claude-dev" / "settings" / "cline_mcp_settings.json"
|
|
22
|
+
if cline_vscode.exists():
|
|
23
|
+
paths.append(("Cline (VS Code)", cline_vscode))
|
|
24
|
+
|
|
25
|
+
# Windsurf/Cline standalone
|
|
26
|
+
home = Path.home()
|
|
27
|
+
cline_standalone = home / ".cline" / "mcp_settings.json"
|
|
28
|
+
if cline_standalone.exists():
|
|
29
|
+
paths.append(("Cline (Standalone)", cline_standalone))
|
|
30
|
+
|
|
31
|
+
# Claude Code (if it exists on Windows - need to verify path)
|
|
32
|
+
# This is a placeholder - actual path may differ
|
|
33
|
+
claude_code = localappdata / "Claude" / "mcp_settings.json"
|
|
34
|
+
if claude_code.exists():
|
|
35
|
+
paths.append(("Claude Code", claude_code))
|
|
36
|
+
|
|
37
|
+
elif system in ["Darwin", "Linux"]: # macOS or Linux
|
|
38
|
+
home = Path.home()
|
|
39
|
+
|
|
40
|
+
# Cline (VS Code extension) - macOS
|
|
41
|
+
if system == "Darwin":
|
|
42
|
+
cline_vscode = home / "Library" / "Application Support" / "Code" / "User" / "globalStorage" / "saoudrizwan.claude-dev" / "settings" / "cline_mcp_settings.json"
|
|
43
|
+
else: # Linux
|
|
44
|
+
cline_vscode = home / ".config" / "Code" / "User" / "globalStorage" / "saoudrizwan.claude-dev" / "settings" / "cline_mcp_settings.json"
|
|
45
|
+
|
|
46
|
+
if cline_vscode.exists():
|
|
47
|
+
paths.append(("Cline (VS Code)", cline_vscode))
|
|
48
|
+
|
|
49
|
+
# Windsurf/Cline standalone
|
|
50
|
+
cline_standalone = home / ".cline" / "mcp_settings.json"
|
|
51
|
+
if cline_standalone.exists():
|
|
52
|
+
paths.append(("Cline (Standalone)", cline_standalone))
|
|
53
|
+
|
|
54
|
+
# Claude Code
|
|
55
|
+
claude_code = home / ".claude" / "mcp_settings.json"
|
|
56
|
+
if claude_code.exists():
|
|
57
|
+
paths.append(("Claude Code", claude_code))
|
|
58
|
+
|
|
59
|
+
return paths
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def backup_config(config_path: Path) -> Path:
|
|
63
|
+
"""Create a backup of the configuration file."""
|
|
64
|
+
backup_path = config_path.with_suffix(config_path.suffix + ".backup")
|
|
65
|
+
shutil.copy2(config_path, backup_path)
|
|
66
|
+
return backup_path
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def get_mfcli_mcp_config() -> dict:
|
|
70
|
+
"""Get the mfcli-mcp server configuration."""
|
|
71
|
+
return {
|
|
72
|
+
"mfcli-mcp": {
|
|
73
|
+
"disabled": False,
|
|
74
|
+
"timeout": 60,
|
|
75
|
+
"type": "stdio",
|
|
76
|
+
"command": "mfcli-mcp"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def update_mcp_config(config_path: Path) -> bool:
|
|
82
|
+
"""Update MCP configuration file with mfcli-mcp server."""
|
|
83
|
+
try:
|
|
84
|
+
# Read existing config
|
|
85
|
+
with open(config_path, 'r') as f:
|
|
86
|
+
config = json.load(f)
|
|
87
|
+
|
|
88
|
+
# Ensure mcpServers key exists
|
|
89
|
+
if "mcpServers" not in config:
|
|
90
|
+
config["mcpServers"] = {}
|
|
91
|
+
|
|
92
|
+
# Check if mfcli-mcp already exists
|
|
93
|
+
if "mfcli-mcp" in config["mcpServers"]:
|
|
94
|
+
print(f" ℹ️ mfcli-mcp already configured in this file")
|
|
95
|
+
return True
|
|
96
|
+
|
|
97
|
+
# Add mfcli-mcp configuration
|
|
98
|
+
mfcli_config = get_mfcli_mcp_config()
|
|
99
|
+
config["mcpServers"].update(mfcli_config)
|
|
100
|
+
|
|
101
|
+
# Create backup
|
|
102
|
+
backup_path = backup_config(config_path)
|
|
103
|
+
print(f" 📋 Backup created: {backup_path}")
|
|
104
|
+
|
|
105
|
+
# Write updated config
|
|
106
|
+
with open(config_path, 'w') as f:
|
|
107
|
+
json.dump(config, f, indent=2)
|
|
108
|
+
|
|
109
|
+
return True
|
|
110
|
+
|
|
111
|
+
except Exception as e:
|
|
112
|
+
print(f" ❌ Error updating config: {e}")
|
|
113
|
+
return False
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def create_mcp_config(config_path: Path) -> bool:
|
|
117
|
+
"""Create a new MCP configuration file with mfcli-mcp server."""
|
|
118
|
+
try:
|
|
119
|
+
# Ensure parent directory exists
|
|
120
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
121
|
+
|
|
122
|
+
# Create new config
|
|
123
|
+
config = {
|
|
124
|
+
"mcpServers": get_mfcli_mcp_config()
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# Write config
|
|
128
|
+
with open(config_path, 'w') as f:
|
|
129
|
+
json.dump(config, f, indent=2)
|
|
130
|
+
|
|
131
|
+
return True
|
|
132
|
+
|
|
133
|
+
except Exception as e:
|
|
134
|
+
print(f" ❌ Error creating config: {e}")
|
|
135
|
+
return False
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def verify_mfcli_installation() -> bool:
|
|
139
|
+
"""Verify that mfcli-mcp is installed and accessible."""
|
|
140
|
+
try:
|
|
141
|
+
# Check if mfcli-mcp is in PATH
|
|
142
|
+
result = shutil.which("mfcli-mcp")
|
|
143
|
+
if result:
|
|
144
|
+
return True
|
|
145
|
+
|
|
146
|
+
# On Windows, also check Scripts directory
|
|
147
|
+
if platform.system() == "Windows":
|
|
148
|
+
scripts_dir = Path(sys.executable).parent / "Scripts"
|
|
149
|
+
mfcli_mcp = scripts_dir / "mfcli-mcp.exe"
|
|
150
|
+
if mfcli_mcp.exists():
|
|
151
|
+
return True
|
|
152
|
+
|
|
153
|
+
return False
|
|
154
|
+
|
|
155
|
+
except Exception:
|
|
156
|
+
return False
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def test_mcp_server() -> bool:
|
|
160
|
+
"""Test if MCP server can be started."""
|
|
161
|
+
print(" Testing MCP server...", end=' ')
|
|
162
|
+
sys.stdout.flush()
|
|
163
|
+
|
|
164
|
+
try:
|
|
165
|
+
import subprocess
|
|
166
|
+
# Try to run mfcli-mcp with a timeout
|
|
167
|
+
result = subprocess.run(
|
|
168
|
+
["mfcli-mcp"],
|
|
169
|
+
capture_output=True,
|
|
170
|
+
timeout=5,
|
|
171
|
+
text=True
|
|
172
|
+
)
|
|
173
|
+
# If it starts without error, that's good enough
|
|
174
|
+
print("✅")
|
|
175
|
+
return True
|
|
176
|
+
except subprocess.TimeoutExpired:
|
|
177
|
+
# Timeout is actually OK - it means the server started
|
|
178
|
+
print("✅")
|
|
179
|
+
return True
|
|
180
|
+
except FileNotFoundError:
|
|
181
|
+
print("❌ mfcli-mcp command not found")
|
|
182
|
+
return False
|
|
183
|
+
except Exception as e:
|
|
184
|
+
print(f"❌ {str(e)[:50]}")
|
|
185
|
+
return False
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def check_mcp_configured() -> Tuple[bool, List[Tuple[str, Path]]]:
|
|
189
|
+
"""
|
|
190
|
+
Check if MCP server is configured in any detected editor.
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
Tuple of (is_configured, config_paths)
|
|
194
|
+
- is_configured: True if mfcli-mcp is found in at least one config
|
|
195
|
+
- config_paths: List of all detected MCP config paths
|
|
196
|
+
"""
|
|
197
|
+
config_paths = get_mcp_config_paths()
|
|
198
|
+
|
|
199
|
+
if not config_paths:
|
|
200
|
+
return False, []
|
|
201
|
+
|
|
202
|
+
# Check if any config has mfcli-mcp configured
|
|
203
|
+
for name, path in config_paths:
|
|
204
|
+
try:
|
|
205
|
+
with open(path, 'r') as f:
|
|
206
|
+
config = json.load(f)
|
|
207
|
+
|
|
208
|
+
if "mcpServers" in config and "mfcli-mcp" in config["mcpServers"]:
|
|
209
|
+
return True, config_paths
|
|
210
|
+
except Exception:
|
|
211
|
+
continue
|
|
212
|
+
|
|
213
|
+
return False, config_paths
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def verify_and_prompt_mcp_setup() -> None:
|
|
217
|
+
"""
|
|
218
|
+
Verify MCP server configuration and prompt user to set it up if needed.
|
|
219
|
+
Called during 'mfcli init' to ensure MCP is configured.
|
|
220
|
+
"""
|
|
221
|
+
print("\n" + "="*70)
|
|
222
|
+
print(" MCP SERVER VERIFICATION")
|
|
223
|
+
print("="*70)
|
|
224
|
+
|
|
225
|
+
# Check if mfcli-mcp is installed
|
|
226
|
+
if not verify_mfcli_installation():
|
|
227
|
+
print("\n ⚠️ mfcli-mcp command not found!")
|
|
228
|
+
print(" MCP server won't be available until installation is complete.")
|
|
229
|
+
print("="*70 + "\n")
|
|
230
|
+
return
|
|
231
|
+
|
|
232
|
+
# Check if MCP is configured
|
|
233
|
+
is_configured, config_paths = check_mcp_configured()
|
|
234
|
+
|
|
235
|
+
if is_configured:
|
|
236
|
+
print("\n ✅ MCP server is already configured!")
|
|
237
|
+
print("="*70 + "\n")
|
|
238
|
+
return
|
|
239
|
+
|
|
240
|
+
# MCP is not configured - prompt user
|
|
241
|
+
if not config_paths:
|
|
242
|
+
print("\n ℹ️ No AI coding assistant configuration files detected.")
|
|
243
|
+
print("\n To use mfcli with AI assistants like Cline or Claude Code,")
|
|
244
|
+
print(" you'll need to configure the MCP server.")
|
|
245
|
+
print("\n Run this command when ready:")
|
|
246
|
+
print(" mfcli setup-mcp")
|
|
247
|
+
print("="*70 + "\n")
|
|
248
|
+
return
|
|
249
|
+
|
|
250
|
+
# Config files exist but mfcli-mcp is not configured
|
|
251
|
+
print(f"\n ⚠️ Found {len(config_paths)} AI coding assistant(s) but mfcli-mcp")
|
|
252
|
+
print(" is not configured yet.")
|
|
253
|
+
print("\n Would you like to configure MCP server now? (y/n): ", end='')
|
|
254
|
+
sys.stdout.flush()
|
|
255
|
+
|
|
256
|
+
try:
|
|
257
|
+
response = input().strip().lower()
|
|
258
|
+
if response in ['y', 'yes']:
|
|
259
|
+
print()
|
|
260
|
+
setup_mcp_servers()
|
|
261
|
+
else:
|
|
262
|
+
print("\n ℹ️ You can configure MCP server later by running:")
|
|
263
|
+
print(" mfcli setup-mcp")
|
|
264
|
+
print("="*70 + "\n")
|
|
265
|
+
except (KeyboardInterrupt, EOFError):
|
|
266
|
+
print("\n\n ℹ️ Skipping MCP configuration. You can run it later with:")
|
|
267
|
+
print(" mfcli setup-mcp")
|
|
268
|
+
print("="*70 + "\n")
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def setup_mcp_servers() -> None:
|
|
272
|
+
"""Auto-configure MCP servers for detected editors."""
|
|
273
|
+
print("\n" + "="*70)
|
|
274
|
+
print(" MCP SERVER AUTO-CONFIGURATION")
|
|
275
|
+
print("="*70)
|
|
276
|
+
print("\n Detecting installed AI coding assistants...")
|
|
277
|
+
|
|
278
|
+
# Verify mfcli installation
|
|
279
|
+
if not verify_mfcli_installation():
|
|
280
|
+
print("\n ❌ mfcli-mcp command not found!")
|
|
281
|
+
print("\n Please ensure mfcli is installed with:")
|
|
282
|
+
print(" pipx install mfcli")
|
|
283
|
+
print("\n Or if installing from source:")
|
|
284
|
+
print(" pip install .")
|
|
285
|
+
print("="*70 + "\n")
|
|
286
|
+
return
|
|
287
|
+
|
|
288
|
+
# Get configuration paths
|
|
289
|
+
config_paths = get_mcp_config_paths()
|
|
290
|
+
|
|
291
|
+
if not config_paths:
|
|
292
|
+
print("\n ℹ️ No AI coding assistants detected.")
|
|
293
|
+
print("\n Supported editors:")
|
|
294
|
+
print(" - Cline (VS Code extension)")
|
|
295
|
+
print(" - Cline (Standalone)")
|
|
296
|
+
print(" - Claude Code")
|
|
297
|
+
print("\n If you have one of these installed, the configuration file may")
|
|
298
|
+
print(" not exist yet. You can create it manually at:")
|
|
299
|
+
|
|
300
|
+
system = platform.system()
|
|
301
|
+
if system == "Windows":
|
|
302
|
+
print("\n Cline (VS Code):")
|
|
303
|
+
print(" %APPDATA%\\Code\\User\\globalStorage\\saoudrizwan.claude-dev\\settings\\cline_mcp_settings.json")
|
|
304
|
+
print("\n Cline (Standalone):")
|
|
305
|
+
print(" %USERPROFILE%\\.cline\\mcp_settings.json")
|
|
306
|
+
else:
|
|
307
|
+
print("\n Cline (VS Code):")
|
|
308
|
+
if system == "Darwin":
|
|
309
|
+
print(" ~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json")
|
|
310
|
+
else:
|
|
311
|
+
print(" ~/.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json")
|
|
312
|
+
print("\n Cline (Standalone):")
|
|
313
|
+
print(" ~/.cline/mcp_settings.json")
|
|
314
|
+
print("\n Claude Code:")
|
|
315
|
+
print(" ~/.claude/mcp_settings.json")
|
|
316
|
+
|
|
317
|
+
print("\n Then run this command again.")
|
|
318
|
+
print("="*70 + "\n")
|
|
319
|
+
return
|
|
320
|
+
|
|
321
|
+
print(f"\n Found {len(config_paths)} configuration file(s):\n")
|
|
322
|
+
|
|
323
|
+
success_count = 0
|
|
324
|
+
for name, path in config_paths:
|
|
325
|
+
print(f" 📝 {name}")
|
|
326
|
+
print(f" {path}")
|
|
327
|
+
|
|
328
|
+
if update_mcp_config(path):
|
|
329
|
+
print(f" ✅ Successfully configured!\n")
|
|
330
|
+
success_count += 1
|
|
331
|
+
else:
|
|
332
|
+
print(f" ❌ Configuration failed\n")
|
|
333
|
+
|
|
334
|
+
if success_count > 0:
|
|
335
|
+
print("="*70)
|
|
336
|
+
print(f" ✅ Successfully configured {success_count} editor(s)!")
|
|
337
|
+
print("="*70)
|
|
338
|
+
print("\n Next steps:")
|
|
339
|
+
print(" 1. Restart your editor (VS Code, Cline, etc.)")
|
|
340
|
+
print(" 2. The mfcli-mcp server should now be available")
|
|
341
|
+
print(" 3. Try using the 'query_local_rag' tool in your AI assistant")
|
|
342
|
+
print("\n To test the MCP server:")
|
|
343
|
+
print(" mfcli doctor")
|
|
344
|
+
print("\n")
|
|
345
|
+
else:
|
|
346
|
+
print("="*70)
|
|
347
|
+
print(" ⚠️ No configurations were updated.")
|
|
348
|
+
print("="*70 + "\n")
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def get_manual_setup_instructions() -> str:
|
|
352
|
+
"""Get manual MCP setup instructions."""
|
|
353
|
+
instructions = """
|
|
354
|
+
Manual MCP Setup Instructions
|
|
355
|
+
==============================
|
|
356
|
+
|
|
357
|
+
If auto-configuration didn't work, you can manually add mfcli-mcp to your
|
|
358
|
+
editor's MCP configuration file.
|
|
359
|
+
|
|
360
|
+
1. Locate your MCP configuration file:
|
|
361
|
+
|
|
362
|
+
Windows (Cline in VS Code):
|
|
363
|
+
%APPDATA%\\Code\\User\\globalStorage\\saoudrizwan.claude-dev\\settings\\cline_mcp_settings.json
|
|
364
|
+
|
|
365
|
+
macOS (Cline in VS Code):
|
|
366
|
+
~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json
|
|
367
|
+
|
|
368
|
+
Linux (Cline in VS Code):
|
|
369
|
+
~/.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json
|
|
370
|
+
|
|
371
|
+
Cline Standalone:
|
|
372
|
+
~/.cline/mcp_settings.json
|
|
373
|
+
|
|
374
|
+
Claude Code:
|
|
375
|
+
~/.claude/mcp_settings.json
|
|
376
|
+
|
|
377
|
+
2. Add the following configuration to the "mcpServers" section:
|
|
378
|
+
|
|
379
|
+
{
|
|
380
|
+
"mcpServers": {
|
|
381
|
+
"mfcli-mcp": {
|
|
382
|
+
"disabled": false,
|
|
383
|
+
"timeout": 60,
|
|
384
|
+
"type": "stdio",
|
|
385
|
+
"command": "mfcli-mcp"
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
3. Save the file and restart your editor.
|
|
391
|
+
|
|
392
|
+
4. The mfcli-mcp server should now be available in your AI assistant.
|
|
393
|
+
"""
|
|
394
|
+
return instructions
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from mfcli.utils.files import file_access_check
|
|
4
|
+
from mfcli.utils.logger import get_logger
|
|
5
|
+
|
|
6
|
+
logger = get_logger(__name__)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def run_migrations():
|
|
10
|
+
from alembic.config import Config
|
|
11
|
+
from alembic import command
|
|
12
|
+
|
|
13
|
+
config_file_path = Path(__file__).parent.parent / "alembic.ini"
|
|
14
|
+
if not file_access_check(config_file_path):
|
|
15
|
+
raise RuntimeError(f"Could not find Alembic config file path: {config_file_path}")
|
|
16
|
+
|
|
17
|
+
alembic_cfg = Config(config_file_path)
|
|
18
|
+
command.upgrade(alembic_cfg, "head")
|
mfcli/utils/orm.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from functools import lru_cache
|
|
2
|
+
|
|
3
|
+
from sqlalchemy import create_engine, event
|
|
4
|
+
from sqlalchemy.orm import sessionmaker, Session as dbSession
|
|
5
|
+
|
|
6
|
+
from mfcli.models.base import Base
|
|
7
|
+
from mfcli.models.bom import BOM
|
|
8
|
+
|
|
9
|
+
from mfcli.utils.config import get_config
|
|
10
|
+
from mfcli.utils.directory_manager import app_dirs
|
|
11
|
+
|
|
12
|
+
config = get_config()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@lru_cache
|
|
16
|
+
def get_db_url() -> str:
|
|
17
|
+
return f"sqlite:///{app_dirs.app_data_dir / "multifactor.db"}"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
engine = create_engine(
|
|
21
|
+
get_db_url(),
|
|
22
|
+
pool_pre_ping=True,
|
|
23
|
+
pool_recycle=1800,
|
|
24
|
+
pool_size=10,
|
|
25
|
+
max_overflow=20,
|
|
26
|
+
pool_timeout=30
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@event.listens_for(engine, "connect")
|
|
31
|
+
def enable_sqlite_fk(dbapi_conn, conn_record):
|
|
32
|
+
cursor = dbapi_conn.cursor()
|
|
33
|
+
cursor.execute("PRAGMA foreign_keys=ON;")
|
|
34
|
+
cursor.close()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
Session = sessionmaker(bind=engine)
|
|
38
|
+
session = dbSession(engine)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def create_orm():
|
|
42
|
+
engine.connect()
|
|
43
|
+
Base.metadata.create_all(engine)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import tempfile
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from uuid import uuid4
|
|
4
|
+
|
|
5
|
+
import pikepdf
|
|
6
|
+
from io import BytesIO
|
|
7
|
+
|
|
8
|
+
from mfcli.utils.logger import get_logger
|
|
9
|
+
|
|
10
|
+
logger = get_logger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class PDFSplitter:
|
|
14
|
+
def __init__(self, file_name: str, content: bytes):
|
|
15
|
+
self._name = file_name
|
|
16
|
+
self._content = content
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def _head_page_limit(total_pages: int) -> int:
|
|
20
|
+
if total_pages <= 30:
|
|
21
|
+
return 10
|
|
22
|
+
elif total_pages <= 100:
|
|
23
|
+
return 20
|
|
24
|
+
else:
|
|
25
|
+
return 30
|
|
26
|
+
|
|
27
|
+
def _open_pdf(self) -> pikepdf.Pdf:
|
|
28
|
+
return pikepdf.open(BytesIO(self._content))
|
|
29
|
+
|
|
30
|
+
def split_pdf_head(self) -> Path:
|
|
31
|
+
with self._open_pdf() as src:
|
|
32
|
+
total_pages = len(src.pages)
|
|
33
|
+
page_limit = self._head_page_limit(total_pages)
|
|
34
|
+
|
|
35
|
+
dst = pikepdf.Pdf.new()
|
|
36
|
+
dst.pages.extend(src.pages[:page_limit])
|
|
37
|
+
|
|
38
|
+
output_path = Path(tempfile.mktemp(suffix=".pdf"))
|
|
39
|
+
dst.save(output_path)
|
|
40
|
+
|
|
41
|
+
return output_path
|
|
42
|
+
|
|
43
|
+
def extract_range(
|
|
44
|
+
self,
|
|
45
|
+
start_page: int,
|
|
46
|
+
end_page: int,
|
|
47
|
+
output_folder: Path,
|
|
48
|
+
) -> Path:
|
|
49
|
+
logger.debug(f"Splitting PDF: {self._name}")
|
|
50
|
+
|
|
51
|
+
output_folder.mkdir(parents=True, exist_ok=True)
|
|
52
|
+
|
|
53
|
+
with self._open_pdf() as src:
|
|
54
|
+
dst = pikepdf.Pdf.new()
|
|
55
|
+
dst.pages.extend(src.pages[start_page:end_page + 1])
|
|
56
|
+
|
|
57
|
+
output_path = output_folder / f"{uuid4().hex}.pdf"
|
|
58
|
+
dst.save(output_path)
|
|
59
|
+
|
|
60
|
+
logger.debug(f"Output PDF part to: {output_path}")
|
|
61
|
+
logger.debug(f"PDF splitter finished: {self._name}")
|
|
62
|
+
|
|
63
|
+
return output_path
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pre-uninstall utility for mfcli.
|
|
3
|
+
This module provides cleanup functionality to ensure graceful uninstallation.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
import subprocess
|
|
9
|
+
import platform
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import List, Tuple
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_running_mfcli_processes() -> List[Tuple[int, str]]:
|
|
15
|
+
"""Find all running mfcli and mfcli-mcp processes."""
|
|
16
|
+
processes = []
|
|
17
|
+
system = platform.system()
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
if system == "Windows":
|
|
21
|
+
# Use tasklist to find processes
|
|
22
|
+
result = subprocess.run(
|
|
23
|
+
["tasklist", "/FI", "IMAGENAME eq python*", "/FO", "CSV", "/NH"],
|
|
24
|
+
capture_output=True,
|
|
25
|
+
text=True,
|
|
26
|
+
timeout=5
|
|
27
|
+
)
|
|
28
|
+
for line in result.stdout.strip().split('\n'):
|
|
29
|
+
if line:
|
|
30
|
+
parts = line.strip('"').split('","')
|
|
31
|
+
if len(parts) >= 2:
|
|
32
|
+
try:
|
|
33
|
+
pid = int(parts[1])
|
|
34
|
+
# Check if it's running mfcli-mcp
|
|
35
|
+
cmdline_result = subprocess.run(
|
|
36
|
+
["wmic", "process", "where", f"ProcessId={pid}", "get", "CommandLine", "/value"],
|
|
37
|
+
capture_output=True,
|
|
38
|
+
text=True,
|
|
39
|
+
timeout=5
|
|
40
|
+
)
|
|
41
|
+
if "mfcli-mcp" in cmdline_result.stdout or "mfcli.mcp.server" in cmdline_result.stdout:
|
|
42
|
+
processes.append((pid, "mfcli-mcp server"))
|
|
43
|
+
except (ValueError, subprocess.TimeoutExpired):
|
|
44
|
+
continue
|
|
45
|
+
else:
|
|
46
|
+
# Unix-like systems
|
|
47
|
+
result = subprocess.run(
|
|
48
|
+
["ps", "aux"],
|
|
49
|
+
capture_output=True,
|
|
50
|
+
text=True,
|
|
51
|
+
timeout=5
|
|
52
|
+
)
|
|
53
|
+
for line in result.stdout.split('\n'):
|
|
54
|
+
if "mfcli-mcp" in line or "mfcli.mcp.server" in line:
|
|
55
|
+
parts = line.split()
|
|
56
|
+
if len(parts) >= 2:
|
|
57
|
+
try:
|
|
58
|
+
pid = int(parts[1])
|
|
59
|
+
processes.append((pid, "mfcli-mcp server"))
|
|
60
|
+
except ValueError:
|
|
61
|
+
continue
|
|
62
|
+
except Exception as e:
|
|
63
|
+
print(f"Warning: Could not check for running processes: {e}", file=sys.stderr)
|
|
64
|
+
|
|
65
|
+
return processes
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def check_mcp_server_status():
|
|
69
|
+
"""Check if MCP server is running and provide guidance."""
|
|
70
|
+
print("\n" + "="*70)
|
|
71
|
+
print(" MFCLI PRE-UNINSTALL CHECK")
|
|
72
|
+
print("="*70 + "\n")
|
|
73
|
+
|
|
74
|
+
processes = get_running_mfcli_processes()
|
|
75
|
+
|
|
76
|
+
if processes:
|
|
77
|
+
print("⚠️ WARNING: mfcli MCP server processes are currently running!\n")
|
|
78
|
+
print("Running processes:")
|
|
79
|
+
for pid, name in processes:
|
|
80
|
+
print(f" • PID {pid}: {name}")
|
|
81
|
+
|
|
82
|
+
print("\nBefore uninstalling, you should:")
|
|
83
|
+
print(" 1. Stop/restart your IDE (VS Code, Cline, Claude Code)")
|
|
84
|
+
print(" 2. Close any applications using mfcli-mcp")
|
|
85
|
+
print(" 3. Wait a few seconds for processes to fully terminate")
|
|
86
|
+
|
|
87
|
+
if platform.system() == "Windows":
|
|
88
|
+
print("\nTo manually stop these processes, run:")
|
|
89
|
+
for pid, _ in processes:
|
|
90
|
+
print(f" taskkill /F /PID {pid}")
|
|
91
|
+
else:
|
|
92
|
+
print("\nTo manually stop these processes, run:")
|
|
93
|
+
for pid, _ in processes:
|
|
94
|
+
print(f" kill {pid}")
|
|
95
|
+
|
|
96
|
+
return False
|
|
97
|
+
else:
|
|
98
|
+
print("✓ No running mfcli-mcp processes detected\n")
|
|
99
|
+
return True
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def cleanup_file_handles():
|
|
103
|
+
"""Attempt to close any ChromaDB or database connections."""
|
|
104
|
+
try:
|
|
105
|
+
# Force garbage collection to close any lingering connections
|
|
106
|
+
import gc
|
|
107
|
+
gc.collect()
|
|
108
|
+
|
|
109
|
+
# Try to close ChromaDB connections if imported
|
|
110
|
+
if 'chromadb' in sys.modules:
|
|
111
|
+
print("Closing ChromaDB connections...")
|
|
112
|
+
|
|
113
|
+
print("✓ Cleanup completed\n")
|
|
114
|
+
except Exception as e:
|
|
115
|
+
print(f"Warning during cleanup: {e}\n")
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def print_uninstall_instructions():
|
|
119
|
+
"""Print step-by-step uninstall instructions."""
|
|
120
|
+
print("\n" + "="*70)
|
|
121
|
+
print(" UNINSTALL INSTRUCTIONS")
|
|
122
|
+
print("="*70 + "\n")
|
|
123
|
+
|
|
124
|
+
print("To safely uninstall mfcli:\n")
|
|
125
|
+
|
|
126
|
+
if platform.system() == "Windows":
|
|
127
|
+
print(" 1. Close VS Code, Cline, or any IDE using mfcli-mcp")
|
|
128
|
+
print(" 2. Wait 5-10 seconds for processes to fully stop")
|
|
129
|
+
print(" 3. Run: pipx uninstall mfcli")
|
|
130
|
+
print("\nIf you still get permission errors:")
|
|
131
|
+
print(" • Run PowerShell as Administrator")
|
|
132
|
+
print(" • Or use: .\\uninstall.ps1")
|
|
133
|
+
print(" • Or manually delete: %USERPROFILE%\\pipx\\venvs\\mfcli")
|
|
134
|
+
else:
|
|
135
|
+
print(" 1. Close your IDE or any applications using mfcli-mcp")
|
|
136
|
+
print(" 2. Wait a few seconds for processes to fully stop")
|
|
137
|
+
print(" 3. Run: pipx uninstall mfcli")
|
|
138
|
+
print("\nIf you still get permission errors:")
|
|
139
|
+
print(" • Use: ./uninstall.sh")
|
|
140
|
+
print(" • Or manually delete: ~/.local/pipx/venvs/mfcli")
|
|
141
|
+
|
|
142
|
+
print("\nNote: Your configuration and data at ~/Multifactor will NOT be deleted.")
|
|
143
|
+
print("To remove that as well, manually delete the Multifactor directory.")
|
|
144
|
+
print()
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def run_pre_uninstall_check():
|
|
148
|
+
"""Main pre-uninstall check function."""
|
|
149
|
+
cleanup_file_handles()
|
|
150
|
+
server_clear = check_mcp_server_status()
|
|
151
|
+
print_uninstall_instructions()
|
|
152
|
+
|
|
153
|
+
if not server_clear:
|
|
154
|
+
print("\n⚠️ WARNING: Active processes detected. Please stop them before uninstalling.")
|
|
155
|
+
return 1
|
|
156
|
+
else:
|
|
157
|
+
print("\n✓ System is ready for uninstallation.")
|
|
158
|
+
return 0
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def main():
|
|
162
|
+
"""Entry point for pre-uninstall command."""
|
|
163
|
+
sys.exit(run_pre_uninstall_check())
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
if __name__ == "__main__":
|
|
167
|
+
main()
|