rename: mail-mcp -> Carrier
Sulkta naming convention. Carrier (the carrier pigeon — single- purpose, reliable, comes back every time) sits alongside Aldabra (giant tortoise), Hawk (eBay-resale eyes), Skald (Norse storyteller), cWHO (monitoring), Cauldron (meal planning), Clawdforge (build), tny (URL shortener), ktra (cargo registry). The full audit/cleanup/Phase-A-B-C arc happened under the mail-mcp name; this commit just renames the identity: - Cargo workspace + crate package name: mail-mcp -> carrier - Binary name: mail-mcp -> carrier - USER_AGENT header: mail-mcp/<ver> -> carrier/<ver> - Config env var: MAIL_MCP_CONFIG -> CARRIER_CONFIG - Default config path: ~/.config/mail-mcp/config.toml -> ~/.config/carrier/config.toml - ServerHandler instructions reference: 'mail-mcp' -> 'Carrier' - README + repo URL refs updated - Workspace path: /root/build/mail-mcp -> /root/build/carrier - Git remote: gitea:Sulkta-Coop/mail-mcp -> gitea:Sulkta-Coop/carrier Tool names stay mail_* (mail_send, mail_inbox_list, mail_reply, etc.) because they describe the email domain — same convention as Aldabra keeps wallet_*/chain_*/dao_*/escrow_*. The server-level identity is Carrier; the tools it carries are mail.
This commit is contained in:
parent
5e1c63eeaa
commit
c43283ad5b
10 changed files with 102 additions and 71 deletions
56
Cargo.lock
generated
56
Cargo.lock
generated
|
|
@ -137,6 +137,34 @@ version = "1.11.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
|
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "carrier"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"async-imap",
|
||||||
|
"base64 0.22.1",
|
||||||
|
"chrono",
|
||||||
|
"dirs 5.0.1",
|
||||||
|
"futures",
|
||||||
|
"lettre",
|
||||||
|
"mail-parser",
|
||||||
|
"rmcp",
|
||||||
|
"rustls",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"schemars",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"shellexpand",
|
||||||
|
"tokio",
|
||||||
|
"tokio-rustls",
|
||||||
|
"toml",
|
||||||
|
"tracing",
|
||||||
|
"tracing-subscriber",
|
||||||
|
"uuid",
|
||||||
|
"webpki-roots 0.26.11",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.62"
|
version = "1.2.62"
|
||||||
|
|
@ -763,34 +791,6 @@ version = "0.4.29"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "mail-mcp"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"async-imap",
|
|
||||||
"base64 0.22.1",
|
|
||||||
"chrono",
|
|
||||||
"dirs 5.0.1",
|
|
||||||
"futures",
|
|
||||||
"lettre",
|
|
||||||
"mail-parser",
|
|
||||||
"rmcp",
|
|
||||||
"rustls",
|
|
||||||
"rustls-pki-types",
|
|
||||||
"schemars",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"shellexpand",
|
|
||||||
"tokio",
|
|
||||||
"tokio-rustls",
|
|
||||||
"toml",
|
|
||||||
"tracing",
|
|
||||||
"tracing-subscriber",
|
|
||||||
"uuid",
|
|
||||||
"webpki-roots 0.26.11",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mail-parser"
|
name = "mail-parser"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
|
|
|
||||||
14
Cargo.toml
14
Cargo.toml
|
|
@ -1,18 +1,22 @@
|
||||||
# Cargo workspace root for mail-mcp.
|
# Cargo workspace root for Carrier — Sulkta's Rust MCP email server.
|
||||||
#
|
#
|
||||||
# One crate today (mail-mcp), workspace shape so we can grow without
|
# Named after the carrier pigeon: single-purpose, reliable, comes back
|
||||||
|
# every time. Carries Sulkta-hosted mail (kayos@/cobb@/abby@/bay@/jay@)
|
||||||
|
# to and from any MCP client.
|
||||||
|
#
|
||||||
|
# One crate today (carrier), workspace shape so we can grow without
|
||||||
# rework. Same pattern as aldabra.
|
# rework. Same pattern as aldabra.
|
||||||
#
|
#
|
||||||
# Workspace deps pinned here; each crate references with `foo = { workspace = true }`.
|
# Workspace deps pinned here; each crate references with `foo = { workspace = true }`.
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = ["crates/mail-mcp"]
|
members = ["crates/carrier"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "http://192.168.0.5:3001/Sulkta-Coop/mail-mcp"
|
repository = "http://192.168.0.5:3001/Sulkta-Coop/carrier"
|
||||||
authors = ["Cobb <cobb@sulkta.com>", "Kayos <kayos@sulkta.com>"]
|
authors = ["Cobb <cobb@sulkta.com>", "Kayos <kayos@sulkta.com>"]
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
|
|
@ -78,7 +82,7 @@ chrono = { version = "0.4", default-features = false, features = ["clock"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
||||||
# Dirs lookup for `~/.config/mail-mcp/config.toml` default path
|
# Dirs lookup for `~/.config/carrier/config.toml` default path
|
||||||
dirs = "5"
|
dirs = "5"
|
||||||
|
|
||||||
# Shell-style env-var expansion for the `password_file` setting
|
# Shell-style env-var expansion for the `password_file` setting
|
||||||
|
|
|
||||||
69
README.md
69
README.md
|
|
@ -1,18 +1,25 @@
|
||||||
# mail-mcp
|
# Carrier
|
||||||
|
|
||||||
Rust MCP server for Sulkta-hosted email. SMTP send + IMAP read with RFC-correct headers, multipart/alternative when HTML is included, multipart/mixed for attachments, threading via `In-Reply-To`/`References`.
|
Sulkta's Rust MCP email server. SMTP send + IMAP read with RFC-correct headers, multipart/alternative when HTML is included, multipart/mixed for attachments, threading via `In-Reply-To`/`References`. Named after the carrier pigeon — single-purpose, reliable, comes back every time.
|
||||||
|
|
||||||
Replaces the `scripts/kayos_mail.py` CLI path that lived in `kayos/openclaw-workspace` since 2026-04-23.
|
10 MCP tools. Multi-account. Attachment-safe. Replaces the `scripts/kayos_mail.py` CLI path that lived in `kayos/openclaw-workspace` since 2026-04-23.
|
||||||
|
|
||||||
## Why a server, not a CLI
|
## Why a server, not a CLI
|
||||||
|
|
||||||
`kayos_mail.py` shipped without `Date` or `Message-ID` headers until a 2026-05-18 patch — exactly the kind of header-discipline regression a typed Rust server prevents at compile time. The "no spam bin" framing is mostly upstream of any client (Rackham postfix + rspamd DKIM-sign at the relay; mail-tester scored 10/10 and port25 SpamAssassin −7.31 on 2026-05-20), but a correct client doesn't trip filters with bad MIME structure, broken threading, or missing headers.
|
`kayos_mail.py` shipped without `Date` or `Message-ID` headers until a 2026-05-18 patch — exactly the kind of header-discipline regression a typed Rust server prevents at compile time. The "no spam bin" framing is mostly upstream of any client (Rackham postfix + rspamd DKIM-sign at the relay; mail-tester scored 10/10 and port25 SpamAssassin −7.31 on 2026-05-20), but a correct client doesn't trip filters with bad MIME structure, broken threading, or missing headers.
|
||||||
|
|
||||||
## Tools (v0.1)
|
## Tools
|
||||||
|
|
||||||
- `mail_send` — send mail. Args: `account?`, `to`, `cc[]?`, `bcc[]?`, `subject`, `body`, `body_html?`, `attachments[]?`, `in_reply_to?`, `references[]?`. Returns `{message_id, sent_at}`.
|
- `mail_send` — `account?`, `to`, `cc[]?`, `bcc[]?`, `subject`, `body`, `body_html?`, `attachments[]?`, `in_reply_to?`, `references[]?`. Returns `{message_id, sent_at}`.
|
||||||
- `mail_inbox_list` — list folder messages newest-first. Args: `account?`, `since?` (YYYY-MM-DD), `unread_only?`, `limit?` (default 50, max 500), `folder?` (default INBOX). Uses `BODY.PEEK` so it does not toggle `\Seen`.
|
- `mail_inbox_list` — newest-first listing. `account?`, `since?` (YYYY-MM-DD), `unread_only?`, `limit?` (default 50, max 500), `folder?` (default INBOX). Uses `BODY.PEEK` so it does not toggle `\Seen`.
|
||||||
- `mail_inbox_read` — fetch one message by UID. Args: `account?`, `uid`, `folder?`, `format?` (`text`|`html`|`raw_eml`). Attachment payloads are not inlined — only filename/mime_type/size metadata.
|
- `mail_inbox_read` — fetch one message by UID. `account?`, `uid`, `folder?`, `format?` (`text`|`html`|`raw_eml`). Attachment payloads NOT inlined — only `{filename, mime_type, size}` metadata. RFC822.SIZE pre-flight rejects messages > 20 MB.
|
||||||
|
- `mail_folder_list` — `account?`. Enumerates IMAP mailboxes. Returns `[{name, delimiter, attributes, selectable}]`.
|
||||||
|
- `mail_search` — raw IMAP SEARCH passthrough. `account?`, `query`, `folder?`, `limit?`. CR/LF + `{N}` literal-form rejected.
|
||||||
|
- `mail_thread` — `account?`, `message_id`, `folder?`, `limit?`. Seed Message-ID → matches seed + any message whose `References` / `In-Reply-To` contains the seed. Oldest-first.
|
||||||
|
- `mail_move` — `account?`, `uid`, `from_folder?`, `to_folder`. UID MOVE (RFC 6851) with COPY + STORE + EXPUNGE fallback.
|
||||||
|
- `mail_mark` — `account?`, `uid`, `action` (`read`/`unread`/`flagged`/`unflagged`/`trash`/`archive`), `folder?`. Toggles `\Seen` / `\Flagged` via UID STORE; `trash` MOVEs to Trash; `archive` errors (no canonical Archive folder on Sulkta Dovecot — use `mail_move`).
|
||||||
|
- `mail_attachment_get` — `account?`, `uid`, `attachment_index`, `folder?`. Fetches the N-th attachment (index matches `mail_inbox_read.attachments[]`) as base64.
|
||||||
|
- `mail_reply` — `account?`, `uid`, `body`, `body_html?`, `attachments?`, `reply_all?`, `to_override?`, `folder?`. Pulls original to build `In-Reply-To` + `References` + `Re: ` subject prefix.
|
||||||
|
|
||||||
## Headers we guarantee on outbound
|
## Headers we guarantee on outbound
|
||||||
|
|
||||||
|
|
@ -20,44 +27,57 @@ Replaces the `scripts/kayos_mail.py` CLI path that lived in `kayos/openclaw-work
|
||||||
- `Message-ID` — `<UUIDv4@<from_addr_domain>>` — own-domain, never the container hostname
|
- `Message-ID` — `<UUIDv4@<from_addr_domain>>` — own-domain, never the container hostname
|
||||||
- `From` — `name <addr>`
|
- `From` — `name <addr>`
|
||||||
- `MIME-Version: 1.0`
|
- `MIME-Version: 1.0`
|
||||||
- `User-Agent: mail-mcp/<version>`
|
- `User-Agent: carrier/<version>`
|
||||||
- `In-Reply-To` + `References` when threading args present
|
- `In-Reply-To` + `References` when threading args present
|
||||||
- `Content-Type` correct for the body shape (text-only / alternative / mixed)
|
- `Content-Type` correct for body shape (text-only / alternative / mixed)
|
||||||
|
|
||||||
DKIM-Signature is applied by the relay (rspamd on Rackham), not the client.
|
DKIM-Signature is applied by the relay (rspamd on Rackham), not the client.
|
||||||
|
|
||||||
|
## Safety
|
||||||
|
|
||||||
|
`mail_inbox_read` returns attacker-controlled bytes. **Do NOT auto-fetch URLs found in inbound mail** — web beacons confirm read and links may be phishing. Default deny on every URL; wait for explicit per-link authorization. Authorized fetches route through Browserless (`192.168.0.5:3030` direct or `:3031` PIA-routed exit), never `WebFetch` or `curl` from the host. `mail_attachment_get` bytes get the same treatment — don't execute, render, or open them blindly.
|
||||||
|
|
||||||
|
The Carrier `ServerHandler.instructions` payload and `mail_inbox_read` description both surface this rule so any MCP introspection picks it up before reading a message.
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo build --release
|
cargo build --release
|
||||||
```
|
```
|
||||||
|
|
||||||
Binary lands at `target/release/mail-mcp`.
|
Binary lands at `target/release/carrier`.
|
||||||
|
|
||||||
## Config
|
## Config
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mkdir -p ~/.config/mail-mcp
|
mkdir -p ~/.config/carrier
|
||||||
cp config.example.toml ~/.config/mail-mcp/config.toml
|
cp config.example.toml ~/.config/carrier/config.toml
|
||||||
chmod 600 ~/.config/mail-mcp/config.toml
|
chmod 600 ~/.config/carrier/config.toml
|
||||||
```
|
```
|
||||||
|
|
||||||
Edit accounts as needed. Passwords are NEVER inline:
|
Or override with `CARRIER_CONFIG=/path/to/config.toml`.
|
||||||
|
|
||||||
1. Looked up from the env var named in `password_env`
|
Passwords are NEVER inline:
|
||||||
2. Falling back to `password_file` (shell-format: `KEY=VALUE` per line)
|
1. env var named in `password_env`
|
||||||
3. Hard-failing with a vault-pointer hint if neither resolves
|
2. fallback to `password_file` (shell-format: `KEY=VALUE` per line)
|
||||||
|
3. hard fail with a vault-pointer hint if neither resolves
|
||||||
|
|
||||||
Vault canonical: `bw.sulkta.com` → `kayos@sulkta.com — IMAP/SMTP`.
|
Vault canonical: `bw.sulkta.com` → `kayos@sulkta.com — IMAP/SMTP`.
|
||||||
|
|
||||||
|
Config file must be `mode & 0o077 == 0` (0600 strict). Carrier refuses to start on loose perms — same posture as ssh-keygen on a private key.
|
||||||
|
|
||||||
## MCP wiring (Claude Code / kayos-house)
|
## MCP wiring (Claude Code / kayos-house)
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"mcpServers": {
|
"mcpServers": {
|
||||||
"mail-mcp": {
|
"carrier": {
|
||||||
"command": "/usr/local/bin/mail-mcp",
|
"command": "/usr/local/bin/carrier",
|
||||||
"args": []
|
"args": [],
|
||||||
|
"env": {
|
||||||
|
"CARRIER_CONFIG": "/root/.openclaw/secrets/carrier-config.toml",
|
||||||
|
"KAYOS_SMTP_PASS": "<from vault: kayos@sulkta.com — IMAP/SMTP>"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -65,9 +85,10 @@ Vault canonical: `bw.sulkta.com` → `kayos@sulkta.com — IMAP/SMTP`.
|
||||||
|
|
||||||
Logging is stderr-only — stdout is the JSON-RPC transport.
|
Logging is stderr-only — stdout is the JSON-RPC transport.
|
||||||
|
|
||||||
## Future phases
|
## Deferred / future
|
||||||
|
|
||||||
- **Phase B** (~200 LOC): multi-account routing across all configured `[accounts.*]`, plus `mail_thread` and `mail_search`.
|
- **Session pool** — single TCP+TLS+LOGIN reused across same-account tool calls (~200ms saved per call). Substantial state-management work; deferred until usage patterns justify.
|
||||||
- **Phase C** (~150 LOC): `mail_mark` (read/unread/flag/trash/archive), `mail_attachment_get`, `mail_reply` helper.
|
- **BODYSTRUCTURE-driven partial body fetch** — fetch only the text/html leaf for `mail_inbox_read text/html` instead of the full RFC822. The 20 MB raw_eml cap already prevents OOM; remaining win is bandwidth on large messages.
|
||||||
|
- **Typed address shape** — `mail_inbox_list` / `_read` currently return `Vec<String>` for `from`/`to`/`cc` in `Name <addr>` shape. A `Vec<{name, addr}>` would let `mail_reply` skip the parse/re-parse dance.
|
||||||
|
|
||||||
Full locked spec: `kayos/openclaw-workspace` → `memory/spec-mail-mcp.md`.
|
Spec at `kayos/openclaw-workspace` → `memory/spec-carrier.md`.
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# mail-mcp config — copy to ~/.config/mail-mcp/config.toml, chmod 600.
|
# Carrier config — copy to ~/.config/carrier/config.toml, chmod 600.
|
||||||
#
|
#
|
||||||
# Passwords are NEVER inline. Each account names an env var (`password_env`)
|
# Passwords are NEVER inline. Each account names an env var (`password_env`)
|
||||||
# AND a fallback file (`password_file`). Lookup order:
|
# AND a fallback file (`password_file`). Lookup order:
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
# mail-mcp — the binary. Stdio MCP server exposing SMTP send + IMAP
|
# Carrier — the binary. Stdio MCP server exposing SMTP send + IMAP
|
||||||
# read tools. Spawned per-session by any MCP client.
|
# read tools. Spawned per-session by any MCP client.
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "mail-mcp"
|
name = "carrier"
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
repository.workspace = true
|
repository.workspace = true
|
||||||
authors.workspace = true
|
authors.workspace = true
|
||||||
description = "Rust MCP server for Sulkta-hosted email (SMTP send + IMAP read)"
|
description = "Carrier — Rust MCP server for Sulkta-hosted email (SMTP send + IMAP read)"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "mail-mcp"
|
name = "carrier"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
//! TOML config + password resolution.
|
//! TOML config + password resolution.
|
||||||
//!
|
//!
|
||||||
//! Config path: `$MAIL_MCP_CONFIG` env, or `~/.config/mail-mcp/config.toml`.
|
//! Config path: `$CARRIER_CONFIG` env, or `~/.config/carrier/config.toml`.
|
||||||
//!
|
//!
|
||||||
//! Password lookup per account:
|
//! Password lookup per account:
|
||||||
//! 1. env var named by `password_env`
|
//! 1. env var named by `password_env`
|
||||||
|
|
@ -160,12 +160,12 @@ fn check_chmod(_path: &std::path::Path) -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn config_path() -> Result<PathBuf> {
|
fn config_path() -> Result<PathBuf> {
|
||||||
if let Ok(p) = std::env::var("MAIL_MCP_CONFIG") {
|
if let Ok(p) = std::env::var("CARRIER_CONFIG") {
|
||||||
return Ok(PathBuf::from(shellexpand::tilde(&p).into_owned()));
|
return Ok(PathBuf::from(shellexpand::tilde(&p).into_owned()));
|
||||||
}
|
}
|
||||||
let home = dirs::config_dir()
|
let home = dirs::config_dir()
|
||||||
.ok_or_else(|| anyhow!("could not resolve $XDG_CONFIG_HOME / ~/.config"))?;
|
.ok_or_else(|| anyhow!("could not resolve $XDG_CONFIG_HOME / ~/.config"))?;
|
||||||
Ok(home.join("mail-mcp").join("config.toml"))
|
Ok(home.join("carrier").join("config.toml"))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
@ -1,8 +1,14 @@
|
||||||
//! mail-mcp — MCP server entry point.
|
//! Carrier — Sulkta's Rust MCP email server.
|
||||||
//!
|
//!
|
||||||
//! Speaks MCP over stdio. Any MCP client (Claude Code, OpenClaw,
|
//! Speaks MCP over stdio. Any MCP client (Claude Code, OpenClaw,
|
||||||
//! kayos-house's bundled claude binary) launches this as a subprocess
|
//! kayos-house's bundled claude binary) launches this as a subprocess
|
||||||
//! and gets `mail_send` + `mail_inbox_list` + `mail_inbox_read`.
|
//! and gets the 10-tool mail surface (mail_send / mail_inbox_list /
|
||||||
|
//! mail_inbox_read / mail_folder_list / mail_search / mail_thread /
|
||||||
|
//! mail_move / mail_mark / mail_attachment_get / mail_reply).
|
||||||
|
//!
|
||||||
|
//! Named after the carrier pigeon — single-purpose, reliable, comes
|
||||||
|
//! back every time. Tool names stay `mail_*` because they describe
|
||||||
|
//! the domain; only the server identity is Carrier.
|
||||||
//!
|
//!
|
||||||
//! Logging is stderr-only — stdout belongs to the JSON-RPC transport.
|
//! Logging is stderr-only — stdout belongs to the JSON-RPC transport.
|
||||||
|
|
||||||
|
|
@ -50,7 +56,7 @@ async fn run() -> Result<()> {
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
accounts = cfg.accounts.len(),
|
accounts = cfg.accounts.len(),
|
||||||
default_account = %cfg.default_account,
|
default_account = %cfg.default_account,
|
||||||
"mail-mcp starting"
|
"carrier starting"
|
||||||
);
|
);
|
||||||
|
|
||||||
let service = MailService::new(cfg);
|
let service = MailService::new(cfg);
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
//! - `Message-ID` — `<uuid-v4@{message_id_domain}>` — own-domain, not local hostname
|
//! - `Message-ID` — `<uuid-v4@{message_id_domain}>` — own-domain, not local hostname
|
||||||
//! - `From` — `name <addr>`
|
//! - `From` — `name <addr>`
|
||||||
//! - `MIME-Version` — lettre auto
|
//! - `MIME-Version` — lettre auto
|
||||||
//! - `User-Agent` — `mail-mcp/<crate version>`
|
//! - `User-Agent` — `carrier/<crate version>`
|
||||||
//! - `In-Reply-To` — if provided
|
//! - `In-Reply-To` — if provided
|
||||||
//! - `References` — if provided (space-joined)
|
//! - `References` — if provided (space-joined)
|
||||||
//!
|
//!
|
||||||
|
|
@ -52,7 +52,7 @@ pub struct SendOutput {
|
||||||
pub sent_at: String, // RFC-3339
|
pub sent_at: String, // RFC-3339
|
||||||
}
|
}
|
||||||
|
|
||||||
const USER_AGENT: &str = concat!("mail-mcp/", env!("CARGO_PKG_VERSION"));
|
const USER_AGENT: &str = concat!("carrier/", env!("CARGO_PKG_VERSION"));
|
||||||
|
|
||||||
/// Hard caps to keep an MCP-driver-gone-wrong from OOM-ing the box.
|
/// Hard caps to keep an MCP-driver-gone-wrong from OOM-ing the box.
|
||||||
/// Match Gmail's effective 25 MB per-message ceiling — any single attachment
|
/// Match Gmail's effective 25 MB per-message ceiling — any single attachment
|
||||||
|
|
@ -601,7 +601,7 @@ impl ServerHandler for MailService {
|
||||||
ServerInfo {
|
ServerInfo {
|
||||||
capabilities: ServerCapabilities::builder().enable_tools().build(),
|
capabilities: ServerCapabilities::builder().enable_tools().build(),
|
||||||
instructions: Some(
|
instructions: Some(
|
||||||
"mail-mcp — Rust MCP server for Sulkta-hosted email. Tools: \
|
"Carrier — Sulkta's Rust MCP email server. Tools: \
|
||||||
mail_send, mail_inbox_list, mail_inbox_read, mail_folder_list, \
|
mail_send, mail_inbox_list, mail_inbox_read, mail_folder_list, \
|
||||||
mail_search, mail_thread, mail_move, mail_mark, \
|
mail_search, mail_thread, mail_move, mail_mark, \
|
||||||
mail_attachment_get, mail_reply. Default account from config; \
|
mail_attachment_get, mail_reply. Default account from config; \
|
||||||
Loading…
Add table
Add a link
Reference in a new issue