Content
# etoro-mcp
MCP server for the [eToro public API](https://api-portal.etoro.com/): market data, balances,
portfolio, trade history, watchlists, alerts, investor rankings, user statistics, social feeds,
and — behind explicit opt-in gates — trading, watchlist/alert mutations, and social posting.
Coverage is organised into **toolsets**; only the toolsets you select are registered, and write
tools are not registered at all unless their gate is enabled — a client connected to the default
configuration cannot see or call them.
## Features
- **Read-first by design** — 62 read tools across eleven of the twelve toolsets (the `social`
toolset is write-only); the 43 write tools exist only when their gate is set — 5 trading
tools behind `ETORO_MCP_ENABLE_TRADING`, the other 38 (watchlist, alert, social, and
notification mutations) behind `ETORO_MCP_ENABLE_WRITES`.
- **Demo environment by default** — trading tools call the demo endpoint group unless
`ETORO_ENV=real` is set.
- **Toolset selection** — register only what you need via `ETORO_MCP_TOOLSETS`; unknown names
fail startup rather than serving a partial tool set.
- **Trimmed responses** — tools return the fields an investor acts on (prices, units, P&L,
timestamps), not raw API dumps; list tools are bounded by a `limit` parameter or by
explicit range parameters (`min_date`/`max_date`/`count`) on the time-series tools.
- **Credentials stay in the environment** — keys travel only in the `x-api-key`/`x-user-key`
headers, and error messages never echo header or credential values.
- **Rate-limit aware** — HTTP 429 responses are retried once, honouring `Retry-After`, re-sending
the same `x-request-id` so a retried order cannot fill twice.
## Toolsets
Select toolsets with `ETORO_MCP_TOOLSETS` (comma-separated; `all` selects every toolset).
Without it, the trading core is registered:
| Toolset | Default | Covers |
|---|---|---|
| `market` | ✓ | Instrument search and details, candles, rates, closing prices, exchanges, instrument types, stock industries |
| `account` | ✓ | Profile, balances (aggregated, by account type, history), cash transactions |
| `trading` | ✓ | Portfolio, P&L, order status, eligibility and cost estimates; gated order/position writes |
| `history` | ✓ | Closed-trade history |
| `watchlists` | ✓ | Own/public/curated watchlists and recommendations; gated watchlist mutations |
| `alerts` | ✓ | Price alerts; gated alert mutations |
| `rankings` | | Investor rankings, presets, tags, sector/industry summaries |
| `users` | | User search, profiles, portfolios, performance and copier statistics |
| `feeds` | | News, following, for-you, instrument, and user feeds; posts and comments |
| `social` | | Gated posting, comments, replies, reactions, polls, and media upload |
| `clubs` | | eToro Club dashboard |
| `notifications` | | Notifications; gated mark-read |
## Tools
### Read tools (registered whenever their toolset is selected)
| Toolset | Tool | Description |
|---|---|---|
| `market` | `search_instruments` | Search instruments by ticker symbol and return candidate matches (contains-matching, so verify the symbol) |
| `market` | `get_instrument_details` | Display data for instruments by id: name, symbol, type, exchange |
| `market` | `get_candles` | OHLCV candles for one instrument (nine intervals, up to 1000 bars) |
| `market` | `get_closing_prices` | Latest official closing prices per instrument |
| `market` | `get_rates` | Real-time bid/ask quotes for instruments |
| `market` | `list_exchanges` | Exchanges (id and description) for resolving `exchangeId` values |
| `market` | `list_instrument_types` | Instrument types (stocks, crypto, ETFs, ...) by id |
| `market` | `list_stock_industries` | Stock industries (id and name) for resolving `stocksIndustryId` values |
| `account` | `get_profile` | The authenticated user's identity and the scopes their keys carry |
| `account` | `get_aggregated_balances` | Current balances aggregated across the user's accounts |
| `account` | `get_balances_by_type` | Current balances for every account of one account type |
| `account` | `get_balance` | Current balance of one specific account |
| `account` | `get_balance_history` | Daily balance snapshots over a date range |
| `account` | `list_cash_transactions` | Transactions of a cash account, newest pages first |
| `trading` | `get_portfolio` | Aggregated portfolio: account totals plus one aggregate per instrument |
| `trading` | `get_portfolio_breakdown` | Position-level snapshot: open positions and pending orders |
| `trading` | `get_pnl` | Portfolio snapshot with unrealized P&L per position |
| `trading` | `get_order` | Look up an order's status by order id or reference id |
| `trading` | `get_close_order_info` | Look up a close order's status by its order id |
| `trading` | `check_trading_eligibility` | Per-instrument trading rules: leverage, sizing mode, SL/TP bounds |
| `trading` | `get_trading_cost_estimate` | Estimate the costs of a hypothetical open order without placing it |
| `history` | `list_trading_history` | Closed trades since a given date |
| `watchlists` | `list_watchlists` | The authenticated user's watchlists with their items |
| `watchlists` | `get_watchlist` | A single watchlist owned by the authenticated user, by id |
| `watchlists` | `get_default_watchlist` | Items of the authenticated user's default watchlist |
| `watchlists` | `get_public_watchlists` | Another user's public watchlists by their numeric user id |
| `watchlists` | `get_curated_lists` | eToro's curated investment lists available to the user |
| `watchlists` | `get_market_recommendations` | Personalized market recommendations for the authenticated user |
| `alerts` | `list_price_alerts` | The active price alerts on the account |
| `rankings` | `list_ranking_presets` | Names of all publicly available ranking presets |
| `rankings` | `get_rankings_by_preset` | Investor ranking rows for a named preset |
| `rankings` | `get_investor_rankings` | A filtered, sorted page of investor ranking rows |
| `rankings` | `get_investor_ranking_row` | The full ranking row for a single investor |
| `rankings` | `bulk_ranking_rows` | Ranking rows for several investors in one call |
| `rankings` | `list_ranking_tags` | The catalog of ranking tags with resolved names |
| `rankings` | `get_ranking_universe_by_sector` | The ranking universe aggregated by sector or industry |
| `users` | `search_users` | Search public investors with performance and risk filters |
| `users` | `get_user_profile` | Public profile and account summary for one or more users |
| `users` | `get_user_portfolio` | A user's live public portfolio: open positions and copy trades |
| `users` | `get_user_performance` | A user's full monthly and yearly gain history |
| `users` | `get_user_daily_performance` | A user's day-by-day or aggregated gain over a date range |
| `users` | `get_user_trade_info` | A user's trading statistics for a period (copy-trading due diligence) |
| `users` | `get_gain_timeseries` | An investor's gain time-series at daily, monthly, or yearly resolution |
| `users` | `get_asset_allocation_history` | A portfolio's daily asset-allocation breakdown over time |
| `users` | `get_market_exposure_history` | A portfolio's daily directional market exposure per instrument |
| `users` | `get_copier_stats` | An investor's live copier count and assets-under-management tier |
| `users` | `get_copiers_info` | Anonymised info about the people copying your portfolio |
| `users` | `search_portfolios` | Search trader portfolios or Smart Portfolios by free text |
| `users` | `get_top_assets` | A user's top assets ranked by impressions-based score |
| `feeds` | `get_news_feed` | The eToro news feed (editorial and market-news posts) |
| `feeds` | `get_following_feed` | Posts from users the authenticated user follows |
| `feeds` | `get_for_you_feed` | The personalised For You feed for the authenticated user |
| `feeds` | `get_instrument_feed` | The discussion feed for one instrument (market) |
| `feeds` | `get_user_feed` | The posts published by one user |
| `feeds` | `get_saved_feed` | The posts the authenticated user has saved |
| `feeds` | `get_pinned_posts` | The posts a user has pinned to their profile |
| `feeds` | `get_post` | A single post by its UUID, with an inline comments preview |
| `feeds` | `list_post_comments` | The top-level comments on a post |
| `feeds` | `list_comment_replies` | The replies nested under one comment |
| `feeds` | `list_post_shares` | Who shared a post (plain reshares and quote shares) |
| `clubs` | `get_club_dashboard` | The eToro Club dashboard for the authenticated user |
| `notifications` | `list_notifications` | The account's in-app notifications, newest data first |
### Trading write tools (registered only when `ETORO_MCP_ENABLE_TRADING` is set)
| Toolset | Tool | Description |
|---|---|---|
| `trading` | `create_order` | Place an order to open a position — **not idempotent**: every call places a new order |
| `trading` | `cancel_order` | Cancel a pending (not yet executed) order |
| `trading` | `close_position` | Close an open position, fully or partially, at market |
| `trading` | `cancel_close_order` | Cancel a pending close order before it executes |
| `trading` | `modify_position_sltp` | Edit an open position's stop-loss and/or take-profit |
### Other write tools (registered only when `ETORO_MCP_ENABLE_WRITES` is set)
| Toolset | Tool | Description |
|---|---|---|
| `watchlists` | `create_watchlist` | Create a new watchlist |
| `watchlists` | `rename_watchlist` | Rename a watchlist |
| `watchlists` | `delete_watchlist` | Delete a watchlist |
| `watchlists` | `add_watchlist_items` | Add items to a watchlist |
| `watchlists` | `remove_watchlist_items` | Remove items from a watchlist |
| `watchlists` | `update_watchlist_items` | Reorder items within a watchlist |
| `watchlists` | `set_default_watchlist` | Set a watchlist as the selected default |
| `watchlists` | `change_watchlist_rank` | Change a watchlist's display position |
| `alerts` | `create_price_alert` | Create a price alert on an instrument |
| `alerts` | `update_price_alert` | Change the target price of an existing price alert |
| `alerts` | `delete_price_alert` | Permanently delete a price alert |
| `notifications` | `mark_notifications_read` | Mark all in-app notifications as read |
| `social` | `create_post` | Create a discussion post on the authenticated user's feed |
| `social` | `create_poll_post` | Create a poll post with 2 to 4 answer options |
| `social` | `update_post` | Edit the authenticated user's own post in-place |
| `social` | `delete_post` | Soft-delete the authenticated user's own post |
| `social` | `pin_post` | Pin one of the authenticated user's own posts to their profile |
| `social` | `unpin_post` | Remove the authenticated user's pin from a post |
| `social` | `create_comment` | Create a top-level comment on a post |
| `social` | `update_comment` | Edit the authenticated user's own comment in-place |
| `social` | `delete_comment` | Soft-delete the authenticated user's own comment |
| `social` | `create_reply` | Create a reply nested under an existing comment |
| `social` | `update_reply` | Edit the authenticated user's own reply in-place |
| `social` | `delete_reply` | Soft-delete the authenticated user's own reply |
| `social` | `share_post` | Share an existing post, as a plain reshare or a quote share |
| `social` | `upload_media` | Upload a JPEG or PNG image (max 8 MB) for use as an attachment; requires `ETORO_MCP_UPLOAD_DIR` and reads only inside it |
| `social` | `like_post` | Like a post |
| `social` | `unlike_post` | Remove a like from a post |
| `social` | `like_comment` | Like a comment on a post |
| `social` | `unlike_comment` | Remove a like from a comment |
| `social` | `like_reply` | Like a reply (a nested answer to a comment) |
| `social` | `unlike_reply` | Remove a like from a reply |
| `social` | `save_post` | Bookmark a post |
| `social` | `unsave_post` | Remove a post from the saved bookmarks |
| `social` | `follow_post` | Subscribe to notifications on a post |
| `social` | `unfollow_post` | Unsubscribe from notifications on a post |
| `social` | `vote_poll` | Cast a vote on a poll option |
| `social` | `remove_poll_vote` | Remove a vote from a poll option |
## Configuration
All configuration comes from environment variables — no config files, no flags carrying secrets.
| Variable | Default | Purpose |
|---|---|---|
| `ETORO_API_KEY` | — (required) | eToro API key, sent as the `x-api-key` header |
| `ETORO_USER_KEY` | — (required) | eToro user key, sent as the `x-user-key` header |
| `ETORO_ENV` | `demo` | `demo` or `real` — which trading endpoint group tools call. eToro keys are environment-bound, so a mismatched key fails at the API |
| `ETORO_MCP_TOOLSETS` | trading core (see table above) | Comma-separated toolset names; `all` selects every toolset. Unknown names fail startup with the list of valid names |
| `ETORO_MCP_ENABLE_TRADING` | off | `true`/`1`/`yes` registers the trading write tools: order placement/cancel, position close, stop-loss/take-profit edits |
| `ETORO_MCP_ENABLE_WRITES` | off | `true`/`1`/`yes` registers all other write tools: watchlist/alert mutations, notification mark-read, social posting and reactions |
| `ETORO_MCP_UPLOAD_DIR` | — (unset) | The **only** directory `upload_media` may read images from. Unset (the default) leaves `upload_media` unregistered, so the server can never read local files. Paths and symlinks resolving outside the directory are rejected |
## Getting started
Run straight from the repository with [uv](https://docs.astral.sh/uv/):
```sh
uvx --from git+https://github.com/florinel-chis/etoro-mcp etoro-mcp
```
The server speaks stdio by default; pass `--transport http --host 127.0.0.1 --port 8000` to serve
streamable HTTP at `/mcp` instead.
> **Warning:** the HTTP endpoint has **no authentication** — anyone who can reach the port can
> call every registered tool with your API keys' privileges. The server therefore
> **refuses to bind a non-loopback `--host` unless `--allow-remote` is also passed**; only use
> that flag behind an authenticated reverse proxy or an otherwise-restricted network (such as
> the Docker port-mapping below).
### MCP client configuration
Add the server to your MCP client's configuration (the exact file location depends on the client):
```json
{
"mcpServers": {
"etoro": {
"command": "uvx",
"args": ["--from", "git+https://github.com/florinel-chis/etoro-mcp", "etoro-mcp"],
"env": {
"ETORO_API_KEY": "your-api-key",
"ETORO_USER_KEY": "your-user-key",
"ETORO_ENV": "demo"
}
}
}
}
```
Or run the Docker image (build it first, see below):
```json
{
"mcpServers": {
"etoro": {
"command": "docker",
"args": ["run", "-i", "--rm", "-e", "ETORO_API_KEY", "-e", "ETORO_USER_KEY", "-e", "ETORO_ENV", "etoro-mcp"],
"env": {
"ETORO_API_KEY": "your-api-key",
"ETORO_USER_KEY": "your-user-key",
"ETORO_ENV": "demo"
}
}
}
}
```
## Docker
```sh
docker build -t etoro-mcp .
# stdio (for MCP client configs)
docker run -i --rm -e ETORO_API_KEY -e ETORO_USER_KEY etoro-mcp
# streamable HTTP at http://127.0.0.1:8000/mcp
# (-p 127.0.0.1:8000:8000 keeps the unauthenticated endpoint off external interfaces;
# --host 0.0.0.0 refers to interfaces inside the container and is required for the
# port mapping to work, hence --allow-remote — the host-side bind stays loopback-only)
docker run --rm -p 127.0.0.1:8000:8000 -e ETORO_API_KEY -e ETORO_USER_KEY \
etoro-mcp --transport http --host 0.0.0.0 --port 8000 --allow-remote
```
## Safety
- Trading tools call the **demo** endpoint group unless `ETORO_ENV=real` is set. Keys are
environment-bound, so a demo key against `real` (or vice versa) fails at the API rather than
trading the wrong account.
- Write tools are hidden behind **two independent gates** — `ETORO_MCP_ENABLE_TRADING` for
order/position writes and `ETORO_MCP_ENABLE_WRITES` for everything else (watchlists, alerts,
notifications, social). Gated tools are never registered, not merely rejected, so MCP clients
cannot discover or call them.
- The HTTP transport is **unauthenticated**: anyone who can reach the port can call every
registered tool — read balances and positions always, and place or close orders when trading
is enabled — using your API keys' privileges. The server refuses non-loopback binds unless
`--allow-remote` is passed; only use that flag behind an authenticated proxy or an
otherwise-restricted network (the stdio transport has no such exposure).
- `upload_media` reads local JPEG/PNG files and returns a **public CDN URL** for them — an
extra privilege class beyond the API keys themselves. It is therefore triple-gated: the
`social` toolset, `ETORO_MCP_ENABLE_WRITES`, **and** `ETORO_MCP_UPLOAD_DIR` must all be set,
and it reads only inside that directory (paths and symlinks resolving outside it are
rejected, file content must actually be JPEG/PNG, and the 8 MB limit is checked before the
file is read).
- `create_order` is **not idempotent across calls**: each call mints a fresh `x-request-id`,
which is the order's idempotency key, so calling it twice places two orders. (The 429 retry
re-sends the same id, so a rate-limited retry cannot double-fill.) Poll fill status with
`get_order` before treating an order as filled — an accepted order can still be rejected.
- Candle history is capped at roughly **1000 bars per interval** with no way to page further
back; use a coarser interval (e.g. `OneWeek`) for deeper history.
- The API enforces **per-group rate quotas** (order execution 20 requests/60 s; market data
120/60 s; portfolio and most other groups 60/60 s; eligibility and cost estimates 20/60 s
each). Each tool's description names its quota; 429s are retried once, honouring
`Retry-After`.
- Trading leveraged products is risky. Use at your own risk, and test everything against a
demo account first.
## Development
```sh
uv sync
uv run pytest
uv run ruff check .
```
Tests are hermetic: all HTTP is mocked with respx and the server is exercised through the
in-memory FastMCP client. No credentials or network access are needed to run them. A manual,
read-only smoke check against a real demo account lives at `scripts/smoke.py`.
## License
MIT — see [LICENSE](LICENSE).
MCP Config
Below is the configuration for this MCP Server. You can copy it directly to Cursor or other MCP clients.
mcp.json
Connection Info
You Might Also Like
No recommendations available