-
Notifications
You must be signed in to change notification settings - Fork 18
(feat): add --console-url testing flag for OAuth against alternative consoles #81
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| # Testing flags | ||
|
|
||
| Testing flags are opt-in behavior overrides for testers — they are off by | ||
| default and are not part of the supported configuration surface. All flags are | ||
| declared in one place, [`src/mcp_server_appwrite/flags.py`](../src/mcp_server_appwrite/flags.py), | ||
| and every flag can be enabled two equivalent ways: | ||
|
|
||
| ```bash | ||
| # CLI argument … | ||
| uv run mcp-server-appwrite --transport http --<name> <value> | ||
|
|
||
| # … or environment variable (also works with Docker/Compose and .env) | ||
| <ENV_VAR>=<value> uv run mcp-server-appwrite --transport http | ||
| ``` | ||
|
|
||
| The CLI argument simply writes through to the environment variable, which is | ||
| the single runtime source of truth. | ||
|
|
||
| ## Adding a flag | ||
|
|
||
| 1. Declare a `Flag(name=..., env=..., help=...)` in `flags.py` and add it to | ||
| `FLAGS`. That's all it takes to get the `--<name>` CLI argument and | ||
| `$<ENV>` variable. | ||
| 2. Read it where needed with `flags.value(flags.MY_FLAG)` — read at request | ||
| time (not import time) so tests can toggle it via `os.environ`. | ||
| 3. Add unit tests for the behavior the flag changes. | ||
| 4. Document it below: what it does, how to enable it, and how to verify it. | ||
|
|
||
| Flags are for testing overrides only — permanent configuration belongs in | ||
| `constants.py` or a plain environment variable. | ||
|
|
||
| ## Available flags | ||
|
|
||
| | CLI | Env | What it does | | ||
| | --- | --- | --- | | ||
| | `--console-url` | `MCP_CONSOLE_URL` | Send OAuth login/consent to an alternative Appwrite Console (HTTP transport only). | | ||
|
|
||
| ### `--console-url` — test OAuth against a pre-release console | ||
|
|
||
| By default the Appwrite authorization server redirects users to the production | ||
| console (`cloud.appwrite.io/console`) for login and consent. This flag lets | ||
| testers run the whole OAuth flow through a different console deployment, for | ||
| example the new console at `new.appwrite.io`. | ||
|
|
||
| How it works: the MCP server advertises itself as the OAuth authorization | ||
| server and mirrors the real server's discovery document with one change — the | ||
| `authorization_endpoint` points at a local `/oauth2/authorize` proxy. The proxy | ||
| forwards each authorize request to the real Appwrite authorize endpoint | ||
| (keeping all upstream validation) and rewrites its consent redirect from the | ||
| default console to `<console-url>/oauth2/consent`. Token, registration, and | ||
| JWKS endpoints stay on the real authorization server, so token validation is | ||
| unchanged. | ||
|
|
||
| **Enable it:** | ||
|
|
||
| ```bash | ||
| MCP_PUBLIC_URL=http://localhost:8000 \ | ||
| uv run mcp-server-appwrite --transport http --console-url https://new.appwrite.io | ||
| ``` | ||
|
|
||
| **Quick checks (no browser):** | ||
|
|
||
| ```bash | ||
| # Protected resource metadata names this MCP server as the authorization server | ||
| curl -s http://localhost:8000/.well-known/oauth-protected-resource | jq .authorization_servers | ||
|
|
||
| # Mirrored discovery points the authorize step at the local proxy | ||
| curl -s http://localhost:8000/.well-known/oauth-authorization-server | jq .authorization_endpoint | ||
|
|
||
| # The authorize proxy rewrites the consent redirect to the override console | ||
| curl -sI "http://localhost:8000/oauth2/authorize?client_id=<id>&response_type=code&redirect_uri=<uri>&scope=openid" | grep -i location | ||
| ``` | ||
|
|
||
| **Full flow with an MCP client:** | ||
|
|
||
| ```bash | ||
| claude mcp add --transport http appwrite-test http://localhost:8000/ | ||
| ``` | ||
|
|
||
| Then run `/mcp` in Claude Code and authenticate — the browser should open the | ||
| override console's sign-in page, and after consent the client completes the | ||
| token exchange against the real authorization server. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| """Central registry for tester/feature flags. | ||
|
|
||
| A flag is an opt-in behavior override for testing (for example, pointing OAuth | ||
| login at a pre-release console). Every flag is declared once here and gets, for | ||
| free, a ``--<name>`` CLI argument and a ``<env>`` environment variable — the CLI | ||
| argument simply writes through to the environment, which is the single runtime | ||
| source of truth (modules read flags per request via :func:`value`, so tests can | ||
| toggle them with ``mock.patch.dict(os.environ, ...)``). | ||
|
|
||
| To add a flag: | ||
|
|
||
| 1. Add a ``Flag`` entry to ``FLAGS`` below. | ||
| 2. Read it where needed with ``flags.value(flags.MY_FLAG)``. | ||
| 3. Document how to enable and test it in ``docs/flags.md``. | ||
|
|
||
| Flags are for testing overrides only — permanent configuration belongs in | ||
| ``constants.py`` or a plain environment variable. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import argparse | ||
| import os | ||
| from dataclasses import dataclass | ||
|
|
||
|
|
||
| @dataclass(frozen=True) | ||
| class Flag: | ||
| name: str | ||
| """Kebab-case CLI name, exposed as ``--<name>``.""" | ||
|
|
||
| env: str | ||
| """Environment variable backing the flag; the CLI argument writes to it.""" | ||
|
|
||
| help: str | ||
| """One-line description shown in ``--help`` and docs.""" | ||
|
|
||
|
|
||
| CONSOLE_URL = Flag( | ||
| name="console-url", | ||
| env="MCP_CONSOLE_URL", | ||
| help=( | ||
| "Base URL of an alternative Appwrite Console to use for OAuth " | ||
| "login/consent (e.g. https://new.appwrite.io). HTTP transport only." | ||
| ), | ||
| ) | ||
|
|
||
| FLAGS: tuple[Flag, ...] = (CONSOLE_URL,) | ||
|
|
||
|
|
||
| def value(flag: Flag) -> str | None: | ||
| """The flag's current value (normalized), or ``None`` when unset.""" | ||
| return os.getenv(flag.env, "").strip().rstrip("/") or None | ||
|
|
||
|
|
||
| def register_cli_args(parser: argparse.ArgumentParser) -> None: | ||
| """Add a ``--<name>`` argument per flag. The default is ``None`` (not the | ||
| environment variable) so an explicit ``--<name> ""`` is distinguishable | ||
| from "not provided" and can clear a flag exported in the shell.""" | ||
| for flag in FLAGS: | ||
| parser.add_argument( | ||
| f"--{flag.name}", | ||
| default=None, | ||
| help=f"Testing flag: {flag.help} (default ${flag.env}).", | ||
| ) | ||
|
|
||
|
|
||
| def apply_cli_args(args: argparse.Namespace) -> None: | ||
| """Write parsed CLI flag values back to their environment variables. | ||
|
|
||
| A flag not provided on the CLI leaves its environment variable untouched; | ||
| an explicit empty value (``--<name> ""``) clears it.""" | ||
| for flag in FLAGS: | ||
| raw = getattr(args, flag.name.replace("-", "_"), None) | ||
| if raw is not None: | ||
| os.environ[flag.env] = raw | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.