hara-gmail-mcp: add mark_read and archive tools (v0.2.0)
Adds two write-capable IMAP tools: - gmail_mark_read: sets \Seen flag on a message - gmail_archive: copies to [Gmail]/All Mail and removes from INBOX The IMAP connection already used SELECT (read-write mode); this just exposes the mutation surface through MCP. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
8056e510c5
commit
4d2e40455d
4 changed files with 65 additions and 6 deletions
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
python3Packages.buildPythonApplication {
|
||||
pname = "hara-gmail-mcp";
|
||||
version = "0.1.0";
|
||||
version = "0.2.0";
|
||||
pyproject = true;
|
||||
src = ./.;
|
||||
nativeBuildInputs = [ python3Packages.setuptools ];
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||
|
||||
[project]
|
||||
name = "hara-gmail-mcp"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
description = "Gmail MCP server for Hara (IMAP+SMTP, throwaway pre-OAuth2)"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
|
|
|
|||
|
|
@ -153,6 +153,34 @@ def read_email(
|
|||
)
|
||||
|
||||
|
||||
def mark_read(
|
||||
store: AccountStore,
|
||||
email_addr: str,
|
||||
uid: str,
|
||||
mailbox: str = "INBOX",
|
||||
) -> None:
|
||||
"""Mark a message as read by adding the \\Seen flag."""
|
||||
account = store.get(email_addr)
|
||||
password = store.password_for(email_addr)
|
||||
with _open(account, password, mailbox) as conn:
|
||||
conn.uid("store", uid, "+FLAGS", r"(\Seen)")
|
||||
|
||||
|
||||
def archive(
|
||||
store: AccountStore,
|
||||
email_addr: str,
|
||||
uid: str,
|
||||
mailbox: str = "INBOX",
|
||||
) -> None:
|
||||
"""Archive a message: copy to All Mail then delete from INBOX."""
|
||||
account = store.get(email_addr)
|
||||
password = store.password_for(email_addr)
|
||||
with _open(account, password, mailbox) as conn:
|
||||
conn.uid("copy", uid, "[Gmail]/All Mail")
|
||||
conn.uid("store", uid, "+FLAGS", r"(\Deleted)")
|
||||
conn.expunge()
|
||||
|
||||
|
||||
def _fetch_summary(conn: imaplib.IMAP4_SSL, uid: str) -> MessageSummary:
|
||||
typ, data = conn.uid(
|
||||
"fetch",
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
"""Hara Gmail MCP server.
|
||||
|
||||
Exposes a small toolset for reading and (later) replying to mail across
|
||||
the configured Gmail accounts. v1 ships read-only tools; reply/archive/label
|
||||
follow once Hara is using these reliably.
|
||||
Exposes a small toolset for reading and writing mail across the configured
|
||||
Gmail accounts.
|
||||
|
||||
Tools:
|
||||
list_accounts() list configured accounts
|
||||
list_inbox(email, limit) recent messages from an account
|
||||
search(email, query, limit) IMAP SEARCH wrapper
|
||||
read_email(email, uid) full body of one message
|
||||
mark_read(email, uid) mark a message as read
|
||||
archive(email, uid) archive a message (remove from INBOX)
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
|
|
@ -21,7 +22,7 @@ from dataclasses import asdict
|
|||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
from .accounts import AccountStore
|
||||
from .imap_client import list_inbox, read_email, search
|
||||
from .imap_client import archive, list_inbox, mark_read, read_email, search
|
||||
|
||||
logger = logging.getLogger("hara_gmail_mcp")
|
||||
|
||||
|
|
@ -92,6 +93,36 @@ def gmail_read_email(email: str, uid: str) -> str:
|
|||
return json.dumps(asdict(msg), ensure_ascii=False)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def gmail_mark_read(email: str, uid: str) -> str:
|
||||
"""Mark a message as read (sets the \\Seen flag).
|
||||
|
||||
Args:
|
||||
email: which configured account
|
||||
uid: the message UID (returned by gmail_list_inbox or gmail_search)
|
||||
|
||||
Returns:
|
||||
JSON object with ok and uid.
|
||||
"""
|
||||
mark_read(_get_store(), email, uid=uid)
|
||||
return json.dumps({"ok": True, "uid": uid})
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def gmail_archive(email: str, uid: str) -> str:
|
||||
"""Archive a message (copies to All Mail, removes from INBOX).
|
||||
|
||||
Args:
|
||||
email: which configured account
|
||||
uid: the message UID (returned by gmail_list_inbox or gmail_search)
|
||||
|
||||
Returns:
|
||||
JSON object with ok and uid.
|
||||
"""
|
||||
archive(_get_store(), email, uid=uid)
|
||||
return json.dumps({"ok": True, "uid": uid})
|
||||
|
||||
|
||||
def main() -> None:
|
||||
logging.basicConfig(
|
||||
level=os.environ.get("HARA_GMAIL_LOG_LEVEL", "INFO"),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue