<?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" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Sudheesh Shetty - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Sudheesh Shetty - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Tue, 23 Jun 2026 22:43:55 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/author/sudheeshshetty/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ From LLMs to LangChain: Understanding How Modern AI Applications Actually Work ]]>
                </title>
                <description>
                    <![CDATA[ Typically, when we start experimenting with AI, many of us begin similarly. We try a single LLM call as the core of an app, like this: const response = await llm.chat("Explain Kubernetes"); For a lit ]]>
                </description>
                <link>https://www.freecodecamp.org/news/from-llms-to-langchain-understanding-how-modern-ai-applications-actually-work/</link>
                <guid isPermaLink="false">6a3aab13b5ad15098db82372</guid>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ llm ]]>
                    </category>
                
                    <category>
                        <![CDATA[ langchain ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Open Source ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Sudheesh Shetty ]]>
                </dc:creator>
                <pubDate>Tue, 23 Jun 2026 15:49:39 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/38787e16-7e86-44da-9a6a-620cc1a99fce.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Typically, when we start experimenting with AI, many of us begin similarly. We try a single LLM call as the core of an app, like this:</p>
<pre><code class="language-plaintext">const response = await llm.chat("Explain Kubernetes");
</code></pre>
<p>For a little while it feels like the whole flow is: the user asks something, and the model returns an answer. That early success often creates a false impression that building AI is just about sending prompts and getting responses.</p>
<p>That simplicity is seductive, but it doesn't hold up. Over time, users want the assistant to find answers in their documents and knowledge bases, call APIs, fetch live data, or trigger services or schedule meetings.</p>
<p>Users also expect the agent to access internal systems and interact with ERPs, CRMs, or other tools holding critical business data. They'll want agents to combine multiple steps, as workflows often require chaining queries, computations, and side effects into reliable processes.</p>
<p>This is where concepts like MCP (the Model Context Protocol) and tools like LangChain come in. Initially, they may seem like buzzwords, but they address different aspects of LLM production.</p>
<p>After experimenting with AI tools, I found that these concepts help solve different problems related to interfaces, orchestration, and system integration.</p>
<p>This article is a practical guide to understanding how LLMs connect with tools, orchestrate workflows, and power real AI applications.</p>
<h3 id="heading-heres-what-well-cover">Here’s what we’ll cover:</h3>
<ol>
<li><p><a href="#heading-what-is-an-llm">What Is an LLM?</a></p>
</li>
<li><p><a href="#heading-why-llms-need-tools">Why LLMs Need Tools</a></p>
</li>
<li><p><a href="#heading-where-mcp-comes-in">Where MCP Comes In</a></p>
</li>
<li><p><a href="#heading-so-what-does-langchain-actually-do">So What Does LangChain Actually Do?</a></p>
</li>
<li><p><a href="#heading-putting-it-together">Putting It Together</a></p>
</li>
<li><p><a href="#heading-what-i-built-while-learning-this">What I Built While Learning This</a></p>
</li>
</ol>
<p>Throughout the article we'll discuss what LLMs are and how they work, what tool-calling looks like in practice, what MCP is and how it works, how LangChain fits into the whole process, and how to put all these tools together.</p>
<p>To follow along, you'll need a basic understanding of Node.js, API operations, and basic JavaScript concepts.</p>
<h2 id="heading-what-is-an-llm"><strong>What Is an LLM?</strong></h2>
<p>LLM stands for <strong>Large Language Model</strong>. It's a class of deep neural networks trained on massive amounts of text to model and generate human-like language. Popular examples you might have heard of include GPT, Claude, Gemini, and Llama.</p>
<h3 id="heading-how-to-call-an-llm-from-a-nodejs-application">How to Call an LLM From a Node.js Application</h3>
<p>Before writing code, let’s understand what it means to call an LLM from a Node.js application.</p>
<p>Calling an LLM means sending input from your application to an AI provider’s API and receiving generated output in return. It's similar to calling any other external service.</p>
<p>In most real-world applications, the model isn't hosted or trained by your application. Instead, providers such as OpenAI and Groq host and maintain the models, while your application communicates with them over HTTP APIs.</p>
<p>In this example, we’ll build a minimal API using Node.js and Express. We’ll create a simple <code>POST /chat</code> endpoint that accepts a user message, sends it to the OpenAI API, receives the generated response, and returns it to the client.</p>
<p>Here, our Node.js server acts as the bridge between the user and the LLM provider.</p>
<p>For this example, create an API key from the <a href="https://console.groq.com/keys">Groq</a> console. Since it offers a free tier, it’s a simple way to experiment and understand the concepts.</p>
<p>First, install the dependencies:</p>
<pre><code class="language-plaintext">npm install express
</code></pre>
<pre><code class="language-javascript">import express from "express";

const app = express();
app.use(express.json());

app.post("/chat", async (req, res) =&gt; {
  const { message } = req.body;
  const response = await fetch("https://api.groq.com/openai/v1/chat/completions", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: GROQ_API_KEY,
    },
    body: JSON.stringify({
      model: "llama-3.3-70b-versatile",
      messages: [{ role: "user", content: message }],
    }),
  });

  const data = await response.json();

  if (!response.ok) {
    return res.status(response.status).json({ error: data });
  }

  const reply = data.choices[0].message.content;

  res.json({ reply });
});

const PORT = process.env.PORT || 8888;
app.listen(PORT, () =&gt; {
  console.log(`Server running on http://localhost:${PORT}`);
});
</code></pre>
<p>Start the server and make a request. Use Postman and do a POST request to <code>/chat</code> using the below body:</p>
<pre><code class="language-plaintext">POST /chat

{
  "message": "Explain Kubernetes"
}
</code></pre>
<p>Example response:</p>
<pre><code class="language-plaintext">{
  "reply": "Kubernetes is a container orchestration platform..."
}
</code></pre>
<p>The backend receives the message, forwards it to the model provider, receives generated text, and returns it to the client.</p>
<p>LLMs are excellent at language-centric tasks: they understand phrasing and intent, generate coherent text, extract structured information from unstructured input, and perform basic reasoning over provided context. These capabilities make them powerful for things like summarization, drafting, and conversational QA.</p>
<p>But there’s an important limitation: LLMs don't automatically know about and can't access your private or live data. They don’t have implicit access to your company database, internal APIs, or the current state of your systems unless you provide that information at runtime.</p>
<p>Because of that limitation, you need secure mechanisms to connect models to live systems and data — which brings us to the idea of tools.</p>
<h2 id="heading-why-llms-need-tools"><strong>Why LLMs Need Tools</strong></h2>
<p>Imagine asking:</p>
<blockquote>
<p>Check my order and raise support if delivery is delayed.</p>
</blockquote>
<p>The model alone can't inspect your order database or create a support ticket in your system. To do that, it must call external functions — for example, a <code>getOrderStatus(orderId)</code> API and a <code>createSupportTicket(orderId, issue)</code> action.</p>
<p>Those callable functions are what we call tools: programmatic interfaces the AI can use to interact with systems and take concrete actions on behalf of users.</p>
<p>A tool is simply a function that an AI model can call to interact with external systems or perform actions.</p>
<p>For example, imagine we have a getOrderStatus(id) function that returns an order’s delivery status.</p>
<p>To expose this to the LLM, we define a tools array. Each tool includes:</p>
<ul>
<li><p>type – currently "function"</p>
</li>
<li><p>function name – the function identifier</p>
</li>
<li><p>function description – helps the LLM decide when to call the tool</p>
</li>
<li><p>function parameters – a JSON Schema describing the arguments the tool expects</p>
</li>
</ul>
<p>Here's an example:</p>
<pre><code class="language-typescript">function getOrderStatus(id) {
  const statuses = ["pending", "success", "cancelled"];
  const status = statuses[Math.floor(Math.random() * statuses.length)];
  return `Your order status is ${status}.`;
}

