#
tokens: 19635/50000 23/23 files
lines: on (toggle) GitHub
raw markdown copy reset
# 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:
--------------------------------------------------------------------------------

```
  1 | # Logs
  2 | logs
  3 | *.log
  4 | npm-debug.log*
  5 | yarn-debug.log*
  6 | yarn-error.log*
  7 | lerna-debug.log*
  8 | .pnpm-debug.log*
  9 | bun-debug.log*
 10 | 
 11 | # Bun
 12 | bun.lockb
 13 | 
 14 | # Diagnostic reports (https://nodejs.org/api/report.html)
 15 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
 16 | 
 17 | # Runtime data
 18 | pids
 19 | *.pid
 20 | *.seed
 21 | *.pid.lock
 22 | 
 23 | # Directory for instrumented libs generated by jscoverage/JSCover
 24 | lib-cov
 25 | 
 26 | # Coverage directory used by tools like istanbul
 27 | coverage
 28 | *.lcov
 29 | 
 30 | # nyc test coverage
 31 | .nyc_output
 32 | 
 33 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
 34 | .grunt
 35 | 
 36 | # Bower dependency directory (https://bower.io/)
 37 | bower_components
 38 | 
 39 | # node-waf configuration
 40 | .lock-wscript
 41 | 
 42 | # Compiled binary addons (https://nodejs.org/api/addons.html)
 43 | build/Release
 44 | 
 45 | # Dependency directories
 46 | node_modules/
 47 | jspm_packages/
 48 | 
 49 | # Snowpack dependency directory (https://snowpack.dev/)
 50 | web_modules/
 51 | 
 52 | # TypeScript cache
 53 | *.tsbuildinfo
 54 | 
 55 | # Optional npm cache directory
 56 | .npm
 57 | 
 58 | # Optional eslint cache
 59 | .eslintcache
 60 | 
 61 | # Optional stylelint cache
 62 | .stylelintcache
 63 | 
 64 | # Microbundle cache
 65 | .rpt2_cache/
 66 | .rts2_cache_cjs/
 67 | .rts2_cache_es/
 68 | .rts2_cache_umd/
 69 | 
 70 | # Optional REPL history
 71 | .node_repl_history
 72 | 
 73 | # Output of 'npm pack'
 74 | *.tgz
 75 | 
 76 | # Yarn Integrity file
 77 | .yarn-integrity
 78 | 
 79 | # Node.js lock files (using Bun now)
 80 | package-lock.json
 81 | yarn.lock
 82 | pnpm-lock.yaml
 83 | 
 84 | # dotenv environment variable files
 85 | .env
 86 | .env.development.local
 87 | .env.test.local
 88 | .env.production.local
 89 | .env.local
 90 | 
 91 | # parcel-bundler cache (https://parceljs.org/)
 92 | .cache
 93 | .parcel-cache
 94 | 
 95 | # Next.js build output
 96 | .next
 97 | out
 98 | 
 99 | # Nuxt.js build / generate output
100 | .nuxt
101 | dist
102 | 
103 | # Build output (TypeScript compilation)
104 | dist/
105 | build/
106 | 
107 | # Gatsby files
108 | .cache/
109 | # Comment in the public line in if your project uses Gatsby and not Next.js
110 | # https://nextjs.org/blog/next-9-1#public-directory-support
111 | # public
112 | 
113 | # vuepress build output
114 | .vuepress/dist
115 | 
116 | # vuepress v2.x temp and cache directory
117 | .temp
118 | .cache
119 | 
120 | # vitepress build output
121 | **/.vitepress/dist
122 | 
123 | # vitepress cache directory
124 | **/.vitepress/cache
125 | 
126 | # Docusaurus cache and generated files
127 | .docusaurus
128 | 
129 | # Serverless directories
130 | .serverless/
131 | 
132 | # FuseBox cache
133 | .fusebox/
134 | 
135 | # DynamoDB Local files
136 | .dynamodb/
137 | 
138 | # TernJS port file
139 | .tern-port
140 | 
141 | # Stores VSCode versions used for testing VSCode extensions
142 | .vscode-test
143 | 
144 | # yarn v2
145 | .yarn/cache
146 | .yarn/unplugged
147 | .yarn/build-state.yml
148 | .yarn/install-state.gz
149 | .pnp.*
150 | 
```

--------------------------------------------------------------------------------
/.changeset/README.md:
--------------------------------------------------------------------------------

```markdown
1 | # Changesets
2 | 
3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
4 | with multi-package repos, or single-package repos to help you version and publish your code. You can
5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets)
6 | 
7 | We have a quick list of common questions to get you started engaging with this project in
8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
9 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # 🥑 Abacate Pay MCP Server
  2 | 
  3 | 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.
  4 | 
  5 | ## ✨ Multi-Tenancy
  6 | 
  7 | **🔐 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.
  8 | 
  9 | ## O que você pode fazer
 10 | 
 11 | - 👥 **Gerenciar clientes**: Criar e listar clientes
 12 | - 💰 **Criar cobranças**: Links de pagamento e faturas  
 13 | - 📱 **QR Codes PIX**: Pagamentos instantâneos
 14 | - 🎫 **Cupons de desconto**: Promoções e descontos
 15 | - 🔄 **Simular pagamentos**: Testar fluxos em desenvolvimento
 16 | 
 17 | ## 🚀 Instalação e Configuração
 18 | 
 19 | ### 1. Clone o repositório
 20 | 
 21 | ```bash
 22 | git clone https://github.com/AbacatePay/abacatepay-mcp.git
 23 | cd abacatepay-mcp
 24 | bun install
 25 | ```
 26 | 
 27 | **📋 Pré-requisitos:**
 28 | - [Bun](https://bun.sh) instalado (versão 1.0.0 ou superior)
 29 | 
 30 | ### 2. Configure no Claude Desktop
 31 | 
 32 | **Modo Multi-Tenant (Recomendado):**
 33 | ```json
 34 | {
 35 |   "mcpServers": {
 36 |     "abacate-pay": {
 37 |       "command": "bun",
 38 |       "args": ["/caminho/completo/para/abacatepay-mcp/src/index.ts"]
 39 |     }
 40 |   }
 41 | }
 42 | ```
 43 | 
 44 | **Modo Legacy (Compatibilidade):**
 45 | ```json
 46 | {
 47 |   "mcpServers": {
 48 |     "abacate-pay": {
 49 |       "command": "bun",
 50 |       "args": ["/caminho/completo/para/abacatepay-mcp/src/index.ts"],
 51 |       "env": {
 52 |         "ABACATE_PAY_API_KEY": "sua_api_key_aqui"
 53 |       }
 54 |     }
 55 |   }
 56 | }
 57 | ```
 58 | 
 59 | ### 3. Configure no Cursor
 60 | 
 61 | **Modo Multi-Tenant (Recomendado):**
 62 | ```json
 63 | {
 64 |   "mcp.servers": {
 65 |     "abacate-pay": {
 66 |       "command": "bun",
 67 |       "args": ["/caminho/completo/para/abacatepay-mcp/src/index.ts"]
 68 |     }
 69 |   }
 70 | }
 71 | ```
 72 | 
 73 | **Modo Legacy (Compatibilidade):**
 74 | ```json
 75 | {
 76 |   "mcp.servers": {
 77 |     "abacate-pay": {
 78 |       "command": "bun",
 79 |       "args": ["/caminho/completo/para/abacatepay-mcp/src/index.ts"],
 80 |       "env": {
 81 |         "ABACATE_PAY_API_KEY": "sua_api_key_aqui"
 82 |       }
 83 |     }
 84 |   }
 85 | }
 86 | ```
 87 | 
 88 | **⚠️ Importante**: 
 89 | - Substitua `/caminho/completo/para/abacatepay-mcp/` pelo caminho real onde você clonou o repositório
 90 | - **Modo Multi-Tenant**: Não configure API key globalmente - ela será fornecida em cada requisição
 91 | - **Modo Legacy**: Configure a API key globalmente para compatibilidade com versões anteriores
 92 | 
 93 | ## 🔑 Como obter sua API Key
 94 | 
 95 | 1. Acesse [Abacate Pay](https://www.abacatepay.com)
 96 | 2. Vá em **Integrar** → **API Keys**
 97 | 3. Copie sua API Key
 98 | 
 99 | ## 📝 Exemplos de Uso
100 | 
101 | ### 🎯 Campanha com Influencer
102 | ```
103 | "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."
104 | ```
105 | 
106 | ### 🔍 Investigação de Cobranças
107 | ```
108 | "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?"
109 | ```
110 | 
111 | ### 💼 Novo Cliente Corporativo  
112 | ```
113 | "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."
114 | ```
115 | 
116 | ## 🔐 Como Funciona
117 | 
118 | ### Modo Multi-Tenant (Recomendado)
119 | 
120 | Cada ferramenta aceita um parâmetro `apiKey` opcional:
121 | 
122 | **Criar Cliente:**
123 | ```json
124 | {
125 |   "apiKey": "sua_chave_api_aqui",
126 |   "name": "João Silva",
127 |   "cellphone": "(11) 99999-9999",
128 |   "email": "[email protected]",
129 |   "taxId": "123.456.789-01"
130 | }
131 | ```
132 | 
133 | **Listar Clientes:**
134 | ```json
135 | {
136 |   "apiKey": "sua_chave_api_aqui"
137 | }
138 | ```
139 | 
140 | ### Modo Legacy (Compatibilidade)
141 | 
142 | No modo legacy, as ferramentas funcionam sem o parâmetro `apiKey`:
143 | 
144 | **Criar Cliente:**
145 | ```json
146 | {
147 |   "name": "João Silva",
148 |   "cellphone": "(11) 99999-9999",
149 |   "email": "[email protected]",
150 |   "taxId": "123.456.789-01"
151 | }
152 | ```
153 | 
154 | ### Vantagens
155 | 
156 | ✅ **Múltiplos usuários**: Diferentes pessoas podem usar o mesmo servidor MCP  
157 | ✅ **Isolamento de dados**: Cada API key acessa apenas seus próprios dados  
158 | ✅ **Flexibilidade**: Pode usar com ou sem API key global  
159 | ✅ **Segurança**: Credenciais não ficam armazenadas no servidor (modo multi-tenant)  
160 | ✅ **Escalabilidade**: Fácil de compartilhar entre equipes  
161 | ✅ **Compatibilidade**: Funciona com configurações existentes  
162 | 
163 | ## 🌐 Uso Remoto e Automação
164 | 
165 | ### HTTP Server para Automação
166 | 
167 | Para usar com ferramentas como n8n, Zapier, ou aplicações customizadas:
168 | 
169 | ```bash
170 | # Start HTTP server
171 | bun run start:http
172 | 
173 | # Ou com porta customizada
174 | MCP_PORT=8080 bun run start:http
175 | ```
176 | 
177 | ### Exemplo de Integração
178 | 
179 | **HTTP Request (n8n/Zapier):**
180 | ```json
181 | POST https://your-server.com/mcp
182 | {
183 |   "jsonrpc": "2.0",
184 |   "id": 1,
185 |   "method": "tools/call",
186 |   "params": {
187 |     "name": "createPixQrCode",
188 |     "arguments": {
189 |       "apiKey": "user_specific_key",
190 |       "amount": 1000,
191 |       "description": "Pagamento via automação"
192 |     }
193 |   }
194 | }
195 | ```
196 | 
197 | **JavaScript/Node.js:**
198 | ```javascript
199 | async function createCustomer(apiKey, customerData) {
200 |   const response = await fetch('https://your-mcp-server.com/mcp', {
201 |     method: 'POST',
202 |     headers: { 'Content-Type': 'application/json' },
203 |     body: JSON.stringify({
204 |       jsonrpc: '2.0',
205 |       id: 1,
206 |       method: 'tools/call',
207 |       params: {
208 |         name: 'createCustomer',
209 |         arguments: { apiKey, ...customerData }
210 |       }
211 |     })
212 |   });
213 |   return response.json();
214 | }
215 | ```
216 | 
217 | ## 🐛 Problemas Comuns
218 | 
219 | ### Erro de API Key
220 | ```
221 | ❌ Falha ao criar cliente: HTTP 401: Unauthorized
222 | ```
223 | **Solução**: 
224 | - **Modo Multi-Tenant**: Verifique se sua API Key está correta e foi fornecida no parâmetro `apiKey`
225 | - **Modo Legacy**: Verifique se sua API Key está correta na configuração global
226 | 
227 | ### MCP Server não conecta
228 | **Solução**: 
229 | 1. Verifique se o caminho para o arquivo está correto
230 | 2. Reinicie o Claude Desktop/Cursor após adicionar a configuração
231 | 3. Certifique-se de que o Bun está instalado e funcionando
232 | 
233 | ### Erro de permissão
234 | **Solução**: Certifique-se de que o Bun está instalado corretamente:
235 | ```bash
236 | # Verificar instalação do Bun
237 | bun --version
238 | 
239 | # Se necessário, instalar o Bun
240 | curl -fsSL https://bun.sh/install | bash
241 | ```
242 | 
243 | ## 🤝 Contribuição
244 | 
245 | Quer contribuir? Veja o [Guia de Contribuição](CONTRIBUTING.md).
246 | 
247 | ## 📄 Licença
248 | 
249 | MIT - veja [LICENSE](LICENSE) para detalhes.
250 | 
251 | ---
252 | 
253 | 
254 | 
255 | 
```

--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------

```markdown
 1 | # 🤝 Como Contribuir - Abacate Pay MCP 🥑
 2 | 
 3 | ## ⚡ Setup Rápido
 4 | 
 5 | ```bash
 6 | # Fork + clone + install
 7 | npm install
 8 | 
 9 | # 🎯 TESTE PRIMEIRO: Se o inspector abrir, está pronto!
10 | npm run inspector
11 | ```
12 | 
13 | ## 🔧 Configuração Local (Claude Desktop)
14 | 
15 | Para testar no Claude Desktop localmente, adicione ao `claude_desktop_config.json`:
16 | 
17 | ```json
18 | {
19 |   "mcpServers": {
20 |     "abacate-pay": {
21 |       "command": "node",
22 |       "args": ["/caminho/completo/para/abacatepay-mcp/dist/index.js"],
23 |       "env": {
24 |         "ABACATE_PAY_API_KEY": "sua_api_key"
25 |       }
26 |     }
27 |   }
28 | }
29 | ```
30 | 
31 | ## 📝 Fazendo Mudanças
32 | 
33 | Após implementar sua funcionalidade ou correção:
34 | 
35 | ```bash
36 | # 1. Crie um changeset para documentar sua mudança
37 | npm run changeset
38 | 
39 | # O CLI vai perguntar:
40 | # - Tipo de mudança (patch/minor/major)
41 | # - Descrição da mudança para usuários finais
42 | 
43 | # 2. Commit tudo junto
44 | git add .
45 | git commit -m "feat: sua funcionalidade + changeset"
46 | ```
47 | 
48 | ---
49 | 
50 | **Dúvidas?** Abra uma [issue](https://github.com/AbacatePay/abacatepay-mcp/issues) 🙋‍♂️
```

--------------------------------------------------------------------------------
/.changeset/config.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "$schema": "https://unpkg.com/@changesets/[email protected]/schema.json",
 3 |   "changelog": "@changesets/cli/changelog",
 4 |   "commit": true,
 5 |   "fixed": [],
 6 |   "linked": [],
 7 |   "access": "public",
 8 |   "baseBranch": "main",
 9 |   "updateInternalDependencies": "patch",
10 |   "ignore": []
11 | }
12 | 
```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
 1 | FROM oven/bun:1-alpine
 2 | 
 3 | # Set resource constraints
 4 | ENV NODE_OPTIONS="--max-old-space-size=500"
 5 | 
 6 | WORKDIR /app
 7 | 
 8 | COPY package.json ./
 9 | RUN bun install
10 | 
11 | COPY . .
12 | 
13 | EXPOSE 3000
14 | 
15 | # Add resource limits via Docker run flags
16 | # Use --cpus=1.0 and --memory=500m when running the container
17 | CMD ["bun", "run", "src/http-server.ts"]
18 | 
19 | 
20 | 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |     "compilerOptions": {
 3 |       "target": "ES2022",
 4 |       "module": "Node16",
 5 |       "moduleResolution": "Node16",
 6 |       "outDir": "./dist",
 7 |       "rootDir": "./src",
 8 |       "strict": true,
 9 |       "esModuleInterop": true,
10 |       "skipLibCheck": true,
11 |       "forceConsistentCasingInFileNames": true
12 |     },
13 |     "include": ["src/**/*"],
14 |     "exclude": ["node_modules", "dist"]
15 |   }
```

--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------

```yaml
 1 | version: '3.8'
 2 | 
 3 | services:
 4 |   abacatepay-mcp:
 5 |     build: .
 6 |     ports:
 7 |       - "${PORT:-3000}:3000"
 8 |     environment:
 9 |       - MCP_TRANSPORT=http
10 |       - MCP_PORT=3000
11 |       - ABACATE_PAY_API_KEY=${ABACATE_PAY_API_KEY}
12 |     restart: unless-stopped
13 |     volumes:
14 |       - .:/app
15 |       - /app/node_modules
16 |     deploy:
17 |       resources:
18 |         limits:
19 |           cpus: '1.0'
20 |           memory: 500M
21 |         reservations:
22 |           cpus: '0.5'
23 |           memory: 250M
24 | 
```

--------------------------------------------------------------------------------
/src/tools/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 2 | import { registerCustomerTools } from "./customer.js";
 3 | import { registerBillingTools } from "./billing.js";
 4 | import { registerPixTools } from "./pix.js";
 5 | import { registerCouponTools } from "./coupon.js";
 6 | import { registerWithdrawTools } from "./withdraw.js";
 7 | 
 8 | export function registerAllTools(server: McpServer) {
 9 |   registerCustomerTools(server);
10 |   registerBillingTools(server);
11 |   registerPixTools(server);
12 |   registerCouponTools(server);
13 |   registerWithdrawTools(server);
14 | } 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import "./config.js"; 
 2 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 3 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
 4 | import { registerAllTools } from "./tools/index.js";
 5 | 
 6 | function createServer(): McpServer {
 7 |   const server = new McpServer({
 8 |     name: "abacatepay-mcp",
 9 |     version: "1.0.0",
10 |     capabilities: {
11 |       resources: {},
12 |       tools: {},
13 |     },
14 |   });
15 | 
16 |   registerAllTools(server);
17 | 
18 |   return server;
19 | }
20 | 
21 | async function main() {
22 |   try {
23 |     const server = createServer();
24 |     const transport = new StdioServerTransport();
25 |     await server.connect(transport);
26 |     console.error("Abacate Pay MCP Server rodando em stdio");
27 |   } catch (error) {
28 |     console.error("Erro fatal em main():", error);
29 |     process.exit(1);
30 |   }
31 | }
32 | 
33 | main();
34 | 
```

--------------------------------------------------------------------------------
/src/http/api.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { validateApiKey, ABACATE_PAY_API_BASE, USER_AGENT } from "../config.js";
 2 | 
 3 | export async function makeAbacatePayRequest<T = any>(
 4 |   endpoint: string, 
 5 |   apiKey?: string,
 6 |   options: RequestInit = {}
 7 | ): Promise<T> {
 8 |   const url = `${ABACATE_PAY_API_BASE}${endpoint}`;
 9 |   
10 |   // Use provided API key or fall back to global API key
11 |   const authKey = apiKey || validateApiKey();
12 |   
13 |   const headers = {
14 |     'Authorization': `Bearer ${authKey}`,
15 |     'Content-Type': 'application/json',
16 |     'User-Agent': USER_AGENT,
17 |     ...options.headers,
18 |   };
19 | 
20 |   const response = await fetch(url, {
21 |     ...options,
22 |     headers,
23 |   });
24 | 
25 |   if (!response.ok) {
26 |     const errorText = await response.text();
27 |     throw new Error(`HTTP ${response.status}: ${errorText}`);
28 |   }
29 | 
30 |   return response.json();
31 | } 
```

--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------

```markdown
 1 | # abacatepay-mcp
 2 | 
 3 | ## 1.0.5
 4 | 
 5 | ### Patch Changes
 6 | 
 7 | - 7502e34: Corrige problema de dependências faltando ao executar via NPX
 8 | 
 9 |   - Implementa bundling com esbuild para incluir todas as dependências em um único arquivo
10 |   - Resolve erro "Cannot find package '@modelcontextprotocol/sdk'"
11 |   - Agora funciona perfeitamente com `npx abacatepay-mcp` sem necessidade de instalação manual
12 |   - Bundle otimizado de ~261KB incluindo todas as dependências necessárias
13 | 
14 | ## 1.0.4
15 | 
16 | ### Patch Changes
17 | 
18 | - bff91cb: Corrige compatibilidade com Node.js 18+
19 | 
20 |   - Atualiza requisito do Node.js de >=22.16.0 para >=18.19.1
21 |   - Adiciona shebang correto no arquivo executável
22 |   - Melhora compatibilidade com a maioria das instalações do Node.js
23 | 
24 | ## 1.0.2
25 | 
26 | ### Patch Changes
27 | 
28 | - 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.
29 | 
```

--------------------------------------------------------------------------------
/src/config.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import minimist from "minimist";
 2 | 
 3 | const argv = minimist(process.argv.slice(2));
 4 | 
 5 | // API key can be provided globally (legacy mode) or per request (multi-tenant mode)
 6 | export const apiKey = argv.key || process.env.ABACATE_PAY_API_KEY;
 7 | 
 8 | export function validateApiKey(): string {
 9 |   if (!apiKey) {
10 |     console.error(
11 |       "⚠️  Chave de API não fornecida globalmente.\n" +
12 |       "O servidor suporta multi-tenancy e requer a chave de API em cada requisição.\n" +
13 |       "Para usar o modo legacy (chave global), configure:\n" +
14 |       "  1. --key sua_chave_aqui\n" +
15 |       "  2. Variável de ambiente ABACATE_PAY_API_KEY"
16 |     );
17 |   }
18 |   return apiKey || '';
19 | }
20 | 
21 | // Só valida se estamos executando como script principal
22 | const isMainModule = process.argv[1] && (
23 |   process.argv[1].endsWith('index.js') || 
24 |   process.argv[1].endsWith('dist/index.js') ||
25 |   process.argv[1].includes('abacatepay-mcp')
26 | );
27 | 
28 | if (isMainModule && !process.env.NODE_ENV?.includes('test')) {
29 |   console.error("✅ Abacate Pay MCP Server iniciado com sucesso");
30 |   if (apiKey) {
31 |     console.error("🔑 Modo legacy ativo - API key global configurada");
32 |   } else {
33 |     console.error("🔐 Multi-tenancy ativo - API keys devem ser fornecidas em cada requisição");
34 |   }
35 | }
36 | 
37 | export const ABACATE_PAY_API_BASE = "https://api.abacatepay.com/v1";
38 | export const USER_AGENT = "abacatepay-mcp/1.0"; 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "abacatepay-mcp",
 3 |   "version": "1.0.6",
 4 |   "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",
 5 |   "main": "src/index.ts",
 6 |   "type": "module",
 7 |   "scripts": {
 8 |     "start": "bun run src/index.ts",
 9 |     "start:http": "bun run src/http-server.ts",
10 |     "dev": "bun --watch src/index.ts",
11 |     "dev:http": "bun --watch src/http-server.ts",
12 |     "inspector": "bun run scripts/inspector.js",
13 |     "lint": "eslint src/ scripts/ --ext .ts,.js",
14 |     "lint:fix": "eslint src/ scripts/ --ext .ts,.js --fix",
15 |     "security": "bun audit && bun run lint",
16 |     "precommit": "bun run security",
17 |     "changeset": "changeset",
18 |     "changeset:version": "changeset version",
19 |     "changeset:publish": "changeset publish",
20 |     "changeset:status": "changeset status"
21 |   },
22 |   "files": [
23 |     "src/",
24 |     "README.md",
25 |     "LICENSE"
26 |   ],
27 |   "bin": {
28 |     "abacatepay-mcp": "src/index.ts"
29 |   },
30 |   "repository": {
31 |     "type": "git",
32 |     "url": "git+https://github.com/AbacatePay/abacatepay-mcp.git"
33 |   },
34 |   "keywords": [
35 |     "mcp",
36 |     "model-context-protocol",
37 |     "abacate-pay",
38 |     "payment",
39 |     "pix",
40 |     "api",
41 |     "claude",
42 |     "ai",
43 |     "assistant",
44 |     "billing",
45 |     "coupon",
46 |     "brazil"
47 |   ],
48 |   "author": "Vinícius Américo",
49 |   "license": "MIT",
50 |   "bugs": {
51 |     "url": "https://github.com/AbacatePay/abacatepay-mcp/issues"
52 |   },
53 |   "homepage": "https://github.com/AbacatePay/abacatepay-mcp#readme",
54 |   "dependencies": {
55 |     "@modelcontextprotocol/sdk": "^1.12.0",
56 |     "express": "^5.1.0",
57 |     "minimist": "^1.2.8",
58 |     "zod": "^3.25.30"
59 |   },
60 |   "devDependencies": {
61 |     "@changesets/cli": "^2.29.4",
62 |     "@eslint/js": "^9.27.0",
63 |     "@types/express": "github:types/express",
64 |     "@types/inquirer": "^9.0.8",
65 |     "@types/minimist": "^1.2.5",
66 |     "@types/node": "^22.15.21",
67 |     "@typescript-eslint/eslint-plugin": "^8.33.0",
68 |     "@typescript-eslint/parser": "^8.33.0",
69 |     "eslint": "^9.27.0",
70 |     "eslint-plugin-security": "^3.0.1",
71 |     "inquirer": "^12.6.3",
72 |     "typescript": "^5.8.3"
73 |   },
74 |   "engines": {
75 |     "bun": ">=1.0.0"
76 |   }
77 | }
78 | 
```

--------------------------------------------------------------------------------
/scripts/inspector.js:
--------------------------------------------------------------------------------

```javascript
 1 | #!/usr/bin/env bun
 2 | 
 3 | import inquirer from 'inquirer';
 4 | import { spawn } from 'child_process';
 5 | import { fileURLToPath } from 'url';
 6 | import { dirname, join } from 'path';
 7 | 
 8 | const __filename = fileURLToPath(import.meta.url);
 9 | const __dirname = dirname(__filename);
10 | const projectRoot = join(__dirname, '..');
11 | 
12 | console.log('🥑 Abacate Pay MCP Inspector');
13 | console.log('================================\n');
14 | 
15 | async function getApiKey() {
16 |     // Verifica se já existe na variável de ambiente
17 |     if (process.env.ABACATE_PAY_API_KEY) {
18 |         console.log('✅ Chave de API encontrada na variável de ambiente');
19 |         return process.env.ABACATE_PAY_API_KEY;
20 |     }
21 |     
22 |     // Pede a chave de forma interativa
23 |     const answers = await inquirer.prompt([
24 |         {
25 |             type: 'password',
26 |             name: 'apiKey',
27 |             message: '🔑 Digite sua chave de API do Abacate Pay:',
28 |             mask: '*',
29 |             validate: (input) => {
30 |                 if (!input || input.trim() === '') {
31 |                     return '❌ Chave de API é obrigatória';
32 |                 }
33 |                 return true;
34 |             }
35 |         }
36 |     ]);
37 |     
38 |     console.log('✅ Chave de API recebida');
39 |     return answers.apiKey;
40 | }
41 | 
42 | async function startInspector(apiKey) {
43 |     console.log('🚀 Iniciando MCP Inspector...\n');
44 |     
45 |     if (!process.env.ABACATE_PAY_API_KEY) {
46 |         console.log('💡 Dica: Para não precisar digitar a chave toda vez, você pode:');
47 |         console.log('   export ABACATE_PAY_API_KEY="sua_chave_aqui"\n');
48 |     }
49 |     
50 |     const inspector = spawn('bunx', [
51 |         '@modelcontextprotocol/inspector', 
52 |         'bun', 
53 |         'src/index.ts'
54 |     ], {
55 |         cwd: projectRoot,
56 |         stdio: 'inherit',
57 |         env: {
58 |             ...process.env,
59 |             ABACATE_PAY_API_KEY: apiKey
60 |         }
61 |     });
62 |     
63 |     inspector.on('close', (code) => {
64 |         if (code !== 0) {
65 |             console.log(`\n❌ MCP Inspector encerrado com código ${code}`);
66 |         }
67 |     });
68 | }
69 | 
70 | async function main() {
71 |     try {
72 |         const apiKey = await getApiKey();
73 |         await startInspector(apiKey);
74 |     } catch (error) {
75 |         console.error('❌ Erro:', error.message);
76 |         process.exit(1);
77 |     }
78 | }
79 | 
80 | main(); 
```

--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------

```javascript
 1 | import js from '@eslint/js';
 2 | import tseslint from '@typescript-eslint/eslint-plugin';
 3 | import tsparser from '@typescript-eslint/parser';
 4 | import security from 'eslint-plugin-security';
 5 | 
 6 | export default [
 7 |   js.configs.recommended,
 8 |   {
 9 |     files: ['src/**/*.ts', 'scripts/**/*.js'],
10 |     languageOptions: {
11 |       parser: tsparser,
12 |       ecmaVersion: 'latest',
13 |       sourceType: 'module',
14 |       globals: {
15 |         console: 'readonly',
16 |         process: 'readonly',
17 |         Buffer: 'readonly',
18 |         __dirname: 'readonly',
19 |         __filename: 'readonly',
20 |         fetch: 'readonly',
21 |         RequestInit: 'readonly',
22 |         Response: 'readonly'
23 |       }
24 |     },
25 |     plugins: {
26 |       '@typescript-eslint': tseslint,
27 |       security: security
28 |     },
29 |     rules: {
30 |       // Regras de segurança - ERRO para problemas críticos
31 |       'security/detect-object-injection': 'error',
32 |       'security/detect-non-literal-regexp': 'error',
33 |       'security/detect-unsafe-regex': 'error',
34 |       'security/detect-buffer-noassert': 'error',
35 |       'security/detect-child-process': 'error', // Mudou para error
36 |       'security/detect-disable-mustache-escape': 'error',
37 |       'security/detect-eval-with-expression': 'error',
38 |       'security/detect-no-csrf-before-method-override': 'error',
39 |       'security/detect-non-literal-fs-filename': 'error', // Mudou para error
40 |       'security/detect-non-literal-require': 'error', // Mudou para error
41 |       'security/detect-possible-timing-attacks': 'error',
42 |       'security/detect-pseudoRandomBytes': 'error',
43 |       
44 |       // Regras TypeScript - ERRO para problemas que quebram o código
45 |       '@typescript-eslint/no-explicit-any': 'warn', // Mantém warn para flexibilidade MCP
46 |       '@typescript-eslint/no-unused-vars': 'error',
47 |       
48 |       // Regras gerais - ERRO para problemas que podem quebrar
49 |       'no-console': 'off', // Permitido para CLI tools
50 |       'no-process-exit': 'off', // Permitido para CLI tools
51 |       'no-undef': 'error',
52 |       'no-unused-vars': 'off', // Delegado para @typescript-eslint
53 |       'no-unreachable': 'error',
54 |       'no-constant-condition': 'error',
55 |       'no-dupe-keys': 'error',
56 |       'no-duplicate-case': 'error',
57 |       'no-empty': 'error',
58 |       'no-extra-boolean-cast': 'error',
59 |       'no-func-assign': 'error',
60 |       'no-invalid-regexp': 'error',
61 |       'no-irregular-whitespace': 'error',
62 |       'no-obj-calls': 'error',
63 |       'no-sparse-arrays': 'error',
64 |       'no-unexpected-multiline': 'error',
65 |       'use-isnan': 'error',
66 |       'valid-typeof': 'error'
67 |     }
68 |   },
69 |   {
70 |     ignores: ['dist/', 'node_modules/']
71 |   }
72 | ]; 
```

--------------------------------------------------------------------------------
/src/tools/withdraw.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 2 | import { z } from "zod";
 3 | import { makeAbacatePayRequest } from "../http/api.js";
 4 | 
 5 | export function registerWithdrawTools(server: McpServer) {
 6 |   server.tool(
 7 |     "createWithdraw",
 8 |     "Cria um novo saque para transferir valores da conta para uma chave PIX",
 9 |     {
10 |       apiKey: z.string().optional().describe("Chave de API do Abacate Pay (opcional se configurada globalmente)"),
11 |       description: z.string().describe("Descrição do saque"),
12 |       externalId: z.string().describe("ID externo para identificação do saque"),
13 |       method: z.string().describe("Método de pagamento (ex: PIX)"),
14 |       amount: z.number().describe("Valor do saque em centavos"),
15 |       pix: z.object({
16 |         type: z.string().describe("Tipo da chave PIX (ex: CPF, CNPJ, EMAIL, PHONE, RANDOM)"),
17 |         key: z.string().describe("Chave PIX (CPF, CNPJ, email, telefone ou chave aleatória)")
18 |       }).describe("Dados da chave PIX para o saque")
19 |     },
20 |     async (params) => {
21 |       const { apiKey, description, externalId, method, amount, pix } = params as any;
22 |       try {
23 |         const requestBody = {
24 |           description,
25 |           externalId,
26 |           method,
27 |           amount,
28 |           pix
29 |         };
30 | 
31 |         const response = await makeAbacatePayRequest<any>("/withdraw/create", apiKey, {
32 |           method: "POST",
33 |           body: JSON.stringify(requestBody)
34 |         });
35 | 
36 |         const data = response.data;
37 |         const amountFormatted = (data.amount / 100).toFixed(2);
38 |         const feeFormatted = (data.platformFee / 100).toFixed(2);
39 |         
40 |         return {
41 |           content: [
42 |             {
43 |               type: "text",
44 |               text: `💰 **Saque criado com sucesso!**\n\n` +
45 |                     `📋 **Detalhes:**\n` +
46 |                     `• ID: ${data.id}\n` +
47 |                     `• Status: ${data.status}\n` +
48 |                     `• Valor: R$ ${amountFormatted}\n` +
49 |                     `• Taxa da Plataforma: R$ ${feeFormatted}\n` +
50 |                     `• ID Externo: ${data.externalId}\n` +
51 |                     `• Tipo: ${data.kind}\n` +
52 |                     `• Criado em: ${new Date(data.createdAt).toLocaleString('pt-BR')}\n` +
53 |                     `• Atualizado em: ${new Date(data.updatedAt).toLocaleString('pt-BR')}\n\n` +
54 |                     `📄 **Comprovante:** ${data.receiptUrl}\n\n` +
55 |                     `${data.devMode ? '⚠️ Modo de desenvolvimento ativo' : '✅ Modo de produção'}`
56 |             }
57 |           ]
58 |         };
59 |       } catch (error) {
60 |         return {
61 |           content: [
62 |             {
63 |               type: "text",
64 |               text: `Falha ao criar saque: ${error instanceof Error ? error.message : 'Erro desconhecido'}`
65 |             }
66 |           ]
67 |         };
68 |       }
69 |     }
70 |   );
71 | }
72 | 
```

--------------------------------------------------------------------------------
/src/tools/customer.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  2 | import { z } from "zod";
  3 | import { makeAbacatePayRequest } from "../http/api.js";
  4 | 
  5 | export function registerCustomerTools(server: McpServer) {
  6 |   server.tool(
  7 |     "createCustomer",
  8 |     "Cria um novo cliente no Abacate Pay",
  9 |     {
 10 |       apiKey: z.string().optional().describe("Chave de API do Abacate Pay (opcional se configurada globalmente)"),
 11 |       name: z.string().describe("Nome completo do cliente"),
 12 |       cellphone: z.string().describe("Celular do cliente (ex: (11) 4002-8922)"),
 13 |       email: z.string().email().describe("E-mail do cliente"),
 14 |       taxId: z.string().describe("CPF ou CNPJ válido do cliente (ex: 123.456.789-01)")
 15 |     },
 16 |     async (params) => {
 17 |       const { apiKey, name, cellphone, email, taxId } = params as any;
 18 |       try {
 19 |         const response = await makeAbacatePayRequest<any>("/customer/create", apiKey, {
 20 |           method: "POST",
 21 |           body: JSON.stringify({
 22 |             name,
 23 |             cellphone,
 24 |             email,
 25 |             taxId
 26 |           })
 27 |         });
 28 | 
 29 |         return {
 30 |           content: [
 31 |             {
 32 |               type: "text",
 33 |               text: `Cliente criado com sucesso!\nID: ${response.data?.id || 'N/A'}\nNome: ${name}\nEmail: ${email}\nCelular: ${cellphone}\nCPF/CNPJ: ${taxId}`
 34 |             }
 35 |           ]
 36 |         };
 37 |       } catch (error) {
 38 |         return {
 39 |           content: [
 40 |             {
 41 |               type: "text",
 42 |               text: `Falha ao criar cliente: ${error instanceof Error ? error.message : 'Erro desconhecido'}`
 43 |             }
 44 |           ]
 45 |         };
 46 |       }
 47 |     }
 48 |   );
 49 | 
 50 |   server.tool(
 51 |     "listCustomers",
 52 |     "Lista todos os clientes cadastrados no Abacate Pay",
 53 |     {
 54 |       apiKey: z.string().optional().describe("Chave de API do Abacate Pay (opcional se configurada globalmente)")
 55 |     },
 56 |     async (params) => {
 57 |       const { apiKey } = params as any;
 58 |       try {
 59 |         const response = await makeAbacatePayRequest<any>("/customer/list", apiKey, {
 60 |           method: "GET"
 61 |         });
 62 | 
 63 |         if (!response.data || response.data.length === 0) {
 64 |           return {
 65 |             content: [
 66 |               {
 67 |                 type: "text",
 68 |                 text: "Nenhum cliente encontrado."
 69 |               }
 70 |             ]
 71 |           };
 72 |         }
 73 | 
 74 |         const customersList = response.data.map((customer: any, index: number) => {
 75 |           const metadata = customer.metadata || {};
 76 |           return `${index + 1}. ID: ${customer.id}
 77 |      Nome: ${metadata.name || 'N/A'}
 78 |      Email: ${metadata.email || 'N/A'}
 79 |      Celular: ${metadata.cellphone || 'N/A'}
 80 |      CPF/CNPJ: ${metadata.taxId || 'N/A'}`;
 81 |         }).join('\n\n');
 82 | 
 83 |         return {
 84 |           content: [
 85 |             {
 86 |               type: "text",
 87 |               text: `Lista de Clientes (${response.data.length} encontrado(s)):\n\n${customersList}`
 88 |             }
 89 |           ]
 90 |         };
 91 |       } catch (error) {
 92 |         return {
 93 |           content: [
 94 |             {
 95 |               type: "text",
 96 |               text: `Falha ao listar clientes: ${error instanceof Error ? error.message : 'Erro desconhecido'}`
 97 |             }
 98 |           ]
 99 |         };
100 |       }
101 |     }
102 |   );
103 | } 
```

--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------

```yaml
  1 | name: CI - Verificações de Qualidade
  2 | 
  3 | on:
  4 |   push:
  5 |     branches: [ main ]
  6 |   pull_request:
  7 |     branches: [ main ]
  8 | 
  9 | jobs:
 10 |   ci:
 11 |     name: ✅ Verificações Obrigatórias
 12 |     runs-on: ubuntu-latest
 13 |     
 14 |     steps:
 15 |     - name: 📥 Checkout do código
 16 |       uses: actions/checkout@v4
 17 | 
 18 |     - name: 🟢 Setup Bun
 19 |       uses: oven-sh/setup-bun@v1
 20 |       with:
 21 |         bun-version: latest
 22 | 
 23 |     - name: 📦 Instalar dependências
 24 |       run: bun install
 25 | 
 26 |     - name: 🔍 Auditoria de segurança
 27 |       run: |
 28 |         echo "🔍 Verificando vulnerabilidades de segurança..."
 29 |         bun audit
 30 |         if [ $? -ne 0 ]; then
 31 |           echo "❌ Vulnerabilidades de alta severidade encontradas!"
 32 |           echo "💡 Execute 'bun audit' para verificar detalhes"
 33 |           exit 1
 34 |         fi
 35 |         echo "✅ Nenhuma vulnerabilidade crítica encontrada"
 36 | 
 37 |     - name: 🧹 Verificação de código (ESLint)
 38 |       run: |
 39 |         echo "🧹 Verificando qualidade do código..."
 40 |         bun run lint
 41 |         if [ $? -ne 0 ]; then
 42 |           echo "❌ Problemas de qualidade de código encontrados!"
 43 |           echo "💡 Execute 'bun run lint:fix' para corrigir automaticamente"
 44 |           exit 1
 45 |         fi
 46 |         echo "✅ Código está seguindo os padrões de qualidade"
 47 | 
 48 |     - name: 🔧 Verificação de tipos TypeScript
 49 |       run: |
 50 |         echo "🔧 Verificando tipos TypeScript..."
 51 |         bunx tsc --noEmit
 52 |         if [ $? -ne 0 ]; then
 53 |           echo "❌ Erros de tipo encontrados!"
 54 |           echo "💡 Corrija os erros de TypeScript antes de continuar"
 55 |           exit 1
 56 |         fi
 57 |         echo "✅ Todos os tipos estão corretos"
 58 | 
 59 |     - name: 🧪 Verificação do script inspector
 60 |       run: |
 61 |         echo "🧪 Testando script inspector..."
 62 |         if [ ! -f "scripts/inspector.js" ]; then
 63 |           echo "❌ Script inspector não encontrado!"
 64 |           exit 1
 65 |         fi
 66 |         
 67 |         # Verifica sintaxe do script
 68 |         bun scripts/inspector.js --help
 69 |         if [ $? -ne 0 ]; then
 70 |           echo "❌ Script inspector tem erros de sintaxe!"
 71 |           exit 1
 72 |         fi
 73 |         
 74 |         echo "✅ Script inspector está funcionando corretamente"
 75 | 
 76 |     - name: 🔍 Verificação de arquivos essenciais
 77 |       run: |
 78 |         echo "🔍 Verificando arquivos essenciais..."
 79 |         
 80 |         # Verifica se os arquivos TypeScript principais existem
 81 |         if [ ! -f "src/index.ts" ]; then
 82 |           echo "❌ Arquivo principal não encontrado!"
 83 |           exit 1
 84 |         fi
 85 |         
 86 |         # Verifica se o package.json tem os scripts necessários
 87 |         if ! grep -q '"start"' package.json || ! grep -q '"inspector"' package.json; then
 88 |           echo "❌ Scripts bun essenciais não encontrados!"
 89 |           echo "📋 Scripts necessários: start, inspector"
 90 |           exit 1
 91 |         fi
 92 |         
 93 |         echo "✅ Todos os arquivos essenciais estão presentes"
 94 | 
 95 |     - name: 🎯 Teste de importação do MCP Server
 96 |       run: |
 97 |         echo "🎯 Testando se o MCP Server pode ser executado..."
 98 |         bun run src/index.ts --help
 99 |         if [ $? -ne 0 ]; then
100 |           echo "❌ Falha ao executar MCP Server!"
101 |           exit 1
102 |         fi
103 |         echo "✅ MCP Server executado com sucesso"
104 | 
105 |     - name: 🎉 Sucesso
106 |       run: |
107 |         echo ""
108 |         echo "🎉 Todas as verificações passaram com sucesso!"
109 |         echo ""
110 |         echo "📋 Resumo das verificações:"
111 |         echo "  ✅ Dependências instaladas"
112 |         echo "  ✅ Segurança verificada"
113 |         echo "  ✅ Qualidade de código aprovada"
114 |         echo "  ✅ Tipos TypeScript corretos"
115 |         echo "  ✅ Script inspector funcional"
116 |         echo "  ✅ Arquivos essenciais presentes"
117 |         echo "  ✅ MCP Server executável"
118 |         echo ""
119 |         echo "🚀 Pronto para merge! 🥑" 
```

--------------------------------------------------------------------------------
/src/http-server.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import "./config.js";
  2 | import express from "express";
  3 | import type { Request, Response } from "express";
  4 | import { randomUUID } from "node:crypto";
  5 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  6 | import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
  7 | import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
  8 | import { registerAllTools } from "./tools/index.js";
  9 | 
 10 | function createServer(): McpServer {
 11 |   const server = new McpServer({
 12 |     name: "abacatepay-mcp",
 13 |     version: "1.0.0",
 14 |     capabilities: {
 15 |       resources: {},
 16 |       tools: {},
 17 |     },
 18 |   });
 19 | 
 20 |   registerAllTools(server);
 21 | 
 22 |   return server;
 23 | }
 24 | 
 25 | async function main() {
 26 |   try {
 27 |     const app = express();
 28 |     app.use((express as any).json({ limit: '10mb' }));
 29 | 
 30 |     // Map to store transports by session ID
 31 |     const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};
 32 | 
 33 |     // Handle POST requests for client-to-server communication
 34 |     app.post('/mcp', async (req: Request, res: Response) => {
 35 |       // Check for existing session ID
 36 |       const sessionId = req.headers['mcp-session-id'] as string | undefined;
 37 |       let transport: StreamableHTTPServerTransport;
 38 | 
 39 |       if (sessionId && transports[sessionId]) {
 40 |         // Reuse existing transport
 41 |         transport = transports[sessionId];
 42 |       } else if (!sessionId && isInitializeRequest((req as any).body)) {
 43 |         // New initialization request
 44 |         transport = new StreamableHTTPServerTransport({
 45 |           sessionIdGenerator: () => randomUUID(),
 46 |           onsessioninitialized: (sessionId) => {
 47 |             // Store the transport by session ID
 48 |             transports[sessionId] = transport;
 49 |           },
 50 |           // DNS rebinding protection is disabled by default for backwards compatibility. If you are running this server
 51 |           // locally, make sure to set:
 52 |           // enableDnsRebindingProtection: true,
 53 |           // allowedHosts: ['127.0.0.1'],
 54 |         });
 55 | 
 56 |         // Clean up transport when closed
 57 |         transport.onclose = () => {
 58 |           if (transport.sessionId) {
 59 |             delete transports[transport.sessionId];
 60 |           }
 61 |         };
 62 | 
 63 |         const server = createServer();
 64 | 
 65 |         // Connect to the MCP server
 66 |         await server.connect(transport);
 67 |       } else {
 68 |         // Invalid request
 69 |         res.status(400).json({
 70 |           jsonrpc: '2.0',
 71 |           error: {
 72 |             code: -32000,
 73 |             message: 'Bad Request: No valid session ID provided',
 74 |           },
 75 |           id: null,
 76 |         });
 77 |         return;
 78 |       }
 79 | 
 80 |       // Handle the request
 81 |       await transport.handleRequest(req, res, (req as any).body);
 82 |     });
 83 | 
 84 |     // Reusable handler for GET and DELETE requests
 85 |     const handleSessionRequest = async (req: Request, res: Response) => {
 86 |       const sessionId = req.headers['mcp-session-id'] as string | undefined;
 87 |       if (!sessionId || !transports[sessionId]) {
 88 |         res.status(400).send('Invalid or missing session ID');
 89 |         return;
 90 |       }
 91 |       
 92 |       const transport = transports[sessionId];
 93 |       await transport.handleRequest(req, res);
 94 |     };
 95 | 
 96 |     // Handle GET requests for server-to-client notifications via SSE
 97 |     app.get('/mcp', handleSessionRequest);
 98 | 
 99 |     // Handle DELETE requests for session termination
100 |     app.delete('/mcp', handleSessionRequest);
101 | 
102 |     // Get port from environment or use default
103 |     const port = parseInt(process.env.MCP_PORT || process.env.PORT || "3000");
104 | 
105 |     app.listen(port, () => {
106 |       console.error(`🚀 Abacate Pay MCP Server rodando em http://localhost:${port}`);
107 |       console.error(`📡 Endpoint: http://localhost:${port}/mcp`);
108 |       console.error(`📖 Documentação: http://localhost:${port}/mcp/schema`);
109 |     });
110 |     
111 |     // Graceful shutdown
112 |     process.on('SIGINT', async () => {
113 |       console.error('\n🛑 Encerrando servidor...');
114 |       // Close all active transports
115 |       for (const transport of Object.values(transports)) {
116 |         await transport.close();
117 |       }
118 |       process.exit(0);
119 |     });
120 | 
121 |   } catch (error) {
122 |     console.error("Erro fatal em main():", error);
123 |     process.exit(1);
124 |   }
125 | }
126 | 
127 | main();
128 | 
```

--------------------------------------------------------------------------------
/src/tools/coupon.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  2 | import { z } from "zod";
  3 | import { makeAbacatePayRequest } from "../http/api.js";
  4 | 
  5 | export function registerCouponTools(server: McpServer) {
  6 |   server.tool(
  7 |     "createCoupon",
  8 |     "Cria um novo cupom de desconto",
  9 |     {
 10 |       apiKey: z.string().optional().describe("Chave de API do Abacate Pay (opcional se configurada globalmente)"),
 11 |       code: z.string().describe("Código único do cupom (ex: DESCONTO20)"),
 12 |       discountKind: z.enum(["PERCENTAGE", "FIXED"]).describe("Tipo de desconto: PERCENTAGE (porcentagem) ou FIXED (valor fixo)"),
 13 |       discount: z.number().describe("Valor do desconto (em % para PERCENTAGE ou em centavos para FIXED)"),
 14 |       notes: z.string().optional().describe("Descrição sobre o cupom"),
 15 |       maxRedeems: z.number().default(-1).describe("Quantidade máxima de usos (-1 para ilimitado)"),
 16 |       metadata: z.object({}).optional().describe("Metadados adicionais do cupom")
 17 |     },
 18 |     async (params) => {
 19 |       const { apiKey, code, discountKind, discount, notes, maxRedeems, metadata } = params as any;
 20 |       try {
 21 |         const requestBody: any = {
 22 |           code,
 23 |           discountKind,
 24 |           discount,
 25 |           maxRedeems
 26 |         };
 27 | 
 28 |         // Adicionar campos opcionais apenas se fornecidos
 29 |         if (notes) {
 30 |           requestBody.notes = notes;
 31 |         }
 32 | 
 33 |         if (metadata) {
 34 |           requestBody.metadata = metadata;
 35 |         }
 36 | 
 37 |         const response = await makeAbacatePayRequest<any>("/coupon/create", apiKey, {
 38 |           method: "POST",
 39 |           body: JSON.stringify(requestBody)
 40 |         });
 41 | 
 42 |         const data = response.data;
 43 |         
 44 |         const discountText = data.discountKind === 'PERCENTAGE' 
 45 |           ? `${data.discount}%` 
 46 |           : `R$ ${(data.discount / 100).toFixed(2)}`;
 47 |         
 48 |         const maxRedeemsText = data.maxRedeems === -1 
 49 |           ? 'Ilimitado' 
 50 |           : `${data.maxRedeems} vezes`;
 51 | 
 52 |         return {
 53 |           content: [
 54 |             {
 55 |               type: "text",
 56 |               text: `🎫 **Cupom criado com sucesso!**\n\n` +
 57 |                     `📋 **Detalhes do Cupom:**\n` +
 58 |                     `• Código: **${data.code}**\n` +
 59 |                     `• Desconto: ${discountText} (${data.discountKind === 'PERCENTAGE' ? 'Porcentagem' : 'Valor Fixo'})\n` +
 60 |                     `• Usos Máximos: ${maxRedeemsText}\n` +
 61 |                     `• Descrição: ${data.notes || 'Sem descrição'}\n\n` +
 62 |                     `✅ O cupom **${data.code}** está pronto para ser usado pelos seus clientes!`
 63 |             }
 64 |           ]
 65 |         };
 66 |       } catch (error) {
 67 |         return {
 68 |           content: [
 69 |             {
 70 |               type: "text",
 71 |               text: `Falha ao criar cupom: ${error instanceof Error ? error.message : 'Erro desconhecido'}`
 72 |             }
 73 |           ]
 74 |         };
 75 |       }
 76 |     }
 77 |   );
 78 | 
 79 |   server.tool(
 80 |     "listCoupons",
 81 |     "Lista todos os cupons de desconto criados no Abacate Pay",
 82 |     {
 83 |       apiKey: z.string().optional().describe("Chave de API do Abacate Pay (opcional se configurada globalmente)")
 84 |     },
 85 |     async (params) => {
 86 |       const { apiKey } = params as any;
 87 |       try {
 88 |         const response = await makeAbacatePayRequest<any>("/coupon/list", apiKey, {
 89 |           method: "GET"
 90 |         });
 91 | 
 92 |         if (!response.data || response.data.length === 0) {
 93 |           return {
 94 |             content: [
 95 |               {
 96 |                 type: "text",
 97 |                 text: "Nenhum cupom encontrado."
 98 |               }
 99 |             ]
100 |           };
101 |         }
102 | 
103 |         const couponsList = response.data.map((coupon: any, index: number) => {
104 |           const discountText = coupon.discountKind === 'PERCENTAGE' 
105 |             ? `${coupon.discount}%` 
106 |             : `R$ ${(coupon.discount / 100).toFixed(2)}`;
107 |           
108 |           const maxRedeemsText = coupon.maxRedeems === -1 
109 |             ? 'Ilimitado' 
110 |             : `${coupon.maxRedeems} vezes`;
111 | 
112 |           return `${index + 1}. 🎫 **${coupon.code}**
113 |      💰 Desconto: ${discountText} (${coupon.discountKind === 'PERCENTAGE' ? 'Porcentagem' : 'Valor Fixo'})
114 |      🔄 Usos: ${maxRedeemsText}
115 |      📝 Descrição: ${coupon.notes || 'Sem descrição'}`;
116 |         }).join('\n\n');
117 | 
118 |         return {
119 |           content: [
120 |             {
121 |               type: "text",
122 |               text: `🎫 **Lista de Cupons** (${response.data.length} encontrado(s)):\n\n${couponsList}`
123 |             }
124 |           ]
125 |         };
126 |       } catch (error) {
127 |         return {
128 |           content: [
129 |             {
130 |               type: "text",
131 |               text: `Falha ao listar cupons: ${error instanceof Error ? error.message : 'Erro desconhecido'}`
132 |             }
133 |           ]
134 |         };
135 |       }
136 |     }
137 |   );
138 | } 
```

--------------------------------------------------------------------------------
/src/tools/billing.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  2 | import { z } from "zod";
  3 | import { makeAbacatePayRequest } from "../http/api.js";
  4 | 
  5 | export function registerBillingTools(server: McpServer) {
  6 |   server.tool(
  7 |     "createBilling",
  8 |     "Cria uma nova cobrança no Abacate Pay",
  9 |     {
 10 |       apiKey: z.string().optional().describe("Chave de API do Abacate Pay (opcional se configurada globalmente)"),
 11 |       frequency: z.enum(["ONE_TIME", "MULTIPLE_PAYMENTS"]).default("ONE_TIME").describe("Tipo de frequência da cobrança"),
 12 |       methods: z.array(z.enum(["PIX"])).default(["PIX"]).describe("Métodos de pagamento (atualmente apenas PIX)"),
 13 |       products: z.array(z.object({
 14 |         externalId: z.string().describe("ID externo do produto"),
 15 |         name: z.string().describe("Nome do produto"),
 16 |         description: z.string().describe("Descrição do produto"),
 17 |         quantity: z.number().describe("Quantidade do produto"),
 18 |         price: z.number().describe("Preço unitário em centavos")
 19 |       })).describe("Lista de produtos"),
 20 |       returnUrl: z.string().url().describe("URL para redirecionar caso o cliente clique em 'Voltar'"),
 21 |       completionUrl: z.string().url().describe("URL para redirecionar quando o pagamento for concluído"),
 22 |       customerId: z.string().optional().describe("ID de um cliente já cadastrado (opcional)")
 23 |     },
 24 |     async (params) => {
 25 |       const { apiKey, frequency, methods, products, returnUrl, completionUrl, customerId } = params as any;
 26 |       try {
 27 |         const requestBody: any = {
 28 |           frequency,
 29 |           methods,
 30 |           products,
 31 |           returnUrl,
 32 |           completionUrl
 33 |         };
 34 | 
 35 |         if (customerId) {
 36 |           requestBody.customerId = customerId;
 37 |         }
 38 | 
 39 |         const response = await makeAbacatePayRequest<any>("/billing/create", apiKey, {
 40 |           method: "POST",
 41 |           body: JSON.stringify(requestBody)
 42 |         });
 43 | 
 44 |         const data = response.data;
 45 |         const totalAmount = (data.amount / 100).toFixed(2);
 46 |         
 47 |         return {
 48 |           content: [
 49 |             {
 50 |               type: "text",
 51 |               text: `Cobrança criada com sucesso! 🎉\n\n` +
 52 |                     `📋 **Detalhes da Cobrança:**\n` +
 53 |                     `• ID: ${data.id}\n` +
 54 |                     `• Status: ${data.status}\n` +
 55 |                     `• Valor Total: R$ ${totalAmount}\n` +
 56 |                     `• Frequência: ${data.frequency}\n` +
 57 |                     `• Métodos: ${data.methods.join(', ')}\n` +
 58 |                     `• Produtos: ${data.products.length} item(s)\n\n` +
 59 |                     `🔗 **Link de Pagamento:**\n${data.url}\n\n` +
 60 |                     `${data.devMode ? '⚠️ Modo de desenvolvimento ativo' : '✅ Modo de produção'}`
 61 |             }
 62 |           ]
 63 |         };
 64 |       } catch (error) {
 65 |         return {
 66 |           content: [
 67 |             {
 68 |               type: "text",
 69 |               text: `Falha ao criar cobrança: ${error instanceof Error ? error.message : 'Erro desconhecido'}`
 70 |             }
 71 |           ]
 72 |         };
 73 |       }
 74 |     }
 75 |   );
 76 | 
 77 |   server.tool(
 78 |     "listBillings",
 79 |     "Lista todas as cobranças criadas no Abacate Pay",
 80 |     {
 81 |       apiKey: z.string().optional().describe("Chave de API do Abacate Pay (opcional se configurada globalmente)")
 82 |     },
 83 |     async (params) => {
 84 |       const { apiKey } = params as any;
 85 |       try {
 86 |         const response = await makeAbacatePayRequest<any>("/billing/list", apiKey, {
 87 |           method: "GET"
 88 |         });
 89 | 
 90 |         if (!response.data || response.data.length === 0) {
 91 |           return {
 92 |             content: [
 93 |               {
 94 |                 type: "text",
 95 |                 text: "Nenhuma cobrança encontrada."
 96 |               }
 97 |             ]
 98 |           };
 99 |         }
100 | 
101 |         const billingsList = response.data.map((billing: any, index: number) => {
102 |           const amount = (billing.amount / 100).toFixed(2);
103 |           const customer = billing.customer?.metadata;
104 |           
105 |           const statusEmojis: Record<string, string> = {
106 |             'PENDING': '⏳',
107 |             'PAID': '✅',
108 |             'EXPIRED': '⏰',
109 |             'CANCELLED': '❌',
110 |             'REFUNDED': '↩️'
111 |           };
112 |           const statusEmoji = statusEmojis[billing.status] || '❓';
113 | 
114 |           return `${index + 1}. ${statusEmoji} **${billing.status}** - R$ ${amount}
115 |      📋 ID: ${billing.id}
116 |      🔗 URL: ${billing.url}
117 |      📦 Produtos: ${billing.products.length} item(s)
118 |      👤 Cliente: ${customer?.name || 'N/A'}
119 |      📅 Frequência: ${billing.frequency}
120 |      💳 Métodos: ${billing.methods.join(', ')}
121 |      ${billing.devMode ? '⚠️ Modo Dev' : '✅ Produção'}`;
122 |         }).join('\n\n');
123 | 
124 |         return {
125 |           content: [
126 |             {
127 |               type: "text",
128 |               text: `📋 **Lista de Cobranças** (${response.data.length} encontrada(s)):\n\n${billingsList}`
129 |             }
130 |           ]
131 |         };
132 |       } catch (error) {
133 |         return {
134 |           content: [
135 |             {
136 |               type: "text",
137 |               text: `Falha ao listar cobranças: ${error instanceof Error ? error.message : 'Erro desconhecido'}`
138 |             }
139 |           ]
140 |         };
141 |       }
142 |     }
143 |   );
144 | } 
```

--------------------------------------------------------------------------------
/src/tools/pix.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  2 | import { z } from "zod";
  3 | import { makeAbacatePayRequest } from "../http/api.js";
  4 | 
  5 | export function registerPixTools(server: McpServer) {
  6 |   server.tool(
  7 |     "createPixQrCode",
  8 |     "Cria um QR Code PIX para pagamento direto",
  9 |     {
 10 |       apiKey: z.string().optional().describe("Chave de API do Abacate Pay (opcional se configurada globalmente)"),
 11 |       amount: z.number().describe("Valor da cobrança em centavos"),
 12 |       expiresIn: z.number().optional().describe("Tempo de expiração em segundos (opcional)"),
 13 |       description: z.string().max(140).optional().describe("Mensagem que aparecerá no pagamento PIX (máx 140 caracteres)"),
 14 |       customer: z.object({
 15 |         name: z.string().describe("Nome completo do cliente"),
 16 |         cellphone: z.string().describe("Celular do cliente"),
 17 |         email: z.string().email().describe("E-mail do cliente"),
 18 |         taxId: z.string().describe("CPF ou CNPJ do cliente")
 19 |       }).optional().describe("Dados do cliente (opcional)")
 20 |     },
 21 |     async (params) => {
 22 |       const { apiKey, amount, expiresIn, description, customer } = params as any;
 23 |       try {
 24 |         const requestBody: any = {
 25 |           amount
 26 |         };
 27 | 
 28 |         if (expiresIn) {
 29 |           requestBody.expiresIn = expiresIn;
 30 |         }
 31 | 
 32 |         if (description) {
 33 |           requestBody.description = description;
 34 |         }
 35 | 
 36 |         if (customer) {
 37 |           requestBody.customer = customer;
 38 |         }
 39 | 
 40 |         const response = await makeAbacatePayRequest<any>("/pixQrCode/create", apiKey, {
 41 |           method: "POST",
 42 |           body: JSON.stringify(requestBody)
 43 |         });
 44 | 
 45 |         const data = response.data;
 46 |         const amountFormatted = (data.amount / 100).toFixed(2);
 47 |         const feeFormatted = (data.platformFee / 100).toFixed(2);
 48 |         
 49 |         return {
 50 |           content: [
 51 |             {
 52 |               type: "text",
 53 |               text: `🎯 **QR Code PIX criado com sucesso!**\n\n` +
 54 |                     `📋 **Detalhes:**\n` +
 55 |                     `• ID: ${data.id}\n` +
 56 |                     `• Valor: R$ ${amountFormatted}\n` +
 57 |                     `• Status: ${data.status}\n` +
 58 |                     `• Taxa da Plataforma: R$ ${feeFormatted}\n` +
 59 |                     `• Criado em: ${new Date(data.createdAt).toLocaleString('pt-BR')}\n` +
 60 |                     `• Expira em: ${new Date(data.expiresAt).toLocaleString('pt-BR')}\n\n` +
 61 |                     `📱 **Código PIX (Copia e Cola):**\n\`\`\`\n${data.brCode}\n\`\`\`\n\n` +
 62 |                     `🖼️ **QR Code Base64:**\n${data.brCodeBase64.substring(0, 100)}...\n\n` +
 63 |                     `${data.devMode ? '⚠️ Modo de desenvolvimento ativo' : '✅ Modo de produção'}`
 64 |             }
 65 |           ]
 66 |         };
 67 |       } catch (error) {
 68 |         return {
 69 |           content: [
 70 |             {
 71 |               type: "text",
 72 |               text: `Falha ao criar QR Code PIX: ${error instanceof Error ? error.message : 'Erro desconhecido'}`
 73 |             }
 74 |           ]
 75 |         };
 76 |       }
 77 |     }
 78 |   );
 79 | 
 80 |   server.tool(
 81 |     "simulatePixPayment",
 82 |     "Simula o pagamento de um QR Code PIX (apenas em modo desenvolvimento)",
 83 |     {
 84 |       apiKey: z.string().optional().describe("Chave de API do Abacate Pay (opcional se configurada globalmente)"),
 85 |       id: z.string().describe("ID do QR Code PIX para simular o pagamento"),
 86 |       metadata: z.object({}).optional().describe("Metadados opcionais para a requisição")
 87 |     },
 88 |     async (params) => {
 89 |       const { apiKey, id, metadata } = params as any;
 90 |       try {
 91 |         const requestBody: any = {};
 92 | 
 93 |         if (metadata) {
 94 |           requestBody.metadata = metadata;
 95 |         }
 96 | 
 97 |         const response = await makeAbacatePayRequest<any>(`/pixQrCode/simulate-payment?id=${id}`, apiKey, {
 98 |           method: "POST",
 99 |           body: JSON.stringify(requestBody)
100 |         });
101 | 
102 |         const data = response.data;
103 |         const amountFormatted = (data.amount / 100).toFixed(2);
104 |         const feeFormatted = (data.platformFee / 100).toFixed(2);
105 |         
106 |         const statusEmojis: Record<string, string> = {
107 |           'PENDING': '⏳',
108 |           'PAID': '✅',
109 |           'EXPIRED': '⏰',
110 |           'CANCELLED': '❌',
111 |           'REFUNDED': '↩️'
112 |         };
113 |         const statusEmoji = statusEmojis[data.status] || '❓';
114 |         
115 |         return {
116 |           content: [
117 |             {
118 |               type: "text",
119 |               text: `${statusEmoji} **Pagamento PIX simulado com sucesso!**\n\n` +
120 |                     `📋 **Detalhes do Pagamento:**\n` +
121 |                     `• ID: ${data.id}\n` +
122 |                     `• Status: ${data.status}\n` +
123 |                     `• Valor: R$ ${amountFormatted}\n` +
124 |                     `• Taxa da Plataforma: R$ ${feeFormatted}\n` +
125 |                     `• Criado em: ${new Date(data.createdAt).toLocaleString('pt-BR')}\n` +
126 |                     `• Atualizado em: ${new Date(data.updatedAt).toLocaleString('pt-BR')}\n` +
127 |                     `• Expira em: ${new Date(data.expiresAt).toLocaleString('pt-BR')}\n\n` +
128 |                     `${data.devMode ? '⚠️ Simulação realizada em modo de desenvolvimento' : '✅ Pagamento em produção'}\n\n` +
129 |                     `🎉 O pagamento foi processado com sucesso!`
130 |             }
131 |           ]
132 |         };
133 |       } catch (error) {
134 |         return {
135 |           content: [
136 |             {
137 |               type: "text",
138 |               text: `Falha ao simular pagamento PIX: ${error instanceof Error ? error.message : 'Erro desconhecido'}`
139 |             }
140 |           ]
141 |         };
142 |       }
143 |     }
144 |   );
145 | 
146 |   server.tool(
147 |     "checkPixStatus",
148 |     "Verifica o status de um QR Code PIX",
149 |     {
150 |       apiKey: z.string().optional().describe("Chave de API do Abacate Pay (opcional se configurada globalmente)"),
151 |       id: z.string().describe("ID do QR Code PIX para verificar o status")
152 |     },
153 |     async (params) => {
154 |       const { apiKey, id } = params as any;
155 |       try {
156 |         const response = await makeAbacatePayRequest<any>(`/pixQrCode/check?id=${id}`, apiKey, {
157 |           method: "GET"
158 |         });
159 | 
160 |         const data = response.data;
161 |         
162 |         const statusEmojis: Record<string, string> = {
163 |           'PENDING': '⏳',
164 |           'PAID': '✅',
165 |           'EXPIRED': '⏰',
166 |           'CANCELLED': '❌',
167 |           'REFUNDED': '↩️'
168 |         };
169 |         const statusEmoji = statusEmojis[data.status] || '❓';
170 |         
171 |         return {
172 |           content: [
173 |             {
174 |               type: "text",
175 |               text: `${statusEmoji} **Status do QR Code PIX**\n\n` +
176 |                     `📋 **ID**: ${id}\n` +
177 |                     `📊 **Status**: ${data.status}\n` +
178 |                     `⏰ **Expira em**: ${new Date(data.expiresAt).toLocaleString('pt-BR')}\n\n` +
179 |                     `${data.status === 'PENDING' ? '⏳ Aguardando pagamento...' : 
180 |                       data.status === 'PAID' ? '✅ Pagamento confirmado!' :
181 |                       data.status === 'EXPIRED' ? '⏰ QR Code expirado' :
182 |                       data.status === 'CANCELLED' ? '❌ QR Code cancelado' :
183 |                       data.status === 'REFUNDED' ? '↩️ Pagamento estornado' : 
184 |                       '❓ Status desconhecido'}`
185 |             }
186 |           ]
187 |         };
188 |       } catch (error) {
189 |         return {
190 |           content: [
191 |             {
192 |               type: "text",
193 |               text: `Falha ao verificar status do PIX: ${error instanceof Error ? error.message : 'Erro desconhecido'}`
194 |             }
195 |           ]
196 |         };
197 |       }
198 |     }
199 |   );
200 | } 
```