Context
upjack.server._make_entity_tool.run() contains this workaround:
```python
Raw Tool subclasses bypass FastMCP's Pydantic deserialization —
object arguments may arrive as JSON strings over stdio transport
parsed: dict[str, Any] = {}
for k, v in arguments.items():
if isinstance(v, str) and v.startswith(("{", "[")):
try:
parsed[k] = json.loads(v)
except (json.JSONDecodeError, ValueError):
parsed[k] = v
else:
parsed[k] = v
```
This exists because Upjack's CRUD tools are registered as raw Tool subclasses (so the input schema can be supplied as raw JSON Schema, not derived from a Python function signature). Raw Tool subclasses skip FastMCP's Pydantic-based deserialization layer. Over stdio transport, object and array arguments can arrive as JSON-serialized strings rather than parsed dicts/lists, so we re-parse them here.
Why this is debt
- It's a transport-layer concern living inside Upjack. The right home is FastMCP (or an MCP transport middleware).
- It's heuristic (
startswith("{", "[")) — not a guaranteed-correct demarshaling.
- It silently eats
JSONDecodeError, which could mask real malformed input.
- It's ~10 lines of code that every maintainer has to understand and preserve.
What to track
- Upstream: check whether FastMCP has a canonical way to register raw-schema tools that does go through Pydantic deserialization. If so, migrate and delete the workaround. File/link an issue at jlowin/fastmcp if the behavior is unintended.
- Alternative: audit whether Upjack could express its tool schemas via function signatures +
Annotated[...] and get FastMCP's native deserialization for free. May conflict with the "raw JSON Schema as source of truth" design.
- Safety net: if we keep the workaround, make the
JSONDecodeError swallow produce a logger.debug (or similar) so it's at least auditable.
Severity
Low. The workaround is functionally correct for the call shapes we see. This issue exists so the code doesn't get ripped out as "dead" by a future maintainer, and so the escape hatch (upstream fix) is visible.
Related
src/upjack/server.py — _make_entity_tool.run()
- Tests that exercise this path:
tests/test_server.py::TestJsonStringDeserialization
Context
upjack.server._make_entity_tool.run()contains this workaround:```python
Raw Tool subclasses bypass FastMCP's Pydantic deserialization —
object arguments may arrive as JSON strings over stdio transport
parsed: dict[str, Any] = {}
for k, v in arguments.items():
if isinstance(v, str) and v.startswith(("{", "[")):
try:
parsed[k] = json.loads(v)
except (json.JSONDecodeError, ValueError):
parsed[k] = v
else:
parsed[k] = v
```
This exists because Upjack's CRUD tools are registered as raw
Toolsubclasses (so the input schema can be supplied as raw JSON Schema, not derived from a Python function signature). Raw Tool subclasses skip FastMCP's Pydantic-based deserialization layer. Over stdio transport, object and array arguments can arrive as JSON-serialized strings rather than parsed dicts/lists, so we re-parse them here.Why this is debt
startswith("{", "[")) — not a guaranteed-correct demarshaling.JSONDecodeError, which could mask real malformed input.What to track
Annotated[...]and get FastMCP's native deserialization for free. May conflict with the "raw JSON Schema as source of truth" design.JSONDecodeErrorswallow produce alogger.debug(or similar) so it's at least auditable.Severity
Low. The workaround is functionally correct for the call shapes we see. This issue exists so the code doesn't get ripped out as "dead" by a future maintainer, and so the escape hatch (upstream fix) is visible.
Related
src/upjack/server.py—_make_entity_tool.run()tests/test_server.py::TestJsonStringDeserialization