Skip to content

Commit f0a3bae

Browse files
committed
Enable deploying the example server stacks multiple times in an account for integ tests
1 parent 57ac94e commit f0a3bae

File tree

3 files changed

+197
-7
lines changed

3 files changed

+197
-7
lines changed
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
# This CloudFormation template configures an IAM identity provider that uses GitHub's OIDC,
2+
# enabling GitHub Actions to run the integration tests against the AWS account where this
3+
# template is deployed.
4+
# aws cloudformation deploy \
5+
# --template-file .github/cloudformation/integ-test-authentication.yaml \
6+
# --stack-name github-integ-test-identity-provider \
7+
# --parameter-overrides GitHubOrg=awslabs RepositoryName=utilities-for-model-context-protocol-with-aws-lambda \
8+
# --capabilities CAPABILITY_IAM \
9+
# --region us-east-2 \
10+
# --profile mcp-lambda-cfn-integ-test-account
11+
12+
Parameters:
13+
GitHubOrg:
14+
Description: Name of GitHub organization/user (case sensitive)
15+
Type: String
16+
RepositoryName:
17+
Description: Name of GitHub repository (case sensitive)
18+
Type: String
19+
OIDCProviderArn:
20+
Description: Arn for the GitHub OIDC Provider.
21+
Default: ""
22+
Type: String
23+
OIDCAudience:
24+
Description: Audience supplied to configure-aws-credentials.
25+
Default: "sts.amazonaws.com"
26+
Type: String
27+
28+
Conditions:
29+
CreateOIDCProvider: !Equals
30+
- !Ref OIDCProviderArn
31+
- ""
32+
33+
Resources:
34+
GithubOidc:
35+
Type: AWS::IAM::OIDCProvider
36+
Condition: CreateOIDCProvider
37+
Properties:
38+
Url: https://token.actions.githubusercontent.com
39+
ClientIdList:
40+
- sts.amazonaws.com
41+
ThumbprintList:
42+
- 6938fd4d98bab03faadb97b34396831e3780aea1
43+
44+
IntegrationTestRole:
45+
Type: AWS::IAM::Role
46+
Properties:
47+
AssumeRolePolicyDocument:
48+
Statement:
49+
- Effect: Allow
50+
Action: sts:AssumeRoleWithWebIdentity
51+
Principal:
52+
Federated: !If
53+
- CreateOIDCProvider
54+
- !Ref GithubOidc
55+
- !Ref OIDCProviderArn
56+
Condition:
57+
StringEquals:
58+
token.actions.githubusercontent.com:aud: !Ref OIDCAudience
59+
StringLike:
60+
token.actions.githubusercontent.com:sub: !Sub repo:${GitHubOrg}/${RepositoryName}:*
61+
62+
IntegrationTestPolicy:
63+
Type: "AWS::IAM::Policy"
64+
Properties:
65+
PolicyName: mcp-lambda-integration-test-runner
66+
PolicyDocument:
67+
Version: "2012-10-17"
68+
Statement:
69+
# Allow integration tests to manage CloudFormation stacks to deploy the example MCP servers
70+
- Effect: Allow
71+
Action:
72+
- "cloudformation:ContinueUpdateRollback"
73+
- "cloudformation:CreateChangeSet"
74+
- "cloudformation:DeleteChangeSet"
75+
- "cloudformation:DeleteStack"
76+
- "cloudformation:DescribeChangeSet"
77+
- "cloudformation:DescribeStacks"
78+
- "cloudformation:ExecuteChangeSet"
79+
Resource:
80+
- !Sub "arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/LambdaMcpServer-*"
81+
# Allow CloudFormation to provision the resources for the example MCP servers
82+
- Effect: Allow
83+
Action:
84+
- "lambda:CreateFunction"
85+
- "lambda:DeleteFunction"
86+
- "lambda:DeleteFunctionCodeSigningConfig"
87+
- "lambda:DeleteFunctionConcurrency"
88+
- "lambda:GetCodeSigningConfig"
89+
- "lambda:GetFunction"
90+
- "lambda:GetFunctionCodeSigningConfig"
91+
- "lambda:GetFunctionRecursionConfig"
92+
- "lambda:GetLayerVersion"
93+
- "lambda:GetRuntimeManagementConfig"
94+
- "lambda:ListFunctions"
95+
- "lambda:ListTags"
96+
- "lambda:PutFunctionCodeSigningConfig"
97+
- "lambda:PutFunctionConcurrency"
98+
- "lambda:PutFunctionRecursionConfig"
99+
- "lambda:PutRuntimeManagementConfig"
100+
- "lambda:TagResource"
101+
- "lambda:UntagResource"
102+
- "lambda:UpdateFunctionCode"
103+
- "lambda:UpdateFunctionConfiguration"
104+
- "lambda:DeleteLayerVersion"
105+
- "lambda:GetLayerVersion"
106+
- "lambda:ListLayerVersions"
107+
- "lambda:PublishLayerVersion"
108+
Resource: "*"
109+
Condition:
110+
"ForAnyValue:StringEquals":
111+
"aws:CalledVia":
112+
- cloudformation.amazonaws.com
113+
- Effect: Allow
114+
Action:
115+
- "iam:PassRole"
116+
Resource:
117+
- !GetAtt LambdaFunctionsRole.Arn
118+
Condition:
119+
"ForAnyValue:StringEquals":
120+
"aws:CalledVia":
121+
- cloudformation.amazonaws.com
122+
- Effect: Allow
123+
Action:
124+
- "ssm:GetParametersByPath"
125+
- "ssm:GetParameters"
126+
- "ssm:GetParameter"
127+
Resource:
128+
- !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/cdk-bootstrap/*/version"
129+
Condition:
130+
"ForAnyValue:StringEquals":
131+
"aws:CalledVia":
132+
- cloudformation.amazonaws.com
133+
# Allow integration tests to upload templates and assets to the CDK bucket
134+
- Effect: Allow
135+
Action:
136+
- "s3:PutObject"
137+
- "s3:AbortMultipartUpload"
138+
- "s3:ListMultipartUploadParts"
139+
Resource:
140+
- "arn:aws:s3:::cdk*/*"
141+
# Allow CloudFormation and Lambda to download templates and assets from the CDK bucket
142+
- Effect: Allow
143+
Action:
144+
- "s3:GetObject"
145+
- "s3:GetObjectVersion"
146+
Resource:
147+
- "arn:aws:s3:::cdk*/*"
148+
Condition:
149+
"ForAnyValue:StringEquals":
150+
"aws:CalledVia":
151+
- cloudformation.amazonaws.com
152+
Roles:
153+
- !Ref IntegrationTestRole
154+
155+
LambdaFunctionsRole:
156+
Type: AWS::IAM::Role
157+
Properties:
158+
RoleName: mcp-lambda-example-servers
159+
ManagedPolicyArns:
160+
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
161+
AssumeRolePolicyDocument:
162+
Statement:
163+
- Effect: Allow
164+
Action: sts:AssumeRole
165+
Principal:
166+
Service: lambda.amazonaws.com
167+
168+
Outputs:
169+
Role:
170+
Value: !GetAtt Role.Arn

examples/servers/time/cdk_stack.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,15 @@ def before_bundling(self, input_dir: str, output_dir: str) -> list[str]:
3434

3535

3636
class LambdaTimeMcpServer(Stack):
37-
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
37+
def __init__(
38+
self, scope: Construct, construct_id: str, stack_name_suffix: str, **kwargs
39+
) -> None:
3840
super().__init__(scope, construct_id, **kwargs)
3941

4042
lambda_python.PythonFunction(
4143
self,
4244
"ServerFunction",
43-
function_name="mcp-server-time",
45+
function_name="mcp-server-time" + stack_name_suffix,
4446
role=iam.Role.from_role_name(self, "Role", "mcp-lambda-example-servers"),
4547
runtime=lambda_.Runtime.PYTHON_3_13,
4648
entry="function",
@@ -65,9 +67,14 @@ def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
6567

6668
app = App()
6769
env = Environment(account=os.environ["CDK_DEFAULT_ACCOUNT"], region="us-east-2")
70+
stack_name_suffix = (
71+
f'-{os.environ["INTEG_TEST_ID"]}' if "INTEG_TEST_ID" in os.environ else ""
72+
)
6873
LambdaTimeMcpServer(
6974
app,
7075
"LambdaMcpServer-Time",
76+
stack_name_suffix,
77+
stack_name="LambdaMcpServer-Time" + stack_name_suffix,
7178
env=env,
7279
)
7380
app.synth()

examples/servers/weather-alerts/lib/weather-alerts-mcp-server.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@ import { Role } from "aws-cdk-lib/aws-iam";
66
import * as path from "path";
77

88
export class WeatherAlertsMcpServer extends cdk.Stack {
9-
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
9+
constructor(
10+
scope: Construct,
11+
id: string,
12+
stackNameSuffix: string,
13+
props?: cdk.StackProps
14+
) {
1015
super(scope, id, props);
1116

1217
// Package local module as a layer for testing
@@ -28,7 +33,7 @@ export class WeatherAlertsMcpServer extends cdk.Stack {
2833
});
2934

3035
new NodejsFunction(this, "function", {
31-
functionName: "mcp-server-weather-alerts",
36+
functionName: "mcp-server-weather-alerts" + stackNameSuffix,
3237
role: Role.fromRoleName(this, "role", "mcp-lambda-example-servers"),
3338
memorySize: 2048,
3439
runtime: Runtime.NODEJS_22_X,
@@ -56,7 +61,15 @@ export class WeatherAlertsMcpServer extends cdk.Stack {
5661
}
5762

5863
const app = new cdk.App();
59-
new WeatherAlertsMcpServer(app, "LambdaMcpServer-WeatherAlerts", {
60-
env: { account: process.env["CDK_DEFAULT_ACCOUNT"], region: "us-east-2" },
61-
});
64+
const stackNameSuffix =
65+
"INTEG_TEST_ID" in process.env ? `-${process.env["INTEG_TEST_ID"]}` : "";
66+
new WeatherAlertsMcpServer(
67+
app,
68+
"LambdaMcpServer-WeatherAlerts",
69+
stackNameSuffix,
70+
{
71+
env: { account: process.env["CDK_DEFAULT_ACCOUNT"], region: "us-east-2" },
72+
stackName: "LambdaMcpServer-WeatherAlerts" + stackNameSuffix,
73+
}
74+
);
6275
app.synth();

0 commit comments

Comments
 (0)