<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Bharat Rawat on Medium]]></title>
        <description><![CDATA[Stories by Bharat Rawat on Medium]]></description>
        <link>https://medium.com/@bharat-rawat?source=rss-1a2ed62837ba------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*02DU49ne54AVIOu69n3r4Q.jpeg</url>
            <title>Stories by Bharat Rawat on Medium</title>
            <link>https://medium.com/@bharat-rawat?source=rss-1a2ed62837ba------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 14 Nov 2026 01:25:35 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@bharat-rawat/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[What Is the Model Context Protocol (MCP), and When Do You Need It?]]></title>
            <link>https://medium.com/@bharat-rawat/what-is-the-model-context-protocol-mcp-and-when-do-you-need-it-24c298ddab01?source=rss-1a2ed62837ba------2</link>
            <guid isPermaLink="false">https://medium.com/p/24c298ddab01</guid>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[ai-agent]]></category>
            <category><![CDATA[mcp-server]]></category>
            <category><![CDATA[api]]></category>
            <dc:creator><![CDATA[Bharat Rawat]]></dc:creator>
            <pubDate>Wed, 07 Oct 2026 11:07:30 GMT</pubDate>
            <atom:updated>2026-10-07T11:27:44.007Z</atom:updated>
            <content:encoded><![CDATA[<h3>What is Model Context Protocol and when do you need it?</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IEdJdxSc1eJe_OZGjUFqaw.jpeg" /></figure><p>The Model Context Protocol (MCP) has become an important piece of infrastructure in the AI ecosystem. It is often described as “<strong>USB-C for AI</strong>”.</p><p>That’s a useful analogy, but it doesn’t answer the question most engineers actually have:</p><blockquote>Why do we need MCP when we already have APIs, SDKs, and function calling?</blockquote><p>The short answer is:</p><blockquote>MCP standardizes the integration boundary between AI applications and external tools and services.</blockquote><p>MCP becomes valuable when the same tools and services need to be consumed by multiple AI applications.</p><h3>The Problem: Connecting AI to the Real World</h3><p>An LLM is good at reasoning and generating text, but useful AI applications need access to systems outside the model. For example:</p><ul><li>Databases</li><li>GitHub</li><li>Slack</li><li>Jira</li><li>CRMs</li><li>Internal APIs</li><li>Documentation</li><li>Business Workflows</li></ul><h3>The Need for an MCP Server</h3><p>A traditional AI application might look like this:</p><figure><img alt="A Traditional AI Application Flow" src="https://cdn-images-1.medium.com/max/220/1*VyfKATD9KjLYvwZJ4GMjpA.png" /></figure><p>And there’s nothing wrong with this architecture. If you’re building one application and you control all of these systems, direct integrations may be the simplest solution.</p><p>The problem appears when <strong>multiple AI applications need access to the same capabilities</strong>.</p><figure><img alt="Multiple AI Applications Accessing Internal Database" src="https://cdn-images-1.medium.com/max/677/1*7814QvQRQv-eQCl2SZQ_eA.png" /></figure><p>Without a common protocol, every application needs its own integration, making the overall system harder to manage.</p><p>MCP addresses this by introducing a standardized protocol between the AI application and the capability provider.</p><figure><img alt="Multiple AI Agents Accessing Internal Database Through MCP" src="https://cdn-images-1.medium.com/max/672/1*HJO-yc1X36OlGrckbhR2HQ.png" /></figure><p><strong>The key value is therefore interoperability.</strong></p><h3>What Is MCP?</h3><p>MCP is a protocol that standardizes how AI applications interact with external tools and services.</p><p>An MCP server can expose things such as:</p><ul><li><strong>Tools — </strong>actions the AI can invoke</li><li><strong>Resources — </strong>contextual information the AI can retrieve</li><li><strong>Prompts — </strong>reusable prompt templates or workflows</li></ul><p>For example, a GitHub MCP server might expose capabilities such as:</p><pre>search_repositories()<br>get_issue()<br>search_pull_requests()<br>create_issue()</pre><p>A database-oriented MCP server might expose:</p><pre>search_customer()<br>get_customer_orders()<br>get_customer_profile()</pre><p>The important part is that the AI application doesn’t need to know how these operations are implemented internally. The server could be backed by:</p><pre>REST API<br>GraphQL<br>gRPC<br>SQL<br>SDK<br>Internal service</pre><h3>MCP vs APIs</h3><p>A common misconception is that MCP is intended to replace REST, gRPC, GraphQL, or other application APIs, but it isn’t.</p><p>Suppose your company already has:</p><pre>GET  /customers/{id}<br>POST /customers/search<br>POST /tickets</pre><p>Those APIs can continue to exist and can be put behind an MCP server. So the MCP server doesn’t replace the APIs, it utilizes them.</p><h3>Do I Need MCP for My Own AI Agent?</h3><p>Well, it depends. If you have only one AI agent that you developed and there are no other agents involved, you don’t necessarily need an MCP server. You can integrate your internal systems and services directly with the agent.</p><p>However, if you have multiple AI agents, including your own agents as well as third-party agents like Claude, ChatGPT, Gemini, etc., and all of them need access to your tools and services, then an MCP server would make sense. It provides a standardized way for different agents to interact with your tools and services.</p><h3>The Real Value of MCP</h3><p>The biggest value of MCP isn’t that it allows an LLM to call a function. We could already do that. The bigger change is where the integration boundary lives.</p><p>Without MCP, integrations often live inside individual AI applications:</p><figure><img alt="AI Application Accessing External Tools Without MCP Server" src="https://cdn-images-1.medium.com/max/387/1*tT92Il8-NUEq8AHazUZ3nw.png" /></figure><p>With MCP, capabilities can be exposed independently:</p><figure><img alt="AI Application Accessing External Tools With MCP Server" src="https://cdn-images-1.medium.com/max/471/1*g1JSmtWQWIABNnaQdIrxZg.png" /></figure><p>That enables a capability provider to build an integration once and make it consumable by multiple compatible AI applications.</p><h3>Conclusion</h3><p>MCP is not a replacement for APIs. And you don’t automatically need it just because you’re building an AI application.</p><p>If you’re building a single AI application that talks to your own backend, direct APIs and function calling may be all you need.</p><p>But if you’re building a tool or service that should be consumed by multiple AI applications, in that case MCP becomes significantly more valuable.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=24c298ddab01" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Building a Production-Ready Text-to-SQL AI Chatbot Without LangChain or LlamaIndex]]></title>
            <link>https://medium.com/@bharat-rawat/building-a-production-ready-text-to-sql-ai-chatbot-without-langchain-or-llamaindex-f3c0ea90247d?source=rss-1a2ed62837ba------2</link>
            <guid isPermaLink="false">https://medium.com/p/f3c0ea90247d</guid>
            <category><![CDATA[ai-chatbot]]></category>
            <category><![CDATA[text-to-sql]]></category>
            <category><![CDATA[llamaindex]]></category>
            <category><![CDATA[chatbot-development]]></category>
            <category><![CDATA[langchain]]></category>
            <dc:creator><![CDATA[Bharat Rawat]]></dc:creator>
            <pubDate>Mon, 07 Sep 2026 17:25:26 GMT</pubDate>
            <atom:updated>2026-09-07T17:25:26.319Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="Building a Production-Ready Text-to-SQL AI Chatbot" src="https://cdn-images-1.medium.com/max/1024/1*WywFC5U2Bs2MEzZCA_Mf4A.png" /></figure><p>My first instinct when I started designing a Text-to-SQL chatbot was fairly straightforward: take a user’s question, give the LLM enough database context, ask it to generate SQL, execute the query, and return the result.</p><p>That sounds reasonable. It is also nowhere near enough for a production-oriented system.</p><p>As I worked through the design, I realized that SQL generation was actually only one part of the problem. The harder questions were around <strong>Context</strong>, <strong>Schema selection</strong>, <strong>Follow-Up Questions</strong>, <strong>Failed SQL</strong>, <strong>Safe Execution</strong>, <strong>Model Providers</strong>, <strong>Observability</strong>, and deciding which parts of the workflow actually needed to run for every request.</p><p>I also deliberately decided not to use LangChain, LlamaIndex, or another orchestration framework. I wanted to understand and control the individual pieces myself.</p><p>The result is a custom architecture built around Django REST Framework, PostgreSQL, Angular, and pluggable LLM providers.</p><p>This is the architecture I am building and the reasoning behind it.</p><blockquote><strong>Note</strong>: This article focuses mainly on <strong>New and Follow-Up user queries</strong> that require SQL generation or updates. Also the components I developed for the chatbot. It does not cover how the chatbot handles greetings, out-of-domain questions, capability-related questions, or clarification requests.</blockquote><h3>Why I Built a Text-to-SQL Chatbot</h3><p>The goal wasn’t to build a chatbot that knows how to write SQL. The goal was to build an internal business analytics assistant.</p><p>The users are non-technical management team members. They shouldn’t need to know SQL, PostgreSQL, Table Names, Column Names, Joins, Database Schemas, etc.</p><p>They should be able to ask a question naturally:</p><pre>How many email issue tickets were created in the last 7 days?</pre><p>Then continue:</p><pre>And in the last 1 month?</pre><p>Then:</p><pre>And how many are still pending?</pre><p>And then:</p><pre>Break them down by priority.</pre><p>The technical implementation behind those questions should be invisible to the user. That distinction influenced almost every architectural decision I made afterward. I wasn’t building a SQL interface. I was <strong>building a business analytics interface that uses Text-to-SQL internally.</strong></p><h3>The Problem I Wanted to Solve</h3><p>A basic Text-to-SQL pipeline looks like this:</p><figure><img alt="Text-to-SQL AI Chatbot Flow Diagram" src="https://cdn-images-1.medium.com/max/819/1*_30_hxcPX6whqkDJfm9Jiw.png" /></figure><p>This is attractive because it’s simple, but consider what happens when the user asks:</p><pre>How many tickets were created for email issues in the last 7 days?</pre><p>Then:</p><pre>And in the last 1 month?</pre><p>The second question isn’t a complete SQL request by itself. The model needs to understand that:</p><ul><li>we’re still talking about tickets</li><li>we’re still talking about email issues</li><li>the metric is still ticket count</li><li>only the time period changed</li></ul><p>Then the user asks:</p><pre>And how many are still pending?</pre><p>Now the system needs to preserve the previous analytical context and modify the analysis again.</p><p>Then the user says:</p><pre>Give me the count of rides scheduled yesterday.</pre><p>That’s a completely different analysis.</p><p>The conversation hasn’t ended, but the active analytical context has changed. This is where I realized that a production Text-to-SQL system needs considerably more structure than: Prompt -&gt; SQL -&gt; Database.</p><h3>Technology Stack</h3><p>The current stack is:</p><ul><li><strong>Angular</strong> for the frontend</li><li><strong>Django REST Framework</strong> for the backend API</li><li><strong>PostgreSQL</strong> as the database being queried</li><li><strong>Qwen2.5-Coder 7B through Ollama</strong> for local development and testing</li><li><strong>OpenAI’s API</strong> for production</li></ul><p>I also deliberately kept the LLM provider behind an abstraction. The application shouldn’t care whether the request is being sent to Ollama locally or to OpenAI APIs in production.</p><p>Conceptually:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/827/1*emTLhAnmDWrHMm4AIPQdOA.png" /></figure><p>This gives me the ability to test the workflow locally without making the entire architecture dependent on one provider.</p><h3>Why I Didn’t Use LangChain or LlamaIndex</h3><p>I considered the usual framework-based approach, but I decided to build the orchestration myself.</p><p>This isn’t because I think LangChain or LlamaIndex are inherently bad choices, but frameworks make debugging very difficult. Also, with a custom orchestration layer, you can track every single prompt.</p><p>Instead of hiding orchestration inside a framework, I wanted those decisions to be visible in my own Python code.</p><p>The trade-off is obvious: I now have to build and maintain that orchestration myself. But for this internal application, that trade-off is acceptable because understanding and controlling the workflow is more important to me than minimizing the amount of code.</p><h3>High-Level Architecture</h3><p>The architecture evolved into something closer to this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/827/1*G7Q-R2S6LyzT-YJMXFbYvQ.png" /></figure><p>There are several specialized components behind this diagram.</p><blockquote><strong>Note</strong>: <strong>Other Workflows</strong> are to handle Greetings, Out-of-Domain questions, Capability related questions, Clarification requests, and other types of user queries.</blockquote><h3>The Major Components</h3><h4>1. Intent Detector</h4><p>The chatbot didn’t have an Intent Detector initially. I introduced one later as I added more workflows, because not every user message needs to go through the SQL Generation workflow.</p><p>The detector is intentionally business-oriented. It doesn’t need to know that the system generates SQL; its job is simply to determine what the user is trying to accomplish and route the request to the appropriate workflow.</p><p>For example:</p><ul><li><em>Hello, how are you? </em>— is a greeting.</li><li><em>What can you do? — </em>is a capability request.</li><li><em>Tell me a joke. / What is the weather today? / What is the match score? </em>— All these questions are out of domain.</li></ul><p>None of these requests require the SQL Generation workflow. The Intent Detector allows the chatbot to identify such requests early and avoid invoking an unnecessary workflow.</p><h4>2. Request Router</h4><p>The Intent Detector answers: <strong><em>“What kind of request is this?”</em></strong></p><p>The Request Router answers: <strong><em>“Which workflow should handle it?”</em></strong></p><p>I deliberately separated these two responsibilities because they solve different problems. The Intent Detector classifies the user’s request, while the Request Router decides where that request should go.</p><p>This separation gives me a cleaner architecture and keeps the responsibilities well defined.</p><p>It may seem like extra abstraction for a small application, but I prefer this approach because the routing logic will likely grow as the system evolves.</p><p>Keeping these responsibilities separate makes that growth easier to manage without making the Intent Detector responsible for workflow-specific decisions.</p><h4>3. Workflows</h4><p>The workflow layer is where the major orchestration happens. I initially had a single SQL Generation workflow, but as the system became more sophisticated, I moved toward separate workflows.</p><h4>4. Schema Metadata</h4><p>One of the most important decisions was <strong>not to send the entire database schema to the model for every request</strong>.</p><p>I maintain business-oriented metadata such as:</p><pre>TABLES = {<br>    &quot;tickets&quot;: {<br>        &quot;description&quot;: &quot;...&quot;<br>    },<br>    # ...<br>}</pre><p>and additional information in a separate dictionary, including Columns, Relationships, and Business Concepts.</p><p>This lets the application progressively provide context to the model. The first step is deliberately broad. I send the available table names and their descriptions to the LLM and ask:</p><p><strong><em>“Which tables are relevant to this question?”</em></strong></p><p>Only after I have the relevant tables do I retrieve their detailed schema information. That leads to a two-stage process:</p><p><em>User Question &gt; All Table Descriptions &gt; Table Selection LLM &gt; Relevant Tables &gt; Columns + Relationships + Business Concepts &gt; SQL Generation LLM</em></p><p>This is much more controlled than blindly putting the entire schema into the SQL-generation prompt.</p><h4>5. LLM Provider</h4><p>I created an abstraction around the model provider. The rest of the application shouldn’t need to know whether it is talking to Ollama or OpenAI.</p><p>The provider layer is responsible for the model-specific communication, while the workflows decide what the model should be asked to do.</p><p>That separation also makes local development practical. I can use Qwen2.5-Coder 7B through Ollama locally and switch to OpenAI APIs without redesigning the workflow.</p><h4>6. Conversation State</h4><p>This was one of the areas where my thinking changed considerably. Initially, it is tempting to think: “I’ll just pass the conversation history to the LLM.”</p><p>But conversation history and analytical state aren’t the same thing. Conversation history tells me what people said. State tells me what we currently know about the active analysis.</p><p>For example, after:</p><p><strong><em>“How many email issue tickets were created in the last 7 days?”</em></strong></p><p>the active state can preserve information such as:</p><pre>{<br>    &quot;active_analysis&quot;: {<br>        &quot;primary_tables&quot;: [<br>            &quot;ticket&quot;,<br>            &quot;ticket_detail&quot; // Contains the ticket origin and related details.<br>        ],<br>        &quot;last_sql&quot;: &quot;...&quot;,<br>        &quot;last_result_summary&quot;: &quot;...&quot;,<br>        &quot;filters&quot;: {<br>            &quot;issue_type&quot;: &quot;email&quot;,<br>            &quot;time_range&quot;: &quot;last_7_days&quot;<br>        }<br>    }<br>}</pre><p>The exact state will evolve, but the important concept is that the state represents the current analytical context. This becomes especially useful when the user asks:</p><p><strong><em>“And in the last month?”</em></strong></p><p>The application doesn’t have to rediscover everything from scratch.</p><h4>7. Table Selector</h4><p>The table selector has one job: Identify which business entities are relevant to the user’s question.</p><p>For example, <strong>“How many email issue tickets were created last week?” </strong>might identify the ticket-related tables rather than exposing every table in the database to the SQL generator. This also helps reduce prompt size and keeps the SQL-generation context focused.</p><h4>8. SQL Generator</h4><p>Once the relevant schema has been selected, the SQL generator receives:</p><ul><li>the user’s question</li><li>relevant table information</li><li>relevant columns</li><li>relationships</li><li>business concepts</li><li>necessary conversational context</li></ul><p>Its job is to produce read-only SQL, which is simply a SELECT query.</p><h4><strong>9. SQL Validator</strong></h4><p>I don’t trust generated SQL simply because it was produced by an LLM. AI-generated queries may contain harmful or destructive clauses, such as DROP, DELETE, or other data-modification statements. They may also generate expensive or inefficient queries that could negatively impact database performance.</p><p>This is why I want a clear boundary between:</p><p><em>LLM-generated SQL &gt; SQL Validation &gt; Database</em></p><p>That boundary is particularly important because this is a read-only internal analytics system. The model should never be given unrestricted access to modify the database.</p><h4>10. SQL Repair</h4><p>SQL generation can fail. A query can have:</p><ul><li>syntax errors</li><li>incorrect assumptions</li><li>invalid references</li><li>other execution problems</li></ul><p>Rather than immediately returning an error to the user, the workflow can send the failure information back to the LLM and ask it to repair the query. <em>Refer to the High Level Architecture above to understand the flow.</em></p><p>I also log these attempts because a production system needs to make failures observable rather than invisible.</p><h4>11. Result Summarizer</h4><p>The database result isn’t necessarily the final response. A user doesn’t want to see a raw database result like:</p><pre>[<br>    {&quot;priority&quot;: &quot;High&quot;, &quot;count&quot;: 125},<br>    {&quot;priority&quot;: &quot;Medium&quot;, &quot;count&quot;: 312}<br>]</pre><p>The final layer converts the result into a concise business-oriented response. The workflow therefore ends with:</p><p><em>SQL Result &gt; Result Summarizer &gt; Business Response</em></p><p>This also keeps technical details away from the management user.</p><h3>Final Thoughts</h3><p>Building a Text-to-SQL chatbot turned out to be much less about <strong>“getting an LLM to write SQL”</strong> than I initially expected. The interesting engineering problem is everything surrounding the SQL.</p><p>I needed a way to select only relevant Schema Context. I needed structured conversation state so follow-up questions could work reliably. I needed intent detection and routing so every message wouldn’t trigger the expensive analytical workflow. I needed separate workflows for all the Intent Types as well as new and continuing analysis. I needed validation and repair because generated SQL isn’t automatically trustworthy.</p><p>The biggest lesson for me is that a production-oriented AI application shouldn’t treat the LLM as the application. The LLM is one component inside the application. The application still needs architecture, state, validation, routing, observability, security, and clear boundaries around what the model is allowed to do.</p><p>And that’s ultimately why I chose to build this system myself instead of handing the entire orchestration problem to a framework.</p><p>The interesting part isn’t just making an LLM generate SQL.</p><p>It’s engineering everything around it so that the system can actually be trusted to have a conversation.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f3c0ea90247d" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>