#
tokens: 14777/50000 23/23 files
lines: off (toggle) GitHub
raw markdown copy
# 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'}`
            }
          ]
        };
      }
    }
  );
} 
```