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 +21 -0
- package/README.md +120 -0
- package/build/api.d.ts +90 -0
- package/build/api.js +246 -0
- package/build/config.d.ts +15 -0
- package/build/config.js +42 -0
- package/build/guards.d.ts +7 -0
- package/build/guards.js +46 -0
- package/build/index.d.ts +2 -0
- package/build/index.js +24 -0
- package/build/tools/auth.d.ts +3 -0
- package/build/tools/auth.js +65 -0
- package/build/tools/calculator.d.ts +3 -0
- package/build/tools/calculator.js +137 -0
- package/build/tools/gtt.d.ts +4 -0
- package/build/tools/gtt.js +297 -0
- package/build/tools/index.d.ts +4 -0
- package/build/tools/index.js +16 -0
- package/build/tools/market.d.ts +3 -0
- package/build/tools/market.js +399 -0
- package/build/tools/orders.d.ts +4 -0
- package/build/tools/orders.js +331 -0
- package/build/tools/portfolio.d.ts +4 -0
- package/build/tools/portfolio.js +186 -0
- package/build/tools/user.d.ts +3 -0
- package/build/tools/user.js +32 -0
- package/build/totp.d.ts +1 -0
- package/build/totp.js +12 -0
- package/build/types.d.ts +186 -0
- package/build/types.js +10 -0
- package/package.json +72 -0
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;
|
package/build/config.js
ADDED
|
@@ -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;
|
package/build/guards.js
ADDED
|
@@ -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
|
+
}
|
package/build/index.d.ts
ADDED
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);
|