Type Safety Improvements: - Change dict → dict[str, Any] throughout codebase for explicit typing - Add PEP 561 py.typed marker file to export type information - Add types-requests to dev dependencies - Improve signal handler typing (FrameType | None) - Improve decorator typing (Callable[..., Awaitable[T]]) - Add quickstart() abstract method to BdClientBase for interface completeness Bug Fixes: - Fix variable shadowing: beads_dir → local_beads_dir in bd_client.py - Improve error handling in mail.py:_call_agent_mail() to prevent undefined error - Make working_dir required (not Optional) in BdDaemonClient - Remove unnecessary 'or' defaults for required Pydantic fields Validation: - mypy passes with no errors - All unit tests passing - Daemon quickstart returns helpful static text (RPC doesn't support this command)
212 lines
5.6 KiB
Python
212 lines
5.6 KiB
Python
"""MCP tools for Agent Mail messaging."""
|
|
|
|
import logging
|
|
from typing import Annotated, Any
|
|
|
|
from .mail import (
|
|
MailError,
|
|
mail_ack,
|
|
mail_delete,
|
|
mail_inbox,
|
|
mail_read,
|
|
mail_reply,
|
|
mail_send,
|
|
)
|
|
from .models import (
|
|
MailAckParams,
|
|
MailDeleteParams,
|
|
MailInboxParams,
|
|
MailReadParams,
|
|
MailReplyParams,
|
|
MailSendParams,
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def beads_mail_send(params: MailSendParams) -> dict[str, Any]:
|
|
"""Send a message to other agents via Agent Mail.
|
|
|
|
Requires BEADS_AGENT_MAIL_URL and BEADS_AGENT_NAME environment variables.
|
|
Auto-detects project from workspace root.
|
|
|
|
Example:
|
|
mail_send(to=["alice"], subject="Review PR", body="Can you review PR #42?")
|
|
|
|
Args:
|
|
params: Message parameters (to, subject, body, etc.)
|
|
|
|
Returns:
|
|
{message_id: int, thread_id: str, sent_to: int}
|
|
|
|
Raises:
|
|
MailError: On configuration or delivery error
|
|
"""
|
|
try:
|
|
return mail_send(
|
|
to=params.to,
|
|
subject=params.subject,
|
|
body=params.body,
|
|
urgent=params.urgent,
|
|
cc=params.cc,
|
|
project_key=params.project_key,
|
|
sender_name=params.sender_name,
|
|
)
|
|
except MailError as e:
|
|
logger.error(f"mail_send failed: {e.message}")
|
|
return {"error": e.code, "message": e.message, "data": e.data}
|
|
|
|
|
|
def beads_mail_inbox(
|
|
params: Annotated[MailInboxParams, "Parameters"] = MailInboxParams(),
|
|
) -> dict[str, Any]:
|
|
"""Get messages from Agent Mail inbox.
|
|
|
|
Requires BEADS_AGENT_MAIL_URL and BEADS_AGENT_NAME environment variables.
|
|
|
|
Example:
|
|
mail_inbox(limit=10, unread_only=True)
|
|
|
|
Args:
|
|
params: Inbox filter parameters
|
|
|
|
Returns:
|
|
{
|
|
messages: [{id, thread_id, from, subject, created_ts, unread, ack_required, urgent, preview}, ...],
|
|
next_cursor: str | None
|
|
}
|
|
|
|
Raises:
|
|
MailError: On configuration or fetch error
|
|
"""
|
|
try:
|
|
return mail_inbox(
|
|
limit=params.limit,
|
|
urgent_only=params.urgent_only,
|
|
unread_only=params.unread_only,
|
|
cursor=params.cursor,
|
|
agent_name=params.agent_name,
|
|
project_key=params.project_key,
|
|
)
|
|
except MailError as e:
|
|
logger.error(f"mail_inbox failed: {e.message}")
|
|
return {"error": e.code, "message": e.message, "data": e.data}
|
|
|
|
|
|
def beads_mail_read(params: MailReadParams) -> dict[str, Any]:
|
|
"""Read full message with body from Agent Mail.
|
|
|
|
By default, marks the message as read. Set mark_read=False to preview without marking.
|
|
|
|
Example:
|
|
mail_read(message_id=123)
|
|
|
|
Args:
|
|
params: Read parameters (message_id, mark_read)
|
|
|
|
Returns:
|
|
{
|
|
id, thread_id, from, to, subject, body,
|
|
created_ts, ack_required, ack_status, read_ts, urgent
|
|
}
|
|
|
|
Raises:
|
|
MailError: On configuration or read error
|
|
"""
|
|
try:
|
|
return mail_read(
|
|
message_id=params.message_id,
|
|
mark_read=params.mark_read,
|
|
agent_name=params.agent_name,
|
|
project_key=params.project_key,
|
|
)
|
|
except MailError as e:
|
|
logger.error(f"mail_read failed: {e.message}")
|
|
return {"error": e.code, "message": e.message, "data": e.data}
|
|
|
|
|
|
def beads_mail_reply(params: MailReplyParams) -> dict[str, Any]:
|
|
"""Reply to a message (preserves thread).
|
|
|
|
Automatically inherits thread_id from the original message.
|
|
|
|
Example:
|
|
mail_reply(message_id=123, body="Thanks, will review today!")
|
|
|
|
Args:
|
|
params: Reply parameters (message_id, body, subject)
|
|
|
|
Returns:
|
|
{message_id: int, thread_id: str}
|
|
|
|
Raises:
|
|
MailError: On configuration or reply error
|
|
"""
|
|
try:
|
|
return mail_reply(
|
|
message_id=params.message_id,
|
|
body=params.body,
|
|
subject=params.subject,
|
|
agent_name=params.agent_name,
|
|
project_key=params.project_key,
|
|
)
|
|
except MailError as e:
|
|
logger.error(f"mail_reply failed: {e.message}")
|
|
return {"error": e.code, "message": e.message, "data": e.data}
|
|
|
|
|
|
def beads_mail_ack(params: MailAckParams) -> dict[str, Any]:
|
|
"""Acknowledge a message (for ack_required messages).
|
|
|
|
Safe to call even if message doesn't require acknowledgement.
|
|
|
|
Example:
|
|
mail_ack(message_id=123)
|
|
|
|
Args:
|
|
params: Acknowledgement parameters (message_id)
|
|
|
|
Returns:
|
|
{acknowledged: True}
|
|
|
|
Raises:
|
|
MailError: On configuration or ack error
|
|
"""
|
|
try:
|
|
return mail_ack(
|
|
message_id=params.message_id,
|
|
agent_name=params.agent_name,
|
|
project_key=params.project_key,
|
|
)
|
|
except MailError as e:
|
|
logger.error(f"mail_ack failed: {e.message}")
|
|
return {"error": e.code, "acknowledged": False, "message": e.message}
|
|
|
|
|
|
def beads_mail_delete(params: MailDeleteParams) -> dict[str, Any]:
|
|
"""Delete (archive) a message from Agent Mail inbox.
|
|
|
|
Note: Agent Mail archives messages rather than permanently deleting them.
|
|
|
|
Example:
|
|
mail_delete(message_id=123)
|
|
|
|
Args:
|
|
params: Delete parameters (message_id)
|
|
|
|
Returns:
|
|
{deleted: True} or {archived: True}
|
|
|
|
Raises:
|
|
MailError: On configuration or delete error
|
|
"""
|
|
try:
|
|
return mail_delete(
|
|
message_id=params.message_id,
|
|
agent_name=params.agent_name,
|
|
project_key=params.project_key,
|
|
)
|
|
except MailError as e:
|
|
logger.error(f"mail_delete failed: {e.message}")
|
|
return {"error": e.code, "deleted": False, "message": e.message}
|