<?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[ Model Context Protocol - 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[ Model Context Protocol - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Mon, 28 Sep 2026 21:39:19 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/model-context-protocol/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Scholarship Research MCP Server with Node.js, Express, and MongoDB ]]>
                </title>
                <description>
                    <![CDATA[ Scholarship hunting is a research job, not a single search box. You filter awards by field, GPA, citizenship, and deadline. You keep a shortlist. You write notes about essays and recommenders. Then yo ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-a-scholarship-research-mcp-server-with-node-js-express-and-mongodb/</link>
                <guid isPermaLink="false">6a9b2db334fe985abf2f91a9</guid>
                
                    <category>
                        <![CDATA[ mcp ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Model Context Protocol ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Express.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ MongoDB ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chinedu Otutu ]]>
                </dc:creator>
                <pubDate>Fri, 04 Sep 2026 20:44:35 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/bc3b99da-15f4-4366-aede-4a2b567724aa.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Scholarship hunting is a research job, not a single search box. You filter awards by field, GPA, citizenship, and deadline. You keep a shortlist. You write notes about essays and recommenders. Then you come back a week later and try to remember why you saved a particular program.</p>
<p>An AI assistant can help with that workflow, but only if it can query a real catalog and persist what you already decided. Chat history isn't a database. A hallucinated deadline is worse than no deadline at all.</p>
<p>The <a href="https://modelcontextprotocol.io/docs/learn/architecture">Model Context Protocol (MCP)</a> is the standard way to give AI apps that kind of access. In this tutorial, you'll build a scholarship research MCP server with Node.js, Express, and MongoDB.</p>
<p>When you finish, Cursor, Claude Desktop, or any other MCP host will be able to search awards, match them to a student profile, bookmark a shortlist, and store research notes. The model stays the reasoning layer while your server owns the data.</p>
<p>You'll build:</p>
<ul>
<li><p>A MongoDB catalog of scholarships, plus saved-list and notes collections</p>
</li>
<li><p>Nine MCP tools for search, matching, deadlines, comparison, and research tracking</p>
</li>
<li><p>Resources so a client can read the catalog without calling a tool</p>
</li>
<li><p>Prompts that turn a student profile into a research plan</p>
</li>
<li><p>An Express app that serves MCP over Streamable HTTP</p>
</li>
</ul>
<p>The sample catalog in this project is a teaching dataset. Amounts, dates, and eligibility rules are simplified. Always confirm details on the official application page before applying.</p>
<h2 id="heading-what-you-need">What You Need</h2>
<p>You should be comfortable with JavaScript and basic Express routing. You don't need prior MCP experience.</p>
<p>Install:</p>
<ul>
<li><p><a href="https://nodejs.org/">Node.js 20</a> or later</p>
</li>
<li><p><a href="https://www.mongodb.com/docs/manual/installation/">MongoDB</a> running locally, or a free <a href="https://www.mongodb.com/atlas">MongoDB Atlas</a> cluster</p>
</li>
<li><p>An MCP client if you want to try the last section. <a href="https://cursor.com/">Cursor</a> and <a href="https://claude.ai/download">Claude Desktop</a> both work.</p>
</li>
</ul>
<p>Docker is enough for MongoDB:</p>
<pre><code class="language-bash">docker run -d --name mongo -p 27017:27017 mongo:7
</code></pre>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-what-you-need">What You Need</a></p>
</li>
<li><p><a href="#heading-what-is-the-model-context-protocol">What Is the Model Context Protocol?</a></p>
</li>
<li><p><a href="#heading-why-a-scholarship-research-server">Why a Scholarship Research Server?</a></p>
</li>
<li><p><a href="#heading-how-the-architecture-fits-together">How the Architecture Fits Together</a></p>
</li>
<li><p><a href="#heading-how-to-set-up-the-project">How to Set Up the Project</a></p>
</li>
<li><p><a href="#heading-how-to-connect-mongodb">How to Connect MongoDB</a></p>
</li>
<li><p><a href="#heading-how-to-model-scholarship-data">How to Model Scholarship Data</a></p>
</li>
<li><p><a href="#heading-how-to-write-the-scholarship-service">How to Write the Scholarship Service</a></p>
</li>
<li><p><a href="#heading-how-to-register-mcp-tools">How to Register MCP Tools</a></p>
</li>
<li><p><a href="#heading-how-to-expose-resources-and-prompts">How to Expose Resources and Prompts</a></p>
</li>
<li><p><a href="#heading-how-to-serve-mcp-over-express">How to Serve MCP Over Express</a></p>
</li>
<li><p><a href="#heading-how-to-seed-the-catalog">How to Seed the Catalog</a></p>
</li>
<li><p><a href="#heading-how-to-test-the-server">How to Test the Server</a></p>
</li>
<li><p><a href="#heading-how-to-connect-cursor-and-claude-desktop">How to Connect Cursor and Claude Desktop</a></p>
</li>
<li><p><a href="#heading-how-a-research-session-runs">How a Research Session Runs</a></p>
</li>
<li><p><a href="#heading-how-the-matching-logic-works">How the Matching Logic Works</a></p>
</li>
<li><p><a href="#heading-what-you-can-build-next">What You Can Build Next</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-what-is-the-model-context-protocol">What Is the Model Context Protocol?</h2>
<p>MCP is an open protocol that lets an AI application talk to external tools and data sources through a shared contract. Anthropic introduced it in 2024, and it's now maintained as an open standard.</p>
<p>Anthropic described MCP as "a USB-C port for AI applications": one connector, many hosts. (Source: <a href="https://www.anthropic.com/news/model-context-protocol">Introducing the Model Context Protocol</a>) Instead of writing one integration for Cursor, another for Claude Desktop, and a third for a custom agent, you implement the protocol once.</p>
<p>The <a href="https://modelcontextprotocol.io/docs/learn/architecture">official architecture overview</a> splits MCP into two layers:</p>
<ul>
<li><p>The <strong>data layer</strong> is JSON-RPC 2.0. Clients and servers exchange requests such as <code>tools/list</code> and <code>tools/call</code>.</p>
</li>
<li><p>The <strong>transport layer</strong> moves those messages. Local servers usually use stdio. Remote or long-running servers use Streamable HTTP.</p>
</li>
</ul>
<p>This tutorial uses Streamable HTTP, because Express is already an HTTP server and you want the catalog available to any client on your machine.</p>
<h3 id="heading-hosts-clients-and-servers">Hosts, Clients, and Servers</h3>
<p>Three roles show up in every MCP setup:</p>
<ul>
<li><p>The <strong>host</strong> is the AI app. Cursor and Claude Desktop are hosts.</p>
</li>
<li><p>The <strong>client</strong> lives inside the host. The host creates one client per connected server.</p>
</li>
<li><p>The <strong>server</strong> is your program. It advertises tools, resources, and prompts, then handles calls.</p>
</li>
</ul>
<p>Your scholarship app is the server. You never talk to the model SDK directly. The host does that.</p>
<h3 id="heading-tools-resources-and-prompts">Tools, Resources, and Prompts</h3>
<p>MCP servers expose three primitives. You'll use all three.</p>
<p><strong>Tools</strong> are actions. The model decides to call them, the way it might call a function in a regular tool-calling API. Search, save, and compare belong here.</p>
<p><strong>Resources</strong> are data the host can read and attach as context. A catalog URI and a per-scholarship URI belong here. The model doesn't have to "take an action" to see them.</p>
<p><strong>Prompts</strong> are named templates. People usually invoke them from a slash command or a menu. A "research plan" prompt belongs here, because it's a workflow you want to start on purpose.</p>
<p>That split matters. If you put everything in tools, the model has to guess when to look things up. Resources and prompts give the host better knobs.</p>
<h2 id="heading-why-a-scholarship-research-server">Why a Scholarship Research Server?</h2>
<p>A weather MCP demo is a single API call. Scholarship research is closer to a real product:</p>
<ul>
<li><p>The catalog must be queryable. Keyword search, GPA filters, and deadline windows all live in the database.</p>
</li>
<li><p>The workflow must persist. A saved shortlist and research notes should survive a new chat.</p>
</li>
<li><p>Eligibility is logic, not prose. A GPA minimum is a number. First-generation-only is a boolean. Put those checks in code so the model can't invent a match.</p>
</li>
<li><p>The output must be inspectable. Students should be able to open the official URL and verify every claim.</p>
</li>
</ul>
<p>MongoDB fits this well. Each scholarship is a document with nested arrays for fields of study, citizenship, and requirements. Saved items and notes are separate collections with references back to the catalog.</p>
<p>You could wrap a public API instead of storing documents. That's a good follow-up. Starting with your own catalog keeps the tutorial self-contained and makes the MCP contract obvious.</p>
<h2 id="heading-how-the-architecture-fits-together">How the Architecture Fits Together</h2>
<p>The finished project looks like this:</p>
<pre><code class="language-text">MCP host (Cursor or Claude Desktop)
        |
        |  Streamable HTTP  POST /mcp
        v
Express app  (createMcpExpressApp)
        |
        |  createMcpHandler factory
        v
McpServer  tools / resources / prompts
        |
        v
Scholarship service
        |
        v
MongoDB  scholarships, savedScholarships, researchnotes
</code></pre>
<p>A few design choices are worth calling out before you write code.</p>
<p>The MCP handler is <strong>stateless</strong>. The SDK runs your server factory once per HTTP request. That's the recommended v2 pattern for Streamable HTTP. Don't keep tool state on the <code>McpServer</code> instance. Keep it in MongoDB.</p>
<p>The database connection is <strong>process-wide</strong>. Connecting on every request would be slow and pointless. You connect once at startup and close over that pool from the tools.</p>
<p>The HTTP surface is small on purpose. <code>/health</code> is for you. <code>/mcp</code> is for the protocol. You don't need a REST API in front of the same data unless you want one later.</p>
<h2 id="heading-how-to-set-up-the-project">How to Set Up the Project</h2>
<p>Create a folder and initialize a Node.js project. ESM is required because the MCP SDK is ESM-first.</p>
<pre><code class="language-bash">mkdir scholarship-research-mcp-server
cd scholarship-research-mcp-server
npm init -y
</code></pre>
<p>Open <code>package.json</code> and set <code>"type": "module"</code>. Then install the SDK, Express, Mongoose, Zod, and dotenv:</p>
<pre><code class="language-bash">npm install @modelcontextprotocol/server @modelcontextprotocol/express @modelcontextprotocol/node express mongoose dotenv zod
</code></pre>
<p>The SDK split into packages in v2:</p>
<ul>
<li><p><code>@modelcontextprotocol/server</code> is the <code>McpServer</code> class and <code>createMcpHandler</code></p>
</li>
<li><p><code>@modelcontextprotocol/express</code> gives you <code>createMcpExpressApp</code>, including DNS rebinding protection</p>
</li>
<li><p><code>@modelcontextprotocol/node</code> adapts the web-standard handler to Node's <code>req</code>/<code>res</code></p>
</li>
</ul>
<p>Create a <code>.env</code> file:</p>
<pre><code class="language-bash">MONGODB_URI=mongodb://127.0.0.1:27017/scholarship_research
PORT=3000
HOST=127.0.0.1
</code></pre>
<p>Add a <code>.gitignore</code> that excludes <code>node_modules</code> and <code>.env</code>.</p>
<p>Your source layout can stay small:</p>
<pre><code class="language-text">src/
  index.js
  config.js
  db.js
  models/
  services/
  mcp/
  utils/
data/
  scholarships.json
scripts/
  seed.js
  smoke-test.js
</code></pre>
<p><code>src/config.js</code> reads environment variables with defaults:</p>
<pre><code class="language-javascript">export const config = {
  mongodbUri: process.env.MONGODB_URI ?? "mongodb://127.0.0.1:27017/scholarship_research",
  port: Number(process.env.PORT ?? 3000),
  host: process.env.HOST ?? "127.0.0.1",
};
</code></pre>
<p>Keep configuration in one file. Tools shouldn't read <code>process.env</code> directly.</p>
<h2 id="heading-how-to-connect-mongodb">How to Connect MongoDB</h2>
<p>Mongoose 9 works cleanly with ESM. A short <code>src/db.js</code> is enough:</p>
<pre><code class="language-javascript">import mongoose from "mongoose";
import { config } from "./config.js";

export async function connectDatabase() {
  mongoose.set("strictQuery", true);
  await mongoose.connect(config.mongodbUri);
  return mongoose.connection;
}
</code></pre>
<p>Call this once in <code>src/index.js</code> before <code>app.listen</code>. If the connection fails, the process should exit. A running Express server with a dead database is harder to debug than a failed startup.</p>
<h2 id="heading-how-to-model-scholarship-data">How to Model Scholarship Data</h2>
<p>You need three collections.</p>
<h3 id="heading-scholarship">Scholarship</h3>
<p>This is the catalog. Store the fields a matching engine actually uses, not a blob of markdown.</p>
<pre><code class="language-javascript">import mongoose from "mongoose";

const scholarshipSchema = new mongoose.Schema(
  {
    title: { type: String, required: true, trim: true },
    provider: { type: String, required: true, trim: true },
    description: { type: String, required: true },
    amountMin: { type: Number, default: 0 },
    amountMax: { type: Number, default: 0 },
    currency: { type: String, default: "USD" },
    deadline: { type: Date, default: null },
    rolling: { type: Boolean, default: false },
    educationLevels: { type: [String], default: ["undergraduate"] },
    fieldsOfStudy: { type: [String], default: ["any"] },
    gpaMinimum: { type: Number, default: null },
    citizenship: { type: [String], default: ["any"] },
    countries: { type: [String], default: ["any"] },
    firstGenerationOnly: { type: Boolean, default: false },
    womenOnly: { type: Boolean, default: false },
    numberOfAwards: { type: Number, default: 1 },
    renewable: { type: Boolean, default: false },
    applicationUrl: { type: String, required: true },
    applicationRequirements: { type: [String], default: [] },
  },
  { timestamps: true },
);

scholarshipSchema.index({
  title: "text",
  provider: "text",
  description: "text",
  fieldsOfStudy: "text",
});
scholarshipSchema.index({ deadline: 1 });
scholarshipSchema.index({ amountMax: -1 });

export const Scholarship = mongoose.model("Scholarship", scholarshipSchema);
</code></pre>
<p>A few field choices are doing real work:</p>
<ul>
<li><p><code>fieldsOfStudy: ["any"]</code> means the award is field-open. The matcher treats <code>any</code> as a wildcard.</p>
</li>
<li><p><code>deadline: null</code> plus <code>rolling: true</code> covers programs that accept applications year-round.</p>
</li>
<li><p><code>applicationUrl</code> is mandatory. Every tool result should point back to a human-verifiable source.</p>
</li>
<li><p><code>gpaMinimum: null</code> means the provider didn't publish a cutoff. That's different from <code>0</code>.</p>
</li>
</ul>
<h3 id="heading-saved-scholarship">Saved Scholarship</h3>
<p>A research list is a join between a person and a catalog row.</p>
<pre><code class="language-javascript">const savedScholarshipSchema = new mongoose.Schema(
  {
    researcherId: { type: String, required: true, default: "default" },
    scholarship: {
      type: mongoose.Schema.Types.ObjectId,
      ref: "Scholarship",
      required: true,
    },
    status: {
      type: String,
      enum: ["saved", "applying", "submitted", "won", "rejected"],
      default: "saved",
    },
  },
  { timestamps: true },
);

savedScholarshipSchema.index({ researcherId: 1, scholarship: 1 }, { unique: true });
</code></pre>
<p>The unique index makes <code>save_scholarship</code> idempotent. Saving the same award twice updates the status instead of creating duplicates.</p>
<p><code>researcherId</code> is a plain string. For a tutorial, that's enough. In production you would take it from an auth token.</p>
<h3 id="heading-research-note">Research Note</h3>
<p>Notes are a separate collection so one scholarship can have many of them.</p>
<pre><code class="language-javascript">const researchNoteSchema = new mongoose.Schema(
  {
    researcherId: { type: String, required: true, default: "default" },
    scholarship: {
      type: mongoose.Schema.Types.ObjectId,
      ref: "Scholarship",
      required: true,
    },
    body: { type: String, required: true, trim: true },
  },
  { timestamps: true },
);
</code></pre>
<p>You now have a catalog, a shortlist, and a notebook. That's the whole product surface the MCP tools will wrap.</p>
<h2 id="heading-how-to-write-the-scholarship-service">How to Write the Scholarship Service</h2>
<p>Keep MongoDB queries out of the MCP layer. Tools should call a service, get plain objects back, and format text. That makes the same functions reusable from a seed script, a smoke test, or a future REST route.</p>
<h3 id="heading-search">Search</h3>
<p>Search is a filter builder. Each optional argument adds a clause. Open or rolling awards stay in the result set. Closed deadlines drop out.</p>
<pre><code class="language-javascript">const OPEN_DEADLINE_FILTER = {
  $or: [{ rolling: true }, { deadline: null }, { deadline: { $gte: new Date() } }],
};

export async function searchScholarships(filters) {
  const query = { ...OPEN_DEADLINE_FILTER };
  const and = [query];

  if (filters.keyword) {
    and.push({
      $or: [
        { title: { $regex: escapeRegex(filters.keyword), $options: "i" } },
        { provider: { $regex: escapeRegex(filters.keyword), $options: "i" } },
        { description: { $regex: escapeRegex(filters.keyword), $options: "i" } },
        { fieldsOfStudy: { $regex: escapeRegex(filters.keyword), $options: "i" } },
      ],
    });
  }

  if (filters.fieldOfStudy) {
    and.push({
      $or: [
        { fieldsOfStudy: { $regex: `^any$`, $options: "i" } },
        { fieldsOfStudy: { $regex: escapeRegex(filters.fieldOfStudy), $options: "i" } },
      ],
    });
  }

  // educationLevel, citizenship, country, minAmount, gpa, flags...

  const results = await Scholarship.find({ $and: and })
    .sort({ deadline: 1, amountMax: -1 })
    .limit(100)
    .lean();

  return rankByFieldMatch(results, filters.fieldOfStudy).slice(0, filters.limit ?? 10);
}
</code></pre>
<p>Two details are easy to skip and worth keeping.</p>
<p>Escape user input before you drop it into <code>$regex</code>. A keyword of <code>(</code> shouldn't become a broken regular expression.</p>
<p>When a student searches for <code>computer science</code>, field-open awards (<code>any</code>) are eligible, but the specific CS scholarships should appear first. Rank in memory after the query. MongoDB already did the eligibility filter. You're only adjusting display order.</p>
<h3 id="heading-match">Match</h3>
<p>Matching is not the same as search. Search is "find documents that look like this." Matching is "here is a student, score every open award."</p>
<p>The service loads open scholarships, then applies hard filters and a score:</p>
<table>
<thead>
<tr>
<th>Signal</th>
<th>Effect</th>
</tr>
</thead>
<tbody><tr>
<td>Education level mismatch</td>
<td>Skip</td>
</tr>
<tr>
<td>GPA below the minimum</td>
<td>Skip</td>
</tr>
<tr>
<td>Citizenship mismatch</td>
<td>Skip</td>
</tr>
<tr>
<td>First-generation-only and student is not</td>
<td>Skip</td>
</tr>
<tr>
<td>Women-only and student is not</td>
<td>Skip</td>
</tr>
<tr>
<td>Education level match</td>
<td>+20</td>
</tr>
<tr>
<td>GPA eligible</td>
<td>+15</td>
</tr>
<tr>
<td>Citizenship eligible</td>
<td>+15</td>
</tr>
<tr>
<td>Field of study match</td>
<td>+30</td>
</tr>
<tr>
<td>Preferred country match</td>
<td>+10</td>
</tr>
<tr>
<td>Amount meets the student's minimum</td>
<td>+10</td>
</tr>
<tr>
<td>First-generation or women-only match</td>
<td>+10</td>
</tr>
</tbody></table>
<p>Hard filters prevent false hope. Soft scores rank the rest. Each result also returns a <code>reasons</code> array, so the model can explain the match instead of inventing one.</p>
<p>That last point is the whole reason to put matching on the server. If you only return raw documents, the model will sometimes "helpfully" include an award the student can't apply for. Returning <code>score</code> and <code>reasons</code> keeps the explanation grounded in code.</p>
<h3 id="heading-save-notes-deadlines-compare">Save, Notes, Deadlines, Compare</h3>
<p>The remaining functions are thin:</p>
<ul>
<li><p><code>saveScholarship</code> upserts by <code>(researcherId, scholarshipId)</code></p>
</li>
<li><p><code>addResearchNote</code> inserts a note after confirming the scholarship exists</p>
</li>
<li><p><code>getUpcomingDeadlines</code> queries <code>deadline</code> between now and <code>now + N days</code></p>
</li>
<li><p><code>compareScholarships</code> loads two or three documents and returns the same fields for each</p>
</li>
</ul>
<p>Validate MongoDB ids with <code>mongoose.Types.ObjectId.isValid</code> before you query. An LLM will occasionally pass a title where you asked for an id. Fail clearly. Don't throw a CastError into the MCP transport.</p>
<h2 id="heading-how-to-register-mcp-tools">How to Register MCP Tools</h2>
<p>Create <code>src/mcp/server.js</code> as a factory. The HTTP handler will call it on every request.</p>
<pre><code class="language-javascript">import { McpServer } from "@modelcontextprotocol/server";
import { registerPrompts } from "./prompts.js";
import { registerResources } from "./resources.js";
import { registerTools } from "./tools.js";

export function createScholarshipServer() {
  const server = new McpServer({
    name: "scholarship-research",
    version: "1.0.0",
  });

  registerTools(server);
  registerResources(server);
  registerPrompts(server);

  return server;
}
</code></pre>
<p>Keep this factory cheap. No database connections, no file reads, and no caches that belong at module scope. The <a href="https://ts.sdk.modelcontextprotocol.io/v2/serving/http.html">HTTP serving guide</a> is explicit about this: create connection pools once at startup, and close over them.</p>
<h3 id="heading-one-tool-fully">One Tool, Fully</h3>
<p><code>registerTool</code> takes a name, a config object, and a handler. The <code>inputSchema</code> is a Zod object. The SDK turns that schema into JSON Schema for <code>tools/list</code>, validates arguments before your handler runs, and infers types if you're on TypeScript.</p>
<pre><code class="language-javascript">import * as z from "zod/v4";

server.registerTool(
  "search_scholarships",
  {
    title: "Search scholarships",
    description:
      "Search the scholarship catalog by keyword, field of study, education level, citizenship, country, GPA, and award amount.",
    inputSchema: z.object({
      keyword: z.string().min(1).optional().describe("Free-text search across title, provider, description, and fields"),
      fieldOfStudy: z.string().optional().describe("For example computer science, public health, or engineering"),
      educationLevel: z.enum(["undergraduate", "graduate", "doctoral"]).optional(),
      citizenship: z.string().optional(),
      country: z.string().optional(),
      minAmount: z.number().nonnegative().optional(),
      gpa: z.number().min(0).max(4).optional(),
      firstGeneration: z.boolean().optional(),
      womenOnly: z.boolean().optional(),
      limit: z.number().int().min(1).max(25).optional(),
    }),
    annotations: { readOnlyHint: true, openWorldHint: false },
  },
  async (args) =&gt; {
    const results = await searchScholarships(args);
    return toolText(formatScholarshipList(results));
  },
);
</code></pre>
<p>Write descriptions as if the model is the only docs the tool will ever get. <code>.describe()</code> on a Zod field survives conversion to JSON Schema. That's how the host tells the model what <code>fieldOfStudy</code> means.</p>
<p><code>title</code> is the human label. <code>description</code> is the model-facing contract. They're not the same string.</p>
<h3 id="heading-annotations">Annotations</h3>
<p>Annotations don't change how the SDK runs the tool. Hosts use them to decide how cautious to be.</p>
<ul>
<li><p><code>readOnlyHint: true</code> for search, get, match, list, compare, and deadlines</p>
</li>
<li><p><code>readOnlyHint: false</code> for save and add-note</p>
</li>
<li><p><code>idempotentHint: true</code> on save, because of the unique index</p>
</li>
<li><p><code>openWorldHint: false</code> because this server talks to your database, not the open web</p>
</li>
</ul>
<p>A host can auto-approve a read-only search and ask the user before a write. That's worth five extra keys in the config.</p>
<h3 id="heading-return-shape">Return Shape</h3>
<p>Every tool returns MCP content blocks:</p>
<pre><code class="language-javascript">export function toolText(text, isError = false) {
  return {
    content: [{ type: "text", text }],
    isError,
  };
}
</code></pre>
<p>Return <code>isError: true</code> for domain failures such as "scholarship not found." Throw only for unexpected failures. The spec treats those differently. A validation error from Zod never reaches your handler. The SDK already converts it into an <code>isError</code> result.</p>
<p>Format lists for a person who is skimming a chat transcript. Include the MongoDB id on every row. Later tools need that id, and the model can't invent a valid ObjectId.</p>
<h3 id="heading-the-full-tool-set">The Full Tool Set</h3>
<p>The server registers nine tools:</p>
<table>
<thead>
<tr>
<th>Tool</th>
<th>What it does</th>
</tr>
</thead>
<tbody><tr>
<td><code>search_scholarships</code></td>
<td>Filter the catalog</td>
</tr>
<tr>
<td><code>get_scholarship</code></td>
<td>Return one full record</td>
</tr>
<tr>
<td><code>match_scholarships</code></td>
<td>Score awards against a student profile</td>
</tr>
<tr>
<td><code>save_scholarship</code></td>
<td>Bookmark an award</td>
</tr>
<tr>
<td><code>list_saved_scholarships</code></td>
<td>Show the shortlist</td>
</tr>
<tr>
<td><code>add_research_note</code></td>
<td>Attach a note</td>
</tr>
<tr>
<td><code>list_research_notes</code></td>
<td>Read notes back</td>
</tr>
<tr>
<td><code>get_upcoming_deadlines</code></td>
<td>Deadline window</td>
</tr>
<tr>
<td><code>compare_scholarships</code></td>
<td>Side-by-side of two or three ids</td>
</tr>
</tbody></table>
<p>That's enough for a research loop: find, inspect, match, save, annotate, and compare.</p>
<p>Resist the urge to add a <code>delete_everything</code> tool. Destructive tools need extra confirmation and aren't part of this workflow.</p>
<h2 id="heading-how-to-expose-resources-and-prompts">How to Expose Resources and Prompts</h2>
<p>Tools aren't the only way a host gets context.</p>
<h3 id="heading-resources">Resources</h3>
<p>A static resource is a fixed URI. The catalog fits that:</p>
<pre><code class="language-javascript">server.registerResource(
  "scholarship-catalog",
  "scholarship://catalog",
  {
    title: "Scholarship catalog",
    description: "Open scholarships currently stored in MongoDB",
    mimeType: "application/json",
  },
  async (uri) =&gt; {
    const catalog = await listCatalog();
    return {
      contents: [
        {
          uri: uri.href,
          mimeType: "application/json",
          text: JSON.stringify(catalog, null, 2),
        },
      ],
    };
  },
);
</code></pre>
<p>A resource template covers a family of URIs. Use <code>scholarship://item/{id}</code> rather than <code>scholarship://{id}</code>. If the pattern is <code>scholarship://{id}</code>, the URI <code>scholarship://catalog</code> becomes ambiguous.</p>
<pre><code class="language-javascript">import { ResourceTemplate } from "@modelcontextprotocol/server";

server.registerResource(
  "scholarship-record",
  new ResourceTemplate("scholarship://item/{id}", {
    list: async () =&gt; {
      const catalog = await listCatalog(20);
      return {
        resources: catalog.map((scholarship) =&gt; ({
          uri: `scholarship://item/${scholarship._id}`,
          name: scholarship.title,
          mimeType: "text/plain",
        })),
      };
    },
  }),
  {
    title: "Scholarship record",
    description: "Full details for one scholarship",
    mimeType: "text/plain",
  },
  async (uri, { id }) =&gt; {
    const scholarship = await getScholarshipById(id);
    return {
      contents: [
        {
          uri: uri.href,
          mimeType: "text/plain",
          text: scholarship
            ? formatScholarship(scholarship)
            : `No scholarship found with id ${id}.`,
        },
      ],
    };
  },
);
</code></pre>
<p><code>list</code> is required on a template. Pass <code>undefined</code> if you can't enumerate instances. Here you can, so the host can show a picker.</p>
<h3 id="heading-prompts">Prompts</h3>
<p>Prompts are workflows you want a person to start. The research plan prompt doesn't query MongoDB itself. It tells the model to use the tools, then structure the answer.</p>
<pre><code class="language-javascript">server.registerPrompt(
  "research-plan",
  {
    title: "Scholarship research plan",
    description: "Build a week-by-week research and application plan from a student profile.",
    argsSchema: z.object({
      fieldOfStudy: z.string(),
      educationLevel: z.string(),
      citizenship: z.string(),
      gpa: z.string(),
      weeks: z.string().optional(),
    }),
  },
  ({ fieldOfStudy, educationLevel, citizenship, gpa, weeks }) =&gt; ({
    messages: [
      {
        role: "user",
        content: {
          type: "text",
          text: `Create a ${weeks || "6"}-week scholarship research plan for this student.

Field of study: ${fieldOfStudy}
Education level: ${educationLevel}
Citizenship: ${citizenship}
GPA: ${gpa}

Use the scholarship research tools to find real awards first. Then produce a shortlist, a week-by-week plan, and risks. Name actual scholarships and dates from the tool results.`,
        },
      },
    ],
  }),
);
</code></pre>
<p>Notice the instruction "use the scholarship research tools." A prompt isn't a substitute for tools. It's a script that makes tool use more likely and the output shape more consistent.</p>
<p>A second prompt, <code>application-checklist</code>, takes a scholarship id and asks for a document list and a backward calendar. That's the kind of repetitive work MCP prompts are good at.</p>
<p>Prompt arguments are strings in many hosts, even when the value is a number. Typing <code>gpa</code> as a string avoids a frustrating <code>expected number, received string</code> error from a slash-command form.</p>
<h2 id="heading-how-to-serve-mcp-over-express">How to Serve MCP Over Express</h2>
<p>This is the part that used to be a page of session-handling code. In SDK v2 it's a factory plus one route.</p>
<pre><code class="language-javascript">import "dotenv/config";
import { createMcpExpressApp } from "@modelcontextprotocol/express";
import { toNodeHandler } from "@modelcontextprotocol/node";
import { createMcpHandler } from "@modelcontextprotocol/server";
import { config } from "./config.js";
import { connectDatabase } from "./db.js";
import { createScholarshipServer } from "./mcp/server.js";

const mcpHandler = createMcpHandler(() =&gt; createScholarshipServer());
const nodeHandler = toNodeHandler(mcpHandler);

const app = createMcpExpressApp({
  host: config.host,
  allowedHosts: ["127.0.0.1", "localhost"],
});

app.get("/health", (_req, res) =&gt; {
  res.json({
    status: "ok",
    service: "scholarship-research-mcp",
    transport: "streamable-http",
  });
});

app.all("/mcp", (req, res) =&gt; {
  void nodeHandler(req, res, req.body);
});

async function start() {
  await connectDatabase();
  app.listen(config.port, config.host, () =&gt; {
    console.log(`Scholarship research MCP server listening on http://${config.host}:${config.port}/mcp`);
  });
}

start();
</code></pre>
<p>Walk through what each helper is doing.</p>
<p><code>createMcpHandler</code> takes a function that returns a fresh <code>McpServer</code>. It exposes a web-standard <code>fetch</code>. That's the same handler you would export from a Cloudflare Worker.</p>
<p><code>toNodeHandler</code> adapts that <code>fetch</code> to Express <code>(req, res)</code>. You pass <code>req.body</code> as the third argument because <code>createMcpExpressApp</code> already ran <code>express.json()</code>. If you omit the body, the adapter tries to read a stream Express already consumed.</p>
<p><code>createMcpExpressApp</code> is <code>express()</code> with two extras: JSON parsing, and Host/Origin checks. Those checks exist because of DNS rebinding. A malicious page can point its own domain at <code>127.0.0.1</code> and, without a Host check, your browser would treat the local MCP server as same-origin. The default bind is <code>127.0.0.1</code> for that reason. The <a href="https://ts.sdk.modelcontextprotocol.io/v2/serving/express.html">Express serving guide</a> covers this in more detail.</p>
<p><code>app.all("/mcp", ...)</code> is intentional. Streamable HTTP uses POST for JSON-RPC, and GET for SSE streams. Registering only POST will break some clients.</p>
<p>Shut the handler down on <code>SIGINT</code>:</p>
<pre><code class="language-javascript">process.on("SIGINT", async () =&gt; {
  await mcpHandler.close();
  process.exit(0);
});
</code></pre>
<p><code>close()</code> waits for in-flight requests. Then you can exit.</p>
<p>Add npm scripts:</p>
<pre><code class="language-json">{
  "scripts": {
    "start": "node src/index.js",
    "dev": "node --watch src/index.js",
    "seed": "node scripts/seed.js",
    "smoke": "node scripts/smoke-test.js"
  }
}
</code></pre>
<p><code>node --watch</code> is enough for local development. You don't need nodemon for this project.</p>
<h2 id="heading-how-to-seed-the-catalog">How to Seed the Catalog</h2>
<p>MCP tools against an empty database will work and return "no scholarships matched." That's correct, and also a bad first impression.</p>
<p>Put 20 to 30 realistic records in <code>data/scholarships.json</code>. Mix:</p>
<ul>
<li><p>Undergraduate and graduate awards</p>
</li>
<li><p>STEM and field-open awards</p>
</li>
<li><p>Country-specific programs and global ones</p>
</li>
<li><p>Rolling deadlines and hard dates</p>
</li>
<li><p>First-generation and women-only flags</p>
</li>
</ul>
<p>A seed script should replace the catalog, not append:</p>
<pre><code class="language-javascript">import "dotenv/config";
import { readFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { connectDatabase } from "../src/db.js";
import { Scholarship } from "../src/models/index.js";

const __dirname = path.dirname(fileURLToPath(import.meta.url));

async function seed() {
  await connectDatabase();
  const raw = await readFile(path.join(__dirname, "..", "data", "scholarships.json"), "utf8");
  await Scholarship.deleteMany({});
  const inserted = await Scholarship.insertMany(JSON.parse(raw));
  console.log(`Seeded ${inserted.length} scholarships.`);
  process.exit(0);
}

seed();
</code></pre>
<p>Run it:</p>
<pre><code class="language-bash">npm run seed
</code></pre>
<p>Treat the JSON as sample data. Names of well-known programs help the tutorial feel real. They also create a duty to say, clearly, that students must verify every number and date on the official site. The <code>applicationUrl</code> field exists so that reminder has somewhere to point.</p>
<p>If you later replace the JSON with a live source, keep the same schema. The MCP tools shouldn't care where the documents came from.</p>
<h2 id="heading-how-to-test-the-server">How to Test the Server</h2>
<p>Start MongoDB, seed, then start the process:</p>
<pre><code class="language-bash">npm run seed
npm start
</code></pre>
<p>You should see:</p>
<pre><code class="language-text">Scholarship research MCP server listening on http://127.0.0.1:3000/mcp
</code></pre>
<h3 id="heading-health-check">Health Check</h3>
<pre><code class="language-bash">curl -s http://127.0.0.1:3000/health
</code></pre>
<p>A JSON <code>status: ok</code> means Express is up. It doesn't mean MCP is wired correctly. For that, send a JSON-RPC request.</p>
<h3 id="heading-list-tools">List Tools</h3>
<pre><code class="language-bash">curl -s -X POST http://127.0.0.1:3000/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
</code></pre>
<p>The response is an SSE event whose <code>data:</code> line contains the JSON-RPC result. You should see all nine tools, each with a JSON Schema derived from Zod.</p>
<p>The <code>Accept</code> header matters. MCP Streamable HTTP can return JSON or an event stream. Asking for both is the compatible choice.</p>
<h3 id="heading-call-a-tool">Call a Tool</h3>
<pre><code class="language-bash">curl -s -X POST http://127.0.0.1:3000/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{
    "jsonrpc":"2.0",
    "id":2,
    "method":"tools/call",
    "params": {
      "name": "search_scholarships",
      "arguments": {
        "fieldOfStudy": "computer science",
        "limit": 3
      }
    }
  }'
</code></pre>
<p>You should get a numbered list with ids, deadlines, and GPA minimums. Copy one id and pass it to <code>get_scholarship</code>.</p>
<p>A small Node smoke test is nicer than raw curl once you're calling several methods. Parse the <code>data:</code> line, then print <code>result.content[0].text</code>. The repo includes <code>scripts/smoke-test.js</code> for that.</p>
<h3 id="heading-inspector">Inspector</h3>
<p>The <a href="https://modelcontextprotocol.io/docs/tools/inspector">MCP Inspector</a> is the official GUI for servers. Point it at <code>http://127.0.0.1:3000/mcp</code> and you can list tools, fill in arguments, and read resources without a host app in the way.</p>
<p>Use Inspector when a host "can't see" your server. If Inspector works and the host doesn't, the bug is in the host config. If Inspector fails, the bug is in your process.</p>
<h2 id="heading-how-to-connect-cursor-and-claude-desktop">How to Connect Cursor and Claude Desktop</h2>
<p>Keep <code>npm start</code> running. MCP over HTTP is a live server, not a one-shot CLI.</p>
<h3 id="heading-cursor">Cursor</h3>
<p>Add a server entry in Cursor's MCP settings. A Streamable HTTP server looks like this:</p>
<pre><code class="language-json">{
  "mcpServers": {
    "scholarship-research": {
      "url": "http://127.0.0.1:3000/mcp"
    }
  }
}
</code></pre>
<p>Restart the MCP session if Cursor had a previous failed connection cached. Then ask:</p>
<blockquote>
<p>I am a first-generation undergraduate studying computer science in the United States, GPA 3.6. Use the scholarship research tools to build a shortlist and a six-week plan.</p>
</blockquote>
<p>You should see the host call <code>match_scholarships</code> or <code>search_scholarships</code>, then <code>get_scholarship</code> for the interesting rows, then maybe <code>save_scholarship</code>. If it never calls a tool, the server isn't actually connected. Check the MCP logs in Cursor before you change code.</p>
<p>You can also invoke the <code>research-plan</code> prompt from the host's prompt menu if it surfaces prompts.</p>
<h3 id="heading-claude-desktop">Claude Desktop</h3>
<p>Claude Desktop's config file lives at:</p>
<ul>
<li><p>macOS: <code>~/Library/Application Support/Claude/claude_desktop_config.json</code></p>
</li>
<li><p>Windows: <code>%APPDATA%\Claude\claude_desktop_config.json</code></p>
</li>
</ul>
<p>Claude Desktop prefers stdio. Bridge your HTTP server with <a href="https://www.npmjs.com/package/mcp-remote"><code>mcp-remote</code></a>:</p>
<pre><code class="language-json">{
  "mcpServers": {
    "scholarship-research": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "http://127.0.0.1:3000/mcp"]
    }
  }
}
</code></pre>
<p>Restart Claude Desktop after you save the file. The scholarship tools should appear in the tool list.</p>
<p>Don't put MongoDB credentials in the Claude config. The Node process already loaded <code>.env</code>. The host only needs the URL.</p>
<h2 id="heading-how-a-research-session-runs">How a Research Session Runs</h2>
<p>Here's a realistic session against the seed catalog. The student is a first-generation undergraduate in computer science, a US citizen, GPA 3.6.</p>
<p>The host calls <code>match_scholarships</code> with that profile. The server returns ranked rows. A women-in-technology award and a first-generation program both score well, for different reasons. A graduate-only award never appears.</p>
<p>The host then calls <code>get_scholarship</code> on the top two ids. Each result includes the official URL, the requirement list, and the deadline. That's the moment to tell the student to open the URL. The model shouldn't be the last word on eligibility.</p>
<p>If an award is worth pursuing, the host calls <code>save_scholarship</code> with a <code>researcherId</code> such as <code>ada</code> and status <code>saved</code>. Later it can set <code>applying</code>. <code>list_saved_scholarships</code> is how a new chat picks up the shortlist. Persistence is the whole point of MongoDB here. Without it, every conversation starts from zero.</p>
<p><code>add_research_note</code> is for the messy human details: "Ask Dr. Chen for a recommendation by October 1." <code>get_upcoming_deadlines</code> is the weekly sweep. <code>compare_scholarships</code> is for the moment the student has two finalists and needs amount, GPA, and requirements in one view.</p>
<p>The <code>research-plan</code> prompt packages that loop. It injects the profile into a user message that tells the model to call tools first and then produce a week-by-week plan. If you invoke the prompt without a connected server, you get a generic essay. If you invoke it with this server running, you get named awards and real dates.</p>
<p>That's the product: a catalog the model can query, a shortlist it can't forget, and prompts that make the workflow repeatable.</p>
<h2 id="heading-how-the-matching-logic-works">How the Matching Logic Works</h2>
<p>It's worth slowing down on matching, because this is the part people are tempted to hand to the model.</p>
<p>Suppose the student is:</p>
<ul>
<li><p>GPA 3.6</p>
</li>
<li><p>Computer science</p>
</li>
<li><p>Undergraduate</p>
</li>
<li><p>United States citizen</p>
</li>
<li><p>First-generation</p>
</li>
<li><p>A woman</p>
</li>
</ul>
<p>The matcher walks every open award.</p>
<p>A women-in-technology scholarship with a 3.3 GPA minimum, CS as a listed field, and US eligibility scores high: education, GPA, citizenship, women-only flag, and field all hit. A first-generation program that is field-open also scores high, because <code>any</code> counts as a field match. A graduate-only award is skipped, even if the title looks relevant. A 3.8 GPA cutoff is skipped, even if everything else fits.</p>
<p>The tool then returns ranked rows with reasons:</p>
<pre><code class="language-text">1. Palantir Women in Technology Scholarship — score 90
   Why: education level matches; GPA 3.6 meets the 3.3 minimum; citizenship is eligible; women-only award matches; field of study matches
</code></pre>
<p>The model can still write a warm paragraph around that. It shouldn't be the component that decided eligibility.</p>
<p>If you extend this later, keep the same split. New eligibility rules belong in the service. New prose belongs in the prompt.</p>
<h2 id="heading-what-you-can-build-next">What You Can Build Next</h2>
<p>The server you have is complete enough to use. It's also a base for a more serious research tool.</p>
<p>First, you could replace the seed file with a live source. Official feeds such as <a href="https://www.grants.gov/">Grants.gov</a> and college-maintained lists are safer than scraping commercial aggregators. Keep your schema. Write an importer that upserts by a stable external id.</p>
<p>You could also add authentication. <code>createMcpExpressApp</code> works with <code>requireBearerAuth</code>. Map <code>researcherId</code> from the verified token instead of a tool argument. The <a href="https://www.npmjs.com/package/@modelcontextprotocol/express">Express adapter</a> documents that middleware.</p>
<p>Try adding full-text search. The schema already has a text index. For a large catalog, Atlas Search or a dedicated search engine will beat a pile of regex filters.</p>
<p>You can track documents, not just notes. A <code>documents</code> collection for transcripts, recommendation status, and essay drafts turns the shortlist into an application tracker.</p>
<p>You could also write tests around the matcher. Eligibility code is where silent bugs hurt people. A table of profiles and expected include/exclude lists will pay for itself.</p>
<p>And you could deploy it. Bind to <code>127.0.0.1</code> on your laptop. If you put this on a network, set <code>allowedHosts</code>, terminate TLS, and require a bearer token. An open MCP server is an open database with a helpful English interface.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this guide, you built a scholarship research MCP server that's more than a toy tool list.</p>
<p>You stored awards, shortlists, and notes in MongoDB. You exposed search, matching, and research-tracking as MCP tools, with Zod schemas the host can advertise to a model. You added resources for the catalog and prompts for repeatable workflows. You served the whole thing over Streamable HTTP with Express, including the Host header checks the SDK enables for localhost.</p>
<p>The pattern transfers. Any research workflow with a catalog and a personal working set can use the same three layers: a service that owns the rules, an <code>McpServer</code> factory that registers primitives, and a small Express app that speaks the protocol.</p>
<p>If you take one idea from this tutorial, take this one: let the model write the plan, and let your server decide what's true.</p>
<p>The sample catalog is for learning. Confirm every scholarship on its official application page before you apply, and before you tell someone else to apply.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How Does an MCP Work Under the Hood? MCP Workflow Explained ]]>
                </title>
                <description>
                    <![CDATA[ We’ve all faced that awkward limitation with AI: it can write code or explain complex topics in seconds, but the moment you ask it to check a local file or run a quick database query, it hits a wall. It’s like having a genius assistant who is locked ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-does-an-mcp-work-under-the-hood/</link>
                <guid isPermaLink="false">6941a65f3076ac3edd6fdaf0</guid>
                
                    <category>
                        <![CDATA[ mcp ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Model Context Protocol ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ajay Patel ]]>
                </dc:creator>
                <pubDate>Tue, 16 Dec 2025 18:35:11 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1765909617721/fa533504-3dab-48c3-9b92-0b89a81af025.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>We’ve all faced that awkward limitation with AI: it can write code or explain complex topics in seconds, but the moment you ask it to check a local file or run a quick database query, it hits a wall. It’s like having a genius assistant who is locked in an empty room—smart, but completely cut off from your actual work. This is where the Model Context Protocol (MCP) changes the game. In this article, we’ll explore MCP in depth.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-mcp-server-a-z-of-model-context-protocol">MCP Server: A-Z of Model Context Protocol</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-mcp-model-context-protocol">What is MCP (Model Context Protocol)?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-architecture-of-mcp">Architecture of MCP</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-does-mcp-work">How Does MCP Work?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-mcp-vs-rag">MCP vs RAG</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-mcp-vs-a2a">MCP vs A2A</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-resources">Resources</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-mcp-server-a-z-of-model-context-protocol">MCP Server: A-Z of Model Context Protocol</h2>
<p>LLMs possess impressive knowledge and reasoning skills, which allow them to perform many complex tasks. But the problem is that their knowledge is limited to their initial training data. It means they can’t access your calendar, run SQL queries, or send an email.</p>
<p>It was clear that, to give the LLMs real-world knowledge, we have to provide some integrations that enable them to access real-time knowledge or perform some actions in the real world. This leads to the classic MxN problems, where developers have to build and maintain custom integrations for every combination of M models and N tools.</p>
<p>The image below properly demonstrates the MxN Problem:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764841852514/f4279e47-416d-4559-8908-16199eab3820.jpeg" alt="mxn problem - connecting every model to every tool individually" class="image--center mx-auto" width="2816" height="1536" loading="lazy"></p>
<p>Function calling (also known as tool calling) provides a powerful and flexible way for OpenAI models to interface with external systems and access data outside their training data. However, this feature is currently exclusive to OpenAI models, creating vendor lock-in.</p>
<p>That’s where MCP steps in. MCP is a write once, use anywhere approach to the problem. An app developer can write a single MCP server for any AI system to use and expose a set of tools and data. Similarly, an AI system can implement the protocol and connect to any MCP server that exists today or in the future.</p>
<h2 id="heading-what-is-mcp-model-context-protocol">What is MCP (Model Context Protocol)?</h2>
<p>MCP is an open-source standard, developed by Anthropic, for connecting AI applications to external systems.</p>
<p>By using an MCP, AI applications like Claude or ChatGPT can connect to data sources like local files and databases, tools like search engines and calculators, and workflows like specialized prompts—enabling them to access key information and perform tasks.</p>
<p>Think of an MCP like a USB-C port for AI applications. Just as USB-C provides a standardized way to connect electronic devices, an MCP provides a standardized way to connect AI applications to external systems.</p>
<p>The image below will help you to better understand the MCP Server:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764763126029/45a8d0a7-a4f4-47e4-afb9-268930bd1c47.png" alt="structure of model context protocol" class="image--center mx-auto" width="1024" height="559" loading="lazy"></p>
<h2 id="heading-architecture-of-mcp">Architecture of MCP</h2>
<p>The Model Context Protocol has a clear structure with components that work together to help LLMs and outside systems interact easily. An MCP follows a simple client-server architecture, which can be broken down into three simple key components:</p>
<h3 id="heading-mcp-host"><strong>MCP Host</strong></h3>
<p>The host is the user-facing AI application, the environment where the AI model lives and interacts with the user. Hosts manage the discovery, permissions, and communication between clients and servers. This ca be a chat application like OpenAI’s ChatGPT interface or Anthropic’s Claude desktop app, or an AI-enhanced IDE like Cursor &amp; Windsurf.</p>
<h3 id="heading-mcp-client"><strong>MCP Client</strong></h3>
<p>The MCP client is a component within the host that handles the low-level communication with the MCP server. MCP clients are instantiated by host applications to communicate with particular MCP servers. Each client handles one direct communication with one server.</p>
<p>Here, the difference is important: the host is the application users interact with, while clients are the components that enable server connections.</p>
<h3 id="heading-mcp-server"><strong>MCP Server</strong></h3>
<p>The MCP server is the external program or service that exposes the capabilities (tools, data, and so on) to the application. An MCP server can be seen as a wrapper around some functionality, which exposes a set of tools or resources in a standardized way so that any MCP client can invoke them.</p>
<p>Servers can run locally on the same machine as the host, or remotely on some cloud service, since an MCP is designed to support both scenarios seamlessly</p>
<p>The image below will help you to better understand the concept:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764841995822/fdec43d4-705e-4385-8eac-b436ec22c386.jpeg" alt="how does mcp work" class="image--center mx-auto" width="2976" height="1440" loading="lazy"></p>
<p>An MCP server can expose one or more capabilities to the client. Capabilities are essentially the features or functions that the server makes available.</p>
<p>The MCP server provides the following capabilities:</p>
<ul>
<li><p><strong>Tools:</strong> Tools are the functions that do something on behalf of the AI model. An AI can use this tool whenever required. Tools are triggered by the AI model’s choice, which means the LLM (via the host) decides to call a tool when it determines it needs to perform a specific task. For example: send_email -&gt; send the email to the user</p>
</li>
<li><p><strong>Resources:</strong> Resources provide read-only data to the AI model. A resource can be a database record or a knowledge base that the AI can query to get information, but can’t modify.</p>
</li>
<li><p><strong>Prompts:</strong> Prompts are the predefined templates or workflows that the server can provide.</p>
</li>
</ul>
<h3 id="heading-transport-layer"><strong>Transport Layer</strong></h3>
<p>The transport layer uses JSON-RPC 2.0 messages to communicate between the client and server. For this, we have mainly two transport methods:</p>
<ul>
<li><p><strong>Standard Input/Output (stdio):</strong> Ideal for local environments, providing fast and synchronous message transmission.</p>
</li>
<li><p><strong>Server-Sent Events (SSE):</strong> Best suited for remote resources, enabling efficient, real-time, one-way data streaming from the server to the client.</p>
</li>
</ul>
<h2 id="heading-how-does-mcp-work">How Does MCP Work?</h2>
<p>An MCP gives an AI assistant the ability to securely use external tools, databases, and services. Imagine you ask Claude:</p>
<blockquote>
<p>“Find the latest sales report in our database and email it to my manager.”</p>
</blockquote>
<h3 id="heading-step-1-tool-discovery"><strong>Step #1 - Tool Discovery</strong></h3>
<p>When we launch any MCP client (Claude Desktop), it connects to your configured MCP servers and asks: “What can I do with available tools?”</p>
<p>Each server responds with its available tools:</p>
<p><code>database_query</code> ,<code>email_sender</code> ,<code>file_browser</code></p>
<p>Now, Claude knows about the tools it has.</p>
<h3 id="heading-step-2-understanding-your-requirement"><strong>Step #2 - Understanding Your Requirement</strong></h3>
<p>Claude reads your query and realizes:</p>
<ul>
<li><p>It needs to retrieve information it doesn’t have (in this case, it has to find the sales data <code>database_query</code>)</p>
</li>
<li><p>It needs to take an external action (send email <code>email_sender</code> )</p>
</li>
</ul>
<p>So Claude plans a 2-step tool sequence.</p>
<h3 id="heading-step-3-ask-for-permission"><strong>Step #3 - Ask for Permission</strong></h3>
<p>Before any external action happens, Claude Desktop prompts you: “Claude wants to query your sales database. Allow?”</p>
<p>Nothing proceeds without your approval. This is core to the MCP’s security model.</p>
<h3 id="heading-step-4-querying-the-database"><strong>Step #4 - Querying the Database</strong></h3>
<p>Once you grant the permission, Claude sends a structured MCP tool call to the <code>database_query</code> server.</p>
<p>Next, the server will run a secure database lookup and return the latest sales report data. This doesn’t give Claude direct access to the database.</p>
<h3 id="heading-step-5-sending-the-email"><strong>Step #5 - Sending the Email</strong></h3>
<p>Once Claude has the data, Claude triggers a second permission prompt: “Claude wants to send an email on your behalf. Approve?”</p>
<p>Once approved, MCP sends the information to the <code>email_sender</code> server, and Claude will format the email &amp; deliver it to your manager</p>
<h3 id="heading-step-6-natural-answer"><strong>Step #6 - Natural Answer</strong></h3>
<p>Claude wraps everything up nicely and sends a response to you, “Done! I found the latest sales report and emailed it to your manager.”</p>
<p>The entire process typically happens in seconds. From your perspective, Claude simply "knows" how to access your database and send emails, but in reality, the MCP has orchestrated a secure, standardized exchange between multiple systems.</p>
<p>The beauty of MCP is that it transforms AI assistants from isolated conversational tools into genuine productivity partners that can interact with your entire digital ecosystem, safely and with your explicit permission every step of the way.</p>
<h2 id="heading-mcp-vs-rag">MCP vs RAG</h2>
<p>Fundamentally, MCP and RAG are built for serving different purposes.</p>
<p>RAG is a technique that is used to supply the relevant knowledge that we have stored in a vector database. In RAG, the user’s query is converted to a vector embedding, which searches through embeddings in the vector database and finds the relevant context based on similarity. This relevant context is then provided to the LLM. It is great for answering questions from large documents like company wikis, knowledge bases, or research papers.</p>
<p>An MCP enables AI models to perform real-world actions with the help of tools. It lets the AI connect to tools and services like databases, APIs, Gmail, calendar, and so on.</p>
<h2 id="heading-mcp-vs-a2a">MCP vs A2A</h2>
<p>The Model Context Protocol (MCP) and the Agent-to-Agent (A2A) protocol are complementary open standards in AI architecture that serve different purposes in how AI agents connect with external systems.</p>
<ul>
<li><p>MCP standardizes how a single AI agent connects to tools, data, and external systems (agent-to-tool communication).</p>
</li>
<li><p>A2A standardizes how multiple, independent AI agents communicate and collaborate with each other (agent-to-agent communication).</p>
</li>
</ul>
<h2 id="heading-resources">Resources</h2>
<p>For more information on the MCP, you can refer to the official website: <a target="_blank" href="http://modelcontextprotocol.io">modelcontextprotocol.io</a>.</p>
<p><strong>Some of the awesome MCP Servers which you can check:</strong></p>
<ul>
<li><p><a target="_blank" href="https://github.com/brave/brave-search-mcp-server">Brave Search MCP Server</a></p>
<ul>
<li>An MCP server implementation that integrates the Brave Search API, providing both web and local search capabilities.</li>
</ul>
</li>
<li><p><a target="_blank" href="https://github.com/getsentry/sentry-mcp">Sentry MCP server</a></p>
<ul>
<li>This server provides tools to inspect error reports, stacktraces, and other debugging information from your Sentry account.</li>
</ul>
</li>
<li><p><a target="_blank" href="https://developers.google.com/maps/ai/mcp">Google Maps MCP Server</a></p>
<ul>
<li>MCP Server for the Google Maps API.</li>
</ul>
</li>
<li><p><a target="_blank" href="https://flyonui.com/mcp">Tailwind MCP Server</a> by FlyonUI</p>
<ul>
<li>MCP Server for FlyoUI - Generate Amazing UIs/Themes/Sections with just a single prompt.</li>
</ul>
</li>
<li><p><a target="_blank" href="https://github.com/idosal/git-mcp">git MCP server</a></p>
<ul>
<li>A Model Context Protocol server for Git repository interaction and automation. This server provides tools to read, search, and manipulate Git repositories via Large Language Models.</li>
</ul>
</li>
<li><p><a target="_blank" href="https://github.com/github/github-mcp-server">GitHub MCP Server</a></p>
<ul>
<li>MCP Server for the GitHub API, enabling file operations, repository management, search functionality, and more.</li>
</ul>
</li>
<li><p><a target="_blank" href="https://shadcnstudio.com/mcp">Shadcn MCP Server</a></p>
<ul>
<li>MCP Server for shadcn/studio - Generate Amazing UIs/Themes/Sections with just a single prompt.</li>
</ul>
</li>
</ul>
<p>You can explore a list of available MCP servers here: <a target="_blank" href="https://github.com/punkpeye/awesome-mcp-servers">https://github.com/punkpeye/awesome-mcp-servers</a></p>
<p>If you're interested in learning how to build your own MCP server, check out this detailed course on Hugging Face: <a target="_blank" href="https://huggingface.co/mcp-course**">https://huggingface.co/mcp-course</a>.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>MCP (Model Context Protocol) is an open-source standard for connecting AI applications to external systems. With MCP, AI models are not just chatbots, they are fully capable agents that can work with your local files, query your database, send emails with your permission and control.</p>
<p>It has also solved the classic MxN problem—developers only need to build the MCP server once, then all other AI systems can integrate the MCP server in their application.</p>
<p>MCP is the revolution in how AI systems can interact with the real world. As the ecosystem of the MCP continues to grow, it will enable AI agents to become more powerful assistants that can operate across diverse environments with reliability and security.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build Your Own MCP Server with Python ]]>
                </title>
                <description>
                    <![CDATA[ Artificial intelligence is evolving at a remarkable pace. Models today can reason, write, code, and analyze information in ways that once seemed impossible. But there’s one major limitation that still holds them back: context. Most AI models don’t ha... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-your-own-mcp-server-with-python/</link>
                <guid isPermaLink="false">69038e6549c53ba349744d5b</guid>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                    <category>
                        <![CDATA[ mcp ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Model Context Protocol ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Manish Shivanandhan ]]>
                </dc:creator>
                <pubDate>Thu, 30 Oct 2025 16:12:21 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1761774966304/dace2a12-ea92-4c59-980a-5c16fb2d317d.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Artificial intelligence is evolving at a remarkable pace. Models today can reason, write, code, and analyze information in ways that once seemed impossible.</p>
<p>But there’s one major limitation that still holds them back: context.</p>
<p>Most AI models don’t have access to your system, files, APIs, or live data. They only know what you tell them in a prompt.</p>
<p>The <a target="_blank" href="https://www.turingtalks.ai/p/how-model-context-protocol-works">Model Context Protocol</a>, also known as MCP, was created to address this problem. It enables AI models to securely connect to your own tools, APIs, and systems via small, structured servers known as MCP servers.</p>
<p>In this guide, you’ll learn how to build your own MCP server using Python. We’ll walk through each part of the code and I’ll explain how it works. </p>
<p>By the end, you’ll have a running MCP server that can add numbers, return random words, and fetch live weather data from the internet. We will also see how to host this MCP server on the cloud. </p>
<h3 id="heading-what-well-cover">What we’ll cover:</h3>
<ul>
<li><p><a class="post-section-overview" href="#heading-understanding-the-model-context-protocol">What is Model Context Protocol</a>?</p>
</li>
<li><p><a class="post-section-overview" href="#heading-setting-up-your-environment">Setting Up Your Environment</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-creating-the-project">Creating the Project</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-configuring-logging">Configuring Logging</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-creating-the-mcp-server">Creating the MCP Server</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-defining-tools">Defining Tools</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-example-1-adding-two-numbers">Example 1: Adding Two Numbers</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-example-2-returning-a-random-secret-word">Example 2: Returning a Random Secret Word</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-example-3-fetching-weather-data">Example 3: Fetching Weather Data</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-running-the-server">Running the Server</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-testing-the-tools">Testing the Tools</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-deploying-your-mcp-server-to-sevalla">Deploying Your MCP Server to Sevalla</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-build-your-own-mcp-server">Why Build Your Own MCP Server</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-expanding-the-server">Expanding the Server</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-what-is-model-context-protocol">What is Model Context Protocol?</h2>
<p>Before diving into the code, it’s important to understand what the Model Context Protocol actually is.</p>
<p>MCP is an open standard that defines how AI models and external systems communicate. You can think of it as an API that’s designed specifically for AI assistants.</p>
<p>If an API lets two software programs exchange data, MCP allows an AI model to talk to your system. This opens up endless possibilities.</p>
<p>You could build an MCP server that lets ChatGPT read files from your local machine, or one that calls your company’s internal APIs to fetch data. You could even expose your own Python functions so that a model can use them as tools.</p>
<p>MCP makes this communication structured, secure, and extendable. It runs on familiar web technologies such as <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events">Server-Sent Events</a>, or SSE, which allow the server to send real-time data streams to the client.</p>
<h2 id="heading-setting-up-your-environment">Setting Up Your Environment</h2>
<p>To follow along, you’ll need Python version 3.9 or higher. You can find the code for this example <a target="_blank" href="https://github.com/sevalla-templates/python-demo-mcp-server">in this repository</a>.</p>
<p>We’ll use a library called <a target="_blank" href="https://github.com/jlowin/fastmcp">FastMCP</a> that simplifies the process of building MCP servers. You can install it using pip:</p>
<pre><code class="lang-powershell">pip install fastmcp requests
</code></pre>
<p>The <code>requests</code> library will be used to make HTTP calls later in the example. Once installed, you’re ready to create your first MCP server.</p>
<h2 id="heading-creating-the-project">Creating the Project</h2>
<p>Create a new file called <code>server.py</code> and start by importing the necessary modules:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> logging
<span class="hljs-keyword">import</span> os
<span class="hljs-keyword">import</span> random
<span class="hljs-keyword">import</span> sys
<span class="hljs-keyword">import</span> requests
<span class="hljs-keyword">from</span> mcp.server.fastmcp <span class="hljs-keyword">import</span> FastMCP
</code></pre>
<p>Here’s what each one does:</p>
<ul>
<li><p>The <code>logging</code> module records what your server is doing.</p>
</li>
<li><p><code>os</code> is used to access environment variables like port numbers.</p>
</li>
<li><p><code>random</code> will help us generate random words.</p>
</li>
<li><p><code>sys</code> allows the script to exit gracefully in case of errors.</p>
</li>
<li><p><code>requests</code> lets us fetch live data from APIs.</p>
</li>
<li><p>And finally, <code>FastMCP</code> turns our Python functions into tools that can be called through the MCP protocol.</p>
</li>
</ul>
<h2 id="heading-configuring-logging">Configuring Logging</h2>
<p>Logging gives you visibility into what your server is doing. It helps during development and is vital when you deploy your server in production.</p>
<pre><code class="lang-python">name = <span class="hljs-string">"demo-mcp-server"</span>
logging.basicConfig(
    level=logging.INFO,
    format=<span class="hljs-string">'%(name)s - %(levelname)s - %(message)s'</span>,
    handlers=[logging.StreamHandler()]
)
logger = logging.getLogger(name)
</code></pre>
<p>This configuration prints log messages to the console in a simple format showing the server name, the log level, and the message. Every time a tool runs, a message will appear in the logs such as:</p>
<pre><code class="lang-powershell">demo<span class="hljs-literal">-mcp</span><span class="hljs-literal">-server</span> - INFO - Tool called: add(<span class="hljs-number">3</span>, <span class="hljs-number">5</span>)
</code></pre>
<h2 id="heading-creating-the-mcp-server">Creating the MCP Server</h2>
<p>Next, we’ll create the server instance that will host our tools.</p>
<pre><code class="lang-python">port = int(os.environ.get(<span class="hljs-string">'PORT'</span>, <span class="hljs-number">8080</span>))
mcp = FastMCP(name, logger=logger, port=port)
</code></pre>
<p>The server will run on the port specified by the environment variable <code>PORT</code>. If that variable isn’t set, it defaults to 8080. The <code>FastMCP</code> object now represents your running MCP server.</p>
<h2 id="heading-defining-tools">Defining Tools</h2>
<p>Each function that you decorate with <code>@mcp.tool()</code> becomes an accessible tool that clients can call. Let’s start with a simple example: an addition tool.</p>
<h3 id="heading-example-1-adding-two-numbers"><strong>Example 1: Adding Two Numbers</strong></h3>
<pre><code class="lang-python"><span class="hljs-meta">@mcp.tool()</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">add</span>(<span class="hljs-params">a: int, b: int</span>) -&gt; int:</span>
    <span class="hljs-string">"""Add two numbers"""</span>
    logger.info(<span class="hljs-string">f"Tool called: add(<span class="hljs-subst">{a}</span>, <span class="hljs-subst">{b}</span>)"</span>)
    <span class="hljs-keyword">return</span> a + b
</code></pre>
<p>This tool takes two numbers, logs the call, and returns their sum. Calling <code>add(3, 5)</code> will return 8.</p>
<p>Even though it’s simple, this shows the basic structure of every MCP tool: input parameters, a logging statement, and a return value.</p>
<h3 id="heading-example-2-returning-a-random-secret-word"><strong>Example 2: Returning a Random Secret Word</strong></h3>
<p>Let’s make another tool that returns a random word from a small list.</p>
<pre><code class="lang-python"><span class="hljs-meta">@mcp.tool()</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_secret_word</span>() -&gt; str:</span>
    <span class="hljs-string">"""Get a random secret word"""</span>
    logger.info(<span class="hljs-string">"Tool called: get_secret_word()"</span>)
    <span class="hljs-keyword">return</span> random.choice([<span class="hljs-string">"apple"</span>, <span class="hljs-string">"banana"</span>, <span class="hljs-string">"cherry"</span>])
</code></pre>
<p>When you call this function, it picks one of the three words at random. Each time you call it, you might get a different result. This function demonstrates how MCP tools can use logic or randomness just like any regular Python function.</p>
<h3 id="heading-example-3-fetching-weather-data">Example 3: Fetching Weather Data</h3>
<p>Now let’s build something more practical. We’ll create a tool that fetches live weather data from the web using the <code>requests</code> library.</p>
<pre><code class="lang-python"><span class="hljs-meta">@mcp.tool()</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_current_weather</span>(<span class="hljs-params">city: str</span>) -&gt; str:</span>
    <span class="hljs-string">"""Get current weather for a city"""</span>
    logger.info(<span class="hljs-string">f"Tool called: get_current_weather(<span class="hljs-subst">{city}</span>)"</span>)

<span class="hljs-keyword">try</span>:
        endpoint = <span class="hljs-string">"https://wttr.in"</span>
        response = requests.get(<span class="hljs-string">f"<span class="hljs-subst">{endpoint}</span>/<span class="hljs-subst">{city}</span>"</span>, timeout=<span class="hljs-number">10</span>)
        response.raise_for_status()
        <span class="hljs-keyword">return</span> response.text
    <span class="hljs-keyword">except</span> requests.RequestException <span class="hljs-keyword">as</span> e:
        logger.error(<span class="hljs-string">f"Error fetching weather data: <span class="hljs-subst">{str(e)}</span>"</span>)
        <span class="hljs-keyword">return</span> <span class="hljs-string">f"Error fetching weather data: <span class="hljs-subst">{str(e)}</span>"</span>
</code></pre>
<p>This tool accepts a city name, sends a request to the public weather service at <code>wttr.in</code>, and returns the text-based weather report. If there’s any issue, such as a network timeout or invalid city name, the function logs an error and returns a descriptive message.</p>
<p>Calling <code>get_current_weather("London")</code> will print a short weather summary for that city.</p>
<h2 id="heading-running-the-server">Running the Server</h2>
<p>Once all your tools are defined, you can start the server. Add the following code to the bottom of your file:</p>
<pre><code class="lang-python"><span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    logger.info(<span class="hljs-string">f"Starting MCP Server on port <span class="hljs-subst">{port}</span>..."</span>)
    <span class="hljs-keyword">try</span>:
        mcp.run(transport=<span class="hljs-string">"sse"</span>)
    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
        logger.error(<span class="hljs-string">f"Server error: <span class="hljs-subst">{str(e)}</span>"</span>)
        sys.exit(<span class="hljs-number">1</span>)
    <span class="hljs-keyword">finally</span>:
        logger.info(<span class="hljs-string">"Server terminated"</span>)
</code></pre>
<p>This block starts the server using the Server-Sent Events transport method. If anything goes wrong, it logs the error and shuts down cleanly.</p>
<p>You can now run the server from your terminal:</p>
<pre><code class="lang-powershell">python server.py
</code></pre>
<p>If everything is working, you’ll see:</p>
<pre><code class="lang-powershell">demo<span class="hljs-literal">-mcp</span><span class="hljs-literal">-server</span> - INFO - Starting MCP Server on port <span class="hljs-number">8080</span>...
</code></pre>
<p>Your MCP server is now live and ready to accept requests.</p>
<h2 id="heading-testing-the-tools">Testing the Tools</h2>
<p>To test your tools, you need an MCP-compatible client such as ChatGPT with developer features or another app that supports the protocol. Once connected, the client will list your available tools.</p>
<p>For example, you can send a request like this:</p>
<pre><code class="lang-powershell">{
  <span class="hljs-string">"tool"</span>: <span class="hljs-string">"add"</span>,
  <span class="hljs-string">"args"</span>: [<span class="hljs-number">5</span>, <span class="hljs-number">7</span>]
}
</code></pre>
<p>The server will respond with:</p>
<pre><code class="lang-powershell">{
  <span class="hljs-string">"result"</span>: <span class="hljs-number">12</span>
}
</code></pre>
<p>The same applies to the other tools such as <code>get_secret_word</code> or <code>get_current_weather</code>.</p>
<p>If you want to test the server directly without the MCP client, you can still send HTTP requests manually (though this bypasses the full protocol logic).</p>
<p>For example, to test your weather tool, you can send a simple GET request:</p>
<pre><code class="lang-powershell"><span class="hljs-built_in">curl</span> http://localhost:<span class="hljs-number">8080</span>/tool/get_current_weather?city=London
</code></pre>
<p>or in Python:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> requests
response = requests.get(<span class="hljs-string">"http://localhost:8080/tool/get_current_weather"</span>, params={<span class="hljs-string">"city"</span>: <span class="hljs-string">"London"</span>})
print(response.text)
</code></pre>
<p>This won’t use the MCP structure (like <code>sse</code> streaming), but it’s a quick sanity check that your server works.</p>
<h2 id="heading-deploying-your-mcp-server-to-sevalla">Deploying Your MCP Server to Sevalla</h2>
<p>You can run this server locally for development. But if you want to use it in production applications, you have to deploy it to a server.</p>
<p>You can choose any cloud provider, like AWS, Heroku, or others to set up this project. But I will be using Sevalla.</p>
<p><a target="_blank" href="https://sevalla.com/">Sevalla</a> is a modern, usage-based Platform-as-a-service provider. It offers application hosting, database, object storage, and static site hosting for your projects.</p>
<p>I am using Sevalla for hosting for two reasons:</p>
<ul>
<li><p>Every platform will charge you for creating a cloud resource. Sevalla comes with a $50 credit for us to use, so we won’t incur any costs for this example.</p>
</li>
<li><p>Sevalla has a <a target="_blank" href="https://docs.sevalla.com/templates/overview">template for Python MCP server</a>, so it simplifies the manual installation and setup for each resource you will need for installation.</p>
</li>
</ul>
<p><a target="_blank" href="https://app.sevalla.com/login">Log in</a> to Sevalla and click on Templates. You can see Python MCP Server as one of the templates.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1761652364887/5003918a-f19a-42bf-94ad-306a3f6ab93c.png" alt="Sevalla Templates" class="image--center mx-auto" width="1000" height="340" loading="lazy"></p>
<p>Click on the “Python MCP Server” template. You will see the resources needed to provision the application. Click on “Deploy Template”. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1761652387263/871bf43f-214a-49c4-9734-7f71d0e5ce32.png" alt="Python MCP Server Resources" class="image--center mx-auto" width="1000" height="428" loading="lazy"></p>
<p>You can see the resource being provisioned. If the deployment doesn't start automatically, click “Deploy now”. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1761652411264/3e5a71c0-71c1-4cf9-92c2-e01fa77b7f45.png" alt="Python MCP Server Resources Provisioning" class="image--center mx-auto" width="1000" height="513" loading="lazy"></p>
<p>Wait for a few minutes. Once the deployment is complete, you will see a green checkmark. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1761652436109/68303890-91a4-4c8e-90d1-f6c142c571a6.png" alt="Python MCP Server Deployment" class="image--center mx-auto" width="1000" height="433" loading="lazy"></p>
<p>Once deployment is complete, click on “Visit app”. You will get a cloud url eg. <a target="_blank" href="https://python-mcp-server-rlfdk.sevalla.app/">https://python-mcp-server-rlfdk.sevalla.app</a>. Use this as the base url instead of the localhost:3000 url. </p>
<p>You now have a production-grade MCP server running on the cloud. You can plug this into any application to fetch data for our LLM applications. </p>
<h2 id="heading-why-build-your-own-mcp-server">Why Build Your Own MCP Server?</h2>
<p>Building an MCP server gives you control and flexibility. </p>
<p>You can connect AI models directly to your databases or internal systems, automate repetitive actions, and decide exactly what data an AI model can access. </p>
<p>It also allows you to experiment quickly. You can start small with a few simple tools and expand later into complex workflows.</p>
<p>By creating your own MCP server, you’re not just writing code – you’re defining how intelligent systems interact with the real world through your logic and data.</p>
<h2 id="heading-expanding-the-server">Expanding the Server</h2>
<p>Once you’ve mastered the basics, it’s easy to extend your server. You can add tools that read and write files, query databases, interact with APIs like GitHub or Slack, or monitor your system. Each new function becomes another tool that your AI can use.</p>
<p>This modular approach lets you build an entire ecosystem of AI-aware tools, each performing a specific task but working together through the same MCP interface.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this tutorial, you learned how to create an MCP server in Python using the FastMCP library. You configured logging, set up a server, defined multiple tools, and learned how to run and test it. You also saw how easily these tools can expose real functionality, like fetching live weather data or performing basic computations.</p>
<p>This structure is simple yet powerful. With just a few lines of Python code, you can build bridges between your systems and intelligent models. The Model Context Protocol represents a step toward AI systems that can truly understand and interact with real-world data and actions.</p>
<p><em>Hope you enjoyed this article. Signup for my free newsletter</em> <a target="_blank" href="https://www.turingtalks.ai/"><strong><em>TuringTalks.ai</em></strong></a> <em>for more hands-on tutorials on AI. You can also</em> <a target="_blank" href="https://manishshivanandhan.com/"><strong><em>visit my website</em></strong></a><em>.</em></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Custom MCP Server with TypeScript – A Handbook for Developers ]]>
                </title>
                <description>
                    <![CDATA[ MCP (Model Context Protocol) lets you connect your code, data, and tools to AI applications like Claude and Cursor. This handbook explains how it works with real-world analogies, and shows you how to build a custom MCP server using TypeScript that fe... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-a-custom-mcp-server-with-typescript-a-handbook-for-developers/</link>
                <guid isPermaLink="false">685c2467df51707f055a263f</guid>
                
                    <category>
                        <![CDATA[ Model Context Protocol ]]>
                    </category>
                
                    <category>
                        <![CDATA[ TypeScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ #ai-tools ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Sumit Saha ]]>
                </dc:creator>
                <pubDate>Wed, 25 Jun 2025 16:31:35 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1750868512407/95f366d3-9115-423a-8d63-66e53171931a.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>MCP (Model Context Protocol) lets you connect your code, data, and tools to AI applications like Claude and Cursor. This handbook explains how it works with real-world analogies, and shows you how to build a custom MCP server using TypeScript that feeds live data into an AI environment.</p>
<h3 id="heading-heres-what-well-cover">Here’s what we’ll cover:</h3>
<ul>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-the-model-context-protocol-mcp">What is the Model Context Protocol (MCP)?</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-does-protocol-mean">What does "Protocol" mean?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-a-model">What is a "Model"?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-context">What is "Context"?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-putting-it-all-together-what-is-model-context-protocol">Putting It All Together: What is Model Context Protocol?</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-why-mcp-is-necessary">Why MCP is Necessary</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-the-mcp-connector-in-action">The MCP Connector in Action</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-universal-access-across-platforms">Universal Access Across Platforms</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-developers-are-key">Developers are Key</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-beyond-built-in-integrations">Beyond Built-in Integrations</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-power-of-reusability">The Power of Reusability</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-burden-without-mcp">The Burden without MCP</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-a-practical-github-example">A Practical GitHub Example</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-mcp-matters-for-developers">Why MCP Matters for Developers</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-rag-vs-mcp">RAG vs MCP</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-rag">What is RAG</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-rag-the-mise-en-place-prep">RAG: The “Mise en Place” Prep</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-mcp-the-rolling-assistant-cart">MCP: The Rolling Assistant Cart</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-bringing-it-all-together">Bringing It All Together</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-mcp-documentation">MCP Documentation</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-ai-apps-talk-to-mcp-servers-a-practical-example">How AI Apps Talk to MCP Servers — A Practical Example</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-scenario-asking-claude-about-your-schedule">Scenario: Asking Claude About Your Schedule</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-discovering-the-right-mcp-server">Discovering the Right MCP Server</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-mcp-server-fetches-and-returns-the-data">MCP Server Fetches and Returns the Data</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-model-converts-structured-data-into-natural-language">Model Converts Structured Data into Natural Language</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-under-the-hood-abstracting-the-complexity">Under the Hood: Abstracting the Complexity</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-mirroring-standard-web-app-workflows">Mirroring Standard Web App Workflows</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-how-mcp-servers-work-internally">How MCP Servers Work Internally</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-mcp-architecture-how-it-all-fits-together">The MCP Architecture — How It All Fits Together</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-1-mcp-host">1. MCP Host</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-2-mcp-client">2. MCP Client</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-3-mcp-server">3. MCP Server</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-4-data-sources-local-or-remote">4. Data Sources – Local or Remote</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-opportunities-for-web-developers">Opportunities for Web Developers</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-sdk-options-pick-your-language">SDK Options: Pick Your Language</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-from-backend-service-to-ai-enabled-developer">From Backend Service to AI-Enabled Developer</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-mcp-server-setup-and-integration">MCP Server Setup and Integration</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-summary">Summary</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>To follow along and get the most out of this guide, you should have:</p>
<ol>
<li><p><strong>Basic understanding of TypeScript or JavaScript:</strong> While we’ll use TypeScript here, knowledge of JavaScript alone is enough to follow the examples.</p>
</li>
<li><p><strong>Familiarity with Node.js and npm:</strong> You should know how to initialize a project, install packages, and run scripts using node and npm.</p>
</li>
<li><p><strong>Experience with working in the terminal/command line:</strong> Especially for understanding concepts like stdin and stdout, and running local servers.</p>
</li>
<li><p><strong>Comfort with environment variables (.env files):</strong> You’ll be setting API keys and other sensitive data in a .env file.</p>
</li>
<li><p><strong>Basic knowledge of REST APIs and HTTP concepts:</strong> This helps in understanding how we used AI tools to fetch context before MCP and why MCP simplifies the process.</p>
</li>
<li><p><strong>Familiarity with Google Cloud / API Console (optional but recommended):</strong> Since this handbook involves integrating with Google Calendar, you should know how to:</p>
<ul>
<li><p>Generate a public Google API key</p>
</li>
<li><p>Find or create a Google Calendar and access its ID</p>
</li>
</ul>
</li>
<li><p><strong>Cursor editor installed (optional but recommended):</strong> To follow the final integration steps with the AI-powered code editor.</p>
</li>
<li><p><strong>Some exposure to AI tools like Claude, Cursor, or ChatGPT:</strong> This helps you grasp how MCP bridges external data with AI context.</p>
</li>
</ol>
<p>I’ve also created a video to go along with this handbook. If you’re the type who likes to learn from video as well as text, you can check it out here:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/XC49e0pliEE" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p> </p>
<h2 id="heading-what-is-the-model-context-protocol-mcp">What is the Model Context Protocol (MCP)?</h2>
<p>Let's start from the very beginning: what exactly is the MCP? MCP stands for <strong>Model Context Protocol</strong>. And if we break it down word by word – "model", "context", and "protocol" – it actually becomes quite easy to understand.</p>
<p>But before diving in, here's a quick background: Model Context Protocol was developed by a company called <a target="_blank" href="https://www.anthropic.com"><strong>Anthropic</strong></a>. You've probably heard of them. They're the ones who built <a target="_blank" href="https://claude.ai"><strong>Claude</strong></a>, the popular AI assistant. They first introduced MCP in November of 2024, and in a short time it’s become a standard adopted by tons of other companies as well, including Microsoft.</p>
<p>Now, let's explore what MCP really means by understanding each term.</p>
<h3 id="heading-what-does-protocol-mean">What does "Protocol" mean?</h3>
<p>Let's start with the last word: Protocol. What does "Protocol" mean? Well, it’s a set of rules.</p>
<p>As developers, we work with protocols all the time. For example, when we work with the <strong>HTTP protocol</strong>, it's not just random communication – there's a set of rules we follow. When we build REST APIs, we use specific methods like <code>GET</code>, <code>POST</code>, <code>PUT</code>, <code>PATCH</code>, or <code>DELETE</code>. We transfer data in specific formats like JSON, XML, or even JSON-RPC. All of this is structured communication that follows a protocol.</p>
<p>In a similar way, AI agents or AI-based applications also need to follow a structured approach when exchanging information. We'll explore that more in a bit, but for now, just remember: a protocol is simply a set of rules.</p>
<h3 id="heading-what-is-a-model">What is a "Model"?</h3>
<p>Next, let's talk about the “Model”. The term "Model" is something you’re likely already quite familiar with. We all use models in one way or another, especially large language models or LLMs.</p>
<p>Take GPT from OpenAI, Gemini from Google, Claude from Anthropic – you may use these every day. There are tons of models available now, like the newer DeepSeek and so on. The point is, we already interact with models regularly. We ask questions, and they give us answers.</p>
<p>But have you ever wondered how these LLMs actually work? Most people think that when you ask a model something, it goes and searches the internet for answers. But that's not how it works.</p>
<p>What these models actually do is <strong>predict the next word</strong> in a sentence – that's it. They're <strong>language experts</strong> – they don't know "facts" in real-time or pull live data from the web. Instead, they've been trained with a huge amount of information beforehand (pre-trained). Then when you ask something, they try to figure out: "What word most likely comes next based on what the user just said?"</p>
<p>That's why when you ask something, the reply appears word by word – like it's typing. And no, that's not some fancy frontend animation. That's just how LLMs work: they predict one word at a time. It looks like typing because it is being generated in real time, one word at a time.</p>
<p>That's the core of <strong>Generative AI</strong>. They're experts in natural language – understanding how we speak, predicting what we're likely to say next and generating responses accordingly.</p>
<h3 id="heading-what-is-context">What is "Context"?</h3>
<p>Now let's move to “Context”. In English, context means the subject or background of something. For example, when you send an email, you add a subject line. And just by looking at the subject, the recipient gets an idea of what the email is about – even before opening it.</p>
<p>Similarly, when you talk to a model like ChatGPT or Claude, you provide a few lines – maybe a question or some background. That input becomes the “Context”.</p>
<p>The model's response entirely depends on the context you provide. It uses that context to start predicting the next word. If the model already knows what you're referring to, based on the context, it'll give you an accurate answer. But if you don't provide enough context, it can't help you properly – even if it's a powerful LLM.</p>
<p>Let me give you a simple example: Suppose you go to Claude and ask, <em>"Who am I?"</em> Will it be able to answer that? No, it won't. But if in a previous message you had told Claude, <em>"Hey, I'm Sumit"</em> and then later in the same session ask <em>"Who am I?"</em>, it will say, <em>"You're Sumit."</em> Why? Because now it has context.</p>
<p>So, context is just background info – and the better context you give, the better the model can respond. That's how these LLMs are designed to work.</p>
<h3 id="heading-putting-it-all-together-what-is-model-context-protocol">Putting It All Together: What is Model Context Protocol?</h3>
<p>So when we say “Model Context Protocol”, we're talking about a <strong>set of rules or protocols</strong> that define how to feed <strong>context</strong> into a <strong>model</strong>. Now, what is this context we're feeding? It could be any kind of external information – something outside the model's default knowledge.</p>
<p>It’s like going to Claude Desktop and telling it, <em>“Hey, I’m Sumit!”</em> and then asking "<em>Who am I?</em>". Again, it’ll know because you told it before.</p>
<p>But here's the catch: models don't magically know about your calendar, your emails, your databases or your files. So how do you make that data available to them? That's where MCP comes in.</p>
<p>MCP lets us feed these external pieces of information – like your schedule, your project data, or anything else – into a model, but in a structured and standardized way. And that's what makes MCP so powerful.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750671107217/c0c8c59d-5c2c-451e-9649-0889cae36c05.png" alt="Model Context Protocol - Structured Communication" class="image--center mx-auto" width="3352" height="1894" loading="lazy"></p>
<h2 id="heading-why-mcp-is-necessary">Why MCP is Necessary</h2>
<p>Now that you understand what MCP is, let's talk about why we need it. Why did Anthropic even invent this thing in the first place?</p>
<p>Let's think about how we use different code editors in our day-to-day work. One really powerful, AI-equipped modern code editor is <a target="_blank" href="https://www.cursor.com">Cursor</a>. Personally, I don't use it regularly, but it’s perfect for this demonstration. Imagine you are inside your Cursor editor. And, as many of you know, you can chat with Cursor while coding. You can ask it to explain something, generate code, refactor logic, and so on.</p>
<h3 id="heading-the-mcp-connector-in-action">The MCP Connector in Action</h3>
<p>Now let's say you ask Cursor something that depends on data from your local machine – maybe a large email database or your own personal documents. Can Cursor access that data by default? No, it can't. But what if – and this is the important part – what if you connect a custom-made component to Cursor?</p>
<p>Let's call it an <strong>MCP server</strong>. If you connect your MCP server to Cursor, then here's what happens: Cursor still can't access your files directly. But now, when you ask it a question, it will turn to this MCP server and say: "<em>Hey, do you know anything about this?</em>" And the MCP server – since you've built it to connect with your files or databases – will fetch the relevant information, turn it into context, and feed that back to the model. Now the model has the necessary background to generate a smart, informed reply.</p>
<p>And the best part? You're not limited to just one connector. You can connect multiple MCP servers to your application.</p>
<h3 id="heading-universal-access-across-platforms">Universal Access Across Platforms</h3>
<p>Let's now walk through a real example – something I'll actually show you later with code examples in this handbook.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750678334371/7244b0ee-f015-48f2-9433-2cf29a194b06.jpeg" alt="How MCP Server communicates with Local Data and generate Response" class="image--center mx-auto" width="3778" height="2136" loading="lazy"></p>
<p>Say you ask Cursor: "<em>Do I have any meetings today?</em>" Now to answer that, the AI would need access to your schedule, right? Let's say you use Google Calendar to manage your meetings. Can Cursor directly connect to your Google Calendar? No, it can't. And not just Cursor – ChatGPT or Claude can't access your calendar either, not unless you manually build that integration.</p>
<p>But here's the thing: what if you want this to work universally? Like, no matter where you ask the question from? You might ask it from Cursor today, but someone else might ask from ChatGPT tomorrow.</p>
<p>In both cases, we want these tools to access your calendar and return the same result. To make that possible, we need a universal way to connect – and that's exactly what an MCP server enables. If you create an MCP server that follows the protocol and hooks into your calendar, then any AI application that supports MCP can connect to it and get the right context. That's another reason MCP is so powerful.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750671538182/0955d5b2-48ce-4e12-877b-0eeb7d2ddad2.png" alt="How MCP feeds context to Cursor Editor" class="image--center mx-auto" width="3334" height="1862" loading="lazy"></p>
<h3 id="heading-developers-are-key">Developers are Key</h3>
<p>And the best part? You (the developer) are the one who will build these MCP servers. This isn't something regular users can build – you need coding skills for this.</p>
<p>This is one reason AI won’t replace developers just yet :)</p>
<h3 id="heading-beyond-built-in-integrations">Beyond Built-in Integrations</h3>
<p>Let's compare that with what used to happen before. For example, today, ChatGPT lets you do web searches – you can just ask it to find something online, and it'll fetch the result. But this feature is only there because <a target="_blank" href="https://openai.com">OpenAI</a>, the makers of ChatGPT, built it into the app.</p>
<p>Now imagine your own product – like my logicBase Labs website. Let's say students come to the site and ask questions through a chat box you’ve built. That AI assistant belongs to you – it's part of your software. You can connect it to any model, like GPT, Claude, whatever, that understands natural language. But you still need to feed it the right information so it can respond meaningfully.</p>
<p>So what do you do? You build your own MCP server, maybe using Node.js, Python, or Java – whatever tech stack you're comfortable with. This MCP server is a completely standalone app. Now you also build your chat interface – the UI where students type questions. You connect it to the LLM (like GPT or Claude) and to your custom-built MCP server.</p>
<h3 id="heading-the-power-of-reusability">The Power of Reusability</h3>
<p>Here's the best part: your MCP server is now independent and reusable. You could even give it to another company, like another EdTech company wants to use your calendar or data handling logic. They can just modify your MCP server, replace the logic with their own data, and use it with their chat client. And boom – it's a universal solution now.</p>
<p>Even better, let's say the data inside my logicBase Labs website changes in the future. No problem! I won’t need to rewrite the connector logic. The code that fetches and formats the data stays the same. The content might change, but the structure is stable.</p>
<h3 id="heading-the-burden-without-mcp">The Burden without MCP</h3>
<p>But if I wasn’t using MCP, what would I have to do? I’d need to build everything into my client. Every AI assistant would need to carry the burden of logic, context building, and data retrieval. If anything changed – say the GitHub repo’s structure, the schedule format, or the database schema – I’d have to go and update every single client individually. That's a nightmare!</p>
<h3 id="heading-a-practical-github-example">A Practical GitHub Example</h3>
<p>Let me give you another solid example. Suppose you want to connect your GitHub to Cursor. You want to say something like: "<em>Hey, push my code to GitHub</em>" – and it just works. To make that happen without MCP, what would you normally need to do? You'd have to:</p>
<ul>
<li><p>Read through the <a target="_blank" href="https://docs.github.com/en/rest">GitHub API documentation</a></p>
</li>
<li><p>Write integration logic</p>
</li>
<li><p>Handle OAuth authentication</p>
</li>
<li><p>Deal with access tokens and API limits</p>
</li>
</ul>
<p>It's complex. It's messy. But imagine this: What if GitHub themselves released their own MCP server? Then all you need to do is:</p>
<ul>
<li><p>Plug that MCP server into Cursor</p>
</li>
<li><p>Let the model discover the capabilities</p>
</li>
<li><p>Say: "<em>Push my code</em>"</p>
</li>
</ul>
<p>And boom – it works! You don't need to write any custom integration logic. That's the magic of MCP. And here's the best part: GitHub already released their <a target="_blank" href="https://github.com/github/github-mcp-server">official MCP server</a>. You can use it right now.</p>
<h3 id="heading-why-mcp-matters-for-developers">Why MCP Matters for Developers</h3>
<p>So I hope you now see the bigger picture. MCP servers are a game-changer. They don't just reduce your workload – they create new job opportunities for developers like us. This isn't going to "replace your job". Rather, it's creating new, valuable work that didn't exist before.</p>
<h2 id="heading-rag-vs-mcp">RAG vs MCP</h2>
<p>Now that we’ve covered MCP, let’s look at another popular approach called <a target="_blank" href="https://en.wikipedia.org/wiki/Retrieval-augmented_generation">RAG</a> and see how they differ. Many AI builders start by using RAG to ground their models in static knowledge, so it’s helpful to see how that approach compares to streaming live data with MCP.</p>
<h3 id="heading-what-is-rag">What is RAG?</h3>
<p>First up, what is RAG? <strong>Retrieval-Augmented Generation</strong> is a technique in which an AI model reaches out to an external “library” of documents at the moment you ask a question. It pulls back just the pages it needs, tucks them into your prompt and then writes its answer using those exact excerpts. In other words, it dynamically augments itself with relevant text from a large corpus.</p>
<h3 id="heading-rag-the-mise-en-place-prep">RAG: The “Mise en Place” Prep</h3>
<p>Imagine you’re the head chef preparing for service. Before the doors open, you and your team do a full <strong>mise en place</strong>: chop, measure, and arrange every ingredient on your counter so it’s ready the moment you need it. When orders start flying in, you simply grab what’s already laid out – no running back to the pantry.</p>
<p>How it works:</p>
<ol>
<li><p>Retrieve: Your system searches a document store for the most relevant “ingredients” (text snippets).</p>
</li>
<li><p>Augment: Those snippets get mixed into your AI prompt.</p>
</li>
<li><p>Generate: The model cooks up an answer grounded in that batch of information.</p>
</li>
</ol>
<p>RAG is great for static or rarely changing content (think policy manuals, research papers, or any “recipe book” that doesn’t get rewritten mid-service).</p>
<h3 id="heading-mcp-the-rolling-assistant-cart">MCP: The Rolling Assistant Cart</h3>
<p>Now imagine halfway through dinner you realize you need a fresh herb or a special garnish that wasn’t prepped. Instead of halting the kitchen, you wheel over an assistant cart loaded with whatever new items appear – they bring you that garnish the second it’s ready.</p>
<p>How it works:</p>
<ol>
<li><p>Subscribe/Stream: Your AI client opens a live line to the data source.</p>
</li>
<li><p>Deliver: As soon as new data (like a live order update or sensor reading) is available, it rolls up to you.</p>
</li>
<li><p>Consume: Your model can tap into that fresh data anytime during generation.</p>
</li>
</ol>
<p>MCP is great for scenarios needing up-to-the-minute info (like live dashboards, chatbots feeding off recent user activity, IoT sensor streams, and so on).</p>
<h3 id="heading-bringing-it-all-together">Bringing It All Together</h3>
<ul>
<li><p>RAG alone: Best when your "mise en place" is extensive enough to cover everything you need – pre-prepared background knowledge.</p>
</li>
<li><p>MCP alone: Required when you need “a rolling cart” of fresh ingredients at one's fingertips.</p>
</li>
<li><p>Combined approach: Do your background “mise en place” with RAG for in-depth context, and keep the assistant cart rolling with MCP to provide live updates – so your AI has deep background knowledge along with real-time freshness.</p>
</li>
</ul>
<h2 id="heading-mcp-documentation">MCP Documentation</h2>
<p>Now let's check out the <a target="_blank" href="https://modelcontextprotocol.io/introduction">official MCP documentation</a>. It’ll help things start to feel much clearer. So what does the definition say?</p>
<blockquote>
<p>MCP is an open protocol that standardizes how applications provide context to LLMs. (<a target="_blank" href="https://modelcontextprotocol.io/introduction">Source: MCP Documentation</a>)</p>
</blockquote>
<p>Yep – exactly what we've already talked about. And then comes a brilliant line from the docs:</p>
<blockquote>
<p>Think of MCP like a USB-C port for AI applications. (<a target="_blank" href="https://modelcontextprotocol.io/introduction">Source: MCP Documentation</a>)</p>
</blockquote>
<p>Let's pause here, because this analogy is super important. Think about the USB-C port on modern devices. We all use it. But remember how things were before? Back in the day, your computer would have tons of different ports – HDMI, VGA, USB-A, audio jack, you name it. You'd have to manage different cables for everything. Maybe your mouse was USB-A, your keyboard used some other port, and your external monitor needed HDMI. It was a mess.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750678229073/7df559c2-48bf-4a39-8415-31276609ca98.jpeg" alt="USB-C universal connector analogy" class="image--center mx-auto" width="1500" height="838" loading="lazy"></p>
<p>But now? Everything uses USB-C. One universal connector for data, power, audio, video – everything. That's exactly what MCP is for AI applications. Instead of building separate integrations or connectors for each AI tool (Cursor, ChatGPT, Claude, and so on), you now build one standardized MCP server and any AI tool that supports MCP can connect to it. That's why this protocol is such a big deal.</p>
<h2 id="heading-how-ai-apps-talk-to-mcp-servers-a-practical-example">How AI Apps Talk to MCP Servers — A Practical Example</h2>
<p>Let me walk you through one more example just to help you really get this. Imagine you're using Claude. You ask it a simple question: “<em>Do I have a meeting today?"</em></p>
<h3 id="heading-scenario-asking-claude-about-your-schedule">Scenario: Asking Claude About Your Schedule</h3>
<p>Now, Claude doesn't actually have that information. If you haven't connected any MCP server, it'll give you a vague answer. Probably something nice and generic, because it's good at natural language – but not specific. But if you want a real answer – something factual – you need to feed it context. And that's where the MCP server steps in.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750671970459/c1cecbfc-1012-4580-ba95-ccfeb0854129.png" alt="Claude vague answer without MCP" class="image--center mx-auto" width="3198" height="1696" loading="lazy"></p>
<h3 id="heading-discovering-the-right-mcp-server">Discovering the Right MCP Server</h3>
<p>Let's say Claude is connected to an MCP server. Now things get interesting. As soon as you ask the question, Claude will first look at the list of MCP servers it's connected to. Then it'll intelligently choose the right one and ask:</p>
<p><em>"Hey, what are your capabilities?"</em></p>
<p>Because your MCP server might be able to do many things. Maybe it can:</p>
<ul>
<li><p>Give a full list of calendar events</p>
</li>
<li><p>Check if there's a meeting on a specific day</p>
</li>
<li><p>Fetch data from Google Calendar</p>
</li>
<li><p>Summarize documents</p>
</li>
</ul>
<p>So the model first figures out:</p>
<p><em>"Which of these capabilities do I need?"</em></p>
<p>In this case, it decides:</p>
<p><em>"Okay, I just need to know if the user has a meeting today."</em></p>
<p>Then Claude sends a message to the MCP server – in a specific format, which we'll look at shortly. It's kind of like how REST APIs work. The message says something like:</p>
<p><em>"Here's the date. Tell me if the user has a meeting."</em></p>
<h3 id="heading-mcp-server-fetches-and-returns-the-data">MCP Server Fetches and Returns the Data</h3>
<p>Now the MCP server takes that input, connects to Google Calendar (or whichever source you've set it up with) and runs the necessary logic. Eventually, it sends back a response, usually in a structured format like <strong>JSON-RPC</strong>. It might return a list of meetings or just one – whatever applies.</p>
<h3 id="heading-model-converts-structured-data-into-natural-language">Model Converts Structured Data into Natural Language</h3>
<p>Now here's the beauty of it. Even though the MCP server is giving back something technical (like JSON), Claude will never show that to the user. Because it's a <strong>language model</strong>, it will convert that structured data into a smooth, natural sentence like:</p>
<p><em>"Yes, you have a meeting with Dr. Chuck at 4 PM."</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750672087724/efa2637d-1a35-4f7a-b8f6-2d9183580c23.png" alt="Claude positive answer with MCP Server" class="image--center mx-auto" width="3386" height="1768" loading="lazy"></p>
<h3 id="heading-under-the-hood-abstracting-the-complexity">Under the Hood: Abstracting the Complexity</h3>
<p>To the user, it feels like magic. But behind the scenes, a lot just happened:</p>
<ul>
<li><p>The model found the right MCP server</p>
</li>
<li><p>It selected the right capability</p>
</li>
<li><p>It passed the correct input</p>
</li>
<li><p>The server ran logic, got the data, and returned a structured result</p>
</li>
<li><p>And finally, the model turned that into human language</p>
</li>
</ul>
<h3 id="heading-mirroring-standard-web-app-workflows">Mirroring Standard Web App Workflows</h3>
<p>This is exactly how our websites work too. Let's say a user visits your website and types something in a message box. You fetch data in the backend, maybe call an API or run a DB query. That response comes back in JSON — but the user never sees that. What they see is the final polished UI response. Same principle here. So I hope it's now clear how powerful this system is.</p>
<h2 id="heading-how-mcp-servers-work-internally">How MCP Servers Work Internally</h2>
<p>Now let's go one step deeper and understand how an MCP server actually works under the hood – technically. An MCP server primarily works through something called <strong>standard input and output</strong>, or in programming terms, <code>stdin</code> and <code>stdout</code>. So what does that mean? Let's break it down with an example.</p>
<p>You know when you open a terminal in the Cursor editor, it gives you a basic shell where you can type in commands? That terminal is using your machine's standard input and output system.</p>
<p>Now typically, when websites communicate with APIs, they use REST APIs over HTTP. But with MCP servers – especially when they're used locally – we don't use HTTP. Here's why: many times, your MCP server is running on your own machine, connected to local databases or files. So instead of going through network calls, it uses direct system-level communication through <code>stdin</code> and <code>stdout</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750678140969/9f22b8a9-9c56-45cc-95ae-5b946e379133.jpeg" alt="MCP local transport process similar to REST API" class="image--center mx-auto" width="3518" height="1992" loading="lazy"></p>
<p>Let's say you're inside Cursor, and you type something like:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">echo</span> <span class="hljs-string">"hello"</span>
</code></pre>
<p>What happens? The terminal reads your input (<code>stdin</code>), processes it and prints <code>hello</code> back to you via <code>stdout</code>. This same pattern is used by MCP servers.</p>
<p>Now imagine the AI application (like Claude) is trying to talk to your MCP server. How does it do that? It doesn't send an HTTP request like a web client. Instead, it writes the request directly into the MCP server's <strong>standard input</strong> – just like how a terminal command works. And then your MCP server reads that input, performs the necessary action (maybe it talks to Google Calendar, a database, a filesystem, whatever) and once it's done, it sends the response back using <strong>standard output</strong>.</p>
<p>Let's imagine a real-life conversation between Claude and your MCP server. You ask Claude:</p>
<p><em>"Do I have a meeting today?"</em></p>
<p>Claude realizes it doesn't have this information on its own. So what does it do? First, it discovers the tools or methods available – it checks all connected MCP servers to see what they can do. Then it intelligently figures out the best method to use. Let's say it figures out:</p>
<p><em>"Alright, I should call the</em> <code>calendar</code> <em>method."</em></p>
<p>It then writes a structured input into your MCP server's stdin, something like:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"method"</span>: <span class="hljs-string">"calendar"</span>,
    <span class="hljs-attr">"params"</span>: {
        <span class="hljs-attr">"date"</span>: <span class="hljs-string">"2025-06-16"</span>
    }
}
</code></pre>
<p>Okay, the real format may differ, but conceptually it's like this. Your MCP server then receives that input, runs the logic, maybe pulls data from your Google Calendar, and then responds like this:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"result"</span>: {
        <span class="hljs-attr">"meetings"</span>: [
            {
                <span class="hljs-attr">"title"</span>: <span class="hljs-string">"Team Sync"</span>,
                <span class="hljs-attr">"time"</span>: <span class="hljs-string">"4:00 PM"</span>
            }
        ]
    }
}
</code></pre>
<p>Now here's the kicker: Claude doesn't show this JSON to the user.</p>
<p>It reads that raw data, runs its natural language model, and finally says:</p>
<p><em>"Yes, you have a meeting 'Team Sync' today at 4 PM."</em></p>
<p>That's the entire lifecycle. And the user? They don't even know what's going on behind the scenes. Just like when a non-technical person uses your website, they don't know about fetch calls or JSON responses. They just see a smooth UI. Same deal here.</p>
<p>And this <code>stdin</code>/<code>stdout</code> approach works great locally – especially for data on your machine. Later, we'll see how things work differently when you connect to remote services. But for now, just remember:</p>
<p>MCP doesn't use HTTP calls for local communication. It works through the terminal – <code>stdin</code> and <code>stdout</code>.</p>
<p>And that makes it fast, secure, and incredibly flexible.</p>
<h2 id="heading-the-mcp-architecture-how-it-all-fits-together">The MCP Architecture — How It All Fits Together</h2>
<p>Let's now take a look at the MCP architecture. Once you see the structure, everything we've discussed will make even more sense. Here's what the diagram shows (the diagram was collected from the <a target="_blank" href="https://modelcontextprotocol.io/introduction">MCP documentation</a>):</p>
<p><a target="_blank" href="https://modelcontextprotocol.io/introduction"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750672357388/9d07577d-447e-4a13-b205-1d0e72b5d18a.png" alt="MCP architecture diagram from the docs" class="image--center mx-auto" width="1896" height="1290" loading="lazy"></a></p>
<p>We have a <strong>host</strong> – that could be Claude, or any AI-powered application. This host is connected to one or more <strong>MCP servers</strong> through the <strong>MCP protocol</strong>. And these MCP servers are in turn connected to <strong>external data sources</strong>, which could be local files or remote services like APIs, calendars, databases, and so on.</p>
<p>Now what does the MCP server do? It retrieves data from those external sources, prepares the appropriate context, and feeds it back to the host using the MCP protocol. That context is then used by the LLM to generate a relevant, natural-sounding response.</p>
<p>All this communication – at least when done locally – happens via standard input and output (<code>stdin</code> and <code>stdout</code>) like we discussed earlier.</p>
<p>Let's go over the components one by one.</p>
<h3 id="heading-1-mcp-host">1. MCP Host</h3>
<p>First, we have the MCP host. This is the AI application, something like Claude, Cursor, or even your own AI interface. If we compare this to traditional web architecture, the host is like the server of your website – the main brain that runs the show. In the context of Cursor, the Cursor editor itself is the MCP host.</p>
<h3 id="heading-2-mcp-client">2. MCP Client</h3>
<p>Next, we have the MCP client. So what's the client in this context? Well, in web development, think of a user's browser as the client – not the user themselves, but the actual browser that sends requests and receives responses. In the MCP world, the MCP client is the internal part of the host that connects to MCP servers.</p>
<p>Let's take Cursor again as an example.</p>
<p>If you go into Cursor's settings, you'll see something called <strong>MCP Tools</strong>. That's where you can add any custom MCP server. Cursor has a built-in client that lets you plug in your own server. If you were building your own editor like Cursor, you'd need to write this client logic yourself to handle things like discovering servers, formatting requests, and reading responses. Good news is, there's <a target="_blank" href="https://modelcontextprotocol.io/quickstart/client">already a spec and libraries</a> to help with that too.</p>
<h3 id="heading-3-mcp-server">3. MCP Server</h3>
<p>Then, of course, comes the MCP server, which we've already talked about at length. It's the tool you build that knows how to fetch or generate context from files, APIs, calendars, anything. You can make it with Node, Python, Java – anything you like. As long as it follows the protocol, it'll work. And remember – it can be reused across different AI apps. That's the beauty of MCP.</p>
<h3 id="heading-4-data-sources-local-or-remote">4. Data Sources – Local or Remote</h3>
<p>Last but not least, we have the data sources. Your MCP server needs to pull data from somewhere. That "somewhere" could be:</p>
<ul>
<li><p>A local SQLite or Postgres DB</p>
</li>
<li><p>Your file system</p>
</li>
<li><p>An external API like Google Calendar or GitHub</p>
</li>
<li><p>A third-party SaaS dashboard</p>
</li>
<li><p>Anything else that holds relevant context</p>
</li>
</ul>
<p>The point is: you abstract away the data handling into your MCP server. So the AI host doesn't care how the data is fetched – it just gets structured context in return.</p>
<p>So to recap:</p>
<ul>
<li><p>The <strong>MCP host</strong> is your AI application (like Claude, Cursor, or a custom app).</p>
</li>
<li><p>The <strong>MCP client</strong> is the bridge inside that host that connects to external MCP servers.</p>
</li>
<li><p>The <strong>MCP server</strong> is what you, the developer, build – to deliver context.</p>
</li>
<li><p>And the <strong>data sources</strong> are whatever backend services or files hold your knowledge.</p>
</li>
</ul>
<p>Everything talks to each other via the MCP protocol. And locally, it all happens through <code>stdin</code>/<code>stdout</code>, like a conversation between programs in the terminal. So that's the whole summary in one go. I hope you understand how it all works.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750672511389/bd68a3f8-7bf3-4fcc-b631-c99a4ea05667.png" alt="MCP components talk to each other" class="image--center mx-auto" width="2586" height="1592" loading="lazy"></p>
<h2 id="heading-opportunities-for-web-developers">Opportunities for Web Developers</h2>
<h3 id="heading-sdk-options-pick-your-language">SDK Options: Pick Your Language</h3>
<p>Alright, soon we will start building an MCP server – and for that, we'll be using the TypeScript SDK. Now, if you look at the documentation, you'll notice that there are many SDKs available. You can build MCP servers using:</p>
<ul>
<li><p>C#</p>
</li>
<li><p>Java</p>
</li>
<li><p>Kotlin</p>
</li>
<li><p>Python</p>
</li>
<li><p>Ruby</p>
</li>
<li><p>Swift (for mobile)</p>
</li>
<li><p>and of course, TypeScript – which is essentially JavaScript with superpowers!</p>
</li>
</ul>
<p>And since JavaScript is like my mother tongue, I'll naturally go with TypeScript here. Now, don't worry – this won't be super technical. I'm not going to sit and code line-by-line with you, but I will walk you through the important parts so you get a clear understanding.</p>
<h3 id="heading-from-backend-service-to-ai-enabled-developer">From Backend Service to AI-Enabled Developer</h3>
<p>What you'll find is that everything we'll be doing here is stuff you likely already know. Because this is still just regular coding. You're going to build a backend service – just like one you may have done hundreds of times before. The only difference is that now, your application will be part of the <strong>MCP ecosystem</strong>.</p>
<p>Think of it like this: As a developer, you're not switching careers. You're not abandoning your current skills. You're still doing what you've always done – writing logic, structuring data, managing APIs. The only shift is in <strong>where</strong> you're plugging that code in. Instead of just serving HTTP requests or returning React components, now your code will be used to feed context into LLMs. And that, right there, is the bridge into the AI world.</p>
<p>Let's be real: in today's world, just building yet another CRUD application isn't enough. If your app isn't deeply integrated into the AI ecosystem, it's going to get left behind. But if you understand concepts like MCP, and if you know how to build and expose structured context to any model, then you're not just a developer anymore. You're an AI-enabled developer!</p>
<p>You're building the infrastructure that connects real-world data to AI applications. And that's huge! That's why I truly believe this whole MCP ecosystem is going to explode in the coming months and years. I believe companies all over the world are going to start building and publishing their own MCP servers – just like how everyone now builds APIs or SDKs. Soon, we'll reach a point where people won't visit your company website to fill out a form or read static FAQs. They'll just ask a question inside ChatGPT**,** Claude**,** or Cursor, like:</p>
<p><em>"What are the pricing plans for logicBase Labs?"</em></p>
<p>And they'll get a response – not because those models are trained on your website, but because you've built an MCP server that gives them real-time, personalized, authenticated data.</p>
<p>So yes, now let's go ahead and build our first MCP server – quickly and in a way that's easy to follow. Because ultimately, this is where developers like you belong: bringing together the best of your existing skills and applying them inside the AI universe.</p>
<h2 id="heading-mcp-server-setup-and-integration">MCP Server Setup and Integration</h2>
<p>So, to build an MCP server, we've landed on the official GitHub repo page for <a target="_blank" href="https://github.com/modelcontextprotocol/typescript-sdk">MCP's TypeScript SDK</a>. Now, for those of you who don't know TypeScript, there's nothing to worry about. Because TypeScript is basically a superset of JavaScript. So even if you're not familiar with TypeScript, it's totally fine. You can write your code in plain JavaScript, and since every valid JavaScript code is also valid TypeScript, you're good to go.</p>
<p>And if you're a regular JavaScript developer, you'll find everything here familiar – just like you'd expect from any typical docs. They've provided a small, simple template for a TypeScript server. It's a single, minimal server setup, and that's exactly the template I would use to build my own server. Let's walk through the setup.</p>
<p>My project is a Node.js project. I've created a <code>server.js</code> file, and honestly, that's the only file I've used in this project. All the code is written inside that one file.</p>
<p>Step-by-step, here's what I did:</p>
<h4 id="heading-1-initialize-the-project">1. Initialize the project</h4>
<pre><code class="lang-bash">npm init
</code></pre>
<p>This creates the <code>package.json</code> file.</p>
<h4 id="heading-2-install-the-required-mcp-package">2. Install the required MCP package</h4>
<p>Run the install command (mentioned in the <a target="_blank" href="https://github.com/modelcontextprotocol/typescript-sdk">docs</a>).</p>
<pre><code class="lang-bash">npm install @modelcontextprotocol/sdk
</code></pre>
<h4 id="heading-3-import-and-create-the-mcp-server">3. Import and create the MCP server</h4>
<p>I imported <code>McpServer</code> from the installed package and then created a new instance using <code>new McpServer()</code>. You need to pass an object with a name and version:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { McpServer } <span class="hljs-keyword">from</span> <span class="hljs-string">"@modelcontextprotocol/sdk/server/mcp.js"</span>;

<span class="hljs-comment">// create the MCP server</span>
<span class="hljs-keyword">const</span> server = <span class="hljs-keyword">new</span> McpServer({
    name: <span class="hljs-string">"Sumit's Calendar"</span>,
    version: <span class="hljs-string">"1.0.0"</span>,
});
</code></pre>
<h4 id="heading-4-add-a-tool-function">4. Add a tool (function)</h4>
<p>Tools are the functions your AI client can invoke. I used <code>server.tool()</code> function from the SDK and passed three things:</p>
<ul>
<li><p>A meaningful name: <code>getMyCalendarDataByDate</code> so that my AI application can understand which tool to call</p>
</li>
<li><p>Input validation using <code>zod</code></p>
</li>
<li><p>An async callback function that fetches meeting data</p>
</li>
</ul>
<pre><code class="lang-typescript"><span class="hljs-comment">// register the tool to MCP</span>
server.tool(
    <span class="hljs-string">"getMyCalendarDataByDate"</span>,
    {
        date: z.string().refine(<span class="hljs-function">(<span class="hljs-params">val</span>) =&gt;</span> !<span class="hljs-built_in">isNaN</span>(<span class="hljs-built_in">Date</span>.parse(val)), {
            message: <span class="hljs-string">"Invalid date format. Please provide a valid date string."</span>,
        }),
    },
    <span class="hljs-keyword">async</span> ({ date }) =&gt; {
        <span class="hljs-keyword">return</span> {
            content: [
                {
                    <span class="hljs-keyword">type</span>: <span class="hljs-string">"text"</span>,
                    text: <span class="hljs-built_in">JSON</span>.stringify(<span class="hljs-keyword">await</span> getMyCalendarDataByDate(date)),
                },
            ],
        };
    }
);
</code></pre>
<p>The callback receives the validated date and uses it to call an async controller function called <code>getMyCalendarDataByDate</code> that fetches data from Google Calendar. Now we will write the function.</p>
<h4 id="heading-5-google-calendar-integration">5. Google Calendar Integration</h4>
<p>First we need to install the <code>googleapis</code> package with the below command in the terminal:</p>
<pre><code class="lang-bash">npm install googleapis
</code></pre>
<p>Then import <code>google</code> object from the installed the package.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { google } <span class="hljs-keyword">from</span> <span class="hljs-string">"googleapis"</span>;
</code></pre>
<p>Now let’s write the function <code>getMyCalendarDataByDate</code> and call the <code>google.calendar</code> method according to <a target="_blank" href="https://developers.google.com/workspace/calendar/api/quickstart/nodejs">Google Calendar API</a>. This <code>google.calendar()</code> method receives an object as parameter and we need to mention <code>version</code> and <code>auth</code> here. <code>version</code> is simply the Calendar API version number and <code>auth</code> is the Google API Public Key for authentication.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getMyCalendarDataByDate</span>(<span class="hljs-params">date</span>) </span>{
    <span class="hljs-keyword">const</span> calendar = google.calendar({
        version: <span class="hljs-string">"v3"</span>,
        auth: process.env.GOOGLE_PUBLIC_API_KEY,
    });
}
</code></pre>
<p>Here, you can see that I’ve used the Google API public key as an environment variable. So, we’ll create a <code>.env</code> file in the root of the project directory and add the following inside that file:</p>
<pre><code class="lang-plaintext">GOOGLE_PUBLIC_API_KEY=WRITE_YOUR_GOOGLE_PUBLIC_API_KEY
</code></pre>
<p>Don’t forget to replace with your own Google Public API Key. You can grab your public key from <a target="_blank" href="https://cloud.google.com/cloud-console">Google Cloud Console</a>.</p>
<p>Now we need to calculate the <code>start</code> and <code>end</code> of the given date (UTC) received as <code>string</code> in the <code>date</code> parameter of the <code>getMyCalendarDataByDate</code> function.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Calculate the start and end of the given date (UTC)</span>
<span class="hljs-keyword">const</span> start = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(date);
start.setUTCHours(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>);
<span class="hljs-keyword">const</span> end = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(start);
end.setUTCDate(end.getUTCDate() + <span class="hljs-number">1</span>);
</code></pre>
<p>Now it’s time to fetch the list of events from my Google Public Calendar. For that, according to Google Calendar API, we need to call the <code>calendar.events.list</code> function and pass necessary options to it:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> calendar.events.list({
    calendarId: process.env.CALENDAR_ID,
    timeMin: start.toISOString(),
    timeMax: end.toISOString(),
    maxResults: <span class="hljs-number">10</span>,
    singleEvents: <span class="hljs-literal">true</span>,
    orderBy: <span class="hljs-string">"startTime"</span>,
});
</code></pre>
<p>Here you can see, I have mentioned my Public Calendar ID using another environment variable called <code>CALENDAR_ID</code>. So go back to your .env file and set the new environment variable:</p>
<pre><code class="lang-plaintext">CALENDAR_ID=YOUR_OWN_PUBLIC_CALENDAR_ID
</code></pre>
<p>Just a quick note – your <code>CALENDAR_ID</code> will be simply your Google Email address, for example <code>someone@gmail.com</code>. Also don’t forget to make your calendar public, otherwise this example and API setup will not work.</p>
<p>To make your Google Calendar public, you need to adjust the calendar's sharing settings in Google Calendar on a computer. Navigate to the calendar you want to share, then find the "Access permissions for events" section and check the box labeled "Make available to public". You can then choose the level of access you want to grant others.</p>
<p>Here's a step-by-step guide:</p>
<ul>
<li><p>Go to <a target="_blank" href="https://calendar.google.com/">Google Calendar</a> on your computer.</p>
</li>
<li><p>Find the calendar you want to share under the "My calendars" section on the left side of the screen.</p>
</li>
<li><p>Click on the three dots (More) next to the calendar name and select "Settings and sharing".</p>
</li>
<li><p>Under "Access permissions for events," check the box next to "Make available to public".</p>
</li>
</ul>
<p>And for the <code>timeMin</code> and <code>timeMax</code> options I have used the <code>start</code> and <code>end</code> date time we just calculated above.</p>
<p>Now we will get the <code>events</code> array from <code>res.data.items</code> and then map through those events to get the final <code>meetings</code> array. We also need to handle blank array for no events.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> events = res.data.items || [];
<span class="hljs-keyword">const</span> meetings = events.map(<span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> start = event.start.dateTime || event.start.date;
    <span class="hljs-keyword">return</span> <span class="hljs-string">`<span class="hljs-subst">${event.summary}</span> at <span class="hljs-subst">${start}</span>`</span>;
});

