Intellidesk Weaver2026년 9월 21일 / 9분 읽기

[1/6] 챗봇의 답은 텍스트여야 하나 - AI는 대답하지 말고 화면을 내라

AI 챗봇이 업무에서 활용도가 낮아지는 지점은 모델의 지능이 아니라 "답이 텍스트"라는 데 있습니다. 275행짜리 조회 결과를 마크다운 표로 받아 본 사람은 압니다. 도구 결과를 텍스트 대신 화면으로 돌려주는 표준 MCP Apps와, 그 위에서 지킨 설계 원칙 세 가지를 실제 구현으로 설명합니다.


1. 275행짜리 답변

구매 담당자가 사내 AI 챗봇에 이렇게 물었다고 해 보겠습니다.

우리 플랜트에서 아직 입고가 끝나지 않은 구매오더 품목 보여줘.

챗봇은 MCP로 붙여 둔 SAP 조회 도구를 골라 부릅니다. 여기까지는 잘 됩니다. 문제는 결과가 돌아온 다음이에요. 도구가 준 건 미결 품목 275행, 열이 열네 개인 표입니다. 이걸 모델이 받아서 마크다운 표로 옮겨 적기 시작합니다.

이때 벌어지는 일은 세 가지입니다.

첫째, 잘립니다. 275행을 전부 옮기면 답변 하나가 수천 토큰이라 모델은 앞의 20행쯤 쓰고 “나머지는 생략합니다”로 끝냅니다. 담당자가 보고 싶었던 건 대개 그 뒤에 있습니다.

둘째, 틀립니다. 숫자를 표에서 표로 옮겨 적는 일은 모델이 특별히 잘하는 일이 아닙니다. 12,400,000이 1,240,000이 되는 걸 간혹 볼 수 있습니다. 사람이 그걸 검산할 방법은 원본을 보는 것뿐인데, 원본은 모델 뒤에 숨어 있어요.

셋째, 되돌아옵니다. “지연일수 큰 순으로 정렬해 줘”라고 하면 모델은 도구를 다시 부르거나(비용, 지연), 자기가 받은 텍스트를 머릿속에서 다시 정렬합니다(위험). 엑셀에 붙여 넣으려 해도 마크다운 표를 복사하면 파이프 문자가 따라옵니다.

이 장면에서 모델을 더 좋은 것으로 바꿔도 달라지는 건 없습니다. 병목은 지능이 아니라 답이 텍스트라는 형식 자체에 있으니까요.

2. 진단 - 도구 결과를 “모델 입력”으로만 본 것

대부분의 챗봇 구현에서 도구 호출은 이렇게 생겼습니다.

사용자 → 모델 → 도구 호출 → 결과(JSON) → 모델 → 답변(텍스트) → 사용자

결과가 반드시 모델을 거쳐서 사람에게 옵니다. 그래서 결과는 모델이 읽기 좋은 크기로 잘려야 하고, 사람은 모델이 옮겨 적은 것만 봅니다.

그런데 사람이 필요로 하는 것과 모델이 필요로 하는 것은 다른 물건입니다.

사람이 필요한 것 모델이 필요한 것
전부. 275행이면 275행 판단에 필요한 만큼. 건수, 상위 몇 개, 이상치
조작 정렬, 필터, 복사, 다른 곳에 붙여 넣기 없음
정확도 원본 그대로 요약이면 됩니다
다음 행동 조건 바꿔 다시 보기 사용자 질문에 답하기

이 둘을 한 통로로 밀어 넣으니 양쪽 다 손해를 봅니다. 사람은 잘린 표를 받고, 모델은 컨텍스트를 표 데이터로 채웁니다.

그러니 통로를 둘로 나누면 됩니다. 결과 원본은 화면으로 사람에게 바로 가고, 모델에게는 요약만 간다. 모델이 할 일은 표를 그리는 게 아니라 “미결 품목이 275건이고, 일부는 50일 넘게 지연됐습니다”라고 말해 주는 것입니다.

말은 쉬운데, 채팅창 안에서 “화면”을 어떻게 만들까요. 여기서 표준이 필요합니다.

3. MCP Apps - 도구가 화면을 함께 낸다

MCP Apps는 MCP 확장입니다. 요지는 한 줄이에요. 도구가 결과와 함께 “이 결과를 그릴 UI”를 가리킬 수 있다.

서버 쪽에서는 도구 정의에 메타 한 줄이 붙습니다.

{
  name: 'mm_po_open_items',
  description: '미입고 구매오더 품목을 조회합니다',
  inputSchema: { /* 플랜트, 구매처, 기준일 … */ },
  _meta: { ui: { resourceUri: 'ui://weaver/query-list' } },
}

ui://weaver/query-list는 MCP 리소스입니다. MIME 타입이 text/html;profile=mcp-app인 HTML 한 장이에요. 채팅 호스트는 도구를 부르기 전에 이 리소스를 읽어 격리된 iframe에 띄우고, 도구 입력과 결과를 iframe 안의 앱에 넘깁니다. 앱은 그걸 그립니다. 필요하면 앱이 호스트를 거쳐 다른 도구를 부를 수도 있습니다.

앱 쪽 코드의 뼈대는 이 정도입니다.

const app = new App({ name: 'query-list', version: '1.0.0' }, {});

app.ontoolinput  = ({ arguments: args }) => conditions.fill(args); // 모델이 넣은 조건
app.ontoolresult = (result) => table.setResult(result);           // 도구 결과 전부

await app.connect();

흐름을 다시 그리면 이렇게 됩니다.

사용자 → 모델 → 도구 호출 ─┬→ 결과 전부 → 앱(iframe) → 사람이 직접 봄
                          └→ 결과 요약 → 모델 → 한두 문장 답변

같은 도구 호출 한 번에서 결과가 두 갈래로 갈라집니다. 이게 전부입니다.

MCP Apps가 확장 규격이라 아무 데서나 되는 건 아닙니다. 호스트가 지원해야 해요. Claude는 interactive connectors라는 이름으로 지원하고, 자체 채팅을 만드는 쪽은 호스트를 직접 구현해야 합니다. 저희는 둘 다였습니다. 자체 채팅에 호스트를 구현했고, 같은 서버를 Claude에도 붙였어요. 호스트 구현 이야기는 2편에서 합니다.

4. 지킨 원칙 세 가지

표준은 “결과를 UI에 넘길 수 있다”까지만 정해 줍니다. 실제로 챗봇이 쓸 만해지려면 그 위에서 몇 가지를 정해야 했습니다.

원칙 1. 화면에는 전부, 모델에는 요약만

도구 결과를 받은 서버는 같은 결과를 두 가지 크기로 만듭니다. 화면(앱)으로 가는 쪽은 전체입니다. 모델로 가는 쪽은 앱이 그릴 표라면 행 수와 앞 50행만, 그 밖의 긴 텍스트는 24,000자에서 자릅니다.

const MAX_MODEL_ROWS = 50;      // 앱이 그리는 표는 모델에 앞 50행만
const MAX_TOOL_TEXT  = 24_000;  // 그 밖의 텍스트 상한

function toModelText(out) {
  if (out.ui && out.rows) {
    return `${out.rows.length}행 (앞 ${MAX_MODEL_ROWS}행만 표시)\n` + toText(out.rows.slice(0, MAX_MODEL_ROWS));
  }
  return out.text.length > MAX_TOOL_TEXT
    ? out.text.slice(0, MAX_TOOL_TEXT) + `\n…(${out.text.length - MAX_TOOL_TEXT}자 생략)`
    : out.text;
}

275행 조회에서 모델이 받는 건 50행입니다. 컨텍스트가 5분의 1로 줄고, 모델이 “275건, 일부는 1,500일 넘게 지연”을 말하는 데 50행이면 충분합니다. 전체가 필요하면 사람이 화면에서 봅니다. 모델은 표를 옮겨 적을 이유가 없어졌으니 옮겨 적다 틀릴 일도 없어요.

원칙 2. 정렬·필터·복사는 클라이언트에서

앱은 받은 결과를 들고 있습니다. 열 머리글을 누르면 정렬하고, 필터 칸에 글자를 넣으면 이미 받은 행 안에서 거릅니다. CSV 복사는 화면에 보이는 필터와 상관없이 전체를 복사합니다. 이 세 가지 동작에 모델도, 서버도, SAP도 다시 불리지 않습니다.

