Resolves #893 (ChatGPT plugin directory ZIP) and #895 (Anthropic directory plugin bundle), and rewrites docs/stackql_release_process.md as the single source of truth for publishing a release across every surface.
What changes
Shared launcher. packaging/mcpb/npm/bin/stackql-mcp.js is now dependency-free: an inline zip reader (central directory walk, zip64 aware, stored and deflate) replaces adm-zip, so package.json has no dependencies and the lockfile is empty. It also honours STACKQL_PLUGIN_CONFIG_DIR (default ~/.stackql) and passes --env.file and --configfile from that directory, which the plugins relied on. Both plugin packages vendor this file byte-for-byte under server/bin/ next to server/platforms.json; the validators enforce equality, so the npm package stays the single source.
OpenAI plugin (packaging/openai-plugin/plugins/stackql): portable Agent Plugins layout (plugin.json with $schema and extensions.com.openai: 30-character shortDescription, support and terms URLs, six capabilities, three prompts, five positive and three negative review test cases on credential-free GitHub data, release notes, onboardingSkill, requires_local_executor; mcp.json with a type: stdio entry and ${PLUGIN_ROOT} paths) alongside the Codex overlay. The overlay .codex-plugin/plugin.json is now rendered from plugin.json, and validate.py fails if it drifts. make openai-plugin-zip VERSION=X.Y.Z builds dist/stackql-plugin-X.Y.Z.zip (plugin contents at the ZIP root, deterministic, hooks and app references and credentials refused, size checked).
Claude plugin (packaging/claude-plugin, new): .claude-plugin/plugin.json with the directory listing fields and the server declared inline as node ${CLAUDE_PLUGIN_ROOT}/server/bin/stackql-mcp.js, README (the listing description, states what the plugin runs, fetches and sends), LICENSE, icon, the same skill and payload. scripts/validate-claude-plugin.py applies the directory's rules (no top-level bin/, no lockfile or node_modules, 256 KiB per file, 512 files, text and images only, README of 40 words). make claude-plugin-mirror pushes it to stackql/stackql-claude-plugin through publish-mirror.sh (new vector; handles an empty mirror), because the directory reads plugins from a GitHub repo and holds every version of a subfolder plugin for a reviewer.
Renderer and release flow. render-platforms.sh gains the claude-plugin vector and a shared plugin payload step (launcher, pins, LICENSE, icon, packaging/mcpb/skills/stackql-setup/SKILL.md); make npm-manifest renders both plugins. The openai-plugin.yml workflow becomes plugins.yml: both validators and both smoke tests on four platforms, a render check against the published checksums, claude plugin validate --strict, and the ZIP as a run artifact. npm, PyPI and both plugin pins are bumped to the published 0.12.824 (the stamp from the last release dispatch was never committed).
Docs. docs/stackql_release_process.md now opens with a surfaces table (manual, semi-automatic, automatic) and adds steps 10 to 13: pin PR, ChatGPT directory upload, Claude plugin mirror and directory publish, assurance, plus a credentials table. packaging/mcpb/docs/anthropic-submission.md is rewritten for the developer portal (MCPB listings are deprecated; a local server is listed only inside a plugin bundle). listings.md, packaging/mcpb/README.md, CLAUDE.md, docs/install.md, docs/chatgpt-codex-plugin.md and packaging/openai-plugin/README.md updated to match.
CI. Pins the Linux Test job in build.yml to ubuntu-24.04: ubuntu-latest rolled to Ubuntu 26.04 while this PR's first run was in flight, and on that image the job's nginx apt-key import fails (gpg: /home/runner/.gnupg: directory does not exist) before any test runs. The amd64 Docker Test shards and the Docker Python Test also fail on 26.04: its newer Docker Compose writes container progress lines into the output the mocked robot suite captures, so 33 exact-output assertions fail, while the arm64 legs on ubuntu-24.04-arm pass. The dockertest amd64 leg and dockertestverify are pinned to 24.04 as well. The remaining ubuntu-latest jobs (Linux Build, Python package build, Docker Build) passed on 26.04 and stay unpinned.
Design choices to debate once
- Dependency-free launcher instead of vendoring
adm-zip (as Package and ship the ChatGPT plugin directory ZIP for the local stdio MCP server #893 proposed): avoids 20 third-party files in two plugin folders, the Anthropic "package install" hold, and a runtime dependency in the npm package. The regression test test_npm_arm64_extracts_exe_into_its_own_cache and the Windows smoke jobs exercise the new extractor on real bundles.
- Mirror repository for the Claude plugin rather than submitting the monorepo subfolder; the source of truth stays here. The repo does not exist yet (
gh repo create is in the checklist).
- The plugin version equals the StackQL version; metadata-only fixes ship with the next release.
- Not done here:
userConfig for the config directory (Cowork drops a server whose option has no default; left for a follow-up), the review.demo_recording_url (must be recorded; validate.py warns while unset), and stackql.io /terms and /support (Terms, support and OpenAI domain challenge pages for the ChatGPT plugin directory listing stackql.io#305).
Test plan
Local, Windows x64, against the installed 0.12.824 binary and the real stackql-mcp-windows-x64.mcpb:
- launcher extraction of
server/stackql.exe from the real bundle matches Python's zipfile byte-for-byte; spawn args carry --approot, --env.file, --configfile
python packaging/openai-plugin/scripts/validate.py and python packaging/mcpb/scripts/validate-claude-plugin.py pass
smoke-test.py passes for both vendored launchers (MCP handshake, tools/list, pull_provider, list_services, reload_credentials)
make openai-plugin-zip VERSION=0.12.824 writes a 10-entry ZIP that passes build-zip.py --check
claude plugin validate ./packaging/claude-plugin --strict (Claude Code 2.1.318, run from WSL): validation passed
CI: stdio-plugins (new), mcp-packaging PR validation (packaging regressions incl. the npm launcher test on ubuntu, windows and windows-arm; smoke-windows runs the npm launcher against freshly built bundles). No Go changes, so no robot scenario; the behaviour change is in packaging scripts covered by the workflows above.
🤖 Generated with Claude Code