Merge remote-tracking branch 'origin/master' into docs/adr-024-dynaconf-config-management
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
"""Migrate scope separator from colon to dot
|
||||
|
||||
Many identity providers reject ':' in OAuth scope names. This migration
|
||||
updates stored scope strings from the old 'resource:action' format to
|
||||
'resource.action' (e.g., 'notes:read' -> 'notes.read').
|
||||
|
||||
See ADR-023 for rationale.
|
||||
|
||||
Revision ID: 004
|
||||
Revises: 003
|
||||
Create Date: 2026-04-07 12:00:00.000000
|
||||
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "004"
|
||||
down_revision = "003"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Replace colon separator with dot in stored scope strings."""
|
||||
|
||||
# Update scopes in app_passwords (JSON array of scope strings)
|
||||
# Only ':' characters in a JSON array like '["notes:read","calendar:write"]'
|
||||
# are inside scope name strings, so REPLACE is safe here.
|
||||
op.execute(
|
||||
"""
|
||||
UPDATE app_passwords
|
||||
SET scopes = REPLACE(scopes, ':', '.')
|
||||
WHERE scopes IS NOT NULL
|
||||
"""
|
||||
)
|
||||
|
||||
# Update requested_scopes in login_flow_sessions
|
||||
op.execute(
|
||||
"""
|
||||
UPDATE login_flow_sessions
|
||||
SET requested_scopes = REPLACE(requested_scopes, ':', '.')
|
||||
WHERE requested_scopes IS NOT NULL
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Revert dot separator back to colon in stored scope strings."""
|
||||
|
||||
op.execute(
|
||||
"""
|
||||
UPDATE app_passwords
|
||||
SET scopes = REPLACE(scopes, '.', ':')
|
||||
WHERE scopes IS NOT NULL
|
||||
"""
|
||||
)
|
||||
|
||||
op.execute(
|
||||
"""
|
||||
UPDATE login_flow_sessions
|
||||
SET requested_scopes = REPLACE(requested_scopes, '.', ':')
|
||||
WHERE requested_scopes IS NOT NULL
|
||||
"""
|
||||
)
|
||||
@@ -462,19 +462,19 @@ async def load_oauth_client_credentials(
|
||||
# These must stay in sync — any scope a tool uses via @require_scopes must be listed here.
|
||||
dcr_scopes = (
|
||||
"openid profile email "
|
||||
"notes:read notes:write calendar:read calendar:write todo:read todo:write "
|
||||
"contacts:read contacts:write cookbook:read cookbook:write deck:read deck:write "
|
||||
"tables:read tables:write files:read files:write sharing:read sharing:write "
|
||||
"news:read news:write collectives:read collectives:write"
|
||||
"notes.read notes.write calendar.read calendar.write todo.read todo.write "
|
||||
"contacts.read contacts.write cookbook.read cookbook.write deck.read deck.write "
|
||||
"tables.read tables.write files.read files.write sharing.read sharing.write "
|
||||
"news.read news.write collectives.read collectives.write"
|
||||
)
|
||||
|
||||
# Add conditional scopes based on server configuration
|
||||
dcr_settings = get_settings()
|
||||
|
||||
# semantic:read gates MCP-server-level semantic search tools
|
||||
# semantic.read gates MCP-server-level semantic search tools
|
||||
if dcr_settings.vector_sync_enabled:
|
||||
dcr_scopes = f"{dcr_scopes} semantic:read"
|
||||
logger.info("✓ semantic:read scope enabled for semantic search tools")
|
||||
dcr_scopes = f"{dcr_scopes} semantic.read"
|
||||
logger.info("✓ semantic.read scope enabled for semantic search tools")
|
||||
|
||||
# offline_access enables refresh tokens for background operations
|
||||
enable_offline_access = dcr_settings.enable_offline_access
|
||||
|
||||
@@ -86,7 +86,7 @@ async def oauth_login(request: Request) -> RedirectResponse | JSONResponse:
|
||||
callback_uri = f"{mcp_server_url}/oauth/callback"
|
||||
|
||||
# Request only basic OIDC scopes for browser session
|
||||
# Note: Nextcloud app scopes (notes:read, etc.) are for MCP client access tokens,
|
||||
# Note: Nextcloud app scopes (notes.read, etc.) are for MCP client access tokens,
|
||||
# not for the MCP server's own browser authentication
|
||||
scopes = "openid profile email offline_access"
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ def require_scopes(*required_scopes: str):
|
||||
users who lack the necessary scopes.
|
||||
|
||||
Args:
|
||||
*required_scopes: Variable number of scope strings required (e.g., "notes:read", "notes:write")
|
||||
*required_scopes: Variable number of scope strings required (e.g., "notes.read", "notes.write")
|
||||
|
||||
Returns:
|
||||
Decorated function that checks scopes before execution
|
||||
@@ -82,15 +82,15 @@ def require_scopes(*required_scopes: str):
|
||||
Example:
|
||||
```python
|
||||
@mcp.tool()
|
||||
@require_scopes("notes:read")
|
||||
@require_scopes("notes.read")
|
||||
async def nc_notes_get_note(ctx: Context, note_id: int):
|
||||
# This tool requires the notes:read scope
|
||||
# This tool requires the notes.read scope
|
||||
...
|
||||
|
||||
@mcp.tool()
|
||||
@require_scopes("notes:write")
|
||||
@require_scopes("notes.write")
|
||||
async def nc_notes_create_note(ctx: Context, ...):
|
||||
# This tool requires the notes:write scope
|
||||
# This tool requires the notes.write scope
|
||||
...
|
||||
```
|
||||
|
||||
@@ -203,12 +203,12 @@ def require_scopes(*required_scopes: str):
|
||||
if any(
|
||||
s.startswith(prefix)
|
||||
for prefix in [
|
||||
"notes:",
|
||||
"calendar:",
|
||||
"contacts:",
|
||||
"files:",
|
||||
"tables:",
|
||||
"deck:",
|
||||
"notes.",
|
||||
"calendar.",
|
||||
"contacts.",
|
||||
"files.",
|
||||
"tables.",
|
||||
"deck.",
|
||||
]
|
||||
)
|
||||
]
|
||||
@@ -223,12 +223,12 @@ def require_scopes(*required_scopes: str):
|
||||
s.startswith(prefix)
|
||||
for s in token_scopes
|
||||
for prefix in [
|
||||
"notes:",
|
||||
"calendar:",
|
||||
"contacts:",
|
||||
"files:",
|
||||
"tables:",
|
||||
"deck:",
|
||||
"notes.",
|
||||
"calendar.",
|
||||
"contacts.",
|
||||
"files.",
|
||||
"tables.",
|
||||
"deck.",
|
||||
]
|
||||
)
|
||||
|
||||
@@ -305,7 +305,7 @@ def check_scopes(ctx: Context, *required_scopes: str) -> tuple[bool, set[str]]:
|
||||
Example:
|
||||
```python
|
||||
async def my_tool(ctx: Context):
|
||||
has_scopes, missing = check_scopes(ctx, "notes:read", "notes:write")
|
||||
has_scopes, missing = check_scopes(ctx, "notes.read", "notes.write")
|
||||
if not has_scopes:
|
||||
# Handle missing scopes
|
||||
...
|
||||
@@ -335,11 +335,11 @@ def get_required_scopes(func: Callable) -> list[str]:
|
||||
|
||||
Example:
|
||||
```python
|
||||
@require_scopes("notes:read", "notes:write")
|
||||
@require_scopes("notes.read", "notes.write")
|
||||
async def my_tool():
|
||||
pass
|
||||
|
||||
scopes = get_required_scopes(my_tool) # ["notes:read", "notes:write"]
|
||||
scopes = get_required_scopes(my_tool) # ["notes.read", "notes.write"]
|
||||
```
|
||||
"""
|
||||
return getattr(func, "_required_scopes", [])
|
||||
@@ -385,14 +385,14 @@ def has_required_scopes(func: Callable, user_scopes: set[str]) -> bool:
|
||||
|
||||
Example:
|
||||
```python
|
||||
@require_scopes("notes:write")
|
||||
@require_scopes("notes.write")
|
||||
async def create_note():
|
||||
pass
|
||||
|
||||
user_scopes = {"notes:read", "notes:write"}
|
||||
user_scopes = {"notes.read", "notes.write"}
|
||||
can_see = has_required_scopes(create_note, user_scopes) # True
|
||||
|
||||
limited_user_scopes = {"notes:read"}
|
||||
limited_user_scopes = {"notes.read"}
|
||||
can_see = has_required_scopes(create_note, limited_user_scopes) # False
|
||||
```
|
||||
"""
|
||||
@@ -431,17 +431,17 @@ def discover_all_scopes(mcp) -> list[str]:
|
||||
mcp = FastMCP("My Server")
|
||||
|
||||
@mcp.tool()
|
||||
@require_scopes("notes:read")
|
||||
@require_scopes("notes.read")
|
||||
async def get_notes():
|
||||
pass
|
||||
|
||||
@mcp.tool()
|
||||
@require_scopes("notes:write")
|
||||
@require_scopes("notes.write")
|
||||
async def create_note():
|
||||
pass
|
||||
|
||||
scopes = discover_all_scopes(mcp)
|
||||
# Returns: ["notes:read", "notes:write", "openid", "profile", "email"]
|
||||
# Returns: ["notes.read", "notes.write", "openid", "profile", "email"]
|
||||
```
|
||||
|
||||
Note:
|
||||
|
||||
@@ -337,7 +337,7 @@ class TokenBrokerService:
|
||||
data = {
|
||||
"grant_type": "refresh_token",
|
||||
"refresh_token": refresh_token,
|
||||
"scope": "openid profile email offline_access notes:read notes:write calendar:read calendar:write",
|
||||
"scope": "openid profile email offline_access notes.read notes.write calendar.read calendar.write",
|
||||
"client_id": self.client_id,
|
||||
"client_secret": self.client_secret,
|
||||
}
|
||||
@@ -521,7 +521,7 @@ class TokenBrokerService:
|
||||
data = {
|
||||
"grant_type": "refresh_token",
|
||||
"refresh_token": current_refresh_token,
|
||||
"scope": "openid profile email offline_access notes:read notes:write calendar:read calendar:write",
|
||||
"scope": "openid profile email offline_access notes.read notes.write calendar.read calendar.write",
|
||||
}
|
||||
|
||||
response = await client.post(
|
||||
|
||||
@@ -90,7 +90,7 @@ from .app import get_app
|
||||
@click.option(
|
||||
"--oauth-scopes",
|
||||
envvar="NEXTCLOUD_OIDC_SCOPES",
|
||||
default="openid profile email notes:read notes:write calendar:read calendar:write todo:read todo:write contacts:read contacts:write cookbook:read cookbook:write deck:read deck:write tables:read tables:write files:read files:write sharing:read sharing:write",
|
||||
default="openid profile email notes.read notes.write calendar.read calendar.write todo.read todo.write contacts.read contacts.write cookbook.read cookbook.write deck.read deck.write tables.read tables.write files.read files.write sharing.read sharing.write",
|
||||
show_default=True,
|
||||
help="OAuth scopes to request during client registration. These define the maximum allowed scopes for the client. Note: Actual supported scopes are discovered dynamically from MCP tools at runtime. (can also use NEXTCLOUD_OIDC_SCOPES env var)",
|
||||
)
|
||||
@@ -153,7 +153,7 @@ def run(
|
||||
|
||||
# OAuth mode with custom scopes and JWT tokens
|
||||
$ nextcloud-mcp-server --nextcloud-host=https://cloud.example.com --oauth \\
|
||||
--oauth-scopes="openid notes:read notes:write" --oauth-token-type=jwt
|
||||
--oauth-scopes="openid notes.read notes.write" --oauth-token-type=jwt
|
||||
|
||||
# OAuth with public issuer URL (for Docker/proxy setups)
|
||||
$ nextcloud-mcp-server --nextcloud-host=http://app --oauth \\
|
||||
|
||||
@@ -54,27 +54,27 @@ class UpdateScopesResponse(BaseResponse):
|
||||
# All supported application-level scopes (frozenset for O(1) membership tests)
|
||||
ALL_SUPPORTED_SCOPES: frozenset[str] = frozenset(
|
||||
{
|
||||
"notes:read",
|
||||
"notes:write",
|
||||
"calendar:read",
|
||||
"calendar:write",
|
||||
"todo:read",
|
||||
"todo:write",
|
||||
"contacts:read",
|
||||
"contacts:write",
|
||||
"files:read",
|
||||
"files:write",
|
||||
"tables:read",
|
||||
"tables:write",
|
||||
"deck:read",
|
||||
"deck:write",
|
||||
"cookbook:read",
|
||||
"cookbook:write",
|
||||
"sharing:read",
|
||||
"sharing:write",
|
||||
"news:read",
|
||||
"news:write",
|
||||
"collectives:read",
|
||||
"collectives:write",
|
||||
"notes.read",
|
||||
"notes.write",
|
||||
"calendar.read",
|
||||
"calendar.write",
|
||||
"todo.read",
|
||||
"todo.write",
|
||||
"contacts.read",
|
||||
"contacts.write",
|
||||
"files.read",
|
||||
"files.write",
|
||||
"tables.read",
|
||||
"tables.write",
|
||||
"deck.read",
|
||||
"deck.write",
|
||||
"cookbook.read",
|
||||
"cookbook.write",
|
||||
"sharing.read",
|
||||
"sharing.write",
|
||||
"news.read",
|
||||
"news.write",
|
||||
"collectives.read",
|
||||
"collectives.write",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -410,7 +410,7 @@ def instrument_tool(func):
|
||||
|
||||
Usage:
|
||||
@mcp.tool()
|
||||
@require_scopes("notes:write")
|
||||
@require_scopes("notes.write")
|
||||
@instrument_tool
|
||||
async def nc_notes_create_note(...):
|
||||
...
|
||||
|
||||
@@ -56,7 +56,7 @@ def register_auth_tools(mcp: FastMCP) -> None:
|
||||
|
||||
Args:
|
||||
ctx: MCP context
|
||||
scopes: Requested application scopes (e.g. ["notes:read", "calendar:write"]).
|
||||
scopes: Requested application scopes (e.g. ["notes.read", "calendar.write"]).
|
||||
If not specified, all available scopes are requested.
|
||||
|
||||
Returns:
|
||||
|
||||
@@ -55,7 +55,7 @@ def configure_calendar_tools(mcp: FastMCP):
|
||||
title="List Calendars",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("calendar:read")
|
||||
@require_scopes("calendar.read")
|
||||
@instrument_tool
|
||||
async def nc_calendar_list_calendars(ctx: Context) -> ListCalendarsResponse:
|
||||
"""List all available calendars for the user"""
|
||||
@@ -69,7 +69,7 @@ def configure_calendar_tools(mcp: FastMCP):
|
||||
title="Create Calendar Event",
|
||||
annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("calendar:write")
|
||||
@require_scopes("calendar.write")
|
||||
@instrument_tool
|
||||
async def nc_calendar_create_event(
|
||||
calendar_name: str,
|
||||
@@ -149,7 +149,7 @@ def configure_calendar_tools(mcp: FastMCP):
|
||||
title="List Calendar Events",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("calendar:read")
|
||||
@require_scopes("calendar.read")
|
||||
@instrument_tool
|
||||
async def nc_calendar_list_events(
|
||||
calendar_name: str,
|
||||
@@ -269,7 +269,7 @@ def configure_calendar_tools(mcp: FastMCP):
|
||||
title="Get Calendar Event",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("calendar:read")
|
||||
@require_scopes("calendar.read")
|
||||
@instrument_tool
|
||||
async def nc_calendar_get_event(
|
||||
calendar_name: str,
|
||||
@@ -285,7 +285,7 @@ def configure_calendar_tools(mcp: FastMCP):
|
||||
title="Update Calendar Event",
|
||||
annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("calendar:write")
|
||||
@require_scopes("calendar.write")
|
||||
@instrument_tool
|
||||
async def nc_calendar_update_event(
|
||||
calendar_name: str,
|
||||
@@ -364,7 +364,7 @@ def configure_calendar_tools(mcp: FastMCP):
|
||||
destructiveHint=True, idempotentHint=True, openWorldHint=True
|
||||
),
|
||||
)
|
||||
@require_scopes("calendar:write")
|
||||
@require_scopes("calendar.write")
|
||||
@instrument_tool
|
||||
async def nc_calendar_delete_event(
|
||||
calendar_name: str,
|
||||
@@ -379,7 +379,7 @@ def configure_calendar_tools(mcp: FastMCP):
|
||||
title="Create Meeting",
|
||||
annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("calendar:write")
|
||||
@require_scopes("calendar.write")
|
||||
@instrument_tool
|
||||
async def nc_calendar_create_meeting(
|
||||
title: str,
|
||||
@@ -449,7 +449,7 @@ def configure_calendar_tools(mcp: FastMCP):
|
||||
title="Get Upcoming Events",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("calendar:read")
|
||||
@require_scopes("calendar.read")
|
||||
@instrument_tool
|
||||
async def nc_calendar_get_upcoming_events(
|
||||
ctx: Context,
|
||||
@@ -512,7 +512,7 @@ def configure_calendar_tools(mcp: FastMCP):
|
||||
title="Find Availability",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("calendar:read")
|
||||
@require_scopes("calendar.read")
|
||||
@instrument_tool
|
||||
async def nc_calendar_find_availability(
|
||||
duration_minutes: int,
|
||||
@@ -596,7 +596,7 @@ def configure_calendar_tools(mcp: FastMCP):
|
||||
title="Bulk Calendar Operations",
|
||||
annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("calendar:write")
|
||||
@require_scopes("calendar.write")
|
||||
@instrument_tool
|
||||
async def nc_calendar_bulk_operations(
|
||||
operation: str, # "update", "delete", "move"
|
||||
@@ -849,7 +849,7 @@ def configure_calendar_tools(mcp: FastMCP):
|
||||
title="Manage Calendar",
|
||||
annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("calendar:write")
|
||||
@require_scopes("calendar.write")
|
||||
@instrument_tool
|
||||
async def nc_calendar_manage_calendar(
|
||||
action: str, # "create", "delete", "update", "list"
|
||||
@@ -922,7 +922,7 @@ def configure_calendar_tools(mcp: FastMCP):
|
||||
title="List Todo Tasks",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("todo:read", "calendar:read")
|
||||
@require_scopes("todo.read", "calendar.read")
|
||||
@instrument_tool
|
||||
async def nc_calendar_list_todos(
|
||||
calendar_name: str,
|
||||
@@ -971,7 +971,7 @@ def configure_calendar_tools(mcp: FastMCP):
|
||||
title="Create Todo Task",
|
||||
annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("todo:write", "calendar:read")
|
||||
@require_scopes("todo.write", "calendar.read")
|
||||
@instrument_tool
|
||||
async def nc_calendar_create_todo(
|
||||
calendar_name: str,
|
||||
@@ -1018,7 +1018,7 @@ def configure_calendar_tools(mcp: FastMCP):
|
||||
title="Update Todo Task",
|
||||
annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("todo:write", "calendar:read")
|
||||
@require_scopes("todo.write", "calendar.read")
|
||||
@instrument_tool
|
||||
async def nc_calendar_update_todo(
|
||||
calendar_name: str,
|
||||
@@ -1084,7 +1084,7 @@ def configure_calendar_tools(mcp: FastMCP):
|
||||
destructiveHint=True, idempotentHint=True, openWorldHint=True
|
||||
),
|
||||
)
|
||||
@require_scopes("todo:write", "calendar:read")
|
||||
@require_scopes("todo.write", "calendar.read")
|
||||
@instrument_tool
|
||||
async def nc_calendar_delete_todo(
|
||||
calendar_name: str,
|
||||
@@ -1108,7 +1108,7 @@ def configure_calendar_tools(mcp: FastMCP):
|
||||
title="Search Todo Tasks",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("todo:read", "calendar:read")
|
||||
@require_scopes("todo.read", "calendar.read")
|
||||
@instrument_tool
|
||||
async def nc_calendar_search_todos(
|
||||
ctx: Context,
|
||||
|
||||
@@ -48,7 +48,7 @@ def configure_collectives_tools(mcp: FastMCP):
|
||||
title="List Collectives",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("collectives:read")
|
||||
@require_scopes("collectives.read")
|
||||
@instrument_tool
|
||||
async def collectives_get_collectives(
|
||||
ctx: Context,
|
||||
@@ -66,7 +66,7 @@ def configure_collectives_tools(mcp: FastMCP):
|
||||
title="List Collective Pages",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("collectives:read")
|
||||
@require_scopes("collectives.read")
|
||||
@instrument_tool
|
||||
async def collectives_get_pages(
|
||||
ctx: Context, collective_id: int
|
||||
@@ -90,7 +90,7 @@ def configure_collectives_tools(mcp: FastMCP):
|
||||
title="Get Collective Page",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("collectives:read")
|
||||
@require_scopes("collectives.read")
|
||||
@instrument_tool
|
||||
async def collectives_get_page(
|
||||
ctx: Context, collective_id: int, page_id: int
|
||||
@@ -138,7 +138,7 @@ def configure_collectives_tools(mcp: FastMCP):
|
||||
title="Search Collective Pages",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("collectives:read")
|
||||
@require_scopes("collectives.read")
|
||||
@instrument_tool
|
||||
async def collectives_search_pages(
|
||||
ctx: Context, collective_id: int, query: str
|
||||
@@ -166,7 +166,7 @@ def configure_collectives_tools(mcp: FastMCP):
|
||||
title="List Collective Tags",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("collectives:read")
|
||||
@require_scopes("collectives.read")
|
||||
@instrument_tool
|
||||
async def collectives_get_tags(
|
||||
ctx: Context, collective_id: int
|
||||
@@ -188,7 +188,7 @@ def configure_collectives_tools(mcp: FastMCP):
|
||||
title="List Trashed Collective Pages",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("collectives:read")
|
||||
@require_scopes("collectives.read")
|
||||
@instrument_tool
|
||||
async def collectives_get_trashed_pages(
|
||||
ctx: Context, collective_id: int
|
||||
@@ -212,7 +212,7 @@ def configure_collectives_tools(mcp: FastMCP):
|
||||
title="List Trashed Collectives",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("collectives:read")
|
||||
@require_scopes("collectives.read")
|
||||
@instrument_tool
|
||||
async def collectives_get_trashed_collectives(
|
||||
ctx: Context,
|
||||
@@ -238,7 +238,7 @@ def configure_collectives_tools(mcp: FastMCP):
|
||||
title="Create Collective",
|
||||
annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("collectives:write")
|
||||
@require_scopes("collectives.write")
|
||||
@instrument_tool
|
||||
async def collectives_create_collective(
|
||||
ctx: Context, name: str, emoji: str | None = None
|
||||
@@ -263,7 +263,7 @@ def configure_collectives_tools(mcp: FastMCP):
|
||||
title="Set Collective Emoji",
|
||||
annotations=ToolAnnotations(idempotentHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("collectives:write")
|
||||
@require_scopes("collectives.write")
|
||||
@instrument_tool
|
||||
async def collectives_set_collective_emoji(
|
||||
ctx: Context, collective_id: int, emoji: str | None = None
|
||||
@@ -295,7 +295,7 @@ def configure_collectives_tools(mcp: FastMCP):
|
||||
title="Trash Collective",
|
||||
annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("collectives:write")
|
||||
@require_scopes("collectives.write")
|
||||
@instrument_tool
|
||||
async def collectives_trash_collective(
|
||||
ctx: Context, collective_id: int
|
||||
@@ -324,7 +324,7 @@ def configure_collectives_tools(mcp: FastMCP):
|
||||
destructiveHint=True, idempotentHint=False, openWorldHint=True
|
||||
),
|
||||
)
|
||||
@require_scopes("collectives:write")
|
||||
@require_scopes("collectives.write")
|
||||
@instrument_tool
|
||||
async def collectives_delete_collective(
|
||||
ctx: Context, collective_id: int
|
||||
@@ -353,7 +353,7 @@ def configure_collectives_tools(mcp: FastMCP):
|
||||
title="Restore Collective",
|
||||
annotations=ToolAnnotations(idempotentHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("collectives:write")
|
||||
@require_scopes("collectives.write")
|
||||
@instrument_tool
|
||||
async def collectives_restore_collective(
|
||||
ctx: Context, collective_id: int
|
||||
@@ -379,7 +379,7 @@ def configure_collectives_tools(mcp: FastMCP):
|
||||
title="Create Collective Page",
|
||||
annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("collectives:write")
|
||||
@require_scopes("collectives.write")
|
||||
@instrument_tool
|
||||
async def collectives_create_page(
|
||||
ctx: Context, collective_id: int, parent_id: int, title: str
|
||||
@@ -412,7 +412,7 @@ def configure_collectives_tools(mcp: FastMCP):
|
||||
title="Move Collective Page",
|
||||
annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("collectives:write")
|
||||
@require_scopes("collectives.write")
|
||||
@instrument_tool
|
||||
async def collectives_move_page(
|
||||
ctx: Context,
|
||||
@@ -453,7 +453,7 @@ def configure_collectives_tools(mcp: FastMCP):
|
||||
title="Trash Collective Page",
|
||||
annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("collectives:write")
|
||||
@require_scopes("collectives.write")
|
||||
@instrument_tool
|
||||
async def collectives_trash_page(
|
||||
ctx: Context, collective_id: int, page_id: int
|
||||
@@ -484,7 +484,7 @@ def configure_collectives_tools(mcp: FastMCP):
|
||||
title="Restore Collective Page",
|
||||
annotations=ToolAnnotations(idempotentHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("collectives:write")
|
||||
@require_scopes("collectives.write")
|
||||
@instrument_tool
|
||||
async def collectives_restore_page(
|
||||
ctx: Context, collective_id: int, page_id: int
|
||||
@@ -512,7 +512,7 @@ def configure_collectives_tools(mcp: FastMCP):
|
||||
title="Set Collective Page Emoji",
|
||||
annotations=ToolAnnotations(idempotentHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("collectives:write")
|
||||
@require_scopes("collectives.write")
|
||||
@instrument_tool
|
||||
async def collectives_set_page_emoji(
|
||||
ctx: Context,
|
||||
@@ -544,7 +544,7 @@ def configure_collectives_tools(mcp: FastMCP):
|
||||
title="Create Collective Tag",
|
||||
annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("collectives:write")
|
||||
@require_scopes("collectives.write")
|
||||
@instrument_tool
|
||||
async def collectives_create_tag(
|
||||
ctx: Context, collective_id: int, name: str, color: str
|
||||
@@ -568,7 +568,7 @@ def configure_collectives_tools(mcp: FastMCP):
|
||||
title="Assign Tag to Collective Page",
|
||||
annotations=ToolAnnotations(idempotentHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("collectives:write")
|
||||
@require_scopes("collectives.write")
|
||||
@instrument_tool
|
||||
async def collectives_assign_tag(
|
||||
ctx: Context, collective_id: int, page_id: int, tag_id: int
|
||||
@@ -596,7 +596,7 @@ def configure_collectives_tools(mcp: FastMCP):
|
||||
title="Remove Tag from Collective Page",
|
||||
annotations=ToolAnnotations(idempotentHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("collectives:write")
|
||||
@require_scopes("collectives.write")
|
||||
@instrument_tool
|
||||
async def collectives_remove_tag(
|
||||
ctx: Context, collective_id: int, page_id: int, tag_id: int
|
||||
|
||||
@@ -99,7 +99,7 @@ def configure_contacts_tools(mcp: FastMCP):
|
||||
title="List Address Books",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("contacts:read")
|
||||
@require_scopes("contacts.read")
|
||||
@instrument_tool
|
||||
async def nc_contacts_list_addressbooks(ctx: Context) -> ListAddressBooksResponse:
|
||||
"""List all addressbooks for the user."""
|
||||
@@ -123,7 +123,7 @@ def configure_contacts_tools(mcp: FastMCP):
|
||||
title="List Contacts",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("contacts:read")
|
||||
@require_scopes("contacts.read")
|
||||
@instrument_tool
|
||||
async def nc_contacts_list_contacts(
|
||||
ctx: Context, *, addressbook: str
|
||||
@@ -146,7 +146,7 @@ def configure_contacts_tools(mcp: FastMCP):
|
||||
title="Create Address Book",
|
||||
annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("contacts:write")
|
||||
@require_scopes("contacts.write")
|
||||
@instrument_tool
|
||||
async def nc_contacts_create_addressbook(
|
||||
ctx: Context, *, name: str, display_name: str
|
||||
@@ -168,7 +168,7 @@ def configure_contacts_tools(mcp: FastMCP):
|
||||
destructiveHint=True, idempotentHint=True, openWorldHint=True
|
||||
),
|
||||
)
|
||||
@require_scopes("contacts:write")
|
||||
@require_scopes("contacts.write")
|
||||
@instrument_tool
|
||||
async def nc_contacts_delete_addressbook(ctx: Context, *, name: str):
|
||||
"""Delete an addressbook."""
|
||||
@@ -179,7 +179,7 @@ def configure_contacts_tools(mcp: FastMCP):
|
||||
title="Create Contact",
|
||||
annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("contacts:write")
|
||||
@require_scopes("contacts.write")
|
||||
@instrument_tool
|
||||
async def nc_contacts_create_contact(
|
||||
ctx: Context, *, addressbook: str, uid: str, contact_data: dict
|
||||
@@ -204,7 +204,7 @@ def configure_contacts_tools(mcp: FastMCP):
|
||||
destructiveHint=True, idempotentHint=True, openWorldHint=True
|
||||
),
|
||||
)
|
||||
@require_scopes("contacts:write")
|
||||
@require_scopes("contacts.write")
|
||||
@instrument_tool
|
||||
async def nc_contacts_delete_contact(ctx: Context, *, addressbook: str, uid: str):
|
||||
"""Delete a contact.
|
||||
@@ -222,7 +222,7 @@ def configure_contacts_tools(mcp: FastMCP):
|
||||
title="Update Contact",
|
||||
annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("contacts:write")
|
||||
@require_scopes("contacts.write")
|
||||
@instrument_tool
|
||||
async def nc_contacts_update_contact(
|
||||
ctx: Context, *, addressbook: str, uid: str, contact_data: dict, etag: str = ""
|
||||
|
||||
@@ -75,7 +75,7 @@ def configure_cookbook_tools(mcp: FastMCP):
|
||||
title="Import Recipe from URL",
|
||||
annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("cookbook:write")
|
||||
@require_scopes("cookbook.write")
|
||||
@instrument_tool
|
||||
async def nc_cookbook_import_recipe(url: str, ctx: Context) -> ImportRecipeResponse:
|
||||
"""Import a recipe from a URL using schema.org metadata.
|
||||
@@ -136,7 +136,7 @@ def configure_cookbook_tools(mcp: FastMCP):
|
||||
title="List Recipes",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("cookbook:read")
|
||||
@require_scopes("cookbook.read")
|
||||
@instrument_tool
|
||||
async def nc_cookbook_list_recipes(ctx: Context) -> ListRecipesResponse:
|
||||
"""Get all recipes in the database"""
|
||||
@@ -165,7 +165,7 @@ def configure_cookbook_tools(mcp: FastMCP):
|
||||
title="Get Recipe",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("cookbook:read")
|
||||
@require_scopes("cookbook.read")
|
||||
@instrument_tool
|
||||
async def nc_cookbook_get_recipe(recipe_id: int, ctx: Context) -> Recipe:
|
||||
"""Get a specific recipe by its ID"""
|
||||
@@ -194,7 +194,7 @@ def configure_cookbook_tools(mcp: FastMCP):
|
||||
title="Create Recipe",
|
||||
annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("cookbook:write")
|
||||
@require_scopes("cookbook.write")
|
||||
@instrument_tool
|
||||
async def nc_cookbook_create_recipe(
|
||||
name: str,
|
||||
@@ -277,7 +277,7 @@ def configure_cookbook_tools(mcp: FastMCP):
|
||||
title="Update Recipe",
|
||||
annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("cookbook:write")
|
||||
@require_scopes("cookbook.write")
|
||||
@instrument_tool
|
||||
async def nc_cookbook_update_recipe(
|
||||
recipe_id: int,
|
||||
@@ -372,7 +372,7 @@ def configure_cookbook_tools(mcp: FastMCP):
|
||||
destructiveHint=True, idempotentHint=True, openWorldHint=True
|
||||
),
|
||||
)
|
||||
@require_scopes("cookbook:write")
|
||||
@require_scopes("cookbook.write")
|
||||
@instrument_tool
|
||||
async def nc_cookbook_delete_recipe(
|
||||
recipe_id: int, ctx: Context
|
||||
@@ -411,7 +411,7 @@ def configure_cookbook_tools(mcp: FastMCP):
|
||||
title="Search Recipes",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("cookbook:read")
|
||||
@require_scopes("cookbook.read")
|
||||
@instrument_tool
|
||||
async def nc_cookbook_search_recipes(
|
||||
query: str, ctx: Context
|
||||
@@ -451,7 +451,7 @@ def configure_cookbook_tools(mcp: FastMCP):
|
||||
title="List Recipe Categories",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("cookbook:read")
|
||||
@require_scopes("cookbook.read")
|
||||
@instrument_tool
|
||||
async def nc_cookbook_list_categories(ctx: Context) -> ListCategoriesResponse:
|
||||
"""Get all known categories.
|
||||
@@ -482,7 +482,7 @@ def configure_cookbook_tools(mcp: FastMCP):
|
||||
title="Get Recipes in Category",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("cookbook:read")
|
||||
@require_scopes("cookbook.read")
|
||||
@instrument_tool
|
||||
async def nc_cookbook_get_recipes_in_category(
|
||||
category: str, ctx: Context
|
||||
@@ -522,7 +522,7 @@ def configure_cookbook_tools(mcp: FastMCP):
|
||||
title="List Recipe Keywords",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("cookbook:read")
|
||||
@require_scopes("cookbook.read")
|
||||
@instrument_tool
|
||||
async def nc_cookbook_list_keywords(ctx: Context) -> ListKeywordsResponse:
|
||||
"""Get all known keywords/tags"""
|
||||
@@ -551,7 +551,7 @@ def configure_cookbook_tools(mcp: FastMCP):
|
||||
title="Get Recipes with Keywords",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("cookbook:read")
|
||||
@require_scopes("cookbook.read")
|
||||
@instrument_tool
|
||||
async def nc_cookbook_get_recipes_with_keywords(
|
||||
keywords: list[str], ctx: Context
|
||||
@@ -589,7 +589,7 @@ def configure_cookbook_tools(mcp: FastMCP):
|
||||
title="Set Cookbook Configuration",
|
||||
annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("cookbook:write")
|
||||
@require_scopes("cookbook.write")
|
||||
@instrument_tool
|
||||
async def nc_cookbook_set_config(
|
||||
folder: str | None = None,
|
||||
@@ -636,7 +636,7 @@ def configure_cookbook_tools(mcp: FastMCP):
|
||||
title="Reindex Recipes",
|
||||
annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("cookbook:write")
|
||||
@require_scopes("cookbook.write")
|
||||
@instrument_tool
|
||||
async def nc_cookbook_reindex(ctx: Context) -> ReindexResponse:
|
||||
"""Trigger a rescan of all recipes into the caching database.
|
||||
|
||||
@@ -126,7 +126,7 @@ def configure_deck_tools(mcp: FastMCP):
|
||||
title="List Deck Boards",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("deck:read")
|
||||
@require_scopes("deck.read")
|
||||
@instrument_tool
|
||||
async def deck_get_boards(ctx: Context) -> ListBoardsResponse:
|
||||
"""Get all Nextcloud Deck boards"""
|
||||
@@ -138,7 +138,7 @@ def configure_deck_tools(mcp: FastMCP):
|
||||
title="Get Deck Board",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("deck:read")
|
||||
@require_scopes("deck.read")
|
||||
@instrument_tool
|
||||
async def deck_get_board(ctx: Context, board_id: int) -> DeckBoard:
|
||||
"""Get details of a specific Nextcloud Deck board"""
|
||||
@@ -150,7 +150,7 @@ def configure_deck_tools(mcp: FastMCP):
|
||||
title="List Deck Stacks",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("deck:read")
|
||||
@require_scopes("deck.read")
|
||||
@instrument_tool
|
||||
async def deck_get_stacks(ctx: Context, board_id: int) -> ListStacksResponse:
|
||||
"""Get all stacks in a Nextcloud Deck board"""
|
||||
@@ -162,7 +162,7 @@ def configure_deck_tools(mcp: FastMCP):
|
||||
title="Get Deck Stack",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("deck:read")
|
||||
@require_scopes("deck.read")
|
||||
@instrument_tool
|
||||
async def deck_get_stack(ctx: Context, board_id: int, stack_id: int) -> DeckStack:
|
||||
"""Get details of a specific Nextcloud Deck stack"""
|
||||
@@ -174,7 +174,7 @@ def configure_deck_tools(mcp: FastMCP):
|
||||
title="List Deck Cards",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("deck:read")
|
||||
@require_scopes("deck.read")
|
||||
@instrument_tool
|
||||
async def deck_get_cards(
|
||||
ctx: Context, board_id: int, stack_id: int
|
||||
@@ -189,7 +189,7 @@ def configure_deck_tools(mcp: FastMCP):
|
||||
title="Get Deck Card",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("deck:read")
|
||||
@require_scopes("deck.read")
|
||||
@instrument_tool
|
||||
async def deck_get_card(
|
||||
ctx: Context, board_id: int, stack_id: int, card_id: int
|
||||
@@ -203,7 +203,7 @@ def configure_deck_tools(mcp: FastMCP):
|
||||
title="List Deck Labels",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("deck:read")
|
||||
@require_scopes("deck.read")
|
||||
@instrument_tool
|
||||
async def deck_get_labels(ctx: Context, board_id: int) -> ListLabelsResponse:
|
||||
"""Get all labels in a Nextcloud Deck board"""
|
||||
@@ -216,7 +216,7 @@ def configure_deck_tools(mcp: FastMCP):
|
||||
title="Get Deck Label",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("deck:read")
|
||||
@require_scopes("deck.read")
|
||||
@instrument_tool
|
||||
async def deck_get_label(ctx: Context, board_id: int, label_id: int) -> DeckLabel:
|
||||
"""Get details of a specific Nextcloud Deck label"""
|
||||
@@ -230,7 +230,7 @@ def configure_deck_tools(mcp: FastMCP):
|
||||
title="Create Deck Board",
|
||||
annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("deck:write")
|
||||
@require_scopes("deck.write")
|
||||
@instrument_tool
|
||||
async def deck_create_board(
|
||||
ctx: Context, title: str, color: str
|
||||
@@ -251,7 +251,7 @@ def configure_deck_tools(mcp: FastMCP):
|
||||
title="Create Deck Stack",
|
||||
annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("deck:write")
|
||||
@require_scopes("deck.write")
|
||||
@instrument_tool
|
||||
async def deck_create_stack(
|
||||
ctx: Context, board_id: int, title: str, order: int
|
||||
@@ -271,7 +271,7 @@ def configure_deck_tools(mcp: FastMCP):
|
||||
title="Update Deck Stack",
|
||||
annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("deck:write")
|
||||
@require_scopes("deck.write")
|
||||
@instrument_tool
|
||||
async def deck_update_stack(
|
||||
ctx: Context,
|
||||
@@ -303,7 +303,7 @@ def configure_deck_tools(mcp: FastMCP):
|
||||
destructiveHint=True, idempotentHint=True, openWorldHint=True
|
||||
),
|
||||
)
|
||||
@require_scopes("deck:write")
|
||||
@require_scopes("deck.write")
|
||||
@instrument_tool
|
||||
async def deck_delete_stack(
|
||||
ctx: Context, board_id: int, stack_id: int
|
||||
@@ -328,7 +328,7 @@ def configure_deck_tools(mcp: FastMCP):
|
||||
title="Create Deck Card",
|
||||
annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("deck:write")
|
||||
@require_scopes("deck.write")
|
||||
@instrument_tool
|
||||
async def deck_create_card(
|
||||
ctx: Context,
|
||||
@@ -366,7 +366,7 @@ def configure_deck_tools(mcp: FastMCP):
|
||||
title="Update Deck Card",
|
||||
annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("deck:write")
|
||||
@require_scopes("deck.write")
|
||||
@instrument_tool
|
||||
async def deck_update_card(
|
||||
ctx: Context,
|
||||
@@ -425,7 +425,7 @@ def configure_deck_tools(mcp: FastMCP):
|
||||
destructiveHint=True, idempotentHint=True, openWorldHint=True
|
||||
),
|
||||
)
|
||||
@require_scopes("deck:write")
|
||||
@require_scopes("deck.write")
|
||||
@instrument_tool
|
||||
async def deck_delete_card(
|
||||
ctx: Context, board_id: int, stack_id: int, card_id: int
|
||||
@@ -451,7 +451,7 @@ def configure_deck_tools(mcp: FastMCP):
|
||||
title="Archive Deck Card",
|
||||
annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("deck:write")
|
||||
@require_scopes("deck.write")
|
||||
@instrument_tool
|
||||
async def deck_archive_card(
|
||||
ctx: Context, board_id: int, stack_id: int, card_id: int
|
||||
@@ -477,7 +477,7 @@ def configure_deck_tools(mcp: FastMCP):
|
||||
title="Unarchive Deck Card",
|
||||
annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("deck:write")
|
||||
@require_scopes("deck.write")
|
||||
@instrument_tool
|
||||
async def deck_unarchive_card(
|
||||
ctx: Context, board_id: int, stack_id: int, card_id: int
|
||||
@@ -503,7 +503,7 @@ def configure_deck_tools(mcp: FastMCP):
|
||||
title="Reorder/Move Deck Card",
|
||||
annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("deck:write")
|
||||
@require_scopes("deck.write")
|
||||
@instrument_tool
|
||||
async def deck_reorder_card(
|
||||
ctx: Context,
|
||||
@@ -539,7 +539,7 @@ def configure_deck_tools(mcp: FastMCP):
|
||||
title="Create Deck Label",
|
||||
annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("deck:write")
|
||||
@require_scopes("deck.write")
|
||||
@instrument_tool
|
||||
async def deck_create_label(
|
||||
ctx: Context, board_id: int, title: str, color: str
|
||||
@@ -559,7 +559,7 @@ def configure_deck_tools(mcp: FastMCP):
|
||||
title="Update Deck Label",
|
||||
annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("deck:write")
|
||||
@require_scopes("deck.write")
|
||||
@instrument_tool
|
||||
async def deck_update_label(
|
||||
ctx: Context,
|
||||
@@ -591,7 +591,7 @@ def configure_deck_tools(mcp: FastMCP):
|
||||
destructiveHint=True, idempotentHint=True, openWorldHint=True
|
||||
),
|
||||
)
|
||||
@require_scopes("deck:write")
|
||||
@require_scopes("deck.write")
|
||||
@instrument_tool
|
||||
async def deck_delete_label(
|
||||
ctx: Context, board_id: int, label_id: int
|
||||
@@ -616,7 +616,7 @@ def configure_deck_tools(mcp: FastMCP):
|
||||
title="Assign Label to Deck Card",
|
||||
annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("deck:write")
|
||||
@require_scopes("deck.write")
|
||||
@instrument_tool
|
||||
async def deck_assign_label_to_card(
|
||||
ctx: Context, board_id: int, stack_id: int, card_id: int, label_id: int
|
||||
@@ -643,7 +643,7 @@ def configure_deck_tools(mcp: FastMCP):
|
||||
title="Remove Label from Deck Card",
|
||||
annotations=ToolAnnotations(idempotentHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("deck:write")
|
||||
@require_scopes("deck.write")
|
||||
@instrument_tool
|
||||
async def deck_remove_label_from_card(
|
||||
ctx: Context, board_id: int, stack_id: int, card_id: int, label_id: int
|
||||
@@ -671,7 +671,7 @@ def configure_deck_tools(mcp: FastMCP):
|
||||
title="Assign User to Deck Card",
|
||||
annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("deck:write")
|
||||
@require_scopes("deck.write")
|
||||
@instrument_tool
|
||||
async def deck_assign_user_to_card(
|
||||
ctx: Context, board_id: int, stack_id: int, card_id: int, user_id: str
|
||||
@@ -700,7 +700,7 @@ def configure_deck_tools(mcp: FastMCP):
|
||||
destructiveHint=True, idempotentHint=True, openWorldHint=True
|
||||
),
|
||||
)
|
||||
@require_scopes("deck:write")
|
||||
@require_scopes("deck.write")
|
||||
@instrument_tool
|
||||
async def deck_unassign_user_from_card(
|
||||
ctx: Context, board_id: int, stack_id: int, card_id: int, user_id: str
|
||||
|
||||
@@ -34,10 +34,10 @@ def configure_news_tools(mcp: FastMCP):
|
||||
title="List News Folders",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("news:read")
|
||||
@require_scopes("news.read")
|
||||
@instrument_tool
|
||||
async def nc_news_list_folders(ctx: Context) -> ListFoldersResponse:
|
||||
"""List all News folders (requires news:read scope)."""
|
||||
"""List all News folders (requires news.read scope)."""
|
||||
client = await get_client(ctx)
|
||||
try:
|
||||
folders_data = await client.news.get_folders()
|
||||
@@ -59,10 +59,10 @@ def configure_news_tools(mcp: FastMCP):
|
||||
title="List News Feeds",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("news:read")
|
||||
@require_scopes("news.read")
|
||||
@instrument_tool
|
||||
async def nc_news_list_feeds(ctx: Context) -> ListFeedsResponse:
|
||||
"""List all News feeds with metadata (requires news:read scope).
|
||||
"""List all News feeds with metadata (requires news.read scope).
|
||||
|
||||
Returns feeds with unread counts, error status, and overall starred count.
|
||||
"""
|
||||
@@ -92,7 +92,7 @@ def configure_news_tools(mcp: FastMCP):
|
||||
title="List News Items",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("news:read")
|
||||
@require_scopes("news.read")
|
||||
@instrument_tool
|
||||
async def nc_news_list_items(
|
||||
ctx: Context,
|
||||
@@ -103,7 +103,7 @@ def configure_news_tools(mcp: FastMCP):
|
||||
limit: int = 50,
|
||||
offset: int = 0,
|
||||
) -> ListItemsResponse:
|
||||
"""List News items (articles) with optional filtering (requires news:read scope).
|
||||
"""List News items (articles) with optional filtering (requires news.read scope).
|
||||
|
||||
Args:
|
||||
feed_id: Filter by specific feed ID
|
||||
@@ -166,10 +166,10 @@ def configure_news_tools(mcp: FastMCP):
|
||||
title="Get News Item",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("news:read")
|
||||
@require_scopes("news.read")
|
||||
@instrument_tool
|
||||
async def nc_news_get_item(item_id: int, ctx: Context) -> GetItemResponse:
|
||||
"""Get a specific News item by ID with full content (requires news:read scope).
|
||||
"""Get a specific News item by ID with full content (requires news.read scope).
|
||||
|
||||
Args:
|
||||
item_id: Item ID
|
||||
@@ -204,12 +204,12 @@ def configure_news_tools(mcp: FastMCP):
|
||||
title="Get Starred News Items",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("news:read")
|
||||
@require_scopes("news.read")
|
||||
@instrument_tool
|
||||
async def nc_news_get_starred_items(
|
||||
ctx: Context, limit: int = 50, offset: int = 0
|
||||
) -> ListItemsResponse:
|
||||
"""Get starred (favorited) News items (requires news:read scope).
|
||||
"""Get starred (favorited) News items (requires news.read scope).
|
||||
|
||||
Convenience method for retrieving user's starred articles.
|
||||
|
||||
@@ -257,12 +257,12 @@ def configure_news_tools(mcp: FastMCP):
|
||||
title="Get Unread News Items",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("news:read")
|
||||
@require_scopes("news.read")
|
||||
@instrument_tool
|
||||
async def nc_news_get_unread_items(
|
||||
ctx: Context, limit: int = 50, offset: int = 0
|
||||
) -> ListItemsResponse:
|
||||
"""Get unread News items (requires news:read scope).
|
||||
"""Get unread News items (requires news.read scope).
|
||||
|
||||
Convenience method for retrieving unread articles across all feeds.
|
||||
|
||||
@@ -310,10 +310,10 @@ def configure_news_tools(mcp: FastMCP):
|
||||
title="Get News Feed Health",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("news:read")
|
||||
@require_scopes("news.read")
|
||||
@instrument_tool
|
||||
async def nc_news_get_feed_health(feed_id: int, ctx: Context) -> FeedHealthResponse:
|
||||
"""Get health status for a specific feed (requires news:read scope).
|
||||
"""Get health status for a specific feed (requires news.read scope).
|
||||
|
||||
Returns error count and last error message if the feed has update issues.
|
||||
|
||||
@@ -357,10 +357,10 @@ def configure_news_tools(mcp: FastMCP):
|
||||
title="Get News App Status",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("news:read")
|
||||
@require_scopes("news.read")
|
||||
@instrument_tool
|
||||
async def nc_news_get_status(ctx: Context) -> GetStatusResponse:
|
||||
"""Get News app status and version (requires news:read scope).
|
||||
"""Get News app status and version (requires news.read scope).
|
||||
|
||||
Returns version information and any configuration warnings.
|
||||
"""
|
||||
|
||||
@@ -92,12 +92,12 @@ def configure_notes_tools(mcp: FastMCP):
|
||||
openWorldHint=True,
|
||||
),
|
||||
)
|
||||
@require_scopes("notes:write")
|
||||
@require_scopes("notes.write")
|
||||
@instrument_tool
|
||||
async def nc_notes_create_note(
|
||||
title: str, content: str, category: str, ctx: Context
|
||||
) -> CreateNoteResponse:
|
||||
"""Create a new note (requires notes:write scope)"""
|
||||
"""Create a new note (requires notes.write scope)"""
|
||||
client = await get_client(ctx)
|
||||
try:
|
||||
note_data = await client.notes.create_note(
|
||||
@@ -145,7 +145,7 @@ def configure_notes_tools(mcp: FastMCP):
|
||||
openWorldHint=True,
|
||||
),
|
||||
)
|
||||
@require_scopes("notes:write")
|
||||
@require_scopes("notes.write")
|
||||
@instrument_tool
|
||||
async def nc_notes_update_note(
|
||||
note_id: int,
|
||||
@@ -155,7 +155,7 @@ def configure_notes_tools(mcp: FastMCP):
|
||||
category: str | None,
|
||||
ctx: Context,
|
||||
) -> UpdateNoteResponse:
|
||||
"""Update an existing note's title, content, or category (requires notes:write scope).
|
||||
"""Update an existing note's title, content, or category (requires notes.write scope).
|
||||
|
||||
REQUIRED: etag parameter must be provided to prevent overwriting concurrent changes.
|
||||
Get the current ETag by first retrieving the note using nc_notes_get_note tool.
|
||||
@@ -217,7 +217,7 @@ def configure_notes_tools(mcp: FastMCP):
|
||||
openWorldHint=True,
|
||||
),
|
||||
)
|
||||
@require_scopes("notes:write")
|
||||
@require_scopes("notes.write")
|
||||
@instrument_tool
|
||||
async def nc_notes_append_content(
|
||||
note_id: int, content: str, ctx: Context
|
||||
@@ -274,10 +274,10 @@ def configure_notes_tools(mcp: FastMCP):
|
||||
openWorldHint=True,
|
||||
),
|
||||
)
|
||||
@require_scopes("notes:read")
|
||||
@require_scopes("notes.read")
|
||||
@instrument_tool
|
||||
async def nc_notes_search_notes(query: str, ctx: Context) -> SearchNotesResponse:
|
||||
"""Search notes by title or content, returning only id, title, and category (requires notes:read scope)."""
|
||||
"""Search notes by title or content, returning only id, title, and category (requires notes.read scope)."""
|
||||
client = await get_client(ctx)
|
||||
try:
|
||||
search_results_raw = await client.notes_search_notes(query=query)
|
||||
@@ -327,10 +327,10 @@ def configure_notes_tools(mcp: FastMCP):
|
||||
openWorldHint=True,
|
||||
),
|
||||
)
|
||||
@require_scopes("notes:read")
|
||||
@require_scopes("notes.read")
|
||||
@instrument_tool
|
||||
async def nc_notes_get_note(note_id: int, ctx: Context) -> Note:
|
||||
"""Get a specific note by its ID (requires notes:read scope)"""
|
||||
"""Get a specific note by its ID (requires notes.read scope)"""
|
||||
client = await get_client(ctx)
|
||||
try:
|
||||
note_data = await client.notes.get_note(note_id)
|
||||
@@ -363,7 +363,7 @@ def configure_notes_tools(mcp: FastMCP):
|
||||
openWorldHint=True,
|
||||
),
|
||||
)
|
||||
@require_scopes("notes:read")
|
||||
@require_scopes("notes.read")
|
||||
@instrument_tool
|
||||
async def nc_notes_get_attachment(
|
||||
note_id: int, attachment_filename: str, ctx: Context
|
||||
@@ -417,7 +417,7 @@ def configure_notes_tools(mcp: FastMCP):
|
||||
openWorldHint=True,
|
||||
),
|
||||
)
|
||||
@require_scopes("notes:write")
|
||||
@require_scopes("notes.write")
|
||||
@instrument_tool
|
||||
async def nc_notes_delete_note(note_id: int, ctx: Context) -> DeleteNoteResponse:
|
||||
"""Delete a note permanently"""
|
||||
|
||||
@@ -473,14 +473,14 @@ async def check_logged_in(ctx: Context, user_id: Optional[str] = None) -> str:
|
||||
"profile",
|
||||
"email",
|
||||
"offline_access", # Critical for background operations
|
||||
"notes:read",
|
||||
"notes:write",
|
||||
"calendar:read",
|
||||
"calendar:write",
|
||||
"contacts:read",
|
||||
"contacts:write",
|
||||
"files:read",
|
||||
"files:write",
|
||||
"notes.read",
|
||||
"notes.write",
|
||||
"calendar.read",
|
||||
"calendar.write",
|
||||
"contacts.read",
|
||||
"contacts.write",
|
||||
"files.read",
|
||||
"files.write",
|
||||
]
|
||||
|
||||
# Generate authorization URL
|
||||
|
||||
@@ -48,7 +48,7 @@ def configure_semantic_tools(mcp: FastMCP):
|
||||
openWorldHint=True, # Queries external Nextcloud service
|
||||
),
|
||||
)
|
||||
@require_scopes("semantic:read")
|
||||
@require_scopes("semantic.read")
|
||||
@instrument_tool
|
||||
async def nc_semantic_search(
|
||||
query: str,
|
||||
@@ -303,7 +303,7 @@ def configure_semantic_tools(mcp: FastMCP):
|
||||
openWorldHint=False, # Searches only indexed Nextcloud data
|
||||
),
|
||||
)
|
||||
@require_scopes("semantic:read")
|
||||
@require_scopes("semantic.read")
|
||||
@instrument_tool
|
||||
async def nc_semantic_search_answer(
|
||||
query: str,
|
||||
@@ -645,7 +645,7 @@ def configure_semantic_tools(mcp: FastMCP):
|
||||
openWorldHint=True,
|
||||
),
|
||||
)
|
||||
@require_scopes("semantic:read")
|
||||
@require_scopes("semantic.read")
|
||||
@instrument_tool
|
||||
async def nc_get_vector_sync_status(ctx: Context) -> VectorSyncStatusResponse:
|
||||
"""Get the current vector sync status.
|
||||
|
||||
@@ -21,7 +21,7 @@ def configure_sharing_tools(mcp: FastMCP):
|
||||
title="Create Share",
|
||||
annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("sharing:write")
|
||||
@require_scopes("sharing.write")
|
||||
@instrument_tool
|
||||
async def nc_share_create(
|
||||
path: str,
|
||||
@@ -66,7 +66,7 @@ def configure_sharing_tools(mcp: FastMCP):
|
||||
destructiveHint=True, idempotentHint=True, openWorldHint=True
|
||||
),
|
||||
)
|
||||
@require_scopes("sharing:write")
|
||||
@require_scopes("sharing.write")
|
||||
@instrument_tool
|
||||
async def nc_share_delete(share_id: int, ctx: Context) -> str:
|
||||
"""Delete a share by its ID.
|
||||
@@ -89,7 +89,7 @@ def configure_sharing_tools(mcp: FastMCP):
|
||||
title="Get Share Details",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("sharing:write")
|
||||
@require_scopes("sharing.write")
|
||||
@instrument_tool
|
||||
async def nc_share_get(share_id: int, ctx: Context) -> str:
|
||||
"""Get information about a specific share.
|
||||
@@ -111,7 +111,7 @@ def configure_sharing_tools(mcp: FastMCP):
|
||||
title="List Shares",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("sharing:write")
|
||||
@require_scopes("sharing.write")
|
||||
@instrument_tool
|
||||
async def nc_share_list(
|
||||
ctx: Context, path: str | None = None, shared_with_me: bool = False
|
||||
@@ -136,7 +136,7 @@ def configure_sharing_tools(mcp: FastMCP):
|
||||
title="Update Share",
|
||||
annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("sharing:write")
|
||||
@require_scopes("sharing.write")
|
||||
@instrument_tool
|
||||
async def nc_share_update(share_id: int, permissions: int, ctx: Context) -> str:
|
||||
"""Update the permissions of an existing share.
|
||||
|
||||
@@ -17,7 +17,7 @@ def configure_tables_tools(mcp: FastMCP):
|
||||
title="List Tables",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("tables:read")
|
||||
@require_scopes("tables.read")
|
||||
@instrument_tool
|
||||
async def nc_tables_list_tables(ctx: Context) -> ListTablesResponse:
|
||||
"""List all tables available to the user"""
|
||||
@@ -30,7 +30,7 @@ def configure_tables_tools(mcp: FastMCP):
|
||||
title="Get Table Schema",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("tables:read")
|
||||
@require_scopes("tables.read")
|
||||
@instrument_tool
|
||||
async def nc_tables_get_schema(table_id: int, ctx: Context):
|
||||
"""Get the schema/structure of a specific table including columns and views"""
|
||||
@@ -41,7 +41,7 @@ def configure_tables_tools(mcp: FastMCP):
|
||||
title="Read Table Rows",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("tables:read")
|
||||
@require_scopes("tables.read")
|
||||
@instrument_tool
|
||||
async def nc_tables_read_table(
|
||||
table_id: int,
|
||||
@@ -57,7 +57,7 @@ def configure_tables_tools(mcp: FastMCP):
|
||||
title="Insert Table Row",
|
||||
annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("tables:write")
|
||||
@require_scopes("tables.write")
|
||||
@instrument_tool
|
||||
async def nc_tables_insert_row(table_id: int, data: dict, ctx: Context):
|
||||
"""Insert a new row into a table.
|
||||
@@ -71,7 +71,7 @@ def configure_tables_tools(mcp: FastMCP):
|
||||
title="Update Table Row",
|
||||
annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("tables:write")
|
||||
@require_scopes("tables.write")
|
||||
@instrument_tool
|
||||
async def nc_tables_update_row(row_id: int, data: dict, ctx: Context):
|
||||
"""Update an existing row in a table.
|
||||
@@ -87,7 +87,7 @@ def configure_tables_tools(mcp: FastMCP):
|
||||
destructiveHint=True, idempotentHint=True, openWorldHint=True
|
||||
),
|
||||
)
|
||||
@require_scopes("tables:write")
|
||||
@require_scopes("tables.write")
|
||||
@instrument_tool
|
||||
async def nc_tables_delete_row(row_id: int, ctx: Context):
|
||||
"""Delete a row from a table"""
|
||||
|
||||
@@ -25,7 +25,7 @@ def configure_webdav_tools(mcp: FastMCP):
|
||||
openWorldHint=True,
|
||||
),
|
||||
)
|
||||
@require_scopes("files:read")
|
||||
@require_scopes("files.read")
|
||||
@instrument_tool
|
||||
async def nc_webdav_list_directory(
|
||||
ctx: Context, path: str = ""
|
||||
@@ -65,7 +65,7 @@ def configure_webdav_tools(mcp: FastMCP):
|
||||
openWorldHint=True,
|
||||
),
|
||||
)
|
||||
@require_scopes("files:read")
|
||||
@require_scopes("files.read")
|
||||
@instrument_tool
|
||||
async def nc_webdav_read_file(path: str, ctx: Context):
|
||||
"""Read the content of a file from NextCloud.
|
||||
@@ -137,7 +137,7 @@ def configure_webdav_tools(mcp: FastMCP):
|
||||
openWorldHint=True,
|
||||
),
|
||||
)
|
||||
@require_scopes("files:write")
|
||||
@require_scopes("files.write")
|
||||
@instrument_tool
|
||||
async def nc_webdav_write_file(
|
||||
path: str, content: str, ctx: Context, content_type: str | None = None
|
||||
@@ -170,7 +170,7 @@ def configure_webdav_tools(mcp: FastMCP):
|
||||
openWorldHint=True,
|
||||
),
|
||||
)
|
||||
@require_scopes("files:write")
|
||||
@require_scopes("files.write")
|
||||
@instrument_tool
|
||||
async def nc_webdav_create_directory(path: str, ctx: Context):
|
||||
"""Create a directory in NextCloud.
|
||||
@@ -192,7 +192,7 @@ def configure_webdav_tools(mcp: FastMCP):
|
||||
openWorldHint=True,
|
||||
),
|
||||
)
|
||||
@require_scopes("files:write")
|
||||
@require_scopes("files.write")
|
||||
@instrument_tool
|
||||
async def nc_webdav_delete_resource(path: str, ctx: Context):
|
||||
"""Delete a file or directory in NextCloud.
|
||||
@@ -213,7 +213,7 @@ def configure_webdav_tools(mcp: FastMCP):
|
||||
openWorldHint=True,
|
||||
),
|
||||
)
|
||||
@require_scopes("files:write")
|
||||
@require_scopes("files.write")
|
||||
@instrument_tool
|
||||
async def nc_webdav_move_resource(
|
||||
source_path: str, destination_path: str, ctx: Context, overwrite: bool = False
|
||||
@@ -240,7 +240,7 @@ def configure_webdav_tools(mcp: FastMCP):
|
||||
openWorldHint=True,
|
||||
),
|
||||
)
|
||||
@require_scopes("files:write")
|
||||
@require_scopes("files.write")
|
||||
@instrument_tool
|
||||
async def nc_webdav_copy_resource(
|
||||
source_path: str, destination_path: str, ctx: Context, overwrite: bool = False
|
||||
@@ -267,7 +267,7 @@ def configure_webdav_tools(mcp: FastMCP):
|
||||
openWorldHint=True,
|
||||
),
|
||||
)
|
||||
@require_scopes("files:read")
|
||||
@require_scopes("files.read")
|
||||
@instrument_tool
|
||||
async def nc_webdav_search_files(
|
||||
ctx: Context,
|
||||
@@ -390,7 +390,7 @@ def configure_webdav_tools(mcp: FastMCP):
|
||||
openWorldHint=True,
|
||||
),
|
||||
)
|
||||
@require_scopes("files:read")
|
||||
@require_scopes("files.read")
|
||||
@instrument_tool
|
||||
async def nc_webdav_find_by_name(
|
||||
pattern: str, ctx: Context, scope: str = "", limit: int | None = None
|
||||
@@ -424,7 +424,7 @@ def configure_webdav_tools(mcp: FastMCP):
|
||||
openWorldHint=True,
|
||||
),
|
||||
)
|
||||
@require_scopes("files:read")
|
||||
@require_scopes("files.read")
|
||||
@instrument_tool
|
||||
async def nc_webdav_find_by_type(
|
||||
mime_type: str, ctx: Context, scope: str = "", limit: int | None = None
|
||||
@@ -458,7 +458,7 @@ def configure_webdav_tools(mcp: FastMCP):
|
||||
openWorldHint=True,
|
||||
),
|
||||
)
|
||||
@require_scopes("files:read")
|
||||
@require_scopes("files.read")
|
||||
@instrument_tool
|
||||
async def nc_webdav_list_favorites(
|
||||
ctx: Context, scope: str = "", limit: int | None = None
|
||||
|
||||
@@ -46,10 +46,10 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
# Scopes required for vector sync operations
|
||||
VECTOR_SYNC_SCOPES = [
|
||||
"notes:read",
|
||||
"files:read",
|
||||
"deck:read",
|
||||
# "news:read", # News app may not be installed
|
||||
"notes.read",
|
||||
"files.read",
|
||||
"deck.read",
|
||||
# "news.read", # News app may not be installed
|
||||
]
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user