Skip to main content
Glama
chrisleekr

MCP Server Boilerplate

by chrisleekr
index.ts13.2 kB
/* eslint-disable max-statements */ /* eslint-disable max-lines-per-function */ /* eslint-disable complexity */ import { Task } from '@aws-sdk/client-ecs'; import yaml from 'yaml'; import { zodToJsonSchema } from 'zod-to-json-schema'; import { config } from '@/config/manager'; import { sendProgressNotification } from '@/core/server'; import { loggingContext } from '@/core/server/http/context'; import { describeServices, describeTasks, getQueryResults, invokeModel, listClusters, startQuery, } from '@/libraries/aws'; import { createStructuredContent, Tool, ToolBuilder, ToolContext, ToolInputSchema, ToolResult, } from '@/tools/types'; import { formatDate } from '@/utils/date'; import packageJson from '../../../../package.json'; import { AWSECSInput, AWSECSInputSchema, AWSECSOutput, AWSECSOutputContent, AWSECSOutputContentSchema, AWSECSOutputSchema, } from './types'; async function* executeAWSECS( input: AWSECSInput, context: ToolContext ): AsyncGenerator<ToolResult & { data?: AWSECSOutput }> { const progressToken = context.progressToken; loggingContext.log('info', `Progress token: ${progressToken}`); loggingContext.setContextValue('tool', 'aws-ecs'); const startTime = Date.now(); try { loggingContext.log('debug', 'Executing AWS ECS tool', { data: { input } }); // Send mid-progress notification if (context.server) { await sendProgressNotification(context.server, { progressToken, progress: 0, total: 100, message: 'Starting AWS ECS tool', }); } // Validate input using Zod schema const validatedInput = AWSECSInputSchema.parse(input); const output: AWSECSOutputContent = { analysis: '', service: { desiredCount: undefined, runningCount: undefined, pendingCount: undefined, events: [], }, task: { cpu: undefined, memory: undefined, healthStatus: undefined, lastStatus: undefined, startedAt: undefined, containers: [], }, cloudwatchLogs: [], }; // Get ECS clusters const listClustersResponse = await listClusters({}); loggingContext.log('info', 'ECS clusters listed', { data: { listClustersResponse }, }); // Find the cluster that matches the input const cluster = listClustersResponse.clusterArns?.find(clusterArn => clusterArn.includes(validatedInput.ecsCluster) ); if (cluster === undefined) { throw new Error(`Cluster ${validatedInput.ecsCluster} not found`); } // Get ECS task describe const describeTasksResponse = await describeTasks({ cluster, tasks: [validatedInput.ecsTaskArn], }); loggingContext.log('info', 'ECS task described', { data: { describeTasksResponse }, }); if (context.server) { await sendProgressNotification(context.server, { progressToken, progress: 20, total: 100, message: 'ECS task described', }); } const task: Task | undefined = describeTasksResponse.tasks?.[0]; output.task.cpu = task?.cpu; output.task.memory = task?.memory; output.task.healthStatus = task?.healthStatus; output.task.lastStatus = task?.lastStatus; output.task.startedAt = task?.startedAt ? formatDate(task.startedAt) : undefined; output.task.containers = describeTasksResponse.tasks?.[0]?.containers?.map( container => ({ name: container.name ?? '', lastStatus: container.lastStatus ?? '', }) ); output.task.tags = task?.tags?.map(tag => ({ key: tag.key ?? '', value: tag.value ?? '', })); output.task.group = task?.group; // Get service from group const serviceName = task?.group?.split(':')[0] === 'service' ? task.group.split(':')[1] : undefined; if (serviceName !== undefined) { const describeServicesResponse = await describeServices({ cluster, services: [serviceName], }); loggingContext.log('info', 'ECS service described', { data: { describeServicesResponse }, }); if (context.server) { await sendProgressNotification(context.server, { progressToken, progress: 40, total: 100, message: 'ECS service described', }); } output.service.desiredCount = describeServicesResponse.services?.[0]?.desiredCount; output.service.runningCount = describeServicesResponse.services?.[0]?.runningCount; output.service.pendingCount = describeServicesResponse.services?.[0]?.pendingCount; output.service.events = describeServicesResponse.services?.[0]?.events ?.slice(0, 5) .map(event => ({ message: event.message ?? '', createdAt: event.createdAt ? formatDate(event.createdAt) : undefined, })); } // Get cloudwatch logs const logGroupName = `/aws/ecs/containerinsights/${validatedInput.ecsCluster}/performance`; const taskId = validatedInput.ecsTaskArn.split('/').pop(); const startQueryResponse = await startQuery({ logGroupName, queryString: `fields @timestamp, CpuReserved, CpuUtilized, MemoryReserved, MemoryUtilized | filter ispresent(TaskId) and TaskId like /${taskId}/ and Type = "Task" | sort @timestamp desc | limit 10`, startTime: Date.now() - 30 * 60 * 1000, // Start time for last 30 minutes endTime: Date.now(), // End time for now }); loggingContext.log('info', 'CloudWatch logs queried', { data: { startQueryResponse }, }); // Poll for query results until it's done while (startQueryResponse.queryId !== undefined) { await new Promise(resolve => global.setTimeout(resolve, 1000)); const getQueryResultsResponse = await getQueryResults({ queryId: startQueryResponse.queryId, }); loggingContext.log('info', 'CloudWatch logs queried', { data: { getQueryResultsResponse }, }); if ( getQueryResultsResponse.status !== undefined && ['Complete', 'Failed', 'Cancelled', 'Timeout', 'Unknown'].includes( getQueryResultsResponse.status ) ) { if (getQueryResultsResponse.status === 'Complete') { output.cloudwatchLogs = getQueryResultsResponse.results?.map(result => { const timestamp = result.find( field => field.field === '@timestamp' )?.value; const logAt = timestamp !== undefined ? formatDate(new Date(timestamp)) : undefined; const cpuReserved = result.find( field => field.field === 'CpuReserved' )?.value; const cpuUtilized = result.find( field => field.field === 'CpuUtilized' )?.value; const cpuUtilizedPercentage = cpuUtilized !== undefined && cpuReserved !== undefined ? (parseFloat(cpuUtilized) / parseFloat(cpuReserved)) * 100 : undefined; const memoryReserved = result.find( field => field.field === 'MemoryReserved' )?.value; const memoryUtilized = result.find( field => field.field === 'MemoryUtilized' )?.value; const memoryUtilizedPercentage = memoryUtilized !== undefined && memoryReserved !== undefined ? (parseFloat(memoryUtilized) / parseFloat(memoryReserved)) * 100 : undefined; return { logAt, cpuReserved: cpuReserved !== undefined ? parseFloat(cpuReserved) : undefined, cpuUtilized: cpuUtilized !== undefined ? parseFloat(cpuUtilized) : undefined, cpuUtilizedPercentage, memoryReserved: memoryReserved !== undefined ? parseFloat(memoryReserved) : undefined, memoryUtilized: memoryUtilized !== undefined ? parseFloat(memoryUtilized) : undefined, memoryUtilizedPercentage, }; }) ?? []; } break; } else { loggingContext.log('info', 'CloudWatch logs query not complete', { data: { getQueryResultsResponse }, }); } } if (context.server) { await sendProgressNotification(context.server, { progressToken, progress: 60, total: 100, message: 'CloudWatch logs queried', }); } // Invoke Bedrock model to summarize all information const content = `You are senior site reliability engineer specialized ECS tasks incident triage. You are given a summary of an ECS service, task and cloudwatch logs for the task. You are to summarize the information in a way that is easy to understand and use. ## ECS Service: ${yaml.stringify(output.service)} ## ECS Task: ${yaml.stringify(output.task)} ## CloudWatch Logs: ${yaml.stringify(output.cloudwatchLogs)} ## Instructions: - Only use the information provided to you to summarize the information. - Do not make up any information. `; const modelId = config.tools.aws.bedrock.model; const body = JSON.stringify({ anthropic_version: 'bedrock-2023-05-31', messages: [ { role: 'user', content, }, ], max_tokens: 1000, temperature: 0.5, }); const invokeModelResponse = await invokeModel({ body, contentType: 'application/json', accept: 'application/json', modelId, trace: 'DISABLED', performanceConfigLatency: 'standard', }); loggingContext.log('info', 'Bedrock model invoked', { data: { invokeModelResponse }, }); const response = invokeModelResponse.body.transformToString(); const jsonResponse = JSON.parse(response.trim()) as { content: { type: string; text: string }[]; }; loggingContext.log('info', 'Bedrock model response parsed', { data: { jsonResponse }, }); output.analysis = jsonResponse.content.find(content => content.type === 'text')?.text ?? ''; // Add links to the analysis const region = config.tools.aws.region; const ecsCluster = validatedInput.ecsCluster; const ecsService = serviceName; output.analysis = `${output.analysis}\n\n[View ECS Service](https://${region}.console.aws.amazon.com/ecs/v2/clusters/${ecsCluster}/services/${ecsService}/health?region=${region})`; if (context.server) { await sendProgressNotification(context.server, { progressToken, progress: 100, total: 100, message: 'AWS ECS tool completed', }); } const executionTime = Date.now() - startTime; // Create structured content for the new MCP spec const structuredOutput = createStructuredContent( output, zodToJsonSchema(AWSECSOutputContentSchema), 'json' ); loggingContext.log('info', 'AWS ECS tool executed successfully', { data: { executionTime, }, }); yield { success: true, executionTime, timestamp: new Date().toISOString(), metadata: { toolVersion: packageJson.version, mcpSpecVersion: '2025-06-18', }, structuredContent: structuredOutput, }; } catch (error) { const executionTime = Date.now() - startTime; const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; loggingContext.log('error', 'AWS ECS tool execution failed', { data: { error: { message: errorMessage, stack: error instanceof Error ? error.stack : undefined, }, input, executionTime, }, }); yield { success: false, error: errorMessage, executionTime, timestamp: new Date().toISOString(), metadata: { toolVersion: packageJson.version, inputValidation: 'failed', }, }; } } export const awsEcsTool: Tool<AWSECSInput, AWSECSOutput> = new ToolBuilder< AWSECSInput, AWSECSOutput >('aws_ecs') .description('Investigate the ECS service, task and cloudwatch logs') .inputSchema(zodToJsonSchema(AWSECSInputSchema) as typeof ToolInputSchema) .outputSchema(zodToJsonSchema(AWSECSOutputSchema)) .examples([ { description: 'Investigate the ECS service, task and cloudwatch logs', input: { ecsCluster: 'ecs-cluster-123456', ecsTaskArn: 'arn:aws:ecs:ap-southeast-2:123456789:task/ecs-cluster-123456/123456789123456789123456789', }, output: { success: true, }, }, ]) .tags(['aws', 'ecs', 'task', 'service', 'cloudwatch']) .version(packageJson.version) .timeout(30000) .streamingImplementation(executeAWSECS) .build();

Latest Blog Posts

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/chrisleekr/mcp-server-boilerplate'

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