fix: coerce JSON-string tags to list in MemoryItem and MCP tools (#682)
MCP tool bridges sometimes serialize JSON arrays as strings during transport, e.g. '["a", "b"]' arrives as the literal string '["a", "b"]' instead of a native JSON array. This causes Pydantic to reject the input with a validation error. Add defensive coercion at two layers: 1. HTTP API (http.py): Pydantic field_validator on MemoryItem.tags with mode="before" that parses JSON strings back into lists. 2. MCP tools (mcp_tools.py): Same coercion in build_content_dict before tags reach the Pydantic model. A plain non-JSON string is wrapped in a single-element list. Correctly-formatted input is passed through unchanged. Co-authored-by: Philipp <philipp@Philipps-MacBook-Pro.local>
This commit is contained in:
parent
4285e94406
commit
c5273f5fd4
2 changed files with 34 additions and 0 deletions
|
|
@ -424,6 +424,27 @@ class MemoryItem(BaseModel):
|
||||||
default=None,
|
default=None,
|
||||||
description="Optional tags for visibility scoping. Memories with tags can be filtered during recall.",
|
description="Optional tags for visibility scoping. Memories with tags can be filtered during recall.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@field_validator("tags", mode="before")
|
||||||
|
@classmethod
|
||||||
|
def coerce_tags(cls, v):
|
||||||
|
"""Coerce JSON-string tags to list.
|
||||||
|
|
||||||
|
MCP tool bridges sometimes serialize JSON arrays as strings during
|
||||||
|
transport, e.g. '["a", "b"]' instead of ["a", "b"]. This validator
|
||||||
|
parses such strings back into lists so the retain call succeeds.
|
||||||
|
A plain non-JSON string is wrapped in a single-element list.
|
||||||
|
"""
|
||||||
|
if isinstance(v, str):
|
||||||
|
try:
|
||||||
|
parsed = json.loads(v)
|
||||||
|
if isinstance(parsed, list):
|
||||||
|
return parsed
|
||||||
|
except (json.JSONDecodeError, TypeError):
|
||||||
|
pass
|
||||||
|
return [v]
|
||||||
|
return v
|
||||||
|
|
||||||
observation_scopes: Literal["per_tag", "combined", "all_combinations"] | list[list[str]] | None = Field(
|
observation_scopes: Literal["per_tag", "combined", "all_combinations"] | list[list[str]] | None = Field(
|
||||||
default=None,
|
default=None,
|
||||||
title="ObservationScopes",
|
title="ObservationScopes",
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,19 @@ def build_content_dict(
|
||||||
Returns:
|
Returns:
|
||||||
Tuple of (content_dict, error_message). error_message is None if successful.
|
Tuple of (content_dict, error_message). error_message is None if successful.
|
||||||
"""
|
"""
|
||||||
|
# Coerce tags from JSON string to list if needed.
|
||||||
|
# MCP tool bridges sometimes serialize JSON arrays as strings during
|
||||||
|
# transport, e.g. '["a", "b"]' instead of ["a", "b"].
|
||||||
|
if isinstance(tags, str):
|
||||||
|
try:
|
||||||
|
parsed = json.loads(tags)
|
||||||
|
if isinstance(parsed, list):
|
||||||
|
tags = parsed
|
||||||
|
except (json.JSONDecodeError, TypeError):
|
||||||
|
pass
|
||||||
|
if isinstance(tags, str):
|
||||||
|
tags = [tags]
|
||||||
|
|
||||||
content_dict: dict[str, Any] = {"content": content, "context": context}
|
content_dict: dict[str, Any] = {"content": content, "context": context}
|
||||||
|
|
||||||
if timestamp:
|
if timestamp:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue