Tags: Wh1isper/mcp-email-server
Tags
feat: report the delivered Message-Id from send_email and forward_ema… …il (#249) Composition already assigned an RFC 5322 Message-Id to every outgoing message, but no submission result carried it, so a caller keeping its own record of sent mail had to choose between an empty identifier and an invented one. Carry the identifier from the SMTP delivery effect through SendMutationOutcome into both tool responses, mirroring the identifier save_to_mailbox already reports. The identifier is attached only where an accepted DATA phase proves which message the provider took. A rejected, timed-out, or otherwise ambiguous submission still reports none, and a failed or unknown Sent copy never removes the identifier of a message that was demonstrably delivered. A clean send appends "Message-Id: <...>" to the success line; a partial delivery reports the same value in its own message-id section. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Restore a container image build and refresh the locked dependencies t… …hat carry advisories (#245) * Restore a container image build A Dockerfile lived in this repository until #203, which removed it together with the publishing job in the release workflow. The last image on ghcr is therefore 0.16.0 from 2026-07-13, while the project has since moved to 1.6.x. Anyone running the server as a container is stuck on that image, and it now reports 176 fixable advisories -- 18 of them in pillow, which arrived through the gradio dependency this project has since dropped. This restores a build, in two stages so the runtime layer carries neither uv nor the build inputs. Two details worth keeping: - The virtualenv stays at /app/.venv. Deployments that run the server over streamable-http rather than stdio address the interpreter directly, and that path was the de-facto interface of the previous image. - tini stays as the init process, as before, so the children the server spawns are reaped rather than accumulating over long-running IMAP sessions. Verified by building the image and driving it over streamable-http: it starts, answers `initialize` and `tools/list` with all 18 tools, and `list_mailboxes` and `list_emails_metadata` return real data against a live IMAP account. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KgNBBghEfVPSevVBzT2CSc * Refresh the locked dependencies that carry advisories A scan of an image built from the current lockfile reports 38 advisories with a fix available. They all sit in transitive pins that the lockfile holds below the fixed releases; the declared ranges in pyproject.toml already allow them. cryptography 46.0.4 -> 50.0.1 python-multipart 0.0.20 -> 0.0.32 PyJWT 2.11.0 -> 2.13.0 starlette 0.48.0 -> 1.6.0 mcp 1.26.0 -> 1.29.1 aiosmtplib 4.0.2 -> 5.1.2 pydantic-settings 2.12.0 -> 2.15.0 python-dotenv 1.1.1 -> 1.2.3 pygments 2.19.2 -> 2.21.0 idna 3.10 -> 3.19 That takes the same image from 38 fixable advisories to 3, the remainder being setuptools and msgpack, which do not move inside the current constraints. Deliberately a targeted `uv lock --upgrade-package ...` rather than a blanket `uv lock --upgrade`. The blanket form also lifts typer and its click dependency, and 10 tests then fail -- eight in test_managed_cli.py, one in test_stdio_protocol.py, one in test_web_ui_server.py -- all around CLI option handling. Those packages carry no advisories, so there was no reason to move them. Verified: `pytest` passes in full, 1457 passed / 37 skipped, identical to the run on the unmodified lockfile. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KgNBBghEfVPSevVBzT2CSc * feat(container): restore verified GHCR releases --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Wh1isper <9573586@qq.com>
fix(metadata): enforce exact datetime filtering (#244) IMAP BEFORE and SINCE discard time and timezone, so datetime queries could return imprecise results or an empty same-day interval. Require timezone-aware boundaries, widen IMAP date criteria into conservative candidate searches, and reapply the exact INTERNALDATE interval before total, ordering, and pagination. Keep the MCP contract, documentation, deterministic coverage, and GreenMail evidence aligned with the corrected behavior. Assisted-by: YAAI <261597362+yaai-bot@users.noreply.github.com>
feat: add semantic IMAP tags and embedded attachment content (#242) * feat: add semantic email tags and attachment content Add account-scoped semantic IMAP tags across legacy and managed configuration, metadata reads, mutations, CLI, and the management UI. Add an independent opt-in MCP embedded attachment content path for clients without a shared filesystem while preserving local download behavior. Derived from and cherry-picked from 68a5c59. Co-authored-by: alonso <acuna007@gmail.com> * fix(managed): migrate schema v3 catalogs to v4 Preserve existing managed accounts, policy, bindings, and secrets when opening the schema introduced before semantic tags and embedded attachment content. Validate the exact v3 catalog, add the new default-disabled fields in one transaction, and record v4 only after schema and invariant checks pass. Assisted-by: YAAI <261597362+yaai-bot@users.noreply.github.com> --------- Co-authored-by: alonso <acuna007@gmail.com>
feat: add forward_email tool (#184) * feat: add forward_email tool Forward an existing message to new recipients, re-attaching the original's MIME parts. Reworked against the Local Email App V2 application boundaries as requested in the review of #184, rather than rebasing the previous app.py/classic-handler implementation. Application layer (application/mutations.py) - ForwardCommand (a ComposeCommand carrying source_email_id, source_mailbox and include_attachments), ForwardSource/ForwardSourcePart, and ForwardService. - Three independent provider effects, each preceded by a fresh authority and policy resolution: the IMAP source read, SMTP delivery, and the sent copy. The recipient allowlist is validated against the pre-open snapshot and again against the opened account. - A failed, denied, cancelled or ambiguous source read aborts before the outgoing provider is opened, so a forward can never be delivered without the parts it was meant to carry. Sentinel ValueError and MutationProviderError propagate out of execute(); only TimeoutError is caught, and it is re-raised as MutationProviderError rather than being folded into a delivery outcome. - Forward bounds reuse APPLICATION_LIMITS: part count, per-part and aggregate attachment bytes, and the derived subject. The derived body (caller note plus forwarded block) is re-validated through ComposeCommand.validate, so an oversized forward is rejected rather than truncated. - The sent-copy tail of SendService.execute is extracted into a shared _complete_send() used by both services, keeping the ambiguity and reconciliation semantics in one place. SendService behavior is unchanged. - Results reuse SendMutationOutcome and existing detail tags. Provider layer (emails/classic.py) - EmailClient.fetch_forward_source() reads the source in a single IMAP session, enforcing the sender allowlist before the body is fetched so a blocked source stays indistinguishable from a missing one. Every failure raises; there is no empty-list sentinel that could be mistaken for "no attachments". - normalize_forwarded_part() round-trips each source part through compat32 before it is attached. Source parts are parsed under policy=default, while compose_message builds compat32 containers that are later flattened under SMTP, SMTPUTF8, compat32 for the IMAP append, and again by aiosmtplib. Attaching a structured part directly loses the RFC 2231 charset label and re-encodes the parameter as unknown-8bit, and does so only when the source header needs refolding. The round-trip freezes part headers as opaque strings, so every send path emits identical bytes. - Parts are re-attached rather than rebuilt from get_payload(decode=True), which preserves message/rfc822 subtrees, zero-byte parts, Content-ID, Content-Transfer-Encoding and Content-Type parameters. - compose_message() and send_email_with_outcome() accept keyword-only extra_parts; existing call sites and behavior are untouched. - When forwarded parts would place raw 8-bit octets on a session that did not advertise 8BITMIME, the transaction fails before MAIL with smtp-8bitmime-required, mirroring the existing smtp-utf8-unsupported pre-flight rejection. send_email_with_outcome has no general 7-bit downgrade; that gap is pre-existing and left for a separate change. Transport and adapter - ClassicMutationProvider.fetch_forward_source()/forward(), both bounded, with the same capability_unavailable guard as send for IMAP-only accounts. - forward_email is annotated as a non-read-only, non-idempotent, open-world mutation, matching send_email, and the catalog fixture is regenerated. - smtp-8bitmime-required is added to the public send detail allowlist so the reason survives redaction. Docs and spec - docs/tools.md, docs/guides.md and docs/configuration.md cover the tool, its annotations, the plain-text quoting limitation and the SMTP requirement. - spec/07 gains a Forward subsection and acceptance criterion 07.12; spec/12 extends the corresponding verification row. Tests: +83 unit and contract tests plus GreenMail stdio E2E coverage that verifies the delivered message and the sent copy over imaplib rather than trusting the server's own response. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: tighten forward policy and size bounds * test: preserve forward source privacy precedence --------- Co-authored-by: Jack Koch <jack@jbkjr.me> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Wh1isper <9573586@qq.com>
PreviousNext