MCP inspect tool

This commit is contained in:
Mats Bosson 2026-03-29 23:49:50 +07:00
parent 9f78750105
commit 5e66d6fae1
2 changed files with 86 additions and 0 deletions

View File

@ -0,0 +1,41 @@
import type { ComponentRegistry } from "../registry.js";
interface InspectInput { component: string; }
interface InspectResult {
name: string; description: string; exportPath: string;
parts: readonly string[]; requiredParts: readonly string[];
props: Record<string, unknown>; hasStyledVersion: boolean; example: string;
}
function toKebab(str: string): string {
return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
}
function generateExample(name: string, parts: readonly string[], requiredParts: readonly string[]): string {
const lines: string[] = [];
lines.push(`import { ${name} } from "pettyui/${toKebab(name)}";`);
lines.push("");
if (requiredParts.includes("Root") || parts.includes("Root")) {
lines.push(`<${name}>`);
for (const part of requiredParts) {
if (part === "Root") continue;
lines.push(` <${name}.${part}>...</${name}.${part}>`);
}
lines.push(`</${name}>`);
} else {
lines.push(`<${name} />`);
}
return lines.join("\n");
}
/** Returns full component schema, parts, and usage example. */
export function handleInspect(registry: ComponentRegistry, input: InspectInput): InspectResult | null {
const comp = registry.getComponent(input.component);
if (!comp) return null;
return {
name: comp.meta.name, description: comp.meta.description, exportPath: comp.exportPath,
parts: comp.meta.parts, requiredParts: comp.meta.requiredParts,
props: comp.jsonSchemas, hasStyledVersion: comp.hasStyledVersion,
example: generateExample(comp.meta.name, comp.meta.parts, comp.meta.requiredParts),
};
}

View File

@ -0,0 +1,45 @@
import { describe, it, expect } from "vitest";
import { handleInspect } from "../../src/tools/inspect.js";
import { ComponentRegistry } from "../../src/registry.js";
describe("pettyui.inspect", () => {
const registry = new ComponentRegistry();
it("returns full component info", () => {
const result = handleInspect(registry, { component: "Dialog" });
expect(result).not.toBeNull();
expect(result?.name).toBe("Dialog");
expect(result?.parts).toContain("Root");
expect(result?.parts).toContain("Content");
expect(result?.exportPath).toBe("pettyui/dialog");
expect(typeof result?.description).toBe("string");
expect(result?.description.length).toBeGreaterThan(0);
});
it("includes JSON schema for props", () => {
const result = handleInspect(registry, { component: "Dialog" });
expect(result).not.toBeNull();
expect(result?.props).toBeDefined();
const dialogRoot = result?.props["dialogRoot"] as { type?: string } | undefined;
expect(dialogRoot).toBeDefined();
expect(dialogRoot?.type).toBe("object");
});
it("includes minimal example", () => {
const result = handleInspect(registry, { component: "Dialog" });
expect(result).not.toBeNull();
expect(result?.example).toContain("Dialog");
expect(result?.example).toContain('from "pettyui/dialog"');
});
it("returns null for unknown component", () => {
const result = handleInspect(registry, { component: "FakeComponent" });
expect(result).toBeNull();
});
it("is case-insensitive", () => {
const result = handleInspect(registry, { component: "dialog" });
expect(result).not.toBeNull();
expect(result?.name).toBe("Dialog");
});
});