tactual-mcp
OfficialTactual
스크린 리더 탐색 비용 분석기. 보조 공학 사용자가 웹의 대화형 대상을 발견하고, 도달하고, 조작하는 것이 얼마나 어려운지 측정합니다.
기능
기존 접근성 도구는 **준수 여부(conformance)**를 확인합니다. ARIA가 올바른가요? 대비 비율이 충분한가요?
Tactual은 **탐색 비용(navigation cost)**을 측정합니다. 스크린 리더 사용자가 결제 버튼에 도달하려면 몇 번의 동작이 필요한가요? 버튼을 지나치면 어떻게 되나요? 버튼의 존재를 발견할 수는 있나요?
이 도구는 Playwright 접근성 스냅샷을 캡처하고, 탐색 그래프를 구축하며, 보조 공학 프로필에 따라 각 대상을 점수화하는 방식으로 작동합니다.
설치
# For CLI usage
npm install tactual playwright
# For MCP server usage (AI tools)
npm install tactual playwright @modelcontextprotocol/sdkPlaywright와 @modelcontextprotocol/sdk는 선택적 피어 의존성입니다. Playwright는 CLI 및 페이지 분석에 필요합니다. MCP SDK는 tactual-mcp 서버를 실행하는 데 필요합니다. 미리 캡처된 상태로 라이브러리 API만 사용하는 경우에는 둘 다 필요하지 않습니다.
빠른 시작
CLI
# Analyze a URL (default profile: generic-mobile-web-sr-v0)
tactual analyze-url https://example.com
# Analyze with a specific AT profile
tactual analyze-url https://example.com --profile voiceover-ios-v0
# Explore hidden UI (menus, tabs, dialogs, disclosures)
tactual analyze-url https://example.com --explore
# Output as JSON, Markdown, or SARIF
tactual analyze-url https://example.com --format json --output report.json
tactual analyze-url https://example.com --format sarif --output report.sarif
# Compare two analysis runs
tactual diff baseline.json candidate.json
# List available AT profiles
tactual profiles
# Run benchmark suite
tactual benchmark
# Initialize a tactual.json config file
tactual init라이브러리 API
import { analyze, getProfile } from "tactual";
import { captureState } from "tactual/playwright";
import { chromium } from "playwright";
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto("https://example.com");
const state = await captureState(page);
await browser.close();
const profile = getProfile("generic-mobile-web-sr-v0");
const result = analyze([state], profile);
for (const finding of result.findings) {
console.log(finding.targetId, finding.scores.overall, finding.severity);
}MCP 서버
Tactual은 AI 에이전트가 사용할 수 있는 MCP 서버를 포함합니다:
# Start the MCP server (stdio transport — default)
tactual-mcp
# Start with HTTP transport (for hosted platforms, remote clients)
tactual-mcp --http # listens on http://127.0.0.1:8787/mcp
tactual-mcp --http --port=3000 # custom port (or set PORT env var)
tactual-mcp --http --host=0.0.0.0 # bind to all interfaces (default: 127.0.0.1)사용 가능한 MCP 도구:
도구 | 설명 |
| 웹 페이지의 SR 탐색 비용을 분석합니다. 기본 형식은 |
| 특정 대상까지의 단계별 탐색 경로를 추적합니다. 각 동작, 비용, 모델링된 SR 안내를 보여줍니다. 대상 ID 또는 glob 패턴(예: |
| 사용 가능한 AT 프로필을 나열합니다 |
| 두 분석 결과를 비교합니다. 해결/추가된 페널티, 심각도 변경, 대상별 상태를 보여줍니다. |
| 영향도별로 순위가 매겨진 수정 제안입니다. SARIF 출력의 경우 중복됩니다(수정 사항이 인라인으로 포함됨). |
| 웹 앱으로 인증하고 세션 상태를 저장합니다. 인증된 콘텐츠를 분석하기 위해 다른 도구에 출력 파일 경로를 |
| 사이트 수준 집계와 함께 여러 페이지를 한 번에 분석합니다. 페이지당 약 200바이트를 반환합니다. 개별 페이지를 분석하기 전 사이트 분류용으로 사용하세요. |
AI 도구별 설정
먼저 프로젝트에 필요한 패키지를 설치하세요:
npm install tactual playwright @modelcontextprotocol/sdkClaude Code — 프로젝트 루트의 .mcp.json에 추가:
{
"mcpServers": {
"tactual": {
"type": "stdio",
"command": "npx",
"args": ["tactual-mcp"]
}
}
}GitHub Copilot — .copilot/mcp.json 또는 ~/.copilot/mcp-config.json에 추가:
{
"mcpServers": {
"tactual": {
"type": "stdio",
"command": "npx",
"args": ["tactual-mcp"]
}
}
}Cursor / Windsurf / Cline — 에디터의 MCP 설정에 동일한 형식으로 추가:
{
"mcpServers": {
"tactual": {
"command": "npx",
"args": ["tactual-mcp"]
}
}
}직접 설치 (전역 설치) — npx를 사용하지 않으려는 경우:
npm install -g tactual playwright
tactual-mcp # starts the MCP server on stdioGitHub Actions
GitHub Actions Marketplace의 복합 액션을 사용하세요:
jobs:
a11y:
runs-on: ubuntu-latest
steps:
- name: Analyze accessibility
uses: tactual-dev/tactual@v0.2.1
with:
url: https://your-app.com
explore: "true"
fail-below: "70"이 액션은 Tactual과 Playwright를 설치하고, 분석을 실행하며, SARIF를 GitHub 코드 스캔에 업로드하고, 평균 점수가 임계값 미만이면 빌드를 실패 처리합니다. 다운스트림 단계를 위해 average-score와 result-file을 출력합니다.
또는 더 많은 제어를 위해 CLI를 직접 사용하세요:
- name: Install Tactual
run: npm install tactual playwright
- name: Install browsers
run: npx playwright install chromium --with-deps
- name: Run accessibility analysis
run: npx tactual analyze-url https://your-app.com --format sarif --output results.sarif --threshold 70
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarif구성
CLI 플래그
Options:
-p, --profile <id> AT profile (default: generic-mobile-web-sr-v0)
-f, --format <format> json | markdown | console | sarif (default: console)
-o, --output <path> Write to file instead of stdout
-d, --device <name> Playwright device emulation
-e, --explore Explore hidden branches
--explore-depth <n> Max exploration depth (default: 3)
--explore-budget <n> Max exploration actions (default: 50)
--explore-max-targets <n> Max accumulated targets before stopping (default: 2000)
--exclude <patterns...> Exclude targets by name/role glob
--exclude-selector <css...> Exclude elements by CSS selector
--focus <landmarks...> Only analyze within these landmarks
--suppress <codes...> Suppress diagnostic codes
--top <n> Show only worst N findings
--min-severity <level> Minimum severity to report
--threshold <n> Exit non-zero if avg score < N
--config <path> Path to tactual.json
--no-headless Headed browser (for bot-blocked sites)
--timeout <ms> Page load timeout (default: 30000)
--probe Run keyboard probes (focus, activation, Escape, Tab)
--wait-for-selector <css> Wait for selector before capturing (for SPAs)
--wait-time <ms> Additional wait after page load
--storage-state <path> Playwright storageState JSON for authenticated pages
--summary-only Return only summary stats, no individual findings
-q, --quiet Suppress info diagnosticstactual.json
tactual init으로 생성하거나 수동으로 생성하세요:
{
"profile": "voiceover-ios-v0",
"exclude": ["easter*", "admin*", "debug*"],
"excludeSelectors": ["#easter-egg", ".admin-only", ".third-party-widget"],
"focus": ["main"],
"suppress": ["possible-cookie-wall"],
"threshold": 70,
"priority": {
"checkout*": "critical",
"footer*": "low",
"analytics*": "ignore"
}
}구성은 작업 디렉토리(tactual.json 또는 .tactualrc.json)에서 자동으로 감지됩니다. CLI 플래그는 구성 설정과 병합되며 이를 재정의합니다.
AT 프로필
프로필 | 플랫폼 | 설명 |
| 모바일 | 정규화된 모바일 SR 기본 요소 (기본값) |
| 모바일 | iOS Safari의 VoiceOver — 로터 기반 탐색 |
| 모바일 | Android Chrome의 TalkBack — 읽기 제어 |
| 데스크톱 | Windows의 NVDA — 브라우즈 모드 단축키 |
| 데스크톱 | Windows의 JAWS — 자동 양식 모드가 있는 가상 커서 |
프로필은 각 탐색 동작의 비용, 점수 차원 가중치, costSensitivity(도달 가능성 감쇠 곡선 조정), 상황별 수정자를 정의합니다. 구현 세부 정보는 src/profiles/를 참조하세요.
점수 산정
각 대상은 5차원 점수 벡터를 받습니다:
차원 | 측정 항목 |
Discoverability (발견 가능성) | 사용자가 대상의 존재를 알 수 있는가? |
Reachability (도달 가능성) | 거기에 도달하기 위한 탐색 비용은 얼마인가? |
Operability (조작 가능성) | 컨트롤이 예측 가능하게 작동하는가? |
Recovery (복구 가능성) | 지나쳤을 때 복구하기가 얼마나 어려운가? |
Interop Risk (상호 운용성 위험) | AT/브라우저 지원 편차가 발생할 가능성은? (페널티) |
차원 가중치는 프로필에 따라 다릅니다:
프로필 | D | R | O | Rec | costSensitivity |
generic-mobile-web-sr-v0 | 0.30 | 0.40 | 0.20 | 0.10 | 1.0 |
voiceover-ios-v0 | 0.30 | 0.35 | 0.20 | 0.15 | 1.1 |
talkback-android-v0 | 0.25 | 0.45 | 0.20 | 0.10 | 1.3 |
nvda-desktop-v0 | 0.35 | 0.25 | 0.30 | 0.10 | 0.7 |
jaws-desktop-v0 | 0.30 | 0.25 | 0.35 | 0.10 | 0.6 |
복합 점수: 가중 기하 평균: overall = exp(sum(w_i * ln(score_i)) / sum(w_i)) - interopRisk. 각 차원은 로그 계산 전 1로 하한이 설정되어 log(0)을 방지합니다. 이는 어느 한 차원에서 0점이 나오면 전체 점수가 급격히 낮아짐을 의미합니다. 도달할 수 없는 것은 조작할 수 없기 때문입니다.
심각도 대역:
점수 | 대역 | 의미 |
90-100 | 강함 | 낮은 우려 |
75-89 | 허용 가능 | 개선 가능 |
60-74 | 보통 | 분류 필요 |
40-59 | 높음 | 의미 있는 마찰 발생 가능 |
0-39 | 심각 | 차단 가능성 높음 |
진단
Tactual은 분석이 신뢰할 수 없을 때 이를 감지하고 보고합니다:
코드 | 수준 | 의미 |
| 오류 | Cloudflare/봇 챌린지 감지됨 |
| 오류 | 대상을 전혀 찾을 수 없음 |
| 경고 | http 페이지치고 대상이 지나치게 적음 |
| 경고 | 1-4개의 대상만 발견됨 |
| 경고 | 인증 제한 콘텐츠 (경로 리디렉션 감지) |
| 정보 | 쿠키 동의창이 콘텐츠를 가릴 수 있음 |
| 경고 | 다른 도메인으로 이동됨 |
| 경고 | 제목 요소를 찾을 수 없음 |
| 경고 | 랜드마크 영역을 찾을 수 없음 |
탐색
--explore 플래그는 제한된 분기 탐색을 활성화합니다:
메뉴, 탭, 공개(disclosure), 아코디언, 대화 상자를 엽니다.
숨겨진 UI에서 새로운 접근성 상태를 캡처합니다.
발견된 대상을
requiresBranchOpen으로 표시합니다.깊이, 동작 횟수, 대상 횟수 및 참신성 예산을 준수합니다.
안전 동작 정책이 파괴적인 상호 작용을 차단합니다.
탐색은 숨겨진 UI(예: 드롭다운 메뉴, 탭 인터페이스, 모달 대화 상자)가 많은 페이지에 유용합니다.
탐색 후보는 반복하기 전에 안정적인 키(역할 + 이름)로 정렬되므로, 동일한 페이지 콘텐츠는 실행 시마다 동일한 탐색 순서를 생성합니다.
탐색 예산
예산 | CLI 플래그 | 기본값 | 목적 |
깊이 |
| 3 | 최대 재귀 깊이 |
동작 |
| 50 | 모든 분기에 걸친 총 클릭 예산 |
대상 |
| 2000 | 누적 대상이 이 수를 초과하면 중지 |
시간 | (라이브러리 전용) | 120s | 전역 벽시계 시간 제한 |
SPA 프레임워크 감지
Tactual은 접근성 트리를 캡처하기 전에 SPA 콘텐츠가 렌더링되었는지 감지합니다. 감지된 프레임워크: React, Next.js, Vue, Nuxt, Angular, Svelte, SvelteKit. 일반 HTML5 콘텐츠 신호(랜드마크, 제목, 탐색, 링크)도 확인됩니다. 자동 감지가 지원되지 않는 SPA의 경우 --wait-for-selector(CLI) 또는 waitForSelector(MCP/API)를 사용하여 앱이 하이드레이션되었음을 나타내는 CSS 선택자를 지정하세요.
초기 프레임워크 감지 후, Tactual은 수렴 기반 폴링을 사용합니다. 대상 수가 안정될 때까지 접근성 트리를 반복적으로 스냅샷하며, 이는 프레임워크와 관계없이 작동합니다.
상호 운용성 위험
Tactual은 a11ysupport.io 및 ARIA-AT 프로젝트에서 파생된 ARIA 역할/속성 지원 데이터의 정적 스냅샷을 포함합니다. 알려진 교차 AT/브라우저 지원 격차가 있는 역할은 상호 운용성 위험 페널티를 받습니다.
역할 | 위험 | 참고 |
| 0 | 잘 지원됨 |
| 5 | 포커스 관리가 다양함 |
| 8 | 가장 상호 운용성 문제가 많은 패턴 |
| 10 | JAWS 외에는 지원이 미흡함 |
| 15 | 오용 시 위험함 |
출력 형식 권장 사항
형식 | 일반적인 크기 | 용도 |
| ~8KB | 터미널에서 인간 검토 |
| ~11KB | PR 및 이슈 댓글 |
| ~18KB | 프로그래밍 방식 소비 |
| ~4-40KB | GitHub 코드 스캔 / CI |
SARIF가 아닌 모든 형식은 요약된 출력을 내보냅니다: 통계, 그룹화된 이슈, 최악의 결과(최대 15개). SARIF는 25개 결과로 제한됩니다. 출력이 잘리면 상단에 메모가 표시됩니다.
MCP 사용 시 sarif가 기본 권장 형식입니다. 최소한의 상태 확인(약 835바이트: 통계, 심각도 수, 상위 3개 이슈)을 위해 summaryOnly: true를 사용하세요.
보정
Tactual은 실제 데이터 세트에 대해 점수 매개변수를 조정하기 위한 보정 프레임워크(src/calibration/, tactual/calibration으로 내보냄)를 포함합니다. 자세한 내용은 docs/CALIBRATION.md를 참조하세요.
개발
npm install # Install dependencies
npm run build # Build with tsup
npm run test # Run unit + integration tests
npm run test:benchmark # Run benchmark suite
npm run typecheck # TypeScript type checking
npm run lint # ESLint보안
브라우저 샌드박싱
Tactual은 항상 기본 Chromium 샌드박싱이 활성화된 상태로 Playwright를 실행합니다. 웹 보안을 비활성화하거나 브라우저의 보안 모델을 수정하지 않습니다. 모든 페이지 상호 작용은 표준 Chromium 프로세스 샌드박스 내에서 발생합니다.
안전 동작 정책
탐색이 활성화(--explore)되면 Tactual은 대화형 요소를 활성화하기 전에 세 가지 계층으로 분류합니다:
계층 | 동작 | 예시 |
안전 | 활성화 | 탭, 메뉴 항목, 공개, 아코디언, 동일 페이지 앵커 |
주의 | 주의하여 활성화 | 외부 링크, 모호한 버튼, 제출 버튼 |
안전하지 않음 | 건너뜀 | 삭제, 로그아웃, 구매, 배포, 구독 취소 |
이는 키워드 기반 휴리스틱입니다. 의미론적 기만(예: 실제로는 데이터를 삭제하는 "저장" 버튼)을 감지하거나 서버 측 동작을 검사할 수는 없습니다. 프로덕션 환경에서는 항상 신뢰할 수 있거나 샌드박스 처리된 환경에서 탐색을 실행하세요.
URL 유효성 검사
모든 URL은 탐색 전에 유효성이 검사됩니다. 기본적으로 사설/내부 IP 범위와 HTTP(S)가 아닌 스키마는 거부됩니다.
라이선스
Apache-2.0
Latest Blog Posts
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/tactual-dev/tactual'
If you have feedback or need assistance with the MCP directory API, please join our Discord server