Modelos de linguagem como o GPT e o Claude estão revolucionando a forma como interagimos com sistemas digitais. Porém, para que eles sejam realmente úteis em contextos mais práticos, precisamos conectá-los a dados e serviços externos. É aí que entra o Model Context Protocol (MCP), criado pela Anthropic.
Neste artigo, vamos mostrar como construir um conector MCP com TypeScript e a API pública da Binance, aplicando arquitetura hexagonal, validação com Zod e boas práticas modernas de desenvolvimento. Bora ver como isso tudo se conecta? 🚀
O que é o Model Context Protocol (MCP)? 🤖
O MCP é um padrão aberto de comunicação baseado em JSON-RPC 2.0 que permite que LLMs interajam com ferramentas externas de forma segura, padronizada e reutilizável.
Imagine poder disponibilizar sua API como um "plugin nativo" para um modelo de IA — sem reinventar a roda. Com o MCP, isso fica simples.
Acesse a documentação oficial aqui: modelcontextprotocol.io
Arquitetura hexagonal na prática 🧱
Utilizamos a arquitetura hexagonal (também conhecida como ports & adapters) para garantir desacoplamento e testabilidade. Essa abordagem organiza nosso código em:
- Domínio: regras de negócio puras, como cálculos de tendência.
- Ports: contratos que o domínio espera para acessar dados externos.
- Adapters: implementações que atendem essas portas, como chamadas à API da Binance.
- Aplicação: onde orquestramos os métodos MCP.
- Interface Primária: o servidor MCP que expõe os métodos.
Estrutura sugerida:
/src
/domain
/ports
BinanceDataPort.ts
MarketAnalysis.ts
/adapters
/secondary
BinanceServiceAdapter.ts
/primary
server.ts
/application
/methods
binanceMethods.ts
Implementando o domínio 📈
// 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 for SMA');
return prices.slice(-period).reduce((acc, val) => acc + val, 0) / period;
}
}
Criando a port (interface) 🔌
// 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<Array<{
id: number;
price: string;
qty: string;
quoteQty: string;
time: number;
isBuyerMaker: boolean;
isBestMatch: boolean;
}>>;
}
Adapter: conectando com a Binance 🌐
// 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) {
const response = await axios.get('https://api.binance.com/api/v3/ticker/price', {
params: { symbol },
});
return response.data;
}
async getOrderBook(symbol: string, limit = 100) {
const response = await axios.get('https://api.binance.com/api/v3/depth', {
params: { symbol, limit },
});
return response.data;
}
async getRecentTrades(symbol: string, limit = 50) {
const response = await axios.get('https://api.binance.com/api/v3/trades', {
params: { symbol, limit },
});
return response.data;
}
}
Expondo via MCP com Zod 🧠
// 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 binanceAdapter = new BinanceServiceAdapter();
const analyzer = new SimpleMarketAnalysis(binanceAdapter);
export const analyzeTrend: ToolMethodDefinition = {
description: 'Performs a basic trend analysis using moving averages.',
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 analyzer.analyze(symbol, interval, limit);
},
};
Subindo o servidor MCP 🚀
// adapters/primary/server.ts
import { createToolServer } from '@modelcontextprotocol/sdk';
import { analyzeTrend } from '../../application/methods/binanceMethods';
createToolServer({
tools: [
{
name: 'binance',
description: 'Binance API wrapper with trend analysis.',
methods: { analyzeTrend },
},
],
port: 3000,
});
Conclusão ✅
Com o MCP e a arquitetura hexagonal, você consegue criar integrações limpas, reutilizáveis e prontas pra escalar. O segredo está em separar responsabilidades e confiar nas boas práticas.
Esse conector com a Binance é só o começo: dá pra plugar qualquer API, incluir mais ferramentas ou até montar uma suíte de análise com múltiplos indicadores.
Se curtiu o conteúdo, deixa aquele like, salva pra consultar depois e compartilha com a galera dev. Até a próxima! 👋
SOCIAL SHARE CARD GENERATOR