memento-mcp-server 1.19.3-c → 1.19.3-e

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memento-mcp-server",
3
- "version": "1.19.3-c",
3
+ "version": "1.19.3-e",
4
4
  "description": "AI Agent 기억 보조 MCP 서버 - 사람의 기억 구조를 모사한 스토리지+검색+요약+망각 메커니즘",
5
5
  "type": "module",
6
6
  "workspaces": [
@@ -37,6 +37,10 @@ let server;
37
37
  let db = null;
38
38
  // core에서 반환된 서비스 (ToolContext 생성 시 사용)
39
39
  let serverServices = null;
40
+ /** Cursor 등 클라이언트가 7초 내에 Initialize 응답을 받도록, 무거운 초기화는 transport 연결 후 백그라운드에서 수행 */
41
+ let initPromise;
42
+ let resolveInit;
43
+ let rejectInit;
40
44
  // MCP 서버에서는 모든 로그 출력을 완전히 차단
41
45
  // 모든 console 메서드를 빈 함수로 교체
42
46
  // MCP 프로토콜 스펙: stdio 전송 시 stdout에는 오직 JSON-RPC 메시지만 출력되어야 함
@@ -145,28 +149,26 @@ async function checkInitialDatabaseStatus() {
145
149
  mcpLogger.logServer('warn', '초기 데이터베이스 상태 확인 실패', { error: error instanceof Error ? error.message : String(error) });
146
150
  }
147
151
  }
148
- // MCP 서버 초기화
149
- async function initializeServer() {
152
+ /**
153
+ * 무거운 초기화(DB·서비스·툴 등). transport 연결 후 백그라운드에서 실행하며,
154
+ * 완료 시 resolveInit()을 호출해 도구/리소스 요청이 동작하도록 함.
155
+ */
156
+ async function runHeavyInit() {
150
157
  try {
151
158
  mcpLogger.logServer('info', `Memento MCP Server v${packageJson.version}`);
152
- mcpLogger.logServer('info', 'MCP 서버 초기화 시작...');
153
- // 설정 검증
159
+ mcpLogger.logServer('info', 'MCP 서버 초기화(백그라운드) 시작...');
154
160
  validateConfig();
155
161
  mcpLogger.logServer('info', '설정 검증 완료');
156
- // @memento/core로 DB·서비스 초기화
157
162
  const core = await createMementoCore({
158
163
  dbPath: process.env.DB_PATH ?? mementoConfig.dbPath
159
164
  });
160
165
  db = core.db;
161
- const services = core.services;
166
+ serverServices = core.services;
162
167
  mcpLogger.logServer('info', '데이터베이스·서비스 초기화 완료');
163
- // 초기 데이터베이스 상태 확인 (주기적 모니터링은 DatabaseLockMonitor가 담당)
164
168
  await checkInitialDatabaseStatus();
165
169
  mcpLogger.logServer('info', '초기 데이터베이스 상태 확인 완료');
166
- serverServices = services;
167
- // 배치 스케줄러는 core bootstrap에서 이미 시작됨 (services.batchScheduler)
170
+ const services = serverServices;
168
171
  mcpLogger.logServer('info', '서비스 초기화 완료');
169
- // 배치 스케줄러 상태 확인
170
172
  const batchScheduler = services.batchScheduler;
171
173
  const status = batchScheduler ? batchScheduler.getStatus() : { isRunning: false, activeJobs: 0, uptime: 0 };
172
174
  mcpLogger.logServer('info', `배치 스케줄러 상태: ${JSON.stringify({
@@ -174,7 +176,6 @@ async function initializeServer() {
174
176
  activeJobs: status.activeJobs,
175
177
  uptime: status.uptime
176
178
  }, null, 2)}`);
177
- // 임베딩 프로바이더 정보 표시
178
179
  const providerInfo = {
179
180
  provider: mementoConfig.embeddingProvider.toUpperCase()
180
181
  };
@@ -192,274 +193,255 @@ async function initializeServer() {
192
193
  }
193
194
  mcpLogger.logServer('info', '임베딩 프로바이더 정보', providerInfo);
194
195
  mcpLogger.logServer('info', '검색 엔진 초기화 완료');
195
- // MCP 서버 생성
196
- // MCP 스펙 준수: logging capability 선언 (MCP Spec 2024-11-05)
197
- // 참조: https://spec.modelcontextprotocol.io/specification/server/#logging
198
- // instructions: InitializeResult에 포함되어 클라이언트(Cursor 등)가 AI에게 Memento 사용법을 안내하는 serverUseInstructions로 활용됨
199
- server = new Server({
200
- name: mementoConfig.serverName,
201
- version: mementoConfig.serverVersion,
202
- }, {
203
- capabilities: {
204
- tools: {},
205
- resources: {},
206
- prompts: {},
207
- logging: {} // MCP 스펙: logging capability 선언 필수
208
- },
209
- instructions: MEMENTO_SERVER_INSTRUCTIONS
210
- });
211
- // MCP 로거에 Server 인스턴스 설정
212
- mcpLogger.setServer(server);
213
- mcpLogger.logServer('info', 'MCP 서버 생성 완료');
214
- // 주의: isTransportConnected 플래그는 server.connect() 호출 직전에 설정됨
215
- // initializeServer()에서는 설정하지 않음
216
- // 도구 레지스트리 가져오기
196
+ resolveInit();
197
+ mcpLogger.logServer('info', 'MCP 서버 초기화(백그라운드) 완료');
198
+ mcpLogger.logServer('info', 'Memento MCP Server가 시작되었습니다!');
199
+ }
200
+ catch (error) {
201
+ const err = error instanceof Error ? error : new Error(String(error));
202
+ rejectInit(err);
203
+ mcpLogger.logServer('error', `서버 초기화 실패: ${err.message}`, { error: err.message, stack: err.stack });
204
+ process.stderr.write(`\n[ERROR] MCP Server Initialization Failed (server stays up; tools will return this error)\n`);
205
+ process.stderr.write(`Error: ${err.message}\n`);
206
+ if (err.stack)
207
+ process.stderr.write(`Stack:\n${err.stack}\n`);
208
+ process.stderr.write(`\n`);
209
+ // Do NOT process.exit(1): client would see "Connection closed" during initializing.
210
+ // Keep server alive so the client gets Initialize response; tool calls will fail with this error.
211
+ }
212
+ }
213
+ /** MCP 핸들러 등록. 각 핸들러는 await initPromise 후 실제 동작 수행(7초 내 Initialize 응답을 위해) */
214
+ function registerHandlers() {
215
+ // Tools 등록
216
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
217
+ await initPromise;
218
+ await mcpLogger.logMCPProtocol('debug', '도구 목록 요청 처리');
217
219
  const toolRegistry = getToolRegistry();
218
- // Tools 등록
219
- server.setRequestHandler(ListToolsRequestSchema, async () => {
220
- await mcpLogger.logMCPProtocol('debug', '도구 목록 요청 처리');
221
- const tools = toolRegistry.getAll();
222
- await mcpLogger.logMCPProtocol('debug', `등록된 도구 개수: ${tools.length}`, { count: tools.length });
223
- return {
224
- tools: tools.map(tool => ({
225
- name: tool.name,
226
- description: tool.description,
227
- inputSchema: tool.inputSchema
228
- }))
229
- };
230
- });
231
- // Resources 목록 핸들러
232
- server.setRequestHandler(ListResourcesRequestSchema, async () => {
233
- await mcpLogger.logMCPProtocol('debug', '리소스 목록 요청 처리');
234
- if (!db) {
235
- throw new Error('Database not initialized');
236
- }
237
- // 모든 메모리 ID 조회
238
- const memories = await DatabaseUtils.all(db, 'SELECT id FROM memory_item ORDER BY created_at DESC LIMIT 1000');
239
- await mcpLogger.logMCPProtocol('debug', `리소스 개수: ${memories.length}`, { count: memories.length });
240
- return {
241
- resources: memories.map((memory) => ({
242
- uri: `memory://${memory.id}`,
243
- name: `Memory ${memory.id}`,
244
- description: `Memory item with ID: ${memory.id}`,
245
- mimeType: 'application/json'
246
- }))
247
- };
248
- });
249
- // Prompts 목록 핸들러 (listOfferingsForUI 등 클라이언트 호출 시 Method not found 방지)
250
- server.setRequestHandler(ListPromptsRequestSchema, async () => {
251
- await mcpLogger.logMCPProtocol('debug', '프롬프트 목록 요청 처리');
252
- return {
253
- prompts: [
254
- {
255
- name: 'memory_injection',
256
- description: '관련 기억을 요약하여 프롬프트에 주입',
257
- arguments: [
258
- { name: 'query', description: '검색할 쿼리', required: true },
259
- { name: 'token_budget', description: '토큰 예산 (기본값: 1000)', required: false },
260
- { name: 'max_memories', description: '최대 기억 개수 (기본값: 5)', required: false }
261
- ]
262
- }
263
- ]
264
- };
265
- });
266
- // Prompt 조회 핸들러
267
- server.setRequestHandler(GetPromptRequestSchema, async (request) => {
268
- const { name } = request.params;
269
- await mcpLogger.logMCPProtocol('debug', `프롬프트 조회 요청: ${name}`, { name });
270
- if (name === 'memory_injection') {
271
- return {
220
+ const tools = toolRegistry.getAll();
221
+ await mcpLogger.logMCPProtocol('debug', `등록된 도구 개수: ${tools.length}`, { count: tools.length });
222
+ return {
223
+ tools: tools.map(tool => ({
224
+ name: tool.name,
225
+ description: tool.description,
226
+ inputSchema: tool.inputSchema
227
+ }))
228
+ };
229
+ });
230
+ // Resources 목록 핸들러
231
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
232
+ await initPromise;
233
+ await mcpLogger.logMCPProtocol('debug', '리소스 목록 요청 처리');
234
+ if (!db) {
235
+ throw new Error('Database not initialized');
236
+ }
237
+ // 모든 메모리 ID 조회
238
+ const memories = await DatabaseUtils.all(db, 'SELECT id FROM memory_item ORDER BY created_at DESC LIMIT 1000');
239
+ await mcpLogger.logMCPProtocol('debug', `리소스 개수: ${memories.length}`, { count: memories.length });
240
+ return {
241
+ resources: memories.map((memory) => ({
242
+ uri: `memory://${memory.id}`,
243
+ name: `Memory ${memory.id}`,
244
+ description: `Memory item with ID: ${memory.id}`,
245
+ mimeType: 'application/json'
246
+ }))
247
+ };
248
+ });
249
+ // Prompts 목록 핸들러 (listOfferingsForUI 등 클라이언트 호출 시 Method not found 방지)
250
+ server.setRequestHandler(ListPromptsRequestSchema, async () => {
251
+ await initPromise;
252
+ await mcpLogger.logMCPProtocol('debug', '프롬프트 목록 요청 처리');
253
+ return {
254
+ prompts: [
255
+ {
256
+ name: 'memory_injection',
272
257
  description: '관련 기억을 요약하여 프롬프트에 주입',
273
258
  arguments: [
274
259
  { name: 'query', description: '검색할 쿼리', required: true },
275
260
  { name: 'token_budget', description: '토큰 예산 (기본값: 1000)', required: false },
276
261
  { name: 'max_memories', description: '최대 기억 개수 (기본값: 5)', required: false }
277
262
  ]
278
- };
279
- }
280
- throw new Error(`Prompt not found: ${name}`);
281
- });
282
- // Resource 읽기 핸들러
283
- server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
284
- const { uri } = request.params;
285
- await mcpLogger.logMCPProtocol('debug', `리소스 읽기 요청: ${uri}`, { uri });
286
- // URI 파싱: memory://{id}?include_neighbors=true
287
- const uriMatch = uri.match(/^memory:\/\/([^?]+)(\?.*)?$/);
288
- if (!uriMatch) {
289
- throw new Error(`Invalid resource URI: ${uri}`);
290
- }
291
- const memoryId = uriMatch[1];
292
- if (!memoryId) {
293
- throw new Error(`Invalid memory ID in URI: ${uri}`);
294
- }
295
- const queryString = uriMatch[2] || '';
296
- const includeNeighbors = queryString.includes('include_neighbors=true');
297
- if (!db) {
298
- throw new Error('Database not initialized');
299
- }
300
- // 메모리 조회
301
- const memory = await DatabaseUtils.get(db, 'SELECT id, type, content, importance, privacy_scope, tags, source, created_at, last_accessed, pinned FROM memory_item WHERE id = ?', [memoryId]);
302
- if (!memory) {
303
- throw new Error(`Memory not found: ${memoryId}`);
304
- }
305
- // 메모리 데이터 구성
306
- const memoryData = {
307
- id: memory.id,
308
- type: memory.type,
309
- content: memory.content,
310
- importance: memory.importance,
311
- privacy_scope: memory.privacy_scope,
312
- tags: memory.tags ? JSON.parse(memory.tags) : [],
313
- source: memory.source,
314
- created_at: memory.created_at,
315
- last_accessed: memory.last_accessed,
316
- pinned: memory.pinned === 1
317
- };
318
- // 이웃 기억 포함 여부 확인
319
- if (includeNeighbors) {
320
- try {
321
- const vectorSearchEngine = getVectorSearchEngine();
322
- const neighborService = new MemoryNeighborService(vectorSearchEngine, serverServices.embeddingService, db);
323
- const neighborsResult = await neighborService.getNeighbors(memoryId, {
324
- limit: 5,
325
- similarity_threshold: 0.8
326
- });
327
- memoryData.neighbors = neighborsResult.neighbors;
328
- memoryData.neighbors_count = neighborsResult.total_count;
329
- memoryData.neighbors_query_time = neighborsResult.query_time;
330
- }
331
- catch (error) {
332
- mcpLogger.logServer('warn', `이웃 기억 조회 실패: ${error instanceof Error ? error.message : String(error)}`, { error: error instanceof Error ? error.message : String(error) });
333
- memoryData.neighbors = [];
334
- memoryData.neighbors_count = 0;
335
263
  }
336
- }
264
+ ]
265
+ };
266
+ });
267
+ // Prompt 조회 핸들러
268
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
269
+ await initPromise;
270
+ const { name } = request.params;
271
+ await mcpLogger.logMCPProtocol('debug', `프롬프트 조회 요청: ${name}`, { name });
272
+ if (name === 'memory_injection') {
337
273
  return {
338
- contents: [
339
- {
340
- uri,
341
- mimeType: 'application/json',
342
- text: JSON.stringify(memoryData, null, 2)
343
- }
274
+ description: '관련 기억을 요약하여 프롬프트에 주입',
275
+ arguments: [
276
+ { name: 'query', description: '검색할 쿼리', required: true },
277
+ { name: 'token_budget', description: '토큰 예산 (기본값: 1000)', required: false },
278
+ { name: 'max_memories', description: '최대 기억 개수 (기본값: 5)', required: false }
344
279
  ]
345
280
  };
346
- });
347
- // Tool 실행 핸들러 - 동시성 제한 적용
348
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
349
- const { name, arguments: args } = request.params;
350
- await mcpLogger.logMCPProtocol('debug', `도구 실행 요청: ${name}`, { toolName: name, args });
351
- // 동시성 제한 적용
352
- await concurrencyLimiter.acquire();
281
+ }
282
+ throw new Error(`Prompt not found: ${name}`);
283
+ });
284
+ // Resource 읽기 핸들러
285
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
286
+ await initPromise;
287
+ const { uri } = request.params;
288
+ await mcpLogger.logMCPProtocol('debug', `리소스 읽기 요청: ${uri}`, { uri });
289
+ // URI 파싱: memory://{id}?include_neighbors=true
290
+ const uriMatch = uri.match(/^memory:\/\/([^?]+)(\?.*)?$/);
291
+ if (!uriMatch) {
292
+ throw new Error(`Invalid resource URI: ${uri}`);
293
+ }
294
+ const memoryId = uriMatch[1];
295
+ if (!memoryId) {
296
+ throw new Error(`Invalid memory ID in URI: ${uri}`);
297
+ }
298
+ const queryString = uriMatch[2] || '';
299
+ const includeNeighbors = queryString.includes('include_neighbors=true');
300
+ if (!db) {
301
+ throw new Error('Database not initialized');
302
+ }
303
+ // 메모리 조회
304
+ const memory = await DatabaseUtils.get(db, 'SELECT id, type, content, importance, privacy_scope, tags, source, created_at, last_accessed, pinned FROM memory_item WHERE id = ?', [memoryId]);
305
+ if (!memory) {
306
+ throw new Error(`Memory not found: ${memoryId}`);
307
+ }
308
+ // 메모리 데이터 구성
309
+ const memoryData = {
310
+ id: memory.id,
311
+ type: memory.type,
312
+ content: memory.content,
313
+ importance: memory.importance,
314
+ privacy_scope: memory.privacy_scope,
315
+ tags: memory.tags ? JSON.parse(memory.tags) : [],
316
+ source: memory.source,
317
+ created_at: memory.created_at,
318
+ last_accessed: memory.last_accessed,
319
+ pinned: memory.pinned === 1
320
+ };
321
+ // 이웃 기억 포함 여부 확인
322
+ if (includeNeighbors) {
353
323
  try {
354
- // Phase 7.8: 공통 에러 핸들러 사용
355
- return await withErrorHandling(async () => {
356
- // 부트스트랩에서 초기화된 서비스 객체를 사용하여 ToolContext 생성
357
- if (!serverServices) {
358
- throw new Error('서비스가 초기화되지 않았습니다');
359
- }
360
- if (!db) {
361
- throw new Error('데이터베이스가 초기화되지 않았습니다');
362
- }
363
- // Phase 7.4: 표준 팩토리 함수 사용
364
- const context = createToolContext(db, serverServices);
365
- // 도구 실행
366
- const toolResult = await toolRegistry.execute(name, args, context);
367
- await mcpLogger.logMCPProtocol('debug', `도구 실행 완료: ${name}`, { toolName: name });
368
- // MCP 형식으로 변환
369
- return {
370
- content: [
371
- {
372
- type: 'text',
373
- text: JSON.stringify(toolResult)
374
- }
375
- ]
376
- };
377
- }, {
378
- operation: 'tool_execution',
379
- toolName: name,
380
- requestId: `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
381
- }, {
382
- errorLoggingService: serverServices.errorLoggingService,
383
- severity: ErrorSeverity.HIGH,
384
- category: ErrorCategory.TOOL_EXECUTION,
385
- transformError: (error) => new Error(`Tool execution failed: ${error.message}`)
324
+ const vectorSearchEngine = getVectorSearchEngine();
325
+ const neighborService = new MemoryNeighborService(vectorSearchEngine, serverServices.embeddingService, db);
326
+ const neighborsResult = await neighborService.getNeighbors(memoryId, {
327
+ limit: 5,
328
+ similarity_threshold: 0.8
386
329
  });
330
+ memoryData.neighbors = neighborsResult.neighbors;
331
+ memoryData.neighbors_count = neighborsResult.total_count;
332
+ memoryData.neighbors_query_time = neighborsResult.query_time;
387
333
  }
388
- finally {
389
- // 동시성 제한 해제
390
- concurrencyLimiter.release();
391
- }
392
- });
393
- // logging/setLevel 핸들러 - MCP 스펙 준수
394
- // MCP 스펙: logging/setLevel 요청 처리 필수
395
- // 참조: https://spec.modelcontextprotocol.io/specification/server/#logging
396
- //
397
- // 구현 사항:
398
- // - 유효한 로그 레벨 검증 (debug, info, warn, error)
399
- // - 환경 변수에 로그 레벨 설정 (MCPLogger가 환경 변수를 읽음)
400
- // - 에러 처리: 잘못된 레벨 시 명확한 에러 메시지 반환
401
- server.setRequestHandler(SetLevelRequestSchema, async (request) => {
402
- const { level } = request.params;
403
- await mcpLogger.logMCPProtocol('debug', `로그 레벨 변경 요청: ${level}`, { level });
404
- // 로그 레벨 검증 (MCP 스펙: 유효한 레벨만 허용)
405
- const validLevels = ['debug', 'info', 'warn', 'error'];
406
- if (!validLevels.includes(level)) {
407
- throw new Error(`Invalid log level: ${level}. Valid levels are: ${validLevels.join(', ')}`);
334
+ catch (error) {
335
+ mcpLogger.logServer('warn', `이웃 기억 조회 실패: ${error instanceof Error ? error.message : String(error)}`, { error: error instanceof Error ? error.message : String(error) });
336
+ memoryData.neighbors = [];
337
+ memoryData.neighbors_count = 0;
408
338
  }
409
- // 환경 변수에 로그 레벨 설정 (MCPLogger의 getCurrentLogLevel()이 환경 변수를 읽음)
410
- // MCP 스펙: 로그 레벨 변경은 즉시 적용되어야 함
411
- process.env.LOG_LEVEL = level;
412
- await mcpLogger.logMCPProtocol('info', `로그 레벨이 ${level}로 변경되었습니다`, { level });
413
- return {};
414
- });
415
- mcpLogger.logServer('info', 'MCP 서버 초기화 완료');
416
- // 실시간 성능 모니터링 시작
417
- // performanceMonitoringIntegration.startRealTimeMonitoring();
418
- mcpLogger.logServer('info', 'Memento MCP Server가 시작되었습니다!');
419
- // process.stderr.write('📊 실시간 성능 모니터링이 활성화되었습니다\n');
420
- }
421
- catch (error) {
422
- // 에러 발생 시 상세 정보를 stderr에 출력
423
- const errorMessage = error instanceof Error ? error.message : String(error);
424
- const errorStack = error instanceof Error ? error.stack : undefined;
425
- mcpLogger.logServer('error', `서버 초기화 실패: ${errorMessage}`, {
426
- error: errorMessage,
427
- stack: errorStack,
428
- type: error instanceof Error ? error.constructor.name : typeof error
429
- });
430
- // stderr에 직접 출력하여 Cursor에서도 확인 가능하도록
431
- process.stderr.write(`\n[ERROR] MCP Server Initialization Failed\n`);
432
- process.stderr.write(`Error: ${errorMessage}\n`);
433
- if (errorStack) {
434
- process.stderr.write(`Stack:\n${errorStack}\n`);
435
339
  }
436
- process.stderr.write(`\n`);
437
- process.exit(1);
438
- }
340
+ return {
341
+ contents: [
342
+ {
343
+ uri,
344
+ mimeType: 'application/json',
345
+ text: JSON.stringify(memoryData, null, 2)
346
+ }
347
+ ]
348
+ };
349
+ });
350
+ // Tool 실행 핸들러 - 동시성 제한 적용
351
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
352
+ await initPromise;
353
+ const { name, arguments: args } = request.params;
354
+ await mcpLogger.logMCPProtocol('debug', `도구 실행 요청: ${name}`, { toolName: name, args });
355
+ await concurrencyLimiter.acquire();
356
+ try {
357
+ return await withErrorHandling(async () => {
358
+ if (!serverServices)
359
+ throw new Error('서비스가 초기화되지 않았습니다');
360
+ if (!db)
361
+ throw new Error('데이터베이스가 초기화되지 않았습니다');
362
+ const context = createToolContext(db, serverServices);
363
+ const toolRegistry = getToolRegistry();
364
+ const toolResult = await toolRegistry.execute(name, args, context);
365
+ await mcpLogger.logMCPProtocol('debug', `도구 실행 완료: ${name}`, { toolName: name });
366
+ // MCP 형식으로 변환
367
+ return {
368
+ content: [
369
+ {
370
+ type: 'text',
371
+ text: JSON.stringify(toolResult)
372
+ }
373
+ ]
374
+ };
375
+ }, {
376
+ operation: 'tool_execution',
377
+ toolName: name,
378
+ requestId: `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
379
+ }, {
380
+ errorLoggingService: serverServices.errorLoggingService,
381
+ severity: ErrorSeverity.HIGH,
382
+ category: ErrorCategory.TOOL_EXECUTION,
383
+ transformError: (error) => new Error(`Tool execution failed: ${error.message}`)
384
+ });
385
+ }
386
+ finally {
387
+ // 동시성 제한 해제
388
+ concurrencyLimiter.release();
389
+ }
390
+ });
391
+ // logging/setLevel 핸들러 - MCP 스펙 준수
392
+ // MCP 스펙: logging/setLevel 요청 처리 필수
393
+ // 참조: https://spec.modelcontextprotocol.io/specification/server/#logging
394
+ //
395
+ // 구현 사항:
396
+ // - 유효한 로그 레벨 검증 (debug, info, warn, error)
397
+ // - 환경 변수에 로그 레벨 설정 (MCPLogger가 환경 변수를 읽음)
398
+ // - 에러 처리: 잘못된 레벨 시 명확한 에러 메시지 반환
399
+ server.setRequestHandler(SetLevelRequestSchema, async (request) => {
400
+ await initPromise;
401
+ const { level } = request.params;
402
+ await mcpLogger.logMCPProtocol('debug', `로그 레벨 변경 요청: ${level}`, { level });
403
+ // 로그 레벨 검증 (MCP 스펙: 유효한 레벨만 허용)
404
+ const validLevels = ['debug', 'info', 'warn', 'error'];
405
+ if (!validLevels.includes(level)) {
406
+ throw new Error(`Invalid log level: ${level}. Valid levels are: ${validLevels.join(', ')}`);
407
+ }
408
+ // 환경 변수에 로그 레벨 설정 (MCPLogger의 getCurrentLogLevel()이 환경 변수를 읽음)
409
+ // MCP 스펙: 로그 레벨 변경은 즉시 적용되어야 함
410
+ process.env.LOG_LEVEL = level;
411
+ await mcpLogger.logMCPProtocol('info', `로그 레벨이 ${level}로 변경되었습니다`, { level });
412
+ return {};
413
+ });
439
414
  }
440
415
  // 서버 시작
441
416
  async function startServer() {
442
417
  try {
443
- // MCP 프로토콜 준수: transport 연결 전까지는 로그를 억제하여 stdout 오염 방지
444
- // initializeServer() 호출 전에 플래그를 설정하여 초기화 중 로그 출력 방지
445
- // ServerState를 통해 mcpLogger에서 접근 가능하도록 설정
446
418
  serverState.setMcpTransportConnected(false);
447
- await initializeServer();
448
- mcpLogger.logServer('info', '서버 초기화 완료');
449
- // Stdio 전송 계층 사용
450
- // MCP SDK의 StdioServerTransport가 stdout을 사용하여 JSON-RPC 메시지 전송
451
- // MCP 프로토콜 스펙: stdio 전송 시 stdout에는 오직 JSON-RPC 메시지만 출력되어야 함
419
+ initPromise = new Promise((resolve, reject) => {
420
+ resolveInit = resolve;
421
+ rejectInit = reject;
422
+ });
423
+ server = new Server({
424
+ name: mementoConfig.serverName,
425
+ version: mementoConfig.serverVersion,
426
+ }, {
427
+ capabilities: {
428
+ tools: {},
429
+ resources: {},
430
+ prompts: {},
431
+ logging: {}
432
+ },
433
+ instructions: MEMENTO_SERVER_INSTRUCTIONS
434
+ });
435
+ mcpLogger.setServer(server);
436
+ registerHandlers();
437
+ // transport를 먼저 연결해 Cursor가 7초 내에 Initialize 응답을 받도록 함
452
438
  const transport = new StdioServerTransport();
453
439
  await server.connect(transport);
454
- // transport 연결 완료 후 로그 출력 허용
455
440
  serverState.setMcpTransportConnected(true);
456
- // 서버 초기화 완료 플래그 설정
457
- // console.error 오버라이드가 MCP Logger를 사용하도록 전환
458
- // MCP 스펙 준수 범위: 서버 초기화 완료 후부터 적용
459
- // 참조: https://spec.modelcontextprotocol.io/specification/server/#logging
460
441
  serverState.setMcpServerInitialized(true);
461
442
  mcpLogger.logServer('info', 'MCP 전송 계층 연결 완료');
462
- // MCP 클라이언트 연결 대기 중
443
+ // 무거운 초기화(DB·서비스)는 백그라운드에서 수행
444
+ void runHeavyInit();
463
445
  mcpLogger.logServer('info', 'MCP 클라이언트 연결 대기 중...');
464
446
  // 서버가 종료될 때까지 대기
465
447
  return new Promise((resolve) => {