🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@wot-ui/cli

Package Overview
Dependencies
Maintainers
1
Versions
13
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@wot-ui/cli - npm Package Compare versions

Comparing version
0.0.1-beta.7
to
0.0.1-beta.8
+203
dist/scanner-DYthnCXr.mjs
import { existsSync, readFileSync, readdirSync } from "node:fs";
import { dirname, join, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { gunzipSync } from "node:zlib";
import { parse } from "@vue/compiler-sfc";
//#region package.json
var version = "0.0.1-beta.8";
//#endregion
//#region src/data/loader.ts
const currentDir = dirname(fileURLToPath(import.meta.url));
function resolveDataDir() {
const candidates = [
join(currentDir, "..", "data"),
join(currentDir, "..", "..", "data"),
join(currentDir, "data")
];
for (const candidate of candidates) if (existsSync(join(candidate, "versions.json")) || existsSync(join(candidate, "versions.json.gz"))) return candidate;
throw new Error("Unable to locate bundled data directory");
}
const dataDir = resolveDataDir();
function readJsonFile(baseName) {
const jsonPath = join(dataDir, `${baseName}.json`);
if (existsSync(jsonPath)) return JSON.parse(readFileSync(jsonPath, "utf8"));
const gzipPath = join(dataDir, `${baseName}.json.gz`);
if (existsSync(gzipPath)) {
const compressed = readFileSync(gzipPath);
return JSON.parse(gunzipSync(compressed).toString("utf8"));
}
throw new Error(`Data file not found for ${baseName}`);
}
function loadVersionsFile() {
return readJsonFile("versions");
}
function loadMetadataFile(versionKey) {
return readJsonFile(versionKey);
}
//#endregion
//#region src/data/version.ts
const VERSION_KEY = "v2";
function resolveVersion(requested) {
const versions = loadVersionsFile();
if (!requested) return VERSION_KEY;
const normalized = requested.trim();
if (versions.aliases[normalized]) return VERSION_KEY;
if (versions.supported.includes(normalized)) return VERSION_KEY;
if (normalized === VERSION_KEY) return VERSION_KEY;
throw new Error(`Unsupported wot-ui version: ${requested}`);
}
//#endregion
//#region src/data/metadata.ts
function loadResolvedMetadata(version$1) {
return loadMetadataFile(resolveVersion(version$1));
}
function listComponents(version$1) {
return loadResolvedMetadata(version$1).components;
}
function findComponent(name, version$1) {
const normalized = name.trim().toLowerCase();
return listComponents(version$1).find((component) => component.name.toLowerCase() === normalized || component.tag.toLowerCase() === normalized);
}
//#endregion
//#region src/utils/files.ts
const DEFAULT_IGNORES = new Set([
".git",
".idea",
".output",
".turbo",
".vscode",
"dist",
"build",
"coverage",
"node_modules"
]);
function walkFiles(rootDir, extensions) {
const results = [];
function visit(dir) {
for (const entry of readdirSync(dir, { withFileTypes: true })) {
if (DEFAULT_IGNORES.has(entry.name)) continue;
const fullPath = join(dir, entry.name);
if (entry.isDirectory()) {
visit(fullPath);
continue;
}
if (extensions.some((extension) => entry.name.endsWith(extension))) results.push(fullPath);
}
}
visit(rootDir);
return results;
}
function safeRelative(rootDir, filePath) {
return relative(rootDir, filePath) || ".";
}
//#endregion
//#region src/utils/scanner.ts
const IMPORT_RE = /from\s+['"]([^'"]*wot[^'"]*)['"]/g;
const TAG_RE = /<\s*(wd-[a-z0-9-]+)/gi;
const BUTTON_RE = /<wd-button\b([^>]*)>([\s\S]*?)<\/wd-button>|<wd-button\b([^>]*)\/>/gi;
function getLineNumber(source, index) {
return source.slice(0, index).split("\n").length;
}
function collectTemplateTags(content) {
const counts = /* @__PURE__ */ new Map();
for (const match of content.matchAll(TAG_RE)) {
const tag = match[1]?.toLowerCase();
if (!tag) continue;
counts.set(tag, (counts.get(tag) ?? 0) + 1);
}
return counts;
}
function collectImports(scriptContent) {
const imports = /* @__PURE__ */ new Set();
for (const match of scriptContent.matchAll(IMPORT_RE)) if (match[1]) imports.add(match[1]);
return [...imports];
}
function analyzeUsage(targetDir, version$1) {
const dir = resolve(targetDir);
const files = walkFiles(dir, [".vue"]);
const knownByTag = new Map(listComponents(version$1).map((component) => [component.tag.toLowerCase(), component]));
const usageMap = /* @__PURE__ */ new Map();
const imports = /* @__PURE__ */ new Set();
for (const file of files) {
const parsed = parse(readFileSync(file, "utf8"), { filename: file });
const template = parsed.descriptor.template?.content ?? "";
const script = [parsed.descriptor.script?.content ?? "", parsed.descriptor.scriptSetup?.content ?? ""].filter(Boolean).join("\n");
for (const item of collectImports(script)) imports.add(item);
for (const [tag, count] of collectTemplateTags(template)) {
const known = knownByTag.get(tag);
const key = known?.name ?? tag;
const existing = usageMap.get(key);
if (existing) {
existing.count += count;
if (!existing.files.includes(safeRelative(dir, file))) existing.files.push(safeRelative(dir, file));
continue;
}
usageMap.set(key, {
name: known?.name ?? tag,
tag,
count,
files: [safeRelative(dir, file)]
});
}
}
return {
scannedFiles: files.length,
components: [...usageMap.values()].sort((left, right) => right.count - left.count || left.name.localeCompare(right.name)),
imports: [...imports].sort()
};
}
function lintProject(targetDir, version$1) {
const dir = resolve(targetDir);
const files = walkFiles(dir, [".vue"]);
const issues = [];
for (const file of files) {
const template = parse(readFileSync(file, "utf8"), { filename: file }).descriptor.template?.content ?? "";
for (const match of template.matchAll(TAG_RE)) {
const tag = match[1]?.toLowerCase();
if (!tag) continue;
if (!findComponent(tag, version$1)) issues.push({
file: safeRelative(dir, file),
line: getLineNumber(template, match.index ?? 0),
rule: "unknown-component",
severity: "warning",
message: `Unknown wot-ui component tag: ${tag}`
});
}
for (const match of template.matchAll(BUTTON_RE)) {
const attrs = (match[1] ?? match[3] ?? "").trim();
const body = (match[2] ?? "").replace(/<[^>]+>/g, "").trim();
if (!/\bicon\s*=/.test(attrs) && !body) issues.push({
file: safeRelative(dir, file),
line: getLineNumber(template, match.index ?? 0),
rule: "button-content",
severity: "warning",
message: "wd-button should include visible text content or an icon attribute."
});
const component = findComponent("wd-button", version$1);
for (const prop of component?.props ?? []) {
if (!prop.deprecated) continue;
if (!(/* @__PURE__ */ new RegExp(`\\b${prop.name}\\b`)).test(attrs)) continue;
issues.push({
file: safeRelative(dir, file),
line: getLineNumber(template, match.index ?? 0),
rule: "deprecated-prop",
severity: "warning",
message: prop.replacement ? `Deprecated prop ${prop.name} detected on wd-button. Use ${prop.replacement} instead.` : `Deprecated prop ${prop.name} detected on wd-button.`
});
}
}
}
return {
scannedFiles: files.length,
issues
};
}
//#endregion
export { resolveVersion as a, listComponents as i, lintProject as n, loadMetadataFile as o, findComponent as r, version as s, analyzeUsage as t };
import { a as resolveVersion, i as listComponents, n as lintProject, o as loadMetadataFile, r as findComponent, s as version } from "./scanner-DYthnCXr.mjs";
import process from "node:process";
import { McpServer, StdioServerTransport } from "@modelcontextprotocol/server";
import * as z from "zod/v4";
//#region src/mcp/prompts.ts
const WOT_EXPERT_PROMPT = [
"You are a wot-ui expert assistant.",
"Always query component metadata before generating code.",
"Prefer using wot_list, wot_info, wot_doc, and wot_token before writing UI code.",
"Assume only wot-ui v2 is supported by this server."
].join(" ");
const WOT_PAGE_GENERATOR_PROMPT = [
"Generate wot-ui pages by first collecting every relevant component API and CSS variable.",
"Prefer existing wd-* components and documented props over ad-hoc custom markup.",
"When theme customization is involved, inspect CSS variables with wot_token first."
].join(" ");
//#endregion
//#region src/mcp/tools.ts
function jsonText(value) {
return JSON.stringify(value, null, 2);
}
function registerMcpTools(server) {
server.registerTool("wot_list", {
description: "List available wot-ui components.",
inputSchema: z.object({ version: z.string().optional() }),
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false
}
}, async ({ version: version$1 }) => {
return { content: [{
type: "text",
text: jsonText({ components: listComponents(version$1) })
}] };
});
server.registerTool("wot_info", {
description: "Get props, events, slots, and CSS variables for a component.",
inputSchema: z.object({
component: z.string(),
version: z.string().optional()
}),
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false
}
}, async ({ component, version: version$1 }) => {
const result = findComponent(component, version$1);
if (!result) return {
isError: true,
content: [{
type: "text",
text: `Component not found: ${component}`
}]
};
return { content: [{
type: "text",
text: jsonText(result)
}] };
});
server.registerTool("wot_doc", {
description: "Get component markdown documentation.",
inputSchema: z.object({
component: z.string(),
version: z.string().optional()
}),
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false
}
}, async ({ component, version: version$1 }) => {
const result = findComponent(component, version$1);
if (!result?.doc) return {
isError: true,
content: [{
type: "text",
text: `Documentation not found: ${component}`
}]
};
return { content: [{
type: "text",
text: result.doc
}] };
});
server.registerTool("wot_demo", {
description: "Get component demo code or list demos.",
inputSchema: z.object({
component: z.string(),
demo: z.string().optional(),
version: z.string().optional()
}),
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false
}
}, async ({ component, demo, version: version$1 }) => {
const result = findComponent(component, version$1);
if (!result) return {
isError: true,
content: [{
type: "text",
text: `Component not found: ${component}`
}]
};
if (!demo) return { content: [{
type: "text",
text: jsonText({ demos: result.demos ?? [] })
}] };
const matched = result.demos?.find((item) => item.name.toLowerCase() === demo.toLowerCase());
if (!matched) return {
isError: true,
content: [{
type: "text",
text: `Demo not found: ${demo}`
}]
};
return { content: [{
type: "text",
text: jsonText(matched)
}] };
});
server.registerTool("wot_token", {
description: "Get component CSS variables.",
inputSchema: z.object({
component: z.string().optional(),
version: z.string().optional()
}),
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false
}
}, async ({ component, version: version$1 }) => {
if (!component) return { content: [{
type: "text",
text: jsonText({ components: listComponents(version$1).map((item) => ({
name: item.name,
cssVars: item.cssVars
})) })
}] };
const result = findComponent(component, version$1);
if (!result) return {
isError: true,
content: [{
type: "text",
text: `Component not found: ${component}`
}]
};
return { content: [{
type: "text",
text: jsonText({
name: result.name,
cssVars: result.cssVars
})
}] };
});
server.registerTool("wot_changelog", {
description: "Get changelog entries for the supported v2 dataset.",
inputSchema: z.object({
version: z.string().optional(),
component: z.string().optional()
}),
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false
}
}, async ({ version: version$1, component }) => {
return { content: [{
type: "text",
text: jsonText({ entries: (loadMetadataFile(resolveVersion(version$1)).changelog ?? []).filter((entry) => {
const versionMatches = version$1 ? entry.version === version$1 || `v${entry.version}` === version$1 : true;
const componentMatches = component ? (entry.components ?? []).some((item) => item.toLowerCase() === component.toLowerCase()) : true;
return versionMatches && componentMatches;
}) })
}] };
});
server.registerTool("wot_lint", {
description: "Lint a local project for wot-ui related issues.",
inputSchema: z.object({
dir: z.string().optional(),
version: z.string().optional()
}),
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true
}
}, async ({ dir, version: version$1 }) => {
return { content: [{
type: "text",
text: jsonText(lintProject(dir ?? process.cwd(), version$1))
}] };
});
}
//#endregion
//#region src/mcp/server.ts
async function startMcpServer() {
const server = new McpServer({
name: "wot-ui",
version
}, {
instructions: "Use wot-ui component tools before generating UI code. Only wot-ui v2 metadata is available in this server.",
capabilities: { logging: {} }
});
registerMcpTools(server);
server.registerPrompt("wot-expert", { description: "General wot-ui expert workflow." }, async () => ({ messages: [{
role: "assistant",
content: {
type: "text",
text: WOT_EXPERT_PROMPT
}
}] }));
server.registerPrompt("wot-page-generator", {
description: "Workflow for generating a wot-ui page.",
argsSchema: z.object({ goal: z.string().optional() })
}, async ({ goal }) => ({ messages: [{
role: "assistant",
content: {
type: "text",
text: goal ? `${WOT_PAGE_GENERATOR_PROMPT} Goal: ${goal}` : WOT_PAGE_GENERATOR_PROMPT
}
}] }));
const transport = new StdioServerTransport();
await server.connect(transport);
const shutdown = async () => {
await server.close();
process.exit(0);
};
process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);
}
//#endregion
export { startMcpServer };
+2
-2
#!/usr/bin/env node
import { a as resolveVersion, i as listComponents, n as lintProject, o as loadMetadataFile, r as findComponent, s as version, t as analyzeUsage } from "./scanner-B5dljsZu.mjs";
import { a as resolveVersion, i as listComponents, n as lintProject, o as loadMetadataFile, r as findComponent, s as version, t as analyzeUsage } from "./scanner-DYthnCXr.mjs";
import process from "node:process";

@@ -331,3 +331,3 @@ import { Command } from "commander";

program.command("mcp").description("Start the wot-ui MCP server").action(async () => {
const { startMcpServer } = await import("./server-Bn2u7Czs.mjs");
const { startMcpServer } = await import("./server-65ejz4L2.mjs");
await startMcpServer();

@@ -334,0 +334,0 @@ });

{
"name": "@wot-ui/cli",
"type": "module",
"version": "0.0.1-beta.7",
"version": "0.0.1-beta.8",
"description": "面向 wot-ui 的 CLI、MCP 与数据提取工具集",

@@ -45,2 +45,4 @@ "license": "MIT",

"@types/node": "^25.0.1",
"@vitest/coverage-v8": "4.0.15",
"baseline-browser-mapping": "^2.10.18",
"bumpp": "^10.3.2",

@@ -63,3 +65,3 @@ "eslint": "^9.39.2",

"lint-staged": {
"*": "eslint --fix"
"*": "eslint --fix --no-warn-ignored"
},

@@ -76,4 +78,3 @@ "scripts": {

"test": "vitest run",
"test:all": "pnpm test",
"test:cli": "pnpm test",
"test:coverage": "vitest run --coverage --coverage.reporter=lcov --coverage.reporter=text",
"test:watch": "vitest",

@@ -80,0 +81,0 @@ "typecheck": "tsc --noEmit",

@@ -1,2 +0,2 @@

# open-wot
# Open Wot

@@ -3,0 +3,0 @@ open-wot 是 wot-ui 的 AI 工具链仓库,当前对外发布的核心包为 `@wot-ui/cli`。它提供命令行工具、MCP Server、离线组件知识库与数据提取脚本,用于把 wot-ui v2 的组件知识接入编辑器、AI Agent 和本地工程分析流程。

import { existsSync, readFileSync, readdirSync } from "node:fs";
import { dirname, join, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { gunzipSync } from "node:zlib";
import { parse } from "@vue/compiler-sfc";
//#region package.json
var version = "0.0.1-beta.7";
//#endregion
//#region src/data/loader.ts
const currentDir = dirname(fileURLToPath(import.meta.url));
function resolveDataDir() {
const candidates = [
join(currentDir, "..", "data"),
join(currentDir, "..", "..", "data"),
join(currentDir, "data")
];
for (const candidate of candidates) if (existsSync(join(candidate, "versions.json")) || existsSync(join(candidate, "versions.json.gz"))) return candidate;
throw new Error("Unable to locate bundled data directory");
}
const dataDir = resolveDataDir();
function readJsonFile(baseName) {
const jsonPath = join(dataDir, `${baseName}.json`);
if (existsSync(jsonPath)) return JSON.parse(readFileSync(jsonPath, "utf8"));
const gzipPath = join(dataDir, `${baseName}.json.gz`);
if (existsSync(gzipPath)) {
const compressed = readFileSync(gzipPath);
return JSON.parse(gunzipSync(compressed).toString("utf8"));
}
throw new Error(`Data file not found for ${baseName}`);
}
function loadVersionsFile() {
return readJsonFile("versions");
}
function loadMetadataFile(versionKey) {
return readJsonFile(versionKey);
}
//#endregion
//#region src/data/version.ts
const VERSION_KEY = "v2";
function resolveVersion(requested) {
const versions = loadVersionsFile();
if (!requested) return VERSION_KEY;
const normalized = requested.trim();
if (versions.aliases[normalized]) return VERSION_KEY;
if (versions.supported.includes(normalized)) return VERSION_KEY;
if (normalized === VERSION_KEY) return VERSION_KEY;
throw new Error(`Unsupported wot-ui version: ${requested}`);
}
//#endregion
//#region src/data/metadata.ts
function loadResolvedMetadata(version$1) {
return loadMetadataFile(resolveVersion(version$1));
}
function listComponents(version$1) {
return loadResolvedMetadata(version$1).components;
}
function findComponent(name, version$1) {
const normalized = name.trim().toLowerCase();
return listComponents(version$1).find((component) => component.name.toLowerCase() === normalized || component.tag.toLowerCase() === normalized);
}
//#endregion
//#region src/utils/files.ts
const DEFAULT_IGNORES = new Set([
".git",
".idea",
".output",
".turbo",
".vscode",
"dist",
"build",
"coverage",
"node_modules"
]);
function walkFiles(rootDir, extensions) {
const results = [];
function visit(dir) {
for (const entry of readdirSync(dir, { withFileTypes: true })) {
if (DEFAULT_IGNORES.has(entry.name)) continue;
const fullPath = join(dir, entry.name);
if (entry.isDirectory()) {
visit(fullPath);
continue;
}
if (extensions.some((extension) => entry.name.endsWith(extension))) results.push(fullPath);
}
}
visit(rootDir);
return results;
}
function safeRelative(rootDir, filePath) {
return relative(rootDir, filePath) || ".";
}
//#endregion
//#region src/utils/scanner.ts
const IMPORT_RE = /from\s+['"]([^'"]*wot[^'"]*)['"]/g;
const TAG_RE = /<\s*(wd-[a-z0-9-]+)/gi;
const BUTTON_RE = /<wd-button\b([^>]*)>([\s\S]*?)<\/wd-button>|<wd-button\b([^>]*)\/>/gi;
function getLineNumber(source, index) {
return source.slice(0, index).split("\n").length;
}
function collectTemplateTags(content) {
const counts = /* @__PURE__ */ new Map();
for (const match of content.matchAll(TAG_RE)) {
const tag = match[1]?.toLowerCase();
if (!tag) continue;
counts.set(tag, (counts.get(tag) ?? 0) + 1);
}
return counts;
}
function collectImports(scriptContent) {
const imports = /* @__PURE__ */ new Set();
for (const match of scriptContent.matchAll(IMPORT_RE)) if (match[1]) imports.add(match[1]);
return [...imports];
}
function analyzeUsage(targetDir, version$1) {
const dir = resolve(targetDir);
const files = walkFiles(dir, [".vue"]);
const knownByTag = new Map(listComponents(version$1).map((component) => [component.tag.toLowerCase(), component]));
const usageMap = /* @__PURE__ */ new Map();
const imports = /* @__PURE__ */ new Set();
for (const file of files) {
const parsed = parse(readFileSync(file, "utf8"), { filename: file });
const template = parsed.descriptor.template?.content ?? "";
const script = [parsed.descriptor.script?.content ?? "", parsed.descriptor.scriptSetup?.content ?? ""].filter(Boolean).join("\n");
for (const item of collectImports(script)) imports.add(item);
for (const [tag, count] of collectTemplateTags(template)) {
const known = knownByTag.get(tag);
const key = known?.name ?? tag;
const existing = usageMap.get(key);
if (existing) {
existing.count += count;
if (!existing.files.includes(safeRelative(dir, file))) existing.files.push(safeRelative(dir, file));
continue;
}
usageMap.set(key, {
name: known?.name ?? tag,
tag,
count,
files: [safeRelative(dir, file)]
});
}
}
return {
scannedFiles: files.length,
components: [...usageMap.values()].sort((left, right) => right.count - left.count || left.name.localeCompare(right.name)),
imports: [...imports].sort()
};
}
function lintProject(targetDir, version$1) {
const dir = resolve(targetDir);
const files = walkFiles(dir, [".vue"]);
const issues = [];
for (const file of files) {
const template = parse(readFileSync(file, "utf8"), { filename: file }).descriptor.template?.content ?? "";
for (const match of template.matchAll(TAG_RE)) {
const tag = match[1]?.toLowerCase();
if (!tag) continue;
if (!findComponent(tag, version$1)) issues.push({
file: safeRelative(dir, file),
line: getLineNumber(template, match.index ?? 0),
rule: "unknown-component",
severity: "warning",
message: `Unknown wot-ui component tag: ${tag}`
});
}
for (const match of template.matchAll(BUTTON_RE)) {
const attrs = (match[1] ?? match[3] ?? "").trim();
const body = (match[2] ?? "").replace(/<[^>]+>/g, "").trim();
if (!/\bicon\s*=/.test(attrs) && !body) issues.push({
file: safeRelative(dir, file),
line: getLineNumber(template, match.index ?? 0),
rule: "button-content",
severity: "warning",
message: "wd-button should include visible text content or an icon attribute."
});
const component = findComponent("wd-button", version$1);
for (const prop of component?.props ?? []) {
if (!prop.deprecated) continue;
if (!(/* @__PURE__ */ new RegExp(`\\b${prop.name}\\b`)).test(attrs)) continue;
issues.push({
file: safeRelative(dir, file),
line: getLineNumber(template, match.index ?? 0),
rule: "deprecated-prop",
severity: "warning",
message: prop.replacement ? `Deprecated prop ${prop.name} detected on wd-button. Use ${prop.replacement} instead.` : `Deprecated prop ${prop.name} detected on wd-button.`
});
}
}
}
return {
scannedFiles: files.length,
issues
};
}
//#endregion
export { resolveVersion as a, listComponents as i, lintProject as n, loadMetadataFile as o, findComponent as r, version as s, analyzeUsage as t };
import { a as resolveVersion, i as listComponents, n as lintProject, o as loadMetadataFile, r as findComponent, s as version } from "./scanner-B5dljsZu.mjs";
import process from "node:process";
import { McpServer, StdioServerTransport } from "@modelcontextprotocol/server";
import * as z from "zod/v4";
//#region src/mcp/prompts.ts
const WOT_EXPERT_PROMPT = [
"You are a wot-ui expert assistant.",
"Always query component metadata before generating code.",
"Prefer using wot_list, wot_info, wot_doc, and wot_token before writing UI code.",
"Assume only wot-ui v2 is supported by this server."
].join(" ");
const WOT_PAGE_GENERATOR_PROMPT = [
"Generate wot-ui pages by first collecting every relevant component API and CSS variable.",
"Prefer existing wd-* components and documented props over ad-hoc custom markup.",
"When theme customization is involved, inspect CSS variables with wot_token first."
].join(" ");
//#endregion
//#region src/mcp/tools.ts
function jsonText(value) {
return JSON.stringify(value, null, 2);
}
function registerMcpTools(server) {
server.registerTool("wot_list", {
description: "List available wot-ui components.",
inputSchema: z.object({ version: z.string().optional() }),
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false
}
}, async ({ version: version$1 }) => {
return { content: [{
type: "text",
text: jsonText({ components: listComponents(version$1) })
}] };
});
server.registerTool("wot_info", {
description: "Get props, events, slots, and CSS variables for a component.",
inputSchema: z.object({
component: z.string(),
version: z.string().optional()
}),
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false
}
}, async ({ component, version: version$1 }) => {
const result = findComponent(component, version$1);
if (!result) return {
isError: true,
content: [{
type: "text",
text: `Component not found: ${component}`
}]
};
return { content: [{
type: "text",
text: jsonText(result)
}] };
});
server.registerTool("wot_doc", {
description: "Get component markdown documentation.",
inputSchema: z.object({
component: z.string(),
version: z.string().optional()
}),
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false
}
}, async ({ component, version: version$1 }) => {
const result = findComponent(component, version$1);
if (!result?.doc) return {
isError: true,
content: [{
type: "text",
text: `Documentation not found: ${component}`
}]
};
return { content: [{
type: "text",
text: result.doc
}] };
});
server.registerTool("wot_demo", {
description: "Get component demo code or list demos.",
inputSchema: z.object({
component: z.string(),
demo: z.string().optional(),
version: z.string().optional()
}),
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false
}
}, async ({ component, demo, version: version$1 }) => {
const result = findComponent(component, version$1);
if (!result) return {
isError: true,
content: [{
type: "text",
text: `Component not found: ${component}`
}]
};
if (!demo) return { content: [{
type: "text",
text: jsonText({ demos: result.demos ?? [] })
}] };
const matched = result.demos?.find((item) => item.name.toLowerCase() === demo.toLowerCase());
if (!matched) return {
isError: true,
content: [{
type: "text",
text: `Demo not found: ${demo}`
}]
};
return { content: [{
type: "text",
text: jsonText(matched)
}] };
});
server.registerTool("wot_token", {
description: "Get component CSS variables.",
inputSchema: z.object({
component: z.string().optional(),
version: z.string().optional()
}),
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false
}
}, async ({ component, version: version$1 }) => {
if (!component) return { content: [{
type: "text",
text: jsonText({ components: listComponents(version$1).map((item) => ({
name: item.name,
cssVars: item.cssVars
})) })
}] };
const result = findComponent(component, version$1);
if (!result) return {
isError: true,
content: [{
type: "text",
text: `Component not found: ${component}`
}]
};
return { content: [{
type: "text",
text: jsonText({
name: result.name,
cssVars: result.cssVars
})
}] };
});
server.registerTool("wot_changelog", {
description: "Get changelog entries for the supported v2 dataset.",
inputSchema: z.object({
version: z.string().optional(),
component: z.string().optional()
}),
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false
}
}, async ({ version: version$1, component }) => {
return { content: [{
type: "text",
text: jsonText({ entries: (loadMetadataFile(resolveVersion(version$1)).changelog ?? []).filter((entry) => {
const versionMatches = version$1 ? entry.version === version$1 || `v${entry.version}` === version$1 : true;
const componentMatches = component ? (entry.components ?? []).some((item) => item.toLowerCase() === component.toLowerCase()) : true;
return versionMatches && componentMatches;
}) })
}] };
});
server.registerTool("wot_lint", {
description: "Lint a local project for wot-ui related issues.",
inputSchema: z.object({
dir: z.string().optional(),
version: z.string().optional()
}),
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true
}
}, async ({ dir, version: version$1 }) => {
return { content: [{
type: "text",
text: jsonText(lintProject(dir ?? process.cwd(), version$1))
}] };
});
}
//#endregion
//#region src/mcp/server.ts
async function startMcpServer() {
const server = new McpServer({
name: "wot-ui",
version
}, {
instructions: "Use wot-ui component tools before generating UI code. Only wot-ui v2 metadata is available in this server.",
capabilities: { logging: {} }
});
registerMcpTools(server);
server.registerPrompt("wot-expert", { description: "General wot-ui expert workflow." }, async () => ({ messages: [{
role: "assistant",
content: {
type: "text",
text: WOT_EXPERT_PROMPT
}
}] }));
server.registerPrompt("wot-page-generator", {
description: "Workflow for generating a wot-ui page.",
argsSchema: z.object({ goal: z.string().optional() })
}, async ({ goal }) => ({ messages: [{
role: "assistant",
content: {
type: "text",
text: goal ? `${WOT_PAGE_GENERATOR_PROMPT} Goal: ${goal}` : WOT_PAGE_GENERATOR_PROMPT
}
}] }));
const transport = new StdioServerTransport();
await server.connect(transport);
const shutdown = async () => {
await server.close();
process.exit(0);
};
process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);
}
//#endregion
export { startMcpServer };