A synchronous Python utility package for building Model Context Protocol (MCP)
servers with msgspec.
This package targets MCP protocol revision 2026-07-28. It implements the
modern stateless protocol: there is no initialize handshake, implicit MCP
session, or long-lived HTTP GET stream.
- Required
server/discoversupport - Per-request protocol version and client capability metadata
- Tools, prompts, resources, simple
{name}resource templates, and completions - Multi-round-trip input requests and retry metadata
- Required
resultType, cache hints, and server identity metadata - Synchronous, framework-independent request handling
- Optional validation of standard Streamable HTTP mirror headers
msgspecmodels for the supported server surface
The core returns either one JSON response or no response for a JSON-RPC
notification. It does not implement optional request-scoped SSE streams or
subscriptions/listen, the tasks extension, or optional x-mcp-header tool
parameter annotations.
pip install mcp-utils-msgspecFor development:
pip install -e '.[dev]'Python 3.10 or newer and msgspec 0.18 or newer are required. Flask and
Gunicorn are optional and only needed for the HTTP example.
from mcp_utils.core import MCPServer
from mcp_utils.schema import GetPromptResult, Message, Role, TextContent
mcp = MCPServer(
name="weather",
version="1.0.0",
instructions="Use get_weather for current conditions.",
)
@mcp.tool()
def get_weather(city: str) -> dict[str, str]:
"""Return the current conditions for a city."""
return {"city": city, "conditions": "sunny"}
@mcp.prompt()
def weather_report(city: str) -> GetPromptResult:
"""Create a prompt asking for a weather report."""
return GetPromptResult(
messages=[
Message(
role=Role.USER,
content=TextContent(text=f"Report the weather in {city}."),
)
]
)Dictionary and other JSON-compatible tool return values are emitted as both
structuredContent and serialized text. A string return value is emitted as
text. A tool may also return CallToolResult directly.
List and resource results default to ttlMs=0 and cacheScope="private".
Servers with globally identical, safely shareable results can opt into caching:
mcp = MCPServer(
name="weather",
version="1.0.0",
cache_ttl_ms=300_000,
cache_scope="public",
)Every modern request includes its protocol version and client capabilities in
params._meta:
message = {
"jsonrpc": "2.0",
"id": "tools-1",
"method": "tools/list",
"params": {
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {},
"io.modelcontextprotocol/clientInfo": {
"name": "example-client",
"version": "1.0.0",
},
}
},
}
response = mcp.handle_message(message)The server rejects an unsupported version with error -32022 and includes its
supported versions. It uses -32021 when a multi-round-trip result requires a
client capability that the request did not declare. initialize, ping, and
notifications/initialized are not modern protocol methods.
Revision 2026-07-28 uses one POST per JSON-RPC message. The GET stream and
Mcp-Session-Id header were removed. Pass the HTTP headers to handle_message
to validate the required body/header mirrors:
from flask import Flask, jsonify, request
import msgspec
from mcp_utils.core import MCPServer
from mcp_utils.schema import MCPErrorResponse
app = Flask(__name__)
mcp = MCPServer("example", "1.0.0")
allowed_origins = {
"http://127.0.0.1:6274",
"http://localhost:6274",
}
@app.post("/mcp")
def mcp_route():
origin = request.headers.get("Origin")
if origin is not None and origin not in allowed_origins:
return "", 403
response = mcp.handle_message(
request.get_json(),
http_headers=request.headers,
)
if response is None:
return "", 202
status = 200
if isinstance(response, MCPErrorResponse):
status = response.http_status_code
return jsonify(msgspec.to_builtins(response)), statusFor HTTP requests, clients must send:
MCP-Protocol-Version, matching the version inparams._metaMcp-Method, matching the JSON-RPC methodMcp-Namefortools/call,prompts/get, andresources/readAccept: application/json, text/event-stream
The application remains responsible for authentication and its allowed-origin
policy. Bind local development servers to 127.0.0.1, not 0.0.0.0.
See examples/flask_app.py for a complete local example.
MCP no longer has protocol-level sessions. A stateful tool should return an opaque handle from a creation tool and require that handle as an ordinary argument on later calls. See examples/python_session_flask.py for this pattern.
A tool, prompt, or resource that needs elicitation, sampling, or roots can
return InputRequiredResult. To inspect the client's inputResponses and the
echoed requestState when it retries, declare the reserved keyword-only
_mcp_request parameter. This parameter receives the decoded MCPRequest and
is omitted from advertised argument schemas:
from mcp_utils.schema import InputRequiredResult, MCPRequest
@mcp.tool()
def confirm_action(
action: str,
*,
_mcp_request: MCPRequest,
) -> dict[str, object] | InputRequiredResult:
responses = _mcp_request.params.get("inputResponses")
if not isinstance(responses, dict):
return InputRequiredResult(
inputRequests={
"confirmation": {
"method": "elicitation/create",
"params": {
"mode": "form",
"message": f"Confirm {action}?",
"requestedSchema": {"type": "object"},
},
}
},
requestState="opaque-application-state",
)
return {"confirmed": responses["confirmation"]}The server returns -32021 if an input request needs a capability missing from
that request's io.modelcontextprotocol/clientCapabilities metadata.
The current Inspector understands modern 2026-07-28 servers and requires
Node 22.19 or newer:
npx @modelcontextprotocol/inspector \
--server-url http://127.0.0.1:9000/mcp \
--transport httpThe CLI can list tools without opening the web interface:
npx @modelcontextprotocol/inspector --cli \
http://127.0.0.1:9000/mcp \
--transport http \
--method tools/listThe package advertises only capabilities backed by current registrations. It
does not advertise optional subscriptions, roots, sampling, or logging.
Roots, sampling, and logging are deprecated in protocol revision 2026-07-28.
Protocol references:
MIT