import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { ZerodhaClient } from './zerodha-client.js';
import dotenv from 'dotenv';
dotenv.config();
class ZerodhaMCPServer {
constructor() {
this.zerodhaClient = new ZerodhaClient(
process.env.ZERODHA_API_KEY,
process.env.ZERODHA_API_SECRET,
process.env.ZERODHA_REDIRECT_URI
);
this.server = new Server(
{
name: 'zerodha-trading-server',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
this.setupToolHandlers();
}
setupToolHandlers() {
// Tool: Get login URL
this.server.setRequestHandler('tools/call', async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'get_login_url':
return {
content: [
{
type: 'text',
text: this.zerodhaClient.getLoginURL()
}
]
};
case 'authenticate':
const { requestToken } = args;
const tokens = await this.zerodhaClient.getAccessToken(requestToken);
return {
content: [
{
type: 'text',
text: JSON.stringify(tokens, null, 2)
}
]
};
case 'get_user_profile':
const profile = await this.zerodhaClient.getUserProfile();
return {
content: [
{
type: 'text',
text: JSON.stringify(profile, null, 2)
}
]
};
case 'get_holdings':
const holdings = await this.zerodhaClient.getHoldings();
return {
content: [
{
type: 'text',
text: JSON.stringify(holdings, null, 2)
}
]
};
case 'get_positions':
const positions = await this.zerodhaClient.getPositions();
return {
content: [
{
type: 'text',
text: JSON.stringify(positions, null, 2)
}
]
};
case 'get_quote':
const { symbols } = args;
const quote = await this.zerodhaClient.getQuote(symbols);
return {
content: [
{
type: 'text',
text: JSON.stringify(quote, null, 2)
}
]
};
case 'place_order':
const orderResult = await this.zerodhaClient.placeOrder(args);
return {
content: [
{
type: 'text',
text: JSON.stringify(orderResult, null, 2)
}
]
};
case 'get_order_history':
const orders = await this.zerodhaClient.getOrderHistory();
return {
content: [
{
type: 'text',
text: JSON.stringify(orders, null, 2)
}
]
};
case 'cancel_order':
const { orderId } = args;
const cancelResult = await this.zerodhaClient.cancelOrder(orderId);
return {
content: [
{
type: 'text',
text: JSON.stringify(cancelResult, null, 2)
}
]
};
case 'get_margins':
const margins = await this.zerodhaClient.getMargins();
return {
content: [
{
type: 'text',
text: JSON.stringify(margins, null, 2)
}
]
};
case 'validate_token':
const isValid = await this.zerodhaClient.validateToken();
return {
content: [
{
type: 'text',
text: JSON.stringify({ isValid }, null, 2)
}
]
};
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error: ${error.message}`
}
]
};
}
});
// Tool definitions
this.server.setRequestHandler('tools/list', async () => {
return {
tools: [
{
name: 'get_login_url',
description: 'Get the login URL for Zerodha OAuth authentication',
inputSchema: {
type: 'object',
properties: {},
required: []
}
},
{
name: 'authenticate',
description: 'Authenticate with Zerodha using request token',
inputSchema: {
type: 'object',
properties: {
requestToken: {
type: 'string',
description: 'Request token received from Zerodha OAuth'
}
},
required: ['requestToken']
}
},
{
name: 'get_user_profile',
description: 'Get the current user profile from Zerodha',
inputSchema: {
type: 'object',
properties: {},
required: []
}
},
{
name: 'get_holdings',
description: 'Get current holdings from Zerodha',
inputSchema: {
type: 'object',
properties: {},
required: []
}
},
{
name: 'get_positions',
description: 'Get current positions from Zerodha',
inputSchema: {
type: 'object',
properties: {},
required: []
}
},
{
name: 'get_quote',
description: 'Get live quotes for symbols',
inputSchema: {
type: 'object',
properties: {
symbols: {
type: 'string',
description: 'Comma-separated list of symbols (e.g., "NSE:RELIANCE,NSE:TCS")'
}
},
required: ['symbols']
}
},
{
name: 'place_order',
description: 'Place a trading order on Zerodha',
inputSchema: {
type: 'object',
properties: {
tradingsymbol: {
type: 'string',
description: 'Trading symbol (e.g., "RELIANCE")'
},
exchange: {
type: 'string',
description: 'Exchange (NSE, BSE)',
default: 'NSE'
},
transaction_type: {
type: 'string',
description: 'BUY or SELL',
enum: ['BUY', 'SELL']
},
quantity: {
type: 'integer',
description: 'Quantity to trade'
},
product: {
type: 'string',
description: 'Product type (CNC, MIS, NRML)',
default: 'MIS'
},
order_type: {
type: 'string',
description: 'Order type (MARKET, LIMIT, SL, SL-M)',
default: 'MARKET'
},
price: {
type: 'number',
description: 'Price for limit orders'
},
trigger_price: {
type: 'number',
description: 'Trigger price for stop loss orders'
}
},
required: ['tradingsymbol', 'transaction_type', 'quantity']
}
},
{
name: 'get_order_history',
description: 'Get order history from Zerodha',
inputSchema: {
type: 'object',
properties: {},
required: []
}
},
{
name: 'cancel_order',
description: 'Cancel an existing order',
inputSchema: {
type: 'object',
properties: {
orderId: {
type: 'string',
description: 'Order ID to cancel'
}
},
required: ['orderId']
}
},
{
name: 'get_margins',
description: 'Get margin information from Zerodha',
inputSchema: {
type: 'object',
properties: {},
required: []
}
},
{
name: 'validate_token',
description: 'Validate if the current access token is still valid',
inputSchema: {
type: 'object',
properties: {},
required: []
}
}
]
};
});
}
async start() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.log('Zerodha MCP Server started');
}
}
// Start the server
const server = new ZerodhaMCPServer();
server.start().catch(console.error);