---
title: "MCP냐 CLI냐는 잘못된 질문입니다. 상태가 어디에 있는지 물어야 합니다"
description: "Tobi Lütke는 MCP 대 CLI 논쟁이 잘못된 계층에서 벌어지고 있다고 말합니다. 단계 사이에 상태가 유지되면 둘 다 잘 작동하기 때문입니다. 동의합니다. 동기화에서 그 상태는 REPL의 힙이 아니라 연동 자체의 레코드이며, 그래서 Tajo의 MCP 도구는 세션에 아무것도 보관하지 않습니다."
url: https://tajo.io/kr/blog/mcp-or-cli-is-the-wrong-question/
markdown: https://tajo.io/kr/blog/mcp-or-cli-is-the-wrong-question.md
lang: ko-KR
author: "Set Noa"
author_url: https://tajo.io/kr/set-noa/
published: 2026-11-04
updated: 2026-11-05
alternates: {en-US: /blog/mcp-or-cli-is-the-wrong-question.md, bg-BG: /bg/blog/mcp-or-cli-is-the-wrong-question.md, cs-CZ: /cz/blog/mcp-or-cli-is-the-wrong-question.md, de-DE: /de/blog/mcp-or-cli-is-the-wrong-question.md, da-DK: /dk/blog/mcp-or-cli-is-the-wrong-question.md, es-ES: /es/blog/mcp-or-cli-is-the-wrong-question.md, fr-FR: /fr/blog/mcp-or-cli-is-the-wrong-question.md, el-GR: /gr/blog/mcp-or-cli-is-the-wrong-question.md, hi-IN: /hi/blog/mcp-or-cli-is-the-wrong-question.md, id-ID: /in/blog/mcp-or-cli-is-the-wrong-question.md, hr-HR: /hr/blog/mcp-or-cli-is-the-wrong-question.md, hu-HU: /hu/blog/mcp-or-cli-is-the-wrong-question.md, it-IT: /it/blog/mcp-or-cli-is-the-wrong-question.md, ja-JP: /jp/blog/mcp-or-cli-is-the-wrong-question.md, ar-SA: /ar/blog/mcp-or-cli-is-the-wrong-question.md, nl-NL: /nl/blog/mcp-or-cli-is-the-wrong-question.md, no-NO: /no/blog/mcp-or-cli-is-the-wrong-question.md, pl-PL: /pl/blog/mcp-or-cli-is-the-wrong-question.md, pt-BR: /pt/blog/mcp-or-cli-is-the-wrong-question.md, ro-RO: /ro/blog/mcp-or-cli-is-the-wrong-question.md, ru-RU: /ru/blog/mcp-or-cli-is-the-wrong-question.md, sv-SE: /se/blog/mcp-or-cli-is-the-wrong-question.md, zh-CN: /zh-cn/blog/mcp-or-cli-is-the-wrong-question.md, sl-SI: /sl/blog/mcp-or-cli-is-the-wrong-question.md, sr-Latn-RS: /sr/blog/mcp-or-cli-is-the-wrong-question.md, th-TH: /th/blog/mcp-or-cli-is-the-wrong-question.md, tr-TR: /tr/blog/mcp-or-cli-is-the-wrong-question.md, ur-PK: /ur/blog/mcp-or-cli-is-the-wrong-question.md, vi-VN: /vi/blog/mcp-or-cli-is-the-wrong-question.md, sk-SK: /sk/blog/mcp-or-cli-is-the-wrong-question.md}
---

