DEV Community

Cover image for Como construir um conector MCP com TypeScript e Binance usando arquitetura hexagonal 🛠️
Cláudio Filipe Lima Rapôso
Cláudio Filipe Lima Rapôso

Posted on

Como construir um conector MCP com TypeScript e Binance usando arquitetura hexagonal 🛠️

Modelos de linguagem como o GPT e o Claude estão cada vez mais presentes no desenvolvimento de aplicações. Mas para eles serem úteis de verdade, precisam se conectar com dados e serviços externos. A solução? Model Context Protocol (MCP).

Neste artigo, vou mostrar como criar um conector para o MCP usando TypeScript, Zod e a API da Binance com uma arquitetura limpa e desacoplada: hexagonal (ports & adapters). Mesmo se você está começando, vai conseguir entender e aplicar no seu projeto 🚀


🤖 O que é o MCP?

O MCP é um protocolo criado pela Anthropic que padroniza como LLMs (Large Language Models) interagem com ferramentas externas. Ele é baseado em JSON-RPC 2.0 e permite integrar APIs de forma modular e segura com agentes inteligentes.

Com o MCP, conseguimos transformar nossas APIs em "ferramentas inteligentes" que os modelos de linguagem entendem e podem usar.

Documentação oficial: https://modelcontextprotocol.io


🧱 Estrutura do Projeto com Arquitetura Hexagonal

A arquitetura hexagonal separa bem as responsabilidades entre domínio (regras de negócio), adaptação de dados e a interface com o usuário ou sistema externo. Isso facilita testes, manutenção e expansão no futuro.

Veja a estrutura sugerida:

/src
  /domain
    /ports
      BinanceDataPort.ts       # contrato da API externa
    MarketAnalysis.ts          # lógica de negócio
  /adapters
    /secondary
      BinanceServiceAdapter.ts # conexão com Binance
    /primary
      stdio-server.ts          # servidor MCP com STDIO
  /application
    /methods
      binanceMethods.ts        # métodos MCP usando o domínio
Enter fullscreen mode Exit fullscreen mode

📈 Lógica de Negócio

Essa é a parte do sistema que calcula se o mercado está em tendência de alta, baixa ou neutra usando médias móveis. É a regra de negócio central.

// domain/MarketAnalysis.ts
import { BinanceDataPort } from './ports/BinanceDataPort';

export class SimpleMarketAnalysis {
  constructor(private readonly binancePort: BinanceDataPort) {}

  async analyze(symbol: string, interval: string, limit: number) {
    const prices = await this.binancePort.getCandles(symbol, interval, limit);
    const shortSMA = this.calculateSMA(prices, 10);
    const longSMA = this.calculateSMA(prices, 20);
    const signal = shortSMA > longSMA ? 'buy' : shortSMA < longSMA ? 'sell' : 'neutral';
    return { shortSMA, longSMA, signal };
  }

  private calculateSMA(prices: number[], period: number): number {
    if (prices.length < period) throw new Error('Not enough data');
    return prices.slice(-period).reduce((a, b) => a + b, 0) / period;
  }
}
Enter fullscreen mode Exit fullscreen mode

Aqui usamos o padrão de injeção de dependência com a interface BinanceDataPort, que permite mudar a fonte dos dados facilmente (ex: usar dados mockados em testes).


🔌 Porta de Comunicação

A porta define um contrato. Ou seja, o que o domínio espera receber de fora. Assim, podemos mudar a implementação sem afetar as regras de negócio.

// domain/ports/BinanceDataPort.ts
export interface BinanceDataPort {
  getCandles(symbol: string, interval: string, limit: number): Promise<number[]>;
  getTickerPrice(symbol: string): Promise<{ symbol: string; price: string }>;
  getOrderBook(symbol: string, limit?: number): Promise<{ lastUpdateId: number; bids: [string, string][]; asks: [string, string][] }>;
  getRecentTrades(symbol: string, limit?: number): Promise<any[]>;
}
Enter fullscreen mode Exit fullscreen mode

🌐 Adapter para a Binance

O adapter é a implementação concreta da BinanceDataPort que de fato acessa os dados na API pública da Binance. Aqui usamos axios para as requisições HTTP.

// adapters/secondary/BinanceServiceAdapter.ts
import axios from 'axios';
import { BinanceDataPort } from '../../domain/ports/BinanceDataPort';

export class BinanceServiceAdapter implements BinanceDataPort {
  async getCandles(symbol: string, interval: string, limit: number) {
    const response = await axios.get('https://api.binance.com/api/v3/klines', { params: { symbol, interval, limit } });
    return response.data.map((c: any[]) => parseFloat(c[4]));
  }
  async getTickerPrice(symbol: string) {
    return (await axios.get('https://api.binance.com/api/v3/ticker/price', { params: { symbol } })).data;
  }
  async getOrderBook(symbol: string, limit = 100) {
    return (await axios.get('https://api.binance.com/api/v3/depth', { params: { symbol, limit } })).data;
  }
  async getRecentTrades(symbol: string, limit = 50) {
    return (await axios.get('https://api.binance.com/api/v3/trades', { params: { symbol, limit } })).data;
  }
}
Enter fullscreen mode Exit fullscreen mode

🧠 Método MCP com Zod

Aqui orquestramos o método que será exposto pelo MCP, validando os parâmetros de entrada com zod e reaproveitando o domínio.

// application/methods/binanceMethods.ts
import { z } from 'zod';
import { ToolMethodDefinition } from '@modelcontextprotocol/sdk';
import { BinanceServiceAdapter } from '../../adapters/secondary/BinanceServiceAdapter';
import { SimpleMarketAnalysis } from '../../domain/MarketAnalysis';

const adapter = new BinanceServiceAdapter();
const analysis = new SimpleMarketAnalysis(adapter);

export const analyzeTrend: ToolMethodDefinition = {
  description: 'Realiza uma análise de tendência com médias móveis.',
  inputSchema: z.object({
    symbol: z.string(),
    interval: z.string().default('1h'),
    limit: z.number().min(20).max(1000).default(100),
  }),
  outputSchema: z.object({
    shortSMA: z.number(),
    longSMA: z.number(),
    signal: z.enum(['buy', 'sell', 'neutral']),
  }),
  handler: async ({ symbol, interval, limit }) => {
    return analysis.analyze(symbol, interval, limit);
  }
};
Enter fullscreen mode Exit fullscreen mode

🚀 Servidor MCP com STDIO

Esse é o ponto de entrada que expõe os métodos para o LLM via o protocolo MCP. Ele escuta via entrada padrão (stdin/stdout), ideal para rodar em agentes locais.

// adapters/primary/stdio-server.ts
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { analyzeTrend } from '../../application/methods/binanceMethods';

const server = new McpServer({ name: 'Binance MCP', version: '1.0.0' });

server.tool('analyzeTrend', analyzeTrend.inputSchema, async (params) => {
  const result = await analyzeTrend.handler(params);
  return {
    content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
  };
});

server.resource(
  'trend-resource',
  new ResourceTemplate('trend://{symbol}', { list: undefined }),
  async (uri, { symbol }) => {
    const result = await analyzeTrend.handler({ symbol, interval: '1h', limit: 100 });
    return {
      contents: [{
        uri: uri.href,
        text: `Trend: ${result.signal}\nSMA10: ${result.shortSMA}\nSMA20: ${result.longSMA}`,
      }]
    };
  }
);

await server.connect(new StdioServerTransport());
Enter fullscreen mode Exit fullscreen mode

✅ Conclusão

O MCP é uma ferramenta poderosa para conectar LLMs com dados reais. Com TypeScript, Zod e arquitetura hexagonal, você consegue criar integrações robustas e escaláveis com APIs como a da Binance.

Esse é só o começo. Dá pra incluir indicadores técnicos, suporte a múltiplos ativos, autenticação com API key e muito mais!

Se curtiu o conteúdo, salva esse post, comenta com dúvidas ou sugestões, e segue pra mais artigos de back-end com IA. 🚀

Top comments (0)