argocd-mcp
Version:
Argo CD MCP Server
808 lines (795 loc) • 25.3 kB
JavaScript
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
var __async = (__this, __arguments, generator) => {
return new Promise((resolve, reject) => {
var fulfilled = (value) => {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
};
var rejected = (value) => {
try {
step(generator.throw(value));
} catch (e) {
reject(e);
}
};
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
step((generator = generator.apply(__this, __arguments)).next());
});
};
// src/cmd/cmd.ts
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
// src/server/transport.ts
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import express from "express";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
// src/logging/logging.ts
import { pino } from "pino";
import { stderr } from "process";
var logger = pino(pino.destination(stderr));
// src/server/server.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
// package.json
var package_default = {
name: "argocd-mcp",
version: "0.2.0",
description: "Argo CD MCP Server",
repository: {
type: "git",
url: "git+https://github.com/akuity/argocd-mcp.git"
},
keywords: [
"mcp",
"argocd",
"argocd-mcp",
"argocd-mcp-server",
"argo-cd",
"argo-cd-mcp",
"argo-cd-mcp-server",
"cicd",
"cicd-mcp",
"cicd-mcp-server",
"gitops",
"gitops-mcp",
"gitops-mcp-server",
"kubernetes",
"kubernetes-mcp",
"kubernetes-mcp-server"
],
main: "dist/index.js",
type: "module",
bin: {
"argocd-mcp": "dist/index.js"
},
files: [
"dist",
"images",
"README.md",
"LICENSE"
],
scripts: {
dev: "tsx watch src/index.ts http",
"dev-sse": "tsx watch src/index.ts sse",
lint: "eslint src/**/*.ts --no-warn-ignored",
"lint:fix": "eslint src/**/*.ts --fix",
build: "tsup",
"build:watch": "tsup --watch",
"generate-types": "dtsgen -c dtsgen.json -o src/types/argocd.d.ts swagger.json",
prepare: "npm run build"
},
author: "Akuity, Inc.",
license: "Apache-2.0",
dependencies: {
"@modelcontextprotocol/sdk": "^1.10.1",
dotenv: "^16.5.0",
express: "^5.1.0",
pino: "^9.6.0",
yargs: "^17.7.2",
zod: "^3.24.3"
},
devDependencies: {
"@dtsgenerator/replace-namespace": "^1.7.0",
"@eslint/js": "^9.25.0",
"@types/express": "^5.0.1",
"@types/node": "^22.14.1",
"@types/yargs": "^17.0.33",
dtsgenerator: "^3.19.2",
eslint: "^9.25.0",
"eslint-config-prettier": "^10.1.2",
"eslint-plugin-prettier": "^5.2.6",
prettier: "3.5.3",
tsup: "^8.4.0",
tsx: "^4.19.3",
typescript: "^5.8.3",
"typescript-eslint": "^8.30.1"
}
};
// src/argocd/http.ts
var HttpClient = class {
constructor(baseUrl, apiToken) {
this.baseUrl = baseUrl;
this.apiToken = apiToken;
this.headers = {
Authorization: `Bearer ${this.apiToken}`,
"Content-Type": "application/json"
};
}
request(url, params, init) {
return __async(this, null, function* () {
const urlObject = this.absUrl(url);
if (params) {
Object.entries(params).forEach(([key, value]) => {
urlObject.searchParams.set(key, (value == null ? void 0 : value.toString()) || "");
});
}
const response = yield fetch(urlObject, __spreadProps(__spreadValues({}, init), {
headers: __spreadValues(__spreadValues({}, init == null ? void 0 : init.headers), this.headers)
}));
const body = yield response.json();
return {
status: response.status,
headers: response.headers,
body
};
});
}
requestStream(url, params, cb, init) {
return __async(this, null, function* () {
var _a;
const urlObject = this.absUrl(url);
if (params) {
Object.entries(params).forEach(([key, value]) => {
urlObject.searchParams.set(key, (value == null ? void 0 : value.toString()) || "");
});
}
const response = yield fetch(urlObject, __spreadProps(__spreadValues({}, init), {
headers: __spreadValues(__spreadValues({}, init == null ? void 0 : init.headers), this.headers)
}));
const reader = (_a = response.body) == null ? void 0 : _a.getReader();
if (!reader) {
throw new Error("response body is not readable");
}
const decoder = new TextDecoder("utf-8");
let buffer = "";
while (true) {
const { done, value } = yield reader.read();
if (done) {
break;
}
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop() || "";
for (const line of lines) {
if (line.trim()) {
const json = JSON.parse(line);
cb == null ? void 0 : cb(json["result"]);
}
}
}
});
}
absUrl(url) {
if (url.startsWith("http://") || url.startsWith("https://")) {
return new URL(url);
}
return new URL(url, this.baseUrl);
}
get(url, params) {
return __async(this, null, function* () {
const response = yield this.request(url, params);
return response;
});
}
getStream(url, params, cb) {
return __async(this, null, function* () {
yield this.requestStream(url, params, cb);
});
}
post(url, params, body) {
return __async(this, null, function* () {
const response = yield this.request(url, params, {
method: "POST",
body: body ? JSON.stringify(body) : void 0
});
return response;
});
}
put(url, params, body) {
return __async(this, null, function* () {
const response = yield this.request(url, params, {
method: "PUT",
body: body ? JSON.stringify(body) : void 0
});
return response;
});
}
delete(url, params) {
return __async(this, null, function* () {
const response = yield this.request(url, params, {
method: "DELETE"
});
return response;
});
}
};
// src/argocd/client.ts
var ArgoCDClient = class {
constructor(baseUrl, apiToken) {
this.baseUrl = baseUrl;
this.apiToken = apiToken;
this.client = new HttpClient(this.baseUrl, this.apiToken);
}
listApplications(params) {
return __async(this, null, function* () {
const { body } = yield this.client.get(`/api/v1/applications`, params);
return body;
});
}
getApplication(applicationName) {
return __async(this, null, function* () {
const { body } = yield this.client.get(
`/api/v1/applications/${applicationName}`
);
return body;
});
}
createApplication(application) {
return __async(this, null, function* () {
const { body } = yield this.client.post(
`/api/v1/applications`,
null,
application
);
return body;
});
}
updateApplication(applicationName, application) {
return __async(this, null, function* () {
const { body } = yield this.client.put(
`/api/v1/applications/${applicationName}`,
null,
application
);
return body;
});
}
deleteApplication(applicationName) {
return __async(this, null, function* () {
const { body } = yield this.client.delete(
`/api/v1/applications/${applicationName}`
);
return body;
});
}
syncApplication(applicationName) {
return __async(this, null, function* () {
const { body } = yield this.client.post(
`/api/v1/applications/${applicationName}/sync`
);
return body;
});
}
getApplicationResourceTree(applicationName) {
return __async(this, null, function* () {
const { body } = yield this.client.get(
`/api/v1/applications/${applicationName}/resource-tree`
);
return body;
});
}
getApplicationManagedResources(applicationName) {
return __async(this, null, function* () {
const { body } = yield this.client.get(
`/api/v1/applications/${applicationName}/managed-resources`
);
return body;
});
}
getApplicationLogs(applicationName) {
return __async(this, null, function* () {
const logs = [];
yield this.client.getStream(
`/api/v1/applications/${applicationName}/logs`,
{
follow: false,
tailLines: 100
},
(chunk) => logs.push(chunk)
);
return logs;
});
}
getWorkloadLogs(applicationName, applicationNamespace, resourceRef) {
return __async(this, null, function* () {
const logs = [];
yield this.client.getStream(
`/api/v1/applications/${applicationName}/logs`,
{
appNamespace: applicationNamespace,
namespace: resourceRef.namespace,
resourceName: resourceRef.name,
group: resourceRef.group,
kind: resourceRef.kind,
version: resourceRef.version,
follow: false,
tailLines: 100
},
(chunk) => logs.push(chunk)
);
return logs;
});
}
getPodLogs(applicationName, podName) {
return __async(this, null, function* () {
const logs = [];
yield this.client.getStream(
`/api/v1/applications/${applicationName}/pods/${podName}/logs`,
{
follow: false,
tailLines: 100
},
(chunk) => logs.push(chunk)
);
return logs;
});
}
getApplicationEvents(applicationName) {
return __async(this, null, function* () {
const { body } = yield this.client.get(
`/api/v1/applications/${applicationName}/events`
);
return body;
});
}
getResourceEvents(applicationName, applicationNamespace, resourceUID, resourceNamespace, resourceName) {
return __async(this, null, function* () {
const { body } = yield this.client.get(
`/api/v1/applications/${applicationName}/events`,
{
appNamespace: applicationNamespace,
resourceNamespace,
resourceUID,
resourceName
}
);
return body;
});
}
getResourceActions(applicationName, applicationNamespace, resourceRef) {
return __async(this, null, function* () {
const { body } = yield this.client.get(
`/api/v1/applications/${applicationName}/resource/actions`,
{
appNamespace: applicationNamespace,
namespace: resourceRef.namespace,
resourceName: resourceRef.name,
group: resourceRef.group,
kind: resourceRef.kind,
version: resourceRef.version
}
);
return body;
});
}
runResourceAction(applicationName, applicationNamespace, resourceRef, action) {
return __async(this, null, function* () {
const { body } = yield this.client.post(
`/api/v1/applications/${applicationName}/resource/actions`,
{
appNamespace: applicationNamespace,
namespace: resourceRef.namespace,
resourceName: resourceRef.name,
group: resourceRef.group,
kind: resourceRef.kind,
version: resourceRef.version
},
action
);
return body;
});
}
};
// src/server/server.ts
import { z as z2 } from "zod";
// src/shared/models/schema.ts
import { z } from "zod";
var ApplicationNamespaceSchema = z.string().describe(
`The namespace of the application.
Note that this may differ from the namespace of individual resources.
Make sure to verify the application namespace in the Application resource \u2014 it is often argocd, but not always.`
);
var ResourceRefSchema = z.object({
uid: z.string(),
kind: z.string(),
namespace: z.string(),
name: z.string(),
version: z.string(),
group: z.string()
});
var ApplicationSchema = z.object({
metadata: z.object({
name: z.string(),
namespace: ApplicationNamespaceSchema
}),
spec: z.object({
project: z.string(),
source: z.object({
repoURL: z.string(),
path: z.string(),
targetRevision: z.string()
}),
syncPolicy: z.object({
syncOptions: z.array(z.string()),
automated: z.object({
prune: z.boolean(),
selfHeal: z.boolean()
}).optional(),
retry: z.object({
limit: z.number(),
backoff: z.object({
duration: z.string(),
maxDuration: z.string(),
factor: z.number()
})
})
}),
destination: z.object({
server: z.string().optional(),
namespace: z.string().optional(),
name: z.string().optional()
}).refine(
(data) => !data.server && !!data.name || !!data.server && !data.name,
{
message: "Only one of server or name must be specified in destination"
}
).describe(
`The destination of the application.
Only one of server or name must be specified.`
)
})
});
// src/server/server.ts
var Server = class extends McpServer {
constructor(serverInfo) {
super({
name: package_default.name,
version: package_default.version
});
this.argocdClient = new ArgoCDClient(serverInfo.argocdBaseUrl, serverInfo.argocdApiToken);
this.addJsonOutputTool(
"list_applications",
"list_applications returns list of applications",
{
search: z2.string().optional().describe(
'Search applications by name. This is a partial match on the application name and does not support glob patterns (e.g. "*"). Optional.'
)
},
(_0) => __async(this, [_0], function* ({ search }) {
return yield this.argocdClient.listApplications({ search: search != null ? search : void 0 });
})
);
this.addJsonOutputTool(
"get_application",
"get_application returns application by application name",
{ applicationName: z2.string() },
(_0) => __async(this, [_0], function* ({ applicationName }) {
return yield this.argocdClient.getApplication(applicationName);
})
);
this.addJsonOutputTool(
"create_application",
"create_application creates application",
{ application: ApplicationSchema },
(_0) => __async(this, [_0], function* ({ application }) {
return yield this.argocdClient.createApplication(application);
})
);
this.addJsonOutputTool(
"update_application",
"update_application updates application",
{ applicationName: z2.string(), application: ApplicationSchema },
(_0) => __async(this, [_0], function* ({ applicationName, application }) {
return yield this.argocdClient.updateApplication(
applicationName,
application
);
})
);
this.addJsonOutputTool(
"delete_application",
"delete_application deletes application",
{ applicationName: z2.string() },
(_0) => __async(this, [_0], function* ({ applicationName }) {
return yield this.argocdClient.deleteApplication(applicationName);
})
);
this.addJsonOutputTool(
"sync_application",
"sync_application syncs application",
{ applicationName: z2.string() },
(_0) => __async(this, [_0], function* ({ applicationName }) {
return yield this.argocdClient.syncApplication(applicationName);
})
);
this.addJsonOutputTool(
"get_application_resource_tree",
"get_application_resource_tree returns resource tree for application by application name",
{ applicationName: z2.string() },
(_0) => __async(this, [_0], function* ({ applicationName }) {
return yield this.argocdClient.getApplicationResourceTree(applicationName);
})
);
this.addJsonOutputTool(
"get_application_managed_resources",
"get_application_managed_resources returns managed resources for application by application name",
{ applicationName: z2.string() },
(_0) => __async(this, [_0], function* ({ applicationName }) {
return yield this.argocdClient.getApplicationManagedResources(applicationName);
})
);
this.addJsonOutputTool(
"get_application_workload_logs",
"get_application_workload_logs returns logs for application workload (Deployment, StatefulSet, Pod, etc.) by application name and resource ref",
{
applicationName: z2.string(),
applicationNamespace: ApplicationNamespaceSchema,
resourceRef: ResourceRefSchema
},
(_0) => __async(this, [_0], function* ({ applicationName, applicationNamespace, resourceRef }) {
return yield this.argocdClient.getWorkloadLogs(
applicationName,
applicationNamespace,
resourceRef
);
})
);
this.addJsonOutputTool(
"get_application_events",
"get_application_events returns events for application by application name",
{ applicationName: z2.string() },
(_0) => __async(this, [_0], function* ({ applicationName }) {
return yield this.argocdClient.getApplicationEvents(applicationName);
})
);
this.addJsonOutputTool(
"get_resource_events",
"get_resource_events returns events for a resource that is managed by an application",
{
applicationName: z2.string(),
applicationNamespace: ApplicationNamespaceSchema,
resourceUID: z2.string(),
resourceNamespace: z2.string(),
resourceName: z2.string()
},
(_0) => __async(this, [_0], function* ({
applicationName,
applicationNamespace,
resourceUID,
resourceNamespace,
resourceName
}) {
return yield this.argocdClient.getResourceEvents(
applicationName,
applicationNamespace,
resourceUID,
resourceNamespace,
resourceName
);
})
);
this.addJsonOutputTool(
"get_resource_actions",
"get_resource_actions returns actions for a resource that is managed by an application",
{
applicationName: z2.string(),
applicationNamespace: ApplicationNamespaceSchema,
resourceRef: ResourceRefSchema
},
(_0) => __async(this, [_0], function* ({ applicationName, applicationNamespace, resourceRef }) {
return yield this.argocdClient.getResourceActions(
applicationName,
applicationNamespace,
resourceRef
);
})
);
this.addJsonOutputTool(
"run_resource_action",
"run_resource_action runs an action on a resource",
{
applicationName: z2.string(),
applicationNamespace: ApplicationNamespaceSchema,
resourceRef: ResourceRefSchema,
action: z2.string()
},
(_0) => __async(this, [_0], function* ({ applicationName, applicationNamespace, resourceRef, action }) {
return yield this.argocdClient.runResourceAction(
applicationName,
applicationNamespace,
resourceRef,
action
);
})
);
}
addJsonOutputTool(name, description, paramsSchema, cb) {
this.tool(name, description, paramsSchema, (...args) => __async(this, null, function* () {
try {
const result = yield cb.apply(this, args);
return {
isError: false,
content: [{ type: "text", text: JSON.stringify(result) }]
};
} catch (error) {
return {
isError: true,
content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }]
};
}
}));
}
};
var createServer = (serverInfo) => {
return new Server(serverInfo);
};
// src/server/transport.ts
import { randomUUID } from "node:crypto";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
var connectStdioTransport = () => {
const server = createServer({
argocdBaseUrl: process.env.ARGOCD_BASE_URL || "",
argocdApiToken: process.env.ARGOCD_API_TOKEN || ""
});
logger.info("Connecting to stdio transport");
server.connect(new StdioServerTransport());
};
var connectSSETransport = (port) => {
const app = express();
const transports = {};
app.get("/sse", (req, res) => __async(void 0, null, function* () {
const server = createServer({
argocdBaseUrl: req.headers["x-argocd-base-url"] || "",
argocdApiToken: req.headers["x-argocd-api-token"] || ""
});
const transport = new SSEServerTransport("/messages", res);
transports[transport.sessionId] = transport;
res.on("close", () => {
delete transports[transport.sessionId];
});
yield server.connect(transport);
}));
app.post("/messages", (req, res) => __async(void 0, null, function* () {
const sessionId = req.query.sessionId;
const transport = transports[sessionId];
if (transport) {
yield transport.handlePostMessage(req, res);
} else {
res.status(400).send(`No transport found for sessionId: ${sessionId}`);
}
}));
logger.info(`Connecting to SSE transport on port: ${port}`);
app.listen(port);
};
var connectHttpTransport = (port) => {
const app = express();
app.use(express.json());
const httpTransports = {};
app.post("/mcp", (req, res) => __async(void 0, null, function* () {
var _a;
const sessionIdFromHeader = req.headers["mcp-session-id"];
let transport;
if (sessionIdFromHeader && httpTransports[sessionIdFromHeader]) {
transport = httpTransports[sessionIdFromHeader];
} else if (!sessionIdFromHeader && isInitializeRequest(req.body)) {
const argocdBaseUrl = req.headers["x-argocd-base-url"] || "";
const argocdApiToken = req.headers["x-argocd-api-token"] || "";
if (argocdBaseUrl == "" || argocdApiToken == "") {
res.status(400).send("x-argocd-base-url and x-argocd-api-token must be provided in headers.");
return;
}
transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
onsessioninitialized: (newSessionId) => {
httpTransports[newSessionId] = transport;
}
});
transport.onclose = () => {
if (transport.sessionId) {
delete httpTransports[transport.sessionId];
}
};
const server = createServer({
argocdBaseUrl,
argocdApiToken
});
yield server.connect(transport);
} else {
const errorMsg = sessionIdFromHeader ? `Invalid or expired session ID: ${sessionIdFromHeader}` : "Bad Request: Not an initialization request and no valid session ID provided.";
res.status(400).json({
jsonrpc: "2.0",
error: {
code: -32e3,
message: errorMsg
},
id: ((_a = req.body) == null ? void 0 : _a.id) !== void 0 ? req.body.id : null
});
return;
}
yield transport.handleRequest(req, res, req.body);
}));
const handleSessionRequest = (req, res) => __async(void 0, null, function* () {
const sessionId = req.headers["mcp-session-id"];
if (!sessionId || !httpTransports[sessionId]) {
res.status(400).send("Invalid or missing session ID");
return;
}
const transport = httpTransports[sessionId];
yield transport.handleRequest(req, res);
});
app.get("/mcp", handleSessionRequest);
app.delete("/mcp", handleSessionRequest);
logger.info(`Connecting to Http Stream transport on port: ${port}`);
app.listen(port);
};
// src/cmd/cmd.ts
var cmd = () => {
const exe = yargs(hideBin(process.argv));
exe.command(
"stdio",
"Start ArgoCD MCP server using stdio.",
() => {
},
() => connectStdioTransport()
);
exe.command(
"sse",
"Start ArgoCD MCP server using SSE.",
(yargs2) => {
return yargs2.option("port", {
type: "number",
default: 3e3
});
},
({ port }) => connectSSETransport(port)
);
exe.command(
"http",
"Start ArgoCD MCP server using Http Stream.",
(yargs2) => {
return yargs2.option("port", {
type: "number",
default: 3e3
});
},
({ port }) => connectHttpTransport(port)
);
exe.demandCommand().parseSync();
};
// src/index.ts
import dotenv from "dotenv";
dotenv.config();
cmd();
//# sourceMappingURL=index.js.map