@denik.me/mcp 0.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/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # @denik/mcp
2
+
3
+ Servidor MCP stdio para [Denik Agenda](https://denik.me). Expone tools para operar una org desde un agente (típicamente un bot de WhatsApp de equipo).
4
+
5
+ ## Instalación
6
+
7
+ ```bash
8
+ npm i -g @denik/mcp
9
+ ```
10
+
11
+ ## Config (Claude Desktop)
12
+
13
+ ```json
14
+ {
15
+ "mcpServers": {
16
+ "denik": {
17
+ "command": "denik-mcp",
18
+ "env": {
19
+ "DENIK_API_KEY": "dk_live_...",
20
+ "DENIK_BASE_URL": "https://denik.me"
21
+ }
22
+ }
23
+ }
24
+ }
25
+ ```
26
+
27
+ La API key se genera desde `Dashboard → Ajustes → API keys`.
28
+
29
+ ## Tools (Fase 1 — read-only)
30
+
31
+ | Tool | Descripción |
32
+ |---|---|
33
+ | `get_today_summary` | Resumen del día (citas, ingresos, no-shows) |
34
+ | `list_events` | Listar citas con filtros (rango, status, etc.) |
35
+ | `get_event` | Detalle de una cita |
36
+ | `list_services` | Servicios activos |
37
+ | `get_service_public_url` | Link público de booking para compartir |
38
+ | `find_customer` | Buscar clientes |
39
+ | `get_customer_appointments` | Historial de un cliente |
40
+ | `get_customer_points` | Puntos de lealtad |
41
+ | `get_org_stats` | Stats en rango custom |
42
+
43
+ Próximas fases: mutaciones (cancel/reschedule/mark_attendance), edición de landing, cupones.
@@ -0,0 +1 @@
1
+ export declare function denikGet<T = unknown>(path: string, params?: Record<string, string | number | boolean | undefined>): Promise<T>;
package/dist/client.js ADDED
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Fetch wrapper contra el backend de Denik.
3
+ * Lee `DENIK_API_KEY` y `DENIK_BASE_URL` del env.
4
+ */
5
+ const BASE_URL = process.env.DENIK_BASE_URL ?? "https://denik.me";
6
+ const API_KEY = process.env.DENIK_API_KEY;
7
+ if (!API_KEY) {
8
+ // No hacer throw aquí — el servidor MCP debe poder arrancar para listar tools.
9
+ // El error sale al invocar una tool.
10
+ console.error("[denik-mcp] WARNING: DENIK_API_KEY env var is not set");
11
+ }
12
+ export async function denikGet(path, params = {}) {
13
+ if (!API_KEY)
14
+ throw new Error("DENIK_API_KEY env var is required");
15
+ const url = new URL(`/api/mcp/${path}`, BASE_URL);
16
+ for (const [k, v] of Object.entries(params)) {
17
+ if (v !== undefined)
18
+ url.searchParams.set(k, String(v));
19
+ }
20
+ const res = await fetch(url, {
21
+ headers: { "X-Denik-Api-Key": API_KEY, Accept: "application/json" },
22
+ });
23
+ if (!res.ok) {
24
+ const text = await res.text();
25
+ throw new Error(`Denik API ${res.status}: ${text}`);
26
+ }
27
+ return res.json();
28
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
5
+ import { denikGet } from "./client.js";
6
+ const server = new Server({ name: "denik-mcp", version: "0.1.0" }, { capabilities: { tools: {} } });
7
+ // ==================== Tool definitions ====================
8
+ const tools = [
9
+ {
10
+ name: "list_events",
11
+ description: "Lista citas del negocio. Útil para '¿qué tengo hoy?', '¿qué hay mañana?'. Acepta rango de fechas ISO y filtros.",
12
+ inputSchema: {
13
+ type: "object",
14
+ properties: {
15
+ from: { type: "string", description: "ISO date inicio (inclusive)" },
16
+ to: { type: "string", description: "ISO date fin (inclusive)" },
17
+ status: { type: "string", enum: ["CONFIRMED", "pending", "CANCELLED"] },
18
+ serviceId: { type: "string" },
19
+ customerId: { type: "string" },
20
+ attended: { type: "boolean" },
21
+ limit: { type: "number", default: 50 },
22
+ },
23
+ },
24
+ handler: async (args) => denikGet("events", { intent: "list", ...args }),
25
+ },
26
+ {
27
+ name: "get_event",
28
+ description: "Devuelve el detalle de una cita por ID (incluye customer, service, meetingLink).",
29
+ inputSchema: {
30
+ type: "object",
31
+ required: ["eventId"],
32
+ properties: { eventId: { type: "string" } },
33
+ },
34
+ handler: async (args) => denikGet("events", { intent: "get", eventId: args.eventId }),
35
+ },
36
+ {
37
+ name: "list_services",
38
+ description: "Lista los servicios activos de la org. Úsalo para resolver IDs antes de agendar.",
39
+ inputSchema: { type: "object", properties: {} },
40
+ handler: async () => denikGet("services", { intent: "list" }),
41
+ },
42
+ {
43
+ name: "get_service_public_url",
44
+ description: "Devuelve el link público de booking de un servicio para compartir.",
45
+ inputSchema: {
46
+ type: "object",
47
+ required: ["serviceId"],
48
+ properties: { serviceId: { type: "string" } },
49
+ },
50
+ handler: async (args) => denikGet("services", { intent: "public_url", serviceId: args.serviceId }),
51
+ },
52
+ {
53
+ name: "find_customer",
54
+ description: "Busca clientes por nombre, email o teléfono. Hasta 20 resultados.",
55
+ inputSchema: {
56
+ type: "object",
57
+ required: ["q"],
58
+ properties: { q: { type: "string" } },
59
+ },
60
+ handler: async (args) => denikGet("customers", { intent: "find", q: args.q }),
61
+ },
62
+ {
63
+ name: "get_customer_appointments",
64
+ description: "Historial de citas de un cliente (últimas 50, más recientes primero).",
65
+ inputSchema: {
66
+ type: "object",
67
+ required: ["customerId"],
68
+ properties: { customerId: { type: "string" } },
69
+ },
70
+ handler: async (args) => denikGet("customers", { intent: "appointments", customerId: args.customerId }),
71
+ },
72
+ {
73
+ name: "get_customer_points",
74
+ description: "Puntos de lealtad acumulados de un cliente (cálculo en vivo sobre citas asistidas).",
75
+ inputSchema: {
76
+ type: "object",
77
+ required: ["customerId"],
78
+ properties: { customerId: { type: "string" } },
79
+ },
80
+ handler: async (args) => denikGet("customers", { intent: "points", customerId: args.customerId }),
81
+ },
82
+ {
83
+ name: "get_today_summary",
84
+ description: "Resumen del día: total de citas, confirmadas, canceladas, no-shows, ingresos. La tool 'estrella' del grupo.",
85
+ inputSchema: { type: "object", properties: {} },
86
+ handler: async () => denikGet("org", { intent: "today" }),
87
+ },
88
+ {
89
+ name: "get_org_stats",
90
+ description: "Estadísticas de la org en un rango custom (from/to ISO).",
91
+ inputSchema: {
92
+ type: "object",
93
+ required: ["from", "to"],
94
+ properties: {
95
+ from: { type: "string" },
96
+ to: { type: "string" },
97
+ },
98
+ },
99
+ handler: async (args) => denikGet("org", { intent: "stats", from: args.from, to: args.to }),
100
+ },
101
+ ];
102
+ // ==================== MCP handlers ====================
103
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
104
+ tools: tools.map((t) => ({
105
+ name: t.name,
106
+ description: t.description,
107
+ inputSchema: t.inputSchema,
108
+ })),
109
+ }));
110
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
111
+ const tool = tools.find((t) => t.name === req.params.name);
112
+ if (!tool) {
113
+ return {
114
+ content: [{ type: "text", text: `Unknown tool: ${req.params.name}` }],
115
+ isError: true,
116
+ };
117
+ }
118
+ try {
119
+ const result = await tool.handler(req.params.arguments ?? {});
120
+ return {
121
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
122
+ };
123
+ }
124
+ catch (err) {
125
+ const message = err instanceof Error ? err.message : String(err);
126
+ return {
127
+ content: [{ type: "text", text: `Error: ${message}` }],
128
+ isError: true,
129
+ };
130
+ }
131
+ });
132
+ // ==================== Start ====================
133
+ const transport = new StdioServerTransport();
134
+ await server.connect(transport);
135
+ console.error("[denik-mcp] server ready on stdio");
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@denik.me/mcp",
3
+ "version": "0.1.0",
4
+ "description": "Denik Agenda MCP stdio server — tools para operar una org desde un agente (p.ej. bot de WhatsApp).",
5
+ "type": "module",
6
+ "bin": {
7
+ "denik-mcp": "./dist/index.js"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "files": ["dist", "README.md"],
11
+ "scripts": {
12
+ "build": "tsc -p tsconfig.json",
13
+ "dev": "tsx src/index.ts",
14
+ "prepublishOnly": "npm run build"
15
+ },
16
+ "keywords": ["mcp", "denik", "agenda", "whatsapp"],
17
+ "license": "MIT",
18
+ "dependencies": {
19
+ "@modelcontextprotocol/sdk": "^1.0.0",
20
+ "zod": "^3.23.0"
21
+ },
22
+ "devDependencies": {
23
+ "@types/node": "^22.0.0",
24
+ "tsx": "^4.19.0",
25
+ "typescript": "^5.6.0"
26
+ },
27
+ "engines": {
28
+ "node": ">=18"
29
+ }
30
+ }