[AI 에이전트](https://tajo.io/kr/blog/?category=ai-agents)

# MCP냐 CLI냐는 잘못된 질문입니다. 상태가 어디에 있는지 물어야 합니다

Tobi Lütke는 MCP 대 CLI 논쟁이 잘못된 계층에서 벌어지고 있다고 말합니다. 단계 사이에 상태가 유지되면 둘 다 잘 작동하기 때문입니다. 동의합니다. 동기화에서 그 상태는 REPL의 힙이 아니라 연동 자체의 레코드이며, 그래서 Tajo의 MCP 도구는 세션에 아무것도 보관하지 않습니다.

MCP CLI 비교

AI 에이전트는 MCP와 CLI 중 무엇을 써야 할까요?

MCP 대 CLI가 잘못된 계층이라는 Lütke의 말은 옳습니다. 단계 사이에 상태가 유지되면 둘 다 잘 작동하고, bash가 이기는 이유는 파일 시스템이 바로 그 상태이기 때문일 뿐입니다. 하지만 동기화에서 상태는 REPL의 힙이 아닙니다. 연동 자체의 레코드, 즉 커서, 대기 중인 제안, 리스, 계획입니다. 그래서 Tajo의 MCP 도구는 세션에 아무것도 보관하지 않으며, 어떤 클라이언트의 어떤 에이전트든 레코드에서 이어서 작업합니다. Tajo는 현재 고객 코드를 실행하지 않고 REPL도 없으며, 아래에서 이를 분명히 밝힙니다.

[자세히 알아보기](https://tajo.io/kr/blog/mcp-or-cli-is-the-wrong-question/#%EA%B7%B8%EA%B0%80-%EB%A7%90%ED%95%98%EB%8A%94-%EA%B3%84%EC%B8%B5)

2026년 9월 21일 UTC 00시 33분, Tobi Lütke는 이렇게 게시했습니다. “LLM을 위한 MCP 대 CLI는 잘못된 논의다. 사람들은 잘못된 계층에서 논쟁하고 있다.” 그리고 [10월 23일](https://x.com/tobi/status/2113596638899974567), 그는 새로 나온 [dquickts](https://github.com/stencil-hq/dquickts)라는 저장소를 가리켰습니다. 이것은 QuickJS의 포크로, `for` 루프 중간에 프로그램을 멈추고, 힙 전체를 바이트로 기록한 뒤, 다른 프로세스에서 다시 실행할 수 있습니다. 그는 “훌륭한 코드 모드 환경”이라고 썼습니다.

계층에 대해서는 그가 옳습니다. 먼저 그의 말로 그에게 동의하고, 그다음 동기화에 쓸모 있는 무언가를 말할 때까지 주장을 좁혀 보겠습니다.

## 그가 말하는 계층

그가 펼친 그대로의 논지를 인용합니다. 비틀려는 논지를 인용할 때 공정한 방법은 이것뿐입니다.

> 둘 다 상태를 유지할 수 있는 REPL 같은 환경을 통해 실행되는 한 놀라울 만큼 잘 작동한다. CLI의 재미있는 점은 CLI 도구가 보통 BASH를 통해 사용되는데, BASH가 마침 영구 상태(파일 시스템)를 가진 REPL이라는 것이다. 그래서 CLI가 MCP보다 훨씬 잘 작동한다. 하지만 이것은 MCP의 본질적인 속성과는 거리가 멀다. 그저 더 나은 하네스가 필요하다는 뜻일 뿐이다.

이 말은 옳고, 많은 사람이 설명 없이 관찰해 온 현상을 설명해 줍니다. CLI를 받은 에이전트는 파일을 쓰고, 세 단계 뒤에 다시 읽고, grep하고, 파이프로 연결합니다. 맨 MCP 서버를 받은 에이전트는 호출당 결과 하나를 받고 나머지는 모두 컨텍스트 창에 담아 다녀야 합니다. CLI는 프로토콜로 이긴 것이 아닙니다. bash에 물건을 둘 자리가 딸려 있었기 때문에 이긴 것입니다.

그의 예측은 여기서 나옵니다. 누군가 SQLite처럼 임베드할 수 있는 엔진을 만들어 bash, TypeScript 또는 도구 호출을 하나의 계획으로 바꾸고, 실행 전에 검사하며, 지속 실행을 위해 설계한다는 것입니다. dquickts는 그 방향으로 가는 한 걸음입니다. [그 README](https://github.com/stencil-hq/dquickts)는 “함수와 변수가 재시작 후에도 살아남는” REPL을 설명합니다. 지속형 호스트 호출도 설명합니다. “스크립트가 몇 시간 뒤에 응답이 오는 호스트 함수를 호출한다. 프로세스는 스크립트를 저장하고 종료한다.”

여기까지는 전적으로 동의합니다. 이제 범위를 좁히겠습니다.

## 어떤 상태가, 어떤 작업을 위해

“상태를 유지한다”는 말에는 질문이 숨어 있습니다. 누구의 상태를, 어떤 형태로 유지하느냐는 것입니다. 코딩 에이전트에게 상태는 저장소와 셸이고, 재시작 후에도 살아남는 REPL은 좋은 답입니다. 두 SaaS 시스템 사이의 동기화를 구축하는 에이전트에게 단계 사이에 필요한 상태는 작업 자체가 이미 정의해 두었습니다. 네 개의 레코드가 그 상태를 담고 있으며, 그중 어느 것도 스크립트의 변수가 아닙니다.

**커서.** 동기화는 소스를 윈도 단위로 읽으며, 마지막 윈도가 어디서 끝났는지 알아야 합니다. Tajo의 동기화 엔진에서 이것은 동기화와 범위마다 [저장되는 커서](https://tajo.io/kr/resources/help/troubleshooting/recover-a-stuck-or-failed-sync/#cursor)로, 실패한 레코드가 없는 배치마다 기록되며 다음 실행은 그 지점에서 이어집니다. 예약 실행은 다른 무엇보다 먼저 시작 지점을 저장하므로, 첫 실행이 실패해도 다음 실행은 같은 지점에서 읽습니다. 그 바탕에 있는 원칙, 즉 테이블이 아니라 변경분만 보낸다는 원칙은 [리버스 ETL 설명](https://tajo.io/kr/blog/what-is-reverse-etl/)에 있습니다. 커서 이야기는 거기에 맡기겠습니다.

**대기 중인 제안.** Tajo의 커넥터 MCP 도구에서 쓰기는 두 번의 호출입니다. `brevo_propose`는 선언된 액션에 비추어 입력을 검증하고 제안을 기록합니다. 아무것도 전송되지 않습니다. 결과에는 제안 ID가 담기고, 사람이 결정해야 할 때는 그것을 승인하는 페이지도 담깁니다. `brevo_apply`는 해당 연결의 승인된 쓰기를 순서대로 실행하고, 아직 사람을 기다리는 첫 번째 쓰기에서 멈춥니다. 그 사이에 있는 레코드가 상태입니다. 이 레코드는 입력과 그 다이제스트를 담고 있어서, 승인된 쓰기를 결정과 적용 사이에 수정할 수 없습니다. 공급자의 응답 값은 담지 않습니다. 추가 전용 승인 레코드가 개인 데이터 저장소가 되어서는 안 되기 때문입니다.

**리스.** 동기화의 한 실행은 전체 수명 동안 하나의 [배타적 리스](https://tajo.io/kr/resources/help/troubleshooting/recover-a-stuck-or-failed-sync/#lease)를 보유합니다. 리스에는 소유자 토큰, 기본 15분의 시간 제한, 갱신이 있습니다. 오래된 소유자는 더 새로운 리스를 해제할 수 없습니다. 리스 저장소에 연결할 수 없으면 실행은 추측하지 않고 시작을 거부합니다. 어느 워커가 실행을 맡고 있는지는 어떤 프로세스의 메모리가 아니라 리스 안에 있는 사실입니다.

**계획.** `syncs.dryRun`은 커서에서 다음 윈도를 읽고, 레코드마다 삽입, 업데이트, 삭제, 변경 없음을 판단한 뒤 정확히 한 행을 기록합니다. 그것이 계획이며 한 시간 동안 보관됩니다. 커서를 옮기지 않고 어느 시스템에도 아무것도 쓰지 않습니다. `syncs.apply`는 그 계획을 한 번 기록합니다. 커서가 움직였거나, 그사이 허용 비율을 넘는 레코드가 바뀌었으면 거부합니다. 반복 호출하면 다시 쓰는 대신 첫 번째 결과를 돌려줍니다.

이것이 단계 사이의 상태입니다. ID를 가진 네 개의 레코드로, 볼 권한이 있는 어떤 에이전트든 읽을 수 있습니다.

## MCP 쪽이 세션에 아무것도 보관하지 않는 이유

상태가 레코드 안에 있으면 세션이 붙들고 있을 것이 남지 않습니다. 그래서 Tajo의 MCP 엔드포인트는 세션을 유지하지 않습니다. 요청마다 새 서버와 세션 ID가 없는 전송 계층이 주어집니다. 읽기는 호출당 요청 하나이며, 하나의 답이 한 턴을 가득 채우지 않도록 결과에 상한이 있습니다. 쓰기는 제안한 다음 적용하며, 들고 다니는 것은 제안의 ID입니다.

프로토콜도 같은 방향으로 움직였습니다. [MCP 사양의 2026-07-28 개정](https://modelcontextprotocol.io/specification/2026-07-28/changelog)은 Streamable HTTP 전송에서 프로토콜 수준의 세션과 `Mcp-Session-Id` 헤더를 제거했습니다. 연속성이 필요한 서버는 이제 자체 핸들을 발급하고 그것을 일반 인수로 돌려받습니다. 제안 ID가 바로 그것입니다. [MCP 서버란 무엇인가](https://tajo.io/kr/blog/what-is-an-mcp-server/)에서 그 개정과 도구 호출이 지속적인 동기화와 어떻게 다른지를 다룹니다. 이 글은 상태가 어디로 가는지만 다룹니다.

연동 작업에서 중요한 결과는 이것입니다. Claude Code의 에이전트가 쓰기를 제안하고 그 세션이 끝났다고 합시다. 한 시간 뒤 사람이 그것을 승인합니다. 다른 클라이언트의 에이전트, 혹은 다음 날의 같은 에이전트가 ID로 apply를 호출하면 쓰기는 한 번 반영됩니다. 충돌한 에이전트는 컨텍스트 창을 잃을 뿐 그 밖에는 아무것도 잃지 않습니다. 아무것도 중단되지 않았으므로 아무것도 재개할 필요가 없었습니다.

## 우리에게 없는 것, 그리고 그 대가

정당한 반론은 REPL 쪽에서 나오며 이렇습니다. “좋습니다, 레코드는 살아남습니다. 하지만 호출할 때마다 이전 호출이 이미 가지고 있던 것을 다시 읽습니다. 스크립트라면 연락처를 한 번 가져와 결합하고 걸러서 숫자 하나를 돌려줄 것입니다. 여러분의 에이전트는 다섯 번 호출하고 매번 비용을 치릅니다.” 이 반론은 메커니즘에 관해서는 옳으며, 우리는 그 비용을 측정하지 않았습니다. 상태를 들고 다닐 때와 그렇지 않을 때 작업당 호출 수를 세는 평가는 아직 만들어지지 않았습니다. 그것이 만들어지기 전까지, 호출 모드가 토큰을 더 쓴다는 주장은 반박되지 않은 채로 남습니다.

세 가지를 더 분명히 밝혀야 합니다. 이 글을 다 읽은 독자가 그것들이 있다고 상상하지 않도록 하기 위해서입니다.

- **현재 고객이 작성한 코드는 실행되지 않습니다.** 커넥터 읽기는 호출당 요청 하나이며, 그 경로에 샌드박스는 없습니다. 우리 스택에 코드 러너가 있지만 내부 도구입니다. 프로덕션에서는 두 개의 별도 플래그가 설정되지 않으면 꺼져 있고, 관리자에게만 응답합니다. 호출마다 새 컨텍스트, 읽기만 허용하는 바인딩, 동기 코드에 대한 2초 예산이 주어집니다. 러너의 헤더 자체가 이것은 정확성의 경계이지 보안의 경계가 아니라고 대문자로 밝힙니다.
- **REPL이 없습니다.** 에이전트가 한 호출에서 계산한 것은 ID를 가진 레코드로 기록되지 않는 한 다음 호출에서 쓸 수 없습니다.
- **영구 에이전트 세션이 없습니다.** 클라이언트는 대화를 보관합니다. Tajo는 제안, 계획, 커서, 리스를 보관합니다. 세 번째 것은 없습니다.

## 지속형 엔진은 무엇을 위한 것인가

이 중 어느 것도 dquickts에 반대하는 주장이 아닙니다. README는 스냅숏의 용도를 나열하며, 그 목록은 훌륭합니다. 유휴 스크립트를 최대 절전 상태로 두고 메모리를 해제한다. 긴 계산을 비어 있는 워커에서 조각으로 나눠 실행한다. 설정을 반복하는 대신 모든 인스턴스를 스냅숏에서 웜 스타트한다. 나중에 응답이 오는 호스트 호출에 스크립트를 대기시킨다. 코딩 에이전트의 작업 공간이나 재시작 후에도 살아남아야 하는 노트북에는 이것이 알맞은 도구입니다.

그래도 동기화는 이것에 의존해서는 안 되며, 그 이유 세 가지는 README가 직접 알려 줍니다. 첫째, “세션 파일은 그것을 쓴 qjs 빌드에만 로드된다.” 한 엔진 버전이 쓴 힙은 업그레이드 후에는 죽은 파일이 되지만, 커서는 문자열입니다. 둘째, “스냅숏은 바이트코드처럼 신뢰된 입력이다”, 그리고 “악의적인 스냅숏은 탐지되지 않는다.” 저장된 힙은 보호해야 하는 대상이지 검토할 수 있는 대상이 아닙니다. 제안은 사람이 승인하기 전에 읽는 행입니다. 셋째, 스냅숏은 고객 레코드를 포함해 스크립트가 읽은 모든 것을 담고 있습니다. 위의 레코드는 그 반대로 만들어졌습니다. 계획은 값이 아니라 다이제스트를 저장하고, 제안 레코드는 공급자의 응답을 절대 저장하지 않습니다.

우리가 따르는 원칙 중 하나는 결정적인 것이 영리한 것보다 낫다는 것입니다. 루프 중간에서 재개되는 힙은 영리합니다. 커서, 제안, 리스, 계획은 따분하고, 검사할 수 있으며, 어떤 엔진에서든, 어떤 클라이언트에서든, 어떤 충돌 후에든 똑같습니다.

Lütke는 올바른 질문을 던졌습니다. 상태는 어디에 있는가? 동기화에서 그 답은 결코 프로세스 안이 될 수 없었습니다. 답은 언제나 레코드 안이어야 했습니다. 쓰기를 승인하는 사람이 그것을 읽을 수 있어야 하는 곳이 바로 거기이기 때문입니다.

## 자주 묻는 질문

**AI 에이전트에는 MCP와 CLI 중 무엇이 더 낫습니까?**

그 자체로는 어느 쪽도 아닙니다. 2026년 9월 21일 Tobi Lütke의 지적은 유효합니다. 단계 사이에 상태를 유지하는 무언가를 통해 호출이 실행되면 둘 다 잘 작동합니다. 오늘날 CLI가 더 낫게 느껴지는 것은 bash가 파일 시스템에 상태를 보관하기 때문입니다. 차이는 프로토콜이 아니라 하네스에 있습니다.

**에이전트는 동기화의 단계 사이에 어떤 상태가 필요합니까?**

연동 자체의 레코드입니다. 마지막 실행이 어디서 멈췄는지 알려 주는 커서, 사람의 승인을 기다리는 제안, 어느 워커가 실행을 맡고 있는지 알려 주는 리스, 그리고 apply가 일치해야 하는 드라이 런 계획입니다. 그중 어느 것도 스크립트의 변수가 아닙니다. ID를 가진 행입니다.

**Tajo의 MCP 서버는 왜 세션에 아무것도 보관하지 않습니까?**

요청마다 새 서버와 세션 ID가 없는 전송 계층이 주어집니다. 다음 호출에 필요한 것은 모두 레코드입니다. 제안 ID, 계획 ID, 커서입니다. 그래서 다른 클라이언트에 있는 두 번째 에이전트가 첫 번째 에이전트가 멈춘 곳에서 이어서 작업할 수 있고, 충돌한 에이전트는 자신의 컨텍스트 창 외에는 아무것도 잃지 않습니다.

**Tajo는 에이전트가 작성한 코드를 실행합니까?**

고객을 위해서는 실행하지 않습니다. 커넥터 도구의 읽기는 호출당 요청 하나이며 샌드박스가 없습니다. 존재하는 코드 러너는 두 개의 플래그 뒤에 있는 내부 도구로, 관리자만 접근할 수 있고, 읽기 전용 바인딩을 가지며, 호출마다 새 컨텍스트를 사용합니다. 이 러너는 스스로 보안 경계가 아니라고 밝힙니다. REPL도, 영구 세션도 없습니다.

**dquickts 같은 지속형 JavaScript 엔진은 어디에 유용합니까?**

README가 용도를 밝힙니다. 유휴 스크립트를 최대 절전 상태로 두기, 긴 계산을 여러 워커에 나눠 실행하기, 스냅숏에서 웜 스타트하기, 몇 시간 뒤에 응답이 오는 호스트 호출에 스크립트를 대기시키기입니다. 모두 실제 용도입니다. 그래도 동기화는 이것에 의존해서는 안 됩니다. 스냅숏은 그것을 쓴 엔진 빌드에만 로드되고, 검토자에게는 불투명하며, 스크립트가 읽은 고객 데이터를 그대로 담고 있기 때문입니다.

## 관련 글

### [답은 맞아도 경로가 틀릴 수 있습니다](https://tajo.io/kr/blog/the-answer-is-right-the-path-is-wrong/)

2026년 10월 8일 · 3 분 읽기

3개 섹션

### [MCP 도구 호출마다 무엇을 로그로 남겨야 할까](https://tajo.io/kr/blog/what-to-log-for-every-mcp-tool-call/)

2026년 11월 6일 · 6 분 읽기

6개 섹션

### [MCP 서버란 무엇인가? 2026년 MCP와 MCP UI의 작동 방식](https://tajo.io/kr/blog/what-is-an-mcp-server/)

2026년 10월 3일 · 11 분 읽기

1단계 → 6단계
