feat: add support for multiple MCP client initialisation
This commit is contained in:
@@ -1,38 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# MCP Client Initializer
|
||||
# This initializer sets up the Model Context Protocol client service
|
||||
# for connecting to external MCP servers like Mintlify documentation.
|
||||
|
||||
# Initialize the MCP client service
|
||||
Rails.application.config.after_initialize do
|
||||
# Initialize the MCP client service singleton
|
||||
McpClientService.instance
|
||||
Rails.logger.info('[MCP] MCP Client Service initialized successfully')
|
||||
|
||||
rescue StandardError => e
|
||||
Rails.logger.error("[MCP] Failed to initialize MCP service: #{e.message}")
|
||||
|
||||
# Don't crash the application, just log the error
|
||||
Rails.logger.warn('[MCP] MCP integration disabled due to initialization error')
|
||||
end
|
||||
|
||||
# Simple health check method for console debugging
|
||||
if Rails.env.development?
|
||||
Rails.application.config.after_initialize do
|
||||
def mcp_health_check
|
||||
service = McpClientService.instance
|
||||
stats = service.stats
|
||||
|
||||
Rails.logger.debug 'MCP Service Health Check'
|
||||
Rails.logger.debug '======================='
|
||||
Rails.logger.debug { "Clients: #{stats[:clients].join(', ')}" }
|
||||
Rails.logger.debug 'Connection Stats:'
|
||||
stats[:connection_stats].each do |server, stat|
|
||||
Rails.logger.debug " #{server}: #{stat[:total_calls]} calls, #{stat[:successful_calls]} successful"
|
||||
end
|
||||
rescue StandardError => e
|
||||
Rails.logger.debug { "Health check failed: #{e.message}" }
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,31 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# MCP Server Auto-Setup Initializer
|
||||
# Automatically sets up MCP servers when the application starts
|
||||
|
||||
Rails.application.config.after_initialize do
|
||||
# Only setup in development, production, or when explicitly enabled
|
||||
if Rails.env.development? || Rails.env.production? || ENV['MCP_AUTO_SETUP'] == 'true'
|
||||
begin
|
||||
# Use a background job or thread to avoid blocking application startup
|
||||
Thread.new do
|
||||
require Rails.root.join('lib/mcp_client_service')
|
||||
service = McpClientService.instance
|
||||
service.initialize_default_servers!
|
||||
|
||||
Rails.logger.info '[MCP Setup] ✅ MCP initialization thread completed'
|
||||
end
|
||||
|
||||
Rails.logger.info '[MCP Setup] ✅ MCP server initialization scheduled successfully'
|
||||
|
||||
rescue StandardError => e
|
||||
Rails.logger.error "[MCP Setup] ❌ Failed to schedule MCP setup: #{e.message}"
|
||||
Rails.logger.error "[MCP Setup] Backtrace: #{e.backtrace.first(3).join(', ')}"
|
||||
# Don't fail application startup if MCP setup fails
|
||||
end
|
||||
else
|
||||
Rails.logger.info "[MCP Setup] ⏭️ Skipping MCP setup (environment: #{Rails.env})"
|
||||
end
|
||||
end
|
||||
|
||||
# MCP initialization is now handled by the McpClientService
|
||||
@@ -34,6 +34,7 @@ RUN apk update && apk add --no-cache \
|
||||
git \
|
||||
curl \
|
||||
xz \
|
||||
expect \
|
||||
&& mkdir -p /var/app \
|
||||
&& gem install bundler
|
||||
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'mcp_client'
|
||||
|
||||
module Mcp
|
||||
# Base class for all MCP clients
|
||||
# Provides common functionality and interface for MCP connections
|
||||
class BaseClient
|
||||
class ConnectionError < StandardError; end
|
||||
class ConfigurationError < StandardError; end
|
||||
class ToolCallError < StandardError; end
|
||||
|
||||
attr_reader :name, :config, :client, :connected
|
||||
|
||||
def initialize(name, config = {})
|
||||
@name = name
|
||||
@config = default_config.merge(config)
|
||||
@client = nil
|
||||
@connected = false
|
||||
@wrapper_script = nil
|
||||
|
||||
post_initialize_setup
|
||||
end
|
||||
|
||||
# Connect to the MCP server
|
||||
def connect!
|
||||
Rails.logger.info("[Mcp::#{self.class.name}] Starting connection to #{@name}...")
|
||||
connection_start = Time.current
|
||||
|
||||
validate_config!
|
||||
@client = create_client
|
||||
@connected = true
|
||||
|
||||
duration = Time.current - connection_start
|
||||
Rails.logger.info("[Mcp::#{self.class.name}] Connected successfully in #{duration.round(3)}s")
|
||||
|
||||
if @config[:test_on_connect]
|
||||
Rails.logger.debug { "[Mcp::#{self.class.name}] Testing connection..." }
|
||||
test_connection
|
||||
end
|
||||
|
||||
rescue StandardError => e
|
||||
@connected = false
|
||||
Rails.logger.error("[Mcp::#{self.class.name}] Connection failed: #{e.message}")
|
||||
Rails.logger.debug { "[Mcp::#{self.class.name}] Error details: #{e.backtrace.first(2).join(', ')}" }
|
||||
raise ConnectionError, "Failed to connect: #{e.message}"
|
||||
end
|
||||
|
||||
# Disconnect from the MCP server
|
||||
def disconnect!
|
||||
@client = nil
|
||||
@connected = false
|
||||
|
||||
# Clean up wrapper script if it exists
|
||||
cleanup_wrapper_script
|
||||
|
||||
Rails.logger.debug { "[Mcp::#{self.class.name}] Disconnected" }
|
||||
end
|
||||
|
||||
# Check if connected
|
||||
def connected?
|
||||
@connected && @client.present?
|
||||
end
|
||||
|
||||
# Call a tool on the MCP server
|
||||
def call_tool(tool_name, arguments = {})
|
||||
ensure_connected!
|
||||
|
||||
Rails.logger.debug { "[Mcp::#{self.class.name}] Calling tool: #{tool_name}" }
|
||||
result = @client.call_tool(tool_name, arguments)
|
||||
Rails.logger.debug { "[Mcp::#{self.class.name}] Tool call successful" }
|
||||
|
||||
result
|
||||
rescue StandardError => e
|
||||
Rails.logger.error("[Mcp::#{self.class.name}] Tool call failed: #{e.message}")
|
||||
raise ToolCallError, "Failed to call tool #{tool_name}: #{e.message}"
|
||||
end
|
||||
|
||||
# List available tools
|
||||
def list_tools
|
||||
ensure_connected!
|
||||
|
||||
tools = @client.list_tools || []
|
||||
Rails.logger.debug { "[Mcp::#{self.class.name}] Found #{tools.length} tools" }
|
||||
|
||||
tools
|
||||
rescue StandardError => e
|
||||
Rails.logger.error("[Mcp::#{self.class.name}] Failed to list tools: #{e.message}")
|
||||
[]
|
||||
end
|
||||
|
||||
# Get client statistics
|
||||
def stats
|
||||
{
|
||||
name: @name,
|
||||
connected: connected?,
|
||||
config: sanitized_config,
|
||||
tools_count: connected? ? list_tools.length : 0
|
||||
}
|
||||
end
|
||||
|
||||
# Get authentication configuration (for debugging)
|
||||
def auth_info
|
||||
return { enabled: false } unless @config[:auth][:enabled]
|
||||
|
||||
{
|
||||
enabled: true,
|
||||
configured_fields: @config[:auth][:config].keys,
|
||||
env_mapping: @config[:auth][:env_mapping]
|
||||
}
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Post-initialization setup - override in subclasses if needed
|
||||
def post_initialize_setup
|
||||
# Override in subclasses for additional setup after config is set
|
||||
end
|
||||
|
||||
# Default configuration - override in subclasses
|
||||
def default_config
|
||||
{
|
||||
transport: 'stdio',
|
||||
test_on_connect: false,
|
||||
auto_setup: false,
|
||||
auth: {
|
||||
enabled: false,
|
||||
config: {},
|
||||
env_mapping: {}
|
||||
},
|
||||
environment: {}
|
||||
}
|
||||
end
|
||||
|
||||
# Validate configuration - override in subclasses
|
||||
def validate_config!
|
||||
Rails.logger.debug { "[Mcp::#{self.class.name}] Validating configuration with command: #{@config[:command]}" }
|
||||
raise ConfigurationError, 'Command is required' if @config[:command].blank?
|
||||
end
|
||||
|
||||
# Create the actual MCP client - override in subclasses
|
||||
def create_client
|
||||
Rails.logger.debug { "[Mcp::#{self.class.name}] Creating client with config: #{@config.inspect}" }
|
||||
case @config[:transport]
|
||||
when 'stdio'
|
||||
create_stdio_client
|
||||
else
|
||||
raise ConfigurationError, "Unsupported transport: #{@config[:transport]}"
|
||||
end
|
||||
end
|
||||
|
||||
# Create stdio client
|
||||
def create_stdio_client
|
||||
command = @config[:command]
|
||||
raise ConfigurationError, 'Command required for stdio transport' if command.blank?
|
||||
|
||||
Rails.logger.debug { "[Mcp::#{self.class.name}] Creating stdio client with command: #{command.join(' ')}" }
|
||||
|
||||
# Prepare environment variables if specified
|
||||
env_vars = @config[:environment] || {}
|
||||
final_command = command
|
||||
|
||||
if env_vars.any?
|
||||
Rails.logger.debug { "[Mcp::#{self.class.name}] Setting environment variables: #{env_vars.keys.join(', ')}" }
|
||||
Rails.logger.debug do
|
||||
"[Mcp::#{self.class.name}] Environment values: #{env_vars.transform_values do |v|
|
||||
v.to_s.length > 10 ? "#{v.to_s[0..8]}..." : v.to_s
|
||||
end}"
|
||||
end
|
||||
|
||||
# Create a wrapper script to ensure environment variables are passed correctly
|
||||
wrapper_script = create_env_wrapper_script(command, env_vars)
|
||||
if wrapper_script
|
||||
final_command = ['sh', wrapper_script]
|
||||
Rails.logger.debug { "[Mcp::#{self.class.name}] Using wrapper script: #{wrapper_script}" }
|
||||
else
|
||||
# Fallback: Set in current process
|
||||
env_vars.each do |key, value|
|
||||
if value
|
||||
ENV[key.to_s] = value.to_s
|
||||
Rails.logger.debug { "[Mcp::#{self.class.name}] Set ENV['#{key}'] = '#{value.to_s[0..8]}...#{value.to_s[-3..]}'" }
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
Rails.logger.debug { "[Mcp::#{self.class.name}] No environment variables to set" }
|
||||
end
|
||||
|
||||
Rails.logger.debug { "[Mcp::#{self.class.name}] Initializing MCP client..." }
|
||||
|
||||
MCPClient.create_client(
|
||||
mcp_server_configs: [
|
||||
MCPClient.stdio_config(
|
||||
command: final_command,
|
||||
name: @name
|
||||
)
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
# Create a temporary wrapper script to ensure environment variables are passed
|
||||
def create_env_wrapper_script(command, env_vars)
|
||||
return nil unless env_vars.any?
|
||||
|
||||
begin
|
||||
script_content = "#!/bin/bash\n"
|
||||
env_vars.each { |key, value| script_content += "export #{key}='#{value}'\n" }
|
||||
script_content += "exec #{command.join(' ')}\n"
|
||||
|
||||
script_path = "/tmp/mcp_wrapper_#{@name}_#{Process.pid}.sh"
|
||||
File.write(script_path, script_content)
|
||||
File.chmod(0o755, script_path)
|
||||
|
||||
@wrapper_script = script_path
|
||||
Rails.logger.debug { "[Mcp::#{self.class.name}] Created wrapper script: #{script_path}" }
|
||||
script_path
|
||||
rescue StandardError => e
|
||||
Rails.logger.warn("[Mcp::#{self.class.name}] Failed to create wrapper script: #{e.message}")
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
# Clean up wrapper script
|
||||
def cleanup_wrapper_script
|
||||
return unless @wrapper_script && File.exist?(@wrapper_script)
|
||||
|
||||
begin
|
||||
File.delete(@wrapper_script)
|
||||
Rails.logger.debug { "[Mcp::#{self.class.name}] Cleaned up wrapper script: #{@wrapper_script}" }
|
||||
rescue StandardError => e
|
||||
Rails.logger.warn("[Mcp::#{self.class.name}] Failed to cleanup wrapper script: #{e.message}")
|
||||
ensure
|
||||
@wrapper_script = nil
|
||||
end
|
||||
end
|
||||
|
||||
# Test connection after connecting
|
||||
def test_connection
|
||||
tools = list_tools
|
||||
Rails.logger.debug { "[Mcp::#{self.class.name}] Connection test successful. Found #{tools.length} tools" }
|
||||
rescue StandardError => e
|
||||
Rails.logger.warn("[Mcp::#{self.class.name}] Connection test failed: #{e.message}")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ensure_connected!
|
||||
return if connected?
|
||||
|
||||
raise ConnectionError, "Not connected to #{@name}. Call connect! first."
|
||||
end
|
||||
|
||||
# Sanitize configuration for logging (remove sensitive data)
|
||||
def sanitized_config
|
||||
config = @config.dup
|
||||
|
||||
# Remove sensitive auth data
|
||||
if config[:auth] && config[:auth][:config]
|
||||
config[:auth] = config[:auth].dup
|
||||
config[:auth][:config] = config[:auth][:config].transform_values { |_| '[REDACTED]' }
|
||||
end
|
||||
|
||||
config
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,224 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../base_client'
|
||||
|
||||
module Mcp
|
||||
module Clients
|
||||
# Acme Mintlify MCP Client
|
||||
# Handles connection to specific Acme Mintlify server (acme-d0cb791b)
|
||||
# Requires api_access_token for authentication
|
||||
class AcmeMintlifyClient < BaseClient
|
||||
DEFAULT_SERVER_ID = 'acme-d0cb791b'
|
||||
|
||||
def initialize(config = {})
|
||||
super('acme_mintlify', build_config(config))
|
||||
end
|
||||
|
||||
def self.connect_with_setup(config = {})
|
||||
Rails.logger.info('[Mcp::Clients::AcmeMintlifyClient] Connecting to Acme Mintlify MCP server...')
|
||||
client = new(config)
|
||||
client.setup_if_needed! if client.config[:auto_setup]
|
||||
client.connect!
|
||||
client
|
||||
end
|
||||
|
||||
def self.connect
|
||||
connect_with_setup({ auto_setup: true })
|
||||
end
|
||||
|
||||
# Setup Acme Mintlify MCP server if needed
|
||||
def setup_if_needed!
|
||||
return if server_installed?
|
||||
|
||||
Rails.logger.info('[Mcp::Clients::AcmeMintlifyClient] Setting up Acme Mintlify MCP server...')
|
||||
|
||||
api_token = @config[:auth][:config]['api_access_token']
|
||||
|
||||
Rails.logger.debug { "[Mcp::Clients::AcmeMintlifyClient] API token: #{api_token}" }
|
||||
Rails.logger.debug { "[Mcp::Clients::AcmeMintlifyClient] Config: #{@config.inspect}" }
|
||||
|
||||
raise ConfigurationError, 'api_access_token is required for automatic setup' if api_token.blank?
|
||||
|
||||
install_server_with_token(api_token)
|
||||
|
||||
raise ConfigurationError, 'Failed to setup Acme Mintlify MCP server automatically' unless server_installed?
|
||||
|
||||
Rails.logger.info('[Mcp::Clients::AcmeMintlifyClient] Setup completed successfully')
|
||||
end
|
||||
|
||||
# Check if server is properly installed
|
||||
def server_installed?
|
||||
!server_path.nil? && File.exist?(server_path)
|
||||
end
|
||||
|
||||
# Get available capabilities
|
||||
def capabilities
|
||||
%w[documentation_search api_access content_retrieval]
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def default_config
|
||||
super.merge({
|
||||
server_id: DEFAULT_SERVER_ID,
|
||||
command: nil, # Will be built after config is set
|
||||
auto_setup: true,
|
||||
test_on_connect: true,
|
||||
auth: {
|
||||
enabled: false, # No runtime auth needed - API key only used during mcp add
|
||||
config: {},
|
||||
env_mapping: {}
|
||||
},
|
||||
environment: {}
|
||||
})
|
||||
end
|
||||
|
||||
def post_initialize_setup
|
||||
# Find the server and build command
|
||||
actual_server_path = find_server_path
|
||||
if actual_server_path
|
||||
@config[:command] = ['node', actual_server_path]
|
||||
@server_path = actual_server_path
|
||||
Rails.logger.debug { "[Mcp::Clients::AcmeMintlifyClient] Using server at: #{actual_server_path}" }
|
||||
else
|
||||
@config[:command] = build_fallback_command
|
||||
end
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def validate_config!
|
||||
# Check if the exact server is installed
|
||||
unless server_installed?
|
||||
if @config[:auto_setup]
|
||||
# For auto-setup, API token can be provided in config for installation
|
||||
api_token = @config.dig(:auth, :config, 'api_access_token')
|
||||
if api_token.blank?
|
||||
raise ConfigurationError,
|
||||
'api_access_token is required for automatic setup of Acme server. ' \
|
||||
'Use AcmeMintlifyClient.connect_with_setup for installation'
|
||||
end
|
||||
setup_if_needed!
|
||||
else
|
||||
raise ConfigurationError,
|
||||
"Acme Mintlify MCP server '#{@config[:server_id]}' not found. " \
|
||||
'Enable auto_setup for automatic installation'
|
||||
end
|
||||
end
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_config(user_config)
|
||||
# Build auth config first
|
||||
auth_config = build_auth_config(user_config[:auth] || {})
|
||||
|
||||
base_config = {
|
||||
server_id: user_config[:server_id] || DEFAULT_SERVER_ID,
|
||||
auto_setup: user_config.fetch(:auto_setup, true),
|
||||
test_on_connect: user_config.fetch(:test_on_connect, true),
|
||||
auth: auth_config,
|
||||
environment: build_environment_config(user_config[:environment] || {}, auth_config)
|
||||
}
|
||||
base_config.merge(user_config.except(:auth, :environment))
|
||||
end
|
||||
|
||||
def build_auth_config(user_auth)
|
||||
# Auth config only used for installation (mcp add step)
|
||||
# Make sure to preserve any auth config passed in
|
||||
auth_config = {}
|
||||
|
||||
# Copy auth config from user input
|
||||
auth_config = user_auth[:config].dup if user_auth && user_auth[:config]
|
||||
|
||||
Rails.logger.debug { "[Mcp::Clients::AcmeMintlifyClient] Building auth config from: #{user_auth.inspect}" }
|
||||
Rails.logger.debug { "[Mcp::Clients::AcmeMintlifyClient] Extracted auth_config: #{auth_config.inspect}" }
|
||||
|
||||
result = {
|
||||
enabled: false, # No runtime auth needed
|
||||
config: auth_config, # Store for installation if needed
|
||||
env_mapping: {}
|
||||
}
|
||||
|
||||
Rails.logger.debug { "[Mcp::Clients::AcmeMintlifyClient] Final auth config: #{result.inspect}" }
|
||||
result
|
||||
end
|
||||
|
||||
def build_environment_config(user_env, _auth_config = nil)
|
||||
# No runtime environment variables needed - server is configured during mcp add
|
||||
user_env
|
||||
end
|
||||
|
||||
def server_path
|
||||
@server_path ||= find_server_path
|
||||
end
|
||||
|
||||
def find_server_path
|
||||
home_dir = Dir.home || '/root'
|
||||
mcp_dir = "#{home_dir}/.mcp"
|
||||
|
||||
return nil unless Dir.exist?(mcp_dir)
|
||||
|
||||
# Only use the exact server ID specified
|
||||
expected_path = "#{mcp_dir}/#{@config[:server_id]}/src/index.js"
|
||||
|
||||
if File.exist?(expected_path)
|
||||
Rails.logger.debug { "[Mcp::Clients::AcmeMintlifyClient] Found exact server at: #{expected_path}" }
|
||||
return expected_path
|
||||
else
|
||||
Rails.logger.debug { "[Mcp::Clients::AcmeMintlifyClient] Server #{@config[:server_id]} not found at #{expected_path}" }
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
def build_fallback_command
|
||||
['node', "#{Dir.home || '/root'}/.mcp/#{@config[:server_id]}/src/index.js"]
|
||||
end
|
||||
|
||||
def install_mcp_cli
|
||||
Rails.logger.debug('[Mcp::Clients::AcmeMintlifyClient] Installing MCP CLI...')
|
||||
system('npm install -g @mintlify/mcp 2>/dev/null')
|
||||
end
|
||||
|
||||
def add_mcp_server
|
||||
Rails.logger.debug { "[Mcp::Clients::AcmeMintlifyClient] Adding server #{@config[:server_id]}..." }
|
||||
|
||||
api_token = @config[:auth][:config]['api_access_token']
|
||||
if api_token.blank?
|
||||
Rails.logger.error('[Mcp::Clients::AcmeMintlifyClient] No API token provided for server installation')
|
||||
return false
|
||||
end
|
||||
|
||||
# Use expect script to handle interactive installation
|
||||
install_with_expect(api_token)
|
||||
end
|
||||
|
||||
def install_server_with_token(_api_token)
|
||||
Rails.logger.debug('[Mcp::Clients::AcmeMintlifyClient] Installing Acme Mintlify MCP server...')
|
||||
|
||||
install_mcp_cli && add_mcp_server
|
||||
end
|
||||
|
||||
def install_with_expect(api_token)
|
||||
Rails.logger.debug('[Mcp::Clients::AcmeMintlifyClient] Installing Acme Mintlify MCP server with expect...')
|
||||
# Use expect to handle the interactive prompt
|
||||
expect_script = <<~SCRIPT
|
||||
#!/usr/bin/expect -f
|
||||
spawn mcp add #{@config[:server_id]}
|
||||
expect "What is the API Key for \\"Chatwoot\\"?"
|
||||
send "#{api_token}\\r"
|
||||
expect eof
|
||||
SCRIPT
|
||||
script_path = "/tmp/mcp_install_#{@config[:server_id]}.exp"
|
||||
Rails.logger.debug { "[Mcp::Clients::AcmeMintlifyClient] Script path: #{script_path}" }
|
||||
File.write(script_path, expect_script)
|
||||
File.chmod(0o755, script_path)
|
||||
|
||||
system(script_path)
|
||||
# File.delete(script_path) if File.exist?(script_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,229 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../base_client'
|
||||
|
||||
module Mcp
|
||||
module Clients
|
||||
# Mintlify MCP Client
|
||||
# Handles connection to Mintlify documentation server
|
||||
class MintlifyClient < BaseClient
|
||||
DEFAULT_SERVER_ID = 'chatwoot-447c5a93'
|
||||
|
||||
def initialize(config = {})
|
||||
super('mintlify', build_config(config))
|
||||
end
|
||||
|
||||
# Quick connect method with auto-setup
|
||||
def self.connect_with_setup(config = {})
|
||||
client = new(config)
|
||||
client.setup_if_needed! if client.config[:auto_setup]
|
||||
client.connect!
|
||||
client
|
||||
end
|
||||
|
||||
# Setup Mintlify MCP server if needed
|
||||
def setup_if_needed!
|
||||
return if server_installed?
|
||||
|
||||
Rails.logger.info('[Mcp::Clients::MintlifyClient] Setting up Mintlify MCP server...')
|
||||
|
||||
install_mcp_cli && add_mcp_server
|
||||
|
||||
raise ConfigurationError, 'Failed to setup Mintlify MCP server automatically' unless server_installed?
|
||||
|
||||
Rails.logger.info('[Mcp::Clients::MintlifyClient] Setup completed successfully')
|
||||
end
|
||||
|
||||
# Check if server is properly installed
|
||||
def server_installed?
|
||||
!server_path.nil? && File.exist?(server_path)
|
||||
end
|
||||
|
||||
# Get available capabilities
|
||||
def capabilities
|
||||
%w[documentation_search code_examples api_reference]
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def default_config
|
||||
super.merge({
|
||||
server_id: DEFAULT_SERVER_ID,
|
||||
command: nil, # Will be built after config is set
|
||||
auto_setup: true,
|
||||
test_on_connect: true,
|
||||
auth: {
|
||||
enabled: false,
|
||||
config: {},
|
||||
env_mapping: {}
|
||||
},
|
||||
environment: {}
|
||||
})
|
||||
end
|
||||
|
||||
def post_initialize_setup
|
||||
# Find the actual server path and update config
|
||||
actual_server_path = find_server_path
|
||||
if actual_server_path
|
||||
@config[:command] = ['node', actual_server_path]
|
||||
@server_path = actual_server_path
|
||||
|
||||
# Extract the actual server ID from the path for logging
|
||||
actual_server_id = File.dirname(actual_server_path).split('/').last
|
||||
Rails.logger.debug { "[Mcp::Clients::MintlifyClient] Using server at: #{actual_server_path} (ID: #{actual_server_id})" }
|
||||
else
|
||||
@config[:command] = build_command
|
||||
end
|
||||
super
|
||||
end
|
||||
|
||||
def validate_config!
|
||||
# Check if the exact server is installed
|
||||
unless server_installed?
|
||||
if @config[:auto_setup]
|
||||
setup_if_needed!
|
||||
else
|
||||
available_servers = list_available_servers
|
||||
error_msg = "Mintlify MCP server '#{@config[:server_id]}' not found.\n"
|
||||
|
||||
if available_servers.any?
|
||||
error_msg += "Available servers: #{available_servers.join(', ')}\n"
|
||||
error_msg += "Please use one of these server IDs or install '#{@config[:server_id]}'"
|
||||
else
|
||||
error_msg += "No MCP servers found. Run 'rake mcp:setup' to install servers."
|
||||
end
|
||||
|
||||
raise ConfigurationError, error_msg
|
||||
end
|
||||
end
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_config(user_config)
|
||||
# Build auth config first
|
||||
auth_config = build_auth_config(user_config[:auth] || {})
|
||||
|
||||
base_config = {
|
||||
server_id: user_config[:server_id] || DEFAULT_SERVER_ID,
|
||||
auto_setup: user_config.fetch(:auto_setup, true),
|
||||
test_on_connect: user_config.fetch(:test_on_connect, true),
|
||||
auth: auth_config,
|
||||
environment: build_environment_config(user_config[:environment] || {}, auth_config)
|
||||
}
|
||||
base_config.merge(user_config.except(:auth, :environment))
|
||||
end
|
||||
|
||||
def build_command
|
||||
['node', server_path]
|
||||
end
|
||||
|
||||
def server_path
|
||||
@server_path ||= find_server_path
|
||||
end
|
||||
|
||||
def find_server_path
|
||||
home_dir = Dir.home || '/root'
|
||||
mcp_dir = "#{home_dir}/.mcp"
|
||||
|
||||
return nil unless Dir.exist?(mcp_dir)
|
||||
|
||||
# Only use the exact server ID specified - no fallback
|
||||
expected_path = "#{mcp_dir}/#{@config[:server_id]}/src/index.js"
|
||||
|
||||
if File.exist?(expected_path)
|
||||
Rails.logger.debug { "[Mcp::Clients::MintlifyClient] Found exact server at: #{expected_path}" }
|
||||
return expected_path
|
||||
else
|
||||
Rails.logger.debug { "[Mcp::Clients::MintlifyClient] Server #{@config[:server_id]} not found at #{expected_path}" }
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
def install_mcp_cli
|
||||
Rails.logger.debug('[Mcp::Clients::MintlifyClient] Installing MCP CLI...')
|
||||
system('npm install -g @mintlify/mcp 2>/dev/null')
|
||||
end
|
||||
|
||||
def add_mcp_server
|
||||
Rails.logger.debug { "[Mcp::Clients::MintlifyClient] Adding server #{@config[:server_id]}..." }
|
||||
|
||||
# Run mcp add command for the server
|
||||
success = system("mcp add #{@config[:server_id]} 2>/dev/null")
|
||||
|
||||
if success
|
||||
Rails.logger.debug { "[Mcp::Clients::MintlifyClient] Successfully added server #{@config[:server_id]}" }
|
||||
else
|
||||
Rails.logger.warn("[Mcp::Clients::MintlifyClient] Failed to add server #{@config[:server_id]}")
|
||||
end
|
||||
|
||||
success
|
||||
end
|
||||
|
||||
def list_available_servers
|
||||
home_dir = Dir.home || '/root'
|
||||
mcp_dir = "#{home_dir}/.mcp"
|
||||
|
||||
return [] unless Dir.exist?(mcp_dir)
|
||||
|
||||
# Find all directories with src/index.js
|
||||
available = []
|
||||
Dir.glob("#{mcp_dir}/*/src/index.js").each do |path|
|
||||
# Skip node_modules directories
|
||||
next if path.include?('/node_modules/')
|
||||
|
||||
# Extract server directory name
|
||||
path_parts = path.split('/')
|
||||
mcp_index = path_parts.index('.mcp')
|
||||
if mcp_index && mcp_index < path_parts.length - 1
|
||||
server_id = path_parts[mcp_index + 1]
|
||||
available << server_id
|
||||
end
|
||||
end
|
||||
|
||||
available
|
||||
end
|
||||
|
||||
def build_auth_config(user_auth)
|
||||
# No default auth fields - completely configurable based on MCP server requirements
|
||||
# Users define whatever auth fields their specific MCP server needs
|
||||
|
||||
# Get environment mapping from user config (no defaults)
|
||||
env_mapping = user_auth[:env_mapping] || {}
|
||||
|
||||
# Build auth config from user config and environment variables
|
||||
auth_config = user_auth[:config] || {}
|
||||
|
||||
# Auto-populate from environment variables if mapping is provided
|
||||
env_mapping.each do |config_key, env_var|
|
||||
auth_config[config_key] = ENV[env_var] if auth_config[config_key].nil? && ENV[env_var]
|
||||
end
|
||||
|
||||
# Determine if auth is enabled
|
||||
enabled = user_auth.fetch(:enabled, auth_config.any? { |_, value| value.present? })
|
||||
|
||||
{
|
||||
enabled: enabled,
|
||||
config: auth_config,
|
||||
env_mapping: env_mapping
|
||||
}
|
||||
end
|
||||
|
||||
def build_environment_config(user_env, auth_config = nil)
|
||||
env_config = {}
|
||||
|
||||
# Add authentication environment variables if configured
|
||||
if auth_config && auth_config[:enabled]
|
||||
auth_config[:env_mapping].each do |config_key, env_var|
|
||||
value = auth_config[:config][config_key]
|
||||
env_config[env_var] = value if value.present?
|
||||
end
|
||||
end
|
||||
|
||||
env_config.merge(user_env)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+145
-257
@@ -1,301 +1,189 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'mcp_client'
|
||||
require 'singleton'
|
||||
require 'timeout'
|
||||
require_relative 'mcp/clients/mintlify_client'
|
||||
require_relative 'mcp/clients/acme_mintlify_client'
|
||||
|
||||
# MCP Client Service - manages connections to MCP servers
|
||||
# This service provides a centralized way to connect to and interact with
|
||||
# Model Context Protocol servers, including the Mintlify documentation server.
|
||||
# MCP Client Service
|
||||
# Main service that manages MCP client connections using a structured approach
|
||||
class McpClientService
|
||||
include Singleton
|
||||
|
||||
class ConfigurationError < StandardError; end
|
||||
class ConnectionError < StandardError; end
|
||||
class TimeoutError < StandardError; end
|
||||
class ClientNotFoundError < StandardError; end
|
||||
|
||||
attr_reader :clients, :connection_stats
|
||||
attr_reader :clients
|
||||
|
||||
def initialize
|
||||
@clients = {}
|
||||
@connection_stats = {}
|
||||
@mutex = Mutex.new
|
||||
setup_clients
|
||||
# Don't auto-setup in initializer to avoid issues with Rails loading
|
||||
end
|
||||
|
||||
# Get or create a client for a specific server
|
||||
def client_for(server_name)
|
||||
@mutex.synchronize do
|
||||
return @clients[server_name] if @clients[server_name]
|
||||
|
||||
Rails.logger.info("[McpClientService] Creating new client for #{server_name}")
|
||||
@clients[server_name] = create_client_for(server_name)
|
||||
end
|
||||
end
|
||||
|
||||
# Execute a tool on a specific server with retry logic
|
||||
def call_tool(server_name, tool_name, arguments = {})
|
||||
validate_call_tool_params(server_name, tool_name, arguments)
|
||||
|
||||
# Initialize both servers with default configurations
|
||||
def initialize_default_servers!
|
||||
Rails.logger.info('[McpClientService] Initializing default MCP servers...')
|
||||
start_time = Time.current
|
||||
attempt = 0
|
||||
max_retries = 3
|
||||
|
||||
begin
|
||||
attempt += 1
|
||||
client = client_for(server_name)
|
||||
raise ConnectionError, "No client available for #{server_name}" unless client
|
||||
initialize_mintlify_server
|
||||
initialize_acme_server
|
||||
|
||||
Rails.logger.debug { "[McpClientService] Calling tool #{tool_name} on #{server_name} (attempt #{attempt})" }
|
||||
duration = Time.current - start_time
|
||||
Rails.logger.info("[McpClientService] All default servers initialized successfully in #{duration.round(2)}s")
|
||||
|
||||
result = Timeout.timeout(60) do # 60 second timeout
|
||||
client.call_tool(tool_name, arguments)
|
||||
end
|
||||
|
||||
record_successful_call(server_name, tool_name, Time.current - start_time)
|
||||
Rails.logger.debug { "[McpClientService] Tool call successful in #{(Time.current - start_time).round(2)}s" }
|
||||
result
|
||||
log_connection_status
|
||||
|
||||
rescue StandardError => e
|
||||
record_failed_call(server_name, tool_name, e)
|
||||
|
||||
if attempt < max_retries && should_retry?(e)
|
||||
Rails.logger.warn("[McpClientService] Retrying tool call #{tool_name} on #{server_name} (attempt #{attempt}/#{max_retries})")
|
||||
sleep(2 * attempt) # Simple backoff
|
||||
retry
|
||||
end
|
||||
|
||||
Rails.logger.error("[McpClientService] Tool call failed after #{attempt} attempts: #{e.message}")
|
||||
handle_tool_call_error(e, server_name, tool_name)
|
||||
Rails.logger.error("[McpClientService] Failed to initialize some servers: #{e.message}")
|
||||
Rails.logger.debug { "[McpClientService] Backtrace: #{e.backtrace.first(3).join(', ')}" }
|
||||
end
|
||||
end
|
||||
|
||||
# List available tools for a server
|
||||
def list_tools(server_name)
|
||||
client = client_for(server_name)
|
||||
return [] unless client
|
||||
# Get or create a client for a specific type
|
||||
def client_for(type, config = {})
|
||||
type = type.to_s.downcase
|
||||
|
||||
Rails.logger.debug { "[McpClientService] Listing tools for server: #{server_name}" }
|
||||
tools = client.list_tools || []
|
||||
return @clients[type] if @clients[type]&.connected?
|
||||
|
||||
Rails.logger.debug { "[McpClientService] Found #{tools.length} tools for #{server_name}" }
|
||||
tools
|
||||
Rails.logger.debug { "[McpClientService] Creating client for #{type}" }
|
||||
connection_start = Time.current
|
||||
|
||||
rescue StandardError => e
|
||||
Rails.logger.error("[McpClientService] Error listing tools for #{server_name}: #{e.message}")
|
||||
record_failed_call(server_name, 'list_tools', e)
|
||||
[]
|
||||
end
|
||||
begin
|
||||
# Create and connect the client with provided config
|
||||
# Each client has its own default configuration
|
||||
client = create_client(type, config)
|
||||
client.connect!
|
||||
|
||||
# Get all available tools across all servers
|
||||
def all_tools
|
||||
tools = {}
|
||||
@clients.each_key do |server_name|
|
||||
tools[server_name] = list_tools(server_name)
|
||||
end
|
||||
tools
|
||||
end
|
||||
@clients[type] = client
|
||||
|
||||
# Reconnect all clients (useful for development/testing)
|
||||
def reconnect_all!
|
||||
@mutex.synchronize do
|
||||
Rails.logger.info('[McpClientService] Reconnecting all MCP clients')
|
||||
close_all_connections
|
||||
@clients.clear
|
||||
@connection_stats.clear
|
||||
setup_clients
|
||||
duration = Time.current - connection_start
|
||||
Rails.logger.info("[McpClientService] #{type} client ready in #{duration.round(2)}s")
|
||||
|
||||
client
|
||||
rescue StandardError => e
|
||||
Rails.logger.error("[McpClientService] Failed to create #{type} client: #{e.message}")
|
||||
raise
|
||||
end
|
||||
end
|
||||
|
||||
# Get connection statistics
|
||||
def stats
|
||||
{
|
||||
clients: @clients.keys,
|
||||
connection_stats: @connection_stats.dup
|
||||
}
|
||||
# Call a tool on a specific client
|
||||
def call_tool(type, tool_name, arguments = {})
|
||||
client = client_for(type)
|
||||
client.call_tool(tool_name, arguments)
|
||||
end
|
||||
|
||||
# List available tools for a client
|
||||
def list_tools(type)
|
||||
client = client_for(type)
|
||||
client.list_tools
|
||||
end
|
||||
|
||||
# Check if a client is connected
|
||||
def connected?(type)
|
||||
client = @clients[type.to_s.downcase]
|
||||
client&.connected? || false
|
||||
end
|
||||
|
||||
# Get available client types
|
||||
def available_types
|
||||
%w[mintlify acme_mintlify]
|
||||
end
|
||||
|
||||
# Quick setup for a client type
|
||||
def setup_client(type)
|
||||
case type.to_s.downcase
|
||||
when 'mintlify'
|
||||
client = Mcp::Clients::MintlifyClient.new
|
||||
client.setup_if_needed!
|
||||
true
|
||||
when 'acme_mintlify'
|
||||
client = Mcp::Clients::AcmeMintlifyClient.new
|
||||
client.setup_if_needed!
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def setup_clients
|
||||
# Set up Mintlify MCP server if enabled
|
||||
setup_mintlify_client if mintlify_enabled?
|
||||
end
|
||||
def initialize_mintlify_server
|
||||
connection_start = Time.current
|
||||
|
||||
def setup_mintlify_client
|
||||
Rails.logger.info('[McpClientService] Setting up Mintlify MCP client')
|
||||
|
||||
validate_mintlify_configuration!
|
||||
|
||||
mcp_client = setup_mintlify_stdio_client
|
||||
|
||||
raise ConnectionError, 'Failed to setup Mintlify client' unless mcp_client
|
||||
|
||||
@clients['mintlify'] = mcp_client
|
||||
initialize_connection_stats('mintlify')
|
||||
Rails.logger.info('[McpClientService] Mintlify MCP client setup complete')
|
||||
|
||||
# Test connection by listing tools (but don't fail if empty)
|
||||
begin
|
||||
tools = mcp_client.list_tools
|
||||
tool_names = extract_tool_names(tools)
|
||||
Rails.logger.info("[McpClientService] Found #{tools.length} tools: #{tool_names.join(', ')}")
|
||||
rescue StandardError => e
|
||||
Rails.logger.warn("[McpClientService] Could not list tools (but client may still work): #{e.message}")
|
||||
end
|
||||
config = {
|
||||
server_id: 'chatwoot-447c5a93',
|
||||
auto_setup: true,
|
||||
test_on_connect: true
|
||||
}
|
||||
|
||||
rescue StandardError => e
|
||||
Rails.logger.error("[McpClientService] Failed to setup Mintlify client: #{e.message}")
|
||||
record_failed_call('mintlify', 'setup', e)
|
||||
raise e unless Rails.env.development?
|
||||
client = create_client('mintlify', config)
|
||||
client.connect!
|
||||
@clients['mintlify'] = client
|
||||
duration = Time.current - connection_start
|
||||
|
||||
Rails.logger.info("[McpClientService] Mintlify server connected successfully in #{duration.round(2)}s")
|
||||
tools = client.list_tools
|
||||
Rails.logger.info("[McpClientService] Mintlify server tools: #{tools.length} tools available")
|
||||
|
||||
rescue StandardError => e
|
||||
Rails.logger.warn("[McpClientService] Mintlify server initialization failed: #{e.message}")
|
||||
Rails.logger.debug { "[McpClientService] Mintlify error backtrace: #{e.backtrace.first(3).join(', ')}" }
|
||||
end
|
||||
end
|
||||
|
||||
def create_client_for(server_name)
|
||||
case server_name
|
||||
def initialize_acme_server
|
||||
connection_start = Time.current
|
||||
|
||||
begin
|
||||
config = {
|
||||
server_id: 'acme-d0cb791b',
|
||||
auto_setup: true,
|
||||
test_on_connect: true,
|
||||
auth: {
|
||||
config: { 'api_access_token' => get_acme_api_token }
|
||||
}
|
||||
}
|
||||
|
||||
client = create_client('acme_mintlify', config)
|
||||
client.connect!
|
||||
@clients['acme_mintlify'] = client
|
||||
duration = Time.current - connection_start
|
||||
|
||||
Rails.logger.info("[McpClientService] Acme server connected successfully in #{duration.round(2)}s")
|
||||
tools = client.list_tools
|
||||
Rails.logger.info("[McpClientService] Acme server tools: #{tools.length} tools available")
|
||||
|
||||
rescue StandardError => e
|
||||
Rails.logger.warn("[McpClientService] Acme server initialization failed: #{e.message}")
|
||||
Rails.logger.debug { "[McpClientService] Acme error backtrace: #{e.backtrace.first(3).join(', ')}" }
|
||||
end
|
||||
end
|
||||
|
||||
def get_acme_api_token
|
||||
# TODO: Update this to use the actual API token, or figure out a better way to get the token
|
||||
ENV['ACME_MINTLIFY_API_TOKEN'] || 'demo_token'
|
||||
end
|
||||
|
||||
def log_connection_status
|
||||
Rails.logger.info('[McpClientService] Connection Status Summary:')
|
||||
|
||||
available_types.each do |type|
|
||||
status = connected?(type) ? 'Connected' : 'Disconnected'
|
||||
Rails.logger.info("[McpClientService] #{type}: #{status}")
|
||||
end
|
||||
|
||||
Rails.logger.info("[McpClientService] MCP setup complete - #{@clients.keys.size} clients ready")
|
||||
end
|
||||
|
||||
def create_client(type, config)
|
||||
case type.to_s.downcase
|
||||
when 'mintlify'
|
||||
setup_mintlify_client
|
||||
@clients['mintlify']
|
||||
Mcp::Clients::MintlifyClient.new(config)
|
||||
when 'acme_mintlify'
|
||||
Mcp::Clients::AcmeMintlifyClient.new(config)
|
||||
else
|
||||
raise ConfigurationError, "Unknown server: #{server_name}"
|
||||
end
|
||||
end
|
||||
|
||||
def mintlify_enabled?
|
||||
# Always enabled - no configuration needed
|
||||
true
|
||||
end
|
||||
|
||||
def validate_mintlify_configuration!
|
||||
home_dir = Dir.home || '/root'
|
||||
mcp_dir = "#{home_dir}/.mcp/#{mcp_server_id}"
|
||||
|
||||
return if File.exist?(mcp_dir)
|
||||
|
||||
Rails.logger.warn('[McpClientService] MCP server directory not found, attempting setup...')
|
||||
setup_mcp_server_if_needed
|
||||
|
||||
return if File.exist?(mcp_dir)
|
||||
|
||||
raise ConfigurationError, "MCP server not found at #{mcp_dir}. Run 'mcp add #{mcp_server_id}'"
|
||||
end
|
||||
|
||||
def mintlify_command_array
|
||||
home_dir = Dir.home || '/root'
|
||||
command = "node #{home_dir}/.mcp/#{mcp_server_id}/src/index.js"
|
||||
command.split
|
||||
end
|
||||
|
||||
def setup_mcp_server_if_needed
|
||||
home_dir = Dir.home || '/root'
|
||||
mcp_dir = "#{home_dir}/.mcp/#{mcp_server_id}"
|
||||
|
||||
return if File.exist?(mcp_dir)
|
||||
|
||||
Rails.logger.info('[McpClientService] Setting up Mintlify MCP server for the first time...')
|
||||
|
||||
# Install MCP CLI and add the server with proper error handling
|
||||
install_success = system('npm install -g @mintlify/mcp 2>/dev/null')
|
||||
add_success = system("mcp add #{mcp_server_id} 2>/dev/null")
|
||||
|
||||
return if install_success && add_success
|
||||
|
||||
Rails.logger.error('[McpClientService] Failed to install or add MCP server')
|
||||
raise ConfigurationError, 'Failed to setup MCP server automatically'
|
||||
end
|
||||
|
||||
def setup_mintlify_stdio_client
|
||||
command_array = mintlify_command_array
|
||||
Rails.logger.info("[McpClientService] Creating stdio client with command: #{command_array.inspect}")
|
||||
|
||||
Timeout.timeout(30) do # 30 second connection timeout
|
||||
mcp_client = MCPClient.create_client(
|
||||
mcp_server_configs: [
|
||||
MCPClient.stdio_config(
|
||||
command: command_array,
|
||||
name: 'mintlify'
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
Rails.logger.info('[McpClientService] Stdio transport successful')
|
||||
mcp_client
|
||||
end
|
||||
rescue StandardError => e
|
||||
Rails.logger.error("[McpClientService] Stdio transport failed: #{e.message}")
|
||||
raise ConnectionError, "Failed to connect to Mintlify MCP server: #{e.message}"
|
||||
end
|
||||
|
||||
def should_retry?(error)
|
||||
return false if error.is_a?(ConfigurationError)
|
||||
return false if error.is_a?(ArgumentError)
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def validate_call_tool_params(server_name, tool_name, arguments)
|
||||
raise ArgumentError, 'server_name cannot be blank' if server_name.blank?
|
||||
raise ArgumentError, 'tool_name cannot be blank' if tool_name.blank?
|
||||
raise ArgumentError, 'arguments must be a Hash' unless arguments.is_a?(Hash)
|
||||
end
|
||||
|
||||
def extract_tool_names(tools)
|
||||
tools.filter_map do |tool|
|
||||
case tool
|
||||
when Hash
|
||||
tool['name']
|
||||
else
|
||||
tool.respond_to?(:name) ? tool.name : tool.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def initialize_connection_stats(server_name)
|
||||
@connection_stats[server_name] = {
|
||||
total_calls: 0,
|
||||
successful_calls: 0,
|
||||
failed_calls: 0,
|
||||
average_response_time: 0.0,
|
||||
last_error: nil,
|
||||
created_at: Time.current
|
||||
}
|
||||
end
|
||||
|
||||
def record_successful_call(server_name, _tool_name, response_time)
|
||||
stats = @connection_stats[server_name] ||= initialize_connection_stats(server_name)
|
||||
stats[:total_calls] += 1
|
||||
stats[:successful_calls] += 1
|
||||
stats[:average_response_time] = ((stats[:average_response_time] * (stats[:total_calls] - 1)) + response_time) / stats[:total_calls]
|
||||
end
|
||||
|
||||
def record_failed_call(server_name, _tool_name, error)
|
||||
stats = @connection_stats[server_name] ||= initialize_connection_stats(server_name)
|
||||
stats[:total_calls] += 1
|
||||
stats[:failed_calls] += 1
|
||||
stats[:last_error] = error.message
|
||||
end
|
||||
|
||||
def handle_tool_call_error(error, server_name, tool_name)
|
||||
case error
|
||||
when Timeout::Error
|
||||
"Tool call timed out: could not #{tool_name} on #{server_name}"
|
||||
when ConnectionError
|
||||
"Connection failed: could not #{tool_name} on #{server_name}"
|
||||
when ConfigurationError
|
||||
"Configuration error: could not #{tool_name} on #{server_name}"
|
||||
else
|
||||
"Error occurred: could not #{tool_name} on #{server_name} (#{error.message})"
|
||||
end
|
||||
end
|
||||
|
||||
def close_all_connections
|
||||
@clients.each_value do |client|
|
||||
client.close if client.respond_to?(:close)
|
||||
rescue StandardError => e
|
||||
Rails.logger.warn("[McpClientService] Error closing client connection: #{e.message}")
|
||||
end
|
||||
end
|
||||
|
||||
def mcp_server_id
|
||||
ENV.fetch('MCP_SERVER_ID') do
|
||||
raise ConfigurationError, 'MCP_SERVER_ID environment variable is required. Please set it to your MCP server identifier.'
|
||||
raise ClientNotFoundError, "Unknown client type: #{type}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user