-
Notifications
You must be signed in to change notification settings - Fork 339
feat: implement --read-only mode with tool filtering #334
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
- Adds --read-only CLI flag to restrict OAuth scopes to read-only permissions - Implements dynamic tool filtering to disable tools requiring write permissions when in read-only mode - Updates auth/scopes.py to manage read-only scope mappings - Enhances @require_google_service and handle_http_errors decorators to propagate scope metadata - Updates documentation in README.md
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR implements a read-only mode feature that restricts the server to only use read-only OAuth scopes and automatically filters out tools requiring write permissions. The implementation adds a command-line flag (--read-only) that, when enabled, switches from full OAuth scopes to their read-only variants and prevents tools with write requirements from being registered.
Key changes:
- Added
--read-onlyCLI flag and mode indicator in startup output - Created
TOOL_READONLY_SCOPES_MAPto define read-only scope variants for each tool - Implemented tool filtering logic that checks required scopes and removes tools needing write permissions
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| main.py | Adds --read-only CLI argument and calls set_read_only() to enable the mode |
| auth/scopes.py | Introduces read-only scope mappings and mode state management with helper functions |
| core/tool_registry.py | Extends filtering logic to check tool scopes against allowed read-only scopes |
| auth/service_decorator.py | Attaches _required_google_scopes to decorated functions for filtering inspection |
| core/utils.py | Propagates scope metadata through wrapper functions |
| README.md | Documents the new --read-only flag usage |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def get_all_read_only_scopes() -> list[str]: | ||
| """Get all possible read-only scopes across all tools.""" | ||
| all_scopes = set(BASE_SCOPES) | ||
| for scopes in TOOL_READONLY_SCOPES_MAP.values(): | ||
| all_scopes.update(scopes) | ||
| return list(all_scopes) |
Copilot
AI
Jan 1, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function returns a list conversion of a set, which doesn't guarantee consistent ordering across calls. If scope ordering matters for OAuth or comparison operations elsewhere in the codebase, consider returning sorted(all_scopes) instead to ensure deterministic behavior.
|
|
||
| required_scopes = getattr(func_to_check, "_required_google_scopes", []) | ||
|
|
||
| if required_scopes: | ||
| # If ANY required scope is not in the allowed read-only scopes, disable the tool | ||
| if not all(scope in allowed_scopes for scope in required_scopes): | ||
| logger.info( | ||
| f"Read-only mode: Disabling tool '{tool_name}' (requires write scopes: {required_scopes})" | ||
| ) | ||
| tools_to_remove.append(tool_name) | ||
|
|
Copilot
AI
Jan 1, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The nested conditionals checking if required_scopes: and then if not all(...) can be simplified. Tools without required scopes (required_scopes is empty) will pass the check, but the logic would be clearer if this case was explicitly handled or the comment explained that tools without scope requirements are allowed in read-only mode.
| required_scopes = getattr(func_to_check, "_required_google_scopes", []) | |
| if required_scopes: | |
| # If ANY required scope is not in the allowed read-only scopes, disable the tool | |
| if not all(scope in allowed_scopes for scope in required_scopes): | |
| logger.info( | |
| f"Read-only mode: Disabling tool '{tool_name}' (requires write scopes: {required_scopes})" | |
| ) | |
| tools_to_remove.append(tool_name) | |
| required_scopes = getattr(func_to_check, "_required_google_scopes", []) | |
| if not required_scopes: | |
| # Tools without required scopes are allowed in read-only mode | |
| continue | |
| # If ANY required scope is not in the allowed read-only scopes, disable the tool | |
| if not all(scope in allowed_scopes for scope in required_scopes): | |
| logger.info( | |
| f"Read-only mode: Disabling tool '{tool_name}' (requires write scopes: {required_scopes})" | |
| ) | |
| tools_to_remove.append(tool_name) |
Feature: Read-Only Mode
Summary
This PR introduces a
--read-onlycommand-line flag that forces the server into a secure, read-only state. When enabled, this mode restricts OAuth scopes to their read-only variants and automatically filters out any tools that require write permissions, ensuring secure and restrictive operation.Key Changes
--read-onlyflag to main.py.TOOL_READONLY_SCOPES_MAPto strictly limit requested scopes to their read-only variants (e.g.,gmail.readonly) when active.Usage
Run the server with the flag and your desired tool tier:
Or combine specific tools with a tier: