MCP Server API Reference

Protocol-level reference for developers building an MCP client against Medved Trader.

If you just want to connect Claude Desktop or a similar assistant, you don't need this page — see the MCP Server help page for setup. This page is for writing a client yourself, or for understanding exactly what a tool call does.

The MCP server, like all of Medved Trader's APIs, requires a Premium subscription. A trial subscription includes it. See Medved Trader APIs for the other APIs.

Contents

Transport and endpoint

ProtocolModel Context Protocol, version 2025-06-18
TransportStreamable HTTP — JSON-RPC 2.0 request objects over POST
EndpointPOST http://localhost:16240/mcp
Server namemedved-trader
Content typeapplication/json both ways

The port is configurable in Settings → App → API; 16240 is the default. Any path other than /mcp returns 404. Medved Trader must be running, the MCP server must be enabled, and at least one API key must exist — with no enabled key the port is never opened.

GET /mcp opens a text/event-stream that is held for 30 seconds and then closed. It exists so clients that probe for a server-initiated SSE channel don't error out. The server never pushes events or notifications, so a client can ignore it entirely.

No TLS. The endpoint is plain HTTP. On loopback that's fine; bound to a private network address, the API key is transmitted in the clear. Never expose the port to the internet.

Authentication

Every request must carry a valid API key, created in Settings → App → API. Send it either way:

Authorization: Bearer <key>
X-API-Key: <key>

A key is the literal string mt_ followed by 43 base64url characters (A–Z a–z 0–9 - _), 46 in total — for example mt_kQ7pR2vN8xLmT4wYcF6gHbJ3sD9aZeU1oI5nX0yV7tE. It is an opaque token: nothing is encoded in it, and the comparison is exact and case-sensitive. Don't parse it, and don't normalise the case.

Authorization is checked first and is what most MCP clients send. A missing, unknown or disabled key returns HTTP 401 with a JSON-RPC error, code -32001:

{"jsonrpc":"2.0","id":null,"error":{"code":-32001,"message":"Unauthorized: a valid API key is required"}}

Scopes

Each key carries a set of scopes, chosen by the user in the settings UI. Every tool declares the one scope it requires:

ScopeTools
MarketDataget_quote, get_history, get_option_chain, enum_symbols, get_linked_symbol, get_linked_symbol_colors
PortfoliosReadenum_portfolios, get_portfolio_symbols
PortfoliosWriteadd_symbols_to_portfolio, remove_symbols_from_portfolio, set_linked_symbol
AccountsReadenum_trading_accounts, start_trading_account, stop_trading_account, get_account_balances, get_positions, get_transactions
TradingExecuteplace_order, modify_order, cancel_order
AppControlget_layout, set_layout, save_layout, load_layout, get_layout_commands, set_layout_commands, update_charts
—get_app_version is unrestricted

New keys default to read-only: MarketData + PortfoliosRead + AccountsRead.

tools/list is filtered to the caller's scopes, so the tool set you get back depends on the key. Build your client against whatever tools/list returns rather than against this page — a hardcoded assumption that place_order exists will break on a read-only key. Calling an out-of-scope tool is a tool error, not a protocol error (see below).

JSON-RPC methods

MethodBehavior
initializeReturns protocolVersion, a tools capability, and serverInfo with the running MT name and version.
pingEmpty result.
tools/listAll tools with name, description and inputSchema. Not paginated.
tools/callInvokes a tool. See below.
resources/listAlways an empty array — MT exposes no resources.
prompts/listAlways an empty array — MT exposes no prompts.
notifications/initialized
notifications/cancelled
Accepted and ignored.

A request with no id is treated as a notification: it returns HTTP 202 with an empty body and never produces a JSON-RPC response, including on error.

tools/call results

Results come back as a single text content block whose text is the tool's JSON payload — you have to parse the string to get the data:

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "content": [ { "type": "text", "text": "[{\"symbol\":\"AAPL\",\"last\":214.29, ...}]" } ],
    "isError": false
  }
}

A full exchange to read a quote:

POST /mcp
Authorization: Bearer mt_kQ7pR2vN8xLmT4wYcF6gHbJ3sD9aZeU1oI5nX0yV7tE
Content-Type: application/json

{"jsonrpc":"2.0","id":2,"method":"tools/call",
 "params":{"name":"get_quote","arguments":{"symbols":"AAPL,MSFT"}}}

Errors

Two distinct kinds, and clients need to handle both.

Protocol errors use the JSON-RPC error member:

CodeHTTPMeaning
-32001401Missing or invalid API key
-32700200Body is not valid JSON
-32601200Unknown method
-32603200Unhandled internal error

