MCP Driven WordPress Plugin 개발하기

CMUX+Browser API vs. TMUX+Playwright

시작은 Agent에게 프롬프트로만 WordPress 플러그인을 만들게 할 때, 맘에 드는 결과물이 나오게 하는 과정이 비싸고 비효율적인 것을 해결하기 위해서였다. WordPress는 OCI 기반 가상호스트에서 동작하고 있어서 초기에는 Local Agent를 WP 환경 바깥 호스트에 직접 ssh로 접속해서 개발하거나, WP가 동작하는 도커 컨테이너 안에서 Agent를 동작시키는 방법 두가지를 사용해보았다.

VibeProxy를 사용하면 가상호스트가 되었든 Docker Container안이든 상관없이 Agent를 끼고 개발할 수 있지만, 웹브라우저를 기동하는 Window 환경이 없는 그 조건에서는 나만 화면을 보면서 Agent에게 내 지시를 받아 처리하는 건 비효율적인 방법으로 회귀하는 셈이다. (Window 환경을 위한 VNC류는 일단 제외한다) 굳이 가상호스트에서 작업해야하는 이유는 호스트의 NGINX Reverse Proxy 설정까지 조절하면서 개발해야하기 때문. NGINX가 여러대 돌아가므로 문맥이란 걸 무시할 순 없다. 또한 보안설정을 빡쎄게 걸어뒀기 때문에 WP 플러그인 실행 단계에서 일일히 조건을 맞춰야 한다. 암튼, 화면 요소에 대해 아무리 명확하게 CSS selector를 사용해서 프롬프트를 지시해도 빡치게 만들기 일쑤고, 처리과정에서 토큰 소비량도 만만찮아서 방법을 찾은 것이 CMUX에서 Browser API로 연동하거나 TMUX+Playwright를 기동하는 방법이었다.

결과는 명확하다. 개발과정에서는 cmux-mcp를 통해 CMUX+Browser API를 사용하는 것이다.

개발과정이라서 자동화 요구보다 지시사항을 제대로 수행하는게 먼저라서 CMUX+Browser API를 택했는데, 의외로 날 고민하게 만드는 건 따로 있었다.

sshfs vs. mutagen

로컬 맥북에서 원격지 도커 컨테이너 쪽으로 ssh 통신은 하는 건 TailScale 덕분에 손쉽게 해결된다. 그러나, 아무리 TailScale 네트워크가 OpenVPN 보다 빨라도 Agent가 파일을 조작할 때 한번에 처리하지 못하는 경우가 빈번하게 생겼다. ssh + script 호출을 inline으로 할 땐 문법에 예민해야하는데, Codex 조차도 Turn 사이사이 계속해서 실패를 함으로써 신경쓰이게 했다. 그렇다고 sshfs를 쓰자니 openFUSE를 설치해야하고, 그건 도저히 방법이 될 수 없었다. 그거 하나 쓰자고 맥북 보안을 풀어버리고 싶진 않다. 그래서, 대체제로써 SMB3, NFS v4, WebDAV, Mutagen, Syncthing, VS Code Remote-SSH 등을 평가하다가 Mutagen으로 최종 결정했다. VS Code Remote-SSH는 원격지에 node 프로세스를 너무 많이 띄운다. 무겁다. 나머지는 다 철지난 기술들. 그러나 Docker가 인수한 Mutagen은 꽤 쓸만했다. 덕분에 프로젝트별로 원격지 폴더를 로컬에 마운트해서 사용할 수 있게 됨. (Permission 설정에 좀 신경써야 한다)

AGENTS.md vs. memory-mcp + serena-mcp + morph-plugin …

이제 내 요청을 찰떡같이 해석해주는 걸 도와줄 MCP를 설정하고 어느 정도 작업진척이 있었다. Morph Plugin은 꽤 쓸만했는데, 꽤 빨리 허용치 한계를 넘어서 제외했다.

xray-mcp는 작은 프로젝트에나 쓸만하고 조금만 커지면 출력이 잘린다. xray-mcp가 문제가 아니라 Agent와 LLM에서 자르는 것 같다.

serena 하나로는 매 Turn 마다 쌓이는 것들을 제때 제때 적용할 수가 없었다. serena-mcp의 메모리 기능은 단순 markdown 저장방법이다.

AGENTS.md 에 어명이니 반드시 따라야 한다고 협박해봐야 100% 실행보장은 안되고, 문맥 쌓이면 밀리는 것은 어쩔 수 없는 일.

xChuCx/agent-memory, basicmachines-co/basic-memory, riponcm/projectmem, bzdOS/hubd, besslframework-stack/project-tessera, tverney/mcp-agent-memory … 등등, 후보 MCP 서버는 좀 있긴한데 MCP 개발을 해보고 싶던 차라서 생각하는 기능의 MCP 서버를 만들기로 결정했다.

CMUX Browser + TMUX
Developing WordPress plugin. Remote Docker Container, macOS Host, CMUX + TMUX + Browser API

CMUX 안에서 TMUX를 사용하는 이유는 두가지 인데, 하나는 CMUX 세션에서 작업이 진행되다가 앱이 먹통되는 사태에 대비하기 위해서고, iTerm2 TMUX 세션으로도 attach 시켜서 다른 태스크를 진행시키기 위해서다. CMUX안의 TMUX 세션은 중첩될 수가 없고 CMUX_* 환경변수가 전달되지 않아서 CMUX Workspace 하나당 하나의 TMUX 세션을 붙이는 게 현재로써는 최선이다. 게다가 Option 키 조합은 제대로 전달하지 못하는 것 같다. 이 때문에 CMUX가 전단에 있으면 만들어둔 RetroStage/SpectraSearch 등등의 TMUX plugin을 제대로 써먹을 수 가 없다. 그래서 TMUX 세션으로 attach 하면, 뭐 그런 제한은 없어지니까.


나의 인지 구조적 특징을 활용하고 도와줄 context-storage-mcp 서버 개발

나의 인지 구조적 특징은, 전체를 보고 있으나 실행은 작은 단위로 잘라서 진행하고 반드시 그 결과를 확인하고 예측과 실제를 비교해야만 다음 스텝으로 진행이 끝없이 이어진다는 점이다. 결과를 비교하지 않으면 창의적인 결과물을 얻기 힘들고, 진행 에너지가 점점 줄어든다. 각각의 단위 실행마다 여러가지 실험을 할 수 있어야 하고, 그 결과를 반드시 어떤 식으로든 종료/매듭지어야 한다. postponed 또는 obsolete 등으로 태그만 적용해도 인지구조에 오버플로우는 줄어든다. 아무것도 남기지 않는 것이 문제다.

LLM을 사용해서 서버관리를 할 때, 호스트에서 처리하는 각각의 태스크는 태스크 상호간에 영향을 미친다. 즉, 독립적이지 않다. 하나의 결정과 처리는 반드시 다른 곳에 영향을 주는 것이 대부분이다. 각 태스크가 어떤 식으로 호스트에 영향을 주는지를 추적해야하는데, LLM으로 서버관리를 하던 초기에는 추적하는 것 자체를 Workspace/Task.list 라는 파일로 만들고, Workspace/T1, Workspace/T2 .. Workspace/T1.B 등과 같이 태스크 식별자로 구분된 폴더에 관련 문서와 사용한 스크립트 등을 쌓아두는 식으로 간편하게 처리했었다. WP plugin을 개발하는 내용에는 서버측 조정 사항이 포함되므로 더더욱 문맥 유지가 필요하다.

문제는 태스크가 쌓이면서 현재 태스크가 이전 태스크의 결정을 업데이트하거나 아예 불필요하게 만들기도 하므로 문서 업데이트가 수시로 일어난다는 점이다. 그것도 그때마다 LLM에게 적용하라고 하면 되지만, 여전히 토큰 비용문제 때문에 그렇게 만만하진 않다. 태스크가 쌓이면 쌓일수록 늘어간다. 그리고 그것을 명시적으로 파일에 기록하면 LLM과 통신하는 문맥에 쓸데없이 끼어들어 문맥이 거대해지는 것을 막을 방법이 없다. 이때, MCP서버간의 통신채널로 그런 정보를 교환하게 하면 /compact 등의 명령으로 문맥을 압축할 때, 대화만 남기고 불필요한 정보를 없애버림으로써 문맥 크기를 유지하는데 도움이 될 것이다. (뭐가 됐든 나는 쩔 수 없이 비용을 항상 염두에 둬야 한다. 비용걱정없으면 뭐… 좋겠지)

