Let agents list and move mail in server folders#5

opened21h agobyMMichael Rodriguez

Agent access (PR #2) and folder navigation (PR #3) were built at the same time, so the agent tools don't know about server folders yet. Agents can find mail in Sent, Drafts and the user's folders and Gmail labels through search_messages, but every such result is labelled only "folder": "filed". They can't list a folder, see which folder a message is in, or move mail into one.

Agent Brief

Category: enhancement Summary: Agents can list the user's server folders, read a folder's messages, and move mail into a folder.

Current behavior: The MCP tools (list_accounts, list_messages, search_messages, read_message, update_messages, send_email) only know Spark's built-in lists. list_messages takes folder from a fixed enum (inbox, new_senders, snoozed, set_aside, done, trash). Messages in MessageState::Filed (Sent, Drafts, Spam, the user's folders and Gmail labels) can't be listed, and in search results their folder is just "filed". update_messages can't file mail into a folder, although the UI can by dragging (a Patch with folder set).

Desired behavior:

  • A new read tool, list_folders, returns the server folders of the accounts shared with the agent: id, account, name, parent, role (sent, drafts, archive, trash, spam, flagged, or none), and when the folder's messages were last synced. It needs read access, like the other read tools.
  • list_messages can list one server folder by id, in addition to the existing built-in lists, with the same paging (limit, offset) and filters. A folder whose messages have never been synced, or not recently, says so in the result instead of looking empty. Folders the user isn't watching are only fetched when opened.
  • Message summaries for filed mail name the folder (its id and name) instead of only "filed".
  • update_messages gets a move action that takes a folder id and files the messages there, exactly as dragging them onto that folder does in the UI. It goes through the same undo window, reaches the server the same way, and swaps labels on Gmail. It needs triage access. A folder from a different account than the message is refused with a clear error.
  • The tool descriptions and the README's AI agents section describe the new capabilities.

Key interfaces:

  • Store::folders() / Store::folder(id) and the Folder type (role: Option<FolderRole>, synced_at): the source for list_folders.
  • Folder membership (folder_members, Store::folder_member_ids): which messages a folder holds.
  • Patch::folder: the existing way to file a message. Archive and Trash folders become Done and Deleted. The agent move should use it, not a new path.
  • Access checks stay inside spark-rs, using the agent Context's per-account access, as the existing tools do.

Acceptance criteria:

  • list_folders returns only folders of accounts the agent has at least read access to, with the fields above.
  • list_messages with a folder id returns that folder's messages, newest first, paged like the built-in lists. An unknown folder id, or one in an unshared account, is an error that says what's wrong.
  • search_messages and list_messages results for filed mail include the folder's id and name.
  • update_messages with the move action and a folder id files the messages there, and the UI shows the same result as a drag. With read-only access it's refused, and with a folder from another account it's refused.
  • Unit tests cover the cases above against an in-memory store, like the existing agent tool tests.
  • cargo clippy --workspace --all-targets -- -D warnings and cargo test --workspace pass.

Out of scope:

  • Creating, renaming or deleting folders through agents.
  • Creating or editing drafts through agents.
  • Smart folders (saved searches) and the sidebar's layout (pins, order, colors).
  • The one-click Connect step and skills (#4).