Compare commits

..

3 Commits

Author SHA1 Message Date
github-actions[bot] 7052c19de0 bump: version 0.24.0 → 0.24.1 2025-11-04 12:28:13 +00:00
Chris Coutinho 921854ce87 Merge pull request #253 from cbcoutinho/renovate/mcp-1.x
fix(deps): update dependency mcp to >=1.20,<1.21
2025-11-04 13:27:46 +01:00
renovate-bot-cbcoutinho[bot] 3e988acb97 fix(deps): update dependency mcp to >=1.20,<1.21 2025-11-04 11:08:34 +00:00
23 changed files with 65 additions and 123 deletions
-3
View File
@@ -18,9 +18,6 @@ jobs:
- name: Linting - name: Linting
run: | run: |
uv run --frozen ruff check uv run --frozen ruff check
- name: Linting
run: |
uv run --frozen ty check -- nextcloud_mcp_server
integration-test: integration-test:
-6
View File
@@ -18,9 +18,3 @@ repos:
entry: uv run ruff format entry: uv run ruff format
language: system language: system
types: [python] types: [python]
- id: ty-check
name: ty-check
language: system
types: [python]
exclude: tests/.*
entry: uv run ty check
+6
View File
@@ -1,3 +1,9 @@
## v0.24.1 (2025-11-04)
### Fix
- **deps**: update dependency mcp to >=1.20,<1.21
## v0.24.0 (2025-11-04) ## v0.24.0 (2025-11-04)
### Feat ### Feat
@@ -31,9 +31,8 @@ else
fi fi
# Configure OIDC Identity Provider with dynamic client registration enabled # Configure OIDC Identity Provider with dynamic client registration enabled
php /var/www/html/occ config:app:set oidc dynamic_client_registration --value='true' # NOTE: String php /var/www/html/occ config:app:set oidc dynamic_client_registration --value='true'
php /var/www/html/occ config:app:set oidc proof_key_for_code_exchange --value=true --type=boolean php /var/www/html/occ config:app:set oidc proof_key_for_code_exchange --value=true --type=boolean
php /var/www/html/occ config:app:set oidc allow_user_settings --value='enabled'
php /var/www/html/occ config:app:set oidc default_token_type --value='jwt' php /var/www/html/occ config:app:set oidc default_token_type --value='jwt'
echo "OIDC app installed and configured successfully" echo "OIDC app installed and configured successfully"
+2 -2
View File
@@ -2,8 +2,8 @@ apiVersion: v2
name: nextcloud-mcp-server name: nextcloud-mcp-server
description: A Helm chart for Nextcloud MCP Server - enables AI assistants to interact with Nextcloud description: A Helm chart for Nextcloud MCP Server - enables AI assistants to interact with Nextcloud
type: application type: application
version: 0.24.0 version: 0.24.1
appVersion: "0.24.0" appVersion: "0.24.1"
keywords: keywords:
- nextcloud - nextcloud
- mcp - mcp
-1
View File
@@ -28,7 +28,6 @@ services:
depends_on: depends_on:
- redis - redis
- db - db
- keycloak
volumes: volumes:
- nextcloud:/var/www/html - nextcloud:/var/www/html
- ./app-hooks:/docker-entrypoint-hooks.d:ro - ./app-hooks:/docker-entrypoint-hooks.d:ro
+1 -1
View File
@@ -818,7 +818,7 @@ def get_app(transport: str = "sse", enabled_apps: list[str] | None = None):
return allowed_tools return allowed_tools
# Replace the tool manager's list_tools method # Replace the tool manager's list_tools method
mcp._tool_manager.list_tools = list_tools_filtered # type: ignore[method-assign] mcp._tool_manager.list_tools = list_tools_filtered
logger.info( logger.info(
"Dynamic tool filtering enabled for OAuth mode (JWT and Bearer tokens)" "Dynamic tool filtering enabled for OAuth mode (JWT and Bearer tokens)"
) )
@@ -34,7 +34,7 @@ class ProgressiveConsentTokenVerifier:
def __init__( def __init__(
self, self,
token_storage: RefreshTokenStorage | None, token_storage: RefreshTokenStorage,
token_broker: Optional[TokenBrokerService] = None, token_broker: Optional[TokenBrokerService] = None,
oidc_discovery_url: Optional[str] = None, oidc_discovery_url: Optional[str] = None,
nextcloud_host: Optional[str] = None, nextcloud_host: Optional[str] = None,
@@ -80,7 +80,7 @@ class ProgressiveConsentTokenVerifier:
# Create token broker if not provided # Create token broker if not provided
if token_broker: if token_broker:
self.token_broker = token_broker self.token_broker = token_broker
elif self.encryption_key and token_storage and self.nextcloud_host: elif self.encryption_key:
self.token_broker = TokenBrokerService( self.token_broker = TokenBrokerService(
storage=token_storage, storage=token_storage,
oidc_discovery_url=self.oidc_discovery_url, oidc_discovery_url=self.oidc_discovery_url,
@@ -89,12 +89,7 @@ class ProgressiveConsentTokenVerifier:
) )
else: else:
self.token_broker = None self.token_broker = None
if not self.encryption_key: logger.warning("Token broker not available - encryption key missing")
logger.warning("Token broker not available - encryption key missing")
elif not token_storage:
logger.warning("Token broker not available - token storage missing")
elif not self.nextcloud_host:
logger.warning("Token broker not available - nextcloud host missing")
async def verify_token(self, token: str) -> Optional[AccessToken]: async def verify_token(self, token: str) -> Optional[AccessToken]:
""" """
@@ -25,7 +25,7 @@ import logging
import os import os
import time import time
from pathlib import Path from pathlib import Path
from typing import Any, Optional from typing import Optional
import aiosqlite import aiosqlite
from cryptography.fernet import Fernet from cryptography.fernet import Fernet
@@ -283,7 +283,7 @@ class RefreshTokenStorage:
) )
async def store_user_profile( async def store_user_profile(
self, user_id: str, profile_data: dict[str, Any] self, user_id: str, profile_data: dict[str, any]
) -> None: ) -> None:
""" """
Store user profile data (cached from IdP userinfo endpoint). Store user profile data (cached from IdP userinfo endpoint).
@@ -314,7 +314,7 @@ class RefreshTokenStorage:
logger.debug(f"Cached user profile for {user_id}") logger.debug(f"Cached user profile for {user_id}")
async def get_user_profile(self, user_id: str) -> Optional[dict[str, Any]]: async def get_user_profile(self, user_id: str) -> Optional[dict[str, any]]:
""" """
Retrieve cached user profile data. Retrieve cached user profile data.
@@ -3,7 +3,7 @@
import logging import logging
import os import os
from functools import wraps from functools import wraps
from typing import Any, Callable from typing import Callable
from mcp.server.auth.middleware.auth_context import get_access_token from mcp.server.auth.middleware.auth_context import get_access_token
from mcp.server.auth.provider import AccessToken from mcp.server.auth.provider import AccessToken
@@ -88,18 +88,15 @@ def require_scopes(*required_scopes: str):
ScopeAuthorizationError: If required scopes are not present in the access token ScopeAuthorizationError: If required scopes are not present in the access token
""" """
def decorator(func: Callable) -> Callable: def decorator(func: Callable):
# Store scope requirements as function metadata for dynamic filtering # Store scope requirements as function metadata for dynamic filtering
func._required_scopes = list(required_scopes) # type: ignore[attr-defined] func._required_scopes = list(required_scopes) # type: ignore
# Get function name for logging (works for any callable)
func_name = getattr(func, "__name__", repr(func))
# Find which parameter receives the Context (FastMCP injects it by name) # Find which parameter receives the Context (FastMCP injects it by name)
context_param_name = find_context_parameter(func) context_param_name = find_context_parameter(func)
@wraps(func) @wraps(func)
async def wrapper(*args: Any, **kwargs: Any) -> Any: async def wrapper(*args, **kwargs):
# Extract context from kwargs (where FastMCP injected it) # Extract context from kwargs (where FastMCP injected it)
ctx: Context | None = ( ctx: Context | None = (
kwargs.get(context_param_name) if context_param_name else None kwargs.get(context_param_name) if context_param_name else None
@@ -109,7 +106,7 @@ def require_scopes(*required_scopes: str):
# No context parameter found - likely BasicAuth mode # No context parameter found - likely BasicAuth mode
# In BasicAuth mode, all operations are allowed # In BasicAuth mode, all operations are allowed
logger.debug( logger.debug(
f"No context parameter for {func_name} - allowing (BasicAuth mode)" f"No context parameter for {func.__name__} - allowing (BasicAuth mode)"
) )
return await func(*args, **kwargs) return await func(*args, **kwargs)
@@ -122,7 +119,7 @@ def require_scopes(*required_scopes: str):
# Not in OAuth mode (BasicAuth or no auth) # Not in OAuth mode (BasicAuth or no auth)
# In BasicAuth mode, all operations are allowed # In BasicAuth mode, all operations are allowed
logger.debug( logger.debug(
f"No access token present for {func_name} - allowing (BasicAuth mode)" f"No access token present for {func.__name__} - allowing (BasicAuth mode)"
) )
return await func(*args, **kwargs) return await func(*args, **kwargs)
@@ -175,7 +172,7 @@ def require_scopes(*required_scopes: str):
if not has_nextcloud_scopes: if not has_nextcloud_scopes:
error_msg = ( error_msg = (
f"Access denied to {func_name}: " f"Access denied to {func.__name__}: "
f"Nextcloud resource access not provisioned. " f"Nextcloud resource access not provisioned. "
f"Please run the 'provision_nextcloud_access' tool first." f"Please run the 'provision_nextcloud_access' tool first."
) )
@@ -186,7 +183,7 @@ def require_scopes(*required_scopes: str):
missing_scopes = required_scopes_set - token_scopes missing_scopes = required_scopes_set - token_scopes
if missing_scopes: if missing_scopes:
error_msg = ( error_msg = (
f"Access denied to {func_name}: " f"Access denied to {func.__name__}: "
f"Missing required scopes: {', '.join(sorted(missing_scopes))}. " f"Missing required scopes: {', '.join(sorted(missing_scopes))}. "
f"Token has scopes: {', '.join(sorted(token_scopes)) if token_scopes else 'none'}" f"Token has scopes: {', '.join(sorted(token_scopes)) if token_scopes else 'none'}"
) )
@@ -195,7 +192,7 @@ def require_scopes(*required_scopes: str):
# All required scopes present - allow execution # All required scopes present - allow execution
logger.debug( logger.debug(
f"Scope authorization passed for {func_name}: {required_scopes}" f"Scope authorization passed for {func.__name__}: {required_scopes}"
) )
return await func(*args, **kwargs) return await func(*args, **kwargs)
+1 -1
View File
@@ -68,7 +68,7 @@ class TokenCache:
logger.debug(f"Using cached token for user {user_id}") logger.debug(f"Using cached token for user {user_id}")
return token return token
async def set(self, user_id: str, token: str, expires_in: int | None = None): async def set(self, user_id: str, token: str, expires_in: int = None):
"""Store token in cache.""" """Store token in cache."""
async with self._lock: async with self._lock:
# Use provided expiry or default TTL # Use provided expiry or default TTL
+1 -4
View File
@@ -114,8 +114,7 @@ class TokenExchangeService:
if not self.oidc_discovery_url: if not self.oidc_discovery_url:
# Fallback to Nextcloud OIDC if no discovery URL # Fallback to Nextcloud OIDC if no discovery URL
self.oidc_discovery_url = urljoin( self.oidc_discovery_url = urljoin(
self.nextcloud_host, # type: ignore[arg-type] self.nextcloud_host, "/.well-known/openid-configuration"
"/.well-known/openid-configuration",
) )
try: try:
@@ -364,7 +363,6 @@ class TokenExchangeService:
True if provisioned, False otherwise True if provisioned, False otherwise
""" """
await self._ensure_storage() await self._ensure_storage()
assert self.storage is not None # _ensure_storage() ensures this
token_data = await self.storage.get_refresh_token(user_id) token_data = await self.storage.get_refresh_token(user_id)
return token_data is not None return token_data is not None
@@ -378,7 +376,6 @@ class TokenExchangeService:
Refresh token if found, None otherwise Refresh token if found, None otherwise
""" """
await self._ensure_storage() await self._ensure_storage()
assert self.storage is not None # _ensure_storage() ensures this
token_data = await self.storage.get_refresh_token(user_id) token_data = await self.storage.get_refresh_token(user_id)
if token_data: if token_data:
return token_data.get("refresh_token") return token_data.get("refresh_token")
+1 -2
View File
@@ -165,7 +165,6 @@ class NextcloudTokenVerifier(TokenVerifier):
""" """
try: try:
# Get signing key from JWKS # Get signing key from JWKS
assert self._jwks_client is not None # Caller should check before calling
signing_key = self._jwks_client.get_signing_key_from_jwt(token) signing_key = self._jwks_client.get_signing_key_from_jwt(token)
# Verify and decode JWT # Verify and decode JWT
@@ -258,7 +257,7 @@ class NextcloudTokenVerifier(TokenVerifier):
try: try:
# Introspection requires client authentication # Introspection requires client authentication
response = await self._client.post( response = await self._client.post(
self.introspection_uri, # type: ignore self.introspection_uri,
data={"token": token}, data={"token": token},
auth=(self.client_id, self.client_secret), auth=(self.client_id, self.client_secret),
) )
+12 -18
View File
@@ -100,7 +100,7 @@ class CalendarClient:
# Use custom PROPFIND with CalendarServer namespace (cs:) for calendar-color. # Use custom PROPFIND with CalendarServer namespace (cs:) for calendar-color.
# caldav library's nsmap lacks "CS" namespace, and its CalendarColor uses # caldav library's nsmap lacks "CS" namespace, and its CalendarColor uses
# Apple iCal namespace which Nextcloud doesn't recognize. # Apple iCal namespace which Nextcloud doesn't recognize.
from lxml import etree # type: ignore[import-untyped] from lxml import etree
propfind_body = """<?xml version="1.0" encoding="utf-8"?> propfind_body = """<?xml version="1.0" encoding="utf-8"?>
<d:propfind xmlns:d="DAV:" xmlns:cs="http://calendarserver.org/ns/" xmlns:c="urn:ietf:params:xml:ns:caldav"> <d:propfind xmlns:d="DAV:" xmlns:cs="http://calendarserver.org/ns/" xmlns:c="urn:ietf:params:xml:ns:caldav">
@@ -261,12 +261,11 @@ class CalendarClient:
result = [] result = []
for event in events: for event in events:
await event.load(only_if_unloaded=True) await event.load(only_if_unloaded=True)
if event.data: event_dict = self._parse_ical_event(event.data)
event_dict = self._parse_ical_event(event.data) if event_dict:
if event_dict: event_dict["href"] = str(event.url)
event_dict["href"] = str(event.url) event_dict["etag"] = ""
event_dict["etag"] = "" result.append(event_dict)
result.append(event_dict)
if len(result) >= limit: if len(result) >= limit:
break break
@@ -315,8 +314,8 @@ class CalendarClient:
await event.load(only_if_unloaded=True) await event.load(only_if_unloaded=True)
# Merge updates into existing iCal data # Merge updates into existing iCal data
updated_ical = self._merge_ical_properties(event.data, event_data, event_uid) # type: ignore[arg-type] updated_ical = self._merge_ical_properties(event.data, event_data, event_uid)
event.data = updated_ical # type: ignore[misc] event.data = updated_ical
await event.save() await event.save()
@@ -350,7 +349,7 @@ class CalendarClient:
event = await calendar.event_by_uid(event_uid) event = await calendar.event_by_uid(event_uid)
await event.load(only_if_unloaded=True) await event.load(only_if_unloaded=True)
event_data = self._parse_ical_event(event.data) if event.data else None # type: ignore[arg-type] event_data = self._parse_ical_event(event.data)
if not event_data: if not event_data:
raise ValueError(f"Failed to parse event data for {event_uid}") raise ValueError(f"Failed to parse event data for {event_uid}")
@@ -417,10 +416,7 @@ class CalendarClient:
# Only load if data not already present from REPORT response # Only load if data not already present from REPORT response
# This avoids 404 errors for virtual calendars (e.g., Deck boards) # This avoids 404 errors for virtual calendars (e.g., Deck boards)
await todo.load(only_if_unloaded=True) await todo.load(only_if_unloaded=True)
if todo.data: todo_dict = self._parse_ical_todo(todo.data)
todo_dict = self._parse_ical_todo(todo.data) # type: ignore[arg-type]
else:
continue
if todo_dict: if todo_dict:
todo_dict["href"] = str(todo.url) todo_dict["href"] = str(todo.url)
todo_dict["etag"] = "" todo_dict["etag"] = ""
@@ -474,14 +470,12 @@ class CalendarClient:
await todo.load(only_if_unloaded=True) await todo.load(only_if_unloaded=True)
logger.debug( logger.debug(
f"Loaded todo {todo_uid}, current data length: {len(todo.data)}" # type: ignore f"Loaded todo {todo_uid}, current data length: {len(todo.data)}"
) )
# Merge updates into existing iCal data # Merge updates into existing iCal data
updated_ical = self._merge_ical_todo_properties( updated_ical = self._merge_ical_todo_properties(
todo.data, # type: ignore[arg-type] todo.data, todo_data, todo_uid
todo_data,
todo_uid,
) )
logger.debug(f"Merged iCal data length: {len(updated_ical)}") logger.debug(f"Merged iCal data length: {len(updated_ical)}")
logger.debug(f"Updated iCal content:\n{updated_ical}") logger.debug(f"Updated iCal content:\n{updated_ical}")
+2 -2
View File
@@ -124,7 +124,7 @@ class ContactsClient(BaseNextcloudClient):
carddav_path = self._get_carddav_base_path() carddav_path = self._get_carddav_base_path()
url = f"{carddav_path}/{addressbook}/{uid}.vcf" url = f"{carddav_path}/{addressbook}/{uid}.vcf"
contact = Contact(fn=contact_data.get("fn"), uid=uid) # type: ignore contact = Contact(fn=contact_data.get("fn"), uid=uid)
if "email" in contact_data: if "email" in contact_data:
contact.email = [{"value": contact_data["email"], "type": ["HOME"]}] contact.email = [{"value": contact_data["email"], "type": ["HOME"]}]
if "tel" in contact_data: if "tel" in contact_data:
@@ -174,7 +174,7 @@ class ContactsClient(BaseNextcloudClient):
) )
else: else:
# Fallback to creating new vCard if we couldn't get existing # Fallback to creating new vCard if we couldn't get existing
contact = Contact(fn=contact_data.get("fn"), uid=uid) # type: ignore contact = Contact(fn=contact_data.get("fn"), uid=uid)
if "email" in contact_data: if "email" in contact_data:
contact.email = [{"value": contact_data["email"], "type": ["HOME"]}] contact.email = [{"value": contact_data["email"], "type": ["HOME"]}]
if "tel" in contact_data: if "tel" in contact_data:
@@ -12,7 +12,7 @@ logger = logging.getLogger(__name__)
try: try:
import io import io
import pytesseract # type: ignore import pytesseract
from PIL import Image from PIL import Image
TESSERACT_AVAILABLE = True TESSERACT_AVAILABLE = True
@@ -112,10 +112,10 @@ class UnstructuredProcessor(DocumentProcessor):
f"Processing document with unstructured... ({elapsed}s elapsed)" f"Processing document with unstructured... ({elapsed}s elapsed)"
) )
try: try:
await progress_callback( # type: ignore await progress_callback(
progress=float(elapsed), # type: ignore progress=float(elapsed),
total=None, # Unknown total duration # type: ignore total=None, # Unknown total duration
message=message, # type: ignore message=message,
) )
logger.debug(f"Progress update sent: {elapsed}s elapsed") logger.debug(f"Progress update sent: {elapsed}s elapsed")
except Exception as e: except Exception as e:
@@ -293,7 +293,7 @@ class UnstructuredProcessor(DocumentProcessor):
self._run_progress_poller, stop_event, progress_callback, start_time self._run_progress_poller, stop_event, progress_callback, start_time
) )
return result # type: ignore return result
async def health_check(self) -> bool: async def health_check(self) -> bool:
"""Check if Unstructured API is available. """Check if Unstructured API is available.
+3 -3
View File
@@ -191,7 +191,7 @@ def configure_cookbook_tools(mcp: FastMCP):
recipe_yield: int | None = None, recipe_yield: int | None = None,
category: str | None = None, category: str | None = None,
keywords: str | None = None, keywords: str | None = None,
ctx: Context = None, # type: ignore ctx: Context = None,
) -> CreateRecipeResponse: ) -> CreateRecipeResponse:
"""Create a new recipe. """Create a new recipe.
@@ -271,7 +271,7 @@ def configure_cookbook_tools(mcp: FastMCP):
recipe_yield: int | None = None, recipe_yield: int | None = None,
category: str | None = None, category: str | None = None,
keywords: str | None = None, keywords: str | None = None,
ctx: Context = None, # type: ignore ctx: Context = None,
) -> UpdateRecipeResponse: ) -> UpdateRecipeResponse:
"""Update an existing recipe. """Update an existing recipe.
@@ -544,7 +544,7 @@ def configure_cookbook_tools(mcp: FastMCP):
folder: str | None = None, folder: str | None = None,
update_interval: int | None = None, update_interval: int | None = None,
print_image: bool | None = None, print_image: bool | None = None,
ctx: Context = None, # type: ignore ctx: Context = None,
) -> ReindexResponse: ) -> ReindexResponse:
"""Set Cookbook app configuration. """Set Cookbook app configuration.
+1 -1
View File
@@ -331,7 +331,7 @@ def configure_notes_tools(mcp: FastMCP):
content, mime_type = await client.webdav.get_note_attachment( content, mime_type = await client.webdav.get_note_attachment(
note_id=note_id, filename=attachment_filename note_id=note_id, filename=attachment_filename
) )
return { # type: ignore return {
"uri": f"nc://Notes/{note_id}/attachments/{attachment_filename}", "uri": f"nc://Notes/{note_id}/attachments/{attachment_filename}",
"mimeType": mime_type, "mimeType": mime_type,
"data": content, "data": content,
+4 -4
View File
@@ -163,7 +163,7 @@ async def provision_nextcloud_access(
if not user_id: if not user_id:
# Get the authorization token from context # Get the authorization token from context
if hasattr(ctx, "authorization") and ctx.authorization: if hasattr(ctx, "authorization") and ctx.authorization:
token = ctx.authorization.token # type: ignore token = ctx.authorization.token
# Decode token to get user info # Decode token to get user info
try: try:
import jwt import jwt
@@ -304,7 +304,7 @@ async def revoke_nextcloud_access(
# Get user ID from context if not provided # Get user ID from context if not provided
if not user_id: if not user_id:
user_id = ( user_id = (
ctx.context.get("user_id", "default_user") # type: ignore ctx.context.get("user_id", "default_user")
if hasattr(ctx, "context") if hasattr(ctx, "context")
else "default_user" else "default_user"
) )
@@ -334,7 +334,7 @@ async def revoke_nextcloud_access(
"OIDC_DISCOVERY_URL", "OIDC_DISCOVERY_URL",
f"{os.getenv('NEXTCLOUD_HOST')}/.well-known/openid-configuration", f"{os.getenv('NEXTCLOUD_HOST')}/.well-known/openid-configuration",
), ),
nextcloud_host=os.getenv("NEXTCLOUD_HOST"), # type: ignore nextcloud_host=os.getenv("NEXTCLOUD_HOST"),
encryption_key=encryption_key, encryption_key=encryption_key,
) )
@@ -382,7 +382,7 @@ async def check_provisioning_status(
# Get user ID from context if not provided # Get user ID from context if not provided
if not user_id: if not user_id:
user_id = ( user_id = (
ctx.context.get("user_id", "default_user") # type: ignore ctx.context.get("user_id", "default_user")
if hasattr(ctx, "context") if hasattr(ctx, "context")
else "default_user" else "default_user"
) )
+2 -3
View File
@@ -1,6 +1,6 @@
[project] [project]
name = "nextcloud-mcp-server" name = "nextcloud-mcp-server"
version = "0.24.0" version = "0.24.1"
description = "Model Context Protocol (MCP) server for Nextcloud integration - enables AI assistants to interact with Nextcloud data" description = "Model Context Protocol (MCP) server for Nextcloud integration - enables AI assistants to interact with Nextcloud data"
authors = [ authors = [
{name = "Chris Coutinho", email = "chris@coutinho.io"} {name = "Chris Coutinho", email = "chris@coutinho.io"}
@@ -10,7 +10,7 @@ license = {text = "AGPL-3.0-only"}
requires-python = ">=3.11" requires-python = ">=3.11"
keywords = ["nextcloud", "mcp", "model-context-protocol", "llm", "ai", "claude", "webdav", "caldav", "carddav"] keywords = ["nextcloud", "mcp", "model-context-protocol", "llm", "ai", "claude", "webdav", "caldav", "carddav"]
dependencies = [ dependencies = [
"mcp[cli] (>=1.19,<1.20)", "mcp[cli] (>=1.20,<1.21)",
"httpx (>=0.28.1,<0.29.0)", "httpx (>=0.28.1,<0.29.0)",
"pillow (>=12.0.0,<12.1.0)", "pillow (>=12.0.0,<12.1.0)",
"icalendar (>=6.0.0,<7.0.0)", "icalendar (>=6.0.0,<7.0.0)",
@@ -102,7 +102,6 @@ dev = [
"pytest-timeout>=2.3.1", "pytest-timeout>=2.3.1",
"ruff>=0.11.13", "ruff>=0.11.13",
"reportlab>=4.0.0", "reportlab>=4.0.0",
"ty>=0.0.1a25",
] ]
[project.scripts] [project.scripts]
Generated
+6 -40
View File
@@ -501,8 +501,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/1f/8e/abdd3f14d735b2929290a018ecf133c901be4874b858dd1c604b9319f064/greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8", size = 587684, upload-time = "2025-08-07T13:18:25.164Z" }, { url = "https://files.pythonhosted.org/packages/1f/8e/abdd3f14d735b2929290a018ecf133c901be4874b858dd1c604b9319f064/greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8", size = 587684, upload-time = "2025-08-07T13:18:25.164Z" },
{ url = "https://files.pythonhosted.org/packages/5d/65/deb2a69c3e5996439b0176f6651e0052542bb6c8f8ec2e3fba97c9768805/greenlet-3.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52", size = 1116647, upload-time = "2025-08-07T13:42:38.655Z" }, { url = "https://files.pythonhosted.org/packages/5d/65/deb2a69c3e5996439b0176f6651e0052542bb6c8f8ec2e3fba97c9768805/greenlet-3.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52", size = 1116647, upload-time = "2025-08-07T13:42:38.655Z" },
{ url = "https://files.pythonhosted.org/packages/3f/cc/b07000438a29ac5cfb2194bfc128151d52f333cee74dd7dfe3fb733fc16c/greenlet-3.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa", size = 1142073, upload-time = "2025-08-07T13:18:21.737Z" }, { url = "https://files.pythonhosted.org/packages/3f/cc/b07000438a29ac5cfb2194bfc128151d52f333cee74dd7dfe3fb733fc16c/greenlet-3.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa", size = 1142073, upload-time = "2025-08-07T13:18:21.737Z" },
{ url = "https://files.pythonhosted.org/packages/67/24/28a5b2fa42d12b3d7e5614145f0bd89714c34c08be6aabe39c14dd52db34/greenlet-3.2.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c9c6de1940a7d828635fbd254d69db79e54619f165ee7ce32fda763a9cb6a58c", size = 1548385, upload-time = "2025-11-04T12:42:11.067Z" },
{ url = "https://files.pythonhosted.org/packages/6a/05/03f2f0bdd0b0ff9a4f7b99333d57b53a7709c27723ec8123056b084e69cd/greenlet-3.2.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03c5136e7be905045160b1b9fdca93dd6727b180feeafda6818e6496434ed8c5", size = 1613329, upload-time = "2025-11-04T12:42:12.928Z" },
{ url = "https://files.pythonhosted.org/packages/d8/0f/30aef242fcab550b0b3520b8e3561156857c94288f0332a79928c31a52cf/greenlet-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9", size = 299100, upload-time = "2025-08-07T13:44:12.287Z" }, { url = "https://files.pythonhosted.org/packages/d8/0f/30aef242fcab550b0b3520b8e3561156857c94288f0332a79928c31a52cf/greenlet-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9", size = 299100, upload-time = "2025-08-07T13:44:12.287Z" },
{ url = "https://files.pythonhosted.org/packages/44/69/9b804adb5fd0671f367781560eb5eb586c4d495277c93bde4307b9e28068/greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd", size = 274079, upload-time = "2025-08-07T13:15:45.033Z" }, { url = "https://files.pythonhosted.org/packages/44/69/9b804adb5fd0671f367781560eb5eb586c4d495277c93bde4307b9e28068/greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd", size = 274079, upload-time = "2025-08-07T13:15:45.033Z" },
{ url = "https://files.pythonhosted.org/packages/46/e9/d2a80c99f19a153eff70bc451ab78615583b8dac0754cfb942223d2c1a0d/greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb", size = 640997, upload-time = "2025-08-07T13:42:56.234Z" }, { url = "https://files.pythonhosted.org/packages/46/e9/d2a80c99f19a153eff70bc451ab78615583b8dac0754cfb942223d2c1a0d/greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb", size = 640997, upload-time = "2025-08-07T13:42:56.234Z" },
@@ -512,8 +510,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/19/0d/6660d55f7373b2ff8152401a83e02084956da23ae58cddbfb0b330978fe9/greenlet-3.2.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0", size = 607586, upload-time = "2025-08-07T13:18:28.544Z" }, { url = "https://files.pythonhosted.org/packages/19/0d/6660d55f7373b2ff8152401a83e02084956da23ae58cddbfb0b330978fe9/greenlet-3.2.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0", size = 607586, upload-time = "2025-08-07T13:18:28.544Z" },
{ url = "https://files.pythonhosted.org/packages/8e/1a/c953fdedd22d81ee4629afbb38d2f9d71e37d23caace44775a3a969147d4/greenlet-3.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0", size = 1123281, upload-time = "2025-08-07T13:42:39.858Z" }, { url = "https://files.pythonhosted.org/packages/8e/1a/c953fdedd22d81ee4629afbb38d2f9d71e37d23caace44775a3a969147d4/greenlet-3.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0", size = 1123281, upload-time = "2025-08-07T13:42:39.858Z" },
{ url = "https://files.pythonhosted.org/packages/3f/c7/12381b18e21aef2c6bd3a636da1088b888b97b7a0362fac2e4de92405f97/greenlet-3.2.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f", size = 1151142, upload-time = "2025-08-07T13:18:22.981Z" }, { url = "https://files.pythonhosted.org/packages/3f/c7/12381b18e21aef2c6bd3a636da1088b888b97b7a0362fac2e4de92405f97/greenlet-3.2.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f", size = 1151142, upload-time = "2025-08-07T13:18:22.981Z" },
{ url = "https://files.pythonhosted.org/packages/27/45/80935968b53cfd3f33cf99ea5f08227f2646e044568c9b1555b58ffd61c2/greenlet-3.2.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ee7a6ec486883397d70eec05059353b8e83eca9168b9f3f9a361971e77e0bcd0", size = 1564846, upload-time = "2025-11-04T12:42:15.191Z" },
{ url = "https://files.pythonhosted.org/packages/69/02/b7c30e5e04752cb4db6202a3858b149c0710e5453b71a3b2aec5d78a1aab/greenlet-3.2.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:326d234cbf337c9c3def0676412eb7040a35a768efc92504b947b3e9cfc7543d", size = 1633814, upload-time = "2025-11-04T12:42:17.175Z" },
{ url = "https://files.pythonhosted.org/packages/e9/08/b0814846b79399e585f974bbeebf5580fbe59e258ea7be64d9dfb253c84f/greenlet-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02", size = 299899, upload-time = "2025-08-07T13:38:53.448Z" }, { url = "https://files.pythonhosted.org/packages/e9/08/b0814846b79399e585f974bbeebf5580fbe59e258ea7be64d9dfb253c84f/greenlet-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02", size = 299899, upload-time = "2025-08-07T13:38:53.448Z" },
{ url = "https://files.pythonhosted.org/packages/49/e8/58c7f85958bda41dafea50497cbd59738c5c43dbbea5ee83d651234398f4/greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31", size = 272814, upload-time = "2025-08-07T13:15:50.011Z" }, { url = "https://files.pythonhosted.org/packages/49/e8/58c7f85958bda41dafea50497cbd59738c5c43dbbea5ee83d651234398f4/greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31", size = 272814, upload-time = "2025-08-07T13:15:50.011Z" },
{ url = "https://files.pythonhosted.org/packages/62/dd/b9f59862e9e257a16e4e610480cfffd29e3fae018a68c2332090b53aac3d/greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945", size = 641073, upload-time = "2025-08-07T13:42:57.23Z" }, { url = "https://files.pythonhosted.org/packages/62/dd/b9f59862e9e257a16e4e610480cfffd29e3fae018a68c2332090b53aac3d/greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945", size = 641073, upload-time = "2025-08-07T13:42:57.23Z" },
@@ -523,8 +519,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/ee/43/3cecdc0349359e1a527cbf2e3e28e5f8f06d3343aaf82ca13437a9aa290f/greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671", size = 610497, upload-time = "2025-08-07T13:18:31.636Z" }, { url = "https://files.pythonhosted.org/packages/ee/43/3cecdc0349359e1a527cbf2e3e28e5f8f06d3343aaf82ca13437a9aa290f/greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671", size = 610497, upload-time = "2025-08-07T13:18:31.636Z" },
{ url = "https://files.pythonhosted.org/packages/b8/19/06b6cf5d604e2c382a6f31cafafd6f33d5dea706f4db7bdab184bad2b21d/greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b", size = 1121662, upload-time = "2025-08-07T13:42:41.117Z" }, { url = "https://files.pythonhosted.org/packages/b8/19/06b6cf5d604e2c382a6f31cafafd6f33d5dea706f4db7bdab184bad2b21d/greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b", size = 1121662, upload-time = "2025-08-07T13:42:41.117Z" },
{ url = "https://files.pythonhosted.org/packages/a2/15/0d5e4e1a66fab130d98168fe984c509249c833c1a3c16806b90f253ce7b9/greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae", size = 1149210, upload-time = "2025-08-07T13:18:24.072Z" }, { url = "https://files.pythonhosted.org/packages/a2/15/0d5e4e1a66fab130d98168fe984c509249c833c1a3c16806b90f253ce7b9/greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae", size = 1149210, upload-time = "2025-08-07T13:18:24.072Z" },
{ url = "https://files.pythonhosted.org/packages/1c/53/f9c440463b3057485b8594d7a638bed53ba531165ef0ca0e6c364b5cc807/greenlet-3.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e343822feb58ac4d0a1211bd9399de2b3a04963ddeec21530fc426cc121f19b", size = 1564759, upload-time = "2025-11-04T12:42:19.395Z" },
{ url = "https://files.pythonhosted.org/packages/47/e4/3bb4240abdd0a8d23f4f88adec746a3099f0d86bfedb623f063b2e3b4df0/greenlet-3.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca7f6f1f2649b89ce02f6f229d7c19f680a6238af656f61e0115b24857917929", size = 1634288, upload-time = "2025-11-04T12:42:21.174Z" },
{ url = "https://files.pythonhosted.org/packages/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b", size = 299685, upload-time = "2025-08-07T13:24:38.824Z" }, { url = "https://files.pythonhosted.org/packages/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b", size = 299685, upload-time = "2025-08-07T13:24:38.824Z" },
{ url = "https://files.pythonhosted.org/packages/22/5c/85273fd7cc388285632b0498dbbab97596e04b154933dfe0f3e68156c68c/greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0", size = 273586, upload-time = "2025-08-07T13:16:08.004Z" }, { url = "https://files.pythonhosted.org/packages/22/5c/85273fd7cc388285632b0498dbbab97596e04b154933dfe0f3e68156c68c/greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0", size = 273586, upload-time = "2025-08-07T13:16:08.004Z" },
{ url = "https://files.pythonhosted.org/packages/d1/75/10aeeaa3da9332c2e761e4c50d4c3556c21113ee3f0afa2cf5769946f7a3/greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f", size = 686346, upload-time = "2025-08-07T13:42:59.944Z" }, { url = "https://files.pythonhosted.org/packages/d1/75/10aeeaa3da9332c2e761e4c50d4c3556c21113ee3f0afa2cf5769946f7a3/greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f", size = 686346, upload-time = "2025-08-07T13:42:59.944Z" },
@@ -532,8 +526,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/dc/8b/29aae55436521f1d6f8ff4e12fb676f3400de7fcf27fccd1d4d17fd8fecd/greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1", size = 694659, upload-time = "2025-08-07T13:53:17.759Z" }, { url = "https://files.pythonhosted.org/packages/dc/8b/29aae55436521f1d6f8ff4e12fb676f3400de7fcf27fccd1d4d17fd8fecd/greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1", size = 694659, upload-time = "2025-08-07T13:53:17.759Z" },
{ url = "https://files.pythonhosted.org/packages/92/2e/ea25914b1ebfde93b6fc4ff46d6864564fba59024e928bdc7de475affc25/greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735", size = 695355, upload-time = "2025-08-07T13:18:34.517Z" }, { url = "https://files.pythonhosted.org/packages/92/2e/ea25914b1ebfde93b6fc4ff46d6864564fba59024e928bdc7de475affc25/greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735", size = 695355, upload-time = "2025-08-07T13:18:34.517Z" },
{ url = "https://files.pythonhosted.org/packages/72/60/fc56c62046ec17f6b0d3060564562c64c862948c9d4bc8aa807cf5bd74f4/greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337", size = 657512, upload-time = "2025-08-07T13:18:33.969Z" }, { url = "https://files.pythonhosted.org/packages/72/60/fc56c62046ec17f6b0d3060564562c64c862948c9d4bc8aa807cf5bd74f4/greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337", size = 657512, upload-time = "2025-08-07T13:18:33.969Z" },
{ url = "https://files.pythonhosted.org/packages/23/6e/74407aed965a4ab6ddd93a7ded3180b730d281c77b765788419484cdfeef/greenlet-3.2.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2917bdf657f5859fbf3386b12d68ede4cf1f04c90c3a6bc1f013dd68a22e2269", size = 1612508, upload-time = "2025-11-04T12:42:23.427Z" },
{ url = "https://files.pythonhosted.org/packages/0d/da/343cd760ab2f92bac1845ca07ee3faea9fe52bee65f7bcb19f16ad7de08b/greenlet-3.2.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:015d48959d4add5d6c9f6c5210ee3803a830dce46356e3bc326d6776bde54681", size = 1680760, upload-time = "2025-11-04T12:42:25.341Z" },
{ url = "https://files.pythonhosted.org/packages/e3/a5/6ddab2b4c112be95601c13428db1d8b6608a8b6039816f2ba09c346c08fc/greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01", size = 303425, upload-time = "2025-08-07T13:32:27.59Z" }, { url = "https://files.pythonhosted.org/packages/e3/a5/6ddab2b4c112be95601c13428db1d8b6608a8b6039816f2ba09c346c08fc/greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01", size = 303425, upload-time = "2025-08-07T13:32:27.59Z" },
] ]
@@ -937,7 +929,7 @@ wheels = [
[[package]] [[package]]
name = "mcp" name = "mcp"
version = "1.19.0" version = "1.20.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "anyio" }, { name = "anyio" },
@@ -946,15 +938,16 @@ dependencies = [
{ name = "jsonschema" }, { name = "jsonschema" },
{ name = "pydantic" }, { name = "pydantic" },
{ name = "pydantic-settings" }, { name = "pydantic-settings" },
{ name = "pyjwt", extra = ["crypto"] },
{ name = "python-multipart" }, { name = "python-multipart" },
{ name = "pywin32", marker = "sys_platform == 'win32'" }, { name = "pywin32", marker = "sys_platform == 'win32'" },
{ name = "sse-starlette" }, { name = "sse-starlette" },
{ name = "starlette" }, { name = "starlette" },
{ name = "uvicorn", marker = "sys_platform != 'emscripten'" }, { name = "uvicorn", marker = "sys_platform != 'emscripten'" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/69/2b/916852a5668f45d8787378461eaa1244876d77575ffef024483c94c0649c/mcp-1.19.0.tar.gz", hash = "sha256:213de0d3cd63f71bc08ffe9cc8d4409cc87acffd383f6195d2ce0457c021b5c1", size = 444163, upload-time = "2025-10-24T01:11:15.839Z" } sdist = { url = "https://files.pythonhosted.org/packages/f8/22/fae38092e6c2995c03232635028510d77e7decff31b4ae79dfa0ba99c635/mcp-1.20.0.tar.gz", hash = "sha256:9ccc09eaadbfbcbbdab1c9723cfe2e0d1d9e324d7d3ce7e332ef90b09ed35177", size = 451377, upload-time = "2025-10-30T22:14:53.421Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/ce/a3/3e71a875a08b6a830b88c40bc413bff01f1650f1efe8a054b5e90a9d4f56/mcp-1.19.0-py3-none-any.whl", hash = "sha256:f5907fe1c0167255f916718f376d05f09a830a215327a3ccdd5ec8a519f2e572", size = 170105, upload-time = "2025-10-24T01:11:14.151Z" }, { url = "https://files.pythonhosted.org/packages/df/00/76fc92f4892d47fecb37131d0e95ea69259f077d84c68f6793a0d96cfe80/mcp-1.20.0-py3-none-any.whl", hash = "sha256:d0dc06f93653f7432ff89f694721c87f79876b6f93741bf628ad1e48f7ac5e5d", size = 173136, upload-time = "2025-10-30T22:14:51.078Z" },
] ]
[package.optional-dependencies] [package.optional-dependencies]
@@ -974,7 +967,7 @@ wheels = [
[[package]] [[package]]
name = "nextcloud-mcp-server" name = "nextcloud-mcp-server"
version = "0.24.0" version = "0.24.1"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "aiosqlite" }, { name = "aiosqlite" },
@@ -1002,7 +995,6 @@ dev = [
{ name = "pytest-timeout" }, { name = "pytest-timeout" },
{ name = "reportlab" }, { name = "reportlab" },
{ name = "ruff" }, { name = "ruff" },
{ name = "ty" },
] ]
[package.metadata] [package.metadata]
@@ -1013,7 +1005,7 @@ requires-dist = [
{ name = "click", specifier = ">=8.1.8" }, { name = "click", specifier = ">=8.1.8" },
{ name = "httpx", specifier = ">=0.28.1,<0.29.0" }, { name = "httpx", specifier = ">=0.28.1,<0.29.0" },
{ name = "icalendar", specifier = ">=6.0.0,<7.0.0" }, { name = "icalendar", specifier = ">=6.0.0,<7.0.0" },
{ name = "mcp", extras = ["cli"], specifier = ">=1.19,<1.20" }, { name = "mcp", extras = ["cli"], specifier = ">=1.20,<1.21" },
{ name = "pillow", specifier = ">=12.0.0,<12.1.0" }, { name = "pillow", specifier = ">=12.0.0,<12.1.0" },
{ name = "pydantic", specifier = ">=2.11.4" }, { name = "pydantic", specifier = ">=2.11.4" },
{ name = "pyjwt", extras = ["crypto"], specifier = ">=2.8.0" }, { name = "pyjwt", extras = ["crypto"], specifier = ">=2.8.0" },
@@ -1032,7 +1024,6 @@ dev = [
{ name = "pytest-timeout", specifier = ">=2.3.1" }, { name = "pytest-timeout", specifier = ">=2.3.1" },
{ name = "reportlab", specifier = ">=4.0.0" }, { name = "reportlab", specifier = ">=4.0.0" },
{ name = "ruff", specifier = ">=0.11.13" }, { name = "ruff", specifier = ">=0.11.13" },
{ name = "ty", specifier = ">=0.0.1a25" },
] ]
[[package]] [[package]]
@@ -1964,31 +1955,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" }, { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" },
] ]
[[package]]
name = "ty"
version = "0.0.1a25"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f6/6b/e73bc3c1039ea72936158a08313155a49e5aa5e7db5205a149fe516a4660/ty-0.0.1a25.tar.gz", hash = "sha256:5550b24b9dd0e0f8b4b2c1f0fcc608a55d0421dd67b6c364bc7bf25762334511", size = 4403670, upload-time = "2025-10-29T19:40:23.647Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8f/3b/4457231238a2eeb04cba4ba7cc33d735be68ee46ca40a98ae30e187de864/ty-0.0.1a25-py3-none-linux_armv6l.whl", hash = "sha256:d35b2c1f94a014a22875d2745aa0432761d2a9a8eb7212630d5caf547daeef6d", size = 8878803, upload-time = "2025-10-29T19:39:42.243Z" },
{ url = "https://files.pythonhosted.org/packages/8a/fa/a328713dd310018fc7a381693d8588185baa2fdae913e01a6839187215df/ty-0.0.1a25-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:192edac94675a468bac7f6e04687a77a64698e4e1fe01f6a048bf9b6dde5b703", size = 8695667, upload-time = "2025-10-29T19:39:45.179Z" },
{ url = "https://files.pythonhosted.org/packages/22/e8/5707939118992ced2bf5385adc3ede7723c1b717b07ad14c495eea1e47b4/ty-0.0.1a25-py3-none-macosx_11_0_arm64.whl", hash = "sha256:949523621f336e01bc7d687b7bd08fe838edadbdb6563c2c057ed1d264e820cf", size = 8159012, upload-time = "2025-10-29T19:39:47.011Z" },
{ url = "https://files.pythonhosted.org/packages/eb/fb/ff313aa71602225cd78f1bce3017713d6d1b1c1e0fa8101ead4594a60d95/ty-0.0.1a25-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94f78f621458c05e59e890061021198197f29a7b51a33eda82bbb036e7ed73d7", size = 8433675, upload-time = "2025-10-29T19:39:48.443Z" },
{ url = "https://files.pythonhosted.org/packages/c0/8d/cc7e7fb57215a15b575a43ed042bdd92971871e0decec1b26d2e7d969465/ty-0.0.1a25-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d9656fca8062a2c6709c30d76d662c96d2e7dbfee8f70e55ec6b6afd67b5d447", size = 8668456, upload-time = "2025-10-29T19:39:50.412Z" },
{ url = "https://files.pythonhosted.org/packages/b8/6d/d7bf5909ed2dcdcbc1e2ca7eea80929893e2d188d9c36b3fcb2b36532ff6/ty-0.0.1a25-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9f3bbf523b49935bbd76e230408d858dce0d614f44f5807bbbd0954f64e0f01", size = 9023543, upload-time = "2025-10-29T19:39:52.292Z" },
{ url = "https://files.pythonhosted.org/packages/b4/b8/72bcefb4be32e5a84f0b21de2552f16cdb4cae3eb271ac891c8199c26b1a/ty-0.0.1a25-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f13ea9815f4a54a0a303ca7bf411b0650e3c2a24fc6c7889ffba2c94f5e97a6a", size = 9700013, upload-time = "2025-10-29T19:39:57.283Z" },
{ url = "https://files.pythonhosted.org/packages/90/0d/cf7e794b840cf6b0bbecb022e593c543f85abad27a582241cf2095048cb1/ty-0.0.1a25-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eab6e33ebe202a71a50c3d5a5580e3bc1a85cda3ffcdc48cec3f1c693b7a873b", size = 9372574, upload-time = "2025-10-29T19:40:04.532Z" },
{ url = "https://files.pythonhosted.org/packages/1e/71/2d35e7d51b48eabd330e2f7b7e0bce541cbd95950c4d2f780e85f3366af1/ty-0.0.1a25-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6b9a31da43424cdab483703a54a561b93aabba84630788505329fc5294a9c62", size = 9535726, upload-time = "2025-10-29T19:40:06.548Z" },
{ url = "https://files.pythonhosted.org/packages/57/d3/01ecc23bbd8f3e0dfbcf9172d06d84e88155c5f416f1491137e8066fd859/ty-0.0.1a25-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a90d897a7c1a5ae9b41a4c7b0a42262a06361476ad88d783dbedd7913edadbc", size = 9003380, upload-time = "2025-10-29T19:40:08.683Z" },
{ url = "https://files.pythonhosted.org/packages/de/f9/cde9380d8a1a6ca61baeb9aecb12cbec90d489aa929be55cd78ad5c2ccd9/ty-0.0.1a25-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:93c7e7ab2859af0f866d34d27f4ae70dd4fb95b847387f082de1197f9f34e068", size = 8401833, upload-time = "2025-10-29T19:40:10.627Z" },
{ url = "https://files.pythonhosted.org/packages/0b/39/0acf3625b0c495011795a391016b572f97a812aca1d67f7a76621fdb9ebf/ty-0.0.1a25-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:4a247061bd32bae3865a236d7f8b6c9916c80995db30ae1600999010f90623a9", size = 8706761, upload-time = "2025-10-29T19:40:12.575Z" },
{ url = "https://files.pythonhosted.org/packages/25/73/7de1648f3563dd9d416d36ab5f1649bfd7b47a179135027f31d44b89a246/ty-0.0.1a25-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1711dd587eccf04fd50c494dc39babe38f4cb345bc3901bf1d8149cac570e979", size = 8792426, upload-time = "2025-10-29T19:40:14.553Z" },
{ url = "https://files.pythonhosted.org/packages/7d/8a/b6e761a65eac7acd10b2e452f49b2d8ae0ea163ca36bb6b18b2dadae251b/ty-0.0.1a25-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5f4c9b0cf7995e2e3de9bab4d066063dea92019f2f62673b7574e3612643dd35", size = 9103991, upload-time = "2025-10-29T19:40:16.332Z" },
{ url = "https://files.pythonhosted.org/packages/e4/25/9324ae947fcc4322470326cf8276a3fc2f08dc82adec1de79d963fdf7af5/ty-0.0.1a25-py3-none-win32.whl", hash = "sha256:168fc8aee396d617451acc44cd28baffa47359777342836060c27aa6f37e2445", size = 8387095, upload-time = "2025-10-29T19:40:18.368Z" },
{ url = "https://files.pythonhosted.org/packages/3b/2b/cb12cbc7db1ba310aa7b1de9b4e018576f653105993736c086ee67d2ec02/ty-0.0.1a25-py3-none-win_amd64.whl", hash = "sha256:a2fad3d8e92bb4d57a8872a6f56b1aef54539d36f23ebb01abe88ac4338efafb", size = 9059225, upload-time = "2025-10-29T19:40:20.278Z" },
{ url = "https://files.pythonhosted.org/packages/2f/c1/f6be8cdd0bf387c1d8ee9d14bb299b7b5d2c0532f550a6693216a32ec0c5/ty-0.0.1a25-py3-none-win_arm64.whl", hash = "sha256:dde2962d448ed87c48736e9a4bb13715a4cced705525e732b1c0dac1d4c66e3d", size = 8536832, upload-time = "2025-10-29T19:40:22.014Z" },
]
[[package]] [[package]]
name = "typer" name = "typer"
version = "0.19.2" version = "0.19.2"