Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
ae32475
feat(release): prepare API docs and tooling delivery
Proxicon Sep 17, 2026
67ac3b5
fix(ci): repair MCP release validation
Proxicon Sep 17, 2026
829e11c
test(mcp): align target validation expectations
Proxicon Sep 17, 2026
c08e9d3
fix(credentials): order SQLite listings by UTC sort key
Proxicon Sep 17, 2026
682c3a2
fix(credentials): require interactive management sessions
Proxicon Sep 17, 2026
036cff0
fix(openapi): describe endpoint auth accurately
Proxicon Sep 17, 2026
ebf2fc3
fix(mcp): support integration credential diagnostics
Proxicon Sep 17, 2026
cf0333f
fix(openapi): expose API build version
Proxicon Sep 17, 2026
2ac3140
fix(release): attach validated draft assets
Proxicon Sep 17, 2026
e9333f5
fix(compose): validate MCP origin configuration
Proxicon Sep 17, 2026
fbce57d
fix(mcp): validate configured origins in host tests
Proxicon Sep 17, 2026
199981e
fix(release): keep asset dry-run output deterministic
Proxicon Sep 17, 2026
e4ed709
fix(mcp): pair local gateway credentials
Proxicon Sep 17, 2026
b1ffe10
fix(mcp): bind self assignment to caller identity
Proxicon Sep 17, 2026
92988ed
fix(compose): secure MCP configuration handoff
Proxicon Sep 17, 2026
80b1f14
test(compose): retain MCP initializer failure logs
Proxicon Sep 17, 2026
9bb565b
fix(compose): preserve MCP probe configuration in cleanup
Proxicon Sep 17, 2026
26a22fd
fix(compose): preserve MCP initializer command arguments
Proxicon Sep 17, 2026
1109f8b
fix(credentials): scope grants to organizations
Proxicon Sep 17, 2026
cc1e183
feat(account): add integration credential management
Proxicon Sep 17, 2026
d37d996
test(release): execute extracted archives
Proxicon Sep 17, 2026
4255f95
fix(ci): stabilize release and credential checks
Proxicon Sep 17, 2026
eafff6c
test(openapi): verify Scalar taxonomy and proxy
Proxicon Sep 17, 2026
58f310b
fix(openapi): order generated API reference deterministically
Proxicon Sep 17, 2026
eb725b1
test(credentials): enforce interactive sibling revocation boundary
Proxicon Sep 17, 2026
4ee209e
test(ux): assert rendered Scalar reference
Proxicon Sep 17, 2026
219310f
test(ux): assert proxied OpenAPI server directly
Proxicon Sep 17, 2026
807a098
test(openapi): align release inventory baseline
Proxicon Sep 17, 2026
a519083
test(ux): reuse proxied OpenAPI payload
Proxicon Sep 17, 2026
cfe3468
test(ux): separate proxy document and server checks
Proxicon Sep 17, 2026
1430eb1
fix(release): retire macOS executable archives
Proxicon Sep 17, 2026
6449cdb
fix(ci): align release rehearsal with supported archives
Proxicon Sep 17, 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
77 changes: 77 additions & 0 deletions .github/workflows/pull-request-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,82 @@ jobs:
if: matrix.name == 'web'
run: tools/ci/smoke-web-static-assets.sh "${{ matrix.image }}"

- name: Validate MCP Compose configuration ownership
if: matrix.name == 'mcp-http'
run: tools/ci/test-mcp-compose-config.sh "${{ matrix.image }}"

release-assets:
name: Release asset rehearsal
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Check out triggering commit
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Set up .NET SDK from global.json
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6
with:
global-json-file: global.json

- name: Assemble self-contained CLI, stdio MCP, and deployment assets
run: tools/release/package-assets.sh 0.0.0-pr "${{ github.sha }}" release-assets

- name: Verify asset manifest and checksums
run: |
test "$(find release-assets -maxdepth 1 -name 'rateldesk-cli-*.tar.gz' | wc -l)" = 2
test "$(find release-assets -maxdepth 1 -name 'rateldesk-cli-*.zip' | wc -l)" = 1
test "$(find release-assets -maxdepth 1 -name 'rateldesk-mcp-stdio-*.tar.gz' | wc -l)" = 2
test "$(find release-assets -maxdepth 1 -name 'rateldesk-mcp-stdio-*.zip' | wc -l)" = 1
(cd release-assets && sha256sum --check SHA256SUMS)
test -s release-assets/release-manifest.json

- name: Execute extracted Linux x64 archives
run: tools/release/test-executable-archives.sh release-assets linux-x64

- name: Test draft release asset upload semantics
run: tools/release/test-publish-release-assets.sh

- name: Upload release rehearsal assets
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-asset-rehearsal
path: |
release-assets/*.tar.gz
release-assets/*.zip
release-assets/SHA256SUMS
release-assets/release-manifest.json
release-assets/release-manifest.json.sha256
retention-days: 14

archive-runtime:
name: Release archive runtime (${{ matrix.rid }})
needs: release-assets
runs-on: ${{ matrix.runner }}
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-latest
rid: linux-x64
- runner: ubuntu-24.04-arm
rid: linux-arm64
- runner: windows-latest
rid: win-x64
steps:
- name: Check out archive validation script
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Download the validated release rehearsal payload
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: release-asset-rehearsal
path: release-assets

- name: Extract and execute matching release archives
shell: bash
run: tools/release/test-executable-archives.sh release-assets "${{ matrix.rid }}"

compose:
name: Compose startup validation
runs-on: ubuntu-latest
Expand Down Expand Up @@ -228,6 +304,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 5
env:
RATELDESK_VERSION: 0.0.0-ci
RATELDESK_POSTGRES_CONNECTION_STRING: Host=postgres.example.test;Port=5432;Database=rateldesk;Username=rateldesk;Password=ci-placeholder;Ssl Mode=Require
RATELDESK_BOOTSTRAP_ADMIN_EMAIL: admin@example.test
RATELDESK_BOOTSTRAP_ADMIN_DISPLAY_NAME: Initial Administrator
Expand Down
162 changes: 145 additions & 17 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,34 @@ jobs:
--build-arg VERSION --build-arg SOURCE_REVISION_ID --build-arg BUILD_TIMESTAMP .
docker build --file docker/web/Dockerfile --tag rateldesk-web:release-validation \
--build-arg VERSION --build-arg SOURCE_REVISION_ID --build-arg BUILD_TIMESTAMP .
docker build --file docker/mcp-http/Dockerfile --tag rateldesk-mcp-http:release-validation \
--build-arg VERSION --build-arg SOURCE_REVISION_ID --build-arg BUILD_TIMESTAMP .
tools/ci/smoke-web-static-assets.sh rateldesk-web:release-validation

- name: Rehearse executable and deployment assets
run: tools/release/package-assets.sh "${{ needs.metadata.outputs.version }}" "${{ needs.metadata.outputs.source_revision }}" release-assets

- name: Verify executable asset checksums
run: (cd release-assets && sha256sum --check SHA256SUMS)

- name: Execute extracted Linux x64 archives
run: tools/release/test-executable-archives.sh release-assets linux-x64

- name: Test draft release asset upload semantics
run: tools/release/test-publish-release-assets.sh

- name: Transfer validated release payload
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-assets
path: |
release-assets/*.tar.gz
release-assets/*.zip
release-assets/SHA256SUMS
release-assets/release-manifest.json
release-assets/release-manifest.json.sha256
if-no-files-found: error

- name: Start and smoke-test source Compose stack
run: |
docker compose -f docker/docker-compose.yml config --quiet
Expand All @@ -125,6 +151,39 @@ jobs:
if: always()
run: docker compose -f docker/docker-compose.yml down --volumes --remove-orphans

archive-runtime:
name: Release archive runtime (${{ matrix.rid }})
needs: validation
runs-on: ${{ matrix.runner }}
permissions:
contents: read
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-latest
rid: linux-x64
- runner: ubuntu-24.04-arm
rid: linux-arm64
- runner: windows-latest
rid: win-x64
steps:
- name: Check out release scripts
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.ref }}

- name: Download the validated release payload
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: release-assets
path: release-assets

- name: Extract and execute matching release archives
shell: bash
run: tools/release/test-executable-archives.sh release-assets "${{ matrix.rid }}"

publish-web:
name: Publish Web image
needs: [metadata, validation]
Expand Down Expand Up @@ -249,9 +308,71 @@ jobs:
provenance: mode=max
sbom: true

publish-mcp-http:
name: Publish HTTP MCP image
needs: [metadata, validation]
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
attestations: write
outputs:
digest: ${{ steps.push.outputs.digest }}
steps:
- name: Check out release tag
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Set up QEMU
uses: docker/setup-qemu-action@1f40c72289eff860ee54a304f1438e3cff362e0a # v4.3.0

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0

- name: Log in to GitHub Container Registry
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Create image tags and labels
id: image
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
with:
images: ghcr.io/bostontechnologies/rateldesk-mcp-http
tags: |
type=raw,value=${{ needs.metadata.outputs.version }}
type=raw,value=latest,enable=${{ needs.metadata.outputs.prerelease == 'false' }}
labels: |
org.opencontainers.image.title=RatelDesk HTTP MCP
org.opencontainers.image.description=RatelDesk stateless HTTP MCP transport
org.opencontainers.image.source=https://github.com/BostonTechnologies/RatelDesk
org.opencontainers.image.revision=${{ needs.metadata.outputs.source_revision }}
org.opencontainers.image.version=${{ needs.metadata.outputs.version }}
org.opencontainers.image.created=${{ needs.metadata.outputs.build_timestamp }}
org.opencontainers.image.licenses=Apache-2.0

- name: Build and push multi-platform image
id: push
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: .
file: docker/mcp-http/Dockerfile
push: true
platforms: linux/amd64,linux/arm64
tags: ${{ steps.image.outputs.tags }}
labels: ${{ steps.image.outputs.labels }}
build-args: |
VERSION=${{ needs.metadata.outputs.version }}
SOURCE_REVISION_ID=${{ needs.metadata.outputs.source_revision }}
BUILD_TIMESTAMP=${{ needs.metadata.outputs.build_timestamp }}
provenance: mode=max
sbom: true

release:
name: Create GitHub Release
needs: [metadata, publish-web, publish-api]
needs: [metadata, validation, archive-runtime, publish-web, publish-api, publish-mcp-http]
runs-on: ubuntu-latest
permissions:
contents: write
Expand All @@ -262,19 +383,25 @@ jobs:
with:
ref: ${{ github.ref }}

- name: Download the validated release payload
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: release-assets
path: release-assets

- name: Verify public packages and anonymous pulls
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ needs.metadata.outputs.version }}
run: |
set -euo pipefail
for image in rateldesk-web rateldesk-api; do
for image in rateldesk-web rateldesk-api rateldesk-mcp-http; do
test "$(gh api "orgs/BostonTechnologies/packages/container/$image" --jq '.visibility')" = public
test "$(gh api "orgs/BostonTechnologies/packages/container/$image" --jq '.repository.full_name')" = BostonTechnologies/RatelDesk
docker pull "ghcr.io/bostontechnologies/$image:$VERSION"
done

- name: Create release after both images are available
- name: Create draft release after images and payload are verified
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ needs.metadata.outputs.version }}
Expand All @@ -283,6 +410,7 @@ jobs:
BUILD_TIMESTAMP: ${{ needs.metadata.outputs.build_timestamp }}
WEB_DIGEST: ${{ needs.publish-web.outputs.digest }}
API_DIGEST: ${{ needs.publish-api.outputs.digest }}
MCP_HTTP_DIGEST: ${{ needs.publish-mcp-http.outputs.digest }}
run: |
set -euo pipefail
prerelease_flag=()
Expand All @@ -295,28 +423,28 @@ jobs:

ghcr.io/bostontechnologies/rateldesk-web:$VERSION
ghcr.io/bostontechnologies/rateldesk-api:$VERSION
ghcr.io/bostontechnologies/rateldesk-mcp-http:$VERSION

Digests

Web: $WEB_DIGEST
API: $API_DIGEST
HTTP MCP: $MCP_HTTP_DIGEST

Source commit: $SOURCE_REVISION
Built: $BUILD_TIMESTAMP

Pull:
docker pull ghcr.io/bostontechnologies/rateldesk-web:$VERSION
docker pull ghcr.io/bostontechnologies/rateldesk-api:$VERSION"

if gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1; then
gh release edit "$GITHUB_REF_NAME" \
--title "RatelDesk $VERSION" \
"${prerelease_flag[@]}" \
--notes "$release_notes"
else
gh release create "$GITHUB_REF_NAME" \
--title "RatelDesk $VERSION" \
--generate-notes \
"${prerelease_flag[@]}" \
--notes "$release_notes"
fi
docker pull ghcr.io/bostontechnologies/rateldesk-api:$VERSION
docker pull ghcr.io/bostontechnologies/rateldesk-mcp-http:$VERSION"

tools/release/prepare-release-manifest.sh \
"$VERSION" "$SOURCE_REVISION" release-assets \
"$WEB_DIGEST" "$API_DIGEST" "$MCP_HTTP_DIGEST" "$GITHUB_REF_NAME"
tools/release/publish-release-assets.sh "$GITHUB_REF_NAME" "$VERSION" release-assets
gh release edit "$GITHUB_REF_NAME" \
--title "RatelDesk $VERSION" \
--draft \
"${prerelease_flag[@]}" \
--notes "$release_notes"
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,16 @@ The main public configuration surfaces are:
See [self-hosting guidance](docs/SELF_HOSTING.md) for configuration, Docker, reverse-proxy, identity, email, AI, MCP, orchestration, telemetry, and troubleshooting guidance.
See [instance branding](docs/branding.md) to customise the customer-facing identity without forking RatelDesk.

## API, CLI, and MCP

The Web-hosted [RatelDesk API reference](/api/docs) groups the public API by product area and sends Try It requests through the established `/api` proxy. Local account login is a browser-cookie flow with CSRF protection; a browser cookie is not a CLI or MCP Bearer credential.

For automation, sign in normally, complete configured MFA, then create a bounded API integration credential through `POST /api/v1/integration-credentials`. The secret is returned only by the create response. Store it in a protected configuration file or secret mount. Its effective access is always the intersection of the account's current authorization, the credential's selected permissions, and its organization scope; revocation and account disablement take effect on later requests.

GitHub Releases contain self-contained `rateldesk` CLI and `rateldesk-mcp` stdio MCP archives for Linux x64/arm64 and Windows x64. The Linux archives target glibc distributions, not Alpine/musl. Both executables support offline `--help` and `--version` before loading credentials.

HTTP MCP is optional and never joins the base Web/API stack. The source and release overlays are documented in [the HTTP MCP example](docker/examples/mcp-http/README.md). The existing Authentik HTTP MCP mode remains a separately configured external identity integration; it is not a fallback for local credentials.

## Contributing and security

Read [CONTRIBUTING.md](CONTRIBUTING.md) before opening a change. Report vulnerabilities privately according to [SECURITY.md](SECURITY.md).
Expand Down
52 changes: 52 additions & 0 deletions docker/docker-compose.mcp.release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: rateldesk

# Optional image-only overlay. It never creates a source build.
services:
mcp-config-init:
image: ghcr.io/bostontechnologies/rateldesk-mcp-http:${RATELDESK_VERSION:?Set RATELDESK_VERSION to an exact published RatelDesk version.}
user: "0:0"
entrypoint: ["/bin/sh", "-ec"]
command:
- >-
mkdir -p /run/rateldesk-mcp &&
cp /input/config.json /run/rateldesk-mcp/config.json &&
chown 10001:10001 /run/rateldesk-mcp/config.json &&
chmod 0400 /run/rateldesk-mcp/config.json
volumes:
- type: bind
source: ${RATELDESK_MCP_CONFIG_FILE:?Set RATELDESK_MCP_CONFIG_FILE to a protected configuration file.}
target: /input/config.json
read_only: true
- type: volume
source: mcp-http-config
target: /run/rateldesk-mcp

mcp-http:
image: ghcr.io/bostontechnologies/rateldesk-mcp-http:${RATELDESK_VERSION:?Set RATELDESK_VERSION to an exact published RatelDesk version.}
environment:
ASPNETCORE_ENVIRONMENT: Production
RATELDESK_MCP_CONFIG: /run/rateldesk-mcp/config.json
Helpdesk__Mcp__Instance: ${RATELDESK_MCP_INSTANCE:-local}
Helpdesk__Mcp__ExpectedApiBaseUrl: http://api:8222/
Helpdesk__Mcp__PublicResourceUri: ${RATELDESK_MCP_PUBLIC_RESOURCE_URI:?Set the canonical HTTPS MCP URL, for example https://mcp.example.test/mcp.}
Helpdesk__Mcp__AllowedOrigins__0: ${RATELDESK_MCP_ALLOWED_ORIGIN:?Set the browser origin allowed to call this MCP endpoint.}
Helpdesk__Mcp__AuthenticationMode: authentik
Authentication__AuthentikMcp__Authority: ${RATELDESK_MCP_AUTHENTIK_AUTHORITY:?Set the configured external authorization server URL.}
Authentication__AuthentikMcp__Audience: ${RATELDESK_MCP_PUBLIC_RESOURCE_URI:?Set the canonical HTTPS MCP URL, for example https://mcp.example.test/mcp.}
Authentication__AuthentikMcp__RequiredScopes__0: ${RATELDESK_MCP_REQUIRED_SCOPE:-helpdesk.mcp}
Authentication__AuthentikMcp__RequiredGroups__0: ${RATELDESK_MCP_REQUIRED_GROUP:-ai-assistant}
volumes:
- type: volume
source: mcp-http-config
target: /run/rateldesk-mcp
read_only: true
depends_on:
api:
condition: service_healthy
mcp-config-init:
condition: service_completed_successfully
ports:
- "127.0.0.1:${RATELDESK_MCP_PORT:-8223}:8223"

volumes:
mcp-http-config:
Loading
Loading