nextcloud-agent 2.4.0__tar.gz → 2.4.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/PKG-INFO +1 -1
  2. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/nextcloud_agent/__init__.py +2 -2
  3. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/nextcloud_agent/api/api_client_base.py +12 -3
  4. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/nextcloud_agent/auth.py +24 -11
  5. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/nextcloud_agent/ontology/certification.json +1 -1
  6. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/nextcloud_agent.egg-info/PKG-INFO +1 -1
  7. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/pyproject.toml +1 -1
  8. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/tests/test_auth.py +17 -0
  9. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/tests/test_files_webdav.py +10 -0
  10. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/LICENSE +0 -0
  11. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/README.md +0 -0
  12. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/nextcloud_agent/__main__.py +0 -0
  13. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/nextcloud_agent/api/__init__.py +0 -0
  14. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/nextcloud_agent/api/api_client_calendar.py +0 -0
  15. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/nextcloud_agent/api/api_client_contacts.py +0 -0
  16. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/nextcloud_agent/api/api_client_files.py +0 -0
  17. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/nextcloud_agent/api/xml_security.py +0 -0
  18. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/nextcloud_agent/api_client.py +0 -0
  19. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/nextcloud_agent/connector_sources.py +0 -0
  20. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/nextcloud_agent/kg_ingest.py +0 -0
  21. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/nextcloud_agent/mcp_config.json +0 -0
  22. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/nextcloud_agent/mcp_server.py +0 -0
  23. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/nextcloud_agent/ontology/__init__.py +0 -0
  24. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/nextcloud_agent/ontology/fixtures/records.json +0 -0
  25. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/nextcloud_agent/ontology/mappings/source.yaml +0 -0
  26. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/nextcloud_agent/ontology/migrations/manifest.json +0 -0
  27. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/nextcloud_agent/ontology/nextcloud.ttl +0 -0
  28. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/nextcloud_agent/ontology/shapes/connector.shacl.ttl +0 -0
  29. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/nextcloud_agent/prompts/file_steward.json +0 -0
  30. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/nextcloud_agent/skills/nextcloud-agent-development/SKILL.md +0 -0
  31. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/nextcloud_agent/skills/nextcloud-agent-operations/SKILL.md +0 -0
  32. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/nextcloud_agent/skills/nextcloud-agent-operations/agents/openai.yaml +0 -0
  33. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/nextcloud_agent/skills/nextcloud-agent-operations/references/catalog.md +0 -0
  34. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/nextcloud_agent/skills/nextcloud-files/SKILL.md +0 -0
  35. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/nextcloud_agent/skills/nextcloud-files/WORKFLOW.md +0 -0
  36. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/nextcloud_agent/skills/nextcloud-ingest/SKILL.md +0 -0
  37. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/nextcloud_agent/skills/nextcloud-ingest/WORKFLOW.md +0 -0
  38. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/nextcloud_agent/skills/nextcloud-shares/SKILL.md +0 -0
  39. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/nextcloud_agent/skills/nextcloud-shares/WORKFLOW.md +0 -0
  40. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/nextcloud_agent.egg-info/SOURCES.txt +0 -0
  41. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/nextcloud_agent.egg-info/dependency_links.txt +0 -0
  42. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/nextcloud_agent.egg-info/entry_points.txt +0 -0
  43. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/nextcloud_agent.egg-info/requires.txt +0 -0
  44. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/nextcloud_agent.egg-info/top_level.txt +0 -0
  45. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/setup.cfg +0 -0
  46. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/tests/conftest.py +0 -0
  47. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/tests/test_api_client.py +0 -0
  48. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/tests/test_fleet_name.py +0 -0
  49. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/tests/test_init.py +0 -0
  50. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/tests/test_init_dynamics.py +0 -0
  51. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/tests/test_kg_ingest.py +0 -0
  52. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/tests/test_mcp_server.py +0 -0
  53. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/tests/test_publication_workflow.py +0 -0
  54. {nextcloud_agent-2.4.0 → nextcloud_agent-2.4.2}/tests/test_xml_security.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nextcloud-agent
3
- Version: 2.4.0
3
+ Version: 2.4.2
4
4
  Summary: Nextcloud MCP Server for Agentic AI!