Only the auth failure changes the HTTP status. Once a request is authenticated, protocol errors are reported in the body with a 200 — don't drive your error handling off the status line.

Tool errors are not JSON-RPC errors at all. A tool that rejects its arguments or can't find what you asked for returns a normal result with isError true and the message as the text block:

{"jsonrpc":"2.0","id":3,
 "result":{"content":[{"type":"text","text":"no such portID"}],"isError":true}}

Unknown tool names come back this way too, not as -32601. So does a scope refusal:

{"jsonrpc":"2.0","id":4,
 "result":{"content":[{"type":"text","text":"Not permitted: this API key does not have the TradingExecute scope required by place_order."}],"isError":true}}

Filtering tools/list is not the enforcement — calling a tool you were never listed is refused here, not silently accepted.

Tools: market data

get_quote

Latest quote for one or more symbols.

ArgumentTypeDescription
symbols reqstringComma-separated symbols, or CURRENT (symbols in the active portfolio window), ACTIVE (symbols across all open portfolio windows), or * (every symbol MT has data for).

Returns an array. Symbols MT has no data for are silently skipped, so the result may be shorter than the request — or empty.

[{ "symbol":"AAPL", "timestamp":"2026-07-15T19:42:11Z",
   "last":214.29, "bid":214.28, "ask":214.30, "change":1.87,
   "volume":42150300, "high":215.10, "low":211.94, "open":212.50,
   "bidSize":300, "askSize":100, "lastVolume":200,
   "avgTrade":186.4, "numTrades":226091, "low52":164.08, "high52":237.49 }]

Timestamps are UTC, ISO 8601 (yyyy-MM-ddTHH:mm:ssZ), the same as the WebSocket API. Prices are numbers, rounded to a sensible precision for the price magnitude.

get_history

Historical OHLCV bars for one symbol.

ArgumentTypeDescription
symbol reqstringSymbol to fetch.
start reqstringUTC, as an ISO 8601 date (2026-07-15) or date-time (2026-07-15T13:30:00Z), or "0" for the earliest data available.
end reqstringUTC, as an ISO 8601 date or date-time, or "0" for the latest. A date on its own includes that whole UTC day.
frequency reqstringA plain number = that many minutes, for intraday bars. Otherwise NxD for N-day bars, or 1W, 1M, 1Y. W, M and Y support only 1.
regularHoursOnly optboolExclude extended-hours bars. Intraday only.
newestFirst optboolReverse the result order. Default is oldest first.

