clasp에 MCP 모드가 도입된 지금, GAS용 자체 제작 MCP에 무엇을 남길 것인가 — 본체와의 역할 분담 및 push 전에 멈추는
요약
본 기사는 GAS 개발을 위한 자체 제작 MCP 툴이 clasp 본체에 MCP 모드가 도입되면서 역할 분담을 고민하는 내용을 다룹니다. 작성자는 자체 툴 대신, 본체가 갖지 못한 'push 전 중단 메커니즘'과 로컬 에뮬레이터 기능만 남기는 방향으로 역할을 재정립했습니다.
핵심 포인트
- 자체 제작 툴은 clasp 본체의 MCP 모드 도입으로 역할이 종료됨.
- 핵심적으로 유지할 기능은 'push 전 중단 메커니즘'과 로컬 에뮬레이터.
- clasp push는 로컬의 대상 파일 일체로 클라우드를 덮어쓰므로 데이터 손실 위험에 유의해야 함.
- AI 에이전트가 GAS를 다루기 위한 개발 워크플로우와 도구 통합을 논함.
본 기사는 다음 사람들을 위해 작성되었습니다.
- AI 에이전트에게 Google Apps Script (GAS) 개발을 시키고 싶은 사람
- clasp 본체의 MCP 모드(
clasp mcp)와 자체 제작 툴 중 어느 것을 사용할지 고민하는 사람 - 실제 GAS 프로젝트를 AI가 다루는 것이 불안한 사람
※ AI 에이전트는 Antigravity를 사용했습니다
※ 제1회, 제2회, 제3회의 속편입니다
전제: clasp 본체에 MCP 모드(clasp mcp)가 도입되면서, clasp를 MCP화한 것만으로 만든 자체 제작 툴은 더 이상 필요하지 않게 보였습니다.
- 발생한 일: 자체 제작 툴을 실제 프로젝트에서 다시 실행해 보니, push 과정에서 클라우드의 파일이 삭제될 위기에 처하는 장면이 있었습니다. 또한, 자체 제작 검증 기능도 AI에게 필요한 정보를 반환하고 있지 않았습니다.
- 방침: clasp 본체와 동일한 기능을 자체적으로 구현하기보다는, 본체에 없는 'push 전에 멈추는 메커니즘'과 '로컬 에뮬레이터', 그리고 'run・logs・deploy' 기능만을 남기는 역할 분담을 했습니다.
본 시리즈(제1회~제3회)에서는 AI 에이전트가 Google Apps Script (GAS)를 직접 조작할 수 있도록, CLI 툴인 clasp를 Model Context Protocol (MCP) 서버로 감싼 자체 제작 툴(gas-manager-mcp)을 만들었습니다.
그 후, clasp 본체에 MCP 모드(clasp mcp)가 추가되었습니다. 2026년 11월 시점의 clasp 3.3.0 버전에는 실험적인 기능(EXPERIMENTAL)으로 다음 5가지 툴이 있습니다.
push_files: 로컬 코드를 클라우드로 반영합니다.pull_files: 클라우드 코드를 로컬로 가져옵니다.create_project: 프로젝트를 새로 만듭니다.clone_project: 기존 프로젝트를 로컬에 복제합니다.list_projects: 프로젝트 목록을 가져옵니다.
clasp는 Google의 GitHub 조직에서 공개된 툴입니다. 다만 README 시작 부분에는 'Google 공식 지원 제품이 아닙니다(This is not an officially supported Google product.)'라고 명시되어 있습니다. 본 기사에서는 '공식'이라는 표현 대신 'clasp 본체'라고 부르겠습니다.
clasp 본체가 MCP로 작동할 수 있다면, clasp를 호출하는 것만으로 만든 자체 제작 툴을 계속 유지할 이유가 없습니다. 자체 제작 툴은 역할이 끝났으며, clasp 본체 하나로 통합하는 것이 자연스럽다고 생각했습니다.
통합하기 전에, 자체 제작 툴에 무엇을 남길 수 있는지 확인하기 위해 실제로 작동 중인 GAS 프로젝트에서 동작 검증을 진행했습니다. 대상은 Qiita의 기사 통계를 매일 스프레드시트에 기록하는 프로젝트입니다.
검증 당시 클라우드 측에는 다음 3개의 파일이 있었습니다.
appsscript.json: 매니페스트 설정qiita_stats_v2.js: Qiita API에서 통계를 가져와 스프레드시트에 기록하는 메인 처리 스크립트Webhook.js: 스프레드시트의 변동을 매시간 감시하여 Discord로 알림을 보내는 스크립트
그런데 로컬의 .claspignore 설정은 다음과 같았습니다.
**/**
!qiita_stats_v2.js
!appsscript.json
Webhook.js는 로컬로 가져온 후에도 push 대상에서 제외되어 있었습니다. clasp push는 로컬의 대상 파일 일체로 클라우드를 덮어씁니다. 이 상태에서 코드가 전송되었다면, Webhook.js는 클라우드의 HEAD에서 사라졌을 것입니다. 과거 버전을 만들어 두지 않았다면 되돌릴 수 없습니다.
이번에는 clasp가 'Script is already up to date'로 판정하여 전송하지 않았기 때문에 삭제는 일어나지 않았습니다. 막을 수 있었던 것은 우연이었습니다. '로컬과 클라우드가 일치한다'는 전제가 무너졌던 사례였습니다.
제3회에서는 에뮬레이터가 api.gs:12와 같이 오류의 위치를 알려주었고, AI가 그것을 읽고 수정하는 로그를 담았습니다.
구현을 다시 읽어보니, 당시 반환되는 것은 '정의되지 않은 호출: 1건'과 같은 건수만었습니다. 파일 이름이나 라인 번호, 무엇이 부족한지는 포함되어 있지 않았습니다. AI는 '무언가 문제가 있다'는 사실은 알지만, 어디를 고쳐야 할지는 모르는 상태였습니다.
인자의 개수는 괄호 안을 쉼표로 구분하여 세고 있었습니다. 문자열 리터럴은 사전에 대체했기 때문에, "a,b"와 같은 문자열은 문제가 되지 않습니다. 반면, setProperty(f(a, b))처럼 인자 안에 함수 호출이 있으면, 실제로는 1개의 인자인데도 2개로 세어 놓치게 됩니다.
사람이 수동으로 작업한다면 '로컬에는 없는데 클라우드에 있는 파일' 상태를 알아차리기 쉽습니다. 하지만 clasp push는 로컬의 대상 파일로 클라우드 측 구성을 덮어씁니다. 가져오지 않은 파일이나, .claspignore로 제외한 파일은 아무런 확인 없이 사라집니다.
에뮬레이터가 warnings를 반환하더라도 결과가 success라면, AI가 그대로 push를 진행하는 것을 막을 방법이 없습니다. 사람은 경고를 보고 멈추지만, AI를 멈추려면 오류(error)로 반환해야 합니다.
clasp 3.3.0의 push_files는 내용을 검증하지 않고 clasp의 push를 실행합니다. 대신 파괴적인 작업임을 나타내는 주석(destructiveHint: true)이 붙어 있습니다. 이 주석을 처리할 수 있는 MCP 클라이언트는 실행 전에 사용자에게 승인을 요청받습니다.
즉, clasp 본체는 '위험한 작업이라는 것을 클라이언트에게 알리고, 실행 여부는 사람이 결정하는' 설계입니다. 사람이 한 번씩 승인한다면 이것으로 충분합니다. AI에게 수정과 push를 계속 맡기고 싶다면, 승인할 때마다 사람의 손이 멈추게 됩니다.
자체 제작 도구는 내부적으로 clasp의 CLI를 호출하여 작동합니다. clasp 본체와 충돌하는 것이 아니라, clasp 위에 올라가 부족한 부분을 보완하는 것입니다.
따라서 clasp 본체와 같은 것은 자체 제작에 포함하지 않고, 본체에는 없는 부분만 남기기로 했습니다.
| 담당 | 기능 |
|---|---|
| clasp 본체의 MCP 모드 | 프로젝트 생성/복제/목록 보기 (create_project / clone_project / list_projects) |
자체 제작 gas-manager-mcp | push (사전 검사 포함), pull (실제 폴더 가드 포함), 로컬 에뮬레이터, run / logs / deploy / deployments / status |
push와 pull은 실제 폴더를 보호하는 가드(guard)를 거치고 싶기 때문에 자체 제작 쪽에 두었습니다. clasp 본체의 pull_files와 중복되지만, 이번 검증이 실제 폴더에서의 pull에서 시작되었다는 점을 고려하여 가드가 작동하는 쪽에 배치했습니다.
두 MCP 서버를 모두 넣으면, AI에게는 clasp 본체의 push_files와 자체 제작의 gas_push 둘 다 보이게 됩니다. AI가 push_files를 선택하면, 자체 제작의 검사를 모두 건너뜁니다. pull_files와 gas_pull도 마찬가지로, pull_files를 사용하면 실제 폴더의 가드가 작동하지 않습니다.
따라서 MCP 클라이언트 설정에서 clasp 본체의 push_files와 pull_files를 사용할 수 없도록 제어해야 합니다.
Claude Code의 경우:
개별 도구를 목록에서 숨길 수는 없지만, 설정 파일(.claude/settings.local.json)의 permissions.deny에 지정함으로써 Claude가 실행하려고 할 때 자동으로 차단(거부)하는 사양이 있습니다 (공식 리퍼런스 기재 권한 설정). .claude/settings.local.json{ "permissions": { "deny": [ "mcp__clasp__push_files", "mcp__clasp__pull_files" ] } }
도구 개별 비활성화가 불가능한 클라이언트(Antigravity 등)의 경우
MCP 서버 단위로만 등록 및 해제가 가능한 환경에서는, "규칙에 사용하지 마라"라고 명시하는 것만으로는 AI가 이를 우회할 위험이 있으며, 이는 본 기사에서 강조하는 '약속이 아닌 시스템으로 막는다'는 원칙과 모순됩니다.
따라서, 도구 단위의 블록 기능이 없는 클라이언트에서는 clasp 본체의 MCP 서버를 처음부터 등록하지 않는 운영을 권장합니다. 프로젝트 생성(create)이나 복제(clone)는 빈번하게 수행하는 작업이 아니므로, 필요한 경우에만 사람이 일반적인 clasp CLI 명령어를 직접 실행하는 것이 오작동의 허점을 막을 수 있습니다.
(※ 참고로, 셸 실행 권한을 가진 에이전트의 경우, 자체 제작 MCP의 가드를 거치지 않고 터미널에서 직접 clasp push를 실행할 수 있는 경로가 남아 있습니다. 이 위험을 방지하려면, clasp 명령어 실행은 사람이 수동으로 수행하는 운영 방식이거나, 에이전트 측의 셸 실행 허가에서 clasp push를 거부하는 제어가 필요합니다.)
clasp의 MCP 모드는 2026년 11월 기준으로 실험적인 기능입니다. 향후 공식 기능이 되거나, push 전 검증이 본체에 통합된다면, 자체 제작하는 부분은 그만큼 축소할 것입니다. 본 기사의 구성은 그때까지의 역할 분담(棲み分け)을 다루고 있습니다.
push 전에 Apps Script API의 projects.getContent를 사용하여 클라우드 상의 파일 목록을 가져옵니다. 이 목록과 push 대상 목록(clasp status --json의 filesToPush)을 비교하여, 클라우드에만 존재하는 파일이 있다면 push를 중단합니다.
// 클라우드 측 파일명과 push 대상 대조 (간략화된 발췌)
export function detectRemoteOrphanDeletions(
filesToPush: string[],
...
인증 정보를 읽을 수 없거나, API 호출에 실패하는 등 클라우드 측 목록을 확인할 수 없는 경우에도 push를 중단합니다. 확인하지 않은 상태로 진행하면 무엇이 삭제될지 알 수 없기 때문입니다. 삭제를 인지하고 진행할 때만 { allowDeleteRemote: true }를 전달합니다.
push 전에, push 대상 파일만을 대상으로 함수의 중복 정의와 인자 부족 여부를 검사합니다. 발견된 경우 경고가 아닌 오류로 반환하며, 파일명, 행 번호, 내용을 첨부합니다. 이는 트러블슈팅 2와 트러블슈팅 3을 거치며 수정한 부분입니다.
🚫 [PUSH BLOCKED] Pre-push validation detected blocking errors in your GAS code.
Fix the issues below or pass { skipValidation: true } if you explicitly wish to bypass this check:
❓ UNDEFINED CALLS: 1 issue(s) detected:
...
운영 환경으로 지정된 폴더에 대한 push, pull, run, deploy 등은 { allowProtected: true }를 명시하지 않는 한 거부됩니다. 보호할 폴더는 환경 변수 GAS_PROTECTED_DIRS로 지정합니다 (쉼표 구분자로 여러 개 지정 가능). 아무것도 지정하지 않으면 어떤 폴더도 보호되지 않습니다.
claude mcp add gas-manager -- ssh sv-home GAS_PROTECTED_DIRS=/path/to/production /usr/bin/node /path/to/gas-manager-mcp/out/index.js
문자열 경로만 비교하는 것만으로는 심볼릭 링크를 통해 우회될 수 있으므로, fs.realpathSync를 사용하여 실제 경로로 변환한 후 비교합니다.
// 실제 경로로 변환하여, 보호 대상 폴더 하위에 있는지 판정 (발췌)
export function isProtectedDirectory(dir: string, customDirs?: string[]): boolean {
const canonicalDir = getCanonicalPath(dir);
...
대상 함수의 호출 역시 vm.runInContext를 거치도록 하고, 5000ms로 시간 제한을 설정합니다. 이전에는 함수를 직접 호출했기 때문에, 무한 루프가 발생하면 에뮬레이터 전체가 멈추고 MCP 클라이언트도 응답하지 않는 문제가 있었습니다.
// 함수의 호출도 vm을 통해 실행하며, 무한 루프는 timeout으로 종료
(vmContext as any).__emuArgs = funcArgs;
result = vm.runInContext(${functionName}(...__emuArgs), vmContext, { timeout: 5000 });
clasp 본체에 맞춰 클라우드나 로컬을 수정하는 gas_push
・gas_pull
・gas_run
・gas_deploy
에는 destructiveHint: true를 추가했습니다. 읽기 전용인 gas_status
・gas_logs
・gas_deployments
・gas_emulate
에는 readOnlyHint: true를 적용했습니다.
직접 만든 검사 메커니즘으로 내용을 확인하고 멈추게 하는 것 외에도, 주석을 처리할 수 있는 MCP 클라이언트에서는 실행 전에 사람의 승인을 요청하는 기능도 사용할 수 있습니다. 어느 한쪽만 사용하는 것이 아니라, 이 두 가지를 결합한 형태입니다.
// 도구 정의의 주석 (발췌)
{
name: "gas_push",
...
| 항목 | clasp 본체의 MCP 모드 | 자체 제작 gas-manager-mcp |
|---|---|---|
| 생성・복제・목록 보기 | 있음 | 없음 (본체에 위임) |
| pull | 있음 (destructiveHint로 사람의 승인 유도) | 있음 (운영 폴더 보호 기능 포함) |
| push | 있음 (검증 없음, destructiveHint로 사람의 승인 유도) | 있음 (사전 코드 검사, 클라우드에만 있는 파일 감지, 운영 폴더 보호) |
| 로컬에서의 실행 확인 | 없음 | 있음 (에뮬레이터) |
| run・logs・deploy・deployments・status | 없음 | 있음 |
push 대기 시간이 증가: 사전 코드 검사 외에도, push를 할 때마다 클라우드 측의 파일 목록을 가져와 비교하기 때문에 clasp 단독의 push보다 시간이 더 오래 걸립니다. 실제 환경에서 측정(3회 평균)한 결과, clasp push가 2.88초였던 것에 비해, gas_push는 4.55초였습니다 (차이분 +1.67초). 안전 확인을 위한 통신 및 처리 비용으로 약 1.7초의 오버헤드가 발생합니다.
삭제 시 명시가 필요: 클라우드 측 파일을 정말로 지우고 싶거나, 운영 폴더를 의도적으로 조작하고 싶을 때는 allowDeleteRemote: true나 allowProtected: true를 전달해야 합니다.
MCP 서버가 2개가 됨: 역할 분담을 했기 때문에 클라이언트에는 두 가지 설정을 모두 해야 하며, clasp 본체의 push_files와 pull_files를 비활성화하는 번거로움이 늘어납니다.
clasp 본체에 MCP 모드가 도입되면서, clasp를 MCP처럼 호출할 수 있게 하는 것만으로 된 자체 제작 도구는 더 이상 필요 없어졌습니다. 자체 제작을 통해 남길 가치가 있었던 것은 push 전에 멈추게 하는 메커니즘(코드 검사, 클라우드에만 있는 파일 감지, 운영 폴더 보호)과, 클라우드에 반영하지 않고 테스트할 수 있는 에뮬레이터였습니다.
사람이 한 번씩 승인하며 사용하는 정도라면, clasp 본체의 MCP 모드로 충분합니다. 하지만 AI에게 수정부터 push까지 계속 맡기고 싶거나, 삭제되면 곤란한 운영 프로젝트가 있다면, 자체 제작 메커니즘을 추가하는 의미가 있습니다.
GAS × AI 에이전트 개발 시리즈
- 제1회: AI 에이전트에게 GAS 개발을 맡기자 MCP 서버를 직접 만들게 된 이야기
- 제2회: 【계속】AI 에이전트에게 GAS 개발을 맡기자 MCP 서버를 직접 만들게 된 이야기
- 제3회: 【제3회】AI 에이전트에게 GAS 개발을 맡기자 ― 에뮬레이터를 통한 사전 검사 및 행 번호 매핑으로 자가 수정시킨 이야기
- 제4회 (본 기사)
- 관련: [GAS] Qiita 아티클 통계(PV・좋아요・스톡)를 전자동화로 시각화하는 GAS를 전력으로 만들어봤다!
- 관련: [GAS] Qiita 아티클 통계(PV・좋아요・스톡)를 전자동화로 시각화하는 GAS에 Notion을 연결해봤다
- 참고: google/clasp (GitHub)
질문이나 다른 환경에서 테스트한 결과가 있다면 댓글로 알려주세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기