5
5
  Author-email: Repository Maintainers <maintainers@knuckles.team>
6
6
  License: MIT
@@ -1,11 +1,11 @@
1
1
  from scion_sdk.connector import Connector
2
2
 
3
3
  from nextcloud_agent.api_client import NextcloudAPI
4
- from nextcloud_agent.auth import get_client
4
+ from nextcloud_agent.auth import make_client
5
5
 
6
6
  connector = Connector(
7
7
  api=NextcloudAPI,
8
- get_client=get_client,
8
+ get_client=make_client,
9
9
  tools="nextcloud_agent.mcp_server",
10
10
  instructions="nextcloud-agent MCP Server — Condensed Action-Routed Tools.",
11
11
  )
@@ -33,6 +33,12 @@ class _StreamedResponse(Protocol):
33
33
  def close(self) -> Any: ...
34
34
 
35
35
 
36
+ def _allowed_private_hosts() -> set[str]:
37
+ """Hosts the operator admits for private addresses and plain http (R002)."""
38
+ raw = setting("NEXTCLOUD_ALLOWED_PRIVATE_HOSTS", "")
39
+ return {host.lower().rstrip(".") for host in csv_values(raw)}
40
+
41
+
36
42
  def _origin(url: str, *, require_clean_base: bool = False) -> tuple[str, str, int]:
37
43
  try:
38
44
  parsed = urlsplit(url)
@@ -57,8 +63,11 @@ def _origin(url: str, *, require_clean_base: bool = False) -> tuple[str, str, in
57
63
  loopback = ipaddress.ip_address(host).is_loopback
58
64
  except ValueError:
59
65
  loopback = host == "localhost"
60
- if not loopback:
61
- raise ValueError("unencrypted service URL is restricted to loopback")
66
+ if not loopback and host not in _allowed_private_hosts():
67
+ raise ValueError(
68
+ "unencrypted service URL is restricted to loopback and "
69
+ "NEXTCLOUD_ALLOWED_PRIVATE_HOSTS"
70
+ )
62
71
  return scheme, host, port
63
72
 
64
73
 
@@ -169,7 +178,7 @@ class BaseApiClient:
169
178
  def files_dav(self) -> WebDavBackend:
170
179
  """The SDK WebDAV backend on the files root (CONN-NEXTCLOUD-AGENT-R002)."""
171
180
  if self._files_dav is None:
172
- hosts = tuple(csv_values(setting("NEXTCLOUD_ALLOWED_PRIVATE_HOSTS", "")))
181
+ hosts = tuple(sorted(_allowed_private_hosts()))
173
182
  self._files_dav = WebDavBackend(
174
183
  self.webdav_base,
175
184
  username=self.username,
@@ -8,19 +8,16 @@ from nextcloud_agent.api_client import NextcloudAPI
8
8
  logger = logging.getLogger(__name__)
9
9
 
10
10
 
11
- def get_client(
11
+ def make_client(
12
12
  base_url: str | None = None,
13
13
  username: str | None = None,
14
14
  password: str | None = None,
15
- ):
16
- """
17
- Yield a NextcloudAPI client and close it afterwards.
15
+ ) -> NextcloudAPI:
16
+ """Build one NextcloudAPI client from arguments or the connector settings.
18
17
 
19
- A plain generator, because FastMCP's ``Depends`` drives generator
20
- dependencies itself; wrapping it in ``contextlib.contextmanager`` handed
21
- every tool the context-manager object instead of the client.
22
-
23
- CONCEPT:AU-OS.config.secrets-authentication
18
+ The SDK per-operation tools call this factory once per call
19
+ (CONN-NEXTCLOUD-AGENT-R002); ``get_client`` wraps it as a FastMCP
20
+ generator dependency that also closes the client.
24
21
  """
25
22
  if not base_url:
26
23
  base_url = setting("NEXTCLOUD_URL", None)
@@ -32,15 +29,31 @@ def get_client(
32
29
  raise ValueError(
33
30
  "Nextcloud URL, username, and password must be provided via arguments or environment variables."
34
31
  )
35
-
36
32
  try:
37
- client = NextcloudAPI(base_url=base_url, username=username, password=password)
33
+ return NextcloudAPI(base_url=base_url, username=username, password=password)
38
34
  except (AuthError, UnauthorizedError) as e:
39
35
  raise RuntimeError(
40
36
  "AUTHENTICATION ERROR: The configured credentials were rejected. "
41
37
  f"Please check your NEXTCLOUD_USERNAME and NEXTCLOUD_PASSWORD environment variables. "
42
38
  f"Error details: {type(e).__name__}"
43
39
  ) from e
40
+
41
+
42
+ def get_client(
43
+ base_url: str | None = None,
44
+ username: str | None = None,
45
+ password: str | None = None,
46
+ ):
47
+ """
48
+ Yield a NextcloudAPI client and close it afterwards.
49
+
50
+ A plain generator, because FastMCP's ``Depends`` drives generator
51
+ dependencies itself; wrapping it in ``contextlib.contextmanager`` handed
52
+ every tool the context-manager object instead of the client.
53
+
54
+ CONCEPT:AU-OS.config.secrets-authentication
55
+ """
56
+ client = make_client(base_url=base_url, username=username, password=password)
44
57
  try:
45
58
  yield client
46
59
  finally:
@@ -29,5 +29,5 @@
29
29
  "signer": "UNSIGNED-PREVIEW",
30
30
  "signing_public_key": "",
31
31
  "status": "source-validated",
32
- "validated_at": "2026-11-05T11:12:30Z"
32
+ "validated_at": "2026-11-05T12:20:44Z"
33
33
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nextcloud-agent
3
- Version: 2.4.0
3
+ Version: 2.4.2
4
4
  Summary: Nextcloud MCP Server for Agentic AI!
5
5
  Author-email: Repository Maintainers <maintainers@knuckles.team>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "nextcloud-agent"
7
- version = "2.4.0"
7
+ version = "2.4.2"
8
8
  description = "Nextcloud MCP Server for Agentic AI!"
9
9
  readme = "README.md"
10
10
  classifiers = [ "Development Status :: 3 - Alpha", "License :: OSI Approved :: MIT License", "Environment :: Console", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3",]
@@ -75,3 +75,20 @@ def test_get_client_is_a_generator_dependency_that_yields_the_client():
75
75
  assert client.base_url == "https://x.test"
76
76
  assert not hasattr(client, "__enter__") or hasattr(client, "get_user_info")
77
77
  gen.close()
78
+
79
+
80
+ @pytest.mark.spec("CONN-NEXTCLOUD-AGENT-R002")
81
+ async def test_per_operation_tools_resolve_a_client_not_a_generator(monkeypatch):
82
+ """The SDK per-operation tools call ``connector.get_client()`` directly."""
83
+ from scion_sdk.mcp._method_invoke import _resolve_client
84
+
85
+ from nextcloud_agent import connector
86
+
87
+ monkeypatch.setenv("NEXTCLOUD_URL", "https://cloud.example")
88
+ monkeypatch.setenv("NEXTCLOUD_USERNAME", "svc")
89
+ monkeypatch.setenv("NEXTCLOUD_PASSWORD", "pw")
90
+ client = await _resolve_client(connector.get_client)
91
+ try:
92
+ assert callable(client.list_files)
93
+ finally:
94
+ client.close()
@@ -143,3 +143,13 @@ def test_sdk_pin_carries_storage_backend():
143
143
  from scion_sdk.storage import WebDavBackend as Backend
144
144
 
145
145
  assert Backend.backend_type == "webdav"
146
+
147
+
148
+ @pytest.mark.spec("CONN-NEXTCLOUD-AGENT-R002")
149
+ def test_plain_http_is_admitted_only_for_listed_hosts(monkeypatch):
150
+ monkeypatch.delenv("NEXTCLOUD_ALLOWED_PRIVATE_HOSTS", raising=False)
151
+ with pytest.raises(ValueError, match="restricted to loopback"):
152
+ NextcloudAPI(base_url="http://files.example", username="svc", password="pw")
153
+ monkeypatch.setenv("NEXTCLOUD_ALLOWED_PRIVATE_HOSTS", "files.example")
154
+ client = NextcloudAPI(base_url="http://files.example", username="svc", password="pw")
155
+ assert client.files_dav.backend_type == "webdav"
File without changes