ShareYourGroupShareYourGroupAI Agents and MCP

Build a tiny MCP server and see the real messages

This server has two tools. We ran it and recorded every message.

Read on 14 November 2026. This field changes fast: check the sources.

The server

Install the SDK with pip install mcp. In version 2 of the SDK the server class is called MCPServer (version 1 called it FastMCP, and importing the old name fails with a message that points to the migration guide). This is the whole server:

"""A tiny MCP server with two tools (add, shout) written with the official Python SDK. Run by verify.py over stdio. Nothing here talks to the internet."""
from mcp.server.mcpserver import MCPServer

mcp = MCPServer('demo')


@mcp.tool()
def add(a: int, b: int) -> int:
    """Add two whole numbers."""
    return a + b


@mcp.tool()
def shout(text: str) -> str:
    """Return the text in capital letters."""
    return text.upper()


if __name__ == '__main__':
    mcp.run()

The text under each function becomes the tool's description, and the types become its input schema.

A real session

We started this server and talked to it over stdio, one JSON message per line, using the SDK version named above. These are the first messages of that session, exactly as exchanged, shown with indentation for reading:

client -> server
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-06-18",
    "capabilities": {},
    "clientInfo": {
      "name": "demo-client",
      "version": "1.0"
    }
  }
}
server -> client
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "capabilities": {
      "prompts": {
        "listChanged": false
      },
      "resources": {
        "listChanged": false,
        "subscribe": false
      },
      "tools": {
        "listChanged": false
      }
    },
    "protocolVersion": "2025-06-18",
    "serverInfo": {
      "name": "demo",
      "version": ""
    }
  }
}
client -> server
{
  "jsonrpc": "2.0",
  "method": "notifications/initialized"
}
client -> server
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/list"
}
server -> client
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "tools": [
      {
        "description": "Add two whole numbers.",
        "inputSchema": {
          "properties": {
            "a": {
              "title": "A",
              "type": "integer"
            },
            "b": {
              "title": "B",
              "type": "integer"
            }
          },
          "required": [
            "a",
            "b"
          ],
          "type": "object",
          "title": "addArguments"
        },
        "name": "add",
        "outputSchema": {
          "properties": {
            "result": {
              "title": "Result",
              "type": "integer"
            }
          },
          "required": [
            "result"
          ],
          "type": "object",
          "title": "addOutput"
        }
      },
      {
        "description": "Return the text in capital letters.",
        "inputSchema": {
          "properties": {
            "text": {
              "title": "Text",
              "type": "string"
            }
          },
          "required": [
            "text"
          ],
          "type": "object",
          "title": "shoutArguments"
        },
        "name": "shout",
        "outputSchema": {
          "properties": {
            "result": {
              "title": "Result",
              "type": "string"
            }
          },
          "required": [
            "result"
          ],
          "type": "object",
          "title": "shoutOutput"
        }
      }
    ]
  }
}
client -> server
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "add",
    "arguments": {
      "a": 2,
      "b": 40
    }
  }
}
server -> client
{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [
      {
        "text": "42",
        "type": "text"
      }
    ],
    "isError": false,
    "structuredContent": {
      "result": 42
    }
  }
}

The server's answer to initialize lists its capabilities and the protocol version. tools/list returns each tool with a description and a JSON Schema for its input and also for its output. tools/call returns the result as text ("42"), as structured content ({"result": 42}) and isError: false.

What happens with mistakes

We also sent two bad requests. A wrong argument type (the text "two" for a whole number) was not run: the server answered with an error text saying that the argument "Input should be a valid integer". Asking for a tool that does not exist gave isError: true with the text "Unknown tool: nothing". In MCP the tool error is returned inside a normal result, so that the model can read what went wrong and try again.

Through the SDK's own client

The SDK's client connected, listed the tools (add, shout) and called shout with "hello", and the result was HELLO.

Next steps

  1. Add a tool that does something useful and safe.
  2. Read the page on MCP security before you give a server access to files or accounts.
  3. Connect the server to an application that supports MCP: see its own documentation.
Why stdio?

It is the simplest transport: the application starts your program and talks to it through its input and output.

Can I write a server in another language?

The project offers SDKs for several languages. We only ran the Python one.

Sources

  • Model Context Protocol Python SDK, installed as mcp 2.3.0
  • Model Context Protocol documentation: Architecture overview

Read on 14 November 2026.

This is an independent guide. It is not connected with Anthropic or the MCP project. This field changes quickly: statements were read on 14 November 2026 from the pages named under Sources (the MCP site shows specification version 2026-07-28 in its page addresses). The demo server and the messages are real: we ran them with the official Python SDK (mcp 2.3.0). No AI model was used for the demos.