apprecio-mcp-base 1.1.1 → 1.1.3
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 +486 -131
- package/cli/dist/generate-feature.js +151 -53
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,137 +1,283 @@
|
|
|
1
|
-
#
|
|
1
|
+
# apprecio-mcp-base
|
|
2
2
|
|
|
3
3
|
Base package para crear servidores MCP (Model Context Protocol) de Apprecio con consistencia y reutilización de código.
|
|
4
4
|
|
|
5
5
|
## 🚀 Características
|
|
6
6
|
|
|
7
7
|
- **Servidor Base Abstracto**: Clase `McpBaseServer` que encapsula toda la lógica común
|
|
8
|
+
- **Sistema de Features**: Arquitectura modular con `FeatureModule`
|
|
8
9
|
- **Configuración Centralizada**: Sistema de configuración con validación Zod
|
|
9
10
|
- **Logger Unificado**: Winston logger con niveles configurables y rotación de archivos
|
|
10
|
-
- **Middlewares Reutilizables**: Autenticación, Rate Limiting, CORS, Helmet
|
|
11
|
+
- **Middlewares Reutilizables**: Autenticación Bearer Token, Rate Limiting, CORS, Helmet
|
|
11
12
|
- **Conectores Database**: MongoDB y Redis pre-configurados
|
|
12
13
|
- **Port Management**: Búsqueda automática de puertos disponibles
|
|
13
14
|
- **Graceful Shutdown**: Manejo automático de señales SIGINT/SIGTERM
|
|
14
15
|
- **TypeScript First**: Completamente tipado con soporte para path aliases
|
|
16
|
+
- **CLI Generator**: Generador de proyectos y features automático
|
|
15
17
|
|
|
16
18
|
## 📦 Instalación
|
|
17
19
|
|
|
18
20
|
```bash
|
|
19
|
-
npm install
|
|
21
|
+
npm install apprecio-mcp-base
|
|
20
22
|
# o
|
|
21
|
-
pnpm add
|
|
23
|
+
pnpm add apprecio-mcp-base
|
|
22
24
|
```
|
|
23
25
|
|
|
24
|
-
## 🎯
|
|
26
|
+
## 🎯 Quick Start
|
|
25
27
|
|
|
26
|
-
### 1
|
|
28
|
+
### Opción 1: Usar el CLI (Recomendado)
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# 1. Setup global link (una vez)
|
|
32
|
+
cd apprecio-mcp-base
|
|
33
|
+
npm run build
|
|
34
|
+
npm link
|
|
35
|
+
|
|
36
|
+
# 2. Generar nuevo proyecto
|
|
37
|
+
mcp-generate init
|
|
38
|
+
|
|
39
|
+
# Responder wizard:
|
|
40
|
+
# - Nombre del proyecto
|
|
41
|
+
# - Ruta
|
|
42
|
+
# - Usar MongoDB (sí/no)
|
|
43
|
+
# - Base URL de tu API
|
|
44
|
+
# ¿Instalar dependencias? Yes
|
|
45
|
+
# ¿Linkear apprecio-mcp-base? Yes
|
|
46
|
+
|
|
47
|
+
# 3. Ejecutar
|
|
48
|
+
cd my-project
|
|
49
|
+
npm run dev
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Opción 2: Manual
|
|
27
53
|
|
|
28
54
|
```typescript
|
|
29
55
|
import {
|
|
30
56
|
McpBaseServer,
|
|
31
|
-
|
|
57
|
+
createServer,
|
|
32
58
|
findAvailablePort,
|
|
33
|
-
createMongoDBConnector
|
|
34
|
-
|
|
35
|
-
|
|
59
|
+
createMongoDBConnector,
|
|
60
|
+
logger,
|
|
61
|
+
type MongoDBConnector
|
|
62
|
+
} from 'apprecio-mcp-base';
|
|
36
63
|
|
|
37
|
-
|
|
38
|
-
class MyCustomMcpServer extends McpBaseServer {
|
|
64
|
+
class MyMcpServer extends McpBaseServer {
|
|
39
65
|
private dbConnector?: MongoDBConnector;
|
|
40
66
|
|
|
41
67
|
protected async registerFeatures(): Promise<void> {
|
|
42
|
-
// Registrar tus features
|
|
43
|
-
this.registerFeature('
|
|
68
|
+
// Registrar tus features
|
|
69
|
+
this.registerFeature('users', usersFeature);
|
|
44
70
|
}
|
|
45
71
|
|
|
46
72
|
protected async onBeforeStart(): Promise<void> {
|
|
47
|
-
|
|
48
|
-
if (
|
|
49
|
-
this.dbConnector = createMongoDBConnector(
|
|
73
|
+
const mongoUri = this.config.mongodbUri;
|
|
74
|
+
if (mongoUri) {
|
|
75
|
+
this.dbConnector = createMongoDBConnector(mongoUri);
|
|
50
76
|
await this.dbConnector.connect();
|
|
51
77
|
}
|
|
52
78
|
}
|
|
53
79
|
|
|
54
80
|
protected async onBeforeShutdown(): Promise<void> {
|
|
55
|
-
// Cleanup
|
|
56
81
|
if (this.dbConnector) {
|
|
57
82
|
await this.dbConnector.disconnect();
|
|
58
83
|
}
|
|
59
84
|
}
|
|
60
85
|
}
|
|
61
86
|
|
|
62
|
-
// Iniciar el servidor
|
|
63
87
|
async function main() {
|
|
64
|
-
const server = new
|
|
65
|
-
name: 'my-
|
|
88
|
+
const server = new MyMcpServer({
|
|
89
|
+
name: 'my-mcp-server',
|
|
66
90
|
version: '1.0.0',
|
|
67
91
|
});
|
|
68
92
|
|
|
69
93
|
const port = await findAvailablePort(server.getConfig().ssePort);
|
|
94
|
+
const { httpServer, transport } = await createServer(port);
|
|
70
95
|
|
|
71
|
-
|
|
72
|
-
mcpServer: server.getMcpServer(),
|
|
73
|
-
config: server.getConfig(),
|
|
74
|
-
port,
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
await server.start(httpServer, port);
|
|
96
|
+
await server.start(httpServer, transport, port);
|
|
78
97
|
}
|
|
79
98
|
|
|
80
99
|
main();
|
|
81
100
|
```
|
|
82
101
|
|
|
83
|
-
|
|
102
|
+
## 🔧 CLI Generator
|
|
103
|
+
|
|
104
|
+
El paquete incluye un CLI para generar proyectos y features automáticamente.
|
|
105
|
+
|
|
106
|
+
### Comandos Disponibles
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# Generar nuevo proyecto
|
|
110
|
+
mcp-generate init
|
|
111
|
+
|
|
112
|
+
# Generar nueva feature
|
|
113
|
+
mcp-generate feature
|
|
114
|
+
|
|
115
|
+
# Help
|
|
116
|
+
mcp-generate --help
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Generar Proyecto
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
mcp-generate init
|
|
123
|
+
|
|
124
|
+
# Wizard interactivo:
|
|
125
|
+
? Nombre del proyecto: my-awesome-mcp
|
|
126
|
+
? Ruta: ./my-awesome-mcp
|
|
127
|
+
? Descripción: My awesome MCP server
|
|
128
|
+
? Autor: Tu Nombre
|
|
129
|
+
? ¿Usar MongoDB? No
|
|
130
|
+
? Base URL de tu API: https://api.example.com
|
|
131
|
+
? ¿Instalar dependencias? Yes
|
|
132
|
+
? ¿Linkear apprecio-mcp-base? Yes
|
|
133
|
+
|
|
134
|
+
# Genera:
|
|
135
|
+
# ✅ Estructura completa del proyecto
|
|
136
|
+
# ✅ Archivos base (main.ts, services, utils)
|
|
137
|
+
# ✅ Scripts de setup (post-install.sh, check-permissions.sh)
|
|
138
|
+
# ✅ Configuración (.env, tsconfig.json, package.json)
|
|
139
|
+
# ✅ README.md completo
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Generar Feature
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
cd my-project
|
|
146
|
+
npm run generate
|
|
147
|
+
|
|
148
|
+
# Wizard interactivo:
|
|
149
|
+
? Nombre de la feature: users
|
|
150
|
+
? Descripción: Manage users
|
|
151
|
+
? ¿Necesita service? Yes
|
|
152
|
+
? Tipo de service: API Client
|
|
153
|
+
? Tools: [x] list, [x] get, [x] create, [x] update
|
|
154
|
+
? Nombre de la entidad: user
|
|
155
|
+
? ¿Auto-registrar en main.ts? Yes
|
|
156
|
+
|
|
157
|
+
# Genera:
|
|
158
|
+
# ✅ users.feature.ts - Feature module con tools
|
|
159
|
+
# ✅ users.service.ts - Business logic
|
|
160
|
+
# ✅ users.validation.ts - Zod schemas
|
|
161
|
+
# ✅ Actualiza main.ts automáticamente
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## 📚 Sistema de Features
|
|
165
|
+
|
|
166
|
+
### Crear un Feature Module
|
|
84
167
|
|
|
85
168
|
```typescript
|
|
86
169
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
87
|
-
import type
|
|
170
|
+
import { logger, type FeatureModule } from 'apprecio-mcp-base';
|
|
88
171
|
|
|
89
|
-
export const
|
|
90
|
-
name: '
|
|
172
|
+
export const usersFeature: FeatureModule = {
|
|
173
|
+
name: 'users',
|
|
91
174
|
|
|
92
175
|
register(mcpServer: McpServer): void {
|
|
93
|
-
// Registrar
|
|
176
|
+
// Registrar tool
|
|
94
177
|
mcpServer.tool(
|
|
95
|
-
'
|
|
96
|
-
'
|
|
178
|
+
'list_users',
|
|
179
|
+
'List all users',
|
|
97
180
|
{
|
|
98
|
-
type: '
|
|
99
|
-
|
|
100
|
-
param: { type: 'string' }
|
|
101
|
-
},
|
|
102
|
-
required: ['param']
|
|
181
|
+
page: { type: 'number', description: 'Page number' },
|
|
182
|
+
limit: { type: 'number', description: 'Items per page' }
|
|
103
183
|
},
|
|
104
|
-
async (
|
|
105
|
-
//
|
|
184
|
+
async (args, extra) => {
|
|
185
|
+
// Extraer token de auth
|
|
186
|
+
const req = extra as any;
|
|
187
|
+
const token = req?.requestInfo?.headers?.authorization;
|
|
188
|
+
|
|
189
|
+
// Tu lógica aquí
|
|
190
|
+
const users = await userService.list(args);
|
|
191
|
+
|
|
106
192
|
return {
|
|
107
193
|
content: [{
|
|
108
194
|
type: 'text',
|
|
109
|
-
text:
|
|
195
|
+
text: JSON.stringify(users)
|
|
110
196
|
}]
|
|
111
197
|
};
|
|
112
198
|
}
|
|
113
199
|
);
|
|
200
|
+
|
|
201
|
+
logger.info('Users feature registered');
|
|
114
202
|
},
|
|
115
203
|
|
|
116
204
|
async initialize(): Promise<void> {
|
|
117
|
-
|
|
118
|
-
console.log('Feature initialized');
|
|
205
|
+
logger.info('Users feature initialized');
|
|
119
206
|
},
|
|
120
207
|
|
|
121
208
|
async cleanup(): Promise<void> {
|
|
122
|
-
|
|
123
|
-
console.log('Feature cleaned up');
|
|
209
|
+
logger.info('Users feature cleaned up');
|
|
124
210
|
}
|
|
125
211
|
};
|
|
126
212
|
```
|
|
127
213
|
|
|
128
|
-
###
|
|
214
|
+
### Registrar Features
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
class MyMcpServer extends McpBaseServer {
|
|
218
|
+
protected async registerFeatures(): Promise<void> {
|
|
219
|
+
this.registerFeature('users', usersFeature);
|
|
220
|
+
this.registerFeature('products', productsFeature);
|
|
221
|
+
this.registerFeature('orders', ordersFeature);
|
|
222
|
+
|
|
223
|
+
logger.info('All features registered');
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## 🔐 Autenticación
|
|
229
|
+
|
|
230
|
+
### Middleware de Autenticación
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
import { createAuthMiddleware } from 'apprecio-mcp-base';
|
|
234
|
+
|
|
235
|
+
// Crear middleware
|
|
236
|
+
const auth = createAuthMiddleware({
|
|
237
|
+
apiKey: process.env.API_KEY,
|
|
238
|
+
enabled: process.env.AUTH_ENABLED !== 'false'
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// Usar en Express
|
|
242
|
+
app.get('/health', handler); // Público
|
|
243
|
+
|
|
244
|
+
app.use('/sse', auth); // Protegido
|
|
245
|
+
app.post('/sse', handler);
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Usar Token en Features
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
mcpServer.tool('secure_action', schema, async (args, extra) => {
|
|
252
|
+
// Extraer token
|
|
253
|
+
const req = extra as any;
|
|
254
|
+
const token = req?.requestInfo?.headers?.authorization;
|
|
255
|
+
|
|
256
|
+
if (!token) {
|
|
257
|
+
throw new Error('Unauthorized');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Validar y usar token
|
|
261
|
+
const isValid = await validateToken(token);
|
|
262
|
+
if (!isValid) {
|
|
263
|
+
throw new Error('Invalid token');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Procesar acción
|
|
267
|
+
return { content: [{ type: 'text', text: 'Success' }] };
|
|
268
|
+
});
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## 📝 Configuración
|
|
272
|
+
|
|
273
|
+
### Variables de Entorno
|
|
129
274
|
|
|
130
275
|
```env
|
|
131
276
|
# Authentication
|
|
132
|
-
API_KEY=your-secret-
|
|
277
|
+
API_KEY=your-secret-key-here
|
|
278
|
+
AUTH_ENABLED=false
|
|
133
279
|
|
|
134
|
-
# Database
|
|
280
|
+
# Database (opcional)
|
|
135
281
|
MONGODB_URI=mongodb://localhost:27017/mydb
|
|
136
282
|
|
|
137
283
|
# Server
|
|
@@ -152,150 +298,359 @@ RATE_LIMIT_WINDOW_MS=900000
|
|
|
152
298
|
RATE_LIMIT_MAX_REQUESTS=100
|
|
153
299
|
```
|
|
154
300
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
### McpBaseServer
|
|
158
|
-
|
|
159
|
-
Clase abstracta base para servidores MCP.
|
|
301
|
+
### Acceder a Configuración
|
|
160
302
|
|
|
161
303
|
```typescript
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
// Métodos públicos
|
|
173
|
-
public getMcpServer(): McpServer
|
|
174
|
-
public getConfig(): BaseConfig
|
|
175
|
-
public getFeatures(): Map<string, FeatureModule>
|
|
176
|
-
|
|
177
|
-
// Método para iniciar el servidor
|
|
178
|
-
async start(httpServer: HttpServer, port: number): Promise<void>
|
|
304
|
+
class MyMcpServer extends McpBaseServer {
|
|
305
|
+
protected async registerFeatures(): Promise<void> {
|
|
306
|
+
// Acceder a config
|
|
307
|
+
const config = this.config;
|
|
308
|
+
|
|
309
|
+
logger.info(`Server port: ${config.ssePort}`);
|
|
310
|
+
logger.info(`MongoDB URI: ${config.mongodbUri}`);
|
|
311
|
+
logger.info(`Log level: ${config.logLevel}`);
|
|
312
|
+
}
|
|
179
313
|
}
|
|
180
314
|
```
|
|
181
315
|
|
|
182
|
-
|
|
316
|
+
## 🗄️ Database Connectors
|
|
183
317
|
|
|
184
|
-
|
|
318
|
+
### MongoDB
|
|
185
319
|
|
|
186
320
|
```typescript
|
|
187
|
-
|
|
188
|
-
constructor(envPath?: string)
|
|
189
|
-
|
|
190
|
-
// Getters
|
|
191
|
-
get apiKey(): string
|
|
192
|
-
get mongodbUri(): string | undefined
|
|
193
|
-
get ssePort(): number
|
|
194
|
-
get logLevel(): 'debug' | 'info' | 'warn' | 'error'
|
|
195
|
-
// ... más getters
|
|
196
|
-
|
|
197
|
-
// Métodos
|
|
198
|
-
getAll(): BaseConfigType
|
|
199
|
-
extend<T>(customConfig: T): BaseConfigType & T
|
|
200
|
-
}
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
### Logger
|
|
204
|
-
|
|
205
|
-
Logger centralizado con Winston.
|
|
321
|
+
import { createMongoDBConnector } from 'apprecio-mcp-base';
|
|
206
322
|
|
|
207
|
-
|
|
208
|
-
|
|
323
|
+
// Crear conector
|
|
324
|
+
const db = createMongoDBConnector('mongodb://localhost:27017/db');
|
|
209
325
|
|
|
210
|
-
//
|
|
211
|
-
|
|
212
|
-
logger.error('Error', error);
|
|
213
|
-
logger.debug('Debug info', { metadata });
|
|
326
|
+
// Conectar
|
|
327
|
+
await db.connect();
|
|
214
328
|
|
|
215
|
-
//
|
|
216
|
-
const
|
|
217
|
-
|
|
329
|
+
// Usar mongoose
|
|
330
|
+
const connection = db.getConnection();
|
|
331
|
+
const User = connection.model('User', userSchema);
|
|
218
332
|
|
|
219
|
-
//
|
|
220
|
-
|
|
333
|
+
// Desconectar
|
|
334
|
+
await db.disconnect();
|
|
221
335
|
```
|
|
222
336
|
|
|
223
|
-
###
|
|
337
|
+
### Redis
|
|
224
338
|
|
|
225
339
|
```typescript
|
|
226
|
-
import {
|
|
340
|
+
import { createRedisConnector } from 'apprecio-mcp-base';
|
|
341
|
+
|
|
342
|
+
// Crear conector
|
|
343
|
+
const redis = createRedisConnector('redis://localhost:6379');
|
|
344
|
+
|
|
345
|
+
// Conectar
|
|
346
|
+
await redis.connect();
|
|
227
347
|
|
|
228
|
-
|
|
348
|
+
// Usar
|
|
349
|
+
const client = redis.getClient();
|
|
350
|
+
await client.set('key', 'value');
|
|
351
|
+
const value = await client.get('key');
|
|
229
352
|
|
|
230
|
-
|
|
231
|
-
|
|
353
|
+
// Desconectar
|
|
354
|
+
await redis.disconnect();
|
|
232
355
|
```
|
|
233
356
|
|
|
234
|
-
|
|
357
|
+
## 📊 Logger
|
|
235
358
|
|
|
236
359
|
```typescript
|
|
237
|
-
import {
|
|
360
|
+
import { logger, createChildLogger, setLogLevel } from 'apprecio-mcp-base';
|
|
238
361
|
|
|
239
|
-
|
|
240
|
-
|
|
362
|
+
// Uso básico
|
|
363
|
+
logger.info('Server started');
|
|
364
|
+
logger.error('Error occurred', error);
|
|
365
|
+
logger.debug('Debug info', { metadata });
|
|
366
|
+
logger.warn('Warning message');
|
|
241
367
|
|
|
242
|
-
//
|
|
243
|
-
const
|
|
368
|
+
// Logger con contexto
|
|
369
|
+
const featureLogger = createChildLogger('UsersFeature');
|
|
370
|
+
featureLogger.info('User created', { userId: 123 });
|
|
244
371
|
|
|
245
|
-
|
|
372
|
+
// Cambiar nivel dinámicamente
|
|
373
|
+
setLogLevel('debug');
|
|
246
374
|
```
|
|
247
375
|
|
|
248
|
-
##
|
|
376
|
+
## 🛠️ Desarrollo
|
|
249
377
|
|
|
250
|
-
### Estructura del
|
|
378
|
+
### Estructura del Proyecto
|
|
251
379
|
|
|
252
380
|
```
|
|
253
|
-
|
|
381
|
+
apprecio-mcp-base/
|
|
382
|
+
├── cli/ # CLI generator
|
|
383
|
+
│ ├── generators/
|
|
384
|
+
│ │ ├── init-generator.ts # Generador de proyectos
|
|
385
|
+
│ │ ├── feature-generator.ts # Generador de features
|
|
386
|
+
│ │ ├── service-generator.ts # Generador de services
|
|
387
|
+
│ │ ├── router-generator.ts # Generador de routers
|
|
388
|
+
│ │ └── main-updater.ts # Actualizador de main.ts
|
|
389
|
+
│ ├── templates/
|
|
390
|
+
│ │ ├── post-install.sh
|
|
391
|
+
│ │ └── check-permissions.sh
|
|
392
|
+
│ └── generate-feature.ts # Entry point del CLI
|
|
254
393
|
├── src/
|
|
255
394
|
│ ├── core/ # Núcleo del framework
|
|
256
395
|
│ │ ├── McpBaseServer.ts # Clase base
|
|
257
|
-
│ │ ├── config.ts #
|
|
258
|
-
│ │ ├── logger.ts # Logger
|
|
396
|
+
│ │ ├── config.ts # Sistema de configuración
|
|
397
|
+
│ │ ├── logger.ts # Logger centralizado
|
|
259
398
|
│ │ └── types.ts # Tipos compartidos
|
|
260
399
|
│ ├── middleware/ # Middlewares Express
|
|
400
|
+
│ │ ├── auth.ts # Autenticación Bearer
|
|
401
|
+
│ │ ├── rate-limit.ts # Rate limiting
|
|
402
|
+
│ │ └── cors.ts # CORS config
|
|
261
403
|
│ ├── database/ # Conectores DB
|
|
404
|
+
│ │ ├── mongodb.ts
|
|
405
|
+
│ │ └── redis.ts
|
|
262
406
|
│ ├── server/ # Server builders
|
|
407
|
+
│ │ └── express.ts
|
|
263
408
|
│ ├── utils/ # Utilidades
|
|
409
|
+
│ │ ├── port.ts # Port finder
|
|
410
|
+
│ │ └── shutdown.ts # Graceful shutdown
|
|
264
411
|
│ └── index.ts # Exports principales
|
|
412
|
+
├── examples/ # Ejemplos de uso
|
|
265
413
|
├── package.json
|
|
266
414
|
├── tsconfig.json
|
|
267
415
|
└── README.md
|
|
268
416
|
```
|
|
269
417
|
|
|
270
|
-
###
|
|
418
|
+
### Scripts Disponibles
|
|
419
|
+
|
|
420
|
+
```bash
|
|
421
|
+
# Build del paquete
|
|
422
|
+
npm run build
|
|
423
|
+
|
|
424
|
+
# Build con watch
|
|
425
|
+
npm run build:watch
|
|
426
|
+
|
|
427
|
+
# Build CLI
|
|
428
|
+
npm run build:cli
|
|
429
|
+
|
|
430
|
+
# Build todo
|
|
431
|
+
npm run build:all
|
|
432
|
+
|
|
433
|
+
# Link global
|
|
434
|
+
npm link
|
|
435
|
+
|
|
436
|
+
# Generate feature (si estás en un proyecto)
|
|
437
|
+
npm run generate
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
### Desarrollo del CLI
|
|
271
441
|
|
|
272
442
|
```bash
|
|
443
|
+
# Build CLI
|
|
444
|
+
npm run build:cli
|
|
445
|
+
|
|
446
|
+
# Link globalmente
|
|
447
|
+
npm link
|
|
448
|
+
|
|
449
|
+
# Usar en cualquier lugar
|
|
450
|
+
mcp-generate init
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
## 🚀 Setup para Desarrollo
|
|
454
|
+
|
|
455
|
+
### 1. Setup Inicial
|
|
456
|
+
|
|
457
|
+
```bash
|
|
458
|
+
# Clone repo
|
|
459
|
+
git clone https://github.com/apprecio/apprecio-mcp-base.git
|
|
460
|
+
cd apprecio-mcp-base
|
|
461
|
+
|
|
462
|
+
# Instalar dependencias
|
|
463
|
+
npm install
|
|
464
|
+
|
|
465
|
+
# Build
|
|
273
466
|
npm run build
|
|
467
|
+
|
|
468
|
+
# Link global
|
|
469
|
+
npm link
|
|
274
470
|
```
|
|
275
471
|
|
|
276
|
-
###
|
|
472
|
+
### 2. Crear Proyecto de Prueba
|
|
277
473
|
|
|
278
474
|
```bash
|
|
475
|
+
# Generar proyecto
|
|
476
|
+
mcp-generate init
|
|
477
|
+
|
|
478
|
+
# Configurar
|
|
479
|
+
cd my-test-project
|
|
480
|
+
npm install
|
|
481
|
+
npm link apprecio-mcp-base
|
|
482
|
+
|
|
483
|
+
# Ejecutar
|
|
279
484
|
npm run dev
|
|
280
485
|
```
|
|
281
486
|
|
|
282
|
-
|
|
487
|
+
### 3. Desarrollo Iterativo
|
|
283
488
|
|
|
284
|
-
|
|
489
|
+
```bash
|
|
490
|
+
# Terminal 1: Watch mode en apprecio-mcp-base
|
|
491
|
+
cd apprecio-mcp-base
|
|
492
|
+
npm run build:watch
|
|
493
|
+
|
|
494
|
+
# Terminal 2: Tu proyecto
|
|
495
|
+
cd my-test-project
|
|
496
|
+
npm run dev
|
|
497
|
+
|
|
498
|
+
# Los cambios en apprecio-mcp-base se reflejan automáticamente
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
## 📚 API Reference
|
|
502
|
+
|
|
503
|
+
### McpBaseServer
|
|
504
|
+
|
|
505
|
+
```typescript
|
|
506
|
+
abstract class McpBaseServer {
|
|
507
|
+
constructor(options: ServerOptions)
|
|
508
|
+
|
|
509
|
+
// Métodos abstractos
|
|
510
|
+
protected abstract registerFeatures(): Promise<void>
|
|
511
|
+
|
|
512
|
+
// Hooks opcionales
|
|
513
|
+
protected async onBeforeStart?(): Promise<void>
|
|
514
|
+
protected async onBeforeShutdown?(): Promise<void>
|
|
515
|
+
|
|
516
|
+
// Métodos públicos
|
|
517
|
+
getMcpServer(): McpServer
|
|
518
|
+
getConfig(): BaseConfig
|
|
519
|
+
getFeatures(): Map<string, FeatureModule>
|
|
520
|
+
|
|
521
|
+
// Lifecycle
|
|
522
|
+
async start(httpServer: HttpServer, transport: Transport, port: number): Promise<void>
|
|
523
|
+
}
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
### FeatureModule
|
|
527
|
+
|
|
528
|
+
```typescript
|
|
529
|
+
interface FeatureModule {
|
|
530
|
+
name: string;
|
|
531
|
+
register(mcpServer: McpServer): void;
|
|
532
|
+
initialize?(): Promise<void>;
|
|
533
|
+
cleanup?(): Promise<void>;
|
|
534
|
+
}
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
### BaseConfig
|
|
538
|
+
|
|
539
|
+
```typescript
|
|
540
|
+
class BaseConfig {
|
|
541
|
+
get apiKey(): string
|
|
542
|
+
get mongodbUri(): string | undefined
|
|
543
|
+
get ssePort(): number
|
|
544
|
+
get logLevel(): 'debug' | 'info' | 'warn' | 'error'
|
|
545
|
+
get mcpTimeout(): number
|
|
546
|
+
get sseTimeout(): number
|
|
547
|
+
get corsAllowOrigin(): string
|
|
548
|
+
get rateLimitWindowMs(): number
|
|
549
|
+
get rateLimitMaxRequests(): number
|
|
550
|
+
}
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
## 🆘 Troubleshooting
|
|
554
|
+
|
|
555
|
+
### Error: Cannot find module 'apprecio-mcp-base'
|
|
556
|
+
|
|
557
|
+
```bash
|
|
558
|
+
# Verificar link global
|
|
559
|
+
npm list -g apprecio-mcp-base --depth=0
|
|
560
|
+
|
|
561
|
+
# Si no está linkeado:
|
|
562
|
+
cd apprecio-mcp-base
|
|
563
|
+
npm run build
|
|
564
|
+
npm link
|
|
565
|
+
|
|
566
|
+
# En tu proyecto:
|
|
567
|
+
npm link apprecio-mcp-base
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
### Error: EACCES (Permission Denied)
|
|
571
|
+
|
|
572
|
+
```bash
|
|
573
|
+
# En el proyecto generado
|
|
574
|
+
./check-permissions.sh
|
|
575
|
+
|
|
576
|
+
# Arreglar permisos
|
|
577
|
+
sudo chown -R $(whoami) .
|
|
578
|
+
chmod -R 755 .
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
### Error: npm cache EACCES
|
|
582
|
+
|
|
583
|
+
```bash
|
|
584
|
+
# Arreglar caché de npm
|
|
585
|
+
sudo chown -R $(whoami) ~/.npm
|
|
586
|
+
npm cache clean --force
|
|
587
|
+
npm install
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### Cambios no se reflejan
|
|
591
|
+
|
|
592
|
+
```bash
|
|
593
|
+
# Rebuild apprecio-mcp-base
|
|
594
|
+
cd apprecio-mcp-base
|
|
595
|
+
npm run build
|
|
596
|
+
|
|
597
|
+
# Los proyectos linkeados ven cambios automáticamente
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
## 💡 Best Practices
|
|
601
|
+
|
|
602
|
+
### ✅ DO:
|
|
603
|
+
|
|
604
|
+
- Usa el CLI para generar proyectos y features
|
|
605
|
+
- Mantén features pequeños y enfocados
|
|
606
|
+
- Usa el logger en lugar de console.log
|
|
607
|
+
- Implementa cleanup en features con recursos
|
|
608
|
+
- Usa TypeScript strict mode
|
|
609
|
+
- Valida inputs con Zod
|
|
610
|
+
|
|
611
|
+
### ❌ DON'T:
|
|
612
|
+
|
|
613
|
+
- No uses `sudo npm install`
|
|
614
|
+
- No modifies archivos generados manualmente
|
|
615
|
+
- No uses console.log en producción
|
|
616
|
+
- No olvides implementar cleanup
|
|
617
|
+
- No hardcodees configuración
|
|
618
|
+
|
|
619
|
+
## 🎯 Ejemplos
|
|
620
|
+
|
|
621
|
+
Ver `examples/` para:
|
|
285
622
|
|
|
286
623
|
- Servidor MCP simple
|
|
287
624
|
- Servidor con MongoDB
|
|
288
625
|
- Servidor con múltiples features
|
|
289
|
-
-
|
|
626
|
+
- Feature con autenticación
|
|
627
|
+
- Feature con validación Zod
|
|
290
628
|
|
|
291
|
-
##
|
|
629
|
+
## 📖 Documentación
|
|
292
630
|
|
|
293
|
-
|
|
631
|
+
- [Model Context Protocol](https://modelcontextprotocol.io) - Especificación MCP
|
|
632
|
+
- [TypeScript](https://www.typescriptlang.org/) - TypeScript docs
|
|
633
|
+
- [Winston](https://github.com/winstonjs/winston) - Logger docs
|
|
634
|
+
- [Zod](https://zod.dev/) - Validation docs
|
|
294
635
|
|
|
295
636
|
## 🤝 Contribuir
|
|
296
637
|
|
|
297
638
|
1. Fork el proyecto
|
|
298
|
-
2. Crea una branch
|
|
299
|
-
3. Commit
|
|
300
|
-
4. Push
|
|
639
|
+
2. Crea una branch: `git checkout -b feature/amazing-feature`
|
|
640
|
+
3. Commit cambios: `git commit -m 'Add amazing feature'`
|
|
641
|
+
4. Push: `git push origin feature/amazing-feature`
|
|
301
642
|
5. Abre un Pull Request
|
|
643
|
+
|
|
644
|
+
## 📄 Licencia
|
|
645
|
+
|
|
646
|
+
MIT © Apprecio
|
|
647
|
+
|
|
648
|
+
## 🔗 Links
|
|
649
|
+
|
|
650
|
+
- [GitHub](https://github.com/apprecio/apprecio-mcp-base)
|
|
651
|
+
- [npm](https://www.npmjs.com/package/apprecio-mcp-base)
|
|
652
|
+
- [Issues](https://github.com/apprecio/apprecio-mcp-base/issues)
|
|
653
|
+
|
|
654
|
+
---
|
|
655
|
+
|
|
656
|
+
**Hecho con ❤️ por el equipo de Apprecio**
|
|
@@ -12,9 +12,6 @@ program
|
|
|
12
12
|
.name('mcp-generate')
|
|
13
13
|
.description('CLI para generar proyectos y features MCP automáticamente')
|
|
14
14
|
.version('1.2.0');
|
|
15
|
-
// ==========================================
|
|
16
|
-
// COMANDO: init
|
|
17
|
-
// ==========================================
|
|
18
15
|
program
|
|
19
16
|
.command('init')
|
|
20
17
|
.description('Inicializa un nuevo proyecto MCP con estructura completa')
|
|
@@ -87,11 +84,112 @@ program
|
|
|
87
84
|
apiBaseUrl: answers.apiBaseUrl
|
|
88
85
|
});
|
|
89
86
|
console.log(chalk.green('\n✅ ¡Proyecto creado exitosamente!\n'));
|
|
87
|
+
// Preguntar si quiere instalar dependencias automáticamente
|
|
88
|
+
const installAnswer = await inquirer.prompt([
|
|
89
|
+
{
|
|
90
|
+
type: 'confirm',
|
|
91
|
+
name: 'installDeps',
|
|
92
|
+
message: '¿Instalar dependencias ahora? (npm install)',
|
|
93
|
+
default: true
|
|
94
|
+
}
|
|
95
|
+
]);
|
|
96
|
+
if (installAnswer.installDeps) {
|
|
97
|
+
console.log(chalk.yellow('\n📦 Instalando dependencias (esto puede tomar unos minutos)...\n'));
|
|
98
|
+
try {
|
|
99
|
+
const { execSync } = await import('child_process');
|
|
100
|
+
// Verificar y limpiar caché de npm si hay problemas de permisos
|
|
101
|
+
try {
|
|
102
|
+
console.log(chalk.gray('Verificando caché de npm...\n'));
|
|
103
|
+
execSync('npm cache verify', {
|
|
104
|
+
stdio: 'pipe',
|
|
105
|
+
encoding: 'utf-8'
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
catch (cacheError) {
|
|
109
|
+
// Si hay error de permisos en caché, intentar limpiar
|
|
110
|
+
if (cacheError.message.includes('EACCES')) {
|
|
111
|
+
console.log(chalk.yellow('⚠️ Detectado problema de permisos en caché de npm\n'));
|
|
112
|
+
console.log(chalk.gray('Intentando limpiar caché...\n'));
|
|
113
|
+
try {
|
|
114
|
+
// Intentar limpiar caché sin sudo primero
|
|
115
|
+
execSync('npm cache clean --force', {
|
|
116
|
+
stdio: 'pipe',
|
|
117
|
+
encoding: 'utf-8'
|
|
118
|
+
});
|
|
119
|
+
console.log(chalk.green('✅ Caché limpiado\n'));
|
|
120
|
+
}
|
|
121
|
+
catch (cleanError) {
|
|
122
|
+
console.log(chalk.yellow('⚠️ No se pudo limpiar caché automáticamente\n'));
|
|
123
|
+
console.log(chalk.white('Ejecuta manualmente:\n'));
|
|
124
|
+
console.log(chalk.cyan(' sudo chown -R $(whoami) ~/.npm\n'));
|
|
125
|
+
console.log(chalk.cyan(' npm cache clean --force\n'));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// Ejecutar npm install de forma síncrona para esperar a que termine
|
|
130
|
+
execSync('sudo npm install', {
|
|
131
|
+
cwd: answers.projectPath,
|
|
132
|
+
stdio: 'inherit', // Muestra el output en tiempo real
|
|
133
|
+
encoding: 'utf-8'
|
|
134
|
+
});
|
|
135
|
+
console.log(chalk.green('\n✅ Dependencias instaladas correctamente\n'));
|
|
136
|
+
// Preguntar si quiere linkear apprecio-mcp-base
|
|
137
|
+
const linkAnswer = await inquirer.prompt([
|
|
138
|
+
{
|
|
139
|
+
type: 'confirm',
|
|
140
|
+
name: 'linkBase',
|
|
141
|
+
message: '¿Linkear apprecio-mcp-base local? (npm link apprecio-mcp-base)',
|
|
142
|
+
default: true
|
|
143
|
+
}
|
|
144
|
+
]);
|
|
145
|
+
if (linkAnswer.linkBase) {
|
|
146
|
+
console.log(chalk.yellow('\n🔗 Linkeando apprecio-mcp-base...\n'));
|
|
147
|
+
try {
|
|
148
|
+
execSync('npm link apprecio-mcp-base', {
|
|
149
|
+
cwd: answers.projectPath,
|
|
150
|
+
stdio: 'inherit',
|
|
151
|
+
encoding: 'utf-8'
|
|
152
|
+
});
|
|
153
|
+
console.log(chalk.green('\n✅ apprecio-mcp-base linkeado correctamente\n'));
|
|
154
|
+
}
|
|
155
|
+
catch (linkError) {
|
|
156
|
+
console.log(chalk.yellow('\n⚠️ Error linkeando apprecio-mcp-base'));
|
|
157
|
+
console.log(chalk.white(' Asegúrate de haber ejecutado en apprecio-mcp-base:'));
|
|
158
|
+
console.log(chalk.cyan(' cd apprecio-mcp-base && npm run build && npm link\n'));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
console.log(chalk.red('\n❌ Error instalando dependencias:', error.message));
|
|
164
|
+
// Detectar error de permisos de npm
|
|
165
|
+
if (error.message.includes('EACCES') && error.message.includes('.npm')) {
|
|
166
|
+
console.log(chalk.yellow('\n⚠️ Problema de permisos en caché de npm detectado\n'));
|
|
167
|
+
console.log(chalk.white('Solución:\n'));
|
|
168
|
+
console.log(chalk.cyan(' sudo chown -R $(whoami) ~/.npm'));
|
|
169
|
+
console.log(chalk.cyan(' npm cache clean --force'));
|
|
170
|
+
console.log(chalk.cyan(` cd ${answers.projectPath}`));
|
|
171
|
+
console.log(chalk.cyan(' npm install\n'));
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
console.log(chalk.white('\nEjecuta manualmente:'));
|
|
175
|
+
console.log(chalk.cyan(` cd ${answers.projectPath}`));
|
|
176
|
+
console.log(chalk.cyan(' npm install\n'));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
90
180
|
console.log(chalk.blue.bold('📚 Próximos pasos:\n'));
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
181
|
+
if (!installAnswer.installDeps) {
|
|
182
|
+
console.log(chalk.white(`1. cd ${answers.projectPath}`));
|
|
183
|
+
console.log(chalk.white(`2. npm install`));
|
|
184
|
+
console.log(chalk.white(`3. npm link apprecio-mcp-base`));
|
|
185
|
+
console.log(chalk.white(`4. Edita .env con tu configuración`));
|
|
186
|
+
console.log(chalk.white(`5. npm run dev\n`));
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
console.log(chalk.white(`1. cd ${answers.projectPath}`));
|
|
190
|
+
console.log(chalk.white(`2. Edita .env con tu configuración`));
|
|
191
|
+
console.log(chalk.white(`3. npm run dev\n`));
|
|
192
|
+
}
|
|
95
193
|
console.log(chalk.blue.bold('🔧 Comandos útiles:\n'));
|
|
96
194
|
console.log(chalk.white(` npm run generate - Generar nueva feature`));
|
|
97
195
|
console.log(chalk.white(` npm run dev - Ejecutar en desarrollo`));
|
|
@@ -102,52 +200,6 @@ program
|
|
|
102
200
|
process.exit(1);
|
|
103
201
|
}
|
|
104
202
|
});
|
|
105
|
-
program
|
|
106
|
-
.command('tool')
|
|
107
|
-
.description('Agrega un tool a una feature existente')
|
|
108
|
-
.action(async () => {
|
|
109
|
-
console.log(chalk.blue.bold('\n🔧 Agregar Tool a Feature\n'));
|
|
110
|
-
// Listar features existentes
|
|
111
|
-
const featuresPath = path.join(process.cwd(), 'src', 'features');
|
|
112
|
-
const features = await fs.readdir(featuresPath);
|
|
113
|
-
if (features.length === 0) {
|
|
114
|
-
console.log(chalk.red('No hay features disponibles. Crea una primero con: mcp-generate feature'));
|
|
115
|
-
process.exit(1);
|
|
116
|
-
}
|
|
117
|
-
const answers = await inquirer.prompt([
|
|
118
|
-
{
|
|
119
|
-
type: 'list',
|
|
120
|
-
name: 'feature',
|
|
121
|
-
message: 'Selecciona la feature:',
|
|
122
|
-
choices: features
|
|
123
|
-
},
|
|
124
|
-
{
|
|
125
|
-
type: 'input',
|
|
126
|
-
name: 'toolName',
|
|
127
|
-
message: 'Nombre del tool (ej: approve_item):',
|
|
128
|
-
validate: (input) => {
|
|
129
|
-
if (!input)
|
|
130
|
-
return 'El nombre es requerido';
|
|
131
|
-
if (!/^[a-z_]+$/.test(input)) {
|
|
132
|
-
return 'El nombre debe ser snake_case';
|
|
133
|
-
}
|
|
134
|
-
return true;
|
|
135
|
-
}
|
|
136
|
-
},
|
|
137
|
-
{
|
|
138
|
-
type: 'input',
|
|
139
|
-
name: 'toolDescription',
|
|
140
|
-
message: 'Descripción del tool:',
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
type: 'confirm',
|
|
144
|
-
name: 'addToService',
|
|
145
|
-
message: '¿Agregar método al service?',
|
|
146
|
-
default: true
|
|
147
|
-
}
|
|
148
|
-
]);
|
|
149
|
-
console.log(chalk.green(`\n✅ Tool ${answers.toolName} agregado a ${answers.feature}\n`));
|
|
150
|
-
});
|
|
151
203
|
// ==========================================
|
|
152
204
|
// COMANDO: feature
|
|
153
205
|
// ==========================================
|
|
@@ -262,5 +314,51 @@ program
|
|
|
262
314
|
process.exit(1);
|
|
263
315
|
}
|
|
264
316
|
});
|
|
317
|
+
program
|
|
318
|
+
.command('tool')
|
|
319
|
+
.description('Agrega un tool a una feature existente')
|
|
320
|
+
.action(async () => {
|
|
321
|
+
console.log(chalk.blue.bold('\n🔧 Agregar Tool a Feature\n'));
|
|
322
|
+
// Listar features existentes
|
|
323
|
+
const featuresPath = path.join(process.cwd(), 'src', 'features');
|
|
324
|
+
const features = await fs.readdir(featuresPath);
|
|
325
|
+
if (features.length === 0) {
|
|
326
|
+
console.log(chalk.red('No hay features disponibles. Crea una primero con: mcp-generate feature'));
|
|
327
|
+
process.exit(1);
|
|
328
|
+
}
|
|
329
|
+
const answers = await inquirer.prompt([
|
|
330
|
+
{
|
|
331
|
+
type: 'list',
|
|
332
|
+
name: 'feature',
|
|
333
|
+
message: 'Selecciona la feature:',
|
|
334
|
+
choices: features
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
type: 'input',
|
|
338
|
+
name: 'toolName',
|
|
339
|
+
message: 'Nombre del tool (ej: approve_item):',
|
|
340
|
+
validate: (input) => {
|
|
341
|
+
if (!input)
|
|
342
|
+
return 'El nombre es requerido';
|
|
343
|
+
if (!/^[a-z_]+$/.test(input)) {
|
|
344
|
+
return 'El nombre debe ser snake_case';
|
|
345
|
+
}
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
type: 'input',
|
|
351
|
+
name: 'toolDescription',
|
|
352
|
+
message: 'Descripción del tool:',
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
type: 'confirm',
|
|
356
|
+
name: 'addToService',
|
|
357
|
+
message: '¿Agregar método al service?',
|
|
358
|
+
default: true
|
|
359
|
+
}
|
|
360
|
+
]);
|
|
361
|
+
console.log(chalk.green(`\n✅ Tool ${answers.toolName} agregado a ${answers.feature}\n`));
|
|
362
|
+
});
|
|
265
363
|
program.parse();
|
|
266
364
|
//# sourceMappingURL=generate-feature.js.map
|