완성작

📮 대망의 마지막 – 리포트까지 자동화하자!

드디어 마지막 이야기입니다. 이제 자동화는 잘~ 돌아가고, Illustrator도 말을 듣고, Gmail도 착착 메일을 보내줍니다. 하지만 뭔가 허전했죠.

“파일만 보내니까 너무 밋밋해…”

그래서 생각했습니다.
“메일에다가 무슨 데이터를 받아서 어떤 작업이 이뤄졌고, 얼마나 걸렸는지도 같이 정리해서 넣으면 더 멋지지 않을까?”

이제 등장해야죠, 우리의 생성형 AI 친구 – Gemini!


📊 작업 리포트는 LLM에게 맡기자

n8n 흐름 중간에 Gemini를 호출해서, 사용자 입력 + 처리 시간 등을 요약 리포트로 정리하게 만들었습니다.
그리고 이 결과를 메일 본문에 파일과 함께 넣는 게 목표였어요.

하지만 여기서부터 또 한참을 삽질하게 됩니다…


😵 n8n 초보의 삽질 다섯 가지

1. 중간 노드 접근하기까지 반나절

처음엔 "순차적으로만 흐른다"고 생각해서, LLM을 거친 뒤 파일을 다시 참조하는 방법을 몰랐습니다.
나중에야 깨달았죠 – n8n에서는 중간 노드도 직접 접근 가능하다는 걸요!

{{ $("파일생성노드이름").binary["data"] }}

…이걸 몰라서 얼마나 헤맸는지 😇


2. 바이너리 다루기… 미궁 속으로

n8n의 기본 데이터 흐름은 JSON이라 그런지, 바이너리 데이터를 노드에 넣는 방법을 몰랐습니다.
LLM에게 물어봤지만 버전이 안 맞았는지 뻔한 헛소리만 들었고, 결국 직접 Base64를 활용하여로 해결.


3. base64로 string 만들기 → 다시 바이너리? 실패의 연속

"그래, base64로 변환한 다음 string을 바이너리로 다시 만들면 되지 않을까?"
→ 그렇게 간단하지 않았습니다.

item이 여러 개인데 하나의 string만 처리해서는 안 됐고,
라인마다 따로 처리해야 했는데 그걸 몰라서 계속 실패… 또 시간 줄줄.


4. Google Sheet 업데이트 안 되는 문제

메일을 보낸 다음, Google Sheet에 ‘완료’로 태그 업데이트를 하려 했는데 첫 번째 줄만 반영됨.
row_number를 이용해서 각 행을 정확히 지정해주는 방식으로 해결!


5. 파일은 하나만 만들어야 하는데…

파일과 데이터는 1:N 관계인데 나중에 바이너리 파일로 변경하는 시점에

같은 파일이 각각 두 개씩 생기는 기이한 현상.
결국 Code 노드로 데이터를 강제로 하나의 item으로 묶어서 해결.

 

 

🧠 n8n을 다뤄 보면서 깨달은 세 가지

✅ 1. 노드 접근 방식은 자유롭다!(이걸 몰라서... 보낸 시간이...)

  • 이전 노드 직접 접근
    {{ $("파일노드").item.json.title }}
  • 현재 item의 데이터
    {{ $json['상태'] }}

🛠️ 2. Set 노드는 데이터를 정리하는 데 최적

  • 필드 이름 변경
  • key 정리
  • 고정값 추가 등등

🔧 3. Code 노드는 만능 도구

  • 복수 아이템 합치기
  • JSON ↔ Binary 변환
  • 복잡한 조건 처리

🎉 끝맺음

프로젝트 초반엔 그까짓 것 "자동화 좀 해보자~"였는데,
끝나고 보니 Illustrator부터 메일 전송, 요약 리포트 작성까지 모두 자동으로 돌아가는 작은 인하우스 플랫폼이 완성됐습니다.

물론 더 예쁘게, 더 잘 만들 수 있는 여지는 많겠지만,
이번 과제를 통해 진짜 많은 걸 배웠습니다.
무엇보다 n8n에 대해 아무것도 몰랐던 무지렁이에서 초보 수준까진 올라온 느낌이랄까요?  😎


다음에 또 삽질기가 있다면 돌아오겠습니다.
읽어주셔서 감사합니다!

 

안녕하세요!
오늘도 학교 과제 덕분에 머리를 싸매며 삽질한 일러스트레이터 자동화 프로젝트 이야기를 이어가겠습니다.
지난 번에는 ExtendScript Toolkit을 통해 Illustrator가 실제로 스크립트로 제어될 수 있다는 걸 확인했었죠.

이제 본격적으로 달려보죠.

🔧 자동화 프로세스의 큰 줄기

우리가 만들고자 한 건 단순했습니다.
사용자의 데이터 입력 → 디자인 작업 자동화 → 결과물 생성 및 전송 → 정리까지, 모든 게 자동으로 처리되는 시스템.

프로세스는 이렇게 짰어요:

  1. 데이터 받기 : 사용자가 Google Sheet에 정보 입력
  2. 작업 지시 : Adobe ExtendScript Toolkit 실행하여 Illustrator 제어
  3. 파일 생성 : AI + JPG 파일 저장
  4. 메일 발송 : Gmail로 첨부파일 전송
  5. 정리 : 작업 완료 후 파일 삭제 및 기록 정리

이 모든 동작이 하나의 플랫폼 아래서 통합되고 관리되어야 했죠.
그 주역은 바로… n8n 이었습니다.


