Files
nextcloud-mcp-server/docs/ADR-011-hybrid-oauth-appapi-deployment.md
T
Chris Coutinho 26099d643d docs: Update ADR-011 to rejected status with Context Agent validation
After comprehensive research, the hybrid OAuth + AppAPI architecture is NOT
being implemented due to fundamental architectural incompatibilities.

Key updates:
- Status: Proposed → Not Planned
- Added validation from Nextcloud Context Agent project
- Context Agent (official NC ExApp with MCP) faces IDENTICAL limitations
- Proves constraints are architectural, not implementation-specific

Context Agent findings:
- ExApp with MCP server endpoint (~28 tools exposed)
- Uses Task Processing API for confirmations (NOT MCP elicitation)
- Works around AppAPI proxy limitations by changing protocol
- MCP endpoint is secondary feature with documented constraints
- Primary use: In-app Assistant integration, not external MCP clients

Critical features impossible through AppAPI proxy:
-  MCP sampling (eliminates RAG/LLM features)
-  MCP elicitation (user prompts)
-  Real-time progress updates
-  Bidirectional streaming
- Validated by Context Agent facing same limitations

Decision rationale:
- MCP requires multi-turn nested interactions
- AppAPI provides stateless request/response proxy only
- No implementation effort can bridge this fundamental gap
- Would require complete AppAPI redesign (WebSocket, message routing)
- Even official Nextcloud projects work around these limitations

Alternative considered for future:
- Register as Task Processing provider (different product)
- Use Nextcloud Assistant UI (not external MCP clients)
- Accept different capabilities (no sampling, custom flows)

OAuth mode remains sole solution for external MCP client integration.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-13 23:30:14 +01:00

56 KiB

ADR-011: Hybrid OAuth and AppAPI Deployment Architecture

Status: Not Planned Date: 2025-01-13 (Initial), 2025-01-13 (Rejected) Related: ADR-004 (Progressive Consent), ADR-005 (Token Audience Validation)

Decision Outcome

After comprehensive research and analysis, this hybrid architecture is NOT being implemented. The investigation revealed fundamental architectural incompatibilities between MCP's protocol requirements and AppAPI's request/response proxy model that cannot be resolved without significant upstream changes to AppAPI. The limitations are too severe to provide a viable user experience for MCP's core features.

Context

The Nextcloud MCP Server currently implements a sophisticated OAuth 2.0 / OIDC authentication architecture (ADR-004, ADR-005) that supports multi-tenant deployments, fine-grained scope-based permissions, and progressive consent. This architecture works well for standalone MCP server deployments where the server operates as a separate service from Nextcloud, requiring users to explicitly authenticate and authorize access to their Nextcloud data.

However, this OAuth-based approach has limitations for certain deployment scenarios:

