MCP 서버

Model Context Protocol(MCP)은 AI 클라이언트가 외부 도구(tool)와 통신할 수 있게 해주는 개방형 표준입니다. dalfox mcp는 stdio 기반 MCP 서버를 실행하여 Claude Desktop, Claude Code, Cursor를 비롯한 모든 MCP 호환 클라이언트가 Dalfox 스캔을 직접 구동할 수 있게 합니다.

서버 시작하기

dalfox mcp

이 서버는 stdin/stdout을 통해 MCP로 통신합니다. 클라이언트에서 실행하세요. 터미널에서 직접 실행하지 않습니다.

Claude Desktop 설정

Claude Desktop MCP 설정(claude_desktop_config.json)에 Dalfox를 추가합니다:

{
  "mcpServers": {
    "dalfox": {
      "command": "dalfox",
      "args": ["mcp"]
    }
  }
}

Claude Desktop을 재시작합니다. Dalfox가 dalfox라는 이름의 도구 제공자(tool-provider)로 나타납니다.

Claude Code(및 기타 CLI)

claude mcp add dalfox -- dalfox mcp

사용 가능한 도구(tool)

여섯 개의 도구(tool)가 노출됩니다. 스캔은 기본적으로 비동기입니다. 스캔을 제출하고, 결과를 폴링한 뒤, 다음 작업으로 넘어갑니다. (preflight_dalfox는 그 자리에서 답하고, scan_with_dalfox는 wait: true를 주면 블로킹합니다 — 둘 다 요청하면 진행률을 스트리밍합니다.)

scan_with_dalfox

스캔을 제출합니다. 즉시 반환합니다.

{
  "target": "https://example.com/search?q=test",
  "method": "GET",
  "param": ["q"],
  "headers": ["Authorization: Bearer token"],
  "encoders": ["url", "html"],
  "timeout": 10,
  "scan_timeout": 0,
  "workers": 50,
  "rate_limit": 0,
  "insecure": true,
  "blind_callback_url": "https://callback.example",
  "deep_scan": false,
  "skip_ast_analysis": false,
  "analyze_external_js": false,
  "detect_outdated_libs": false
}

encoders는 구현된 페이로드 인코더의 어떤 조합이든 받습니다: url, html, htmlpad, 2url, 3url, 4url, base64, unicode, zwsp. 위 예시는 ["url", "html"]을 보여줍니다. 변형(mutation) 커버리지를 높이려면 더 추가하세요. 순서는 중요하지 않습니다. 스캐너는 인코더를 고정된 우선순위 순서(url → html → htmlpad → 2url → 3url → 4url → base64 → unicode → zwsp)로 적용하고 출력을 중복 제거합니다. 인코딩을 완전히 비활성화하려면 ["none"]을 사용하세요. --encoders / -e CLI 플래그와 대응됩니다. 알 수 없는 인코더 이름은 곧바로 거부됩니다 — 그렇지 않으면 어떤 인코더에도 매칭되지 않아 스캔의 페이로드 커버리지가 조용히 줄어듭니다.

method는 CLI가 허용하는 것과 동일한 메서드 집합으로 검증되며 자동으로 대문자로 변환됩니다("post" → "POST"). 지원하지 않는 메서드를 보내면 잘못된 메서드를 전송하는 스캔이 실행되는 대신 오류가 반환됩니다.

blind_callback_url은 비어 있거나(= 블라인드 XSS 사용 안 함) http:// / https://로 시작해야 합니다. 이 값을 설정하면 저장형 블라인드 XSS 주입이 켜집니다 — <script src=...> 페이로드가 모든 쿼리·바디·헤더·쿠키 파라미터에 기록되어 대상에 그대로 남습니다. 그래서 콜백을 받을 수 없는 값은 그 페이로드만 남기고 아무 소득도 없으므로 아예 거부합니다. remote_payloads / remote_wordlists도 등록된 프로바이더인지 검사합니다. 인식되지 않는 이름은 조용히 아무것도 받아오지 않은 채, 요청한 페이로드 커버리지가 빠진 스캔을 done으로 보고하게 만들기 때문입니다.

insecure는 TLS 인증서 검증을 제어합니다(기본값 true, 스캐너 친화적). 인증서 검증을 강제하고 자체 서명되었거나 만료된 인증서를 거부하려면 false로 설정하세요. --insecure CLI 플래그와 대응됩니다.

analyze_external_js는 옵트인 방식입니다(기본값 false). 프리플라이트 시점에 동일 출처의 <script src> 번들을 가져와 AST DOM-XSS 분석을 실행하려면 true로 설정하세요. 모든 싱크(sink) 로직이 외부 번들에 있고 페이지에 서버 측 반사가 없는 SPA에 유용합니다. 제한: 파일 16개, 파일당 512 KiB. (--include-url / --exclude-url 스코프 필터는 CLI 전용입니다. MCP 인자가 따로 없으며, 보내면 에러가 납니다.)

detect_outdated_libs는 옵트인 방식입니다(기본값 false). 오래되었거나 알려진 취약점이 있는 JS 라이브러리에 대해 정보성 [I] 탐지 결과도 내보내려면 true로 설정하세요(CWE-1104, 추가 요청 0건). 꺼두면 스캔은 XSS만 보고합니다.

rate_limit은 스캔의 아웃바운드 초당 요청 수를 제한합니다(0 = 무제한, 기본값). 모든 워커 태스크에 걸쳐 적용됩니다 — 불안정한 대상을 부드럽게 다루거나 WAF 임계값 아래로 유지하는 데 사용하세요.

scan_timeout은 스캔 전체에 허용되는 실제 경과 시간(초)입니다(기본값 0 = 무제한). 요청별 timeout과는 구별됩니다. 시간이 초과되면 스캔이 중단되고 부분 탐지 결과를 유지하며 scan_timeout을 언급하는 error_message와 함께 cancelled 상태로 정리됩니다. 길거나 deep_scan 실행에 한도를 두어 에이전트의 폴링 루프가 반드시 종료되도록 설정하세요.

위 블록은 발췌입니다. 이 도구가 받는 모든 필드와 각각의 기본값은 다음과 같습니다. 필수 필드는 target 하나뿐입니다.

{
  "target": "https://example.com/search?q=test",
  "param": [],
  "method": "GET",
  "data": null,
  "headers": [],
  "cookies": [],
  "user_agent": null,
  "encoders": ["url", "html"],
  "timeout": 10,
  "scan_timeout": 0,
  "delay": 0,
  "follow_redirects": false,
  "insecure": true,
  "proxy": null,
  "include_request": false,
  "include_response": false,
  "skip_mining": false,
  "skip_discovery": false,
  "deep_scan": false,
  "skip_ast_analysis": false,
  "analyze_external_js": false,
  "detect_outdated_libs": false,
  "blind_callback_url": null,
  "workers": 50,
  "rate_limit": 0,
  "waf_bypass": "auto",
  "skip_waf_probe": false,
  "force_waf": null,
  "waf_evasion": false,
  "waf_min_confidence": 0.3,
  "remote_payloads": [],
  "remote_wordlists": [],
  "max_payloads_per_param": 0,
  "wait": false,
  "wait_timeout_sec": 300
}

data는 POST/PUT의 요청 본문으로, form-urlencoded ("user=admin&pass=test") 또는 JSON 문자열을 받습니다. cookies는 "name=value" 형태의 항목을, headers는 "Name: Value" 형태의 전체 줄을 받으며, user_agent는 User-Agent 헤더를 덮어씁니다.

도구가 모르는 필드명은 조용히 버려지지 않고 거부됩니다. 호출은 JSON-RPC 오류(-32602, invalid params)로 돌아오며, 문제가 된 키와 허용되는 키 전체 목록을 담습니다. 그렇지 않으면 cookies를 한 글자만 틀려도 대상이 비인증 상태로 스캔되고, 아무것도 찾지 못한 채 status: "done"으로 끝나 실제 클린 결과와 구분할 수 없게 됩니다. 거부된 호출에는 scan_id가 없으므로 폴링할 대상도 없고, 실행된 스캔으로 오해할 여지도 없습니다.

잘못된 인자는 모두 같은 채널로 옵니다. target 누락, 문자열 자리에 들어온 숫자, 상한을 넘은 값 모두 마찬가지이므로 클라이언트는 error만 보면 됩니다. 도구 결과는 실제로 실행된 도구(tool)를 위해 남겨 둡니다. 상한은 REST API와 같습니다: timeout 1–299초, delay 0–9999ms, workers 1–500, scan_timeout 0–86400초, max_payloads_per_param 0–100000.

주의할 점이 하나 있습니다. 일부 호스트는 JSON-RPC 오류의 텍스트를 모델에 돌려주지 않고 사용자에게 일반적인 실패만 보여 주므로, 모델은 어떤 키가 틀렸는지 알 수 없게 됩니다.

흔한 실수가 오류가 되지 않도록 REST API 쪽 철자도 별칭으로 받습니다. target에 url, cookies에 cookie, headers에 header, workers에 worker, blind_callback_url에 blind입니다. cookie는 리스트 대신 Cookie: 헤더 문자열 하나("sid=abc; lang=en")도 받습니다. 도구 스키마가 알리는 이름은 위의 MCP 정식 명칭이며, 별칭은 REST 문서를 보고 작성한 인자도 의도한 그대로 스캔되게 하려고 존재합니다.

다른 표면에 있는 옵션 중 둘은 별칭을 두지 않고 의도적으로 빼두었으며, 요청하면 에러가 납니다. REST의 callback_url(모델이 원하는 호스트로 스캔 결과를 내보낼 수 있는 웹훅)과, CLI의 --cookie-from-raw에 해당하는 cookie_from_raw(서버 측 파일 읽기)입니다. 쿠키는 cookies로 직접 넘기세요.

별칭을 일반적인 REST 호환 모드로 오해하지 않도록, 세 가지 한계를 밝혀둡니다.

  • 인자는 평평합니다. REST는 옵션을 options 아래에 중첩하지만 이 도구는 최상위에서 받으며, options는 알 수 없는 필드입니다.
  • 이름만 매핑합니다. 별칭은 철자를 옮길 뿐이고, 타입이 틀린 값은 그대로 거부됩니다. 예외는 cookie 하나로, REST의 Cookie: 헤더 문자열 하나(그리고 쿠키 없음을 뜻하는 null)를 받습니다.
  • 공개 스키마에는 없습니다. 스키마는 MCP 정식 철자만 알립니다. 호출 전에 inputSchema로 인자를 검증하는 클라이언트는 REST 철자로 된 호출을 서버에 닿기도 전에 거부합니다. 별칭은 인자를 검증 없이 그대로 넘기는 호출자를 위한 것이니, 가급적 정식 이름을 쓰세요.

preflight_dalfox는 scan_with_dalfox보다 의도적으로 좁은 집합을 받습니다. 페이로드를 보내지 않으므로 속도 조절·워커·WAF 처리·블라인드 XSS·대기에 관한 옵션은 작용할 대상이 없어 거부됩니다. 받는 필드는 아래에 따로 적어두었습니다. (REST의 POST /preflight는 대신 스캔 본문 전체를 받고, delay, worker, rate_limit로도 속도를 조절하며, 쓸 데가 없는 옵션은 무시합니다. 두 표면이 실제로 갈리는 유일한 지점입니다.) 자격 증명과 대상은 그대로 전달됩니다. 쿠키 없이 preflight를 돌리면 인증된 스캔이 찾아낼 파라미터를 적게 보고하게 되기 때문입니다.

delay는(기본값 0, 범위 0~9999) 요청 사이에 그만큼의 밀리초를 대기하고, follow_redirects는(기본값 false) 스캐너가 3xx 응답을 따라가게 하며, proxy는 모든 요청을 HTTP 또는 SOCKS 프록시("http://127.0.0.1:8080")로 보냅니다.

include_request와 include_response는(둘 다 기본값 false) 원본 HTTP 요청 텍스트와 원본 응답 본문을 각 탐지 결과에 첨부해 포렌식 분석에 쓰이도록 합니다. 응답이 클 수 있으므로 증거가 필요할 때만 켜세요.

WAF 관련 다섯 개 필드는 CLI의 WAF 플래그와 대응됩니다. waf_bypass는 처리 모드를 고릅니다: "auto"(탐지 후 우회, 기본값) 또는 "off"(탐지하고 보고만 함). "force"도 받지만 "auto"와 똑같이 동작합니다. skip_waf_probe는(기본값 false) 자극 프로브만 건너뛰며, 프리플라이트 응답에 대한 패시브 탐지는 그대로 실행됩니다. force_waf는 탐지 결과 대신 특정 프로필(예: "cloudflare", "akamai", "modsec")을 고정합니다. "auto"와 "force" 모두에서 적용되며, "off"에서는 보고만 되고 우회는 적용되지 않습니다. waf_evasion은(기본값 false) 적응형 우회를 켭니다. waf_min_confidence는 [0.0, 1.0] 범위의 탐지 신뢰도 하한이며(기본값 0.3), 이보다 낮은 핑거프린트는 버려집니다. waf_bypass나 force_waf에 알 수 없는 값을 주거나 waf_min_confidence가 범위를 벗어나면 invalid_params로 거부됩니다.

remote_payloads와 remote_wordlists는(둘 다 기본값 []) 스캔을 시작하기 전에 원격 제공자로부터 추가 XSS 페이로드("portswigger", "payloadbox")와 파라미터 워드리스트("burp", "assetnote")를 가져옵니다.

max_payloads_per_param은 각 파라미터를 테스트할 페이로드 수의 상한입니다(기본값 0 = 내장 안전 상한을 제외하면 무제한). 에이전트의 스모크 스캔에는 10~50 같은 작은 값을 쓰세요.

wait는(기본값 false) 이 호출을 블로킹 방식으로 바꿉니다. 즉시 {scan_id, status: "queued"}를 반환하는 대신 스캔이 done / error / cancelled에 도달할 때까지 블로킹한 뒤 get_results_dalfox와 동일한 형태를 반환합니다. wait_timeout_sec는(기본값 300, 범위 1~86400) 그 대기의 실제 경과 시간 예산이며 wait가 false이면 무시됩니다. 시간이 초과되면 작업은 계속 실행되고 응답에 wait_timed_out: true가 담깁니다.

응답:

{ "scan_id": "9f2c…", "target": "https://example.com/search?q=test", "status": "queued" }

get_results_dalfox

스캔을 폴링합니다. 준비되면 상태, 진행률, 결과를 반환합니다.

{ "scan_id": "9f2c…" }

응답(진행 중):

{
  "scan_id": "9f2c…",
  "target": "…",
  "status": "running",
  "settled": false,
  "progress": {
    "params_total": 10,
    "params_tested": 4,
    "requests_sent": 215,
    "requests_failed": 0,
    "findings_so_far": 1,
    "estimated_completion_pct": 40,
    "suggested_poll_interval_ms": 2000
  }
}

전체 상태 응답에는 results(스캔이 종료될 때까지 null이며, 대상에 끝내 닿지 못했거나 시작 전에 취소된 스캔은 종료된 뒤에도 null), pagination, queued_at_ms, started_at_ms, finished_at_ms, duration_ms, 그리고 값이 있을 때 error_message도 담깁니다. requests_failed는 대상에 닿지 못한 요청 수입니다. 이 값이 requests_sent의 큰 비중을 차지한다면 탐지 결과 0건은 대상이 깨끗하다는 뜻이 아니라 스캔이 사실상 돌지 않았다는 뜻입니다.

응답(완료):

{
  "scan_id": "9f2c…",
  "status": "done",
  "settled": true,
  "results": [
    {
      "type": "V",
      "type_description": "Vulnerable - dalfox asserts this input is exploitable; act on it",
      "detection_method": "dom-verification",
      "confidence": "high",
      "confidence_reason": "DOM verification confirmed an executable position (DOM marker)",
      "inject_type": "inHTML",
      "method": "GET",
      "param": "q",
      "payload": "<svg/onload=alert(1)>",
      "evidence": "DOM verification successful for param q (DOM marker)",
      "cwe": "CWE-79",
      "severity": "High"
    }
  ]
}

탐지 결과를 담은 모든 응답에는 _untrusted_content_notice가 함께 실립니다. 경고가 경고 대상보다 먼저 읽히도록 JSON의 첫 번째 키로 직렬화됩니다. evidence, response, request, payload, param, location, message_str 필드는 스캔 대상이 고른 바이트를 그대로 인용한 것이고, 그 대상은 지금 시험받고 있는 쪽입니다 — 에이전트는 이를 보고할 데이터로만 읽어야 하며 절대 지시로 받아들이면 안 됩니다. 스캔된 페이지는 모델에게 건네는 지시문처럼 보이는 텍스트를 심어둘 수 있고, 거기에 따르면 다음 호출의 target, proxy, blind_callback_url, include_*를 대상이 고르게 됩니다. preflight_dalfox도 파라미터를 발견했다면 같은 안내를 붙입니다. 발견된 각 파라미터의 name은 대상의 마크업에서 뽑아낸 것이기 때문입니다.

offset과 limit으로 큰 결과 집합을 페이지 단위로 넘길 수 있고, pagination은 {total, offset, limit, returned, has_more}를 보고합니다. 한 페이지는 추가로 2 MiB로 제한됩니다. 탐지 결과가 몇 건 나올지는 호출자가 아니라 대상이 정하기 때문입니다. 이 예산으로 페이지가 잘리면 pagination에 truncated_by_size: true와 max_page_bytes가 추가됩니다 — limit이 요청한 것보다 적게 돌아왔을 뿐, 나머지는 다음 offset에 그대로 있습니다. 예산보다 큰 단일 탐지 결과는 버리지 않고 혼자 내보내므로 페이지 넘기기가 멈추지 않습니다.

progress.estimated_completion_pct와 params_tested는 발견된 각 파라미터가 완료될 때마다 실시간으로 증가합니다. 따라서 폴링 간격을 조절하는 데 사용할 수 있습니다 — suggested_poll_interval_ms를 따르세요. 스캔이 queued를 벗어나면 상태 응답에 settled도 담깁니다. 종료된 워커가 아직 정리 중이면 false이고, 레코드를 삭제해도 안전해지면 true가 됩니다. settled: true가 될 때까지는 0이 아닌 폴링 간격을 따르고, 그 뒤에 delete_scan_dalfox를 호출하세요.

대상에 도달할 수 없으면(DNS 실패, 연결 거부, TLS 오류, 타임아웃) 스캔은 빈 results와 함께 done으로 끝나는 대신 CONNECTION_FAILED를 포함하는 error_message와 함께 status: "error"로 끝납니다 — preflight_dalfox가 reachable: false로 보고하는 것과 동일한 구분입니다. target은 http:// 또는 https://로 시작해야 합니다.

인증 세션이 스캔 도중 끊어진 경우도 마찬가지입니다. 호출이 자격증명(cookies, 또는 headers의 Cookie / Authorization 항목)을 담고 있으면, Dalfox는 스캔 전에 인증된 응답의 지문을 잡아 두고 스캔이 끝날 때 다시 확인합니다. 그 사이에 세션이 만료됐다면 빈 results와 함께 done으로 끝나는 대신 SESSION_LOST:로 시작하는 error_message와 함께 status: "error"로 종료됩니다. 이런 스캔을 "XSS 없음"으로 요약하지 마십시오 — 실제로 테스트된 것이 없습니다. 자격증명을 넘기지 않으면 모니터링은 비용이 들지 않습니다.

list_scans_dalfox

추적 중인 모든 스캔을 최신순으로 나열합니다. 인자는 모두 선택 사항입니다:

{ "status": "running", "offset": 0, "limit": 0 }

status는 queued, running, done, error, cancelled 중 하나이고, offset과 limit으로 목록을 페이지 단위로 넘깁니다(limit: 0이 기본값이며 offset부터 전부 반환).

total, scans: [{scan_id, target, status, settled, result_count, queued_at_ms, started_at_ms, finished_at_ms, duration_ms}], pagination: {offset, limit, returned, has_more}를 반환하며, 실패한 스캔에는 error_message가 붙습니다 — 이것이 없으면 status: "error", result_count: 0인 행은 깨끗하게 끝난 스캔과 똑같아 보입니다.

cancel_scan_dalfox

대기 중이거나 실행 중인 스캔을 중단합니다:

{ "scan_id": "9f2c…" }

{scan_id, target, cancelled, previous_status}를 반환합니다. cancelled는 스캔이 queued나 running이었을 때만 true입니다. 이미 끝난 스캔이라면 이 호출은 아무 일도 하지 않고 cancelled는 false입니다. 실행 중인 스캔은 다음 취소 확인 지점에서 멈추고, 부분 결과를 가진 채 cancelled로 목록에 남습니다.

delete_scan_dalfox

추적 중인 스캔을 메모리에서 영구적으로 제거합니다. 종료된 스캔(done, error, cancelled) 중 워커가 정리를 끝낸 경우에만 삭제할 수 있습니다. 실행 중이거나 대기 중인 스캔은 먼저 취소해야 합니다. 취소 직후 삭제에서 워커가 정리 중이라는 오류가 나오면 스캔 상태를 조회하고 잠시 후 다시 삭제해야 합니다. 종료된 스캔은 1시간 후 자동으로 정리되기도 합니다.

{ "scan_id": "9f2c…" }

{scan_id, target, deleted: true, previous_status}를 반환합니다.

preflight_dalfox

페이로드를 보내지 않고 대상을 분석합니다. 스캔을 확정하기 전에 범위를 정하는 데 유용합니다.

받는 모든 필드와 각각의 기본값입니다. 필수 필드는 target 하나뿐입니다:

{
  "target": "https://example.com",
  "param": [],
  "method": "GET",
  "data": null,
  "headers": [],
  "cookies": [],
  "user_agent": null,
  "timeout": 10,
  "proxy": null,
  "follow_redirects": false,
  "insecure": true,
  "skip_discovery": false,
  "skip_mining": false,
  "encoders": ["url", "html"],
  "max_payloads_per_param": 0,
  "deep_scan": false
}

도달 가능 여부, 발견된 파라미터, 예상 요청 수를 반환합니다: {target, reachable, method, params_discovered, estimated_total_requests, params: [{name, location, estimated_requests}]}. 도달할 수 없는 대상은 reachable: false와 error_code: "CONNECTION_FAILED"로 돌아옵니다. param은 스캔 도구와의 대칭을 위해 받기만 하고 적용하지 않습니다. 프리플라이트는 항상 발견된 파라미터 전체를 보고합니다.

encoders, max_payloads_per_param, deep_scan는 그 자체로 요청을 보내지 않습니다. 뒤이어 실행할 scan_with_dalfox 호출을 설명하는 값이며, estimated_total_requests가 그 스캔의 확장 폭을 반영하도록 합니다. 실제로 스캔할 때 쓸 값을 그대로 넘기세요.

추정치는 스캔이 파라미터마다 실행하는 두 단계(리플렉션, DOM 검증)를 모두 세며, 각 단계를 파라미터당 페이로드 상한 안으로 맞춥니다. --dry-run과 같은 계산입니다. 다만 하한값입니다. WAF 우회 변형과 스캔이 덧붙이는 CSP/기술 스택별 페이로드는 세지 않습니다.

용량 제한

MCP 서버는 활성(대기 중이거나 실행 중인) 스캔을 최대 100개, 동시 프리플라이트를 최대 32개까지 유지합니다. 한도를 넘는 호출은 서버가 용량을 다 썼다는 JSON-RPC -32603 오류로 거부됩니다. -32602와 달리 스캔이 끝나거나 취소된 뒤 다시 시도할 만한 오류입니다. 취소한 스캔은 워커가 실제로 멈출 때까지(settled: true) 자리를 계속 차지합니다. 종료된 스캔은 최대 1000개까지 보관하며, 넘치면 가장 오래된 것부터 버리고, 종료된 스캔은 모두 끝난 지 1시간 뒤 정리됩니다.

스캔 하나가 테스트하는 파라미터는 최대 512개입니다. 그보다 많은 파라미터를 드러내는 대상에서는 발견한 목록이 잘리고 스캔은 그대로 done으로 끝나므로, 중요한 파라미터는 param으로 골라 넘기세요.

구조화된 결과

모든 도구(tool)는 tools/list에 outputSchema를 게시하고, tools/call 응답에 그 스키마를 따르는 structuredContent 객체를 함께 돌려줍니다. 클라이언트는 문자열을 파싱하는 대신 응답을 검증하고 바로 분해해서 쓸 수 있습니다. 같은 JSON은 기존처럼 텍스트 콘텐츠 블록으로도 그대로 나가므로, 지금 텍스트를 읽고 있는 쪽은 아무것도 바꿀 필요가 없습니다.

각 도구(tool)는 표시용 title과 표준 동작 힌트도 함께 싣습니다. 클라이언트가 어떤 호출을 물어보지 않고 해도 되는지 스스로 판단하는 근거입니다:

도구(tool) readOnlyHint destructiveHint idempotentHint openWorldHint
scan_with_dalfox false true false true
preflight_dalfox false false false true
get_results_dalfox true — true false
list_scans_dalfox true — true false
cancel_scan_dalfox false false true false
delete_scan_dalfox false true false false

openWorldHint: true는 네트워크로 제3자 호스트에 도달하는 도구(tool)를 가리킵니다. destructiveHint: true는 무언가를 영구히 바꿔 놓을 수 있는 둘을 가리킵니다. 하나는 기록을 버리고, 다른 하나는 발견된 모든 파라미터에 페이로드를 주입합니다 — 직접 넘긴 POST 본문도 포함되고, blind_callback_url을 설정했다면 대상에 남는 저장형 <script src=...>까지 포함됩니다. preflight_dalfox 역시 read-only가 아닙니다: 공격 페이로드는 보내지 않지만 method와 data를 받고 마이닝 단계가 프로브 요청을 보내므로, POST preflight는 대상의 상태를 바꿀 수 있습니다.

initialize 핸드셰이크는 서버를 dalfox(표시 제목 Dalfox XSS Scanner)와 그 자신의 버전으로 식별하고, 아이콘과 문서 사이트(websiteUrl)도 함께 알립니다. tools, resources, prompts, completions 기능(capability)을 선언하며, 의도한 도구(tool) 호출 순서, 탐지 결과의 축을 읽는 법, 그리고 아래의 출처 규칙을 담은 instructions를 돌려줍니다.

진행률, 리소스, 프롬프트

진행률. wait=true인 scan_with_dalfox 호출이나 preflight_dalfox 호출에 _meta.progressToken을 붙이면, 호출이 열려 있는 동안 Dalfox가 그 토큰으로 notifications/progress를 흘려보냅니다 — 몇 분이 걸릴 수 있는 작업에서 클라이언트가 조용한 스피너 대신 실제 움직임을 보여줍니다. 스캔의 경우 숫자 progress는 누적 전송 요청 수이고, 단계, 테스트한 파라미터 수, 지금까지의 탐지 결과, 대상에 끝내 닿지 못한 요청 수는 message에 담깁니다. preflight_dalfox는 대신 2초마다 하트비트를 보냅니다 (analyzing target (Ns elapsed)). 종료 상태에 대해서는 아무것도 보내지 않습니다. 그 신호는 도구(tool) 호출의 결과 자체입니다.

취소. 진행 중인 wait=true 호출에 notifications/cancelled를 보내면 대기만이 아니라 스캔 자체가 멈춥니다 — 작업은 the client cancelled the tool call이라는 error_message와 함께 cancelled로 정리되고, 그때까지 찾은 것은 유지됩니다. 대기 예산이 그냥 만료되는 경우(wait_timed_out: true)와는 의도적으로 다릅니다. 그때는 계속 폴링할 수 있도록 스캔이 살아 있습니다.

리소스. 스캔은 호출 대상일 뿐 아니라 주소를 가진 리소스이기도 합니다:

URI 내용
dalfox://scans 작업 목록 — list_scans_dalfox가 반환하는 것과 같은 본문
dalfox://scan/{scan_id} 스캔 하나의 상태·진행률·탐지 결과 — get_results_dalfox와 같은 본문

resources/list는 목록과 함께 추적 중인 스캔을 하나씩 돌려줍니다(커서로 페이징). 호스트의 컨텍스트 선택기에 빈 템플릿이 아니라 실제 스캔이 뜬다는 뜻입니다. 목록 리소스를 읽을 때는 200행에서 스스로 끊습니다 — resources/read에는 페이지 인자가 없으므로, 어디서 잘렸는지는 본문의 pagination이 말해 줍니다. scan_with_dalfox와 get_results_dalfox의 결과에는 그 스캔을 가리키는 resource_link 콘텐츠 블록도 함께 실려서, 모델이 결과를 다시 인용하게 하는 대신 클라이언트가 탐지 결과를 그대로 첨부할 수 있습니다. 이 블록을 해석하지 못하는 2025-06-18 이전 리비전을 협상한 클라이언트에게는 붙이지 않습니다.

프롬프트. 클라이언트의 프롬프트 메뉴에 두 가지 워크플로를 게시합니다:

프롬프트 인자 하는 일
scan_target target preflight로 규모를 재고, 스캔을 돌리고, 확신도 순으로 보고
triage_findings scan_id 끝난 스캔을 type / detection_method 축으로 읽고, 무엇이 커버되지 않았는지까지 말하기

completion/complete는 triage 프롬프트와 dalfox://scan/{scan_id} 템플릿의 scan_id 인자를 추적 중인 스캔에서 채워 줍니다. scan id는 사람이 손으로 옮겨 적지 않는 64자 다이제스트라 이게 중요합니다.

일반적인 에이전트 흐름

  1. 에이전트가 preflight_dalfox를 호출하여 대상을 확인하고 파라미터 수를 셉니다.
  2. 에이전트가 scan_with_dalfox를 호출하여 scan_id를 받습니다.
  3. 에이전트가 진행률 객체의 suggested_poll_interval_ms를 사용하여 get_results_dalfox를 폴링합니다.
  4. 상태가 종료되었고 settled == true가 되면 에이전트가 delete_scan_dalfox를 호출할 수 있습니다. 그 뒤 탐지 결과를 요약하여 사용자에게 다시 보고합니다.

스캔이 비동기이므로 에이전트는 응답성을 유지합니다. 오래 걸리는 작업을 한 번의 호출로 묶고 싶다면 wait=true에 진행률 토큰을 함께 붙이세요.

권한 및 안전

CLI와 같은 규칙이 적용되며, Dalfox가 대신 확인해 주지는 못합니다: 테스트 권한이 있는 대상만 스캔하세요. 에이전트의 시스템 프롬프트에서 "모든 스캔 전에 범위를 확인하세요"와 같은 명시적 사용자 확인 단계 뒤에 Dalfox MCP 호출을 두는 것을 고려하세요.

탐지 결과는 에이전트에게 신뢰할 수 없는 입력입니다. CLI나 REST API와 달리 MCP는 스캔 출력을 "읽은 대로 행동하는" 모델에게 건네고, 탐지 결과에 인용된 바이트는 전부 대상이 고른 것입니다. Dalfox는 그런 응답에 _untrusted_content_notice를 붙이지만 이는 상기시키는 라벨이지 샌드박스가 아닙니다 — 범위 결정(어느 대상, 어느 프록시, 어느 콜백)은 운영자가 쥐고 있어야 하며, 스캐너가 페이지에서 읽어온 무언가가 그것을 바꾸게 두면 안 됩니다.

스캔의 error_message도 마찬가지입니다. 인증 세션이 스캔 도중 끊기면 Dalfox는 오리진이 리다이렉트한 URL을 그대로 보고하므로, 탐지 결과가 하나도 없는 스캔에서도 그 필드는 대상이 고른 값을 인용합니다. 그 값을 실어 나르는 본문(상태 폴링, list_scans_dalfox 목록, 대응 리소스)에는 같은 이유로 _untrusted_content_notice가 붙습니다.

문제 해결

  • 도구(tool)가 표시되지 않나요? MCP 클라이언트가 사용하는 PATH에 dalfox 바이너리가 있는지 확인하세요. macOS의 Claude Desktop에서는 대개 /usr/local/bin 또는 /opt/homebrew/bin입니다.
  • 결과가 비어 있나요? 다시 폴링하세요. 스캔은 비동기입니다. suggested_poll_interval_ms를 폴링 주기로 사용하세요.
  • 로그를 보고 싶나요? 설정하는 동안 dalfox mcp --debug를 실행하세요. 디버그 라인은 stderr로 가므로 MCP 채널을 오염시키지 않습니다.
ESC