📦 자동화 도구들, 이제는 본격적인 조합 시작

각각의 기능을 담당할 도구들은 이미 정해져 있었습니다:

  • Google Sheet : 사용자로부터 데이터를 받아올 창구
  • Adobe ExtendScript Toolkit + PyAutoGUI : Illustrator 자동 수정 담당
  • Illustrator : 자동으로 텍스트 변경하고 파일 저장
  • Gmail : 결과물을 첨부해서 보내주는 메일봇
  • Gemini : 사용자 데이터를 정리하고 설명문 작성까지

이 모든 걸 n8n 이라는 워크플로우 엔진 하나로 연결하려는 야심찬 계획이었죠.
(굳이 n8n 없어도 되긴 되는데.....)


⚙️ 사전 작업, NAS에 n8n 설치하기

자동화를 하려면 일단 서버가 필요합니다.
그래서 저희는 집에 있는 Synology NAS n8n 을 설치했습니다.
항상 켜져 있고 외부 접속도 가능하다는 점에서 정말 좋은 선택이었어요.

설치 방법은 크게 두 가지가 있었는데,
PostgreSQL과 연동하는 방식도 있었지만, 결국 N8N 자체 서버 + Web Station을 활용하는 간단한 방식으로 성공했고,
이후엔 웹브라우저로 n8n.XXXX.synology.me 접속만으로도 작업이 가능하게 되었죠.

(참고로 설치 가이드는 이곳 을 참고했어요!)


🔑 Google OAuth 인증, 처음 만드는 API 계정

복수 계정을 만들려고 PC에서 만들려고 했지만 계속 인증 오류가 나서 결국 모바일로 계정을 새로 만들었네요... 😅

 

n8n에서 Google Sheet를 접근하려면 당연히 OAuth 인증이 필요했습니다.
OAuth 설정이 완료된 이후, n8n 에서 인증키를 등록한 뒤

Google Sheet의 데이터를 읽어오는 건 n8n의 내장 노드로 금방 해결할 수 있었어요.
이렇게 데이터를 얻으면, 다음 단계로 넘어갈 준비가 끝났다는 뜻입니다.


💻 "웹"에서 온 데이터, 어떻게든 "로컬"에 심어보자 – n8n과의 사투

자동화 프로젝트가 어느 정도 윤곽을 갖추고 나니, 실제로 작동하는 흐름도 눈으로 확인할 수 있게 되었어요.
Google Sheet에 데이터를 입력하면 Illustrator 파일이 자동으로 생성되고, JPG까지 변환되어 Gmail으로 전송되는 일련의 과정이 말이죠.

 

하지만 여기엔 한 가지 큰 문제가 있었어요.

(Google Sheet)에서 온 데이터를, 어떻게 로컬 (일러스트레이터가 돌아가는 PC)로 가져갈 것인가?”


📥 웹에서 로컬로 데이터를 내리는 방법, 이게 이렇게 복잡할 줄이야…

처음엔 단순하게 생각했어요.
“n8n에서 Google Sheet API로 바로 읽어서 스크립트에 던져주면 끝 아냐?”
그런데 문제는 ExtendScript Toolkit 로컬 환경에서만 동작 한다는 거였죠.

그래서 n8n에서 웹훅(webhook) 을 활용해 이런 방식을 시도했습니다:

  1. Google Sheet 데이터를 n8n에서 받아옴
  2. JSON 형식으로 저장
  3. 웹훅 URL을 통해 로컬 서버에서 이 데이터를 GET 요청 으로 다운로드
  4. ExtendScript가 이 파일을 읽고 작업 시작

이렇게 하면 n8n이 중간 다리 역할을 해주면서,
로컬에서도 웹에서 받은 데이터를 사용할 수 있게 되더라고요.


⚠️ 그런데 사실… 더 쉬운 방법도 있었어요.

사실 Google Sheet 자체를 pyAutoGUI 시점에 직접 접근할 수도 있고,
OAuth 인증을 걸치면 스크립트 단독으로도 데이터를 읽을 수 있긴 합니다.
하지만 저희는 이미 n8n 위에 모든 로직을 얹어놨고,
이걸 또 처음부터 다시 짜기엔 그냥 너무 싫어서...…

꾸역 꾸역 기존 구조에 맞춰서라도 해보자…”

 

결국 웹훅으로 대기하고, 파일을 만들어 로컬로 다운받는 방식을 택하게 되었죠.
쉽진 않았지만, 일단 돌아가기 시작하면 그만~~~~~~


📤 반대로, 로컬에서 웹(n8n)으로 결과물을 보내는 건?

AI 파일과 JPG 파일이 생성된 후에는,
이걸 다시 n8n에게 넘겨줘서 Gmail로 발송해야 했습니다.
이번에도 같은 방식을 썼어요: 로컬에서 웹훅으로 파일 업로드

  1. Illustrator 작업 완료 → AI & JPG 파일 저장
  2. PyAutoGUI로 ExtendScript 종료 후, Python 스크립트 실행
  3. 생성된 파일들을 POST 요청으로 n8n 웹훅 주소에 업로드
  4. n8n에서 Gmail 노드 호출하여 메일 전송

여기서도 역시 “왜 그냥 로컬에서 구글 인증 받고 바로 메일 보내면 안 돼?”라는 질문이 떠올랐지만,
이미 n8n 위에 전체 흐름을 얹어놓은 상태라서…

