@formmy.app/mcp-server 0.2.0 → 0.4.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/index.js +1 -1
- package/dist/sdk-client.d.ts +18 -0
- package/dist/sdk-client.js +6 -0
- package/dist/tools/conversations.js +43 -1
- package/package.json +1 -1
- package/src/index.ts +1 -1
- package/src/sdk-client.ts +28 -1
- package/src/tools/conversations.ts +57 -1
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import { registerDocumentTools } from "./tools/documents.js";
|
|
|
7
7
|
import { registerConversationTools } from "./tools/conversations.js";
|
|
8
8
|
const server = new McpServer({
|
|
9
9
|
name: "formmy",
|
|
10
|
-
version: "0.
|
|
10
|
+
version: "0.3.0",
|
|
11
11
|
});
|
|
12
12
|
registerAgentTools(server);
|
|
13
13
|
registerChatTools(server);
|
package/dist/sdk-client.d.ts
CHANGED
|
@@ -29,8 +29,26 @@ export declare function updateDocument(documentId: string, data: {
|
|
|
29
29
|
export declare function deleteDocument(documentId: string): Promise<unknown>;
|
|
30
30
|
export declare function listConversations(agentId: string): Promise<unknown>;
|
|
31
31
|
export declare function getConversation(conversationId: string): Promise<unknown>;
|
|
32
|
+
export declare function listConversationEstados(agentId: string): Promise<unknown>;
|
|
32
33
|
export declare function setConversationStatus(conversationId: string, label: string, color: string): Promise<unknown>;
|
|
33
34
|
export declare function addConversationTag(conversationId: string, label: string, color: string, comment?: string): Promise<unknown>;
|
|
34
35
|
export declare function removeConversationTag(conversationId: string, tagLabel: string): Promise<unknown>;
|
|
36
|
+
export declare function createOrder(conversationId: string, data: {
|
|
37
|
+
folio?: string;
|
|
38
|
+
cliente?: string;
|
|
39
|
+
tel?: string;
|
|
40
|
+
total?: number;
|
|
41
|
+
estatus?: string;
|
|
42
|
+
status?: "ABIERTA" | "CERRADA";
|
|
43
|
+
notas?: string;
|
|
44
|
+
cotizacionUrl?: string;
|
|
45
|
+
direccionEntrega?: {
|
|
46
|
+
label?: string;
|
|
47
|
+
direccion?: string;
|
|
48
|
+
cp?: string;
|
|
49
|
+
ciudad?: string;
|
|
50
|
+
mapsUrl?: string;
|
|
51
|
+
};
|
|
52
|
+
}): Promise<unknown>;
|
|
35
53
|
export declare function shareAgent(agentId: string, email: string, role?: string): Promise<unknown>;
|
|
36
54
|
export declare function connectIntegration(agentId: string, integration: string, config: Record<string, string>): Promise<unknown>;
|
package/dist/sdk-client.js
CHANGED
|
@@ -124,6 +124,9 @@ export async function listConversations(agentId) {
|
|
|
124
124
|
export async function getConversation(conversationId) {
|
|
125
125
|
return sdkFetch("conversations.get", { params: { conversationId } });
|
|
126
126
|
}
|
|
127
|
+
export async function listConversationEstados(agentId) {
|
|
128
|
+
return sdkFetch("conversations.estados", { params: { agentId } });
|
|
129
|
+
}
|
|
127
130
|
// --- Agent bridge mutations (NANOCLAW_WEBHOOK_SECRET auth) ---
|
|
128
131
|
// Pega a /api/v1/agents/conversations, no a /api/v2/sdk. Usa Bearer con el
|
|
129
132
|
// mismo secret que ya autentica el puente Formmy ↔ NanoClaw bidireccional,
|
|
@@ -159,6 +162,9 @@ export async function addConversationTag(conversationId, label, color, comment)
|
|
|
159
162
|
export async function removeConversationTag(conversationId, tagLabel) {
|
|
160
163
|
return bridgeFetch("remove_tag", { conversationId, tagLabel });
|
|
161
164
|
}
|
|
165
|
+
export async function createOrder(conversationId, data) {
|
|
166
|
+
return bridgeFetch("create_order", { conversationId, ...data });
|
|
167
|
+
}
|
|
162
168
|
// --- Sharing ---
|
|
163
169
|
export async function shareAgent(agentId, email, role) {
|
|
164
170
|
return sdkFetch("agents.share", {
|
|
@@ -13,7 +13,13 @@ export function registerConversationTools(server) {
|
|
|
13
13
|
const result = await sdk.getConversation(conversationId);
|
|
14
14
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
15
15
|
});
|
|
16
|
-
server.tool("
|
|
16
|
+
server.tool("list_conversation_estados", "List the valid CRM estados (kanban columns) configured for this agent. ALWAYS call this before set_conversation_status so you move leads within the defined set instead of inventing new labels/colors. Returns an array of { label, color }. Pick the closest matching estado and reuse its exact label and color.", {
|
|
17
|
+
agentId: z.string().describe("Agent ID or slug"),
|
|
18
|
+
}, async ({ agentId }) => {
|
|
19
|
+
const result = await sdk.listConversationEstados(agentId);
|
|
20
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
21
|
+
});
|
|
22
|
+
server.tool("set_conversation_status", "Set the CRM status (estado) of a conversation. Use when the user signals a clear state change: payment confirmed → 'Pago confirmado' (#10B981), needs human → 'Solo operador' (#3B82F6), in progress → 'Atendiendo' (#F59E0B), resolved → 'Atendido' (#10B981). label is free-form text; color must be a hex string. Prefer reusing a label/color from list_conversation_estados. The operator sees the new chip in the conversations list immediately.", {
|
|
17
23
|
conversationId: z.string().describe("Conversation ID"),
|
|
18
24
|
label: z.string().describe("Status label, e.g. 'Pago confirmado', 'Solo operador'"),
|
|
19
25
|
color: z.string().describe("Hex color, e.g. '#10B981' (green), '#3B82F6' (blue), '#F59E0B' (amber), '#EF4444' (red)"),
|
|
@@ -37,4 +43,40 @@ export function registerConversationTools(server) {
|
|
|
37
43
|
const result = await sdk.removeConversationTag(conversationId, tagLabel);
|
|
38
44
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
39
45
|
});
|
|
46
|
+
server.tool("create_order", "Record an order (cotización/pedido) for a conversation in Formmy, mirroring what is sent to Kommo. It shows in the contact panel's Órdenes accordion, moves the kanban card when `estatus` matches a CRM estado (call list_conversation_estados first), and bumps the conversation to the top. Does NOT replace the Kommo push — call it IN ADDITION. Use when an order/quote is confirmed or its status changes; only the most recent order drives the card and the board value.", {
|
|
47
|
+
conversationId: z.string().describe("Conversation ID"),
|
|
48
|
+
folio: z.string().optional().describe("Order folio/number, e.g. '260521-007'"),
|
|
49
|
+
cliente: z.string().optional().describe("Customer name"),
|
|
50
|
+
tel: z.string().optional().describe("Contact phone"),
|
|
51
|
+
total: z
|
|
52
|
+
.number()
|
|
53
|
+
.optional()
|
|
54
|
+
.describe("Order total / budget as a number, e.g. 783"),
|
|
55
|
+
estatus: z
|
|
56
|
+
.string()
|
|
57
|
+
.optional()
|
|
58
|
+
.describe("Free-form status; match a CRM estado label (list_conversation_estados) so the card moves to that column, e.g. 'Pago a contra entrega'"),
|
|
59
|
+
status: z
|
|
60
|
+
.enum(["ABIERTA", "CERRADA"])
|
|
61
|
+
.optional()
|
|
62
|
+
.describe("Order lifecycle (default ABIERTA). Use CERRADA when delivered/closed."),
|
|
63
|
+
notas: z.string().optional().describe("Special instructions / internal notes"),
|
|
64
|
+
cotizacionUrl: z
|
|
65
|
+
.string()
|
|
66
|
+
.optional()
|
|
67
|
+
.describe("URL to the quote PDF deployed on EasyBits"),
|
|
68
|
+
direccionEntrega: z
|
|
69
|
+
.object({
|
|
70
|
+
label: z.string().optional(),
|
|
71
|
+
direccion: z.string().optional(),
|
|
72
|
+
cp: z.string().optional(),
|
|
73
|
+
ciudad: z.string().optional(),
|
|
74
|
+
mapsUrl: z.string().optional(),
|
|
75
|
+
})
|
|
76
|
+
.optional()
|
|
77
|
+
.describe("Delivery address snapshot { label, direccion, cp, ciudad, mapsUrl }"),
|
|
78
|
+
}, async ({ conversationId, ...data }) => {
|
|
79
|
+
const result = await sdk.createOrder(conversationId, data);
|
|
80
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
81
|
+
});
|
|
40
82
|
}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
package/src/sdk-client.ts
CHANGED
|
@@ -184,6 +184,10 @@ export async function getConversation(conversationId: string) {
|
|
|
184
184
|
return sdkFetch("conversations.get", { params: { conversationId } });
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
+
export async function listConversationEstados(agentId: string) {
|
|
188
|
+
return sdkFetch("conversations.estados", { params: { agentId } });
|
|
189
|
+
}
|
|
190
|
+
|
|
187
191
|
// --- Agent bridge mutations (NANOCLAW_WEBHOOK_SECRET auth) ---
|
|
188
192
|
// Pega a /api/v1/agents/conversations, no a /api/v2/sdk. Usa Bearer con el
|
|
189
193
|
// mismo secret que ya autentica el puente Formmy ↔ NanoClaw bidireccional,
|
|
@@ -192,7 +196,7 @@ export async function getConversation(conversationId: string) {
|
|
|
192
196
|
const BRIDGE_SECRET = process.env.NANOCLAW_WEBHOOK_SECRET || "";
|
|
193
197
|
|
|
194
198
|
async function bridgeFetch(
|
|
195
|
-
intent: "set_estado" | "add_tag" | "remove_tag",
|
|
199
|
+
intent: "set_estado" | "add_tag" | "remove_tag" | "create_order",
|
|
196
200
|
body: Record<string, unknown>,
|
|
197
201
|
): Promise<unknown> {
|
|
198
202
|
if (!BRIDGE_SECRET) {
|
|
@@ -240,6 +244,29 @@ export async function removeConversationTag(
|
|
|
240
244
|
return bridgeFetch("remove_tag", { conversationId, tagLabel });
|
|
241
245
|
}
|
|
242
246
|
|
|
247
|
+
export async function createOrder(
|
|
248
|
+
conversationId: string,
|
|
249
|
+
data: {
|
|
250
|
+
folio?: string;
|
|
251
|
+
cliente?: string;
|
|
252
|
+
tel?: string;
|
|
253
|
+
total?: number;
|
|
254
|
+
estatus?: string;
|
|
255
|
+
status?: "ABIERTA" | "CERRADA";
|
|
256
|
+
notas?: string;
|
|
257
|
+
cotizacionUrl?: string;
|
|
258
|
+
direccionEntrega?: {
|
|
259
|
+
label?: string;
|
|
260
|
+
direccion?: string;
|
|
261
|
+
cp?: string;
|
|
262
|
+
ciudad?: string;
|
|
263
|
+
mapsUrl?: string;
|
|
264
|
+
};
|
|
265
|
+
},
|
|
266
|
+
) {
|
|
267
|
+
return bridgeFetch("create_order", { conversationId, ...data });
|
|
268
|
+
}
|
|
269
|
+
|
|
243
270
|
// --- Sharing ---
|
|
244
271
|
|
|
245
272
|
export async function shareAgent(
|
|
@@ -27,9 +27,21 @@ export function registerConversationTools(server: McpServer) {
|
|
|
27
27
|
}
|
|
28
28
|
);
|
|
29
29
|
|
|
30
|
+
server.tool(
|
|
31
|
+
"list_conversation_estados",
|
|
32
|
+
"List the valid CRM estados (kanban columns) configured for this agent. ALWAYS call this before set_conversation_status so you move leads within the defined set instead of inventing new labels/colors. Returns an array of { label, color }. Pick the closest matching estado and reuse its exact label and color.",
|
|
33
|
+
{
|
|
34
|
+
agentId: z.string().describe("Agent ID or slug"),
|
|
35
|
+
},
|
|
36
|
+
async ({ agentId }) => {
|
|
37
|
+
const result = await sdk.listConversationEstados(agentId);
|
|
38
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
|
|
30
42
|
server.tool(
|
|
31
43
|
"set_conversation_status",
|
|
32
|
-
"Set the CRM status (estado) of a conversation. Use when the user signals a clear state change: payment confirmed → 'Pago confirmado' (#10B981), needs human → 'Solo operador' (#3B82F6), in progress → 'Atendiendo' (#F59E0B), resolved → 'Atendido' (#10B981). label is free-form text; color must be a hex string. The operator sees the new chip in the conversations list immediately.",
|
|
44
|
+
"Set the CRM status (estado) of a conversation. Use when the user signals a clear state change: payment confirmed → 'Pago confirmado' (#10B981), needs human → 'Solo operador' (#3B82F6), in progress → 'Atendiendo' (#F59E0B), resolved → 'Atendido' (#10B981). label is free-form text; color must be a hex string. Prefer reusing a label/color from list_conversation_estados. The operator sees the new chip in the conversations list immediately.",
|
|
33
45
|
{
|
|
34
46
|
conversationId: z.string().describe("Conversation ID"),
|
|
35
47
|
label: z.string().describe("Status label, e.g. 'Pago confirmado', 'Solo operador'"),
|
|
@@ -68,4 +80,48 @@ export function registerConversationTools(server: McpServer) {
|
|
|
68
80
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
69
81
|
}
|
|
70
82
|
);
|
|
83
|
+
|
|
84
|
+
server.tool(
|
|
85
|
+
"create_order",
|
|
86
|
+
"Record an order (cotización/pedido) for a conversation in Formmy, mirroring what is sent to Kommo. It shows in the contact panel's Órdenes accordion, moves the kanban card when `estatus` matches a CRM estado (call list_conversation_estados first), and bumps the conversation to the top. Does NOT replace the Kommo push — call it IN ADDITION. Use when an order/quote is confirmed or its status changes; only the most recent order drives the card and the board value.",
|
|
87
|
+
{
|
|
88
|
+
conversationId: z.string().describe("Conversation ID"),
|
|
89
|
+
folio: z.string().optional().describe("Order folio/number, e.g. '260521-007'"),
|
|
90
|
+
cliente: z.string().optional().describe("Customer name"),
|
|
91
|
+
tel: z.string().optional().describe("Contact phone"),
|
|
92
|
+
total: z
|
|
93
|
+
.number()
|
|
94
|
+
.optional()
|
|
95
|
+
.describe("Order total / budget as a number, e.g. 783"),
|
|
96
|
+
estatus: z
|
|
97
|
+
.string()
|
|
98
|
+
.optional()
|
|
99
|
+
.describe(
|
|
100
|
+
"Free-form status; match a CRM estado label (list_conversation_estados) so the card moves to that column, e.g. 'Pago a contra entrega'",
|
|
101
|
+
),
|
|
102
|
+
status: z
|
|
103
|
+
.enum(["ABIERTA", "CERRADA"])
|
|
104
|
+
.optional()
|
|
105
|
+
.describe("Order lifecycle (default ABIERTA). Use CERRADA when delivered/closed."),
|
|
106
|
+
notas: z.string().optional().describe("Special instructions / internal notes"),
|
|
107
|
+
cotizacionUrl: z
|
|
108
|
+
.string()
|
|
109
|
+
.optional()
|
|
110
|
+
.describe("URL to the quote PDF deployed on EasyBits"),
|
|
111
|
+
direccionEntrega: z
|
|
112
|
+
.object({
|
|
113
|
+
label: z.string().optional(),
|
|
114
|
+
direccion: z.string().optional(),
|
|
115
|
+
cp: z.string().optional(),
|
|
116
|
+
ciudad: z.string().optional(),
|
|
117
|
+
mapsUrl: z.string().optional(),
|
|
118
|
+
})
|
|
119
|
+
.optional()
|
|
120
|
+
.describe("Delivery address snapshot { label, direccion, cp, ciudad, mapsUrl }"),
|
|
121
|
+
},
|
|
122
|
+
async ({ conversationId, ...data }) => {
|
|
123
|
+
const result = await sdk.createOrder(conversationId, data);
|
|
124
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
125
|
+
}
|
|
126
|
+
);
|
|
71
127
|
}
|