etoro-mcp
MCP server for the eToro public API: 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 writetools are not registered at all unless their gate is enabled — a client connected to the defaultconfiguration cannot see or call them.
Features
- Read-first by design — 62 read tools across eleven of the twelve toolsets (the
socialtoolset is write-only); the 43 write tools exist only when their gate is set — 5 tradingtools behindETORO_MCP_ENABLE_TRADING, the other 38 (watchlist, alert, social, andnotification mutations) behindETORO_MCP_ENABLE_WRITES. - Demo environment by default — trading tools call the demo endpoint group unless
ETORO_ENV=realis set. - Toolset selection — register only what you need via
ETORO_MCP_TOOLSETS; unknown namesfail 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
limitparameter or byexplicit 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-keyheaders, and error messages never echo header or credential values. - Rate-limit aware — HTTP 429 responses are retried once, honouring
Retry-After, re-sendingthe samex-request-idso 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:
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 servestreamable HTTP at /mcp instead.
Warning: the HTTP endpoint has no authentication — anyone who can reach the port cancall every registered tool with your API keys' privileges. The server thereforerefuses to bind a non-loopback
--hostunless--allow-remoteis also passed; only usethat flag behind an authenticated reverse proxy or an otherwise-restricted network (such asthe Docker port-mapping below).
MCP client configuration
Add the server to your MCP client's configuration (the exact file location depends on the client):
{
"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):
{
"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
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=realis set. Keys areenvironment-bound, so a demo key againstreal(or vice versa) fails at the API rather thantrading the wrong account. - Write tools are hidden behind two independent gates —
ETORO_MCP_ENABLE_TRADINGfororder/position writes andETORO_MCP_ENABLE_WRITESfor everything else (watchlists, alerts,notifications, social). Gated tools are never registered, not merely rejected, so MCP clientscannot discover or call them. - The HTTP transport is unauthenticated: anyone who can reach the port can call everyregistered tool — read balances and positions always, and place or close orders when tradingis enabled — using your API keys' privileges. The server refuses non-loopback binds unless
--allow-remoteis passed; only use that flag behind an authenticated proxy or anotherwise-restricted network (the stdio transport has no such exposure). upload_mediareads local JPEG/PNG files and returns a public CDN URL for them — anextra privilege class beyond the API keys themselves. It is therefore triple-gated: thesocialtoolset,ETORO_MCP_ENABLE_WRITES, andETORO_MCP_UPLOAD_DIRmust all be set,and it reads only inside that directory (paths and symlinks resolving outside it arerejected, file content must actually be JPEG/PNG, and the 8 MB limit is checked before thefile is read).create_orderis not idempotent across calls: each call mints a freshx-request-id,which is the order's idempotency key, so calling it twice places two orders. (The 429 retryre-sends the same id, so a rate-limited retry cannot double-fill.) Poll fill status withget_orderbefore 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 furtherback; use a coarser interval (e.g.
OneWeek) for deeper history. - The API enforces per-group rate quotas (order execution 20 requests/60 s; market data120/60 s; portfolio and most other groups 60/60 s; eligibility and cost estimates 20/60 seach). 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 ademo account first.
Development
uv sync
uv run pytest
uv run ruff check .
Tests are hermetic: all HTTP is mocked with respx and the server is exercised through thein-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.