mcp-server-appwrite 2.3.0__tar.gz → 2.4.0__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 (138) hide show
  1. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/PKG-INFO +1 -1
  2. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/docs/documentation-search.md +12 -0
  3. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/pyproject.toml +1 -1
  4. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/constants.py +2 -0
  5. mcp_server_appwrite-2.4.0/src/mcp_server_appwrite/data/docs_index.npz +4 -0
  6. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/docs_search.py +8 -1
  7. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/http_app.py +51 -1
  8. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/operator.py +4 -0
  9. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/e2e/test_tools.py +59 -0
  10. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/uv.lock +130 -86
  11. mcp_server_appwrite-2.3.0/src/mcp_server_appwrite/data/docs_index.npz +0 -4
  12. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/.dockerignore +0 -0
  13. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/.env.example +0 -0
  14. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/.github/workflows/ci.yml +0 -0
  15. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/.github/workflows/production.yml +0 -0
  16. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/.github/workflows/publish.yml +0 -0
  17. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/.github/workflows/staging.yml +0 -0
  18. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/.github/workflows/update-docs-index.yml +0 -0
  19. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/.gitignore +0 -0
  20. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/.python-version +0 -0
  21. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/AGENTS.md +0 -0
  22. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/CLAUDE.md +0 -0
  23. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/Dockerfile +0 -0
  24. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/LICENSE +0 -0
  25. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/README.md +0 -0
  26. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/compose.yaml +0 -0
  27. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/docs/apps.md +0 -0
  28. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/docs/appwrite-mcp-flow.png +0 -0
  29. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/docs/appwrite-mcp-flow.svg +0 -0
  30. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/docs/authentication.md +0 -0
  31. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/docs/development.md +0 -0
  32. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/docs/events.md +0 -0
  33. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/docs/flags.md +0 -0
  34. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/docs/images/claude-desktop-local-mcp-servers.png +0 -0
  35. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/docs/images/codex-mcp.png +0 -0
  36. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/docs/images/cursor-mcp.png +0 -0
  37. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/docs/self-hosted.md +0 -0
  38. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/docs/tool-surface.md +0 -0
  39. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/glama.json +0 -0
  40. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/scripts/build_docs_index.py +0 -0
  41. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/scripts/docs_release_notes.py +0 -0
  42. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/scripts/render_server_json.py +0 -0
  43. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/server.template.json +0 -0
  44. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/__init__.py +0 -0
  45. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/__main__.py +0 -0
  46. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/apps/__init__.py +0 -0
  47. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/apps/executions.py +0 -0
  48. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/apps/files.py +0 -0
  49. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/apps/protocol.py +0 -0
  50. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/apps/resource.py +0 -0
  51. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/apps/usage.py +0 -0
  52. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/apps/users.py +0 -0
  53. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/apps/views.py +0 -0
  54. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/assets/__init__.py +0 -0
  55. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/assets/favicon.svg +0 -0
  56. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/assets/instructions.json +0 -0
  57. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/assets/view.html +0 -0
  58. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/auth.py +0 -0
  59. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/catalog_policy.py +0 -0
  60. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/context.py +0 -0
  61. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/docs.py +0 -0
  62. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/docs_index.py +0 -0
  63. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/docs_source.py +0 -0
  64. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/error_classification.py +0 -0
  65. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/error_monitoring.py +0 -0
  66. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/events/__init__.py +0 -0
  67. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/events/bus.py +0 -0
  68. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/events/catalog.py +0 -0
  69. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/events/delivery.py +0 -0
  70. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/events/egress.py +0 -0
  71. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/events/envelope.py +0 -0
  72. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/events/errors.py +0 -0
  73. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/events/ingress.py +0 -0
  74. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/events/projection.py +0 -0
  75. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/events/protocol.py +0 -0
  76. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/events/streams.py +0 -0
  77. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/events/subscriptions.py +0 -0
  78. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/events/webhooks.py +0 -0
  79. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/flags.py +0 -0
  80. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/headers.py +0 -0
  81. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/instructions.py +0 -0
  82. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/legacy.py +0 -0
  83. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/server.py +0 -0
  84. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/service.py +0 -0
  85. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/telemetry.py +0 -0
  86. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/src/mcp_server_appwrite/tool_manager.py +0 -0
  87. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/e2e/fixtures/appwrite/deployment_function_duplicate.json +0 -0
  88. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/e2e/fixtures/appwrite/deployment_function_failed.json +0 -0
  89. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/e2e/fixtures/appwrite/deployment_function_ready.json +0 -0
  90. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/e2e/fixtures/appwrite/deployment_site_ready.json +0 -0
  91. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/e2e/fixtures/appwrite/execution_async_failed.json +0 -0
  92. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/e2e/fixtures/appwrite/execution_async_waiting.json +0 -0
  93. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/e2e/fixtures/appwrite/execution_sync_failed.json +0 -0
  94. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/e2e/fixtures/appwrite/file_created.json +0 -0
  95. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/e2e/fixtures/appwrite/function_activation.json +0 -0
  96. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/e2e/fixtures/appwrite/row_created.json +0 -0
  97. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/e2e/fixtures/appwrite/user_created.json +0 -0
  98. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/e2e/support.py +0 -0
  99. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/e2e/test_apps.py +0 -0
  100. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/e2e/test_events_ingress.py +0 -0
  101. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/e2e/test_events_protocol.py +0 -0
  102. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/e2e/test_events_stream.py +0 -0
  103. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/e2e/test_events_subscribe.py +0 -0
  104. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/e2e/test_stdio.py +0 -0
  105. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/integration/support.py +0 -0
  106. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/integration/test_avatars.py +0 -0
  107. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/integration/test_console_sdk_oauth.py +0 -0
  108. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/integration/test_docs_index.py +0 -0
  109. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/integration/test_docs_source.py +0 -0
  110. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/integration/test_functions.py +0 -0
  111. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/integration/test_locale.py +0 -0
  112. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/integration/test_messaging.py +0 -0
  113. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/integration/test_oauth_discovery.py +0 -0
  114. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/integration/test_operator.py +0 -0
  115. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/integration/test_sites.py +0 -0
  116. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/integration/test_storage.py +0 -0
  117. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/integration/test_tables_db.py +0 -0
  118. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/integration/test_teams.py +0 -0
  119. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/integration/test_users.py +0 -0
  120. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/test_all.py +0 -0
  121. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/unit/fixtures/jwe-v1.json +0 -0
  122. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/unit/test_auth.py +0 -0
  123. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/unit/test_context.py +0 -0
  124. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/unit/test_docs.py +0 -0
  125. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/unit/test_docs_search.py +0 -0
  126. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/unit/test_error_classification.py +0 -0
  127. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/unit/test_error_monitoring.py +0 -0
  128. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/unit/test_events_delivery.py +0 -0
  129. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/unit/test_events_egress.py +0 -0
  130. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/unit/test_events_envelope.py +0 -0
  131. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/unit/test_events_protocol.py +0 -0
  132. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/unit/test_http_app.py +0 -0
  133. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/unit/test_instructions.py +0 -0
  134. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/unit/test_operator.py +0 -0
  135. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/unit/test_release_metadata.py +0 -0
  136. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/unit/test_server.py +0 -0
  137. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/unit/test_service.py +0 -0
  138. {mcp_server_appwrite-2.3.0 → mcp_server_appwrite-2.4.0}/tests/unit/test_telemetry.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: mcp-server-appwrite
