<?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>Tutorial on RockB</title><link>https://baeseokjae.github.io/tags/tutorial/</link><description>Recent content in Tutorial on RockB</description><image><title>RockB</title><url>https://baeseokjae.github.io/images/og-default.png</url><link>https://baeseokjae.github.io/images/og-default.png</link></image><generator>Hugo</generator><language>en-us</language><lastBuildDate>Thu, 16 Apr 2026 04:08:44 +0000</lastBuildDate><atom:link href="https://baeseokjae.github.io/tags/tutorial/index.xml" rel="self" type="application/rss+xml"/><item><title>MCP Server Tutorial 2026: Build Your First Model Context Protocol Server</title><link>https://baeseokjae.github.io/posts/mcp-server-tutorial-2026/</link><pubDate>Thu, 16 Apr 2026 04:08:44 +0000</pubDate><guid>https://baeseokjae.github.io/posts/mcp-server-tutorial-2026/</guid><description>Step-by-step MCP server tutorial: build, test, and deploy your first Model Context Protocol server using Python FastMCP or TypeScript in 2026.</description><content:encoded><![CDATA[<p>You can build a working MCP server with 2–3 tools in under 30 minutes using Python FastMCP. This tutorial walks through every step — from installing the SDK to testing with MCP Inspector and deploying locally or to a remote server.</p>
<h2 id="what-is-mcp-and-why-does-it-matter-in-2026">What Is MCP and Why Does It Matter in 2026?</h2>
<p>MCP (Model Context Protocol) is an open standard created by Anthropic in November 2024 that defines how AI models connect to external tools, data sources, and services. Before MCP, every AI integration was a bespoke REST API wrapper — each model provider invented its own function-calling format, and every tool had to be re-implemented per-client. MCP standardizes this: you build a server once, and any MCP-compatible client (Claude, Cursor, VS Code Copilot, custom agents) can discover and call your tools automatically. By early 2026, over 5,000 MCP servers are publicly available, and Anthropic, OpenAI, and Google have all committed to the protocol. The shift parallels what LSP (Language Server Protocol) did for editor tooling — one interface, many clients. If you&rsquo;re building AI tooling in 2026, MCP is the integration layer you ship to.</p>
<h2 id="why-build-an-mcp-server-key-benefits-and-use-cases">Why Build an MCP Server? Key Benefits and Use Cases</h2>
<p>Building an MCP server exposes your capabilities to AI agents in a way that REST APIs alone cannot. An MCP server speaks the language AI clients expect — structured tool definitions with typed schemas, resource listings, and reusable prompt templates — so clients can discover what you offer without reading documentation. By April 2026, over 1,800 MCP servers are listed in public directories, covering everything from database access to payment processing. The core use cases are: (1) <strong>wrapping internal APIs</strong> so Claude or Cursor can query your services directly, (2) <strong>exposing database read/write tools</strong> to agents running automated workflows, (3) <strong>building specialized agents</strong> that combine multiple data sources under one server, and (4) <strong>integrating dev tools</strong> (GitHub, Jira, Slack) into coding assistants. The decisive advantage over REST: AI clients can introspect your tool schemas at runtime, so there&rsquo;s no separate documentation step — the model learns what your server can do by asking.</p>
<h2 id="mcp-architecture-deep-dive-tools-resources-prompts">MCP Architecture Deep Dive: Tools, Resources, Prompts</h2>
<p>An MCP server exposes three primitive types: Tools, Resources, and Prompts. Understanding these before writing code saves significant refactoring later.</p>
<p><strong>Tools</strong> are the most common primitive — functions the model can call with structured arguments. Each tool has a name, description (shown to the model), and a JSON Schema for its input parameters. The model decides when to call a tool based on your description, so tool descriptions are part of your API surface. A weather lookup tool might be: <code>get_weather(city: str) -&gt; dict</code>. Tools are the right primitive for anything that performs an action or returns computed results.</p>
<p><strong>Resources</strong> are read-only data sources the client can query. A PostgreSQL database, a file system, or a documentation corpus are natural resources. Resources use URI templates (<code>postgres://mydb/users/{id}</code>) and return structured or unstructured content. They&rsquo;re closer to GET endpoints than function calls — no side effects expected.</p>
<p><strong>Prompts</strong> are reusable prompt templates with arguments. A <code>summarize_pr</code> prompt might take a PR number and return a structured system+user message pair. This primitive is underused in 2026 but powerful for building consistent agent behaviors across clients.</p>
<p>The transport layer connects clients to your server. <strong>STDIO</strong> transport (local subprocess communication) has ~1ms latency and is the default for local tools embedded in editors. <strong>Streamable HTTP</strong> (formerly SSE) targets remote deployments and adds 10–100ms overhead but works across network boundaries. Choose STDIO for local dev tools; choose HTTP for multi-tenant SaaS or cloud agents.</p>
<h3 id="the-json-rpc-layer-under-the-hood">The JSON-RPC Layer Under the Hood</h3>
<p>MCP uses JSON-RPC 2.0 as its wire format. Every tool call is a <code>tools/call</code> request; tool discovery is <code>tools/list</code>. You rarely interact with this directly — the SDKs handle it — but understanding it helps when debugging connection issues. The MCP Inspector (covered later) exposes raw JSON-RPC messages, which is invaluable when something goes wrong.</p>
<h2 id="choosing-your-sdk-python-fastmcp-vs-typescript-vs-go">Choosing Your SDK: Python FastMCP vs TypeScript vs Go</h2>
<p>Choosing the right SDK matters more for developer ergonomics than for runtime performance — all four major SDKs (Python, TypeScript, Go, Kotlin) produce spec-compliant servers. The practical choice in 2026 comes down to your existing stack and how quickly you need to ship.</p>
<table>
  <thead>
      <tr>
          <th>SDK</th>
          <th>Best For</th>
          <th>Tool Definition Style</th>
          <th>Maturity</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Python FastMCP</strong></td>
          <td>Data/ML workloads, scripting</td>
          <td><code>@mcp.tool()</code> decorator</td>
          <td>Stable, most examples</td>
      </tr>
      <tr>
          <td><strong>TypeScript</strong></td>
          <td>Node.js backends, browser tooling</td>
          <td>Explicit Zod schemas</td>
          <td>Stable, typed end-to-end</td>
      </tr>
      <tr>
          <td><strong>Go</strong></td>
          <td>High-concurrency services</td>
          <td>Struct-based handlers</td>
          <td>Beta, growing</td>
      </tr>
      <tr>
          <td><strong>Kotlin</strong></td>
          <td>JVM services, Android</td>
          <td>Coroutine-based</td>
          <td>Beta</td>
      </tr>
  </tbody>
</table>
<p><strong>Python FastMCP</strong> wins for speed-to-working-server. The decorator API is two lines per tool versus TypeScript&rsquo;s 10+ lines of schema declaration. If you&rsquo;re prototyping or working in a Python-heavy org, start here. <strong>TypeScript</strong> is the right choice if you&rsquo;re already running Node.js infrastructure — you get compile-time validation of your schemas and seamless integration with existing Express or Fastify apps. <strong>Go</strong> is the pick for high-throughput production services where you need goroutine concurrency and predictable memory, but the ecosystem is still catching up as of Q1 2026.</p>
<p>This tutorial uses Python FastMCP for its simplicity, with a TypeScript comparison at the end.</p>
<h2 id="step-by-step-tutorial-build-your-first-mcp-server-in-python">Step-by-Step Tutorial: Build Your First MCP Server in Python</h2>
<p>Building your first MCP server with Python FastMCP takes under 30 minutes if you follow this sequence exactly. FastMCP is the fastest path to a spec-compliant server because it infers tool schemas from Python type annotations and docstrings — you write normal Python functions and the framework handles JSON-RPC registration, schema generation, and transport setup automatically. This tutorial builds a server with two tools: a safe mathematical expression evaluator and an async currency exchange rate lookup that calls an external API. By the end, you&rsquo;ll have a server you can connect to Claude Desktop, MCP Inspector, or any MCP-compatible agent framework. The same patterns — decorated functions, typed parameters, structured return values — scale to production database servers and multi-tool agent backends. All code is self-contained and runs with Python 3.10+ and a single <code>pip install</code>.</p>
<h3 id="prerequisites">Prerequisites</h3>
<ul>
<li>Python 3.10+</li>
<li><code>pip</code> or <code>uv</code></li>
<li>Node.js 18+ (for MCP Inspector)</li>
</ul>
<h3 id="step-1-install-fastmcp">Step 1: Install FastMCP</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>pip install fastmcp
</span></span><span style="display:flex;"><span><span style="color:#75715e"># or with uv (recommended):</span>
</span></span><span style="display:flex;"><span>uv add fastmcp
</span></span></code></pre></div><p>FastMCP is a third-party framework built on top of the official Anthropic <code>mcp</code> Python SDK. It reduces boilerplate dramatically — the official SDK requires manual schema registration; FastMCP infers schemas from Python type annotations.</p>
<h3 id="step-2-create-the-server-file">Step 2: Create the Server File</h3>
<p>Create <code>server.py</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> fastmcp <span style="color:#f92672">import</span> FastMCP
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> httpx
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>mcp <span style="color:#f92672">=</span> FastMCP(<span style="color:#e6db74">&#34;my-first-server&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">@mcp.tool</span>()
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">calculate</span>(expression: str) <span style="color:#f92672">-&gt;</span> str:
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    Evaluate a mathematical expression safely.
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    Examples: &#39;2 + 2&#39;, &#39;10 * 3.14&#39;, &#39;(100 / 4) ** 2&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    &#34;&#34;&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">try</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># Use eval with restricted builtins — no imports allowed</span>
</span></span><span style="display:flex;"><span>        allowed <span style="color:#f92672">=</span> {<span style="color:#e6db74">&#34;__builtins__&#34;</span>: {}}
</span></span><span style="display:flex;"><span>        result <span style="color:#f92672">=</span> eval(expression, allowed)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> str(result)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">except</span> <span style="color:#a6e22e">Exception</span> <span style="color:#66d9ef">as</span> e:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Error: </span><span style="color:#e6db74">{</span>e<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">@mcp.tool</span>()
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">async</span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">get_exchange_rate</span>(base: str, target: str) <span style="color:#f92672">-&gt;</span> dict:
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    Get the current exchange rate between two currencies.
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    Args:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        base: Source currency code (e.g., &#39;USD&#39;)
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        target: Target currency code (e.g., &#39;EUR&#39;)
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    Returns dict with rate and timestamp.
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    &#34;&#34;&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">with</span> httpx<span style="color:#f92672">.</span>AsyncClient() <span style="color:#66d9ef">as</span> client:
</span></span><span style="display:flex;"><span>        resp <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> client<span style="color:#f92672">.</span>get(
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;https://api.exchangerate-api.com/v4/latest/</span><span style="color:#e6db74">{</span>base<span style="color:#f92672">.</span>upper()<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>        )
</span></span><span style="display:flex;"><span>        data <span style="color:#f92672">=</span> resp<span style="color:#f92672">.</span>json()
</span></span><span style="display:flex;"><span>        rate <span style="color:#f92672">=</span> data[<span style="color:#e6db74">&#34;rates&#34;</span>]<span style="color:#f92672">.</span>get(target<span style="color:#f92672">.</span>upper())
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> rate <span style="color:#f92672">is</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> {<span style="color:#e6db74">&#34;error&#34;</span>: <span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Unknown currency: </span><span style="color:#e6db74">{</span>target<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>}
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;base&#34;</span>: base<span style="color:#f92672">.</span>upper(),
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;target&#34;</span>: target<span style="color:#f92672">.</span>upper(),
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;rate&#34;</span>: rate,
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;date&#34;</span>: data[<span style="color:#e6db74">&#34;date&#34;</span>],
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> __name__ <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;__main__&#34;</span>:
</span></span><span style="display:flex;"><span>    mcp<span style="color:#f92672">.</span>run()
</span></span></code></pre></div><p>Key points: FastMCP extracts the tool name from the function name, the description from the docstring, and the input schema from the type annotations. <code>str</code>, <code>int</code>, <code>float</code>, <code>dict</code>, <code>list</code>, and <code>Optional[T]</code> all map to JSON Schema types automatically.</p>
<h3 id="step-3-run-the-server">Step 3: Run the Server</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>python server.py
</span></span></code></pre></div><p>With no arguments, FastMCP runs in STDIO mode — the server reads JSON-RPC messages from stdin and writes responses to stdout. This is exactly what MCP clients expect for subprocess-style integration.</p>
<p><strong>Critical pitfall</strong>: Never write to stdout yourself (no <code>print()</code> statements in your tool code). Stdout is the MCP transport channel. Use <code>stderr</code> for debug output or FastMCP&rsquo;s built-in logging:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">import</span> sys
</span></span><span style="display:flex;"><span>print(<span style="color:#e6db74">&#34;debug message&#34;</span>, file<span style="color:#f92672">=</span>sys<span style="color:#f92672">.</span>stderr)  <span style="color:#75715e"># OK</span>
</span></span><span style="display:flex;"><span>print(<span style="color:#e6db74">&#34;debug message&#34;</span>)  <span style="color:#75715e"># BREAKS the transport</span>
</span></span></code></pre></div><h3 id="step-4-add-the-server-to-claude-desktop-optional">Step 4: Add the Server to Claude Desktop (Optional)</h3>
<p>If you have Claude Desktop installed, add your server to <code>~/Library/Application Support/Claude/claude_desktop_config.json</code> (macOS) or <code>%APPDATA%\Claude\claude_desktop_config.json</code> (Windows):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;mcpServers&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;my-first-server&#34;</span>: {
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;command&#34;</span>: <span style="color:#e6db74">&#34;python&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;args&#34;</span>: [<span style="color:#e6db74">&#34;/absolute/path/to/server.py&#34;</span>]
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Restart Claude Desktop. Your tools now appear in every conversation.</p>
<h3 id="typescript-equivalent-for-comparison">TypeScript Equivalent for Comparison</h3>
<p>The same server in TypeScript (using the official <code>@modelcontextprotocol/sdk</code>):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">McpServer</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@modelcontextprotocol/sdk/server/mcp.js&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">StdioServerTransport</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@modelcontextprotocol/sdk/server/stdio.js&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">z</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;zod&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">server</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">McpServer</span>({ <span style="color:#a6e22e">name</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;my-first-server&#34;</span>, <span style="color:#a6e22e">version</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;1.0.0&#34;</span> });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">server</span>.<span style="color:#a6e22e">tool</span>(
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;calculate&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;Evaluate a mathematical expression safely.&#34;</span>,
</span></span><span style="display:flex;"><span>  { <span style="color:#a6e22e">expression</span>: <span style="color:#66d9ef">z.string</span>().<span style="color:#a6e22e">describe</span>(<span style="color:#e6db74">&#34;Math expression like &#39;2 + 2&#39;&#34;</span>) },
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">async</span> ({ <span style="color:#a6e22e">expression</span> }) <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// safe eval implementation
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">return</span> { <span style="color:#a6e22e">content</span><span style="color:#f92672">:</span> [{ <span style="color:#66d9ef">type</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;text&#34;</span>, <span style="color:#a6e22e">text</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;result&#34;</span> }] };
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">transport</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">StdioServerTransport</span>();
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">await</span> <span style="color:#a6e22e">server</span>.<span style="color:#a6e22e">connect</span>(<span style="color:#a6e22e">transport</span>);
</span></span></code></pre></div><p>TypeScript requires explicit Zod schema definitions per parameter — more verbose but catches type mismatches at compile time. For teams shipping production TypeScript services, this trade-off is worth it.</p>
<h2 id="testing-with-mcp-inspector-interactive-development-workflow">Testing with MCP Inspector: Interactive Development Workflow</h2>
<p>MCP Inspector is the official browser-based debugging tool for MCP servers, maintained by Anthropic as part of the core SDK. It connects directly to your server and exposes a full testing UI where you can discover tools, call them with custom arguments, inspect raw JSON-RPC messages, and verify resource and prompt registrations — all without writing a single line of client code. In practice, Inspector eliminates the cycle of &ldquo;start Claude Desktop, restart, test, repeat&rdquo; that slows local development to a crawl. Instead: run your server through Inspector&rsquo;s proxy, open the browser, call your tools, iterate. A typical development session catches schema issues, missing error handling, and malformed responses in minutes rather than hours. As of April 2026, MCP Inspector supports all transport modes (STDIO and HTTP) and all three MCP primitives. It&rsquo;s the single most important tool in the MCP developer workflow — use it before every deployment.</p>
<p>Installing and running MCP Inspector takes under two minutes. Run your server via Inspector&rsquo;s proxy: <code>npx @modelcontextprotocol/inspector python server.py</code>. This starts a local web UI (default: <code>http://localhost:5173</code>) with three panels: server connection status, tool list (auto-populated from your <code>tools/list</code> response), and a call interface where you can set arguments and execute tools.</p>
<h3 id="what-to-check-in-every-testing-session">What to Check in Every Testing Session</h3>
<p><strong>Tool discovery</strong>: After connecting, click &ldquo;List Tools.&rdquo; You should see all your tools with their descriptions and parameter schemas exactly as defined. If a tool is missing or shows wrong schema, check your type annotations or decorators.</p>
<p><strong>Happy-path calls</strong>: Call each tool with valid input. Verify response structure matches what you expect. For the calculator: input <code>&quot;2 + 2&quot;</code>, expect <code>&quot;4&quot;</code>.</p>
<p><strong>Error handling</strong>: Test with invalid input — empty strings, wrong types, out-of-range values. Your server should return structured error messages, not crash.</p>
<p><strong>Raw JSON-RPC view</strong>: Toggle the inspector&rsquo;s &ldquo;Show raw messages&rdquo; option to see the actual JSON-RPC request/response pairs. This is essential when debugging schema issues — you can see exactly what the client sends and what your server returns.</p>
<p>MCP Inspector also tests Resources and Prompts if your server exposes them. Run a full test pass before every deployment.</p>
<h2 id="deploying-your-server-local-vs-remote-options">Deploying Your Server: Local vs Remote Options</h2>
<p>Choosing between local (STDIO) and remote (Streamable HTTP) deployment is one of the more consequential architectural decisions when building an MCP server, because it affects latency, multi-tenancy, authentication, and how clients discover and connect to your server.</p>
<p><strong>Local STDIO deployment</strong> runs your server as a child process managed by the client. Latency is ~1ms. Setup is minimal: the client config points to your executable. This is the right choice for developer tools that live on a single machine — Cursor plugins, CLI helpers, local database inspectors. The downside: every user installs and runs the binary themselves; no centralized management.</p>
<p><strong>Remote Streamable HTTP deployment</strong> exposes your server over HTTP. The client connects to a URL like <code>https://api.yourservice.com/mcp</code>. This model supports multi-tenant SaaS (one server instance, many users), cloud-hosted agents, and scenarios where the server needs access to infrastructure that users shouldn&rsquo;t touch (internal databases, credentials). The trade-off is complexity: you manage authentication, connection state, and deployment.</p>
<h3 id="deploying-to-http-with-fastmcp">Deploying to HTTP with FastMCP</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e"># server_http.py</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> fastmcp <span style="color:#f92672">import</span> FastMCP
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>mcp <span style="color:#f92672">=</span> FastMCP(<span style="color:#e6db74">&#34;my-first-server&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># ... tool definitions same as before ...</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> __name__ <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;__main__&#34;</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Run as HTTP server on port 8000</span>
</span></span><span style="display:flex;"><span>    mcp<span style="color:#f92672">.</span>run(transport<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;http&#34;</span>, host<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;0.0.0.0&#34;</span>, port<span style="color:#f92672">=</span><span style="color:#ae81ff">8000</span>)
</span></span></code></pre></div><p>For production, add authentication middleware. FastMCP supports standard ASGI middleware — wrap with an API key check:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> starlette.middleware.base <span style="color:#f92672">import</span> BaseHTTPMiddleware
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">APIKeyMiddleware</span>(BaseHTTPMiddleware):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">dispatch</span>(self, request, call_next):
</span></span><span style="display:flex;"><span>        key <span style="color:#f92672">=</span> request<span style="color:#f92672">.</span>headers<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;x-api-key&#34;</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> key <span style="color:#f92672">!=</span> <span style="color:#e6db74">&#34;your-secret-key&#34;</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">from</span> starlette.responses <span style="color:#f92672">import</span> JSONResponse
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> JSONResponse({<span style="color:#e6db74">&#34;error&#34;</span>: <span style="color:#e6db74">&#34;Unauthorized&#34;</span>}, status_code<span style="color:#f92672">=</span><span style="color:#ae81ff">401</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">await</span> call_next(request)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>mcp<span style="color:#f92672">.</span>app<span style="color:#f92672">.</span>add_middleware(APIKeyMiddleware)
</span></span></code></pre></div><h3 id="docker-deployment">Docker Deployment</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-dockerfile" data-lang="dockerfile"><span style="display:flex;"><span><span style="color:#66d9ef">FROM</span><span style="color:#e6db74"> python:3.12-slim</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">WORKDIR</span><span style="color:#e6db74"> /app</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">COPY</span> requirements.txt .<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> pip install -r requirements.txt<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">COPY</span> server_http.py .<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">EXPOSE</span><span style="color:#e6db74"> 8000</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">CMD</span> [<span style="color:#e6db74">&#34;python&#34;</span>, <span style="color:#e6db74">&#34;server_http.py&#34;</span>]<span style="color:#960050;background-color:#1e0010">
</span></span></span></code></pre></div><p>Build and run: <code>docker build -t my-mcp-server . &amp;&amp; docker run -p 8000:8000 my-mcp-server</code>. For Kubernetes deployments, treat your MCP server like any stateless HTTP service — horizontal scaling works out of the box because MCP connections are request-scoped, not persistent.</p>
<h2 id="advanced-patterns-database-integration-and-production-readiness">Advanced Patterns: Database Integration and Production Readiness</h2>
<p>Production MCP servers serving real agent workflows need more than basic tool definitions — they need connection pooling, observability, rate limiting, and graceful error handling. This section covers the patterns that separate a weekend prototype from a production-grade server.</p>
<p>The most common production use case is wrapping a PostgreSQL (or any SQL) database so agents can query it directly. Here is a complete example using <code>asyncpg</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">import</span> asyncpg
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> fastmcp <span style="color:#f92672">import</span> FastMCP
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> typing <span style="color:#f92672">import</span> Optional
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> os
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>mcp <span style="color:#f92672">=</span> FastMCP(<span style="color:#e6db74">&#34;postgres-server&#34;</span>)
</span></span><span style="display:flex;"><span>pool: Optional[asyncpg<span style="color:#f92672">.</span>Pool] <span style="color:#f92672">=</span> <span style="color:#66d9ef">None</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">@mcp.on_startup</span>()
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">async</span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">startup</span>():
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">global</span> pool
</span></span><span style="display:flex;"><span>    pool <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> asyncpg<span style="color:#f92672">.</span>create_pool(
</span></span><span style="display:flex;"><span>        dsn<span style="color:#f92672">=</span>os<span style="color:#f92672">.</span>environ[<span style="color:#e6db74">&#34;DATABASE_URL&#34;</span>],
</span></span><span style="display:flex;"><span>        min_size<span style="color:#f92672">=</span><span style="color:#ae81ff">5</span>,
</span></span><span style="display:flex;"><span>        max_size<span style="color:#f92672">=</span><span style="color:#ae81ff">20</span>,
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">@mcp.on_shutdown</span>()
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">async</span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">shutdown</span>():
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> pool:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">await</span> pool<span style="color:#f92672">.</span>close()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">@mcp.tool</span>()
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">async</span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">query_users</span>(email_filter: Optional[str] <span style="color:#f92672">=</span> <span style="color:#66d9ef">None</span>) <span style="color:#f92672">-&gt;</span> list[dict]:
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    Query users from the database.
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    Args:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        email_filter: Optional email substring to filter by (e.g. &#39;@acme.com&#39;)
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    Returns list of user records (id, email, created_at).
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    &#34;&#34;&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">with</span> pool<span style="color:#f92672">.</span>acquire() <span style="color:#66d9ef">as</span> conn:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> email_filter:
</span></span><span style="display:flex;"><span>            rows <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> conn<span style="color:#f92672">.</span>fetch(
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;SELECT id, email, created_at FROM users WHERE email ILIKE $1 LIMIT 100&#34;</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;%</span><span style="color:#e6db74">{</span>email_filter<span style="color:#e6db74">}</span><span style="color:#e6db74">%&#34;</span>,
</span></span><span style="display:flex;"><span>            )
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span>            rows <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> conn<span style="color:#f92672">.</span>fetch(
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;SELECT id, email, created_at FROM users LIMIT 100&#34;</span>
</span></span><span style="display:flex;"><span>            )
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> [dict(row) <span style="color:#66d9ef">for</span> row <span style="color:#f92672">in</span> rows]
</span></span></code></pre></div><p><strong>SQL injection note</strong>: always use parameterized queries (<code>$1</code>, <code>$2</code>) — never string-format user input into SQL. MCP tool arguments come from AI model outputs, which can be adversarially crafted via prompt injection.</p>
<h3 id="adding-opentelemetry-observability">Adding OpenTelemetry Observability</h3>
<p>Instrument your server with OpenTelemetry to track which tools are called, with what arguments, and how long they take:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> opentelemetry <span style="color:#f92672">import</span> trace
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> opentelemetry.sdk.trace <span style="color:#f92672">import</span> TracerProvider
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> opentelemetry.exporter.otlp.proto.grpc.trace_exporter <span style="color:#f92672">import</span> OTLPSpanExporter
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>tracer <span style="color:#f92672">=</span> trace<span style="color:#f92672">.</span>get_tracer(<span style="color:#e6db74">&#34;mcp-server&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">@mcp.tool</span>()
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">async</span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">query_users</span>(email_filter: Optional[str] <span style="color:#f92672">=</span> <span style="color:#66d9ef">None</span>) <span style="color:#f92672">-&gt;</span> list[dict]:
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">with</span> tracer<span style="color:#f92672">.</span>start_as_current_span(<span style="color:#e6db74">&#34;query_users&#34;</span>) <span style="color:#66d9ef">as</span> span:
</span></span><span style="display:flex;"><span>        span<span style="color:#f92672">.</span>set_attribute(<span style="color:#e6db74">&#34;filter&#34;</span>, email_filter <span style="color:#f92672">or</span> <span style="color:#e6db74">&#34;none&#34;</span>)
</span></span><span style="display:flex;"><span>        result <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> _do_query(email_filter)
</span></span><span style="display:flex;"><span>        span<span style="color:#f92672">.</span>set_attribute(<span style="color:#e6db74">&#34;result_count&#34;</span>, len(result))
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> result
</span></span></code></pre></div><p>This integrates with Grafana, Jaeger, or any OTLP-compatible backend and gives you full visibility into agent behavior in production.</p>
<h3 id="rate-limiting">Rate Limiting</h3>
<p>Agents can call tools in tight loops. Protect expensive operations with a token bucket:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">import</span> asyncio
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> time
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">RateLimiter</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> __init__(self, calls_per_minute: int):
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>rate <span style="color:#f92672">=</span> calls_per_minute <span style="color:#f92672">/</span> <span style="color:#ae81ff">60</span>
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>tokens <span style="color:#f92672">=</span> calls_per_minute
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>last_update <span style="color:#f92672">=</span> time<span style="color:#f92672">.</span>monotonic()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">acquire</span>(self):
</span></span><span style="display:flex;"><span>        now <span style="color:#f92672">=</span> time<span style="color:#f92672">.</span>monotonic()
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>tokens <span style="color:#f92672">=</span> min(
</span></span><span style="display:flex;"><span>            self<span style="color:#f92672">.</span>tokens <span style="color:#f92672">+</span> (now <span style="color:#f92672">-</span> self<span style="color:#f92672">.</span>last_update) <span style="color:#f92672">*</span> self<span style="color:#f92672">.</span>rate,
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">60.0</span>,
</span></span><span style="display:flex;"><span>        )
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>last_update <span style="color:#f92672">=</span> now
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> self<span style="color:#f92672">.</span>tokens <span style="color:#f92672">&lt;</span> <span style="color:#ae81ff">1</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">await</span> asyncio<span style="color:#f92672">.</span>sleep((<span style="color:#ae81ff">1</span> <span style="color:#f92672">-</span> self<span style="color:#f92672">.</span>tokens) <span style="color:#f92672">/</span> self<span style="color:#f92672">.</span>rate)
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>tokens <span style="color:#f92672">-=</span> <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>limiter <span style="color:#f92672">=</span> RateLimiter(calls_per_minute<span style="color:#f92672">=</span><span style="color:#ae81ff">30</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">@mcp.tool</span>()
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">async</span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">expensive_operation</span>(query: str) <span style="color:#f92672">-&gt;</span> dict:
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;&#34;&#34;Run an expensive external API call.&#34;&#34;&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">await</span> limiter<span style="color:#f92672">.</span>acquire()
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># ... do work ...</span>
</span></span></code></pre></div><h2 id="common-pitfalls-and-best-practices">Common Pitfalls and Best Practices</h2>
<p>Building your first MCP server surfaces a predictable set of mistakes. Here is what developers commonly get wrong, and the fixes that save hours of debugging.</p>
<p><strong>Pitfall 1: Writing to stdout breaks the transport.</strong> Every <code>print()</code> in your server code corrupts the STDIO channel. The client sees garbage JSON and disconnects. Fix: use <code>logging</code> with a <code>StreamHandler</code> pointing to <code>sys.stderr</code>, or use FastMCP&rsquo;s built-in <code>mcp.logger</code>.</p>
<p><strong>Pitfall 2: Vague tool descriptions hurt model performance.</strong> The model decides when to call your tool based solely on its description. A description like &ldquo;Does stuff with data&rdquo; is useless. Write descriptions like a senior dev explaining the function to a new teammate: include the precondition, what it returns, and any important limits. The extra 20 words in a description can double tool call accuracy.</p>
<p><strong>Pitfall 3: Over-broad tools create ambiguity.</strong> A single <code>query_database(sql: str)</code> tool sounds convenient but forces the model to write raw SQL, handle escaping, and guess your schema. Instead, expose narrow, purpose-built tools: <code>get_user_by_email</code>, <code>list_recent_orders</code>, <code>search_products</code>. Narrow tools mean fewer model errors.</p>
<p><strong>Pitfall 4: Missing error returns.</strong> If your tool raises an unhandled exception, the MCP client receives a protocol-level error with no detail. Always catch exceptions and return structured error info: <code>return {&quot;error&quot;: &quot;user_not_found&quot;, &quot;message&quot;: f&quot;No user with id {user_id}&quot;}</code>. The model can read this and decide what to do next.</p>
<p><strong>Pitfall 5: Not validating tool output size.</strong> MCP responses are passed directly into the model&rsquo;s context window. A tool that returns 500KB of database rows will blow the context limit. Enforce pagination: always cap results at 100 rows, add a <code>cursor</code> parameter for pagination, and document the limits in the tool description.</p>
<p><strong>Best practice: version your tools.</strong> Use the server version string and include it in error messages. When you change a tool&rsquo;s behavior, bump the version. Agent workflows that depend on your server need to know when the contract changes.</p>
<h2 id="future-of-mcp-trends-and-predictions-for-2027">Future of MCP: Trends and Predictions for 2027</h2>
<p>The MCP ecosystem is accelerating fast, and the trajectory through 2026 points to three converging shifts that will shape what you build next year. First, <strong>remote MCP registries</strong> are emerging as the distribution layer for AI tools — think npm for MCP servers. By Q3 2026, several competing registries have launched, and Anthropic is expected to standardize a discovery protocol so clients can auto-install servers from a central index. Second, <strong>multi-agent MCP chaining</strong> — where one MCP server calls tools on other MCP servers — is becoming a first-class pattern. The spec now supports agent-to-agent tool calls, enabling complex workflows like &ldquo;research agent calls web-search server, passes results to summarization server, posts to Slack via another server,&rdquo; all coordinated through MCP. Third, <strong>authentication and authorization</strong> are getting standardized: the MCP spec&rsquo;s OAuth integration (currently in draft) will let servers declare required scopes and clients handle token flows automatically, removing the bespoke auth plumbing every production server currently needs.</p>
<p>For developers building now: invest in clean tool schemas, good descriptions, and narrow tool surfaces. The model quality improvements coming in 2026–2027 amplify the quality of your tool interface — a well-described tool will be called correctly more often as models improve, but a vague tool description remains a liability regardless of model version.</p>
<h2 id="faq">FAQ</h2>
<p>These are the questions developers most commonly ask when building their first MCP server. Each answer is self-contained — you don&rsquo;t need to read the full article to use them. Topics cover SDK selection, primitive types, testing workflow, transport layer differences, and security. If you&rsquo;re new to MCP, start with the tool vs. resource vs. prompt distinction (question 2) and the STDIO vs. HTTP transport choice (question 4) — these two decisions shape everything else in your server design. For production deployments, pay close attention to the SQL injection answer (question 5): MCP tool arguments flow from AI model outputs and require the same defensive handling as user-supplied HTTP input. The MCP spec is evolving rapidly; check the official Anthropic SDK changelog for breaking changes before upgrading FastMCP or the TypeScript SDK in an active deployment.</p>
<h3 id="what-language-should-i-use-to-build-an-mcp-server-in-2026">What language should I use to build an MCP server in 2026?</h3>
<p>Python with FastMCP is the fastest path to a working server — you can ship 2–3 tools in under 30 minutes with minimal boilerplate. Use TypeScript if your existing backend is Node.js, since you get compile-time schema validation and seamless integration with Express or Fastify. Go is the right choice for high-concurrency production services, but the SDK ecosystem is still maturing as of Q1 2026. Start with Python unless you have a specific reason to choose otherwise.</p>
<h3 id="what-is-the-difference-between-mcp-tools-resources-and-prompts">What is the difference between MCP tools, resources, and prompts?</h3>
<p><strong>Tools</strong> are callable functions the model can invoke with arguments — think POST endpoints. <strong>Resources</strong> are read-only data sources the client can query by URI, like GET endpoints with no side effects. <strong>Prompts</strong> are reusable message templates with parameters that produce structured system/user message pairs. Most servers only need Tools; add Resources if you&rsquo;re exposing queryable datasets, and Prompts if you want to standardize how the model approaches specific workflows.</p>
<h3 id="how-do-i-test-my-mcp-server-without-integrating-it-into-claude">How do I test my MCP server without integrating it into Claude?</h3>
<p>Use MCP Inspector: <code>npx @modelcontextprotocol/inspector python server.py</code>. This opens a browser UI where you can discover tools, set arguments, call them, and see raw JSON-RPC messages. It&rsquo;s the official testing tool from Anthropic and supports all three primitives (Tools, Resources, Prompts). Run every new tool through Inspector before adding it to a client.</p>
<h3 id="what-is-the-difference-between-stdio-and-http-transport-in-mcp">What is the difference between STDIO and HTTP transport in MCP?</h3>
<p>STDIO transport runs your server as a local subprocess with ~1ms latency — best for developer tools on a single machine (Cursor plugins, CLI tools). HTTP transport (Streamable HTTP) exposes your server over a URL, enables multi-tenant deployments, and works across network boundaries at 10–100ms latency. Choose STDIO for local tools; choose HTTP when multiple users or cloud agents need to reach your server.</p>
<h3 id="how-do-i-prevent-sql-injection-in-an-mcp-database-server">How do I prevent SQL injection in an MCP database server?</h3>
<p>Always use parameterized queries, never string-format user input into SQL. In <code>asyncpg</code>: use <code>conn.fetch(&quot;SELECT ... WHERE id = $1&quot;, user_id)</code>. In <code>psycopg2</code>: use <code>cursor.execute(&quot;SELECT ... WHERE id = %s&quot;, (user_id,))</code>. MCP tool arguments come from AI model outputs, which can be adversarially manipulated via prompt injection — treat them with the same distrust as user-supplied HTTP input. Add input validation (check types, enforce length limits) before passing anything to a database query.</p>
]]></content:encoded></item></channel></rss>