MCP Transports That Still Matter: stdio vs Streamable HTTP (and Why SSE Is a Trap) A developer writing as 晚安code on Juejin published a version-aware walkthrough of building a minimal MCP server with Python's FastMCP, showing how type annotations and docstrings generate the JSON Schema and tool descriptions models rely on. The post flags HTTP+SSE as deprecated in favor of stdio and Streamable HTTP, and notes the one-line transport switch in the FastMCP run path. It recommends pinning the SDK (around 1.27.x at time of writing) because tutorial snippets in the MCP ecosystem rot quickly. Attributed Chinese → English compile not original authorship Original title: MCP Server 开发入门:手把手写一个能跑的 Server,三种协议怎么选 Author: 晚安code URL: https://juejin.cn/post/7673880140422955046 https://juejin.cn/post/7673880140422955046 Date: 2026-08-15 Why this matters for English readers: Most English MCP intros still paste SSE configs from early 2025. This post is short, version-aware, and mechanism-first: it separates what an MCP Server is from how the bytes move , flags HTTP+SSE as deprecated, and shows the one-line transport switch in the Python FastMCP path. Useful if you are wiring Cursor / Claude Desktop locally and deciding whether a remote Streamable HTTP deploy is worth the auth/ops cost. If you have ever watched an LLM “want” to check a package status and then fail because it has no eyes and no hands, you already understand why MCP exists. The interesting part is not the metaphor “USB-C for AI” . The interesting part is the boundary: a small process you control that exposes typed tools and read-only resources , so the model can call out without owning your database credentials or your shell. This compile re-explains 晚安code’s Juejin walkthrough in English: a minimal FastMCP server, how to debug it, and how to pick a transport without learning an obsolete one first. MCP Model Context Protocol is an open protocol for connecting model hosts to external tools and data. A host Claude Desktop, Cursor, your own agent runtime talks to one or more MCP Servers . Each server is ordinary application code—Python or Node—that wraps APIs, files, or internal services behind a stable interface. Why not let the model call HTTP and SQL directly? Because unconstrained tools are how you get “buy ten refrigerators” demos. The server is the policy layer: allowlists, auth, rate limits, and “this tool may read but not write.” That is the product value, not the JSON-RPC ceremony. Minimal mental model: uv skip the fragile pip folklore The source uses uv Astral as a fast env + dependency manager. On Linux/macOS the install path differs from the Windows PowerShell one-liner in the article; the important sequence is: install uv from https://docs.astral.sh/uv/ if needed uv python install 3.11 uv init . -p 3.11 uv add "mcp cli " mcp cli pulls the official Python SDK and the Inspector CLI helpers. Activate the .venv uv creates before you debug in VS Code / Cursor. The article notes SDK ~1.27.x at time of writing—pin what you ship; tutorial snippets rot fast in this ecosystem. FastMCP in mcp.server.fastmcp is the high-level API: decorators instead of hand-rolled JSON-RPC handlers. python from mcp.server.fastmcp import FastMCP mcp = FastMCP "Demo" @mcp.tool def add a: int, b: int - int: """Add two numbers.""" return a + b @mcp.resource "greeting://{name}" def greeting name: str - str: """Greet someone by name.""" return f"Hello, {name} " if name == " main ": mcp.run default transport: stdio Three details that burn hours when omitted: | Requirement | Why it matters | |---|---| | Type annotations on tool args | FastMCP builds JSON Schema inputSchema from them. No types → weak or empty schemas → worse model calls. | | Docstrings | Become the tool/resource description the model reads when deciding whether to call. Empty docstring ≈ invisible tool. | | if name == " main ": | Import-only modules never start the stdio loop; “nothing happens” is usually this. | Tool vs resource is a semantic contract, not a style preference: | | @mcp.tool | @mcp.resource | |---|---|---| | Intent | Perform an action | Expose read-only data | | Side effects | Expected write, call API, mutate | Should be none | | Discovery | tools/list + model-driven call | URI / URI template | | Examples | add , send email, query order | greeting://{name} , config blob | Treat tools as verbs and resources as nouns. Mixing “read file” into a write-capable god-tool is how local servers become shell-shaped. python server.py smoke: process starts, no immediate crash mcp dev server.py Inspector UI: list tools, invoke, inspect errors Version pitfall called out in the source: older blog posts import MCPServer from paths that only exist on pre-alpha SDK v2 sketches. On the stable 1.x line you want from mcp.server.fastmcp import FastMCP . If an import fails on day one, assume tutorial/SDK skew , not “MCP is broken.” Transport is not a fashion label. It decides whether the server is a local child process or a network service . command + args from config. This is the right default while you are learning and while tools only need the developer’s laptop. Switching in FastMCP is intentionally boring: mcp.run local stdio mcp.run transport="streamable-http" remote Streamable HTTP Once you leave stdio, you own authentication, rate limits, observability, and deployment. The protocol does not magically make a public URL safe. SSE-over-HTTP was an early remote option. Per the article’s reading of the ecosystem including SEP-2596 era deprecation notes around March 2025 , new work should not start on SSE . Some TypeScript SDK paths have already removed SSE server support. Old Chinese and English tutorials still teach it; treat those snippets as historical. Cheat sheet: | Transport | Where it runs | How it talks | Use when | Status | |---|---|---|---|---| | stdio | Same machine as host | stdin/stdout | Cursor, Claude Desktop, local CLI | Prefer for local | | Streamable HTTP | Your server / cluster | HTTP bidirectional streaming | Shared/prod remote tools | Prefer for remote | | SSE | Remote | HTTP unidirectional push | Legacy clients only | Deprecated | Hosts differ in file names, but the stdio shape is the same idea: name → command → args → optional env. Conceptually: { "mcpServers": { "demo": { "command": "uv", "args": "run", "python", "/absolute/path/to/server.py" } } } Prefer absolute paths and the same interpreter that has mcp installed. After restart, confirm the host lists add and can call it with integers—not with natural language stuffed into a single string field. The source’s closing advice is the right engineering posture: the barrier to a first MCP Server is low; the barrier to a correct remote deployment is ops, not decorators. Read the official specification when you outgrow demos—secondary tutorials lag the SDK by months. Compiler byline: YongBo Yu, Toronto · https://yongbo-yu.vercel.app https://yongbo-yu.vercel.app · https://github.com/YongBoYu1 https://github.com/YongBoYu1 Attribution: Mechanisms and examples adapted from 晚安code, MCP Server 开发入门:手把手写一个能跑的 Server,三种协议怎么选 https://juejin.cn/post/7673880140422955046 2026-08-15 . Re-explained in original English wording; not a verbatim translation.