saxo-mcp
This server lets an AI assistant read a Saxo Bank portfolio and market data via MCP, and optionally trade only if explicitly enabled.
View account summary, balances, open positions, and open orders
Fetch account performance history and transactions/trades/bookings/order activities
Search instruments and get contract details, current quotes, and historical OHLC bars
Optionally precheck, place, modify, and cancel orders when
SAXO_TRADING=enabledAccess all tools over stdio or a secured HTTP endpoint
Defaults to read-only; trading tools are hard-blocked unless explicitly enabled
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., "@saxo-mcpwhat's my current portfolio 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.
saxo-mcp
A minimal Model Context Protocol server for the Saxo Bank OpenAPI. It lets an AI assistant answer questions about your portfolio (accounts, balances, positions, open orders, history) and look up market data (instrument search, contract details, quotes, historical bars).
Read-only by default. Trading is hard-blocked unless you set SAXO_TRADING=enabled. While
blocked, the order tools do not exist and the HTTP client refuses to send any write request (see
Trading and the hard block).
Contents
Related MCP server: rgo-trading-api
Requirements
Node.js 20 or newer (tested on 22) on Linux, macOS or Windows. ARM64 is fine.
A Saxo developer account and a registered app at https://www.developer.saxo/openapi/appmanagement:
Grant type: Authorization Code Grant (PKCE).
Redirect URL:
http://localhost:8765/callback(any localhost port works; keep.envin sync).Trading permission: disabled unless you intend to use the trading tools (see below).
Environment: Simulation while developing.
Setup
git clone <this repo> && cd saxo-mcp
npm install
cp .env.example .env
chmod 600 .envEdit .env:
Variable | Meaning |
| The AppKey shown for your app in the Saxo developer portal. |
|
|
| Must match the redirect URL registered on the app. Must be |
| Optional. Where tokens are stored. Default |
|
|
| HTTP entry point only. Bearer token clients must send. |
| HTTP entry point only. Loopback port, default |
.env and the token file are listed in .gitignore.
Logging in (OAuth PKCE)
The MCP server runs headless over stdio, so login is a separate one-off command:
npm run loginIt prints a Saxo login URL (and tries to open it), starts a temporary HTTP server on the redirect port, waits for Saxo to send the browser back with an authorization code, exchanges the code for tokens, and writes them to the token file. Tokens are never printed.
Check it worked:
npm run whoamiLogging in on a headless VPS
The callback goes to localhost on the machine running npm run login. On a VPS with no
browser, forward the port from your laptop and use your laptop's browser:
# on your laptop
ssh -L 8765:localhost:8765 user@your-vps
# in that SSH session, on the VPS
cd saxo-mcp && SAXO_NO_BROWSER=1 npm run loginCopy the printed URL into your laptop browser. After login, Saxo redirects to
http://localhost:8765/callback, which the tunnel delivers to the VPS.
What PKCE is and why it is used
OAuth's authorization-code flow sends the browser to Saxo to log in, and Saxo sends it back to
your app with a short-lived code. The app then swaps that code for tokens. In the classic flow
that swap is protected by a client secret. A CLI or desktop app cannot keep a secret (it lives in
a file on disk), so PKCE (RFC 7636) replaces it with a per-login secret that only exists in
memory:
Generate a random
code_verifier.Put
SHA-256(code_verifier)(base64url) in the login URL ascode_challenge.When exchanging the code, send the original
code_verifier. Saxo hashes it and checks it matches the challenge it saw in step 2.
An attacker who steals the code from the redirect cannot use it because they never saw the
verifier. A random state value is also sent and checked on the way back so a forged redirect
cannot be injected into your login attempt.
Saxo specifics worth knowing:
Only the app key (
client_id) and the verifier are sent to the token endpoint. No secret.Saxo also requires the
code_verifieron refresh requests, so it is stored next to the refresh token.Every refresh returns a new refresh token and invalidates the old one. The token file is rewritten atomically on each refresh.
Access tokens are short-lived (about 20 minutes); refresh tokens live longer (about an hour). The HTTP server keeps itself logged in with a 15-minute keep-alive. The stdio server only refreshes when a tool is called, so after a long idle period its refresh token expires and tools return
AUTHENTICATION REQUIRED ... run npm run login.
Running the MCP server
npm run build
node dist/index.js # speaks MCP over stdin/stdout; logs go to stderrExample client configuration (Claude Desktop / Claude Code style):
{
"mcpServers": {
"saxo": {
"command": "node",
"args": ["/absolute/path/to/saxo-mcp/dist/index.js"],
"cwd": "/absolute/path/to/saxo-mcp"
}
}
}The server reads .env from its working directory, so set cwd (or export the variables in the
client's env block).
Remote access over HTTPS
A second entry point, dist/http-server.js, runs the same tools over the MCP Streamable HTTP
transport. The stdio entry point is unchanged and both can run side by side.
Listens on
127.0.0.1:HTTP_PORT(default 3000) only. It is never internet-facing; Caddy terminates TLS in front of it.Every request must present
MCP_ACCESS_TOKEN, either asAuthorization: Bearer <token>(primary) or as?token=<token>in the URL for MCP clients whose connector UI cannot set headers (for example Claude's custom connectors). Either one being valid is enough; otherwise the request gets a 401 before any MCP or Saxo code runs. Both paths use the same constant-time comparison, the token is never logged, and the query parameter is stripped from the URL right after the check. Over HTTPS the query string is encrypted like the rest of the request.Endpoints:
POST/GET/DELETE /mcp(the MCP session) andGET /healthz(unauthenticated liveness probe that returnsok).Sessions: each MCP client gets its own session keyed by
Mcp-Session-Id; all sessions share one token manager, so refreshes never race. Idle sessions are closed after 30 minutes.Requests whose
Hostheader is notlocalhost/127.0.0.1are rejected (DNS-rebinding hardening). The provided Caddyfile forwards the upstream host, so nothing else is required.Keep-alive: once at startup and every 15 minutes it makes one lightweight authenticated call (
GET /port/v1/users/me) purely so the token manager refreshes before expiry. Saxo's refresh token lapses after about an hour without use, so this is what survives an idle night. Success logs one line; failure logs a warning namingnpm run loginand never crashes the process.Hot reload: the token file's directory is watched. Running
npm run loginwhile the server is up is picked up within a second, no restart needed. The stdio entry point has neither feature; it is not the process running unattended.
Setup, Caddyfile, pm2 and firewall steps are in deploy/README.md, with the
Caddyfile itself at deploy/Caddyfile.
npm run build
MCP_ACCESS_TOKEN=$(openssl rand -hex 32) HTTP_PORT=3000 npm run start:http # or via pm2, see deploy/Tools
All tools are annotated readOnlyHint: true and return JSON.
Portfolio (only need your account context):
Tool | What it returns | Saxo endpoint(s) |
| User, client entity and the list of accounts with their |
|
| Cash, total value, margin available/used, unrealised P&L, position and order counts. |
|
| Open positions with open/current price, exposure, P&L. |
|
| Lists open/working orders. Viewing only. |
|
| Performance summary or day-by-day time series for a |
|
|
|
|
Market data (need a UIC from search_instruments):
Tool | What it returns | Saxo endpoint(s) |
| Instruments matching a name/ticker/ISIN with |
|
| Contract specs, tick size, lot sizes, exchange and trading schedule. |
|
| Bid, ask, mid, last traded, day high/low, change, market open flag. |
|
| OHLC bars; |
|
Optional parameters shared by the portfolio tools: accountKey (defaults to the client's default
account) and wholeClient: true (aggregate over all accounts).
Trading (only registered when SAXO_TRADING=enabled):
Tool | What it does | Saxo endpoint |
| Dry run: validation, estimated costs and margin impact. Places nothing. |
|
| Places a Market/Limit/Stop/StopLimit/TrailingStop order. Requires |
|
| Replaces amount/price/type/duration of an open order. Requires |
|
| Cancels an open order by |
|
get_account_summary reports whether trading is enabled so the assistant can tell the user.
Trading and the hard block
Trading is off unless .env contains exactly SAXO_TRADING=enabled. The block is enforced in
three independent places, so no single mistake can open it:
Config. Only the literal value
enabledsetstradingEnabled.1,true,on,yesand typos are rejected at startup with a configuration error.Tool registration. With trading disabled,
precheck_order,place_order,modify_orderandcancel_orderare never registered. An MCP client cannot see or call them.HTTP client.
SaxoClient.post/patch/deletere-check the switch on every call and throwTradingDisabledErrorbefore any network I/O. Even when enabled, writes are limited to/trade/v2/orders,/trade/v2/orders/precheckand/trade/v2/orders/{OrderId}. Nothing else in the API can be written.
To enable: set SAXO_TRADING=enabled, make sure the Saxo app itself has trading permission, and
restart the server. It logs a warning on stderr at startup. To disable again: set it back to
disabled (or remove the line) and restart. Restart is required; the switch is read once.
Safety features when enabled:
place_order,modify_orderandcancel_orderrequireconfirm: true, and their descriptions instruct the assistant to obtain explicit user confirmation of every parameter first.precheck_ordergives costs and margin impact without placing anything.Every write sends a fresh
X-Request-ID, which Saxo uses to de-duplicate accidental resubmits.Orders are sent with
ManualOrder: true, meaning a human made the decision.Combine with
SAXO_ENV=simwhile testing. Trading on LIVE requires bothSAXO_ALLOW_LIVE=1andSAXO_TRADING=enabled.
How the pieces fit together
src/
index.ts stdio entry point (unchanged by the HTTP work)
http-server.ts Streamable HTTP entry point: loopback listener, sessions, bearer auth
http/auth.ts constant-time bearer token check
server.ts builds the McpServer and registers the tools; createDeps() is shared
config.ts .env loading, SIM/LIVE endpoints, LIVE guard
login.ts `npm run login` (interactive PKCE flow)
whoami.ts `npm run whoami` (auth sanity check)
auth/
pkce.ts verifier / challenge / state generation
oauth.ts authorize URL, code exchange, refresh (form POSTs to /token)
callbackServer.ts loopback HTTP server that catches the redirect
tokenStore.ts 0600 JSON token file, atomic writes
tokenManager.ts hands out a valid access token, refreshes before expiry
saxo/
client.ts HTTP client: read-only path guard, 401 handling, trading hard block
portfolio.ts typed wrappers for the portfolio / history / report endpoints
marketdata.ts typed wrappers for reference data, quotes and charts
trading.ts order precheck / place / modify / cancel (gated)
tools/
shared.ts result/error formatting, common zod parameters
portfolio.ts the six portfolio tools
marketdata.ts the four market data tools
trading.ts the four trading tools (registered only when enabled)Request flow for a tool call: tool handler -> PortfolioApi/MarketDataApi -> SaxoClient.get
-> TokenManager.getAccessToken (refresh if within 60 s of expiry) -> fetch with
Authorization: Bearer. A 401 triggers one forced refresh and retry; a second 401, or a rejected
refresh, becomes an AUTHENTICATION REQUIRED tool error that tells the user to run npm run login.
Read-only guarantees
These apply to the ten portfolio and market data tools, and to the whole server while trading is disabled.
SaxoClient.gethas no method parameter. It can only sendGET.Every path is checked by
assertReadOnlyPathbefore a URL is built. Allowed:/port/,/ref/,/chart/,/hist/,/cs/v1/reports/,/cs/v1/audit/,/root/v1/sessions/, plus the exact path/trade/v1/infoprices(and/list). Everything else under/trade/and anysubscriptionspath is rejected withReadOnlyViolationError./trade/v1/infopricesis Saxo's informational quote endpoint. It is aGETthat returns bid/ask/last and cannot create, change or cancel anything. It is the only REST way to read a current quote, which is whyget_instrument_priceuses it. If you would rather not touch the/trade/service group at all, remove the two entries fromREAD_ONLY_EXACTinsrc/saxo/client.tsand the price tool will fail closed;get_chart_datawithhorizon: 1still gives the latest one-minute bar.If you never intend to trade, register the Saxo app with trading permission disabled so the token itself cannot trade either.
The test suite asserts that every request made by the read tools is a
GET, that no path other than/trade/v1/infopricesunder/trade/is requested by them, and that with trading disabled no write request leaves the process even when the client is called directly.
Security notes
.envand.saxo-tokens.jsonare gitignored. Keep themchmod 600.Tokens are never logged or printed. Error bodies from the token endpoint are redacted before they reach a message.
All logging goes to stderr; stdout is reserved for the MCP protocol.
The callback server binds to
127.0.0.1only and shuts down as soon as the login completes, fails or times out (5 minutes).SAXO_ENV=liveis refused unless you also setSAXO_ALLOW_LIVE=1. Do not do that until the server has been exercised against SIM.
Development
npm run dev # run the stdio server from TypeScript sources
npm run dev:http # run the HTTP server from TypeScript sources
npm run typecheck
npm test # unit tests + in-process MCP client tests against a fake Saxo
npm run buildThe tests do not contact Saxo. They cover the PKCE math (RFC 7636 test vector), token storage permissions, refresh handling, the read-only guard, the trading hard block in both states, and every tool end to end via an in-memory MCP transport.
Available Tools
10 toolsget_account_historyAccount performance historyARead-onlyIdempotent
Historical account performance. report=summary (default) returns key figures, returns, allocation and trade statistics for the period; report=timeseries returns day-by-day account value / balance / time-weighted return series. Choose a standardPeriod (Month, Quarter, Year, AllTime) or an explicit fromDate/toDate.
| Name | Required | Description | Default |
|---|---|---|---|
| report | No | ||
| toDate | No | Date in YYYY-MM-DD format | |
| fromDate | No | Date in YYYY-MM-DD format | |
| accountKey | No | Saxo AccountKey. Omit to use the client's default account (see get_account_summary for the list). | |
| wholeClient | No | If true, aggregate across ALL accounts of the client instead of one account. | |
| standardPeriod | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover readOnly, idempotent, and non-destructive behavior. The description adds useful behavioral context: it explains what each report mode returns, notes the default report is summary, and describes the timeseries contents. No contradictions with annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences with no filler: purpose first, then the two report modes, then period selection. Every sentence adds distinct necessary information and is front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description gives a reasonable high-level view of what each report returns. However, it doesn't specify behavior when neither standardPeriod nor a date range is provided, nor the interaction rules between those parameters. These are minor gaps for an otherwise clear tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is only 67%, with report and standardPeriod lacking schema-level descriptions. The description compensates by explaining the enum meanings and the choice between standardPeriod and explicit dates. It does not dwell on accountKey/wholeClient, but those already have schema descriptions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states 'Historical account performance' with explicit verb+resource, and distinguishes two report modes with distinct outputs. It differentiates from siblings like get_account_summary by focusing on historical data rather than current summary.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage context is implied by 'Historical' and the mention of period selection, but there are no explicit when-to-use or when-not-to-use statements, nor named alternatives. The description never excludes get_account_summary or get_balances, leaving the agent to infer when this tool is preferred.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_account_summaryAccount summaryARead-onlyIdempotent
Basic information about the logged-in Saxo user, the client entity and all its accounts (AccountKey, currency, type). Call this first: other tools take an optional AccountKey from here.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover read-only, idempotent, open-world, and non-destructive behavior. The description adds that the data is about the logged-in user and includes account fields, which is useful context, but it does not disclose additional behavioral details such as auth requirements or response shape beyond the listed fields.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, no filler. The purpose is front-loaded and the usage directive is placed immediately after, making the description easy to parse and act on.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a zero-parameter bootstrap tool, this is complete: it states what is returned, names the key field used by other tools, and explains when to call it. The annotations cover safety characteristics and there is no output schema requirement to compensate for.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has zero parameters, so the schema already covers everything. The description adds no parameter-specific detail, but none is needed; the baseline of 4 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly identifies the resource: basic information about the logged-in user, the client entity, and all accounts, including concrete fields like AccountKey, currency, and type. It is clear and distinct from balance/position/order tools, though it does not explicitly name sibling tools in the purpose statement.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly tells the agent to call this first and explains that other tools take an optional AccountKey from here. This is a strong, actionable usage directive that reduces ambiguity about sequencing and data flow.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_balancesBalancesARead-onlyIdempotent
Cash balance, total account value, margin available/used, unrealised P&L and open position/order counts for one account (default) or the whole client.
| Name | Required | Description | Default |
|---|---|---|---|
| accountKey | No | Saxo AccountKey. Omit to use the client's default account (see get_account_summary for the list). | |
| wholeClient | No | If true, aggregate across ALL accounts of the client instead of one account. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, and destructiveHint false, so the description's safety burden is low. The description adds useful scope behavior (default account vs whole client) and enumerates returned data, but it does not describe aggregation details, error conditions, or response shape. This is acceptable but not rich behavioral context.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single compact sentence that front-loads the main returned fields and then states the scoping option. Every part earns its place, with no redundancy or filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a two-parameter read-only tool, the description covers the returned categories and the account-scoping behavior, and it links to get_account_summary for account-key context. No output schema exists, but the field enumeration gives a workable picture of the response despite the absence of detailed return-structure documentation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%; both accountKey and wholeClient are already documented in the schema. The description's mention of 'default account' and 'whole client' largely paraphrases the schema without adding new semantic meaning. The baseline of 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly identifies the resource (balances) and enumerates the specific data returned: cash balance, account value, margin, unrealised P&L, and position/order counts. It also distinguishes between a single account and the whole client, making the tool's purpose recognizable even without an explicit comparison to sibling tools.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explains the main usage decision: one account by default or the whole client via wholeClient, and points to get_account_summary for account keys. It does not explicitly say when to avoid this tool in favor of siblings like get_positions or get_orders, but the clear scoping guidance covers most practical use.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_chart_dataHistorical OHLC barsARead-onlyIdempotent
Historical price bars (open/high/low/close, volume where available; FX returns bid/ask OHLC). horizon is the bar size in minutes: 1, 5, 10, 15, 30, 60, 120, 240, 360, 480, 1440, 10080, 43200 (1440 = daily, 10080 = weekly, 43200 = monthly). Up to 1200 bars per call; pass time + mode to page further back.
| Name | Required | Description | Default |
|---|---|---|---|
| uic | Yes | Saxo UIC (universal instrument code) from search_instruments. | |
| mode | No | Bars From or UpTo `time` (default UpTo). | |
| time | No | ISO-8601 timestamp anchor, e.g. 2024-01-31T00:00:00Z. | |
| count | No | Number of bars (default Saxo: 1200). | |
| horizon | Yes | ||
| assetType | Yes | Saxo AssetType exactly as returned by search_instruments, e.g. "Stock", "FxSpot", "Etf", "CfdOnStock", "ContractFutures". |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description adds significant behavioral context beyond the annotations: it notes volume is only available where applicable, FX returns bid/ask OHLC, the max bars per call (1200), and how to page further back. These details are not in the annotations and are crucial for correct invocation and interpretation.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is compact, using only two sentences. It front-loads the core purpose, then efficiently lists horizon values and paging behavior, with no wasted words or redundant information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description covers the main return content (OHLC, volume, FX specifics), the bar-size enumeration, and paging mechanics, which are sufficient for a read-only tool. It does not explicitly describe the exact response structure (e.g., array of bar objects), but that is reasonably implied and not critical given the lack of an output schema.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The description provides essential meaning for the horizon parameter, which is completely undocumented in the schema, by listing all valid minute values and their daily/weekly/monthly equivalents. It also clarifies paging behavior with time and mode, adding value beyond the schema's brief descriptions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly identifies the tool as returning historical price bars (OHLC, volume) and explains the horizon meanings, which is specific and unambiguous. However, it does not explicitly distinguish this tool from sibling get_instrument_price, though the title 'Historical OHLC bars' and the focus on historical data imply the difference.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides usage guidance for paging (pass time + mode) and explains horizon values, but it does not mention when to use this tool vs alternatives like get_instrument_price. There is no explicit 'use this for historical data, use that for current price' guidance, leaving the agent to infer the distinction from context.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_instrument_detailsInstrument detailsARead-onlyIdempotent
Contract specification for one instrument: description, currency, exchange, tick size / price decimals, lot and minimum trade size, trading sessions (trading hours) and other reference data.
| Name | Required | Description | Default |
|---|---|---|---|
| uic | Yes | Saxo UIC (universal instrument code) from search_instruments. | |
| assetType | Yes | Saxo AssetType exactly as returned by search_instruments, e.g. "Stock", "FxSpot", "Etf", "CfdOnStock", "ContractFutures". | |
| includeTradingSchedule | No | Also fetch the trading schedule (default true). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, and destructiveHint=false, covering the safety profile. The description adds useful context about the returned content, including trading sessions and trade size fields, but does not detail response behavior beyond that. No contradiction exists.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single front-loaded sentence. It begins with the core purpose and then enumerates the relevant specification fields without redundant wording.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read-only reference-data lookup, the description gives enough content detail to convey what the tool returns. It relies on the schema for parameter prerequisites and defaults, which is acceptable since schema coverage is complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already documents uic, assetType, and includeTradingSchedule. The description lists output content rather than parameter semantics, so it adds little beyond the schema; baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly identifies the resource (one instrument) and the kind of data returned (contract specification: currency, exchange, tick size, lot size, trading sessions, etc.). It is specific enough to be distinguishable from price or chart tools, though it does not explicitly name sibling alternatives.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The usage context is implied: after obtaining a uic and assetType, an agent would call this tool to fetch static reference data. However, the description does not explicitly state when to use this tool versus search_instruments, get_instrument_price, or other siblings.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_instrument_priceCurrent price quoteARead-onlyIdempotent
Current informational quote for an instrument: bid, ask, mid, last traded, day open/high/low, net and percent change, market open flag and whether the price is delayed. Read-only; never creates an order.
| Name | Required | Description | Default |
|---|---|---|---|
| uic | Yes | Saxo UIC (universal instrument code) from search_instruments. | |
| assetType | Yes | Saxo AssetType exactly as returned by search_instruments, e.g. "Stock", "FxSpot", "Etf", "CfdOnStock", "ContractFutures". |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, idempotentHint=true, and destructiveHint=false. The description adds value by listing the returned fields (bid, ask, mid, etc.) and explicitly stating 'Read-only; never creates an order,' reinforcing safety. It also mentions the delayed-price flag, adding context about data currency without contradicting annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single sentence that front-loads the purpose ('Current informational quote') and then lists the fields. It's efficient with no filler, though slightly long; still, every word earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple two-parameter tool with no output schema, the description adequately lists the returned data points, giving an agent a clear expectation of the response. It doesn't cover error handling or edge cases, but that's not essential for this straightforward quote tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema has 100% coverage, with both parameters (uic and assetType) fully described including examples. The tool description adds no additional parameter semantics, so the baseline of 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states it provides a current informational quote with specific fields (bid, ask, mid, etc.), distinguishing it from account/history siblings like get_positions or get_orders. The explicit 'never creates an order' further clarifies its non-action purpose.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies usage for obtaining current price data, and the context makes it clear this is for quotes versus historical data (get_chart_data) or instrument details (get_instrument_details). It doesn't name alternatives explicitly, but the purpose is unambiguous, so it meets the 'clear context, no exclusions' bar.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_ordersOpen orders (view only)ARead-onlyIdempotent
LISTS currently open/working orders (type, amount, price, status, duration). This is strictly a viewing tool: this server cannot place, modify or cancel orders.
| Name | Required | Description | Default |
|---|---|---|---|
| accountKey | No | Saxo AccountKey. Omit to use the client's default account (see get_account_summary for the list). | |
| wholeClient | No | If true, aggregate across ALL accounts of the client instead of one account. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, idempotentHint=true, and destructiveHint=false, so the safety profile is fully covered. The description adds the behavioral detail that the server cannot place, modify, or cancel orders, which reinforces the read-only nature. It does not disclose pagination, ordering, or whether the list is sorted, but with strong annotations the bar is lower and the description adds some context.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences with no filler. The core purpose and the read-only constraint are front-loaded, and every sentence adds value. The description is appropriately sized for a simple list tool.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read-only list tool with no required parameters and full schema coverage, the description is nearly complete. It covers the purpose, the fields returned, and the behavioral constraint. It does not describe the output format or pagination, but there is no output schema and the tool is simple; the missing details are minor for an agent deciding whether to call it.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so both parameters are already documented in the schema. The description does not add extra meaning about the parameters beyond what the schema provides. Baseline 3 is appropriate when the schema carries the full parameter documentation burden.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool lists currently open/working orders and enumerates the fields returned (type, amount, price, status, duration). It also explicitly distinguishes itself as a viewing tool, which differentiates it from any order-management siblings. The title 'Open orders (view only)' reinforces the purpose.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description says it is strictly a viewing tool and that the server cannot place, modify, or cancel orders, which implies it should not be used for order management. It does not explicitly name alternative tools for placing/modifying/canceling orders, but among the listed siblings there are no order-management tools, so the exclusion is clear enough. The accountKey parameter description references get_account_summary for the default account list, providing some cross-tool guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_positionsOpen positionsARead-onlyIdempotent
Open positions with open price, current price, exposure and profit/loss. view="individual" (default) lists every position; view="net" nets positions per instrument.
| Name | Required | Description | Default |
|---|---|---|---|
| view | No | individual (default) or net | |
| accountKey | No | Saxo AccountKey. Omit to use the client's default account (see get_account_summary for the list). | |
| wholeClient | No | If true, aggregate across ALL accounts of the client instead of one account. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover read-only, idempotent, and non-destructive behavior, so the bar for additional context is lower. The description adds meaningful behavior beyond those hints: it explains that view='individual' lists every position and view='net' nets positions per instrument, and it enumerates the response fields.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences with no filler: the first states the resource and key fields, the second explains the view parameter's behavior. Every word earns its place, and the main purpose is front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple read tool with rich annotations and fully documented parameters, the description covers the return fields and view semantics. It does not mention pagination or empty results, but those are not critical for a straightforward positions listing and the output schema is absent, so the description carries the return-value burden adequately.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3. The description adds genuine value by explaining the effect of the view enum values ('lists every position' vs 'nets positions per instrument'), which is not fully captured in the schema's one-word descriptions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the resource (open positions) and the returned fields (open price, current price, exposure, P/L), making the tool's function obvious. It does not explicitly name or contrast a sibling tool, but the resource is distinct enough from get_balances, get_orders, and get_transactions that an agent can tell them apart.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives no explicit guidance on when to use this tool versus alternatives, such as get_orders or get_balances. It only describes what the tool returns and the view parameter behavior, leaving the selection decision entirely to inference from the resource name.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_transactionsTransaction / trade historyARead-onlyIdempotent
Historical transaction log. type=trades (default): executed trades. type=bookings: cash bookings such as deposits, withdrawals, dividends, fees and settlements. type=order_activities: audit trail of order events (placed, filled, cancelled). Defaults to the last 30 days.
| Name | Required | Description | Default |
|---|---|---|---|
| top | No | Max rows to return (default 200). | |
| type | No | ||
| toDate | No | Date in YYYY-MM-DD format | |
| fromDate | No | Date in YYYY-MM-DD format | |
| accountKey | No | Saxo AccountKey. Omit to use the client's default account (see get_account_summary for the list). | |
| wholeClient | No | If true, aggregate across ALL accounts of the client instead of one account. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, idempotentHint=true, and destructiveHint=false, so the safety profile is covered. The description adds genuinely useful behavioral context beyond the annotations by explaining what each type contains and that the date range defaults to the last 30 days. No contradiction exists between the description and annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is three tight sentences that front-load the core concept ('Historical transaction log') and then efficiently enumerate the type variants and the default time window. Every sentence earns its place, and there is no redundant repetition of schema property names.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description carries responsibility for explaining what the tool returns, and it does a good job by describing the categories of records. It does not specify the response shape or ordering, and it leaves accountKey/wholeClient behavior to the schema, but for a read-only list tool the provided context is largely sufficient.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 83%, which sets a baseline of 3, but the description meaningfully enriches the 'type' parameter by spelling out the three enum values and giving concrete examples like deposits, withdrawals, dividends, fees, and settlements. It also clarifies the default date behavior, which the schema does not state.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly identifies the resource as a historical transaction log and enumerates the three distinct transaction types (trades, bookings, order_activities), which gives the agent a concrete sense of what this tool returns. However, it lacks an explicit verb like 'retrieves' or 'lists' and does not differentiate itself from the sibling get_account_history, so it stops short of a 5.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies when to use the tool by defining each type and stating that trades is the default, and it communicates the default 30-day window. It does not explicitly name alternatives or state when not to use this tool versus get_account_history or get_orders, so the guidance is serviceable but not explicit.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_instrumentsSearch instrumentsARead-onlyIdempotent
Look up instruments by name, ticker or ISIN and get their Saxo UIC + AssetType, which the other market data tools need. Returns Identifier (the UIC), Symbol, Description, AssetType, ExchangeId and CurrencyCode.
| Name | Required | Description | Default |
|---|---|---|---|
| top | No | Max results (default 20). | |
| keywords | Yes | Name, ticker or ISIN, e.g. "Apple", "AAPL", "EURUSD". | |
| assetTypes | No | Comma-separated AssetType filter, e.g. "Stock" or "Stock,Etf" or "FxSpot". Omit for all. | |
| exchangeId | No | Exchange filter, e.g. "NASDAQ", "NYSE", "XETR". | |
| includeNonTradable | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already establish this as read-only, idempotent, and non-destructive. The description adds useful context by listing returned fields and mentioning that other tools need the UIC + AssetType, but it does not disclose further behavioral traits such as match semantics, result limits beyond the schema's top parameter, or ordering behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two focused sentences with no filler: the purpose is front-loaded, the returned fields are listed clearly, and every sentence earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description is complete enough for selecting and invoking the tool: it explains what the tool returns and why it matters for other market-data tools. Minor gaps remain around includeNonTradable and explicit routing to sibling tools, but the annotations and schema carry much of the load.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 80%, so the schema already documents most parameters. The description adds no parameter-level detail beyond the schema and leaves the undocumented includeNonTradable parameter unaddressed, so it meets but does not exceed the baseline.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: 'Look up instruments by name, ticker or ISIN' and clarifies the key output (Saxo UIC + AssetType). It also distinguishes this tool from sibling market-data tools by explaining that those tools depend on these identifiers.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description clearly positions the tool as the lookup step that feeds other market data tools, giving an agent a strong signal for when to invoke it. It does not explicitly name alternative tools or state when not to use them, but the intended role is clear.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
10 tool updates
v0.1.0- First observed
get_account_history - First observed
get_account_summary - First observed
get_balances - First observed
get_chart_data - First observed
get_instrument_details - First observed
get_instrument_price - First observed
get_orders - First observed
get_positions - First observed
get_transactions - First observed
search_instruments
TDQS
Scored across 10 tools
Each tool maps to a distinct resource and action: account metadata, balances, positions, orders, history, transactions, instrument search, contract details, current quote, and historical bars. The two historical tools are clearly differentiated as performance reports versus transaction logs.
All tool names follow a consistent lowercase verb_noun snake_case pattern, with get_* for lookups and search_instruments for query-based discovery. The naming is predictable and easy for an agent to navigate.
Ten tools is well-scoped for a read-only Saxo data server, covering account, portfolio, transaction, and market data without unnecessary redundancy. Each tool earns its place in the set.
The surface covers the full read-only lifecycle: discover instruments, fetch contract details, get current and historical prices, and retrieve account, position, order, and transaction data. No critical dead ends exist, and the only omitted actions like order placement are explicitly out of scope.
Maintenance
Related MCP Connectors
Read-only access to Genie accounts, transactions, investments, and financial summaries.
- HAVNOAuthapp.havnre
Read-only AI access to HAVN properties, leads, tasks, files, media, and analytics.
Trade journal plus read-only market, research, and brokerage data tools for external AI clients.
- OsboonOAuthcom.osboon
Read-only AI access to Osboon business card analytics, viewers, links, connections and contacts.
Related MCP Servers
- AlicenseAqualityDmaintenanceEnables AI assistants to interact with OKX trading accounts through read-only access to retrieve portfolio information, trading positions, order history, and account analytics. Provides secure, local processing of trading data without storing sensitive information or enabling trade execution.57 npm4MIT
- AlicenseNot gradedqualityDmaintenanceEnables AI assistants to read positions, deals, and orders from the RGO trading platform via natural language, with optional trading capabilities when explicitly enabled.MIT
- AlicenseNot gradedqualityCmaintenanceEnables AI assistants read-only access to Sprinklr data via MCP, allowing querying reports, searching cases, and calling Sprinklr API endpoints.7 npmISC
- AlicenseBqualityDmaintenanceGives AI assistants real-time access to Interactive Brokers accounts via the Client Portal Web API, with 26 read-only tools for portfolio, market data, options, and scanner.327 npm2MIT