From caf0f6c01d020c0eb87f4a317e4868594bbd6bab Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 05:42:50 +0000 Subject: [PATCH 1/7] Turn the fork into the Mobbin agent plugin Replace the upstream agent-plugins-example contents with Mobbin's own plugin: the hosted MCP server over Streamable HTTP plus a design-research skill, and a validator/CI that enforces Agent Plugins 1.0.0 conformance. Refs PRJ-2411 Co-Authored-By: xingxiang --- .github/workflows/validate.yml | 20 ++ .gitignore | 1 + README.md | 110 ++++------ mcp.json | 9 + package-lock.json | 107 ++++++++++ package.json | 13 ++ plugin.json | 14 +- schemas/1.0.0/mcp.schema.json | 120 +++++++++++ schemas/1.0.0/plugin.schema.json | 65 ++++++ scripts/validate.mjs | 193 ++++++++++++++++++ skills/migrate-agent-plugin/SKILL.md | 68 ------ .../references/client-extensions.md | 76 ------- .../references/migration-guide.md | 114 ----------- .../references/validation-checklist.md | 49 ----- skills/mobbin-design-research/SKILL.md | 45 ++++ 15 files changed, 624 insertions(+), 380 deletions(-) create mode 100644 .github/workflows/validate.yml create mode 100644 .gitignore create mode 100644 mcp.json create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 schemas/1.0.0/mcp.schema.json create mode 100644 schemas/1.0.0/plugin.schema.json create mode 100644 scripts/validate.mjs delete mode 100644 skills/migrate-agent-plugin/SKILL.md delete mode 100644 skills/migrate-agent-plugin/references/client-extensions.md delete mode 100644 skills/migrate-agent-plugin/references/migration-guide.md delete mode 100644 skills/migrate-agent-plugin/references/validation-checklist.md create mode 100644 skills/mobbin-design-research/SKILL.md diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 0000000..9e9ff1f --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,20 @@ +name: Validate + +on: + push: + pull_request: + +permissions: + contents: read + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + - run: npm ci + - run: npm run validate diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/README.md b/README.md index e4c6928..b8bfd44 100644 --- a/README.md +++ b/README.md @@ -1,87 +1,59 @@ -# Agent Plugins Example +# Mobbin Agent Plugin -A copyable reference package for the [Agent Plugins Specification v1.0.0](https://agent-plugins.org/specification). +The Mobbin Agent Plugin packages Mobbin's hosted MCP server and a design-research skill for Agent +Plugins 1.0.0 clients. It helps agents find real-world UI screens, multi-step flows, and website +sections from Mobbin's design library. -This repository demonstrates the portable core and includes an Agent Skill that helps migrate existing plugins from client-specific formats. It is a reference example, not a substitute for the normative specification; if they differ, the specification wins. +## Install -## Layout - -```text -agent-plugins-example/ -├── plugin.json # Required portable manifest -├── skills/ # Portable Agent Skills -│ └── migrate-agent-plugin/ -│ ├── SKILL.md -│ └── references/ -│ ├── client-extensions.md -│ ├── migration-guide.md -│ └── validation-checklist.md -├── README.md -└── LICENSE +```bash +npx plugins add mobbin/mobbin-agent-plugin ``` -The smallest valid plugin needs only `plugin.json`. This example includes a skill so it is useful as well as structurally illustrative. +Any client that supports Agent Plugins 1.0.0 can load it — ChatGPT and Codex, Cursor, GitHub +Copilot, Kiro, and VS Code at the specification's launch. + +Several clients also offer a native Mobbin setup path that predates this plugin, which stays +supported: [ChatGPT](https://docs.mobbin.com/mcp/clients/chatgpt), +[Cursor](https://docs.mobbin.com/mcp/clients/cursor), +[VS Code](https://docs.mobbin.com/mcp/clients/vscode), +[Codex CLI](https://docs.mobbin.com/mcp/clients/codex-cli), and +[others](https://docs.mobbin.com/mcp/clients/other). -## The portable core +On first use, the client opens a browser for OAuth authorization. You need a Mobbin account on a +Pro, Team, or Enterprise plan. The plugin contains no API key or other credential. -Agent Plugins v1 defines two portable component types: +## What it installs -- Agent Skills under immediate child directories of `skills/`. -- MCP servers in an optional root `mcp.json` using the matching v1.0.0 MCP schema. +- The `mobbin` MCP server at `https://api.mobbin.com/mcp` using Streamable HTTP. +- The `mobbin-design-research` skill, which teaches agents when and how to use Mobbin's three + search tools. -The root `plugin.json` schema is closed. Do not add `hooks`, `agents`, `commands`, `mcpServers`, `lspServers`, or arbitrary client fields at its top level. Put portable MCP configuration in `mcp.json` and client-owned manifest data inside `extensions`. +Mobbin provides these read-only MCP tools: -See the authoritative documentation for the [plugin manifest](https://agent-plugins.org/plugin-authors/manifest), [skills](https://agent-plugins.org/plugin-authors/skills), [MCP servers](https://agent-plugins.org/plugin-authors/mcp-servers), and [client extensions](https://agent-plugins.org/plugin-authors/client-extensions). +- `search_screens` — UI screens. +- `search_flows` — multi-step user journeys such as onboarding and checkout. +- `search_sections` — website sections such as pricing pages, heroes, and footers. -## Add client-specific capabilities safely +Results include images, metadata, and links back to Mobbin. Agents should inspect the images before +recommending references and cite each mentioned screen with its `mobbin_url`. -Hooks and similar capabilities are not portable v1 components. A client can add them through a reverse-domain extension namespace it owns and documents: +## Layout ```text -your-plugin/ +mobbin-agent-plugin/ ├── plugin.json +├── mcp.json ├── skills/ -├── mcp.json # Optional portable MCP configuration -└── com.vendor.client/ # Optional client extension - └── hooks/ - └── hooks.json -``` - -If a client uses both manifest data and files, `plugin.json` can contain an extension object: - -```json -{ - "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", - "name": "your-plugin", - "extensions": { - "com.vendor.client": { - "settingDefinedByThatClient": true - } - } -} +│ └── mobbin-design-research/ +│ └── SKILL.md +├── schemas/1.0.0/ +│ ├── plugin.schema.json +│ └── mcp.schema.json +├── scripts/ +│ └── validate.mjs +└── .github/workflows/validate.yml ``` -`com.vendor.client` is illustrative. Use only a namespace and fields documented by the client that owns them. Other clients ignore an extension they do not implement, leaving the portable skills and MCP configuration usable. - -## Migrate without breaking the existing plugin - -Use an additive migration: - -1. Add and validate the root `plugin.json` without deleting working platform files. -2. Move or copy reusable skills into `skills//SKILL.md`. -3. Convert portable MCP servers to root `mcp.json` with explicit transport types. -4. Keep hooks, agents, commands, LSP, UI, and marketplace metadata in a client extension or a separate compatibility package required by that platform. -5. Test the portable core and every supported client package before removing legacy files. - -The included [`migrate-agent-plugin` skill](skills/migrate-agent-plugin/SKILL.md) contains the full migration workflow, artifact mapping, extension strategy, and validation checklist. - -## Copy this example - -1. Copy this directory and rename it to match your plugin. -2. Update `name`, `version`, `description`, `author`, and other allowed metadata in `plugin.json`. -3. Replace the example skill or add more immediate children under `skills/`. -4. Add `mcp.json` only if the plugin provides MCP servers. -5. Add client extensions only for namespaces implemented by your target clients. -6. Validate paths, schemas, skills, and each supported client integration. - -The directory name and manifest name do not have to match under the portable specification, but keeping them identical is strongly recommended for predictable packaging and discovery. +Read the [Mobbin MCP introduction](https://docs.mobbin.com/mcp/introduction) for client setup and +authorization details. diff --git a/mcp.json b/mcp.json new file mode 100644 index 0000000..4fc355d --- /dev/null +++ b/mcp.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json", + "mcpServers": { + "mobbin": { + "type": "streamable-http", + "url": "https://api.mobbin.com/mcp" + } + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..2d7750e --- /dev/null +++ b/package-lock.json @@ -0,0 +1,107 @@ +{ + "name": "mobbin-agent-plugin", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "mobbin-agent-plugin", + "devDependencies": { + "ajv": "8.20.0", + "ajv-formats": "3.0.1", + "yaml": "2.9.0" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", + "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..2ad7677 --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "name": "mobbin-agent-plugin", + "private": true, + "type": "module", + "scripts": { + "validate": "node scripts/validate.mjs" + }, + "devDependencies": { + "ajv": "8.20.0", + "ajv-formats": "3.0.1", + "yaml": "2.9.0" + } +} diff --git a/plugin.json b/plugin.json index e422ca8..c7b7512 100644 --- a/plugin.json +++ b/plugin.json @@ -1,8 +1,14 @@ { "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", - "name": "agent-plugins-example", - "version": "1.0.0", - "description": "A copyable reference package and migration guide for Agent Plugins v1.0.0.", + "name": "mobbin", + "version": "0.1.0", + "description": "Search Mobbin's real-world UI design library through MCP and design research guidance.", + "author": { + "name": "Mobbin", + "url": "https://mobbin.com" + }, + "homepage": "https://docs.mobbin.com/mcp/introduction", + "repository": "https://github.com/mobbin/mobbin-agent-plugin", "license": "MIT", - "keywords": ["agent-plugins", "example", "migration", "skills"] + "keywords": ["mobbin", "design-research", "ui-design", "ux-design", "mcp"] } diff --git a/schemas/1.0.0/mcp.schema.json b/schemas/1.0.0/mcp.schema.json new file mode 100644 index 0000000..a9139a4 --- /dev/null +++ b/schemas/1.0.0/mcp.schema.json @@ -0,0 +1,120 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json", + "title": "Agent Plugins MCP Configuration", + "description": "Machine-readable schema for mcp.json in Agent Plugins 1.0.0. The Agent Plugins specification defines additional semantic and operational requirements.", + "type": "object", + "properties": { + "$schema": { + "const": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json", + "description": "Canonical identifier of the MCP configuration schema for the Agent Plugins version targeted by this document." + }, + "mcpServers": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/server" + } + } + }, + "required": ["$schema", "mcpServers"], + "additionalProperties": false, + "$defs": { + "server": { + "title": "MCP server", + "oneOf": [ + { + "$ref": "#/$defs/stdioServer" + }, + { + "$ref": "#/$defs/streamableHttpServer" + }, + { + "$ref": "#/$defs/sseServer" + } + ] + }, + "stdioServer": { + "title": "stdio MCP server", + "type": "object", + "properties": { + "type": { + "const": "stdio" + }, + "command": { + "type": "string", + "minLength": 1, + "description": "Executable token. Resolution rules are defined by the Agent Plugins specification." + }, + "args": { + "type": "array", + "items": { + "type": "string" + } + }, + "env": { + "type": "object", + "propertyNames": { + "not": { + "enum": ["PLUGIN_ROOT", "PLUGIN_DATA"] + } + }, + "additionalProperties": { + "type": "string" + } + }, + "cwd": { + "type": "string", + "pattern": "^(?:\\./|\\$\\{PLUGIN_ROOT\\}(?:/|$)|\\$\\{PLUGIN_DATA\\}(?:/|$))", + "description": "Plugin-relative, PLUGIN_ROOT-rooted, or PLUGIN_DATA-rooted working directory. Filesystem containment is validated separately." + } + }, + "required": ["type", "command"], + "additionalProperties": false + }, + "streamableHttpServer": { + "title": "Streamable HTTP MCP server", + "type": "object", + "properties": { + "type": { + "const": "streamable-http" + }, + "url": { + "type": "string", + "minLength": 1, + "description": "MCP endpoint URL. URL semantics are defined by the Agent Plugins specification." + }, + "headers": { + "$ref": "#/$defs/headers" + } + }, + "required": ["type", "url"], + "additionalProperties": false + }, + "sseServer": { + "title": "Legacy HTTP+SSE MCP server", + "type": "object", + "properties": { + "type": { + "const": "sse" + }, + "url": { + "type": "string", + "minLength": 1, + "description": "MCP endpoint URL. URL semantics are defined by the Agent Plugins specification." + }, + "headers": { + "$ref": "#/$defs/headers" + } + }, + "required": ["type", "url"], + "additionalProperties": false + }, + "headers": { + "title": "HTTP headers", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } +} diff --git a/schemas/1.0.0/plugin.schema.json b/schemas/1.0.0/plugin.schema.json new file mode 100644 index 0000000..8fed0e1 --- /dev/null +++ b/schemas/1.0.0/plugin.schema.json @@ -0,0 +1,65 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", + "title": "Agent Plugins Manifest", + "description": "Machine-readable schema for plugin.json in Agent Plugins 1.0.0. The Agent Plugins specification defines additional semantic and operational requirements.", + "type": "object", + "properties": { + "$schema": { + "const": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", + "description": "Canonical identifier of the plugin manifest schema for the Agent Plugins version targeted by this document." + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64, + "pattern": "^(?!.*(?:--|\\.\\.))[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$", + "description": "Human-readable plugin name." + }, + "version": { + "type": "string" + }, + "description": { + "type": "string" + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "homepage": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "license": { + "type": "string" + }, + "keywords": { + "type": "array", + "items": { + "type": "string" + } + }, + "extensions": { + "type": "object", + "description": "Client-specific manifest data keyed by reverse-domain extension namespace. Agent Plugins assigns no semantics to namespace object contents.", + "additionalProperties": { + "type": "object" + } + } + }, + "required": ["$schema", "name"], + "additionalProperties": false +} diff --git a/scripts/validate.mjs b/scripts/validate.mjs new file mode 100644 index 0000000..0d3292f --- /dev/null +++ b/scripts/validate.mjs @@ -0,0 +1,193 @@ +import Ajv from "ajv/dist/2020.js"; +import addFormats from "ajv-formats"; +import { readFile, readdir, realpath } from "node:fs/promises"; +import { createRequire } from "node:module"; +import { dirname, relative, resolve } from "node:path"; +import process from "node:process"; +import YAML from "yaml"; + +const require = createRequire(import.meta.url); +const root = resolve(dirname(new URL(import.meta.url).pathname), ".."); +const errors = []; + +function fail(message) { + errors.push(message); +} + +async function readJson(path) { + try { + return JSON.parse(await readFile(resolve(root, path), "utf8")); + } catch (error) { + fail(`${path}: ${error.message}`); + return null; + } +} + +function validateSchema(schemaPath, data, label) { + if (data === null) return; + const schema = require(resolve(root, schemaPath)); + const ajv = new Ajv({ allErrors: true, strict: true }); + addFormats(ajv); + const valid = ajv.validate(schema, data); + if (!valid) { + for (const error of ajv.errors ?? []) { + fail(`${label}: ${error.instancePath || "/"} ${error.message}`); + } + } +} + +function isObject(value) { + return value !== null && typeof value === "object" && !Array.isArray(value); +} + +function validateSkillFrontmatter(path, source) { + if (!source.startsWith("---\n")) { + fail(`${path}: missing YAML frontmatter`); + return; + } + const end = source.indexOf("\n---", 4); + if (end === -1) { + fail(`${path}: unterminated YAML frontmatter`); + return; + } + + let frontmatter; + try { + frontmatter = YAML.parse(source.slice(4, end)); + } catch (error) { + fail(`${path}: invalid YAML frontmatter: ${error.message}`); + return; + } + if (!isObject(frontmatter)) { + fail(`${path}: frontmatter must be a YAML mapping`); + return; + } + + const allowed = new Set([ + "name", + "description", + "license", + "compatibility", + "metadata", + "allowed-tools", + ]); + for (const key of Object.keys(frontmatter)) { + if (!allowed.has(key)) fail(`${path}: unsupported frontmatter field "${key}"`); + } + + const skillDir = path.split("/").at(-2); + const name = frontmatter.name; + if ( + typeof name !== "string" || + name.length < 1 || + name.length > 64 || + !/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(name) || + name.includes("--") + ) { + fail(`${path}: name must be 1-64 lowercase letters, numbers, and single hyphens`); + } else if (name !== skillDir) { + fail(`${path}: name must match parent directory "${skillDir}"`); + } + + const description = frontmatter.description; + if (typeof description !== "string" || description.trim() === "" || description.length > 1024) { + fail(`${path}: description must be non-empty and at most 1024 characters`); + } + if (frontmatter.license !== undefined && typeof frontmatter.license !== "string") { + fail(`${path}: license must be a string`); + } + if ( + frontmatter.compatibility !== undefined && + (typeof frontmatter.compatibility !== "string" || frontmatter.compatibility.length > 500) + ) { + fail(`${path}: compatibility must be a string of at most 500 characters`); + } + if (frontmatter.metadata !== undefined) { + if (!isObject(frontmatter.metadata)) fail(`${path}: metadata must be a mapping`); + else { + for (const [key, value] of Object.entries(frontmatter.metadata)) { + if (typeof key !== "string" || typeof value !== "string") { + fail(`${path}: metadata keys and values must be strings`); + } + } + } + } + if (frontmatter["allowed-tools"] !== undefined && typeof frontmatter["allowed-tools"] !== "string") { + fail(`${path}: allowed-tools must be a space-separated string`); + } +} + +async function validatePackagePaths() { + const rootReal = await realpath(root); + async function walk(path) { + for (const entry of await readdir(path, { withFileTypes: true })) { + if (entry.name === ".git" || entry.name === "node_modules") continue; + const current = resolve(path, entry.name); + let target; + try { + target = await realpath(current); + } catch (error) { + fail(`package path ${relative(root, current)} cannot be resolved: ${error.message}`); + continue; + } + if (relative(rootReal, target).startsWith("..")) { + fail(`package path escapes plugin root: ${relative(root, current)} -> ${target}`); + } + if (entry.isDirectory() && !entry.isSymbolicLink()) await walk(current); + } + } + await walk(root); +} + +function validateConfigSafety(mcp) { + for (const [name, server] of Object.entries(mcp?.mcpServers ?? {})) { + if (server.cwd !== undefined && !server.cwd.startsWith("./")) { + fail(`mcpServers.${name}.cwd must start with "./"`); + } + if (server.headers) { + for (const [header, value] of Object.entries(server.headers)) { + const key = header.toLowerCase(); + if (/(authorization|api[-_]?key|token|secret|password|credential|cookie|session)/i.test(key)) { + fail(`mcpServers.${name}.headers.${header} looks like a credential header`); + } + if (/(bearer\s|basic\s|sk[-_]|secret|password|token|\$\{|<[^>]+>)/i.test(value)) { + fail(`mcpServers.${name}.headers.${header} looks like a credential value`); + } + } + } + } +} + +const plugin = await readJson("plugin.json"); +const mcp = await readJson("mcp.json"); +validateSchema("schemas/1.0.0/plugin.schema.json", plugin, "plugin.json"); +validateSchema("schemas/1.0.0/mcp.schema.json", mcp, "mcp.json"); +validateConfigSafety(mcp); +await validatePackagePaths(); + +try { + for (const entry of await readdir(resolve(root, "skills"), { withFileTypes: true })) { + if (!entry.isDirectory()) continue; + const path = `skills/${entry.name}/SKILL.md`; + try { + validateSkillFrontmatter(path, await readFile(resolve(root, path), "utf8")); + } catch (error) { + fail(`${path}: ${error.message}`); + } + } +} catch (error) { + fail(`skills/: ${error.message}`); +} + +if (errors.length > 0) { + console.error(`FAIL: ${errors.length} validation error${errors.length === 1 ? "" : "s"}`); + for (const error of errors) console.error(`- ${error}`); + process.exitCode = 1; +} else { + console.log("PASS: plugin.json schema"); + console.log("PASS: mcp.json schema"); + console.log("PASS: skill frontmatter"); + console.log("PASS: package path containment"); + console.log("PASS: MCP header credential safety"); + console.log("Validation passed."); +} diff --git a/skills/migrate-agent-plugin/SKILL.md b/skills/migrate-agent-plugin/SKILL.md deleted file mode 100644 index 7fa96b5..0000000 --- a/skills/migrate-agent-plugin/SKILL.md +++ /dev/null @@ -1,68 +0,0 @@ ---- -name: migrate-agent-plugin -description: Migrate an existing Claude, Copilot, Codex, Cursor, Kiro, VS Code, or other client-specific agent plugin to the portable Agent Plugins v1 structure while preserving platform-specific hooks, agents, commands, LSP, UI, and marketplace behavior. Use when auditing, converting, or modernizing an agent plugin. -license: MIT -metadata: - version: "1.0.0" ---- - -# Migrate an Agent Plugin - -Convert an existing plugin to the Agent Plugins v1 portable core without prematurely removing behavior required by its current clients. - -## Source of truth - -Use the current [Agent Plugins specification](https://agent-plugins.org/specification) as the normative source. - -Read these references before editing: - -- [Migration guide](references/migration-guide.md) -- [Client extensions](references/client-extensions.md) -- [Validation checklist](references/validation-checklist.md) - -## Workflow - -1. Inventory the current plugin before moving files. - - Record every manifest, skill, prompt or command, agent, MCP server, hook, LSP server, UI resource, script, secret requirement, and marketplace entry. - - Identify the clients that currently load each artifact and the install paths or discovery rules they require. - - Run existing tests or capture a manual smoke-test baseline. - -2. Classify each artifact. - - Portable core: root `plugin.json`, Agent Skills in `skills/`, and MCP servers in root `mcp.json`. - - Client extension: additional behavior loaded through a reverse-domain namespace owned and documented by a client. - - Compatibility layer: legacy files or a generated client package retained until that client supports the portable or namespaced form. - - Distribution metadata: marketplace catalogs, install policy, signing, and release configuration; these are outside the portable package format. - -3. Add the portable manifest. - - Create `plugin.json` at the plugin root. - - Set `$schema` to `https://agent-plugins.org/schemas/1.0.0/plugin.schema.json`. - - Include `name` and only supported metadata fields. - - Do not put component paths or client fields such as `hooks`, `agents`, `skills`, or `mcpServers` at the top level. - -4. Normalize portable components. - - Put each skill at `skills//SKILL.md`; only immediate children of `skills/` are discovered. - - Make each skill name match its parent directory and the Agent Skills naming rules. - - If MCP is present, convert it to root `mcp.json`, declare the matching v1.0.0 schema, and give every server an explicit `stdio`, `streamable-http`, or `sse` type. - - Use `${PLUGIN_ROOT}` for packaged read-only resources and `${PLUGIN_DATA}` for persistent writable state where the MCP schema permits expansion. - -5. Preserve non-core behavior. - - Use a client extension only when the target client publishes a reverse-domain namespace and its semantics. - - If the client still requires a legacy layout, keep or generate a separate compatibility package. Treat the portable files as the source of truth and avoid manually maintaining divergent copies. - - Do not invent a vendor namespace and assume an unrelated client will load it. - -6. Validate and test incrementally. - - Validate the portable manifest, every skill, optional MCP configuration, and package path containment. - - Test each supported client independently, including hooks and other compatibility behavior. - - Remove legacy artifacts only after the replacement passes the same behavior checks. - -## Required migration report - -Before finishing, report: - -- The discovered source format and target clients. -- A mapping from every original artifact to portable core, extension, compatibility layer, distribution metadata, or removal. -- Files added, moved, generated, retained, and intentionally omitted. -- Validation and client smoke-test results. -- Remaining client-specific risks or manual steps. - -Prefer an additive, reversible migration. Never claim that hooks, agents, commands, LSP servers, UI, or marketplace metadata became portable Agent Plugins v1 components. diff --git a/skills/migrate-agent-plugin/references/client-extensions.md b/skills/migrate-agent-plugin/references/client-extensions.md deleted file mode 100644 index 1284ab6..0000000 --- a/skills/migrate-agent-plugin/references/client-extensions.md +++ /dev/null @@ -1,76 +0,0 @@ -# Client Extensions - -Agent Plugins v1 keeps the portable core small. Client extensions provide an escape hatch for hooks, agents, commands, LSP, UI, and other behavior that has not become portable. - -## Rules - -1. Extension namespaces are reverse-domain identifiers, such as `com.vendor.client`. -2. The client that owns the namespace defines its fields, files, validation, and runtime behavior. -3. Manifest extension data belongs under `extensions` in root `plugin.json`. -4. Extension files belong in a top-level directory whose name exactly matches the namespace. -5. Other clients ignore namespaces they do not implement without losing valid portable components. -6. An extension is not a way for a plugin author to make up fields that existing clients will automatically understand. - -## Manifest data - -```json -{ - "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", - "name": "your-plugin", - "extensions": { - "com.vendor.client": { - "settingDefinedByThatClient": true - } - } -} -``` - -The portable specification validates only that each namespace value is an object. The owning client defines everything inside it. - -## Extension files - -```text -your-plugin/ -├── plugin.json -├── skills/ -└── com.vendor.client/ - ├── hooks/ - │ └── hooks.json - └── agents/ - └── reviewer.md -``` - -This layout has effect only if `com.vendor.client` actually implements those paths. - -## Choose the right compatibility strategy - -### The client documents an Agent Plugins extension namespace - -Use its exact namespace, fields, and directory layout. Test failures in the extension separately from the portable skills and MCP configuration. - -### The client supports Agent Plugins core but still discovers legacy add-ons - -Keep the root manifest conforming. Follow the client's documented additive loading behavior for legacy hooks or agents, and label those files as client-specific. If that layout conflicts with strict portable packaging, generate a separate client distribution from the portable source. - -### The client does not support Agent Plugins core - -Keep the legacy plugin working and add a portable sibling package. Share underlying skill text, scripts, and server code where safe, but avoid symlinks that resolve outside either package root. - -## Hooks - -Hooks are a common extension candidate, but Agent Plugins v1 does not define their event names, input/output protocol, command format, security model, or discovery path. Preserve the existing hook until the target client documents a replacement. Review hook scripts as executable code and test approval, denial, failure, and timeout behavior after migration. - -## Do not put client fields at the manifest top level - -These examples are nonconforming in an Agent Plugins v1 root manifest: - -```json -{ - "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", - "name": "your-plugin", - "hooks": "hooks.json", - "agents": "agents/" -} -``` - -Use a documented extension or a compatibility package instead. diff --git a/skills/migrate-agent-plugin/references/migration-guide.md b/skills/migrate-agent-plugin/references/migration-guide.md deleted file mode 100644 index 1186a34..0000000 --- a/skills/migrate-agent-plugin/references/migration-guide.md +++ /dev/null @@ -1,114 +0,0 @@ -# Migration Guide - -Use this guide to map an existing plugin into the Agent Plugins v1 portable core while keeping client-specific behavior available. - -## 1. Inventory before conversion - -Locate all plugin manifests and component roots. Common legacy or client-specific artifacts include: - -- `.claude-plugin/plugin.json`, `.plugin/plugin.json`, `.github/plugin/plugin.json`, `.codex-plugin/plugin.json`, or a root manifest without the Agent Plugins `$schema`. -- Skills under `skills/`, `.agents/skills/`, `.github/skills/`, `.claude/skills/`, or a configured custom path. -- MCP configuration in `.mcp.json`, `.github/mcp.json`, another client config, or inline manifest fields. -- Hooks in `hooks.json`, `hooks/hooks.json`, a settings file, or inline manifest fields. -- Commands, prompts, custom agents, LSP servers, UI assets, authentication declarations, and marketplace catalogs. - -Do not delete or move anything until its consumer and replacement are known. - -## 2. Map every artifact - -| Existing artifact | Agent Plugins v1 destination | Compatibility action | -| --- | --- | --- | -| Plugin identity and metadata | Root `plugin.json` | Retain a legacy manifest only if a target client still requires it. Generate copies from one source when possible. | -| Reusable skill | `skills//SKILL.md` | Normalize frontmatter and keep scripts, references, and assets inside the skill directory. | -| MCP server | Root `mcp.json` | Convert client-specific fields and declare an explicit transport type. Keep a client adapter only for unsupported fields or transports. | -| Hook | No portable v1 destination | Use a client-owned extension namespace or retain a client compatibility package. | -| Custom agent or persona | No portable v1 destination | Keep it in a client extension or compatibility package. Convert to a skill only when on-demand instructions truly preserve its semantics. | -| Command or prompt | No portable v1 destination | Convert reusable task instructions to a skill when appropriate; otherwise retain the client feature. | -| LSP server | No portable v1 destination | Retain it as a client extension or compatibility package. | -| UI or app integration | No portable v1 destination | Retain it as a client extension or compatibility package. | -| Marketplace entry, install policy, signing | Outside the portable package | Keep it in the platform's distribution repository or release process. | - -## 3. Create the portable manifest - -Start with the smallest valid root manifest: - -```json -{ - "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", - "name": "your-plugin" -} -``` - -Allowed optional fields are `version`, `description`, `author`, `homepage`, `repository`, `license`, `keywords`, and `extensions`. The schema is closed. Unknown top-level fields are nonconforming even when a particular client historically accepted them. - -Plugin names are 1–64 characters, use lowercase ASCII letters, digits, hyphens, and periods, begin and end with an alphanumeric character, and contain neither `--` nor `..`. - -## 4. Normalize skills - -Each discoverable skill must be an immediate child of `skills/`: - -```text -skills/ -└── deploy/ - ├── SKILL.md - ├── scripts/ - ├── references/ - └── assets/ -``` - -The `SKILL.md` name must match its parent directory. Keep skill-relative dependencies inside that directory and update references after moving files. Do not rely on recursive discovery of nested skill directories. - -## 5. Convert MCP configuration - -Portable MCP configuration belongs in root `mcp.json`, not inline in `plugin.json`: - -```json -{ - "$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json", - "mcpServers": { - "example": { - "type": "stdio", - "command": "node", - "args": ["${PLUGIN_ROOT}/server/index.js"], - "cwd": "${PLUGIN_ROOT}" - } - } -} -``` - -Use a single executable token for `command`; do not put a shell command line in that field. A bundled executable uses a plugin-relative `./path`. Non-loopback remote servers use HTTPS. Do not embed secrets in remote headers. - -## 6. Preserve platform behavior - -Migration should be additive first: - -1. Add the portable root manifest and components. -2. Leave the working client package intact. -3. Make portable files the source of truth. -4. Generate or copy legacy adapters only when client documentation requires them. -5. Test every supported client. -6. Remove old files only after their consumers have migrated. - -A repository can keep the portable plugin and client adapters as siblings: - -```text -repository/ -├── plugin/ # Agent Plugins v1 portable package -└── client-adapters/ # Generated or maintained platform packages - ├── client-a/ - └── client-b/ -``` - -An adapter is not part of the portable core. Clearly label which files are canonical and automate synchronization when multiple manifests or layouts must ship. - -## 7. Test the migration - -Test at least: - -- Loading the plugin with a conforming Agent Plugins client. -- Skill discovery and activation. -- Every MCP transport and tool, when present. -- Legacy installation and all retained hooks, agents, commands, LSP, or UI behavior. -- Upgrade and rollback from the last released client-specific package. - -The migration is complete only when the portable core validates and the promised client behaviors still work. diff --git a/skills/migrate-agent-plugin/references/validation-checklist.md b/skills/migrate-agent-plugin/references/validation-checklist.md deleted file mode 100644 index 9be4420..0000000 --- a/skills/migrate-agent-plugin/references/validation-checklist.md +++ /dev/null @@ -1,49 +0,0 @@ -# Validation Checklist - -## Package - -- [ ] `plugin.json` is a regular file at the plugin root. -- [ ] Every packaged or resolved path remains inside the plugin root. -- [ ] Symlinks, junctions, and reparse points do not escape the package. -- [ ] No credentials, tokens, or private keys are embedded in the package. - -## Manifest - -- [ ] `$schema` is `https://agent-plugins.org/schemas/1.0.0/plugin.schema.json`. -- [ ] `name` satisfies the v1 length and character rules. -- [ ] Only `$schema`, `name`, `version`, `description`, `author`, `homepage`, `repository`, `license`, `keywords`, and `extensions` appear at the top level. -- [ ] Every `extensions` member is an object keyed by a reverse-domain namespace. -- [ ] Optional metadata has the type required by the schema. - -## Skills - -- [ ] Each skill is an immediate child directory of `skills/`. -- [ ] Each skill contains a regular file named exactly `SKILL.md`. -- [ ] The frontmatter `name` matches the directory name and Agent Skills naming rules. -- [ ] `description` explains both what the skill does and when to use it. -- [ ] Referenced scripts, references, and assets exist within the skill directory. -- [ ] Each skill validates independently; one invalid skill should not hide failures in another. - -## MCP, when present - -- [ ] Root `mcp.json` uses `https://agent-plugins.org/schemas/1.0.0/mcp.schema.json`. -- [ ] Its specification version matches `plugin.json`. -- [ ] Every server declares exactly one supported transport variant. -- [ ] `command` is one executable token, not a shell command string. -- [ ] Plugin-relative executable paths and working directories begin with `./`. -- [ ] `${PLUGIN_ROOT}` and `${PLUGIN_DATA}` appear only in fields where expansion is defined. -- [ ] Remote non-loopback URLs use HTTPS and contain no embedded credentials. - -## Client compatibility - -- [ ] Every client extension uses a namespace implemented and documented by its owning client. -- [ ] Hooks, agents, commands, LSP, UI, and marketplace metadata are not presented as portable v1 components. -- [ ] A compatibility package remains available for clients that still require a legacy layout. -- [ ] Portable and legacy manifests are generated from one metadata source where practical. -- [ ] Installation, update, rollback, and behavior smoke tests pass for every supported client. - -## Handoff - -- [ ] The migration report maps every original artifact to its new owner and location. -- [ ] Removed files have a verified replacement and recovery path. -- [ ] Remaining limitations and manual release steps are documented. diff --git a/skills/mobbin-design-research/SKILL.md b/skills/mobbin-design-research/SKILL.md new file mode 100644 index 0000000..f97901d --- /dev/null +++ b/skills/mobbin-design-research/SKILL.md @@ -0,0 +1,45 @@ +--- +name: mobbin-design-research +description: Use Mobbin for visual design research when a user asks for UI inspiration, screen references, flow examples, website sections, competitive patterns, or app-specific design examples. Choose the right Mobbin search tool, phrase focused queries, inspect returned images, and cite every screen recommendation. +license: MIT +--- + +# Mobbin design research + +Use Mobbin when the task needs real-world UI references rather than generic design advice. + +## Choose the search + +- Use `search_screens` for one screen or UI state, such as a login screen, checkout screen, or + empty state. +- Use `search_flows` for one multi-step journey, such as onboarding or checkout. Search separate + journeys in separate calls. +- Use `search_sections` for one website section, such as a pricing page, hero section, footer, or + signup form. + +## Write the query + +Describe one intent in plain language: what the user sees and how the elements relate. Be specific; +detail helps. Name a specific app when the user wants examples from that app. Use the tool's +dedicated parameters for platform and other filters instead of putting them in the query. + +Keep queries focused. Search separate intents separately. Prefer concrete UI elements, content, and +actions over vague style words or disconnected keyword lists. + +## Set the parameters + +- `search_screens.platform` and `search_flows.platform` are required: use `ios` or `web`. +- `search_screens.mode` defaults to `deep`, an AI pipeline that interprets intent and re-scores + candidates. Drop to `standard` when latency matters more than precision, and to `fast` for + keyword-style lookups. +- Keep result limits low enough to inspect every returned image. +- Paginate `search_screens` with `exclude_screen_ids`, passing the IDs from earlier results. +- Paginate `search_flows` and `search_sections` with their `page` parameter. + +## Evaluate and cite + +Inspect the returned images before drawing conclusions or recommending a result. Use metadata to +filter and organize results, not as a substitute for visual inspection. + +Every screen you mention must be a Markdown link to its returned `mobbin_url`. Cite flows and +sections with their returned Mobbin links when presenting them as references. From 27ed4e39bdaf914d37c327e5dd339f4bd1c28b35 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 06:06:49 +0000 Subject: [PATCH 2/7] Fix plugin installer MCP compatibility Co-Authored-By: xingxiang --- .mcp.json | 8 ++++++++ README.md | 3 +++ scripts/validate.mjs | 31 +++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 .mcp.json diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 0000000..1bbd086 --- /dev/null +++ b/.mcp.json @@ -0,0 +1,8 @@ +{ + "mcpServers": { + "mobbin": { + "type": "http", + "url": "https://api.mobbin.com/mcp" + } + } +} diff --git a/README.md b/README.md index b8bfd44..a5d3315 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,9 @@ Pro, Team, or Enterprise plan. The plugin contains no API key or other credentia - The `mobbin-design-research` skill, which teaches agents when and how to use Mobbin's three search tools. +The root `mcp.json` is the standard configuration; `.mcp.json` is a compatibility copy for older +installers that still probe the dot-prefixed filename. + Mobbin provides these read-only MCP tools: - `search_screens` — UI screens. diff --git a/scripts/validate.mjs b/scripts/validate.mjs index 0d3292f..b1eba7f 100644 --- a/scripts/validate.mjs +++ b/scripts/validate.mjs @@ -160,9 +160,40 @@ function validateConfigSafety(mcp) { const plugin = await readJson("plugin.json"); const mcp = await readJson("mcp.json"); +const compatibilityMcp = await readJson(".mcp.json"); validateSchema("schemas/1.0.0/plugin.schema.json", plugin, "plugin.json"); validateSchema("schemas/1.0.0/mcp.schema.json", mcp, "mcp.json"); validateConfigSafety(mcp); +if (!isObject(compatibilityMcp) || !isObject(compatibilityMcp.mcpServers)) { + fail(".mcp.json: mcpServers must be an object"); +} else if (isObject(mcp?.mcpServers)) { + const standardServers = mcp.mcpServers; + const compatibilityServers = compatibilityMcp.mcpServers; + const standardNames = Object.keys(standardServers).sort(); + const compatibilityNames = Object.keys(compatibilityServers).sort(); + if (JSON.stringify(standardNames) !== JSON.stringify(compatibilityNames)) { + fail(".mcp.json: server names must match mcp.json"); + } + for (const name of standardNames) { + const standard = standardServers[name]; + const compatibility = compatibilityServers[name]; + if (!isObject(compatibility)) { + fail(`.mcp.json: server "${name}" must be an object`); + continue; + } + if (standard.url !== compatibility.url) { + fail(`.mcp.json: server "${name}" URL must match mcp.json`); + } + const transports = { + "streamable-http": "http", + sse: "sse", + stdio: "stdio", + }; + if (transports[standard.type] !== compatibility.type) { + fail(`.mcp.json: server "${name}" transport does not correspond to mcp.json`); + } + } +} await validatePackagePaths(); try { From ddd38b7eb672b69e8af93d5b0700fdfd7f45dde1 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 06:15:57 +0000 Subject: [PATCH 3/7] Scope plugin to Mobbin MCP server Co-Authored-By: xingxiang --- README.md | 14 ++-- package-lock.json | 19 +----- package.json | 3 +- scripts/validate.mjs | 93 -------------------------- skills/mobbin-design-research/SKILL.md | 45 ------------- 5 files changed, 6 insertions(+), 168 deletions(-) delete mode 100644 skills/mobbin-design-research/SKILL.md diff --git a/README.md b/README.md index a5d3315..47ef0d4 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Mobbin Agent Plugin -The Mobbin Agent Plugin packages Mobbin's hosted MCP server and a design-research skill for Agent -Plugins 1.0.0 clients. It helps agents find real-world UI screens, multi-step flows, and website -sections from Mobbin's design library. +The Mobbin Agent Plugin packages Mobbin's hosted MCP server for Agent Plugins 1.0.0 clients. It +helps agents find real-world UI screens, multi-step flows, and website sections from Mobbin's +design library. ## Install @@ -26,8 +26,6 @@ Pro, Team, or Enterprise plan. The plugin contains no API key or other credentia ## What it installs - The `mobbin` MCP server at `https://api.mobbin.com/mcp` using Streamable HTTP. -- The `mobbin-design-research` skill, which teaches agents when and how to use Mobbin's three - search tools. The root `mcp.json` is the standard configuration; `.mcp.json` is a compatibility copy for older installers that still probe the dot-prefixed filename. @@ -38,8 +36,7 @@ Mobbin provides these read-only MCP tools: - `search_flows` — multi-step user journeys such as onboarding and checkout. - `search_sections` — website sections such as pricing pages, heroes, and footers. -Results include images, metadata, and links back to Mobbin. Agents should inspect the images before -recommending references and cite each mentioned screen with its `mobbin_url`. +Results include images, metadata, and links back to Mobbin. ## Layout @@ -47,9 +44,6 @@ recommending references and cite each mentioned screen with its `mobbin_url`. mobbin-agent-plugin/ ├── plugin.json ├── mcp.json -├── skills/ -│ └── mobbin-design-research/ -│ └── SKILL.md ├── schemas/1.0.0/ │ ├── plugin.schema.json │ └── mcp.schema.json diff --git a/package-lock.json b/package-lock.json index 2d7750e..6f53ab4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,8 +7,7 @@ "name": "mobbin-agent-plugin", "devDependencies": { "ajv": "8.20.0", - "ajv-formats": "3.0.1", - "yaml": "2.9.0" + "ajv-formats": "3.0.1" } }, "node_modules/ajv": { @@ -86,22 +85,6 @@ "engines": { "node": ">=0.10.0" } - }, - "node_modules/yaml": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", - "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", - "dev": true, - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } } } } diff --git a/package.json b/package.json index 2ad7677..4776e90 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,6 @@ }, "devDependencies": { "ajv": "8.20.0", - "ajv-formats": "3.0.1", - "yaml": "2.9.0" + "ajv-formats": "3.0.1" } } diff --git a/scripts/validate.mjs b/scripts/validate.mjs index b1eba7f..fa717eb 100644 --- a/scripts/validate.mjs +++ b/scripts/validate.mjs @@ -4,7 +4,6 @@ import { readFile, readdir, realpath } from "node:fs/promises"; import { createRequire } from "node:module"; import { dirname, relative, resolve } from "node:path"; import process from "node:process"; -import YAML from "yaml"; const require = createRequire(import.meta.url); const root = resolve(dirname(new URL(import.meta.url).pathname), ".."); @@ -40,83 +39,6 @@ function isObject(value) { return value !== null && typeof value === "object" && !Array.isArray(value); } -function validateSkillFrontmatter(path, source) { - if (!source.startsWith("---\n")) { - fail(`${path}: missing YAML frontmatter`); - return; - } - const end = source.indexOf("\n---", 4); - if (end === -1) { - fail(`${path}: unterminated YAML frontmatter`); - return; - } - - let frontmatter; - try { - frontmatter = YAML.parse(source.slice(4, end)); - } catch (error) { - fail(`${path}: invalid YAML frontmatter: ${error.message}`); - return; - } - if (!isObject(frontmatter)) { - fail(`${path}: frontmatter must be a YAML mapping`); - return; - } - - const allowed = new Set([ - "name", - "description", - "license", - "compatibility", - "metadata", - "allowed-tools", - ]); - for (const key of Object.keys(frontmatter)) { - if (!allowed.has(key)) fail(`${path}: unsupported frontmatter field "${key}"`); - } - - const skillDir = path.split("/").at(-2); - const name = frontmatter.name; - if ( - typeof name !== "string" || - name.length < 1 || - name.length > 64 || - !/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(name) || - name.includes("--") - ) { - fail(`${path}: name must be 1-64 lowercase letters, numbers, and single hyphens`); - } else if (name !== skillDir) { - fail(`${path}: name must match parent directory "${skillDir}"`); - } - - const description = frontmatter.description; - if (typeof description !== "string" || description.trim() === "" || description.length > 1024) { - fail(`${path}: description must be non-empty and at most 1024 characters`); - } - if (frontmatter.license !== undefined && typeof frontmatter.license !== "string") { - fail(`${path}: license must be a string`); - } - if ( - frontmatter.compatibility !== undefined && - (typeof frontmatter.compatibility !== "string" || frontmatter.compatibility.length > 500) - ) { - fail(`${path}: compatibility must be a string of at most 500 characters`); - } - if (frontmatter.metadata !== undefined) { - if (!isObject(frontmatter.metadata)) fail(`${path}: metadata must be a mapping`); - else { - for (const [key, value] of Object.entries(frontmatter.metadata)) { - if (typeof key !== "string" || typeof value !== "string") { - fail(`${path}: metadata keys and values must be strings`); - } - } - } - } - if (frontmatter["allowed-tools"] !== undefined && typeof frontmatter["allowed-tools"] !== "string") { - fail(`${path}: allowed-tools must be a space-separated string`); - } -} - async function validatePackagePaths() { const rootReal = await realpath(root); async function walk(path) { @@ -196,20 +118,6 @@ if (!isObject(compatibilityMcp) || !isObject(compatibilityMcp.mcpServers)) { } await validatePackagePaths(); -try { - for (const entry of await readdir(resolve(root, "skills"), { withFileTypes: true })) { - if (!entry.isDirectory()) continue; - const path = `skills/${entry.name}/SKILL.md`; - try { - validateSkillFrontmatter(path, await readFile(resolve(root, path), "utf8")); - } catch (error) { - fail(`${path}: ${error.message}`); - } - } -} catch (error) { - fail(`skills/: ${error.message}`); -} - if (errors.length > 0) { console.error(`FAIL: ${errors.length} validation error${errors.length === 1 ? "" : "s"}`); for (const error of errors) console.error(`- ${error}`); @@ -217,7 +125,6 @@ if (errors.length > 0) { } else { console.log("PASS: plugin.json schema"); console.log("PASS: mcp.json schema"); - console.log("PASS: skill frontmatter"); console.log("PASS: package path containment"); console.log("PASS: MCP header credential safety"); console.log("Validation passed."); diff --git a/skills/mobbin-design-research/SKILL.md b/skills/mobbin-design-research/SKILL.md deleted file mode 100644 index f97901d..0000000 --- a/skills/mobbin-design-research/SKILL.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -name: mobbin-design-research -description: Use Mobbin for visual design research when a user asks for UI inspiration, screen references, flow examples, website sections, competitive patterns, or app-specific design examples. Choose the right Mobbin search tool, phrase focused queries, inspect returned images, and cite every screen recommendation. -license: MIT ---- - -# Mobbin design research - -Use Mobbin when the task needs real-world UI references rather than generic design advice. - -## Choose the search - -- Use `search_screens` for one screen or UI state, such as a login screen, checkout screen, or - empty state. -- Use `search_flows` for one multi-step journey, such as onboarding or checkout. Search separate - journeys in separate calls. -- Use `search_sections` for one website section, such as a pricing page, hero section, footer, or - signup form. - -## Write the query - -Describe one intent in plain language: what the user sees and how the elements relate. Be specific; -detail helps. Name a specific app when the user wants examples from that app. Use the tool's -dedicated parameters for platform and other filters instead of putting them in the query. - -Keep queries focused. Search separate intents separately. Prefer concrete UI elements, content, and -actions over vague style words or disconnected keyword lists. - -## Set the parameters - -- `search_screens.platform` and `search_flows.platform` are required: use `ios` or `web`. -- `search_screens.mode` defaults to `deep`, an AI pipeline that interprets intent and re-scores - candidates. Drop to `standard` when latency matters more than precision, and to `fast` for - keyword-style lookups. -- Keep result limits low enough to inspect every returned image. -- Paginate `search_screens` with `exclude_screen_ids`, passing the IDs from earlier results. -- Paginate `search_flows` and `search_sections` with their `page` parameter. - -## Evaluate and cite - -Inspect the returned images before drawing conclusions or recommending a result. Use metadata to -filter and organize results, not as a substitute for visual inspection. - -Every screen you mention must be a Markdown link to its returned `mobbin_url`. Cite flows and -sections with their returned Mobbin links when presenting them as references. From 0064bce92f2b30c0cba15ec99164c09ba1c66e52 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 06:18:29 +0000 Subject: [PATCH 4/7] Support MCP-only plugin discovery Co-Authored-By: xingxiang --- .claude-plugin/plugin.json | 5 +++++ .codex-plugin/plugin.json | 5 +++++ README.md | 8 ++++++-- scripts/validate.mjs | 17 +++++++++++++++++ 4 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 .claude-plugin/plugin.json create mode 100644 .codex-plugin/plugin.json diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..6e80f5b --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,5 @@ +{ + "name": "mobbin", + "version": "0.1.0", + "description": "Search Mobbin's real-world UI design library through MCP and design research guidance." +} diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json new file mode 100644 index 0000000..6e80f5b --- /dev/null +++ b/.codex-plugin/plugin.json @@ -0,0 +1,5 @@ +{ + "name": "mobbin", + "version": "0.1.0", + "description": "Search Mobbin's real-world UI design library through MCP and design research guidance." +} diff --git a/README.md b/README.md index 47ef0d4..f0cda38 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,9 @@ Pro, Team, or Enterprise plan. The plugin contains no API key or other credentia - The `mobbin` MCP server at `https://api.mobbin.com/mcp` using Streamable HTTP. -The root `mcp.json` is the standard configuration; `.mcp.json` is a compatibility copy for older -installers that still probe the dot-prefixed filename. +The root `plugin.json` and `mcp.json` are the standard configurations. The root `.mcp.json`, +`.claude-plugin/plugin.json`, and `.codex-plugin/plugin.json` are compatibility copies for current +installers that still probe client-specific filenames and directories. Mobbin provides these read-only MCP tools: @@ -44,6 +45,9 @@ Results include images, metadata, and links back to Mobbin. mobbin-agent-plugin/ ├── plugin.json ├── mcp.json +├── .mcp.json +├── .claude-plugin/plugin.json +├── .codex-plugin/plugin.json ├── schemas/1.0.0/ │ ├── plugin.schema.json │ └── mcp.schema.json diff --git a/scripts/validate.mjs b/scripts/validate.mjs index fa717eb..f7dd123 100644 --- a/scripts/validate.mjs +++ b/scripts/validate.mjs @@ -83,6 +83,8 @@ function validateConfigSafety(mcp) { const plugin = await readJson("plugin.json"); const mcp = await readJson("mcp.json"); const compatibilityMcp = await readJson(".mcp.json"); +const claudePlugin = await readJson(".claude-plugin/plugin.json"); +const codexPlugin = await readJson(".codex-plugin/plugin.json"); validateSchema("schemas/1.0.0/plugin.schema.json", plugin, "plugin.json"); validateSchema("schemas/1.0.0/mcp.schema.json", mcp, "mcp.json"); validateConfigSafety(mcp); @@ -116,6 +118,20 @@ if (!isObject(compatibilityMcp) || !isObject(compatibilityMcp.mcpServers)) { } } } +for (const [path, compatibilityPlugin] of [ + [".claude-plugin/plugin.json", claudePlugin], + [".codex-plugin/plugin.json", codexPlugin], +]) { + if (!isObject(compatibilityPlugin)) { + fail(`${path}: manifest must be an object`); + continue; + } + for (const field of ["name", "version", "description"]) { + if (compatibilityPlugin[field] !== plugin?.[field]) { + fail(`${path}: ${field} must match plugin.json`); + } + } +} await validatePackagePaths(); if (errors.length > 0) { @@ -125,6 +141,7 @@ if (errors.length > 0) { } else { console.log("PASS: plugin.json schema"); console.log("PASS: mcp.json schema"); + console.log("PASS: vendor manifest synchronization"); console.log("PASS: package path containment"); console.log("PASS: MCP header credential safety"); console.log("Validation passed."); From 05771fc8ef9a9b9b0dff53f7acd19fe3de8406ab Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 06:20:08 +0000 Subject: [PATCH 5/7] Update plugin description for MCP package Co-Authored-By: xingxiang --- .claude-plugin/plugin.json | 2 +- .codex-plugin/plugin.json | 2 +- plugin.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 6e80f5b..4489e28 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,5 +1,5 @@ { "name": "mobbin", "version": "0.1.0", - "description": "Search Mobbin's real-world UI design library through MCP and design research guidance." + "description": "Search Mobbin's UI library for screens, flows, and website sections from your agent." } diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 6e80f5b..4489e28 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,5 +1,5 @@ { "name": "mobbin", "version": "0.1.0", - "description": "Search Mobbin's real-world UI design library through MCP and design research guidance." + "description": "Search Mobbin's UI library for screens, flows, and website sections from your agent." } diff --git a/plugin.json b/plugin.json index c7b7512..467d5ef 100644 --- a/plugin.json +++ b/plugin.json @@ -2,7 +2,7 @@ "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", "name": "mobbin", "version": "0.1.0", - "description": "Search Mobbin's real-world UI design library through MCP and design research guidance.", + "description": "Search Mobbin's UI library for screens, flows, and website sections from your agent.", "author": { "name": "Mobbin", "url": "https://mobbin.com" From 774b03a0666204aad193b81328be8e47953c4d5f Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 06:30:57 +0000 Subject: [PATCH 6/7] Remove nonstandard installer shims Co-Authored-By: xingxiang --- .claude-plugin/plugin.json | 5 ---- .codex-plugin/plugin.json | 5 ---- .mcp.json | 8 ------ README.md | 37 +++++++++++++-------------- scripts/validate.mjs | 52 -------------------------------------- 5 files changed, 18 insertions(+), 89 deletions(-) delete mode 100644 .claude-plugin/plugin.json delete mode 100644 .codex-plugin/plugin.json delete mode 100644 .mcp.json diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json deleted file mode 100644 index 4489e28..0000000 --- a/.claude-plugin/plugin.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "mobbin", - "version": "0.1.0", - "description": "Search Mobbin's UI library for screens, flows, and website sections from your agent." -} diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json deleted file mode 100644 index 4489e28..0000000 --- a/.codex-plugin/plugin.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "mobbin", - "version": "0.1.0", - "description": "Search Mobbin's UI library for screens, flows, and website sections from your agent." -} diff --git a/.mcp.json b/.mcp.json deleted file mode 100644 index 1bbd086..0000000 --- a/.mcp.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "mcpServers": { - "mobbin": { - "type": "http", - "url": "https://api.mobbin.com/mcp" - } - } -} diff --git a/README.md b/README.md index f0cda38..c0e83ae 100644 --- a/README.md +++ b/README.md @@ -6,19 +6,22 @@ design library. ## Install -```bash -npx plugins add mobbin/mobbin-agent-plugin -``` - -Any client that supports Agent Plugins 1.0.0 can load it — ChatGPT and Codex, Cursor, GitHub -Copilot, Kiro, and VS Code at the specification's launch. - -Several clients also offer a native Mobbin setup path that predates this plugin, which stays -supported: [ChatGPT](https://docs.mobbin.com/mcp/clients/chatgpt), -[Cursor](https://docs.mobbin.com/mcp/clients/cursor), -[VS Code](https://docs.mobbin.com/mcp/clients/vscode), -[Codex CLI](https://docs.mobbin.com/mcp/clients/codex-cli), and -[others](https://docs.mobbin.com/mcp/clients/other). +The package is a portable Agent Plugins 1.0.0 directory. Use the installation flow documented by +your client: + +- [VS Code](https://code.visualstudio.com/docs/agent-customization/agent-plugins): install from + the marketplace, use **Chat: Install Plugin From Source**, or register a local checkout with + `chat.pluginLocations`. +- [Cursor](https://cursor.com/docs/plugins): install from Customize, or load a local checkout + from `~/.cursor/plugins/local/`. +- [GitHub Copilot](https://docs.github.com/en/copilot/reference/copilot-cli-reference/cli-plugin-reference): + use `copilot plugin install OWNER/REPO` or the documented local/repository flow. +- [Kiro](https://kiro.dev/docs/powers/installation/): use **Powers → Add Custom Power**. +- [ChatGPT and Codex](https://developers.openai.com/plugins): install through OpenAI's plugin + directory. + +Claude Code is not an Agent Plugins-compatible client; use Mobbin's +[`claude mcp add` setup](https://docs.mobbin.com/mcp/clients/claude-code) instead. On first use, the client opens a browser for OAuth authorization. You need a Mobbin account on a Pro, Team, or Enterprise plan. The plugin contains no API key or other credential. @@ -27,9 +30,8 @@ Pro, Team, or Enterprise plan. The plugin contains no API key or other credentia - The `mobbin` MCP server at `https://api.mobbin.com/mcp` using Streamable HTTP. -The root `plugin.json` and `mcp.json` are the standard configurations. The root `.mcp.json`, -`.claude-plugin/plugin.json`, and `.codex-plugin/plugin.json` are compatibility copies for current -installers that still probe client-specific filenames and directories. +The root `plugin.json` and `mcp.json` are the only package configuration files required by the +Agent Plugins standard. Mobbin provides these read-only MCP tools: @@ -45,9 +47,6 @@ Results include images, metadata, and links back to Mobbin. mobbin-agent-plugin/ ├── plugin.json ├── mcp.json -├── .mcp.json -├── .claude-plugin/plugin.json -├── .codex-plugin/plugin.json ├── schemas/1.0.0/ │ ├── plugin.schema.json │ └── mcp.schema.json diff --git a/scripts/validate.mjs b/scripts/validate.mjs index f7dd123..54e1b32 100644 --- a/scripts/validate.mjs +++ b/scripts/validate.mjs @@ -35,10 +35,6 @@ function validateSchema(schemaPath, data, label) { } } -function isObject(value) { - return value !== null && typeof value === "object" && !Array.isArray(value); -} - async function validatePackagePaths() { const rootReal = await realpath(root); async function walk(path) { @@ -82,56 +78,9 @@ function validateConfigSafety(mcp) { const plugin = await readJson("plugin.json"); const mcp = await readJson("mcp.json"); -const compatibilityMcp = await readJson(".mcp.json"); -const claudePlugin = await readJson(".claude-plugin/plugin.json"); -const codexPlugin = await readJson(".codex-plugin/plugin.json"); validateSchema("schemas/1.0.0/plugin.schema.json", plugin, "plugin.json"); validateSchema("schemas/1.0.0/mcp.schema.json", mcp, "mcp.json"); validateConfigSafety(mcp); -if (!isObject(compatibilityMcp) || !isObject(compatibilityMcp.mcpServers)) { - fail(".mcp.json: mcpServers must be an object"); -} else if (isObject(mcp?.mcpServers)) { - const standardServers = mcp.mcpServers; - const compatibilityServers = compatibilityMcp.mcpServers; - const standardNames = Object.keys(standardServers).sort(); - const compatibilityNames = Object.keys(compatibilityServers).sort(); - if (JSON.stringify(standardNames) !== JSON.stringify(compatibilityNames)) { - fail(".mcp.json: server names must match mcp.json"); - } - for (const name of standardNames) { - const standard = standardServers[name]; - const compatibility = compatibilityServers[name]; - if (!isObject(compatibility)) { - fail(`.mcp.json: server "${name}" must be an object`); - continue; - } - if (standard.url !== compatibility.url) { - fail(`.mcp.json: server "${name}" URL must match mcp.json`); - } - const transports = { - "streamable-http": "http", - sse: "sse", - stdio: "stdio", - }; - if (transports[standard.type] !== compatibility.type) { - fail(`.mcp.json: server "${name}" transport does not correspond to mcp.json`); - } - } -} -for (const [path, compatibilityPlugin] of [ - [".claude-plugin/plugin.json", claudePlugin], - [".codex-plugin/plugin.json", codexPlugin], -]) { - if (!isObject(compatibilityPlugin)) { - fail(`${path}: manifest must be an object`); - continue; - } - for (const field of ["name", "version", "description"]) { - if (compatibilityPlugin[field] !== plugin?.[field]) { - fail(`${path}: ${field} must match plugin.json`); - } - } -} await validatePackagePaths(); if (errors.length > 0) { @@ -141,7 +90,6 @@ if (errors.length > 0) { } else { console.log("PASS: plugin.json schema"); console.log("PASS: mcp.json schema"); - console.log("PASS: vendor manifest synchronization"); console.log("PASS: package path containment"); console.log("PASS: MCP header credential safety"); console.log("Validation passed."); From 75e86d02f2e378d0a11e221b926dd091ad5bc9b6 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 07:09:59 +0000 Subject: [PATCH 7/7] Keep validator schema-focused Co-Authored-By: xingxiang --- package-lock.json | 21 +----------------- package.json | 3 +-- scripts/validate.mjs | 51 ++------------------------------------------ 3 files changed, 4 insertions(+), 71 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6f53ab4..df014f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,8 +6,7 @@ "": { "name": "mobbin-agent-plugin", "devDependencies": { - "ajv": "8.20.0", - "ajv-formats": "3.0.1" + "ajv": "8.20.0" } }, "node_modules/ajv": { @@ -27,24 +26,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", diff --git a/package.json b/package.json index 4776e90..60ce757 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,6 @@ "validate": "node scripts/validate.mjs" }, "devDependencies": { - "ajv": "8.20.0", - "ajv-formats": "3.0.1" + "ajv": "8.20.0" } } diff --git a/scripts/validate.mjs b/scripts/validate.mjs index 54e1b32..b6f61a5 100644 --- a/scripts/validate.mjs +++ b/scripts/validate.mjs @@ -1,8 +1,7 @@ import Ajv from "ajv/dist/2020.js"; -import addFormats from "ajv-formats"; -import { readFile, readdir, realpath } from "node:fs/promises"; +import { readFile } from "node:fs/promises"; import { createRequire } from "node:module"; -import { dirname, relative, resolve } from "node:path"; +import { dirname, resolve } from "node:path"; import process from "node:process"; const require = createRequire(import.meta.url); @@ -26,7 +25,6 @@ function validateSchema(schemaPath, data, label) { if (data === null) return; const schema = require(resolve(root, schemaPath)); const ajv = new Ajv({ allErrors: true, strict: true }); - addFormats(ajv); const valid = ajv.validate(schema, data); if (!valid) { for (const error of ajv.errors ?? []) { @@ -35,53 +33,10 @@ function validateSchema(schemaPath, data, label) { } } -async function validatePackagePaths() { - const rootReal = await realpath(root); - async function walk(path) { - for (const entry of await readdir(path, { withFileTypes: true })) { - if (entry.name === ".git" || entry.name === "node_modules") continue; - const current = resolve(path, entry.name); - let target; - try { - target = await realpath(current); - } catch (error) { - fail(`package path ${relative(root, current)} cannot be resolved: ${error.message}`); - continue; - } - if (relative(rootReal, target).startsWith("..")) { - fail(`package path escapes plugin root: ${relative(root, current)} -> ${target}`); - } - if (entry.isDirectory() && !entry.isSymbolicLink()) await walk(current); - } - } - await walk(root); -} - -function validateConfigSafety(mcp) { - for (const [name, server] of Object.entries(mcp?.mcpServers ?? {})) { - if (server.cwd !== undefined && !server.cwd.startsWith("./")) { - fail(`mcpServers.${name}.cwd must start with "./"`); - } - if (server.headers) { - for (const [header, value] of Object.entries(server.headers)) { - const key = header.toLowerCase(); - if (/(authorization|api[-_]?key|token|secret|password|credential|cookie|session)/i.test(key)) { - fail(`mcpServers.${name}.headers.${header} looks like a credential header`); - } - if (/(bearer\s|basic\s|sk[-_]|secret|password|token|\$\{|<[^>]+>)/i.test(value)) { - fail(`mcpServers.${name}.headers.${header} looks like a credential value`); - } - } - } - } -} - const plugin = await readJson("plugin.json"); const mcp = await readJson("mcp.json"); validateSchema("schemas/1.0.0/plugin.schema.json", plugin, "plugin.json"); validateSchema("schemas/1.0.0/mcp.schema.json", mcp, "mcp.json"); -validateConfigSafety(mcp); -await validatePackagePaths(); if (errors.length > 0) { console.error(`FAIL: ${errors.length} validation error${errors.length === 1 ? "" : "s"}`); @@ -90,7 +45,5 @@ if (errors.length > 0) { } else { console.log("PASS: plugin.json schema"); console.log("PASS: mcp.json schema"); - console.log("PASS: package path containment"); - console.log("PASS: MCP header credential safety"); console.log("Validation passed."); }