<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Mcp-Server on Vitalii Sukach</title><link>https://sukachvitally.github.io/tags/mcp-server/</link><description>Recent content in Mcp-Server on Vitalii Sukach</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Mon, 15 Jun 2026 20:00:06 +0000</lastBuildDate><atom:link href="https://sukachvitally.github.io/tags/mcp-server/index.xml" rel="self" type="application/rss+xml"/><item><title>MCP: The Protocol That Finally Taught AI to Stop Being Useless</title><link>https://sukachvitally.github.io/posts/mcp-the-protocol-that-finally-taught-ai-to-stop-being-useless/</link><pubDate>Mon, 15 Jun 2026 20:00:06 +0000</pubDate><guid>https://sukachvitally.github.io/posts/mcp-the-protocol-that-finally-taught-ai-to-stop-being-useless/</guid><description>&lt;p>&lt;em>A beginner’s guide to Model Context Protocol — the thing that lets your AI assistant actually do stuff, not just talk about doing stuff.&lt;/em>&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://sukachvitally.github.io/posts/mcp-the-protocol-that-finally-taught-ai-to-stop-being-useless/image-01.png">&lt;/p>
&lt;p>Picture this: you’ve got the most brilliant assistant in the world. Knows everything. Speaks 50 languages. Can explain quantum physics and write poetry at the same time. But here’s the catch — &lt;strong>they’re locked in a glass box&lt;/strong>. They can see your computer screen. They can describe what they’d do if they could reach it. But touch anything? Nope. Read a file? Uh-uh. Check your calendar? Dream on.&lt;/p></description><content:encoded><![CDATA[<p><em>A beginner’s guide to Model Context Protocol — the thing that lets your AI assistant actually do stuff, not just talk about doing stuff.</em></p>
<p><img loading="lazy" src="https://sukachvitally.github.io/posts/mcp-the-protocol-that-finally-taught-ai-to-stop-being-useless/image-01.png"></p>
<p>Picture this: you’ve got the most brilliant assistant in the world. Knows everything. Speaks 50 languages. Can explain quantum physics and write poetry at the same time. But here’s the catch — <strong>they’re locked in a glass box</strong>. They can see your computer screen. They can describe what they’d do if they could reach it. But touch anything? Nope. Read a file? Uh-uh. Check your calendar? Dream on.</p>
<p>That was AI assistants until very recently. Incredibly smart, utterly helpless. Like hiring Einstein to organize your sock drawer and then refusing to let him into the room.</p>
<p>Enter <strong>MCP — the Model Context Protocol</strong>. The thing that breaks the glass and lets your AI assistant actually <em>do the thing</em>.</p>
<blockquote>
<p><em>MCP is essentially a USB standard for AI. Before USB, every device needed its own special plug. Now there’s one protocol, and everything just… works.</em></p></blockquote>
<p>By the end of this article you’ll understand what MCP is, why it exists, how it works, and — most importantly — you’ll build your own working MCP server from scratch and connect it to Claude. Buckle up.</p>
<h3 id="a-brief-history-of-ais-helplessness-problem">A Brief History of AI’s Helplessness Problem</h3>
<p>To understand why MCP is a big deal, we need to appreciate just how spectacularly bad the situation was before it.</p>
<blockquote>
<p><strong>EARLY 2023</strong></p></blockquote>
<blockquote>
<p>Large Language Models (LLMs) become genuinely impressive. Everyone rushes to build assistants. The dream: AI that manages your life. The reality: AI that describes your life back to you very eloquently.</p></blockquote>
<blockquote>
<p><strong>MID 2023</strong></p></blockquote>
<blockquote>
<p>“Function calling” arrives. Now you can tell the AI: “hey, you can call these functions.” Progress! But every app invents its own format. A plugin for ChatGPT works nowhere else. A LangChain tool chain doesn’t talk to Claude. It’s the Wild West, but with JSON.</p></blockquote>
<blockquote>
<p><strong>LATE 2023–2024</strong></p></blockquote>
<blockquote>
<p>Developers build increasingly elaborate custom integrations. Everyone reinvents the wheel. Slightly differently. Slightly wrongly. A thousand incompatible “AI tool” standards bloom. This is the Tower of Babel, Silicon Valley edition.</p></blockquote>
<blockquote>
<p><strong>NOVEMBER 2024</strong></p></blockquote>
<blockquote>
<p><strong>Anthropic releases the Model Context Protocol</strong>. An open standard. One protocol to rule them all. The developer community collectively exhales. Finally.</p></blockquote>
<blockquote>
<p><strong>2025</strong></p></blockquote>
<blockquote>
<p>MCP adoption explodes. OpenAI, Google, Microsoft all announce support. Hundreds of MCP servers appear on GitHub. Your AI can now read files, browse the web, query databases, control your computer, send Slack messages, and order pizza. (Well, almost.)</p></blockquote>
<p>The key insight Anthropic had was embarrassingly simple: <strong>instead of every AI model learning to speak every tool’s language, make every tool learn one common language</strong>. The result is MCP.</p>
<h3 id="what-is-mcp-actually">What Is MCP, Actually?</h3>
<p>MCP stands for <strong>Model Context Protocol</strong>. It’s an open standard (meaning anyone can use it, free of charge) that defines how AI models communicate with external tools and data sources.</p>
<p>Think of it as a formal language that both sides agree to speak. The AI model is one speaker. Your tool (a file reader, a database, a weather API, your smart fridge) is the other speaker. MCP is the language between them.</p>
<blockquote>
<p><strong>The one-sentence explanation:</strong><br>
MCP lets AI models talk to <em>any</em> tool through a single standardized protocol, so you write the connector once and it works everywhere.</p></blockquote>
<p>Technically, MCP is a client-server architecture built on top of <a href="https://www.jsonrpc.org/specification"><strong>JSON-RPC</strong></a> <strong>2.0</strong>. Don’t be scared by that. It means messages are passed as readable JSON objects, and there’s a strict format for how requests and responses look. Clean, simple, predictable.</p>
<h3 id="the-architecture-threeamigos">The Architecture: Three Amigos</h3>
<p>MCP has three main characters. Let’s meet them:</p>
<p><img loading="lazy" src="https://sukachvitally.github.io/posts/mcp-the-protocol-that-finally-taught-ai-to-stop-being-useless/image-02.png"></p>
<h4 id="the-host">The Host</h4>
<blockquote>
<p>The application running the AI. Could be Claude Desktop, VS Code with Copilot, or your own app. The host is the conductor — it decides when the AI needs a tool and coordinates everything.</p></blockquote>
<h4 id="the-client">The Client</h4>
<blockquote>
<p>Technically a component <em>inside</em> the host. It handles the actual MCP communication — sends requests to servers, gets back results, feeds them to the AI. You don’t usually build this yourself.</p></blockquote>
<h4 id="the-server">The Server</h4>
<blockquote>
<p><strong>This is what you build.</strong> An MCP server exposes capabilities — tools, resources, and prompts — that the AI can use. Your server could wrap a database, a REST API, the local file system, or anything else. This is where the magic you write lives.</p></blockquote>
<h3 id="core-concepts-tools-resources-prompts">Core Concepts: Tools, Resources &amp; Prompts</h3>
<p>MCP servers expose three types of things to AI models. Think of these as different kinds of “capabilities”:</p>
<p><img loading="lazy" src="https://sukachvitally.github.io/posts/mcp-the-protocol-that-finally-taught-ai-to-stop-being-useless/image-03.png"></p>
<p>For most real-world use cases you’ll mainly deal with <strong>Tools</strong>. That’s where the action is.</p>
<h3 id="how-they-talk-the-mcp-messageformat">How They Talk: The MCP Message Format</h3>
<p>MCP messages are JSON objects following the <a href="https://www.jsonrpc.org/specification"><strong>JSON-RPC 2.0</strong></a> specification. Every single message — whether it’s a greeting, a tool call, or an error — follows the same envelope structure. Once you learn the envelope, the rest is just filling in the blanks.</p>
<h4 id="the-anatomy-of-everymessage">The Anatomy of Every Message</h4>
<p>All JSON-RPC messages share the same top-level fields. Let’s dissect them:</p>
<p><img loading="lazy" src="https://sukachvitally.github.io/posts/mcp-the-protocol-that-finally-taught-ai-to-stop-being-useless/image-04.png"></p>
<h3 id="phase-1the-handshake-initialize">Phase 1 — The Handshake: initialize</h3>
<p>Before any tools are called, the host and server must introduce themselves. This is the very first exchange that happens when Claude Desktop spawns your server process. Think of it as two people shaking hands and agreeing on a common language before getting to business.</p>
<p>1A. HOST → SERVER: INITIALIZE REQUEST</p>
<pre tabindex="0"><code>{
  &#34;jsonrpc&#34;: &#34;2.0&#34;,
  &#34;id&#34;: 0,
  &#34;method&#34;: &#34;initialize&#34;,
  &#34;params&#34;: {
    &#34;protocolVersion&#34;: &#34;2024-11-05&#34;,        // MCP spec version the host supports
    &#34;capabilities&#34;: {
      &#34;roots&#34;: { &#34;listChanged&#34;: true },    // Host can notify server about root changes
      &#34;sampling&#34;: {}                         // Host supports LLM sampling requests
    },
    &#34;clientInfo&#34;: {
      &#34;name&#34;: &#34;Claude Desktop&#34;,
      &#34;version&#34;: &#34;0.7.0&#34;
    }
  }
}
</code></pre><p>1B. SERVER → HOST: INITIALIZE RESPONSE</p>
<pre tabindex="0"><code>{
  &#34;jsonrpc&#34;: &#34;2.0&#34;,
  &#34;id&#34;: 0,                                    // Same id — this is the matching reply
  &#34;result&#34;: {
    &#34;protocolVersion&#34;: &#34;2024-11-05&#34;,        // Server confirms the version
    &#34;capabilities&#34;: {
      &#34;tools&#34;: { &#34;listChanged&#34;: false }    // Server exposes tools (and won&#39;t hot-reload them)
    },
    &#34;serverInfo&#34;: {
      &#34;name&#34;: &#34;my-first-mcp-server&#34;,
      &#34;version&#34;: &#34;1.0.0&#34;
    }
  }
}
</code></pre><p>1C. HOST → SERVER: INITIALIZED NOTIFICATION (NO REPLY EXPECTED)</p>
<pre tabindex="0"><code>{
  &#34;jsonrpc&#34;: &#34;2.0&#34;,
  &#34;method&#34;: &#34;notifications/initialized&#34;
  // No &#34;id&#34; field — this is a notification, not a request.
  // Notifications are fire-and-forget; the server never replies.
}
</code></pre><p>After this three-step handshake, the connection is live and both sides know what the other can do. Now the real fun begins.</p>
<h3 id="phase-2tool-discovery-toolslist">Phase 2 — Tool Discovery: tools/list</h3>
<p>The host immediately asks: “OK, what tools do you have?” This lets the AI know its full menu of capabilities before the user types a single character.</p>
<p>2A. HOST → SERVER: TOOLS/LIST REQUEST</p>
<pre tabindex="0"><code>{
  &#34;jsonrpc&#34;: &#34;2.0&#34;,
  &#34;id&#34;: 1,
  &#34;method&#34;: &#34;tools/list&#34;
  // No &#34;params&#34; needed — just asking for the full list.
}
</code></pre><p>2B. SERVER → HOST: TOOLS/LIST RESPONSE (WITH FULL INPUTSCHEMA)</p>
<pre tabindex="0"><code>{
  &#34;jsonrpc&#34;: &#34;2.0&#34;,
  &#34;id&#34;: 1,
  &#34;result&#34;: {
    &#34;tools&#34;: [
      {
        &#34;name&#34;: &#34;get_weather&#34;,
        &#34;description&#34;: &#34;Get current weather for any city.&#34;,
        &#34;inputSchema&#34;: {          // Standard JSON Schema object
          &#34;type&#34;: &#34;object&#34;,
          &#34;properties&#34;: {
            &#34;city&#34;: {
              &#34;type&#34;: &#34;string&#34;,
              &#34;description&#34;: &#34;City name, e.g. Belfast&#34;
            },
            &#34;unit&#34;: {
              &#34;type&#34;: &#34;string&#34;,
              &#34;enum&#34;: [&#34;celsius&#34;, &#34;fahrenheit&#34;],   // Constrained to specific values
              &#34;default&#34;: &#34;celsius&#34;
            }
          },
          &#34;required&#34;: [&#34;city&#34;]            // &#34;unit&#34; is optional, &#34;city&#34; is not
        }
      },
      {
        &#34;name&#34;: &#34;search_database&#34;,
        &#34;description&#34;: &#34;Search products by keyword with optional filters.&#34;,
        &#34;inputSchema&#34;: {
          &#34;type&#34;: &#34;object&#34;,
          &#34;properties&#34;: {
            &#34;query&#34;: { &#34;type&#34;: &#34;string&#34; },
            &#34;limit&#34;: {
              &#34;type&#34;: &#34;integer&#34;,
              &#34;minimum&#34;: 1,
              &#34;maximum&#34;: 100,
              &#34;default&#34;: 10
            },
            &#34;tags&#34;: {
              &#34;type&#34;: &#34;array&#34;,              // Arrays of primitives are fine
              &#34;items&#34;: { &#34;type&#34;: &#34;string&#34; }
            }
          },
          &#34;required&#34;: [&#34;query&#34;]
        }
      }
    ]
  }
}
</code></pre><blockquote>
<p>The inputSchema is standard <a href="https://json-schema.org/understanding-json-schema/about"><strong>JSON Schema</strong></a>. You can use type, enum, minimum, maximum, default, array, nested object — any JSON Schema feature. The AI model reads this schema and uses it to build valid arguments automatically. Good schemas = fewer mistakes by the AI.</p></blockquote>
<h3 id="phase-3tool-execution-toolscall">Phase 3 — Tool Execution: tools/call</h3>
<p>The user says something. The AI decides a tool is needed. It fires a tools/callwith the arguments it constructed from the schema.</p>
<p>3A. SIMPLE CALL — ONE STRING ARGUMENT</p>
<pre tabindex="0"><code>{
  &#34;jsonrpc&#34;: &#34;2.0&#34;,
  &#34;id&#34;: 2,
  &#34;method&#34;: &#34;tools/call&#34;,
  &#34;params&#34;: {
    &#34;name&#34;: &#34;get_weather&#34;,
    &#34;arguments&#34;: {
      &#34;city&#34;: &#34;Belfast&#34;           // Only the required field; &#34;unit&#34; uses its default
    }
  }
}
</code></pre><p>3B. COMPLEX CALL — MULTIPLE TYPES INCLUDING ARRAY</p>
<pre tabindex="0"><code>{
  &#34;jsonrpc&#34;: &#34;2.0&#34;,
  &#34;id&#34;: 3,
  &#34;method&#34;: &#34;tools/call&#34;,
  &#34;params&#34;: {
    &#34;name&#34;: &#34;search_database&#34;,
    &#34;arguments&#34;: {
      &#34;query&#34;: &#34;wireless headphones&#34;,
      &#34;limit&#34;: 5,                  // integer
      &#34;tags&#34;: [&#34;bluetooth&#34;, &#34;noise-cancelling&#34;]  // array
    }
  }
}
</code></pre><p>3C. SUCCESS RESPONSE — TEXT CONTENT</p>
<pre tabindex="0"><code>{
  &#34;jsonrpc&#34;: &#34;2.0&#34;,
  &#34;id&#34;: 2,
  &#34;result&#34;: {
    &#34;content&#34;: [
      {
        &#34;type&#34;: &#34;text&#34;,
        &#34;text&#34;: &#34;Current weather in Belfast: 12°C, wind 18 km/h&#34;
      }
    ],
    &#34;isError&#34;: false          // Optional. Explicitly marks success.
  }
}
</code></pre><p>3D. SUCCESS RESPONSE — MULTIPLE CONTENT BLOCKS</p>
<pre tabindex="0"><code>{
  &#34;jsonrpc&#34;: &#34;2.0&#34;,
  &#34;id&#34;: 3,
  &#34;result&#34;: {
    &#34;content&#34;: [
      {
        &#34;type&#34;: &#34;text&#34;,
        &#34;text&#34;: &#34;Found 2 results for &#39;wireless headphones&#39;:&#34;
      },
      {
        &#34;type&#34;: &#34;text&#34;,         // Second block — same message, more content
        &#34;text&#34;: &#34;1. Sony WH-1000XM5 — £279\n2. Bose QC45 — £249&#34;
      }
      // Could also include &#34;image&#34; blocks with base64 data,
      // or &#34;resource&#34; blocks referencing URIs
    ]
  }
}
</code></pre><h3 id="phase-4when-things-go-wrong-error-responses">Phase 4 — When Things Go Wrong: Error Responses</h3>
<p>There are two distinct flavours of error in MCP. Don’t mix them up — they mean very different things.</p>
<p>PROTOCOL ERROR — THE REQUEST ITSELF WAS MALFORMED</p>
<pre tabindex="0"><code>{
  &#34;jsonrpc&#34;: &#34;2.0&#34;,
  &#34;id&#34;: 4,
  &#34;error&#34;: {                         // &#34;error&#34; field, NOT &#34;result&#34;
    &#34;code&#34;: -32602,                  // Standard JSON-RPC error code
    &#34;message&#34;: &#34;Invalid params&#34;,
    &#34;data&#34;: {
      &#34;details&#34;: &#34;Required field &#39;city&#39; is missing&#34;
    }
  }
}
</code></pre><p><img loading="lazy" src="https://sukachvitally.github.io/posts/mcp-the-protocol-that-finally-taught-ai-to-stop-being-useless/image-05.png"></p>
<p>TOOL-LEVEL ERROR — THE TOOL RAN BUT THE OPERATION FAILED</p>
<pre tabindex="0"><code>{
  &#34;jsonrpc&#34;: &#34;2.0&#34;,
  &#34;id&#34;: 5,
  &#34;result&#34;: {                        // Still &#34;result&#34;, NOT &#34;error&#34; !
    &#34;content&#34;: [
      {
        &#34;type&#34;: &#34;text&#34;,
        &#34;text&#34;: &#34;City &#39;Narnia&#39; not found in geocoding database.&#34;
      }
    ],
    &#34;isError&#34;: true               // Flag tells the AI: tool ran, but reported failure
  }
}
</code></pre><blockquote>
<p><strong>Key distinction:</strong> A <em>protocol error</em> (using the &ldquo;error&rdquo; field) means the communication broke — bad JSON, unknown method, missing required fields. A <em>tool error</em> (using &ldquo;result&rdquo; with &ldquo;isError&rdquo;: true) means the tool ran fine but the operation itself failed — user not found, city doesn&rsquo;t exist, API returned 404. Always use isError: true for business logic failures so the AI can read the message and react intelligently.</p></blockquote>
<h3 id="bonus-resource-reads-and-notifications">Bonus: Resource Reads and Notifications</h3>
<p>Tools aren’t the only thing MCP can do. Here’s what a <strong>resource read</strong> looks like — useful when you want to expose file contents or database records the AI can browse:</p>
<p>RESOURCES/READ REQUEST AND RESPONSE</p>
<pre tabindex="0"><code>// Request
{
  &#34;jsonrpc&#34;: &#34;2.0&#34;,
  &#34;id&#34;: 6,
  &#34;method&#34;: &#34;resources/read&#34;,
  &#34;params&#34;: {
    &#34;uri&#34;: &#34;file:///home/user/notes.txt&#34;    // Resource URIs identify the data source
  }
}
// Response
{
  &#34;jsonrpc&#34;: &#34;2.0&#34;,
  &#34;id&#34;: 6,
  &#34;result&#34;: {
    &#34;contents&#34;: [
      {
        &#34;uri&#34;: &#34;file:///home/user/notes.txt&#34;,
        &#34;mimeType&#34;: &#34;text/plain&#34;,
        &#34;text&#34;: &#34;Buy milk. Deploy the thing. Don&#39;t forget stand-up.&#34;
      }
    ]
  }
}
</code></pre><p>And a <strong>server notification</strong> — fire-and-forget messages the server sends without being asked (no id, no reply expected):</p>
<p>SERVER → HOST NOTIFICATION (NO REPLY)</p>
<pre tabindex="0"><code>{
  &#34;jsonrpc&#34;: &#34;2.0&#34;,
  &#34;method&#34;: &#34;notifications/tools/list_changed&#34;,
  // No &#34;id&#34; — this is a push notification.
  // The server is telling the host: &#34;Hey, my tool list changed.
  //  Please call tools/list again to refresh.&#34;
}
</code></pre><p>Clean and readable. No magic, no wizardry. Just JSON going back and forth. This predictability is exactly what makes MCP powerful — any language, any framework, any tool can implement it.</p>
<h3 id="two-ways-to-connect-stdio-vshttpsse">Two Ways to Connect: stdio vs HTTP/SSE</h3>
<p>MCP servers can communicate via two transport mechanisms:</p>
<p><strong>stdio (Standard I/O)</strong> — The simplest option. Your server reads from stdin and writes to stdout. The host app spawns the server as a subprocess. Perfect for local tools running on the same machine.</p>
<p><strong>HTTP + SSE (Server-Sent Events)</strong> — For remote servers. The client sends POST requests to the server, and the server streams events back. Use this when your MCP server lives on a different machine (a cloud server, a colleague’s computer, etc.).</p>
<blockquote>
<p>Start with <strong>stdio</strong>. It’s simpler, requires no networking setup, and is perfect for local tooling. You can always switch to HTTP later.</p></blockquote>
<h3 id="tutorial-building-serverpy-step-bystep">Tutorial: Building server.py Step by Step</h3>
<p>Enough theory. Let’s write the actual code. Below is a complete, annotated server.py that exposes three tools: a dad joke generator, a task list, and a real live weather lookup. We&rsquo;ll build it section by section so nothing is a mystery.</p>
<p>📦<strong>Install first:</strong> pip install mcp — that&rsquo;s the only dependency. Python 3.10+ required.</p>
<h4 id="part-1imports--server-instantiation">Part 1 — Imports &amp; Server Instantiation</h4>
<p>The MCP SDK ships everything you need in the mcp package. We create a Server instance and tell it our name and version — this is what Claude Desktop sees in the handshake.</p>
<p><strong>server.py — Part 1: imports and setup</strong></p>
<pre tabindex="0"><code># Standard library — no extra installs needed for HTTP calls
import asyncio
import random
import json
import urllib.request
import urllib.parse
import sys                   # needed for stderr debug output
# MCP SDK - install with: pip install mcp
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp import types
# Create the server. The name/version appear during the MCP handshake.
# Claude Desktop displays this name in its connected servers list.
app = Server(&#34;my-first-mcp-server&#34;)
</code></pre><h4 id="part-2declaring-tools-with-applist_tools">Part 2 — Declaring Tools with @app.list_tools()</h4>
<p>This decorator registers the handler that responds to tools/list requests. The AI calls this automatically on startup to discover your full menu of capabilities. <strong>The descriptions you write here are read by the AI</strong> — write them like you&rsquo;re explaining the tool to a smart colleague, not a compiler.</p>
<p><strong>server.py — Part 2: tool declarations</strong></p>
<pre tabindex="0"><code>@app.list_tools()
async def list_tools() -&gt; list[types.Tool]:
    return [
        # ── Tool 1: dad_joke ──────────────────────────────────────
        # No arguments. The inputSchema is an empty object.
        # The AI will call this whenever the user needs cheering up.
        types.Tool(
            name=&#34;dad_joke&#34;,
            description=&#34;Returns a random, gloriously terrible dad joke. Use when morale is low.&#34;,
            inputSchema={
                &#34;type&#34;: &#34;object&#34;,
                &#34;properties&#34;: {},    # no arguments - jokes are free
                &#34;required&#34;: [],
            },
        ),
        # ── Tool 2: get_tasks ─────────────────────────────────────
        # One required string argument: username.
        # &#34;required&#34; list enforces that the AI must supply it.
        types.Tool(
            name=&#34;get_tasks&#34;,
            description=&#34;Returns the task list for a given user.&#34;,
            inputSchema={
                &#34;type&#34;: &#34;object&#34;,
                &#34;properties&#34;: {
                    &#34;username&#34;: {
                        &#34;type&#34;: &#34;string&#34;,
                        &#34;description&#34;: &#34;The username whose tasks to fetch, e.g. &#39;alice&#39;&#34;,
                    },
                },
                &#34;required&#34;: [&#34;username&#34;],
            },
        ),
        # ── Tool 3: get_weather ───────────────────────────────────
        # Calls the real Open-Meteo API. No API key required.
        # &#34;unit&#34; is optional - has a default value in the handler.
        types.Tool(
            name=&#34;get_weather&#34;,
            description=&#34;Get current weather for any city. Uses Open-Meteo (free, no API key needed).&#34;,
            inputSchema={
                &#34;type&#34;: &#34;object&#34;,
                &#34;properties&#34;: {
                    &#34;city&#34;: {
                        &#34;type&#34;: &#34;string&#34;,
                        &#34;description&#34;: &#34;City name, e.g. Belfast, Tokyo, New York&#34;,
                    },
                    &#34;unit&#34;: {
                        &#34;type&#34;: &#34;string&#34;,
                        &#34;enum&#34;: [&#34;celsius&#34;, &#34;fahrenheit&#34;],
                        &#34;description&#34;: &#34;Temperature unit. Defaults to celsius.&#34;,
                    },
                },
                &#34;required&#34;: [&#34;city&#34;],   # &#34;unit&#34; is optional - omit from required
            },
        ),
    ]
</code></pre><h4 id="part-3handling-tool-calls-with-appcall_tool">Part 3 — Handling Tool Calls with @app.call_tool()</h4>
<p>When the AI decides to invoke a tool, this handler fires. It receives the tool name and the arguments dict the AI built from your schema. Return a list of TextContent objects — even if there&rsquo;s only one. Always return a list; the protocol expects it.</p>
<p><strong>server.py — Part 3: tool logic</strong></p>
<pre tabindex="0"><code>@app.call_tool()
async def call_tool(name: str, arguments: dict) -&gt; list[types.TextContent]:
# ── dad_joke ──────────────────────────────────────────────
    if name == &#34;dad_joke&#34;:
        jokes = [
            &#34;Why don&#39;t scientists trust atoms? Because they make up everything.&#34;,
            &#34;I told my wife she was drawing her eyebrows too high. She looked surprised.&#34;,
            &#34;Why do programmers prefer dark mode? Because light attracts bugs.&#34;,
            &#34;I asked my dog what two minus two is. He said nothing.&#34;,
            &#34;Why did the MCP server go to therapy? Too many unhandled exceptions.&#34;,
        ]
        return [types.TextContent(type=&#34;text&#34;, text=random.choice(jokes))]
    # ── get_tasks ─────────────────────────────────────────────
    # In real life you&#39;d query a DB here. We use a dict for clarity.
    if name == &#34;get_tasks&#34;:
        username = arguments.get(&#34;username&#34;, &#34;&#34;).lower()
        fake_tasks = {
            &#34;alice&#34;: [&#34;Review PR #42&#34;, &#34;Update docs&#34;, &#34;Fix that bug you&#39;ve been ignoring&#34;],
            &#34;bob&#34;:   [&#34;Stand-up meeting&#34;, &#34;Drink coffee&#34;, &#34;Question all life choices&#34;],
        }
        tasks = fake_tasks.get(username, [&#34;No tasks found. Either very productive or very lazy.&#34;])
        formatted = &#34;\n&#34;.join(f&#34;{i+1}. {t}&#34; for i, t in enumerate(tasks))
        return [types.TextContent(type=&#34;text&#34;, text=f&#34;Tasks for {username}:\n{formatted}&#34;)]
    # ── get_weather ───────────────────────────────────────────
    # Two HTTP calls: geocode the city name → get weather by lat/lon.
    # Uses urllib (stdlib) so no extra dependencies.
    if name == &#34;get_weather&#34;:
        city = arguments.get(&#34;city&#34;, &#34;&#34;)
        unit = arguments.get(&#34;unit&#34;, &#34;celsius&#34;)   # default if AI omits it
        # Step 1 - turn city name into coordinates
        geo_url = (
            &#34;https://geocoding-api.open-meteo.com/v1/search&#34;
            f&#34;?name={urllib.parse.quote(city)}&amp;count=1&#34;
        )
        with urllib.request.urlopen(geo_url) as r:
            geo = json.loads(r.read())
        if not geo.get(&#34;results&#34;):
            # Tool ran fine, but the operation failed - use isError flag
            return [types.TextContent(type=&#34;text&#34;, text=f&#39;City &#34;{city}&#34; not found.&#39;)]
        result = geo[&#34;results&#34;][0]
        lat, lon, found_name = result[&#34;latitude&#34;], result[&#34;longitude&#34;], result[&#34;name&#34;]
        # Step 2 - fetch current weather for those coordinates
        wx_url = (
            &#34;https://api.open-meteo.com/v1/forecast&#34;
            f&#34;?latitude={lat}&amp;longitude={lon}&#34;
            f&#34;¤t_weather=true&amp;temperature_unit={unit}&#34;
        )
        with urllib.request.urlopen(wx_url) as r:
            wx = json.loads(r.read())
        cw = wx[&#34;current_weather&#34;]
        symbol = &#34;°C&#34; if unit == &#34;celsius&#34; else &#34;°F&#34;
        return [types.TextContent(            type=&#34;text&#34;,            text=f&#34;{found_name}: {cw[&#39;temperature&#39;]}{symbol}, wind {cw[&#39;windspeed&#39;]} km/h&#34;,        )]
    # Unknown tool - raise so MCP returns a proper protocol error
    raise ValueError(f&#34;Unknown tool: {name}&#34;)
</code></pre><h3 id="part-4wiring-up-stdio-transport">Part 4 — Wiring Up stdio Transport</h3>
<p>The final piece connects the server to the MCP host via stdin/stdout. stdio_server() returns two async streams; we hand them to app.run() and it takes care of the rest. The key thing to remember: <strong>never</strong> <strong>print() to stdout</strong> anywhere in your code — that channel belongs to MCP. Debug with sys.stderr only.</p>
<p>server.py — Part 4: entry point</p>
<pre tabindex="0"><code>async def main():
    # stdio_server() yields (read_stream, write_stream) for the MCP host.
    # Everything inside this block is the server&#39;s lifetime.
    async with stdio_server() as (read_stream, write_stream):
        print(&#34;MCP server started&#34;, file=sys.stderr)   # stderr is safe for debug
        await app.run(
            read_stream,
            write_stream,
            app.create_initialization_options(),
        )
if __name__ == &#34;__main__&#34;:
    asyncio.run(main())
</code></pre><p>💡<strong>Why</strong> <strong>async?</strong> MCP uses async I/O because servers often wait on multiple things at once — the host, network calls, file reads. Python&rsquo;s asyncio lets a single thread handle all of that without blocking. You don&rsquo;t need to understand the internals; just know that every handler must be async def, and use await before any I/O call.</p>
<h3 id="connecting-your-server-to-claudedesktop">Connecting Your Server to Claude Desktop</h3>
<p>Once you’ve written your server.py, plugging it into Claude Desktop takes two minutes. Download Claude Desktop from <strong>claude.ai/download</strong> — it&rsquo;s free and has an MCP client built in. Then locate the config file for your OS:</p>
<p><img loading="lazy" src="https://sukachvitally.github.io/posts/mcp-the-protocol-that-finally-taught-ai-to-stop-being-useless/image-06.png"></p>
<p>Register your server by adding it to that file:</p>
<p>CLAUDE_DESKTOP_CONFIG.JSON</p>
<pre tabindex="0"><code>{
  &#34;mcpServers&#34;: {
    &#34;my-first-mcp-server&#34;: {
      &#34;command&#34;: &#34;python&#34;,
      &#34;args&#34;: [
        &#34;/absolute/path/to/my-first-mcp-server/server.py&#34;
      ],
      &#34;env&#34;: {
        &#34;MY_API_KEY&#34;: &#34;sk-...&#34;    // Pass secrets here; read with os.environ in Python
      }
    }
  }
}
</code></pre><blockquote>
<p><strong>Important:</strong> Use the <em>absolute path</em> to your server.py file. No tildes, no relative paths. Get the path with pwd on Mac/Linux or cd on Windows, and paste the full thing in.</p></blockquote>
<p>Fully quit Claude Desktop (not just close the window) and reopen it. Look for the 🔧 tools icon in the chat input — that means MCP is connected. Then just ask Claude naturally: <em>“Tell me a dad joke”</em> or <em>“What tasks does Alice have?”</em> — it figures out on its own when to call your tools. No special syntax needed.</p>
<h3 id="best-practices-aka-mistakes-im-saving-youfrom">Best Practices (a.k.a. Mistakes I’m Saving You From)</h3>
<p><strong>Write excellent descriptions</strong></p>
<blockquote>
<p>The AI reads your tool descriptions to decide when to use them. Vague descriptions = confused AI = bad results. Be specific: what does the tool do, when should it be used, what do the arguments mean?</p></blockquote>
<p><strong>Never</strong> <strong>print() to stdout for debug output</strong></p>
<blockquote>
<p>In stdio transport, stdout is the MCP communication channel. Any stray print() call will corrupt the protocol stream. Always write debug output to stderr: print(&ldquo;debug&rdquo;, file=sys.stderr).</p></blockquote>
<p><strong>Validate your inputs</strong></p>
<blockquote>
<p>The AI sometimes passes unexpected values. Check that required arguments exist. Validate formats. Return helpful error messages rather than cryptic crashes.</p></blockquote>
<p><strong>Keep tools focused</strong></p>
<blockquote>
<p>One tool, one job. Don’t build a do_everything() tool. Small, well-named tools are easier for the AI to reason about and call correctly. Think Unix philosophy: do one thing well.</p></blockquote>
<p><strong>Use environment variables for secrets</strong></p>
<blockquote>
<p>Never hardcode API keys. Pass them via the env key in your claude_desktop_config.json: &ldquo;env&rdquo;: { &ldquo;API_KEY&rdquo;: &ldquo;your-key-here&rdquo; }. Read with os.environ[&ldquo;API_KEY&rdquo;] in your server.</p></blockquote>
<h3 id="the-mcp-ecosystem-you-dont-have-to-build-everything">The MCP Ecosystem: You Don’t Have to Build Everything</h3>
<p>Here’s some great news: the open-source community has already built hundreds of MCP servers you can just plug in and use. Some highlights:</p>
<p><img loading="lazy" src="https://sukachvitally.github.io/posts/mcp-the-protocol-that-finally-taught-ai-to-stop-being-useless/image-07.png"></p>
<p>Find these and more at <strong>github.com/modelcontextprotocol/servers</strong> — the official repo — and on <strong>mcp.so</strong>, a community directory. There’s a good chance what you need already exists.</p>
<h3 id="wrapping-up-why-thismatters">Wrapping Up: Why This Matters</h3>
<p>MCP isn’t just a neat technical standard. It represents a fundamental shift in what AI assistants actually are.</p>
<p>Before MCP: AI as an <em>oracle</em>. You ask, it tells. Great for knowledge questions. Useless for action.</p>
<p>After MCP: AI as an <em>agent</em>. You describe what you want, it figures out which tools to use, calls them, interprets the results, and gets the job done. The AI becomes an active participant rather than a fancy search engine.</p>
<blockquote>
<p>The future isn’t AI that can describe how to fix your code. It’s AI that fixes your code, runs the tests, creates the PR, and messages your team. MCP is the foundation that makes all of that possible.</p></blockquote>
<p>You’ve now seen the full picture: the history, the architecture, the wire format, and a working server from scratch. The next step is yours. Pick a tool you use daily — a database, an API, a local service — and wrap it in an MCP server. Once you’ve done it once, you’ll find excuses to do it for everything.</p>
<p>Happy building. 🚀</p>
]]></content:encoded></item><item><title>Claude Code: The Complete Guide Nobody Wrote</title><link>https://sukachvitally.github.io/posts/claude-code-the-complete-guide-nobody-wrote/</link><pubDate>Tue, 02 Jun 2026 17:21:27 +0000</pubDate><guid>https://sukachvitally.github.io/posts/claude-code-the-complete-guide-nobody-wrote/</guid><description>&lt;p>&lt;img loading="lazy" src="https://sukachvitally.github.io/posts/claude-code-the-complete-guide-nobody-wrote/image-01.png">&lt;/p>
&lt;h3 id="the-agentic-loop-how-claude-actuallythinks">The Agentic Loop (How Claude Actually Thinks)&lt;/h3>
&lt;p>Before anything else, you need to understand what makes Claude Code different from your IDE’s autocomplete. Autocomplete guesses the next word. Claude Code runs a loop — it gathers context, takes action, verifies results, then repeats until the task is done. Think of it less like a text editor feature and more like a developer who works asynchronously while you make coffee.&lt;/p></description><content:encoded><![CDATA[<p><img loading="lazy" src="https://sukachvitally.github.io/posts/claude-code-the-complete-guide-nobody-wrote/image-01.png"></p>
<h3 id="the-agentic-loop-how-claude-actuallythinks">The Agentic Loop (How Claude Actually Thinks)</h3>
<p>Before anything else, you need to understand what makes Claude Code different from your IDE’s autocomplete. Autocomplete guesses the next word. Claude Code runs a loop — it gathers context, takes action, verifies results, then repeats until the task is done. Think of it less like a text editor feature and more like a developer who works asynchronously while you make coffee.</p>
<p>The loop has three phases that blend together depending on the task:</p>
<p><img loading="lazy" src="https://sukachvitally.github.io/posts/claude-code-the-complete-guide-nobody-wrote/image-02.png"></p>
<p>The loop adapts to what you ask. A question might only hit phase 1. A bug fix cycles through all three phases multiple times. A full feature might chain dozens of tool calls before Claude surfaces for air. You’re part of the loop too — you can interrupt at any point with Ctrl+C and steer it in a different direction.</p>
<blockquote>
<p><strong>THE HONEST SUMMARY</strong></p></blockquote>
<blockquote>
<p>Claude Code is basically a language model with access to your entire computer strapped to it. It can read everything, run anything you could run from a terminal, and it just keeps going until it either finishes the task or you tell it to stop. This is either exhilarating or terrifying depending on how much you trust your test coverage.</p></blockquote>
<h3 id="sessions--how-claude-remembers-ordoesnt">Sessions &amp; How Claude Remembers (Or Doesn’t)</h3>
<p>Here’s the thing about Claude: <strong>every session starts with a completely blank memory.</strong> No recollection of last week’s chat. No memory of that convention you mentioned. Tabula rasa. Every. Single. Time. If you don’t explicitly persist knowledge, it’s gone when the session closes.</p>
<p>This isn’t a bug — it’s how LLMs work. But Claude Code gives you two mechanisms to work around it:</p>
<p><img loading="lazy" src="https://sukachvitally.github.io/posts/claude-code-the-complete-guide-nobody-wrote/image-03.png"></p>
<h4 id="how-a-session-actuallystarts">How a session actually starts</h4>
<p>When you run claude in a directory, here&rsquo;s what happens in the first few seconds before you&rsquo;ve typed anything:</p>
<ol>
<li>**Settings load (all scopes)**Global ~/.claude/settings.json → project .claude/settings.json → local .claude/settings.local.json. Scalar settings use the most specific value; array settings (like permissions) merge across all scopes.</li>
<li><strong>MCP servers connect</strong>All configured MCP servers from .mcp.json and ~/.claude.json start up. Tool schemas are deferred and load on demand. If a server fails to connect, Claude continues without it — it just loses those tools.</li>
<li><strong>CLAUDE.md files load into context</strong>Org-level → user-level (~/.claude/CLAUDE.md) → project-level (./CLAUDE.md) → local (./CLAUDE.local.md). Plus your ~/.claude/MEMORY.md auto memory. All of this costs context window tokens immediately.</li>
<li><strong>Plugins activate</strong>All enabled plugins load their skills, hooks, MCP servers, and settings. Run /reload-plugins to activate changes mid-session.</li>
<li><strong>Session is ready</strong>You see the prompt. Everything above has already happened. Claude now has your project context and is ready to work.</li>
</ol>
<blockquote>
<p><strong>CONTEXT WINDOW BUDGET</strong></p></blockquote>
<blockquote>
<p>Everything loaded at startup costs tokens before you type a single character. A bloated CLAUDE.md, a dozen MCP servers, and a large MEMORY.md can eat a significant chunk of your context window. Run /status to see current token usage. Use /compact when a session gets long.</p></blockquote>
<h3 id="theclaude-directoryevery-folder-explained">The .claude Directory — Every Folder Explained</h3>
<p>The .claude/ folder is Claude Code&rsquo;s configuration home in your project. There&rsquo;s also ~/.claude/ in your home directory for global settings. Here&rsquo;s the complete layout with every file and folder you&rsquo;ll actually encounter:</p>
<p><img loading="lazy" src="https://sukachvitally.github.io/posts/claude-code-the-complete-guide-nobody-wrote/image-04.png"></p>
<blockquote>
<p><strong>COMMIT STRATEGY</strong></p></blockquote>
<blockquote>
<p>Everything marked <strong>committed</strong> should go in Git so your whole team gets the same Claude setup. Everything marked <strong>gitignored</strong> is personal — Claude automatically adds settings.local.json to your global Git ignore. Add CLAUDE.local.md to your project&rsquo;s .gitignore manually.</p></blockquote>
<h3 id="claudemdthe-fullstory">CLAUDE.md — The Full Story</h3>
<p>You’ve heard about CLAUDE.md. Everyone mentions it. But few people explain the <em>entire</em> system behind it, which has four separate scope levels that load in a specific order:</p>
<p><img loading="lazy" src="https://sukachvitally.github.io/posts/claude-code-the-complete-guide-nobody-wrote/image-05.png"></p>
<p>They load in that order — managed first, local last. More specific scopes appear later in the context window, giving them higher effective priority.</p>
<h4 id="what-to-actually-put-in-claudemd">What to actually put in CLAUDE.md</h4>
<p>The golden rule: add something to CLAUDE.md when Claude makes the same mistake a second time. Here’s a battle-tested example for a Spring Boot project:</p>
<p><img loading="lazy" src="https://sukachvitally.github.io/posts/claude-code-the-complete-guide-nobody-wrote/image-06.png"></p>
<blockquote>
<p><strong>SIZE MATTERS</strong></p></blockquote>
<blockquote>
<p>Target under 200 lines per CLAUDE.md. Longer files still load fully but adherence drops. If it’s getting long, split topic-specific rules into .claude/rules/ files and skills for complex procedures — they load conditionally and save your context window.</p></blockquote>
<h4 id="auto-memory-memorymd">Auto Memory (MEMORY.md)</h4>
<p>This is the magical part most people miss. Claude can write its own notes — automatically. When you correct Claude, it optionally remembers that correction for next session in ~/.claude/MEMORY.md. The first 200 lines or 25KB load at the start of every session.</p>
<p>Enable it with /memory in session or toggle it in settings. Then: tell Claude something once, it remembers it forever (until you delete the entry). This is how you get a Claude that feels like it&rsquo;s been on your team for months.</p>
<h3 id="settingsjsonthe-enforcement-layer">settings.json — The Enforcement Layer</h3>
<p>There’s a critical distinction most people miss: <strong>CLAUDE.md is guidance Claude reads.settings.json is configuration Claude Code enforces.</strong> One is a polite suggestion. The other is a locked door.</p>
<p>Settings live in .claude/settings.json (project/team) and .claude/settings.local.json(personal overrides). Here&rsquo;s what you can configure:</p>
<p><img loading="lazy" src="https://sukachvitally.github.io/posts/claude-code-the-complete-guide-nobody-wrote/image-07.png"></p>
<blockquote>
<p><strong>ALLOW VS DENY PRECEDENCE</strong></p></blockquote>
<blockquote>
<p>Deny rules always win over allow rules. If you allow Bash(kubectl *) and deny Bash(kubectl delete *), the delete command is blocked. Patterns support wildcards — Bash(npm test *)matches any command starting with npm test.</p></blockquote>
<h3 id="rulesscoped-instructions-that-load-ondemand">rules/ — Scoped Instructions That Load On Demand</h3>
<p>Once your CLAUDE.md hits 200 lines, start splitting it into .claude/rules/. Rules are Markdown files that can load <em>conditionally</em> based on which files Claude is working with — only when it reads a file that matches the rule&rsquo;s path pattern. This keeps your global context lean while ensuring Claude gets the right rules at the right time.</p>
<p><img loading="lazy" src="https://sukachvitally.github.io/posts/claude-code-the-complete-guide-nobody-wrote/image-08.png"></p>
<p><img loading="lazy" src="https://sukachvitally.github.io/posts/claude-code-the-complete-guide-nobody-wrote/image-09.png"></p>
<blockquote>
<p><strong>THE REAL POWER</strong></p></blockquote>
<blockquote>
<p>A rule file without paths: frontmatter loads every session like CLAUDE.md. A rule WITH paths:only loads when Claude reads a matching file. This means your Kafka rules don&rsquo;t eat context window during a CLAUDE.md update, and your migration rules only appear when Claude actually opens a migration file.</p></blockquote>
<h3 id="skillsyour-custom-slashcommands">skills/ — Your Custom Slash Commands</h3>
<p>Skills are reusable prompts you or Claude can invoke by name using /skill-name. Think of them as saved workflows — things you do repeatedly that are more than a one-liner but less than a custom tool. You write the instructions once in a SKILL.md, and invoke it whenever you need it.</p>
<p><img loading="lazy" src="https://sukachvitally.github.io/posts/claude-code-the-complete-guide-nobody-wrote/image-10.png"></p>
<p>Now you type /db-migrate and Claude walks through all seven steps. No more explaining the process from scratch every time. Skills also accept arguments via $ARGUMENTS:</p>
<p><img loading="lazy" src="https://sukachvitally.github.io/posts/claude-code-the-complete-guide-nobody-wrote/image-11.png"></p>
<p>Usage: /review-pr 127 — Claude reviews MR #127 with your exact conventions baked in.</p>
<blockquote>
<p>SKILLS VS COMMANDS</p></blockquote>
<blockquote>
<p>You may see references to .claude/commands/ in older docs — this is the same mechanism as skills. For new workflows, use skills/ instead: same /name invocation, plus you can bundle supporting files alongside the SKILL.md.</p></blockquote>
<h3 id="hooksthe-automation-engine">Hooks — The Automation Engine</h3>
<p>Hooks are shell commands that fire automatically at specific points in Claude’s lifecycle. They’re deterministic — they <em>always</em> run when triggered, regardless of what Claude decides. This is the difference between “please format the file” (Claude might forget) and “PostToolUse hook runs Spotless” (it definitely happens).</p>
<h4 id="available-hookevents">Available hook events</h4>
<p><strong>PreToolUse</strong><em>Fires before any tool call. Can block execution (exit 2). Use to protect files or validate commands.</em></p>
<p><strong>PostToolUse</strong><em>Fires after a tool completes. Perfect for auto-formatting files after Claude edits them.</em></p>
<p><strong>Notification</strong><em>Fires when Claude needs your input. Desktop notification so you don’t stare at the terminal.</em></p>
<p><strong>SessionStart</strong><em>Fires when a session begins. Inject additional context, set environment, run setup scripts.</em></p>
<p><strong>PreCompact</strong><em>Fires before conversation compaction. Re-inject context that would otherwise be lost.</em></p>
<p><strong>Stop</strong><em>Fires when Claude finishes a response. Useful for post-response validation or logging.</em></p>
<h3 id="practical-examples-for-a-javaspring-bootproject">Practical examples for a Java/Spring Boot project</h3>
<p><img loading="lazy" src="https://sukachvitally.github.io/posts/claude-code-the-complete-guide-nobody-wrote/image-12.png"></p>
<p><img loading="lazy" src="https://sukachvitally.github.io/posts/claude-code-the-complete-guide-nobody-wrote/image-13.png"></p>
<blockquote>
<p><strong>EXIT CODES MATTER</strong></p></blockquote>
<blockquote>
<p>exit 0 = allow. exit 1 = warn Claude but let it continue. exit 2 = hard block — Claude gets told why and has to try something else. It&rsquo;s the digital equivalent of a bouncer who also explains the dress code.</p></blockquote>
<h3 id="mcpconnecting-claude-to-theworld">MCP — Connecting Claude to the World</h3>
<p>Model Context Protocol (MCP) is an open standard for connecting AI to external tools. Without MCP, Claude can only see your filesystem and run terminal commands. With MCP, Claude can query your Jira board, check Sentry errors, read Slack messages, query your PostgreSQL database, and more — all without you copying and pasting anything.</p>
<blockquote>
<p><strong>THE MENTAL MODEL</strong></p></blockquote>
<blockquote>
<p>MCP is how Claude gets a login to your tools. Without it: “here’s a pasted error from Sentry.” With it: “Claude, check what errors happened in prod in the last hour and correlate them with my recent commits.”</p></blockquote>
<h4 id="three-types-of-mcpservers">Three types of MCP servers</h4>
<p><img loading="lazy" src="https://sukachvitally.github.io/posts/claude-code-the-complete-guide-nobody-wrote/image-14.png"></p>
<h4 id="project-scoped-mcp-mcpjson">Project-scoped MCP (.mcp.json)</h4>
<p>Put your team’s MCP servers in .mcp.json at the project root. This file gets committed to Git so everyone on the team gets the same tools:</p>
<p><img loading="lazy" src="https://sukachvitally.github.io/posts/claude-code-the-complete-guide-nobody-wrote/image-15.png"></p>
<h4 id="user-scoped-mcp-personal-tools">User-scoped MCP (personal tools)</h4>
<p>For tools only you need (not your team), use the &ndash;scope user flag. This writes to ~/.claude.json instead of .mcp.json:</p>
<p><img loading="lazy" src="https://sukachvitally.github.io/posts/claude-code-the-complete-guide-nobody-wrote/image-16.png"></p>
<blockquote>
<p><strong>CHANNELS: MCP THAT PUSHES TO CLAUDE</strong></p></blockquote>
<blockquote>
<p>Normal MCP is pull — Claude queries a server when it needs something. Channels flip this: an MCP server <em>pushes</em> messages into your running session. This means Claude can react to CI results, monitoring alerts, or Slack messages while you’re doing something else. Enable with &ndash;channelsflag on startup.</p></blockquote>
<h3 id="pluginspackage--share-everything">Plugins — Package &amp; Share Everything</h3>
<p>Everything you’ve built — skills, hooks, MCP servers, rules — can be packaged into a <strong>plugin</strong> and shared with your team, your org, or the whole world. Plugins are just directories with a .claude-plugin/plugin.json manifest. They&rsquo;re the difference between &ldquo;config that lives in one project&rdquo; and &ldquo;config that lives everywhere.&rdquo;</p>
<h4 id="standalone-config-vsplugins">Standalone config vs plugins</h4>
<p><img loading="lazy" src="https://sukachvitally.github.io/posts/claude-code-the-complete-guide-nobody-wrote/image-17.png"></p>
<h4 id="plugin-structure">Plugin structure</h4>
<p><img loading="lazy" src="https://sukachvitally.github.io/posts/claude-code-the-complete-guide-nobody-wrote/image-18.png"></p>
<p><img loading="lazy" src="https://sukachvitally.github.io/posts/claude-code-the-complete-guide-nobody-wrote/image-19.png"></p>
<h4 id="installing-aplugin">Installing a plugin</h4>
<p><img loading="lazy" src="https://sukachvitally.github.io/posts/claude-code-the-complete-guide-nobody-wrote/image-20.png"></p>
<blockquote>
<p><strong>THE REAL VALUE</strong></p></blockquote>
<blockquote>
<p>Your platform team builds a plugin once with all the Spring Boot conventions, Kafka rules, code review skills, and MCP servers your org uses. Every developer installs it once. Every project gets the same setup. No more “did you add the Flyway rules to your CLAUDE.md?” Slack messages.</p></blockquote>
<h3 id="subagentsclaude-spawningclaude">Subagents — Claude Spawning Claude</h3>
<p>This is the part where things get genuinely science-fiction. Subagents let Claude spin up <em>other Claude instances</em> to handle subtasks in parallel or in isolated contexts. The main Claude orchestrates; the subagents execute. Think distributed computing, but the workers are also AI and can also spawn more workers.</p>
<p><img loading="lazy" src="https://sukachvitally.github.io/posts/claude-code-the-complete-guide-nobody-wrote/image-21.png"></p>
<p>Subagents can maintain their own MEMORY.md too. Imagine a &ldquo;database specialist&rdquo; subagent that remembers every schema change it&rsquo;s ever helped with. That&rsquo;s the direction this is heading.</p>
<blockquote>
<p><strong>WHEN TO USE SUBAGENTS</strong></p></blockquote>
<blockquote>
<p>For most day-to-day tasks, you don’t need subagents. They’re most useful when you have truly parallel workstreams (implement feature A and feature B simultaneously without conflicts), when you need a clean context for a specific task, or when you’re building automation pipelines that run without human interaction.</p></blockquote>
<h3 id="complete-quick-reference">Complete Quick Reference</h3>
<ul>
<li><strong>.claude/settings.json</strong> — permissions + hooks + model config. Enforced, not just read.</li>
<li><strong>CLAUDE.md</strong> — persistent instructions. Team version in ./CLAUDE.md, personal in ~/.claude/CLAUDE.md.</li>
<li><strong>MEMORY.md</strong> — auto-written by Claude. Persists learnings across sessions (200 lines / 25KB limit).</li>
<li><strong>.claude/rules/</strong> — conditional instruction files. Use paths: frontmatter to load only for relevant files.</li>
<li><strong>.claude/skills/</strong> — custom /commands. Each skill = a folder with SKILL.md. Accepts $ARGUMENTS.</li>
<li><strong>hooks</strong> — deterministic automation. PreToolUse (block/allow), PostToolUse (format), Notification (alert).</li>
<li><strong>.mcp.json</strong> — team MCP servers (committed). ~/.claude.json for personal servers.</li>
<li><strong>Plugins</strong> — packaged, shareable bundles of skills + hooks + MCP + settings. Install with /plugin install.</li>
<li><strong>Subagents</strong> — parallel Claude instances for complex multi-track workflows. Three isolation levels.</li>
<li><strong>/init</strong> — always run first in a new project. Generates CLAUDE.md automatically.</li>
<li><strong>/compact</strong> — compresses long sessions. <strong>/clear</strong> = fresh start (CLAUDE.md reloads).</li>
<li><strong>/mcp</strong> — shows server status + tool count. <strong>/hooks</strong> — shows configured hooks. <strong>/memory</strong> — edit CLAUDE.md.</li>
</ul>
]]></content:encoded></item></channel></rss>