const tools = [
  {
    type: "function",
    function: {
      name: "getOrderStatus",
      description: "Get the status of an order by its ID",
      parameters: {
        type: "object",
        properties: {
          id: { type: "string", description: "The order ID" },
        },
        required: ["id"],
      },
    },
  },
];
</code></pre>
<p>The above tool format is for Grok. Different LLM providers may use different formats for defining tools, but the overall idea remains the same.</p>
<p>When making the API call, we pass both the user messages and the list of available tools.</p>
<pre><code class="language-typescript">body: JSON.stringify({
    model: "llama-3.3-70b-versatile",
    messages: [{ role: "user", content: message }],
    tools,
}),
</code></pre>
<p>After the API call, the LLM decides whether a tool is needed. If a tool call is requested, our application executes the corresponding function and sends the result back to the model.</p>
<p>For this example, we'll only handle the <code>getOrderStatus</code> tool. We can check whether the model requested a tool call like this:</p>
<pre><code class="language-typescript">const toolCall = data.choices[0].message.tool_calls[0];
const { id } = JSON.parse(toolCall.function.arguments);
const toolResult = getOrderStatus(id)
</code></pre>
<p>and later we can pass the message context with tool result</p>
<pre><code class="language-typescript">body: JSON.stringify({
    model: "llama-3.3-70b-versatile",
    messages: [
        { role: "user", content: message },
        assistantMessage,
        { role: "tool", tool_call_id: toolCall.id, content: toolResult },
    ],
    tools,
}),
</code></pre>
<p>Finally, return the response:</p>
<pre><code class="language-typescript">return res.json({ reply: followUpData.choices[0].message.content });
</code></pre>
<p>Here's a diagram of the flow:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a1fa5fdc5c3ae375fb38ab2/22d6dc4d-ad5e-4fbb-84f6-71c367565282.png" alt="User -> LLM -> Tool Execution -> Tool Result -> Final Response" style="display:block;margin:0 auto" width="1774" height="887" loading="lazy">

<p>The LLM decides whether a tool is needed and generates the required inputs, while your application executes the function.</p>
<h2 id="heading-where-mcp-comes-in"><strong>Where MCP Comes In</strong></h2>
<p>Tools are simple. You define functions and tell the AI what it can use.</p>
<p>For example, <code>getOrderStatus()</code> works well when all tools are built inside your application. But as applications grow, tools may come from many places, like Slack, GitHub, databases, internal systems, or third-party services. Each one may expose tools differently.</p>
<p>This is where <a href="https://www.freecodecamp.org/news/how-does-an-mcp-work-under-the-hood/">MCP (Model Context Protocol) helps</a>. Think of MCP as a common language that lets AI systems connect to external tools in a consistent way.</p>
<p>Tools define what the AI can do. MCP standardizes how the AI connects to and uses those tools.</p>
<p>Now let’s extend the previous /chat API example so the LLM can use tools exposed through MCP. There are multiple ways to do this:</p>
<ul>
<li><p>build and host your own MCP server and expose your application functions</p>
</li>
<li><p>connect to existing third-party MCP servers such as Slack</p>
</li>
</ul>
<p>For this tutorial, we'll keep things simple and use a remote MCP server approach because it's easier to understand.</p>
<pre><code class="language-plaintext">npm install express @modelcontextprotocol/sdk zod
</code></pre>
<p>Now let’s create our own MCP server and expose the same <code>getOrderStatus</code> function as an MCP tool:</p>
<pre><code class="language-typescript">import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { z } from "zod";

function getOrderStatus(id) {
  const statuses = ["pending", "success", "cancelled"];
  const status = statuses[Math.floor(Math.random() * statuses.length)];
  return `Your order status is ${status}.`;
}

function createOrderServer() {
  const server = new McpServer({ name: "order-server", version: "1.0.0" });

  server.registerTool(
    "getOrderStatus",
    {
      description: "Get the status of an order by its ID",
      inputSchema: { id: z.string() },
    },
    async ({ id }) =&gt; ({
      content: [{ type: "text", text: getOrderStatus(id) }],
    })
  );

  return server;
}

const app = createMcpExpressApp({ host: "0.0.0.0" });

app.post("/mcp", async (req, res) =&gt; {
  const server = createOrderServer();
  const transport = new StreamableHTTPServerTransport({
    sessionIdGenerator: undefined,
  });

  res.on("close", () =&gt; {
    transport.close();
    server.close();
  });

  await server.connect(transport);
  await transport.handleRequest(req, res, req.body);
});

