Skip to main content
Glama
SirAdlerBD

saxo-mcp

by SirAdlerBD

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 .env in 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 .env

Edit .env:

Variable

Meaning

SAXO_APP_KEY

The AppKey shown for your app in the Saxo developer portal.

SAXO_ENV

sim (default). live is refused unless SAXO_ALLOW_LIVE=1 is also set.

SAXO_REDIRECT_URI

Must match the redirect URL registered on the app. Must be http://localhost:<port>/....

SAXO_TOKEN_FILE

Optional. Where tokens are stored. Default ./.saxo-tokens.json, created with mode 0600.

SAXO_TRADING

disabled (default) or enabled. Anything else is rejected at startup.

MCP_ACCESS_TOKEN

HTTP entry point only. Bearer token clients must send. openssl rand -hex 32. Min 32 chars.

HTTP_PORT

HTTP entry point only. Loopback port, default 3000.

.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 login

It 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 whoami

Logging 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 login

Copy 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:

  1. Generate a random code_verifier.

  2. Put SHA-256(code_verifier) (base64url) in the login URL as code_challenge.

  3. 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_verifier on 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 stderr

Example 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 as Authorization: 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) and GET /healthz (unauthenticated liveness probe that returns ok).

  • 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 Host header is not localhost/127.0.0.1 are 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 naming npm run login and never crashes the process.

  • Hot reload: the token file's directory is watched. Running npm run login while 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)

get_account_summary

User, client entity and the list of accounts with their AccountKeys. Call this first.

/port/v1/users/me, /port/v1/clients/me, /port/v1/accounts/me

get_balances

Cash, total value, margin available/used, unrealised P&L, position and order counts.

/port/v1/balances

get_positions

Open positions with open/current price, exposure, P&L. view: "net" nets per instrument.

/port/v1/positions, /port/v1/netpositions

get_orders

Lists open/working orders. Viewing only.

/port/v1/orders

get_account_history

Performance summary or day-by-day time series for a standardPeriod or date range.

/hist/v4/performance/summary, /hist/v4/performance/timeseries

get_transactions

trades (executed trades), bookings (cash movements) or order_activities (order audit log).

/cs/v1/reports/trades/{ClientKey}, /cs/v1/reports/bookings/{ClientKey}, /cs/v1/audit/orderactivities

Market data (need a UIC from search_instruments):

Tool

What it returns

Saxo endpoint(s)

search_instruments

Instruments matching a name/ticker/ISIN with Identifier (UIC) and AssetType.

/ref/v1/instruments

get_instrument_details

Contract specs, tick size, lot sizes, exchange and trading schedule.

/ref/v1/instruments/details/{Uic}/{AssetType}, /ref/v1/instruments/tradingschedule/{Uic}/{AssetType}

get_instrument_price

Bid, ask, mid, last traded, day high/low, change, market open flag.

GET /trade/v1/infoprices (informational quote, see below)

get_chart_data

OHLC bars; horizon in minutes (1 ... 43200), up to 1200 bars, pageable by time.

/chart/v3/charts

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

precheck_order

Dry run: validation, estimated costs and margin impact. Places nothing.

POST /trade/v2/orders/precheck

place_order

Places a Market/Limit/Stop/StopLimit/TrailingStop order. Requires confirm: true.

POST /trade/v2/orders

modify_order

Replaces amount/price/type/duration of an open order. Requires confirm: true.

PATCH /trade/v2/orders

cancel_order

Cancels an open order by OrderId. Requires confirm: true.

DELETE /trade/v2/orders/{OrderId}

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:

  1. Config. Only the literal value enabled sets tradingEnabled. 1, true, on, yes and typos are rejected at startup with a configuration error.

  2. Tool registration. With trading disabled, precheck_order, place_order, modify_order and cancel_order are never registered. An MCP client cannot see or call them.

  3. HTTP client. SaxoClient.post/patch/delete re-check the switch on every call and throw TradingDisabledError before any network I/O. Even when enabled, writes are limited to /trade/v2/orders, /trade/v2/orders/precheck and /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_order and cancel_order require confirm: true, and their descriptions instruct the assistant to obtain explicit user confirmation of every parameter first.

  • precheck_order gives 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=sim while testing. Trading on LIVE requires both SAXO_ALLOW_LIVE=1 and SAXO_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.get has no method parameter. It can only send GET.

  • Every path is checked by assertReadOnlyPath before 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 any subscriptions path is rejected with ReadOnlyViolationError.

  • /trade/v1/infoprices is Saxo's informational quote endpoint. It is a GET that returns bid/ask/last and cannot create, change or cancel anything. It is the only REST way to read a current quote, which is why get_instrument_price uses it. If you would rather not touch the /trade/ service group at all, remove the two entries from READ_ONLY_EXACT in src/saxo/client.ts and the price tool will fail closed; get_chart_data with horizon: 1 still 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/infoprices under /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

  • .env and .saxo-tokens.json are gitignored. Keep them chmod 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.1 only and shuts down as soon as the login completes, fails or times out (5 minutes).

  • SAXO_ENV=live is refused unless you also set SAXO_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 build

The 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 tools
get_account_historyAccount performance historyA
Read-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.

