bharatstock-mcp 0.2.0 → 0.2.2

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 CHANGED
@@ -2,15 +2,10 @@
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.
5
+ ## 0.2.2 (2026-11-15)
6
+ - Security maintenance: updated `@modelcontextprotocol/sdk` to ^1.32.2 (fixes the OAuth credential-binding, cross-session task access and request-size/DoS advisories), `zod` to ^4.6.7, and refreshed transitive dependencies (hono, body-parser, qs, fast-uri, ip-address). `npm audit` reports 0 vulnerabilities. No change to the tool surface or behaviour.
7
+ ## 0.2.1 (2026-11-06)
8
+ - Maintenance release. No change to the tool surface or behaviour.
14
9
 
15
10
  ## 0.1.5 (2026-11-03)
16
11
  - Broadened the listing metadata for discoverability: a keyword-rich
package/README.md CHANGED
@@ -127,8 +127,9 @@ This server is a thin layer over the official [`bharatstock`](https://www.npmjs.
127
127
  MIT
128
128
 
129
129
  ## Changelog
130
+ - **0.2.2** (2026-11-15): security maintenance: updated `@modelcontextprotocol/sdk` to ^1.32.2 (fixes the OAuth credential-binding, cross-session task access and request-size/DoS advisories), `zod` to ^4.6.7, and refreshed transitive dependencies (hono, body-parser, qs, fast-uri, ip-address). `npm audit` reports 0 vulnerabilities. No change to the tool surface or behaviour.
130
131
 
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.2.1** (2026-11-06): maintenance release. No change to the tool surface or behaviour.
132
133
  - **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
134
  - **0.1.4** (2026-11-03): published to the official MCP Registry as `com.bharatstockapi/bharatstock-mcp`. No tool/schema change.
134
135
  - **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.
@@ -41,115 +41,9 @@ 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
47
44
  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
151
45
  var SERVER_NAME = "bharatstock";
152
- var SERVER_VERSION = "0.2.0";
46
+ var SERVER_VERSION = "0.2.2";
153
47
  async function run(fn) {
154
48
  try {
155
49
  const data = await fn();
@@ -165,13 +59,12 @@ async function run(fn) {
165
59
  }
166
60
  }
167
61
  function buildServer(options = {}) {
168
- const { adminSecret, operatorFetchImpl, ...sdkOptions } = options;
169
- const client = new BharatStock(sdkOptions);
62
+ const client = new BharatStock(options);
170
63
  const server = new McpServer({ name: SERVER_NAME, version: SERVER_VERSION });
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).");
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).");
175
68
  server.registerTool(
176
69
  "get_stock_quote",
177
70
  {
@@ -196,7 +89,7 @@ function buildServer(options = {}) {
196
89
  title: "Get quotes for multiple stocks",
197
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.",
198
91
  inputSchema: {
199
- symbols: z2.array(z2.string()).min(1).max(50).describe("List of ticker symbols (max 50).")
92
+ symbols: z.array(z.string()).min(1).max(50).describe("List of ticker symbols (max 50).")
200
93
  }
201
94
  },
202
95
  async ({ symbols }) => run(() => client.stocks.quotes(symbols))
@@ -211,8 +104,8 @@ function buildServer(options = {}) {
211
104
  fromDate,
212
105
  toDate,
213
106
  exchange,
214
- page: z2.number().int().positive().optional(),
215
- pageSize: z2.number().int().positive().max(500).optional()
107
+ page: z.number().int().positive().optional(),
108
+ pageSize: z.number().int().positive().max(500).optional()
216
109
  }
217
110
  },
218
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 }))
@@ -224,10 +117,10 @@ function buildServer(options = {}) {
224
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.",
225
118
  inputSchema: {
226
119
  ticker,
227
- periodType: z2.enum(["quarterly", "annual"]).optional().describe("Reporting cadence. Defaults to quarterly."),
120
+ periodType: z.enum(["quarterly", "annual"]).optional().describe("Reporting cadence. Defaults to quarterly."),
228
121
  exchange,
229
- page: z2.number().int().positive().optional(),
230
- pageSize: z2.number().int().positive().max(50).optional()
122
+ page: z.number().int().positive().optional(),
123
+ pageSize: z.number().int().positive().max(50).optional()
231
124
  }
232
125
  },
233
126
  async ({ ticker: ticker2, periodType, exchange: exchange2, page, pageSize }) => run(() => client.stocks.financials(ticker2, { periodType, exchange: exchange2, page, pageSize }))
@@ -251,8 +144,8 @@ function buildServer(options = {}) {
251
144
  fromDate,
252
145
  toDate,
253
146
  exchange,
254
- page: z2.number().int().positive().optional(),
255
- pageSize: z2.number().int().positive().max(500).optional()
147
+ page: z.number().int().positive().optional(),
148
+ pageSize: z.number().int().positive().max(500).optional()
256
149
  }
257
150
  },
258
151
  async ({ ticker: ticker2, fromDate: fromDate2, toDate: toDate2, exchange: exchange2, page, pageSize }) => run(
@@ -267,8 +160,8 @@ function buildServer(options = {}) {
267
160
  inputSchema: {
268
161
  ticker,
269
162
  exchange,
270
- page: z2.number().int().positive().optional(),
271
- pageSize: z2.number().int().positive().max(50).optional()
163
+ page: z.number().int().positive().optional(),
164
+ pageSize: z.number().int().positive().max(50).optional()
272
165
  }
273
166
  },
274
167
  async ({ ticker: ticker2, exchange: exchange2, page, pageSize }) => run(() => client.stocks.shareholding(ticker2, { exchange: exchange2, page, pageSize }))
@@ -280,10 +173,10 @@ function buildServer(options = {}) {
280
173
  description: "Corporate actions for a stock: dividends, bonuses, stock splits, rights issues, and buybacks, with ex-dates.",
281
174
  inputSchema: {
282
175
  ticker,
283
- actionType: z2.string().optional().describe("Filter by type, e.g. dividend, bonus, split."),
176
+ actionType: z.string().optional().describe("Filter by type, e.g. dividend, bonus, split."),
284
177
  exchange,
285
- page: z2.number().int().positive().optional(),
286
- pageSize: z2.number().int().positive().max(50).optional()
178
+ page: z.number().int().positive().optional(),
179
+ pageSize: z.number().int().positive().max(50).optional()
287
180
  }
288
181
  },
289
182
  async ({ ticker: ticker2, actionType, exchange: exchange2, page, pageSize }) => run(() => client.stocks.corporateActions(ticker2, { actionType, exchange: exchange2, page, pageSize }))
@@ -295,8 +188,8 @@ function buildServer(options = {}) {
295
188
  description: "Bulk deals reported for a specific stock.",
296
189
  inputSchema: {
297
190
  ticker,
298
- page: z2.number().int().positive().optional(),
299
- pageSize: z2.number().int().positive().max(100).optional()
191
+ page: z.number().int().positive().optional(),
192
+ pageSize: z.number().int().positive().max(100).optional()
300
193
  }
301
194
  },
302
195
  async ({ ticker: ticker2, page, pageSize }) => run(() => client.stocks.bulkDeals(ticker2, { page, pageSize }))
@@ -308,8 +201,8 @@ function buildServer(options = {}) {
308
201
  description: "Block deals reported for a specific stock.",
309
202
  inputSchema: {
310
203
  ticker,
311
- page: z2.number().int().positive().optional(),
312
- pageSize: z2.number().int().positive().max(100).optional()
204
+ page: z.number().int().positive().optional(),
205
+ pageSize: z.number().int().positive().max(100).optional()
313
206
  }
314
207
  },
315
208
  async ({ ticker: ticker2, page, pageSize }) => run(() => client.stocks.blockDeals(ticker2, { page, pageSize }))
@@ -321,8 +214,8 @@ function buildServer(options = {}) {
321
214
  description: "SEBI PIT insider-trading disclosures for a specific stock.",
322
215
  inputSchema: {
323
216
  ticker,
324
- page: z2.number().int().positive().optional(),
325
- pageSize: z2.number().int().positive().max(100).optional()
217
+ page: z.number().int().positive().optional(),
218
+ pageSize: z.number().int().positive().max(100).optional()
326
219
  }
327
220
  },
328
221
  async ({ ticker: ticker2, page, pageSize }) => run(() => client.stocks.insiderTrades(ticker2, { page, pageSize }))
@@ -333,9 +226,9 @@ function buildServer(options = {}) {
333
226
  title: "Compare peers in a sector",
334
227
  description: "Compare valuation/profitability ratios across the peers in a sector, for a quick relative-value view.",
335
228
  inputSchema: {
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()
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()
339
232
  }
340
233
  },
341
234
  async ({ sector, sort, limit }) => run(() => client.stocks.compare(sector, { sort, limit }))
@@ -346,8 +239,8 @@ function buildServer(options = {}) {
346
239
  title: "Search stocks",
347
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.",
348
241
  inputSchema: {
349
- q: z2.string().describe("Search text (symbol fragment or company name)."),
350
- limit: z2.number().int().positive().max(50).optional()
242
+ q: z.string().describe("Search text (symbol fragment or company name)."),
243
+ limit: z.number().int().positive().max(50).optional()
351
244
  }
352
245
  },
353
246
  async ({ q, limit }) => run(() => client.search(q, { limit }))
@@ -358,8 +251,8 @@ function buildServer(options = {}) {
358
251
  title: "Get top market movers",
359
252
  description: "Ranked market movers: top gainers, top losers, or most-active stocks for the latest trading session. Use category to pick which.",
360
253
  inputSchema: {
361
- category: z2.enum(["gainers", "losers", "active"]).optional().describe("Which ranking. Defaults to gainers."),
362
- limit: z2.number().int().positive().max(100).optional()
254
+ category: z.enum(["gainers", "losers", "active"]).optional().describe("Which ranking. Defaults to gainers."),
255
+ limit: z.number().int().positive().max(100).optional()
363
256
  }
364
257
  },
365
258
  async ({ category, limit }) => run(() => client.movers({ category, limit }))
@@ -370,9 +263,9 @@ function buildServer(options = {}) {
370
263
  title: "Get price shockers",
371
264
  description: "Stocks that moved at least a threshold percentage vs the previous close, in either direction.",
372
265
  inputSchema: {
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()
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()
376
269
  }
377
270
  },
378
271
  async ({ minChangePct, direction, limit }) => run(() => client.priceShockers({ minChangePct, direction, limit }))
@@ -383,13 +276,13 @@ function buildServer(options = {}) {
383
276
  title: "Run stock screener",
384
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.",
385
278
  inputSchema: {
386
- filters: z2.array(z2.string()).optional().describe("Filter expressions like 'pe_ratio.lt.15'."),
387
- sector: z2.string().optional(),
279
+ filters: z.array(z.string()).optional().describe("Filter expressions like 'pe_ratio.lt.15'."),
280
+ sector: z.string().optional(),
388
281
  exchange,
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()
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()
393
286
  }
394
287
  },
395
288
  async ({ filters, sector, exchange: exchange2, sortBy, sortOrder, page, pageSize }) => run(
@@ -402,8 +295,8 @@ function buildServer(options = {}) {
402
295
  title: "List indices",
403
296
  description: "List market indices (NIFTY 50, SENSEX, sectoral indices, etc.) with their latest values.",
404
297
  inputSchema: {
405
- page: z2.number().int().positive().optional(),
406
- pageSize: z2.number().int().positive().max(200).optional()
298
+ page: z.number().int().positive().optional(),
299
+ pageSize: z.number().int().positive().max(200).optional()
407
300
  }
408
301
  },
409
302
  async ({ page, pageSize }) => run(() => client.indices.list({ page, pageSize }))
@@ -414,11 +307,11 @@ function buildServer(options = {}) {
414
307
  title: "Get historical index prices",
415
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.",
416
309
  inputSchema: {
417
- name: z2.string().describe("Index name, e.g. 'NIFTY 50'."),
310
+ name: z.string().describe("Index name, e.g. 'NIFTY 50'."),
418
311
  fromDate,
419
312
  toDate,
420
- page: z2.number().int().positive().optional(),
421
- pageSize: z2.number().int().positive().max(500).optional()
313
+ page: z.number().int().positive().optional(),
314
+ pageSize: z.number().int().positive().max(500).optional()
422
315
  }
423
316
  },
424
317
  async ({ name, fromDate: fromDate2, toDate: toDate2, page, pageSize }) => run(() => client.indices.prices(name, { fromDate: fromDate2, toDate: toDate2, page, pageSize }))
@@ -431,8 +324,8 @@ function buildServer(options = {}) {
431
324
  inputSchema: {
432
325
  fromDate,
433
326
  toDate,
434
- limit: z2.number().int().positive().max(500).optional(),
435
- latest: z2.boolean().optional().describe("Return only the most recent day.")
327
+ limit: z.number().int().positive().max(500).optional(),
328
+ latest: z.boolean().optional().describe("Return only the most recent day.")
436
329
  }
437
330
  },
438
331
  async ({ fromDate: fromDate2, toDate: toDate2, limit, latest }) => run(() => client.market.fiiDii({ fromDate: fromDate2, toDate: toDate2, limit, latest }))
@@ -443,9 +336,9 @@ function buildServer(options = {}) {
443
336
  title: "Get market-wide bulk deals",
444
337
  description: "Bulk deals across the whole market for the latest sessions.",
445
338
  inputSchema: {
446
- buySell: z2.enum(["BUY", "SELL"]).optional(),
447
- page: z2.number().int().positive().optional(),
448
- pageSize: z2.number().int().positive().max(100).optional()
339
+ buySell: z.enum(["BUY", "SELL"]).optional(),
340
+ page: z.number().int().positive().optional(),
341
+ pageSize: z.number().int().positive().max(100).optional()
449
342
  }
450
343
  },
451
344
  async ({ buySell, page, pageSize }) => run(() => client.deals.bulk({ buySell, page, pageSize }))
@@ -456,9 +349,9 @@ function buildServer(options = {}) {
456
349
  title: "Get market-wide block deals",
457
350
  description: "Block deals across the whole market for the latest sessions.",
458
351
  inputSchema: {
459
- buySell: z2.enum(["BUY", "SELL"]).optional(),
460
- page: z2.number().int().positive().optional(),
461
- pageSize: z2.number().int().positive().max(100).optional()
352
+ buySell: z.enum(["BUY", "SELL"]).optional(),
353
+ page: z.number().int().positive().optional(),
354
+ pageSize: z.number().int().positive().max(100).optional()
462
355
  }
463
356
  },
464
357
  async ({ buySell, page, pageSize }) => run(() => client.deals.block({ buySell, page, pageSize }))
@@ -469,8 +362,8 @@ function buildServer(options = {}) {
469
362
  title: "Get market-wide insider trades",
470
363
  description: "SEBI PIT insider-trading disclosures across the whole market.",
471
364
  inputSchema: {
472
- page: z2.number().int().positive().optional(),
473
- pageSize: z2.number().int().positive().max(100).optional()
365
+ page: z.number().int().positive().optional(),
366
+ pageSize: z.number().int().positive().max(100).optional()
474
367
  }
475
368
  },
476
369
  async ({ page, pageSize }) => run(() => client.deals.insiderTrades({ page, pageSize }))
@@ -481,14 +374,14 @@ function buildServer(options = {}) {
481
374
  title: "Search mutual-fund schemes",
482
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.",
483
376
  inputSchema: {
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()
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()
492
385
  }
493
386
  },
494
387
  async (args) => run(() => client.mutualFunds.listSchemes(args))
@@ -499,7 +392,7 @@ function buildServer(options = {}) {
499
392
  title: "Get mutual-fund scheme detail",
500
393
  description: "One scheme's identity plus its latest NAV (with as-of date).",
501
394
  inputSchema: {
502
- schemeCode: z2.string().describe("AMFI scheme code.")
395
+ schemeCode: z.string().describe("AMFI scheme code.")
503
396
  }
504
397
  },
505
398
  async ({ schemeCode }) => run(() => client.mutualFunds.getScheme(schemeCode))
@@ -510,10 +403,10 @@ function buildServer(options = {}) {
510
403
  title: "Get mutual-fund NAV history",
511
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.",
512
405
  inputSchema: {
513
- schemeCode: z2.string().describe("AMFI scheme code."),
406
+ schemeCode: z.string().describe("AMFI scheme code."),
514
407
  fromDate,
515
408
  toDate,
516
- limit: z2.number().int().positive().max(2e3).optional()
409
+ limit: z.number().int().positive().max(2e3).optional()
517
410
  }
518
411
  },
519
412
  async ({ schemeCode, fromDate: fromDate2, toDate: toDate2, limit }) => run(() => client.mutualFunds.nav(schemeCode, { fromDate: fromDate2, toDate: toDate2, limit }))
@@ -524,7 +417,7 @@ function buildServer(options = {}) {
524
417
  title: "Get mutual-fund trailing returns",
525
418
  description: "Trailing returns (1m/3m/6m/1y/3y/5y + since-inception) computed from the scheme's NAV series (CAGR for >=1y horizons).",
526
419
  inputSchema: {
527
- schemeCode: z2.string().describe("AMFI scheme code.")
420
+ schemeCode: z.string().describe("AMFI scheme code.")
528
421
  }
529
422
  },
530
423
  async ({ schemeCode }) => run(() => client.mutualFunds.returns(schemeCode))
@@ -536,9 +429,9 @@ function buildServer(options = {}) {
536
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'.",
537
430
  inputSchema: {
538
431
  ticker,
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()
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()
542
435
  }
543
436
  },
544
437
  async ({ ticker: ticker2, month, page, pageSize }) => run(() => client.stocks.mfHoldings(ticker2, { month, page, pageSize }))
@@ -549,10 +442,10 @@ function buildServer(options = {}) {
549
442
  title: "List / search mutual-fund portfolios",
550
443
  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.",
551
444
  inputSchema: {
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()
445
+ q: z.string().optional().describe("Substring match on scheme name."),
446
+ fundHouse: z.string().optional().describe("Filter by fund house (substring)."),
447
+ limit: z.number().int().positive().max(200).optional(),
448
+ offset: z.number().int().nonnegative().optional()
556
449
  }
557
450
  },
558
451
  async ({ q, fundHouse, limit, offset }) => run(() => client.mutualFunds.listPortfolios({ q, fundHouse, limit, offset }))
@@ -563,10 +456,10 @@ function buildServer(options = {}) {
563
456
  title: "Get a mutual-fund scheme's portfolio holdings",
564
457
  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.",
565
458
  inputSchema: {
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()
459
+ schemeName: z.string().describe("Holdings scheme name, exactly as returned by list_mutual_fund_portfolios."),
460
+ month: z.string().optional().describe("Portfolio month YYYY-MM. Latest if omitted."),
461
+ page: z.number().int().positive().optional(),
462
+ pageSize: z.number().int().positive().max(100).optional()
570
463
  }
571
464
  },
572
465
  async ({ schemeName, month, page, pageSize }) => run(() => client.mutualFunds.getPortfolioHoldings(schemeName, { month, page, pageSize }))
@@ -580,10 +473,6 @@ function buildServer(options = {}) {
580
473
  },
581
474
  async () => run(() => client.status())
582
475
  );
583
- if (adminSecret) {
584
- const baseUrl = sdkOptions.baseUrl ?? "https://bharatstockapi.com";
585
- registerOperatorTools(server, { baseUrl, adminSecret, fetchImpl: operatorFetchImpl });
586
- }
587
476
  return server;
588
477
  }
