MCP 문서 관리

메뉴

문서 정보

최종 수정일:
2025-05-13 01:26

GPT with Obsidian Save Flask App

ChatGPT 대화 내용을 Obsidian에 저장하는 Flask 앱 개발 문서

📋 개요

이 문서는 Playwright와 MCP(Model Context Protocol)를 활용해 ChatGPT 대화 내용을 Obsidian의 .md 파일로 저장하는 Flask 웹 애플리케이션 개발 과정을 설명합니다. Flask 앱은 웹 인터페이스와 API를 제공하며, Playwright로 ChatGPT 대화를 수집하고 MCP 서버를 통해 Obsidian Vault에 저장합니다. Mac 환경을 기준으로 하며, 대화별 파일 저장, 날짜별 통합 저장, 태그 및 YAML 프론트매터 지원을 포함합니다.

요구사항

  • 목표: ChatGPT 대화 내용을 자동으로 추출해 Obsidian Vault에 .md 파일로 저장.
  • 기술 스택:
    • Flask: 웹 서버 및 API.
    • Playwright: ChatGPT 웹 UI 스크레이핑.
    • MCP: Obsidian Vault와의 동기화.
    • Obsidian: 대화 기록 저장 및 관리.
  • 기능:
    • 웹 UI를 통한 대화 수집 트리거.
    • 대화별 또는 날짜별 .md 파일 저장.
    • YAML 프론트매터에 태그 및 생성 날짜 포함.
    • MCP 서버를 통한 Vault 동기화.

✅ 전제 조건

  • 설치 필요:

    • Python 3.x 및 pip.
    • Node.js (Playwright 및 MCP용).
    • Python 패키지: flask, playwright, requests.
    • Node.js 패키지: playwright, @modelcontextprotocol/sdk, @modelcontextprotocol/server-filesystem.
    • 명령어:

      pip install flask playwright requests
      playwright install
      npm install playwright @modelcontextprotocol/sdk @modelcontextprotocol/server-filesystem
  • 환경 설정:

    • Obsidian Vault 경로 (예: /Users/yourname/ObsidianVault).
    • MCP 서버 실행: npx @modelcontextprotocol/server-filesystem ~/ObsidianVault --port 3030.
    • ChatGPT 로그인 정보 (수동 로그인 또는 API 키).

🧩 구성 개요

  1. Flask 앱:
    • 웹 인터페이스: 대화 수집 및 저장 트리거.
    • API 엔드포인트: Playwright 실행 및 .md 파일 저장.
  2. Playwright: ChatGPT 웹페이지에서 대화 추출, 임시 파일(messages.txt)에 저장.
  3. MCP 서버: Obsidian Vault에 .md 파일 동기화.
  4. 저장 옵션:
    • 대화별 개별 파일 (예: Chat_2025-05-13_001.md).
    • 날짜별 통합 파일 (예: ChatGPT_2025-05-13.md).
    • YAML 프론트매터에 태그와 타임스탬프 포함.
  5. 자동화: Flask 앱 내 스케줄링 또는 외부 트리거.

🛠️ 개발 과정

1. 디렉토리 구조

chatgpt-to-obsidian/
├── app.py                # Flask 앱 메인 파일
├── chatgpt_export.js     # Playwright 스크립트
├── templates/
│   └── index.html        # 웹 인터페이스 HTML
├── static/
│   └── style.css         # 스타일시트
├── requirements.txt      # Python 의존성
├── package.json          # Node.js 의존성
└── ObsidianVault/        # Obsidian Vault 디렉토리 (예시)

2. Flask 앱 (app.py)

Flask 앱은 웹 UI와 /scrape 엔드포인트를 제공하며, Playwright로 수집한 대화를 MCP 서버를 통해 저장합니다.

from flask import Flask, render_template, request, jsonify
import subprocess
import os
from datetime import datetime
import requests

app = Flask(__name__)

# 설정
OBSIDIAN_VAULT = "/Users/yourname/ObsidianVault"  # 실제 Vault 경로로 변경
MCP_ENDPOINT = "http://localhost:3030"
NODE_SCRIPT = os.path.join(os.path.dirname(__file__), "chatgpt_export.js")