n8n에서 다 처리되도록 만들고 싶다!
(명색이 자동화 프로젝트인데, 분산된 시스템은 싫단 말이에요…)


🧠 명색이 AI 과목 과제인데, LLM 없이는 좀 그렇잖아?

메일 발송까지는 잘 됐는데,
막상 메일을 보면 뭔가 허전하더라구요.
그래서 이번엔 Gemini 에게 요청 데이터와 변경 내용을 넘겨서 요약본을 만들어달라고 했습니다.

이 자동화 과정 중에 AI도 넣고 싶었습니다.

"이거 다 스크립트랑 매크로로만 해결했네. 그래도 명색에 AI 과목 과제인데 첨가향이라도 뿌려야 하나"

 

그래서 등장한 주인공이 바로 LLM , 이번엔 Google Gemini 였습니다.
AI가 직접 일러스트를 그리는 건 아니지만, 사용자가 입력한 데이터와 그에 따른 텍스트 변경 내역을 정리 해서 보여주는 역할을 맡겼죠.

 

이제부터 비극이 시작되었지요..

 

안녕하세요.

오늘은 학교 과제 덕분에 시작하게 된 일러스트레이터 자동화 프로젝트 이야기 를 준비했습니다.
처음엔 ‘자동화’라는 단어부터 낯설고 어렵게 느껴졌지만, 막상 시작해보니 그저 삽질의 연속....

 

과제지만 의미있는 주제를 삼아보자

우리 팀에서 이 자동화 프로젝트가 본격적으로 움직인 건, 팀원 중 한 분이 “일러스트레이터 작업을 자동화해 보고 싶다.” 라고

하셔서 현업에서 실제로 어떤 작업인지 들어보고, 저는 바로 반응했죠.

 

그냥 배워서 직접 하시는 게 더 빠르지 않을까요?

 

윈도우 기반 앱인 Illustrator를 스크립트로 조작하는 건 불가능할 거고, 매크로나 RPA 같은 걸 도입해야 하는데…
그걸 누가 짜겠어요..
결국 다른 아이디어를 구상하려던 찰나, 운명 같은 검색 결과 를 마주하게 됩니다.

 

과연, 자동화는 가능할까?

웹 서핑 도중 문득 이런 궁금증이 들었어요.

"혹시 우리처럼 Illustrator 작업을 자동화해보고 싶어 하는 사람들이 있을까?"

 

검색 결과, 세상은 역시 넓고 깊었습니다.
Adobe ExtendScript Toolkit 이라는 도구를 발견하고는 눈앞이 확 밝아졌죠. 💡
이걸 계기로 저희는 자동화 여정에 발을 내딛게 되었답니다.

 

오오, 이런 신세계가 있다니!

 

사용한 도구들: 여정의 동료들.

프로젝트를 진행하면서 함께했던 도구들을 소개할께요.

  • Adobe Illustrator : 텍스트 레이어 수정 (이번 과제의 핵심)
  • Adobe ExtendScript Toolkit : 일러스트레이터 제어용 확장 도구
  • PyAutoGUI : ExtendScript Toolkit을 자동 조작하는 자동화 도우미
  • n8n : 전체 프로세스를 관리하는 자동화 플랫폼 (자동화 위에 자동화!)
  • Gmail, Google Sheet, Gemini : 데이터 수집과 활용을 지원하는 든든한 동료들

첫 목표: 일러스트레이터가 정말 스크립트로 제어될까?

우선 확인해야 할 건 하나뿐이었어요.

"일러스트레이터가 ExtendScript로 진짜 조작이 가능한가?"

 

다행히 요구하는 작업은 단순했습니다. 템플릿 파일을 열고 텍스트만 바꾸면 됐으니까요!

다만 그 양이 만만치 않았다는 거… 😅

 

그래서 다음과 같은 단계를 세웠지요..

  1. 스크립트가 찾기 쉬운 레이어 이름 규칙 만들기
  2. ExtendScript로 원하는 레이어 탐색
  3. 찾은 레이어의 텍스트 변경

처음 다뤄보는 도구라 걱정이 많았지만, ExtendScript는 JavaScript 기반 이라 그런지 의외로 접근성이 좋았어요.

“이 정도면 해볼 만하겠는데?”


실제로 코드를 짜보니 오! 신기하게도 잘 작동했습니다. ✨
이거 그냥 날로 낼름 먹겠는데 

그런데… 자동화는 그렇게 끝나지 않아요 😅

처음엔 단순한 스크립트 하나로 시작했는데, 어느새 PyAutoGUI, n8n 등등…
점점 더 많은 자동화를 위한 자동화를 하고 그 자동화를 위해 또 다른 자동화를......

헐...

 

정말 그때는 몰랐어요. 앞으로 얼마나 더 많은 도구들과 머리 싸매야 할 지를 말이에요.

 

 

이번 글에서는 MCP 툴 없이 직접 모델과 통신한 구현MCP 툴 기반 구조를 나란히 비교해보려 합니다.

두 방식 모두 질문 → SQL 생성 → 실행 → 요약이라는 흐름은 같지만, 구현 철학과 역할 분담 구조가 완전히 다릅니다.


1. 직접 모델 호출 방식 (비-MCP 방식)

먼저, 모델을 직접 호출해 SQL을 만들고, 그 결과를 요약하는 전통적인 접근입니다.

const result = await model.generateContent(prompt);
const text = (await result.response.text()).trim();

그리고 결과 rows를 직접 넣어서 요약 프롬프트를 또 호출하죠.

