Repository navigation
refactor!: intent-level tools on the Python SDK, instead of one tool per endpoint - #1
Merged
Merged
Conversation
…per endpoint The server turned each of the API's 105 OpenAPI operations into a tool, so an agent drove Kaiten in HTTP verbs: UUIDs in, full replacement bodies out, and no way to tell a write that changed something from one that did not. It now exposes 31 tools, each an intent in Kaiten's own vocabulary, following the MCP v2 rules of the product vision: intent-level tools, sliced reads, merging writes that report truthfully (created / updated / unchanged / rejected, with the changes read back from Kaiten), slugs everywhere, and a trace before a change (affected instances, delete previews). Every call goes through the official SDK (kaitencloud), generated from the OpenAPI document: its methods, models, enums (the tools' input schemas), its enforcement arithmetic and its flag builders. The repository declares no API contract of its own. Each request still acts as its caller's own token. Also: FastMCP 4.1, stdio and Streamable HTTP transports from one `kaiten-mcp` entry point, a read-only mode, an image that defaults to HTTP and publishes to GHCR, and an e2e suite that drives every tool against a live Kaiten. BREAKING CHANGE: every tool name and input changed, and KAITEN_API_BASE_URL must now point at Kaiten's gateway, which accepts ksh_ tokens, rather than at the API container. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 15, 2026
kaiten's .env.example puts the gateway on 6060; 6000 was a port nothing listens on by default. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The licensing and contribution pack the other public repositories ship (sdk-python, sdk-go, cli), adapted to the MCP server: - LICENSE is now the Apache-2.0 text apache.org publishes, byte for byte: the previous one reworded section 9 and replaced the appendix. - NOTICE, DCO.md, CONTRIBUTING.md, SECURITY.md, CODEOWNERS (@kaitencloud/engineering), the pull request template and the DCO workflow, which runs from main. - Every Python file opens with the SPDX notice; tests/test_headers.py fails on one that does not. - The wheel declares Apache-2.0 and carries LICENSE and NOTICE (license-files, hatchling 1.27 or later), and so does the image, which installs that wheel. The CI jobs are now named lint, test and build: the checks the default-branch ruleset of the public repositories requires. The documentation pointed at the SaaS stack's local gateway (port 6100), which is not public. The client examples now target this repository's compose.yml (localhost:8000/mcp) and Kaiten's own gateway (localhost:6060/api), and the e2e suite runs against Kaiten's `task quickstart` dataset. Signed-off-by: Alexandre Bergere <alexandre.bergere@kaiten.sh>
Signed-off-by: tomflenner <flennertom@gmail.com>
Contributor
Author
|
Integration update:
Local Docker verification is running. GitHub currently reports no new CI run for the pushed head; the existing CI results belong to the previous head. |
Contributor
Author
|
Final verification: local Docker build passed and the runtime image contains LICENSE and NOTICE. #1 has now been merged using Rebase and merge as requested. All 56 offline tests and 5 live e2e tests passed before merging. Historical author/co-author metadata and existing sign-offs were preserved; no other contributor's commits were signed on their behalf. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The server turned each of the API's 105 OpenAPI operations into a tool. An agent had to drive Kaiten in HTTP verbs: UUIDs in, full-replacement
PUTbodies out, and no way to tell a write that changed something from one that did not.What
31 intent-level tools in Kaiten's own vocabulary (customers, instances, plans, grants, usage, flags, releases), designed on the MCP v2 rules in the product vision (
knowledges/kaiten_vision_en.md§ Agent-Readiness):kaiten_provision_instanceresolves a plan to its current version, finds the zone, creates the customer if asked, sets the term.kaiten_get_customeris a 360 view in one callused / limit / percent / state, soft-limit ceiling included)created / updated / unchanged / recorded / rejected / preview / deleted, withchangesread back from Kaiten after the write. A no-op writes nothing and saysunchangedaffected_instances;kaiten_deletepreviews the blast radius untilconfirm=true; a schema change is dry-run firstoverview·searchget_customer·get_instance·list_instancessave_customer·provision_instance·update_instanceget_usagereport_usageget_catalogsave_entitlement·save_license·set_license_stateget_flags·evaluate_flags·test_rulesave_flagget_deploymentscreate_release·deploy_release·save_deployment_zoneget_access·get_audit_trailissue_token·revoke_tokenget_metadata_fields·get_connectorssave_metadata_field·configure_connectordeleteNo contract is duplicated here. Every call goes through the official SDK,
kaitencloud, which is generated from the OpenAPI document. That covers its methods and models, its enums (which become the tools' input schemas:EntitlementType,LicenseType,InstanceStatus,Scope…),kaitencloud.usagefor enforcement arithmetic, andkaitencloud.targetingfor flag shapes. One shared client reads the caller's bearer token per request through the SDK's callable credential.Transports and packaging. FastMCP 2.14 → 4.1. One
kaiten-mcpentry point serves:uvx --from git+…, ordocker run -i … stdio);/mcp, for remote clients behind the gateway. This is the current spec's remote transport; SSE is deprecated.There is also
GET /health, Host/Origin protection, and an image that defaults to HTTP and is built on every PR and published to GHCR frommainand tags.MCP.mdhas configurations for Claude Code, Claude Desktop, Cursor and VS Code.Verified
uv run pytest: 23 offline tests. They cover the limit arithmetic, honest statuses (a no-op sends only aGET), error mapping, read-only mode, and that every tool declares its effect.uv run pytest -m e2e: 5/5 against a live SaaS stack (saas-apitask up+ Demo Sandbox seed,ksh_PAT). Every tool, through a real MCP client. It covers the full commercial lifecycle (entitlement → plan → customer → instance → metering incl. a refused report → new version with a soft limit → upgrade/renew → in-place edit with affected instances → archive/default → delete preview/refusal), flags (lint refusal, merge, unknown variant), releases/zones/tokens, and metadata dry-run. It cleans up after itself.mcp, and is reached through Envoy atlocalhost:6100/mcpover Streamable HTTP with the PAT. Without a PAT it gets a 401 at the gateway. stdio works viauv run,docker run -iand--read-only(15 tools).Breaking
KAITEN_API_BASE_URLmust be Kaiten's gateway, not the API container:/mcpforwards the rawksh_token, and only the gateway exchanges it.KAITEN_API_OPENAPI_URLis gone.Found along the way (upstream)
Follow-up PRs, one per real finding:
PUT /feature-flags/{slug}withoutslugblanks the flag's slug: the flag becomes unreachable, and the next slug-less update 500sfeature_flags.updatenever sends the slug, so it triggers the abovekaiten_save_flagsends the slug through the SDK's transport until #7 is releasedfeature-flags/targeting/{context,lint,test}; they were excluded as console-only toolingkaiten_test_ruleand the save-flag lint call them through the SDK's transport until #8 is releasedGET /releasesnever fillscomponents, which the schema declaresviews.releases()reads each release on its own until #56 shipsunarchivetakes an id REST can't findkaiten_save_metadata_fieldcan archive but not find an archived field until #57 (and an SDK bump)task down+task up: Debezium's offsets outlive the database (this is why the audit trail was empty here)kaiten_get_audit_trailreturns events again on a fresh stackNot bugs, after a closer look:
familySlug. It is write-only by design ("Never returned — see familyId"). Plans are resolved throughlicense_families.list(), which is the intended route.nullon an optional field as absent, on every endpoint. kaiten's update-instance comment, which says an explicit null "fails validation", is inaccurate. Clearing a zone would be a new capability (and probably a new event), so it's a product decision, not a fix.kaiten_evaluate_flagskeeps calling OFREP through the SDK's transport.Also fixed on this branch: the default gateway URL is kaiten's own
.env.exampleport, 6060, not 6000.🤖 Generated with Claude Code