From 66087d57974572f78693269c297a414ba35f76c1 Mon Sep 17 00:00:00 2001 From: albnnc Date: Mon, 6 Jul 2026 10:55:06 +0300 Subject: [PATCH] w --- .gitignore | 1 + owa_mcp/server.py | 4 +-- owa_mcp/server_lifecycle.py | 69 +++++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 9892a8f..68a06dc 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ __pycache__/ .mcpregistry_* .playwright-mcp/ .salt +.venv *.egg-info/ *.pyc build diff --git a/owa_mcp/server.py b/owa_mcp/server.py index 3807931..361f576 100644 --- a/owa_mcp/server.py +++ b/owa_mcp/server.py @@ -119,12 +119,12 @@ def _parse_args(argv=None) -> argparse.Namespace: parser.add_argument( "--enabled-tools", default=None, - help="Comma-separated whitelist of tool names to expose", + help="Comma-separated whitelist of tool names or group aliases (read-only, write-only) to expose", ) parser.add_argument( "--disabled-tools", default=None, - help="Comma-separated blacklist of tool names to hide", + help="Comma-separated blacklist of tool names or group aliases (read-only, write-only) to hide", ) return parser.parse_args(argv) diff --git a/owa_mcp/server_lifecycle.py b/owa_mcp/server_lifecycle.py index 154f8a0..8e84485 100644 --- a/owa_mcp/server_lifecycle.py +++ b/owa_mcp/server_lifecycle.py @@ -213,6 +213,59 @@ class SessionKeepalive: logger.debug("Keepalive ping failed (non-critical): %s", exc) +# ── Tool groups ───────────────────────────────────────────────────────── + +TOOL_GROUPS: dict[str, set[str]] = { + "read-only": { + "get_emails", + "get_email", + "search_emails", + "get_calendar_events", + "get_event_links", + "find_person", + "get_folders", + "check_session", + "find_free_time", + "find_meeting_time", + "get_meeting_stats", + "get_meeting_contacts", + "download_event_attachments", + "get_email_links", + "download_attachments", + "login", + }, + "write-only": { + "send_email", + "reply_email", + "forward_email", + "delete_email", + "move_email", + "mark_email_read", + "create_meeting", + "update_meeting", + "cancel_meeting", + "respond_to_meeting", + "create_folder", + "rename_folder", + "empty_folder", + "delete_folder", + "move_folder", + }, +} + +# ── Alias resolution ──────────────────────────────────────────────────── + + +def _resolve_aliases(names: list[str]) -> set[str]: + resolved: set[str] = set() + for name in names: + if name in TOOL_GROUPS: + resolved |= TOOL_GROUPS[name] + else: + resolved.add(name) + return resolved + + # ── Tool filtering ─────────────────────────────────────────────────────── @@ -236,8 +289,24 @@ def apply_tool_filters( if not canonical: return + # ── Group integrity check ─────────────────────────────────────────────── + all_grouped: set[str] = set().union(*TOOL_GROUPS.values()) + missing = canonical - all_grouped + assert not missing, f"Tools not in any group: {sorted(missing)}" + overlaps: set[str] = set() + for i, (g1, s1) in enumerate(TOOL_GROUPS.items()): + for g2, s2 in list(TOOL_GROUPS.items())[i + 1 :]: + overlaps |= s1 & s2 + assert not overlaps, f"Tools in multiple groups: {sorted(overlaps)}" + allowed: set[str] = set(canonical) + # ── Resolve aliases ──────────────────────────────────────────────────── + if enabled: + enabled = sorted(_resolve_aliases(enabled)) + if disabled: + disabled = sorted(_resolve_aliases(disabled)) + # ── Whitelist ────────────────────────────────────────────────────────── if enabled: enabled_set = {n.strip() for n in enabled if n.strip()}