Skip to main content
Uploaded files are surfaced as MCP resources so clients that speak 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

Response:

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 — after POST /api/uploads/{chat_id}/{filename} saves a new file.

Uploads

Upload stays on HTTP — MCP has no upload primitive:
After the POST returns, the resource is immediately visible to resources/list.