daedalus
Daedalus
Chrome 확장 프로그램을 통한 원격 브라우저 제어. Chrome이 확장 프로그램을 실행하는 페이지에서 eval 브리지 + 영구 핫픽스 + 탭별 제어 + 스크린샷, CDP, 쿠키, 네트워크 캡처를 제공합니다.
설치
설치 전에 알아두어야 할 것: 일반 eval은 배너 없는 MAIN-world 주입을 사용합니다. 소스 없는 CSP 프로브가 동적 컴파일이 가능하다는 것을 확인하지 못하면 Daedalus는 CDP 폴백을 시도합니다. 폴백이 실행되는 동안 부착에 성공하면 Chrome의 "Daedalus started debugging this browser" 배너가 표시됩니다. CDP 세션을 유지하거나 네트워크 캡처를 계속 진행하면 해당 부착 상태가 더 오래 유지될 수 있습니다. 이 배너는 디버거 부착을 알려줄 뿐 값 무결성(value-integrity)을 보장하지 않습니다.
Chrome에서 압축 해제된 확장 프로그램(extension/)을 로드합니다:
chrome://extensions를 방문합니다.개발자 모드를 사용합니다.
압축 해제된 확장 프로그램을 로드를 클릭하고
extension/을 선택합니다.
최초 설치 시 고유 토큰이 자동으로 생성된 뒤 chrome.storage.local에 저장됩니다. 확장 프로그램 옵션 페이지(퍼즐 아이콘 → Daedalus → Options)에서 확인하거나 변경하세요.
다중 탭 병렬 스크래핑을 위해 Chrome의 백그라운드 탭 스로틀링을 사용합니다:
chrome --disable-background-timer-throttling --disable-backgrounding-occluded-windows --disable-renderer-backgrounding동작 방식
토큰: 설치 시
crypto.randomUUID()를 사용해 한 번 생성되며,chrome.storage.local에 저장됩니다.탭 ID: Chrome 네이티브
tabsAPI를 사용료 즉 각 탭은 Chrome의tabId로 식별되고, 생성/업데이트 시 30초간의chrome.alarms하트비트로 등록됩니다.단일 SSE 스트림:
background.js는 하나의 지속적인fetchSSE 연결(tab=extension)을 열고 들어오는 명령을 해당 탭으로 전달합니다.페이지 브리지:
content.js(ISOLATED world)는window.GM메시지를 background와page.js(MAIN world) 사이에서 중계합니다. Eval은 먼저chrome.scriptingMAIN-world 주입을 사용합니다. 소스 없는 동적 컴파일 프로브는 CSP로 차단된 페이지를 CDP로 라우팅하며, CDP 부작에 실패하면 페이지 릴레이로 이어집니다. 모든 채널은 페이지의MAIN-world 의미론으로 실행됩니다.핫픽스: 확장 프로그램 전체의
chrome.storage.local키daedalus-hotfixes에 저장되며(토큰별 아님), 조건에 맞는 각 최상위 페이지가 로드될 때마다 재시됩니다. 비영구 수정에는 버전 게이트가 적용됩니다. 토큰을 순환해도 이 저장소는 격리되거나 지워지지 않습니다.
명령 전송
Daedalus는 확장 명령 표면을 MCP 서버로 <your-bridge>/mcp(streamable-HTTP 전송)에서 노출합니다. 요청을 보내기 전에 Bearer 값이 CLI의 기존 설정 경로를 통해 확인된 브리지 토큰과 정확히 일치해야 합니다. TOKEN은 선택적인 _settings 제공자를 포함해 DAEDALUS_TOKEN을 덮어씁니다. 구성된 토큰이 없으면 MCP 표면은 401로 fail-closed합니다. Claude Code(또한 모든 MCP 클라이언트)에 다음으로 추가합니다:
{
"mcpServers": {
"daedalus": {
"url": "https://daedalus.example.com/mcp",
"headers": { "Authorization": "Bearer <your-bridge-token>" }
}
}
}브리지 토근은 확장 프로그램이 설치 시 생성하는 토큰이며 확장 프로그램 옵션 페이지(퍼즐 아이콘 → Daedalus → Options)에서 확인할 수 있습니다.
이 예시는 MCP 서버를 공개 호스트명으로 노출하지만, 전송 프로토콜의 기본 허용 호스트는 루프백(127.0.0.1:*,localhost:*)만입니다. DAEDALUS_MCP_ALLOWED_HOSTS에 공개 호스트명을 지정하지 않으면 프록시된 요청이 거부됩니다. 세 가지 MCP 설정은 아래 "Server" 섹션에서 확인할 수 있습니다.
7개 그룹(tabs, eval/debug, media, cookies, CSS/blocking, hotfixes, network/CDP)에 나누어진 40개 도구가 제공됩니다. 전체 목록은 CLAUDE.md의 <mcp> 섹션에 보거나 MCP 엔드포인트의 tools/list을 호출하면 됩니다.
수동 검증 헬퍼(MCP 클라이언트 필요 없음):
TOKEN=<tok> python3 scripts/mcp_probe.py list
TOKEN=<tok> python3 scripts/mcp_probe.py call title '{"tab_id":"<tabId>"}'
TOKEN=<tok> python3 scripts/mcp_probe.py call screenshot '{"include_image":true}'CLI
이 저장소에는 lewheel 패키지(daedalus_cli/)로 셸 CLI가 포함되어 있으며 daedalus 명령으로 설치됩니다. DAEDALUS_URL과 DAEDALUS_TOKEN을 환경에서 읽고, TOKEN은 일회성 덮어쓰기, ID=<tabId>는 특정 탭을 대상으로 합니다(생략하면 브로드캐스트):
DAEDALUS_TOKEN=<tok> daedalus tabs
DAEDALUS_TOKEN=<tok> ID=<tabId> daedalus title
DAEDALUS_TOKEN=<tok> daedalus exec myid 'document.title'daedalus --help는 모든 하위 명령을 열거하며 각각 별도 --help를 지원합니다. exec로 보내는 code는 표현식 또는 함수 본문으로, 그 반환 값이 결과로 돌아옵니다 — 명세는 위 "명령 전송" 섹션을 참조하세요.
선택적 import 시드도 제공됩니다. sys.path에서 _settings 모듈을 import할 수 있다면, CLI는 DAEDALUS_URL 및 DAEDALUS_TOKEN을 처리할 때 내장 환경 폴백 대신 해당 모듈의 setting(name, default)과 required(name) 함수를 사용합니다. TOKEN 환경 변수는 일회성 토큰 덮어쓰기로서 남고 우선합니다. _settings가 없으면 DAEDALUS_URL은 기본값을 사용하며 DAEDALUS_TOKEN은 필수입니다. ID는 특정 탭을 대상으로 하는 환경 변수로 계속 유지됩니다.
또는 MCP나 CLI를 쓰지 않고 원시 명령 파일을 원자적으로 게시할 수 있습니다. 최종 .json 이름에 직접 리다이렉트하는 것은 스트림이 파일이 완료되기 전에 관찰될 수 있으므로 지원되지 않습니다. 동일한 디렉토리에 .tmp로 끝나는 형재 파일을 쓴 다음 해당 디렉토리 안에서 파일 이름을 변경합니다:
# Broadcast to all tabs
commands_dir="$DAEDALUS_DIR/commands"
final="$commands_dir/<token>.json"
tmp="$(mktemp "$commands_dir/.<token>.XXXXXX.tmp")"
printf '%s\n' '{"id":"test1","code":"document.title"}' > "$tmp" &&
mv "$tmp" "$final"
# Target a specific tab
final="$commands_dir/<token>_<tabId>.json"
tmp="$(mktemp "$commands_dir/.<token>_<tabId>.XXXXXX.tmp")"
printf '%s\n' '{"id":"test1","code":"document.title"}' > "$tmp" &&
mv "$tmp" "$final"읽는 과정은 .tmp 이름의 형제 파일을 무시합니다. 이전 작성자가 .json 최종 이름에 유효하지 않은 JSON을 남긴 경우, 읽는 축은 그 파일을 수정하지 않고 무시하고 재시도합니다(삭제하지 않음). 원자적 이름 변경이 끝난 후 SSE 스트림이 명령을 전달하고 소모합니다. 결과는 $DAEDALUS_DIR/results/<token>_<tabId>.json(각 탭별) 및 $DAEDALUS_DIR/results/<token>.json(마지막 기록 승자)에 저장됩니다. page-main 주입 및 페이지 릴레이 eval 완료에는 exec_ms 필드(페이지 컨텍스트 실행 시간)가 포함될 수 있습니다. 페이지는 어떤 채널에서든 이 필드를 위조하거나 생략할 수 있습니다. CDP 완료에는 exec_ms가 없습니다. 대기열로 전달된 결과에는 roundtrip_ms가 포함됨 — 명령 큐(PUT /command)에서 결과 도착(POST /result)까지 서버가 관측한 전체 왕복 시간(밀리초)입니다. 이 시간에는 큐 대기 + SSE 전송 + 클라이언트 릴레이 + 실행 + 복귀가 모두 포함되므로, 두 필드가 모두 존재할 때 roundtrip_ms − exec_ms는 대략적인 전송/큐 오버헤드를 나타냅니다. 두 계측 서로 다른 시계를 사용합니다. exec_ms은 page의 performance.now()를 사용하고, roundtrip_ms은 서버 wall-clock 밀리초를 사용하므로 차이는 정확한 뺄셈보다는 근사치입니다. _did가 없는 레거시 프레임에는 roundtrip_ms가 포함되지 않습니다.
비동기 지원
기본 page-main 채널은 표현식을 페이지 소유의 eval으로, 함수 본문은 페이지 소유의 Function으로 실행하며, async 래퍼는 최상위 await을 제공합니다. 소스를 제출하기 전 background는 상수 Function 프로브 하나만 주입합니다. 그 프로브가 true를 반환하면 소스 주입은 한 번만 시도되고 모든 결과는 최종입니다. 값, 예외, 주입 전송 오류가 모두 보고되며 CDP에서 소스를 다시 시도하지 않습니다. 페이지는 Function에 소유권을 가지므로 이 라우팅 힌트에 영향을 줄 수 있지만, 프로브에는 제출된 소스가 포함되지 않으므로 다른 채널을 선택해도 소스의 부작용이 중복되지 않습니다.
소스 없는 프로브가 true를 반환하지 않으면, 대개 페이지 CSP가 dynamic polling을 막기 때문에, Daedalus는 CDP를 시도합니다. 부착에 성공하면 Chrome의 debugger 배너가 표시됩니다. Runtime.evaluate는 최상 await을 위해 REPL 모드를 사용합니다. return을 포함한 소스는 래퍼 프로브가 표현식으로 파싱하는 경우가 아니라면 함수 본문으로 취급됩니다. 그 래퍼 프로브는 파서 휴리스틱몇이며 실행 경계가 아니므로 교묘하게 조작된 소스가 빠져서 실행될 수 있습니다. 마지막 CDP 평가가 dispatch된 후 모든 결과는 최종입니다. 제출된 소스가 실험되기 전의 attach 또는 shape 심패만 페이지 릴레이로 이동합니다. CDP 프로미스 확약은 10초로 제한되며, 세션을 유지하거나 capture가 디버거 부착 상태를 보존해도 결과 및 예외 객체 핸들은 해제됩니다.
통제하지 않는 페이지에서 실행된 JavaScript는 실행된 채널에 관계없이 그 페이지가 선택한 값을 반환합니다. world 필드는 실제로 어떤 채널이 소스를 실행했는지만 기록하며 CSP 및 디버거 동작을 위한 진단 메타데이터기, 신뢰 신호가 아닙니다. world 평가 값은 일반 주입의 page-main, 선언자 폴백의 cdp, 최종 릴레이의 page:<hostname>이며, 여기서 <hostname>는 콘텐츠 스크립트의 location.hostname입니다. background가 page: 접두어를 추가하므로 릴레이 호스트 이름은 cdp 또는 page-main과 충돌할 수 없습니다. 이 namespace의 특성은 만약 결과 값에 대한 의미를 설명하지 않습니다. CLI는 해당 필드를 channel=...로 표시하고 dashboard에도 그렇게 표시합니다. MCP의 exec, put, result, ping은 원래의 world 값을 그대로 보존합니다. 어떤 것도 신뢰 class를 할당하지 않습니다.
기본 주입과 CDP 폴백은 문서화된 sloppy-mode 케이스에 대해 classic-script 동작을 유지합니다: with는 컴파일되고, legacy 8진수 표현식은 허용되며, 선언되지 않은 할당은 전역을 생성합니다. CDP REPL mode는 반복적인 let 또는 const 선언도 허용합니다. CLI와 MCP 결과 대기 시간은 기본 15초입니다. 호출자 타임아웃은 페이지에서 이미 실행 중인 코드를 취소하지 않습니다. Blob 릴레이는 await을 포함하는 코드에 대해 10초, 그 외에는 3초를 대기한 다음 폴백 시간 초과를 보고합니다.
릴레이 연관성은 서술적이고 제한적입니다. 발신 탭이 확인되어야 하고, 수락된 메시지가 랜덤 릴레이 ID를 소모하며, 소스 없는 주입 프로브와 사전 요청 CDP 폴백이 끝나기 전에는 릴레이 ID가 등록되지 않습니다. 이 제어는 교차 탭 또는 중복 릴레이 완료를 막지만 값 무결성은 확립하지 않습니다.
최대 1,000개의 페이지 릴레이 항목까있는 상태가 될 수 있습니다. 크기가 가득 차면 새 폴백은 활성 항목을 삭제하지 않고 최종 용량 오류를 하나 받습니다. 등록된 각 항목은 300,000ms가 지난 후 만료됩니다. 동일 탐 복사나 조기 전달 실패로 항목이 제거되는 경우가 없으면(same-tab completion) 하나의 최종 타임아웃 오류를 받습니다.
대시보드
<your-bridge>/dashboard의 웹 기반 제어판으로, CLI 없이 확장과 전체 명령 세트를 제어할 수 있습니다 — 실시간 탭 목록, eval REPL, 스크린샷, 쿠키, 핫픽스, 차단 목록, 네트워크 캡처, CDP, CSS 주입, fetch 타이밍, 업로드 브라우저.
대확장 프로그램이 제어하는 탭에서 URL을 엽니다.
§12 Settings로 이동하여 확장 옵션 페이지(퍼즐 아이콘 → Daedalus → Options)에서 토큰을 붙여넣어 저장합니다.
대시보드가 실시간 이벤트를 구독하면 상단 막대의 SSE 상태 점이 시안색으로 바뀝니다.
server.py가 저장소의 dashboard/ 디렉토리에서 직접 제공하오며(바닐라 JS + ES 모듈, 빌드 없음), 기존 /stream 엔드포인트를 통해 실시간 업데이트가 이루어집니다. server.py는 /register가 기존 탭을 업데이트할 때와 /sync-tabs, /unregister, /result의 성공 경로에서 commands/<token>_dashboard/<ts>_<uuid>.json에 이벤트를 enqueue합니다. /unregister는 탭이 존재하지 않더라도 이벤트를 방출합니다. 대시보드는 이 이벤트를 kind:'event' 프레임으로 소비합니다.
주의: 확장 프로그램의 content + page 스크립트는 대시보드 탭을 포함한 일치하는 모든 페이지에 주입됩니다. 브로드캐스트 eval 명령(exec -b)은 대시보드 내부에서도 실행됩니다. 방해가 되는 코드를 실행하기 전에는 탭 단위로 지정하거나 대시보드를 먼저닫는 것이 좋습니다.
자동 패치
핫픽스
조건에 맞는 상위 페이지가 로드될 때마다 다시 적용되는 작은 패치를 유지합니다. MCP 도구인 store_hotfix, list_hotfixes, clear_hotfix, clear_hotfixes, set_permanent를 통해 사용합니다. 예시(프로브 스크립트를 이용한):
TOKEN=<tok> python3 scripts/mcp_probe.py call store_hotfix '{"fix_id":"my-fix","code":"console.log(\"patched\")"}'
TOKEN=<tok> python3 scripts/mcp_probe.py call store_hotfix '{"fix_id":"always-on","code":"console.log(\"baseline\")","permanent":true}'
TOKEN=<tok> python3 scripts/mcp_probe.py call set_permanent '{"fix_id":"my-fix","permanent":true}'
TOKEN=<tok> python3 scripts/mcp_probe.py call list_hotfixes
TOKEN=<tok> python3 scripts/mcp_probe.py call clear_hotfix '{"fix_id":"my-fix"}'
TOKEN=<tok> python3 scripts/mcp_probe.py call clear_hotfixes
TOKEN=<tok> python3 scripts/mcp_probe.py call clear_hotfixes '{"include_permanent":true}'핫픽스는 기본적으로 버전 게이트가 적용됩니다. 확장 버전 변경 후 유지되었던 비영구 핫픽스는 삭제하지 않고 건너뜁니다. 수정 사항을 저장하면 해당 레코드가 현재 버전에 맞게 업데이트되어 유지된 비영구 핫픽스가 다시 적용 가능해집니다. 수정 사항을 permanent (store_hotfix의 permanent: true 또는 set_permanent)로 지정하면 버전 간에도 재생 됩니다. clear_hotfixes는 기본적으로 비영구 수정사항만 제거하고 영구수정을 유지합니다. include_permanent: true를 보내면 전체 스토어가 제거됩니다.
확장 명령
The background service worker accepts 집계 commands (issued via MCP tools or by writing JSON with "type": "..." to $DAEDALUS_DIR/commands/):
명령 | 용도 |
| 보이는 탭을 PNG로 캡처 |
| 원시 Chrome DevTools Protocol 호출 실행 |
| CDP를 통한 전체 요청/응답 인터셉트 |
| 쿠키 저장소 접근 |
| 탭 제어 |
| 탭별 CSS 주입 |
| declarativeNetRequest 차단 |
| 핫픽스 관리 (영구 수정은 버전이 올라가도 유지됨) |
| 확장 프로그램 자체를 디스크에서 다시 로드 |
| fetch 릴레이용 진단 링 버퍼 |
GM 브리지
window.GM (page.js의 MAIN world)은 다음 Tampermonkey 스타일 하위 집합을 제공합니다:
메서드 | 설명 |
| 확장 전체 |
| 확장 전체 저장소에 예약되지 않은 문자열 키를 씁니다 |
| 확장 전체 저장소에서 예약되지 않은 문자열 키를 삭제합니다 |
| 예약되지 않은 저장소 키를 나열합니다 |
| 백그라운드 중계 HTTP 요청 (CSP 면제) |
| CSS 주입 |
| 클립보드에 쓰기 |
| 데스크톱 알림 |
| 새 탭 열기 |
| 다운로드 실행 |
| 스크립트 메타데이터 |
쿠키 접근은 페이지가 아니라 운영자 기능입니다. 위의 토큰 인증 방식인 cookies / set-cookie / remove-cookie / clear-cookies 명령을 통해 이루어지며 의도적으로 페이지 컨텍스트에 노출되지 않습니다 — page.js는 일치하는 각 최상위 페이지에서 실행되므로, 그렇지 않으면 자체 document.cookie로는 볼 수 없는 쿠키를 읽을 수 있습니다.
아키텍처
Browser (matching tab) Server (your bridge host)
┌────────────────────────────────────────────┐ ┌──────────────────────┐
│ MAIN world │ │ bridge (server.py) │
│ ├─ page-main: default injection channel │ │ /stream?token │
│ └─ page.js: GM + relay channel │ │ watches commands/ │
│ ▲ │ │ │
│ │ window.postMessage │ │ /result writes │
│ content.js (ISOLATED) │ │ results/ │
│ ▲ │ │ │
│ │ chrome.runtime │ │ │
│ background.js (service worker) │ │ │
│ ├─ CDP: CSP fallback channel │ │ │
│ ├─ single SSE stream ◄────────────────────┼───┤ │
│ └─ POST result, fetch ────────────────────┼──►│ │
└────────────────────────────────────────────┘ └──────────────────────┘단일 SSE 스트림: 백그라운드가 단일
fetchSSE 연결(tab=extension)을 열고chrome.tabs.sendMessage를 통해 명령을 대상 탭에 라우팅합니다. 30초 워치독이 오래된 스트림에 강제로 재연결합니다.탭별 라우팅: 명령은 특정 Chrome의
tabId로 주소를 지정하거나 모든 탭에 브로드캐스트합니다.CSP 동작:
GM.xmlhttpRequest는 HTTP 작업을 백그라운드 서비스 워커의fetch로 보냅니다. Eval은 일반적으로 배너가 없는 MAIN-world 주입을 사용합니다. 소스 없는 프로브가 동적 컴파일을 사용할 수 없다고 보고하면 CDP가 CSP 폴백을 제공하고, 연결이 수락되면 Chrome의 디버거 배너를 표시합니다. 연결 실패는 페이지/Blob 릴레이로 진행됩니다. 결과적으로 생성된world값은 해당 채널을 설명하며 무결성을 주장하지 않습니다.
서버
세 가지 필수 설정과 함께 브리지를 시작하세요 — DAEDALUS_DIR 또는 DAEDALUS_PORT가 없으면 시작 시 종료되고, 구성된 토큰이 없으면 모든 브리지 제어 경로가 안전하게 실패(fail closed)합니다:
DAEDALUS_DIR=<data-dir> DAEDALUS_PORT=<port> DAEDALUS_TOKEN=<bridge-token> python3 server.pyserver.py는 사용자가 쓰는 어떤 슈퍼바이저 아래에서도 실행됩니다 (여기서는 systemd 유닛). localhost를 넘어서 노출한다면 그 앞에 TLS 종료 리버스 프록시를 두십시오. 브리지 자체는 일반 HTTP를 사용합니다. 브리지 제어 및 저장소 경로는 그 토큰을 CLI 구성 경로를 통해 해석된 하나의 구성된 시크릿과 비교합니다. TOKEN은 일회성 오버라이드이고, 그 외에는 DAEDALUS_TOKEN이 필요합니다 (임베딩 _settings 모듈이 이를 제공할 수도 있음). 구성 누락과 불일치는 안전하게 실패합니다. 페이지를 대상으로 하는 POST /segment와 GET /segment-status 경로만 작업 범위(job-scoped)의 자격 증명을 대신 사용합니다.
프로세스 내부 MCP 프런트 엔드는 선택 설정 세 개를 받습니다. 하나의 리스너를 설명하기 때문에 함께 문서화합니다: DAEDALUS_MCP_PORT (기본값 8086)는 바인딩할 루프백 포트이고, 그 도구 핸들러는 DAEDALUS_PORT=0일 때를 포함해 브리지의 실제 바인딩된 루프백 URL을 사용합니다. DAEDALUS_LOCAL_URL은 다른 곳에서 실행되는 브리지를 앞세운 독립 MCP 배포를 위해 그 URL을 명시적으로 재정의합니다. 그리고 DAEDALUS_MCP_ALLOWED_HOSTS (기본값 127.0.0.1:*,localhost:*)는 DNS 리바인딩 보호가 허용하는 쉼표로 구분된 호스트 허용 목록이므로, 공개 호스트 이름으로 /mcp를 앞세우려면 그 호스트 이름을 여기에 지정해야 합니다.
엔드포인트: GET /stream, GET /tabs, GET /health, GET /dashboard[/<asset>], POST /register, POST /sync-tabs, POST /unregister, POST /poll, POST /result, PUT /command, GET /result, POST/GET/DELETE /upload, GET /screenshot, POST /segment-job, POST /segment + GET /segment-status. POST /segment-job는 구성된 브리지 토큰을 요구하고, POST /segment와 GET /segment-status만 작업 범위의 sig를 받습니다. 페이로드 및 엔드포인트에 대한 자세한 내용은 CLAUDE.md를 참조하세요.
POST /poll는 레거시 브로드캐스트 명령 파일이 있으면 해당 파일을 소비하고 삭제합니다.
PUT /command는 대상별 FIFO 디렉터리 큐에 명령을 넣습니다 (한 탭에 대한 연속 명령이 더 이상 덮어쓰지 않음). 각각 배달 id (_did)를 붙여 확장 프로그램이 재전송된 프레임을 중복 제거할 수 있게 하고, DAEDALUS_CMD_TTL초 (기본 90) 후에 클레임되지 않은 명령을 TTL로 제거합니다. 백그라운드 수집기가 이 TTL을 적용하며, SSE 소비자가 연결되지 않아도 빈 큐 디렉터리를 제거합니다. GET /health는 스트림/레지스트리/마지막 배달 상태를 보고하여 조용히 죽은 브리지를 감지합니다.
브리지는 값 하나를 선택하는 대신 반복되는 권한 전달자를 거부합니다: 쿼리 문자열이나 JSON 본문의 token, 세그먼트 기능 경로의 job/sig가 그 대상이며, 반복되는 값이 같거나 비어 있어도 거부합니다. MCP 전송도 역시 반복된 Authorization, Mcp-Session-Id, Host, Origin 헤더와 세그먼트 도구에 대한 반복된 job 인자를 거부합니다.
확장 프로그램이 결과를 게시하면, 서버는 명령의 _did를 deliveryId로 노출하고 새 resultGeneration을 할당합니다. CLI와 MCP 대기자는 먼저 공유 결과 슬롯을 엿보고 명령 id와 deliveryId를 모두 일치시킨 다음, GET /result?...&consume=1&expected=<resultGeneration>으로 해당 세대를 조건부 소비합니다. 그 요청 사이에 다른 결과가 슬롯을 대체하면, 조건부 소비는 새 결과를 제자리에 남겨두고 소비되지 않았다고 보고합니다. expected 없는 단독 consume=1은 현재 슬롯에 대한 파괴적인 호환 읽기로 남습니다.
GET /stream은 SSE 연결을 무기한 유지하고, 타이머를 따라 연결을 주기적으로 바꾸는 대신 DAEDALUS_STREAM_KEEPALIVE초 (기본 15)마다 주석을 보내 활성 상태를 증면합니다. DAEDALUS_STREAM_MAX_AGE (기본 3600)은 마지막 수단의 상한일 뿐입니다. 종료 경로는 tests/test_stream_lifecycle.py로 고정됩니다.
DAEDALUS_MAX_BODY_SIZE (기본 64 * 1024 * 1024바이트, 64 MiB)는 POST, PUT, DELETE 핸들러가 읽는 요청 본문 크기를 제한합니다. 선언된 본문이 한도를 넘으면 413가 응답니다. 더 큰 세그먼트나 다른 페이로드를 중계할 때는 이 값을 늘리세요.
파일시스템으로 구현되는 호출자 값은 하나의 경로 성분 정책을 사용합니다. .., C0/C1 제어 문자와 서로게이트 문자, Windows에서 유효하지 않은 경로 문자 및 기기 이름, 끝에 붙은 점 또는 공백, 그리고 240바이트를 초과하는 UTF-8 인코딩을 거부합니다. 브리지 토큰은 더 엄격하며 점과 밑줄도 거부합니다. 다른 UTF-8 작업 이름은 허용되므로 클라이언트는 이들을 쿼리 문자열에서 URL 인코딩해야 합니다.
POST /segment-job는 세 개 고정 할당량을 각 새 작업 레코드에 복사합니다: DAEDALUS_MAX_SEGMENT_INDEX (기본 99999), DAEDALUS_MAX_SEGMENTS_PER_JOB (기본 10000), 그리고 DAEDALUS_MAX_SEGMENT_JOB_SIZE (기본 4 * 1024 * 1024 * 1024바이트, 4 GiB). 다음 설정은 이후에 생성되는 작업에만 영향을 줍니다. 저장된 할당량을 가진 작업은 기록된 값을 계속 사용합니다. DAEDALUS_MAX_BODY_SIZE는 개별 세그먼트 요청 하나하나의 크기도 제한합니다.
보안
확장 프로그램을 설치하기 전에 이 내용을 읽으세요.
이것은 일치하는 모든 최상위 페이지에 그(<all_urls>, MAIN world)를 주입합니다**, 따라서 put으로 전송된 스크립트가 교차 출처 요청을 할 수 있습니다. 여기서 의도적으로 정할 부분은 그 결과입니다. 방문하는 일치되는 어떤 사이트든 이 shim에 접근할 수 있으므로, 그 사이트 스스로가 확장 프로그램의 권한을 통해 교차 출처 요청을 발행할 수 있습니다. 이는 와일드카드 @match를 쓰는 userscript 매니저와 똑같습니다. 이것이 브라우징 환경에서 용납되지 않는다면, extension/manifest.json의 matches을 실제로 다루는 호스트로 좁히고 브리지가 다른 페이지에서는 아무 일도 하지 않는다는 것을 받아들이세요.
브리지 토큰과 서버 URL은 페이지의 GM 저장 메서드를 통해 도달할 수 없습니다. 이들은 chrome.storage.local 안의 daedalus- 접두사 아래 저장되며, 릴레이는 페이지 위임을 위해 그 네임스페이스의 읽기/쓰기/일람을 거부합니다. 이 규칙이 없다면 방문한 모든 사이트가 GM.getValue('daedalus-token')으로 브리지 토큰을 읽거나, GM.setValue('daedalus-server', ...)으로 브리지를 조용히 바꿔치기할 수 있습니다. tests/test_repo_contract.py가 이 규칙을 고정합니다.
핫픽스 소스는 의도적으로 페이지에 전달되는 상태일 뿐이며, 기밀 확장 상태가 아닙니다. content.js는 daedalFx? 실제로 "content.js reads the daege..." Let's keep: content.js는 daedalicious 레코드를 읽고, 영구 핫픽스와 확장 버전에 맞는 비영구 핫픽스를 선택하여 그 선택된 전체 객체를 각 대상 페이지에 게시합니다. 그런 다음 MAIN-world page.js가 각 객체의 code를 평가하므로, 페이지는 페이지로 전달된 소스를 관찰할 수 있습니다.
MCP 소지자는 MCP 프로세스가 읽을 서버 호스트 파일을 지정할 수 없습니다. MCP의 put, CSS 주입/제거 및 핫픽스 저장 도구는 인라인 소스만 받습니다. 로컬 CLI는 파일 경로 편의는 유지하지만, CLI 프로세스가 그 운영자 지정 파일을 읽고 내용을 인라인으로 제출합니다. 따라서 브리지 토큰을 보유하면 저장된 핫픽스 소스 읽기를 포함해 문서화된 브라우저 및 확장 제어 권한이 부여하지만, 이 도구를 통해 임의의 호스트 파일시스템을 읽을 수 있게 되는 것은 아닙니다.
브리지 제어 및 저장 라우트는 설정된 브리지 토큰을 요구하며, /segment와 /segment-status만 유일한 권한(capability) 예외입니다. 이 서버는 요청 토큰을 TOKEN 또는 DAEDALUS_TOKEN에서 해석한 비밀값과 비교하고, 비밀값이 설정되어 있지 않으면 요청을 거부합니다. POST /segment-job은 작업 범위 권한(job-scoped capability)을 발급하기 때문에 해당 브리지 토큰이 필요합니다. 신뢰할 수 없는 페이지의 JavaScript는 그 권한으로 해당 작업의 게시와 조회만 수행할 수 있습니다. 브리지 토큰을 보유한 사람은 누구든 여러분의 브라우저를 조작할 수 있습니다. TLS를 종료하는 리버스 프록시 없이 브리지 포트를 루프백 밖으로 노출하지 말고, 토큰을 자격 증명으로 취급하십시오.
평가 결과에는 값 무결성(value-integrity) 보장이 없습니다. 여러분이 제어하지 않는 페이지에서 평가된 JavaScript는, 어떤 채널로 실행되었든 그 페이지가 선택할 수 있는 값을 반환합니다. world 필드는 제출된 소스가 어떻게 실행되었는지를 말해줄 뿐, 해당 값을 신뢰할지는 결정하지 않습니다. page-main은 일반적인 MAIN-world 주입이고, cdp는 검사자(Inspector) CSP 폴백이며, page:<hostname>은 릴레이입니다. 필수 page: 접두사는 페이지 컨텍스트 외부에서 추가되므로 어떤 호스트명도 cdp 또는 page-main을 만들어낼 수 없습니다. 그 충돌 방지는 단지 기술적 특성일 뿐입니다. page-main에서 페이지 소유의 eval 및 Function 바인딩은 제출된 소스를 읽을 수 있으며 반환값에도 영향을 줄 수 있습니다.
CDP 컴파일은 페이지의 eval 및 Function 바인딩을 해석하지 않으며, 구현은 직렬화하기 전에 직접 핸들을 참조로 가져옵니다. 그런 전송 동작은 신뢰 경계를 바꾸지 않습니다. 제출된 소스는 여전히 페이지가 제어하는 상태를 읽을 수 있고, CDP가 받기 전에 페이지의 프로미스 메커니즘을 통해 원시(primitive)를 포함한 어떤 값이든 전달할 수 있습니다. 제출된 소스가 그런 페이지 제어 경로를 사용할 때마다 페이지는 cdp로 반환되는 값도 선택할 수 있습니다.
릴레이는 해당 무작위 id와 연결된 저장된 호출만 수용하며, 송신자의 탭이 일치해야 하고, 그 항목은 한 번만 소비합니다. 이 다운그레이드는 브리지 토큰, 서버 URL, 전달 id 또는 결과 라우트를 노출하지 않으며, 추가 확장 기능이나 브라우저 권한을 부여하지 않고, 다른 탭의 호출에 영향을 미칠 수 없습니다. 이러한 속성들은 라우팅과 브라우저 권한을 보호할 뿐이며, 릴레이 표시나 반환된 JavaScript 값을 신뢰 신호로 만들지 않습니다.
배포
브리지는 루프백에서 평문 HTTP로 통신합니다. 브리지 제어 및 저장 라우트는 설정된 토큰을 요구하며, 토큰이 없으면 거부(fail closed)합니다. /segment와 /segment-status만 작업 범위의 권한을 사용합니다. 브리지가 의도적으로 처리하지 않는 두 가지 사항은, 그 책임이 브리지 앞에 있는 무언가에 속하기 때문입니다.
TLS 및 CORS.
server.py는 CORS 헤더를 보내지 않습니다. 브리지가 크로스 오리진 환경에 있을 때, 예시 HLS 릴레이의 페이지 쪽fetch호출은GET /segment-status와POST /segment둘 다에 접근해야 합니다. 프록시는 두 라우트 모두에서 페이지의 출처를 허용하고 POST의 preflight가 요구하는 메서드/헤더를 처리해야 합니다. 상태GET요청이 차단되거나, 사용할 수 없거나, 2xx가 아니거나, 유효하지 않으면 예시는 이 작업을 새것으로 보고 세그먼트를 모두 다시 POST합니다. 그런 쓰기는 여전히 같은 인덱스별 파일을 덮어쓰지만, 재개/건너뛰기 절약은 손실됩니다. 배포가 그런 CORS 정책을 제공할 수 없다면 대신 두 요청을 확장 기능의 GM 브리지를 통해 라우팅하세요.저장된 업로드 서빙. 대시보드는
/uploads/<path>에 다운로드 링크를 달아 주지만 브리지에는 그런 라우트가 없습니다. 실행 콘텐츠가 대시보드의 토큰이 보관된 저장소와 같은 출처를 공유하지 않도록, 호출자가 제공한 업로드 이름과 바이트를 대시보드 출처에서 직접 복용하지 마세요. 해당 링크를 사용할 수 없게 두거나,/uploads/가 별도의 다운로드 전용 출처로 리다이렉트하여Content-Disposition: attachment를 강제하고,application/octet-stream과X-Content-Type-Options: nosniff를 추가 헤더로 보내십시오.GET /upload(목록)와GET /screenshot은 브리지 자체가 제공하므로 프록시 도움이 필요하지 않습니다.
설정된 토큰으로 직접 실행하면 그 두 가지가 그냥 없고 — 다른 모든 것은 동작합니다.
파일
파일 | 설명 |
| MV3 매니페스트 |
| 서비스 워커 — SSE, 명령 디스패치, fetch 릴레이, 스크린샷, CDP, 쿠키, 다운로드 |
| 페이지와 백그라운드 사이의 메시지 릴레이 |
| MAIN-world 브리지 — |
| 토큰/서버 설정 UI |
| 디버그 서버 (또한 127.0.0.1:8086에서 MCP 데몬 스레드를 호스팅) |
| MCP 서버 — 확장 명령 인터페이스를 HTTP로 |
| 수동 검증을 위한 최소 MCP 클라이언트 도우미 |
| 모든 버전 위치에 걸친 버전 수정 검사/올림 |
| pre-commit + pre-push 버전-일관성 게이트 |
|
|
|
|
|
|
| 테스트 스위트; |
예제
examples/는 put으로 페이지 안에서 실행하기 위한 스크립트를 담고 있습니다. 여섯 개 중 다섯는 특정 사이트가 아닌 브리지의 한 부분을 보여줍니다. Discord 예제는 의도적으로 사이트 특화인데, 가상화된 목록을 거슬러 올라가는 것은 실제 가상화된 목록이 없으면 보여줄 기법이 아니기 때문입니다.
예제 | 보여 주는 것 |
|
|
|
|
| 다시 요청을 멈추지 않는 플레이어 인스턴스를 찾아 제거하기 |
| React가 제어하는 입력을 채워 React 자체 state가 실제 전환 |
| 가상화된 메시지 목록을 거슬러 올라가며 긁어오기 |
| 가장 작게 가능한 |
이들은 전송 전에 __PLACEHOLDER__ 치환으로 설정 토큰을 받습니다. put은 함수 호출 대신 스크립트를 보내기 때문에 인자를 전달할 방법이 없습니다.
각 스크립트는 단독 스크립트가 아니라 비동기 함수의 본문(BODY)입니다. 브리지가 보내는 스크립트를 래핑하기 때문입니다. 그래서 대부분이 최상위 return으로 끝나고, 그중 하나인 scrape-discord-messages.js는 최상위 await도 사용합니다. 둘 다 실제 실행에서 유효합니다. node --check는 각 파일을 CommonJS wrapper로 파싱하고, 이 래퍼는 최상위 return을 허용하지만 최상위 await는 거부합니다 — 그래서 그 파일 하나만 문법 검사에 실패합니다.
개발
버전 문자열은 확장, 대시보드, CLI 패키지의 여러 위치에 있습니다. python3 scripts/check_versions.py가 그 장소를 모두 열거합니다 — 이 문장이 그 다음에 추가될 때마다 낡을 숫자를 되새기지 않는 이유입니다. 버전을 함께 올려 놓되, 절대 손으로 올리지 마세요:
python3 scripts/check_versions.py --set 0.18.0 # rewrite every site
python3 scripts/check_versions.py # verify the working tree.githooks/pre-commit은 인덱스를 검사하고 .githooks/pre-push는 푸시된 각 커밋을 검사하므로, 절반 버전(半)이 적용될 수 없습니다. 모든 클론은 한 번 옵트인이 필요합니다. 그렇게 하지 않으면 git은 추적된 디렉토리에서 훅을 실행하지 않습니다:
git config core.hooksPath .githooks브라우저가 편집하는 checkout이 아니라 다른 checkout을 사용하고 있다면 그쪽에도 훅을 설치하세요. 확장프로그램을 다시 로드하면 파일은 여러분의 것이 아니라 브라우저가 가진 copy 쪽을 다시 읽는다는 점을 잊지 마세요.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Hosted real Google Chrome MCP with per-user persistent state. Navigate, click, type, screenshot.
Browser MCP for logged-in tasks. Uses your Chrome — credentials stay local. Zero-token replay.
Access Kernel's cloud-based browsers and app actions via MCP (remote HTTP + OAuth).
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/Nitjsefnie-Harness-Commons/daedalus'
If you have feedback or need assistance with the MCP directory API, please join our Discord server