예를 들어 CSV block 이라는 기능을 사용하는 여러 메뉴를 만들었는데, 어느 페이지에서 수정이 발생하면 그 기능을 사용하는 모든 메뉴에 적용해야할 때가 있다. 하나의 파일로 모든 메뉴를 처리하게 하는 건 기본적인 조치사항이지만 조금씩 다를때는 그 메뉴마다 규칙을 지켜야 한다. 그걸 기억해서 처리하라고 직접 명령하는 건 무리데스. 개발과정에서 작은 단위의 분기가 계속해서 발생하고, 그때마다 누적된 결과가 이전 규칙을 업데이트 해야한다. 그걸 단순히 파일로 기억시키는 건 기술적인 문제를 초래하는 것보다 토큰비용 문제를 초래한다. 암튼 내가 구상한 건 태스크를 진행하면서 이전 내용과 현재 내용을 계속해서 업데이트하고 내가 뭐라고 지껄이든 간에 내 요청에 찰떡같이 알아듣고 실행하도록 도와줄 MCP를 만드는 것이다.

ctxstore MCP 라고 개명, 꽤 쓸만하다

역시, 실전적으로 MCP 서버를 사용하면서 제작하니 꽤 빨리 어떻게 해야할지를 알게된다. 이미 Graph Network 방식의 데이터 저장과 이용방식을 적용했다. 설정이 거의 필요없는 LanceDB를 사용해서 RAG 방식으로 검색을 해볼까도 생각했으나, 그건 나중으로 미루었다. 현재는 MCP 자체에 LLM에게 사용방법을 강하게 어필하고 AGENTS.md 파일에 사용을 강제하는 규정을 넣고있으나, 가능한 AGENTS.md 파일엔 규정 자체를 넣지 않아도 동작시키는게 목표다. 그게 쉽지 않는게, ctxstore 가 제공하는 기능이 태스크/기억 관련이라 경쟁 상대가 너무 많고 드러나기엔 일반적인 문맥이다. 주요 특징은 아래와 같다:

– Agent가 기준 문서를 무분별하게 재작성하지 않도록 기록 영역과 제안 영역을 분리해 두었다.

– topic별 문맥을 사람 지정 순서대로 로드한다.

– `nginx`, `tls`, `firewall` 같은 자연스러운 주제어를 실제 bundle topic으로 해석하는 topic resolver를 제공한다.

– `T1`, `T8.A` 같은 TaskID 중심 색인을 만들어 과거 작업, 관련 태스크, 최신 작업 순서를 조회할 수 있다.

– topic 생성, 병합, keyword 보강, include 재배치 같은 topic 유지보수 도구를 제공한다.

– CLI importer로 기존 문서 폴더를 `.context_storage` 구조로 부트스트랩할 수 있다.

– 모든 문서는 매 도구 호출 시 새로 읽어 사람이 직접 수정한 내용이 다음 호출에 반영될 수 있다.
– vocabulary를 관리하여 같은 뜻, 다른 표현을 찰떡같이 알아듣는다.

이를 테면, 태스크 하나를 진행하면서 발생하는 루틴을 하나의 이름으로 정의하고, 그 루틴과 관련된 태스크를 topic으로 묶거나 keyword로 엮어두면, 다른 세션에서 아무런 언급이 없어도 해당 표현에 해당하는 decision을 모두 탐색하여 곧바로 문맥에 적용하므로 아주, 아주, 속이 시원했다. 게다가 Context 문서를 직접 보고 수정하면 곧 바로 적용되므로 동적 AGENTS.md 파일을 만든 것과 비슷한 효과가 있다.

 몇일간 더 사용해보고 글을 이어가겠다.

화상을 입었다

빡치는 건 아파서가 아니라

뻔히 보이는 잠깐의 부주의함으로 인해 오랬동안 눈에 거슬리는 상처가 남는다는 거다.

감정도 마찬가지라는 생각이 또 소리치듯 올라왔다.

타인의 사정을 내가 이해한다고 해서

내게 남아있는 생채기의 감각이 사라지지 않듯이

왜 그렇게 했는지 이해시킨다고 해서 상대방의 감정에 남긴 생채기가 깨끗하지진 않을거다.

그래서, 감정은 소중하다.

너무 뒤늦게,, 감정의 소중함을 알게된 때에는

나도 당신도 너도, 돌이킬 수 없는 마음의 생채기가 감정을 딱딱하게 한다.

그래서 성경에도 분명히 강한 경고가 있었는데,

난 그걸 항상 맘에 두고 있었는데,

다 … 소용없는 짓거리였나.

잠4:23 무릇 지킬만한 것보다 더욱 네 마음을 지키라 생명의 근원이 이에서 남이니라

Watch over your heart with all diligence, For from it flow the springs of life.

Pico-8 Game: Gadget_v2

WordPress Page를 쓰지 않고, 일반 Post 게시물에서 React/Flutter로 만든 페이지/앱을 임베딩하여 마치 WrodPress 게시물의 일부처럼 보이게하고 싶었다. 페이지나 포스트 하나 하나가 다른 앱을 보는 것인가 착각할 정도로 독립된 영역에서 동작하되, 업데이트와 관리가 가능해야한다는 요구조건을 만족시키는 방법을 실험하기 위한 게시물이다. 겉으로는 단순히 WASM으로 변환된 Pico8 게임이긴 한데, URL 부분에서 거추장스런 파라미터를 제거하고 단독 게시물 보기에서는 전체화면이 되면서도 게시물 리스트에서는 일반 게시물처럼 동작한다. 물론 Admin용 Plugin을 통해서 제어를 해야 한다. 계속 다듬어가면 WordPress 버전의 앱스토어 처럼될지 누가 아나;;

이유는 모르겠는데 Pico-8 에뮬레이터에선 이 게임만 주구장창 한다.

빨리 턴이 끝나는 것도 이유일 것이구,

쫒아오는 놈들이 늘어갈수록 머리 돌아가는 소리가 들려서인지도 모르겠다.

코인을 10개 획득할 때마다 x 키를 누르면 싹쓸이 할 기회가 온다.

득달같이 달려드는 놈들을 피해 요리조리;; 손가락 빠지는 재미를 느껴보라

Press Enter Key …

second life

만날 사람과 통화를 하는데 그 사람이 “내일 오후 6시까지는 바빠서 만날 수가 없어” 라고 말한다면,

그건 내일 오후 6시 이후에는 만날 수 있다는 말이지 만나지 않겠다거나 만날 수 없다는 말은 아니다.

 

마찬가지로 성경에 기록된 “천 년이 차기까지는 살지 못한다”는 표현은 “천 년이 차면 살아난다”라는 선언과 같다. 

계20:5 (그 나머지 죽은 자들은 그 천년이 차기까지 살지 못하더라) 이는 첫째 부활이라

The rest of the dead did not come to life until the thousand years were completed. This is the first resurrection.

 

첫째 부활이 있다면 두번째 부활도 있다는게 뭐가 그리 이상한가? 

두번째 죽음이 있으려면 첫번째 죽음이 선행되야 하는 것 처럼 당연한 논리다.

계20:14 사망과 음부도 불못에 던지우니 이것은 둘째 사망 곧 불못이라

Then death and Hades were thrown into the lake of fire. This is the second death, the lake of fire.

 

그러나 너무 뻔뻔하게도 한번 죽으면 끝이라는 이상한 논리로 세뇌당했기 때문에 

이 간단한 셈법을 마치 이단의 괴수라도 되야 말할 수 있는듯 꺼린다.

 

사람은 두번 째 삶이 있다는 것을 전혀 기대하지도 못하고 않하는 것이 더 낫다. 그렇지 않다면 첫번째 삶을 개판으로 살 가능성이 절대적으로 높기 때문이다.

하지만 거의 대부분의 사람은 떠올리기도 싫은 기억들을 가지고 있기에,

두번째 삶을 산다는 것이 믿어지면 기억을 밀어버리고 싶은 충동에 쌓일지도 모른다. 

똑같은 신체조건, 능력조건, 기억을 가지고 두번 째 삶을 산다는 건, 누군가에게는 첫번 째 삶에서의 불평등과 불만을 이어가는 것과 다를바가 없을수도 있기 때문이다.

 

두번 째 삷이 있다는 것이 믿겨지면 당신은 어떤 생각이 들었는지 내게도 알려주면 고맙겠다. 
참고로 나는 내 기억을 밀어버리고 싶은 쪽이다.

 

아, 그리고 두번 째 삶도 아기부터 시작하는 건 아니라는 건 확실하다.

클래식 기타는 내 스타일이 아니었으니,  가능하면 두번째 삶에선 바이올린부터 시작하고 싶은데… 가능하면 좋겠다.

