포스트

[MCP] Blog Generator MCP 개발기 — AI로 기술 블로그를 자동 생성하는 MCP 서버 만들기

[MCP] Blog Generator MCP 개발기 — AI로 기술 블로그를 자동 생성하는 MCP 서버 만들기

[MCP] Blog Generator MCP 개발 심층 분석: AI로 기술 블로그 자동 생성 서버 구축기

안녕하세요. 백엔드 개발자 정지원입니다. 현재 AI를 활용한 개발 자동화 도구 개발에 몰두하고 있습니다. 이 글은 Blog Generator MCP의 설계부터 6번의 메이저 버전 업그레이드까지의 여정을 심층적으로 다룹니다. 재미있는 점은, 이 글 또한 MCP의 도움을 받아 작성되었다는 것입니다!

목차

  1. 배경 및 문제 정의: 블로그, 쓰고는 싶은데…
  2. MCP란 무엇인가: Anthropic의 오픈 프로토콜 활용
  3. 전체 아키텍처: 10개의 도구, Standard/Pro Mode
  4. 버전 진화: 좌충우돌 성장기
  5. 주요 학습 내용: 개발하며 얻은 인사이트
  6. MCP 서버 만들기: 핵심 코드 살펴보기
  7. 결론 및 회고: 앞으로의 계획
  8. 참고 자료

1. 배경 및 문제 정의: 블로그, 쓰고는 싶은데…

기술 블로그를 운영하는 것은 개발자에게 여러모로 유익합니다. 자신의 지식을 정리하고 공유하며, 다른 개발자들과 소통할 수 있는 기회를 제공합니다. 하지만 현실은 녹록지 않습니다.

블로그 운영의 어려움구체적 상황결과
시간 부족업무 + 코딩 + 학습으로 글 쓸 여유 없음블로그 방치
글쓰기 진입 장벽빈 에디터 앞에서 막막함시작조차 못함
품질 고민“이 정도로 올려도 될까?” 고민완성 후에도 게시 망설임
반복 작업frontmatter, 이미지, 태그 등 매번 수동 설정귀찮음 누적

💡 핵심 동기: 개발 과정에서 이미 코드를 작성하고, 의사결정을 하고, 문제를 해결하고 있습니다. 이 과정을 자동으로 블로그 글로 변환할 수 있다면?

이러한 문제를 해결하기 위해, AI를 활용하여 기술 블로그를 자동으로 생성해주는 도구를 개발하기로 결심했습니다. 그것이 바로 Blog Generator MCP의 시작입니다.

flowchart LR
    A[개발자의 일상] --> B{블로그 글감}
    B --> C[코드 변경사항]
    B --> D[문제 해결 경험]
    B --> E[기술 학습 메모]
    B --> F[Notion 문서]
    C & D & E & F --> G[Blog Generator MCP]
    G --> H[고품질 기술 블로그]

    style G fill:#ccf,stroke:#333,stroke-width:2px
    style H fill:#9f9,stroke:#333,stroke-width:2px

2. MCP란 무엇인가: Anthropic의 오픈 프로토콜 활용

MCP(Model Context Protocol)는 Anthropic에서 공개한 오픈 프로토콜로, AI 모델이 외부 서비스와 상호작용할 수 있도록 표준화된 인터페이스를 제공합니다.

flowchart TD
    subgraph Client["MCP 클라이언트 (Claude Desktop / Claude Code)"]
        A[AI 모델 - Claude]
    end

    subgraph Server["MCP 서버 (Blog Generator)"]
        B[Tool 1: blog_start_draft]
        C[Tool 2: blog_get_status]
        D[Tool 3: blog_apply_feedback]
        E[Tool 4: blog_finalize_draft]
        F[Tool 5: blog_save]
        G[Tool 6: blog_deploy_github]
        H[Tool 7: blog_start_review]
        I[Tool 8: blog_start_draft_pro]
        J[Tool 9: blog_apply_feedback_pro]
        K[Tool 10: blog_apply_review_feedback]
    end

    subgraph External["외부 서비스"]
        L[Gemini API]
        M[Anthropic API]
        N[GitHub API]
        O[Notion API]
    end

    A <-->|stdio / HTTP| Server
    Server --> L & M & N & O

    style Client fill:#f9f,stroke:#333,stroke-width:2px
    style Server fill:#ccf,stroke:#333,stroke-width:2px
    style External fill:#ffc,stroke:#333,stroke-width:2px

MCP의 핵심 개념

