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
14 changes: 14 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,20 @@ jobs:
with:
version: "0.11.22"

- name: Install Dragonfly
# The push delivery flows start their own bus, on the Dragonfly
# version the clusters run.
env:
VERSION: v2.0.0
SHA256: 31060f32858f99a751cae8b54b7b5b556a9b7eb855c30b79bfc3ab7acb26928a
run: |
curl -fsSL -o dragonfly.tar.gz "https://github.com/dragonflydb/dragonfly/releases/download/$VERSION/dragonfly-x86_64.tar.gz"
echo "$SHA256 dragonfly.tar.gz" | sha256sum -c -
tar -xzf dragonfly.tar.gz dragonfly-x86_64
sudo install dragonfly-x86_64 /usr/local/bin/dragonfly
rm dragonfly.tar.gz dragonfly-x86_64
dragonfly --version

- name: Install dependencies
run: uv sync --frozen --group e2e

Expand Down
6 changes: 4 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Source lives in `src/mcp_server_appwrite/`:
| `docs_search.py` | In-process semantic docs search (`appwrite_search_docs`) over a prebuilt index. |
| `docs_source.py` | Fetches the published docs (`/docs/llms.txt` + per-page `.md` exports on appwrite.io) and chunks them for the index build. |
| `docs_index.py` | Writes the index artifact deterministically and atomically: page and chunk hashes, reuse of cached chunk vectors, build lock, and the added/changed/removed page report. |
| `events/` | MCP Events (behind `flags.EVENTS`, HTTP only): `catalog.py` (event catalog), `protocol.py` (capability, `events/list` and the subscribe methods), `subscriptions.py` + `webhooks.py` (`events/subscribe` / `events/unsubscribe`: one managed Appwrite webhook per subscription), `envelope.py` (subscription state sealed with the `mcp-event-subscriptions` JWE profile), `egress.py` + `delivery.py` (SSRF-safe signed delivery), `projection.py` + `ingress.py` (Appwrite webhook ingress at `/appwrite/webhooks/{id}`). See [docs/events.md](docs/events.md). |
| `events/` | MCP Events (behind `flags.EVENTS`, HTTP only): `catalog.py` (event catalog), `protocol.py` (capability, `events/list` and the subscribe methods), `subscriptions.py` + `webhooks.py` (`events/subscribe` / `events/unsubscribe`: one managed Appwrite webhook per subscription), `envelope.py` (subscription state sealed with the `mcp-event-subscriptions` JWE profile), `egress.py` + `delivery.py` (SSRF-safe signed delivery), `projection.py` + `ingress.py` (Appwrite webhook ingress at `/appwrite/webhooks/{id}`), `streams.py` + `bus.py` (push delivery: `events/stream`, behind `flags.EVENTS_PUSH`, with events routed between replicas over Redis pub/sub). See [docs/events.md](docs/events.md). |
| `telemetry.py` | OpenTelemetry metrics layer (OTLP/HTTP). No-op unless an OTLP endpoint is configured and the transport is `http`. |
| `data/` | Committed docs index artifact (`docs_index.npz`: vectors, chunk map, chunk hashes, embedded `meta.json`), shipped in the wheel/image. |

Expand Down Expand Up @@ -121,7 +121,9 @@ Run these locally before opening a PR. They mirror the `CI` workflow
```
Boots the real hosted HTTP app on a random localhost port and drives it over
real HTTP the way an MCP client does. No credentials: only the OAuth token
verifier is stubbed. Runs on every PR, forks included. Prefer adding
verifier is stubbed. Runs on every PR, forks included. The push delivery
flows start a real `dragonfly` (CI) or `redis-server`, which must be on
`PATH` (`brew install redis` or `apt-get install redis-server`). Prefer adding
assertions to an existing flow over adding unit tests; add a unit test only
for what no e2e flow can reach, and say why in its module docstring.

Expand Down
18 changes: 18 additions & 0 deletions compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ services:
OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-}
SENTRY_DSN: ${SENTRY_DSN:-}
SENTRY_ENVIRONMENT: ${SENTRY_ENVIRONMENT:-development}
MCP_EVENTS: ${MCP_EVENTS:-}
MCP_EVENTS_PUSH: ${MCP_EVENTS_PUSH:-}
MCP_EVENTS_SEALING_KEYS: ${MCP_EVENTS_SEALING_KEYS:-}
MCP_EVENTS_REDIS_URL: redis://dragonfly:6379/0
depends_on:
dragonfly:
condition: service_healthy
healthcheck:
test:
[
Expand All @@ -30,3 +37,14 @@ services:
timeout: 3s
retries: 3
start_period: 10s

# Carries push events between replicas (MCP_EVENTS_PUSH), as in the cluster;
# any Redis-protocol server works. See docs/events.md.
dragonfly:
image: docker.dragonflydb.io/dragonflydb/dragonfly:v2.0.0
command: ["dragonfly", "--logtostderr", "--dbfilename=", "--proactor_threads=2"]
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
8 changes: 8 additions & 0 deletions docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ docker compose up --build
> then move it first, then remove the old one 25 hours after that reached every
> replica (see "Key rotation" in [events.md](events.md)).

> MCP Events push delivery (`--events-push`) routes events between replicas
> through a Redis-protocol server (Dragonfly in the cluster) at
> `MCP_EVENTS_REDIS_URL`. Compose runs Dragonfly and points the server at it; see [flags.md](flags.md#--events-push--push-delivery-in-progress).

**`uv` directly — HTTP:**

```bash
Expand Down Expand Up @@ -73,6 +77,10 @@ E2E tests boot the real hosted HTTP app on a random localhost port and drive it
over real HTTP like an MCP client. Only the OAuth token verifier is stubbed, so
they need no credentials and run on every PR.

The push-delivery flows also start a real bus on a random port: `dragonfly` when
it is on `PATH`, as in CI, otherwise `redis-server` (`brew install redis` or
`apt-get install redis-server`; Dragonfly has no macOS build).

Integration tests create and delete **real** Appwrite resources. They
authenticate via `APPWRITE_PROJECT_ID`, `APPWRITE_API_KEY`, `APPWRITE_ENDPOINT`
(shell or `.env`) and are skipped when no credentials are present.
Expand Down
Loading
Loading