{"id":3769,"date":"2025-05-13T07:02:32","date_gmt":"2025-05-13T07:02:32","guid":{"rendered":"https:\/\/mailitics.com\/index.php\/2025\/05\/13\/how-i-finally-understood-mcp-and-got-it-working-irl-2\/"},"modified":"2025-05-13T07:02:32","modified_gmt":"2025-05-13T07:02:32","slug":"how-i-finally-understood-mcp-and-got-it-working-irl-2","status":"publish","type":"post","link":"https:\/\/mailitics.com\/index.php\/2025\/05\/13\/how-i-finally-understood-mcp-and-got-it-working-irl-2\/","title":{"rendered":"How I Finally Understood MCP\u200a\u2014\u200aand Got It Working in Real Life"},"content":{"rendered":"<p>    How I Finally Understood MCP\u200a\u2014\u200aand Got It Working in Real Life<br \/>\n \t<BR><br \/>\n<BR><\/BR><br \/>\n    <!-- no image --><br \/>\n \t<BR><br \/>\n<BR><\/BR><\/p>\n<div>\n<h1 class=\"wp-block-heading\">Table of Content<\/h1>\n<ol class=\"wp-block-list\">\n<li class=\"wp-block-list-item\"><a href=\"https:\/\/towardsdatascience.com\/#section1\" target=\"_blank\" rel=\"noreferrer noopener\"><mdspan datatext=\"el1745428604481\" class=\"mdspan-comment\">Introduction<\/mdspan>: Why I Wrote This<\/a><\/li>\n<li class=\"wp-block-list-item\"><a href=\"https:\/\/towardsdatascience.com\/#section2\" target=\"_blank\" rel=\"noreferrer noopener\">The Evolution of Tool Integration with LLMs<\/a><\/li>\n<li class=\"wp-block-list-item\"><a href=\"https:\/\/towardsdatascience.com\/#section3\" target=\"_blank\" rel=\"noreferrer noopener\">What Is Model Context Protocol (MCP), Really?<\/a><\/li>\n<li class=\"wp-block-list-item\">\n<a href=\"https:\/\/towardsdatascience.com\/#section4\" target=\"_blank\" rel=\"noreferrer noopener\">Wait, MCP sounds like RAG\u2026 but is it?<\/a><\/p>\n<ol class=\"wp-block-list\">\n<li class=\"wp-block-list-item\"><a href=\"https:\/\/towardsdatascience.com\/#section4-1\" target=\"_blank\" rel=\"noreferrer noopener\">In an MCP-based setup<\/a><\/li>\n<li class=\"wp-block-list-item\"><a href=\"https:\/\/towardsdatascience.com\/#section4-2\" target=\"_blank\" rel=\"noreferrer noopener\">In a traditional RAG system<\/a><\/li>\n<li class=\"wp-block-list-item\"><a href=\"https:\/\/towardsdatascience.com\/#section4-3\" target=\"_blank\" rel=\"noreferrer noopener\">Traditional RAG Implementation<\/a><\/li>\n<li class=\"wp-block-list-item\"><a href=\"https:\/\/towardsdatascience.com\/#section4-4\" target=\"_blank\" rel=\"noreferrer noopener\">MCP Implementation<\/a><\/li>\n<\/ol>\n<\/li>\n<li class=\"wp-block-list-item\"><a href=\"https:\/\/towardsdatascience.com\/#section5\" target=\"_blank\" rel=\"noreferrer noopener\">Quick recap!<\/a><\/li>\n<li class=\"wp-block-list-item\"><a href=\"https:\/\/towardsdatascience.com\/#section6\" target=\"_blank\" rel=\"noreferrer noopener\">Core Capabilities of an MCP Server<\/a><\/li>\n<li class=\"wp-block-list-item\">\n<a href=\"https:\/\/towardsdatascience.com\/#section7\" target=\"_blank\" rel=\"noreferrer noopener\">Real-World Example: Claude Desktop + MCP (Pre-built Servers<\/a>)<\/li>\n<li class=\"wp-block-list-item\"><a href=\"https:\/\/towardsdatascience.com\/#section8\" target=\"_blank\" rel=\"noreferrer noopener\">Build Your Own: Custom MCP Server from Scratch <\/a><\/li>\n<li class=\"wp-block-list-item\"><a href=\"https:\/\/towardsdatascience.com\/#section9\" target=\"_blank\" rel=\"noreferrer noopener\"><img data-recalc-dims=\"1\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/s.w.org\/images\/core\/emoji\/15.0.3\/72x72\/1f389.png?ssl=1\" alt=\"\ud83c\udf89\" class=\"wp-smiley\" style=\"height: 1em; max-height: 1em;\"> Congrats, You\u2019ve Mastered MCP!<\/a><\/li>\n<li class=\"wp-block-list-item\"><a href=\"https:\/\/towardsdatascience.com\/#section10\" target=\"_blank\" rel=\"noreferrer noopener\">References<\/a><\/li>\n<\/ol>\n<hr class=\"wp-block-separator has-alpha-channel-opacity is-style-dotted\">\n<h2 class=\"wp-block-heading\" id=\"section1\">\n<mdspan datatext=\"el1745428604481\" class=\"mdspan-comment\">Introduction<\/mdspan>: Why I Wrote This<\/h2>\n<p class=\"wp-block-paragraph\" id=\"59cd\">I will be honest. When I first saw the term \u201cModel Context Protocol (<a href=\"https:\/\/towardsdatascience.com\/tag\/mcp\/\" title=\"mcp\">mcp<\/a>),\u201d I did what most developers do when confronted with yet another new acronym: I skimmed a tutorial, saw some JSON, and quietly moved on. \u201cToo abstract,\u201d I thought. Fast-forward to when I actually tried to integrate some custom tools with Claude Desktop\u2014 something that needed memory or access to external tools \u2014 and suddenly, MCP wasn\u2019t just relevant. It was essential.<\/p>\n<p class=\"wp-block-paragraph\" id=\"701a\">The problem? None of the tutorials I came across felt beginner-friendly. Most jumped straight into building a custom MCP server without explaining in details<em> why<\/em> you\u2019d need a server in the first place \u2014 let alone mentioning that prebuilt MCP servers already exist and work out of the box. So, I decided to learn it from the ground up.<\/p>\n<p class=\"wp-block-paragraph\" id=\"d85c\">I read everything I could, experimented with both prebuilt and custom servers, integrated it with Claude Desktop and tested whether I could explain it to my friends \u2014people with zero prior context. When I finally got the nod from them, I knew I could break it down for anyone, even if you\u2019ve never heard of MCP until five minutes ago.<\/p>\n<p class=\"wp-block-paragraph\" id=\"2942\">This article breaks down what MCP is, why it matters, and how it compares to other popular architectures like RAG. We\u2019ll go from \u201cwhat even is this?\u201d to spinning up your own working Claude integration \u2014 no prior MCP knowledge required. If you\u2019ve ever struggled to get your AI model to feel a little less like a goldfish, this is for you.<\/p>\n<h2 class=\"wp-block-heading\" id=\"section2\">The Evolution of Tool Integration with LLMs<\/h2>\n<p class=\"wp-block-paragraph\">Before diving into MCP, let\u2019s understand the progression of how we connect Large Language Models (LLMs) to external tools and data:<\/p>\n<figure class=\"wp-block-image alignwide size-large\"><img data-recalc-dims=\"1\" height=\"501\" width=\"1024\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/contributor.insightmediagroup.io\/wp-content\/uploads\/2025\/04\/image-78-1024x501.png?resize=1024%2C501&#038;ssl=1\" alt=\"\" class=\"wp-image-601909\"><figcaption class=\"wp-element-caption\">Image by author<\/figcaption><\/figure>\n<ol class=\"wp-block-list\">\n<li class=\"wp-block-list-item\">\n<strong>Standalone LLMs<\/strong>: Initially, models like GPT and Claude operated in isolation, relying solely on their training data. They couldn\u2019t access real-time information or interact with external systems.<\/li>\n<li class=\"wp-block-list-item\">\n<strong>Tool Binding:<\/strong> As LLMs advanced, developers created methods to \u201cbind\u201d tools directly to models. For example, with LangChain or similar frameworks, you could do something like:<\/li>\n<\/ol>\n<pre class=\"wp-block-prismatic-blocks .wp-block-prismatic-blocks .token.keyword { color: #07a; } .token.string #690; .token.function #dd4a68; .token.decorator #998;\"><code class=\"language-python\">llm = ChatAnthropic()\naugmented_llm = llm.bind_tools([search_tool, calculator_tool])<\/code><\/pre>\n<p class=\"wp-block-paragraph\" id=\"42fd\">This works well for individual scripts but doesn\u2019t scale easily across applications. <strong><em>Why?<\/em><\/strong> Because tool binding in frameworks like LangChain is typically designed around <strong>single-session, stateless interactions<\/strong>, meaning every time you spin up a new agent or function call, you\u2019re often re-defining which tools it can access. There\u2019s no centralized way to manage tools across multiple interfaces or user contexts.<\/p>\n<p class=\"wp-block-paragraph\" id=\"ca7f\"><strong>3. Application Integration Challenge<\/strong>: The real complexity arises when you want to integrate tools with AI-powered applications like IDEs (Cursor, VS Code), chat interfaces (Claude Desktop), or other productivity tools. Each application would need custom connectors for every possible tool or data source, creating a tangled web of integrations.<\/p>\n<p class=\"wp-block-paragraph\" id=\"b75c\">This is where MCP enters the picture \u2014 providing a standardized layer of abstraction for connecting AI applications to external tools and data sources.<\/p>\n<h2 class=\"wp-block-heading\" id=\"section3\">What Is Model Context Protocol (MCP), Really?<\/h2>\n<p class=\"wp-block-paragraph\" id=\"8988\">Let\u2019s break it down:<\/p>\n<ul class=\"wp-block-list\">\n<li class=\"wp-block-list-item\">\n<strong>Model<\/strong>: The LLM at the heart of your application \u2014 GPT, Claude, whatever. It\u2019s a powerful reasoning engine but limited by what it was trained on and how much context it can hold.<\/li>\n<li class=\"wp-block-list-item\">\n<strong>Context<\/strong>: The extra information your model needs to do its job \u2014 documents, search results, user preferences, recent history. Context extends the model\u2019s capabilities beyond its training set.<\/li>\n<li class=\"wp-block-list-item\">\n<strong>Protocol<\/strong>: A standardized way of communicating between components. Think of it as a common language that lets your model interact with tools and data sources in a predictable way.<\/li>\n<\/ul>\n<p class=\"wp-block-paragraph\" id=\"5f26\">Put those three together, and <strong>MCP<\/strong> becomes a framework that connects models to contextual information and tools through a consistent, modular, and interoperable interface.<\/p>\n<p class=\"wp-block-paragraph\" id=\"c5be\">Much like HTTP enabled the web by standardizing how browsers talk to servers, MCP standardizes how AI applications interact with external data and capabilities.<\/p>\n<hr class=\"wp-block-separator has-alpha-channel-opacity is-style-dotted\">\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><strong>Pro tip! <\/strong>An easy way to visualize MCP is to think of it like <strong>tool binding for the entire AI stack<\/strong>, not just a single agent. <strong>That\u2019s why <\/strong><a href=\"https:\/\/docs.anthropic.com\/en\/docs\/agents-and-tools\/mcp\" target=\"_blank\" rel=\"noreferrer noopener\"><strong>Anthropic describes MCP<\/strong><\/a><strong> as \u201ca USB-C port for AI applications.\u201d<\/strong><\/p>\n<\/blockquote>\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" height=\"858\" width=\"1024\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/contributor.insightmediagroup.io\/wp-content\/uploads\/2025\/04\/image-79-1024x858.png?resize=1024%2C858&#038;ssl=1\" alt=\"\" class=\"wp-image-601910\"><figcaption class=\"wp-element-caption\">Image by author, inspired by <a href=\"https:\/\/www.youtube.com\/watch?v=CDjjaTALI68&amp;t=467s\" target=\"_blank\" rel=\"noreferrer noopener\">Understanding MCP From Scratch<\/a> by LangChain<\/figcaption><\/figure>\n<hr class=\"wp-block-separator has-alpha-channel-opacity is-style-dotted\">\n<h2 class=\"wp-block-heading\" id=\"section4\">Wait, MCP sounds like RAG\u2026 but is it?<\/h2>\n<p class=\"wp-block-paragraph\" id=\"4eb0\">A lot of people ask, \u201cHow is this different from RAG?\u201d Great question.<\/p>\n<p class=\"wp-block-paragraph\" id=\"d31e\">At a glance, both MCP and RAG aim to solve the same problem: give language models access to relevant, external information. But how they do it \u2014 and how maintainable they are \u2014 differs significantly.<\/p>\n<h3 class=\"wp-block-heading\" id=\"section4-1\">In an MCP-based setup<\/h3>\n<ul class=\"wp-block-list\">\n<li class=\"wp-block-list-item\">Your AI app (host\/client) connects to an MCP document server<\/li>\n<li class=\"wp-block-list-item\">You interact with context using a standardized protocol<\/li>\n<li class=\"wp-block-list-item\">You can add new documents or tools without modifying the app<\/li>\n<li class=\"wp-block-list-item\">Everything works via the same interface, consistently<\/li>\n<\/ul>\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" height=\"1011\" width=\"1024\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/contributor.insightmediagroup.io\/wp-content\/uploads\/2025\/04\/image-80-1024x1011.png?resize=1024%2C1011&#038;ssl=1\" alt=\"\" class=\"wp-image-601911\"><figcaption class=\"wp-element-caption\">Image by author, inspired by <a href=\"https:\/\/modelcontextprotocol.io\/introduction\" target=\"_blank\" rel=\"noreferrer noopener\">MCP Documentation<\/a>.<\/figcaption><\/figure>\n<h3 class=\"wp-block-heading\" id=\"section4-2\">In a traditional RAG system<\/h3>\n<ul class=\"wp-block-list\">\n<li class=\"wp-block-list-item\">Your app manually builds and queries a vector database<\/li>\n<li class=\"wp-block-list-item\">You often need custom embedding logic, retrievers, and loaders<\/li>\n<li class=\"wp-block-list-item\">Adding new sources means rewriting part of your app code<\/li>\n<li class=\"wp-block-list-item\">Every integration is bespoke, tightly coupled to your app logic<\/li>\n<\/ul>\n<p class=\"wp-block-paragraph\" id=\"e2da\"><strong>The key distinction is abstraction: <\/strong>The <em>Protocol<\/em> in <a href=\"https:\/\/towardsdatascience.com\/tag\/model-context-protocol\/\" title=\"Model Context Protocol\">Model Context Protocol<\/a> is nothing but a <em>standardized abstraction layer<\/em> that defines <strong>bidirectional communication<\/strong> between MCP Client\/Host and MCP Servers.<\/p>\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" height=\"725\" width=\"1024\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/contributor.insightmediagroup.io\/wp-content\/uploads\/2025\/04\/image-81-1024x725.png?resize=1024%2C725&#038;ssl=1\" alt=\"\" class=\"wp-image-601912\"><figcaption class=\"wp-element-caption\">Image by author, inspired by <a href=\"https:\/\/modelcontextprotocol.io\/introduction\" target=\"_blank\" rel=\"noreferrer noopener\">MCP Documentation<\/a>.<\/figcaption><\/figure>\n<p class=\"wp-block-paragraph\" id=\"4b25\">MCP gives your app the ability to ask, \u201cGive me information about X,\u201d without knowing <em>how<\/em> that info is stored or retrieved. RAG systems require your app to manage all of that.<\/p>\n<p class=\"wp-block-paragraph\" id=\"eade\"><strong>With MCP, your application logic stays the same, even as your document sources evolve.<\/strong><\/p>\n<p class=\"wp-block-paragraph\" id=\"bbbb\">Let\u2019s look at some high-level codes to see how these approaches differ:<\/p>\n<h3 class=\"wp-block-heading\" id=\"section4-3\">Traditional RAG Implementation<\/h3>\n<p class=\"wp-block-paragraph\" id=\"dc3a\">In a traditional RAG implementation, your application code directly manages connections to document sources:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\"># Hardcoded vector store logic\nvectorstore = FAISS.load_local(\"store\/embeddings\")\nretriever = vectorstore.as_retriever()\nresponse = retriever.invoke(\"query about LangGraph\")<\/code><\/pre>\n<p class=\"wp-block-paragraph\">With tool binding, you define tools and bind them to an LLM, but still need to modify the tool implementation to incorporate new data sources. You still need to update the tool implementation when your backend changes.<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\">@tool\ndef search_docs(query: str):\n    return search_vector_store(query)<\/code><\/pre>\n<h3 class=\"wp-block-heading\" id=\"section4-4\">MCP Implementation<\/h3>\n<p class=\"wp-block-paragraph\" id=\"bd5d\">With MCP, your application connects to a standardized interface, and the server handles the specifics of document sources:<\/p>\n<pre class=\"wp-block-prismatic-blocks .wp-block-prismatic-blocks .token.keyword { color: #07a; } .token.string #690; .token.function #dd4a68; .token.decorator #998;\"><code class=\"language-python\"># MCP Client\/Host: Client\/Host stays the same\n\n# MCP Server: Define your MCP server\n# Import necessary libraries\nfrom typing import Any\nfrom mcp.server.fastmcp import FastMCP\n\n# Initialize FastMCP server\nmcp = FastMCP(\"your-server\")\n\n# Implement your server's tools\n@mcp.tool()\nasync def example_tool(param1: str, param2: int) -&gt; str:\n    \"\"\"An example tool that demonstrates MCP functionality.\n    \n    Args:\n        param1: First parameter description\n        param2: Second parameter description\n    \n    Returns:\n        A string result from the tool execution\n    \"\"\"\n    # Tool implementation\n    result = f\"Processed {param1} with value {param2}\"\n    return result\n\n# Example of adding a resource (optional)\n@mcp.resource()\nasync def get_example_resource() -&gt; bytes:\n    \"\"\"Provides example data as a resource.\n    \n    Returns:\n        Binary data that can be read by clients\n    \"\"\"\n    return b\"Example resource data\"\n\n# Example of adding a prompt template (optional)\nmcp.add_prompt(\n    \"example-prompt\",\n    \"This is a template for {{purpose}}. You can use it to {{action}}.\"\n)\n\n# Run the server\nif __name__ == \"__main__\":\n    mcp.run(transport=\"stdio\")<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Then, you configure the host or client (like Claude Desktop) to use the server by updating its configuration file.<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-json\">{\n    \"mcpServers\": {\n        \"your-server\": {\n            \"command\": \"uv\",\n            \"args\": [\n                \"--directory\",\n                \"\/ABSOLUTE\/PATH\/TO\/PARENT\/FOLDER\/your-server\",\n                \"run\",\n                \"your-server.py\"\n            ]\n        }\n    }\n}<\/code><\/pre>\n<hr class=\"wp-block-separator has-alpha-channel-opacity is-style-dotted\">\n<p class=\"wp-block-paragraph\" id=\"3730\">If you change where or how the resources\/documents are stored, <strong>you update the server \u2014 not the client.<\/strong><\/p>\n<p class=\"wp-block-paragraph\" id=\"bd8f\"><strong><em>That\u2019s the magic of abstraction.<\/em><\/strong><\/p>\n<p class=\"wp-block-paragraph\" id=\"0d22\">And for many use cases \u2014 especially in production environments like IDE extensions or commercial applications \u2014 you <em>can\u2019t<\/em> touch the client code at all. MCP\u2019s decoupling is more than just a nice-to-have: it\u2019s a necessity. It isolates the application code so that only the server-side logic (tools, data sources, or embeddings) needs to evolve. The host application remains untouched. This enables rapid iteration and experimentation without risking regression or violating application constraints.<\/p>\n<hr class=\"wp-block-separator has-alpha-channel-opacity is-style-dotted\">\n<h2 class=\"wp-block-heading\" id=\"section5\">Quick recap!<\/h2>\n<p class=\"wp-block-paragraph\">Hopefully, by now, it\u2019s clear why MCP actually matters.<\/p>\n<p class=\"wp-block-paragraph\" id=\"5b16\">Imagine you\u2019re building an AI assistant that needs to:<\/p>\n<ul class=\"wp-block-list\">\n<li class=\"wp-block-list-item\">Tap into a knowledge base<\/li>\n<li class=\"wp-block-list-item\">Execute code or scripts<\/li>\n<li class=\"wp-block-list-item\">Keep track of past user conversations<\/li>\n<\/ul>\n<p class=\"wp-block-paragraph\" id=\"8b6d\">Without MCP, you\u2019re stuck writing custom glue code for every single integration. Sure, it works \u2014 until it doesn\u2019t. It\u2019s fragile, messy, and a nightmare to maintain at scale.<\/p>\n<p class=\"wp-block-paragraph\" id=\"9ce7\"><strong>MCP fixes this<\/strong> by acting as a universal adapter between your model and the outside world. You can plug in new tools or data sources without rewriting your model logic. That means <strong>faster iteration, cleaner code, fewer bugs<\/strong>, and AI applications that are actually modular and maintainable.<\/p>\n<p class=\"wp-block-paragraph\" id=\"f4e4\">And I hope you were paying attention when I said <strong>MCP enables bidirectional communication<\/strong> between the host (client) and the server \u2014 because this unlocks one of MCP\u2019s most powerful use cases: <strong>persistent memory<\/strong>.<\/p>\n<p class=\"wp-block-paragraph\" id=\"1a47\">Out of the box, LLMs are goldfish. They forget everything unless you manually stuff the entire history into the context window. But with MCP, you can:<\/p>\n<ul class=\"wp-block-list\">\n<li class=\"wp-block-list-item\">Store and retrieve past interactions<\/li>\n<li class=\"wp-block-list-item\">Keep track of long-term user preferences<\/li>\n<li class=\"wp-block-list-item\">Build assistants that actually \u201cremember\u201d full projects or ongoing sessions<\/li>\n<\/ul>\n<p class=\"wp-block-paragraph\" id=\"7331\">No more clunky prompt-chaining hacks or fragile memory workarounds. <strong>MCP gives your model a brain that lasts longer than a single chat.<\/strong><\/p>\n<h2 class=\"wp-block-heading\" id=\"section6\">Core Capabilities of an MCP Server<\/h2>\n<p class=\"wp-block-paragraph\" id=\"4f24\">With all that in mind, it\u2019s pretty clear: <strong>the MCP server is the MVP of the whole protocol.<\/strong><\/p>\n<p class=\"wp-block-paragraph\" id=\"4c1a\">It\u2019s the central hub that defines the capabilities your model can actually use. There are three main types:<\/p>\n<ul class=\"wp-block-list\">\n<li class=\"wp-block-list-item\">\n<strong>Resources:<\/strong> Think of these as external data sources \u2014 PDFs, APIs, databases. The model can pull them in for context, but it can\u2019t change them. Read-only.<\/li>\n<li class=\"wp-block-list-item\">\n<strong>Tools:<\/strong> These are the actual functions the model can call \u2014 run code, search the web, generate summaries, you name it.<\/li>\n<li class=\"wp-block-list-item\">\n<strong>Prompts:<\/strong> Predefined templates that guide the model\u2019s behavior or structure its responses. Like giving it a playbook.<\/li>\n<\/ul>\n<p class=\"wp-block-paragraph\" id=\"ed1d\">What makes MCP powerful is that all of these are exposed through a <strong>single, consistent protocol<\/strong>. That means the model can request, invoke, and incorporate them without needing custom logic for each one. Just plug into the MCP server, and everything\u2019s ready to go.<\/p>\n<h2 class=\"wp-block-heading\" id=\"section7\">Real-World Example: Claude Desktop + MCP (Pre-built Servers)<\/h2>\n<p class=\"wp-block-paragraph\" id=\"9660\">Out of the box, <strong>Anthropic offers a bunch of pre-built MCP servers<\/strong> you can plug into your AI apps \u2014 things like Claude Desktop, Cursor, and more. Setup is super quick and painless.<\/p>\n<p class=\"wp-block-paragraph\" id=\"4d0c\">For the full list of available servers, head over to the <a href=\"https:\/\/github.com\/modelcontextprotocol\/servers\" rel=\"noreferrer noopener\" target=\"_blank\">MCP Servers Repository.<\/a> It\u2019s your buffet of ready-to-use integrations.<\/p>\n<p class=\"wp-block-paragraph\" id=\"09d4\">In this section, I\u2019ll walk you through a practical example: <strong>extending Claude Desktop<\/strong> so it can read from your computer\u2019s file system, write new files, move them around, and even search through them.<\/p>\n<p class=\"wp-block-paragraph\" id=\"b380\">This walkthrough is based on the <a href=\"https:\/\/modelcontextprotocol.io\/quickstart\/user\" rel=\"noreferrer noopener\" target=\"_blank\">Quickstart<\/a> guide from the official docs, but honestly, that guide skips a few key details \u2014 especially if you\u2019ve never touched these settings before. So I\u2019m filling in the gaps and sharing the extra tips I picked up along the way to save you the headache.<\/p>\n<h3 class=\"wp-block-heading\" id=\"3092\">1. Download Claude Desktop<\/h3>\n<p class=\"wp-block-paragraph\" id=\"bd78\">First things first \u2014 grab <a href=\"https:\/\/claude.ai\/download\" rel=\"noreferrer noopener\" target=\"_blank\"><strong>Claude Desktop<\/strong><\/a>. Choose the version for <strong>macOS<\/strong> or <strong>Windows<\/strong> (sorry Linux folks, no support just yet).<\/p>\n<p class=\"wp-block-paragraph\" id=\"8f0a\">Follow the installation steps as prompted.<\/p>\n<p class=\"wp-block-paragraph\" id=\"bdc7\">Already have it installed? Make sure you\u2019re on the latest version by clicking the <strong>Claude menu<\/strong> on your computer and selecting <strong>\u201cCheck for Updates\u2026\u201d<\/strong><\/p>\n<h3 class=\"wp-block-heading\" id=\"e692\">2. Check the Prerequisites<\/h3>\n<p class=\"wp-block-paragraph\" id=\"0d23\">You\u2019ll need <strong>Node.js<\/strong> installed on your machine to get this running smoothly.<\/p>\n<p class=\"wp-block-paragraph\" id=\"0f94\">To check if you already have Node installed:<\/p>\n<ul class=\"wp-block-list\">\n<li class=\"wp-block-list-item\">\n<strong>On macOS<\/strong>: Open the <strong>Terminal<\/strong> from your Applications folder.<\/li>\n<li class=\"wp-block-list-item\">\n<strong>On Windows<\/strong>: Press <code>Windows + R<\/code>, type <code>cmd<\/code>, and hit Enter.<\/li>\n<li class=\"wp-block-list-item\">Then run the following command in your terminal:<\/li>\n<\/ul>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\">node --version<\/code><\/pre>\n<p class=\"wp-block-paragraph\" id=\"3073\">If you see a version number, you\u2019re good to go. If not, head over to <a href=\"https:\/\/nodejs.org\/\" rel=\"noreferrer noopener\" target=\"_blank\">nodejs.org<\/a> and install the latest <strong>LTS version<\/strong>.<\/p>\n<h3 class=\"wp-block-heading\" id=\"c639\">3. Enable Developer Mode<\/h3>\n<p class=\"wp-block-paragraph\" id=\"204c\">Open Claude Desktop and click on the \u201cClaude\u201d menu in the top-left corner of your screen. From there, select <strong>Help<\/strong>.<\/p>\n<p class=\"wp-block-paragraph\" id=\"e478\">On macOS, it should look something like this:<\/p>\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" height=\"478\" width=\"1024\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/contributor.insightmediagroup.io\/wp-content\/uploads\/2025\/04\/image-82-1024x478.png?resize=1024%2C478&#038;ssl=1\" alt=\"\" class=\"wp-image-601913\"><figcaption class=\"wp-element-caption\">Image by author<\/figcaption><\/figure>\n<p class=\"wp-block-paragraph\" id=\"0bf3\">From the drop-down menu, select <strong>\u201cEnable Developer Mode.\u201d<\/strong><\/p>\n<p class=\"wp-block-paragraph\" id=\"9a80\">If you\u2019ve already enabled it before, it won\u2019t show up again \u2014 but if this is your first time, it should be right there in the list.<\/p>\n<p class=\"wp-block-paragraph\" id=\"0287\">Once <strong>Developer Mode<\/strong> is turned on:<\/p>\n<ol class=\"wp-block-list\">\n<li class=\"wp-block-list-item\">Click on <strong>\u201cClaude\u201d<\/strong> in the top-left menu again.<\/li>\n<li class=\"wp-block-list-item\">Select <strong>\u201cSettings.\u201d<\/strong>\n<\/li>\n<li class=\"wp-block-list-item\">A new pop-up window will appear \u2014 look for the <strong>\u201cDeveloper\u201d<\/strong> tab in the left-hand navigation bar. That\u2019s where all the good stuff lives.<\/li>\n<\/ol>\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" height=\"724\" width=\"1024\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/contributor.insightmediagroup.io\/wp-content\/uploads\/2025\/04\/image-83-1024x724.png?resize=1024%2C724&#038;ssl=1\" alt=\"\" class=\"wp-image-601914\"><figcaption class=\"wp-element-caption\">Image by author<\/figcaption><\/figure>\n<h3 class=\"wp-block-heading\" id=\"c1e5\">4. Set Up the Configuration File<\/h3>\n<p class=\"wp-block-paragraph\" id=\"1f1b\">Still in the <strong>Developer<\/strong> settings, click on <strong>\u201cEdit Config.\u201d<\/strong><\/p>\n<p class=\"wp-block-paragraph\" id=\"cd01\">This will create a configuration file if one doesn\u2019t already exist and open it directly in your file system.<\/p>\n<p class=\"wp-block-paragraph\" id=\"8508\">The file location depends on your OS:<\/p>\n<ul class=\"wp-block-list\">\n<li class=\"wp-block-list-item\">\n<strong>macOS:<\/strong> <code>~\/Library\/Application Support\/Claude\/claude_desktop_config.json<\/code>\n<\/li>\n<li class=\"wp-block-list-item\">\n<strong>Windows:<\/strong> <code>%APPDATA%Claudeclaude_desktop_config.json<\/code>\n<\/li>\n<\/ul>\n<p class=\"wp-block-paragraph\" id=\"f594\">This is where you\u2019ll define the servers and capabilities you want Claude to use \u2014 so keep this file open, we\u2019ll be editing it next.<\/p>\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" height=\"692\" width=\"1024\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/contributor.insightmediagroup.io\/wp-content\/uploads\/2025\/04\/image-84-1024x692.png?resize=1024%2C692&#038;ssl=1\" alt=\"\" class=\"wp-image-601915\"><figcaption class=\"wp-element-caption\">Image by author<\/figcaption><\/figure>\n<p class=\"wp-block-paragraph\" id=\"0eff\">Open the config file (<code>claude_desktop_config.json<\/code>) in any text editor. Replace its contents with the following, depending on your OS:<\/p>\n<h4 class=\"wp-block-heading\" id=\"e2e9\">For macOS:<\/h4>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-json\">{\n  \"mcpServers\": {\n    \"filesystem\": {\n      \"command\": \"npx\",\n      \"args\": [\n        \"-y\",\n        \"@modelcontextprotocol\/server-filesystem\",\n        \"\/Users\/username\/Desktop\",\n        \"\/Users\/username\/Downloads\"\n      ]\n    }\n  }\n}<\/code><\/pre>\n<h4 class=\"wp-block-heading\">For Windows:<\/h4>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-json\">{\n  \"mcpServers\": {\n    \"filesystem\": {\n      \"command\": \"npx\",\n      \"args\": [\n        \"-y\",\n        \"@modelcontextprotocol\/server-filesystem\",\n        \"C:\\Users\\username\\Desktop\",\n        \"C:\\Users\\username\\Downloads\"\n      ]\n    }\n  }\n}<\/code><\/pre>\n<p class=\"wp-block-paragraph\"><strong>Make sure to replace <\/strong><code>\"username\"<\/code><strong> with your actual system username.<\/strong> The paths listed here should point to valid folders on your machine\u2014this setup gives Claude access to your <strong>Desktop<\/strong> and <strong>Downloads<\/strong>, but you can add more paths if needed.<\/p>\n<h4 class=\"wp-block-heading\" id=\"836f\">What This Does<\/h4>\n<p class=\"wp-block-paragraph\" id=\"f050\">This config tells <strong>Claude Desktop<\/strong> to automatically start an MCP server called <code>\"filesystem\"<\/code> every time the app launches. That server runs using <code>npx<\/code> and spins up <code>@modelcontextprotocol\/server-filesystem<\/code>, which is what lets Claude interact with your file system\u2014read, write, move files, search directories, etc.<\/p>\n<h4 class=\"wp-block-heading\" id=\"8443\">\n<img data-recalc-dims=\"1\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/s.w.org\/images\/core\/emoji\/15.0.3\/72x72\/26a0.png?ssl=1\" alt=\"\u26a0\" class=\"wp-smiley\" style=\"height: 1em; max-height: 1em;\"> Command Privileges<\/h4>\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\" id=\"bed8\">Just a heads-up: Claude will run these commands with your user account\u2019s permissions, meaning it can access and modify local files. Only add commands to the config file if you understand and trust the server you\u2019re hooking up \u2014 no random packages from the internet!<\/p>\n<\/blockquote>\n<h3 class=\"wp-block-heading\" id=\"32ad\">5. Restart Claude<\/h3>\n<p class=\"wp-block-paragraph\" id=\"9980\">Once you\u2019ve updated and saved your configuration file, <strong>restart Claude Desktop<\/strong> to apply the changes.<\/p>\n<p class=\"wp-block-paragraph\" id=\"094a\">After it boots up, you should see a <strong>hammer icon<\/strong> in the bottom-left corner of the input box. That\u2019s your signal that the developer tools \u2014 and your custom MCP server \u2014 are up and running.<\/p>\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" height=\"369\" width=\"1024\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/contributor.insightmediagroup.io\/wp-content\/uploads\/2025\/04\/image-85-1024x369.png?resize=1024%2C369&#038;ssl=1\" alt=\"\" class=\"wp-image-601916\"><figcaption class=\"wp-element-caption\">Image by author<\/figcaption><\/figure>\n<p class=\"wp-block-paragraph\">After clicking the <strong>hammer icon<\/strong>, you should see the list of tools exposed by the <strong>Filesystem MCP Server<\/strong> \u2014 things like reading files, writing files, searching directories, and so on.<\/p>\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" height=\"1024\" width=\"792\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/contributor.insightmediagroup.io\/wp-content\/uploads\/2025\/04\/image-86-792x1024.png?resize=792%2C1024&#038;ssl=1\" alt=\"\" class=\"wp-image-601917\"><figcaption class=\"wp-element-caption\">Image by author<\/figcaption><\/figure>\n<p class=\"wp-block-paragraph\" id=\"b5dd\">If you don\u2019t see your server listed or nothing shows up, don\u2019t worry. Jump over to the <a href=\"https:\/\/modelcontextprotocol.io\/quickstart\/user#troubleshooting\" rel=\"noreferrer noopener\" target=\"_blank\"><strong>Troubleshooting<\/strong><\/a> section in the official documentation for some quick debugging tips to get things back on track.<\/p>\n<h3 class=\"wp-block-heading\" id=\"61a1\">6. Try It Out!<\/h3>\n<p class=\"wp-block-paragraph\" id=\"000a\">Now that everything\u2019s set up, you can start chatting with Claude about your file system \u2014 <strong>and it should know when to call the right tools<\/strong>.<\/p>\n<p class=\"wp-block-paragraph\" id=\"2f4c\">Here are a few things you can try asking:<\/p>\n<ul class=\"wp-block-list\">\n<li class=\"wp-block-list-item\"><em>\u201cCan you write a poem and save it to my Desktop?\u201d<\/em><\/li>\n<li class=\"wp-block-list-item\"><em>\u201cWhat are some work-related files in my Downloads folder?\u201d<\/em><\/li>\n<li class=\"wp-block-list-item\"><em>\u201cCan you take all the images on my Desktop and move them to a new folder called \u2018Images\u2019?\u201d<\/em><\/li>\n<\/ul>\n<p class=\"wp-block-paragraph\" id=\"ac8d\">When needed, Claude will automatically invoke the appropriate tools and <strong>ask for your approval<\/strong> before doing anything on your system. You stay in control, and Claude gets the job done.<\/p>\n<h2 class=\"wp-block-heading\" id=\"section8\">Build Your Own: Custom MCP Server from Scratch <\/h2>\n<p class=\"wp-block-paragraph\" id=\"458f\"><strong>Alright, ready to level up?<\/strong><\/p>\n<p class=\"wp-block-paragraph\" id=\"0ac1\">In this section, you\u2019ll go from user to builder. We\u2019re going to write a custom MCP server that Claude can talk to \u2014 specifically, a tool that lets it search the latest documentation from AI libraries like LangChain, OpenAI, MCP (yes, we\u2019re using MCP to learn MCP), and LlamaIndex.<\/p>\n<p class=\"wp-block-paragraph\" id=\"2065\">Because let\u2019s be honest \u2014 how many times have you watched Claude confidently spit out deprecated code or reference libraries that haven\u2019t been updated since 2021?<\/p>\n<p class=\"wp-block-paragraph\" id=\"1a8d\">This tool uses real-time search, scrapes live content, and gives your assistant fresh knowledge on demand. Yes, it\u2019s as cool as it sounds.<\/p>\n<p class=\"wp-block-paragraph\" id=\"87be\">The project is built using the official MCP SDK from Anthropic. If you\u2019re comfortable with Python and the command line, you\u2019ll be up and running in no time. And even if you\u2019re not \u2014 don\u2019t worry. We\u2019ll walk through everything step by step, including the parts most tutorials just assume you already know.<\/p>\n<h3 class=\"wp-block-heading\" id=\"e3f1\">Prerequisites<\/h3>\n<p class=\"wp-block-paragraph\" id=\"37c4\">Before we dive in, here are the things you need installed on your system:<\/p>\n<ul class=\"wp-block-list\">\n<li class=\"wp-block-list-item\">\n<strong>Python 3.10 or higher <\/strong>\u2014 this is the programming language we\u2019ll use<\/li>\n<li class=\"wp-block-list-item\">\n<strong>MCP SDK (v1.2.0 or higher)<\/strong> \u2014 this gives you all the tools to create a Claude-compatible server (which will be installed in upcoming parts)<\/li>\n<li class=\"wp-block-list-item\">\n<a href=\"https:\/\/github.com\/astral-sh\/uv\" target=\"_blank\" rel=\"noreferrer noopener\"><strong>uv<\/strong><\/a><strong> (package manager) <\/strong>\u2014 think of it like a modern version of pip, but much faster and easier to use for projects (which will be installed in upcoming parts)<\/li>\n<\/ul>\n<h3 class=\"wp-block-heading\" id=\"8ef0\">Step 1: Install <code>uv<\/code> (the Package Manager)<\/h3>\n<p class=\"wp-block-paragraph\" id=\"2562\">I<mdspan datatext=\"el1746803343129\" class=\"mdspan-comment\">f you\u2019ve used Python before, you might be used to <code>pip<\/code>. <code>uv<\/code> is like pip\u2019s cooler, more modern cousin. We\u2019ll use it to set up and manage our project. Run the command below in your terminal to install <code>uv<\/code>:<\/mdspan><\/p>\n<p class=\"wp-block-paragraph\"><strong>On macOS\/Linux:<\/strong> <\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\">curl \u2013LsSf https:\/\/astral.sh\/uv\/install.sh | sh<\/code><\/pre>\n<p class=\"wp-block-paragraph\"><strong>On Windows:<\/strong><\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\">powershell \u2013ExecutionPolicy ByPass -c \"irm https:\/\/astral.sh\/uv\/install.ps1 | iex\"<\/code><\/pre>\n<p class=\"wp-block-paragraph\" id=\"81ab\">This will download and install <code>uv<\/code> on your machine. Once it\u2019s done, <strong>close and reopen your terminal<\/strong> to make sure the <code>uv<\/code> command is recognized. (If you\u2019re on Windows, you can use WSL or follow their Windows instructions.)<\/p>\n<p class=\"wp-block-paragraph\" id=\"724e\">To check that it\u2019s working, run this command in your terminal:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\">uv --version<\/code><\/pre>\n<p class=\"wp-block-paragraph\" id=\"2588\">If you see a version number, you\u2019re good to go.<\/p>\n<h3 class=\"wp-block-heading\" id=\"6140\">Step 2: Set Up Your Project<\/h3>\n<p class=\"wp-block-paragraph\" id=\"e3e9\">Now we\u2019re going to create a folder for our MCP server and get all the pieces in place. In your terminal, run these commands:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\"># Create and enter your project folder\nuv init mcp-server\ncd mcp-server\n\n# Create a virtual environment\nuv venv\n# Activate the virtual environment\nsource .venv\/bin\/activate  # Windows: .venvScriptsactivate<\/code><\/pre>\n<p class=\"wp-block-paragraph\"><strong>Wait \u2014 what\u2019s all this?<\/strong><\/p>\n<ul class=\"wp-block-list\">\n<li class=\"wp-block-list-item\">\n<code>uv init mcp-server <\/code>sets up a blank Python project named<code> mcp-server<\/code> .<\/li>\n<li class=\"wp-block-list-item\">\n<code>uv venv<\/code> creates a virtual environment (your private sandbox for this project).<\/li>\n<li class=\"wp-block-list-item\">\n<code>source .venv\/bin\/activate<\/code> turns on that environment so everything you install stays inside it.<\/li>\n<\/ul>\n<h3 class=\"wp-block-heading\" id=\"4055\">Step 3: Install the Required Packages<\/h3>\n<p class=\"wp-block-paragraph\" id=\"48a7\">Inside your virtual environment, install the tools you\u2019ll need:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\">uv add \"mcp[cli]\" httpx beautifulsoup4 python-dotenv<\/code><\/pre>\n<p class=\"wp-block-paragraph\" id=\"e85c\">Here\u2019s what each package does:<\/p>\n<ul class=\"wp-block-list\">\n<li class=\"wp-block-list-item\">\n<code>mcp[cli]<\/code>: The core SDK that lets you build servers Claude can talk to<\/li>\n<li class=\"wp-block-list-item\">\n<code>httpx<\/code>: Used to make HTTP requests (like fetching data from websites)<\/li>\n<li class=\"wp-block-list-item\">\n<code>beautifulsoup4<\/code>: Helps us extract readable text from messy HTML<\/li>\n<li class=\"wp-block-list-item\">\n<code>python-dotenv<\/code>: Lets us load API keys from a <code>.env<\/code> file<\/li>\n<\/ul>\n<p class=\"wp-block-paragraph\" id=\"9861\">Before we start writing code, it\u2019s a good idea to open the project folder in a text editor so you can see all your files in one place and edit them easily.<\/p>\n<p class=\"wp-block-paragraph\" id=\"ba8e\">If you\u2019re using <strong>VS Code<\/strong> (which I highly recommend if you\u2019re not sure what to use), just run this from inside your <code>mcp-server<\/code> folder:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\">code .<\/code><\/pre>\n<p class=\"wp-block-paragraph\">This command tells VS Code to open the <strong>current folder<\/strong> (<code>.<\/code> just means \u201cright here\u201d).<\/p>\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\" id=\"48cf\"><em><img data-recalc-dims=\"1\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/s.w.org\/images\/core\/emoji\/15.0.3\/72x72\/1f6e0.png?ssl=1\" alt=\"\ud83d\udee0\" class=\"wp-smiley\" style=\"height: 1em; max-height: 1em;\"> If the <\/em><code>code<\/code><em> command doesn\u2019t work, you probably need to enable it<\/em>:<\/p>\n<p class=\"wp-block-paragraph\" id=\"fd9f\"><em>1. Open VS Code<\/em><\/p>\n<p class=\"wp-block-paragraph\" id=\"bd55\"><em>2. Press <\/em><code>Cmd+Shift+P<\/code><em> (or <\/em><code>Ctrl+Shift+P<\/code><em> on Windows)<\/em><\/p>\n<p class=\"wp-block-paragraph\" id=\"090a\"><em>3. Type: <\/em><code>Shell Command: Install 'code' command in PATH<\/code><\/p>\n<p class=\"wp-block-paragraph\" id=\"08bd\"><em>4. Hit Enter, then restart your terminal<\/em><\/p>\n<p class=\"wp-block-paragraph\" id=\"0433\"><em>If you\u2019re using another editor like PyCharm or Sublime Text, you can just open the <\/em><code>mcp-server<\/code><em> folder manually from within the app.<\/em><\/p>\n<\/blockquote>\n<h3 class=\"wp-block-heading\" id=\"32f7\">Step 3.5: Get Your Serper API Key (for Web Search)<\/h3>\n<p class=\"wp-block-paragraph\" id=\"0bc6\">To power our real-time documentation search, we\u2019ll use <a href=\"https:\/\/serper.dev\/\" rel=\"noreferrer noopener\" target=\"_blank\">Serper<\/a> \u2014 a simple and fast Google Search API that works great for AI agents.<\/p>\n<p class=\"wp-block-paragraph\" id=\"4058\">Here\u2019s how to set it up:<\/p>\n<ol class=\"wp-block-list\">\n<li class=\"wp-block-list-item\">Head over to <a href=\"https:\/\/serper.dev\/\" target=\"_blank\" rel=\"noreferrer noopener\">serper.dev<\/a> and click <strong>Sign Up:<br \/><\/strong>It\u2019s free for basic usage and works perfectly for this project.<\/li>\n<li class=\"wp-block-list-item\">Once signed in, go to your <strong>Dashboard:<br \/><\/strong>You\u2019ll see your <strong>API Key<\/strong> listed there. Copy it.<\/li>\n<li class=\"wp-block-list-item\">In your project folder, create a file called <code>.env:&lt;br&gt;<\/code>This is where we\u2019ll store the key securely (so we\u2019re not hardcoding it).<\/li>\n<li class=\"wp-block-list-item\">Add this line to your <code>.env<\/code> file:<\/li>\n<\/ol>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-markdown\">SERPER_API_KEY=your-api-key-here<\/code><\/pre>\n<p class=\"wp-block-paragraph\" id=\"a267\">Replace <code>your-api-key-here<\/code> with the actual key you copied<\/p>\n<p class=\"wp-block-paragraph\" id=\"f0c4\">That\u2019s it \u2014 now your server can talk to Google via Serper and pull in fresh docs when Claude asks.<\/p>\n<h3 class=\"wp-block-heading\" id=\"60cf\">Step 4: Write the Server Code<\/h3>\n<p class=\"wp-block-paragraph\" id=\"0ca5\">Now that your project is set up and your virtual environment is running, it\u2019s time to actually write the server.<\/p>\n<p class=\"wp-block-paragraph\" id=\"b274\">This server is going to:<\/p>\n<ul class=\"wp-block-list\">\n<li class=\"wp-block-list-item\">Accept a question like: <em>\u201cHow do I use retrievers in LangChain?\u201d<\/em>\n<\/li>\n<li class=\"wp-block-list-item\">Know which documentation site to search (e.g., LangChain, OpenAI, etc.)<\/li>\n<li class=\"wp-block-list-item\">Use a web search API (Serper) to find the best links from that site<\/li>\n<li class=\"wp-block-list-item\">Visit those pages and scrape the actual content<\/li>\n<li class=\"wp-block-list-item\">Return that content to Claude<\/li>\n<\/ul>\n<p class=\"wp-block-paragraph\" id=\"b1e5\">This is what makes your Claude smarter \u2014 it can look things up from real docs instead of making things up based on old data.<\/p>\n<hr class=\"wp-block-separator has-alpha-channel-opacity is-style-dotted\">\n<h3 class=\"wp-block-heading\" id=\"9e86\">\n<img data-recalc-dims=\"1\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/s.w.org\/images\/core\/emoji\/15.0.3\/72x72\/26a0.png?ssl=1\" alt=\"\u26a0\" class=\"wp-smiley\" style=\"height: 1em; max-height: 1em;\"> Quick Reminder About Ethical Scraping<\/h3>\n<p class=\"wp-block-paragraph\" id=\"e470\"><strong>Always respect the site you\u2019re scraping.<\/strong> Use this responsibly. Avoid hitting pages too often, don\u2019t scrape behind login walls, and check the site\u2019s <code>robots.txt<\/code> file to see what\u2019s allowed. You can read more about it <a href=\"https:\/\/developers.google.com\/search\/docs\/crawling-indexing\/robots\/intro\" target=\"_blank\" rel=\"noreferrer noopener\">here.<\/a><\/p>\n<p class=\"wp-block-paragraph\" id=\"f4b9\">Your tool is only as useful as it is respectful. That\u2019s how we build AI systems that are not just smart \u2014 but sustainable too.<\/p>\n<hr class=\"wp-block-separator has-alpha-channel-opacity is-style-dotted\">\n<h4 class=\"wp-block-heading\" id=\"5351\"><strong>1. Create Your Server File<\/strong><\/h4>\n<p class=\"wp-block-paragraph\" id=\"cede\">First, run this from inside your <code>mcp-server<\/code> folder to create a new file:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\">touch main.py<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Then open that file in your editor (if it isn\u2019t open already). Replace the code there with the following:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\">from mcp.server.fastmcp import FastMCP\nfrom dotenv import load_dotenv\nimport httpx\nimport json\nimport os\nfrom bs4 import BeautifulSoup\nload_dotenv()\n\nmcp = FastMCP(\"docs\")\n\nUSER_AGENT = \"docs-app\/1.0\"\nSERPER_URL = \"https:\/\/google.serper.dev\/search\"\n\ndocs_urls = {\n    \"langchain\": \"python.langchain.com\/docs\",\n    \"llama-index\": \"docs.llamaindex.ai\/en\/stable\",\n    \"openai\": \"platform.openai.com\/docs\",\n    \"mcp\": \"modelcontextprotocol.io\"\n}\n\nasync def search_web(query: str) -&gt; dict | None:\n    payload = json.dumps({\"q\": query, \"num\": 2})\n    headers = {\n        \"X-API-KEY\": os.getenv(\"SERPER_API_KEY\"),\n        \"Content-Type\": \"application\/json\",\n    }\n\n    async with httpx.AsyncClient() as client:\n        try:\n            response = await client.post(\n                SERPER_URL, headers=headers, data=payload, timeout=30.0\n            )\n            response.raise_for_status()\n            return response.json()\n        except httpx.TimeoutException:\n            return {\"organic\": []}\n        except httpx.HTTPStatusError as e:\n            print(f\"HTTP error occurred: {e}\")\n            return {\"organic\": []}\n  \nasync def fetch_url(url: str) -&gt; str:\n    async with httpx.AsyncClient(headers={\"User-Agent\": USER_AGENT}) as client:\n        try:\n            response = await client.get(url, timeout=30.0)\n            response.raise_for_status()\n            soup = BeautifulSoup(response.text, \"html.parser\")\n            \n            # Try to extract main content and remove navigation, sidebars, etc.\n            main_content = soup.find(\"main\") or soup.find(\"article\") or soup.find(\"div\", class_=\"content\")\n            \n            if main_content:\n                text = main_content.get_text(separator=\"n\", strip=True)\n            else:\n                text = soup.get_text(separator=\"n\", strip=True)\n                \n            # Limit content length if it's too large\n            if len(text) &gt; 8000:\n                text = text[:8000] + \"... [content truncated]\"\n                \n            return text\n        except httpx.TimeoutException:\n            return \"Timeout error when fetching the URL\"\n        except httpx.HTTPStatusError as e:\n            return f\"HTTP error occurred: {e}\"\n\n@mcp.tool()  \nasync def get_docs(query: str, library: str) -&gt; str:\n    \"\"\"\n    Search the latest docs for a given query and library.\n    Supports langchain, openai, mcp and llama-index.\n\n    Args:\n        query: The query to search for (e.g. \"Chroma DB\")\n        library: The library to search in (e.g. \"langchain\")\n\n    Returns:\n        Text from the docs\n    \"\"\"\n    if library not in docs_urls:\n        raise ValueError(f\"Library {library} not supported by this tool. Supported libraries: {', '.join(docs_urls.keys())}\")\n    \n    query = f\"site:{docs_urls[library]} {query}\"\n    results = await search_web(query)\n    \n    if not results or len(results.get(\"organic\", [])) == 0:\n        return \"No results found\"\n    \n    combined_text = \"\"\n    for i, result in enumerate(results[\"organic\"]):\n        url = result[\"link\"]\n        title = result.get(\"title\", \"No title\")\n        \n        # Add separator between results\n        if i &gt; 0:\n            combined_text += \"nn\" + \"=\"*50 + \"nn\"\n            \n        combined_text += f\"Source: {title}nURL: {url}nn\"\n        page_content = await fetch_url(url)\n        combined_text += page_content\n    \n    return combined_text\n\n\nif __name__ == \"__main__\":\n    mcp.run(transport=\"stdio\")<\/code><\/pre>\n<h4 class=\"wp-block-heading\" id=\"a2c5\"><strong>2. How The Code Works<\/strong><\/h4>\n<p class=\"wp-block-paragraph\" id=\"9b8c\">First, we set up the foundation of our custom MCP server. It pulls in all the libraries you\u2019ll need \u2014 like tools for making web requests, cleaning up webpages, and loading secret API keys. It also creates your server and names it <code>\"docs\"<\/code> so Claude knows what to call. Then, it lists the documentation sites (like LangChain, OpenAI, MCP, and LlamaIndex) your tool will search through. Finally, it sets the URL for the Serper API, which is what the tool will use to send Google search queries. Think of it as prepping your workspace before actually building the tool.<\/p>\n<details class=\"wp-block-details is-layout-flow wp-block-details-is-layout-flow\">\n<summary>Click here to see the revelant code snippet<\/summary>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\">from mcp.server.fastmcp import FastMCP\nfrom dotenv import load_dotenv\nimport httpx\nimport json\nimport os\nfrom bs4 import BeautifulSoup\nload_dotenv()\n\nmcp = FastMCP(\"docs\")\n\nUSER_AGENT = \"docs-app\/1.0\"\nSERPER_URL = \"https:\/\/google.serper.dev\/search\"\n\ndocs_urls = {\n    \"langchain\": \"python.langchain.com\/docs\",\n    \"llama-index\": \"docs.llamaindex.ai\/en\/stable\",\n    \"openai\": \"platform.openai.com\/docs\",\n    \"mcp\": \"modelcontextprotocol.io\"\n}<\/code><\/pre>\n<\/details>\n<p class=\"wp-block-paragraph\" id=\"c973\">Then, we define a function that lets our tool talk to the <strong>Serper API<\/strong>, which we\u2019ll use as a wrapper around Google Search.<\/p>\n<p class=\"wp-block-paragraph\" id=\"9ebc\">This function, <code>search_web<\/code>, takes in a query string, builds a request, and sends it off to the search engine. It includes your API key for authentication, tells Serper we\u2019re sending JSON, and limits the number of search results to 2 for speed and focus. The function returns a dictionary containing the structured results, and it also gracefully handles timeouts or any errors that might come from the API. This is the part that helps Claude figure out <em>where<\/em> to look before we even fetch the content.<\/p>\n<details class=\"wp-block-details is-layout-flow wp-block-details-is-layout-flow\">\n<summary>Click here to see the relevant code snippet<\/summary>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\">async def search_web(query: str) -&gt; dict | None:\n    payload = json.dumps({\"q\": query, \"num\": 2})\n    headers = {\n        \"X-API-KEY\": os.getenv(\"SERPER_API_KEY\"),\n        \"Content-Type\": \"application\/json\",\n    }\n\n    async with httpx.AsyncClient() as client:\n        try:\n            response = await client.post(\n                SERPER_URL, headers=headers, data=payload, timeout=30.0\n            )\n            response.raise_for_status()\n            return response.json()\n        except httpx.TimeoutException:\n            return {\"organic\": []}\n        except httpx.HTTPStatusError as e:\n            print(f\"HTTP error occurred: {e}\")\n            return {\"organic\": []}<\/code><\/pre>\n<\/details>\n<p class=\"wp-block-paragraph\">Once we\u2019ve found a few promising links, we need a way to extract just the <strong>useful content<\/strong> from those web pages. That\u2019s what <code>fetch_url<\/code> does. It visits each URL, grabs the full HTML of the page, and then uses BeautifulSoup to filter out just the readable parts\u2014things like paragraphs, headings, and examples. We try to prioritize sections like <code>&lt;main&gt;<\/code>, <code>&lt;article&gt;<\/code>, or containers with a <code>.content<\/code> class, which usually hold the good stuff. If the page is super long, we also trim it down to avoid flooding the output. Think of this as the \u201creader mode\u201d for Claude\u2014it turns cluttered webpages into clean text it can understand.<\/p>\n<details class=\"wp-block-details is-layout-flow wp-block-details-is-layout-flow\">\n<summary>Click here to see the relevant code snippet<\/summary>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\">async def fetch_url(url: str) -&gt; str:\n    async with httpx.AsyncClient(headers={\"User-Agent\": USER_AGENT}) as client:\n        try:\n            response = await client.get(url, timeout=30.0)\n            response.raise_for_status()\n            soup = BeautifulSoup(response.text, \"html.parser\")\n            \n            # Try to extract main content and remove navigation, sidebars, etc.\n            main_content = soup.find(\"main\") or soup.find(\"article\") or soup.find(\"div\", class_=\"content\")\n            \n            if main_content:\n                text = main_content.get_text(separator=\"n\", strip=True)\n            else:\n                text = soup.get_text(separator=\"n\", strip=True)\n                \n            # Limit content length if it's too large\n            if len(text) &gt; 8000:\n                text = text[:8000] + \"... [content truncated]\"\n                \n            return text\n        except httpx.TimeoutException:\n            return \"Timeout error when fetching the URL\"\n        except httpx.HTTPStatusError as e:\n            return f\"HTTP error occurred: {e}\"<\/code><\/pre>\n<\/details>\n<p class=\"wp-block-paragraph\" id=\"fcfa\">Now comes the main act: the actual tool function that Claude will call.<\/p>\n<p class=\"wp-block-paragraph\" id=\"c260\">The <code>get_docs<\/code> function is where everything comes together. Claude will pass it a <strong>query<\/strong> and the name of a <strong>library<\/strong> (like <code>\"llama-index\"<\/code>), and this function will:<\/p>\n<ol class=\"wp-block-list\">\n<li class=\"wp-block-list-item\">Check if that library is supported<\/li>\n<li class=\"wp-block-list-item\">Build a site-specific search query (e.g., <code>site:docs.llamaindex.ai \"vector store\"<\/code>)<\/li>\n<li class=\"wp-block-list-item\">Use <code>search_web()<\/code> to get the top results<\/li>\n<li class=\"wp-block-list-item\">Use <code>fetch_url()<\/code> to visit and extract the content<\/li>\n<li class=\"wp-block-list-item\">Format everything into a nice, readable string that Claude can understand and return<\/li>\n<\/ol>\n<p class=\"wp-block-paragraph\" id=\"3aeb\">We also include titles, URLs, and some visual separators between each result, so Claude can reference or cite them if needed.<\/p>\n<details class=\"wp-block-details is-layout-flow wp-block-details-is-layout-flow\">\n<summary>Click here to see the relevant code snippet<\/summary>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\">@mcp.tool()  \nasync def get_docs(query: str, library: str) -&gt; str:\n    \"\"\"\n    Search the latest docs for a given query and library.\n    Supports langchain, openai, mcp and llama-index.\n\n    Args:\n        query: The query to search for (e.g. \"Chroma DB\")\n        library: The library to search in (e.g. \"langchain\")\n\n    Returns:\n        Text from the docs\n    \"\"\"\n    if library not in docs_urls:\n        raise ValueError(f\"Library {library} not supported by this tool. Supported libraries: {', '.join(docs_urls.keys())}\")\n    \n    query = f\"site:{docs_urls[library]} {query}\"\n    results = await search_web(query)\n    \n    if not results or len(results.get(\"organic\", [])) == 0:\n        return \"No results found\"\n    \n    combined_text = \"\"\n    for i, result in enumerate(results[\"organic\"]):\n        url = result[\"link\"]\n        title = result.get(\"title\", \"No title\")\n        \n        # Add separator between results\n        if i &gt; 0:\n            combined_text += \"nn\" + \"=\"*50 + \"nn\"\n            \n        combined_text += f\"Source: {title}nURL: {url}nn\"\n        page_content = await fetch_url(url)\n        combined_text += page_content\n    \n    return combined_text<\/code><\/pre>\n<\/details>\n<p class=\"wp-block-paragraph\">Finally, this line kicks everything off. It tells the MCP server to start listening for input from Claude using standard input\/output (which is how Claude Desktop talks to external tools). This line always lives at the bottom of your script.<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\">if __name__ == \"__main__\":\n    mcp.run(transport=\"stdio\")<\/code><\/pre>\n<h3 class=\"wp-block-heading\" id=\"433f\">Step 5: Test and Run Your Server<\/h3>\n<p class=\"wp-block-paragraph\" id=\"dc46\">Alright, your server is coded and ready to go \u2014 now let\u2019s run it and see it in action. There are two main ways to test your MCP server:<\/p>\n<h4 class=\"wp-block-heading\" id=\"c5df\">Development Mode (Recommended for Building &amp; Testing)<\/h4>\n<p class=\"wp-block-paragraph\" id=\"7f24\">The easiest way to test your server during development is to use:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\">mcp dev main.py<\/code><\/pre>\n<p class=\"wp-block-paragraph\">This command launches the <a href=\"https:\/\/modelcontextprotocol.io\/docs\/tools\/inspector\" target=\"_blank\" rel=\"noreferrer noopener\"><strong>MCP Inspector<\/strong><\/a>, which opens up a local web interface in your browser. It\u2019s like a control panel for your server.<\/p>\n<figure class=\"wp-block-image alignwide size-large\"><img data-recalc-dims=\"1\" height=\"453\" width=\"1024\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/contributor.insightmediagroup.io\/wp-content\/uploads\/2025\/05\/image-124-1024x453.png?resize=1024%2C453&#038;ssl=1\" alt=\"\" class=\"wp-image-603959\"><figcaption class=\"wp-element-caption\">Image by author<\/figcaption><\/figure>\n<p class=\"wp-block-paragraph\" id=\"c273\">Here\u2019s what you can do with it:<\/p>\n<ul class=\"wp-block-list\">\n<li class=\"wp-block-list-item\">Interactively test your tools (like <code>get_docs<\/code>)<\/li>\n<li class=\"wp-block-list-item\">View detailed logs and error messages in real time<\/li>\n<li class=\"wp-block-list-item\">Monitor performance and response times<\/li>\n<li class=\"wp-block-list-item\">Set or override environment variables temporarily<\/li>\n<\/ul>\n<p class=\"wp-block-paragraph\" id=\"1b08\">Use this mode while building and debugging. You\u2019ll be able to see exactly what Claude would see and quickly fix any issues before integrating with the full Claude Desktop app.<\/p>\n<h4 class=\"wp-block-heading\" id=\"14cc\">Claude Desktop Integration (For Regular Use)<\/h4>\n<p class=\"wp-block-paragraph\" id=\"df1d\">Once your server works and you\u2019re happy with it, you can install it into Claude Desktop:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\">mcp install main.py<\/code><\/pre>\n<p class=\"wp-block-paragraph\" id=\"8945\">This command will:<\/p>\n<ul class=\"wp-block-list\">\n<li class=\"wp-block-list-item\">Add your server into Claude\u2019s configuration file (the JSON file we fiddled with earlier) automatically<\/li>\n<li class=\"wp-block-list-item\">Enable it to run every time you launch Claude Desktop<\/li>\n<li class=\"wp-block-list-item\">Make it available through the Developer Tools (<img data-recalc-dims=\"1\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/s.w.org\/images\/core\/emoji\/15.0.3\/72x72\/1f528.png?ssl=1\" alt=\"\ud83d\udd28\" class=\"wp-smiley\" style=\"height: 1em; max-height: 1em;\"> hammer icon)<\/li>\n<\/ul>\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\" id=\"0d39\"><strong><em>But hold on \u2014 there\u2019s one small catch\u2026<\/em><\/strong><\/p>\n<\/blockquote>\n<h4 class=\"wp-block-heading\" id=\"6c44\">\n<img data-recalc-dims=\"1\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/s.w.org\/images\/core\/emoji\/15.0.3\/72x72\/26a0.png?ssl=1\" alt=\"\u26a0\" class=\"wp-smiley\" style=\"height: 1em; max-height: 1em;\"> Current Issue: <code>uv<\/code> Command Is Hardcoded<\/h4>\n<p class=\"wp-block-paragraph\" id=\"e55e\">Right now, there\u2019s an open issue in the <code>mcp<\/code> library: when it writes your server into Claude\u2019s config file, it <strong>hardcodes<\/strong> the command as just <code>\"uv\"<\/code>. That works <em>only<\/em> if <code>uv<\/code> is globally available in your PATH \u2014 which isn\u2019t always the case, especially if you installed it locally with pipx or a custom method.<\/p>\n<p class=\"wp-block-paragraph\" id=\"8e11\">So we need to fix it manually. Here\u2019s how:<\/p>\n<h5 class=\"wp-block-heading\" id=\"91a1\">Manually Update Claude\u2019s Config File<\/h5>\n<ol class=\"wp-block-list\">\n<li class=\"wp-block-list-item\"><strong>Open your Claude config file:<\/strong><\/li>\n<\/ol>\n<p class=\"wp-block-paragraph\"><strong>On MacOS:<\/strong><\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\">code ~\/Library\/Application Support\/Claude\/claude_desktop_config.json<\/code><\/pre>\n<p class=\"wp-block-paragraph\"><strong>On Windows:<\/strong><\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\">code $env:AppDataClaudeclaude_desktop_config.json<\/code><\/pre>\n<p class=\"wp-block-paragraph\"><img data-recalc-dims=\"1\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/s.w.org\/images\/core\/emoji\/15.0.3\/72x72\/1f4a1.png?ssl=1\" alt=\"\ud83d\udca1\" class=\"wp-smiley\" style=\"height: 1em; max-height: 1em;\"> If you\u2019re not using VS Code, replace <code>code<\/code> with your text editor of choice (like <code>open<\/code>, <code>nano<\/code>, or <code>subl<\/code>).<\/p>\n<p class=\"wp-block-paragraph\">2. <strong>Find the section that looks like this:<\/strong><\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-json\">\"docs\": {\n  \"command\": \"uv\",\n  \"args\": [\n    \"run\",\n    \"--with\",\n    \"mcp[cli]\",\n    \"mcp\",\n    \"run\",\n    \"\/PATH\/TO\/mcp-server\/main.py\"\n  ]\n}<\/code><\/pre>\n<p class=\"wp-block-paragraph\" id=\"dd74\">3.<strong> Update the <\/strong><code>\"command\"<\/code><strong> value to the absolute path of <\/strong><code>uv<\/code><strong> on your system.<\/strong><\/p>\n<ul class=\"wp-block-list\">\n<li class=\"wp-block-list-item\">To find it, run this in your terminal:<\/li>\n<\/ul>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\">which uv<\/code><\/pre>\n<ul class=\"wp-block-list\">\n<li class=\"wp-block-list-item\">It\u2019ll return something like:<\/li>\n<\/ul>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\">\/Users\/your_username\/.local\/bin\/uv<\/code><\/pre>\n<ul class=\"wp-block-list\">\n<li class=\"wp-block-list-item\">Now replace <code>\"uv\"<\/code> in the config with that full path:<\/li>\n<\/ul>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-json\">\"docs\": {\n  \"command\": \"\/Users\/your_username\/.local\/bin\/uv\",\n  \"args\": [\n    \"run\",\n    \"--with\",\n    \"mcp[cli]\",\n    \"mcp\",\n    \"run\",\n    \"PATH\/TO\/mcp-server\/main.py\"\n  ]\n}<\/code><\/pre>\n<p class=\"wp-block-paragraph\" id=\"c8ad\">4. <strong>Save the file and restart Claude Desktop.<\/strong><\/p>\n<p class=\"wp-block-paragraph\"><img data-recalc-dims=\"1\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/s.w.org\/images\/core\/emoji\/15.0.3\/72x72\/2705.png?ssl=1\" alt=\"\u2705\" class=\"wp-smiley\" style=\"height: 1em; max-height: 1em;\"> <strong>That\u2019s It!<\/strong><\/p>\n<p class=\"wp-block-paragraph\" id=\"84fa\">Now Claude Desktop will recognize your custom <code>docs<\/code> tool, and anytime you open the Developer Tools (<img data-recalc-dims=\"1\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/s.w.org\/images\/core\/emoji\/15.0.3\/72x72\/1f528.png?ssl=1\" alt=\"\ud83d\udd28\" class=\"wp-smiley\" style=\"height: 1em; max-height: 1em;\">), it\u2019ll show up. You can chat with Claude and ask things like:<\/p>\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\" id=\"f610\"><em>\u201cCan you check the latest MCP docs for how to build a custom server?\u201d<\/em><\/p>\n<\/blockquote>\n<p class=\"wp-block-paragraph\" id=\"d494\">And Claude will call your server, search the docs, pull the content, and use it in its response \u2014 live. You can view a quick demo <a href=\"https:\/\/drive.google.com\/file\/d\/1xFqx0KfviGcor7Fnzk8MdmQJUDjQsfLW\/view?usp=sharing\" rel=\"noreferrer noopener\" target=\"_blank\">here.<\/a><\/p>\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" height=\"892\" width=\"1024\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/contributor.insightmediagroup.io\/wp-content\/uploads\/2025\/05\/image-125-1024x892.png?resize=1024%2C892&#038;ssl=1\" alt=\"\" class=\"wp-image-603960\"><figcaption class=\"wp-element-caption\">Image by author<\/figcaption><\/figure>\n<hr class=\"wp-block-separator has-alpha-channel-opacity is-style-ornamental\">\n<h2 class=\"wp-block-heading\" id=\"section9\">\n<img data-recalc-dims=\"1\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/s.w.org\/images\/core\/emoji\/15.0.3\/72x72\/1f389.png?ssl=1\" alt=\"\ud83c\udf89\" class=\"wp-smiley\" style=\"height: 1em; max-height: 1em;\"> Congrats, You\u2019ve Mastered MCP!<\/h2>\n<p class=\"wp-block-paragraph\" id=\"398f\">You did it. You\u2019ve gone from zero to building, testing, and integrating your <strong>very own Claude-compatible MCP server<\/strong> \u2014 and that is <em>no<\/em> small feat.<\/p>\n<p class=\"wp-block-paragraph\" id=\"6486\">Take a moment. Stretch. Sip some coffee. Pat yourself on the back. You didn\u2019t just write some Python \u2014 you built a real, production-grade tool that extends an LLM\u2019s capabilities in a modular, secure, and powerful way.<\/p>\n<p class=\"wp-block-paragraph\" id=\"9a76\">Seriously, most devs don\u2019t get this far. You now understand:<\/p>\n<ul class=\"wp-block-list\">\n<li class=\"wp-block-list-item\">How MCP works under the hood<\/li>\n<li class=\"wp-block-list-item\">How to build and expose tools Claude can use<\/li>\n<li class=\"wp-block-list-item\">How to wire up real-time web search and content extraction<\/li>\n<li class=\"wp-block-list-item\">How to debug, test, and integrate the whole thing with Claude Desktop<\/li>\n<\/ul>\n<p class=\"wp-block-paragraph\" id=\"b572\">You didn\u2019t just learn it \u2014 you shipped it.<\/p>\n<p class=\"wp-block-paragraph\" id=\"9938\">Want to go even deeper? There\u2019s a whole world of <strong>agentic workflows<\/strong>, <strong>custom tools<\/strong>, and <strong>collaborative LLMs<\/strong> waiting to be built. But for now?<\/p>\n<p class=\"wp-block-paragraph\" id=\"2cb3\"><strong>Take the win. You earned it.<\/strong> <img data-recalc-dims=\"1\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/s.w.org\/images\/core\/emoji\/15.0.3\/72x72\/1f3c6.png?ssl=1\" alt=\"\ud83c\udfc6\" class=\"wp-smiley\" style=\"height: 1em; max-height: 1em;\"><\/p>\n<p class=\"wp-block-paragraph\" id=\"ea05\">Now go ask Claude something fun and let your new tool flex.<\/p>\n<hr class=\"wp-block-separator has-alpha-channel-opacity is-style-dotted\">\n<h2 class=\"wp-block-heading\" id=\"section10\">References<\/h2>\n<p class=\"wp-block-paragraph\" id=\"d660\">[1] Anthropic, <em>Model Context Protocol: Introduction<\/em> (2024), modelcontextprotocol.io<br \/>[2] LangChain, <em>MCP From Scratch<\/em> (2024), <a href=\"https:\/\/mirror-feeling-d80.notion.site\/MCP-From-Scratch-1b9808527b178040b5baf83a991ed3b2?pvs=4\" rel=\"noreferrer noopener\" target=\"_blank\">Notion<\/a><br \/>[3] A. Alejandro, <em>MCP Server Example<\/em> (2024), <a href=\"https:\/\/github.com\/alejandro-ao\/mcp-server-example\" rel=\"noreferrer noopener\" target=\"_blank\">GitHub Repository<\/a><br \/>[4] O. Santos, <em>Integrating Agentic RAG with MCP Servers: Technical Implementation Guide<\/em> (2024), Medium<\/p>\n<p>The post <a href=\"https:\/\/towardsdatascience.com\/how-i-finally-understood-mcp-and-got-it-working-irl-2\/\">How I Finally Understood MCP\u200a\u2014\u200aand Got It Working in Real Life<\/a> appeared first on <a href=\"https:\/\/towardsdatascience.com\/\">Towards Data Science<\/a>.<\/p>\n<\/div>\n<p> \t<BR><br \/>\n <BR><\/BR><br \/>\n    Hailey Quach<br \/>\n \t<BR><br \/>\n<BR><\/BR><br \/>\n<a href=\"https:\/\/towardsdatascience.com\/how-i-finally-understood-mcp-and-got-it-working-irl-2\/\">Go to original source<\/a><br \/>\n \t<BR><br \/>\n <BR><\/BR><\/p>\n","protected":false},"excerpt":{"rendered":"<p>How I Finally Understood MCP\u200a\u2014\u200aand Got It Working in Real Life Table of Content Introduction: Why I Wrote This The Evolution of Tool Integration with LLMs What Is Model Context Protocol (MCP), Really? Wait, MCP sounds like RAG\u2026 but is it? In an MCP-based setup In a traditional RAG system Traditional RAG Implementation MCP Implementation [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[62,2046,69,240,71,2533,2654],"tags":[2118,41,314],"class_list":["post-3769","post","type-post","status-publish","format-standard","hentry","category-aimldsaimlds","category-anthropic-claude","category-artificial-intelligence","category-editors-pick","category-large-language-models","category-mcp","category-model-context-protocol","tag-mcp","tag-what","tag-why"],"_links":{"self":[{"href":"https:\/\/mailitics.com\/index.php\/wp-json\/wp\/v2\/posts\/3769"}],"collection":[{"href":"https:\/\/mailitics.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mailitics.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mailitics.com\/index.php\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/mailitics.com\/index.php\/wp-json\/wp\/v2\/comments?post=3769"}],"version-history":[{"count":0,"href":"https:\/\/mailitics.com\/index.php\/wp-json\/wp\/v2\/posts\/3769\/revisions"}],"wp:attachment":[{"href":"https:\/\/mailitics.com\/index.php\/wp-json\/wp\/v2\/media?parent=3769"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mailitics.com\/index.php\/wp-json\/wp\/v2\/categories?post=3769"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mailitics.com\/index.php\/wp-json\/wp\/v2\/tags?post=3769"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}