kaitencloud-mcp 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kaitencloud_mcp-0.2.0/.github/CODEOWNERS +6 -0
- kaitencloud_mcp-0.2.0/.github/PULL_REQUEST_TEMPLATE.md +21 -0
- kaitencloud_mcp-0.2.0/.github/workflows/ci.yml +142 -0
- kaitencloud_mcp-0.2.0/.github/workflows/dco.yml +126 -0
- kaitencloud_mcp-0.2.0/.gitignore +4 -0
- kaitencloud_mcp-0.2.0/CHANGELOG.md +26 -0
- kaitencloud_mcp-0.2.0/CONTRIBUTING.md +101 -0
- kaitencloud_mcp-0.2.0/DCO.md +37 -0
- kaitencloud_mcp-0.2.0/Dockerfile +30 -0
- kaitencloud_mcp-0.2.0/LICENSE +202 -0
- kaitencloud_mcp-0.2.0/MCP.md +117 -0
- kaitencloud_mcp-0.2.0/NOTICE +3 -0
- kaitencloud_mcp-0.2.0/PKG-INFO +149 -0
- kaitencloud_mcp-0.2.0/README.md +129 -0
- kaitencloud_mcp-0.2.0/SECURITY.md +21 -0
- kaitencloud_mcp-0.2.0/compose.yml +17 -0
- kaitencloud_mcp-0.2.0/pyproject.toml +57 -0
- kaitencloud_mcp-0.2.0/src/kaiten_mcp/__init__.py +4 -0
- kaitencloud_mcp-0.2.0/src/kaiten_mcp/__main__.py +43 -0
- kaitencloud_mcp-0.2.0/src/kaiten_mcp/config.py +50 -0
- kaitencloud_mcp-0.2.0/src/kaiten_mcp/sdk.py +111 -0
- kaitencloud_mcp-0.2.0/src/kaiten_mcp/server.py +95 -0
- kaitencloud_mcp-0.2.0/src/kaiten_mcp/shapes.py +301 -0
- kaitencloud_mcp-0.2.0/src/kaiten_mcp/tools/__init__.py +41 -0
- kaitencloud_mcp-0.2.0/src/kaiten_mcp/tools/access.py +175 -0
- kaitencloud_mcp-0.2.0/src/kaiten_mcp/tools/accounts.py +414 -0
- kaitencloud_mcp-0.2.0/src/kaiten_mcp/tools/catalog.py +496 -0
- kaitencloud_mcp-0.2.0/src/kaiten_mcp/tools/delete.py +153 -0
- kaitencloud_mcp-0.2.0/src/kaiten_mcp/tools/flags.py +395 -0
- kaitencloud_mcp-0.2.0/src/kaiten_mcp/tools/overview.py +159 -0
- kaitencloud_mcp-0.2.0/src/kaiten_mcp/tools/releases.py +240 -0
- kaitencloud_mcp-0.2.0/src/kaiten_mcp/tools/settings.py +187 -0
- kaitencloud_mcp-0.2.0/src/kaiten_mcp/tools/usage.py +147 -0
- kaitencloud_mcp-0.2.0/src/kaiten_mcp/views.py +193 -0
- kaitencloud_mcp-0.2.0/tests/e2e/test_live.py +391 -0
- kaitencloud_mcp-0.2.0/tests/test_dco.py +73 -0
- kaitencloud_mcp-0.2.0/tests/test_headers.py +31 -0
- kaitencloud_mcp-0.2.0/tests/test_health.py +55 -0
- kaitencloud_mcp-0.2.0/tests/test_shapes.py +52 -0
- kaitencloud_mcp-0.2.0/tests/test_tools.py +104 -0
- kaitencloud_mcp-0.2.0/uv.lock +1525 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Every change to this repository needs the approval of a member of @kaitencloud/engineering.
|
|
2
|
+
#
|
|
3
|
+
# The default-branch ruleset requires a review from a code owner. With this file, that means an
|
|
4
|
+
# approval from one of the team's members -- never from a GitHub App or a workflow's token,
|
|
5
|
+
# which cannot belong to a team, and never from the pull request's own author.
|
|
6
|
+
* @kaitencloud/engineering
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
## What does this PR change?
|
|
2
|
+
|
|
3
|
+
<!-- Describe the change. -->
|
|
4
|
+
|
|
5
|
+
## Why?
|
|
6
|
+
|
|
7
|
+
<!-- Explain the motivation. -->
|
|
8
|
+
|
|
9
|
+
## Testing
|
|
10
|
+
|
|
11
|
+
<!-- Explain how this was tested. -->
|
|
12
|
+
|
|
13
|
+
## Checklist
|
|
14
|
+
|
|
15
|
+
- [ ] I have read `CONTRIBUTING.md`.
|
|
16
|
+
- [ ] Every commit in this PR includes a valid DCO `Signed-off-by` line.
|
|
17
|
+
- [ ] I have the right to submit all material in this PR.
|
|
18
|
+
- [ ] I have not included secrets or confidential data.
|
|
19
|
+
- [ ] I have updated tests where appropriate.
|
|
20
|
+
- [ ] I have updated documentation where appropriate.
|
|
21
|
+
- [ ] I have preserved required third-party licenses and attributions.
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["main"]
|
|
6
|
+
tags: ["v*"]
|
|
7
|
+
pull_request:
|
|
8
|
+
branches: ["main"]
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
|
|
13
|
+
# The job names are the checks the default-branch ruleset requires: lint, test and build.
|
|
14
|
+
jobs:
|
|
15
|
+
lint:
|
|
16
|
+
name: lint
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v5
|
|
20
|
+
|
|
21
|
+
- name: Set up uv
|
|
22
|
+
uses: astral-sh/setup-uv@v7
|
|
23
|
+
with:
|
|
24
|
+
version: "0.9.1"
|
|
25
|
+
enable-cache: true
|
|
26
|
+
|
|
27
|
+
- name: Install
|
|
28
|
+
run: uv sync --locked
|
|
29
|
+
|
|
30
|
+
- name: Lint
|
|
31
|
+
run: uv run ruff check . && uv run ruff format --check .
|
|
32
|
+
|
|
33
|
+
test:
|
|
34
|
+
name: test
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
steps:
|
|
37
|
+
- uses: actions/checkout@v5
|
|
38
|
+
|
|
39
|
+
- name: Set up uv
|
|
40
|
+
uses: astral-sh/setup-uv@v7
|
|
41
|
+
with:
|
|
42
|
+
version: "0.9.1"
|
|
43
|
+
enable-cache: true
|
|
44
|
+
|
|
45
|
+
- name: Install
|
|
46
|
+
run: uv sync --locked
|
|
47
|
+
|
|
48
|
+
# The e2e suite needs a live Kaiten and is deselected by default; these
|
|
49
|
+
# drive every tool's wiring through the real SDK over a mocked API.
|
|
50
|
+
- name: Test
|
|
51
|
+
run: uv run pytest
|
|
52
|
+
|
|
53
|
+
# A pull request builds the image so a broken Dockerfile fails here; main and
|
|
54
|
+
# tags publish it to GHCR as ghcr.io/kaitencloud/kaiten-mcp. The package is
|
|
55
|
+
# created by this repository's first publish and linked to it by the image's
|
|
56
|
+
# source label, so it takes this repository's visibility: publish only once the
|
|
57
|
+
# repository is public. (`mcp` is a different, older package, linked to
|
|
58
|
+
# kaiten-internal, that this repository's token cannot write.)
|
|
59
|
+
build:
|
|
60
|
+
name: build
|
|
61
|
+
runs-on: ubuntu-latest
|
|
62
|
+
needs: [lint, test]
|
|
63
|
+
permissions:
|
|
64
|
+
contents: read
|
|
65
|
+
packages: write
|
|
66
|
+
env:
|
|
67
|
+
IMAGE: ghcr.io/${{ github.repository_owner }}/kaiten-mcp
|
|
68
|
+
steps:
|
|
69
|
+
- uses: actions/checkout@v5
|
|
70
|
+
|
|
71
|
+
- uses: docker/setup-qemu-action@v3
|
|
72
|
+
- uses: docker/setup-buildx-action@v3
|
|
73
|
+
|
|
74
|
+
# Logged in on every run of this repository, pull requests included: the
|
|
75
|
+
# build cache lives in the registry and is read through this login. A pull
|
|
76
|
+
# request from a fork gets a read-only token, which still reads the cache.
|
|
77
|
+
- name: Log in to GHCR
|
|
78
|
+
uses: docker/login-action@v3
|
|
79
|
+
with:
|
|
80
|
+
registry: ghcr.io
|
|
81
|
+
username: ${{ github.actor }}
|
|
82
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
83
|
+
|
|
84
|
+
- id: meta
|
|
85
|
+
uses: docker/metadata-action@v5
|
|
86
|
+
with:
|
|
87
|
+
images: ${{ env.IMAGE }}
|
|
88
|
+
tags: |
|
|
89
|
+
type=ref,event=branch
|
|
90
|
+
type=ref,event=pr
|
|
91
|
+
type=semver,pattern={{version}}
|
|
92
|
+
type=semver,pattern={{major}}.{{minor}}
|
|
93
|
+
type=sha
|
|
94
|
+
|
|
95
|
+
# The cache is a tag of the image's own package, not the GitHub Actions cache:
|
|
96
|
+
# that one is read-only on re-runs of an old pull request, and failed this
|
|
97
|
+
# job's build with "failed to reserve cache". Only main and tags write it.
|
|
98
|
+
- name: Build
|
|
99
|
+
uses: docker/build-push-action@v6
|
|
100
|
+
with:
|
|
101
|
+
context: .
|
|
102
|
+
platforms: linux/amd64,linux/arm64
|
|
103
|
+
push: ${{ github.event_name == 'push' }}
|
|
104
|
+
tags: ${{ steps.meta.outputs.tags }}
|
|
105
|
+
labels: ${{ steps.meta.outputs.labels }}
|
|
106
|
+
cache-from: type=registry,ref=${{ env.IMAGE }}:buildcache
|
|
107
|
+
cache-to: ${{ github.event_name == 'push' && format('type=registry,ref={0}:buildcache,mode=max', env.IMAGE) || '' }}
|
|
108
|
+
|
|
109
|
+
# A `v*` tag publishes the Python package to PyPI as kaitencloud-mcp, once lint,
|
|
110
|
+
# test and the image build have passed on it. Trusted publishing: no token is
|
|
111
|
+
# stored; PyPI trusts this workflow, in the `pypi` environment, to publish the
|
|
112
|
+
# project (see CONTRIBUTING.md, "Releasing").
|
|
113
|
+
publish:
|
|
114
|
+
name: publish
|
|
115
|
+
runs-on: ubuntu-latest
|
|
116
|
+
needs: [lint, test, build]
|
|
117
|
+
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
|
|
118
|
+
environment: pypi
|
|
119
|
+
permissions:
|
|
120
|
+
contents: read
|
|
121
|
+
id-token: write
|
|
122
|
+
steps:
|
|
123
|
+
- uses: actions/checkout@v5
|
|
124
|
+
|
|
125
|
+
- name: Set up uv
|
|
126
|
+
uses: astral-sh/setup-uv@v7
|
|
127
|
+
with:
|
|
128
|
+
version: "0.9.1"
|
|
129
|
+
|
|
130
|
+
- name: The tag is the project's version
|
|
131
|
+
run: |
|
|
132
|
+
version=$(uv version --short)
|
|
133
|
+
if [ "v$version" != "$GITHUB_REF_NAME" ]; then
|
|
134
|
+
echo "::error::tag $GITHUB_REF_NAME does not match pyproject.toml's version $version"
|
|
135
|
+
exit 1
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
- name: Build
|
|
139
|
+
run: uv build
|
|
140
|
+
|
|
141
|
+
- name: Publish
|
|
142
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
name: DCO
|
|
2
|
+
|
|
3
|
+
# Every commit of a pull request must be signed off by its author, as DCO.md and
|
|
4
|
+
# CONTRIBUTING.md ask: a `Signed-off-by: Name <email>` trailer naming the commit's author.
|
|
5
|
+
# Require the `DCO` check on main; this workflow is the source of truth.
|
|
6
|
+
#
|
|
7
|
+
# pull_request_target runs the workflow as it is on main, never as a pull request edits it,
|
|
8
|
+
# so a contributor cannot change the check that judges their own commits. It is safe because
|
|
9
|
+
# nothing here checks out or runs the pull request's code: the check below reads the commits'
|
|
10
|
+
# metadata from the API, and it needs no other file, so it can be copied to any repository.
|
|
11
|
+
on:
|
|
12
|
+
pull_request_target:
|
|
13
|
+
branches: ["main"]
|
|
14
|
+
|
|
15
|
+
permissions:
|
|
16
|
+
contents: read
|
|
17
|
+
pull-requests: read
|
|
18
|
+
|
|
19
|
+
jobs:
|
|
20
|
+
dco:
|
|
21
|
+
name: DCO
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
timeout-minutes: 5
|
|
24
|
+
steps:
|
|
25
|
+
- name: Every commit is signed off by its author
|
|
26
|
+
shell: python
|
|
27
|
+
env:
|
|
28
|
+
GITHUB_TOKEN: ${{ github.token }}
|
|
29
|
+
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
30
|
+
PR_COMMIT_COUNT: ${{ github.event.pull_request.commits }}
|
|
31
|
+
run: |
|
|
32
|
+
"""Fail when a commit of the pull request is not signed off by its author (DCO 1.1)."""
|
|
33
|
+
|
|
34
|
+
import json
|
|
35
|
+
import os
|
|
36
|
+
import re
|
|
37
|
+
import subprocess
|
|
38
|
+
import sys
|
|
39
|
+
import urllib.request
|
|
40
|
+
|
|
41
|
+
SIGN_OFF = re.compile(
|
|
42
|
+
r"^Signed-off-by:[ \t]*(?P<name>.+?)[ \t]*<(?P<email>[^<>]+)>[ \t]*$", re.M
|
|
43
|
+
)
|
|
44
|
+
FIX = (
|
|
45
|
+
"Sign commits off with `git commit -s`. To fix this pull request:\n\n"
|
|
46
|
+
" git commit --amend --signoff --no-edit # the latest commit\n"
|
|
47
|
+
" git rebase --signoff origin/main # every commit of the branch\n"
|
|
48
|
+
" git push --force-with-lease\n\n"
|
|
49
|
+
"See CONTRIBUTING.md and DCO.md."
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def problem(commit):
|
|
54
|
+
"""Why a commit, as the API lists a pull request's commits, fails; None if it passes.
|
|
55
|
+
|
|
56
|
+
A commit passes when one of its Signed-off-by trailers names its author, by name and
|
|
57
|
+
by email, ignoring case. Merge commits can contain conflict resolutions and must be
|
|
58
|
+
signed off too. A bot account certifies nothing, so it is not checked.
|
|
59
|
+
"""
|
|
60
|
+
if (commit.get("author") or {}).get("type") == "Bot":
|
|
61
|
+
return None
|
|
62
|
+
author = commit["commit"]["author"]
|
|
63
|
+
# Parse only the final trailer block, not quoted sign-offs in the message body.
|
|
64
|
+
trailers = subprocess.run(
|
|
65
|
+
["git", "interpret-trailers", "--parse"],
|
|
66
|
+
input=commit["commit"]["message"], text=True, capture_output=True, check=True,
|
|
67
|
+
).stdout
|
|
68
|
+
signoffs = [(m["name"], m["email"]) for m in SIGN_OFF.finditer(trailers)]
|
|
69
|
+
if any(
|
|
70
|
+
name.lower() == author["name"].lower() and email.lower() == author["email"].lower()
|
|
71
|
+
for name, email in signoffs
|
|
72
|
+
):
|
|
73
|
+
return None
|
|
74
|
+
expected = f"{author['name']} <{author['email']}>"
|
|
75
|
+
if not signoffs:
|
|
76
|
+
return f"no Signed-off-by; expected {expected}"
|
|
77
|
+
found = ", ".join(f"{name} <{email}>" for name, email in signoffs)
|
|
78
|
+
return f"signed off by {found}, not by its author: expected {expected}"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def commits():
|
|
82
|
+
"""Every commit of the pull request, page by page."""
|
|
83
|
+
api = os.environ.get("GITHUB_API_URL", "https://api.github.com")
|
|
84
|
+
repository, number = os.environ["GITHUB_REPOSITORY"], os.environ["PR_NUMBER"]
|
|
85
|
+
page = 1
|
|
86
|
+
while True:
|
|
87
|
+
request = urllib.request.Request(
|
|
88
|
+
f"{api}/repos/{repository}/pulls/{number}/commits?per_page=100&page={page}",
|
|
89
|
+
headers={
|
|
90
|
+
"Accept": "application/vnd.github+json",
|
|
91
|
+
"Authorization": f"Bearer {os.environ['GITHUB_TOKEN']}",
|
|
92
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
93
|
+
},
|
|
94
|
+
)
|
|
95
|
+
with urllib.request.urlopen(request, timeout=30) as response:
|
|
96
|
+
batch = json.load(response)
|
|
97
|
+
yield from batch
|
|
98
|
+
if len(batch) < 100:
|
|
99
|
+
return
|
|
100
|
+
page += 1
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def main():
|
|
104
|
+
# GitHub's pull-request commits endpoint is capped at 250, even with pagination.
|
|
105
|
+
expected = int(os.environ["PR_COMMIT_COUNT"])
|
|
106
|
+
if expected > 250:
|
|
107
|
+
print("::error title=DCO::Pull requests over 250 commits must be split for DCO verification.")
|
|
108
|
+
return 1
|
|
109
|
+
listed = list(commits())
|
|
110
|
+
if len(listed) != expected:
|
|
111
|
+
print("::error title=DCO::Commit count changed or the API returned an incomplete list; rerun the check.")
|
|
112
|
+
return 1
|
|
113
|
+
failed = [(commit, why) for commit in listed if (why := problem(commit)) is not None]
|
|
114
|
+
for commit, why in failed:
|
|
115
|
+
subject = commit["commit"]["message"].splitlines()[0]
|
|
116
|
+
print(f"::error title=DCO::{commit['sha']} {subject}: {why}")
|
|
117
|
+
if failed:
|
|
118
|
+
print(f"\n{len(failed)} of {len(listed)} commits are not signed off by their author.")
|
|
119
|
+
print(f"\n{FIX}")
|
|
120
|
+
return 1
|
|
121
|
+
print(f"All {len(listed)} commits are signed off by their author.")
|
|
122
|
+
return 0
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
if __name__ == "__main__":
|
|
126
|
+
sys.exit(main())
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project adheres to
|
|
5
|
+
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [0.2.0] - 2026-10-22
|
|
8
|
+
|
|
9
|
+
The first public release of the Kaiten MCP server. It replaces the server that turned every operation
|
|
10
|
+
of the Kaiten API into a tool of its own, which was never released.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- 31 intent-level tools over the Kaiten API, built on the official Python SDK (`kaitencloud`):
|
|
15
|
+
orientation and search, customers and instances, usage, the catalog and Licenses, feature flags,
|
|
16
|
+
releases and deployment zones, access and audit, settings, and a deletion that previews its blast
|
|
17
|
+
radius until confirmed.
|
|
18
|
+
- The stdio transport, with the token in `KAITEN_API_TOKEN`, and Streamable HTTP at `/mcp`, where
|
|
19
|
+
each request brings its own `Authorization: Bearer ksh_...` header.
|
|
20
|
+
- A read-only mode (`KAITEN_MCP_READ_ONLY=true` or `--read-only`) that registers the 15 read tools
|
|
21
|
+
only.
|
|
22
|
+
- `GET /health`: the server's version, and whether Kaiten's gateway answers.
|
|
23
|
+
- The package on PyPI, [`kaitencloud-mcp`](https://pypi.org/project/kaitencloud-mcp/), and the image
|
|
24
|
+
`ghcr.io/kaitencloud/kaiten-mcp`.
|
|
25
|
+
|
|
26
|
+
[0.2.0]: https://github.com/kaitencloud/mcp/releases/tag/v0.2.0
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Contributing to the Kaiten MCP server
|
|
2
|
+
|
|
3
|
+
Thank you for contributing to the Kaiten MCP server.
|
|
4
|
+
|
|
5
|
+
This project is licensed under the Apache License, Version 2.0.
|
|
6
|
+
|
|
7
|
+
## Developer Certificate of Origin
|
|
8
|
+
|
|
9
|
+
Kaiten uses the Developer Certificate of Origin 1.1 (DCO) for contributions.
|
|
10
|
+
|
|
11
|
+
Every commit must include a `Signed-off-by` trailer matching the commit author.
|
|
12
|
+
|
|
13
|
+
Use:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
git commit -s -m "Describe your change"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
This produces:
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
Signed-off-by: Jane Doe <jane@example.com>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`git commit -s` is a **DCO sign-off**: it certifies the contribution under
|
|
26
|
+
[DCO.md](./DCO.md). `git commit -S`, with a capital S, is a **cryptographic
|
|
27
|
+
commit signature** made with a GPG or SSH key. They are not the same thing, and
|
|
28
|
+
the sign-off is what this project requires. There is nothing to install.
|
|
29
|
+
|
|
30
|
+
### Fixing a missing sign-off
|
|
31
|
+
|
|
32
|
+
A check named **DCO** verifies every commit of every pull request, and it is the
|
|
33
|
+
final word. If it fails, it names the commits to fix.
|
|
34
|
+
|
|
35
|
+
For the latest commit:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
git commit --amend --signoff --no-edit
|
|
39
|
+
git push --force-with-lease
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
For several commits, sign off every commit of your branch at once:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
git rebase --signoff origin/main
|
|
46
|
+
git push --force-with-lease
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Before you open a pull request
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
uv sync
|
|
53
|
+
uv run ruff check . && uv run ruff format --check .
|
|
54
|
+
uv run pytest
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Every Python file opens with the project's license notice, and the tests check it:
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
# Copyright 2026 KAITEN INC
|
|
61
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Contribution rules
|
|
65
|
+
|
|
66
|
+
Please:
|
|
67
|
+
|
|
68
|
+
- keep pull requests focused;
|
|
69
|
+
- add or update tests when behavior changes;
|
|
70
|
+
- update documentation when relevant;
|
|
71
|
+
- do not include secrets, customer data, or confidential information;
|
|
72
|
+
- do not submit code or assets that you do not have the right to contribute;
|
|
73
|
+
- preserve required third-party license and attribution notices.
|
|
74
|
+
|
|
75
|
+
Accepted contributions are contributed under Apache-2.0.
|
|
76
|
+
|
|
77
|
+
## Releasing
|
|
78
|
+
|
|
79
|
+
Releases are made by maintainers. A merge to `main` publishes the container image
|
|
80
|
+
`ghcr.io/kaitencloud/kaiten-mcp:main`. To release a version, set `version` in
|
|
81
|
+
`pyproject.toml`, merge it, and push the tag:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
git tag v0.3.0
|
|
85
|
+
git push origin v0.3.0
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
CI then publishes the image as `:0.3.0` and `:0.3`, and the Python package to PyPI as
|
|
89
|
+
[`kaitencloud-mcp`](https://pypi.org/project/kaitencloud-mcp/). The `publish` job refuses a
|
|
90
|
+
tag that does not match `pyproject.toml`'s version.
|
|
91
|
+
|
|
92
|
+
PyPI publishing uses trusted publishing, so no token is stored: the project's publisher on
|
|
93
|
+
PyPI must name this repository, the `CI` workflow (`ci.yml`) and the `pypi` environment.
|
|
94
|
+
The image's package is created by the first publish and takes the repository's visibility,
|
|
95
|
+
so the repository must be public before then.
|
|
96
|
+
|
|
97
|
+
## Security
|
|
98
|
+
|
|
99
|
+
Do not report unpatched vulnerabilities in public issues.
|
|
100
|
+
|
|
101
|
+
See [SECURITY.md](./SECURITY.md).
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
Developer Certificate of Origin
|
|
2
|
+
Version 1.1
|
|
3
|
+
|
|
4
|
+
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
|
|
5
|
+
1 Letterman Drive
|
|
6
|
+
Suite D4700
|
|
7
|
+
San Francisco, CA, 94129
|
|
8
|
+
|
|
9
|
+
Everyone is permitted to copy and distribute verbatim copies of this
|
|
10
|
+
license document, but changing it is not allowed.
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
Developer's Certificate of Origin 1.1
|
|
14
|
+
|
|
15
|
+
By making a contribution to this project, I certify that:
|
|
16
|
+
|
|
17
|
+
(a) The contribution was created in whole or in part by me and I
|
|
18
|
+
have the right to submit it under the open source license
|
|
19
|
+
indicated in the file; or
|
|
20
|
+
|
|
21
|
+
(b) The contribution is based upon previous work that, to the best
|
|
22
|
+
of my knowledge, is covered under an appropriate open source
|
|
23
|
+
license and I have the right under that license to submit that
|
|
24
|
+
work with modifications, whether created in whole or in part
|
|
25
|
+
by me, under the same open source license (unless I am
|
|
26
|
+
permitted to submit under a different license), as indicated
|
|
27
|
+
in the file; or
|
|
28
|
+
|
|
29
|
+
(c) The contribution was provided directly to me by some other
|
|
30
|
+
person who certified (a), (b) or (c) and I have not modified
|
|
31
|
+
it.
|
|
32
|
+
|
|
33
|
+
(d) I understand and agree that this project and the contribution
|
|
34
|
+
are public and that a record of the contribution (including all
|
|
35
|
+
personal information I submit with it, including my sign-off) is
|
|
36
|
+
maintained indefinitely and may be redistributed consistent with
|
|
37
|
+
this project or the open source license(s) involved.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Kaiten MCP server.
|
|
2
|
+
#
|
|
3
|
+
# docker run -p 8000:8000 -e KAITEN_API_BASE_URL=https://kaiten.example.com/api ghcr.io/kaitencloud/kaiten-mcp
|
|
4
|
+
# Streamable HTTP at /mcp; each request authenticates with its own bearer token.
|
|
5
|
+
# docker run -i --rm -e KAITEN_API_BASE_URL=... -e KAITEN_API_TOKEN=ksh_... ghcr.io/kaitencloud/kaiten-mcp stdio
|
|
6
|
+
# stdio, for a local client that spawns the server.
|
|
7
|
+
|
|
8
|
+
ARG PYTHON_BASE=3.13-slim
|
|
9
|
+
|
|
10
|
+
FROM python:$PYTHON_BASE AS builder
|
|
11
|
+
COPY --from=ghcr.io/astral-sh/uv:0.9.1 /uv /uvx /bin/
|
|
12
|
+
ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy
|
|
13
|
+
WORKDIR /app
|
|
14
|
+
# Dependencies first, so a source change does not reinstall them.
|
|
15
|
+
COPY pyproject.toml uv.lock ./
|
|
16
|
+
RUN uv sync --locked --no-dev --no-install-project
|
|
17
|
+
COPY README.md LICENSE NOTICE ./
|
|
18
|
+
COPY src ./src
|
|
19
|
+
RUN uv sync --locked --no-dev --no-editable
|
|
20
|
+
|
|
21
|
+
FROM python:$PYTHON_BASE
|
|
22
|
+
WORKDIR /app
|
|
23
|
+
COPY --from=builder --chown=1000:1000 /app/.venv /app/.venv
|
|
24
|
+
USER 1000
|
|
25
|
+
ENV PATH="/app/.venv/bin:$PATH" PYTHONUNBUFFERED=1 MCP_PORT=8000
|
|
26
|
+
EXPOSE 8000
|
|
27
|
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
|
|
28
|
+
CMD python -c "import os, urllib.request; urllib.request.urlopen(f'http://127.0.0.1:{os.environ[\"MCP_PORT\"]}/health', timeout=5)" || exit 1
|
|
29
|
+
ENTRYPOINT ["kaiten-mcp"]
|
|
30
|
+
CMD ["http"]
|