Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/appwrite-mcp-flow.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions docs/tool-surface.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ flowchart LR

subgraph CAT[Internal catalog — authentication-aware]
direction LR
K[OAuth: 40 services / 1007 tools<br/>API key: 26 services / 663 tools]
K[OAuth: 41 services / 1016 tools<br/>API key: 26 services / 663 tools]
end

CT -->|large output| R[(MCP resource<br/>preview + URI)]
Expand All @@ -47,8 +47,8 @@ flowchart LR
`context=organization`, or `context=project`. Hosted calls enforce the required
top-level `organization_id` or `project_id` before making a request.
- **Access** is still gated per-route by the scopes granted to the OAuth token.
- **Hosted OAuth** registers all 40 services and 1007 methods shipped by
`appwrite-console` 0.7.0. This adds console control-plane services including
- **Hosted OAuth** registers all 41 services and 1016 methods shipped by
`appwrite-console` 0.8.1. This adds console control-plane services including
projects, organizations, domains, migrations, dedicated databases, usage, VCS,
vectors, WAF, notifications, and regions.
- **API-key stdio** deliberately registers only the 663 project-key-compatible
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"anyio>=4.0.0",
"appwrite-console>=0.7.1,<0.8",
"appwrite-console>=0.8.1,<0.9",
"docstring-parser>=0.16",
"mcp[cli]>=2,<3",
"python-dotenv>=1.0.1",
Expand Down
1 change: 1 addition & 0 deletions src/mcp_server_appwrite/catalog_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
}
),
"apps": frozenset({"delete_installation"}),
"avatars": frozenset({"delete_photo", "update_photo"}),
"documents_db": frozenset(
{
"create_documents",
Expand Down
38 changes: 36 additions & 2 deletions tests/unit/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -923,8 +923,8 @@ def test_register_services_returns_fresh_manager(self):
{service.service_name for service in manager_a.services},
set(SERVICE_CLASSES),
)
self.assertEqual(len(manager_a.services), 40)
self.assertEqual(len(manager_a.get_all_tools()), 1007)
self.assertEqual(len(manager_a.services), 41)
self.assertEqual(len(manager_a.get_all_tools()), 1016)

def test_console_sdk_0_7_surface(self):
manager = register_services(object(), profile=OAUTH_PROFILE)
Expand All @@ -935,6 +935,8 @@ def test_console_sdk_0_7_surface(self):
self.assertIn("growth_create_conversation", tools)
self.assertIn("organization_create_project_key", tools)
self.assertIn("tables_db_create_cutover", tools)
self.assertIn("analytics_list_metrics", tools)
self.assertIn("avatars_update_photo", tools)
for removed_tool in (
"project_get_usage",
"projects_list_dev_keys",
Expand Down Expand Up @@ -984,6 +986,35 @@ def test_console_sdk_0_7_surface(self):
]
self.assertIn("bun-1.4", build_runtime["enum"])

def test_console_sdk_accepts_null_billing_fields(self):
# Cloud returns null for these fields; earlier SDKs rejected the
# responses (Sentry MCP-1G/1S/1F/1P/1N/1W).
from appwrite_console.models.dedicated_database_restoration import (
DedicatedDatabaseRestoration,
)
from appwrite_console.models.invoice import Invoice
from appwrite_console.models.organization import Organization
from appwrite_console.models.payment_method import PaymentMethod
from pydantic import TypeAdapter

cases = {
Organization: ("paymentMethodId",),
PaymentMethod: ("mandateId", "state", "lastError"),
Invoice: ("clientSecret", "lastError"),
DedicatedDatabaseRestoration: ("targetTime",),
}
for model, aliases in cases.items():
fields = {
field.alias or name: field for name, field in model.model_fields.items()
}
for alias in aliases:
with self.subTest(model=model.__name__, field=alias):
field = fields[alias]
self.assertFalse(field.is_required())
self.assertIsNone(
TypeAdapter(field.annotation).validate_python(None)
)

def test_advisor_tools_are_project_scoped(self):
manager = register_services(object(), profile=OAUTH_PROFILE)
advisor_tools = {
Expand Down Expand Up @@ -1013,6 +1044,9 @@ def test_api_key_profile_only_advertises_server_capabilities(self):
self.assertNotIn("organizations", service_names)
self.assertNotIn("documents_db_list_operations", tool_names)
self.assertNotIn("tables_db_create_cutover", tool_names)
self.assertNotIn("avatars_update_photo", tool_names)
self.assertNotIn("avatars_delete_photo", tool_names)
self.assertNotIn("analytics", service_names)
self.assertNotIn("growth", service_names)
for method in (
"create_ephemeral_project_key",
Expand Down
8 changes: 4 additions & 4 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading