MCP Server
Model Context Protocol integration for AI assistants.
Overview
The MCP (Model Context Protocol) server allows AI assistants like Claude to interact with the TradingView Screener API through a standardized interface.
Installation
The MCP server is included with the main package:
bash
npm install tradingview-screenerStarting the Server
Standalone Mode
bash
npx tradingview-screener-mcpProgrammatic Usage
typescript
import { startMCPServer } from 'tradingview-screener/mcp';
await startMCPServer();Available Tools
The MCP server provides 5 tools for querying financial data:
1. discover_fields
Search for available fields/indicators.
json
{
"name": "discover_fields",
"description": "Search for available fields and indicators",
"inputSchema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Search query (e.g., 'price', 'volume', 'rsi')"
},
"assetType": {
"type": "string",
"enum": ["stock", "crypto", "forex", "bond", "futures"],
"description": "Asset type to search in"
}
},
"required": ["query"]
}
}Example Request
json
{
"name": "discover_fields",
"arguments": {
"query": "dividend",
"assetType": "stock"
}
}Example Response
json
{
"fields": [
{
"name": "DIVIDEND_YIELD_FWD",
"label": "Dividend yield (forward)",
"format": "percent"
},
{
"name": "DIVIDEND_PAYOUT_RATIO_TTM",
"label": "Dividend payout ratio",
"format": "percent"
}
],
"count": 2
}2. custom_query
Build flexible custom queries.
json
{
"name": "custom_query",
"description": "Build custom screener queries",
"inputSchema": {
"type": "object",
"properties": {
"assetType": {
"type": "string",
"enum": ["stock", "crypto", "forex", "bond", "futures"],
"description": "Asset type to query"
},
"filters": {
"type": "array",
"description": "Filter conditions",
"items": {
"type": "object",
"properties": {
"field": { "type": "string" },
"operator": { "type": "string" },
"value": {}
}
}
},
"fields": {
"type": "array",
"description": "Fields to return",
"items": { "type": "string" }
},
"sort": {
"type": "object",
"properties": {
"field": { "type": "string" },
"ascending": { "type": "boolean" }
}
},
"limit": {
"type": "number",
"description": "Maximum results"
}
},
"required": ["assetType"]
}
}Example Request
json
{
"name": "custom_query",
"arguments": {
"assetType": "stock",
"filters": [
{
"field": "MARKET_CAPITALIZATION",
"operator": "greater",
"value": 10000000000
},
{
"field": "DIVIDEND_YIELD_FWD",
"operator": "greater",
"value": 3
}
],
"fields": ["NAME", "PRICE", "DIVIDEND_YIELD_FWD", "MARKET_CAPITALIZATION"],
"sort": {
"field": "DIVIDEND_YIELD_FWD",
"ascending": false
},
"limit": 20
}
}3. search_stocks
Preset stock queries.
json
{
"name": "search_stocks",
"description": "Search stocks with common presets",
"inputSchema": {
"type": "object",
"properties": {
"preset": {
"type": "string",
"enum": ["large_cap", "dividend", "value", "growth", "top_gainers", "top_losers"],
"description": "Preset query type"
},
"limit": {
"type": "number",
"description": "Maximum results"
}
},
"required": ["preset"]
}
}Example Request
json
{
"name": "search_stocks",
"arguments": {
"preset": "dividend",
"limit": 10
}
}4. search_crypto
Search cryptocurrencies.
json
{
"name": "search_crypto",
"description": "Search cryptocurrencies",
"inputSchema": {
"type": "object",
"properties": {
"minMarketCap": {
"type": "number",
"description": "Minimum market cap in USD"
},
"minVolume24h": {
"type": "number",
"description": "Minimum 24h volume in USD"
},
"limit": {
"type": "number",
"description": "Maximum results"
}
}
}
}Example Request
json
{
"name": "search_crypto",
"arguments": {
"minMarketCap": 1000000000,
"minVolume24h": 100000000,
"limit": 20
}
}5. get_top_movers
Find biggest gainers/losers.
json
{
"name": "get_top_movers",
"description": "Get top gainers or losers",
"inputSchema": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["gainers", "losers"],
"description": "Type of movers"
},
"assetType": {
"type": "string",
"enum": ["stock", "crypto"],
"description": "Asset type"
},
"limit": {
"type": "number",
"description": "Maximum results"
}
},
"required": ["type"]
}
}Example Request
json
{
"name": "get_top_movers",
"arguments": {
"type": "gainers",
"assetType": "stock",
"limit": 10
}
}Server Configuration
Environment Variables
bash
# Server port (default: stdio)
MCP_PORT=3000
# Log level
MCP_LOG_LEVEL=info
# Cache settings
MCP_CACHE_ENABLED=true
MCP_CACHE_TTL=60000 # 1 minuteConfiguration File
Create mcp-config.json:
json
{
"server": {
"name": "tradingview-screener",
"version": "1.0.0"
},
"transport": {
"type": "stdio"
},
"cache": {
"enabled": true,
"ttl": 60000
},
"rateLimit": {
"enabled": true,
"maxRequests": 100,
"windowMs": 60000
}
}Server Implementation
Basic Server
typescript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { StockScreener, CryptoScreener, StockField } from 'tradingview-screener';
const server = new Server(
{
name: 'tradingview-screener',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
// Register tools
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'search_stocks',
description: 'Search for stocks',
inputSchema: {
type: 'object',
properties: {
preset: {
type: 'string',
enum: ['large_cap', 'dividend'],
},
},
},
},
],
}));
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (name === 'search_stocks') {
const screener = new StockScreener();
if (args.preset === 'large_cap') {
screener.where(StockField.MARKET_CAPITALIZATION.gt(10e9));
} else if (args.preset === 'dividend') {
screener.where(StockField.DIVIDEND_YIELD_FWD.gte(3));
}
const results = await screener.get();
return {
content: [
{
type: 'text',
text: JSON.stringify(results, null, 2),
},
],
};
}
throw new Error(`Unknown tool: ${name}`);
});
// Start server
const transport = new StdioServerTransport();
await server.connect(transport);With Error Handling
typescript
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
if (name === 'custom_query') {
const screener = createScreener(args);
const results = await screener.get();
return {
content: [
{
type: 'text',
text: JSON.stringify(results, null, 2),
},
],
};
}
throw new Error(`Unknown tool: ${name}`);
} catch (error) {
return {
content: [
{
type: 'text',
text: JSON.stringify({
error: error.message,
code: error.code,
}),
},
],
isError: true,
};
}
});Testing the Server
Using MCP CLI
bash
# Install MCP CLI
npm install -g @modelcontextprotocol/cli
# Test discover_fields
mcp call tradingview-screener-mcp discover_fields '{"query": "price", "assetType": "stock"}'
# Test custom_query
mcp call tradingview-screener-mcp custom_query '{
"assetType": "stock",
"filters": [
{"field": "PRICE", "operator": "greater", "value": 100}
],
"limit": 10
}'Using curl (HTTP mode)
bash
curl -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-d '{
"method": "tools/call",
"params": {
"name": "search_stocks",
"arguments": {
"preset": "large_cap",
"limit": 10
}
}
}'Monitoring
Logging
typescript
import { Logger } from '@modelcontextprotocol/sdk/shared/logger.js';
const logger = new Logger('tradingview-screener');
server.setRequestHandler(CallToolRequestSchema, async (request) => {
logger.info('Tool called', { name: request.params.name });
try {
// Handle request
const result = await handleTool(request);
logger.info('Tool completed', { name: request.params.name });
return result;
} catch (error) {
logger.error('Tool failed', { name: request.params.name, error });
throw error;
}
});Metrics
typescript
const metrics = {
calls: 0,
errors: 0,
totalTime: 0,
};
server.setRequestHandler(CallToolRequestSchema, async (request) => {
metrics.calls++;
const start = Date.now();
try {
const result = await handleTool(request);
metrics.totalTime += Date.now() - start;
return result;
} catch (error) {
metrics.errors++;
throw error;
}
});
// Log metrics every minute
setInterval(() => {
console.log('Metrics:', metrics);
}, 60000);Security
Authentication
typescript
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const apiKey = request.params.meta?.apiKey;
if (!isValidApiKey(apiKey)) {
throw new Error('Unauthorized');
}
// Process request
});Rate Limiting
typescript
const rateLimiter = new Map<string, number[]>();
function checkRateLimit(userId: string): boolean {
const now = Date.now();
const userRequests = rateLimiter.get(userId) || [];
// Remove requests older than 1 minute
const recentRequests = userRequests.filter(time => now - time < 60000);
if (recentRequests.length >= 100) {
return false; // Rate limit exceeded
}
recentRequests.push(now);
rateLimiter.set(userId, recentRequests);
return true;
}Best Practices
- Error Handling: Always return structured errors
- Validation: Validate all input parameters
- Caching: Cache results to reduce API calls
- Logging: Log all requests for debugging
- Rate Limiting: Protect against abuse
- Documentation: Keep tool schemas up-to-date
Next Steps
- Claude Desktop Integration - Using with Claude
- Custom Tools - Building custom tools
- API Reference - Complete API