groww-mcp 1.0.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.
- checksums.yaml +7 -0
- data/.env.example +19 -0
- data/CHANGELOG.md +29 -0
- data/LICENSE +21 -0
- data/README.md +308 -0
- data/bin/groww-mcp +46 -0
- data/lib/groww_mcp/auth.rb +118 -0
- data/lib/groww_mcp/base_tool.rb +22 -0
- data/lib/groww_mcp/client.rb +356 -0
- data/lib/groww_mcp/tools/auth_tools.rb +42 -0
- data/lib/groww_mcp/tools/instrument_tools.rb +112 -0
- data/lib/groww_mcp/tools/market_tools.rb +207 -0
- data/lib/groww_mcp/tools/option_chain_tools.rb +125 -0
- data/lib/groww_mcp/tools/order_tools.rb +211 -0
- data/lib/groww_mcp/tools/portfolio_tools.rb +109 -0
- data/lib/groww_mcp/tools/smart_order_tools.rb +227 -0
- data/lib/groww_mcp/tools/user_tools.rb +24 -0
- data/lib/groww_mcp/version.rb +5 -0
- data/lib/groww_mcp.rb +51 -0
- metadata +92 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "mcp"
|
|
4
|
+
|
|
5
|
+
module GrowwMcp
|
|
6
|
+
module Tools
|
|
7
|
+
class GetQuote < GrowwMcp::BaseTool
|
|
8
|
+
description "Get live market quote for a stock or F&O instrument. " \
|
|
9
|
+
"Returns last price, OHLC, bid/ask, volume, and change %. " \
|
|
10
|
+
"⚠️ Requires Groww Live Data subscription (₹499/mo)."
|
|
11
|
+
|
|
12
|
+
input_schema(
|
|
13
|
+
properties: {
|
|
14
|
+
trading_symbol: {
|
|
15
|
+
type: "string",
|
|
16
|
+
description: "Trading symbol (e.g., RELIANCE, NIFTY24JUL25000CE)",
|
|
17
|
+
},
|
|
18
|
+
exchange: {
|
|
19
|
+
type: "string",
|
|
20
|
+
enum: %w[NSE BSE],
|
|
21
|
+
description: "Exchange (default: NSE)",
|
|
22
|
+
},
|
|
23
|
+
segment: {
|
|
24
|
+
type: "string",
|
|
25
|
+
enum: %w[CASH FNO],
|
|
26
|
+
description: "Market segment (default: CASH)",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
required: ["trading_symbol"],
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
class << self
|
|
33
|
+
def call(server_context:, trading_symbol:, exchange: "NSE", segment: "CASH")
|
|
34
|
+
client = server_context[:client]
|
|
35
|
+
result = client.quote(trading_symbol, exchange: exchange, segment: segment)
|
|
36
|
+
format_response(result)
|
|
37
|
+
rescue GrowwMcp::ForbiddenError
|
|
38
|
+
MCP::Tool::Response.new([{
|
|
39
|
+
type: "text",
|
|
40
|
+
text: "❌ Live Data access denied. This requires the Groww Live Data subscription (₹499 + GST/month). " \
|
|
41
|
+
"Subscribe at: Groww App → Settings → Trading APIs.",
|
|
42
|
+
}])
|
|
43
|
+
rescue GrowwMcp::ApiError => e
|
|
44
|
+
error_response(e)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
class GetLTP < GrowwMcp::BaseTool
|
|
50
|
+
description "Get last traded price (LTP) for multiple stocks at once (max 50 symbols). " \
|
|
51
|
+
"Symbols are sent to the API as comma-separated EXCHANGE_SYMBOL pairs " \
|
|
52
|
+
"(e.g. NSE_RELIANCE,NSE_TCS) — bare symbols are automatically prefixed with the exchange. " \
|
|
53
|
+
"⚠️ Requires Groww Live Data subscription (₹499/mo)."
|
|
54
|
+
|
|
55
|
+
input_schema(
|
|
56
|
+
properties: {
|
|
57
|
+
symbols: {
|
|
58
|
+
type: "array",
|
|
59
|
+
items: { type: "string" },
|
|
60
|
+
description: "Array of trading symbols (max 50). Bare symbols (e.g. RELIANCE) get the " \
|
|
61
|
+
"exchange prefix added automatically; already-prefixed pairs (e.g. NSE_RELIANCE) " \
|
|
62
|
+
"are used as-is.",
|
|
63
|
+
},
|
|
64
|
+
exchange: {
|
|
65
|
+
type: "string",
|
|
66
|
+
enum: %w[NSE BSE],
|
|
67
|
+
description: "Exchange used to prefix bare symbols (default: NSE)",
|
|
68
|
+
},
|
|
69
|
+
segment: {
|
|
70
|
+
type: "string",
|
|
71
|
+
enum: %w[CASH FNO],
|
|
72
|
+
description: "Market segment (default: CASH)",
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
required: ["symbols"],
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
class << self
|
|
79
|
+
def call(server_context:, symbols:, exchange: "NSE", segment: "CASH")
|
|
80
|
+
if symbols.length > 50
|
|
81
|
+
return MCP::Tool::Response.new([{ type: "text", text: "❌ Maximum 50 symbols allowed per request." }])
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
client = server_context[:client]
|
|
85
|
+
result = client.ltp(symbols, exchange: exchange, segment: segment)
|
|
86
|
+
format_response(result)
|
|
87
|
+
rescue GrowwMcp::ForbiddenError
|
|
88
|
+
MCP::Tool::Response.new([{
|
|
89
|
+
type: "text",
|
|
90
|
+
text: "❌ Live Data access denied. Requires Groww Live Data subscription (₹499 + GST/month).",
|
|
91
|
+
}])
|
|
92
|
+
rescue GrowwMcp::ApiError => e
|
|
93
|
+
error_response(e)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
class GetOHLC < GrowwMcp::BaseTool
|
|
99
|
+
description "Get Open, High, Low, Close prices for multiple stocks (max 50). " \
|
|
100
|
+
"Symbols are sent to the API as comma-separated EXCHANGE_SYMBOL pairs " \
|
|
101
|
+
"(e.g. NSE_RELIANCE,NSE_TCS) — bare symbols are automatically prefixed with the exchange. " \
|
|
102
|
+
"⚠️ Requires Groww Live Data subscription (₹499/mo)."
|
|
103
|
+
|
|
104
|
+
input_schema(
|
|
105
|
+
properties: {
|
|
106
|
+
symbols: {
|
|
107
|
+
type: "array",
|
|
108
|
+
items: { type: "string" },
|
|
109
|
+
description: "Array of trading symbols (max 50). Bare symbols (e.g. RELIANCE) get the " \
|
|
110
|
+
"exchange prefix added automatically; already-prefixed pairs (e.g. NSE_RELIANCE) " \
|
|
111
|
+
"are used as-is.",
|
|
112
|
+
},
|
|
113
|
+
exchange: {
|
|
114
|
+
type: "string",
|
|
115
|
+
enum: %w[NSE BSE],
|
|
116
|
+
description: "Exchange used to prefix bare symbols (default: NSE)",
|
|
117
|
+
},
|
|
118
|
+
segment: {
|
|
119
|
+
type: "string",
|
|
120
|
+
enum: %w[CASH FNO],
|
|
121
|
+
description: "Market segment (default: CASH)",
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
required: ["symbols"],
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
class << self
|
|
128
|
+
def call(server_context:, symbols:, exchange: "NSE", segment: "CASH")
|
|
129
|
+
if symbols.length > 50
|
|
130
|
+
return MCP::Tool::Response.new([{ type: "text", text: "❌ Maximum 50 symbols allowed per request." }])
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
client = server_context[:client]
|
|
134
|
+
result = client.ohlc(symbols, exchange: exchange, segment: segment)
|
|
135
|
+
format_response(result)
|
|
136
|
+
rescue GrowwMcp::ForbiddenError
|
|
137
|
+
MCP::Tool::Response.new([{
|
|
138
|
+
type: "text",
|
|
139
|
+
text: "❌ Live Data access denied. Requires Groww Live Data subscription (₹499 + GST/month).",
|
|
140
|
+
}])
|
|
141
|
+
rescue GrowwMcp::ApiError => e
|
|
142
|
+
error_response(e)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
class GetHistoricalData < GrowwMcp::BaseTool
|
|
148
|
+
description "Get historical candlestick (OHLCV) data for a stock or F&O instrument. " \
|
|
149
|
+
"Useful for technical analysis and backtesting. " \
|
|
150
|
+
"⚠️ Requires Groww Live Data subscription (₹499/mo)."
|
|
151
|
+
|
|
152
|
+
input_schema(
|
|
153
|
+
properties: {
|
|
154
|
+
trading_symbol: {
|
|
155
|
+
type: "string",
|
|
156
|
+
description: "Trading symbol (e.g., RELIANCE, NIFTY24JUL25000CE)",
|
|
157
|
+
},
|
|
158
|
+
start_time: {
|
|
159
|
+
type: %w[integer string],
|
|
160
|
+
description: "Start time — either a \"yyyy-MM-dd HH:mm:ss\" string (e.g. \"2026-07-01 09:15:00\") " \
|
|
161
|
+
"or a numeric epoch timestamp (epoch seconds are auto-converted to the " \
|
|
162
|
+
"milliseconds the API expects; epoch milliseconds pass through as-is)",
|
|
163
|
+
},
|
|
164
|
+
end_time: {
|
|
165
|
+
type: %w[integer string],
|
|
166
|
+
description: "End time — either a \"yyyy-MM-dd HH:mm:ss\" string (e.g. \"2026-07-22 15:30:00\") " \
|
|
167
|
+
"or a numeric epoch timestamp (epoch seconds are auto-converted to the " \
|
|
168
|
+
"milliseconds the API expects; epoch milliseconds pass through as-is)",
|
|
169
|
+
},
|
|
170
|
+
exchange: {
|
|
171
|
+
type: "string",
|
|
172
|
+
enum: %w[NSE BSE],
|
|
173
|
+
description: "Exchange (default: NSE)",
|
|
174
|
+
},
|
|
175
|
+
segment: {
|
|
176
|
+
type: "string",
|
|
177
|
+
enum: %w[CASH FNO],
|
|
178
|
+
description: "Market segment (default: CASH)",
|
|
179
|
+
},
|
|
180
|
+
interval_in_minutes: {
|
|
181
|
+
type: "integer",
|
|
182
|
+
description: "Candle interval in minutes (optional — e.g. 1, 5, 15, 60, 1440 for daily)",
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
required: %w[trading_symbol start_time end_time],
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
class << self
|
|
189
|
+
def call(server_context:, trading_symbol:, start_time:, end_time:, exchange: "NSE", segment: "CASH",
|
|
190
|
+
interval_in_minutes: nil)
|
|
191
|
+
client = server_context[:client]
|
|
192
|
+
result = client.historical_candles(trading_symbol, start_time, end_time,
|
|
193
|
+
exchange: exchange, segment: segment,
|
|
194
|
+
interval_in_minutes: interval_in_minutes)
|
|
195
|
+
format_response(result)
|
|
196
|
+
rescue GrowwMcp::ForbiddenError
|
|
197
|
+
MCP::Tool::Response.new([{
|
|
198
|
+
type: "text",
|
|
199
|
+
text: "❌ Historical Data access denied. Requires Groww Live Data subscription (₹499 + GST/month).",
|
|
200
|
+
}])
|
|
201
|
+
rescue GrowwMcp::ApiError => e
|
|
202
|
+
error_response(e)
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GrowwMcp
|
|
4
|
+
module Tools
|
|
5
|
+
class GetOptionChain < GrowwMcp::BaseTool
|
|
6
|
+
description "Get the complete option chain for an underlying instrument (e.g., NIFTY, BANKNIFTY, RELIANCE) " \
|
|
7
|
+
"for a specific expiry date. " \
|
|
8
|
+
"Returns all available strike prices with CE and PE data including LTP, OI (Open Interest), " \
|
|
9
|
+
"volume, IV (Implied Volatility), and Greeks (Delta, Gamma, Theta, Vega). " \
|
|
10
|
+
"Essential for options analysis and strategy selection. " \
|
|
11
|
+
"⚠️ Requires Groww Live Data subscription (₹499/mo)."
|
|
12
|
+
|
|
13
|
+
input_schema(
|
|
14
|
+
properties: {
|
|
15
|
+
trading_symbol: {
|
|
16
|
+
type: "string",
|
|
17
|
+
description: "Underlying symbol (e.g., NIFTY, BANKNIFTY, RELIANCE)",
|
|
18
|
+
},
|
|
19
|
+
expiry_date: {
|
|
20
|
+
type: "string",
|
|
21
|
+
description: "Expiry date in yyyy-MM-dd format (e.g., 2026-07-30)",
|
|
22
|
+
},
|
|
23
|
+
exchange: {
|
|
24
|
+
type: "string",
|
|
25
|
+
enum: %w[NSE BSE],
|
|
26
|
+
description: "Exchange (default: NSE)",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
required: %w[trading_symbol expiry_date],
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
class << self
|
|
33
|
+
def call(server_context:, trading_symbol:, expiry_date:, exchange: "NSE")
|
|
34
|
+
client = server_context[:client]
|
|
35
|
+
result = client.option_chain(trading_symbol, expiry_date: expiry_date, exchange: exchange)
|
|
36
|
+
format_response(result)
|
|
37
|
+
rescue GrowwMcp::ForbiddenError
|
|
38
|
+
MCP::Tool::Response.new([{
|
|
39
|
+
type: "text",
|
|
40
|
+
text: "❌ Option Chain access denied. Requires Groww Live Data subscription (₹499 + GST/month). " \
|
|
41
|
+
"Subscribe at: Groww App → Settings → Trading APIs.",
|
|
42
|
+
}])
|
|
43
|
+
rescue GrowwMcp::ApiError => e
|
|
44
|
+
error_response(e)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
class GetGreeks < GrowwMcp::BaseTool
|
|
50
|
+
description "Get option Greeks (Delta, Gamma, Theta, Vega, Rho) and IV for a specific option contract. " \
|
|
51
|
+
"Provide the option's trading symbol, its underlying, and the expiry date. " \
|
|
52
|
+
"⚠️ Requires Groww Live Data subscription (₹499/mo)."
|
|
53
|
+
|
|
54
|
+
input_schema(
|
|
55
|
+
properties: {
|
|
56
|
+
trading_symbol: {
|
|
57
|
+
type: "string",
|
|
58
|
+
description: "Option contract trading symbol (e.g., NIFTY24JUL25000CE)",
|
|
59
|
+
},
|
|
60
|
+
underlying: {
|
|
61
|
+
type: "string",
|
|
62
|
+
description: "Underlying symbol (e.g., NIFTY, BANKNIFTY, RELIANCE)",
|
|
63
|
+
},
|
|
64
|
+
expiry: {
|
|
65
|
+
type: "string",
|
|
66
|
+
description: "Expiry date in yyyy-MM-dd format (e.g., 2026-07-30)",
|
|
67
|
+
},
|
|
68
|
+
exchange: {
|
|
69
|
+
type: "string",
|
|
70
|
+
enum: %w[NSE BSE],
|
|
71
|
+
description: "Exchange (default: NSE)",
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
required: %w[trading_symbol underlying expiry],
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
class << self
|
|
78
|
+
def call(server_context:, trading_symbol:, underlying:, expiry:, exchange: "NSE")
|
|
79
|
+
client = server_context[:client]
|
|
80
|
+
result = client.greeks(trading_symbol: trading_symbol, underlying: underlying,
|
|
81
|
+
expiry: expiry, exchange: exchange)
|
|
82
|
+
format_response(result)
|
|
83
|
+
rescue GrowwMcp::ForbiddenError
|
|
84
|
+
MCP::Tool::Response.new([{
|
|
85
|
+
type: "text",
|
|
86
|
+
text: "❌ Greeks access denied. Requires Groww Live Data subscription (₹499 + GST/month). " \
|
|
87
|
+
"Subscribe at: Groww App → Settings → Trading APIs.",
|
|
88
|
+
}])
|
|
89
|
+
rescue GrowwMcp::ApiError => e
|
|
90
|
+
error_response(e)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
class GetOrderTrades < GrowwMcp::BaseTool
|
|
96
|
+
description "Get all trade executions for a specific order. " \
|
|
97
|
+
"An order can have multiple partial fills — this shows each fill with price, quantity, and timestamp."
|
|
98
|
+
|
|
99
|
+
input_schema(
|
|
100
|
+
properties: {
|
|
101
|
+
order_id: {
|
|
102
|
+
type: "string",
|
|
103
|
+
description: "Groww order ID",
|
|
104
|
+
},
|
|
105
|
+
segment: {
|
|
106
|
+
type: "string",
|
|
107
|
+
enum: %w[CASH FNO],
|
|
108
|
+
description: "Market segment (default: CASH)",
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
required: ["order_id"],
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
class << self
|
|
115
|
+
def call(server_context:, order_id:, segment: "CASH")
|
|
116
|
+
client = server_context[:client]
|
|
117
|
+
result = client.order_trades(order_id, segment: segment)
|
|
118
|
+
format_response(result)
|
|
119
|
+
rescue GrowwMcp::ApiError => e
|
|
120
|
+
error_response(e)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "mcp"
|
|
4
|
+
|
|
5
|
+
module GrowwMcp
|
|
6
|
+
module Tools
|
|
7
|
+
class GetOrders < GrowwMcp::BaseTool
|
|
8
|
+
description "Get list of all orders. Optionally filter by market segment (CASH/FNO) " \
|
|
9
|
+
"and paginate results. Shows order status, prices, quantities, and timestamps."
|
|
10
|
+
|
|
11
|
+
input_schema(
|
|
12
|
+
properties: {
|
|
13
|
+
segment: {
|
|
14
|
+
type: "string",
|
|
15
|
+
enum: %w[CASH FNO],
|
|
16
|
+
description: "Filter by market segment (optional)",
|
|
17
|
+
},
|
|
18
|
+
page: {
|
|
19
|
+
type: "integer",
|
|
20
|
+
description: "Page number for pagination (optional)",
|
|
21
|
+
},
|
|
22
|
+
page_size: {
|
|
23
|
+
type: "integer",
|
|
24
|
+
description: "Number of orders per page (optional, default: 20)",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
class << self
|
|
30
|
+
def call(server_context:, segment: nil, page: nil, page_size: nil)
|
|
31
|
+
client = server_context[:client]
|
|
32
|
+
result = client.order_list(segment: segment, page: page, page_size: page_size)
|
|
33
|
+
format_response(result)
|
|
34
|
+
rescue GrowwMcp::ApiError => e
|
|
35
|
+
error_response(e)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class GetOrderDetail < GrowwMcp::BaseTool
|
|
41
|
+
description "Get detailed status of a specific order by its Groww order ID. " \
|
|
42
|
+
"Shows fill price, pending quantity, rejection reason, and timestamps."
|
|
43
|
+
|
|
44
|
+
input_schema(
|
|
45
|
+
properties: {
|
|
46
|
+
order_id: {
|
|
47
|
+
type: "string",
|
|
48
|
+
description: "Groww order ID",
|
|
49
|
+
},
|
|
50
|
+
segment: {
|
|
51
|
+
type: "string",
|
|
52
|
+
enum: %w[CASH FNO],
|
|
53
|
+
description: "Market segment (default: CASH)",
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
required: ["order_id"],
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
class << self
|
|
60
|
+
def call(server_context:, order_id:, segment: "CASH")
|
|
61
|
+
client = server_context[:client]
|
|
62
|
+
result = client.order_detail(order_id, segment: segment)
|
|
63
|
+
format_response(result)
|
|
64
|
+
rescue GrowwMcp::ApiError => e
|
|
65
|
+
error_response(e)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
class PlaceOrder < GrowwMcp::BaseTool
|
|
71
|
+
description "Place a buy or sell order on Groww. Supports MARKET, LIMIT, SL, and SL_M order types. " \
|
|
72
|
+
"For stocks use segment=CASH with product CNC (delivery) or MIS (intraday). " \
|
|
73
|
+
"For F&O use segment=FNO with product NRML. " \
|
|
74
|
+
"⚠️ This executes a REAL trade with REAL money. Always confirm with the user first."
|
|
75
|
+
|
|
76
|
+
input_schema(
|
|
77
|
+
properties: {
|
|
78
|
+
trading_symbol: {
|
|
79
|
+
type: "string",
|
|
80
|
+
description: "Stock/option symbol (e.g., RELIANCE, NIFTY24JUL25000CE)",
|
|
81
|
+
},
|
|
82
|
+
exchange: {
|
|
83
|
+
type: "string",
|
|
84
|
+
enum: %w[NSE BSE],
|
|
85
|
+
description: "Exchange (default: NSE)",
|
|
86
|
+
},
|
|
87
|
+
transaction_type: {
|
|
88
|
+
type: "string",
|
|
89
|
+
enum: %w[BUY SELL],
|
|
90
|
+
description: "Buy or sell",
|
|
91
|
+
},
|
|
92
|
+
order_type: {
|
|
93
|
+
type: "string",
|
|
94
|
+
enum: %w[MARKET LIMIT SL SL_M],
|
|
95
|
+
description: "MARKET=instant, LIMIT=at price, SL=stop loss limit, SL_M=stop loss market",
|
|
96
|
+
},
|
|
97
|
+
quantity: {
|
|
98
|
+
type: "integer",
|
|
99
|
+
description: "Number of shares/lots",
|
|
100
|
+
},
|
|
101
|
+
price: {
|
|
102
|
+
type: "number",
|
|
103
|
+
description: "Limit price (required for LIMIT and SL orders)",
|
|
104
|
+
},
|
|
105
|
+
trigger_price: {
|
|
106
|
+
type: "number",
|
|
107
|
+
description: "Trigger price (required for SL and SL_M orders)",
|
|
108
|
+
},
|
|
109
|
+
product: {
|
|
110
|
+
type: "string",
|
|
111
|
+
enum: %w[CNC MIS NRML],
|
|
112
|
+
description: "CNC=delivery, MIS=intraday, NRML=F&O",
|
|
113
|
+
},
|
|
114
|
+
segment: {
|
|
115
|
+
type: "string",
|
|
116
|
+
enum: %w[CASH FNO],
|
|
117
|
+
description: "Market segment (default: CASH)",
|
|
118
|
+
},
|
|
119
|
+
validity: {
|
|
120
|
+
type: "string",
|
|
121
|
+
enum: %w[DAY],
|
|
122
|
+
description: "Order validity (default: DAY)",
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
required: %w[trading_symbol transaction_type order_type quantity product],
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
class << self
|
|
129
|
+
def call(server_context:, trading_symbol:, transaction_type:, order_type:, quantity:, product:,
|
|
130
|
+
exchange: "NSE", price: nil, trigger_price: nil, segment: "CASH", validity: "DAY")
|
|
131
|
+
client = server_context[:client]
|
|
132
|
+
order = {
|
|
133
|
+
trading_symbol: trading_symbol,
|
|
134
|
+
exchange: exchange,
|
|
135
|
+
transaction_type: transaction_type,
|
|
136
|
+
order_type: order_type,
|
|
137
|
+
quantity: quantity,
|
|
138
|
+
product: product,
|
|
139
|
+
segment: segment,
|
|
140
|
+
validity: validity,
|
|
141
|
+
}
|
|
142
|
+
order[:price] = price if price
|
|
143
|
+
order[:trigger_price] = trigger_price if trigger_price
|
|
144
|
+
|
|
145
|
+
result = client.place_order(order)
|
|
146
|
+
format_response(result)
|
|
147
|
+
rescue GrowwMcp::ApiError => e
|
|
148
|
+
error_response(e)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
class ModifyOrder < GrowwMcp::BaseTool
|
|
154
|
+
description "Modify an existing open order (change quantity, price, or trigger price). " \
|
|
155
|
+
"The Groww API requires quantity, order_type, and segment on every modify — " \
|
|
156
|
+
"pass the order's current values for anything you are not changing."
|
|
157
|
+
|
|
158
|
+
input_schema(
|
|
159
|
+
properties: {
|
|
160
|
+
order_id: { type: "string", description: "Groww order ID to modify" },
|
|
161
|
+
quantity: { type: "integer", description: "Quantity (required — pass current value if unchanged)" },
|
|
162
|
+
order_type: { type: "string", enum: %w[MARKET LIMIT SL SL_M], description: "Order type (required — pass current value if unchanged)" },
|
|
163
|
+
segment: { type: "string", enum: %w[CASH FNO], description: "Market segment of the order" },
|
|
164
|
+
price: { type: "number", description: "New price (optional)" },
|
|
165
|
+
trigger_price: { type: "number", description: "New trigger price (optional)" },
|
|
166
|
+
},
|
|
167
|
+
required: %w[order_id quantity order_type segment],
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
class << self
|
|
171
|
+
def call(server_context:, order_id:, quantity:, order_type:, segment:, price: nil, trigger_price: nil)
|
|
172
|
+
client = server_context[:client]
|
|
173
|
+
mods = {
|
|
174
|
+
quantity: quantity,
|
|
175
|
+
order_type: order_type,
|
|
176
|
+
segment: segment,
|
|
177
|
+
}
|
|
178
|
+
mods[:price] = price if price
|
|
179
|
+
mods[:trigger_price] = trigger_price if trigger_price
|
|
180
|
+
|
|
181
|
+
result = client.modify_order(order_id, mods)
|
|
182
|
+
format_response(result)
|
|
183
|
+
rescue GrowwMcp::ApiError => e
|
|
184
|
+
error_response(e)
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
class CancelOrder < GrowwMcp::BaseTool
|
|
190
|
+
description "Cancel an open/pending order by its Groww order ID."
|
|
191
|
+
|
|
192
|
+
input_schema(
|
|
193
|
+
properties: {
|
|
194
|
+
order_id: { type: "string", description: "Groww order ID to cancel" },
|
|
195
|
+
segment: { type: "string", enum: %w[CASH FNO], description: "Market segment (default: CASH)" },
|
|
196
|
+
},
|
|
197
|
+
required: ["order_id"],
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
class << self
|
|
201
|
+
def call(server_context:, order_id:, segment: "CASH")
|
|
202
|
+
client = server_context[:client]
|
|
203
|
+
result = client.cancel_order(order_id, segment: segment)
|
|
204
|
+
format_response(result)
|
|
205
|
+
rescue GrowwMcp::ApiError => e
|
|
206
|
+
error_response(e)
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "mcp"
|
|
4
|
+
|
|
5
|
+
module GrowwMcp
|
|
6
|
+
module Tools
|
|
7
|
+
class GetHoldings < GrowwMcp::BaseTool
|
|
8
|
+
description "Get all current stock holdings from your DEMAT account. " \
|
|
9
|
+
"Returns each stock with ISIN, trading symbol, quantity, average buy price, " \
|
|
10
|
+
"pledged/locked quantities, and tradable exchanges."
|
|
11
|
+
|
|
12
|
+
input_schema(properties: {})
|
|
13
|
+
|
|
14
|
+
class << self
|
|
15
|
+
def call(server_context:)
|
|
16
|
+
client = server_context[:client]
|
|
17
|
+
result = client.holdings
|
|
18
|
+
format_response(result)
|
|
19
|
+
rescue GrowwMcp::ApiError => e
|
|
20
|
+
error_response(e)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class GetPositions < GrowwMcp::BaseTool
|
|
26
|
+
description "Get all open intraday and F&O positions with unrealized P&L. " \
|
|
27
|
+
"Optionally filter by market segment (CASH or FNO)."
|
|
28
|
+
|
|
29
|
+
input_schema(
|
|
30
|
+
properties: {
|
|
31
|
+
segment: {
|
|
32
|
+
type: "string",
|
|
33
|
+
enum: %w[CASH FNO],
|
|
34
|
+
description: "Filter by market segment (optional)",
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
class << self
|
|
40
|
+
def call(server_context:, segment: nil)
|
|
41
|
+
client = server_context[:client]
|
|
42
|
+
result = client.positions(segment: segment)
|
|
43
|
+
format_response(result)
|
|
44
|
+
rescue GrowwMcp::ApiError => e
|
|
45
|
+
error_response(e)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
class GetMargins < GrowwMcp::BaseTool
|
|
51
|
+
description "Get available margin and funds details including clear cash, collateral, " \
|
|
52
|
+
"equity margin (CNC/MIS), F&O margin (SPAN/exposure), and commodity margin."
|
|
53
|
+
|
|
54
|
+
input_schema(properties: {})
|
|
55
|
+
|
|
56
|
+
class << self
|
|
57
|
+
def call(server_context:)
|
|
58
|
+
client = server_context[:client]
|
|
59
|
+
result = client.margins
|
|
60
|
+
format_response(result)
|
|
61
|
+
rescue GrowwMcp::ApiError => e
|
|
62
|
+
error_response(e)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
class CalculateMargin < GrowwMcp::BaseTool
|
|
68
|
+
description "Calculate required margin for one or more orders before placing them. " \
|
|
69
|
+
"Useful to check if you have sufficient funds."
|
|
70
|
+
|
|
71
|
+
input_schema(
|
|
72
|
+
properties: {
|
|
73
|
+
segment: {
|
|
74
|
+
type: "string",
|
|
75
|
+
enum: %w[CASH FNO],
|
|
76
|
+
description: "Market segment for the whole basket (sent as query param)",
|
|
77
|
+
},
|
|
78
|
+
orders: {
|
|
79
|
+
type: "array",
|
|
80
|
+
description: "Array of order objects to calculate margin for (sent as the JSON request body)",
|
|
81
|
+
items: {
|
|
82
|
+
type: "object",
|
|
83
|
+
properties: {
|
|
84
|
+
trading_symbol: { type: "string", description: "Stock/option symbol (e.g., RELIANCE, NIFTY24JUL25000CE)" },
|
|
85
|
+
transaction_type: { type: "string", enum: %w[BUY SELL], description: "Buy or sell" },
|
|
86
|
+
quantity: { type: "integer", description: "Number of shares/lots" },
|
|
87
|
+
price: { type: "number", description: "Limit price (for LIMIT/SL orders)" },
|
|
88
|
+
order_type: { type: "string", enum: %w[MARKET LIMIT SL SL_M], description: "Order type" },
|
|
89
|
+
product: { type: "string", enum: %w[CNC MIS NRML], description: "CNC=delivery, MIS=intraday, NRML=F&O" },
|
|
90
|
+
},
|
|
91
|
+
required: %w[trading_symbol transaction_type quantity order_type product],
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
required: %w[segment orders],
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
class << self
|
|
99
|
+
def call(server_context:, segment:, orders:)
|
|
100
|
+
client = server_context[:client]
|
|
101
|
+
result = client.calculate_margin(orders, segment: segment)
|
|
102
|
+
format_response(result)
|
|
103
|
+
rescue GrowwMcp::ApiError => e
|
|
104
|
+
error_response(e)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|