const prompt = `
  질문: ${question}
  결과: ${JSON.stringify(rows)}
  → 요약해줘
`;
const summary = (await model.generateContent(prompt)).response.text();

이 구조의 특징은:

  • 모델에게 질문과 함께 컨텍스트를 주입
  • 직접적인 요청 흐름
  • 개발자가 모든 흐름을 설계함

2. MCP 툴 기반 구조

이번엔 MCP 툴 기반 구조입니다. 모델에게 툴 목록을 미리 등록해두고, 질문이 오면 모델이 스스로 "어떤 툴을 어떤 순서로 호출할지" 결정하도록 합니다.

MCP 방식에서는 이런 3개의 툴이 등록되어 있습니다:

🔧 1) generate-sql

server.tool(
  'generate-sql',
  { properties: { question, tables } },
  async (args) => {
    const prompt = `질문: ${args.question}
테이블: ${args.tables.join(', ')}`;
    const result = await model.generateContent(prompt);
    return result.response.text();
  }
);

🔧 2) execute-sql

server.tool(
  'execute-sql',
  { properties: { sql: { type: 'string' } } },
  async (args) => {
    const result = await pool.query(args.sql);
    return JSON.stringify(result.rows);
  }
);

🔧 3) summarize-sql-result

server.tool(
  'summarize-sql-result',
  { properties: { sql, rows } },
  async (args) => {
    const prompt = `질문 결과 요약:
SQL: ${args.sql}
데이터: ${JSON.stringify(args.rows)}`;
    const result = await model.generateContent(prompt);
    return result.response.text();
  }
);

각 툴은 독립적으로 실행되며, 모델이 "연결 순서"를 판단하여 generate → execute → summarize 흐름을 조립합니다.


💡 동일한 기능을 직접 호출 방식으로 구현하면?

MCP 없이 구현한 코드에서는 아래처럼 3개의 함수를 직접 호출합니다:

📦 1) SQL 생성

async function generateSQL(question, tables) {
  const prompt = `질문: ${question}
테이블: ${tables.join(', ')}`;
  const result = await model.generateContent(prompt);
  return result.response.text();
}

📦 2) SQL 실행

const rows = await pool.query(sql);

📦 3) 결과 요약

async function generateSummary(question, rows) {
  const prompt = `질문: ${question}
데이터: ${JSON.stringify(rows)}`;
  const result = await model.generateContent(prompt);
  return result.response.text();
}

결론적으로 MCP 방식은 동일한 함수 단위를 툴로 감싸서 등록해두고, 모델이 필요한 순서로 호출하게 만든 구조입니다.

예: 등록된 MCP 툴 목록

  • generate-sql: 자연어 → SQL
  • execute-sql: SQL 실행
  • summarize-sql-result: 결과 요약
const parsedSteps = JSON.parse(await model.generateContent(prompt));
for (const step of parsedSteps) {
  const toolResult = await server.callTool(step.tool, step.args);
}

 

이 구조는:

  • 툴을 기반으로 설계됨
  • 모델이 실행 순서를 조립함
  • 서버는 단순히 툴 실행자 역할

구조적 비교

항목 직접 모델 호출 방식 MCP 툴 기반 구조
흐름 설계 개발자가 모두 설계 모델이 조립
확장성 프롬프트마다 추가 구현 필요 툴만 추가하면 됨
복잡도 처음은 단순하나 점점 커짐 초반 설계가 필요하지만 구조는 깔끔
모델 의존도 단발성 호출 중심 연속된 흐름도 모델이 주도

실제로 구현해보니

기능적인 결과는 거의 같습니다. 질문하면 SQL 생성되고, 실행되고, 요약도 됩니다. 사용자 입장에서는 두 방식 차이를 못 느낄 정도죠.

하지만 서버 입장에서 느껴지는 구조적 차이는 꽤 큽니다.

  • 비-MCP는 일회성 처리가 편하고 빠름
  • MCP는 구조를 한번 만들면 계속 재사용, 확장이 쉬움

또한 툴 단위로 분리해두면, 각 도구의 로깅, 테스트, 교체가 훨씬 유리해집니다.


마치며

두 가지 구조를 비교해보며 느낀 점은:

“작고 단순한 프로젝트는 직접 호출이 빠르지만, 복잡해질수록 MCP 기반이 구조적으로 더 낫다.”

 

 

긴 글 읽어 주셔서 감사합니다.

 

그리고 지금까지 소스 공개합니다.


REST API 기준

https://github.com/pm2makeq/chatbot_restapi

 

GitHub - pm2makeq/chatbot_restapi: restapi chatbot

restapi chatbot. Contribute to pm2makeq/chatbot_restapi development by creating an account on GitHub.

github.com

 

MCP 기준

https://github.com/pm2makeq/mcp-project

 

GitHub - pm2makeq/mcp-project

Contribute to pm2makeq/mcp-project development by creating an account on GitHub.

github.com

 

전체글

 

지난 글에서는 MCP 기반 챗봇의 클라이언트 구현을 중심으로 다뤘습니다. 이번에는 진짜 핵심인 서버 편입니다. 챗봇이 작동하려면, 결국 서버가 질문을 받아서 SQL을 생성하고 실행하고 요약까지 해줘야 하니까요.

이번 글에서는 동일한 기능을 REST API 방식과 MCP 방식 두 가지로 각각 구현해본 뒤, 어떤 차이가 있었는지 정리해보았습니다.


🧩 REST API 방식부터 살펴봅니다

app.post("/ask", async (req, res) => {
  const { question, tables } = req.body;
  const updatedTables = [...(tables || []), "students"];

  const sql = await generateSQL(question, updatedTables);
  const result = await client.query(sql);
  const summary = await generateSummary(question, result.rows);

  res.json({ sql, rows: result.rows, summary });
});

REST 방식은 익숙합니다. 구조도 간단하죠:

  1. 질문 받기
  2. SQL 생성
  3. SQL 실행
  4. 결과 요약

이걸 하나의 API에서 처리하면 됩니다. 명확하고 직선적인 구조, 딱 필요한 것만 구현하면 됩니다.


🔁 MCP 방식은 이렇게 다릅니다

이번엔 같은 기능을 MCP 방식으로 구현한 서버입니다. 구조는 좀 더 복잡해 보일 수 있지만, 핵심은 모델이 도구들을 직접 호출하게 하는 것입니다.

const prompt = `
  - 질문을 받으면 generate-sql → execute-sql → summarize-sql-result 순으로
  - 각 도구에 필요한 args를 구성해서 JSON 배열로 응답
`;

const result = await model.generateContent(prompt);
const parsedSteps = JSON.parse(result);

for (const step of parsedSteps) {
  const toolResult = await server.callTool(step.tool, step.args);
  // 결과 누적 및 가공
}

MCP 방식의 차이점은 다음과 같습니다:

  • 질문을 받고 → 어떤 도구를 쓸지 모델이 판단함
  • 각 도구에 적절한 인자를 주입함
  • 실행 결과를 다시 다음 도구로 넘김

즉, 흐름을 사람이 짜는 게 아니라 모델이 조립합니다. 이것이 MCP의 핵심이죠.


⚙️ 구조 비교 정리

항목 REST 방식 MCP 방식
흐름 제어 서버 코드에 명시적으로 작성 모델이 도구 흐름을 결정
유연성 새로운 기능 추가 시 코드 수정 필요 도구만 추가하면 됨
모델 참여도 모델은 단순 질문 응답 모델이 도구 조합까지 주도

🧪 돌려보고 느낀 점

둘 다 돌아가는 기능은 거의 똑같습니다. 결과도 유사하고, 처리 시간도 큰 차이는 없었습니다.

특히 클라이언트 입장에서는 둘이 어떻게 구현되었는지 알 수 없을 정도로 결과만 놓고 보면 똑같습니다.

하지만 차이점은 "설계자 관점"에서 확연했습니다:

  • REST는 내가 다 설계해야 함
  • MCP는 모델이 설계의 일부를 맡음

따라서 도구가 많아지고 복잡해질수록 MCP가 더 깔끔하게 느껴졌습니다.


✍️ 마치며

서버 편을 정리하며 느낀 건 하나입니다:

“처음엔 REST가 편하지만, 확장성과 유지보수를 생각하면 MCP 구조가 훨씬 유연하다.”


 

다음 편에서는 이 MCP 서버와 API 서버를 연결하는 구조를 정리해보겠습니다.

 

다음 글에서 뵐게요 👋

 

다음글

 

학생 관리 RAG 챗봇, MCP 툴로 붙여보니 이런 느낌이었습니다

 

저는 요즘 챗봇을 만들면서 개발자로서의 사고방식이 꽤 많이 바뀌고 있습니다. 특히 MCP를 도입하고 나서는, 단순히 API를 설계하던 습관에서 벗어나 모델이 중심이 되는 구조를 점점 익혀가고 있는데요.

이번에는 실제로 학생 정보를 관리하는 간단한 챗봇을 MCP 기반으로 구현해본 경험을 이야기해보려 합니다.

개발은 결국 시행착오죠. 잘된 점도 있었고, "어 이건 아닌데..." 싶은 부분도 있었거든요.

 

 

개발은 결국 시행착오죠. 엣헴!

 

🧩 목표는 단순했습니다: "질문하면 요약해줘"

기획 자체는 별거 없었습니다. 그냥 이거였어요:

"학생 테이블을 대상으로, 사용자가 자연어로 질문하면 → SQL을 생성하고 → 실행한 뒤 → 결과를 요약해 보여주는 챗봇을 만들자"

기존이라면 이걸 어떻게 구현했을까요? 아마도:

  • 질문을 백엔드에서 해석하고
  • SQL을 직접 작성해서 날리고
  • 결과를 프론트에 출력하는 방식이었겠죠.

이번에는 다릅니다. MCP 툴을 이용해 이 흐름을 모델이 직접 주도하도록 구성했습니다.

  • /proxy/8080/ask 라우터 하나에 모든 로직을 몰아넣고
  • 질문만 던지면 → SQL 생성 → 실행 → 요약까지 한번에
  • 결과는 sql, rows, summary 3개 필드로 정리

💻 핵심 함수 하나로 설명됩니다

async function callAsk(question, tables = []) {
  tables.push("students");
  const resp = await fetch('/proxy/8080/ask', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ question, tables }),
  });

  let data;
  try {
    data = await resp.json();
  } catch (e) {
    return { summary: '서버 응답을 해석할 수 없습니다.' };
  }

  return {
    sql: data.sql ?? 'SQL 없음',
    rows: data.rows ?? '데이터 없음',
    summary: data.summary ?? '요약 없음'
  };
}

딱 하나의 질문으로 3가지 정보를 받아옵니다. 이게 가능했던 건, MCP 툴 내부에 질문 → SQL 생성 → 실행 → 요약까지 연결되어 있었기 때문이죠.

🎨 UI는 이렇게 그렸습니다

  • 🤖/👤 아이콘으로 역할 구분
  • 🕑 처리 중… 문구로 피드백 주고
  • 응답이 오면 SQL, 데이터, 요약 순서로 정리해서 보여줍니다

기능적으로는 단순하지만, 사용성은 굉장히 직관적입니다.

 


🧪 돌려보고 느낀 점

사실, REST API 방식으로 구현했을 때와 이번 MCP 방식은 기능적으로 큰 차이는 없었습니다. 질문을 받고 → SQL을 만들고 → 실행하고 → 요약을 보여준다는 흐름은 거의 같았고요.

특히 클라이언트 측에서는 거의 차이를 느끼기 어려웠습니다. fetch로 요청을 보내고, 응답을 받아 출력하는 구조는 동일했기 때문입니다. 즉, 사용자 인터페이스나 처리 흐름 자체는 바뀌지 않았습니다.

그렇다면 굳이 MCP를 왜 썼냐고요? 핵심은 "모델 중심의 설계 경험"이었습니다.


🚀 다음은 API 서버 연동입니다

  • MCP 툴 연동
  • 자연어 질문을 도구로 연결해 답변 생성하기
  • 예외처리

다음에 또 뵐게요! 👋

 

다음글

여윽시 AI 이미지 문자 깨지는 것 보소

Gemini로 만든 챗봇, 이번엔 MCP까지 붙여봤습니다 (실전편)

안녕하세요. 이번 글에서는 Gemini 기반 챗봇을 만들며 겪은 실전 경험담을 공유하려고 합니다.

기존에는 익숙한 방식인 REST API로 구현했다가, 이번에는 MCP(Model Context Protocol) 방식으로 확장해보며 구조의 차이점을 정리해보았습니다.


1. 기존 방식: REST API로 빠르게 챗봇 만들기

처음엔 누구나 그렇듯 익숙한 Express.js 기반으로 작업을 시작했습니다.

사용자의 메시지를 받아서 → Gemini에게 넘기고 → 응답을 반환하는 전형적인 구조였죠.

app.post('/', async (req, res) => {
  const userMessage = req.body.message;
  const response = await gemini.chat.send({
    messages: [{ role: 'user', content: userMessage }]
  });
  res.json({ reply: response.candidates[0].content });
});

딱 보면 아시겠지만, "딱 그만큼만 동작합니다."

  • DB 연결? 직접 구현.
  • 외부 도구 호출? 별도 라우트 생성.
  • 구조 확장성? 🤷

📌 좋았던 점

  • 빠르게 시작 가능
  • 테스트와 디버깅이 쉬움
  • 구조가 단순해서 명확함

📌 아쉬웠던 점

  • 기능 추가 시 API 설계, 문서화, 코드 작성까지 반복
  • Gemini는 내가 만든 API를 모름 😭

2. 그래서 해봤습니다: MCP 방식으로 리팩토링

이번엔 도구 중심 구조(MCP)를 도입해봤습니다.

쉽게 말하면,

“모델이 스스로 어떤 도구(tool)를 써야 할지 판단하고, 필요한 입력값을 넘겨주면, 서버는 그 도구만 실행해서 결과를 리턴해주는 구조”입니다.

app.post('/mcp/execute', async (req, res) => {
  const { tool, input } = req.body;

  if (tool === 'get-students') {
    const result = await getStudents(input);
    return res.json({ result });
  }

  // 그 외 다양한 tool 핸들링...
});

이제는 Gemini가 get-students, execute-sql, summarize-result 같은 툴들을 직접 호출할 수 있게 됐습니다.

📌 좋았던 점

  • 모델이 “할 수 있는 일”을 인식함 (도구 목록 자동 전달)
  • 새로운 기능을 도구 단위로 확장 → 훨씬 유연함
  • 여러 툴들을 조합해 쓸 수 있어 자동화에도 강함

📌 아쉬웠던 점

  • 초반 러닝커브가 있음
  • 전적으로 AI에게 프로세스를 위임하기 때문에 원하지 않는 동작을 할 확률이 높다.
  • 스펙(JSON 구조, 설명 등)을 잘못 주면 모델이 해석 못 함.

3. REST vs MCP 구조 요약 비교

항목 REST API MCP 방식
요청 방식 사용자가 명시적으로 호출 모델이 도구를 자동 호출
기능 확장 라우트 추가 필요 도구만 추가하면 됨
모델 인식 해당되는 모델의 API를 분석해서 사용 툴 정의만 해주면 AI가 알아서 프로세스를 만들어서 처리함
응답 흐름 고정된 응답 구조 모델이 결과를 요약할 수도 있음

4. 이번 개발 하면서 느낀 점

REST API 방식은 빠르게 만들고 테스트하는 데 최적입니다. 하지만 기능이 늘어나면 스펙 전달, 라우트 정리, 모델 연동 등에서 점점 무거워졌습니다.

반면 MCP 방식은 처음엔 낯설지만, 도구 단위로 기능을 나누고 모델이 이를 스스로 활용하게 만들 수 있다는 점이 상당히 "모델 친화적"이라고 느꼈습니다.

덕분에 다양한 상황에 개발 없이 툴만 만들어 놓으면 AI가 알아서 프로세스를 처리 할 수 있는 구조가 되었죠.


마무리하며

이번 실전 챗봇 구현을 통해 REST API → MCP로 넘어가는 구조적 전환을 확인했습니다.

  • 단순 기능 구현엔 REST
  • 모델 중심 워크플로우엔 MCP

다음에는 하나 하나 코드 뜯어보면서 확인해 봅시다.

감사합니다!

다음글

일단 AI가 개쩜.

자연어로 SQL을 짜준다? Gemini API로 만든 쿼리 생성 서버

"학생 수학 점수 90점 이상만 보여줘"
이걸 SQL로 자동 변환해준다구요?


1. 개요(원래 MCP 때문에 시작했는데 만들고 보니...)

AI한테 질문만 던지면 알아서 SQL로 바꿔주고,
그걸 실행해서 결과를 정리까지 해주는 서버를 만들었습니다.

  • 사용된 기술: Node.js + Express + PostgreSQL + Gemini 1.5 Flash
  • 주요 기능: 자연어 → SQL 변환 + 실행 + 결과 요약
  • 목숨 걸고 막은 것: 이상한 질문(예: 첫 사랑이 누구인지), 이상한 SQL (예: 삭제 요청 등)

2. 기본 구조 요약

import { GoogleGenerativeAI } from "@google/generative-ai";
import { Pool } from "pg";
import express from "express";
  • dotenv로 환경 변수 관리
  • PG Pool로 PostgreSQL 연결
  • Google Gemini API로 AI 프롬프트 주고받기

3. 핵심 포인트: 질문 → SQL 만들기

질문은 이렇게 생겼습니다:

{
  "question": "수학 점수가 90점 이상인 학생 목록 알려줘"
}

프롬프트는 이렇게 들어갑니다:

▶ 너는 학생 성적만 다루는 챗봇이야.
▶ SQL 말고 다른 말 하면 안돼.
▶ 국어, 수학, 영어, 과학, 사회, 체육, 미술만 질문 가능.
▶ INSERT, DELETE 하면 무조건 “못하겠습니다”로 끝내.

그리고 결과로 이런 SQL이 뿅:

SELECT name, student_number FROM students WHERE math >= 90;

4. 실행 + 요약까지 싹 다 해줌

SQL을 실행한 결과는 pg로 받아오고요.
그 다음 다시 Gemini한테 넘겨서 요약 부탁드립니다:

질문: 수학 점수 90점 이상
결과: [{ name: "홍길동", student_number: 10101 }, ...]
요약: "수학 점수가 90점 이상인 학생은 총 5명이며..."

말그대로
질문 → SQL → 실행 → 요약 4단 콤보입니다.


5. 직접 써보면 이렇게 동작합니다

POST /ask
Content-Type: application/json

{
  "question": "과학 점수가 80점 넘는 학생이 몇 명이야?"
}

응답:

{
  "sql": "SELECT COUNT(*) FROM students WHERE science > 80;",
  "rows": [{ "count": 12 }],
  "summary": "과학 점수가 80점을 넘는 학생은 총 12명입니다."
}

 

쿼리도 만들어 줘요

 

요약도 해준답니다.
답할 수 없는 질문(쿼리를 못 만드는 상황)


6. 막아야 할 건 확실히 막음

  • INSERT, DELETE 같은 위험한 쿼리는 아예 프롬프트에서 차단
  • 못하겠습니다 나오면 서버도 SQL 실행 안 함
  • 테이블이 students로 고정되어 있어 스키마도 안전하게 관리됨

7. 사용 팁

  • .env 설정 꼭 필요합니다 (GEMINI_API_KEY, DATA_BASE_URL)
  • 질문이 과목 관련이 아니면 그냥 "못하겠습니다"만 나옴
  • 결과 요약은 Gemini한테 한 번 더 맡기지만, 응답 속도 빠름 (1~2초 내외)

8. 마무리

이제는 굳이 SQL 배우지 않아도 인공지능이 사내 데이터베이스 접속해서 데이터 가져오고 정리도 다 해줍니다.
업무 시스템에서 엑셀 다운로드는 이제 그만합시다.

이제 개발자의 시대는 끝났다.

만들면서 보니 젤 성능이 떨어지는 Gemini 1.5 Flash인데도 단순하지만 이 정도 퍼포먼스에 한번 놀랐다.

 

근데 이거 코드도 AI가 짜주고 블로그도 AI가 만들어주면 나는 뭐하는 사람인가?

(역시 개발자는 유수한 역사와 전통의 빛나는 치킨집인가...)


그럴 일 없습니다. 그 사람이 저였습니다..엉..엉..엉....

 

위의 코드의 소스입니다.

https://github.com/pm2makeq/chatbot_restapi

 

GitHub - pm2makeq/chatbot_restapi: restapi chatbot

restapi chatbot. Contribute to pm2makeq/chatbot_restapi development by creating an account on GitHub.

github.com

 

.env에는 아래와 같이 설정합니다.

GEMINI_API_KEY=xxxx

DATA_BASE_URL=xxxx

 

다음글

 

 

이젠 바야흐로 대 AI의 시대!!

원피스는 그 곳에!!!

 

우리 모두다 알고 있듯이 인공지능 / 로봇 25년 너무나도 핫한 키워드다.

내가 있는 곳의 산업군이 IT쪽이다 보니 너무 절실하게 느껴진다.

 

SI 산업 변화에 큰 파도가 근 30녀간 몇 번왔는데 아래와 같이 생각된다.

