Skip to main content
Glama

Tatum MCP Server

Official
gateway.ts15 kB
import { Tool } from '@modelcontextprotocol/sdk/types.js'; import { Gateway, GatewayChain, ExternalBlockchain, TatumApiResponse } from '../types.js'; import { chainProtocolMap, getChainProtocol, getChainApiConfig, ChainApiConfig } from '../utils/chains.js'; // Gateway tool definitions - these belong with the gateway service export const GATEWAY_TOOLS: Tool[] = [ { name: 'gateway_get_supported_chains', description: "Get a list of all supported blockchain networks available through Tatum's RPC gateways", inputSchema: { type: 'object', properties: {}, required: [] } }, { name: 'gateway_get_supported_methods', description: 'Get supported RPC methods for a specific blockchain chain', inputSchema: { type: 'object', properties: { chain: { type: 'string', description: "The blockchain network identifier. Examples: 'bitcoin-mainnet', 'ethereum-mainnet', 'litecoin-mainnet', 'polygon-mainnet', 'tron-mainnet', 'bsc-mainnet', 'solana-mainnet'. Use gateway_get_supported_chains to see all available networks.", examples: ['bitcoin-mainnet', 'ethereum-mainnet', 'litecoin-mainnet', 'polygon-mainnet', 'solana-mainnet'] } }, required: ['chain'] } }, { name: 'gateway_execute_rpc', description: "Execute blockchain RPC calls through Tatum's gateway infrastructure. Supports both JSON-RPC and REST API calls depending on the blockchain. For JSON-RPC methods, use simple method names like 'getblockcount' or 'eth_getBalance'. For REST calls, use full HTTP method and path like 'POST /getnowblock'. Parameters should be provided as an array for JSON-RPC or object for REST calls.", inputSchema: { type: 'object', properties: { chain: { type: 'string', description: "The blockchain network identifier. Examples: 'bitcoin-mainnet', 'ethereum-mainnet', 'litecoin-mainnet', 'polygon-mainnet', 'tron-mainnet', 'bsc-mainnet'. Use gateway_get_supported_chains to see all available networks.", examples: ['bitcoin-mainnet', 'ethereum-mainnet', 'litecoin-mainnet', 'polygon-mainnet'] }, method: { type: 'string', description: "The RPC method or REST endpoint to call. For JSON-RPC: use method names like 'getblockcount', 'getbestblockhash', 'eth_getBalance', 'eth_blockNumber'. For REST: use HTTP method + path like 'POST /getnowblock', 'GET /getinfo'. The gateway will automatically detect the protocol based on the format. Use gateway_get_supported_methods to see all available methods.", examples: ['getblockcount', 'getbestblockhash', 'eth_getBalance', 'POST /getnowblock'] }, params: { type: 'array', description: "Parameters for the RPC method. For JSON-RPC: provide as array (e.g., ['0x742d35Cc6074C4532895c05b22629ce5b3c28da4', 'latest'] for eth_getBalance). For REST: provide as array with single object element. Leave empty array [] if no parameters needed.", items: {}, default: [] } }, required: ['chain', 'method'] } } ]; export class GatewayService { private cachedGateways: Gateway[] = []; private cachedChains: string[] = []; private methodsCache: Map<string, any> = new Map(); private dataFetched = false; private readonly BLOCKCHAINS_URL = 'https://blockchains.tatum.io/blockchains.json'; private readonly apiKey: string; constructor(apiKey?: string) { this.apiKey = apiKey || process.env.TATUM_API_KEY || ''; } /** * Initialize the service by fetching blockchain data from external API */ public async initialize(): Promise<void> { if (this.dataFetched) { return; } try { console.error('Fetching blockchain data from Tatum API...'); const response = await fetch(this.BLOCKCHAINS_URL); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const externalBlockchains: ExternalBlockchain[] = await response.json(); // Transform external data to internal format this.cachedGateways = this.transformToGateways(externalBlockchains); this.cachedChains = this.extractChainNames(externalBlockchains); this.dataFetched = true; console.error(`Loaded ${this.cachedChains.length} networks from ${this.cachedGateways.length} blockchains`); } catch (error) { console.error('Failed to fetch blockchain data:', error instanceof Error ? error.message : 'Unknown error'); // Fallback to empty data this.cachedGateways = []; this.cachedChains = []; this.dataFetched = true; throw new Error(`Failed to initialize GatewayService: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Get all available blockchain networks */ public async getAvailableChains(): Promise<Gateway[]> { await this.ensureDataLoaded(); return [...this.cachedGateways]; // Return a copy to prevent mutation } /** * Get supported chains as string array */ public async getSupportedChains(): Promise<string[]> { await this.ensureDataLoaded(); return [...this.cachedChains]; // Return a copy to prevent mutation } /** * Get gateway URL for a specific chain */ public async getGatewayUrl(chainName: string): Promise<string | undefined> { await this.ensureDataLoaded(); for (const gateway of this.cachedGateways) { const chain = gateway.chains.find(c => c.chain === chainName || c.gatewayName === chainName); if (chain) { return chain.gatewayUrl; } } return undefined; } /** * Get available methods for a specific gateway */ public async getAvailableMethods(gatewayUrl: string): Promise<any> { // Check cache first if (this.methodsCache.has(gatewayUrl)) { return this.methodsCache.get(gatewayUrl); } try { const response = await fetch(`${gatewayUrl}/_methods`, { headers: { 'X-API-Key': this.apiKey, 'Content-Type': 'application/json' } }); if (!response.ok) { throw new Error(`Methods endpoint not available: ${response.status}`); } const methods = await response.json(); this.methodsCache.set(gatewayUrl, methods); return methods; } catch (error) { throw new Error(`Failed to fetch methods: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Execute RPC or REST request to gateway */ public async executeRequest({ gatewayUrl, method, body }: { gatewayUrl: string; method: string; body?: any; }): Promise<TatumApiResponse> { try { // Simple check: if method contains a space, assume it's a REST call (e.g., "POST /path"). // Otherwise, assume it's a JSON-RPC call. if (method.includes(' ')) { return await this.executeRestRequest(gatewayUrl, method, body); } else { return await this.executeJsonRpcRequest(gatewayUrl, method, body); } } catch (error: any) { return { error: error.message || 'Request failed', status: error.status || 500, statusText: error.statusText || 'Error' }; } } /** * Execute request with intelligent protocol detection based on chain */ public async executeChainRequest({ chainName, method, params = [] }: { chainName: string; method: string; params?: any[]; }): Promise<TatumApiResponse> { try { const gatewayUrl = await this.getGatewayUrl(chainName); if (!gatewayUrl) { return { error: `Gateway URL not found for chain: ${chainName}`, status: 404, statusText: 'Not Found' }; } const protocol = getChainProtocol(chainName); const apiConfig = getChainApiConfig(chainName); if (protocol === 'rest') { // For REST chains, enhance method handling with chain-specific configurations let restMethod = method; // Handle chain-specific method formatting if (!method.includes(' ') && !method.startsWith('GET') && !method.startsWith('POST')) { // Add base path prefix if configured if (apiConfig.basePathPrefix && !method.startsWith(apiConfig.basePathPrefix)) { restMethod = `GET ${apiConfig.basePathPrefix}/${method.replace(/^\//, '')}`; } else { restMethod = `GET /${method.replace(/^\//, '')}`; } } // Special handling for specific chains if (chainName.startsWith('kadena-')) { // Kadena requires specific path structure if (!method.includes('chainweb')) { const network = chainName.includes('testnet') ? 'testnet04' : 'mainnet01'; restMethod = `GET /chainweb/0.0/${network}/${method.replace(/^\//, '')}`; } } else if (chainName.startsWith('cardano-')) { // Cardano uses Blockfrost API structure if (!method.includes('api/v0')) { restMethod = `GET /api/v0/${method.replace(/^\//, '')}`; } } else if (chainName.startsWith('flow-')) { // Flow uses specific v1 API structure if (!method.includes('v1')) { restMethod = `GET /v1/${method.replace(/^\//, '')}`; } } return await this.executeRestRequestWithConfig(gatewayUrl, restMethod, params, apiConfig); } else { // For JSON-RPC chains like Ethereum return await this.executeJsonRpcRequest(gatewayUrl, method, params); } } catch (error: any) { return { error: error.message || 'Request failed', status: error.status || 500, statusText: error.statusText || 'Error' }; } } /** * Execute JSON-RPC request */ private async executeJsonRpcRequest(url: string, method: string, params: any[] = []): Promise<TatumApiResponse> { const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': this.apiKey }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }) }); const data = await response.json(); return { data, status: response.status, statusText: response.statusText }; } /** * Execute REST request with chain-specific configuration */ private async executeRestRequestWithConfig( baseUrl: string, methodPath: string, params?: any, apiConfig?: any ): Promise<TatumApiResponse> { const [httpMethod, restPath] = methodPath.includes(' ') ? methodPath.split(' ') : ['GET', methodPath]; let url = `${baseUrl}${restPath}`; // Handle query parameters for GET requests if (httpMethod.toUpperCase() === 'GET' && params && Array.isArray(params) && params.length > 0) { const queryParams = new URLSearchParams(); // If params is an array with a single object, use that object's properties as query params if (params.length === 1 && typeof params[0] === 'object' && params[0] !== null) { const paramObj = params[0]; Object.entries(paramObj).forEach(([key, value]) => { if (value !== undefined && value !== null) { queryParams.append(key, String(value)); } }); } // If there are query parameters, append them to the URL if (queryParams.toString()) { url += (url.includes('?') ? '&' : '?') + queryParams.toString(); } } const requestOptions: RequestInit = { method: httpMethod.toUpperCase(), headers: { 'Content-Type': 'application/json', 'X-API-Key': this.apiKey } }; // Add body for POST requests if (httpMethod.toUpperCase() === 'POST' && params) { requestOptions.body = JSON.stringify(params); } try { const response = await fetch(url, requestOptions); // Handle different response types let data: any; const contentType = response.headers.get('content-type'); try { if (contentType && contentType.includes('application/json')) { // Try to parse as JSON const text = await response.text(); if (text.trim()) { data = JSON.parse(text); } else { data = null; // Empty response } } else { // Non-JSON response, return as text data = await response.text(); } } catch (parseError) { // If JSON parsing fails, try to get the raw text try { data = await response.text(); } catch (textError) { data = { error: 'Failed to parse response', originalError: parseError, url: url, method: httpMethod }; } } // Enhanced error handling based on chain configuration if (!response.ok) { const errorMessage = data?.message || data?.error || `HTTP ${response.status}: ${response.statusText}`; return { error: errorMessage, status: response.status, statusText: response.statusText, data: data }; } return { data, status: response.status, statusText: response.statusText }; } catch (networkError: any) { return { error: `Network error: ${networkError.message}`, status: 500, statusText: 'Network Error', data: null }; } } /** * Execute REST request (legacy method for backward compatibility) */ private async executeRestRequest(baseUrl: string, methodPath: string, params?: any): Promise<TatumApiResponse> { return this.executeRestRequestWithConfig(baseUrl, methodPath, params); } /** * Transform external blockchain data to internal Gateway format */ private transformToGateways(externalBlockchains: ExternalBlockchain[]): Gateway[] { return externalBlockchains.map(blockchain => ({ name: blockchain.name, docs: blockchain.docs, chains: blockchain.chains.map(chain => ({ chain: chain.chain, gatewayName: chain.gatewayName, gatewayUrl: chain.gatewayUrl })) })); } /** * Extract all chain names from external blockchain data */ private extractChainNames(externalBlockchains: ExternalBlockchain[]): string[] { const chains: string[] = []; for (const blockchain of externalBlockchains) { for (const chain of blockchain.chains) { chains.push(chain.gatewayName); } } return chains; } /** * Ensure data is loaded before operations */ private async ensureDataLoaded(): Promise<void> { if (!this.dataFetched) { await this.initialize(); } } }

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/tatumio/tatum-mcp'

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