const PORT = process.env.PORT || 3001;
app.listen(PORT, "0.0.0.0", () =&gt; {
  console.log(`Order MCP server running on http://0.0.0.0:${PORT}/mcp`);
});
</code></pre>
<p>This is useful when you want to expose your own application functions through MCP. Typically, the MCP server runs separately and is accessed by MCP clients. Now any MCP client can connect to this server and discover the available tools automatically.</p>
<p>The same idea applies to third-party MCP servers.</p>
<p>For example, if a Slack MCP server is available, we can connect to it instead of writing Slack integration code ourselves.</p>
<p>In that case, our application isn't directly calling Slack APIs. It connects to the Slack MCP server, which exposes Slack-related tools using the MCP standard.</p>
<p>So the difference is:</p>
<ul>
<li><p>For our own features, we can build our own MCP server</p>
</li>
<li><p>For external systems, we can use existing MCP servers when available</p>
</li>
</ul>
<p>Now we can pass MCP servers to the LLM request:</p>
<pre><code class="language-typescript">body: JSON.stringify({
  model: "llama-3.3-70b-versatile",
  messages: [{ role: "user", content: message }],
  tools: [
    {
      type: "mcp",
      server_label: "OrderServer",
      server_url: `http://0.0.0.0:${PORT}/mcp`,
      server_description: "Get the status of an order by its ID",
    },
    {
      type: "mcp",
      server_label: "Slack",
      server_url: "https://mcp.slack.com/mcp",
      server_description: "Send and read Slack messages",
      headers: {
        Authorization: `Bearer ${process.env.SLACK_BOT_TOKEN}`,
      },
    },
  ],
})
</code></pre>
<p>We can also use local MCP servers instead of remote URLs by connecting through transports such as <code>StdioClientTransport</code>. In that case, we connect locally, discover the available tools, and expose them to the LLM.</p>
<p>Now if the user sends:</p>
<pre><code class="language-json">{
  "message": "What is status of order 123"
}
</code></pre>
<p>The LLM decides whether a tool is needed, MCP exposes and executes the tool, and the final response is returned to the user.</p>
<p>The flow becomes:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a1fa5fdc5c3ae375fb38ab2/2db75d86-db9a-477e-b578-92221a490a2a.png" alt="User -> /chat api -> LLM -> MCP Tool -> Tool Result -> Tool Response" style="display:block;margin:0 auto" width="1774" height="887" loading="lazy">

<p>This standardization makes integrations far more reusable: instead of rewriting glue logic for each new connector, teams can register MCP-compliant tools and let the orchestrator and model handle discovery and invocation.</p>
<h2 id="heading-so-what-does-langchain-actually-do"><strong>So What Does LangChain Actually Do?</strong></h2>
<p>I initially thought LangChain was simply another wrapper around LLM APIs, but it is better understood as an orchestration framework for AI workflows. Tools let an LLM perform actions. MCP standardizes how tools are exposed. LangChain helps coordinate models, tools, and application logic to build multi-step workflows.</p>
<p>For example:</p>
<blockquote>
<p>User: Find flights, compare prices, book hotel, send confirmation.</p>
</blockquote>
<p>Now the system may need to:</p>
<ul>
<li><p>Check order status</p>
</li>
<li><p>Decide whether support is needed</p>
</li>
<li><p>Create a support ticket</p>
</li>
<li><p>Generate the final response</p>
</li>
</ul>
<p>Without orchestration, you would manually control each step. LangChain helps manage this flow.</p>
<p>To use LangChain, Install the required packages:</p>
<pre><code class="language-json">npm install express langchain @langchain/groq
</code></pre>
<p>We'll reuse the same tool functions from earlier:</p>
<pre><code class="language-typescript">import express from "express";
import { createAgent } from "langchain";
import { ChatGroq } from "@langchain/groq";

const app = express();
app.use(express.json());

const agent = createAgent({
  model: new ChatGroq({
    model: "llama-3.3-70b-versatile",
    apiKey: GROQ_API_KEY,
  }),
  tools: [
    {
      name: "getOrderStatus",
      description:
        "Get order status",
      execute: ({ id }) =&gt;
        getOrderStatus(id), // we have this function above
    },
    {
      name: "createSupportTicket",
      description:
        "Create support ticket",
      execute: ({ id }) =&gt;
        createSupportTicket(id), //imagine a function that creates a support ticket
    },
  ],
});

app.post(
  "/chat",
  async (req, res) =&gt; {
    const { message } = req.body;

    const response =
      await agent.invoke({
        messages: [
          {
            role: "user",
            content: message,
          },
        ],
      });

    res.json({
      reply:
        response.messages
          ?.at(-1)
          ?.text,
    });
  }
);

app.listen(3000);
</code></pre>
<p>Now the flow becomes:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a1fa5fdc5c3ae375fb38ab2/bd2a266c-39eb-4f3e-9909-ad81360bccb7.png" alt="Horizontal architecture diagram showing User → /chat API → LangChain Agent → OpenAI → Tool → Tool Result → Final Response." style="display:block;margin:0 auto" width="1930" height="815" loading="lazy">

