Skip to content

Commit fa1502f

Browse files
committed
feat(sample): add the stateful ecr sample
1 parent 5798fa8 commit fa1502f

26 files changed

+7552
-7
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ This repo provides samples to demonstrate how to build your own Generative AI so
2222
| [Code Expert](samples/code-expert/) | This project addresses the scalability limitations of manual code reviews by leveraging artificial intelligence to perform expert code reviews automatically. It leverages the [Bedrock Batch Step Functions CDK construct](https://github.com/awslabs/generative-ai-cdk-constructs/blob/main/src/patterns/gen-ai/aws-bedrock-batch-stepfn/README.md). | Backend | Python for Backend and Demo, TypeScript for CDK |
2323
|[Bedrock Agent UI Wrapper](samples/bedrock-agent-ui-wrapper/)| This sample provides a CDK construct that creates an API layer and frontend application for Amazon Bedrock Agents. It includes authentication with Amazon Cognito, agent trace streaming, and can be deployed locally or on ECS Fargate. | API layer + Frontend | Python|
2424
|[Stateless MCP Server on AWS Lambda](samples/mcp-stateless-lambda/)| Sample MCP Server running natively on AWS Lambda and API Gateway without any extra bridging components or custom transports and a test MCP client. | API layer | TypeScript |
25-
|[Stateless MCP Server on ECS](samples/mcp-stateless-ecs/)| Sample MCP Server running natively on ECS Fargate and ALB without any extra bridging components or custom transports and a test MCP client. | API layer | TypeScript |
25+
|[Stateless MCP Server on ECS](samples/mcp-stateless-ecs/)| Sample stateless MCP Server running natively on ECS Fargate and ALB without any extra bridging components or custom transports and a test MCP client. | API layer | TypeScript |
26+
|[Stateful MCP Server on ECS](samples/mcp-stateful-ecs/)| Sample stateful MCP Server running natively on ECS Fargate and ALB without any extra bridging components or custom transports and a test MCP client. | API layer | TypeScript |
2627

2728
## Contributing
2829

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
!jest.config.js
2+
*.d.ts
3+
node_modules
4+
5+
# CDK asset staging directory
6+
.cdk.staging
7+
cdk.out
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
*.ts
2+
!*.d.ts
3+
4+
# CDK asset staging directory
5+
.cdk.staging
6+
cdk.out

samples/mcp-stateful-ecs/README.md

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
# Stateful MCP Server on ECS Fargate
2+
3+
This is a sample MCP Server running natively on on ECS Fargate and ALB without any extra bridging components or custom transports. This is now possible thanks to the [Streamable HTTP transport](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) introduced in v2025-03-26.
4+
5+
The solution provides the CDK code to deploy the server on AWS, as well as a sample client to test the deployed server.
6+
7+
The original Terraform version of this sample is available [here](https://github.com/aws-samples/sample-serverless-mcp-servers/tree/main/stateful-mcp-on-ecs-nodejs)
8+
9+
## Overview
10+
11+
MCP Server can run in two modes - stateless and stateful. This repo demonstrates the stateful mode.
12+
13+
Stateful mode implies a persistent SSE connection established between MCP Client and MCP Server. This connection is used for MCP Server to be able to support resumability and proactively send notifications to MCP Clients. This works fine when you have a single instance of MCP Server running (e.g a single ECS Task). This does not work out-of-the-box if you want to have more than one ECS Task since a session will be established with one task, but subsequent requests may hit a different task.
14+
15+
As of building this sample (early May 2025), the TypeScript implementation of MCP Server SDK does not support externalizing session info, meaning session cannot be synchronized across different server instances.
16+
17+
It is possible to address this concern by using ALB with cookie-based sticky sessions, which will insure that requests for a session established with a particular task will always be forwarded to the same task. However, MCP Client SDK does not support cookies by default. To address this concern, this sample injects cookie support into `fetch`, the framework MCP Client uses under-the-hood for HTTP communications (see [mcp_client/index.js](./mcp_client/index.js))
18+
19+
By default, this sample uses the default ALB endpoint, which is HTTP only. See [this](https://github.com/awslabs/aws-solutions-constructs/tree/main/source/patterns/%40aws-solutions-constructs/aws-alb-fargate) to configure the code to use HTTPS.
20+
21+
Only use HTTP for testing purposes ONLY!!! NEVER expose ANYTHING via plain HTTP, always use HTTPS!!!
22+
23+
![Architecture Diagram](./doc/architecture.png)
24+
25+
## Folder Structure
26+
27+
This sample application codebase is organized into folders : the backend code lives in ```bin/mcp-stateful-ecs.ts``` and uses the AWS CDK resources defined in the ```lib``` folder.
28+
29+
The key folders are:
30+
31+
```
32+
samples/mcp-stateful-ecs
33+
34+
├── bin
35+
│ └── mcp-stateful-ecs.ts # Backend - CDK app
36+
├── lib # CDK Stacks
37+
│ ├── mcp-stateful-ecs-stack.ts # Stack deploying the resources
38+
├── mcp_client # test mcp client to connect to the remote server
39+
├── mcp_server # mcp server implementation
40+
```
41+
42+
## Getting started
43+
44+
### Prerequisites
45+
46+
- An AWS account. We recommend you deploy this solution in a new account.
47+
- [AWS CLI](https://aws.amazon.com/cli/): configure your credentials
48+
49+
```
50+
aws configure --profile [your-profile]
51+
AWS Access Key ID [None]: xxxxxx
52+
AWS Secret Access Key [None]:yyyyyyyyyy
53+
Default region name [None]: us-east-1
54+
Default output format [None]: json
55+
```
56+
57+
- Node.js: v18.12.1
58+
- [AWS CDK](https://github.com/aws/aws-cdk/releases/tag/v2.114.0): 2.114.0
59+
- jq: jq-1.6
60+
61+
### Deploy the solution
62+
63+
This project is built using the [AWS Cloud Development Kit (CDK)](https://aws.amazon.com/cdk/). See [Getting Started With the AWS CDK](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html) for additional details and prerequisites.
64+
65+
1. Clone this repository.
66+
67+
```shell
68+
git clone https://github.com/aws-samples/generative-ai-cdk-constructs-samples.git
69+
```
70+
71+
2. Enter the code sample backend directory.
72+
73+
```shell
74+
cd samples/mcp-stateful-ecs
75+
```
76+
77+
3. Install packages
78+
79+
```shell
80+
npm install
81+
```
82+
83+
4. Install the dependencies
84+
85+
```shell
86+
(cd mcp_client && npm install)
87+
(cd mcp_server && npm install)
88+
```
89+
90+
5. Boostrap AWS CDK resources on the AWS account.
91+
92+
```shell
93+
cdk bootstrap aws://ACCOUNT_ID/REGION
94+
```
95+
96+
(optional) If needed, update the region in [mcp-stateful-ecs.ts](./bin/mcp-stateful-ecs.ts). Default is `us-east-1`
97+
98+
```
99+
env: {
100+
region: 'us-east-1'
101+
},
102+
```
103+
104+
6. Deploy the sample in your account.
105+
106+
```shell
107+
$ cdk deploy
108+
```
109+
110+
The command above will deploy one stack in your account. With the default configuration of this sample.
111+
112+
To protect you against unintended changes that affect your security posture, the AWS CDK Toolkit prompts you to approve security-related changes before deploying them. You will need to answer yes to get all the stack deployed.
113+
114+
7. Retrieve the value of the CfnOutput related to your remote MCP server endpoint from the stack
115+
116+
```shell
117+
$ aws cloudformation describe-stacks --stack-name McpStatefulEcsStack --query "Stacks[0].Outputs[?contains(OutputKey, 'McpServerEndpoint')].OutputValue"
118+
119+
[
120+
"OutputValue": "http://<endpoint>.us-east-1.elb/mcp"
121+
]
122+
```
123+
124+
8. Export an env variable named `MCP_SERVER_ENDPOINT` with the previous output value
125+
126+
```shell
127+
export MCP_SERVER_ENDPOINT='value'
128+
```
129+
130+
### Test your remote MCP Server with MCP client
131+
132+
Use the provided mcp client to test your remote mcp server
133+
134+
```shell
135+
node mcp_client/index.js
136+
```
137+
138+
Observe the response:
139+
140+
```
141+
Connecting ENDPOINT_URL=http://XXXXXXXX.us-east-1.elb/mcp
142+
connected
143+
listTools response: { tools: [ { name: 'ping', inputSchema: [Object] } ] }
144+
callTool:ping response: {
145+
content: [
146+
{
147+
type: 'text',
148+
text: 'pong! taskId=task/McpStatefulEcsStack-AlbToFargateclusterC8F84258-DmTWrC1tlDtl/4bfaa6a6ddc14281b2457fbd70bd5565 v=0.0.11 d=100'
149+
}
150+
]
151+
}
152+
callTool:ping response: {
153+
content: [
154+
{
155+
type: 'text',
156+
text: 'pong! taskId=task/McpStatefulEcsStack-AlbToFargateclusterC8F84258-DmTWrC1tlDtl/4bfaa6a6ddc14281b2457fbd70bd5565 v=0.0.11 d=50'
157+
}
158+
]
159+
}
160+
```
161+
162+
## Clean up
163+
164+
Do not forget to delete the stack to avoid unexpected charges.
165+
166+
```shell
167+
$ cdk destroy
168+
```
169+
170+
Delete associated logs created by the different services in Amazon CloudWatch logs.
171+
172+
## Content Security Legal Disclaimer
173+
174+
The sample code; software libraries; command line tools; proofs of concept; templates; or other related technology (including any of the foregoing that are provided by our personnel) is provided to you as AWS Content under the AWS Customer Agreement, or the relevant written agreement between you and AWS (whichever applies). You should not use this AWS Content in your production accounts, or on production or other critical data. You are responsible for testing, securing, and optimizing the AWS Content, such as sample code, as appropriate for production grade use based on your specific quality control practices and standards. Deploying AWS Content may incur AWS charges for creating or using AWS chargeable resources, such as running Amazon EC2 instances or using Amazon S3 storage.
175+
176+
## Operational Metrics Collection
177+
178+
This solution collects anonymous operational metrics to help AWS improve the quality and features of the solution. Data collection is subject to the AWS Privacy Policy (https://aws.amazon.com/privacy/). To opt out of this feature, simply remove the tag(s) starting with “uksb-” or “SO” from the description(s) in any CloudFormation templates or CDK TemplateOptions.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/usr/bin/env node
2+
import * as cdk from 'aws-cdk-lib';
3+
import { McpStatefulEcsStack } from '../lib/mcp-stateful-ecs-stack';
4+
5+
const app = new cdk.App();
6+
new McpStatefulEcsStack(app, 'McpStatefulEcsStack', {
7+
env: {
8+
region: 'us-east-1'
9+
},
10+
/* If you don't specify 'env', this stack will be environment-agnostic.
11+
* Account/Region-dependent features and context lookups will not work,
12+
* but a single synthesized template can be deployed anywhere. */
13+
14+
/* Uncomment the next line to specialize this stack for the AWS Account
15+
* and Region that are implied by the current CLI configuration. */
16+
// env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
17+
18+
/* Uncomment the next line if you know exactly what Account and Region you
19+
* want to deploy the stack to. */
20+
// env: { account: '123456789012', region: 'us-east-1' },
21+
22+
/* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */
23+
description: 'Description: (uksb-1tupboc43) (tag:mcp-stateful-ecs-sample)'
24+
});

samples/mcp-stateful-ecs/cdk.json

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
{
2+
"app": "npx ts-node --prefer-ts-exts bin/mcp-stateful-ecs.ts",
3+
"watch": {
4+
"include": [
5+
"**"
6+
],
7+
"exclude": [
8+
"README.md",
9+
"cdk*.json",
10+
"**/*.d.ts",
11+
"**/*.js",
12+
"tsconfig.json",
13+
"package*.json",
14+
"yarn.lock",
15+
"node_modules",
16+
"test"
17+
]
18+
},
19+
"context": {
20+
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
21+
"@aws-cdk/core:checkSecretUsage": true,
22+
"@aws-cdk/core:target-partitions": [
23+
"aws",
24+
"aws-cn"
25+
],
26+
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
27+
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
28+
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
29+
"@aws-cdk/aws-iam:minimizePolicies": true,
30+
"@aws-cdk/core:validateSnapshotRemovalPolicy": true,
31+
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
32+
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
33+
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
34+
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
35+
"@aws-cdk/core:enablePartitionLiterals": true,
36+
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
37+
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
38+
"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
39+
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
40+
"@aws-cdk/aws-route53-patters:useCertificate": true,
41+
"@aws-cdk/customresources:installLatestAwsSdkDefault": false,
42+
"@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
43+
"@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
44+
"@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
45+
"@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
46+
"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
47+
"@aws-cdk/aws-redshift:columnId": true,
48+
"@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
49+
"@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
50+
"@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
51+
"@aws-cdk/aws-kms:aliasNameRef": true,
52+
"@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
53+
"@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
54+
"@aws-cdk/aws-efs:denyAnonymousAccess": true,
55+
"@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true,
56+
"@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true,
57+
"@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true,
58+
"@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true,
59+
"@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true,
60+
"@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true,
61+
"@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true,
62+
"@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true,
63+
"@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true,
64+
"@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true,
65+
"@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true,
66+
"@aws-cdk/aws-eks:nodegroupNameAttribute": true,
67+
"@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true,
68+
"@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true,
69+
"@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false,
70+
"@aws-cdk/aws-s3:keepNotificationInImportedBucket": false,
71+
"@aws-cdk/aws-ecs:enableImdsBlockingDeprecatedFeature": false,
72+
"@aws-cdk/aws-ecs:disableEcsImdsBlocking": true,
73+
"@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": true,
74+
"@aws-cdk/aws-dynamodb:resourcePolicyPerReplica": true,
75+
"@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": true,
76+
"@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": true,
77+
"@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": true,
78+
"@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": true,
79+
"@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": true,
80+
"@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": true,
81+
"@aws-cdk/aws-ec2:bastionHostUseAmazonLinux2023ByDefault": true,
82+
"@aws-cdk/aws-route53-targets:userPoolDomainNameMethodWithoutCustomResource": true,
83+
"@aws-cdk/aws-elasticloadbalancingV2:albDualstackWithoutPublicIpv4SecurityGroupRulesDefault": true,
84+
"@aws-cdk/aws-iam:oidcRejectUnauthorizedConnections": true,
85+
"@aws-cdk/core:enableAdditionalMetadataCollection": true,
86+
"@aws-cdk/aws-lambda:createNewPoliciesWithAddToRolePolicy": true,
87+
"@aws-cdk/aws-s3:setUniqueReplicationRoleName": true
88+
}
89+
}
62.3 KB
Loading
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module.exports = {
2+
testEnvironment: 'node',
3+
roots: ['<rootDir>/test'],
4+
testMatch: ['**/*.test.ts'],
5+
transform: {
6+
'^.+\\.tsx?$': 'ts-jest'
7+
}
8+
};

0 commit comments

Comments
 (0)