mcp-supabase-crm-biancode 1.0.0 → 1.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/dist/client.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * HTTP client for CRM Biancode Edge Functions (list-sellers, create-sale).
2
+ * HTTP client for CRM Biancode Edge Functions.
3
3
  * Auth via x-api-token header.
4
4
  */
5
5
  export interface Seller {
@@ -13,8 +13,36 @@ export interface Seller {
13
13
  export interface ListSellersResponse {
14
14
  sellers: Seller[];
15
15
  }
16
+ export declare function getSellers(): Promise<ListSellersResponse>;
17
+ export interface Client {
18
+ id: string;
19
+ name: string;
20
+ email?: string | null;
21
+ phone?: string | null;
22
+ cpf_cnpj?: string | null;
23
+ project_name?: string | null;
24
+ }
25
+ export interface SearchClientsResponse {
26
+ clients: Client[];
27
+ total: number;
28
+ }
29
+ export interface CreateClientBody {
30
+ name: string;
31
+ email?: string;
32
+ phone?: string;
33
+ cpf_cnpj?: string;
34
+ address?: string;
35
+ project_name?: string;
36
+ }
37
+ export interface CreateClientResponse {
38
+ success: boolean;
39
+ client: Client;
40
+ }
41
+ export declare function searchClients(search?: string): Promise<SearchClientsResponse>;
42
+ export declare function createClient(body: CreateClientBody): Promise<CreateClientResponse>;
16
43
  export interface CreateSaleBody {
17
- client_id: string;
44
+ client_name?: string;
45
+ client_id?: string;
18
46
  title: string;
19
47
  total_amount: number;
20
48
  sale_date: string;
@@ -24,8 +52,10 @@ export interface CreateSaleBody {
24
52
  installment_start_date?: string;
25
53
  status?: string;
26
54
  goal_id?: string;
55
+ payment_method?: string;
27
56
  tax_percentage?: number;
28
57
  gateway_fee_percentage?: number;
58
+ seller_names?: string[];
29
59
  sellers?: Array<{
30
60
  seller_id: string;
31
61
  commission_amount: number;
@@ -34,9 +64,13 @@ export interface CreateSaleBody {
34
64
  export interface CreateSaleResponse {
35
65
  success: boolean;
36
66
  sale_id: string;
67
+ client_name: string | null;
68
+ goal_id: string | null;
37
69
  installments_created: number;
38
70
  receivables_created: number;
39
71
  sellers_linked: number;
72
+ seller_names?: string[];
73
+ tax_percentage_applied: number;
74
+ gateway_fee_percentage_applied: number;
40
75
  }
41
- export declare function getSellers(): Promise<ListSellersResponse>;
42
76
  export declare function createSale(body: CreateSaleBody): Promise<CreateSaleResponse>;
package/dist/client.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * HTTP client for CRM Biancode Edge Functions (list-sellers, create-sale).
2
+ * HTTP client for CRM Biancode Edge Functions.
3
3
  * Auth via x-api-token header.
4
4
  */
5
5
  import pRetry from "p-retry";
@@ -36,7 +36,7 @@ async function request(path, token, options = {}) {
36
36
  }
37
37
  if (!res.ok) {
38
38
  const text = await res.text();
39
- throw new Error(`CRM API ${res.status}: ${text.slice(0, 300)}`);
39
+ throw new Error(`CRM API ${res.status}: ${text.slice(0, 500)}`);
40
40
  }
41
41
  return res.json();
42
42
  }
@@ -57,6 +57,18 @@ export async function getSellers() {
57
57
  const { token } = getConfig();
58
58
  return request("/list-sellers", token);
59
59
  }
60
+ export async function searchClients(search) {
61
+ const { token } = getConfig();
62
+ const qs = search ? `?search=${encodeURIComponent(search)}` : "";
63
+ return request(`/list-clients${qs}`, token);
64
+ }
65
+ export async function createClient(body) {
66
+ const { token } = getConfig();
67
+ return request("/create-client", token, {
68
+ method: "POST",
69
+ body: JSON.stringify(body),
70
+ });
71
+ }
60
72
  export async function createSale(body) {
61
73
  const { token } = getConfig();
62
74
  return request("/create-sale", token, {
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
5
5
  import { createServer } from "node:http";
6
6
  import { sellersList } from "./tools/sellers.js";
7
7
  import { createSaleTool } from "./tools/sales.js";
8
+ import { clientsSearchTool, clientCreateTool } from "./tools/clients.js";
8
9
  import * as schemas from "./schemas/inputs.js";
9
10
  function safe(name, fn) {
10
11
  return async (args) => {
@@ -20,16 +21,26 @@ function safe(name, fn) {
20
21
  }
21
22
  const server = new McpServer({
22
23
  name: "supabase-crm-biancode",
23
- version: "1.0.0",
24
+ version: "1.1.0",
24
25
  });
26
+ server.registerTool("clients_search", {
27
+ title: "Buscar clientes",
28
+ description: "Busca clientes pelo nome no CRM. Use para encontrar o cliente antes de cadastrar uma venda, ou para verificar se um cliente ja existe.",
29
+ inputSchema: schemas.clientsSearchSchema,
30
+ }, safe("clients_search", (a) => clientsSearchTool(a)));
31
+ server.registerTool("client_create", {
32
+ title: "Cadastrar cliente",
33
+ description: "Cadastra um novo cliente no CRM. Apenas o nome e obrigatorio. Use quando o cliente nao for encontrado pela busca.",
34
+ inputSchema: schemas.clientCreateSchema,
35
+ }, safe("client_create", (a) => clientCreateTool(a)));
25
36
  server.registerTool("sellers_list", {
26
- title: "List sellers",
27
- description: "Lista vendedores cadastrados no CRM. Use para obter os IDs ao cadastrar uma venda e escolher quem participou (comissao).",
37
+ title: "Listar vendedores",
38
+ description: "Lista todos os vendedores cadastrados no CRM com nomes e percentuais de comissao.",
28
39
  inputSchema: schemas.sellersListSchema,
29
40
  }, safe("sellers_list", (a) => sellersList(a)));
30
41
  server.registerTool("create_sale", {
31
- title: "Create sale",
32
- description: "Cadastra nova venda no CRM. Insere em sales, sale_installments, accounts_receivable e sale_sellers. Chame sellers_list antes para obter IDs dos vendedores.",
42
+ title: "Cadastrar venda",
43
+ description: "Cadastra nova venda no CRM. Aceita o NOME do cliente (nao precisa de UUID). Aceita NOMES dos vendedores e calcula comissao automaticamente. Campos minimos: client_name, title, total_amount, sale_date.",
33
44
  inputSchema: schemas.createSaleSchema,
34
45
  }, safe("create_sale", (a) => createSaleTool(a)));
35
46
  const port = parseInt(process.env.MCP_PORT ?? "", 10);
@@ -1,70 +1,68 @@
1
1
  import { z } from "zod";
2
2
  export declare const sellersListSchema: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
3
- export declare const sellerEntrySchema: z.ZodObject<{
4
- seller_id: z.ZodString;
5
- commission_amount: z.ZodNumber;
3
+ export declare const clientsSearchSchema: z.ZodObject<{
4
+ search: z.ZodString;
6
5
  }, "strip", z.ZodTypeAny, {
7
- seller_id: string;
8
- commission_amount: number;
6
+ search: string;
9
7
  }, {
10
- seller_id: string;
11
- commission_amount: number;
8
+ search: string;
9
+ }>;
10
+ export declare const clientCreateSchema: z.ZodObject<{
11
+ name: z.ZodString;
12
+ email: z.ZodOptional<z.ZodString>;
13
+ phone: z.ZodOptional<z.ZodString>;
14
+ cpf_cnpj: z.ZodOptional<z.ZodString>;
15
+ address: z.ZodOptional<z.ZodString>;
16
+ project_name: z.ZodOptional<z.ZodString>;
17
+ }, "strip", z.ZodTypeAny, {
18
+ name: string;
19
+ email?: string | undefined;
20
+ phone?: string | undefined;
21
+ cpf_cnpj?: string | undefined;
22
+ address?: string | undefined;
23
+ project_name?: string | undefined;
24
+ }, {
25
+ name: string;
26
+ email?: string | undefined;
27
+ phone?: string | undefined;
28
+ cpf_cnpj?: string | undefined;
29
+ address?: string | undefined;
30
+ project_name?: string | undefined;
12
31
  }>;
13
32
  export declare const createSaleSchema: z.ZodObject<{
14
- client_id: z.ZodString;
33
+ client_name: z.ZodString;
15
34
  title: z.ZodString;
16
35
  total_amount: z.ZodNumber;
17
36
  sale_date: z.ZodString;
18
- description: z.ZodOptional<z.ZodString>;
37
+ seller_names: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
19
38
  installments: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
20
- due_day: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
39
+ payment_method: z.ZodDefault<z.ZodOptional<z.ZodEnum<["pix", "cartao_parcelado", "pix_parcelado"]>>>;
40
+ description: z.ZodOptional<z.ZodString>;
41
+ due_day: z.ZodOptional<z.ZodNumber>;
21
42
  installment_start_date: z.ZodOptional<z.ZodString>;
22
- status: z.ZodDefault<z.ZodOptional<z.ZodString>>;
23
- goal_id: z.ZodOptional<z.ZodString>;
24
- tax_percentage: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
25
- gateway_fee_percentage: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
26
- sellers: z.ZodOptional<z.ZodArray<z.ZodObject<{
27
- seller_id: z.ZodString;
28
- commission_amount: z.ZodNumber;
29
- }, "strip", z.ZodTypeAny, {
30
- seller_id: string;
31
- commission_amount: number;
32
- }, {
33
- seller_id: string;
34
- commission_amount: number;
35
- }>, "many">>;
43
+ client_id: z.ZodOptional<z.ZodString>;
36
44
  }, "strip", z.ZodTypeAny, {
37
- status: string;
38
- client_id: string;
45
+ client_name: string;
39
46
  title: string;
40
47
  total_amount: number;
41
48
  sale_date: string;
42
49
  installments: number;
43
- due_day: number;
44
- tax_percentage: number;
45
- gateway_fee_percentage: number;
50
+ payment_method: "pix" | "cartao_parcelado" | "pix_parcelado";
51
+ seller_names?: string[] | undefined;
46
52
  description?: string | undefined;
53
+ due_day?: number | undefined;
47
54
  installment_start_date?: string | undefined;
48
- goal_id?: string | undefined;
49
- sellers?: {
50
- seller_id: string;
51
- commission_amount: number;
52
- }[] | undefined;
55
+ client_id?: string | undefined;
53
56
  }, {
54
- client_id: string;
57
+ client_name: string;
55
58
  title: string;
56
59
  total_amount: number;
57
60
  sale_date: string;
58
- status?: string | undefined;
59
- description?: string | undefined;
61
+ seller_names?: string[] | undefined;
60
62
  installments?: number | undefined;
63
+ payment_method?: "pix" | "cartao_parcelado" | "pix_parcelado" | undefined;
64
+ description?: string | undefined;
61
65
  due_day?: number | undefined;
62
66
  installment_start_date?: string | undefined;
63
- goal_id?: string | undefined;
64
- tax_percentage?: number | undefined;
65
- gateway_fee_percentage?: number | undefined;
66
- sellers?: {
67
- seller_id: string;
68
- commission_amount: number;
69
- }[] | undefined;
67
+ client_id?: string | undefined;
70
68
  }>;
@@ -1,25 +1,59 @@
1
1
  import { z } from "zod";
2
2
  const dateStr = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Formato YYYY-MM-DD");
3
3
  export const sellersListSchema = z.object({});
4
- export const sellerEntrySchema = z.object({
5
- seller_id: z.string().uuid().describe("UUID do vendedor (use sellers_list para obter os IDs)"),
6
- commission_amount: z.number().min(0).describe("Valor da comissao em reais (total da venda)"),
4
+ export const clientsSearchSchema = z.object({
5
+ search: z
6
+ .string()
7
+ .min(1)
8
+ .describe("Nome (ou parte do nome) do cliente para buscar. Ex: 'Lincoln', 'Maria Silva'"),
9
+ });
10
+ export const clientCreateSchema = z.object({
11
+ name: z.string().min(1).describe("Nome completo do cliente (obrigatorio)"),
12
+ email: z.string().email().optional().describe("Email do cliente"),
13
+ phone: z.string().optional().describe("Telefone do cliente"),
14
+ cpf_cnpj: z.string().optional().describe("CPF ou CNPJ do cliente"),
15
+ address: z.string().optional().describe("Endereco do cliente"),
16
+ project_name: z.string().optional().describe("Nome do projeto/obra do cliente"),
7
17
  });
8
18
  export const createSaleSchema = z.object({
9
- client_id: z.string().uuid().describe("UUID do cliente no CRM"),
10
- title: z.string().min(1).describe("Nome/titulo da venda"),
11
- total_amount: z.number().positive().describe("Valor total da venda em reais"),
12
- sale_date: dateStr.describe("Data da venda (YYYY-MM-DD)"),
13
- description: z.string().optional().describe("Descricao opcional"),
14
- installments: z.number().int().min(1).max(24).optional().default(1).describe("Numero de parcelas"),
15
- due_day: z.number().int().min(1).max(28).optional().default(15).describe("Dia do vencimento de cada parcela"),
16
- installment_start_date: dateStr.optional().describe("Data da primeira parcela (YYYY-MM-DD)"),
17
- status: z.string().optional().default("pending").describe("Status da venda"),
18
- goal_id: z.string().uuid().optional().describe("UUID da meta associada"),
19
- tax_percentage: z.number().min(0).max(100).optional().default(0).describe("Percentual de imposto"),
20
- gateway_fee_percentage: z.number().min(0).max(100).optional().default(0).describe("Percentual de taxa gateway"),
21
- sellers: z
22
- .array(sellerEntrySchema)
19
+ client_name: z
20
+ .string()
21
+ .min(1)
22
+ .describe("Nome do cliente (ou parte). O sistema busca automaticamente. Se nao encontrar, use client_create para cadastrar primeiro."),
23
+ title: z.string().min(1).describe("Titulo/descricao da venda. Ex: 'Projeto residencial'"),
24
+ total_amount: z.number().positive().describe("Valor total da venda em reais. Ex: 5000"),
25
+ sale_date: dateStr.describe("Data da venda no formato YYYY-MM-DD. Ex: '2026-03-01'"),
26
+ seller_names: z
27
+ .array(z.string().min(1))
28
+ .optional()
29
+ .describe("Nomes dos vendedores que participaram. Ex: ['Natalia Muzzi']. A comissao e calculada automaticamente."),
30
+ installments: z
31
+ .number()
32
+ .int()
33
+ .min(1)
34
+ .max(24)
35
+ .optional()
36
+ .default(1)
37
+ .describe("Numero de parcelas (1 = a vista). Ex: 2"),
38
+ payment_method: z
39
+ .enum(["pix", "cartao_parcelado", "pix_parcelado"])
40
+ .optional()
41
+ .default("pix")
42
+ .describe("Forma de pagamento: pix (a vista), pix_parcelado ou cartao_parcelado"),
43
+ description: z.string().optional().describe("Observacoes adicionais sobre a venda"),
44
+ due_day: z
45
+ .number()
46
+ .int()
47
+ .min(1)
48
+ .max(28)
49
+ .optional()
50
+ .describe("Dia do vencimento das parcelas (1-28). Padrao: 15"),
51
+ installment_start_date: dateStr
52
+ .optional()
53
+ .describe("Data da primeira parcela (YYYY-MM-DD). Padrao: data da venda"),
54
+ client_id: z
55
+ .string()
56
+ .uuid()
23
57
  .optional()
24
- .describe("Vendedores que participaram da venda e suas comissoes (use sellers_list para obter IDs)"),
58
+ .describe("UUID do cliente (alternativa a client_name, use apenas se ja souber o ID)"),
25
59
  });
@@ -0,0 +1,11 @@
1
+ import type { z } from "zod";
2
+ import type { clientsSearchSchema, clientCreateSchema } from "../schemas/inputs.js";
3
+ type ToolResult = {
4
+ content: Array<{
5
+ type: "text";
6
+ text: string;
7
+ }>;
8
+ };
9
+ export declare function clientsSearchTool(args: z.infer<typeof clientsSearchSchema>): Promise<ToolResult>;
10
+ export declare function clientCreateTool(args: z.infer<typeof clientCreateSchema>): Promise<ToolResult>;
11
+ export {};
@@ -0,0 +1,35 @@
1
+ import { searchClients, createClient } from "../client.js";
2
+ function text(data) {
3
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
4
+ }
5
+ export async function clientsSearchTool(args) {
6
+ const { clients, total } = await searchClients(args.search);
7
+ return text({
8
+ total,
9
+ clients: clients.map((c) => ({
10
+ id: c.id,
11
+ name: c.name,
12
+ email: c.email,
13
+ phone: c.phone,
14
+ cpf_cnpj: c.cpf_cnpj,
15
+ project_name: c.project_name,
16
+ })),
17
+ hint: total === 0
18
+ ? `Nenhum cliente encontrado com "${args.search}". Use client_create para cadastrar.`
19
+ : "Use o nome do cliente no campo client_name da tool create_sale.",
20
+ });
21
+ }
22
+ export async function clientCreateTool(args) {
23
+ const result = await createClient({
24
+ name: args.name,
25
+ email: args.email,
26
+ phone: args.phone,
27
+ cpf_cnpj: args.cpf_cnpj,
28
+ address: args.address,
29
+ project_name: args.project_name,
30
+ });
31
+ return text({
32
+ ...result,
33
+ hint: `Cliente "${result.client.name}" criado. Agora use create_sale com client_name: "${result.client.name}".`,
34
+ });
35
+ }
@@ -4,20 +4,27 @@ function text(data) {
4
4
  }
5
5
  export async function createSaleTool(args) {
6
6
  const body = {
7
- client_id: args.client_id,
8
7
  title: args.title,
9
8
  total_amount: args.total_amount,
10
9
  sale_date: args.sale_date,
11
- description: args.description,
12
10
  installments: args.installments,
13
- due_day: args.due_day,
14
- installment_start_date: args.installment_start_date,
15
- status: args.status,
16
- goal_id: args.goal_id,
17
- tax_percentage: args.tax_percentage,
18
- gateway_fee_percentage: args.gateway_fee_percentage,
19
- sellers: args.sellers,
11
+ payment_method: args.payment_method,
20
12
  };
13
+ if (args.client_id) {
14
+ body.client_id = args.client_id;
15
+ }
16
+ else {
17
+ body.client_name = args.client_name;
18
+ }
19
+ if (args.seller_names && args.seller_names.length > 0) {
20
+ body.seller_names = args.seller_names;
21
+ }
22
+ if (args.description)
23
+ body.description = args.description;
24
+ if (args.due_day)
25
+ body.due_day = args.due_day;
26
+ if (args.installment_start_date)
27
+ body.installment_start_date = args.installment_start_date;
21
28
  const result = await createSale(body);
22
29
  return text(result);
23
30
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-supabase-crm-biancode",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "MCP server for CRM Biancode - list sellers and create sales via Supabase Edge Functions",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/src/client.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * HTTP client for CRM Biancode Edge Functions (list-sellers, create-sale).
2
+ * HTTP client for CRM Biancode Edge Functions.
3
3
  * Auth via x-api-token header.
4
4
  */
5
5
 
@@ -45,7 +45,7 @@ async function request<T>(
45
45
  }
46
46
  if (!res.ok) {
47
47
  const text = await res.text();
48
- throw new Error(`CRM API ${res.status}: ${text.slice(0, 300)}`);
48
+ throw new Error(`CRM API ${res.status}: ${text.slice(0, 500)}`);
49
49
  }
50
50
  return res.json() as Promise<T>;
51
51
  } finally {
@@ -63,6 +63,8 @@ async function request<T>(
63
63
  });
64
64
  }
65
65
 
66
+ // ── Sellers ──
67
+
66
68
  export interface Seller {
67
69
  id: string;
68
70
  name: string;
@@ -76,8 +78,60 @@ export interface ListSellersResponse {
76
78
  sellers: Seller[];
77
79
  }
78
80
 
81
+ export async function getSellers(): Promise<ListSellersResponse> {
82
+ const { token } = getConfig();
83
+ return request<ListSellersResponse>("/list-sellers", token);
84
+ }
85
+
86
+ // ── Clients ──
87
+
88
+ export interface Client {
89
+ id: string;
90
+ name: string;
91
+ email?: string | null;
92
+ phone?: string | null;
93
+ cpf_cnpj?: string | null;
94
+ project_name?: string | null;
95
+ }
96
+
97
+ export interface SearchClientsResponse {
98
+ clients: Client[];
99
+ total: number;
100
+ }
101
+
102
+ export interface CreateClientBody {
103
+ name: string;
104
+ email?: string;
105
+ phone?: string;
106
+ cpf_cnpj?: string;
107
+ address?: string;
108
+ project_name?: string;
109
+ }
110
+
111
+ export interface CreateClientResponse {
112
+ success: boolean;
113
+ client: Client;
114
+ }
115
+
116
+ export async function searchClients(search?: string): Promise<SearchClientsResponse> {
117
+ const { token } = getConfig();
118
+ const qs = search ? `?search=${encodeURIComponent(search)}` : "";
119
+ return request<SearchClientsResponse>(`/list-clients${qs}`, token);
120
+ }
121
+
122
+ export async function createClient(body: CreateClientBody): Promise<CreateClientResponse> {
123
+ const { token } = getConfig();
124
+ return request<CreateClientResponse>("/create-client", token, {
125
+ method: "POST",
126
+ body: JSON.stringify(body),
127
+ });
128
+ }
129
+
130
+ // ── Sales ──
131
+
79
132
  export interface CreateSaleBody {
80
- client_id: string;
133
+ client_name?: string;
134
+ client_id?: string;
81
135
  title: string;
82
136
  total_amount: number;
83
137
  sale_date: string;
@@ -87,22 +141,24 @@ export interface CreateSaleBody {
87
141
  installment_start_date?: string;
88
142
  status?: string;
89
143
  goal_id?: string;
144
+ payment_method?: string;
90
145
  tax_percentage?: number;
91
146
  gateway_fee_percentage?: number;
147
+ seller_names?: string[];
92
148
  sellers?: Array<{ seller_id: string; commission_amount: number }>;
93
149
  }
94
150
 
95
151
  export interface CreateSaleResponse {
96
152
  success: boolean;
97
153
  sale_id: string;
154
+ client_name: string | null;
155
+ goal_id: string | null;
98
156
  installments_created: number;
99
157
  receivables_created: number;
100
158
  sellers_linked: number;
101
- }
102
-
103
- export async function getSellers(): Promise<ListSellersResponse> {
104
- const { token } = getConfig();
105
- return request<ListSellersResponse>("/list-sellers", token);
159
+ seller_names?: string[];
160
+ tax_percentage_applied: number;
161
+ gateway_fee_percentage_applied: number;
106
162
  }
107
163
 
108
164
  export async function createSale(body: CreateSaleBody): Promise<CreateSaleResponse> {
package/src/index.ts CHANGED
@@ -5,6 +5,7 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
5
5
  import { createServer } from "node:http";
6
6
  import { sellersList } from "./tools/sellers.js";
7
7
  import { createSaleTool } from "./tools/sales.js";
8
+ import { clientsSearchTool, clientCreateTool } from "./tools/clients.js";
8
9
  import * as schemas from "./schemas/inputs.js";
9
10
 
10
11
  type ToolResult = { content: Array<{ type: "text"; text: string }> };
@@ -26,15 +27,37 @@ function safe<T>(
26
27
 
27
28
  const server = new McpServer({
28
29
  name: "supabase-crm-biancode",
29
- version: "1.0.0",
30
+ version: "1.1.0",
30
31
  });
31
32
 
33
+ server.registerTool(
34
+ "clients_search",
35
+ {
36
+ title: "Buscar clientes",
37
+ description:
38
+ "Busca clientes pelo nome no CRM. Use para encontrar o cliente antes de cadastrar uma venda, ou para verificar se um cliente ja existe.",
39
+ inputSchema: schemas.clientsSearchSchema,
40
+ },
41
+ safe("clients_search", (a) => clientsSearchTool(a))
42
+ );
43
+
44
+ server.registerTool(
45
+ "client_create",
46
+ {
47
+ title: "Cadastrar cliente",
48
+ description:
49
+ "Cadastra um novo cliente no CRM. Apenas o nome e obrigatorio. Use quando o cliente nao for encontrado pela busca.",
50
+ inputSchema: schemas.clientCreateSchema,
51
+ },
52
+ safe("client_create", (a) => clientCreateTool(a))
53
+ );
54
+
32
55
  server.registerTool(
33
56
  "sellers_list",
34
57
  {
35
- title: "List sellers",
58
+ title: "Listar vendedores",
36
59
  description:
37
- "Lista vendedores cadastrados no CRM. Use para obter os IDs ao cadastrar uma venda e escolher quem participou (comissao).",
60
+ "Lista todos os vendedores cadastrados no CRM com nomes e percentuais de comissao.",
38
61
  inputSchema: schemas.sellersListSchema,
39
62
  },
40
63
  safe("sellers_list", (a) => sellersList(a))
@@ -43,9 +66,9 @@ server.registerTool(
43
66
  server.registerTool(
44
67
  "create_sale",
45
68
  {
46
- title: "Create sale",
69
+ title: "Cadastrar venda",
47
70
  description:
48
- "Cadastra nova venda no CRM. Insere em sales, sale_installments, accounts_receivable e sale_sellers. Chame sellers_list antes para obter IDs dos vendedores.",
71
+ "Cadastra nova venda no CRM. Aceita o NOME do cliente (nao precisa de UUID). Aceita NOMES dos vendedores e calcula comissao automaticamente. Campos minimos: client_name, title, total_amount, sale_date.",
49
72
  inputSchema: schemas.createSaleSchema,
50
73
  },
51
74
  safe("create_sale", (a) => createSaleTool(a))
@@ -4,26 +4,65 @@ const dateStr = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Formato YYYY-MM-DD");
4
4
 
5
5
  export const sellersListSchema = z.object({});
6
6
 
7
- export const sellerEntrySchema = z.object({
8
- seller_id: z.string().uuid().describe("UUID do vendedor (use sellers_list para obter os IDs)"),
9
- commission_amount: z.number().min(0).describe("Valor da comissao em reais (total da venda)"),
7
+ export const clientsSearchSchema = z.object({
8
+ search: z
9
+ .string()
10
+ .min(1)
11
+ .describe("Nome (ou parte do nome) do cliente para buscar. Ex: 'Lincoln', 'Maria Silva'"),
12
+ });
13
+
14
+ export const clientCreateSchema = z.object({
15
+ name: z.string().min(1).describe("Nome completo do cliente (obrigatorio)"),
16
+ email: z.string().email().optional().describe("Email do cliente"),
17
+ phone: z.string().optional().describe("Telefone do cliente"),
18
+ cpf_cnpj: z.string().optional().describe("CPF ou CNPJ do cliente"),
19
+ address: z.string().optional().describe("Endereco do cliente"),
20
+ project_name: z.string().optional().describe("Nome do projeto/obra do cliente"),
10
21
  });
11
22
 
12
23
  export const createSaleSchema = z.object({
13
- client_id: z.string().uuid().describe("UUID do cliente no CRM"),
14
- title: z.string().min(1).describe("Nome/titulo da venda"),
15
- total_amount: z.number().positive().describe("Valor total da venda em reais"),
16
- sale_date: dateStr.describe("Data da venda (YYYY-MM-DD)"),
17
- description: z.string().optional().describe("Descricao opcional"),
18
- installments: z.number().int().min(1).max(24).optional().default(1).describe("Numero de parcelas"),
19
- due_day: z.number().int().min(1).max(28).optional().default(15).describe("Dia do vencimento de cada parcela"),
20
- installment_start_date: dateStr.optional().describe("Data da primeira parcela (YYYY-MM-DD)"),
21
- status: z.string().optional().default("pending").describe("Status da venda"),
22
- goal_id: z.string().uuid().optional().describe("UUID da meta associada"),
23
- tax_percentage: z.number().min(0).max(100).optional().default(0).describe("Percentual de imposto"),
24
- gateway_fee_percentage: z.number().min(0).max(100).optional().default(0).describe("Percentual de taxa gateway"),
25
- sellers: z
26
- .array(sellerEntrySchema)
24
+ client_name: z
25
+ .string()
26
+ .min(1)
27
+ .describe(
28
+ "Nome do cliente (ou parte). O sistema busca automaticamente. Se nao encontrar, use client_create para cadastrar primeiro."
29
+ ),
30
+ title: z.string().min(1).describe("Titulo/descricao da venda. Ex: 'Projeto residencial'"),
31
+ total_amount: z.number().positive().describe("Valor total da venda em reais. Ex: 5000"),
32
+ sale_date: dateStr.describe("Data da venda no formato YYYY-MM-DD. Ex: '2026-03-01'"),
33
+ seller_names: z
34
+ .array(z.string().min(1))
35
+ .optional()
36
+ .describe(
37
+ "Nomes dos vendedores que participaram. Ex: ['Natalia Muzzi']. A comissao e calculada automaticamente."
38
+ ),
39
+ installments: z
40
+ .number()
41
+ .int()
42
+ .min(1)
43
+ .max(24)
44
+ .optional()
45
+ .default(1)
46
+ .describe("Numero de parcelas (1 = a vista). Ex: 2"),
47
+ payment_method: z
48
+ .enum(["pix", "cartao_parcelado", "pix_parcelado"])
49
+ .optional()
50
+ .default("pix")
51
+ .describe("Forma de pagamento: pix (a vista), pix_parcelado ou cartao_parcelado"),
52
+ description: z.string().optional().describe("Observacoes adicionais sobre a venda"),
53
+ due_day: z
54
+ .number()
55
+ .int()
56
+ .min(1)
57
+ .max(28)
58
+ .optional()
59
+ .describe("Dia do vencimento das parcelas (1-28). Padrao: 15"),
60
+ installment_start_date: dateStr
61
+ .optional()
62
+ .describe("Data da primeira parcela (YYYY-MM-DD). Padrao: data da venda"),
63
+ client_id: z
64
+ .string()
65
+ .uuid()
27
66
  .optional()
28
- .describe("Vendedores que participaram da venda e suas comissoes (use sellers_list para obter IDs)"),
67
+ .describe("UUID do cliente (alternativa a client_name, use apenas se ja souber o ID)"),
29
68
  });
@@ -0,0 +1,47 @@
1
+ import { searchClients, createClient } from "../client.js";
2
+ import type { z } from "zod";
3
+ import type { clientsSearchSchema, clientCreateSchema } from "../schemas/inputs.js";
4
+
5
+ type ToolResult = { content: Array<{ type: "text"; text: string }> };
6
+
7
+ function text(data: unknown): ToolResult {
8
+ return { content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }] };
9
+ }
10
+
11
+ export async function clientsSearchTool(
12
+ args: z.infer<typeof clientsSearchSchema>
13
+ ): Promise<ToolResult> {
14
+ const { clients, total } = await searchClients(args.search);
15
+ return text({
16
+ total,
17
+ clients: clients.map((c) => ({
18
+ id: c.id,
19
+ name: c.name,
20
+ email: c.email,
21
+ phone: c.phone,
22
+ cpf_cnpj: c.cpf_cnpj,
23
+ project_name: c.project_name,
24
+ })),
25
+ hint:
26
+ total === 0
27
+ ? `Nenhum cliente encontrado com "${args.search}". Use client_create para cadastrar.`
28
+ : "Use o nome do cliente no campo client_name da tool create_sale.",
29
+ });
30
+ }
31
+
32
+ export async function clientCreateTool(
33
+ args: z.infer<typeof clientCreateSchema>
34
+ ): Promise<ToolResult> {
35
+ const result = await createClient({
36
+ name: args.name,
37
+ email: args.email,
38
+ phone: args.phone,
39
+ cpf_cnpj: args.cpf_cnpj,
40
+ address: args.address,
41
+ project_name: args.project_name,
42
+ });
43
+ return text({
44
+ ...result,
45
+ hint: `Cliente "${result.client.name}" criado. Agora use create_sale com client_name: "${result.client.name}".`,
46
+ });
47
+ }
@@ -11,21 +11,27 @@ function text(data: unknown): ToolResult {
11
11
  export async function createSaleTool(
12
12
  args: z.infer<typeof createSaleSchema>
13
13
  ): Promise<ToolResult> {
14
- const body = {
15
- client_id: args.client_id,
14
+ const body: Record<string, unknown> = {
16
15
  title: args.title,
17
16
  total_amount: args.total_amount,
18
17
  sale_date: args.sale_date,
19
- description: args.description,
20
18
  installments: args.installments,
21
- due_day: args.due_day,
22
- installment_start_date: args.installment_start_date,
23
- status: args.status,
24
- goal_id: args.goal_id,
25
- tax_percentage: args.tax_percentage,
26
- gateway_fee_percentage: args.gateway_fee_percentage,
27
- sellers: args.sellers,
19
+ payment_method: args.payment_method,
28
20
  };
29
- const result = await createSale(body);
21
+
22
+ if (args.client_id) {
23
+ body.client_id = args.client_id;
24
+ } else {
25
+ body.client_name = args.client_name;
26
+ }
27
+
28
+ if (args.seller_names && args.seller_names.length > 0) {
29
+ body.seller_names = args.seller_names;
30
+ }
31
+ if (args.description) body.description = args.description;
32
+ if (args.due_day) body.due_day = args.due_day;
33
+ if (args.installment_start_date) body.installment_start_date = args.installment_start_date;
34
+
35
+ const result = await createSale(body as any);
30
36
  return text(result);
31
37
  }