Skip to content

Commit 5798fa8

Browse files
committed
chore(sample): add missing files
1 parent 6cff68d commit 5798fa8

File tree

7 files changed

+247
-30
lines changed

7 files changed

+247
-30
lines changed

samples/mcp-stateless-ecs/README.md

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,35 @@ This is a sample MCP Server running natively on on ECS Fargate and ALB without a
44

55
The solution provides the CDK code to deploy the server on AWS, as well as a sample client to test the deployed server.
66

7-
The original Terraform version of this sample is available [here](https://github.com/aws-samples/sample-serverless-mcp-servers/tree/main/stateless-mcp-on-lambda-nodejs)
7+
The original Terraform version of this sample is available [here](https://github.com/aws-samples/sample-serverless-mcp-servers/tree/main/stateless-mcp-on-ecs-nodejs)
88

99
## Overview
1010

1111
MCP Server can run in two modes - stateless and stateful. This repo demonstrates the stateless mode.
1212

1313
In stateless mode, clients do not establish persistent SSE connections to MCP Server. This means clients will not receive proactive notifications from the server. On the other hand, stateless mode allows you to scale your server horizontally.
1414

15-
This sample implements simple authorization demo with API Gateway Custom Authorizer.
15+
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.
16+
17+
Only use HTTP for testing purposes ONLY!!! NEVER expose ANYTHING via plain HTTP, always use HTTPS!!!
1618

1719
![Architecture Diagram](./doc/architecture.png)
1820

1921
## Folder Structure
2022

21-
This sample application codebase is organized into folders : the backend code lives in ```bin/mcp-stateless-lambda.ts``` and uses the AWS CDK resources defined in the ```lib``` folder.
23+
This sample application codebase is organized into folders : the backend code lives in ```bin/mcp-stateless-ecs.ts``` and uses the AWS CDK resources defined in the ```lib``` folder.
2224

2325
The key folders are:
2426

2527
```
26-
samples/mcp-stateless-lambda
28+
samples/mcp-stateless-ecs
2729
2830
├── bin
29-
│ └── mcp-stateless-lambda.ts # Backend - CDK app
31+
│ └── mcp-stateless-ecs.ts # Backend - CDK app
3032
├── lib # CDK Stacks
31-
│ ├── mcp-stateless-lambda-stack.ts # Stack deploying the resources
33+
│ ├── mcp-stateless-ecs-stack.ts # Stack deploying the resources
3234
├── mcp_client # test mcp client to connect to the remote server
33-
├── lambdas # lambda functions
35+
├── mcp_server # mcp server implementation
3436
```
3537

3638
## Getting started
@@ -65,7 +67,7 @@ This project is built using the [AWS Cloud Development Kit (CDK)](https://aws.am
6567
2. Enter the code sample backend directory.
6668

6769
```shell
68-
cd samples/mcp-stateless-lambda
70+
cd samples/mcp-stateless-ecs
6971
```
7072

7173
3. Install packages
@@ -87,6 +89,14 @@ This project is built using the [AWS Cloud Development Kit (CDK)](https://aws.am
8789
cdk bootstrap aws://ACCOUNT_ID/REGION
8890
```
8991

92+
(optional) If needed, update the region in [mcp-stateless-ecs.ts](./bin/mcp-stateless-ecs.ts). Default is `us-east-1`
93+
94+
```
95+
env: {
96+
region: 'us-east-1'
97+
},
98+
```
99+
90100
6. Deploy the sample in your account.
91101
92102
```shell
@@ -100,10 +110,10 @@ To protect you against unintended changes that affect your security posture, the
100110
7. Retrieve the value of the CfnOutput related to your remote MCP server endpoint from the stack
101111
102112
```shell
103-
$ aws cloudformation describe-stacks --stack-name McpStatelessLambdaStack --query "Stacks[0].Outputs[?contains(OutputKey, 'McpServerEndpoint')].OutputValue"
113+
$ aws cloudformation describe-stacks --stack-name McpStatelessEcsStack --query "Stacks[0].Outputs[?contains(OutputKey, 'McpServerEndpoint')].OutputValue"
104114
105115
[
106-
"OutputValue": "https://<endpoint>/dev/mcp"
116+
"OutputValue": "http://<endpoint>/dev/mcp"
107117
]
108118
```
109119
@@ -124,22 +134,22 @@ node mcp_client/index.js
124134
Observe the response:
125135

126136
```
127-
Connecting ENDPOINT_URL=XXXXXXXX.amazonaws.com/dev/mcp
137+
Connecting ENDPOINT_URL=http://XXXXXXXX.us-east-1.elb/mcp
128138
connected
129139
listTools response: { tools: [ { name: 'ping', inputSchema: [Object] } ] }
130140
callTool:ping response: {
131141
content: [
132142
{
133143
type: 'text',
134-
text: 'pong! logStream=2025/05/06/[$LATEST]7037eebd7f314fa18d6320801a54a50f v=0.0.12 d=49'
144+
text: 'pong! taskId=task/McpStatelessEcsStack-AlbToFargateclusterC8F84258-uYsPRhnSCgeG/b181d8eb08a34bc78723ef7022262305 v=0.0.14 d=99'
135145
}
136146
]
137147
}
138148
callTool:ping response: {
139149
content: [
140150
{
141151
type: 'text',
142-
text: 'pong! logStream=2025/05/06/[$LATEST]7037eebd7f314fa18d6320801a54a50f v=0.0.12 d=101'
152+
text: 'pong! taskId=task/McpStatelessEcsStack-AlbToFargateclusterC8F84258-uYsPRhnSCgeG/b181d8eb08a34bc78723ef7022262305 v=0.0.14 d=51'
143153
}
144154
]
145155
}

samples/mcp-stateless-ecs/bin/mcp-stateless-ecs.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import { McpStatelessEcsStack } from '../lib/mcp-stateless-ecs-stack';
44

55
const app = new cdk.App();
66
new McpStatelessEcsStack(app, 'McpStatelessEcsStack', {
7+
env: {
8+
region: 'us-east-1'
9+
},
710
/* If you don't specify 'env', this stack will be environment-agnostic.
811
* Account/Region-dependent features and context lookups will not work,
912
* but a single synthesized template can be deployed anywhere. */
Lines changed: 147 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,155 @@
1+
/**
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
5+
* with the License. A copy of the License is located at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES
10+
* OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions
11+
* and limitations under the License.
12+
*/
113
import * as cdk from 'aws-cdk-lib';
214
import { Construct } from 'constructs';
3-
// import * as sqs from 'aws-cdk-lib/aws-sqs';
15+
import * as ecs from 'aws-cdk-lib/aws-ecs';
16+
import path = require('path');
17+
import { AlbToFargate } from '@aws-solutions-constructs/aws-alb-fargate';
18+
import * as ecr_assets from 'aws-cdk-lib/aws-ecr-assets';
19+
import * as iam from 'aws-cdk-lib/aws-iam';
20+
import { Duration, RemovalPolicy } from 'aws-cdk-lib';
21+
import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2';
22+
import * as logs from 'aws-cdk-lib/aws-logs';
423

524
export class McpStatelessEcsStack extends cdk.Stack {
625
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
726
super(scope, id, props);
827

9-
// The code that defines your stack goes here
28+
const execution_role = new iam.Role(
29+
this,
30+
"ExecutionRole",
31+
{
32+
assumedBy: new iam.ServicePrincipal("ecs-tasks.amazonaws.com"),
33+
managedPolicies: [
34+
iam.ManagedPolicy.fromAwsManagedPolicyName("service-role/AmazonECSTaskExecutionRolePolicy")
35+
]
36+
}
37+
)
1038

11-
// example resource
12-
// const queue = new sqs.Queue(this, 'McpStatelessEcsQueue', {
13-
// visibilityTimeout: cdk.Duration.seconds(300)
14-
// });
15-
}
16-
}
39+
const task_role = new iam.Role(
40+
this,
41+
"TaskRole",
42+
{
43+
assumedBy: new iam.ServicePrincipal("ecs-tasks.amazonaws.com"),
44+
}
45+
)
46+
47+
task_role.addToPolicy(
48+
new iam.PolicyStatement(
49+
{
50+
actions: ["logs:CreateLogGroup",
51+
"logs:CreateLogStream",
52+
"logs:PutLogEvents",
53+
],
54+
resources: ["*"],
55+
}
56+
)
57+
)
58+
59+
const log_group = new logs.LogGroup(
60+
this,
61+
"LogGroup",
62+
{
63+
retention: logs.RetentionDays.ONE_WEEK,
64+
removalPolicy: RemovalPolicy.DESTROY,
65+
}
66+
)
67+
68+
const task_definition = new ecs.FargateTaskDefinition(
69+
this,
70+
"TaskDefinition",
71+
{
72+
taskRole: task_role,
73+
executionRole: execution_role,
74+
}
75+
)
76+
77+
const container_definition_props = {
78+
image: ecs.ContainerImage.fromAsset(
79+
path.join(__dirname, '../mcp_server'),
80+
{
81+
file: 'Dockerfile',
82+
platform: ecr_assets.Platform.LINUX_AMD64
83+
}
84+
),
85+
essential: true,
86+
portMappings: [
87+
{
88+
containerPort: 3000,
89+
hostPort: 3000,
90+
protocol: ecs.Protocol.TCP
91+
}
92+
],
93+
logging: ecs.LogDrivers.awsLogs({
94+
logGroup: log_group,
95+
streamPrefix: "ecs"
96+
})
97+
}
98+
99+
task_definition.addContainer("McpServerContainer",
100+
{
101+
image: ecs.ContainerImage.fromAsset(
102+
path.join(__dirname, '../mcp_server'),
103+
{
104+
file: 'Dockerfile',
105+
platform: ecr_assets.Platform.LINUX_AMD64
106+
}
107+
),
108+
essential: true,
109+
portMappings: [
110+
{
111+
containerPort: 3000,
112+
hostPort: 3000,
113+
protocol: ecs.Protocol.TCP
114+
}
115+
],
116+
logging: ecs.LogDrivers.awsLogs({
117+
logGroup: log_group,
118+
streamPrefix: "ecs"
119+
})
120+
}
121+
)
122+
123+
// Define target group configuration
124+
const target_group_props = {
125+
port: 3000,
126+
protocol: elbv2.ApplicationProtocol.HTTP,
127+
healthCheck: {
128+
path: "/health",
129+
port: "3000",
130+
healthyHttpCodes: "200",
131+
interval: Duration.seconds(30),
132+
timeout: Duration.seconds(5)
133+
},
134+
targetType: elbv2.TargetType.IP
135+
}
136+
137+
const albToFargate = new AlbToFargate(this, 'AlbToFargate', {
138+
publicApi: true,
139+
fargateTaskDefinitionProps: task_definition,
140+
targetGroupProps: target_group_props,
141+
containerDefinitionProps: container_definition_props,
142+
listenerProps: {
143+
port: 80,
144+
protocol: elbv2.ApplicationProtocol.HTTP,
145+
},
146+
});
147+
148+
// add output for the endpoint
149+
new cdk.CfnOutput(this, 'McpServerEndpoint', {
150+
description: 'MCP Server Endpoint',
151+
value: `http://${albToFargate.loadBalancer.loadBalancerDnsName}/mcp`,
152+
exportName: `${cdk.Stack.of(this).stackName}${id}McpServerEndpoint`,
153+
});
154+
155+
}}

samples/mcp-stateless-ecs/mcp_client/package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

samples/mcp-stateless-ecs/mcp_server/package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)