Skip to content

Refactor to hosted HTTP + OAuth 2.1 transport#42

Draft
ChiragAgg5k wants to merge 2 commits into
mainfrom
refactor/hosted-http-oauth
Draft

Refactor to hosted HTTP + OAuth 2.1 transport#42
ChiragAgg5k wants to merge 2 commits into
mainfrom
refactor/hosted-http-oauth

Conversation

@ChiragAgg5k

Copy link
Copy Markdown
Member

Summary

Replaces the stdio + API-key MCP with a hosted, multi-tenant OAuth 2.1 resource server over the MCP Streamable HTTP transport. Users authenticate against Appwrite Cloud's per-project OAuth server; the MCP validates the bearer token and forwards it to the Appwrite REST API, which accepts the OAuth2 access token directly (AccessToken::findIdentity). This follows the MCP authorization spec (OAuth 2.1 + PKCE, RFC 9728 protected-resource metadata, RFC 8414/OIDC AS discovery, RFC 7591 DCR, RFC 8707 resource indicators).

Connection URL is per-project: https://<host>/<project_id>/mcp.

Changes

  • Transport — custom Starlette ASGI app (http_app.py) serving Streamable HTTP at /{project_id}/mcp (SSE responses, stateless). stdio transport, --transport/MCP_TRANSPORT removed.
  • Auth (auth.py) — RFC 9728 protected-resource metadata route, WWW-Authenticate 401 challenge, and a per-project RS256/JWKS TokenVerifier that derives the project from the JWT iss claim and enforces RFC 8707 audience binding. Reuses the SDK's BearerAuthBackend/AuthContextMiddleware so the token reaches tool handlers via get_access_token().
  • Execution — split tool schema (built once via SDK introspection) from binding; each call re-binds the SDK method to a per-request client built from the request's OAuth token (resolve_client/build_client_for_request). The appwrite_search_tools/appwrite_call_tool operator surface and write-confirmation guard are unchanged.
  • Packaging/docsDockerfile, streamable-http remote in server.json, rewritten HTTP/OAuth README, .env.example. Version → 0.5.0.
  • Tooling — added Ruff linter (E/F/W/I, E501 left to black) + CI lint step; pinned all GitHub Actions to commit SHAs; pinned uv (0.11.22) and build/twine; added a Docker-build CI job.

API-key helpers (build_client, etc.) remain only as integration-test scaffolding — the running server has no API-key path.

Testing

  • 33 unit tests pass (incl. new test_auth.py); ruff check and black --check clean.
  • Docker image builds and the container serves /healthz 200, correct RFC 9728 metadata, and an unauthenticated 401 + WWW-Authenticate challenge.

Cloud-side dependencies (required for the full live OAuth round-trip)

These live in the Appwrite Cloud repo, not here:

  1. RFC 8707 Resource Indicators must write the requested resource into the issued JWT aud (the verifier requires audience binding).
  2. RFC 7591 Dynamic Client Registration endpoint on the OAuth2 module.
  3. Recommended: RFC 8414 metadata alias; and each project's oAuth2Server.scopes must include the advertised scope set.

Notes

  • Left as floating (open to hardening): Docker base python:3.12-slim tag and runs-on: ubuntu-latest.
  • Ruff rule set kept focused (no UP/B) to avoid churn; easy to expand later.

Replace the stdio + API-key server with a hosted, multi-tenant OAuth 2.1
resource server over the MCP Streamable HTTP transport. Users authenticate
against Appwrite Cloud's per-project OAuth server; the MCP validates the
bearer token and forwards it to the Appwrite REST API (which accepts the
OAuth2 access token directly).

- Transport: custom Starlette ASGI app serving Streamable HTTP at
  /{project_id}/mcp (SSE responses, stateless). stdio transport removed.
- Auth (new auth.py): RFC 9728 protected-resource metadata, WWW-Authenticate
  401 challenge, and a per-project RS256/JWKS token verifier that derives the
  project from the JWT `iss` claim and enforces RFC 8707 audience binding.
- Execution: split tool schema (built once via SDK introspection) from
  binding; each call re-binds the SDK method to a per-request client built
  from the request's OAuth token.
- Packaging: Dockerfile, streamable-http remote in server.json, HTTP/OAuth
  README, .env.example. Bump to 0.5.0.
- Tooling: add Ruff linter (E/F/W/I) alongside black; pin GitHub Actions to
  commit SHAs, pin uv (0.11.22) and build/twine; add Docker-build CI job.

API-key helpers remain only as integration-test scaffolding.
- register_services now auto-discovers every Appwrite SDK service class
  (14 services, 376 tools) via an EXCLUDED_SERVICES knob, instead of a
  hardcoded list of 9. Adds account, databases, graphql, health, tokens.
- protected-resource metadata's scopes_supported is now sourced live from
  the project's authorization-server discovery (cached per project), so it
  never drifts from the tool surface. Removed the static scope list.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant