Merge remote-tracking branch 'origin/master' into fix/contacts-create-dropped-fields-716

This commit is contained in:
Chris Coutinho
2026-04-23 07:31:09 +02:00
9 changed files with 665 additions and 103 deletions
+30 -20
View File
@@ -23,10 +23,8 @@ from nextcloud_mcp_server.api.management import (
_parse_int_param,
_sanitize_error_for_client,
_validate_query_string,
extract_bearer_token,
validate_token_and_get_user,
)
from nextcloud_mcp_server.client import NextcloudClient
from nextcloud_mcp_server.config import get_settings
from nextcloud_mcp_server.embedding.service import get_embedding_service
from nextcloud_mcp_server.search import (
@@ -34,6 +32,10 @@ from nextcloud_mcp_server.search import (
SemanticSearchAlgorithm,
)
from nextcloud_mcp_server.search.context import get_chunk_with_context
from nextcloud_mcp_server.vector.oauth_sync import (
NotProvisionedError,
get_user_client_basic_auth,
)
from nextcloud_mcp_server.vector.placeholder import get_placeholder_filter
from nextcloud_mcp_server.vector.qdrant_client import get_qdrant_client
from nextcloud_mcp_server.vector.visualization import compute_pca_coordinates
@@ -512,11 +514,6 @@ async def get_chunk_context(request: Request) -> JSONResponse:
# Convert doc_id to int if possible (most IDs are int)
doc_id_val: str | int = int(doc_id) if doc_id.isdigit() else doc_id
# Get bearer token for client initialization
token = extract_bearer_token(request)
if not token:
raise ValueError("Missing token")
# Get Nextcloud host from OAuth context
oauth_ctx = request.app.state.oauth_context
nextcloud_host = oauth_ctx.get("config", {}).get("nextcloud_host", "")
@@ -524,10 +521,19 @@ async def get_chunk_context(request: Request) -> JSONResponse:
if not nextcloud_host:
raise ValueError("Nextcloud host not configured")
# Initialize authenticated Nextcloud client
async with NextcloudClient.from_token(
base_url=nextcloud_host, token=token, username=user_id
) as nc_client:
# Use the user's stored app password for Nextcloud calls.
# The OAuth bearer is only used to authenticate Astrolabe → MCP Server;
# MCP Server → Nextcloud always uses the app password provisioned
# during the authorization step.
try:
nc_client = await get_user_client_basic_auth(user_id, nextcloud_host)
except NotProvisionedError as e:
return JSONResponse(
{"success": False, "error": str(e)},
status_code=401,
)
async with nc_client:
chunk_context = await get_chunk_with_context(
nc_client=nc_client,
user_id=user_id,
@@ -687,11 +693,6 @@ async def get_pdf_preview(request: Request) -> JSONResponse:
except ValueError as e:
return JSONResponse({"success": False, "error": str(e)}, status_code=400)
# Get bearer token for WebDAV authentication
token = extract_bearer_token(request)
if not token:
raise ValueError("Missing token")
# Get Nextcloud host from OAuth context
oauth_ctx = request.app.state.oauth_context
nextcloud_host = oauth_ctx.get("config", {}).get("nextcloud_host", "")
@@ -699,10 +700,19 @@ async def get_pdf_preview(request: Request) -> JSONResponse:
if not nextcloud_host:
raise ValueError("Nextcloud host not configured")
# Download PDF via WebDAV using user's token
async with NextcloudClient.from_token(
base_url=nextcloud_host, token=token, username=user_id
) as nc_client:
# Use the user's stored app password for Nextcloud calls.
# The OAuth bearer is only used to authenticate Astrolabe → MCP Server;
# MCP Server → Nextcloud always uses the app password provisioned
# during the authorization step.
try:
nc_client = await get_user_client_basic_auth(user_id, nextcloud_host)
except NotProvisionedError as e:
return JSONResponse(
{"success": False, "error": str(e)},
status_code=401,
)
async with nc_client:
pdf_bytes, _ = await nc_client.webdav.read_file(file_path)
# Check file size limit (50 MB)
+24 -4
View File
@@ -34,6 +34,10 @@ from nextcloud_mcp_server.search import (
SemanticSearchAlgorithm,
)
from nextcloud_mcp_server.search.context import get_chunk_with_context
from nextcloud_mcp_server.vector.oauth_sync import (
NotProvisionedError,
get_user_client_basic_auth,
)
from nextcloud_mcp_server.vector.pca import PCA
from nextcloud_mcp_server.vector.placeholder import get_placeholder_filter
from nextcloud_mcp_server.vector.qdrant_client import get_qdrant_client
@@ -554,12 +558,28 @@ async def chunk_context_endpoint(request: Request) -> JSONResponse:
# Convert doc_id to int (all document types use int IDs)
doc_id_int = int(doc_id)
# Get authenticated Nextcloud client
# Use context expansion module to fetch chunk with surrounding context
async with await _get_authenticated_client_for_userinfo(request) as nc_client:
user_id = request.user.display_name
settings = get_settings()
nextcloud_host = settings.nextcloud_host
if not nextcloud_host:
raise RuntimeError("Nextcloud host not configured")
# Use the user's stored app password for Nextcloud calls.
# The session cookie only authenticates the browser → MCP Server hop;
# MCP Server → Nextcloud always uses the app password provisioned
# during the authorization step.
try:
nc_client = await get_user_client_basic_auth(user_id, nextcloud_host)
except NotProvisionedError as e:
return JSONResponse(
{"success": False, "error": str(e)},
status_code=401,
)
async with nc_client:
chunk_context = await get_chunk_with_context(
nc_client=nc_client,
user_id=request.user.display_name, # User ID from auth
user_id=user_id,
doc_id=doc_id_int,
doc_type=doc_type,
chunk_start=start,