# Directory Structure
```
├── .changeset
│ ├── config.json
│ └── README.md
├── .github
│ └── workflows
│ └── ci.yml
├── .gitignore
├── bun.lock
├── CHANGELOG.md
├── CONTRIBUTING.md
├── docker-compose.yml
├── Dockerfile
├── eslint.config.js
├── LICENSE
├── package.json
├── README.md
├── scripts
│ └── inspector.js
├── src
│ ├── config.ts
│ ├── http
│ │ └── api.ts
│ ├── http-server.ts
│ ├── index.ts
│ └── tools
│ ├── billing.ts
│ ├── coupon.ts
│ ├── customer.ts
│ ├── index.ts
│ ├── pix.ts
│ └── withdraw.ts
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
bun-debug.log*
# Bun
bun.lockb
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# Node.js lock files (using Bun now)
package-lock.json
yarn.lock
pnpm-lock.yaml
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Build output (TypeScript compilation)
dist/
build/
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# vitepress build output
**/.vitepress/dist
# vitepress cache directory
**/.vitepress/cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
```
--------------------------------------------------------------------------------
/.changeset/README.md:
--------------------------------------------------------------------------------
```markdown
# Changesets
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
# 🥑 Abacate Pay MCP Server
Um servidor MCP (Model Context Protocol) para integração com a API do Abacate Pay, permitindo gerenciar pagamentos, clientes e cobranças diretamente através de assistentes de IA como Claude e Cursor.
## ✨ Multi-Tenancy
**🔐 Multi-tenancy ativo!** O servidor suporta múltiplos clientes simultaneamente. Cada requisição pode incluir sua própria chave de API, permitindo que diferentes usuários/organizações usem o mesmo servidor MCP com suas respectivas contas do Abacate Pay.
## O que você pode fazer
- 👥 **Gerenciar clientes**: Criar e listar clientes
- 💰 **Criar cobranças**: Links de pagamento e faturas
- 📱 **QR Codes PIX**: Pagamentos instantâneos
- 🎫 **Cupons de desconto**: Promoções e descontos
- 🔄 **Simular pagamentos**: Testar fluxos em desenvolvimento
## 🚀 Instalação e Configuração
### 1. Clone o repositório
```bash
git clone https://github.com/AbacatePay/abacatepay-mcp.git
cd abacatepay-mcp
bun install
```
**📋 Pré-requisitos:**
- [Bun](https://bun.sh) instalado (versão 1.0.0 ou superior)
### 2. Configure no Claude Desktop
**Modo Multi-Tenant (Recomendado):**
```json
{
"mcpServers": {
"abacate-pay": {
"command": "bun",
"args": ["/caminho/completo/para/abacatepay-mcp/src/index.ts"]
}
}
}
```
**Modo Legacy (Compatibilidade):**
```json
{
"mcpServers": {
"abacate-pay": {
"command": "bun",
"args": ["/caminho/completo/para/abacatepay-mcp/src/index.ts"],
"env": {
"ABACATE_PAY_API_KEY": "sua_api_key_aqui"
}
}
}
}
```
### 3. Configure no Cursor
**Modo Multi-Tenant (Recomendado):**
```json
{
"mcp.servers": {
"abacate-pay": {
"command": "bun",
"args": ["/caminho/completo/para/abacatepay-mcp/src/index.ts"]
}
}
}
```
**Modo Legacy (Compatibilidade):**
```json
{
"mcp.servers": {
"abacate-pay": {
"command": "bun",
"args": ["/caminho/completo/para/abacatepay-mcp/src/index.ts"],
"env": {
"ABACATE_PAY_API_KEY": "sua_api_key_aqui"
}
}
}
}
```
**⚠️ Importante**:
- Substitua `/caminho/completo/para/abacatepay-mcp/` pelo caminho real onde você clonou o repositório
- **Modo Multi-Tenant**: Não configure API key globalmente - ela será fornecida em cada requisição
- **Modo Legacy**: Configure a API key globalmente para compatibilidade com versões anteriores
## 🔑 Como obter sua API Key
1. Acesse [Abacate Pay](https://www.abacatepay.com)
2. Vá em **Integrar** → **API Keys**
3. Copie sua API Key
## 📝 Exemplos de Uso
### 🎯 Campanha com Influencer
```
"Eu contratei um influencer chamado Alex para divulgar meu negócio. Você pode criar um cupom com 15% de desconto usando o código ALEX15 que vale para até 100 usos? Preciso acompanhar o desempenho da campanha."
```
### 🔍 Investigação de Cobranças
```
"Tive uma cobrança estranha ontem que não reconheço. Você pode buscar todas as cobranças de ontem e me mostrar os detalhes para eu verificar o que pode ter acontecido?"
```
### 💼 Novo Cliente Corporativo
```
"Acabei de fechar um contrato com a empresa TechSolutions LTDA (CNPJ: 12.345.678/0001-90). Pode criar o cadastro deles com o email [email protected] e telefone (11) 3456-7890? Depois preciso gerar um QR Code PIX de R$ 10 para o pagamento."
```
## 🔐 Como Funciona
### Modo Multi-Tenant (Recomendado)
Cada ferramenta aceita um parâmetro `apiKey` opcional:
**Criar Cliente:**
```json
{
"apiKey": "sua_chave_api_aqui",
"name": "João Silva",
"cellphone": "(11) 99999-9999",
"email": "[email protected]",
"taxId": "123.456.789-01"
}
```
**Listar Clientes:**
```json
{
"apiKey": "sua_chave_api_aqui"
}
```
### Modo Legacy (Compatibilidade)
No modo legacy, as ferramentas funcionam sem o parâmetro `apiKey`:
**Criar Cliente:**
```json
{
"name": "João Silva",
"cellphone": "(11) 99999-9999",
"email": "[email protected]",
"taxId": "123.456.789-01"
}
```
### Vantagens
✅ **Múltiplos usuários**: Diferentes pessoas podem usar o mesmo servidor MCP
✅ **Isolamento de dados**: Cada API key acessa apenas seus próprios dados
✅ **Flexibilidade**: Pode usar com ou sem API key global
✅ **Segurança**: Credenciais não ficam armazenadas no servidor (modo multi-tenant)
✅ **Escalabilidade**: Fácil de compartilhar entre equipes
✅ **Compatibilidade**: Funciona com configurações existentes
## 🌐 Uso Remoto e Automação
### HTTP Server para Automação
Para usar com ferramentas como n8n, Zapier, ou aplicações customizadas:
```bash
# Start HTTP server
bun run start:http
# Ou com porta customizada
MCP_PORT=8080 bun run start:http
```
### Exemplo de Integração
**HTTP Request (n8n/Zapier):**
```json
POST https://your-server.com/mcp
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "createPixQrCode",
"arguments": {
"apiKey": "user_specific_key",
"amount": 1000,
"description": "Pagamento via automação"
}
}
}
```
**JavaScript/Node.js:**
```javascript
async function createCustomer(apiKey, customerData) {
const response = await fetch('https://your-mcp-server.com/mcp', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'tools/call',
params: {
name: 'createCustomer',
arguments: { apiKey, ...customerData }
}
})
});
return response.json();
}
```
## 🐛 Problemas Comuns
### Erro de API Key
```
❌ Falha ao criar cliente: HTTP 401: Unauthorized
```
**Solução**:
- **Modo Multi-Tenant**: Verifique se sua API Key está correta e foi fornecida no parâmetro `apiKey`
- **Modo Legacy**: Verifique se sua API Key está correta na configuração global
### MCP Server não conecta
**Solução**:
1. Verifique se o caminho para o arquivo está correto
2. Reinicie o Claude Desktop/Cursor após adicionar a configuração
3. Certifique-se de que o Bun está instalado e funcionando
### Erro de permissão
**Solução**: Certifique-se de que o Bun está instalado corretamente:
```bash
# Verificar instalação do Bun
bun --version
# Se necessário, instalar o Bun
curl -fsSL https://bun.sh/install | bash
```
## 🤝 Contribuição
Quer contribuir? Veja o [Guia de Contribuição](CONTRIBUTING.md).
## 📄 Licença
MIT - veja [LICENSE](LICENSE) para detalhes.
---
```
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
```markdown
# 🤝 Como Contribuir - Abacate Pay MCP 🥑
## ⚡ Setup Rápido
```bash
# Fork + clone + install
npm install
# 🎯 TESTE PRIMEIRO: Se o inspector abrir, está pronto!
npm run inspector
```
## 🔧 Configuração Local (Claude Desktop)
Para testar no Claude Desktop localmente, adicione ao `claude_desktop_config.json`:
```json
{
"mcpServers": {
"abacate-pay": {
"command": "node",
"args": ["/caminho/completo/para/abacatepay-mcp/dist/index.js"],
"env": {
"ABACATE_PAY_API_KEY": "sua_api_key"
}
}
}
}
```
## 📝 Fazendo Mudanças
Após implementar sua funcionalidade ou correção:
```bash
# 1. Crie um changeset para documentar sua mudança
npm run changeset
# O CLI vai perguntar:
# - Tipo de mudança (patch/minor/major)
# - Descrição da mudança para usuários finais
# 2. Commit tudo junto
git add .
git commit -m "feat: sua funcionalidade + changeset"
```
---
**Dúvidas?** Abra uma [issue](https://github.com/AbacatePay/abacatepay-mcp/issues) 🙋♂️
```
--------------------------------------------------------------------------------
/.changeset/config.json:
--------------------------------------------------------------------------------
```json
{
"$schema": "https://unpkg.com/@changesets/[email protected]/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": true,
"fixed": [],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}
```
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
FROM oven/bun:1-alpine
# Set resource constraints
ENV NODE_OPTIONS="--max-old-space-size=500"
WORKDIR /app
COPY package.json ./
RUN bun install
COPY . .
EXPOSE 3000
# Add resource limits via Docker run flags
# Use --cpus=1.0 and --memory=500m when running the container
CMD ["bun", "run", "src/http-server.ts"]
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
```
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
```yaml
version: '3.8'
services:
abacatepay-mcp:
build: .
ports:
- "${PORT:-3000}:3000"
environment:
- MCP_TRANSPORT=http
- MCP_PORT=3000
- ABACATE_PAY_API_KEY=${ABACATE_PAY_API_KEY}
restart: unless-stopped
volumes:
- .:/app
- /app/node_modules
deploy:
resources:
limits:
cpus: '1.0'
memory: 500M
reservations:
cpus: '0.5'
memory: 250M
```
--------------------------------------------------------------------------------
/src/tools/index.ts:
--------------------------------------------------------------------------------
```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { registerCustomerTools } from "./customer.js";
import { registerBillingTools } from "./billing.js";
import { registerPixTools } from "./pix.js";
import { registerCouponTools } from "./coupon.js";
import { registerWithdrawTools } from "./withdraw.js";
export function registerAllTools(server: McpServer) {
registerCustomerTools(server);
registerBillingTools(server);
registerPixTools(server);
registerCouponTools(server);
registerWithdrawTools(server);
}
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
import "./config.js";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { registerAllTools } from "./tools/index.js";
function createServer(): McpServer {
const server = new McpServer({
name: "abacatepay-mcp",
version: "1.0.0",
capabilities: {
resources: {},
tools: {},
},
});
registerAllTools(server);
return server;
}
async function main() {
try {
const server = createServer();
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Abacate Pay MCP Server rodando em stdio");
} catch (error) {
console.error("Erro fatal em main():", error);
process.exit(1);
}
}
main();
```
--------------------------------------------------------------------------------
/src/http/api.ts:
--------------------------------------------------------------------------------
```typescript
import { validateApiKey, ABACATE_PAY_API_BASE, USER_AGENT } from "../config.js";
export async function makeAbacatePayRequest<T = any>(
endpoint: string,
apiKey?: string,
options: RequestInit = {}
): Promise<T> {
const url = `${ABACATE_PAY_API_BASE}${endpoint}`;
// Use provided API key or fall back to global API key
const authKey = apiKey || validateApiKey();
const headers = {
'Authorization': `Bearer ${authKey}`,
'Content-Type': 'application/json',
'User-Agent': USER_AGENT,
...options.headers,
};
const response = await fetch(url, {
...options,
headers,
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`HTTP ${response.status}: ${errorText}`);
}
return response.json();
}
```
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
```markdown
# abacatepay-mcp
## 1.0.5
### Patch Changes
- 7502e34: Corrige problema de dependências faltando ao executar via NPX
- Implementa bundling com esbuild para incluir todas as dependências em um único arquivo
- Resolve erro "Cannot find package '@modelcontextprotocol/sdk'"
- Agora funciona perfeitamente com `npx abacatepay-mcp` sem necessidade de instalação manual
- Bundle otimizado de ~261KB incluindo todas as dependências necessárias
## 1.0.4
### Patch Changes
- bff91cb: Corrige compatibilidade com Node.js 18+
- Atualiza requisito do Node.js de >=22.16.0 para >=18.19.1
- Adiciona shebang correto no arquivo executável
- Melhora compatibilidade com a maioria das instalações do Node.js
## 1.0.2
### Patch Changes
- f020ec1: Migra sistema de release do release-it para Changesets. Isso melhora o processo de contribuição permitindo que contribuidores documentem suas próprias mudanças e facilita o gerenciamento de releases para mantenedores.
```
--------------------------------------------------------------------------------
/src/config.ts:
--------------------------------------------------------------------------------
```typescript
import minimist from "minimist";
const argv = minimist(process.argv.slice(2));
// API key can be provided globally (legacy mode) or per request (multi-tenant mode)
export const apiKey = argv.key || process.env.ABACATE_PAY_API_KEY;
export function validateApiKey(): string {
if (!apiKey) {
console.error(
"⚠️ Chave de API não fornecida globalmente.\n" +
"O servidor suporta multi-tenancy e requer a chave de API em cada requisição.\n" +
"Para usar o modo legacy (chave global), configure:\n" +
" 1. --key sua_chave_aqui\n" +
" 2. Variável de ambiente ABACATE_PAY_API_KEY"
);
}
return apiKey || '';
}
// Só valida se estamos executando como script principal
const isMainModule = process.argv[1] && (
process.argv[1].endsWith('index.js') ||
process.argv[1].endsWith('dist/index.js') ||
process.argv[1].includes('abacatepay-mcp')
);
if (isMainModule && !process.env.NODE_ENV?.includes('test')) {
console.error("✅ Abacate Pay MCP Server iniciado com sucesso");
if (apiKey) {
console.error("🔑 Modo legacy ativo - API key global configurada");
} else {
console.error("🔐 Multi-tenancy ativo - API keys devem ser fornecidas em cada requisição");
}
}
export const ABACATE_PAY_API_BASE = "https://api.abacatepay.com/v1";
export const USER_AGENT = "abacatepay-mcp/1.0";
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "abacatepay-mcp",
"version": "1.0.6",
"description": "🥑 Servidor MCP (Model Context Protocol) para integração com a API do Abacate Pay - Gerencie clientes, cobranças, PIX e cupons através de assistentes de IA",
"main": "src/index.ts",
"type": "module",
"scripts": {
"start": "bun run src/index.ts",
"start:http": "bun run src/http-server.ts",
"dev": "bun --watch src/index.ts",
"dev:http": "bun --watch src/http-server.ts",
"inspector": "bun run scripts/inspector.js",
"lint": "eslint src/ scripts/ --ext .ts,.js",
"lint:fix": "eslint src/ scripts/ --ext .ts,.js --fix",
"security": "bun audit && bun run lint",
"precommit": "bun run security",
"changeset": "changeset",
"changeset:version": "changeset version",
"changeset:publish": "changeset publish",
"changeset:status": "changeset status"
},
"files": [
"src/",
"README.md",
"LICENSE"
],
"bin": {
"abacatepay-mcp": "src/index.ts"
},
"repository": {
"type": "git",
"url": "git+https://github.com/AbacatePay/abacatepay-mcp.git"
},
"keywords": [
"mcp",
"model-context-protocol",
"abacate-pay",
"payment",
"pix",
"api",
"claude",
"ai",
"assistant",
"billing",
"coupon",
"brazil"
],
"author": "Vinícius Américo",
"license": "MIT",
"bugs": {
"url": "https://github.com/AbacatePay/abacatepay-mcp/issues"
},
"homepage": "https://github.com/AbacatePay/abacatepay-mcp#readme",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.12.0",
"express": "^5.1.0",
"minimist": "^1.2.8",
"zod": "^3.25.30"
},
"devDependencies": {
"@changesets/cli": "^2.29.4",
"@eslint/js": "^9.27.0",
"@types/express": "github:types/express",
"@types/inquirer": "^9.0.8",
"@types/minimist": "^1.2.5",
"@types/node": "^22.15.21",
"@typescript-eslint/eslint-plugin": "^8.33.0",
"@typescript-eslint/parser": "^8.33.0",
"eslint": "^9.27.0",
"eslint-plugin-security": "^3.0.1",
"inquirer": "^12.6.3",
"typescript": "^5.8.3"
},
"engines": {
"bun": ">=1.0.0"
}
}
```
--------------------------------------------------------------------------------
/scripts/inspector.js:
--------------------------------------------------------------------------------
```javascript
#!/usr/bin/env bun
import inquirer from 'inquirer';
import { spawn } from 'child_process';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const projectRoot = join(__dirname, '..');
console.log('🥑 Abacate Pay MCP Inspector');
console.log('================================\n');
async function getApiKey() {
// Verifica se já existe na variável de ambiente
if (process.env.ABACATE_PAY_API_KEY) {
console.log('✅ Chave de API encontrada na variável de ambiente');
return process.env.ABACATE_PAY_API_KEY;
}
// Pede a chave de forma interativa
const answers = await inquirer.prompt([
{
type: 'password',
name: 'apiKey',
message: '🔑 Digite sua chave de API do Abacate Pay:',
mask: '*',
validate: (input) => {
if (!input || input.trim() === '') {
return '❌ Chave de API é obrigatória';
}
return true;
}
}
]);
console.log('✅ Chave de API recebida');
return answers.apiKey;
}
async function startInspector(apiKey) {
console.log('🚀 Iniciando MCP Inspector...\n');
if (!process.env.ABACATE_PAY_API_KEY) {
console.log('💡 Dica: Para não precisar digitar a chave toda vez, você pode:');
console.log(' export ABACATE_PAY_API_KEY="sua_chave_aqui"\n');
}
const inspector = spawn('bunx', [
'@modelcontextprotocol/inspector',
'bun',
'src/index.ts'
], {
cwd: projectRoot,
stdio: 'inherit',
env: {
...process.env,
ABACATE_PAY_API_KEY: apiKey
}
});
inspector.on('close', (code) => {
if (code !== 0) {
console.log(`\n❌ MCP Inspector encerrado com código ${code}`);
}
});
}
async function main() {
try {
const apiKey = await getApiKey();
await startInspector(apiKey);
} catch (error) {
console.error('❌ Erro:', error.message);
process.exit(1);
}
}
main();
```
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
```javascript
import js from '@eslint/js';
import tseslint from '@typescript-eslint/eslint-plugin';
import tsparser from '@typescript-eslint/parser';
import security from 'eslint-plugin-security';
export default [
js.configs.recommended,
{
files: ['src/**/*.ts', 'scripts/**/*.js'],
languageOptions: {
parser: tsparser,
ecmaVersion: 'latest',
sourceType: 'module',
globals: {
console: 'readonly',
process: 'readonly',
Buffer: 'readonly',
__dirname: 'readonly',
__filename: 'readonly',
fetch: 'readonly',
RequestInit: 'readonly',
Response: 'readonly'
}
},
plugins: {
'@typescript-eslint': tseslint,
security: security
},
rules: {
// Regras de segurança - ERRO para problemas críticos
'security/detect-object-injection': 'error',
'security/detect-non-literal-regexp': 'error',
'security/detect-unsafe-regex': 'error',
'security/detect-buffer-noassert': 'error',
'security/detect-child-process': 'error', // Mudou para error
'security/detect-disable-mustache-escape': 'error',
'security/detect-eval-with-expression': 'error',
'security/detect-no-csrf-before-method-override': 'error',
'security/detect-non-literal-fs-filename': 'error', // Mudou para error
'security/detect-non-literal-require': 'error', // Mudou para error
'security/detect-possible-timing-attacks': 'error',
'security/detect-pseudoRandomBytes': 'error',
// Regras TypeScript - ERRO para problemas que quebram o código
'@typescript-eslint/no-explicit-any': 'warn', // Mantém warn para flexibilidade MCP
'@typescript-eslint/no-unused-vars': 'error',
// Regras gerais - ERRO para problemas que podem quebrar
'no-console': 'off', // Permitido para CLI tools
'no-process-exit': 'off', // Permitido para CLI tools
'no-undef': 'error',
'no-unused-vars': 'off', // Delegado para @typescript-eslint
'no-unreachable': 'error',
'no-constant-condition': 'error',
'no-dupe-keys': 'error',
'no-duplicate-case': 'error',
'no-empty': 'error',
'no-extra-boolean-cast': 'error',
'no-func-assign': 'error',
'no-invalid-regexp': 'error',
'no-irregular-whitespace': 'error',
'no-obj-calls': 'error',
'no-sparse-arrays': 'error',
'no-unexpected-multiline': 'error',
'use-isnan': 'error',
'valid-typeof': 'error'
}
},
{
ignores: ['dist/', 'node_modules/']
}
];
```
--------------------------------------------------------------------------------
/src/tools/withdraw.ts:
--------------------------------------------------------------------------------
```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { makeAbacatePayRequest } from "../http/api.js";
export function registerWithdrawTools(server: McpServer) {
server.tool(
"createWithdraw",
"Cria um novo saque para transferir valores da conta para uma chave PIX",
{
apiKey: z.string().optional().describe("Chave de API do Abacate Pay (opcional se configurada globalmente)"),
description: z.string().describe("Descrição do saque"),
externalId: z.string().describe("ID externo para identificação do saque"),
method: z.string().describe("Método de pagamento (ex: PIX)"),
amount: z.number().describe("Valor do saque em centavos"),
pix: z.object({
type: z.string().describe("Tipo da chave PIX (ex: CPF, CNPJ, EMAIL, PHONE, RANDOM)"),
key: z.string().describe("Chave PIX (CPF, CNPJ, email, telefone ou chave aleatória)")
}).describe("Dados da chave PIX para o saque")
},
async (params) => {
const { apiKey, description, externalId, method, amount, pix } = params as any;
try {
const requestBody = {
description,
externalId,
method,
amount,
pix
};
const response = await makeAbacatePayRequest<any>("/withdraw/create", apiKey, {
method: "POST",
body: JSON.stringify(requestBody)
});
const data = response.data;
const amountFormatted = (data.amount / 100).toFixed(2);
const feeFormatted = (data.platformFee / 100).toFixed(2);
return {
content: [
{
type: "text",
text: `💰 **Saque criado com sucesso!**\n\n` +
`📋 **Detalhes:**\n` +
`• ID: ${data.id}\n` +
`• Status: ${data.status}\n` +
`• Valor: R$ ${amountFormatted}\n` +
`• Taxa da Plataforma: R$ ${feeFormatted}\n` +
`• ID Externo: ${data.externalId}\n` +
`• Tipo: ${data.kind}\n` +
`• Criado em: ${new Date(data.createdAt).toLocaleString('pt-BR')}\n` +
`• Atualizado em: ${new Date(data.updatedAt).toLocaleString('pt-BR')}\n\n` +
`📄 **Comprovante:** ${data.receiptUrl}\n\n` +
`${data.devMode ? '⚠️ Modo de desenvolvimento ativo' : '✅ Modo de produção'}`
}
]
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Falha ao criar saque: ${error instanceof Error ? error.message : 'Erro desconhecido'}`
}
]
};
}
}
);
}
```
--------------------------------------------------------------------------------
/src/tools/customer.ts:
--------------------------------------------------------------------------------
```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { makeAbacatePayRequest } from "../http/api.js";
export function registerCustomerTools(server: McpServer) {
server.tool(
"createCustomer",
"Cria um novo cliente no Abacate Pay",
{
apiKey: z.string().optional().describe("Chave de API do Abacate Pay (opcional se configurada globalmente)"),
name: z.string().describe("Nome completo do cliente"),
cellphone: z.string().describe("Celular do cliente (ex: (11) 4002-8922)"),
email: z.string().email().describe("E-mail do cliente"),
taxId: z.string().describe("CPF ou CNPJ válido do cliente (ex: 123.456.789-01)")
},
async (params) => {
const { apiKey, name, cellphone, email, taxId } = params as any;
try {
const response = await makeAbacatePayRequest<any>("/customer/create", apiKey, {
method: "POST",
body: JSON.stringify({
name,
cellphone,
email,
taxId
})
});
return {
content: [
{
type: "text",
text: `Cliente criado com sucesso!\nID: ${response.data?.id || 'N/A'}\nNome: ${name}\nEmail: ${email}\nCelular: ${cellphone}\nCPF/CNPJ: ${taxId}`
}
]
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Falha ao criar cliente: ${error instanceof Error ? error.message : 'Erro desconhecido'}`
}
]
};
}
}
);
server.tool(
"listCustomers",
"Lista todos os clientes cadastrados no Abacate Pay",
{
apiKey: z.string().optional().describe("Chave de API do Abacate Pay (opcional se configurada globalmente)")
},
async (params) => {
const { apiKey } = params as any;
try {
const response = await makeAbacatePayRequest<any>("/customer/list", apiKey, {
method: "GET"
});
if (!response.data || response.data.length === 0) {
return {
content: [
{
type: "text",
text: "Nenhum cliente encontrado."
}
]
};
}
const customersList = response.data.map((customer: any, index: number) => {
const metadata = customer.metadata || {};
return `${index + 1}. ID: ${customer.id}
Nome: ${metadata.name || 'N/A'}
Email: ${metadata.email || 'N/A'}
Celular: ${metadata.cellphone || 'N/A'}
CPF/CNPJ: ${metadata.taxId || 'N/A'}`;
}).join('\n\n');
return {
content: [
{
type: "text",
text: `Lista de Clientes (${response.data.length} encontrado(s)):\n\n${customersList}`
}
]
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Falha ao listar clientes: ${error instanceof Error ? error.message : 'Erro desconhecido'}`
}
]
};
}
}
);
}
```
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
```yaml
name: CI - Verificações de Qualidade
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
ci:
name: ✅ Verificações Obrigatórias
runs-on: ubuntu-latest
steps:
- name: 📥 Checkout do código
uses: actions/checkout@v4
- name: 🟢 Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: latest
- name: 📦 Instalar dependências
run: bun install
- name: 🔍 Auditoria de segurança
run: |
echo "🔍 Verificando vulnerabilidades de segurança..."
bun audit
if [ $? -ne 0 ]; then
echo "❌ Vulnerabilidades de alta severidade encontradas!"
echo "💡 Execute 'bun audit' para verificar detalhes"
exit 1
fi
echo "✅ Nenhuma vulnerabilidade crítica encontrada"
- name: 🧹 Verificação de código (ESLint)
run: |
echo "🧹 Verificando qualidade do código..."
bun run lint
if [ $? -ne 0 ]; then
echo "❌ Problemas de qualidade de código encontrados!"
echo "💡 Execute 'bun run lint:fix' para corrigir automaticamente"
exit 1
fi
echo "✅ Código está seguindo os padrões de qualidade"
- name: 🔧 Verificação de tipos TypeScript
run: |
echo "🔧 Verificando tipos TypeScript..."
bunx tsc --noEmit
if [ $? -ne 0 ]; then
echo "❌ Erros de tipo encontrados!"
echo "💡 Corrija os erros de TypeScript antes de continuar"
exit 1
fi
echo "✅ Todos os tipos estão corretos"
- name: 🧪 Verificação do script inspector
run: |
echo "🧪 Testando script inspector..."
if [ ! -f "scripts/inspector.js" ]; then
echo "❌ Script inspector não encontrado!"
exit 1
fi
# Verifica sintaxe do script
bun scripts/inspector.js --help
if [ $? -ne 0 ]; then
echo "❌ Script inspector tem erros de sintaxe!"
exit 1
fi
echo "✅ Script inspector está funcionando corretamente"
- name: 🔍 Verificação de arquivos essenciais
run: |
echo "🔍 Verificando arquivos essenciais..."
# Verifica se os arquivos TypeScript principais existem
if [ ! -f "src/index.ts" ]; then
echo "❌ Arquivo principal não encontrado!"
exit 1
fi
# Verifica se o package.json tem os scripts necessários
if ! grep -q '"start"' package.json || ! grep -q '"inspector"' package.json; then
echo "❌ Scripts bun essenciais não encontrados!"
echo "📋 Scripts necessários: start, inspector"
exit 1
fi
echo "✅ Todos os arquivos essenciais estão presentes"
- name: 🎯 Teste de importação do MCP Server
run: |
echo "🎯 Testando se o MCP Server pode ser executado..."
bun run src/index.ts --help
if [ $? -ne 0 ]; then
echo "❌ Falha ao executar MCP Server!"
exit 1
fi
echo "✅ MCP Server executado com sucesso"
- name: 🎉 Sucesso
run: |
echo ""
echo "🎉 Todas as verificações passaram com sucesso!"
echo ""
echo "📋 Resumo das verificações:"
echo " ✅ Dependências instaladas"
echo " ✅ Segurança verificada"
echo " ✅ Qualidade de código aprovada"
echo " ✅ Tipos TypeScript corretos"
echo " ✅ Script inspector funcional"
echo " ✅ Arquivos essenciais presentes"
echo " ✅ MCP Server executável"
echo ""
echo "🚀 Pronto para merge! 🥑"
```
--------------------------------------------------------------------------------
/src/http-server.ts:
--------------------------------------------------------------------------------
```typescript
import "./config.js";
import express from "express";
import type { Request, Response } from "express";
import { randomUUID } from "node:crypto";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
import { registerAllTools } from "./tools/index.js";
function createServer(): McpServer {
const server = new McpServer({
name: "abacatepay-mcp",
version: "1.0.0",
capabilities: {
resources: {},
tools: {},
},
});
registerAllTools(server);
return server;
}
async function main() {
try {
const app = express();
app.use((express as any).json({ limit: '10mb' }));
// Map to store transports by session ID
const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};
// Handle POST requests for client-to-server communication
app.post('/mcp', async (req: Request, res: Response) => {
// Check for existing session ID
const sessionId = req.headers['mcp-session-id'] as string | undefined;
let transport: StreamableHTTPServerTransport;
if (sessionId && transports[sessionId]) {
// Reuse existing transport
transport = transports[sessionId];
} else if (!sessionId && isInitializeRequest((req as any).body)) {
// New initialization request
transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
onsessioninitialized: (sessionId) => {
// Store the transport by session ID
transports[sessionId] = transport;
},
// DNS rebinding protection is disabled by default for backwards compatibility. If you are running this server
// locally, make sure to set:
// enableDnsRebindingProtection: true,
// allowedHosts: ['127.0.0.1'],
});
// Clean up transport when closed
transport.onclose = () => {
if (transport.sessionId) {
delete transports[transport.sessionId];
}
};
const server = createServer();
// Connect to the MCP server
await server.connect(transport);
} else {
// Invalid request
res.status(400).json({
jsonrpc: '2.0',
error: {
code: -32000,
message: 'Bad Request: No valid session ID provided',
},
id: null,
});
return;
}
// Handle the request
await transport.handleRequest(req, res, (req as any).body);
});
// Reusable handler for GET and DELETE requests
const handleSessionRequest = async (req: Request, res: Response) => {
const sessionId = req.headers['mcp-session-id'] as string | undefined;
if (!sessionId || !transports[sessionId]) {
res.status(400).send('Invalid or missing session ID');
return;
}
const transport = transports[sessionId];
await transport.handleRequest(req, res);
};
// Handle GET requests for server-to-client notifications via SSE
app.get('/mcp', handleSessionRequest);
// Handle DELETE requests for session termination
app.delete('/mcp', handleSessionRequest);
// Get port from environment or use default
const port = parseInt(process.env.MCP_PORT || process.env.PORT || "3000");
app.listen(port, () => {
console.error(`🚀 Abacate Pay MCP Server rodando em http://localhost:${port}`);
console.error(`📡 Endpoint: http://localhost:${port}/mcp`);
console.error(`📖 Documentação: http://localhost:${port}/mcp/schema`);
});
// Graceful shutdown
process.on('SIGINT', async () => {
console.error('\n🛑 Encerrando servidor...');
// Close all active transports
for (const transport of Object.values(transports)) {
await transport.close();
}
process.exit(0);
});
} catch (error) {
console.error("Erro fatal em main():", error);
process.exit(1);
}
}
main();
```
--------------------------------------------------------------------------------
/src/tools/coupon.ts:
--------------------------------------------------------------------------------
```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { makeAbacatePayRequest } from "../http/api.js";
export function registerCouponTools(server: McpServer) {
server.tool(
"createCoupon",
"Cria um novo cupom de desconto",
{
apiKey: z.string().optional().describe("Chave de API do Abacate Pay (opcional se configurada globalmente)"),
code: z.string().describe("Código único do cupom (ex: DESCONTO20)"),
discountKind: z.enum(["PERCENTAGE", "FIXED"]).describe("Tipo de desconto: PERCENTAGE (porcentagem) ou FIXED (valor fixo)"),
discount: z.number().describe("Valor do desconto (em % para PERCENTAGE ou em centavos para FIXED)"),
notes: z.string().optional().describe("Descrição sobre o cupom"),
maxRedeems: z.number().default(-1).describe("Quantidade máxima de usos (-1 para ilimitado)"),
metadata: z.object({}).optional().describe("Metadados adicionais do cupom")
},
async (params) => {
const { apiKey, code, discountKind, discount, notes, maxRedeems, metadata } = params as any;
try {
const requestBody: any = {
code,
discountKind,
discount,
maxRedeems
};
// Adicionar campos opcionais apenas se fornecidos
if (notes) {
requestBody.notes = notes;
}
if (metadata) {
requestBody.metadata = metadata;
}
const response = await makeAbacatePayRequest<any>("/coupon/create", apiKey, {
method: "POST",
body: JSON.stringify(requestBody)
});
const data = response.data;
const discountText = data.discountKind === 'PERCENTAGE'
? `${data.discount}%`
: `R$ ${(data.discount / 100).toFixed(2)}`;
const maxRedeemsText = data.maxRedeems === -1
? 'Ilimitado'
: `${data.maxRedeems} vezes`;
return {
content: [
{
type: "text",
text: `🎫 **Cupom criado com sucesso!**\n\n` +
`📋 **Detalhes do Cupom:**\n` +
`• Código: **${data.code}**\n` +
`• Desconto: ${discountText} (${data.discountKind === 'PERCENTAGE' ? 'Porcentagem' : 'Valor Fixo'})\n` +
`• Usos Máximos: ${maxRedeemsText}\n` +
`• Descrição: ${data.notes || 'Sem descrição'}\n\n` +
`✅ O cupom **${data.code}** está pronto para ser usado pelos seus clientes!`
}
]
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Falha ao criar cupom: ${error instanceof Error ? error.message : 'Erro desconhecido'}`
}
]
};
}
}
);
server.tool(
"listCoupons",
"Lista todos os cupons de desconto criados no Abacate Pay",
{
apiKey: z.string().optional().describe("Chave de API do Abacate Pay (opcional se configurada globalmente)")
},
async (params) => {
const { apiKey } = params as any;
try {
const response = await makeAbacatePayRequest<any>("/coupon/list", apiKey, {
method: "GET"
});
if (!response.data || response.data.length === 0) {
return {
content: [
{
type: "text",
text: "Nenhum cupom encontrado."
}
]
};
}
const couponsList = response.data.map((coupon: any, index: number) => {
const discountText = coupon.discountKind === 'PERCENTAGE'
? `${coupon.discount}%`
: `R$ ${(coupon.discount / 100).toFixed(2)}`;
const maxRedeemsText = coupon.maxRedeems === -1
? 'Ilimitado'
: `${coupon.maxRedeems} vezes`;
return `${index + 1}. 🎫 **${coupon.code}**
💰 Desconto: ${discountText} (${coupon.discountKind === 'PERCENTAGE' ? 'Porcentagem' : 'Valor Fixo'})
🔄 Usos: ${maxRedeemsText}
📝 Descrição: ${coupon.notes || 'Sem descrição'}`;
}).join('\n\n');
return {
content: [
{
type: "text",
text: `🎫 **Lista de Cupons** (${response.data.length} encontrado(s)):\n\n${couponsList}`
}
]
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Falha ao listar cupons: ${error instanceof Error ? error.message : 'Erro desconhecido'}`
}
]
};
}
}
);
}
```
--------------------------------------------------------------------------------
/src/tools/billing.ts:
--------------------------------------------------------------------------------
```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { makeAbacatePayRequest } from "../http/api.js";
export function registerBillingTools(server: McpServer) {
server.tool(
"createBilling",
"Cria uma nova cobrança no Abacate Pay",
{
apiKey: z.string().optional().describe("Chave de API do Abacate Pay (opcional se configurada globalmente)"),
frequency: z.enum(["ONE_TIME", "MULTIPLE_PAYMENTS"]).default("ONE_TIME").describe("Tipo de frequência da cobrança"),
methods: z.array(z.enum(["PIX"])).default(["PIX"]).describe("Métodos de pagamento (atualmente apenas PIX)"),
products: z.array(z.object({
externalId: z.string().describe("ID externo do produto"),
name: z.string().describe("Nome do produto"),
description: z.string().describe("Descrição do produto"),
quantity: z.number().describe("Quantidade do produto"),
price: z.number().describe("Preço unitário em centavos")
})).describe("Lista de produtos"),
returnUrl: z.string().url().describe("URL para redirecionar caso o cliente clique em 'Voltar'"),
completionUrl: z.string().url().describe("URL para redirecionar quando o pagamento for concluído"),
customerId: z.string().optional().describe("ID de um cliente já cadastrado (opcional)")
},
async (params) => {
const { apiKey, frequency, methods, products, returnUrl, completionUrl, customerId } = params as any;
try {
const requestBody: any = {
frequency,
methods,
products,
returnUrl,
completionUrl
};
if (customerId) {
requestBody.customerId = customerId;
}
const response = await makeAbacatePayRequest<any>("/billing/create", apiKey, {
method: "POST",
body: JSON.stringify(requestBody)
});
const data = response.data;
const totalAmount = (data.amount / 100).toFixed(2);
return {
content: [
{
type: "text",
text: `Cobrança criada com sucesso! 🎉\n\n` +
`📋 **Detalhes da Cobrança:**\n` +
`• ID: ${data.id}\n` +
`• Status: ${data.status}\n` +
`• Valor Total: R$ ${totalAmount}\n` +
`• Frequência: ${data.frequency}\n` +
`• Métodos: ${data.methods.join(', ')}\n` +
`• Produtos: ${data.products.length} item(s)\n\n` +
`🔗 **Link de Pagamento:**\n${data.url}\n\n` +
`${data.devMode ? '⚠️ Modo de desenvolvimento ativo' : '✅ Modo de produção'}`
}
]
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Falha ao criar cobrança: ${error instanceof Error ? error.message : 'Erro desconhecido'}`
}
]
};
}
}
);
server.tool(
"listBillings",
"Lista todas as cobranças criadas no Abacate Pay",
{
apiKey: z.string().optional().describe("Chave de API do Abacate Pay (opcional se configurada globalmente)")
},
async (params) => {
const { apiKey } = params as any;
try {
const response = await makeAbacatePayRequest<any>("/billing/list", apiKey, {
method: "GET"
});
if (!response.data || response.data.length === 0) {
return {
content: [
{
type: "text",
text: "Nenhuma cobrança encontrada."
}
]
};
}
const billingsList = response.data.map((billing: any, index: number) => {
const amount = (billing.amount / 100).toFixed(2);
const customer = billing.customer?.metadata;
const statusEmojis: Record<string, string> = {
'PENDING': '⏳',
'PAID': '✅',
'EXPIRED': '⏰',
'CANCELLED': '❌',
'REFUNDED': '↩️'
};
const statusEmoji = statusEmojis[billing.status] || '❓';
return `${index + 1}. ${statusEmoji} **${billing.status}** - R$ ${amount}
📋 ID: ${billing.id}
🔗 URL: ${billing.url}
📦 Produtos: ${billing.products.length} item(s)
👤 Cliente: ${customer?.name || 'N/A'}
📅 Frequência: ${billing.frequency}
💳 Métodos: ${billing.methods.join(', ')}
${billing.devMode ? '⚠️ Modo Dev' : '✅ Produção'}`;
}).join('\n\n');
return {
content: [
{
type: "text",
text: `📋 **Lista de Cobranças** (${response.data.length} encontrada(s)):\n\n${billingsList}`
}
]
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Falha ao listar cobranças: ${error instanceof Error ? error.message : 'Erro desconhecido'}`
}
]
};
}
}
);
}
```
--------------------------------------------------------------------------------
/src/tools/pix.ts:
--------------------------------------------------------------------------------
```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { makeAbacatePayRequest } from "../http/api.js";
export function registerPixTools(server: McpServer) {
server.tool(
"createPixQrCode",
"Cria um QR Code PIX para pagamento direto",
{
apiKey: z.string().optional().describe("Chave de API do Abacate Pay (opcional se configurada globalmente)"),
amount: z.number().describe("Valor da cobrança em centavos"),
expiresIn: z.number().optional().describe("Tempo de expiração em segundos (opcional)"),
description: z.string().max(140).optional().describe("Mensagem que aparecerá no pagamento PIX (máx 140 caracteres)"),
customer: z.object({
name: z.string().describe("Nome completo do cliente"),
cellphone: z.string().describe("Celular do cliente"),
email: z.string().email().describe("E-mail do cliente"),
taxId: z.string().describe("CPF ou CNPJ do cliente")
}).optional().describe("Dados do cliente (opcional)")
},
async (params) => {
const { apiKey, amount, expiresIn, description, customer } = params as any;
try {
const requestBody: any = {
amount
};
if (expiresIn) {
requestBody.expiresIn = expiresIn;
}
if (description) {
requestBody.description = description;
}
if (customer) {
requestBody.customer = customer;
}
const response = await makeAbacatePayRequest<any>("/pixQrCode/create", apiKey, {
method: "POST",
body: JSON.stringify(requestBody)
});
const data = response.data;
const amountFormatted = (data.amount / 100).toFixed(2);
const feeFormatted = (data.platformFee / 100).toFixed(2);
return {
content: [
{
type: "text",
text: `🎯 **QR Code PIX criado com sucesso!**\n\n` +
`📋 **Detalhes:**\n` +
`• ID: ${data.id}\n` +
`• Valor: R$ ${amountFormatted}\n` +
`• Status: ${data.status}\n` +
`• Taxa da Plataforma: R$ ${feeFormatted}\n` +
`• Criado em: ${new Date(data.createdAt).toLocaleString('pt-BR')}\n` +
`• Expira em: ${new Date(data.expiresAt).toLocaleString('pt-BR')}\n\n` +
`📱 **Código PIX (Copia e Cola):**\n\`\`\`\n${data.brCode}\n\`\`\`\n\n` +
`🖼️ **QR Code Base64:**\n${data.brCodeBase64.substring(0, 100)}...\n\n` +
`${data.devMode ? '⚠️ Modo de desenvolvimento ativo' : '✅ Modo de produção'}`
}
]
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Falha ao criar QR Code PIX: ${error instanceof Error ? error.message : 'Erro desconhecido'}`
}
]
};
}
}
);
server.tool(
"simulatePixPayment",
"Simula o pagamento de um QR Code PIX (apenas em modo desenvolvimento)",
{
apiKey: z.string().optional().describe("Chave de API do Abacate Pay (opcional se configurada globalmente)"),
id: z.string().describe("ID do QR Code PIX para simular o pagamento"),
metadata: z.object({}).optional().describe("Metadados opcionais para a requisição")
},
async (params) => {
const { apiKey, id, metadata } = params as any;
try {
const requestBody: any = {};
if (metadata) {
requestBody.metadata = metadata;
}
const response = await makeAbacatePayRequest<any>(`/pixQrCode/simulate-payment?id=${id}`, apiKey, {
method: "POST",
body: JSON.stringify(requestBody)
});
const data = response.data;
const amountFormatted = (data.amount / 100).toFixed(2);
const feeFormatted = (data.platformFee / 100).toFixed(2);
const statusEmojis: Record<string, string> = {
'PENDING': '⏳',
'PAID': '✅',
'EXPIRED': '⏰',
'CANCELLED': '❌',
'REFUNDED': '↩️'
};
const statusEmoji = statusEmojis[data.status] || '❓';
return {
content: [
{
type: "text",
text: `${statusEmoji} **Pagamento PIX simulado com sucesso!**\n\n` +
`📋 **Detalhes do Pagamento:**\n` +
`• ID: ${data.id}\n` +
`• Status: ${data.status}\n` +
`• Valor: R$ ${amountFormatted}\n` +
`• Taxa da Plataforma: R$ ${feeFormatted}\n` +
`• Criado em: ${new Date(data.createdAt).toLocaleString('pt-BR')}\n` +
`• Atualizado em: ${new Date(data.updatedAt).toLocaleString('pt-BR')}\n` +
`• Expira em: ${new Date(data.expiresAt).toLocaleString('pt-BR')}\n\n` +
`${data.devMode ? '⚠️ Simulação realizada em modo de desenvolvimento' : '✅ Pagamento em produção'}\n\n` +
`🎉 O pagamento foi processado com sucesso!`
}
]
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Falha ao simular pagamento PIX: ${error instanceof Error ? error.message : 'Erro desconhecido'}`
}
]
};
}
}
);
server.tool(
"checkPixStatus",
"Verifica o status de um QR Code PIX",
{
apiKey: z.string().optional().describe("Chave de API do Abacate Pay (opcional se configurada globalmente)"),
id: z.string().describe("ID do QR Code PIX para verificar o status")
},
async (params) => {
const { apiKey, id } = params as any;
try {
const response = await makeAbacatePayRequest<any>(`/pixQrCode/check?id=${id}`, apiKey, {
method: "GET"
});
const data = response.data;
const statusEmojis: Record<string, string> = {
'PENDING': '⏳',
'PAID': '✅',
'EXPIRED': '⏰',
'CANCELLED': '❌',
'REFUNDED': '↩️'
};
const statusEmoji = statusEmojis[data.status] || '❓';
return {
content: [
{
type: "text",
text: `${statusEmoji} **Status do QR Code PIX**\n\n` +
`📋 **ID**: ${id}\n` +
`📊 **Status**: ${data.status}\n` +
`⏰ **Expira em**: ${new Date(data.expiresAt).toLocaleString('pt-BR')}\n\n` +
`${data.status === 'PENDING' ? '⏳ Aguardando pagamento...' :
data.status === 'PAID' ? '✅ Pagamento confirmado!' :
data.status === 'EXPIRED' ? '⏰ QR Code expirado' :
data.status === 'CANCELLED' ? '❌ QR Code cancelado' :
data.status === 'REFUNDED' ? '↩️ Pagamento estornado' :
'❓ Status desconhecido'}`
}
]
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Falha ao verificar status do PIX: ${error instanceof Error ? error.message : 'Erro desconhecido'}`
}
]
};
}
}
);
}
```