Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
21 changes: 21 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,27 @@ jobs:
- run:
name: Install dependencies
command: npm ci
- run:
# 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
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
command: npm run build
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
4 changes: 2 additions & 2 deletions server.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -11,7 +11,7 @@
{
"registryType": "npm",
"identifier": "@rundeck/mcp",
"version": "1.0.0",
"version": "SNAPSHOT",
"transport": {
"type": "stdio"
},
Expand Down
12 changes: 7 additions & 5 deletions src/__tests__/tools/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
rundeckSetupToken,
rundeckListEndpoints,
isRunnerCredentialRegenerationEndpoint,
USER_AGENT,
} from "../../tools/api.js";
import { configManager } from "../../config.js";

Expand Down Expand Up @@ -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,
});
});

Expand Down
5 changes: 5 additions & 0 deletions src/tools/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 = {
Expand Down