bharatstock-mcp 0.1.2 → 0.2.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 +30 -0
- package/README.md +4 -0
- package/dist/{chunk-LW5UFTIW.js → chunk-NXHQTUBJ.js} +190 -79
- package/dist/http.js +1 -1
- package/dist/index.d.ts +41 -2
- package/dist/index.js +6 -2
- package/package.json +24 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `bharatstock-mcp` are documented here (newest first).
|
|
4
4
|
|
|
5
|
+
## 0.2.0 (2026-11-06)
|
|
6
|
+
- Added an internal **operator mode** (not for customers). When the
|
|
7
|
+
`BHARATSTOCK_ADMIN_SECRET` env var is set alongside the API key, the server
|
|
8
|
+
additionally registers four support-ticket tools — `get_support_tickets`,
|
|
9
|
+
`get_support_ticket`, `set_ticket_status`, `log_ticket_event` — which call
|
|
10
|
+
the hosted MCP endpoint with that secret to list, inspect, and update
|
|
11
|
+
support tickets. A normal customer config (no admin secret) never registers
|
|
12
|
+
them, and the hosted server independently re-checks the secret, so the
|
|
13
|
+
consumer surface is unchanged.
|
|
14
|
+
|
|
15
|
+
## 0.1.5 (2026-11-03)
|
|
16
|
+
- Broadened the listing metadata for discoverability: a keyword-rich
|
|
17
|
+
`description` and expanded `keywords` (NSE, BSE, NIFTY, SENSEX, mutual funds,
|
|
18
|
+
NAVs, shareholding, FII/DII, corporate actions, bulk/block deals, insider
|
|
19
|
+
trades, fundamentals screener, Indian equities). No tool/schema change.
|
|
20
|
+
|
|
21
|
+
## 0.1.4 (2026-11-03)
|
|
22
|
+
- Published to the official MCP Registry as
|
|
23
|
+
`com.bharatstockapi/bharatstock-mcp`. No tool/schema change.
|
|
24
|
+
|
|
25
|
+
## 0.1.3 (2026-11-02)
|
|
26
|
+
|
|
27
|
+
- Bumped the `bharatstock` JS SDK dependency to ^0.1.12. The financials tool
|
|
28
|
+
output now carries the as-filed segment reconciliation fields:
|
|
29
|
+
`segmentResultsTotal` (the filing's own segment total — the anchor the
|
|
30
|
+
per-segment results roll up toward PBIT) and `segmentNotes` / `resultsNotes`
|
|
31
|
+
(the narrative note blocks; the segment note carries the unallocated /
|
|
32
|
+
elimination / exceptional reconciliation in prose). Pass-through via the SDK;
|
|
33
|
+
no tool/schema change.
|
|
34
|
+
|
|
5
35
|
## 0.1.2 (2026-10-05)
|
|
6
36
|
|
|
7
37
|
- Bumped the `bharatstock` JS SDK dependency to ^0.1.11, which made paginated
|
package/README.md
CHANGED
|
@@ -128,6 +128,10 @@ MIT
|
|
|
128
128
|
|
|
129
129
|
## Changelog
|
|
130
130
|
|
|
131
|
+
- **0.2.0** (2026-11-06): added an internal operator mode (not for customers). Setting `BHARATSTOCK_ADMIN_SECRET` alongside the API key registers four support-ticket management tools that call the hosted MCP endpoint with that secret. A normal customer config (no admin secret) is unaffected — the consumer tool surface is unchanged.
|
|
132
|
+
- **0.1.5** (2026-11-03): broadened the registry/npm listing metadata — a keyword-rich `description` and expanded `keywords` (NSE, BSE, NIFTY, SENSEX, mutual funds, shareholding, FII/DII, corporate actions, screener, Indian equities) so the server is easier to find. No tool/schema change.
|
|
133
|
+
- **0.1.4** (2026-11-03): published to the official MCP Registry as `com.bharatstockapi/bharatstock-mcp`. No tool/schema change.
|
|
134
|
+
- **0.1.3** (2026-11-02): picks up `bharatstock` JS SDK 0.1.12, so the financials tool output now carries the as-filed segment reconciliation fields — `segmentResultsTotal` (the filing's own segment total) and `segmentNotes` / `resultsNotes` (the narrative note blocks; the segment note carries the unallocated/elimination/exceptional reconciliation in prose). Pass-through via the SDK; no tool/schema change.
|
|
131
135
|
- **0.1.2** (2026-10-05): picks up `bharatstock` JS SDK 0.1.11, whose paginated responses now pass through every API field (new or modified) with no SDK change — so every field the API returns (e.g. a scheme portfolio's `coveragePct`, and anything added later) now flows through to the MCP tool output. No tool/schema change.
|
|
132
136
|
- **0.1.1** (2026-09-20): added two mutual-fund portfolio tools — `list_mutual_fund_portfolios` (discover the schemes we hold monthly portfolio data for, by scheme name) and `get_mutual_fund_portfolio_holdings` (a scheme's equity portfolio for a month, with month-over-month quantity change). These are the inverse of `get_mutual_fund_holdings` and are keyed by the holdings scheme-name string, not the AMFI scheme_code.
|
|
133
137
|
- **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.
|
|
@@ -41,9 +41,115 @@ async function checkAuthorization(baseUrl, apiKey, fetchImpl = fetch) {
|
|
|
41
41
|
// src/server.ts
|
|
42
42
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
43
43
|
import { BharatStock } from "bharatstock";
|
|
44
|
+
import { z as z2 } from "zod";
|
|
45
|
+
|
|
46
|
+
// src/operator.ts
|
|
44
47
|
import { z } from "zod";
|
|
48
|
+
async function callOperatorTool(opts, name, args) {
|
|
49
|
+
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
50
|
+
const url = `${opts.baseUrl.replace(/\/$/, "")}/v1/mcp`;
|
|
51
|
+
const rpc = {
|
|
52
|
+
jsonrpc: "2.0",
|
|
53
|
+
id: Date.now(),
|
|
54
|
+
method: "tools/call",
|
|
55
|
+
params: { name, arguments: args }
|
|
56
|
+
};
|
|
57
|
+
try {
|
|
58
|
+
const resp = await fetchImpl(url, {
|
|
59
|
+
method: "POST",
|
|
60
|
+
headers: {
|
|
61
|
+
"Content-Type": "application/json",
|
|
62
|
+
"X-Admin-Secret": opts.adminSecret
|
|
63
|
+
},
|
|
64
|
+
body: JSON.stringify(rpc)
|
|
65
|
+
});
|
|
66
|
+
const bodyText = await resp.text();
|
|
67
|
+
let body;
|
|
68
|
+
try {
|
|
69
|
+
body = JSON.parse(bodyText);
|
|
70
|
+
} catch {
|
|
71
|
+
body = void 0;
|
|
72
|
+
}
|
|
73
|
+
if (!resp.ok) {
|
|
74
|
+
const msg = body?.error?.message || bodyText.slice(0, 500) || `HTTP ${resp.status}`;
|
|
75
|
+
return { isError: true, content: [{ type: "text", text: `BharatStock operator error: ${msg}` }] };
|
|
76
|
+
}
|
|
77
|
+
const rpcErr = body?.error;
|
|
78
|
+
if (rpcErr) {
|
|
79
|
+
return { isError: true, content: [{ type: "text", text: `BharatStock operator error: ${rpcErr.message}` }] };
|
|
80
|
+
}
|
|
81
|
+
const result = body?.result;
|
|
82
|
+
if (result && Array.isArray(result.content)) {
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
return { content: [{ type: "text", text: JSON.stringify(body, null, 2) }] };
|
|
86
|
+
} catch (err) {
|
|
87
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
88
|
+
return { isError: true, content: [{ type: "text", text: `BharatStock operator transport error: ${message}` }] };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function registerOperatorTools(server, opts) {
|
|
92
|
+
server.registerTool(
|
|
93
|
+
"get_support_tickets",
|
|
94
|
+
{
|
|
95
|
+
title: "List support tickets (operator)",
|
|
96
|
+
description: "OPERATOR-ONLY. List support tickets (customer feedback/email reports) with status (open/investigating/waiting/resolved/closed, or 'all') and category filters. Default shows the live queue (excludes resolved/closed).",
|
|
97
|
+
inputSchema: {
|
|
98
|
+
status: z.string().optional(),
|
|
99
|
+
category: z.string().optional(),
|
|
100
|
+
limit: z.number().int().positive().optional(),
|
|
101
|
+
offset: z.number().int().nonnegative().optional()
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
async (args) => callOperatorTool(opts, "get_support_tickets", args)
|
|
105
|
+
);
|
|
106
|
+
server.registerTool(
|
|
107
|
+
"get_support_ticket",
|
|
108
|
+
{
|
|
109
|
+
title: "Get one support ticket in full (operator)",
|
|
110
|
+
description: "OPERATOR-ONLY. One support ticket in full: the original message, its activity trail (status changes, notes, fixes applied), and any drafted reply. Use this to pick up a ticket and see everything already tried.",
|
|
111
|
+
inputSchema: { id: z.number().int().positive() }
|
|
112
|
+
},
|
|
113
|
+
async (args) => callOperatorTool(opts, "get_support_ticket", args)
|
|
114
|
+
);
|
|
115
|
+
server.registerTool(
|
|
116
|
+
"set_ticket_status",
|
|
117
|
+
{
|
|
118
|
+
title: "Set a support ticket's status (operator, write)",
|
|
119
|
+
description: "OPERATOR-ONLY WRITE. Transition a ticket's status (open/investigating/waiting/resolved/closed) with an optional note. Moving to resolved/closed with a note records the resolution summary.",
|
|
120
|
+
inputSchema: {
|
|
121
|
+
id: z.number().int().positive(),
|
|
122
|
+
status: z.enum(["open", "investigating", "waiting", "resolved", "closed"]),
|
|
123
|
+
note: z.string().optional()
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
async (args) => callOperatorTool(opts, "set_ticket_status", args)
|
|
127
|
+
);
|
|
128
|
+
server.registerTool(
|
|
129
|
+
"log_ticket_event",
|
|
130
|
+
{
|
|
131
|
+
title: "Append an event to a ticket's trail (operator, write)",
|
|
132
|
+
description: "OPERATOR-ONLY WRITE. Append an entry to a ticket's activity trail, e.g. event_type='fix_applied', detail='ran fix_eps_sign, 4 rows, DQ green'. event_type one of status_change/note/reply_sent/fix_applied/reopened/category_change.",
|
|
133
|
+
inputSchema: {
|
|
134
|
+
id: z.number().int().positive(),
|
|
135
|
+
event_type: z.enum([
|
|
136
|
+
"status_change",
|
|
137
|
+
"note",
|
|
138
|
+
"reply_sent",
|
|
139
|
+
"fix_applied",
|
|
140
|
+
"reopened",
|
|
141
|
+
"category_change"
|
|
142
|
+
]),
|
|
143
|
+
detail: z.string().min(1)
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
async (args) => callOperatorTool(opts, "log_ticket_event", args)
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/server.ts
|
|
45
151
|
var SERVER_NAME = "bharatstock";
|
|
46
|
-
var SERVER_VERSION = "0.
|
|
152
|
+
var SERVER_VERSION = "0.2.0";
|
|
47
153
|
async function run(fn) {
|
|
48
154
|
try {
|
|
49
155
|
const data = await fn();
|
|
@@ -59,12 +165,13 @@ async function run(fn) {
|
|
|
59
165
|
}
|
|
60
166
|
}
|
|
61
167
|
function buildServer(options = {}) {
|
|
62
|
-
const
|
|
168
|
+
const { adminSecret, operatorFetchImpl, ...sdkOptions } = options;
|
|
169
|
+
const client = new BharatStock(sdkOptions);
|
|
63
170
|
const server = new McpServer({ name: SERVER_NAME, version: SERVER_VERSION });
|
|
64
|
-
const ticker =
|
|
65
|
-
const exchange =
|
|
66
|
-
const fromDate =
|
|
67
|
-
const toDate =
|
|
171
|
+
const ticker = z2.string().describe("NSE/BSE ticker symbol (e.g. RELIANCE, TCS) or ISIN.");
|
|
172
|
+
const exchange = z2.enum(["NSE", "BSE"]).optional().describe("Optional exchange to disambiguate a cross-listed stock.");
|
|
173
|
+
const fromDate = z2.string().optional().describe("Start date, YYYY-MM-DD (inclusive).");
|
|
174
|
+
const toDate = z2.string().optional().describe("End date, YYYY-MM-DD (inclusive).");
|
|
68
175
|
server.registerTool(
|
|
69
176
|
"get_stock_quote",
|
|
70
177
|
{
|
|
@@ -89,7 +196,7 @@ function buildServer(options = {}) {
|
|
|
89
196
|
title: "Get quotes for multiple stocks",
|
|
90
197
|
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
198
|
inputSchema: {
|
|
92
|
-
symbols:
|
|
199
|
+
symbols: z2.array(z2.string()).min(1).max(50).describe("List of ticker symbols (max 50).")
|
|
93
200
|
}
|
|
94
201
|
},
|
|
95
202
|
async ({ symbols }) => run(() => client.stocks.quotes(symbols))
|
|
@@ -104,8 +211,8 @@ function buildServer(options = {}) {
|
|
|
104
211
|
fromDate,
|
|
105
212
|
toDate,
|
|
106
213
|
exchange,
|
|
107
|
-
page:
|
|
108
|
-
pageSize:
|
|
214
|
+
page: z2.number().int().positive().optional(),
|
|
215
|
+
pageSize: z2.number().int().positive().max(500).optional()
|
|
109
216
|
}
|
|
110
217
|
},
|
|
111
218
|
async ({ ticker: ticker2, fromDate: fromDate2, toDate: toDate2, exchange: exchange2, page, pageSize }) => run(() => client.stocks.prices(ticker2, { fromDate: fromDate2, toDate: toDate2, exchange: exchange2, page, pageSize }))
|
|
@@ -117,10 +224,10 @@ function buildServer(options = {}) {
|
|
|
117
224
|
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
225
|
inputSchema: {
|
|
119
226
|
ticker,
|
|
120
|
-
periodType:
|
|
227
|
+
periodType: z2.enum(["quarterly", "annual"]).optional().describe("Reporting cadence. Defaults to quarterly."),
|
|
121
228
|
exchange,
|
|
122
|
-
page:
|
|
123
|
-
pageSize:
|
|
229
|
+
page: z2.number().int().positive().optional(),
|
|
230
|
+
pageSize: z2.number().int().positive().max(50).optional()
|
|
124
231
|
}
|
|
125
232
|
},
|
|
126
233
|
async ({ ticker: ticker2, periodType, exchange: exchange2, page, pageSize }) => run(() => client.stocks.financials(ticker2, { periodType, exchange: exchange2, page, pageSize }))
|
|
@@ -144,8 +251,8 @@ function buildServer(options = {}) {
|
|
|
144
251
|
fromDate,
|
|
145
252
|
toDate,
|
|
146
253
|
exchange,
|
|
147
|
-
page:
|
|
148
|
-
pageSize:
|
|
254
|
+
page: z2.number().int().positive().optional(),
|
|
255
|
+
pageSize: z2.number().int().positive().max(500).optional()
|
|
149
256
|
}
|
|
150
257
|
},
|
|
151
258
|
async ({ ticker: ticker2, fromDate: fromDate2, toDate: toDate2, exchange: exchange2, page, pageSize }) => run(
|
|
@@ -160,8 +267,8 @@ function buildServer(options = {}) {
|
|
|
160
267
|
inputSchema: {
|
|
161
268
|
ticker,
|
|
162
269
|
exchange,
|
|
163
|
-
page:
|
|
164
|
-
pageSize:
|
|
270
|
+
page: z2.number().int().positive().optional(),
|
|
271
|
+
pageSize: z2.number().int().positive().max(50).optional()
|
|
165
272
|
}
|
|
166
273
|
},
|
|
167
274
|
async ({ ticker: ticker2, exchange: exchange2, page, pageSize }) => run(() => client.stocks.shareholding(ticker2, { exchange: exchange2, page, pageSize }))
|
|
@@ -173,10 +280,10 @@ function buildServer(options = {}) {
|
|
|
173
280
|
description: "Corporate actions for a stock: dividends, bonuses, stock splits, rights issues, and buybacks, with ex-dates.",
|
|
174
281
|
inputSchema: {
|
|
175
282
|
ticker,
|
|
176
|
-
actionType:
|
|
283
|
+
actionType: z2.string().optional().describe("Filter by type, e.g. dividend, bonus, split."),
|
|
177
284
|
exchange,
|
|
178
|
-
page:
|
|
179
|
-
pageSize:
|
|
285
|
+
page: z2.number().int().positive().optional(),
|
|
286
|
+
pageSize: z2.number().int().positive().max(50).optional()
|
|
180
287
|
}
|
|
181
288
|
},
|
|
182
289
|
async ({ ticker: ticker2, actionType, exchange: exchange2, page, pageSize }) => run(() => client.stocks.corporateActions(ticker2, { actionType, exchange: exchange2, page, pageSize }))
|
|
@@ -188,8 +295,8 @@ function buildServer(options = {}) {
|
|
|
188
295
|
description: "Bulk deals reported for a specific stock.",
|
|
189
296
|
inputSchema: {
|
|
190
297
|
ticker,
|
|
191
|
-
page:
|
|
192
|
-
pageSize:
|
|
298
|
+
page: z2.number().int().positive().optional(),
|
|
299
|
+
pageSize: z2.number().int().positive().max(100).optional()
|
|
193
300
|
}
|
|
194
301
|
},
|
|
195
302
|
async ({ ticker: ticker2, page, pageSize }) => run(() => client.stocks.bulkDeals(ticker2, { page, pageSize }))
|
|
@@ -201,8 +308,8 @@ function buildServer(options = {}) {
|
|
|
201
308
|
description: "Block deals reported for a specific stock.",
|
|
202
309
|
inputSchema: {
|
|
203
310
|
ticker,
|
|
204
|
-
page:
|
|
205
|
-
pageSize:
|
|
311
|
+
page: z2.number().int().positive().optional(),
|
|
312
|
+
pageSize: z2.number().int().positive().max(100).optional()
|
|
206
313
|
}
|
|
207
314
|
},
|
|
208
315
|
async ({ ticker: ticker2, page, pageSize }) => run(() => client.stocks.blockDeals(ticker2, { page, pageSize }))
|
|
@@ -214,8 +321,8 @@ function buildServer(options = {}) {
|
|
|
214
321
|
description: "SEBI PIT insider-trading disclosures for a specific stock.",
|
|
215
322
|
inputSchema: {
|
|
216
323
|
ticker,
|
|
217
|
-
page:
|
|
218
|
-
pageSize:
|
|
324
|
+
page: z2.number().int().positive().optional(),
|
|
325
|
+
pageSize: z2.number().int().positive().max(100).optional()
|
|
219
326
|
}
|
|
220
327
|
},
|
|
221
328
|
async ({ ticker: ticker2, page, pageSize }) => run(() => client.stocks.insiderTrades(ticker2, { page, pageSize }))
|
|
@@ -226,9 +333,9 @@ function buildServer(options = {}) {
|
|
|
226
333
|
title: "Compare peers in a sector",
|
|
227
334
|
description: "Compare valuation/profitability ratios across the peers in a sector, for a quick relative-value view.",
|
|
228
335
|
inputSchema: {
|
|
229
|
-
sector:
|
|
230
|
-
sort:
|
|
231
|
-
limit:
|
|
336
|
+
sector: z2.string().describe("Sector name, e.g. 'Information Technology'."),
|
|
337
|
+
sort: z2.string().optional().describe("Sort key, e.g. market_cap, pe_ratio."),
|
|
338
|
+
limit: z2.number().int().positive().max(100).optional()
|
|
232
339
|
}
|
|
233
340
|
},
|
|
234
341
|
async ({ sector, sort, limit }) => run(() => client.stocks.compare(sector, { sort, limit }))
|
|
@@ -239,8 +346,8 @@ function buildServer(options = {}) {
|
|
|
239
346
|
title: "Search stocks",
|
|
240
347
|
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
348
|
inputSchema: {
|
|
242
|
-
q:
|
|
243
|
-
limit:
|
|
349
|
+
q: z2.string().describe("Search text (symbol fragment or company name)."),
|
|
350
|
+
limit: z2.number().int().positive().max(50).optional()
|
|
244
351
|
}
|
|
245
352
|
},
|
|
246
353
|
async ({ q, limit }) => run(() => client.search(q, { limit }))
|
|
@@ -251,8 +358,8 @@ function buildServer(options = {}) {
|
|
|
251
358
|
title: "Get top market movers",
|
|
252
359
|
description: "Ranked market movers: top gainers, top losers, or most-active stocks for the latest trading session. Use category to pick which.",
|
|
253
360
|
inputSchema: {
|
|
254
|
-
category:
|
|
255
|
-
limit:
|
|
361
|
+
category: z2.enum(["gainers", "losers", "active"]).optional().describe("Which ranking. Defaults to gainers."),
|
|
362
|
+
limit: z2.number().int().positive().max(100).optional()
|
|
256
363
|
}
|
|
257
364
|
},
|
|
258
365
|
async ({ category, limit }) => run(() => client.movers({ category, limit }))
|
|
@@ -263,9 +370,9 @@ function buildServer(options = {}) {
|
|
|
263
370
|
title: "Get price shockers",
|
|
264
371
|
description: "Stocks that moved at least a threshold percentage vs the previous close, in either direction.",
|
|
265
372
|
inputSchema: {
|
|
266
|
-
minChangePct:
|
|
267
|
-
direction:
|
|
268
|
-
limit:
|
|
373
|
+
minChangePct: z2.number().positive().optional().describe("Minimum abs % move. Default 5."),
|
|
374
|
+
direction: z2.enum(["up", "down", "both"]).optional(),
|
|
375
|
+
limit: z2.number().int().positive().max(100).optional()
|
|
269
376
|
}
|
|
270
377
|
},
|
|
271
378
|
async ({ minChangePct, direction, limit }) => run(() => client.priceShockers({ minChangePct, direction, limit }))
|
|
@@ -276,13 +383,13 @@ function buildServer(options = {}) {
|
|
|
276
383
|
title: "Run stock screener",
|
|
277
384
|
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
385
|
inputSchema: {
|
|
279
|
-
filters:
|
|
280
|
-
sector:
|
|
386
|
+
filters: z2.array(z2.string()).optional().describe("Filter expressions like 'pe_ratio.lt.15'."),
|
|
387
|
+
sector: z2.string().optional(),
|
|
281
388
|
exchange,
|
|
282
|
-
sortBy:
|
|
283
|
-
sortOrder:
|
|
284
|
-
page:
|
|
285
|
-
pageSize:
|
|
389
|
+
sortBy: z2.string().optional().describe("Sort key, default market_cap."),
|
|
390
|
+
sortOrder: z2.enum(["asc", "desc"]).optional(),
|
|
391
|
+
page: z2.number().int().positive().optional(),
|
|
392
|
+
pageSize: z2.number().int().positive().max(100).optional()
|
|
286
393
|
}
|
|
287
394
|
},
|
|
288
395
|
async ({ filters, sector, exchange: exchange2, sortBy, sortOrder, page, pageSize }) => run(
|
|
@@ -295,8 +402,8 @@ function buildServer(options = {}) {
|
|
|
295
402
|
title: "List indices",
|
|
296
403
|
description: "List market indices (NIFTY 50, SENSEX, sectoral indices, etc.) with their latest values.",
|
|
297
404
|
inputSchema: {
|
|
298
|
-
page:
|
|
299
|
-
pageSize:
|
|
405
|
+
page: z2.number().int().positive().optional(),
|
|
406
|
+
pageSize: z2.number().int().positive().max(200).optional()
|
|
300
407
|
}
|
|
301
408
|
},
|
|
302
409
|
async ({ page, pageSize }) => run(() => client.indices.list({ page, pageSize }))
|
|
@@ -307,11 +414,11 @@ function buildServer(options = {}) {
|
|
|
307
414
|
title: "Get historical index prices",
|
|
308
415
|
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
416
|
inputSchema: {
|
|
310
|
-
name:
|
|
417
|
+
name: z2.string().describe("Index name, e.g. 'NIFTY 50'."),
|
|
311
418
|
fromDate,
|
|
312
419
|
toDate,
|
|
313
|
-
page:
|
|
314
|
-
pageSize:
|
|
420
|
+
page: z2.number().int().positive().optional(),
|
|
421
|
+
pageSize: z2.number().int().positive().max(500).optional()
|
|
315
422
|
}
|
|
316
423
|
},
|
|
317
424
|
async ({ name, fromDate: fromDate2, toDate: toDate2, page, pageSize }) => run(() => client.indices.prices(name, { fromDate: fromDate2, toDate: toDate2, page, pageSize }))
|
|
@@ -324,8 +431,8 @@ function buildServer(options = {}) {
|
|
|
324
431
|
inputSchema: {
|
|
325
432
|
fromDate,
|
|
326
433
|
toDate,
|
|
327
|
-
limit:
|
|
328
|
-
latest:
|
|
434
|
+
limit: z2.number().int().positive().max(500).optional(),
|
|
435
|
+
latest: z2.boolean().optional().describe("Return only the most recent day.")
|
|
329
436
|
}
|
|
330
437
|
},
|
|
331
438
|
async ({ fromDate: fromDate2, toDate: toDate2, limit, latest }) => run(() => client.market.fiiDii({ fromDate: fromDate2, toDate: toDate2, limit, latest }))
|
|
@@ -336,9 +443,9 @@ function buildServer(options = {}) {
|
|
|
336
443
|
title: "Get market-wide bulk deals",
|
|
337
444
|
description: "Bulk deals across the whole market for the latest sessions.",
|
|
338
445
|
inputSchema: {
|
|
339
|
-
buySell:
|
|
340
|
-
page:
|
|
341
|
-
pageSize:
|
|
446
|
+
buySell: z2.enum(["BUY", "SELL"]).optional(),
|
|
447
|
+
page: z2.number().int().positive().optional(),
|
|
448
|
+
pageSize: z2.number().int().positive().max(100).optional()
|
|
342
449
|
}
|
|
343
450
|
},
|
|
344
451
|
async ({ buySell, page, pageSize }) => run(() => client.deals.bulk({ buySell, page, pageSize }))
|
|
@@ -349,9 +456,9 @@ function buildServer(options = {}) {
|
|
|
349
456
|
title: "Get market-wide block deals",
|
|
350
457
|
description: "Block deals across the whole market for the latest sessions.",
|
|
351
458
|
inputSchema: {
|
|
352
|
-
buySell:
|
|
353
|
-
page:
|
|
354
|
-
pageSize:
|
|
459
|
+
buySell: z2.enum(["BUY", "SELL"]).optional(),
|
|
460
|
+
page: z2.number().int().positive().optional(),
|
|
461
|
+
pageSize: z2.number().int().positive().max(100).optional()
|
|
355
462
|
}
|
|
356
463
|
},
|
|
357
464
|
async ({ buySell, page, pageSize }) => run(() => client.deals.block({ buySell, page, pageSize }))
|
|
@@ -362,8 +469,8 @@ function buildServer(options = {}) {
|
|
|
362
469
|
title: "Get market-wide insider trades",
|
|
363
470
|
description: "SEBI PIT insider-trading disclosures across the whole market.",
|
|
364
471
|
inputSchema: {
|
|
365
|
-
page:
|
|
366
|
-
pageSize:
|
|
472
|
+
page: z2.number().int().positive().optional(),
|
|
473
|
+
pageSize: z2.number().int().positive().max(100).optional()
|
|
367
474
|
}
|
|
368
475
|
},
|
|
369
476
|
async ({ page, pageSize }) => run(() => client.deals.insiderTrades({ page, pageSize }))
|
|
@@ -374,14 +481,14 @@ function buildServer(options = {}) {
|
|
|
374
481
|
title: "Search mutual-fund schemes",
|
|
375
482
|
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
483
|
inputSchema: {
|
|
377
|
-
q:
|
|
378
|
-
amc:
|
|
379
|
-
category:
|
|
380
|
-
plan:
|
|
381
|
-
option:
|
|
382
|
-
isin:
|
|
383
|
-
limit:
|
|
384
|
-
offset:
|
|
484
|
+
q: z2.string().optional(),
|
|
485
|
+
amc: z2.string().optional(),
|
|
486
|
+
category: z2.string().optional(),
|
|
487
|
+
plan: z2.string().optional(),
|
|
488
|
+
option: z2.string().optional(),
|
|
489
|
+
isin: z2.string().optional(),
|
|
490
|
+
limit: z2.number().int().positive().max(200).optional(),
|
|
491
|
+
offset: z2.number().int().nonnegative().optional()
|
|
385
492
|
}
|
|
386
493
|
},
|
|
387
494
|
async (args) => run(() => client.mutualFunds.listSchemes(args))
|
|
@@ -392,7 +499,7 @@ function buildServer(options = {}) {
|
|
|
392
499
|
title: "Get mutual-fund scheme detail",
|
|
393
500
|
description: "One scheme's identity plus its latest NAV (with as-of date).",
|
|
394
501
|
inputSchema: {
|
|
395
|
-
schemeCode:
|
|
502
|
+
schemeCode: z2.string().describe("AMFI scheme code.")
|
|
396
503
|
}
|
|
397
504
|
},
|
|
398
505
|
async ({ schemeCode }) => run(() => client.mutualFunds.getScheme(schemeCode))
|
|
@@ -403,10 +510,10 @@ function buildServer(options = {}) {
|
|
|
403
510
|
title: "Get mutual-fund NAV history",
|
|
404
511
|
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
512
|
inputSchema: {
|
|
406
|
-
schemeCode:
|
|
513
|
+
schemeCode: z2.string().describe("AMFI scheme code."),
|
|
407
514
|
fromDate,
|
|
408
515
|
toDate,
|
|
409
|
-
limit:
|
|
516
|
+
limit: z2.number().int().positive().max(2e3).optional()
|
|
410
517
|
}
|
|
411
518
|
},
|
|
412
519
|
async ({ schemeCode, fromDate: fromDate2, toDate: toDate2, limit }) => run(() => client.mutualFunds.nav(schemeCode, { fromDate: fromDate2, toDate: toDate2, limit }))
|
|
@@ -417,7 +524,7 @@ function buildServer(options = {}) {
|
|
|
417
524
|
title: "Get mutual-fund trailing returns",
|
|
418
525
|
description: "Trailing returns (1m/3m/6m/1y/3y/5y + since-inception) computed from the scheme's NAV series (CAGR for >=1y horizons).",
|
|
419
526
|
inputSchema: {
|
|
420
|
-
schemeCode:
|
|
527
|
+
schemeCode: z2.string().describe("AMFI scheme code.")
|
|
421
528
|
}
|
|
422
529
|
},
|
|
423
530
|
async ({ schemeCode }) => run(() => client.mutualFunds.returns(schemeCode))
|
|
@@ -429,9 +536,9 @@ function buildServer(options = {}) {
|
|
|
429
536
|
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
537
|
inputSchema: {
|
|
431
538
|
ticker,
|
|
432
|
-
month:
|
|
433
|
-
page:
|
|
434
|
-
pageSize:
|
|
539
|
+
month: z2.string().optional().describe("Portfolio month YYYY-MM-DD (month-end). Latest if omitted."),
|
|
540
|
+
page: z2.number().int().positive().optional(),
|
|
541
|
+
pageSize: z2.number().int().positive().max(200).optional()
|
|
435
542
|
}
|
|
436
543
|
},
|
|
437
544
|
async ({ ticker: ticker2, month, page, pageSize }) => run(() => client.stocks.mfHoldings(ticker2, { month, page, pageSize }))
|
|
@@ -442,10 +549,10 @@ function buildServer(options = {}) {
|
|
|
442
549
|
title: "List / search mutual-fund portfolios",
|
|
443
550
|
description: "List/search the mutual-fund schemes we hold monthly PORTFOLIO data for (scheme discovery, to populate a picker). This is the INVERSE of get_mutual_fund_holdings: that answers 'which funds own stock X', this answers 'which schemes do we have portfolios for'. Each row is keyed by the holdings scheme-NAME string (NOT the AMFI scheme_code used by the scheme/NAV tools) and has no Direct/Regular distinction, since AMFI publishes monthly portfolios at the scheme level. Use the returned scheme_name with get_mutual_fund_portfolio_holdings.",
|
|
444
551
|
inputSchema: {
|
|
445
|
-
q:
|
|
446
|
-
fundHouse:
|
|
447
|
-
limit:
|
|
448
|
-
offset:
|
|
552
|
+
q: z2.string().optional().describe("Substring match on scheme name."),
|
|
553
|
+
fundHouse: z2.string().optional().describe("Filter by fund house (substring)."),
|
|
554
|
+
limit: z2.number().int().positive().max(200).optional(),
|
|
555
|
+
offset: z2.number().int().nonnegative().optional()
|
|
449
556
|
}
|
|
450
557
|
},
|
|
451
558
|
async ({ q, fundHouse, limit, offset }) => run(() => client.mutualFunds.listPortfolios({ q, fundHouse, limit, offset }))
|
|
@@ -456,10 +563,10 @@ function buildServer(options = {}) {
|
|
|
456
563
|
title: "Get a mutual-fund scheme's portfolio holdings",
|
|
457
564
|
description: "One scheme's equity PORTFOLIO for a month (the stocks it holds) with month-over-month quantity change. The INVERSE of get_mutual_fund_holdings (which funds own a stock). schemeName must match the stored holdings name exactly, as returned by list_mutual_fund_portfolios \u2014 it is the scheme-NAME string, NOT the AMFI scheme_code. month is 'YYYY-MM' (latest if omitted). An unknown scheme name returns an empty portfolio, not an error.",
|
|
458
565
|
inputSchema: {
|
|
459
|
-
schemeName:
|
|
460
|
-
month:
|
|
461
|
-
page:
|
|
462
|
-
pageSize:
|
|
566
|
+
schemeName: z2.string().describe("Holdings scheme name, exactly as returned by list_mutual_fund_portfolios."),
|
|
567
|
+
month: z2.string().optional().describe("Portfolio month YYYY-MM. Latest if omitted."),
|
|
568
|
+
page: z2.number().int().positive().optional(),
|
|
569
|
+
pageSize: z2.number().int().positive().max(100).optional()
|
|
463
570
|
}
|
|
464
571
|
},
|
|
465
572
|
async ({ schemeName, month, page, pageSize }) => run(() => client.mutualFunds.getPortfolioHoldings(schemeName, { month, page, pageSize }))
|
|
@@ -473,6 +580,10 @@ function buildServer(options = {}) {
|
|
|
473
580
|
},
|
|
474
581
|
async () => run(() => client.status())
|
|
475
582
|
);
|
|
583
|
+
if (adminSecret) {
|
|
584
|
+
const baseUrl = sdkOptions.baseUrl ?? "https://bharatstockapi.com";
|
|
585
|
+
registerOperatorTools(server, { baseUrl, adminSecret, fetchImpl: operatorFetchImpl });
|
|
586
|
+
}
|
|
476
587
|
return server;
|
|
477
588
|
}
|
|
478
589
|
|
package/dist/http.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,34 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { BharatStockOptions } from 'bharatstock';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* OPERATOR mode for the BharatStock MCP server.
|
|
6
|
+
*
|
|
7
|
+
* These tools are for the operator ONLY — they manage support tickets and are
|
|
8
|
+
* NOT part of the consumer surface. They are registered only when a
|
|
9
|
+
* `BHARATSTOCK_ADMIN_SECRET` is configured (see index.ts), and they do NOT go
|
|
10
|
+
* through the consumer `bharatstock` SDK (which is X-API-Key scoped and knows
|
|
11
|
+
* nothing about tickets). Instead each tool makes a JSON-RPC `tools/call` to
|
|
12
|
+
* the hosted MCP endpoint `POST {baseUrl}/v1/mcp` with an `X-Admin-Secret`
|
|
13
|
+
* header — the server exposes the operator tool catalogue only to a request
|
|
14
|
+
* that carries a valid admin secret (see app/api/v1/mcp.py).
|
|
15
|
+
*
|
|
16
|
+
* So the wall is end-to-end: a normal (API-key) MCP session never sees these
|
|
17
|
+
* tools (they aren't registered), and even if a tool name were guessed, the
|
|
18
|
+
* hosted server rejects an operator tool without the admin secret.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
type FetchLike = (input: string, init?: {
|
|
22
|
+
method?: string;
|
|
23
|
+
headers?: Record<string, string>;
|
|
24
|
+
body?: string;
|
|
25
|
+
}) => Promise<{
|
|
26
|
+
ok: boolean;
|
|
27
|
+
status: number;
|
|
28
|
+
text: () => Promise<string>;
|
|
29
|
+
json: () => Promise<unknown>;
|
|
30
|
+
}>;
|
|
31
|
+
|
|
4
32
|
/**
|
|
5
33
|
* Builds the BharatStock MCP server: a thin layer that exposes the
|
|
6
34
|
* BharatStock REST API's consumer-facing (X-API-Key gated) endpoints as
|
|
@@ -14,14 +42,25 @@ import { BharatStockOptions } from 'bharatstock';
|
|
|
14
42
|
*/
|
|
15
43
|
|
|
16
44
|
declare const SERVER_NAME = "bharatstock";
|
|
17
|
-
declare const SERVER_VERSION = "0.
|
|
45
|
+
declare const SERVER_VERSION = "0.2.0";
|
|
46
|
+
/** Extra, non-consumer options for buildServer. When `adminSecret` is set the
|
|
47
|
+
* OPERATOR ticket tools are additionally registered (see operator.ts); they
|
|
48
|
+
* are absent otherwise, so a normal consumer session never sees them. */
|
|
49
|
+
interface BuildServerExtras {
|
|
50
|
+
/** Operator admin secret. When present, register the support-ticket tools. */
|
|
51
|
+
adminSecret?: string;
|
|
52
|
+
/** Injectable fetch for the operator tools' JSON-RPC calls (tests). Named
|
|
53
|
+
* distinctly from the SDK's own `fetchImpl` so the two don't collide when
|
|
54
|
+
* the options objects are intersected. */
|
|
55
|
+
operatorFetchImpl?: FetchLike;
|
|
56
|
+
}
|
|
18
57
|
/**
|
|
19
58
|
* Construct the MCP server and register all tools against a BharatStock
|
|
20
59
|
* client. Exposed separately from the stdio wiring so tests can build a
|
|
21
60
|
* server with an injected `fetchImpl` and drive it over an in-memory
|
|
22
61
|
* transport (no network, no real key).
|
|
23
62
|
*/
|
|
24
|
-
declare function buildServer(options?: BharatStockOptions): McpServer;
|
|
63
|
+
declare function buildServer(options?: BharatStockOptions & BuildServerExtras): McpServer;
|
|
25
64
|
|
|
26
65
|
/**
|
|
27
66
|
* Shared plan-gate check for both the stdio and HTTP transports.
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
SERVER_VERSION,
|
|
5
5
|
buildServer,
|
|
6
6
|
checkAuthorization
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-NXHQTUBJ.js";
|
|
8
8
|
|
|
9
9
|
// src/index.ts
|
|
10
10
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -25,7 +25,11 @@ async function main() {
|
|
|
25
25
|
`);
|
|
26
26
|
process.exit(1);
|
|
27
27
|
}
|
|
28
|
-
const
|
|
28
|
+
const adminSecret = process.env.BHARATSTOCK_ADMIN_SECRET || void 0;
|
|
29
|
+
if (adminSecret) {
|
|
30
|
+
process.stderr.write("bharatstock-mcp: operator mode ON (support-ticket tools registered).\n");
|
|
31
|
+
}
|
|
32
|
+
const server = buildServer({ apiKey, baseUrl, adminSecret });
|
|
29
33
|
const transport = new StdioServerTransport();
|
|
30
34
|
await server.connect(transport);
|
|
31
35
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bharatstock-mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"mcpName": "com.bharatstockapi/bharatstock-mcp",
|
|
5
|
+
"description": "Official Model Context Protocol (MCP) server for the BharatStock API - Indian stock market data (NSE/BSE) as AI-agent tools: real-time quotes, EOD prices, financial statements, ratios, shareholding patterns (FII/DII/promoter), corporate actions, NIFTY/SENSEX indices, mutual fund holdings and NAVs, FII/DII activity, bulk/block deals, insider trades, and a fundamentals stock screener for Indian equities.",
|
|
5
6
|
"license": "MIT",
|
|
6
7
|
"type": "module",
|
|
7
8
|
"bin": {
|
|
@@ -16,13 +17,32 @@
|
|
|
16
17
|
"keywords": [
|
|
17
18
|
"mcp",
|
|
18
19
|
"model-context-protocol",
|
|
20
|
+
"mcp-server",
|
|
19
21
|
"nse",
|
|
20
22
|
"bse",
|
|
23
|
+
"nifty",
|
|
24
|
+
"sensex",
|
|
21
25
|
"india",
|
|
26
|
+
"indian-stocks",
|
|
27
|
+
"indian-stock-market",
|
|
22
28
|
"stocks",
|
|
29
|
+
"stock-market",
|
|
30
|
+
"stock-api",
|
|
31
|
+
"stock-screener",
|
|
32
|
+
"equities",
|
|
33
|
+
"mutual-funds",
|
|
34
|
+
"nav",
|
|
35
|
+
"shareholding",
|
|
36
|
+
"fii-dii",
|
|
37
|
+
"corporate-actions",
|
|
38
|
+
"financials",
|
|
39
|
+
"fundamentals",
|
|
23
40
|
"market-data",
|
|
24
41
|
"finance",
|
|
25
|
-
"
|
|
42
|
+
"trading",
|
|
43
|
+
"investing",
|
|
44
|
+
"ai-tools",
|
|
45
|
+
"llm"
|
|
26
46
|
],
|
|
27
47
|
"homepage": "https://bharatstockapi.com",
|
|
28
48
|
"engines": {
|
|
@@ -35,7 +55,7 @@
|
|
|
35
55
|
},
|
|
36
56
|
"dependencies": {
|
|
37
57
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
38
|
-
"bharatstock": "^0.1.
|
|
58
|
+
"bharatstock": "^0.1.12",
|
|
39
59
|
"zod": "^3.23.8"
|
|
40
60
|
},
|
|
41
61
|
"devDependencies": {
|