[codex] Harden MCP multimodal media intake - #68
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
@claude Please review this PR throughly., If any manual tests or anything else is needed please ask. Please do not leave any decisison tree or dry runs. go to eeach line of code, do a dry run, make a dependency tree, dry run those codes, find bugs and tell me. Even if you think this might be not an issue, but dry run is telling yo,u, tell me,. USe a real productin case where you will make a test suite, dry run it, see the procvess, report if it breaks, and tell me again, |
|
Claude finished @Atharva-Kanherkar's task in 5m 29s —— View job PR Review: [codex] Harden MCP multimodal media intakeTasks:
Dependency TreeBug #1 — HIGH:
|
| # | Severity | Location | Issue |
|---|---|---|---|
| 1 | HIGH | server.py:206-208 |
max_media_bytes not propagated to EpisodicStore — limits are decoupled |
| 2 | HIGH | server.py:441-445 |
TOCTOU: stat() after os.access() emits raw OS error on file deletion |
| 3 | MEDIUM | server.py:463-467 |
mimetypes.guess_extension returns .mpga on Linux, bypassing _MEDIA_TYPE_SUFFIXES → potential ValueError from MediaStore |
| 4 | MEDIUM | server.py:445 + episodic_store.py:204 |
Redundant size checks with different error messages; EpisodicStore check is dead code in default config |
| 5 | LOW | test_mcp_server.py:136-147 |
Recall assertion passes trivially (single record), doesn't test similarity |
| 6 | LOW | test_mcp_server.py:95-151 |
_MEDIA_TYPE_SUFFIXES fallback is never exercised by any test |
There was a problem hiding this comment.
Pull request overview
This PR hardens the MCP server’s multimodal media intake by enforcing media size limits earlier, improving host-side error messages for unreadable local paths, and adding regression tests covering local-path and inline media scenarios.
Changes:
- Add configurable max media size handling in
MemoryMCPServer, with explicit size validation for both local paths and inline base64 payloads. - Improve error messages when local
file_pathinputs are not readable from the MCP server host. - Extend MCP regression tests for local screenshot/audio/video/PDF paths, inline materialization, and oversized payload rejection.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
mcp_server/server.py |
Adds media suffix defaults, host-visible path errors, and size validation via _validate_media_size() for both file-path and inline media flows. |
tests/test_mcp_server.py |
Adds coverage for local media-path persistence + recall, and validates oversized inline payload rejection and improved local-path error messaging. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| max_media_bytes: int | None = None, | ||
| ) -> None: | ||
| self._config = config.validate() | ||
| self._embedder = embedder | ||
| self._max_media_bytes = ( |
There was a problem hiding this comment.
max_media_bytes is only enforced in the MCP intake layer (_prepare_media), but it is not propagated into the underlying stores created by build_runtime() (which still use config.MEDIA_EMBED_MAX_BYTES). This can lead to inconsistent behavior (e.g., server accepts a payload that the store later rejects, or vice-versa). Consider plumbing max_media_bytes through build_runtime() into SemanticStore/EpisodicStore/ProceduralStore, or removing this parameter from MemoryMCPServer if it’s intended to be intake-only.
| try: | ||
| payload = base64.b64decode(inline.inline_base64, validate=True) | ||
| except binascii.Error as exc: | ||
| raise ValueError("inline_base64 must be valid base64") from exc | ||
| if not payload: |
There was a problem hiding this comment.
Inline payload size is validated only after base64.b64decode(...) has already materialized the full bytes in memory. A very large inline_base64 can still cause high memory usage/DoS before the size check runs. Consider rejecting earlier by estimating decoded size from the base64 string length (or enforcing transport-level request limits) before decoding.
| def test_prepare_media_reports_local_path_errors_from_server_host_view(): | ||
| server = make_server() | ||
| missing_path = "/tmp/agentic_memory_missing_screenshot.png" | ||
| try: | ||
| with pytest.raises(FileNotFoundError) as exc_info: | ||
| server._prepare_media( # type: ignore[attr-defined] | ||
| RememberEpisodeRequest( | ||
| session_id="session-1", | ||
| text="Missing screenshot", | ||
| media={"file_path": missing_path}, | ||
| ).media, | ||
| "image", | ||
| ) | ||
| assert "MCP server host" in str(exc_info.value) | ||
| assert missing_path in str(exc_info.value) |
There was a problem hiding this comment.
This test uses a hard-coded /tmp/agentic_memory_missing_screenshot.png path; if that file exists on a developer/CI machine, the assertion expecting FileNotFoundError will fail. Prefer generating a guaranteed-missing path (e.g., tmp_path / "missing.png" or a TemporaryDirectory + non-existent filename).
| def test_prepare_media_reports_local_path_errors_from_server_host_view(): | |
| server = make_server() | |
| missing_path = "/tmp/agentic_memory_missing_screenshot.png" | |
| try: | |
| with pytest.raises(FileNotFoundError) as exc_info: | |
| server._prepare_media( # type: ignore[attr-defined] | |
| RememberEpisodeRequest( | |
| session_id="session-1", | |
| text="Missing screenshot", | |
| media={"file_path": missing_path}, | |
| ).media, | |
| "image", | |
| ) | |
| assert "MCP server host" in str(exc_info.value) | |
| assert missing_path in str(exc_info.value) | |
| def test_prepare_media_reports_local_path_errors_from_server_host_view(tmp_path: Path): | |
| server = make_server() | |
| missing_path = tmp_path / "agentic_memory_missing_screenshot.png" | |
| try: | |
| with pytest.raises(FileNotFoundError) as exc_info: | |
| server._prepare_media( # type: ignore[attr-defined] | |
| RememberEpisodeRequest( | |
| session_id="session-1", | |
| text="Missing screenshot", | |
| media={"file_path": str(missing_path)}, | |
| ).media, | |
| "image", | |
| ) | |
| assert "MCP server host" in str(exc_info.value) | |
| assert str(missing_path) in str(exc_info.value) |
|
@claude re review. patched important parts |
|
Claude finished @Atharva-Kanherkar's task in 2m 12s —— View job Re-review: [codex] Harden MCP multimodal media intakeTasks:
Status of Previous Findings
All HIGH and MEDIUM bugs from the prior review are addressed. Below are the remaining issues — all LOW severity. Remaining Issue #1 — LOW: Hard-coded
|
Summary
Why
Issue #62 requires MCP multimodal inputs that work without FastAPI uploads, fail clearly on remote hosts when local paths are invalid, and return durable stored paths instead of temporary blobs. The shared MCP intake layer already handled most of the data flow, but it was missing early size enforcement and clearer host-visibility errors for local paths.
Validation
PYTHONPATH=. .venv/bin/pytest -q tests/test_mcp_server.py tests/test_mcp_schemas.pyPYTHONPATH=. .venv/bin/pytest -q tests/test_mcp_server.py tests/test_mcp_schemas.py tests/test_api.pyCloses #62.