From dca675210493e2ceff2eca01ab64d64c1ed451d2 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Tue, 6 Oct 2026 21:00:15 +0500 Subject: [PATCH 1/5] feat: make the plugin an Agent Plugins 1.0 package Add a portable plugin.json beside .cursor-plugin/plugin.json so Codex, GitHub Copilot, and other conformant clients can load the plugin. Add $schema and type to mcp.json, and re-vendor coder/skills at 3c99837 so the workspaces skill name matches its directory. The validator now checks plugin.json, mcp.json, skill names, and that both manifests agree; CI requires all three versions to match. Bump to 0.3.0. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_019YbnTXGyHn3KBrSw2R4D12 --- .cursor-plugin/marketplace.json | 2 +- .github/scripts/sync-skills-vendor.json | 2 +- .github/workflows/validate.yml | 5 +++-- CONTRIBUTING.md | 21 ++++++++++++------- VENDOR.md | 5 +++-- plugins/coder/.cursor-plugin/plugin.json | 2 +- plugins/coder/CHANGELOG.md | 7 +++++++ plugins/coder/README.md | 2 ++ plugins/coder/mcp.json | 2 ++ plugins/coder/plugin.json | 26 ++++++++++++++++++++++++ plugins/coder/skills/workspaces/SKILL.md | 2 +- 11 files changed, 61 insertions(+), 15 deletions(-) create mode 100644 plugins/coder/plugin.json diff --git a/.cursor-plugin/marketplace.json b/.cursor-plugin/marketplace.json index 185742f..618c731 100644 --- a/.cursor-plugin/marketplace.json +++ b/.cursor-plugin/marketplace.json @@ -6,7 +6,7 @@ }, "metadata": { "description": "Coder plugins for Cursor", - "version": "0.2.0", + "version": "0.3.0", "pluginRoot": "plugins" }, "plugins": [ diff --git a/.github/scripts/sync-skills-vendor.json b/.github/scripts/sync-skills-vendor.json index 6f497ce..4b3b3e5 100644 --- a/.github/scripts/sync-skills-vendor.json +++ b/.github/scripts/sync-skills-vendor.json @@ -1,6 +1,6 @@ { "repo": "coder/skills", - "pin": "e439e4bf0e9b517292adaac92d1c1b029810603c", + "pin": "3c998377afa72c4d03503468196721ef3c772d7f", "paths": [ "skills/setup", "skills/templates", diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 15e56f3..43fdbd2 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -28,9 +28,10 @@ jobs: run: | set -euo pipefail PLUGIN=$(jq -er '.version' plugins/coder/.cursor-plugin/plugin.json) + PORTABLE=$(jq -er '.version' plugins/coder/plugin.json) MARKET=$(jq -er '.metadata.version' .cursor-plugin/marketplace.json) - if [ "$PLUGIN" != "$MARKET" ]; then - echo "::error::plugin.json is $PLUGIN but marketplace.json is $MARKET" + if [ "$PLUGIN" != "$MARKET" ] || [ "$PLUGIN" != "$PORTABLE" ]; then + echo "::error::.cursor-plugin/plugin.json is $PLUGIN, plugin.json is $PORTABLE, marketplace.json is $MARKET" exit 1 fi if ! printf '%s' "$PLUGIN" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a3f22f0..df822c8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,7 +5,8 @@ ```text .cursor-plugin/marketplace.json Marketplace manifest plugins/coder/ The Coder plugin - .cursor-plugin/plugin.json Plugin manifest (canonical version) + plugin.json Agent Plugins 1.0 manifest (portable) + .cursor-plugin/plugin.json Cursor manifest (canonical version) CHANGELOG.md Release history mcp.json Coder remote MCP server (used by /coder-agent) skills/ vendored coder/skills (workspaces, setup, templates, modules) @@ -21,22 +22,28 @@ scripts/validate-template.mjs Manifest and frontmatter validation 2. Edit the plugin. Vendored skill directories are regenerated by the sync script; change them in [coder/skills](https://github.com/coder/skills) instead. See [VENDOR.md](VENDOR.md). -3. Validate: +3. Keep `plugins/coder/plugin.json` and `.cursor-plugin/plugin.json` in + step: `name`, `version`, `description`, `author`, `homepage`, + `repository`, `license`, and `keywords` must be identical. Cursor-only + fields (`displayName`, `logo`, `variables`) go in the Cursor manifest + only, because `plugin.json` is a closed schema. +4. Validate: ```sh node scripts/validate-template.mjs ``` -4. Test locally by copying `plugins/coder` to `~/.cursor/plugins/local/coder` +5. Test locally by copying `plugins/coder` to `~/.cursor/plugins/local/coder` and reloading Cursor. -5. Open a pull request. Use conventional commit titles +6. Open a pull request. Use conventional commit titles (`feat:`, `fix:`, `chore:`, `docs:`). ## Releasing -1. Bump `version` in `plugins/coder/.cursor-plugin/plugin.json` and - `metadata.version` in `.cursor-plugin/marketplace.json` to the same value. - The `validate` workflow fails if they differ. +1. Bump `version` in `plugins/coder/.cursor-plugin/plugin.json`, + `plugins/coder/plugin.json`, and `metadata.version` in + `.cursor-plugin/marketplace.json` to the same value. The `validate` + workflow fails if they differ. 2. Add an entry to `plugins/coder/CHANGELOG.md` for the new version. 3. Merge to `main`. The `release` workflow tags `vX.Y.Z` and publishes a GitHub Release when the version has no existing tag. diff --git a/VENDOR.md b/VENDOR.md index 837003f..b7b8442 100644 --- a/VENDOR.md +++ b/VENDOR.md @@ -20,8 +20,9 @@ to `main`. node .github/scripts/sync-skills.mjs ``` -3. Bump `version` in `plugins/coder/.cursor-plugin/plugin.json` and - `metadata.version` in `.cursor-plugin/marketplace.json`. Cursor skips +3. Bump `version` in `plugins/coder/.cursor-plugin/plugin.json`, + `plugins/coder/plugin.json`, and `metadata.version` in + `.cursor-plugin/marketplace.json`. Cursor skips installs whose version has not changed. 4. Add an entry to `plugins/coder/CHANGELOG.md` for the new version. 5. Run `node scripts/validate-template.mjs`. diff --git a/plugins/coder/.cursor-plugin/plugin.json b/plugins/coder/.cursor-plugin/plugin.json index 9e4f2c6..e265eb7 100644 --- a/plugins/coder/.cursor-plugin/plugin.json +++ b/plugins/coder/.cursor-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "coder", "displayName": "Coder", - "version": "0.2.0", + "version": "0.3.0", "description": "Skills for installing, configuring, and operating a self-hosted Coder deployment from the Cursor IDE, plus the Coder MCP server for managing and using the deployment: workspaces, templates, and Coder Agents.", "author": { "name": "Coder", diff --git a/plugins/coder/CHANGELOG.md b/plugins/coder/CHANGELOG.md index 4e752eb..67f5eb6 100644 --- a/plugins/coder/CHANGELOG.md +++ b/plugins/coder/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 0.3.0 + +- Made the plugin an [Agent Plugins 1.0](https://github.com/agentplugins/agent-plugins-spec) package so Codex, GitHub Copilot, and other conformant clients can load it: added a portable `plugin.json` next to `.cursor-plugin/plugin.json`, and added `$schema` and `type` to `mcp.json`. +- Re-vendored `coder/skills` at `3c99837`: the `workspaces` skill is now named `workspaces`, matching its directory, as the Agent Skills spec requires. It was `coder-workspaces`. +- `scripts/validate-template.mjs` now checks `plugin.json`, `mcp.json`, skill names, and that both manifests agree. CI also requires all three versions to match. +- The `coder` MCP server and `/coder-agent` still work only in Cursor: the URL uses the `${CODER_URL}` plugin variable, which Agent Plugins does not allow in `url`. + ## 0.2.0 - Added the Coder remote MCP server (`mcp.json`) and the `/coder-agent` command for delegating tasks to Coder Agents chats. Requires Coder v2.38 or later, with the `mcp-server-http` experiment and `CODER_OAUTH2_PROVIDER_ENABLE=true`. diff --git a/plugins/coder/README.md b/plugins/coder/README.md index fa66f2a..12a1495 100644 --- a/plugins/coder/README.md +++ b/plugins/coder/README.md @@ -6,6 +6,8 @@ and using the deployment: workspaces, templates, and Coder Agents. ## Included +- `plugin.json`: Agent Plugins 1.0 manifest, the portable metadata read by any conformant client +- `.cursor-plugin/plugin.json`: Cursor manifest, which adds `displayName`, `logo`, and the `CODER_URL` variable - `skills/workspaces/`, `skills/setup/`, `skills/templates/`, `skills/modules/`: vendored from [coder/skills](https://github.com/coder/skills) - `mcp.json`: Coder remote MCP server at `${CODER_URL}/api/experimental/mcp/http`, where `${CODER_URL}` is the plugin variable the user sets under **Configure** (not a shell environment variable). Gives the agent Coder's tools for workspaces, templates, files, and Coder Agents chats; `/coder-agent` builds on it. - `commands/coder-agent.md`: delegate a task to a Coder Agent and supervise it, through MCP diff --git a/plugins/coder/mcp.json b/plugins/coder/mcp.json index 515ea02..c262a69 100644 --- a/plugins/coder/mcp.json +++ b/plugins/coder/mcp.json @@ -1,6 +1,8 @@ { + "$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json", "mcpServers": { "coder": { + "type": "streamable-http", "url": "${CODER_URL}/api/experimental/mcp/http" } } diff --git a/plugins/coder/plugin.json b/plugins/coder/plugin.json new file mode 100644 index 0000000..721dc59 --- /dev/null +++ b/plugins/coder/plugin.json @@ -0,0 +1,26 @@ +{ + "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", + "name": "coder", + "version": "0.3.0", + "description": "Skills for installing, configuring, and operating a self-hosted Coder deployment from the Cursor IDE, plus the Coder MCP server for managing and using the deployment: workspaces, templates, and Coder Agents.", + "author": { + "name": "Coder", + "email": "support@coder.com", + "url": "https://coder.com" + }, + "homepage": "https://coder.com", + "repository": "https://github.com/coder/cursor-plugin", + "license": "MIT", + "keywords": [ + "coder", + "cloud development environments", + "remote development", + "workspaces", + "terraform", + "templates", + "mcp", + "self-hosted", + "ai agents", + "devops" + ] +} diff --git a/plugins/coder/skills/workspaces/SKILL.md b/plugins/coder/skills/workspaces/SKILL.md index 9462d1e..28dcef3 100644 --- a/plugins/coder/skills/workspaces/SKILL.md +++ b/plugins/coder/skills/workspaces/SKILL.md @@ -1,5 +1,5 @@ --- -name: coder-workspaces +name: workspaces description: > Use an existing Coder deployment from the command line with the `coder` CLI: list, inspect, create, start, stop, and delete From 23174135c9213022c0447675be6330a020b06a63 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Tue, 6 Oct 2026 21:01:20 +0500 Subject: [PATCH 2/5] feat: validate Agent Plugins manifests and document cross-client support Extend validate-template.mjs to check plugin.json and mcp.json against the closed Agent Plugins 1.0 schemas, require skill names to match their directories, and require plugin.json and .cursor-plugin/plugin.json to agree. Warn that a placeholder in an MCP url is honored only by Cursor. Add an "Other agents" section to the README. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_019YbnTXGyHn3KBrSw2R4D12 --- README.md | 12 ++ scripts/validate-template.mjs | 267 ++++++++++++++++++++++++++++++++++ 2 files changed, 279 insertions(+) diff --git a/README.md b/README.md index b5fcfcf..73b382b 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,18 @@ whatever session the `coder` CLI already has from `coder login`. | "Set up Coder on this VM." | Uses the `setup` skill to install and bootstrap a deployment. | | "Delegate this refactor to a Coder Agent and tell me when it's done." | Creates a Coder Agents chat with `coder_create_chat` and waits with `coder_await_chat`. | +## Other agents + +The plugin is also an [Agent Plugins 1.0](https://github.com/agentplugins/agent-plugins-spec) +package (`plugins/coder/plugin.json`), so clients that support the standard, +such as Codex and GitHub Copilot, can load the four skills. The skills work +the same everywhere. + +The Coder MCP server and `/coder-agent` are Cursor-only. The server URL uses +the `${CODER_URL}` plugin variable, which the standard does not allow in +`url`, and commands are not part of the standard. Other clients skip the MCP +server and keep the skills. + ## Self-hosted and air-gapped deployments Everything in this plugin operates against your own deployment. Nothing is diff --git a/scripts/validate-template.mjs b/scripts/validate-template.mjs index 5310b9e..19a4f2d 100644 --- a/scripts/validate-template.mjs +++ b/scripts/validate-template.mjs @@ -232,6 +232,272 @@ async function validateComponentFrontmatter(pluginDir, pluginName) { } } +// Agent Plugins 1.0.0 (https://github.com/agentplugins/agent-plugins-spec). +// The checks below mirror the closed schemas in spec/1.0.0.md so CI needs no +// network access or schema validator dependency. +const AGENT_PLUGIN_SCHEMA = "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json"; +const AGENT_MCP_SCHEMA = "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json"; +const agentPluginNamePattern = /^(?!.*(?:--|\.\.))[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/; +const agentSkillNamePattern = /^(?!.*--)[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/; +const agentManifestFields = [ + "$schema", + "name", + "version", + "description", + "author", + "homepage", + "repository", + "license", + "keywords", + "extensions", +]; +// Metadata that must be identical in plugin.json and .cursor-plugin/plugin.json. +const sharedManifestFields = [ + "name", + "version", + "description", + "author", + "homepage", + "repository", + "license", + "keywords", +]; + +function isPlainObject(value) { + return value !== null && typeof value === "object" && !Array.isArray(value); +} + +function isDeepEqual(a, b) { + return JSON.stringify(a) === JSON.stringify(b); +} + +function validateAgentManifest(manifest, pluginName) { + const label = `${pluginName}: plugin.json`; + + for (const key of Object.keys(manifest)) { + if (!agentManifestFields.includes(key)) { + addError( + `${label} has unknown top-level field "${key}". Move client-specific data under "extensions" or keep it in .cursor-plugin/plugin.json.` + ); + } + } + + if (manifest.$schema !== AGENT_PLUGIN_SCHEMA) { + addError(`${label} "$schema" must be "${AGENT_PLUGIN_SCHEMA}".`); + } + + if ( + typeof manifest.name !== "string" || + manifest.name.length === 0 || + manifest.name.length > 64 || + !agentPluginNamePattern.test(manifest.name) + ) { + addError( + `${label} "name" must be 1-64 lowercase alphanumerics, hyphens, or periods, start and end alphanumeric, with no "--" or "..".` + ); + } + + for (const key of ["version", "description", "homepage", "repository", "license"]) { + if (manifest[key] !== undefined && typeof manifest[key] !== "string") { + addError(`${label} "${key}" must be a string.`); + } + } + + if (manifest.author !== undefined) { + if (!isPlainObject(manifest.author)) { + addError(`${label} "author" must be an object.`); + } else { + for (const [key, value] of Object.entries(manifest.author)) { + if (!["name", "email", "url"].includes(key) || typeof value !== "string") { + addError(`${label} "author" may only contain string "name", "email", and "url" fields.`); + break; + } + } + } + } + + if ( + manifest.keywords !== undefined && + (!Array.isArray(manifest.keywords) || manifest.keywords.some((k) => typeof k !== "string")) + ) { + addError(`${label} "keywords" must be an array of strings.`); + } + + if (manifest.extensions !== undefined) { + if (!isPlainObject(manifest.extensions)) { + addError(`${label} "extensions" must be an object.`); + } else if (Object.values(manifest.extensions).some((value) => !isPlainObject(value))) { + addError(`${label} every "extensions" namespace value must be an object.`); + } + } +} + +function validateAgentMcpServer(server, serverName, pluginName) { + const label = `${pluginName}: mcp.json server "${serverName}"`; + if (!isPlainObject(server)) { + addError(`${label} must be an object.`); + return; + } + + const variants = { + stdio: { required: ["type", "command"], allowed: ["type", "command", "args", "env", "cwd"] }, + "streamable-http": { required: ["type", "url"], allowed: ["type", "url", "headers"] }, + sse: { required: ["type", "url"], allowed: ["type", "url", "headers"] }, + }; + const variant = variants[server.type]; + if (!variant) { + addError(`${label} "type" must be one of: ${Object.keys(variants).join(", ")}.`); + return; + } + + for (const key of variant.required) { + if (typeof server[key] !== "string" || server[key].length === 0) { + addError(`${label} requires a non-empty string "${key}".`); + } + } + for (const key of Object.keys(server)) { + if (!variant.allowed.includes(key)) { + addError(`${label} has field "${key}" that is not valid for type "${server.type}".`); + } + } + + if (server.type === "stdio") { + if (server.args !== undefined && (!Array.isArray(server.args) || server.args.some((a) => typeof a !== "string"))) { + addError(`${label} "args" must be an array of strings.`); + } + if (server.env !== undefined) { + if (!isPlainObject(server.env) || Object.values(server.env).some((v) => typeof v !== "string")) { + addError(`${label} "env" must be an object of strings.`); + } else if ("PLUGIN_ROOT" in server.env || "PLUGIN_DATA" in server.env) { + addError(`${label} "env" must not set PLUGIN_ROOT or PLUGIN_DATA.`); + } + } + if (server.cwd !== undefined && !/^(?:\.\/|\$\{PLUGIN_ROOT\}(?:\/|$)|\$\{PLUGIN_DATA\}(?:\/|$))/.test(server.cwd)) { + addError(`${label} "cwd" must start with "./", "\${PLUGIN_ROOT}", or "\${PLUGIN_DATA}".`); + } + } else { + if (server.headers !== undefined) { + if (!isPlainObject(server.headers) || Object.values(server.headers).some((v) => typeof v !== "string")) { + addError(`${label} "headers" must be an object of strings.`); + } + } + // The spec forbids placeholder expansion in "url", so a client that + // follows it skips this server. Cursor expands plugin variables itself. + if (typeof server.url === "string" && server.url.includes("${")) { + addWarning( + `${pluginName}: mcp.json server "${serverName}" url contains a placeholder. Agent Plugins forbids expansion in "url", so only Cursor will connect to it.` + ); + } + } +} + +function readSkillFrontmatter(content) { + const match = normalizeNewlines(content).match(/^---\n([\s\S]*?)\n---\n/); + if (!match) { + return {}; + } + const lines = match[1].split("\n"); + const fields = {}; + for (let i = 0; i < lines.length; i += 1) { + const field = lines[i].match(/^([A-Za-z][\w-]*):\s*(.*)$/); + if (!field) { + continue; + } + let value = field[2].trim(); + // Folded (>) and literal (|) block scalars continue on indented lines. + if (/^[>|][+-]?$/.test(value)) { + const parts = []; + while (i + 1 < lines.length && /^\s+\S/.test(lines[i + 1])) { + parts.push(lines[i + 1].trim()); + i += 1; + } + value = parts.join(" "); + } + fields[field[1]] = value.replace(/^(["'])(.*)\1$/, "$2"); + } + return fields; +} + +async function validateAgentSkills(pluginDir, pluginName) { + const skillsDir = path.join(pluginDir, "skills"); + if (!(await pathExists(skillsDir))) { + return; + } + const entries = await fs.readdir(skillsDir, { withFileTypes: true }); + for (const entry of entries) { + if (!entry.isDirectory()) { + continue; + } + const skillFile = path.join(skillsDir, entry.name, "SKILL.md"); + if (!(await pathExists(skillFile))) { + continue; + } + const label = `${pluginName}: skills/${entry.name}/SKILL.md`; + const fields = readSkillFrontmatter(await fs.readFile(skillFile, "utf8")); + if (fields.name !== entry.name) { + addError( + `${label} "name" (${JSON.stringify(fields.name)}) must match its directory name "${entry.name}". Fix it in coder/skills and re-vendor.` + ); + } + if ( + typeof fields.name === "string" && + (fields.name.length > 64 || !agentSkillNamePattern.test(fields.name)) + ) { + addError(`${label} "name" must be 1-64 lowercase alphanumerics and single hyphens, not starting or ending with a hyphen.`); + } + if (typeof fields.description !== "string" || fields.description.length === 0 || fields.description.length > 1024) { + addError(`${label} "description" must be 1-1024 characters.`); + } + } +} + +async function validateAgentPlugin(pluginDir, pluginName, cursorManifest) { + const manifestPath = path.join(pluginDir, "plugin.json"); + const manifest = await readJsonFile(manifestPath, `${pluginName} Agent Plugins manifest`); + if (manifest) { + if (!isPlainObject(manifest)) { + addError(`${pluginName}: plugin.json must contain a JSON object.`); + } else { + validateAgentManifest(manifest, pluginName); + for (const field of sharedManifestFields) { + if (!isDeepEqual(manifest[field], cursorManifest[field])) { + addError( + `${pluginName}: "${field}" differs between plugin.json and .cursor-plugin/plugin.json. Keep them identical.` + ); + } + } + } + } + + const mcpPath = path.join(pluginDir, "mcp.json"); + if (await pathExists(mcpPath)) { + const mcp = await readJsonFile(mcpPath, `${pluginName} MCP configuration`); + if (mcp) { + if (!isPlainObject(mcp)) { + addError(`${pluginName}: mcp.json must contain a JSON object.`); + } else { + for (const key of Object.keys(mcp)) { + if (key !== "$schema" && key !== "mcpServers") { + addError(`${pluginName}: mcp.json has unknown top-level field "${key}".`); + } + } + if (mcp.$schema !== AGENT_MCP_SCHEMA) { + addError(`${pluginName}: mcp.json "$schema" must be "${AGENT_MCP_SCHEMA}".`); + } + if (!isPlainObject(mcp.mcpServers)) { + addError(`${pluginName}: mcp.json "mcpServers" must be an object.`); + } else { + for (const [serverName, server] of Object.entries(mcp.mcpServers)) { + validateAgentMcpServer(server, serverName, pluginName); + } + } + } + } + } + + await validateAgentSkills(pluginDir, pluginName); +} + function resolveMarketplaceSource(source, pluginRoot) { if (typeof source !== "string" || source.length === 0) { return null; @@ -343,6 +609,7 @@ async function main() { } await validateComponentFrontmatter(pluginDir, entry.name); + await validateAgentPlugin(pluginDir, entry.name, pluginManifest); const hooksPath = path.join(pluginDir, "hooks", "hooks.json"); if (!(await pathExists(hooksPath))) { From f98ceabf0ceb44e45ee56a022c2fc979dab010fb Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Tue, 6 Oct 2026 21:19:42 +0500 Subject: [PATCH 3/5] feat: keep the URL-templated MCP server out of the portable package The spec forbids placeholders in an MCP url, and every Coder deployment has its own URL. Move the server from mcp.json to .cursor-plugin/mcp.json and point the Cursor manifest at it with mcpServers, which replaces root mcp.json discovery in Cursor. The portable package now has no mcp.json, so conformant clients load the skills and never see an invalid MCP entry. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_019YbnTXGyHn3KBrSw2R4D12 --- CONTRIBUTING.md | 2 +- plugins/coder/.cursor-plugin/mcp.json | 7 +++++++ plugins/coder/.cursor-plugin/plugin.json | 1 + plugins/coder/CHANGELOG.md | 4 ++-- plugins/coder/README.md | 4 ++-- 5 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 plugins/coder/.cursor-plugin/mcp.json diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index df822c8..c898c56 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,8 +7,8 @@ plugins/coder/ The Coder plugin plugin.json Agent Plugins 1.0 manifest (portable) .cursor-plugin/plugin.json Cursor manifest (canonical version) + .cursor-plugin/mcp.json Coder remote MCP server (Cursor only, used by /coder-agent) CHANGELOG.md Release history - mcp.json Coder remote MCP server (used by /coder-agent) skills/ vendored coder/skills (workspaces, setup, templates, modules) commands/ /coder-agent assets/logo.svg diff --git a/plugins/coder/.cursor-plugin/mcp.json b/plugins/coder/.cursor-plugin/mcp.json new file mode 100644 index 0000000..515ea02 --- /dev/null +++ b/plugins/coder/.cursor-plugin/mcp.json @@ -0,0 +1,7 @@ +{ + "mcpServers": { + "coder": { + "url": "${CODER_URL}/api/experimental/mcp/http" + } + } +} diff --git a/plugins/coder/.cursor-plugin/plugin.json b/plugins/coder/.cursor-plugin/plugin.json index e265eb7..1b74a78 100644 --- a/plugins/coder/.cursor-plugin/plugin.json +++ b/plugins/coder/.cursor-plugin/plugin.json @@ -24,6 +24,7 @@ "devops" ], "logo": "assets/logo.svg", + "mcpServers": ".cursor-plugin/mcp.json", "variables": { "type": "object", "properties": { diff --git a/plugins/coder/CHANGELOG.md b/plugins/coder/CHANGELOG.md index 67f5eb6..91702a6 100644 --- a/plugins/coder/CHANGELOG.md +++ b/plugins/coder/CHANGELOG.md @@ -2,10 +2,10 @@ ## 0.3.0 -- Made the plugin an [Agent Plugins 1.0](https://github.com/agentplugins/agent-plugins-spec) package so Codex, GitHub Copilot, and other conformant clients can load it: added a portable `plugin.json` next to `.cursor-plugin/plugin.json`, and added `$schema` and `type` to `mcp.json`. +- Made the plugin an [Agent Plugins 1.0](https://github.com/agentplugins/agent-plugins-spec) package so Codex, GitHub Copilot, and other conformant clients can load it: added a portable `plugin.json` next to `.cursor-plugin/plugin.json`. - Re-vendored `coder/skills` at `3c99837`: the `workspaces` skill is now named `workspaces`, matching its directory, as the Agent Skills spec requires. It was `coder-workspaces`. - `scripts/validate-template.mjs` now checks `plugin.json`, `mcp.json`, skill names, and that both manifests agree. CI also requires all three versions to match. -- The `coder` MCP server and `/coder-agent` still work only in Cursor: the URL uses the `${CODER_URL}` plugin variable, which Agent Plugins does not allow in `url`. +- Moved the Coder MCP server from `mcp.json` to `.cursor-plugin/mcp.json`, referenced by `mcpServers` in the Cursor manifest. Its URL uses the `${CODER_URL}` plugin variable, which Agent Plugins does not allow in `url`, so the server and `/coder-agent` work only in Cursor. Other clients no longer see an invalid MCP entry. ## 0.2.0 diff --git a/plugins/coder/README.md b/plugins/coder/README.md index 12a1495..7bb33d7 100644 --- a/plugins/coder/README.md +++ b/plugins/coder/README.md @@ -7,9 +7,9 @@ and using the deployment: workspaces, templates, and Coder Agents. ## Included - `plugin.json`: Agent Plugins 1.0 manifest, the portable metadata read by any conformant client -- `.cursor-plugin/plugin.json`: Cursor manifest, which adds `displayName`, `logo`, and the `CODER_URL` variable +- `.cursor-plugin/plugin.json`: Cursor manifest, which adds `displayName`, `logo`, `mcpServers`, and the `CODER_URL` variable - `skills/workspaces/`, `skills/setup/`, `skills/templates/`, `skills/modules/`: vendored from [coder/skills](https://github.com/coder/skills) -- `mcp.json`: Coder remote MCP server at `${CODER_URL}/api/experimental/mcp/http`, where `${CODER_URL}` is the plugin variable the user sets under **Configure** (not a shell environment variable). Gives the agent Coder's tools for workspaces, templates, files, and Coder Agents chats; `/coder-agent` builds on it. +- `.cursor-plugin/mcp.json`: Coder remote MCP server (Cursor only; referenced by `mcpServers` in the Cursor manifest) at `${CODER_URL}/api/experimental/mcp/http`, where `${CODER_URL}` is the plugin variable the user sets under **Configure** (not a shell environment variable). Gives the agent Coder's tools for workspaces, templates, files, and Coder Agents chats; `/coder-agent` builds on it. - `commands/coder-agent.md`: delegate a task to a Coder Agent and supervise it, through MCP See the [repository README](../../README.md) for setup and usage, and From ca4f545cf2b737d8f7568db4a268597caa47fa64 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Tue, 6 Oct 2026 21:19:44 +0500 Subject: [PATCH 4/5] chore: remove root mcp.json from the portable package The server now lives in .cursor-plugin/mcp.json. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_019YbnTXGyHn3KBrSw2R4D12 --- plugins/coder/mcp.json | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 plugins/coder/mcp.json diff --git a/plugins/coder/mcp.json b/plugins/coder/mcp.json deleted file mode 100644 index c262a69..0000000 --- a/plugins/coder/mcp.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json", - "mcpServers": { - "coder": { - "type": "streamable-http", - "url": "${CODER_URL}/api/experimental/mcp/http" - } - } -} From 07aff6269b26d3b1a68835952b31d096e2340c63 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Tue, 6 Oct 2026 21:20:48 +0500 Subject: [PATCH 5/5] docs: explain the Cursor-only MCP server; stop warning when mcpServers is set Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_019YbnTXGyHn3KBrSw2R4D12 --- README.md | 10 ++++++---- scripts/validate-template.mjs | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 73b382b..f1e8cc7 100644 --- a/README.md +++ b/README.md @@ -115,10 +115,12 @@ package (`plugins/coder/plugin.json`), so clients that support the standard, such as Codex and GitHub Copilot, can load the four skills. The skills work the same everywhere. -The Coder MCP server and `/coder-agent` are Cursor-only. The server URL uses -the `${CODER_URL}` plugin variable, which the standard does not allow in -`url`, and commands are not part of the standard. Other clients skip the MCP -server and keep the skills. +The Coder MCP server and `/coder-agent` are Cursor-only. Every deployment has +its own URL, and the standard allows no placeholder in an MCP `url`, so the +server is declared in `.cursor-plugin/mcp.json` with Cursor's `${CODER_URL}` +plugin variable. The portable package has no root `mcp.json`, so other +clients load the skills and ignore the server. Commands are not part of the +standard either. ## Self-hosted and air-gapped deployments diff --git a/scripts/validate-template.mjs b/scripts/validate-template.mjs index 19a4f2d..0536f29 100644 --- a/scripts/validate-template.mjs +++ b/scripts/validate-template.mjs @@ -617,7 +617,7 @@ async function main() { } const mcpPath = path.join(pluginDir, "mcp.json"); - if (!(await pathExists(mcpPath))) { + if (!pluginManifest.mcpServers && !(await pathExists(mcpPath))) { addWarning(`${entry.name}: no mcp.json file found (only needed when using MCP servers).`); } }