mcp-server-saxo
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mcp-server-saxo what is my current account balance?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mcp-server-saxo
TypeScript MCP server for the Saxo Bank OpenAPI. Works against both the SIM (simulation/demo) and LIVE environments. Same shape as the rest of the Borgels MCP server family: typed, documented, policy-aware, credential-sane, and audit-friendly.
Disclaimer: This is an independent, unofficial project by Borgels. Borgels is not affiliated with, endorsed by, or supported by Saxo Bank A/S. "Saxo", "Saxo Bank", and the Saxo OpenAPI are referenced only to describe what this server talks to. You need your own Saxo developer credentials, and use of the Saxo OpenAPI is subject to Saxo Bank's own terms and licensing. Trading on LIVE moves real money. You are responsible for any orders this server places on your behalf.
Scope
Supported Saxo OpenAPI service groups:
Root (session, diagnostics)
Reference Data (instruments, exchanges)
Trading (info prices, snapshot chart, order place / modify / cancel / precheck)
Portfolio (accounts, balances, positions, closed positions, orders)
Value Add (price alert definitions and notification settings)
Streaming subscriptions (WebSocket), Value Add performance, and Client
Management beyond accounts/me are out of scope for v1.
Related MCP server: IBKR MCP Server
Quickstart on SIM
npm install
npm run build
cp .env.example .envThen pick one of:
Path A — 24-hour token (quickest, one-shot)
Sign up for a free SIM account at https://www.developer.saxo/.
App Management → Generate 24-hour token (no app required).
Paste it into
.env:SAXO_ENVIRONMENT=sim SAXO_ACCESS_TOKEN=...Start the server (see Path A/B
startblock below).
The token expires after 24h; you'll need to repeat step 2 to keep going.
Path B — OAuth app (refreshes automatically)
For anything you run longer than a day, do the OAuth dance once and let the server refresh tokens for you. This is also the path you'll need for LIVE.
Sign up at https://www.developer.saxo/.
App Management → Create application (mark as SIM, allow trading if you'll be placing orders). Pick either grant type — the server supports both:
Code (confidential client, has an App Secret)
PKCE (public client, no secret — slightly safer for distributable clients but functionally equivalent for this server)
Register the redirect URL in the portal:
For Code apps: register exactly
http://localhost:8765/callback. Saxo matches the full URL including port at runtime, and rejects IP-literal redirects likehttp://127.0.0.1:..., so use the hostname.For PKCE apps: register
http://localhost/callback— Saxo's PKCE flow requires the registered URL to omit the port ("When registering the redirect URL with your application, it cannot include a port number"). The server still sendshttp://localhost:8765/callbackat runtime; Saxo matches port-blind. Mismatching this returnsunauthorized_client.
Put the credentials in
.env:SAXO_ENVIRONMENT=sim SAXO_APP_KEY=... SAXO_APP_SECRET=... # Only for Code-grant apps. Omit for PKCE. SAXO_REDIRECT_URI=http://localhost:8765/callbackRun the auth CLI:
npm run auth -- --env simThis opens your browser to Saxo's authorize page, you click Allow, and the CLI writes
SAXO_ACCESS_TOKEN,SAXO_REFRESH_TOKEN, andSAXO_TOKEN_EXPIRES_ATback into.env. Both grant types use the same flow; the server detects the absence ofSAXO_APP_SECRETand switches to PKCE-style token exchange (no Authorization header,client_idin the form body).
The OAuth access token Saxo issues is short-lived (~20 minutes on SIM),
but SaxoClient proactively refreshes it from the refresh token ~60s
before expiry, so the server stays alive indefinitely.
Start the server
npm run dev # stdio transport
# or
npm run dev:http # http://127.0.0.1:3000/mcpMarket data is a second, separate consent
The 24-hour token unlocks authenticated API access, but live bid/ask
quotes via /trade/v1/infoprices require a separate per-exchange market
data agreement (NYSE, OPRA, EUREX, etc.). Until you accept it,
saxo_get_infoprice returns PriceTypeAsk/Bid: "NoAccess" with Amount: 0,
and saxo_session_me reports MarketDataViaOpenApiTermsAccepted: false.
There is no Saxo OpenAPI endpoint to flip this flag programmatically —
it's a human consent screen. Find it in the Saxo trading platform
(SaxoTraderGO → settings → live data subscriptions) or developer.saxo. Once
accepted, the same token starts returning quotes (typically DelayedByMinutes: 15
unless the current OpenAPI session has TradeLevel: "FullTradingAndChat").
DataLevel should stay at Saxo's default Standard; Saxo documents that it
has no impact for third-party applications.
Use saxo_get_session_capabilities to read TradeLevel directly, and
saxo_set_session_trade_level to switch between FullTradingAndChat and
OrdersOnly. Only one session per Saxo user can have FullTradingAndChat, so
upgrading this MCP session can downgrade SaxoTraderGO/PRO or another OpenAPI
session. saxo_diagnostics watches session events and flags TradeLevel
problems in warnings[]; infoprices responses are decorated with _warning
when they're NoAccess.
Authentication
This server reads credentials from environment variables only — they are never accepted as tool arguments.
Variable | Required for | Notes |
| always |
|
| always | Bearer token. 24-hour token for SIM, OAuth token for LIVE. |
| LIVE / long-running SIM | Together with app credentials enables 401-auto-refresh. |
| refresh / OAuth | Application key from the developer portal. |
| refresh / OAuth | Application secret. |
| OAuth | Defaults to |
| optional | Request timeout in ms (default 30000). |
Two ways to log in for LIVE / long-running SIM
Option A — CLI (one-off, scriptable):
SAXO_APP_KEY=... SAXO_APP_SECRET=... npm run auth -- --env liveThe CLI starts a local callback listener, opens your browser to the Saxo
authorize endpoint with a PKCE challenge, and writes
SAXO_ACCESS_TOKEN/SAXO_REFRESH_TOKEN/SAXO_TOKEN_EXPIRES_AT back into
.env.
Option B — From inside the MCP client (saxo_oauth_* tools):
Set
SAXO_APP_KEY+SAXO_APP_SECRETin the MCP server environment. In packaged clients such as Claude Desktop / MCPB this usually means client config, not a local env file.For the smooth local flow, call
saxo_oauth_login. It starts the loopback listener, opens the browser by default, waits for approval, exchanges the code, and updates the running MCP server in memory.Tokens are not written to disk unless
writeToEnvFile=trueis supplied, in which case they are written to.envin the server's working directory (the target path is not configurable from the tool call).
The saxo_oauth_* tools are only registered on the local stdio transport;
the optional HTTP server does not expose them.
For clients that want to control their own UI, use the lower-level two-step
flow: call saxo_oauth_start, open the returned authorizeUrl (or set
openBrowser=true), then call saxo_oauth_complete with the returned
ticketId.
The MCP server only listens on loopback (127.0.0.1) for the callback, so the
flow never touches the public network beyond Saxo itself.
Install
The server is published as @borgels/mcp-server-saxo on npm and
as an .mcpb bundle (MCP Bundle, the new name for DXT) on each
GitHub Release.
The protocol layer is the same everywhere — stdio + MCP JSON-RPC. The
table below is just the per-client config syntax.
Client | How to install |
Claude Desktop (1.8089+) | Download |
Claude Desktop (legacy | Add the universal JSON block to |
Claude Code (CLI) |
|
Cursor | Settings → MCP → Add server, or |
Codex (OpenAI) |
|
Windsurf / Cline / Zed / continue.dev | Settings UI, point at |
MCP Inspector (debug) |
|
Self-host (any client) |
|
Universal config
For any client that uses the standard mcpServers config schema:
{
"mcpServers": {
"saxo": {
"command": "npx",
"args": ["-y", "@borgels/mcp-server-saxo"],
"env": {
"SAXO_ENVIRONMENT": "sim",
"SAXO_ACCESS_TOKEN": "your-24h-sim-token"
}
}
}
}That's the minimal form — fine for a quick SIM test. For durable
use (no daily token paste), do the OAuth dance once with
npm run auth -- --env sim against a clone of this repo, then copy
the resulting OAuth env vars into the block:
{
"mcpServers": {
"saxo": {
"command": "npx",
"args": ["-y", "@borgels/mcp-server-saxo"],
"env": {
"SAXO_ENVIRONMENT": "sim",
"SAXO_APP_KEY": "...",
"SAXO_APP_SECRET": "...",
"SAXO_REFRESH_TOKEN": "...",
"SAXO_ACCESS_TOKEN": "...",
"SAXO_TOKEN_EXPIRES_AT": "2026-05-19T18:00:37.823Z",
"SAXO_ENABLE_LIVE_TRADING": "false"
}
}
}
}SAXO_ACCESS_TOKEN may be expired at startup — SaxoClient decodes
the JWT exp, detects it's within 60s of expiry, and runs the
refresh-token grant before sending the first request. The refresh
token is what really matters at cold start.
Restart after editing
Most MCP clients spawn server processes only at startup. After editing the client's config, fully quit and reopen it. Some clients (notably Claude Desktop) keep running in the system tray when the window is closed — make sure you actually exit before reopening.
Troubleshooting
If the server doesn't appear in the client's tool list, check the client's MCP logs (each client documents its log location).
Verify
npx -y @borgels/mcp-server-saxoruns from a fresh shell — it should start, register tools, and wait for stdin without error.Once connected, call
saxo_diagnosticsfirst when something looks off — itswarnings[]array surfaces missing market-data terms, near-expiry tokens,TradeLevelnot FullTradingAndChat, and LIVE order writes withoutSAXO_ENABLE_LIVE_TRADING.
Building from source (for hacking or before npm publish)
git clone https://github.com/Borgels/mcp-server-saxo.git
cd mcp-server-saxo
npm install
npm run build # produces dist/transports/stdio.js
npm testUse "command": "node", "args": ["/absolute/path/to/dist/transports/stdio.js"]
in your client config to point at the built source. For the MCPB bundle,
npm run mcpb:pack produces mcp-server-saxo.mcpb in the project root.
Start Here
Use saxo_capabilities first when an MCP client needs to decide which Saxo
tool to call. It returns tool descriptions, examples, identifier formats, and
safety notes without contacting Saxo.
{ "query": "place order", "limit": 5 }Optional Alpha Vantage enrichment
The strategy screeners are Saxo-first. They work without third-party data using
Saxo instruments, prices, chart bars, option chains, and account/position
context. If ALPHA_VANTAGE_API_KEY is set, stock and option strategy tools can
optionally enrich candidates with Alpha Vantage OVERVIEW,
NEWS_SENTIMENT, and EARNINGS_CALENDAR.
Leave ALPHA_VANTAGE_API_KEY unset to keep the server Saxo-only. Alpha
Vantage is optional because tiers and rate limits vary. For deeper research,
run Alpha Vantage's own MCP server beside this Saxo server and pass normalized
research into externalContextBySymbol.
Factor and portfolio context
Use saxo_screen_stock_factors when you want stock factor context with Saxo
quote/liquidity data, Saxo chart context, optional account sizing, optional
fundamentals/news enrichment, and warnings:
{
"accountKey": "your-account-key",
"market": "us",
"universe": "large_cap",
"objective": "balanced",
"riskProfile": "balanced",
"includeAccountContext": true,
"includeFundamentalContext": true,
"riskBudgetPercentPerIdea": 1,
"maxSingleNamePercent": 10,
"maxResults": 10
}Use saxo_screen_option_strategy_factors when you want optionable underlyings
and explicit strategy candidates with liquidity, structure, chart, IV/Greeks,
optional news, and account sizing factors:
{
"accountKey": "your-account-key",
"market": "us_nasdaq",
"underlyingUniverse": "auto",
"strategies": ["put_credit_spread", "iron_condor"],
"includeAccountContext": true,
"riskBudgetPercent": 1,
"requireGreeks": true,
"maxThetaDailyPercentOfRisk": 1,
"maxUnderlyings": 50,
"maxUnderlyingScan": 500,
"maxSymbolsToPlan": 5,
"maxPlans": 10
}Option candidate generation uses Saxo option-chain quotes, Saxo Greeks, optional OptionsChain IV context, and optional account-aware sizing. The output contains factor scores and warnings, not pass/watchlist/reject verdicts or confidence labels.
Use saxo_analyze_portfolio_context for whole-account context. It reads the
account snapshot, runs the stock and option factor tools, then returns risk
budgets, stock/option factor summaries, concentration context, sector context,
and warnings. It does not return target allocations, deployment stages, or
recommended contract counts:
{
"accountKey": "your-account-key",
"objective": "balanced_growth_income",
"riskProfile": "balanced",
"portfolioProfile": "concentrated_conviction",
"deploymentStyle": "staged",
"targetInvestedPercent": 80,
"cashReservePercent": 10,
"maxCashDollars": 1000,
"maxSingleNamePercent": 10,
"maxSectorPercent": 35,
"maxOptionsRiskPercent": 5,
"riskBudgetPercentPerIdea": 1,
"maxSelectedUnderlyings": 5,
"minPositionRiskDollars": 2500,
"maxContractsPerPosition": 20,
"fragmentationPolicy": "reject",
"requireGreeks": true,
"maxThetaDailyPercentOfRisk": 1,
"stockUniverse": "large_cap",
"stockMaxCandidates": 120,
"discoverOptionCandidates": true,
"optionDiscoveryUniverse": "auto",
"optionTheses": [
{
"name": "Long-term stock replacement",
"symbols": ["NVO"],
"role": "core_conviction",
"conviction": "high",
"horizon": "leaps",
"preferredStructures": ["long_call", "debit_spread"],
"targetRiskPercent": 5
}
],
"includeStocks": true,
"includeOptions": true
}Set discoverOptionCandidates=true to blend user-supplied option symbols or
theses with deterministic discovery from Saxo market movers. If the Saxo
account is not approved for short option legs, set allowShortOptionLegs=false;
short-leg structures are filtered from candidate generation.
Use saxo_review_strategy_positions after execution to monitor open stock and
option strategies against the plan you opened. Pass the executed stock leg or
option legs plus entry metrics from the selected plan or fill. The tool matches
those legs to open Saxo positions, refreshes quotes, estimates current value
and P/L, and evaluates deterministic profit-taking/loss rules. Option
strategies additionally include Greeks, theta, DTE, roll, and close rules. It
returns verdicts such as hold, review, consider_trim, consider_close,
and roll_watch; execution remains a separate explicit order workflow.
Tools
All tools are registered with MCP annotations (readOnlyHint,
destructiveHint, idempotentHint, openWorldHint) so clients can reason
about safety. Read-only tools work on SIM and LIVE without extra opt-in. Write
tools (orders, OAuth) follow the LIVE Trading Safety
rules.
Read-only
Tool | Endpoint | Purpose |
| — | Discover tools without calling Saxo. |
|
| Authenticated user (Name, ClientKey, UserKey, MarketDataViaOpenApiTermsAccepted). |
|
| Current AuthenticationLevel, DataLevel, and TradeLevel without running diagnostics. |
| (aggregated) | Session + capabilities + token expiry + warnings (market-data terms, TradeLevel, token close to expiry). |
|
| Inspect Saxo feature flags for News, Calendar, Gainers/Losers, and Chart. |
|
| Search by keyword + asset type. |
|
| Detailed metadata for one or many Uics. |
|
| List exchanges (or one by ExchangeId). |
|
| Strikes + expirations. |
| (uses option chain) | Cheap helper: just the expiries (date, days, strike count) for an option root. |
|
| Standardized option-expiry calendar (3rd Friday monthlies, quarterlies, weeklies). Distinct from |
| (composes search + chain) | Convenience helper: given symbol + expiry + strike + Call/Put, returns the leg Uic in one call instead of 4. Picks multi-leg-capable root when ambiguous. |
|
| Snapshot bid/ask/last for one instrument. Adds |
|
| Snapshot prices for multiple Uics. |
|
| Historical OHLC bars (horizon in minutes). |
| Saxo instruments + info prices | User-friendly top gainers/losers and pre-market screeners. |
| (uses infoprices) | Fetch bid/ask per leg and compute worst-case, mid, best-case net debit for a multi-leg spread. |
| (pure math) | Given side + strikes + debit + contracts: max loss, max gain, breakeven, R/R. Applies 100x option multiplier. |
| Option chain + prices | Explicit option strategy candidates with factor scores. |
| Market screener + chart TA + OptionsChain IV + planner | Cross-symbol option factor screening. |
| Saxo instruments + prices + chart TA + optional fundamentals/news | Stock factor screening without verdicts. |
| Account snapshot + stock/options factor tools | Whole-account factor, budget, and constraint context. |
| Positions + quotes (+ Greeks for options) | Post-execution strategy follow-up with hold/trim/close/roll verdicts. |
|
| List the client's trading accounts. |
|
| Cash + margin balance. |
|
| Open positions (one row per fill). |
|
| Positions aggregated per instrument (one row per Uic). Right view for current exposure. |
|
| Closed positions / history. |
|
| Recent account events: orders placed/modified/cancelled, trades, dividends, corporate actions. |
|
| Working orders. |
|
| One order by id. |
|
| List Saxo price alerts, optionally by state. |
|
| One price alert definition by id. |
|
| Price-alert email/popup/sound settings. |
Write — guarded
Tool | Endpoint | Guards |
|
| Policy + audit. No execution. |
|
| LIVE: |
|
| Same as place_order. |
|
| Policy + audit. |
|
| Validate a spread (no execution). |
|
| Place a spread atomically with a single net debit/credit limit. |
|
| Adjust spread Amount or OrderPrice. |
|
| Cancel the whole strategy. |
|
| LIVE alert writes require |
|
| Partial tool input is merged with the current definition before PUT. |
|
| Delete one or more price alerts. |
|
| Update email/popup/sound settings. |
|
| LIVE requires |
| OAuth2 PKCE | One-call local login; updates in-process tokens. Optional env-file persist. |
| OAuth2 PKCE | Loopback redirect only; reads app creds from env. |
| OAuth2 PKCE | Replaces in-process tokens. Optional |
| — | Closes a pending OAuth listener. |
Place / modify order body
Order tools accept Saxo's standard POST /trade/v2/orders body. Required
fields: AccountKey, Uic, AssetType, BuySell, Amount, OrderType,
OrderDuration. Optional: OrderPrice (Limit/StopLimit), StopPrice
(Stop/StopLimit), ManualOrder, ExternalReference, and Orders[] for
related orders (OCO, IfDone, brackets).
{
"AccountKey": "your-account-key",
"Uic": 211,
"AssetType": "Stock",
"BuySell": "Buy",
"Amount": 1,
"OrderType": "Market",
"OrderDuration": { "DurationType": "DayOrder" }
}Multi-leg option order body
Multi-leg tools wrap Saxo's /trade/v2/orders/multileg family. OrderType
must be Limit. OrderPrice is always positive — the absolute limit
price you are willing to pay (debit spread) or receive (credit spread).
Saxo's API rejects negative OrderPrice with "Price cannot be
negative"; the debit/credit direction is implicit in each leg's
BuySell. Legs[] accepts 2–20 legs that all share the same option
root (same underlying + expiry).
{
"AccountKey": "your-account-key",
"OrderType": "Limit",
"OrderPrice": 1.08,
"OrderDuration": { "DurationType": "GoodTillCancel" },
"ManualOrder": true,
"ExternalReference": "bull-call-spread-1",
"Legs": [
{
"Uic": 14853018,
"AssetType": "StockOption",
"BuySell": "Buy",
"Amount": 150,
"ToOpenClose": "ToOpen"
},
{
"Uic": 14853056,
"AssetType": "StockOption",
"BuySell": "Sell",
"Amount": 150,
"ToOpenClose": "ToOpen"
}
]
}Saxo returns a MultiLegOrderId plus per-leg Orders[].OrderId values.
Use saxo_modify_multileg_order (Amount/OrderPrice only) or
saxo_cancel_multileg_order (cancels the whole strategy) afterwards. To
find the per-leg Uics, start with saxo_search_instruments for the
underlying, then saxo_get_option_chain to read off strikes and
expirations.
LIVE Trading Safety
LIVE writes are denied by default. To enable them you must do all three:
Set
SAXO_ENVIRONMENT=live.Set
SAXO_ENABLE_LIVE_TRADING=true.Point
SAXO_POLICY_PATHat apolicy.jsonthat sets"allow_live_writes": true.
Price-alert writes are separate from trading writes. To create, update, delete,
or change notification settings on LIVE you must set
SAXO_ENABLE_LIVE_ALERT_WRITES=true and set
"allow_live_alert_writes": true in policy. This does not enable order
placement.
A copy of policy.example.json is included. Supported fields:
Field | Effect |
| Master switch for all order writes on LIVE. |
| Master switch for price-alert create/update/delete/settings writes on LIVE. |
| Allows changing LIVE session TradeLevel ( |
| On LIVE, place/modify (single and multi-leg) run a Saxo precheck first and abort unless it returns |
| Set to |
| Whitelist of AssetTypes that may be ordered. |
| Whitelist of AccountKeys that may be traded. |
| Blocklist of Uics. |
| Per-AssetType caps; |
| Cap on |
saxo_modify_order / saxo_modify_multileg_order load the existing order
from Saxo and evaluate the policy against the real order merged with the
requested changes; a mismatching AccountKey/Uic/AssetType is refused.
Even on SIM, all write tools run through the policy check (it just defaults to permissive). Use the policy in SIM too if you want predictable limits.
Optional HTTP Server
The local stdio transport is the default for agent compatibility. A small Streamable HTTP entry point is also available:
PORT=3000 SAXO_ACCESS_TOKEN=... npm run dev:httpBy default the HTTP server binds to 127.0.0.1, limits request bodies to
10 MiB, allows browser CORS only from loopback origins, and does not require
an HTTP Bearer token. Override with MCP_HTTP_HOST, MCP_MAX_BODY_BYTES,
MCP_ALLOWED_ORIGINS, MCP_ALLOW_ANY_ORIGIN=true, and MCP_HTTP_TOKEN
(compared in constant time). The MCP endpoint is
POST http://127.0.0.1:3000/mcp. The saxo_oauth_* tools are not available
over HTTP.
Verification
npm run typecheck
npm test
npm run buildOptional live SIM smoke test (requires a 24-hour SIM token):
SAXO_ACCESS_TOKEN="your-sim-token" npm run smoke:liveReleasing
Tagged releases trigger
.github/workflows/release.yml. On
git push --tags vX.Y.Z:
Typecheck, test, build, validate the MCPB manifest.
Publish
@borgels/mcp-server-saxo@X.Y.Zto npm with--provenanceso the package carries a SLSA attestation linking it to the exact CI run + commit.Reinstall with
--omit=devand pack a small.mcpbbundle (~2.6 MB; full dev install yields ~17 MB).Create a GitHub Release with the matching CHANGELOG section as the body and the
.mcpbattached.
Pre-release tags (e.g. v0.1.1-rc.1) publish under the next npm
dist-tag and as a GitHub pre-release.
One-time setup before the first release
Create the npm org. At https://www.npmjs.com/org/create, create the
borgelsorganization (free, public packages).Bootstrap the first publish. Trusted Publishing (see below) can't be configured until the package exists on npm, so the very first publish needs a token. Either:
Local one-shot:
npm publish --access publicwith a one-time Automation Token logged in as a member of theborgelsorg. Run this once from a clean checkout of the tagged commit. Revoke the token immediately afterwards.CI bootstrap: add the token as the
NPM_TOKENrepo secret, pushv0.1.1-rc.1, let CI publish, then delete the secret.
Configure Trusted Publishing (one-time, per package): https://www.npmjs.com/package/@borgels/mcp-server-saxo/access → Trusted publishing → Add publisher. Enter
Organization:
BorgelsRepository:
mcp-server-saxoWorkflow filename:
release.ymlEnvironment: leave blank (or set to e.g.
productionif you want to gate releases behind a GitHub Environment approval).
Revoke the bootstrap token and delete the
NPM_TOKENrepo secret. From this point on, every release authenticates via GitHub Actions OIDC — no long-lived secrets anywhere.
The same three steps repeat once per sibling Borgels MCP server when they migrate to this template.
Rate Limits
Saxo applies per-service-group rate limits (typically ~120 requests per
minute per session per service group, and ~1 order per second). On 429 the
server preserves the retry-after header on the thrown SaxoHttpError so
callers can back off.
Security And Audit
All credentials (
SAXO_ACCESS_TOKEN,SAXO_REFRESH_TOKEN,SAXO_APP_KEY,SAXO_APP_SECRET) are read only from the MCP server environment.Credentials are never accepted as tool arguments.
Error formatting and audit records redact
Authorization: Bearer ...,access_token,refresh_token, andSAXO_APP_SECRETstyle material.The OAuth listener only binds to loopback. Non-loopback
SAXO_REDIRECT_URIis rejected at startup.If
SAXO_AUDIT_LOGis set, every tool call writes a JSONL line with timestamp, request id, tool name, environment, action (start/finish/error/policy_denied), SHA-256 hash of the input, status, and redacted error text. Raw inputs and tokens are not written.Reports of suspected vulnerabilities go privately to security@borgels.com. Do not include credentials or personal data in public GitHub issues.
API Sources
Saxo Developer Portal: https://www.developer.saxo/
Saxo OpenAPI Reference: https://www.developer.saxo/openapi/referencedocs
Saxo OpenAPI Learn: https://www.developer.saxo/openapi/learn
License
Apache-2.0. See LICENSE.
Available Tools
51 toolssaxo_analyze_portfolio_contextAnalyze Portfolio ContextBRead-onlyIdempotent
Read-only whole-account context analyzer. Combines account snapshot, stock factors, option factors, risk budgets, concentration context, and warnings without allocation or deployment recommendations.
Inputs: accountKey (required), objective, riskProfile, portfolioProfile, +40 more.
saxo_cancel_multileg_orderCancel Multi-Leg Option OrderADestructive
Cancel a working multi-leg order. Cancels the whole strategy — individual legs cannot be cancelled separately.
Inputs: multiLegOrderId (required), accountKey (required).
saxo_cancel_orderCancel OrderADestructive
Cancel one or more working orders. LIVE writes require SAXO_ENABLE_LIVE_TRADING=true.
Inputs: orderIds (required), accountKey (required).
saxo_capabilitiesSearch Saxo CapabilitiesARead-onlyIdempotent
Search the Saxo MCP server capabilities and examples. Use this first when deciding which Saxo tool to call.
Inputs: query, limit.
saxo_compute_spread_quoteCompute Spread QuoteARead-onlyIdempotent
Fetch live bid/ask for each leg of a multi-leg option strategy and compute the worst-case, best-case, and mid net debit. Result is positive when the strategy is a net debit (you pay), negative when it is a net credit (you receive). Surfaces NoAccess warnings per leg when market-data terms are missing.
Inputs: legs (required), accountKey.
saxo_create_price_alertCreate Price AlertBDestructive
Create a Saxo price alert definition. LIVE alert writes require SAXO_ENABLE_LIVE_ALERT_WRITES=true plus policy.allow_live_alert_writes=true.
Inputs: AccountId (required), Uic (required), AssetType (required), TargetValue (required), +7 more.
saxo_delete_price_alertsDelete Price AlertsADestructive
Delete one or more Saxo price alert definitions. LIVE alert writes require SAXO_ENABLE_LIVE_ALERT_WRITES=true plus policy.allow_live_alert_writes=true.
Inputs: alertDefinitionIds (required).
saxo_diagnosticsSaxo OpenAPI DiagnosticsARead-onlyIdempotent
Hit the Saxo diagnostics endpoint to verify connectivity.
Inputs: no named parameters.
saxo_estimate_vertical_spreadEstimate Vertical Spread RiskARead-onlyIdempotent
Pure math: given side (BullCall/BearCall/BullPut/BearPut), longStrike, shortStrike, debit (negative for credit spreads), and contracts, returns max loss, max gain, and breakeven in account currency, applying the option contract multiplier (100 for US equity options).
Inputs: side (required), longStrike (required), shortStrike (required), debit (required), +2 more.
saxo_feature_availabilityGet Saxo Feature AvailabilityARead-onlyIdempotent
Return Saxo feature flags for News, Calendar, Gainers/Losers, and Chart. Diagnostic only: availability flags do not guarantee that every feature has a public documented endpoint exposed by this MCP server.
Inputs: no named parameters.
saxo_find_option_legFind Option Leg by Symbol/Expiry/StrikeARead-onlyIdempotent
Convenience helper that resolves an option leg Uic from human-readable parameters (symbol + expiry + strike + Call/Put). Compresses the 4-step option-discovery workflow (search instrument → search option root → fetch chain → locate strike) into one call. Useful before saxo_place_order / saxo_place_multileg_order. When multiple option roots match (e.g. ADR vs. local listing), prefers the multi-leg-capable root and surfaces alternatives in warnings[]; pass exchangeId to disambiguate.
Inputs: symbol (required), expiry (required), strike (required), putCall (required), +1 more.
saxo_generate_option_strategy_candidatesGenerate Option Strategy CandidatesARead-onlyIdempotent
Read-only option candidate generator for explicit caller-provided strategies. Returns structures, legs, pricing, Greeks, and factor context; does not choose a playbook, call precheck, or place orders.
Inputs: strategies (required), keywords, optionRootId, accountKey, +17 more.
saxo_get_balanceGet Account BalanceCRead-onlyIdempotent
Fetch the cash + margin balance for an account.
Inputs: accountKey, clientKey.
saxo_get_chartGet Chart (Historical OHLC)BRead-onlyIdempotent
Fetch historical OHLC bars for an instrument. Horizon is in minutes (1, 5, 60, 1440 ...). Count defaults to Saxo default (max 1200).
Inputs: uic (required), assetType (required), horizon (required), count, +3 more.
saxo_get_infopriceGet Snapshot PriceARead-onlyIdempotent
Fetch a snapshot bid/ask/last price for a single instrument. Snapshot only — no subscription side effects.
Inputs: uic (required), assetType (required), accountKey, amount, +1 more.
saxo_get_infoprices_listGet Snapshot Prices (List)BRead-onlyIdempotent
Fetch snapshot prices for multiple Uics in one call.
Inputs: uics (required), assetType (required), accountKey, fieldGroups.
saxo_get_instrument_detailsGet Instrument DetailsBRead-onlyIdempotent
Fetch detailed metadata for one or more instruments by Uic + AssetType.
Inputs: uics (required), assetType (required), accountKey, fieldGroups.
saxo_get_option_chainGet Option ChainARead-onlyIdempotent
Fetch the option chain (strikes + expirations) for an option root. Use this after saxo_search_instruments with assetTypes=[StockOption] to find the Uic of each option leg before placing a multi-leg spread. Set normalize=true (default) to return one row per strike with callUic+putUic; normalize=false returns the raw Saxo OptionSpace shape.
Inputs: optionRootId (required), expiryDates, strikeCount, clientKey, +3 more.
saxo_get_orderGet OrderBRead-onlyIdempotent
Fetch a specific order by OrderId.
Inputs: orderId (required), clientKey, fieldGroups.
saxo_get_price_alertGet Price AlertARead-onlyIdempotent
Fetch one Saxo price alert definition by AlertDefinitionId.
Inputs: alertDefinitionId (required).
saxo_get_price_alert_user_settingsGet Price Alert Notification SettingsARead-onlyIdempotent
Read the current user price-alert notification settings (email, popup, sound).
Inputs: no named parameters.
saxo_get_session_capabilitiesGet Saxo Session CapabilitiesARead-onlyIdempotent
Return current Saxo session capabilities, including TradeLevel and DataLevel, without running diagnostics.
Inputs: no named parameters.
saxo_list_accountsList AccountsBRead-onlyIdempotent
List the authenticated client's trading accounts.
Inputs: clientKey, includeSubAccounts.
saxo_list_activitiesList Account ActivitiesARead-onlyIdempotent
Recent account events from /port/v1/activities — placed/modified/cancelled orders, trades, dividend payments, corporate actions. Pass fromDateTime/toDateTime (ISO 8601 with timezone) to scope; defaults to a recent window on Saxo side. Useful for "what happened on my account today?" reasoning.
Inputs: clientKey, accountKey, fromDateTime, toDateTime, +3 more.
saxo_list_closed_positionsList Closed PositionsCRead-onlyIdempotent
List closed positions / trade history.
Inputs: clientKey, accountKey, fromDate, toDate, +2 more.
saxo_list_exchangesList ExchangesBRead-onlyIdempotent
List Saxo-supported exchanges, or fetch one by ExchangeId.
Inputs: exchangeId, top, skip.
saxo_list_net_positionsList Net Positions (Aggregated)ARead-onlyIdempotent
List positions aggregated per instrument (one row per Uic with the net amount), rather than per individual fill. Right view for "what is my current exposure?" — no manual deduplication needed.
Inputs: clientKey, accountKey, fieldGroups, top, +1 more.
saxo_list_option_expiriesList Option ExpiriesBRead-onlyIdempotent
Cheap helper that returns just the available expiries for an option root: expiry date, days-to-expiry, last trade date, and strike count. Use to pick an expiry before pulling the full chain.
Inputs: optionRootId (required), clientKey, accountKey, trading.
saxo_list_ordersList OrdersBRead-onlyIdempotent
List working orders for the authenticated client or a specific account.
Inputs: clientKey, accountKey, status, fieldGroups, +2 more.
saxo_list_positionsList Open PositionsARead-onlyIdempotent
List open positions for the authenticated client or a specific account. Returns one row per position (multiple rows per instrument if filled at different prices). Use saxo_list_net_positions for the per-instrument aggregated view.
Inputs: clientKey, accountKey, fieldGroups, top, +1 more.
saxo_list_price_alertsList Price AlertsBRead-onlyIdempotent
List Saxo price alert definitions for the current user, optionally filtered by state.
Inputs: inlinecount, skip, top, state.
saxo_list_standard_option_expiriesList Standard Option Expiry DatesARead-onlyIdempotent
Return the standardized option-expiry calendar (3rd Friday monthlies, quarterlies, weeklies) from Saxo reference data. Useful for "is 2027-01-15 a standard monthly?" reasoning. For per-option-root expiries, use saxo_list_option_expiries instead.
Inputs: fromDate.
saxo_modify_multileg_orderModify Multi-Leg Option OrderADestructive
Modify a working multi-leg order. Only Amount (scaled symmetrically across legs) and OrderPrice can be changed.
Inputs: AccountKey (required), MultiLegOrderId (required), Amount, OrderPrice.
saxo_modify_orderModify OrderBDestructive
Modify a working order (amount, price, duration). Same LIVE guards as saxo_place_order.
Inputs: OrderId (required), AccountKey (required), Uic (required), AssetType (required), +5 more.
saxo_oauth_cancelCancel Saxo OAuth LoginADestructive
Cancel a pending OAuth login flow (closes the callback listener).
Inputs: ticketId (required).
saxo_oauth_completeComplete Saxo OAuth LoginADestructive
Wait for the Saxo callback, exchange the code for tokens, and update the running MCP server. Optionally writes tokens to a .env file.
Inputs: ticketId (required), timeoutSeconds, writeToEnvFile, envFilePath.
saxo_oauth_loginRun Saxo OAuth LoginADestructive
Run the full Saxo OAuth2 + PKCE login in one MCP call. Starts a loopback callback listener, optionally opens the browser, waits for approval, exchanges tokens, updates the running MCP server, and optionally persists tokens to an env file.
Inputs: environment, timeoutSeconds, openBrowser, writeToEnvFile, +1 more.
saxo_oauth_startStart Saxo OAuth LoginADestructive
Begin a Saxo OAuth2 + PKCE login. Requires SAXO_APP_KEY + SAXO_APP_SECRET in the MCP server environment. Returns a ticketId and an authorizeUrl, optionally opening it in the browser. Then call saxo_oauth_complete with the ticketId.
Inputs: environment, openBrowser.
saxo_place_multileg_orderPlace Multi-Leg Option OrderADestructive
Place a multi-leg option strategy as one atomic order with a single limit price. OrderType must be Limit. OrderPrice is always positive — the absolute price you are willing to pay (debit) or receive (credit); Saxo infers direction from the legs. All legs must share the same option root (same underlying + expiry). Returns MultiLegOrderId plus per-leg OrderIds.
Inputs: AccountKey (required), OrderType (required), OrderDuration (required), Legs (required), +3 more.
saxo_place_orderPlace OrderADestructive
Place a new Saxo order. Defaults to SIM. LIVE writes require SAXO_ENABLE_LIVE_TRADING=true plus a policy.json that sets allow_live_writes=true. Policy may also cap Amount/AssetType/AccountKey/Uic/notional.
Inputs: AccountKey (required), Uic (required), AssetType (required), BuySell (required), +9 more.
saxo_precheck_multileg_orderPrecheck Multi-Leg Option OrderADestructive
Validate a multi-leg option strategy (vertical/calendar spread, condor, straddle, etc.) without placing it. OrderType must be Limit; OrderPrice is always positive — the absolute limit price you are willing to pay (debit spreads) or receive (credit spreads). Saxo infers debit vs credit from the Buy/Sell direction of the legs and rejects negative OrderPrice with "Price cannot be negative." All legs must share the same option root.
Inputs: AccountKey (required), OrderType (required), OrderDuration (required), Legs (required), +3 more.
saxo_precheck_orderPrecheck OrderCDestructive
Validate an order against Saxo (margin, prices, instrument rules) without placing it. Runs through the policy + audit even though no execution happens.
Inputs: AccountKey (required), Uic (required), AssetType (required), BuySell (required), +9 more.
saxo_review_strategy_positionsReview Strategy PositionsARead-onlyIdempotent
Read-only follow-up review for executed stock and option strategies. Matches expected legs to open positions, refreshes quotes, adds Greeks/DTE for options, evaluates P/L, trim/close/roll rules, and returns deterministic decision support. Does not precheck or place orders.
Inputs: accountKey, clientKey, strategySnapshotPath, reviewDepth, +12 more.
saxo_screen_marketScreen MarketARead-onlyIdempotent
User-friendly read-only market screener for presets like top gainers, top losers, pre-market gainers, and pre-market losers. Uses Saxo instruments and InfoPrices only; output depends on market-data permissions and delay settings.
Inputs: preset (required), market, exchangeIds, assetType, +4 more.
saxo_screen_option_strategy_factorsScreen Option Strategy FactorsARead-onlyIdempotent
Read-only factor screener for explicit option strategies across symbols or Saxo market movers. Returns candidate structures, liquidity, chart, IV/Greeks, optional news, and sizing context without verdicts or confidence labels.
Inputs: strategies (required), accountKey, market, symbols, +30 more.
saxo_screen_stock_factorsScreen Stock FactorsARead-onlyIdempotent
Read-only stock factor screener with Saxo quotes, chart context, optional account sizing, and optional Alpha Vantage fundamentals/news. Returns factors and warnings without verdicts or confidence labels.
Inputs: accountKey, market, symbols, excludeSymbols, +21 more.
saxo_search_instrumentsSearch InstrumentsARead-onlyIdempotent
Search Saxo reference data for instruments by keyword and asset type. Returns matching instruments with Uic and AssetType (use those as input to other tools).
Inputs: keywords, assetTypes, exchangeIds, accountKey, +3 more.
saxo_session_meGet Saxo SessionARead-onlyIdempotent
Return the current Saxo session (ClientKey, UserKey, default account, culture). Useful to verify the access token works.
Inputs: no named parameters.
saxo_set_session_trade_levelSet Saxo Session TradeLevelADestructive
Set session TradeLevel to FullTradingAndChat or OrdersOnly and return the confirmed session capabilities. LIVE requires policy.allow_live_session_capability_writes=true.
Inputs: tradeLevel (required), confirmTimeoutMs.
saxo_update_price_alertUpdate Price AlertADestructive
Update an existing Saxo price alert definition. Partial input is merged with the current alert before PUT because Saxo expects the full definition body.
Inputs: AlertDefinitionId (required), AccountId, Uic, AssetType, +8 more.
saxo_update_price_alert_user_settingsUpdate Price Alert Notification SettingsBDestructive
Update the current user price-alert notification settings. Partial input is merged with current settings before PUT.
Inputs: EmailAddress, NotifyWithMail, NotifyWithPopup, Sound.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 51 tools
Each tool targets a distinct function or resource type. Tools like list_positions vs list_net_positions, place_order vs place_multileg_order, and the various option-related tools are clearly differentiated by their descriptions, leaving no ambiguity.
All tools follow a consistent 'saxo_verb_noun' pattern in lowercase snake_case. Verbs are chosen appropriately and nouns specify the target, making the naming predictable and easy to navigate.
With 51 tools, the count exceeds 25, which per the rubric is considered too many for the apparent scope. While the server covers a complex trading domain, the tool surface feels bloated and could benefit from consolidation.
The tool set covers the full lifecycle of trading interactions: account management, order placement (single and multi-leg), prechecks, positions, market screening, alerts, OAuth, session management, and diagnostics. No obvious gaps are apparent for the stated purpose.
Maintenance
Related MCP Connectors
Agentic brokerage access to a US brokerage account: quotes, orders, positions, cash and documents.
Connect brokerage accounts to AI assistants: holdings, orders, asset lookup, and approved trading.
OpenFinance gives any AI agent easy trading across every major market through one hosted MCP server. Trade crypto spot and perpetuals on Hyperliquid with market, limit and TWAP orders, leverage and margin control. Trade stocks, indices, commodities, forex and pre-IPO names as Hyperliquid HIP-3 perps. Trade Polymarket prediction markets with odds, order books, positions, PnL and trader leaderboards. Swap and bridge across 40+ EVM chains and Solana with Relay and Jupiter, find memecoins across 200+ chains, launch tokens on Robinhood Chain and Solana, and buy crypto with card, bank or UPI. Write actions are designed to ask for your confirmation first. Connect with OAuth (sign in to OpenFinance and approve) or an API key. Works with Claude, ChatGPT, Cursor, VS Code, Codex and any MCP client. No install. Home: https://openfinance.tech/agents · Docs: https://docs.openfinance.tech · GitHub: https://github.com/openfinance-tech/mcp
Personal finance, bank account, and shared memory connector for Claude, ChatGPT, Gemini Spark & more
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables trading and portfolio management through the Alpaca API, allowing users to place orders, manage positions and watchlists, access market data, and retrieve account information through natural language.22 npm4ISC
- AlicenseNot gradedqualityDmaintenanceProvides AI models with secure access to Interactive Brokers trading data and functionality, enabling account management, market data retrieval, and trading operations through natural language interactions.18MIT
- AlicenseNot gradedqualityFmaintenanceEnables natural language trading operations through Alpaca's API, supporting stocks, options, crypto, portfolio management, and real-time market data with comprehensive order execution and account management capabilities.MIT
- AlicenseAqualityDmaintenanceEnables AI assistants to interact with Trading 212 investment accounts for portfolio tracking, account management, and real-time order execution. It supports managing investment pies, analyzing historical data, and monitoring market performance across multiple instrument types.232MIT