avios-cli 0.5.0__tar.gz → 0.6.1__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 (58) hide show
  1. {avios_cli-0.5.0 → avios_cli-0.6.1}/CHANGELOG.md +34 -0
  2. {avios_cli-0.5.0 → avios_cli-0.6.1}/PKG-INFO +45 -7
  3. {avios_cli-0.5.0 → avios_cli-0.6.1}/README.md +40 -4
  4. {avios_cli-0.5.0 → avios_cli-0.6.1}/pyproject.toml +13 -3
  5. avios_cli-0.6.1/src/avios/__init__.py +7 -0
  6. {avios_cli-0.5.0 → avios_cli-0.6.1}/src/avios/auth.py +20 -4
  7. avios_cli-0.6.1/src/avios/mcp_entry.py +36 -0
  8. avios_cli-0.6.1/src/avios/mcp_server.py +675 -0
  9. {avios_cli-0.5.0 → avios_cli-0.6.1}/tests/test_auth.py +23 -4
  10. avios_cli-0.6.1/tests/test_mcp_server.py +623 -0
  11. {avios_cli-0.5.0 → avios_cli-0.6.1}/uv.lock +671 -2
  12. avios_cli-0.5.0/src/avios/__init__.py +0 -6
  13. {avios_cli-0.5.0 → avios_cli-0.6.1}/.editorconfig +0 -0
  14. {avios_cli-0.5.0 → avios_cli-0.6.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  15. {avios_cli-0.5.0 → avios_cli-0.6.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  16. {avios_cli-0.5.0 → avios_cli-0.6.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  17. {avios_cli-0.5.0 → avios_cli-0.6.1}/.github/dependabot.yml +0 -0
  18. {avios_cli-0.5.0 → avios_cli-0.6.1}/.github/workflows/ci.yml +0 -0
  19. {avios_cli-0.5.0 → avios_cli-0.6.1}/.github/workflows/publish.yml +0 -0
  20. {avios_cli-0.5.0 → avios_cli-0.6.1}/.gitignore +0 -0
  21. {avios_cli-0.5.0 → avios_cli-0.6.1}/.pre-commit-config.yaml +0 -0
  22. {avios_cli-0.5.0 → avios_cli-0.6.1}/.python-version +0 -0
  23. {avios_cli-0.5.0 → avios_cli-0.6.1}/CONTRIBUTING.md +0 -0
  24. {avios_cli-0.5.0 → avios_cli-0.6.1}/LICENSE +0 -0
  25. {avios_cli-0.5.0 → avios_cli-0.6.1}/SECURITY.md +0 -0
  26. {avios_cli-0.5.0 → avios_cli-0.6.1}/docs/dashboard.svg +0 -0
  27. {avios_cli-0.5.0 → avios_cli-0.6.1}/docs/reward-flights.svg +0 -0
  28. {avios_cli-0.5.0 → avios_cli-0.6.1}/scripts/screenshot.py +0 -0
  29. {avios_cli-0.5.0 → avios_cli-0.6.1}/src/avios/accounts.py +0 -0
  30. {avios_cli-0.5.0 → avios_cli-0.6.1}/src/avios/aggregate.py +0 -0
  31. {avios_cli-0.5.0 → avios_cli-0.6.1}/src/avios/cli.py +0 -0
  32. {avios_cli-0.5.0 → avios_cli-0.6.1}/src/avios/client.py +0 -0
  33. {avios_cli-0.5.0 → avios_cli-0.6.1}/src/avios/config.py +0 -0
  34. {avios_cli-0.5.0 → avios_cli-0.6.1}/src/avios/endpoints.py +0 -0
  35. {avios_cli-0.5.0 → avios_cli-0.6.1}/src/avios/finnair.py +0 -0
  36. {avios_cli-0.5.0 → avios_cli-0.6.1}/src/avios/models.py +0 -0
  37. {avios_cli-0.5.0 → avios_cli-0.6.1}/src/avios/programmes.py +0 -0
  38. {avios_cli-0.5.0 → avios_cli-0.6.1}/src/avios/rewards.py +0 -0
  39. {avios_cli-0.5.0 → avios_cli-0.6.1}/src/avios/session.py +0 -0
  40. {avios_cli-0.5.0 → avios_cli-0.6.1}/src/avios/tui/__init__.py +0 -0
  41. {avios_cli-0.5.0 → avios_cli-0.6.1}/src/avios/tui/app.py +0 -0
  42. {avios_cli-0.5.0 → avios_cli-0.6.1}/src/avios/tui/art.py +0 -0
  43. {avios_cli-0.5.0 → avios_cli-0.6.1}/src/avios/tui/rewards.py +0 -0
  44. {avios_cli-0.5.0 → avios_cli-0.6.1}/src/avios/tui/styles.tcss +0 -0
  45. {avios_cli-0.5.0 → avios_cli-0.6.1}/src/avios/tui/widgets.py +0 -0
  46. {avios_cli-0.5.0 → avios_cli-0.6.1}/tests/conftest.py +0 -0
  47. {avios_cli-0.5.0 → avios_cli-0.6.1}/tests/fixtures/balance.json +0 -0
  48. {avios_cli-0.5.0 → avios_cli-0.6.1}/tests/fixtures/reward_search.rsc +0 -0
  49. {avios_cli-0.5.0 → avios_cli-0.6.1}/tests/fixtures/transactions.json +0 -0
  50. {avios_cli-0.5.0 → avios_cli-0.6.1}/tests/test_accounts.py +0 -0
  51. {avios_cli-0.5.0 → avios_cli-0.6.1}/tests/test_aggregate.py +0 -0
  52. {avios_cli-0.5.0 → avios_cli-0.6.1}/tests/test_cli.py +0 -0
  53. {avios_cli-0.5.0 → avios_cli-0.6.1}/tests/test_client.py +0 -0
  54. {avios_cli-0.5.0 → avios_cli-0.6.1}/tests/test_finnair.py +0 -0
  55. {avios_cli-0.5.0 → avios_cli-0.6.1}/tests/test_models.py +0 -0
  56. {avios_cli-0.5.0 → avios_cli-0.6.1}/tests/test_rewards.py +0 -0
  57. {avios_cli-0.5.0 → avios_cli-0.6.1}/tests/test_session.py +0 -0
  58. {avios_cli-0.5.0 → avios_cli-0.6.1}/tests/test_tui.py +0 -0
@@ -6,6 +6,40 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.6.1] - 2026-11-11
10
+
11
+ ### Fixed
12
+ - `avios login ba` no longer reports "reward-flight login did not succeed" when BA's
13
+ reward app runs on the main `ba_session_id` session instead of issuing a separate
14
+ `__session`/`appSession*` cookie. The login now completes once the reward page is
15
+ loaded with a valid session.
16
+
17
+ ## [0.6.0] - 2026-08-11
18
+
19
+ ### Added
20
+ - **MCP server (`avios-mcp`).** Exposes your Avios accounts and British Airways
21
+ reward availability to Claude and any other MCP client over stdio. Install with
22
+ `uv tool install 'avios-cli[mcp]'`; sessions come from `avios login`, so the
23
+ server never performs a login itself. Closes #45.
24
+
25
+ Tools: `list_accounts`, `get_balance`, `get_transactions`,
26
+ `get_pending_transactions`, `whoami`, `search_reward_flights`.
27
+
28
+ Design constraints worth stating, because they are what make it safe to point an
29
+ agent at:
30
+
31
+ - **Read-only.** Nothing books, spends, or mutates a session.
32
+ - **No credentials in output.** Every tool returns an explicit response model
33
+ rather than a raw API payload, so cookies, API keys and the `whoami` id token
34
+ never leave the process.
35
+ - **Reward search is batched and rate-limited.** BA blocks by IP address, so an
36
+ agent fanning out over routes could lock you out of the site. A whole return
37
+ trip goes through one batched, single-browser-session search; the server
38
+ enforces a cooldown between searches; and a block is reported as terminal and
39
+ explicitly not retryable.
40
+ - **MCP is an optional extra**, so a plain CLI install does not pull in the SDK's
41
+ web stack.
42
+
9
43
  ## [0.5.0] - 2026-08-11
10
44
 
11
45
  ### Fixed
@@ -1,14 +1,14 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: avios-cli
3
- Version: 0.5.0
4
- Summary: A CLI and TUI for Avios balances, transactions, and BA reward-flight availability.
3
+ Version: 0.6.1
4
+ Summary: A CLI, TUI and MCP server for Avios balances, transactions, and BA reward-flight availability.
5
5
  Project-URL: Homepage, https://github.com/alexechoi/avios-cli
6
6
  Project-URL: Repository, https://github.com/alexechoi/avios-cli
7
7
  Project-URL: Issues, https://github.com/alexechoi/avios-cli/issues
8
8
  Author: Alex Choi
9
9
  License-Expression: MIT
10
10
  License-File: LICENSE
11
- Keywords: avios,british-airways,cli,loyalty,terminal,tui
11
+ Keywords: avios,british-airways,cli,loyalty,mcp,terminal,tui
12
12
  Classifier: Development Status :: 3 - Alpha
13
13
  Classifier: Environment :: Console
14
14
  Classifier: Intended Audience :: End Users/Desktop
@@ -31,6 +31,8 @@ Requires-Dist: rich>=13.7
31
31
  Requires-Dist: textual>=0.80
32
32
  Requires-Dist: typer>=0.12
33
33
  Provides-Extra: login
34
+ Provides-Extra: mcp
35
+ Requires-Dist: mcp<3,>=2.0; extra == 'mcp'
34
36
  Description-Content-Type: text/markdown
35
37
 
36
38
  # avios-cli
@@ -39,10 +41,10 @@ Description-Content-Type: text/markdown
39
41
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
40
42
  [![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/)
41
43
 
42
- A **CLI and TUI for your Avios programmes** — check balances, browse transactions,
43
- and search British Airways reward-flight availability without leaving the terminal.
44
- British Airways, Iberia, and Aer Lingus use avios.com; Finnair Plus uses Finnair's
45
- own OAuth and loyalty API.
44
+ A **CLI, TUI and MCP server for your Avios programmes** — check balances, browse
45
+ transactions, and search British Airways reward-flight availability without leaving
46
+ the terminal, or hand the same data to an AI agent. British Airways, Iberia, and
47
+ Aer Lingus use avios.com; Finnair Plus uses Finnair's own OAuth and loyalty API.
46
48
 
47
49
  > ⚠️ **Unofficial.** This project is not affiliated with, authorised by, or endorsed by
48
50
  > Avios, British Airways, Finnair or IAG Loyalty. It drives the same private endpoints
@@ -178,6 +180,41 @@ A tabbed full-screen application:
178
180
  Press `r` to refresh the dashboard and `q` to quit. Reward searches run only when
179
181
  you press Search or Enter.
180
182
 
183
+ ## MCP server
184
+
185
+ Expose your accounts to Claude and other MCP clients, read-only:
186
+
187
+ ```bash
188
+ uv tool install 'avios-cli[mcp]'
189
+ avios login ba # sessions come from the CLI; the server never logs in
190
+ ```
191
+
192
+ ```jsonc
193
+ // Claude Desktop: claude_desktop_config.json
194
+ {
195
+ "mcpServers": {
196
+ "avios": { "command": "avios-mcp" }
197
+ }
198
+ }
199
+ ```
200
+
201
+ Or, for Claude Code:
202
+
203
+ ```bash
204
+ claude mcp add avios -- uvx --from 'avios-cli[mcp]' avios-mcp
205
+ ```
206
+
207
+ Tools: `list_accounts`, `get_balance`, `get_transactions`,
208
+ `get_pending_transactions`, `whoami`, `search_reward_flights`.
209
+
210
+ Every tool is read-only — nothing books, spends, or changes a session — and no
211
+ cookie or token is ever returned. `search_reward_flights` searches a whole return
212
+ trip in one call and is rate-limited server-side, because it drives a real browser
213
+ and British Airways blocks by IP address; an agent looping over routes could get
214
+ you locked out of the site.
215
+
216
+ ## Notes on reward search
217
+
181
218
  Reward search is an unofficial view of BA's own flight finder. It supports direct
182
219
  BA flights, three-letter airport/city codes, and seat counts by cabin. Searching
183
220
  an exact `--date` also fetches the Avios price per cabin for your party; calendar
@@ -197,6 +234,7 @@ one-way searches sharing one browser session.
197
234
  - [x] Multiple accounts (BA, Iberia, Aer Lingus, Finnair) with combined views
198
235
  - [x] British Airways reward-flight **availability** search (CLI + TUI)
199
236
  - [x] Avios pricing per cabin for an exact date
237
+ - [x] MCP server (`avios-mcp`) for AI agents
200
238
 
201
239
  ## Development
202
240
 
@@ -4,10 +4,10 @@
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
5
5
  [![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/)
6
6
 
7
- A **CLI and TUI for your Avios programmes** — check balances, browse transactions,
8
- and search British Airways reward-flight availability without leaving the terminal.
9
- British Airways, Iberia, and Aer Lingus use avios.com; Finnair Plus uses Finnair's
10
- own OAuth and loyalty API.
7
+ A **CLI, TUI and MCP server for your Avios programmes** — check balances, browse
8
+ transactions, and search British Airways reward-flight availability without leaving
9
+ the terminal, or hand the same data to an AI agent. British Airways, Iberia, and
10
+ Aer Lingus use avios.com; Finnair Plus uses Finnair's own OAuth and loyalty API.
11
11
 
12
12
  > ⚠️ **Unofficial.** This project is not affiliated with, authorised by, or endorsed by
13
13
  > Avios, British Airways, Finnair or IAG Loyalty. It drives the same private endpoints
@@ -143,6 +143,41 @@ A tabbed full-screen application:
143
143
  Press `r` to refresh the dashboard and `q` to quit. Reward searches run only when
144
144
  you press Search or Enter.
145
145
 
146
+ ## MCP server
147
+
148
+ Expose your accounts to Claude and other MCP clients, read-only:
149
+
150
+ ```bash
151
+ uv tool install 'avios-cli[mcp]'
152
+ avios login ba # sessions come from the CLI; the server never logs in
153
+ ```
154
+
155
+ ```jsonc
156
+ // Claude Desktop: claude_desktop_config.json
157
+ {
158
+ "mcpServers": {
159
+ "avios": { "command": "avios-mcp" }
160
+ }
161
+ }
162
+ ```
163
+
164
+ Or, for Claude Code:
165
+
166
+ ```bash
167
+ claude mcp add avios -- uvx --from 'avios-cli[mcp]' avios-mcp
168
+ ```
169
+
170
+ Tools: `list_accounts`, `get_balance`, `get_transactions`,
171
+ `get_pending_transactions`, `whoami`, `search_reward_flights`.
172
+
173
+ Every tool is read-only — nothing books, spends, or changes a session — and no
174
+ cookie or token is ever returned. `search_reward_flights` searches a whole return
175
+ trip in one call and is rate-limited server-side, because it drives a real browser
176
+ and British Airways blocks by IP address; an agent looping over routes could get
177
+ you locked out of the site.
178
+
179
+ ## Notes on reward search
180
+
146
181
  Reward search is an unofficial view of BA's own flight finder. It supports direct
147
182
  BA flights, three-letter airport/city codes, and seat counts by cabin. Searching
148
183
  an exact `--date` also fetches the Avios price per cabin for your party; calendar
@@ -162,6 +197,7 @@ one-way searches sharing one browser session.
162
197
  - [x] Multiple accounts (BA, Iberia, Aer Lingus, Finnair) with combined views
163
198
  - [x] British Airways reward-flight **availability** search (CLI + TUI)
164
199
  - [x] Avios pricing per cabin for an exact date
200
+ - [x] MCP server (`avios-mcp`) for AI agents
165
201
 
166
202
  ## Development
167
203
 
@@ -4,13 +4,13 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "avios-cli"
7
- description = "A CLI and TUI for Avios balances, transactions, and BA reward-flight availability."
7
+ description = "A CLI, TUI and MCP server for Avios balances, transactions, and BA reward-flight availability."
8
8
  readme = "README.md"
9
9
  license = "MIT"
10
10
  license-files = ["LICENSE"]
11
11
  requires-python = ">=3.10"
12
12
  authors = [{ name = "Alex Choi" }]
13
- keywords = ["avios", "british-airways", "cli", "tui", "loyalty", "terminal"]
13
+ keywords = ["avios", "british-airways", "cli", "tui", "loyalty", "terminal", "mcp"]
14
14
  classifiers = [
15
15
  "Development Status :: 3 - Alpha",
16
16
  "Environment :: Console",
@@ -45,6 +45,10 @@ dependencies = [
45
45
  # base package, so `avios-cli[login]` resolves to the same thing as `avios-cli`.
46
46
  login = []
47
47
 
48
+ # MCP server (`avios-mcp`). Optional so a plain CLI install stays small; the SDK
49
+ # pulls in a web stack the terminal app has no use for.
50
+ mcp = ["mcp>=2.0,<3"]
51
+
48
52
  # Dev tooling lives in a PEP 735 dependency-group (not an extra) so that `uv sync`
49
53
  # and `uv run` install it by default — an extra would be pruned by `uv run`.
50
54
  [dependency-groups]
@@ -56,12 +60,18 @@ dev = [
56
60
  "ruff>=0.6",
57
61
  "mypy>=1.10",
58
62
  "types-PyYAML",
63
+ # In the dev group, not just the `mcp` extra, so CI type-checks and tests the
64
+ # MCP server rather than skipping it.
65
+ "mcp>=2.0,<3",
59
66
  ]
60
67
 
61
68
  [project.scripts]
62
69
  avios = "avios.cli:app"
63
70
  # Alias so `uvx avios-cli` (matching the package name) works too.
64
71
  avios-cli = "avios.cli:app"
72
+ # MCP server over stdio; needs the `mcp` extra. The wrapper module reports a
73
+ # missing extra as one readable line rather than an import traceback.
74
+ avios-mcp = "avios.mcp_entry:main"
65
75
 
66
76
  [project.urls]
67
77
  Homepage = "https://github.com/alexechoi/avios-cli"
@@ -99,5 +109,5 @@ mypy_path = "src"
99
109
  plugins = ["pydantic.mypy"]
100
110
 
101
111
  [[tool.mypy.overrides]]
102
- module = ["browser_cookie3.*", "playwright.*"]
112
+ module = ["browser_cookie3.*", "playwright.*", "mcp.*"]
103
113
  ignore_missing_imports = true
@@ -0,0 +1,7 @@
1
+ """avios — a CLI, TUI and MCP server for avios.com.
2
+
3
+ View your Avios balance, transactions and reward-flight availability from the
4
+ terminal, or serve them to an AI agent with ``avios-mcp``.
5
+ """
6
+
7
+ __version__ = "0.6.1"
@@ -33,6 +33,7 @@ from avios.session import cookie_header_from
33
33
  LOGIN_TIMEOUT_MS = 300_000 # 5 minutes to complete password + captcha + MFA
34
34
  LOGIN_POLL_MS = 2_000 # how often to check whether the session is authenticated
35
35
  SUPPORTED_BROWSERS = ("chrome", "firefox", "edge", "brave", "safari", "chromium")
36
+ BA_ACCOUNT_SESSION_COOKIE = "ba_session_id"
36
37
  BA_REWARD_SEARCH_PATH = "/en-GB/spend-avios/search-reward-flights"
37
38
 
38
39
  # Chromium-family user-data roots per browser (relative to home), used to discover
@@ -140,14 +141,29 @@ def login_via_browser(
140
141
  return _only_avios(cookies)
141
142
 
142
143
 
143
- def _has_ba_reward_session(ctx: Any) -> bool:
144
- """Whether BA's separate reward-search application session is present."""
145
- names = {
144
+ def _avios_cookie_names(ctx: Any) -> set[str]:
145
+ return {
146
146
  str(cookie.get("name", ""))
147
147
  for cookie in ctx.cookies()
148
148
  if "avios.com" in str(cookie.get("domain", ""))
149
149
  }
150
- return "__session" in names or any(name.startswith("appSession") for name in names)
150
+
151
+
152
+ def _has_ba_reward_session(ctx: Any) -> bool:
153
+ """Whether a session usable by BA's reward-search application is present.
154
+
155
+ BA historically issued a separate ``__session``/``appSession*`` cookie for the
156
+ reward app. It now also serves the reward app from the main ``ba_session_id``
157
+ session without setting those cookies, so that cookie counts too. The caller
158
+ still requires the browser to be sitting on the reward page, so a redirect to a
159
+ separate login keeps the wait going.
160
+ """
161
+ names = _avios_cookie_names(ctx)
162
+ return (
163
+ "__session" in names
164
+ or BA_ACCOUNT_SESSION_COOKIE in names
165
+ or any(name.startswith("appSession") for name in names)
166
+ )
151
167
 
152
168
 
153
169
  def _wait_for_ba_reward_auth(ctx: Any, page: Any, base_url: str, timeout_ms: int) -> bool:
@@ -0,0 +1,36 @@
1
+ """Console-script wrapper for ``avios-mcp``.
2
+
3
+ Kept separate from :mod:`avios.mcp_server` so that a missing ``mcp`` extra produces
4
+ one actionable line on stderr instead of an ``ImportError`` traceback — the server
5
+ is normally launched by an MCP client, which shows the user very little.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import sys
11
+ from importlib.util import find_spec
12
+
13
+ INSTALL_HINT = (
14
+ "avios-mcp needs the `mcp` extra, which is not installed.\n"
15
+ "\n"
16
+ " uv tool install 'avios-cli[mcp]'\n"
17
+ "\n"
18
+ "or run it without installing:\n"
19
+ "\n"
20
+ " uvx --from 'avios-cli[mcp]' avios-mcp"
21
+ )
22
+
23
+
24
+ def main() -> None:
25
+ """Serve the Avios MCP server over stdio."""
26
+ if find_spec("mcp") is None:
27
+ print(INSTALL_HINT, file=sys.stderr)
28
+ raise SystemExit(1)
29
+
30
+ from avios.mcp_server import mcp
31
+
32
+ mcp.run(transport="stdio")
33
+
34
+
35
+ if __name__ == "__main__":
36
+ main()