3
- Version: 2.3.0
3
+ Version: 2.4.0
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
@@ -20,6 +20,18 @@ otherwise the server boots without it.
20
20
  | `DOCS_SEARCH_MIN_SCORE` | No | `0.25` | Minimum cosine score for a match. |
21
21
  | `DOCS_SEARCH_LIMIT` | No | `5` (max `10`) | Default max pages returned. |
22
22
 
23
+ ## Search route
24
+
25
+ The deprecated docs MCP server at `mcp-for-docs.appwrite.io` has no OAuth session to use, so it searches through this public route instead of the MCP endpoint:
26
+
27
+ ```bash
28
+ curl -X POST https://mcp.appwrite.io/docs/search \
29
+ -H 'content-type: application/json' \
30
+ -d '{"query": "create a row"}'
31
+ ```
32
+
33
+ It answers `{"results": [{"path", "title", "description", "score", "content"}]}` with up to 5 pages, `400` for an invalid query or one over 500 characters, `413` for a body over 16 KiB, and `502` when the embedding request fails. The route exists only when documentation search is available.
34
+
23
35
  ## Rebuilding the index
24
36
 
25
37
  Re-run when the docs change, then commit the refreshed artifact:
@@ -36,7 +36,7 @@ integration = [
36
36
 
37
37
  [dependency-groups]
38
38
  dev = [
39
- "black==26.3.1",
39
+ "black==26.10.1",
40
40
  "ruff==0.15.18",
41
41
  "pyright==1.1.411",
42
42
  ]
@@ -114,6 +114,8 @@ DOCS_DEFAULT_LIMIT = 5
114
114
  DOCS_MAX_LIMIT = 10
115
115
  DOCS_DEFAULT_MIN_SCORE = 0.25
116
116
  DOCS_MIN_QUERY_LENGTH = 3
117
+ DOCS_MAX_QUERY_LENGTH = 500
118
+ DOCS_MAX_BODY_BYTES = 16 * 1024
117
119
 
118
120
  DATA_DIR = Path(__file__).parent / "data"
119
121
  INDEX_FILE = "docs_index.npz"
@@ -0,0 +1,4 @@
1
+ [diffend] Oversized file quarantined before diffing.
2
+ name: mcp_server_appwrite-2.4.0/src/mcp_server_appwrite/data/docs_index.npz
3
+ size: 35820220 bytes
4
+ sha256: 1d70caae9062ecf77513f23cee05e5c9c0c0213d0db8258316cd94b73ae20f38
@@ -178,7 +178,10 @@ class DocsSearch:
178
178
  },
