Skip to main content
Glama
by akuity
server.ts13.5 kB
import { McpServer, ToolCallback } from '@modelcontextprotocol/sdk/server/mcp.js'; import packageJSON from '../../package.json' with { type: 'json' }; import { ArgoCDClient } from '../argocd/client.js'; import { z, ZodRawShape } from 'zod'; import { V1alpha1Application, V1alpha1ResourceResult } from '../types/argocd-types.js'; import { ApplicationNamespaceSchema, ApplicationSchema, ResourceRefSchema } from '../shared/models/schema.js'; type ServerInfo = { argocdBaseUrl: string; argocdApiToken: string; }; export class Server extends McpServer { private argocdClient: ArgoCDClient; constructor(serverInfo: ServerInfo) { super({ name: packageJSON.name, version: packageJSON.version }); this.argocdClient = new ArgoCDClient(serverInfo.argocdBaseUrl, serverInfo.argocdApiToken); const isReadOnly = String(process.env.MCP_READ_ONLY ?? '') .trim() .toLowerCase() === 'true'; // Always register read/query tools this.addJsonOutputTool( 'list_applications', 'list_applications returns list of applications', { search: z .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.' ), limit: z .number() .int() .positive() .optional() .describe( 'Maximum number of applications to return. Use this to reduce token usage when there are many applications. Optional.' ), offset: z .number() .int() .min(0) .optional() .describe( 'Number of applications to skip before returning results. Use with limit for pagination. Optional.' ) }, async ({ search, limit, offset }) => await this.argocdClient.listApplications({ search: search ?? undefined, limit, offset }) ); this.addJsonOutputTool( 'get_application', 'get_application returns application by application name. Optionally specify the application namespace to get applications from non-default namespaces.', { applicationName: z.string(), applicationNamespace: ApplicationNamespaceSchema.optional() }, async ({ applicationName, applicationNamespace }) => await this.argocdClient.getApplication(applicationName, applicationNamespace) ); this.addJsonOutputTool( 'get_application_resource_tree', 'get_application_resource_tree returns resource tree for application by application name', { applicationName: z.string() }, async ({ applicationName }) => await this.argocdClient.getApplicationResourceTree(applicationName) ); this.addJsonOutputTool( 'get_application_managed_resources', 'get_application_managed_resources returns managed resources for application by application name with optional filtering. Use filters to avoid token limits with large applications. Examples: kind="ConfigMap" for config maps only, namespace="production" for specific namespace, or combine multiple filters.', { applicationName: z.string(), kind: z .string() .optional() .describe( 'Filter by Kubernetes resource kind (e.g., "ConfigMap", "Secret", "Deployment")' ), namespace: z.string().optional().describe('Filter by Kubernetes namespace'), name: z.string().optional().describe('Filter by resource name'), version: z.string().optional().describe('Filter by resource API version'), group: z.string().optional().describe('Filter by API group'), appNamespace: z.string().optional().describe('Filter by Argo CD application namespace'), project: z.string().optional().describe('Filter by Argo CD project') }, async ({ applicationName, kind, namespace, name, version, group, appNamespace, project }) => { const filters = { ...(kind && { kind }), ...(namespace && { namespace }), ...(name && { name }), ...(version && { version }), ...(group && { group }), ...(appNamespace && { appNamespace }), ...(project && { project }) }; return await this.argocdClient.getApplicationManagedResources( applicationName, Object.keys(filters).length > 0 ? filters : undefined ); } ); 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 and optionally container name', { applicationName: z.string(), applicationNamespace: ApplicationNamespaceSchema, resourceRef: ResourceRefSchema, container: z.string() }, async ({ applicationName, applicationNamespace, resourceRef, container }) => await this.argocdClient.getWorkloadLogs( applicationName, applicationNamespace, resourceRef as V1alpha1ResourceResult, container ) ); this.addJsonOutputTool( 'get_application_events', 'get_application_events returns events for application by application name', { applicationName: z.string() }, async ({ applicationName }) => await this.argocdClient.getApplicationEvents(applicationName) ); this.addJsonOutputTool( 'get_resource_events', 'get_resource_events returns events for a resource that is managed by an application', { applicationName: z.string(), applicationNamespace: ApplicationNamespaceSchema, resourceUID: z.string(), resourceNamespace: z.string(), resourceName: z.string() }, async ({ applicationName, applicationNamespace, resourceUID, resourceNamespace, resourceName }) => await this.argocdClient.getResourceEvents( applicationName, applicationNamespace, resourceUID, resourceNamespace, resourceName ) ); this.addJsonOutputTool( 'get_resources', 'get_resources return manifests for resources specified by resourceRefs. If resourceRefs is empty or not provided, fetches all resources managed by the application.', { applicationName: z.string(), applicationNamespace: ApplicationNamespaceSchema, resourceRefs: ResourceRefSchema.array().optional() }, async ({ applicationName, applicationNamespace, resourceRefs }) => { let refs = resourceRefs || []; if (refs.length === 0) { const tree = await this.argocdClient.getApplicationResourceTree(applicationName); refs = tree.nodes?.map((node) => ({ uid: node.uid!, version: node.version!, group: node.group!, kind: node.kind!, name: node.name!, namespace: node.namespace! })) || []; } return Promise.all( refs.map((ref) => this.argocdClient.getResource(applicationName, applicationNamespace, ref) ) ); } ); this.addJsonOutputTool( 'get_resource_actions', 'get_resource_actions returns actions for a resource that is managed by an application', { applicationName: z.string(), applicationNamespace: ApplicationNamespaceSchema, resourceRef: ResourceRefSchema }, async ({ applicationName, applicationNamespace, resourceRef }) => await this.argocdClient.getResourceActions( applicationName, applicationNamespace, resourceRef as V1alpha1ResourceResult ) ); // Only register modification tools if not in read-only mode if (!isReadOnly) { this.addJsonOutputTool( 'create_application', 'create_application creates a new ArgoCD application in the specified namespace. The application.metadata.namespace field determines where the Application resource will be created (e.g., "argocd", "argocd-apps", or any custom namespace).', { application: ApplicationSchema }, async ({ application }) => await this.argocdClient.createApplication(application as V1alpha1Application) ); this.addJsonOutputTool( 'update_application', 'update_application updates application', { applicationName: z.string(), application: ApplicationSchema }, async ({ applicationName, application }) => await this.argocdClient.updateApplication( applicationName, application as V1alpha1Application ) ); this.addJsonOutputTool( 'delete_application', 'delete_application deletes application. Specify applicationNamespace if the application is in a non-default namespace to avoid permission errors.', { applicationName: z.string(), applicationNamespace: ApplicationNamespaceSchema.optional().describe( 'The namespace where the application is located. Required if application is not in the default namespace.' ), cascade: z .boolean() .optional() .describe('Whether to cascade the deletion to child resources'), propagationPolicy: z .string() .optional() .describe('Deletion propagation policy (e.g., "Foreground", "Background", "Orphan")') }, async ({ applicationName, applicationNamespace, cascade, propagationPolicy }) => { const options: Record<string, string | boolean> = {}; if (applicationNamespace) options.appNamespace = applicationNamespace; if (cascade !== undefined) options.cascade = cascade; if (propagationPolicy) options.propagationPolicy = propagationPolicy; return await this.argocdClient.deleteApplication( applicationName, Object.keys(options).length > 0 ? options : undefined ); } ); this.addJsonOutputTool( 'sync_application', 'sync_application syncs application. Specify applicationNamespace if the application is in a non-default namespace to avoid permission errors.', { applicationName: z.string(), applicationNamespace: ApplicationNamespaceSchema.optional().describe( 'The namespace where the application is located. Required if application is not in the default namespace.' ), dryRun: z .boolean() .optional() .describe('Perform a dry run sync without applying changes'), prune: z .boolean() .optional() .describe('Remove resources that are no longer defined in the source'), revision: z .string() .optional() .describe('Sync to a specific revision instead of the latest'), syncOptions: z .array(z.string()) .optional() .describe( 'Additional sync options (e.g., ["CreateNamespace=true", "PrunePropagationPolicy=foreground"])' ) }, async ({ applicationName, applicationNamespace, dryRun, prune, revision, syncOptions }) => { const options: Record<string, string | boolean | string[]> = {}; if (applicationNamespace) options.appNamespace = applicationNamespace; if (dryRun !== undefined) options.dryRun = dryRun; if (prune !== undefined) options.prune = prune; if (revision) options.revision = revision; if (syncOptions) options.syncOptions = syncOptions; return await this.argocdClient.syncApplication( applicationName, Object.keys(options).length > 0 ? options : undefined ); } ); this.addJsonOutputTool( 'run_resource_action', 'run_resource_action runs an action on a resource', { applicationName: z.string(), applicationNamespace: ApplicationNamespaceSchema, resourceRef: ResourceRefSchema, action: z.string() }, async ({ applicationName, applicationNamespace, resourceRef, action }) => await this.argocdClient.runResourceAction( applicationName, applicationNamespace, resourceRef as V1alpha1ResourceResult, action ) ); } } private addJsonOutputTool<Args extends ZodRawShape, T>( name: string, description: string, paramsSchema: Args, cb: (...cbArgs: Parameters<ToolCallback<Args>>) => T ) { this.tool(name, description, paramsSchema as ZodRawShape, async (...args) => { try { const result = await cb.apply(this, args as Parameters<ToolCallback<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) }] }; } }); } } export const createServer = (serverInfo: ServerInfo) => { return new Server(serverInfo); };

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/akuity/argocd-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server