ParametersJSON Schema
NameRequiredDescriptionDefault
reportNo
toDateNoDate in YYYY-MM-DD format
fromDateNoDate in YYYY-MM-DD format
accountKeyNoSaxo AccountKey. Omit to use the client's default account (see get_account_summary for the list).
wholeClientNoIf true, aggregate across ALL accounts of the client instead of one account.
standardPeriodNo

TDQS

A4.2/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines3/5

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 summaryA
Read-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.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.2/5.0
Behavior3/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose4/5

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.

Usage Guidelines5/5

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_balancesBalancesA
Read-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.

ParametersJSON Schema
NameRequiredDescriptionDefault
accountKeyNoSaxo AccountKey. Omit to use the client's default account (see get_account_summary for the list).
wholeClientNoIf true, aggregate across ALL accounts of the client instead of one account.

TDQS

A3.8/5.0
Behavior3/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines4/5

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 barsA
Read-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.

ParametersJSON Schema
NameRequiredDescriptionDefault
uicYesSaxo UIC (universal instrument code) from search_instruments.
modeNoBars From or UpTo `time` (default UpTo).
timeNoISO-8601 timestamp anchor, e.g. 2024-01-31T00:00:00Z.
countNoNumber of bars (default Saxo: 1200).
horizonYes
assetTypeYesSaxo AssetType exactly as returned by search_instruments, e.g. "Stock", "FxSpot", "Etf", "CfdOnStock", "ContractFutures".

TDQS

A4.1/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters5/5

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.

Purpose4/5

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.

Usage Guidelines2/5

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 detailsA
Read-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.

ParametersJSON Schema
NameRequiredDescriptionDefault
uicYesSaxo UIC (universal instrument code) from search_instruments.
assetTypeYesSaxo AssetType exactly as returned by search_instruments, e.g. "Stock", "FxSpot", "Etf", "CfdOnStock", "ContractFutures".
includeTradingScheduleNoAlso fetch the trading schedule (default true).

TDQS

A3.6/5.0
Behavior3/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines3/5

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 quoteA
Read-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.

ParametersJSON Schema
NameRequiredDescriptionDefault
uicYesSaxo UIC (universal instrument code) from search_instruments.
assetTypeYesSaxo AssetType exactly as returned by search_instruments, e.g. "Stock", "FxSpot", "Etf", "CfdOnStock", "ContractFutures".

TDQS

A4.1/5.0
Behavior4/5

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.

Conciseness4/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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)A
Read-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.

ParametersJSON Schema
NameRequiredDescriptionDefault
accountKeyNoSaxo AccountKey. Omit to use the client's default account (see get_account_summary for the list).
wholeClientNoIf true, aggregate across ALL accounts of the client instead of one account.

TDQS

A4/5.0
Behavior3/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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 positionsA
Read-onlyIdempotent

Open positions with open price, current price, exposure and profit/loss. view="individual" (default) lists every position; view="net" nets positions per instrument.

ParametersJSON Schema
NameRequiredDescriptionDefault
viewNoindividual (default) or net
accountKeyNoSaxo AccountKey. Omit to use the client's default account (see get_account_summary for the list).
wholeClientNoIf true, aggregate across ALL accounts of the client instead of one account.

TDQS

A3.7/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose4/5

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.

Usage Guidelines2/5

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 historyA
Read-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.

ParametersJSON Schema
NameRequiredDescriptionDefault
topNoMax rows to return (default 200).
typeNo
toDateNoDate in YYYY-MM-DD format
fromDateNoDate in YYYY-MM-DD format
accountKeyNoSaxo AccountKey. Omit to use the client's default account (see get_account_summary for the list).
wholeClientNoIf true, aggregate across ALL accounts of the client instead of one account.

TDQS

A3.9/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose4/5

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.

Usage Guidelines3/5

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 instrumentsA
Read-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.

ParametersJSON Schema
NameRequiredDescriptionDefault
topNoMax results (default 20).
keywordsYesName, ticker or ISIN, e.g. "Apple", "AAPL", "EURUSD".
assetTypesNoComma-separated AssetType filter, e.g. "Stock" or "Stock,Etf" or "FxSpot". Omit for all.
exchangeIdNoExchange filter, e.g. "NASDAQ", "NYSE", "XETR".
includeNonTradableNo

TDQS

A4/5.0
Behavior3/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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.

  1. 10 tool updatesv0.1.0
    • First observedget_account_history
    • First observedget_account_summary
    • First observedget_balances
    • First observedget_chart_data
    • First observedget_instrument_details
    • First observedget_instrument_price
    • First observedget_orders
    • First observedget_positions
    • First observedget_transactions
    • First observedsearch_instruments

TDQS

A4.2/5.0

Scored across 10 tools

Disambiguation5/5

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.

Naming Consistency5/5

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.

Tool Count5/5

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.

Completeness5/5

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

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    D
    maintenance
    Enables 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.
    5
    7 npm
    4
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables AI assistants to read positions, deals, and orders from the RGO trading platform via natural language, with optional trading capabilities when explicitly enabled.
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI assistants read-only access to Sprinklr data via MCP, allowing querying reports, searching cases, and calling Sprinklr API endpoints.
    7 npm
    ISC
  • A
    license
    B
    quality
    D
    maintenance
    Gives 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.
    32
    7 npm
    2
    MIT