개념설명Blog Generator에서의 활용
ToolAI가 호출할 수 있는 함수blog_start_draft, blog_save 등 10개 도구
Transport클라이언트-서버 통신 방식stdio (로컬) / HTTP (원격)
Schema도구의 입출력 정의Zod 스키마로 타입 안전성 확보
ResourceAI에게 제공하는 데이터작업 상태, 초안 내용 등

📝 참고: MCP는 단순히 API를 호출하는 것이 아니라, AI 모델이 “도구를 사용하는 방법”을 이해할 수 있도록 설계되었습니다. Zod 스키마의 description 필드가 AI에게 도구 사용법을 설명하는 핵심 역할을 합니다.

3. 전체 아키텍처: 10개의 도구, Standard/Pro Mode

MCP는 현재 10개의 도구로 구성되어 있으며, 크게 Standard ModePro Mode 두 가지 모드를 제공합니다.

flowchart TD
    subgraph Input["입력 유형"]
        I1[keyword - 키워드/주제]
        I2[code - 코드 스니펫]
        I3[memo - 메모/노트]
        I4[git_push - Git 변경사항]
        I5[notion - Notion 페이지]
    end

    subgraph Standard["Standard Mode (Gemini)"]
        S1[blog_start_draft] --> S2[blog_get_status]
        S2 --> S3{피드백?}
        S3 -->|Yes| S4[blog_apply_feedback]
        S4 --> S2
        S3 -->|No| S5[blog_start_review]
        S5 --> S6[blog_apply_review_feedback]
        S6 --> S7[blog_finalize_draft]
    end

    subgraph Pro["Pro Mode (Claude Opus)"]
        P1[blog_start_draft_pro] --> P2[blog_get_status]
        P2 --> P3{피드백?}
        P3 -->|Yes| P4[blog_apply_feedback_pro]
        P4 --> P2
        P3 -->|No| P5[blog_finalize_draft]
    end

    subgraph Output["출력"]
        O1[blog_save - 로컬 저장]
        O2[blog_deploy_github - GitHub 배포]
    end

    Input --> S1 & P1
    S7 & P5 --> O1 & O2

    style Standard fill:#f9f,stroke:#333,stroke-width:2px
    style Pro fill:#ccf,stroke:#333,stroke-width:2px

Standard Mode vs Pro Mode 비교

특성Standard ModePro Mode
AI 모델Google Gemini (Flash/Pro)Anthropic Claude Opus
입력 방식keyword, code, memo, git_push, notioncode_diff + dev_log
글 스타일tutorial, til, deep-dive, troubleshootingdeep-dive 중심
웹 검색선택적 활성화기본 활성화
검수 기능blog_start_review로 별도 검수작성 시 자체 검수
비용Gemini API 비용 (저렴)Anthropic API 비용 (고가)
품질양호최상급
속도빠름 (Flash 기준 30초~1분)느림 (2~5분)

4. 버전 진화: 좌충우돌 성장기

MCP는 처음부터 완벽한 모습이 아니었습니다. 수많은 시행착오와 개선을 거쳐 현재의 모습으로 발전해왔습니다.

timeline
    title Blog Generator MCP 버전 히스토리
    v1.0 : 기본 생성기 4 도구
         : Initial commit
    v2.0 : 인터랙티브 워크플로우
         : sql.js 상태 관리
         : Zod 스키마
    v3.0 : Pro Mode 도입
         : Gemini 분석 + Claude 작성
    v3.1 : instructions_file
         : 사용자 정의 작성 지침
    v3.2 : 환경 변수 지원
         : API 키 안전 관리
    v4.1 : Claude Opus 단일 파이프라인
         : 웹 검색 + 썸네일 자동화

v1.0: 기본 생성기 — 4개의 도구로 시작

MCP의 첫 번째 버전은 가장 기본적인 파이프라인을 구현했습니다.

sequenceDiagram
    participant User as 사용자
    participant MCP as MCP Server
    participant Gemini as Gemini API

    User->>MCP: blog_start_draft(keyword, content)
    MCP->>Gemini: 블로그 초안 생성 요청
    Gemini-->>MCP: 초안 반환
    MCP-->>User: task_id 반환

    User->>MCP: blog_get_status(task_id)
    MCP-->>User: 진행률 + 결과

    User->>MCP: blog_apply_feedback(task_id, feedback)
    MCP->>Gemini: 피드백 반영 요청
    Gemini-->>MCP: 수정된 초안
    MCP-->>User: 수정 완료

    User->>MCP: blog_save(task_id)
    MCP-->>User: 마크다운 파일 저장 완료