<span class="hljs-keyword">if</span> (meetings.length &gt; <span class="hljs-number">0</span>) {
    <span class="hljs-keyword">return</span> {
        meetings,
    };
} <span class="hljs-keyword">else</span> {
    <span class="hljs-keyword">return</span> {
        meetings: [],
    };
}
</code></pre>
<p>Let’s do some error handling. We will simply push our above event fetching logic inside a <code>try/catch</code> block and handle error inside the <code>catch</code> block. So below is our updated code:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> calendar.events.list({
        calendarId: process.env.CALENDAR_ID,
        timeMin: start.toISOString(),
        timeMax: end.toISOString(),
        maxResults: <span class="hljs-number">10</span>,
        singleEvents: <span class="hljs-literal">true</span>,
        orderBy: <span class="hljs-string">"startTime"</span>,
    });

    <span class="hljs-keyword">const</span> events = res.data.items || [];
    <span class="hljs-keyword">const</span> meetings = events.map(<span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
        <span class="hljs-keyword">const</span> start = event.start.dateTime || event.start.date;
        <span class="hljs-keyword">return</span> <span class="hljs-string">`<span class="hljs-subst">${event.summary}</span> at <span class="hljs-subst">${start}</span>`</span>;
    });

    <span class="hljs-keyword">if</span> (meetings.length &gt; <span class="hljs-number">0</span>) {
        <span class="hljs-keyword">return</span> {
            meetings,
        };
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">return</span> {
            meetings: [],
        };
    }
} <span class="hljs-keyword">catch</span> (err) {
    <span class="hljs-keyword">return</span> {
        error: err.message,
    };
}
</code></pre>
<p>To run the server locally using <code>stdin</code>/<code>stdout</code>, I used the <code>stdioServerTransport()</code> function from the MCP package and passed it to the server's <code>start()</code> method. This part looks like:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> transport = stdioServerTransport();
server.start(transport);
</code></pre>
<p>Then I wrapped everything inside an async <code>init()</code> function to avoid top-level <code>await</code> and call the <code>init</code> function.</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">init</span>(<span class="hljs-params"></span>)</span>{
    <span class="hljs-keyword">const</span> transport = stdioServerTransport();
    server.start(transport);
}

init();
</code></pre>
<h4 id="heading-6-final-source-code">6. Final Source Code</h4>
<p>So below is the complete code for my <code>server.js</code> file:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { McpServer } <span class="hljs-keyword">from</span> <span class="hljs-string">"@modelcontextprotocol/sdk/server/mcp.js"</span>;
<span class="hljs-keyword">import</span> { StdioServerTransport } <span class="hljs-keyword">from</span> <span class="hljs-string">"@modelcontextprotocol/sdk/server/stdio.js"</span>;
<span class="hljs-keyword">import</span> dotenv <span class="hljs-keyword">from</span> <span class="hljs-string">"dotenv"</span>;
<span class="hljs-keyword">import</span> { google } <span class="hljs-keyword">from</span> <span class="hljs-string">"googleapis"</span>;
<span class="hljs-keyword">import</span> { z } <span class="hljs-keyword">from</span> <span class="hljs-string">"zod"</span>;

dotenv.config();

<span class="hljs-comment">// create the MCP server</span>
<span class="hljs-keyword">const</span> server = <span class="hljs-keyword">new</span> McpServer({
    name: <span class="hljs-string">"Sumit's Calendar"</span>,
    version: <span class="hljs-string">"1.0.0"</span>,
});

<span class="hljs-comment">// tool function</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getMyCalendarDataByDate</span>(<span class="hljs-params">date</span>) </span>{
    <span class="hljs-keyword">const</span> calendar = google.calendar({
        version: <span class="hljs-string">"v3"</span>,
        auth: process.env.GOOGLE_PUBLIC_API_KEY,
    });

    <span class="hljs-comment">// Calculate the start and end of the given date (UTC)</span>
    <span class="hljs-keyword">const</span> start = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(date);
    start.setUTCHours(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>);
    <span class="hljs-keyword">const</span> end = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(start);
    end.setUTCDate(end.getUTCDate() + <span class="hljs-number">1</span>);

    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> calendar.events.list({
            calendarId: process.env.CALENDAR_ID,
            timeMin: start.toISOString(),
            timeMax: end.toISOString(),
            maxResults: <span class="hljs-number">10</span>,
            singleEvents: <span class="hljs-literal">true</span>,
            orderBy: <span class="hljs-string">"startTime"</span>,
        });

        <span class="hljs-keyword">const</span> events = res.data.items || [];
        <span class="hljs-keyword">const</span> meetings = events.map(<span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
            <span class="hljs-keyword">const</span> start = event.start.dateTime || event.start.date;
            <span class="hljs-keyword">return</span> <span class="hljs-string">`<span class="hljs-subst">${event.summary}</span> at <span class="hljs-subst">${start}</span>`</span>;
        });

        <span class="hljs-keyword">if</span> (meetings.length &gt; <span class="hljs-number">0</span>) {
            <span class="hljs-keyword">return</span> {
                meetings,
            };
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-keyword">return</span> {
                meetings: [],
            };
        }
    } <span class="hljs-keyword">catch</span> (err) {
        <span class="hljs-keyword">return</span> {
            error: err.message,
        };
    }
}

