Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
4bba66f
feat(skills): add Govern Python API guidance
pportal2 Sep 11, 2026
7953866
fix(skills): correct Govern guidance from live validation
pportal2 Sep 11, 2026
9bc058b
fix(skills): cover Govern 15 capabilities and governance design
pportal2 Sep 11, 2026
c2d5536
feat(govern): add a single govern tool over the Govern Python SDK
pportal2 Sep 15, 2026
03ae32f
fix(govern): match delegation and users-container contracts verified …
pportal2 Sep 15, 2026
4afd6ec
Merge origin/main into feat/govern-python-skill
pportal2 Oct 1, 2026
336c71c
Merge origin/main into feat/govern-tool
pportal2 Oct 1, 2026
8f53862
fix(govern): list the govern tool in capabilities and state why HTTP …
pportal2 Oct 1, 2026
a40c4d4
feat(govern): run HTTP-mode calls as the caller through a Govern admi…
pportal2 Oct 1, 2026
77c5d2a
revert(govern): keep the govern tool stdio-only for the proof of concept
pportal2 Oct 1, 2026
11b3066
fix(govern): return compact search hits, plain not-found errors, and …
pportal2 Oct 1, 2026
af892c5
fix(govern): add IDs only to metadata updates and name the HTML error…
pportal2 Oct 2, 2026
0aea975
fix(govern): read the Govern node only from the environment and fix r…
pportal2 Oct 2, 2026
c5b3960
Merge origin/main into feat/govern-tool
pportal2 Oct 5, 2026
6dc4b90
feat(govern): use the active govern instance instead of environment v…
pportal2 Oct 5, 2026
e7ec4f4
feat(govern): integrate original Govern work with shared API operations
pmasiphelps Nov 3, 2026
6342cd3
docs(govern): add 15.1 tiles, chart format and API gaps
pportal2 Nov 3, 2026
1a21fe8
fix(govern): return the Govern error message to the agent
pportal2 Nov 6, 2026
55eef4d
fix(govern): list DRAFT blueprint versions through the designer listing
pportal2 Nov 6, 2026
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
4 changes: 2 additions & 2 deletions CODING_STANDARDS_AND_STRUCTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,8 @@ export DKU_API_KEY="your-api-key"
- Classify v1 tools with `PROJECT_READ_POLICY`, `PROJECT_EXECUTION_POLICY`, `ADMIN_POLICY` or `PROJECT_EDIT_POLICY`; use `API_OPERATIONS_POLICY` for shared operation tools and `INSTANCE_MANAGEMENT_POLICY` for connection controls. The categories describe purpose: execution includes status/polling, administration includes instance inspection and project folders, and project edits include project Cobuild.
- Only the `INSTANCE_MANAGEMENT_POLICY` group is exempt from requiring a selected profile. All v1 and v2 Dataiku tools, including operation search/describe, require one. Read/write execution handlers validate their selected operation directly.
- Every tool declares allowed_instance_types metadata. Middleware enforces it against the pinned request instance before backend token exchange. Operation handlers additionally enforce the selected operation’s instance policy, read/write effect, arguments and transport before invoking its SDK adapter; missing declarations fail closed.
- `SUPPORTED_DSS_INSTANCE_TYPES` identifies types served by the DSS client independently of project-tool policies.
- Product operations use public Python SDK methods only; do not call private transport helpers or add direct endpoint fallbacks. Deployer supports stdio and HTTP; other product support is deferred.
- `SUPPORTED_DSS_INSTANCE_TYPES` identifies types served by the DSS client independently of project-tool policies; Govern uses its own SDK client.
- Product operations use public Python SDK methods only; do not call private transport helpers or add direct endpoint fallbacks. Govern execution is stdio-only; Agent Management support remains deferred.
- Operation arguments use one Pydantic model for discovery and execution. Never accept caller-supplied HTTP methods or paths. Keep backend requirements and response projections explicit; catalog presence does not verify live server support. Preserve Cobuild routing for in-project asset construction.
- The registered set is the contract; `tests/test_tool_surface.py` pins the exact catalog. Any tool add/remove/rename updates that pinned set in the same change.

Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ for node selection, production effects and coverage limits.

