🌐 Website
A Model Context Protocol (MCP) server, written in .NET, that exposes the Börsdata financial data API (instruments, markets, stock prices, KPIs, and financial reports) as MCP tools over stdio. Works with any MCP client.
Disclaimer: This is an unofficial, community-built project and is not affiliated with, endorsed by, or sponsored by Börsdata AB. "Börsdata" is a trademark of Börsdata AB, used here only to describe compatibility with their public API. You are responsible for your own Börsdata subscription, API key, and compliance with Börsdata's own terms of service. This software is provided as-is, without warranty of any kind — see LICENSE.
Once connected, you can ask your AI assistant things like this in swedish:
- "Screena fram alla nordiska bolag med P/E under 10 och positiv vinsttillväxt senaste 5 åren"
- "Jämför rörelsemarginalen för Sandvik, SKF och Trelleborg kvartalsvis de senaste 3 åren"
- "Vilka bolag har störst blankningspositioner just nu och hur har deras kurser utvecklats senaste månaden?"
- "Visa insiderköp över 1 MSEK i Volvo och Latour det senaste halvåret"
- "Analysera Hacksaws R12-rapporter och identifiera trender i omsättningstillväxt och marginalutveckling"
- "Vilka bolag i tekniksektorn har kommande rapportdatum inom de närmaste två veckorna?"
- "Sammanfatta nyckeltalen för Boliden — P/E, EV/EBITDA, direktavkastning och skuldsättningsgrad — och jämför med branschsnittet"
- "Hämta utdelningshistoriken för Swedbank och beräkna den genomsnittliga utdelningstillväxten per år"
| Tool | Description | Parameters |
|---|---|---|
search_instruments |
Searches instruments by name, ticker, or ISIN; supports market/country/sector/branch filters, Nordic/global/all universes, deterministic sorting, and cursor-paginated results. Returns { totalMatched, returned, instruments, nextCursor? } |
First call: search, marketId, countryId, sectorId, branchId, universe (nordic/global/all), sortBy (ticker/name), sortDirection (asc/desc), pageSize (default 50, max 200). Later pages: cursor only. |
list_markets |
Lists all markets known to Börsdata (e.g. Stockholm Large Cap, First North) | — |
list_branches |
Lists all industry branches known to Börsdata | — |
list_sectors |
Lists all sectors known to Börsdata | — |
list_countries |
Lists all countries known to Börsdata | — |
list_kpi_metadata |
Lists all KPIs known to Börsdata (kpiId, Swedish/English name, format, whether the value is a string) — use to find the kpiId for screen_instruments/get_kpi_screener/get_kpi_history/get_kpi_list_screener |
— |
list_kpi_history_options |
Searches a bundled copy of Börsdata's official KPI History table for exact, valid kpiId/reportType/priceType combinations. Makes no Börsdata API request. Use before either KPI history tool whenever the combination is unknown; latest is not a history price type. |
kpiId, query, maxCount (at least kpiId or query is required) |
list_kpi_screener_options |
Searches a bundled copy of Börsdata's official KPI Screener List for exact, valid kpiId/calcGroup/calc combinations. Makes no Börsdata API request. Use this before the screener tools whenever the combination is unknown; do not guess. |
kpiId, query, maxCount (at least kpiId or query is required) |
get_stock_splits |
Stock splits and reverse splits across all instruments (small, ~44 entries live) — split date, ratio, type. Each result is enriched with ticker/name |
— |
list_report_metadata |
Lists metadata for every field returned by get_reports/get_kpi_summary (property name, Swedish/English display name, format) — use to look up what a report field means |
— |
list_translation_metadata |
Lists Börsdata's translation table (translationKey plus Swedish/English name) used for coded labels across the API, e.g. sector/branch names |
— |
get_instruments_updated |
Last-updated timestamps for recently-updated instruments, most-recent-first. Does not cover every instrument — an id missing from the results just hasn't updated recently. Each result is enriched with ticker/name. Returns { totalMatched, returned, values } |
instrumentIds (comma-separated), maxCount (optional — omitting both returns ~700 entries) |
get_kpis_updated |
The single global timestamp for when Börsdata's KPI calculations were last refreshed (not per-instrument). Returns { kpisCalcUpdated } |
— |
get_stock_prices |
Daily stock price history (open, high, low, close, volume) for one instrument. Omitting from/to/maxCount returns Börsdata's default 10-year window. maxCount is a lookback window in years (1-20, Börsdata's own limit for this endpoint), not a count of days/entries — use from/to for an exact date range or a small recent window |
instrumentId (required); from, to, maxCount (optional) |
get_latest_stock_prices |
Latest available daily OHLCV records, enriched with ticker/name, sorted and cursor-paginated. Returns { totalMatched, returned, values, nextCursor? } with compact Börsdata fields i/d/h/l/c/o/v. |
First call: instrumentIds, global, sortBy, sortDirection, pageSize (optional). Later pages: cursor only. |
get_stock_prices_by_date |
Daily OHLCV records for a specific historical date; a non-trading day returns no results. Uses the same enriched, sorted and paginated shape as get_latest_stock_prices. |
First call: date (required); instrumentIds, global, sortBy, sortDirection, pageSize (optional). Later pages: cursor only. |
get_kpi_screener |
A calculated KPI value (e.g. P/E, revenue growth) for one instrument — named to match Börsdata's own "KPI Screener" terminology for this endpoint | instrumentId, kpiId, calcGroup, calc (all required) |
get_kpi_history |
How a KPI (e.g. P/E) has trended over time for one instrument, unlike get_kpi_screener's single current value. The combination is validated against list_kpi_history_options before the API call. |
instrumentId, kpiId, reportType, priceType (required); maxCount (optional) |
get_kpi_summary |
Every KPI Börsdata tracks for one instrument across multiple periods in one call — unlike get_kpi_screener's single value for one specific KPI. Each entry is keyed by KpiId (see list_kpi_metadata to resolve names) |
instrumentId, reportType (year/quarter/r12) (required); maxCount (optional, caps periods per KPI) |
get_reports |
Financial reports (income statement, balance sheet, cash flow) for one instrument | instrumentId, reportType (year/quarter/r12) (all required) |
screen_instruments |
Recommended for finding Nordic or global instruments that satisfy one or more financial KPI conditions. Validates every KPI combination against the bundled official catalog, fetches complete KPI lists internally, combines filters with AND logic, and returns a cursor-paginated result snapshot. Call again with only cursor when nextCursor is returned. |
First call: kpiFilters (required); global, countryIds, marketIds, sectorIds, branchIds, sortBy, pageSize (optional). Later pages: cursor only. |
get_kpi_list_screener |
A calculated KPI value (e.g. P/E) for every Nordic instrument on Börsdata in one call — a raw, complete mirror of Börsdata's bulk endpoint for export or custom processing, with no sort, filter, or count control (~14,000 entries). Prefer screen_instruments for KPI-condition screening. Each result is enriched with ticker/name. Returns { kpiId, calcGroup, calc, values } |
kpiId, calcGroup, calc (all required) |
get_global_kpi_list_screener |
The raw, complete global counterpart to get_kpi_list_screener, mirroring Börsdata's separate global endpoint with no result cap. Prefer screen_instruments for KPI-condition screening. Returns { kpiId, calcGroup, calc, values } |
kpiId, calcGroup, calc (all required) |
get_kpi_history_array |
Historical values for a KPI (e.g. P/E) over time for a list of up to 50 instruments in one call — the bulk version of get_kpi_history. The combination is validated against list_kpi_history_options before the API call. Returns one entry per instrument under kpisList (or an error field per instrument if unresolved). |
kpiId, reportType, priceType, instrumentIds (comma-separated, all required); maxCount (optional — caps periods per instrument) |
get_reports_compound |
All financial report types (year, quarter, r12) for one instrument in a single call — unlike get_reports, which returns just one report type per call |
instrumentId (required); maxYearCount, maxR12QCount, original (optional — Börsdata defaults 10/10, max 20/40; original returns figures in the instrument's original reporting currency) |
get_reports_array |
All financial report types (year, quarter, r12) for a list of instruments in one call — the bulk version of get_reports_compound, filtering server-side by instrumentIds. Returns one entry per instrument under reportList (or an error field per instrument if unresolved) |
instrumentIds (comma-separated, required); maxYearCount, maxR12QCount, original (optional, same as get_reports_compound) |
get_instrument_descriptions |
Swedish/English company description text for a list of instruments (max 50 per Börsdata's own limit) — a direct mirror of Börsdata's own "Instrument Description" endpoint. Returns { insId, languageCode, text } per instrument (or an error field if unresolved) |
instrumentIds (comma-separated, required, max 50) |
get_report_calendar |
Report release dates for specified instruments — Börsdata returns each instrument's full past + already-scheduled history in one list, so pass fromDate (e.g. today) to get only upcoming reports. Returns { instruments: [{ insId, totalMatched, returned, reports }] } |
instrumentIds (comma-separated, required); fromDate, toDate, maxCount (optional) |
get_dividend_calendar |
Dividend ex-dates and amounts for specified instruments — same full past + already-scheduled history in one list as get_report_calendar, so pass fromDate for only upcoming dividends. Returns { instruments: [{ insId, totalMatched, returned, dividends }] } |
instrumentIds (comma-separated, required); fromDate, toDate, maxCount (optional) |
get_insider_holdings |
Insider transactions (board members/executives trading their own company's shares) for specified instruments, sorted most-recent-first. Use direction (based on the sign of shares) rather than Börsdata's undocumented transactionType codes to distinguish acquisitions from disposals. Returns { instruments: [{ insId, totalMatched, returned, transactions }] } |
instrumentIds (comma-separated, required); fromDate, toDate, minAmount, direction (increase/decrease), maxCount (optional) |
get_buyback_holdings |
Share buyback transactions for specified instruments, sorted most-recent-first (same full-history-oldest-first caveat as get_insider_holdings). Returns { instruments: [{ insId, totalMatched, returned, buybacks }] } |
instrumentIds (comma-separated, required); fromDate, toDate, maxCount (optional) |
get_short_holdings |
Short-position data (shorting %, holders, days-to-cover, trend) — covers every Nordic instrument in one call unless filtered. Sorted by shorting percent descending by default. Each result is enriched with ticker/name. Returns { totalMatched, returned, values } |
instrumentIds (comma-separated), minShortingPercent, sortAscending, maxCount (all optional — omitting all of these returns ~400+ entries) |
instrumentId is the insId returned by search_instruments, and is used
by every other tool that operates on a specific instrument.
The option catalogs live in src/BorsdataMcp/Data and are embedded in the application at build
time. To refresh them, download Börsdata's
KPI-History.md and
Kpi-Screener-List.md, then run:
python3 scripts/generate-kpi-history-catalog.py KPI-History.md \
src/BorsdataMcp/Data/kpi-history-options.json --retrieved YYYY-MM-DD
python3 scripts/generate-kpi-screener-catalog.py Kpi-Screener-List.md \
src/BorsdataMcp/Data/kpi-screener-options.json --retrieved YYYY-MM-DD- .NET 10 SDK (building/running from source) — the self-contained
.mcpbDesktop Extension below needs no .NET installed at all; only the-portablevariant needs the .NET 10 runtime - Your own Börsdata account with API access (borsdata.se) and its API key. This is required for every user, individually — there is no shared or bundled key; each installation talks to Börsdata under its own account and subscription.
Set your API key via either:
src/BorsdataMcp/appsettings.json(Borsdata:ApiKey), or- the
Borsdata__ApiKeyenvironment variable (do not commit a real key toappsettings.json)
Screening defaults are configured under Screening in appsettings.json: 50 results per page,
a maximum page size of 200, and an absolute snapshot lifetime of 15 minutes. The equivalent
environment variables are Screening__DefaultPageSize, Screening__MaxPageSize, and
Screening__SnapshotTtl.
dotnet run --project src/BorsdataMcpThe server communicates over stdio, so it's normally launched by an MCP client rather than run directly in a terminal — point your client's server config at this command (or a published build).
run-dev.sh (Linux/macOS) / run-dev.cmd (Windows) do the same thing but
source .env first, so you don't have to export Borsdata__ApiKey
yourself:
cp .env.example .env # then fill in Borsdata__ApiKey
./run-dev.shdotnet build
dotnet testtests/BorsdataMcp.Tests are fast, in-process unit tests. tests/BorsdataMcp.IntegrationTests
spawn the real server as a subprocess and connect to it over stdio with a real MCP client, with
its Börsdata calls redirected (via the Borsdata__BaseUrl env var) to a local fake HTTP server —
no real Börsdata API key or network access is needed. They verify: the server starts and completes
the MCP initialize handshake; tools/list succeeds and returns the expected tools with valid
input schemas; a representative tool call succeeds against canned fixture data; invalid input
produces a real, useful error message (not a generic collapse); the server process exits cleanly
when stdin closes; and stdout never carries anything but valid JSON-RPC frames (a stray log line
there would corrupt the protocol stream for every real MCP client). Run just these with:
dotnet test tests/BorsdataMcp.IntegrationTestsCI runs both projects on ubuntu-latest, windows-latest, and macos-latest. Limitations:
these tests check MCP protocol mechanics against canned fixture data only — they don't exercise
real Börsdata API behavior or data, and no actual LLM's tool-selection or reasoning is in the
loop, unlike using the server through a real AI client.
Two Claude-specific ways to register this server, on top of the generic stdio usage above.
Local server config: copy .mcp.json.example to .mcp.json (already
gitignored — it holds an absolute, machine-specific path) and point
command at run-dev.sh/run-dev.cmd.
Desktop Extension (.mcpb): for Claude apps whose Chat/Cowork surface
only reads MCP servers from a remote Connectors registry, not a local
config file. A Desktop Extension
is a small zip, installed via drag-and-drop into Settings → Extensions,
that bundles this server plus a manifest telling Claude how to launch it
and what to ask for (the API key) at install time.
Each Release ships four .mcpb files — pick one:
borsdata-mcp-<version>-linux-x64.mcpb— self-contained, Linux only. Bundles the .NET runtime itself, so nothing needs to be installed separately. Recommended for most Linux users. (Confirmed end-to-end on Linux.)borsdata-mcp-<version>-win-x64.mcpb— self-contained, Windows only. Same as above but for Windows; not yet verified end-to-end there.borsdata-mcp-<version>-osx-universal.mcpb— self-contained, macOS only, a universal binary covering both Intel and Apple Silicon Macs. Not yet verified end-to-end there.borsdata-mcp-<version>-portable.mcpb— framework-dependent, works on Linux/macOS/Windows, but requires the .NET 10 runtime (not the SDK) to already be installed on the machine. Smaller download; an alternative to the self-contained builds above for anyone who already has .NET installed.
Or build any of these yourself:
# portable (framework-dependent, needs .NET 10 runtime installed)
dotnet publish src/BorsdataMcp/BorsdataMcp.csproj -c Release -o mcpb/server/app
cd mcpb && npx --yes @anthropic-ai/mcpb pack # produces mcpb.mcpb
# self-contained (bundles the runtime; substitute win-x64 for Windows)
dotnet publish src/BorsdataMcp/BorsdataMcp.csproj -c Release -r linux-x64 --self-contained true -p:PublishSingleFile=true -o mcpb/server/app
# then edit mcpb/manifest.json's server.entry_point/mcp_config.command to
# "server/app/BorsdataMcp" (or "server/app/BorsdataMcp.exe" for win-x64) —
# see .github/workflows/build.yml for the exact jq patch CI applies —
# before packing
# self-contained universal macOS build (run on an actual Mac — needs `lipo`)
dotnet publish src/BorsdataMcp/BorsdataMcp.csproj -c Release -r osx-x64 --self-contained true -p:PublishSingleFile=true -o publish-osx-x64
dotnet publish src/BorsdataMcp/BorsdataMcp.csproj -c Release -r osx-arm64 --self-contained true -p:PublishSingleFile=true -o publish-osx-arm64
mkdir -p mcpb/server/app
lipo -create -output mcpb/server/app/BorsdataMcp publish-osx-x64/BorsdataMcp publish-osx-arm64/BorsdataMcp
cp publish-osx-x64/appsettings.json mcpb/server/app/appsettings.json
# then edit mcpb/manifest.json the same way as above (entry_point/command to
# "server/app/BorsdataMcp", compatibility.platforms to ["darwin"]) before packingDrag it into Settings → Extensions and enter your Börsdata API key when
prompted (collected by the install UI, not read from .env).
You need your own Börsdata account with API access to use this extension. It does not come with a Börsdata subscription or API key — every installation requires the user's own borsdata.se account and key entered at install time; there is no shared key.
src/BorsdataMcp— the MCP server:Program.cswires up hosting, DI, and the stdio transport;BorsdataApiClientwraps the Börsdata HTTP API;AuthKeyHandlerattaches the API key to every outgoing request;Tools/contains the[McpServerTool]-attributed methods exposed to clients.tests/BorsdataMcp.Tests— unit tests.tests/BorsdataMcp.IntegrationTests— subprocess-level MCP protocol tests: a real MCP client, a real server process, and a fake Börsdata HTTP backend.mcpb/— packages the server as a Claude Desktop Extension; see above.
See CONTRIBUTING.md for how to get set up and submit changes.