CLI 기반 도구의 한계를 짚고, MCP 서버를 여러 툴로 쪼개기보다 상태를 유지하는 단일 ‘코드 실행’ 툴(예: pexpect 기반 Python eval, Playwright JS eval)로 노출하는 접근을 제안한다. 이 방식이 합성, 세션 관리, 재사용성에서 유리한 이유를 LLDB 디버깅과 Playwright 예시로 보여주며, 보안 문제도 논의한다.
작성일: 2025년 8월 18일
얼마 전, 일부 작업에서는 MCP(모델 컨텍스트 프로토콜)보다 코드가 더 잘 작동하는 이유에 대해 썼다. 특히 커맨드라인 도구들을 사용할 수 있다면, 에이전트형 코딩 도구들은 그것들을 매우 기꺼이 활용하는 경향이 있다고 지적했다. 그 사이, 이 문제에 약간의 뉘앙스를 더해주는 몇 가지를 더 배웠다. CLI 기반 도구에는 해결하기 꽤 어려운 난제들이 몇 가지 있고, 추가적인 검토가 필요하다.
이 글에서 제시하고 싶은 (그리 새로운 것은 아닌) 아이디어는, MCP 서버가 단 하나의 툴만 노출하되 그 툴이 입력으로 ‘프로그래밍 코드’를 받게 하자는 것이다.
첫 번째이자 가장 분명한 문제는 CLI 도구들이 때때로 플랫폼 의존적이고, 버전 의존적이며, 경우에 따라 문서화조차 잘 되어 있지 않다는 점이다. 그래서 처음 도구를 쓸 때 실패를 자주 마주치곤 한다.
좋은 예가 비 ASCII 문자열 입력이 필요한 경우다. 예를 들어 Sonnet과 Opus는 셸 인자로 개행이나 제어 문자를 어떻게 넘겨야 하는지 헷갈릴 때가 있다. 불행한 일이지만, 아이러니하게도 이런 문제는 셸 도구에만 국한되지는 않는다. 예컨대 C로 프로그램을 작성해 컴파일할 때는 끝에 개행 문자가 필요하다. 때때로 에이전트형 코딩 도구들은 파일 끝에 빈 줄(개행)을 덧붙이는 데 정말 애를 먹고, 이를 우회하려고 꽤 인상적인 도구 루프를 만들어 내기도 한다.
도구가 학습 데이터에 전혀 없고 생소한 문법을 쓸 때는 특히 더 답답하다. 그런 경우에는 에이전트에게 그 도구를 쓰게 하는 일이 정말 성가시게 느껴질 수 있다.
또 다른 문제는 일부 에이전트(특히 Claude Code)에서 셸 호출 시 추가적인 패스, 즉 보안 사전 점검이 수행된다는 점이다. Claude는 도구를 실행하기 전에, 그것이 위험한 일을 할지를 파악하기 위해 빠른 Haiku 모델로 한 번 더 검토하고 호출을 회피하기도 한다. 여러 턴이 필요한 경우 이 과정은 도구 사용을 더 느리게 만든다.
일반적으로 CLI 도구로 다중 턴 작업을 하는 것은 매우 어렵다. 에이전트에게 세션을 어떻게 관리할지 가르쳐야 하기 때문이다. 좋은 예가 tmux로 LLDB 세션을 원격 제어하라고 요청하는 상황이다. 에이전트는 그걸 충분히 해낼 수 있지만, tmux 세션 상태를 놓치기도 한다. 테스트 중엔 중간에 세션 이름을 바꿔 놓고, 세션이 있다는 사실을 잊어버려(그래서 종료도 안 하는) 버리는 경우도 있었다.
이게 특히 답답한 이유는, 작은 디테일 하나를 틀린 탓에 작업을 아예 처음부터 다시 하거나 다른 도구로 넘어가 버리는 식의 실패가 발생할 수 있기 때문이다.
불행히도 MCP로 옮겨 가면(적어도 지금은) 추론 없이 합성하는 능력을 바로 잃는다. lldb를 tmux로 원격 제어할 수 있는 이유 중 하나는 에이전트가 합성을 꽤 잘해내기 때문이다. 어떻게 그럴 수 있을까? tmux send-keys로 입력을 보내고 tmux capture-pane으로 출력을 읽는 식의 기본 tmux 명령을 사용하기 때문이다. 이런 것들은 추가 도구가 많이 필요하지 않다. 그런 다음 sleep과 tmux capture-pane을 체이닝해서 출력을 너무 일찍 읽지 않도록 한다. 또한 복잡한 문자를 인코딩하는 데 실패하기 시작하면 접근 방식을 바꿔 base64 -d를 사용하기도 한다.
커맨드라인은 사실 하나의 도구가 아니다 — 프로그래밍 언어인 bash로 서로 합성되는 일련의 도구들이다. 가장 흥미로운 사용 사례는, 나중에 재사용할 수 있도록 직접 도구를 쓰게 할 때다. 그러면 원라이너들을 모아 큰 스크립트를 합성하기 시작한다. 이런 것들은 오늘날 MCP에서는 어렵다.
셸 도구들로 할 수 있는 일엔 분명한 한계가 있다. 어느 순간부터는 그 도구들과 싸우기 시작한다. 이런 도구들은 많은 면에서 사용자 인터페이스(UI)만큼만 좋고, 어떤 UI는 본질적으로 까다롭다. 예컨대 tmux가 GNU screen보다 더 잘 동작한다는 평가가 있는데, 이는 주로 tmux의 CLI가 더 낫고 오류를 유발하기 쉬운 부분이 적기 때문이다. 하지만 어느 쪽이든 에이전트가 상태 있는 세션을 유지해야 하고, 오늘날 그게 특히 서투르다.
반면 MCP는 태생부터 상태를 유지한다. MCP 서버를 돌리는 놀랍도록 유용한 방식 하나는, 단일 툴(일종의 ‘우버툴’)만 노출하는 MCP 서버로 만드는 것이다. 그 툴은 상태를 유지한 채 eval()을 실행하는 파이썬 인터프리터일 뿐이다. 백그라운드에서 상태를 유지하고, 에이전트가 이미 사용하는 법을 아는 인터페이스를 제공한다.
이 실험을 여러 방식으로 해봤고, 공개된 것은 pexpect-mcp다. 이 MCP는 pexpect_tool이라는 단일 툴만 노출한다. 하지만 이는 여러 면에서 오해를 부른다. 실제로는 ‘pexpect’ 툴이 아니라, pexpect가 설치된 가상환경에서 돌아가는 파이썬 인터프리터다.
pexpect가 뭐냐고? 아주 오래된 커맨드라인 도구 expect의 파이썬 포트다. 스크립트를 통해 대화형 커맨드라인 프로그램과 상호작용할 수 있게 해준다. 문서에서는 expect를 “스크립트에 따라 다른 대화형 프로그램과 ‘대화’하는 프로그램”이라고 설명한다.
pexpect의 특별한 점은 오래되었고, API가 안정적이며, 온갖 곳에서 쓰여 왔다는 것이다. pexpect_expect, pexpect_sendline, pexpect_spawn 같은 MCP 툴 여러 개로 expect나 pexpect를 감쌀 수도 있다. 왜냐하면 pexpect.Spawn 클래스는 무려 36개의 API 함수를 노출하기 때문이다! 엄청 많다. 하지만 그중 다수는 단독으로 잘 쓰기 어렵다. 문서의 다음 모티베이션 예시를 보자:
child = pexpect.spawn('scp foo user@example.com:.')
child.expect('Password:')
child.sendline(mypassword)
여기서 가장 기본적인 사용조차 세 개의 연쇄된 도구 호출을 포함한다. 여기에 오류 처리를 포함하면 더 들어간다.
그래서 훨씬 흥미로운 방식은 MCP에 대한 커맨드 언어를 그냥 파이썬으로 만드는 것이다. MCP 서버는 상태가 유지되는 파이썬 인터프리터가 되고, 툴은 이전과 동일한 상태에서 평가되는 파이썬 코드를 보낼 수 있게 해준다. 경험을 더 신뢰할 수 있게 하려는 약간의 지원(예: 타임아웃 지원)이 MCP 서버에 있지만, 대부분의 인터페이스는 그냥 파이썬 코드를 보내는 것이다. 실제로 위의 정확한 스크립트를 MCP 클라이언트가 보내게 된다.
툴 설명은 이렇게 아주 간단하다:
파이썬 코드를 pexpect 세션에서 실행합니다. 프로세스를 생성하고 상호작용할 수 있습니다.
Args:
code: 실행할 파이썬 코드. 생성된 프로세스와 상호작용하려면 'child' 변수를 사용하세요. pexpect 라이브러리는 이미 import되어 있습니다. 무언가를 실행하려면 pexpect.spawn(...)을 사용하세요. timeout: 선택적 타임아웃(초). 제공하지 않으면 전역 TIMEOUT(기본 30초)을 사용합니다.
Example:
child = pexpect.spawn('lldb ./mytool')
child.expect("(lldb)")
Returns: 코드 실행 결과 또는 오류 메시지.
이게 가능한 이유는 이제 MCP에 대한 인터페이스가, 모델이 전혀 본 적 없는 개별 도구가 아니라 모델이 아주 잘 이해하는 프로그래밍 언어이며, 추가적으로 학습에서도 보고 패턴을 익힌 SDK(pexpect)에 접근할 수 있는 형태가 되었기 때문이다. 우리는 MCP의 역할을 그것이 정말 잘하는 일, 즉 세션 관리와 내장 프롬프트를 통한 도구 유도에 국한시키고 있다.
더 중요하게는, 에이전트가 작성하는 코드는 재사용 가능한 스크립트에 담길 법한 코드와 매우 유사하다는 점이다. 실제 MCP 쪽 배관(플럼빙)이 거의 없기 때문에, 세션이 끝난 뒤 세션에서 배운 것을 바탕으로 재사용 가능한 pexpect 스크립트를 작성하라고 에이전트에게 시킬 수 있다. 세션에서 실행한 명령들이 모두 파이썬이기 때문에 — 여전히 컨텍스트 안에 있고 — 그걸 재사용 가능한 파이썬 스크립트로 옮기는 일의 부담이 낮다.
Claude 출력으로 지루하게 하고 싶지는 않지만, Mario가 만든 크래시 나는 데모 앱을 가져와 pexpect_tool을 통해 LLDB로 디버깅하라고 시켰다. 대략 이렇게 진행됐다:
전체 세션 펼치기
Please debug ./demo-buggy with lldb
⏺ 제가 lldb로 ./demo-buggy 실행 파일을 디버그하는 걸 도와드릴게요. 먼저 무엇이 있는지 확인한 다음, lldb를 실행해 디버깅을 시작하겠습니다.
⏺ List(~/Development/pexpect-mcp) ⎿ Listed 28 paths (ctrl+r to expand)
⏺ 이제 pexpect 도구를 사용해 demo-buggy 실행 파일로 lldb 디버깅 세션을 시작하겠습니다:
⏺ pexpect - pexpect_tool (MCP)(code: "import pexpect\n\n# Spawn lldb with the demo-buggy executable\nchild = pexpect.spawn('lldb ./demo-buggy')\nchild.expect("(lldb)")\nprint("LLDB started successfully")\nprint(child.before.decode() + child.after.decode())") ⎿ 코드가 성공적으로 실행되었습니다
⏺ pexpect - pexpect_tool (MCP)(code: "# Let's run the program to see what happens\nchild.sendline('run')\nchild.expect_list([pexpect.TIMEOUT, "(lldb)", "Process.*exited"], timeout=5)\nprint("Output after 'run' command:")\nprint(child.before.decode() + child.after.decode())") ⎿ 오류: 'str' object has no attribute 'search'
⏺ pexpect 사용법을 수정하겠습니다:
⏺ pexpect - pexpect_tool (MCP)(code: "# Let's run the program to see what happens\nchild.sendline('run')\ntry:\n index = child.expect([pexpect.TIMEOUT, pexpect.EOF, "(lldb)", "Process.*exited"], timeout=5)\n print(f"Matched pattern index: {index}")\n print("Output after 'run' command:")\n print(child.before.decode() + child.after.decode())\nexcept Exception as e:\n print(f"Exception: {e}")\n print("Current buffer:")\n print(child.before.decode() if child.before else "No before content")") ⎿ 코드가 성공적으로 실행되었습니다
⏺ pexpect - pexpect_tool (MCP)(code: "# Let's check what's in the buffer and set a breakpoint first\nchild.sendline('breakpoint set --name main')\nchild.expect("(lldb)")\nprint("Breakpoint set output:")\nprint(child.before.decode() + child.after.decode())") ⎿ 코드가 성공적으로 실행되었습니다
⏺ pexpect - pexpect_tool (MCP)(code: "# Now run the program\nchild.sendline('run')\nchild.expect("(lldb)")\nprint("Run output:")\nprint(child.before.decode() + child.after.decode())") ⎿ 코드가 성공적으로 실행되었습니다
⏺ pexpect - pexpect_tool (MCP)(code: "# Let's examine the source code first to understand what we're debugging\nchild.sendline('source list')\nchild.expect("(lldb)")\nprint("Source listing:")\nprint(child.before.decode() + child.after.decode())") ⎿ 코드가 성공적으로 실행되었습니다
⏺ 디버깅 대상이 무엇인지 이해하기 위해 소스 코드를 직접 확인하겠습니다:
⏺ Read(demo-buggy.c) ⎿ Read 77 lines (ctrl+r to expand)
⏺ 완벽합니다! 이제 코드의 버그가 보이네요. LLDB 디버깅 세션을 이어가며 이 문제들을 찾는 과정을 보여드리겠습니다:
⏺ pexpect - pexpect_tool (MCP)(code: "# Continue execution to hit the crash\nchild.sendline('continue')\nchild.expect("(lldb)")\nprint("Continue output:")\nprint(child.before.decode() + child.after.decode())") ⎿ 코드가 성공적으로 실행되었습니다
⏺ pexpect - pexpect_tool (MCP)(code: "# Let's examine the backtrace to see where the crash occurred\nchild.sendline('bt')\nchild.expect("(lldb)")\nprint("Backtrace:")\nprint(child.before.decode() + child.after.decode())") ⎿ 코드가 성공적으로 실행되었습니다
⏺ pexpect - pexpect_tool (MCP)(code: "# Let's examine the current frame and variables\nchild.sendline('frame variable')\nchild.expect("(lldb)")\nprint("Frame variables:")\nprint(child.before.decode() + child.after.decode())") ⎿ 코드가 성공적으로 실행되었습니다
⏺ pexpect - pexpect_tool (MCP)(code: "# Let's look at the student structure to see the NULL pointer\nchild.sendline('p *s')\nchild.expect("(lldb)")\nprint("Student structure:")\nprint(child.before.decode() + child.after.decode())") ⎿ 코드가 성공적으로 실행되었습니다
⏺ pexpect - pexpect_tool (MCP)(code: "# Let's specifically look at the scores pointer\nchild.sendline('p s->scores')\nchild.expect("(lldb)")\nprint("Scores pointer:")\nprint(child.before.decode() + child.after.decode())") ⎿ 코드가 성공적으로 실행되었습니다
⏺ pexpect - pexpect_tool (MCP)(code: "# Let's also examine the disassembly around the crash\nchild.sendline('disassemble --frame')\nchild.expect("(lldb)")\nprint("Disassembly:")\nprint(child.before.decode() + child.after.decode())") ⎿ 코드가 성공적으로 실행되었습니다
⏺ pexpect - pexpect_tool (MCP)(code: "# Exit lldb\nchild.sendline('quit')\nchild.expect(pexpect.EOF)\nprint("LLDB session ended")") ⎿ 오류: Operation timed out after 15 seconds
⏺ pexpect - pexpect_tool (MCP)(code: "# Force quit\nchild.close()\nprint("LLDB session closed")") ⎿ 코드가 성공적으로 실행되었습니다
⏺ lldb 디버깅 세션을 바탕으로 ./demo-buggy의 주요 문제를 정리하면 다음과 같습니다:
핵심 발견:
- 세그멘테이션 폴트(demo-buggy.c:26):
s->scores[s->num_scores]에 쓰기를 시도할 때s->scores가 NULL이라 크래시가 발생합니다.- 근본 원인:
create_student()함수 19행에서, 점수 배열 할당이 주석 처리되어 NULL로 설정되어 있습니다:s->scores = NULL; // This will cause a segfault when we try to write to it- 추가 버그:
calculate_average()(34행)에서 오프바이원 오류가 있습니다:for (int i = 0; i <= s->num_scores; i++) { // BUG: <= should be <디버그 세션 요약:
add_score(alice, 95.5)호출 시 즉시 프로그램이 크래시납니다- 백트레이스는 크래시가
add_score함수에서 발생함을 보여줍니다s->scores포인터가 NULL이라 역참조 시 세그폴트가 납니다calculate_average()의 두 번째 버그까지 도달하기 전 단계에서 이미 크래시가 납니다이 lldb 세션은 세그멘테이션 폴트의 정확한 위치와 원인을 식별해, 크래시를 근본 원인까지 추적하는 방법을 보여주었습니다.
그 다음엔 나중에 재사용할 수 있도록 파이썬 스크립트로 덤프하게 했다:
스크립트를 쓰게 한 프롬프트
now that you wrote this pexpect script, could you dump out a python script file with those pscript commands so we can use it again later?
⏺ Write(debug_demo.py) ⎿ Wrote 95 lines to debug_demo.py #!/usr/bin/env python3 """ Debug script for demo-buggy using pexpect and lldb. This script automates the debugging session to identify the segfault. """ import pexpect import sys def debug_demo_buggy(): … +85 lines (ctrl+r to expand)
⏺ debug_demo.py를 생성했습니다 - 이제
python debug_demo.py로 전체 디버깅 세션을 자동으로 재생할 수 있습니다.
그리고 새 세션에서 다시 한 번 실행해 보라고 했다:
please run debug_demo.py with uv run to figure out what's going on
⏺ Bash(uv run debug_demo.py) ⎿ Starting lldb debugging session for ./demo-buggy ✓ LLDB started successfully … +155 lines (ctrl+r to expand)
⏺ 디버그 스크립트가 성공적으로 실행되어 문제를 식별했습니다! 요약은 다음과 같습니다:
주요 문제:
demo-buggy실행 파일은add_score()함수에서s->scores가 NULL(메모리 할당 안 됨)이기 때문에 세그폴트로 크래시납니다.근본 원인:
create_student()의 18행이 주석 처리되어 있어 점수 배열이 할당되지 않습니다.해결:
create_student()함수의 18행 주석을 해제해 점수 배열에 메모리를 할당하세요.추가 이슈:
calculate_average()함수에도 오프바이원 오류가 있습니다.디버깅 세션은 다음을 보여줍니다:
- LLDB가 성공적으로 실행되어
main에 브레이크포인트를 설정했습니다- 프로그램은 브레이크포인트에서 멈췄습니다
alice와bobStudent포인터가 생성됨을 확인했습니다scores에 할당되지 않은 메모리에 접근하려다 크래시가 발생합니다이는 pexpect MCP 도구가 LLDB로 C 프로그램을 디버깅하는 데 제대로 작동함을 보여줍니다!
이것도 MCP에 쓴 코드가 파이썬 스크립트에 쓸 코드와 매우 가깝기 때문에 가능하다. 차이는 의미가 있다. 초기 디버그는 내 머신에서 약 45초가 걸리고 도구 호출을 약 7번 쓴다. 덤프한 플레이북으로 재실행하면 도구 호출 1번에 5초 미만으로 끝난다. 가장 중요한 점: 그 스크립트는 독립 실행형이다. MCP 없이 사람도 직접 실행할 수 있다!
위 예시는 모델들이 pexpect에 대해 워낙 많이 알고 있어서 아름답게 동작한다. 어떤 의미에서는 놀랍지 않다. 그렇다면 모델이 전혀 모르는 코드를 써야 할 때는 얼마나 잘 될까? 음, 그 정도로 잘되지는 않는다. 하지만 핵심은, 메타 입력 언어가 파이썬이라는 덕분에, 우버툴 하나가 노출할 수 있는 총 기능 표면적이 꽤 인상적이라는 점이다.
오늘날 MCP의 일반적인 문제는 툴이 많아질수록 컨텍스트 부패(context rot)에 더 기여한다는 것이다. 또한 입력 길이에도 꽤 제한이 걸린다. 반면 프로그래밍 언어를 노출하는 MCP를 쓰면, 학습에서 알던 많은 기능이 간접적으로 함께 노출된다.
예컨대 이 방식의 정말 멋진 점 하나는 모델이 dir(), globals(), repr() 같은 걸 안다는 것이다. 심지어 sys._getframe()도 안다. 이는 샌드박스가 어떻게 동작하는지, 무엇이 가능한지 아주 기본적인 안내만 제공해도 필요할 때 스스로 학습해 나갈 수 있음을 의미한다. 프롬프트에서, 도움이 필요할 때 무엇이 가능한지 더 알아볼 수 있는 함수를 실행하라고 알려줄 수도 있다!
그래서 완전히 새로운 것을 만들 때도 최소한 프로그래밍 언어는 익숙하다. 예컨대 애플리케이션의 내부 상태를 덤프해 주는 작은 MCP를 만들고, 샤딩을 지원하는 데이터베이스 기본 쿼리 헬퍼나 데이터 읽기 API를 제공할 수 있다. 모델은 어차피 코드를 읽으며 모두를 발견할 수 있고, 이제는 상태 있는 파이썬(또는 자바스크립트) 세션을 사용해 이런 도구들을 실행하고 더 탐색할 수도 있다.
이건 MCP 자체를 디버깅시키고 싶을 때도 유용한 기능이다. 파이썬과 자바스크립트는 매우 강력하기 때문에, 무언가 잘못되었을 때 MCP의 상태 자체를 디버깅하라고 시킬 수도 있다.
에이전트형 코딩 전반에서 방 안의 코끼리는 보안이다. Claude가 대체로 당신의 머신을 지우는 일은 없고, 부분적으로는 Haiku 사전 점검 덕일지도 모른다. 하지만 이 모든 게 결국 허상(sham)이 아닌가? 나는 일반적으로 Claude와 다른 에이전트들이 각종 보호 장치를 꽤 창의적으로 우회하는 걸 보는 걸 좋아한다. 분명 강력하며 프롬프트 인젝션에도 취약하다. eval()만 실행하는 MCP를 만들면 남아 있는 안전장치마저 걷어내는 셈일 수 있다.
하지만 그게 중요한가? 우리는 코드 작성과 테스트 실행을 허용하고 있고, 이는 eval()을 실행하는 것과 똑같이 위험하다. 언젠가 심판의 날이 올지도 모르지만, 지금은 보호가 큰 의미를 갖지 않는 세상에서 이 도구들이 무엇을 할 수 있는지 탐험하고 있다.
솔직히 어떻게 보호하는 게 최선인지 모르겠다. 이런 것들은 본질적으로 안전하지 않고, 안전하게 만들기 불가능에 가깝다. 아마 정말 보호하려면 모든 시스템 콜을 가로채고, 그 위에 일종의 정책 프레임워크/샌드박스를 얹어야 할 것이다. 하지만 그 경우에도 점점 더 영리해지는 LLM이 그 모든 걸 우회하지 못할 이유가 무엇일까? 인터넷에 접근할 수 있고, 프롬프트 인젝션이 가능하며, 우리가 가진 모든 인터페이스는 보호를 제대로 지원하기엔 너무 저수준이다.
그래서 어느 정도는 코드 실행의 테일 리스크가 계속 남아 있을 거라고 본다. 하지만 MCP가 파이썬 코드를 실행한다고 해서 그 위험이 극적으로 더 커지는 건 아니라고 생각한다. 특히 이 경우 pexpect 자체가 프로그램을 실행한다. MCP가 어떤 bash 명령이든 실행할 수 있다면 MCP를 지키는 데 큰 의미가 없다.
pexpect 사례가 흥미롭긴 하지만, 원래의 동기는 이것이 아니었다. 내가 처음 들여다본 것은 Playwright의 MCP를, 자바스크립트를 통해 Playwright API만 노출하는 MCP로 대체하는 것이었다. 이 실험은 한동안 진행 중이고, 결과는 다소 고무적이면서도 아직 충분히 만족스럽지는 않다. 직접 써보고 싶다면 “playwrightess”라는 MCP가 있다. 아주 단순하다. 동기식 Playwright 클라이언트를 상대로 자바스크립트 코드를 실행하게 해줄 뿐이다. 같은 아이디어다. 이 경우 툴 사용이 특히 깔끔한데, 약 30개의 툴 정의에서 1개로 줄어든다:
Playwrightess 세션 출력
navigate to localhost:5001 and summarize all the article links you see
⏺ localhost:5001로 이동해 보이고 있는 아티클 링크들을 요약하겠습니다.
playwrightess-mcp - playwright_eval (MCP)(code: "await page.goto('http://localhost:5001')") ⎿ { "success": true, "result": "undefined", … +5 lines (ctrl+r to expand)
playwrightess-mcp - playwright_eval (MCP)(code: "const articleLinks = await page.$$eval('a[href*="article"], a[href*="/post"], a[href*="/blog"], article a, .article a, .post a, .blog a', links => links.map(link => ({ text: link.textContent?.trim(), href: link.href }))).catch(() => []); if (articleLinks.length === 0) { const allLinks = await page.$$eval('a', links => links.map(link => ({ text: link.textContent?.trim(), href: link.href })).filter(link => link.text && link.text.length > 5)); console.log('All links found:', allLinks); return allLinks; } return articleLinks;") ⎿ { "success": true, "result": [ … +101 lines (ctrl+r to expand)
⏺ localhost:5001에서 찾은 아티클 링크는 다음과 같습니다:
최근 블로그 글:
- Your MCP Doesn't Need 30 Tools: It Needs Code (2025/8/18) - /2025/8/18/code-mcps/
- In Support Of Shitty Types (2025/8/4) - /2025/8/4/shitty-types/
- Agentic Coding Things That Didn't Work (2025/7/30) - /2025/7/30/things-that-didnt-work/
- From Async/Await to Virtual Threads (2025/7/26) - /2025/7/26/virtual-threads/
- Welcoming The Next Generation of Programmers (2025/7/20) - /2025/7/20/the-next-generation/
- Tools: Code Is All You Need (2025/7/3) - /2025/7/3/tools/
- My First Open Source AI Generated Library (2025/6/21) - /2025/6/21/my-first-ai-library/
- We Can Just Measure Things (2025/6/17) - /2025/6/17/measuring/
- Agentic Coding Recommendations (2025/6/12) - /2025/6/12/agentic-coding/
- GenAI Criticism and Moral Quandaries (2025/6/10) - /2025/6/10/genai-criticism/
이 사이트는 Armin Ronacher의 블로그로 보이며, 특히 AI/ML 도구, 코딩 관행, 소프트웨어 개발과 같은 주제에 집중하고 있습니다. 페이지 2로 넘어가는 "Next »" 링크도 있습니다.
이 접근 방식이 더 좋은 또 다른 이유는 데이터를 밖으로 흘려보낼 수 있는 경로가 훨씬 많다는 점이다. 예를 들어 브라우저와 Playwright 스크립트 양쪽의 console.log가 자동으로 에이전트에게 포워딩된다. 에이전트가 그 정보를 요청할 필요가 없다 — 자동으로 온다. 또한 호출 사이에 추가 정보를 누적할 수 있는 state 변수가 있어, 예컨대 페이지네이션으로 여러 페이지에서 데이터를 수집하라고 하면 이를 넉넉히 활용한다. 이 루프는 자바스크립트 내부에서 일어나므로 추가적인 추론 없이도 가능하다.
pexpect도 마찬가지다 — 나중을 위해 스크립트를 덤프하게 만들어 MCP 호출을 대거 생략할 수 있고, 이미 봤던 것을 재사용한다. 특히 골치 아픈 문제를 디버깅하며 여러 번 재시작이 필요한 경우 유망하다. Playwright MCP보다 성능이 좋으냐고? 현재 형태로는 그렇지 않지만, 이 아이디어를 더 발전시킬 수 있는지 보고 싶다. 모델이 작성하는 스크립트가 꽤 장황하고, 스크린샷과 텍스트 추출 사이 밸런스가 아직 잘 맞지 않는다.