feat: MCP 서버 추가 — AI 에이전트용 웹 검사 도구

Node.js + TypeScript MCP 서버 구현:
- 5개 도구: inspect_page, inspect_site, get_inspection, get_issues, get_history
- 듀얼 트랜스포트: stdio (Claude Desktop) + Streamable HTTP (Docker/원격)
- i18n 지원 (영어/한국어)
- Docker 통합 (port 3100) + Nginx /mcp 프록시
- Smithery 레지스트리 배포 설정

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jungwoo choi
2026-02-14 15:44:35 +09:00
parent bffce65aca
commit 69e0f80282
51 changed files with 4327 additions and 0 deletions

1
mcp/dist/i18n/en.d.ts vendored Normal file
View File

@ -0,0 +1 @@
export declare const en: Record<string, string>;

49
mcp/dist/i18n/en.js vendored Normal file
View File

@ -0,0 +1,49 @@
export const en = {
// --- Tool descriptions ---
"inspect_page.description": "Inspect a single web page for HTML/CSS quality, accessibility (WCAG/KWCAG), SEO, and performance/security. Returns scores, grades, and top issues. Takes 10-30 seconds.",
"inspect_page.param.url": "The URL of the web page to inspect (must start with http:// or https://)",
"inspect_page.param.standard": "Accessibility standard to check against (default: wcag_2.1_aa)",
"inspect_site.description": "Start a site-wide crawl and inspection. Crawls links from the root URL and inspects each page. Returns a site_inspection_id for tracking. Use get_inspection to check results later.",
"inspect_site.param.url": "Root URL of the site to crawl and inspect",
"inspect_site.param.max_pages": "Maximum number of pages to crawl (default: 20, max: 500, 0: unlimited)",
"inspect_site.param.max_depth": "Maximum link depth to crawl (default: 2, max: 3)",
"get_inspection.description": "Get detailed inspection results by inspection ID. Works for both single-page and site inspections.",
"get_inspection.param.id": "The inspection_id or site_inspection_id to retrieve",
"get_issues.description": "Get a filtered list of issues found during an inspection. Filter by category and/or severity.",
"get_issues.param.id": "The inspection_id to get issues for",
"get_issues.param.category": "Filter by category: html_css, accessibility, seo, or performance_security",
"get_issues.param.severity": "Filter by severity: critical, major, minor, or info",
"get_history.description": "List recent inspection history. Optionally filter by URL substring.",
"get_history.param.url": "Optional URL substring to filter results",
"get_history.param.limit": "Number of results to return (default: 10)",
"common.param.language": "Response language: 'en' for English, 'ko' for Korean",
// --- Result formatting ---
"result.title": "Web Inspection Result",
"result.overall_score": "Overall Score",
"result.duration": "Duration",
"result.standard": "Standard",
"result.category_scores": "Category Scores",
"result.category": "Category",
"result.score": "Score",
"result.grade": "Grade",
"result.issues": "Issues",
"result.issue_summary": "Issue Summary",
"result.total": "Total",
"result.top_issues": "Top Issues (Critical/Major)",
"result.message": "Message",
"result.element": "Element",
"result.suggestion": "Suggestion",
"result.severity": "Severity",
"result.more_issues_hint": "Use `get_issues` with inspection_id '{inspectionId}' to see all issues with filtering.",
// --- Tool responses ---
"inspect_page.timeout": "Inspection timed out for {url}. Inspection ID: {inspectionId}. Try get_inspection later.",
"inspect_site.started": "Site inspection started successfully.",
"inspect_site.follow_up_hint": "The crawl is running in the background. Use `get_inspection` with ID '{id}' to check the result after a few minutes.",
"get_inspection.not_found": "Inspection '{id}' not found.",
"get_issues.title": "Issue List",
"get_issues.total": "Total Issues",
"get_issues.filters": "Filters",
"get_history.title": "Inspection History",
"get_history.total": "Total Records",
};
//# sourceMappingURL=en.js.map

1
mcp/dist/i18n/en.js.map vendored Normal file
View File

@ -0,0 +1 @@
{"version":3,"file":"en.js","sourceRoot":"","sources":["../../src/i18n/en.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,EAAE,GAA2B;IACxC,4BAA4B;IAC5B,0BAA0B,EACxB,yKAAyK;IAC3K,wBAAwB,EACtB,0EAA0E;IAC5E,6BAA6B,EAC3B,gEAAgE;IAClE,0BAA0B,EACxB,sLAAsL;IACxL,wBAAwB,EAAE,2CAA2C;IACrE,8BAA8B,EAC5B,wEAAwE;IAC1E,8BAA8B,EAC5B,kDAAkD;IACpD,4BAA4B,EAC1B,oGAAoG;IACtG,yBAAyB,EACvB,qDAAqD;IACvD,wBAAwB,EACtB,+FAA+F;IACjG,qBAAqB,EAAE,qCAAqC;IAC5D,2BAA2B,EACzB,2EAA2E;IAC7E,2BAA2B,EACzB,qDAAqD;IACvD,yBAAyB,EACvB,qEAAqE;IACvE,uBAAuB,EAAE,0CAA0C;IACnE,yBAAyB,EAAE,2CAA2C;IACtE,uBAAuB,EAAE,sDAAsD;IAE/E,4BAA4B;IAC5B,cAAc,EAAE,uBAAuB;IACvC,sBAAsB,EAAE,eAAe;IACvC,iBAAiB,EAAE,UAAU;IAC7B,iBAAiB,EAAE,UAAU;IAC7B,wBAAwB,EAAE,iBAAiB;IAC3C,iBAAiB,EAAE,UAAU;IAC7B,cAAc,EAAE,OAAO;IACvB,cAAc,EAAE,OAAO;IACvB,eAAe,EAAE,QAAQ;IACzB,sBAAsB,EAAE,eAAe;IACvC,cAAc,EAAE,OAAO;IACvB,mBAAmB,EAAE,6BAA6B;IAClD,gBAAgB,EAAE,SAAS;IAC3B,gBAAgB,EAAE,SAAS;IAC3B,mBAAmB,EAAE,YAAY;IACjC,iBAAiB,EAAE,UAAU;IAC7B,yBAAyB,EACvB,wFAAwF;IAE1F,yBAAyB;IACzB,sBAAsB,EACpB,0FAA0F;IAC5F,sBAAsB,EAAE,uCAAuC;IAC/D,6BAA6B,EAC3B,sHAAsH;IACxH,0BAA0B,EAAE,8BAA8B;IAC1D,kBAAkB,EAAE,YAAY;IAChC,kBAAkB,EAAE,cAAc;IAClC,oBAAoB,EAAE,SAAS;IAC/B,mBAAmB,EAAE,oBAAoB;IACzC,mBAAmB,EAAE,eAAe;CACrC,CAAC"}

6
mcp/dist/i18n/index.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
/**
* Translate a key with optional interpolation.
* Usage: t("inspect_page.description", "ko")
* t("result.title", "en", { url: "..." })
*/
export declare function t(key: string, lang: "en" | "ko", params?: Record<string, string | number>): string;

18
mcp/dist/i18n/index.js vendored Normal file
View File

@ -0,0 +1,18 @@
import { en } from "./en.js";
import { ko } from "./ko.js";
const messages = { en, ko };
/**
* Translate a key with optional interpolation.
* Usage: t("inspect_page.description", "ko")
* t("result.title", "en", { url: "..." })
*/
export function t(key, lang, params) {
let text = messages[lang]?.[key] || messages["en"]?.[key] || key;
if (params) {
for (const [k, v] of Object.entries(params)) {
text = text.replace(new RegExp(`\\{${k}\\}`, "g"), String(v));
}
}
return text;
}
//# sourceMappingURL=index.js.map

1
mcp/dist/i18n/index.js.map vendored Normal file
View File

@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/i18n/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAC7B,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAE7B,MAAM,QAAQ,GAA2C,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC;AAEpE;;;;GAIG;AACH,MAAM,UAAU,CAAC,CACf,GAAW,EACX,IAAiB,EACjB,MAAwC;IAExC,IAAI,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC;IACjE,IAAI,MAAM,EAAE,CAAC;QACX,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAC5C,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAChE,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}

1
mcp/dist/i18n/ko.d.ts vendored Normal file
View File

@ -0,0 +1 @@
export declare const ko: Record<string, string>;

49
mcp/dist/i18n/ko.js vendored Normal file
View File

@ -0,0 +1,49 @@
export const ko = {
// --- 도구 설명 ---
"inspect_page.description": "단일 웹 페이지의 HTML/CSS 품질, 접근성(WCAG/KWCAG), SEO, 성능/보안을 검사합니다. 점수, 등급, 주요 이슈를 반환합니다. 10-30초 소요.",
"inspect_page.param.url": "검사할 웹 페이지 URL (http:// 또는 https://로 시작)",
"inspect_page.param.standard": "접근성 검사 표준 (기본값: wcag_2.1_aa)",
"inspect_site.description": "사이트 전체 크롤링 및 검사를 시작합니다. 루트 URL에서 링크를 따라가며 각 페이지를 검사합니다. site_inspection_id를 반환하며, get_inspection으로 결과를 확인할 수 있습니다.",
"inspect_site.param.url": "크롤링 및 검사할 사이트의 루트 URL",
"inspect_site.param.max_pages": "최대 크롤링 페이지 수 (기본값: 20, 최대: 500, 0: 무제한)",
"inspect_site.param.max_depth": "최대 크롤링 깊이 (기본값: 2, 최대: 3)",
"get_inspection.description": "검사 ID로 상세 결과를 조회합니다. 단일 페이지 및 사이트 검사 모두 지원.",
"get_inspection.param.id": "조회할 inspection_id 또는 site_inspection_id",
"get_issues.description": "검사에서 발견된 이슈 목록을 필터링하여 조회합니다. 카테고리 및 심각도로 필터링 가능.",
"get_issues.param.id": "이슈를 조회할 inspection_id",
"get_issues.param.category": "카테고리 필터: html_css, accessibility, seo, performance_security",
"get_issues.param.severity": "심각도 필터: critical, major, minor, info",
"get_history.description": "최근 검사 이력을 조회합니다. URL로 필터링 가능.",
"get_history.param.url": "결과를 필터링할 URL (부분 문자열)",
"get_history.param.limit": "반환할 결과 수 (기본값: 10)",
"common.param.language": "응답 언어: 'en' 영어, 'ko' 한국어",
// --- 결과 포맷 ---
"result.title": "웹 검사 결과",
"result.overall_score": "종합 점수",
"result.duration": "소요 시간",
"result.standard": "검사 표준",
"result.category_scores": "카테고리별 점수",
"result.category": "카테고리",
"result.score": "점수",
"result.grade": "등급",
"result.issues": "이슈",
"result.issue_summary": "이슈 요약",
"result.total": "전체",
"result.top_issues": "주요 이슈 (Critical/Major)",
"result.message": "메시지",
"result.element": "요소",
"result.suggestion": "개선 제안",
"result.severity": "심각도",
"result.more_issues_hint": "`get_issues` 도구에 inspection_id '{inspectionId}'를 사용하여 모든 이슈를 필터링해서 확인할 수 있습니다.",
// --- 도구 응답 ---
"inspect_page.timeout": "{url} 검사가 시간 초과되었습니다. Inspection ID: {inspectionId}. 나중에 get_inspection으로 확인해 보세요.",
"inspect_site.started": "사이트 검사가 성공적으로 시작되었습니다.",
"inspect_site.follow_up_hint": "크롤링이 백그라운드에서 실행 중입니다. 몇 분 후 `get_inspection`에 ID '{id}'를 사용하여 결과를 확인하세요.",
"get_inspection.not_found": "검사 '{id}'를 찾을 수 없습니다.",
"get_issues.title": "이슈 목록",
"get_issues.total": "전체 이슈 수",
"get_issues.filters": "필터",
"get_history.title": "검사 이력",
"get_history.total": "전체 레코드",
};
//# sourceMappingURL=ko.js.map

1
mcp/dist/i18n/ko.js.map vendored Normal file
View File

@ -0,0 +1 @@
{"version":3,"file":"ko.js","sourceRoot":"","sources":["../../src/i18n/ko.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,EAAE,GAA2B;IACxC,gBAAgB;IAChB,0BAA0B,EACxB,6FAA6F;IAC/F,wBAAwB,EACtB,yCAAyC;IAC3C,6BAA6B,EAC3B,8BAA8B;IAChC,0BAA0B,EACxB,oHAAoH;IACtH,wBAAwB,EAAE,uBAAuB;IACjD,8BAA8B,EAC5B,yCAAyC;IAC3C,8BAA8B,EAC5B,2BAA2B;IAC7B,4BAA4B,EAC1B,6CAA6C;IAC/C,yBAAyB,EACvB,yCAAyC;IAC3C,wBAAwB,EACtB,kDAAkD;IACpD,qBAAqB,EAAE,uBAAuB;IAC9C,2BAA2B,EACzB,6DAA6D;IAC/D,2BAA2B,EACzB,sCAAsC;IACxC,yBAAyB,EACvB,+BAA+B;IACjC,uBAAuB,EAAE,uBAAuB;IAChD,yBAAyB,EAAE,oBAAoB;IAC/C,uBAAuB,EAAE,0BAA0B;IAEnD,gBAAgB;IAChB,cAAc,EAAE,SAAS;IACzB,sBAAsB,EAAE,OAAO;IAC/B,iBAAiB,EAAE,OAAO;IAC1B,iBAAiB,EAAE,OAAO;IAC1B,wBAAwB,EAAE,UAAU;IACpC,iBAAiB,EAAE,MAAM;IACzB,cAAc,EAAE,IAAI;IACpB,cAAc,EAAE,IAAI;IACpB,eAAe,EAAE,IAAI;IACrB,sBAAsB,EAAE,OAAO;IAC/B,cAAc,EAAE,IAAI;IACpB,mBAAmB,EAAE,wBAAwB;IAC7C,gBAAgB,EAAE,KAAK;IACvB,gBAAgB,EAAE,IAAI;IACtB,mBAAmB,EAAE,OAAO;IAC5B,iBAAiB,EAAE,KAAK;IACxB,yBAAyB,EACvB,gFAAgF;IAElF,gBAAgB;IAChB,sBAAsB,EACpB,oFAAoF;IACtF,sBAAsB,EAAE,wBAAwB;IAChD,6BAA6B,EAC3B,0EAA0E;IAC5E,0BAA0B,EAAE,uBAAuB;IACnD,kBAAkB,EAAE,OAAO;IAC3B,kBAAkB,EAAE,SAAS;IAC7B,oBAAoB,EAAE,IAAI;IAC1B,mBAAmB,EAAE,OAAO;IAC5B,mBAAmB,EAAE,QAAQ;CAC9B,CAAC"}