@jaypie/mcp 0.2.12 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -10,13 +10,15 @@ import * as fs from 'node:fs/promises';
10
10
  import matter from 'gray-matter';
11
11
  import * as https from 'node:https';
12
12
  import { Llm } from '@jaypie/llm';
13
+ import { spawn } from 'node:child_process';
14
+ import * as os from 'node:os';
13
15
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
14
16
  import { randomUUID } from 'node:crypto';
15
17
 
16
18
  /**
17
19
  * Datadog API integration module
18
20
  */
19
- const nullLogger = {
21
+ const nullLogger$1 = {
20
22
  info: () => { },
21
23
  error: () => { },
22
24
  };
@@ -69,7 +71,7 @@ function buildDatadogQuery(options) {
69
71
  /**
70
72
  * Search Datadog logs
71
73
  */
72
- async function searchDatadogLogs(credentials, options = {}, logger = nullLogger) {
74
+ async function searchDatadogLogs(credentials, options = {}, logger = nullLogger$1) {
73
75
  const effectiveQuery = buildDatadogQuery(options);
74
76
  const effectiveFrom = options.from || "now-15m";
75
77
  const effectiveTo = options.to || "now";
@@ -182,7 +184,7 @@ async function searchDatadogLogs(credentials, options = {}, logger = nullLogger)
182
184
  * Aggregate Datadog logs using the Analytics API
183
185
  * Groups logs by specified fields and computes aggregations
184
186
  */
185
- async function aggregateDatadogLogs(credentials, options, logger = nullLogger) {
187
+ async function aggregateDatadogLogs(credentials, options, logger = nullLogger$1) {
186
188
  const effectiveQuery = buildDatadogQuery(options);
187
189
  const effectiveFrom = options.from || "now-15m";
188
190
  const effectiveTo = options.to || "now";
@@ -317,7 +319,7 @@ async function aggregateDatadogLogs(credentials, options, logger = nullLogger) {
317
319
  /**
318
320
  * List Datadog monitors with optional filtering
319
321
  */
320
- async function listDatadogMonitors(credentials, options = {}, logger = nullLogger) {
322
+ async function listDatadogMonitors(credentials, options = {}, logger = nullLogger$1) {
321
323
  logger.info("Fetching Datadog monitors");
322
324
  const queryParams = new URLSearchParams();
323
325
  if (options.tags && options.tags.length > 0) {
@@ -415,7 +417,7 @@ async function listDatadogMonitors(credentials, options = {}, logger = nullLogge
415
417
  /**
416
418
  * List Datadog Synthetic tests
417
419
  */
418
- async function listDatadogSynthetics(credentials, options = {}, logger = nullLogger) {
420
+ async function listDatadogSynthetics(credentials, options = {}, logger = nullLogger$1) {
419
421
  logger.info("Fetching Datadog Synthetic tests");
420
422
  return new Promise((resolve) => {
421
423
  const requestOptions = {
@@ -502,7 +504,7 @@ async function listDatadogSynthetics(credentials, options = {}, logger = nullLog
502
504
  /**
503
505
  * Get recent results for a specific Synthetic test
504
506
  */
505
- async function getDatadogSyntheticResults(credentials, publicId, logger = nullLogger) {
507
+ async function getDatadogSyntheticResults(credentials, publicId, logger = nullLogger$1) {
506
508
  logger.info(`Fetching results for Synthetic test: ${publicId}`);
507
509
  return new Promise((resolve) => {
508
510
  const requestOptions = {
@@ -587,7 +589,7 @@ async function getDatadogSyntheticResults(credentials, publicId, logger = nullLo
587
589
  /**
588
590
  * Query Datadog metrics
589
591
  */
590
- async function queryDatadogMetrics(credentials, options, logger = nullLogger) {
592
+ async function queryDatadogMetrics(credentials, options, logger = nullLogger$1) {
591
593
  logger.info(`Querying metrics: ${options.query}`);
592
594
  logger.info(`Time range: ${options.from} to ${options.to}`);
593
595
  const queryParams = new URLSearchParams({
@@ -680,7 +682,7 @@ async function queryDatadogMetrics(credentials, options, logger = nullLogger) {
680
682
  /**
681
683
  * Search Datadog RUM events
682
684
  */
683
- async function searchDatadogRum(credentials, options = {}, logger = nullLogger) {
685
+ async function searchDatadogRum(credentials, options = {}, logger = nullLogger$1) {
684
686
  const effectiveQuery = options.query || "*";
685
687
  const effectiveFrom = options.from || "now-15m";
686
688
  const effectiveTo = options.to || "now";
@@ -885,7 +887,328 @@ function listLlmProviders() {
885
887
  };
886
888
  }
887
889
 
888
- const BUILD_VERSION_STRING = "@jaypie/mcp@0.2.12#512ddebf"
890
+ /**
891
+ * AWS CLI integration module
892
+ * Provides a structured interface for common AWS operations via the AWS CLI
893
+ */
894
+ const nullLogger = {
895
+ info: () => { },
896
+ error: () => { },
897
+ };
898
+ /**
899
+ * Parse AWS CLI error messages into user-friendly descriptions
900
+ */
901
+ function parseAwsError(stderr, service, command) {
902
+ if (stderr.includes("ExpiredToken") || stderr.includes("Token has expired")) {
903
+ return "AWS credentials have expired. Run 'aws sso login' or refresh your credentials.";
904
+ }
905
+ if (stderr.includes("NoCredentialProviders") ||
906
+ stderr.includes("Unable to locate credentials")) {
907
+ return "No AWS credentials found. Configure credentials with 'aws configure' or 'aws sso login'.";
908
+ }
909
+ if (stderr.includes("AccessDenied") || stderr.includes("Access Denied")) {
910
+ return `Access denied for ${service}:${command}. Check your IAM permissions.`;
911
+ }
912
+ if (stderr.includes("ResourceNotFoundException")) {
913
+ return `Resource not found. Check that the specified resource exists in the correct region.`;
914
+ }
915
+ if (stderr.includes("ValidationException")) {
916
+ const match = stderr.match(/ValidationException[^:]*:\s*(.+)/);
917
+ return match
918
+ ? `Validation error: ${match[1].trim()}`
919
+ : "Validation error in request parameters.";
920
+ }
921
+ if (stderr.includes("ThrottlingException") ||
922
+ stderr.includes("Rate exceeded")) {
923
+ return "AWS API rate limit exceeded. Wait a moment and try again.";
924
+ }
925
+ if (stderr.includes("InvalidParameterValue")) {
926
+ const match = stderr.match(/InvalidParameterValue[^:]*:\s*(.+)/);
927
+ return match
928
+ ? `Invalid parameter: ${match[1].trim()}`
929
+ : "Invalid parameter value provided.";
930
+ }
931
+ return stderr.trim();
932
+ }
933
+ /**
934
+ * Parse relative time strings like 'now-1h' to Unix timestamps
935
+ */
936
+ function parseRelativeTime(timeStr) {
937
+ const now = Date.now();
938
+ if (timeStr === "now") {
939
+ return now;
940
+ }
941
+ // Handle relative time like 'now-15m', 'now-1h', 'now-1d'
942
+ const relativeMatch = timeStr.match(/^now-(\d+)([smhd])$/);
943
+ if (relativeMatch) {
944
+ const value = parseInt(relativeMatch[1], 10);
945
+ const unit = relativeMatch[2];
946
+ const multipliers = {
947
+ s: 1000,
948
+ m: 60 * 1000,
949
+ h: 60 * 60 * 1000,
950
+ d: 24 * 60 * 60 * 1000,
951
+ };
952
+ return now - value * multipliers[unit];
953
+ }
954
+ // Handle ISO 8601 format
955
+ const parsed = Date.parse(timeStr);
956
+ if (!isNaN(parsed)) {
957
+ return parsed;
958
+ }
959
+ // Default to the current time if parsing fails
960
+ return now;
961
+ }
962
+ /**
963
+ * Execute an AWS CLI command and return parsed JSON output
964
+ */
965
+ async function executeAwsCommand(service, command, args, options = {}, logger = nullLogger) {
966
+ const fullArgs = [service, command, ...args, "--output", "json"];
967
+ if (options.profile) {
968
+ fullArgs.push("--profile", options.profile);
969
+ }
970
+ if (options.region) {
971
+ fullArgs.push("--region", options.region);
972
+ }
973
+ logger.info(`Executing: aws ${fullArgs.join(" ")}`);
974
+ return new Promise((resolve) => {
975
+ const proc = spawn("aws", fullArgs);
976
+ let stdout = "";
977
+ let stderr = "";
978
+ proc.stdout.on("data", (data) => {
979
+ stdout += data.toString();
980
+ });
981
+ proc.stderr.on("data", (data) => {
982
+ stderr += data.toString();
983
+ });
984
+ proc.on("close", (code) => {
985
+ if (code !== 0) {
986
+ logger.error(`AWS CLI error: ${stderr}`);
987
+ resolve({
988
+ success: false,
989
+ error: parseAwsError(stderr, service, command),
990
+ });
991
+ return;
992
+ }
993
+ // Handle empty output (some commands return nothing on success)
994
+ if (!stdout.trim()) {
995
+ resolve({ success: true });
996
+ return;
997
+ }
998
+ try {
999
+ const data = JSON.parse(stdout);
1000
+ resolve({ success: true, data });
1001
+ }
1002
+ catch {
1003
+ // Some commands return plain text
1004
+ resolve({ success: true, data: stdout.trim() });
1005
+ }
1006
+ });
1007
+ proc.on("error", (error) => {
1008
+ if (error.message.includes("ENOENT")) {
1009
+ resolve({
1010
+ success: false,
1011
+ error: "AWS CLI not found. Install it from https://aws.amazon.com/cli/",
1012
+ });
1013
+ }
1014
+ else {
1015
+ resolve({ success: false, error: error.message });
1016
+ }
1017
+ });
1018
+ });
1019
+ }
1020
+ /**
1021
+ * List available AWS profiles from ~/.aws/config and ~/.aws/credentials
1022
+ */
1023
+ async function listAwsProfiles(logger = nullLogger) {
1024
+ const profiles = [];
1025
+ const homeDir = os.homedir();
1026
+ try {
1027
+ // Parse ~/.aws/config
1028
+ const configPath = path.join(homeDir, ".aws", "config");
1029
+ try {
1030
+ const configContent = await fs.readFile(configPath, "utf-8");
1031
+ const profileRegex = /\[profile\s+([^\]]+)\]|\[default\]/g;
1032
+ let match;
1033
+ while ((match = profileRegex.exec(configContent)) !== null) {
1034
+ const name = match[1] || "default";
1035
+ profiles.push({
1036
+ name,
1037
+ source: "config",
1038
+ });
1039
+ }
1040
+ logger.info(`Found ${profiles.length} profiles in config`);
1041
+ }
1042
+ catch {
1043
+ logger.info("No ~/.aws/config file found");
1044
+ }
1045
+ // Parse ~/.aws/credentials
1046
+ const credentialsPath = path.join(homeDir, ".aws", "credentials");
1047
+ try {
1048
+ const credentialsContent = await fs.readFile(credentialsPath, "utf-8");
1049
+ const profileRegex = /\[([^\]]+)\]/g;
1050
+ let match;
1051
+ while ((match = profileRegex.exec(credentialsContent)) !== null) {
1052
+ const name = match[1];
1053
+ // Only add if not already in the list
1054
+ if (!profiles.find((p) => p.name === name)) {
1055
+ profiles.push({
1056
+ name,
1057
+ source: "credentials",
1058
+ });
1059
+ }
1060
+ }
1061
+ logger.info(`Total profiles after credentials: ${profiles.length}`);
1062
+ }
1063
+ catch {
1064
+ logger.info("No ~/.aws/credentials file found");
1065
+ }
1066
+ return { success: true, data: profiles };
1067
+ }
1068
+ catch (error) {
1069
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
1070
+ logger.error(`Error listing profiles: ${errorMessage}`);
1071
+ return { success: false, error: errorMessage };
1072
+ }
1073
+ }
1074
+ // Step Functions operations
1075
+ async function listStepFunctionExecutions(options, logger = nullLogger) {
1076
+ const args = ["--state-machine-arn", options.stateMachineArn];
1077
+ if (options.statusFilter) {
1078
+ args.push("--status-filter", options.statusFilter);
1079
+ }
1080
+ if (options.maxResults) {
1081
+ args.push("--max-results", String(options.maxResults));
1082
+ }
1083
+ return executeAwsCommand("stepfunctions", "list-executions", args, { profile: options.profile, region: options.region }, logger);
1084
+ }
1085
+ async function stopStepFunctionExecution(options, logger = nullLogger) {
1086
+ const args = ["--execution-arn", options.executionArn];
1087
+ if (options.cause) {
1088
+ args.push("--cause", options.cause);
1089
+ }
1090
+ return executeAwsCommand("stepfunctions", "stop-execution", args, { profile: options.profile, region: options.region }, logger);
1091
+ }
1092
+ // Lambda operations
1093
+ async function listLambdaFunctions(options = {}, logger = nullLogger) {
1094
+ const args = [];
1095
+ if (options.maxResults) {
1096
+ args.push("--max-items", String(options.maxResults));
1097
+ }
1098
+ const result = await executeAwsCommand("lambda", "list-functions", args, { profile: options.profile, region: options.region }, logger);
1099
+ // Filter by prefix if specified
1100
+ if (result.success && result.data && options.functionNamePrefix) {
1101
+ result.data.Functions = result.data.Functions.filter((f) => f.FunctionName.startsWith(options.functionNamePrefix));
1102
+ }
1103
+ return result;
1104
+ }
1105
+ async function getLambdaFunction(options, logger = nullLogger) {
1106
+ return executeAwsCommand("lambda", "get-function", ["--function-name", options.functionName], { profile: options.profile, region: options.region }, logger);
1107
+ }
1108
+ // CloudWatch Logs operations
1109
+ async function filterLogEvents(options, logger = nullLogger) {
1110
+ const args = ["--log-group-name", options.logGroupName];
1111
+ if (options.filterPattern) {
1112
+ args.push("--filter-pattern", options.filterPattern);
1113
+ }
1114
+ if (options.startTime) {
1115
+ const startMs = parseRelativeTime(options.startTime);
1116
+ args.push("--start-time", String(startMs));
1117
+ }
1118
+ if (options.endTime) {
1119
+ const endMs = parseRelativeTime(options.endTime);
1120
+ args.push("--end-time", String(endMs));
1121
+ }
1122
+ {
1123
+ args.push("--limit", String(options.limit));
1124
+ }
1125
+ return executeAwsCommand("logs", "filter-log-events", args, { profile: options.profile, region: options.region }, logger);
1126
+ }
1127
+ // S3 operations
1128
+ async function listS3Objects(options, logger = nullLogger) {
1129
+ const args = ["--bucket", options.bucket];
1130
+ if (options.prefix) {
1131
+ args.push("--prefix", options.prefix);
1132
+ }
1133
+ if (options.maxResults) {
1134
+ args.push("--max-items", String(options.maxResults));
1135
+ }
1136
+ return executeAwsCommand("s3api", "list-objects-v2", args, { profile: options.profile, region: options.region }, logger);
1137
+ }
1138
+ // CloudFormation operations
1139
+ async function describeStack(options, logger = nullLogger) {
1140
+ return executeAwsCommand("cloudformation", "describe-stacks", ["--stack-name", options.stackName], { profile: options.profile, region: options.region }, logger);
1141
+ }
1142
+ // DynamoDB operations
1143
+ async function describeDynamoDBTable(options, logger = nullLogger) {
1144
+ return executeAwsCommand("dynamodb", "describe-table", ["--table-name", options.tableName], { profile: options.profile, region: options.region }, logger);
1145
+ }
1146
+ async function scanDynamoDB(options, logger = nullLogger) {
1147
+ const args = ["--table-name", options.tableName];
1148
+ if (options.filterExpression) {
1149
+ args.push("--filter-expression", options.filterExpression);
1150
+ }
1151
+ if (options.expressionAttributeValues) {
1152
+ args.push("--expression-attribute-values", options.expressionAttributeValues);
1153
+ }
1154
+ {
1155
+ args.push("--limit", String(options.limit));
1156
+ }
1157
+ return executeAwsCommand("dynamodb", "scan", args, { profile: options.profile, region: options.region }, logger);
1158
+ }
1159
+ async function queryDynamoDB(options, logger = nullLogger) {
1160
+ const args = [
1161
+ "--table-name",
1162
+ options.tableName,
1163
+ "--key-condition-expression",
1164
+ options.keyConditionExpression,
1165
+ "--expression-attribute-values",
1166
+ options.expressionAttributeValues,
1167
+ ];
1168
+ if (options.indexName) {
1169
+ args.push("--index-name", options.indexName);
1170
+ }
1171
+ if (options.filterExpression) {
1172
+ args.push("--filter-expression", options.filterExpression);
1173
+ }
1174
+ if (options.limit) {
1175
+ args.push("--limit", String(options.limit));
1176
+ }
1177
+ if (options.scanIndexForward === false) {
1178
+ args.push("--no-scan-index-forward");
1179
+ }
1180
+ return executeAwsCommand("dynamodb", "query", args, { profile: options.profile, region: options.region }, logger);
1181
+ }
1182
+ async function getDynamoDBItem(options, logger = nullLogger) {
1183
+ return executeAwsCommand("dynamodb", "get-item", ["--table-name", options.tableName, "--key", options.key], { profile: options.profile, region: options.region }, logger);
1184
+ }
1185
+ // SQS operations
1186
+ async function listSQSQueues(options = {}, logger = nullLogger) {
1187
+ const args = [];
1188
+ if (options.queueNamePrefix) {
1189
+ args.push("--queue-name-prefix", options.queueNamePrefix);
1190
+ }
1191
+ return executeAwsCommand("sqs", "list-queues", args, { profile: options.profile, region: options.region }, logger);
1192
+ }
1193
+ async function getSQSQueueAttributes(options, logger = nullLogger) {
1194
+ return executeAwsCommand("sqs", "get-queue-attributes", ["--queue-url", options.queueUrl, "--attribute-names", "All"], { profile: options.profile, region: options.region }, logger);
1195
+ }
1196
+ async function receiveSQSMessage(options, logger = nullLogger) {
1197
+ const args = ["--queue-url", options.queueUrl];
1198
+ {
1199
+ args.push("--max-number-of-messages", String(options.maxNumberOfMessages));
1200
+ }
1201
+ {
1202
+ args.push("--visibility-timeout", String(options.visibilityTimeout));
1203
+ }
1204
+ args.push("--attribute-names", "All");
1205
+ return executeAwsCommand("sqs", "receive-message", args, { profile: options.profile, region: options.region }, logger);
1206
+ }
1207
+ async function purgeSQSQueue(options, logger = nullLogger) {
1208
+ return executeAwsCommand("sqs", "purge-queue", ["--queue-url", options.queueUrl], { profile: options.profile, region: options.region }, logger);
1209
+ }
1210
+
1211
+ const BUILD_VERSION_STRING = "@jaypie/mcp@0.3.1#4e71c6ce"
889
1212
  ;
890
1213
  const __filename$1 = fileURLToPath(import.meta.url);
891
1214
  const __dirname$1 = path.dirname(__filename$1);
@@ -1780,6 +2103,853 @@ function createMcpServer(options = {}) {
1780
2103
  };
1781
2104
  });
1782
2105
  log.info("Registered tool: llm_list_providers");
2106
+ // AWS CLI Tools
2107
+ // AWS List Profiles
2108
+ server.tool("aws_list_profiles", "List available AWS profiles from ~/.aws/config and credentials.", {}, async () => {
2109
+ log.info("Tool called: aws_list_profiles");
2110
+ const result = await listAwsProfiles(log);
2111
+ if (!result.success) {
2112
+ return {
2113
+ content: [
2114
+ {
2115
+ type: "text",
2116
+ text: `Error listing profiles: ${result.error}`,
2117
+ },
2118
+ ],
2119
+ };
2120
+ }
2121
+ const profiles = result.data || [];
2122
+ if (profiles.length === 0) {
2123
+ return {
2124
+ content: [
2125
+ {
2126
+ type: "text",
2127
+ text: "No AWS profiles found. Configure profiles in ~/.aws/config or ~/.aws/credentials.",
2128
+ },
2129
+ ],
2130
+ };
2131
+ }
2132
+ const formatted = profiles
2133
+ .map((p) => `- ${p.name} (${p.source})`)
2134
+ .join("\n");
2135
+ return {
2136
+ content: [
2137
+ {
2138
+ type: "text",
2139
+ text: [
2140
+ `Found ${profiles.length} AWS profiles:`,
2141
+ "",
2142
+ formatted,
2143
+ "",
2144
+ "Use the 'profile' parameter in other AWS tools to specify which profile to use.",
2145
+ ].join("\n"),
2146
+ },
2147
+ ],
2148
+ };
2149
+ });
2150
+ log.info("Registered tool: aws_list_profiles");
2151
+ // Step Functions: List Executions
2152
+ server.tool("aws_stepfunctions_list_executions", "List Step Function executions for a state machine. Useful for finding stuck or running executions.", {
2153
+ stateMachineArn: z.string().describe("ARN of the state machine"),
2154
+ statusFilter: z
2155
+ .enum([
2156
+ "RUNNING",
2157
+ "SUCCEEDED",
2158
+ "FAILED",
2159
+ "TIMED_OUT",
2160
+ "ABORTED",
2161
+ "PENDING_REDRIVE",
2162
+ ])
2163
+ .optional()
2164
+ .describe("Filter by execution status"),
2165
+ profile: z.string().optional().describe("AWS profile to use"),
2166
+ region: z.string().optional().describe("AWS region"),
2167
+ maxResults: z
2168
+ .number()
2169
+ .optional()
2170
+ .describe("Max results (1-1000, default 100)"),
2171
+ }, async ({ stateMachineArn, statusFilter, profile, region, maxResults }) => {
2172
+ log.info("Tool called: aws_stepfunctions_list_executions");
2173
+ const result = await listStepFunctionExecutions({
2174
+ stateMachineArn,
2175
+ statusFilter,
2176
+ profile,
2177
+ region,
2178
+ maxResults,
2179
+ }, log);
2180
+ if (!result.success) {
2181
+ return {
2182
+ content: [
2183
+ {
2184
+ type: "text",
2185
+ text: `Error: ${result.error}`,
2186
+ },
2187
+ ],
2188
+ };
2189
+ }
2190
+ const executions = result.data?.executions || [];
2191
+ if (executions.length === 0) {
2192
+ return {
2193
+ content: [
2194
+ {
2195
+ type: "text",
2196
+ text: `No ${statusFilter || ""} executions found for state machine.`,
2197
+ },
2198
+ ],
2199
+ };
2200
+ }
2201
+ const formatted = executions
2202
+ .map((e) => `- ${e.name} (${e.status}) started ${e.startDate}`)
2203
+ .join("\n");
2204
+ return {
2205
+ content: [
2206
+ {
2207
+ type: "text",
2208
+ text: [
2209
+ `Found ${executions.length} executions:`,
2210
+ "",
2211
+ formatted,
2212
+ "",
2213
+ "Use aws_stepfunctions_stop_execution to stop running executions.",
2214
+ ].join("\n"),
2215
+ },
2216
+ ],
2217
+ };
2218
+ });
2219
+ log.info("Registered tool: aws_stepfunctions_list_executions");
2220
+ // Step Functions: Stop Execution
2221
+ server.tool("aws_stepfunctions_stop_execution", "Stop a running Step Function execution. Use with caution - this will abort the workflow.", {
2222
+ executionArn: z.string().describe("ARN of the execution to stop"),
2223
+ cause: z
2224
+ .string()
2225
+ .optional()
2226
+ .describe("Description of why the execution was stopped"),
2227
+ profile: z.string().optional().describe("AWS profile to use"),
2228
+ region: z.string().optional().describe("AWS region"),
2229
+ }, async ({ executionArn, cause, profile, region }) => {
2230
+ log.info("Tool called: aws_stepfunctions_stop_execution");
2231
+ const result = await stopStepFunctionExecution({
2232
+ executionArn,
2233
+ cause,
2234
+ profile,
2235
+ region,
2236
+ }, log);
2237
+ if (!result.success) {
2238
+ return {
2239
+ content: [
2240
+ {
2241
+ type: "text",
2242
+ text: `Error stopping execution: ${result.error}`,
2243
+ },
2244
+ ],
2245
+ };
2246
+ }
2247
+ return {
2248
+ content: [
2249
+ {
2250
+ type: "text",
2251
+ text: `Execution stopped successfully at ${result.data?.stopDate || "unknown time"}.`,
2252
+ },
2253
+ ],
2254
+ };
2255
+ });
2256
+ log.info("Registered tool: aws_stepfunctions_stop_execution");
2257
+ // Lambda: List Functions
2258
+ server.tool("aws_lambda_list_functions", "List Lambda functions in the account. Filter by function name prefix.", {
2259
+ functionNamePrefix: z
2260
+ .string()
2261
+ .optional()
2262
+ .describe("Filter by function name prefix"),
2263
+ profile: z.string().optional().describe("AWS profile to use"),
2264
+ region: z.string().optional().describe("AWS region"),
2265
+ maxResults: z.number().optional().describe("Max results to return"),
2266
+ }, async ({ functionNamePrefix, profile, region, maxResults }) => {
2267
+ log.info("Tool called: aws_lambda_list_functions");
2268
+ const result = await listLambdaFunctions({
2269
+ functionNamePrefix,
2270
+ profile,
2271
+ region,
2272
+ maxResults,
2273
+ }, log);
2274
+ if (!result.success) {
2275
+ return {
2276
+ content: [
2277
+ {
2278
+ type: "text",
2279
+ text: `Error: ${result.error}`,
2280
+ },
2281
+ ],
2282
+ };
2283
+ }
2284
+ const functions = result.data?.Functions || [];
2285
+ if (functions.length === 0) {
2286
+ return {
2287
+ content: [
2288
+ {
2289
+ type: "text",
2290
+ text: functionNamePrefix
2291
+ ? `No functions found with prefix "${functionNamePrefix}".`
2292
+ : "No Lambda functions found in this account/region.",
2293
+ },
2294
+ ],
2295
+ };
2296
+ }
2297
+ const formatted = functions
2298
+ .map((f) => `- ${f.FunctionName} (${f.Runtime || "unknown runtime"}, ${f.MemorySize}MB)`)
2299
+ .join("\n");
2300
+ return {
2301
+ content: [
2302
+ {
2303
+ type: "text",
2304
+ text: [
2305
+ `Found ${functions.length} Lambda functions:`,
2306
+ "",
2307
+ formatted,
2308
+ "",
2309
+ "Use aws_lambda_get_function for details on a specific function.",
2310
+ ].join("\n"),
2311
+ },
2312
+ ],
2313
+ };
2314
+ });
2315
+ log.info("Registered tool: aws_lambda_list_functions");
2316
+ // Lambda: Get Function
2317
+ server.tool("aws_lambda_get_function", "Get configuration and details for a specific Lambda function.", {
2318
+ functionName: z.string().describe("Function name or ARN"),
2319
+ profile: z.string().optional().describe("AWS profile to use"),
2320
+ region: z.string().optional().describe("AWS region"),
2321
+ }, async ({ functionName, profile, region }) => {
2322
+ log.info("Tool called: aws_lambda_get_function");
2323
+ const result = await getLambdaFunction({
2324
+ functionName,
2325
+ profile,
2326
+ region,
2327
+ }, log);
2328
+ if (!result.success) {
2329
+ return {
2330
+ content: [
2331
+ {
2332
+ type: "text",
2333
+ text: `Error: ${result.error}`,
2334
+ },
2335
+ ],
2336
+ };
2337
+ }
2338
+ return {
2339
+ content: [
2340
+ {
2341
+ type: "text",
2342
+ text: JSON.stringify(result.data, null, 2),
2343
+ },
2344
+ ],
2345
+ };
2346
+ });
2347
+ log.info("Registered tool: aws_lambda_get_function");
2348
+ // CloudWatch Logs: Filter Log Events
2349
+ server.tool("aws_logs_filter_log_events", "Search CloudWatch Logs for a log group. Filter by pattern and time range.", {
2350
+ logGroupName: z
2351
+ .string()
2352
+ .describe("Log group name (e.g., /aws/lambda/my-function)"),
2353
+ filterPattern: z
2354
+ .string()
2355
+ .optional()
2356
+ .describe("CloudWatch filter pattern (e.g., 'ERROR', '{ $.level = \"error\" }')"),
2357
+ startTime: z
2358
+ .string()
2359
+ .optional()
2360
+ .describe("Start time (ISO 8601 or relative like 'now-1h'). Defaults to 'now-15m'."),
2361
+ endTime: z
2362
+ .string()
2363
+ .optional()
2364
+ .describe("End time (ISO 8601 or 'now'). Defaults to 'now'."),
2365
+ profile: z.string().optional().describe("AWS profile to use"),
2366
+ region: z.string().optional().describe("AWS region"),
2367
+ limit: z
2368
+ .number()
2369
+ .optional()
2370
+ .describe("Max events to return (default 100)"),
2371
+ }, async ({ logGroupName, filterPattern, startTime, endTime, profile, region, limit, }) => {
2372
+ log.info("Tool called: aws_logs_filter_log_events");
2373
+ const result = await filterLogEvents({
2374
+ logGroupName,
2375
+ filterPattern,
2376
+ startTime: startTime || "now-15m",
2377
+ endTime: endTime || "now",
2378
+ limit: limit || 100,
2379
+ profile,
2380
+ region,
2381
+ }, log);
2382
+ if (!result.success) {
2383
+ return {
2384
+ content: [
2385
+ {
2386
+ type: "text",
2387
+ text: `Error: ${result.error}`,
2388
+ },
2389
+ ],
2390
+ };
2391
+ }
2392
+ const events = result.data?.events || [];
2393
+ if (events.length === 0) {
2394
+ return {
2395
+ content: [
2396
+ {
2397
+ type: "text",
2398
+ text: `No log events found matching the filter in ${logGroupName}.`,
2399
+ },
2400
+ ],
2401
+ };
2402
+ }
2403
+ const formatted = events
2404
+ .map((e) => {
2405
+ const timestamp = new Date(e.timestamp).toISOString();
2406
+ return `[${timestamp}] ${e.message}`;
2407
+ })
2408
+ .join("\n");
2409
+ return {
2410
+ content: [
2411
+ {
2412
+ type: "text",
2413
+ text: [`Found ${events.length} log events:`, "", formatted].join("\n"),
2414
+ },
2415
+ ],
2416
+ };
2417
+ });
2418
+ log.info("Registered tool: aws_logs_filter_log_events");
2419
+ // S3: List Objects
2420
+ server.tool("aws_s3_list_objects", "List objects in an S3 bucket with optional prefix filtering.", {
2421
+ bucket: z.string().describe("S3 bucket name"),
2422
+ prefix: z.string().optional().describe("Object key prefix filter"),
2423
+ profile: z.string().optional().describe("AWS profile to use"),
2424
+ region: z.string().optional().describe("AWS region"),
2425
+ maxResults: z.number().optional().describe("Max results to return"),
2426
+ }, async ({ bucket, prefix, profile, region, maxResults }) => {
2427
+ log.info("Tool called: aws_s3_list_objects");
2428
+ const result = await listS3Objects({
2429
+ bucket,
2430
+ prefix,
2431
+ profile,
2432
+ region,
2433
+ maxResults,
2434
+ }, log);
2435
+ if (!result.success) {
2436
+ return {
2437
+ content: [
2438
+ {
2439
+ type: "text",
2440
+ text: `Error: ${result.error}`,
2441
+ },
2442
+ ],
2443
+ };
2444
+ }
2445
+ const objects = result.data?.Contents || [];
2446
+ if (objects.length === 0) {
2447
+ return {
2448
+ content: [
2449
+ {
2450
+ type: "text",
2451
+ text: prefix
2452
+ ? `No objects found with prefix "${prefix}" in bucket ${bucket}.`
2453
+ : `Bucket ${bucket} is empty.`,
2454
+ },
2455
+ ],
2456
+ };
2457
+ }
2458
+ const formatted = objects
2459
+ .map((o) => {
2460
+ const size = o.Size < 1024
2461
+ ? `${o.Size}B`
2462
+ : o.Size < 1024 * 1024
2463
+ ? `${(o.Size / 1024).toFixed(1)}KB`
2464
+ : `${(o.Size / (1024 * 1024)).toFixed(1)}MB`;
2465
+ return `- ${o.Key} (${size}, ${o.LastModified})`;
2466
+ })
2467
+ .join("\n");
2468
+ return {
2469
+ content: [
2470
+ {
2471
+ type: "text",
2472
+ text: [
2473
+ `Found ${objects.length} objects in ${bucket}:`,
2474
+ "",
2475
+ formatted,
2476
+ ].join("\n"),
2477
+ },
2478
+ ],
2479
+ };
2480
+ });
2481
+ log.info("Registered tool: aws_s3_list_objects");
2482
+ // CloudFormation: Describe Stack
2483
+ server.tool("aws_cloudformation_describe_stack", "Get details and status of a CloudFormation stack.", {
2484
+ stackName: z.string().describe("Stack name or ARN"),
2485
+ profile: z.string().optional().describe("AWS profile to use"),
2486
+ region: z.string().optional().describe("AWS region"),
2487
+ }, async ({ stackName, profile, region }) => {
2488
+ log.info("Tool called: aws_cloudformation_describe_stack");
2489
+ const result = await describeStack({
2490
+ stackName,
2491
+ profile,
2492
+ region,
2493
+ }, log);
2494
+ if (!result.success) {
2495
+ return {
2496
+ content: [
2497
+ {
2498
+ type: "text",
2499
+ text: `Error: ${result.error}`,
2500
+ },
2501
+ ],
2502
+ };
2503
+ }
2504
+ const stack = result.data?.Stacks?.[0];
2505
+ if (!stack) {
2506
+ return {
2507
+ content: [
2508
+ {
2509
+ type: "text",
2510
+ text: `Stack "${stackName}" not found.`,
2511
+ },
2512
+ ],
2513
+ };
2514
+ }
2515
+ const outputs = stack.Outputs?.map((o) => ` - ${o.OutputKey}: ${o.OutputValue}`).join("\n");
2516
+ const params = stack.Parameters?.map((p) => ` - ${p.ParameterKey}: ${p.ParameterValue}`).join("\n");
2517
+ return {
2518
+ content: [
2519
+ {
2520
+ type: "text",
2521
+ text: [
2522
+ `Stack: ${stack.StackName}`,
2523
+ `Status: ${stack.StackStatus}`,
2524
+ stack.StackStatusReason
2525
+ ? `Reason: ${stack.StackStatusReason}`
2526
+ : null,
2527
+ `Created: ${stack.CreationTime}`,
2528
+ stack.LastUpdatedTime
2529
+ ? `Last Updated: ${stack.LastUpdatedTime}`
2530
+ : null,
2531
+ stack.Description ? `Description: ${stack.Description}` : null,
2532
+ "",
2533
+ outputs ? `Outputs:\n${outputs}` : null,
2534
+ params ? `Parameters:\n${params}` : null,
2535
+ ]
2536
+ .filter(Boolean)
2537
+ .join("\n"),
2538
+ },
2539
+ ],
2540
+ };
2541
+ });
2542
+ log.info("Registered tool: aws_cloudformation_describe_stack");
2543
+ // DynamoDB: Describe Table
2544
+ server.tool("aws_dynamodb_describe_table", "Get metadata about a DynamoDB table including key schema, indexes, and provisioned capacity.", {
2545
+ tableName: z.string().describe("DynamoDB table name"),
2546
+ profile: z.string().optional().describe("AWS profile to use"),
2547
+ region: z.string().optional().describe("AWS region"),
2548
+ }, async ({ tableName, profile, region }) => {
2549
+ log.info("Tool called: aws_dynamodb_describe_table");
2550
+ const result = await describeDynamoDBTable({
2551
+ tableName,
2552
+ profile,
2553
+ region,
2554
+ }, log);
2555
+ if (!result.success) {
2556
+ return {
2557
+ content: [
2558
+ {
2559
+ type: "text",
2560
+ text: `Error: ${result.error}`,
2561
+ },
2562
+ ],
2563
+ };
2564
+ }
2565
+ return {
2566
+ content: [
2567
+ {
2568
+ type: "text",
2569
+ text: JSON.stringify(result.data?.Table, null, 2),
2570
+ },
2571
+ ],
2572
+ };
2573
+ });
2574
+ log.info("Registered tool: aws_dynamodb_describe_table");
2575
+ // DynamoDB: Scan
2576
+ server.tool("aws_dynamodb_scan", "Scan a DynamoDB table. Use sparingly on large tables - prefer query when possible.", {
2577
+ tableName: z.string().describe("DynamoDB table name"),
2578
+ filterExpression: z
2579
+ .string()
2580
+ .optional()
2581
+ .describe("Filter expression (e.g., 'status = :s')"),
2582
+ expressionAttributeValues: z
2583
+ .string()
2584
+ .optional()
2585
+ .describe('JSON object of attribute values (e.g., \'{\\":s\\":{\\"S\\":\\"active\\"}}\')'),
2586
+ limit: z.number().optional().describe("Max items to return (default 25)"),
2587
+ profile: z.string().optional().describe("AWS profile to use"),
2588
+ region: z.string().optional().describe("AWS region"),
2589
+ }, async ({ tableName, filterExpression, expressionAttributeValues, limit, profile, region, }) => {
2590
+ log.info("Tool called: aws_dynamodb_scan");
2591
+ const result = await scanDynamoDB({
2592
+ tableName,
2593
+ filterExpression,
2594
+ expressionAttributeValues,
2595
+ limit: limit || 25,
2596
+ profile,
2597
+ region,
2598
+ }, log);
2599
+ if (!result.success) {
2600
+ return {
2601
+ content: [
2602
+ {
2603
+ type: "text",
2604
+ text: `Error: ${result.error}`,
2605
+ },
2606
+ ],
2607
+ };
2608
+ }
2609
+ const items = result.data?.Items || [];
2610
+ if (items.length === 0) {
2611
+ return {
2612
+ content: [
2613
+ {
2614
+ type: "text",
2615
+ text: `No items found in table ${tableName}.`,
2616
+ },
2617
+ ],
2618
+ };
2619
+ }
2620
+ return {
2621
+ content: [
2622
+ {
2623
+ type: "text",
2624
+ text: [
2625
+ `Found ${items.length} items:`,
2626
+ "",
2627
+ JSON.stringify(items, null, 2),
2628
+ ].join("\n"),
2629
+ },
2630
+ ],
2631
+ };
2632
+ });
2633
+ log.info("Registered tool: aws_dynamodb_scan");
2634
+ // DynamoDB: Query
2635
+ server.tool("aws_dynamodb_query", "Query a DynamoDB table by partition key. More efficient than scan for targeted lookups.", {
2636
+ tableName: z.string().describe("DynamoDB table name"),
2637
+ keyConditionExpression: z
2638
+ .string()
2639
+ .describe("Key condition (e.g., 'pk = :pk')"),
2640
+ expressionAttributeValues: z
2641
+ .string()
2642
+ .describe("JSON object of attribute values"),
2643
+ indexName: z.string().optional().describe("GSI or LSI name to query"),
2644
+ filterExpression: z
2645
+ .string()
2646
+ .optional()
2647
+ .describe("Additional filter expression"),
2648
+ limit: z.number().optional().describe("Max items to return"),
2649
+ scanIndexForward: z
2650
+ .boolean()
2651
+ .optional()
2652
+ .describe("Sort ascending (true) or descending (false)"),
2653
+ profile: z.string().optional().describe("AWS profile to use"),
2654
+ region: z.string().optional().describe("AWS region"),
2655
+ }, async ({ tableName, keyConditionExpression, expressionAttributeValues, indexName, filterExpression, limit, scanIndexForward, profile, region, }) => {
2656
+ log.info("Tool called: aws_dynamodb_query");
2657
+ const result = await queryDynamoDB({
2658
+ tableName,
2659
+ keyConditionExpression,
2660
+ expressionAttributeValues,
2661
+ indexName,
2662
+ filterExpression,
2663
+ limit,
2664
+ scanIndexForward,
2665
+ profile,
2666
+ region,
2667
+ }, log);
2668
+ if (!result.success) {
2669
+ return {
2670
+ content: [
2671
+ {
2672
+ type: "text",
2673
+ text: `Error: ${result.error}`,
2674
+ },
2675
+ ],
2676
+ };
2677
+ }
2678
+ const items = result.data?.Items || [];
2679
+ if (items.length === 0) {
2680
+ return {
2681
+ content: [
2682
+ {
2683
+ type: "text",
2684
+ text: `No items found matching the query.`,
2685
+ },
2686
+ ],
2687
+ };
2688
+ }
2689
+ return {
2690
+ content: [
2691
+ {
2692
+ type: "text",
2693
+ text: [
2694
+ `Found ${items.length} items:`,
2695
+ "",
2696
+ JSON.stringify(items, null, 2),
2697
+ ].join("\n"),
2698
+ },
2699
+ ],
2700
+ };
2701
+ });
2702
+ log.info("Registered tool: aws_dynamodb_query");
2703
+ // DynamoDB: Get Item
2704
+ server.tool("aws_dynamodb_get_item", "Get a single item from a DynamoDB table by its primary key.", {
2705
+ tableName: z.string().describe("DynamoDB table name"),
2706
+ key: z
2707
+ .string()
2708
+ .describe('JSON object of the primary key (e.g., \'{\\"pk\\":{\\"S\\":\\"user#123\\"},\\"sk\\":{\\"S\\":\\"profile\\"}}\')'),
2709
+ profile: z.string().optional().describe("AWS profile to use"),
2710
+ region: z.string().optional().describe("AWS region"),
2711
+ }, async ({ tableName, key, profile, region }) => {
2712
+ log.info("Tool called: aws_dynamodb_get_item");
2713
+ const result = await getDynamoDBItem({
2714
+ tableName,
2715
+ key,
2716
+ profile,
2717
+ region,
2718
+ }, log);
2719
+ if (!result.success) {
2720
+ return {
2721
+ content: [
2722
+ {
2723
+ type: "text",
2724
+ text: `Error: ${result.error}`,
2725
+ },
2726
+ ],
2727
+ };
2728
+ }
2729
+ if (!result.data?.Item) {
2730
+ return {
2731
+ content: [
2732
+ {
2733
+ type: "text",
2734
+ text: `Item not found with the specified key.`,
2735
+ },
2736
+ ],
2737
+ };
2738
+ }
2739
+ return {
2740
+ content: [
2741
+ {
2742
+ type: "text",
2743
+ text: JSON.stringify(result.data.Item, null, 2),
2744
+ },
2745
+ ],
2746
+ };
2747
+ });
2748
+ log.info("Registered tool: aws_dynamodb_get_item");
2749
+ // SQS: List Queues
2750
+ server.tool("aws_sqs_list_queues", "List SQS queues in the account. Filter by queue name prefix.", {
2751
+ queueNamePrefix: z
2752
+ .string()
2753
+ .optional()
2754
+ .describe("Filter by queue name prefix"),
2755
+ profile: z.string().optional().describe("AWS profile to use"),
2756
+ region: z.string().optional().describe("AWS region"),
2757
+ }, async ({ queueNamePrefix, profile, region }) => {
2758
+ log.info("Tool called: aws_sqs_list_queues");
2759
+ const result = await listSQSQueues({
2760
+ queueNamePrefix,
2761
+ profile,
2762
+ region,
2763
+ }, log);
2764
+ if (!result.success) {
2765
+ return {
2766
+ content: [
2767
+ {
2768
+ type: "text",
2769
+ text: `Error: ${result.error}`,
2770
+ },
2771
+ ],
2772
+ };
2773
+ }
2774
+ const queues = result.data?.QueueUrls || [];
2775
+ if (queues.length === 0) {
2776
+ return {
2777
+ content: [
2778
+ {
2779
+ type: "text",
2780
+ text: queueNamePrefix
2781
+ ? `No queues found with prefix "${queueNamePrefix}".`
2782
+ : "No SQS queues found in this account/region.",
2783
+ },
2784
+ ],
2785
+ };
2786
+ }
2787
+ const formatted = queues.map((url) => `- ${url}`).join("\n");
2788
+ return {
2789
+ content: [
2790
+ {
2791
+ type: "text",
2792
+ text: [
2793
+ `Found ${queues.length} queues:`,
2794
+ "",
2795
+ formatted,
2796
+ "",
2797
+ "Use aws_sqs_get_queue_attributes for details on a specific queue.",
2798
+ ].join("\n"),
2799
+ },
2800
+ ],
2801
+ };
2802
+ });
2803
+ log.info("Registered tool: aws_sqs_list_queues");
2804
+ // SQS: Get Queue Attributes
2805
+ server.tool("aws_sqs_get_queue_attributes", "Get attributes for an SQS queue including approximate message count, visibility timeout, and dead-letter config.", {
2806
+ queueUrl: z.string().describe("SQS queue URL"),
2807
+ profile: z.string().optional().describe("AWS profile to use"),
2808
+ region: z.string().optional().describe("AWS region"),
2809
+ }, async ({ queueUrl, profile, region }) => {
2810
+ log.info("Tool called: aws_sqs_get_queue_attributes");
2811
+ const result = await getSQSQueueAttributes({
2812
+ queueUrl,
2813
+ profile,
2814
+ region,
2815
+ }, log);
2816
+ if (!result.success) {
2817
+ return {
2818
+ content: [
2819
+ {
2820
+ type: "text",
2821
+ text: `Error: ${result.error}`,
2822
+ },
2823
+ ],
2824
+ };
2825
+ }
2826
+ const attrs = result.data?.Attributes;
2827
+ if (!attrs) {
2828
+ return {
2829
+ content: [
2830
+ {
2831
+ type: "text",
2832
+ text: `No attributes found for queue.`,
2833
+ },
2834
+ ],
2835
+ };
2836
+ }
2837
+ const formatted = Object.entries(attrs)
2838
+ .map(([key, value]) => `- ${key}: ${value}`)
2839
+ .join("\n");
2840
+ return {
2841
+ content: [
2842
+ {
2843
+ type: "text",
2844
+ text: [`Queue Attributes:`, "", formatted].join("\n"),
2845
+ },
2846
+ ],
2847
+ };
2848
+ });
2849
+ log.info("Registered tool: aws_sqs_get_queue_attributes");
2850
+ // SQS: Receive Message
2851
+ server.tool("aws_sqs_receive_message", "Receive messages from an SQS queue for inspection. Messages are returned to the queue after visibility timeout.", {
2852
+ queueUrl: z.string().describe("SQS queue URL"),
2853
+ maxNumberOfMessages: z
2854
+ .number()
2855
+ .optional()
2856
+ .describe("Max messages to receive (1-10, default 1)"),
2857
+ visibilityTimeout: z
2858
+ .number()
2859
+ .optional()
2860
+ .describe("Seconds to hide message (default 30)"),
2861
+ profile: z.string().optional().describe("AWS profile to use"),
2862
+ region: z.string().optional().describe("AWS region"),
2863
+ }, async ({ queueUrl, maxNumberOfMessages, visibilityTimeout, profile, region, }) => {
2864
+ log.info("Tool called: aws_sqs_receive_message");
2865
+ const result = await receiveSQSMessage({
2866
+ queueUrl,
2867
+ maxNumberOfMessages: maxNumberOfMessages || 1,
2868
+ visibilityTimeout: visibilityTimeout || 30,
2869
+ profile,
2870
+ region,
2871
+ }, log);
2872
+ if (!result.success) {
2873
+ return {
2874
+ content: [
2875
+ {
2876
+ type: "text",
2877
+ text: `Error: ${result.error}`,
2878
+ },
2879
+ ],
2880
+ };
2881
+ }
2882
+ const messages = result.data?.Messages || [];
2883
+ if (messages.length === 0) {
2884
+ return {
2885
+ content: [
2886
+ {
2887
+ type: "text",
2888
+ text: `No messages available in the queue.`,
2889
+ },
2890
+ ],
2891
+ };
2892
+ }
2893
+ const formatted = messages
2894
+ .map((m, i) => {
2895
+ return [
2896
+ `Message ${i + 1}:`,
2897
+ ` ID: ${m.MessageId}`,
2898
+ ` Body: ${m.Body}`,
2899
+ m.Attributes
2900
+ ? ` Attributes: ${JSON.stringify(m.Attributes)}`
2901
+ : null,
2902
+ ]
2903
+ .filter(Boolean)
2904
+ .join("\n");
2905
+ })
2906
+ .join("\n\n");
2907
+ return {
2908
+ content: [
2909
+ {
2910
+ type: "text",
2911
+ text: [
2912
+ `Received ${messages.length} messages (will be returned to queue after visibility timeout):`,
2913
+ "",
2914
+ formatted,
2915
+ ].join("\n"),
2916
+ },
2917
+ ],
2918
+ };
2919
+ });
2920
+ log.info("Registered tool: aws_sqs_receive_message");
2921
+ // SQS: Purge Queue
2922
+ server.tool("aws_sqs_purge_queue", "Delete all messages from an SQS queue. Use with caution - this is irreversible.", {
2923
+ queueUrl: z.string().describe("SQS queue URL"),
2924
+ profile: z.string().optional().describe("AWS profile to use"),
2925
+ region: z.string().optional().describe("AWS region"),
2926
+ }, async ({ queueUrl, profile, region }) => {
2927
+ log.info("Tool called: aws_sqs_purge_queue");
2928
+ const result = await purgeSQSQueue({
2929
+ queueUrl,
2930
+ profile,
2931
+ region,
2932
+ }, log);
2933
+ if (!result.success) {
2934
+ return {
2935
+ content: [
2936
+ {
2937
+ type: "text",
2938
+ text: `Error: ${result.error}`,
2939
+ },
2940
+ ],
2941
+ };
2942
+ }
2943
+ return {
2944
+ content: [
2945
+ {
2946
+ type: "text",
2947
+ text: `Queue purged successfully. All messages have been deleted.`,
2948
+ },
2949
+ ],
2950
+ };
2951
+ });
2952
+ log.info("Registered tool: aws_sqs_purge_queue");
1783
2953
  log.info("MCP server configuration complete");
1784
2954
  return server;
1785
2955
  }