# 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 | }
```