@jaypie/mcp 0.2.12 → 0.3.0
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/README.md +62 -0
- package/dist/aws.d.ts +197 -0
- package/dist/index.js +1179 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/prompts/Jaypie_CICD_with_GitHub_Actions.md +141 -14
- package/prompts/Jaypie_Init_CICD_with_GitHub_Actions.md +143 -96
- package/prompts/Jaypie_MCP_Package.md +249 -0
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
|
-
|
|
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.0#8cd307b1"
|
|
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
|
}
|