def save_to_mcp(content, filename, tags=[]):
    """MCP 서버를 통해 Obsidian Vault에 저장"""
    file_path = f"ChatGPT/{filename}"
    headers = {"Content-Type": "text/plain"}
    payload = {
        "vault": OBSIDIAN_VAULT,
        "content": f"""---
tags: [{', '.join(tags)}]
created_at: {datetime.now().strftime('%Y-%m-%d')}
---
# ChatGPT 대화 기록 - {datetime.now().strftime('%Y-%m-%d')}

{content}
"""
    }
    response = requests.post(f"{MCP_ENDPOINT}/write/{file_path}", json=payload, headers=headers)
    return response.status_code == 200

@app.route("/")
def index():
    """웹 인터페이스 렌더링"""
    return render_template("index.html")

@app.route("/scrape", methods=["POST"])
def scrape():
    """ChatGPT 대화 수집 및 저장"""
    try:
        # Playwright 스크립트 실행
        result = subprocess.run(["node", NODE_SCRIPT], capture_output=True, text=True)
        if result.returncode != 0:
            return jsonify({"error": result.stderr}), 500

        # 출력 파싱
        with open("messages.txt", "r") as f:
            messages = f.read().splitlines()

        # 옵션 처리
        date = datetime.now().strftime("%Y-%m-%d")
        tags = request.form.get("tags", "ChatGPT,AI").split(",")
        separate_files = request.form.get("separate", "false").lower() == "true"

        if separate_files:
            for i, msg in enumerate(messages, 1):
                filename = f"Chat_{date}_{i}.md"
                content = f"### Message {i}\n{msg}"
                save_to_mcp(content, filename, tags)
        else:
            filename = f"ChatGPT_{date}.md"
            content = "\n\n".join(f"### Message {i}\n{msg}" for i, msg in enumerate(messages, 1))
            save_to_mcp(content, filename, tags)

        return jsonify({"message": "대화가 성공적으로 저장되었습니다!", "files": filename})
    except Exception as e:
        return jsonify({"error": str(e)}), 500

if __name__ == "__main__":
    app.run(debug=True, port=5000)

3. Playwright 스크립트 (chatgpt_export.js)

ChatGPT 대화를 수집해 messages.txt에 저장합니다.

const { chromium } = require('playwright');
const fs = require('fs');

(async () => {
  const browser = await chromium.launch({ headless: false });
  const context = await browser.newContext();
  const page = await context.newPage();

  try {
    await page.goto('https://chat.openai.com/');
    await page.waitForSelector('.text-base', { timeout: 30000 });

    const messages = await page.$$eval('.text-base', elements =>
      elements.map(el => {
        const role = el.closest('[data-user-message]') ? 'User' : 'Assistant';
        return `${role}: ${el.innerText.trim()}`;
      })
    );

    fs.writeFileSync('messages.txt', messages.join('\n'));
  } catch (error) {
    console.error('Error:', error);
    process.exit(1);
  } finally {
    await browser.close();
  }
})();

4. 웹 인터페이스 (templates/index.html)

웹 UI로 대화 수집을 트리거합니다.

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <title>ChatGPT to Obsidian</title>
  <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
  <h1>ChatGPT 대화 → Obsidian 저장</h1>
  <form id="scrape-form">
    <label for="tags">태그 (쉼표로 구분):</label>
    <input type="text" id="tags" name="tags" value="ChatGPT,AI"><br>
    <label for="separate">대화별 개별 파일 저장:</label>
    <input type="checkbox" id="separate" name="separate"><br>
    <button type="submit">대화 수집 및 저장</button>
  </form>
  <div id="result"></div>

  <script>
    document.getElementById('scrape-form').addEventListener('submit', async (e) => {
      e.preventDefault();
      const formData = new FormData(e.target);
      const response = await fetch('/scrape', {
        method: 'POST',
        body: formData
      });
      const result = await response.json();
      document.getElementById('result').innerText = JSON.stringify(result, null, 2);
    });
  </script>
</body>
</html>

5. 스타일시트 (static/style.css)

body { font-family: Arial, sans-serif; margin: 20px; }
form { margin: 20px 0; }
label { margin-right: 10px; }
button { padding: 10px 20px; }
#result { margin-top: 20px; white-space: pre-wrap; }

6. 의존성

  • Python (requirements.txt):

    flask==2.3.3
    playwright==1.44.0
    requests==2.31.0

    설치: pip install -r requirements.txt

  • Node.js (package.json):

    {
      "dependencies": {
        "playwright": "^1.44.0",
        "@modelcontextprotocol/sdk": "latest",
        "@modelcontextprotocol/server-filesystem": "latest"
      }
    }

    설치: npm install