<p>LangChain doesn't replace tools or MCP. It sits above them and coordinates how everything works together.</p>
<h2 id="heading-putting-it-together"><strong>Putting It Together</strong></h2>
<p>A modern AI application usually has multiple layers working together. The LLM handles reasoning and language generation. Tools perform real operations such as reading data, calling APIs, or executing actions. MCP helps standardize how those tools are exposed and accessed. LangChain helps orchestrate the interaction between models, tools, and workflows.</p>
<p>By separating these responsibilities, applications become easier to extend, maintain, and scale.</p>
<p>The goal is more than just generating text. You want to be able to build systems that can reason, retrieve information, take actions, and reliably solve real user problems.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a1fa5fdc5c3ae375fb38ab2/bfc88660-3145-4b89-a626-158c4ec52bcc.png" alt="User ->LLM -> LangChain -> MCP -> Tools -> Systems &amp; Data" style="display:block;margin:0 auto" width="1536" height="1024" loading="lazy">

<h2 id="heading-what-i-built-while-learning-this"><strong>What I Built While Learning This</strong></h2>
<p>After understanding the concepts above, I wanted to reduce some of this setup for my own projects. As I experimented, I noticed most applications recreate the same plumbing over and over: connecting an LLM, wiring up tools, managing execution, and exposing orchestration patterns.</p>
<p>So I built a small open-source toolkit to reduce that setup. The goal was simple: you should be able to focus on business logic instead of wiring AI infrastructure.</p>
<p>Current capabilities:</p>
<ul>
<li><p>LLM integration</p>
</li>
<li><p>Tool registration</p>
</li>
<li><p>Tool execution</p>
</li>
<li><p>Chat orchestration</p>
</li>
<li><p>LangChain support</p>
</li>
<li><p>Extensible architecture</p>
</li>
</ul>
<h3 id="heading-packages">Packages:</h3>
<p>AI Chat Widget: <a href="https://www.npmjs.com/package/ai-chat-toolkit-widget">https://www.npmjs.com/package/ai-chat-toolkit-widget</a></p>
<p>AI Chat Server: <a href="https://www.npmjs.com/package/ai-chat-toolkit-server">https://www.npmjs.com/package/ai-chat-toolkit-server</a></p>
<p>GitHub Repository: <a href="https://github.com/sudheeshshetty/ai-chat-toolkit">https://github.com/sudheeshshetty/ai-chat-toolkit</a></p>
<p>To build a server using the toolkit:</p>
<pre><code class="language-typescript">npm install express ai-chat-toolkit-server
</code></pre>
<p>Create the chat server:</p>
<pre><code class="language-typescript">const aiChat = new AiChatServer({
  path: "/my-chat",
  provider: "groq",
  apiKey: process.env.API_KEY,
  model: process.env.MODEL || "llama-3.3-70b-versatile",
  cors: {
    origin: "http://localhost:5174",
  },
  orchestration: "langchain",
  maxToolRounds: 6,
  systemPrompt:
    "You are a helpful operations assistant for a demo store. Keep answers concise.",
});
</code></pre>
<p>Add your tools:</p>
<pre><code class="language-typescript">aiChat.addTools([
  {
    name: "...",
    description: "...",
    inputSchema: { ... },
    handler: async (input) =&gt; { /* runs in Node */ },
  },
]);
</code></pre>
<p>Attach it to your Express app:</p>
<pre><code class="language-typescript">aiChat.attach(app);
</code></pre>
<p>Now <code>/my-chat</code> is exposed in your Express server and can be used directly.</p>
<p>You can also use <code>ai-chat-toolkit-widget</code> if you want to skip building the chat UI.</p>
<p>Examples are available in the repository, so you can try it out quickly.</p>
<p>A quick glance of one of the examples:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a1fa5fdc5c3ae375fb38ab2/a9079710-be65-472b-881f-350daeeb0f3b.gif" alt="a9079710-be65-472b-881f-350daeeb0f3b" style="display:block;margin:0 auto" width="3456" height="2234" loading="lazy">

<p>If you find it useful, I’d appreciate a star, feedback, or contributions on GitHub as I continue improving the developer experience and exploring new ideas.<br>Thanks for reading — I hope this helped make LLMs, tools, MCP, and LangChain feel a little less magical and a lot more practical.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
