@atehra/mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,153 @@
1
+ # @atehra/mcp
2
+
3
+ Servidor MCP (Model Context Protocol) oficial da Atehra. Permite que Claude, Cursor, ChatGPT e qualquer cliente MCP operem a infraestrutura financeira BR da Atehra em linguagem natural.
4
+
5
+ > **A primeira fintech brasileira projetada pra rodar via agente de IA.**
6
+
7
+ ## O que dá pra fazer
8
+
9
+ Com este servidor ligado, seu agente de IA pode:
10
+
11
+ - **Consultar saldo, A receber, antecipações disponíveis** em tempo real
12
+ - **Criar cobranças** em PIX ou boleto pra qualquer cliente
13
+ - **Gerar links de pagamento** reutilizáveis
14
+ - **Antecipar vendas no cartão** com cotação prévia
15
+ - **Sacar saldo** pra conta externa via PIX ou TED
16
+ - **Configurar a régua de cobrança** em linguagem natural
17
+ - **Ver métricas do negócio** (receita recorrente mensal, taxa de cancelamento, valor por cliente)
18
+ - **Listar clientes, assinaturas e faturas** com filtros
19
+
20
+ ## Instalação
21
+
22
+ ### Pré-requisitos
23
+
24
+ 1. Conta na Atehra (`atehra.com`)
25
+ 2. API key (`atk_test_...` pra sandbox, `atk_live_...` pra produção)
26
+
27
+ ### No Claude Desktop
28
+
29
+ Adicione ao arquivo de configuração (`~/Library/Application Support/Claude/claude_desktop_config.json` no macOS):
30
+
31
+ ```json
32
+ {
33
+ "mcpServers": {
34
+ "atehra": {
35
+ "command": "npx",
36
+ "args": ["-y", "@atehra/mcp"],
37
+ "env": {
38
+ "ATEHRA_API_KEY": "atk_test_sua_chave_aqui"
39
+ }
40
+ }
41
+ }
42
+ }
43
+ ```
44
+
45
+ Reinicie o Claude Desktop. Pronto.
46
+
47
+ ### No Cursor
48
+
49
+ Adicione ao `.cursor/mcp.json` no seu projeto:
50
+
51
+ ```json
52
+ {
53
+ "mcpServers": {
54
+ "atehra": {
55
+ "command": "npx",
56
+ "args": ["-y", "@atehra/mcp"],
57
+ "env": {
58
+ "ATEHRA_API_KEY": "atk_test_sua_chave_aqui"
59
+ }
60
+ }
61
+ }
62
+ }
63
+ ```
64
+
65
+ ## Modo sandbox vs produção
66
+
67
+ O servidor detecta o modo pelo prefixo da chave:
68
+
69
+ - `atk_test_...` → sandbox (dados de teste, dinheiro não é real)
70
+ - `atk_live_...` → produção (dinheiro é real)
71
+
72
+ **Recomendação forte:** comece sempre em sandbox.
73
+
74
+ ## Segurança em produção
75
+
76
+ Em produção (`atk_live_`), ações que movem dinheiro real exigem **confirmação dupla**:
77
+
78
+ 1. Você pede pro Claude executar (ex.: "saca tudo pra minha conta principal")
79
+ 2. O servidor retorna: "Vou sacar R$ X pra conta Y. Confirme chamando de novo com `confirm: true`"
80
+ 3. Claude repassa pra você pedir confirmação humana
81
+ 4. Você confirma
82
+ 5. Claude chama de novo com `confirm: true` e a ação executa
83
+
84
+ Em sandbox a confirmação é pulada (tudo é teste).
85
+
86
+ ## As 16 ferramentas
87
+
88
+ ### Saldo e dinheiro
89
+ - `get_balance` — saldo disponível e a receber
90
+ - `list_anticipatable_payments` — vendas no cartão que dão pra antecipar
91
+ - `quote_anticipation` — calcular taxa e valor líquido
92
+ - `request_anticipation` — antecipar pagamento (confirmação dupla em produção)
93
+ - `request_withdrawal` — sacar saldo (confirmação dupla em produção)
94
+
95
+ ### Cobrar e clientes
96
+ - `create_customer` — criar cliente
97
+ - `list_customers` — listar clientes
98
+ - `create_plan` — criar plano de produto (assinatura ou avulso)
99
+ - `create_checkout_session` — gerar cobrança PIX ou boleto pra um cliente
100
+ - `create_checkout_link` — gerar link de pagamento reutilizável
101
+ - `list_recent_orders` — listar últimas vendas
102
+
103
+ ### Números do negócio
104
+ - `get_metrics_overview` — receita, MRR, churn, ARPU, LTV
105
+ - `list_subscriptions` — assinaturas ativas
106
+ - `list_invoices` — faturas (pagas, pendentes, vencidas)
107
+
108
+ ### Régua de cobrança
109
+ - `get_dunning_config` — ver régua atual
110
+ - `update_dunning_config` — atualizar régua de cobrança
111
+
112
+ ## Exemplos de uso
113
+
114
+ Com o servidor ligado, peça ao Claude:
115
+
116
+ > "Quanto eu tenho de saldo e quanto posso antecipar agora?"
117
+
118
+ > "Cria cliente Maria Silva com email maria@exemplo.com.br e CPF 12345678900, depois cria uma cobrança PIX de R$ 1.500 com vencimento sexta."
119
+
120
+ > "Configura a régua: tenta cobrar de novo no dia 1, manda email no dia 3, suspende no dia 7."
121
+
122
+ > "Quais clientes têm fatura vencida há mais de 5 dias?"
123
+
124
+ > "Como tá o MRR esse mês comparado ao anterior?"
125
+
126
+ ## Variáveis de ambiente
127
+
128
+ | Variável | Obrigatório | Descrição |
129
+ |---|---|---|
130
+ | `ATEHRA_API_KEY` | Sim | Chave da API (`atk_test_...` ou `atk_live_...`) |
131
+ | `ATEHRA_BASE_URL` | Não | URL base da API. Padrão: `https://api.atehra.com` |
132
+
133
+ ## Desenvolvimento
134
+
135
+ ```bash
136
+ npm install
137
+ npm run build
138
+ npm start
139
+ ```
140
+
141
+ Pra testar localmente com o Claude Desktop, aponte o `command` pro `node` e o `args` pro caminho do `dist/index.js`:
142
+
143
+ ```json
144
+ {
145
+ "command": "node",
146
+ "args": ["/caminho/absoluto/para/atehra/mcp/dist/index.js"],
147
+ "env": { "ATEHRA_API_KEY": "atk_test_..." }
148
+ }
149
+ ```
150
+
151
+ ## Licença
152
+
153
+ MIT
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Cliente HTTP simples pra API da Atehra.
3
+ *
4
+ * Usa fetch nativo (Node 18+). Autentica via header x-api-key.
5
+ * Retorna o JSON da resposta; lança erro com mensagem útil se a API responder não-2xx.
6
+ */
7
+ import type { Env } from "./env.js";
8
+ export declare class AtehraClient {
9
+ private env;
10
+ constructor(env: Env);
11
+ request<T = unknown>(method: "GET" | "POST" | "PATCH" | "PUT" | "DELETE", path: string, opts?: {
12
+ body?: unknown;
13
+ query?: Record<string, string | number | undefined>;
14
+ }): Promise<T>;
15
+ }
package/dist/client.js ADDED
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Cliente HTTP simples pra API da Atehra.
3
+ *
4
+ * Usa fetch nativo (Node 18+). Autentica via header x-api-key.
5
+ * Retorna o JSON da resposta; lança erro com mensagem útil se a API responder não-2xx.
6
+ */
7
+ export class AtehraClient {
8
+ env;
9
+ constructor(env) {
10
+ this.env = env;
11
+ }
12
+ async request(method, path, opts = {}) {
13
+ const url = new URL(path, this.env.baseUrl + "/");
14
+ if (opts.query) {
15
+ for (const [k, v] of Object.entries(opts.query)) {
16
+ if (v !== undefined && v !== "")
17
+ url.searchParams.set(k, String(v));
18
+ }
19
+ }
20
+ const headers = {
21
+ "x-api-key": this.env.apiKey,
22
+ "user-agent": "@atehra/mcp",
23
+ };
24
+ if (opts.body !== undefined)
25
+ headers["content-type"] = "application/json";
26
+ const res = await fetch(url.toString(), {
27
+ method,
28
+ headers,
29
+ body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
30
+ });
31
+ const text = await res.text();
32
+ let data;
33
+ try {
34
+ data = text ? JSON.parse(text) : null;
35
+ }
36
+ catch {
37
+ data = text;
38
+ }
39
+ if (!res.ok) {
40
+ const msg = extractErrorMessage(data) ?? `HTTP ${res.status}`;
41
+ const error = new Error(`Atehra API ${method} ${path} → ${msg}`);
42
+ error.status = res.status;
43
+ error.data = data;
44
+ throw error;
45
+ }
46
+ return data;
47
+ }
48
+ }
49
+ function extractErrorMessage(data) {
50
+ if (data && typeof data === "object" && data !== null) {
51
+ const obj = data;
52
+ if (typeof obj["message"] === "string")
53
+ return obj["message"];
54
+ if (typeof obj["error"] === "string")
55
+ return obj["error"];
56
+ }
57
+ return null;
58
+ }
package/dist/env.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Lê e valida variáveis de ambiente.
3
+ *
4
+ * ATEHRA_API_KEY (obrigatória) — chave da API. Prefixo determina o modo:
5
+ * - atk_test_... → sandbox
6
+ * - atk_live_... → produção
7
+ *
8
+ * ATEHRA_BASE_URL (opcional) — URL base da API. Default: https://api.atehra.com
9
+ */
10
+ export type Mode = "test" | "live";
11
+ export interface Env {
12
+ apiKey: string;
13
+ baseUrl: string;
14
+ mode: Mode;
15
+ }
16
+ export declare function loadEnv(): Env;
package/dist/env.js ADDED
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Lê e valida variáveis de ambiente.
3
+ *
4
+ * ATEHRA_API_KEY (obrigatória) — chave da API. Prefixo determina o modo:
5
+ * - atk_test_... → sandbox
6
+ * - atk_live_... → produção
7
+ *
8
+ * ATEHRA_BASE_URL (opcional) — URL base da API. Default: https://api.atehra.com
9
+ */
10
+ export function loadEnv() {
11
+ const apiKey = process.env["ATEHRA_API_KEY"];
12
+ if (!apiKey) {
13
+ throw new Error("ATEHRA_API_KEY não definida. Gere uma chave em atehra.com e defina no ambiente.");
14
+ }
15
+ const mode = apiKey.startsWith("atk_live_")
16
+ ? "live"
17
+ : apiKey.startsWith("atk_test_")
18
+ ? "test"
19
+ : (() => {
20
+ throw new Error("ATEHRA_API_KEY tem prefixo inválido. Use atk_test_... (sandbox) ou atk_live_... (produção).");
21
+ })();
22
+ const baseUrl = (process.env["ATEHRA_BASE_URL"] ?? "https://api.atehra.com").replace(/\/+$/, "");
23
+ return { apiKey, baseUrl, mode };
24
+ }
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @atehra/mcp — Servidor MCP oficial da Atehra
4
+ *
5
+ * Sobe um servidor MCP via stdio e registra as 16 ferramentas da V1:
6
+ *
7
+ * Saldo (5): get_balance, list_anticipatable_payments, quote_anticipation,
8
+ * request_anticipation, request_withdrawal
9
+ *
10
+ * Cobrar (6): create_customer, list_customers, create_plan,
11
+ * create_checkout_session, create_checkout_link, list_recent_orders
12
+ *
13
+ * Números (3): get_metrics_overview, list_subscriptions, list_invoices
14
+ *
15
+ * Régua (2): get_dunning_config, update_dunning_config
16
+ */
17
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @atehra/mcp — Servidor MCP oficial da Atehra
4
+ *
5
+ * Sobe um servidor MCP via stdio e registra as 16 ferramentas da V1:
6
+ *
7
+ * Saldo (5): get_balance, list_anticipatable_payments, quote_anticipation,
8
+ * request_anticipation, request_withdrawal
9
+ *
10
+ * Cobrar (6): create_customer, list_customers, create_plan,
11
+ * create_checkout_session, create_checkout_link, list_recent_orders
12
+ *
13
+ * Números (3): get_metrics_overview, list_subscriptions, list_invoices
14
+ *
15
+ * Régua (2): get_dunning_config, update_dunning_config
16
+ */
17
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
18
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
19
+ import { loadEnv } from "./env.js";
20
+ import { AtehraClient } from "./client.js";
21
+ import { registerBalanceTools } from "./tools/balance.js";
22
+ import { registerCustomerTools } from "./tools/customers.js";
23
+ import { registerPlanTools } from "./tools/plans.js";
24
+ import { registerChargeTools } from "./tools/charges.js";
25
+ import { registerMetricsTools } from "./tools/metrics.js";
26
+ import { registerBillingTools } from "./tools/billing.js";
27
+ import { registerDunningTools } from "./tools/dunning.js";
28
+ async function main() {
29
+ const env = loadEnv();
30
+ const client = new AtehraClient(env);
31
+ const server = new McpServer({
32
+ name: "atehra",
33
+ version: "0.1.0",
34
+ });
35
+ // Registra todas as 16 ferramentas
36
+ registerBalanceTools(server, client, env);
37
+ registerCustomerTools(server, client);
38
+ registerPlanTools(server, client);
39
+ registerChargeTools(server, client);
40
+ registerMetricsTools(server, client);
41
+ registerBillingTools(server, client);
42
+ registerDunningTools(server, client);
43
+ // Log de boot vai pra stderr (stdout é reservado pro protocolo MCP)
44
+ process.stderr.write(`[atehra-mcp] iniciado. Modo: ${env.mode}. Base URL: ${env.baseUrl}\n`);
45
+ const transport = new StdioServerTransport();
46
+ await server.connect(transport);
47
+ }
48
+ main().catch((err) => {
49
+ process.stderr.write(`[atehra-mcp] erro fatal: ${err instanceof Error ? err.message : String(err)}\n`);
50
+ process.exit(1);
51
+ });
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Confirmação dupla pra ações que movem dinheiro real.
3
+ *
4
+ * Em produção (atk_live_), ações sensíveis (saque, antecipação) exigem que
5
+ * o caller chame a tool 2x: a primeira chamada retorna um aviso pedindo confirmação,
6
+ * a segunda (com `confirm: true`) executa de fato.
7
+ *
8
+ * Em sandbox (atk_test_), o aviso é pulado — tudo é teste.
9
+ */
10
+ import type { Mode } from "./env.js";
11
+ export interface ConfirmationCheck {
12
+ mode: Mode;
13
+ confirm: boolean | undefined;
14
+ actionDescription: string;
15
+ }
16
+ export interface ConfirmationResult {
17
+ shouldExecute: boolean;
18
+ warningMessage?: string;
19
+ }
20
+ export declare function checkConfirmation({ mode, confirm, actionDescription }: ConfirmationCheck): ConfirmationResult;
package/dist/safety.js ADDED
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Confirmação dupla pra ações que movem dinheiro real.
3
+ *
4
+ * Em produção (atk_live_), ações sensíveis (saque, antecipação) exigem que
5
+ * o caller chame a tool 2x: a primeira chamada retorna um aviso pedindo confirmação,
6
+ * a segunda (com `confirm: true`) executa de fato.
7
+ *
8
+ * Em sandbox (atk_test_), o aviso é pulado — tudo é teste.
9
+ */
10
+ export function checkConfirmation({ mode, confirm, actionDescription }) {
11
+ // Em sandbox, sempre executa
12
+ if (mode === "test")
13
+ return { shouldExecute: true };
14
+ // Em produção, exige confirm: true explícito
15
+ if (confirm === true)
16
+ return { shouldExecute: true };
17
+ return {
18
+ shouldExecute: false,
19
+ warningMessage: [
20
+ `⚠️ AÇÃO EM PRODUÇÃO — MOVIMENTAÇÃO REAL DE DINHEIRO`,
21
+ ``,
22
+ actionDescription,
23
+ ``,
24
+ `Pra confirmar e executar de fato, chame esta ferramenta de novo com o parâmetro \`confirm: true\`.`,
25
+ `Antes de confirmar, repasse pro usuário humano e peça autorização explícita.`,
26
+ ].join("\n"),
27
+ };
28
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Grupo A — Saldo e dinheiro (5 ferramentas)
3
+ *
4
+ * get_balance, list_anticipatable_payments, quote_anticipation,
5
+ * request_anticipation, request_withdrawal
6
+ */
7
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8
+ import type { AtehraClient } from "../client.js";
9
+ import type { Env } from "../env.js";
10
+ export declare function registerBalanceTools(server: McpServer, client: AtehraClient, env: Env): void;
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Grupo A — Saldo e dinheiro (5 ferramentas)
3
+ *
4
+ * get_balance, list_anticipatable_payments, quote_anticipation,
5
+ * request_anticipation, request_withdrawal
6
+ */
7
+ import { z } from "zod";
8
+ import { checkConfirmation } from "../safety.js";
9
+ export function registerBalanceTools(server, client, env) {
10
+ // ── get_balance ────────────────────────────────────────────────────────────
11
+ server.tool("get_balance", "Retorna o saldo da conta Atehra: quanto está disponível pra saque agora, quanto ainda está a receber (cobrado mas aguardando liberação) e a moeda. Use sempre que o usuário perguntar quanto tem na conta, quanto pode sacar ou quanto está pra entrar.", {}, async () => {
12
+ const balance = await client.request("GET", "/v1/balance");
13
+ const formatBRL = (cents) => `R$ ${(cents / 100).toLocaleString("pt-BR", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
14
+ return {
15
+ content: [
16
+ {
17
+ type: "text",
18
+ text: [
19
+ `Modo: ${env.mode === "live" ? "🟢 produção" : "🟡 sandbox"}`,
20
+ `Saldo disponível pra saque: ${formatBRL(balance.available)}`,
21
+ `A receber (cobrado, aguardando liberação): ${formatBRL(balance.pending)}`,
22
+ `Moeda: ${balance.currency}`,
23
+ ].join("\n"),
24
+ },
25
+ ],
26
+ };
27
+ });
28
+ // ── list_anticipatable_payments ────────────────────────────────────────────
29
+ server.tool("list_anticipatable_payments", "Lista todos os pagamentos no cartão que estão pendentes de liberação e podem ser antecipados. Cada item traz o valor bruto, o valor líquido após taxa, o tipo (PIX, BOLETO, CREDIT_CARD), o vencimento e quantos dias faltam pra liberação automática. Use quando o usuário quiser antecipar recebíveis ou ver o que dá pra antecipar.", {}, async () => {
30
+ const payments = await client.request("GET", "/v1/balance/anticipatable-payments");
31
+ if (payments.length === 0) {
32
+ return {
33
+ content: [
34
+ { type: "text", text: "Nenhum pagamento disponível pra antecipação no momento." },
35
+ ],
36
+ };
37
+ }
38
+ const total = payments.reduce((sum, p) => sum + p.net, 0);
39
+ const formatBRL = (cents) => `R$ ${(cents / 100).toLocaleString("pt-BR", { minimumFractionDigits: 2 })}`;
40
+ const lines = [
41
+ `${payments.length} pagamento(s) disponível(is) pra antecipar.`,
42
+ `Total líquido se antecipar tudo: ${formatBRL(total)}`,
43
+ ``,
44
+ ...payments.map((p, i) => `${i + 1}. id=${p.id} · ${p.billingType} · bruto ${formatBRL(p.gross)} · líquido ${formatBRL(p.net)} · vence ${p.dueDate} (em ${p.daysUntilDue} dias)`),
45
+ ];
46
+ return { content: [{ type: "text", text: lines.join("\n") }] };
47
+ });
48
+ // ── quote_anticipation ─────────────────────────────────────────────────────
49
+ server.tool("quote_anticipation", "Calcula a taxa e o valor líquido pra antecipar um pagamento específico. Não executa a antecipação, só simula. Use SEMPRE antes de chamar request_anticipation pra mostrar pro usuário quanto vai chegar líquido e a taxa efetiva. Passe paymentId OU installmentId (não os dois).", {
50
+ paymentId: z
51
+ .string()
52
+ .optional()
53
+ .describe("ID do pagamento Asaas. Use quando antecipar um pagamento avulso."),
54
+ installmentId: z
55
+ .string()
56
+ .optional()
57
+ .describe("ID da parcela. Use quando antecipar parcelas de cartão."),
58
+ }, async ({ paymentId, installmentId }) => {
59
+ if (!paymentId && !installmentId) {
60
+ return {
61
+ content: [
62
+ { type: "text", text: "Erro: passe paymentId OU installmentId. Pelo menos um é obrigatório." },
63
+ ],
64
+ isError: true,
65
+ };
66
+ }
67
+ const quote = await client.request("GET", "/v1/balance/anticipations/quote", {
68
+ query: { paymentId, installmentId },
69
+ });
70
+ const formatBRL = (cents) => `R$ ${(cents / 100).toLocaleString("pt-BR", { minimumFractionDigits: 2 })}`;
71
+ return {
72
+ content: [
73
+ {
74
+ type: "text",
75
+ text: [
76
+ `Cotação de antecipação:`,
77
+ ` Valor bruto: ${formatBRL(quote.grossAmount)}`,
78
+ ` Taxa: ${formatBRL(quote.feeAmount)} (${(quote.effectiveRate * 100).toFixed(2)}% efetivo)`,
79
+ ` Valor líquido (chega pra você): ${formatBRL(quote.netAmount)}`,
80
+ ``,
81
+ `Pra executar a antecipação, chame request_anticipation com o mesmo ID.`,
82
+ ].join("\n"),
83
+ },
84
+ ],
85
+ };
86
+ });
87
+ // ── request_anticipation ───────────────────────────────────────────────────
88
+ server.tool("request_anticipation", "Executa a antecipação de um pagamento. AÇÃO IRREVERSÍVEL que move dinheiro. Em produção exige confirm:true (após o usuário humano autorizar). Em sandbox executa direto. Sempre chame quote_anticipation antes pra mostrar a cotação.", {
89
+ paymentId: z.string().optional().describe("ID do pagamento Asaas a antecipar."),
90
+ installmentId: z.string().optional().describe("ID da parcela a antecipar."),
91
+ confirm: z
92
+ .boolean()
93
+ .optional()
94
+ .describe("Em produção: passe true SÓ após o usuário humano autorizar explicitamente. Em sandbox: ignorado."),
95
+ }, async ({ paymentId, installmentId, confirm }) => {
96
+ if (!paymentId && !installmentId) {
97
+ return {
98
+ content: [{ type: "text", text: "Erro: passe paymentId OU installmentId." }],
99
+ isError: true,
100
+ };
101
+ }
102
+ const check = checkConfirmation({
103
+ mode: env.mode,
104
+ confirm,
105
+ actionDescription: `Antecipar ${paymentId ? `pagamento ${paymentId}` : `parcela ${installmentId}`}. O valor líquido cai na conta Atehra na hora; a taxa é descontada do bruto.`,
106
+ });
107
+ if (!check.shouldExecute) {
108
+ return { content: [{ type: "text", text: check.warningMessage }] };
109
+ }
110
+ const result = await client.request("POST", "/v1/balance/anticipations", {
111
+ body: { paymentId, installmentId },
112
+ });
113
+ return {
114
+ content: [
115
+ { type: "text", text: `Antecipação executada com sucesso.\n\n${JSON.stringify(result, null, 2)}` },
116
+ ],
117
+ };
118
+ });
119
+ // ── request_withdrawal ─────────────────────────────────────────────────────
120
+ server.tool("request_withdrawal", "Saca dinheiro do saldo da Atehra pra uma conta externa via PIX ou TED. AÇÃO IRREVERSÍVEL que move dinheiro real. Em produção exige confirm:true após autorização humana explícita. Em sandbox executa direto.", {
121
+ amount: z
122
+ .number()
123
+ .int()
124
+ .positive()
125
+ .describe("Valor a sacar em centavos (ex: 100000 = R$ 1.000,00). Mínimo R$ 10,00 (1000)."),
126
+ destinationType: z.enum(["PIX", "TED"]).describe("Método de saque."),
127
+ pixKey: z.string().optional().describe("Chave PIX de destino (obrigatório se destinationType=PIX)."),
128
+ pixKeyType: z
129
+ .enum(["CPF", "CNPJ", "EMAIL", "PHONE", "EVP"])
130
+ .optional()
131
+ .describe("Tipo da chave PIX."),
132
+ bankCode: z.string().optional().describe("Código do banco (obrigatório se destinationType=TED)."),
133
+ bankAgency: z.string().optional().describe("Agência (obrigatório se destinationType=TED)."),
134
+ bankAccount: z.string().optional().describe("Número da conta (obrigatório se destinationType=TED)."),
135
+ bankAccountType: z
136
+ .enum(["CHECKING", "SAVINGS"])
137
+ .optional()
138
+ .describe("Tipo da conta TED (corrente ou poupança)."),
139
+ confirm: z.boolean().optional().describe("Em produção: true só após autorização humana."),
140
+ }, async (args) => {
141
+ const { confirm, ...payload } = args;
142
+ const formatBRL = (cents) => `R$ ${(cents / 100).toLocaleString("pt-BR", { minimumFractionDigits: 2 })}`;
143
+ const destinationDesc = payload.destinationType === "PIX"
144
+ ? `via PIX pra chave ${payload.pixKey} (${payload.pixKeyType})`
145
+ : `via TED pra banco ${payload.bankCode}, ag ${payload.bankAgency}, conta ${payload.bankAccount}`;
146
+ const check = checkConfirmation({
147
+ mode: env.mode,
148
+ confirm,
149
+ actionDescription: `Sacar ${formatBRL(payload.amount)} ${destinationDesc}.`,
150
+ });
151
+ if (!check.shouldExecute) {
152
+ return { content: [{ type: "text", text: check.warningMessage }] };
153
+ }
154
+ const result = await client.request("POST", "/v1/balance/withdrawals", { body: payload });
155
+ return {
156
+ content: [
157
+ { type: "text", text: `Saque solicitado com sucesso.\n\n${JSON.stringify(result, null, 2)}` },
158
+ ],
159
+ };
160
+ });
161
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Grupo C (parte 2) — Assinaturas e faturas (2 ferramentas)
3
+ *
4
+ * list_subscriptions, list_invoices
5
+ */
6
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
+ import type { AtehraClient } from "../client.js";
8
+ export declare function registerBillingTools(server: McpServer, client: AtehraClient): void;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Grupo C (parte 2) — Assinaturas e faturas (2 ferramentas)
3
+ *
4
+ * list_subscriptions, list_invoices
5
+ */
6
+ import { z } from "zod";
7
+ export function registerBillingTools(server, client) {
8
+ // ── list_subscriptions ─────────────────────────────────────────────────────
9
+ server.tool("list_subscriptions", "Lista assinaturas da conta com filtro opcional por status. Status possíveis: active (ativa), trialing (em teste grátis), past_due (atrasada), suspended (suspensa por inadimplência), cancelled (cancelada). Use pra ver quem está pagando, quem cancelou, quem está em risco.", {
10
+ status: z
11
+ .enum(["active", "trialing", "past_due", "suspended", "cancelled"])
12
+ .optional()
13
+ .describe("Filtrar por status. Sem filtro = todas."),
14
+ limit: z.number().int().positive().max(100).optional().describe("Quantas retornar (default: 20)."),
15
+ }, async ({ status, limit }) => {
16
+ const result = await client.request("GET", "/v1/subscriptions", { query: { status, limit: limit ?? 20 } });
17
+ if (result.data.length === 0) {
18
+ return { content: [{ type: "text", text: "Nenhuma assinatura encontrada com esses filtros." }] };
19
+ }
20
+ const formatBRL = (cents) => `R$ ${(cents / 100).toLocaleString("pt-BR", { minimumFractionDigits: 2 })}`;
21
+ const lines = [
22
+ `Total: ${result.total}`,
23
+ `Mostrando ${result.data.length}:`,
24
+ ``,
25
+ ...result.data.map((s, i) => {
26
+ const customer = s.customer?.email ?? "—";
27
+ const plan = s.plan ? `${s.plan.name} (${formatBRL(s.plan.price)})` : "—";
28
+ const renewsAt = s.currentPeriodEnd
29
+ ? new Date(s.currentPeriodEnd).toLocaleDateString("pt-BR")
30
+ : "—";
31
+ return `${i + 1}. ${customer} · ${plan} · ${s.status} · próximo ciclo: ${renewsAt} (id=${s.id})`;
32
+ }),
33
+ ];
34
+ return { content: [{ type: "text", text: lines.join("\n") }] };
35
+ });
36
+ // ── list_invoices ──────────────────────────────────────────────────────────
37
+ server.tool("list_invoices", "Lista faturas (cobranças geradas, dentro ou fora de assinatura) com filtro opcional por status e período. Status: paid (paga), pending (pendente), overdue (vencida), refunded. Use pra ver fluxo de pagamentos, identificar inadimplência, gerar relatórios.", {
38
+ status: z.enum(["paid", "pending", "overdue", "refunded"]).optional().describe("Filtrar por status."),
39
+ period: z
40
+ .string()
41
+ .regex(/^\d{4}-\d{2}$/)
42
+ .optional()
43
+ .describe("Período YYYY-MM. Default: todas."),
44
+ }, async ({ status, period }) => {
45
+ const invoices = await client.request("GET", "/v1/billing/invoices", { query: { status, period } });
46
+ if (invoices.length === 0) {
47
+ return { content: [{ type: "text", text: "Nenhuma fatura encontrada com esses filtros." }] };
48
+ }
49
+ const formatBRL = (cents) => `R$ ${(cents / 100).toLocaleString("pt-BR", { minimumFractionDigits: 2 })}`;
50
+ const lines = [
51
+ `Total de faturas: ${invoices.length}`,
52
+ ``,
53
+ ...invoices.slice(0, 50).map((inv, i) => {
54
+ const customer = inv.customer?.email ?? "—";
55
+ const due = inv.dueDate ? new Date(inv.dueDate).toLocaleDateString("pt-BR") : "—";
56
+ const paid = inv.paidAt ? ` · paga em ${new Date(inv.paidAt).toLocaleDateString("pt-BR")}` : "";
57
+ return `${i + 1}. ${customer} · ${formatBRL(inv.amount)} · ${inv.status} · vence ${due}${paid} (id=${inv.id})`;
58
+ }),
59
+ invoices.length > 50 ? `\n(mostrando primeiras 50 de ${invoices.length})` : "",
60
+ ].filter(Boolean);
61
+ return { content: [{ type: "text", text: lines.join("\n") }] };
62
+ });
63
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Grupo B (parte 3) — Cobranças (3 ferramentas)
3
+ *
4
+ * create_checkout_session, create_checkout_link, list_recent_orders
5
+ *
6
+ * IMPORTANTE: este MCP só expõe cobrança em PIX e BOLETO.
7
+ * Cobrança em CREDIT_CARD exige dados sensíveis do cartão e deve ser feita
8
+ * via dashboard ou checkout link público — não via agente de IA.
9
+ */
10
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
11
+ import type { AtehraClient } from "../client.js";
12
+ export declare function registerChargeTools(server: McpServer, client: AtehraClient): void;
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Grupo B (parte 3) — Cobranças (3 ferramentas)
3
+ *
4
+ * create_checkout_session, create_checkout_link, list_recent_orders
5
+ *
6
+ * IMPORTANTE: este MCP só expõe cobrança em PIX e BOLETO.
7
+ * Cobrança em CREDIT_CARD exige dados sensíveis do cartão e deve ser feita
8
+ * via dashboard ou checkout link público — não via agente de IA.
9
+ */
10
+ import { z } from "zod";
11
+ export function registerChargeTools(server, client) {
12
+ // ── create_checkout_session ────────────────────────────────────────────────
13
+ server.tool("create_checkout_session", "Cria uma cobrança imediata em PIX ou BOLETO pra um cliente específico em cima de um plano. Retorna o link de pagamento e (se PIX) o QR code. Use depois de ter o cliente (create_customer) e o plano (create_plan). Pra cartão de crédito, use create_checkout_link em vez (cartão exige dados sensíveis).", {
14
+ customerId: z.string().describe("ID interno Atehra do cliente (do create_customer ou list_customers)."),
15
+ planId: z.string().describe("ID do plano a cobrar (do create_plan)."),
16
+ taxId: z
17
+ .string()
18
+ .describe("CPF ou CNPJ do cliente final (só números, ex: '12345678900'). Obrigatório pra geração de cobrança."),
19
+ billingType: z.enum(["PIX", "BOLETO"]).describe("Método de cobrança: PIX (instantâneo) ou BOLETO."),
20
+ couponCode: z.string().optional().describe("Código de cupom de desconto, se aplicável."),
21
+ }, async (args) => {
22
+ const session = await client.request("POST", "/v1/checkout/session", {
23
+ body: args,
24
+ });
25
+ return {
26
+ content: [
27
+ {
28
+ type: "text",
29
+ text: [
30
+ `Cobrança criada com sucesso (${args.billingType}).`,
31
+ ``,
32
+ `Dados retornados pela plataforma:`,
33
+ JSON.stringify(session, null, 2),
34
+ ``,
35
+ `Compartilhe o link/QR com o cliente. A plataforma vai te avisar via webhook quando ele pagar.`,
36
+ ].join("\n"),
37
+ },
38
+ ],
39
+ };
40
+ });
41
+ // ── create_checkout_link ───────────────────────────────────────────────────
42
+ server.tool("create_checkout_link", "Gera um link de pagamento reutilizável pra um plano. O cliente abre o link, escolhe o método (PIX, boleto ou cartão) e paga. Bom pra divulgação em redes sociais, email, WhatsApp. Diferente de create_checkout_session, este é genérico (não exige cliente nem CPF antes).", {
43
+ planId: z.string().describe("ID do plano a vender."),
44
+ name: z.string().optional().describe("Label interno do link (ex: 'Black Friday 2026'). Só pra organização."),
45
+ slug: z
46
+ .string()
47
+ .regex(/^[a-zA-Z0-9_-]+$/)
48
+ .min(3)
49
+ .max(64)
50
+ .optional()
51
+ .describe("Slug customizado da URL. Auto-gerado se omitido."),
52
+ successUrl: z.string().url().optional().describe("URL pra redirecionar após pagamento bem-sucedido."),
53
+ cancelUrl: z.string().url().optional().describe("URL pra redirecionar se o cliente cancelar."),
54
+ maxUses: z.number().int().positive().optional().describe("Máximo de usos permitidos. Default: ilimitado."),
55
+ expiresAt: z.string().datetime().optional().describe("Data/hora de expiração ISO 8601. Default: nunca expira."),
56
+ }, async (args) => {
57
+ const link = await client.request("POST", "/v1/checkout/links", {
58
+ body: args,
59
+ });
60
+ return {
61
+ content: [
62
+ {
63
+ type: "text",
64
+ text: `Link de pagamento criado:\n\n${JSON.stringify(link, null, 2)}`,
65
+ },
66
+ ],
67
+ };
68
+ });
69
+ // ── list_recent_orders ─────────────────────────────────────────────────────
70
+ server.tool("list_recent_orders", "Lista as vendas (orders) mais recentes da conta. Cada venda tem cliente, plano, valor, status (pending/paid/refunded/cancelled) e data. Use pra ver atividade recente, identificar quem pagou ou quem está pendente.", {
71
+ status: z
72
+ .enum(["pending", "paid", "refunded", "cancelled"])
73
+ .optional()
74
+ .describe("Filtrar por status."),
75
+ customerId: z.string().optional().describe("Filtrar por cliente específico."),
76
+ limit: z.number().int().positive().max(50).optional().describe("Quantas vendas retornar (default: 10, máx 50)."),
77
+ }, async ({ status, customerId, limit }) => {
78
+ const result = await client.request("GET", "/v1/orders", {
79
+ query: { status, customerId, limit: limit ?? 10 },
80
+ });
81
+ if (result.data.length === 0) {
82
+ return { content: [{ type: "text", text: "Nenhuma venda encontrada com esses filtros." }] };
83
+ }
84
+ const formatBRL = (cents) => `R$ ${(cents / 100).toLocaleString("pt-BR", { minimumFractionDigits: 2 })}`;
85
+ const lines = [
86
+ `Total de vendas que batem com o filtro: ${result.total}`,
87
+ `Mostrando ${result.data.length}:`,
88
+ ``,
89
+ ...result.data.map((o, i) => {
90
+ const customer = o.customer?.email ?? "—";
91
+ const plan = o.plan?.name ?? "—";
92
+ const date = new Date(o.createdAt).toLocaleDateString("pt-BR");
93
+ return `${i + 1}. ${customer} · ${plan} · ${formatBRL(o.amount)} · ${o.status} · ${date} (id=${o.id})`;
94
+ }),
95
+ ];
96
+ return { content: [{ type: "text", text: lines.join("\n") }] };
97
+ });
98
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Grupo B (parte 1) — Clientes (2 ferramentas)
3
+ *
4
+ * create_customer, list_customers
5
+ */
6
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
+ import type { AtehraClient } from "../client.js";
8
+ export declare function registerCustomerTools(server: McpServer, client: AtehraClient): void;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Grupo B (parte 1) — Clientes (2 ferramentas)
3
+ *
4
+ * create_customer, list_customers
5
+ */
6
+ import { z } from "zod";
7
+ export function registerCustomerTools(server, client) {
8
+ // ── create_customer ────────────────────────────────────────────────────────
9
+ server.tool("create_customer", "Cria um novo cliente final no catálogo da Atehra. Esse cliente é vinculado à conta da Atehra do usuário (não ao Asaas). Use antes de criar cobrança ou assinatura pra um cliente novo. Se o cliente já existe, use list_customers pra encontrar.", {
10
+ externalId: z
11
+ .string()
12
+ .min(1)
13
+ .describe("ID único do cliente no seu sistema (ex: 'user_123', 'cliente-acme'). Não pode repetir."),
14
+ email: z.string().email().describe("Email do cliente. Usado pra cobranças, dunning, comunicação."),
15
+ name: z.string().optional().describe("Nome do cliente. Opcional mas recomendado pra personalização."),
16
+ }, async ({ externalId, email, name }) => {
17
+ const customer = await client.request("POST", "/v1/customers", { body: { externalId, email, name } });
18
+ return {
19
+ content: [
20
+ {
21
+ type: "text",
22
+ text: [
23
+ `Cliente criado:`,
24
+ ` ID interno Atehra: ${customer.id}`,
25
+ ` ID externo: ${customer.externalId}`,
26
+ ` Email: ${customer.email}`,
27
+ ` Nome: ${customer.name ?? "(sem nome)"}`,
28
+ ``,
29
+ `Use o ID interno (${customer.id}) pra criar cobranças e assinaturas.`,
30
+ ].join("\n"),
31
+ },
32
+ ],
33
+ };
34
+ });
35
+ // ── list_customers ─────────────────────────────────────────────────────────
36
+ server.tool("list_customers", "Lista clientes do catálogo com paginação. Use pra encontrar um cliente existente pelo email ou nome, ou pra ver quantos clientes a conta tem. Retorna até 20 clientes por chamada.", {
37
+ page: z.number().int().positive().optional().describe("Página (default: 1)."),
38
+ limit: z.number().int().positive().max(100).optional().describe("Quantos por página (default: 20, máximo 100)."),
39
+ }, async ({ page, limit }) => {
40
+ const result = await client.request("GET", "/v1/customers", {
41
+ query: { page: page ?? 1, limit: limit ?? 20 },
42
+ });
43
+ if (result.data.length === 0) {
44
+ return { content: [{ type: "text", text: "Nenhum cliente cadastrado ainda." }] };
45
+ }
46
+ const lines = [
47
+ `Total de clientes: ${result.total}`,
48
+ `Mostrando página ${result.page} (${result.data.length} de ${result.total})`,
49
+ ``,
50
+ ...result.data.map((c, i) => `${i + 1}. ${c.email}${c.name ? ` (${c.name})` : ""} — id=${c.id} · externalId=${c.externalId}`),
51
+ ];
52
+ return { content: [{ type: "text", text: lines.join("\n") }] };
53
+ });
54
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Grupo D — Régua de cobrança (2 ferramentas)
3
+ *
4
+ * get_dunning_config, update_dunning_config
5
+ *
6
+ * A régua define o que acontece quando um cliente atrasa:
7
+ * - retryDays: em quais dias após o vencimento a régua dispara
8
+ * - actions: o que fazer em cada dia (retry, email, whatsapp, suspend)
9
+ *
10
+ * IMPORTANTE: as ações 'email' e 'whatsapp' DISPARAM WEBHOOK pra o sistema
11
+ * do cliente integrar. A Atehra não envia diretamente.
12
+ */
13
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
14
+ import type { AtehraClient } from "../client.js";
15
+ export declare function registerDunningTools(server: McpServer, client: AtehraClient): void;
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Grupo D — Régua de cobrança (2 ferramentas)
3
+ *
4
+ * get_dunning_config, update_dunning_config
5
+ *
6
+ * A régua define o que acontece quando um cliente atrasa:
7
+ * - retryDays: em quais dias após o vencimento a régua dispara
8
+ * - actions: o que fazer em cada dia (retry, email, whatsapp, suspend)
9
+ *
10
+ * IMPORTANTE: as ações 'email' e 'whatsapp' DISPARAM WEBHOOK pra o sistema
11
+ * do cliente integrar. A Atehra não envia diretamente.
12
+ */
13
+ import { z } from "zod";
14
+ export function registerDunningTools(server, client) {
15
+ // ── get_dunning_config ─────────────────────────────────────────────────────
16
+ server.tool("get_dunning_config", "Retorna a configuração atual da régua de cobrança: em quais dias após o vencimento ela age, e o que faz em cada dia (tentar cobrar de novo, disparar webhook pra email, disparar webhook pra WhatsApp, suspender acesso).", {}, async () => {
17
+ const raw = await client.request("GET", "/v1/dunning/config");
18
+ // A API retorna { configured: false } quando nunca foi configurada
19
+ if ("configured" in raw && raw.configured === false) {
20
+ return {
21
+ content: [
22
+ {
23
+ type: "text",
24
+ text: [
25
+ `Régua de cobrança ainda não configurada.`,
26
+ ``,
27
+ `Use update_dunning_config pra criar uma régua. Exemplo de régua agressiva:`,
28
+ ` retryDays: [1, 3, 5, 7]`,
29
+ ` actions: [`,
30
+ ` { day: 1, action: "retry" },`,
31
+ ` { day: 3, action: "email" },`,
32
+ ` { day: 5, action: "whatsapp" },`,
33
+ ` { day: 7, action: "suspend" }`,
34
+ ` ]`,
35
+ ].join("\n"),
36
+ },
37
+ ],
38
+ };
39
+ }
40
+ const config = raw;
41
+ const lines = [
42
+ `Régua de cobrança atual:`,
43
+ ``,
44
+ `Dias de tentativa após o vencimento: ${config.retryDays.join(", ")}`,
45
+ ``,
46
+ `Ações configuradas:`,
47
+ ...config.actions.map((a) => ` Dia ${a.day}: ${describeAction(a.action)}`),
48
+ ``,
49
+ config.whatsappMessage
50
+ ? `Mensagem WhatsApp configurada: "${config.whatsappMessage}"`
51
+ : `Mensagem WhatsApp: usando default da plataforma`,
52
+ ``,
53
+ `Nota: 'email' e 'whatsapp' disparam webhook pra o sistema do cliente integrar com o provedor dele (Resend, Twilio, etc.). A Atehra não envia diretamente.`,
54
+ ];
55
+ return { content: [{ type: "text", text: lines.join("\n") }] };
56
+ });
57
+ // ── update_dunning_config ──────────────────────────────────────────────────
58
+ server.tool("update_dunning_config", "Atualiza a régua de cobrança. Defina em quais dias após o vencimento ela age e o que faz. Exemplo de régua agressiva: retry no dia 1, email no dia 3, WhatsApp no dia 5, suspende no dia 7. Use linguagem natural do usuário pra montar.", {
59
+ retryDays: z
60
+ .array(z.number().int().positive())
61
+ .min(1)
62
+ .describe("Lista de dias após o vencimento em que a régua roda. Dias devem ser positivos (1, 3, 5...). Ex: [1, 3, 5, 7]."),
63
+ actions: z
64
+ .array(z.object({
65
+ day: z.number().int().positive().describe("Em qual dia da retryDays a ação acontece. Deve ser positivo."),
66
+ action: z
67
+ .enum(["retry", "email", "whatsapp", "suspend"])
68
+ .describe("retry = nova tentativa de cobrança (cria PIX como alternativa). email = dispara webhook dunning.email. whatsapp = dispara webhook dunning.whatsapp. suspend = bloqueia acesso do cliente (revoga entitlements)."),
69
+ }))
70
+ .describe("Lista de ações em cada dia. Pode ter múltiplas ações no mesmo dia."),
71
+ whatsappMessage: z
72
+ .string()
73
+ .optional()
74
+ .describe("Template da mensagem WhatsApp. Default: 'Olá! Identificamos um pagamento pendente de R$X. Podemos ajudar?'"),
75
+ }, async (args) => {
76
+ const updated = await client.request("PUT", "/v1/dunning/config", { body: args });
77
+ return {
78
+ content: [
79
+ {
80
+ type: "text",
81
+ text: `Régua atualizada com sucesso.\n\nNova configuração:\n${JSON.stringify(updated, null, 2)}`,
82
+ },
83
+ ],
84
+ };
85
+ });
86
+ }
87
+ function describeAction(action) {
88
+ switch (action) {
89
+ case "retry":
90
+ return "tentar nova cobrança (cria PIX alternativo via Asaas)";
91
+ case "email":
92
+ return "disparar webhook 'dunning.email' (cliente integra com seu provedor)";
93
+ case "whatsapp":
94
+ return "disparar webhook 'dunning.whatsapp' (cliente integra com seu provedor)";
95
+ case "suspend":
96
+ return "suspender assinatura (revoga acesso do cliente final)";
97
+ default:
98
+ return action;
99
+ }
100
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Grupo C (parte 1) — Métricas (1 ferramenta)
3
+ *
4
+ * get_metrics_overview
5
+ */
6
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
+ import type { AtehraClient } from "../client.js";
8
+ export declare function registerMetricsTools(server: McpServer, client: AtehraClient): void;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Grupo C (parte 1) — Métricas (1 ferramenta)
3
+ *
4
+ * get_metrics_overview
5
+ */
6
+ import { z } from "zod";
7
+ export function registerMetricsTools(server, client) {
8
+ server.tool("get_metrics_overview", "Retorna a visão geral dos números do negócio pra um período: receita total, MRR (receita recorrente mensal), assinaturas ativas, total de clientes, taxa de cancelamento (churn), ARPU (receita média por cliente) e LTV (valor total do cliente). Use sempre que o usuário perguntar como o negócio está indo, ou pedir números do mês/comparação entre períodos.", {
9
+ period: z
10
+ .string()
11
+ .regex(/^\d{4}-\d{2}$/, "Formato deve ser YYYY-MM (ex: '2026-05').")
12
+ .optional()
13
+ .describe("Período no formato YYYY-MM. Default: mês atual."),
14
+ }, async ({ period }) => {
15
+ const metrics = await client.request("GET", "/v1/metrics/overview", {
16
+ query: period ? { period } : undefined,
17
+ });
18
+ const formatBRL = (cents) => `R$ ${(cents / 100).toLocaleString("pt-BR", { minimumFractionDigits: 2 })}`;
19
+ return {
20
+ content: [
21
+ {
22
+ type: "text",
23
+ text: [
24
+ `Visão geral — período ${metrics.period}`,
25
+ ``,
26
+ `Receita total no período: ${formatBRL(metrics.revenue)}`,
27
+ `MRR (receita recorrente mensal): ${formatBRL(metrics.mrr)}`,
28
+ `ARR (receita recorrente anual projetada): ${formatBRL(metrics.arr)}`,
29
+ ``,
30
+ `Assinaturas ativas: ${metrics.activeSubscriptions}`,
31
+ `Em teste grátis: ${metrics.trialingSubscriptions}`,
32
+ `Total de clientes: ${metrics.totalCustomers}`,
33
+ ``,
34
+ `ARPU (receita média por cliente): ${formatBRL(metrics.arpu)}`,
35
+ `LTV (valor total estimado por cliente): ${formatBRL(metrics.ltv)}`,
36
+ `Taxa de cancelamento (churn): ${metrics.churnRate.toFixed(2)}%`,
37
+ ].join("\n"),
38
+ },
39
+ ],
40
+ };
41
+ });
42
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Grupo B (parte 2) — Planos (1 ferramenta)
3
+ *
4
+ * create_plan
5
+ *
6
+ * A plataforma Atehra é baseada em planos. Toda cobrança é cobrança de um plano.
7
+ * Pra cobrar valor avulso arbitrário (ex: "R$ 2.000"), o caller precisa criar
8
+ * um plano com aquele preço primeiro e depois criar a checkout session.
9
+ */
10
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
11
+ import type { AtehraClient } from "../client.js";
12
+ export declare function registerPlanTools(server: McpServer, client: AtehraClient): void;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Grupo B (parte 2) — Planos (1 ferramenta)
3
+ *
4
+ * create_plan
5
+ *
6
+ * A plataforma Atehra é baseada em planos. Toda cobrança é cobrança de um plano.
7
+ * Pra cobrar valor avulso arbitrário (ex: "R$ 2.000"), o caller precisa criar
8
+ * um plano com aquele preço primeiro e depois criar a checkout session.
9
+ */
10
+ import { z } from "zod";
11
+ export function registerPlanTools(server, client) {
12
+ server.tool("create_plan", "Cria um plano de produto. Toda cobrança na Atehra é feita em cima de um plano. Use 'one_time' pra cobrança avulsa (ex: 'Pacote 2000') ou 'subscription' pra assinatura recorrente (mensal/anual). Depois use create_checkout_session ou create_checkout_link pra cobrar.", {
13
+ name: z.string().min(1).describe("Nome legível do plano (ex: 'Plano Premium', 'Cobrança Avulsa 2000')."),
14
+ slug: z
15
+ .string()
16
+ .regex(/^[a-z0-9_-]+$/, "Slug deve ter só letras minúsculas, números, hifens e underscores.")
17
+ .min(1)
18
+ .describe("Slug único usado em URLs (ex: 'premium', 'avulso_2000', 'pacote-pro')."),
19
+ price: z
20
+ .number()
21
+ .int()
22
+ .positive()
23
+ .describe("Preço em centavos (ex: 200000 = R$ 2.000,00)."),
24
+ type: z
25
+ .enum(["subscription", "one_time"])
26
+ .describe("'subscription' pra recorrente. 'one_time' pra pagamento único (cobrança avulsa)."),
27
+ interval: z
28
+ .enum(["month", "year"])
29
+ .optional()
30
+ .describe("Apenas pra type=subscription: 'month' ou 'year'. Default: 'month'."),
31
+ description: z.string().optional().describe("Descrição opcional pro cliente final."),
32
+ trialDays: z.number().int().nonnegative().optional().describe("Dias de teste grátis. Só pra assinatura."),
33
+ }, async ({ name, slug, price, type, interval, description, trialDays }) => {
34
+ const body = { name, slug, price, type };
35
+ if (description !== undefined)
36
+ body["description"] = description;
37
+ if (type === "subscription") {
38
+ body["interval"] = interval ?? "month";
39
+ if (trialDays !== undefined) {
40
+ body["trialType"] = "time";
41
+ body["trialDays"] = trialDays;
42
+ }
43
+ }
44
+ const plan = await client.request("POST", "/v1/plans", { body });
45
+ const formatBRL = (cents) => `R$ ${(cents / 100).toLocaleString("pt-BR", { minimumFractionDigits: 2 })}`;
46
+ return {
47
+ content: [
48
+ {
49
+ type: "text",
50
+ text: [
51
+ `Plano criado:`,
52
+ ` ID: ${plan.id}`,
53
+ ` Nome: ${plan.name}`,
54
+ ` Slug: ${plan.slug}`,
55
+ ` Tipo: ${plan.type}${type === "subscription" ? ` (${interval ?? "month"})` : ""}`,
56
+ ` Preço: ${formatBRL(plan.price)}`,
57
+ ``,
58
+ `Use o ID do plano (${plan.id}) pra criar cobranças via create_checkout_session ou links via create_checkout_link.`,
59
+ ].join("\n"),
60
+ },
61
+ ],
62
+ };
63
+ });
64
+ }
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@atehra/mcp",
3
+ "version": "0.1.0",
4
+ "description": "Servidor MCP oficial da Atehra — opere a infraestrutura financeira BR via Claude, Cursor, ChatGPT e qualquer cliente MCP",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "atehra-mcp": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "README.md"
13
+ ],
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "dev": "tsc --watch",
17
+ "start": "node dist/index.js",
18
+ "smoke": "node --test dist/tests/smoke.test.js",
19
+ "prepublishOnly": "npm run build"
20
+ },
21
+ "keywords": [
22
+ "atehra",
23
+ "mcp",
24
+ "model-context-protocol",
25
+ "fintech",
26
+ "billing",
27
+ "brazil",
28
+ "pix",
29
+ "boleto",
30
+ "claude",
31
+ "cursor"
32
+ ],
33
+ "license": "MIT",
34
+ "homepage": "https://atehra.com/mcp",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "https://github.com/atehra/atehra.git",
38
+ "directory": "mcp"
39
+ },
40
+ "dependencies": {
41
+ "@modelcontextprotocol/sdk": "^1.0.4",
42
+ "zod": "^3.23.8"
43
+ },
44
+ "devDependencies": {
45
+ "@types/node": "^22.0.0",
46
+ "typescript": "^5.5.0"
47
+ },
48
+ "engines": {
49
+ "node": ">=18"
50
+ }
51
+ }