Skip to content

Conversation

@DixonDs
Copy link
Contributor

@DixonDs DixonDs commented Dec 30, 2025

Feature: Read-Only Mode

Summary

This PR introduces a --read-only command-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

  • CLI Arg: Added --read-only flag to main.py.
  • Scope Management: Implemented TOOL_READONLY_SCOPES_MAP to strictly limit requested scopes to their read-only variants (e.g., gmail.readonly) when active.
  • Tool Filtering: Added logic to automatically inspect tool metadata and unregister any tool requiring write permissions at startup.

Usage

Run the server with the flag and your desired tool tier:

uv run main.py --tool-tier core --read-only

Or combine specific tools with a tier:

uv run main.py --tools gmail drive --tool-tier extended --read-only

- 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
@taylorwilsdon taylorwilsdon requested a review from Copilot January 1, 2026 16:13
@taylorwilsdon taylorwilsdon self-assigned this Jan 1, 2026
@taylorwilsdon taylorwilsdon added the enhancement New feature or request label Jan 1, 2026
Copy link
Contributor

Copilot AI left a 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-only CLI flag and mode indicator in startup output
  • Created TOOL_READONLY_SCOPES_MAP to 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.

Comment on lines +164 to +169
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)
Copy link

Copilot AI Jan 1, 2026

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.

Copilot uses AI. Check for mistakes.
Comment on lines +112 to 122

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)

Copy link

Copilot AI Jan 1, 2026

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.

Suggested change
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)

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants