Summary of Today’s Gen AI MCP Session: Off the Laptop – JSON-RPC and Streamable HTTP (5 Nov 2026)

Everything so far has run over stdio, which means one machine. Today we changed one line and put the server on the network.

0. First: The SDK Renamed Its Server Class

MCP Python SDK v2 renamed FastMCP to MCPServer. Every example from here on uses it.

# v1 (old) — fails on a fresh install
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("cafe")

# v2 (current)
from mcp.server import MCPServer
mcp = MCPServer("cafe")
  • The decorators don’t change. @mcp.tool(), @mcp.resource() and @mcp.prompt() work exactly as before.
  • There’s no shim. pip install mcp now installs v2, so the old import raises ModuleNotFoundError: No module named 'mcp.server.fastmcp'.
  • Staying on v1 for now? Pin it with mcp>=1.28,<2. Otherwise the next install upgrades you silently.
  • Don’t confuse this with the standalone FastMCP project. That’s a separate library with its own versions. This rename only affects the class inside the official SDK.

1. The Language Underneath: JSON-RPC

MCP clients and servers talk in JSON-RPC 2.0. Every request has the same shape:

{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "tools/call",
  "params": { "name": "place_order", "arguments": { "table": 4 } }
}
  • id matches a response to its request.
  • method says what’s wanted, such as tools/list, tools/call or resources/read.
  • params carries the details.

You never write this. You write a Python function with @mcp.tool(), and the SDK converts it to and from JSON-RPC.

The model doesn’t write it either. Here’s who does what:

Model → "call place_order with table 4"  (a tool call, in the model's own format)
Client → translates it into JSON-RPC → sends it to the server
Server → runs the function → replies in JSON-RPC
Client → hands the result back to the model

The model only ever sees tools and results. JSON-RPC lives between the client and the server, and that’s exactly the job the client exists to do.

2. Transport Options

TransportUse it for
stdioLocal development, one machine
Streamable HTTPAcross machines; the server becomes a web service
In-memoryAutomated tests only
CustomPossible, rarely needed
HTTP + SSEDeprecated; replaced by Streamable HTTP

3. Switching to Streamable HTTP

The key point: your tools, resources and prompts don’t change at all. Only the way the server starts changes.

from mcp.server import MCPServer

mcp = MCPServer("cafe")

# ... every @mcp.tool(), @mcp.resource() and @mcp.prompt() exactly as before ...

if __name__ == "__main__":
    mcp.run(transport="streamable-http", host="127.0.0.1", port=8000)
  • "streamable-http" uses a hyphen. With an underscore it won’t start.
  • In v2, host and port go on run(), not on the constructor.
  • The server listens at http://127.0.0.1:8000/mcp, with one endpoint, /mcp, by default.
  • Uvicorn, a Python ASGI server, handles the HTTP traffic underneath and processes requests asynchronously.

Or start it from the CLI without editing the file:

uv run mcp run cafe_server.py --transport streamable-http

On the host:

HostWho can reach it
127.0.0.1Only your machine. Use this while testing
0.0.0.0Anyone on your network

There’s no authentication yet, so binding to 0.0.0.0 hands your tools to everyone on the Wi-Fi. Only use it when you mean to.

4. How the HTTP Exchange Works

  • Every client-to-server message is an HTTP POST to /mcp.
  • The server replies either with a single JSON response or by opening an SSE stream when it has several messages to send back.

So SSE didn’t disappear. The old transport was retired, and SSE survives as one of the ways Streamable HTTP responds.

5. Testing with the MCP Inspector

Choose Streamable HTTP in the Inspector, enter the server URL, and connect. The Inspector discovers the server’s tools, resources and prompts, and lets you call them by hand.

Public servers we tried:

ServerResult
Microsoft LearnWorked straight away, with no authentication
Our calculator and cafe serversWorked over HTTP
GitHub, Swiggy DineoutBlocked by authentication

That last row is the important one. Authentication is the main barrier to using public MCP servers, and it’s the next thing we have to solve.

Also worth knowing: Firecrawl offers an MCP server for scraping and crawling web pages.

6. A First Look at the Client

v2 ships a first-class Client in the same package. With your server running over HTTP:

import asyncio
from mcp import Client

async def main():
    async with Client("http://127.0.0.1:8000/mcp") as client:
        result = await client.call_tool("place_order", {"table": 4, "items": ["chai"]})
        print(result.structured_content)

asyncio.run(main())

A URL means Streamable HTTP. This is the piece Claude Desktop and the Inspector have been playing for us, and it’s where we’re headed.

7. Into Production

Once a server runs over Streamable HTTP, it’s a web application. You can deploy it inside the organisation’s network or to the cloud behind a public address. From there, DevOps treats it like any other web service: URLs, configuration, scaling and monitoring.

8. What’s Next

  1. Security: HTTPS, plus authentication and authorisation, so only the right callers reach the right tools.
  2. Wiring into real systems: MCP servers that sit in front of existing organisational APIs.
  3. Writing an MCP client properly: starting from the Client above, so the client’s job stops being a black box.

✅ Action Items

  1. Migrate your cafe server to MCPServer: change the import and the class name, and leave the rest alone.
  2. Switch it to Streamable HTTP, bound to 127.0.0.1.
  3. Connect from the MCP Inspector using the /mcp URL, and call place_order.
  4. Call it from the Client example in section 6.
  5. Commit the cafe server code to the repository.

⏭️ Next up: securing MCP servers with authentication.

By continuous learner

enthusiastic technology learner

Leave a comment