mcp-argocd 0.1.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.
- mcp_argocd-0.1.0/.env.example +16 -0
- mcp_argocd-0.1.0/.github/dependabot.yml +24 -0
- mcp_argocd-0.1.0/.github/workflows/link-check.yml +23 -0
- mcp_argocd-0.1.0/.github/workflows/lint.yml +11 -0
- mcp_argocd-0.1.0/.github/workflows/publish.yml +49 -0
- mcp_argocd-0.1.0/.github/workflows/release.yml +308 -0
- mcp_argocd-0.1.0/.github/workflows/tests.yml +21 -0
- mcp_argocd-0.1.0/.gitignore +43 -0
- mcp_argocd-0.1.0/AGENTS.md +181 -0
- mcp_argocd-0.1.0/CHANGELOG.md +7 -0
- mcp_argocd-0.1.0/LICENSE +21 -0
- mcp_argocd-0.1.0/PKG-INFO +339 -0
- mcp_argocd-0.1.0/README.md +302 -0
- mcp_argocd-0.1.0/evaluations/eval.xml +67 -0
- mcp_argocd-0.1.0/gemini-extension.json +33 -0
- mcp_argocd-0.1.0/llms-full.txt +211 -0
- mcp_argocd-0.1.0/llms.txt +28 -0
- mcp_argocd-0.1.0/pyproject.toml +106 -0
- mcp_argocd-0.1.0/scripts/derive_artifacts.py +78 -0
- mcp_argocd-0.1.0/server.json +43 -0
- mcp_argocd-0.1.0/src/mcp_argocd/__init__.py +69 -0
- mcp_argocd-0.1.0/src/mcp_argocd/__main__.py +5 -0
- mcp_argocd-0.1.0/src/mcp_argocd/client.py +176 -0
- mcp_argocd-0.1.0/src/mcp_argocd/config.py +73 -0
- mcp_argocd-0.1.0/src/mcp_argocd/exceptions.py +52 -0
- mcp_argocd-0.1.0/src/mcp_argocd/py.typed +0 -0
- mcp_argocd-0.1.0/src/mcp_argocd/resources/api-token-setup.md +20 -0
- mcp_argocd-0.1.0/src/mcp_argocd/resources/applicationsets.md +10 -0
- mcp_argocd-0.1.0/src/mcp_argocd/resources/gitops-change-flow.md +8 -0
- mcp_argocd-0.1.0/src/mcp_argocd/resources/prompts/diagnose-sync-failure.md +9 -0
- mcp_argocd-0.1.0/src/mcp_argocd/resources/prompts/fleet-status.md +8 -0
- mcp_argocd-0.1.0/src/mcp_argocd/resources/prompts/inspect-applicationset.md +9 -0
- mcp_argocd-0.1.0/src/mcp_argocd/resources/prompts/review-drift.md +8 -0
- mcp_argocd-0.1.0/src/mcp_argocd/resources/prompts/rollback-application.md +9 -0
- mcp_argocd-0.1.0/src/mcp_argocd/resources/prompts/safe-sync.md +9 -0
- mcp_argocd-0.1.0/src/mcp_argocd/resources/prompts/triage-application.md +9 -0
- mcp_argocd-0.1.0/src/mcp_argocd/resources/rbac.md +10 -0
- mcp_argocd-0.1.0/src/mcp_argocd/resources/rollback.md +9 -0
- mcp_argocd-0.1.0/src/mcp_argocd/resources/status-triage.md +25 -0
- mcp_argocd-0.1.0/src/mcp_argocd/resources/sync-safety.md +17 -0
- mcp_argocd-0.1.0/src/mcp_argocd/servers/__init__.py +0 -0
- mcp_argocd-0.1.0/src/mcp_argocd/servers/_helpers.py +126 -0
- mcp_argocd-0.1.0/src/mcp_argocd/servers/argocd.py +2043 -0
- mcp_argocd-0.1.0/src/mcp_argocd/servers/prompts.py +104 -0
- mcp_argocd-0.1.0/src/mcp_argocd/servers/resources.py +109 -0
- mcp_argocd-0.1.0/tests/__init__.py +0 -0
- mcp_argocd-0.1.0/tests/conftest.py +82 -0
- mcp_argocd-0.1.0/tests/fixtures/README.md +61 -0
- mcp_argocd-0.1.0/tests/fixtures/application.json +173 -0
- mcp_argocd-0.1.0/tests/fixtures/application_list.json +320 -0
- mcp_argocd-0.1.0/tests/fixtures/applicationset.json +80 -0
- mcp_argocd-0.1.0/tests/fixtures/cluster.json +41 -0
- mcp_argocd-0.1.0/tests/fixtures/events.json +30 -0
- mcp_argocd-0.1.0/tests/fixtures/logs.ndjson +4 -0
- mcp_argocd-0.1.0/tests/fixtures/managed_resources.json +28 -0
- mcp_argocd-0.1.0/tests/fixtures/project.json +65 -0
- mcp_argocd-0.1.0/tests/fixtures/repository.json +24 -0
- mcp_argocd-0.1.0/tests/fixtures/resource_tree.json +87 -0
- mcp_argocd-0.1.0/tests/fixtures/version.json +12 -0
- mcp_argocd-0.1.0/tests/test_links.py +128 -0
- mcp_argocd-0.1.0/tests/unit/__init__.py +0 -0
- mcp_argocd-0.1.0/tests/unit/test_cli.py +52 -0
- mcp_argocd-0.1.0/tests/unit/test_client.py +210 -0
- mcp_argocd-0.1.0/tests/unit/test_config.py +122 -0
- mcp_argocd-0.1.0/tests/unit/test_derived_artifacts.py +30 -0
- mcp_argocd-0.1.0/tests/unit/test_doc_parity.py +81 -0
- mcp_argocd-0.1.0/tests/unit/test_exceptions.py +92 -0
- mcp_argocd-0.1.0/tests/unit/test_helpers.py +142 -0
- mcp_argocd-0.1.0/tests/unit/test_prompts.py +108 -0
- mcp_argocd-0.1.0/tests/unit/test_resources.py +69 -0
- mcp_argocd-0.1.0/tests/unit/test_server_assembly.py +69 -0
- mcp_argocd-0.1.0/tests/unit/test_tool_contract.py +342 -0
- mcp_argocd-0.1.0/tests/unit/test_tools.py +980 -0
- mcp_argocd-0.1.0/uv.lock +2303 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Argo CD base URL, including any path prefix (required)
|
|
2
|
+
ARGOCD_URL=https://argocd.example.com
|
|
3
|
+
|
|
4
|
+
# Bearer token (required) — any of these work, checked in this order:
|
|
5
|
+
# - ARGOCD_TOKEN
|
|
6
|
+
# - ARGOCD_AUTH_TOKEN (the argocd CLI's name)
|
|
7
|
+
# - ARGOCD_API_TOKEN (the official server's name)
|
|
8
|
+
# Generate one: argocd account generate-token --account <name> --expires-in 24h
|
|
9
|
+
ARGOCD_TOKEN=
|
|
10
|
+
|
|
11
|
+
# Optional settings
|
|
12
|
+
# ARGOCD_READ_ONLY=false
|
|
13
|
+
# ARGOCD_TIMEOUT=30
|
|
14
|
+
# ARGOCD_SSL_VERIFY=true
|
|
15
|
+
# ARGOCD_INSECURE=false
|
|
16
|
+
# ARGOCD_APP_NAMESPACE=
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
- package-ecosystem: pip
|
|
4
|
+
directory: /
|
|
5
|
+
schedule:
|
|
6
|
+
interval: weekly
|
|
7
|
+
day: monday
|
|
8
|
+
open-pull-requests-limit: 10
|
|
9
|
+
labels:
|
|
10
|
+
- dependencies
|
|
11
|
+
commit-message:
|
|
12
|
+
prefix: "chore(deps)"
|
|
13
|
+
|
|
14
|
+
- package-ecosystem: github-actions
|
|
15
|
+
directory: /
|
|
16
|
+
schedule:
|
|
17
|
+
interval: weekly
|
|
18
|
+
day: monday
|
|
19
|
+
open-pull-requests-limit: 5
|
|
20
|
+
labels:
|
|
21
|
+
- dependencies
|
|
22
|
+
- ci
|
|
23
|
+
commit-message:
|
|
24
|
+
prefix: "chore(ci)"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
name: Link check
|
|
2
|
+
|
|
3
|
+
# Documentation links are checked on a schedule rather than per-push. They
|
|
4
|
+
# assert on third-party availability, so a transient failure at pypi.org or
|
|
5
|
+
# shields.io says nothing about the commit that triggered it -- it only reddens
|
|
6
|
+
# an unrelated PR. Weekly is often enough to catch real rot, and a failure here
|
|
7
|
+
# is information rather than a merge blocker.
|
|
8
|
+
on:
|
|
9
|
+
schedule:
|
|
10
|
+
- cron: "0 6 * * 1" # Mondays, 06:00 UTC
|
|
11
|
+
workflow_dispatch:
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
links:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v7
|
|
18
|
+
- uses: astral-sh/setup-uv@v7
|
|
19
|
+
with:
|
|
20
|
+
python-version: "3.13"
|
|
21
|
+
- run: uv sync --all-extras
|
|
22
|
+
# -m network selects exactly what the default run excludes.
|
|
23
|
+
- run: uv run pytest -m network tests/test_links.py
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
build-n-publish:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
environment: pypi
|
|
12
|
+
permissions:
|
|
13
|
+
id-token: write
|
|
14
|
+
contents: write
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v7
|
|
18
|
+
|
|
19
|
+
- name: Install uv
|
|
20
|
+
uses: astral-sh/setup-uv@v7
|
|
21
|
+
with:
|
|
22
|
+
version: "latest"
|
|
23
|
+
|
|
24
|
+
- name: Set up Python
|
|
25
|
+
run: uv python install
|
|
26
|
+
|
|
27
|
+
- name: Build package
|
|
28
|
+
run: uv build
|
|
29
|
+
|
|
30
|
+
- name: Publish to PyPI
|
|
31
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
32
|
+
|
|
33
|
+
- name: Create GitHub Release
|
|
34
|
+
uses: softprops/action-gh-release@v3
|
|
35
|
+
with:
|
|
36
|
+
files: dist/*
|
|
37
|
+
generate_release_notes: true
|
|
38
|
+
env:
|
|
39
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
40
|
+
|
|
41
|
+
- name: Install mcp-publisher
|
|
42
|
+
run: |
|
|
43
|
+
curl -sL https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_linux_amd64.tar.gz | tar xz -C /usr/local/bin mcp-publisher
|
|
44
|
+
mcp-publisher --help
|
|
45
|
+
|
|
46
|
+
- name: Publish to MCP Registry
|
|
47
|
+
run: |
|
|
48
|
+
mcp-publisher login github-oidc
|
|
49
|
+
mcp-publisher publish
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
name: Semantic Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
inputs:
|
|
6
|
+
bump:
|
|
7
|
+
description: "Version bump type (ignored if version is set)"
|
|
8
|
+
required: true
|
|
9
|
+
type: choice
|
|
10
|
+
options:
|
|
11
|
+
- patch
|
|
12
|
+
- minor
|
|
13
|
+
- major
|
|
14
|
+
version:
|
|
15
|
+
description: "Explicit version (e.g. 0.7.0) — overrides bump"
|
|
16
|
+
required: false
|
|
17
|
+
type: string
|
|
18
|
+
default: ""
|
|
19
|
+
dry_run:
|
|
20
|
+
description: "Dry run (no tag/push)"
|
|
21
|
+
required: false
|
|
22
|
+
type: boolean
|
|
23
|
+
default: false
|
|
24
|
+
|
|
25
|
+
jobs:
|
|
26
|
+
release:
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
permissions:
|
|
29
|
+
contents: write
|
|
30
|
+
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@v7
|
|
33
|
+
with:
|
|
34
|
+
fetch-depth: 0
|
|
35
|
+
|
|
36
|
+
- name: Install uv
|
|
37
|
+
uses: astral-sh/setup-uv@v7
|
|
38
|
+
with:
|
|
39
|
+
version: "latest"
|
|
40
|
+
|
|
41
|
+
- name: Set up Python
|
|
42
|
+
run: uv python install
|
|
43
|
+
|
|
44
|
+
- name: Get current version
|
|
45
|
+
id: current
|
|
46
|
+
run: |
|
|
47
|
+
version=$(grep '^version' pyproject.toml | head -1 | sed 's/.*"\(.*\)"/\1/')
|
|
48
|
+
echo "version=$version" >> "$GITHUB_OUTPUT"
|
|
49
|
+
echo "Current version: $version"
|
|
50
|
+
|
|
51
|
+
- name: Compute next version
|
|
52
|
+
id: next
|
|
53
|
+
run: |
|
|
54
|
+
if [ -n "${{ inputs.version }}" ]; then
|
|
55
|
+
version="${{ inputs.version }}"
|
|
56
|
+
echo "Explicit version: $version"
|
|
57
|
+
else
|
|
58
|
+
IFS='.' read -r major minor patch <<< "${{ steps.current.outputs.version }}"
|
|
59
|
+
case "${{ inputs.bump }}" in
|
|
60
|
+
major) major=$((major + 1)); minor=0; patch=0 ;;
|
|
61
|
+
minor) minor=$((minor + 1)); patch=0 ;;
|
|
62
|
+
patch) patch=$((patch + 1)) ;;
|
|
63
|
+
esac
|
|
64
|
+
version="$major.$minor.$patch"
|
|
65
|
+
fi
|
|
66
|
+
echo "version=$version" >> "$GITHUB_OUTPUT"
|
|
67
|
+
echo "Next version: $version"
|
|
68
|
+
|
|
69
|
+
- name: Update pyproject.toml version
|
|
70
|
+
run: |
|
|
71
|
+
sed -i "s/^version = \".*\"/version = \"${{ steps.next.outputs.version }}\"/" pyproject.toml
|
|
72
|
+
cat pyproject.toml | grep '^version'
|
|
73
|
+
|
|
74
|
+
- name: Update llms-full.txt version
|
|
75
|
+
run: |
|
|
76
|
+
for f in llms-full.txt; do
|
|
77
|
+
[ -f "$f" ] && sed -i 's/^- \*\*Version\*\*: .*/- **Version**: ${{ steps.next.outputs.version }}/' "$f"
|
|
78
|
+
done
|
|
79
|
+
|
|
80
|
+
- name: Update server.json version
|
|
81
|
+
env:
|
|
82
|
+
NEXT_VERSION: ${{ steps.next.outputs.version }}
|
|
83
|
+
run: |
|
|
84
|
+
python3 - <<'PY'
|
|
85
|
+
import json
|
|
86
|
+
import os
|
|
87
|
+
|
|
88
|
+
version = os.environ["NEXT_VERSION"]
|
|
89
|
+
|
|
90
|
+
with open("server.json") as f:
|
|
91
|
+
data = json.load(f)
|
|
92
|
+
data["version"] = version
|
|
93
|
+
for pkg in data.get("packages", []):
|
|
94
|
+
pkg["version"] = version
|
|
95
|
+
with open("server.json", "w") as f:
|
|
96
|
+
json.dump(data, f, indent=2)
|
|
97
|
+
f.write("\n")
|
|
98
|
+
PY
|
|
99
|
+
|
|
100
|
+
- name: Derive gemini-extension.json and llms.txt
|
|
101
|
+
run: python scripts/derive_artifacts.py
|
|
102
|
+
|
|
103
|
+
- name: Regenerate uv.lock
|
|
104
|
+
run: uv lock
|
|
105
|
+
|
|
106
|
+
- name: Generate CHANGELOG.md
|
|
107
|
+
id: changelog
|
|
108
|
+
env:
|
|
109
|
+
NEXT_VERSION: ${{ steps.next.outputs.version }}
|
|
110
|
+
run: |
|
|
111
|
+
# Find previous tag
|
|
112
|
+
PREV_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
|
|
113
|
+
if [ -z "$PREV_TAG" ]; then
|
|
114
|
+
RANGE="HEAD"
|
|
115
|
+
else
|
|
116
|
+
RANGE="${PREV_TAG}..HEAD"
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
DATE=$(date -u +%Y-%m-%d)
|
|
120
|
+
ENTRY="## [${NEXT_VERSION}] - ${DATE}"$'\n'
|
|
121
|
+
|
|
122
|
+
# Collect commits grouped by type
|
|
123
|
+
# Classify on the SUBJECT only. `git log --grep` matches the whole
|
|
124
|
+
# commit message, so a wrapped body line beginning "test ..." filed the
|
|
125
|
+
# same commit under both "Bug Fixes" and "Tests".
|
|
126
|
+
ALL_COMMITS=$(git log "$RANGE" --pretty=format:"%s|%h" 2>/dev/null || true)
|
|
127
|
+
for type_label in "feat:Features" "fix:Bug Fixes" "refactor:Refactoring" "docs:Documentation" "perf:Performance" "test:Tests" "chore:Chores"; do
|
|
128
|
+
type="${type_label%%:*}"
|
|
129
|
+
label="${type_label#*:}"
|
|
130
|
+
commits=$(printf '%s\n' "$ALL_COMMITS" \
|
|
131
|
+
| { grep -E "^${type}(\(.*\))?!?:" || true; } \
|
|
132
|
+
| while IFS='|' read -r msg hash; do printf -- "- %s (%s)\n" "$msg" "$hash"; done)
|
|
133
|
+
if [ -n "$commits" ]; then
|
|
134
|
+
ENTRY+=$'\n'"### ${label}"$'\n'
|
|
135
|
+
ENTRY+="${commits}"$'\n'
|
|
136
|
+
fi
|
|
137
|
+
done
|
|
138
|
+
|
|
139
|
+
# Collect commits that don't match any conventional type
|
|
140
|
+
other=$(printf '%s\n' "$ALL_COMMITS" \
|
|
141
|
+
| { grep -v -E "^(feat|fix|refactor|docs|perf|test|chore)(\(.*\))?!?:" || true; } \
|
|
142
|
+
| while IFS='|' read -r msg hash; do
|
|
143
|
+
if [ -n "$msg" ]; then printf -- "- %s (%s)\n" "$msg" "$hash"; fi
|
|
144
|
+
done)
|
|
145
|
+
if [ -n "$other" ]; then
|
|
146
|
+
ENTRY+=$'\n'"### Other"$'\n'
|
|
147
|
+
ENTRY+="${other}"$'\n'
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
# Prepend to existing CHANGELOG.md or create new
|
|
151
|
+
if [ -f CHANGELOG.md ]; then
|
|
152
|
+
# Insert new entry after the first line (# Changelog header)
|
|
153
|
+
{
|
|
154
|
+
head -1 CHANGELOG.md
|
|
155
|
+
echo ""
|
|
156
|
+
echo "$ENTRY"
|
|
157
|
+
tail -n +2 CHANGELOG.md
|
|
158
|
+
} > CHANGELOG.tmp
|
|
159
|
+
mv CHANGELOG.tmp CHANGELOG.md
|
|
160
|
+
else
|
|
161
|
+
{
|
|
162
|
+
echo "# Changelog"
|
|
163
|
+
echo ""
|
|
164
|
+
echo "$ENTRY"
|
|
165
|
+
} > CHANGELOG.md
|
|
166
|
+
fi
|
|
167
|
+
|
|
168
|
+
# Save entry for summary display
|
|
169
|
+
echo "$ENTRY" > /tmp/changelog-entry.txt
|
|
170
|
+
echo "Generated CHANGELOG.md entry for v${NEXT_VERSION}"
|
|
171
|
+
|
|
172
|
+
- name: Verify build
|
|
173
|
+
run: uv build
|
|
174
|
+
|
|
175
|
+
- name: Create release commit and tag via API
|
|
176
|
+
if: ${{ !inputs.dry_run }}
|
|
177
|
+
env:
|
|
178
|
+
GH_TOKEN: ${{ secrets.RELEASE_PAT }}
|
|
179
|
+
NEXT_VERSION: ${{ steps.next.outputs.version }}
|
|
180
|
+
run: |
|
|
181
|
+
MAIN_SHA=$(gh api repos/${{ github.repository }}/git/ref/heads/main --jq '.object.sha')
|
|
182
|
+
BASE_TREE=$(gh api repos/${{ github.repository }}/git/commits/$MAIN_SHA --jq '.tree.sha')
|
|
183
|
+
|
|
184
|
+
# Create blobs for all modified files (use temp files to avoid ARG_MAX limits on large files)
|
|
185
|
+
create_blob() {
|
|
186
|
+
local file="$1"
|
|
187
|
+
base64 -w 0 "$file" > /tmp/b64content.txt
|
|
188
|
+
jq -Rs '{"encoding":"base64","content":rtrimstr("\n")}' /tmp/b64content.txt > /tmp/blob.json
|
|
189
|
+
gh api repos/${{ github.repository }}/git/blobs \
|
|
190
|
+
-X POST --input /tmp/blob.json --jq '.sha'
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
PYPROJECT_BLOB=$(create_blob pyproject.toml)
|
|
194
|
+
UVLOCK_BLOB=$(create_blob uv.lock)
|
|
195
|
+
CHANGELOG_BLOB=$(create_blob CHANGELOG.md)
|
|
196
|
+
LLMS_BLOB=$(create_blob llms.txt)
|
|
197
|
+
LLMS_FULL_BLOB=$(create_blob llms-full.txt)
|
|
198
|
+
SERVER_BLOB=$(create_blob server.json)
|
|
199
|
+
GEMINI_BLOB=$(create_blob gemini-extension.json)
|
|
200
|
+
|
|
201
|
+
# Create tree with all release files
|
|
202
|
+
NEW_TREE=$(gh api repos/${{ github.repository }}/git/trees \
|
|
203
|
+
-X POST \
|
|
204
|
+
--input - <<EOF | jq -r '.sha'
|
|
205
|
+
{
|
|
206
|
+
"base_tree": "$BASE_TREE",
|
|
207
|
+
"tree": [
|
|
208
|
+
{
|
|
209
|
+
"path": "pyproject.toml",
|
|
210
|
+
"mode": "100644",
|
|
211
|
+
"type": "blob",
|
|
212
|
+
"sha": "$PYPROJECT_BLOB"
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
"path": "uv.lock",
|
|
216
|
+
"mode": "100644",
|
|
217
|
+
"type": "blob",
|
|
218
|
+
"sha": "$UVLOCK_BLOB"
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
"path": "CHANGELOG.md",
|
|
222
|
+
"mode": "100644",
|
|
223
|
+
"type": "blob",
|
|
224
|
+
"sha": "$CHANGELOG_BLOB"
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
"path": "llms.txt",
|
|
228
|
+
"mode": "100644",
|
|
229
|
+
"type": "blob",
|
|
230
|
+
"sha": "$LLMS_BLOB"
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
"path": "llms-full.txt",
|
|
234
|
+
"mode": "100644",
|
|
235
|
+
"type": "blob",
|
|
236
|
+
"sha": "$LLMS_FULL_BLOB"
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
"path": "server.json",
|
|
240
|
+
"mode": "100644",
|
|
241
|
+
"type": "blob",
|
|
242
|
+
"sha": "$SERVER_BLOB"
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
"path": "gemini-extension.json",
|
|
246
|
+
"mode": "100644",
|
|
247
|
+
"type": "blob",
|
|
248
|
+
"sha": "$GEMINI_BLOB"
|
|
249
|
+
}
|
|
250
|
+
]
|
|
251
|
+
}
|
|
252
|
+
EOF
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
# Create the commit
|
|
256
|
+
COMMIT_SHA=$(gh api repos/${{ github.repository }}/git/commits \
|
|
257
|
+
-X POST \
|
|
258
|
+
--input - <<EOF | jq -r '.sha'
|
|
259
|
+
{
|
|
260
|
+
"message": "chore(release): $NEXT_VERSION",
|
|
261
|
+
"tree": "$NEW_TREE",
|
|
262
|
+
"parents": ["$MAIN_SHA"],
|
|
263
|
+
"author": {
|
|
264
|
+
"name": "github-actions[bot]",
|
|
265
|
+
"email": "github-actions[bot]@users.noreply.github.com"
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
EOF
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
# Update main to point to the new commit
|
|
272
|
+
gh api repos/${{ github.repository }}/git/refs/heads/main \
|
|
273
|
+
-X PATCH \
|
|
274
|
+
-f sha="$COMMIT_SHA" \
|
|
275
|
+
-F force=false
|
|
276
|
+
|
|
277
|
+
# Create the tag reference
|
|
278
|
+
gh api repos/${{ github.repository }}/git/refs \
|
|
279
|
+
-X POST \
|
|
280
|
+
-f ref="refs/tags/v$NEXT_VERSION" \
|
|
281
|
+
-f sha="$COMMIT_SHA"
|
|
282
|
+
|
|
283
|
+
echo "Created commit $COMMIT_SHA and tag v$NEXT_VERSION"
|
|
284
|
+
|
|
285
|
+
- name: Dry run summary
|
|
286
|
+
if: ${{ inputs.dry_run }}
|
|
287
|
+
run: |
|
|
288
|
+
echo "## Dry Run Summary" >> "$GITHUB_STEP_SUMMARY"
|
|
289
|
+
echo "- Current: ${{ steps.current.outputs.version }}" >> "$GITHUB_STEP_SUMMARY"
|
|
290
|
+
echo "- Next: ${{ steps.next.outputs.version }}" >> "$GITHUB_STEP_SUMMARY"
|
|
291
|
+
echo "- Bump: ${{ inputs.bump }}" >> "$GITHUB_STEP_SUMMARY"
|
|
292
|
+
echo "- No changes pushed." >> "$GITHUB_STEP_SUMMARY"
|
|
293
|
+
echo "" >> "$GITHUB_STEP_SUMMARY"
|
|
294
|
+
echo "### Changelog Preview" >> "$GITHUB_STEP_SUMMARY"
|
|
295
|
+
echo '```' >> "$GITHUB_STEP_SUMMARY"
|
|
296
|
+
cat /tmp/changelog-entry.txt >> "$GITHUB_STEP_SUMMARY"
|
|
297
|
+
echo '```' >> "$GITHUB_STEP_SUMMARY"
|
|
298
|
+
|
|
299
|
+
- name: Release summary
|
|
300
|
+
if: ${{ !inputs.dry_run }}
|
|
301
|
+
run: |
|
|
302
|
+
echo "## Release v${{ steps.next.outputs.version }}" >> "$GITHUB_STEP_SUMMARY"
|
|
303
|
+
echo "- Tag pushed. Publish workflow will deploy to PyPI." >> "$GITHUB_STEP_SUMMARY"
|
|
304
|
+
echo "" >> "$GITHUB_STEP_SUMMARY"
|
|
305
|
+
echo "### Changelog Entry" >> "$GITHUB_STEP_SUMMARY"
|
|
306
|
+
echo '```' >> "$GITHUB_STEP_SUMMARY"
|
|
307
|
+
cat /tmp/changelog-entry.txt >> "$GITHUB_STEP_SUMMARY"
|
|
308
|
+
echo '```' >> "$GITHUB_STEP_SUMMARY"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
on: [push, pull_request]
|
|
3
|
+
jobs:
|
|
4
|
+
test:
|
|
5
|
+
runs-on: ubuntu-latest
|
|
6
|
+
strategy:
|
|
7
|
+
matrix:
|
|
8
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
9
|
+
steps:
|
|
10
|
+
- uses: actions/checkout@v7
|
|
11
|
+
- uses: astral-sh/setup-uv@v7
|
|
12
|
+
with:
|
|
13
|
+
python-version: ${{ matrix.python-version }}
|
|
14
|
+
- run: uv sync --all-extras
|
|
15
|
+
- run: uv run pytest --cov --cov-report=xml --cov-fail-under=98
|
|
16
|
+
- name: Upload coverage
|
|
17
|
+
if: matrix.python-version == '3.13'
|
|
18
|
+
uses: codecov/codecov-action@v7
|
|
19
|
+
with:
|
|
20
|
+
file: coverage.xml
|
|
21
|
+
continue-on-error: true
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*$py.class
|
|
4
|
+
*.so
|
|
5
|
+
dist/
|
|
6
|
+
build/
|
|
7
|
+
*.egg-info/
|
|
8
|
+
*.egg
|
|
9
|
+
.eggs/
|
|
10
|
+
.venv/
|
|
11
|
+
venv/
|
|
12
|
+
env/
|
|
13
|
+
.env
|
|
14
|
+
.env.local
|
|
15
|
+
*.log
|
|
16
|
+
.mypy_cache/
|
|
17
|
+
.pytest_cache/
|
|
18
|
+
.ruff_cache/
|
|
19
|
+
htmlcov/
|
|
20
|
+
.coverage
|
|
21
|
+
.coverage.*
|
|
22
|
+
coverage.xml
|
|
23
|
+
*.cover
|
|
24
|
+
.tox/
|
|
25
|
+
.nox/
|
|
26
|
+
.hypothesis/
|
|
27
|
+
*.swp
|
|
28
|
+
*.swo
|
|
29
|
+
*~
|
|
30
|
+
.DS_Store
|
|
31
|
+
Thumbs.db
|
|
32
|
+
.idea/
|
|
33
|
+
.vscode/
|
|
34
|
+
*.iml
|
|
35
|
+
.claude/
|
|
36
|
+
.cursorrules
|
|
37
|
+
.cursor/
|
|
38
|
+
|
|
39
|
+
# 1Password shell-plugin config: local tool state, not project config.
|
|
40
|
+
.op/
|
|
41
|
+
|
|
42
|
+
# Internal ticket tracker and spec; never committed.
|
|
43
|
+
.internal/
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# mcp-argocd — Agent Context
|
|
2
|
+
|
|
3
|
+
MCP server exposing 37 tools, 7 resources, and 7 prompts over the Argo CD REST API. Read-mostly
|
|
4
|
+
operations for Argo CD 3.x: application status, sync, rollback, drift, logs, ApplicationSets,
|
|
5
|
+
clusters, projects, and repositories. Needs an API token; no cluster access.
|
|
6
|
+
|
|
7
|
+
## Protocol Support
|
|
8
|
+
|
|
9
|
+
Supports the MCP 2026-07-28 specification (MCP 2.0) and stays compatible with 2025-11-25 clients.
|
|
10
|
+
Built on FastMCP 4.x and the MCP Python SDK 2.x. Verified over `stdio` and `streamable-http`.
|
|
11
|
+
|
|
12
|
+
Transports: `stdio` (default), `streamable-http` (recommended for remote), and `sse`. The
|
|
13
|
+
2026-07-28 specification deprecates `sse`, so the server prints a warning when you use it. The
|
|
14
|
+
server uses no roots, sampling, logging, elicitation, or resource subscriptions, so the 2026-07-28
|
|
15
|
+
deprecations do not affect it.
|
|
16
|
+
|
|
17
|
+
## Architecture
|
|
18
|
+
|
|
19
|
+
- **Entry point**: `src/mcp_argocd/__init__.py` — click CLI, loads `.env`, runs the FastMCP server
|
|
20
|
+
- **Client**: `src/mcp_argocd/client.py` — async httpx client; path-prefix-safe `{url}/api/v1` base with `/api/version` requested absolutely; grpc-gateway error mapping; `stream_lines` for logs
|
|
21
|
+
- **Tools**: `src/mcp_argocd/servers/argocd.py` — all 37 FastMCP tool registrations and the `_slim_*` functions
|
|
22
|
+
- **Resources**: `src/mcp_argocd/servers/resources.py` — 7 MCP resources (4 `resource://rules/*`, 3 `resource://guides/*`), content in `src/mcp_argocd/resources/*.md`
|
|
23
|
+
- **Prompts**: `src/mcp_argocd/servers/prompts.py` — 7 MCP prompts (multi-tool workflows)
|
|
24
|
+
- **Helpers**: `src/mcp_argocd/servers/_helpers.py` — file loader with traversal guard, `_scrub` over one `SECRET_KEYS` tuple, `_page`, `_split_app_name`, `_parse_resource_selector`, `_truncate`, `_terminal`
|
|
25
|
+
- **Config**: `src/mcp_argocd/config.py` — `ArgoCDConfig` built from env vars
|
|
26
|
+
- **Exceptions**: `src/mcp_argocd/exceptions.py` — `ArgoCDError` base; `ArgoCDApiError` (with `grpc_code`), `ArgoCDAuthError`, `ArgoCDNotFoundError`, `ArgoCDConflictError`, `ArgoCDWriteDisabledError`, `ArgoCDTimeoutError`
|
|
27
|
+
- **Tests**: `tests/` — `unit/test_tools.py`, `test_tool_contract.py`, `test_client.py`, `test_config.py`, `test_exceptions.py`, `test_helpers.py`, `test_server_assembly.py`, `test_resources.py`, `test_prompts.py`, `test_doc_parity.py`, `test_derived_artifacts.py`, plus `tests/test_links.py`. Fixtures in `tests/fixtures/` and shared fixtures in `tests/conftest.py`
|
|
28
|
+
|
|
29
|
+
## Development
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
uv sync --all-extras
|
|
33
|
+
uv run pytest --cov
|
|
34
|
+
uv run ruff check .
|
|
35
|
+
uv run ruff format --check . # CI runs --check; formatting drift fails the build
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`pytest` uses `asyncio_mode = "auto"` — async tests need no marker. Ruff line length is 100,
|
|
39
|
+
target py310; lint rules include `S` (bandit), `EM`, `N`, `UP`. `tests/**` waives `S101`,
|
|
40
|
+
`S105`, `S106`.
|
|
41
|
+
|
|
42
|
+
## Patterns
|
|
43
|
+
|
|
44
|
+
- All tools are `async def` returning JSON strings
|
|
45
|
+
- `_ok(data)` for success, `_err(e)` for failure, `_paginated(items, total, has_more, next_offset)` for lists; `_ok_scrubbed` for repository/cluster/project payloads
|
|
46
|
+
- `_slim_*` functions trim each payload type; `get_*` tools expose `full=True` for the raw payload
|
|
47
|
+
- Write access control: `@tool_result(write=True)` runs `_check_write(ctx)`, which raises `ArgoCDWriteDisabledError` when `ARGOCD_READ_ONLY=true`
|
|
48
|
+
- Client-side paging: Argo CD list endpoints take no paging parameters, so the server fetches once, filters, sorts by name, and slices
|
|
49
|
+
- `namespace/name` handling: `_split_app_name` splits apps-in-any-namespace names into `name` + `appNamespace`
|
|
50
|
+
- Tags: every tool tagged `{"argocd", "<category>", "read"|"write"}`
|
|
51
|
+
- Parameters use `Annotated[type, Field(description=...)]`; `Literal[...]` for closed sets
|
|
52
|
+
|
|
53
|
+
## MCP Compliance Rules
|
|
54
|
+
|
|
55
|
+
### Tool annotations (mandatory)
|
|
56
|
+
Every tool MUST have `annotations={}` with at minimum `readOnlyHint`.
|
|
57
|
+
- Read tools: `annotations={"readOnlyHint": True, "idempotentHint": True, "openWorldHint": True}`
|
|
58
|
+
- Non-destructive writes: `destructiveHint: False`, `readOnlyHint: False`
|
|
59
|
+
- Destructive writes: `destructiveHint: True`, `readOnlyHint: False`
|
|
60
|
+
- Idempotent writes: add `idempotentHint: True`
|
|
61
|
+
|
|
62
|
+
### Tool descriptions
|
|
63
|
+
1-2 sentences. Front-load what it does AND what it returns.
|
|
64
|
+
|
|
65
|
+
### Error handling
|
|
66
|
+
- Every tool wraps in `@tool_result`; expected `ArgoCDError` failures become the `_err` JSON envelope, anything else surfaces as a `ToolError` with a logged traceback.
|
|
67
|
+
- Error text MUST be actionable: what went wrong plus a suggested fix.
|
|
68
|
+
- Never expose the token, stack traces, or request headers.
|
|
69
|
+
|
|
70
|
+
### Parameter design
|
|
71
|
+
- `Annotated[type, Field(description="...")]` on every parameter; a default on every optional.
|
|
72
|
+
- `Literal[...]` for known value sets; flatten — no nested dicts unless necessary.
|
|
73
|
+
|
|
74
|
+
### Read-only mode
|
|
75
|
+
Every write tool MUST call `_check_write(ctx)` (via `@tool_result(write=True)`) before any mutation.
|
|
76
|
+
|
|
77
|
+
## Naming verbs
|
|
78
|
+
|
|
79
|
+
`list`, `get`, `sync`, `rollback`, `terminate`, `create`, `patch`, `delete`, `run`, `invalidate`,
|
|
80
|
+
`generate`, `wait`, `can`.
|
|
81
|
+
|
|
82
|
+
## Tool Categories (37)
|
|
83
|
+
|
|
84
|
+
| Category | Count | Operations |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| Applications — read | 14 | list, get, resource-tree, managed-resources, resource, manifests, events, logs, history, revision-metadata, operation, wait, sync-windows, resource-actions |
|
|
87
|
+
| Applications — write | 8 | sync, rollback, terminate, create, patch, delete, run action, delete resource |
|
|
88
|
+
| ApplicationSets | 3 | list, get, generate (dry-run) |
|
|
89
|
+
| Projects | 2 | list, get |
|
|
90
|
+
| Clusters | 3 | list, get, invalidate cache |
|
|
91
|
+
| Repositories | 3 | list, refs, apps |
|
|
92
|
+
| Server and account | 4 | version, userinfo, can-i, settings |
|
|
93
|
+
|
|
94
|
+
## Common Workflows
|
|
95
|
+
|
|
96
|
+
- **Status triage**: `argocd_get_application` → `argocd_get_resource_tree` → `argocd_get_application_events` → `argocd_get_pod_logs`
|
|
97
|
+
- **Diagnose sync failure**: `argocd_get_operation` → `argocd_get_application_events` → `argocd_get_managed_resources`
|
|
98
|
+
- **Review drift**: `argocd_list_applications(sync_status=OutOfSync)` → `argocd_get_managed_resources`
|
|
99
|
+
- **Safe sync**: `argocd_get_sync_windows` → `argocd_sync_application(dry_run=True)` → `argocd_sync_application(wait=True)` → `argocd_get_application`
|
|
100
|
+
- **Rollback**: `argocd_get_application_history` → `argocd_get_revision_metadata` → `argocd_rollback_application(wait=True)`
|
|
101
|
+
- **Fleet status**: `argocd_list_clusters` → `argocd_list_applications`
|
|
102
|
+
- **Inspect ApplicationSet**: `argocd_get_applicationset` → `argocd_generate_applicationset`
|
|
103
|
+
|
|
104
|
+
## Prompts
|
|
105
|
+
|
|
106
|
+
Prompt content lives as `.md` files in `src/mcp_argocd/resources/prompts/`. `servers/prompts.py`
|
|
107
|
+
loads each via `string.Template.safe_substitute` and registers it with `@mcp.prompt()`. Each
|
|
108
|
+
returns a user message (workflow template) plus an assistant acknowledgment.
|
|
109
|
+
|
|
110
|
+
| Prompt | Purpose | Tags |
|
|
111
|
+
|---|---|---|
|
|
112
|
+
| `triage_application` | Diagnose a Degraded/OutOfSync app | argocd, triage |
|
|
113
|
+
| `diagnose_sync_failure` | Classify a sync failure | argocd, sync |
|
|
114
|
+
| `review_drift` | Review OutOfSync apps and diffs | argocd, drift |
|
|
115
|
+
| `safe_sync` | Dry-run then sync and wait | argocd, sync |
|
|
116
|
+
| `rollback_application` | Roll back with the auto-sync check | argocd, rollback |
|
|
117
|
+
| `fleet_status` | Cluster and app health across a fleet | argocd, fleet |
|
|
118
|
+
| `inspect_applicationset` | Compare generated vs existing apps | argocd, applicationset |
|
|
119
|
+
|
|
120
|
+
## Environment Variables
|
|
121
|
+
|
|
122
|
+
| Variable | Required | Default | Notes |
|
|
123
|
+
|---|---|---|---|
|
|
124
|
+
| `ARGOCD_URL` | yes | — | Base URL including any path prefix; trailing slash stripped; `/api/v1` appended. Falls back to `ARGOCD_SERVER` |
|
|
125
|
+
| `ARGOCD_TOKEN` | yes | — | Bearer token; also reads `ARGOCD_AUTH_TOKEN`, `ARGOCD_API_TOKEN` |
|
|
126
|
+
| `ARGOCD_READ_ONLY` | no | `false` | `true`/`1`/`yes` blocks the 9 write tools before any API call |
|
|
127
|
+
| `ARGOCD_TIMEOUT` | no | `30` | Request timeout in seconds |
|
|
128
|
+
| `ARGOCD_SSL_VERIFY` | no | `true` | `false`/`0`/`no` skips verification; `ARGOCD_INSECURE=true` is an alias |
|
|
129
|
+
| `ARGOCD_APP_NAMESPACE` | no | — | Default `appNamespace` for apps-in-any-namespace installs |
|
|
130
|
+
|
|
131
|
+
CLI flags override env: `--argocd-url`, `--argocd-token`, `--read-only`, `--insecure`, plus
|
|
132
|
+
`--transport {stdio,sse,streamable-http}` with `--host` and `--port` for non-stdio transports.
|
|
133
|
+
|
|
134
|
+
## Release Workflow
|
|
135
|
+
|
|
136
|
+
Releases run through GitHub Actions — never bump versions manually.
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
gh workflow run release.yml -f version=0.1.0
|
|
140
|
+
gh workflow run release.yml -f bump=minor -f dry_run=true # preview changelog, no push
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
1. `release.yml` (workflow_dispatch) — bumps the version in `pyproject.toml`, `llms-full.txt`,
|
|
144
|
+
`server.json`; runs `scripts/derive_artifacts.py`; regenerates `uv.lock`; prepends a CHANGELOG
|
|
145
|
+
entry; creates the release commit and tag via the GitHub API with `RELEASE_PAT`.
|
|
146
|
+
2. `publish.yml` (on a `v*` tag) — builds the wheel, publishes to PyPI (trusted publishing,
|
|
147
|
+
environment `pypi`), creates the GitHub Release, then publishes to the MCP Registry.
|
|
148
|
+
|
|
149
|
+
Rules: never edit the `pyproject.toml` version directly; never create tags manually; use
|
|
150
|
+
Conventional Commits (`feat:`, `fix:`, `docs:`, …).
|
|
151
|
+
|
|
152
|
+
## Documentation Freshness (mandatory)
|
|
153
|
+
|
|
154
|
+
When a changeset adds, removes, or modifies tools, resources, or prompts, update ALL of these in
|
|
155
|
+
the same commit:
|
|
156
|
+
|
|
157
|
+
- `README.md` — counts in heading and intro, tool table, full tool reference, usage examples, permissions
|
|
158
|
+
- `llms.txt` — count in tagline and documentation link (derived from `llms-full.txt`)
|
|
159
|
+
- `llms-full.txt` — count in tagline, documentation link, full tool reference
|
|
160
|
+
- `AGENTS.md` — counts in intro, tool category table
|
|
161
|
+
- `server.json` — description field (≤100 chars)
|
|
162
|
+
|
|
163
|
+
`llms.txt` and `gemini-extension.json` are derived — run `python scripts/derive_artifacts.py`,
|
|
164
|
+
never hand-edit them.
|
|
165
|
+
|
|
166
|
+
## Known Limitations
|
|
167
|
+
|
|
168
|
+
- 37 tools in one server file, past the 5-15 guideline. If you refactor it, split by category.
|
|
169
|
+
- Errors come back as successful tool results carrying `{"error": ...}` (soft-error pattern);
|
|
170
|
+
callers inspect the JSON rather than relying on protocol-level errors.
|
|
171
|
+
- Argo CD list endpoints have no server-side paging, so a very large install pays one full fetch
|
|
172
|
+
per list call; the server pages the result client-side.
|
|
173
|
+
- Pod logs are capped at 1000 lines.
|
|
174
|
+
|
|
175
|
+
## Future work
|
|
176
|
+
|
|
177
|
+
- **OAuth 2.1 / identity passthrough** is out of scope for 0.1.0 (spec decision D6). A design
|
|
178
|
+
exists at `.internal/specs/2026-10-03-oauth21.md`; when it lands it applies to all four sibling
|
|
179
|
+
servers at once. Until then, use an API-key account or a project-role token.
|
|
180
|
+
- **Session login** (`ARGOCD_USERNAME`/`ARGOCD_PASSWORD` → `POST /api/v1/session`) is deferred: it
|
|
181
|
+
works only for local accounts with the `login` capability, never SSO, and tokens expire in 24h.
|