first commit
This commit is contained in:
389
mcp_config.py
Normal file
389
mcp_config.py
Normal file
@@ -0,0 +1,389 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
MCP Server Configuration and Management
|
||||
|
||||
This module provides utilities to configure and manage the MCP server
|
||||
for LLM_Train models.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, messagebox, filedialog
|
||||
from typing import Dict, Any, Optional
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class MCPServerConfig:
|
||||
"""Configuration management for the MCP server."""
|
||||
|
||||
def __init__(self, config_file: str = "mcp_config.json"):
|
||||
self.config_file = config_file
|
||||
self.default_config = {
|
||||
"server": {
|
||||
"name": "llm-train-models",
|
||||
"version": "1.0.0",
|
||||
"description": "Multi-model MCP server for LLM_Train",
|
||||
"host": "localhost",
|
||||
"port": 8000,
|
||||
"auto_start": False
|
||||
},
|
||||
"models": {
|
||||
"cache_size": 3,
|
||||
"default_context": 4096,
|
||||
"default_gpu_layers": 0
|
||||
},
|
||||
"logging": {
|
||||
"level": "INFO",
|
||||
"file": "mcp_server.log"
|
||||
}
|
||||
}
|
||||
self.config = self.load_config()
|
||||
|
||||
def load_config(self) -> Dict[str, Any]:
|
||||
"""Load configuration from file."""
|
||||
try:
|
||||
if os.path.exists(self.config_file):
|
||||
with open(self.config_file, 'r') as f:
|
||||
loaded = json.load(f)
|
||||
# Merge with defaults
|
||||
return self._merge_config(self.default_config, loaded)
|
||||
return self.default_config.copy()
|
||||
except Exception as e:
|
||||
print(f"Error loading MCP config: {e}")
|
||||
return self.default_config.copy()
|
||||
|
||||
def _merge_config(self, defaults: Dict, loaded: Dict) -> Dict:
|
||||
"""Merge loaded config with defaults."""
|
||||
result = defaults.copy()
|
||||
for key, value in loaded.items():
|
||||
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
|
||||
result[key] = self._merge_config(result[key], value)
|
||||
else:
|
||||
result[key] = value
|
||||
return result
|
||||
|
||||
def save_config(self) -> bool:
|
||||
"""Save configuration to file."""
|
||||
try:
|
||||
with open(self.config_file, 'w') as f:
|
||||
json.dump(self.config, f, indent=2)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error saving MCP config: {e}")
|
||||
return False
|
||||
|
||||
def get(self, path: str, default: Any = None) -> Any:
|
||||
"""Get config value using dot notation."""
|
||||
keys = path.split('.')
|
||||
value = self.config
|
||||
for key in keys:
|
||||
if isinstance(value, dict) and key in value:
|
||||
value = value[key]
|
||||
else:
|
||||
return default
|
||||
return value
|
||||
|
||||
def set(self, path: str, value: Any):
|
||||
"""Set config value using dot notation."""
|
||||
keys = path.split('.')
|
||||
target = self.config
|
||||
for key in keys[:-1]:
|
||||
if key not in target:
|
||||
target[key] = {}
|
||||
target = target[key]
|
||||
target[keys[-1]] = value
|
||||
|
||||
def generate_claude_config(self) -> Dict[str, Any]:
|
||||
"""Generate Claude Desktop MCP configuration."""
|
||||
script_path = os.path.abspath("mcp_server.py")
|
||||
python_path = sys.executable
|
||||
|
||||
return {
|
||||
"mcpServers": {
|
||||
"llm-train-models": {
|
||||
"command": python_path,
|
||||
"args": [script_path],
|
||||
"env": {
|
||||
"PYTHONPATH": os.getcwd()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class MCPConfigGUI:
|
||||
"""GUI for configuring the MCP server."""
|
||||
|
||||
def __init__(self, parent: tk.Tk):
|
||||
self.parent = parent
|
||||
self.config = MCPServerConfig()
|
||||
self.dialog = tk.Toplevel(parent)
|
||||
self.dialog.title("MCP Server Configuration")
|
||||
self.dialog.geometry("600x500")
|
||||
self.dialog.transient(parent)
|
||||
self.dialog.grab_set()
|
||||
|
||||
self._build_ui()
|
||||
self._load_values()
|
||||
|
||||
def _build_ui(self):
|
||||
"""Build the configuration UI."""
|
||||
notebook = ttk.Notebook(self.dialog)
|
||||
notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
||||
|
||||
# Server Settings Tab
|
||||
server_frame = ttk.Frame(notebook)
|
||||
notebook.add(server_frame, text="Server")
|
||||
self._build_server_tab(server_frame)
|
||||
|
||||
# Model Settings Tab
|
||||
model_frame = ttk.Frame(notebook)
|
||||
notebook.add(model_frame, text="Models")
|
||||
self._build_models_tab(model_frame)
|
||||
|
||||
# Claude Integration Tab
|
||||
claude_frame = ttk.Frame(notebook)
|
||||
notebook.add(claude_frame, text="Claude Integration")
|
||||
self._build_claude_tab(claude_frame)
|
||||
|
||||
# Buttons
|
||||
button_frame = ttk.Frame(self.dialog)
|
||||
button_frame.pack(fill=tk.X, padx=10, pady=(0, 10))
|
||||
|
||||
ttk.Button(button_frame, text="Save", command=self._save_config).pack(side=tk.RIGHT, padx=5)
|
||||
ttk.Button(button_frame, text="Cancel", command=self.dialog.destroy).pack(side=tk.RIGHT)
|
||||
ttk.Button(button_frame, text="Test Server", command=self._test_server).pack(side=tk.LEFT)
|
||||
|
||||
def _build_server_tab(self, parent: ttk.Frame):
|
||||
"""Build server settings tab."""
|
||||
frame = ttk.LabelFrame(parent, text="Server Configuration", padding=10)
|
||||
frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
||||
|
||||
# Server name
|
||||
ttk.Label(frame, text="Server Name:").grid(row=0, column=0, sticky=tk.W, pady=5)
|
||||
self.server_name_var = tk.StringVar()
|
||||
ttk.Entry(frame, textvariable=self.server_name_var, width=30).grid(row=0, column=1, sticky=tk.W, pady=5)
|
||||
|
||||
# Auto start
|
||||
self.auto_start_var = tk.BooleanVar()
|
||||
ttk.Checkbutton(frame, text="Auto-start server with application",
|
||||
variable=self.auto_start_var).grid(row=1, column=0, columnspan=2, sticky=tk.W, pady=10)
|
||||
|
||||
# Logging level
|
||||
ttk.Label(frame, text="Logging Level:").grid(row=2, column=0, sticky=tk.W, pady=5)
|
||||
self.log_level_var = tk.StringVar()
|
||||
ttk.Combobox(frame, textvariable=self.log_level_var,
|
||||
values=["DEBUG", "INFO", "WARNING", "ERROR"],
|
||||
state="readonly", width=15).grid(row=2, column=1, sticky=tk.W, pady=5)
|
||||
|
||||
# Log file
|
||||
ttk.Label(frame, text="Log File:").grid(row=3, column=0, sticky=tk.W, pady=5)
|
||||
log_frame = ttk.Frame(frame)
|
||||
log_frame.grid(row=3, column=1, sticky=tk.W, pady=5)
|
||||
|
||||
self.log_file_var = tk.StringVar()
|
||||
ttk.Entry(log_frame, textvariable=self.log_file_var, width=25).pack(side=tk.LEFT)
|
||||
ttk.Button(log_frame, text="Browse",
|
||||
command=self._browse_log_file).pack(side=tk.LEFT, padx=5)
|
||||
|
||||
def _build_models_tab(self, parent: ttk.Frame):
|
||||
"""Build model settings tab."""
|
||||
frame = ttk.LabelFrame(parent, text="Model Configuration", padding=10)
|
||||
frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
||||
|
||||
# Cache size
|
||||
ttk.Label(frame, text="Model Cache Size:").grid(row=0, column=0, sticky=tk.W, pady=5)
|
||||
self.cache_size_var = tk.IntVar()
|
||||
cache_spin = tk.Spinbox(frame, from_=1, to=10, textvariable=self.cache_size_var, width=10)
|
||||
cache_spin.grid(row=0, column=1, sticky=tk.W, pady=5)
|
||||
ttk.Label(frame, text="models").grid(row=0, column=2, sticky=tk.W, padx=5)
|
||||
|
||||
# Default context
|
||||
ttk.Label(frame, text="Default Context Size:").grid(row=1, column=0, sticky=tk.W, pady=5)
|
||||
self.default_ctx_var = tk.IntVar()
|
||||
ctx_spin = tk.Spinbox(frame, from_=512, to=32768, increment=512,
|
||||
textvariable=self.default_ctx_var, width=10)
|
||||
ctx_spin.grid(row=1, column=1, sticky=tk.W, pady=5)
|
||||
ttk.Label(frame, text="tokens").grid(row=1, column=2, sticky=tk.W, padx=5)
|
||||
|
||||
# Default GPU layers
|
||||
ttk.Label(frame, text="Default GPU Layers:").grid(row=2, column=0, sticky=tk.W, pady=5)
|
||||
self.default_gpu_var = tk.IntVar()
|
||||
gpu_spin = tk.Spinbox(frame, from_=0, to=100, textvariable=self.default_gpu_var, width=10)
|
||||
gpu_spin.grid(row=2, column=1, sticky=tk.W, pady=5)
|
||||
ttk.Label(frame, text="layers").grid(row=2, column=2, sticky=tk.W, padx=5)
|
||||
|
||||
# Info
|
||||
info_text = ("Cache size determines how many models can be loaded simultaneously.\\n"
|
||||
"Default settings are used when loading models via MCP.")
|
||||
ttk.Label(frame, text=info_text, foreground="gray").grid(row=3, column=0, columnspan=3, pady=10)
|
||||
|
||||
def _build_claude_tab(self, parent: ttk.Frame):
|
||||
"""Build Claude integration tab."""
|
||||
frame = ttk.LabelFrame(parent, text="Claude Desktop Integration", padding=10)
|
||||
frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
||||
|
||||
# Instructions - wrapped properly
|
||||
instructions_frame = ttk.Frame(frame)
|
||||
instructions_frame.pack(fill=tk.X, pady=(0, 10))
|
||||
|
||||
# Create a text widget for instructions with proper wrapping
|
||||
instructions_text = tk.Text(instructions_frame, height=8, wrap=tk.WORD,
|
||||
background="#f0f0f0", relief=tk.FLAT,
|
||||
borderwidth=0, state=tk.DISABLED)
|
||||
instructions_text.pack(fill=tk.X)
|
||||
|
||||
# Add properly formatted instructions
|
||||
instructions_content = """To use this MCP server with Claude Desktop, follow these steps:
|
||||
|
||||
1. Locate your Claude Desktop configuration file:
|
||||
• Windows: %APPDATA%\\Claude\\claude_desktop_config.json
|
||||
• macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
|
||||
• Linux: ~/.config/Claude/claude_desktop_config.json
|
||||
|
||||
2. Open the configuration file in a text editor (create it if it doesn't exist)
|
||||
|
||||
3. Add the JSON configuration shown below to the file
|
||||
|
||||
4. Save the file and restart Claude Desktop
|
||||
|
||||
5. The LLM_Train models server will be available in Claude Desktop"""
|
||||
|
||||
instructions_text.config(state=tk.NORMAL)
|
||||
instructions_text.insert(1.0, instructions_content)
|
||||
instructions_text.config(state=tk.DISABLED)
|
||||
|
||||
# Config display
|
||||
ttk.Label(frame, text="Configuration JSON:", font=("TkDefaultFont", 10, "bold")).pack(anchor=tk.W, pady=(10,5))
|
||||
|
||||
# Create text frame with proper layout
|
||||
text_frame = ttk.Frame(frame)
|
||||
text_frame.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
# Make the config text read-only and styled
|
||||
self.config_text = tk.Text(text_frame, height=12, width=70, wrap=tk.WORD,
|
||||
state=tk.DISABLED, background="#f8f8f8",
|
||||
font=("Courier", 9))
|
||||
config_scroll = ttk.Scrollbar(text_frame, orient="vertical", command=self.config_text.yview)
|
||||
self.config_text.configure(yscrollcommand=config_scroll.set)
|
||||
|
||||
self.config_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||||
config_scroll.pack(side=tk.RIGHT, fill=tk.Y)
|
||||
|
||||
# Buttons
|
||||
button_frame = ttk.Frame(frame)
|
||||
button_frame.pack(fill=tk.X, pady=10)
|
||||
|
||||
ttk.Button(button_frame, text="Copy to Clipboard",
|
||||
command=self._copy_config).pack(side=tk.LEFT, padx=5)
|
||||
ttk.Button(button_frame, text="Save to File",
|
||||
command=self._save_claude_config).pack(side=tk.LEFT, padx=5)
|
||||
ttk.Button(button_frame, text="Refresh",
|
||||
command=self._update_claude_config).pack(side=tk.LEFT, padx=5)
|
||||
|
||||
self._update_claude_config()
|
||||
|
||||
def _load_values(self):
|
||||
"""Load configuration values into UI."""
|
||||
self.server_name_var.set(self.config.get('server.name', 'llm-train-models'))
|
||||
self.auto_start_var.set(self.config.get('server.auto_start', False))
|
||||
self.log_level_var.set(self.config.get('logging.level', 'INFO'))
|
||||
self.log_file_var.set(self.config.get('logging.file', 'mcp_server.log'))
|
||||
|
||||
self.cache_size_var.set(self.config.get('models.cache_size', 3))
|
||||
self.default_ctx_var.set(self.config.get('models.default_context', 4096))
|
||||
self.default_gpu_var.set(self.config.get('models.default_gpu_layers', 0))
|
||||
|
||||
def _save_config(self):
|
||||
"""Save configuration."""
|
||||
self.config.set('server.name', self.server_name_var.get())
|
||||
self.config.set('server.auto_start', self.auto_start_var.get())
|
||||
self.config.set('logging.level', self.log_level_var.get())
|
||||
self.config.set('logging.file', self.log_file_var.get())
|
||||
|
||||
self.config.set('models.cache_size', self.cache_size_var.get())
|
||||
self.config.set('models.default_context', self.default_ctx_var.get())
|
||||
self.config.set('models.default_gpu_layers', self.default_gpu_var.get())
|
||||
|
||||
if self.config.save_config():
|
||||
messagebox.showinfo("Success", "Configuration saved successfully!")
|
||||
self.dialog.destroy()
|
||||
else:
|
||||
messagebox.showerror("Error", "Failed to save configuration")
|
||||
|
||||
def _browse_log_file(self):
|
||||
"""Browse for log file location."""
|
||||
filename = filedialog.asksaveasfilename(
|
||||
title="Select Log File",
|
||||
defaultextension=".log",
|
||||
filetypes=[("Log files", "*.log"), ("All files", "*.*")]
|
||||
)
|
||||
if filename:
|
||||
self.log_file_var.set(filename)
|
||||
|
||||
def _update_claude_config(self):
|
||||
"""Update the Claude configuration display."""
|
||||
config = self.config.generate_claude_config()
|
||||
config_json = json.dumps(config, indent=2)
|
||||
|
||||
self.config_text.delete(1.0, tk.END)
|
||||
self.config_text.insert(1.0, config_json)
|
||||
|
||||
def _copy_config(self):
|
||||
"""Copy configuration to clipboard."""
|
||||
config_text = self.config_text.get(1.0, tk.END)
|
||||
self.dialog.clipboard_clear()
|
||||
self.dialog.clipboard_append(config_text)
|
||||
messagebox.showinfo("Copied", "Configuration copied to clipboard!")
|
||||
|
||||
def _save_claude_config(self):
|
||||
"""Save Claude configuration to file."""
|
||||
filename = filedialog.asksaveasfilename(
|
||||
title="Save Claude Config",
|
||||
defaultextension=".json",
|
||||
filetypes=[("JSON files", "*.json"), ("All files", "*.*")],
|
||||
initialvalue="claude_desktop_config.json"
|
||||
)
|
||||
|
||||
if filename:
|
||||
try:
|
||||
config = self.config.generate_claude_config()
|
||||
with open(filename, 'w') as f:
|
||||
json.dump(config, f, indent=2)
|
||||
messagebox.showinfo("Saved", f"Configuration saved to {filename}")
|
||||
except Exception as e:
|
||||
messagebox.showerror("Error", f"Failed to save file: {e}")
|
||||
|
||||
def _test_server(self):
|
||||
"""Test the MCP server."""
|
||||
try:
|
||||
# Try to run the server with --help to test if it's working
|
||||
result = subprocess.run([
|
||||
sys.executable, "mcp_server.py", "--help"
|
||||
], capture_output=True, text=True, timeout=10)
|
||||
|
||||
if result.returncode == 0:
|
||||
messagebox.showinfo("Test Successful",
|
||||
"MCP server appears to be working correctly!")
|
||||
else:
|
||||
messagebox.showerror("Test Failed",
|
||||
f"Server test failed:\\n{result.stderr}")
|
||||
except Exception as e:
|
||||
messagebox.showerror("Test Error", f"Failed to test server: {e}")
|
||||
|
||||
|
||||
def open_mcp_config(parent: tk.Tk):
|
||||
"""Open the MCP configuration dialog."""
|
||||
MCPConfigGUI(parent)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Test the configuration
|
||||
root = tk.Tk()
|
||||
root.withdraw() # Hide main window
|
||||
|
||||
app = MCPConfigGUI(root)
|
||||
root.mainloop()
|
||||
Reference in New Issue
Block a user