Development Guide

Guide for developers who want to contribute to or fork the Strava MCP Server.

Table of contents

  1. Quick Start
    1. Prerequisites
    2. Local Setup
    3. Get Strava Refresh Token
    4. Local Development Server
  2. Architecture
    1. System Overview
    2. Component Architecture
  3. Project Structure
  4. Key Components
    1. 1. Lambda Handler (src/lambda-web.ts)
    2. 2. Strava Client (src/lib/strava-client.ts)
    3. 3. MCP Tools (src/tools/*.ts)
    4. 4. Deployment System
  5. Adding a New Tool
    1. Step 1: Create Tool File
    2. Step 2: Register Tool
    3. Step 3: Add Types
    4. Step 4: Document Tool
  6. Development Workflow
    1. Local Development
    2. Testing Lambda Locally
    3. Build and Deploy
  7. Important Patterns
    1. OAuth Token Management
    2. Error Handling
    3. Logging Convention
  8. Testing
    1. Unit Tests
    2. Integration Testing
    3. Manual Testing Checklist
  9. Common Issues
    1. “sam: command not found”
    2. “Unable to locate credentials”
    3. OAuth Token Expired
    4. TypeScript Errors
  10. Code Style
    1. TypeScript
    2. Naming Conventions
    3. Documentation
  11. Deployment Best Practices
    1. Environment Variables
    2. Security
    3. Performance
  12. Contributing
  13. Resources

Quick Start

Prerequisites

Local Setup

# Clone the repository
git clone https://github.com/Stealinglight/StravaMCP.git
cd StravaMCP

# Install dependencies
bun install

# Copy environment template
cp .env.example .env

# Edit .env with your Strava credentials
# STRAVA_CLIENT_ID=your_client_id
# STRAVA_CLIENT_SECRET=your_client_secret
# STRAVA_REFRESH_TOKEN=your_refresh_token

Get Strava Refresh Token

node get-token.js YOUR_CLIENT_ID YOUR_CLIENT_SECRET

This opens your browser for Strava authorization and displays your refresh token.

Local Development Server

# Start development server (with auto-reload)
bun run dev

# Server runs at http://localhost:3000
# MCP endpoint: http://localhost:3000/mcp

The local server uses JSON-RPC over HTTP for MCP connections.


Architecture

Mermaid Diagrams: The architecture diagrams below use Mermaid.js for interactive visualization. They render automatically on GitHub Pages and can be edited directly in the markdown.

System Overview

graph TB
    subgraph "MCP Clients"
        A[Claude Desktop]
        B[Claude Web]
        C[Claude Mobile]
    end
    
    subgraph "AWS Lambda"
        D[Lambda Handler<br/>lambda-web.ts]
        E[Bearer Token<br/>Middleware]
        F[MCP Server<br/>@modelcontextprotocol/sdk]
    end
    
    subgraph "Strava Integration"
        G[StravaClient<br/>OAuth Handler]
        H[MCP Tools<br/>11 Strava APIs]
    end
    
    subgraph "Strava API"
        I[Strava v3 API<br/>developers.strava.com]
    end
    
    A --> D
    B --> D
    C --> D
    D --> E
    E --> F
    F --> H
    H --> G
    G --> I
    
    style D fill:#FF6B6B
    style F fill:#4ECDC4
    style G fill:#45B7D1
    style H fill:#96CEB4

Component Architecture

graph LR
    subgraph "MCP Layer"
        A[Tool Definitions]
        B[Input Schemas]
        C[Error Handlers]
    end
    
    subgraph "Business Logic"
        D[Activities Tools]
        E[Athlete Tools]
        F[Streams Tools]
        G[Clubs Tools]
        H[Uploads Tools]
    end
    
    subgraph "API Layer"
        I[StravaClient]
        J[Token Refresh]
        K[HTTP Client]
    end
    
    subgraph "External"
        L[Strava API]
    end
    
    A --> D
    A --> E
    A --> F
    A --> G
    A --> H
    
    D --> I
    E --> I
    F --> I
    G --> I
    H --> I
    
    I --> J
    I --> K
    K --> L
    
    style I fill:#FF6B6B
    style J fill:#FFD93D

Project Structure

StravaMCP/
├── src/
│   ├── lambda-web.ts              # Lambda entry point (Streamable HTTP)
│   ├── index.ts               # Local dev server (JSON-RPC over HTTP)
│   ├── lib/
│   │   └── strava-client.ts   # OAuth client with auto-refresh
│   ├── tools/                 # MCP tool implementations
│   │   ├── activities.ts      # Activity CRUD operations
│   │   ├── athlete.ts         # Athlete profile & stats
│   │   ├── streams.ts         # Telemetry data access
│   │   ├── clubs.ts           # Club activities
│   │   └── uploads.ts         # File upload handling
│   ├── config/
│   │   ├── env.ts             # Environment variables
│   │   └── types.ts           # TypeScript type definitions
│   └── utils/
│       ├── errors.ts          # Error handling utilities
│       └── formatters.ts      # Response formatting
├── scripts/
│   ├── deploy.ts              # Automated deployment script
│   └── show-config.ts         # Display MCP configuration
├── docs/                      # GitHub Pages documentation
│   ├── index.md               # Home page
│   ├── deployment.md          # Deployment guide
│   ├── authentication.md      # Auth guide
│   ├── api.md                 # API reference
│   ├── examples.md            # Usage examples
│   ├── freetier.md            # AWS Free Tier guide
│   ├── development.md         # This file
│   └── changelog.md           # Version history
├── template.yaml              # AWS SAM CloudFormation template
├── tsconfig.json              # TypeScript configuration
├── package.json               # Dependencies and scripts
├── get-token.js               # OAuth token retrieval helper
└── README.md                  # Project overview

Key Components

1. Lambda Handler (src/lambda-web.ts)

The Lambda entry point implements:

  • Streamable HTTP Transport for remote MCP
  • Bearer Token Authentication middleware
  • MCP Server Integration via @modelcontextprotocol/sdk
  • JSON-RPC over HTTP for tool communication

Key Functions:

  • handler() - Main Lambda handler
  • Authentication middleware validates Authorization: Bearer <token>
  • Routes requests to MCP server
  • Streams responses back to client

2. Strava Client (src/lib/strava-client.ts)

OAuth 2.0 client with automatic token management:

Features:

  • Automatic Token Refresh - Refreshes 5 minutes before expiry
  • Thread-Safe - Prevents concurrent refresh attempts
  • Generic Request Method - Supports all Strava API endpoints
  • Type-Safe - Full TypeScript support

Methods:

  • request<T>(method, endpoint, data?, config?) - Generic API request
  • get<T>(endpoint, config?) - GET request
  • post<T>(endpoint, data?, config?) - POST request
  • put<T>(endpoint, data?, config?) - PUT request
  • delete<T>(endpoint, config?) - DELETE request

Critical: Never implement your own token refresh logic. StravaClient handles this automatically and safely.

3. MCP Tools (src/tools/*.ts)

All tools follow this structure:

import { z } from 'zod';
import { StravaClient } from '../lib/strava-client.js';
import { withErrorHandling } from '../utils/errors.js';

// 1. Define Zod schema for validation
export const MyToolSchema = z.object({
  param1: z.string().describe('Parameter description'),
  param2: z.number().optional().describe('Optional parameter'),
});

// 2. Implement tool function
export const myTool = withErrorHandling(
  async (client: StravaClient, params: z.infer<typeof MyToolSchema>) => {
    return await client.get<ResponseType>('/endpoint', { params });
  }
);

// 3. Define MCP tool metadata
export const myToolsDefinition = [
  {
    name: 'my_tool',
    description: 'Detailed description for AI agents...',
    inputSchema: {
      type: 'object',
      properties: {
        param1: { type: 'string', description: 'Parameter description' },
        param2: { type: 'number', description: 'Optional parameter' },
      },
      required: ['param1'],
    },
  },
];

4. Deployment System

scripts/deploy.ts:

  • Auto-generates AUTH_TOKEN if not present
  • Runs sam build && sam deploy --guided
  • Displays formatted MCP configuration
  • Saves configuration to samconfig.toml

scripts/show-config.ts:

  • Reads AUTH_TOKEN from samconfig.toml
  • Fetches Function URL from CloudFormation
  • Displays complete MCP client configuration

Adding a New Tool

Follow these steps to add a new Strava API tool:

Step 1: Create Tool File

Create src/tools/my-new-tool.ts:

import { z } from 'zod';
import { StravaClient } from '../lib/strava-client.js';
import { withErrorHandling } from '../utils/errors.js';

// Define input schema
export const GetMyDataSchema = z.object({
  id: z.number().describe('Resource ID'),
  detailed: z.boolean().optional().describe('Include detailed info'),
});

// Implement function
export const getMyData = withErrorHandling(
  async (client: StravaClient, params: z.infer<typeof GetMyDataSchema>) => {
    const { id, detailed } = params;
    return await client.get(`/my-endpoint/${id}`, {
      params: { detailed },
    });
  }
);

// Export tool definition
export const myNewTools = [
  {
    name: 'get_my_data',
    description: `Retrieves data from Strava endpoint.
    
    Use this tool when you need to access specific resource data.
    
    Parameters:
    - id: The resource identifier
    - detailed: Set to true for complete information
    
    Returns detailed resource information including metadata.`,
    inputSchema: {
      type: 'object' as const,
      properties: {
        id: {
          type: 'number' as const,
          description: 'Resource ID',
        },
        detailed: {
          type: 'boolean' as const,
          description: 'Include detailed information',
        },
      },
      required: ['id'],
    },
  },
];

Step 2: Register Tool

Add to src/lambda-web.ts (Lambda) and src/index.ts (local dev):

import { myNewTools } from './tools/my-new-tool.js';

// In setupServer() function
server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    ...activitiesTools,
    ...athleteTools,
    // ... other tools
    ...myNewTools, // Add your new tool
  ],
}));

Step 3: Add Types

Update src/config/types.ts if needed:

export interface MyDataResponse {
  id: number;
  name: string;
  data: any;
}

Step 4: Document Tool

Add to docs/api.md:

### get_my_data

Retrieves data from Strava endpoint.

**Parameters**:
- `id` (number, required): Resource ID
- `detailed` (boolean, optional): Include detailed information

**Example**:
```json
{
  "id": 123456,
  "detailed": true
}

Response: Resource data with metadata


### Step 5: Test

```bash
# Local test
bun run dev

# Deploy
bun run build:lambda
bun run deploy:fast

Development Workflow

Local Development

  1. Start dev server:
    bun run dev
    
  2. Configure Claude Desktop to connect to local server:
    {
      "mcpServers": {
        "strava-local": {
          "url": "http://localhost:3000/mcp"
        }
      }
    }
    
  3. Make changes and test (server auto-reloads)

Testing Lambda Locally

# Test Lambda handler locally
bun run dev:lambda

# Or use SAM
sam build
sam local invoke

Build and Deploy

# Full build
bun run build:lambda

# Deploy with prompts
bun run deploy

# Quick deploy (uses saved config)
bun run deploy:fast

# View configuration
bun run deploy:show-config

Important Patterns

OAuth Token Management

Never implement custom token refresh logic

The StravaClient handles token refresh automatically:

  • Refreshes 5 minutes before expiry
  • Thread-safe (prevents concurrent refreshes)
  • Retries failed requests with fresh token

Error Handling

All tools use withErrorHandling wrapper:

export const myTool = withErrorHandling(
  async (client, params) => {
    // Implementation
  }
);

This provides:

  • Consistent error format
  • Proper error logging
  • Graceful failure handling

Logging Convention

Critical: Use console.error() for ALL logging

// ✅ Correct
console.error('[StravaClient] Token refreshed');
console.error('[MyTool] Processing request:', params);

// ❌ Wrong - breaks MCP protocol
console.log('This will break MCP communication');

Why? MCP protocol uses stdout for communication. Logging to stdout will corrupt the protocol messages.


Testing

Unit Tests

Coming soon - test framework setup in progress

Integration Testing

Test deployed Lambda function:

# Get your AUTH_TOKEN and URL
bun run deploy:show-config

# Test health endpoint
curl -H "Authorization: Bearer <token>" <function-url>/health

# Test with Claude
# Configure Claude Desktop and try: "Get my recent activities"

Manual Testing Checklist

  • Local dev server starts without errors
  • Lambda deployment succeeds
  • Health check returns 200 OK
  • Claude Desktop can connect
  • All 11 tools are listed
  • Sample tool execution works
  • OAuth token refresh works
  • Error handling returns proper messages

Common Issues

“sam: command not found”

Install AWS SAM CLI:

brew install aws-sam-cli

“Unable to locate credentials”

Configure AWS CLI:

aws configure

OAuth Token Expired

Re-run token script:

node get-token.js YOUR_CLIENT_ID YOUR_CLIENT_SECRET

Update deployment:

sam deploy --parameter-overrides StravaRefreshToken=NEW_TOKEN

TypeScript Errors

Ensure dependencies are installed:

bun install
bun run typecheck

Code Style

TypeScript

  • Strict mode enabled - No implicit any
  • Use explicit types for function parameters and returns
  • Prefer interfaces for object shapes
  • Use const for immutable values

Naming Conventions

  • Files: kebab-case (strava-client.ts)
  • Functions: camelCase (getActivities)
  • Types: PascalCase (ActivitySummary)
  • Constants: UPPER_SNAKE_CASE (STREAM_TYPES)

Documentation

  • JSDoc comments for all public functions
  • Parameter descriptions for all Zod schemas
  • Detailed tool descriptions for AI agents

Deployment Best Practices

Environment Variables

Always use environment variables for:

  • API credentials (Strava tokens)
  • Auth tokens (AUTH_TOKEN)
  • Configuration values

Define in template.yaml and load in src/config/env.ts.

Security

  • Never commit .env or samconfig.toml
  • Use gitignore for sensitive files
  • Rotate tokens periodically
  • Review CloudWatch logs for suspicious activity

Performance

  • Minimize cold starts - Keep Lambda warm if needed
  • Optimize bundle size - Only include necessary dependencies
  • Use ARM64 architecture - 20% better performance

Contributing

See CONTRIBUTING.md for contribution guidelines.


Resources


Need help? Open an issue on GitHub!