From 4edc73f39e1c258aa03b4c76edd20818fc595a62 Mon Sep 17 00:00:00 2001 From: Ned Martorell <32619850+nmartorell@users.noreply.github.com> Date: Mon, 10 Aug 2026 22:20:32 +0200 Subject: [PATCH 1/2] refactor(plugins): migrate codex package to agent plugins --- .agents/plugins/marketplace.json | 20 ++++++++++ .codex-plugin/plugin.json | 48 ------------------------ .github/workflows/bump.yml | 3 +- AGENTS.md | 2 +- CODING_STANDARDS_AND_STRUCTURE.md | 4 +- README.md | 6 +-- RELEASE.md | 2 +- cli/cli.py | 2 +- mcp.json | 4 +- plugin.json | 22 ++++++++++- pyproject.toml | 1 - tests/test_agent_plugins.py | 61 ++++++++++++++++++++++++++----- 12 files changed, 104 insertions(+), 71 deletions(-) create mode 100644 .agents/plugins/marketplace.json delete mode 100644 .codex-plugin/plugin.json diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json new file mode 100644 index 00000000..999f7d80 --- /dev/null +++ b/.agents/plugins/marketplace.json @@ -0,0 +1,20 @@ +{ + "name": "dataiku", + "interface": { + "displayName": "Dataiku" + }, + "plugins": [ + { + "name": "dataiku-headless", + "source": { + "source": "local", + "path": "./" + }, + "policy": { + "installation": "AVAILABLE", + "authentication": "ON_USE" + }, + "category": "developer-tools" + } + ] +} diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json deleted file mode 100644 index bd1a0277..00000000 --- a/.codex-plugin/plugin.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "name": "dataiku-headless", - "version": "0.2.0", - "description": "Connect your agent to Dataiku: inspect projects, datasets, recipes, ML, and agents with typed MCP tools, and drive Dataiku Cobuild to build project-level assets.", - "author": { - "name": "Dataiku", - "url": "https://www.dataiku.com/" - }, - "homepage": "https://github.com/dataiku/dataiku-headless", - "repository": "https://github.com/dataiku/dataiku-headless", - "license": "Apache-2.0", - "keywords": ["dataiku", "cobuild"], - "skills": "./skills/", - "mcpServers": { - "dataiku": { - "type": "stdio", - "command": "sh", - "args": ["./bin/launcher.sh"], - "cwd": ".", - "env": { - "DKU_MCP_TRANSPORT": "stdio" - }, - "env_vars": [ - "DKU_INSTANCE_NAME", - "DKU_DSS_URL", - "DKU_API_KEY", - "DKU_NO_CHECK_CERTIFICATE", - "DKU_CONFIG_FILE" - ] - } - }, - "interface": { - "displayName": "Dataiku Headless", - "shortDescription": "Inspect and build Dataiku projects with Cobuild", - "longDescription": "Connect your agent to Dataiku: inspect projects, datasets, recipes, ML, and agents with typed MCP tools, and drive Dataiku Cobuild to build project-level assets.", - "developerName": "Dataiku", - "category": "developer-tools", - "capabilities": ["Interactive", "Write"], - "websiteURL": "https://github.com/dataiku/dataiku-headless", - "defaultPrompt": [ - "Connect to a Dataiku instance", - "How many projects are in this Dataiku instance?" - ], - "brandColor": "#00A6A6", - "composerIcon": "./docs/assets/logo_black.png", - "logo": "./docs/assets/logo_black.png" - } -} diff --git a/.github/workflows/bump.yml b/.github/workflows/bump.yml index 94c6ef60..8ab8a404 100644 --- a/.github/workflows/bump.yml +++ b/.github/workflows/bump.yml @@ -104,9 +104,8 @@ jobs: project=$(python3 -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])') portable=$(jq -r '.version' plugin.json) claude=$(jq -r '.version' .claude-plugin/plugin.json) - codex=$(jq -r '.version' .codex-plugin/plugin.json) - for pair in "portable:${portable}" "claude:${claude}" "codex:${codex}"; do + for pair in "portable:${portable}" "claude:${claude}"; do name=${pair%%:*} found=${pair#*:} if [ "${found}" != "${project}" ]; then diff --git a/AGENTS.md b/AGENTS.md index b45e59f8..ba1e2d8c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -24,7 +24,7 @@ Use this file when changing this repository. It is not an operating guide for us - Tool registration imports: `dataiku_mcp/__init__.py`. - Project dependencies, Python support, version, and CLI entry points: `pyproject.toml`. - Standalone server dependency pins and Python floor: the PEP 723 block in `bin/run_mcp.py`. -- Portable Agent Plugins package: root `plugin.json` + `mcp.json` (skills under `skills/`). Harness-specific launch config: `.mcp.json`, `.claude-plugin/plugin.json`, and `.codex-plugin/plugin.json`. +- Portable Agent Plugins package: root `plugin.json` + `mcp.json` (skills under `skills/`). Harness-specific launch config: `.mcp.json` and `.claude-plugin/plugin.json`. - User-facing installation and architecture overview: `README.md`. - Release behavior: `RELEASE.md` and `.github/workflows/bump.yml`. - CI behavior: `.github/workflows/ci.yml` and `.github/workflows/pr-title.yml`. diff --git a/CODING_STANDARDS_AND_STRUCTURE.md b/CODING_STANDARDS_AND_STRUCTURE.md index 7524f3e8..0288d238 100644 --- a/CODING_STANDARDS_AND_STRUCTURE.md +++ b/CODING_STANDARDS_AND_STRUCTURE.md @@ -32,7 +32,7 @@ export DKU_API_KEY="your-api-key" | Workflow prompts | `dataiku_mcp/prompts/workflows.py` | | Project/dataset/folder/recipe/ML skills | `skills/**/SKILL.md` | | Cobuild conversation tools | `dataiku_mcp/tools/cobuild.py` | -| Portable Agent Plugins package | root `plugin.json` + `mcp.json` (keep harness manifests in sync for MCP launch) | +| Portable Agent Plugins package | root `plugin.json` + `mcp.json` (keep the Claude compatibility manifest in sync for MCP launch) | ## Error Handling - Prefer simple, readable tool handlers: keep top-level control flow short, avoid repeated Dataiku lookups, and use local helpers only when they improve clarity. @@ -95,7 +95,7 @@ uv run --quiet ./bin/run_mcp.py # skip the launcher, straight to the server There are two files, and the split matters: - **`bin/run_mcp.py`** is the server entry point. It carries [PEP 723](https://peps.python.org/pep-0723/) inline metadata — pinned dependencies and `requires-python` — so uv can build its runtime environment with no project install. -- **`bin/launcher.sh`** is what every manifest (`mcp.json`, `.mcp.json`, `.claude-plugin`, `.codex-plugin`, root Agent Plugins package) actually runs, and the only launcher. It picks a runtime in three tiers and `exec`s the server on the first that works, or exits non-zero with install instructions: +- **`bin/launcher.sh`** is what every manifest (`mcp.json`, `.mcp.json`, `.claude-plugin`, and the root Agent Plugins package) actually runs, and the only launcher. It picks a runtime in three tiers and `exec`s the server on the first that works, or exits non-zero with install instructions: 1. `uv run`, if a `uv` on `PATH` answers `uv --version`. 2. A venv under `${PLUGIN_DATA}` (falling back to `${CLAUDE_PLUGIN_DATA}`, then `$PLUGIN_ROOT/.deps`) with the pinned dependencies pip-installed into it, built by the first interpreter that satisfies `requires-python`. Candidates are deduplicated by resolved path, so aliases of one broken interpreter are not retried a dozen times. 3. `@dataiku/uv@0.12.0` through `npx` or `pnpx`, probed with `--help` — a runner on `PATH` still has to be able to fetch the package. diff --git a/README.md b/README.md index 78de5a4d..88a11f73 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Each plugin bundles the skills and starts the same local `stdio` MCP server. The This repository is an [Agent Plugins](https://agent-plugins.org/) v1.0.0 package: root `plugin.json`, root `mcp.json`, and Agent Skills under `skills/`. Clients that implement the standard can load the portable core directly from this directory (stdio MCP currently assumes a POSIX host with `sh`, matching the existing Claude/Codex launch path). -Harness-specific manifests (`.claude-plugin/`, `.codex-plugin/`, …) remain for install paths those clients already support. They are parallel legacy packaging, not reverse-domain Agent Plugins extension directories; the portable files are the cross-client floor. +The remaining `.claude-plugin/` directory is Claude Code compatibility packaging. Codex uses the portable package and the repository marketplace at `.agents/plugins/marketplace.json`; Codex-specific presentation metadata is kept in the portable manifest's `com.openai` extension. ### Claude Code CLI @@ -235,11 +235,11 @@ uv run dataiku-headless │ └── run_mcp.py # Server entry point: PEP 723 script pinning the runtime deps inline ├── plugin.json # Agent Plugins v1.0.0 portable manifest ├── mcp.json # Agent Plugins portable stdio MCP config +├── .agents/plugins/ +│ └── marketplace.json # Codex repository marketplace (single-plugin, source: "./") ├── .claude-plugin/ │ ├── plugin.json # Claude Code plugin manifest (skills + unconfigured stdio MCP) │ └── marketplace.json # Marketplace catalog (single-plugin, source: "./") -├── .codex-plugin/ -│ └── plugin.json # Codex manifest with skills, stdio MCP, and env_vars passthrough ├── .mcp.json # Shared MCP config (sh ./bin/launcher.sh) for contributor dogfooding ├── CODING_STANDARDS_AND_STRUCTURE.md # Contributor guide └── pyproject.toml diff --git a/RELEASE.md b/RELEASE.md index db7d57a0..59ac86b8 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -19,7 +19,7 @@ artifacts: The version number still matters even without an index: Commitizen keeps it in lockstep across `pyproject.toml` and the plugin manifests (portable Agent Plugins -`plugin.json`, plus the Claude Code and Codex compatibility manifests), and the +`plugin.json` plus the Claude Code compatibility manifest), and the manifest version is how a harness notices there's a newer plugin to install. `bump.yml` bumps locally first (`push: false`), verifies that lockstep held across all manifests (and that Agent Plugins `$schema` URLs were not rewritten), diff --git a/cli/cli.py b/cli/cli.py index bdd8cc31..8a39ea64 100644 --- a/cli/cli.py +++ b/cli/cli.py @@ -4,7 +4,7 @@ * ``serve`` — run the FastMCP server (the default when no subcommand is given, preserving the legacy ``dataiku-headless`` behavior). -Skills ship in the plugin's standard ``skills/`` directory. Claude Code discovers that directory automatically, Codex declares it in ``.codex-plugin/plugin.json``, and Cortex Code consumes the compatible Claude plugin layout. Anyone cloning this repo directly already has ``skills/`` on disk to point their harness at, so this CLI doesn't duplicate that as a copy command. +Skills ship in the plugin's standard ``skills/`` directory. Claude Code discovers that directory automatically, Codex discovers it through the root Agent Plugins package, and Cortex Code consumes the compatible Claude plugin layout. Anyone cloning this repo directly already has ``skills/`` on disk to point their harness at, so this CLI doesn't duplicate that as a copy command. """ from __future__ import annotations diff --git a/mcp.json b/mcp.json index c354c3eb..42f67c73 100644 --- a/mcp.json +++ b/mcp.json @@ -6,8 +6,10 @@ "command": "sh", "args": ["${PLUGIN_ROOT}/bin/launcher.sh"], "env": { + "DKU_MCP_TRANSPORT": "stdio", "UV_CACHE_DIR": "${PLUGIN_DATA}/uv-cache" - } + }, + "cwd": "${PLUGIN_ROOT}" } } } diff --git a/plugin.json b/plugin.json index d753df83..dc102f33 100644 --- a/plugin.json +++ b/plugin.json @@ -10,5 +10,25 @@ "homepage": "https://github.com/dataiku/dataiku-headless", "repository": "https://github.com/dataiku/dataiku-headless", "license": "Apache-2.0", - "keywords": ["dataiku", "cobuild", "mcp", "agent"] + "keywords": ["dataiku", "cobuild", "mcp", "agent"], + "extensions": { + "com.openai": { + "interface": { + "displayName": "Dataiku Headless", + "shortDescription": "Inspect and build Dataiku projects with Cobuild", + "longDescription": "Connect your agent to Dataiku: inspect projects, datasets, recipes, ML, and agents with typed MCP tools, and drive Dataiku Cobuild to build project-level assets.", + "developerName": "Dataiku", + "category": "developer-tools", + "capabilities": ["Interactive", "Write"], + "websiteURL": "https://github.com/dataiku/dataiku-headless", + "defaultPrompt": [ + "Connect to a Dataiku instance", + "How many projects are in this Dataiku instance?" + ], + "brandColor": "#00A6A6", + "composerIcon": "./docs/assets/logo_black.png", + "logo": "./docs/assets/logo_black.png" + } + } + } } diff --git a/pyproject.toml b/pyproject.toml index 757637e7..502350e5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,7 +77,6 @@ major_version_zero = true version_files = [ "plugin.json:\"version\":", ".claude-plugin/plugin.json:\"version\":", - ".codex-plugin/plugin.json:\"version\":", ] [dependency-groups] diff --git a/tests/test_agent_plugins.py b/tests/test_agent_plugins.py index 350b2720..ff19a028 100644 --- a/tests/test_agent_plugins.py +++ b/tests/test_agent_plugins.py @@ -1,9 +1,9 @@ """Agent Plugins v1.0.0 portable package contract. This repo ships as an Agent Plugins package (root ``plugin.json`` + ``mcp.json`` -+ ``skills/``) while retaining harness-specific manifests under -``.claude-plugin/`` and ``.codex-plugin/``. These tests pin the portable floor -and keep version fields in lockstep with ``[project].version``. ++ ``skills/``) while retaining a Claude Code compatibility manifest under +``.claude-plugin/``. These tests pin the portable floor and keep version fields +in lockstep with ``[project].version``. """ from __future__ import annotations @@ -17,6 +17,7 @@ PLUGIN_SCHEMA = "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json" MCP_SCHEMA = "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json" +MARKETPLACE_PATH = ROOT / ".agents" / "plugins" / "marketplace.json" # Closed portable manifest fields (Agent Plugins §5.2). PLUGIN_TOP_LEVEL = { @@ -71,6 +72,27 @@ def test_portable_plugin_manifest_is_agent_plugins_v1(): assert set(author) <= {"name", "email", "url"} assert all(isinstance(v, str) for v in author.values()) + assert manifest["extensions"] == { + "com.openai": { + "interface": { + "displayName": "Dataiku Headless", + "shortDescription": "Inspect and build Dataiku projects with Cobuild", + "longDescription": manifest["description"], + "developerName": "Dataiku", + "category": "developer-tools", + "capabilities": ["Interactive", "Write"], + "websiteURL": "https://github.com/dataiku/dataiku-headless", + "defaultPrompt": [ + "Connect to a Dataiku instance", + "How many projects are in this Dataiku instance?", + ], + "brandColor": "#00A6A6", + "composerIcon": "./docs/assets/logo_black.png", + "logo": "./docs/assets/logo_black.png", + } + } + } + def test_portable_mcp_config_is_agent_plugins_v1_stdio(): config = _load_json(ROOT / "mcp.json") @@ -93,12 +115,32 @@ def test_portable_mcp_config_is_agent_plugins_v1_stdio(): # Reserved names are client-supplied only (Agent Plugins §9.2). assert "PLUGIN_ROOT" not in env assert "PLUGIN_DATA" not in env + assert env.get("DKU_MCP_TRANSPORT") == "stdio" assert env.get("UV_CACHE_DIR") == "${PLUGIN_DATA}/uv-cache" - - cwd = server.get("cwd") - if cwd is not None: - assert _CWD_RE.match(cwd), cwd - assert ".." not in cwd + assert server["cwd"] == "${PLUGIN_ROOT}" + assert _CWD_RE.match(server["cwd"]) + + +def test_codex_marketplace_loads_the_repository_root_plugin(): + marketplace = _load_json(MARKETPLACE_PATH) + + assert marketplace == { + "name": "dataiku", + "interface": {"displayName": "Dataiku"}, + "plugins": [ + { + "name": "dataiku-headless", + "source": {"source": "local", "path": "./"}, + "policy": { + "installation": "AVAILABLE", + "authentication": "ON_USE", + }, + "category": "developer-tools", + } + ], + } + assert (MARKETPLACE_PATH.parents[2] / "plugin.json").is_file() + assert not (ROOT / ".codex-plugin" / "plugin.json").exists() def test_plugin_and_mcp_schema_versions_match(): @@ -144,8 +186,7 @@ def test_plugin_versions_match_project_version(): expected = _project_version() portable = _load_json(ROOT / "plugin.json")["version"] claude = _load_json(ROOT / ".claude-plugin" / "plugin.json")["version"] - codex = _load_json(ROOT / ".codex-plugin" / "plugin.json")["version"] - assert portable == claude == codex == expected + assert portable == claude == expected def test_commitizen_version_selector_preserves_schema_urls(): From f4a79f1c2a0adf9175a78c47a4f66e0e8c848087 Mon Sep 17 00:00:00 2001 From: Ned Martorell <32619850+nmartorell@users.noreply.github.com> Date: Mon, 10 Aug 2026 22:24:04 +0200 Subject: [PATCH 2/2] chore(plugins): remove checkout mcp config --- .mcp.json | 9 --------- AGENTS.md | 2 +- CODING_STANDARDS_AND_STRUCTURE.md | 2 +- README.md | 1 - 4 files changed, 2 insertions(+), 12 deletions(-) delete mode 100644 .mcp.json diff --git a/.mcp.json b/.mcp.json deleted file mode 100644 index 293d7190..00000000 --- a/.mcp.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "mcpServers": { - "dataiku": { - "type": "stdio", - "command": "sh", - "args": ["./bin/launcher.sh"] - } - } -} diff --git a/AGENTS.md b/AGENTS.md index ba1e2d8c..1ebb737c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -24,7 +24,7 @@ Use this file when changing this repository. It is not an operating guide for us - Tool registration imports: `dataiku_mcp/__init__.py`. - Project dependencies, Python support, version, and CLI entry points: `pyproject.toml`. - Standalone server dependency pins and Python floor: the PEP 723 block in `bin/run_mcp.py`. -- Portable Agent Plugins package: root `plugin.json` + `mcp.json` (skills under `skills/`). Harness-specific launch config: `.mcp.json` and `.claude-plugin/plugin.json`. +- Portable Agent Plugins package: root `plugin.json` + `mcp.json` (skills under `skills/`). The Claude compatibility launch config is `.claude-plugin/plugin.json`. - User-facing installation and architecture overview: `README.md`. - Release behavior: `RELEASE.md` and `.github/workflows/bump.yml`. - CI behavior: `.github/workflows/ci.yml` and `.github/workflows/pr-title.yml`. diff --git a/CODING_STANDARDS_AND_STRUCTURE.md b/CODING_STANDARDS_AND_STRUCTURE.md index 0288d238..8835166b 100644 --- a/CODING_STANDARDS_AND_STRUCTURE.md +++ b/CODING_STANDARDS_AND_STRUCTURE.md @@ -95,7 +95,7 @@ uv run --quiet ./bin/run_mcp.py # skip the launcher, straight to the server There are two files, and the split matters: - **`bin/run_mcp.py`** is the server entry point. It carries [PEP 723](https://peps.python.org/pep-0723/) inline metadata — pinned dependencies and `requires-python` — so uv can build its runtime environment with no project install. -- **`bin/launcher.sh`** is what every manifest (`mcp.json`, `.mcp.json`, `.claude-plugin`, and the root Agent Plugins package) actually runs, and the only launcher. It picks a runtime in three tiers and `exec`s the server on the first that works, or exits non-zero with install instructions: +- **`bin/launcher.sh`** is what every manifest (`mcp.json`, `.claude-plugin`, and the root Agent Plugins package) actually runs, and the only launcher. It picks a runtime in three tiers and `exec`s the server on the first that works, or exits non-zero with install instructions: 1. `uv run`, if a `uv` on `PATH` answers `uv --version`. 2. A venv under `${PLUGIN_DATA}` (falling back to `${CLAUDE_PLUGIN_DATA}`, then `$PLUGIN_ROOT/.deps`) with the pinned dependencies pip-installed into it, built by the first interpreter that satisfies `requires-python`. Candidates are deduplicated by resolved path, so aliases of one broken interpreter are not retried a dozen times. 3. `@dataiku/uv@0.12.0` through `npx` or `pnpx`, probed with `--help` — a runner on `PATH` still has to be able to fetch the package. diff --git a/README.md b/README.md index 88a11f73..6f895e71 100644 --- a/README.md +++ b/README.md @@ -240,7 +240,6 @@ uv run dataiku-headless ├── .claude-plugin/ │ ├── plugin.json # Claude Code plugin manifest (skills + unconfigured stdio MCP) │ └── marketplace.json # Marketplace catalog (single-plugin, source: "./") -├── .mcp.json # Shared MCP config (sh ./bin/launcher.sh) for contributor dogfooding ├── CODING_STANDARDS_AND_STRUCTURE.md # Contributor guide └── pyproject.toml ```