XTBApiServer
by sharplygroup
- src
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { WebSocketManager, AccountOperations, CalculationOperations, MarketDataOperations, NewsOperations, ServerOperations, SymbolOperations, TradeOperations, TradingOperations } from '@sharplygroup/xtb-api-js';
import * as dotenv from "dotenv";
dotenv.config({ path: ".env.e2e" });
const USER_ID = process.env.XTB_USERID || "";
const PASSWORD = process.env.XTB_PASSWORD || "";
const DEMO_ACCOUNT = process.env.XTB_DEMO === "true";
async function createXTBClient() {
const wsManager = new WebSocketManager({
userId: USER_ID,
password: PASSWORD,
demo: DEMO_ACCOUNT
});
try {
await wsManager.connect();
return wsManager;
} catch (error) {
console.error("Login error:", error);
throw error;
}
}
async function exposeXtbOperations(server: McpServer, wsManager: any) {
const accountOperations = new AccountOperations(wsManager);
const calculationOperations = new CalculationOperations(wsManager);
const marketDataOperations = new MarketDataOperations(wsManager);
const newsOperations = new NewsOperations(wsManager);
const serverOperations = new ServerOperations(wsManager);
const symbolOperations = new SymbolOperations(wsManager);
const tradeOperations = new TradeOperations(wsManager);
const tradingOperations = new TradingOperations(wsManager);
// Account Operations
server.tool(
"getCurrentUserData",
"Returns information about account currency and account leverage.",
{},
async () => {
const data = await accountOperations.getCurrentUserData();
return { content: [{ type: "text", text: JSON.stringify(data) }] };
}
);
server.tool(
"getMarginLevel",
"Returns various account indicators.",
{},
async () => {
const data = await accountOperations.getMarginLevel();
return { content: [{ type: "text", text: JSON.stringify(data) }] };
}
);
// Calculation Operations
server.tool(
"getCommissionDef",
"Returns calculation of commission and rate of exchange.",
{ symbol: z.string(), volume: z.number() },
async ({ symbol, volume }) => {
const data = await calculationOperations.getCommissionDef(symbol, volume);
return { content: [{ type: "text", text: JSON.stringify(data) }] };
}
);
server.tool(
"getMarginTrade",
"Returns expected margin for given instrument and volume.",
{ symbol: z.string(), volume: z.number() },
async ({ symbol, volume }) => {
const data = await calculationOperations.getMarginTrade(symbol, volume);
return { content: [{ type: "text", text: JSON.stringify(data) }] };
}
);
server.tool(
"getProfitCalculation",
"Calculates estimated profit for given deal data.",
{ closePrice: z.number(), cmd: z.number(), openPrice: z.number(), symbol: z.string(), volume: z.number() },
async ({ closePrice, cmd, openPrice, symbol, volume }) => {
const data = await calculationOperations.getProfitCalculation(closePrice, cmd, openPrice, symbol, volume);
return { content: [{ type: "text", text: JSON.stringify(data) }] };
}
);
// MarketDataOperations
server.tool(
"getCalendar",
"Returns calendar with market events.",
{},
async () => {
const data = await marketDataOperations.getCalendar();
return { content: [{ type: "text", text: JSON.stringify(data) }] };
}
);
server.tool(
"getChartLastRequest",
"Returns chart info, from start date to the current time.",
{ info: z.any() },
async ({ info }) => {
const data = await marketDataOperations.getChartLastRequest(info);
return { content: [{ type: "text", text: JSON.stringify(data) }] };
}
);
server.tool(
"getChartRangeRequest",
"Returns chart info with data between given start and end dates.",
{ info: z.any() },
async ({ info }) => {
const data = await marketDataOperations.getChartRangeRequest(info);
return { content: [{ type: "text", text: JSON.stringify(data) }] };
}
);
server.tool(
"getTickPrices",
"Returns array of current quotations for given symbols, only quotations that changed from given timestamp are returned.",
{ level: z.number(), symbols: z.string().array(), timestamp: z.number() },
async ({ level, symbols, timestamp }) => {
const data = await marketDataOperations.getTickPrices(level, symbols, timestamp);
return { content: [{ type: "text", text: JSON.stringify(data) }] };
}
);
server.tool(
"getTradingHours",
"Returns quotes and trading times.",
{ symbols: z.string().array() },
async ({ symbols }) => {
const data = await marketDataOperations.getTradingHours(symbols);
return { content: [{ type: "text", text: JSON.stringify(data) }] };
}
);
// NewsOperations
server.tool(
"getNews",
"Returns news from trading server which were sent within specified period of time.",
{ end: z.number(), start: z.number() },
async ({ end, start }) => {
const data = await newsOperations.getNews(end, start);
return { content: [{ type: "text", text: JSON.stringify(data) }] };
}
);
// ServerOperations
server.tool(
"getServerTime",
"Returns current time on trading server.",
{},
async () => {
const data = await serverOperations.getServerTime();
return { content: [{ type: "text", text: JSON.stringify(data) }] };
}
);
server.tool(
"getVersion",
"Returns the current API version.",
{},
async () => {
const data = await serverOperations.getVersion();
return { content: [{ type: "text", text: JSON.stringify(data) }] };
}
);
server.tool(
"ping",
"Regularly calling this function is enough to refresh the internal state of all the components in the system.",
{},
async () => {
await serverOperations.ping();
return { content: [{ type: "text", text: "Ping successful" }] };
}
);
// SymbolOperations
server.tool(
"getAllSymbols",
"Returns an array of all symbols available for the user.",
{},
async () => {
const data = await symbolOperations.getAllSymbols();
return { content: [{ type: "text", text: JSON.stringify(data) }] };
}
);
server.tool(
"getSymbol",
"Returns information about the symbol available for the user.",
{ symbol: z.string() },
async ({ symbol }) => {
const data = await symbolOperations.getSymbol(symbol);
return { content: [{ type: "text", text: JSON.stringify(data) }] };
}
);
// TradeOperations
server.tool(
"getTrades",
"Returns an array of user's trades.",
{ openedOnly: z.boolean() },
async ({ openedOnly }) => {
const data = await tradeOperations.getTrades(openedOnly);
return { content: [{ type: "text", text: JSON.stringify(data) }] };
}
);
server.tool(
"getTradeRecords",
"Returns an array of trades listed in the `orders` argument.",
{ orders: z.number().array() },
async ({ orders }) => {
const data = await tradeOperations.getTradeRecords(orders);
return { content: [{ type: "text", text: JSON.stringify(data) }] };
}
);
server.tool(
"getTradesHistory",
"Returns an array of user's trades which were closed within specified period of time.",
{ end: z.number(), start: z.number() },
async ({ end, start }) => {
const data = await tradeOperations.getTradesHistory(end, start);
return { content: [{ type: "text", text: JSON.stringify(data) }] };
}
);
server.tool(
"getTradeStatus",
"Returns current transaction status.",
{ order: z.number() },
async ({ order }) => {
const data = await tradeOperations.getTradeStatus(order);
return { content: [{ type: "text", text: JSON.stringify(data) }] };
}
);
// TradingOperations
server.tool(
"tradeTransaction",
"Starts a trade transaction.",
{ tradeTransInfo: z.any() },
async ({ tradeTransInfo }) => {
const data = await tradingOperations.tradeTransaction(tradeTransInfo);
return { content: [{ type: "text", text: JSON.stringify(data) }] };
}
);
server.tool(
"tradeTransactionStatus",
"Returns current transaction status.",
{ order: z.number() },
async ({ order }) => {
const data = await tradingOperations.tradeTransactionStatus(order);
return { content: [{ type: "text", text: JSON.stringify(data) }] };
}
);
// Command execution tool
server.tool(
"executeCommand",
"Executes a command on the server.",
{ command: z.string() },
async ({ command }) => {
try {
interface ExecResult {
stdout: string;
stderr: string;
}
const { exec } = require('child_process');
const result: ExecResult = await new Promise((resolve, reject) => {
exec(command, (error: Error | null, stdout: string, stderr: string) => {
if (error) {
reject(error);
return;
}
resolve({ stdout, stderr });
});
});
const output = `Stdout: ${result.stdout}\nStderr: ${result.stderr}`;
return { content: [{ type: "text", text: output }] };
} catch (error: any) {
return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
}
}
);
// Prompts
server.prompt(
"getAccountDataPrompt",
{ },
async () => {
return {
messages: [{
role: "user",
content: {
type: "text",
text: "Get the current account data."
}
}]
};
}
);
server.prompt(
"getSymbolInfoPrompt",
{ symbol: z.string().describe("The symbol to get information about") },
async ({ symbol }) => {
return {
messages: [{
role: "user",
content: {
type: "text",
text: `Get information about the symbol ${symbol}.`
}
}]
};
}
);
}
async function startMcpServer() {
const server = new McpServer({
name: "XTBApiServer",
version: require('../package.json').version,
});
try {
const wsManager = await createXTBClient();
await exposeXtbOperations(server, wsManager);
const transport = new StdioServerTransport();
await server.connect(transport);
console.log("XTBApiServer running on stdio");
} catch (e) {
console.error("Error starting server:", e);
}
}
startMcpServer();