aiwebhelper
# AIWebHelper
**브라우저 한 창을 사람과 AI 가 같이 보는** MCP 서버.
사람은 고칠 곳을 짚고, AI 는 같은 화면을 읽고 누른다.
## 왜
사람은 화면을 보고 AI 는 파일을 본다. 그 사이를 스크린샷으로 이으면 서로 짐작하게 되고,
짐작은 틀린다. 「이 부분 좀 고쳐 줘」 — 「어느 부분요?」 — 스크린샷 — 「거기 말고 그 위」.
이 도구는 그 왕복을 없앤다. 사람이 **Alt+클릭**으로 짚으면 AI 가 이렇게 받는다:
```
하고 싶은 말: 이 합계 글씨가 너무 작아요
글자: 8,700,000원
요소: span.v.mono.income
선택자: div.ledg-sum span.v.mono.income
짐작: apps/mail/src/client/ledger/ (클래스 ledg-sum)
감싼 것: div.ledg-sum ← div.ledg-sums ← div.ledg
⚠ 잘림: 6px 모자람 (112px 필요, 106px 있음)
크기: 106×23
스타일: block · 17px · rgb(30,142,90)
주소: http://localhost:5360/ledger/e2fa416e
창 폭: 1000px (기기 픽셀비 2)
```
**창 폭과 잘린 양이 같이 오는 것**이 특히 값지다. 잘림은 폭에 따라 나타났다 사라져서,
「잘려요」와 「안 잘리는데요」가 둘 다 참인 일이 실제로 생긴다.
## 어떻게 도나
두 짝이다.
- **브라우저 확장** (`extension/`) — 도구 버튼이나 `Alt+Shift+P` 로 켠다.
**켠 탭에만** 붙는다. 켜기 전에는 어느 페이지에도 들어가지 않는다.
서버를 부르는 일은 **확장이 대신 한다**(`bridge.js`). 페이지에서 직접 `127.0.0.1` 을
부르면 사이트가 막을 수 있어서다 — X 처럼 `connect-src` 를 못 박아 둔 곳에서는
짚어도 「Failed to fetch」 로 끝난다. 확장은 그 벽에 걸리지 않아 어디서나 같게 돈다.
(북마클릿에는 다리가 없으므로 그때는 페이지가 직접 부른다.)
- **여기** (`server.js`) — 짚기를 HTTP 로 받아 두고(`127.0.0.1:47391`), AI 에게 MCP 도구로 내준다.
반대 방향도 같은 문으로 간다 — AI 가 시킬 일을 두면 페이지가 **열어 둔 물음**으로 가져간다.
0.5초마다 두드리는 대신 열어 두어서, 사람이 쓰는 페이지 안에서 종일 돌아도 조용하다.
프로젝트에 심지 않는 것이 요점이다. 도구가 프로젝트를 따라다니면 프로젝트마다 다시 심어야
하고, 배포에 섞일 걱정도 생긴다.
짚기 조각(`extension/picker.js`)은 **한 벌뿐**이다. 확장이 그 파일을 넣고, 서버도 같은 파일을
`/picker.js` 로 내준다(북마클릿용). 두 벌을 두면 «북마클릿에서는 되는데 확장에서는 안 되는»
종류의 일이 생긴다.
### 포트
`47391`. 7000·8000·5000번대는 개발 서버가 붐벼서 언젠가 부딪히고, 부딪히면 짚기가 **먼저 뜬
쪽으로 조용히 흘러간다.** 그래서 문을 못 열면 모든 도구 응답 앞에 그 사실을 붙인다.
`AIWH_PORT` 로 옮길 수 있고, 그때 `/picker.js` 는 **자기가 듣는 포트를 박아서** 내준다.
## 붙이기
`~/.claude.json` (또는 쓰는 MCP 클라이언트의 설정)에:
```json
{
"mcpServers": {
"aiwebhelper": {
"command": "node",
"args": ["/절대/경로/AIWebHelper/server.js"]
}
}
}
```
포트를 바꾸려면 `"env": { "AIWH_PORT": "47391" }`.
## 확장 얹기 (처음 한 번)
1. 크롬에서 `chrome://extensions` 를 연다
2. 오른쪽 위 **개발자 모드**를 켠다
3. **압축해제된 확장 프로그램을 로드합니다** → 이 저장소의 `extension/` 폴더를 고른다
> 명령줄 `--load-extension` 은 최근 크롬(137+)에서 막혔다. 위 세 걸음이 유일한 길이고,
> 한 번만 하면 된다. 엣지·웨일도 같은 자리에 있다.
북마클릿으로 쓰고 싶으면 AI 에게 «`how_to_start`» 를 물으면 준다 — 확장을 못 얹는 브라우저용이다.
## 쓰기
1. 고칠 화면으로 간다 (프로덕션이든 로컬이든)
2. 도구 버튼을 누르거나 **`Alt+Shift+P`** — 배지에 `ON` 이 뜬다
3. **그냥 쓴다** — 도구는 방해하지 않는다. 앱이 평소대로 돈다
4. 고칠 곳에서 **Alt+클릭** → 화면 안 입력칸에 무엇을 고쳤으면 하는지 적고 Enter (비워도 된다)
5. AI 는 `wait_for_pick` 으로 기다리다가 그대로 받는다
켜 두면 그때부터 **한 창을 같이 본다.** 「이 화면 좀 봐 줘」 하면 AI 가 읽고, 「여기 눌러 봐」
하면 누른다. AI 도 「이거 말씀이신가요」를 말 대신 `page_point` 로 짚어 보인다.
`Alt+P` 로 «늘 고르기»(여러 개를 잇달아 짚을 때), `Esc` 로 끄기, 버튼 다시 누르면 꺼짐.
## 도구
사람 → AI:
| | |
|---|---|
| `wait_for_pick` | 사람이 짚을 때까지 기다렸다 받는다. «어디 고칠까요» 대신 부른다 |
| `latest_pick` | 가장 최근에 짚은 것 |
| `list_picks` | 최근 것들 |
| `clear_picks` | 비우기 |
| `how_to_start` | 사람에게 건넬 안내(북마클릿) |
AI → 사람이 보는 그 창:
| | |
|---|---|
| `page_look` | 지금 화면 — 주소·글·누를 수 있는 것들 |
| `page_point` | 「이거 말하는 거예요」 형광펜으로 짚어 보인다 |
| `page_measure` | 크기·자리·글자·여백, **잘렸으면 몇 px 모자란지** |
| `page_click` · `page_fill` · `page_key` | 누르기 · 적기 · 키 |
| `page_goto` | 그 탭을 다른 주소로 |
| `page_run` | 나머지 전부 — 스크롤·폭 바꾸기·여러 단계 |
| `page_list` | 지금 켜 둔 탭들 |
탭이 둘 이상이면 조작 도구는 **하지 않고 되묻는다.** `page` 에 주소나 제목의 일부를 준다.
같은 주소가 둘로 보이면 짚기가 두 벌 뜬 것이라 되묻지 않고 나중 벌이 한다 — 같은 화면이니
고를 것이 없다.
`page_run` 은 사이트가 `unsafe-eval` 을 막아 두면(X 가 그렇다) 확장에게 넘겨 돌린다.
그마저 안 되는 브라우저에서는 **`page_measure` 를 쓴다** — 재는 일에는 코드 실행이 필요 없다.
## 같이 보기 — 알고 쓸 것
`page_*` 는 **사람이 로그인해 둔 진짜 창**을 움직인다. 눌리면 진짜로 저장되고 발송되고
지워진다. 되돌릴 수 없다. 편한 만큼 그게 값이다.
울타리는 셋이다.
- **켠 탭에서만 돈다.** 켜기 전에는 어느 페이지에도 들어가지 않는다. `Esc` 나 도구 버튼으로
끄면 그 자리에서 끊긴다. 배지의 `ON` 이 지금 이어져 있다는 표시다.
- **`127.0.0.1` 에만 연다.** 밖에서 이 창을 움직일 길은 없다.
- **애매하면 안 누른다.** 「저장」이 셋인 화면에서 첫 번째를 몰래 고르지 않고, 목록을 돌려주고
`nth` 로 정하게 한다. 되묻는 값은 왕복 한 번이지만 틀리는 값은 되돌릴 수 없다.
`host_permissions` 가 넓은 것은 **켠 탭이 페이지를 옮겨도 관을 잇기 위해서**다. `activeTab`
은 화면을 옮기면 사라져서, 켜 두고 링크 한 번만 눌러도 끊긴다. 넓은 만큼 **켠 탭 목록이
유일한 울타리**이므로, 안 쓸 때는 꺼 두는 편이 낫다.
## 고칠 때
```
node test.mjs
```
서버·확장·페이지 셋에 걸쳐 있어서, 한 곳만 고치고 올리면 나머지는 옛것으로 남는다. 그때
증상은 «고쳤는데 그대로» 라 무엇이 옛것인지 알아내는 데만 한참 걸린다 — 실제로 그렇게
새로고침을 다섯 번 되돌렸다. 브라우저 없이 20초 안에 도니 올리기 전에 한 번 돌린다.
**판 번호를 세 곳에서 맞춘다** — `server.js`·`extension/picker.js` 의 `AIWH_VERSION`,
`extension/manifest.json` 의 `version`. 시험이 검사하고, 어긋나면 서버가 도구 답 앞에
«무엇이 옛것인지» 를 붙인다.
브라우저에서만 알 수 있는 것(진짜 클릭 · 사이트의 CSP · 확장 다리)은 시험에서 안 잡힌다.
그건 확장을 새로고침하고 한 번 짚어 보는 수밖에 없다.
## 규칙 — 클래스에서 파일 짚기
`presets/*.json` 에 프로젝트마다 「클래스 접두어 → 파일 자리」를 적어 둔다.
주소로 알아서 고른다(`match`).
```json
{
"name": "내 프로젝트",
"match": ["localhost", "example.com"],
"rules": [{ "prefix": "ledg-", "home": "src/client/ledger/" }]
}
```
**규칙이 없어도 된다.** 요소·선택자·감싼 것·잘림·창 폭은 그대로 나온다 — 규칙은 덤이다.
이 방식이 React 의 소스 정보(`_debugSource`)보다 오래간다. 그건 React 19 에서 없어졌고,
프로덕션 빌드에서는 컴포넌트 이름도 압축되어 사라진다. **CSS 클래스는 빌드를 지나도 남는다.**
## 안 만든 것
- **화면 캡처.** 글자·크기·잘린 양이 있으면 대개 그림이 필요 없고, 그림은 대화를 무겁게 한다.
- **앱에 심는 방식.** 프로젝트마다 다시 심어야 하고 배포에 섞일 걱정이 따라온다.
- **Electron 껍데기.** 처음엔 그렇게 만들었는데, 그러면 사람이 복사해서 붙여넣는 왕복이 남는다.
MCP 면 AI 가 바로 받는다.
- **원격 접속.** `127.0.0.1` 에만 연다. 남의 브라우저에서 온 짚기를 받을 이유가 없다.
TDQS
Scored across 13 tools
The tools split cleanly into pick management, page interaction, and onboarding. The pick tools each have a distinct behavior — waiting, returning latest, listing, clearing — and the page_* tools are distinct interaction actions, so agents should be able to choose the right one.
The page_* tools follow a clear, consistent verb pattern, but the pick-related tools are inconsistent: latest_pick, list_picks, clear_picks, and wait_for_pick mix singular/plural and different phrase structures. how_to_start is also a one-off, making overall naming mixed but still readable.
With 13 tools, the server is well-scoped for its purpose. The tools cover onboarding, pick lifecycle, tab awareness, page reading, interaction, and a JavaScript escape hatch without feeling bloated or redundant.
The tool set covers the full assisted-browsing workflow: setup, picking, waiting, page introspection, navigation, clicking, typing, pointing out, and arbitrary JavaScript for unusual cases. The page_run tool especially fills any long-tail gaps that the more specialized tools cannot handle.