chore: publish from main

This commit is contained in:
github-actions[bot]
2026-08-24 00:26:50 +00:00
parent 060daade7c
commit 7f365df724
11 changed files with 835 additions and 32 deletions
+87 -4
View File
@@ -6,7 +6,7 @@ import { fileURLToPath } from "url";
import { ROOT_FOLDER } from "./constants.mjs";
import { readExternalPlugins } from "./external-plugin-validation.mjs";
import { validateLicenseField } from "./lib/license.mjs";
import { AGENT_PLUGIN_SCHEMA_URL, validateAgentPluginManifest } from "./agent-plugin-schema.mjs";
import { AGENT_PLUGIN_SCHEMA_URL, validateAgentPluginManifest, validateAgentPluginMcpConfig } from "./agent-plugin-schema.mjs";
const PLUGINS_DIR = path.join(ROOT_FOLDER, "plugins");
const EXTENSIONS_DIR = path.join(ROOT_FOLDER, "extensions");
@@ -212,9 +212,77 @@ function validateExtensionReferences(plugin, pluginDir) {
return errors;
}
function validateCompositionNamespace(plugin) {
export function validateMcpConfig(pluginDir) {
const errors = [];
const compositionFields = ["agents", "hooks", "mcpServers", "skills"];
const legacyPath = path.join(pluginDir, ".mcp.json");
if (fs.existsSync(legacyPath)) {
errors.push("MCP configuration must live at mcp.json in the plugin root, not .mcp.json");
}
const mcpJsonPath = path.join(pluginDir, "mcp.json");
let mcpStat;
try {
mcpStat = fs.lstatSync(mcpJsonPath);
} catch (error) {
if (error.code === "ENOENT") {
try {
fs.readlinkSync(mcpJsonPath);
errors.push("mcp.json is a dangling symbolic link");
} catch (readlinkError) {
if (readlinkError.code !== "EINVAL" && readlinkError.code !== "ENOENT") {
errors.push(`mcp.json could not be inspected: ${readlinkError.message}`);
}
}
return errors;
}
errors.push(`mcp.json could not be inspected: ${error.message}`);
return errors;
}
let pluginRoot;
let resolvedMcpJsonPath;
try {
pluginRoot = fs.realpathSync.native(pluginDir);
resolvedMcpJsonPath = fs.realpathSync.native(mcpJsonPath);
} catch (error) {
if (mcpStat.isSymbolicLink() && error.code === "ENOENT") {
errors.push("mcp.json is a dangling symbolic link");
} else {
errors.push(`mcp.json could not be resolved: ${error.message}`);
}
return errors;
}
const relativeMcpPath = path.relative(pluginRoot, resolvedMcpJsonPath);
if (relativeMcpPath === ".." ||
relativeMcpPath.startsWith(`..${path.sep}`) ||
path.isAbsolute(relativeMcpPath)) {
errors.push("mcp.json must resolve to a file inside the plugin root");
return errors;
}
if (!fs.statSync(resolvedMcpJsonPath).isFile()) {
errors.push("mcp.json must be a regular file");
return errors;
}
const parsed = parseJsonFile(resolvedMcpJsonPath);
if (parsed.parseError) {
errors.push(`failed to parse mcp.json: ${parsed.parseError}`);
return errors;
}
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
errors.push("mcp.json must contain a top-level object");
return errors;
}
errors.push(...validateAgentPluginMcpConfig(parsed, pluginDir).map((message) => `mcp.json ${message}`));
return errors;
}
export function validateCompositionNamespace(plugin) {
const errors = [];
const compositionFields = ["agents", "hooks", "skills"];
const extensions = plugin.extensions;
const composition = extensions?.[AWESOME_COPILOT_NAMESPACE];
@@ -230,6 +298,17 @@ function validateCompositionNamespace(plugin) {
return errors;
}
if (extensions && typeof extensions === "object" && !Array.isArray(extensions)) {
for (const [namespace, value] of Object.entries(extensions)) {
if (value && typeof value === "object" && !Array.isArray(value) && value.mcpServers !== undefined) {
errors.push(`extensions["${namespace}"].mcpServers is not supported; declare MCP servers in mcp.json at the plugin root`);
}
}
if (extensions.mcpServers !== undefined) {
errors.push("extensions.mcpServers is not supported; declare MCP servers in mcp.json at the plugin root");
}
}
for (const field of compositionFields) {
if (extensions?.[field] !== undefined) {
errors.push(`extensions.${field} must be moved to extensions["${AWESOME_COPILOT_NAMESPACE}"].${field}`);
@@ -290,12 +369,16 @@ function validatePlugin(folderName) {
// Rule 5b: license (shared with external plugins). Non-SPDX is a warning, not an error.
const warnings = [];
for (const field of ["agents", "hooks", "mcpServers", "skills"]) {
if (plugin.mcpServers !== undefined) {
errors.push("mcpServers must be declared in mcp.json at the plugin root, not in plugin.json");
}
for (const field of ["agents", "hooks", "skills"]) {
if (plugin[field] !== undefined) {
errors.push(`${field} must be moved to extensions["${AWESOME_COPILOT_NAMESPACE}"].${field}`);
}
}
errors.push(...validateCompositionNamespace(plugin));
errors.push(...validateMcpConfig(pluginDir));
const licenseResult = validateLicenseField(plugin.license, { required: false });
errors.push(...licenseResult.errors);
warnings.push(...licenseResult.warnings);