diff --git a/README.md b/README.md index 6f996be..757aae8 100644 --- a/README.md +++ b/README.md @@ -160,7 +160,7 @@ bundle exec bin/mail_mcp generate \ | `list_mailboxes` | — | List all IMAP folders | | `list_mail_messages` | `folder`, `page`, `per_page` | List messages with pagination | | `get_mail_message` | `folder`, `uid` | Fetch full message; attachments uploaded to S3 and returned as presigned URLs | -| `search_mail_messages` | `folder`, `query` | Raw IMAP SEARCH criteria, e.g. `UNSEEN` or `FROM alice@example.com SINCE 01-Jan-2025` | +| `search_mail_messages` | `folder`, `query` | Raw IMAP SEARCH criteria, e.g. `UNSEEN` or `FROM alice@example.com SINCE 01-Jan-2025`; returns UIDs | | `send_mail_message` | `to`, `subject`, `text_body`, `cc`, `bcc`, `html_body`, `attachment_urls`, `folder` | Send via SMTP and append to the Sent folder via IMAP; attachments fetched from S3 presigned URLs | | `create_draft_mail_message` | `to`, `subject`, `text_body`, `cc`, `bcc`, `html_body`, `attachment_urls`, `folder` | Append to Drafts via IMAP APPEND; attachments fetched from S3 presigned URLs | | `delete_mail_message` | `folder`, `uid` | Mark `\Deleted` + EXPUNGE | diff --git a/lib/mail_mcp/imap_client.rb b/lib/mail_mcp/imap_client.rb index 9b12886..d145741 100644 --- a/lib/mail_mcp/imap_client.rb +++ b/lib/mail_mcp/imap_client.rb @@ -90,7 +90,10 @@ def get_message(folder:, uid:) def search_messages(folder:, query:) MailMCP.logger.info { "IMAP search_messages folder=#{folder.inspect} query=#{query.inspect}" } @imap.examine(folder) - results = @imap.search(query.split) + # UID SEARCH, not SEARCH: the ids returned here are fed back into + # get_message/delete_message/move_message/update_flags, which all issue + # UID commands. Sequence numbers would only match until the first expunge. + results = @imap.uid_search(query.split) MailMCP.logger.debug { "IMAP search_messages matched=#{results.size}" } results end diff --git a/lib/mail_mcp/tools/search_mail_messages_tool.rb b/lib/mail_mcp/tools/search_mail_messages_tool.rb index 1eeaa4d..59af44a 100644 --- a/lib/mail_mcp/tools/search_mail_messages_tool.rb +++ b/lib/mail_mcp/tools/search_mail_messages_tool.rb @@ -1,7 +1,8 @@ module MailMCP class SearchMailMessagesTool < Tool tool_name "search_mail_messages" - description "Search messages in an IMAP folder using raw IMAP SEARCH criteria" + description "Search messages in an IMAP folder using raw IMAP SEARCH criteria. " \ + "Returns message UIDs usable with get_mail_message" annotations( title: "Search Mail Messages", read_only_hint: true, diff --git a/spec/mail_mcp/imap_client_spec.rb b/spec/mail_mcp/imap_client_spec.rb index d2b3985..6837bcc 100644 --- a/spec/mail_mcp/imap_client_spec.rb +++ b/spec/mail_mcp/imap_client_spec.rb @@ -108,15 +108,15 @@ end describe "#search_messages" do - it "passes raw query string to IMAP SEARCH" do - allow(imap).to receive(:search).with(["UNSEEN"]).and_return([1, 2, 3]) + it "passes raw query string to IMAP UID SEARCH" do + allow(imap).to receive(:uid_search).with(["UNSEEN"]).and_return([1, 2, 3]) client = described_class.new(imap) result = client.search_messages(folder: "INBOX", query: "UNSEEN") expect(result).to eq([1, 2, 3]) end it "supports multi-word criteria" do - allow(imap).to receive(:search).with(["FROM", "alice@example.com"]).and_return([5]) + allow(imap).to receive(:uid_search).with(["FROM", "alice@example.com"]).and_return([5]) client = described_class.new(imap) result = client.search_messages(folder: "INBOX", query: "FROM alice@example.com") expect(result).to eq([5])