@quantyapp/quanty-mcp-server 1.0.8 → 1.0.10

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.
@@ -8,6 +8,15 @@ export interface BankSummary {
8
8
  visibility: 'public' | 'private' | 'subscription';
9
9
  categoryCount: number;
10
10
  }
11
+ export interface CompositionInput {
12
+ id: string;
13
+ code: string;
14
+ description: string;
15
+ unit: string;
16
+ quantity: number;
17
+ unitCost: number;
18
+ itemType?: string;
19
+ }
11
20
  export interface BankItem {
12
21
  id: string;
13
22
  code: string;
@@ -15,7 +24,8 @@ export interface BankItem {
15
24
  unit: string;
16
25
  unitCost: number;
17
26
  itemType?: string;
18
- composition?: BankItem[];
27
+ isComposition: boolean;
28
+ composition?: CompositionInput[];
19
29
  }
20
30
  export interface BankCategory {
21
31
  id: string;
@@ -39,7 +49,7 @@ export declare function searchBankItems(bankId: string, query: string, limit?: n
39
49
  /**
40
50
  * Prepares importing an item from bank to budget
41
51
  */
42
- export declare function prepareImportItem(budgetId: string, bankItem: BankItem): {
52
+ export declare function prepareImportItem(budgetId: string, bankItem: BankItem, quantity: number): {
43
53
  action: string;
44
54
  preview: string;
45
55
  data: any;
@@ -1 +1 @@
1
- {"version":3,"file":"bankService.d.ts","sourceRoot":"","sources":["../../src/core/bankService.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,MAAM,WAAW,WAAW;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,QAAQ,GAAG,SAAS,GAAG,cAAc,CAAC;IAClD,aAAa,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,QAAQ;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,QAAQ,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,YAAY;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,wBAAsB,SAAS,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAkBzE;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAgB/E;AAgBD;;;GAGG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAyG5G;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC7B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,QAAQ,GACnB;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,GAAG,CAAA;CAAE,CAgBhD;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAuD9F;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAiD9F"}
1
+ {"version":3,"file":"bankService.d.ts","sourceRoot":"","sources":["../../src/core/bankService.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,MAAM,WAAW,WAAW;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,QAAQ,GAAG,SAAS,GAAG,cAAc,CAAC;IAClD,aAAa,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,gBAAgB;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,QAAQ;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,OAAO,CAAC;IACvB,WAAW,CAAC,EAAE,gBAAgB,EAAE,CAAC;CACpC;AAED,MAAM,WAAW,YAAY;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACrB;AA+CD;;GAEG;AACH,wBAAsB,SAAS,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAkBzE;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAgB/E;AAgBD;;;GAGG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAwF5G;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC7B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,MAAM,GACjB;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,GAAG,CAAA;CAAE,CA+BhD;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CA+C9F;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAyC9F"}
@@ -1,4 +1,46 @@
1
1
  import { supabase } from './supabase.js';
2
+ // === HELPER FUNCTIONS FOR COMPOSITION PARSING ===
3
+ function isItemComposition(item) {
4
+ if (item.composition && Array.isArray(item.composition) && item.composition.length > 0)
5
+ return true;
6
+ if (item.itemType?.toLowerCase().includes('compos'))
7
+ return true;
8
+ if (item.code?.startsWith('C.'))
9
+ return true;
10
+ return false;
11
+ }
12
+ function extractCompositionInputs(item) {
13
+ const sources = ['composition', 'children', 'insumos', 'items', 'components', 'compositionItems'];
14
+ for (const key of sources) {
15
+ if (item[key] && Array.isArray(item[key]) && item[key].length > 0) {
16
+ return item[key].map((input) => ({
17
+ id: input.id || crypto.randomUUID(),
18
+ code: input.code || '',
19
+ description: input.description || '',
20
+ unit: input.unit || '',
21
+ quantity: input.quantity || input.coefficient || input.coeficiente || 0,
22
+ unitCost: input.unitCost || input.unit_cost || 0,
23
+ itemType: input.itemType || input.type || 'INSUMO'
24
+ }));
25
+ }
26
+ }
27
+ return [];
28
+ }
29
+ function toBankItem(item) {
30
+ const isComp = isItemComposition(item);
31
+ const inputs = extractCompositionInputs(item);
32
+ return {
33
+ id: item.id,
34
+ code: item.code || '',
35
+ description: item.description || '',
36
+ unit: item.unit || '',
37
+ unitCost: item.unitCost || 0,
38
+ itemType: isComp ? 'Composição' : (item.itemType || 'Insumo'),
39
+ isComposition: isComp,
40
+ composition: inputs.length > 0 ? inputs : undefined
41
+ };
42
+ }
43
+ // ===============================================
2
44
  /**
3
45
  * Lists available composition banks for the tenant
4
46
  */
@@ -91,29 +133,10 @@ export async function searchBankItems(bankId, query, limit = 50) {
91
133
  return true;
92
134
  if (matchesQuery(item)) {
93
135
  seenIds.add(item.id);
94
- results.push({
95
- id: item.id,
96
- code: item.code || '',
97
- description: item.description || '',
98
- unit: item.unit || '',
99
- unitCost: item.unitCost || 0,
100
- itemType: item.itemType || detectItemType(item),
101
- composition: item.composition // Mantém referência para importação
102
- });
136
+ results.push(toBankItem(item)); // Usa o Helper toBankItem
103
137
  }
104
138
  return results.length < limit;
105
139
  }
106
- /**
107
- * Detecta o tipo do item baseado na estrutura
108
- */
109
- function detectItemType(item) {
110
- if (item.composition && item.composition.length > 0) {
111
- return 'Composição';
112
- }
113
- if (item.itemType)
114
- return item.itemType;
115
- return 'Insumo';
116
- }
117
140
  /**
118
141
  * RECURSÃO PROFUNDA: Busca em qualquer estrutura de nós
119
142
  * Suporta: items, children, composition, categories
@@ -142,7 +165,9 @@ export async function searchBankItems(bankId, query, limit = 50) {
142
165
  return false;
143
166
  }
144
167
  // Busca recursiva em todas as propriedades que podem conter filhos
145
- const childKeys = ['items', 'children', 'composition', 'categories', 'rows'];
168
+ // NOTA: 'composition' e 'items' dentro de um item podem ser ignorados se quisermos só buscar itens "pai"
169
+ // mas para profundidade máxima vamos varrer tudo. O addIfMatches filtra duplicatas pelo ID.
170
+ const childKeys = ['items', 'children', 'composition', 'categories', 'rows', 'compositionItems'];
146
171
  for (const key of childKeys) {
147
172
  if (node[key] && Array.isArray(node[key])) {
148
173
  if (!searchRecursive(node[key]))
@@ -159,19 +184,32 @@ export async function searchBankItems(bankId, query, limit = 50) {
159
184
  /**
160
185
  * Prepares importing an item from bank to budget
161
186
  */
162
- export function prepareImportItem(budgetId, bankItem) {
187
+ export function prepareImportItem(budgetId, bankItem, quantity) {
188
+ let compositionForBudget = undefined;
189
+ if (bankItem.isComposition && bankItem.composition && bankItem.composition.length > 0) {
190
+ compositionForBudget = bankItem.composition.map(input => ({
191
+ id: crypto.randomUUID(),
192
+ code: input.code,
193
+ description: input.description,
194
+ unit: input.unit,
195
+ quantity: input.quantity,
196
+ unitCost: input.unitCost,
197
+ itemType: input.itemType
198
+ }));
199
+ }
163
200
  return {
164
201
  action: 'import_item',
165
- preview: `Importar "${bankItem.description}" (${bankItem.unit}) do banco para o orçamento`,
202
+ preview: `Importar "${bankItem.description}" (${quantity} ${bankItem.unit}) do banco para o orçamento`,
166
203
  data: {
167
204
  budgetId,
168
205
  item: {
206
+ code: bankItem.code,
169
207
  description: bankItem.description,
170
208
  unit: bankItem.unit,
171
- quantity: 0,
209
+ quantity: quantity,
172
210
  unitCost: bankItem.unitCost,
173
- isComposition: !!(bankItem.composition && bankItem.composition.length > 0),
174
- composition: bankItem.composition // Traz os insumos junto
211
+ isComposition: bankItem.isComposition,
212
+ composition: compositionForBudget
175
213
  }
176
214
  }
177
215
  };
@@ -206,19 +244,11 @@ export async function getBankItemById(bankId, itemId) {
206
244
  if (typeof node === 'object') {
207
245
  // Verifica se é o item procurado
208
246
  if (node.id === itemId) {
209
- foundItem = {
210
- id: node.id,
211
- code: node.code || '',
212
- description: node.description || '',
213
- unit: node.unit || '',
214
- unitCost: node.unitCost || 0,
215
- itemType: node.itemType,
216
- composition: node.composition
217
- };
247
+ foundItem = toBankItem(node); // USA O HELPER
218
248
  return true;
219
249
  }
220
250
  // Busca em filhos
221
- const childKeys = ['items', 'children', 'composition', 'categories', 'rows'];
251
+ const childKeys = ['items', 'children', 'composition', 'categories', 'rows', 'compositionItems'];
222
252
  for (const key of childKeys) {
223
253
  if (node[key] && Array.isArray(node[key])) {
224
254
  if (findById(node[key]))
@@ -258,15 +288,7 @@ export async function getCategoryItems(bankId, categoryId) {
258
288
  function extractItems(node) {
259
289
  if (node.items) {
260
290
  for (const item of node.items) {
261
- items.push({
262
- id: item.id,
263
- code: item.code || '',
264
- description: item.description || '',
265
- unit: item.unit || '',
266
- unitCost: item.unitCost || 0,
267
- itemType: item.itemType,
268
- composition: item.composition
269
- });
291
+ items.push(toBankItem(item));
270
292
  }
271
293
  }
272
294
  if (node.children) {
package/dist/mcp/index.js CHANGED
@@ -362,31 +362,19 @@ async function handleToolCall(auth, name, args) {
362
362
  descricao: i.description,
363
363
  unidade: i.unit,
364
364
  custo_unitario: i.unitCost,
365
- tipo: i.itemType
365
+ tipo: i.itemType,
366
+ is_composicao: i.isComposition,
367
+ qtd_insumos: i.composition?.length || 0
366
368
  }))
367
369
  };
368
370
  }
369
371
  // === WRITE (with confirmation) ===
370
372
  case 'quanty_importar_item': {
371
- // Primeiro buscamos os detalhes do item no banco para preparar a importação
372
- // Como prepareImportItem precisa do objeto BankItem completo, vamos simular ou buscar
373
- // Para simplificar, vamos usar uma busca direta pelo ID no banco via searchBankItems (ajustando a query seria ideal, mas aqui vamos assumir que o ID é suficiente ou implementaremos um getBankItem)
374
- // Melhor abordagem: vamos buscar o item específico.
375
- // O bankService não tem getBankItem exposto, então vamos adicionar uma busca rápida ou usar search
376
- const items = await bankService.searchBankItems(args.banco_id, ''); // Busca ampla é ruim.
377
- // Vamos adicionar uma função getBankItem no bankService ou buscar na lista filtrada.
378
- // WORKAROUND: Vamos criar a pendência com os dados mínimos e deixar o execute resolver ou
379
- // implementar getBankItem no bankService agora.
380
- // Vamos assumir que o usuário passou dados válidos e deixar a validação para o execute se possível.
381
- // Mas prepareImportItem pede um BankItem.
382
- // Vamos adicionar getBankItemById no bankService agora.
383
373
  const item = await bankService.getBankItemById(args.banco_id, args.banco_item_id);
384
374
  if (!item)
385
375
  return { erro: 'Item não encontrado no banco de dados.' };
386
- const prepared = bankService.prepareImportItem(args.orcamento_id, item);
387
- // Injetamos a quantidade que o usuário pediu, pois o prepareImportItem define como 0
388
- prepared.data.item.quantity = args.quantidade;
389
- prepared.preview = prepared.preview.replace('quantidade: 0', `quantidade: ${args.quantidade}`); // Ajuste cosmético se houver
376
+ // Passa a quantidade diretamente para o preparador
377
+ const prepared = bankService.prepareImportItem(args.orcamento_id, item, args.quantidade);
390
378
  const pendingId = generatePendingId();
391
379
  pendingOperations.set(pendingId, {
392
380
  action: prepared.action,
@@ -395,8 +383,10 @@ async function handleToolCall(auth, name, args) {
395
383
  });
396
384
  return {
397
385
  pending_id: pendingId,
398
- preview: `Importar "${item.description}" (${args.quantidade} ${item.unit}) - Pressione executar para confirmar.`,
399
- mensagem: 'Use quanty_executar com este pending_id para confirmar a importação.'
386
+ preview: prepared.preview,
387
+ is_composicao: item.isComposition,
388
+ qtd_insumos: item.composition?.length || 0,
389
+ mensagem: 'Use quanty_executar para confirmar.'
400
390
  };
401
391
  }
402
392
  case 'quanty_preparar_orcamento': {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quantyapp/quanty-mcp-server",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "MCP Server para conectar Claude Desktop ao Quanty - Sistema de Orçamentos de Engenharia",
5
5
  "type": "module",
6
6
  "main": "dist/mcp/index.js",