Intraday bars carry a timestamp (the bar's start, UTC, ISO 8601); end-of-day bars carry a date (yyyy-MM-dd) instead. That's the only shape difference:

// frequency "5"    -> { "timestamp":"2026-07-15T13:35:00Z", "open":212.5, "high":213.0,
//                       "low":212.4, "close":212.9, "volume":184300 }
// frequency "1D"   -> { "date":"2026-07-15", "open":212.5, "high":215.1,
//                       "low":211.9, "close":214.3, "volume":42150300 }

History is served from what MT has stored and from its data source; intraday depth is bounded by your configured max stored days.

get_option_chain

ArgumentTypeDescription
symbol reqstringUnderlying symbol.
force optboolRequest a fresh chain from the provider instead of using the cached one.
[{ "symbol":".AAPL_260918C220", "expiration":"2026-09-18", "strike":220.0, "type":"C" }]

symbol is the option in MT's own symbol format, usable in any other tool; expiration is yyyy-MM-dd.

This call waits for the data provider to answer, for up to 60 seconds. After that it returns a tool error saying it timed out. Chains can run to thousands of rows.

enum_symbols

ArgumentTypeDescription
active optboolOnly symbols in currently open portfolio windows. Default returns every symbol MT knows.
["AAPL","MSFT","SPY"]

get_linked_symbol

ArgumentTypeDescription
color reqstringLink colour group, from get_linked_symbol_colors.
{ "symbol":"AAPL", "color":"Red" }

get_linked_symbol_colors

Lists the link colour groups. Active is the group that follows the active window: it can be read with get_linked_symbol but not set.

Takes no arguments.

["Blue","Green","Red","Cyan", ... ,"Chocolate","Active"]

set_linked_symbol

Sets the symbol for a link colour group, retargeting every window linked to that colour. This changes what the user is looking at.

ArgumentTypeDescription
symbol reqstringSymbol to link.
color reqstringLink colour group. Active cannot be set.
{ "status":"OK", "symbol":"AAPL", "color":"Red" }

Tools: portfolios

Portfolios come in two kinds, and it matters here. Simple portfolios hold per-row detail — quantity, cost basis, commission, notes — and each row has an itemID. Watchlists hold symbols only. Tools that take detail work on simple portfolios; against a watchlist they either ignore the detail or return an error, as noted.

enum_portfolios

No arguments.

[{ "portID":"A3F2C1", "name":"Tech" }]

get_portfolio_symbols

ArgumentTypeDescription
portID reqstringPortfolio ID, or CURRENT for the active portfolio window.
detailed optboolReturn per-row detail. Ignored for watchlists, which always return a plain symbol list.
// detailed: false  ->  ["AAPL","MSFT"]
// detailed: true   ->  [{ "sym":"AAPL", "itemID":"K7X2", "qty":100,
//                         "paid":185.5, "comm":1.0, "note":"core" }]

In detailed mode, zero-valued and empty fields are omitted rather than sent as 0 or "". This is the call that gives you the itemID values that remove_symbols_from_portfolio needs.

add_symbols_to_portfolio

Adds symbols to a portfolio, updates existing rows, or creates a new portfolio.

ArgumentTypeDescription
portID optstringPortfolio to add to, or CURRENT. Required unless new is set. With new, assigns a specific ID; omit to have one generated.
new optboolCreate a new simple portfolio. Cannot be combined with portID: "CURRENT".
name optstringName for a new portfolio, or renames an existing one.
symbols optstring[]Plain symbols. Ones already present are skipped.
items optobject[]Detailed rows. Simple portfolios only — an error against a watchlist.

Each entry in items takes sym, qty, paid, comm, note, purchaseDate, and optionally itemID. Supplying an itemID that already exists updates that row in place; anything else appends a new one.

{ "status":"OK", "portID":"A3F2C1", "created":false }

On an in-place update, every field is written from what you send — fields you omit are set to zero or null, not preserved. Read the row first and send it back whole.

remove_symbols_from_portfolio

ArgumentTypeDescription
portID reqstringPortfolio ID, or CURRENT.
itemID reqstring[]Row IDs to remove, from get_portfolio_symbols with detailed: true.
{ "status":"OK", "portID":"A3F2C1" }

Tools: trading

These place real orders against real money. There is no paper-trading or dry-run mode, and no confirmation step in the protocol.

Every tool here takes an accountKey from enum_trading_accounts. All of them except enum_trading_accounts and start_trading_account require the account to have been started first.

The read tools need AccountsRead; the three order tools need TradingExecute on top. A key with only AccountsRead can connect an account and read everything about it but cannot send an order, and won't see the order tools listed at all.

enum_trading_accounts

No arguments. Lists enabled accounts only.

[{ "accountKey":"IB:U1234567", "accountID":"U1234567",
   "loginNick":"IB Main", "accountNick":"Margin" }]

start_trading_account

Connects an account so balances, positions, transactions and orders become available. Call once before anything else in this group.

ArgumentTypeDescription
accountKey reqstringFrom enum_trading_accounts.
{ "status":"OK", "message":"Trading account IB:U1234567 started" }

This is the one tool that reports failure as {"status":"fail"} inside a normal success result rather than as a tool error — a broker declining to connect isn't a caller mistake. Check status, not just isError. If API trading isn't permitted for the account's data source, that is a tool error.

stop_trading_account

Disconnects an account. Takes accountKey. Returns {"status":"OK", ...}.

get_account_balances

ArgumentTypeDescription
accountKey reqstring
forceRefresh optboolAsk the broker for fresh data instead of using MT's cache.

Returns the broker's balance items. Fields vary by broker — don't hardcode a schema.

get_positions

Same arguments as get_account_balances. Returns open positions; fields vary by broker.

get_transactions

ArgumentTypeDescription
accountKey reqstring
forceRefresh optboolAsk the broker for fresh data.
changesOnly optboolOnly what changed since your last call. Ignored when forceRefresh is set.

Orders and transactions, including live order status. This is how you find out what happened to an order you sent.

place_order

ArgumentTypeDescription
accountKey reqstring
order reqobjectOrder object with a Legs array of at least one leg.

Each leg takes Symbol, Action (Buy, Sell, SellShort, BuyToCover), Quantity, OrderType (Market, Limit, Stop, StopLimit), and LimitPrice / StopPrice as the type requires. TIF, Routing, DisplaySize and SpInstructions are optional.

{ "accountKey": "IB:U1234567",
  "order": { "Legs": [ { "Symbol":"AAPL", "Action":"Buy", "Quantity":100,
                         "OrderType":"Limit", "LimitPrice":210.00, "TIF":"Day" } ] } }
{ "status":"OK", "message":"new order sent", "reqID":"3f2a...-...-..." }

"status":"OK" means sent, not accepted and not filled. The call returns as soon as the order goes to the broker; it does not wait for acknowledgement. Correlate the returned reqID against get_transactions to find out what actually happened. A rejected order still returns OK here.

modify_order

Same arguments as place_order, but order must include OrderID.

{ "accountKey": "IB:U1234567",
  "order": { "OrderID":"00051f7d.6743a1e2", "Legs": [ { "LimitPrice": 211.50 } ] } }

Leg fields you omit are filled in from the existing order — quantity, order type, prices, TIF, routing and so on — so you only send what changes. Symbol and action always come from the original order and can't be changed by modifying; cancel and re-place instead. Same send-only semantics as place_order.

cancel_order

ArgumentTypeDescription
accountKey reqstring
orderID reqstring[]One or more order IDs.
{ "status":"OK", "message":"cancel sent", "reqID":"7b1c...-...-..." }

Send-only, as above. Confirm via get_transactions.

Tools: application

get_app_version

No arguments. Unrestricted — any valid key may call it.

{ "app":"Medved Trader", "version":"1.2.3.4" }

Layout commands

A layout file can carry layout commands: settings that are applied every time the file is loaded (load_layout, File > Load Layout, the Layout Library), before its windows open. Each is one string, VERB arg arg. The settings they change are saved and stay changed after another layout is loaded. Unknown or invalid commands in a file are logged and skipped when it loads. The tools below check them first and write nothing if any is invalid. The full list of commands is in the set_layout_commands tool description returned by tools/list. Currently:

CommandEffect
SourcePriority <SourceID> <Never|Low|Normal|High>Smart Source Priority of a data source the user has set up — the same setting as Settings > Sources. Skipped if the source is not set up.
ChartOddLots <On|Off>Includes or excludes odd-lot trades on intraday charts — the same setting as Settings > Charts “Include Odd Lots on intraday charts”. Affects US stocks and warrants only. Open charts reload when it changes.

Saving the layout again from the application (File > Save Layout) drops its commands. get_layout never returns any. set_layout runs commands in its XML once, but stores nothing.

save_layout

ArgumentTypeDescription
namestringLayout name (saved in the user's layouts folder with the .layout extension) or a full file path.
encrypted optboolEncrypt the file instead of saving clear XML. Default false.
commands optstring[]Layout commands to store in the file.

Saves the current window layout. Needs AppControl.

{ "name": "Futures", "commands": ["SourcePriority SCHWAB High", "SourcePriority DXFEED Normal"] }
{ "status":"OK", "path":"C:\\...\\Futures.layout", "commands":["SourcePriority SCHWAB High","SourcePriority DXFEED Normal"] }

get_layout_commands

ArgumentTypeDescription
namestringLayout name or full file path. The file must exist.
{ "path":"C:\\...\\Futures.layout", "commands":["SourcePriority SCHWAB High"] }

set_layout_commands

ArgumentTypeDescription
namestringLayout name or full file path. The file must exist.
commandsstring[]The commands, replacing any the file has. An empty array removes them.

Rewrites only the commands. The file's windows, the screens it was saved on, and its clear or encrypted format are kept. It does not run the commands: call load_layout to apply the layout and its commands. Layout Library files that carry a version header are refused. Needs AppControl.

{ "status":"OK", "path":"C:\\...\\Futures.layout", "commands":["SourcePriority SCHWAB High"] }

update_charts

ArgumentTypeDescription
symbol optstringOnly charts showing this symbol. Omit or "*" for all open charts.
drawings optboolAlso re-read the charts' drawings from the drawings database. Default false.

Makes open chart windows recalculate and repaint. It does not download history again. With drawings, the charts also re-read their drawings (trendlines, boxes, fibs, text and so on). Use that after changing the drawings database from outside the application: open charts don't otherwise notice such changes until they are closed and reopened. Needs AppControl.

{ "symbol": "AAPL", "drawings": true }
{ "status":"OK", "charts":2, "symbols":["AAPL"] }

charts counts the chart windows flagged; symbols lists their distinct symbols. The call returns before the charts refresh — each one acts on its next refresh cycle, normally within a second.

There is no shutdown tool. MCP does not expose one, and no scope enables one. The WebSocket and HTTP APIs have their own shutdown commands; if you need to close the application programmatically, use those. Application lifetime is deliberately outside what an MCP client can reach.

Client notes