본문 바로가기
개발

Claude를 활용한 MCP(Model Context Protocol) 설정 가이드 - Part 4: 실전 업무 자동화 응용 💼

by D-Project 2025. 3. 29.

안녕하세요! 지금까지 MCP의 기본 개념부터 서버 구현, 확장까지 살펴봤는데요. 이번 마지막 포스팅에서는 이러한 MCP 기능들을 활용해 실제 비즈니스 환경에서 Claude를 통한 업무 자동화를 구현하는 방법을 알아보겠습니다. 실용적인 예시와 함께 Claude와 MCP를 활용한 생산성 향상 방법을 소개해 드립니다! 😊

실전 업무 자동화 시나리오 🔄

MCP와 Claude를 결합하면 다양한 업무 자동화가 가능합니다. 몇 가지 주요 시나리오를 살펴보겠습니다:

1. 자동 데이터 분석 및 보고서 생성 📊

MCP를 활용하면 Claude가 데이터 분석과 보고서 생성 작업을 자동화할 수 있습니다.

// handlers/dataAnalysisHandler.js - 데이터 분석 자동화 핸들러
const fs = require('fs-extra');
const path = require('path');
const { parse } = require('csv-parse/sync');
const { transform } = require('stream-transform');
const logger = require('../utils/logger');
const { exec } = require('child_process');
const { promisify } = require('util');
const execAsync = promisify(exec);

// 기본 데이터 디렉토리
const DATA_DIR = process.env.DATA_DIR || path.join(__dirname, '../data');
const REPORTS_DIR = path.join(DATA_DIR, 'reports');

// 디렉토리 생성
fs.ensureDirSync(DATA_DIR);
fs.ensureDirSync(REPORTS_DIR);

// CSV 파일 분석
const analyzeCSVFile = async (req, res) => {
  try {
    const { filePath, options = {} } = req.body;

    if (!filePath) {
      return res.status(400).json({ error: '파일 경로가 필요합니다.' });
    }

    // 기본 옵션 설정
    const parseOptions = {
      columns: true,
      skipEmptyLines: true,
      delimiter: options.delimiter || ',',
      ...options
    };

    // 파일 경로 확인
    const fullPath = path.isAbsolute(filePath) ? filePath : path.join(DATA_DIR, filePath);

    // 파일 존재 확인
    if (!await fs.pathExists(fullPath)) {
      return res.status(404).json({ error: '파일을 찾을 수 없습니다.' });
    }

    // CSV 파일 읽기
    const fileContent = await fs.readFile(fullPath, 'utf8');

    // CSV 파싱
    const records = parse(fileContent, parseOptions);

    // 기본 통계 계산
    const stats = calculateBasicStats(records);

    // 열/컬럼 분석
    const columnAnalysis = analyzeColumns(records);

    // 상관 관계 분석 (숫자 데이터 간)
    const correlations = calculateCorrelations(records, columnAnalysis.numerical);

    logger.info('CSV 파일 분석 완료', { filePath });
    return res.json({
      filename: path.basename(fullPath),
      recordCount: records.length,
      columns: Object.keys(records[0] || {}),
      stats,
      columnAnalysis,
      correlations,
      sampleData: records.slice(0, 5) // 샘플 데이터 (처음 5개 행)
    });

  } catch (error) {
    logger.error('CSV 파일 분석 실패', { error: error.message });
    return res.status(500).json({ error: `CSV 파일을 분석할 수 없습니다: ${error.message}` });
  }
};

// 기본 통계 계산 (열별)
const calculateBasicStats = (records) => {
  if (!records.length) return {};

  const columns = Object.keys(records[0]);
  const stats = {};

  columns.forEach(column => {
    // 해당 열의 모든 값 추출
    const values = records.map(record => record[column]);

    // 값이 숫자인지 확인
    const numericValues = values
      .filter(val => val !== null && val !== undefined && val !== '')
      .map(val => {
        const parsed = parseFloat(val);
        return !isNaN(parsed) ? parsed : null;
      })
      .filter(val => val !== null);

    // 숫자 데이터인 경우 통계 계산
    if (numericValues.length > 0) {
      const sum = numericValues.reduce((acc, val) => acc + val, 0);
      const mean = sum / numericValues.length;

      // 정렬된 배열에서 중앙값 계산
      const sorted = [...numericValues].sort((a, b) => a - b);
      const midIndex = Math.floor(sorted.length / 2);
      const median = sorted.length % 2 === 0
        ? (sorted[midIndex - 1] + sorted[midIndex]) / 2
        : sorted[midIndex];

      // 최소값, 최대값, 분산, 표준편차 계산
      const min = Math.min(...numericValues);
      const max = Math.max(...numericValues);
      const variance = numericValues.reduce((acc, val) => acc + Math.pow(val - mean, 2), 0) / numericValues.length;
      const stdDev = Math.sqrt(variance);

      stats[column] = {
        type: 'numeric',
        count: numericValues.length,
        mean,
        median,
        min,
        max,
        range: max - min,
        variance,
        stdDev
      };
    } else {
      // 카테고리형 데이터 분석
      const valueFrequency = {};
      values.forEach(val => {
        if (val === undefined || val === null || val === '') return;
        valueFrequency[val] = (valueFrequency[val] || 0) + 1;
      });

      // 가장 많이 등장한 값 찾기
      let mostFrequent = null;
      let maxFrequency = 0;

      Object.keys(valueFrequency).forEach(val => {
        if (valueFrequency[val] > maxFrequency) {
          mostFrequent = val;
          maxFrequency = valueFrequency[val];
        }
      });

      stats[column] = {
        type: 'categorical',
        uniqueCount: Object.keys(valueFrequency).length,
        mostFrequent,
        mostFrequentCount: maxFrequency,
        frequencies: valueFrequency
      };
    }
  });

  return stats;
};

// 열 분석 (데이터 타입, 결측치 등)
const analyzeColumns = (records) => {
  if (!records.length) return { numerical: [], categorical: [], boolean: [], date: [] };

  const columns = Object.keys(records[0]);
  const analysis = {
    numerical: [],
    categorical: [],
    boolean: [],
    date: []
  };

  const columnDetails = {};

  columns.forEach(column => {
    // 해당 열의 모든 값 추출
    const values = records.map(record => record[column]);

    // 결측치 분석
    const nullCount = values.filter(val => val === null || val === undefined || val === '').length;
    const nullPercentage = (nullCount / values.length) * 100;

    // 열 타입 추론
    const nonNullValues = values.filter(val => val !== null && val !== undefined && val !== '');
    const typeAnalysis = inferColumnType(nonNullValues);

    // 데이터 타입에 따라 그룹화
    if (typeAnalysis.type === 'numeric') {
      analysis.numerical.push(column);
    } else if (typeAnalysis.type === 'boolean') {
      analysis.boolean.push(column);
    } else if (typeAnalysis.type === 'date') {
      analysis.date.push(column);
    } else {
      analysis.categorical.push(column);
    }

    // 열 상세 정보 저장
    columnDetails[column] = {
      type: typeAnalysis.type,
      nullCount,
      nullPercentage,
      uniqueCount: typeAnalysis.uniqueCount,
      ...typeAnalysis.additionalInfo
    };
  });

  return {
    ...analysis,
    details: columnDetails
  };
};

// 데이터 타입 추론
const inferColumnType = (values) => {
  if (!values.length) return { type: 'unknown', uniqueCount: 0 };

  // 고유값 계산
  const uniqueValues = new Set(values);
  const uniqueCount = uniqueValues.size;

  // 불리언 타입 확인
  const booleanValues = ['true', 'false', true, false, '1', '0', 1, 0, 'yes', 'no', 'y', 'n'];
  if (uniqueCount <= 2 && values.every(val => booleanValues.includes(val.toString().toLowerCase()))) {
    return { 
      type: 'boolean', 
      uniqueCount,
      additionalInfo: {
        trueValues: values.filter(val => ['true', 'yes', 'y', '1', 1].includes(val.toString().toLowerCase())).length,
        falseValues: values.filter(val => ['false', 'no', 'n', '0', 0].includes(val.toString().toLowerCase())).length
      }
    };
  }

  // 날짜 타입 확인
  const datePattern = /^\d{4}[-/](?:0?[1-9]|1[0-2])[-/](?:0?[1-9]|[12][0-9]|3[01])$/;
  if (values.every(val => String(val).match(datePattern) || !isNaN(Date.parse(val)))) {
    // 날짜 범위 분석
    const dates = values.map(val => new Date(val));
    const timestamps = dates.map(date => date.getTime());

    return { 
      type: 'date', 
      uniqueCount,
      additionalInfo: {
        minDate: new Date(Math.min(...timestamps)).toISOString(),
        maxDate: new Date(Math.max(...timestamps)).toISOString(),
        range: `${Math.floor((Math.max(...timestamps) - Math.min(...timestamps)) / (1000 * 60 * 60 * 24))} days`
      }
    };
  }

  // 숫자 타입 확인
  if (values.every(val => !isNaN(parseFloat(val)))) {
    return { 
      type: 'numeric', 
      uniqueCount,
      additionalInfo: {}
    };
  }

  // 카테고리형 타입 (기본값)
  return { 
    type: 'categorical', 
    uniqueCount,
    additionalInfo: {
      // 고유값이 너무 많은 경우 샘플만 표시
      examples: uniqueCount > 10 ? Array.from(uniqueValues).slice(0, 10) : Array.from(uniqueValues)
    }
  };
};

// 상관 관계 계산 (숫자형 데이터 간)
const calculateCorrelations = (records, numericalColumns) => {
  if (!records.length || numericalColumns.length < 2) return {};

  const correlations = {};

  // 모든 숫자 열 쌍에 대한 상관 계수 계산
  for (let i = 0; i < numericalColumns.length; i++) {
    const col1 = numericalColumns[i];

    // 해당 열의 유효한 숫자 값만 추출
    const values1 = records.map(record => {
      const val = record[col1];
      return val !== null && val !== undefined && val !== '' ? parseFloat(val) : null;
    }).filter(val => val !== null && !isNaN(val));

    correlations[col1] = {};

    for (let j = 0; j < numericalColumns.length; j++) {
      const col2 = numericalColumns[j];

      // 동일한 열은 상관 계수 1
      if (col1 === col2) {
        correlations[col1][col2] = 1;
        continue;
      }

      // 이미 계산된 상관 계수는 재사용
      if (correlations[col2] && correlations[col2][col1] !== undefined) {
        correlations[col1][col2] = correlations[col2][col1];
        continue;
      }

      // 해당 열의 유효한 숫자 값만 추출
      const values2 = records.map(record => {
        const val = record[col2];
        return val !== null && val !== undefined && val !== '' ? parseFloat(val) : null;
      }).filter(val => val !== null && !isNaN(val));

      // 두 열 모두 유효한 값이 있는 행만 사용
      const pairedValues = [];
      for (let k = 0; k < records.length; k++) {
        const val1 = parseFloat(records[k][col1]);
        const val2 = parseFloat(records[k][col2]);

        if (!isNaN(val1) && !isNaN(val2)) {
          pairedValues.push({ x: val1, y: val2 });
        }
      }

      // 피어슨 상관 계수 계산
      correlations[col1][col2] = calculatePearsonCorrelation(pairedValues);
    }
  }

  return correlations;
};

// 피어슨 상관 계수 계산
const calculatePearsonCorrelation = (pairedValues) => {
  if (!pairedValues.length) return null;

  // x 값의 합과 평균
  const sumX = pairedValues.reduce((acc, pair) => acc + pair.x, 0);
  const meanX = sumX / pairedValues.length;

  // y 값의 합과 평균
  const sumY = pairedValues.reduce((acc, pair) => acc + pair.y, 0);
  const meanY = sumY / pairedValues.length;

  // 상관 계수 계산을 위한 값들
  let numerator = 0;
  let denomX = 0;
  let denomY = 0;

  pairedValues.forEach(pair => {
    const xDiff = pair.x - meanX;
    const yDiff = pair.y - meanY;

    numerator += xDiff * yDiff;
    denomX += Math.pow(xDiff, 2);
    denomY += Math.pow(yDiff, 2);
  });

  // 분모가 0이면 상관 계수는 0
  if (denomX === 0 || denomY === 0) return 0;

  // 상관 계수 계산 (-1 ~ 1 사이의 값)
  const correlation = numerator / Math.sqrt(denomX * denomY);

  // 소수점 3자리까지 반올림
  return Math.round(correlation * 1000) / 1000;
};

// 시각화 데이터 준비
const prepareVisualizationData = (records, column, options = {}) => {
  if (!records.length || !column) return null;

  const values = records.map(record => record[column]);

  // 숫자형 데이터인지 확인
  const parsedValues = values.map(val => {
    if (val === null || val === undefined || val === '') return null;
    const parsed = parseFloat(val);
    return !isNaN(parsed) ? parsed : val;
  });

  // 숫자형 데이터인 경우
  if (parsedValues.every(val => val === null || typeof val === 'number')) {
    // 히스토그램 데이터 생성
    const validValues = parsedValues.filter(val => val !== null);
    const binCount = options.binCount || 10;

    const min = Math.min(...validValues);
    const max = Math.max(...validValues);
    const binSize = (max - min) / binCount;

    const bins = Array(binCount).fill(0);

    validValues.forEach(val => {
      const binIndex = Math.min(Math.floor((val - min) / binSize), binCount - 1);
      bins[binIndex]++;
    });

    return {
      type: 'histogram',
      bins: bins.map((count, i) => ({
        bin: `${(min + i * binSize).toFixed(2)} - ${(min + (i + 1) * binSize).toFixed(2)}`,
        count
      }))
    };
  }

  // 카테고리형 데이터인 경우
  const valueFrequency = {};
  values.forEach(val => {
    if (val === undefined || val === null || val === '') return;
    valueFrequency[val] = (valueFrequency[val] || 0) + 1;
  });

  return {
    type: 'categorical',
    frequencies: Object.entries(valueFrequency).map(([value, count]) => ({
      value,
      count
    }))
  };
};

// 데이터 변환 요약
const summarizeDataTransformation = async (req, res) => {
  try {
    const { 
      sourcePath, 
      transformations = [],
      options = {}
    } = req.body;

    if (!sourcePath) {
      return res.status(400).json({ error: '원본 파일 경로가 필요합니다.' });
    }

    if (!transformations.length) {
      return res.status(400).json({ error: '적용할 변환이 필요합니다.' });
    }

    // 파일 경로 확인
    const fullPath = path.isAbsolute(sourcePath) ? sourcePath : path.join(DATA_DIR, sourcePath);

    // 파일 존재 확인
    if (!await fs.pathExists(fullPath)) {
      return res.status(404).json({ error: '파일을 찾을 수 없습니다.' });
    }

    // CSV 파일 읽기
    const fileContent = await fs.readFile(fullPath, 'utf8');

    // CSV 파싱 옵션
    const parseOptions = {
      columns: true,
      skipEmptyLines: true,
      delimiter: options.delimiter || ',',
      ...options
    };

    // CSV 파싱
    const records = parse(fileContent, parseOptions);

    // 변환 전 데이터 요약
    const originalSummary = {
      recordCount: records.length,
      columns: Object.keys(records[0] || {})
    };

    // 변환 적용 및 결과 요약
let transformedRecords = [...records];
const transformationResults = [];

// 각 변환 적용
for (const transformation of transformations) {
  const { type, params } = transformation;
  const beforeCount = transformedRecords.length;

  switch (type) {
    case 'filter':
      // 조건에 맞는 레코드만 필터링
      transformedRecords = filterRecords(transformedRecords, params);
      break;

    case 'select':
      // 특정 열만 선택
      transformedRecords = selectColumns(transformedRecords, params);
      break;

    case 'rename':
      // 열 이름 변경
      transformedRecords = renameColumns(transformedRecords, params);
      break;

    case 'transform':
      // 열 데이터 변환
      transformedRecords = transformColumns(transformedRecords, params);
      break;

    case 'groupBy':
      // 그룹화 및 집계
      transformedRecords = groupByColumns(transformedRecords, params);
      break;

    case 'sort':
      // 정렬
      transformedRecords = sortRecords(transformedRecords, params);
      break;

    default:
      transformationResults.push({
        type,
        success: false,
        error: '지원되지 않는 변환 유형',
        affectedRecords: 0
      });
      continue;
  }

  const afterCount = transformedRecords.length;

  transformationResults.push({
    type,
    success: true,
    affectedRecords: Math.abs(afterCount - beforeCount),
    resultCount: afterCount
  });
}

// 변환 후 데이터 요약
const transformedSummary = {
  recordCount: transformedRecords.length,
  columns: Object.keys(transformedRecords[0] || {})
};

logger.info('데이터 변환 요약 완료', { sourcePath });
return res.json({
  original: originalSummary,
  transformed: transformedSummary,
  transformations: transformationResults,
  sampleData: transformedRecords.slice(0, 5) // 변환된 데이터 샘플 (처음 5개 행)
});

// 변환 적용 및 결과 요약
let transformedRecords = [...records];
const transformationResults = [];

// 각 변환 적용
for (const transformation of transformations) {
  const { type, params } = transformation;
  const beforeCount = transformedRecords.length;

  switch (type) {
    case 'filter':
      // 조건에 맞는 레코드만 필터링
      transformedRecords = filterRecords(transformedRecords, params);
      break;

    case 'select':
      // 특정 열만 선택
      transformedRecords = selectColumns(transformedRecords, params);
      break;

    case 'rename':
      // 열 이름 변경
      transformedRecords = renameColumns(transformedRecords, params);
      break;

    case 'transform':
      // 열 데이터 변환
      transformedRecords = transformColumns(transformedRecords, params);
      break;

    case 'groupBy':
      // 그룹화 및 집계
      transformedRecords = groupByColumns(transformedRecords, params);
      break;

    case 'sort':
      // 정렬
      transformedRecords = sortRecords(transformedRecords, params);
      break;

    default:
      transformationResults.push({
        type,
        success: false,
        error: '지원되지 않는 변환 유형',
        affectedRecords: 0
      });
      continue;
  }

  const afterCount = transformedRecords.length;

  transformationResults.push({
    type,
    success: true,
    affectedRecords: Math.abs(afterCount - beforeCount),
    resultCount: afterCount
  });
}

// 변환 후 데이터 요약
const transformedSummary = {
  recordCount: transformedRecords.length,
  columns: Object.keys(transformedRecords[0] || {})
};

logger.info('데이터 변환 요약 완료', { sourcePath });
return res.json({
  original: originalSummary,
  transformed: transformedSummary,
  transformations: transformationResults,
  sampleData: transformedRecords.slice(0, 5) // 변환된 데이터 샘플 (처음 5개 행)
});

2. 코드 관리 자동화 👨‍💻

MCP와 Claude를 활용하면 GitHub 저장소 관리, 코드 리뷰, 테스트 생성 등의 개발 작업을 자동화할 수 있습니다.

// handlers/githubHandler.js - GitHub 연동 핸들러
const axios = require('axios');
const logger = require('../utils/logger');
require('dotenv').config();

// GitHub API 기본 URL
const GITHUB_API_URL = 'https://api.github.com';

// GitHub API 호출 도우미 함수
const githubApiRequest = async (method, endpoint, data = null) => {
  const token = process.env.GITHUB_TOKEN;

  if (!token) {
    throw new Error('GitHub 액세스 토큰이 설정되지 않았습니다.');
  }

  try {
    const response = await axios({
      method,
      url: `${GITHUB_API_URL}${endpoint}`,
      headers: {
        'Authorization': `token ${token}`,
        'Accept': 'application/vnd.github.v3+json'
      },
      data: data
    });

    return response.data;
  } catch (error) {
    logger.error('GitHub API 요청 실패', { 
      endpoint, 
      error: error.message,
      response: error.response?.data
    });
    throw error;
  }
};

// 코드 리뷰 분석
const analyzeCodeForReview = async (req, res) => {
  try {
    const { owner, repo, pull_number } = req.body;

    if (!owner || !repo || !pull_number) {
      return res.status(400).json({ error: '저장소 소유자, 저장소 이름, PR 번호가 필요합니다.' });
    }

    // PR 정보 가져오기
    const pullRequest = await githubApiRequest('GET', `/repos/${owner}/${repo}/pulls/${pull_number}`);

    // PR의 변경된 파일 목록 가져오기
    const files = await githubApiRequest('GET', `/repos/${owner}/${repo}/pulls/${pull_number}/files`);

    // 코드 품질 분석 결과 생성
    const analysisResults = [];

    for (const file of files) {
      // 변경 사항이 너무 큰 파일은 건너뛰기
      if (file.changes > 1000) {
        analysisResults.push({
          filename: file.filename,
          status: file.status,
          changes: file.changes,
          tooLargeToAnalyze: true,
          recommendations: [`변경 사항이 너무 큰 파일입니다 (${file.changes}줄). 더 작은 PR로 나누는 것을 고려하세요.`]
        });
        continue;
      }

      // 삭제된 파일은 분석 건너뛰기
      if (file.status === 'removed') {
        analysisResults.push({
          filename: file.filename,
          status: file.status,
          changes: file.changes,
          recommendations: []
        });
        continue;
      }

      // 파일 내용 가져오기
      const fileContent = await githubApiRequest('GET', `/repos/${owner}/${repo}/contents/${file.filename}?ref=${pullRequest.head.ref}`);

      // base64 인코딩된 내용 디코딩
      const content = Buffer.from(fileContent.content, 'base64').toString();

      // 파일 확장자 추출
      const extension = file.filename.split('.').pop().toLowerCase();

      // 파일 유형별 분석
      const analysis = analyzeFileContent(content, extension);

      analysisResults.push({
        filename: file.filename,
        status: file.status,
        changes: file.changes,
        ...analysis
      });
    }

    logger.info('코드 리뷰 분석 완료', { owner, repo, pull_number });
    return res.json({
      pullRequest: {
        title: pullRequest.title,
        number: pullRequest.number,
        author: pullRequest.user.login,
        createdAt: pullRequest.created_at,
        updatedAt: pullRequest.updated_at,
        state: pullRequest.state,
        changedFiles: files.length,
        additions: pullRequest.additions,
        deletions: pullRequest.deletions
      },
      analysisResults
    });

  } catch (error) {
    logger.error('코드 리뷰 분석 실패', { error: error.message });

    if (error.response && error.response.status === 404) {
      return res.status(404).json({ error: '저장소 또는 PR을 찾을 수 없습니다.' });
    } else if (error.response && error.response.status === 401) {
      return res.status(401).json({ error: 'GitHub API 인증에 실패했습니다. 토큰을 확인하세요.' });
    }

    return res.status(500).json({ error: `코드 리뷰 분석을 수행할 수 없습니다: ${error.message}` });
  }
};

// 파일 내용 분석 (간단한 구현)
const analyzeFileContent = (content, extension) => {
  // 분석 결과 객체
  const analysis = {
    codeMetrics: {},
    potentialIssues: [],
    recommendations: []
  };

  // 빈 줄 및 주석 수 계산
  const lines = content.split('\n');
  const totalLines = lines.length;

  let emptyLines = 0;
  let commentLines = 0;

  // 언어별 주석 패턴
  const commentPatterns = {
    js: [/^\s*\/\//, /^\s*\/\*/, /\*\//],
    py: [/^\s*#/],
    rb: [/^\s*#/],
    java: [/^\s*\/\//, /^\s*\/\*/, /\*\//],
    cs: [/^\s*\/\//, /^\s*\/\*/, /\*\//],
    go: [/^\s*\/\//],
    rs: [/^\s*\/\//],
    php: [/^\s*\/\//, /^\s*#/, /^\s*\/\*/, /\*\//],
    html: [/^\s*<!--/, /-->/],
    css: [/^\s*\/\*/, /\*\//]
  };

  // 선택할 패턴 결정
  let patterns = commentPatterns.js; // 기본값

  if (extension === 'py' || extension === 'ipynb') {
    patterns = commentPatterns.py;
  } else if (extension === 'rb') {
    patterns = commentPatterns.rb;
  } else if (extension === 'java') {
    patterns = commentPatterns.java;
  } else if (extension === 'cs') {
    patterns = commentPatterns.cs;
  } else if (extension === 'go') {
    patterns = commentPatterns.go;
  } else if (extension === 'rs') {
    patterns = commentPatterns.rs;
  } else if (extension === 'php') {
    patterns = commentPatterns.php;
  } else if (extension === 'html' || extension === 'htm' || extension === 'xml') {
    patterns = commentPatterns.html;
  } else if (extension === 'css' || extension === 'scss' || extension === 'sass' || extension === 'less') {
    patterns = commentPatterns.css;
  }

  // 주석 및 빈 줄 계산
  let inBlockComment = false;

  for (const line of lines) {
    const trimmedLine = line.trim();

    if (trimmedLine === '') {
      emptyLines++;
      continue;
    }

    // 블록 주석 내부인 경우
    if (inBlockComment) {
      commentLines++;

      // 블록 주석 종료 확인
      if (patterns.some(pattern => pattern.toString().includes('\\*\\/') && trimmedLine.match(pattern))) {
        inBlockComment = false;
      }
      continue;
    }

    // 한 줄 주석 또는 블록 주석 시작 확인
    if (patterns.some(pattern => trimmedLine.match(pattern))) {
      commentLines++;

      // 블록 주석 시작 확인
      if (patterns.some(pattern => pattern.toString().includes('\\/\\*') && trimmedLine.match(pattern))
          && !patterns.some(pattern => pattern.toString().includes('\\*\\/') && trimmedLine.match(pattern))) {
        inBlockComment = true;
      }
    }
  }

  // 코드 메트릭 계산
  const codeLines = totalLines - emptyLines - commentLines;
  const commentRatio = commentLines / (codeLines > 0 ? codeLines : 1);

  analysis.codeMetrics = {
    totalLines,
    codeLines,
    commentLines,
    emptyLines,
    commentRatio: commentRatio.toFixed(2)
  };

  // 잠재적 이슈 분석

  // 1. 과도하게 긴 줄 확인
  const longLines = lines.filter(line => line.length > 100);
  if (longLines.length > 0) {
    analysis.potentialIssues.push({
      type: 'longLines',
      count: longLines.length,
      message: `100자를 초과하는 줄이 ${longLines.length}개 있습니다.`
    });

    analysis.recommendations.push('코드 가독성을 위해 줄 길이를 80-100자 이내로 유지하세요.');
  }

  // 2. 주석 비율 확인
  if (commentRatio < 0.1 && codeLines > 50) {
    analysis.potentialIssues.push({
      type: 'lowCommentRatio',
      ratio: commentRatio.toFixed(2),
      message: `주석 비율이 낮습니다 (${(commentRatio * 100).toFixed(1)}%).`
    });

    analysis.recommendations.push('코드 품질 및 유지보수성을 위해 충분한 주석을 추가하세요.');
  }

  // 3. 중복 코드 블록 감지 (간단한 구현)
  const codeDuplicates = findDuplicateBlocks(lines, 6); // 6줄 이상 중복 블록 찾기
  if (codeDuplicates.length > 0) {
    analysis.potentialIssues.push({
      type: 'duplicateCode',
      count: codeDuplicates.length,
      blocks: codeDuplicates,
      message: `중복된 코드 블록이 ${codeDuplicates.length}개 발견되었습니다.`
    });

    analysis.recommendations.push('중복 코드를 함수나 클래스로 리팩토링하여 재사용성을 높이세요.');
  }

  // 4. 보안 이슈 감지 (간단한 구현)
  const securityIssues = findSecurityIssues(content, extension);
  if (securityIssues.length > 0) {
    analysis.potentialIssues.push({
      type: 'securityIssues',
      issues: securityIssues,
      message: `${securityIssues.length}개의 잠재적 보안 이슈가 발견되었습니다.`
    });

    securityIssues.forEach(issue => {
      analysis.recommendations.push(issue.recommendation);
    });
  }

  return analysis;
};

// 중복 코드 블록 찾기 (간단한 구현)
const findDuplicateBlocks = (lines, minBlockSize) => {
  const duplicates = [];
  const blockMap = new Map();

  for (let i = 0; i <= lines.length - minBlockSize; i++) {
    const block = lines.slice(i, i + minBlockSize).join('\n');

    // 유의미한 내용이 있는 블록만 검사 (빈 줄이나 주석만 있는 블록 제외)
    if (block.trim().length > 0 && !block.split('\n').every(line => line.trim() === '' || line.trim().startsWith('//'))) {
      if (blockMap.has(block)) {
        const firstOccurrence = blockMap.get(block);

        // 아직 중복으로 기록되지 않은 경우에만 추가
        if (!duplicates.some(dup => dup.firstLine === firstOccurrence && dup.duplicateLine === i)) {
          duplicates.push({
            firstLine: firstOccurrence,
            duplicateLine: i,
            size: minBlockSize,
            content: block.substring(0, 100) + (block.length > 100 ? '...' : '') // 내용 일부만 표시
          });
        }
      } else {
        blockMap.set(block, i);
      }
    }
  }

  return duplicates;
};

// 보안 이슈 찾기 (간단한 구현)
const findSecurityIssues = (content, extension) => {
  const issues = [];

  // 언어별 보안 패턴
  const securityPatterns = {
    js: [
      { pattern: /eval\s*\(/, 
        message: 'eval() 함수 사용', 
        recommendation: '동적 코드 실행에 eval() 대신 더 안전한 대안을 사용하세요.' },
      { pattern: /document\.write\s*\(/, 
        message: 'document.write() 사용', 
        recommendation: 'XSS 취약점 방지를 위해 document.write() 대신 안전한 DOM 조작 방법을 사용하세요.' },
      { pattern: /(?:password|token|secret|key).*?=.*?["'](?!process)/i, 
        message: '하드코딩된 암호 또는 키', 
        recommendation: '암호, 토큰, API 키 등의 민감한 정보는 환경 변수나 설정 파일로 분리하세요.' }
    ],
    py: [
      { pattern: /exec\s*\(/, 
        message: 'exec() 함수 사용', 
        recommendation: '동적 코드 실행에 exec() 대신 더 안전한 대안을 사용하세요.' },
      { pattern: /(?:password|token|secret|key).*?=.*?["']/i, 
        message: '하드코딩된 암호 또는 키', 
        recommendation: '암호, 토큰, API 키 등의 민감한 정보는 환경 변수나 설정 파일로 분리하세요.' },
      { pattern: /subprocess\.call\s*\(.*?shell\s*=\s*True/i, 
        message: 'shell=True로 subprocess 호출', 
        recommendation: '명령 주입 방지를 위해 shell=True 매개변수를 사용하지 마세요.' }
    ]
  };

  // 선택할 패턴 결정
  const patterns = securityPatterns[extension] || securityPatterns.js;

  // 각 패턴에 대해 검사
  for (const { pattern, message, recommendation } of patterns) {
    if (pattern.test(content)) {
      issues.push({
        pattern: pattern.toString(),
        message,
        recommendation
      });
    }
  }

  return issues;
};

module.exports = {
  analyzeCodeForReview
};

3. 자동 콘텐츠 생성 및 관리 📝

MCP를 활용해 Claude가 마케팅 콘텐츠, 기술 문서, 보고서 등을 자동으로 생성하고 관리할 수 있습니다.

// handlers/contentHandler.js - 콘텐츠 생성 핸들러
const fs = require('fs-extra');
const path = require('path');
const axios = require('axios');
const logger = require('../utils/logger');
require('dotenv').config();

// 기본 콘텐츠 디렉토리
const CONTENT_DIR = process.env.CONTENT_DIR || path.join(__dirname, '../content');

// 디렉토리 생성
fs.ensureDirSync(CONTENT_DIR);

// 콘텐츠 템플릿 생성
const generateContentTemplate = async (req, res) => {
  try {
    const { 
      contentType, 
      title, 
      targetAudience, 
      keyPoints = [], 
      tone = 'professional',
      length = 'medium'
    } = req.body;

    if (!contentType || !title) {
      return res.status(400).json({ error: '콘텐츠 유형과 제목이 필요합니다.' });
    }

    // 콘텐츠 유형별 구조 정의
    const contentStructures = {
      blogPost: {
        sections: [
          { name: 'introduction', title: '소개', description: '주제 소개 및 독자의 관심 유도' },
          { name: 'mainContent', title: '본문', description: '주요 내용을 논리적으로 구성 (여러 섹션으로 나눌 수 있음)' },
          { name: 'conclusion', title: '결론', description: '핵심 포인트 요약 및 행동 촉구' }
        ],
        extras: ['featuredImage', 'tags', 'metaDescription']
      },
      technicalDocument: {
        sections: [
          { name: 'abstract', title: '초록', description: '문서의 목적과 범위 요약' },
          { name: 'introduction', title: '소개', description: '주제의 배경 및 중요성 설명' },
          { name: 'methodology', title: '방법론', description: '접근 방식 및 사용된 기술 설명' },
          { name: 'findings', title: '발견 사항', description: '주요 정보 및 데이터 제시' },
          { name: 'discussion', title: '논의', description: '발견 사항의 의미와 영향 분석' },
          { name: 'conclusion', title: '결론', description: '주요 포인트 요약 및 후속 단계 제안' },
          { name: 'references', title: '참고 문헌', description: '인용된 자료 목록' }
        ],
        extras: ['figures', 'tables', 'appendices']
      },
      marketingEmail: {
        sections: [
          { name: 'subject', title: '제목', description: '열람률을 높이는 매력적인 이메일 제목' },
          { name: 'greeting', title: '인사말', description: '독자를 맞이하는 친근한 인사' },
          { name: 'introduction', title: '소개', description: '핵심 가치 제안과 이메일 목적' },
          { name: 'mainContent', title: '본문', description: '주요 혜택과 기능 설명' },
          { name: 'callToAction', title: '행동 촉구', description: '독자가 취해야 할 다음 단계' },
          { name: 'closing', title: '맺음말', description: '인사 및 서명' }
        ],
        extras: ['previewText', 'images', 'buttonText']
      },
      productDescription: {
        sections: [
          { name: 'headline', title: '헤드라인', description: '제품의 핵심 가치 제안' },
          { name: 'overview', title: '개요', description: '제품이 해결하는 문제와 주요 장점' },
          { name: 'features', title: '기능', description: '주요 기능 및 특징 목록' },
          { name: 'benefits', title: '혜택', description: '고객이 얻는 구체적인 혜택' },
          { name: 'specifications', title: '사양', description: '기술적 세부 사항 및 규격' },
          { name: 'pricing', title: '가격', description: '가격 정보 및 구매 옵션' },
          { name: 'callToAction', title: '행동 촉구', description: '구매 또는 자세한 정보 확인 방법' }
        ],
        extras: ['images', 'testimonials', 'guarantees']
      },
      pressRelease: {
        sections: [
          { name: 'headline', title: '헤드라인', description: '주요 뉴스를 담은 명확한 제목' },
          { name: 'dateline', title: '날짜 및 위치', description: '보도 자료 발행 날짜와 위치' },
          { name: 'leadParagraph', title: '리드 문단', description: '가장 중요한 정보를 담은 첫 문단' },
          { name: 'bodyParagraphs', title: '본문', description: '세부 정보 및 인용구' },
          { name: 'boilerplate', title: '회사 소개', description: '회사에 대한 표준 설명' },
          { name: 'contactInfo', title: '연락처 정보', description: '미디어 문의를 위한 연락처' }
        ],
        extras: ['quotes', 'mediaAssets', 'notes']
      }
    };

    // 콘텐츠 유형 확인
    if (!contentStructures[contentType]) {
      return res.status(400).json({ 
        error: '지원되지 않는 콘텐츠 유형입니다.',
        supportedTypes: Object.keys(contentStructures)
      });
    }

    // 선택된 구조 가져오기
    const structure = contentStructures[contentType];

    // 콘텐츠 길이에 따른 단어 수 계산
    const wordCounts = {
      short: {
        blogPost: { total: 500, perSection: { introduction: 75, mainContent: 350, conclusion: 75 } },
        technicalDocument: { total: 1000, perSection: { abstract: 100, introduction: 150, methodology: 200, findings: 250, discussion: 150, conclusion: 100, references: 50 } },
        marketingEmail: { total: 200, perSection: { subject: 7, greeting: 10, introduction: 40, mainContent: 100, callToAction: 15, closing: 25 } },
        productDescription: { total: 300, perSection: { headline: 10, overview: 50, features: 100, benefits: 75, specifications: 40, pricing: 15, callToAction: 10 } },
        pressRelease: { total: 400, perSection: { headline: 10, dateline: 10, leadParagraph: 75, bodyParagraphs: 250, boilerplate: 40, contactInfo: 15 } }
      },
      medium: {
        blogPost: { total: 1000, perSection: { introduction: 150, mainContent: 700, conclusion: 150 } },
        technicalDocument: { total: 2000, perSection: { abstract: 200, introduction: 300, methodology: 400, findings: 500, discussion: 300, conclusion: 200, references: 100 } },
        marketingEmail: { total: 400, perSection: { subject: 10, greeting: 20, introduction: 80, mainContent: 200, callToAction: 30, closing: 50 } },
        productDescription: { total: 600, perSection: { headline: 15, overview: 100, features: 200, benefits: 150, specifications: 80, pricing: 30, callToAction: 20 } },
        pressRelease: { total: 800, perSection: { headline: 15, dateline: 15, leadParagraph: 150, bodyParagraphs: 500, boilerplate: 80, contactInfo: 30 } }
      },
      long: {
        blogPost: { total: 2000, perSection: { introduction: 300, mainContent: 1400, conclusion: 300 } },
        technicalDocument: { total: 4000, perSection: { abstract: 400, introduction: 600, methodology: 800, findings: 1000, discussion: 600, conclusion: 400, references: 200 } },
        marketingEmail: { total: 800, perSection: { subject: 15, greeting: 40, introduction: 160, mainContent: 400, callToAction: 60, closing: 100 } },
        productDescription: { total: 1200, perSection: { headline: 20, overview: 200, features: 400, benefits: 300, specifications: 160, pricing: 60, callToAction: 40 } },
        pressRelease: { total: 1600, perSection: { headline: 20, dateline: 20, leadParagraph: 300, bodyParagraphs: 1000, boilerplate: 160, contactInfo: 60 } }
      }
    };

    // 선택된 콘텐츠 유형과 길이에 따른 단어 수 가져오기
    const wordCount = wordCounts[length][contentType] || wordCounts.medium[contentType];

    // 템플릿 생성
    const template = {
      title,
      contentType,
      targetAudience,
      tone,
      wordCount: wordCount.total,
      keyPoints,
      createdAt: new Date().toISOString(),
      sections: structure.sections.map(section => ({
        ...section,
        wordCount: wordCount.perSection[section.name] || Math.floor(wordCount.total / structure.sections.length),
        content: ''
      })),
      extras: structure.extras.reduce((acc, extra) => {
        acc[extra] = '';
        return acc;
      }, {})
    };

    // 템플릿 파일로 저장
    const filename = `${contentType}_${title.replace(/\s+/g, '_').toLowerCase()}_template.json`;
    const filePath = path.join(CONTENT_DIR, filename);

    await fs.writeFile(filePath, JSON.stringify(template, null, 2), 'utf8');

    logger.info('콘텐츠 템플릿 생성 완료', { contentType, title });
    return res.json({
      message: '콘텐츠 템플릿이 성공적으로 생성되었습니다.',
      template,
      filePath: filename
    });

  } catch (error) {
    logger.error('콘텐츠 템플릿 생성 실패', { error: error.message });
    return res.status(500).json({ error: `콘텐츠 템플릿을 생성할 수 없습니다: ${error.message}` });
  }
};

// 마케팅 이메일 생성 (계속)
const generateMarketingEmail = async (req, res) => {
  try {
    const { 
      product, 
      audience, 
      goal, 
      keyFeatures = [], 
      promotion = null,
      brandGuidelines = {},
      tone = 'friendly'
    } = req.body;

    if (!product || !audience || !goal) {
      return res.status(400).json({ error: '제품, 대상 고객, 이메일 목표가 필요합니다.' });
    }

    // 이메일 구조 생성
    const emailTemplate = {
      subject: '',
      previewText: '',
      greeting: '',
      introduction: '',
      mainContent: '',
      features: keyFeatures,
      promotion: promotion,
      callToAction: '',
      closing: '',
      metadata: {
        product,
        audience,
        goal,
        tone,
        brandGuidelines,
        createdAt: new Date().toISOString()
      }
    };

    // 목표에 따른 주제 및 내용 제안
    const goalSuggestions = {
      productLaunch: {
        subject: `신제품 출시: ${product} - 당신을 위해 특별히 제작된 혁신`,
        introduction: `${audience}를 위한 새로운 솔루션, ${product}를 소개합니다.`,
        ctaSuggestion: `지금 바로 ${product} 살펴보기`
      },
      promotion: {
        subject: `특별 할인: ${product} - 한정 기간 특별 혜택`,
        introduction: `${audience}님을 위한 특별한 제안이 있습니다.`,
        ctaSuggestion: `지금 혜택 받기`
      },
      reEngagement: {
        subject: `${audience}님, 그동안 어떠셨나요? 다시 만나뵙게 되어 기쁩니다`,
        introduction: `오랜만에 인사드립니다. ${audience}님께 새로운 소식을 전해드리고 싶었어요.`,
        ctaSuggestion: `다시 시작하기`
      },
      newsletter: {
        subject: `${product} 뉴스레터: 최신 업데이트 및 팁`,
        introduction: `${audience}님, 이번 달 ${product} 소식을 알려드립니다.`,
        ctaSuggestion: `더 알아보기`
      },
      event: {
        subject: `초대합니다: ${product} 특별 이벤트`,
        introduction: `${audience}님을 저희 특별 이벤트에 초대합니다.`,
        ctaSuggestion: `지금 등록하기`
      }
    };

    // 목표에 맞는 제안 선택
    const suggestionType = goalSuggestions[goal] ? goal : 'productLaunch';
    const suggestions = goalSuggestions[suggestionType];

    // 이메일 템플릿 업데이트
    emailTemplate.subject = suggestions.subject;
    emailTemplate.previewText = `${audience}를 위한 ${product}의 특별한 소식이 있습니다.`;
    emailTemplate.greeting = `안녕하세요, {recipient_name}님!`;
    emailTemplate.introduction = suggestions.introduction;
    emailTemplate.callToAction = suggestions.ctaSuggestion;
    emailTemplate.closing = `감사합니다,\n{sender_name}\n{company_name} 드림`;

    // 템플릿 파일로 저장
    const filename = `email_${product.replace(/\s+/g, '_').toLowerCase()}_${goal}.json`;
    const filePath = path.join(CONTENT_DIR, filename);

    await fs.writeFile(filePath, JSON.stringify(emailTemplate, null, 2), 'utf8');

    logger.info('마케팅 이메일 템플릿 생성 완료', { product, goal });
    return res.json({
      message: '마케팅 이메일 템플릿이 성공적으로 생성되었습니다.',
      template: emailTemplate,
      filePath: filename,
      suggestions: {
        subjectAlternatives: [
          `${audience}를 위한 ${product} - 놓치지 마세요`,
          `${product}로 ${audience}의 문제를 해결하세요`,
          `${audience}님, ${product}에 대한 소식이 있습니다`
        ],
        ctaAlternatives: [
          `지금 확인하기`,
          `자세히 알아보기`,
          `시작하기`
        ]
      }
    });

  } catch (error) {
    logger.error('마케팅 이메일 생성 실패', { error: error.message });
    return res.status(500).json({ error: `마케팅 이메일을 생성할 수 없습니다: ${error.message}` });
  }
};

// 기술 문서 템플릿 생성
const generateTechnicalDocument = async (req, res) => {
  try {
    const { 
      title, 
      product, 
      audience = 'developers', 
      documentType = 'tutorial',
      topics = [],
      complexity = 'intermediate'
    } = req.body;

    if (!title || !product) {
      return res.status(400).json({ error: '문서 제목과 제품 이름이 필요합니다.' });
    }

    // 문서 유형에 따른 구조 정의
    const documentStructures = {
      tutorial: [
        { name: 'introduction', title: '소개', description: '튜토리얼의 목적과 학습 목표 설명' },
        { name: 'prerequisites', title: '사전 준비 사항', description: '필요한 지식, 도구, 환경 설정' },
        { name: 'steps', title: '단계별 가이드', description: '주요 단계를 순차적으로 설명' },
        { name: 'examples', title: '예제', description: '실제 사용 예시 코드' },
        { name: 'troubleshooting', title: '문제 해결', description: '일반적인 오류 및 해결 방법' },
        { name: 'conclusion', title: '결론', description: '요약 및 다음 단계' },
        { name: 'resources', title: '추가 자료', description: '참고 자료 및 관련 문서 링크' }
      ],
      api: [
        { name: 'overview', title: '개요', description: 'API의 목적과 주요 기능' },
        { name: 'authentication', title: '인증', description: 'API 인증 방법 및 보안' },
        { name: 'endpoints', title: '엔드포인트', description: '사용 가능한 API 엔드포인트' },
        { name: 'requests', title: '요청 형식', description: '요청 매개변수 및 형식' },
        { name: 'responses', title: '응답 형식', description: '응답 코드 및 데이터 형식' },
        { name: 'examples', title: '예제', description: '다양한 API 호출 예시' },
        { name: 'rateLimit', title: '사용량 제한', description: 'API 사용량 제한 정책' },
        { name: 'errors', title: '오류 처리', description: '오류 코드 및 메시지 설명' }
      ],
      conceptual: [
        { name: 'introduction', title: '소개', description: '개념의 중요성과 배경' },
        { name: 'definition', title: '정의', description: '주요 개념 및 용어 정의' },
        { name: 'explanation', title: '상세 설명', description: '개념에 대한 심층적인 설명' },
        { name: 'diagrams', title: '다이어그램', description: '시각적 표현 및 설명' },
        { name: 'useCases', title: '활용 사례', description: '실제 응용 사례 및 예시' },
        { name: 'bestPractices', title: '모범 사례', description: '권장 패턴 및 안티패턴' },
        { name: 'summary', title: '요약', description: '핵심 내용 요약' },
        { name: 'furtherReading', title: '추가 자료', description: '심화 학습을 위한 자료' }
      ],
      reference: [
        { name: 'introduction', title: '소개', description: '레퍼런스 목적 및 범위' },
        { name: 'glossary', title: '용어 정의', description: '주요 용어 및 정의' },
        { name: 'specifications', title: '사양', description: '기술적 사양 및 요구사항' },
        { name: 'components', title: '구성 요소', description: '주요 구성 요소 설명' },
        { name: 'syntax', title: '구문', description: '언어 또는 명령어 구문' },
        { name: 'parameters', title: '매개변수', description: '지원되는 매개변수 목록' },
        { name: 'examples', title: '예제', description: '사용 예시' },
        { name: 'appendices', title: '부록', description: '추가 참고 자료' }
      ]
    };

    // 문서 유형 확인
    if (!documentStructures[documentType]) {
      return res.status(400).json({ 
        error: '지원되지 않는 문서 유형입니다.',
        supportedTypes: Object.keys(documentStructures)
      });
    }

    // 복잡도에 따른 설정
    const complexitySettings = {
      beginner: {
        language: '초보자를 위한 쉬운 언어와 용어',
        examples: '기본적이고 간단한 예제',
        depth: '핵심 개념에 집중',
        prerequisites: '최소한의 사전 지식 필요'
      },
      intermediate: {
        language: '일반적인 기술 용어와 개념 사용',
        examples: '실제 시나리오 기반의 예제',
        depth: '주요 개념과 중급 수준의 내용',
        prerequisites: '기본 지식 보유 가정'
      },
      advanced: {
        language: '전문적인 용어와 심층적인 설명',
        examples: '복잡한 시나리오 및 고급 예제',
        depth: '심화 내용 및 내부 동작 원리',
        prerequisites: '관련 분야의 상당한 경험 필요'
      }
    };

    // 문서 템플릿 생성
    const documentTemplate = {
      title,
      product,
      audience,
      documentType,
      complexity,
      complexityGuidelines: complexitySettings[complexity] || complexitySettings.intermediate,
      topics,
      createdAt: new Date().toISOString(),
      sections: documentStructures[documentType].map(section => ({
        ...section,
        content: ''
      })),
      metadata: {
        estimatedReadingTime: '00:00',
        lastUpdated: new Date().toISOString(),
        version: '1.0.0',
        status: 'draft'
      }
    };

    // 템플릿 파일로 저장
    const filename = `techdoc_${title.replace(/\s+/g, '_').toLowerCase()}.json`;
    const filePath = path.join(CONTENT_DIR, filename);

    await fs.writeFile(filePath, JSON.stringify(documentTemplate, null, 2), 'utf8');

    logger.info('기술 문서 템플릿 생성 완료', { title, documentType });
    return res.json({
      message: '기술 문서 템플릿이 성공적으로 생성되었습니다.',
      template: documentTemplate,
      filePath: filename
    });

  } catch (error) {
    logger.error('기술 문서 템플릿 생성 실패', { error: error.message });
    return res.status(500).json({ error: `기술 문서 템플릿을 생성할 수 없습니다: ${error.message}` });
  }
};

module.exports = {
  generateContentTemplate,
  generateMarketingEmail,
  generateTechnicalDocument
};

코드를 완성해 드리겠습니다.

// 마케팅 이메일 생성 (계속)
const generateMarketingEmail = async (req, res) => {
  try {
    const { 
      product, 
      audience, 
      goal, 
      keyFeatures = [], 
      promotion = null,
      brandGuidelines = {},
      tone = 'friendly'
    } = req.body;

    if (!product || !audience || !goal) {
      return res.status(400).json({ error: '제품, 대상 고객, 이메일 목표가 필요합니다.' });
    }

    // 이메일 구조 생성
    const emailTemplate = {
      subject: '',
      previewText: '',
      greeting: '',
      introduction: '',
      mainContent: '',
      features: keyFeatures,
      promotion: promotion,
      callToAction: '',
      closing: '',
      metadata: {
        product,
        audience,
        goal,
        tone,
        brandGuidelines,
        createdAt: new Date().toISOString()
      }
    };

    // 목표에 따른 주제 및 내용 제안
    const goalSuggestions = {
      productLaunch: {
        subject: `신제품 출시: ${product} - 당신을 위해 특별히 제작된 혁신`,
        introduction: `${audience}를 위한 새로운 솔루션, ${product}를 소개합니다.`,
        ctaSuggestion: `지금 바로 ${product} 살펴보기`
      },
      promotion: {
        subject: `특별 할인: ${product} - 한정 기간 특별 혜택`,
        introduction: `${audience}님을 위한 특별한 제안이 있습니다.`,
        ctaSuggestion: `지금 혜택 받기`
      },
      reEngagement: {
        subject: `${audience}님, 그동안 어떠셨나요? 다시 만나뵙게 되어 기쁩니다`,
        introduction: `오랜만에 인사드립니다. ${audience}님께 새로운 소식을 전해드리고 싶었어요.`,
        ctaSuggestion: `다시 시작하기`
      },
      newsletter: {
        subject: `${product} 뉴스레터: 최신 업데이트 및 팁`,
        introduction: `${audience}님, 이번 달 ${product} 소식을 알려드립니다.`,
        ctaSuggestion: `더 알아보기`
      },
      event: {
        subject: `초대합니다: ${product} 특별 이벤트`,
        introduction: `${audience}님을 저희 특별 이벤트에 초대합니다.`,
        ctaSuggestion: `지금 등록하기`
      }
    };

    // 목표에 맞는 제안 선택
    const suggestionType = goalSuggestions[goal] ? goal : 'productLaunch';
    const suggestions = goalSuggestions[suggestionType];

    // 이메일 템플릿 업데이트
    emailTemplate.subject = suggestions.subject;
    emailTemplate.previewText = `${audience}를 위한 ${product}의 특별한 소식이 있습니다.`;
    emailTemplate.greeting = `안녕하세요, {recipient_name}님!`;
    emailTemplate.introduction = suggestions.introduction;
    emailTemplate.callToAction = suggestions.ctaSuggestion;
    emailTemplate.closing = `감사합니다,\n{sender_name}\n{company_name} 드림`;

    // 템플릿 파일로 저장
    const filename = `email_${product.replace(/\s+/g, '_').toLowerCase()}_${goal}.json`;
    const filePath = path.join(CONTENT_DIR, filename);

    await fs.writeFile(filePath, JSON.stringify(emailTemplate, null, 2), 'utf8');

    logger.info('마케팅 이메일 템플릿 생성 완료', { product, goal });
    return res.json({
      message: '마케팅 이메일 템플릿이 성공적으로 생성되었습니다.',
      template: emailTemplate,
      filePath: filename,
      suggestions: {
        subjectAlternatives: [
          `${audience}를 위한 ${product} - 놓치지 마세요`,
          `${product}로 ${audience}의 문제를 해결하세요`,
          `${audience}님, ${product}에 대한 소식이 있습니다`
        ],
        ctaAlternatives: [
          `지금 확인하기`,
          `자세히 알아보기`,
          `시작하기`
        ]
      }
    });

  } catch (error) {
    logger.error('마케팅 이메일 생성 실패', { error: error.message });
    return res.status(500).json({ error: `마케팅 이메일을 생성할 수 없습니다: ${error.message}` });
  }
};

// 기술 문서 템플릿 생성
const generateTechnicalDocument = async (req, res) => {
  try {
    const { 
      title, 
      product, 
      audience = 'developers', 
      documentType = 'tutorial',
      topics = [],
      complexity = 'intermediate'
    } = req.body;

    if (!title || !product) {
      return res.status(400).json({ error: '문서 제목과 제품 이름이 필요합니다.' });
    }

    // 문서 유형에 따른 구조 정의
    const documentStructures = {
      tutorial: [
        { name: 'introduction', title: '소개', description: '튜토리얼의 목적과 학습 목표 설명' },
        { name: 'prerequisites', title: '사전 준비 사항', description: '필요한 지식, 도구, 환경 설정' },
        { name: 'steps', title: '단계별 가이드', description: '주요 단계를 순차적으로 설명' },
        { name: 'examples', title: '예제', description: '실제 사용 예시 코드' },
        { name: 'troubleshooting', title: '문제 해결', description: '일반적인 오류 및 해결 방법' },
        { name: 'conclusion', title: '결론', description: '요약 및 다음 단계' },
        { name: 'resources', title: '추가 자료', description: '참고 자료 및 관련 문서 링크' }
      ],
      api: [
        { name: 'overview', title: '개요', description: 'API의 목적과 주요 기능' },
        { name: 'authentication', title: '인증', description: 'API 인증 방법 및 보안' },
        { name: 'endpoints', title: '엔드포인트', description: '사용 가능한 API 엔드포인트' },
        { name: 'requests', title: '요청 형식', description: '요청 매개변수 및 형식' },
        { name: 'responses', title: '응답 형식', description: '응답 코드 및 데이터 형식' },
        { name: 'examples', title: '예제', description: '다양한 API 호출 예시' },
        { name: 'rateLimit', title: '사용량 제한', description: 'API 사용량 제한 정책' },
        { name: 'errors', title: '오류 처리', description: '오류 코드 및 메시지 설명' }
      ],
      conceptual: [
        { name: 'introduction', title: '소개', description: '개념의 중요성과 배경' },
        { name: 'definition', title: '정의', description: '주요 개념 및 용어 정의' },
        { name: 'explanation', title: '상세 설명', description: '개념에 대한 심층적인 설명' },
        { name: 'diagrams', title: '다이어그램', description: '시각적 표현 및 설명' },
        { name: 'useCases', title: '활용 사례', description: '실제 응용 사례 및 예시' },
        { name: 'bestPractices', title: '모범 사례', description: '권장 패턴 및 안티패턴' },
        { name: 'summary', title: '요약', description: '핵심 내용 요약' },
        { name: 'furtherReading', title: '추가 자료', description: '심화 학습을 위한 자료' }
      ],
      reference: [
        { name: 'introduction', title: '소개', description: '레퍼런스 목적 및 범위' },
        { name: 'glossary', title: '용어 정의', description: '주요 용어 및 정의' },
        { name: 'specifications', title: '사양', description: '기술적 사양 및 요구사항' },
        { name: 'components', title: '구성 요소', description: '주요 구성 요소 설명' },
        { name: 'syntax', title: '구문', description: '언어 또는 명령어 구문' },
        { name: 'parameters', title: '매개변수', description: '지원되는 매개변수 목록' },
        { name: 'examples', title: '예제', description: '사용 예시' },
        { name: 'appendices', title: '부록', description: '추가 참고 자료' }
      ]
    };

    // 문서 유형 확인
    if (!documentStructures[documentType]) {
      return res.status(400).json({ 
        error: '지원되지 않는 문서 유형입니다.',
        supportedTypes: Object.keys(documentStructures)
      });
    }

    // 복잡도에 따른 설정
    const complexitySettings = {
      beginner: {
        language: '초보자를 위한 쉬운 언어와 용어',
        examples: '기본적이고 간단한 예제',
        depth: '핵심 개념에 집중',
        prerequisites: '최소한의 사전 지식 필요'
      },
      intermediate: {
        language: '일반적인 기술 용어와 개념 사용',
        examples: '실제 시나리오 기반의 예제',
        depth: '주요 개념과 중급 수준의 내용',
        prerequisites: '기본 지식 보유 가정'
      },
      advanced: {
        language: '전문적인 용어와 심층적인 설명',
        examples: '복잡한 시나리오 및 고급 예제',
        depth: '심화 내용 및 내부 동작 원리',
        prerequisites: '관련 분야의 상당한 경험 필요'
      }
    };

    // 문서 템플릿 생성
    const documentTemplate = {
      title,
      product,
      audience,
      documentType,
      complexity,
      complexityGuidelines: complexitySettings[complexity] || complexitySettings.intermediate,
      topics,
      createdAt: new Date().toISOString(),
      sections: documentStructures[documentType].map(section => ({
        ...section,
        content: ''
      })),
      metadata: {
        estimatedReadingTime: '00:00',
        lastUpdated: new Date().toISOString(),
        version: '1.0.0',
        status: 'draft'
      }
    };

    // 템플릿 파일로 저장
    const filename = `techdoc_${title.replace(/\s+/g, '_').toLowerCase()}.json`;
    const filePath = path.join(CONTENT_DIR, filename);

    await fs.writeFile(filePath, JSON.stringify(documentTemplate, null, 2), 'utf8');

    logger.info('기술 문서 템플릿 생성 완료', { title, documentType });
    return res.json({
      message: '기술 문서 템플릿이 성공적으로 생성되었습니다.',
      template: documentTemplate,
      filePath: filename
    });

  } catch (error) {
    logger.error('기술 문서 템플릿 생성 실패', { error: error.message });
    return res.status(500).json({ error: `기술 문서 템플릿을 생성할 수 없습니다: ${error.message}` });
  }
};

module.exports = {
  generateContentTemplate,
  generateMarketingEmail,
  generateTechnicalDocument
};

4. 고객 지원 자동화 🎯

MCP를 활용하면 Claude가 고객 지원 작업을 자동화하여 질문 이해, 적절한 응답 제공, 감정 분석 등을 수행할 수 있습니다.

// handlers/customerSupportHandler.js - 고객 지원 자동화 핸들러
const fs = require('fs-extra');
const path = require('path');
const natural = require('natural');
const logger = require('../utils/logger');
require('dotenv').config();

// 기본 지식 베이스 디렉토리
const KNOWLEDGE_DIR = process.env.KNOWLEDGE_DIR || path.join(__dirname, '../knowledge');

// 디렉토리 생성
fs.ensureDirSync(KNOWLEDGE_DIR);

// 감정 분석기 초기화
const tokenizer = new natural.WordTokenizer();
const analyzer = new natural.SentimentAnalyzer('en', natural.PorterStemmer, 'afinn');

// 지원 요청 분석
const analyzeCustomerQuery = async (req, res) => {
  try {
    const { 
      query, 
      customerContext = {}, 
      productCategory = null
    } = req.body;

    if (!query) {
      return res.status(400).json({ error: '고객 질의가 필요합니다.' });
    }

    // 감정 분석
    const tokens = tokenizer.tokenize(query);
    const sentimentScore = analyzer.getSentiment(tokens);

    // 감정 수준 결정
    let sentiment;
    if (sentimentScore < -0.3) {
      sentiment = 'negative';
    } else if (sentimentScore > 0.3) {
      sentiment = 'positive';
    } else {
      sentiment = 'neutral';
    }

    // 주제 식별 (간단한 키워드 기반 접근법)
    const keywords = {
      billing: ['bill', 'payment', 'charge', 'refund', 'price', 'cost', 'subscription', 'cancel', 'plan'],
      technical: ['error', 'bug', 'issue', 'problem', 'crash', 'broken', 'doesn\'t work', 'help', 'how to', 'install', 'setup'],
      account: ['login', 'password', 'account', 'profile', 'settings', 'email', 'sign in', 'access'],
      product: ['feature', 'missing', 'product', 'upgrade', 'downgrade', 'version', 'function', 'capability'],
      shipping: ['delivery', 'ship', 'shipping', 'track', 'package', 'order', 'arrival', 'receive']
    };

    // 각 주제별 점수 계산
    const topicScores = Object.entries(keywords).reduce((scores, [topic, words]) => {
      scores[topic] = words.reduce((count, word) => {
        if (query.toLowerCase().includes(word.toLowerCase())) {
          return count + 1;
        }
        return count;
      }, 0);
      return scores;
    }, {});

    // 최고 점수 주제 찾기
    let primaryTopic = 'general';
    let highestScore = 0;

    Object.entries(topicScores).forEach(([topic, score]) => {
      if (score > highestScore) {
        highestScore = score;
        primaryTopic = topic;
      }
    });

    // 우선순위 결정 (감정 + 주제 기반)
    let priority = 'medium';

    if (sentiment === 'negative') {
      priority = 'high';
      if (primaryTopic === 'technical' || primaryTopic === 'billing') {
        priority = 'urgent';
      }
    } else if (primaryTopic === 'billing' || primaryTopic === 'technical') {
      priority = 'high';
    } else if (sentiment === 'positive') {
      priority = 'low';
    }

    // FAQ 매칭 검색
    const faqMatches = await findFaqMatches(query, productCategory);

    // 응답 제안 생성
    const responseTemplate = await generateResponseTemplate(query, primaryTopic, sentiment, customerContext, faqMatches);

    logger.info('고객 질의 분석 완료', { primaryTopic, sentiment, priority });
    return res.json({
      query,
      analysis: {
        sentiment: {
          score: sentimentScore,
          label: sentiment
        },
        topicScores,
        primaryTopic,
        priority
      },
      matchedFaqs: faqMatches,
      responseTemplate
    });

  } catch (error) {
    logger.error('고객 질의 분석 실패', { error: error.message });
    return res.status(500).json({ error: `고객 질의를 분석할 수 없습니다: ${error.message}` });
  }
};

// FAQ 매칭 검색
const findFaqMatches = async (query, productCategory = null) => {
  try {
    // 실제 구현에서는 벡터 검색 또는 임베딩 기반 검색을 사용하는 것이 좋음
    // 여기서는 간단한 키워드 기반 접근법 사용

    // FAQ 파일 목록 가져오기
    const faqDir = path.join(KNOWLEDGE_DIR, 'faqs');
    fs.ensureDirSync(faqDir);

    // 제품 카테고리별 FAQ 파일 선택
    let faqFilePath;
    if (productCategory && await fs.pathExists(path.join(faqDir, `faq_${productCategory}.json`))) {
      faqFilePath = path.join(faqDir, `faq_${productCategory}.json`);
    } else {
      faqFilePath = path.join(faqDir, 'faq_general.json');
    }

    // 기본 FAQ 파일이 없는 경우 생성
    if (!await fs.pathExists(faqFilePath)) {
      const defaultFaqs = [
        {
          question: '결제는 어떻게 하나요?',
          answer: '저희는 신용카드, 체크카드, 계좌이체 등 다양한 결제 방법을 제공하고 있습니다. 웹사이트의 결제 페이지에서 원하는 방법을 선택하실 수 있습니다.',
          category: 'billing',
          keywords: ['payment', 'pay', 'bill', 'credit card', 'charge']
        },
        {
          question: '계정에 로그인할 수 없습니다. 어떻게 해야 하나요?',
          answer: '로그인 페이지에서 "비밀번호 찾기" 옵션을 선택하여 비밀번호를 재설정하실 수 있습니다. 계속해서 문제가 발생한다면 고객지원팀에 문의해 주세요.',
          category: 'account',
          keywords: ['login', 'password', 'forgot', 'can\'t access', 'account']
        },
        {
          question: '배송은 얼마나 걸리나요?',
          answer: '일반 배송은 1-3 영업일, 특급 배송은 24시간 이내에 처리됩니다. 주문 확인 이메일에서 배송 추적 정보를 확인하실 수 있습니다.',
          category: 'shipping',
          keywords: ['shipping', 'delivery', 'time', 'track', 'order']
        }
      ];

      await fs.writeFile(faqFilePath, JSON.stringify(defaultFaqs, null, 2), 'utf8');
    }

    // FAQ 데이터 읽기
    const faqData = JSON.parse(await fs.readFile(faqFilePath, 'utf8'));

    // 검색어와 FAQ 항목 비교
    const queryLower = query.toLowerCase();
    const matches = faqData.map(faq => {
      // 키워드 매칭 점수 계산
      const keywordScore = faq.keywords.reduce((score, keyword) => {
        if (queryLower.includes(keyword.toLowerCase())) {
          return score + 1;
        }
        return score;
      }, 0);

      // 질문 유사성 (간단한 단어 겹침 기반)
      const questionWords = new Set(faq.question.toLowerCase().split(/\s+/));
      const queryWords = new Set(queryLower.split(/\s+/));
      const commonWords = [...queryWords].filter(word => questionWords.has(word) && word.length > 3);
      const similarityScore = commonWords.length;

      // 총 점수 계산
      const totalScore = keywordScore + similarityScore;

      return {
        ...faq,
        matchScore: totalScore
      };
    });

    // 점수에 따라 정렬 및 상위 3개 반환
    return matches
      .filter(match => match.matchScore > 0)
      .sort((a, b) => b.matchScore - a.matchScore)
      .slice(0, 3);

  } catch (error) {
    logger.error('FAQ 매칭 검색 실패', { error: error.message });
    return [];
  }
};

// 응답 템플릿 생성
const generateResponseTemplate = async (query, topic, sentiment, customerContext, faqMatches) => {
  // 주제별 응답 템플릿
  const topicTemplates = {
    billing: {
      greeting: '안녕하세요, {customer_name}님. 결제 관련 문의에 대해 도움을 드리겠습니다.',
      closing: '추가 결제 관련 문의가 있으시면 언제든지 말씀해 주세요.'
    },
    technical: {
      greeting: '안녕하세요, {customer_name}님. 기술적인 문제 해결을 도와드리겠습니다.',
      closing: '문제가 계속되면 추가 정보를 알려주시거나 실시간 기술 지원을 요청해 주세요.'
    },
    account: {
      greeting: '안녕하세요, {customer_name}님. 계정 관련 문의에 대해 답변드립니다.',
      closing: '계정 관련 추가 도움이 필요하시면 언제든지 문의해 주세요.'
    },
    product: {
      greeting: '안녕하세요, {customer_name}님. 제품 관련 문의에 감사드립니다.',
      closing: '저희 제품에 대해 추가 질문이 있으시면 언제든지 말씀해 주세요.'
    },
    shipping: {
      greeting: '안녕하세요, {customer_name}님. 배송 관련 문의에 대해 도움을 드리겠습니다.',
      closing: '배송 상태 업데이트가 필요하시면 언제든지 알려주세요.'
    },
    general: {
      greeting: '안녕하세요, {customer_name}님. 문의에 감사드립니다.',
      closing: '추가 질문이 있으시면 언제든지 말씀해 주세요.'
    }
  };

  // 감정별 응답 조정
  const sentimentAdjustments = {
    negative: {
      preface: '불편을 끼쳐드려 정말 죄송합니다. 최대한 빠르게 도움을 드리겠습니다.',
      closing: '이 문제가 신속히 해결되도록 최선을 다하겠습니다. 불편을 끼쳐드려 다시 한번 사과드립니다.'
    },
    neutral: {
      preface: '문의하신 내용에 대해 답변드립니다.',
      closing: ''
    },
    positive: {
      preface: '문의에 감사드립니다.',
      closing: '저희 서비스를 이용해 주셔서 감사합니다!'
    }
  };

  // 기본 템플릿 선택
  const topicTemplate = topicTemplates[topic] || topicTemplates.general;
  const sentimentTemplate = sentimentAdjustments[sentiment] || sentimentAdjustments.neutral;

  // 고객 이름 처리
  const customerName = customerContext.name || '고객';

  // FAQ 매치가 있으면 답변 포함
  let faqContent = '';
  if (faqMatches.length > 0) {
    const bestMatch = faqMatches[0];
    faqContent = `\n\n${bestMatch.answer}`;

    if (faqMatches.length > 1) {
      faqContent += '\n\n관련 FAQ:';
      for (let i = 1; i < faqMatches.length; i++) {
        faqContent += `\n- ${faqMatches[i].question}`;
      }
    }
  }

  // 응답 템플릿 조합
  const responseTemplate = {
    greeting: topicTemplate.greeting.replace('{customer_name}', customerName),
    preface: sentimentTemplate.preface,
    mainContent: faqContent || '\n\n[문의 내용에 따른 맞춤 응답 필요]',
    closing: sentimentTemplate.closing + ' ' + topicTemplate.closing,
    signature: '\n\n감사합니다,\n고객 지원팀 드림'
  };

  // 전체 응답 조합
  responseTemplate.fullResponse = 
    responseTemplate.greeting + '\n\n' + 
    responseTemplate.preface + 
    responseTemplate.mainContent + '\n\n' + 
    responseTemplate.closing + 
    responseTemplate.signature;

  return responseTemplate;
};

module.exports = {
  analyzeCustomerQuery
};

MCP와 Claude를 활용한 실전 업무 자동화 구현 🏆

이제 위에서 구현한 기능들을 Claude와 통합하여 완전한 업무 자동화 솔루션을 구현해 봅시다. 다음은 Claude가 여러 업무 자동화 작업을 수행할 수 있게 하는 메인 서버 파일의 통합 예시입니다:

// index.js - 모든 업무 자동화 핸들러를 통합한 메인 서버 파일
const express = require('express');
const cors = require('cors');
const path = require('path');
const logger = require('./utils/logger');
const { authenticateRequest } = require('./utils/auth');
const { checkConnection } = require('./utils/database');
const { apiLimiter } = require('./utils/rateLimiter');

// 핸들러 모듈 불러오기
const fileHandler = require('./handlers/fileHandler');
const searchHandler = require('./handlers/searchHandler');
const databaseHandler = require('./handlers/databaseHandler');
const weatherHandler = require('./handlers/weatherHandler');
const dataAnalysisHandler = require('./handlers/dataAnalysisHandler');
const githubHandler = require('./handlers/githubHandler');
const contentHandler = require('./handlers/contentHandler');
const customerSupportHandler = require('./handlers/customerSupportHandler');

require('dotenv').config();

// Express 앱 생성
const app = express();
const PORT = process.env.PORT || 3000;

// 미들웨어 설정
app.use(cors());
app.use(express.json({ limit: '50mb' }));
app.use(express.urlencoded({ extended: true, limit: '50mb' }));

// 서버 상태 확인 엔드포인트 (인증 필요 없음)
app.get('/health', (req, res) => {
  res.json({ status: 'ok', timestamp: new Date().toISOString() });
});

// 서버 정보 엔드포인트 (MCP 사양 정보)
app.get('/.well-known/mcp-server-info', (req, res) => {
  res.json({
    name: "Claude 업무 자동화 MCP 서버",
    version: "1.0.0",
    description: "Claude를 활용한 다양한 업무 자동화 기능을 제공하는 MCP 서버",
    capabilities: [
      "file_operations",
      "web_search",
      "database_operations",
      "weather_api",
      "data_analysis",
      "github_integration",
      "content_generation",
      "customer_support"
    ],
    endpoints: {
      // 파일 시스템 엔드포인트
      "file/info": "파일 정보 조회",
      "file/read": "파일 읽기",
      "file/write": "파일 쓰기",
      "file/list": "디렉토리 목록 조회",
      
      // 검색 엔드포인트
      "search/web": "웹 검색 수행",
      
      // 데이터베이스 엔드포인트
      "database/status": "데이터베이스 연결 상태 확인",
      "database/users": "사용자 목록 조회",
      "database/users/get": "사용자 상세 정보 조회",
      "database/users/create": "사용자 생성",
      "database/users/update": "사용자 정보 업데이트",
      "database/notes": "노트 목록 조회",
      "database/notes/create": "노트 생성",
      "database/notes/update": "노트 업데이트",
      "database/notes/delete": "노트 삭제",
      
      // 날씨 API 엔드포인트
      "api/weather/current": "현재 날씨 정보 조회",
      "api/weather/forecast": "일기 예보 조회",
      
      // 데이터 분석 엔드포인트
      "analysis/csv": "CSV 파일 분석",
      "analysis/transform": "데이터 변환 요약",
      
      // GitHub 통합 엔드포인트
      "github/code-review": "코드 리뷰 분석",
      
      // 콘텐츠 생성 엔드포인트
      "content/template": "콘텐츠 템플릿 생성",
      "content/email": "마케팅 이메일 생성",
      "content/techdoc": "기술 문서 템플릿 생성",
      
      // 고객 지원 엔드포인트
      "support/analyze-query": "고객 질의 분석"
    }
  });
});

// 서버 시작 시 데이터베이스 연결
(async () => {
  try {
    await checkConnection();
    logger.info('데이터베이스가 성공적으로 연결되었습니다.');
  } catch (error) {
    logger.warn('데이터베이스 연결에 실패했습니다.', { error: error.message });
  }
})();

// ===== 파일 시스템 엔드포인트 =====
app.post('/file/info', authenticateRequest, fileHandler.getFileInfo);
app.post('/file/read', authenticateRequest, fileHandler.readFile);
app.post('/file/write', authenticateRequest, fileHandler.writeFile);
app.post('/file/list', authenticateRequest, fileHandler.listDirectory);

// ===== 검색 엔드포인트 =====
app.post('/search/web', authenticateRequest, apiLimiter, searchHandler.performWebSearch);

// ===== 데이터베이스 엔드포인트 =====
app.get('/database/status', authenticateRequest, databaseHandler.checkDatabaseConnection);
app.post('/database/users', authenticateRequest, databaseHandler.getUsers);
app.post('/database/users/get', authenticateRequest, databaseHandler.getUserById);
app.post('/database/users/create', authenticateRequest, databaseHandler.createUser);
app.post('/database/users/update', authenticateRequest, databaseHandler.updateUser);
app.post('/database/notes', authenticateRequest, databaseHandler.getNotes);
app.post('/database/notes/create', authenticateRequest, databaseHandler.createNote);
app.post('/database/notes/update', authenticateRequest, databaseHandler.updateNote);
app.post('/database/notes/delete', authenticateRequest, databaseHandler.deleteNote);

// ===== 날씨 API 엔드포인트 =====
app.post('/api/weather/current', authenticateRequest, apiLimiter, weatherHandler.getCurrentWeather);
app.post('/api/weather/forecast', authenticateRequest, apiLimiter, weatherHandler.getWeatherForecast);

// ===== 데이터 분석 엔드포인트 =====
app.post('/analysis/csv', authenticateRequest, dataAnalysisHandler.analyzeCSVFile);
app.post('/analysis/transform', authenticateRequest, dataAnalysisHandler.summarizeDataTransformation);

// ===== GitHub 통합 엔드포인트 =====
app.post('/github/code-review', authenticateRequest, apiLimiter, githubHandler.analyzeCodeForReview);

// ===== 콘텐츠 생성 엔드포인트 =====
app.post('/content/template', authenticateRequest, contentHandler.generateContentTemplate);
app.post('/content/email', authenticateRequest, contentHandler.generateMarketingEmail);
app.post('/content/techdoc', authenticateRequest, contentHandler.generateTechnicalDocument);

// ===== 고객 지원 엔드포인트 =====
app.post('/support/analyze-query', authenticateRequest, customerSupportHandler.analyzeCustomerQuery);

// ===== 오류 처리 미들웨어 =====
app.use((err, req, res, next) => {
  logger.error('서버 오류', { error: err.message, stack: err.stack });
  res.status(500).json({ error: '서버 내부 오류가 발생했습니다.' });
});

// ===== 404 Not Found 처리 =====
app.use((req, res) => {
  logger.warn('존재하지 않는 엔드포인트 접근', { path: req.originalUrl });
  res.status(404).json({ error: '요청한 리소스를 찾을 수 없습니다.' });
});

// ===== 서버 시작 =====
app.listen(PORT, () => {
  logger.info(`MCP 서버가 포트 ${PORT}에서 실행 중입니다.`);
  console.log(`MCP 서버가 포트 ${PORT}에서 실행 중입니다.`);
  console.log(`서버 정보: http://localhost:${PORT}/.well-known/mcp-server-info`);
  console.log(`건강 상태 확인: http://localhost:${PORT}/health`);
});

Claude를 활용한 업무 자동화 실전 활용 가이드 🌟

지금까지 MCP 서버를 통해 Claude가 다양한 비즈니스 환경에서 활용할 수 있는 기능들을 구현해보았습니다. 이제 이러한 기능들을 실제 업무에 적용하는 방법을 알아보겠습니다.

실제 업무 시나리오 예시

1. 데이터 분석 워크플로우 🔍

마케팅 팀이 매월 판매 데이터를 분석하고 보고서를 작성해야 하는 상황입니다. Claude와 MCP를 활용하면 다음과 같이 자동화할 수 있습니다:

  1. Claude에게 "이번 달 판매 데이터를 분석하고 보고서를 작성해줘"라고 요청합니다.
  2. Claude는 MCP를 통해 CSV 파일에 접근하여 데이터를 분석합니다.
  3. 판매 트렌드, 인사이트, 이상치 등을 자동으로 식별합니다.
  4. 시각적으로 매력적인 보고서를 자동으로 생성하고 파일로 저장합니다.
  5. 필요에 따라 이메일로 보고서를 배포할 수도 있습니다.

전체 과정이 5분 이내에 완료되어, 기존에 수 시간이 걸리던 작업이 크게 단축됩니다.

2. 개발자 코드 리뷰 지원 👨‍💻

소프트웨어 개발 팀에서 코드 리뷰 과정을 개선하고자 합니다. Claude와 MCP를 활용하면:

  1. 개발자가 풀 리퀘스트(PR)를 생성하면 Claude에게 "PR #123을 리뷰해줘"라고 요청합니다.
  2. Claude는 MCP의 GitHub 통합 기능을 사용해 코드 변경사항에 접근합니다.
  3. 자동으로 코드 품질, 잠재적 버그, 보안 이슈를 분석합니다.
  4. 개선 제안과 코드 스타일 가이드라인 준수 여부를 포함한 상세 리뷰를 제공합니다.
  5. 필요시 개선된 코드를 직접 제안할 수도 있습니다.

이를 통해 리뷰 품질을 높이고 개발자 간 지식 공유를 촉진하며, 시니어 개발자의 리뷰 부담을 줄일 수 있습니다.

3. 마케팅 콘텐츠 생성 파이프라인 ✍️

마케팅 팀이 다양한 채널에 맞는 콘텐츠를 지속적으로 생성해야 합니다. Claude와 MCP를 활용하면:

  1. 마케팅 담당자가 "다음 주 이메일 뉴스레터와 소셜 미디어 포스트를 작성해줘"라고 요청합니다.
  2. Claude는 MCP를 통해 제품 정보, 브랜드 가이드라인, 이전 캠페인 데이터에 접근합니다.
  3. 타겟 고객층에 맞춘 개인화된 이메일 뉴스레터를 생성합니다.
  4. 각 소셜 미디어 플랫폼의 특성에 맞는 포스트도 함께 작성합니다.
  5. 콘텐츠를 검토하고 필요한 수정을 적용한 뒤 최종 버전을 파일로 저장합니다.

이를 통해 콘텐츠 생산 시간을 90%까지 단축하고, 일관된 브랜드 메시지를 유지할 수 있습니다.

4. 고객 지원 개인화 및 효율화 🎯

고객 지원 팀이 늘어나는 문의량을 효율적으로 처리해야 합니다. Claude와 MCP를 활용하면:

  1. 고객 문의가 접수되면 Claude에게 "이 고객 문의를 분석하고 응답 초안을 작성해줘"라고 요청합니다.
  2. Claude는 MCP를 통해 고객 이력, 제품 정보, FAQ 데이터베이스에 접근합니다.
  3. 문의 내용을 분석하여 우선순위를 매기고, 감정 상태를 파악합니다.
  4. 개인화된 응답 초안을 작성하고, 필요시 추가 정보나 리소스를 추천합니다.
  5. 고객 지원 담당자가 최종 검토 후 응답을 발송합니다.

이를 통해 응답 시간을 단축하고, 고객 만족도를 높이며, 지원 팀의 스트레스를 줄일 수 있습니다.

구현 시 고려사항 🔧

업무 자동화 솔루션을 성공적으로 구현하기 위해 다음 사항을 고려해야 합니다:

  1. 보안 및 개인정보: 민감한 데이터 처리 시 엄격한 보안 조치를 적용하세요. 암호화, 접근 제어, 감사 로깅을 구현하고, 규제 준수 여부를 확인하세요.
  2. 확장성: 시스템이 증가하는 요청 볼륨을 처리할 수 있도록 설계하세요. 로드 밸런싱, 캐싱, 데이터베이스 최적화를 고려하세요.
  3. 유지보수성: 코드를 모듈화하고 잘 문서화하여 향후 유지보수를 용이하게 하세요. 자동화된 테스트를 구현하고 CI/CD 파이프라인을 설정하세요.
  4. 사용자 경험: 최종 사용자가 시스템과 쉽게 상호작용할 수 있도록 직관적인 인터페이스를 제공하세요. 사용자 피드백을 수집하고 지속적으로 개선하세요.
  5. 비용 효율성: API 호출, 서버 리소스, 스토리지 등의 비용을 모니터링하고 최적화하세요. 사용량 기반 스케일링을 고려하세요.

마무리 및 다음 단계 🚀

이번 포스팅 시리즈를 통해 Claude와 MCP를 활용한 다양한 업무 자동화 방법을 살펴보았습니다. 우리는 기본 MCP 설정부터 시작하여 서버 구현, 확장, 그리고 실제 업무 환경에서의 활용까지 모든 단계를 다루었습니다.

이제 여러분은 다음과 같은 역량을 갖추게 되었습니다:

  • MCP 서버를 설계하고 구현하는 방법
  • 다양한 외부 시스템과 통합하는 방법
  • 실제 비즈니스 문제를 해결하기 위한 자동화 솔루션을 개발하는 방법

다음 단계로 고려할 수 있는 사항:

  1. 사용자 인터페이스 개발: 웹 또는 데스크톱 인터페이스를 통해 비기술 사용자도 쉽게 접근할 수 있는 솔루션으로 확장하세요.
  2. 자동화 워크플로우 구축: 여러 MCP 기능을 연결하여 end-to-end 자동화 워크플로우를 구현하세요.
  3. 피드백 루프 통합: 사용자 피드백과 결과를 수집하여 시스템을 지속적으로 개선하는 메커니즘을 구축하세요.
  4. 확장된 통합: ERP, CRM, 프로젝트 관리 도구 등 추가 비즈니스 시스템과의 통합을 개발하세요.
  5. AI 기능 확장: 이미지 처리, 음성 인식, 자연어 처리 등 추가 AI 기능을 통합하세요.

MCP와 Claude를 활용한 업무 자동화는 아직 초기 단계에 있지만, 이미 많은 기업에서 생산성 향상과 비용 절감을 실현하고 있습니다. 이 기술의 발전과 함께 더 많은 혁신적인 응용 사례가 나타날 것입니다.

여러분의 비즈니스에 맞는 자동화 솔루션을 구축하여 AI 시대의 경쟁 우위를 확보하세요!

이 포스팅이 Claude와 MCP를 통한 업무 자동화의 가능성을 이해하는 데 도움이 되었기를 바랍니다. 질문이나 의견, 성공 사례가 있으시면 댓글로 공유해 주세요! 😊