🚀 실행 방법

  1. MCP 서버 실행:

    npx @modelcontextprotocol/server-filesystem ~/ObsidianVault --port 3030
  2. Flask 앱 실행:

    python app.py
  3. 웹 인터페이스 접속:

    • http://localhost:5000에 접속.
    • 태그 입력, 대화별 저장 옵션 선택 후 "대화 수집 및 저장" 클릭.
    • 결과 확인:

    • Obsidian Vault의 ChatGPT 폴더에 .md 파일 생성.

    • 예: ChatGPT_2025-05-13.md 또는 Chat_2025-05-13_001.md.

📂 저장 예시

단일 파일 (ChatGPT_2025-05-13.md):

---
tags: [ChatGPT, AI]
created_at: 2025-05-13
---
# ChatGPT 대화 기록 - 2025-05-13

### Message 1
User: 플레이라이트로 채팅 내용을 옵시디언에 저장할 수 있을까?

### Message 2
Assistant: 가능합니다. 다음과 같은 방식으로 저장할 수 있습니다...

대화별 파일 (Chat_2025-05-13_1.md):

---
tags: [ChatGPT, AI]
created_at: 2025-05-13
---
# ChatGPT 대화 기록 - 2025-05-13

### Message 1
User: 플레이라이트로 채팅 내용을 옵시디언에 저장할 수 있을까?

🛠️ 개발 과정 요약

  1. 초기 워크플로우:

    • Playwright로 ChatGPT 대화 추출.
    • fs.md 파일 생성.
    • MCP 서버로 Obsidian Vault 동기화.
    • cron 또는 launchd로 자동화 제안.
    • Flask 앱 변환:

    • Flask로 웹 UI와 API 엔드포인트 구현.

    • Playwright 스크립트를 subprocess로 호출.
    • MCP 서버를 통해 .md 파일 저장.
    • 대화별/날짜별 저장, 태그 및 YAML 지원.
    • 개선점:

    • 사용자/봇 메시지 구분.

    • 에러 처리 및 타임아웃 설정.
    • 웹 UI에 태그 입력 및 저장 옵션 추가.

⚠️ 주의사항

  • ChatGPT DOM 변경:
    • .text-base 셀렉터는 UI 업데이트로 바뀔 수 있음. 정기적 테스트 필요.
  • 로그인 처리:
    • 수동 로그인 필요. API 키 또는 쿠키로 자동화 가능.
  • MCP 서버:
    • Flask 앱 실행 전 MCP 서버가 localhost:3030에서 실행 중이어야 함.
  • Obsidian Vault:
    • OBSIDIAN_VAULT 경로를 실제 경로로 설정.

📝 추가 확장 제안

  1. 로그인 자동화:

    • ChatGPT API 또는 쿠키로 자동 로그인.
    • 예:

      page.evaluate("() => { localStorage.setItem('auth_token', 'your_token'); }")
  2. 스케줄링:

    • apscheduler로 주기적 실행:

      from apscheduler.schedulers.background import BackgroundScheduler
      scheduler = BackgroundScheduler()
      scheduler.add_job(scrape, 'interval', hours=24)
      scheduler.start()
  3. API 확장:

    • /api/scrape 엔드포인트로 외부 호출 지원:

      @app.route("/api/scrape", methods=["POST"])
      def api_scrape():
          data = request.json
          tags = data.get("tags", ["ChatGPT", "AI"])
          separate = data.get("separate", False)
          # 로직 추가
  4. Obsidian 플러그인 연동:

    • Dataview로 태그/날짜 쿼리.
    • Templater로 파일 생성 템플릿화.
    • 다른 플랫폼:

    • Grok 대화 저장: x.com UI 스크레이핑 또는 xAI API (https://x.ai/api).

    • Claude 대화: Anthropic API 또는 Playwright.
    • 배포:

    • Heroku, AWS, Docker로 Flask 앱 배포.


🔚 결론

이 Flask 앱은 Playwright와 MCP를 활용해 ChatGPT 대화를 Obsidian에 효과적으로 저장합니다. 웹 UI와 API로 유연한 사용이 가능하며, 대화별/날짜별 저장, 태그 및 YAML 지원으로 Obsidian과의 호환성을 높였습니다. 추가 요구사항(예: 서브폴더 구조, 자동화, 다른 플랫폼 지원)이 있다면, 이를 반영해 코드를 확장할 수 있습니다.