mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-21 04:05:26 -04:00
fix(email): enforce MCP owner boundaries (#4335)
* fix(email): enforce MCP owner boundaries * fix(email): fail closed for unowned MCP fallback
This commit is contained in:
+26
-5
@@ -323,6 +323,24 @@ _MCP_TOOL_MAP = {
|
||||
"web_fetch": ("web_fetch", "web_fetch"),
|
||||
"generate_image": ("image_gen", "generate_image"),
|
||||
}
|
||||
_EMAIL_MCP_OWNER_ARG = "_odysseus_owner"
|
||||
|
||||
|
||||
def _parse_qualified_mcp_args(tool: str, content: str) -> tuple[Dict, Optional[str]]:
|
||||
raw = (content or "").strip()
|
||||
if not raw:
|
||||
return {}, None
|
||||
try:
|
||||
parsed = json.loads(raw)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
if tool.startswith("mcp__email__"):
|
||||
return {}, "Email MCP tool arguments must be a JSON object."
|
||||
return {}, None
|
||||
if not isinstance(parsed, dict):
|
||||
if tool.startswith("mcp__email__"):
|
||||
return {}, "Email MCP tool arguments must be a JSON object."
|
||||
return {}, None
|
||||
return parsed, None
|
||||
|
||||
|
||||
def _parse_generate_image(content: str) -> Dict:
|
||||
@@ -858,12 +876,15 @@ async def _execute_tool_block_impl(
|
||||
# MCP tool dispatch
|
||||
mcp = get_mcp_manager()
|
||||
if mcp:
|
||||
try:
|
||||
args = json.loads(content) if content.strip().startswith("{") else {}
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
args = {}
|
||||
desc = f"mcp: {tool}"
|
||||
result = await mcp.call_tool(tool, args)
|
||||
args, parse_error = _parse_qualified_mcp_args(tool, content)
|
||||
if parse_error:
|
||||
result = {"error": parse_error, "exit_code": 1}
|
||||
else:
|
||||
if tool.startswith("mcp__email__") and owner:
|
||||
args = dict(args)
|
||||
args[_EMAIL_MCP_OWNER_ARG] = owner
|
||||
result = await mcp.call_tool(tool, args)
|
||||
else:
|
||||
desc = f"mcp: {tool}"
|
||||
result = {"error": "MCP manager not available", "exit_code": 1}
|
||||
|
||||
+10
-7
@@ -1206,23 +1206,26 @@ def function_call_to_tool_block(name: str, arguments: str) -> Optional[ToolBlock
|
||||
logger.error(f"Failed to parse function call arguments for {name}: {arguments}")
|
||||
return None
|
||||
|
||||
tool_type = _TOOL_NAME_MAP.get(name, name)
|
||||
_BUILTIN_EMAIL_TOOLS = {"list_email_accounts", "send_email", "list_emails", "read_email", "reply_to_email",
|
||||
"archive_email", "delete_email", "mark_email_read", "bulk_email", "download_attachment"}
|
||||
|
||||
# Some models emit valid JSON that isn't an object (e.g. a bare array
|
||||
# ["ls -la"], string, or number) as the function arguments. Every branch
|
||||
# below assumes a dict and calls args.get(...), so a non-dict would raise
|
||||
# AttributeError and abort the whole agent stream. Coerce to {} instead.
|
||||
# ["ls -la"], string, or number) as function arguments. Most local tools keep
|
||||
# the legacy empty-object coercion for stream robustness, but email MCP tools
|
||||
# must fail closed so a malformed call cannot read the default mailbox.
|
||||
if not isinstance(args, dict):
|
||||
if tool_type.startswith("mcp__email__") or name in _BUILTIN_EMAIL_TOOLS:
|
||||
logger.warning(f"Non-object email function call arguments for {name}: {args!r}; rejecting")
|
||||
return None
|
||||
logger.warning(f"Non-object function call arguments for {name}: {args!r}; treating as empty")
|
||||
args = {}
|
||||
|
||||
tool_type = _TOOL_NAME_MAP.get(name, name)
|
||||
|
||||
# Allow MCP tools through (namespaced as mcp__serverid__toolname)
|
||||
if tool_type.startswith("mcp__"):
|
||||
content = json.dumps(args) if args else "{}"
|
||||
return ToolBlock(tool_type, content)
|
||||
# Email tools are implemented as MCP — route them to email
|
||||
_BUILTIN_EMAIL_TOOLS = {"list_email_accounts", "send_email", "list_emails", "read_email", "reply_to_email",
|
||||
"archive_email", "delete_email", "mark_email_read", "bulk_email", "download_attachment"}
|
||||
if name in _BUILTIN_EMAIL_TOOLS:
|
||||
return ToolBlock(f"mcp__email__{name}", json.dumps(args) if args else "{}")
|
||||
if tool_type not in TOOL_TAGS:
|
||||
|
||||
Reference in New Issue
Block a user