<span class="hljs-comment">// register the tool to MCP</span>
server.tool(
    <span class="hljs-string">"getMyCalendarDataByDate"</span>,
    {
        date: z.string().refine(<span class="hljs-function">(<span class="hljs-params">val</span>) =&gt;</span> !<span class="hljs-built_in">isNaN</span>(<span class="hljs-built_in">Date</span>.parse(val)), {
            message: <span class="hljs-string">"Invalid date format. Please provide a valid date string."</span>,
        }),
    },
    <span class="hljs-keyword">async</span> ({ date }) =&gt; {
        <span class="hljs-keyword">return</span> {
            content: [
                {
                    <span class="hljs-keyword">type</span>: <span class="hljs-string">"text"</span>,
                    text: <span class="hljs-built_in">JSON</span>.stringify(<span class="hljs-keyword">await</span> getMyCalendarDataByDate(date)),
                },
            ],
        };
    }
);

<span class="hljs-comment">// set transport</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">init</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> transport = <span class="hljs-keyword">new</span> StdioServerTransport();
    <span class="hljs-keyword">await</span> server.connect(transport);
}

<span class="hljs-comment">// call the initialization</span>
init();
</code></pre>
<p>Then install the necessary <code>dotenv</code>, <code>googleapis</code>, and <code>zod</code> packages with the below command:</p>
<pre><code class="lang-bash">npm install dotenv googleapis zod
</code></pre>
<p>Now you can start the server with the command <code>node server.js</code> in your terminal and check whether everything is working properly or not. In case you get any warning to add a <code>type: “module”</code> line inside your <code>package.json</code> file, go ahead and do that. This warning is expected because we are using ES Module syntax for importing our packages instead of default Common JS syntax.</p>
<p>Finally, we are done with the coding part.</p>
<h4 id="heading-7-connecting-with-cursor-editor">7. Connecting with Cursor editor</h4>
<p>After setting up the server, I needed to register it inside the <strong>Cursor</strong> editor:</p>
<p>Start by opening Cursor Settings → Tools &amp; Integrations → New MCP Server.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750673140780/14ac470e-38e7-4cf4-bef8-823fc155c015.png" alt="How to connect MCP Server in cursor" class="image--center mx-auto" width="3840" height="2160" loading="lazy"></p>
<p>Inside the object, provide a new object with the below properties according to <a target="_blank" href="https://docs.cursor.com/context/model-context-protocol#manual-configuration">Cursor Client setup guide</a> mentioned in the <a target="_blank" href="https://docs.cursor.com/welcome">Cursor Docs</a>:</p>
<ul>
<li><p>A name: <code>Sumit's Calendar Data</code></p>
</li>
<li><p>Command: <code>node</code></p>
</li>
<li><p>Arguments: full path to <code>server.js</code></p>
</li>
<li><p>Environment variables: API key and Calendar ID</p>
</li>
</ul>
<p>Example:</p>
<pre><code class="lang-json">{
    mcpServers: {
        <span class="hljs-attr">"sumits-calendar-data"</span>: {
            command: <span class="hljs-string">"node"</span>,
            args: [<span class="hljs-string">"/full/path/to/project/server.js"</span>],
            env: {
                GOOGLE_API_KEY: <span class="hljs-string">"..."</span>,
                CALENDAR_ID: <span class="hljs-string">"..."</span>,
            },
        },
    },
}
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750673187700/9a07465a-0e7b-4bb7-9523-286dcf38373a.png" alt="How to connect MCP Server in Cursor" class="image--center mx-auto" width="3840" height="2160" loading="lazy"></p>
<p>Save and restart Cursor. The tool will now show as <strong>active (green)</strong>.</p>
<h4 id="heading-8-test-your-mcp-server">8. Test Your MCP Server</h4>
<p>Now, open the Cursor chat window and type:</p>
<p><em>"Do I have any meetings today?"</em></p>
<p>You'll see that:</p>
<ul>
<li><p>It detects the intent</p>
</li>
<li><p>Chooses the correct MCP tool</p>
</li>
<li><p>Passes today's date as input</p>
</li>
<li><p>MCP server returns structured data</p>
</li>
<li><p>The AI client responds naturally. In my case, I saved an event inside my Google Calendar on today’s date so it returned:</p>
</li>
</ul>
<p><em>"Yes, you have a meeting with Dr. Chuck at 4:00 PM."</em></p>
<p>It even works in other languages. If you ask the same question another language other than English, you still get the correct answer. If there are no meetings for a given date, for example if you write:</p>
<p><em>“Do I have any meeting tomorrow?”</em></p>
<p>It replies:</p>
<p><em>"No, you do not have any meetings scheduled for tomorrow."</em></p>
<p>So now your custom MCP server is fully working, feeding real data from Google Calendar into your AI editor.</p>
<p>This unlocks huge possibilities. Imagine the same approach with GitHub, Notion, internal dashboards, CRMs – anything. It all starts with building and wiring up your MCP server the right way.</p>
<p>Let me know if you would like to build one for your own project! And if this handbook was even a little bit helpful in getting your first MCP server up and running, I’d love to hear about it – it would be great inspiration for me to write more guides like this in the future.</p>
<h2 id="heading-summary">Summary</h2>
<p>You can find all the source code from this handbook in <a target="_blank" href="https://github.com/logicbaselabs/mcp-tutorial">this GitHub repository</a>. If it helped you in any way, consider giving it a star to show your support!</p>
<p>Also, if you found the handbook valuable, feel free to share it with others who might benefit from it. I’d really appreciate your thoughts – mention me on X <a target="_blank" href="https://x.com/sumit_analyzen">@sumit_analyzen</a>, watch my <a target="_blank" href="https://youtube.com/@logicBaseLabs">coding tutorials</a>, or simply <a target="_blank" href="https://www.linkedin.com/in/sumitanalyzen">connect with me on LinkedIn</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