589
478
 
package/dist/http.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  buildServer,
4
4
  checkAuthorization
5
- } from "./chunk-NXHQTUBJ.js";
5
+ } from "./chunk-PWPXQCNU.js";
6
6
 
7
7
  // src/http.ts
8
8
  import { randomUUID } from "crypto";
package/dist/index.d.ts CHANGED
@@ -1,34 +1,6 @@
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
-
32
4
  /**
33
5
  * Builds the BharatStock MCP server: a thin layer that exposes the
34
6
  * BharatStock REST API's consumer-facing (X-API-Key gated) endpoints as
@@ -42,25 +14,14 @@ type FetchLike = (input: string, init?: {
42
14
  */
43
15
 
44
16
  declare const SERVER_NAME = "bharatstock";
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
- }
17
+ declare const SERVER_VERSION = "0.2.2";
57
18
  /**
58
19
  * Construct the MCP server and register all tools against a BharatStock
59
20
  * client. Exposed separately from the stdio wiring so tests can build a
60
21
  * server with an injected `fetchImpl` and drive it over an in-memory
61
22
  * transport (no network, no real key).
62
23
  */
63
- declare function buildServer(options?: BharatStockOptions & BuildServerExtras): McpServer;
24
+ declare function buildServer(options?: BharatStockOptions): McpServer;
64
25
 
