Module: ActionMCP::Server::Tools

Included in:
TransportHandler
Defined in:
lib/action_mcp/server/tools.rb

Instance Method Summary collapse

Instance Method Details

#send_tools_call(request_id, tool_name, arguments, _meta = {}) ⇒ Object



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/action_mcp/server/tools.rb', line 39

def send_tools_call(request_id, tool_name, arguments, _meta = {})
  # Find tool in session's registry
  tool_class = session.registered_tools.find { |t| t.tool_name == tool_name }

  unless tool_class
    Rails.logger.error "Tool not found: #{tool_name}. Registered tools: #{session.registered_tools.map(&:tool_name).join(', ')}"
    send_jsonrpc_error(request_id, :method_not_found,
                       "Tool '#{tool_name}' not found or not registered for this session")
    return
  end

  # Check if tool requires consent and if consent is granted
  if tool_class.respond_to?(:requires_consent?) && tool_class.requires_consent? && !session.consent_granted_for?(tool_name)
    # Use custom error response for consent required (-32002)
    error = {
      code: -32_002,
      message: "Consent required for tool '#{tool_name}'"
    }
    send_jsonrpc_response(request_id, error: error)
    return
  end

  begin
    # Create tool and set execution context with request info
    tool = tool_class.new(arguments)
    tool.with_context({
                        session: session,
                        request: {
                          params: {
                            name: tool_name,
                            arguments: arguments,
                            _meta: _meta
                          }
                        }
                      })

    # Wrap tool execution with Rails reloader for development
    result = if Rails.env.development?
               # Preserve Current attributes across reloader boundary
               current_user = ActionMCP::Current.user
               current_gateway = ActionMCP::Current.gateway

               Rails.application.reloader.wrap do
                 # Restore Current attributes inside reloader
                 ActionMCP::Current.user = current_user
                 ActionMCP::Current.gateway = current_gateway
                 tool.call
               end
    else
               tool.call
    end

    if result.is_error
      # Protocol error
      send_jsonrpc_response(request_id, error: result.to_h)
    else
      # Success OR tool execution error - both are valid JSON-RPC responses
      send_jsonrpc_response(request_id, result: result.to_h)
    end
  rescue ArgumentError => e
    # Handle parameter validation errors
    send_jsonrpc_error(request_id, :invalid_params, e.message)
  rescue StandardError => e
    # Log the actual error for debugging
    Rails.logger.error "Tool execution error: #{e.class} - #{e.message}"
    Rails.logger.error e.backtrace.join("\n")
    send_jsonrpc_error(request_id, :internal_error, "An unexpected error occurred.")
  end
end

#send_tools_list(request_id, params = {}) ⇒ Object



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/action_mcp/server/tools.rb', line 6

def send_tools_list(request_id, params = {})
  protocol_version = session.protocol_version
  # Extract progress token from _meta if provided
  progress_token = params.dig("_meta", "progressToken")

  # Send initial progress notification if token is provided
  if progress_token
    send_progress_notification(
      progressToken: progress_token,
      progress: 0,
      message: "Starting tools list retrieval"
    )
  end

  # Use session's registered tools instead of global registry
  registered_tools = session.registered_tools

  tools = registered_tools.map do |tool_class|
    tool_class.to_h(protocol_version: protocol_version)
  end

  # Send completion progress notification if token is provided
  if progress_token
    send_progress_notification(
      progressToken: progress_token,
      progress: 100,
      message: "Tools list retrieval complete"
    )
  end

  send_jsonrpc_response(request_id, result: { tools: tools })
end