---
title: "Emacs-AI 통합 패러다임: MCP, REPL, 그리고 진정한 협업"
url: "https://notes.junghanacs.com/notes/20251121T132314"
author: "Junghan Kim"
date: "2025-11-21T13:23:00+09:00"
lastmod: "2025-11-21T13:23:00+09:00"
tags: ["elisp","emacs","mcp","repl"]
description: "Emacs와 AI를 MCP와 REPL 중심 협업 패러다임으로 다시 묶어 보는 노트이다."
---

> [!abstract] 이 노트에 대하여
> 
> Emacs의 강점인 REPL과 라이브 프로그래밍을 AI 통합에 어떻게 되살릴 수 있는지 묻는다. 단방향 명령 실행이 아닌 상태 공유형 협업 패러다임을 제안하는 노트다.

-   [Emacs agent-shell + OpenCode MCP 통합 실행 계획](https://notes.junghanacs.com/notes/20251121T132839)
-   [agent-shell + OpenCode 커스텀 가이드: ACP 프로토콜 통합](https://notes.junghanacs.com/notes/20251121T131412)

## Emacs-AI 통합 패러다임

### 핵심 문제 인식

"이맥스에서 에이전트를 사용할 때 이맥스의 장점을 전혀 활용하지 못하고 있다"

이맥스의 진정한 힘:

-   **REPL 방식 개발**: eval → 즉시 적용 → 테스트 → 다시 수정
-   **현재 상태 공유**: 버퍼, 변수, 함수 정의, 패키지 정보
-   **라이브 프로그래밍**: 실행 중인 환경을 직접 수정

기존 AI 통합의 한계:

-   **단방향**: AI → 이맥스 (명령만 실행)
-   **비동기적**: 결과 받고 → 다시 물어봄
-   **컨텍스트 단절**: 이맥스 내부 상태를 AI가 모름

### 세 가지 통합 패러다임

#### 1. 네이티브 Elisp Eval 방식 (직접 통합)

##### Efrit (Steve Yegge)

-   이맥스 프로세스 내부에서 실행
-   \`eval\` 함수로 AI가 모든 기능에 직접 접근
-   MCP 없이 순수 Elisp로 작동

**장점**:

-   완전한 이맥스 통합
-   외부 프로토콜 오버헤드 없음
-   모든 Elisp 기능 직접 사용

**단점**:

-   초기 개발 단계 (불안정)
-   작성자도 "terrible at multi-step tasks"라고 인정
-   보안 위험 (무제한 eval)

**현실**: 많은 사용자가 기본 설정조차 어려워하고 있음

##### Emigo

#### 2. MCP 기반 방식 (표준 프로토콜)

**MCP(Model Context Protocol)**: Anthropic이 2024년 11월 발표

-   "AI를 위한 USB-C"
-   M×N 문제 해결 (각 AI가 각 도구에 개별 통합 필요 → 표준 프로토콜)
-   자율 에이전트를 위해 설계됨

##### A. 이맥스를 MCP 서버로 만들기

**elisp-dev-mcp** (Laurynas Biveinis)

Elisp 개발을 위한 MCP 서버

**제공 도구**:

-   \`describe-function\`: 함수 문서 추출
-   \`describe-variable\`: 변수 정보 조회
-   \`find-function\`: 소스 코드 정의 찾기
-   \`info-lookup\`: Info 문서 접근

**특징**:

-   읽기 전용 도구 (안전함)
-   mcp-server-lib.el 위에 구축
-   실제로 Claude와 도그푸딩하며 개발 중
-   커밋 히스토리에 Claude가 공동 저자로 나타남

**설치**: \`\`\`elisp (use-package mcp-server-lib :ensure t)

;; elisp-dev-mcp를 stdio 모드로 시작 M-x mcp-server-lib-start \`\`\`

---

**emacs-mcp-server** (rhblind)

포괄적 제어를 목표로 하는 MCP 서버

**제공 도구**:

-   \`eval-elisp\`: 임의의 Elisp 코드 실행 ⚠️
-   버퍼 수정
-   파일 작업
-   이맥스 환경 전체 제어

**보안 모델**:

-   권한 프롬프트
-   위험 함수 화이트리스트
-   "paranoid mode" 옵션
-   백업 강력 권장

**경고**: eval 권한은 매우 위험! 신중하게 사용할 것

---

**org-mcp** (Laurynas Biveinis)

Org-mode를 위한 MCP 서버

**제공 도구**:

-   Org 파일 읽기/쓰기
-   TODO 항목 조작
-   태그 관리
-   일정 작업

**특징**:

-   구조화된 API로 Org 파일 상호작용
-   쓰기 권한 부여 (백업 필수!)
-   자동 버전 관리 강력 권장

##### B. 이맥스 내에서 MCP 클라이언트 사용

**mcp.el** (lizqwerscott)

이맥스 내부의 MCP 클라이언트

**기능**:

-   stdio와 SSE를 통해 MCP 서버 연결
-   filesystem, fetch 등 다양한 서버 통합
-   mcp-hub를 통한 서버 관리
-   roots 프로토콜로 파일시스템 접근 제어

**API**: \`\`\`elisp ;; 동기 API (mcp-call-tool server-name tool-name args) (mcp-read-resource server-name uri) (mcp-get-prompt server-name prompt-name args)

;; 비동기 API (mcp-call-tool-async server-name tool-name args callback) \`\`\`

**설치**: \`\`\`elisp (use-package mcp :ensure t :config (mcp-hub-start)) ; 서버 관리 인터페이스 시작 \`\`\`

---

**gptel + MCP 통합**

gptel은 MCP 통합을 공식 지원

**설정**: \`\`\`elisp (use-package gptel :ensure t :config ;; MCP 도구를 gptel에 등록 (require 'mcp)

;; filesystem MCP 서버 연결 (mcp-add-server "filesystem" :command '("npx" "-y" "@modelcontextprotocol/server-filesystem" "/path/to/allowed/dir"))

;; gptel이 MCP 도구 사용 가능하도록 설정 (setq gptel-use-mcp-tools t)) \`\`\`

**사용**: \`\`\`elisp ;; gptel 버퍼에서 ;; AI가 filesystem MCP 서버의 도구를 사용하여 파일 읽기/쓰기 가능 \`\`\`

#### 3. ACP 기반 방식 (Agent Client Protocol)

**agent-shell + OpenCode**

현재 우리 환경: \`\`\` Emacs (agent-shell) └─ ACP Protocol └─ OpenCode Agent ├─ Model: Claude 3.7 Sonnet ├─ MCP Servers: GitHub, Google Workspace, Playwright 등 └─ 서브에이전트: PM, code-refactor, code-reviewer \`\`\`

**장점**:

-   OpenCode의 모든 기능 활용
-   다중 모델 지원
-   서브에이전트 시스템
-   MCP 서버 생태계

**단점**:

-   이맥스 내부 상태 접근 불가 (현재)
-   REPL 방식 개발 어려움 (현재)

### 당신이 놓친 핵심

#### 양방향 상호작용의 부재

**기존 방식 (단방향)**: \`\`\` AI → 명령 → 이맥스 (실행) 이맥스 → 결과 → AI (출력만 받음) \`\`\`

**진정한 REPL 방식 (양방향)**: \`\`\` AI → Elisp 코드 → 이맥스 (eval) 이맥스 → 결과 + 현재 상태 → AI AI → 수정된 코드 → 이맥스 (eval) ...반복... \`\`\`

#### MCP가 해결하는 부분

**이맥스 상태를 AI에게 노출**:

-   \`elisp-dev-mcp\`: 함수 정의, 변수 값, 문서 읽기
-   \`emacs-mcp-server\`: eval-elisp로 직접 코드 실행
-   \`org-mcp\`: Org 파일 구조 조회 및 수정

**실전 예시**: \`\`\`

1.  AI: "현재 버퍼의 \`my-function\` 정의를 보여줘" → elisp-dev-mcp: find-function → 이맥스: (defun my-function ...)

2.  AI: "이 함수를 리팩토링해줄게" → 코드 생성

3.  AI: "eval해서 테스트해보자" → emacs-mcp-server: eval-elisp → 이맥스: 즉시 적용

4.  AI: "변수 \`my-var\` 값이 뭐야?" → elisp-dev-mcp: describe-variable → 이맥스: 42

5.  AI: "값을 100으로 바꿔보자" → emacs-mcp-server: eval-elisp (setq my-var 100) → 이맥스: 즉시 반영

\`\`\`

### 실용적 선택지

#### 지금 당장 시도할 만한 것

##### 1. gptel + mcp.el (추천 ⭐⭐⭐⭐⭐)

**장점**:

-   가장 안정적
-   이맥스 네이티브 경험
-   기존 gptel 워크플로우에 MCP 도구 추가
-   점진적 도입 가능

**설치**: \`\`\`elisp ;; ~/.doom.d/packages.el (package! gptel) (package! mcp)

;; ~/.doom.d/config.el (use-package mcp :ensure t :config (mcp-add-server "filesystem" :command '("npx" "-y" "@modelcontextprotocol/server-filesystem" (expand-file-name "~/")))

(mcp-add-server "git" :command '("uvx" "mcp-server-git" "--repository" (expand-file-name "~/repos"))))

(use-package gptel :ensure t :config (setq gptel-use-mcp-tools t) (setq gptel-model 'claude-3-7-sonnet gptel-backend (gptel-make-anthropic "Claude" :stream t :key (auth-source-pick-first-password :host "anthropic.com")))) \`\`\`

**사용**: \`\`\`elisp M-x gptel

;; AI가 MCP 도구 사용: "~/repos/my-project의 파일 목록을 보여줘" → filesystem MCP 서버 사용

"이 저장소의 최근 커밋을 확인해줘" → git MCP 서버 사용 \`\`\`

##### 2. elisp-dev-mcp (추천 ⭐⭐⭐⭐)

**장점**:

-   안전한 읽기 전용
-   패키지 개발에 특화
-   이맥스 인트로스펙션

**설치**: \`\`\`bash git clone <https://github.com/laurynas-biveinis/elisp-dev-mcp.git> cd elisp-dev-mcp npm install \`\`\`

**이맥스 설정**: \`\`\`elisp (use-package mcp-server-lib :ensure t) \`\`\`

**사용** (외부 AI에서): \`\`\`json // Claude Desktop 설정 { "mcpServers": { "elisp-dev": { "command": "node", "args": ["/path/to/elisp-dev-mcp/build/index.js"], "env": { "EMACS_SERVER_FILE": "/tmp/emacs1000/server" } } } } \`\`\`

**Claude에서**: \`\`\` "Emacs에서 \`use-package\` 함수의 정의를 보여줘" → elisp-dev-mcp: find-function

"gptel 패키지의 주요 변수들을 설명해줘" → elisp-dev-mcp: describe-variable \`\`\`

##### 3. emacs-mcp-server (주의해서 ⚠️)

**언제 사용**:

-   eval 권한이 **반드시** 필요할 때
-   철저한 백업이 준비되었을 때
-   위험을 이해하고 있을 때

**설치**: \`\`\`bash git clone <https://github.com/rhblind/emacs-mcp-server.git> cd emacs-mcp-server npm install \`\`\`

**보안 설정**: \`\`\`elisp ;; paranoid mode 활성화 (setq mcp-server-paranoid-mode t)

;; 허용할 함수만 화이트리스트 (setq mcp-server-allowed-functions '(setq defun defvar let let\* progn)) \`\`\`

**사용**: \`\`\` "이 버퍼의 내용을 소문자로 변환해줘" → emacs-mcp-server: eval-elisp → (downcase-region (point-min) (point-max)) \`\`\`

### 당신의 환경에 맞는 전략

#### 현재 상황

-   Emacs (Doom)
-   agent-shell (ACP 프로토콜)
-   OpenCode (MCP 서버 통합됨)

#### 3단계 통합 계획

##### Phase 1: MCP 클라이언트 (Emacs 내부)

**목표**: 이맥스 안에서 MCP 도구 사용

\`\`\`elisp ;; ~/.doom.d/packages.el (package! mcp)

;; ~/.doom.d/config.el (use-package mcp :config ;; OpenCode가 이미 사용하는 MCP 서버들을 이맥스에서도 사용 (mcp-add-server "git" :command '("uvx" "mcp-server-git" "--repository" (expand-file-name "~/repos")))

(mcp-add-server "github" :command '("/home/tbdhny/sync/emacs/claude-config/opencode/github-mcp-with-env.sh"))

;; 이맥스 전용 MCP 서버 (mcp-add-server "org" :command '("npx" "-y" "org-mcp" "--org-directory" (expand-file-name "~/org"))))

;; Elisp 버퍼에서 MCP 도구 사용 (defun my/mcp-git-log () (interactive) (let ((result (mcp-call-tool "git" "git-log" '((limit . 10))))) (message "%s" result))) \`\`\`

**효과**:

-   이맥스에서 직접 MCP 도구 호출 가능
-   OpenCode와 같은 도구 공유

##### Phase 2: MCP 서버 (Emacs를 노출)

**목표**: OpenCode가 이맥스 상태 접근

\`\`\`elisp ;; elisp-dev-mcp 서버 시작 (use-package mcp-server-lib :config (mcp-server-lib-start)) ; stdio 모드로 시작

;; 또는 수동으로 M-x mcp-server-lib-start \`\`\`

**OpenCode 설정**: \`\`\`jsonc _/ ~_.config/opencode/opencode.jsonc { "mcp": { "emacs-elisp-dev": { "type": "local", "command": ["node", "/path/to/elisp-dev-mcp/build/index.js"], "environment": { "EMACS_SERVER_FILE": "/tmp/emacs1000/server" }, "enabled": true } } } \`\`\`

**효과**:

-   OpenCode 에이전트가 이맥스 함수 정의 조회 가능
-   변수 값 확인 가능
-   Info 문서 접근 가능

##### Phase 3: 양방향 통합 (REPL 방식)

**목표**: agent-shell ↔ OpenCode ↔ Emacs MCP

\`\`\`elisp ;; agent-shell에서 이맥스 컨텍스트 전달 (defun agent-shell-send-with-emacs-context (prompt) "현재 이맥스 상태와 함께 프롬프트 전송" (interactive "sPrompt: ") (let\* ((buffer-info (buffer-name)) (major-mode major-mode) (point-info (point)) (region (when (use-region-p) (buffer-substring-no-properties (region-beginning) (region-end)))) ;; MCP로 추가 컨텍스트 수집 (git-status (mcp-call-tool "git" "git-status" nil)) (context (format " &lt;emacs-context&gt; Buffer: %s Mode: %s Point: %d Region: %s Git Status: %s &lt;/emacs-context&gt;

%s" buffer-info major-mode point-info (or region "none") git-status prompt))) (agent-shell-send-prompt context)))

;; OpenCode가 제안한 코드를 이맥스에서 eval (defun agent-shell-eval-response () "OpenCode의 응답에서 Elisp 코드를 추출하여 eval" (interactive) (let\* ((response (agent-shell-last-response)) (code (extract-elisp-code response))) ; 코드 블록 파싱 (when code (eval (read code)) (message "Evaluated: %s" code))))

;; 키바인딩 (map! :map agent-shell-mode-map :leader :desc "Send with Emacs context" "a e" #'agent-shell-send-with-emacs-context :desc "Eval last response" "a E" #'agent-shell-eval-response) \`\`\`

**워크플로우**: \`\`\`

1.  [Emacs] 함수 정의 커서에서 C-c a e "이 함수를 리팩토링해줘"

2.  [agent-shell] → OpenCode
    -   emacs-elisp-dev MCP로 함수 정의 조회
    -   git MCP로 변경 이력 확인
    -   컨텍스트 기반으로 리팩토링 제안

3.  [OpenCode] → agent-shell 리팩토링된 코드 반환

4.  [Emacs] C-c a E → 제안된 코드 즉시 eval → 테스트

5.  [Emacs] 결과 확인 후 다시 C-c a e "타입 힌트를 추가해줘"

6.  반복...

\`\`\`

### 구체적 구현 예시

#### 1. Elisp 개발 워크플로우

\`\`\`elisp ;; 1. 함수 개발 시작 (defun my-new-function (arg) "TODO: 문서 작성" arg)

;; 2. agent-shell에 요청 C-c a e "이 함수에 에러 핸들링과 문서를 추가해줘"

;; 3. OpenCode가 emacs-elisp-dev MCP로: ;; - describe-function으로 유사 함수 찾기 ;; - info-lookup으로 Elisp 매뉴얼 참조 ;; - 개선된 코드 제안

;; 4. 제안 받음: (defun my-new-function (arg) "Process ARG with error handling. Returns processed result or nil on error." (condition-case err (progn (unless arg (error "ARG must not be nil")) (process-arg arg)) (error (message "Error in my-new-function: %s" (error-message-string err)) nil)))

;; 5. 즉시 eval C-c a E

;; 6. 테스트 (my-new-function nil) ; → 에러 처리 확인 (my-new-function "test") ; → 정상 작동 확인

;; 7. 추가 개선 요청 C-c a e "타입 체크를 더 강화해줘" \`\`\`

#### 2. 패키지 설정 최적화

\`\`\`elisp ;; 현재 설정 (use-package gptel :config (setq gptel-model 'claude-3-7-sonnet))

;; agent-shell에 요청 C-c a e "이 gptel 설정을 최적화해줘. 키바인딩과 커스텀 백엔드 추가"

;; OpenCode가 elisp-dev-mcp로: ;; - describe-variable gptel-\* 변수들 조회 ;; - find-function gptel 관련 함수들 확인 ;; - 최적화된 설정 제안

;; 제안 받음: (use-package gptel :bind (("C-c g g" . gptel) ("C-c g s" . gptel-send) ("C-c g r" . gptel-rewrite)) :config (setq gptel-model 'claude-3-7-sonnet gptel-default-mode 'org-mode gptel-use-mcp-tools t)

;; 커스텀 백엔드 (gptel-make-anthropic "Claude-Fast" :stream t :key (auth-source-pick-first-password :host "anthropic.com") :models '(claude-haiku-4-5)))

;; 즉시 eval C-c a E

;; 테스트 C-c g g ; gptel 버퍼 열기 \`\`\`

#### 3. Org-mode 워크플로우

\`\`\`elisp ;; org-mcp 서버 설정 (mcp-add-server "org" :command '("npx" "-y" "org-mcp" "--org-directory" (expand-file-name "~/org")))

;; OpenCode에서 Org 파일 조작 C-c a e "~/org/tasks.org에서 DONE 상태인 TODO를 ARCHIVE로 옮겨줘"

;; OpenCode가 org-mcp로: ;; - Org 파일 파싱 ;; - DONE TODO 찾기 ;; - ARCHIVE 처리 ;; - 결과 보고

;; 결과 확인 C-x C-f ~/org/tasks.org \`\`\`

### 보안 고려사항

#### 1. 읽기 전용 우선

\`\`\`elisp ;; 안전: elisp-dev-mcp (읽기만) (mcp-server-lib-start)

;; 위험: emacs-mcp-server (eval 가능) ;; → paranoid mode 필수 (setq mcp-server-paranoid-mode t) \`\`\`

#### 2. 화이트리스트 기반

\`\`\`elisp ;; eval 허용 함수 제한 (setq mcp-server-allowed-functions '(;; 변수 조작 setq setq-default ;; 함수 정의 defun defvar defcustom ;; 안전한 제어 구조 let let\* progn if when unless ;; 메시지만 message)) \`\`\`

#### 3. 백업 자동화

\`\`\`elisp ;; eval 전 자동 백업 (defun my/backup-before-eval () (when (buffer-file-name) (let ((backup-file (concat (buffer-file-name) ".mcp-backup"))) (copy-file (buffer-file-name) backup-file t))))

(add-hook 'mcp-server-before-eval-hook #'my/backup-before-eval) \`\`\`

#### 4. Git 통합

\`\`\`elisp ;; eval 후 자동 커밋 (defun my/git-commit-after-eval () (when (and (buffer-file-name) (magit-git-repo-p)) (magit-stage-file (buffer-file-name)) (magit-commit-create '("-m" "MCP eval: auto-commit"))))

(add-hook 'mcp-server-after-eval-hook #'my/git-commit-after-eval) \`\`\`

### 성능 최적화

#### 1. 컨텍스트 크기 제한

\`\`\`elisp (defun agent-shell-send-with-emacs-context (prompt) (let\* ((max-region-size 1000) ; 최대 1000자 (region (when (use-region-p) (let ((text (buffer-substring-no-properties (region-beginning) (region-end)))) (if (&gt; (length text) max-region-size) (concat (substring text 0 max-region-size) "...") text))))) ;; ... )) \`\`\`

#### 2. MCP 서버 선택적 활성화

\`\`\`elisp ;; 프로젝트별로 필요한 서버만 시작 (defun my/start-project-mcp-servers () (interactive) (cond ((projectile-project-type) ;; Elisp 프로젝트 ((eq (projectile-project-type) 'emacs-lisp) (mcp-start-server "elisp-dev")) ;; Web 프로젝트 ((member (projectile-project-type) '(npm yarn)) (mcp-start-server "playwright")))) (t ;; 기본: git만 (mcp-start-server "git")))) \`\`\`

#### 3. 캐싱

\`\`\`elisp ;; 자주 조회하는 정보는 캐시 (defvar my/mcp-cache (make-hash-table :test 'equal))

(defun my/mcp-call-tool-cached (server tool args) (let ((key (format "%s:%s:%s" server tool args))) (or (gethash key my/mcp-cache) (puthash key (mcp-call-tool server tool args) my/mcp-cache)))) \`\`\`

### 디버깅

#### 1. MCP 트래픽 로깅

\`\`\`elisp (setq mcp-debug t) ; MCP 통신 로그 활성화

;; 로그 확인 M-x mcp-show-log \`\`\`

#### 2. agent-shell 연동 확인

\`\`\`elisp (defun my/test-agent-shell-mcp () "agent-shell과 MCP 연동 테스트" (interactive) (let ((test-cases '(("git" "git-status" nil) ("elisp-dev" "describe-function" (("function" . "use-package"))) ("org" "list-todos" (("file" . "~/org/tasks.org")))))) (dolist (case test-cases) (let ((server (nth 0 case)) (tool (nth 1 case)) (args (nth 2 case))) (condition-case err (progn (message "Testing %s:%s..." server tool) (mcp-call-tool server tool args) (message "✓ %s:%s OK" server tool)) (error (message "✗ %s:%s FAILED: %s" server tool (error-message-string err)))))))) \`\`\`

### 로드맵

#### Phase 1: 기초 (1주)

-   [X] gptel + mcp.el 설치
-   [ ] elisp-dev-mcp 설정
-   [ ] agent-shell에서 기본 MCP 호출 테스트

#### Phase 2: 통합 (2주)

-   [ ] agent-shell ↔ OpenCode ↔ Emacs MCP 연결
-   [ ] 컨텍스트 전달 함수 구현
-   [ ] eval 워크플로우 구축

#### Phase 3: 최적화 (2주)

-   [ ] 성능 튜닝 (캐싱, 선택적 로딩)
-   [ ] 보안 강화 (화이트리스트, 백업)
-   [ ] 프로젝트별 프로필

#### Phase 4: 고급 (진행 중)

-   [ ] LSP ↔ MCP 브릿지
-   [ ] 멀티 에이전트 협업
-   [ ] 자동화된 테스트 및 검증

### 결론

**핵심 인사이트**: 진정한 이맥스-AI 통합은 "에디터 안에서 채팅"이 아니라 **"에디터를 에이전트에게 주는 것"**

**MCP의 가치**:

-   프로토콜 우선 (vendor-neutral)
-   자율 에이전트를 위해 설계됨
-   M×N 문제 해결

**실용적 시작점**:

1.  gptel + mcp.el (가장 안정적)
2.  elisp-dev-mcp (안전한 이맥스 노출)
3.  agent-shell 통합 (점진적)

**당신의 장점**:

-   Doom Emacs (강력한 설정 프레임워크)
-   agent-shell (ACP 프로토콜)
-   OpenCode (MCP 생태계)

이 세 가지를 연결하면 **진정한 REPL 방식 AI 협업** 이 가능합니다!

### 참고 자료

#### 프로젝트

-   elisp-dev-mcp: <https://github.com/laurynas-biveinis/elisp-dev-mcp>
-   emacs-mcp-server: <https://github.com/rhblind/emacs-mcp-server>
-   org-mcp: <https://github.com/laurynas-biveinis/org-mcp>
-   mcp.el: <https://github.com/lizqwerscott/mcp.el>
-   gptel: <https://github.com/karthink/gptel>

#### 문서

-   MCP 공식: <https://modelcontextprotocol.io>
-   Anthropic MCP 발표: <https://www.anthropic.com/news/model-context-protocol>
-   MCP 명세: <https://spec.modelcontextprotocol.io>

#### 관련 llmlog

-   [agent-shell + OpenCode 커스텀 가이드](https://notes.junghanacs.com/notes/20251121T131412)
-   [브라우저 자동화 도구 비교](https://notes.junghanacs.com/notes/20251121T122829)

### APPENDIX: OpenCode agent-shell-mode 구현 제안

#### 개요

agent-shell에서 OpenCode를 사용할 때 MCP 기반 Emacs 통합을 최대한 활용하는 방법입니다. 기존 agent-shell-mode를 확장하여 OpenCode의 장점과 Emacs의 REPL 특성을 결합합니다.

#### 핵심 아키텍처

\`\`\` ┌──────────────────────────────────────────────────────────┐ │ Emacs (Doom) │ │ ┌────────────────────────────────────────────────────┐ │ │ │ agent-shell-opencode-mode │ │ │ │ ┌──────────────┐ ┌──────────────┐ │ │ │ │ │ MCP Client │◄────────┤ Context │ │ │ │ │ │ (mcp.el) │ │ Collector │ │ │ │ │ └──────┬───────┘ └──────────────┘ │ │ │ │ │ │ │ │ │ │ ACP Protocol │ │ │ │ ▼ │ │ │ │ ┌──────────────────────────────────────┐ │ │ │ │ │ OpenCode Agent │ │ │ │ │ │ ┌─────────────┐ ┌────────────────┐│ │ │ │ │ │ │ MCP Servers │ │ Sub-agents ││ │ │ │ │ │ │ - github │ │ - code-refactor││ │ │ │ │ │ │ - git │ │ - PM ││ │ │ │ │ │ │ - emacs-dev │ │ - reviewer ││ │ │ │ │ │ └─────────────┘ └────────────────┘│ │ │ │ │ └──────────────────────────────────────┘ │ │ │ └────────────────────────────────────────────────────┘ │ │ │ │ ┌────────────────────────────────────────────────────┐ │ │ │ MCP Server (Emacs 노출) │ │ │ │ ┌──────────────┐ ┌──────────────┐ │ │ │ │ │ elisp-dev │ │ org-mcp │ │ │ │ │ │ (read-only) │ │ (org files) │ │ │ │ │ └──────────────┘ └──────────────┘ │ │ │ └────────────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────────┘ \`\`\`

#### 패키지 구조

\`\`\`elisp ;; ~/.doom.d/packages.el (package! agent-shell) ; 기본 agent-shell (package! mcp) ; MCP 클라이언트 (package! gptel) ; 선택적 (기존 gptel 사용자)

;; MCP 서버 (외부 설치 필요) ;; npm install -g elisp-dev-mcp ;; npm install -g org-mcp \`\`\`

#### 설정 파일

##### ~/.doom.d/config.el

\`\`\`elisp ;;; agent-shell + OpenCode 통합

;; 1. MCP 클라이언트 설정 (use-package! mcp :config ;; Emacs가 MCP 클라이언트로서 OpenCode의 서버들 사용 (mcp-add-server "git" :command '("uvx" "mcp-server-git" "--repository" (expand-file-name "~/repos")))

(mcp-add-server "github" :command '("bash" (expand-file-name "~/sync/emacs/claude-config/opencode/github-mcp-with-env.sh")))

;; 프로젝트별 활성화 (defun my/mcp-enable-project-servers () "프로젝트 타입에 따라 MCP 서버 활성화" (when (projectile-project-p) (pcase (projectile-project-type) ('emacs-lisp ;; Elisp 프로젝트 nil) ; elisp-dev는 MCP 서버로 이미 실행 중 ('npm ;; JavaScript 프로젝트 (mcp-add-server "playwright" :command '("npx" "@playwright/mcp@latest" "--extension"))) ('python ;; Python 프로젝트 nil)))) ; TODO: Python MCP 서버 추가

(add-hook 'projectile-after-switch-project-hook #'my/mcp-enable-project-servers))

;; 2. MCP 서버 설정 (Emacs를 노출) (use-package! mcp-server-lib :defer t :config ;; Emacs를 MCP 서버로 시작 (daemon 모드) ;; OpenCode가 연결할 수 있도록 stdio 모드 (defun my/start-emacs-mcp-server () "Emacs를 MCP 서버로 시작" (interactive) (mcp-server-lib-start) (message "Emacs MCP server started on stdio")))

;; 3. agent-shell-opencode-mode 정의 (define-minor-mode agent-shell-opencode-mode "OpenCode 전용 agent-shell 강화 모드" :lighter " OC" :keymap (let ((map (make-sparse-keymap))) ;; 컨텍스트 전달 (define-key map (kbd "C-c a e") #'agent-shell-send-with-context) (define-key map (kbd "C-c a r") #'agent-shell-send-region-with-context)

;; 에이전트 제어 (define-key map (kbd "C-c a a") #'agent-shell-switch-opencode-agent) (define-key map (kbd "C-c a m") #'agent-shell-switch-opencode-model)

;; MCP 제어 (define-key map (kbd "C-c a M") #'agent-shell-enable-mcp-server)

;; 응답 처리 (define-key map (kbd "C-c a E") #'agent-shell-eval-last-response) (define-key map (kbd "C-c a A") #'agent-shell-apply-last-diff)

map))

;; 4. 컨텍스트 수집기 (defun agent-shell--collect-emacs-context () "현재 Emacs 상태를 수집하여 컨텍스트 생성" (let\* ((buffer-info (format "Buffer: %s\nMode: %s\nPoint: %d" (buffer-name) major-mode (point)))

(region-info (when (use-region-p) (let ((text (buffer-substring-no-properties (region-beginning) (region-end)))) (format "\n&lt;region&gt;\n%s\n&lt;/region&gt;" (if (&gt; (length text) 1000) (concat (substring text 0 1000) "\n...") text)))))

(git-info (when (and (fboundp 'mcp-call-tool) (mcp-server-active-p "git")) (condition-case nil (format "\n&lt;git-status&gt;\n%s\n&lt;/git-status&gt;" (mcp-call-tool "git" "git-status" nil)) (error ""))))

(project-info (when (projectile-project-p) (format "\nProject: %s\nType: %s" (projectile-project-name) (projectile-project-type))))

(lsp-info (when (bound-and-true-p lsp-mode) (let ((symbol (thing-at-point 'symbol))) (when symbol (condition-case nil (format "\n&lt;lsp-context symbol=\\"%s\\"&gt;\n%s\n&lt;/lsp-context&gt;" symbol (lsp-hover-info-at-point)) (error "")))))))

(concat buffer-info region-info git-info project-info lsp-info)))

;; 5. 컨텍스트와 함께 프롬프트 전송 (defun agent-shell-send-with-context (prompt) "Emacs 컨텍스트와 함께 프롬프트를 OpenCode에 전송" (interactive "sPrompt: ") (let\* ((context (agent-shell--collect-emacs-context)) (full-prompt (format "%s\n\n&lt;emacs-context&gt;\n%s\n&lt;/emacs-context&gt;" prompt context))) (agent-shell-send-prompt full-prompt)))

(defun agent-shell-send-region-with-context () "선택 영역과 컨텍스트를 함께 전송" (interactive) (unless (use-region-p) (user-error "No region selected")) (let\* ((prompt (read-string "What to do with this code: ")) (context (agent-shell--collect-emacs-context))) (agent-shell-send-prompt (format "%s\n\n&lt;emacs-context&gt;\n%s\n&lt;/emacs-context&gt;" prompt context))))

;; 6. OpenCode 에이전트 전환 (defun agent-shell-switch-opencode-agent (agent) "OpenCode 에이전트를 전환" (interactive (list (completing-read "Agent: " '("build" "plan" "PM" "code-refactor" "code-reviewer" "security-forensics") nil t))) (agent-shell-send-command (format "/mode %s" agent)) (message "Switched to %s agent" agent))

;; 7. OpenCode 모델 전환 (defun agent-shell-switch-opencode-model (model) "OpenCode 모델을 전환" (interactive (list (completing-read "Model: " '("anthropic/claude-sonnet-4-5" "anthropic/claude-haiku-4-5" "openrouter/openai/gpt-5.1-chat" "openrouter/openai/gpt-5.1-codex" "deepseek/deepseek-coder") nil t))) ;; ACP 프로토콜로 모델 변경 (구현 필요) ;; 현재는 설정 파일에서 agent별 모델 지정 (message "Model switching via ACP not yet implemented. Use agent config."))

;; 8. MCP 서버 동적 활성화 (defun agent-shell-enable-mcp-server (server) "MCP 서버를 동적으로 활성화" (interactive (list (completing-read "MCP Server: " '("playwright-extension" "browserbase" "n8n-mcp" "supabase") nil t))) (pcase server ("playwright-extension" (mcp-add-server "playwright" :command '("npx" "@playwright/mcp@latest" "--extension"))) ("browserbase" (mcp-add-server "browserbase" :command '("bash" (expand-file-name "~/sync/emacs/claude-config/mcp-wrappers/browserbase-mcp-wrapper.sh")))) ("n8n-mcp" (mcp-add-server "n8n" :command '("bash" (expand-file-name "~/sync/emacs/claude-config/mcp-wrappers/n8n-mcp-wrapper.sh")))) ("supabase" (mcp-add-server "supabase" :command '("bash" (expand-file-name "~/sync/emacs/claude-config/mcp-wrappers/supabase-mcp-wrapper.sh"))))) (message "Enabled MCP server: %s" server))

;; 9. 응답 처리 - Elisp eval (defun agent-shell-eval-last-response () "마지막 응답에서 Elisp 코드를 추출하여 eval" (interactive) (let\* ((response (agent-shell--get-last-response)) (code (agent-shell--extract-elisp-code response))) (if code (progn (when (y-or-n-p (format "Eval this code?\n\n%s" code)) (condition-case err (progn (eval (read code)) (message "✓ Code evaluated successfully")) (error (message "✗ Eval error: %s" (error-message-string err)))))) (message "No Elisp code found in response"))))

(defun agent-shell--extract-elisp-code (text) "응답에서 Elisp 코드 블록 추출" (when (string-match "\`\`\`\\\\(?:elisp\\\|emacs-lisp\\\)\n\\\\(\\\(?:.\\\|\n\\\)\*?\\\\)\`\`\`" text) (match-string 1 text)))

(defun agent-shell--get-last-response () "마지막 agent 응답 가져오기" ;; agent-shell 구현에 따라 다를 수 있음 ;; 버퍼에서 마지막 응답 추출 (save-excursion (goto-char (point-max)) (let ((end (point))) (when (re-search-backward "^▼\\\\|^Agent&gt;" nil t) (buffer-substring-no-properties (point) end)))))

;; 10. 응답 처리 - Diff 적용 (defun agent-shell-apply-last-diff () "마지막 응답의 diff를 현재 버퍼에 적용" (interactive) (let\* ((response (agent-shell--get-last-response)) (diff (agent-shell--extract-diff response))) (if diff (when (y-or-n-p "Apply this diff?") (agent-shell--apply-diff diff)) (message "No diff found in response"))))

(defun agent-shell--extract-diff (text) "응답에서 unified diff 추출" (when (string-match "\`\`\`diff\n\\\\(\\\(?:.\\\|\n\\\)\*?\\\\)\`\`\`" text) (match-string 1 text)))

(defun agent-shell--apply-diff (diff) "Unified diff를 현재 버퍼에 적용" ;; diff-mode 사용 (let ((diff-buffer (generate-new-buffer "**agent-diff**"))) (with-current-buffer diff-buffer (insert diff) (diff-mode) (goto-char (point-min))) (pop-to-buffer diff-buffer) (message "Review diff and use 'C-c C-a' to apply")))

;; 11. agent-shell 후크 (add-hook 'agent-shell-mode-hook (lambda () (agent-shell-opencode-mode 1)))

;; 12. hydra 메뉴 (선택적) (when (featurep 'hydra) (defhydra hydra-agent-shell-opencode (:color blue :hint nil) " ^Send^ ^Agent^ ^MCP^ ^Response^ ^^^^^^^^----------------------------------------------------------------- e: with context a: switch M: enable server E: eval code r: region m: model ^ ^ A: apply diff p: plain ^ ^ ^ ^ ^ ^ " ("e" agent-shell-send-with-context) ("r" agent-shell-send-region-with-context) ("p" agent-shell-send-prompt) ("a" agent-shell-switch-opencode-agent) ("m" agent-shell-switch-opencode-model) ("M" agent-shell-enable-mcp-server) ("E" agent-shell-eval-last-response) ("A" agent-shell-apply-last-diff) ("q" nil "quit"))

;; Hydra 키바인딩 (map! :map agent-shell-mode-map :leader :prefix ("o" . "opencode") :desc "OpenCode menu" "o" #'hydra-agent-shell-opencode/body)) \`\`\`

##### OpenCode 설정 업데이트

\`\`\`jsonc _/ ~_.config/opencode/opencode.jsonc

{ "mcp": { // 기존 서버들...

// Emacs MCP 서버 추가 "emacs-elisp-dev": { "type": "local", "command": [ "node", "/path/to/elisp-dev-mcp/build/index.js" ], "environment": { "EMACS_SERVER_FILE": "/tmp/emacs1000/server" }, "enabled": true },

"emacs-org": { "type": "local", "command": [ "npx", "-y", "org-mcp", "--org-directory", "/home/tbdhny/org" ], "enabled": true } },

"agent": { // Elisp 개발 전용 에이전트 "elisp-dev": { "mode": "subagent", "description": "Elisp development and Emacs configuration expert", "model": "anthropic/claude-sonnet-4-5", "prompt": "You are an Emacs Lisp expert. Use emacs-elisp-dev MCP server to:\n- Query function definitions with find-function\n- Check variable values with describe-variable\n- Look up documentation with info-lookup\n\nAlways check existing implementations before suggesting new code.", "tools": { "write": true, "edit": true, "bash": false, "mcp_emacs-elisp-dev": true }, "permission": { "write": "ask", "edit": "ask" } } } } \`\`\`

#### 사용 워크플로우

##### 1. 시작 (한 번만)

\`\`\`bash

emacs --daemon

emacsclient -c

M-x my/start-emacs-mcp-server \`\`\`

##### 2. Elisp 개발

\`\`\`elisp ;; 1. 함수 작성 (defun my-new-function (arg) "TODO" arg)

;; 2. 커서를 함수 위에 두고 C-c a e ; agent-shell-send-with-context "이 함수에 에러 핸들링과 docstring을 추가해줘"

;; 3. OpenCode가 emacs-elisp-dev MCP로: ;; - 유사 함수 찾기 ;; - Elisp 매뉴얼 참조 ;; - 개선된 코드 제안

;; 4. 응답 확인 후 C-c a E ; agent-shell-eval-last-response ;; → 제안된 코드 즉시 eval

;; 5. 테스트 (my-new-function nil) (my-new-function "test")

;; 6. 추가 개선 C-c a e "타입 체크 강화" \`\`\`

##### 3. 프로젝트 작업

\`\`\`elisp ;; 1. 프로젝트 오픈 SPC p p ; projectile-switch-project

;; 2. MCP 서버 자동 활성화 (프로젝트 타입 기반) ;; → my/mcp-enable-project-servers 실행됨

;; 3. 파일 수정 C-c a r ; agent-shell-send-region-with-context ;; 선택한 코드 영역을 OpenCode에 전송 ;; Git 상태, LSP 정보 포함

;; 4. OpenCode가 컨텍스트 기반 제안 ;; - git MCP로 변경 이력 확인 ;; - emacs-elisp-dev로 함수 정의 조회 ;; - LSP 정보로 타입 확인

;; 5. Diff 적용 C-c a A ; agent-shell-apply-last-diff \`\`\`

##### 4. Org-mode 작업

\`\`\`elisp ;; 1. Org 파일 열기 C-x C-f ~/org/tasks.org

;; 2. OpenCode에 요청 C-c a e "DONE 상태인 TODO들을 ARCHIVE로 옮겨줘"

;; 3. OpenCode가 emacs-org MCP로: ;; - Org 파일 파싱 ;; - TODO 상태 확인 ;; - ARCHIVE 처리

;; 4. 결과 확인 C-x C-s ; 저장 \`\`\`

#### 고급 사용법

##### 멀티 에이전트 협업

\`\`\`elisp ;; 1. code-reviewer로 현재 코드 분석 C-c a a code-reviewer C-c a e "이 모듈의 보안 취약점을 찾아줘"

;; 2. 결과 확인 후 code-refactor로 전환 C-c a a code-refactor C-c a e "방금 찾은 취약점을 수정해줘"

;; 3. 수정 적용 C-c a E

;; 4. 다시 reviewer로 검증 C-c a a code-reviewer C-c a e "수정이 제대로 되었는지 확인해줘" \`\`\`

##### 브라우저 자동화 통합

\`\`\`elisp ;; 1. Playwright MCP 활성화 C-c a M playwright-extension

;; 2. 웹 스크래핑 요청 C-c a e "<https://example.com에서> 데이터를 수집해서 Org 파일로 저장해줘"

;; 3. OpenCode가: ;; - playwright MCP로 브라우저 제어 ;; - 데이터 추출 ;; - emacs-org MCP로 Org 파일 생성

;; 4. 결과 확인 C-x C-f ~/org/scraped-data.org \`\`\`

#### 트러블슈팅

##### MCP 서버 연결 실패

\`\`\`elisp ;; 1. MCP 로그 확인 (setq mcp-debug t) M-x mcp-show-log

;; 2. Emacs 서버 파일 확인 M-: server-socket-dir ;; → _tmp/emacs1000_

;; 3. elisp-dev-mcp 환경 변수 확인 ;; OpenCode 설정에서: "environment": { "EMACS_SERVER_FILE": "/tmp/emacs1000/server" ; 경로 일치 확인 } \`\`\`

##### ACP 프로토콜 디버깅

\`\`\`elisp ;; agent-shell 디버그 모드 (setq agent-shell-debug t)

;; 로그 버퍼 확인 **Messages** **agent-shell-debug** \`\`\`

##### 성능 문제

\`\`\`elisp ;; 컨텍스트 크기 제한 (setq agent-shell-max-context-size 1000) ; 1000자로 제한

;; MCP 캐싱 활성화 (setq mcp-enable-cache t)

;; 불필요한 MCP 서버 비활성화 M-x mcp-stop-server RET playwright RET \`\`\`

#### 장점 정리

1.  **완전한 양방향 통합**
    -   Emacs → OpenCode (컨텍스트 전달)
    -   OpenCode → Emacs (MCP로 상태 조회)

2.  **REPL 방식 개발**
    -   코드 제안 → 즉시 eval → 테스트 → 수정

3.  **멀티 에이전트 활용**
    -   작업별 최적화된 에이전트
    -   서브에이전트 간 협업

4.  **MCP 생태계**
    -   Git, GitHub, Org-mode 통합
    -   브라우저 자동화 (필요시)
    -   확장 가능

5.  **점진적 도입**
    -   기존 agent-shell 워크플로우 유지
    -   필요한 기능만 활성화

#### 다음 단계

1.  **elisp-dev-mcp 설치 및 테스트**
2.  **OpenCode 설정 업데이트**
3.  **Doom config에 agent-shell-opencode-mode 추가**
4.  **실제 Elisp 개발로 검증**
5.  **워크플로우 최적화 및 문서화**

### 메타데이터

-   작성일: 2025-11-21 금요일 13:23
-   업데이트: 2025-11-21 금요일 13:30 (APPENDIX 추가)
-   작성자: AI Assistant (OpenCode via ACP)
-   컨텍스트: Emacs-AI 통합, MCP, REPL 방식 개발
-   디바이스: LAPTOP
-   핵심 개념: "에디터를 에이전트에게 주는 것"
-   추가 내용: agent-shell-opencode-mode 구체적 구현
