Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .agents/plugins/marketplace.json
Original file line number Diff line number Diff line change
@@ -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"
}
]
}
48 changes: 0 additions & 48 deletions .codex-plugin/plugin.json

This file was deleted.

3 changes: 1 addition & 2 deletions .github/workflows/bump.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 0 additions & 9 deletions .mcp.json

This file was deleted.

2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/`). 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`.
Expand Down
4 changes: 2 additions & 2 deletions CODING_STANDARDS_AND_STRUCTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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`, `.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.
Expand Down
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -235,12 +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
```
Expand Down
2 changes: 1 addition & 1 deletion RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
2 changes: 1 addition & 1 deletion cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
}
}
}
22 changes: 21 additions & 1 deletion plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
}
}
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
61 changes: 51 additions & 10 deletions tests/test_agent_plugins.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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 = {
Expand Down Expand Up @@ -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")
Expand All @@ -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():
Expand Down Expand Up @@ -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():
Expand Down