그림도 포기하지 않을 작정.

 

WP 방어

십수년간 업데이트 해오던 내 자료;; 홀라당 날려먹고 백업도 없어서 블로그를 내 팽겨친채 있다가, 다시 운영하려 할 때 걸림돌이 보안 문제였다.  워드프레스 사이트를 운영해본 사람들은 얼마나 공격질을 해대는지 이해할 것이다. 블로그를 내팽겨친 건 그 보안문제를 해결할 마땅한 방법이 없었기도 하거니와 시간내서 그 정도를 만들고 싶지도 않았기 때문이다.

그런데, 이제 우리에겐 Agent가 있지않은가?!ㅎ 그냥 시키면 된다. 그래서 RAM은 22G 라서 넉넉하다 못해 광활하지만 CPU나 Storage 제약이 심한 OCI 가상머신에서 동작시킬 방어방법을 생각해내고 구현하는게 최종목적이었다.

Snort3를 돌리는 건 파리잡자고 머신건 쓰는 격 아니던가. 그럼에도 불구하고 사용하는 건 HTTP/HTTPS 포트에 다른 프로토콜을 사용하는 경우를 차단하기 위해서다. 일단 걸리면 무조건 하루동안 block이 되게 했다. 정상 사용자가 아닌 것이 확실하기 때문. 그리고 많은 규칙을 만드는 것 보다 NGINX 접근에 따라 실시간으로 반응하게 하는게 이 구성의 핵심이다. 즉, 불필요한 로그인을 시도하는 것 자체가 3시간~6시간 block 감이고, .env 또는 그 변형을 찾거나 언제적 cgi-bin/ 을 호출하는 것 같은 짓거리를 하면 곧바로 6시간 block을 먹인다.  block은 차단이 아니라 packet drop 방식을 사용하는데, 상대방에겐 사이트가 하염없이 연결 대기중 상태가 된다. 그래서 봇이나 앱으로 내용 검색을 시도하면 timeout을 설정하지 않는 이상 앱이나 봇이 무한대기를 할 것이다. 상대방을 짜증나게해서 아예 탐색 대상에서 내 서버를 제외시키려는 의도라고나 할까;;;

또한 secure_link를 사용해서 REFERER 없이 접근하는 것을 막고, REFERER를 위조하는 경우도 막는다. 아… 물론 그렇게 대단한게 있어서 막는게 아니라, 사이트 트래픽이 곧 비용이라서 그런거다.

GeoIP를 사용해서 상당수 국가에서 오는 트래픽 자체를 막았다. 차단국가는 CN,KP,TW,SG,IN,VN,HK,MY,ID,TH,MM,BD,LA,PK,AF,IR,IQ,SA,YE,OM,RU,US,DE,BR.   미국도 포함된다ㅋ  겁나게 많은 공격이 미국발이다.

 

현재상태는 조용히 게시글만 보고 댓글 달 사람만 알음알음 오면 된다. 잘 나가는 블로그가 되면 뭐 그때 생각해보자.

관제하는 Admin Plugin을 만들게되면 이 게시글을 업데이트 할 예정.

 

 

Agent에게 일을 시킬 때 중요한 건, 여러 태스크 간의 목적과 결과를 참조하게 하는 것이었다. Hermes는 개발단계에선 부적절하다.

방법이야 여러가지가 있겠고 나는 나대로의 방법으로 진행했는데 토큰 소모가 좀 많았던게 흠이다. 지금 생각은 그 과정자체를 MCP로 제어하면 토큰 소모를 줄일 수 있지 않았을까 생각이 든다.

Telegram Noti는 초반엔 유용했는데, 뻔한 패턴이 반복되니 꺼버리고 그냥 차단 시간/일수를 왕창 늘여서 불필요한 알림을 줄이는 쪽으로 가게됐다.

`260630 현재, 차단된 IP는 10개 미만이다.

target         country  expires_at        remaining   reason
176.32.193.16  AM       2026-06-30 11:49  0d/1h/1m    nginx-access-general-protection-ip-host-access
206.189.5.249  NL       2026-06-30 11:50  0d/1h/2m    nginx-access-general-protection-ip-host-access
34.78.81.50    BE       2026-06-30 11:03  0d/0h/15m   nginx-access-general-protection-ip-host-access
93.174.93.12   NL       2026-07-01 07:32  0d/20h/44m  repeated-block-immediate:2026-06-30