mcp-utils-msgspec
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.
Features
- 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.
Installation
pip install mcp-utils-msgspec
For 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.
Define a server
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",
)
Handle a request
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.
Streamable HTTP with Flask
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)), status
For 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.
Stateful tools
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.
Multi-round-trip input
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.
Testing with MCP Inspector
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 http
The CLI can list tools without opening the web interface:
npx @modelcontextprotocol/inspector --cli \
http://127.0.0.1:9000/mcp \
--transport http \
--method tools/list
Protocol scope
The 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:
Related projects
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mcp_utils_msgspec-3.0.1-py3-none-any.whl.
File metadata
- Download URL: mcp_utils_msgspec-3.0.1-py3-none-any.whl
- Upload date:
- Size: 16.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2c114963e377021906e88c64913ee6fa54a8a09ae5476f31cd6b38447b549456
|
|
| MD5 |
855dfe388c353c4d5e640c1d60fa0036
|
|
| BLAKE2b-256 |
5777ec4c0127523fa1dcffa526e18fbf0e9c303e07f9fc8c5973ee2ca28cd064
|