Deployment Complexity: Setting up the OAuth flow requires:

  • Configuring an identity provider (Keycloak, Nextcloud's built-in OIDC, etc.)
  • Registering OAuth clients via Dynamic Client Registration (DCR)
  • Configuring redirect URIs, token endpoints, and JWKS endpoints
  • Setting up token storage for refresh tokens
  • Managing token lifecycle (refresh, expiration, revocation)

For administrators who simply want to enable MCP functionality within their existing Nextcloud instance, this setup burden is significant—especially compared to traditional Nextcloud apps that can be installed with a single click.

Integration Limitations: The current architecture treats Nextcloud as an external API, accessed via HTTP with bearer token authentication. This prevents the MCP server from:

  • Registering UI components in Nextcloud (menu items, file actions, dashboard widgets)
  • Responding to Nextcloud events and webhooks natively
  • Leveraging Nextcloud's built-in permission system
  • Being managed via Nextcloud's app management interface

Single-Tenant Overhead: For single-tenant deployments (one Nextcloud instance, one MCP server), the OAuth architecture's multi-tenant capabilities (separate tokens per user, per-user consent) add complexity without providing value. The admin already trusts the MCP server by deploying it—explicit per-user consent becomes redundant.

Nextcloud AppAPI (ExApp) Architecture

Nextcloud provides an AppAPI framework for running External Applications (ExApps)—applications that run outside the Nextcloud PHP process but integrate deeply with Nextcloud. ExApps are the evolution of traditional PHP-based Nextcloud apps, designed to support multiple programming languages (Python, Go, etc.) and run in separate containers for enhanced security and stability.

Key AppAPI Characteristics:

  1. Shared Secret Authentication: ExApps use a permanent shared secret instead of OAuth tokens. Nextcloud passes user context via the AUTHORIZATION-APP-API header: base64(userId:secret).

  2. Built-in Proxy: AppAPI provides a production-ready HTTP proxy at /index.php/apps/app_api/proxy/{appId}/{path} that:

    • Routes external requests through Nextcloud to the ExApp
    • Translates Nextcloud user sessions into authenticated ExApp requests
    • Supports HTTP streaming (including Server-Sent Events for MCP)
    • Enforces access control (PUBLIC, USER, ADMIN levels)
  3. Native Integration: ExApps can register:

    • Top menu items
    • File actions (right-click context menus)
    • Dashboard widgets
    • Settings pages
    • Background jobs (cron tasks)
    • Event listeners
  4. Admin-Controlled Permissions: Scopes are granted at installation time by the administrator (via info.xml manifest), not per-user. Users inherit the app's permissions based on their Nextcloud account privileges.

  5. Container Deployment: ExApps run as Docker containers managed by Nextcloud's daemon configuration, with automatic lifecycle management (start, stop, update, uninstall).

Example ExApp Deployment Flow:

# Admin registers ExApp via occ command
php occ app_api:app:register nextcloud_mcp_server \
  --json-info '{"id":"nextcloud_mcp_server","secret":"<generated>",...}'

# OR: One-click install from Nextcloud app store (future)

MCP Client Connection via Proxy:

User → Nextcloud (session auth) → AppAPI Proxy → ExApp MCP Server

The proxy handles user authentication and injects the AUTHORIZATION-APP-API header, so the ExApp receives pre-authenticated requests with user context.

Why Both Architectures Are Valuable

OAuth Mode Best For:

  • Multi-tenant SaaS: One MCP server instance serving multiple Nextcloud instances
  • External MCP clients: Clients not integrated with Nextcloud (Claude Desktop, custom clients)
  • Fine-grained consent: Users explicitly authorize which scopes the MCP client can access
  • Per-user token management: Background jobs that require long-lived refresh tokens
  • Standalone services: MCP server as a separate service with its own lifecycle

AppAPI Mode Best For:

  • Single-tenant deployments: One Nextcloud instance with integrated MCP functionality
  • Simplified administration: One-click installation via Nextcloud app store
  • Native Nextcloud integration: UI components, event listeners, dashboard widgets
  • Admin-controlled permissions: Trust model where admin installation implies consent
  • Nextcloud-managed lifecycle: Automatic container management and updates

Both deployment models serve legitimate use cases. Rather than choosing one over the other, a hybrid architecture supporting both modes maximizes flexibility and serves the broadest user base.

Design Challenges

Code Duplication Risk: Implementing two separate authentication mechanisms could lead to massive code duplication—separate tool implementations, separate clients, separate tests—doubling maintenance burden.

Abstraction Complexity: Creating an abstraction layer that works seamlessly for both OAuth (token-based, per-user scopes) and AppAPI (header-based, admin-granted permissions) risks over-engineering and introducing bugs.

Dependency Management: OAuth mode requires authlib, pyjwt, aiosqlite. AppAPI mode requires nc_py_api. Installing both adds bloat; making them mutually exclusive complicates development.

Testing Burden: Supporting both modes doubles the test matrix—each integration test must pass in both OAuth and AppAPI configurations.

Documentation Complexity: Users need clear guidance on when to use each mode, how to configure it, and how they differ in behavior.

Despite these challenges, the value proposition—supporting both standalone OAuth deployments and native Nextcloud integration—justifies the architectural investment.

AppAPI Limitations and Challenges

While AppAPI provides valuable benefits for Nextcloud integration, research has identified several technical limitations that constrain the MCP server's capabilities in AppAPI mode compared to OAuth mode. These limitations stem from fundamental architectural differences between the two approaches.

Authentication Challenges

Session Cookie Dependency: The AppAPI proxy relies on Nextcloud user sessions for authentication. When a user accesses /index.php/apps/app_api/proxy/{appId}/{path}, the proxy extracts the user ID from their Nextcloud session cookie and converts it to the AUTHORIZATION-APP-API header.

Non-Browser Client Problem: MCP clients like Claude Desktop are non-browser applications that cannot:

  • Handle browser-based session cookies
  • Interact with Nextcloud login forms
  • Complete standard OAuth browser flows

Workaround Required: To support non-browser MCP clients in AppAPI mode, one of the following solutions is needed:

  1. App Passwords (Simplest): Add BasicAuth support to the AppAPI proxy. Users generate app-specific passwords in Nextcloud's security settings, and MCP clients send Authorization: Basic base64(username:appPassword). The proxy validates credentials and converts to AUTHORIZATION-APP-API headers. Requires upstream AppAPI modification.

  2. OAuth 2.0 Integration (Most Secure): Integrate MCP's OAuth client with Nextcloud's OIDC provider. MCP clients obtain access tokens via standard OAuth flow, include Authorization: Bearer <token> headers, and the proxy validates tokens via introspection. Requires significant proxy modification and token caching for performance.

  3. PUBLIC Routes with Internal Auth (Bypass Proxy): Register ExApp routes as PUBLIC (no authentication) and implement validation within the ExApp. Less secure and duplicates authentication logic.

Recommendation: Implement app password support (Option 1) as Phase 1, with OAuth 2.0 (Option 2) as a long-term production-ready solution.

Streaming Limitations

Buffered, Not Real-Time: The AppAPI proxy supports HTTP streaming via Guzzle's stream: true option, which efficiently handles large response bodies. However, the streaming is buffered, not incremental:

  • ExApp generates response chunks
  • Guzzle streams response to proxy (low memory usage)
  • Proxy accumulates complete response
  • Client receives response only after completion

SSE Response Handling: While ExApps can return Content-Type: text/event-stream responses, the events arrive at the client as a complete buffered stream, not as individual events in real-time. This works for bounded operations (finite event streams) but not for true real-time streaming.

No WebSocket Support: The proxy does not support WebSocket protocol upgrades. WebSocket requires:

  • HTTP/1.1 Upgrade protocol handling
  • Different HTTP client library (Guzzle doesn't support WebSocket)
  • Persistent bidirectional connection management
  • Significant proxy architectural changes

Impact on MCP Transports:

Transport AppAPI Support Streaming Usability
Streamable HTTP Buffered Bounded Works today
stdio Future True Ideal but requires AppAPI changes
SSE (legacy) ⚠️ Buffered Bounded Deprecated by MCP
WebSocket Major rewrite True Not viable

Recommendation: Use Streamable HTTP transport (MCP 2025-03-26+), which works with current AppAPI proxy. Operations complete as bounded streams. For true real-time streaming, advocate for stdio transport support in AppAPI (requires docker exec -i integration, estimated 2-4 weeks development).

Notification and Progress Update Limitations

MCP's Notification Model: The MCP protocol supports server-to-client notifications over persistent connections (SSE or WebSocket). This enables:

  • Progress updates during long-running operations
  • Sampling requests (LLM generation via client)
  • Asynchronous events from server to client

AppAPI's Request/Response Architecture: The AppAPI proxy operates on a request/response model:

  • Client sends HTTP request through proxy
  • ExApp processes and returns response
  • Connection closes
  • No persistent connection to original client
  • ExApp doesn't know who the client is (only sees proxy)

Feature Incompatibility:

Feature OAuth/SSE Mode AppAPI Mode Notes
Tool execution Immediate Immediate Works fine
Progress updates Real-time via MCP No channel Must use Nextcloud notifications
Sampling (LLM/RAG) Via MCP protocol No protocol forwarding Critical limitation
Long operations With incremental progress ⚠️ Background tasks only Different pattern required
Bidirectional events Via persistent connection Request/response only Architectural incompatibility

Workarounds for AppAPI Mode:

  1. Short Operations (<30s): Direct request/response works fine. No changes needed.

  2. Long Operations: Use FastAPI BackgroundTasks + Nextcloud notifications:

    @mcp.tool()
    async def long_operation(ctx: NextcloudApp, background: BackgroundTasks):
        task_id = uuid.uuid4()
        background.add_task(process_long_operation, ctx, task_id)
        return {"status": "accepted", "task_id": task_id}
    
    def process_long_operation(nc: NextcloudApp, task_id: str):
        result = do_work()
        nc.notifications.create("Task completed", f"Result: {result}")
    
  3. Sampling/LLM Features: Not possible in AppAPI mode. The MCP sampling protocol (ctx.session.create_message()) requires bidirectional communication with the client. Tools using sampling must detect the deployment mode and gracefully degrade:

    @mcp.tool()
    async def semantic_search_answer(query: str, ctx: Context | NextcloudApp):
        if isinstance(ctx, NextcloudApp):
            # AppAPI mode: return documents only, no LLM generation
            return SearchResponse(results=documents, message="LLM generation not available in AppAPI mode")
        else:
            # OAuth mode: use sampling for LLM answer
            answer = await ctx.session.create_message(...)
            return SamplingResponse(answer=answer, sources=documents)
    

Recommendation: Document feature limitations clearly. Disable sampling-based tools in AppAPI mode with helpful error messages. Use Nextcloud's notification system for progress updates on long-running operations.

Webhook and Callback Support

Outbound Webhooks (Nextcloud → ExApp): Well Supported

ExApps can register webhooks via the webhooks_listener app:

nc.webhooks.register(
    http_method="POST",
    uri="/webhook/file_created",
    event="OCP\\Files\\Events\\Node\\NodeCreatedEvent",
    event_filter={...}
)

Inbound Notifications (ExApp → Nextcloud): Supported

ExApps can send notifications to users:

nc.notifications.create(
    subject="Task completed",
    message="Your file is ready",
    link=result_url
)

Limitation: ExApps cannot send MCP protocol notifications back through the proxy to the original MCP client. Communication is ExApp → Nextcloud → User UI, not ExApp → MCP Client.

Validation: Nextcloud Context Agent as Real-World Example

The limitations documented above are not theoretical - they are validated by Nextcloud's own Context Agent project, which is an AppAPI ExApp that exposes MCP functionality and faces identical constraints.

Context Agent Architecture

Context Agent (~/Software/context_agent/) is an official Nextcloud project that demonstrates the ExApp architecture in practice:

Type: AppAPI External App (ExApp) written in Python with FastMCP

Dual MCP Role:

  1. MCP Server: Exposes ~28 tools (calendar, contacts, files, talk, mail, deck) via /mcp endpoint
  2. MCP Client: Consumes external MCP servers using langchain-mcp-adapters

Route Configuration (appinfo/info.xml):

<route>
  <url>mcp</url>
  <verb>POST,GET,DELETE</verb>
  <access_level>USER</access_level>
</route>

Access Pattern: External MCP clients connect via AppAPI proxy:

MCP Client → /apps/app_api/proxy/context_agent/mcp → Context Agent ExApp

Context Agent Faces Identical Limitations

Despite being an official Nextcloud project with MCP integration, Context Agent has exactly the same AppAPI proxy limitations documented in this ADR:

What Works:

  • Basic MCP tools (request → response pattern)
  • Tool listing and discovery
  • Stateless HTTP transport
  • User authentication via AppAPI headers

What Doesn't Work (identical to our findings):

  • MCP sampling (LLM completion requests)
  • MCP elicitation (user input prompts)
  • Real-time progress updates
  • Bidirectional streaming communication
  • Server-initiated notifications via MCP protocol

Context Agent's Workaround Strategy

Context Agent successfully provides agent functionality by working around the MCP protocol limitations rather than relying on them:

Primary Use Case: In-app AI agent called by Nextcloud Assistant (native PHP app)

  • Uses Nextcloud's Task Processing API for orchestration (not external MCP clients)
  • Implements custom confirmation flow via Assistant UI (not MCP elicitation)
  • Uses LangGraph state machine for agent logic
  • Leverages Nextcloud APIs for user interaction

Confirmation Flow Example (ex_app/lib/agent.py:105-117):

if state_snapshot.next == ('dangerous_tools', ):
    if task['input']['confirmation'] == 0:
        # User denied via Nextcloud UI - return denial message
        return ToolMessage(
            tool_call_id=tool_call["id"],
            content=f"API call denied by user. Reasoning: '{task['input']['input']}'"
        )
    else:
        # User approved via Nextcloud UI - execute tool
        execute_tool()

Flow:

  1. Agent queues dangerous tool call
  2. Returns to Assistant with actions field (outside MCP)
  3. Assistant UI prompts user for confirmation in Nextcloud
  4. User approves/denies in Nextcloud interface
  5. New task submitted with confirmation field
  6. Agent proceeds based on confirmation

Key Insight: Context Agent does NOT use MCP's native elicitation - it implements a completely custom confirmation flow through Nextcloud's Task Processing API because MCP elicitation is impossible through the AppAPI proxy.

Architecture Comparison

Context Agent Primary Use (Works):
Nextcloud Assistant UI → Task Processing API → Context Agent ExApp → Tools
                        ✅ Custom confirmation via NC APIs (not MCP)

Context Agent MCP Endpoint (Limited):
External MCP Client → AppAPI Proxy → Context Agent /mcp → Tools
                     ❌ Sampling blocked (same as our server)

Our MCP Server Use Case (Blocked):
External MCP Client → AppAPI Proxy → Our ExApp → Nextcloud APIs
                     ❌ Sampling blocked (same limitation)

Why Context Agent Succeeds Despite Limitations

Context Agent works because:

  1. Different primary use case: In-app integration via Task Processing API, not external MCP clients
  2. Custom workarounds: Uses Nextcloud APIs for features MCP can't provide through proxy
  3. Accepts limitations: MCP endpoint is secondary feature with documented constraints
  4. Alternative protocols: Uses Task Processing API as primary interface, MCP as optional

Implications for Our MCP Server

Context Agent validates that:

  1. AppAPI proxy limitations are architectural, not implementation-specific
  2. All ExApps face identical constraints - even official Nextcloud projects
  3. Workarounds exist but require using Nextcloud APIs outside MCP protocol
  4. Different use cases require different approaches:
    • In-app integration: Use Task Processing API (like Context Agent)
    • External MCP clients: Require OAuth mode (no viable AppAPI solution)

If our MCP server's primary use case is external MCP clients (Claude Desktop, custom clients), AppAPI mode provides no viable path forward. The limitations eliminate core MCP features (sampling/RAG, real-time progress) that are essential for external client integration.

If targeting in-app integration (like Context Agent), we would need to:

  • Register as Task Processing provider
  • Implement custom confirmation via Assistant UI
  • Use Nextcloud APIs for user interaction
  • Accept that MCP protocol features are unavailable

This represents a fundamentally different product with different capabilities and user experience.

Conclusion from Context Agent Analysis

The existence of Context Agent with identical limitations strengthens the case against AppAPI mode for our use case:

  • Official Nextcloud project faces same constraints
  • Successfully works around them by changing use case and protocol
  • Confirms limitations are inherent to ExApp architecture
  • Demonstrates that MCP protocol features cannot work through AppAPI proxy

Our recommendation: Continue with OAuth mode for external MCP clients, do not pursue AppAPI mode unless the product vision shifts to in-app integration via Task Processing API.

Decision

We will NOT implement AppAPI mode for external MCP client integration. The project will continue with OAuth mode exclusively.

Rationale

After comprehensive research including analysis of Nextcloud's own Context Agent project, the limitations of AppAPI ExApp architecture for MCP integration are too severe to provide acceptable user experience:

Critical Missing Features in AppAPI Mode:

  1. No MCP sampling - Eliminates RAG/LLM generation features (ADR-008)
  2. No real-time progress - Breaks user experience for long-running operations
  3. No bidirectional streaming - Core MCP protocol features unusable
  4. Buffered-only streaming - Defeats the purpose of streaming protocols

These are not implementation challenges - they are fundamental architectural incompatibilities between:

  • MCP's requirements: Multi-turn nested interactions, server-initiated requests, bidirectional streaming
  • AppAPI's architecture: Stateless request/response proxy, no persistent connections, no message routing

Validation from Context Agent: Nextcloud's official MCP-enabled ExApp faces identical limitations and works around them by:

  • Using Task Processing API instead of MCP protocol for user interaction
  • Targeting in-app Assistant integration, not external MCP clients
  • Accepting that MCP endpoint is secondary with documented constraints

Why OAuth Mode Remains Sole Solution

OAuth mode provides all essential MCP features:

  • Full MCP protocol support (sampling, elicitation, streaming)
  • Real-time progress updates
  • Multi-tenant capability
  • External client integration (Claude Desktop, custom clients)
  • Fine-grained per-user permissions
  • Proven architecture (production-ready)

Alternative Considered: Task Processing Provider

If in-app Nextcloud integration is desired in the future, the correct approach would be:

  • Register as Task Processing provider (like Context Agent)
  • Use Nextcloud Assistant UI for user interaction
  • Implement custom flows via Task Processing API
  • Accept that this is a different product with different capabilities

This would be a separate feature, not a replacement for external MCP client support.

Original Hybrid Architecture (Documented for Reference)

The following sections document the hybrid architecture that was researched but not implemented. They are preserved for reference and to document why this approach was rejected.

Core Principles (Not Implemented)

  1. Single Codebase: One repository, one Docker image build process, mode selection via environment variables
  2. Maximum Code Sharing: 100% sharing of MCP tool implementations, client libraries, and business logic
  3. Backwards Compatibility: OAuth mode remains the default; existing deployments continue working unchanged
  4. Mode Detection: Runtime mode selection via APPAPI_MODE=true environment variable
  5. Abstraction Over Duplication: Unified client interface that tools call regardless of mode
  6. Graceful Degradation: Features that only work in one mode (e.g., UI registration in AppAPI) degrade gracefully

Architecture Overview

┌────────────────────────────────────────────────────────────┐
│  MCP Tool Layer (100% Shared)                              │
│  - nc_notes_create_note()                                  │
│  - nc_calendar_get_events()                                │
│  - nc_semantic_search()                                    │
│  └─ Calls: get_client(ctx) → NextcloudClient              │
└────────────────────────────────────────────────────────────┘
                           │
                           ▼
┌────────────────────────────────────────────────────────────┐
│  Client Abstraction Layer (New)                            │
│  - get_client(ctx: Context | NextcloudApp)                 │
│  - Returns NextcloudClient regardless of mode              │
└────────────────────────────────────────────────────────────┘
                           │
          ┌────────────────┴────────────────┐
          ▼                                 ▼
┌──────────────────────┐        ┌──────────────────────┐
│  OAuth Mode          │        │  AppAPI Mode         │
│  - Token validation  │        │  - Header validation │
│  - Token refresh     │        │  - User context from │
│  - DCR               │        │    AUTHORIZATION-    │
│  - Scope checking    │        │    APP-API header    │
└──────────────────────┘        └──────────────────────┘
          │                                 │
          ▼                                 ▼
┌──────────────────────┐        ┌──────────────────────┐
│  NextcloudClient     │        │  NextcloudClient     │
│  (token-based HTTP)  │        │  (via nc_py_api)     │
└──────────────────────┘        └──────────────────────┘

File Structure

nextcloud_mcp_server/
├── app.py                          # MODIFIED: Mode detection and delegation
├── app_oauth.py                    # NEW: OAuth-specific FastMCP setup
├── app_exapp.py                    # NEW: AppAPI-specific FastMCP setup
├── auth/
│   ├── bearer_auth.py              # OAuth token verification (existing)
│   ├── appapi_auth.py              # NEW: AppAPI header validation
│   ├── client_factory.py           # NEW: Mode-aware client abstraction
│   ├── scope_authorization.py      # MODIFIED: Mode-aware scope checking
│   └── context_helper.py           # MODIFIED: Support both Context and NextcloudApp
├── client/                         # NO CHANGES (100% shared)
│   ├── notes.py
│   ├── calendar.py
│   └── ...
├── server/                         # NO CHANGES (100% shared)
│   ├── notes.py
│   ├── calendar.py
│   └── ...
└── models/                         # NO CHANGES (100% shared)

appinfo/                            # NEW: ExApp manifest
├── info.xml                        # ExApp metadata and permissions
└── routes.xml                      # Proxy route configuration

Feature Parity Matrix

The following table documents feature support and differences between OAuth and AppAPI deployment modes. Understanding these differences is critical for users choosing a deployment mode.

Feature Category Feature OAuth Mode AppAPI Mode Notes
Authentication
Multi-user support Per-user tokens Per-request user context Both support multiple users
Non-browser clients OAuth flow ⚠️ Requires app passwords AppAPI needs BasicAuth support
Token management Refresh tokens N/A Shared secret OAuth uses refresh tokens
Fine-grained scopes Per-user consent ⚠️ Admin-granted AppAPI scopes set at install
Token revocation Per-user ⚠️ Uninstall app Different security models
Core Functionality
Tool execution Full support Full support All MCP tools work
Resource queries Full support Full support No functional differences
Error handling Full support Full support Same error reporting
Streaming & Communication
HTTP streaming Real-time ⚠️ Buffered AppAPI accumulates complete response
SSE transport Real-time events ⚠️ Buffered stream AppAPI receives all events at once
Streamable HTTP Real-time ⚠️ Buffered Works but not incremental
WebSocket ⚠️ Not implemented Not possible Neither mode supports currently
stdio transport Not applicable ⚠️ Future possibility Would require AppAPI changes
Progress & Notifications
Progress updates Real-time via MCP No channel Critical difference
MCP notifications Server → Client Impossible No persistent connection
Nextcloud notifications N/A ExApp → NC UI AppAPI advantage
Long-running ops With progress ⚠️ Background + notification Different UX patterns
Advanced Features
Sampling (LLM/RAG) Full support Not possible Critical limitation
Background sync workers With refresh tokens ⚠️ Via NC cron Different mechanisms
Vector database sync Background workers ⚠️ Webhook-driven Different sync strategies
Semantic search With sampling ⚠️ Without sampling Limited in AppAPI mode
Integration
Nextcloud UI components External service Native integration AppAPI advantage
File actions Not possible Right-click menus AppAPI advantage
Dashboard widgets Not possible Native widgets AppAPI advantage
Event listeners ⚠️ Via polling Via webhooks AppAPI advantage
Settings page Separate UI NC settings AppAPI advantage
Deployment
Installation ⚠️ Manual OAuth setup One-click install AppAPI advantage
Multi-tenant One server, many NC instances Single NC instance OAuth advantage
Standalone deployment Independent service Requires Nextcloud OAuth advantage
Container management Manual NC-managed AppAPI advantage
Updates Manual Via app store AppAPI advantage
Development & Testing
Local development Simple setup ⚠️ Requires NC instance OAuth easier for dev
Testing infrastructure Well established ⚠️ Needs work OAuth tests mature
Debug tooling Standard HTTP ⚠️ Via proxy OAuth simpler

Legend:

  • Full Support: Feature works as expected with no limitations
  • ⚠️ Partial/Different: Feature works but with limitations or different implementation
  • Not Supported: Feature not available or not possible
  • N/A Not Applicable: Feature doesn't apply to this mode

Key Takeaways:

  1. OAuth mode is best for:

    • Multi-tenant SaaS deployments
    • External MCP client access (Claude Desktop, custom clients)
    • Features requiring sampling/LLM generation (RAG)
    • Real-time progress updates
    • Standalone service deployments
  2. AppAPI mode is best for:

    • Single-tenant Nextcloud integration
    • Simplified installation and management
    • Native UI components and event listeners
    • Admin-controlled deployments
    • Nextcloud app store distribution
  3. Critical limitations in AppAPI mode:

    • No sampling support (RAG features unavailable)
    • No real-time progress updates (use Nextcloud notifications instead)
    • Buffered streaming only (not true real-time)
    • Requires app passwords for non-browser clients (needs AppAPI enhancement)

Implementation Details

1. Mode Detection (app.py)

import os
from typing import Literal

def get_deployment_mode() -> Literal["oauth", "exapp"]:
    """Detect deployment mode from environment."""
    if os.getenv("APPAPI_MODE") == "true":
        return "exapp"
    return "oauth"  # Default for backwards compatibility

def get_app(transport: str = "sse", enabled_apps: list[str] | None = None):
    """Get FastMCP app configured for the detected deployment mode."""
    mode = get_deployment_mode()

    if mode == "exapp":
        from nextcloud_mcp_server.app_exapp import create_exapp_app
        return create_exapp_app(transport, enabled_apps)
    else:
        from nextcloud_mcp_server.app_oauth import create_oauth_app
        return create_oauth_app(transport, enabled_apps)

# Entry point for MCP CLI
mcp = get_app()

2. Client Abstraction (auth/client_factory.py)

from typing import Union
from mcp.server.fastmcp import Context

# Conditional import (only available if nc_py_api installed)
try:
    from nc_py_api import NextcloudApp
except ImportError:
    NextcloudApp = None  # type: ignore

async def get_client(ctx: Union[Context, NextcloudApp]) -> NextcloudClient:
    """Get authenticated NextcloudClient for the current request (mode-aware)."""

    # AppAPI mode: ctx is NextcloudApp from nc_py_api
    if NextcloudApp and isinstance(ctx, NextcloudApp):
        return await _get_exapp_client(ctx)

    # OAuth mode: ctx is FastMCP Context
    if isinstance(ctx, Context):
        return await _get_oauth_client(ctx)

    raise TypeError(f"Unexpected context type: {type(ctx)}")

async def _get_exapp_client(nc: NextcloudApp) -> NextcloudClient:
    """Create NextcloudClient from nc_py_api NextcloudApp."""
    # Wrap nc_py_api session in NextcloudClient interface
    return NextcloudClient.from_nc_py_api(nc)

async def _get_oauth_client(ctx: Context) -> NextcloudClient:
    """Create NextcloudClient from OAuth access token."""
    # Existing OAuth logic from context_helper.py
    access_token = ctx.request_context.access_token
    username = access_token.claims.get("preferred_username")
    return NextcloudClient.from_token(
        base_url=settings.nextcloud_host,
        token=access_token.token,
        username=username
    )

3. Tool Signatures (server/notes.py)

from typing import Union
from mcp.server.fastmcp import Context

try:
    from nc_py_api import NextcloudApp
except ImportError:
    NextcloudApp = type(None)  # Fallback if not installed

@mcp.tool()
@require_scopes("notes:write")
async def nc_notes_create_note(
    title: str,
    content: str,
    category: str,
    ctx: Union[Context, NextcloudApp],  # Union type for both modes
) -> CreateNoteResponse:
    """Create a new note."""
    client = await get_client(ctx)  # Mode-aware helper
    result = await client.notes.create_note(
        title=title,
        content=content,
        category=category
    )
    return CreateNoteResponse(
        success=True,
        note=result,
        message=f"Created note '{title}'"
    )

Key Points:

  • Tools accept Context | NextcloudApp union type
  • No changes to business logic or client calls
  • get_client() abstraction handles mode detection

4. Scope Authorization (auth/scope_authorization.py)

def require_scopes(*required_scopes: str):
    """Decorator for scope/permission checking (mode-aware)."""

    def decorator(func: Callable) -> Callable:
        func._required_scopes = list(required_scopes)

        @wraps(func)
        async def wrapper(*args: Any, **kwargs: Any) -> Any:
            ctx = kwargs.get("ctx")

            # AppAPI mode: Check Nextcloud permissions
            if NextcloudApp and isinstance(ctx, NextcloudApp):
                await _check_exapp_permissions(ctx, required_scopes)

            # OAuth mode: Check token scopes (existing logic)
            elif isinstance(ctx, Context):
                await _check_oauth_scopes(ctx, required_scopes)

            return await func(*args, **kwargs)

        return wrapper
    return decorator

async def _check_exapp_permissions(nc: NextcloudApp, scopes: list[str]) -> None:
    """Validate ExApp has required permissions via Nextcloud capabilities."""
    # In AppAPI mode, permissions are granted at install time
    # Check against user's Nextcloud capabilities for additional validation
    # E.g., "notes:write" → verify user has notes app enabled
    pass  # Implementation depends on Nextcloud capability API

async def _check_oauth_scopes(ctx: Context, scopes: list[str]) -> None:
    """Validate OAuth token contains required scopes (existing logic)."""
    # ... existing implementation

5. Dependency Management (pyproject.toml)

[project]
dependencies = [
    "mcp[cli] >=1.21,<1.22",
    "httpx >=0.28.1,<0.29.0",
    "pydantic >=2.10.4,<2.11.0",
    # ... other shared dependencies
]

[dependency-groups]
oauth = [
    "authlib >=1.6.5",
    "pyjwt[crypto] >=2.8.0",
    "jwcrypto >=1.5.6",
    "aiosqlite >=0.20.0",
]
exapp = [
    "nc_py_api >=0.21.0",
]
dev = [
    # ... existing dev dependencies
]

Installation:

# OAuth mode (current default)
uv sync --group oauth

# AppAPI mode
uv sync --group exapp

# Development (both modes)
uv sync --group oauth --group exapp --group dev

6. Docker Multi-Stage Build (Dockerfile)

# Stage 1: Base image with shared dependencies
FROM python:3.11-slim AS base
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN pip install uv && uv sync --no-dev

# Stage 2: OAuth variant (default)
FROM base AS oauth
RUN uv sync --group oauth --no-dev
COPY nextcloud_mcp_server/ ./nextcloud_mcp_server/
ENV DEPLOYMENT_MODE=oauth
EXPOSE 8000
CMD ["uv", "run", "mcp", "run", "nextcloud_mcp_server.app:mcp"]

# Stage 3: AppAPI variant
FROM base AS exapp
RUN uv sync --group exapp --no-dev
COPY nextcloud_mcp_server/ ./nextcloud_mcp_server/
COPY appinfo/ ./appinfo/
ENV APPAPI_MODE=true
EXPOSE 8000
CMD ["uv", "run", "python", "-m", "nextcloud_mcp_server.app_exapp"]

Build Commands:

# Build OAuth variant
docker build --target oauth -t nextcloud-mcp-server:oauth .

# Build AppAPI variant
docker build --target exapp -t nextcloud-mcp-server:exapp .

# Multi-platform builds
docker buildx build --platform linux/amd64,linux/arm64 \
  --target oauth -t ghcr.io/cbcoutinho/nextcloud-mcp-server:oauth --push .

7. ExApp Manifest (appinfo/info.xml)

<?xml version="1.0"?>
<info xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="https://apps.nextcloud.com/schema/apps/info.xsd">
    <id>nextcloud_mcp_server</id>
    <name>Nextcloud MCP Server</name>
    <summary>Model Context Protocol server for AI assistant integration</summary>
    <description><![CDATA[
    Exposes Nextcloud functionality via the Model Context Protocol (MCP).
    Enables AI assistants like Claude to access Notes, Calendar, Contacts, Files, and more.
    Supports semantic search across all Nextcloud content via vector embeddings.
    ]]></description>

    <version>0.33.0</version>
    <licence>agpl</licence>
    <author>Chris Coutinho</author>

    <namespace>NextcloudMcpServer</namespace>
    <category>integration</category>
    <category>tools</category>

    <bugs>https://github.com/cbcoutinho/nextcloud-mcp-server/issues</bugs>
    <repository>https://github.com/cbcoutinho/nextcloud-mcp-server</repository>

    <dependencies>
        <nextcloud min-version="30" max-version="31"/>
    </dependencies>

    <external-app>
        <docker-install>
            <registry>ghcr.io</registry>
            <image>cbcoutinho/nextcloud-mcp-server</image>
            <image-tag>exapp</image-tag>
        </docker-install>

        <scopes>
            <value>FILES</value>
            <value>NOTIFICATIONS</value>
            <value>CALENDAR</value>
            <value>CONTACTS</value>
            <value>ALL</value>
        </scopes>

        <routes>
            <route>
                <url>/mcp.*</url>
                <verb>GET,POST</verb>
                <access_level>USER</access_level>
                <headers_to_exclude>[]</headers_to_exclude>
            </route>
        </routes>
    </external-app>
</info>

Proxy Route: /index.php/apps/app_api/proxy/nextcloud_mcp_server/mcp/sse

Configuration

OAuth Mode (Existing)

# Environment variables (unchanged)
NEXTCLOUD_HOST=https://nextcloud.example.com
OIDC_CLIENT_ID=abc123
OIDC_CLIENT_SECRET=xyz789
ENABLE_TOKEN_EXCHANGE=false

AppAPI Mode (New)

# Environment variables
APPAPI_MODE=true
APP_ID=nextcloud_mcp_server
APP_SECRET=<shared-secret-from-appapi>
APP_VERSION=0.33.0
NEXTCLOUD_URL=https://nextcloud.example.com

MCP Client Configuration

OAuth Mode

{
  "mcpServers": {
    "nextcloud": {
      "url": "https://mcp.example.com/sse",
      "transport": "sse"
    }
  }
}

AppAPI Mode (via Nextcloud Proxy)

{
  "mcpServers": {
    "nextcloud": {
      "url": "https://nextcloud.example.com/index.php/apps/app_api/proxy/nextcloud_mcp_server/mcp/sse",
      "transport": "sse",
      "headers": {
        "Cookie": "nc_session=<user-session-cookie>"
      }
    }
  }
}

Consequences

Positive

  1. Flexible Deployment: Users choose the mode that fits their use case—OAuth for multi-tenant SaaS, AppAPI for single-tenant integration.

  2. Simplified Administration: AppAPI mode enables one-click installation via Nextcloud app store, dramatically reducing setup complexity for single-tenant deployments.

  3. Native Nextcloud Integration: AppAPI mode unlocks UI components (menu items, file actions), event listeners, and dashboard widgets.

  4. No Breaking Changes: OAuth mode remains the default; all existing deployments continue working without modification.

  5. Maximum Code Sharing: 90%+ code reuse achieved through abstraction layer—tools, clients, models, and business logic fully shared.

  6. Future-Proof: As Nextcloud's ExApp ecosystem matures, we gain access to new integration capabilities without architectural changes.

  7. Ecosystem Compatibility: AppAPI mode makes the MCP server compatible with other Nextcloud ExApps, enabling potential cross-app integrations.

Negative

  1. Increased Complexity: Abstraction layer adds indirection and complexity to the codebase. Developers must understand both authentication mechanisms.

  2. Testing Burden: Must test both modes in CI/CD. Integration test matrix doubles in size.

  3. Dependency Management: Optional dependencies (authlib vs nc_py_api) complicate installation and development setup.

  4. Documentation Overhead: Users need clear guidance on when to use each mode, how they differ in behavior, and how to configure them.

  5. Maintenance Burden: Two authentication paths to maintain, two deployment modes to support, two sets of environment variables to document.

  6. Feature Parity Challenges: Some features may work better in one mode than the other (e.g., background jobs in OAuth mode, UI integration in AppAPI mode).

Neutral

  1. Separate Docker Images: Users explicitly choose via image tag (:oauth or :exapp), making mode selection clear but requiring separate builds.

  2. Mode-Specific Features: Some capabilities only work in specific modes (progressive consent in OAuth, UI registration in AppAPI).

  3. Community Bifurcation: User base may split between OAuth and AppAPI camps, potentially fragmenting community support.

  4. AppAPI Dependency: AppAPI mode's stability depends on Nextcloud's AppAPI maintenance and evolution.

Alternative Approaches Considered

Alternative 1: OAuth-Only (Status Quo)

Description: Continue with current OAuth architecture, do not add AppAPI support.

Pros:

  • No additional complexity
  • Single authentication mechanism to maintain
  • Works today with multi-tenant support

Cons:

  • Misses opportunity for simplified Nextcloud integration
  • Higher barrier to entry for single-tenant users
  • Cannot leverage ExApp ecosystem benefits
  • Requires separate deployment and management infrastructure

Rejected Because: Ignores clear demand for native Nextcloud integration and simpler deployment model.

Alternative 2: Migrate Fully to AppAPI

Description: Remove OAuth support entirely, implement only AppAPI authentication.

Pros:

  • Simpler codebase (single authentication mechanism)
  • Tight Nextcloud integration
  • Easier administration for Nextcloud users

Cons:

  • Breaking change: All existing OAuth deployments stop working
  • Loses multi-tenant capability: Cannot serve multiple Nextcloud instances from one MCP server
  • External client support lost: MCP clients not integrated with Nextcloud cannot connect
  • Background job complexity: Would need to reimplement background sync using Nextcloud cron

Rejected Because: Breaks existing deployments and eliminates valuable multi-tenant capabilities.

Alternative 3: Separate Repositories

Description: Fork codebase into nextcloud-mcp-server (OAuth) and nextcloud-mcp-exapp (AppAPI).

Pros:

  • Clean separation of concerns
  • No abstraction layer complexity
  • Independent versioning and release cycles

Cons:

  • Massive code duplication: 90%+ of code (tools, clients, models) duplicated
  • Maintenance nightmare: Bug fixes must be applied to both repos
  • Community fragmentation: Two projects instead of one unified effort
  • Feature divergence: Repositories inevitably drift apart over time

Rejected Because: Maintenance burden far outweighs benefits of clean separation.

Alternative 4: AppAPI Plugin to Existing OAuth Server

Description: Keep OAuth server unchanged, create separate AppAPI plugin that proxies to it.

Pros:

  • No changes to existing OAuth server
  • AppAPI support added as separate component

Cons:

  • Double proxying: Client → Nextcloud → AppAPI plugin → OAuth MCP server → Nextcloud
  • Authentication complexity: AppAPI plugin must translate headers to OAuth tokens
  • Performance overhead: Extra network hops add latency
  • Deployment complexity: Two containers to manage instead of one

Rejected Because: Architectural complexity and performance overhead unjustified.

Alternative 5: Runtime Mode Switching (Single Image)

Description: Single Docker image that detects mode at runtime via environment variable (no separate builds).

Pros:

  • Single image to build and distribute
  • Simplest CI/CD pipeline
  • Users can switch modes by changing environment variables

Cons:

  • Larger image size: Includes both authlib and nc_py_api dependencies
  • Unused code: OAuth-only deployments carry AppAPI code and vice versa
  • Security surface: All dependencies present even if not used

Decision: Implement multi-stage Docker builds instead for smaller, mode-specific images.

Implementation Plan

Phase 1: Foundation (Days 1-3)

Goal: Create abstraction layer without breaking existing OAuth functionality.

  1. Extract OAuth setup from app.py into app_oauth.py
  2. Add mode detection function to app.py
  3. Create auth/client_factory.py with get_client() abstraction
  4. Update context.py to accept Context | NextcloudApp union type
  5. Make @require_scopes decorator mode-aware
  6. Add conditional imports for nc_py_api (graceful degradation if not installed)
  7. Run full test suite—all existing OAuth tests must pass

Phase 2: AppAPI Implementation (Days 4-6)

Goal: Implement AppAPI mode and achieve feature parity with OAuth.

  1. Create app_exapp.py with FastAPI + AppAPIAuthMiddleware setup
  2. Implement auth/appapi_auth.py for header validation
  3. Add NextcloudClient.from_nc_py_api() adapter method
  4. Create appinfo/info.xml ExApp manifest
  5. Configure proxy routes for /mcp/* endpoints
  6. Implement ExApp-specific permission checking in scope_authorization.py
  7. Manual testing: Register ExApp in test Nextcloud instance, verify connection

Phase 3: Build & Deployment (Days 7-8)

Goal: Prepare distribution artifacts for both modes.

  1. Update Dockerfile with multi-stage builds (base → oauth, base → exapp)
  2. Add dependency groups to pyproject.toml (oauth, exapp)
  3. Update CI/CD pipeline to build both Docker images (:oauth, :exapp)
  4. Create AppAPI deployment documentation (docs/appapi-deployment.md)
  5. Update docs/installation.md with deployment options section
  6. Update docs/authentication.md with AppAPI mode details
  7. Test Docker builds locally for both modes

Phase 4: Testing & Documentation (Days 9-10)

Goal: Ensure both modes work correctly and are well-documented.

  1. Update tests/conftest.py to support mode-based fixtures
  2. Add TEST_MODE environment variable for test execution
  3. Run integration tests against both modes
  4. Add AppAPI-specific integration tests (proxy, header validation)
  5. Update README.md with deployment options comparison table
  6. Create this ADR (ADR-011)
  7. Write blog post / announcement explaining hybrid architecture

Total Estimated Time: 10 days

Success Criteria

  • All existing OAuth tests pass without modification
  • New AppAPI integration tests pass
  • Both Docker images build successfully
  • ExApp can be registered in test Nextcloud instance
  • MCP client can connect via AppAPI proxy
  • All MCP tools work in both modes
  • No breaking changes for existing OAuth deployments
  • Documentation covers both deployment modes

To Update

  • docs/installation.md - Add AppAPI installation section
  • docs/authentication.md - Document both authentication modes
  • docs/configuration.md - Add AppAPI environment variables
  • README.md - Add deployment options comparison

To Create

  • docs/appapi-deployment.md - Comprehensive AppAPI setup guide
  • docs/architecture-hybrid.md - Detailed hybrid architecture documentation
  • docs/comparison-oauth-vs-appapi.md - When to use each mode
  • ADR-004: Progressive Consent (OAuth-specific, note AppAPI differences)
  • ADR-005: Token Audience Validation (OAuth-specific)
  • ADR-007: Background Vector Sync (note implications for AppAPI mode)

Open Questions

Resolved via Research (2025-01-13)

The following questions have been investigated and resolved through detailed analysis of AppAPI and nc_py_api codebases:

1. Non-Browser Client Authentication Resolved

Question: How do MCP clients (Claude Desktop, CLI tools) authenticate to the AppAPI proxy when they can't handle browser-based session cookies?

Answer: AppAPI proxy currently relies on Nextcloud session cookies. Non-browser clients require one of:

  • App Passwords (Recommended Phase 1): Add BasicAuth support to AppAPI proxy. Users generate app passwords in Nextcloud UI, MCP clients send Authorization: Basic headers. Requires upstream contribution to AppAPI.
  • OAuth 2.0 (Long-term): Integrate MCP OAuth with Nextcloud OIDC provider, validate Bearer tokens via introspection.

Decision: Document app password requirement. Contribute BasicAuth support to AppAPI upstream. See "Authentication Challenges" section for implementation details.

2. Streaming Transport Compatibility Resolved

Question: How does the AppAPI proxy handle streamable HTTP? Will it work when MCP deprecates SSE?

Answer: AppAPI proxy supports buffered streaming via Guzzle's stream: true, not real-time streaming:

  • ExApp generates response chunks
  • Proxy accumulates complete response
  • Client receives full stream after completion
  • Works for bounded operations (finite event streams)
  • Does NOT support: Real-time incremental streaming, WebSocket protocol

Transport Compatibility:

  • Streamable HTTP (MCP 2025-03-26+): Works today, buffered
  • WebSocket: Would require major proxy rewrite (not viable)
  • ⚠️ stdio: Ideal for containerized ExApps but requires AppAPI changes (docker exec -i support)

Decision: Use Streamable HTTP transport for AppAPI mode. Document buffering limitations. Advocate for stdio transport support in future AppAPI versions. See "Streaming Limitations" section for details.

3. Callbacks and Progress Updates Resolved

Question: How do callbacks/webhooks work with ExApps? Can MCP's notification model work through the proxy?

Answer: Fundamental incompatibility between MCP's notification model and AppAPI's request/response architecture:

What DOES work:

  • Webhooks (Nextcloud → ExApp): Well supported via webhooks_listener
  • Notifications (ExApp → Nextcloud): Via nc.notifications.create()
  • Background tasks: Via FastAPI BackgroundTasks

What DOESN'T work:

  • MCP protocol notifications (server → client): No persistent connection through proxy
  • Real-time progress updates: No channel to original MCP client
  • Sampling/RAG features: Requires bidirectional MCP protocol

Decision:

  • Short operations (<30s): Direct request/response (works fine)
  • Long operations: Use BackgroundTasks + Nextcloud notifications
  • Sampling features: OAuth mode only (not possible in AppAPI)
  • Document limitations clearly in Feature Parity Matrix

See "Notification and Progress Update Limitations" section for workarounds.

Remaining Open Questions

4. Scope-to-Permission Mapping: How should MCP scopes (e.g., notes:write) map to Nextcloud capability checks? Need Nextcloud capability API documentation to implement robust permission validation in AppAPI mode.

5. Background Jobs Architecture: OAuth mode uses background workers with refresh tokens. AppAPI mode should likely use:

  • Webhook-driven processing (ADR-010 pattern)
  • Nextcloud cron jobs for periodic tasks
  • Different sync strategy than OAuth mode

6. UI Integration Priority: Should initial AppAPI implementation include UI components (menu items, file actions), or ship with proxy support only and add UI later? Recommend: Phase 1 without UI, add in Phase 2 if needed.

7. Version Compatibility: What range of Nextcloud versions should we support? info.xml currently specifies 30-31. Should we support older versions (28-29)?

These remaining questions should be resolved during implementation. Create follow-up issues to track decisions.

Conclusion

After comprehensive research and analysis, the hybrid OAuth + AppAPI architecture is not viable for this project's use case. While AppAPI ExApps provide value for in-app Nextcloud integration, the architectural constraints fundamentally conflict with MCP's protocol requirements for external client integration.

Key Findings

  1. Architectural Incompatibility is Fundamental

    • MCP requires multi-turn nested interactions (sampling, elicitation)
    • AppAPI provides stateless request/response proxy only
    • No amount of implementation effort can bridge this gap
    • Would require complete AppAPI proxy redesign (WebSocket support, message routing, stateful connections)
  2. Validation from Nextcloud's Own Projects

    • Context Agent (official Nextcloud ExApp with MCP) faces identical limitations
    • Works around them by using Task Processing API instead of MCP protocol
    • Proves limitations are inherent to ExApp architecture, not our implementation
    • Even official projects cannot make MCP's bidirectional features work through AppAPI proxy
  3. Critical Features Lost in AppAPI Mode

    • Sampling/RAG (ADR-008) - Core semantic search value proposition
    • Real-time progress updates - Essential for long-running operations
    • Bidirectional streaming - Foundation of MCP protocol
    • ⚠️ Buffered-only streaming - Defeats purpose of streaming
  4. Different Use Cases Require Different Solutions

    • External MCP clients (our use case): Require OAuth mode
    • In-app integration (Context Agent's use case): Use Task Processing API
    • These are fundamentally different products with different capabilities

Decision

Continue with OAuth mode exclusively. It provides all essential features:

  • Full MCP protocol support (sampling, elicitation, streaming)
  • Multi-tenant capability
  • External client integration (Claude Desktop, custom clients)
  • Fine-grained per-user permissions
  • Real-time progress updates
  • Production-ready architecture

Future Consideration

If in-app Nextcloud integration is desired later, the correct approach would be:

  • Register as Task Processing provider (separate from MCP server)
  • Integrate with Nextcloud Assistant UI
  • Use Task Processing API for orchestration
  • Accept this is a different product with different capabilities (no sampling, custom confirmation flows)

This ADR documents the research and analysis that led to this decision, preserving the investigation for future reference and demonstrating due diligence in exploring AppAPI integration.