당연한 얘기 같지만 텍스트 챗봇에서는 이 셋이 전부 “모델에게 다시 말하기”였습니다. 각각이 도구 재호출이고, 라이선스 호출 수이고, 몇 초의 대기였어요.

원칙 3. 앱을 못 그리는 곳에서는 텍스트로 자연스럽게 내려간다

MCP Apps를 지원하지 않는 클라이언트도 같은 서버에 붙습니다. 그쪽에는 _meta.ui가 무시되고 결과 JSON이 그대로 갑니다. 도구를 둘로 만들지 않습니다. 같은 도구가 지원하는 호스트에서는 화면으로, 아닌 호스트에서는 텍스트로 보일 뿐입니다.

이 원칙이 있어야 “우리 채팅에서만 되는 기능”이 되지 않습니다. Claude Desktop에 붙여도, 앱을 못 그리는 다른 에이전트에 붙여도 도구는 하나입니다.

5. 실제로는 이렇게 보입니다

채팅 답변 안에 뜬 조회 화면 카드. 위에 건수·지연일수 타일, 아래에 275행짜리 결과 표

구현 예 - “SP10 플랜트 미입고 구매오더 품목 보여줘”의 답. 도구 결과는 카드 안의 표(275행 · 14열)로 사람에게 바로 갑니다. 정렬·결과 내 필터·CSV 복사는 이 안에서 끝납니다.

같은 카드의 머리글을 펼치면 모델 쪽으로 무엇이 갔는지 보입니다.

도구 카드를 펼친 모습. 입력 JSON과 결과 원문의 첫머리에 rowCount 275가 보인다

구현 예 - 모델에게 넘어간 원문. rowCount: 275와 앞 50행뿐입니다. 나머지 225행은 모델이 본 적이 없고, 볼 필요도 없습니다.

이 대화에서 모델이 쓴 답은 세 줄이었습니다. “SP10 플랜트의 미입고 구매오더 품목은 275건입니다. 결과가 많아 전체 목록은 화면 표에서 확인할 수 있습니다.” 그리고 부분 입고와 전량 미입고가 섞여 있다는 것, 일부는 1,500일 넘게 지연됐다는 것. 표는 모델이 그리지 않았고, 정렬은 사용자가 열 머리글을 눌러서 했습니다.

6. 여기까지의 한계, 그리고 다음 글

이 구조로 “답이 잘리고, 틀리고, 되돌아오는” 문제는 없어집니다. 남는 문제가 둘 있어요.

하나는 호스트입니다. 3절에서 “iframe에 띄우고 결과를 넘긴다”고 한 줄로 썼지만, 실제로는 격리 정책, 앱과 호스트 사이의 메시지 브리지, 앱이 도구를 다시 부를 때의 권한, 접었다 펼 때의 상태, 외부 호스트의 미묘한 차이까지 직접 풀어야 했습니다. 표준 문서에는 없는 부분이라 2편에서 코드와 함께 다룹니다.

다른 하나는 조건입니다. 표가 화면에 떠도, 플랜트를 바꾸려면 아직 모델에게 말해야 합니다. “이번엔 B플랜트로, 구매처는 이쪽, 기준일은 저쪽”처럼 조건 다섯 개를 한 문장에 넣으면 모델은 넷은 맞히고 하나를 틀립니다. 어느 하나인지는 표만 봐서는 모르고요. 조회 상태를 대화 밖으로 꺼내는 이야기는 3편입니다.


이 글의 구현은 Intellidesk Weaver에 들어 있습니다. 다음 글: [2/6] 채팅창에 앱을 띄우는 법 - MCP Apps 호스트 구현기.

Intellidesk Weaver

SAP 자산을 AI가 쓰는 도구로, 답은 화면으로.

OData·CDS·테이블·ABAP 자산을 하나의 카탈로그로 묶어 MCP 도구로 내보내고, 결과는 채팅 안의 조회 화면·입력 폼·업무 앱으로 돌려줍니다.

Weaver 살펴보기