Skip to content

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-screener

Starting the Server ​

Standalone Mode ​

bash
npx tradingview-screener-mcp

Programmatic 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 minute

Configuration 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 ​

  1. Error Handling: Always return structured errors
  2. Validation: Validate all input parameters
  3. Caching: Cache results to reduce API calls
  4. Logging: Log all requests for debugging
  5. Rate Limiting: Protect against abuse
  6. Documentation: Keep tool schemas up-to-date

Next Steps ​

Released under the MIT License.