Skip to content

Commit 367e123

Browse files
committed
Automate MCP Docker publishing, versioning, and hybrid deployment validation
- Add GitHub Actions workflow to build, test, tag, release, and publish the MCP server to GitHub Container Registry and MCP Registry - Introduce automated version detection, server.json schema validation, and GitHub tagging for version control - Update Dockerfile with MCP server validation label - Add detailed publishing guide in PUBLISHING.md for streamlined version management - Enhance README and server.json with consistent version references for Docker and MCP Registry usage
1 parent 01d4dbe commit 367e123

File tree

6 files changed

+452
-19
lines changed

6 files changed

+452
-19
lines changed

.github/workflows/main.yml

Lines changed: 196 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,31 @@
1-
name: Build CodeAlive MCP Server docker image
1+
name: Build and Publish CodeAlive MCP Server
22

33
on:
44
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
58

69
env:
710
DOCKER_REGISTRY: ghcr.io
811
DOCKER_USERNAME: ${{ github.actor }}
912
DOCKER_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
1013
IMAGE_NAME: ghcr.io/codealive-ai/codealive-mcp
1114

15+
permissions:
16+
id-token: write # Required for MCP Registry OIDC authentication
17+
contents: write # For creating tags and releases
18+
packages: write
19+
1220
jobs:
13-
build-mcp-server:
14-
name: MCP Server docker image
21+
build-and-publish:
22+
name: Build, Test, and Publish MCP Server
1523
runs-on: ubuntu-latest
16-
permissions:
17-
contents: read
18-
packages: write
1924
steps:
2025
- name: Checkout repository
2126
uses: actions/checkout@v4
27+
with:
28+
fetch-depth: 0 # Fetch all history for version detection
2229

