angel-one-mcp 1.0.1

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mohamed Ameer Noufil N (https://ameernoufil.in)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,120 @@
1
+ # Angel One MCP Server
2
+
3
+ A [Model Context Protocol](https://modelcontextprotocol.io) server that exposes [Angel One SmartAPI](https://smartapi.angelone.in/) as tools for LLM clients like Claude Desktop, Claude Code, and other MCP-compatible apps.
4
+
5
+ Trade stocks, track portfolios, analyze markets, and manage orders — all through natural language.
6
+
7
+ ## Features
8
+
9
+ - **30+ tools** covering orders, portfolio, market data, GTT rules, and more
10
+ - **Auto TOTP** — generates login codes automatically, no manual entry
11
+ - **Lazy authentication** — logs in on first API call, refreshes tokens proactively
12
+ - **Safety guards** — two-tier soft/hard limits on order quantity and value to prevent fat-finger trades
13
+ - **Zero SDK dependency** — direct `fetch()` calls to Angel One REST API
14
+
15
+ ---
16
+
17
+ ## Quick Start
18
+
19
+ > Copy the prompt below and paste it into your AI agent. It will handle the rest.
20
+
21
+ ```
22
+ Install and configure the Angel One MCP server by following the instructions here:
23
+ https://raw.githubusercontent.com/ameernoufil/angel-one-mcp/main/docs/llm-setup.md
24
+ ```
25
+
26
+ ---
27
+
28
+ ## Manual Setup
29
+
30
+ ### Prerequisites
31
+
32
+ - Node.js 18+ (use `nvm use` if you have [nvm](https://github.com/nvm-sh/nvm))
33
+ - Angel One trading account
34
+ - SmartAPI key from [smartapi.angelone.in](https://smartapi.angelone.in/)
35
+ - TOTP authenticator set up on your Angel One account
36
+
37
+ ### 1. Get your credentials
38
+
39
+ 1. Log in to [SmartAPI portal](https://smartapi.angelone.in/)
40
+ 2. Create an app to get your **API Key**
41
+ 3. Note your **Client ID** (trading account code)
42
+ 4. Set up TOTP and save the **Base32 secret** (shown during TOTP setup, not the QR code)
43
+
44
+ ### 2. Install and configure
45
+
46
+ ```bash
47
+ git clone https://github.com/ameernoufil/angel-one-mcp.git
48
+ cd angel-one-mcp
49
+ npm install
50
+ cp .env.example .env
51
+ ```
52
+
53
+ Edit `.env` with your credentials:
54
+
55
+ ```env
56
+ ANGEL_API_KEY=your_smartapi_key
57
+ ANGEL_CLIENT_ID=your_client_id
58
+ ANGEL_PASSWORD=your_mpin
59
+ ANGEL_TOTP_SECRET=your_base32_totp_secret
60
+ ```
61
+
62
+ ### 3. Build and run
63
+
64
+ ```bash
65
+ npm run build
66
+ npm start
67
+ ```
68
+
69
+ ### 4. Add to your MCP client
70
+
71
+ Add to your MCP client config (e.g. Claude Desktop, Claude Code, Cursor):
72
+
73
+ ```json
74
+ {
75
+ "mcpServers": {
76
+ "angel-one": {
77
+ "type": "stdio",
78
+ "command": "node",
79
+ "args": ["/absolute/path/to/angel-one-mcp/build/index.js"],
80
+ "env": {
81
+ "ANGEL_API_KEY": "your_smartapi_key",
82
+ "ANGEL_CLIENT_ID": "your_client_id",
83
+ "ANGEL_PASSWORD": "your_mpin",
84
+ "ANGEL_TOTP_SECRET": "your_base32_totp_secret"
85
+ }
86
+ }
87
+ }
88
+ }
89
+ ```
90
+
91
+ Or if using `.env`, omit the `env` block — the server loads `.env` automatically.
92
+
93
+ ### Safety Limits Configuration
94
+
95
+ **Soft limits** (bypassable with `force: true`):
96
+
97
+ | Limit | Default | Env Var |
98
+ |-------|---------|---------|
99
+ | Max order quantity | 25 | `SOFT_MAX_ORDER_QTY` |
100
+ | Max order value | ₹10,000 | `SOFT_MAX_ORDER_VALUE` |
101
+
102
+ **Hard limits** (env var change + restart):
103
+
104
+ | Limit | Default | Env Var |
105
+ |-------|---------|---------|
106
+ | Max order quantity | 100 | `HARD_MAX_ORDER_QTY` |
107
+ | Max order value | ₹1,00,000 | `HARD_MAX_ORDER_VALUE` |
108
+
109
+ ### Development
110
+
111
+ ```bash
112
+ npm run dev # Watch mode
113
+ npm run lint # ESLint
114
+ npm run lint:fix # ESLint with auto-fix
115
+ npm run format # Prettier
116
+ ```
117
+
118
+ ## License
119
+
120
+ [MIT](LICENSE)
package/build/api.d.ts ADDED
@@ -0,0 +1,90 @@
1
+ import type { Config } from "./config.js";
2
+ import type { CancelGttRuleParams, CancelOrderParams, ConvertPositionParams, CreateGttRuleParams, ModifyGttRuleParams, ModifyOrderParams, PlaceOrderParams } from "./types.js";
3
+ export declare class AngelOneAPI {
4
+ private config;
5
+ private jwtToken;
6
+ private refreshToken;
7
+ private feedToken;
8
+ private tokenExpiry;
9
+ private loginPromise;
10
+ constructor(config: Config);
11
+ private getHeaders;
12
+ private request;
13
+ ensureAuthenticated(): Promise<void>;
14
+ login(): Promise<void>;
15
+ logout(): Promise<void>;
16
+ private refreshTokens;
17
+ private authenticatedRequest;
18
+ getProfile<T>(): Promise<T | null>;
19
+ getRMS<T>(): Promise<T | null>;
20
+ getHoldings<T>(): Promise<T | null>;
21
+ getPositions<T>(): Promise<T | null>;
22
+ getOrderBook<T>(): Promise<T | null>;
23
+ getTradeBook<T>(): Promise<T | null>;
24
+ placeOrder<T>(params: PlaceOrderParams): Promise<T | null>;
25
+ modifyOrder<T>(params: ModifyOrderParams): Promise<T | null>;
26
+ cancelOrder<T>(params: CancelOrderParams): Promise<T | null>;
27
+ getLtpData<T>(params: {
28
+ exchange: string;
29
+ tradingsymbol: string;
30
+ symboltoken: string;
31
+ }): Promise<T | null>;
32
+ searchScrip<T>(params: {
33
+ exchange: string;
34
+ searchscrip: string;
35
+ }): Promise<T | null>;
36
+ getCandleData<T>(params: {
37
+ exchange: string;
38
+ symboltoken: string;
39
+ interval: string;
40
+ fromdate: string;
41
+ todate: string;
42
+ }): Promise<T | null>;
43
+ getMarketQuote<T>(params: {
44
+ mode: string;
45
+ exchangeTokens: Record<string, string[]>;
46
+ }): Promise<T | null>;
47
+ createGttRule<T>(params: CreateGttRuleParams): Promise<T | null>;
48
+ modifyGttRule<T>(params: ModifyGttRuleParams): Promise<T | null>;
49
+ cancelGttRule<T>(params: CancelGttRuleParams): Promise<T | null>;
50
+ getGttRuleDetails<T>(params: {
51
+ id: string;
52
+ }): Promise<T | null>;
53
+ getGttRuleList<T>(params: {
54
+ status: string[];
55
+ page: number;
56
+ count: number;
57
+ }): Promise<T | null>;
58
+ getAllHoldings<T>(): Promise<T | null>;
59
+ convertPosition<T>(params: ConvertPositionParams): Promise<T | null>;
60
+ getOrderDetails<T>(orderId: string): Promise<T | null>;
61
+ getOIData<T>(params: {
62
+ exchange: string;
63
+ symboltoken: string;
64
+ interval: string;
65
+ fromdate: string;
66
+ todate: string;
67
+ }): Promise<T | null>;
68
+ getOptionGreeks<T>(params: {
69
+ name: string;
70
+ expirydate: string;
71
+ }): Promise<T | null>;
72
+ getGainersLosers<T>(params: {
73
+ datatype: string;
74
+ expirytype: string;
75
+ }): Promise<T | null>;
76
+ getPutCallRatio<T>(): Promise<T | null>;
77
+ getOIBuildup<T>(params: {
78
+ expirytype: string;
79
+ datatype: string;
80
+ }): Promise<T | null>;
81
+ getNseIntraday<T>(): Promise<T | null>;
82
+ getBseIntraday<T>(): Promise<T | null>;
83
+ estimateMargin<T>(params: {
84
+ positions: unknown[];
85
+ }): Promise<T | null>;
86
+ estimateCharges<T>(params: {
87
+ orders: unknown[];
88
+ }): Promise<T | null>;
89
+ isAuthenticated(): boolean;
90
+ }
package/build/api.js ADDED
@@ -0,0 +1,246 @@
1
+ import { generateTOTP } from "./totp.js";
2
+ import { AngelOneError } from "./types.js";
3
+ const BASE_URL = "https://apiconnect.angelone.in";
4
+ export class AngelOneAPI {
5
+ config;
6
+ jwtToken = null;
7
+ refreshToken = null;
8
+ feedToken = null;
9
+ tokenExpiry = 0;
10
+ loginPromise = null;
11
+ constructor(config) {
12
+ this.config = config;
13
+ }
14
+ getHeaders(authenticated = false) {
15
+ const headers = {
16
+ "Content-Type": "application/json",
17
+ Accept: "application/json",
18
+ "X-UserType": "USER",
19
+ "X-SourceID": "WEB",
20
+ "X-ClientLocalIP": "127.0.0.1",
21
+ "X-ClientPublicIP": "127.0.0.1",
22
+ "X-MACAddress": "AA:AA:AA:AA:AA:AA",
23
+ "X-PrivateKey": this.config.apiKey,
24
+ };
25
+ if (authenticated && this.jwtToken) {
26
+ headers["Authorization"] = `Bearer ${this.jwtToken}`;
27
+ }
28
+ return headers;
29
+ }
30
+ async request(method, endpoint, body, authenticated = true) {
31
+ const url = `${BASE_URL}${endpoint}`;
32
+ const headers = this.getHeaders(authenticated);
33
+ const options = { method, headers };
34
+ if (body) {
35
+ options.body = JSON.stringify(body);
36
+ }
37
+ const response = await fetch(url, options);
38
+ const contentType = response.headers.get("content-type") ?? "";
39
+ if (!contentType.includes("application/json")) {
40
+ throw new AngelOneError(`HTTP ${response.status}: ${response.statusText} (non-JSON response)`, "HTTP_ERROR", response.status);
41
+ }
42
+ const data = (await response.json());
43
+ if (!data.status) {
44
+ throw new AngelOneError(data.message || "API request failed", data.errorcode || "UNKNOWN", response.status);
45
+ }
46
+ if (data.data === null || data.data === undefined) {
47
+ return null;
48
+ }
49
+ return data.data;
50
+ }
51
+ async ensureAuthenticated() {
52
+ if (this.jwtToken && Date.now() < this.tokenExpiry - 5 * 60 * 1000) {
53
+ return;
54
+ }
55
+ if (this.jwtToken && this.refreshToken && Date.now() < this.tokenExpiry) {
56
+ try {
57
+ await this.refreshTokens();
58
+ return;
59
+ }
60
+ catch {
61
+ // Refresh failed, fall through to full login
62
+ }
63
+ }
64
+ await this.login();
65
+ }
66
+ async login() {
67
+ if (this.loginPromise) {
68
+ await this.loginPromise;
69
+ return;
70
+ }
71
+ this.loginPromise = (async () => {
72
+ try {
73
+ const totp = generateTOTP(this.config.totpSecret);
74
+ const data = await this.request("POST", "/rest/auth/angelbroking/user/v1/loginByPassword", {
75
+ clientcode: this.config.clientId,
76
+ password: this.config.password,
77
+ totp,
78
+ }, false);
79
+ if (!data) {
80
+ throw new AngelOneError("Login succeeded but returned no data", "LOGIN_NO_DATA", 200);
81
+ }
82
+ this.jwtToken = data.jwtToken;
83
+ this.refreshToken = data.refreshToken;
84
+ this.feedToken = data.feedToken;
85
+ this.tokenExpiry = Date.now() + 24 * 60 * 60 * 1000;
86
+ }
87
+ catch (error) {
88
+ this.jwtToken = null;
89
+ this.refreshToken = null;
90
+ this.feedToken = null;
91
+ this.tokenExpiry = 0;
92
+ throw error;
93
+ }
94
+ finally {
95
+ this.loginPromise = null;
96
+ }
97
+ })();
98
+ await this.loginPromise;
99
+ }
100
+ async logout() {
101
+ if (this.jwtToken) {
102
+ try {
103
+ await this.request("POST", "/rest/secure/angelbroking/user/v1/logout", {
104
+ clientcode: this.config.clientId,
105
+ }, true);
106
+ }
107
+ catch {
108
+ // Best-effort — clear tokens regardless
109
+ }
110
+ }
111
+ this.jwtToken = null;
112
+ this.refreshToken = null;
113
+ this.feedToken = null;
114
+ this.tokenExpiry = 0;
115
+ }
116
+ async refreshTokens() {
117
+ const data = await this.request("POST", "/rest/auth/angelbroking/jwt/v1/generateTokens", { refreshToken: this.refreshToken }, true);
118
+ if (!data) {
119
+ throw new AngelOneError("Token refresh returned no data", "REFRESH_NO_DATA", 200);
120
+ }
121
+ this.jwtToken = data.jwtToken;
122
+ this.refreshToken = data.refreshToken;
123
+ this.feedToken = data.feedToken;
124
+ this.tokenExpiry = Date.now() + 24 * 60 * 60 * 1000;
125
+ }
126
+ async authenticatedRequest(method, endpoint, body) {
127
+ await this.ensureAuthenticated();
128
+ try {
129
+ return await this.request(method, endpoint, body, true);
130
+ }
131
+ catch (error) {
132
+ if (error instanceof AngelOneError &&
133
+ (error.errorcode === "AG8001" || error.statusCode === 401)) {
134
+ this.jwtToken = null;
135
+ await this.login();
136
+ return await this.request(method, endpoint, body, true);
137
+ }
138
+ throw error;
139
+ }
140
+ }
141
+ // User
142
+ async getProfile() {
143
+ return this.authenticatedRequest("GET", "/rest/secure/angelbroking/user/v1/getProfile");
144
+ }
145
+ async getRMS() {
146
+ return this.authenticatedRequest("GET", "/rest/secure/angelbroking/user/v1/getRMS");
147
+ }
148
+ // Portfolio
149
+ async getHoldings() {
150
+ return this.authenticatedRequest("GET", "/rest/secure/angelbroking/portfolio/v1/getHolding");
151
+ }
152
+ async getPositions() {
153
+ return this.authenticatedRequest("GET", "/rest/secure/angelbroking/order/v1/getPosition");
154
+ }
155
+ // Orders
156
+ async getOrderBook() {
157
+ return this.authenticatedRequest("GET", "/rest/secure/angelbroking/order/v1/getOrderBook");
158
+ }
159
+ async getTradeBook() {
160
+ return this.authenticatedRequest("GET", "/rest/secure/angelbroking/order/v1/getTradeBook");
161
+ }
162
+ async placeOrder(params) {
163
+ return this.authenticatedRequest("POST", "/rest/secure/angelbroking/order/v1/placeOrder", params);
164
+ }
165
+ async modifyOrder(params) {
166
+ return this.authenticatedRequest("POST", "/rest/secure/angelbroking/order/v1/modifyOrder", params);
167
+ }
168
+ async cancelOrder(params) {
169
+ return this.authenticatedRequest("POST", "/rest/secure/angelbroking/order/v1/cancelOrder", params);
170
+ }
171
+ // Market Data
172
+ async getLtpData(params) {
173
+ return this.authenticatedRequest("POST", "/rest/secure/angelbroking/order/v1/getLtpData", params);
174
+ }
175
+ async searchScrip(params) {
176
+ return this.authenticatedRequest("POST", "/rest/secure/angelbroking/order/v1/searchScrip", params);
177
+ }
178
+ async getCandleData(params) {
179
+ return this.authenticatedRequest("POST", "/rest/secure/angelbroking/historical/v1/getCandleData", params);
180
+ }
181
+ async getMarketQuote(params) {
182
+ return this.authenticatedRequest("POST", "/rest/secure/angelbroking/market/v1/quote", params);
183
+ }
184
+ // GTT
185
+ async createGttRule(params) {
186
+ return this.authenticatedRequest("POST", "/gtt-service/rest/secure/angelbroking/gtt/v1/createRule", params);
187
+ }
188
+ async modifyGttRule(params) {
189
+ return this.authenticatedRequest("POST", "/gtt-service/rest/secure/angelbroking/gtt/v1/modifyRule", params);
190
+ }
191
+ async cancelGttRule(params) {
192
+ return this.authenticatedRequest("POST", "/gtt-service/rest/secure/angelbroking/gtt/v1/cancelRule", params);
193
+ }
194
+ async getGttRuleDetails(params) {
195
+ return this.authenticatedRequest("POST", "/rest/secure/angelbroking/gtt/v1/ruleDetails", params);
196
+ }
197
+ async getGttRuleList(params) {
198
+ return this.authenticatedRequest("POST", "/rest/secure/angelbroking/gtt/v1/ruleList", params);
199
+ }
200
+ // Extended Portfolio
201
+ async getAllHoldings() {
202
+ return this.authenticatedRequest("GET", "/rest/secure/angelbroking/portfolio/v1/getAllHolding");
203
+ }
204
+ async convertPosition(params) {
205
+ return this.authenticatedRequest("POST", "/rest/secure/angelbroking/order/v1/convertPosition", params);
206
+ }
207
+ // Individual Order
208
+ async getOrderDetails(orderId) {
209
+ if (!/^\d+$/.test(orderId)) {
210
+ throw new AngelOneError("Invalid orderId: must contain only digits", "INVALID_PARAM", 400);
211
+ }
212
+ return this.authenticatedRequest("GET", `/rest/secure/angelbroking/order/v1/details/${orderId}`);
213
+ }
214
+ // Market Analytics
215
+ async getOIData(params) {
216
+ return this.authenticatedRequest("POST", "/rest/secure/angelbroking/historical/v1/getOIData", params);
217
+ }
218
+ async getOptionGreeks(params) {
219
+ return this.authenticatedRequest("POST", "/rest/secure/angelbroking/marketData/v1/optionGreek", params);
220
+ }
221
+ async getGainersLosers(params) {
222
+ return this.authenticatedRequest("POST", "/rest/secure/angelbroking/marketData/v1/gainersLosers", params);
223
+ }
224
+ async getPutCallRatio() {
225
+ return this.authenticatedRequest("GET", "/rest/secure/angelbroking/marketData/v1/putCallRatio");
226
+ }
227
+ async getOIBuildup(params) {
228
+ return this.authenticatedRequest("POST", "/rest/secure/angelbroking/marketData/v1/OIBuildup", params);
229
+ }
230
+ async getNseIntraday() {
231
+ return this.authenticatedRequest("GET", "/rest/secure/angelbroking/marketData/v1/nseIntraday");
232
+ }
233
+ async getBseIntraday() {
234
+ return this.authenticatedRequest("GET", "/rest/secure/angelbroking/marketData/v1/bseIntraday");
235
+ }
236
+ // Calculator
237
+ async estimateMargin(params) {
238
+ return this.authenticatedRequest("POST", "/rest/secure/angelbroking/margin/v1/batch", params);
239
+ }
240
+ async estimateCharges(params) {
241
+ return this.authenticatedRequest("POST", "/rest/secure/angelbroking/brokerage/v1/estimateCharges", params);
242
+ }
243
+ isAuthenticated() {
244
+ return this.jwtToken !== null && Date.now() < this.tokenExpiry;
245
+ }
246
+ }
@@ -0,0 +1,15 @@
1
+ export interface SafetyConfig {
2
+ softMaxOrderQty: number;
3
+ hardMaxOrderQty: number;
4
+ softMaxOrderValue: number;
5
+ hardMaxOrderValue: number;
6
+ }
7
+ export interface Credentials {
8
+ apiKey: string;
9
+ clientId: string;
10
+ password: string;
11
+ totpSecret: string;
12
+ }
13
+ export interface Config extends Credentials, SafetyConfig {
14
+ }
15
+ export declare function loadConfig(): Config;
@@ -0,0 +1,42 @@
1
+ export function loadConfig() {
2
+ const apiKey = process.env.ANGEL_API_KEY;
3
+ const clientId = process.env.ANGEL_CLIENT_ID;
4
+ const password = process.env.ANGEL_PASSWORD;
5
+ const totpSecret = process.env.ANGEL_TOTP_SECRET;
6
+ if (!apiKey || !clientId || !password || !totpSecret) {
7
+ const missing = [
8
+ !apiKey && "ANGEL_API_KEY",
9
+ !clientId && "ANGEL_CLIENT_ID",
10
+ !password && "ANGEL_PASSWORD",
11
+ !totpSecret && "ANGEL_TOTP_SECRET",
12
+ ].filter(Boolean);
13
+ throw new Error(`Missing required env vars: ${missing.join(", ")}`);
14
+ }
15
+ const parseIntSafe = (name, val, fallback) => {
16
+ if (!val)
17
+ return fallback;
18
+ const parsed = Math.floor(Number(val));
19
+ if (!Number.isFinite(parsed) || parsed <= 0) {
20
+ process.stderr.write(`Warning: invalid value "${val}" for ${name}, using default ${fallback}\n`);
21
+ return fallback;
22
+ }
23
+ return parsed;
24
+ };
25
+ const config = {
26
+ apiKey,
27
+ clientId,
28
+ password,
29
+ totpSecret,
30
+ softMaxOrderQty: parseIntSafe("SOFT_MAX_ORDER_QTY", process.env.SOFT_MAX_ORDER_QTY, 25),
31
+ hardMaxOrderQty: parseIntSafe("HARD_MAX_ORDER_QTY", process.env.HARD_MAX_ORDER_QTY, 100),
32
+ softMaxOrderValue: parseIntSafe("SOFT_MAX_ORDER_VALUE", process.env.SOFT_MAX_ORDER_VALUE, 10000),
33
+ hardMaxOrderValue: parseIntSafe("HARD_MAX_ORDER_VALUE", process.env.HARD_MAX_ORDER_VALUE, 100000),
34
+ };
35
+ if (config.softMaxOrderQty > config.hardMaxOrderQty) {
36
+ throw new Error(`SOFT_MAX_ORDER_QTY (${config.softMaxOrderQty}) must be ≤ HARD_MAX_ORDER_QTY (${config.hardMaxOrderQty})`);
37
+ }
38
+ if (config.softMaxOrderValue > config.hardMaxOrderValue) {
39
+ throw new Error(`SOFT_MAX_ORDER_VALUE (${config.softMaxOrderValue}) must be ≤ HARD_MAX_ORDER_VALUE (${config.hardMaxOrderValue})`);
40
+ }
41
+ return config;
42
+ }
@@ -0,0 +1,7 @@
1
+ import type { SafetyConfig } from "./config.js";
2
+ export interface GuardResult {
3
+ allowed: boolean;
4
+ error?: string;
5
+ warning?: string;
6
+ }
7
+ export declare function validateOrderLimits(config: SafetyConfig, qty: number, price: number | null, force: boolean): GuardResult;
@@ -0,0 +1,46 @@
1
+ export function validateOrderLimits(config, qty, price, force) {
2
+ if (!Number.isFinite(qty) || qty <= 0) {
3
+ return { allowed: false, error: `Invalid quantity: must be a finite positive number` };
4
+ }
5
+ if (qty > config.hardMaxOrderQty) {
6
+ return {
7
+ allowed: false,
8
+ error: `HARD LIMIT: Quantity ${qty} exceeds maximum ${config.hardMaxOrderQty}. Change HARD_MAX_ORDER_QTY env var to increase.`,
9
+ };
10
+ }
11
+ if (price !== null) {
12
+ if (!Number.isFinite(price) || price <= 0) {
13
+ return { allowed: false, error: `Invalid price: must be a finite positive number` };
14
+ }
15
+ const value = qty * price;
16
+ if (value > config.hardMaxOrderValue) {
17
+ return {
18
+ allowed: false,
19
+ error: `HARD LIMIT: Order value ₹${value.toLocaleString("en-IN")} (${qty} × ₹${price}) exceeds maximum ₹${config.hardMaxOrderValue.toLocaleString("en-IN")}. Change HARD_MAX_ORDER_VALUE env var to increase.`,
20
+ };
21
+ }
22
+ }
23
+ if (!force) {
24
+ if (qty > config.softMaxOrderQty) {
25
+ return {
26
+ allowed: false,
27
+ error: `Quantity ${qty} exceeds soft limit ${config.softMaxOrderQty}. Pass force: true to override, or change SOFT_MAX_ORDER_QTY env var.`,
28
+ };
29
+ }
30
+ if (price !== null) {
31
+ const value = qty * price;
32
+ if (value > config.softMaxOrderValue) {
33
+ return {
34
+ allowed: false,
35
+ error: `Order value ₹${value.toLocaleString("en-IN")} (${qty} × ₹${price}) exceeds soft limit ₹${config.softMaxOrderValue.toLocaleString("en-IN")}. Pass force: true to override, or change SOFT_MAX_ORDER_VALUE env var.`,
36
+ };
37
+ }
38
+ }
39
+ }
40
+ return {
41
+ allowed: true,
42
+ ...(price === null && {
43
+ warning: "MARKET order: value-based safety limits cannot be enforced without a known price. Only quantity limits applied.",
44
+ }),
45
+ };
46
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import "dotenv/config";
package/build/index.js ADDED
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ import "dotenv/config";
3
+ import { readFileSync } from "node:fs";
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import { AngelOneAPI } from "./api.js";
7
+ import { loadConfig } from "./config.js";
8
+ import { registerAllTools } from "./tools/index.js";
9
+ const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
10
+ const config = loadConfig();
11
+ const api = new AngelOneAPI(config);
12
+ const safetyConfig = {
13
+ softMaxOrderQty: config.softMaxOrderQty,
14
+ hardMaxOrderQty: config.hardMaxOrderQty,
15
+ softMaxOrderValue: config.softMaxOrderValue,
16
+ hardMaxOrderValue: config.hardMaxOrderValue,
17
+ };
18
+ const server = new McpServer({
19
+ name: "angel-one",
20
+ version: pkg.version,
21
+ });
22
+ registerAllTools(server, api, safetyConfig);
23
+ const transport = new StdioServerTransport();
24
+ await server.connect(transport);
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { AngelOneAPI } from "../api.js";
3
+ export declare function registerAuthTools(server: McpServer, api: AngelOneAPI): void;