memento-mcp-server 1.11.0-a1 → 1.11.1-a

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.
Files changed (75) hide show
  1. package/README.md +7 -0
  2. package/dist/server/context.d.ts +36 -0
  3. package/dist/server/context.d.ts.map +1 -0
  4. package/dist/server/context.js +45 -0
  5. package/dist/server/context.js.map +1 -0
  6. package/dist/server/handlers/anchor-map.handler.d.ts +60 -0
  7. package/dist/server/handlers/anchor-map.handler.d.ts.map +1 -0
  8. package/dist/server/handlers/anchor-map.handler.js +190 -0
  9. package/dist/server/handlers/anchor-map.handler.js.map +1 -0
  10. package/dist/server/http-server.d.ts.map +1 -1
  11. package/dist/server/http-server.js +41 -1131
  12. package/dist/server/http-server.js.map +1 -1
  13. package/dist/server/middleware/error-handler.middleware.d.ts +25 -0
  14. package/dist/server/middleware/error-handler.middleware.d.ts.map +1 -0
  15. package/dist/server/middleware/error-handler.middleware.js +97 -0
  16. package/dist/server/middleware/error-handler.middleware.js.map +1 -0
  17. package/dist/server/middleware/index.d.ts +8 -0
  18. package/dist/server/middleware/index.d.ts.map +1 -0
  19. package/dist/server/middleware/index.js +8 -0
  20. package/dist/server/middleware/index.js.map +1 -0
  21. package/dist/server/middleware/service-injector.middleware.d.ts +30 -0
  22. package/dist/server/middleware/service-injector.middleware.d.ts.map +1 -0
  23. package/dist/server/middleware/service-injector.middleware.js +20 -0
  24. package/dist/server/middleware/service-injector.middleware.js.map +1 -0
  25. package/dist/server/middleware/tool-context.middleware.d.ts +29 -0
  26. package/dist/server/middleware/tool-context.middleware.d.ts.map +1 -0
  27. package/dist/server/middleware/tool-context.middleware.js +34 -0
  28. package/dist/server/middleware/tool-context.middleware.js.map +1 -0
  29. package/dist/server/routes/admin.routes.d.ts +12 -0
  30. package/dist/server/routes/admin.routes.d.ts.map +1 -0
  31. package/dist/server/routes/admin.routes.js +338 -0
  32. package/dist/server/routes/admin.routes.js.map +1 -0
  33. package/dist/server/routes/api.routes.d.ts +13 -0
  34. package/dist/server/routes/api.routes.d.ts.map +1 -0
  35. package/dist/server/routes/api.routes.js +122 -0
  36. package/dist/server/routes/api.routes.js.map +1 -0
  37. package/dist/server/routes/mcp.routes.d.ts +22 -0
  38. package/dist/server/routes/mcp.routes.d.ts.map +1 -0
  39. package/dist/server/routes/mcp.routes.js +383 -0
  40. package/dist/server/routes/mcp.routes.js.map +1 -0
  41. package/dist/server/routes/tools.routes.d.ts +13 -0
  42. package/dist/server/routes/tools.routes.d.ts.map +1 -0
  43. package/dist/server/routes/tools.routes.js +99 -0
  44. package/dist/server/routes/tools.routes.js.map +1 -0
  45. package/dist/services/anchor/anchor-cache-service.d.ts +77 -0
  46. package/dist/services/anchor/anchor-cache-service.d.ts.map +1 -0
  47. package/dist/services/anchor/anchor-cache-service.js +193 -0
  48. package/dist/services/anchor/anchor-cache-service.js.map +1 -0
  49. package/dist/services/anchor/anchor-interfaces.d.ts +143 -0
  50. package/dist/services/anchor/anchor-interfaces.d.ts.map +1 -0
  51. package/dist/services/anchor/anchor-interfaces.js +24 -0
  52. package/dist/services/anchor/anchor-interfaces.js.map +1 -0
  53. package/dist/services/anchor/anchor-manager.d.ts +71 -0
  54. package/dist/services/anchor/anchor-manager.d.ts.map +1 -0
  55. package/dist/services/anchor/anchor-manager.js +205 -0
  56. package/dist/services/anchor/anchor-manager.js.map +1 -0
  57. package/dist/services/anchor/anchor-search-service.d.ts +115 -0
  58. package/dist/services/anchor/anchor-search-service.d.ts.map +1 -0
  59. package/dist/services/anchor/anchor-search-service.js +799 -0
  60. package/dist/services/anchor/anchor-search-service.js.map +1 -0
  61. package/dist/services/anchor/index.d.ts +11 -0
  62. package/dist/services/anchor/index.d.ts.map +1 -0
  63. package/dist/services/anchor/index.js +10 -0
  64. package/dist/services/anchor/index.js.map +1 -0
  65. package/dist/services/anchor-manager.d.ts +22 -208
  66. package/dist/services/anchor-manager.d.ts.map +1 -1
  67. package/dist/services/anchor-manager.js +72 -1088
  68. package/dist/services/anchor-manager.js.map +1 -1
  69. package/dist/services/error-logging-service.d.ts +1 -0
  70. package/dist/services/error-logging-service.d.ts.map +1 -1
  71. package/dist/services/error-logging-service.js +2 -0
  72. package/dist/services/error-logging-service.js.map +1 -1
  73. package/dist/tools/forget-tool.js +1 -1
  74. package/dist/tools/forget-tool.js.map +1 -1
  75. package/package.json +3 -1
@@ -24,6 +24,14 @@ import { DatabaseUtils } from '../utils/database.js';
24
24
  import { getToolRegistry } from '../tools/index.js';
25
25
  import Database from 'better-sqlite3';
26
26
  import packageJson from '../../package.json' with { type: 'json' };
27
+ // Phase 1.2: 라우터 import
28
+ import { createToolsRouter } from './routes/tools.routes.js';
29
+ import { createAdminRouter } from './routes/admin.routes.js';
30
+ import { createApiRouter } from './routes/api.routes.js';
31
+ import { createMcpRouter } from './routes/mcp.routes.js';
32
+ import { broadcastAnchorMapUpdate } from './handlers/anchor-map.handler.js';
33
+ // Phase 0: 공통 미들웨어 import
34
+ import { createServiceInjector, createToolContextMiddleware, errorHandler } from './middleware/index.js';
27
35
  // 전역 변수
28
36
  let db = null;
29
37
  let searchEngine;
@@ -39,6 +47,9 @@ let consolidationScoreService = null;
39
47
  let writeCoalescingManager = null;
40
48
  // 부트스트랩에서 반환된 전체 서비스 객체 (ToolContext 생성 시 사용)
41
49
  let serverServices = null;
50
+ // Phase 1.2: 라우터에서 사용할 전역 변수들
51
+ // SSE Transport 저장소 (MCP 라우터용)
52
+ const transports = {};
42
53
  function setTestDependencies({ database, searchEngine: search, hybridSearchEngine: hybrid, embeddingService: embedding }) {
43
54
  db = database;
44
55
  searchEngine = search ?? new SearchEngine();
@@ -68,240 +79,17 @@ app.get('/health', (req, res) => {
68
79
  timestamp: new Date().toISOString()
69
80
  });
70
81
  });
71
- app.get('/tools', (req, res) => {
72
- try {
73
- const toolRegistry = getToolRegistry();
74
- const tools = toolRegistry.getAll();
75
- res.json({
76
- tools,
77
- count: tools.length,
78
- server: mementoConfig.serverName
79
- });
80
- }
81
- catch (error) {
82
- console.error('❌ 도구 목록 조회 실패:', error);
83
- res.status(500).json({
84
- error: 'Failed to get tools',
85
- message: error instanceof Error ? error.message : 'Unknown error'
86
- });
87
- }
88
- });
89
- /**
90
- * Anchor Map 업데이트를 WebSocket 구독자에게 브로드캐스트
91
- */
92
- async function broadcastAnchorMapUpdate(agentId) {
93
- if (!anchorMapSubscribers.has(agentId) || anchorMapSubscribers.get(agentId).size === 0) {
94
- return; // 구독자가 없으면 브로드캐스트하지 않음
95
- }
96
- try {
97
- // Anchor Map 데이터 생성 (API 엔드포인트와 동일한 로직)
98
- if (!db || !serverServices || !serverServices.anchorManager) {
99
- return;
100
- }
101
- const anchorManager = serverServices.anchorManager;
102
- const anchors = await anchorManager.getAnchor(agentId);
103
- if (!anchors || (Array.isArray(anchors) && anchors.length === 0)) {
104
- // 앵커가 없어도 빈 데이터로 브로드캐스트
105
- const updateData = {
106
- agent_id: agentId,
107
- anchors: [],
108
- nodes: [],
109
- links: [],
110
- timestamp: new Date().toISOString()
111
- };
112
- const subscribers = anchorMapSubscribers.get(agentId);
113
- for (const ws of subscribers) {
114
- if (ws.readyState === 1) { // WebSocket.OPEN
115
- ws.send(JSON.stringify({
116
- type: 'anchor_map_update',
117
- data: updateData
118
- }));
119
- }
120
- }
121
- return;
122
- }
123
- const anchorList = Array.isArray(anchors) ? anchors : [anchors];
124
- // 각 앵커 주변의 관련 메모리 검색 및 네트워크 데이터 구성
125
- const nodes = [];
126
- const links = [];
127
- const processedMemoryIds = new Set();
128
- // 각 앵커에 대해 처리
129
- for (const anchor of anchorList) {
130
- if (!anchor.memory_id)
131
- continue;
132
- const anchorMemory = db.prepare(`
133
- SELECT id, content, type, importance, created_at
134
- FROM memory_item
135
- WHERE id = ?
136
- `).get(anchor.memory_id);
137
- if (anchorMemory) {
138
- nodes.push({
139
- id: anchor.memory_id,
140
- type: 'anchor',
141
- slot: anchor.slot,
142
- content: anchorMemory.content.substring(0, 100),
143
- importance: anchorMemory.importance,
144
- created_at: anchorMemory.created_at
145
- });
146
- processedMemoryIds.add(anchor.memory_id);
147
- try {
148
- const slotConfig = anchorManager.getSlotConfig(anchor.slot);
149
- const searchResult = await anchorManager.searchLocal(agentId, anchor.slot, undefined, slotConfig.hop_limit, { limit: 50 });
150
- for (const item of searchResult.items) {
151
- if (processedMemoryIds.has(item.id))
152
- continue;
153
- nodes.push({
154
- id: item.id,
155
- type: 'memory',
156
- content: item.content.substring(0, 100),
157
- hop_distance: item.hop_distance || 0,
158
- similarity: item.similarity,
159
- importance: item.importance,
160
- created_at: item.created_at
161
- });
162
- processedMemoryIds.add(item.id);
163
- if (item.hop_distance === 1) {
164
- links.push({
165
- source: anchor.memory_id,
166
- target: item.id,
167
- type: 'hop',
168
- hop_distance: 1,
169
- similarity: item.similarity
170
- });
171
- }
172
- }
173
- }
174
- catch (error) {
175
- console.error(`❌ 앵커 ${anchor.slot} 주변 메모리 검색 실패:`, error);
176
- }
177
- }
178
- }
179
- // memory_link 테이블 활용
180
- for (const node of nodes) {
181
- if (node.type === 'anchor')
182
- continue;
183
- const linkedMemories = db.prepare(`
184
- SELECT target_memory_id, similarity, created_at
185
- FROM memory_link
186
- WHERE source_memory_id = ?
187
- UNION
188
- SELECT source_memory_id, similarity, created_at
189
- FROM memory_link
190
- WHERE target_memory_id = ?
191
- `).all(node.id, node.id);
192
- for (const link of linkedMemories) {
193
- const linkedId = link.target_memory_id || link.source_memory_id;
194
- if (linkedId && processedMemoryIds.has(linkedId)) {
195
- links.push({
196
- source: node.id,
197
- target: linkedId,
198
- type: 'link',
199
- similarity: link.similarity
200
- });
201
- }
202
- }
203
- }
204
- const updateData = {
205
- agent_id: agentId,
206
- anchors: anchorList,
207
- nodes,
208
- links,
209
- timestamp: new Date().toISOString()
210
- };
211
- // 구독자에게 브로드캐스트
212
- const subscribers = anchorMapSubscribers.get(agentId);
213
- for (const ws of subscribers) {
214
- if (ws.readyState === 1) { // WebSocket.OPEN
215
- try {
216
- ws.send(JSON.stringify({
217
- type: 'anchor_map_update',
218
- data: updateData
219
- }));
220
- }
221
- catch (error) {
222
- console.error('❌ WebSocket 브로드캐스트 실패:', error);
223
- }
224
- }
225
- }
226
- console.log(`📡 Anchor Map 업데이트 브로드캐스트: agent_id=${agentId}, subscribers=${subscribers.size}`);
227
- }
228
- catch (error) {
229
- console.error(`❌ Anchor Map 브로드캐스트 실패 (agent: ${agentId}):`, error);
230
- }
231
- }
232
- // 도구 실행 엔드포인트
233
- app.post('/tools/:name', async (req, res) => {
234
- const { name } = req.params;
235
- const params = req.body;
236
- try {
237
- const toolRegistry = getToolRegistry();
238
- // 부트스트랩에서 초기화된 서비스 객체를 사용하여 ToolContext 생성
239
- if (!serverServices) {
240
- return res.status(500).json({
241
- error: 'Services not initialized',
242
- message: '서비스가 초기화되지 않았습니다'
243
- });
244
- }
245
- const context = {
246
- db,
247
- services: {
248
- searchEngine: serverServices.searchEngine,
249
- hybridSearchEngine: serverServices.hybridSearchEngine,
250
- embeddingService: serverServices.embeddingService,
251
- forgettingPolicyService: serverServices.forgettingPolicyService,
252
- performanceMonitor: serverServices.performanceMonitor,
253
- databaseOptimizer: serverServices.databaseOptimizer,
254
- errorLoggingService: serverServices.errorLoggingService,
255
- performanceAlertService: serverServices.performanceAlertService,
256
- consolidationScoreService: serverServices.consolidationScoreService,
257
- writeCoalescingManager: serverServices.writeCoalescingManager,
258
- anchorManager: serverServices.anchorManager
259
- }
260
- };
261
- // 도구 실행
262
- const toolResult = await toolRegistry.execute(name, params, context);
263
- // MCP 형식의 ToolResult에서 실제 데이터 추출
264
- // content 배열의 첫 번째 항목의 text를 JSON 파싱
265
- let actualResult = toolResult;
266
- if (toolResult.content && Array.isArray(toolResult.content) && toolResult.content.length > 0) {
267
- const firstContent = toolResult.content[0];
268
- if (firstContent && firstContent.text) {
269
- try {
270
- const textContent = firstContent.text;
271
- actualResult = JSON.parse(textContent);
272
- }
273
- catch (parseError) {
274
- // JSON 파싱 실패 시 원본 content 사용
275
- actualResult = toolResult;
276
- }
277
- }
278
- }
279
- // 앵커 관련 도구 실행 후 WebSocket 브로드캐스트
280
- if (name === 'set_anchor' || name === 'clear_anchor') {
281
- const agentId = params.agent_id || 'default';
282
- // 비동기로 브로드캐스트 (응답 지연 방지)
283
- setImmediate(() => {
284
- broadcastAnchorMapUpdate(agentId).catch(error => {
285
- console.error('❌ Anchor Map 브로드캐스트 실패:', error);
286
- });
287
- });
288
- }
289
- return res.json({
290
- result: actualResult,
291
- tool: name,
292
- timestamp: new Date().toISOString()
293
- });
294
- }
295
- catch (error) {
296
- console.error(`❌ Tool ${name} 실행 실패:`, error);
297
- return res.status(500).json({
298
- error: 'Tool execution failed',
299
- tool: name,
300
- message: error instanceof Error ? error.message : 'Unknown error'
301
- });
302
- }
303
- });
304
- // 대시보드 라우트
82
+ // Phase 1.2: 라우터 등록
83
+ // WebSocket 클라이언트 관리 (Anchor Map 업데이트용) - 라우터에서도 사용
84
+ const anchorMapSubscribers = new Map(); // agent_id -> WebSocket Set
85
+ // 라우터 등록 (서비스 초기화 후 업데이트됨)
86
+ let toolsRouter = null;
87
+ let adminRouter = null;
88
+ let apiRouter = null;
89
+ let mcpRouter = null;
90
+ // Phase 1.2: 기존 엔드포인트는 모두 라우터로 이동됨
91
+ // 주석 처리된 기존 코드는 제거됨 (tools.routes.ts, admin.routes.ts, api.routes.ts, mcp.routes.ts로 이동)
92
+ // 대시보드 라우트 (정적 파일 서빙)
305
93
  app.get('/dashboard', (req, res) => {
306
94
  res.sendFile('dashboard.html', { root: 'static' }, (err) => {
307
95
  if (err) {
@@ -310,901 +98,8 @@ app.get('/dashboard', (req, res) => {
310
98
  }
311
99
  });
312
100
  });
313
- // Anchor Map API 엔드포인트
314
- app.get('/api/anchors/map', async (req, res) => {
315
- const agentId = req.query.agent_id || 'default';
316
- try {
317
- // 데이터베이스 연결 확인
318
- if (!db || !serverServices) {
319
- return res.status(500).json({
320
- error: 'Services not initialized',
321
- message: '서비스가 초기화되지 않았습니다'
322
- });
323
- }
324
- const anchorManager = serverServices.anchorManager;
325
- if (!anchorManager) {
326
- return res.status(500).json({
327
- error: 'AnchorManager not available',
328
- message: 'AnchorManager가 사용할 수 없습니다'
329
- });
330
- }
331
- // 앵커 정보 조회
332
- const anchors = await anchorManager.getAnchor(agentId);
333
- if (!anchors || (Array.isArray(anchors) && anchors.length === 0)) {
334
- return res.json({
335
- agent_id: agentId,
336
- anchors: [],
337
- nodes: [],
338
- links: [],
339
- timestamp: new Date().toISOString()
340
- });
341
- }
342
- const anchorList = Array.isArray(anchors) ? anchors : [anchors];
343
- // 각 앵커 주변의 관련 메모리 검색 및 네트워크 데이터 구성
344
- const nodes = [];
345
- const links = [];
346
- const processedMemoryIds = new Set();
347
- // 각 앵커에 대해 처리
348
- for (const anchor of anchorList) {
349
- if (!anchor.memory_id)
350
- continue;
351
- // 앵커 노드 추가
352
- const anchorMemory = db.prepare(`
353
- SELECT id, content, type, importance, created_at
354
- FROM memory_item
355
- WHERE id = ?
356
- `).get(anchor.memory_id);
357
- if (anchorMemory) {
358
- nodes.push({
359
- id: anchor.memory_id,
360
- type: 'anchor',
361
- slot: anchor.slot,
362
- content: anchorMemory.content.substring(0, 100), // 처음 100자만
363
- importance: anchorMemory.importance,
364
- created_at: anchorMemory.created_at
365
- });
366
- processedMemoryIds.add(anchor.memory_id);
367
- // 앵커 주변 메모리 검색 (hop 거리별)
368
- try {
369
- const slotConfig = anchorManager.getSlotConfig(anchor.slot);
370
- const searchResult = await anchorManager.searchLocal(agentId, anchor.slot, undefined, // query 없이 앵커 기반 recall
371
- slotConfig.hop_limit, { limit: 50 } // 충분한 수의 메모리 가져오기
372
- );
373
- // 검색 결과를 노드와 링크로 변환
374
- for (const item of searchResult.items) {
375
- if (processedMemoryIds.has(item.id))
376
- continue;
377
- nodes.push({
378
- id: item.id,
379
- type: 'memory',
380
- content: item.content.substring(0, 100),
381
- hop_distance: item.hop_distance || 0,
382
- similarity: item.similarity,
383
- importance: item.importance,
384
- created_at: item.created_at
385
- });
386
- processedMemoryIds.add(item.id);
387
- // 링크 추가 (앵커에서 메모리로)
388
- if (item.hop_distance === 1) {
389
- links.push({
390
- source: anchor.memory_id,
391
- target: item.id,
392
- type: 'hop',
393
- hop_distance: 1,
394
- similarity: item.similarity
395
- });
396
- }
397
- }
398
- }
399
- catch (error) {
400
- console.error(`❌ 앵커 ${anchor.slot} 주변 메모리 검색 실패:`, error);
401
- }
402
- }
403
- }
404
- // memory_link 테이블을 활용한 직접 연결 정보 추가
405
- for (const node of nodes) {
406
- if (node.type === 'anchor')
407
- continue;
408
- // memory_link 테이블 스키마: source_id, target_id, relation_type, created_at
409
- const linkedMemories = db.prepare(`
410
- SELECT target_id, relation_type, created_at
411
- FROM memory_link
412
- WHERE source_id = ?
413
- UNION
414
- SELECT source_id, relation_type, created_at
415
- FROM memory_link
416
- WHERE target_id = ?
417
- `).all(node.id, node.id);
418
- for (const link of linkedMemories) {
419
- const linkedId = link.target_id || link.source_id;
420
- if (linkedId && processedMemoryIds.has(linkedId)) {
421
- // 이미 노드에 있는 메모리와의 직접 연결
422
- // relation_type을 기반으로 similarity 추정 (기본값 0.7)
423
- const similarity = link.relation_type === 'derived_from' ? 0.9 :
424
- link.relation_type === 'cause_of' ? 0.8 : 0.7;
425
- links.push({
426
- source: node.id,
427
- target: linkedId,
428
- type: 'link',
429
- similarity: similarity
430
- });
431
- }
432
- }
433
- }
434
- return res.json({
435
- agent_id: agentId,
436
- anchors: anchorList,
437
- nodes,
438
- links,
439
- timestamp: new Date().toISOString()
440
- });
441
- }
442
- catch (error) {
443
- console.error(`❌ Anchor Map 데이터 조회 실패 (agent: ${agentId}):`, error);
444
- return res.status(500).json({
445
- error: 'Failed to get anchor map data',
446
- message: error instanceof Error ? error.message : 'Unknown error',
447
- agent_id: agentId
448
- });
449
- }
450
- });
451
- // 이웃 기억 조회 엔드포인트
452
- app.get('/memories/:id/neighbors', async (req, res) => {
453
- const { id } = req.params;
454
- const limit = req.query.limit ? parseInt(req.query.limit, 10) : 5;
455
- const similarityThreshold = req.query.similarity_threshold
456
- ? parseFloat(req.query.similarity_threshold)
457
- : 0.8;
458
- try {
459
- // 데이터베이스 연결 확인
460
- if (!db) {
461
- return res.status(500).json({
462
- error: 'Database not connected',
463
- message: '데이터베이스가 연결되지 않았습니다'
464
- });
465
- }
466
- // 파라미터 검증
467
- if (isNaN(limit) || limit < 1 || limit > 50) {
468
- return res.status(400).json({
469
- error: 'Invalid limit parameter',
470
- message: 'limit은 1 이상 50 이하여야 합니다'
471
- });
472
- }
473
- if (isNaN(similarityThreshold) || similarityThreshold < 0 || similarityThreshold > 1) {
474
- return res.status(400).json({
475
- error: 'Invalid similarity_threshold parameter',
476
- message: 'similarity_threshold는 0 이상 1 이하여야 합니다'
477
- });
478
- }
479
- // MemoryNeighborService 인스턴스 생성
480
- const vectorSearchEngine = getVectorSearchEngine();
481
- const neighborService = new MemoryNeighborService(vectorSearchEngine, embeddingService);
482
- // 데이터베이스 설정
483
- neighborService.setDatabase(db);
484
- // 이웃 기억 조회
485
- const result = await neighborService.getNeighbors(id, {
486
- limit,
487
- similarity_threshold: similarityThreshold
488
- });
489
- return res.json({
490
- memory_id: result.memory_id,
491
- neighbors: result.neighbors,
492
- total_count: result.total_count,
493
- query_time: result.query_time,
494
- timestamp: new Date().toISOString()
495
- });
496
- }
497
- catch (error) {
498
- // MemoryNotFoundError 처리 (404)
499
- if (error instanceof MemoryNotFoundError) {
500
- return res.status(404).json({
501
- error: 'Memory not found',
502
- message: `메모리를 찾을 수 없습니다: ${id}`,
503
- memory_id: id
504
- });
505
- }
506
- // 기타 에러 처리 (500)
507
- console.error(`❌ 이웃 기억 조회 실패 (${id}):`, error);
508
- return res.status(500).json({
509
- error: 'Failed to get memory neighbors',
510
- message: error instanceof Error ? error.message : 'Unknown error',
511
- memory_id: id
512
- });
513
- }
514
- });
515
- // MCP SSE 엔드포인트 - MCP SDK 호환 구현
516
- // Store transports by session ID
517
- const transports = {};
518
- // SSE endpoint for establishing the stream
519
- app.get('/mcp', async (req, res) => {
520
- console.log('🔗 MCP SSE 클라이언트 연결 요청');
521
- try {
522
- // SSE 헤더 설정
523
- res.writeHead(200, {
524
- 'Content-Type': 'text/event-stream',
525
- 'Cache-Control': 'no-cache, no-transform',
526
- 'Connection': 'keep-alive',
527
- 'Access-Control-Allow-Origin': '*',
528
- 'Access-Control-Allow-Headers': 'Cache-Control, Content-Type, Authorization',
529
- 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
530
- 'X-Accel-Buffering': 'no' // nginx 버퍼링 비활성화
531
- });
532
- // Generate session ID
533
- const sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
534
- // Send the endpoint event with session ID
535
- const endpointUrl = `/messages?sessionId=${sessionId}`;
536
- res.write(`event: endpoint\ndata: ${endpointUrl}\n\n`);
537
- // MCP 서버 준비 완료 알림 (클라이언트가 initialize를 보내야 함)
538
- res.write(`data: {"type": "ready"}\n\n`);
539
- // Keep-alive ping 전송
540
- const keepAliveInterval = setInterval(() => {
541
- if (res.writableEnded) {
542
- clearInterval(keepAliveInterval);
543
- return;
544
- }
545
- try {
546
- res.write(`data: {"type": "ping"}\n\n`);
547
- }
548
- catch (error) {
549
- clearInterval(keepAliveInterval);
550
- }
551
- }, 30000); // 30초마다 ping
552
- // Store the transport info
553
- transports[sessionId] = {
554
- res: res,
555
- sessionId: sessionId,
556
- keepAliveInterval: keepAliveInterval
557
- };
558
- // 연결 종료 처리
559
- req.on('close', () => {
560
- console.log(`🔌 MCP SSE 클라이언트 연결 정상 종료됨 (session: ${sessionId})`);
561
- clearInterval(keepAliveInterval);
562
- delete transports[sessionId];
563
- });
564
- req.on('error', (error) => {
565
- // ECONNRESET은 정상적인 연결 종료이므로 에러로 처리하지 않음
566
- if (error.code === 'ECONNRESET') {
567
- console.log(`🔌 MCP SSE 클라이언트 연결 정상 종료됨 (session: ${sessionId})`);
568
- }
569
- else {
570
- console.error(`❌ MCP SSE 연결 에러 (session: ${sessionId}):`, error);
571
- }
572
- clearInterval(keepAliveInterval);
573
- delete transports[sessionId];
574
- });
575
- console.log(`✅ MCP SSE 스트림 설정 완료 (session: ${sessionId})`);
576
- return;
577
- }
578
- catch (error) {
579
- console.error('❌ SSE 스트림 설정 실패:', error);
580
- if (!res.headersSent) {
581
- res.status(500).send('Error establishing SSE stream');
582
- }
583
- return;
584
- }
585
- });
586
- // Messages endpoint for receiving client JSON-RPC requests
587
- app.post('/messages', express.json(), async (req, res) => {
588
- console.log('📨 MCP 메시지 수신:', req.body.method);
589
- // Extract session ID from URL query parameter
590
- const sessionId = req.query.sessionId;
591
- if (!sessionId) {
592
- console.error('❌ No session ID provided in request URL');
593
- res.status(400).send('Missing sessionId parameter');
594
- return;
595
- }
596
- const transport = transports[sessionId];
597
- if (!transport) {
598
- console.error(`❌ No active transport found for session ID: ${sessionId}`);
599
- res.status(404).send('Session not found');
600
- return;
601
- }
602
- const message = req.body;
603
- let result;
604
- console.log(`🔍 MCP 메시지 처리 중: ${message.method}`, JSON.stringify(message, null, 2));
605
- try {
606
- if (message.method === 'initialize') {
607
- console.log('🚀 MCP initialize 요청 처리 중...');
608
- result = {
609
- jsonrpc: '2.0',
610
- id: message.id,
611
- result: {
612
- protocolVersion: '2024-11-05',
613
- capabilities: {
614
- tools: {}
615
- },
616
- serverInfo: {
617
- name: 'memento-memory',
618
- version: '0.1.0'
619
- }
620
- }
621
- };
622
- console.log('✅ MCP initialize 응답 생성 완료:', JSON.stringify(result, null, 2));
623
- }
624
- else if (message.method === 'notifications/initialized') {
625
- console.log('🔔 MCP initialized 알림 수신');
626
- result = {
627
- jsonrpc: '2.0',
628
- id: message.id,
629
- result: {}
630
- };
631
- }
632
- else if (message.method === 'tools/list') {
633
- console.log('📋 MCP tools/list 요청 처리 중...');
634
- try {
635
- const toolRegistry = getToolRegistry();
636
- const tools = toolRegistry.getAll();
637
- console.log('🔍 도구 목록 사용, 길이:', tools.length);
638
- result = {
639
- jsonrpc: '2.0',
640
- id: message.id,
641
- result: { tools }
642
- };
643
- console.log('✅ MCP tools/list 응답 생성 완료, tools 개수:', tools.length);
644
- console.log('🔍 응답 크기:', JSON.stringify(result).length, 'bytes');
645
- // SSE 응답 즉시 전송
646
- console.log('📤 SSE 응답 즉시 전송 중...');
647
- if (transport && transport.res && !transport.res.writableEnded) {
648
- const sseData = `data: ${JSON.stringify(result)}\n\n`;
649
- transport.res.write(sseData);
650
- console.log('✅ SSE 응답 즉시 전송 완료, 크기:', sseData.length, 'bytes');
651
- }
652
- else {
653
- console.error('❌ SSE transport가 유효하지 않음');
654
- }
655
- // HTTP 응답 전송
656
- res.json({ status: 'ok' });
657
- return;
658
- }
659
- catch (toolsError) {
660
- console.error('❌ tools/list 처리 중 오류:', toolsError);
661
- const errorResult = {
662
- jsonrpc: '2.0',
663
- id: message.id,
664
- error: {
665
- code: -32603,
666
- message: 'Internal error',
667
- data: toolsError instanceof Error ? toolsError.message : String(toolsError)
668
- }
669
- };
670
- if (transport && transport.res && !transport.res.writableEnded) {
671
- transport.res.write(`data: ${JSON.stringify(errorResult)}\n\n`);
672
- }
673
- res.json({ status: 'error' });
674
- return;
675
- }
676
- }
677
- else if (message.method === 'tools/call') {
678
- const { name, arguments: args } = message.params;
679
- const toolRegistry = getToolRegistry();
680
- // 부트스트랩에서 초기화된 서비스 객체를 사용하여 ToolContext 생성
681
- if (!serverServices) {
682
- result = {
683
- jsonrpc: '2.0',
684
- id: message.id,
685
- error: {
686
- code: -32603,
687
- message: 'Internal error',
688
- data: '서비스가 초기화되지 않았습니다'
689
- }
690
- };
691
- }
692
- else {
693
- const context = {
694
- db,
695
- services: {
696
- searchEngine: serverServices.searchEngine,
697
- hybridSearchEngine: serverServices.hybridSearchEngine,
698
- embeddingService: serverServices.embeddingService,
699
- forgettingPolicyService: serverServices.forgettingPolicyService,
700
- performanceMonitor: serverServices.performanceMonitor,
701
- databaseOptimizer: serverServices.databaseOptimizer,
702
- errorLoggingService: serverServices.errorLoggingService,
703
- performanceAlertService: serverServices.performanceAlertService,
704
- consolidationScoreService: serverServices.consolidationScoreService,
705
- writeCoalescingManager: serverServices.writeCoalescingManager,
706
- anchorManager: serverServices.anchorManager
707
- }
708
- };
709
- // 도구 실행
710
- const toolResult = await toolRegistry.execute(name, args, context);
711
- result = {
712
- jsonrpc: '2.0',
713
- id: message.id,
714
- result: { content: [{ type: 'text', text: JSON.stringify(toolResult) }] }
715
- };
716
- }
717
- }
718
- else if (message.method === 'prompts/list') {
719
- console.log('📋 MCP prompts/list 요청 처리 중...');
720
- const prompts = [
721
- {
722
- name: 'memory_injection',
723
- description: '관련 기억을 요약하여 프롬프트에 주입',
724
- arguments: [
725
- {
726
- name: 'query',
727
- description: '검색할 쿼리',
728
- required: true
729
- },
730
- {
731
- name: 'token_budget',
732
- description: '토큰 예산 (기본값: 1000)',
733
- required: false
734
- },
735
- {
736
- name: 'max_memories',
737
- description: '최대 기억 개수 (기본값: 5)',
738
- required: false
739
- }
740
- ]
741
- }
742
- ];
743
- result = {
744
- jsonrpc: '2.0',
745
- id: message.id,
746
- result: { prompts }
747
- };
748
- console.log('✅ MCP prompts/list 응답 생성 완료');
749
- }
750
- else if (message.method === 'prompts/get') {
751
- const { name } = message.params;
752
- if (name === 'memory_injection') {
753
- result = {
754
- jsonrpc: '2.0',
755
- id: message.id,
756
- result: {
757
- description: '관련 기억을 요약하여 프롬프트에 주입',
758
- arguments: [
759
- {
760
- name: 'query',
761
- description: '검색할 쿼리',
762
- required: true
763
- },
764
- {
765
- name: 'token_budget',
766
- description: '토큰 예산 (기본값: 1000)',
767
- required: false
768
- },
769
- {
770
- name: 'max_memories',
771
- description: '최대 기억 개수 (기본값: 5)',
772
- required: false
773
- }
774
- ]
775
- }
776
- };
777
- }
778
- else {
779
- result = {
780
- jsonrpc: '2.0',
781
- id: message.id,
782
- error: {
783
- code: -32601,
784
- message: 'Prompt not found'
785
- }
786
- };
787
- }
788
- }
789
- else if (message.method === 'prompts/call') {
790
- const { name, arguments: args } = message.params;
791
- if (name === 'memory_injection') {
792
- try {
793
- // 부트스트랩에서 초기화된 서비스 객체를 사용하여 ToolContext 생성
794
- if (!serverServices) {
795
- result = {
796
- jsonrpc: '2.0',
797
- id: message.id,
798
- error: {
799
- code: -32603,
800
- message: 'Internal error',
801
- data: '서비스가 초기화되지 않았습니다'
802
- }
803
- };
804
- }
805
- else {
806
- // MemoryInjectionPrompt 도구 사용
807
- const toolRegistry = getToolRegistry();
808
- const context = {
809
- db,
810
- services: {
811
- searchEngine: serverServices.searchEngine,
812
- hybridSearchEngine: serverServices.hybridSearchEngine,
813
- embeddingService: serverServices.embeddingService,
814
- forgettingPolicyService: serverServices.forgettingPolicyService,
815
- performanceMonitor: serverServices.performanceMonitor,
816
- databaseOptimizer: serverServices.databaseOptimizer,
817
- errorLoggingService: serverServices.errorLoggingService,
818
- performanceAlertService: serverServices.performanceAlertService,
819
- consolidationScoreService: serverServices.consolidationScoreService,
820
- writeCoalescingManager: serverServices.writeCoalescingManager
821
- }
822
- };
823
- const promptResult = await toolRegistry.execute('memory_injection', args, context);
824
- result = {
825
- jsonrpc: '2.0',
826
- id: message.id,
827
- result: promptResult
828
- };
829
- }
830
- }
831
- catch (error) {
832
- result = {
833
- jsonrpc: '2.0',
834
- id: message.id,
835
- error: {
836
- code: -32603,
837
- message: 'Prompt execution failed',
838
- data: error instanceof Error ? error.message : 'Unknown error'
839
- }
840
- };
841
- }
842
- }
843
- else {
844
- result = {
845
- jsonrpc: '2.0',
846
- id: message.id,
847
- error: {
848
- code: -32601,
849
- message: 'Prompt not found'
850
- }
851
- };
852
- }
853
- }
854
- else {
855
- result = {
856
- jsonrpc: '2.0',
857
- id: message.id,
858
- error: {
859
- code: -32601,
860
- message: 'Method not found'
861
- }
862
- };
863
- }
864
- // Send response via SSE
865
- console.log('📤 SSE 응답 전송 중:', JSON.stringify(result).substring(0, 200) + '...');
866
- try {
867
- // transport 객체 유효성 확인
868
- if (!transport || !transport.res || transport.res.writableEnded) {
869
- console.error('❌ SSE transport가 유효하지 않음');
870
- res.status(500).json({ error: 'SSE transport invalid' });
871
- return;
872
- }
873
- // SSE 응답 전송
874
- const sseData = `data: ${JSON.stringify(result)}\n\n`;
875
- transport.res.write(sseData);
876
- console.log('✅ SSE 응답 전송 완료, 크기:', sseData.length, 'bytes');
877
- }
878
- catch (sseError) {
879
- console.error('❌ SSE 응답 전송 실패:', sseError);
880
- // SSE 전송 실패 시에도 HTTP 응답은 정상 처리
881
- }
882
- // Send HTTP response
883
- res.json({ status: 'ok' });
884
- }
885
- catch (error) {
886
- console.error('❌ MCP 메시지 처리 실패:', error);
887
- const errorResponse = {
888
- jsonrpc: '2.0',
889
- id: message?.id || null,
890
- error: {
891
- code: -32603,
892
- message: 'Internal error',
893
- data: error instanceof Error ? error.message : 'Unknown error'
894
- }
895
- };
896
- // Send error via SSE
897
- try {
898
- if (transport && transport.res && !transport.res.writableEnded) {
899
- const errorSseData = `data: ${JSON.stringify(errorResponse)}\n\n`;
900
- transport.res.write(errorSseData);
901
- console.log('✅ SSE 에러 응답 전송 완료');
902
- }
903
- else {
904
- console.error('❌ SSE transport가 유효하지 않아 에러 응답 전송 실패');
905
- }
906
- }
907
- catch (errorSseError) {
908
- console.error('❌ SSE 에러 응답 전송 실패:', errorSseError);
909
- }
910
- // Send HTTP response
911
- res.json({ status: 'error' });
912
- }
913
- });
914
- // 관리자 API 엔드포인트들
915
- app.post('/admin/memory/cleanup', async (req, res) => {
916
- try {
917
- // 메모리 정리 로직 (기존 CleanupMemoryTool 로직)
918
- if (!db) {
919
- return res.status(500).json({ error: '데이터베이스가 연결되지 않았습니다' });
920
- }
921
- // 간단한 메모리 정리 구현
922
- const result = await db.prepare(`
923
- DELETE FROM memory_item
924
- WHERE pinned = FALSE
925
- AND type = 'working'
926
- AND created_at < datetime('now', '-2 days')
927
- `).run();
928
- return res.json({
929
- message: '메모리 정리 완료',
930
- deleted_count: result.changes,
931
- timestamp: new Date().toISOString()
932
- });
933
- }
934
- catch (error) {
935
- console.error('❌ 메모리 정리 실패:', error);
936
- return res.status(500).json({
937
- error: '메모리 정리 실패',
938
- message: error instanceof Error ? error.message : 'Unknown error'
939
- });
940
- }
941
- });
942
- app.get('/admin/stats/forgetting', async (req, res) => {
943
- try {
944
- // 망각 통계 로직 (기존 ForgettingStatsTool 로직)
945
- if (!db) {
946
- return res.status(500).json({ error: '데이터베이스가 연결되지 않았습니다' });
947
- }
948
- const stats = await db.prepare(`
949
- SELECT
950
- type,
951
- COUNT(*) as total_count,
952
- COUNT(CASE WHEN pinned = TRUE THEN 1 END) as pinned_count,
953
- COUNT(CASE WHEN created_at < datetime('now', '-30 days') THEN 1 END) as old_count
954
- FROM memory_item
955
- GROUP BY type
956
- `).all();
957
- return res.json({
958
- message: '망각 통계 조회 완료',
959
- stats,
960
- timestamp: new Date().toISOString()
961
- });
962
- }
963
- catch (error) {
964
- console.error('❌ 망각 통계 조회 실패:', error);
965
- return res.status(500).json({
966
- error: '망각 통계 조회 실패',
967
- message: error instanceof Error ? error.message : 'Unknown error'
968
- });
969
- }
970
- });
971
- app.get('/admin/stats/performance', async (req, res) => {
972
- try {
973
- // 성능 통계 로직 (기존 PerformanceStatsTool 로직)
974
- if (!db) {
975
- return res.status(500).json({ error: '데이터베이스가 연결되지 않았습니다' });
976
- }
977
- const stats = await db.prepare(`
978
- SELECT
979
- COUNT(*) as total_memories,
980
- COUNT(CASE WHEN type = 'working' THEN 1 END) as working_memories,
981
- COUNT(CASE WHEN type = 'episodic' THEN 1 END) as episodic_memories,
982
- COUNT(CASE WHEN type = 'semantic' THEN 1 END) as semantic_memories,
983
- COUNT(CASE WHEN type = 'procedural' THEN 1 END) as procedural_memories,
984
- COUNT(CASE WHEN pinned = TRUE THEN 1 END) as pinned_memories
985
- FROM memory_item
986
- `).get();
987
- return res.json({
988
- message: '성능 통계 조회 완료',
989
- stats,
990
- timestamp: new Date().toISOString()
991
- });
992
- }
993
- catch (error) {
994
- console.error('❌ 성능 통계 조회 실패:', error);
995
- return res.status(500).json({
996
- error: '성능 통계 조회 실패',
997
- message: error instanceof Error ? error.message : 'Unknown error'
998
- });
999
- }
1000
- });
1001
- app.post('/admin/database/optimize', async (req, res) => {
1002
- try {
1003
- // 데이터베이스 최적화 로직 (기존 DatabaseOptimizeTool 로직)
1004
- if (!db) {
1005
- return res.status(500).json({ error: '데이터베이스가 연결되지 않았습니다' });
1006
- }
1007
- // VACUUM 실행
1008
- await db.prepare('VACUUM').run();
1009
- // ANALYZE 실행
1010
- await db.prepare('ANALYZE').run();
1011
- return res.json({
1012
- message: '데이터베이스 최적화 완료',
1013
- timestamp: new Date().toISOString()
1014
- });
1015
- }
1016
- catch (error) {
1017
- console.error('❌ 데이터베이스 최적화 실패:', error);
1018
- return res.status(500).json({
1019
- error: '데이터베이스 최적화 실패',
1020
- message: error instanceof Error ? error.message : 'Unknown error'
1021
- });
1022
- }
1023
- });
1024
- app.get('/admin/stats/errors', async (req, res) => {
1025
- try {
1026
- // 에러 통계 로직 (기존 errorStatsTool 로직)
1027
- res.json({
1028
- message: '에러 통계 조회 완료',
1029
- stats: {
1030
- total_errors: 0,
1031
- recent_errors: [],
1032
- error_types: {}
1033
- },
1034
- timestamp: new Date().toISOString()
1035
- });
1036
- }
1037
- catch (error) {
1038
- console.error('❌ 에러 통계 조회 실패:', error);
1039
- res.status(500).json({
1040
- error: '에러 통계 조회 실패',
1041
- message: error instanceof Error ? error.message : 'Unknown error'
1042
- });
1043
- }
1044
- });
1045
- app.post('/admin/errors/resolve', async (req, res) => {
1046
- try {
1047
- const { errorId, resolvedBy, reason } = req.body;
1048
- // 에러 해결 로직 (기존 resolveErrorTool 로직)
1049
- res.json({
1050
- message: '에러 해결 완료',
1051
- errorId,
1052
- resolvedBy,
1053
- reason,
1054
- timestamp: new Date().toISOString()
1055
- });
1056
- }
1057
- catch (error) {
1058
- console.error('❌ 에러 해결 실패:', error);
1059
- res.status(500).json({
1060
- error: '에러 해결 실패',
1061
- message: error instanceof Error ? error.message : 'Unknown error'
1062
- });
1063
- }
1064
- });
1065
- app.get('/admin/alerts/performance', async (req, res) => {
1066
- try {
1067
- // 성능 알림 로직 (기존 performanceAlertsTool 로직)
1068
- res.json({
1069
- message: '성능 알림 조회 완료',
1070
- alerts: [],
1071
- timestamp: new Date().toISOString()
1072
- });
1073
- }
1074
- catch (error) {
1075
- console.error('❌ 성능 알림 조회 실패:', error);
1076
- res.status(500).json({
1077
- error: '성능 알림 조회 실패',
1078
- message: error instanceof Error ? error.message : 'Unknown error'
1079
- });
1080
- }
1081
- });
1082
- // 배치 스케줄러 관리 API
1083
- app.get('/admin/batch/status', async (req, res) => {
1084
- try {
1085
- const batchScheduler = getBatchScheduler();
1086
- const status = batchScheduler.getStatus();
1087
- res.json({
1088
- message: '배치 스케줄러 상태 조회 완료',
1089
- status,
1090
- timestamp: new Date().toISOString()
1091
- });
1092
- }
1093
- catch (error) {
1094
- console.error('❌ 배치 스케줄러 상태 조회 실패:', error);
1095
- res.status(500).json({
1096
- error: '배치 스케줄러 상태 조회 실패',
1097
- message: error instanceof Error ? error.message : 'Unknown error'
1098
- });
1099
- }
1100
- });
1101
- app.post('/admin/batch/run', async (req, res) => {
1102
- try {
1103
- const { jobType } = req.body;
1104
- if (!jobType || !['cleanup', 'monitoring'].includes(jobType)) {
1105
- return res.status(400).json({
1106
- error: 'Invalid job type. Must be "cleanup" or "monitoring"'
1107
- });
1108
- }
1109
- const batchScheduler = getBatchScheduler();
1110
- const result = await batchScheduler.runJob(jobType);
1111
- return res.json({
1112
- message: `배치 작업 ${jobType} 실행 완료`,
1113
- result,
1114
- timestamp: new Date().toISOString()
1115
- });
1116
- }
1117
- catch (error) {
1118
- console.error('❌ 배치 작업 실행 실패:', error);
1119
- return res.status(500).json({
1120
- error: '배치 작업 실행 실패',
1121
- message: error instanceof Error ? error.message : 'Unknown error'
1122
- });
1123
- }
1124
- });
1125
- // 성능 모니터링 API
1126
- app.get('/admin/performance/metrics', async (req, res) => {
1127
- try {
1128
- const monitor = getPerformanceMonitor();
1129
- const metrics = await monitor.collectMetrics();
1130
- res.json({
1131
- message: '성능 지표 수집 완료',
1132
- metrics,
1133
- timestamp: new Date().toISOString()
1134
- });
1135
- }
1136
- catch (error) {
1137
- console.error('❌ 성능 지표 수집 실패:', error);
1138
- res.status(500).json({
1139
- error: '성능 지표 수집 실패',
1140
- message: error instanceof Error ? error.message : 'Unknown error'
1141
- });
1142
- }
1143
- });
1144
- app.get('/admin/performance/alerts', async (req, res) => {
1145
- try {
1146
- const monitor = getPerformanceMonitor();
1147
- const alerts = monitor.getActiveAlerts();
1148
- res.json({
1149
- message: '성능 알림 조회 완료',
1150
- alerts,
1151
- count: alerts.length,
1152
- timestamp: new Date().toISOString()
1153
- });
1154
- }
1155
- catch (error) {
1156
- console.error('❌ 성능 알림 조회 실패:', error);
1157
- res.status(500).json({
1158
- error: '성능 알림 조회 실패',
1159
- message: error instanceof Error ? error.message : 'Unknown error'
1160
- });
1161
- }
1162
- });
1163
- app.get('/admin/performance/summary', async (req, res) => {
1164
- try {
1165
- const monitor = getPerformanceMonitor();
1166
- const summary = monitor.getPerformanceSummary();
1167
- res.json({
1168
- message: '성능 요약 조회 완료',
1169
- summary,
1170
- timestamp: new Date().toISOString()
1171
- });
1172
- }
1173
- catch (error) {
1174
- console.error('❌ 성능 요약 조회 실패:', error);
1175
- res.status(500).json({
1176
- error: '성능 요약 조회 실패',
1177
- message: error instanceof Error ? error.message : 'Unknown error'
1178
- });
1179
- }
1180
- });
1181
- app.post('/admin/performance/alerts/:alertId/resolve', async (req, res) => {
1182
- try {
1183
- const { alertId } = req.params;
1184
- const monitor = getPerformanceMonitor();
1185
- const resolved = monitor.resolveAlert(alertId);
1186
- if (resolved) {
1187
- res.json({
1188
- message: '알림 해결 완료',
1189
- alertId,
1190
- timestamp: new Date().toISOString()
1191
- });
1192
- }
1193
- else {
1194
- res.status(404).json({
1195
- error: '알림을 찾을 수 없습니다',
1196
- alertId
1197
- });
1198
- }
1199
- }
1200
- catch (error) {
1201
- console.error('❌ 알림 해결 실패:', error);
1202
- res.status(500).json({
1203
- error: '알림 해결 실패',
1204
- message: error instanceof Error ? error.message : 'Unknown error'
1205
- });
1206
- }
1207
- });
101
+ // Phase 1.2: 기존 엔드포인트는 모두 라우터로 이동됨
102
+ // 주석 처리된 코드는 제거됨 (tools.routes.ts, admin.routes.ts, api.routes.ts, mcp.routes.ts로 이동)
1208
103
  // 서버 초기화
1209
104
  async function initializeServer() {
1210
105
  try {
@@ -1232,6 +127,22 @@ async function initializeServer() {
1232
127
  // Vector Search Engine 초기화 (HTTP 서버 전용)
1233
128
  vectorSearchEngine = getVectorSearchEngine();
1234
129
  vectorSearchEngine.initialize(db);
130
+ // Phase 0: 공통 미들웨어 적용
131
+ // 서비스 주입 미들웨어 (모든 라우터에 적용)
132
+ app.use(createServiceInjector(serverServices, db));
133
+ // Phase 1.2: 라우터 초기화 및 등록
134
+ toolsRouter = createToolsRouter(db, serverServices, anchorMapSubscribers);
135
+ adminRouter = createAdminRouter(db);
136
+ apiRouter = createApiRouter(db, serverServices);
137
+ mcpRouter = createMcpRouter(db, serverServices, transports);
138
+ // 라우터 등록
139
+ // ToolContext 미들웨어는 /tools 라우터에만 적용 (도구 실행 시 필요)
140
+ app.use('/tools', createToolContextMiddleware, toolsRouter);
141
+ app.use('/admin', adminRouter);
142
+ app.use('/api', apiRouter);
143
+ app.use('/', mcpRouter); // /mcp, /messages는 루트에 등록
144
+ // Phase 0: 공통 에러 핸들러 미들웨어 (모든 라우터 이후에 적용)
145
+ app.use(errorHandler);
1235
146
  console.log('✅ 서비스 초기화 완료');
1236
147
  // 배치 스케줄러 시작 (이미 실행 중이면 먼저 중지)
1237
148
  const batchScheduler = getBatchScheduler();
@@ -1312,8 +223,7 @@ function registerCleanupHandlers() {
1312
223
  }
1313
224
  // WebSocket 서버 설정
1314
225
  const wss = new WebSocketServer({ server });
1315
- // WebSocket 클라이언트 관리 (Anchor Map 업데이트용)
1316
- const anchorMapSubscribers = new Map(); // agent_id -> WebSocket Set
226
+ // Phase 1.2: anchorMapSubscribers는 위에서 이미 선언됨
1317
227
  wss.on('connection', (ws) => {
1318
228
  console.log('🔗 WebSocket 클라이언트 연결됨');
1319
229
  ws.on('message', async (data) => {