resources/* (Claude Desktop, Agents SDK, MCP Inspector) can list and read them without going through bash_tool.
URI shape
chat_id is embedded in the URI because Agents SDK and Inspector don’t re-send X-Chat-Id on per-resource calls — URIs must be self-contained.
URL-encoding: FastMCP’s ResourceTemplate.matches uses [^/]+ per template param (blocks nested paths). Encoding the path flattens it without forking the SDK.
resources/list
resources/read
- Text MIME types (
text/*+ a short allowlist) →contents[0].text. - Everything else →
contents[0].blob(base64).
Dynamic registration
sync_chat_resources(chat_id) clears previously-registered entries for that chat and re-adds from the current filesystem state, under an asyncio.Lock to avoid “dict changed size during iteration” when a concurrent resources/list runs during an upload.
Called from:
docker_manager._create_container— initial sync when the container spins up.app.py:upload_file— afterPOST /api/uploads/{chat_id}/{filename}saves a new file.
Uploads
Upload stays on HTTP — MCP has no upload primitive:resources/list.