179
179
  )
180
180
 
181
- def search(self, arguments: dict[str, Any] | None) -> list[ToolContent]:
181
+ def find(
182
+ self, arguments: dict[str, Any] | None
183
+ ) -> tuple[str, list[dict[str, Any]]]:
184
+ """Return the normalized query and its ranked pages."""
182
185
  arguments = arguments or {}
183
186
  query = str(arguments.get("query", "")).strip()
184
187
  if len(query) < DOCS_MIN_QUERY_LENGTH:
@@ -198,6 +201,10 @@ class DocsSearch:
198
201
  "Documentation search embedding request failed. "
199
202
  "Check OPENAI_API_KEY and outbound connectivity to OpenAI."
200
203
  ) from exc
204
+ return query, results
205
+
206
+ def search(self, arguments: dict[str, Any] | None) -> list[ToolContent]:
207
+ query, results = self.find(arguments)
201
208
 
202
209
  if not results:
203
210
  return [
@@ -13,6 +13,9 @@ Builds a single-tenant Starlette ASGI app for the served Appwrite project:
13
13
  following ``resource_metadata`` from the 401 challenge. Mirrors the Appwrite
14
14
  authorization server's discovery document verbatim.
15
15
  * ``/healthz`` — liveness probe.
16
+ * ``/docs/search`` — public documentation search for the deprecated docs MCP
17
+ server, whose clients have no user to sign in. Results and limits are fixed, so
18
+ a call costs one small embedding.
16
19
  * ``/.well-known/openai-apps-challenge`` — public domain verification token.
17
20
  * ``/appwrite/webhooks/{id}`` — MCP Events ingress for Appwrite webhooks, only
18
21
  with the events flag on. Unauthenticated by bearer token: the Appwrite
@@ -39,6 +42,7 @@ from importlib.resources import files
39
42
  from types import FrameType
40
43
  from urllib.parse import urlsplit
41
44
 
45
+ import anyio.to_thread
42
46
  import httpx
43
47
  import uvicorn
44
48
  from mcp.server.auth.middleware.auth_context import AuthContextMiddleware
@@ -71,7 +75,14 @@ from .auth import (
71
75
  public_base_url,
72
76
  resource_metadata_url,
73
77
  )
74
- from .constants import CORS_HEADERS, SERVER_VERSION
78
+ from .constants import (
79
+ CORS_HEADERS,
80
+ DOCS_DEFAULT_LIMIT,
81
+ DOCS_MAX_BODY_BYTES,
82
+ DOCS_MAX_QUERY_LENGTH,
83
+ SERVER_VERSION,
84
+ )
85
+ from .docs_search import DocsSearch
75
86
  from .events import protocol as events_protocol
76
87
  from .events.ingress import Ingress
77
88
  from .events.streams import Settings, Streams
@@ -449,6 +460,34 @@ async def oauth_authorize_proxy_endpoint(request: Request) -> Response:
449
460
  )
450
461
 
451
462
 
463
+ def docs_search_endpoint(docs_search: DocsSearch):
464
+ async def endpoint(request: Request) -> JSONResponse:
465
+ try:
466
+ raw = b""
467
+ async for chunk in request.stream():
468
+ raw += chunk
469
+ if len(raw) > DOCS_MAX_BODY_BYTES:
470
+ return JSONResponse({"error": "body_too_large"}, status_code=413)
471
+ body = json.loads(raw)
472
+ query = body.get("query") if isinstance(body, dict) else None
473
+ if not isinstance(query, str) or len(query) > DOCS_MAX_QUERY_LENGTH:
474
+ raise ValueError(
475
+ f"query must be a string of at most {DOCS_MAX_QUERY_LENGTH} characters."
476
+ )
477
+ _query, results = await anyio.to_thread.run_sync(
478
+ docs_search.find,
479
+ {"query": query, "limit": DOCS_DEFAULT_LIMIT},
480
+ abandon_on_cancel=True,
481
+ )
482
+ except ValueError as error:
483
+ return JSONResponse({"error": str(error)}, status_code=400)
484
+ except RuntimeError as error:
485
+ return JSONResponse({"error": str(error)}, status_code=502)
486
+ return JSONResponse({"results": results})
487
+
488
+ return endpoint
489
+
490
+
452
491
  async def health_endpoint(request: Request) -> PlainTextResponse:
453
492
  return PlainTextResponse(f"appwrite-mcp {SERVER_VERSION} ok")
454
493
 
@@ -560,6 +599,17 @@ def build_app(
560
599
  Route("/favicon.ico", endpoint=favicon_ico_endpoint, methods=["GET"]),
561
600
  Route("/healthz", endpoint=health_endpoint, methods=["GET"]),
562
601
  *([ingress.route] if ingress is not None else []),
602
+ *(
603
+ [
604
+ Route(
605
+ "/docs/search",
606
+ endpoint=docs_search_endpoint(operator.docs_search),
607
+ methods=["POST"],
608
+ )
609
+ ]
610
+ if operator.docs_search is not None
611
+ else []
612
+ ),
563
613
  Route(
564
614
  "/",
565
615
  endpoint=mcp_endpoint,
@@ -182,6 +182,10 @@ class Operator:
182
182
  def docs_enabled(self) -> bool:
183
183
  return self._docs_search is not None
184
184
 
185
+ @property
186
+ def docs_search(self) -> DocsSearch | None:
187
+ return self._docs_search
188
+
185
189
  def get_public_tools(self, *, apps: bool = False) -> list[types.Tool]:
186
190
  """The exposed tools, with the MCP Apps view tools when ``apps``."""
187
191
  return [public.tool for public in self._tools(apps).values()]
@@ -19,10 +19,14 @@ import base64
19
19
  import json
20
20
  import unittest
21
21
  from typing import Any
22
+ from unittest import mock
22
23
  from urllib.parse import unquote
23
24
 
25
+ import httpx
24
26
  from support import PNG, SCOPES, TOKEN, Client, Cloud, Project, Server, collector
25
27
 
28
+ from mcp_server_appwrite import docs_search
29
+
26
30
  PROJECT = "6630f1a2b3c4d5e6f7a8"
27
31
  STAGING = Project("internal-staging", buckets={"photos"}, name="Staging")
28
32
  """The project's ``staging`` environment, a project whose own ID is internal."""
@@ -63,6 +67,61 @@ def tearDownModule() -> None:
63
67
  cloud.close()
64
68
 
65
69
 
70
+ class DocsSearchRoute(unittest.TestCase):
71
+ """``POST /docs/search``, which the deprecated docs MCP server calls without a
72
+ user. Only the OpenAI query embedding is stubbed; the index is the real one."""
73
+
74
+ def setUp(self) -> None:
75
+ patch = mock.patch.object(
76
+ docs_search,
77
+ "_default_embedder",
78
+ lambda: lambda _query: [1.0] + [0.0] * 1535,
79
+ )
80
+ patch.start()
81
+ self.addCleanup(patch.stop)
82
+
83
+ def server(self) -> Server:
84
+ return Server(
85
+ {
86
+ "APPWRITE_ENDPOINT": cloud.endpoint,
87
+ "DOCS_SEARCH_MIN_SCORE": "-1",
88
+ "OPENAI_API_KEY": "stubbed",
89
+ }
90
+ )
91
+
92
+ def test_returns_a_fixed_number_of_pages_without_auth(self):
93
+ with self.server() as server:
94
+ found = httpx.post(
95
+ f"{server.url}/docs/search",
96
+ json={"query": "create a row", "limit": 10},
97
+ )
98
+ self.assertEqual(found.status_code, 200)
99
+ results = found.json()["results"]
100
+ self.assertTrue(1 <= len(results) <= 5)
101
+ self.assertEqual(
102
+ set(results[0]), {"path", "title", "description", "score", "content"}
103
+ )
104
+
105
+ def test_rejects_invalid_queries_before_embedding_them(self):
106
+ with self.server() as server:
107
+ for invalid in (
108
+ {"query": "x"},
109
+ {"query": None},
110
+ {"query": "x" * 501},
111
+ ["create a row"],
112
+ ):
113
+ refused = httpx.post(f"{server.url}/docs/search", json=invalid)
114
+ self.assertEqual(refused.status_code, 400, invalid)
115
+
116
+ malformed = httpx.post(f"{server.url}/docs/search", content="nope")
117
+ self.assertEqual(malformed.status_code, 400)
118
+
119
+ oversized = httpx.post(
120
+ f"{server.url}/docs/search", content=b"x" * (16 * 1024 + 1)
121
+ )
122
+ self.assertEqual(oversized.status_code, 413)
123
+
124
+
66
125
  class ToolsFlow(unittest.TestCase):
67
126
  server: Server
68
127
  client: Client