Skip to main content
Glama
cbcoutinho

Nextcloud MCP Server

by cbcoutinho
context.py6.55 kB
"""Helper functions for accessing context in MCP tools.""" import logging from httpx import BasicAuth from mcp.server.fastmcp import Context from nextcloud_mcp_server.client import NextcloudClient from nextcloud_mcp_server.config import ( DeploymentMode, get_deployment_mode, get_settings, ) logger = logging.getLogger(__name__) async def get_client(ctx: Context) -> NextcloudClient: """ Get the appropriate Nextcloud client based on authentication mode. ADR-016 compliant implementation supporting three deployment modes: 1. Smithery stateless mode (SMITHERY_DEPLOYMENT=true): Create client from session configuration (nextcloud_url, username, app_password) No persistent state - client created per-request from Smithery session config. 2. BasicAuth mode: Returns shared client from lifespan context 3. OAuth mode: a. Multi-audience mode (ENABLE_TOKEN_EXCHANGE=false, default): Token already contains both MCP and Nextcloud audiences - use directly b. Token exchange mode (ENABLE_TOKEN_EXCHANGE=true): Exchange MCP token for Nextcloud token via RFC 8693 SECURITY: Token passthrough has been REMOVED. All OAuth modes validate proper token audiences per MCP Security Best Practices specification. Note: Nextcloud doesn't support OAuth scopes natively. Scopes are enforced by the MCP server via @require_scopes decorator, not by the IdP. This function automatically detects the authentication mode by checking the deployment mode and type of the lifespan context. Args: ctx: MCP request context Returns: NextcloudClient configured for the current authentication mode Raises: AttributeError: If context doesn't contain expected data ValueError: If Smithery mode but session config is missing required fields Example: ```python @mcp.tool() async def my_tool(ctx: Context): client = await get_client(ctx) return await client.capabilities() ``` """ deployment_mode = get_deployment_mode() # ADR-016: Smithery stateless mode - create client from session config if deployment_mode == DeploymentMode.SMITHERY_STATELESS: return _get_client_from_session_config(ctx) settings = get_settings() lifespan_ctx = ctx.request_context.lifespan_context # BasicAuth mode - use shared client (no token exchange) if hasattr(lifespan_ctx, "client"): return lifespan_ctx.client # OAuth mode (has 'nextcloud_host' attribute) if hasattr(lifespan_ctx, "nextcloud_host"): from nextcloud_mcp_server.auth.context_helper import ( get_client_from_context, get_session_client_from_context, ) if settings.enable_token_exchange: # Mode 2: Exchange MCP token for Nextcloud token # Token was validated to have MCP audience in UnifiedTokenVerifier # Now exchange it for Nextcloud audience return await get_session_client_from_context( ctx, lifespan_ctx.nextcloud_host ) else: # Mode 1: Multi-audience token - use directly # Token was validated to have MCP audience in UnifiedTokenVerifier # Nextcloud will independently validate its own audience when receiving API calls return get_client_from_context(ctx, lifespan_ctx.nextcloud_host) # Unknown context type raise AttributeError( f"Lifespan context does not have 'client' or 'nextcloud_host' attribute. " f"Type: {type(lifespan_ctx)}" ) def _get_client_from_session_config(ctx: Context) -> NextcloudClient: """ Create NextcloudClient from Smithery session configuration. ADR-016: In Smithery stateless mode, each request includes session config with the user's Nextcloud credentials. This function creates a fresh client for each request - no state is persisted between requests. For container runtime, config is extracted from URL query parameters by SmitheryConfigMiddleware and stored in a context variable. Expected session config fields (from Smithery configSchema): - nextcloud_url: str - Nextcloud instance URL (required) - username: str - Nextcloud username (required) - app_password: str - Nextcloud app password (required) Args: ctx: MCP request context (not used directly for Smithery config) Returns: NextcloudClient configured with session credentials Raises: ValueError: If required session config fields are missing """ # ADR-016: Get session config from context variable (set by SmitheryConfigMiddleware) from nextcloud_mcp_server.app import get_smithery_session_config session_config = get_smithery_session_config() if session_config is None: raise ValueError( "Session configuration required in Smithery mode. " "Ensure nextcloud_url, username, and app_password are provided as URL query parameters." ) # Extract required fields - config is always a dict from SmitheryConfigMiddleware nextcloud_url = session_config.get("nextcloud_url") username = session_config.get("username") app_password = session_config.get("app_password") # Validate required fields missing_fields = [] if not nextcloud_url: missing_fields.append("nextcloud_url") if not username: missing_fields.append("username") if not app_password: missing_fields.append("app_password") if missing_fields: raise ValueError( f"Missing required session config fields: {', '.join(missing_fields)}. " f"Configure these in the Smithery connection settings." ) # Type assertions after validation (for type checker) # These are guaranteed to be str after the missing_fields check above assert nextcloud_url is not None assert username is not None assert app_password is not None # Validate URL format if not nextcloud_url.startswith(("http://", "https://")): raise ValueError( f"Invalid nextcloud_url: {nextcloud_url}. " f"Must start with http:// or https://" ) logger.debug(f"Creating Smithery client for {nextcloud_url} as {username}") # Create client with session credentials using BasicAuth return NextcloudClient( base_url=nextcloud_url, username=username, auth=BasicAuth(username, app_password), )

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/cbcoutinho/nextcloud-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server