diff --git a/.gitignore b/.gitignore index 73787dbc..c78f3267 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ node_modules dist dist-lib -dist-mcp *.tsbuildinfo .DS_Store .claude/settings.local.json diff --git a/CLAUDE.md b/CLAUDE.md index 1c86c683..2f1ae4e7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -88,7 +88,7 @@ edits and checks. | Package | Published? | Description | | --- | --- | --- | | `packages/ghost` | yes: `@design-intelligence/ghost` | The public package. Ships the `ghost` CLI, node authoring, corpus validation, gather/pull/pulse, review packet assembly, and the unified skill bundle. Shared runtime lives in `packages/ghost/src/ghost-core`. | -| `packages/vessel-react` | no | A standalone shadcn component registry plus `vessel-mcp` MCP server: the opinionated default reference body. Design-system-agnostic; nothing in ghost requires it. | +| `packages/vessel-react` | no | A standalone shadcn component registry and reference component system: the opinionated default reference body. Design-system-agnostic; nothing in ghost requires it. | | `packages/vessel-light` | no | Vessel's design language as a portable `.ghost/` package for agents writing raw HTML/CSS. No build, no dependencies. | | `packages/steering-control` | no | Before/after evaluation harness: measures what handing an agent a `.ghost` package buys, as a deterministic `report.html`. | | `apps/docs` | no | Public thesis site. | diff --git a/README.md b/README.md index a84852a5..682b9299 100644 --- a/README.md +++ b/README.md @@ -171,7 +171,7 @@ The export audits `materials` entries and reports paths that moved. | Path | Role | Published? | | ---- | ---- | --- | | [`packages/ghost`](./packages/ghost) | The public `ghost` CLI, node authoring, package validation, gather/pull, review packet assembly, and the skill bundle. | yes: `@design-intelligence/ghost` | -| [`packages/vessel-react`](./packages/vessel-react) | A standalone shadcn component registry plus `vessel-mcp` MCP server. | no | +| [`packages/vessel-react`](./packages/vessel-react) | A standalone shadcn component registry and reference component system. | no | | [`packages/vessel-light`](./packages/vessel-light) | Vessel's design language as a portable `.ghost/` package for agents writing raw HTML/CSS. | no | | [`packages/steering-control`](./packages/steering-control) | Before/after evaluation harness: measures what a `.ghost` package buys as a self-contained `report.html`. | no | | [`apps/docs`](./apps/docs) | Public thesis site. | no | diff --git a/packages/vessel-react/README.md b/packages/vessel-react/README.md index 2eb4a50c..c544c3da 100644 --- a/packages/vessel-react/README.md +++ b/packages/vessel-react/README.md @@ -95,17 +95,6 @@ pnpm --filter @design-intelligence/vessel-react build:lib See [`apps/docs`](../../apps/docs) for the live component catalogue. -## MCP server - -`vessel` also ships a `vessel-mcp` bin — a Model Context Protocol server that re-exposes the component registry to AI assistants (Claude Code, Cursor, etc.) so they can discover and install components without a human in the loop. 5 tools, 2 resources. Source lives in `src/mcp/`, built separately via `tsconfig.mcp.json` → `dist-mcp/`. - -```bash -pnpm --filter @design-intelligence/vessel-react build:mcp -node packages/vessel-react/dist-mcp/bin.js # stdio server -``` - -Wire it into your MCP host by pointing at the `vessel-mcp` bin. - ## License Apache-2.0 diff --git a/packages/vessel-react/package.json b/packages/vessel-react/package.json index 1c141b9b..ede121df 100644 --- a/packages/vessel-react/package.json +++ b/packages/vessel-react/package.json @@ -6,9 +6,6 @@ "main": "./dist-lib/index.js", "module": "./dist-lib/index.js", "types": "./dist-lib/index.d.ts", - "bin": { - "vessel-mcp": "./dist-mcp/bin.js" - }, "exports": { ".": { "types": "./dist-lib/index.d.ts", @@ -21,7 +18,6 @@ }, "files": [ "dist-lib", - "dist-mcp", "src/styles", "registry.json" ], @@ -29,9 +25,8 @@ "**/*.css" ], "scripts": { - "build": "vite build --config vite.lib.config.ts && tsc --build tsconfig.lib.json && node scripts/resolve-dts-paths.mjs && tsc --build tsconfig.mcp.json", + "build": "vite build --config vite.lib.config.ts && tsc --build tsconfig.lib.json && node scripts/resolve-dts-paths.mjs", "build:lib": "vite build --config vite.lib.config.ts && tsc --build tsconfig.lib.json && node scripts/resolve-dts-paths.mjs", - "build:mcp": "tsc --build tsconfig.mcp.json", "build:base": "node scripts/build-base-vars.mjs", "build:presets": "node scripts/build-presets.mjs", "generate:skills": "node scripts/generate-skills.mjs", @@ -44,7 +39,6 @@ }, "dependencies": { "@hookform/resolvers": "^5.2.2", - "@modelcontextprotocol/sdk": "^1.29.0", "@radix-ui/react-accordion": "^1.2.12", "@radix-ui/react-alert-dialog": "^1.1.15", "@radix-ui/react-aspect-ratio": "^1.1.8", @@ -108,7 +102,6 @@ "tw-animate-css": "^1.4.0", "use-stick-to-bottom": "^1.1.3", "vaul": "^1.1.2", - "zod": "^4.3.6", "zustand": "^5.0.12" }, "peerDependencies": { diff --git a/packages/vessel-react/src/mcp/bin.ts b/packages/vessel-react/src/mcp/bin.ts deleted file mode 100644 index c20c22cf..00000000 --- a/packages/vessel-react/src/mcp/bin.ts +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env node -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { createServer } from "./server.js"; - -const server = createServer(); -const transport = new StdioServerTransport(); -await server.connect(transport); diff --git a/packages/vessel-react/src/mcp/data.ts b/packages/vessel-react/src/mcp/data.ts deleted file mode 100644 index 630652f9..00000000 --- a/packages/vessel-react/src/mcp/data.ts +++ /dev/null @@ -1,148 +0,0 @@ -import fs from "node:fs"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; - -const packageRoot = fileURLToPath(new URL("../", import.meta.url)); - -export interface RegistryFile { - type: string; - target: string; - path: string; -} - -export interface RegistryCssVars { - theme?: Record; - light?: Record; - dark?: Record; -} - -export interface RegistryItem { - name: string; - type: string; - title?: string; - description?: string; - author?: string; - style?: string; - iconLibrary?: string; - baseColor?: string; - dependencies?: string[]; - devDependencies?: string[]; - registryDependencies?: string[]; - files: RegistryFile[]; - categories?: string[]; - cssVars?: RegistryCssVars; -} - -export interface Registry { - $schema: string; - name: string; - homepage: string; - items: RegistryItem[]; -} - -const registryRaw = fs.readFileSync( - path.join(packageRoot, "registry.json"), - "utf-8", -); -const registry: Registry = JSON.parse(registryRaw); - -const skillsContent = fs.readFileSync( - path.join(packageRoot, ".shadcn", "skills.md"), - "utf-8", -); - -const itemsByName = new Map(); -for (const item of registry.items) { - itemsByName.set(item.name, item); -} - -export interface ItemSummary { - name: string; - type: string; - categories: string[]; - dependencies: string[]; - registryDependencies: string[]; - description?: string; -} - -export function searchItems( - query?: string, - category?: string, - aiOnly?: boolean, -): ItemSummary[] { - let items = registry.items; - - if (category) { - const lower = category.toLowerCase(); - items = items.filter((i) => - (i.categories ?? []).some((c) => c.toLowerCase() === lower), - ); - } - - if (aiOnly) { - items = items.filter((i) => (i.categories ?? []).includes("ai")); - } - - if (query) { - const lower = query.toLowerCase(); - items = items.filter((i) => i.name.toLowerCase().includes(lower)); - } - - return items.map((i) => ({ - name: i.name, - type: i.type, - categories: i.categories ?? [], - dependencies: i.dependencies ?? [], - registryDependencies: i.registryDependencies ?? [], - description: i.description, - })); -} - -export function getRegistryItem(name: string): RegistryItem | undefined { - return itemsByName.get(name); -} - -export function getCategoriesWithCounts(): Record< - string, - { name: string; count: number } -> { - const result: Record = {}; - for (const item of registry.items) { - for (const cat of item.categories ?? []) { - if (result[cat]) { - result[cat].count++; - } else { - result[cat] = { name: cat, count: 1 }; - } - } - } - return result; -} - -export function getThemePreset(name: string): RegistryCssVars | undefined { - const item = registry.items.find( - (i) => i.type === "registry:theme" && i.name === name, - ); - return item?.cssVars; -} - -export function getComponentSource(name: string): string | null { - const item = itemsByName.get(name); - if (!item || item.files.length === 0) return null; - - const fullPath = path.join(packageRoot, item.files[0].path); - - try { - return fs.readFileSync(fullPath, "utf-8"); - } catch { - return null; - } -} - -export function getRegistryRaw(): string { - return registryRaw; -} - -export function getSkills(): string { - return skillsContent; -} diff --git a/packages/vessel-react/src/mcp/resources.ts b/packages/vessel-react/src/mcp/resources.ts deleted file mode 100644 index e22f5f98..00000000 --- a/packages/vessel-react/src/mcp/resources.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { getRegistryRaw, getSkills } from "./data.js"; - -export function registerResources(server: McpServer): void { - server.resource( - "ghost UI Registry", - "ghost://registry", - { mimeType: "application/json" }, - async () => ({ - contents: [ - { - uri: "ghost://registry", - mimeType: "application/json", - text: getRegistryRaw(), - }, - ], - }), - ); - - server.resource( - "ghost UI Skills", - "ghost://skills", - { mimeType: "text/markdown" }, - async () => ({ - contents: [ - { - uri: "ghost://skills", - mimeType: "text/markdown", - text: getSkills(), - }, - ], - }), - ); -} diff --git a/packages/vessel-react/src/mcp/server.ts b/packages/vessel-react/src/mcp/server.ts deleted file mode 100644 index 188d4834..00000000 --- a/packages/vessel-react/src/mcp/server.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { registerResources } from "./resources.js"; -import { registerTools } from "./tools.js"; - -export function createServer(): McpServer { - const server = new McpServer({ - name: "vessel", - version: "0.1.0", - }); - - registerTools(server); - registerResources(server); - - return server; -} diff --git a/packages/vessel-react/src/mcp/tools.ts b/packages/vessel-react/src/mcp/tools.ts deleted file mode 100644 index f2bcc9d4..00000000 --- a/packages/vessel-react/src/mcp/tools.ts +++ /dev/null @@ -1,137 +0,0 @@ -import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { z } from "zod"; -import { - getCategoriesWithCounts, - getComponentSource, - getRegistryItem, - getThemePreset, - searchItems, -} from "./data.js"; - -const REGISTRY_URL = "https://block.github.io/ghost/r/registry.json"; - -export function registerTools(server: McpServer): void { - server.tool( - "search_components", - "Search ghost UI components by name, category, or AI filter", - { - query: z - .string() - .optional() - .describe("Substring to match against component names"), - category: z - .string() - .optional() - .describe("Filter by category (e.g. input, layout, ai)"), - aiOnly: z - .boolean() - .optional() - .describe("Only return AI-category components"), - }, - async ({ query, category, aiOnly }) => { - const results = searchItems(query, category, aiOnly); - return { - content: [ - { - type: "text" as const, - text: JSON.stringify(results, null, 2), - }, - ], - }; - }, - ); - - server.tool( - "get_component", - "Get full metadata and source code for a ghost UI component", - { - name: z.string().describe("Component name from the registry"), - }, - async ({ name }) => { - const item = getRegistryItem(name); - if (!item) { - return { - content: [ - { - type: "text" as const, - text: JSON.stringify({ error: `Component "${name}" not found` }), - }, - ], - }; - } - const source = getComponentSource(name); - return { - content: [ - { - type: "text" as const, - text: JSON.stringify({ ...item, source }, null, 2), - }, - ], - }; - }, - ); - - server.tool( - "get_install_command", - "Generate a shadcn install command for ghost UI components", - { - components: z - .array(z.string()) - .describe("Array of component names to install"), - }, - async ({ components }) => { - const cmd = `npx shadcn@latest add --registry ${REGISTRY_URL} ${components.join(" ")}`; - return { - content: [{ type: "text" as const, text: cmd }], - }; - }, - ); - - server.tool( - "list_categories", - "List all ghost UI component categories with counts", - {}, - async () => { - const categories = getCategoriesWithCounts(); - return { - content: [ - { - type: "text" as const, - text: JSON.stringify(categories, null, 2), - }, - ], - }; - }, - ); - - server.tool( - "get_theme", - "Get CSS variables for a ghost UI theme preset", - { - name: z.string().describe("Theme preset name"), - }, - async ({ name }) => { - const cssVars = getThemePreset(name); - if (!cssVars) { - return { - content: [ - { - type: "text" as const, - text: JSON.stringify({ - error: `Theme "${name}" not found`, - }), - }, - ], - }; - } - return { - content: [ - { - type: "text" as const, - text: JSON.stringify(cssVars, null, 2), - }, - ], - }; - }, - ); -} diff --git a/packages/vessel-react/tsconfig.mcp.json b/packages/vessel-react/tsconfig.mcp.json deleted file mode 100644 index abfa7cd3..00000000 --- a/packages/vessel-react/tsconfig.mcp.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "composite": true, - "outDir": "./dist-mcp", - "rootDir": "./src/mcp" - }, - "include": ["src/mcp/**/*.ts"] -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 490f895d..5682f802 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -289,9 +289,6 @@ importers: '@hookform/resolvers': specifier: ^5.2.2 version: 5.2.2(react-hook-form@7.72.1(react@19.1.0)) - '@modelcontextprotocol/sdk': - specifier: ^1.29.0 - version: 1.29.0(zod@4.3.6) '@radix-ui/react-accordion': specifier: ^1.2.12 version: 1.2.12(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -481,9 +478,6 @@ importers: vaul: specifier: ^1.1.2 version: 1.1.2(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - zod: - specifier: ^4.3.6 - version: 4.3.6 zustand: specifier: ^5.0.12 version: 5.0.12(@types/react@19.1.2)(immer@11.1.4)(react@19.1.0)(use-sync-external-store@1.6.0(react@19.1.0)) @@ -6461,28 +6455,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@modelcontextprotocol/sdk@1.29.0(zod@4.3.6)': - dependencies: - '@hono/node-server': 1.19.14(hono@4.12.14) - ajv: 8.18.0 - ajv-formats: 3.0.1(ajv@8.18.0) - content-type: 1.0.5 - cors: 2.8.6 - cross-spawn: 7.0.6 - eventsource: 3.0.7 - eventsource-parser: 3.0.8 - express: 5.2.1 - express-rate-limit: 8.3.2(express@5.2.1) - hono: 4.12.14 - jose: 6.2.2 - json-schema-typed: 8.0.2 - pkce-challenge: 5.0.1 - raw-body: 3.0.2 - zod: 4.3.6 - zod-to-json-schema: 3.25.2(zod@4.3.6) - transitivePeerDependencies: - - supports-color - '@mswjs/interceptors@0.41.4': dependencies: '@open-draft/deferred-promise': 2.2.0 @@ -11281,10 +11253,6 @@ snapshots: dependencies: zod: 3.25.76 - zod-to-json-schema@3.25.2(zod@4.3.6): - dependencies: - zod: 4.3.6 - zod@3.25.76: {} zod@4.3.6: {} diff --git a/tsconfig.json b/tsconfig.json index 1fd4370a..ec5b4e73 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,8 +12,5 @@ "forceConsistentCasingInFileNames": true }, "files": [], - "references": [ - { "path": "packages/ghost" }, - { "path": "packages/vessel-react/tsconfig.mcp.json" } - ] + "references": [{ "path": "packages/ghost" }] }