65
26
  /**
66
27
  * 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-NXHQTUBJ.js";
7
+ } from "./chunk-PWPXQCNU.js";
8
8
 
9
9
  // src/index.ts
10
10
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -25,11 +25,7 @@ async function main() {
25
25
  `);
26
26
  process.exit(1);
27
27
  }
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 });
28
+ const server = buildServer({ apiKey, baseUrl });
33
29
  const transport = new StdioServerTransport();
34
30
  await server.connect(transport);
35
31
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bharatstock-mcp",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "mcpName": "com.bharatstockapi/bharatstock-mcp",
5
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.",
6
6
  "license": "MIT",
@@ -54,13 +54,13 @@
54
54
  "typecheck": "tsc --noEmit"
55
55
  },
56
56
  "dependencies": {
57
- "@modelcontextprotocol/sdk": "^1.12.0",
57
+ "@modelcontextprotocol/sdk": "^1.32.2",
58
58
  "bharatstock": "^0.1.12",
59
- "zod": "^3.23.8"
59
+ "zod": "^4.6.7"
60
60
  },
61
61
  "devDependencies": {
62
62
  "@types/node": "^22.0.0",
63
- "tsup": "^8.0.2",
63
+ "tsup": "^9.0.0",
64
64
  "typescript": "^5.5.4",
65
65
  "vitest": "^5.0.0"
66
66
  }