Direct project reads, execution and administration support Design, Automation.
Project edits and Cobuild require Design. The four shared operation tools expose
13 public SDK actions for Deployer, restricted to their matching profiles.
99 public SDK actions for Deployer, Govern, restricted to their matching profiles.
Search and describe load schemas on demand; separate read/write tools execute them.
See the [operation guide](skills/dataiku-headless/references/operations.md).

Expand Down Expand Up @@ -194,7 +194,7 @@ Each profile requires `instance_type`: `design`, `automation`, `deployer`, `gove
assigned `design` and rewritten on disk before loading. This temporary migration
is scheduled for deprecation by 0.9.0; existing values are preserved and validated.
The declared type selects tool policy; backend features and permissions are validated by Dataiku. A local Deployer on a Design or Automation node requires a separate `deployer` profile for that URL.
Design, Automation and Deployer use the DSS client. Agent Management and Govern remain configurable profiles with instance controls only and report unsupported without a backend call.
Design, Automation and Deployer use the DSS client. Govern uses its own client in stdio and reports unsupported_authentication over HTTP. Agent Management remain configurable profiles with instance controls only and report unsupported without a backend call.

**Environment override.** To select an explicit target, including inside a Code
Studio, set these three variables in your environment or copy
Expand Down Expand Up @@ -269,6 +269,7 @@ The Dataiku Headless marketplace plugin uses stdio transport. For customer-manag
│ │ ├── dashboards.py # Dashboard inspection tools
│ │ ├── datasets.py # Dataset inspection tools + local-file upload writes
│ │ ├── deployer.py # Internal Deployer operation adapters
│ │ ├── govern.py # Govern SDK operation adapters
│ │ ├── operations.py # Shared discovery/read/write MCP tools
│ │ ├── evaluation_stores.py # Evaluation Store inspection tools
│ │ ├── flow.py # Flow inspection tools
Expand Down
25 changes: 25 additions & 0 deletions dataiku_mcp/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,31 @@ def get_dss_client() -> dataikuapi.DSSClient:
return client


def get_govern_client() -> dataikuapi.GovernClient:
"""Construct a Govern client with the pinned profile’s request credentials."""
instance = request.get_pinned_instance()
if instance.instance_type != "govern":
raise ValueError(
"Select an instance of type govern before using Govern operations."
)
if request.is_http_request():
raise ValueError(
"Govern operations require stdio; HTTP authentication is unsupported."
)
client = dataikuapi.GovernClient(
instance.url,
api_key=instance.api_key,
internal_ticket=instance.api_ticket,
extra_headers={"X-DKU-Client-Application": "dataiku-headless"},
)
client._session.verify = (
False
if instance.no_check_certificate
else instance.encrypted_rpc_cert_path or True
)
return client


def get_dataiku_version(client: dataikuapi.DSSClient) -> str:
"""Return the instance's Dataiku version, or an empty string if unavailable."""
try:
Expand Down
2 changes: 1 addition & 1 deletion dataiku_mcp/instance_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
PROJECT_EXECUTION_POLICY = {"allowed_instance_types": ["design", "automation"]}
ADMIN_POLICY = {"allowed_instance_types": ["design", "automation"]}
PROJECT_EDIT_POLICY = {"allowed_instance_types": ["design"]}
API_OPERATIONS_POLICY = {"allowed_instance_types": ["deployer"]}
API_OPERATIONS_POLICY = {"allowed_instance_types": ["deployer", "govern"]}
INSTANCE_MANAGEMENT_POLICY = {"allowed_instance_types": list(get_args(InstanceType))}


Expand Down
98 changes: 98 additions & 0 deletions dataiku_mcp/operation_schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Copyright 2026 Dataiku SAS
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Structural schemas for Govern operation adapters."""

from typing import Annotated, Any, Literal

from pydantic import Field, create_model, model_validator

from .operation_registry import OperationArguments
from .tools.utils.validation import PathSegment

NonEmpty = Annotated[str, Field(min_length=1)]


class UserContainer(OperationArguments):
type: Literal["user"]
login: PathSegment


class DeleteTimeSeriesArguments(OperationArguments):
time_series_id: PathSegment
min_timestamp: int | None = None
max_timestamp: int | None = None
delete_all: bool = False

@model_validator(mode="after")
def explicit_range(self):
unbounded = self.min_timestamp is None and self.max_timestamp is None
if unbounded != self.delete_all:
raise ValueError("Use a timestamp range or delete_all=true, not both")
if (
self.min_timestamp is not None
and self.max_timestamp is not None
and self.min_timestamp > self.max_timestamp
):
raise ValueError("min_timestamp must not exceed max_timestamp")
return self


def govern_arguments(name, spec):
if name == "delete_time_series_values":
return DeleteTimeSeriesArguments
kinds = {
"string": str,
"boolean": bool,
"integer": int,
"object": dict[str, Any],
"list": list[Any],
"any": Any,
}
fields = {}
for param in spec.params:
kind = PathSegment if param.path else kinds[param.kind]
if param.kind == "string" and param.required and not param.path:
kind = NonEmpty
if param.name == "users_container":
kind = UserContainer
if param.name == "status":
if name == "update_signoff_status":
kind = Literal[
"NOT_STARTED",
"WAITING_FOR_FEEDBACK",
"WAITING_FOR_APPROVAL",
"APPROVED",
"REJECTED",
"ABANDONED",
]
elif name in {"add_signoff_feedback", "update_signoff_feedback"}:
kind = Literal["APPROVED", "MINOR_ISSUE", "MAJOR_ISSUE"]
elif name in {"add_signoff_approval", "update_signoff_approval"}:
kind = Literal["APPROVED", "REJECTED", "ABANDONED"]
if not param.required:
kind = kind | None
description = param.description
if param.kind in {"object", "list"}:
description += " Backend-defined contents; inspect current definitions before mutation."
constraints = {"ge": 1} if param.name in {"page_size", "max_results"} else {}
fields[param.name] = (
kind,
Field(
default=... if param.required else None,
description=description,
**constraints,
),
)
return create_model(name + "Arguments", __base__=OperationArguments, **fields)
59 changes: 56 additions & 3 deletions dataiku_mcp/operations_catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.

"""Audited operation declarations for the Project and API Deployer."""
"""Audited operation declarations for Deployer and Govern."""

from .operation_registry import Operation, arguments_for, make_catalog
from .tools import deployer
from .tools import deployer, govern
from .operation_schemas import govern_arguments


def _deployer(
Expand Down Expand Up @@ -97,4 +98,56 @@ def _deployer(
)


OPERATIONS = make_catalog(_DEPLOYER_OPERATIONS)
def _govern_operation(name, spec):
arguments = govern_arguments(name, spec)
example = {}
for param in spec.params:
if param.required:
example[param.name] = {
"string": "discovered-" + param.name.replace("_", "-"),
"object": {},
"list": [],
"boolean": False,
"integer": 1,
"any": "value",
}[param.kind]
if name == "delete_time_series_values":
example["delete_all"] = True
if "status" in example and name in {
"update_signoff_status",
"add_signoff_feedback",
"update_signoff_feedback",
"add_signoff_approval",
"update_signoff_approval",
}:
example["status"] = "APPROVED"
if "users_container" in example:
example["users_container"] = {"type": "user", "login": "discovered-user"}
return Operation(
operation_id="govern." + name,
domain="govern",
category=spec.domain,
description=spec.description,
effect="read" if spec.kind == "read" else "write",
allowed_instance_types=("govern",),
arguments=arguments,
handler=govern.operation_handler(spec),
backend="govern",
transports=("stdio",),
requirements="Requires a compatible Govern release and a configured Govern profile with stdio credentials. Govern HTTP authentication is unsupported. Blueprint-specific definitions remain backend-defined; inspect current definitions before replacing them.",
response_contract="Public Govern SDK JSON with credential fields recursively redacted. Write and delete actions operate on Govern records, not DSS projects. Definition replacements require preserving unrelated fields; inspect and re-read after mutations. No retry on uncertain outcome.",
example=example,
destructive=spec.kind != "read" and not name.startswith("create_"),
local_file_fields=("file_path", "output_path"),
)


OPERATIONS = make_catalog(
[
*_DEPLOYER_OPERATIONS,
*[
_govern_operation(name, spec)
for name, spec in govern.GOVERN_OPERATIONS.items()
],
]
)
4 changes: 4 additions & 0 deletions dataiku_mcp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@ async def on_call_tool(
instance.instance_type in SUPPORTED_DSS_INSTANCE_TYPES
)
if access_token is not None and requires_dss_token:
if instance.instance_type == "govern":
raise ValueError(
"Govern operations require stdio; HTTP authentication is unsupported."
)
dss_token = await exchange_http_token(access_token.token)
dss_token_reset_token = request.bind_http_dss_token(dss_token)
return await call_next(context)
Expand Down
19 changes: 15 additions & 4 deletions dataiku_mcp/setup_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ def _page(
<div class="grid">
<div><label for="name">Instance name</label><input id="name" name="name" type="text" placeholder="production" value="{name}" maxlength="80" required><div class="hint">A short name used when switching instances.</div></div>
<div><label for="description">Description</label><input id="description" name="description" type="text" placeholder="Production Dataiku" value="{description}"></div>
<div class="full"><label for="instance_type">Instance type</label><select id="instance_type" name="instance_type" required><option value="" disabled{"" if instance_type else " selected"}>Choose an instance type</option>{instance_type_options}</select><div class="hint">Agent Management and Govern profiles can be saved without testing. Their API tools are not available yet.</div></div>
<div class="full"><label for="instance_type">Instance type</label><select id="instance_type" name="instance_type" required><option value="" disabled{"" if instance_type else " selected"}>Choose an instance type</option>{instance_type_options}</select><div class="hint">Agent Management profiles can be saved without testing. Their API tools are not available yet.</div></div>
<div class="full"><label for="url">Instance URL</label><input id="url" name="url" type="url" placeholder="https://your-instance.dataiku.com" value="{url}" aria-describedby="url-hint" required><div class="hint" id="url-hint">Enter the URL of your Dataiku instance.</div></div>
<div class="full"><label for="api_key">API key</label><input id="api_key" name="api_key" type="password" value="{api_key}" required><div class="hint">Create one in Dataiku under Profile &amp; Settings → API keys.</div></div>
</div>
Expand Down Expand Up @@ -245,12 +245,20 @@ def _success_page(name: str) -> str:


def _test_connection(values: dict) -> None:
if values["instance_type"] not in SUPPORTED_DSS_INSTANCE_TYPES:
if (
values["instance_type"] not in SUPPORTED_DSS_INSTANCE_TYPES
and values["instance_type"] != "govern"
):
raise ValueError(
f"Connection testing is unavailable for {values['instance_type']}. Save the profile without testing."
)
try:
client = dataikuapi.DSSClient(values["url"], values["api_key"])
factory = (
dataikuapi.GovernClient
if values["instance_type"] == "govern"
else dataikuapi.DSSClient
)
client = factory(values["url"], values["api_key"])
client._session.verify = not values["no_check_certificate"]
client.get_auth_info()
except Exception as exc:
Expand Down Expand Up @@ -333,7 +341,10 @@ def do_POST(self) -> None:
return
if action != "save":
raise ValueError("Unknown setup action.")
if values["instance_type"] in SUPPORTED_DSS_INSTANCE_TYPES and (
if (
values["instance_type"] in SUPPORTED_DSS_INSTANCE_TYPES
or values["instance_type"] == "govern"
) and (
not session_state
or session_state.validated_connection
!= _connection_settings(values)
Expand Down
Loading
Loading