Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 92f2d74637 | |||
| 656acc2c1f | |||
| c726e25e8b | |||
| 355bd1bad3 | |||
| 989d3f2857 | |||
| 92d5cd4e26 | |||
| 5823286907 | |||
| 7fb6613bc2 | |||
| cd6f0ffa63 | |||
| 5d98858bb6 | |||
| af7c752cc1 | |||
| 2526390ce8 | |||
| 0b5571f3d7 | |||
| 059f37d093 | |||
| 28ad0aefbf | |||
| 6ce9599757 | |||
| 1cdf148899 |
@@ -33,7 +33,7 @@ jobs:
|
||||
|
||||
- name: Run Claude Code Review
|
||||
id: claude-review
|
||||
uses: anthropics/claude-code-action@64c7a0ef71df67b14cb4471f4d9c8565c61042bf # v1.0.66
|
||||
uses: anthropics/claude-code-action@cd77b50d2b0808657f8e6774085c8bf54484351c # v1.0.72
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
allowed_bots: "renovate-bot-cbcoutinho"
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
|
||||
- name: Run Claude Code
|
||||
id: claude
|
||||
uses: anthropics/claude-code-action@64c7a0ef71df67b14cb4471f4d9c8565c61042bf # v1.0.66
|
||||
uses: anthropics/claude-code-action@cd77b50d2b0808657f8e6774085c8bf54484351c # v1.0.72
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
VECTOR_SYNC_SCAN_INTERVAL: "5"
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1
|
||||
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
|
||||
|
||||
- name: Wait for Nextcloud to be ready
|
||||
run: |
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1
|
||||
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
|
||||
- name: Install Python 3.11
|
||||
run: uv python install 3.11
|
||||
- name: Build
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1
|
||||
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
|
||||
- name: Check format
|
||||
run: uv run --frozen ruff format --diff
|
||||
- name: Linting
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1
|
||||
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
|
||||
- name: Run unit tests
|
||||
run: uv run pytest -v -m unit -o "addopts=-p no:asyncio"
|
||||
|
||||
@@ -48,14 +48,14 @@ jobs:
|
||||
# Version-specific image pins — Renovate updates these via customManagers
|
||||
# renovate: datasource=docker depName=docker.io/library/nextcloud
|
||||
- nextcloud_version: "31"
|
||||
nextcloud_image: "docker.io/library/nextcloud:31.0.8@sha256:92bc503ea0c19789f402b0469ecfb8df1ffea81e2bf90a45bba39063a626aa00"
|
||||
nextcloud_image: "docker.io/library/nextcloud:31.0.14@sha256:9bf3fae91aad4dca3eff02c1f71df8d5c6705a349065fb537aa5c5ef578f1013"
|
||||
# renovate: datasource=docker depName=docker.io/library/nextcloud
|
||||
- nextcloud_version: "32"
|
||||
nextcloud_image: "docker.io/library/nextcloud:32.0.6@sha256:5c4e09f72f096cd68379a8ae69f71e61d13da5a07430fc4a17c702a14e6a4267"
|
||||
# renovate: datasource=docker depName=docker.io/library/nextcloud
|
||||
# Disabled until all upstream apps support NC 33
|
||||
# - nextcloud_version: "33"
|
||||
# nextcloud_image: "docker.io/library/nextcloud:33.0.0@sha256:ff2cbaab14c85e587b5541e3aff4216a8a484e06424ebae661581937c0c8da0c"
|
||||
# nextcloud_image: "docker.io/library/nextcloud:33.0.0@sha256:d53f6cb35b0712aa890a5e4a8ca21043d6fcd390f38c55b710816dd7cbc2edc0"
|
||||
|
||||
# Mode-specific properties
|
||||
- mode: single-user
|
||||
@@ -112,7 +112,7 @@ jobs:
|
||||
if: matrix.mode != 'single-user'
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: 24
|
||||
|
||||
- name: Build Astrolabe app
|
||||
if: matrix.mode != 'single-user'
|
||||
@@ -134,7 +134,7 @@ jobs:
|
||||
NEXTCLOUD_IMAGE: ${{ matrix.nextcloud_image }}
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1
|
||||
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
|
||||
|
||||
- name: Install Playwright
|
||||
if: matrix.needs-playwright
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.commitizen]
|
||||
name = "cz_conventional_commits"
|
||||
version = "0.57.94"
|
||||
version = "0.58.3"
|
||||
tag_format = "nextcloud-mcp-server-$version"
|
||||
version_scheme = "semver"
|
||||
update_changelog_on_bump = true
|
||||
|
||||
@@ -14,6 +14,41 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Configurable resource limits
|
||||
- Grafana dashboard annotations
|
||||
|
||||
## nextcloud-mcp-server-0.58.3 (2026-03-16)
|
||||
|
||||
## nextcloud-mcp-server-0.58.2 (2026-03-14)
|
||||
|
||||
## nextcloud-mcp-server-0.58.1 (2026-03-03)
|
||||
|
||||
## nextcloud-mcp-server-0.58.0 (2026-03-03)
|
||||
|
||||
### Feat
|
||||
|
||||
- **auth**: implement OAuth AS proxy to fix audience mismatch (ADR-023)
|
||||
- **ci**: add Nextcloud version matrix (NC 31, 32, 33)
|
||||
- **helm**: add login-flow auth mode to Helm chart (ADR-022)
|
||||
- add Docker Compose profiles and Login Flow v2 service
|
||||
|
||||
### Fix
|
||||
|
||||
- replace assert with proper guard and invalidate scope cache after provisioning
|
||||
- disable NC rate limiting in dev/CI and add token endpoint diagnostics
|
||||
- address review feedback — security, caching, CI 429 retry
|
||||
- skip keycloak hook when profile inactive and update stale PRM test
|
||||
- address remaining PR #589 review findings
|
||||
- address PR #589 review findings
|
||||
- address PR review issues for Login Flow v2
|
||||
- address PR #589 review feedback (round 2)
|
||||
- **ci**: remove dev OIDC mount to fix HTTP 500 in single-user/multi-user-basic
|
||||
- **ci**: fix health check timeout and per-profile MCP server URL routing
|
||||
- **ci**: fix PHP gating, add multi-user-basic matrix entry, upload debug artifacts
|
||||
- address PR #589 review feedback for Login Flow v2
|
||||
- **ci**: fix integration test collection and skip Playwright in CI
|
||||
- **test**: fix 17 pre-existing unit test failures and add astrolabe CI build
|
||||
- **ci**: keep third_party mount, always build submodules in CI
|
||||
- **ci**: revert accidental third_party mount, use compose override for OIDC
|
||||
- **ci**: don't block integration matrix on unit-test failures
|
||||
|
||||
## nextcloud-mcp-server-0.57.94 (2026-03-03)
|
||||
|
||||
### Fix
|
||||
|
||||
@@ -2,7 +2,7 @@ apiVersion: v2
|
||||
name: nextcloud-mcp-server
|
||||
description: A Helm chart for Nextcloud MCP Server - enables AI assistants to interact with Nextcloud
|
||||
type: application
|
||||
version: 0.57.94
|
||||
version: 0.58.3
|
||||
appVersion: "0.65.0"
|
||||
keywords:
|
||||
- nextcloud
|
||||
|
||||
@@ -235,24 +235,26 @@ async def get_server_status(request: Request) -> JSONResponse:
|
||||
if mode == AuthMode.MULTI_USER_BASIC:
|
||||
response_data["supports_app_passwords"] = settings.enable_offline_access
|
||||
|
||||
# Include OIDC configuration if OAuth is available
|
||||
# This includes OAuth mode AND hybrid mode (multi_user_basic + offline_access)
|
||||
# Astrolabe needs OIDC config to discover IdP for OAuth flow in hybrid mode
|
||||
oauth_provisioning_available = auth_mode == "oauth" or (
|
||||
mode == AuthMode.MULTI_USER_BASIC and settings.enable_offline_access
|
||||
)
|
||||
if oauth_provisioning_available:
|
||||
# Provide IdP discovery information for NC PHP app
|
||||
oidc_config = {}
|
||||
# Include OIDC configuration for client discovery (e.g. Astrolabe PHP app).
|
||||
# Always attempt to provide oidc.discovery_url so clients can discover the
|
||||
# IdP regardless of the current auth mode. This enables smoother transitions
|
||||
# between auth modes and lets Astrolabe pre-discover OIDC endpoints.
|
||||
oidc_config: dict[str, str] = {}
|
||||
|
||||
if settings.oidc_discovery_url:
|
||||
oidc_config["discovery_url"] = settings.oidc_discovery_url
|
||||
if settings.oidc_discovery_url:
|
||||
# Explicit OIDC_DISCOVERY_URL takes precedence
|
||||
oidc_config["discovery_url"] = settings.oidc_discovery_url
|
||||
elif settings.nextcloud_host:
|
||||
# Auto-derive from NEXTCLOUD_HOST — Nextcloud exposes OIDC discovery
|
||||
# at the standard well-known path when user_oidc is enabled
|
||||
host = settings.nextcloud_host.rstrip("/")
|
||||
oidc_config["discovery_url"] = f"{host}/.well-known/openid-configuration"
|
||||
|
||||
if settings.oidc_issuer:
|
||||
oidc_config["issuer"] = settings.oidc_issuer
|
||||
if settings.oidc_issuer:
|
||||
oidc_config["issuer"] = settings.oidc_issuer
|
||||
|
||||
if oidc_config:
|
||||
response_data["oidc"] = oidc_config
|
||||
if oidc_config:
|
||||
response_data["oidc"] = oidc_config
|
||||
|
||||
return JSONResponse(response_data)
|
||||
|
||||
|
||||
+6
-2
@@ -7,14 +7,18 @@
|
||||
"dependencyDashboard": true,
|
||||
"packageRules": [
|
||||
{
|
||||
"matchPackageNames": ["pillow"],
|
||||
"matchPackageNames": [
|
||||
"pillow"
|
||||
],
|
||||
"allowedVersions": "<12.0.0"
|
||||
}
|
||||
],
|
||||
"customManagers": [
|
||||
{
|
||||
"customType": "regex",
|
||||
"fileMatch": ["^\\.github/workflows/test\\.yml$"],
|
||||
"managerFilePatterns": [
|
||||
"/^\\.github/workflows/test\\.yml$/"
|
||||
],
|
||||
"matchStrings": [
|
||||
"nextcloud_image:\\s*\"(?<depName>[^:]+):(?<currentValue>[^@]+)@(?<currentDigest>sha256:[a-f0-9]+)\""
|
||||
],
|
||||
|
||||
@@ -37,6 +37,7 @@ def create_mock_settings(
|
||||
oidc_issuer: str | None = None,
|
||||
vector_sync_enabled: bool = False,
|
||||
nextcloud_url: str = "http://localhost",
|
||||
nextcloud_host: str | None = "http://localhost",
|
||||
enable_token_exchange: bool = False,
|
||||
mcp_client_id: str | None = None,
|
||||
mcp_client_secret: str | None = None,
|
||||
@@ -49,6 +50,7 @@ def create_mock_settings(
|
||||
settings.oidc_issuer = oidc_issuer
|
||||
settings.vector_sync_enabled = vector_sync_enabled
|
||||
settings.nextcloud_url = nextcloud_url
|
||||
settings.nextcloud_host = nextcloud_host
|
||||
settings.enable_token_exchange = enable_token_exchange
|
||||
settings.mcp_client_id = mcp_client_id
|
||||
settings.mcp_client_secret = mcp_client_secret
|
||||
@@ -133,6 +135,7 @@ class TestStatusEndpointOidcConfig:
|
||||
enable_offline_access=False, # Key difference: no offline access
|
||||
oidc_discovery_url="http://keycloak/.well-known/openid-configuration",
|
||||
oidc_issuer="http://keycloak/realms/test",
|
||||
nextcloud_host=None,
|
||||
)
|
||||
|
||||
with (
|
||||
@@ -196,12 +199,13 @@ class TestStatusEndpointOidcConfig:
|
||||
)
|
||||
|
||||
def test_single_user_basic_no_oidc(self):
|
||||
"""Test that single-user BasicAuth mode doesn't return OIDC config."""
|
||||
"""Test that single-user BasicAuth mode doesn't return OIDC config when no host."""
|
||||
mock_settings = create_mock_settings(
|
||||
enable_multi_user_basic=False,
|
||||
enable_offline_access=False,
|
||||
oidc_discovery_url="http://keycloak/.well-known/openid-configuration",
|
||||
oidc_issuer="http://keycloak/realms/test",
|
||||
nextcloud_host=None,
|
||||
)
|
||||
|
||||
with (
|
||||
@@ -344,3 +348,127 @@ class TestStatusEndpointBasicResponse:
|
||||
data = response.json()
|
||||
|
||||
assert data["vector_sync_enabled"] is True
|
||||
|
||||
|
||||
class TestStatusEndpointOidcAutoDerivation:
|
||||
"""Tests for OIDC discovery_url auto-derivation from NEXTCLOUD_HOST."""
|
||||
|
||||
def test_derives_discovery_url_from_nextcloud_host(self):
|
||||
"""Test that discovery_url is auto-derived from nextcloud_url when not explicit."""
|
||||
mock_settings = create_mock_settings(
|
||||
oidc_discovery_url=None,
|
||||
oidc_issuer=None,
|
||||
)
|
||||
mock_settings.nextcloud_host = "https://cloud.example.com"
|
||||
|
||||
with (
|
||||
patch(
|
||||
"nextcloud_mcp_server.api.management.get_settings",
|
||||
return_value=mock_settings,
|
||||
),
|
||||
patch(
|
||||
"nextcloud_mcp_server.api.management.detect_auth_mode",
|
||||
return_value=AuthMode.SINGLE_USER_BASIC,
|
||||
),
|
||||
):
|
||||
app = create_test_app()
|
||||
client = TestClient(app)
|
||||
response = client.get("/api/v1/status")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
||||
assert "oidc" in data
|
||||
assert (
|
||||
data["oidc"]["discovery_url"]
|
||||
== "https://cloud.example.com/.well-known/openid-configuration"
|
||||
)
|
||||
|
||||
def test_derives_discovery_url_strips_trailing_slash(self):
|
||||
"""Test that trailing slash on nextcloud_host is stripped."""
|
||||
mock_settings = create_mock_settings(
|
||||
oidc_discovery_url=None,
|
||||
oidc_issuer=None,
|
||||
)
|
||||
mock_settings.nextcloud_host = "https://cloud.example.com/"
|
||||
|
||||
with (
|
||||
patch(
|
||||
"nextcloud_mcp_server.api.management.get_settings",
|
||||
return_value=mock_settings,
|
||||
),
|
||||
patch(
|
||||
"nextcloud_mcp_server.api.management.detect_auth_mode",
|
||||
return_value=AuthMode.SINGLE_USER_BASIC,
|
||||
),
|
||||
):
|
||||
app = create_test_app()
|
||||
client = TestClient(app)
|
||||
response = client.get("/api/v1/status")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
||||
assert "oidc" in data
|
||||
assert (
|
||||
data["oidc"]["discovery_url"]
|
||||
== "https://cloud.example.com/.well-known/openid-configuration"
|
||||
)
|
||||
|
||||
def test_explicit_discovery_url_takes_precedence(self):
|
||||
"""Test that explicit OIDC_DISCOVERY_URL overrides auto-derivation."""
|
||||
mock_settings = create_mock_settings(
|
||||
oidc_discovery_url="https://keycloak.example.com/.well-known/openid-configuration",
|
||||
oidc_issuer=None,
|
||||
)
|
||||
mock_settings.nextcloud_host = "https://cloud.example.com"
|
||||
|
||||
with (
|
||||
patch(
|
||||
"nextcloud_mcp_server.api.management.get_settings",
|
||||
return_value=mock_settings,
|
||||
),
|
||||
patch(
|
||||
"nextcloud_mcp_server.api.management.detect_auth_mode",
|
||||
return_value=AuthMode.SINGLE_USER_BASIC,
|
||||
),
|
||||
):
|
||||
app = create_test_app()
|
||||
client = TestClient(app)
|
||||
response = client.get("/api/v1/status")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
||||
assert "oidc" in data
|
||||
assert (
|
||||
data["oidc"]["discovery_url"]
|
||||
== "https://keycloak.example.com/.well-known/openid-configuration"
|
||||
)
|
||||
|
||||
def test_no_oidc_when_no_host_and_no_discovery_url(self):
|
||||
"""Test that oidc block is absent when neither host nor discovery_url is set."""
|
||||
mock_settings = create_mock_settings(
|
||||
oidc_discovery_url=None,
|
||||
oidc_issuer=None,
|
||||
)
|
||||
mock_settings.nextcloud_host = None
|
||||
|
||||
with (
|
||||
patch(
|
||||
"nextcloud_mcp_server.api.management.get_settings",
|
||||
return_value=mock_settings,
|
||||
),
|
||||
patch(
|
||||
"nextcloud_mcp_server.api.management.detect_auth_mode",
|
||||
return_value=AuthMode.SINGLE_USER_BASIC,
|
||||
),
|
||||
):
|
||||
app = create_test_app()
|
||||
client = TestClient(app)
|
||||
response = client.get("/api/v1/status")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
||||
assert "oidc" not in data
|
||||
|
||||
Reference in New Issue
Block a user