Class: SchwabMCP::Tools::CancelOrderTool

Inherits:
MCP::Tool
  • Object
show all
Extended by:
Loggable
Defined in:
lib/schwab_mcp/tools/cancel_order_tool.rb

Class Method Summary collapse

Methods included from Loggable

log_debug, log_error, log_fatal, log_info, log_warn, logger

Class Method Details

.call(order_id:, account_name:, server_context:) ⇒ Object



36
37
38
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/schwab_mcp/tools/cancel_order_tool.rb', line 36

def self.call(order_id:, account_name:, server_context:)
  log_info("Attempting to cancel order ID: #{order_id} in account: #{}")

  unless .end_with?('_ACCOUNT')
    log_error("Invalid account name format: #{}")
    return MCP::Tool::Response.new([{
      type: "text",
      text: "**Error**: Account name must end with '_ACCOUNT'. Example: 'TRADING_BROKERAGE_ACCOUNT'"
    }])
  end

  unless order_id.match?(/^\d+$/)
    log_error("Invalid order ID format: #{order_id}")
    return MCP::Tool::Response.new([{
      type: "text",
      text: "**Error**: Order ID must be numeric. Example: '123456789'"
    }])
  end

  begin
    client = SchwabClientFactory.create_client
    return SchwabClientFactory.client_error_response unless client

    available_accounts = client.
    unless available_accounts.include?()
      log_error("Account name '#{}' not found in configured accounts")
      return MCP::Tool::Response.new([{
        type: "text",
        text: "**Error**: Account name '#{}' not found in configured accounts.\n\nAvailable accounts: #{available_accounts.join(', ')}\n\nTo configure: Add the account to your schwab_rb configuration file."
      }])
    end

    log_debug("Using account name: #{}")
    log_debug("Verifying order exists before attempting cancellation")

    order = client.get_order(order_id, account_name: ) # returns SchwabRb::DataObjects::Order
    unless order
      log_warn("Order not found or empty response for order ID: #{order_id}")
      return MCP::Tool::Response.new([{
        type: "text",
        text: "**Warning**: Order ID #{order_id} not found or empty response. Order may not exist in the specified account."
      }])
    end

    order_status = order.status
    cancelable = order.respond_to?(:cancelable) ? order.cancelable : true # fallback if attribute not present

    log_debug("Order found - Status: #{order_status}, Cancelable: #{cancelable}")
    if cancelable == false
      log_warn("Order #{order_id} is not cancelable (Status: #{order_status})")
      return MCP::Tool::Response.new([{
        type: "text",
        text: "**Warning**: Order ID #{order_id} cannot be cancelled.\n\n**Current Status**: #{order_status}\n**Cancelable**: #{cancelable}\n\nOrders that are already filled, cancelled, or expired cannot be cancelled."
      }])
    end

    log_info("Attempting to cancel order ID: #{order_id} (Status: #{order_status})")
    cancel_response = client.cancel_order(order_id, account_name: )

    if cancel_response.respond_to?(:status) && cancel_response.status == 200
      log_info("Successfully cancelled order ID: #{order_id}")
      formatted_response = format_cancellation_success(order_id, , order)
    elsif cancel_response.respond_to?(:status) && cancel_response.status == 404
      log_warn("Order not found during cancellation: #{order_id}")
      return MCP::Tool::Response.new([{
        type: "text",
        text: "**Warning**: Order ID #{order_id} not found during cancellation. It may have already been cancelled or filled."
      }])
    else
      log_info("Order cancellation request submitted for order ID: #{order_id}")
      formatted_response = format_cancellation_success(order_id, , order)
    end

    MCP::Tool::Response.new([{
      type: "text",
      text: formatted_response
    }])

  # No JSON::ParserError rescue needed with data objects
  rescue => e
    log_error("Error cancelling order ID #{order_id}: #{e.message}")
    log_debug("Backtrace: #{e.backtrace.first(3).join('\n')}")

    error_message = if e.message.include?("401") || e.message.include?("403")
      "**Error**: Authorization failed. Check your API credentials and permissions for order cancellation."
    elsif e.message.include?("400")
      "**Error**: Bad request. Order ID #{order_id} may be invalid or cannot be cancelled at this time."
    elsif e.message.include?("404")
      "**Error**: Order ID #{order_id} not found in the specified account."
    else
      "**Error** cancelling order ID #{order_id}: #{e.message}"
    end

    MCP::Tool::Response.new([{
      type: "text",
      text: "#{error_message}\n\n#{e.backtrace.first(3).join('\n')}"
    }])
  end
end