2330
- name: Set up Python
2431
uses: actions/setup-python@v5
@@ -30,7 +37,7 @@ jobs:
3037
run: |
3138
python -m pip install --upgrade pip
3239
pip install -e .
33-
pip install pytest pytest-asyncio pytest-mock pytest-cov
40+
pip install pytest pytest-asyncio pytest-mock pytest-cov jsonschema
3441
3542
- name: Run tests
3643
run: |
@@ -45,26 +52,202 @@ jobs:
4552
junit/test-results.xml
4653
coverage.xml
4754
55+
- name: Check for version change
56+
id: version-check
57+
run: |
58+
# Get current version from pyproject.toml
59+
CURRENT_VERSION=$(grep '^version = ' pyproject.toml | sed 's/version = "\(.*\)"/\1/')
60+
echo "current_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
61+
62+
# Check if tag already exists
63+
if git tag -l "v$CURRENT_VERSION" | grep -q "v$CURRENT_VERSION"; then
64+
echo "version_changed=false" >> $GITHUB_OUTPUT
65+
echo "Tag v$CURRENT_VERSION already exists"
66+
else
67+
echo "version_changed=true" >> $GITHUB_OUTPUT
68+
echo "New version detected: $CURRENT_VERSION"
69+
fi
70+
71+
- name: Update server.json version
72+
if: steps.version-check.outputs.version_changed == 'true'
73+
run: |
74+
python -c "
75+
import json
76+
with open('server.json', 'r') as f:
77+
data = json.load(f)
78+
version = '${{ steps.version-check.outputs.current_version }}'
79+
data['version'] = version
80+
if 'packages' in data:
81+
for package in data['packages']:
82+
package['version'] = version
83+
transport = package.get('transport', {})
84+
args = transport.get('args') if isinstance(transport, dict) else None
85+
if isinstance(args, list):
86+
for idx, arg in enumerate(args):
87+
if isinstance(arg, str) and 'ghcr.io/codealive-ai/codealive-mcp:' in arg:
88+
args[idx] = f'ghcr.io/codealive-ai/codealive-mcp:v{version}'
89+
with open('server.json', 'w') as f:
90+
json.dump(data, f, indent=2)
91+
"
92+
4893
- name: Set up Docker Buildx
49-
uses: docker/setup-buildx-action@v2
94+
uses: docker/setup-buildx-action@v3
95+
5096
- name: Docker meta
5197
id: meta
52-
uses: docker/metadata-action@v3
98+
uses: docker/metadata-action@v5
5399
with:
54100
images: ${{ env.IMAGE_NAME }}
101+
tags: |
102+
type=ref,event=branch
103+
type=ref,event=pr
104+
type=semver,pattern={{version}},value=${{ steps.version-check.outputs.current_version }},enable=${{ github.ref == 'refs/heads/main' && steps.version-check.outputs.version_changed == 'true' }}
105+
type=semver,pattern=v{{version}},value=${{ steps.version-check.outputs.current_version }},enable=${{ github.ref == 'refs/heads/main' && steps.version-check.outputs.version_changed == 'true' }}
106+
type=raw,value=latest,enable={{is_default_branch}}
107+
55108
- name: Login to GitHub Container Registry
56-
uses: docker/login-action@v2
109+
uses: docker/login-action@v3
57110
with:
58111
registry: ${{ env.DOCKER_REGISTRY }}
59112
username: ${{ env.DOCKER_USERNAME }}
60113
password: ${{ env.DOCKER_PASSWORD }}
61-
- name: Build and push
62-
uses: docker/build-push-action@v3
114+
115+
- name: Build and push Docker image
116+
uses: docker/build-push-action@v5
63117
with:
64118
push: true
65119
platforms: linux/amd64,linux/arm64
66120
file: ./Dockerfile
67121
tags: ${{ steps.meta.outputs.tags }}
68-
labels: ${{ steps.meta.outputs.labels }}
122+
labels: |
123+
${{ steps.meta.outputs.labels }}
124+
io.modelcontextprotocol.server.name=io.github.codealive-ai/codealive-mcp
69125
cache-from: type=gha
70126
cache-to: type=gha
127+
128+
- name: Create git tag
129+
if: steps.version-check.outputs.version_changed == 'true' && github.ref == 'refs/heads/main'
130+
run: |
131+
git config user.name "github-actions[bot]"
132+
git config user.email "github-actions[bot]@users.noreply.github.com"
133+
git tag "v${{ steps.version-check.outputs.current_version }}"
134+
git push origin "v${{ steps.version-check.outputs.current_version }}"
135+
136+
- name: Validate server.json
137+
if: steps.version-check.outputs.version_changed == 'true' && github.ref == 'refs/heads/main'
138+
run: |
139+
python - <<'PY'
140+
import json
141+
import sys
142+
import urllib.request
143+
from jsonschema import ValidationError, validate
144+
145+
schema_url = "https://static.modelcontextprotocol.io/schemas/2025-09-16/server.schema.json"
146+
147+
try:
148+
with urllib.request.urlopen(schema_url, timeout=30) as response:
149+
schema = json.load(response)
150+
except Exception as exc:
151+
print(f"✗ Unable to download server.json schema: {exc}")
152+
sys.exit(1)
153+
154+
try:
155+
with open('server.json', 'r') as f:
156+
data = json.load(f)
157+
except Exception as exc:
158+
print(f"✗ Failed to load server.json: {exc}")
159+
sys.exit(1)
160+
161+
try:
162+
validate(instance=data, schema=schema)
163+
print('✓ server.json schema validation passed')
164+
except ValidationError as exc:
165+
print(f"✗ server.json schema validation failed: {exc.message}")
166+
sys.exit(1)
167+
168+
has_packages = 'packages' in data and len(data['packages']) > 0
169+
has_remotes = 'remotes' in data and len(data['remotes']) > 0
170+
171+
if not (has_packages or has_remotes):
172+
print('✗ Must have either packages or remotes configured')
173+
sys.exit(1)
174+
175+
if has_packages:
176+
for idx, pkg in enumerate(data['packages']):
177+
registry_type = pkg.get('registryType')
178+
identifier = pkg.get('identifier')
179+
if registry_type not in ['npm', 'pypi', 'nuget', 'oci', 'mcpb']:
180+
print(f"✗ Package {idx}: Invalid registry type '{registry_type}'")
181+
sys.exit(1)
182+
print(f"✓ Package {idx}: {registry_type.upper()} -> {identifier}")
183+
184+
if has_remotes:
185+
for idx, remote in enumerate(data['remotes']):
186+
remote_type = remote.get('type')
187+
url = remote.get('url')
188+
if remote_type not in ['sse', 'streamable-http']:
189+
print(f"✗ Remote {idx}: Invalid transport type '{remote_type}'")
190+
sys.exit(1)
191+
print(f"✓ Remote {idx}: {remote_type} -> {url}")
192+
193+
print('✓ server.json validation passed (hybrid deployment)')
194+
PY
195+
196+
- name: Install MCP Publisher CLI
197+
if: steps.version-check.outputs.version_changed == 'true' && github.ref == 'refs/heads/main'
198+
run: |
199+
curl -L "https://github.com/modelcontextprotocol/registry/releases/download/v1.0.0/mcp-publisher_1.0.0_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher
200+
chmod +x mcp-publisher
201+
202+
- name: Login to MCP Registry (GitHub OIDC)
203+
if: steps.version-check.outputs.version_changed == 'true' && github.ref == 'refs/heads/main'
204+
run: |
205+
./mcp-publisher login github-oidc
206+
207+
- name: Publish to MCP Registry
208+
if: steps.version-check.outputs.version_changed == 'true' && github.ref == 'refs/heads/main'
209+
run: |
210+
./mcp-publisher publish server.json
211+
212+
- name: Create GitHub Release
213+
if: steps.version-check.outputs.version_changed == 'true' && github.ref == 'refs/heads/main'
214+
uses: softprops/action-gh-release@v1
215+
with:
216+
tag_name: v${{ steps.version-check.outputs.current_version }}
217+
name: CodeAlive MCP v${{ steps.version-check.outputs.current_version }}
218+
body: |
219+
## CodeAlive MCP Server v${{ steps.version-check.outputs.current_version }}
220+
221+
### 🚀 Hybrid Deployment Options
222+
223+
**Docker Container (Local)**
224+
```bash
225+
docker run --rm -i -e CODEALIVE_API_KEY=your-key ghcr.io/codealive-ai/codealive-mcp:v${{ steps.version-check.outputs.current_version }}
226+
```
227+
228+
**MCP Registry**
229+
```json
230+
{
231+
"name": "io.github.codealive-ai/codealive-mcp",
232+
"transport": {
233+
"type": "stdio",
234+
"command": "docker",
235+
"args": ["run", "--rm", "-i", "-e", "CODEALIVE_API_KEY=YOUR_API_KEY_HERE", "ghcr.io/codealive-ai/codealive-mcp:v${{ steps.version-check.outputs.current_version }}"]
236+
}
237+
}
238+
```
239+
240+
**Remote HTTP (Zero Setup)**
241+
```json
242+
{
243+
"transport": {
244+
"type": "http",
245+
"url": "https://mcp.codealive.ai/api"
246+
},
247+
"headers": {
248+
"Authorization": "Bearer your-codealive-api-key"
249+
}
250+
}
251+
```
252+
draft: false
253+
prerelease: false

CLAUDE.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,25 @@ The server is designed to integrate with:
8989
- Continue (via config.yaml)
9090
- Any MCP-compatible AI client
9191

92-
Key integration consideration: AI clients should use `get_data_sources` first to discover available repositories/workspaces, then use those IDs for targeted search and chat operations.
92+
Key integration consideration: AI clients should use `get_data_sources` first to discover available repositories/workspaces, then use those IDs for targeted search and chat operations.
93+
94+
## Publishing and Releases
95+
96+
### Version Management
97+
When making significant changes, consider incrementing the version in `pyproject.toml`:
98+
```toml
99+
version = "0.3.0" # Increment for new features, bug fixes, or breaking changes
100+
```
101+
102+
### Automated Publishing
103+
The project uses automated publishing:
104+
- **Trigger**: Push version change to `main` branch
105+
- **Process**: Tests → Build → Docker → MCP Registry → GitHub Release
106+
- **Result**: Available at `io.github.codealive-ai/codealive-mcp` in MCP Registry
107+
108+
### Version Guidelines
109+
- **Patch** (0.2.0 → 0.2.1): Bug fixes, minor improvements
110+
- **Minor** (0.2.0 → 0.3.0): New features, enhancements
111+
- **Major** (0.2.0 → 1.0.0): Breaking changes, major releases
112+
113+
When implementing features or fixes, evaluate if they warrant a version bump for users to benefit from the changes through the MCP Registry.

Dockerfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
# Dockerfile for CodeAlive MCP Server
33
FROM python:3.11-slim AS base
44

5+
# MCP Server validation label for Docker registry
6+
LABEL io.modelcontextprotocol.server.name="io.github.codealive-ai/codealive-mcp"
7+
58
# Set working directory
69
WORKDIR /app
710

0 commit comments

Comments
 (0)