Skip to content

JSON-string arg deserialization is a FastMCP workaround — track upstream #24

@mgoldsborough

Description

@mgoldsborough

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

  1. 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.
  2. 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.
  3. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions