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>
197 lines
8.2 KiB
JavaScript
197 lines
8.2 KiB
JavaScript
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
import { ApiClient } from "./api-client.js";
|
|
import { t } from "./i18n/index.js";
|
|
import { inspectPage } from "./tools/inspect-page.js";
|
|
import { inspectSite } from "./tools/inspect-site.js";
|
|
import { getInspection } from "./tools/get-inspection.js";
|
|
import { getIssues } from "./tools/get-issues.js";
|
|
import { getHistory } from "./tools/get-history.js";
|
|
const STANDARDS = [
|
|
"wcag_2.0_a",
|
|
"wcag_2.0_aa",
|
|
"wcag_2.1_aa",
|
|
"wcag_2.2_aa",
|
|
"kwcag_2.1",
|
|
"kwcag_2.2",
|
|
];
|
|
export function createServer(apiUrl, defaultLang) {
|
|
const client = new ApiClient(apiUrl);
|
|
const server = new Server({ name: "web-inspector-mcp", version: "1.0.0" }, { capabilities: { tools: {} } });
|
|
// ── List tools ──────────────────────────────────────────
|
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
tools: [
|
|
{
|
|
name: "inspect_page",
|
|
description: t("inspect_page.description", defaultLang),
|
|
inputSchema: {
|
|
type: "object",
|
|
properties: {
|
|
url: {
|
|
type: "string",
|
|
description: t("inspect_page.param.url", defaultLang),
|
|
},
|
|
accessibility_standard: {
|
|
type: "string",
|
|
enum: STANDARDS,
|
|
description: t("inspect_page.param.standard", defaultLang),
|
|
},
|
|
language: {
|
|
type: "string",
|
|
enum: ["en", "ko"],
|
|
description: t("common.param.language", defaultLang),
|
|
},
|
|
},
|
|
required: ["url"],
|
|
},
|
|
},
|
|
{
|
|
name: "inspect_site",
|
|
description: t("inspect_site.description", defaultLang),
|
|
inputSchema: {
|
|
type: "object",
|
|
properties: {
|
|
url: {
|
|
type: "string",
|
|
description: t("inspect_site.param.url", defaultLang),
|
|
},
|
|
max_pages: {
|
|
type: "number",
|
|
description: t("inspect_site.param.max_pages", defaultLang),
|
|
},
|
|
max_depth: {
|
|
type: "number",
|
|
description: t("inspect_site.param.max_depth", defaultLang),
|
|
},
|
|
accessibility_standard: {
|
|
type: "string",
|
|
enum: STANDARDS,
|
|
description: t("inspect_page.param.standard", defaultLang),
|
|
},
|
|
language: {
|
|
type: "string",
|
|
enum: ["en", "ko"],
|
|
description: t("common.param.language", defaultLang),
|
|
},
|
|
},
|
|
required: ["url"],
|
|
},
|
|
},
|
|
{
|
|
name: "get_inspection",
|
|
description: t("get_inspection.description", defaultLang),
|
|
inputSchema: {
|
|
type: "object",
|
|
properties: {
|
|
id: {
|
|
type: "string",
|
|
description: t("get_inspection.param.id", defaultLang),
|
|
},
|
|
language: {
|
|
type: "string",
|
|
enum: ["en", "ko"],
|
|
description: t("common.param.language", defaultLang),
|
|
},
|
|
},
|
|
required: ["id"],
|
|
},
|
|
},
|
|
{
|
|
name: "get_issues",
|
|
description: t("get_issues.description", defaultLang),
|
|
inputSchema: {
|
|
type: "object",
|
|
properties: {
|
|
id: {
|
|
type: "string",
|
|
description: t("get_issues.param.id", defaultLang),
|
|
},
|
|
category: {
|
|
type: "string",
|
|
enum: [
|
|
"html_css",
|
|
"accessibility",
|
|
"seo",
|
|
"performance_security",
|
|
],
|
|
description: t("get_issues.param.category", defaultLang),
|
|
},
|
|
severity: {
|
|
type: "string",
|
|
enum: ["critical", "major", "minor", "info"],
|
|
description: t("get_issues.param.severity", defaultLang),
|
|
},
|
|
language: {
|
|
type: "string",
|
|
enum: ["en", "ko"],
|
|
description: t("common.param.language", defaultLang),
|
|
},
|
|
},
|
|
required: ["id"],
|
|
},
|
|
},
|
|
{
|
|
name: "get_history",
|
|
description: t("get_history.description", defaultLang),
|
|
inputSchema: {
|
|
type: "object",
|
|
properties: {
|
|
url: {
|
|
type: "string",
|
|
description: t("get_history.param.url", defaultLang),
|
|
},
|
|
limit: {
|
|
type: "number",
|
|
description: t("get_history.param.limit", defaultLang),
|
|
},
|
|
language: {
|
|
type: "string",
|
|
enum: ["en", "ko"],
|
|
description: t("common.param.language", defaultLang),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
],
|
|
}));
|
|
// ── Call tool ────────────────────────────────────────────
|
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
const { name, arguments: args = {} } = request.params;
|
|
const lang = args.language || defaultLang;
|
|
try {
|
|
let text;
|
|
switch (name) {
|
|
case "inspect_page":
|
|
text = await inspectPage(client, args.url, lang, args.accessibility_standard);
|
|
break;
|
|
case "inspect_site":
|
|
text = await inspectSite(client, args.url, lang, args.max_pages, args.max_depth, args.accessibility_standard);
|
|
break;
|
|
case "get_inspection":
|
|
text = await getInspection(client, args.id, lang);
|
|
break;
|
|
case "get_issues":
|
|
text = await getIssues(client, args.id, lang, args.category, args.severity);
|
|
break;
|
|
case "get_history":
|
|
text = await getHistory(client, lang, args.url, args.limit);
|
|
break;
|
|
default:
|
|
return {
|
|
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
isError: true,
|
|
};
|
|
}
|
|
return { content: [{ type: "text", text }] };
|
|
}
|
|
catch (error) {
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
return {
|
|
content: [{ type: "text", text: `Error: ${message}` }],
|
|
isError: true,
|
|
};
|
|
}
|
|
});
|
|
return server;
|
|
}
|
|
//# sourceMappingURL=server.js.map
|