bharatstock-mcp 0.1.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.
- package/CHANGELOG.md +24 -0
- package/LICENSE +21 -0
- package/README.md +129 -0
- package/dist/chunk-RPAWW7CN.js +456 -0
- package/dist/http.d.ts +8 -0
- package/dist/http.js +113 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.js +42 -0
- package/package.json +47 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `bharatstock-mcp` are documented here (newest first).
|
|
4
|
+
|
|
5
|
+
## 0.1.0 (2026-09-22)
|
|
6
|
+
|
|
7
|
+
- Initial release.
|
|
8
|
+
- Local **stdio** server (`bharatstock-mcp`) and remote **Streamable HTTP**
|
|
9
|
+
server (`bharatstock-mcp-http`) wrapping the official `bharatstock` JS SDK.
|
|
10
|
+
- Remote server is multi-tenant (per-request key) and accepts the key on
|
|
11
|
+
`Authorization: Bearer` or `X-API-Key`.
|
|
12
|
+
- MCP is a Developer/Pro feature, gated server-side via `/v1/mcp/authorize`
|
|
13
|
+
(checked at stdio startup and on every remote request).
|
|
14
|
+
- Tools: `get_stock_quote`, `get_company_profile`, `get_stock_quotes_batch`,
|
|
15
|
+
`get_stock_prices`, `get_financials`, `get_stock_fundamentals`,
|
|
16
|
+
`get_technical_indicators`, `get_shareholding`, `get_corporate_actions`,
|
|
17
|
+
`get_stock_bulk_deals`, `get_stock_block_deals`, `get_stock_insider_trades`,
|
|
18
|
+
`compare_sector_peers`, `search_stocks`, `get_stock_rank`,
|
|
19
|
+
`get_price_shockers`, `run_screener`, `list_indices`, `get_index_prices`,
|
|
20
|
+
`get_fii_dii_activity`, `get_bulk_deals`, `get_block_deals`,
|
|
21
|
+
`get_insider_trades`, `search_mutual_fund_schemes`,
|
|
22
|
+
`get_mutual_fund_scheme`, `get_mutual_fund_nav`, `get_mutual_fund_returns`,
|
|
23
|
+
`get_mutual_fund_holdings`, `get_data_status`.
|
|
24
|
+
- Reads `BHARATSTOCK_API_KEY` and optional `BHARATSTOCK_BASE_URL` from env.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 BharatStock
|
|
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,129 @@
|
|
|
1
|
+
# bharatstock-mcp
|
|
2
|
+
|
|
3
|
+
Official **Model Context Protocol (MCP)** server for the [BharatStock API](https://bharatstockapi.com) โ reliable Indian stock market data (NSE/BSE) exposed as tools your AI assistant can call directly.
|
|
4
|
+
|
|
5
|
+
Point any MCP host (Claude Desktop, Cursor, Kiro, VS Code, etc.) at this server and your model can ask for live quotes, financials, mutual-fund NAVs, screener results and more โ no code required.
|
|
6
|
+
|
|
7
|
+
๐ **[Jump to Changelog](#changelog)**
|
|
8
|
+
|
|
9
|
+
## What you need
|
|
10
|
+
|
|
11
|
+
- Node.js 20+
|
|
12
|
+
- A BharatStock API key (`bsk_live_...`) on the **Developer or Pro plan** โ MCP access is a paid feature. The server verifies your plan on startup and refuses to run for Free/Starter keys. Your plan's rate limit and history depth apply exactly as they do over REST. Sign up / upgrade at [bharatstockapi.com](https://bharatstockapi.com/#pricing).
|
|
13
|
+
|
|
14
|
+
## Setup
|
|
15
|
+
|
|
16
|
+
Add the server to your MCP host's config. Most hosts use a `mcpServers` block:
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"mcpServers": {
|
|
21
|
+
"bharatstock": {
|
|
22
|
+
"command": "npx",
|
|
23
|
+
"args": ["-y", "bharatstock-mcp"],
|
|
24
|
+
"env": { "BHARATSTOCK_API_KEY": "bsk_live_your_key_here" }
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`npx -y bharatstock-mcp` downloads and runs the server on demand โ no global install needed. Restart your host and the BharatStock tools appear.
|
|
31
|
+
|
|
32
|
+
### Environment variables
|
|
33
|
+
|
|
34
|
+
| Variable | Required | Description |
|
|
35
|
+
| --- | --- | --- |
|
|
36
|
+
| `BHARATSTOCK_API_KEY` | yes | Your `bsk_live_...` key. |
|
|
37
|
+
| `BHARATSTOCK_BASE_URL` | no | Override the API host (self-hosted/staging). Defaults to the public API. |
|
|
38
|
+
|
|
39
|
+
## Tools
|
|
40
|
+
|
|
41
|
+
Each tool wraps a consumer-facing BharatStock endpoint. Your model picks the right one from its description.
|
|
42
|
+
|
|
43
|
+
**Stocks**
|
|
44
|
+
- `get_stock_quote` โ latest price + market cap + headline metrics for a ticker
|
|
45
|
+
- `get_company_profile` โ company identity/classification for a ticker
|
|
46
|
+
- `get_stock_quotes_batch` โ latest snapshot for up to 50 tickers at once
|
|
47
|
+
- `get_stock_prices` โ historical daily OHLCV (split/bonus-adjusted)
|
|
48
|
+
- `get_financials` โ quarterly/annual P&L, balance sheet, cash flow
|
|
49
|
+
- `get_stock_fundamentals` โ current ratios (P/E, P/B, ROE, ROCE, EPS, yieldโฆ)
|
|
50
|
+
- `get_technical_indicators` โ daily indicators over a date range
|
|
51
|
+
- `get_shareholding` โ quarterly promoter/FII/DII/public holding
|
|
52
|
+
- `get_corporate_actions` โ dividends, bonuses, splits, rights, buybacks
|
|
53
|
+
- `get_stock_bulk_deals` / `get_stock_block_deals` โ deals for one stock
|
|
54
|
+
- `get_stock_insider_trades` โ SEBI PIT disclosures for one stock
|
|
55
|
+
- `compare_sector_peers` โ relative valuation across a sector
|
|
56
|
+
|
|
57
|
+
**Discovery & rankings**
|
|
58
|
+
- `search_stocks` โ resolve a name to a ticker
|
|
59
|
+
- `get_stock_rank` โ top gainers / losers / most-active
|
|
60
|
+
- `get_price_shockers` โ stocks moving beyond a % threshold
|
|
61
|
+
- `run_screener` โ fundamental screen (`pe_ratio.lt.15`, `roe.gt.18`, โฆ)
|
|
62
|
+
|
|
63
|
+
**Indices & market**
|
|
64
|
+
- `list_indices` โ indices with latest values
|
|
65
|
+
- `get_index_prices` โ historical index values
|
|
66
|
+
- `get_fii_dii_activity` โ institutional net buy/sell by day
|
|
67
|
+
- `get_bulk_deals` / `get_block_deals` / `get_insider_trades` โ market-wide
|
|
68
|
+
|
|
69
|
+
**Mutual funds**
|
|
70
|
+
- `search_mutual_fund_schemes` โ find a scheme_code across all AMCs
|
|
71
|
+
- `get_mutual_fund_scheme` โ scheme identity + latest NAV
|
|
72
|
+
- `get_mutual_fund_nav` โ daily NAV history
|
|
73
|
+
- `get_mutual_fund_returns` โ trailing returns (1mโฆ5y, CAGR)
|
|
74
|
+
- `get_mutual_fund_holdings` โ which funds hold a given stock
|
|
75
|
+
|
|
76
|
+
**Platform**
|
|
77
|
+
- `get_data_status` โ public data-integrity/freshness report
|
|
78
|
+
|
|
79
|
+
## Remote (hosted HTTP) mode
|
|
80
|
+
|
|
81
|
+
If your AI host connects to a **URL** instead of spawning a local process, use our hosted remote MCP endpoint โ no install:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
https://bharatstockapi.com/v1/mcp
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Config for a URL-based host:
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"mcpServers": {
|
|
92
|
+
"bharatstock": {
|
|
93
|
+
"url": "https://bharatstockapi.com/v1/mcp",
|
|
94
|
+
"headers": { "Authorization": "Bearer bsk_live_..." }
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Pass your key on **either** header (Bearer preferred): `Authorization: Bearer bsk_live_...` or `X-API-Key: bsk_live_...`. Same Developer/Pro gate and rate/limit rules as everywhere else. No OAuth โ the API key is the credential.
|
|
101
|
+
|
|
102
|
+
### Self-hosting the remote server (optional)
|
|
103
|
+
|
|
104
|
+
This package also ships a standalone remote server (`bharatstock-mcp-http`) if you'd rather run your own:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
BHARATSTOCK_BASE_URL=https://bharatstockapi.com PORT=8080 npx -y bharatstock-mcp-http
|
|
108
|
+
# POST /mcp (the MCP endpoint) | GET /healthz (liveness)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
It's stateless (a fresh server per request), multi-tenant (each request carries its own key), and validates/plan-gates every request before any tool runs.
|
|
112
|
+
|
|
113
|
+
## How it works
|
|
114
|
+
|
|
115
|
+
This server is a thin layer over the official [`bharatstock`](https://www.npmjs.com/package/bharatstock) JS SDK. Auth, retries, base URL and typed decoding all come from the SDK, so the tool set tracks the API surface. Tool results are returned as JSON text.
|
|
116
|
+
|
|
117
|
+
## Notes
|
|
118
|
+
|
|
119
|
+
- **MCP is a Developer/Pro feature.** On startup the server calls the API to check your plan; a Free/Starter key gets a clear "upgrade" message and the server exits without connecting.
|
|
120
|
+
- Free-tier keys are limited to a recent history window on deep-history tools (prices, financials, NAV, etc.); paid keys get the full archive โ identical to the REST behaviour.
|
|
121
|
+
- Errors from the API (invalid key, rate limit, not found) are returned as a tool error message the model can read, not a crash.
|
|
122
|
+
|
|
123
|
+
## License
|
|
124
|
+
|
|
125
|
+
MIT
|
|
126
|
+
|
|
127
|
+
## Changelog
|
|
128
|
+
|
|
129
|
+
- **0.1.0** (2026-09-22): initial release. Local stdio server (`bharatstock-mcp`) + remote Streamable HTTP server (`bharatstock-mcp-http`, multi-tenant, Bearer/X-API-Key auth). MCP is a Developer/Pro feature, gated via `/v1/mcp/authorize`. Tools for stocks (quote, profile, prices, financials, fundamentals, technicals, shareholding, corporate actions, deals, insider trades, peer compare), discovery (search, movers, price shockers, screener), indices, market-wide (FII/DII, deals, insider), and mutual funds (schemes, scheme, NAV, returns, holdings), plus data status.
|
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/auth.ts
|
|
4
|
+
var DEFAULT_UPGRADE_URL = "https://bharatstockapi.com/#pricing";
|
|
5
|
+
async function checkAuthorization(baseUrl, apiKey, fetchImpl = fetch) {
|
|
6
|
+
let res;
|
|
7
|
+
try {
|
|
8
|
+
res = await fetchImpl(`${baseUrl}/v1/mcp/authorize`, {
|
|
9
|
+
method: "GET",
|
|
10
|
+
headers: { "X-API-Key": apiKey, Accept: "application/json" }
|
|
11
|
+
});
|
|
12
|
+
} catch (err) {
|
|
13
|
+
return { ok: true };
|
|
14
|
+
}
|
|
15
|
+
if (res.ok) {
|
|
16
|
+
let plan;
|
|
17
|
+
try {
|
|
18
|
+
const body = await res.json();
|
|
19
|
+
plan = body?.plan;
|
|
20
|
+
} catch {
|
|
21
|
+
}
|
|
22
|
+
return { ok: true, plan };
|
|
23
|
+
}
|
|
24
|
+
if (res.status === 401) {
|
|
25
|
+
return { ok: false, status: 401, message: "Invalid or missing API key." };
|
|
26
|
+
}
|
|
27
|
+
if (res.status === 403) {
|
|
28
|
+
let message = "MCP access requires the Developer or Pro plan.";
|
|
29
|
+
let upgradeUrl = DEFAULT_UPGRADE_URL;
|
|
30
|
+
try {
|
|
31
|
+
const body = await res.json();
|
|
32
|
+
if (body?.detail?.message) message = body.detail.message;
|
|
33
|
+
if (body?.detail?.upgrade_url) upgradeUrl = body.detail.upgrade_url;
|
|
34
|
+
} catch {
|
|
35
|
+
}
|
|
36
|
+
return { ok: false, status: 403, message, upgradeUrl };
|
|
37
|
+
}
|
|
38
|
+
return { ok: true };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/server.ts
|
|
42
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
43
|
+
import { BharatStock } from "bharatstock";
|
|
44
|
+
import { z } from "zod";
|
|
45
|
+
var SERVER_NAME = "bharatstock";
|
|
46
|
+
var SERVER_VERSION = "0.1.0";
|
|
47
|
+
async function run(fn) {
|
|
48
|
+
try {
|
|
49
|
+
const data = await fn();
|
|
50
|
+
return {
|
|
51
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
52
|
+
};
|
|
53
|
+
} catch (err) {
|
|
54
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
55
|
+
return {
|
|
56
|
+
isError: true,
|
|
57
|
+
content: [{ type: "text", text: `BharatStock API error: ${message}` }]
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function buildServer(options = {}) {
|
|
62
|
+
const client = new BharatStock(options);
|
|
63
|
+
const server = new McpServer({ name: SERVER_NAME, version: SERVER_VERSION });
|
|
64
|
+
const ticker = z.string().describe("NSE/BSE ticker symbol (e.g. RELIANCE, TCS) or ISIN.");
|
|
65
|
+
const exchange = z.enum(["NSE", "BSE"]).optional().describe("Optional exchange to disambiguate a cross-listed stock.");
|
|
66
|
+
const fromDate = z.string().optional().describe("Start date, YYYY-MM-DD (inclusive).");
|
|
67
|
+
const toDate = z.string().optional().describe("End date, YYYY-MM-DD (inclusive).");
|
|
68
|
+
server.registerTool(
|
|
69
|
+
"get_stock_quote",
|
|
70
|
+
{
|
|
71
|
+
title: "Get stock quote",
|
|
72
|
+
description: "Latest snapshot for a stock: company name, latest price, day change, 52-week high/low, market cap, and headline valuation metrics (P/E, P/B, EPS). Use this for a single 'what is X trading at' question.",
|
|
73
|
+
inputSchema: { ticker, exchange }
|
|
74
|
+
},
|
|
75
|
+
async ({ ticker: ticker2, exchange: exchange2 }) => run(() => client.stocks.get(ticker2, { exchange: exchange2 }))
|
|
76
|
+
);
|
|
77
|
+
server.registerTool(
|
|
78
|
+
"get_company_profile",
|
|
79
|
+
{
|
|
80
|
+
title: "Get company profile",
|
|
81
|
+
description: "Company identity and classification for a stock: full company name, sector, exchange listing(s), and identifiers. This is the same detail record as get_stock_quote (which also carries price/metrics); use this when you only need who the company is, not the live price.",
|
|
82
|
+
inputSchema: { ticker, exchange }
|
|
83
|
+
},
|
|
84
|
+
async ({ ticker: ticker2, exchange: exchange2 }) => run(() => client.stocks.get(ticker2, { exchange: exchange2 }))
|
|
85
|
+
);
|
|
86
|
+
server.registerTool(
|
|
87
|
+
"get_stock_quotes_batch",
|
|
88
|
+
{
|
|
89
|
+
title: "Get quotes for multiple stocks",
|
|
90
|
+
description: "Latest price snapshot for up to 50 tickers in one call. Prefer this over many get_stock_quote calls when comparing several stocks.",
|
|
91
|
+
inputSchema: {
|
|
92
|
+
symbols: z.array(z.string()).min(1).max(50).describe("List of ticker symbols (max 50).")
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
async ({ symbols }) => run(() => client.stocks.quotes(symbols))
|
|
96
|
+
);
|
|
97
|
+
server.registerTool(
|
|
98
|
+
"get_stock_prices",
|
|
99
|
+
{
|
|
100
|
+
title: "Get historical daily prices",
|
|
101
|
+
description: "Historical end-of-day OHLCV for a stock, including bonus/split adjusted close. Paginated. Free-tier keys get a recent window; paid keys get the full multi-year archive.",
|
|
102
|
+
inputSchema: {
|
|
103
|
+
ticker,
|
|
104
|
+
fromDate,
|
|
105
|
+
toDate,
|
|
106
|
+
exchange,
|
|
107
|
+
page: z.number().int().positive().optional(),
|
|
108
|
+
pageSize: z.number().int().positive().max(500).optional()
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
async ({ ticker: ticker2, fromDate: fromDate2, toDate: toDate2, exchange: exchange2, page, pageSize }) => run(() => client.stocks.prices(ticker2, { fromDate: fromDate2, toDate: toDate2, exchange: exchange2, page, pageSize }))
|
|
112
|
+
);
|
|
113
|
+
server.registerTool(
|
|
114
|
+
"get_financials",
|
|
115
|
+
{
|
|
116
|
+
title: "Get financial statements",
|
|
117
|
+
description: "Quarterly or annual financial statements (P&L, balance sheet, cash flow) for a stock, as filed with the exchange. Set periodType to 'annual' for yearly figures or 'quarterly' for quarterly results.",
|
|
118
|
+
inputSchema: {
|
|
119
|
+
ticker,
|
|
120
|
+
periodType: z.enum(["quarterly", "annual"]).optional().describe("Reporting cadence. Defaults to quarterly."),
|
|
121
|
+
exchange,
|
|
122
|
+
page: z.number().int().positive().optional(),
|
|
123
|
+
pageSize: z.number().int().positive().max(50).optional()
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
async ({ ticker: ticker2, periodType, exchange: exchange2, page, pageSize }) => run(() => client.stocks.financials(ticker2, { periodType, exchange: exchange2, page, pageSize }))
|
|
127
|
+
);
|
|
128
|
+
server.registerTool(
|
|
129
|
+
"get_stock_fundamentals",
|
|
130
|
+
{
|
|
131
|
+
title: "Get stock fundamentals / ratios",
|
|
132
|
+
description: "Current computed valuation and profitability ratios for a stock: P/E, P/B, ROE, ROCE, EPS, dividend yield, margins, and more. Use this for 'is X expensive / how profitable is X' questions.",
|
|
133
|
+
inputSchema: { ticker, exchange }
|
|
134
|
+
},
|
|
135
|
+
async ({ ticker: ticker2, exchange: exchange2 }) => run(() => client.stocks.ratios(ticker2, { exchange: exchange2 }))
|
|
136
|
+
);
|
|
137
|
+
server.registerTool(
|
|
138
|
+
"get_technical_indicators",
|
|
139
|
+
{
|
|
140
|
+
title: "Get technical indicators",
|
|
141
|
+
description: "Daily technical indicators for a stock (moving averages, RSI, etc.) over a date range. Free-tier keys get a recent window.",
|
|
142
|
+
inputSchema: {
|
|
143
|
+
ticker,
|
|
144
|
+
fromDate,
|
|
145
|
+
toDate,
|
|
146
|
+
exchange,
|
|
147
|
+
page: z.number().int().positive().optional(),
|
|
148
|
+
pageSize: z.number().int().positive().max(500).optional()
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
async ({ ticker: ticker2, fromDate: fromDate2, toDate: toDate2, exchange: exchange2, page, pageSize }) => run(
|
|
152
|
+
() => client.stocks.technicalIndicators(ticker2, { fromDate: fromDate2, toDate: toDate2, exchange: exchange2, page, pageSize })
|
|
153
|
+
)
|
|
154
|
+
);
|
|
155
|
+
server.registerTool(
|
|
156
|
+
"get_shareholding",
|
|
157
|
+
{
|
|
158
|
+
title: "Get shareholding pattern",
|
|
159
|
+
description: "Quarterly shareholding pattern for a stock: promoter / FII / DII / public holding percentages over time.",
|
|
160
|
+
inputSchema: {
|
|
161
|
+
ticker,
|
|
162
|
+
exchange,
|
|
163
|
+
page: z.number().int().positive().optional(),
|
|
164
|
+
pageSize: z.number().int().positive().max(50).optional()
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
async ({ ticker: ticker2, exchange: exchange2, page, pageSize }) => run(() => client.stocks.shareholding(ticker2, { exchange: exchange2, page, pageSize }))
|
|
168
|
+
);
|
|
169
|
+
server.registerTool(
|
|
170
|
+
"get_corporate_actions",
|
|
171
|
+
{
|
|
172
|
+
title: "Get corporate actions",
|
|
173
|
+
description: "Corporate actions for a stock: dividends, bonuses, stock splits, rights issues, and buybacks, with ex-dates.",
|
|
174
|
+
inputSchema: {
|
|
175
|
+
ticker,
|
|
176
|
+
actionType: z.string().optional().describe("Filter by type, e.g. dividend, bonus, split."),
|
|
177
|
+
exchange,
|
|
178
|
+
page: z.number().int().positive().optional(),
|
|
179
|
+
pageSize: z.number().int().positive().max(50).optional()
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
async ({ ticker: ticker2, actionType, exchange: exchange2, page, pageSize }) => run(() => client.stocks.corporateActions(ticker2, { actionType, exchange: exchange2, page, pageSize }))
|
|
183
|
+
);
|
|
184
|
+
server.registerTool(
|
|
185
|
+
"get_stock_bulk_deals",
|
|
186
|
+
{
|
|
187
|
+
title: "Get bulk deals for a stock",
|
|
188
|
+
description: "Bulk deals reported for a specific stock.",
|
|
189
|
+
inputSchema: {
|
|
190
|
+
ticker,
|
|
191
|
+
page: z.number().int().positive().optional(),
|
|
192
|
+
pageSize: z.number().int().positive().max(100).optional()
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
async ({ ticker: ticker2, page, pageSize }) => run(() => client.stocks.bulkDeals(ticker2, { page, pageSize }))
|
|
196
|
+
);
|
|
197
|
+
server.registerTool(
|
|
198
|
+
"get_stock_block_deals",
|
|
199
|
+
{
|
|
200
|
+
title: "Get block deals for a stock",
|
|
201
|
+
description: "Block deals reported for a specific stock.",
|
|
202
|
+
inputSchema: {
|
|
203
|
+
ticker,
|
|
204
|
+
page: z.number().int().positive().optional(),
|
|
205
|
+
pageSize: z.number().int().positive().max(100).optional()
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
async ({ ticker: ticker2, page, pageSize }) => run(() => client.stocks.blockDeals(ticker2, { page, pageSize }))
|
|
209
|
+
);
|
|
210
|
+
server.registerTool(
|
|
211
|
+
"get_stock_insider_trades",
|
|
212
|
+
{
|
|
213
|
+
title: "Get insider trades for a stock",
|
|
214
|
+
description: "SEBI PIT insider-trading disclosures for a specific stock.",
|
|
215
|
+
inputSchema: {
|
|
216
|
+
ticker,
|
|
217
|
+
page: z.number().int().positive().optional(),
|
|
218
|
+
pageSize: z.number().int().positive().max(100).optional()
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
async ({ ticker: ticker2, page, pageSize }) => run(() => client.stocks.insiderTrades(ticker2, { page, pageSize }))
|
|
222
|
+
);
|
|
223
|
+
server.registerTool(
|
|
224
|
+
"compare_sector_peers",
|
|
225
|
+
{
|
|
226
|
+
title: "Compare peers in a sector",
|
|
227
|
+
description: "Compare valuation/profitability ratios across the peers in a sector, for a quick relative-value view.",
|
|
228
|
+
inputSchema: {
|
|
229
|
+
sector: z.string().describe("Sector name, e.g. 'Information Technology'."),
|
|
230
|
+
sort: z.string().optional().describe("Sort key, e.g. market_cap, pe_ratio."),
|
|
231
|
+
limit: z.number().int().positive().max(100).optional()
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
async ({ sector, sort, limit }) => run(() => client.stocks.compare(sector, { sort, limit }))
|
|
235
|
+
);
|
|
236
|
+
server.registerTool(
|
|
237
|
+
"search_stocks",
|
|
238
|
+
{
|
|
239
|
+
title: "Search stocks",
|
|
240
|
+
description: "Fuzzy-search listed stocks by ticker symbol or company name. Use this to resolve a company name to a ticker before calling other tools.",
|
|
241
|
+
inputSchema: {
|
|
242
|
+
q: z.string().describe("Search text (symbol fragment or company name)."),
|
|
243
|
+
limit: z.number().int().positive().max(50).optional()
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
async ({ q, limit }) => run(() => client.search(q, { limit }))
|
|
247
|
+
);
|
|
248
|
+
server.registerTool(
|
|
249
|
+
"get_stock_rank",
|
|
250
|
+
{
|
|
251
|
+
title: "Get top market movers",
|
|
252
|
+
description: "Ranked market movers: top gainers, top losers, or most-active stocks for the latest trading session. Use category to pick which.",
|
|
253
|
+
inputSchema: {
|
|
254
|
+
category: z.enum(["gainers", "losers", "active"]).optional().describe("Which ranking. Defaults to gainers."),
|
|
255
|
+
limit: z.number().int().positive().max(100).optional()
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
async ({ category, limit }) => run(() => client.movers({ category, limit }))
|
|
259
|
+
);
|
|
260
|
+
server.registerTool(
|
|
261
|
+
"get_price_shockers",
|
|
262
|
+
{
|
|
263
|
+
title: "Get price shockers",
|
|
264
|
+
description: "Stocks that moved at least a threshold percentage vs the previous close, in either direction.",
|
|
265
|
+
inputSchema: {
|
|
266
|
+
minChangePct: z.number().positive().optional().describe("Minimum abs % move. Default 5."),
|
|
267
|
+
direction: z.enum(["up", "down", "both"]).optional(),
|
|
268
|
+
limit: z.number().int().positive().max(100).optional()
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
async ({ minChangePct, direction, limit }) => run(() => client.priceShockers({ minChangePct, direction, limit }))
|
|
272
|
+
);
|
|
273
|
+
server.registerTool(
|
|
274
|
+
"run_screener",
|
|
275
|
+
{
|
|
276
|
+
title: "Run stock screener",
|
|
277
|
+
description: "Screen stocks by fundamental filters. `filters` is a list of 'metric.operator.value' strings, e.g. ['pe_ratio.lt.15', 'roe.gt.18']. marketCap filter values are in Crores. Optionally scope by sector.",
|
|
278
|
+
inputSchema: {
|
|
279
|
+
filters: z.array(z.string()).optional().describe("Filter expressions like 'pe_ratio.lt.15'."),
|
|
280
|
+
sector: z.string().optional(),
|
|
281
|
+
exchange,
|
|
282
|
+
sortBy: z.string().optional().describe("Sort key, default market_cap."),
|
|
283
|
+
sortOrder: z.enum(["asc", "desc"]).optional(),
|
|
284
|
+
page: z.number().int().positive().optional(),
|
|
285
|
+
pageSize: z.number().int().positive().max(100).optional()
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
async ({ filters, sector, exchange: exchange2, sortBy, sortOrder, page, pageSize }) => run(
|
|
289
|
+
() => client.screener.run({ filters, sector, exchange: exchange2, sortBy, sortOrder, page, pageSize })
|
|
290
|
+
)
|
|
291
|
+
);
|
|
292
|
+
server.registerTool(
|
|
293
|
+
"list_indices",
|
|
294
|
+
{
|
|
295
|
+
title: "List indices",
|
|
296
|
+
description: "List market indices (NIFTY 50, SENSEX, sectoral indices, etc.) with their latest values.",
|
|
297
|
+
inputSchema: {
|
|
298
|
+
page: z.number().int().positive().optional(),
|
|
299
|
+
pageSize: z.number().int().positive().max(200).optional()
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
async ({ page, pageSize }) => run(() => client.indices.list({ page, pageSize }))
|
|
303
|
+
);
|
|
304
|
+
server.registerTool(
|
|
305
|
+
"get_index_prices",
|
|
306
|
+
{
|
|
307
|
+
title: "Get historical index prices",
|
|
308
|
+
description: "Historical daily values for an index over a date range. Free-tier keys get a recent window; paid keys get the full archive.",
|
|
309
|
+
inputSchema: {
|
|
310
|
+
name: z.string().describe("Index name, e.g. 'NIFTY 50'."),
|
|
311
|
+
fromDate,
|
|
312
|
+
toDate,
|
|
313
|
+
page: z.number().int().positive().optional(),
|
|
314
|
+
pageSize: z.number().int().positive().max(500).optional()
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
async ({ name, fromDate: fromDate2, toDate: toDate2, page, pageSize }) => run(() => client.indices.prices(name, { fromDate: fromDate2, toDate: toDate2, page, pageSize }))
|
|
318
|
+
);
|
|
319
|
+
server.registerTool(
|
|
320
|
+
"get_fii_dii_activity",
|
|
321
|
+
{
|
|
322
|
+
title: "Get FII/DII activity",
|
|
323
|
+
description: "Foreign (FII) and domestic (DII) institutional investor net buy/sell activity (Rs Crores) by trading day. Pass latest=true for only the most recent day.",
|
|
324
|
+
inputSchema: {
|
|
325
|
+
fromDate,
|
|
326
|
+
toDate,
|
|
327
|
+
limit: z.number().int().positive().max(500).optional(),
|
|
328
|
+
latest: z.boolean().optional().describe("Return only the most recent day.")
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
async ({ fromDate: fromDate2, toDate: toDate2, limit, latest }) => run(() => client.market.fiiDii({ fromDate: fromDate2, toDate: toDate2, limit, latest }))
|
|
332
|
+
);
|
|
333
|
+
server.registerTool(
|
|
334
|
+
"get_bulk_deals",
|
|
335
|
+
{
|
|
336
|
+
title: "Get market-wide bulk deals",
|
|
337
|
+
description: "Bulk deals across the whole market for the latest sessions.",
|
|
338
|
+
inputSchema: {
|
|
339
|
+
buySell: z.enum(["BUY", "SELL"]).optional(),
|
|
340
|
+
page: z.number().int().positive().optional(),
|
|
341
|
+
pageSize: z.number().int().positive().max(100).optional()
|
|
342
|
+
}
|
|
343
|
+
},
|
|
344
|
+
async ({ buySell, page, pageSize }) => run(() => client.deals.bulk({ buySell, page, pageSize }))
|
|
345
|
+
);
|
|
346
|
+
server.registerTool(
|
|
347
|
+
"get_block_deals",
|
|
348
|
+
{
|
|
349
|
+
title: "Get market-wide block deals",
|
|
350
|
+
description: "Block deals across the whole market for the latest sessions.",
|
|
351
|
+
inputSchema: {
|
|
352
|
+
buySell: z.enum(["BUY", "SELL"]).optional(),
|
|
353
|
+
page: z.number().int().positive().optional(),
|
|
354
|
+
pageSize: z.number().int().positive().max(100).optional()
|
|
355
|
+
}
|
|
356
|
+
},
|
|
357
|
+
async ({ buySell, page, pageSize }) => run(() => client.deals.block({ buySell, page, pageSize }))
|
|
358
|
+
);
|
|
359
|
+
server.registerTool(
|
|
360
|
+
"get_insider_trades",
|
|
361
|
+
{
|
|
362
|
+
title: "Get market-wide insider trades",
|
|
363
|
+
description: "SEBI PIT insider-trading disclosures across the whole market.",
|
|
364
|
+
inputSchema: {
|
|
365
|
+
page: z.number().int().positive().optional(),
|
|
366
|
+
pageSize: z.number().int().positive().max(100).optional()
|
|
367
|
+
}
|
|
368
|
+
},
|
|
369
|
+
async ({ page, pageSize }) => run(() => client.deals.insiderTrades({ page, pageSize }))
|
|
370
|
+
);
|
|
371
|
+
server.registerTool(
|
|
372
|
+
"search_mutual_fund_schemes",
|
|
373
|
+
{
|
|
374
|
+
title: "Search mutual-fund schemes",
|
|
375
|
+
description: "List/search mutual-fund schemes across all AMCs (industry-wide AMFI dataset). Filter by name (q), AMC, category, plan (Direct/Regular), option (Growth/IDCW), or exact ISIN. Use this to find a scheme_code.",
|
|
376
|
+
inputSchema: {
|
|
377
|
+
q: z.string().optional(),
|
|
378
|
+
amc: z.string().optional(),
|
|
379
|
+
category: z.string().optional(),
|
|
380
|
+
plan: z.string().optional(),
|
|
381
|
+
option: z.string().optional(),
|
|
382
|
+
isin: z.string().optional(),
|
|
383
|
+
limit: z.number().int().positive().max(200).optional(),
|
|
384
|
+
offset: z.number().int().nonnegative().optional()
|
|
385
|
+
}
|
|
386
|
+
},
|
|
387
|
+
async (args) => run(() => client.mutualFunds.listSchemes(args))
|
|
388
|
+
);
|
|
389
|
+
server.registerTool(
|
|
390
|
+
"get_mutual_fund_scheme",
|
|
391
|
+
{
|
|
392
|
+
title: "Get mutual-fund scheme detail",
|
|
393
|
+
description: "One scheme's identity plus its latest NAV (with as-of date).",
|
|
394
|
+
inputSchema: {
|
|
395
|
+
schemeCode: z.string().describe("AMFI scheme code.")
|
|
396
|
+
}
|
|
397
|
+
},
|
|
398
|
+
async ({ schemeCode }) => run(() => client.mutualFunds.getScheme(schemeCode))
|
|
399
|
+
);
|
|
400
|
+
server.registerTool(
|
|
401
|
+
"get_mutual_fund_nav",
|
|
402
|
+
{
|
|
403
|
+
title: "Get mutual-fund NAV history",
|
|
404
|
+
description: "Daily NAV history for a scheme, most recent first. Free-tier keys are limited to a recent window; paid keys get the full archive.",
|
|
405
|
+
inputSchema: {
|
|
406
|
+
schemeCode: z.string().describe("AMFI scheme code."),
|
|
407
|
+
fromDate,
|
|
408
|
+
toDate,
|
|
409
|
+
limit: z.number().int().positive().max(2e3).optional()
|
|
410
|
+
}
|
|
411
|
+
},
|
|
412
|
+
async ({ schemeCode, fromDate: fromDate2, toDate: toDate2, limit }) => run(() => client.mutualFunds.nav(schemeCode, { fromDate: fromDate2, toDate: toDate2, limit }))
|
|
413
|
+
);
|
|
414
|
+
server.registerTool(
|
|
415
|
+
"get_mutual_fund_returns",
|
|
416
|
+
{
|
|
417
|
+
title: "Get mutual-fund trailing returns",
|
|
418
|
+
description: "Trailing returns (1m/3m/6m/1y/3y/5y + since-inception) computed from the scheme's NAV series (CAGR for >=1y horizons).",
|
|
419
|
+
inputSchema: {
|
|
420
|
+
schemeCode: z.string().describe("AMFI scheme code.")
|
|
421
|
+
}
|
|
422
|
+
},
|
|
423
|
+
async ({ schemeCode }) => run(() => client.mutualFunds.returns(schemeCode))
|
|
424
|
+
);
|
|
425
|
+
server.registerTool(
|
|
426
|
+
"get_mutual_fund_holdings",
|
|
427
|
+
{
|
|
428
|
+
title: "Get mutual-fund holdings for a stock",
|
|
429
|
+
description: "Which mutual-fund schemes hold a given stock (portfolio positions aggregated per stock), optionally for a specific portfolio month. Answers 'which funds own X'.",
|
|
430
|
+
inputSchema: {
|
|
431
|
+
ticker,
|
|
432
|
+
month: z.string().optional().describe("Portfolio month YYYY-MM-DD (month-end). Latest if omitted."),
|
|
433
|
+
page: z.number().int().positive().optional(),
|
|
434
|
+
pageSize: z.number().int().positive().max(200).optional()
|
|
435
|
+
}
|
|
436
|
+
},
|
|
437
|
+
async ({ ticker: ticker2, month, page, pageSize }) => run(() => client.stocks.mfHoldings(ticker2, { month, page, pageSize }))
|
|
438
|
+
);
|
|
439
|
+
server.registerTool(
|
|
440
|
+
"get_data_status",
|
|
441
|
+
{
|
|
442
|
+
title: "Get data-integrity status",
|
|
443
|
+
description: "Public data-trust report: freshness and correctness status of the BharatStock pipelines. No sensitive detail.",
|
|
444
|
+
inputSchema: {}
|
|
445
|
+
},
|
|
446
|
+
async () => run(() => client.status())
|
|
447
|
+
);
|
|
448
|
+
return server;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
export {
|
|
452
|
+
checkAuthorization,
|
|
453
|
+
SERVER_NAME,
|
|
454
|
+
SERVER_VERSION,
|
|
455
|
+
buildServer
|
|
456
|
+
};
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import * as http from 'http';
|
|
2
|
+
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
3
|
+
|
|
4
|
+
/** Extract the API key from Authorization: Bearer or X-API-Key. */
|
|
5
|
+
declare function extractApiKey(headers: IncomingMessage["headers"]): string | undefined;
|
|
6
|
+
declare function createHttpServer(): http.Server<typeof IncomingMessage, typeof ServerResponse>;
|
|
7
|
+
|
|
8
|
+
export { createHttpServer, extractApiKey };
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
buildServer,
|
|
4
|
+
checkAuthorization
|
|
5
|
+
} from "./chunk-RPAWW7CN.js";
|
|
6
|
+
|
|
7
|
+
// src/http.ts
|
|
8
|
+
import { randomUUID } from "crypto";
|
|
9
|
+
import { createServer } from "http";
|
|
10
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
11
|
+
import { DEFAULT_BASE_URL } from "bharatstock";
|
|
12
|
+
var PORT = Number(process.env.PORT || 8080);
|
|
13
|
+
var BASE_URL = (process.env.BHARATSTOCK_BASE_URL || DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
14
|
+
function extractApiKey(headers) {
|
|
15
|
+
const auth = headers["authorization"];
|
|
16
|
+
if (typeof auth === "string") {
|
|
17
|
+
const m = auth.match(/^Bearer\s+(.+)$/i);
|
|
18
|
+
if (m) return m[1].trim();
|
|
19
|
+
}
|
|
20
|
+
const xApiKey = headers["x-api-key"];
|
|
21
|
+
if (typeof xApiKey === "string" && xApiKey.trim()) return xApiKey.trim();
|
|
22
|
+
if (Array.isArray(xApiKey) && xApiKey[0]) return xApiKey[0].trim();
|
|
23
|
+
return void 0;
|
|
24
|
+
}
|
|
25
|
+
function sendJson(res, status, body) {
|
|
26
|
+
const payload = JSON.stringify(body);
|
|
27
|
+
res.writeHead(status, { "Content-Type": "application/json" });
|
|
28
|
+
res.end(payload);
|
|
29
|
+
}
|
|
30
|
+
function jsonRpcError(res, status, message) {
|
|
31
|
+
sendJson(res, status, {
|
|
32
|
+
jsonrpc: "2.0",
|
|
33
|
+
error: { code: status === 401 || status === 403 ? -32001 : -32e3, message },
|
|
34
|
+
id: null
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
async function readBody(req) {
|
|
38
|
+
const chunks = [];
|
|
39
|
+
for await (const chunk of req) chunks.push(chunk);
|
|
40
|
+
if (chunks.length === 0) return void 0;
|
|
41
|
+
const raw = Buffer.concat(chunks).toString("utf8");
|
|
42
|
+
try {
|
|
43
|
+
return JSON.parse(raw);
|
|
44
|
+
} catch {
|
|
45
|
+
return void 0;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async function handleMcp(req, res) {
|
|
49
|
+
const apiKey = extractApiKey(req.headers);
|
|
50
|
+
if (!apiKey) {
|
|
51
|
+
jsonRpcError(res, 401, "Missing API key. Pass Authorization: Bearer <key> or X-API-Key.");
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const gate = await checkAuthorization(BASE_URL, apiKey);
|
|
55
|
+
if (!gate.ok) {
|
|
56
|
+
const suffix = gate.upgradeUrl ? ` Upgrade at ${gate.upgradeUrl}` : "";
|
|
57
|
+
jsonRpcError(res, gate.status, `${gate.message}${suffix}`);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const body = await readBody(req);
|
|
61
|
+
const server = buildServer({ apiKey, baseUrl: BASE_URL });
|
|
62
|
+
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
|
|
63
|
+
res.on("close", () => {
|
|
64
|
+
void transport.close();
|
|
65
|
+
void server.close();
|
|
66
|
+
});
|
|
67
|
+
try {
|
|
68
|
+
await server.connect(transport);
|
|
69
|
+
await transport.handleRequest(req, res, body);
|
|
70
|
+
} catch (err) {
|
|
71
|
+
if (!res.headersSent) {
|
|
72
|
+
jsonRpcError(res, 500, `Internal server error: ${String(err)}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function createHttpServer() {
|
|
77
|
+
return createServer((req, res) => {
|
|
78
|
+
const url = req.url || "/";
|
|
79
|
+
const path = url.split("?")[0];
|
|
80
|
+
if (req.method === "GET" && path === "/healthz") {
|
|
81
|
+
sendJson(res, 200, { status: "ok", service: "bharatstock-mcp-http" });
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (path === "/mcp") {
|
|
85
|
+
if (req.method === "POST") {
|
|
86
|
+
void handleMcp(req, res);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
res.writeHead(405, { "Content-Type": "application/json", Allow: "POST" }).end(
|
|
90
|
+
JSON.stringify({
|
|
91
|
+
jsonrpc: "2.0",
|
|
92
|
+
error: { code: -32e3, message: "Method not allowed. This endpoint is POST-only (stateless)." },
|
|
93
|
+
id: null
|
|
94
|
+
})
|
|
95
|
+
);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
jsonRpcError(res, 404, "Not found. The MCP endpoint is POST /mcp.");
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function main() {
|
|
102
|
+
const server = createHttpServer();
|
|
103
|
+
server.listen(PORT, () => {
|
|
104
|
+
console.log(`bharatstock-mcp-http listening on :${PORT} (POST /mcp), API ${BASE_URL} [${randomUUID().slice(0, 8)}]`);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
if (process.env.NODE_ENV !== "test") {
|
|
108
|
+
main();
|
|
109
|
+
}
|
|
110
|
+
export {
|
|
111
|
+
createHttpServer,
|
|
112
|
+
extractApiKey
|
|
113
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { BharatStockOptions } from 'bharatstock';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Builds the BharatStock MCP server: a thin layer that exposes the
|
|
6
|
+
* BharatStock REST API's consumer-facing (X-API-Key gated) endpoints as
|
|
7
|
+
* Model Context Protocol tools.
|
|
8
|
+
*
|
|
9
|
+
* Every tool is a thin wrapper over the official `bharatstock` JS SDK, so
|
|
10
|
+
* this server inherits the SDK's auth, base URL, retry/backoff and typed
|
|
11
|
+
* decoding, and stays in sync with the API surface. When the SDK adds a
|
|
12
|
+
* response field, it flows through automatically (results are serialized as
|
|
13
|
+
* JSON); a genuinely NEW endpoint is the only case that needs a new tool.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
declare const SERVER_NAME = "bharatstock";
|
|
17
|
+
declare const SERVER_VERSION = "0.1.0";
|
|
18
|
+
/**
|
|
19
|
+
* Construct the MCP server and register all tools against a BharatStock
|
|
20
|
+
* client. Exposed separately from the stdio wiring so tests can build a
|
|
21
|
+
* server with an injected `fetchImpl` and drive it over an in-memory
|
|
22
|
+
* transport (no network, no real key).
|
|
23
|
+
*/
|
|
24
|
+
declare function buildServer(options?: BharatStockOptions): McpServer;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Shared plan-gate check for both the stdio and HTTP transports.
|
|
28
|
+
*
|
|
29
|
+
* MCP access is a Developer/Pro feature. The authoritative gate is
|
|
30
|
+
* server-side: `GET /v1/mcp/authorize` on the BharatStock API returns 200
|
|
31
|
+
* for an eligible key, 401 for an invalid key, and 403 for a valid key on an
|
|
32
|
+
* ineligible plan. Every MCP tool call is also a real X-API-Key request, so
|
|
33
|
+
* this check is about failing fast with a clear message, not the last line
|
|
34
|
+
* of defence.
|
|
35
|
+
*/
|
|
36
|
+
type AuthResult = {
|
|
37
|
+
ok: true;
|
|
38
|
+
plan?: string;
|
|
39
|
+
} | {
|
|
40
|
+
ok: false;
|
|
41
|
+
status: number;
|
|
42
|
+
message: string;
|
|
43
|
+
upgradeUrl?: string;
|
|
44
|
+
};
|
|
45
|
+
/** Call GET /v1/mcp/authorize with the given key. `baseUrl` must not have a
|
|
46
|
+
* trailing slash. `fetchImpl` is injectable for tests. */
|
|
47
|
+
declare function checkAuthorization(baseUrl: string, apiKey: string, fetchImpl?: typeof fetch): Promise<AuthResult>;
|
|
48
|
+
|
|
49
|
+
export { SERVER_NAME, SERVER_VERSION, buildServer, checkAuthorization };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
SERVER_NAME,
|
|
4
|
+
SERVER_VERSION,
|
|
5
|
+
buildServer,
|
|
6
|
+
checkAuthorization
|
|
7
|
+
} from "./chunk-RPAWW7CN.js";
|
|
8
|
+
|
|
9
|
+
// src/index.ts
|
|
10
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
11
|
+
import { DEFAULT_BASE_URL } from "bharatstock";
|
|
12
|
+
async function main() {
|
|
13
|
+
const apiKey = process.env.BHARATSTOCK_API_KEY;
|
|
14
|
+
if (!apiKey) {
|
|
15
|
+
process.stderr.write(
|
|
16
|
+
"bharatstock-mcp: BHARATSTOCK_API_KEY is not set. Set it in your MCP host config's `env` block. Get a key at https://bharatstockapi.com\n"
|
|
17
|
+
);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
const baseUrl = (process.env.BHARATSTOCK_BASE_URL || DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
21
|
+
const gate = await checkAuthorization(baseUrl, apiKey);
|
|
22
|
+
if (!gate.ok) {
|
|
23
|
+
const suffix = gate.upgradeUrl ? ` Upgrade at ${gate.upgradeUrl}` : "";
|
|
24
|
+
process.stderr.write(`bharatstock-mcp: ${gate.message}${suffix}
|
|
25
|
+
`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
const server = buildServer({ apiKey, baseUrl });
|
|
29
|
+
const transport = new StdioServerTransport();
|
|
30
|
+
await server.connect(transport);
|
|
31
|
+
}
|
|
32
|
+
main().catch((err) => {
|
|
33
|
+
process.stderr.write(`bharatstock-mcp failed to start: ${String(err)}
|
|
34
|
+
`);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
});
|
|
37
|
+
export {
|
|
38
|
+
SERVER_NAME,
|
|
39
|
+
SERVER_VERSION,
|
|
40
|
+
buildServer,
|
|
41
|
+
checkAuthorization
|
|
42
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bharatstock-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Official Model Context Protocol (MCP) server for the BharatStock API - reliable Indian stock market data (NSE/BSE) as AI-agent tools.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"bharatstock-mcp": "./dist/index.js",
|
|
9
|
+
"bharatstock-mcp-http": "./dist/http.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"README.md",
|
|
14
|
+
"CHANGELOG.md"
|
|
15
|
+
],
|
|
16
|
+
"keywords": [
|
|
17
|
+
"mcp",
|
|
18
|
+
"model-context-protocol",
|
|
19
|
+
"nse",
|
|
20
|
+
"bse",
|
|
21
|
+
"india",
|
|
22
|
+
"stocks",
|
|
23
|
+
"market-data",
|
|
24
|
+
"finance",
|
|
25
|
+
"ai-tools"
|
|
26
|
+
],
|
|
27
|
+
"homepage": "https://bharatstockapi.com",
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=20"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsup",
|
|
33
|
+
"test": "vitest run",
|
|
34
|
+
"typecheck": "tsc --noEmit"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
38
|
+
"bharatstock": "^0.1.8",
|
|
39
|
+
"zod": "^3.23.8"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^22.0.0",
|
|
43
|
+
"tsup": "^8.0.2",
|
|
44
|
+
"typescript": "^5.5.4",
|
|
45
|
+
"vitest": "^5.0.0"
|
|
46
|
+
}
|
|
47
|
+
}
|