Skip to main content
Glama
index.ts25.9 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import { generateConsent, consumeConsent, getAuthState, getAccessToken, isTokenValid, resetAuthState, getStep2Instructions, getFundLimit, placeOrder, modifyOrder, cancelOrder, getOrderBook, getOrderByID, getOrderByCorrelationID, getTradeBook, getOrderTrades, placeSuperOrder, modifySuperOrder, cancelSuperOrderLeg, getSuperOrderBook, } from './authentication.js'; import { validateConfig } from './config.js'; // Validate configuration on startup validateConfig(); // Create MCP Server instance with tools capability const server = new Server({ name: 'dhan-mcp-server', version: '1.0.0', }, { capabilities: { tools: {}, }, }); // Define tool schemas const tools = [ { name: 'start_authentication', description: 'Initiates the DhanHQ authentication flow (Step 1). Returns a login URL that you must open in your browser to authenticate.', inputSchema: { type: 'object' as const, properties: {}, required: [], }, }, { name: 'get_login_instructions', description: 'Gets the login instructions and URL for Step 2 (browser-based login). You must complete this step manually by opening the URL in your browser.', inputSchema: { type: 'object' as const, properties: { consentAppId: { type: 'string' as const, description: 'The consentAppId from Step 1 (start_authentication). If not provided, the latest one from the current session will be used.', }, }, required: [], }, }, { name: 'complete_authentication', description: 'Completes the authentication flow (Step 3). Takes the tokenId from the redirect URL after browser login and exchanges it for an access token.', inputSchema: { type: 'object' as const, properties: { tokenId: { type: 'string' as const, description: 'The tokenId received in the redirect URL after browser login (Step 2).', }, }, required: ['tokenId'], }, }, { name: 'check_auth_status', description: 'Checks the current authentication status and returns details about the active access token if available.', inputSchema: { type: 'object' as const, properties: {}, required: [], }, }, { name: 'reset_authentication', description: 'Clears the current authentication state and resets the session.', inputSchema: { type: 'object' as const, properties: {}, required: [], }, }, { name: 'get_fund_limit', description: 'Retrieves account fund limit information including available balance, margins, collateral, and withdrawable balance. Requires authentication.', inputSchema: { type: 'object' as const, properties: {}, required: [], }, }, // ===== ORDER MANAGEMENT TOOLS ===== { name: 'place_order', description: 'Places a new order on DhanHQ. Requires authentication. Supports MARKET, LIMIT, STOP_LOSS, and STOP_LOSS_MARKET orders.', inputSchema: { type: 'object' as const, properties: { dhanClientId: { type: 'string', description: 'Your Dhan client ID' }, correlationId: { type: 'string', description: 'Unique correlation ID for order tracking' }, transactionType: { type: 'string', enum: ['BUY', 'SELL'] }, exchangeSegment: { type: 'string', description: 'e.g., NSE_EQ, BSE_EQ, NFO_FUT' }, productType: { type: 'string', enum: ['CNC', 'INTRADAY', 'MARGIN', 'MTF', 'CO', 'BO'], description: 'CNC=Delivery, INTRADAY=Intraday, MARGIN=Margin, CO=Cover Order, BO=Bracket Order', }, orderType: { type: 'string', enum: ['MARKET', 'LIMIT', 'STOP_LOSS', 'STOP_LOSS_MARKET'], }, validity: { type: 'string', enum: ['DAY', 'IOC'], description: 'DAY=Valid for today, IOC=Immediate or Cancel', }, securityId: { type: 'string', description: 'Security ID for the instrument' }, quantity: { type: 'number', description: 'Quantity to order' }, price: { type: 'number', description: 'Price per share (required for LIMIT/STOP_LOSS)' }, triggerPrice: { type: 'number', description: 'Trigger price (required for STOP_LOSS/STOP_LOSS_MARKET)' }, disclosedQuantity: { type: 'number', description: 'Quantity visible in order book (min 30% of quantity)' }, afterMarketOrder: { type: 'boolean', description: 'Flag for after-market orders' }, amoTime: { type: 'string', enum: ['PRE_OPEN', 'OPEN', 'OPEN_30', 'OPEN_60'], description: 'AMO timing' }, boProfitValue: { type: 'number', description: 'BO target price change' }, boStopLossValue: { type: 'number', description: 'BO stop loss price change' }, }, required: [ 'dhanClientId', 'correlationId', 'transactionType', 'exchangeSegment', 'productType', 'orderType', 'validity', 'securityId', 'quantity', 'price', ], }, }, { name: 'modify_order', description: 'Modifies a pending order. Can change price, quantity, order type, and other parameters. Requires authentication.', inputSchema: { type: 'object' as const, properties: { orderId: { type: 'string', description: 'Order ID to modify' }, dhanClientId: { type: 'string', description: 'Your Dhan client ID' }, orderType: { type: 'string', enum: ['MARKET', 'LIMIT', 'STOP_LOSS', 'STOP_LOSS_MARKET'], }, quantity: { type: 'number', description: 'New quantity' }, price: { type: 'number', description: 'New price (for LIMIT/STOP_LOSS)' }, triggerPrice: { type: 'number', description: 'New trigger price (for STOP_LOSS/STOP_LOSS_MARKET)' }, disclosedQuantity: { type: 'number', description: 'New disclosed quantity' }, }, required: ['orderId', 'dhanClientId', 'orderType'], }, }, { name: 'cancel_order', description: 'Cancels a pending order. Requires authentication and a valid order ID.', inputSchema: { type: 'object' as const, properties: { orderId: { type: 'string', description: 'Order ID to cancel' }, }, required: ['orderId'], }, }, { name: 'get_order_book', description: 'Retrieves all orders placed during the day with their current status. Requires authentication.', inputSchema: { type: 'object' as const, properties: {}, required: [], }, }, { name: 'get_order_by_id', description: 'Retrieves the details and status of a specific order by order ID. Requires authentication.', inputSchema: { type: 'object' as const, properties: { orderId: { type: 'string', description: 'Order ID to retrieve' }, }, required: ['orderId'], }, }, { name: 'get_order_by_correlation_id', description: 'Retrieves order status using the user-specified correlation ID. Requires authentication.', inputSchema: { type: 'object' as const, properties: { correlationId: { type: 'string', description: 'Correlation ID used when placing the order' }, }, required: ['correlationId'], }, }, { name: 'get_trade_book', description: 'Retrieves all trades executed during the day. Useful for tracking fills and execution prices. Requires authentication.', inputSchema: { type: 'object' as const, properties: {}, required: [], }, }, { name: 'get_order_trades', description: 'Retrieves all trades for a specific order. Useful for partial fills or bracket/cover orders. Requires authentication.', inputSchema: { type: 'object' as const, properties: { orderId: { type: 'string', description: 'Order ID to get trades for' }, }, required: ['orderId'], }, }, // ===== SUPER ORDER MANAGEMENT TOOLS ===== { name: 'place_super_order', description: 'Places a smart super order combining entry, target, and stop-loss legs. Supports trailing stop loss. Requires authentication.', inputSchema: { type: 'object' as const, properties: { dhanClientId: { type: 'string', description: 'Your Dhan client ID' }, correlationId: { type: 'string', description: 'Unique correlation ID for order tracking' }, transactionType: { type: 'string', enum: ['BUY', 'SELL'] }, exchangeSegment: { type: 'string', description: 'e.g., NSE_EQ, BSE_EQ' }, productType: { type: 'string', enum: ['CNC', 'INTRADAY', 'MARGIN', 'MTF', 'CO', 'BO'], }, orderType: { type: 'string', enum: ['MARKET', 'LIMIT', 'STOP_LOSS', 'STOP_LOSS_MARKET'], }, securityId: { type: 'string', description: 'Security ID for the instrument' }, quantity: { type: 'number', description: 'Quantity for entry leg' }, price: { type: 'number', description: 'Entry price' }, targetPrice: { type: 'number', description: 'Target price for profit booking' }, stopLossPrice: { type: 'number', description: 'Stop loss price' }, trailingJump: { type: 'number', description: 'Trailing stop loss jump amount (optional)', }, }, required: [ 'dhanClientId', 'correlationId', 'transactionType', 'exchangeSegment', 'productType', 'orderType', 'securityId', 'quantity', 'price', 'targetPrice', 'stopLossPrice', ], }, }, { name: 'modify_super_order', description: 'Modifies any leg of a pending or part-traded super order. Requires authentication.', inputSchema: { type: 'object' as const, properties: { orderId: { type: 'string', description: 'Super order ID to modify' }, dhanClientId: { type: 'string', description: 'Your Dhan client ID' }, legName: { type: 'string', enum: ['ENTRY_LEG', 'TARGET_LEG', 'STOP_LOSS_LEG'], description: 'Which leg to modify', }, orderType: { type: 'string', enum: ['MARKET', 'LIMIT', 'STOP', 'STOP_LIMIT'], }, quantity: { type: 'number', description: 'New quantity' }, price: { type: 'number', description: 'New price' }, targetPrice: { type: 'number', description: 'New target price (for entry leg)' }, stopLossPrice: { type: 'number', description: 'New stop loss price' }, trailingJump: { type: 'number', description: 'New trailing jump amount' }, }, required: ['orderId', 'dhanClientId', 'legName', 'orderType'], }, }, { name: 'cancel_super_order_leg', description: 'Cancels a specific leg of a super order (ENTRY_LEG, TARGET_LEG, or STOP_LOSS_LEG). Requires authentication.', inputSchema: { type: 'object' as const, properties: { orderId: { type: 'string', description: 'Super order ID' }, orderLeg: { type: 'string', enum: ['ENTRY_LEG', 'TARGET_LEG', 'STOP_LOSS_LEG'], description: 'Which leg to cancel', }, }, required: ['orderId', 'orderLeg'], }, }, { name: 'get_super_order_book', description: 'Retrieves all super orders placed during the day with nested leg details. Requires authentication.', inputSchema: { type: 'object' as const, properties: {}, required: [], }, }, ]; // Register list tools handler server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools, })); // Register call tool handler server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case 'start_authentication': { console.error('[Tool] Executing: start_authentication'); const result = await generateConsent(); return { content: [ { type: 'text' as const, text: JSON.stringify(result, null, 2), }, ], }; } case 'get_login_instructions': { console.error('[Tool] Executing: get_login_instructions'); const authState = getAuthState(); const consentAppId = (args as Record<string, unknown>).consentAppId || authState.consentAppId; if (!consentAppId) { throw new Error( 'No consentAppId available. Please run start_authentication first.' ); } const instructions = getStep2Instructions(consentAppId as string); return { content: [ { type: 'text' as const, text: JSON.stringify(instructions, null, 2), }, ], }; } case 'complete_authentication': { console.error('[Tool] Executing: complete_authentication'); const { tokenId } = args as Record<string, unknown>; if (!tokenId) { throw new Error('tokenId is required'); } const authToken = await consumeConsent(tokenId as string); return { content: [ { type: 'text' as const, text: JSON.stringify( { success: true, message: 'Authentication completed successfully', authToken, }, null, 2 ), }, ], }; } case 'check_auth_status': { console.error('[Tool] Executing: check_auth_status'); const authState = getAuthState(); const accessToken = getAccessToken(); const tokenValid = isTokenValid(); const status = { authenticated: !!authState.authToken, hasValidToken: tokenValid, accessToken: accessToken ? '***REDACTED***' : null, authState: { consentAppId: authState.consentAppId || null, consentAppStatus: authState.consentAppStatus || null, tokenId: authState.tokenId ? '***REDACTED***' : null, clientInfo: authState.authToken ? { dhanClientId: authState.authToken.dhanClientId, dhanClientName: authState.authToken.dhanClientName, dhanClientUcc: authState.authToken.dhanClientUcc, expiryTime: authState.authToken.expiryTime, givenPowerOfAttorney: authState.authToken.givenPowerOfAttorney, generatedAt: authState.authToken.generatedAt, } : null, }, }; return { content: [ { type: 'text' as const, text: JSON.stringify(status, null, 2), }, ], }; } case 'reset_authentication': { console.error('[Tool] Executing: reset_authentication'); resetAuthState(); return { content: [ { type: 'text' as const, text: JSON.stringify( { success: true, message: 'Authentication state has been reset', }, null, 2 ), }, ], }; } case 'get_fund_limit': { console.error('[Tool] Executing: get_fund_limit'); const fundLimit = await getFundLimit(); return { content: [ { type: 'text' as const, text: JSON.stringify(fundLimit, null, 2), }, ], }; } // ===== ORDER MANAGEMENT HANDLERS ===== case 'place_order': { console.error('[Tool] Executing: place_order'); const orderArgs = args as Record<string, unknown>; const result = await placeOrder({ dhanClientId: orderArgs.dhanClientId as string, correlationId: orderArgs.correlationId as string, transactionType: orderArgs.transactionType as 'BUY' | 'SELL', exchangeSegment: orderArgs.exchangeSegment as string, productType: orderArgs.productType as 'CNC' | 'INTRADAY' | 'MARGIN' | 'MTF' | 'CO' | 'BO', orderType: orderArgs.orderType as 'LIMIT' | 'MARKET' | 'STOP_LOSS' | 'STOP_LOSS_MARKET', validity: orderArgs.validity as 'DAY' | 'IOC', securityId: orderArgs.securityId as string, quantity: orderArgs.quantity as number, price: orderArgs.price as number | undefined, triggerPrice: orderArgs.triggerPrice as number | undefined, disclosedQuantity: orderArgs.disclosedQuantity as number | undefined, afterMarketOrder: orderArgs.afterMarketOrder as boolean | undefined, amoTime: orderArgs.amoTime as 'PRE_OPEN' | 'OPEN' | 'OPEN_30' | 'OPEN_60' | undefined, boProfitValue: orderArgs.boProfitValue as number | undefined, boStopLossValue: orderArgs.boStopLossValue as number | undefined, }); return { content: [ { type: 'text' as const, text: JSON.stringify(result, null, 2), }, ], }; } case 'modify_order': { console.error('[Tool] Executing: modify_order'); const modifyArgs = args as Record<string, unknown>; const result = await modifyOrder(modifyArgs.orderId as string, { dhanClientId: modifyArgs.dhanClientId as string, orderId: modifyArgs.orderId as string, orderType: modifyArgs.orderType as 'LIMIT' | 'MARKET' | 'STOP_LOSS' | 'STOP_LOSS_MARKET', quantity: modifyArgs.quantity as number | undefined, price: modifyArgs.price as number | undefined, triggerPrice: modifyArgs.triggerPrice as number | undefined, disclosedQuantity: modifyArgs.disclosedQuantity as number | undefined, }); return { content: [ { type: 'text' as const, text: JSON.stringify(result, null, 2), }, ], }; } case 'cancel_order': { console.error('[Tool] Executing: cancel_order'); const { orderId } = args as Record<string, unknown>; const result = await cancelOrder(orderId as string); return { content: [ { type: 'text' as const, text: JSON.stringify(result, null, 2), }, ], }; } case 'get_order_book': { console.error('[Tool] Executing: get_order_book'); const orders = await getOrderBook(); return { content: [ { type: 'text' as const, text: JSON.stringify(orders, null, 2), }, ], }; } case 'get_order_by_id': { console.error('[Tool] Executing: get_order_by_id'); const { orderId } = args as Record<string, unknown>; const order = await getOrderByID(orderId as string); return { content: [ { type: 'text' as const, text: JSON.stringify(order, null, 2), }, ], }; } case 'get_order_by_correlation_id': { console.error('[Tool] Executing: get_order_by_correlation_id'); const { correlationId } = args as Record<string, unknown>; const order = await getOrderByCorrelationID(correlationId as string); return { content: [ { type: 'text' as const, text: JSON.stringify(order, null, 2), }, ], }; } case 'get_trade_book': { console.error('[Tool] Executing: get_trade_book'); const trades = await getTradeBook(); return { content: [ { type: 'text' as const, text: JSON.stringify(trades, null, 2), }, ], }; } case 'get_order_trades': { console.error('[Tool] Executing: get_order_trades'); const { orderId } = args as Record<string, unknown>; const trades = await getOrderTrades(orderId as string); return { content: [ { type: 'text' as const, text: JSON.stringify(trades, null, 2), }, ], }; } // ===== SUPER ORDER MANAGEMENT HANDLERS ===== case 'place_super_order': { console.error('[Tool] Executing: place_super_order'); const soArgs = args as Record<string, unknown>; const result = await placeSuperOrder({ dhanClientId: soArgs.dhanClientId as string, correlationId: soArgs.correlationId as string, transactionType: soArgs.transactionType as 'BUY' | 'SELL', exchangeSegment: soArgs.exchangeSegment as string, productType: soArgs.productType as 'CNC' | 'INTRADAY' | 'MARGIN' | 'MTF' | 'CO' | 'BO', orderType: soArgs.orderType as 'LIMIT' | 'MARKET' | 'STOP_LOSS' | 'STOP_LOSS_MARKET', securityId: soArgs.securityId as string, quantity: soArgs.quantity as number, price: soArgs.price as number, targetPrice: soArgs.targetPrice as number, stopLossPrice: soArgs.stopLossPrice as number, trailingJump: soArgs.trailingJump as number | undefined, }); return { content: [ { type: 'text' as const, text: JSON.stringify(result, null, 2), }, ], }; } case 'modify_super_order': { console.error('[Tool] Executing: modify_super_order'); const modSOArgs = args as Record<string, unknown>; const result = await modifySuperOrder( modSOArgs.orderId as string, modSOArgs.legName as string, modSOArgs ); return { content: [ { type: 'text' as const, text: JSON.stringify(result, null, 2), }, ], }; } case 'cancel_super_order_leg': { console.error('[Tool] Executing: cancel_super_order_leg'); const { orderId, orderLeg } = args as Record<string, unknown>; const result = await cancelSuperOrderLeg( orderId as string, orderLeg as string ); return { content: [ { type: 'text' as const, text: JSON.stringify(result, null, 2), }, ], }; } case 'get_super_order_book': { console.error('[Tool] Executing: get_super_order_book'); const superOrders = await getSuperOrderBook(); return { content: [ { type: 'text' as const, text: JSON.stringify(superOrders, null, 2), }, ], }; } default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); console.error(`[Tool] Execution error: ${errorMessage}`); throw error; } }); // Start server with stdio transport async function main() { // Log to stderr to avoid interfering with stdio protocol const logToStderr = (...args: unknown[]) => { console.error(...args); }; logToStderr('🚀 Starting DhanHQ MCP Server...'); logToStderr('📋 Available tools:'); logToStderr(' 🔐 Authentication:'); logToStderr(' • start_authentication - Initiate auth flow (Step 1)'); logToStderr(' • get_login_instructions - Get browser login URL (Step 2)'); logToStderr(' • complete_authentication - Complete auth with tokenId (Step 3)'); logToStderr(' • check_auth_status - Check current auth status'); logToStderr(' • reset_authentication - Reset auth state'); logToStderr(' 💰 Account:'); logToStderr(' • get_fund_limit - Get account balance and margins'); logToStderr(' 📊 Order Management:'); logToStderr(' • place_order - Place a new order'); logToStderr(' • modify_order - Modify pending order'); logToStderr(' • cancel_order - Cancel pending order'); logToStderr(' • get_order_book - Get all orders for the day'); logToStderr(' • get_order_by_id - Get specific order details'); logToStderr(' • get_order_by_correlation_id - Get order by correlation ID'); logToStderr(' • get_trade_book - Get all trades for the day'); logToStderr(' • get_order_trades - Get trades for specific order'); logToStderr(' 🎯 Super Orders (Entry + Target + SL):'); logToStderr(' • place_super_order - Place smart super order'); logToStderr(' • modify_super_order - Modify super order leg'); logToStderr(' • cancel_super_order_leg - Cancel super order leg'); logToStderr(' • get_super_order_book - Get all super orders'); logToStderr('\n💡 To debug, run: npm run inspector\n'); const transport = new StdioServerTransport(); await server.connect(transport); logToStderr('✓ Server connected and ready for MCP Inspector'); } main().catch((error) => { console.error('Fatal error:', error); process.exit(1); });

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/harshitdynamite/DhanMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server