<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Govind Sisara</title>
    <description>The latest articles on DEV Community by Govind Sisara (@govind_sisara).</description>
    <link>https://dev.to/govind_sisara</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3993452%2Fd4c17d12-35ba-446d-96df-9f5581115ef3.jpeg</url>
      <title>DEV Community: Govind Sisara</title>
      <link>https://dev.to/govind_sisara</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/govind_sisara"/>
    <language>en</language>
    <item>
      <title>I built an open-source MCP server that gives any AI assistant live NSE + BSE stock data</title>
      <dc:creator>Govind Sisara</dc:creator>
      <pubDate>Sat, 20 Jun 2026 02:54:53 +0000</pubDate>
      <link>https://dev.to/govind_sisara/i-built-an-open-source-mcp-server-that-gives-any-ai-assistant-live-nse-bse-stock-data-5f8h</link>
      <guid>https://dev.to/govind_sisara/i-built-an-open-source-mcp-server-that-gives-any-ai-assistant-live-nse-bse-stock-data-5f8h</guid>
      <description>&lt;h2&gt;
  
  
  The itch
&lt;/h2&gt;

&lt;p&gt;Ask ChatGPT or Claude "what was Reliance's operating margin last quarter?" and you get one of two answers: a polite "I don't have live data," or a confident, wrong number. The reasoning is great. The data access is nonexistent — these models are blind to anything past their training cut-off, and they have zero native window into NSE/BSE.&lt;/p&gt;

&lt;p&gt;So I built an MCP server to fix it, and open-sourced it. This is the build story + a quick-start if you want to plug it into your own setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  30-second MCP refresher
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Model Context Protocol&lt;/strong&gt; is an open standard that lets AI assistants call external tools through a consistent interface. Instead of guessing, the model issues a structured request — &lt;code&gt;get_stock_quote&lt;/code&gt; for &lt;code&gt;RELIANCE&lt;/code&gt; — gets real data back, and reasons over it. One protocol, and Claude, ChatGPT, Cursor, Gemini, and Grok can all hit the same data source.&lt;/p&gt;

&lt;p&gt;It's basically USB-C for AI tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I exposed: 34 tools
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;search_stocks            screen_stocks (326 fundamental filters)
screen_stocks_technical  get_company_profile   get_financials
get_stock_quote          get_price_history     get_shareholding
get_fii_dii_detail       market_ipo            market_fno_ban
get_user_portfolio       add_to_watchlist      ...and 21 more
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Coverage: all ~8,200 NSE + BSE stocks, fundamentals, technicals, institutional flows, market data, and portfolio tracking. (It's a research tool, not a broker — it doesn't place trades.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture decisions
&lt;/h2&gt;

&lt;p&gt;The big one: &lt;strong&gt;remote-first, on the edge.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────┐   JSON-RPC / HTTPS   ┌──────────────────────┐
│  AI Assistant   │ ───────────────────► │  Cloudflare Worker   │
│ (Claude, etc.)  │ ◄─────────────────── │  (stateless MCP)     │
└─────────────────┘                       └──────────────────────┘
                                              │
                                   D1 (users, tokens, usage)
                                   KV (rate limits, auth codes)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most MCP servers ship as local stdio processes you have to install and run. That's a friction wall for non-developers. I wanted someone to paste one URL into claude.ai and be done — so the server runs as a Cloudflare Worker. Globally distributed, no servers to babysit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stateless transport
&lt;/h3&gt;

&lt;p&gt;The MCP SDK's &lt;code&gt;WebStandardStreamableHTTPServerTransport&lt;/code&gt; in stateless mode maps perfectly onto Workers — any request hits any edge location and is served identically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebStandardStreamableHTTPServerTransport&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;sessionIdGenerator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// stateless&lt;/span&gt;
  &lt;span class="na"&gt;enableJsonResponse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Auth was the hard part
&lt;/h3&gt;

&lt;p&gt;MCP clients vary wildly. Chat apps (Claude.ai, ChatGPT) expect a full OAuth flow with &lt;strong&gt;Dynamic Client Registration (RFC 7591)&lt;/strong&gt; plus discovery endpoints (RFC 8414, RFC 9728). Code editors often just want a bearer token.&lt;/p&gt;

&lt;p&gt;The trick that makes the OAuth handshake "just work": return a &lt;code&gt;401&lt;/code&gt; with a &lt;code&gt;WWW-Authenticate&lt;/code&gt; header on the first &lt;code&gt;initialize&lt;/code&gt;. That's the signal that kicks off the client's built-in OAuth flow.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;authHeader&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Bearer &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isPublicMethod&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;authentication_required&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WWW-Authenticate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="s2"&gt;`Bearer resource_metadata="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;metadataUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;", scope="openid email"`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}},&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the server accepts &lt;strong&gt;both&lt;/strong&gt; short-lived HMAC access tokens and long-lived personal tokens, distinguished by prefix (&lt;code&gt;tpt_rt_…&lt;/code&gt;). One auth surface, two credential types. &lt;code&gt;tools/list&lt;/code&gt; and &lt;code&gt;ping&lt;/code&gt; stay public so registries can discover the catalog without auth.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rate limiting that doesn't punish honest users
&lt;/h3&gt;

