finanzen-zero-mcp 0.1.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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Klaus Deja
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,408 @@
1
+ Metadata-Version: 2.4
2
+ Name: finanzen-zero-mcp
3
+ Version: 0.1.0
4
+ Summary: MCP server for the German broker Finanzen.net ZERO
5
+ Keywords: finanzen.net,zero,broker,trading,mcp,model-context-protocol
6
+ Author: Klaus Deja
7
+ Author-email: Klaus Deja <klaus.deja@easesolutions.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Topic :: Office/Business :: Financial :: Investment
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Classifier: Typing :: Typed
19
+ Requires-Dist: finanzen-zero-api>=0.1.0,<0.2.0
20
+ Requires-Dist: mcp>=2.3.0,<3.0.0
21
+ Requires-Dist: pydantic>=2.14.1
22
+ Requires-Dist: pydantic-settings>=2.16.1,<3.0.0
23
+ Requires-Python: >=3.13
24
+ Project-URL: Homepage, https://github.com/wtfuii/zero-mcp
25
+ Project-URL: Repository, https://github.com/wtfuii/zero-mcp
26
+ Project-URL: Issues, https://github.com/wtfuii/zero-mcp/issues
27
+ Project-URL: Releases, https://github.com/wtfuii/zero-mcp/releases
28
+ Description-Content-Type: text/markdown
29
+
30
+ <h1 align="center">zero-mcp</h1>
31
+
32
+ <p align="center">
33
+ <strong>MCP server for the German broker Finanzen.net ZERO</strong> πŸ‡©πŸ‡ͺ<br>
34
+ Local stdio MCP server wrapping the unofficial Python client <code>finanzen-zero-api</code>.
35
+ </p>
36
+
37
+ <p align="center">
38
+ <a href="https://github.com/wtfuii/zero-mcp/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/wtfuii/zero-mcp/actions/workflows/ci.yml/badge.svg"></a>
39
+ <a href="https://pypi.org/project/finanzen-zero-mcp/"><img alt="PyPI" src="https://img.shields.io/pypi/v/finanzen-zero-mcp?logo=pypi&logoColor=white"></a>
40
+ <a href="https://www.python.org/downloads/"><img alt="Python 3.13+" src="https://img.shields.io/badge/python-3.13%2B-3776AB?logo=python&logoColor=white"></a>
41
+ <a href="https://mypy.readthedocs.io/en/stable/"><img alt="mypy: strict" src="https://img.shields.io/badge/mypy-strict-2A6DB2"></a>
42
+ <a href="#development"><img alt="Tests: offline only" src="https://img.shields.io/badge/tests-offline%20only-success"></a>
43
+ <a href="https://github.com/wtfuii/zero-mcp/blob/main/LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-yellow.svg"></a>
44
+ <a href="#project-status"><img alt="Status: alpha" src="https://img.shields.io/badge/status-alpha-orange.svg"></a>
45
+ </p>
46
+
47
+ > **Unofficial and unsupported.** Finanzen.net ZERO has no public API. This server wraps
48
+ > [`finanzen-zero-api`](https://github.com/wtfuii/zero-api), which mimics the broker's private
49
+ > web/app endpoints; they can change at any time. Use it only on an account you are allowed to
50
+ > automate, and never with money you cannot afford to lose. **Order tools move real money.**
51
+
52
+ `zero-mcp` turns the [`finanzen-zero-api`](https://github.com/wtfuii/zero-api) Python client into a
53
+ local [Model Context Protocol](https://modelcontextprotocol.io) server. It is published on PyPI as
54
+ [`finanzen-zero-mcp`](https://pypi.org/project/finanzen-zero-mcp/) and installs the `zero-mcp`
55
+ console script. An MCP host such as Claude Desktop, Claude Code or Cursor starts the server over
56
+ **stdio**; an assistant can then read the account, search instruments, inspect charts, list depot
57
+ positions and watchlist entries, review orders, and place or cancel orders. `ZERO_MODE` controls
58
+ whether the mutating tools are exposed and whether orders need confirmation β€” see
59
+ [Configuration](#configuration).
60
+
61
+ ## Contents
62
+
63
+ - [Features](#features)
64
+ - [Tools](#tools)
65
+ - [Requirements](#requirements)
66
+ - [Installation](#installation)
67
+ - [Configuration](#configuration)
68
+ - [Connect an MCP host](#connect-an-mcp-host)
69
+ - [Usage examples](#usage-examples)
70
+ - [Authentication and 2FA](#authentication-and-2fa)
71
+ - [Output conventions](#output-conventions)
72
+ - [Safety and security](#safety-and-security)
73
+ - [Architecture](#architecture)
74
+ - [Development](#development)
75
+ - [Limitations](#limitations)
76
+ - [Project status](#project-status)
77
+ - [License](#license)
78
+
79
+ ## Features
80
+
81
+ - **Full library coverage.** Account, instrument search/master data/detail/charts, depot positions,
82
+ watchlist, orders, and the app-push 2FA that unlocks order execution.
83
+ - **Local stdio only.** The MCP host spawns the process; there is no listening port, no browser and
84
+ no Playwright/Selenium dependency.
85
+ - **One serialized session.** All tools share a single lock-protected `ZeroClient`; broker calls are
86
+ serialized so concurrent requests cannot race the login, session refresh, 2FA unlock or order
87
+ flow.
88
+ - **Errors meant for a model.** Missing configuration, bad credentials, 2FA timeouts, broker
89
+ rejections and network failures become actionable MCP tool errors instead of stack traces.
90
+ - **Offline test suite.** A mocked broker client and a real stdio subprocess test; no live calls and
91
+ no credentials needed in CI.
92
+
93
+ ## Tools
94
+
95
+ Fifteen tools are registered in the default mode. The ten read tools are always available; the five
96
+ write tools are absent from `tools/list` when `ZERO_MODE=READ`.
97
+
98
+ | Tool | Kind | What it does |
99
+ |---|---|---|
100
+ | `get_account` | read | Cash account (Verrechnungskonto): buying/transfer power, IBAN, saldo, closing date. |
101
+ | `search_instruments` | read | Typeahead search by name, WKN or ISIN; per-asset-class hit counts. |
102
+ | `get_instrument` | read | Master record: asset class, trading state, fees, venues and MIC codes. |
103
+ | `get_instrument_detail` | read | Market data and fundamentals: quote, ranges, volume, profile, dividends. |
104
+ | `get_chart` | read | Price history for one scale (`Intraday` … `Maximum`). |
105
+ | `get_positions` | read | Depot positions with quantity, latest quote and entry quotes. |
106
+ | `get_watchlist` | read | Watchlist entries with quotes and metadata. |
107
+ | `list_orders` | read | Orders from one filter bucket (the broker's UI tabs) β€” `Open` (working, default), `Filled` (executed) or `Canceled` β€” with `has_next` pagination info. |
108
+ | `get_session_status` | read | Whether the session is unlocked for order execution. |
109
+ | `list_enrolled_devices` | read | Devices that can receive the 2FA push (with serials). |
110
+ | `add_to_watchlist` | write | Add an instrument by ISIN. |
111
+ | `remove_from_watchlist` | write | Remove an instrument by ISIN. |
112
+ | `unlock_order_execution` | write | Send the app push and wait for approval. |
113
+ | `place_order` | **write, real money** | Place a market/limit/stop-limit/OCO/trailing-stop order. |
114
+ | `cancel_order` | **write, real money** | Cancel an open order by its `order_id`. |
115
+
116
+ Order tools unlock the 2FA session automatically and can block for up to 60 seconds while the push is
117
+ approved. Their confirmation policy is set by `ZERO_MODE` (see [Configuration](#configuration)); for
118
+ device selection see [Authentication and 2FA](#authentication-and-2fa).
119
+
120
+ ## Requirements
121
+
122
+ | Requirement | Why |
123
+ |---|---|
124
+ | **Python 3.13+** | The project targets 3.13 and is strictly typed. |
125
+ | **A Finanzen.net ZERO account** | Email + password login is the only supported method. |
126
+ | **The ZERO mobile app** | Needed only for order execution: the 2FA push is approved on a paired device. Read-only tools work without it. |
127
+ | **Network access to `mein.finanzen-zero.net`** | Every endpoint lives there. There is no sandbox. |
128
+ | **An MCP host** | Claude Desktop, Claude Code, Cursor, or any client that launches local stdio servers. |
129
+
130
+ ## Installation
131
+
132
+ From this checkout, with [uv](https://docs.astral.sh/uv/):
133
+
134
+ ```bash
135
+ git clone https://github.com/wtfuii/zero-mcp.git
136
+ cd zero-mcp
137
+ uv sync
138
+ ```
139
+
140
+ `uv sync` installs the server and its dependencies into `.venv`. The MCP host should run the
141
+ `zero-mcp` console script through `uv run` (see [Connect an MCP host](#connect-an-mcp-host)).
142
+
143
+ With pip:
144
+
145
+ ```bash
146
+ pip install finanzen-zero-mcp
147
+ zero-mcp
148
+ ```
149
+
150
+ Or run it without installing, with uv:
151
+
152
+ ```bash
153
+ uvx --from finanzen-zero-mcp zero-mcp
154
+ ```
155
+
156
+ The PyPI distribution is `finanzen-zero-mcp`; the console script keeps the shorter name `zero-mcp`.
157
+ `zero-mcp` reads MCP messages from stdin and writes them to stdout. Running it directly in a
158
+ terminal will appear to hang; that is expected β€” an MCP host normally owns the process.
159
+
160
+ ## Configuration
161
+
162
+ Configuration is read from environment variables, which the MCP host passes to the stdio process.
163
+
164
+ | Variable | Required | Default | Meaning |
165
+ |---|---|---|---|
166
+ | `ZERO_USERNAME` | for any broker call | β€” | The ZERO login **e-mail address** (the client's `email`). |
167
+ | `ZERO_PASSWORD` | for any broker call | β€” | The ZERO account password. |
168
+ | `ZERO_DEFAULT_DEVICE` | no | β€” | 2FA device serial used when several devices are enrolled. |
169
+ | `ZERO_MODE` | no | `WRITE` | `READ`, `WRITE` or `YOLO`; see [below](#safety-modes-zero_mode). Case-insensitive. |
170
+
171
+ Startup and `tools/list` work without credentials; a tool call then fails with a clear
172
+ `not configured` message. Credentials are held in memory by the `ZeroClient`, are never written to
173
+ disk by this server, and are never logged. The password is stored as a `SecretStr`.
174
+
175
+ > Environment variables are visible to the spawned process and (depending on the host) to other
176
+ > local processes. Do not commit an MCP config containing your password, and prefer
177
+ > `ZERO_MODE=READ` unless you actively want trading tools.
178
+
179
+ ### Safety modes (`ZERO_MODE`)
180
+
181
+ `ZERO_MODE` is one switch with three mutually exclusive, case-insensitive values:
182
+
183
+ | Value | Registered tools | Order confirmation |
184
+ |---|---|---|
185
+ | `READ` | read tools only; the five write tools (watchlist edits, 2FA unlock, place/cancel order) are not registered | not applicable |
186
+ | `WRITE` (default) | all fifteen tools | the model must obtain the user's explicit confirmation before `place_order` or `cancel_order` |
187
+ | `YOLO` | all fifteen tools | no confirmation requirement in the server instructions or order tool descriptions |
188
+
189
+ Tools removed in `READ` never appear in `tools/list`. `WRITE` and `YOLO` differ only in the
190
+ instruction attached to the order tools; neither changes tool annotations. In every mode the broker's
191
+ app-push 2FA and any tool-approval prompt configured in the MCP host still apply β€” the server cannot
192
+ override its host.
193
+
194
+ > **`YOLO` is a footgun.** It removes the model-level requirement to ask before placing or cancelling
195
+ > an order. Use it only on an account and with amounts you can afford to trade without a human in
196
+ > the loop.
197
+
198
+ ## Connect an MCP host
199
+
200
+ Use an **absolute path** to this checkout:
201
+
202
+ ```json
203
+ {
204
+ "mcpServers": {
205
+ "finanzen-zero": {
206
+ "command": "uv",
207
+ "args": ["run", "--directory", "/absolute/path/to/zero-mcp", "zero-mcp"],
208
+ "env": {
209
+ "ZERO_USERNAME": "you@example.com",
210
+ "ZERO_PASSWORD": "your-password",
211
+ "ZERO_DEFAULT_DEVICE": "optional-device-serial",
212
+ "ZERO_MODE": "WRITE"
213
+ }
214
+ }
215
+ }
216
+ }
217
+ ```
218
+
219
+ - Claude Desktop: `claude_desktop_config.json` (Settings β†’ Developer β†’ Edit Config).
220
+ - Cursor: `.cursor/mcp.json` (project) or `~/.cursor/mcp.json` (global).
221
+ - Any host that accepts `command`/`args`/`env`.
222
+
223
+ GUI hosts often start with a minimal `PATH` that does not contain `uv`. After `uv sync`, point the
224
+ config at the console script directly instead:
225
+
226
+ ```json
227
+ {
228
+ "mcpServers": {
229
+ "finanzen-zero": {
230
+ "command": "/absolute/path/to/zero-mcp/.venv/bin/zero-mcp",
231
+ "env": {
232
+ "ZERO_USERNAME": "you@example.com",
233
+ "ZERO_PASSWORD": "your-password"
234
+ }
235
+ }
236
+ }
237
+ }
238
+ ```
239
+
240
+ On Windows the script is `.venv\Scripts\zero-mcp.exe`.
241
+
242
+ ### Claude Code
243
+
244
+ ```bash
245
+ claude mcp add finanzen-zero \
246
+ --env ZERO_USERNAME=you@example.com \
247
+ --env ZERO_PASSWORD=your-password \
248
+ --env ZERO_MODE=WRITE \
249
+ -- uv run --directory /absolute/path/to/zero-mcp zero-mcp
250
+ ```
251
+
252
+ ### Read-only setup
253
+
254
+ Enable only the ten read tools and keep the trading surface out of the model's context entirely:
255
+
256
+ ```json
257
+ {
258
+ "mcpServers": {
259
+ "finanzen-zero": {
260
+ "command": "uv",
261
+ "args": ["run", "--directory", "/absolute/path/to/zero-mcp", "zero-mcp"],
262
+ "env": {
263
+ "ZERO_USERNAME": "you@example.com",
264
+ "ZERO_PASSWORD": "your-password",
265
+ "ZERO_MODE": "READ"
266
+ }
267
+ }
268
+ }
269
+ }
270
+ ```
271
+
272
+ Restart the MCP host after changing environment variables. The process logs its mode and whether
273
+ credentials are configured to **stderr** on startup, never to stdout.
274
+
275
+ ## Usage examples
276
+
277
+ Once the server is connected, ask the assistant in natural language:
278
+
279
+ - β€œWhat is my current buying power and next closing date?”
280
+ - β€œSearch for Apple and show me the one-year chart.”
281
+ - β€œWhich positions do I hold and what are they worth at the latest quote?”
282
+ - β€œList my open orders and cancel order ID `GBR…`.”
283
+ - β€œPlace a limit buy for 1 share of Apple at €200 on gettex.”
284
+ - β€œI approved the push β€” check whether order execution is unlocked.”
285
+
286
+ The assistant discovers the account through the read tools. There is no separate login tool, and the
287
+ shared session is reused across calls.
288
+
289
+ ## Authentication and 2FA
290
+
291
+ Login is handled entirely by `finanzen-zero-api`:
292
+
293
+ 1. The first broker call performs a Cognito SRP login over HTTP through the broker's `/apic/authc`
294
+ proxy. No browser and no API key are involved.
295
+ 2. The one-hour IdToken is refreshed with the refresh-token cookie; a rejected session is
296
+ re-established once and the request retried.
297
+ 3. Sessions are in memory. Restarting the MCP server logs in again; no cookies are persisted.
298
+
299
+ Order execution additionally requires the app-push **session TAN** unlock:
300
+
301
+ 1. `place_order` / `cancel_order` probe `is_authorized`; when locked they request a push to the
302
+ configured device and wait for approval.
303
+ 2. `unlock_order_execution` exposes that step explicitly, with a configurable timeout and no-op when
304
+ already unlocked. It is useful only for pre-authorizing a batch or waiting longer than the order
305
+ tools' built-in 60 seconds.
306
+ 3. If exactly one device is enrolled it is used automatically. With several devices, set
307
+ `ZERO_DEFAULT_DEVICE` or call `list_enrolled_devices` and pass a serial; otherwise the call fails
308
+ rather than guessing.
309
+
310
+ Keep the host clock synchronized (NTP): SRP signs a UTC timestamp, and large clock skew makes the
311
+ login fail.
312
+
313
+ ## Output conventions
314
+
315
+ - Tool results are JSON objects. Lists are nested under a descriptive key (`positions`, `orders`,
316
+ `entries`, `devices`, `bars`) and usually come with a `count`.
317
+ - Keys use the Python client's **snake_case** field names (the names documented in the
318
+ `finanzen-zero-api` README), not the broker's camelCase wire aliases.
319
+ - Account, position, order and watchlist amounts are **decimal strings** (e.g. `"1000.50"`) so
320
+ precision survives the JSON round-trip. Parse them before doing arithmetic. Market-data values
321
+ such as chart bars and instrument detail quotes keep the broker's wire floats and are numbers.
322
+ - Timestamps are ISO 8601 strings; dates use `YYYY-MM-DD`. Account amounts are EUR; instrument and
323
+ order prices use the instrument's quote currency (`quote_currency_code`), not necessarily EUR.
324
+ - Failures are MCP `isError` results with a human/model-readable message, including the broker's
325
+ HTTP status, error code and field errors when available.
326
+
327
+ ## Safety and security
328
+
329
+ - **Real orders.** `place_order` and `cancel_order` are marked `destructiveHint` and
330
+ `readOnlyHint=false`. A market order can fill immediately and cannot be undone. If a placement
331
+ fails after submission (timeout or network error), check `list_orders` before retrying β€” the order
332
+ may already exist.
333
+ - **Credentials.** Passwords arrive via environment variables and stay in memory. This server does
334
+ not read `.env` files, keychains or config files. Treat MCP host config files as secrets when they
335
+ contain `ZERO_PASSWORD`.
336
+ - **Unofficial endpoints.** The broker can change or block these private endpoints at any time;
337
+ schema drift is surfaced as a tool error rather than silently wrong data. Automating a brokerage
338
+ account may violate the broker's terms β€” use only an account you are allowed to automate.
339
+
340
+ ## Architecture
341
+
342
+ ```
343
+ src/zero_mcp/
344
+ β”œβ”€β”€ server.py # MCPServer (MCP SDK v2), dynamic tool registration, stdio main()
345
+ β”œβ”€β”€ config.py # ZERO_* env settings, lazy credential validation
346
+ β”œβ”€β”€ client.py # ClientProvider + lazy, lock-protected shared ZeroClient
347
+ β”œβ”€β”€ errors.py # zero_api/httpx exceptions β†’ actionable ToolError
348
+ β”œβ”€β”€ serialization.py # Pydantic model β†’ JSON-safe dict
349
+ β”œβ”€β”€ __main__.py # python -m zero_mcp
350
+ └── tools/ # vertical slices, one module per broker area
351
+ β”œβ”€β”€ account.py
352
+ β”œβ”€β”€ instrument.py
353
+ β”œβ”€β”€ portfolio.py
354
+ β”œβ”€β”€ watchlist.py
355
+ β”œβ”€β”€ orders.py
356
+ └── sessiontan.py
357
+ ```
358
+
359
+ The MCP server is deliberately thin: it owns argument mapping, error translation, tool annotations,
360
+ `ZERO_MODE` gating and the confirmation/YOLO instructions. Everything else β€” HTTP, SRP login, refresh,
361
+ 2FA, Pydantic models β€” belongs to [`finanzen-zero-api`](https://github.com/wtfuii/zero-api).
362
+
363
+ ## Development
364
+
365
+ ```bash
366
+ uv sync
367
+ uv run mypy . # strict; run first
368
+ uv run pytest # offline; no broker calls
369
+ uv build # wheel + sdist
370
+ uvx twine check --strict dist/*
371
+ ```
372
+
373
+ To exercise the server by hand, launch it through any MCP client or the MCP Inspector:
374
+
375
+ ```bash
376
+ npx @modelcontextprotocol/inspector uv run --directory . zero-mcp
377
+ ```
378
+
379
+ Set `ZERO_USERNAME`/`ZERO_PASSWORD` in the inspector's environment only if you intend to make live
380
+ (read-only) calls. There is **no live smoke test in CI** and tests must never contact the broker.
381
+
382
+ See [AGENTS.md](AGENTS.md) for the architecture rules, MCP SDK conventions and contribution
383
+ constraints. Releases are cut from a `vX.Y.Z` tag and published to PyPI through Trusted Publishing;
384
+ see [RELEASING.md](RELEASING.md) for the registry setup and release checklist.
385
+
386
+ ## Limitations
387
+
388
+ - **Version coupling.** The wrapper pins `finanzen-zero-api>=0.1.0,<0.2.0`. Model or endpoint
389
+ changes in the alpha client can break tool output until the wrapper is updated.
390
+ - **stdio only.** No HTTP/SSE transport, no multi-user server, no OAuth. One process, one account.
391
+ - **No order modification.** The broker client supports placing and cancelling, not amending.
392
+ - **Blocking 2FA.** `place_order`, `cancel_order` and `unlock_order_execution` wait for the phone
393
+ approval; MCP hosts with a short tool timeout may abort the call before the push is accepted.
394
+ - **No streaming quotes.** Charts and details are snapshots returned by the private endpoints.
395
+ - **One page of orders.** `list_orders` returns a single page plus `has_next`; the page size is
396
+ bounded by the tool's `size` argument (max 1000).
397
+ - **Unknown rate limits.** The broker publishes none; avoid tight polling loops.
398
+
399
+ ## Project status
400
+
401
+ Alpha (`0.1.0`). All slices currently supported by `finanzen-zero-api` are wrapped: account,
402
+ instruments, portfolio, watchlist, orders and session TAN/2FA. The tool surface may change between
403
+ minor versions while the wrapper stabilizes. Pin the version you depend on.
404
+
405
+ ## License
406
+
407
+ MIT β€” see [LICENSE](LICENSE). This project is not affiliated with Finanzen.net, Finanzen.net ZERO,
408
+ or Baader Bank.