@chinchillaenterprises/mcp-amplify
Version:
AWS Amplify MCP server with intelligent deployment automation, specialized logging suite, and recursive resource discovery
176 lines • 8.01 kB
JavaScript
import * as fs from 'fs';
import path from 'path';
import { AppSyncClient, ListGraphqlApisCommand, ListDataSourcesCommand } from '@aws-sdk/client-appsync';
/**
* Discover all resources in an Amplify sandbox
*
* Strategy:
* 1. Read amplify_outputs.json to get GraphQL URL and auth config
* 2. Query AppSync to find API by matching GraphQL URL
* 3. List all data sources (DynamoDB tables + Lambda functions)
* 4. Return complete sandbox resource inventory
*/
export async function handleAmplifyDiscoverSandboxResources(args) {
try {
// Get outputs file path
const outputsPath = args?.outputsPath || path.join(process.cwd(), 'amplify_outputs.json');
// Read amplify_outputs.json
if (!fs.existsSync(outputsPath)) {
throw new Error(`amplify_outputs.json not found at ${outputsPath}. ` +
`Make sure you've run "npx ampx sandbox" in your Amplify project.`);
}
const outputsContent = fs.readFileSync(outputsPath, 'utf-8');
const outputs = JSON.parse(outputsContent);
// Validate outputs structure
if (!outputs.data || !outputs.data.url) {
throw new Error('Invalid amplify_outputs.json: missing data.url. ' +
'Make sure your sandbox has deployed successfully.');
}
const targetGraphqlUrl = outputs.data.url;
const region = outputs.data.aws_region || 'us-east-1';
// Initialize AppSync client
const appSyncClient = new AppSyncClient({ region });
// Step 1: Find AppSync API by matching GraphQL URL
let foundApi = null;
let allApis = [];
try {
const listApisResponse = await appSyncClient.send(new ListGraphqlApisCommand({}));
allApis = listApisResponse.graphqlApis || [];
for (const api of allApis) {
if (api.uris?.GRAPHQL === targetGraphqlUrl) {
foundApi = api;
break;
}
}
}
catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to query AppSync APIs: ${errorMsg}`);
}
if (!foundApi) {
throw new Error(`Could not find AppSync API matching URL ${targetGraphqlUrl}. ` +
`The sandbox may not be running. Run "npx ampx sandbox" to start it.`);
}
const apiId = foundApi.apiId;
// Step 2: Get data sources for this API
let dataSourcesResponse;
try {
dataSourcesResponse = await appSyncClient.send(new ListDataSourcesCommand({ apiId }));
}
catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to query AppSync data sources: ${errorMsg}`);
}
const dataSources = dataSourcesResponse.dataSources || [];
// Step 3: Organize data sources by type
const dynamodbTables = dataSources
.filter(ds => ds.type === 'AMAZON_DYNAMODB')
.map(ds => ({
name: ds.dynamodbConfig?.tableName,
dataSourceName: ds.name,
region: ds.dynamodbConfig?.awsRegion
}));
const lambdaFunctions = dataSources
.filter(ds => ds.type === 'AWS_LAMBDA')
.map(ds => {
const arn = ds.lambdaConfig?.lambdaFunctionArn || '';
const functionName = arn.split(':').pop() || 'unknown';
return {
name: functionName,
arn: arn,
dataSourceName: ds.name
};
});
const otherDataSources = dataSources
.filter(ds => ds.type !== 'AMAZON_DYNAMODB' && ds.type !== 'AWS_LAMBDA')
.map(ds => ({
name: ds.name,
type: ds.type
}));
// Step 4: Extract auth and model information
const authConfig = outputs.auth ? {
userPoolId: outputs.auth.user_pool_id,
clientId: outputs.auth.user_pool_client_id,
identityPoolId: outputs.auth.identity_pool_id,
oauthProviders: outputs.auth.oauth?.identity_providers || [],
oauthDomain: outputs.auth.oauth?.domain,
passwordPolicy: outputs.auth.password_policy,
mfaConfiguration: outputs.auth.mfa_configuration
} : null;
const models = outputs.data?.model_introspection?.models || {};
const modelNames = Object.keys(models);
// Step 5: Build response
return {
success: true,
sandbox: {
apiId,
graphqlUrl: targetGraphqlUrl,
region,
apiName: foundApi.name,
defaultAuthType: foundApi.authenticationType,
additionalAuthTypes: foundApi.additionalAuthenticationProviders?.map(auth => auth.authenticationType) || []
},
authentication: authConfig,
dynamoDB: {
count: dynamodbTables.length,
tables: dynamodbTables
},
lambda: {
count: lambdaFunctions.length,
functions: lambdaFunctions
},
dataModels: {
count: modelNames.length,
models: modelNames.map(modelName => {
const model = models[modelName];
const fields = Object.keys(model.fields || {});
const hasAuth = model.attributes?.some((a) => a.type === 'auth') || false;
const indexes = model.attributes
?.filter((a) => a.type === 'key')
.map((a) => a.properties?.name) || [];
return {
name: modelName,
fieldCount: fields.length,
fields: fields,
hasAuthRules: hasAuth,
customIndexes: indexes
};
})
},
otherDataSources: {
count: otherDataSources.length,
sources: otherDataSources
},
summary: {
totalDataSources: dataSources.length,
totalDynamoDBTables: dynamodbTables.length,
totalLambdaFunctions: lambdaFunctions.length,
totalDataModels: modelNames.length,
authEnabled: authConfig !== null,
hasOAuth: (authConfig?.oauthProviders?.length || 0) > 0
},
nextSteps: [
`Query DynamoDB tables: Use the table names above with AWS CLI or AWS SDK`,
`Invoke Lambda functions: Use the function ARNs with AWS Lambda SDK`,
`Query GraphQL API: Use ${targetGraphqlUrl} with your configured auth method`,
`Use data models: Models are available via GraphQL queries: ${modelNames.slice(0, 3).join(', ')}${modelNames.length > 3 ? ', ...' : ''}`
]
};
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
// Provide helpful context based on error type
let helpText = '';
if (errorMessage.includes('not found') || errorMessage.includes('ENOENT')) {
helpText = '\n\nMake sure you are in your Amplify project directory and have run: npx ampx sandbox';
}
else if (errorMessage.includes('credentials') || errorMessage.includes('EAUTH')) {
helpText = '\n\nMake sure your AWS credentials are configured: aws configure';
}
else if (errorMessage.includes('permission')) {
helpText = '\n\nYou may not have permission to access AppSync. Check your AWS IAM permissions.';
}
throw new Error(`Failed to discover sandbox resources: ${errorMessage}${helpText}`);
}
}
//# sourceMappingURL=sandbox-resource-discovery-handlers.js.map