2 TIRE Application(델파이, 비베) -> 3 TIRE Web Application(Java, .Net) -> 모바일(안드로이드, IOS) -> AI(현재)

 

슬슬 고객사에서도 AI 프로젝트에 대한 이야기가 내가 속해 있는 SI 업체까지 문의가 내려오는거 보면

이미 시장에 보편화되어가고 정착해가려는 과정인것 같아 보인다.

 

고객님의 요구사항은 이거다.

 

"고객님"

"현재 상품 운송정보를 지도에 표시해 주고 사용자가 문의하면 그 관련된 정보를 답해주는 서비스를 AI로 하고 싶습니다."

 

"나님"

그럼 고객한테 운송정보를 제공할 수 있는 포탈을 만드시고.. 상담사를 채용하여 문의에 답변을....

 

 

뭐시라?

"고객님"

아니 요즘 유행하는거 있잖아요!?!?! AI를 이용한 챗봇 같은거!

 

"나님"

아하 이런거요?

https://www.asiae.co.kr/article/2024080209074522225

 

[단독]AI에 수천억 썼지만…"상담 만족도 20% 그쳐"[뺑뺑이 AI콜센터]② - 아시아경제

편집자주“(AI가 아니라) ‘사람’입니다 고객님. 무엇을 도와드릴까요?” 누구나 한 번쯤은 이용할 일이 있는 콜센터, 언제나 상담원이 친절하게 전화를 받아 문...

www.asiae.co.kr

 

나 역시 몇번 사용해봤지만 정말 거지같은 사용자 경험에 결국은 "상담원 Help Me!"를 외쳤던 기억이 새록 새록하다.

그래도 위와 같은 실패하는 시도들이 있기 때문에 오늘날 같은 기술의 발전이 있는거 아니겠는가?

 

이러한 맥락으로 MCP(Model Context Protocol) 라는 기술도 생겨나고 세상은 너무나도 빠르게 흘러가고 있다.  

 

예전에는 생성형 AI(챗GPT, 클로드 같은 LLM)과 어플리테이션을 연동하려면 각 회사의 API도 알아야하고 툴도 만들어야하고 

Langchain, Lama-Index 같은것도 배워야 했다.

 

그런데

클로드를 만든 앤트로픽 회사가 생성형 AI이랑 연결할 수 있는 표준 프로토콜을 만들꺼야~ "뿌우뿌우~" 했지만 흥행 참패 ㅋㅋ

 

하지만

월 20만명이 사용하는 Cusor AI IDE에 MCP 기능을 접목 시키면서 폭발적인 인기를 끌게 되었다. 

 

다만 현재 MCP를 쉽게 사용하려면 Cursor AI나 클로드 데스크탑 ("Host"라 칭함)를 다운로드 받고

 

https://smithery.ai/

 

Smithery - Model Context Protocol Registry

Provide real-time and historical Formula 1 racing data through a standardized protocol. Access live telemetry, session statuses, driver and constructor standings, race results, weather, and circuit information seamlessly. Enhance your applications with com

smithery.ai

 

위의 사이트 가서 필요한 Tool("Server")를 사용하여 Host에 붙이면 끗!(이 과정은 정말 10분에 할수 있을 정도로 쉽다.)

 

자 이제 고객님에게 달려가 위와 같이 말씀 드리면 모든게 우리 모두 행복질 수 있습네돠아아아아아!!!! 

 

 

 

"고객님"

나의 고객님한테 저와 같은 절차로 설치하게 하라고요?????

 

"나님"

아...하하하.. 그건 좀 어렵겠죠?????

( 대한민국 실업률도 높은데 포탈하나 개발하시고 상담 인력 채용하셔서 고객 서비스 하시지...그게 대기업 낙수효과 아닌가욧!!!!!)

 

"나의 회사 임원님"

우리 고객사님의 니즈를 타계 할 뭐 아이디어 없나????

 

"나님"

찾아보겠슴돠! (그걸 알고 있음 내가 여기있겠냐!!!!)

 

까라면 까야하는 직장인의 애환이 담긴 AI 문외한의 AI 챗봇 구축기 시작.......

 

참고 기사

https://byline.network/2025/04/8-327

 
다음글

 

스프링 부트에서 위의 라이브러리를 가져다 쓰면 UI를 수정하기 굉장히 어렵다.

 

이번에도 역시 구글 신에게 도움을 받았네.

 

내용은 즉 SpringDoc-OpenApi의 스웨거 버전을 exclude 시키고 swagger UI를 수정해서 jar로 만들어

로컬에 library로 다시 넣는다. 임.

 

정말 디자인 개떡같아 외부로 보내주기 부끄러웠는데 개발자가 보기에 색감도 좋아지고 만족스럽다.

 

How to customize your Swagger UI with Spring Boot and Gradle

https://blog.devgenius.io/how-to-customize-your-swagger-ui-with-spring-boot-and-gradle-2fd32508c29a

 

How to customize your Swagger UI with Spring Boot and Gradle

So for a couple of days now I tried to find a way how to customize my Swagger UI to replace the Swagger logo and modify some CSS for it to…

blog.devgenius.io

https://github.com/ostranme/swagger-ui-themes

스웨거 테마

 

GitHub - ostranme/swagger-ui-themes: A collection of css themes to spice up your Swagger docs

:boom: A collection of css themes to spice up your Swagger docs - GitHub - ostranme/swagger-ui-themes: A collection of css themes to spice up your Swagger docs

github.com

 

+ Recent posts