A Model Context Protocol (MCP) server built with Spring AI MCP, Spring Boot, GraalVM, and AWS Cognito OAuth2, deployed on AWS EC2 with Google SSO and Streaming HTTP/SSE support.
This project implements a production-ready MCP server using the Spring AI MCP Server library (spring-ai-starter-mcp-server-webmvc:1.1.0-M4) that provides tools for AI assistants to interact with through the JSON-RPC 2.0 protocol with Server-Sent Events (SSE) streaming. The server leverages:
- Spring AI MCP - Automatic tool discovery and registration with
@Toolannotations - AWS Cognito - Enterprise-grade OAuth2 authentication
- Google SSO - Seamless user authentication via Google accounts
- Spring Boot 3.3.x - Modern Java framework with production-ready features
- GraalVM - Optional native image compilation for faster startup
- EC2 Deployment - Runs on EC2 with embedded Tomcat for full Spring MVC support
- CloudFormation - Infrastructure as Code for reproducible deployments
- ✅ OAuth2 Authentication - AWS Cognito with Google SSO integration
- ✅ Spring Security - Enterprise-grade security with OAuth2 Client support
- ✅ Streamable HTTP Transport - MCP spec 2025-03-26 (implemented using SSE)
- ✅ Spring AI MCP Server - Official Spring AI MCP library
- ✅ Automatic Tool Discovery - @Tool annotated methods auto-registered
- ✅ Spring Boot 3.3.x - Latest Spring framework with enhanced features
- ✅ EC2 Deployment - Full CloudFormation template with VPC, security groups
- ✅ GraalVM Native Image - Optional native compilation for faster startup
- ✅ Web UI - Thymeleaf template showing user info and authentication status
- ✅ Example Tools - Pre-built tools (add, multiply, echo, calculator, getCurrentTime)
- ✅ Rich Documentation - Tools with markdown descriptions
- ✅ Extensible Architecture - Add tools by defining @Tool methods
┌─────────────┐
│ Client │
│ (Claude) │
└─────┬───────┘
│ HTTPS + SSE Streaming
│
┌─────▼──────────────┐
│ Lambda Function │
│ URL (Streaming) │
└─────┬──────────────┘
│
┌─────▼──────────┐
│ Lambda │
│ Function │
│ ┌──────────┐ │
│ │ Spring │ │
│ │ AI MCP │ │
│ │ Server │ │
│ │ (SSE) │ │
│ └──────────┘ │
└────────────────┘
Note: This architecture has been replaced with EC2 deployment (see below).
┌──────────────┐
│ User │
│ (Browser) │
└──────┬───────┘
│ 1. Login with Google
│
┌──────▼────────────────┐
│ AWS Cognito │
│ User Pool │
│ ┌─────────────────┐ │
│ │ Google Identity │ │
│ │ Provider │ │
│ └─────────────────┘ │
└──────┬────────────────┘
│ 2. OAuth2 Tokens
│
┌──────▼────────────────────────┐
│ EC2 Instance │
│ ┌─────────────────────────┐ │
│ │ Spring Boot App │ │
│ │ ┌───────────────────┐ │ │
│ │ │ Spring Security │ │ │
│ │ │ OAuth2 Client │ │ │
│ │ └────────┬──────────┘ │ │
│ │ │ │ │
│ │ ┌────────▼──────────┐ │ │
│ │ │ Spring AI MCP │ │ │
│ │ │ Server │ │ │
│ │ │ (Streamable HTTP) │ │ │
│ │ │ /mcp/messages │ │ │
│ │ └────────┬──────────┘ │ │
│ │ │ │ │
│ │ ┌────────▼──────────┐ │ │
│ │ │ @Tool Services │ │ │
│ │ │ (Auto-discovered) │ │ │
│ │ └───────────────────┘ │ │
│ └─────────────────────────┘ │
└────────────────────────────────┘
┌──────────────┐
│ AI Client │
│ (Claude) │
└──────┬───────┘
│ 3. MCP Requests via Streamable HTTP
│ (JSON-RPC over SSE)
│
└──────► /mcp/messages (Streamable HTTP endpoint)
-
AWS Cognito User Pool
- Manages user authentication
- Integrates Google as identity provider
- Issues OAuth2 tokens (ID token, access token, refresh token)
-
Spring Security OAuth2
- Validates OAuth2 tokens
- Manages user sessions
- Protects MCP endpoints
-
Spring AI MCP Server
- Auto-configured via
spring-ai-starter-mcp-server-webmvc - Handles JSON-RPC 2.0 protocol
- Uses Streamable HTTP transport (implemented via SSE)
- Endpoint:
/mcp/messagesfor Streamable HTTP - Automatically discovers
@Toolannotated methods
- Auto-configured via
-
EC2 Instance
- Runs embedded Tomcat server
- Deployed via CloudFormation
- Configured with security groups and IAM roles
This server uses Streamable HTTP transport (MCP spec 2025-03-26):
- Protocol: Streamable HTTP (the MCP transport mode)
- Implementation: Server-Sent Events (SSE) technology
- Endpoint:
/mcp/messages - Benefits: Real-time streaming, better for long-running operations
- Support: Full Spring MVC with servlet container
- Java 21 or higher
- Gradle 8.x or higher
- AWS CLI configured with appropriate credentials
- EC2 Key Pair for SSH access
- Google Cloud account for OAuth2 credentials (Client ID and Secret)
- GraalVM (optional, for native image compilation)
- jq (optional, for testing scripts)
mcp-server-lambda/
├── src/
│ ├── main/
│ │ ├── java/com/example/mcp/
│ │ │ ├── service/tools/ # MCP tool definitions (@Tool methods)
│ │ │ │ ├── BasicMcpTools.java
│ │ │ │ └── AdvancedMcpTools.java
│ │ │ ├── controller/ # Spring MVC controllers
│ │ │ │ └── AuthController.java
│ │ │ ├── config/ # Spring configuration
│ │ │ │ └── SecurityConfig.java
│ │ │ └── McpServerApplication.java
│ │ └── resources/
│ │ ├── templates/ # Thymeleaf templates
│ │ │ └── index.html
│ │ ├── META-INF/native-image/ # GraalVM configs
│ │ └── application.yml # Application config with OAuth2
│ └── test/
│ └── java/com/example/mcp/
│ └── McpToolsTest.java
├── build.gradle # Gradle build with Spring AI MCP and OAuth2
├── cloudformation-ec2.yaml # CloudFormation template for EC2
├── deploy-ec2.sh # EC2 deployment script
├── build-and-deploy-app.sh # Build and deploy application
├── QUICKSTART.md # Quick start guide
└── README.md
The server comes with several example tools:
-
add - Adds two numbers
- Parameters:
a(number),b(number) - Returns: Result of a + b
- Parameters:
-
multiply - Multiplies two numbers
- Parameters:
a(number),b(number) - Returns: Result of a × b
- Parameters:
-
echo - Echoes back a message
- Parameters:
message(string) - Returns: "Echo: {message}"
- Parameters:
-
calculator - Performs arithmetic operations
- Parameters:
operation(add/subtract/multiply/divide),a(number),b(number) - Returns: Result of the operation
- Parameters:
-
get_current_time - Returns current time in timezone
- Parameters:
timezone(string, default: "UTC") - Returns: Current time in the specified timezone
- Parameters:
For a quick 10-minute setup, see QUICKSTART.md.
# Build the project
./gradlew clean buildThis creates a Spring Boot executable JAR at: build/libs/mcp-server-lambda-1.0.0.jar
For faster startup times (optional):
# Install GraalVM first
# Then run native build
./gradlew nativeCompileThis creates a native executable at: build/native/nativeCompile/mcp-server
- Go to Google Cloud Console
- Create an OAuth 2.0 Client ID
- Note down the Client ID and Client Secret
- You'll update redirect URIs after deployment
aws ec2 create-key-pair --key-name mcp-server-key \
--query 'KeyMaterial' --output text > mcp-server-key.pem
chmod 400 mcp-server-key.pemSet environment variables:
export GOOGLE_CLIENT_ID="your-google-client-id"
export GOOGLE_CLIENT_SECRET="your-google-client-secret"
export KEY_PAIR_NAME="mcp-server-key"Deploy the CloudFormation stack:
./deploy-ec2.sh dev us-east-1This will create:
- VPC with public subnet
- EC2 instance with Java 21
- Security groups for HTTP/HTTPS
- AWS Cognito User Pool with Google identity provider
- OAuth2 configuration
After deployment, add these redirect URIs in Google Cloud Console:
http://[EC2_PUBLIC_DNS]:8080/login/oauth2/code/cognito
http://[EC2_PUBLIC_IP]:8080/login/oauth2/code/cognito
./build-and-deploy-app.sh dev us-east-1This will:
- Build the Spring Boot JAR
- Retrieve Cognito Client Secret
- Upload JAR to EC2
- Configure systemd service
- Start the application
Open your browser and navigate to:
http://[EC2_PUBLIC_DNS]:8080
Click "Login with Google via Cognito" to authenticate.
curl http://[EC2_PUBLIC_IP]:8080/actuator/healthReplace [EC2_PUBLIC_IP] with your EC2 instance's public IP address.
curl -X POST http://[EC2_PUBLIC_IP]:8080/mcp \
-H 'Content-Type: application/json' \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {}
}'curl -X POST http://[EC2_PUBLIC_IP]:8080/mcp \
-H 'Content-Type: application/json' \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/list",
"params": {}
}'curl -X POST http://[EC2_PUBLIC_IP]:8080/mcp \
-H 'Content-Type: application/json' \
-d '{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "add",
"arguments": {
"a": 10,
"b": 5
}
}
}'SSH into EC2 and view logs:
ssh -i mcp-server-key.pem ec2-user@[EC2_PUBLIC_IP]
sudo journalctl -u mcp-server -fWith Spring AI MCP, adding a new tool is as simple as creating a method annotated with @Tool. The method is automatically discovered and registered as an MCP tool.
package com.example.mcp.service.tools;
import org.springframework.ai.mcp.server.Tool;
import org.springframework.ai.mcp.server.ToolParam;
import org.springframework.stereotype.Service;
import java.util.Map;
@Service
public class MyCustomTools {
@Tool(name = "toUppercase", description = """
Converts the provided text to uppercase.
**Use Cases:**
- Format text for headers or titles
- Normalize text case
- Text processing operations
**Returns:**
A map containing:
- text: The original text
- uppercased: The text converted to uppercase
""")
public Map<String, Object> toUppercase(
@ToolParam(description = "The text to convert to uppercase") String text) {
String uppercased = text.toUpperCase();
return Map.of(
"text", text,
"uppercased", uppercased
);
}
@Tool(name = "reverseString", description = """
Reverses the provided text string.
**Use Cases:**
- String manipulation demos
- Text processing operations
- Palindrome checking
**Returns:**
A map containing:
- original: The original text
- reversed: The text reversed
""")
public Map<String, Object> reverseString(
@ToolParam(description = "The text to reverse") String text) {
String reversed = new StringBuilder(text).reverse().toString();
return Map.of(
"original", text,
"reversed", reversed
);
}
}Key Points:
- Use
@Serviceto make the class a Spring component - Use
@Toolannotation on methods to define MCP toolsname: The tool name (how it's called via MCP)description: Detailed description with use cases and return format (supports markdown)
- Use
@ToolParamto describe each parameter - Return
Map<String, Object>with structured results - Spring AI automatically discovers and registers
@Toolmethods as MCP tools - Support rich descriptions with markdown formatting
The tools will be automatically registered and available via the MCP protocol with no additional configuration needed!
The application is configured via environment variables set in the systemd service:
SERVER_PORT- Application port (default: 8080)COGNITO_CLIENT_ID- AWS Cognito User Pool Client IDCOGNITO_CLIENT_SECRET- AWS Cognito User Pool Client SecretCOGNITO_ISSUER_URI- Cognito issuer URI (e.g.,https://cognito-idp.us-east-1.amazonaws.com/us-east-1_XXXXX)COGNITO_USER_POOL_ID- AWS Cognito User Pool IDCOGNITO_DOMAIN- Cognito domain for authenticationAWS_REGION- AWS region (e.g., us-east-1)
Key configuration sections:
spring:
security:
oauth2:
client:
registration:
cognito:
client-id: ${COGNITO_CLIENT_ID}
client-secret: ${COGNITO_CLIENT_SECRET}
scope: [openid, profile, email]
ai:
mcp:
server:
protocol: STREAMABLE # MCP spec 2025-03-26
type: ASYNC # For streaming
sse:
endpoint: "/mcp/messages"- Instance Type: t3.small (or configurable via CloudFormation parameter)
- Runtime: Java 21 (Amazon Corretto)
- Service: systemd service (
mcp-server.service) - Auto-start: Enabled on boot
Native image configuration files are located in:
src/main/resources/META-INF/native-image/reflect-config.jsonsrc/main/resources/META-INF/native-image/resource-config.jsonsrc/main/resources/META-INF/native-image/native-image.properties
- Startup time: ~10-15 seconds
- Memory usage: ~300-400MB
- Response time: <100ms
- JAR size: ~80-100MB
- Startup time: ~1-2 seconds
- Memory usage: ~100-150MB
- Response time: <50ms
- Binary size: ~40-60MB
- Cold start: ~500ms-1s
- Warm execution: <50ms
- Package size: ~30-40MB
Problem: Gradle build fails
# Clean and rebuild
./gradlew clean build --refresh-dependenciesProblem: Native image compilation fails
# Ensure GraalVM is properly installed
java -version # Should show GraalVM
# Check native-image tool
which native-imageProblem: CloudFormation stack creation fails
- Check AWS CloudFormation console for detailed errors
- Verify Google OAuth credentials are correct
- Ensure EC2 key pair exists in the region
Problem: Application won't start on EC2
- SSH into EC2:
ssh -i mcp-server-key.pem ec2-user@[EC2_IP] - Check service status:
sudo systemctl status mcp-server - View logs:
sudo journalctl -u mcp-server -n 100 --no-pager
Problem: OAuth2 login fails
- Verify Google OAuth redirect URIs are configured correctly
- Check Cognito User Pool and Client configuration
- Verify Cognito Client Secret in systemd service file
Problem: Can't connect to EC2
- Verify security group allows inbound traffic on port 8080
- Check EC2 instance is running
- Verify public IP/DNS is correct
Problem: Connection refused
- Verify EC2 instance is running
- Check application is running:
sshinto EC2 and runsudo systemctl status mcp-server - Verify security group allows inbound traffic
Problem: CORS errors
- Check SecurityConfig.java CORS configuration
- Verify allowed origins are configured correctly
SSH into EC2 and view real-time logs:
ssh -i mcp-server-key.pem ec2-user@[EC2_PUBLIC_IP]
sudo journalctl -u mcp-server -fView last 100 log lines:
sudo journalctl -u mcp-server -n 100 --no-pagerRestart application:
ssh -i mcp-server-key.pem ec2-user@[EC2_PUBLIC_IP]
sudo systemctl restart mcp-serverStop application:
sudo systemctl stop mcp-serverCheck service status:
sudo systemctl status mcp-serverConfigure CloudWatch agent on EC2 for centralized logging:
- Agent is pre-installed via UserData script
- Configure log groups in CloudWatch console
EC2 pricing (approximate for t3.small in us-east-1):
- On-Demand: ~$0.0208/hour = ~$15/month
- Data transfer: First 100GB/month free, then $0.09/GB
- Cognito: Free tier: 50,000 MAUs (Monthly Active Users)
- CloudWatch: Basic monitoring free, detailed monitoring $0.14/instance/month
For typical development usage:
- Monthly cost: ~$15-20 (EC2 instance running 24/7)
- Can reduce costs by stopping instance when not needed
- OAuth2 Authentication: All endpoints protected by AWS Cognito
- EC2 Security Groups: Configure to allow only necessary traffic
- SSH Access: Limit SSH access to specific IP ranges
- HTTPS: Configure SSL/TLS with AWS Certificate Manager (recommended for production)
- Secrets: Cognito Client Secret managed via systemd environment variables
- IAM Roles: EC2 instance uses least-privilege IAM role
- VPC: Instance deployed in dedicated VPC with public subnet
To contribute to this project:
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests
- Submit a pull request
This project is licensed under the MIT License.
- Model Context Protocol Documentation
- Spring AI MCP Documentation
- Spring Boot Documentation
- Spring Security OAuth2 Client
- GraalVM Native Image
For issues and questions:
- Open an issue on GitHub
- Check the Troubleshooting section
- View application logs on EC2:
sudo journalctl -u mcp-server -f - Check AWS CloudFormation console for deployment issues