이 시점의 핵심 도전 과제는 장시간 소요되는 AI 작업을 어떻게 처리할 것인가였습니다. MCP 도구 호출은 기본적으로 동기적이지만, Gemini API 호출은 수십 초가 걸릴 수 있습니다.

v2.0: 인터랙티브 워크플로우, sql.js, Zod 스키마

v2.0에서는 사용자 경험을 크게 개선했습니다.

기능도입 이유구현 방식
sql.js작업 상태 영속화 필요서버리스 SQLite, 메모리 DB
백그라운드 작업AI 호출 대기 시간 해결비동기 실행 + 폴링
Zod 스키마타입 안전성 + AI UX입력 검증 + description
인터랙티브 피드백사용자 개입도 향상피드백 히스토리 관리
다중 입력 유형다양한 글감 지원keyword, code, memo, git_push

sql.js를 선택한 이유:

flowchart TD
    A{DB 선택} --> B[PostgreSQL/MySQL]
    A --> C[SQLite]
    A --> D[sql.js]
    A --> E[인메모리 Map]

    B --> B1[❌ 서버 필요, 과도한 복잡도]
    C --> C1[❌ 네이티브 바인딩, 설치 문제]
    D --> D1[✅ 순수 JS, 제로 의존성, WASM]
    E --> E1[❌ 프로세스 재시작 시 데이터 유실]

    style D1 fill:#9f9,stroke:#333,stroke-width:2px

v3.0: Pro Mode — Gemini 분석 + Claude 작성

v3.0에서 가장 큰 변화는 AI 분업 패턴의 도입이었습니다.

sequenceDiagram
    participant User as 사용자
    participant MCP as MCP Server
    participant Gemini as Gemini API
    participant Claude as Claude API

    User->>MCP: blog_start_draft_pro(code_diff, dev_log)
    MCP->>Gemini: 코드 분석 요청
    Note right of Gemini: 코드 구조 분석<br/>핵심 변경사항 추출<br/>기술적 인사이트 도출
    Gemini-->>MCP: 분석 결과 반환

    MCP->>Claude: 블로그 작성 요청 (분석 결과 포함)
    Note right of Claude: 분석 결과 기반 글 작성<br/>코드 예시 포함<br/>Mermaid 다이어그램 생성
    Claude-->>MCP: 완성된 블로그 초안
    MCP-->>User: task_id 반환

🔥 핵심 인사이트: Gemini는 대용량 코드 분석에 강하고, Claude는 구조화된 글쓰기에 강합니다. 각 모델의 장점을 조합하면 단일 모델보다 훨씬 높은 품질의 결과물을 얻을 수 있었습니다.

v3.1 ~ v3.2: instructions_file과 환경 변수

버전기능해결한 문제
v3.1instructions_file 파라미터매번 스타일 가이드를 복붙하는 번거로움 해소
v3.2환경 변수 지원API 키를 파라미터로 전달하는 보안 위험 제거

instructions_file은 마크다운 파일 경로를 지정하면 해당 파일의 내용을 작성 지침으로 사용합니다. 이를 통해 팀 단위의 글쓰기 스타일 가이드를 공유하고, 일관된 품질의 블로그 글을 생성할 수 있게 되었습니다.

v4.1: Claude Opus 단일 파이프라인

최신 버전에서는 Claude Opus의 성능 향상으로 분석과 작성을 하나의 모델에서 처리하도록 변경되었습니다.

비교 항목v3.0 (Gemini + Claude)v4.1 (Claude Opus 단일)
API 호출 횟수2회 (분석 + 작성)1회
총 소요 시간3~5분2~3분
컨텍스트 유실분석→작성 전달 시 일부 유실없음 (단일 컨텍스트)
비용Gemini + ClaudeClaude만
글 품질우수최우수

5. 주요 학습 내용: 개발하며 얻은 인사이트

5.1 Zod 스키마 = MCP UX

Zod 스키마를 어떻게 정의하느냐가 MCP의 사용자 경험을 결정합니다. AI 모델은 스키마의 description을 읽고 도구를 어떻게 사용할지 결정하기 때문입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ❌ 나쁜 예 - AI가 이해하기 어려움
const BadSchema = z.object({
  type: z.string(),
  data: z.string(),
});

// ✅ 좋은 예 - AI가 정확하게 이해
const GoodSchema = z.object({
  input_type: z.enum(["keyword", "code", "memo", "git_push", "notion"])
    .describe("입력 유형: keyword(키워드/주제), code(코드 스니펫), memo(메모/노트), git_push(git 변경사항), notion(Notion 페이지 URL)"),
  content: z.string()
    .min(1).max(50000)
    .describe("블로그 글 생성에 사용할 입력 내용"),
  style: z.enum(["tutorial", "til", "deep-dive", "troubleshooting"])
    .default("tutorial")
    .describe("블로그 글 스타일"),
});

💡 핵심 포인트: describe()에 작성하는 설명은 사용자가 아닌 AI 모델을 위한 것입니다. AI가 어떤 상황에서 이 도구를 사용해야 하는지, 각 파라미터에 어떤 값을 넣어야 하는지 명확히 안내해야 합니다.

5.2 백그라운드 작업 + 폴링 패턴

AI API 호출은 수십 초가 걸릴 수 있습니다. MCP 도구 호출 시 즉시 응답하고, 백그라운드에서 작업을 처리한 뒤 폴링으로 상태를 확인하는 패턴을 사용했습니다.

sequenceDiagram
    participant Client as Claude (클라이언트)
    participant MCP as MCP Server
    participant DB as sql.js DB
    participant AI as Gemini/Claude API

    Client->>MCP: blog_start_draft(...)
    MCP->>DB: 작업 생성 (status: pending)
    MCP-->>Client: { task_id, status: "pending" }

    Note over MCP,AI: 백그라운드 실행
    MCP->>AI: AI 호출 (비동기)
    MCP->>DB: status: in_progress, progress: 30
    AI-->>MCP: 결과 반환
    MCP->>DB: status: completed, result: draft

    Client->>MCP: blog_get_status(task_id)
    MCP->>DB: 상태 조회
    DB-->>MCP: { status, progress, result }
    MCP-->>Client: 작업 완료 + 결과

5.3 에러 메시지에 해결 방법 제시

MCP 도구에서 에러가 발생했을 때, 단순히 에러 내용만 표시하는 것이 아니라 해결 방법까지 함께 제시하면 AI가 자동으로 문제를 해결할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
// ❌ 나쁜 에러 메시지
throw new Error("API key invalid");

// ✅ 좋은 에러 메시지 - AI가 사용자에게 안내 가능
throw new Error(
  "Gemini API 키가 유효하지 않습니다. " +
  "다음 방법으로 해결할 수 있습니다:\n" +
  "1. GEMINI_API_KEY 환경변수가 올바르게 설정되어 있는지 확인\n" +
  "2. Google AI Studio에서 새 API 키 발급: https://aistudio.google.com/\n" +
  "3. claude_desktop_config.json의 env 섹션에 키 추가"
);

5.4 환경 변수의 중요성

관리 방식보안편의성적용
파라미터 직접 전달❌ 대화 기록에 노출❌ 매번 입력v1.0~v3.1
환경 변수✅ 프로세스 내부에서만 접근✅ 한번 설정으로 영구v3.2+
.env 파일⚠️ 실수로 커밋 가능✅ 편리지원

6. MCP 서버 만들기: 핵심 코드 살펴보기

서버 초기화

MCP 서버는 @modelcontextprotocol/sdk를 사용하여 구축됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

// 1. 서버 인스턴스 생성
const server = new McpServer({
  name: "blog-generator",
  version: "4.1.0",
});

// 2. 도구 등록
registerStartDraftTool(server);      // blog_start_draft
registerGetStatusTool(server);       // blog_get_status
registerApplyFeedbackTool(server);   // blog_apply_feedback
registerFinalizeDraftTool(server);   // blog_finalize_draft
registerSaveBlogTool(server);        // blog_save
registerDeployGithubTool(server);    // blog_deploy_github
registerStartReviewTool(server);     // blog_start_review
registerStartDraftProTool(server);   // blog_start_draft_pro
registerApplyFeedbackProTool(server);// blog_apply_feedback_pro
registerApplyReviewFeedbackTool(server); // blog_apply_review_feedback

// 3. Transport 연결 및 실행
const transport = new StdioServerTransport();
await server.connect(transport);

도구 등록 패턴

각 도구는 server.registerTool() 또는 server.tool()로 등록합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import { z } from "zod";

