ChatGPT 안에서 Velt 댓글이 포함된 실시간 tldraw 화이트보드 만들기🤯🔥

발행: (2025년 12월 11일 오전 03:58 GMT+9)
8 min read
원문: Dev.to

Source: Dev.to

우리가 만들고 있는 것

우리는 ChatGPT로 제어할 수 있는 협업 화이트보드를 만들고 있습니다. ChatGPT에게 도형을 추가하거나, 스티키 노트를 붙이거나, 레이아웃을 변경하라고 말하면 보드가 즉시 업데이트됩니다. 팀원들은 보드에 참여해 실시간으로 변화를 확인하고 캔버스에 직접 댓글을 남길 수 있습니다.

우리는 TLDraw 를 캔버스로, Velt 를 실시간 협업을 위해, 그리고 Model Context Protocol (MCP) 을 사용해 모든 것을 ChatGPT와 연결합니다. 최종적으로 ChatGPT 안에서 동작하고 자연어에 반응하는 실용적인 화이트보드 앱을 얻게 됩니다.

기본 이해

GPT 앱은 두 부분으로 구성됩니다:

  1. Web 위젯 – ChatGPT 안에 렌더링되는 UI(우리의 화이트보드 캔버스).
  2. MCP 서버 – ChatGPT가 호출할 수 있는 도구들을 정의하는 Node.js 서비스(예: “사각형 추가”).

“사각형을 그려줘” 라고 말하면 ChatGPT가 도구 정의를 읽고 MCP 서버를 호출하며, 서버는 캔버스를 업데이트합니다.

chatgpt UI

TLDraw

  • 캔버스, 그리기 도구, 도형, 텍스트를 담당합니다.
  • @tldraw/sync 를 통해 실시간 보드 동기화를 제공 – 같은 방에 있는 모든 사용자가 즉시 업데이트를 봅니다.

Velt

  • 협업 기능을 제공합니다: 댓글, 실시간 커서, 존재 표시기.
  • 프론트엔드에서는 React 컴포넌트로, 백엔드에서는 REST API로 동작합니다.

이 두 라이브러리가 UI를 만들고, MCP 서버가 UI와 ChatGPT를 연결합니다.

MCP 서버

MCP 서버는 ChatGPT가 호출할 수 있는 도구들을 정의하는 Node.js 앱입니다. 각 도구는 이름, 설명, 그리고 필요한 매개변수를 기술한 inputSchema 를 포함합니다.

const tools = [
  {
    name: "add-item",
    description: "Add an item to the list",
    inputSchema: {
      type: "object",
      properties: {
        text: {
          type: "string",
          description: "The item text"
        },
        priority: {
          type: "string",
          enum: ["low", "medium", "high"],
          description: "Item priority"
        }
      },
      required: ["text"]
    }
  }
];

description 은 언제 도구를 사용해야 하는지 ChatGPT에 안내하고, inputSchema 는 어떤 데이터를 보내야 하는지 알려줍니다.

사전 준비 사항

서비스필요 사항
VeltAPI 키와 Auth Token (댓글 및 협업 처리)
TLDraw라이선스 키 (화이트보드 캔버스에 필요)
ngrok로컬 서버를 ChatGPT에 노출하기 위해 사용
ChatGPT Plus커스텀 앱 사용에 필수

설정

git clone https://github.com/Studio1HQ/velt-app-examples
cd velt-app-examples
pnpm install

cd syncboard_server
pnpm install
cd ..

Chrome 142+ 사용자: local‑network‑access‑check 플래그를 비활성화하세요.

  1. chrome://flags/ 열기
  2. local-network-access-check 검색
  3. Disabled 로 설정하고 Chrome 재시작

화이트보드 만들기

프로젝트는 프론트엔드(위젯)와 백엔드(MCP 서버)로 나뉩니다.

src/syncboard/          # 프론트엔드 화이트보드
├── syncboard.jsx       # 캔버스와 Velt 컴포넌트
├── mockUsers.js        # 테스트 사용자 (Bob & Alice)
└── index.jsx           # 진입점

syncboard_server/       # 백엔드 MCP 서버
└── src/
    ├── server.ts       # 도구 정의
    └── velt/           # 댓글 핸들러

TLDraw 설정

src/syncboard/syncboard.jsx 를 열고 기본 TLDraw 캔버스를 추가합니다.

import { Tldraw } from 'tldraw';
import { useSyncDemo } from '@tldraw/sync';
import 'tldraw/tldraw.css';

function SyncboardCanvas() {
  const store = useSyncDemo({
    roomId: import.meta.env.VITE_TLDRAW_ROOM_ID // e.g., "my-room-abc"
  });

  return <Tldraw store={store} />;
}

useSyncDemo 는 지정된 roomId 에 연결된 동기화 스토어를 생성합니다. 해당 방에 있는 모든 참가자는 동일한 캔버스 상태를 공유합니다.

Velt 추가

TLDraw 캔버스를 Velt의 Provider 로 감싸고 협업 컴포넌트를 추가합니다.

// syncboard.jsx
import {
  VeltProvider,
  useVeltClient,
  VeltComments,
  VeltPresence,
  VeltCursor,
  VeltCommentTool,
  VeltSidebarButton
} from '@veltdev/react';
import { Tldraw } from 'tldraw';
import { useSyncDemo } from '@tldraw/sync';
import 'tldraw/tldraw.css';
import { useEffect, useState } from 'react';

function SyncboardCanvas() {
  const store = useSyncDemo({
    roomId: import.meta.env.VITE_TLDRAW_ROOM_ID
  });

  const { client } = useVeltClient();
  const [veltReady, setVeltReady] = useState(false);

  // Initialize Velt client
  useEffect(() => {
    const init = async () => {
      if (!client || veltReady) return;

      await client.identify(currentUser, { forceReset: true });
      await client.setDocument('syncboard-whiteboard', {
        documentName: 'Syncboard Collaborative Whiteboard'
      });

      setVeltReady(true);
    };
    init();
  }, [client, veltReady]);

  return (
    <>
      {/* Top bar with collaboration controls */}
      {veltReady && <VeltSidebarButton />}
      {veltReady && <VeltCommentTool />}

      {/* Main canvas */}
      <Tldraw store={store} />

      {/* Velt overlays */}
      {veltReady && (
        <>
          <VeltComments />
          <VeltPresence />
          <VeltCursor />
        </>
      )}
    </>
  );
}

export default function Syncboard() {
  return (
    <VeltProvider>
      <SyncboardCanvas />
    </VeltProvider>
  );
}

Velt 컴포넌트는 TLDraw 캔버스 위에 댓글 스레드, 존재 표시기, 실시간 커서를 추가합니다.

MCP 서버 (백엔드)

syncboard_server/src/server.ts 에 MCP 서버를 만듭니다. 아래는 보드에 사각형을 추가하는 도구를 정의한 최소 예시입니다.

import { createMcpServer } from '@openai/mcp';
import { addRectangle } from './velt/rectangleHandler'; // your custom logic

const tools = [
  {
    name: "add-rectangle",
    description: "Add a rectangle shape to the whiteboard",
    inputSchema: {
      type: "object",
      properties: {
        width: { type: "number", description: "Width of the rectangle" },
        height: { type: "number", description: "Height of the rectangle" },
        color: { type: "string", description: "Fill color (hex)" }
      },
      required: ["width", "height"]
    }
  }
];

const server = createMcpServer({
  tools,
  handler: async (toolName, params) => {
    if (toolName === "add-rectangle") {
      await addRectangle(params);
    }
    // Add more tool handlers as needed
  }
});

server.listen(3000, () => console.log("MCP server listening on port 3000"));

addRectangle 은 Velt 문서(REST API 또는 SDK)를 통해 TLDraw 스토어에 사각형 도형을 삽입하도록 구현해야 합니다.

ngrok 으로 서버를 외부에 노출해 ChatGPT가 접근할 수 있게 합니다:

ngrok http 3000

생성된 HTTPS URL을 복사하고 ChatGPT 앱 설정에서 MCP 엔드포인트로 지정합니다.

앱 실행하기

  1. 프론트엔드(위젯) 시작 – 보통 Vite 혹은 선호하는 개발 서버를 사용합니다.
  2. MCP 서버 시작 (node syncboard_server/src/server.ts).
  3. ngrok 으로 서버를 노출합니다.
  4. ChatGPT에서 새 커스텀 앱을 추가하고 ngrok URL을 지정합니다.
  5. 상호작용해 보세요! 예시 프롬프트:
    • “파란색 사각형을 가로 200 px, 세로 100 px 로 그려줘.”
    • “‘Sprint goals’ 라는 스티키 노트를 추가해줘.”

ChatGPT가 해당 도구를 호출하면 MCP 서버가 Velt 문서를 업데이트하고, 변경 사항이 공유 화이트보드에 즉시 반영됩니다.

Back to Blog

관련 글

더 보기 »