rohitg00/ai-engineering-from-scratch · error · ValueError
progressToken must be a string or integer
Error message
progressToken must be a string or integer
What it means
Before running the tool handler the server validates metadata['progressToken']: when present it must be str or int. bool is explicitly rejected because in Python bool is a subclass of int, so True would otherwise slip through the isinstance check — the guard exists to make that trap visible.
Source
Thrown at certifications/claude/lessons/11-mcp-server-design-and-integration/code/main.py:323
def _call_tool(
self, params: dict[str, Any], metadata: dict[str, Any]
) -> tuple[dict[str, Any], list[dict[str, Any]]]:
name = params["name"]
if not isinstance(name, str) or not name:
raise ValueError("name must be a non-empty string")
tool = self.tools.get(name)
if tool is None:
raise ValueError("unknown tool")
arguments = tool.validate_arguments(params.get("arguments", {}))
if name == "prepare_review":
return self._prepare_review(params, metadata, arguments), []
token = metadata.get("progressToken")
notifications: list[dict[str, Any]] = []
if token is not None:
if not isinstance(token, (str, int)) or isinstance(token, bool):
raise ValueError("progressToken must be a string or integer")
notifications = [
self._progress(token, 0, 1, "starting"),
self._progress(token, 1, 1, "complete"),
]
value = tool.handler(arguments)
return self._complete(
content=[{"type": "text", "text": json.dumps(value)}], isError=False
), notifications
def _prepare_review(
self,
params: dict[str, Any],
metadata: dict[str, Any],
arguments: dict[str, Any],
) -> dict[str, Any]:
input_requests = {
"workspace_scope": {"method": "roots/list", "params": {}},
"review_sample": {View on GitHub (pinned to 39ea8a1c6d)
Solutions
- Send progressToken as a string or integer, e.g. "tok-1" or 42
- Omit progressToken entirely when progress notifications are not wanted
- Never send booleans — they pass isinstance(token, int) checks in looser code but are rejected here by design
Example fix
# before
meta = {"progressToken": True}
# after
meta = {"progressToken": "tok-1"} Defensive patterns
Strategy: type-guard
Validate before calling
token = meta.get("progressToken")
if token is not None and (not isinstance(token, (str, int)) or isinstance(token, bool)):
raise ValueError("progressToken must be a string or integer")
server.exchange("tools/call", params, metadata=meta) Type guard
def is_valid_progress_token(token: object) -> bool:
return token is None or (isinstance(token, (str, int)) and not isinstance(token, bool)) Prevention
- Generate tokens as strings ('tok-<n>') or ints
- Remember bool is an int subclass in Python — exclude it explicitly
- Omit the token when you do not want progress notifications
When it happens
Trigger: tools/call with a progressToken set to true/false, a float, a list, or a dict in the request metadata (_meta).
Common situations: A JSON config or UI checkbox serialized as boolean into progressToken; client library defaulting the token to an options object; copying a whole request payload as the token.
Related errors
- -32600
- -32603
- _meta.{PROTOCOL_VERSION_KEY} is required
- _meta.{CLIENT_CAPABILITIES_KEY} is required
- server/discover accepts no params beyond _meta
AI-assisted analysis of rohitg00/ai-engineering-from-scratch@39ea8a1c6d (2026-08-26).
Data as JSON: /api/errors/aacae5953dc23611.
Report an issue: GitHub.