&lt;p&gt;Two independent layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Per-minute burst smoother&lt;/strong&gt; (KV, &lt;em&gt;fails open&lt;/em&gt;) — stops one token from dumping its whole daily allowance in seconds. If KV hiccups, allow the request; the daily cap is the real ceiling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Daily/monthly quota&lt;/strong&gt; (D1, &lt;em&gt;fails closed&lt;/em&gt;) — an atomic &lt;code&gt;UPSERT&lt;/code&gt; reserves a unit &lt;em&gt;before&lt;/em&gt; the tool runs. Limiter unreachable → deny, never hand out unmetered access.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The part I'm happiest with: &lt;strong&gt;failed calls get refunded.&lt;/strong&gt; The MCP SDK throws &lt;code&gt;InvalidParams&lt;/code&gt; whenever a tool argument fails schema validation — and LLMs hallucinate bad arguments constantly. Charging a user's quota because their model fumbled an argument would be infuriating.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;quotaConsumed&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;quotaConsumed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;            &lt;span class="c1"&gt;// guard against double-refund&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hasError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitUntil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;refundRateLimitV2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;month&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitUntil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;trackToolCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The npm package is just a bridge
&lt;/h3&gt;

&lt;p&gt;For stdio-only clients there's a tiny npm package — ~300 lines, zero runtime deps. No business logic: it reads JSON-RPC from stdin, forwards to the Worker over HTTPS, writes responses to stdout. It auto-detects message framing (Content-Length vs newline-delimited JSON, which differ across clients) and refreshes the access token before expiry. All tool logic lives on the server, so new tools ship instantly without anyone running &lt;code&gt;npm update&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick start
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;No install&lt;/strong&gt; (claude.ai, ChatGPT, Gemini, Grok) — paste the URL, sign in with Google:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://mcp.tapetide.com/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Local via npm&lt;/strong&gt; (Cursor, Windsurf, Claude Desktop):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"tapetide"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tapetide-mcp"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"TAPETIDE_TOKEN"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your_token_here"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then just ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Find mid-caps where FII holding rose last quarter, ROE above 15%, and RSI under 40."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Lessons if you're building your own MCP server
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Go remote if your audience isn't all devs.&lt;/strong&gt; A pasteable URL beats a &lt;code&gt;npx&lt;/code&gt; command for reach.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stateless + edge is a natural fit&lt;/strong&gt; for MCP's request/response shape.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Budget real time for OAuth + DCR.&lt;/strong&gt; It's the least glamorous, most fragile part — implement the &lt;code&gt;.well-known&lt;/code&gt; discovery endpoints precisely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Support more than one credential type.&lt;/strong&gt; Chat apps and editors want different things.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refund quota on errors.&lt;/strong&gt; LLMs make bad tool calls all the time; don't bill users for it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Free and open source under MIT. Source, all 34 tools, and setup guides:&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;&lt;a href="https://github.com/Tapetide-hq/nse-bse-indian-stock-market-data-mcp" rel="noopener noreferrer"&gt;https://github.com/Tapetide-hq/nse-bse-indian-stock-market-data-mcp&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you build something on top of it or have ideas for tools you'd want, I'd love to hear it in the comments.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>typescript</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
