@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 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.1.0",
10
+ version: "0.3.0",
11
11
  });
12
12
  registerAgentTools(server);
13
13
  registerChatTools(server);
@@ -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>;
@@ -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("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. The operator sees the new chip in the conversations list immediately.", {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@formmy.app/mcp-server",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "MCP server for managing Formmy agents via Claude Code",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/src/index.ts CHANGED
@@ -8,7 +8,7 @@ import { registerConversationTools } from "./tools/conversations.js";
8
8
 
9
9
  const server = new McpServer({
10
10
  name: "formmy",
11
- version: "0.1.0",
11
+ version: "0.3.0",
12
12
  });
13
13
 
14
14
  registerAgentTools(server);
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
  }