From 5c6fd8fef32e926bc7d1ebc09bb079e891954419 Mon Sep 17 00:00:00 2001 From: josedario87 Date: Tue, 28 Oct 2025 10:49:18 -0600 Subject: [PATCH] Agregar servidor MCP Metabase MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Implementado mcp-metabase-server con TypeScript - 9 herramientas para interactuar con Metabase API - Soporta listar/buscar cards, ejecutar queries con parámetros - Soporta crear y actualizar cards - Autenticación con API Key - Agregado servicio al docker-compose.yml - Configurado en Traefik sin autenticación Authentik - Actualizado README con documentación completa - Variables y secrets configurados en Gitea --- .env.example | 6 + README.md | 39 ++++ docker-compose.yml | 31 +++ mcp-metabase-server/.gitignore | 6 + mcp-metabase-server/Dockerfile | 22 ++ mcp-metabase-server/README.md | 155 +++++++++++++ mcp-metabase-server/package.json | 30 +++ mcp-metabase-server/src/index.ts | 348 ++++++++++++++++++++++++++++++ mcp-metabase-server/tsconfig.json | 21 ++ 9 files changed, 658 insertions(+) create mode 100644 mcp-metabase-server/.gitignore create mode 100644 mcp-metabase-server/Dockerfile create mode 100644 mcp-metabase-server/README.md create mode 100644 mcp-metabase-server/package.json create mode 100644 mcp-metabase-server/src/index.ts create mode 100644 mcp-metabase-server/tsconfig.json diff --git a/.env.example b/.env.example index cc7fa1e..ac526c1 100644 --- a/.env.example +++ b/.env.example @@ -68,3 +68,9 @@ REGISTRY_PASSWORD=mi-password-secreto GIT_URL=https://gitea.ejemplo.com GIT_DOMAIN=gitea.ejemplo.com GIT_TOKEN=token-de-gitea-aqui + +# Servidor MCP para Metabase API +# El servidor estará disponible en: METABASE_DOMAIN/mcp (sin autenticación) +METABASE_DOMAIN=metabase.ejemplo.com +METABASE_INTERNAL_URL=http://metabase:3000 +METABASE_API_KEY=mb_xxxxxxxxxxxxxxxxxxxxxxxxx diff --git a/README.md b/README.md index 72f5442..45aea9d 100644 --- a/README.md +++ b/README.md @@ -21,12 +21,18 @@ claude mcp add --transport http nucleodocs-gitea https://gitea.nucleoriofrio.com claude mcp add chrome-devtools npx -- chrome-devtools-mcp@latest --isolated=true ``` +### MCP Metabase Server +```bash +claude mcp add --transport http nucleodocs-metabase https://metabase.nucleoriofrio.com/mcp +``` + ### Scopes Disponibles **Para compartir con el equipo** (crea .mcp.json en el proyecto): ```bash claude mcp add --transport http nucleodocs-docker --scope project https://docker.nucleoriofrio.com/mcp claude mcp add --transport http nucleodocs-gitea --scope project https://gitea.nucleoriofrio.com/mcp +claude mcp add --transport http nucleodocs-metabase --scope project https://metabase.nucleoriofrio.com/mcp claude mcp add chrome-devtools --scope project npx -- chrome-devtools-mcp@latest --isolated=true ``` @@ -34,6 +40,7 @@ claude mcp add chrome-devtools --scope project npx -- chrome-devtools-mcp@latest ```bash claude mcp add --transport http nucleodocs-docker --scope user https://docker.nucleoriofrio.com/mcp claude mcp add --transport http nucleodocs-gitea --scope user https://gitea.nucleoriofrio.com/mcp +claude mcp add --transport http nucleodocs-metabase --scope user https://metabase.nucleoriofrio.com/mcp claude mcp add chrome-devtools --scope user npx -- chrome-devtools-mcp@latest --isolated=true ``` @@ -45,6 +52,7 @@ claude mcp list # Ver detalles de un servidor específico claude mcp get nucleodocs-docker claude mcp get nucleodocs-gitea +claude mcp get nucleodocs-metabase claude mcp get chrome-devtools # Dentro de Claude Code, verificar el estado @@ -78,6 +86,7 @@ Este repositorio contiene la documentación del funcionamiento del sistema Nucle - ✅ Claude Code hooks para monitoreo de Actions - ✅ MCP Server Docker para gestión de contenedores - ✅ MCP Server Gitea para API de Gitea +- ✅ MCP Server Metabase para análisis y reportes - ✅ MCP Server Chrome DevTools para testing e interacción con navegador ## Servicios @@ -115,6 +124,28 @@ Servidor MCP personalizado construido con TypeScript que expone la API de Gitea - `POST /mcp` - Protocolo MCP para operaciones Gitea - `GET /health` - Health check del servicio +### MCP Metabase Server (`mcp-metabase`) +Servidor MCP personalizado construido con TypeScript que expone la API de Metabase a través del protocolo MCP sobre HTTP. Este servicio: +- Implementa 9 herramientas para interactuar con Metabase +- Usa el puerto 3000 para comunicación HTTP +- Se conecta a Metabase mediante URL interna y autenticación con API Key +- Se ejecuta en las redes `principal` y `traefik-network` +- **Expuesto públicamente en `metabase.nucleoriofrio.com/mcp` SIN autenticación Authentik** +- Prioridad 200 en Traefik para evitar conflictos con otros routers +- Herramientas disponibles: + - `metabase_cards` - Listar y buscar cards/questions + - `metabase_card_info` - Obtener detalles de una card + - `metabase_execute_card` - Ejecutar cards con parámetros + - `metabase_create_card` - Crear nuevas cards/questions + - `metabase_update_card` - Actualizar nombre y descripción de cards + - `metabase_collections` - Listar colecciones + - `metabase_databases` - Listar bases de datos + - `metabase_dashboards` - Listar dashboards + - `metabase_dashboard_info` - Obtener detalles de un dashboard +- Endpoints disponibles: + - `POST /mcp` - Protocolo MCP para operaciones Metabase + - `GET /health` - Health check del servicio + ## Ejemplos de Uso Una vez agregados los servidores MCP, podrás usar las herramientas directamente en Claude Code: @@ -133,6 +164,14 @@ Una vez agregados los servidores MCP, podrás usar las herramientas directamente > Busca issues abiertos en el repositorio analiticaNucleo ``` +**Para Metabase:** +``` +> Lista todas las cards de Metabase +> Busca cards que contengan "ventas" en el nombre +> Ejecuta la card 123 con parámetros de fecha +> Crea una nueva card con una query SQL +``` + **Para Chrome DevTools:** ``` > Abre https://docs.nucleoriofrio.com y toma un snapshot diff --git a/docker-compose.yml b/docker-compose.yml index f70b0fc..c332356 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -113,6 +113,37 @@ services: - "traefik.http.middlewares.${APP_NAME}-mcp-gitea-headers.headers.customrequestheaders.X-Forwarded-Proto=https" - "traefik.http.routers.${APP_NAME}-mcp-gitea.middlewares=${APP_NAME}-mcp-gitea-stripprefix,${APP_NAME}-mcp-gitea-headers" + mcp-metabase: + image: ${REG}/${REPO_OWNER}/mcp-metabase-server:latest + container_name: ${APP_NAME}-mcp-metabase + restart: unless-stopped + environment: + - PORT=3000 + - METABASE_URL=${METABASE_INTERNAL_URL} + - METABASE_API_KEY=${METABASE_API_KEY} + networks: + - principal + - traefik-network + labels: + # Traefik labels - Exposición sin autenticación + - "traefik.enable=true" + - "traefik.docker.network=traefik-network" + + # Service + - "traefik.http.services.${APP_NAME}-mcp-metabase.loadbalancer.server.port=3000" + + # Router sin autenticación para /mcp en metabase domain + - "traefik.http.routers.${APP_NAME}-mcp-metabase.rule=Host(`${METABASE_DOMAIN}`) && PathPrefix(`/mcp`)" + - "traefik.http.routers.${APP_NAME}-mcp-metabase.entrypoints=websecure" + - "traefik.http.routers.${APP_NAME}-mcp-metabase.tls.certresolver=letsencrypt" + - "traefik.http.routers.${APP_NAME}-mcp-metabase.priority=200" + - "traefik.http.routers.${APP_NAME}-mcp-metabase.service=${APP_NAME}-mcp-metabase" + + # Middlewares para MCP Metabase + - "traefik.http.middlewares.${APP_NAME}-mcp-metabase-stripprefix.stripprefix.prefixes=/mcp" + - "traefik.http.middlewares.${APP_NAME}-mcp-metabase-headers.headers.customrequestheaders.X-Forwarded-Proto=https" + - "traefik.http.routers.${APP_NAME}-mcp-metabase.middlewares=${APP_NAME}-mcp-metabase-stripprefix,${APP_NAME}-mcp-metabase-headers" + networks: principal: external: true diff --git a/mcp-metabase-server/.gitignore b/mcp-metabase-server/.gitignore new file mode 100644 index 0000000..dd03dcc --- /dev/null +++ b/mcp-metabase-server/.gitignore @@ -0,0 +1,6 @@ +node_modules +dist +npm-debug.log +.env +*.log +.DS_Store diff --git a/mcp-metabase-server/Dockerfile b/mcp-metabase-server/Dockerfile new file mode 100644 index 0000000..bfe005c --- /dev/null +++ b/mcp-metabase-server/Dockerfile @@ -0,0 +1,22 @@ +FROM node:20-alpine + +WORKDIR /app + +# Copiar archivos de configuración +COPY package*.json ./ +COPY tsconfig.json ./ + +# Instalar dependencias +RUN npm ci + +# Copiar código fuente +COPY src ./src + +# Compilar TypeScript +RUN npm run build + +# Exponer puerto +EXPOSE 3000 + +# Iniciar servidor +CMD ["npm", "start"] diff --git a/mcp-metabase-server/README.md b/mcp-metabase-server/README.md new file mode 100644 index 0000000..11dc38e --- /dev/null +++ b/mcp-metabase-server/README.md @@ -0,0 +1,155 @@ +# MCP Metabase Server + +Servidor MCP (Model Context Protocol) para interactuar con Metabase API. + +## Instalación Rápida + +```bash +claude mcp add --transport http nucleodocs-metabase https://metabase.nucleoriofrio.com/mcp +``` + +## Características + +Proporciona herramientas MCP para: + +- **metabase_cards**: Listar y buscar cards/questions +- **metabase_card_info**: Obtener detalles de una card específica +- **metabase_execute_card**: Ejecutar cards con parámetros dinámicos +- **metabase_create_card**: Crear nuevas cards/questions +- **metabase_update_card**: Actualizar nombre y descripción de cards +- **metabase_collections**: Listar colecciones +- **metabase_databases**: Listar bases de datos (con opción de incluir metadata) +- **metabase_dashboards**: Listar dashboards +- **metabase_dashboard_info**: Obtener detalles de un dashboard + +## Variables de Entorno + +```bash +PORT=3000 # Puerto del servidor (default: 3000) +METABASE_URL=http://metabase:3000 # URL de Metabase +METABASE_API_KEY=mb_xxxxxxxxxxxxx # API Key de Metabase (requerida) +``` + +## Autenticación + +Este servidor usa **API Keys** de Metabase para autenticación. + +### Crear una API Key en Metabase: + +1. Ve a Settings → Admin → API Keys +2. Crea una nueva API Key +3. Copia la key y configúrala como `METABASE_API_KEY` + +## Desarrollo Local + +```bash +# Instalar dependencias +npm install + +# Desarrollo con hot reload +npm run dev + +# Build +npm run build + +# Producción +npm start +``` + +## Docker + +```bash +# Build +docker build -t mcp-metabase-server . + +# Run +docker run -p 3000:3000 \ + -e METABASE_URL=http://metabase:3000 \ + -e METABASE_API_KEY=mb_xxxxx \ + mcp-metabase-server +``` + +## Uso con Claude Code + +```bash +claude mcp add --transport http nucleodocs-metabase https://metabase.tudominio.com/mcp +``` + +## Ejemplos de Uso + +### Listar todas las cards + +```typescript +{ + "action": "list" +} +``` + +### Buscar cards por nombre + +```typescript +{ + "action": "search", + "query": "ventas" +} +``` + +### Ejecutar card con parámetros + +```typescript +{ + "card_id": 123, + "parameters": [ + { + "type": "date/single", + "target": ["variable", ["template-tag", "fecha_desde"]], + "value": "2025-01-01" + } + ] +} +``` + +### Crear nueva card con SQL nativa + +```typescript +{ + "name": "Reporte de Ventas", + "description": "Ventas del último mes", + "dataset_query": { + "type": "native", + "database": 2, + "native": { + "query": "SELECT * FROM ventas WHERE fecha >= {{fecha_desde}}", + "template_tags": { + "fecha_desde": { + "type": "date", + "name": "fecha_desde", + "display_name": "Fecha Desde" + } + } + } + }, + "display": "table", + "collection_id": 5 +} +``` + +## API Metabase + +Documentación: https://www.metabase.com/docs/latest/api + +Endpoints implementados: +- `GET /api/card` - Listar cards +- `GET /api/card/:id` - Obtener card +- `POST /api/card/:id/query` - Ejecutar card +- `POST /api/card` - Crear card +- `PUT /api/card/:id` - Actualizar card +- `GET /api/collection` - Listar colecciones +- `GET /api/database` - Listar bases de datos +- `GET /api/database/:id/metadata` - Metadata de BD +- `GET /api/dashboard` - Listar dashboards +- `GET /api/dashboard/:id` - Obtener dashboard + +## Licencia + +MIT diff --git a/mcp-metabase-server/package.json b/mcp-metabase-server/package.json new file mode 100644 index 0000000..231851b --- /dev/null +++ b/mcp-metabase-server/package.json @@ -0,0 +1,30 @@ +{ + "name": "mcp-metabase-server", + "version": "1.0.0", + "description": "MCP server for Metabase API integration", + "main": "dist/index.js", + "type": "module", + "scripts": { + "build": "tsc", + "start": "node dist/index.js", + "dev": "tsx watch src/index.ts" + }, + "keywords": [ + "mcp", + "metabase", + "api" + ], + "author": "Nucleo Rio Frio", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.0.4", + "express": "^4.21.2", + "zod": "^3.24.1" + }, + "devDependencies": { + "@types/express": "^5.0.0", + "@types/node": "^22.10.5", + "typescript": "^5.7.2", + "tsx": "^4.19.2" + } +} diff --git a/mcp-metabase-server/src/index.ts b/mcp-metabase-server/src/index.ts new file mode 100644 index 0000000..83419ad --- /dev/null +++ b/mcp-metabase-server/src/index.ts @@ -0,0 +1,348 @@ +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; +import express from 'express'; +import { z } from 'zod'; + +// ==================== Configuración ==================== +const METABASE_URL = process.env.METABASE_URL || 'http://metabase:3000'; +const METABASE_API_KEY = process.env.METABASE_API_KEY || ''; +const PORT = parseInt(process.env.PORT || '3000', 10); + +if (!METABASE_API_KEY) { + console.error('ERROR: METABASE_API_KEY no está configurada'); + process.exit(1); +} + +// ==================== Cliente Metabase ==================== +async function metabaseFetch( + endpoint: string, + options: RequestInit = {} +): Promise { + const url = `${METABASE_URL}${endpoint}`; + + const headers: HeadersInit = { + 'Content-Type': 'application/json', + 'X-API-KEY': METABASE_API_KEY, + ...options.headers, + }; + + try { + const response = await fetch(url, { + ...options, + headers, + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Metabase API error (${response.status}): ${errorText}`); + } + + return await response.json(); + } catch (error) { + console.error(`Error en petición a ${endpoint}:`, error); + throw error; + } +} + +// ==================== Servidor MCP ==================== +const server = new McpServer({ + name: 'nucleodocs-metabase', + version: '1.0.0', +}); + +// ==================== Herramientas ==================== + +// 1. Listar/buscar cards +server.registerTool({ + name: 'metabase_cards', + metadata: { + title: 'Listar o buscar Cards/Questions', + description: 'Lista todas las cards o busca cards por nombre/colección', + }, + inputSchema: z.object({ + action: z.enum(['list', 'search']).describe('Acción a realizar'), + query: z.string().optional().describe('Término de búsqueda (para action=search)'), + collection_id: z.number().optional().describe('ID de colección para filtrar'), + }), + outputSchema: z.object({ + cards: z.array(z.any()), + count: z.number(), + }), + handler: async (input) => { + const { action, query, collection_id } = input; + + let endpoint = '/api/card'; + const params = new URLSearchParams(); + + if (collection_id !== undefined) { + params.append('collection', collection_id.toString()); + } + + if (params.toString()) { + endpoint += `?${params.toString()}`; + } + + const cards = await metabaseFetch(endpoint); + + let filteredCards = cards; + if (action === 'search' && query) { + const searchLower = query.toLowerCase(); + filteredCards = cards.filter(card => + card.name?.toLowerCase().includes(searchLower) || + card.description?.toLowerCase().includes(searchLower) + ); + } + + return { + cards: filteredCards, + count: filteredCards.length, + }; + }, +}); + +// 2. Obtener detalles de una card +server.registerTool({ + name: 'metabase_card_info', + metadata: { + title: 'Obtener detalles de una Card', + description: 'Obtiene información detallada de una card específica por su ID', + }, + inputSchema: z.object({ + card_id: z.number().describe('ID de la card'), + }), + outputSchema: z.any(), + handler: async (input) => { + const { card_id } = input; + const card = await metabaseFetch(`/api/card/${card_id}`); + return card; + }, +}); + +// 3. Ejecutar card con parámetros +server.registerTool({ + name: 'metabase_execute_card', + metadata: { + title: 'Ejecutar Card con parámetros', + description: 'Ejecuta una card/question existente, opcionalmente con parámetros', + }, + inputSchema: z.object({ + card_id: z.number().describe('ID de la card a ejecutar'), + parameters: z.array(z.object({ + type: z.string().describe('Tipo de parámetro (ej: date/single, category, etc.)'), + target: z.any().describe('Target del parámetro'), + value: z.any().describe('Valor del parámetro'), + })).optional().describe('Parámetros para la query'), + }), + outputSchema: z.any(), + handler: async (input) => { + const { card_id, parameters } = input; + + const body: any = {}; + if (parameters && parameters.length > 0) { + body.parameters = parameters; + } + + const result = await metabaseFetch(`/api/card/${card_id}/query`, { + method: 'POST', + body: JSON.stringify(body), + }); + + return result; + }, +}); + +// 4. Crear nueva card +server.registerTool({ + name: 'metabase_create_card', + metadata: { + title: 'Crear nueva Card', + description: 'Crea una nueva card/question en Metabase', + }, + inputSchema: z.object({ + name: z.string().describe('Nombre de la card'), + description: z.string().optional().describe('Descripción de la card'), + dataset_query: z.object({ + type: z.enum(['native', 'query']).describe('Tipo de query'), + database: z.number().describe('ID de la base de datos'), + native: z.object({ + query: z.string().describe('Query SQL'), + template_tags: z.record(z.any()).optional().describe('Tags de template para parámetros'), + }).optional().describe('Query nativa SQL (si type=native)'), + query: z.any().optional().describe('Query MBQL (si type=query)'), + }).describe('Configuración de la query'), + display: z.string().default('table').describe('Tipo de visualización (table, bar, line, etc.)'), + visualization_settings: z.record(z.any()).optional().describe('Configuración de visualización'), + collection_id: z.number().optional().describe('ID de la colección donde guardar la card'), + }), + outputSchema: z.any(), + handler: async (input) => { + const body: any = { + name: input.name, + dataset_query: input.dataset_query, + display: input.display, + visualization_settings: input.visualization_settings || {}, + }; + + if (input.description) { + body.description = input.description; + } + + if (input.collection_id !== undefined) { + body.collection_id = input.collection_id; + } + + const result = await metabaseFetch('/api/card', { + method: 'POST', + body: JSON.stringify(body), + }); + + return result; + }, +}); + +// 5. Actualizar card (nombre/descripción) +server.registerTool({ + name: 'metabase_update_card', + metadata: { + title: 'Actualizar Card', + description: 'Actualiza el nombre y/o descripción de una card existente', + }, + inputSchema: z.object({ + card_id: z.number().describe('ID de la card a actualizar'), + name: z.string().optional().describe('Nuevo nombre de la card'), + description: z.string().optional().describe('Nueva descripción de la card'), + }), + outputSchema: z.any(), + handler: async (input) => { + const { card_id, name, description } = input; + + if (!name && !description) { + throw new Error('Debe proporcionar al menos name o description para actualizar'); + } + + const body: any = {}; + if (name) body.name = name; + if (description !== undefined) body.description = description; + + const result = await metabaseFetch(`/api/card/${card_id}`, { + method: 'PUT', + body: JSON.stringify(body), + }); + + return result; + }, +}); + +// 6. Listar colecciones +server.registerTool({ + name: 'metabase_collections', + metadata: { + title: 'Listar Colecciones', + description: 'Lista todas las colecciones disponibles en Metabase', + }, + inputSchema: z.object({}), + outputSchema: z.array(z.any()), + handler: async () => { + const collections = await metabaseFetch('/api/collection'); + return collections; + }, +}); + +// 7. Listar bases de datos +server.registerTool({ + name: 'metabase_databases', + metadata: { + title: 'Listar Bases de Datos', + description: 'Lista todas las bases de datos configuradas en Metabase', + }, + inputSchema: z.object({ + include_tables: z.boolean().optional().default(false).describe('Incluir metadatos de tablas y campos'), + }), + outputSchema: z.any(), + handler: async (input) => { + const { include_tables } = input; + const databases = await metabaseFetch('/api/database'); + + if (include_tables) { + // Obtener metadata completa de cada base de datos + const databasesWithMetadata = await Promise.all( + databases.map(async (db) => { + try { + const metadata = await metabaseFetch(`/api/database/${db.id}/metadata`); + return { ...db, metadata }; + } catch (error) { + console.error(`Error obteniendo metadata de DB ${db.id}:`, error); + return db; + } + }) + ); + return databasesWithMetadata; + } + + return databases; + }, +}); + +// 8. Listar dashboards +server.registerTool({ + name: 'metabase_dashboards', + metadata: { + title: 'Listar Dashboards', + description: 'Lista todos los dashboards disponibles', + }, + inputSchema: z.object({}), + outputSchema: z.array(z.any()), + handler: async () => { + const dashboards = await metabaseFetch('/api/dashboard'); + return dashboards; + }, +}); + +// 9. Obtener detalles de un dashboard +server.registerTool({ + name: 'metabase_dashboard_info', + metadata: { + title: 'Obtener detalles de Dashboard', + description: 'Obtiene información detallada de un dashboard específico incluyendo sus cards', + }, + inputSchema: z.object({ + dashboard_id: z.number().describe('ID del dashboard'), + }), + outputSchema: z.any(), + handler: async (input) => { + const { dashboard_id } = input; + const dashboard = await metabaseFetch(`/api/dashboard/${dashboard_id}`); + return dashboard; + }, +}); + +// ==================== Servidor Express ==================== +const app = express(); +app.use(express.json()); + +// Health check +app.get('/health', (req, res) => { + res.json({ + status: 'ok', + server: 'nucleodocs-metabase', + metabase_url: METABASE_URL, + timestamp: new Date().toISOString() + }); +}); + +// Endpoint MCP (Traefik hace StripPrefix de /mcp) +app.post('/', async (req, res) => { + const transport = new StreamableHTTPServerTransport({ + endpoint: '/', + sessionIdGenerator: () => Math.random().toString(36).substring(7), + }); + + await transport.handleRequest(req, res, server); +}); + +// Iniciar servidor +app.listen(PORT, '0.0.0.0', () => { + console.log(`✅ MCP Metabase Server corriendo en puerto ${PORT}`); + console.log(`📊 Conectado a Metabase: ${METABASE_URL}`); + console.log(`🔑 Usando autenticación con API Key`); +}); diff --git a/mcp-metabase-server/tsconfig.json b/mcp-metabase-server/tsconfig.json new file mode 100644 index 0000000..cc84a6d --- /dev/null +++ b/mcp-metabase-server/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ES2022", + "lib": ["ES2022"], + "moduleResolution": "node", + "rootDir": "./src", + "outDir": "./dist", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +}