From c2ee958f5b1a2600ee6e8b9992b64d563af072fc Mon Sep 17 00:00:00 2001 From: smartinellibenedetti <139791797+smartinellibenedetti@users.noreply.github.com> Date: Wed, 19 Aug 2026 16:21:40 -0600 Subject: [PATCH 1/3] Add User-Agent header to api_call requests Sends "rundeck-mcp/" on every outbound API call. The version is baked into src/tools/api.ts's USER_AGENT constant at build time by a new CI step (guarded by a strict vX.Y.Z tag format check to avoid shell injection via CIRCLE_TAG), so branch/PR builds keep the SNAPSHOT default. Co-Authored-By: Claude Sonnet 5 --- .circleci/config.yml | 17 +++++++++++++++++ src/__tests__/tools/api.test.ts | 12 +++++++----- src/tools/api.ts | 5 +++++ 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index cb1dc69..3f0e196 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -64,6 +64,23 @@ jobs: - run: name: Install dependencies command: npm ci + - run: + # Bakes the release version into the User-Agent header at build time (see + # src/tools/api.ts's USER_AGENT constant) so it's static in the published + # dist/ — no runtime file reads. Only runs on tag builds; the version stays + # "SNAPSHOT" for branch/PR builds. CIRCLE_TAG is validated against a strict + # vX.Y.Z pattern first: git ref names may contain shell metacharacters like + # $() or backticks, and CIRCLE_TAG is interpolated into a shell command + # below, so an unvalidated tag would be a command-injection vector. + name: Bake release version into User-Agent + command: | + if [ -n "${CIRCLE_TAG:-}" ]; then + if ! echo "${CIRCLE_TAG}" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+$'; then + echo "CIRCLE_TAG '${CIRCLE_TAG}' does not match expected vX.Y.Z pattern — failing." >&2 + exit 1 + fi + sed -i "s/rundeck-mcp\/SNAPSHOT/rundeck-mcp\/${CIRCLE_TAG#v}/" src/tools/api.ts + fi - run: name: Build command: npm run build diff --git a/src/__tests__/tools/api.test.ts b/src/__tests__/tools/api.test.ts index f6e958a..6cd4b5c 100644 --- a/src/__tests__/tools/api.test.ts +++ b/src/__tests__/tools/api.test.ts @@ -7,6 +7,7 @@ import { rundeckSetupToken, rundeckListEndpoints, isRunnerCredentialRegenerationEndpoint, + USER_AGENT, } from "../../tools/api.js"; import { configManager } from "../../config.js"; @@ -137,11 +138,12 @@ describe("API Tools", () => { // The endpoint "/projects" gets prepended with the API base URL const callArgs = mockFetch.mock.calls[0]; expect(callArgs[0]).toContain("/api/59/projects"); - expect(callArgs[1]).toMatchObject({ - method: "GET", - headers: expect.objectContaining({ - "X-Rundeck-Auth-Token": "test-token", - }), + expect(callArgs[1]).toMatchObject({ method: "GET" }); + expect((callArgs[1] as RequestInit).headers).toEqual({ + "X-Rundeck-Auth-Token": "test-token", + "Accept": "application/json", + "Content-Type": "application/json", + "User-Agent": USER_AGENT, }); }); diff --git a/src/tools/api.ts b/src/tools/api.ts index a2e4cda..132b921 100644 --- a/src/tools/api.ts +++ b/src/tools/api.ts @@ -7,6 +7,10 @@ import { configManager } from "../config.js"; import { listApiEndpoints } from "../resources/api.js"; import { loadOpenApiDocument, validateOpenApiRequest } from "../utils/openapi-validate.js"; +// Replaced with the release tag's version by the CI pipeline's tagged builds (see +// .circleci/config.yml's "build" job); stays "SNAPSHOT" on branch/PR builds. +export const USER_AGENT = "rundeck-mcp/SNAPSHOT"; + /** * Node's `fetch` (undici) collapses every network-level failure into a generic * `TypeError: fetch failed`, with the actual reason nested one level down in @@ -123,6 +127,7 @@ export async function rundeckApiCall(params: { "X-Rundeck-Auth-Token": config.apiToken, "Accept": "application/json", "Content-Type": params.content_type || "application/json", + "User-Agent": USER_AGENT, }; const options: RequestInit = { From ba535e715aeecd30f686da3cb11da5d0d4d617f8 Mon Sep 17 00:00:00 2001 From: smartinellibenedetti <139791797+smartinellibenedetti@users.noreply.github.com> Date: Wed, 19 Aug 2026 16:22:18 -0600 Subject: [PATCH 2/3] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8b63143..a39774c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@rundeck/mcp", - "version": "0.0.1", + "version": "SNAPSHOT", "description": "MCP server for Rundeck documentation", "type": "module", "main": "dist/index.js", From b8a9cd2ac0e7585e38b74da17939c919965627d6 Mon Sep 17 00:00:00 2001 From: smartinellibenedetti <139791797+smartinellibenedetti@users.noreply.github.com> Date: Wed, 19 Aug 2026 16:31:20 -0600 Subject: [PATCH 3/3] Bake release version into server.json alongside User-Agent Sets server.json's version fields to "SNAPSHOT" as the placeholder, matching src/tools/api.ts's USER_AGENT, and extends the CI build step's sed replacement to patch both so server.json doesn't drift from the actual release version. Co-Authored-By: Claude Sonnet 5 --- .circleci/config.yml | 22 +++++++++++++--------- server.json | 4 ++-- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3f0e196..0614af8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -65,21 +65,25 @@ jobs: name: Install dependencies command: npm ci - run: - # Bakes the release version into the User-Agent header at build time (see - # src/tools/api.ts's USER_AGENT constant) so it's static in the published - # dist/ — no runtime file reads. Only runs on tag builds; the version stays - # "SNAPSHOT" for branch/PR builds. CIRCLE_TAG is validated against a strict - # vX.Y.Z pattern first: git ref names may contain shell metacharacters like - # $() or backticks, and CIRCLE_TAG is interpolated into a shell command - # below, so an unvalidated tag would be a command-injection vector. - name: Bake release version into User-Agent + # Bakes the release version into the User-Agent header (src/tools/api.ts's + # USER_AGENT constant, compiled into dist/ below) and into server.json's + # version fields (MCP registry manifest — not read by this pipeline, but + # kept in sync with the release for whatever reads it out of the tagged + # checkout). Both stay "SNAPSHOT" on branch/PR builds. CIRCLE_TAG is + # validated against a strict vX.Y.Z pattern first: git ref names may + # contain shell metacharacters like $() or backticks, and CIRCLE_TAG is + # interpolated into a shell command below, so an unvalidated tag would be + # a command-injection vector. + name: Bake release version into User-Agent and server.json command: | if [ -n "${CIRCLE_TAG:-}" ]; then if ! echo "${CIRCLE_TAG}" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+$'; then echo "CIRCLE_TAG '${CIRCLE_TAG}' does not match expected vX.Y.Z pattern — failing." >&2 exit 1 fi - sed -i "s/rundeck-mcp\/SNAPSHOT/rundeck-mcp\/${CIRCLE_TAG#v}/" src/tools/api.ts + VERSION="${CIRCLE_TAG#v}" + sed -i "s/rundeck-mcp\/SNAPSHOT/rundeck-mcp\/${VERSION}/" src/tools/api.ts + sed -i "s/\"SNAPSHOT\"/\"${VERSION}\"/" server.json fi - run: name: Build diff --git a/server.json b/server.json index ba279e3..1df9421 100644 --- a/server.json +++ b/server.json @@ -2,7 +2,7 @@ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json", "name": "io.github.rundeckpro/rundeck-mcp", "description": "Rundeck MCP server — exposes Rundeck documentation, API access, and job/runner management tools to AI assistants.", - "version": "1.0.0", + "version": "SNAPSHOT", "repository": { "url": "https://github.com/rundeckpro/rundeck_mcp", "source": "github" @@ -11,7 +11,7 @@ { "registryType": "npm", "identifier": "@rundeck/mcp", - "version": "1.0.0", + "version": "SNAPSHOT", "transport": { "type": "stdio" },