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
📈 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;
}
}
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[]>;
}
🌐 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;
}
}
🧠 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);
}
};
🚀 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());
✅ 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)