From 155fac9ba247be60e0ab2e26b8ff6ae27c1a6966 Mon Sep 17 00:00:00 2001 From: MeekPhills <117056626+MeekPhills@users.noreply.github.com> Date: Sun, 23 Aug 2026 12:06:25 -0400 Subject: [PATCH] feat: register Operations MCP from plugins --- .claude-plugin/marketplace.json | 4 +-- README.md | 17 ++++++---- package-lock.json | 4 +-- package.json | 2 +- .../.claude-plugin/plugin.json | 3 +- plugins/claude/operations-pulse/.mcp.json | 11 +++++++ .../skills/operations-pulse/SKILL.md | 8 +++-- .../.codex-plugin/plugin.json | 4 +-- plugins/openai/operations-pulse/.mcp.json | 11 +++++++ .../skills/operations-pulse/SKILL.md | 8 +++-- scripts/sync-registrations.mjs | 22 +++++++++++++ scripts/validate.mjs | 25 +++++++++++--- skill/operations-pulse/SKILL.md | 8 +++-- tests/registration.test.mjs | 33 +++++++++++++++++-- 14 files changed, 128 insertions(+), 32 deletions(-) create mode 100644 plugins/claude/operations-pulse/.mcp.json create mode 100644 plugins/openai/operations-pulse/.mcp.json diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 029481d..73a5924 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -19,8 +19,8 @@ "name": "operations-pulse", "source": "./plugins/claude/operations-pulse", "strict": true, - "version": "0.1.0" + "version": "0.1.1" } ], - "version": "0.1.0" + "version": "0.1.1" } diff --git a/README.md b/README.md index dab455e..75f16a0 100644 --- a/README.md +++ b/README.md @@ -110,8 +110,8 @@ load. Removal requires `schedule uninstall --confirm-uninstall`. - `packages/core` — SQLite ledger and deterministic checks. - `packages/mcp` — MCP stdio server. - `skill/operations-pulse` — portable agent skill. -- `plugins/openai/operations-pulse` — generated Codex skill wrapper. -- `plugins/claude/operations-pulse` — generated Claude Code skill wrapper. +- `plugins/openai/operations-pulse` — generated Codex skill and MCP wrapper. +- `plugins/claude/operations-pulse` — generated Claude Code skill and MCP wrapper. - `.agents/plugins/marketplace.json` — repository-local Codex marketplace. - `.claude-plugin/marketplace.json` — repository-local Claude marketplace. - `mcp-registry/operations-pulse/server.json` — staged official MCP Registry metadata. @@ -153,11 +153,14 @@ npm run registration:sync npm run registration:check ``` -The wrappers intentionally register only the portable skill. They do not -install npm packages, register the MCP server, authenticate an account, or -change user-level host settings. The separately staged MCP identity is -`org.openlyuseful/operations-pulse`, backed by the future npm package -`@openly-useful/operations-pulse-mcp`. +The wrappers register the portable skill plus the immutable stdio command +`npx --yes @openly-useful/operations-pulse-mcp@0.1.0`. They do not authenticate +an account, install a scheduler, configure adapters, or collect credentials. +The corresponding MCP Registry identity is `org.openlyuseful/operations-pulse`. +After installing or upgrading the plugin, start a new Codex task or restart +Claude Code so the host refreshes its MCP tools. By default the MCP database is +`.operations-pulse/pulse.sqlite` under the host process working directory; +set `OPERATIONS_PULSE_DB` in the host environment to choose an explicit path. Local generation and validation are allowed. Public repository creation, package publication, MCP Registry submission, marketplace submission, diff --git a/package-lock.json b/package-lock.json index 1e600db..dec662c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "operations-pulse", - "version": "0.1.0", + "version": "0.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "operations-pulse", - "version": "0.1.0", + "version": "0.1.1", "license": "MIT", "workspaces": [ "packages/*" diff --git a/package.json b/package.json index d6406bb..b3b7688 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "operations-pulse", - "version": "0.1.0", + "version": "0.1.1", "private": true, "description": "Local-first operational heartbeats, durable tickets, and governed tool connections.", "license": "MIT", diff --git a/plugins/claude/operations-pulse/.claude-plugin/plugin.json b/plugins/claude/operations-pulse/.claude-plugin/plugin.json index eb58de8..9403881 100644 --- a/plugins/claude/operations-pulse/.claude-plugin/plugin.json +++ b/plugins/claude/operations-pulse/.claude-plugin/plugin.json @@ -8,8 +8,9 @@ "description": "Run governed local operational heartbeats and maintain durable ticket memory.", "homepage": "https://openlyuseful.org", "license": "MIT", + "mcpServers": "./.mcp.json", "name": "operations-pulse", "repository": "https://github.com/Openly-Useful/operations-pulse", "skills": "./skills/", - "version": "0.1.0" + "version": "0.1.1" } diff --git a/plugins/claude/operations-pulse/.mcp.json b/plugins/claude/operations-pulse/.mcp.json new file mode 100644 index 0000000..b12c0b8 --- /dev/null +++ b/plugins/claude/operations-pulse/.mcp.json @@ -0,0 +1,11 @@ +{ + "mcpServers": { + "operations-pulse": { + "args": [ + "--yes", + "@openly-useful/operations-pulse-mcp@0.1.0" + ], + "command": "npx" + } + } +} diff --git a/plugins/claude/operations-pulse/skills/operations-pulse/SKILL.md b/plugins/claude/operations-pulse/skills/operations-pulse/SKILL.md index 3a113c0..1945d93 100644 --- a/plugins/claude/operations-pulse/skills/operations-pulse/SKILL.md +++ b/plugins/claude/operations-pulse/skills/operations-pulse/SKILL.md @@ -39,9 +39,11 @@ read-only except for storing the pulse record. ## Run locally -These commands are for a source checkout after building the repository. A -marketplace plugin installs the portable skill only; it does not install an -npm package, register an MCP server, or change host settings. +These commands are for a source checkout after building the repository. The +marketplace plugin installs the portable skill and registers the pinned +`@openly-useful/operations-pulse-mcp@0.1.0` stdio server through `npx`. Start a +new task after install or upgrade so the host refreshes its MCP tool registry. +The plugin does not install a scheduler, configure adapters, or collect credentials. ```bash node packages/core/dist/cli.js init diff --git a/plugins/openai/operations-pulse/.codex-plugin/plugin.json b/plugins/openai/operations-pulse/.codex-plugin/plugin.json index 290fa4b..ceb11d1 100644 --- a/plugins/openai/operations-pulse/.codex-plugin/plugin.json +++ b/plugins/openai/operations-pulse/.codex-plugin/plugin.json @@ -22,13 +22,13 @@ "longDescription": "Run local repository, documentation, and log checks; correlate signals with durable ticket memory; and explain governed connection options without treating agent output as authority.", "privacyPolicyURL": "https://openlyuseful.org/legal/privacy", "shortDescription": "Governed operational heartbeats and ticket memory", - "supportURL": "https://openlyuseful.org/support", "termsOfServiceURL": "https://openlyuseful.org/legal/terms", "websiteURL": "https://openlyuseful.org" }, "license": "MIT", + "mcpServers": "./.mcp.json", "name": "operations-pulse", "repository": "https://github.com/Openly-Useful/operations-pulse", "skills": "./skills/", - "version": "0.1.0" + "version": "0.1.1" } diff --git a/plugins/openai/operations-pulse/.mcp.json b/plugins/openai/operations-pulse/.mcp.json new file mode 100644 index 0000000..b12c0b8 --- /dev/null +++ b/plugins/openai/operations-pulse/.mcp.json @@ -0,0 +1,11 @@ +{ + "mcpServers": { + "operations-pulse": { + "args": [ + "--yes", + "@openly-useful/operations-pulse-mcp@0.1.0" + ], + "command": "npx" + } + } +} diff --git a/plugins/openai/operations-pulse/skills/operations-pulse/SKILL.md b/plugins/openai/operations-pulse/skills/operations-pulse/SKILL.md index 3a113c0..1945d93 100644 --- a/plugins/openai/operations-pulse/skills/operations-pulse/SKILL.md +++ b/plugins/openai/operations-pulse/skills/operations-pulse/SKILL.md @@ -39,9 +39,11 @@ read-only except for storing the pulse record. ## Run locally -These commands are for a source checkout after building the repository. A -marketplace plugin installs the portable skill only; it does not install an -npm package, register an MCP server, or change host settings. +These commands are for a source checkout after building the repository. The +marketplace plugin installs the portable skill and registers the pinned +`@openly-useful/operations-pulse-mcp@0.1.0` stdio server through `npx`. Start a +new task after install or upgrade so the host refreshes its MCP tool registry. +The plugin does not install a scheduler, configure adapters, or collect credentials. ```bash node packages/core/dist/cli.js init diff --git a/scripts/sync-registrations.mjs b/scripts/sync-registrations.mjs index cabd611..8ad69a1 100644 --- a/scripts/sync-registrations.mjs +++ b/scripts/sync-registrations.mjs @@ -8,6 +8,7 @@ import { readdirSync, readFileSync, rmSync, + writeFileSync, } from "node:fs"; import { dirname, join, relative, resolve } from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; @@ -19,6 +20,17 @@ const wrapperSkillRoots = ["openai", "claude"].map((host) => ); const canonicalCatalogRoot = join(registrationRoot, "catalog"); const packagedCatalogRoot = join(registrationRoot, "packages", "mcp", "catalog"); +const pluginMcpConfigPaths = ["openai", "claude"].map((host) => + join(registrationRoot, "plugins", host, "operations-pulse", ".mcp.json"), +); +const pluginMcpConfig = Buffer.from(`${JSON.stringify({ + mcpServers: { + "operations-pulse": { + args: ["--yes", "@openly-useful/operations-pulse-mcp@0.1.0"], + command: "npx", + }, + }, +}, null, 2)}\n`); function walkFiles(directory) { const files = []; @@ -63,6 +75,12 @@ export function registrationSyncErrors() { if (!expected.has(path)) errors.push(`Unexpected generated artifact file: ${relative(registrationRoot, join(target, path))}`); } } + for (const path of pluginMcpConfigPaths) { + if (!existsSync(path)) errors.push(`Generated artifact is missing ${relative(registrationRoot, path)}`); + else if (!readFileSync(path).equals(pluginMcpConfig)) { + errors.push(`Generated artifact drift: ${relative(registrationRoot, path)}`); + } + } return errors; } @@ -85,6 +103,10 @@ export function writeRegistrationSkillCopies() { count += 1; } } + for (const path of pluginMcpConfigPaths) { + writeFileSync(path, pluginMcpConfig); + count += 1; + } return count; } diff --git a/scripts/validate.mjs b/scripts/validate.mjs index 778a4a5..04e72b6 100644 --- a/scripts/validate.mjs +++ b/scripts/validate.mjs @@ -32,6 +32,8 @@ const codexMarketplace = json(".agents/plugins/marketplace.json"); const claudeMarketplace = json(".claude-plugin/marketplace.json"); const codexPlugin = json("plugins/openai/operations-pulse/.codex-plugin/plugin.json"); const claudePlugin = json("plugins/claude/operations-pulse/.claude-plugin/plugin.json"); +const codexMcp = json("plugins/openai/operations-pulse/.mcp.json"); +const claudeMcp = json("plugins/claude/operations-pulse/.mcp.json"); const expectedPublisher = { name: "Openly Useful", @@ -47,6 +49,14 @@ const expectedPolicies = { const expectedRepository = "https://github.com/Openly-Useful/operations-pulse"; const expectedMcpName = "org.openlyuseful/operations-pulse"; const expectedMcpPackage = "@openly-useful/operations-pulse-mcp"; +const expectedPluginMcp = { + mcpServers: { + "operations-pulse": { + args: ["--yes", "@openly-useful/operations-pulse-mcp@0.1.0"], + command: "npx", + }, + }, +}; function same(left, right) { return JSON.stringify(left) === JSON.stringify(right); @@ -183,8 +193,8 @@ if (publisher.lastUpdated !== "2026-08-23") { fail("Publisher mirror projection date is not current."); } -if (rootPackage.version !== corePackage.version || rootPackage.version !== mcpPackage.version) { - fail("Root, core, and MCP package versions must agree."); +if (rootPackage.version !== "0.1.1" || corePackage.version !== "0.1.0" || mcpPackage.version !== "0.1.0") { + fail("Plugin release must be 0.1.1 while immutable npm artifacts remain 0.1.0."); } if (mcpPackage.name !== expectedMcpPackage || mcpPackage.mcpName !== expectedMcpName) { fail("MCP npm package name or official MCP Registry identity is invalid."); @@ -240,6 +250,7 @@ if (claudeMarketplace.$schema !== "https://json.schemastore.org/claude-code-mark || claudeMarketplace.version !== rootPackage.version || claudeMarketplace.plugins?.length !== 1 || claudeMarketplace.plugins[0]?.source !== "./plugins/claude/operations-pulse" + || claudeMarketplace.plugins[0]?.version !== rootPackage.version || claudeMarketplace.plugins[0]?.strict !== true) { fail("Claude marketplace metadata is invalid."); } @@ -253,21 +264,23 @@ for (const [label, manifest] of [["Codex", codexPlugin], ["Claude", claudePlugin || manifest.homepage !== expectedPublisher.url || manifest.repository !== expectedRepository || manifest.skills !== "./skills/" - || "mcpServers" in manifest) { - fail(`${label} plugin manifest identity or skill-only boundary is invalid.`); + || manifest.mcpServers !== "./.mcp.json") { + fail(`${label} plugin manifest identity or MCP registration boundary is invalid.`); } validateAuthor(manifest.author, `${label} plugin author`); } if (codexPlugin.interface?.developerName !== expectedPublisher.name || codexPlugin.interface?.privacyPolicyURL !== expectedPolicies.privacy || codexPlugin.interface?.termsOfServiceURL !== expectedPolicies.terms - || codexPlugin.interface?.supportURL !== expectedPolicies.support || codexPlugin.interface?.websiteURL !== expectedPublisher.url) { fail("Codex public interface URLs must derive from the publisher record."); } if (claudePlugin.$schema !== "https://json.schemastore.org/claude-code-plugin-manifest.json") { fail("Claude plugin manifest schema is invalid."); } +if (!same(codexMcp, expectedPluginMcp) || !same(claudeMcp, expectedPluginMcp)) { + fail("Codex and Claude plugins must pin the immutable Operations Pulse MCP stdio package."); +} for (const error of registrationSyncErrors()) fail(error); @@ -281,6 +294,8 @@ for (const required of [ "publisher/publisher.json", ".agents/plugins/marketplace.json", ".claude-plugin/marketplace.json", + "plugins/openai/operations-pulse/.mcp.json", + "plugins/claude/operations-pulse/.mcp.json", "mcp-registry/operations-pulse/server.json", "packages/mcp/README.md", "packages/mcp/LICENSE", diff --git a/skill/operations-pulse/SKILL.md b/skill/operations-pulse/SKILL.md index 3a113c0..1945d93 100644 --- a/skill/operations-pulse/SKILL.md +++ b/skill/operations-pulse/SKILL.md @@ -39,9 +39,11 @@ read-only except for storing the pulse record. ## Run locally -These commands are for a source checkout after building the repository. A -marketplace plugin installs the portable skill only; it does not install an -npm package, register an MCP server, or change host settings. +These commands are for a source checkout after building the repository. The +marketplace plugin installs the portable skill and registers the pinned +`@openly-useful/operations-pulse-mcp@0.1.0` stdio server through `npx`. Start a +new task after install or upgrade so the host refreshes its MCP tool registry. +The plugin does not install a scheduler, configure adapters, or collect credentials. ```bash node packages/core/dist/cli.js init diff --git a/tests/registration.test.mjs b/tests/registration.test.mjs index 5376fbf..3616741 100644 --- a/tests/registration.test.mjs +++ b/tests/registration.test.mjs @@ -37,7 +37,7 @@ test("publisher mirror keeps the one-entity boundary founder-operated and format ]); }); -test("Codex and Claude catalogs point to self-contained generated skill wrappers", () => { +test("Codex and Claude catalogs point to generated plugin wrappers", () => { const codex = json(".agents/plugins/marketplace.json"); const claude = json(".claude-plugin/marketplace.json"); const version = json("package.json").version; @@ -73,11 +73,10 @@ test("provider wrapper manifests derive publisher identity and policy URLs", () assert.equal(manifest.version, version); assert.equal(manifest.skills, "./skills/"); assert.deepEqual(manifest.author, author); - assert.equal("mcpServers" in manifest, false); + assert.equal(manifest.mcpServers, "./.mcp.json"); } assert.equal(openai.interface.privacyPolicyURL, publisher.policies.privacy); assert.equal(openai.interface.termsOfServiceURL, publisher.policies.terms); - assert.equal(openai.interface.supportURL, publisher.policies.support); }); test("official MCP Registry and npm identities agree", () => { @@ -96,6 +95,34 @@ test("official MCP Registry and npm identities agree", () => { assert.equal(registry.packages[0].transport.type, "stdio"); }); +test("Codex and Claude plugins register the immutable MCP stdio package", () => { + const pluginVersion = json("package.json").version; + const mcpPackage = json("packages/mcp/package.json"); + const codexPlugin = json("plugins/openai/operations-pulse/.codex-plugin/plugin.json"); + const claudePlugin = json("plugins/claude/operations-pulse/.claude-plugin/plugin.json"); + const claudeMarketplace = json(".claude-plugin/marketplace.json"); + const expectedMcp = { + mcpServers: { + "operations-pulse": { + args: ["--yes", "@openly-useful/operations-pulse-mcp@0.1.0"], + command: "npx", + }, + }, + }; + + assert.equal(pluginVersion, "0.1.1"); + assert.equal(mcpPackage.version, "0.1.0"); + assert.equal(codexPlugin.version, pluginVersion); + assert.equal(claudePlugin.version, pluginVersion); + assert.equal(claudeMarketplace.version, pluginVersion); + assert.equal(claudeMarketplace.plugins[0].version, pluginVersion); + assert.equal(codexPlugin.mcpServers, "./.mcp.json"); + assert.equal(claudePlugin.mcpServers, "./.mcp.json"); + assert.deepEqual(json("plugins/openai/operations-pulse/.mcp.json"), expectedMcp); + assert.deepEqual(json("plugins/claude/operations-pulse/.mcp.json"), expectedMcp); + assert.equal("env" in expectedMcp.mcpServers["operations-pulse"], false); +}); + test("npm artifact gate accepts founder authorization while provider review remains tracked", () => { const publisher = json("publisher/publisher.json"); assert.deepEqual(publicationErrors(publisher), []);