mcp-server-appwrite 2.1.2__tar.gz → 2.1.3__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 (132) hide show
  1. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/PKG-INFO +1 -1
  2. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/data/docs_index.npz +1 -1
  3. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/server.py +4 -1
  4. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/e2e/support.py +31 -9
  5. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/e2e/test_tools.py +21 -3
  6. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/.dockerignore +0 -0
  7. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/.env.example +0 -0
  8. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/.github/workflows/ci.yml +0 -0
  9. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/.github/workflows/production.yml +0 -0
  10. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/.github/workflows/publish.yml +0 -0
  11. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/.github/workflows/staging.yml +0 -0
  12. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/.github/workflows/update-docs-index.yml +0 -0
  13. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/.gitignore +0 -0
  14. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/.python-version +0 -0
  15. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/AGENTS.md +0 -0
  16. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/CLAUDE.md +0 -0
  17. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/Dockerfile +0 -0
  18. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/LICENSE +0 -0
  19. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/README.md +0 -0
  20. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/compose.yaml +0 -0
  21. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/docs/apps.md +0 -0
  22. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/docs/appwrite-mcp-flow.png +0 -0
  23. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/docs/appwrite-mcp-flow.svg +0 -0
  24. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/docs/authentication.md +0 -0
  25. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/docs/development.md +0 -0
  26. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/docs/documentation-search.md +0 -0
  27. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/docs/events.md +0 -0
  28. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/docs/flags.md +0 -0
  29. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/docs/images/claude-desktop-local-mcp-servers.png +0 -0
  30. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/docs/images/codex-mcp.png +0 -0
  31. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/docs/images/cursor-mcp.png +0 -0
  32. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/docs/self-hosted.md +0 -0
  33. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/docs/tool-surface.md +0 -0
  34. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/glama.json +0 -0
  35. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/pyproject.toml +0 -0
  36. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/scripts/build_docs_index.py +0 -0
  37. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/scripts/docs_release_notes.py +0 -0
  38. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/scripts/render_server_json.py +0 -0
  39. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/server.template.json +0 -0
  40. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/__init__.py +0 -0
  41. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/__main__.py +0 -0
  42. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/apps/__init__.py +0 -0
  43. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/apps/protocol.py +0 -0
  44. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/apps/resource.py +0 -0
  45. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/apps/views.py +0 -0
  46. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/assets/__init__.py +0 -0
  47. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/assets/favicon.svg +0 -0
  48. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/assets/instructions.json +0 -0
  49. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/assets/view.html +0 -0
  50. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/auth.py +0 -0
  51. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/catalog_policy.py +0 -0
  52. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/constants.py +0 -0
  53. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/context.py +0 -0
  54. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/docs.py +0 -0
  55. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/docs_index.py +0 -0
  56. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/docs_search.py +0 -0
  57. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/docs_source.py +0 -0
  58. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/error_classification.py +0 -0
  59. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/error_monitoring.py +0 -0
  60. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/events/__init__.py +0 -0
  61. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/events/bus.py +0 -0
  62. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/events/catalog.py +0 -0
  63. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/events/delivery.py +0 -0
  64. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/events/egress.py +0 -0
  65. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/events/envelope.py +0 -0
  66. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/events/errors.py +0 -0
  67. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/events/ingress.py +0 -0
  68. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/events/projection.py +0 -0
  69. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/events/protocol.py +0 -0
  70. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/events/streams.py +0 -0
  71. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/events/subscriptions.py +0 -0
  72. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/events/webhooks.py +0 -0
  73. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/flags.py +0 -0
  74. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/http_app.py +0 -0
  75. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/instructions.py +0 -0
  76. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/legacy.py +0 -0
  77. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/operator.py +0 -0
  78. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/service.py +0 -0
  79. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/telemetry.py +0 -0
  80. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/src/mcp_server_appwrite/tool_manager.py +0 -0
  81. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/e2e/fixtures/appwrite/deployment_function_duplicate.json +0 -0
  82. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/e2e/fixtures/appwrite/deployment_function_failed.json +0 -0
  83. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/e2e/fixtures/appwrite/deployment_function_ready.json +0 -0
  84. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/e2e/fixtures/appwrite/deployment_site_ready.json +0 -0
  85. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/e2e/fixtures/appwrite/execution_async_failed.json +0 -0
  86. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/e2e/fixtures/appwrite/execution_async_waiting.json +0 -0
  87. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/e2e/fixtures/appwrite/execution_sync_failed.json +0 -0
  88. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/e2e/fixtures/appwrite/file_created.json +0 -0
  89. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/e2e/fixtures/appwrite/function_activation.json +0 -0
  90. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/e2e/fixtures/appwrite/row_created.json +0 -0
  91. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/e2e/fixtures/appwrite/user_created.json +0 -0
  92. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/e2e/test_apps.py +0 -0
  93. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/e2e/test_events_ingress.py +0 -0
  94. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/e2e/test_events_protocol.py +0 -0
  95. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/e2e/test_events_stream.py +0 -0
  96. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/e2e/test_events_subscribe.py +0 -0
  97. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/e2e/test_stdio.py +0 -0
  98. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/integration/support.py +0 -0
  99. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/integration/test_avatars.py +0 -0
  100. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/integration/test_console_sdk_oauth.py +0 -0
  101. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/integration/test_docs_index.py +0 -0
  102. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/integration/test_docs_source.py +0 -0
  103. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/integration/test_functions.py +0 -0
  104. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/integration/test_locale.py +0 -0
  105. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/integration/test_messaging.py +0 -0
  106. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/integration/test_oauth_discovery.py +0 -0
  107. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/integration/test_operator.py +0 -0
  108. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/integration/test_sites.py +0 -0
  109. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/integration/test_storage.py +0 -0
  110. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/integration/test_tables_db.py +0 -0
  111. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/integration/test_teams.py +0 -0
  112. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/integration/test_users.py +0 -0
  113. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/test_all.py +0 -0
  114. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/unit/fixtures/jwe-v1.json +0 -0
  115. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/unit/test_auth.py +0 -0
  116. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/unit/test_context.py +0 -0
  117. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/unit/test_docs.py +0 -0
  118. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/unit/test_docs_search.py +0 -0
  119. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/unit/test_error_classification.py +0 -0
  120. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/unit/test_error_monitoring.py +0 -0
  121. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/unit/test_events_delivery.py +0 -0
  122. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/unit/test_events_egress.py +0 -0
  123. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/unit/test_events_envelope.py +0 -0
  124. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/unit/test_events_protocol.py +0 -0
  125. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/unit/test_http_app.py +0 -0
  126. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/unit/test_instructions.py +0 -0
  127. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/unit/test_operator.py +0 -0
  128. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/unit/test_release_metadata.py +0 -0
  129. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/unit/test_server.py +0 -0
  130. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/unit/test_service.py +0 -0
  131. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/tests/unit/test_telemetry.py +0 -0
  132. {mcp_server_appwrite-2.1.2 → mcp_server_appwrite-2.1.3}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: mcp-server-appwrite
3
- Version: 2.1.2
3
+ Version: 2.1.3
4
4
  Summary: Build and deploy application backends. Add authentication, PostgreSQL/MySQL databases, vector databases, storage, serverless functions, realtime, messaging, hosting, domains, CDN and firewall.
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
- name: mcp_server_appwrite-2.1.2/src/mcp_server_appwrite/data/docs_index.npz
2
+ name: mcp_server_appwrite-2.1.3/src/mcp_server_appwrite/data/docs_index.npz
3
3
  size: 35543664 bytes
4
4
  sha256: 61d266fdb9a958b272d1b351cc6ebd6e824b0d0fe294483703e0795e4b0580df
@@ -988,8 +988,11 @@ def _perform_bounded_binary_client_call(
988
988
  if method.lower() != "get" or response_type != "json":
989
989
  raise RuntimeError(f"Unsupported bounded binary request for {tool_name}.")
990
990
 
991
+ # Merge by lowercased name: the SDK repeats the project header in the call's
992
+ # own headers with different casing, and both copies would be sent, which
993
+ # Appwrite reads as one comma-joined project ID that does not exist.
991
994
  request_headers = {
992
- key: value
995
+ key.lower(): value
993
996
  for key, value in {**client._global_headers, **(headers or {})}.items()
994
997
  if value
995
998
  }
@@ -1027,6 +1027,11 @@ SCOPES = frozenset(
1027
1027
  APPS_SCOPES = frozenset({"rows.read", "rows.write", "functions.write", "sites.write"})
1028
1028
  """The scopes the MCP Apps views need on top of :data:`SCOPES`."""
1029
1029
 
1030
+ PNG = base64.b64decode(
1031
+ "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=="
1032
+ )
1033
+ """A 1x1 PNG: what Appwrite's image previews return, in miniature."""
1034
+
1030
1035
  CUSTOM_ID = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]{0,35}")
1031
1036
 
1032
1037
  AUTH_PASSWORD_MAX = 2048
@@ -1122,17 +1127,27 @@ class Cloud:
1122
1127
  def _serve(self) -> None:
1123
1128
  length = int(self.headers.get("Content-Length", "0") or 0)
1124
1129
  body = self.rfile.read(length) if length else b""
1125
- headers = {name.lower(): value for name, value in self.headers.items()}
1130
+ # A header sent twice reaches Appwrite as one comma-joined
1131
+ # value, as the proxies in front of it combine them.
1132
+ headers: dict[str, str] = {}
1133
+ for name, value in self.headers.items():
1134
+ name = name.lower()
1135
+ headers[name] = (
1136
+ f"{headers[name]}, {value}" if name in headers else value
1137
+ )
1126
1138
  status, payload = cloud._handle(self.command, self.path, headers, body)
1127
- content = b"" if payload is None else json.dumps(payload).encode()
1139
+ if isinstance(payload, bytes):
1140
+ content, kind = payload, "image/png"
1141
+ else:
1142
+ content = b"" if payload is None else json.dumps(payload).encode()
1143
+ # Appwrite answers 204 with an HTML content type, which the
1144
+ # Python SDK returns as raw bytes.
1145
+ kind = (
1146
+ "text/html; charset=UTF-8"
1147
+ if payload is None
1148
+ else "application/json"
1149
+ )
1128
1150
  self.send_response(status)
1129
- # Appwrite answers 204 with an HTML content type, which the
1130
- # Python SDK returns as raw bytes.
1131
- kind = (
1132
- "text/html; charset=UTF-8"
1133
- if payload is None
1134
- else "application/json"
1135
- )
1136
1151
  self.send_header("Content-Type", kind)
1137
1152
  self.send_header("Content-Length", str(len(content)))
1138
1153
  self.end_headers()
@@ -1396,6 +1411,13 @@ class Cloud:
1396
1411
  "bytesMax": 0,
1397
1412
  "bytesUsed": 0,
1398
1413
  }
1414
+ if match := re.fullmatch(
1415
+ r"/v1/storage/buckets/([^/]+)/files/([^/]+)/preview", path
1416
+ ):
1417
+ need("files.read")
1418
+ if match.group(1) not in project.buckets:
1419
+ raise _Refusal(404, "storage_bucket_not_found", "Bucket not found")
1420
+ return 200, PNG
1399
1421
  if match := re.fullmatch(r"/v1/storage/buckets/([^/]+)", path):
1400
1422
  need("buckets.read")
1401
1423
  if match.group(1) not in project.buckets:
@@ -10,10 +10,11 @@ anything else, ``appwrite_call_write_tool`` is destructive and refuses reads.
10
10
  so a refused call is checked to have made no Appwrite request at all.
11
11
  """
12
12
 
13
+ import base64
13
14
  import unittest
14
15
  from typing import Any
15
16
 
16
- from support import TOKEN, Client, Cloud, Project, Server, collector
17
+ from support import PNG, SCOPES, TOKEN, Client, Cloud, Project, Server, collector
17
18
 
18
19
  PROJECT = "6630f1a2b3c4d5e6f7a8"
19
20
  HINTS = ("readOnlyHint", "destructiveHint", "idempotentHint", "openWorldHint")
@@ -40,8 +41,8 @@ cloud: Cloud
40
41
  def setUpModule() -> None:
41
42
  global cloud
42
43
  cloud = Cloud()
43
- cloud.add(Project(PROJECT))
44
- cloud.grant(TOKEN, {PROJECT})
44
+ cloud.add(Project(PROJECT, buckets={"photos"}))
45
+ cloud.grant(TOKEN, {PROJECT}, SCOPES | {"files.read"})
45
46
 
46
47
 
47
48
  def tearDownModule() -> None:
@@ -210,6 +211,23 @@ class ToolsFlow(unittest.TestCase):
210
211
  wrong + 2,
211
212
  )
212
213
 
214
+ def test_binary_reads_reach_the_project(self):
215
+ # The SDK sends the project header in the call's own headers too; sent
216
+ # twice, Appwrite reads "id, id" and finds no such project.
217
+ result = self.call(
218
+ "appwrite_call_tool",
219
+ {
220
+ "tool_name": "storage_get_file_preview",
221
+ "project_id": PROJECT,
222
+ "arguments": {"bucket_id": "photos", "file_id": "sunrise", "width": 64},
223
+ },
224
+ )
225
+ self.assertFalse(result.get("isError"), result)
226
+ (image,) = result["content"]
227
+ self.assertEqual(image["type"], "image")
228
+ self.assertEqual(image["mimeType"], "image/png")
229
+ self.assertEqual(base64.b64decode(image["data"]), PNG)
230
+
213
231
 
214
232
  if __name__ == "__main__":
215
233
  unittest.main()