### Docker Best Practices
1. **Multi-stage builds**: Separate build and runtime environments
2. **Minimal base images**: Use Alpine or slim variants
3. **Non-root users**: Run processes as non-privileged users
4. **Version pinning**: Pin specific base image versions
5. **Layer optimization**: Organize Dockerfile to optimize layer caching
6. **Health checks**: Implement container health checks
7. **Resource limits**: Set memory and CPU limits
### npx Best Practices
1. **Specify versions**: Use exact versions for production
2. **Verify packages**: Use the `-y` flag cautiously
3. **Registry security**: Consider using private registries
4. **Cache management**: Be aware of npx caching behavior
5. **Performance**: Consider startup time for frequently used servers
6. **Exit handling**: Implement proper process exit handling
7. **Dependency size**: Keep dependencies minimal
### uv Best Practices
1. **Virtual environments**: Always use dedicated virtual environments
2. **Lockfiles**: Maintain lockfiles for dependency consistency
3. **Offline mode**: Consider offline mode for production
4. **Caching**: Configure appropriate cache locations
5. **Integration**: Ensure proper integration with existing Python tools
6. **Activation scripts**: Handle environment activation properly
7. **Platform compatibility**: Test on all target platforms
# Package Publishing
1. Always validate your package locally before publishing
2. Follow semantic versioning for all releases
3. Clean, build, test, publish, validate installation
## Clean Build Process
When building the package, follow these steps in order:
```bash
# Step 1: Clean previous builds
npm run clean
# Step 2: Build the package
npm run build
# Step 3: Publish to npm registry
npm publish
```
## Clean Installation Validation
After publishing, ALWAYS verify installation from the registry:
```bash
# Step 1: Remove local installation to ensure clean test
rm -rf /home/sparrow/.nvm/versions/node/v23.7.0/lib/node_modules/@sparesparrow/mcp-prompts/
# Step 2: Install globally from registry
npm install -g @sparesparrow/mcp-prompts
# Step 3: Verify execution
npx -y @sparesparrow/mcp-prompts
```
## Version Management
1. Use semantic versioning (MAJOR.MINOR.PATCH)
2. Update version in package.json before publishing:
```json
{
"name": "@sparesparrow/mcp-prompts",
"version": "1.2.3", // Increment appropriately
...
}
```
## Common Publishing Issues
When encountering publishing errors:
1. Check npm token: `npm token list`
2. Verify package name is available: `npm view @sparesparrow/mcp-prompts`
3. Ensure you have proper access rights: `npm access ls-packages`
4. Inspect package contents before publishing: `npm pack`
5. Validate package.json:
- Required fields: name, version, main, types/typings
- Files array must include all necessary files
- Dependencies must use compatible version ranges
## Publishing Process Decision Tree
1. Is this a breaking change?
- Yes → Increment MAJOR version
- No → Continue to next question
2. Does this add new functionality?
- Yes → Increment MINOR version
- No → Continue to next question
3. Is this a bug fix or patch?
- Yes → Increment PATCH version
- No → Consider if release is necessary
## Iterative Delivery Checklist
Before merging feature branches to main:
- [ ] Run clean, build, test cycle
- [ ] Publish to npm
- [ ] Verify global installation
- [ ] Test executed package
- [ ] If all tests pass, proceed with next feature
- [ ] If any tests fail, fix issues before continuing
---
description: "Guidelines for implementing and deploying MCP servers with Server-Sent Events (SSE) transport"
globs:
- "**/*.js"
- "**/*.ts"
- "**/*.py"
- "**/server.js"
- "**/server.ts"
- "**/sse.js"
- "**/sse.ts"
---
# MCP Server Implementation with SSE Transport
This rule provides guidelines for developing, deploying, and managing MCP servers that use Server-Sent Events (SSE) transport instead of the default stdio transport.
## Core Concepts
1. **SSE Transport**: HTTP-based transport mechanism with server-to-client streaming
2. **Client-Server Model**: Network-based client-server communication pattern
3. **Authentication**: Security mechanisms for client validation
4. **Scalability**: Patterns for scaling SSE-based MCP servers
5. **Integration**: Methods for integrating with various clients
## TypeScript Implementation
### Server Setup with Express
```typescript
// src/sse.ts
import express from "express";
import cors from "cors";
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import { setupTools } from "./tools.js";
import { setupResources } from "./resources.js";
import { setupPrompts } from "./prompts.js";
// Create Express app
const app = express();
const PORT = process.env.PORT || 3000;
// Configure CORS
const allowedOrigins = process.env.ALLOWED_ORIGINS
? process.env.ALLOWED_ORIGINS.split(",")
: ["*"];
app.use(cors({
origin: (origin, callback) => {
if (!origin || allowedOrigins.includes("*") || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error("Not allowed by CORS"));
}
}
}));
// Parse JSON bodies
app.use(express.json());
// Create MCP server
const server = new Server({
name: "mcp-server-sse",
version: "1.0.0"
}, {
capabilities: {
resources: { subscribe: true, listChanged: true },
tools: { listChanged: true },
prompts: { listChanged: true }
}
});
// Setup handlers
setupTools(server);
setupResources(server);
setupPrompts(server);
// Setup authentication (optional)
function authenticate(req, res, next) {
const apiKey = req.headers["x-api-key"];
const authType = process.env.AUTH_TYPE || "none";
if (authType === "none") {
return next();
}
if (authType === "api-key" && apiKey === process.env.API_KEY) {
return next();
}
return res.status(401).json({ error: "Unauthorized" });
}
// Set up connection endpoint
app.get("/connect", authenticate, async (req, res) => {
try {
// Initialize SSE transport
const transport = new SSEServerTransport("/messages", res);
// Log connection
console.error(`Client connected: ${req.ip}`);
// Connect server to transport
await server.connect(transport);
} catch (error) {
console.error("Connection error:", error);
res.status(500).end();
}
});
// Set up message endpoint
app.post("/messages", authenticate, express.json(), async (req, res) => {
try {
// Get client ID from request
const clientId = req.headers["x-client-id"];
if (!clientId) {
return res.status(400).json({ error: "Missing client ID" });
}
// Find the transport for this client
const transport = server.getTransport(clientId);
if (!transport) {
return res.status(404).json({ error: "Client not found" });
}
// Handle the message
await transport.handlePostMessage(req, res);
} catch (error) {
console.error("Message handling error:", error);
res.status(500).json({ error: "Internal server error" });
}
});
// Health check endpoint
app.get("/health", (req, res) => {
res.json({ status: "ok" });
});
// Start HTTP server
app.listen(PORT, () => {
console.error(`MCP server listening on port ${PORT}`);
console.error(`Server is using SSE transport`);
console.error(`CORS allowed origins: ${allowedOrigins.join(", ")}`);
});
```
### Client Usage
```typescript
// Client usage example
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
async function connectToServer() {
// Initialize client
const client = new Client(
{ name: "my-client", version: "1.0.0" },
{ capabilities: {} }
);
// Create SSE transport
const transport = new SSEClientTransport({
url: "http://localhost:3000",
connectEndpoint: "/connect",
messageEndpoint: "/messages",
headers: {
"X-API-Key": "your-api-key-here"
}
});
// Connect to server
await client.connect(transport);
// Use the client
const tools = await client.listTools();
console.log("Available tools:", tools);
}
connectToServer().catch(console.error);
```
## Python Implementation
### Server Setup with FastAPI
```python
# src/sse.py
import os
import asyncio
from fastapi import FastAPI, Request, Response, Header, HTTPException, Depends
from fastapi.middleware.cors import CORSMiddleware
from typing import Optional, List
from mcp.server import Server, NotificationOptions
from mcp.server.models import InitializationOptions
from mcp.server.fastapi import add_mcp_routes
# Create FastAPI app
app = FastAPI()
# Configure CORS
allowed_origins = os.getenv("ALLOWED_ORIGINS", "*").split(",")
app.add_middleware(
CORSMiddleware,
allow_origins=allowed_origins,
allow_credentials=True,
allow_methods=["GET", "POST"],
allow_headers=["*"],
)
# Create MCP server
server = Server("mcp-server-sse")
# Setup handlers
from .resources import register_resources
from .tools import register_tools
from .prompts import register_prompts
register_resources(server)
register_tools(server)
register_prompts(server)
# Authentication dependency
async def authenticate(x_api_key: Optional[str] = Header(None)):
auth_type = os.getenv("AUTH_TYPE", "none")
if auth_type == "none":
return True
if auth_type == "api-key":
api_key = os.getenv("API_KEY", "")
if x_api_key == api_key:
return True
raise HTTPException(status_code=401, detail="Unauthorized")
# Add MCP routes with authentication
add_mcp_routes(app, "/mcp", server, dependencies=[Depends(authenticate)])
# Health check endpoint
@app.get("/health")
async def health_check():
return {"status": "ok"}
# Run the server
if __name__ == "__main__":
import uvicorn
port = int(os.getenv("PORT", "3000"))
uvicorn.run("sse:app", host="0.0.0.0", port=port, log_level="info")
```
### Client Usage
```python
# Client usage example
import asyncio
from mcp.client import Client
from mcp.client.http import HttpClientParameters
async def connect_to_server():
# Create client parameters
params = HttpClientParameters(
base_url="http://localhost:3000/mcp",
headers={"X-API-Key": "your-api-key-here"}
)
# Create and initialize client
client = Client()
await client.connect_http(params)
# Use the client
tools = await client.list_tools()
print("Available tools:", tools)
if __name__ == "__main__":
asyncio.run(connect_to_server())
```
## Deployment Options
### 1. Docker with Nginx
```dockerfile
# Dockerfile for SSE transport
FROM node:18-alpine AS build
WORKDIR /app
# Install dependencies
COPY package*.json ./
RUN npm ci
# Copy and build source
COPY tsconfig.json ./
COPY src ./src
RUN npm run build
# Production image
FROM node:18-alpine
WORKDIR /app
# Copy package files and build output
COPY --from=build /app/package*.json ./
COPY --from=build /app/build ./build
# Install production dependencies
RUN npm ci --omit=dev
# Use non-root user for security
USER node
# Expose port
EXPOSE 3000
# Start SSE server
CMD ["node", "build/sse.js"]
```
```nginx
# nginx.conf
server {
listen 443 ssl;
server_name mcp.example.com;
ssl_certificate /etc/nginx/ssl/server.crt;
ssl_certificate_key /etc/nginx/ssl/server.key;
# Enable HTTP/2
http2 on;
# SSL configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# Headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Proxy settings for SSE
location / {
proxy_pass http://mcp-server:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# SSE specific settings
proxy_set_header Connection "";
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 86400s;
}
}
```
### 2. Kubernetes Deployment
```yaml
# kubernetes/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: mcp-server-sse
labels:
app: mcp-server-sse
spec:
replicas: 2
selector:
matchLabels:
app: mcp-server-sse
template:
metadata:
labels:
app: mcp-server-sse
spec:
containers:
- name: mcp-server
image: ghcr.io/your-org/mcp-server-sse:v1.0.0
ports:
- containerPort: 3000
env:
- name: PORT
value: "3000"
- name: AUTH_TYPE
value: "api-key"
- name: API_KEY
valueFrom:
secretKeyRef:
name: mcp-server-secrets
key: api-key
- name: ALLOWED_ORIGINS
value: "https://app1.example.com,https://app2.example.com"
resources:
limits:
cpu: "1"
memory: "512Mi"
requests:
cpu: "0.2"
memory: "256Mi"
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: mcp-server-sse
spec:
selector:
app: mcp-server-sse
ports:
- port: 80
targetPort: 3000
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: mcp-server-ingress
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
spec:
rules:
- host: mcp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: mcp-server-sse
port:
number: 80
tls:
- hosts:
- mcp.example.com
secretName: mcp-tls-cert
```
### 3. AWS Lambda Deployment
```typescript
// src/lambda.ts
import serverless from "serverless-http";
import express from "express";
import cors from "cors";
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import { setupTools } from "./tools.js";
import { setupResources } from "./resources.js";
import { setupPrompts } from "./prompts.js";
// Create Express app
const app = express();
// Configure CORS
app.use(cors({
origin: process.env.ALLOWED_ORIGINS?.split(",") || "*"
}));
// Parse JSON bodies
app.use(express.json());
// Create MCP server
const server = new Server({
name: "mcp-server-lambda",
version: "1.0.0"
}, {
capabilities: {
resources: { subscribe: true },
tools: {},
prompts: {}
}
});
// Setup handlers
setupTools(server);
setupResources(server);
setupPrompts(server);
// Authentication middleware
function authenticate(req, res, next) {
const apiKey = req.headers["x-api-key"];
if (process.env.AUTH_TYPE === "api-key" && apiKey !== process.env.API_KEY) {
return res.status(401).json({ error: "Unauthorized" });
}
next();
}
// Set up connection endpoint
app.get("/connect", authenticate, async (req, res) => {
const transport = new SSEServerTransport("/messages", res);
await server.connect(transport);
});
// Set up message endpoint
app.post("/messages", authenticate, async (req, res) => {
const clientId = req.headers["x-client-id"];
const transport = server.getTransport(clientId);
if (!transport) {
return res.status(404).json({ error: "Client not found" });
}
await transport.handlePostMessage(req, res);
});
// Health check endpoint
app.get("/health", (req, res) => {
res.json({ status: "ok" });
});
// Export the serverless handler
export const handler = serverless(app);
```
```yaml
# serverless.yml
service: mcp-server-sse
provider:
name: aws
runtime: nodejs18.x
region: us-east-1
memorySize: 256
timeout: 30
environment:
AUTH_TYPE: ${env:AUTH_TYPE, 'api-key'}
API_KEY: ${env:API_KEY, ''}
ALLOWED_ORIGINS: ${env:ALLOWED_ORIGINS, '*'}
functions:
api:
handler: build/lambda.handler
events:
- httpApi:
path: /connect
method: get
- httpApi:
path: /messages
method: post
- httpApi:
path: /health
method: get
```
## Claude Desktop Integration
### Configuration for SSE Transport
```json
{
"mcpServers": {
"mcp-server-sse": {
"transport": "sse",
"url": "http://localhost:3000",
"connectEndpoint": "/connect",
"messageEndpoint": "/messages",
"headers": {
"X-API-Key": "your-api-key-here"
}
}
}
}
```
### Alternative: Proxy Command
```json
{
"mcpServers": {
"mcp-server-sse": {
"command": "curl",
"args": [
"-X", "POST",
"-H", "Content-Type: application/json",
"-H", "X-API-Key: your-api-key-here",
"-d", "{\"clientId\": \"claude-desktop\"}",
"http://localhost:3000/connect"
]
}
}
}
```
## Authentication Methods
### 1. API Key Authentication
```typescript
// Middleware for API key authentication
function apiKeyAuth(req, res, next) {
const apiKey = req.headers["x-api-key"];
if (!apiKey || apiKey !== process.env.API_KEY) {
return res.status(401).json({ error: "Invalid API key" });
}
next();
}
// Apply to routes
app.get("/connect", apiKeyAuth, async (req, res) => {
// Handler code
});
app.post("/messages", apiKeyAuth, async (req, res) => {
// Handler code
});
```
### 2. JWT Authentication
```typescript
import jwt from "jsonwebtoken";
// Middleware for JWT authentication
function jwtAuth(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith("Bearer ")) {
return res.status(401).json({ error: "Missing authorization header" });
}
const token = authHeader.split(" ")[1];
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
return res.status(401).json({ error: "Invalid token" });
}
}
// Apply to routes
app.get("/connect", jwtAuth, async (req, res) => {
// Handler code
});
app.post("/messages", jwtAuth, async (req, res) => {
// Handler code
});
```
### 3. OAuth 2.0 Integration
```typescript
import axios from "axios";
// Middleware for OAuth 2.0 authentication
async function oauthAuth(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith("Bearer ")) {
return res.status(401).json({ error: "Missing authorization header" });
}
const token = authHeader.split(" ")[1];
try {
// Validate token with OAuth provider
const response = await axios.get(process.env.OAUTH_INTROSPECTION_URL, {
headers: {
Authorization: `Bearer ${process.env.OAUTH_CLIENT_SECRET}`
},
params: {
token
}
});
if (!response.data.active) {
return res.status(401).json({ error: "Invalid token" });
}
req.user = response.data;
next();
} catch (error) {
return res.status(401).json({ error: "Token validation failed" });
}
}
// Apply to routes
app.get("/connect", oauthAuth, async (req, res) => {
// Handler code
});
app.post("/messages", oauthAuth, async (req, res) => {
// Handler code
});
```
## Scaling Considerations
### 1. Connection Management
```typescript
// Store active connections in memory or distributed cache
const connections = new Map();
app.get("/connect", authenticate, async (req, res) => {
const clientId = req.headers["x-client-id"] || generateId();
// Initialize SSE transport
const transport = new SSEServerTransport("/messages", res);
// Store connection
connections.set(clientId, {
transport,
lastSeen: Date.now()
});
// Set up cleanup on disconnect
res.on("close", () => {
connections.delete(clientId);
console.error(`Client disconnected: ${clientId}`);
});
// Connect server to transport
await server.connect(transport);
});
```
### 2. Load Balancing
```nginx
# nginx load balancing
upstream mcp_servers {
# Simple round-robin
server mcp-server-1:3000;
server mcp-server-2:3000;
# With sticky sessions (recommended)
# ip_hash;
# or
# sticky cookie srv_id expires=1h domain=.example.com path=/;
}
server {
listen 443 ssl;
server_name mcp.example.com;
# SSL configuration
ssl_certificate /etc/nginx/ssl/server.crt;
ssl_certificate_key /etc/nginx/ssl/server.key;
location / {
proxy_pass http://mcp_servers;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
# SSE specific settings
proxy_set_header Connection "";
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 86400s;
}
}
```
### 3. Horizontal Scaling with Redis
```typescript
import express from "express";
import Redis from "ioredis";
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
// Create Redis clients
const redis = new Redis(process.env.REDIS_URL);
const redisSub = new Redis(process.env.REDIS_URL);
// Create Express app
const app = express();
// Create MCP server
const server = new Server({
name: "mcp-server-sse",
version: "1.0.0"
}, {
capabilities: {
resources: { subscribe: true },
tools: {},
prompts: {}
}
});
// Set up connection endpoint
app.get("/connect", authenticate, async (req, res) => {
const clientId = req.headers["x-client-id"] || generateId();
const instanceId = process.env.INSTANCE_ID || generateId();
// Initialize SSE transport
const transport = new SSEServerTransport("/messages", res);
// Store client-instance mapping in Redis
await redis.set(`client:${clientId}`, instanceId);
// Set up cleanup on disconnect
res.on("close", async () => {
await redis.del(`client:${clientId}`);
console.error(`Client disconnected: ${clientId}`);
});
// Connect server to transport
await server.connect(transport);
});
// Set up message endpoint
app.post("/messages", authenticate, async (req, res) => {
const clientId = req.headers["x-client-id"];
if (!clientId) {
return res.status(400).json({ error: "Missing client ID" });
}
// Check if this instance handles this client
const instanceId = await redis.get(`client:${clientId}`);
const currentInstanceId = process.env.INSTANCE_ID || generateId();
if (instanceId !== currentInstanceId) {
// Forward to the correct instance via Redis pub/sub
await redis.publish("message-forward", JSON.stringify({
clientId,
body: req.body
}));
return res.status(200).json({ forwarded: true });
}
// Handle message locally
const transport = server.getTransport(clientId);
if (!transport) {
return res.status(404).json({ error: "Client not found" });
}
await transport.handlePostMessage(req, res);
});
// Subscribe to forwarded messages
redisSub.subscribe("message-forward");
redisSub.on("message", async (channel, message) => {
if (channel === "message-forward") {
const { clientId, body } = JSON.parse(message);
const transport = server.getTransport(clientId);
if (transport) {
// Create a mock request and response
const mockReq = { body, headers: { "x-client-id": clientId } };
const mockRes = {
status: () => ({ json: () => {} }),
json: () => {}
};
await transport.handlePostMessage(mockReq, mockRes);
}
}
});
```
## Best Practices
### 1. Security Guidelines
1. **TLS Encryption**: Always use HTTPS in production
2. **Authentication**: Implement proper authentication
3. **CORS Configuration**: Restrict allowed origins
4. **Rate Limiting**: Protect against DoS attacks
5. **Input Validation**: Validate all client input
6. **Content Security Policy**: Implement appropriate CSP headers
7. **Request Logging**: Log authentication and access attempts
8. **Dependency Management**: Keep dependencies updated for security patches
### 2. Performance Optimization
1. **Connection Pooling**: Reuse database connections
2. **Response Compression**: Enable gzip/brotli compression
3. **Caching**: Implement appropriate cache headers
4. **Keep-Alive**: Configure proper keep-alive settings
5. **HTTP/2**: Enable HTTP/2 for multiplexing
6. **Memory Management**: Monitor and limit memory usage
7. **CPU Optimization**: Profile and optimize CPU-intensive operations
8. **Connection Timeouts**: Implement appropriate timeouts
### 3. Monitoring and Debugging
1. **Health Endpoints**: Implement comprehensive health checks
2. **Metrics**: Collect usage and performance metrics
3. **Logging**: Implement structured logging
4. **Error Tracking**: Capture and report errors
5. **Tracing**: Implement distributed tracing for requests
6. **Alerting**: Set up alerts for critical issues
7. **Documentation**: Maintain clear documentation for operation
8. **Debugging Tools**: Implement diagnostic endpoints for troubleshooting
## Comparative Analysis
| Feature | stdio Transport | SSE Transport |
|---------------------------|--------------------------|--------------------------|
| **Network Model** | Local process | Client-server over HTTP |
| **Connection Scope** | Single machine | Network-wide |
| **Security** | Process isolation | HTTP security mechanisms |
| **Scalability** | Limited to local process | Horizontally scalable |
| **Client Compatibility** | Local clients only | Any HTTP client |
| **Authentication** | OS-level | Application-level |
| **Deployment Options** | Limited | Flexible |
| **Load Balancing** | Not applicable | Fully supported |
| **Development Complexity**| Lower | Higher |
| **Resource Usage** | Lower | Higher |
## Decision Criteria
Choose SSE transport when:
1. You need to serve multiple clients simultaneously
2. Clients are located on different machines
3. You need to scale horizontally
4. You need to deploy behind load balancers
5. You want to leverage HTTP-based security mechanisms
6. You need cross-platform compatibility
7. You're implementing a public-facing API
Choose stdio transport when:
1. You're running on a single machine
2. You have a 1:1 relationship between server and client
3. You need minimal latency
4. You have simpler security requirements
5. You want the simplest implementation
6. You're integrating with local tooling
7. You want to minimize resource usage
# MCP-Prompts Project Guidelines
## Project Overview
MCP-Prompts provides a focused, efficient prompt management system built on MCP server standards. The project follows a modular architecture pattern with clear separation of concerns and leverages existing MCP servers rather than reimplementing functionality.
## Core Architecture Principles
### 1. Minimalist Implementation
Follow these principles for a streamlined codebase:
- Implement only what's necessary to achieve core functionality
- Reuse existing MCP servers and clients wherever possible
- Maintain a flat, consistent file structure
- Limit the number of dependencies
### 2. Unified Type System
Maintain a single source of truth for type definitions:
```typescript
// src/interfaces.ts - Single location for all type definitions
export interface Prompt {
id: string;
name: string;
description?: string;
content: string;
isTemplate: boolean;
variables?: string[];
tags?: string[];
category?: string;
createdAt: string;
updatedAt: string;
version: number;
metadata?: Record<string, any>;
}
export interface TemplateVariables {
[key: string]: string | number | boolean;
}
export interface ListPromptsOptions {
tag?: string;
isTemplate?: boolean;
category?: string;
search?: string;
sort?: string;
order?: 'asc' | 'desc';
limit?: number;
offset?: number;
}
export interface ApplyTemplateResult {
content: string;
originalPrompt: Prompt;
appliedVariables: TemplateVariables;
missingVariables?: string[];
}
// Additional types as needed...
```
### 3. Adapter Pattern for MCP Integration
Use a consistent adapter approach for all MCP server integrations:
```typescript
// src/adapters.ts
export interface MCPAdapter<T> {
connect(): Promise<void>;
disconnect(): Promise<void>;
isConnected(): boolean;
// Type-specific methods defined in derived interfaces
}
export interface StorageAdapter extends MCPAdapter<Prompt> {
getPrompt(id: string): Promise<Prompt>;
savePrompt(prompt: Prompt): Promise<void>;
listPrompts(options?: ListPromptsOptions): Promise<Prompt[]>;
deletePrompt(id: string): Promise<void>;
}
// Implementation example
export class FilesystemAdapter implements StorageAdapter {
// Implementation details...
}
```
## Implementation Guidelines
### 1. MCP Tool Implementation
Implement MCP tools following this pattern:
```typescript
// src/index.ts
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { z } from "zod";
export function setupPromptTools(server: Server, promptService: PromptService) {
// Add prompt
server.tool(
"add_prompt",
{
prompt: z.object({
name: z.string(),
description: z.string().optional(),
content: z.string(),
isTemplate: z.boolean().default(false),
tags: z.array(z.string()).optional(),
category: z.string().optional()
})
},
async ({ prompt }) => {
try {
const result = await promptService.addPrompt(prompt);
return {
content: [{
type: "text",
text: `Prompt added successfully with ID: ${result.id}`
}]
};
} catch (error) {
return {
isError: true,
content: [{
type: "text",
text: `Error adding prompt: ${error.message}`
}]
};
}
}
);
// Get prompt
server.tool(
"get_prompt",
{
id: z.string()
},
async ({ id }) => {
try {
const prompt = await promptService.getPrompt(id);
return {
content: [{
type: "text",
text: JSON.stringify(prompt, null, 2)
}]
};
} catch (error) {
return {
isError: true,
content: [{
type: "text",
text: `Error retrieving prompt: ${error.message}`
}]
};
}
}
);
// Additional tools (list_prompts, apply_template, etc.)...
}
```
### 2. Resource Implementation
Implement MCP resources following this pattern:
```typescript
// src/resources.ts
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/resources.js";
export function setupPromptResources(server: Server, promptService: PromptService) {
// Define prompt resource
server.resource(
"prompt",
new ResourceTemplate("prompt://{id}", { list: "prompt://" }),
async (uri, { id }) => {
const prompt = await promptService.getPrompt(id);
return {
contents: [{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify(prompt, null, 2)
}]
};
}
);
// Define templates resource
server.resource(
"templates",
new ResourceTemplate("templates://", { list: undefined }),
async (uri) => {
const templates = await promptService.listPrompts({ isTemplate: true });
return {
contents: [{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify(templates, null, 2)
}]
};
}
);
// Additional resources as needed...
}
```
### 3. Utility Functions
Keep utility functions focused and minimal:
```typescript
// src/index.ts
import { Prompt, TemplateVariables, ApplyTemplateResult } from "../core/types";
export function applyTemplate(prompt: Prompt, variables: TemplateVariables): ApplyTemplateResult {
if (!prompt.isTemplate) {
return {
content: prompt.content,
originalPrompt: prompt,
appliedVariables: {}
};
}
// Extract variables from template
const templateVariables = extractVariables(prompt.content);
const missingVariables = templateVariables.filter(v => !variables[v]);
// Apply substitution
let content = prompt.content;
for (const [key, value] of Object.entries(variables)) {
const regex = new RegExp(`\\{\\{\\s*${key}\\s*\\}\\}`, 'g');
content = content.replace(regex, String(value));
}
return {
content,
originalPrompt: prompt,
appliedVariables: variables,
missingVariables: missingVariables.length > 0 ? missingVariables : undefined
};
}
export function extractVariables(content: string): string[] {
const matches = content.match(/\{\{([^}]+)\}\}/g) || [];
return matches
.map(match => match.replace(/\{\{|\}\}/g, '').trim())
.filter((v, i, a) => a.indexOf(v) === i); // Remove duplicates
}
// Additional focused utility functions as needed...
```
### 4. Main Server Setup
Implement a clean, straightforward server setup:
```typescript
// src/index.ts
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { PromptService } from "./services/prompt-service.js";
import { setupPromptTools } from "./tools/prompt-tools.js";
import { setupPromptResources } from "./resources/prompt-resources.js";
import { createStorageAdapter } from "./adapters/storage-adapter.js";
async function main() {
try {
// Initialize server
const server = new Server({
name: "mcp-prompts",
version: "1.0.0"
}, {
capabilities: {
resources: { subscribe: false },
tools: { listChanged: false },
prompts: { listChanged: false }
}
});
// Initialize storage adapter based on configuration
const storageAdapter = createStorageAdapter(process.env.STORAGE_TYPE || "file");
await storageAdapter.connect();
// Initialize service
const promptService = new PromptService(storageAdapter);
// Set up tools and resources
setupPromptTools(server, promptService);
setupPromptResources(server, promptService);
// Start server
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("MCP Prompts server started");
} catch (error) {
console.error("Failed to start server:", error);
process.exit(1);
}
}
main();
```
## Docker Integration
Use a simple Docker setup focused on the core functionality:
```dockerfile
# Dockerfile
FROM node:18-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY tsconfig.json ./
COPY src ./src
RUN npm run build
FROM node:18-alpine
WORKDIR /app
COPY --from=build /app/package*.json ./
COPY --from=build /app/build ./build
RUN npm ci --omit=dev
RUN mkdir -p /app/prompts
ENV NODE_ENV=production
ENV STORAGE_TYPE=file
ENV PROMPTS_DIR=/app/prompts
ENTRYPOINT ["node", "build/index.js"]
```
```yaml
# docker-compose.yml
version: '3.8'
services:
mcp-prompts:
build: .
volumes:
- ./prompts:/app/prompts
environment:
- STORAGE_TYPE=file
- PROMPTS_DIR=/app/prompts
stdin_open: true
tty: true
```
## Testing Approach
Implement a focused testing strategy:
```typescript
// tests/prompt-service.test.ts
import { PromptService } from '../services/prompt-service';
import { MockStorageAdapter } from './mocks/mock-storage-adapter';
describe('PromptService', () => {
let service: PromptService;
let mockAdapter: MockStorageAdapter;
beforeEach(() => {
mockAdapter = new MockStorageAdapter();
service = new PromptService(mockAdapter);
});
test('should get prompt by id', async () => {
const testPrompt = {
id: 'test-1',
name: 'Test Prompt',
content: 'Test content',
isTemplate: false,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
version: 1
};
mockAdapter.getPrompt.mockResolvedValue(testPrompt);
const result = await service.getPrompt('test-1');
expect(result).toEqual(testPrompt);
expect(mockAdapter.getPrompt).toHaveBeenCalledWith('test-1');
});
// Additional tests...
});
```
## Claude Desktop Integration
Create a simple integration with Claude Desktop:
```json
// claude_desktop_config.json
{
"mcpServers": {
"mcp-prompts": {
"command": "node",
"args": ["/path/to/mcp-prompts/build/index.js"],
"env": {
"STORAGE_TYPE": "file",
"PROMPTS_DIR": "/path/to/prompts"
}
}
}
}
```
## Best Practices
1. **Keep it Simple**: Implement the minimal functionality needed to meet requirements
2. **Consistent Naming**: Use clear, consistent naming conventions across the codebase
3. **Focused Modules**: Each module should have a single responsibility
4. **Leverage Existing Tools**: Use existing MCP servers instead of reimplementing functionality
5. **Error Handling**: Implement comprehensive error handling for a robust user experience
6. **Documentation**: Document all public interfaces and configuration options
7. **Testing**: Write tests for core functionality to ensure reliability
8. **Type Safety**: Use TypeScript's type system to prevent errors
9. **Minimal Dependencies**: Keep external dependencies to a minimum