claudepod 1.0.2 → 1.1.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.
@@ -0,0 +1,31 @@
1
+ #!/bin/bash
2
+ # Sanitize system prompt markdown for Claude CLI usage
3
+ # This script converts a markdown file to a format suitable for --append-system-prompt
4
+
5
+ set -euo pipefail
6
+
7
+ if [ $# -eq 0 ]; then
8
+ echo "Usage: $0 <markdown-file>"
9
+ exit 1
10
+ fi
11
+
12
+ MARKDOWN_FILE="$1"
13
+
14
+ if [ ! -f "$MARKDOWN_FILE" ]; then
15
+ echo "Error: File '$MARKDOWN_FILE' not found"
16
+ exit 1
17
+ fi
18
+
19
+ # Read the markdown file and perform sanitization
20
+ cat "$MARKDOWN_FILE" | \
21
+ # Remove leading/trailing whitespace from each line
22
+ sed 's/^[[:space:]]*//; s/[[:space:]]*$//' | \
23
+ # Escape double quotes
24
+ sed 's/"/\\"/g' | \
25
+ # Escape backslashes
26
+ sed 's/\\/\\\\/g' | \
27
+ # Join all lines with literal \n
28
+ tr '\n' '\001' | \
29
+ sed 's/\001/\\n/g' | \
30
+ # Remove any trailing \n
31
+ sed 's/\\n$//'
@@ -0,0 +1,210 @@
1
+ #!/bin/bash
2
+
3
+ # Claude Core Configuration Module
4
+ # Handles Claude directory creation and core configuration files
5
+
6
+ setup_claude_core_config() {
7
+ echo "🔧 Setting up Claude core configuration..."
8
+
9
+ # Claude Code uses multiple possible config locations
10
+ local claude_dirs=(
11
+ "/home/node/.claude"
12
+ "/home/node/.config/claude"
13
+ )
14
+
15
+ for dir in "${claude_dirs[@]}"; do
16
+ if [ ! -d "$dir" ]; then
17
+ mkdir -p "$dir"
18
+ echo "📁 Created configuration directory: $dir"
19
+ fi
20
+ # Set proper permissions
21
+ chown -R node:node "$dir"
22
+ chmod -R 700 "$dir"
23
+ done
24
+
25
+ # Copy optimized settings.json from devcontainer config
26
+ setup_claude_settings
27
+
28
+ # Copy MCP configuration
29
+ setup_claude_mcp_config
30
+
31
+ # Copy system prompt
32
+ setup_claude_system_prompt
33
+
34
+ # Copy output styles
35
+ setup_claude_output_styles
36
+
37
+ echo "📋 Claude core configuration processing completed"
38
+ }
39
+
40
+ setup_claude_settings() {
41
+ local settings_source="/workspace/.devcontainer/config/claude/settings.json"
42
+ local settings_target_home="/home/node/.claude/settings.json"
43
+ local settings_target_workspace="/workspace/.claude/settings.json"
44
+
45
+ if [ ! -f "$settings_source" ]; then
46
+ echo "⚠️ Claude settings template not found, using defaults"
47
+ return 1
48
+ fi
49
+
50
+ # Check if we should override existing files or only create if missing
51
+ local copy_settings=false
52
+ if [ "${OVERRIDE_CLAUDE_SETTINGS:-false}" = "true" ]; then
53
+ echo "📝 OVERRIDE_CLAUDE_SETTINGS=true: Forcing overwrite of Claude settings"
54
+ copy_settings=true
55
+ elif [ ! -f "$settings_target_home" ] && [ ! -f "$settings_target_workspace" ]; then
56
+ echo "📝 Creating Claude settings (no existing files found)"
57
+ copy_settings=true
58
+ else
59
+ echo "📁 Preserving existing Claude settings (set OVERRIDE_CLAUDE_SETTINGS=true to overwrite)"
60
+ fi
61
+
62
+ if [ "$copy_settings" = "true" ]; then
63
+ # Create backups of existing configurations
64
+ create_config_backup "$settings_target_home"
65
+ create_config_backup "$settings_target_workspace"
66
+
67
+ # Copy to home directory (will be bind-mounted to workspace)
68
+ cp "$settings_source" "$settings_target_home"
69
+ chown node:node "$settings_target_home"
70
+ chmod 600 "$settings_target_home"
71
+
72
+ # Also copy directly to workspace for redundancy
73
+ cp "$settings_source" "$settings_target_workspace"
74
+ chown node:node "$settings_target_workspace"
75
+ chmod 600 "$settings_target_workspace"
76
+
77
+ echo "📋 Claude settings configured"
78
+ fi
79
+ }
80
+
81
+ setup_claude_mcp_config() {
82
+ local mcp_source="/workspace/.devcontainer/config/claude/mcp.json"
83
+ local mcp_target_home="/home/node/.claude/mcp.json"
84
+ local mcp_target_workspace="/workspace/.claude/mcp.json"
85
+
86
+ if [ ! -f "$mcp_source" ]; then
87
+ echo "📝 MCP configuration template not found, will be generated in post-start phase"
88
+ return 0
89
+ fi
90
+
91
+ local copy_mcp=false
92
+ if [ "${OVERRIDE_CLAUDE_MCP:-false}" = "true" ]; then
93
+ echo "📝 OVERRIDE_CLAUDE_MCP=true: Forcing overwrite of Claude MCP configuration"
94
+ copy_mcp=true
95
+ elif [ ! -f "$mcp_target_home" ] && [ ! -f "$mcp_target_workspace" ]; then
96
+ echo "📝 Creating Claude MCP configuration (no existing files found)"
97
+ copy_mcp=true
98
+ else
99
+ echo "📁 Preserving existing Claude MCP configuration (set OVERRIDE_CLAUDE_MCP=true to overwrite)"
100
+ fi
101
+
102
+ if [ "$copy_mcp" = "true" ]; then
103
+ # Create backups of existing MCP configurations
104
+ create_config_backup "$mcp_target_home"
105
+ create_config_backup "$mcp_target_workspace"
106
+
107
+ cp "$mcp_source" "$mcp_target_home"
108
+ cp "$mcp_source" "$mcp_target_workspace"
109
+ chown node:node "$mcp_target_home" "$mcp_target_workspace"
110
+ chmod 600 "$mcp_target_home" "$mcp_target_workspace"
111
+
112
+ echo "📋 Claude MCP configuration set up"
113
+ fi
114
+ }
115
+
116
+ setup_claude_system_prompt() {
117
+ local prompt_source="/workspace/.devcontainer/config/claude/system-prompt.md"
118
+ local prompt_target_home="/home/node/.claude/system-prompt.md"
119
+ local prompt_target_workspace="/workspace/.claude/system-prompt.md"
120
+
121
+ if [ ! -f "$prompt_source" ]; then
122
+ echo "📝 System prompt template not found, skipping"
123
+ return 0
124
+ fi
125
+
126
+ local copy_prompt=false
127
+ if [ "${OVERRIDE_CLAUDE_SYSTEM_PROMPT:-false}" = "true" ]; then
128
+ echo "📝 OVERRIDE_CLAUDE_SYSTEM_PROMPT=true: Forcing overwrite of Claude system prompt"
129
+ copy_prompt=true
130
+ elif [ ! -f "$prompt_target_home" ] && [ ! -f "$prompt_target_workspace" ]; then
131
+ echo "📝 Creating Claude system prompt (no existing files found)"
132
+ copy_prompt=true
133
+ else
134
+ echo "📁 Preserving existing Claude system prompt (set OVERRIDE_CLAUDE_SYSTEM_PROMPT=true to overwrite)"
135
+ fi
136
+
137
+ if [ "$copy_prompt" = "true" ]; then
138
+ # Create backups of existing system prompt configurations
139
+ create_config_backup "$prompt_target_home"
140
+ create_config_backup "$prompt_target_workspace"
141
+
142
+ cp "$prompt_source" "$prompt_target_home"
143
+ cp "$prompt_source" "$prompt_target_workspace"
144
+ chown node:node "$prompt_target_home" "$prompt_target_workspace"
145
+ chmod 600 "$prompt_target_home" "$prompt_target_workspace"
146
+
147
+ echo "📋 Claude system prompt configured"
148
+ fi
149
+ }
150
+
151
+ setup_claude_output_styles() {
152
+ local styles_source="/workspace/.devcontainer/config/claude/output-styles"
153
+ local styles_target_home="/home/node/.claude/output-styles"
154
+ local styles_target_workspace="/workspace/.claude/output-styles"
155
+
156
+ if [ ! -d "$styles_source" ]; then
157
+ echo "📝 Output styles directory not found, skipping"
158
+ return 0
159
+ fi
160
+
161
+ local copy_styles=false
162
+ if [ "${OVERRIDE_CLAUDE_OUTPUT_STYLES:-false}" = "true" ]; then
163
+ echo "📝 OVERRIDE_CLAUDE_OUTPUT_STYLES=true: Forcing overwrite of Claude output styles"
164
+ copy_styles=true
165
+ elif [ ! -d "$styles_target_home" ] && [ ! -d "$styles_target_workspace" ]; then
166
+ echo "📝 Creating Claude output styles (no existing directories found)"
167
+ copy_styles=true
168
+ else
169
+ echo "📁 Preserving existing Claude output styles (set OVERRIDE_CLAUDE_OUTPUT_STYLES=true to overwrite)"
170
+ fi
171
+
172
+ if [ "$copy_styles" = "true" ]; then
173
+ # Create backups of existing output styles directories
174
+ if [ -d "$styles_target_home" ]; then
175
+ local backup_dir="/workspace/.devcontainer/config/backups"
176
+ mkdir -p "$backup_dir"
177
+ local timestamp=$(date +"%Y%m%d_%H%M%S")
178
+ local backup_home="${backup_dir}/home_node_.claude_output-styles.${timestamp}.backup"
179
+ cp -r "$styles_target_home" "$backup_home"
180
+ chown -R node:node "$backup_home"
181
+ chmod -R 600 "$backup_home"
182
+ echo "📦 Created backup: $backup_home"
183
+ fi
184
+
185
+ if [ -d "$styles_target_workspace" ]; then
186
+ local backup_dir="/workspace/.devcontainer/config/backups"
187
+ mkdir -p "$backup_dir"
188
+ local timestamp=$(date +"%Y%m%d_%H%M%S")
189
+ local backup_workspace="${backup_dir}/workspace_.claude_output-styles.${timestamp}.backup"
190
+ cp -r "$styles_target_workspace" "$backup_workspace"
191
+ chown -R node:node "$backup_workspace"
192
+ chmod -R 600 "$backup_workspace"
193
+ echo "📦 Created backup: $backup_workspace"
194
+ fi
195
+
196
+ # Copy to home directory (will be bind-mounted to workspace)
197
+ mkdir -p "$styles_target_home"
198
+ cp -r "$styles_source"/* "$styles_target_home"/
199
+ chown -R node:node "$styles_target_home"
200
+ chmod -R 600 "$styles_target_home"
201
+
202
+ # Also copy directly to workspace for redundancy
203
+ mkdir -p "$styles_target_workspace"
204
+ cp -r "$styles_source"/* "$styles_target_workspace"/
205
+ chown -R node:node "$styles_target_workspace"
206
+ chmod -R 600 "$styles_target_workspace"
207
+
208
+ echo "📋 Claude output styles configured"
209
+ fi
210
+ }
@@ -0,0 +1,143 @@
1
+ #!/bin/bash
2
+
3
+ # SearXNG MCP Enhanced Configuration Module
4
+ # Handles MCP SearXNG Enhanced installation and configuration setup
5
+
6
+ install_searxng_mcp() {
7
+ echo "🔧 Installing MCP SearXNG Enhanced..."
8
+
9
+ # Define installation paths
10
+ local mcp_install_dir="/usr/local/mcp-servers/searxng"
11
+ local temp_dir="/tmp/mcp-searxng-enhanced"
12
+ local repo_url="https://github.com/OvertliDS/mcp-searxng-enhanced"
13
+
14
+ # Create installation directory
15
+ sudo mkdir -p "$mcp_install_dir"
16
+
17
+ # Clone repository to temporary location
18
+ echo "📥 Cloning MCP SearXNG Enhanced repository..."
19
+ if [ -d "$temp_dir" ]; then
20
+ rm -rf "$temp_dir"
21
+ fi
22
+
23
+ git clone "$repo_url" "$temp_dir" 2>/dev/null || {
24
+ echo "❌ Failed to clone repository from $repo_url"
25
+ return 1
26
+ }
27
+
28
+ # Install Python dependencies using uv with the correct Python version
29
+ echo "📦 Installing Python dependencies..."
30
+ sudo /home/node/.local/bin/uv pip install --python /usr/local/python/current/bin/python3 --system -r "$temp_dir/requirements.txt" || {
31
+ echo "❌ Failed to install Python dependencies"
32
+ return 1
33
+ }
34
+
35
+ # Copy MCP server files
36
+ echo "📋 Copying MCP server files..."
37
+ sudo cp "$temp_dir/mcp_server.py" "$mcp_install_dir/"
38
+ sudo chmod +x "$mcp_install_dir/mcp_server.py"
39
+
40
+ # Copy additional files if they exist
41
+ if [ -f "$temp_dir/README.md" ]; then
42
+ sudo cp "$temp_dir/README.md" "$mcp_install_dir/"
43
+ fi
44
+
45
+ # Clean up temporary directory
46
+ rm -rf "$temp_dir"
47
+
48
+ echo "✅ MCP SearXNG Enhanced installed successfully"
49
+ }
50
+
51
+ setup_searxng_config() {
52
+ echo "🔧 Setting up SearXNG configuration..."
53
+
54
+ local config_source="/workspace/.devcontainer/config/searxng/ods_config.json"
55
+ local config_target="/home/node/.claude/ods_config.json"
56
+
57
+ if [ ! -f "$config_source" ]; then
58
+ echo "⚠️ SearXNG configuration template not found, using defaults"
59
+ return 1
60
+ fi
61
+
62
+ # Ensure Claude directory exists
63
+ mkdir -p "/home/node/.claude"
64
+
65
+ local copy_config=false
66
+ if [ "${OVERRIDE_SEARXNG_CONFIG:-false}" = "true" ]; then
67
+ echo "📝 OVERRIDE_SEARXNG_CONFIG=true: Forcing overwrite of SearXNG configuration"
68
+ copy_config=true
69
+ elif [ ! -f "$config_target" ]; then
70
+ echo "📝 Creating SearXNG configuration (no existing file found)"
71
+ copy_config=true
72
+ else
73
+ echo "📁 Preserving existing SearXNG configuration (set OVERRIDE_SEARXNG_CONFIG=true to overwrite)"
74
+ fi
75
+
76
+ if [ "$copy_config" = "true" ]; then
77
+ # Copy configuration file
78
+ cp "$config_source" "$config_target"
79
+ chown node:node "$config_target"
80
+ chmod 600 "$config_target"
81
+
82
+ echo "📋 Copied optimized SearXNG configuration"
83
+ fi
84
+
85
+ echo "✅ SearXNG configuration ready"
86
+ }
87
+
88
+ validate_searxng_connection() {
89
+ echo "🔍 Validating SearXNG connection..."
90
+
91
+ local searxng_url="${SEARXNG_ENGINE_API_BASE_URL:-http://localhost:8080/search}"
92
+
93
+ # Test connection to SearXNG instance
94
+ if command -v curl >/dev/null 2>&1; then
95
+ if curl -s --connect-timeout 10 --max-time 30 "${searxng_url%/search}" >/dev/null; then
96
+ echo "✅ SearXNG instance at $searxng_url is accessible"
97
+ return 0
98
+ else
99
+ echo "⚠️ Warning: SearXNG instance at $searxng_url may not be accessible"
100
+ echo " MCP server will still be configured but may fail during runtime"
101
+ return 1
102
+ fi
103
+ else
104
+ echo "⚠️ curl not found, skipping connection validation"
105
+ return 1
106
+ fi
107
+ }
108
+
109
+ setup_searxng_environment() {
110
+ echo "🌍 Setting up SearXNG environment variables..."
111
+
112
+ # Set default environment variables if not already set
113
+ export ODS_CONFIG_PATH="${ODS_CONFIG_PATH:-/home/node/.claude/ods_config.json}"
114
+ export SEARXNG_ENGINE_API_BASE_URL="${SEARXNG_ENGINE_API_BASE_URL:-http://localhost:8080/search}"
115
+ export DESIRED_TIMEZONE="${DESIRED_TIMEZONE:-America/New_York}"
116
+
117
+ echo "📋 Environment variables configured:"
118
+ echo " ODS_CONFIG_PATH=$ODS_CONFIG_PATH"
119
+ echo " SEARXNG_ENGINE_API_BASE_URL=$SEARXNG_ENGINE_API_BASE_URL"
120
+ echo " DESIRED_TIMEZONE=$DESIRED_TIMEZONE"
121
+
122
+ echo "✅ SearXNG environment ready"
123
+ }
124
+
125
+ # Main setup function
126
+ setup_searxng() {
127
+ if [ "${ENABLE_SEARXNG_ENHANCED_MCP:-true}" != "true" ]; then
128
+ echo "⏭️ SearXNG Enhanced MCP is disabled (set ENABLE_SEARXNG_ENHANCED_MCP=true to enable)"
129
+ return 0
130
+ fi
131
+
132
+ echo "🔄 Setting up MCP SearXNG Enhanced..."
133
+
134
+ # Run installation and configuration steps
135
+ install_searxng_mcp || return 1
136
+ setup_searxng_config || return 1
137
+ setup_searxng_environment
138
+ validate_searxng_connection || echo " (Connection validation failed, but continuing with setup)"
139
+
140
+ echo "✅ MCP SearXNG Enhanced setup complete"
141
+
142
+ return 0
143
+ }
@@ -0,0 +1,47 @@
1
+ #!/bin/bash
2
+
3
+ # Serena Configuration Module
4
+ # Handles Serena MCP server configuration setup
5
+
6
+ setup_serena_config() {
7
+ echo "🔧 Setting up Serena configuration..."
8
+
9
+ local serena_source="/workspace/.devcontainer/config/serena/serena_config.yml"
10
+ local serena_target_home="/home/node/.serena/serena_config.yml"
11
+ local serena_target_workspace="/workspace/.serena/serena_config.yml"
12
+
13
+ if [ ! -f "$serena_source" ]; then
14
+ echo "⚠️ Serena configuration template not found, using defaults"
15
+ return 1
16
+ fi
17
+
18
+ # Ensure Serena directories exist
19
+ mkdir -p "/home/node/.serena" "/workspace/.serena"
20
+
21
+ local copy_serena=false
22
+ if [ "${OVERRIDE_SERENA_CONFIG:-false}" = "true" ]; then
23
+ echo "📝 OVERRIDE_SERENA_CONFIG=true: Forcing overwrite of Serena configuration"
24
+ copy_serena=true
25
+ elif [ ! -f "$serena_target_home" ] && [ ! -f "$serena_target_workspace" ]; then
26
+ echo "📝 Creating Serena configuration (no existing files found)"
27
+ copy_serena=true
28
+ else
29
+ echo "📁 Preserving existing Serena configuration (set OVERRIDE_SERENA_CONFIG=true to overwrite)"
30
+ fi
31
+
32
+ if [ "$copy_serena" = "true" ]; then
33
+ # Copy to home directory (will be bind-mounted to workspace)
34
+ cp "$serena_source" "$serena_target_home"
35
+ chown node:node "$serena_target_home"
36
+ chmod 600 "$serena_target_home"
37
+
38
+ # Also copy directly to workspace for redundancy
39
+ cp "$serena_source" "$serena_target_workspace"
40
+ chown node:node "$serena_target_workspace"
41
+ chmod 600 "$serena_target_workspace"
42
+
43
+ echo "📋 Copied optimized Serena configuration"
44
+ fi
45
+
46
+ echo "✅ Serena configuration ready"
47
+ }
@@ -0,0 +1,41 @@
1
+ #!/bin/bash
2
+
3
+ # TaskMaster Configuration Module
4
+ # Handles TaskMaster AI configuration setup
5
+
6
+ setup_taskmaster_config() {
7
+ echo "🔧 Setting up TaskMaster configuration..."
8
+
9
+ local taskmaster_source="/workspace/.devcontainer/config/taskmaster/config.json"
10
+ local taskmaster_target_workspace="/workspace/.taskmaster/config.json"
11
+
12
+ if [ ! -f "$taskmaster_source" ]; then
13
+ echo "⚠️ TaskMaster configuration template not found, using defaults"
14
+ return 1
15
+ fi
16
+
17
+ # Ensure TaskMaster directory exists
18
+ mkdir -p "/workspace/.taskmaster"
19
+
20
+ local copy_taskmaster=false
21
+ if [ "${OVERRIDE_TASKMASTER_CONFIG:-false}" = "true" ]; then
22
+ echo "📝 OVERRIDE_TASKMASTER_CONFIG=true: Forcing overwrite of TaskMaster configuration"
23
+ copy_taskmaster=true
24
+ elif [ ! -f "$taskmaster_target_workspace" ]; then
25
+ echo "📝 Creating TaskMaster configuration (no existing files found)"
26
+ copy_taskmaster=true
27
+ else
28
+ echo "📁 Preserving existing TaskMaster configuration (set OVERRIDE_TASKMASTER_CONFIG=true to overwrite)"
29
+ fi
30
+
31
+ if [ "$copy_taskmaster" = "true" ]; then
32
+ # Copy to workspace
33
+ cp "$taskmaster_source" "$taskmaster_target_workspace"
34
+ chown node:node "$taskmaster_target_workspace"
35
+ chmod 600 "$taskmaster_target_workspace"
36
+
37
+ echo "📋 Copied optimized TaskMaster configuration"
38
+ fi
39
+
40
+ echo "✅ TaskMaster configuration ready"
41
+ }
@@ -0,0 +1,205 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Generate MCP configuration from template based on environment variables
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ // Load environment variables from .env file (industry standard approach)
10
+ const ENV_FILE_PATH = '/workspace/.devcontainer/.env';
11
+ try {
12
+ require('dotenv').config({ path: ENV_FILE_PATH, override: true });
13
+ console.log(`[MCP Config Generator] Loaded environment from ${ENV_FILE_PATH}`);
14
+ } catch (error) {
15
+ console.log(`[MCP Config Generator] dotenv not available, using process.env: ${error.message}`);
16
+ }
17
+
18
+ const CONFIG_DIR = '/workspace/.devcontainer/config/claude';
19
+ const TEMPLATE_PATH = path.join(CONFIG_DIR, 'mcp.json.template');
20
+ const OUTPUT_PATH = path.join(CONFIG_DIR, 'mcp.json');
21
+ const BACKUP_PATH = path.join(CONFIG_DIR, 'mcp.json.backup');
22
+
23
+ function log(message) {
24
+ console.log(`[MCP Config Generator] ${message}`);
25
+ }
26
+
27
+ function parseBoolean(value, defaultValue = false) {
28
+ if (!value) return defaultValue;
29
+ const lowerValue = value.toLowerCase().trim();
30
+ return lowerValue === 'true' || lowerValue === '1' || lowerValue === 'yes';
31
+ }
32
+
33
+ function expandEnvironmentVariables(text, env) {
34
+ return text.replace(/\$\{([^}:-]+)(:-([^}]*))?\}/g, (match, varName, _, defaultValue) => {
35
+ const envValue = env[varName];
36
+ if (envValue !== undefined) {
37
+ return envValue;
38
+ }
39
+ return defaultValue || '';
40
+ });
41
+ }
42
+
43
+ function hasRequiredApiKeys(requires, env) {
44
+ if (!requires || requires.length === 0) return true;
45
+
46
+ return requires.every(key => {
47
+ const value = env[key];
48
+ return value && value.trim() !== '';
49
+ });
50
+ }
51
+
52
+ function validateMcpConfiguration(config) {
53
+ try {
54
+ // Validate top-level structure
55
+ if (!config || typeof config !== 'object') {
56
+ log('Validation Error: Configuration must be an object');
57
+ return false;
58
+ }
59
+
60
+ if (!config.mcpServers || typeof config.mcpServers !== 'object') {
61
+ log('Validation Error: Configuration must have mcpServers object');
62
+ return false;
63
+ }
64
+
65
+ // Validate each server configuration
66
+ for (const [serverName, serverConfig] of Object.entries(config.mcpServers)) {
67
+ if (!validateServerConfig(serverName, serverConfig)) {
68
+ return false;
69
+ }
70
+ }
71
+
72
+ log('MCP configuration validation passed');
73
+ return true;
74
+ } catch (error) {
75
+ log(`Validation Error: ${error.message}`);
76
+ return false;
77
+ }
78
+ }
79
+
80
+ function validateServerConfig(serverName, config) {
81
+ // Check for required fields based on server type
82
+ if (config.command) {
83
+ // Command-based server
84
+ if (!Array.isArray(config.args)) {
85
+ log(`Validation Error: Server ${serverName} with command must have args array`);
86
+ return false;
87
+ }
88
+ } else if (config.type === 'http') {
89
+ // HTTP-based server
90
+ if (!config.url || typeof config.url !== 'string') {
91
+ log(`Validation Error: HTTP server ${serverName} must have valid url`);
92
+ return false;
93
+ }
94
+
95
+ // Validate URL format
96
+ try {
97
+ new URL(config.url);
98
+ } catch (urlError) {
99
+ log(`Validation Error: Server ${serverName} has invalid URL: ${config.url}`);
100
+ return false;
101
+ }
102
+ } else {
103
+ log(`Validation Error: Server ${serverName} must have either 'command' or 'type' field`);
104
+ return false;
105
+ }
106
+
107
+ return true;
108
+ }
109
+
110
+ function generateMcpConfig() {
111
+ try {
112
+ // Check if template exists
113
+ if (!fs.existsSync(TEMPLATE_PATH)) {
114
+ log(`Template file not found: ${TEMPLATE_PATH}`);
115
+ return false;
116
+ }
117
+
118
+ // Read template
119
+ const templateContent = fs.readFileSync(TEMPLATE_PATH, 'utf8');
120
+ let templateData;
121
+
122
+ try {
123
+ // First expand environment variables in the template
124
+ const expandedTemplate = expandEnvironmentVariables(templateContent, process.env);
125
+ templateData = JSON.parse(expandedTemplate);
126
+ } catch (error) {
127
+ log(`Failed to parse template: ${error.message}`);
128
+ return false;
129
+ }
130
+
131
+ // Create backup of existing config if it exists
132
+ if (fs.existsSync(OUTPUT_PATH)) {
133
+ fs.copyFileSync(OUTPUT_PATH, BACKUP_PATH);
134
+ log(`Created backup: ${BACKUP_PATH}`);
135
+ }
136
+
137
+ // Generate output configuration
138
+ const outputConfig = {
139
+ mcpServers: {}
140
+ };
141
+
142
+ let enabledCount = 0;
143
+ let disabledCount = 0;
144
+
145
+ // Process each server
146
+ for (const [serverName, serverData] of Object.entries(templateData.servers)) {
147
+ const enabled = parseBoolean(serverData.enabled, false);
148
+ const hasApiKeys = hasRequiredApiKeys(serverData.requires, process.env);
149
+
150
+ if (enabled && hasApiKeys) {
151
+ // Server is enabled and has required API keys
152
+ // Flatten the config structure - remove the nested "config" wrapper
153
+ outputConfig.mcpServers[serverName] = serverData.config;
154
+ enabledCount++;
155
+ log(`✓ Enabled: ${serverName}`);
156
+ } else {
157
+ // Server is disabled or missing API keys
158
+ disabledCount++;
159
+ if (!enabled) {
160
+ log(`✗ Disabled: ${serverName} (ENABLE_${serverName.toUpperCase().replace(/-/g, '_')}_MCP=false)`);
161
+ } else {
162
+ log(`✗ Disabled: ${serverName} (missing required API keys: ${serverData.requires?.join(', ')})`);
163
+ }
164
+ }
165
+ }
166
+
167
+ // Keep environment variables as references, don't expand them
168
+ const finalConfigText = JSON.stringify(outputConfig, null, 2);
169
+
170
+ // Write output
171
+ fs.writeFileSync(OUTPUT_PATH, finalConfigText);
172
+
173
+ // Basic validation - ensure output is valid JSON
174
+ try {
175
+ JSON.parse(finalConfigText);
176
+ } catch (parseError) {
177
+ log(`Error: Generated configuration is not valid JSON: ${parseError.message}`);
178
+ return false;
179
+ }
180
+
181
+ // Advanced validation - validate MCP configuration structure
182
+ if (!validateMcpConfiguration(outputConfig)) {
183
+ log(`Error: Generated MCP configuration failed validation`);
184
+ return false;
185
+ }
186
+
187
+ log(`Configuration generated successfully!`);
188
+ log(`Servers enabled: ${enabledCount}, disabled: ${disabledCount}`);
189
+ log(`Output: ${OUTPUT_PATH}`);
190
+
191
+ return true;
192
+
193
+ } catch (error) {
194
+ log(`Error generating configuration: ${error.message}`);
195
+ return false;
196
+ }
197
+ }
198
+
199
+ // Main execution
200
+ if (require.main === module) {
201
+ const success = generateMcpConfig();
202
+ process.exit(success ? 0 : 1);
203
+ }
204
+
205
+ module.exports = { generateMcpConfig };