AWS CodePipeline MCP Server
by cuongdev
Verified
# MCP Server for AWS CodePipeline - Beginner's Guide
## 1. Project Overview
### What is an MCP Server?
A Model Context Protocol (MCP) server creates a bridge between Cascade (the AI assistant in Windsurf IDE) and external services like AWS. Think of it as a translator that converts natural language requests into API calls to specific services.
**Example**: When you ask Cascade "List all my CodePipeline pipelines," the MCP server translates this into an AWS API call and returns the results.
### Goals
- Create a Model Context Protocol (MCP) server that allows Cascade to interact with AWS CodePipeline
- Enable natural language control of pipeline operations through Windsurf
- Provide a secure and efficient bridge between the Windsurf IDE and AWS services
### What You'll Build
- A server that can list and manage AWS CodePipeline resources
- Functionality to view pipeline states, executions, and details
- Capability to trigger pipeline executions, approvals, and other operations
- Proper authentication and error handling mechanisms
### Key Technologies
- **TypeScript**: Used for type-safe development (don't worry if you're new to it!)
- **Node.js**: Runtime environment for JavaScript
- **Express**: Simple HTTP server framework
- **AWS SDK**: Library to interact with AWS services
- **Model Context Protocol SDK**: Framework for MCP implementation
- **ES Modules**: Modern JavaScript module system
### Visual Overview
```
User → Windsurf → Cascade → MCP Server → AWS CodePipeline
↑ |
| |
└─────────────── Results ────────────────┘
```
## 2. Getting Started: Step by Step
### Setting Up Your Project
1. **Create a new project folder**
```bash
mkdir my-mcp-server
cd my-mcp-server
```
2. **Initialize Node.js project**
```bash
npm init -y
```
3. **Install dependencies**
```bash
npm install @modelcontextprotocol/sdk express aws-sdk cors body-parser dotenv
npm install --save-dev typescript @types/express @types/node @types/cors @types/body-parser
```
4. **Create TypeScript configuration**
Create a file named `tsconfig.json`:
```json
{
"compilerOptions": {
"target": "ES2020",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"outDir": "./dist",
"strict": true
},
"include": ["src/**/*"]
}
```
5. **Add scripts to package.json**
Update your `package.json` to include:
```json
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "ts-node src/index.ts"
}
```
### Folder Structure Overview
Create this folder structure step by step:
```
my-mcp-server/
├── src/
│ ├── config/ # Server configuration
│ ├── controllers/ # HTTP request handlers
│ ├── routes/ # API routes
│ ├── services/ # Business logic & AWS API calls
│ ├── utils/ # Helper functions
│ ├── types/ # TypeScript interfaces
│ ├── index.ts # Entry point
│ └── mcp-server.ts # MCP implementation
├── .env # Environment variables
├── .gitignore # Git ignore rules
└── package.json # Project config
```
**Pro Tip**: Don't worry about creating all files at once. We'll build them one by one as we progress.
### How the Files Work Together
```
┌─────────────┐ ┌──────────────┐ ┌────────────┐ ┌─────────┐
│ index.ts │────▶│ mcp-server.ts│────▶│ controllers │────▶│services │────▶ AWS
└─────────────┘ └──────────────┘ └────────────┘ └─────────┘
▲ ▲ ▲ ▲
│ │ │ │
└─ Starts both ◀────┘ │ │
servers Uses interfaces ────┘ │
from types/ Calls methods ───┘
```
## 3. Building Your First MCP Server Components
### Core Concepts Made Simple
Let's break down each key component with simple explanations and examples:
### Step 1: Define Your Environment Helper
Create `src/utils/env.ts` to load environment variables:
```typescript
// src/utils/env.ts
import dotenv from 'dotenv';
import path from 'path';
export function loadEnv(): void {
// Load .env file if it exists
dotenv.config();
console.log('Environment variables loaded');
}
// Helper to get environment variables
export function getEnv(key: string, defaultValue: string = ''): string {
return process.env[key] || defaultValue;
}
```
### Step 2: Create Server Configuration
Create `src/config/server-config.ts` to define your server's metadata:
```typescript
// src/config/server-config.ts
export const serverConfig = {
name: "my-aws-mcp-server",
version: "1.0.0",
displayName: "My AWS MCP Server",
description: "MCP server for interacting with AWS services",
publisher: "Your Name",
license: "MIT"
};
```
### Step 3: Define Your AWS Service
Create `src/services/aws-service.ts` to handle AWS SDK calls:
```typescript
// src/services/aws-service.ts
import AWS from 'aws-sdk';
import { getEnv } from '../utils/env.js';
export class AWSService {
private awsService: AWS.Service;
constructor() {
const region = getEnv('AWS_REGION', 'us-west-2');
// Initialize the specific AWS SDK service you need
// For example, for S3:
this.awsService = new AWS.S3({ region });
console.log(`AWS service initialized with region: ${region}`);
}
// Add methods for each AWS operation
// Example for S3 listBuckets:
async listItems() {
try {
// Replace this with your specific AWS service call
const result = await this.awsService.listBuckets().promise();
return result;
} catch (error) {
console.error('Error listing items:', error);
throw error;
}
}
}
```
### Step 4: Create a Controller
Create `src/controllers/aws-controller.ts` to handle HTTP requests:
```typescript
// src/controllers/aws-controller.ts
import { Request, Response } from 'express';
import { AWSService } from '../services/aws-service.js';
export class AWSController {
private awsService: AWSService;
constructor() {
this.awsService = new AWSService();
}
// Example controller method
listItems = async (req: Request, res: Response): Promise<void> => {
try {
const items = await this.awsService.listItems();
res.status(200).json({ items });
} catch (error) {
console.error('Error in listItems controller:', error);
res.status(500).json({ error: 'Failed to list items' });
}
};
}
```
### Step 5: Define Routes
Create `src/routes/aws-routes.ts` to define API endpoints:
```typescript
// src/routes/aws-routes.ts
import { Router } from 'express';
import { AWSController } from '../controllers/aws-controller.js';
const router = Router();
const awsController = new AWSController();
// Define routes
router.get('/items', awsController.listItems);
export default router;
```
### Understanding MCP Interfaces
Here are the key interfaces you'll use, simplified:
#### Tool Interface
```typescript
// This is what Cascade sends to your MCP server
interface Tool {
name: string; // The name of the tool (e.g., "list_buckets")
parameters: { // The parameters the user provided
[key: string]: any // E.g., { "region": "us-west-2" }
};
}
```
#### Tool Definition Interface
```typescript
// This tells Cascade what tools your MCP server provides
interface ToolDefinition {
name: string; // Tool name (e.g., "list_buckets")
description: string; // Human-readable description
parameters: { // What parameters this tool accepts
type: "object",
properties: { // Define each parameter
paramName: {
type: "string", // Parameter type
description: "What this parameter does"
}
// More parameters...
}
}
}
```
## 4. Creating the MCP Server Implementation
### Step 6: Implement the MCP Server
Create `src/mcp-server.ts` - this is the heart of your MCP implementation:
```typescript
// src/mcp-server.ts
import { Server as BaseMCPServer } from "@modelcontextprotocol/sdk/server/index.js";
// Define your tools
const toolDefinitions = [
{
name: "list_items", // Tool name for Cascade to call
description: "List all items", // Human-readable description
parameters: {
type: "object",
properties: {}
}
},
// Add more tool definitions here
];
// Create the MCP server class
export class MCPServer extends BaseMCPServer {
constructor() {
super();
this.registerTools();
}
private registerTools() {
// Register each tool with its handler
this.router.register("list_items", this.handleTool.bind(this));
// Register more tools here
}
// Handle tool execution
async handleTool(tool) {
try {
// Call your API server
const response = await fetch(`http://localhost:3000/api/${tool.name.replace('_', '/')}`, {
method: "GET",
headers: { 'Content-Type': 'application/json' }
});
const data = await response.json();
// Return success response
return {
status: 'success',
result: data
};
} catch (error) {
console.error(`Error executing tool ${tool.name}:`, error);
// Return error response
return {
status: 'error',
error: {
code: 'EXECUTION_ERROR',
message: `Failed to execute tool: ${error.message}`
}
};
}
}
// Return tool definitions to the MCP framework
getToolDefinitions() {
return toolDefinitions;
}
}
```
### Step 7: Create the Main Entry Point
Create `src/index.ts` to start both servers:
```typescript
// src/index.ts
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { loadEnv, getEnv } from './utils/env.js';
import express from 'express';
import bodyParser from 'body-parser';
import cors from 'cors';
import { MCPServer } from './mcp-server.js';
import awsRoutes from './routes/aws-routes.js';
// Load environment variables
loadEnv();
// Create and start the MCP server
const server = new MCPServer();
const transport = new StdioServerTransport();
server.connect(transport);
// Create and start the HTTP server
const app = express();
const PORT = parseInt(getEnv('PORT', '3000'));
// Configure middleware
app.use(cors());
app.use(bodyParser.json());
// Configure routes
app.use('/api', awsRoutes);
// Health check route
app.get('/health', (req, res) => {
res.status(200).json({ status: 'ok' });
});
// Start the HTTP server
app.listen(PORT, () => {
console.log(`HTTP server listening on port ${PORT}`);
});
console.log("MCP Server started successfully");
```
### How the Processing Flow Works (Made Simple)
```
1. User: "List my AWS items"
↓
2. Cascade understands and calls your tool "list_items"
↓
3. MCP Server receives the request through StdioServerTransport
↓
4. MCPServer.handleTool() makes an HTTP request to your Express server
↓
5. Express routes the request to your controller
↓
6. Controller calls your service
↓
7. Service makes AWS API call
↓
8. Results travel back up the chain to the user
```
**Key Point**: Your MCP server actually has TWO servers running:
1. The MCP protocol server (communicates with Cascade)
2. An HTTP server (handles API requests from the MCP server)
## 5. Final Steps and Troubleshooting
### Step 8: Configure Environment Variables
Create a `.env` file in your project root:
```
PORT=3000
AWS_REGION=us-west-2
AWS_ACCESS_KEY_ID=your_access_key_here
AWS_SECRET_ACCESS_KEY=your_secret_key_here
```
### Step 9: Create a .gitignore File
Create a `.gitignore` file to prevent sensitive information from being committed:
```
node_modules/
dist/
.env
*.log
.DS_Store
```
### Step 10: Build and Run Your Server
```bash
# Build your TypeScript code
npm run build
# Start your server
npm start
```
### Step 11: Configure Windsurf
Update your Windsurf MCP configuration (typically in `~/.codeium/windsurf/mcp_config.json`):
```json
{
"mcpServers": {
"your-service-name": {
"command": "npx",
"args": [
"-y",
"path/to/your-mcp-server/dist/index.js"
],
"env": {
"AWS_REGION": "your-region",
"AWS_ACCESS_KEY_ID": "your-access-key",
"AWS_SECRET_ACCESS_KEY": "your-secret-key"
}
}
}
}
```
### Adding New AWS Operations (Simple Steps)
1. **Add a service method**: Create a new method in your service class
2. **Add a controller method**: Create a handler in your controller
3. **Add a route**: Connect the controller to an API endpoint
4. **Add a tool definition**: Tell Cascade about your new capability
5. **Register the tool handler**: Connect the tool to your implementation
### Common Issues and Solutions
#### 1. "Cannot find module" errors
**Problem**: TypeScript can't find imported modules
**Solution**:
- Make sure you're using `.js` extensions in imports (ES modules requirement)
- Check that the module is installed in package.json
#### 2. AWS SDK errors
**Problem**: AWS operations fail with authentication errors
**Solution**:
- Verify your AWS credentials in the .env file
- Check that the region is correct
- Ensure proper IAM permissions for the AWS operations
#### 3. MCP Server not communicating with Windsurf
**Problem**: Cascade can't access your tools
**Solution**:
- Check the path to your index.js file in mcp_config.json
- Verify that you're returning proper tool definitions
- Make sure both your Express and MCP servers are running
#### 4. "TypeError: Cannot read property of undefined"
**Problem**: Trying to access properties that don't exist
**Solution**:
- Use optional chaining (`?.`) when accessing nested properties
- Add null checks before accessing properties
- Add console.log statements to debug object structures
### What Next?
Once you have your basic MCP server working:
1. **Expand functionality**: Add more AWS operations
2. **Refine error handling**: Provide detailed error messages
3. **Add validation**: Validate inputs before processing
4. **Implement testing**: Add unit and integration tests
5. **Improve documentation**: Add comments and API docs
---
By following this beginner-friendly guide, you can create your own MCP server for any AWS service. Start with something simple, get it working, and then gradually expand its capabilities. Good luck with your MCP development journey!