Skip to content

FastMCP: remember/recall/get_telemetry return 'structured_content must be a dict' despite success #17

@PSGSupport

Description

@PSGSupport

Summary

Three MCP tools return a FastMCP runtime error to the client even though the underlying memory operation succeeds and persists correctly:

  • mcp__cortex__remember
  • mcp__cortex__recall
  • mcp__cortex__get_telemetry

Error message:

structured_content must be a dict or None. Got str: '{...}'. Tools should wrap non-dict values based on their output_schema.

The data inside the error payload is the correct JSON; it's just being returned as a stringified JSON instead of a dict, so FastMCP rejects it before delivery.

Reproducer

From a strict MCP client (e.g. Claude Code's tool runtime), call:

remember({content: \"any non-trivial content\", tags: [\"test\"]})

The DB write happens (verified by memory_stats count incrementing and direct psql on the memories table), but the client sees the schema-wrap error.

The same shape of error occurs on recall and get_telemetry. Tools that work cleanly include memory_stats, forget, query_methodology, list_domains, wiki_list, wiki_read, detect_gaps, seed_project, consolidate, rebuild_profiles, backfill_memories.

Likely cause

The handlers' return values are being JSON-encoded to a string before FastMCP sees them. FastMCP expects a dict (or None) when an output_schema is declared. Either:

  1. The handlers wrap the dict in json.dumps() somewhere in the response path, or
  2. The output_schema decorator is missing on these specific handlers and FastMCP's auto-wrap can't infer the structure.

Comparing the affected vs. clean handlers should localize quickly — the cleanly-working handlers presumably return dicts directly without the JSON string round-trip.

Impact

  • Every call from a strict MCP client surfaces as an exception even when the op succeeded — confusing UX, and unsafe for any caller that bails on tool exceptions.
  • Workaround: ignore the error and verify via memory_stats / direct recall from a different code path. Not a production-acceptable workaround.

Environment

  • Cortex 3.14.5
  • Python 3.12 inside Docker container (cortex-mcp:latest built from upstream source via local Dockerfile)
  • FastMCP version: whatever is pinned in pyproject.toml (fastmcp>=2.0.0)
  • Client: Claude Code MCP runtime

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions