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 +37 -3
- package/dist/client.js +14 -2
- package/dist/index.js +16 -5
- package/dist/schemas/inputs.d.ts +42 -44
- package/dist/schemas/inputs.js +52 -18
- package/dist/tools/clients.d.ts +11 -0
- package/dist/tools/clients.js +35 -0
- package/dist/tools/sales.js +16 -9
- package/package.json +1 -1
- package/src/client.ts +64 -8
- package/src/index.ts +28 -5
- package/src/schemas/inputs.ts +57 -18
- package/src/tools/clients.ts +47 -0
- package/src/tools/sales.ts +17 -11
package/dist/client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* HTTP client for CRM Biancode Edge Functions
|
|
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
|
-
|
|
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
|
|
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,
|
|
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.
|
|
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: "
|
|
27
|
-
description: "Lista vendedores cadastrados no CRM
|
|
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: "
|
|
32
|
-
description: "Cadastra nova venda no CRM.
|
|
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);
|
package/dist/schemas/inputs.d.ts
CHANGED
|
@@ -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
|
|
4
|
-
|
|
5
|
-
commission_amount: z.ZodNumber;
|
|
3
|
+
export declare const clientsSearchSchema: z.ZodObject<{
|
|
4
|
+
search: z.ZodString;
|
|
6
5
|
}, "strip", z.ZodTypeAny, {
|
|
7
|
-
|
|
8
|
-
commission_amount: number;
|
|
6
|
+
search: string;
|
|
9
7
|
}, {
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
33
|
+
client_name: z.ZodString;
|
|
15
34
|
title: z.ZodString;
|
|
16
35
|
total_amount: z.ZodNumber;
|
|
17
36
|
sale_date: z.ZodString;
|
|
18
|
-
|
|
37
|
+
seller_names: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
19
38
|
installments: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
20
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
44
|
-
|
|
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
|
-
|
|
49
|
-
sellers?: {
|
|
50
|
-
seller_id: string;
|
|
51
|
-
commission_amount: number;
|
|
52
|
-
}[] | undefined;
|
|
55
|
+
client_id?: string | undefined;
|
|
53
56
|
}, {
|
|
54
|
-
|
|
57
|
+
client_name: string;
|
|
55
58
|
title: string;
|
|
56
59
|
total_amount: number;
|
|
57
60
|
sale_date: string;
|
|
58
|
-
|
|
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
|
-
|
|
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
|
}>;
|
package/dist/schemas/inputs.js
CHANGED
|
@@ -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
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
.
|
|
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("
|
|
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
|
+
}
|
package/dist/tools/sales.js
CHANGED
|
@@ -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
|
-
|
|
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
package/src/client.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* HTTP client for CRM Biancode Edge Functions
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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: "
|
|
58
|
+
title: "Listar vendedores",
|
|
36
59
|
description:
|
|
37
|
-
"Lista vendedores cadastrados no CRM
|
|
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: "
|
|
69
|
+
title: "Cadastrar venda",
|
|
47
70
|
description:
|
|
48
|
-
"Cadastra nova venda no CRM.
|
|
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))
|
package/src/schemas/inputs.ts
CHANGED
|
@@ -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
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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("
|
|
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
|
+
}
|
package/src/tools/sales.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
}
|