server.tool(
  "blog_start_draft",  // 도구 이름
  {
    // Zod 스키마로 입력 정의
    input_type: z.enum(["keyword", "code", "memo", "git_push", "notion"])
      .describe("입력 유형"),
    content: z.string().min(1).max(50000)
      .describe("블로그 글 생성에 사용할 입력 내용"),
    style: z.enum(["tutorial", "til", "deep-dive", "troubleshooting"])
      .default("tutorial")
      .describe("블로그 글 스타일"),
    model: z.enum(["gemini-1.5-flash", "gemini-1.5-pro", "gemini-2.0-flash"])
      .default("gemini-1.5-flash")
      .describe("사용할 Gemini 모델"),
    web_search: z.boolean().default(false)
      .describe("웹 검색 활용 여부"),
  },
  async (params) => {
    // 1. 작업 생성
    const taskId = generateTaskId();
    await createTask(taskId, params);

    // 2. 백그라운드 실행
    generateDraftInBackground(taskId, params);

    // 3. 즉시 응답
    return {
      content: [{
        type: "text",
        text: JSON.stringify({
          task_id: taskId,
          status: "pending",
          message: "블로그 생성이 시작되었습니다."
        })
      }]
    };
  }
);

프로젝트 구조

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
blog-generator-mcp/
├── src/
│   ├── index.ts              # 서버 진입점
│   ├── types.ts              # Zod 스키마 + 타입 정의
│   ├── tools/
│   │   ├── startDraft.ts     # Standard Mode 초안 생성
│   │   ├── startDraftPro.ts  # Pro Mode 초안 생성
│   │   ├── getStatus.ts      # 작업 상태 조회
│   │   ├── applyFeedback.ts  # 피드백 반영
│   │   ├── finalizeDraft.ts  # 초안 확정
│   │   ├── saveBlog.ts       # 로컬 저장
│   │   ├── deployGithub.ts   # GitHub 배포
│   │   └── startReview.ts    # 검수
│   └── services/
│       ├── database.ts       # sql.js DB 관리
│       ├── gemini.ts         # Gemini API 클라이언트
│       ├── claude.ts         # Claude API 클라이언트
│       ├── github.ts         # GitHub API 클라이언트
│       ├── env.ts            # 환경 변수 관리
│       ├── instructions.ts   # 작성 지침 로더
│       └── thumbnail.ts      # 썸네일 이미지 검색
├── data/
│   └── tasks.db              # sql.js 데이터베이스
├── posts/                    # 생성된 블로그 글 저장
├── example-instructions.md   # 기본 작성 지침
├── package.json
└── tsconfig.json

7. 결론 및 회고: 앞으로의 계획

Blog Generator MCP는 “블로그 쓰기가 귀찮다”는 단순한 불편함에서 시작하여, 6번의 메이저 버전 업그레이드를 거치며 10개의 도구를 갖춘 본격적인 AI 블로그 자동화 시스템으로 성장했습니다.

개발을 통해 배운 점

학습 포인트내용
MCP 설계Zod 스키마의 description이 AI UX를 결정한다
AI 분업각 모델의 강점을 조합하면 단일 모델보다 나은 결과를 얻는다
비동기 패턴백그라운드 작업 + 폴링이 장시간 AI 작업의 해법이다
에러 설계에러 메시지에 해결 방법을 포함하면 AI가 자동 복구할 수 있다
환경 변수민감 정보는 반드시 환경 변수로 관리해야 한다
지침 파일글쓰기 스타일을 파일로 외부화하면 품질 일관성을 유지할 수 있다

향후 계획

flowchart LR
    A[현재 v4.1] --> B[v5.0 계획]
    B --> C[다중 AI 모델 통합]
    B --> D[이미지 자동 생성]
    B --> E[시리즈 글 관리]
    B --> F[SEO 자동 최적화]
    B --> G[다국어 지원]

    style A fill:#9f9,stroke:#333,stroke-width:2px
    style B fill:#ccf,stroke:#333,stroke-width:2px
  • 다중 AI 모델 통합: OpenAI GPT, Google Gemini, Anthropic Claude를 상황에 맞게 자동 선택
  • 이미지 자동 생성: DALL-E, Midjourney 등을 활용한 블로그 삽화 자동 생성
  • 시리즈 글 관리: 연재물의 이전/다음 편 자동 연결 및 목차 관리
  • SEO 자동 최적화: 키워드 분석, 메타 태그 자동 생성, 내부 링크 추천

8. 참고 자료

📚 공식 문서

📝 기술 블로그

🎓 튜토리얼 가이드


이 글은 Blog Generator MCP의 도움을 받아 작성되었습니다. 궁금한 점이나 피드백이 있다면 언제든지 댓글로 남겨주세요!

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.